一、随机化介绍
- 随着设计变得越来越复杂,要产生一个完整的激励集来测试设计的功能也变得越来越困难了。解决的办法是采用约束的随机测试法(CRT)自动产生测试集。
- 准备CRT的环境要比准备定向测试集的环境复杂,CRT环境不仅需要产生激励,还需要通过参考模型、传输函数或其他方法预测输出结果。
- CRT由两部分组成:使用随机的数据流为DUT产生输入的测试代码,以及伪随机数发生器的种子。通过改变种子的值,就可以改变CRT的行为。
二、什么需要随机化
调用$random函数通常只是是产生随机化的数据,一些隐藏的BUG大都在控制路径里。必须对DUT里的所有的关键点都采用随机化的技术,随机化使控制路径里的每一个分支都可能被测试。
需要考虑设计输入的各个方面:
- 器件配置
- 环境配置
- 原始输入数据
- 封装后的输入数据
- 协议异常
- 延时
- 事务状态
- 错误error和违规violation
1、器件配置
在RTL级设计的测试中,因为没有测试足够多的配置,可能会遗漏BUG。随着时间的变化,DUT的配置会变得越来越随机。
2、环境配置
通常设计的器件在一个包含了若干个器件的环境里工作,要随机化整个环境,包括对象的数量以及它们如何配置的。
3、原始输入数据
例如总线写操作的数据或ATM信元填充的随机数据。需要设计协议的各个层次以及故障注入。
4、封装后的输入数据
很多器件会处理激励的不同层次。协议的每个层次都有自己的控制域,可以采用随机化的方法测试不同的组合。需要编写约束以产生有效的控制域,同时还要允许注入故障。
5、协议异常、错误和违规
测试故障注入是否会产生死锁或者进入不正确的状态。还要保证测试平台能模拟通信中断的情况。测试平台应该能够产生功能正确的激励,然后通过翻转某一个配置位,在随机的时间间隔产生随机的错误类型。
6、延时
许多的通信协议定义了延时的范围。在比周期级验证更低的级别,一些设计会对时钟抖动非常敏感。时钟发生器应该是位于测试平台之外的一个模块,这样它就可以在有效区域Active Region产生事件,和其他设计事件一样。
二、SV中的随机化
1、带有随机变量的简单类
class Packet;
//随机变量
rand bit[31:0] src,dst,data[8]; //每次随机化这个类时,都会赋一个值
randc bit[7:0] kind; //周期随机性,所有可能的值都赋过值后随机值才可能重复。周期性是指单一变量的周期性
//src的约束
constraint c{src > 10;
src < 15;}
endclass
Packet p;
initial begin
p = new();
assert (p.randomize()); //randomize函数在遇到约束方面的问题时返回0
else $fatal(0,"Packet::randomize failed");
transmit(p);
end
/*
不能再类的构造函数里随机化对象,因为在随机化前可能需要打开或者关闭约束、改变权重,甚至添加新的约束。
类里的所有变量都应该是随机的和公有的,这样测试平台才能最大程度地控制DUT。
*/
2、检查随机化的结果
randomize()函数为类里所有的rand和randc类型的随机变量赋一个随机值,并保证不违背所有有效的约束。如果随机化成功函数返回1,否则函数返回0。
3、约束求解
约束表达式的求解是由SystemVerilog的约束求解器完成的。求解器能够选择满足约束的值这个值由SystemVerilog的PRNG从一个初始值seed产生。如果SV的仿真器每次使用相同的初始值、相同的测试平台,那么仿真的结果也是相同的。各种仿真器的求解器都是不同的,因此使用不同的仿真器时受约束的随机测试得到的结果也有可能不同,针织同一个仿真器的不同版本的仿真结果也不相同。SV便标准定义了表达式的含义以及产生的合法值,但没有规定求解器计算约束的准确顺序。
4、什么可以被随机化
SV可以随机化整型变量,即由位组构成的变量。尽管只能随机化2值数据类型,但位也可以是2值或4值类型。所以可以使用整数和位矢量,但不能使用随机字符串,或在约束中指向句柄。
三、约束
有用的激励并不仅仅是随机值——各个变量之间有着相互关系。否则仿真器可能需要很长时间才能产生需要的激励值,或激励向量里会包含无效的值。你需要用包含一个或多个约束表达式的约束块定义这些互相关系,SV会选择满足所有表达式的随机值。每一个表达式里至少有一个变量必须是rand或randc类型的随机变量。
1、什么是约束
受约束的随机类
class Stim;
const bit[31:0] CONGEST_ADDR = 42;
typedef enum{READ,WRITE,CONTROL} stim_e;
randc stim_e kind;
rand bit[31:0] len,src,dst;
bit congestion_test;
//在约束块里只能包含表达式,不能进行赋值,应该用关系运算符为随机变量赋一个固定的值,如len==42
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
2、权重分布
- dist操作符允许产生权重分布,这样某些值的选取机会要比其他值更大一些。
- dist操作符带有一个值的列表以及相应的权重,中间用:=或:/分开。
- 值或权重可以是常数或者变量。值可以是一个值或值的范围。权重不用百分比表示,权重的和也不必是100。
- :=操作符表示值范围内的每一个值的权重是相同的,:/操作符表示权重要均分到值范围内的每一个值。
使用dist的权重随机分布
rand int src,dst;
constraint c_dist{
src dist{0:=40, [1:3]:=60};
//src=0,weight=40/220 220=40+3*60
//src=1,weight=60/220
//src=2,weight=60/220
//src=3,weight=60/220
dst dist{0:/40, [1:3]:/60};
//dst=0,weight=40/100
//dst=1,weight=20/100
//dst=2,weight=20/100
//dst=3,weight=20/100
}
动态改变权重
class BusOp;
typedef enum{BYTE,WORD,LWRD} length_e;
rand length_e len;
//dist约束的权重
bit[31:0] w_byte = 1,w_word = 3,w_lwrd = 5;
constraint c_len{
len dist {BYTE:=w_byte, //使用可变的权重
WORD:=w_word, //来选择随机的操作数长度
LWRD:=w_lwrd
}
}
endclass
3、集合(set)成员和inside运算符
inside运算符产生一个值的集合。除非对变量还存在其他约束,否则SV会在集合里去随机值,且各个值选取的机会相同。在集合里也可以使用变量。
随机值的集合
rand int c;
int low,high;
constraint c_range{
c inside {[low:high]}; //low<=c并且c<=high
}
/*
采用这种方法可以使得约束参数化,不用修改约束,测试平台就可以改变激励发生器的行为。
如果low>high则会产生一个空的集合,导致约束的错误。
*/
使用$指定最大和最小值
rand bit[6:0] b; //0<=b<=127
rand bit[5:0] e; //0<=e<=63
constraint c_range{
b inside {[$:4],[20:$]}; //0<=b<=4 || 20<=b<=127
e inside {[$:4],[20:$]}; //0<=e<=4 || 20<=e<=63
}
4、在集合里使用数组
使用数组的随机集合约束
rand int f;
int fib[5] = '{1,2,3,4,5,8};
constraint c_fibonacci{
f inside fib; //集合里的每一个值取出的概率都是相同的,即使值在数组中出现多次
}
使用randc随机地选取数组的值
class RandcInside;
int array[]; //带选取的值
randc bit[16:0] index; //指向数组的指针
function new(input int a[]);
array = a;
endfunction
function int pick //返回刚选取出的值
return array[index];
endfunction
constraint c_size{
index < array.size;
}
endclass
initial begin
RandcInside ri;
ri = new('{1,3,5,7,9,11,13});
repeat(ri.array.size) begin
assert(ri.randomize());
$display("Picked %2d [%0d]", ri.pick(), ri.index);
end
end
5、条件约束
有的时候我们需要让一个约束表达式只在某些时候才有效,例如一条总线支持字节、字、长字的读操作,但只支持长字的写操作。
->操作符可以产生和case操作符效果类似的语句块,可以用于枚举类型的表达式。
带有->操作符的约束块
class BusOp;
...
constraint c_io{
(io_space_mode) -> addr[31] == 1'b1;
}
endclass
带有if-else操作符的约束块
class BusOp;
...
constraint c_len_rw{
if(op == READ)
len inside {[BYTE:LWRD]};
else
len == LWRD
}
6、双向约束
约束块不像自上向下执行的程序性代码,它们是声明性的代码,是并行的,所有的约束表达式同时有效。SV约束也是双向的,这表示它会同时计算所有的随机变量的约束。
7、使用合适的数学运算来提高效率
约束求解器可以有效地处理简单的数学运算,例如加减、位提取和移位。SV任何没有显式声明的位宽的常数都是作为32位数值对待的。