HDL4SE:软件工程师学习Verilog语言

Posted 饶先宏

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HDL4SE:软件工程师学习Verilog语言相关的知识,希望对你有一定的参考价值。

4 模拟器

总是不能运行一个应用程序,对学习语言是致命的,一个Hello, World!级别的应用就这么复杂,时间长了会把人的耐心磨尽。因此本节我们先暂停对verilog语言的学习,来讨论模拟器的实现,试图给出一个初步的实现,至少能够完成前面一节中给出的应用。当然,编译器还没有那么快,我们就用手工编译好了,好在这个应用的逻辑不算复杂,手工编译(相当于c语言下写汇编)也还是可以接受的,顺便也看看编译器要输出什么样的结果,模拟器才能接受并运行。
本节经过努力,终于让例子在模拟器上跑起来了,可以在windows和linux上编译运行,按说在Mac上也应该可以,没有进行更多的测试了。这是跑的界面,使用F1–F10来模拟10个按键,按键的状态绘制在数码管下面,红色表示按下,绿色表示弹起,实际实现时,通过按F1–F10来切换按下和弹起,F3按下才会计数:

下面是实际跑起来的画面,左边是Windows,右边是Linux,中间的显示器接到Linux上:

本节中有大段大段的代码,建议用电脑看效果好一些,最好按照后面的指引将代码下载一下来编译运行,体会一下其中的运行过程。
我们首先还是先用verilog语言把前面的应用做完,然后再讨论如何运行它。

4.1 译码器的实现

前面我们已经有了主模块,计数器模块,现在还差一个译码模块,就是把计数值翻译到数码管的控制信号。前面说到,一个数码管靠8个位来控制,ABCDEFG,小数点分别对应其中的第0位到第7位。每一位为1就点亮对应的LED段,0则不点亮。我们这个应用中只显示计数值,因此小数点是不用的,可以一直设置为0,一个数码管显示一个10进制数字,对应关系如下(如果输入不是一个十进制数字,我们可以显示一个E,表示出错了):

NDPGFEDCBAV
0001111118’b00111111
1000001108’b00000110
2010110118’b01011011
3010011118’b01001111
4011001108’b01100110
5011011018’b01101101
6011111018’b01111101
7000001118’b00000111
8011111118’b01111111
9011011118’b01101111
E011110018’b01111001

如果用c语言写,这个是比较简单的,一个switch语句就搞定了。verilog中有对应的语句,就是case语句,具体的语法以及如何编译的讨论我们后面的章节再详细介绍。这里先直接用着,软件工程师靠猜也该知道怎么回事:

module dec2seg(input [3:0] dec, output [7:0] seg);
wire [3:0] dec;
reg [7:0] seg;
always @(dec) 
  case (dec)
    4'd0:seg = 8'b00111111;
    4'd1:seg = 8'b00000110;
    4'd2:seg = 8'b01011011;
    4'd3:seg = 8'b01001111;
    4'd4:seg = 8'b01100110;
    4'd5:seg = 8'b01101101;
    4'd6:seg = 8'b01111101;
    4'd7:seg = 8'b00000111;
    4'd8:seg = 8'b01111111;
    4'd9:seg = 8'b01101111;
    default:seg = 8'b01111001;
  endcase
endmodule  

这个实现看着很简洁,case语句可以用两种方式表示,一种是编译成一个多路选择器外加11个常数基本单元,另一种对返回都是常数的多路选择器干脆编译成一个ROM。
我们先来考虑基本单元如何表示,如何在我们的模拟系统中表达出来。

4.2 基本单元的表达及系统库

每个FPGA或ASIC都有自己的基本单元库。比较复杂的基本单元,开发工具还提供所谓的IP生成工具来根据用户的配置参数生成代码,这些代码可以是verilog代码,其中包括完整的逻辑实现,还能够设置包括一些时序参数以供仿真工具进行仿真,还有一些额外的信息用来指导编译工具(综合工具)如何连接到硬件实现的基本单元上去。常见的比如RAM,FIFO,DSP单元等等。比如下面的verilog代码,就是Altera(被Intel收购了)的FPGA开发工具Quartus II生成的一个8位16选一的多路选择器:

