SystemVerilog 随机约束与功能覆盖率语法详细介绍

SystemVerilog 随机约束与功能覆盖率语法详细介绍

相关标准:IEEE 1800 SystemVerilog LRM(Language Reference Manual)

一、随机约束(Randomization & Constraints)

1.1 随机化基础

1.1.1 rand 与 randc

SystemVerilog 中,变量需要通过关键字声明为可随机化类型:

关键字 说明
rand 标准随机变量,每次随机化独立,值在取值范围内均匀分布
randc 循环随机变量,会遍历取值范围内的所有值,同一周期内不重复
systemverilog 复制代码
class myPacket;
    rand bit [1:0] mode;   // 普通随机:每次 0~3 中独立取值
    randc bit [2:0] key;   // 循环随机:0~7 循环遍历,不重复
endclass

randc 的典型应用场景包括生成不重复的 ID 或地址序列。对于 8 位 randc 变量,需要调用 randomize() 256 次才能遍历所有可能值。

1.1.2 randomize() 方法

通过 randomize() 函数触发随机化:

systemverilog 复制代码
class Packet;
    rand bit [31:0] addr;
    rand bit [31:0] data;
endclass

Packet pkt = new();
if (pkt.randomize() == 1) begin
    $display("随机化成功");
end else begin
    $display("随机化失败");
end

返回 1:随机化成功

返回 0:随机化失败(如约束冲突)

推荐使用 assert() 确保随机化成功:

systemverilog 复制代码
assert(pkt.randomize());

注意:SystemVerilog 只能随机化 2 值数据类型(bit、int、enum 等),无法直接随机出 X/Z 值。stringreal 类型也无法随机化。

