【验证小白】静态模块(module或interface等)中的可控随机探索($urandom/$random)

前言

搭建简单的superbench

在centOS中依靠makefile和VCS搭建了自己的平台之后,可以逐步将之前记录和解决的问题付诸实践,慢慢的归纳和分享。这次要来解决的是一个经典问题,就是验证环境中的静态模块(主要是module和interface)的随机问题;

内容较多,节约时间可直接至“结论”;

背景

我需要做一个延时模块,目的是把输入信号延时一段时间,然后输出,主要的应用场景就是模拟跨异步传输延时误差和总线传输延时误差;

因为总线不止1bit,因此要例化多个模块,那么我的需求是这样的:

1.同一个延时模块进行多次延时,每次随机值应该不同;

2.不同延时模块进行同一次延时,延时随机值应该不同;

3.随机延时值必须可控,即相同种子的两次用例结果应该是一致的;

一般来说,我们做随机,常用的函数就是几个:$random、$urandom、$urandom_range(min, max)、std:randomize(),其中第一个大家一定都知道是要尽量避免使用的,因为其不受seed控制(顺便说一下,关于这里建议还是查询下sv标准,绿皮书里的说明也有些错误),这里就重点说明下$urandom与$random好了;

$urandom与$random

关于这两个系统函数呢,其实各路英雄也总结的很多了,核心就是一句话:$random不受seed控制,$urandom受seed控制,应该避免使用前者;

因为我这次是要在静态模块中使用随机,所以要进一步明确:

1.$random在module/interface等静态模块中使用,同一模块多次例化,那么单次run时不同例化实体中每一次例化的随机值是不同的,多次run的话宏观看跟第一次run没有区别;

2.$urandom在module/interface等静态模块中使用,同一模块多次例化,那么单次run时不同例化实体中每一次例化的随机值是相同的,多次run的话宏观看$urandom会受到seed的控制;

这么说实在是太饶了,不如我来做了实验吧!

实验代码如下:

module rand_test();
    int a, b;
    initial begin
        for (int xx=0; xx<3; xx++)begin
            a = $urandom;
            b = $random;
            $display("%m.a = %0d, b = %0d", a, b);
            #100ns;
        end
    end
endmodule

module harness();
    
    rand_test U_A();
    rand_test U_B();
    rand_test U_C();

    initial #1000ns $finish;
endmodule

make run seed=100打印结果:

      (Specify +UVM_NO_RELNOTES to turn off this notice)

harness.U_A.a = 1547241734, b = 303379748
harness.U_B.a = 1547241734, b = -1064739199
harness.U_C.a = 1547241734, b = -2071669239
harness.U_A.a = 5490726, b = -1309649309
harness.U_B.a = 5490726, b = 112818957
harness.U_C.a = 5490726, b = 1189058957
harness.U_A.a = 90939801, b = -1295874971
harness.U_B.a = 90939801, b = -1992863214
harness.U_C.a = 90939801, b = 15983361
$finish called from file "../top/harness.sv", line 20.

make run seed=200打印结果:

      (Specify +UVM_NO_RELNOTES to turn off this notice)

harness.U_A.a = -1061171152, b = 303379748
harness.U_B.a = -1061171152, b = -1064739199
harness.U_C.a = -1061171152, b = -2071669239
harness.U_A.a = -1209238210, b = -1309649309
harness.U_B.a = -1209238210, b = 112818957
harness.U_C.a = -1209238210, b = 1189058957
harness.U_A.a = -183526848, b = -1295874971
harness.U_B.a = -183526848, b = -1992863214
harness.U_C.a = -183526848, b = 15983361
$finish called from file "../top/harness.sv", line 20.

观察两次的结果可以看出来:

如果使用$urandom(a使用的),同于一个例化模块而言,的确单次run中多次随机值不同,多次run时候也是不同的且能受seed控制,但是同个模块的多次例化同一次随机却是一样的,根本没能起到不同总线随机延时的功能;

如果使用$random(b使用的),能起到不同总线随机延时的功能,但是对于同一个例化,多次run时候随机值宏观看是一样的且不能受seed控制;

$urandom_range(min, max)、std:randomize()甚至randcase都和$urandom有相近的性质,也无法解决问题;

要解决这个问题,还是先把在$random上做文章的心思抛开了,这个系统函数本身就是个不受seed控制的随机数发生器,无论如何也进化不了了;

所以需要$urandom上做文章,寻找同一个例化模块的不同点;

基于%m的随机化

我想了挺长时间,终于在吃香菇鸡米饭的一个中午豁然开朗!每个模块的例化路径不是一定不重样的么,这个不就可以作为我随机的基石么!!想明白了之后我又上网一查,发现显然不是我一个人想明白了,原来也有人探索过类似的做法了,不过没关系,前人的经验也可以为我所用嘛!

