VCD 波形导出与 GTKWave 查看:dump 任务、分层导出与 .gtkw 布局
跑通 iverilog 第一次仿真之后,下一步几乎总是「把波形调出来看」。这一步横跨两个工具:testbench 里的 dump 系统任务决定记录什么,vvp 负责把值变化写成 VCD(Value Change Dump,值变化转储)文本,GTKWave 这类查看器再把文本画成波形。本篇按正常流程把这条链路一次走通:四个 dump 系统任务的用法、$dumpvars 分层导出的深度差异、$dumpall 的快照语义、VCD 文件的文本结构,以及 GTKWave 里加信号、搜索、保存 .gtkw 布局。
本文要回答的问题
$dumpfile/$dumpvars/$dumpon/$dumpoff/$dumpall各自管什么,深度参数 0/1/2 导出的内容差多少?- VCD 文件里
$scope/$var声明段和#时间戳值变化段分别长什么样? - 拿到
.vcd后在 GTKWave 里怎么加信号、搜信号、把布局存成.gtkw下次直接恢复? - 只想记录一段时间窗的波形,
$dumpoff/$dumpon恢复时为什么还要补$dumpall?
这张卡是全篇的主线:VCD 导出不是一个单一命令,而是「空间上选层次、时间上选窗口」两个维度的组合,产物是一个可阅读的 ASCII 文本;GTKWave 的布局并不写回 VCD,而是存在独立的 .gtkw 文件里。
一、这一步在仿真流程里的位置
波形导出发生在「编译之后、查看之前」:iverilog 只把 RTL 和 testbench 编译成仿真可执行文件,它本身不产波形;真正写 VCD 的是运行器 vvp,而且只在 testbench 主动调用 dump 系统任务时才写。下游的 GTKWave 是纯消费者,不参与仿真。
读图抓住颜色分界:蓝色是你写的输入,灰色编译只做翻译,橙色 vvp 才是产波形的环节,绿色的 VCD 与 GTKWave 是产物和消费者。这也解释了为什么「只跑了 iverilog 没跑 vvp」时目录里找不到波形——编译和记录是两件事。
二、逐段解读:dump 语句与运行命令
最小 testbench 里只需在 initial 块开头写两行:
initial begin
$dumpfile("depth0.vcd"); // 输出文件名
$dumpvars(0, tb_d0); // 从 tb_d0 开始,深度 0 = 全层次记录
#12 rst_n = 1'b1;
#300 $finish;
end五个系统任务分工明确,缺哪个都不报错,行为却完全不同:
| 系统任务 | 作用 | 不写会怎样 |
|---|---|---|
$dumpfile("f.vcd") | 指定 VCD 文件名 | 不落盘到预期文件 |
$dumpvars(深度, 层次) | 选定记录的层次起点与向下深度 | 没有任何记录 |
$dumpoff | 暂停记录,给所有信号写一帧 x 基线 | 不影响 |
$dumpon | 恢复记录 | dumpoff 之后一直是空白窗 |
$dumpall | 立即写一帧全部信号当前值 | 恢复点缺少全量基线,长总线跳变要靠推断 |
$dumpvars 第一个参数是深度,语义是「从指定层次向下数几层」:1 只记 testbench 顶层本身的信号,2 再收一层例化模块,0 是特殊值,表示不限深度、整棵层次树全收。第二个参数写错(写成不存在的层次名)编译期可能不拦,但记录范围会不对。
完整命令只有三步,参数没有多余花样:
iverilog -g2012 -o simv_d0 tb_d0.v counter.v # -g2012 启用 SystemVerilog 词法;-o 指定输出名
vvp simv_d0 # 运行,VCD info 行会提示文件已打开
gtkwave depth0.vcd # 查看器直接吃 VCD(需本机已装 GTKWave)三、输入输出:VCD 文本结构逐行读
VCD 是给程序读的,但本质是 ASCII 文本,出问题时 head/grep 就能查。文件分两段:$enddefinitions $end 之前是声明段,登记时间精度、层次和变量;之后是值变化段,按仿真时间追加。声明段里每个模块层级是一对 $scope module 名字/$upscope,每个信号一行 $var 类型 位宽 标识符 名字——末尾那个单字符(!、"、#)是压缩用的短标识符,值变化段只引用它,不写信号全名。
本次用刻意做成两级层级的 8 位计数器(tb_d0 → dut(cnt_top) → u_inc(inc_add))跑同一套激励,三档深度对比如下。
这组数字要横着读:深度 1 只有 1 个 scope、4 个变量(testbench 自己的 clk/rst_n/cnt/co);深度 2 多出 dut 层的 nxt 与内部 cnt,共 9 个变量;深度 0 再收进 u_inc 的 a/y,共 11 个变量、3 层 scope。三档时间戳条数都是 65——深度控制的是记哪些信号,不改变仿真事件本身,文件体积却从 2545 字节降到 1264 字节。大设计用深度裁剪体积的实测见 iverilog VCD 波形。
声明段与值变化段的真实文本如下:
$timescale
1ps
$end
$scope module tb_d0 $end
$var wire 1 ! co $end
$var wire 8 " cnt [7:0] $end
$var reg 1 # clk $end
$scope module dut $end
$var wire 8 % nxt [7:0] $end
...
$enddefinitions $end
#0
$dumpvars
bx "
x!
$end
#5000
1#两个细节:一是 $timescale 取的是 testbench `timescale 1ns/1ps 的精度 1ps,所以 #5000 表示 5000ps 即 5ns;二是值变化段开头的 $dumpvars 块给出 #0 时刻全部被记录信号的初值(bx 是 8 位未知、x! 是 1 位未知),之后只有发生变化的信号才在新时间戳下追加一行。grep -c '^#' file.vcd 数时间戳、grep '^\$scope' 查层次,是不打开 GUI 也能验收 VCD 的两个命令。
四、常见错与检查点
| 现象 | 检查 | 纠正 |
|---|---|---|
跑完没有 .vcd 文件 | 源码 grep dumpvars;确认跑了 vvp 而非只编译 | 补 $dumpfile+$dumpvars 后重跑 vvp,排查全文见 iverilog 缺少 dumpvars |
| GTKWave 左侧 SST 里只有 tb 一层,找不到 dut 内部信号 | grep '^\$scope' f.vcd 看层数 | 深度从 1 改成 0,或 $dumpvars(0, tb.dut) 单独点名子层次 |
| 恢复记录后波形起点总线是一条平线,跳变沿前没有值 | dumpoff 之后只写了 dumpon | $dumpon 后紧跟一句 $dumpall 补当前值快照 |
| GTKWave 报 parse error 或加载后无信号 | 文件是否被传输截断、时间戳是否乱序 | 对照 GTKWave VCD 解析失败 的分类检查 |
五、真实跑一遍:时间窗控制与 GTKWave 查看
tb_window.v 在 100ns 暂停、200ns 恢复,并在恢复时补 $dumpall。vvp 写出的 VCD 里,暂停与恢复两处的文本是最好的机制说明。
读这两段抓住三点:$dumpoff 块里 8 个信号全部写成 x,这是查看器画「暂停段未知」的依据;$dumpon 块在同一时刻给出全部信号的真实当前值——此时计数器已跑到 b10011(十进制 19),组合输出 nxt 为 b10100(20);标准上恢复后应再显式调一次 $dumpall 保证跨仿真器都有全量基线,Icarus Verilog 13.0 里它会再写一帧相同快照,两段内容一致。对照时间戳序列,#100000 与 #200000 之间整整 100ns 没有任何事件落盘,时间窗就是这样被抠掉的。
拿到 VCD 后在 GTKWave 里的标准动作是四步。第一,打开:macOS 用 brew install --cask gtkwave 安装,随后 gtkwave depth0.vcd,或先开程序再从 File 读入。第二,加信号:左上 SST(层次树)点中某个 module(如 dut),下方信号列表选中要观察的信号,点 Append 或双击,信号进入右侧波形窗口,再点工具栏的 Zoom Fit 展开全程。第三,搜索:SST 的信号过滤框支持通配符,输入 *cnt* 可跨层次筛出所有 cnt;波形区选中某信号后可用数据查找跳到等于指定值的时刻。第四,保存布局:File → Write Save File 存成 depth0.gtkw,它只记录「加了哪些信号、进制与顺序」等布局信息,不复制波形数据;下次 gtkwave -a depth0.gtkw depth0.vcd 一行恢复现场,.gtkw 是纯文本,适合随工程一起版本管理。注意保存文件与 VCD 的相对路径,移动目录后要重新指定波形文件。
实验环境
- 机器/系统:Mac / macOS(Darwin 25.5.0)
- 工具版本:Icarus Verilog version 13.0 (stable) (v13_0)(
iverilog -V真实输出);本机未安装 GTKWave,界面操作描述其通用用法 - 日期:2026-11-03
- 完整命令:
bash run.sh(依次编译运行 tb_d0/tb_d1/tb_d2/tb_window,统计 scope/var/时间戳数并摘录 VCD 文本段);查看波形:gtkwave depth0.vcd
代码获取
本文全部实验代码(counter.v、三个深度 testbench、tb_window.v、run.sh、Makefile)已同步到公开仓库 geek-doc.cn/experiments/flow-iverilog-vcd-to-gtkwave,克隆后执行 make run 复现四组 VCD 对照,make gtkwave 在已安装 GTKWave 的机器上打开波形。
这一篇在知识体系里的位置
本篇是开源 EDA 仿真流程中「波形导出与查看」这一步的正向操作说明:仿真本身的跑通见 iverilog 第一次仿真,同主题的体积控制实测见 iverilog VCD 波形:层级控制与大文件瘦身;导出环节的两个高频故障分别对应 iverilog 仿真跑完无波形输出 与 GTKWave VCD 解析失败,vvp 的文本日志与 -l 选项边界见 vvp 日志与跟踪选项。
总结
VCD 波形导出与 GTKWave 查看是一条三段链路:testbench 用 $dumpfile 定名、$dumpvars(深度, 层次) 在空间上选记录范围(深度 1 只记 TB 层、2 收到 dut、0 全层次,实测变量数 4/9/11),用 $dumpoff/$dumpon 在时间上抠掉无关窗口、恢复时补 $dumpall 全量快照;vvp 产出的 VCD 是声明段加 #时间戳 值变化段的 ASCII 文本,grep 即可验收;GTKWave 侧用 SST 选层次、Append 加信号、通配符搜索,布局另存 .gtkw 后用 gtkwave -a 一行恢复。