激励产生有三种办法:直接测试(directed test,DT)、随机测试(random test,RT)
、直接随机测试(direct random test,DRT)
随机测试的验证平台通常更加复杂,需要激励产生器、监控器、检查器等验证组件。
随机生成机制共有四种:内置系统函数、randcase、randsequence结构、基于对象的随机生成 、标准随机函数std::randomize()
随机系统函数
$random: random_function::=$random [(seed)];
该函数返回32位,有符号的,伪随机数。
$urandom: function int unsigned $urandom[(int seed)];
该函数返回32位,无符号整数。
$urandom_range():function int unsigned_range(int unsigned maxval,int unsigned minval); 该函数返回一个在(maxval,minval)之间的无符号整数。
标准概率分布函数:dist_uniform, dist_normal, dist_exponential, dist_poisson, dist_t, dist_erlang, dist_chi_square. 每个系统函数将返回一个伪随机数,其遵从函数名所表示的随机分布。
randcase/randsequence
①随机分支选择randcase
根据分支权重来随机的选择执行某一条分支
randcase
3:x=1;
1:x=2; 权重和为3+1+4=8,所以第一支被选中的概率为:3/8=0.375
4:x=3;
endcase //注意过程块结尾是endcase
若权重为0,则该分支不会被执行,若所有分支权重都为0,则所有分支都不会被选中,仿真工具会发布警告信息。
此外,权重也可以是表达式(比如a+b、a|b)
②随机序列生成:randsequence
用于随机的产生结构化的激励序列
randsequence(main) //括号中的变量为顶层生成式,该式子决定了生成序列
main : first second done; //生成的序列由first second done构成
first : add|dec; //first生成式为add、dec二选一
second : {$display(“second”);}; //second生成式为输出second
done : add:3|dec:(1+1); //done生成式为选中add概率为60%,选中dec为40%
add : {$display(“add”);};
dec : {$display(“dec”);};
endsequence //注意该过程块结尾是endsequence,注意与endcase区分。
以上代码所输出的结果共有四种:
add second add、dec second add、add second dec、dec second dec
此外,注意到randsequence关键字后还有一个括号,括号内表示的是顶层生成式,若一个随机生成序列没有指定顶层表达式,则默认块内第一个生成式为顶层生成式。
基于对象的随机生成
基于对象的随机生成一共包括三个步骤:1.定义随机变量。2.指定约束条件(可选)。3.通过调用内置方法randomize()产生随机。
①定义随机变量
1.rand关键字声明的变量
rand bit [2:0]y; //声明了y是一个3位的无符号整数,其值在0到7的概率是相等的。
2.ranc关键字声明的变量
Randc bit [2:0]y; //声明了y是一个3位无符号整数,随机化过程为:先在取值范围内计算一个初始的随机排列(洗牌),然后再连续的调用中按顺序返回这些值(发牌)。当返回该排列的最后一个元素时,会重新计算一个新的随机排列(重新洗牌)。
注意:数组被声明成rand或者randc的时候,意味着数组中所有元素都被随机化。此外,对象的句柄也可以用rand声明(但不能用randc),此时对象中所有变量和约束都被并发的求解。
②指定约束条件
随机变量的值可以通过约束块中的约束表达式来约束,约束块的声明使用constrain 关键字:constrain constrain_identifier {{constrain_block_item}}
注意约束块结尾没有分号
约束快中常用的操作符inside,dist、条件->,if/else、循环语句foreach:
- inside集合操作
表达形式:expression inside {gather_range};
rand integer x,y,z;
constrain c1 {x inside { 3 , [9:15] , [y,2*y] }; } //x的取值在集合范围内,[9:15]是从9到15
iInteger fivers[0:3]={5,10,15,20}
constrain c2 { !( x inside fivers); }//x的取值在fivers表示的范围之外,注意取反操作
所有可选值具有相同的概率,注意该操作符构成了一条语句,包括在约束块内要以分号结尾(如上代码标红部分)
2.dist分布操作
表达形式:expression dist {value_range1:= dist_whight,value_range2 :/ dist_whight};
条目表达式1 操作符 权重 条目表达式2 操作符 权重
rand int x,y,z;
x!=8; //此处约束了x不等于8
constrain c3 {x dist {1:=1,8:=2,3:=5};}//由于x≠8,所有x只能=1、3,权重比为1:5
constrain c4 {y dist {[1:3]:=1,4:=2,5:=5};}//x的取值有1,2,3,4,5,权重比为1:1:1:2:5
constrain c5 {z dist {[1:3]:/1,4:=2,5:=5};}//x取值不变,权重比变为1/3:1/3:1/3:2:5
由上可知:当条目表达式为一个范围时,操作符:=和操作符:/表达的含义不同。其中:=表示范围中的每一个值都被指定权重
:/表示范围内的n个值的权重为指定权重,则范围内的每个值的权重为range_weight/n
注意:dist表达式不能应用的randc变量
同样的,该操作符也构成了一条语句,包括在约束块内要以分号结尾(如上代码标红部分)
3.条件操作
共有两种结构来声明条件操作分别是蕴含操作:->和if/else
蕴含操作:a->b表示若a为真,则b会被受到约束。
constrain c6 {mode==large->len<10;mode==small->len>10;}
注意该操作符构成一条语句,因此结尾要用分号分隔开,而不是用逗号
mode==large和mode==small相当于条件a,若mode==large成立,那么len<10。若mode==small成立,那么len>10。如果两个都不成立,那么len不受约束。
If/else
从上面对蕴含操作的解释可以发现,蕴含操作和if/else操作可以互相转换:
constrain c7 {
if(mode==large)
len<10;
else if(mode==small)
len>10;}4
4.foreach迭代操作
该操作常与其他随机操作搭配使用,来对数组元素进行随机
rand byte a[];//动态数组a
constrain c8 {foreach(a[i]) a[i] inside {[2:5]};}//a中的每一个元素都在[2:5]之内
constrain c9 {foreach(a[j]) a[j]>2*j;}//a的每个元素大于索引值的两倍
constrain c10 {a.size inside {[]1:10};}//动态数组a的尺寸在[1:10]
constrain c11 {foreach(a[k]) (k<a.size)->(a[k+1]>a[k]);}//数组a降序排列
约束块c10属于数组的大小约束,约束块c11属于数组的迭代约束,如果一个数组即被大小约束又被迭代约束,那么大小约束先被解析。
5.slove...before操作
我们一般希望解析器能确保选择的有效值概率相等,然而在蕴含操作中:
rand bit s;rand bit[31:0]d;
constrain c12 {s->d==0;} //s蕴含着d等于0,由于s和d是同时确定的,所以[s,d]共有223种组合,而该约束成立时的组合[1,0]的概率仅为1/223,非常低,趋向于0.
因此,为了解决这个问题,提出一种slove...before结构:slove a before b;意为a在求解b之前求解。于是上述代码再加一个约束语句后:
constrain c12 {s->d==0;}
constrain c13 {slove s before d;}//该约束条件存在,s先被解析。因此s为真的概率为50%,之后当d为真后再解析d,而d为0的概率也为50%,故[1,0]组合的的概率为1/4,与[0,0],[0,!==0],[1,!==0]的概率都相等。
注意:约束仅支持两态值,四态值(x,z)或四态操作符(===,!===)会导致错误
③随机方法
对象中的变量使用randomize()方法进行随机化,它的原型是类的一个虚函数:virtual function in randomize(); //虚方法
如果调用该函数产生的随机变量符合约束,那么该函数返回1,否则返回0.因此需要检查函数返回状态来确定是否随机成功。该方法是类的内置方法,不可重写。
在调用类的内置方法randomize()之前会调用另一个内置方法:pre_randomize(),
该内置方法是一个函数,函数体中的内容可被重写,用于初始化和预处理。
function void pre_randomize; //该方法不是虚方法
if(super)
super.pre_randomize();
初始化程序 //可重写
endfunction
在调用类的内置方法randomize()之后会调用另一个内置方法:post_randomize(), 该内置方法是一个函数,函数内容可被重写,用于对象随机化之后清除、打印诊断及后处理。
function void post_randomize; //该方法不是虚方法
if(super)
super.post_randomize();
后处理程序 //可重写
endfunction
④随机使能控制
以上①-③说明了基于对象的随机生成的步骤,一般定义的随机变量和约束默认都是被激活的,如果想要关闭对象中的某个变量的随机属性,则可以用随机变量使能控制rand_mode()和随机约束使能控制constrain_mode()
1.随机变量使能控制rand_mode()
该方法既可以是一个任务,又可以是一个方法;
做任务被调用时传入参数1,则激活随机变量,传入参数0则关闭随机属性。
做函数被调用时返回该变量的激活状态,1为激活,0为关闭
class packet; rand int x,y; ... endclass
initial begin
packet pk1=new;
pk1.rand_mode(0); //关闭对象中的所有随机变量
pk1.x.rand_mode(1); //使能对象中x变量的随机属性
ret=pk1.y.rand_mode(); //将对象中y的激活状态赋值给ret变量。
end
2.随机约束使能控制constrain_mode()
该方法也是既可以是函数也可以是任务,使用方法类似rand_mode(),只不过rand_mode是对随机变量操作,而constrain_mode是对约束块名字进行操作。
⑤约束的动态修改
如果在类中定义好了约束,在例化对象中使用时又有额外的约束怎么办?
常采用randomize...with结构来添加约束:
With后的约束块与类中声的约束块具有相同的格式。
标准随机函数
在类中可以在变量的前面添加上rand,同时加上constraint,在randomize的时候随机.但是,有时候在task或者function中需要对一个临时变量做随机,此时便可以借助于std::randomize(a,b,c)with实现对变量的随机.
function bit gen();
bit success,rd1,rd2;
success=std::randomize(rd1,rd2)with{rd1<rd2;}; //调用标准随机函数,将输入函数的变
return rd; 量rd随机化,该函数也可以和with搭配
endfunction