3、仿真调试基础:VCS/Verilog仿真器使用、波形查看技巧、断言(Assertion)基础
3.1 VCS仿真器:从编译到运行
做数字验证,VCS 是绕不开的工具。我个人习惯把仿真流程拆成三步:编译、运行、调试。
编译阶段,说白了就是把你的 RTL 代码和 testbench 翻译成可执行的二进制文件。我常用的命令长这样:
vcs -full64 -sverilog +v2k -debug_access+all \
-f filelist.f -l compile.log \
-timescale=1ns/1ps \
-o simv
这里有几个关键点:
-debug_access+all:打开全部调试能力,波形 dump 全靠它-sverilog:支持 SystemVerilog 语法,断言和覆盖率都靠它-timescale:定义时间精度,我建议统一用 1ns/1ps
编译通过后,运行就简单了:
./simv -l run.log +UVM_TESTNAME=my_test
嗯,这里要注意:+UVM_TESTNAME 是 UVM 的标配,如果你用纯 Verilog 做验证,直接跑 ./simv 就行。
我的小技巧: 编译时加
-j8 可以并行编译,大型设计能省一半时间。我曾经有个项目,编译一次要 40 分钟,加了并行后缩到 12 分钟。
3.2 波形查看:别只会看波形
波形是验证工程师的眼睛。但很多人只会拖拽信号看高低电平,这远远不够。
我常用的波形查看流程:
- 先看顶层接口信号(时钟、复位、握手信号)
- 再看关键状态机跳转
- 最后深入数据通路
在 VCS 里 dump 波形,我推荐用 VPD 格式:
initial begin
$vcdpluson(0, tb_top);
$vcdplusmemon;
end
或者用更现代的 FSDB 格式(配合 Verdi 使用):
initial begin
$fsdbDumpfile("wave.fsdb");
$fsdbDumpvars(0, tb_top);
$fsdbDumpMDA(); // dump 多维数组
end
避坑指南: 我曾经 debug 一个数据错位问题,看了三天波形没找到原因。后来发现是没 dump 内部数组信号。加上
$fsdbDumpMDA() 后,十分钟就定位到了问题。
波形查看的几个实用技巧:
- 设置标记点: 在关键时间点按 M 键标记,方便来回跳转
- 信号分组: 把地址、数据、控制信号分不同颜色组
- 使用公式: 在 Verdi 里可以直接写表达式,比如
data_in === data_out来快速比对 - 查找上升沿: 用
@(posedge clk)快速定位时钟沿
3.3 断言(Assertion)基础:让仿真自己发现问题
断言是什么?说白了就是给仿真器装上一双眼睛,让它自己盯着关键行为。出错了立刻报,不用你一行行看波形。
断言分两种:
- 立即断言: 像 if 语句一样,在仿真时刻立即检查
- 并发断言: 基于时钟沿,检查跨周期的时序关系
先看一个简单的立即断言:
always_comb begin
assert (data_valid && data_ready) else
$error("握手信号同时为高时出错");
end
再看并发断言,这才是重头戏:
property p_req_ack;
@(posedge clk)
req |=> ##[1:3] ack;
endproperty
assert property (p_req_ack)
else $fatal("请求后 1-3 个周期内必须有应答");
这里 |=> 表示「下一个时钟沿」,##[1:3] 表示 1 到 3 个周期内。你想想看,这种跨周期的检查,用手动看波形得累死。
注意: 断言不是万能的。我曾经在一个项目里写了 200 多条断言,结果仿真跑得巨慢。后来发现是某个断言里用了
$past() 函数,每次时钟沿都要回溯 100 个周期。优化后速度提升了 3 倍。
3.4 实战:断言 + 波形联动调试
光写断言不够,你得知道断言失败时怎么快速定位。我的做法是三步走:
- 看断言失败信息: 仿真日志里会打印失败时间、信号值
- 打开波形定位: 在 Verdi 里按 F 键直接跳转到失败时间点
- 回溯原因: 从失败点往前推,看是什么导致了违规
举个例子,假设断言报 req_ack 失败:
# 仿真日志输出
"test.sv":45: (vlog-2110) Assertion failed: p_req_ack
Time: 1200 ns, Scope: tb_top.u_dut
Values: req=1, ack=0
看到这个,我直接打开波形,定位到 1200ns。发现 req 在 1100ns 拉高,但 ack 一直没反应。再往前看,发现复位信号在 1050ns 被拉低了——原来是复位期间发了请求,逻辑没处理。
我的经验: 断言失败后,别急着改代码。先问三个问题:
1. 是设计 bug 还是断言写错了?
2. 这个场景在 spec 里有没有定义?
3. 其他模块有没有类似问题?
我曾经因为一个断言失败,顺藤摸瓜找到了三个关联模块的 bug。
1. 是设计 bug 还是断言写错了?
2. 这个场景在 spec 里有没有定义?
3. 其他模块有没有类似问题?
我曾经因为一个断言失败,顺藤摸瓜找到了三个关联模块的 bug。
3.5 本章知识体系
下面这张图总结了仿真调试的核心逻辑,从编译到波形再到断言,环环相扣:
这张图展示了仿真调试的完整闭环。你从编译开始,跑出波形后用断言自动检查,发现问题再回到编译阶段修改。循环越快,验证效率越高。
最后说一句: 仿真调试不是看谁波形看得快,而是看谁能用工具把问题自动揪出来。断言就是你的得力助手,别嫌麻烦,多写几条,后面 debug 能省十倍时间。
可可精品资料库