3、功能覆盖模型:覆盖组、覆盖点、交叉覆盖、覆盖仓的定义与使用

功能覆盖率,说白了就是验证的「体检报告」。

你写了那么多测试用例,跑了那么多仿真,到底测全了没有?哪些场景测到了,哪些还没测到?功能覆盖模型就是回答这些问题的。我个人习惯,在项目一开始就把覆盖模型搭好,而不是等到验证快结束了才补。为什么?因为覆盖模型能指导你写测试用例,而不是反过来。

3.1 覆盖组(covergroup)—— 你的「检查清单」

覆盖组是一个用户自定义的容器。你把想要检查的信号、状态、组合都放进去。它有点像你出差前的行李清单——哪些东西带了,哪些没带,一目了然。

我在项目中遇到过一种情况:团队里有人把所有的覆盖点都写在一个巨大的覆盖组里,结果维护起来特别痛苦。我建议按功能模块或协议状态机来划分覆盖组。比如一个覆盖组专门检查AXI的写通道,另一个专门检查读通道。

核心要点: 覆盖组可以定义在类里,也可以定义在模块或接口中。它需要被实例化,并且通过 sample() 方法来采样数据。
// 一个简单的覆盖组示例
covergroup fifo_cg @(posedge clk);
  // 覆盖点定义在这里
endgroup

// 在类中实例化
class fifo_monitor;
  fifo_cg cg;
  
  function new();
    cg = new();
  endfunction
  
  task run();
    // 采样
    cg.sample();
  endtask
endclass
我的小技巧: 我习惯用 @(posedge clk) 事件来触发采样,这样能保证采样时刻和时序逻辑同步。如果你用组合逻辑采样,很容易采到毛刺或中间态。

3.2 覆盖点(coverpoint)—— 盯住关键信号

覆盖点就是你要检查的具体信号或表达式。每个覆盖点可以定义多个仓(bins),用来划分你关心的取值区间。

你想想看,一个32位的数据总线,你不可能去覆盖每一个值——2的32次方,仿真到天荒地老也跑不完。所以我们要做的是「分仓」,把连续的值空间划分成几个有意义的区间。

covergroup fifo_cg @(posedge clk);
  // 覆盖点:fifo的深度
  depth: coverpoint fifo_depth {
    bins empty = {0};           // 空
    bins almost_empty = {1, 2}; // 几乎空
    bins mid = {[3:12]};        // 中间区域
    bins almost_full = {13, 14};// 几乎满
    bins full = {15};           // 满
  }
endgroup

这里要注意,{[3:12]} 这种写法表示从3到12的所有整数。如果你想要的是3或12两个值,应该写成 {3, 12}。嗯,这个细节我刚开始也搞混过。

我曾经踩过的坑: 有一次我定义了一个覆盖点,但忘了指定仓。默认情况下,SystemVerilog会为每个值自动建仓。结果仿真跑了三天,生成了几万个仓,覆盖率报告根本没法看。所以,一定要显式定义仓,除非你确定自动建仓是你想要的。

3.3 覆盖仓(bins)—— 精细化管理

仓是覆盖点的基本单位。每个仓代表一组你关心的取值。除了上面提到的普通仓,还有几种特殊仓,我简单列一下:

仓类型 语法 说明
普通仓 bins xxx = {value}; 指定一个或多个值
范围仓 bins xxx = {[low:high]}; 指定一个连续区间
非法仓 illegal_bins xxx = {value}; 如果采样到该值,直接报错
忽略仓 ignore_bins xxx = {value}; 采样到该值时忽略,不计入覆盖率
自动仓 bins xxx[] = {[low:high]}; 自动将区间分成多个仓
// 非法仓和忽略仓的使用场景
covergroup packet_cg @(posedge clk);
  pkt_type: coverpoint pkt_type {
    // 合法类型
    bins data = {0, 1, 2};
    bins ctrl = {3, 4};
    // 类型5是保留的,不应该出现
    illegal_bins reserved = {5};
    // 类型6和7在本次验证中不关心
    ignore_bins dont_care = {6, 7};
  }
endgroup
为什么用非法仓? 我个人的经验是,非法仓不仅能帮你检查覆盖率,还能在仿真阶段就发现设计bug。比如某个状态机不应该进入非法状态,一旦进入,仿真立刻报错,比事后看覆盖率报告要快得多。

3.4 交叉覆盖(cross cover)—— 组合才是王道

