在上一篇文章中,我們討論并得出了一種構(gòu)建可控隨機(jī)的方案:
【芯片驗(yàn)證】異步電路碎碎念(十)在靜態(tài)對象內(nèi)構(gòu)建可控隨機(jī)·方案一
這篇文章呢我們討論另外一種方案,為啥有方案了還要嘗試其他的方案呢?我自己對$random這個(gè)函數(shù)相當(dāng)?shù)牟恍湃,這個(gè)函數(shù)不受seed控制到低隨機(jī)時(shí)候從哪找的種子我也不太清楚,萬一跟地域、時(shí)間、服務(wù)器、工具等特性有關(guān)系,環(huán)境一遷移導(dǎo)致隨機(jī)無法復(fù)現(xiàn)怎么辦呢。所以我希望有一種原理明明白白的可控隨機(jī)方案,當(dāng)然這里并不是說$random+$urandom不能用哈,實(shí)際上我知道的很多地方就是直接用這個(gè)的。
原理明明白白的可控隨機(jī)方案怎么來呢?既然不信任$random那必然是從$urandom入手看了,還記得$random的問題是啥來著吧?不能感知靜態(tài)模塊的不同例化路徑差異。那么讓他感知一下例化路徑差異不就好了嘛,怎么感知這個(gè)事,相信大家對這個(gè)字符都很熟悉:%m,對就是在打印里用的那個(gè)字符。這個(gè)字符的作用是打印當(dāng)前模塊(或獨(dú)立命名域)的例化路徑,打印里的前半部分就是靠這個(gè)打印出來的:
$display("%m new_rand_value = 'h%0h", new_rand_value);
----->
testbench.u_rand_test1 new_rand_value = 'hd7也就是說,一個(gè)靜態(tài)模塊自身只要例化在了DUT中,那么我們就能夠獲取到其例化路徑,以字符串的方式我們來獲取以下路徑:string path_str;
initial begin
path_str = $psprintf(path_str, "%m");
$display("inst path is %s", path_str);
end對應(yīng)的打印結(jié)果是:inst path is testbench.u_rand_test0
inst path is testbench.u_rand_test1好的那么也就是說,雖然$urandom不能感知例化路徑,但是模塊自身能夠感知,因此只要把這個(gè)路徑引入到$urandom中去就可以了。這時(shí)有人可能就要問了,%m是字符串,$urandom的種子是數(shù)字啊,這要怎么引進(jìn)去呢?年輕了不是,誰說%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你看根據(jù)例化路徑得到的數(shù)字這不就出來了嘛,不過仔細(xì)觀察一下會(huì)發(fā)現(xiàn),這路徑差異比較近,得出的數(shù)字差異也太小了,這個(gè)數(shù)給到$urandom里去隨機(jī)出來的值別在差不多那就不好了。所以直接把這個(gè)值給$urandom肯定不行,那么怎么和$urandom結(jié)合使用能夠起到作用呢?下面是我選擇一種方式: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給一個(gè)初始隨機(jī)值,不同模塊是一樣的沒關(guān)系,這個(gè)值結(jié)合后面路徑的微小差異就可以成就“蝴蝶效應(yīng)”。之后從尾部到頭部路徑字符,因?yàn)椴町惣性谖膊克砸獜奈膊块_始,str.getc(i)獲取字符串對應(yīng)位置字符的ASCII碼,再跟path_int取異或,累計(jì)擴(kuò)大差異性,而后再加入一個(gè)$urandom進(jìn)一步擴(kuò)大差異性。循環(huán)之后得到了擴(kuò)大差異之后的數(shù),此時(shí)最后的結(jié)果可能是負(fù)數(shù),這不是我們所需要的因此通過$abs函數(shù)全部取正。經(jīng)過這些操作后,咱們來看下進(jìn)行隨機(jī)后的結(jié)果:
inst path is testbench.u_rand_test0, path_int = 'h66b884a6
inst path is testbench.u_rand_test1, path_int = 'h49aee46多測試幾個(gè)種子: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對其中的一個(gè)種子結(jié)果進(jìn)行復(fù)現(xiàn):inst path is testbench.u_rand_test0, path_int = 'h21669974
inst path is testbench.u_rand_test1, path_int = 'h19e00dfa
tc seed = 62302096穩(wěn)定復(fù)現(xiàn),那么這種方法顯然是符合我們預(yù)期的。其實(shí)后來我想了下,如果想擴(kuò)大差異,是不是多嵌套幾個(gè)$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 = 38445033emm果然是不行,所以還是用上面提供的方法吧。那么下一個(gè)問題就來了,這么用起來呢,總不能每次需要隨機(jī)時(shí)候都寫這么一大片的代碼吧?答案也很簡單,封裝成一個(gè)函數(shù)不就好了嘛:
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測試一下結(jié)果: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看起來是沒有什么問題的。那么繼續(xù)往下探索,即使封裝為函數(shù)了,難不成每個(gè)要進(jìn)行隨機(jī)的模塊里面都寫一次這個(gè)函數(shù)嗎?當(dāng)然是不需要的,有兩種思路可以處理這個(gè)問題,一是將函數(shù)定義為全局可見的方法,而path_str作為作為函數(shù)的輸入,這樣調(diào)用時(shí)傳參本模塊的path_str就可以了。第二種思路是進(jìn)行宏封裝,我選擇的是第二種思路因?yàn)槲掖_實(shí)不喜歡在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而后在需要進(jìn)行隨機(jī)的模塊里RTL前面的地方找個(gè)犄角旮旯寫一下宏,后面就可以:`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事情到這里基本就已經(jīng)解決了,我們以自定義的urandom()函數(shù)平替了系統(tǒng)的$urandom()函數(shù),達(dá)到了在靜態(tài)模塊中等價(jià)于$urandom()的可控和差異化隨機(jī)功能。最后再來拓展實(shí)現(xiàn)另外一個(gè)功能,我們想隨機(jī)一個(gè)數(shù)值那是不是經(jīng)常會(huì)使用$urandom_range(),可是在靜態(tài)模塊中怎么做到這一點(diǎn)呢?也很簡單,加一個(gè)宏不就可以了: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打印結(jié)果為: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如果想要小數(shù)、負(fù)數(shù)和浮點(diǎn)數(shù)怎么辦呢?就根據(jù)這套方法來拓展下就可以。
系列文章入口——
【芯片設(shè)計(jì)】SoC 101(一):緒論 | 【芯片設(shè)計(jì)】FIFO漫談(零)從無處不在的FIFO開始說起 | 【芯片設(shè)計(jì)】計(jì)算機(jī)體系結(jié)構(gòu)(一)虛擬內(nèi)存 | 【芯片設(shè)計(jì)】深入理解AMBA總線(零)緒論
| 【芯片設(shè)計(jì)】握手協(xié)議的介紹與時(shí)序說明 | 【芯片設(shè)計(jì)】復(fù)位那些小事 —— 復(fù)位消抖 | 【芯片設(shè)計(jì)】快速入門數(shù)字芯片設(shè)計(jì)(一)Introduction | 【芯片驗(yàn)證】UVM源碼計(jì)劃(零)下定決心讀源碼前的自測環(huán)節(jié)
| 【芯片設(shè)計(jì)】異步電路碎碎念(一) 到底什么是異步電路
| 【芯片設(shè)計(jì)】從RTL到GDS(一):Introduction
| 其他文章鏈接——
【芯片驗(yàn)證】sva_assertion: 15道助力飛升的斷言練習(xí) | 【芯片驗(yàn)證】可能是RTL定向驗(yàn)證的巔峰之作 | 【芯片驗(yàn)證】RTL仿真中X態(tài)行為的傳播 —— 從xprop說起 | 【芯片驗(yàn)證】年輕人的第一個(gè)systemVerilog驗(yàn)證環(huán)境全工程與解析 |
【芯片設(shè)計(jì)】verilog中有符號(hào)數(shù)和無符號(hào)數(shù)的本質(zhì)探究
| 【芯片設(shè)計(jì)】論RTL中always語法的消失術(shù) | 【芯片設(shè)計(jì)】代碼即注釋,注釋即代碼 | 【芯片設(shè)計(jì)】700行代碼的risc處理器你確實(shí)不能要求太多了 |
入職芯片開發(fā)部門后,每天摸魚之外的時(shí)間我們要做些什么呢 | 如何計(jì)算系統(tǒng)的outstanding 和 burst length? | 芯片搬磚日常·逼死強(qiáng)迫癥的關(guān)鍵詞不對齊事件 | 熟人社會(huì)里,一群沒有社會(huì)價(jià)值的局外人 |
|