【FPGA学习】- Verilog基础语法
Verilog简介
Verilog的全称为Verilog HDL,其中的HDL 是指Hardware Description Language,即硬件描述语言,是对硬件的一种描述方法。数字电路发展初期,由于数字电路器件结构简单,所以设计过程中并不会使用硬件描述语言。然而目前随着设计电路规模的不断扩大,设计人员急需计算机的大力辅助,于是促进了电子设计自动化(Electronic Design Automation,EDA)的出现和发展。Verilog就是在这种情况下出现的,Verilog采用编写代码的方式来设计数字电路,向下可以描述基本逻辑门的连接,向上可以描述电路的整体功能,设计简单,管理方便,维护容易,可以大大提高数字电路的设计速度。
数字电路设计中有两种设计的基本方法:自底向上和自顶向下的设计方法。Verilog编码过程中使用的是自顶向下的设计方法,如图1所示。在设计过程中,设计者首先对于数字电路有一个整体设计,然后将整体设计又拆分为几个子模块,一般按照功能进行拆分。子模块有的还可以进行进一步的划分,然后依次按照最底层到最顶层的方式来实现这些模块,最终就可以完成整体设计。在Verilog设计的顶层代码中,一般看不到功能性描述,而都是模块的实例化语句,全是因为子模块的调用。

图1 自顶向下设计流程
Verilog模块结构与门级建模
模块结构
Verilog使用模块的概念来表示一个基本的功能块。一个模块可以是一个元件,也可以是低层次模块的组合。模块定义以关键字module开始,模块名,端口列表必须出现在其它部分的前面,endmodule必须是模块的最后一条语句。模块内部的组成部分是:端口及变量声明,数据流语句,低层模块实例,行为语句块,任务及函数。这些组成部分可以在模块中的任意位置,以任意顺序出现。
1 module 模块名(端口名1,端口名2,...); 2 3 //端口声明 4 input 端口名1,端口名2,...; 5 output 端口名3,端口名4,...; 6 7 //变量声明 8 wire 变量名1,变量名2,...; 9 reg 变量名3,变量名4,...; 10 11 //数据流语句 12 assign 线网信号名/输出端口名 = 运算表达式; 13 14 //低层模块实例 15 调用模块名 实例名(端口连接); 16 17 //行为语句块 18 initial 19 begin 20 ... 21 end 22 always @(*) 23 begin 24 ... 25 end 26 27 //任务及函数 28 任务名(信号对照列表); 29 待赋值变量 = 函数名称(信号对照列表); 30 31 endmodule
门级建模
**********待更新***********
Verilog模块实例化
在Verilog中,模块的实例化就是对已有模块的调用,而实例化的关键在于端口的连接。
端口
端口就是模块与外界交互的接口。对于外部环境来讲,模块内部不可见,对于模块的调用只能通过端口来进行。在模块的定义中包含一个可选的端口列表,如图2所示。如果模块与外部环境没有交换任何信号,则可以没有端口列表。
![]()
图2 端口列表
端口声明
端口列表中的所有端口必须在模块中进行声明,在Verilog中,端口有三种类型:
| Verilog关键字 | 端口类型 |
| input | 输入端口 |
| output | 输出端口 |
| inout | 输入/输出端口 |
在Verilog中,所有的端口隐含的声明为wire类型,因此如果希望端口具有wire数据类型,则将其声明为三种类型之一;如果输出类型的端口需要保存数值,则必须将其显式地声明为reg数据类型。(input和inout不能够声明为reg类型,这是因为reg类型变量是用来保存数值的,而输入端口只反映与其相连的外部信号的变化。)
端口定义时,默认一位宽度,即只能传播一位信号,如果定义的端口中包含多位信息,则需要按照一下的语法指定端口的宽度。
1 端口类型 [端口宽度-1,0] 端口名;
eg. input[7, 0] a; //输入端口a的宽度为8位。
端口连接规则
端口可以看成是由相互连接的两个部分组成。一部分位于模块内部,一部分位于模块外部,端口在实例引用的过程中,端口之间的连接必须遵守一些规则。如图3所示。
1.输入端口:从模块内部来讲,输入端口必须是线网数据类型;从模块外部来讲,输入端口可以连接到线网或寄存器数据类型的变量。
2.输出端口:从模块内部来讲,输出端口可以是线网或寄存器数据类型;从模块外部来讲,输出端口必须连接到线网数据类型的变量。
3.输入/输出端口:无论在模块内部还是外部,都必须是线网数据类型。
4.位宽匹配:在实例引用的时候,Verilog建议端口的内外两部分位宽一致,虽然也允许位宽不同,但是会给予警告。
5.未连接端口:Verilog允许模块实例的端口保持未连接的状态。