按照这个思路,我们来获取下每个inst的路径,并转为数值;获取路径的话很简单,有一个神奇的标识符——%m,说实话,自从我认识了%m后我就感觉人生都亮了,所以改一改module rand_test()的代码:

    int a, b;

    string       path_str;
    reg [2047:0] path_int;
    initial begin
        path_str = $psprintf(path_str, "%m");
        path_int = path_str;
        $display("path_str=%s, path_int=%0d", path_str, path_int);
    end

$psprintf也可以考虑使用$sformat(path_str, "%m")替代,harness代码不变,打印结果如下:

      (Specify +UVM_NO_RELNOTES to turn off this notice)

path_str=harness.U_A, path_int=126188465672475447999684417
path_str=harness.U_B, path_int=126188465672475447999684418
path_str=harness.U_C, path_int=126188465672475447999684419
$finish called from file "../top/harness.sv", line 29.

普大喜奔!确实能找到不一样的随机量了,但是有个问题啊,这个path_int只有后几位是不一样的,这区分度也太小了吧?

因此我引入了以下几个“扩大”随机的方法,先把代码放上来:

1    string       path_str;
2    int          path_int;
3    initial begin
4        path_str = $psprintf(path_str, "%m");
5        path_int = $urandom();
6        for(int i=path_str.len; i>=0; i=i-1)begin
7            path_int = path_int ^ path_str.getc(i);
8            path_int = $urandom(path_int);
9        end
10       path_int = $abs(path_int);
11       $display("path_str=%s, path_int=%0d", path_str, path_int);
12   end

1.line5,先把path_int通过$urandom给一个初始随机值,即使三个模块U_A、U_B、U_C是一样的也没关系,这个值结合后面路径的微小差异,就可以成就“蝴蝶效应”;

2.line6,从尾部-根部遍历路径字符,因为差异集中在尾部;

3.line7,str.getc(i)获取字符串对应位置字符的ASCII码,再跟path_int取异或,累计扩大差异性;

4.line8,进一步扩大差异性;

5.line10,实验过程中发现最后的结果可能是负数,这不是我所需要的,因此通过$abs函数全部取正;

好的,让我们来看看结果,seed = 100:

      (Specify +UVM_NO_RELNOTES to turn off this notice)

path_str=harness.U_A, path_int=137603673
path_str=harness.U_B, path_int=976424504
path_str=harness.U_C, path_int=1319816804
$finish called from file "../top/harness.sv", line 36.

seed = 200:

      (Specify +UVM_NO_RELNOTES to turn off this notice)

path_str=harness.U_A, path_int=1872717276
path_str=harness.U_B, path_int=548080505
path_str=harness.U_C, path_int=1711115269
$finish called from file "../top/harness.sv", line 36.

再来一次seed = 100:

      (Specify +UVM_NO_RELNOTES to turn off this notice)

path_str=harness.U_A, path_int=137603673
path_str=harness.U_B, path_int=976424504
path_str=harness.U_C, path_int=1319816804
$finish called from file "../top/harness.sv", line 36.

稳定复现,还有比这更完美的事么!

可能有,后来我想了想,如果我是path_int = $urandom($urandom($urandom($urandom(path_int)))),是不是一样可以把微小的差异扩大啊?

封function+宏定义

进一步的,我把这个成果封成function+宏,以便之后取用:

`define module_urandom_define \
    string   path_str; \
    initial  path_str = $psprintf(path_str, "%m"); \
    \
    function integer urandom; \
        integer seed, i; \
        begin \
            seed = $urandom(); \
            for(i=path_str.len; i>=0; i=i-1)begin \
                seed = seed ^ path_str.getc(i); \
                seed = $urandom(seed); \
            end \
            urandom = $abs(seed); \
        end \
    endfunction

module rand_test();
    int a, b;
    
    `module_urandom_define;
    
    initial begin
        for (int xx=0; xx<3; xx++)begin
            a = urandom();
            b = urandom();
            $display("%m.a = %0d, b = %0d", a, b);
            #100ns;
        end
    end
endmodule

module harness();
    
    rand_test U_A();
    rand_test U_B();
    rand_test U_C();

    initial #1000ns $finish;
endmodule

