4、Modelsim/Questa Sim 脚本化:命令行编译、仿真、波形查看
说实话,很多工程师用了好几年 Modelsim,还是习惯点鼠标。点一下编译,点一下仿真,再点一下添加波形。嗯,一次两次还行,但如果你要跑 50 个测试用例呢?手指头都得点抽筋。
我个人习惯是:能用脚本解决的,绝不用鼠标。脚本化仿真,说白了就是把你的操作步骤写成 .do 文件。这样不仅快,还能保证每次跑的流程一模一样。我在项目中遇到过好几次,同事手动操作漏了某个编译选项,结果仿真结果对不上,查了一整天。
4.1 为什么非要脚本化?
你想想看,手动仿真最大的问题是什么?
- 不可重复:今天点的步骤,明天可能就忘了
- 效率低:每次改完代码,都要重新点一遍
- 容易出错:少点一个库,仿真直接崩
- 没法集成:CI/CD 流程里,谁帮你点鼠标?
脚本化之后,一个命令搞定全部。我团队里现在新来的同事,第一天我就让他们学 .do 文件怎么写。
4.2 基础:Tcl 脚本与 .do 文件
Modelsim 和 Questa Sim 都支持 Tcl 脚本。.do 文件就是 Tcl 脚本的一种形式。你可以在 GUI 里敲命令,也可以直接写文件批量执行。
最简单的例子:
# 编译设计文件
vlog -work work ../rtl/top.v
vlog -work work ../rtl/sub_module.v
# 编译测试平台
vlog -work work ../tb/tb_top.v
# 启动仿真
vsim -voptargs=+acc work.tb_top
# 添加波形
add wave -r /*
# 运行
run 1us
把这个保存成 run.do,然后在 Modelsim 里执行:
do run.do
搞定。就这么简单。
4.3 命令行编译:vlog 与 vcom
Modelsim 支持两种语言:Verilog 用 vlog,VHDL 用 vcom。我个人更常用 Verilog,所以重点讲 vlog。
常用选项:
| 选项 | 说明 | 示例 |
|---|---|---|
| -work | 指定库名 | vlog -work work file.v |
| -sv | 启用 SystemVerilog | vlog -sv file.sv |
| -cover | 开启覆盖率收集 | vlog -cover bces file.v |
| -mfcu | 多文件编译单元 | vlog -mfcu *.v |
| -lint | 检查 lint 警告 | vlog -lint file.v |
我曾经踩过一个坑:编译顺序。如果你的模块 A 调用了模块 B,必须先编译 B,再编译 A。否则会报找不到模块的错误。后来我学乖了,用文件列表 + 排序脚本来自动处理。
# 推荐做法:用文件列表
vlog -work work -f filelist.f
# filelist.f 内容示例
../rtl/sub_module.v
../rtl/top.v
../tb/tb_top.sv
4.4 命令行仿真:vsim 的进阶用法
编译完就是仿真。vsim 命令的选项比 vlog 还多,但常用的就几个:
# 基本用法
vsim -voptargs=+acc work.tb_top
# 带参数传递
vsim -voptargs=+acc work.tb_top -gTEST_WIDTH=32
# 批量跑测试用例
vsim -c -voptargs=+acc work.tb_top -do "run -all; exit"
这里有个关键点:-voptargs=+acc。这个选项会保留所有信号的可见性,方便调试。但代价是仿真速度会慢一点。如果你只关心最终结果,可以去掉它。
我一般这样用:
- 调试阶段:加
+acc,方便看波形 - 回归测试:不加
+acc,跑得快
4.5 波形查看:自动添加与保存
手动添加波形太累了。尤其是几百个信号的设计,一个个点得点到什么时候?
我的做法是:写一个专门的 wave.do 文件。
# wave.do
onerror {resume}
# 清除旧波形
quietly WaveRestoreZoom {0 ps} {1 us}
# 添加顶层信号
add wave -divider "Top Level"
add wave -hex /tb_top/dut/clk
add wave -hex /tb_top/dut/rst_n
# 添加内部总线
add wave -divider "Internal Bus"
add wave -hex /tb_top/dut/bus/addr
add wave -hex /tb_top/dut/bus/data
add wave /tb_top/dut/bus/valid
# 添加状态机
add wave -divider "FSM"
add wave -hex /tb_top/dut/fsm/state
add wave /tb_top/dut/fsm/state_next
# 保存波形配置
wave save wave.do
然后在仿真脚本里调用:
vsim -voptargs=+acc work.tb_top
do wave.do
run 1us
add wave -r /* 可以递归添加所有信号。但信号太多时仿真会变慢。我建议只添加关键信号,或者分层次添加。
4.6 完整示例:一键仿真脚本
下面是我项目里常用的完整脚本,你可以直接拿去改:
# run_all.do
# 作者:我
# 功能:一键编译+仿真+波形查看
# 清理旧文件
if {[file exists work]} {
vdel -all
}
# 创建库
vlib work
vmap work work
# 编译 RTL
echo "=== Compiling RTL ==="
vlog -work work -sv ../rtl/*.sv
vlog -work work ../rtl/*.v
# 编译 TB
echo "=== Compiling Testbench ==="
vlog -work work -sv ../tb/tb_top.sv
# 检查编译错误
if {[vcom -work work -check_syntax ../rtl/*.vhd] != 0} {
echo "ERROR: Compilation failed!"
exit 1
}
# 启动仿真
echo "=== Starting Simulation ==="
vsim -voptargs=+acc -t 1ps work.tb_top
# 加载波形配置
if {[file exists wave.do]} {
do wave.do
} else {
add wave -r /*
}
# 运行
echo "=== Running ==="
run 10us
# 保存波形
wave save result.wlf
echo "=== Done ==="
执行方式:
vsim -c -do run_all.do
或者直接在 GUI 里:
do run_all.do
4.7 避坑指南
我这些年踩过的坑,分享给你:
- 路径问题:脚本里的路径尽量用相对路径。我见过有人用绝对路径,换台电脑就跑不了。
- 库冲突:不同版本的 Modelsim 库不兼容。记得每次跑之前
vdel -all清理一下。 - 退出码:在 CI 里跑仿真,记得检查退出码。仿真失败要返回非零值,否则 CI 会以为通过了。
- 波形文件太大:跑长时间仿真时,波形文件会很大。可以用
log -r /*只记录需要的信号。
-c 选项(命令行模式),结果仿真窗口弹出来卡住了,整个 CI 流程挂了半小时。从那以后,我所有自动化脚本都默认加 -c。
4.8 进阶:批量跑测试用例
如果你有多个测试用例,可以这样写:
# batch_run.do
set testcases {test1 test2 test3 test4}
foreach test $testcases {
echo "=== Running $test ==="
# 重新编译(如果需要)
vlog -work work ../tb/tb_${test}.sv
# 仿真
vsim -c -voptargs=+acc work.tb_${test} -do "
run -all;
if {[string equal [examine -radix hex /tb_top/dut/result] 32'hDEAD_BEEF]} {
echo \"PASS: $test\";
} else {
echo \"FAIL: $test\";
}
exit;
"
}
这样跑完所有用例,结果一目了然。我团队现在每天跑 200+ 个用例,全靠这套脚本撑着。
嗯,这一章就到这里。下一章我会讲如何用脚本做自动化回归测试和覆盖率收集,那才是真正的大招。