目录
1. 随机约束和分布
1.1 为什么需要随机?
- 芯片体积增大,复杂度日渐提高,在20年前定向测试已经无法满足验证的需求,而随机测试的比例逐渐提高。
- 定向测试能找到你认为可能存在的缺陷,而随机测试可以找到连你都没有想到的缺陷。
- 随机测试的环境要求比定向测试复杂,它需要激励、参考模型和在线比较。上百次的仿真不再需要人为参与,以此来提高验证效率。
- 随机测试相对于定向测试可以减少相当多的代码量,而产生的激励较定向测试也更多样。
- 如果说定向测试是”站着测”,那么随机测试就是“躺着测”、“跪着测”、“跑着测”,各种花式测,只有想不到,没有测不到。
- 如果随机没有约束,意味着什么?是绝对的自由吗?肯定不是,它是一匹脱缰的野马,没有拘束,产生有效激励的同时也产生了很多无效和非法的激励。
- 那么我们想要的随机自由是一种合法的随机,需要限定激励的合法范围。同时,伴随测试的进行,约束甚至应该“变形”,变得更趋于为测试的数值范围或者期待的数值范围。
- 随机的对象不只是一个数据,而是有联系的变量集。通常这些变量会被封装在一个数据类中,同时需要在类中声明数据之间的约束关系。因此约束之后要产生随机数据需要一个“求解器”,即在满足数据本身和数据之间约束关系时的随机数值解。
- 约束不但可以指定数据的取值范围,还可以指定各个数值的随机权重分布。
1.2 我们要随机什么?
- 器件配置:通过寄存器和系统信号
- 环境配置:随机化验证环境,例如合理的时钟和外部反馈信号
- 原始输入数据:例如MCDF数据包的长度、带宽,数据间的顺序
- 延时:握手信号之间的时序关系,例如valid和ready,req和ack之间的时序关系
- 协议异常:如果反馈信号给出异常,那么设计是否可以保持后续数据处理的稳定性呢?
1.3 声名随机变量的类
- 随机化是为了产生更多可能的驱动,因此在软件世界"class"侧的运用更多,所以我们倾向于将相关数据有机整理在一个类的同时,也用rand关键词来表明它们的随机属性。
- randc表示周期随机性,即所有可能的值都赋过值后随机值才可能重复。
- 随机属性需要配合SV预定义的类随机函数 std::randomize() 使用。即只有通过声明rand变量,并且在后期通过对象调用 randomize() 函数才可以随机化变量。
- 约束constraint也同随机变量一起在类中声明。
例子
// 定义一个名为Packet的类
class Packet;
// 定义随机变量
rand bit [31:0] src, dst, data[8]; // src、dst为32位宽的随机变量,data是一个包含8个元素的数组,每个元素都是32位宽的随机变量
randc bit [7:0] kind; // kind是一个8位宽的随机循环变量,意味着它的值会在随机化过程中遍历所有可能的值
// 约束src的值范围
constraint c {
src > 10; // src必须大于10
src < 15; // src必须小于15
}
endclass
// 初始块开始
initial begin
Packet p; // 声明一个Packet类型的变量p
p = new(); // 创建一个新的Packet对象并赋值给p
assert (p.randomize()) else // 尝试随机化Packet对象p
$fatal(0, "Packet::randomize failed"); // 如果随机化失败,则输出致命错误信息并终止仿真
transmit(p); // 调用transmit函数,传递参数p
end
rand和randc
class Example;
rand bit [7:0] a; // 普通随机变量a
randc bit [7:0] b; // 随机循环变量b
endclass
在这个例子中,a可以随机取值为0到255之间的任何数字,包括重复值。而b也会随机取值为0到255之间的值,但在所有256个可能值都被取过后,才会开始新的循环并再次从头开始取值。
1.4 什么是约束
- 约束表达式的求解是由sV的约束求解器(constraint solver)完成的。
- 求解器能够选择满足约束的值,这个值是有SV的PRNG(伪随机数发生器Pseudo random number generator)从一个初始值(seed)产生。只要改变种子的值,就可以改变CRT的行为。
- SV标准定义了表达式的含义以及产生的合法值,但没有规定求解器计算约束的准确顺序。这即是说,不同仿真器对于同一个约束类和种子值求解出的数值可能是不相同。
- 什么可以被约束?SV只能随机化2值数据类型,但位可以是2值或4值。这即是说,无法随机化出×值和Z值,也无法随机化字符串。
class data;
rand bit [2:0] month;
rand bit [4:0] day;
rand int year;
constraint c_data {
month inside {[1:12]};
day inside {[1:31]};
year inside {[2010:2030]};
}
endclass
Q:
下面哪些解是约束求解器合理的随机数值组合?
A. month = 2, day = 31, year = 2017
B. month = 1, day = 30, year = 2020
C. month = 10, day = 31, year = 2022
D. month = 7, day = 31, year = 2045
A:
C. month是3bit,能表示的范围是[0:7]
D. 年限不符
// 定义一个名为 Stim 的类,用于生成测试激励
class Stim;
// 定义一个常量 CONGEST_ADDR,表示拥塞测试中使用的特定地址
const bit [31:0] CONGEST_ADDR = 42;
// 定义一个枚举类型 stim_e,表示激励的种类(操作类型)
typedef enum {READ, WRITE, CONTROL} stim_e;
// 声明一个随机循环变量 kind,其类型为 stim_e。
// randc 表示在随机化时会遍历所有可能值,不重复直到所有值都被使用过一次。
randc stim_e kind; // enumerated var
// 声明三个 32 位的随机变量:
// len:数据包长度
// src:源地址
// dst:目标地址
rand bit [31:0] len, src, dst;
// 声明一个非随机变量 congestion_test,用于控制是否启用拥塞测试约束
bit congestion_test;
// 定义约束块 c_stim,对随机变量施加限制条件
constraint c_stim {
// 数据包长度必须大于 0 且小于 1000
len < 1000;
len > 0;
// 根据 congestion_test 的值应用不同的约束
if (congestion_test) {
// 如果是拥塞测试,则:
// 目标地址(dst)必须位于 CONGEST_ADDR ± 100 的范围内
dst inside {[CONGEST_ADDR-100 : CONGEST_ADDR+100]};
// 源地址(src)必须等于 CONGEST_ADDR
src == CONGEST_ADDR;
}
else {
// 如果不是拥塞测试,则:
// 源地址(src)只能是以下范围之一:
// - 等于 0
// - 在 2 到 10 之间(包括 2 和 10)
// - 在 100 到 107 之间(包括 100 和 107)
src inside {0, [2:10], [100:107]};
}
} // end of constraint block
endclass // end of class Stim
1.5 权重分布
- 关键词dist可以在约束中用来产生随机数值的权重分布,这样某些值的选取机会要比其他值更大些
- dist操作符带有一个值的列表以及相应的权重,中间用 : = 或 : / 分开。值或权重可以是常数或者变量,可以使用权重变量来随时改变值的概率分布,甚至可以把权重设为0,从而删除一个值
- 权重不用百分比表示,权重的和也不必是100
- : = 操作符表示值范围内的每一个值的权重是相同的,: / 操作符表示权重要平均分到值范围内的每一个值
rand int src, dst;
constraint c_dist {
src dist {0:=40, [1:3]:=60}; // 产生40个单位的0,1-3每个数产生60个,共产生220个
// src = 1, weight = 40/220
// src = 2, weight = 60/220
// src = 3, weight = 60/220
// src = 4, weight = 60/220
dst dist {0:/40, [1:3]:/60}; // 产生40个单位的0,1-3每个数总共产生60个,共产生100个数
// dst = 0, weight = 40/100
// dst = 1, weight = 20/100
// dst = 2, weight = 20/100
// dst = 3, weight = 20/100
}
1.6 集合成员和inside运算符
- inside表示变量应该属于某一个值的集合,除非还存在其他约束,否则随机变量在集合里取值的概率是相等的。集合里也可以使用变量
rand int c;
int lo, hi;
constraint c_range {
c inside {[lo:hi]}; // lo <= c && c <= hi
}
- 使用“$”指定最大值和最小值
rand bit [6:0] b; // 0~127
rand bit [5:0] e; // 0~63
constraint c_range {
b inside {[$:4], [20,$]}; // 0 <= b <= 4 || 20 <=b <= 127
e inside {[$:4], [20,$]}; // 0 <= e <= 4 || 20 <=e <= 127
}
1.7 条件约束
- 可以通过 -> 或者 if-else 来让一个约束表达式在特定时刻有效
class BusOp;
...
constraint c_io {
(io_space_mode) -> addr[31] == 1'b1;
}
endclass
class BusOp;
...
constraint c_len_rw {
if (op == READ)
len inside {[BYTE:LWRD]};
else
len == LWRD;
}
endclass
1.8 双向约束
- 约束是声明性代码,是并行的,所有的约束表达式同时有效(单个约束块里多个约束条件和多个约束块都是并行的)
- 子类继承父类后,子类的约束不能和父类冲突
1.9 软约束
在 SystemVerilog 中,soft 约束是一种特殊的约束类型,它允许约束求解器(constraint solver)优先考虑这些约束,但不会强制必须满足它们。如果不能满足 soft 约束,约束求解器会尝试找到一个尽可能接近满足所有 soft 约束的解决方案,而不是直接报错或失败。
关键点
- 优先级较低:与硬约束相比,
soft约束的优先级较低。硬约束是必须被满足的条件,而软约束则更像是“尽量满足”的建议。 - 覆盖性:可以在运行时通过其他约束覆盖
soft约束。例如,在某些情况下,你可能希望临时忽略某些默认值,这时可以使用更具体的约束来覆盖soft约束。 - 灵活性:增加了验证环境的灵活性,使得生成随机数据更加灵活和适应不同的测试需求。
constraint cstr{
soft ch_id == -1;
soft pkt_id == -1;
soft data_size == -1;
soft data_nidles == -1;
soft pkt_nidles == -1;
soft ntrans == 10;
}
在这个约束块中,所有的约束都是 soft 类型,这意味着:
- 如果没有其他约束或者外部条件改变这些变量的值,那么这些变量将保持为 -1 或者 10(对于 ntrans),这是默认的行为。
- 但是,如果有更高优先级的约束(比如在调用 randomize 方法时传递了特定的约束条件),这些 soft 约束可以被覆盖。
assert(req.randomize with {
local::ch_id >= 0 -> ch_id == local::ch_id;
local::pkt_id >= 0 -> pkt_id == local::pkt_id;
local::data_nidles >= 0 -> data_nidles == local::data_nidles;
local::pkt_nidles >= 0 -> pkt_nidles == local::pkt_nidles;
local::data_size >0 -> data.size() == local::data_size;
})
这里,如果 local::ch_id、local::pkt_id 等变量被设置为非负值,则这些具体的值将会覆盖相应的 soft 约束,从而生成符合这些条件的事务对象。
2. 约束快控制
2.1 打开或关闭约束
- 一个类可以包含多个约束块。可以把不同约束块用于不同测试。
- 一般情况下,各个约束块之间的约束内容是互相协调不违背的,因此通过随机函数产生随机数时可以找到合适的解。
- 对于其它情况,例如根据不同需要,来选择使能哪些约束块,禁止哪些约束块的要求,可以使用内建的constraint_mode()函数打开或者关闭约束。
2.1.1 constraint_mode()
class Packet;
rand int length;
constraint c_short {length inside {[1:32]};}
constraint c_long {length inside {[1000:1023]};}
endclass
module test;
Packet P;
initial begin
p = new();
p.c_short.constraint_mode(0);//关闭c_short约束
assert (p.randomize());
transmit(p);
p.constraint_mode(0);//关闭类中所有约束
p.c_short.constraint_mode(1);//打开c_short约束
transmit(p);
end
endmodule
- constraint_mode(0):关闭约束
- constraint_mode(1):打开约束
- 非激活状态下的约束在调用randomize()函数时将失效
Q:
如果在随机化对象时,不禁止其中的任何一个约束块(即
c_short和c_long都生效),那么调用randomize()函数后,下列哪一个数值可能是p.length的值?选项:
- A. 0
- B. 1
- C. 1000
- D. 报错
- 若PRNG伪随机数发生器找不到合适的解,即约束冲突,里面所有的变量默认都是0,此次随机失败
- 上述代码,不关闭两个约束,在tb创建对象后,然后打印p.length,编译不会报错,仿真结果为0;不关闭两个约束,在tb创建对象后,调用randomize()类随机函数,编译不会报错,因为randomize()返回值为0,仿真时提示有错 A)
// 定义一个名为 Packet 的类,用于表示数据包
class Packet;
// 声明一个随机整型变量 length,表示数据包长度
rand int length;
// 定义约束块 c_short:限制 length 的取值范围为 [1:32]
constraint c_short {
length inside {[1:32]};
}
// 定义约束块 c_long:限制 length 的取值范围为 [1000:1023]
constraint c_long {
length inside {[1000:1023]};
}
endclass : Packet
// 定义测试模块 test
module test;
// 声明一个指向 Packet 类的句柄 p
Packet p;
// 初始过程块,在仿真开始时执行一次
initial begin
// 实例化一个新的 Packet 对象,并将其地址赋给句柄 p
p = new();
// 关闭约束块 c_short,即暂时不参与随机化
p.c_short.constraint_mode(0); // 关闭短包约束
// 对对象进行随机化,此时只受 c_long 约束影响
assert (p.randomize())
else $error("Randomization failed!");
// 调用 transmit 函数发送当前生成的数据包
transmit(p);
// 关闭整个 Packet 类的所有约束
p.constraint_mode(0); // 关闭所有约束
// 启用之前被关闭的约束块 c_short
p.c_short.constraint_mode(1); // 打开短包约束
// 再次进行随机化,此时只有 c_short 约束生效
assert (p.randomize())
else $error("Randomization failed!");
// 再次调用 transmit 发送新生成的数据包
transmit(p);
end // end of initial block
endmodule : test
| 第一次随机化 | p.c_short.constraint_mode(0)<br>p.randomize() | 只有 c_long 生效 → length ∈ [1000, 1023] |
| 第二次随机化 | p.constraint_mode(0)<br>p.c_short.constraint_mode(1)<br>p.randomize() | 只有 c_short 生效 → length ∈ [1, 32] |
2.1.2 rand_mode()
- 用来打开或关闭随机变量
- 非激活状态下,给表示该变量不被声明为rand或者randc,不能通过randomize()函数随机化赋值
class Packet();
rand int src, dst;
endclass
int r;
Packet packet_a = new;
packet_a.rand_mode(0); //将Packet类中的两个随机变量设置为非激活态,即src与dst变为非随机变量,不能通过randomize函数赋值
packet_a.src.rand_mode(1); //将Packet类中的src设置为激活态,可以通过randomize函数进行赋值
r=packet_a.dst.rand_mode(); //读取dst变量的激活状态
2.2 内嵌约束
- SV允许使用randomize()with来增加额外的约束,这和在类里增加约束是等效的,但同时要注意类内部约束和外部约束之间不要冲突,如果出现互相违背的情况,那么随机数值求解会失败
class Transaction;
rand bit [31: 0] addr, data;
constraint c1 {
soft addr inside {[0:100], [1000:2000]};
}
endclass
module test;
Transaction t;
initial begin
t = new();
// addr is 50-100, 1000-1500, data < 10
assert(t.randomize() with {addr >= 50; addr <= 1500; data <10;});
driveBus(t);
// force addr to a specific value, data > 10
assert(t.randomize() with {addr == 2000; data <10;});
driveBus(t);
end
endmodule
Q:
如果assert(t.randomize() with {addr inside [200:300]; data inside [10:20];});,那么哪个值是合理的随机数值?
A. 报错,10
B. 报错,报错
C. 200,10
D. 0,10
A:
选C。
对于任何一个约束冲突、不满足,此次随机的addr、data都没办法做求解,全部失败。但是添加了soft关键词,软约束,当外部的约束或其他的约束和原本的约束叠加在一起的时候有冲突,那么soft处的优先级更低,其他地方的优先,没报错


&spm=1001.2101.3001.5002&articleId=148058055&d=1&t=3&u=dd432436bb7845669034d2233cd34551)
1万+

被折叠的 条评论
为什么被折叠?



