西洲渡

一只还没迈入IC领域的小菜鸡

SV系统验证(7)——随机约束

概述

  • 随着设计越来越大,要产生一个完整的激励来测试功能也变得越来越困难。定向激励的测试方法早已经无法满足检查功能完整性的要求。
  • SoC的集成度提高带来的模块之间交互的复杂度也是指数攀升,这就使得无法预测接下来用户使用过程中会发生什么样的状况。
  • 随机-约束,两个词组合在一起就构成了目前动态仿真验证的主流方法。
  • 随机约束测试(CRT,Constrained-Random Test)即能够产生你感兴趣的、你想不到的的测试向量,通过回归测试、替随机种子的方式来提高单位测试用例的覆盖率收集效率。
  • 随机测试带来的额外负担是环境的复杂度会提高,因为从环境组件上考虑不再只需要发送激励的组件,而且还包括监测器、比较器等。
  • 随机测试带来的环境复杂度还包括由于对环境复用和测试复用带来的组件封装要求,这会使得代码量加大。
  • 这种额外的代码量、环境集成要求也随之带来了更加灵活、更易集成复用的验证环境。

产生随机数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module stim; 
bit [15:0] addr;
bit [31:0] data;
function bit gen_stim();
bit success, rd_wr;
success = randomize(addr, data, rd_wr); //call std::randomize 使用系统函数调用随机数库
return rd_wr;
endfunction
endmodule
//通过 系统函数std::randomize() 对一些变量即可完成随机化或者理解为产生随机数并赋予这些变量。

//我们还可以通过其它一些与随机数生成有关的系统函数来产生。
$urandom(); //可以生成一个32位的无符号随机数。
$urandom_range(maxval, minval=0); //产生在max和min之间的无符号随机数

约束随机变量

  • 以上我们独立地生成一些随机数,在面向DUT的随机激励发生过程中,为了符合协议、满足测试需求,我们还需要添加一些约束。这些“约束”会使得变量朝着希望他们变化的方向去随机。不但如此,这些约束也会对变量与变量之间的关系生效。
  • 我们需要一个“载体“去容纳这些变量以及它们之间的约束这个“载体”即是类(在SV中只能是类),而类的成员变量均可声明为“随机“属性,用rand或者randc来表示。

随机变量

  • 任何类中的整型(bit/byte/int)变量都可以声明为rand/randc。

  • 定长数组、动态数组、关联数组和队列都可以声明为rand/randc,可以对动态数组和队列的长度加以约束

    1
    2
    3
    rand bit [70] len;
    rand integer data[]
    constraint db{data.size==len;}
  • 指向对象的句柄成员,也可以声明为rand(不能被声明为randc),随机时该句柄指向对象中的随机变量也会一并被随机

带有约束随机的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Packet;
rand bit [31:0] src, dst, data[8]; //The random variables
randc bit [7:0] kind;
constraint cstr{src>10;
src<15;} // Limit the values for src
function print();
$display("src is %0d\n dst is %0d\n kind is %0d\n data is %p",src, dst, kind, data);
endclass
Packet p;
initial begin
p=new(); //Create a packet
$display("before randomize");
p.print;
p.randomize() //如果没有指定参数,那么值随机化含有rand属性的变量,如果指定了,按指定参数随机化
$display("after randomize");
p.print;
end

//非组合型结构体可以声明为rand,非组合型的成员可以声明rand/randc。
typedef struct{
randc int addr = 1+constant; //非组合型的成员
int crc;
rand byte data[]={1,2,3,4}; //非组合型的成员
} header;
rand header h1; //非组合型结构体,如果需要在类里声明结构体,声明时前面要添加rand

//添加额外约束(临时约束)
typedef struct{
rand bit [31:0] src;
rand bit [31:0] dst;
rand bit [31:0] data[4];
rand bit [7:0] kind;
} packet_t;
packet_t pkt;
initial begin
std::randomize(pkt) with (src>10, src<15); //添加额外约束(临时约束)
end

修饰符rand和randc

  • 对于rand修饰符,表示在可生成的范围内,每个值的可能性相同的(每次随机都从整个范围内随机选取)。
  • 对于randc修饰符,它的值将会随机并且遍历其可取值范围(在整个范围内,每个数只可能出现一次)。
    1
    rand bit [7:0] y;
    image.png

约束块

  • 有用的激励不仅仅是随机值,变量之间也有着相互关系
  • 没有约束的随机变量会包含许多无效的和非法的值,这会使得有效激励的产生变得低效。
  • 需要用包含一个或多个约束表达式的约束块定义这些相互关系
  • 约束块支持整形通过set操作符来设置它们的可取值范围。
  • 除了成员集合设置(以下代代码均声明在类中),约束块也支持设置可取值的同时也为其设置随机时的权重。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    rand integer x,y,z; 
    constraint cl {x inside {3, 5, [9:15], [24:32], [y:2*y], z};} //可取范围:3、5、9-15、24-32、y-2y、z

    rand integer a,b,c;
    constraint c2 {a inside {b,c};}

    integer fives[4]='{5,10,15,20};
    rand integer v;
    constraint c3{v inside {fives};} //可取范围:five数组的成员

权重分布

对于:=操作符,它们表示每一个值的权重是相同的。
对于:/操作符,它们表示权重会平均分配到每一个值。

1
2
3
4
5
6
//操作符控制权重分布
x在100, 101, 102, 200300的权重是1-1-1-2-5
xdist {[100102]:=1, 200:=2, 300:=5}

x在100, 101, 102, 200300的权重是1/3-1/3-1/3-2-5
xdist {[100102]:/1, 200:=2, 300:=5}

