作者简介
尼德兰的喵,数字芯片前端,ai与智驾赛道打工人。常年在csdn、知乎、稀土掘 金、博客园、github、gitee等各大论坛与社区打地铺久住,公众号“芯时代青年”。
讨论另外一种方案,为啥有方案了还要尝试其他的方案呢?我自己对$random 这个函数相当的不信任,这个函数不受 seed 控制到低随机时候从哪找的种子我也不太清楚,万一跟地域、时间、服务器、工具等特性有关系,环境一迁移导致随机无法复现怎么办呢。所以我希望有一种原理明明白白的可控随机方案,当然这里并不是说$random+$urandom 不能用哈,实际上我知道的很多地方就是直接用这个的。
原理明明白白的可控随机方案怎么来呢?既然不信任$random 那必然是从$urandom 入手看了,还记得$random 的问题是啥来着吧?不能感知静态模块的不同例化路径差异。那么让他感知一下例化路径差异不就好了嘛,怎么感知这个事,相信大家对这个字符都很熟悉:%m,对就是在打印里用的那个字符。这个字符的作用是打印当前模块(或独立命名域)的例化路径,打印里的前半部分就是靠这个打印出来的:
$display("%m new_rand_value = 'h%0h", new_rand_value);
----->
testbench.u_rand_test1 new_rand_value = 'hd7
也就是说,一个静态模块自身只要例化在了 DUT 中,那么我们就能够获取到其例化路径,以字符串的方式我们来获取以下路径:
string path_str;
initial begin
path_str = $psprintf(path_str, "%m");
$display("inst path is %s", path_str);
end
对应的打印结果是:
inst path is testbench.u_rand_test0
inst path is testbench.u_rand_test1
好的那么也就是说,虽然$urandom 不能感知例化路径,但是模块自身能够感知,因此只要把这个路径引入到$urandom 中去就可以了。这时有人可能就要问了,%m 是字符串,$urandom 的种子是数字啊,这要怎么引进去呢?年轻了不是,谁说%m 就一定是字符串了:
string path_str;
int path_int;
initial begin
path_str = $psprintf(path_str, "%m");
path_int = path_str;
$display("inst path is %s, path_int = 'h%0h", path_str,
path_int);
end
----->
inst path is testbench.u_rand_test0, path_int = 'h65737430
inst path is testbench.u_rand_test1, path_int = 'h65737431
你看根据例化路径得到的数字这不就出来了嘛,不过仔细观察一下会发现,这路径差异比较近,得出的数字差异也太小了,这个数给到$urandom 里去随机出来的值别在差不多那就不好了。所以直接把这个值给$urandom 肯定不行,那么怎么和$urandom 结合使用能够起到作用呢?下面是我选择一种方式:
string path_str;
int path_int;
initial begin
path_str = $psprintf(path_str, "%m");
path_int = $urandom();
for(int i=path_str.len; i>=0; i=i-1)begin
path_int = path_int ^ path_str.getc(i);
path_int = $urandom(path_int);
end
path_int = $abs(path_int);
$display("inst path is %s, path_int = 'h%0h", path_str, path_int);
end
具体解释一下这些代码,先把 path_int 通过$urandom 给一个初始随机值,不同模块是一样的没关系,这个值结合后面路径的微小差异就可以成就“蝴蝶效应”。之后从尾部到头部路径字符,因为差异集中在尾部所以要从尾部开始,str.getc(i)获取字符串对应位置字符的ASCII 码,再跟 path_int 取异或,累计扩大差异性,而后再加入一个$urandom 进一步扩大差异性。循环之后得到了扩大差异之后的数,此时最后的结果可能是负数,这不是我们所需要的因此通过$abs 函数全部取正。
经过这些操作后,咱们来看下进行随机后的结果:
inst path is testbench.u_rand_test0, path_int = 'h66b884a6
inst path is testbench.u_rand_test1, path_int = 'h49aee46
多测试几个种子:
inst path is testbench.u_rand_test0, path_int = 'h21669974
inst path is testbench.u_rand_test1, path_int = 'h19e00dfa
tc seed = 62302096
inst path is testbench.u_rand_test0, path_int = 'h19bc271c
inst path is testbench.u_rand_test1, path_int = 'h7eef9304
tc seed = 81054216
inst path is testbench.u_rand_test0, path_int = 'h268220ce
inst path is testbench.u_rand_test1, path_int = 'h44c8d731
tc seed = 53991314
对其中的一个种子结果进行复现:
inst path is testbench.u_rand_test0, path_int = 'h21669974
inst path is testbench.u_rand_test1, path_int = 'h19e00dfa
tc seed = 62302096
稳定复现,那么这种方法显然是符合我们预期的。其实后来我想了下,如果想扩大差异,是不是多嵌套几个$urandom 就可以了,比如$urandom($urandom($urandom($urandom(path_int)))),于是试了试:
string path_str;
int path_int;
initial begin
path_str = $psprintf(path_str, "%m");
path_int = path_str;
path_int =
$urandom($urandom($urandom($urandom(path_int))));
$display("inst path is %s, path_int = 'h%0h", path_str, path_int);
end
inst path is testbench.u_rand_test0, path_int = 'h6a6ed13
inst path is testbench.u_rand_test1, path_int = 'h86b6f7a1
tc seed = 0
inst path is testbench.u_rand_test0, path_int = 'h6a6ed13
inst path is testbench.u_rand_test1, path_int = 'h86b6f7a1
tc seed = 38445033
emmm 果然是不行,所以还是用上面提供的方法吧。那么下一个问题就来了,这么用起来呢,总不能每次需要随机时候都写这么一大片的代码吧?
答案也很简单,封装成一个函数不就好了嘛:
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
initial begin
int rand_test;
rand_test = urandom();
$display("%m, rand_test = %0d", rand_test);
end
测试一下结果:
testbench.u_rand_test0.unnamed$$_0, rand_test =
1641770003
testbench.u_rand_test1.unnamed$$_0, rand_test = 621788312
tc name = sanity
tc seed = 59908161
testbench.u_rand_test0.unnamed$$_0, rand_test =
1271321994
testbench.u_rand_test1.unnamed$$_0, rand_test = 588367474
tc name = sanity
tc seed = 73017818
testbench.u_rand_test0.unnamed$$_0, rand_test = 97687901
testbench.u_rand_test1.unnamed$$_0, rand_test = 704389240
tc name = sanity
tc seed = 75798405
看起来是没有什么问题的。那么继续往下探索,即使封装为函数了,难不成每个要进行随机的模块里面都写一次这个函数吗?当然是不需要的,有两种思路可以处理这个问题,一是将函数定义为全局可见的方法,而 path_str 作为作为函数的输入,这样调用时传参本模块的path_str 就可以了。第二种思路是进行宏封装,我选择的是第二种思路因为我确实不喜欢在 RTL 里有太多冗余的代码。
在宏定义文件中做宏:
`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
而后在需要进行随机的模块里 RTL 前面的地方找个犄角旮旯写一下宏,后面就可以:
`module_urandom_define
initial begin
int rand_test;
rand_test = urandom();
$display("%m test1, rand_test = %0d", rand_test);
rand_test = urandom();
$display("%m test2, rand_test = %0d", rand_test);
end
然后就可以使用下看看了:
testbench.u_rand_test0.unnamed$$_0 test1, rand_test =
1970541737
testbench.u_rand_test0.unnamed$$_0 test2, rand_test =
128592474
testbench.u_rand_test1.unnamed$$_0 test1, rand_test =
849448805
testbench.u_rand_test1.unnamed$$_0 test2, rand_test =
1978420876
tc name = sanity
tc seed = 17411917
testbench.u_rand_test0.unnamed$$_0 test1, rand_test =
887806234
testbench.u_rand_test0.unnamed$$_0 test2, rand_test =
216253277
testbench.u_rand_test1.unnamed$$_0 test1, rand_test =
1150158848
testbench.u_rand_test1.unnamed$$_0 test2, rand_test =
1381425996
tc name = sanity
tc seed = 50449658
testbench.u_rand_test0.unnamed$$_0 test1, rand_test =
439486355
testbench.u_rand_test0.unnamed$$_0 test2, rand_test =
1519906787
testbench.u_rand_test1.unnamed$$_0 test1, rand_test =
219267827
testbench.u_rand_test1.unnamed$$_0 test2, rand_test =
133804852
tc name = sanity
tc seed = 64935128
事情到这里基本就已经解决了,我们以自定义的 urandom()函数平替了系统的$urandom()函数,达到了在静态模块中等价于$urandom()的可控和差异化随机功能。最后再来拓展实现另外一个功能,我们想随机一个数值那是不是经常会使用$urandom_range(),可是在静态模块中怎么做到这一点呢?也很简单,加一个宏不就可以了:
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
测试一下:
initial begin
int rand_test;
rand_test = urandom_range(0,100);
$display("%m test1, rand_test = %0d", rand_test);
rand_test = urandom_range(100,1000);
$display("%m test2, rand_test = %0d", rand_test);
end
打印结果为:
testbench.u_rand_test0.unnamed$$_0 test1, rand_test = 75
testbench.u_rand_test0.unnamed$$_0 test2, rand_test = 892
testbench.u_rand_test1.unnamed$$_0 test1, rand_test = 71
testbench.u_rand_test1.unnamed$$_0 test2, rand_test = 461
tc name = sanity
tc seed = 16870716
testbench.u_rand_test0.unnamed$$_0 test1, rand_test = 63
testbench.u_rand_test0.unnamed$$_0 test2, rand_test = 944
testbench.u_rand_test1.unnamed$$_0 test1, rand_test = 54
testbench.u_rand_test1.unnamed$$_0 test2, rand_test = 172
tc name = sanity
tc seed = 47491380