【Verilog HDL】FPGA 仿真和调试脚本常用系统任务
一、显示任务$display和$write
系统显示任务$display和$write在仿真测试中是最为常用的信息显示方式。$display和$write任务最主要的区别在于,$display在一次输出后会自动换行,而$write则不会,他们的其他用法格式基本类似。
【语法结构】
【任务名】(“【可选字符串】+【格式】”,【信号1】,【信号2】,……)
【任务名】可以是$display,$displayb,$displayo,$displayh,$write,$writeb,$writeo或$writeh。
【格式】由“%”和格式字符组成,【信号】为进行显示的信号名,【信号】数量和【格式】数量必须对应。
若不指定显示【信号】的【格式】,则【信号】显示的格式将会是$display和$write默认为十进制,$displayb和$writeb默认为二进制,$displayo和$writeo默认为八进制,$displayh和$writeh默认为十六进制。
$display举例如下:
// 系统打印任务$display的使用 `timescale 1ns/1ns module testbench_top(); reg [31:0] rval; initial begin rval = 101; $display("rval = %h hex %d decimal", rval, rval); $display("rval = %0h hex %0d decimal", rval, rval); $display("rval = %o octal\nrval = %b bin", rval, rval); $display("rval has %c ascii character value", rval); $display("current scope is %m"); $display("%s is ascii value for 101", 101); #101; $display("rval = %h hex %d decimal", rval, rval); $stop; end endmodule
# rval = 00000065 hex 101 decimal # rval = 65 hex 101 decimal # rval = 00000000145 octal # rval = 00000000000000000000000001100101 bin # rval has e ascii character value # current scope is testbench_top # e is ascii value for 101 # simulation time is 105
默认情况下,输出显示的数值所占字符个数由输出信号的数值类型和位宽决定。例如该例子中32位寄存器rval以16进制显示时,其最大值是FFFFFFFF,所以即便显示数值是65,但在显示时也会占用8个字符位。除了十进制显示时会默认将高位0以空格填充,其它进制都会将高位0显示出来。在%和格式字符之间可以添加数字0,来隐藏前置的0或空格,使得第一个非0数字顶格显示。
二、监视任务$monitor
系统监视任务$monitor可以在仿真测试脚本中实现对任何变量或表达式取值的监视和显示。$monitor和$display的语法结构与用法类似。
当$monitor任务中包含一个或多个监控信号并运行时,若参数列表中由任何的变量或表达式的值发生变化,所有参数列表中的信号值都将输出显示。同一时刻若有两个或多个参数发生变化,则合并一次输出显示。
$monitor任务在申明后默认开启,在其运行期间若调用系统任务$monitoroff则关闭$monitor,直到调用系统任务$monitoron后重新开启。
【语法结构】
【任务名】(“【可选字符串】+【格式】”,【信号1】,【信号2】,……)
// 监控每个时钟周期递增的4位计数器o_cnt // 系统复位后,监控信号o_cnt的变化,输出最新数值和时间戳 initial begin @(posedge rst_n); $monitor("o_cnt is %d at %0dns", o_cnt, $time); end // 当o_cnt取值为6~12范围时,关闭监控 always @(posedge clk) begin if (o_cnt == 4'd5) $monitoroff; else if (o_cnt == 4'd12) $monitoron; end
三、文本操作任务$fopen $fclose $fwrite $readmemh $readmemb
1、$fopen:打开指定文件名的文件,并返回一个32位的多通道描述符或32位的文件描述符(取决于“文件操作类型”的设置)。
【语法结构】 // 指定文件操作类型的语法结构 integer [文件描述符] = $fopen("[文件名]","[文件操作类型]"); // 不指定文件操作类型的语法结构 integer [多通道描述符] = $fopen("[文件名]");
【语法结构】 $fclose("[文件描述符或多通道描述符]");
3、$fwrite:用于向指定文件写入数据,写入后不自动换行。
【语法结构】 $fwrite("[文件描述符或多通道描述符]","[字符串或数据格式]","[寄存器等]");
4、$readmemh 十六进制文件读取
【语法结构】 $readmemh("文件名", 存储器名, 起始地址, 终止地址); // 存储器名即定义的数组名
// 打开output_file文件夹并创建一个文本result_data.txt,定义文件描述符w_file。 // 注意output_file文件夹必须事先创建好,否则可能报错 // 文本result_data.txt则无需创建,此代码会自动新建。 integer w_file; // 定义整型数据类型的文件描述符 initial begin w_file = $fopen("./output_file/result_data.txt", "w"); // 向文件中写入字符串 $fwrite(w_file, "The result of counter is :\n"); end // o_data信号以16进制写入w_file指向的文本文件中 always @ (posedge clk) begin if(o_vld) begin $fwrite(w_file, "%4x\n", o_data); end end // 最后要记得$fclose关闭文件
// 从input文件夹读取16进制文本hex_file_1.txt
// 读取的文本数据存储在data_men_1数组寄存器中
// data_mem_1数组寄存器的深度为8,位宽为16bits
reg [15:0] data_mem_1 [7:0];
initial $readmemh("./input_file/hex_file_1.txt", data_mem_1);
四、随机数系统任务$random
$random 是Verilog提供的一个随机数生成系统任务,调用该任务后,将会返回一个32bit的integer类型的有符号的值。其调用格式有3种:
$random;
$random();
$random(seed);
$random的返回值是一个32位的整数,有时不需要这么大的数。如果希望随机数的值能固定在某个范围,可以使用:$random%b;那么生成的随机数的范围就是 [ ( -b+1 ) : (b- 1 ) ], 即对b取余。(比如当除数用10,生成范围[-9:9])。
如果希望只生成正数范围内的随机数,可以使用:{$random}%b;那么生成的随机数的范围就是 [ 0 : (b - 1 ) ]。
实际上,$random并不是一个真正意义上的随机数生成函数,如果每次仿真调用它的时间一致(其实是种子seed一致),那么其生成的随机数就是一致的。$random中的seed数据类型可以是reg,integer或者time。
仿真中调用随机数生成函数的常用用法是:
// 输入 data_in,位宽【a-1:0】,即位宽a,其值范围2^a, // Verilog语法即2**a, 2**a表示2的a次方。 // 所以如果需要模拟data_in的随机输入,通常这样调用: data_in = {$random}%(2**a);
input [3:0] data_in; //其值范围为2进制0000~1111(即十进制0-15), data_in = {$random}%(2**4); //即data_in = {$random}%16),生成的随机数范围为0-15,完美覆盖输入数据的全部范围。
data_in = min + {$random}%(max-min+1) // 产生一个在min和max之间的随机数字
//每10ns产生一个随机数 initial begin rand_data = 0; repeat(10) begin #10 rand_data = {$random(1)}%100; end #5 $finish; end
即
- $random与$random()的用法、结果都是一致的
- $random%b可以生成范围 [ ( -b+1 ) : (b- 1 ) ]内的随机数
- {$random}%b可以生成范围 [ 0: (b- 1 ) ]内的随机数