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 分析流程
- 选择覆盖模型:明确以语句、块、分支中哪些作为收敛指标,通常分支覆盖被作为更可靠的“底线”。
- 运行回归测试:收集仿真/执行过程中的覆盖数据。
- 识别未覆盖区域:按模块、层级或功能聚类未命中点,定位未被激励驱动的代码。
- 分析原因:区分是激励盲区、约束过紧、无效代码、还是休眠代码。
- 补充针对性用例:对未覆盖分支或基本块构造定向激励。
- 收敛再评估:重跑回归,观察覆盖率增长曲线是否趋于平缓,直到达到既定阈值。
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 实践清单
- 建立验证计划时,将分支覆盖作为代码覆盖率收敛的主要门槛指标。
- 收集覆盖率时同时导出“未覆盖语句列表”和“未覆盖分支列表”,按模块排序并人工审核。
- 针对分支未覆盖点,使用约束随机激励配合定向用例补充覆盖。
- 每轮回归后记录覆盖率增量,绘制收敛曲线,避免只汇报最终数字。
- 在代码评审中同步检查可测性,减少冗余分支和不可达分支。
- 将覆盖率数据纳入版本管理,定期与历史基线对比,防止覆盖率回退。