这一篇在干嘛?
仿真调试最常见的两个痛点:一是「改动之后波形哪里变了看不出来」,二是「每天重复敲同一串命令敲到手酸」。这一篇对应官方 Tutorial 的第 18 章(波形比较)和第 19 章(Tcl/DO 自动化):波形比较能自动算出两次仿真之间测试信号与参考信号的时序差异;DO 文件则把一串命令打包成脚本,甚至可以完全脱离 GUI 在命令行跑仿真。学完这一篇,你的仿真工作流会从「手工时代」进入「脚本时代」。
波形比较:四步找出两次仿真的差异(对应原文第 18 章)
先记住总流程:四个步骤
波形比较(Waveform Compare)计算测试信号(test signals)与参考信号(reference signals)之间的时序差异。比较波形的一般流程分四步:
- 选择要比较的仿真或数据集(dataset);
- 指定要比较的信号或区域(region);
- 运行比较;
- 查看比较结果。
本节练习的思路是:先运行并保存一次仿真 → 修改一个源文件 → 再跑一次仿真 → 最后比较两次运行的结果。
实验设计
示例设计由一个控制行为级存储器的有限状态机(FSM)组成,测试平台 test_sm 提供激励。QuestaSim 安装自带 Verilog 和 VHDL 两个版本:
- Verilog:
<install_dir>/examples/tutorials/verilog/compare - VHDL:
<install_dir>/examples/tutorials/vhdl/compare
本节用 Verilog 版做练习(有 VHDL license 可改用 VHDL 版),需要区分时文中会分别说明。想深入了解可参阅 User’s Manual 中的 Waveform Compare 与 Recording Simulation Results With Datasets 章节。
第一步:建立参考数据集 gold.wlf
参考数据集(reference dataset)是测试数据集将来要比对的基准,它可以是一个已保存的数据集、当前仿真数据集,或者当前仿真数据集的任意一部分。.wlf 文件就是 QuestaSim 记录仿真结果的波形日志文件。
操作步骤:
- 新建目录,把示例目录中的所有文件复制进去;
- 启动 QuestaSim:在 UNIX shell 输入
vsim或用 Windows 图标(弹出 Welcome 对话框就点 Close),然后 File > Change Directory 切换到新目录; - 依次执行以下命令:
Verilog:
vlib work
vlog *.v
vopt +acc test_sm -o opt_test_gold
vsim -wlf gold.wlf opt_test_gold
add wave *
run 750 ns
quit -simVHDL:
vlib work
vcom -93 sm.vhd sm_seq.vhd sm_sram.vhd test_sm.vhd
vopt +acc test_sm -o opt_test_gold
vsim -wlf gold.wlf opt_test_gold
add wave *
run 750 ns
quit -sim关键在 vsim -wlf gold.wlf ...:-wlf 开关让本次仿真的结果保存为名为 gold.wlf 的参考数据集(gold = 黄金参考,硬件圈惯用说法)。
第二步:修改测试平台,生成测试数据集
测试数据集(test dataset)就是将来要和参考数据集比较的那个 .wlf 文件。为了省事,我们直接从刚跑完的仿真出发——但要先编辑测试平台,制造两次运行之间的差异。
Verilog 版:
- 选择 File > Open 打开
test_sm.v; - 在 Source 窗口右键,取消弹出菜单中的 Read Only 勾选,让文件可编辑;
- 滚动到第 122 行,内容如下:
@ (posedge clk) wt_wd('h10,'haa);- 把数据模式
'haa改成'hab,即:
@ (posedge clk) wt_wd('h10,'hab);- 选择 File > Save 保存文件;
- 重新编译并重跑仿真:
vlog test_sm.v
vopt +acc test_sm -o opt_test_sm -noincr
vsim opt_test_sm
add wave *
run 750 nsVHDL 版(编辑 test_sm.vhd):滚动到第 151 行,把 wt_wd (16#10#, 16#aa#, clk, into); 中的 'aa' 改成 'ab'(即 wt_wd (16#10#, 16#ab#, clk, into);),保存后重新编译:
vcom test_sm.vhd
vopt +acc test_sm -o opt_test_sm
vsim opt_test_sm
add wave *
run 750 ns第三步:用 Comparison Wizard 执行比较
QuestaSim 自带比较向导(Comparison Wizard)一步步引导你完成配置,你也可以用菜单或命令行手动配置。这里用向导:
a. 选择 Tools > Waveform Compare > Comparison Wizard;
b. 点 Browse 按钮,选择 gold.wlf 作为参考数据集——它就是第一次仿真保存的结果:
Comparison Wizard
The first step in creating a comparison is to open the reference and test datasets (.wlf files).
Either dataset can be a saved .wlf file or a dataset that is already opened.
Use the Browse buttons to browse for a saved dataset, or click the down arrow to select a file from the dataset selection history.
Reference Dataset
gold.wlf
Browse...
Test Dataset
Use Current Simulation
Update comparison after each run
Specify Dataset
Browse...
Next >
< Previous
Cancelc. 测试数据集保持 Use Current Simulation(用当前仿真),点 Next; d. 第二个对话框选择 Compare All Signals(比较所有信号),点 Next:

e. 接下来三个对话框依次点 Next、Compute Differences Now、Finish。
QuestaSim 执行比较,并在 Wave 窗口中显示被比较的信号。对应四步流程:第 1、2 步在向导里完成,第 3 步是 Compute Differences Now,第 4 步马上开始——查看结果。
第四步:查看比较结果
比较数据会显示在 Structure (compare)、Transcript、Objects、Wave 和 List 窗口中。被比较的对象(compare 对象)用黄色三角形标记。
- Compare 窗口:显示被比较的区域;
- Transcript 窗口:显示参考与测试数据集之间找到的差异总数;
- Objects 窗口:当你在 Structure (compare) 窗口选中某个比较对象时,它显示具体的比较差异:

Wave 窗口中的比较信息
Wave 窗口用图形方式清晰呈现比较结果:
- 路径名列中的时序差异用红色 × 标注:

- 波形视图中的红色区域就是时序差异所在;
- 滚动条上的红色线条同样标示差异位置;
- 被注解(annotated)的差异用蓝色高亮。
Wave 窗口还有六个比较图标,可以在差异之间快速跳转。从左到右依次是:查找第一个差异、查找上一个注解差异、查找上一个差异、查找下一个差异、查找下一个注解差异、查找最后一个差异。这些图标会在所有信号的差异之间循环跳转;只想看某一条选中信号上的差异,用 <tab> 和 <shift>-<tab>。
List 窗口中的比较信息
比较结果也可以在 List 窗口里按表格查看:
a. 在主窗口菜单栏选 View > List;
b. 把 Main 窗口 compare 标签里的 test_sm 比较对象拖到 List 窗口;
c. 向下滚动窗口。
差异行用黄色高亮标注,被注解过的差异用红色高亮。可以看到从 455000 ps 起,outof 信号的 gold 值是 000000aa,而 sim 值是 000000ab——正是我们改掉的那一个比特:
| compare:/test_sm/\rst<>rst\ | |||||||
|---|---|---|---|---|---|---|---|
| ps→ | delta→ | compare:/test_sm/\outof<>outof\(gold) | (sim) | compare:/test_sm/\out_wire<>out_wire\(gold) | (sim) | compare:/test_sm/\clk<>clk\(gold) | (sim) |
| 430000 | +0 | 00000000 | 00000000 | 0 | 0 | 1 | 1 |
| 431000 | +1 | 00000000 | 00000000 | 0 | 0 | 1 | 1 |
| 435000 | +0 | 00000000 | 00000000 | 0 | 0 | 1 | 1 |
| 440000 | +0 | 00000000 | 00000000 | 0 | 0 | 0 | 0 |
| 450000 | +0 | 00000000 | 00000000 | 0 | 0 | 1 | 1 |
| 451000 | +1 | 00000000 | 00000000 | 0 | 0 | 1 | 1 |
| 455000 | +0 | 000000aa | 000000ab | 0 | 0 | 1 | 1 |
| 460000 | +0 | 000000aa | 000000ab | 0 | 0 | 0 | 0 |
| 469000 | +1 | 000000aa | 000000ab | 0 | 0 | 0 | 0 |
| 470000 | +0 | 000000aa | 000000ab | 0 | 0 | 1 | 1 |
| 471000 | +1 | 000000aa | 000000ab | 0 | 0 | 1 | 1 |
| 475000 | +0 | 000000aa | 000000ab | 0 | 0 | 1 | 1 |
| 480000 | +0 | 000000aa | 000000ab | 0 | 0 | 0 | 0 |
| 490000 | +0 | 000000aa | 000000ab | 0 | 0 | 1 | 1 |
| 491000 | +1 | 000000aa | 000000ab | 0 | 0 | 1 | 1 |
| ……(后续行略) |
保存与重新加载比较数据
比较数据可以保存下来以后再看——既能存成纯文本报告,也能存成可重新加载回 QuestaSim 的文件。要重新加载,必须保存两个文件:先把计算出的差异存到一个文件,再把比较的配置规则(rules)存到另一个单独的文件;重新加载数据时,必须先打开参考数据集。
- 把比较数据保存为文本文件:
a. 主窗口选择 Tools > Waveform Compare > Differences > Write Report;
b. 点 Save——在当前目录保存
compare.txt; c. 在 VSIM> 提示符输入notepad compare.txt查看报告:
Notepad
File Edit Window
compare.txt
Total signals compared = 11
Total primary differences = 6
Total secondary differences = 6
Number of primary signals with differences = 4
Diff number 1, From time 135 ns delta 0 to time 155 ns delta 0.
gold:/test_sm/into = 000000aa
sim:/test_sm/into = 000000ab
Diff number 2, From time 135 ns delta 0 to time 155 ns delta 0.
gold:/test_sm/into[0] = 0
sim:/test_sm/into[0] = 1
Diff number 3, From time 171 ns delta 1 to time 191 ns delta 1.
gold:/test_sm/dat = 000000aa
sim:/test_sm/dat = 000000ab
Diff number 4, From time 171 ns delta 1 to time 191 ns delta 1.
gold:/test_sm/dat[0] = 0
sim:/test_sm/dat[0] = 1
Diff number 5, From time 409 ns delta 1 to time 411 ns delta 1.
gold:/test_sm/dat = 000000aa
sim:/test_sm/dat = 000000ab
Diff number 6, From time 409 ns delta 1 to time 411 ns delta 1.
gold:/test_sm/dat[0] = 0
sim:/test_sm/dat[0] = 1
compare.txt报告一目了然:总共比较了 11 个信号,6 处差异,全部源于 into 信号上 aa 与 ab 的一比特之差。看完关闭 Notepad。
- 把比较数据保存为可重新加载的文件:
a. 选择 Tools > Waveform Compare > Difference > Save,点 Save——在当前目录保存
compare.dif(差异数据); b. 选择 Tools > Waveform Compare > Rules > Save,点 Save——保存compare.rul(比较规则); c. 选择 Tools > Waveform Compare > End Comparison 结束本次比较。
重新加载前,先打开参考数据集
比较结果(.dif)和比较规则(.rul)都依赖参考数据集。重新加载时如果没有先打开 gold.wlf,比较就无法恢复。
- 重新加载比较数据: a. 在 Structure (sim) 窗口激活的状态下,选择 File > Open; b. 把文件类型切换为 Log Files (*.wlf):

c. 双击 gold.wlf 打开该数据集;
d. 选择 Tools > Waveform Compare > Reload。由于保存时用的是默认文件名,对话框里已自动填好正确的 Waveform Rules 和 Waveform Difference 文件:

e. 点 OK。比较被重新加载,把比较对象拖到 Wave 或 List 窗口即可再次查看差异。
本章收尾
本节结束。继续下一章前需要结束当前仿真并关闭 gold.wlf 数据集:
- 在 VSIM> 提示符输入
quit -sim; - 在 Questa SIM> 提示符输入
dataset close gold。
DO 文件与 Tcl:把重复劳动交给脚本(对应原文第 19 章)
DO 文件为什么值得学
前面的章节里,除了执行几个现成的 DO 文件,我们一直以交互模式使用 QuestaSim:通过 GUI 菜单或主窗口命令行一条条敲命令。但当任务需要反复执行时,DO 文件能大幅提升效率——这就是它 automates 仿真的价值所在:
- DO 文件是脚本,一次执行一大批命令;
- 简单时就是一串带参数的 QuestaSim 命令;复杂时就是完整的 Tcl 程序,能用变量、条件执行等编程结构;
- 既可以在 GUI 里执行,也可以在系统命令提示符下运行——完全不打开 GUI。
前提
本章假设你已把
<install_dir>/<platform>目录加入 PATH;否则需要为 vlib、vmap、vlog、vcom、vsim 等工具写完整路径。
FPGA 语境
用安路 TD 做开发时,工程编译靠 IDE 一键完成,但功能仿真回归往往要反复跑。把「编译 + 加载 + 加激励 + 出报告」写成 DO 文件,一次投入,长期复用,这正是工业化验证流程的基础。
手写一个简单的 DO 文件
创建 DO 文件简单到什么程度?在文本文件里敲一组命令就行。本练习创建一个 DO 文件:加载设计、往 Wave 窗口加信号、给信号加激励、推进仿真。此外还可以把保存的 transcript 文件转换成 DO 文件(参阅 GUI Reference Manual 中的 Saving a Transcript File as a DO file)。
- 切换到 Basic Simulation 一课创建的目录。
- 创建 DO 文件:选择 File > New > Source > Do 新建,在 Source 窗口输入:
vsim testcounter_opt
add wave count
add wave clk
add wave reset
force -freeze clk 0 0, 1 {50 ns} -r 100
force reset 1
run 100
force reset 0
run 300
force reset 1
run 400
force reset 0
run 200逐条看懂这些命令:
vsim testcounter_opt:加载优化后的设计;add wave ...:把 count、clk、reset 三个信号加入 Wave 窗口;force -freeze clk 0 0, 1 {50 ns} -r 100:制造时钟——在 0 时刻把 clk 强制为 0,50 ns 后变为 1,-r 100表示每 100 ns 重复一次,即周期 100 ns 的方波;-freeze表示强制期间信号值被冻结;force reset 1 / run 100 / force reset 0 / run 300 / ...:一段完整的复位时序——先复位保持 100 ns,释放 300 ns,再复位 400 ns,再释放 200 ns,覆盖复位与正常工作两种情形。
- 保存文件:File > Save As,文件名填
sim.do,存到当前目录。 - 执行 DO 文件:在 VSIM> 提示符输入:
do sim.doQuestaSim 加载设计、执行保存的命令并在 Wave 窗口画出波形:

- 练习完成后选择 File > Quit 退出 QuestaSim。
命令行模式:不开 GUI 也能仿真
「命令行模式」(command-line mode)指不启动 GUI、直接在 DOS/UNIX 提示符下运行仿真。QuestaSim 的许多命令(vsim、vlib、vlog 等)本身就是独立的可执行文件,可以在系统命令提示符直接调用;还可以在调用仿真器时指定一个包含其他 QuestaSim 命令的 DO 文件。
-
新建目录,复制以下文件进去:
/<install_dir>/examples/tutorials/verilog/automation/counter.v/<install_dir>/examples/tutorials/verilog/automation/stim.do
(有 VHDL license 的可改用
/<install_dir>/examples/tutorials/vhdl/automation目录下的 counter.vhd 和 stim.do。) -
创建设计库并编译源文件。在第 1 步的新目录里输入: a.
vlib work; b. Verilog 输入vlog counter.v(VHDL 则输入vcom counter.vhd)。 -
创建 DO 文件: a. 打开文本编辑器; b. 输入以下内容:
# list all signals in decimal format
add list -decimal *
#change radix to symbolic
radix -symbolic
# read in stimulus
do stim.do
# output results
write list counter.lst
# quit the simulation
quit -fc. 以 sim.do 为名保存到当前目录。
注意这个 DO 文件的精妙之处:add list -decimal * 把所有信号按十进制记录到 List 窗口;do stim.do 嵌套调用另一个现成的激励 DO 文件;write list counter.lst 把仿真结果输出到文本文件;quit -f 强制退出——一整条「批处理流水线」。
- 优化 counter 设计单元:
vopt +acc counter -o counter_opt- 运行命令行模式仿真:
vsim -c -do sim.do counter_opt -wlf counter_opt.wlf两个关键参数:-c 指示 QuestaSim 不启动 GUI;-wlf 把仿真结果保存进 WLF 文件——这样之后仍可在 GUI 里查看结果用于调试。
- 查看 List 输出:打开
counter.lst查看仿真结果,Verilog 版输出类似下表(VHDL 版可能略有不同):
| counter.lst | |||
|---|---|---|---|
| 0 | 10 | 20 | 30 40 |
| 1 | ns | /counter/count | |
| 2 | delta | /counter/clk | |
| 3 | /counter/reset | ||
| 4 | 0 +0 | x z 1 | |
| 5 | 3 +0 | 0 z 1 | |
| 6 | 50 +0 | 0 1 1 | |
| 7 | 100 +0 | 0 0 1 | |
| 8 | 100 +1 | 0 0 0 | |
| 9 | 150 +0 | 0 1 0 | |
| 10 | 152 +0 | 1 1 0 | |
| 11 | 200 +0 | 1 0 0 | |
| 12 | 250 +0 | 1 1 0 | |
| 13 | 252 +0 | 2 1 0 | |
| 14 | 300 +0 | 2 0 0 | |
| 15 | 350 +0 | 2 1 0 |
- 回 GUI 查看结果。因为结果已保存进
counter_opt.wlf,用-view参数调用 VSIM 即可在 GUI 中查看(确认 PATH 环境变量指向当前版本的 QuestaSim): a. 在提示符输入:
vsim -view counter_opt.wlfGUI 打开后,主窗口工作区显示名为 counter_opt 的数据集标签(看不到 Objects 窗口就选 View > Objects):
counter_opt - Default
Instance Design unit
counter counter
y Files counter_opt Objects
Name Value Kind
count 8'h64 Packed Array
clk 1'h0 Net
reset 1'h0 Netb. 右键 counter 实例选 Add Wave,波形即显示在 Wave 窗口。 8. 看完选择 File > Quit 关闭 QuestaSim。
回归仿真的标准姿势
-c(无界面)跑完仿真 +-wlf保存结果 +vsim -view xxx.wlf事后查看,是自动化回归测试的标配组合:跑的时候不占 GUI 资源,出问题了再打开波形细看。
用 Tcl 编程:when + bookmark 自动标注关键波形
前面用的 DO 文件只包含 QuestaSim 命令,但 DO 文件本质上是 Tcl 脚本——所以过程(procedure)、条件运算、数学与三角函数、正则表达式等各种 Tcl 结构都可以用。
本练习写一个简单的 Tcl 脚本:检测信号上的特定值,并在该值出现时自动添加书签(bookmark)来缩放 Wave 窗口。书签可以保存 Wave 窗口的某个缩放范围和滚动位置;脚本还会在主窗口创建名为书签的按钮。
- 创建脚本: a. 在文本编辑器中打开新文件,输入:
proc add_wave_zoom {stime num} {
echo "Bookmarking wave $num"
bookmark add wave "bk$num" "[expr $stime - 100] [expr $stime + 50]" 0
add button "$num" [list bookmark goto wave bk$num]}这几行做的事:
-
定义一个名为
add_wave_zoom的过程,带两个参数 stime(时间)和 num(编号); -
用
bookmark add wave创建书签,缩放范围是「当前仿真时间减 100 个时间单位」到「当前仿真时间加 50 个时间单位」([expr ...]是 Tcl 的算术表达式求值); -
用
add button在主窗口加一个按钮,点击即跳转到该书签。b. 在脚本底部追加:
add wave -r /*
when {clk'event and clk="1"} {
echo "Count is [exa count]"
if {[examine count]== "8'h27"} {
add_wave_zoom $now 1
} elseif {[examine count]== "8'h47"} {
add_wave_zoom $now 2
}
}这几行做的事:
-
add wave -r /*:递归地把所有信号加入 Wave 窗口; -
when {clk'event and clk="1"}:QuestaSim 的 when 语句——每当 clk 发生事件且变为 1(即时钟上升沿)时,执行花括号里的命令; -
每个上升沿用
examine(缩写 exa)读出 count 的当前值并 echo 打印;如果等于 8’h27 就在当前位置建 1 号书签,等于 8’h47 就建 2 号书签——脚本根据仿真内容自动标注了关键时间点,人不用盯着波形找。c. 以
add_bkmrk.do为名,把脚本保存到 Basic Simulation 一课创建的目录。
- 加载 test_counter 设计单元,并确保进制设为二进制:
a. 启动 QuestaSim;
b. File > Change Directory 切换到保存 DO 文件的目录;
c. 在 Questa SIM> 提示符输入
radix -binary; d. 输入:
vsim testcounter_opt- 执行 DO 文件并运行:
a. 在 VSIM> 提示符输入
do add_bkmrk.do; b. 输入run 1500 ns。
仿真运行完毕,DO 文件创建了两个书签,并在主窗口工具栏上生成了标注「1」「2」的按钮:

c. 点击这两个按钮,观察 Wave 窗口自动缩放并滚动到 count 等于指定值的时间点; d. 若 Wave 窗口停靠在主窗口内,先点击波形区域使其成为活动窗口,再选 Bookmarks > bk1(未停靠则在 Wave 窗口菜单选)。Wave 窗口会缩放滚动到 count 为 8’h27 的时刻,bk2 同理。
本章收尾
选择 File > Quit 关闭 QuestaSim,本章结束。想继续深挖 Tcl,可参阅 User’s Manual 中的 Tcl and DO Files 章节,以及 Brent B. Welch 的《Practical Programming in Tcl and Tk》。
自测
自测
- 波形比较的一般流程分哪四步?本例中 Comparison Wizard 的哪些对话框分别对应了这几步?
- 保存可重新加载的比较数据时必须保存哪两个文件?重新加载前有什么前提条件?
vsim -c -do sim.do counter_opt -wlf counter_opt.wlf中的-c和-wlf分别起什么作用?仿真结束后如何回到 GUI 查看保存的结果?force -freeze clk 0 0, 1 {50 ns} -r 100是如何产生一个周期 100 ns 时钟的?- 脚本里的
when {clk'event and clk="1"}语句在什么时机触发?触发后脚本利用书签和按钮实现了什么效果?