3. 功能覆盖率入门:SystemVerilog中的covergroup与coverpoint基础语法

功能覆盖率,说白了就是回答一个问题:「我们测了哪些功能点?」

代码覆盖率告诉你代码跑了多少行,但功能覆盖率告诉你——你关心的那些场景到底有没有被触发。我刚开始做验证那会儿,总觉得代码覆盖率达到100%就万事大吉了。结果有一次,一个关键的状态机跳转路径根本没走到,代码覆盖率却是绿的。嗯,从那以后,我再也不敢只看代码覆盖率了。

3.1 为什么需要功能覆盖率?

先想一个问题:你写了一个加法器,测试用例把所有的输入组合都跑了一遍,代码覆盖率100%。但你的设计文档里写着「加法器支持饱和截断」,这个功能你测了吗?

代码覆盖率不知道你在测什么功能。它只管「这行代码执行了没有」,不管「这个功能点验证了没有」。

功能覆盖率就是用来弥补这个鸿沟的。它让你把设计规范里的功能点,映射成可量化的覆盖率指标。

核心思想:功能覆盖率 = 你关心的场景 / 实际被触发的场景

3.2 covergroup 是什么?

covergroup 是 SystemVerilog 里用来定义功能覆盖率模型的容器。你可以把它理解成一个「采样篮子」,里面放着你想要监控的信号和变量。

我个人习惯把 covergroup 定义在 interface 或者 testbench 的顶层模块里。这样方便统一管理,也方便复用。

来看一个最简单的例子:

covergroup cg_adder;
  // 这里放 coverpoint
endgroup

// 实例化
cg_adder cg_inst = new();

注意,covergroup 需要手动实例化。不像 module 那样例化就自动创建。我刚开始学的时候经常忘记 new(),结果覆盖率一直为零,查了半天才发现是没实例化。

3.3 coverpoint:定义你要采什么

coverpoint 就是 covergroup 里的「采样点」。你告诉它:帮我盯着这个信号,看看它都出现了哪些值。

covergroup cg_adder;
  coverpoint sum;  // 盯着 sum 信号
endgroup

这样写,仿真器会自动把 sum 的所有取值记录下来,然后统计每个值被命中了多少次。

但问题来了——如果 sum 是 32 位的,那就有 2^32 种可能。你不可能每个值都去覆盖。这时候就需要 bin 的概念。

3.4 自动分 bin 与手动分 bin

默认情况下,SystemVerilog 会自动为 coverpoint 创建 bin。对于枚举类型,每个枚举值一个 bin。对于整型,它会自动划分成若干个区间。

但我建议你手动分 bin。为什么?因为自动分 bin 可能把重要的边界值给吞掉了。

举个例子:

covergroup cg_adder;
  coverpoint sum {
    bins zero = {0};           // 单独一个 bin 给 0
    bins small = {[1:10]};     // 1 到 10 归为一个 bin
    bins large = {[11:255]};   // 11 到 255 归为一个 bin
    bins overflow = {[256:$]}; // 256 以上归为 overflow
  }
endgroup

这样写,你一眼就能看出:sum 有没有出现过 0?小数值有没有覆盖到?有没有溢出?

我的习惯:边界值单独建 bin,中间值可以合并。比如地址范围,0、最大值、中间值各一个 bin,剩下的按区间合并。

3.5 交叉覆盖率:组合场景的威力

单个 coverpoint 只能看一个信号。但很多 bug 出现在多个信号的组合上。比如:opcode 是加法,同时 carry 为 1,这种组合你测了吗?

交叉覆盖率用 cross 关键字来实现:

covergroup cg_alu;
  coverpoint opcode;
  coverpoint carry;
  
  cross opcode, carry;  // 自动生成所有组合的 bin
endgroup

这里有个坑:如果 opcode 有 4 个 bin,carry 有 2 个 bin,那交叉之后就是 8 个 bin。如果每个 coverpoint 都有几十个 bin,交叉之后可能爆炸。

