写在前面:冲冲冲
1.随机化验证策略
为什么使用随机化验证策略?
- 设计复杂度提高之后,直接测试(定向测试)(directed testcase),没有办法通过穷举法验证所有矢量
- 定向测试案例用于检查确定的设计属性,仅仅用于检查可以预期的错误
- 定向测试方式跟测试时间是线性关系
- 定向测试案例不能检查隐形的错误
解决办法:采用带约束的随机化测试(CRT),对输入的激励随机化
2.随机内容有哪些?
必须对DUT里的所有关键点都采用随机化的技术。
(1)RTL设计的配置:
- 需要测试足够多
(2)环境的配置:
- 随机化配置整个环境(输入数据数量、输出数据类型)
(3)原始输入数据:
- 对输入数据进行随机化
- 在输入数据的范围内进行随机化
(4)封装的输入数据:
- 如果数据进行了一层层封装,不同的封装可以随机化,如TCP/IP
(5)协议异常、错误(error)和违规(violation):
- 验证系统如何处理错误
- 期望的错误类型,在随机的时间间隔里产生随机的错误类型,确保系统设计可以正确的处理这些错误
(6)时间延迟(时钟周期)
- 根据协议要求随机插入时间延迟(latency)
- 检查设计对时钟周期的敏感性
- 不需要对建立时间和保持时间进行验证
- 测试平台应该在每一个测试里都使用随机的、有效的延时,以便于发现设计中的Bug。
3.SV的随机化
- 随机化允许用户自动生成随机的输入激励,用于验证功能
- SV允许用户使用特定的约束,将随机输入的数据约束在有效的范围内
- 必须在OOP(class)中指定随机约束
3.1带有随机变量的简单类
例子:简单的随机类
//SV绿皮书
//例6.1简单的随机类
class packet;
//随机变量,rand普通随机,randc循环随机
//随机变量
rand bit [31:0] src, dst, data[8];
//周期性随机变量
randc bit [7:0] kind;
//src的约束,表示随机时src介于10和15之间
//如果不加约束,src每个数据出现的概率为1/256
constraint c {src > 10;src < 15;}
endclass
Packet p;
initial begin
// 创建一个对象
p=new();
//随机化约束一般写在class中,用点的方式去调用
assert (p.randomize());//用断言检查随机化,随机化成功,函数返回1;失败返回0
else $fatal(0,“Packet::randomize failed”); //随机化失败,函数返回0,并显示错误信息
transmit (p);
end
这个实例中,包括普通随机数rand、循环随机数randc、约束constraint、randomize等,记住这个例子。
- rand 修饰符,表示每次随机化这个类时,这些变量都会赋一个值(类似抽签,抽完放回去继续抽)。
- randc修饰符,表示周期随机性,即所有可能的值赋过值后随机值才可能重复(类似抽签,抽完不放回继续抽)。
- constraint约束是一组用来确定变量的值的范围的关系表达式,表达式的值永远为真。约束表达式放在{…}括号中,而没有放在begin和end之间,因为这段代码是声明性质,而不是程序性质。
- randomize()函数在遇到约束方面的问题时返回0。本例使用断言来检查randomize函数的结果,并使用$fatal来终止仿真。针对不同的仿真工具,需使用相应的选项来使断言能够终止仿真过程
在这里,我们需要明确几个问题:
- 在类中指定约束
- 不能在类的构造函数中随机化对象,因为在随机化前可能需要打开或关闭约東、改变权重,甚至添加新的约束。构造函数用来初始化对象的变量,不能在这里调用 randomi ze()函数。
- 类里的所有变量都应该是随机的( random)和公有的( public),这样测试平台才能最大程度地控制DUT。
4.随机化的约束
4.1约束解释器
- 解析约束的关系
- 相同的种子(seed)生成相同的随机数;使用不同的种子,可以生成一组不同的随机数
- 不同EDA工具厂商的约束解析器是定制的
4.2约束constraint
需要使用包含一个或多个约束表达式的约束块定义激励向量间的关系,SV会选择满足所有表达式的随机值。
每个表达式至少有一个变量必须是rand或randc类型的随机变量,如:
误例:没有随机变量的约束
//SV绿皮书
//例6.2没有随机变量的约束
class Child;
bit [31:0] age;//错误,应该使用rand或randc
constraint c_teenager{age>12;age<20;}
endclass
正例:受约束的随机类
//SV绿皮书
//例6.3受约束的随机类
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;
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
分析下这个实例:
- 在一个表达式中最多只能使用一个关系操作符(<、<=、==、>=、>),如:
-
//SV绿皮书 //例6.4不正确的排序约束 class order; rand bit [7:0] lo,med,hi; constraint bad {lo<med<hi;}//错误的做法 endclass 将上面修改,就正确了 //例6.6固定顺序的约束 class order; rand bit [7:0] lo,med,hi; constraint bad {lo<med;med<hi;}//只能用二进制约束? endclass
- 暂留
4.3权重分布
数值分布操作符:dist操作符
- 给部分数值增加权重,这样某些值的选取机会要比其他值更大一些。
测试案例需要相关的数值;为了测试结果指定一定的数值分布
- 两个操作符: := 或:/
:=操作符表示指定的数值具有相同的分布权重
:/ 操作符表示指定的数值均分重权
- 值或权重可以是常数或变量。值可以是一个值或值的范围,例如[lo:hi]。
- 权重更不用百分比表示,权重的和也不必是100。
- randc关键字申明的随机变量不能设置权重(因为是周期循环随机)
实例:静态改变权重
//SV绿皮书
//例6.7使用dist的权重随机分布
rand int src,dst;
constraint c_dist {
src dist {0:=40,[1:3]:=60};//src的值可能是0、1、2、3,其中0的权重是40,其他的权重是60
dst dist {0:/40, [1:3]:/60};//src的值可能是0、1、2、3,其中0的权重是40,其他的权重和是60
}
这段代码的权重分布如下:
4.4集合(set)成员和inside运算符
inside {} 运算符:产生一个值的集合。SV在值的集合里取随机值时,各个值的选取机会是相等的。
实例:inside的使用
//SV绿皮书
//例6.9随机值的集合
rand int c; //随机变量
int lo, hi; //作为上限和下限的非随机变量
constraint c_range {
c inside {[lo:hi]}; // lo < = c 并且 c < = hi
}
- 此处,应该满足lo<hi,否则导致约束错误。
- 可以把inside的约束看成foreach约束
实例:使用“$”指定最大值和最小值
//SV绿皮书
//例6.10使用“$”指定最大值和最小值
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
}
实例:随机集合约束的取反(!取反操作符)
//SV绿皮书
//例6.11 随机集合约束的取反
constraint c_range {
!(c inside {[lo:hi]}) ;//c < lo 或 c > hi
}
实例:在集合里使用数组
//SV绿皮书
//例6.12使用数组的随机集合约束
rand int f;
int fib[5]='{1,2,3,5,8};
constraint c_fibonacci{
f inside fib;
}
上面的例子还可以扩展成下面这种格式:
//例6.13等价的约束
constraint c_fibonacci{
(f = = fib[0])|| //f = =1
(f = = fib[1])|| //f= =2
(f = = fib[2])|| //f= =3
(f = = fib[3])|| //f= =5
(f = = fib[4]); // f= =8
}
实例:在inside约束中有重复的值
//SV绿皮书
//例6.14在inside约束中有重复的值
module Randomization_6_14();
class Weighted; //定义一个类
rand int val;
int array[]='{1,1,2,3,5,8,8,8,8,8};//声明了数组,并填入数组
constraint c {val inside array;} //inside,在数组中取值
endclass
Weighted W; //声明一个句柄
initial begin
int count[9],maxx[$];
W=new(); //创建一个对象
repeat(2000)begin
assert (W.randomize());//断言判断随机化约束,成功返回1,失败返回0
//count[w.val]++;//统计值的个数
count[W.val]+=1;
end
maxx=count.max();//获取最大值
//输出值的分布
foreach(count[i])
if(count[i]) begin
$write("count[%0d]=%5d",i,count[i]);
repeat(count[i]*40/maxx[0]) $write("* ");
$display;
end
end
endmodule
上面这段代码的仿真结果如下:
Compiler version I-2014.03; Runtime version I-2014.03; Aug 17 07:42 2020
count[1]= 443* * * * * * * * * * * * * * * * *
count[2]= 192* * * * * * *
count[3]= 178* * * * * * *
count[5]= 195* * * * * * *
count[8]= 992* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
V C S S i m u l a t i o n R e p o r t
为什么和书中例6.15的仿真结果不一样,值得思考!
实例:从数组中取出随机值
//SV绿皮书
module Randomization_6_16();
//例6.16从数组中取出随机值的类
class Days; //定义一个类
typedef enum {SUN,MON,TUE,WED,THU,FRI,SAT} days_e;
days_e choices[$];
rand days_e choice;
constraint cday {choice inside choices;}
endclass
//例6.17从数组中取出随机值
initial begin
Days days; //声明一个句柄
days=new(); //创建一个对象
days.choices={Days::SUN,Days::SAT};
assert (days.randomize());//断言判断随机化约束,成功返回1,失败返回0
$display("Radom weekend day %s\n",days.choice.name);//name函数返回的是枚举值的字符串
days.choices={Days::MON,Days::TUE,Days::WED,Days::THU,Days::FRI};
assert (days.randomize());//断言判断随机化约束,成功返回1,失败返回0
$display("Radom weekend day %s\n",days.choice.name);
end
endmodule
仿真结果:
Radom weekend day SUN
Radom weekend day WED
实例:使用randc随机地选取数组的值
//SV绿皮书
//例6.18使用randc随机地选取数组的值
module Randomization_6_18();
class RandcInside; //定义一个类
int array[]; //待选取的值
randc bit [15: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
endmodule
仿真结果:
Compiler version I-2014.03; Runtime version I-2014.03; Aug 17 08:29 2020
Picked 3 [1]
Picked 13 [6]
Picked 11 [5]
Picked 1 [0]
Picked 5 [2]
Picked 7 [3]
Picked 9 [4]
V C S S i m u l a t i o n R e p o r t
4.5条件约束
约束语句提供了两种语法用于声明条件关系:->和if-else
总结,也可以用下面这种方式等价表示:
-
mode == small -> len < 10; mode == large -> len > 100; //下面这种方式与上面等价,推荐使用上面的方式 if(mode == small) len<10; else if (mode == large) len>100;
-
rand bit [3:0] a,b; constraint c {(a==0) -> b==1;} //下面这种方式与上面等价,推荐使用上面的方式 rand bit [3:0] a,b; if(a==0) b==1;
4.6双向约束
- 约束语句是并行的声明性语句(declarative),不是过程化语句(procedural)
- 所有的约束表达式同时生效
//SV绿皮书
//例6.21双向约束
rand logic [15:0] r,s,t;
constraint c_bidir{
r < t;
s == r;
t < 30;
s > 25;
}
这段代码的约束范围为:25<s==r<t<30
对这个双向约束的求解如下:
解 | r | s | t |
A | 26 | 26 | 27 |
B | 26 | 26 | 28 |
C | 26 | 26 | 29 |
D | 27 | 27 | 28 |
E | 27 | 27 | 29 |
F | 28 | 28 | 29 |
4.7解的概率
没有约束的类
//SV绿皮书
//例6.24没有约束的类
class Unconstrained;
rand bit x; //0或1
randc bit [1:0] y;//0,1,2或3
endclass
没有约束的类的概率是相等的,如下:
带有关系操作的类
//SV绿皮书
//例6.25带有关系操作的类
class Unconstrained;
rand bit x; //0或1
randc bit [1:0] y;//0,1,2或3
constraint c_xy{
(x==0) -> y==0;
}
endclass
带有关系操作的类的解如下(注意对比上一个例子):
带有关系操作和约束的类
//SV绿皮书
//例6.26带有关系操作和约束的类
class Unconstrained;
rand bit x; //0或1
randc bit [1:0] y;//0,1,2或3
constraint c_xy{
y > 0;
(x==0) -> y==0;
}
endclass
使用solve...before约束引导概率分布(这个不太懂!)
//SV绿皮书
//例6.27带有关系操作和约束的类
class Unconstrained;
rand bit x; //0或1
randc bit [1:0] y;//0,1,2或3
constraint c_xy{
(x==0) -> y==0;
solve x before y;
}
endclass
4.8控制多个约束块
使用rand_mode()函数可以关闭随机变量
- 随机变量处于非激活状态,表示该变量不能声明为rand或randc
- rand_mode()函数是SV的内建函数,不能被覆盖
- 0-OFF:将随机变量设置为非激活状态( inactive),不能通过 randomize0函数进行随机化
- 1- ON :将随机变量设置为激活状态( active),可以通过调用 randomize()函数随机产生数据
通过 constraint_mode()函数激活或关闭约束
- 非激活状态的约束不能通过调用 randomize0函数随机化
- 所有的约束初始状态都是激活状态
- constraint_mode()函数是SV内建的函数,不能被覆盖
- 0-OFF:将约束块设置为非激活状态( inactive), randomize0函数将不起作用
- 1- ON :将约束块设置为激活状态( active), randomize()函数将起作用
//SV绿皮书
//例6.28使用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();
//通过禁止c_short约束产生长包
p.c_short.constraint_mode(0);
assert (p.randomize());
transmit (p);
//通过禁止所有的约束,然后使能短包约束来产生短包
//then enabling only the short constraint
p.constraint_mode(0);
p.c_short.constraint_mode(1);
assert (p.randomize());
transmit (p);
end
写在最后:练习最重要!