Verilog 里的 C 语言 printf 家族
有三个系统任务可以向仿真器的标准输出打印内容:
$display:打印一次,末尾加换行。$write:打印一次,不加换行。$monitor:每当被监视的信号变化时自动打印。
这三个任务都接收一个格式字符串和一个参数列表,和 printf 一样。格式说明符与 C 类似,但有 Verilog 自己的特点。
$display:默认之选
$display 是最常用的:
你会看到类似这样的输出:
Hello, world.
byte_val = ab
byte_val = 10101011
byte_val = 171
byte_val = 171 (no padding)
multi: nibble=1010 count=42
说明:
%d会根据操作数的大小填充到默认宽度。对于 8 位的reg,宽度是 3 个字符(能容纳255)。前导空格在表格式输出中可能不好看,用%0d可以去掉它们。%h、%b、%o默认也有类似的填充。当对齐没有用处时,大多数测试平台代码都使用%0形式。- 末尾的换行是自动添加的。不要在
$display格式字符串末尾加\n,否则会多出一个空行。
格式说明符
Verilog 支持的格式说明符:
| 说明符 | 含义 |
|---|---|
%b | 二进制 |
%d | 十进制(信号为有符号时按有符号显示) |
%h 或 %x | 十六进制 |
%o | 八进制 |
%c | 单个 ASCII 字符(低 8 位) |
%s | 字符串 |
%t | 仿真时间 |
%m | 当前作用域的层次化名称 |
%% | 一个字面的 % |
%0X | 不带前导填充,适用于 %b、%d 等任意说明符 |
%b、%d、%h、%o 这四个能覆盖 95% 的使用场景。其次最常用的是 %t,凡是需要带时间戳的日志行都会用到它。
$write:不换行
$write 与 $display 完全相同,只是不会在末尾添加换行:
输出:
abc
done
在循环体中拼出一整行时很有用:
$write("[");
for (integer i = 0; i < 8; i = i + 1) $write("%h ", arr[i]);
$display("]");
$monitor:变化时自动打印
$monitor 注册一个监视列表。只要格式字符串中提到的任何信号发生变化,仿真器就会重新求值并打印:
你会看到三行输出,每次输入变化各一行。不需要在每次改变激励后手动调用 $display,$monitor 会替你完成。
两个限制:
- 同一时间只能有一个
$monitor生效。 再次调用会替换之前的监视列表。用$monitoroff和$monitoron可以暂时关闭和重新开启监视。 - 同一时间步内的多次变化只打印一次。 如果
a和b都在时间 5 变化,monitor 只会触发一次,同时显示两个新值,而不是触发两次。
各自适合的场景
$display:大多数测试平台输出。在施加激励后、重要的状态转换后,或者在采样用的always @(posedge clk)块中显式调用它。$write:需要用循环或几个小片段拼出一行时。$monitor:需要持续跟踪一小组信号,并且只在它们变化时才看到输出时。适合初步调试;在回归测试脚本中较难使用,因为输出的总行数不确定。
对大多数工作流程来说,$display 就够用了。只有当你确实需要由变化驱动的持续输出时,才使用 $monitor。
处理时间
$time 以 64 位整数返回当前仿真时间。把它与 %0t 搭配使用:
$display("at %0t: signal flipped", $time);
输出类似 at 25: signal flipped(单位取决于你的 timescale)。
如果需要小于一个时间单位的精度(很少见),改用 $realtime,它返回一个 real。
%t 会自动用仿真器选定的默认宽度格式化时间,%0t 则去掉填充。
在时钟沿上采样
监视时序设计的一种简洁写法:单独写一个 always @(posedge clk) 块,每个周期打印一次:
这种采样写法保证每个时钟恰好输出一行日志,非常适合按输出内容做模式匹配的回归测试。
写入日志文件
用 $fopen 打开文件,再用 $fdisplay 记录日志(它和 $display 一样,只是写入文件句柄):
integer fd;
initial begin
fd = $fopen("results.txt", "w");
$fdisplay(fd, "test=%s status=%s", test_name, status);
$fclose(fd);
end
$fopen 返回一个 32 位句柄;把它作为第一个参数传给 $fdisplay、$fwrite、$fstrobe 等。除此之外,这些函数与对应的控制台打印函数完全相同。
接下来
$display 等任务给你的是文本日志。如果要做可视化调试,也就是观察信号的电平随时间的变化,你需要 VCD 波形。下一篇文档 Dumpfile 与 VCD 讲解 $dumpfile 和 $dumpvars,这两个调用能把你的仿真变成可以来回拖动查看的图形化波形。
常见问题
Verilog 中 $display 和 $monitor 有什么区别?
$display 在执行时立即打印一次,就像 C 语言中的 printf。$monitor 注册一个监视列表,列表中任何信号变化时,都会自动打印格式化后的消息。同一时间只能有一个 $monitor 生效,再次调用会替换之前的监视列表。
Verilog $display 支持哪些格式说明符?
常用的有:%b(二进制)、%d(十进制)、%h(十六进制)、%o(八进制)、%c(取低字节的单个字符)、%s(字符串)、%t(仿真时间)、%m(层次化实例名)。用 %0d 这种写法可以去掉前导填充:%d 会填充到默认宽度,%0d 不做任何填充。
Verilog 中的 $write 是什么?
$write 和 $display 类似,但不会在末尾添加换行。当你想用多次调用拼出一行输出时很有用。最后用一个 $display(不带参数,或者以换行结尾)来结束这一行。
在 Verilog 中如何打印仿真时间?
把 $time(需要小于一个时间单位的精度时用 $realtime)和 %t 格式说明符一起使用:$display("at time %t: ...", $time);。用 %0t 去掉默认填充。如果只想要时间单位的整数计数,%0d 配合 $time 也可以:$display("t=%0d", $time);。