4. bin的高级用法:ignore_bins、illegal_bins、wildcard bins、cross bins

好,咱们接着聊bin。上一节我们讲了基础bin的定义方式,说白了就是告诉仿真器「我要盯着哪些值看」。但实际项目中,光盯着看还不够——你得告诉仿真器「哪些值你别管」、「哪些值一出现就报错」,甚至「我要同时看两个信号的关系」。这就是今天要聊的四个高级用法。

4.1 ignore_bins:眼不见为净

先说说 ignore_bins。这玩意儿我太熟了——刚做验证那会儿,我傻乎乎地把所有可能的值都列进bin里,结果覆盖率报告出来一堆「未覆盖」的坑。后来才发现,有些值根本不需要关心。

举个例子,你有个3位的信号,取值范围0~7。但设计规格说了,这个信号永远不会等于5和6。那你何必还盯着它们看?直接忽略掉就好。

covergroup cg_ignore;
    coverpoint addr {
        bins valid[] = {[0:4], 7};  // 只关心这6个值
        ignore_bins unused = {5, 6}; // 5和6直接忽略
    }
endgroup

嗯,这里要注意:ignore_bins 的优先级是最高的。就算你同时定义了 binsignore_bins,只要某个值落在忽略区间里,它就不会被任何bin统计到。我见过有人把同一个值既放在正常bin里又放在ignore里,结果仿真器直接报warning——别这么干,逻辑上说不通。

我的习惯:在项目初期,我会先列一个「不关心值」的清单,比如保留地址、非法操作码等。把这些值提前扔进ignore_bins里,能省掉后期大量分析「假未覆盖」的时间。

4.2 illegal_bins:一出现就翻车

如果说 ignore_bins 是「我不想看见你」,那 illegal_bins 就是「你敢出现我就死给你看」。这玩意儿在验证里特别有用——尤其是当你发现某个值理论上永远不该出现时。

我曾经在一个总线协议项目里吃过亏。协议规定地址必须按4字节对齐,但设计里有个bug,偶尔会发出非对齐地址。当时我没用 illegal_bins,结果这个bug在仿真里跑了三天才被发现。后来我学乖了——只要是不该出现的值,直接标成illegal。

covergroup cg_illegal;
    coverpoint addr {
        illegal_bins bad_addr = {[1:3], [5:7]}; 
        // 地址必须4字节对齐,0、4、8...才是合法的
    }
endgroup

一旦某个值落进 illegal_bins,仿真器会立刻报错并停止仿真(默认行为)。你可以通过仿真选项调整这个行为,但我建议别改——既然标了illegal,就该让它停下来。

避坑指南:我曾经犯过一个错——把illegal_bins的范围写得太宽了。比如本来只想排除5和6,结果写成了[5:6],但实际设计里5是合法的。仿真跑了半天,突然停住,一查发现是illegal_bins误报。所以,写illegal_bins之前,一定要和设计人员double-check。

4.3 wildcard bins:模糊匹配的艺术

接下来是 wildcard bins。这玩意儿说白了就是「我不关心某些位,你帮我模糊匹配」。在SystemVerilog里,wildcard用 ? 表示「任意值」。

举个例子,你有个状态机的状态编码是3位的,但设计里只用了000、001、010、100这四种状态。其他组合(011、101、110、111)都是非法状态。你可以这么写:

covergroup cg_wildcard;
    coverpoint state {
        wildcard bins legal_states = {3'b000, 3'b001, 3'b010, 3'b100};
        wildcard bins illegal_states = {3'b?11, 3'b1?1, 3'b11?};
        // ?表示任意值,所以?11匹配011和111
    }
endgroup

你想想看,如果没有wildcard,你得把四个非法状态一个一个列出来,多麻烦。用 ? 一个表达式就搞定了。

我个人习惯在地址译码验证里大量使用wildcard。比如某个外设的地址范围是0x1000~0x1FFF,但实际只用了0x1000、0x1100、0x1200这几个地址。用wildcard可以快速匹配「高4位固定,低12位任意」的模式。

核心要点:wildcard bins里的 ? 只能匹配0或1,不能匹配X或Z。如果你需要匹配X或Z,得用 default 或者单独定义。

4.4 cross bins:组合覆盖的利器

最后,也是最强大的——cross bins。这玩意儿能让你同时看两个或多个coverpoint的组合情况。说白了,就是「当A等于某个值时,B等于什么?」

我记得有个项目,验证一个AXI总线的主机。我们需要确保「当写地址落在某个区间时,写数据不能全为0」。这种跨信号的约束,用单个coverpoint根本搞不定,必须用cross。

covergroup cg_cross;
    addr: coverpoint waddr {
        bins low  = {[0:100]};
        bins mid  = {[101:200]};
        bins high = {[201:255]};
    }
    
    data: coverpoint wdata {
        bins zero = {0};
        bins nonzero = {[1:255]};
    }
    
    cross addr, data {
        // 自动生成3*2=6个交叉bin
        // 但我们可以手动定义更精细的bin
        bins addr_low_zero = binsof(addr.low) && binsof(data.zero);
        bins addr_mid_zero = binsof(addr.mid) && binsof(data.zero);
        bins addr_high_zero = binsof(addr.high) && binsof(data.zero);
    }
endgroup

这里有个坑——cross bins的数量会爆炸。如果你有两个coverpoint,每个有10个bin,cross之后就是100个bin。三个coverpoint就是1000个。所以,我建议你只在必要的时候用cross,而且尽量控制每个coverpoint的bin数量。

我的经验:用cross之前,先问问自己「我真的需要同时看这两个信号吗?」很多时候,单个coverpoint加上合理的assertion就能解决问题。cross虽然强大,但用多了会让覆盖率报告变得臃肿,分析起来也费劲。

4.5 四种bin的优先级与组合使用

这四种bin可以混着用,但要注意优先级:ignore_bins > illegal_bins > wildcard bins > 普通 bins。也就是说,如果一个值同时被ignore和illegal定义了,ignore会生效,illegal反而不会触发。

我建议你在实际项目中这样组合:

  • 先用 ignore_bins 排除所有「不关心」的值
  • 再用 illegal_bins 标记所有「不该出现」的值
  • 然后用 wildcard bins 或普通 bins 定义你关心的值
  • 最后用 cross bins 检查跨信号的关系

这个顺序能帮你快速定位问题。我曾经在一个项目里,先定义了illegal_bins,结果仿真一跑就停——原来有个值被误标了。调整之后,再跑就顺利多了。

最后提醒一句:别为了追求覆盖率数字而滥用这些高级bin。我见过有人把整个地址空间都用wildcard覆盖了,结果覆盖率报告显示100%,但实际验证质量一塌糊涂。工具只是辅助,真正重要的是你理解设计的行为。