Menu

Verilog $dumpfile 与 $dumpvars:生成 VCD 波形

如何让测试平台输出 VCD 波形:$dumpfile、$dumpvars、作用域的选择,以及如何在 GTKWave 或浏览器编辑器中查看生成的文件。

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

你真正需要的是波形

$display 输出的是文本。做个快速检查没问题,但一旦你在调试一个有时序问题的计数器,或者一个卡住的状态机,你需要的是一张图:信号以电压随时间变化的形式画出来,可以滚动、可以缩放,还能在任意一次跳变上放置光标。

这张图就是 VCD 文件,全称 “value change dump”(值变化转储),是 IEEE 在最初的 Verilog 规范中就标准化了的一种文本格式。每个现代仿真器都能写出它,每个现代波形查看器都能读取它。生成它的代价,只是测试平台里的两行代码。

这两行代码

在一个 initial 块里(通常就是放激励的那个块)加上:

$dumpfile("dump.vcd");
$dumpvars(0, test);
  • $dumpfile 指定输出文件名。可以传任意路径;如果没有写目录,文件会生成在仿真器的工作目录中。
  • $dumpvars(0, test) 的意思是“递归记录 test 作用域及其所有子作用域中的每个信号,深度不限”。

设置就这么多。运行仿真后,你会得到一个 dump.vcd,可以用 GTKWave 或浏览器编辑器的“波形”标签页打开。

完整示例

运行它。“波形”标签页现在会显示三个信号:clk、reset 和 count,覆盖整个仿真时间。你可以在任意时间点放置光标,读出该时刻的信号值,也可以拖动框选来放大,查看单个时钟周期。

$dumpvars 的第一个参数有什么用

$dumpvars(depth, scope) 会遍历 scope 及其包含的每个子实例,最多向下记录 depth 层的信号。深度取值如下:

  • 0:不限层数。scope 中的每个信号以及所有嵌套子模块中的信号都会被记录。
  • 1:只记录直接声明在 scope 中的信号,不记录子模块。
  • 2:scope 加上一层子模块。
  • N:scope 加上 N-1 层子模块。

实际上,几乎所有测试平台都用 $dumpvars(0, test)。全部记录的开销很小(VCD 只存储跳变,不存储保持不变的状态),而你肯定不想调试到一半才发现需要的信号没有被转储。

如果设计非常大,VCD 文件也太大,可以有选择地转储:

$dumpvars(0, dut.inner_module);     // just the interesting sub-module
$dumpvars(0, dut.regs);             // just the register file

可以多次调用 $dumpvars,把多个作用域累加起来。

只转储指定信号

$dumpvars 也可以接收一组具体的信号,而不是整个作用域:

$dumpvars(0, test.clk, test.reset, dut.count);

这样只会记录这三个信号。设计规模巨大、而你只关心少数几个信号时,这种写法很有用。对于小设计,全部转储更简单。

在仿真过程中控制转储

还有两个任务和 $dumpvars 搭配使用:

  • $dumpoff:暂停转储,之后的变化不再写入 VCD。
  • $dumpon:恢复转储。
initial begin
    $dumpfile("dump.vcd");
    $dumpvars(0, test);

    // ... interesting region ...

    #1000 $dumpoff;
    // ... long boring region we don't want in the VCD ...
    #5000 $dumpon;

    // ... another interesting region ...
end

这样就能在长时间测试中跳过那段无聊的“跑一百万个周期”,而不会生成一个 5GB 的 VCD 文件。

查看结果

主要有两种方式:

在浏览器编辑器中

本页的编辑器可以直接在页面里显示 VCD。运行仿真,切换到“波形”标签页,信号会按层次结构列在一侧,另一侧是可拖动的光标。在任意一条波形上点击即可放置光标,每个信号名旁边会用小标签显示该时刻的信号值。

在 GTKWave 中

