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 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
}
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_cmd、bins write_cmd,而不是bins cmd1 - 统一风格:要么全小写加下划线,要么驼峰,别混着用
- 避免过长:比如
bins very_long_name_that_no_one_can_remember就太过了 - 特殊bin加前缀:比如
bins illegal_xxx、bins default_xxx
举个例子:
coverpoint cmd {
bins read_op = {READ};
bins write_op = {WRITE};
bins idle_op = {IDLE};
bins illegal_op = default;
}
这样命名,任何人看到代码都知道每个bin的含义。嗯,这里要注意,default只能有一个,而且它会捕获所有没被其他bin覆盖的值。
3.5 避坑指南
最后,分享几个我踩过的坑:
- 自动bin太多:我曾经对一个64位信号用了自动bin,结果仿真跑了三天,生成了几万个bin,报告根本没法看。后来改成手动bin,只关注关键值,问题立刻解决。
- bin重复定义:同一个值出现在多个bin里,仿真器会报warning。虽然不影响功能,但看着烦。我习惯用
ignore_bins先排除不需要的值。 - 忘记加default:如果你手动定义了bin,但没有加
default,那么没被覆盖到的值就不会被统计。这会导致覆盖率虚高——你以为都测到了,其实还有漏的。
好了,关于coverpoint和bin,今天就聊这么多。记住一句话:bin分得好,覆盖率报告才有意义。不然你就是在自欺欺人。