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 里。这样结构清晰,也方便复用。

小提示:covergroup 可以带参数。比如你想在不同的测试用例里用不同的采样条件,传个参数进去就行。

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(后面会讲)。

注意:coverpoint 的变量如果位宽太大(比如 32 位),自动生成的 bins 会爆炸。我见过有人直接 cover 一个 32 位地址总线,结果仿真跑了三天还没结束。嗯,这是个教训。

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);
核心要点:iff 不是用来过滤错误的,而是用来过滤“无意义”的采样。如果你发现某个 coverpoint 的覆盖率一直上不去,先看看是不是 iff 条件太苛刻了。

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 的。

我的习惯:在调试阶段,我会把 per_instance 打开。等调试完了,再关掉,看全局覆盖率。这样既能定位问题,又能得到最终的合并结果。

知识体系总览

下面这张图把本章的核心逻辑串起来了。你可以看到 covergroup 是顶层容器,里面装 coverpoint,coverpoint 里定义 bins,再加上 iff 和 option 来控制采样行为。

covergroup coverpoint bins bins add = {4'b0001}; | bins others = default; iff option 核心逻辑 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 是框架,coverpoint 是监控点,bins 是统计粒度,iff 是采样门控,option.per_instance 是实例隔离。把这五个东西用好,你的覆盖率模型就搭起来了。

嗯,今天就聊到这儿。这些语法看起来简单,但真正用好需要多练。下次你写 covergroup 的时候,可以想想:我到底关心哪些值?哪些情况不需要采样?每个实例要不要分开看?想清楚了再动手。

可可精品资料库