【芯片验证】异步电路碎碎念(十一)在静态对象内构建可控随机·方案二

乐活   2024-11-11 12:01   北京  

在上一篇文章中,我们讨论并得出了一种构建可控随机的方案:

【芯片验证】异步电路碎碎念(十)在静态对象内构建可控随机·方案一

这篇文章呢我们讨论另外一种方案,为啥有方案了还要尝试其他的方案呢?我自己对$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
emm果然是不行,所以还是用上面提供的方法吧。那么下一个问题就来了,这么用起来呢,总不能每次需要随机时候都写这么一大片的代码吧?

答案也很简单,封装成一个函数不就好了嘛:

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
如果想要小数、负数和浮点数怎么办呢?就根据这套方法来拓展下就可以。

系列文章入口——

【芯片设计】SoC 101(一):绪论
【芯片设计】FIFO漫谈(零)从无处不在的FIFO开始说起
【芯片设计】计算机体系结构(一)虚拟内存

【芯片设计】深入理解AMBA总线(零)绪论

【芯片设计】握手协议的介绍与时序说明
【芯片设计】复位那些小事 —— 复位消抖
【芯片设计】快速入门数字芯片设计(一)Introduction
【芯片验证】UVM源码计划(零)下定决心读源码前的自测环节
【芯片设计】异步电路碎碎念(一) 到底什么是异步电路
【芯片设计】从RTL到GDS(一):Introduction
其他文章链接——
【芯片验证】sva_assertion: 15道助力飞升的断言练习
【芯片验证】可能是RTL定向验证的巅峰之作
【芯片验证】RTL仿真中X态行为的传播 —— 从xprop说起
【芯片验证】年轻人的第一个systemVerilog验证环境全工程与解析
【芯片设计】verilog中有符号数和无符号数的本质探究
【芯片设计】论RTL中always语法的消失术
【芯片设计】代码即注释,注释即代码
【芯片设计】700行代码的risc处理器你确实不能要求太多了
入职芯片开发部门后,每天摸鱼之外的时间我们要做些什么呢
如何计算系统的outstanding 和 burst length?
芯片搬砖日常·逼死强迫症的关键词不对齐事件
熟人社会里,一群没有社会价值的局外人

芯时代青年
专心数字前端全流程,芯时代有为青年的自我修养
 最新文章