我曾经在一个项目里,把两个各有 16 个 bin 的 coverpoint 做了交叉,结果生成了 256 个 bin。仿真跑了三天,大部分 bin 都是空的。后来我加了 ignore_bins 过滤掉无效组合,才把覆盖率跑上去。

3.6 选项与条件采样

有时候你不想在每个时钟周期都采样。比如只在 valid 为高的时候采样:

covergroup cg_data @(posedge clk);
  coverpoint data iff (valid);  // valid 为高时才采样
endgroup

还可以用 option.per_instance 来控制是否按实例统计覆盖率:

covergroup cg_fifo;
  option.per_instance = 1;  // 每个实例单独统计
  coverpoint wr_ptr;
  coverpoint rd_ptr;
endgroup

我个人习惯在调试阶段打开 per_instance,这样能定位到是哪个模块没覆盖到。回归测试时再关掉,合并统计。

3.7 知识体系总览

下面这张图帮你理清 covergroup 的核心结构:

covergroup 知识体系 covergroup coverpoint cross option / iff bins 定义 ignore_bins 组合场景覆盖 per_instance / weight 核心:定义采样点 → 分 bin → 组合 → 条件控制 最终目标:量化功能验证的完备性

3.8 一个完整的例子

把上面学到的串起来,写一个完整的 covergroup:

interface fifo_if;
  logic [7:0] data_in;
  logic [7:0] data_out;
  logic       wr_en;
  logic       rd_en;
  logic       full;
  logic       empty;
  logic [3:0] fifo_count;
endinterface

class fifo_coverage;
  fifo_if vif;
  
  covergroup cg_fifo @(posedge vif.clk);
    // 数据覆盖
    coverpoint vif.data_in {
      bins zero    = {8'h00};
      bins max     = {8'hFF};
      bins middle  = {[8'h01:8'hFE]};
    }
    
    // 状态覆盖
    coverpoint vif.fifo_count {
      bins empty    = {0};
      bins one      = {1};
      bins full     = {16};
      bins partial  = {[2:15]};
    }
    
    // 操作组合覆盖
    coverpoint vif.wr_en;
    coverpoint vif.rd_en;
    cross vif.wr_en, vif.rd_en {
      // 忽略同时读写的情况(如果设计不允许)
      ignore_bins illegal = binsof(vif.wr_en) && binsof(vif.rd_en);
    }
    
    // 条件采样:只在复位释放后采样
    coverpoint vif.data_out iff (!vif.rst_n);
    
  endgroup
  
  function new(fifo_if vif);
    this.vif = vif;
    cg_fifo = new();
  endfunction
  
endclass

这个例子覆盖了:数据值、FIFO深度、读写操作组合、以及复位后的输出。基本上一个 FIFO 的核心功能点都覆盖到了。

3.9 避坑指南

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

  • bin 数量失控:交叉覆盖率不要无脑全组合。先用 ignore_bins 过滤掉无效组合。
  • 采样时机不对:默认是每次 covergroup 被触发时采样。如果你用 @(posedge clk),那每个时钟周期都采。有时候你只想在事务结束时采一次,记得用 sample() 方法手动控制。
  • 忘记实例化:covergroup 定义好了,不 new() 就是空的。我见过有人写了 500 行的 covergroup,结果覆盖率一直是 0%,就是因为忘了 new。
  • 忽略边界值:自动分 bin 可能把 0 和最大值合并到区间里。手动分 bin 时,一定要把边界值单独拎出来。

一个小技巧:在仿真日志里打印 $get_coverage(),可以实时看到总体覆盖率。我习惯在每个测试用例结束时打印一次,方便快速定位哪个用例覆盖率低。

功能覆盖率入门其实不难。记住三个核心:covergroup 是容器,coverpoint 是采样点,cross 是组合。先把这三个概念搞明白,后面再学 transition coverage、illegal_bins 这些进阶内容就顺了。


公众号:蓝海资料掘金营,微信deep3321