GTKWave 脚本化查看流程:.gtkw 保存文件与 tcl 批导出
跑完一次仿真拿到 VCD 只是开始,真正耗时间的是「看波形」:在 SST 信号树里一层层展开、双击添加十几个信号、设进制、调顺序。看一次手点一次,换台机器、换个同事、跑一轮回归,全部重来。GTKWave 对此给了两条自动化出口:布局可以存成纯文本的 .gtkw save 文件随工程走,重复动作可以写成 Tcl 脚本用 gtkwave -S 批量执行,连信号数据都能导出成文本交给别的工具。本篇把这条脚本化查看流程在无显示环境(Xvfb)下真跑一遍。VCD 本身怎么从 testbench 导出来,见 iverilog VCD 波形:层级控制与大文件瘦身。
本文要回答的问题
gtkwave的 save 文件和波形文件是什么关系,.gtkw文本里每个分区记了什么?-a、-S、-x、-O、-f、-o这些命令行参数分别在什么场景用?- Tcl 脚本里怎么按层次路径加信号、怎么把信号清单和总线跳变值导出成文本?
- 服务器没有显示器怎么跑批处理,常见的信号路径不匹配、脚本挂死怎么查?
这张概念卡是全篇主线:波形文件、save 文件、Tcl 脚本是三样独立的东西。波形文件是只读的数据源;.gtkw 回答「上次我看了哪些信号、什么顺序和进制」;Tcl 脚本回答「把这套动作重复一遍并产出数据」。理解了这层分工,命令行参数就只是三种资产的拼装方式。
一、这一步在波形调试流程里的位置
脚本化查看发生在「波形已经生成、肉眼分析之前」。上游是仿真器产出的 VCD/FST,下游有两个方向:给人看(一行命令恢复布局)或给程序用(导出文本数据再用 awk/Python 处理)。两种自动化的入口不同,但共用同一份波形。
读图看颜色:蓝色是上游仿真器,灰色波形文件是中心数据源,紫色 save 与橙色 Tcl 是两条互不依赖的自动化路径,最终都汇向绿色的使用端。什么时候用哪条?只想保留「我习惯的信号摆位」用 save;要做回归对比、要把某条总线每个时刻的值喂给别的分析工具,用 Tcl。
二、命令逐段解读:gtkwave 命令行参数
完整用法是 gtkwave [OPTION]... [DUMPFILE] [SAVEFILE] [RCFILE],三个位置参数都是可选的,脚本化场景常用以下几个:
| 参数 | 作用 | 典型场景 / 不加会怎样 |
|---|---|---|
-f, --dump=FILE | 指定波形文件 | 与位置参数等价;波形名每次变化时用 |
-a, --save=FILE | 指定 save 文件 | 恢复布局;不加则打开空窗口手动加信号 |
-S, --script=FILE | 启动时执行 Tcl 脚本后留在主循环 | 批处理核心;脚本里要自己调 Quit 退出 |
-x, --exit | 加载完波形立即退出 | 无显示环境做加载冒烟测试、配合 -S 自动收尾 |
-O, --output=FILE | 把 stdout/stderr 重定向到文件 | 批处理收日志,避免输出污染终端 |
-o, --optimize | 把 VCD 优化转成 FST | VCD 太大时一次性转紧凑二进制格式 |
-F, --fastload | 生成/使用 VCD recoder 快速加载文件 | 反复加载同一个大 VCD 时省时间 |
-r, --rcfile=FILE | 指定界面配置文件 | 团队统一颜色、字体等界面默认值 |
一个最常用的批处理组合是 xvfb-run gtkwave -S export.tcl wave.vcd:xvfb-run 给它一个虚拟显示(GTKWave 本质是 GTK 图形程序,没有 DISPLAY 会直接报 Could not initialize GTK 退出),-S 在波形加载后执行脚本,脚本末尾调 gtkwave::/File/Quit 收尾。如果只是想验证「这个 VCD 能不能被正确解析」,用 gtkwave -x wave.vcd,加载完打印时间范围立即退出,返回码 0 即通过,适合塞进 CI。
三、输入输出文件:VCD、.gtkw 与导出文本
输入波形沿用 VCD 的声明段与值变化段结构:$scope/$var 声明层次与信号,#时间戳 记录值变化。脚本里引用信号必须用 SST 里的全路径,总线要带位选,如 tb_counter.cnt[7:0];路径写错 Tcl 不报错,只是信号列表为空。
.gtkw 是纯文本,分三段理解:头部 [dumpfile]/[dumpfile_mtime]/[dumpfile_size] 记录它配套的波形文件路径和指纹;[timestart]、[size]、[sst_width] 等是视图状态;信号区每行一个信号全路径,行首 @28、@22 是该信号的数据格式标记(28 为普通波形、22 为十六进制)。注意 [dumpfile] 存的是绝对路径,工程目录搬迁后需要重新指定波形,或用 -a 时在命令行补上波形位置参数。
Tcl 脚本本身吃的是波形,吐出的是两类自定义文本。下面的脚本先按路径加四个信号,再用 getNumFacs/getFacName 遍历 dump 里全部信号(facility)导出清单,用 signalChangeList 导出总线每次跳变:
gtkwave::loadFile wave.vcd
gtkwave::addSignalsFromList [list \
{tb_counter.clk} {tb_counter.rst_n} \
{tb_counter.cnt[7:0]} {tb_counter.co}]
set nfacs [gtkwave::getNumFacs]
set f [open facs.txt w]
for {set i 0} {$i < $nfacs} {incr i} {
puts $f "$i [gtkwave::getFacDir $i] [gtkwave::getFacName $i]"
}
close $f
set changes [gtkwave::signalChangeList {tb_counter.cnt[7:0]}]
set cf [open cnt_changes.txt w]
foreach {t v} $changes { puts $cf "$t $v" }
close $cf
gtkwave::/File/Write_Save_File_As layout.gtkw
gtkwave::/File/Quit两个 Tcl 语法坑:信号名含 [7:0],方括号在 Tcl 里是命令替换,必须用 {} 包起来,连双引号字符串里写 cnt[7:0] 都会被解析成命令 7:0 而报 invalid command name "7:0";写文件用 Tcl 原生的 open/puts/close,不要依赖菜单导出,Print_To_File 这类菜单命令在无显示环境会弹对话框挂死。
四、常见错与检查点
| 现象 | 检查 | 纠正 |
|---|---|---|
启动即报 Could not initialize GTK! Is DISPLAY env var/xhost set? | 是否在纯命令行环境直接跑 gtkwave | 套 xvfb-run -a gtkwave ...,或用 -x 只做加载验证 |
脚本跑完信号窗口是空的,getDisplayedSignals 输出空白 | 信号路径是否与 SST 一致;总线是否漏了 [7:0] | 先用 getNumFacs+getFacName 导出全部信号名,照抄完整路径 |
Tcl 报 invalid command name "7:0" | 信号名是否写在双引号或裸串里 | 一律用 {tb.cnt[7:0]} 花括号包裹 |
| 批处理不退出,直到超时被杀 | 脚本末尾是否 Quit;是否调了弹对话框的菜单命令 | 末尾加 gtkwave::/File/Quit;写文件改用 Tcl open/puts |
-a layout.gtkw 打开后提示找不到波形 | save 里 [dumpfile] 是旧绝对路径 | 命令行补波形参数 gtkwave -a layout.gtkw wave.vcd |
五、真实跑一遍:无显示环境全流程
用一个 8 位复位计数器做最小设计,容器内 iverilog 12.0 编译运行产出 wave.vcd(1293 字节、时间范围 0–212000ps),随后 xvfb-run gtkwave -S export.tcl wave.vcd 一把跑完加信号、导清单、导跳变、写 save。
这张终端卡读三个数字:TOTAL_TRACES: 4 说明按路径添加的四个信号全部命中、没有一条因路径写错被静默丢弃;FACS_EXPORTED: 9 是整个 dump 的信号总数——testbench 层 4 个、例化的 dut 层 5 个(含 parameter W);CNT_CHANGES: 23 是计数器总线的跳变条数。再用 -x -O load.out 验证纯加载路径,日志里 [0] start time.、[212000] end time. 加 Exiting early because of --exit request.,返回码 0;最后 gtkwave -x -a layout.gtkw wave.vcd 验证 save 文件能被无交互加载。
导出的跳变表前几行如下,时间单位是 ps,值是十六进制:0 0xxx 是初始未知态,5000 0x00 是复位释放前的首帧,之后每 10000ps(一个时钟周期,testbench 里 #5 clk=~clk)加一。表尾还有一条时间戳为 9223372036854775806(64 位最大值减 1 的哨兵值)的结束记录,脚本化后处理时应按时间上限过滤掉,别把它当成真实跳变。
生成的 layout.gtkw 与第三节描述完全对应:[dumpfile] "/work/wave.vcd" 是容器内绝对路径,信号区 @28 挂 clk/rst_n/co、@22 挂十六进制显示的 cnt[7:0]。把这个文件和 Tcl 脚本一起放进工程目录,任何人拿到同一个 VCD 都能一行命令恢复你的查看现场,回归跑完还能直接 diff 跳变表——这正是脚本化相对手点的价值。VCD 没导出来的前置故障见 iverilog 仿真跑完无波形输出,波形文件本身解析失败的分类见 GTKWave VCD 解析失败。
实验环境
- 机器/系统:macOS 主机 + Docker 容器
geek-eda:0.3-sta(Debian,Xvfb 虚拟显示) - 工具版本:Icarus Verilog version 12.0 (stable);GTKWave Analyzer v3.3.116 (w)1999-2023 BSI(均为容器内
iverilog -V、gtkwave -V真实输出) - 日期:2026-11-03
- 完整命令:
bash run.sh(依次执行版本查询、iverilog -g2012编译、vvp运行、xvfb-run -a gtkwave -S export.tcl wave.vcd批处理、gtkwave -x -O加载验证、gtkwave -x -asave 恢复验证)
代码获取
本文全部实验代码(counter.v、tb_counter.v、export.tcl、run.sh、Makefile)已同步到公开仓库 geek-doc.cn/experiments/flow-gtkwave-scripting,本机装好 Docker 与 geek-eda:0.3-sta 镜像后执行 make run 即可复现九步全流程,产物为 layout.gtkw、facs.txt、cnt_changes.txt。
这一篇在知识体系里的位置
本篇讲波形查看环节的自动化复用:仿真本身的跑通见 iverilog 第一次仿真,VCD 层次裁剪与大文件瘦身见 iverilog VCD 波形:层级控制与大文件瘦身,VCD/SAIF 活动率数据格式见 VCD 与 SAIF 数据格式;两个相邻故障页是 iverilog 仿真跑完无波形输出 与 GTKWave VCD 解析失败。
总结
GTKWave 脚本化查看流程的核心是把「看波形」拆成三样资产:只读的 VCD/FST 波形、记录信号布局的纯文本 .gtkw([dumpfile] 存波形绝对路径,@28/@22 段记信号与进制)、记录重复动作的 Tcl 脚本;命令行上 -a 恢复布局、-S 执行脚本、-x 加载即退、-O 重定向日志,无显示服务器套 xvfb-run;Tcl 里用 addSignalsFromList 按全路径加信号([7:0] 必须花括号包裹)、getNumFacs 导信号清单、signalChangeList 导出每次跳变(实测 23 条、表尾哨兵时间戳需过滤),末尾 gtkwave::/File/Quit 防挂死,九步流程在容器内全部返回码 0。