1.2 约束块(Constraint Block

约束块是类的成员,使用 constraint 关键字声明,约束块名称在类内必须唯一。

systemverilog 复制代码
class bus;
    rand bit [31:0] addr, data;
    constraint addr_c { addr[1:0] == 2'b0; }  // 地址低2位恒为0
endclass

上面的约束表示:无论 addr 取什么值,其低 2 位必须为 0。

1.2.1 简单表达式约束

约束块中只能使用关系操作符(<、<=、==、>=、>),每个表达式只能使用一个关系操作符:

systemverilog 复制代码
class bus;
    rand bit [31:0] data;
    constraint data_c {
        data > 20;
        data < 100;      // 不能写成 20 < data < 100
    }
endclass

使用 == 可约束变量取固定值:

systemverilog 复制代码
constraint data_c {
    data == 32'h5A5A_5A5A;  // data 恒为 0x5A5A5A5A
}

1.3 权重分布约束:dist

dist 操作符用于控制不同值被随机到的概率权重。

1.3.1 :=权重语法

:= 表示每个值独立分配权重,总权重为各值权重之和:

systemverilog 复制代码
constraint data_c {
    data dist {
        0   := 10,        // 值 0 的权重为 10
        [1:3] := 80       // 值 1、2、3 各有权重 80
    };
}

// 总权重 = 10 + 80*3 = 250

// P(data=0) = 10/250, P(data=1) = 80/250, ...

1.3.2 :/ 权重语法

:/ 表示权重按范围分配,范围内的每个值平分该权重:

systemverilog 复制代码
constraint data_c {
    data dist {
        0   :/ 10,        // 值 0 的权重为 10
        [1:3] :/ 90       // 权重 90 在 1、2、3 之间平分
    };
}

// 总权重 = 100

// P(data=0) = 10/100, P(data=1/2/3) = 30/100 各

1.4 集合约束:inside

inside 运算符用于约束变量在指定集合中取值,集合中每个值被选中的概率相等:

systemverilog 复制代码
class date;
    rand bit [2:0] month;
    rand bit [4:0] day;
    rand int year;
    
    constraint c_date {
        month inside {[1:12]};           // 1~12
        day inside {[1:31]};             // 1~31
        year inside {[2010:2030]};       // 2010~2030
    }
endclass

使用 $ 表示边界值:

systemverilog 复制代码
rand bit [6:0] b;
constraint c_range {
    b inside { [$:4], [20:$] };  // 0<=b<=4 或 20<=b<=127
}

1.5 条件约束

2.5.1 蕴含操作符 ->

-> 表示条件满足时必须满足后续约束:

systemverilog 复制代码
class transaction;
    rand bit a;
    rand bit [1:0] b;
    constraint cons {
        (a == 0) -> (b == 0);  // 若 a=0,则 b 必须为 0
    }
endclass
1.5.2 if-else 约束
systemverilog 复制代码
constraint cons {
    if (a == 0)
        b == 0;
    else
        b inside {[1:3]};
}

1.6 求解顺序:solve...before

solve...before 用于控制约束求解器的变量求解顺序:

systemverilog 复制代码
class packet;
    rand bit a;
    rand bit [1:0] b;
    constraint cons {
        (a == 0) -> (b == 0);
    }
    constraint solve_order {
        solve a before b;  // 先求解 a,再求解 b
    }
endclass

solve...before 仅影响随机值的概率分布,不影响约束的最终解集。它主要用于:

  • 优化求解器的性能

  • 打破对称性以获得更均匀的分布

注意:在 randc 修饰的变量上应避免使用 solve...before

1.7 软约束(Soft Constraint)

软约束使用 soft 关键字,当与其他约束冲突时会被自动忽略:

systemverilog 复制代码
class A;
    rand bit [31:0] addr;
    constraint default_addr {
        soft addr inside {[0:100]};  // 默认约束,可被覆盖
    }
endclass

// 使用时可通过 inline constraint 覆盖软约束
A a = new();
a.randomize() with { addr == 200; };  // 软约束被忽略,addr=200

软约束常用于指定默认值和默认分布。

1.8 内联约束(Inline Constraint

使用 with 关键字在调用 randomize() 时附加额外约束:

systemverilog 复制代码
class Packet;
    rand bit [31:0] addr;
    rand bit [31:0] data;
    constraint addr_range { addr inside {[0:255]}; }
endclass

Packet pkt = new();
// 在原有约束基础上,额外约束 data 为特定值
pkt.randomize() with { data == 32'hDEAD_BEEF; };

如果内联约束与原有约束冲突,随机化将失败。

1.9 约束控制

1.9.1 constraint_mode()

启用或禁用约束块:

systemverilog 复制代码
class Packet;
    rand bit [31:0] addr;
    constraint addr_c { addr < 100; }
    constraint data_c { addr > 50; }
endclass

Packet pkt = new();
pkt.addr_c.constraint_mode(0);  // 禁用 addr_c 约束
pkt.randomize();                 // 只有 data_c 生效
1.9.2 rand_mode()

启用或禁用变量的随机化:

systemverilog 复制代码
pkt.addr.rand_mode(0);  // addr 不再随机化,保持当前值
pkt.randomize();        // 其他 rand 变量仍随机化

1.10 数组约束

1.10.1 动态数组大小约束
systemverilog 复制代码
class Packet;
    rand bit [63:0] payload [$];
    constraint payload_size {
        payload.size() inside {[1:64]};  // 数组大小 1~64
    }
endclass
1.10.2 foreach 遍历约束
systemverilog 复制代码
class Packet;
    rand bit [7:0] data [10];
    constraint foreach (data[i]) {
        data[i] inside {[0:255]};
        if (i > 0) data[i] != data[i-1];  // 相邻元素不相等
    }
endclass
1.10.3 生成唯一元素数组
systemverilog 复制代码
class UniqueArray;
    rand bit [7:0] arr [10];
    constraint unique_c {
        foreach (arr[i])
            foreach (arr[j])
                if (i != j) arr[i] != arr[j];  // 所有元素互不相同
    }
endclass

1.11 约束中的常见问题

约束冲突:多个约束相互矛盾会导致 randomize() 返回 0

过度约束:约束过紧会减少有效随机值的多样性

性能问题:复杂的约束(特别是大量 foreachunique)可能降低求解器性能

二、功能覆盖率(Functional Coverage)

功能覆盖率用于衡量验证过程中哪些功能场景已被测试到。它不同于代码覆盖率(检查代码是否被执行)和断言覆盖率(检查断言是否被触发)。

2.1 covergroup 基础

covergroup 是功能覆盖率的容器,可以包含一个或多个 coverpoint。它类似于类,定义后可多次实例化。

2.1.1 定义与例化
systemverilog 复制代码
// covergroup 定义
covergroup my_cg @(posedge clk iff reset_n);
    // coverpoint 定义
endgroup

// 例化方式一

my_cg cg1 = new();

// 例化方式二(推荐,支持多次例化)

my_cg cg = new();

covergroup 可以在 module、interfaceclass 中定义。

2.1.2 采样方式

方式一:事件触发采样

covergroup 定义时指定采样事件:

systemverilog 复制代码
covergroup cg @(posedge clk);
    // 每个时钟上升沿自动采样
endgroup

方式二:显式调用 sample()

systemverilog 复制代码
covergroup cg;
    // 定义 coverpoint
endgroup

cg cg_inst = new();
// 在需要时手动采样
cg_inst.sample();
2.1.3 带参数的 covergroup
systemverilog 复制代码
// 简单参数
covergroup CoverPort(int mid);
    coverpoint port {
        bins lo = {[0:mid-1]};
        bins hi = {[mid:$]};
    }
endgroup

// 引用传递(实时检测信号变化)
bit [2:0] port_a;
covergroup CoverPort(ref bit [2:0] port, input int mid);
    coverpoint port {
        bins lo = {[0:mid-1]};
        bins hi = {[mid:$]};
    }
endgroup

CoverPort cpa = new(port_a, 4);  // 传递 port_a 的引用

2.2 coverpoint(覆盖点)

coverpoint 用于定义对单个信号或表达式的覆盖率。

2.2.1 自动创建仓(bins

如果不显式定义 bins,SystemVerilog 会自动为每个值创建一个仓:

systemverilog 复制代码
covergroup cg;
    coverpoint data;  // 自动为 data 的每个值创建 bin
endgroup

对于 n 位的整数变量,会自动创建 2^n 个仓,但最多 64 个;超过 64 时每个仓覆盖 2^n/64 个值。

2.2.2 显式定义 bins
systemverilog 复制代码
bit [3:0] data;
covergroup cg @(posedge clk);
    data_cp: coverpoint data {
        bins zero      = {0};           // 单个值
        bins low       = {[1:7]};       // 范围 1~7
        bins high      = {[8:15]};      // 范围 8~15
        bins even      = {0,2,4,6,8,10,12,14};  // 枚举列表
        bins others    = default;       // 未覆盖的值
    }
endgroup
2.2.3 ignore_binsillegal_bins
systemverilog 复制代码
coverpoint data {
    bins valid  = {[0:10]};
    ignore_bins unused = {[11:13]};   // 忽略这些值,不计入覆盖率
    illegal_bins error = {14, 15};    // 这些值出现时触发错误
}

ignore_bins:指定需要忽略的仓,不计入总覆盖率

illegal_bins:指定非法仓,采样到时会触发运行时错误

2.2.4 带条件的 coverpoint(iff)
systemverilog 复制代码
coverpoint data iff (enable == 1);  // 仅当 enable=1 时采样

iff 条件仅控制计数器的使能,不影响 bin 的创建。

2.3 cross(交叉覆盖率)

cross 用于定义多个 coverpoint 之间的组合覆盖率。

systemverilog 复制代码
covergroup cg @(posedge clk);
    cmd_cp: coverpoint cmd { bins read = {0}; bins write = {1}; }
    size_cp: coverpoint size { bins small = {[0:7]}; bins large = {[8:15]}; }
    
    // 交叉覆盖:命令与大小的所有组合
    cross cmd_cp, size_cp;
endgroup
2.3.1 交叉覆盖中的 binsofintersect
systemverilog 复制代码
cross a, b {
    // 忽略 a=0 且 b=0 的组合
    ignore_bins a0b0 = binsof(a) intersect {0} && binsof(b) intersect {0};
    
    // 非法组合
    illegal_bins err = binsof(a) intersect {3} && binsof(b) intersect {3};
}

binsof() 用于选择特定覆盖点的 bin,intersect 用于指定值范围。

2.4 covergroup 内建方法

方法 说明
sample() 手动触发采样
get_coverage() 获取同一类型所有覆盖组的覆盖率
get_inst_coverage() 获取当前实例的覆盖率
start() 启动覆盖率收集
stop() 停止覆盖率收集
使用示例:
systemverilog 复制代码
cg cg_inst = new();
cg_inst.sample();
real cov = cg_inst.get_inst_coverage();
$display("当前覆盖率: %0.2f%%", cov * 100);

系统函数 $get_coverage() 可获取整个测试平台的功能覆盖率。

2.5 covergroup 选项(options

covergroup 提供多种配置选项:

systemverilog 复制代码
covergroup cg;
    option.per_instance = 1;      // 每个实例独立计算覆盖率
    option.goal = 100;            // 覆盖率目标(%)
    option.weight = 1;            // 该覆盖组的权重
    option.comment = "覆盖组说明";
endgroup

常用选项:

per_instance:设为 1 时每个实例独立统计

goal:覆盖率目标百分比

weight:在总覆盖率中的权重

auto_bin_max:自动创建的最大 bin 数量

三、随机约束与功能覆盖率的协同

约束随机验证(CRV)的完整流程是:

  • 定义随机变量与约束:描述激励的合法范围

  • 运行随机测试:生成满足约束的激励

  • 收集功能覆盖率:度量哪些场景已被覆盖

  • 分析覆盖率缺口:找出未覆盖的功能点

  • 调整约束或增加测试:针对缺口补充测试

systemverilog 复制代码
// 覆盖率驱动的约束调整示例
class Test;
    rand cmd_t cmd;
    rand size_t size;
    
    constraint default_c {
        cmd inside {READ, WRITE};
        size inside {[1:64]};
    }
    
    // 可根据覆盖率反馈动态调整约束
    function void adjust_constraints(real coverage);
        if (coverage < 50) begin
            // 扩大随机范围以探索更多场景
        end
    endfunction
endclass

四、总结

随机约束核心要点:

特性 关键语法 用途
随机变量 rand / randc 声明可随机化的变量
约束块 constraint 定义变量间的限制关系
权重分布 dist := / dist :/ 控制不同值的出现概率
集合约束 inside 限定取值在指定集合内
条件约束 -> / if-else 表达条件依赖关系
求解顺序 solve...before 控制求解器变量顺
软约束 soft 可被覆盖的默认约束

功能覆盖率核心要点:

特性 关键语法 用途
覆盖组 covergroup 功能覆盖率的容器
覆盖点 coverpoint 单个信号/表达式的覆盖
bins 值的分组与统计
忽略/非法仓 ignore_bins / illegal_bins 控制哪些值计入覆盖
交叉覆盖 cross 多个覆盖点的组合覆盖
采样 sample() / @(event) 触发覆盖率收集
最佳实践建议:
  • 约束应声明式而非过程式:描述"是什么"而非"如何做"

  • 避免过度约束:保留足够的随机空间以发现边界情况

  • 使用 assert(randomize()) 确保随机化成功

  • 合理设计 coverage bin:既不过粗(遗漏细节)也不过细(性能开销大)

  • 迭代优化:根据覆盖率报告持续调整约束