2. VCS编译与仿真流程实战
上一节我们理清了UVM验证平台的整体结构,但再漂亮的架构也必须落到工具链上跑起来才有意义。本节的目标很明确:用一个最小的SystemVerilog模块,把vcs编译 → simv仿真 → 波形查看 → urg覆盖率收集这条完整链路走通一遍,形成后续所有章节都可以复用的操作基线。这条基线一旦建立,后面无论是加UVM组件、加约束、加回归脚本,都只是在这条链路上做增量,而不是重新摸索工具用法。
一、先明确四个核心命令的分工
VCS工具链里最常打交道的命令其实只有四个,但它们各自负责的阶段完全不同,混淆了就容易在排障时找错方向:
- vcs:编译命令。把SystemVerilog源码、UVM库、测试平台代码一起分析(analyze)、展开(elaborate),最终生成一个可执行仿真文件,默认名为
simv。它只做“翻译和链接”,不跑仿真。 - simv:仿真命令。由vcs生成的可执行文件,负责真正推进仿真时间、执行initial/always块、产生波形和覆盖率数据。
- urg:覆盖率报告命令。读取simv运行后产生的
.vdb覆盖率数据库,生成HTML或文本形式的覆盖率报告。 - dve / verdi:波形查看工具。dve是VCS自带的波形与调试环境,verdi是更常用的高级调试工具,两者都读取
.vcd或.fsdb波形文件。
理解这个分工后,一个完整的验证迭代循环就是:改代码 → vcs编译 → simv仿真 → 看波形/看覆盖率 → 回到改代码。下面这张图把这条链路和每个阶段的输入输出画清楚,后面所有操作都围绕它展开。
二、最小示例:一个带覆盖率的计数器
为了让链路可跑通,我们准备两个文件。第一个是被测设计counter.sv,一个4位同步计数器,带使能和复位:
module counter (
input logic clk,
input logic rst_n,
input logic en,
output logic [3:0] cnt
);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) cnt <= 4'd0;
else if (en) cnt <= cnt + 4'd1;
end
endmodule
第二个是测试平台tb_top.sv,它产生时钟、施加激励,并声明覆盖率采集:
module tb_top;
logic clk = 0;
logic rst_n, en;
logic [3:0] cnt;
counter dut (.clk(clk), .rst_n(rst_n), .en(en), .cnt(cnt));
always #5 clk = ~clk; // 10ns 周期
initial begin
rst_n = 0; en = 0;
#20 rst_n = 1;
repeat (20) begin
@(posedge clk);
en = $urandom_range(0,1);
end
#20 $finish;
end
initial begin
$dumpfile("dump.vcd");
$dumpvars(0, tb_top);
end
endmodule
注意$dumpfile/$dumpvars负责产生波形,而覆盖率不是靠代码里的$dump,而是靠编译和仿真时的-cm选项打开。这是初学者最容易混淆的一点:波形靠系统函数,覆盖率靠命令行开关。
三、编译与仿真命令逐条拆解
编译命令建议写成一行,方便后续放进Makefile:
vcs -sverilog -full64 -debug_access+all \
-cm line+cond+fsm+branch \
-l compile.log \
counter.sv tb_top.sv
各选项含义:-sverilog启用SystemVerilog语法;-full64生成64位可执行文件;-debug_access+all打开调试访问权限,否则dve/verdi无法读取内部信号;-cm line+cond+fsm+branch指定采集行覆盖率、条件覆盖率、状态机覆盖率和分支覆盖率;-l compile.log把编译日志写入文件,便于出错时回溯。编译成功后当前目录会出现simv和simv.daidir。
仿真命令必须带上与编译一致的-cm选项,否则覆盖率数据库不会生成:
./simv -cm line+cond+fsm+branch -l sim.log
运行结束后,目录里会多出simv.vdb(覆盖率数据库)和dump.vcd(波形文件)。如果仿真报“$finish at simulation time 220”,说明正常结束;如果卡住不动,通常是initial块里缺少$finish或时钟没有翻转。
四、波形查看与覆盖率报告
查看波形有两种常用方式。用VCS自带的dve:
dve -vpd dump.vcd &
用Verdi(需要VCS支持FSDB,通常在testbench里改用$fsdbDumpfile):
verdi -ssf dump.fsdb &
打开后重点观察三件事:复位期间cnt是否被清零、en=1时cnt是否在每个时钟上升沿加1、cnt从15回到0时是否自然回绕。这三条对上了,说明设计和激励的基本行为正确。
覆盖率报告用urg生成:
urg -dir simv.vdb -report urgReport
执行后打开urgReport/dashboard.html,可以看到各类覆盖率的汇总百分比。对于这个最小例子,行覆盖率通常能到较高水平,但条件覆盖率往往偏低,因为en的0/1组合虽然都出现过,但rst_n与en的交叉组合未必被激励覆盖到。这正是覆盖率驱动验证的起点:看到未覆盖项,回到testbench补充激励,再重新编译仿真。
make comp、make sim、make cov三条命令即可完成迭代。这是建立工具链操作基线最实用的产出。
五、常见报错与判断依据
| 现象 | 可能原因 | 处理方向 |
|---|---|---|
| 编译报“token is not a valid” | 缺少-sverilog,或文件扩展名不是.sv | 补上编译选项,确认文件后缀 |
| simv运行后没有simv.vdb | 仿真命令漏了-cm选项 | 编译和仿真两侧的-cm必须一致 |
| dve打开看不到内部信号 | 编译时未加-debug_access+all | 重新编译,加上调试访问选项 |
| 仿真一直不结束 | initial块缺少$finish | 在激励末尾加$finish |
| urg报告覆盖率全为0 | vdb路径错误或未采集 | 确认-dir指向实际vdb目录 |
把这条链路走通之后,你就拥有了一个可重复、可扩展的最小验证环境。后续引入UVM的driver、monitor、scoreboard时,编译命令会增加UVM库路径和-ntb_opts uvm,仿真命令会增加+UVM_TESTNAME,但底层的“编译—仿真—波形—覆盖率”四步结构不会变。本节的价值就在于把这个不变的结构先钉死,让后面所有复杂内容都有落脚点。