图3 端口连接规则
端口与外部信号的连接
1.按顺序连接方式:按照原模块的端口列表中给出的顺序,依次将定义好的连线连接在端口上。有时候模块中的端口无需连接,此时只需要把端口位置留成空白即可。优点是形式简单,使用方便。
moduel example(A, B, C); //原模块 example m1(a, b, c); //以顺序连接方式调用 example m2(a, , c); //b端口悬空,不接入电路
2.按名称连接方式:端口和外部信号按照其名字进行连接。使用的语法为:.原模块的端口名称(模块调用后连接的连线名称),不需要连接的端口只需要忽略掉即可。优点是只要端口名字不变,模块端口顺序发生改变,实例的端口连线也无需调整。
moduel example(A, B, C); //原模块 example m1(.B(b), .C(c), .A(a) ); //以名称连接方式调用 example m2(.A(a), .C(c)); //b端口悬空,不接入电路
实例化示例以及语法
模块实例化的语法格式如下:模块名称 实例名称(端口连接);
//示例 module X(A, B, C); //原模块 input A, B; output C; assign C = A & B; endmodule module x(a, b, c); input a, b; output c; X x1(a, b, c); //按顺序方式连接 X x2(.C(c), .A(a), .B(b)); //按名称方式连接 endmodule
RTL建模
RTL(Register Transfer Level,寄存器传输级):不关注寄存器和组合逻辑的细节(如使用了多少逻辑门,逻辑门之间的连接拓扑结构等),通过描述寄存器与寄存器之间的逻辑功能来描述电路的HDL层次。RTL级是比门级更高的抽象层次,使用RTL级语言描述硬件电路一般比门级描述简单高效得多。
RTL级的重要特点是:可综合。综合即是指将原理图或者HDL语言描述的电路转换成逻辑门的连接,门级网表。RTL级综合指将RTL级源码翻译并优化为门级网表。
一般的RTL级的设计包含三个部分:时钟域描述、时序逻辑描述、组合逻辑描述。在RTL级推荐的设计步骤如下:
1.功能定义与模块拆分。
2.定义所有模块接口。
3.设计时钟域:第一注意全局时钟资源几乎没有时钟偏斜(Clock Skew)但时延(Clock Delay)大,驱动能力强;第二全局时钟偏斜小,但时延小,驱动能力次之。
4.考虑设计中的关键路径:关键路径即时序要求最紧张的路径,主要由频率、建立时间(Tsetup)、保持时间(Thold)等制约,同时可以用pipeline或者逻辑复用等方法缓解。
5.顶层设计:推荐使用自顶向下的设计,这同模块规划是一致的。
6.FSM状态机。
7.时序逻辑设计。
8.组合逻辑设计。
操作数与操作符
RTL级建模使用表达式而非门级原语来描述设计。表达式由操作符和操作数构成,其目的是根据操作符的意义计算出一个结果值。
eg. addr1[20:17] + addr2[20:17]
操作数
操作数的种类包括参数,线网,寄存器,整数和时间。在Verilog中,一般有四种值来表示电路中的电平逻辑。
| 0 | 逻辑0或假 |
| 1 | 逻辑1或真 |
| x/X | 未知 |
| z/Z | 高阻 |
数据类型
线网(wire):wire 类型表示硬件单元之间的物理连线,由其连接的器件输出端连续驱动。如果没有驱动元件连接到 wire 型变量,缺省值一般为 "Z"。
寄存器(reg):reg类型用来表示存储单元,会保持原来的值,直到被改写。
最常用的数据类型就是wire类型和reg类型,其余的类型可以理解为这两种基本类型的辅助和扩展。
向量:当位宽大于1时,wire或reg就可以声明为向量类型。
wire [3:0] data; //声明4bit的线性变量data reg [31:0] counter; //声明32bit位宽的寄存器变量counter reg [7:0] byte1[3:0]; //声明四个8bit位宽的寄存器变量,表示为byte1[0][7:0],...,byte1[3][7:0]
整数,实数,时间寄存器变量
整数:整数类型用关键字 integer 来声明。位宽一般为32 bit(与编译器有关),不用声明。reg 型变量为无符号数,而 integer 型变量为有符号数。
integer i;//整型变量,一般用于辅助
实数:实数用关键字 real 来声明,可用十进制或科学计数法来表示。默认值为 0。如果将一个实数赋值给一个整数,则只有实数的整数部分会赋值给整数。
real data; //实型变量,用于辅助
时间:Verilog 使用特殊的时间寄存器 time 型变量,对仿真时间进行保存。其宽度一般为 64 bit,通过调用系统函数 $time 获取当前仿真时间。
time sim_time; initial begin #50 sim_time = $time; end
数组:在 Verilog 中允许声明 reg, wire, integer, time, real 及其向量类型的数组。数组维数没有限制。
integer i; wire [7:0] data[3:0]; reg [63:0] counter[64:0][32:0]; i = 32`d0; //将i赋值为32bit位的0 data[2] = 8`b00000010; //将data的第三个元素赋值为8bit的0 conuter[60][30][15:0] = 15`d4; //将标号为counter[60][30]的15~0bit赋值为4
存储器:存储器变量就是一种寄存器数组,可用来描述 RAM 或 ROM 的行为。
reg [7:0] mem[1023:0]; //1k字节的存储器
参数:参数用来表示常量,用关键字 parameter 声明,只能赋值一次。对某个模块中的参数做临时修改,可以使用实例化方式或者层次化方式。
module example(X, Y); parameter size = 8; parameter width = size - 1; endmodule ****************** module test; example #(1, 2) t1(x1, y1); //实例化方法修改参数 endmodule ***************** module test; example t2(x2, y2); //层次化方法修改参数 endmodule module annotate; defparam test.t2.size = 3,test.t2.width = 100; endmodule *****************
字符串:字符串保存在 reg 类型的变量中,每个字符占用一个字节(8bit)。字符串不能多行书写,即字符串中不能包含回车符。
reg [14*8-1:0] str ; initial begin str = "hello,verilog!"; end
数值表示
整数表示方法:常用数值有四种进制形式:二级制,八进制,十进制和十六进制,分别用数字B, O, D, H来表示,不区分大小写。基本的表示格式如下:<数值位宽>`<数值进制><数值>。唯有数值不可缺少,缺省位宽,一般默认为32bit;缺省进制,默认为十进制。位数较多时,可以使用下划线_来提高可读性。负数通常在表示位宽的数字前面加一个减号来表示。
3'b011; 8'ha5; 6'o43; //相当于10011 'o43; //相当于00000000000000000000000000010011 43; //相当于十进制的43,二进制表示为00000000000000000000000000101011 16'b0000_0010_0011_1101; //下划线间隔数字,提高可读性,不改变原有的值
-6'd15; //-15的表示方法
实数表示方法:实数表示方法有十进制和科学计数法。
//十进制表示法 3.1415 0.01 //科学计数法 1.2e4; //大小为12000 1_0001e4; //大小为100010000 1e-3; //大小为0.001
字符串表示方法:字符串是由双引号包起来的字符队列。保存在 reg 类型的变量中,每个字符占用一个字节(8bit)。
操作符
操作符类型
操作符对操作数进行运算并产生一个结果。Verilog中提供了许多类型的操作符,分别是算术,逻辑,关系,等价,按位,缩减,移位,拼接和条件操作符等。操作符按照参与运算的操作数目可以分为单目操作符,双目操作符和多目操作符。下表为所有操作符的符号。
| 操作类型 | 操作符 | 执行的操作 | 操作数的个数 |
| 算术 | + | 加 | 2 |
| - | 减 | 2 | |
| * | 乘 | 2 | |
| / | 除 | 2 | |
| % | 取模 | 2 | |
| ** | 求幂 | 2 | |
| 逻辑 | ! | 逻辑取反 | 1 |
| && | 逻辑与 | 2 | |
| || | 逻辑或 | 2 | |
| 关系 | > | 大于 | 2 |
| < | 小于 | 2 | |
| >= | 大于等于 | 2 | |
| <= | 小于等于 | 2 | |
| 等价 | == | 相等 | 2 |
| != | 不等 | 2 | |
| === | case相等 | 2 | |
| !== | case不等 | 2 | |
| 按位 | ~ | 按位求反 | 1 |
| & | 按位与 | 2 | |
| | | 按位或 | 2 | |
| ^ | 按位异或 | 2 | |
| ^~或~^ | 按位同或 | 2 | |
| 缩减 | & | 缩减与 | 1 |
| ~& | 缩减与非 | 1 | |
| | | 缩减或 | 1 | |
| ~| | 缩减或非 | 1 | |
| ^ | 缩减异或 | 1 | |
| ^~或~^ | 缩减同或 | 1 | |
| 移位 | >> | 右移 | 2 |
| << | 左移 | 2 | |
| >>> | 算术右移 | 2 | |
| <<< | 算术左移 | 2 | |
| 拼接 | { } | 拼接 | 任意 |
| 复制 | { { } } | 复制 | 任意 |
| 条件 | ? : | 条件 | 3 |
算术操作符:如果任意一位为X,那么运算结果全部为X。此外 + 和 - 可以作为单目操作符使用,这时,他们表示操作数的正负。在Verilog中,负数使用二进制补码表示,所以一般用整数或实数表示负数,避免使用<数值位宽>`<数值进制><数值>这样的形式来表示负数。
//使用整数或实数来表示负数 -10 / 5;//值为-2 //使用<数值位宽>`<数值进制><数值>格式表示负数 -’d10 / 5; //值为10的二进制补码 / 5 //这样计算的结果不容易控制,建议尽量不使用此种方法
逻辑操作符:计算的结果是一个1位的值:0表示假,1表示真,X表示不确定。如果一个操作数不为0,则等价于逻辑1(真);如果为0,则等价于逻辑0(假);如果任意一位为X或Z,等价于X,仿真器一般将其作为假来处理。
按位操作符:对两个操作数中的每一位进行按位操作。如果两个操作数不相等,则使用0向左扩展来使得两操作数位宽相等。运算的结果是0,1,X。
缩减操作符:缩减操作符为单目操作符。对这个向量逐位操作,最后产生一个一位的结果。
//A = 4'b1010, B = 4'b0000, C = 4'b001X; //逻辑操作符 A && B; //等于0,相当于(逻辑值 1 &&逻辑值 0) A || C; //等于X,相当于(逻辑值 1 || X) !B; //等于1 //按位操作符 !B; //结果为:4'b1111 A & B; //结果为:4'b0000 A | C; //结果为:4'b101X //缩减操作符 &A; //结果为:1&0&1&0,结果为1'b0 |B; //结果为:0|0|0|0,结果为1'b0
关系操作符:关系操作符的表达式为真,结果为1;表达式为假,结果为0;如果操作数中含有X或Z,结果为X。
等价操作符:逻辑等和逻辑不等的可能逻辑值为0,1,X;case等和case不等的逻辑值为0,1。
| 表达式 | 说明 | 可能的逻辑值 |
| a == b | a等于b, 操作数中有X或Z,则结果不定 | 0, 1, X |
| a != b | a等于b, 操作数中有X或Z,则结果不定 | 0, 1, X |
| a === b | a等于b, 包括X和Z | 0, 1 |
| a !== b | a等于b, 包括X和Z | 0, 1 |
//X = 4'b1010, Y = 4'b1101, Z = 4'b1xxz, M= 4'b1xxz, N = 4'b1xxx; A == B; //结果为逻辑值0 X != Y; //结果为逻辑值1 X == Z; //结果为逻辑值x Z === M; //结果为逻辑值1 Z === N; //结果为逻辑值0 M !== N; //结果为逻辑值1
移位操作符:包括逻辑移位操作符和算术移位操作符。逻辑移位,不考虑符号位,左移时,低位补0,右移时,高位补0;算术右移,左侧均补符号位,即正数补0,负数补1,算术左移,右侧补0。(左移一位就是乘以2,右移一位就是除以2)
拼接操作符:{ },将多个操作数拼接在一起。之间用逗号隔开。
重复操作符:多次拼接同一个操作符。
//A = 1'b1, B = 2'b00, C = 3'b101; {A, B}; //值为3'b100 {A, C, 3'b000}; //值为7'b1101000 {2{A}, 3{B}}; //值为8'b11000000
条件操作符:条件表达式 ?真表达式 :假表达式。首先计算条件表达式的值,如果为真,则计算正表达式;如果为假,则计算假表达式;如果不确定,则两个表达式中的值均计算,然后对两个结果的每一位进行判断,相等则取值,不相等则取X。
操作符优先级

