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 值。string 和 real 类型也无法随机化。
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
过度约束:约束过紧会减少有效随机值的多样性
性能问题:复杂的约束(特别是大量 foreach 和 unique)可能降低求解器性能
二、功能覆盖率(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、interface 或 class 中定义。
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_bins 与 illegal_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 交叉覆盖中的 binsof 与 intersect
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:既不过粗(遗漏细节)也不过细(性能开销大) -
迭代优化:根据覆盖率报告持续调整约束