verilog-HDL基础
函数与任务
将Verilog HDL模块中重复使用的代码段定义为子程序,既能够使描述代码具有更好的可阅读性、可维护性和可移植性,又能够减少出错的概率。
Verilog HDL支持两种形式的子程序:函数(functions)和任务(tasks)。函数和任务既可以由系统定义,也可以由用户定义。
函数是具有独立运算功能的单元电路,每次调用根据输入重新计算输出结果。 在Verilog HDL中,函数定义以关键词function开始,以关键词endfunction结束。
函数定义的语法格式为function [位宽] 函数名;输入端口声明;语句1;语句2;...endfunction
在Verilog HDL中,对函数的调用是通过函数名完成的。函数名在函数体中代表一个变量,函数调用的返回值是通过函数名传递给调用语句。
函数调用的语法格式为 <线网/变量名> = <函数名> ( 线网/变量1 ,..., 线网/变量n )
module dec3_8(d,y);input [2:0] d;output reg [7:0] y;always @(d)begin y[3:0] = func_dec2_4(~d[2],d[1:0]); y[7:4] = func_dec2_4( d[2],d[1:0]);end
2线-4线 译码函数
function [3:0] func_dec2_4; // 函数定义 input en;input [1:0] a;if (en)case (a)2'b00: func_dec2_4 = 4'b0001;2'b01: func_dec2_4 = 4'b0010;2'b10: func_dec2_4 = 4'b0100;2'b11: func_dec2_4 = 4'b1000;default: func_dec2_4 = 4'b0000;endcaseelsefunc_dec2_4 = 4'b0000;endfunction
endmodule
任务
在Verilog HDL中,任务定义以关键词task开始,以关键词endtask结束。
任务定义的语法格式为task 任务名;输入/输出端口声明;语句1;...语句n;endtask
任务调用的语法格式为任务名 (线网/变量1,...,线网/变量n);
在定义Verilog任务时,需要注意以下几点: (1) 在task定义语句的首行不列出端口名称; (2) 任务的端口数量没有限制,也可以没有端口; (3) 在task定义中不能包含过程语句。
与函数不同,任务允许有多个输出,并且允许有延迟、时间或事件控制,因此,任务比函数应用更为广泛。
用4位偶校验器实现16位校验的思路是:将输入的16位数据分为四组,每组4位,用4位偶校验器进行校验,然后将4组的校验结果再进行校验,得到16位校验结果。
实现16位偶校验的Verilog参考代码如下:
task even_parity_4b;input [3:0] I;output EP4bout;EP4bout = ^I;endtask
endmodule
module even_parity_16b(D,EPout);input [15:0] D;output EPout;assign EPout = ^D;
endmodule
编译指令
Verilog HDL支持编译指令,对模块进行编译时先对编译指令进行处理,然后再对代码进行编译。
(1) 宏定义指令:`define,`undef;(2) 条件编译指令:`ifdef,`else,`endif;(3) 文件包含指令:`include;(4) 时间尺度指令:`timescale;(5) `default_nettype;(6) `resetall;(7) `line;(8) `unconnect_drive,`nounconnected-drive;(9) `celldefine,`endcelldefine。
宏定义指令是文本宏替换指令,用指定的标识符代表字符串,以增加代码的可阅读性和可维护性。 宏定义指令应用的语法格式为: `define 标识符(宏名) 字符串(宏内容)
应用宏定义指令时,需要注意以下三点: (1)“`define”是编辑指令,不是HDL语句,所以不需要在行末加分号。(2) “`define”用于定义全局符号量,既可以在模块内定义,也可以在模块外定义,对同时编译的模块均有效。(3)宏定义指令的作用范围是从编译器遇到该指令开始直到编译结束,或者遇到指令“`undef”后失效。(4)“`define”定义的全局符号量,引用时必须在标识符前加反引号“`”,表示该标识符为宏名。
条件编译指令“`ifdef”用于对代码进行选择性编译。当定义的条件满足时对部分代码段进行编译,不满足时不编译,或者对其它代码段进行编译。 条件编译指令有两种基本应用形式。具体的语法格式为:`ifdef 宏名 代码段 `endif 或者 `ifdef 宏名 代码段1 `else 代码段2 `endif
wire a,b;
wire y;
`define OPMODE
`ifdef OPMODE // 如果定义了“OPMODE”assign y = a & b ; // 应用数据流实现
`else // 否则and U_and (y,a,b); // 应用基元实现
`endif
文件包含指令“`include”用于将指定文件的代码复制到当前文件中,一起进行编译。
文件包含指令应用的语法格式为 `include “<被包含的文件名>”
使用文件包含指令应注意以下三点: (1)一条“`include”指令只能包含一个文件。有多个文件需要包含时,需要用多条“`include”进行描述; (2)被包含的文件需要写出完整的文件名信息,包括文件类型名; (3)被包含的文件名中可以含有文件的绝对路径或者相对路径,但建议只包含文件名,不含有文件路径,以增强代码的可移植性。
时间尺度指令“`timescale”用来定义模块仿真的时间单位和时间精度
在“`timescale”指令中,用“数字+单位”来定义时间单位和时间精度,其中数字必须为整数,并且有效数字为1、10和100,单位用s、ms、us、ns、ps或者fs表示。时间单位和参量值如表2-16所示。
编写 testbench
如果需要对设计好的模块进行仿真分析,以验证其功能的正确性,那么需要为模块编写测试平台文件(testbench),为被测模块施加激励信号,例化被测模块,调用仿真软件计算被测模块的输出,并输出仿真结果供设计者进行分析。
测试平台文件testbench有两个不同于可综合模块的特点:(1) testbech既没有输入,也没有输出,而是将被测模块的输入定义为内部寄存器变量,将被测模块的输出定义为内部线网信号;(2) testbech用于仿真分析,不需要综合,因此可以使用Verilog HDL定义的所有语句来编写代码,而不像设计功能模块一样,只能应用可综合的语句进行描述。
在数字系统测试中,testbench与被测模块分开进行设计。仿真时,testbench调用仿真软件,传递输入激励并接收输出的线网信号。
测试平台文件testbench的基本结构为:
`timescale 仿真时间单位/仿真精度
module module_vlg_tst();// testbench没有输入输出端口reg 变量名1,...,变量名n; // 将被测模块的输入作为变量进行定义wire 线网名1,...,线网名m; // 将被测模块的输出作为线网进行定义应用过程语句initial/always描述激励信号波形;例化被测模块,传递输入激励并接收输出;调用系统任务(或函数)显示信号波形;
endmodule
Verilog HDL预定义了许多系统任务与函数,用户可以在testbench中直接应用系统任务和函数实现一些特定的功能。这些任务和函数均以“$”开头,如$display、$time和$monitor等,以区分用户定义的任务和函数。
系统任务$display和$write用于在控制台上显示字符信息。$display和$write的区别在于$display在显示完信息后会自动换行,而$write不会。
调用系统任务$display和$write的语法格式为 $display(“格式控制字符串”,输出参数列表); $write(“格式控制字符串”,输出参数列表);
系统任务$monitor用来监测和显示任何指定的线网/变量或者表达式的值。调用系统任务$monitor的语法格式为 $monitor(“格式控制字符串”,参数1, …,参数n);
系统函数$time用于返回当前的仿真时间。$monitor ($time,"rxd=%b txd=%b",rxd,txd);
$timeformat任务调用的语法格式如下: $timeformat (时间单位数,时间精度,后缀,最小宽度);
系统任务$finish用于结束当前仿真。执行$finish任务时,结束当前仿真并退出。
4选一数据选择器:
module MUX4to1(d0,d1,d2,d3,a,y);input d0,d1,d2,d3;input [1:0] a;output reg y;// 功能描述always @(d0,d1,d2,d3,a)case (a)2'b00: y = d0;2'b01: y = d1;2'b10: y = d2;2'b11: y = d3;default: y = d0;endcase
endmodule