如果你在本地运行了仿真(先 iverilog -o sim test.v,再 vvp sim),用下面的命令打开生成的 VCD:

gtkwave dump.vcd

GTKWave 加载文件后,会在左侧显示作用域树,然后等你把信号拖到右侧的波形区域。在多位信号上点右键可以更改显示格式(二进制、十六进制、十进制、模拟)。用底部的搜索框可以跳转到指定时间或指定的信号跳变。

常用写法

一个小小的测试平台模板

大多数测试平台文件都以这样一个块开头。把所有内容合并到一个 initial 里,就能保持简短:

initial begin
    $dumpfile("dump.vcd");
    $dumpvars(0, test);

    // ... stimulus ...

    $finish;
end

按条件转储

在只想为失败的测试输出 VCD 的测试环境中:

initial begin
    if (DUMP_VCD) begin
        $dumpfile("dump.vcd");
        $dumpvars(0, test);
    end
    // ... stimulus ...
end

DUMP_VCD 可以是一个从命令行设置的 parameter,或者根据测试模式来定义。这样在回归测试套件中能节省磁盘空间。

存储器转储

存储器(非压缩数组)默认不会被 $dumpvars 转储,因为它们可能非常大。需要的话,显式地使用 $dumpvars(0, dut.memory),或者在部分仿真器中使用 $dumpmem。

常见错误

VCD 文件是空的。 你忘了写 $dumpfile 或 $dumpvars,或者仿真在任何信号变化之前就调用了 $finish。设置完成后至少再运行几个时间单位。

波形中缺少某些信号。 你传入的作用域没有包含它们。$dumpvars(0, dut) 只记录 dut 内部的内容;如果测试平台在 test 这一层驱动信号,这些信号就不会出现。始终从测试平台的作用域开始转储:$dumpvars(0, test)。

VCD 文件太大。 长时间仿真加上快速变化的宽信号,会产生大量 VCD 行。有三种解决办法:转储更小的作用域;在无聊的部分前后使用 $dumpoff/$dumpon;或者换用更紧凑的格式,比如 FST(iverilog 和 GTKWave 都支持,使用 -fst 参数)。

接下来

本章的最后一篇文档 时间刻度与延迟 会讲解 `timescale 指令,以及 #delay 究竟如何对应到真实时间。读完它,你就从头到尾学完了这套文档。

常见问题

Verilog 中的 VCD 文件是什么?

VCD 是 Value Change Dump(值变化转储)的缩写,是一种记录仿真过程中每次信号跳变的文本格式。仿真器先写下每个信号在时间 0 的初始值,之后每次变化都连同时间戳一起记录。GTKWave 等波形查看器读取这个文件,把它画成可以滚动和缩放的图形化时序图。

Verilog 中如何生成 VCD 文件?

在测试平台的 initial 块里加两个系统任务:$dumpfile("dump.vcd"); 指定输出文件名,$dumpvars(0, top_module); 记录 top_module 及其下层的所有信号。仿真结束后,你会得到一个 dump.vcd 文件,任何波形查看器都能打开。

Verilog 中 $dumpvars(0, ...) 是什么意思?

$dumpvars(depth, instance) 从 instance 开始记录信号,并向下递归 depth 层。$dumpvars(0, test) 的意思是“递归记录 test 作用域及其所有子作用域中的全部信号”:深度 0 是特殊值,表示不限层数。$dumpvars(1, test) 则只记录直接位于 test 中的信号,不包括任何被实例化的子模块。

Verilog 生成的 VCD 文件为什么是空的?

常见原因有三个:根本没有调用 $dumpfile/$dumpvars;仿真在任何信号变化之前就执行到了 $finish(在转储调用之后至少再运行几个时间单位);或者传给 $dumpvars 的作用域和实际层次结构对不上。一个最小的可用写法是 $dumpfile("dump.vcd"); $dumpvars(0, test); #10; $finish;。

Coddy 编程语言插图

和 Coddy 一起学编程

开始