2. 覆盖点与仓:coverpoint 声明与 bins 定义
好,咱们今天聊聊覆盖组里最核心的两个东西——覆盖点(coverpoint)和仓(bins)。
说白了,覆盖点就是你想观察的那个信号。仓呢,就是把这个信号的值域切成若干段,每一段就是一个仓。你想想看,没有仓,你怎么知道信号具体覆盖了哪些值?
2.1 覆盖点的声明
覆盖点的声明其实很简单。在 covergroup 里面,直接用 coverpoint 关键字加上信号名就行。
covergroup my_cg;
coverpoint addr; // 最简单的声明
coverpoint data;
coverpoint ctrl;
endgroup
我个人习惯,会把所有要观察的信号都列出来。但要注意,不是所有信号都适合做覆盖点。比如时钟信号,你覆盖它干嘛?
嗯,这里有个小细节。覆盖点默认会对信号的所有可能取值进行采样。但实际项目中,我们往往只关心某些特定值。这时候就需要手动定义仓了。
2.2 自动定义的仓
如果你不手动定义仓,工具会自动帮你生成。对于 2 位信号,自动生成 4 个仓(00, 01, 10, 11)。对于 8 位信号,那就是 256 个仓。
注意:自动仓虽然省事,但会带来两个问题:
- 仓太多,仿真速度变慢
- 很多仓可能永远覆盖不到,造成覆盖率假象
我曾经在一个项目中,同事用了自动仓,结果 8 位地址信号生成了 256 个仓。仿真跑了一整天,覆盖率才 30%。后来我改成手动定义,只关心关键的 10 个地址,覆盖率一下子就上去了。
2.3 手动定义仓
手动定义仓,用 bins 关键字。语法其实很灵活,我给大家列几种常见用法。
2.3.1 单个值仓
coverpoint addr {
bins zero = {0}; // 只关心地址为0的情况
bins max = {255}; // 只关心地址为255的情况
}
2.3.2 范围仓
coverpoint data {
bins low = {[0:63]}; // 低64个值
bins mid = {[64:191]}; // 中间128个值
bins high = {[192:255]}; // 高64个值
}
你看,这样就把 256 个值压缩成了 3 个仓。覆盖率报告看起来清爽多了。
2.3.3 交叉仓
有时候我们需要看两个信号的组合。比如读写操作和地址的组合。
coverpoint rw {
bins read = {0};
bins write = {1};
}
coverpoint addr {
bins low = {[0:127]};
bins high = {[128:255]};
}
cross rw, addr; // 自动生成 2x2=4 个交叉仓
小技巧:交叉覆盖很容易产生大量仓。两个 8 位信号交叉,就是 65536 个仓。我建议先手动分仓,再交叉,这样仓的数量可控。
2.4 特殊仓:ignore_bins 和 illegal_bins
这两个东西很实用。ignore_bins 用来排除你不关心的值,illegal_bins 用来标记不应该出现的值。
coverpoint status {
bins good[] = {[0:5]}; // 正常状态
ignore_bins reserved = {6, 7}; // 保留位,不关心
illegal_bins error = {8'hFF}; // 这个值不应该出现
}
我记得有一次,芯片规格书里写某个寄存器的 bit[3:0] 只有 0-7 是合法的。结果仿真时发现覆盖率一直上不去。后来一查,原来是设计里误写了 8。用 illegal_bins 一抓就抓出来了。
2.5 自动仓与手动仓的选择
| 场景 | 推荐方式 | 原因 |
|---|---|---|
| 信号位宽小(≤4位) | 自动仓 | 仓数量少,不影响性能 |
| 信号位宽大(≥8位) | 手动仓 | 避免仓爆炸 |
| 只关心特定值 | 手动仓 | 精准覆盖,节省时间 |
| 需要交叉覆盖 | 先手动再交叉 | 控制交叉仓数量 |
2.6 本章知识体系
下面这张图,是我自己画的覆盖点与仓的知识结构。你看一眼就能明白它们之间的关系。
核心要点:
- 覆盖点声明用
coverpoint,简单直接 - 自动仓适合小位宽信号,大位宽信号一定要手动分仓
- 手动仓用
bins关键字,支持单个值、范围、交叉 ignore_bins排除不关心的值,illegal_bins抓设计错误- 交叉覆盖前先手动分仓,避免仓爆炸
好了,覆盖点和仓的基本用法就这些。说白了就是一句话:你想看什么,就定义什么仓。别让工具替你瞎操心,自己动手,覆盖率才靠谱。