在module中直接`module_urandom_define后,就可一直接使用函数urandom(),seed = 100时打印结果如下:

      (Specify +UVM_NO_RELNOTES to turn off this notice)

harness.U_A.a = 1471040728, b = 863921358
harness.U_B.a = 1338760535, b = 342532348
harness.U_C.a = 1904052621, b = 2077234749
harness.U_A.a = 1485973489, b = 1802195813
harness.U_B.a = 1046096403, b = 1198609932
harness.U_C.a = 968193349, b = 152370968
harness.U_A.a = 2088965477, b = 1393854800
harness.U_B.a = 1045163969, b = 1599575068
harness.U_C.a = 1987425378, b = 1015766576
$finish called from file "../top/harness.sv", line 42.

seed = 200时打印结果如下:

      (Specify +UVM_NO_RELNOTES to turn off this notice)

harness.U_A.a = 786960723, b = 1012746144
harness.U_B.a = 993085640, b = 327905961
harness.U_C.a = 175655228, b = 341306910
harness.U_A.a = 1769729887, b = 1776605454
harness.U_B.a = 1756385201, b = 1608046143
harness.U_C.a = 601311374, b = 1036857959
harness.U_A.a = 1325663168, b = 248563413
harness.U_B.a = 1528493527, b = 399659340
harness.U_C.a = 2138557678, b = 1356991027
$finish called from file "../top/harness.sv", line 42.

目前看没有什么问题,因此我决定再多封装一个函数urandom_range():

`define module_urandom_define \
    string   path_str; \
    initial  path_str = $psprintf(path_str, "%m"); \
    \
    function integer urandom; \
        integer seed, i; \
        begin \
            seed = $urandom(); \
            for(i=path_str.len; i>=0; i=i-1)begin \
                seed = seed ^ path_str.getc(i); \
                seed = $urandom(seed); \
            end \
            urandom = $abs(seed); \
        end \
    endfunction \
    function integer urandom_range(); \
        input integer min, max; \
        integer seed, i; \
        begin \
            seed = $urandom(); \
            for(i=path_str.len; i>=0; i=i-1)begin \
                seed = seed ^ path_str.getc(i); \
                seed = $urandom(seed); \
            end \
            urandom_range = min + $abs(seed % (max - min)); \
        end \
    endfunction

module rand_test();
    int a, b;
    
    `module_urandom_define;
    
    initial begin
        for (int xx=0; xx<3; xx++)begin
            a = urandom();
            b = urandom_range(10,200);
            $display("%m.a = %0d, b = %0d", a, b);
            #100ns;
        end
    end
endmodule

打印结果如下:

seed = 100

      (Specify +UVM_NO_RELNOTES to turn off this notice)

harness.U_A.a = 1471040728, b = 108
harness.U_B.a = 1338760535, b = 168
harness.U_C.a = 1904052621, b = 99
harness.U_A.a = 1485973489, b = 33
harness.U_B.a = 1046096403, b = 72
harness.U_C.a = 968193349, b = 98
harness.U_A.a = 2088965477, b = 180
harness.U_B.a = 1045163969, b = 38
harness.U_C.a = 1987425378, b = 176
$finish called from file "../top/harness.sv", line 51.


seed = 200

      (Specify +UVM_NO_RELNOTES to turn off this notice)

harness.U_A.a = 786960723, b = 174
harness.U_B.a = 993085640, b = 171
harness.U_C.a = 175655228, b = 40
harness.U_A.a = 1769729887, b = 14
harness.U_B.a = 1756385201, b = 153
harness.U_C.a = 601311374, b = 39
harness.U_A.a = 1325663168, b = 103
harness.U_B.a = 1528493527, b = 50
harness.U_C.a = 2138557678, b = 17
$finish called from file "../top/harness.sv", line 51.

从结果来看,应该是满足之前提到的三个要求的:

1.同一个延时模块进行多次延时,每次随机值应该不同;

2.不同延时模块进行同一次延时,延时随机值应该不同;

3.随机延时值必须可控,即相同种子的两次用例结果应该是一致的;

结论

最后我交出的代码就是这样的:

`define module_urandom_define \
    string   path_str; \
    initial  path_str = $psprintf(path_str, "%m"); \
    \
    function integer urandom; \
        integer seed, i; \
        begin \
            seed = $urandom(); \
            for(i=path_str.len; i>=0; i=i-1)begin \
                seed = seed ^ path_str.getc(i); \
                seed = $urandom(seed); \
            end \
            urandom = $abs(seed); \
        end \
    endfunction \
    function integer urandom_range(); \
        input integer min, max; \
        integer seed, i; \
        begin \
            seed = $urandom(); \
            for(i=path_str.len; i>=0; i=i-1)begin \
                seed = seed ^ path_str.getc(i); \
                seed = $urandom(seed); \
            end \
            urandom_range = min + $abs(seed % (max - min)); \
        end \
    endfunction

module rand_test();
    int a, b;
    
    `module_urandom_define;
    
    initial begin
        for (int xx=0; xx<3; xx++)begin
            a = urandom();
            b = urandom_range(10,200);
            $display("%m.a = %0d, b = %0d", a, b);
            #100ns;
        end
    end
endmodule

 

你可能感兴趣的:(IC路上的探索,verilog,systemverilog,random)