SV学习(8)——随机约束和分布、约束块控制
- 1. 随即约束和分布
- 1.1. 为什么需要随机?
- 1.2. 要随机做什么?
- 1.3. 声明随机变量的类
- 1.4. 什么是约束
- 1.5. 权重分布
- 1.6. 集合成员和inside运算符
- 1.7. 条件约束
- 1.8. 双向约束
- 2. 约束块控制
- 2.1. 打开或关闭约束
- 2.1.1. constraint_mode()
- 2.1.2. rand_mode()
- 2.2. 内嵌约束
- 3. 补充
- 内嵌约束(指向模糊)
1. 随即约束和分布
1.1. 为什么需要随机?
- 芯片体积增大,复杂度日渐提高,传统定向测试已经无法满足验证的需求,而随机测试的比例逐渐提高;
- 定向测试能找到你认为可能存在缺陷,而随机测试可以找到连你都没想想到的缺陷;
- 随机测试相对于定向测试可以减少相当多的代码量,而产生的激励较定向测试也更加多样;
1.2. 要随机做什么?
- 器件配置:通过寄存器和系统信号
- 换镜配置:随机化验证环境,例如合理的时钟和外部反馈信号
- 原始输入数据:例如MCDF数据包的长度、带宽、数据间的顺序
- 延时:握手信号之间的时序关系,例如valid和ready,req和ack之间的时序关系
- 协议异常:如果反馈信号给出异常,那么设计是否可以保持后续数据处理的稳定性呢
1.3. 声明随机变量的类
- 随机变量只能在class中声明,rand和randc都只能修饰类的成员变量
- rand,表示每次随机化这个类时,这些变量都会赋一个值,每次随机的概率都是一样的
- randc,周期随机性,即所有可能出现的值都赋过值后随机值才可能重复
- 随机属性需要配合SV预定义的类随机函数std::randomize()使用。即只有通过声明rand变量,并且在后期通过对象调用randomize()函数才可以随机化变量
- 约束constraint也同随机变量一起在类中声明
class Packet;
// the random variables
rand bit [31: 0] src, dst, data[8];
randc bit [ 7: 0] kind;
// limit the value for src
constraint c {src > 10;
src < 15;}
endclass
Packet p;
initial begin
p = new(); // creat a packet
assert (p.randomize()) else
$fatal(0, "Packet::randomize failed");
transmit(p);
end
1.4. 什么是约束
- 约束表达式的求解是由SV的约束求解器(constraint solver)完成的
- 求解器能够选择满足约束的值,这个值是由SV的PRNG伪随机数发生器从一个初始值seed产生。只要改变种子的值,就可以改变CTR的行为
- SV标准定义了表达式的含义以及产生的合法值,但没有规定求解器计算约束的准确顺序。也就是说,不同仿真器对于同一个约束类和种子值求解出的数值可能是不相同的
- 什么可以被约束?SV只能随机化2值数据类型,但位可以是2值或4值。也就是说,无法随机化出X值和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
【An】
C. month是3bit,能表示的范围是[0:7]
D. 年限不符
class Stim;
const bit [31: 0] CONGEST_ADDR = 42;
typedef enum {READ, WRITE, CONTROL} stim_e;
randc stim_e kind; // enumerated var
rand bit [31: 0] len, src, dst;
bit congestion_test;
constraint c_stim {
len < 1000;
len > 0;
if (congestion_test) {
dst inside {[CONGEST_ADDR-100 : CONGEST_ADDR+100]};
src == CONGEST_ADDR;
}
else
src inside {0, [2:10], [100:107]};
}
endclass
const是为了避免变量被赋值
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
}
使用inside约束从重复的值序列里选出随机值,并打印出各种值的分布,从中可以看出咯咯直的选取的概率:
class Weighted;
rand int val;
int array[] = '{1, 1, 2, 3, 5, 8, 8, 8, 8, 8};
constraint c {
val inside array;
}
endclass
module test;
Weighted w;
initial begin
int count[9], maxx[$];
w = new();
repeat(2000) begin
assert(w.randomize());
count[w.val]++;
end
maxx = count.max();
foreach(count[i])
if(count[i]) begin
$write("count[%0d] = %5d", i, count[i]);
$display;
end
end
endmodule
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. 双向约束
- 约束是声明性代码,是并行的,所有的约束表达式同时有效(单个约束块里多个约束条件和多个约束块都是并行的)
- 子类继承父类后,子类的约束不能和父类冲突
2. 约束块控制
3.22补充:
前面说过随机变量需要容器装起来再使用,也就是类,结构体里面也能声明rand变量,但是不能放约束
9.2再补充一下
interface中的变量也可以赋随机值:不能用rand关键字指定,否则编译报错;可以在interface中这么用:
2.1. 打开或关闭约束
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()函数时将失效
- 若PRNG伪随机数发生器找不到合适的解,即约束冲突,里面所有的变量默认都是0,此次随机失败
- 上述代码,不关闭两个约束,在tb创建对象后,然后打印p.length,编译不会报错,仿真结果为0;不关闭两个约束,在tb创建对象后,调用randomize()类随机函数,编译不会报错,因为randomize()返回值为0,仿真时提示有错
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
【An】
选C。
对于任何一个约束冲突、不满足,此次随机的addr、data都没办法做求解,全部失败。但是添加了soft关键词,软约束,当外部的约束或其他的约束和原本的约束叠加在一起的时候有冲突,那么soft处的优先级更低,其他地方的优先,没报错
3. 补充
内嵌约束(指向模糊)
class C1;
rand integer x;
endclass
class C2;
integer x;
integer y;
task diot (C1 f, integer x, integer z);
int result;
result = f.randomize() with {x < y + z;}
endtask
endclass
result = f.randomize() with {x < y + z;}
因为内嵌约束是关于类C1的句柄f所指的对象,x对应的是C1里的x。
使用local::x可以索引到函数传参的x。
使用local::this.x可以索引到C2的x。