3. coverpoint与bin:coverpoint的定义、自动bin、手动bin、bin的命名规则

好,咱们今天聊聊coverpoint和bin。这是功能覆盖率的核心,说白了就是“你想看什么”和“怎么看”。

我刚开始做验证那会儿,总觉得覆盖率就是跑完仿真看个百分比。后来被老工程师点醒——你coverpoint定义得不对,bin分得不好,那个百分比就是骗自己的。嗯,咱们今天就把这事说透。

3.1 coverpoint的定义

coverpoint,就是你要观察的那个信号或表达式。比如你想看“addr”这个地址信号有没有覆盖到所有范围,那就定义一个coverpoint。

语法很简单:

covergroup my_cg;
    coverpoint addr;
endgroup

这就完了?对,最简单的定义就是这样。但实际项目中,我们很少这么裸写。为什么?因为默认的自动bin可能不是你想要的。

我个人习惯,在定义coverpoint时,一定会加上option.auto_bin_max或者手动指定bin。不然你想想看,一个32位的地址,自动给你分1024个bin,仿真跑完一看,全是空的,那有什么意义?

小技巧: 我建议coverpoint的名字和信号名保持一致,或者用有业务含义的名字。比如 coverpoint addr 可以写成 coverpoint addr { ... },但如果你要表达“读地址”和“写地址”,最好分开定义。

3.2 自动bin

自动bin,就是SystemVerilog帮你自动划分的bin。默认情况下,对于枚举类型,每个枚举值一个bin;对于整型,最多自动分1024个bin。

举个例子:

bit [3:0] data;
covergroup auto_bin_cg;
    coverpoint data;  // 自动分16个bin,每个值一个
endgroup

这里data是4位,所以自动分16个bin,每个bin对应一个值。看起来挺合理对吧?

但我在项目中遇到过一个问题:一个8位的控制信号,理论上只有0x00、0x01、0x80、0xFF四个有效值。自动bin给我分了256个,仿真跑完一看,252个bin是0次命中,4个bin各命中了几次。这报告看起来太乱了。

所以自动bin适合什么场景?适合信号取值比较均匀、范围不大的情况。比如状态机的状态枚举,或者小位宽的地址。

自动bin的个数可以用option.auto_bin_max控制:

coverpoint addr {
    option.auto_bin_max = 64;  // 最多64个自动bin
}
注意: 自动bin的个数不是精确的,它只是一个上限。实际bin的个数可能少于这个值。我曾经因为这个踩过坑——以为设了64个bin,结果只生成了32个,查了半天才发现是信号取值范围本身就小。

3.3 手动bin

手动bin,才是真正体现验证工程师水平的地方。说白了,就是你知道哪些值重要,哪些值需要合并,哪些值需要排除。

手动bin有三种常见形式:

类型 语法 说明
单个值 bins my_bin = {5}; 只观察值为5的情况
值范围 bins range_bin = {[10:20]}; 观察10到20之间的所有值
值列表 bins list_bin = {1, 3, 5, 7}; 观察这几个特定值

举个例子,假设我们要观察一个AXI总线的burst长度:

covergroup axi_burst_cg;
    coverpoint burst_len {
        bins fixed_1  = {1};           // 单次传输
        bins burst_4  = {4};           // 4拍突发
        bins burst_8  = {8};           // 8拍突发
        bins burst_16 = {16};          // 16拍突发
        bins others   = default;       // 其他所有值
    }
endgroup

你看,这样定义之后,报告就清晰多了。一眼就能看出哪些burst长度被覆盖到了,哪些没有。

我曾经在一个项目中,把所有的非法值都归到一个bin里,用default或者illegal_bins。这样一旦出现非法值,覆盖率报告会直接标红,方便定位问题。

3.4 bin的命名规则

bin的命名,看起来是个小事,但做不好真的很头疼。我见过有人写:

bins b1 = {1};
bins b2 = {2};
bins b3 = {3};

这种命名,一个月后你自己都看不懂b1代表什么。

我个人建议的命名规则:

  • 有业务含义:比如 bins read_cmdbins write_cmd,而不是 bins cmd1
  • 统一风格:要么全小写加下划线,要么驼峰,别混着用
  • 避免过长:比如 bins very_long_name_that_no_one_can_remember 就太过了
  • 特殊bin加前缀:比如 bins illegal_xxxbins default_xxx

举个例子:

coverpoint cmd {
    bins read_op   = {READ};
    bins write_op  = {WRITE};
    bins idle_op   = {IDLE};
    bins illegal_op = default;
}

这样命名,任何人看到代码都知道每个bin的含义。嗯,这里要注意,default只能有一个,而且它会捕获所有没被其他bin覆盖的值。

核心要点: bin的命名要让人一眼看懂,而不是让人去猜。你写的代码是给人看的,不是给机器看的。

3.5 避坑指南

最后,分享几个我踩过的坑:

  • 自动bin太多:我曾经对一个64位信号用了自动bin,结果仿真跑了三天,生成了几万个bin,报告根本没法看。后来改成手动bin,只关注关键值,问题立刻解决。
  • bin重复定义:同一个值出现在多个bin里,仿真器会报warning。虽然不影响功能,但看着烦。我习惯用ignore_bins先排除不需要的值。
  • 忘记加default:如果你手动定义了bin,但没有加default,那么没被覆盖到的值就不会被统计。这会导致覆盖率虚高——你以为都测到了,其实还有漏的。

好了,关于coverpoint和bin,今天就聊这么多。记住一句话:bin分得好,覆盖率报告才有意义。不然你就是在自欺欺人。