04 SV 验证-随机化

介绍

为什么需要随机?

  • 芯片体积增大,复杂度日渐提高,原先的定向测试无法满足需求,因此随机测试比例不断提高
  • CRT 方法通过随机激励,可以找到意想不到的bug
  • CRT 需要激励,参考模型和在线比较,搭建完环境后,即可运行上百次仿真而无需手工检查结果,可以提高验证效率

为什么需要约束?

  • 如果没有约束,产生有效激励的同时也产生了很多无效和非法的激励

  • 约束不但可以指定数据的取值范围,还可以指定各个数值的随机权重分布

  • 我们需要的是合法的随机,需要限定激励的合法范围

需要随机什么?

  • 器件配置:通过寄存器和系统信号
  • 环境配置:随机化验证环境,例如合理的时钟和外部反馈信号
  • 原始输入数据:例如MCDF数据包的长度、带宽,数据间的顺序
  • 延时:握手信号之间的时序关系,例如valid和ready,req和ack之间的时序关系
  • 协议异常:如果反馈信号给出异常,那么设计是否可以保持后续数据处理的稳定性?

随机约束和分布

声明随机变量

声明rand变量,在后期通过对象调用randomize() 函数随机化变量, 约束constraint也在类中声明

SV 只能随机化2值数据类型, 无法随机化出X,Z值和字符串。但是可以用logic类型,不会报错,但是不会生成X,Z值。

1
class packet;
2
    rand bit [31:0] src;
3
    randc bit[7:0] kind;
4
    // limit the values for src
5
    constraint c {src > 10;
6
                  src < 15;}
7
endclass
8
9
packet p;
10
initial begin
11
    p = new();
12
    assert(p.randomize()) else
13
    $fatal(0, "randomize failed");
14
    transmit(p);
15
end

rand 和 randc 区别:

  • rand: 每次随机化这个类后,这些变量都会赋一个值,并且概率都是相同的
  • randc: 周期随机性,所有可能值都赋过值后,才可能重复

权重分布

关键词 dist 可以在约束中用来产生随机数值的权重分布,:= 表示每个值的权重是相同的, :/表示权重要平分到每一个值, 权重和值可以是常数或变量

1
rand int src, dst;
2
constraint c_dist {
3
    src dist {0 := 40, [1:3] := 60};
4
    // 0:40/220, 1:60/220, 2:60/220, 3:60/220
5
    dst dist {0 :/ 40, [1:3] :/ 60}
6
    // 0:40/100, 1:20/100... 三个数平分60个单位
7
}

集合成员和inside运算符

1
rand int c;
2
int lo, hi;
3
constraint c_range{
4
    c inside {[lo:hi]}; //相当于 lo<=c,c<=hi
5
}
6
// 选择一个集合之外的值,只需要用取反操作符!
7
constraint c_range{
8
    !(c inside {[lo:hi]});
9
}

使用”$”指定最大值和最小值

1
rand bit [6:0] a; // 0~127
2
constraint c_range{
3
    a inside {[$:4], [20:$]}; // 0<=a<=4 || 20<=a<=127
4
}

条件约束

可以通过 –> 或 if-else 来让一个约束表达式在特定时刻有效

1
class busOp;
2
    ...
3
    constraint c_io{
4
        (io_space_mode) ->
5
        addr[31] == 1'b1;
6
    }
7
    
8
    constraint c_len_rw {
9
        if(op == READ)
10
            len inside {[BYTE:LWRD]};
11
        else
12
            len == LWRD;
13
    }

约束块都是声明性的代码,是并行的,所有的约束表达式同时有效。

约束块的控制

打开关闭约束块

  • 一个类可以包含多个约束块,可以把不同的约束块用于不同的测试。
  • 一般情况下,各个约束块之间的约束内容是互相协调不违背的。
  • 对于其他情况,可以根据不同的需要来选择使能哪些约束块,禁止哪些约束块,可以使用内建的 constraint_mode() 函数打开或关闭约束。
1
class packet;
2
    rand bit length;
3
    constraint c_short {
4
        length inside {[1:32]};
5
    }
6
    constraint c_long {
7
        length inside {[1000:1023]};
8
    }
9
endclass
10
11
packet p;
12
initial begin
13
    p = new();
14
    p.c_short.constraint_mode(0); // 关闭short constraint
15
    assert(p.randomize());
16
    transmit(p);
17
    p.constraint_mode(0); // 所有的constraints都关闭
18
    p.c_short.constraint_mode(1); //只打开short constraint
19
    assert(p.randomize());
20
    transmit(p);
21
end

内嵌约束

SV 允许使用 randomize() with来增加额外的约束,这和在类里加约束是等效的,但同时要注意内部约束和外部约束之间应该是协调的,如果相违背,随机数值求解会失败。

1
class transaction;
2
    rand bit [31:0] addr, data;
3
    constraint c1 {
4
        soft addr inside {[0:100], [1000:2000]};
5
    }
6
endclass
7
8
transaction t;
9
initial begin
10
    t = new();
11
    // addr is 50-100,1000-1500
12
    // data < 10
13
    assert(t.randomize() with {addr >= 50; addr<= 1500; data < 10;});

QA:

如果t.randomize() with {addr inside [200:300]; data inside [10:20];} , 那么哪个值是合理的随机数值?

addr=200, data=10

soft 关键字,软约束: 如果外部也有一个约束,此时soft的约束优先级更低,若没有soft会报错

随机函数

