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仿真 → 看波形/看覆盖率 → 回到改代码。下面这张图把这条链路和每个阶段的输入输出画清楚,后面所有操作都围绕它展开。

VCS 最小验证链路:编译 → 仿真 → 波形 / 覆盖率 源码输入 counter.sv tb_top.sv vcs 编译 -sverilog -full64 -debug_access+all -cm line+cond+fsm simv 仿真 ./simv +ntb_random_seed -cm line+cond+fsm -l sim.log 波形文件 dump.vcd / dump.fsdb 覆盖率数据库 simv.vdb dve / verdi 查看波形、定位信号 urg 覆盖率报告 urg -dir simv.vdb 迭代回路:改代码 → 重新编译 → 重新仿真

二、最小示例:一个带覆盖率的计数器

为了让链路可跑通,我们准备两个文件。第一个是被测设计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补充激励,再重新编译仿真。

建议把上述命令固化成一个Makefile,后续每节只需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报告覆盖率全为0vdb路径错误或未采集确认-dir指向实际vdb目录

把这条链路走通之后,你就拥有了一个可重复、可扩展的最小验证环境。后续引入UVM的driver、monitor、scoreboard时,编译命令会增加UVM库路径和-ntb_opts uvm,仿真命令会增加+UVM_TESTNAME,但底层的“编译—仿真—波形—覆盖率”四步结构不会变。本节的价值就在于把这个不变的结构先钉死,让后面所有复杂内容都有落脚点。