3. 代码覆盖率基础:语句、分支与块覆盖

芯片验证和软件验证都在回答同一个问题:测试到底有没有把设计跑透?代码覆盖率是从代码静态结构出发,度量测试激励对实现代码的“触及程度”。如果覆盖率长期不收敛,往往意味着测试约束、激励生成或检查逻辑存在盲区。本章聚焦最基础的三种度量粒度:语句覆盖(Statement/Line Coverage)、块覆盖(Block Coverage)和分支覆盖(Branch Coverage)。

本节核心问题

  • 为什么语句覆盖接近 100%,仍可能漏掉严重功能缺陷?
  • 块覆盖和语句覆盖本质区别在哪里?
  • 分支覆盖在验证收敛中扮演什么角色?

3.1 核心概念

覆盖类型 覆盖对象 典型粒度 一句话理解
语句覆盖 可执行语句 每一条语句 每一行可执行代码是否被至少执行一次
块覆盖 基本块 连续顺序执行语句集合 每个基本块是否至少被进入一次
分支覆盖 条件转移边 if/else、case、三目运算 每个分支真/假方向是否都被走到

下图展示三种覆盖在验证收敛中的关系:测试激励先驱动执行,语句、块、分支从不同角度度量执行痕迹,最终汇入覆盖率综合评估。

测试激励执行 语句覆盖 每个语句执行 块覆盖 每个基本块进入 分支覆盖 每个方向走通 覆盖率计算 与收敛评估 三种覆盖从不同代码结构粒度反映测试进展,共同支撑收敛判断

3.2 原理机制

语句覆盖(Statement Coverage)是最直观的度量:将被测代码中的可执行语句一一列出,测试执行过程中至少执行到的语句数占总语句数的比例。它只能回答“这条语句有没有跑过”,不能回答“这条语句所有关键情形是否都验证过”。

块覆盖(Block Coverage)把没有分支、连续顺序执行的一组语句视为一个“基本块”,覆盖率按基本块计算。相比逐语句统计,块覆盖降低了冗余度,更接近功能单元的执行轨迹。

分支覆盖(Branch Coverage)针对条件跳转,统计每个分支入口的“真/假”两侧是否都被到达。例如 if (a && b) 即使整体条件为真多次,仍需要覆盖整体为假的分支路径。

验证启示:覆盖率工具通常基于插桩实现,在语句、分支或块入口处插入计数代码。硬件验证中,SystemVerilog 覆盖率语法(covergroup / coverpoint / cross)和仿真器内置的代码覆盖率工具都是类似思路。

3.3 分析流程

  1. 选择覆盖模型:明确以语句、块、分支中哪些作为收敛指标,通常分支覆盖被作为更可靠的“底线”。
  2. 运行回归测试:收集仿真/执行过程中的覆盖数据。
  3. 识别未覆盖区域:按模块、层级或功能聚类未命中点,定位未被激励驱动的代码。
  4. 分析原因:区分是激励盲区、约束过紧、无效代码、还是休眠代码。
  5. 补充针对性用例:对未覆盖分支或基本块构造定向激励。
  6. 收敛再评估:重跑回归,观察覆盖率增长曲线是否趋于平缓,直到达到既定阈值。

3.4 案例说明

以一段 C 函数为例:

int abs_sum(int x, int y) {
    int s;
    if (x > 0) {
        s = x + y;
    } else {
        s = y - x;
    }
    if (s < 0) {
        s = -s;
    }
    return s;
}

使用不同测试用例观察覆盖差异:

测试用例 语句覆盖 块覆盖 分支覆盖
x=1, y=2 部分(else 未执行) 部分(else 块未进入) 部分(x>0 为假、s<0 为真未覆盖)
x=1, y=-3 部分(else 未执行) 部分(else 块未进入) 部分(x>0 为假未覆盖)
x=-1, y=5 全部语句执行 全部块进入 x>0 真分支未覆盖,仍不完整
x=-1, y=-2 全部语句执行 全部块进入 全部 4 个分支方向覆盖

案例分析结论:第一个分支 if (x > 0) 的真假两侧需要两个用例;第二个分支 if (s < 0) 的真假两侧同样需要两个用例。两个用例交叉组合后,才可能达到完整分支覆盖。语句覆盖达到 100% 时,分支覆盖仍可能低于 100%。

3.5 表格总结

维度 语句覆盖 块覆盖 分支覆盖
衡量粒度 单条语句 基本块 分支边
统计成本 高,插桩点多 中,按块聚合 中,需识别分支路径
典型弱点 掩盖分支未覆盖 不区分块内路径差异 不覆盖条件内部组合
收敛信号 语句执行率接近 100% 块命中率接近 100% 各分支方向命中率接近 100%
工程价值 快速发现未执行代码 模块级执行轨迹判断 功能分支验证充分性指标

3.6 常见误区

  • 误区:语句覆盖 100% 就代表充分验证。实际上,被执行的语句不意味着被正确检查,也不意味着所有条件组合被覆盖。
  • 误区:分支覆盖等同于条件覆盖。分支覆盖只关心整体真假方向,不关心组合条件中每个子条件的取值。例如 if (a && b) 整体为真,可能只覆盖了 a=1,b=1,未覆盖 a=1,b=0 等。
  • 误区:块覆盖与语句覆盖无差别。块覆盖将连续执行语句合并,计算更快;但若某基本块内部存在长组合逻辑,块覆盖无法体现块内部数据多样化程度。
  • 误区:只追求 100% 覆盖率数字。无效代码、防御性代码、不可达分支会导致覆盖率难以收敛,需要先清理或标注排除,而不是盲目堆用例。

3.7 实践清单

  • 建立验证计划时,将分支覆盖作为代码覆盖率收敛的主要门槛指标。
  • 收集覆盖率时同时导出“未覆盖语句列表”和“未覆盖分支列表”,按模块排序并人工审核。
  • 针对分支未覆盖点,使用约束随机激励配合定向用例补充覆盖。
  • 每轮回归后记录覆盖率增量,绘制收敛曲线,避免只汇报最终数字。
  • 在代码评审中同步检查可测性,减少冗余分支和不可达分支。
  • 将覆盖率数据纳入版本管理,定期与历史基线对比,防止覆盖率回退。