【Verilog】系统任务和编译指令
系统任务和编译指令
- 一、系统任务
- 1. 输出类任务: $display $write $strobe $monitor
- 2. 仿真控制类: $stop $finish
- 3. 仿真时间: $time $stime $realtime
- 4. 命令行传参: $test\$plusargs $value\$plusargs
- 二、编译指令
- 1. 宏定义:`define
- 2. 条件编译:`ifdef
- 3. 文件包含:`include
- 4. `default_nettype
- 5. `resetall
- 6. `celldefine
Verilog中的两个特殊概念:系统任务和编译指令。
一、系统任务
Verilog 为某些常用操作提供了标准的系统任务(也称系统函数), 这些操作包括屏幕显示 、线网值动态监视 、 暂停和结束仿真等。所有的系统任务都具有$<keyword>
的形式。
系统任务主要用于仿真调试与控制,而非综合生成硬件电路。
1. 输出类任务: $display $write $strobe $monitor
$display
的使用方法和 C 语言中的 printf 函数非常类似,可以直接打印字符串,也可以在字符串中指定变量的格式对相关变量进行打印。
$display("This is a test."); //直接打印字符串
$display("This is a test number: %b.", num); //打印变量 num 为二进制格式
如果没有指定变量的显示格式,变量值会根据在字符串的位置显示出来,相当于参与了字符串连接。例如:
$display("This is a test number: ", num, "!!!");
常用的输出格式:
在输出浮点数的时候,还可以指定精度:
module top;initial beginreal num = 123.456789;$display("Default precision: %f", num); // 默认6位小数$display("2 decimal places: %.2f", num); // 2位小数$display("5 decimal places: %.5f", num); // 5位小数end
endmodule
此外,还可以使用转义字符显示特殊字符:
$wirte
使用方法与 $display
完全一样,只是前者会在每次显示信息完毕后不会自动换行,后者会自动换行。
当输出后不需要换行时,可以使用显示任务 $write
。
$strobe
的使用方法与 $display
一致,但打印信息的时间和 $display
有所差异。
当许多语句与 $display
任务在同一时间内执行时,这些语句和 $display 的执行顺序是不确定的,一般按照程序的顺序结构执行。
$strobe
则是在其他语句执行完毕之后,才执行显示任务。
示例1:
reg [3:0] a ;initial begina = 1 ;#1 ;a <= a + 1 ;//第一次显示$display("$display excuting result: %d.", a);$strobe("$strobe excuting result: %d.", a);#1 ;$display();//第二次显示$display("$display excuting result: %d.", a);$strobe("$strobe excuting result: %d.", a);
end
执行结果如下:
执行第一次显示任务时,非阻塞赋值与 $display
同时执行,$display
显示赋值之前的变量值,而 $strobe
显示赋值之后的变量值。
示例2:
integer i ;initial beginfor (i=0; i<4; i=i+1) begin$display("Run times of $display: %d.", i);$strobe("Run times of $strobe: %d.", i);end
end
显示结果如下:
$display
按照程序结构,执行显示操作 4 次。而此循环语句是在 0 时刻执行的,所以 $strobe
显示的变量值是循环结束时变量的结果,即 i=4 退出循环后 $strobe
才会执行。
$monitor
为监测任务,用于变量的持续监测。只要变量发生了变化,$monitor
就会打印显示出对应的信息。
reg [3:0] cnt ;
initial begincnt = 3 ;forever begin# 5 ;if (cnt<7) cnt = cnt + 1 ;end
endinitial begin$monitor("Counter change to value %d at the time %t.", cnt, $time);
end
显示的内容如下:
2. 仿真控制类: $stop $finish
$finish
是结束本次仿,$stop
是暂停当前的仿真。仿真暂停后通过 Verilog 仿真工具或命令行还可以使仿真继续进行,而结束仿真后仿真无论如何也不能再进行
示例:
initial beginforever begin#100;if ($time >= 10000) $finish(0) ;//if ($time >= 10000) $finish(1) ;//if ($time >= 10000) $finish(2) ;end
end
$finish(0)
:仿真退出时不打印任何信息。
$finish(1)
:仿真退出时打印仿真时间(单位是ps)和 $finish
所在的行信息
$finish(2)
:仿真退出时不仅打印仿真时间和行信息,还打印一些其他信息。
3. 仿真时间: $time $stime $realtime
$realtime
会按照当前的时间精度对仿真时间进行准确读取,而 $time
和 $stime
会根据时间精度对当前时间进行四舍五入的读取。
示例:
initial begin#10;$display("$time output1: %t", $time);$display("$stime output1: %t", $stime);$display("$realtime output1: %t", $realtime);#3.2;$display("$time output2: %t", $time);$display("$stime output2: %t", $stime);$display("$realtime output2: %t", $realtime);#5.6;$display("$time output2: %t", $time);$display("$stime output2: %t", $stime);$display("$realtime output2: %t", $realtime);
end
结果如下:
4. 命令行传参: $test$plusargs $value$plusargs
仿真时可通过命令行传参的方式进行参数的传递:
使用 $test$plusargs( str )
时,只需在仿真命令行中加入"+str "即可。
使用 $value$plusargs( str,var )
时,需要在 str 内部指定传递参数时数值的类型。而在命令行传递参数时,数值不需要添加任何有关进制的说明,只保留相关进制的数值即可。命令行传递参数的格式需要参照 $value$plusargs
时 str 声明的格式。
示例:
initial beginif ($test$plusargs("DISPLAY_CTRL")) begin$display("Display simulation information!!!");end
endreg [1:0] display_sel ;initial beginif ($value$plusargs("INFO_SEL=%b", display_sel)) begin$display("Parameter transfer succeeds!!!");endelse begindisplay_sel = 2'b0 ;end
endinitial begin#1 ;if (display_sel == 2'b01)$display("You have selected Runoob!!!");else if (display_sel == 2'b10)$display("You have selected Verilog!!!");else if (display_sel == 2'b11)$display("You have selected Me!!!");else$display("What do you really what???");
end
在仿真的命令行中添加 +DISPLAY_CTRL +INFO_SEL=01
即可传递参数:
./simv \+DISPLAY_CTRL +INFO_SEL=01 \-l logs/run.log
输出结果如下:
因为在定义 INFO_SEL
的时候,已经指定其类型为二进制(INFO_SEL=%b
),所有船体给它的 01 会被仿真工具认为是二进制,而不是其他类型。
二、编译指令
就像 C/C++ 程序需要编译成 .exe 或可执行文件才能运行一样,VCS 编译就是将 HDL “源代码” 编译成计算机 CPU 可以直接高效执行的“仿真程序”(simv),在编译的过程中会对源代码进行分析、检查和优化。
编译指令(Compiler Directives) 是一种特殊的预处理命令,以特定符号(Verilog中的是 `)开头。它们在代码编译前被处理,用于指导编译器如何解析、转换或配置源代码,从而影响编译结果和最终生成的仿真模型。
编译指令并不是Verilog语言的一部分,而是编译器的处理指令
Verilog 中常用的一些编译指令如下:
1. 宏定义:`define
在编译阶段,define
用于定义宏常量或者宏函数,或者说是用于文本替换。
一旦 define
指令被编译,其在整个编译过程中都会有效,相当于是定义了一个 “全局变量”。
例如,在一个文件中定义宏常量:
`define DATA_DW 32
在另一个文件中可以直接使用 DATA_DW:
reg [`DATA_WIDTH-1:0] data
使用 define
定义函数(或者说带参数的宏)的示例如下:
`define MAX(a, b) ((a) > (b) ? (a) : (b))initial $display("Max: %d", `MAX(5, 9));
如果使用 define
定义的内容很多,可以使用换行符:
`define COUNTER_MODULE(name, width) \
module name ( \input wire clk, \input wire reset_n, \input wire enable, \output reg [width-1:0] count \
); \always @(posedge clk or negedge reset_n) begin \if (!reset_n) \count <= {width{1'b0}}; \else if (enable) \count <= count + 1'b1; \end \
endmodule//===================================================`COUNTER_MODULE(counter8, 8) // 生成8位计数器
`COUNTER_MODULE(counter16, 16) // 生成16位计数器
undef
用于取消之前定义的宏,释放宏名称以便重新定义。
使用 define
定义宏的时候是不能重复定义的,需通过 undef
指令取消原有定义之后才能重新定义。
`undef DATA_DW
2. 条件编译:`ifdef
条件编译指令允许根据预定义条件选择性地包含或排除代码段,实现一套代码支持多种配置的目标。
条件编译相关的命令包括:indef、ifndef、elsif、else、endif
。
`ifdef FPGA_TARGET`ifdef XILINX_FPGA// Xilinx FPGA特定代码parameter VENDOR = "XILINX";(* KEEP = "TRUE" *) wire keep_signal;`elsif ALTERA_FPGA// Intel/Altera FPGA特定代码parameter VENDOR = "INTEL";(* preserve *) wire keep_signal;`else// 其他FPGAparameter VENDOR = "GENERIC";`endif
`elsif ASIC_TARGET// ASIC特定代码parameter VENDOR = "ASIC";// 功耗优化代码
`else// 默认仿真代码parameter VENDOR = "SIMULATION";
`endif
3. 文件包含:`include
include
指令在编译时将指定文件的内容插入到当前位置,该指令通常用于将全局或公用的头文件包含在设计文件里。
文件路径既可以使用相对路径,也可以使用绝对路径。
`include "../../param.v"
`include "cpu_defines.v"
为了避免重复定义,include
命令中指定的头文件一般包含保护机制:
// 每个.vh文件都应该有保护机制
`ifndef PROJECT_DEFINES_VH`define PROJECT_DEFINES_VH// 使用严格的网络类型检查`default_nettype none// 常量定义`define WORD_SIZE 32`define CACHE_SIZE 1024// 宏函数定义`define CLOG2(x) $clog2(x)`endif // PROJECT_DEFINES_V
4. `default_nettype
该指令用于为隐式的线网变量指定为线网类型,即将没有被声明的连线定义为线网类型。
// 设置默认为wire类型(默认值)
`default_nettype wire// 禁用隐式网络声明(推荐)
`default_nettype nonemodule strict_module (input wire clk,input wire reset_n,output wire result
);// 所有信号必须显式声明wire internal_signal; // 必须声明reg state_reg; // 必须声明assign internal_signal = clk & reset_n;assign result = internal_signal;
endmodule// 恢复默认行为
`default_nettype wire
5. `resetall
该编译器指令将所有的编译指令重新设置为默认值。
resetall
可以使得缺省连线类型为线网类型。
当 resetall
加到模块最后时,可以将当前的 timescale
取消,防止其进一步传递,只保证当前的 timescale
在局部有效,避免 timescale
的错误继承。
6. `celldefine
celldefine
和 endcelldefine
用于标记标准单元库中的基本单元,他们包含模块的定义。例如一些与、或、非门,一些 PLL 单元,PAD 模型,以及一些 Analog IP 等。
`celldefine
module (input clk,input rst,output clk_pll,output flag);……
endmodule
`endcelldefine