随机约束和分布
- 定向测试能找到可能存在的缺陷,而随机测试可以找到没有想到的缺陷。随机测试环境比定向测试复杂,需要激励、参考模型和在线比较,验证效率更高。减少测试用例的代码,环境代码基本没有减少。
- 没有约束,产生有效激励的同时也产生了很多无效和非法的激励。那么我们想要的随机自由是一种合法的随机,需要限定激励的合法范围。同时,伴随测试的进行,约束甚至应该"变形”,变得更趋子为测试的数值范围或者期待的数值范围。约束不但可以指定数据的取值范围,还可以指定各个数值的随机权重分布。
- 随机的对象不只是一个数据,而是有联系的变量集。通常这些变量会被封装在一个数据类中,同时需要在类中声明数据之间的约束关系。因此约束之后要产生随机数据需要一个“求解器”,即在满足数据本身和数据之间约束关系时的随机数值解。随机发生在仿真的时候。
- 随机内容:
- 器件配置:通过寄存器和系统信号
- 环境配置:随机化验证环境,例如合理的时钟和外部反馈信号
- 原始输入数据:例如MCDF数据包的长度、带宽,数据间的顺序
- 延时:握手信号之间的时序关系,例如valid和ready,req和ack之间的时序关系
- 协议异常:如果反馈信号给出异常,那么设计是否可以保持后续数据处理的稳定性呢?
rand、randc表明随机属性,randc表示周期随机性,即所有可能的值都赋过值后随机值才可能重复。rand、randc用于类的成员变量,方法里的局部变量不行。只有在通过声明rand变量,并且在后期通过对象调用randomize()函数<此为预定义函数>才可以随机化变量。约束constraint可同随机变量一起在类中声明。
class packet;
……
endclass
packet p;
initial begin
p=new();
p.randomiz(); //句柄调用随机函数
end
class date;
rand bit[2:0] month; //由于位宽的限定,month数值不大于7
rand bit[4:0] day;
rand int year;
constraint c_data{ //约束会有命名,此处就是c_data
month inside {[1:12]}; //注意虽然此处约束为1到12,但还要注意变量month本身的范围
day inside {[1:31]}; //inside表示变量属于集合,除非存在其他约束,否则随机变量在其
year inside {[2010,2030]}; 取值的概率是相等的,集合可以用变量
}
endclass
约束
- 约束表达式的求解是由SV的约束求解器(constraint slover)完成
- 求解器能够选择满足约束的值,该值由sv的PRNG(伪随机数发生器)从一个初始值(seed)产生。只要改变种子的值,就可以改变CRT的行为。
- sv没有规定求解器计算约束的准确顺序,即不同仿真器对于同一约束类和种子值求解出的数值可能不同
- sv只能随机化2值数据类型,但位可以是2值或者4值,无法随机化出x值或者z值,也无法随机化字符串。(rand建议使用bit)
权重分布
- 利用dist在约束中产生随机数值的权重分布
- dist操作符带有一个值的列表以及相应的权重,中间用:=(每一个值的权重是相同的)或者:/(权重要平均分到值范围内的每一个值)分开。值或权重可以是常数或者变量
- 权重不用百分比,和也不必是100
rand int src,dsa;
constraint c_dist{
src dist {0:=40,[1:3]:=60}; //0占40,1,2,3分别占60,共220
dsa dist {0:/40,[1:3]:/60}; //0占40,1,2,3分别占20,共100
}
rand bit [6:0] b; //0<= b <= 127
constraint c_range {
b inside {[$:4],[20:$];} //0<=b<=4||20<=b<=127 "$"指定最大值和最小值 b[$]表示最后一个元素
}
条件约束:可以通过->或者if-else来让约束表达式在特定时刻有效
双向约束:约束块是声明性代码,是并行的,所有约束表达式同时有效;同时约束是双向的,会同时计算所有的随机变量的约束。
constraint c_io {
(io_space_mode) ->
addr[31] == 1'b1;
}
constraint c_len_rw{
if(op == READ)
len inside {[BYTE:LWRD]}; //除非还存在其他约束,否则随机变量在集合里取值的概率是相等的
else
len == LWRD;
}
用$表示最大值或最小值
a inside {[$:4],[20:$]};
约束块的控制
一般各个约束块协调不违背。随机不成功,默认都是0。
打开或关闭约束:可以通过内建的constraint_mode()函数打开或关闭约束,通过条件语句也可以控制需要的约束块
class packet;
rand int length;
constraint c_short {length inside {[1:32]};}
constraint c_long {length inside {[1000:1023]};}
endclass
//在类外面对约束进行控制
packet p;
initial begin
p=new();
p.c_short.constrain_mode(0); //disable the short constraint
p.constrain_mode(0); //disable all constraint
p.c_long.constrain_mode(1); //enable the long constraint
assert(p.randmize());
transmit(p);
end
//若不禁止约束块,调用函数randomize(),p.length为多少
randmize()有返回值,为0 仿真报告时有warning
内嵌约束块:通过使能和禁止约束的代码会增加测试复杂性,sv允许使用randomize() with增加额外的约束。类的内部约束和外部约束之间应该是协调的,若出现互相违背,那么随机数值求解会失败。
class Transaction;
rand bit [31:0] addr,data;
constraint c1 {
soft addr inside {[0:100],[1000:2000]}; //此处为软约束,优先级较低,当存在冲突时可以使用
soft
}
endclass
Transaction t;
initial begin
t=new();
assert(t.randomize() with
{addr >= 50;addr <= 1500;data < 10;})
assert(t.randomize() with
{addr inside [200:300];data inside [10:20];}) //200,10
end
随机函数
- sv有两个预定义的void类型函数pre_randomize()和post_randomize()函数。可以在类中定义这两个函数,分别定义随机化前后的行为
- 如果定义了这两个函数,对象在执行randomize()之前或者之后会分别执行这两个函数。所以这两个函数可以看作randomize()函数的回调函数(callback function)
系统随机函数
- $random()平均分布,返回32位有符号随机函数
- $urandom()平均分布,返回32位无符号数随机数
- $urandom_range()在指定范围内的平均分布
- 在调用randomize()时可以传递变量的一个子集,这样只会随机化类里的几个变量。
- 只有参数列表里的变量才会被随机化,其他变量会被当作状态变量当而不会做被随机化。
- 所有的约束仍然保持有效。
- 初学者需要注意该种应用针对的是类里所有被指定或者没有被指定rand的变量都可以作为randomize()的参数而被随机化。
class Rising;
byte low;
rand byte med,hi;
constraint up {
low<med;med<hi;
}
endclass
initial begin
Rising r;
r=new(); low med high
r.randomize(); //随机化med,hi,不随机low 0 2 5
r.randomize(med); //随机化med 随机化单个变量 0 3 5
r.randomize(low); //随机化low 1 3 5
end 注意每次randomize,前面的数值不会变
若只调用r.randomize(low);low、med、hi结果会是报错,0,0
因为开始low、med、hi开始默认是0;med和hi不会参与到randomize,保持为0,而约束constraint
存在违背问题(med和hi为0,约束条件不满足),randomize失败,所有开始randomize()
数组约束
class dyn_size;
rand logic[31:0] d[];
constraint d_size {d.size() inside {[1:10]};} //多数情况下,数组大小应该给定范围
防止生成过大体积数组或空数组,还可用
sum(),product(),and(),or(),xor()等方法
endclass //sv中foreach也可对数组每一个元素进行约束
class goog_sum5;
rand unit len[];
constraint c_len {
foreach (len[i]) len[i] inside {[1:255];}; //foreach更多是方便对单个元素进行操作
len.sum() < 1024;
len.sum() inside {[1:8]};
}
产生唯一元素值的数组
class Uniqueslow;
randc bit[7:0]ua[64];
constraint c {
foreach(ua[i])
foreach(ua[j])
if(i!=j)
ua[i]!=ua[j];
}
endclass
//使用randc变量辅助
class rand8;
randc bit [7:0] val;
endclass
class littleuniquearry;
bit[7:0] ua [64]; //ua不是rand
function void pre_randomize();
randc8 rc8;
rc8=new();
foreach(ua[i])begin
assert(rc8.randmozie());
ua[i]=rc8.val; //256个数里取64个数
end
endfunction
endclass
class uniquearray;
rand bit[7:0] ua [64];
constraint c { unique{ua};}
endclass
使用foreach进行约束时的注意
class packet;
rand bit[3:0] da []; //动态数组,声明为rand,在randomize时会填充数组
constraint da {
da.size() inside{[3:5]};
foreach(da[i]) da[i]<=da[i+1]; //此处的da为5个数时, i取值范围[0:4],会出现da[4]<da[5]的错
误情况,不合理的约束,应该改为
foreach(da[i]) if(i<=da.size()-2) da[i] <=da[i+1].
}
endclass
packet p;
initial begin
p=new();
p.randomize() with {da.size() inside {3,5};};
end
随机化句柄数组
- 随机句柄数组功能是在调用其所在类的随机函数时,随机函数会随机化数组中的每一个句柄所指向的对象。因此随机句柄数组的声明一定要添加rand来表示其随机化的属性,同时在调用随机函数前要保证句柄数组中的每一个句柄元素都是非悬空的,这需要在随机化之前为每一个元素句柄构建对象
- 如果要产生多个随机对象,那么可能需要建立随机句柄数组。和整数数组不同,需要在随机化前分配所有的元素,因为随机求解器不会创建对象。使用动态数组可以按照需要分配最大数量的元素,然后再使用约束减少数组的大小。再随机化时,动态句柄数组的大小可以保持不变或减少,但不能增加。
parameter MAX_SIZE=10;
class RandStuff;
bit[1:0] value=1; //两位bit,注意此处没有被rand/rnadc修饰,句柄randomize时不会影响其
变量
endclass
class RandArray;
rand RandStuff array[]; //此处动态数组的建立?存放类的句柄的动态数组
constraint c{ //句柄指明为rand,表示句柄指向的对象的变量也会被随机化,要求(句柄没
有悬空;类里面有没有变量被修饰为rand)
array.size() inside {[1:MAX_SIZE]};
}
function new();
array=new[MAX_SIZE] //分配最大容量
foreach(array[i])
array[i]=new(); //10个句柄
endfunction
endclass
RandArray ra;
initial begin
ra=new();
assert(ra.randomize()); //随机化数组,可能减小数组,甚至会randomize句柄指向的对象
foreach(ra.array[i]) //ra.randomize会先随机化元素个数,之后由于句柄为rand,会进
$display(ra.array[i].value); //一步randomize被修饰为rand的变量
end
ra.randomize() with {array.size==2} 1,1,若randstuff中value声明为rand,则value的值可为0,1,2,3
句柄为rand,在randomize之前,不能悬空,必须要指向对象,之后会randomize对象中被rand修饰的变量
随机控制
randsequence,随机安排组织原子(atomic)测试序列,可能用于协调激励组件和测试用例(uvm中另有专门方法)
initial begin
for(int i=0;i<15,i++)begin
randsequence(stream) //stream为入口,先从stream进来
stream:cfg_read:=1| //进入各分支权重不一样
io_read:=2|
mem_read:=5;
cfg_read:{cfg_read_task;}| //一半可能执行,一半可能执行完重复执行
{cfg_read_task;}cfg_read;
mem_read:{mem_read_task;}|
{mem_read_task;}mem_read;
io_read:{io_read_task;}|
{io_read_task;}io_read;
endsequence
end
end
randcase建立随机决策树,存在问题是没有变量可供追踪调试,对应随机约束中的dist权重约束+if_else条件约束组合。
initial begin
int len;
randcase
1:len=$urandom_range(0,2); //10%
8:len=$urandon_range(3,5); //80%
1:len=$urandom_range(6,7); //10%
endcase
$display("len=%0d",len);
end