4、SystemVerilog 覆盖语法:covergroup、coverpoint、bins、iff、option.per_instance
好,咱们今天聊聊覆盖率的语法核心。说白了,就是怎么用 SystemVerilog 把“你到底测了哪些东西”这件事给量化出来。
我刚开始做验证那会儿,总觉得覆盖率是个玄学。后来踩过几次坑才明白,覆盖率不是目的,而是手段。它告诉你:你的测试到底有没有把设计跑透。
4.1 covergroup:覆盖率的容器
covergroup 就像个收纳盒。你把所有想监控的信号、变量都扔进去。它可以在类里定义,也可以在 module 或 interface 里定义。
covergroup cg_alu @(posedge clk);
// 这里放 coverpoint
endgroup : cg_alu
我个人习惯把 covergroup 定义在 monitor 或者 scoreboard 里。这样结构清晰,也方便复用。
4.2 coverpoint:你要看哪个信号?
coverpoint 就是具体要覆盖的变量。可以是 reg、wire,也可以是整型表达式。
covergroup cg_alu;
opcode : coverpoint alu_op;
a : coverpoint alu_a;
b : coverpoint alu_b;
endgroup
这里我监控了三个信号:操作码、输入 A、输入 B。每个 coverpoint 都会自动生成一些 bins(后面会讲)。
4.3 bins:你到底关心哪些值?
bins 是覆盖率的灵魂。它定义了你要统计哪些具体的值或值域。
自动生成的 bins 是每个值一个 bin。但很多时候我们只关心某些特定值,或者某些范围。
covergroup cg_alu;
opcode : coverpoint alu_op {
bins add = {4'b0001};
bins sub = {4'b0010};
bins mul = {4'b0100};
bins div = {4'b1000};
bins others = default;
}
endgroup
这里我只关心四种操作码。其他的都扔到 default 里。为什么?因为设计里只定义了这四种操作,其他的都是非法值。你想想看,如果非法值也被覆盖到了,那说明你的测试可能触发了不该触发的状态。
还有一种常见用法是 bins[](带方括号),用来生成一组 bins:
data : coverpoint alu_result {
bins low = {[0:100]};
bins mid = {[101:200]};
bins high = {[201:255]};
bins others = default;
}
我曾经在一个项目中,用 bins[] 把地址空间分成 16 个区间,每个区间一个 bin。这样既能保证覆盖到所有地址范围,又不会因为地址太多导致仿真跑不动。
4.4 iff:条件采样
iff 是个好东西。它让你只在特定条件下才采样。说白了就是加个门控。
covergroup cg_alu @(posedge clk);
opcode : coverpoint alu_op iff (!reset);
endgroup
这里我加了 iff (!reset),意思是复位期间不采样。为什么?因为复位时信号都是无效的,采样了也是白采,还会污染覆盖率数据。
我遇到过一种情况:某个信号只在特定模式下有效。比如在 DMA 模式下,地址总线才有意义。这时候用 iff 就特别合适:
addr : coverpoint addr_bus iff (mode == DMA_MODE);
4.5 option.per_instance:每个实例单独统计
默认情况下,covergroup 的覆盖率是全局统计的。也就是说,如果你例化了 10 个同样的 covergroup,它们的采样数据是合并在一起的。
但有时候我们需要看每个实例的单独情况。比如你有 4 个相同的通道,你想知道每个通道的覆盖情况是否一致。这时候就需要 option.per_instance = 1。
covergroup cg_channel @(posedge clk);
option.per_instance = 1;
data : coverpoint ch_data;
endgroup
设置了这个选项后,每个实例都会有自己的覆盖率统计。你可以单独看 instance_0 的覆盖率,也可以看 instance_1 的。
知识体系总览
下面这张图把本章的核心逻辑串起来了。你可以看到 covergroup 是顶层容器,里面装 coverpoint,coverpoint 里定义 bins,再加上 iff 和 option 来控制采样行为。
避坑指南
我总结几个常见的坑,都是我曾经踩过的:
- bins 定义太细:32 位信号每个值一个 bin,仿真直接卡死。解决办法是用
bins[]分组,或者用ignore_bins排除不关心的值。 - iff 条件写反:比如
iff (reset),结果只在复位时采样。复位时信号都是无效的,采了也白采。我犯过这个错,查了两天才发现。 - per_instance 忘记开:多个实例的覆盖率混在一起,分不清哪个通道没测到。调试时一定要打开,等最终回归再关掉。
- coverpoint 变量位宽不一致:比如你 cover 一个 8 位信号,但设计里实际只用了 4 位。这时候自动生成的 bins 会包含很多无效值。建议用
wildcard bins或者手动定义。
嗯,今天就聊到这儿。这些语法看起来简单,但真正用好需要多练。下次你写 covergroup 的时候,可以想想:我到底关心哪些值?哪些情况不需要采样?每个实例要不要分开看?想清楚了再动手。