单个信号的覆盖往往不够。很多bug是在特定组合下才出现的。交叉覆盖就是用来检查多个覆盖点之间的组合情况。

举个例子,你单独检查FIFO的读使能和写使能,可能覆盖率都是100%。但读和写同时为高的情况呢?这就是交叉覆盖要干的事。

covergroup fifo_cg @(posedge clk);
  wr_en: coverpoint write_enable {
    bins low = {0};
    bins high = {1};
  }
  
  rd_en: coverpoint read_enable {
    bins low = {0};
    bins high = {1};
  }
  
  // 交叉覆盖:读写使能的四种组合
  rw_cross: cross wr_en, rd_en;
endgroup

默认情况下,交叉覆盖会生成所有组合的仓。上面这个例子会生成4个仓:(low, low)、(low, high)、(high, low)、(high, high)。

注意: 交叉覆盖的仓数量是各覆盖点仓数的乘积。如果wr_en有10个仓,rd_en有10个仓,交叉后就是100个仓。如果再加上第三个覆盖点...你想想看,仓数会爆炸。所以,交叉覆盖要慎用,只交叉真正有意义的组合。

我遇到过最夸张的一次,有人交叉了5个覆盖点,每个覆盖点有几十个仓,结果生成了上百万个交叉仓。仿真跑了整整一周,覆盖率报告打开就卡死。后来我帮他重新设计,只交叉了最关键的两个信号,问题一下就定位到了。

// 更精细的交叉覆盖控制
covergroup fifo_cg @(posedge clk);
  wr_en: coverpoint write_enable {
    bins low = {0};
    bins high = {1};
  }
  
  rd_en: coverpoint read_enable {
    bins low = {0};
    bins high = {1};
  }
  
  depth: coverpoint fifo_depth {
    bins empty = {0};
    bins full = {15};
    bins other = {[1:14]};
  }
  
  // 只交叉读写使能,不交叉depth
  rw_cross: cross wr_en, rd_en;
  
  // 也可以自定义交叉仓
  rw_depth_cross: cross wr_en, rd_en, depth {
    // 只关心同时读写且FIFO满的情况
    bins rw_full = binsof(wr_en.high) && binsof(rd_en.high) && binsof(depth.full);
  }
endgroup
避坑指南: 我曾经在交叉覆盖里用了 binsof 和 intersect 操作符,结果写错了条件,导致某些组合永远达不到。检查了半天才发现是条件写反了。所以,写完交叉覆盖后,建议先跑几个简单的测试用例,手动验证一下仓的命中逻辑是否正确。

3.5 覆盖模型的设计原则

说了这么多,我总结几条实战中总结出来的原则:

  • 先设计,后编码。 别上来就写代码。先在纸上画出状态机、数据流,标出哪些信号需要覆盖,哪些组合需要交叉。
  • 覆盖组按功能划分。 一个覆盖组对应一个功能模块或一个协议通道。别把所有东西塞到一个覆盖组里。
  • 仓的数量要可控。 单个覆盖点的仓数建议不超过20个。交叉覆盖的仓数建议不超过1000个。超过这个数,覆盖率报告就很难看了。
  • 非法仓和忽略仓要用好。 非法仓帮你抓bug,忽略仓帮你过滤不关心的场景,两者都能让覆盖率报告更干净。
  • 覆盖率不是越高越好。 100%的覆盖率不代表没有bug。但覆盖率太低,一定有问题。我个人习惯,功能覆盖率目标定在95%以上,剩下的5%留给那些确实无法达到的边界情况。
功能覆盖模型核心结构 覆盖组 (covergroup) 覆盖点 (coverpoint) 交叉覆盖 (cross) 覆盖仓 (bins) 覆盖点类型 • 普通仓:指定具体值 • 范围仓:连续区间 • 非法/忽略仓:特殊处理 交叉覆盖要点 • 组合多个覆盖点 • 仓数 = 各点仓数乘积 • 用 binsof 自定义 覆盖仓原则 • 显式定义,避免自动 • 数量可控(≤20/点) • 交叉仓 ≤1000 目标:95%+ 功能覆盖率,指导测试用例设计

好了,关于功能覆盖模型的核心概念就讲到这里。覆盖组、覆盖点、交叉覆盖、覆盖仓,这四个概念是功能覆盖率验证的基石。你只要把它们的用法和设计原则搞清楚了,后面写覆盖模型就会顺手很多。

可可精品资料库