/* 
前面有一大段注释,大概是说这个模块的名称是 LPM_MUX
模拟库在lpm中。有些综合工具似乎把综合工具使用的一些
提示信息放在注释中。
*/
// synopsys translate_off
`timescale 1 ps / 1 ps
// synopsys translate_on
module mux4x8 (
	data0x,
	data10x,
	data11x,
	data12x,
	data13x,
	data14x,
	data15x,
	data1x,
	data2x,
	data3x,
	data4x,
	data5x,
	data6x,
	data7x,
	data8x,
	data9x,
	sel,
	result);

	input	[7:0]  data0x;
	input	[7:0]  data10x;
	input	[7:0]  data11x;
	input	[7:0]  data12x;
	input	[7:0]  data13x;
	input	[7:0]  data14x;
	input	[7:0]  data15x;
	input	[7:0]  data1x;
	input	[7:0]  data2x;
	input	[7:0]  data3x;
	input	[7:0]  data4x;
	input	[7:0]  data5x;
	input	[7:0]  data6x;
	input	[7:0]  data7x;
	input	[7:0]  data8x;
	input	[7:0]  data9x;
	input	[3:0]  sel;
	output	[7:0]  result;

	wire [7:0] sub_wire0;
	wire [7:0] sub_wire17 = data15x[7:0];
	wire [7:0] sub_wire16 = data14x[7:0];
	wire [7:0] sub_wire15 = data13x[7:0];
	wire [7:0] sub_wire14 = data12x[7:0];
	wire [7:0] sub_wire13 = data11x[7:0];
	wire [7:0] sub_wire12 = data10x[7:0];
	wire [7:0] sub_wire11 = data9x[7:0];
	wire [7:0] sub_wire10 = data8x[7:0];
	wire [7:0] sub_wire9 = data7x[7:0];
	wire [7:0] sub_wire8 = data6x[7:0];
	wire [7:0] sub_wire7 = data5x[7:0];
	wire [7:0] sub_wire6 = data4x[7:0];
	wire [7:0] sub_wire5 = data3x[7:0];
	wire [7:0] sub_wire4 = data2x[7:0];
	wire [7:0] sub_wire3 = data1x[7:0];
	wire [7:0] result = sub_wire0[7:0];
	wire [7:0] sub_wire1 = data0x[7:0];
	wire [127:0] sub_wire2 = sub_wire17, sub_wire16, sub_wire15, sub_wire14, sub_wire13, sub_wire12, sub_wire11, sub_wire10, sub_wire9, sub_wire8, sub_wire7, sub_wire6, sub_wire5, sub_wire4, sub_wire3, sub_wire1;

	lpm_mux	LPM_MUX_component (
				.data (sub_wire2),
				.sel (sel),
				.result (sub_wire0)
				// synopsys translate_off
				,
				.aclr (),
				.clken (),
				.clock ()
				// synopsys translate_on
				);
	defparam
		LPM_MUX_component.lpm_size = 16,
		LPM_MUX_component.lpm_type = "LPM_MUX",
		LPM_MUX_component.lpm_width = 8,
		LPM_MUX_component.lpm_widths = 4;


endmodule
// ============================================================
// CNX file retrieval info
// ============================================================
// Retrieval info: PRIVATE: INTENDED_DEVICE_FAMILY STRING "Cyclone IV GX"
// Retrieval info: PRIVATE: SYNTH_WRAPPER_GEN_POSTFIX STRING "0"
// Retrieval info: PRIVATE: new_diagram STRING "1"
// Retrieval info: LIBRARY: lpm lpm.lpm_components.all
// Retrieval info: CONSTANT: LPM_SIZE NUMERIC "16"
// Retrieval info: CONSTANT: LPM_TYPE STRING "LPM_MUX"
// Retrieval info: CONSTANT: LPM_WIDTH NUMERIC "8"
// Retrieval info: CONSTANT: LPM_WIDTHS NUMERIC "4"
// Retrieval info: USED_PORT: data0x 0 0 8 0 INPUT NODEFVAL "data0x[7..0]"
// Retrieval info: USED_PORT: data10x 0 0 8 0 INPUT NODEFVAL "data10x[7..0]"
// Retrieval info: USED_PORT: data11x 0 0 8 0 INPUT NODEFVAL "data11x[7..0]"
// Retrieval info: USED_PORT: data12x 0 0 8 0 INPUT NODEFVAL "data12x[7..0]"
// Retrieval info: USED_PORT: data13x 0 0 8 0 INPUT NODEFVAL "data13x[7..0]"
// Retrieval info: USED_PORT: data14x 0 0 8 0 INPUT NODEFVAL "data14x[7..0]"
// Retrieval info: USED_PORT: data15x 0 0 8 0 INPUT NODEFVAL "data15x[7..0]"
// Retrieval info: USED_PORT: data1x 0 0 8 0 INPUT NODEFVAL "data1x[7..0]"
// Retrieval info: USED_PORT: data2x 0 0 8 0 INPUT NODEFVAL "data2x[7..0]"
// Retrieval info: USED_PORT: data3x 0 0 8 0 INPUT NODEFVAL "data3x[7..0]"
// Retrieval info: USED_PORT: data4x 0 0 8 0 INPUT NODEFVAL "data4x[7..0]"
// Retrieval info: USED_PORT: data5x 0 0 8 0 INPUT NODEFVAL "data5x[7..0]"
// Retrieval info: USED_PORT: data6x 0 0 8 0 INPUT NODEFVAL "data6x[7..0]"
// Retrieval info: USED_PORT: data7x 0 0 8 0 INPUT NODEFVAL "data7x[7..0]"
// Retrieval info: USED_PORT: data8x 0 0 8 0 INPUT NODEFVAL "data8x[7..0]"
// Retrieval info: USED_PORT: data9x 0 0 8 0 INPUT NODEFVAL "data9x[7..0]"
// Retrieval info: USED_PORT: result 0 0 8 0 OUTPUT NODEFVAL "result[7..0]"
// Retrieval info: USED_PORT: sel 0 0 4 0 INPUT NODEFVAL "sel[3..0]"
// Retrieval info: CONNECT: @data 0 0 8 0 data0x 0 0 8 0
// Retrieval info: CONNECT: @data 0 0 8 80 data10x 0 0 8 0
// Retrieval info: CONNECT: @data 0 0 8 88 data11x 0 0 8 0
// Retrieval info: CONNECT: @data 0 0 8 96 data12x 0 0 8 0
// Retrieval info: CONNECT: @data 0 0 8 104 data13x 0 0 8 0
// Retrieval info: CONNECT: @data 0 0 8 112 data14x 0 0 8 0
// Retrieval info: CONNECT: @data 0 0 8 120 data15x 0 0 8 0
// Retrieval info: CONNECT: @data 0 0 8 8 data1x 0 0 8 0
// Retrieval info: CONNECT: @data 0 0 8 16 data2x 0 0 8 0
// Retrieval info: CONNECT: @data 0 0 8 24 data3x 0 0 8 0
// Retrieval info: CONNECT: @data 0 0 8 32 data4x 0 0 8 0
// Retrieval info: CONNECT: @data 0 0 8 40 data5x 0 0 8 0
// Retrieval info: CONNECT: @data 0 0 8 48 data6x 0 0 8 0
// Retrieval info: CONNECT: @data 0 0 8 56 data7x 0 0 8 0
// Retrieval info: CONNECT: @data 0 0 8 64 data8x 0 0 8 0
// Retrieval info: CONNECT: @data 0 0 8 72 data9x 0 0 8 0
// Retrieval info: CONNECT: @sel 0 0 4 0 sel 0 0 4 0
// Retrieval info: CONNECT: result 0 0 8 0 @result 0 0 8 0
// Retrieval info: GEN_FILE: TYPE_NORMAL mux4x8.v TRUE
// Retrieval info: GEN_FILE: TYPE_NORMAL mux4x8.inc FALSE
// Retrieval info: GEN_FILE: TYPE_NORMAL mux4x8.cmp FALSE
// Retrieval info: GEN_FILE: TYPE_NORMAL mux4x8.bsf FALSE
// Retrieval info: GEN_FILE: TYPE_NORMAL mux4x8_inst.v FALSE
// Retrieval info: GEN_FILE: TYPE_NORMAL mux4x8_bb.v TRUE
// Retrieval info: LIB_FILE: lpm

把前后的注释删掉,可能不会影响后面的编译(综合),但是如果再想用Altera的生成工具打开修改,那就会报错了:

可见其中的注释中还是存放了很多信息的,估计是用来指导Altera的开发工具来编译这个模块的。

这个例子可能不是很合适,因为数据选择开关也许不是所选择的fpga器件中的基本单元。不是很清楚altera的综合工具是如何将lpm_mux单元映射到FPGA内部的基本单元,也许是内部还有一次编译,也许这就是基本单元了。不过不管如何对于用户而言,就不需要写里边的选择器逻辑了,只要直接用就好,altera来保证这个单元实现的正确性和高效性,跟基本单元也就没有多大区别了。
那么,我们的模拟器用什么办法来表示模拟器内的基本单元呢,前面谈过,我们模拟器基于LCOM来做的,因此可以考虑定义基本单元时,用module来定义,然后在module声明前用attribute_instance来说明这是一个HDL4SE模拟器的基本单元,以及实现的LCOM对象的CLSID,使用时将CLSID和实例化参数传输到对象生成例程中,就可以生成一个基本单元的LCOM对象。比如4选1的多路选择器模块,可以这么定义:

(* 
  HDL4SE="LCOM", 
  CLSID="041F3AA1-97CD-4412-9E8E-D04ADF291AE2", 
  softmodule="hdl4se" 
*) 
module  hdl4se_mux4 #(WIDTH=8)
  (
    input [1:0] sel,
    input [WIDTH-1:0] in0,
    input [WIDTH-1:0] in1,
    input [WIDTH-1:0] in2,
    input [WIDTH-1:0] in3,
    output [WIDTH-1:0] data
  );
   reg [WIDTH-1:0] data;
   wire [1:0] sel;
   wire [WIDTH-1:0] in0, in1, in2, in3;
   always @*
     case (sel)
       2'd0: data = in0;
       2'd1: data = in1;
       2'd2: data = in2;
       2'd3: data = in3;
     endcase
endmodule

画成基本单元图就是:
这种定义给出了模拟器中多路选择器的verilog描述,其中的参数WIDTH来定义选择器的数据线宽度。声明前面的attribute_instance中指定HDL4SE=“LCOM"表示这是一个LCOM对象,其CLSID是"041F3AA1-97CD-4412-9E8E-D04ADF291AE2”,softmodule="hdl4se"则表示这个对象实现在hdl4se库中,这个库如果在linux系统中可以用.so文件实现,如果在windows系统中,可以用.dll文件实现。当然也可以编译为静态库,直接连接在模拟器中内置实现,这样就可以不指定softmodule了。
这种定义方式也可以给出实现逻辑供其他的FPGA和ASIC开发平台来使用。对我们的HDL4SE开发平台,已经不需要用户实现了,其中的逻辑,实现在LCOM对象中。编译器发现module定义时指定了HDL4SE="LCOM"和CLSID这样的attribute_instance,就知道这个是基本单元,由软件库(softmodule指定,如果不指定,则有模拟器内置)实现,此时编译器其中的实现逻辑就不再处理。编译成目标代码时直接生成加载对应的库,并根据实例化参数生成LCOM对象。
这种做法给我们很大的想像空间,这样我们可以在体系架构设计之初用c语言或者其他语言,实现很多颗粒度很大的模块作为基本单元,这样可以大大减少体系架构设计之初就要求做很细的RTL实现带来的困难,另外颗粒度大的基本单元可以大幅度减少模拟所需要的时间,可以大幅度提高设计迭代的效率。具体应用中可以把模拟器作为系统的CModel使用,其中的很多模块甚至可以由第三方实现和发布,这种发布可以用二进制库的方式,可以有效保护第三方的知识产权。体系架构设计完成后,再根据具体的目标FPGA平台或ASIC平台进行详细设计和实现。
同样,常数基本单元,我们这么定义:

(* 
   HDL4SE="LCOM", 
   CLSID="8FBE5B87-B484-4f95-8291-DBEF86A1C354",
   softmodule="hdl4se" 
*) 
module hdl4se_const #(WIDTH=8, VALUE=8'b0) 
  (output [WIDTH-1:0] data);
  wire [WIDTH-1:0] data;
  assign data = VALUE;
endmodule

我们不希望你写的verilog源代码中的线网像下面这个样子,让人看不清连接关系:
至少应该是这个样子:


(两张图片都来自于网络)
为此,我们再增加几个线网的合并和拆分基本单元,类似于机房的线缆捆扎器:

(* 
   HDL4SE="LCOM", 
   CLSID="D5152459-6798-49C8-8376-21EBE8A9EE3C",
   softmodule="hdl4se" 
*) 
module hdl4se_split4
	#(INPUTWIDTH=32, 
         OUTPUTWIDTH0=8, OUTPUTFROM0=0, 
         OUTPUTWIDTH1=8, OUTPUTFROM1=8
         OUTPUTWIDTH2=8, OUTPUTFROM2=16, 
         OUTPUTWIDTH3=8, OUTPUTFROM3=24
         ) 
  (
      input [INPUTWIDTH-1:0] wirein
      output [OUTPUTWIDTH0-1:0] wireout0,
      output [OUTPUTWIDTH1-1:0] wireout1,
      output [OUTPUTWIDTH2-1:0] wireout2,
      output [OUTPUTWIDTH3-1:0] wireout3,
    );
  wire [INPUTWIDTH-1:0] wirein;
  wire [OUTPUTWIDTH0-1:0] wireout0;
  wire [OUTPUTWIDTH1-1:0] wireout1;
  wire [OUTPUTWIDTH2-1:0] wireout2;
  wire [OUTPUTWIDTH3-1:0] wireout3;
  assign wireout0 = wirein[OUTPUTWIDTH0+OUTPUTFROM0-1:OUTPUTFROM0];
  assign wireout1 = wirein[OUTPUTWIDTH1+OUTPUTFROM1-1:OUTPUTFROM1];
  assign wireout2 = wirein[OUTPUTWIDTH2+OUTPUTFROM2-1:OUTPUTFROM2];
  assign wireout3 = wirein[OUTPUTWIDTH3+OUTPUTFROM3-1:OUTPUTFROM3];
endmodule

(* 
   HDL4SE="LCOM", 
   CLSID="0234ECE7-A9C5-406B-9AE7-4841EA0DF7C9",
   softmodule="hdl4se" 
*) 
module hdl4se_bind4
	#(
         WIDTH0=8, 
         WIDTH1=8, 
         WIDTH2=8, 
         WIDTH3=8
         ) 
  (
      input [WIDTH0-1:0] wirein0,
      input [WIDTH1-1:0] wirein1,
      input [WIDTH2-1:0] wirein2,
      input [WIDTH3-1:0] wirein3,
      output [WIDTH0+WIDTH1+WIDTH2+WIDTH3-1:0] wireout
    );
  wire [WIDTH0-1:0] wirein0;
  wire [WIDTH1-1:0] wirein1;
  wire [WIDTH2-1:0] wirein2;
  wire [WIDTH3-1:0] wirein3;
  wire [WIDTH0+WIDTH1+WIDTH2+WIDTH3-1:0] wireout;
  assign wireout = wirein3, wirein2, wirein1, wirein0;
endmodule


当然,数字工程师可能从来没有用过这样的模型,因为在FPGA和ASIC设计过程中,这种模型是没有必要存在的,一方面设计过程中直接写线网的部分访问([])或者线网合并运算符号()即可,不需要这样的描述。另一方面FPGA和ASIC的开发工具认为线网和寄存器本质上都是一位一位的,因此在编译过程中所有的组合电路的输出分拆到位的,每一位一个函数生成的,因此也没有这样的基本单元。然而在hdl4se系统中还是有这个必要的,主要是hdl4se要支持大颗粒度的模拟,这样把线网也尽可能做成大颗粒度的,可以提高模拟效率,多股的线缆是有必要作为基本单元出现的,当然线缆抽头和捆扎也就有必要了。

这样前面的译码器,我们可以用hdl4se的基本单元描述出来:

module dec2seg(input [3:0] dec, output [7:0] seg);
  wire [7:0] wire_cst0;
  hdl4se_const #(8, 8'b00111111) const_cst0(wire_cst0);
  wire [7:0] wire_cst1;
  hdl4se_const #(8, 8'b00000110) const_cst1(wire_cst1);
  wire [7:0] wire_cst2;
  hdl4se_const #(8, 8'b01011011) const_cst2(wire_cst2);
  wire [7:0] wire_cst3;
  hdl4se_const #(8, 8'b01001111) const_cst3(wire_cst3);
  wire [7:0] wire_cst4;
  hdl4se_const #(8, 8'b01100110) const_cst4(wire_cst4);
  wire [7:0] wire_cst5;
  hdl4se_const #(8, 8'b01101101) const_cst5(wire_cst5);
  wire [7:0] wire_cst6;
  hdl4se_const #(8, 8'b01111101) const_cst6(wire_cst6);
  wire [7:0] wire_cst7;
  hdl4se_const #(8, 8'b00000111) const_cst7(wire_cst7);
  wire [7:0] wire_cst8;
  hdl4se_const #(8, 8'b01111111) const_cst8(wire_cst8);
  wire [7:0] wire_cst9;
  hdl4se_const #(8, 8'b01101111) const_cst9(wire_cst9);
  wire [7:0] wire_cst10;
  hdl4se_const #(8, 8'b01111001) const_cst10(wire_cst10);

  hdl4se_mux16 #(8) mux_dec(dec, 
    wire_cst0, 
    wire_cst1, 
    wire_cst2, 
    wire_cst3, 
    wire_cst4, 
    wire_cst5, 
    wire_cst6, 
    wire_cst7, 
    wire_cst8, 
    wire_cst9, 
    wire_cst10, 
    wire_cst10, 
    wire_cst10, 
    wire_cst10, 
    wire_cst10, 
    wire_cst10, 
    wire_cst10 
    seg)HDL4SE:软件工程师学习Verilog语言(十四)

HDL4SE:软件工程师学习Verilog语言

HDL4SE:软件工程师学习Verilog语言

HDL4SE:软件工程师学习Verilog语言

HDL4SE:软件工程师学习Verilog语言

HDL4SE:软件工程师学习Verilog语言