Menu

Verilog $display 与 $monitor:在测试平台中打印

$display、$write 和 $monitor 如何工作:常用的格式说明符、它们之间的区别,以及各自适合的场景。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

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);。

Coddy 编程语言插图

和 Coddy 一起学编程

开始