3. 代码覆盖率收集实战:使用VCS/IUS工具收集代码覆盖率,生成报告解读

代码覆盖率,说白了就是衡量你的验证到底跑了多少“路”。

我刚开始做验证那会儿,总觉得功能覆盖点写够了就万事大吉。直到有一次,一个很隐蔽的bug逃逸到了后端——原因很简单:有一段代码分支,仿真从来没进去过。从那以后,我对代码覆盖率再也不敢马虎了。

这一章,我们就来聊聊怎么用VCS和IUS(现在叫Xcelium)这两个主流工具,把代码覆盖率跑起来,并且把报告看懂。

3.1 什么是代码覆盖率?为什么它重要?

代码覆盖率衡量的是:你的仿真用例,到底执行了设计代码的哪些部分。

它不像功能覆盖率那样关心“业务场景是否完备”,它只关心“代码本身有没有被跑到”。

核心观点:代码覆盖率是验证完备性的“底线检查”。如果一段代码从未被执行过,那它里面的bug永远不可能被发现。

常见的代码覆盖率类型有这些:

覆盖率类型 含义 我关注的点
行覆盖率 (Line) 每一行代码是否被执行过 最基础,但容易被满足
翻转覆盖率 (Toggle) 每个信号是否发生过0→1和1→0的跳变 检查信号活动性,对低功耗验证很重要
条件覆盖率 (Condition) 条件表达式中的每个子条件是否取过真/假 容易遗漏,我吃过亏
分支覆盖率 (Branch) if/case语句的每个分支是否进入过 必须100%,否则有死代码风险
状态机覆盖率 (FSM) 状态机的每个状态和转移是否被访问过 复杂FSM的噩梦

3.2 使用VCS收集代码覆盖率

VCS是Synopsys家的仿真器。我个人习惯在编译和运行阶段分别加开关。

3.2.1 编译阶段:开启覆盖率收集

编译时,加上 -cm 选项来指定要收集的覆盖率类型。

vcs -sverilog -cm line+cond+tgl+branch+fsm \
    -cm_dir ./cov_dir \
    top_tb.sv top_design.v \
    -l vcs_compile.log

这里 -cm_dir 指定覆盖率数据库存放目录。如果不指定,默认会生成 simv.cm 目录。

我的小技巧:建议每次回归都指定独立的 -cm_dir,比如 ./cov_dir/run_001,方便后续合并。

3.2.2 运行阶段:生成覆盖率数据

运行仿真时,同样需要加 -cm 选项:

./simv -cm line+cond+tgl+branch+fsm \
       -cm_dir ./cov_dir \
       -l vcs_sim.log

仿真结束后,会在 ./cov_dir 下生成 .vdb 文件。这就是原始的覆盖率数据库。

3.2.3 合并与报告生成

多轮回归跑完后,可以用 urg 工具合并并生成报告:

urg -dir ./cov_dir/run_001 \
       ./cov_dir/run_002 \
       ./cov_dir/run_003 \
    -dbname merged_cov \
    -report urg_report \
    -l urg.log

执行后,urg_report 目录下会生成HTML报告。用浏览器打开 urg_report/dashboard.html 就能看到汇总信息。

3.3 使用IUS/Xcelium收集代码覆盖率

Cadence家的工具,命令稍有不同,但思路一样。

3.3.1 编译与运行

使用 irun 或 xrun 命令,加 -coverage 选项:

xrun -sverilog -coverage all \
     -covoverwrite \
     -covdirectory ./cov_dir \
     top_tb.sv top_design.v \
     -l xrun.log

-coverage all 表示收集所有类型。你也可以指定 -coverage b(分支)、-coverage c(条件)等。

注意:IUS默认不会覆盖旧的覆盖率数据库。加上 -covoverwrite 可以避免每次都要手动删目录。但如果你需要保留历史数据,就别加这个选项。

3.3.2 报告生成

使用 imc 工具来查看和生成报告:

imc -load ./cov_dir \
    -report ./imc_report \
    -severity all \
    -log imc.log

或者用图形界面:

imc -load ./cov_dir &

我个人更喜欢命令行方式,方便集成到自动化流程里。

3.4 报告解读:怎么看?看什么?

报告生成了,但很多人只是看一眼“总覆盖率90%”就完事了。这远远不够。

我一般会按以下步骤来解读:

  1. 看全局汇总:先看总覆盖率,但别太当真。90%和95%在数字上差别不大,但背后可能差了好几个bug。
  2. 看最低的模块:找到覆盖率最低的模块。这通常是验证的薄弱环节。
  3. 看未覆盖的分支:重点分析分支覆盖率和条件覆盖率。行覆盖率很容易到100%,但分支未必。
  4. 分析未覆盖的原因:是死代码?还是遗漏了测试场景?

避坑指南:我曾经遇到过一个项目,行覆盖率99%,但分支覆盖率只有60%。一查,发现一个复杂的case语句,有十几个分支只跑到了3个。那里面藏了一个地址译码错误——如果当时只看行覆盖率,这个bug就漏了。

3.5 代码覆盖率收集流程总览

下面这张图,是我自己总结的代码覆盖率收集流程。你可以把它贴在工位旁边。

代码覆盖率收集流程 RTL设计代码 编译:加 -cm / -coverage 仿真运行 生成 .vdb 数据库 urg / imc 合并生成报告 关键开关 VCS: -cm line+cond+tgl IUS: -coverage all 合并: urg -dir ... 常见坑 忘记加 -cm 开关 目录被覆盖没备份 只看行覆盖率

3.6 常见问题与避坑

  • 覆盖率数据为0? 检查编译和运行是否都加了 -cm 或 -coverage 选项。我经常忘记在运行阶段加。
  • 合并后覆盖率反而降低了? 检查是否合并了不同版本的代码。不同版本的代码覆盖率不能合并。
  • 某些分支永远覆盖不到? 可能是死代码,也可能是验证场景遗漏。先确认是否是设计冗余,再决定是否要补用例。
  • 报告打开是空的? 检查 -cm_dir 路径是否正确,以及是否有写权限。

重要提醒:代码覆盖率不是越高越好。100%的代码覆盖率不代表没有bug。它只是告诉你“代码被执行了”,但不保证执行的结果是正确的。功能覆盖率才是验证完备性的最终裁判。