图4 操作符优先级(图片来源:菜鸟教程)
连续赋值语句
assign语句
assign语句是Verilog基本语句中的连续赋值语句,用于对wire(线网)进行赋值。连续赋值语句必须以assign开始,其语法格式如下:
assign 线网信号名 = 运算表达式;
连续赋值语句的左值必须是一个标量或向量线网,或者是模块声明的输出端,绝不可以是向量或向量寄存器。连续赋值语句总是处于激活状态,只要任意一个操作数发生变化,表达式就会立即被重新计算。操作数可以是标量或向量的线网或寄存器,也可以是函数调用。
//普通连续赋值 wire out; assign out = i1 & i2; //隐式连续赋值,线网声明的同时赋值 wire out = i1 & i2; //隐式线网声明,out被仿真器推断为线网 wire i1,i2; assign out = i1 & i2;
延迟
连续赋值语句中的延迟用于控制任意操作数发生变化到语句左值被赋予新值之间的时间间隔。
普通赋值延迟:延迟值位于关键字assign的后边。脉冲宽度小于赋值延迟的输入变化不会对输出产生影响。
wire out;
assign #10 out = in1 & in2; //只有在10个时间单位后,out才会被赋新值
隐式连续赋值延迟:使用隐式连续赋值语句来说明对线网的赋值以及赋值延迟。
wire #10 out = i1 & i2;
线网声明延迟:在声明线网的时候指定一个延迟,这样对该线网的任何赋值都会被推迟指定的时间。
wire #10 out; assign out = i1 & i2;
结构化过程语句
在Verilog中,每个initial语句和always语句代表一个独立的执行过程,每个过程仿真时间都从0开始执行,并且这两种语句不可以嵌套使用。
initial语句
所有的位于initial语句内的语句构成了一个initial块。initial块从仿真0时刻开始执行,在整个仿真过程中只执行一次。如果有多个initial块,则每个块的执行是各自独立的。initial内部一般使用begin-end将多个语句组合起来。由于initial语句块在整个仿真期间只能执行一次,因此一般被用于初始化,信号监视,生成仿真波形等目的。
initial m=1'b1; //单条语句,不适用begin-end initial begin m=1'b1; //多条语句使用begin-end n=1'b0; end
always语句
always语句块从仿真0时刻开始顺序执行其中的行为语句;最后一条执行完毕后,再次循环执行其中的第一条语句,如此循环往复,直至整个仿真结束。因此always语句常对数字电路中反复执行的活动建模。always结构中所有语句的左端变量都必须是reg类型。
always #10 clk = ~clk;
时序控制
在Verilog中,提供了两种种时序控制方法,基于延迟的时序控制,基于事件的时序控制。
基于延迟的时序控制:指定了语句从开始执行到执行完成之间的时间间隔。延迟值可以是数字标识符或表达式。
1.常规延迟控制:常规延迟控制位于赋值语句的左边,用于指定一个非零延迟值。
//定义参数 parameter latency = 20; initial begin y = 1; #latency x = y + 1; end //y=1从零时刻开始执行,在第20个时间单位执行x=y+1;
2.内嵌赋值延迟控制:内嵌延迟控制位于赋值符的右边。常规延迟赋值推迟的整个赋值语句的执行,内嵌延迟的是右侧表达式的值赋予左侧值的时间。
initial begin y = 1; x = #latency y + 1; end //在仿真的0时刻取得y的值,计算y+1,等待20个时间单位之后,再将值赋给x,在20个时间单位内,y的值改变不会影响x的值。而常规赋值延迟是会产生影响的
3.零延迟控制:用于保证带有零延迟控制语句在执行时刻相同的多条语句中最后执行。很少使用。
initial begin x = 0; y = 0; end initial begin #0 x = 1; //零延迟控制 #0 y = 1; end //四条赋值语句都在仿真0时刻运行,但零延迟的语句会在最后执行,所以仿真结束x=y=1, 但这两个变量的执行顺序不确定。
基于事件的时序控制:在verilog中,事件是指某个寄存器或者线网变量的值发生了变化。Verilog提供了四种类型的事件控制,常规事件控制,命名事件控制,OR事件控制,电平敏感时序控制。
1.常规事件控制:事件控制使用符号@来说明,语句执行的条件是信号的值发生变化,发生正向跳变和负向跳变。关键字posedge和negedge用于表示正向跳变和负向跳变。(正向跳变:0 -> 1, x, z; x -> 1; z -> 1。负向跳变:1 -> 0, x, z; x -> 0; z -> 0。)
@(clk) a = b; //clk变化,执行a=b @(posedge clk) a = b; //clk发生正向跳变,执行a=b a = @(negedge clk) b; //立即计算b的值,在clk负向跳变的时刻赋值给a
2.命名事件控制:在程序中声明event(事件)类型的变量,触发该变量并进行识别。
event data; initial begin //触发事件 #20 ->data; end always @(data) //等待事件的触发并进行操作 x = 1;
3.OR事件控制:多个信号或者事件中的任意一个都可以触发执行,可以使用关键字or进行连接,也可以使用逗号“,”替代。多个事件名或信号名组成的列表成为敏感列表。其中@(*),表示对always语句块中的所有信号敏感。
always @(clk or rst) begin a = b; end always @(clk, rst) begin a = b; end
4.电平敏感时序控制:Verilog 中还支持使用电平作为敏感信号来控制时序,即后面语句的执行需要等待某个条件为真。Verilog 中使用关键字 wait 来表示这种电平敏感情况。
always wait (clk) a = a + 1; //仿真器连续监视clk的值,只有其值为1时,才执行a =a+1;
过程赋值语句,循环语句和判断语句
过程赋值语句
Verilog中包含两种过程赋值语句:阻塞赋值和非阻塞赋值语句。
1.阻塞赋值:阻塞赋值语句使用“=”作为赋值标志。在顺序块中,一条阻塞语句执行完毕,才会执行下一条阻塞赋值语句。每条语句左侧的值会及时改变,并为之后的语句使用。
2,非阻塞赋值:非阻塞赋值语句使用“<=”作为赋值标志。同一时间,前面语句的赋值不能被后续语句使用。语句块中的赋值是在右侧运算完毕时统一进行的。(先将计算结果存入临时寄存器中,右侧计算完毕后,统一将临时寄存器中的值赋值给左侧的所有变量。)
//阻塞赋值 always @(*) begin e = a + b; f = c + d; out = e * f; end //非阻塞赋值 always @(*) begin e <= a + b; f <= c + d; out <= e * f; end //非阻塞赋值中,out的值不是最新的
3. 顺序块:使用begin-end包含起来的语句块。内部包含的语句顺序执行。
4.并行块:使用fork-join包含起来的语句块。内部包含的语句同时开始执行。由于电路中并不能对并行处理做映射实现,所以并行块很少使用。
循环语句
Verilog中有四种类型的循环语句:while,for,repeat,forever。循环语句只能在alway或者initial语句块中使用。
1.while循环:当判断条件为假时,while循环停止。
while (判断条件) begin 循环体语句; end
2.for循环:for循环的最大特点是简洁。
for(初始化条件;判断条件;变量控制) begin 循环体语句; end
3.repeat循环:将循环体语句执行某些次数。
repaet (次数) begin 循环体语句; end
4.forever循环:表示永远循环。一般和时序控制相结合,否则会陷入死循环。一般用于生成时钟信号。
initial begin clock = 0; forever #10 clock = ~clock; //生成周期为20个时间单位的clock信号 end
判断语句
在always语句结构中可以使用的判断语句中有if语句和case语句两种。
1.if语句:关键字为if...else...,有三种类型。
//无else语句 if(条件) 执行语句; //if...else...语句 if(条件) 执行语句1; else 执行语句2; //多分支if语句 if(条件1) 执行语句1; else if(条件2) 执行语句2; else 执行语句3;
2.case语句:又称多路分支语句,可以实现多个分支。case语句有两个变形,casex和casez,其中casex要慎用。使用casez语句和”?”配合,可以过滤不相关的比特位。
case(表达式) 分支1:语句1; 分支2:语句2; ... default:默认项 endcase //casez使用示例 casez (data) 2'b0?: a=1; 2'b?0: a=0; endcase
任务及函数的使用
在Verilog中,允许设计者将多个地方的相同代码提取出来,编写成任务及函数,以使得代码简洁,易懂。
任务
任务使用关键字task...endtask,进行声明。任务的声明格式如下:
task 任务名称; input [宽度声明] 输入信号名; output [宽度声明] 输出信号名; reg 任务所用变量; begin <任务包含的语句> end endtask
函数
任务使用关键字function...endfunction,进行声明。任务的声明格式如下:
function 返回值的类型和范围 函数名; input [端口范围] 端口声明; reg, integer 变量声明; begin <阻塞赋值语句> end endfunction
任务与函数都可以在模块中被调用,但是两者之间有一些区别,如下表。
| 任务task | 函数function |
| 可以有任意个输入,或者无输入 | 至少要有一个输入 |
| 可以有任意个输出,或者无输出 | 没有output定义的输出 |
| 通过output与外界联系 | 通过函数名与外界联系 |
| 内部可以声明变量,但不能是wire型 | 内部可以声明变量,但不能是wire型 |
| begin...end前没有always和initial语句 | begin...end前没有always和initial语句 |
| begin..end内部语句没有限制 | begin..end内部只能使用阻塞赋值语句,且不能有任何与时间相关的语句 |
| 内部可以调用任务及函数 | 内部只能调用函数,不能调用任务 |
| 直接调用即可 | 调用时需要使用“=”进行赋值 |
常见的系统任务及函数
1.$display:用于单次的显示输出。
2.$monitor:用于多次的监控输出。在仿真开始时执行,然后一直监视输出,当要显示的变量发生变化时,就会执行一次。
3.$stop:停止当前仿真,等待下一步命令。
4.$finish:终止仿真并退出仿真器。
5.$random(seed):用于生成一个32位的有符号的随机整数数值。
6.$fopen("文件名"):用于打开文件,常和$fclose("文件名")组合使用。
7.$readmemb("文件名称",存储器名):用于数据文件对存储器进行初始化。
参考资料
[1] Verilog教程,菜鸟教程,https://www.runoob.com/w3cnote/verilog-tutorial.html。
[2] 数字电路实验教程,Vlab,https://vlab.ustc.edu.cn/guide/doc_verilog.html
[3] 黄海,于斌,Verilog HDL 设计实用教程[M],北京:清华大学出版社,2021.
modelsim安装地址:FPGA Software Download Center (intel.com)

浙公网安备 33010602011771号