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。但实际项目中,有些组合更重要,需要优先覆盖。

权重设置有两种方式:

  1. 在covergroup层面设置:影响所有交叉bin
  2. 在单个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 知识体系图

下面这张图展示了交叉覆盖率的完整知识结构,我画出来方便你理解:

交叉覆盖率知识体系 交叉覆盖率 cross定义 coverpoint组合 排除非法bin ignore/illegal 权重设置 option.weight 调试技巧 binsof/手动采样 自动生成bin 乘法效应 ignore_bins illegal_bins 全局权重 局部权重 $coverage_merge binsof/intersect 手动采样 目标:加速覆盖率收敛,确保验证完备性

4.6 实战经验总结

最后,分享几条我在项目中积累的经验:

场景 建议做法 原因
组合数量过多(>1000) 使用ignore_bins减少无效组合 避免仿真性能下降
关键组合难以随机到 提高权重 + 手动采样 加速收敛
调试阶段 用binsof单独查看某个覆盖点 快速定位问题
回归测试 检查illegal_bins是否触发 确保设计行为正确

一句话总结:交叉覆盖率是验证的「组合拳」,用好了能发现单点覆盖发现不了的问题。但要注意控制bin数量,别让组合爆炸拖垮仿真。

可可精品资料库