3、功能覆盖率入门:功能覆盖点的定义、仓(bin)的概念、覆盖组(covergroup)的基本结构

各位同学,咱们今天聊聊功能覆盖率。说实话,我刚入行那会儿,对功能覆盖率也是一头雾水。总觉得代码覆盖率跑一跑就完事了,干嘛还要搞个功能覆盖率?直到有一次,我负责一个总线仲裁器的验证,代码覆盖率100%,但芯片流片回来就是有bug。后来一查,是某个仲裁优先级组合压根没测到。嗯,从那以后,我再也不敢小看功能覆盖率了。

3.1 什么是功能覆盖点?

功能覆盖点,说白了就是你想检查的某个信号或表达式。它回答一个问题:「这个信号有没有出现过我想要的值?」

举个例子。你设计了一个FIFO,你想知道它什么时候满、什么时候空。那「fifo_full」和「fifo_empty」就是两个功能覆盖点。

核心定义:功能覆盖点(coverpoint)是覆盖组中的一个成员,它指定了一个被监视的表达式或变量,并定义了该表达式需要被观察的取值集合。

我个人习惯,在定义覆盖点之前,先问自己三个问题:

  • 这个信号的关键取值有哪些?
  • 这些取值是否覆盖了所有设计边界?
  • 有没有遗漏的非法组合?

3.2 仓(bin)的概念——你真正关心的那些值

仓,英文叫bin。你可以把它理解成一个「值的小抽屉」。每个覆盖点下面,你可以定义若干个仓,每个仓对应一组你关心的取值。

比如,你想监控一个3位的状态机状态(取值范围0~7),但你只关心状态0、状态3、状态7。那你只需要定义三个仓:

covergroup state_cg;
  coverpoint state {
    bins idle = {0};
    bins active = {3};
    bins error = {7};
  }
endgroup

你看,这里每个bin就是一个「小抽屉」。仿真跑完后,工具会告诉你每个抽屉里掉进去了多少次。

我的经验:不要贪多。我曾经在一个项目里,把每个覆盖点都定义了256个bin,结果覆盖率报告看得我眼花缭乱。后来我学乖了,只定义那些真正有意义的边界值和关键值。你想想看,一个信号有256种取值,但真正需要关心的可能就5~6个。

3.2.1 自动仓 vs 显式仓

SystemVerilog提供了两种仓的定义方式:

类型 说明 示例
自动仓 工具自动为每个可能取值创建bin bins auto[] = {[0:7]};
显式仓 用户手动指定关心的取值 bins my_bin = {3, 5, 7};
忽略仓 明确告诉工具「这个值我不关心」 bins ignore = {2, 4};
非法仓 如果出现这个值,报错 bins illegal = {1, 6};

避坑指南:我曾经在一个项目中,忘记给某个覆盖点设置ignore_bins,结果仿真器自动为所有256个取值都创建了bin。覆盖率报告显示只有2%的覆盖率,吓得我以为是验证环境出了问题。后来才发现,是自动仓太多了,大部分取值根本不会出现。所以,能用显式仓就别用自动仓,除非你的信号取值范围很小。

3.3 覆盖组(covergroup)的基本结构

覆盖组,你可以把它想象成一个「覆盖点的集合」。它把多个相关的覆盖点打包在一起,统一采样、统一管理。

基本语法长这样:

covergroup fifo_cg @(posedge clk);
  // 覆盖点1:FIFO状态
  coverpoint fifo_state {
    bins empty = {0};
    bins full  = {7};
    bins mid   = {[1:6]};
  }
  
  // 覆盖点2:写使能
  coverpoint wr_en {
    bins wr_active = {1};
    bins wr_idle   = {0};
  }
  
  // 覆盖点3:读使能
  coverpoint rd_en {
    bins rd_active = {1};
    bins rd_idle   = {0};
  }
endgroup

这里有几个关键点:

  • 采样事件:@(posedge clk) 表示在每个时钟上升沿采样一次
  • 覆盖点:每个coverpoint监控一个信号
  • 仓定义:每个coverpoint下面可以定义多个bin

3.3.1 覆盖组的实例化

定义好覆盖组之后,还需要实例化才能用:

fifo_cg cg_inst = new();
// 在测试用例中,覆盖组会在每个时钟沿自动采样

我建议你把覆盖组的实例化放在monitor或者scoreboard里,这样便于统一管理。

3.4 知识体系结构图

下面这张图,是我自己总结的功能覆盖率核心逻辑,你一看就明白了:

功能覆盖率核心知识体系 覆盖组 (covergroup) 覆盖点 (coverpoint) —— 你想检查的信号或表达式 仓 (bin) —— 你关心的具体取值 自动仓 工具自动创建 显式仓 手动指定取值 忽略仓 不关心的取值 非法仓 出现即报错 核心逻辑:覆盖组 → 覆盖点 → 仓,逐层细化你的验证目标

3.5 实战小技巧

最后,分享几个我在项目中积累的小技巧:

  1. 覆盖点命名要有意义:别用cp1、cp2这种名字。我见过一个项目,覆盖点名字全是cp_xxx,三个月后连写代码的人自己都看不懂了。
  2. 仓的数量要适中:一个覆盖点下面,仓的数量建议控制在5~20个。太少测不全,太多看不完。
  3. 别忘了交叉覆盖率:单个覆盖点只能检查一个信号。如果你想检查两个信号的组合,需要用cross。这个我们后面会详细讲。
  4. 覆盖率不是越高越好:100%的功能覆盖率不代表没有bug。它只能告诉你「这些场景我测过了」,不能告诉你「所有场景都测过了」。

我的习惯:每次写完覆盖组,我都会先跑一个简单的测试用例,然后打开覆盖率报告看一眼。确认每个bin都有命中,再继续写下一个覆盖组。这样能避免到最后才发现某个bin定义错了。

好了,功能覆盖率的入门就讲到这里。记住三个关键词:覆盖点、仓、覆盖组。把这套逻辑理清楚,后面的交叉覆盖率和覆盖率合并就简单多了。

可可精品资料库