第四章:代码覆盖率分析

代码覆盖率,说白了就是衡量你的验证到底「覆盖」了多少设计代码。我见过不少工程师,跑完仿真看到 100% 的行覆盖率就以为万事大吉了——结果芯片回来照样出 bug。为什么?因为行覆盖率只是最基础的一层,真正的坑在后面。

我个人习惯把代码覆盖率分成五个维度来看:行、分支、条件、路径、触发条件。每个维度都有它的侧重点,也有它的局限性。咱们一个一个说。

4.1 行覆盖率

行覆盖率是最直观的。它告诉你:每一行代码,仿真时有没有被执行到。

举个例子:

always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        cnt <= 0;
    end else if (en) begin
        cnt <= cnt + 1;
    end
end

如果仿真时 rst_n 从来没拉低过,那 cnt <= 0 这一行就是 0% 覆盖。嗯,这其实是个危险信号——你的复位逻辑根本没被验证到。

关键点:行覆盖率 100% 不代表逻辑正确。它只说明代码被执行了,但执行的结果对不对,它不管。

我在项目中遇到过这样的情况:行覆盖率 98%,但有一个分支条件从来没被触发过。那个分支恰恰是处理边界情况的——结果流片回来,边界场景下芯片直接挂死。

4.2 分支覆盖率

分支覆盖率看的是 if-else、case 语句的每个分支有没有走到。你想想看,一个 if 语句有两个分支:条件为真走一个,条件为假走另一个。行覆盖率只看 if 里面的代码有没有执行,但分支覆盖率会检查「条件为假」那个分支有没有被覆盖。

if (data_valid) begin
    data_out <= data_in;
end else begin
    data_out <= 0;
end

行覆盖率可能显示两行都执行了,但分支覆盖率会告诉你:data_valid 为假的那个分支,真的被触发过吗?

我的经验:分支覆盖率低于 90% 的模块,我一般不会签收。尤其是 case 语句的 default 分支,很多人忘了覆盖它。

4.3 条件覆盖率

条件覆盖率比分支覆盖率更细。它看的是每个条件表达式里的子条件。比如:

if (a && b) begin
    // do something
end

分支覆盖率只关心 a && b 整体为真或为假。但条件覆盖率会检查:a=1, b=0 的情况有没有?a=0, b=1 的情况有没有?a=0, b=0 呢?

我曾经吃过这个亏。一个组合逻辑里写了 if (sel_a || sel_b),仿真时 sel_a 和 sel_b 从来没同时为 0 过。结果某个配置下两个信号都拉低,逻辑直接跑飞了。

注意:条件覆盖率要求每个子条件独立翻转。如果表达式里有短路逻辑(比如 && 和 ||),有些组合可能永远测不到——但这恰恰是你要关注的。

4.4 路径覆盖率

路径覆盖率是代码覆盖率里最难搞的一个。它看的是从输入到输出的所有组合路径。一个简单的 if-else 嵌套,路径数量就可能爆炸。

if (a) begin
    if (b) begin
        // path 1
    end else begin
        // path 2
    end
end else begin
    if (c) begin
        // path 3
    end else begin
        // path 4
    end
end

这里有 4 条路径。如果再加一个条件,路径数翻倍。实际项目中,路径覆盖率很少要求 100%,因为不现实。但关键路径——比如状态机跳转、仲裁逻辑——我会特别关注。

实用建议:路径覆盖率不要盲目追求 100%。先分析哪些路径是「可达的」,哪些是「冗余的」。冗余路径覆盖不到,反而说明设计可能有问题。

4.5 触发条件

触发条件覆盖率,也叫 toggle coverage。它看的是每个信号有没有从 0 翻到 1、从 1 翻到 0。听起来简单,但实际很有用。

我记得有一次做 PCIe 控制器验证,行覆盖率 99%,分支覆盖率 95%,但触发条件覆盖率只有 60%。一查,发现很多控制信号从来没翻转过——比如中断请求信号一直为 0。这说明中断逻辑根本没被触发过。

覆盖率类型 关注点 典型目标 我的建议
行覆盖率 代码是否被执行 100% 基础,但别迷信
分支覆盖率 分支是否都走到 >90% 重点关注 default
条件覆盖率 子条件是否独立覆盖 >85% 小心短路逻辑
路径覆盖率 组合路径是否覆盖 关键路径 100% 别贪多,抓重点
触发条件 信号是否翻转 >95% 控制信号尤其重要

4.6 五种覆盖率的层次关系

这五种覆盖率不是孤立的。它们有层次关系:行覆盖率是最粗的,触发条件是最细的。但细不一定就好——关键是要找到「验证的盲区」。

我的工作流:先看行覆盖率,快速定位没执行到的代码。然后看分支和条件覆盖率,找逻辑分支的遗漏。最后看触发条件,确认关键信号都动过。路径覆盖率我一般只在状态机和复杂组合逻辑里用。

下面这张图展示了五种覆盖率的关系:

代码覆盖率五层模型 行覆盖率 分支覆盖率 条件覆盖率 路径覆盖率 触发条件覆盖率 关注信号翻转 独立于逻辑结构 最基础 逻辑分支 子条件独立 组合路径 从粗到细,逐层深入

4.7 避坑指南

最后,分享几个我踩过的坑:

  • 别只看汇总数据。 我曾经看到模块级覆盖率 95%,但某个子模块只有 30%。汇总数据会掩盖局部问题。
  • 小心死代码。 有些代码永远执行不到——比如 case 语句里多余的 branch。行覆盖率 100% 但死代码还在,说明设计有问题。
  • 触发条件不是越高越好。 有些信号本来就不该翻转(比如常高或常低的配置信号)。强行让它翻转反而会引入 bug。
  • 路径覆盖率别贪多。 我见过有人把路径覆盖率目标定到 100%,结果写了上千个 testcase 还没跑完。不现实。

总结一下:代码覆盖率是验证的「体检报告」,不是「健康证明」。它能告诉你哪里没测到,但不能告诉你测到的地方对不对。真正靠谱的验证,还是要靠功能覆盖率和断言来补位。

嗯,这一章就到这里。代码覆盖率是验证的基础功,但也是最容易被忽视的。希望你能从这五个维度重新审视自己的验证环境——说不定能发现不少盲区。


公众号:蓝海资料掘金营,微信deep3321