4. 交叉覆盖率:cross定义、排除非法bin、权重设置与调试技巧
交叉覆盖率,说白了就是看多个覆盖点组合在一起的情况。我刚开始做验证那会儿,总觉得单个覆盖点就够了,直到有一次漏掉了一个组合场景——地址和数据总线的某种特定组合没测到,结果后端反馈说那个路径的时序有问题。嗯,从那以后,我对交叉覆盖率就特别上心。
4.1 cross的基本定义
在SystemVerilog里,定义交叉覆盖率很简单。你只需要用cross关键字把两个或多个覆盖点组合起来就行。
covergroup mem_cg;
addr: coverpoint addr {
bins low = {[0:127]};
bins high = {[128:255]};
}
rw: coverpoint rw {
bins read = {0};
bins write = {1};
}
// 定义交叉覆盖率
addr_x_rw: cross addr, rw;
endgroup
这段代码会生成4个交叉bin:low_read、low_write、high_read、high_write。每个组合都会自动统计。
关键点:交叉覆盖率的bin数量是各覆盖点bin数的乘积。如果addr有2个bin,rw有2个bin,那交叉就是4个bin。这个乘法效应你一定要注意,不然很容易爆bin。
4.2 排除非法bin
实际项目中,不是所有组合都是合法的。比如,某些地址范围不允许写操作。这时候就需要排除非法bin。
我个人习惯用ignore_bins或者illegal_bins来处理。区别在于:
- ignore_bins:忽略这些组合,不统计也不报错
- illegal_bins:如果出现这些组合,直接报错
covergroup mem_cg;
addr: coverpoint addr {
bins low = {[0:127]};
bins high = {[128:255]};
}
rw: coverpoint rw {
bins read = {0};
bins write = {1};
}
addr_x_rw: cross addr, rw {
// 排除非法组合:高地址不允许写
ignore_bins high_write = binsof(addr) intersect {high} &&
binsof(rw) intersect {write};
// 标记非法组合:低地址不允许读(假设设计如此)
illegal_bins low_read = binsof(addr) intersect {low} &&
binsof(rw) intersect {read};
}
endgroup
我曾经踩过的坑:有一次我用了ignore_bins,但忘记检查这些组合是否真的不会出现。结果仿真跑了三天,发现某个非法组合其实出现了,但因为被忽略了,覆盖率报告显示100%,实际上那个场景根本没测到。所以,能用illegal_bins的地方,尽量用它。
4.3 权重设置
交叉覆盖率的权重设置,很多人容易忽略。默认情况下,所有交叉bin的权重都是1。但实际项目中,有些组合更重要,需要优先覆盖。
权重设置有两种方式:
- 在covergroup层面设置:影响所有交叉bin
- 在单个cross上设置:只影响特定的交叉
covergroup mem_cg with function sample(int addr, int rw);
// 设置整个covergroup的权重
option.weight = 2;
addr: coverpoint addr {
bins low = {[0:127]};
bins high = {[128:255]};
}
rw: coverpoint rw {
bins read = {0};
bins write = {1};
}
// 单独设置交叉权重
addr_x_rw: cross addr, rw {
option.weight = 5; // 这个交叉更重要
}
endgroup
我的建议:权重设置不要滥用。我一般只在以下情况使用:
- 某个组合是核心功能路径,需要优先覆盖
- 某个组合很难随机到,需要提高权重来加速收敛
- 调试阶段,临时提高某个交叉的权重来验证
4.4 调试技巧
交叉覆盖率调试起来比较头疼,因为组合太多。我总结了几条实用技巧:
4.4.1 使用$coverage_merge查看交叉bin
仿真结束后,用$coverage_merge可以查看哪些交叉bin没有被覆盖到。这个命令会生成详细的报告,告诉你每个bin的命中次数。
// 在仿真结束时调用
initial begin
#1000;
$coverage_merge("coverage_report.txt");
end
4.4.2 用binsof和intersect做精细化调试
调试时,我经常用binsof和intersect来定位问题。比如,我想看看某个地址范围的所有交叉情况:
addr_x_rw: cross addr, rw {
// 只关注低地址的交叉
bins low_addr_x_all = binsof(addr) intersect {low};
}
这样就能单独看低地址的交叉情况,不用被高地址的数据干扰。
4.4.3 使用covergroup的sample方法手动采样
有时候自动采样不够灵活,我会手动调用sample方法。这在调试阶段特别有用:
covergroup mem_cg;
// ...
endgroup
mem_cg cg = new();
initial begin
// 手动采样特定组合
cg.sample(100, 0); // addr=100, rw=read
cg.sample(200, 1); // addr=200, rw=write
end
核心思路:调试交叉覆盖率,说白了就是找到「哪些组合没被覆盖」以及「为什么没被覆盖」。前者靠报告,后者靠手动采样和约束分析。
4.5 知识体系图
下面这张图展示了交叉覆盖率的完整知识结构,我画出来方便你理解:
4.6 实战经验总结
最后,分享几条我在项目中积累的经验:
| 场景 | 建议做法 | 原因 |
|---|---|---|
| 组合数量过多(>1000) | 使用ignore_bins减少无效组合 | 避免仿真性能下降 |
| 关键组合难以随机到 | 提高权重 + 手动采样 | 加速收敛 |
| 调试阶段 | 用binsof单独查看某个覆盖点 | 快速定位问题 |
| 回归测试 | 检查illegal_bins是否触发 | 确保设计行为正确 |
一句话总结:交叉覆盖率是验证的「组合拳」,用好了能发现单点覆盖发现不了的问题。但要注意控制bin数量,别让组合爆炸拖垮仿真。
可可精品资料库