📌 本篇导读

知识点 难度 实战价值 典型场景
功能覆盖率核心概念 ⭐⭐ ⭐⭐⭐⭐⭐ 验证完备性度量、验证计划落地
covergroup 定义与实例化 ⭐⭐⭐ ⭐⭐⭐⭐⭐ 覆盖模型搭建
coverpoint 基础用法 ⭐⭐⭐ ⭐⭐⭐⭐⭐ 采样点定义
自动 bins 与显式 bins ⭐⭐⭐ ⭐⭐⭐⭐⭐ 取值区间划分
带条件的 bins 与通配符 bins ⭐⭐⭐ ⭐⭐⭐⭐ 复杂场景过滤
ignore_binsillegal_bins ⭐⭐⭐ ⭐⭐⭐⭐ 排除无效/非法场景
cross 交叉覆盖率 ⭐⭐⭐ ⭐⭐⭐⭐ 多变量组合场景覆盖
交叉爆炸问题与解决方案 ⭐⭐⭐⭐ ⭐⭐⭐⭐ 避免覆盖率模型性能问题
过渡覆盖率(transition ⭐⭐⭐ ⭐⭐⭐ 状态机跳转与时序覆盖
覆盖率采样与运行时控制 ⭐⭐ ⭐⭐⭐⭐⭐ 动态控制覆盖率收集
覆盖率驱动验证(CDV)流程 ⭐⭐⭐⭐ ⭐⭐⭐⭐ 闭环验证管理

💡 高频误区预警

  • 混淆功能覆盖率与代码覆盖率(代码覆盖率≠功能完备性)
  • 忘记实例化 covergroup 导致覆盖率始终为 0
  • 混淆数组 bins范围 binsbins a[] = {[0:3]} vs bins a = {[0:3]}
  • 未加 iff 条件导致复位/未就绪期间采集无效数据
  • 滥用 ignore_bins 掩盖验证缺口

⚠️ 本文所有内容 仅用于验证,不可综合。功能覆盖率是验证完备性的唯一量化标准,直接决定验证收工节点。


1 功能覆盖率核心概念

1.1 为什么必须做功能覆盖率

代码覆盖率(行、分支、条件、状态机)只能证明代码被执行过,但无法证明所有设计意图和功能场景都被测试过

典型反例

  • 代码覆盖率 100%,但从未测试过“写操作后立即读”的关键时序
  • 代码覆盖率 100%,但从未覆盖“边界长度帧”“错误帧”等异常场景

功能覆盖率的核心价值:

  1. 验证计划转化为可执行的量化指标
  2. 自动发现未被测试的功能场景
  3. 指导验证资源投入(优先覆盖低覆盖率场景)
  4. 作为验证签收的核心依据

1.2 基本工作流程

制定验证计划

编写覆盖模型

运行随机测试

收集覆盖率

达到目标?

分析未覆盖点

调整约束/写定向测试

验证签收


2 covergroup 基础

covergroup 是 SystemVerilog 中定义功能覆盖率模型的基本单元,是一个可复用的类,可以包含多个 coverpointcross

2.1 基本语法结构

covergroup 覆盖组名 [([参数列表])] [@(触发事件)];
  option.选项名 = 值;          // 覆盖组级选项
  coverpoint 变量名 {          // 覆盖点定义
    option.选项名 = 值;        // 覆盖点级选项
    bins 名称 = {取值集合};    // 显式 bins 定义
    ignore_bins 名称 = {取值集合}; // 忽略 bins
    illegal_bins 名称 = {取值集合}; // 非法 bins
  }
  cross 变量1, 变量2 {         // 交叉覆盖定义
    option.选项名 = 值;        // 交叉级选项
  }
endgroup

2.2 三种实例化与采样方式

方式1:独立实例化 + 显式采样(最灵活)
// 定义覆盖组
covergroup cg_simple;
  coverpoint a;
  coverpoint b;
endgroup

// 实例化与采样
bit [1:0] a, b;
cg_simple cg_inst = new();  // 必须调用 new() 实例化

initial begin
  a = 2'b01; b = 2'b10;
  cg_inst.sample();          // 显式采样当前值
end
方式2:类内封装 + 自动采样(最常用)

covergroup 封装在事务类中,在 post_randomize() 中自动采样,实现“随机化即采样”。

class Transaction;
  rand bit [2:0] kind;
  rand bit [7:0] data;
  
  // 类内定义覆盖组
  covergroup cg_trans;
    coverpoint kind;
    coverpoint data;
  endgroup
  
  function new();
    cg_trans = new();  // 构造函数中实例化
  endfunction
  
  // 随机化完成后自动采样
  function void post_randomize();
    cg_trans.sample();
  endfunction
endclass
方式3:事件触发自动采样(适合时序场景)

无需显式调用 sample(),在指定事件发生时自动采样,配合 iff 过滤无效状态。

bit clk, rst_n, rx_valid;
bit [7:0] rx_data;

// 时钟上升沿且复位释放时自动采样
covergroup cg_rx @(posedge clk);
  coverpoint rx_data iff (rst_n && rx_valid);
endgroup

cg_rx cg_rx_inst = new();  // 实例化后自动开始监听事件

2.3 参数化覆盖组(复用性设计)

通过参数实现覆盖组的通用化,不同实例可以有不同的配置。关键:采样变量必须通过 ref 参数传入,否则所有实例共享同一个外部变量。

// 正确的参数化覆盖组(符合 IEEE 1800-2017)
covergroup cg_param (ref int val, int max_val = 255, int auto_bin_num = 8);
  option.auto_bin_max = auto_bin_num;
  coverpoint val {
    bins min = {0};
    bins max = {max_val};
  }
endgroup

// 创建不同配置的实例,分别绑定不同的变量
int val8, val16;
cg_param cg_8bit = new(val8, 255, 16);   // 绑定到 val8,最大值255,自动分16个bin
cg_param cg_4bit = new(val16, 15, 16);   // 绑定到 val16,最大值15,自动分16个bin

常见错误:不要省略 ref 关键字。如果不使用 ref,覆盖组会去外部作用域查找名为 val 的变量,导致所有实例都采样同一个外部变量,覆盖率数据完全错误。

2.4 实战扩展:接口绑定

在实际项目中,更常用的是传递 virtual interface 作为 ref 参数,实现覆盖组与 DUT 接口的解耦绑定:

covergroup cg_bus (virtual bus_if vif, int addr_width=32);
  coverpoint vif.addr {
    bins low_addr = {[0:1023]};
    bins high_addr = {[1024:(1<<addr_width)-1]};
  }
  coverpoint vif.cmd;
endgroup

3 coverpointbins 详解

coverpoint 定义需要采样的变量,bins 定义该变量的取值区间划分,是功能覆盖率的核心。

3.1 自动 bins

如果不手动定义 bins,SystemVerilog 会根据变量类型自动生成:

  • 枚举类型:为每个枚举值生成一个单独的 bin
  • 整型/位向量:生成均匀分布的自动 bins,数量由 option.auto_bin_max 控制(标准默认值:64)
enum {IDLE, READ, WRITE, ERROR} state;
coverpoint state;  // 自动生成 4 个 bins:IDLE, READ, WRITE, ERROR

bit [7:0] data;
coverpoint data;   // 默认生成 64 个均匀区间的 bins(每个 bin 4 个值)

3.2 显式 bins(核心重点)

手动定义 bins 可以精确控制覆盖粒度,是验证计划落地的关键。

3.2.1 单个值与范围 bins
coverpoint frame_len {
  // 单个值 bin:每个 bin 对应一个特定值
  bins len_0   = {0};
  bins len_46  = {46};
  bins len_1500 = {1500};
  
  // 范围 bin:一个 bin 包含范围内所有值
  bins normal_len = {[47:1499]};  // 所有正常长度共用一个 bin
  
  // 默认 bin:收集所有未被其他 bins 覆盖的值
  bins others = default;
}
3.2.2 数组 bins

数组 bins 用于为取值集合中的每个独立值生成一个单独的 bin,特别适合枚举类型和有限取值范围的信号。

// 数组 bins:为 0~7 的每个值生成一个独立 bin(共 8 个 bin)
bins each_op[] = {[0:7]};

精确说明:数组 bins 的取值集合可以是单值列表、范围,或两者的组合,每个值生成一个独立 bin

⚠️ 注意:如果信号位宽大于取值范围所需的位数(例如 bit [7:0] opcode),[0:7] 仅覆盖 0~7 的值,8~255 的值会被自动归入隐式 default bin(除非显式定义了 default bin 或 ignore_bins)。

与普通 bins 的对比

bins one_bin = {[0:7]};      // 1个bin,包含8个值
bins eight_bins[] = {[0:7]}; // 8个bin,每个bin包含1个值
3.2.3 带条件的 bins( with 子句)

使用 with 子句过滤符合特定条件的值,实现复杂场景的覆盖。

coverpoint payload_len {
  // 一个 bin 包含所有奇数长度
  bins odd_len = {[46:1500]} with (item % 2 == 1);
  
  // 为每个偶数长度生成单独的 bin
  bins even_len[] = {[46:1500]} with (item % 2 == 0);
  
  // 覆盖大于 1000 的长度
  bins long_len = {[46:1500]} with (item > 1000);
}
3.2.4 通配符 bins(wildcard

只关心特定 bit 位,其余位用 ? 表示通配(匹配 0 或 1),适合地址空间、指令编码等场景。

coverpoint addr [31:0] {
  // 最高 4 位为 0x8 的地址(外设空间)
  wildcard bins periph_addr = {32'h8???_????};
  
  // 最低 2 位为 0 的地址(4字节对齐)
  wildcard bins aligned_addr = {32'h????_???0};
}

3.3 忽略 bins(ignore_bins

排除不关心或不可达的取值,这些 bins 未命中不会影响覆盖率百分比。

coverpoint reserved_field {
  // 预留字段的取值不参与覆盖率计算
  ignore_bins reserved = {2'b10, 2'b11};
}

coverpoint cmd {
  // 忽略所有大于 3 的无效命令
  ignore_bins invalid = {[4:$]};
}

3.4 非法 bins(illegal_bins

如果采样到非法值,仿真会立即报错并终止,用于捕获设计中不应该出现的状态。

coverpoint parity {
  // 奇偶校验位只能是 01 或 10
  illegal_bins bad_parity = {2'b00, 2'b11};
}

coverpoint state {
  // 不允许直接从 ERROR 跳转到 IDLE
  illegal_bins error_to_idle = (ERROR => IDLE);
}

4 覆盖选项(option

option 用于配置覆盖组、覆盖点和交叉的行为,分为全局选项实例选项

4.1 常用选项速查表

选项 作用域 含义 默认值
option.weight 所有 该对象在总覆盖率中的权重 1
option.goal 所有 目标覆盖率百分比(达到即视为完成) 100
option.comment 所有 注释文本,显示在覆盖率报告中 “”
option.per_instance covergroup 是否单独统计每个实例的覆盖率 0(合并统计)
option.at_least 所有 至少命中多少次才计入覆盖 1
option.auto_bin_max coverpoint 自动 bins 的最大数量 64
option.cross_auto_bin_max cross 交叉自动 bins 的最大数量 64
option.cross_num_print_missing cross 打印未覆盖交叉组合的最大数量 10

4.2 选项使用示例

covergroup cg_ethernet;
  // 覆盖组级选项
  option.per_instance = 1;   // 每个实例单独统计覆盖率
  option.goal = 95;          // 目标覆盖率 95%
  option.comment = "Ethernet MAC receive coverage";
  option.weight = 2;         // 该覆盖组权重为 2(比其他组更重要)
  
  coverpoint frame_len {
    // 覆盖点级选项
    option.at_least = 2;     // 每个 bin 至少命中 2 次才计入覆盖
    option.auto_bin_max = 8; // 自动 bins 数量为 8
    bins min = {46};
    bins max = {1500};
  }
endgroup

4.3 关键选项说明

  • option.per_instance:当同一个覆盖组被多个实例使用时(如多个通道的相同覆盖模型),设置为 1 可以单独统计每个通道的覆盖率,避免“一个通道覆盖了所有 bins 就认为全部覆盖”的问题。
  • option.at_least:设置为大于 1 的值可以避免单次偶然命中就认为场景被覆盖,提高验证的可靠性。
  • option.auto_bin_max:对于位宽较大的变量(如 32 位地址),应适当减小该值,避免生成过多 bins 导致仿真内存占用过高。
  • option.cross_num_print_missing:调试交叉覆盖率的必备工具。默认值为 10;设置为 0 时打印所有未覆盖的组合(适合覆盖率接近 100% 时排查最后几个点)。

5 交叉覆盖率(cross

交叉覆盖率用于测量两个或多个变量的组合覆盖情况,是验证多变量交互场景的核心手段。

5.1 基本交叉

class Packet;
  rand bit [1:0] cmd;   // 0:READ, 1:WRITE, 2:IDLE, 3:RESERVED
  rand bit [2:0] size;  // 0~7 突发长度
  
  covergroup cg;
    cp_cmd : coverpoint cmd {
      ignore_bins reserved = {3};  // 排除预留命令
    }
    cp_size : coverpoint size;
    
    // 交叉覆盖 cmd 和 size 的所有有效组合(3×8=24 个 bins)
    cmd_size_cross : cross cmd, size;
  endgroup
  
  function new();
    cg = new();
  endfunction
endclass

5.2 交叉爆炸问题与解决方案

当交叉变量较多或每个变量取值较多时,bins 数量会呈指数增长(如 4 个 8 位变量交叉会生成 40 亿个 bins),导致仿真内存占用过高甚至崩溃。

解决方案

  1. 减少交叉变量数量:优先交叉 2-3 个关键变量,非关键变量单独覆盖
  2. 使用 ignore_bins 排除无效组合:过滤不可能出现或不关心的组合
  3. 使用带标签的交叉:只覆盖特定的组合,而非所有组合
  4. 限制自动 bins 数量:通过 option.cross_auto_bin_max 限制交叉自动 bins 数量

5.3 带标签的交叉覆盖

使用 binsof(coverpoint) intersect {value_set} 语法只覆盖特定组合,是解决交叉爆炸的最佳实践。

// 定义枚举类型(提高可读性)
typedef enum {READ, WRITE, CLEAR, INVALID} cmd_t;
cmd_t cmd;
int len;

covergroup cg_cross;
  cp_cmd: coverpoint cmd;
  cp_len: coverpoint len {
    bins short = {[1:16]};
    bins long = {[17:1024]};
  }
  
  cross cp_cmd, cp_len {
    // 使用枚举常量提高可读性
    bins rw_short = binsof(cp_cmd) intersect {READ, WRITE} && 
                    binsof(cp_len) intersect {short};
    bins rw_long  = binsof(cp_cmd) intersect {READ, WRITE} && 
                    binsof(cp_len) intersect {long};
    ignore_bins invalid = binsof(cp_cmd) intersect {INVALID};
  }
endgroup

最佳实践:使用枚举常量代替硬编码数字,当枚举定义修改时,交叉条件会自动同步更新,避免维护错误。

⚠️ 标准语法强制要求binsof() 的参数必须是覆盖点名,不能是 bin 名。错误写法:binsof(cp_cmd.READ),正确写法:binsof(cp_cmd) intersect {READ}

5.4 交叉覆盖率的权重

可以为交叉中的特定 bins 设置更高的权重,确保关键组合被优先覆盖。

cross cmd, size {
  bins critical = binsof(cp_cmd) intersect {WRITE} && 
                  binsof(cp_size) intersect {7} with (weight = 5);
  bins normal = binsof(cp_cmd) intersect {0, 1} && 
                binsof(cp_size) intersect {[0:6]};
}

6 覆盖率采样与运行时控制

6.1 采样触发方式对比

触发方式 优点 缺点 适用场景
显式调用 sample() 最灵活,可在任意时机触发 需要手动调用 事务级验证、复杂时序场景
事件自动触发 无需手动调用,时序准确 不够灵活 时钟域同步信号、接口信号
类内自动采样 与事务绑定,不易遗漏 只能在随机化后采样 约束随机验证

6.2 covergroup 内置方法(标准定义)

IEEE 1800-2017 定义了 covergroup 的标准内置方法,用于运行时控制覆盖率收集:

cg_inst.sample();                      // 采样当前变量值,累积计数
real cov = cg_inst.get_coverage();     // 返回该覆盖组的当前覆盖率(0~100)
cg_inst.start();                       // 开始收集覆盖率(实例化后默认已开始)
cg_inst.stop();                        // 停止收集覆盖率
cg_inst.reset();                       // 复位该覆盖组的所有计数到 0

6.3 全局覆盖率查询

// 获取所有已实例化覆盖组的加权平均覆盖率
real total_cov = $get_coverage();

精确说明

  • $get_coverage() 返回的是所有已实例化且未调用 stop() 的覆盖组的加权平均覆盖率,权重由 option.weight(默认值 1)决定。
  • 返回值范围是 0.0 ~ 100.0(百分比)。如果所有覆盖组都未采样,返回 0.0。
  • 如果没有任何覆盖组实例,返回 0.0。

6.4 wildcard 与 ignore_bins/illegal_bins 组合

wildcard 关键字允许使用 x? 匹配任意位值,与 ignore_binsillegal_bins 组合使用,是总线验证中处理地址/数据位掩码的标准用法。

典型示例(AXI 总线地址过滤)

covergroup cg_axi_addr (virtual axi_if vif);
  coverpoint vif.araddr {
    // 忽略高 4 位为 0xF 的所有保留地址
    wildcard ignore_bins reserved_addr = {32'hF???_????};
    
    // 忽略所有奇数地址(32位对齐访问要求)
    wildcard ignore_bins unaligned_addr = {32'h????_???1};
    
    // 标记高 4 位为 0xE 的地址为非法访问
    wildcard illegal_bins illegal_addr = {32'hE???_????};
  }
endgroup

实战价值:这种写法可以简洁地过滤掉大量不需要覆盖的地址范围,避免手动列出所有可能值。


7 过渡覆盖率(transition

过渡覆盖率用于测量变量的状态变化序列,验证状态机跳转、信号时序等动态行为。

7.1 基本过渡

coverpoint state {
  // 覆盖单个跳转:IDLE → READ
  bins idle_to_read = (IDLE => READ);
  
  // 覆盖多个跳转:IDLE → READ 或 IDLE → WRITE
  bins idle_to_active = (IDLE => READ, WRITE);
  
  // 覆盖连续跳转:IDLE → READ → WRITE
  bins idle_read_write = (IDLE => READ => WRITE);
}

7.2 重复过渡

使用 [*n] 表示连续 n 次相同状态,[*m:n] 表示连续 m 到 n 次相同状态。

coverpoint bus_state {
  // IDLE 连续 2 个周期后跳转到 BUSY
  bins idle2busy = (IDLE [*2] => BUSY);
  
  // BUSY 连续 3-5 个周期后跳转到 IDLE
  bins busy2idle = (BUSY [*3:5] => IDLE);
  
  // 任意状态跳转到 ERROR
  bins any_to_error = ([*1:$] => ERROR);
}

7.3 非连续过渡([->n]

使用 [->n] 表示非连续出现 n 次指定状态,适合验证“事件发生后某个状态出现”的场景。

coverpoint {
  // 复位后,在任意时刻出现 1 次 tx_done
  bins reset_to_tx_done = (RST => [*0:$] => tx_done [->1]);
}

明白了,你希望保留 8.1、8.2、8.3 的结构,但内容更简洁,去掉冗长的解释。下面给出精简但完整可运行的版本。


8 完整工程示例:以太网 MAC 接收验证

/
//================================================
// 8.1 DUT(以太网 MAC 接收简化模型)
//================================================
module eth_mac_rx (
    input  logic        clk,
    input  logic        rst_n,
    input  logic        rx_valid,
    input  logic [7:0]  rx_data,
    output logic        rx_ready,
    output logic        frame_ok,
    output logic [15:0] frame_len,
    output logic [15:0] eth_type
);
    logic [7:0] last_data;
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            frame_ok <= 0;
            frame_len <= 0;
            eth_type <= 0;
            last_data <= 0;
        end else begin
            if (rx_valid) last_data <= rx_data;
            if (rx_valid && rx_data == 8'hFF) begin
                frame_ok <= 1;
                case (last_data)
                    8'h01: begin eth_type <= 16'h0800; frame_len <= 46;  end
                    8'h02: begin eth_type <= 16'h0806; frame_len <= 46;  end
                    8'h03: begin eth_type <= 16'h8100; frame_len <= 46;  end
                    8'h04: begin eth_type <= 16'h0800; frame_len <= 1500; end
                    8'h05: begin eth_type <= 16'h0806; frame_len <= 1500; end
                    8'h06: begin eth_type <= 16'h8100; frame_len <= 1500; end
                    default: begin eth_type <= 16'h0800; frame_len <= 100; end
                endcase
            end else begin
                frame_ok <= 0;
            end
        end
    end
    assign rx_ready = 1'b1;
endmodule

//================================================
// 8.2 事务类(仅用于示意,未实际使用随机激励)
//================================================
class EthFrame;
    rand bit [15:0] eth_type;
    rand int payload_len;
    constraint c_eth_type { eth_type inside {16'h0800, 16'h0806, 16'h8100}; }
    constraint c_len { payload_len inside {[46:1500]}; }
endclass

//================================================
// 覆盖模型(独立模块,采用下降沿采样避免竞争)
//================================================
module eth_coverage (
    input logic clk,
    input logic rst_n,
    input logic frame_ok,
    input logic [15:0] frame_len,
    input logic [15:0] eth_type
);
    covergroup cg_eth_rx;
        cp_eth_type: coverpoint eth_type {
            bins ipv4 = {16'h0800};
            bins arp  = {16'h0806};
            bins vlan = {16'h8100};
        }
        cp_frame_len: coverpoint frame_len {
            bins min   = {46};
            bins max   = {1500};
            bins other = default;
        }
        cross cp_eth_type, cp_frame_len;
    endgroup

    cg_eth_rx cg_inst;

    // 使用下降沿采样,确保 DUT 输出已稳定
    always @(negedge clk) begin
        if (rst_n && frame_ok) begin
            cg_inst.sample();
        end
    end

    initial begin
        cg_inst = new();
    end
endmodule

//================================================
// 接口定义(连接 DUT 和测试模块)
//================================================
interface eth_if(input logic clk);
    logic rst_n;
    logic rx_valid;
    logic [7:0] rx_data;
    logic rx_ready;
    logic frame_ok;
    logic [15:0] frame_len;
    logic [15:0] eth_type;
endinterface

//================================================
// 测试模块(使用 module,避免 program 的兼容性问题)
//================================================
module eth_test(eth_if intf);
    // 发送一帧的辅助任务
    task send_frame(byte data_byte);
        @(posedge intf.clk);
        intf.rx_valid = 1;
        intf.rx_data  = data_byte;
        @(posedge intf.clk);
        intf.rx_data  = 8'hFF;   // 结束标志
        @(posedge intf.clk);
        intf.rx_valid = 0;
        intf.rx_data  = 8'h00;
        // 等待 DUT 输出产生 frame_ok 脉冲
        @(posedge intf.clk);
    endtask

    initial begin
        $display("\n========== Starting Test ==========");

        // 复位 DUT
        intf.rst_n = 0;
        repeat(10) @(posedge intf.clk);
        intf.rst_n = 1;
        repeat(2)  @(posedge intf.clk);

        // 发送覆盖所有组合的测试帧
        $display("Sending: IPv4, length 46");
        send_frame(8'h01);

        $display("Sending: ARP, length 46");
        send_frame(8'h02);

        $display("Sending: VLAN, length 46");
        send_frame(8'h03);

        $display("Sending: IPv4, length 1500");
        send_frame(8'h04);

        $display("Sending: ARP, length 1500");
        send_frame(8'h05);

        $display("Sending: VLAN, length 1500");
        send_frame(8'h06);

        // 等待所有采样完成
        repeat(10) @(posedge intf.clk);

        // 打印覆盖率报告
        $display("\n========== Coverage Report ==========");
        $display("Total coverage (global): %.2f%%", $get_coverage());
        $display("  cp_eth_type coverage: %.2f%%", top_tb.cov_inst.cg_inst.cp_eth_type.get_coverage());
        $display("  cp_frame_len coverage: %.2f%%", top_tb.cov_inst.cg_inst.cp_frame_len.get_coverage());
        // 注意:交叉覆盖的覆盖率已包含在 $get_coverage() 中,无法直接通过句柄获取

        $finish;
    end
endmodule

//================================================
// 顶层模块(包含时钟、DUT、覆盖率模块、测试模块)
//================================================
module top_tb;
    logic clk = 0;
    initial forever #5 clk = ~clk;   // 100MHz 时钟

    eth_if intf(clk);
    eth_mac_rx dut (
        .clk      (clk),
        .rst_n    (intf.rst_n),
        .rx_valid (intf.rx_valid),
        .rx_data  (intf.rx_data),
        .rx_ready (intf.rx_ready),
        .frame_ok (intf.frame_ok),
        .frame_len(intf.frame_len),
        .eth_type (intf.eth_type)
    );

    eth_coverage cov_inst (
        .clk      (clk),
        .rst_n    (intf.rst_n),
        .frame_ok (intf.frame_ok),
        .frame_len(intf.frame_len),
        .eth_type (intf.eth_type)
    );

    eth_test test(intf);
endmodule

运行命令(VCS)

vcs -sverilog top_tb.sv eth_mac_rx.sv -l comp.log
./simv -l sim.log
urg -dir simv.vdb -report urg_report
  • 仿真结果
    在这里插入图片描述

在这里插入图片描述


9 覆盖率驱动验证(CDV)最佳实践

9.1 标准 CDV 流程

  1. 验证计划先行:在编写任何代码前,明确所有功能点、边界条件和异常场景,并映射到覆盖点。
  2. 覆盖模型与 DUT 并行开发:在 DUT 开发的同时编写覆盖模型,确保覆盖模型与设计意图一致。
  3. 分层覆盖
    • 模块级:覆盖模块内部的所有状态和接口
    • 系统级:覆盖模块间交互和系统级场景
  4. 定期覆盖率分析:每周分析覆盖率报告,优先解决低覆盖率关键场景。
  5. 定向测试补充:对于随机测试难以覆盖的场景(如极端边界、异常时序),编写定向测试。
  6. 覆盖率签收
    • 所有关键覆盖点达到 100% 覆盖率
    • 剩余未覆盖点必须经过评审,确认是不可达场景并签署豁免

9.2 常见错误与避坑指南

错误 后果 解决方案
覆盖模型与验证计划脱节 覆盖率无法反映验证完备性 每个覆盖点必须对应验证计划中的一个功能点
过度追求 100% 覆盖率 浪费验证资源在无关场景 区分关键场景和非关键场景,设置合理目标覆盖率
滥用 ignore_bins 掩盖验证漏洞 所有 ignore_bins 必须加注释说明原因,并经过评审
交叉爆炸 仿真慢、内存高 使用带标签的交叉,减少交叉变量数量
采样时机错误 覆盖率数据不准确 在信号稳定后采样,复位期间使用 iff 过滤
未设置 option.at_least 单次偶然命中即视为覆盖 关键场景设置 option.at_least = 2 或更高

9.3 覆盖率报告解读要点

  1. 优先看覆盖组级覆盖率:确认整体验证进度
  2. 重点关注未覆盖的 bins:分析是随机概率低还是约束限制
  3. 检查交叉覆盖率:多变量交互场景是 bug 高发区
  4. 查看过渡覆盖率:确保状态机跳转和时序行为正确
  5. 分析实例级覆盖率:确认所有参数化实例、多通道配置都被充分覆盖

10 总结

功能覆盖率是 SystemVerilog 验证的核心技术,它将主观的验证计划转化为客观的量化指标,是验证完备性的唯一可靠依据。

关键要点回顾

  • 正确区分功能覆盖率与代码覆盖率
  • 掌握 covergroup 的三种采样方式,特别注意类内封装和事件触发
  • 参数化覆盖组必须使用 ref 传递采样变量
  • 灵活运用 binsignore_binsillegal_bins 控制覆盖粒度
  • 交叉覆盖率必须注意 bin 爆炸问题,优先使用带标签的交叉
  • 过渡覆盖率用于验证状态跳转与时序行为
  • 遵循 CDV 流程,定期分析并迭代测试

📢 关于文章:原创实战分享,转载需注明出处。

📚 参考文档IEEE Standard for SystemVerilog

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