前言
基于《IEEE Standard for SystemVerilog — Unified Hardware Design, Specification, and Verification Language》18章的学习和自己的理解。有不对的地方希望大家补充。 编译工具 Cadence的Xcelium。
正文
基本概念
constraint 通常定义在class中, 在class中定义好后,执行randomize()函数, randomize函数会在new时随机所有的random变量。
如果想要不让constraint生效,控制方式:
- 想要让整体的contraint 不生效, 使用: constraint_mode()进行控制.
- 只想让某个rand 变量不再受到constraint控制, 使用: rand_mode()进行控制。
class mybus extends Bus;
rand bit[31:0] addr;
rand AddrType atype
constraint addr_range_cons{
(atype == low) -> addr inside{[0:10]};
(atype == high) -> addr inside{[20:30]};
}
....
task send_bus(mybus bus);
bus.addr_range.constraint_mode(0); //让addr_range约束失效
bus.atype = low;
bus.atype.rand_mode(0); //将atype的值的为固定值:low
bus.randomize();
...
endtask
constraint_mode通常在constraint hierachies中使用,什么是constraint hierachies参考:
https://verificationacademy.com/forums/systemverilog/what-hierarchical-constraints-system-verilog
随机变量
随机变量可以约束成2种类型:rand, randc,区别:
rand:声明一个随机变量,每次随机时,重新计算。
randc声明的变量是一个随机循环变量,相当于随机产生一个包含所有取值范围的序列(变量可以取的变量范围,不考虑其他约束),然后每次随机时依次从该序列中取值。当约束条件发送变化,或者当前随机取值的数组中的变量无法满足当前约束时,则重新计算该序列。
注意:
1. 系统要求对randc的变量进行随机(由于randc的定义要求),因此如果一组rand 和randc的混合约束,可能会导致求解失败. 因此randc随机的变量约束应该尽量简洁与其他变量减少耦合。
2. 如果一个randc的变量被声明为一个static,那么该变量在其他子class中随机时,所有子类中的该变量共用同一个随机值序列,每次随机取随机序列的下一个值。
可约束的变量包括:
- 只能是integral type类型的变量。
- 只支持2-state的变量,不支持4-state,无法约束成x,z,=== , !==等4-state的表达式也不支持。
- 整个array/动态arrays、queue都可以被约束,可以约束size,也可以约束单个数组的元素,可以用rand,randc来随机
如果一个已经赋值的数组重新randomize,数组内的元素将被randomize覆盖,如果对size进行约束,那么动态数组或者queue将会重新指定size大小。如果想要在对size进行重新约束确想保留部分数组的值不被随机覆盖,对想要保留的值使用rand_mode来进行控制。
class C;
rand u_a[5];
endclass
C c = new();
c.u_a[2].rand_mode(0);
- object handle可以被声明为rand类型,那么该object中的变量和constraint可以在object handle被randomize时,同步进行randomize, 对该object handle的随机实际不会改变的object handle。不可以使用randc。
- unpacked structure只能使用 rand来进行随机,structure内部的变量可以使用rand、randc来进行随机。 packed structure可以使用rand、randc进行随机。
Constraint blocks
constraint block有两种constraint prototype:explicit、implicit form。
如果是explicit的话,如果没有在class外没有相应的constraint块,会报错,implicit则会默认认为是一个空的constraint,不会报错。
class C
rand int a;
constraint proto1; //implicit
constraint proto2{a ==2}; //implicit
external constraint proto3; //explicit
endclass
constraint C::proto1{a == 1};
constraint C::proto3{a inside {[1:10]}}
继承自superclass的derived class会带有superclass的所有constraint。如果在derived class中想要override superclass的某个constarint,取同名的constraint便可以覆盖, prototype不同也可以被覆盖。
对于一个abstract class,可以定义一个pure constraint,继承自这个class的non-abstract class中必须包含该同名的非pure constraint。该pure constraint必须不能包含constraint 块,但derived class是可以包含的。 如果一个virutal class 是继承自一个 non-abstract class,该virutal class也可以将superclass中的constraint override 为一个pure constraint。
virtual class C
pure constraint demo_cons;
endclass
class C1 extends C;
constraint demo_cons ;
endclass
Constraint常见的约束
- inside, 用于表述某个在某个范围内,inside 是双向的
rand int a,b,c;
constraint demo_cons{
a inside {b,c}; //inside 双向约束了a==b || a==c
}
- dist 可以调整变量随机值的权重分布情况,不能对randc变量使用。
rand int a,b;
constraint demo{
a dist{[0:2]:=1, [3:4]:=2, 5:=5}; //a随机到0,1,2,3,4,5的权重分别为:1,1,1,2,2,5,总权重:12
b dist{[0:2]:/1, [3:4]:/2, 5:/5}; //a随机到1,2,3,4,5的权重分别为:1/3,1/3,1/3,1/2,1/2,5 总权重:8
}
不在dist范围内的值认为权重是0,将不会被随机到,
如果有明确的需求,尽量减少dist的使用,复杂的dist分布会增大求解器的负担,并且可能会导致意想不到的结果。
- unique, 可以方便的约束数组/多个变量间的值是不同的; 不能在randc的变量使用。
rand byte a[5];
rand byte b,c;
constraint demos {unique {b,a[2:3],c};} //约束是b,a[2],a[3],c的值不同
cosntraint demo2 {c ==5;} //约束c==5, 那么a[2], a[3], c不能是5
- implication, 就是"->"用于条件控制,等价与 if-else,这个约束是双向的
class u1;
rand bit[2:0] c,d;
cosntraint demo{c==1-> d==2;} //表示如果c==1则d==2;
endclass
class u2;
rand bit[2:0] a,b,c,d;
constraint demo{c==1-> d==2; c dist{[0:7]/1}} //加入了c dist分布的约束
constriant demo2{solve a before b; a==1->b=2; } //通过调整求解顺序也可以。
endclass
repeat(1000) assert(u1.randomize());
repeat(1000) assert(u2.randomize());
结果:
u1中 c==1 出现了 18次 约等于1/57
u2中 c==1 出现了 131次 约等于1/8
u1这时c=1的概率是1/57,因为约束是双向的,所以c,d的组合有8*8 = 64种,去掉(1,0), (1,1), (1,3) …(1,7) 这7种情况,剩余的57种,所以是1/57。这个可能不是设计者预期的,如u2加入了c的分布约束或者如demo2调整求解顺序,则可以将c=1概率调整到1/8。 if-else相同
- foreach,用于对数组/队列的每个元素都进行约束。
对于多维数组可以进行简写;
顺序 1 2 3 3 4 1 2
int A[2][3][4]; bit[3:0][2:0] B[3][5]
foreach(A[i,j,k]) ... //i代表A顺序中的1, j代表2, k代表3
foreach(B[i,j,k,l]) ... //i代表A顺序中的1, j代表2, k代表3,l代表4
注意foreach中 index需要明确上下边界,避免访问越界,如果动态数组在constraint中给与size的cons,size将=0.
int A[];
contstraint demo{
foreach(A[i]){A[i] < A[i+1]} //在i=4时会发生越界
foreach(A[i]){if(i<A.size-1){A[i]<A[i+1]}} //加入对index约束
A.size == 5; //先计算A.size == 5,然后再求解其他的东西,没有这一句size=0
}
如果constraint 同时对一个动态数组的元素和size进行约束,那么会先求解size,之后再foreach中A.size会作为一个state variable传入,但这时可能会导致求解失败。
- 数组reduction methods
数组自带的reduction methods,可以在constarint中使用。如:sum, product, xor,and,or等
rand bit[8:0] A[3];
constraint u1{A.sum() < 16'h100;} // 等价于 A[0]+A[1]+A[2]<16'h100, 可能是 9‘h1FF + 9'h1+ 9'h1 = 9’h1 < 9'h100;
constraint u2{A.sum() with (int'(item)) < 16'h100;} //等价于 int'(A[0])+int'(A[1])+ int'(A[2]) <16'h100
注意这里A.sum() 返回值的数据类型与A相同, 如A的类型是bit[8:0],那么A.sum的值依然是bit[8:0],那么A.sum的值可能是A的多个元素相加后的值截位后的值,这个肯定不是使用者期望的,所以可以像u2一样。
- Global constraint, 如果一个class member 被声明成rand,那么对于这个class member的约束称为global constraint。
class A; rand int v; endclass
class B extend A;
rand A left;
rand A right;
constraint u{ left.v<v; right.v>v; }
endclass
哪些变量和约束可以被randomize,遵循这些规则
- 从调用randomize()的object开始,将object中所有声明为rand的并且是active(rand_mode==1)的object添加到一个集合中,如果这些object中还有rand object,那么重复这个过程。这个步骤中添加的object称为:the active random objects
- 从所有的the active random objects 中选取所有active的约束,这些constraint就是可以randomize的。
- 选取所有的the active random objects的rand的并且是active的变量,这些变量就是需要被randomize的变量,其余变量都会被作为常量。
- solve before,变量求解顺序。 对于前文提到的 使用->后导致求解概率发生变化,可以调整求解顺序来解决该问题。注意只是通过调节求解顺序来改变概率分布,提高求解速度,不会导致求解失败。
rand bit a;
rand bit[31:0] b;
rand int x,y,z;
constraint demo1{a==0 -> b==32'h0}; //a==0的概率是1/2^32+1
constraint demo1{solve a before b; a==0 -> b==32'h0}; //a==0的概率是1/2
constraitn demo2{solve x,y before z; x+y->z; }; //也可以使用","隔离多个变量
注意: 1. 只能对rand使用, 不能对randc使用,且只能对integral type使用。
2. 使用时在constraint块中同时包含ordering以及variable的constraint,不能分开在两个constarint block中。
3. 不能有循环: solve a before b; solve b before a;
-
static constraint, constraint 如果定义成static,那么对这个的约束使用 constraint_mode将会影响所有包含该constraint的例化class。
-
function in constraint, constraint 可以将一段复杂的约束放入function中,代码更加简洁。
function int count_ones(bit[8:0] v);
for(count_ones=0;v!=0;v>>1)
count_ones += v;
endfunction
constarint u{x == count_ones(v);}
注意function在constraint中也有很多限制:
- function只允许input,ref两类参数。
- constraint中的function应该是automatic的,不会对外部造成影响。
- function会先被调用在求解时,返回值对于其他约束将时一个state variable,可以理解为一个定值,也可以理解为function是高优先级的。
eg:
function int F(int w); F = w*2 ;endfunciton
rand int x,y;
constraint C{x == F(y)};
constraint D{y inside{1,2,3,4};}
这里因为F(y)需要先求解,所以约束D先求解,计算出y和F(y),然后再计算x。 这里指出:这个是将整个求解空间进行细分,先计算高优先级的约束(这时根本不会考虑低优先级的约束),再计算低优先级的约束,此时可能会导致整个约束求解失败。
-Constraint guards,在constarint可以使用一些predicate expression,这些expression会在求解器求解前先进行计算,然后控制求解器的行为。predicate expression包含constant/ State variables/ object handle comparison 。predicate expression的作用是1. 避免求解器因为某些变量未定义等问题出错,2. 控制不同条件下的求解结果。
class demo;
rand int n;
rand Slist next;
constarint sort {n<next.n} //如果next是一个空指针,将会导致fail。
constarint sort2{if(next != n) n<next.n} //加入next非空的判断,避免非空时出现问题,这里if(next != n) 就是predict expression
endclass
在predicate expression中也可以包括多个子条件,多个子条件间可以用&&, ||, !来组合,每个子条件的结果可以用四个状态来表示:
– 0 FALSE 子expression的结果是不成立的。
– 1 TRUE 子expression的结果是成立的。
– E ERROR 子expression产生了错误,如:空指针等
– R RANDOM子expression包括一个random variables,无法被计算。
多个子expression的结果经过逻辑组合结果:
总结: &&: 多个子expression: 优先级0>E>R>1
||:多个子expression: 优先级1>E>R>0
!:则 0->1, 1->0,其余不变
如果: 1. 结果是TRUE,这条constraint就是一个unconditional consrtaint(绝对的约束)
2. 结果是FALSE, 这条constraint将不在生效,求解器不会报错
3. 结果是ERROR,求解器报错
4. 结果是RADNOM,这条约束就是一个conditional constraint,结果取决于子expression的结果
class D;
int z;
endclass
class C;
rand int x,y;
D a,b;
constraint c1 {(x<y || a.z>b.z || a.z==5)-> x+y==10; }
endclass
分析:三个子expression: 1. (x<y) , 2.(a.z>b.z), 3.(a,z == 5)。这里x,y是random variable,所以1.(x< y)的结果是R,则需要看其他条件
case1: a non-null, b null, a.x = 5。1. R, 2. E, 3,1 结果为1,则x+y == 10 一定生效,这里条件1将会被忽略
case2: a null ,1. R, 2. E,3.E,结果是E 报错
case3:a non-null, b non-null , a.z =10, 6.z = 20。1.R , 2. 0, 3.0 结果为R, 则约束为(x<y) ->(x+y==10 )。
这个地方太tricky,平时写时不建议这么搞,写这么复杂的条件,分析不清楚就会把自己搞死,如果确实需要predicate expression,建议尽量让predeicate expression的如果出现你预期外的情况(如ERROR状态)。上报ERROR就好了
- soft constraint
soft constraint是用来解决superclass与derived class之间的约束冲突的问题,表示一个低优先级的约束,如果有更高优先级的软约束/硬约束(不带soft关键字的),软约束会被他们覆盖掉。 环境中最后指定的软约束会覆盖之前的软约束。
soft constraint的规则如下:- 在同一个constraint block, class、struct中,后定义的软约束比先定义的有更高优先级。
- external constraint与class内的constraint的优先级取决与在class中constraint声明的顺序
- 指针的软约束优先级低于class中直接定义的软约束。
- 每个rand 指针中的软约束的优先级与其指针声明顺序相关。
- derived class的软约束的优先级高于superclass
- 在randomize() method中的软约束优先级高于在该class中的优先级
class B1;
rand int x;
constraint a{soft x>10; soft x<100}; //软约束分别为a1, a2
endclass
class D1 extends B1;
constraint b{soft x inside {[5:9]};} //b1
endclass
class B2;
rand int y;
constraint c{soft y>10}; //c1
endclass
class D2 extends B2;
constraint d {soft y ==1;} //d1
constraint e;
rand D1 p1;
rand B1 p2;
rand D1 p3;
constraint f{soft p1.x < p2.x}; //f1
endclass
constraint D2::e{soft y>100;} //e1
D2 d = new();
d.randomize() with {soft y inside{10,20,30}; soft y<p1.x;}; //i1 i2
优先级:i2>i1>f1>e1>d1>c1>p3.b1>p3.a2>p3.a1>p2.a2>p2.a1>p1.b1>p1.a2>p1.a1
- disable soft constraint,将低优先的约束都disable掉,只保留高优先级的约束
class A
rand int x;
constraint A1 {soft x==3;}
constraint A2 {disable soft x;} //将低优先级的A1约束disable
constraint A3 {soft x inside {1,2};}
endclass
Randomization methods
randomize()
class都有内建的随机methods,可以进行随机, 包括:
virtual function int randomize();
function void pre_randomize();
function void post_randomize();
可以看到randomize()有返回值,return 0 表示随机失败, return 1 表示随机成功。所以randomize一定要加入对随机结果的判断。
但pre/post_randomize()是没有返回值。随机过程是在调用 obj.randomize()时,首先调用pre_randomize(),在所有random变量完成随机后,再调用post_randomize()。pre/post_randomize都可以再class中override,override后在调用该对象的randomize()时,将自动调用override后的pre/post_randomize。randomize()不可以被override
如果随机变量被声明为static类型, 所有 instance共享同一份, 每次randomize(), 所有instance中都会发生变化。
如果randomize() 失败了, 所有随机变量将会保持随机前的值
如果randomize() 失败了, post_randomize()将不会被调用
randomize() with
使用obj.randomize() with, 用户可以在调用randomize时添加约束。
变量查找顺序:
identifier_list为空,表示随机是unrestricted的, unrestricted表示constraint_block中的变量在解析时首先从obj中去查找,如果找不到则开始在调用该randomize()的作用域查找。使用this和super则强制将变量的范围绑定在obj中。
标准中可以定义identifier_list来指定哪些变量在obj内部进行查找,其余未定义的变量则从randomize()外部查找。(Xcelium不支持)
local:: 可以bypass obj内部,直接从randomize()调用的作用域查找
class C1;
rand int x,y;
constarint { soft y == 100};
endclass
class demo;
int x,y,z;
C1 c1;
z=10;
c1.randomize() with { this.x == y+1+z}; //x,y 均为C1的变量 z为demo的变量
c1.randomize() with { this.z == y+1}; //编译错误, this.z在c1中找不到
c1.randomize() with { local::x == y+1+z}; //y为C1的变量 x,z为demo的变量
c1.randomize() with(x) { this.x == y+1+z}; //x为C1的变量 y,z为demo的变量 Xcelium不支持
endclass
randomize(variable_list)也可以通过variable_list 来指定哪些随机变量本次进行随机,如果variable_list为空,则所有随机变量都会被随机, 如果非空,那么variable_list的变量将被随机,其他的随机变量将作为state_variable不再被随机。但注意此时约束仍然起作用
randomize(null) 表示所有随机变量都是state variable,这时只检查约束是否满足。
class U;
rand int x,y,z;
constarint C{x==y; z<100};
endclass
...
U u1 =new;
u1.randomize(); //随机x,y,z 结果: x=100, y=100, z= 5
u1.randomize(x); //x是随机变量,y,z是state varibale, y,z 值不变,但因为 ==是双向的,所以x依然为100
u1.z= 200;
u1.randomize(x); //x是随机变量,y,z是state varibale,但z=200不满足约束,求解失败。
int success = u1.randomize(null); //表示所有变量都是state variable,因为z=200不满足约束。success = 0;
如果你已经随机过一次object,之后你希望只改变object中的一个变量值,其余随机值不需要改变,那么你不需要用rand_mode来固定他们,只需要将要随机的变量加入到variable_list中即可。