covergroup option 选项:per_instance、at_least、goal、weight 与 type_option
更新时间:2026-09-28。上一篇 SystemVerilog covergroup、coverpoint 与 bins:功能覆盖率的三块积木 把「采什么、分几个桶」讲清楚了;这一篇只解决另一半:同一个 covergroup 被例化很多份时,报表怎么算、每个桶命中几次才算"够"、各实例是合并还是分开统计。这些行为全部由 covergroup option 选项控制,也是 SystemVerilog 代码覆盖率与功能覆盖率:两套账本怎么一起看 里"覆盖率收敛"数字背后的真正旋钮。
本文要回答的问题
- covergroup 里
option.xxx和type_option.xxx有什么区别,各自在哪一层生效? per_instance打开后,报表里每个实例一份数据,合并逻辑怎么变?at_least、goal、weight三个选项分别改什么?默认值各是多少?- 哪些组合是验证工程师天天在用的,哪些写法看着无害其实让覆盖率"虚高"?
一、两套选项:option 管实例,type_option 管类型
SystemVerilog 的 covergroup 有两层身份:类型(type)和实例(instance)。你在 class 里声明一个 covergroup 类型,仿真中可能 new 出几十上百个实例——每个 monitor 一份、每路 agent 一份。选项也按这两层分成两套:
option.*:实例级,只影响写它的那个实例。可以在 covergroup 体内写(对所有实例生效的默认),也可以在new之后对单个实例改。type_option.*:类型级,影响这个 covergroup 类型的所有实例,必须在 covergroup 体内写,编译期就定死。
最容易记混的是 strobe、merge_instances、per_instance 这三个:前两个是 type_option(类型级),后一个是 option(实例级)。写错位置仿真器直接报编译错,但报错信息往往只指到行号,不告诉你"这是类型级选项不该写成 option",新手要在这个坑上浪费半小时。
| 选项 | 级别 | 默认值 | 作用一句话 |
|---|---|---|---|
option.per_instance | 实例 | 0 | 1 = 每个实例单独出一份覆盖率 |
option.at_least | 实例(也可 coverpoint 级) | 1 | 一个 bin 命中几次才算覆盖 |
option.goal | 实例(也可 coverpoint 级) | 100 | 该 covergroup/coverpoint 的目标百分比 |
option.weight | 实例(也可 coverpoint 级) | 1 | 计算总覆盖率时占的权重 |
option.name | 实例 | 自动生成 | 实例在报表里的名字 |
type_option.strobe | 类型 | 0 | 1 = 在 postpone 区域采样,躲竞争 |
type_option.merge_instances | 类型 | 0 | 1 = 把所有实例合并成一份统计 |
二、per_instance:一个 covergroup,一百份报表
option.per_instance = 1 是多实例环境(比如 16 路 AXI agent)的标配。不开它,仿真器默认把同名 covergroup 的所有实例累计进同一份数据:16 路里只要有任何一路命中过 awlen==8,这个 bin 就算覆盖。对"每一路都要独立验证"的协议来说这等于虚报。
covergroup cg_axi_burst @(posedge vif.aclk);
option.per_instance = 1;
option.name = "cg_axi_burst";
cp_len: coverpoint vif.awlen {
bins single = {0};
bins short = {[1:3]};
bins long = {[4:15]};
option.at_least = 4; // 每个 bin 命中 4 次才算
}
endgroup配合 option.name 或 $get_inst_coverage 上报,报表里就能看到 cg_axi_burst_0 到 cg_axi_burst_15 各自的覆盖率,哪一路 stimulus 没打到位一眼可见。per_instance 和 goal 联用时还有一个细节:判定收敛是对每个实例独立算的,即实例 0 达到 100% 不代表实例 3 也达标,回归脚本要逐实例抓数字,不能只看类型汇总行。
三、at_least、goal、weight:三个"判定"选项
这三个选项不改采样行为,改的是报表怎么算分:
option.at_least = N:bin 命中次数低于 N 视为未覆盖。burst 类场景建议设 3~8,避免随机 stimulus 蹭到一次就过关;罕见错误码 bin 保持默认 1 即可。option.goal = P:该实体的目标百分比,默认 100。有些 coverpoint 明知收敛不到 100%(比如依赖外部 VIP 的异常路径),把 goal 设成 80 并在回归脚本里按 goal 判定通过/失败,比硬凑 100% 诚实。option.weight = W:算父级覆盖率时该实体的权重。16 个普通 coverpoint 配 1 个关键 coverpoint,想让关键的那个说了算,就把它 weight 调高(比如 8)、其余设为 1;不参与总分的探索性 coverpoint 可以weight = 0直接踢出汇总。
三者写在 covergroup 体里是默认值,写在某个 coverpoint/交叉体里覆盖默认,也可以仿真中通过 cg.inst_option.at_least = ... 对单个实例动态调整——动态调整常用于"冒烟测试降低 at_least、回归测试恢复"。
四、选项配置速查与合并流向
下图把类型级与实例级选项的作用范围、以及 per_instance/merge_instances 对数据流向的影响画在一起:
读这张图抓两点:一是数据流——每个实例先独立累积,per_instance 决定报表里是否拆开,merge_instances 决定是否最终合一;二是尾部那个坑——per_instance 和 merge_instances 语义互斥,都设 1 时按 LRM 语义 merge_instances 生效,per_instance 被悄悄忽略,报表只剩一份合并数据,新人常在这里查半天"为什么我的实例级报表不见了"。
五、常用组合与踩坑清单
日常用得最多的三种组合:
- 多通道协议验证:
per_instance=1+at_least=4+name带通道号——每路独立出数,命中要求适中。 - 单实例控制覆盖率(如 FSM 状态交叉):
per_instance=0(只有一份实例,无所谓)+weight拉高——让它在总覆盖率里有话语权。 - 冒烟/回归双档:编译期默认
at_least=8,冒烟脚本在new后改inst_option.at_least=1快速过一遍,回归用默认值。
| 坑 | 表现 | 处理 |
|---|---|---|
| merge_instances 与 per_instance 同设 | 实例级报表消失 | 只保留一个,语义互斥 |
| at_least 写在 covergroup 但 coverpoint 又覆盖 | 某 coverpoint 判定和预期不同 | 明确"默认+局部覆盖"层级,注释写清 |
| weight=0 后总分反而降 | 探索性 cp 之前一直拉高总分 | 踢出汇总前先在历史报表里看它对总分的贡献 |
| goal 设 100 但实际收敛到 92 | 回归永远红 | 按可达性分析下调 goal 或补 bins,不要硬凑 |
| 动态改 inst_option 时机太晚 | 改完第一次采样已发生 | 在 new 完 covergroup 后、第一次 sample 前改 |
这一篇在知识体系里的位置
- 上游积木:SystemVerilog covergroup、coverpoint 与 bins:功能覆盖率的三块积木 讲采什么,本篇讲怎么算分。
- 收敛全局:SystemVerilog 代码覆盖率与功能覆盖率:两套账本怎么一起看 说明功能覆盖率只是两套账本之一,goal 判定要和代码覆盖率一起看。
总结
covergroup option 选项分两层:type_option(strobe、merge_instances)管整个类型的采样与合并,option(per_instance、at_least、goal、weight)管每个实例的出数方式与判定标准;多实例环境开 per_instance 拆报表、用 at_least 提命中门槛、用 goal/weight 定收敛判定与汇总话语权,并记住 merge_instances 与 per_instance 互斥这一铁律,覆盖率的"分"才算得诚实。