本文转载自CSDN博主「孤独的单刀」的原创文章,原文链接:https://blog.csdn.net/wuzhikaidetb/article/details/125340502
概述
在验证调试过程中,如果有时候能在终端打印一些信息是非常有帮助的。
比如你在验证一个串口的环回模块,发送端每隔一段时间就会发送1个BYTE数据到接收端。如果你不想通过一个一个地比对波形来验证发送与接收是否一致的话,你可以选择将每一个发送的值和接收的值直接打印到终端。
又比如你的 RTL 中某个参数出现了一个不在预期范围内的值,你就可以在此时打印一条错误信息到终端,这样很快就可以知道 RTL 是否有问题,而不是双眼一直死死地盯着你的波形图。
Verilog语法给我们提供了4个系统函数,都可以在终端显示变量信息,根据其使用方法可以划分为3类:
$display, $write
$strobe
$monitor
$display和$write
$display 可以直接打印一条文本信息,并在每一次 $display 执行后会自动换行,比如:
`timescale 1ns / 1ns
module test_tb();
initial begin
$display("China NO1!");
$display("USA NO2!");
end
endmodule
在 vivado 窗口观察的打印结果:
$write 的用法与 $display 一致,区别在于,一条 $write 语句执行完后,不会自动换行。比如下面的代码:
`timescale 1ns / 1ns
module test_tb();
initial begin
$write("China NO1!");
$write("USA NO2!");
end
endmodule
在 vivado 窗口观察的打印结果:
这两个系统函数除了直接打印文本外,也可以打印变量的值,其格式为(以 $display 为例):
$display("%b %b",a,b) ;
和 C 语言一样,其中 a, b 为期望输出的变量值,%b 为变量输出的格式,其表示为2进制输出。其他的输出格式如下:
%h 或 %H | 十六进制格式输出 | %c 或 %C | ASCII 码格式输出 |
%d 或 %D | 十进制格式输出 | %e 或 %E | 指数格式输出 |
%o 或 %O | 八进制格式输出 | %f 或 %F | 浮点数 (real 型) 格式输出 |
%b 或 %B | 二进制格式输出 | %t 或 %T | 当前时间格式输出 |
%s 或 %S | 字符串格式输出 | %m 或 %M | 当前层次访问路径输出 |
除此,使用转义字符,例如:
\n | 换行符 |
\t | 制表符(Tab 键) |
\\ | 反斜杠"\"符 |
\" | 双引号 |
%% | 百分号"%" |
\0 | 八进制代表的字符 |
\0x | 十六进制代表的字符 |
比如,输出2个数相加过程及其结果:
`timescale 1ns / 1ns
module test_tb();
reg a;
reg b;
reg [1:0] sum;
initial begin
a = 0;
b = 0;
sum = a + b;
$display("%b + %b = %b",a,b,sum);
#10
a = 1;
b = 1;
sum = a + b;
$display("%b + %b = %b",a,b,sum);
end
endmodule
其打印结果展示了两次1bit的二进制加法过程和结果:
$strobe
$strobe 为选通显示任务。$strobe 使用方法与 $display 一致,但打印信息的时间和 $display 有所差异(也可以直接打印文本)。
当许多语句与 $display 任务在同一时间内执行时,这些语句和 $display 的执行顺序是不确定的,一般按照程序的顺序结构执行。两者的区别在于:$strobe 命令会在当前时间部结束时完成;而 $display 是只要仿真器看到就会立即执行。
$strobe 是在其他语句执行完毕之后,才执行显示任务。例如:
`timescale 1ns / 1ns
module test_tb();
reg [3:0] a ;
initial begin
$strobe("begin!");
a = 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);
$strobe("end!");
end
endmodule
其打印结果如下:
可以看到,$strobe 与 $display 的打印内容不是一致的。
这是因为该语句: a <= a + 1 ;也就是说 a 的第二次赋值是非阻塞赋值,而非阻塞赋值是需要时间的。
在第一次打印时,$display 不会管你 a 是阻塞赋值还是非阻塞赋值,它就直接打印 a 当前的值1。而 $strobe 则会等到非阻塞赋值完成后再打印,所以其打印的值为2。
在第二次打印时,又延时了1ns,所以此时的非阻塞赋值完成,那么 $strobe 与 $display 的打印内容就均为2了。
所以 $strobe 这个系统任务通常是用来打印当前非阻塞赋值的变量值的。
$monitor
$monitor 为监测任务,用于变量的持续监测。只要变量发生了变化,$monitor 就会打印显示出对应的信息。 其使用方法与 $display一致。
下面的代码用 $monitor 来实现监控 a, b, c 这3个变量,只要其中一个发生了变化,就会立马在终端打印。
`timescale 1ns / 1ns
module test_tb();
reg [1:0] a ;
reg [1:0] b ;
reg [1:0] c ;
initial begin
a = 0 ;
b = 0 ;
c = 0 ;
$monitor("a=%d b=%d c=%d",a,b,c);
#50 $finish; // 50ns后停止
end
always #10 begin // 每10ns,随机生成a,b,c
a = {$random}%4;
b = {$random}%4;
c = {$random}%4;
end
endmodule
终端打印结果如下: