覆盖率合并排除:多 test 合并、exclude 注释与 UCIS 回归收敛全流程
更新时间:2026-09-28。在 代码覆盖率与功能覆盖率 里,我们知道了覆盖率分两大类、各自度量什么;在 covergroup、coverpoint 与 bins 里,学会了怎么把验证计划落成可采样的容器。但单个 test 跑出的覆盖率永远只是碎片——一个 IP 的 signoff 覆盖率来自几百上千个 test 的合并结果,中间还夹杂着大量「确认不可达、合理排除」的条目。这一篇专门讲覆盖率合并排除这条流水线:合并怎么做、排除怎么写、报告怎么读,直到覆盖率数字可以拿去评审。
本文要回答的问题
- 为什么单个 test 的覆盖率没有意义,合并之后数字为什么会「神奇地」上涨?
- UCIS 数据库是什么,各家仿真器的 merge 命令在做什么同一件事?
- exclude 注释该写什么内容,为什么「unreachable」三个字会被评审打回?
- 合并出的总覆盖率 98% 和「可以 signoff」之间还差哪些检查?
- 合并收敛的常见陷阱(bins 错位、权重污染、重复计数)怎么识别和规避?
一、为什么必须合并:单 test 覆盖率的局限
约束随机验证的基本假设是:没有任何一个 test 能覆盖全部场景。定向 test 各自负责一小块,随机 test 靠种子散布采面。一个跑了两小时的随机 test,功能覆盖率可能只有 35%,代码覆盖率 60%——这个数字本身说明不了任何问题,它只说明「这一个随机序列走到了这些地方」。
合并(merge)把同一回归里所有 test 的覆盖率数据取并集:test A 打到的 bin、test B 打到的分支,在合并结果里都算命中。这正是 回归管理与 triage 里每晚跑几百个 test 的意义所在——不是为了刷通过率,而是让每个 test 贡献它独有的那部分覆盖。
上图是理解合并的核心心智模型:每个 test 贡献一块独有色块,合并后是并集而不是平均。这里有两个直接推论。第一,看单 test 覆盖率排名没有太大意义,一个 41% 的 test 如果和别人的覆盖高度重叠,边际贡献可能不如一个 28% 的 test;很多工具提供「test grading」功能,就是按边际贡献给 test 排序,砍掉零贡献的 test 缩短回归时间。第二,合并覆盖率与 100% 之间的缺口,就是接下来要处理的工作——要么补 test,要么走排除流程。
| 视角 | 单 test 覆盖率 | 合并覆盖率 |
|---|---|---|
| 回答的问题 | 这个序列走到了哪 | 整个回归一共走到了哪 |
| 与 signoff 的关系 | 无直接关系 | signoff 的输入 |
| 低说明什么 | 可能是正常的 | 缺口必须逐条处置 |
| 典型用途 | 调 test、grading | 收敛跟踪、评审 |
二、UCIS:覆盖率的统一存储格式
早期每家仿真器的覆盖率数据库都是私有格式,VCS 的 .vdb、Xcelium 的 cov_work、Questa 的 .ucdb,换工具就得换一整套脚本。UCIS(Unified Coverage Interoperability Standard,统一覆盖率互操作标准)是 Accellera 定义的开放数据模型,让覆盖率数据可以在不同工具之间流动——仿真器写出 UCIS,merge 工具、报告工具、甚至形式验证工具都能读同一个库。实际工程里即使不换仿真器,UCIS 的命令行工具链(merge、report、exclude 管理)也是各家收敛流程的共同骨架。
一次典型回归的数据流是这样的:每个 test 仿真结束时把命中率写进自己的 UCIS 文件(一个 test 一个文件,带 testname 属性);回归调度器收集全部文件;merge 步骤把它们合并成一个总库;报告步骤从总库生成 HTML 或文本报告。关键性质是合并不丢来源——合并后的库保留了「哪个 bin 被哪些 test 命中」的映射,所以你能反查「这个 corner 是谁打到的」,test 挂了重跑也只需要替换它自己的那份数据再重合并,不用整个回归重来。
工程实践上有三条经验。其一,合并脚本里给每次回归的总库打时间戳归档,覆盖率收敛是一条需要看趋势的曲线,昨天的 87% 和今天的 89% 差在哪两个 bin,只有留了历史库才能 diff 出来。其二,合并时确认所有输入库来自同一份 RTL 和同一份 testbench 编译——覆盖率对象按层级路径和行号索引,RTL 改了一行,旧库里的行号映射就错位了,混着合并出来的数字是垃圾。多数团队的做法是 RTL 冻结点打 tag,覆盖率库按 tag 分目录存放。其三,别只合并功能覆盖率忘了代码覆盖率,两类数据各出一个合并库,报告分开看,它们回答的是不同问题。
三、exclude 注释:排除不是掩盖,是论证
合并之后剩下的缺口分两类:真实没测到的(该补 test),和确认不可达或不适用的(该排除)。排除(exclude)是覆盖率收敛里最容易被滥用、也最受评审关注的环节。排除本身不丢人——死代码、工具链生成的保护分支、配置裁剪掉的功能块,这些条目永远打不到;丢人的是没有论证的排除。
// 反面教材:这样的 exclude 注释会被评审当场打回
// cov: exclude bin cfg_reg.reserved_write
// reason: unreachable
// 正面示例:说清三件事——为什么不可达、谁确认的、会不会回来
// cov: exclude bin cfg_reg.reserved_write
// reason: reserved[7:4] 写入被总线桥在地址译码阶段直接丢弃(见
// bridge_spec 3.2 节),DUT 内部该路径无激励来源。
// 已由设计 owner W 在 2026-09-20 覆盖率评审确认;
// 若后续版本开放保留位(roadmap V2.1),需撤销此排除并重测。一条合格的 exclude 注释包含三个要素:机制论证(为什么打不到,引用设计规格或代码事实,不是「我觉得」)、责任人确认(设计 owner 签字,验证工程师不能单方面排除自己测不到的东西)、失效条件(什么变化会让这条排除不再成立)。第三条最常被忽略,也最重要——RTL 会演进,今天不可达的路径下个版本可能就活了;评审记录里写清失效条件,回归脚本甚至可以挂钩子:相关文件变更时自动把这些 exclude 标成「待复审」。
功能覆盖率和代码覆盖率的排除流程还不太一样。covergroup 的 bin 排除是验证团队内部的事,因为 bin 的定义本来就在 testbench 里;代码覆盖率的 exclude(某行、某分支、某个 FSM 转移)动的是「设计被测程度」的度量,通常要求设计 owner 和验证 lead 双签,大项目还会要求独立评审人抽查一定比例的 exclude 条目。这个分工对应 UVM 覆盖率闭环与 signoff 里的签字矩阵。
这张流程图是收敛期的日常循环:每一轮合并产生缺口清单,每条缺口走「补或排」二选一,排除项过评审。注意 addtest 回流到 gap 这条边——补了 test 要重跑重合并才能看到缺口缩小,收敛不是写文档写出来的,是一轮一轮回归跑出来的。
| 缺口类型 | 处置 | 谁签字 | 典型例子 |
|---|---|---|---|
| 未测到的合法场景 | 补 test / 加约束 | 验证 lead | 某中断优先级组合没随出来 |
| 结构上不可达 | exclude + 论证 | 设计 owner | default 分支后还有 unreachable 保护 |
| 配置裁剪掉的功能 | exclude + 配置说明 | 验证 lead | 低端版本裁掉的通道 3 |
| 工具误报/模型差异 | exclude + 工具工单 | 双方 + 工具确认 | 综合插入的 test 逻辑被算进行覆盖 |
四、从合并报告到收敛决策
评审会上看的从来不是单个百分比,而是分解后的数字加趋势。一份能支撑决策的合并报告至少拆成这几块:代码覆盖率按模块分解(statement / branch / condition / FSM 各自的数)、功能覆盖率按 covergroup 分解(对照 验证计划到覆盖率的映射 逐条核对)、exclude 条目数和占比、以及和上一次回归的 diff。
有几个数字组合值得特别警惕。总覆盖率 96% 但某个新加模块只有 70%——加权平均把短板藏起来了,按模块分解才看得见。覆盖率涨得异常快——先别高兴,检查是不是有人把大 covergroup 的 bins 改粗了,或者排除清单上周翻了一倍。代码覆盖率 100% 而功能覆盖率 80%——典型的「代码都跑了但没验对场景」,说明 stimulus 在反复走同样的路径,该动约束而不是庆祝。反过来功能覆盖率很高但某模块分支覆盖率低,往往意味着 covergroup 定义和 RTL 行为脱节,bin 打到了不代表那条代码路径真的按规格走通了。
趋势图的价值在于「收敛速度」这个信号:覆盖率曲线长期平缓,说明现有 test 集合的边际贡献耗尽,继续堆随机种子是浪费机器时间,该回头补 scenario 或者加形式验证(参考 形式验证基础 用证明补齐仿真打不到的深度状态)。这也是 merge 数据库归档的直接回报——没有历史曲线,「趋缓」这个词就没有数据支撑。
五、常见合并陷阱与排查
- bins 错位:RTL 或 covergroup 改动后用旧库合并。表现是覆盖率突然跳变或出现成片的「幽灵缺口」。处理:库按编译 tag 归档,merge 脚本前置校验版本戳。
- 重复计数:同一个 test 的 ucdb 被收集了两次(调度器重试机制常干这事)。功能覆盖合并取并集不受影响,但 test grading 和 per-test 命中率统计会虚高。处理:merge 前按 testname + seed 去重。
- 权重污染:工具支持给 covergroup 或模块设权重,有人为了让总数字好看调大高覆盖模块的权重。处理:signoff 报告只认未加权分解数字,权重表进评审材料。
- 层级路径漂移:testbench 重构后 covergroup 实例路径变了,新库和旧库的同名容器对不上,合并出两份各覆盖一半。处理:路径稳定化(
option.per_instance、显式命名),重构后废弃旧库。 - exclude 作用域过宽:按模块排除时用了通配,把相邻不该排的逻辑一起排了。处理:exclude 粒度到行/bin 级,定期用「全量统计(含已排除项)」对账,看排除总量是否异常。
- 合并库当档案库:只留合并后的总库,丢了 per-test 文件。一旦要反查或重 grading 就无米下锅。处理:per-test 库至少保留到 signoff,总库归档带输入清单。
这一篇在知识体系里的位置
- 向上承接:代码覆盖率与功能覆盖率 定义了两类覆盖率度量什么,本篇讲它们的回归级汇总与收敛。
- 横向相关:covergroup、coverpoint 与 bins 是被合并与排除的对象定义;回归管理与 triage 提供产生覆盖率数据的回归机制。
- 落地延伸:UVM 覆盖率闭环与 signoff 把本篇的合并报告与排除清单放进正式签字流程;验证计划到覆盖率的映射 负责让缺口清单能逐条对回验证计划。
总结
覆盖率合并排除是回归收敛的中央流水线:每个 test 的 UCIS 数据取并集合并成总库(保留 test 归属、按编译版本归档),合并缺口走「补 test 或 exclude」二选一,exclude 必须带机制论证、责任人签字和失效条件,signoff 看的是分解数字、收敛趋势与排除清单质量,而不是一个孤零零的总百分比。