SV 提供了两个预定义的void类型的函数 pre_randomize()post_randomize() , 如果在类中定义这两个函数,那么对象在执行了randomize() 之前或之后会分别执行这两个函数。

随机化个别变量

可以在调用 randomize() 时,可以在参数列表里加入变量,这样只有这些变量才会被随机化,其他变量会被当做状态变量而不会被随机化。

注意:无论变量是不是rand类型,都可以作为 randomize() 的参数而被随机化。

1
class rising;
2
    byte low;
3
    rand byte med, hi;
4
    constraint up{
5
        low<med; med<hi;
6
    }
7
endclass
8
// 情况1
9
initial begin
10
    rising r;
11
    r=new();
12
    r.randomize(); // 随机化med,hi
13
    r.randomize(med); // 只随机化med
14
    r.randomize(low); // 只随机化low
15
end
16
17
// 情况2
18
initial begin
19
    rising r;
20
    r=new();
21
    r.randomize(low);
22
end

对于情况2,med和hi没有参与randomize过程,值为0,randomize(low)后, 约束med<hi不满足报错。因此我们应该先对整个对象随机化,使它满足约束,然后再只随机化其中一个变量。

数组约束

数组大小约束

多数情况下,数组的大小应该给定范围,防止生成过大体积的数组或空数组。

1
class dyn_size;
2
    rand logic [31:0] d[];
3
    constraint d_size {
4
        d.size inside {[1:10]};
5
    }

size() 函数可以约束动态数组或队列的元素个数

使用inside约束可以设置数组大小的上下限。记住,一定要设置上限,否则会产生成千上万个元素,导致随即求解器要用很长的时间才能求解。

约束数组中的元素

SV 可以利用foreach对数组的每一个元素进行约束。针对动态数组,foreach更适合对非固定大小数组中每个元素的约束。

1
class good_sum;
2
    rand uint len[];
3
    constraint c_len {
4
        foreach(len[i])
5
            len[i] inside {[1:255]};
6
        len.sum() < 1024;
7
        len.size() inside {[1:8]};
8
    }

产生唯一元素的数组

如果想要产生一个随机数组,它的每一个元素的值都是唯一的。

方法一:

1
class unique_slow;
2
    rand bit [7:0] ua[64];
3
    constraint c {
4
        foreach(ua[i])
5
            foreach(ua[j])
6
                if(i != j) // 除了元素自己
7
                    ua[i] != ua[j]; // 和其他元素比较都不同
8
    }

会产生超过4000个独立的约束,从而降低仿真速度。

方法二:

也可以利用 randc 变量来辅助生成唯一元素的数组。

1
class randc8;
2
    randc bit [7:0] val; // val: 0-255,随机1-256次中,元素是唯一的
3
endclass
4
5
class little_unique_array;
6
    bit [7:0] ua [64];
7
    function void pre_randomize();
8
        randc8 rc8;
9
        rc8 = new(); // 不能放在foreach里
10
        foreach(ua[i]) begin // 随机化了64次,小于256,所以元素都是唯一的
11
            assert(rc8.randomize());
12
            ua[i] = rc8.val;
13
        end
14
    endfunction
15
endclass
16
17
initial begin
18
    little_unique_array lua;
19
    lua = new();
20
    lua.randomize(); // 会先执行pre_randomize

随机化句柄数组

1
parameter MAX_SIZE = 10;
2
class rand_stuff;
3
    bit [1:0] value = 1;
4
    // rand bit [1:0] value;
5
endclass
6
7
class rand_array;
8
    rand rand_stuff array[]; // 不要忘记使用rand!
9
    constraint c {
10
        array.size inside {
11
            [1:MAX_SIZE];
12
        }
13
    }
14
    function new();
15
        array = new[MAX_SIZE];
16
        foreach(array[i])
17
            array[i] = new();
18
    endfunction
19
endclass
20
21
rand_array ra;
22
initial begin
23
    ra = new(); // 构造数组和所有的对象,ra 里有十个对象
24
    assert(ra.randomize() with {array.size == 2});
25
    foreach(ra.array[i])
26
        $display(ra.array[i].value);
27
end

随机化句柄指向的对象和数组个数,但是句柄指向的对象value不是一个rand变量,array[0].value=1, array[1].value=1, 若没有function,句柄数组中的每一个句柄元素都是悬空的,所以需要在随机化之前为每一个元素句柄构造对象。

句柄声明为rand?

句柄指向的对象里的变量也会被随机化,但前提是句柄没有悬空,并且其变量也声明为rand。

随机控制

1
initial begin
2
    for(int i=0; i<15; i++) begin
3
        randsequence(stream)
4
        	stream : cfg_read := 1 | io_read :=2 | mem_read := 5;
5
            cfg_read : {cfg_read_task;} | {cfg_read_task;} cfg_read;
6
            mem_read : {mem_read_task;} | {mem_read_task;} mem_read;
7
            io_read : {io_read_task;} | {io_read_task;} io_read;
8
        endsequence
9
    end
10
end
11
12
task cfg_read_task;
13
    ...
14
endtask

以上代码产生了stream序列,它可以是cfg_read, io_read 或 mem_read, 权重不同,随机选取。

cfg_read 可以是对cfg_read_task的一次调用,也可以是在改任务调用后尾随一个cfg_read。

randsequence 的一个优点是它是程序性的代码,在执行过程中可以逐步调试,或增加$display 语句。如果调用对象的randomize() 函数,它要么成功,要么失败,无法知道它执行的过程。