唯一标识

  • unique可以用来约束一组变量,使得其在随机后变量之间不会有相同的数值。
    1
    2
    3
    4
    5
    rand byte a[5]; 
    rand byte b;
    rand byte excluded;
    constraint u {unique {b, a[2:3], excluded};} //a[2], a[3], b和excluded在随机化之后将包含不相同的数值。
    constraint exclusion {excluded==5;}

条件约束

  • 可以使用if-else或者 ->操作符来表示条件约束。
    1
    2
    3
    4
    5
    6
    7
    8
    mode==little -> len<10
    mode==big -> len>100

    bit [3:0]a,b;
    constraint c{(a==0)->(b==1);}
    if(mode==little)
    len<10; else if(mode==big)
    len>100;

迭代约束

  • foreach可以用来迭代约束数组中的元素,这些数组可以是长数组、动态数组、关联数组或者队列。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    class C; 
    rand byte A[];
    constraint C1 { foreach (A[i]) A[i] inside{2,4,8,16}; } //A[]的成员在{2、4、8、16}中选取
    constraint C2 { foreach (A[j]) A[j]>2*j; } //未限定A[]的size,则size默认是0,成员大小也是0
    endclass

    constraint {
    { foreach (A[i]) A[i] inside{2,4,8,16};
    foreach (A[j]) A[j]>2*j;
    A.size()<100 } //限定数组大小之后,用VCS可以仿真出来,questsim无法仿真(不同仿真器底层不同)

    //数组缩减的方法做迭代约束
    class C;
    rand bit [7:0] A[];
    constraint cl{A.size()==5;}
    constraint c2{A.sum()<1000;}
    endclass
    A[0]+A[1]+A[2]+A[3]+A[4]<1000

函数调用

  • 有时候在一些表达式中无法简单地来表述约束,可以在约束块中调用该函数来描述约束。
    1
    2
    3
    4
    5
    6
    function                             //例如要计算合并数组中的1
    int count_ones(bit [9:0]w);
    for(count_ones=0; W!=0; W=W>>1)
    count_ones +=w &1'b1;
    endfunction
    constraint C1 {length ==count_ones)v);} //约束中调用函数计数

软约束

  • 在没有soft描述时的约束,我们称之为硬约束,而带有soft描述是软约束
  • 软约束用来指定变量的默认值和权重。
  • 如果用户在使用时,指定了外部约束对同一个变量做二次约束,用户定义了子类,也对同一个变量做二次约束时,那么硬约束可以软约束,并且不会导致随机数产生的失败。
    1
    2
    3
    4
    5
    6
    class Packet; 
    rand int length;
    constraint deflt {soft length inside {32,1024};} //软约束 长度可选32和1024 如果有多个软约束采用就近原则
    endclass
    Packet p=new();
    p.randomize() with {length==1512;} //硬约束 长度为1512 优先级高于软约束

内嵌约束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class C1; 
rand integer x;
endclass
class C2;
integer x;
integer y;
task doit(Cl f, integer x, integer z);
int result;
result = f.randomize() with {x<y+z;}; //x采取就近原则(加上this也是指向C1),指向C1类型的对象f中的x
endtask //如果需要指向C2中的x需要加上限定词local
endclass

//以上代码指向不明确不推荐重名,以下代码为较好的格式
class C;
rand integer x;
endclass
function int F(C obj, integer y);
F=obj.randomize.() with (x){x<y;};
endfunction

local域指向

  • 之前在使用内嵌约束随机时,对于同名的变量处在不同的域中可能会出现指向模糊的情况,之前我们已经给出了一种方法。
  • 接下来我们可以通过local::的域索引方式来明确随机变量的指即local::指向的变量会在包含randomize()方法的对象中。
    1
    2
    3
    4
    5
    6
    class C; 
    rand integer x;
    endclass
    function int F(C obj, integer x=20);
    F=obj.randomize() with {x < local::x;}; //索引当前文本域中的x,即function中的x
    endfunction

随机控制

  • rand_mode可以使能或者禁止随机变量,当随机数被禁止时,同普通变量一样,不参与随机化。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class Packet;
    rand integer source value, dest value;
    ... other declarations
    endclass

    int ret;
    Packet packet a=new;
    //关闭所有变量的随机化
    packet a.rand_mode(0);

    //使能souce_value的随机化
    packet a.source_value.rand_mode(1);
    ret=packet_a.dest_value.rand_mode();

    randomize(source_value) //使用randomize,不管变量在定义的时候有没有rand属性,传入参数都可以直接随机化,并且原来定义时候的rand属性在有参数之后不起作用。

约束控制

  • 类似于随机控制,一些约束块或者某个类的约束块集合都可以使用constraint_mode实现单个控制或者集体控制。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class Packet;
    rand integer source value;
    constraint filterl {source value>2*m;}
    endclass
    function integer toggle_rand(Packet p); //实现反转
    if(p.filterl.constraint_mode())
    p.filterl.constraint_mode(0);
    else p.filterl.constraint_mode(1);
    toggle_rand=p.randomize();
    endfunction

内嵌变量控制

  • 在使用类的随机化函数randomize()时,如果伴有参数,那么只会随机化这些变量(参数优先级高于声明优先级),而其余变量无论是否之前被声明为rand/randc,都将不会参与到随机化当中
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class CA; 
    rand byte x,y;
    byte v,w;
    constraint cl{x<v && y>w;};
    endclass
    CA a=new;
    a.randomize(); //随机变量:x, y
    a.randomize(x); //随机变量:x
    a.randomize(v,w);//随机变量:v, w
    a.randomize(w,x);//随机变量:w,x

思考问题

  • 随机测试相比于定向测试,就你目前的认识,你认为有哪些优缺点?