(筆記) $dispaly()、$strobe()、$monitor() 、$fwrite()與blocking / nonblocking的關係 (SOC) (Verilog) (Debussy) (Verdi)
Abstract
除了看波型圖外,在寫Testbench時還可搭配Verilog本身所帶的一些函數做驗證,如$display()、$strobe()、$monitor()與$fwrite()等,這些函數在遇到blocking與nonblocking時,該如何使用才正確呢?它與Debussy / Verdi的nWave又有什麼關係呢?
Introduction
使用環境:ModelSim SE 6.3e + Debussy 5.4 v9
本文將討論以下主題:
1.Verilog的『stratified event queue』
2.blocking / nonblocking與$display()、$strobe()、$monitor()、$fwrite()的執行順序
3.Debussy / Verdi的nWave與blocking / nonblocking的關係
4.Conclusion
先來看一下摘自[1] Clifford E. Cummings 2000, Nonblocking Assignments in Verilog Synthesis, Coding Styles That Kills, Sunburst Design, Inc.這篇paper的一段範例,我稍加修改,另外加上了常用的$fwrite()與Debussy的$fsdbDumpvars()與$fsdbDumpvars()一起測試。
nb_schedule1.v / Verilog
1 module nb_schedule1;
2
3 reg a, b;
4 integer fp;
5
6 initial begin
7 fp = $fopen("log.txt","w");
8 a = 0;
9 b = 0;
10 #1;
11 a = 0;
12 b = 1;
13 a <= b;
14 b <= a;
15
16 $monitor("%0dns :\$monitor: a=%b b=%b" , $stime, a, b);
17 $display("%0dns :\$display: a=%b b=%b" , $stime, a, b);
18 $strobe ("%0dns :\$strobe : a=%b b=%b\n", $stime, a, b);
19 $fwrite(fp, "%0dns :\$fwrite : a=%b b=%b\n", $stime, a, b);
20 #0 $display("%0dns :#0 : a=%b b=%b" , $stime, a, b);
21
22
23 #1 $monitor("%0dns :\$monitor: a=%b b=%b" , $stime, a, b);
24 $display("%0dns :\$display: a=%b b=%b" , $stime, a, b);
25 $strobe ("%0dns :\$strobe : a=%b b=%b\n", $stime, a, b);
26 $fwrite(fp, "%0dns :\$fwrite : a=%b b=%b\n", $stime, a, b);
27 #0 $display("%0dns :#0 : a=%b b=%b" , $stime, a, b);
28
29
30 $fclose(fp);
31 end
32
33 initial begin
34 $fsdbDumpfile("nb_schedule1.fsdb");
35 $fsdbDumpvars(0, nb_schedule1);
36 end
37
38 endmodule
執行結果
# 1ns :$display: a=0 b=1
# 1ns :#0 : a=0 b=1
# 1ns :$monitor: a=1 b=0
# 1ns :$strobe : a=1 b=0
#
# 2ns :$display: a=1 b=0
# 2ns :#0 : a=1 b=0
# 2ns :$monitor: a=1 b=0
# 2ns :$strobe : a=1 b=0
1ns :$fwrite : a=0 b=1
2ns :$fwrite : a=1 b=0
Debussy的nWave
假如結果如你預期,恭喜你,那表示你的觀念非常清楚,如果不是,請你繼續往下看,我將會解釋這段範例程式。
Verilog的『stratified event queue』
我在(原創) 深入探討blocking與nonblocking (SOC) (Verilog)曾經討論過Verilog在Simulator內實際的運行方式,這裡再稍為提一下。
在軟體的世界,程式碼基本上是一行一行地執行,也就是再同一個時間點,只有一件事情會發生;但在硬體的世界裡,電路卻可以平行執行,也就是在同一個時間點,有很多事情可以同時發生。我們現在要在一個只能『循序處理』的軟體去『模擬』能『平行處理』的硬體,勢必有些特殊的機制才行。
在Simulator內,為了要模擬出硬體的平行處理機制,時間軸是個虛擬的時間軸,另外搭配了5個event queue,也就是說,當一個時間點的5個event queue內的所有程式碼都執行過後,時間軸才會自動加1,如此就很巧妙的『看起來』好像在同一個時間點作了很多事情,不過事實上對於軟體與CPU來說,依然是同一時間點只發生了一件事情。
在IEEE Verilog Standard中定義了這5個Event Queue,依序討論如下:
1.Active Events:
大部分的Verilog程式碼都在此執行,其中包括以綠色字表示的blocking asignments以及continuous assignments (也就是用assign寫的),至於nonblocking部分,只執行RHS (Right Hand Side),也就是只執行nonblocking右測的部分,唯一用紅色表示的是本文所要討論的$display()與$fwrite(),也是屬於Active Events,值的注意的是,在Active Events中的程式碼,若是同一個時間點,且在同一個sequential block,程式碼是依序執行,但是IEEE Verilog Standard並不保證在同一個時間點,不同的sequential block內的程式碼誰先執行,也就是說若你寫的程式碼都屬於Active Events所執行,但分屬不同的sequential block,且彼此互相依賴,就可能出現Race Condition的情形發生。
當在Active Events的程式碼執行完後,會自動移出Active Events。
2.Inactive Events
當Active Events執行完後,Inactive Events的程式碼會依序變成Active Events而執行之。
Inactive Events執行的是blocking assignments且帶#0 delay,因為是#0,所以理論上還是屬於同一個時間點,只是執行順序略晚於正常的blocking assignments,這也是為什麼遇到Race Condition時,常常加上#0就會正常,這就是因為#0是屬於Inactive Events,執行順序很明顯的在Active Events之後,因此不會有在Active Events內不保證誰先誰後執行,而造成Race Condition發生。
很多人認為#0會在每個時間點的最後執行,這是個錯誤的觀念,事實上,#0只是在Inactive Events,比Nonblocking Events還要早。
3.Nonblocking Events
當Inactive Events執行完後,接著Nonblocking Event的程式碼會依序變成Active Events而執行之。
Nonblocking Events執行的是nonblocking的LHS(Left Hand Side)部分,在此我們可以明確地發現,blocking發生在Active Events,而nonblocking雖然在Active Events已經執行了RHS,但真正完成LHS是在Nonblocking Events,也就是blocking會在nonblocking之前先完成。這也是為什麼同一個時間點,nonblocking可以讀到blocking所做的改變,但是blocking卻無法讀到nonblocking的改變,因為blocking已經先執行,必須要等到下一個clk edge才能讀到nonblocking所做的改變。
除此之外,還有一個常見的迷思也可在此澄清,我們都知道寫sequential logic時要用nonblocking寫,事實上用blocking也是可以寫,只是blocking寫很容易造成Race Condition,要很小心注意其先後順序,但為什麼用nonblocking寫就不會有Race Condition呢?
理由就在於nonblocking的RHS是在Active Events,而LHS是在Nonblocking Events,儘管彼此互相依賴,但Nonblocking Events很明顯地在Active Events之後,所以執行順序可以明確地確定,不像用blocking寫時,因為在Active Events內執行順序不確定,造成結果不可預期而產生Race Condition。
4.Monitor Events
當Nonblocking Events執行完後,接著Monitor Events的程式碼會依序變成Active Events而執行之。
我們都知道$display()無法觀察nonblocking所改變的結果,必須要使用$strobe()才可以,這是為什麼呢?因為$display()是屬於Active Events,而nonblocking完成於Nonblocking Events,明顯在Active Events之後,所以$display()跟本欄不到nonblocking所造成的改變,必須使用比Nonblocking Events更晚執行的Monitor Events中的$strobe()才可以觀察到nonblocking所改變的結果。
5.Verilog PLI Events
當Monitor Events執行完後,接著Verilog PLI Events的程式碼會依序變成Active Events而執行之。
Verilog PLI是允許你用C語言去寫一些Simulator的擴充功能,如Debussy / Verdi的$fsdbDumpfile()、$fsdbDumpvars()就是透過Verilog PLI,以前我ㄧ直搞不懂為什麼我用$display()與$fwrite()所dump的值與在Debussy / Verdi的nWave所看到的值不一樣,後來才發現原來$display()與$fwrite()是屬於Active Events,而$fsdbDumpfile()與$fsdbDumpvars是屬於Verilog PLI Events,比Nonblocking Events晚執行,這也是為什麼Debussy / Verdi可以顯示每個時間點nonblocking的值,而$display()與$fwrite()卻無法dump每個時間點的nonblocking,彼此的結果會差1個clock。
blocking / nonblocking與$display()、$strobe()、$monitor()、$fwrite()的執行順序
了解Verilog Simulator的『stratified event queue』之後,為了解釋nb_schedule1為什麼會有這樣的執行結果,我們將所有程式依照其在Event Queue的執行順序重新排列如下圖所示:
我們可以發現,儘管nonblocking與$monitor()、$strobe()寫在$display()與$fwrite()前面,但真正在執行時,Verilog Simulator還是會把它放在該放的Event Queue內,因為$display()與$fwrite()是放在Active Events,所以會印出a=0, b=1,接下來是Inactive Events的#0 $display(),也是顯示a=0, b=1,再過來是Nonblocking Events,執行 a <= b與b<=a,最後才是Monitor Events的$monitor()與$strobe(),因為nonblocking已經執行過,a與b的值已經改變,所以a=1, b=0,接下來因為a與b的值都不再改變,所以印出的值都不會再變。
Debussy / Verdi的nWave與blocking / nonblocking的關係
在nWave內,1 ns時,所顯示的是a=1, b=0,若你是用$display()與$fwrite()去dump資料時,會得到a=0, b=1,也就是說,$display()與$fwrite()看到的是blocking的值,而nWave看到的是nonblocking的值,因為$fsdbDumpfile()與$fsdbDumpvars()是在Verilog PLI Events,執行點是在Nonblocking Events之後。
這也是為什麼有些工程師在寫sequential logic時,會在nonblocking加上#1,為的就是在Debussy / Verdi下看波形圖時比較好看,而Synthesizer會自動忽略nonblocking的#1,所以結果不會受影響。
Conclusion
1.因為blocking與nonblocking的關係,所以程式碼執行的順序不見的會依照我們所寫的順序,而是要依照『stratified event queue』的執行順序執行。
2.若皆屬Active Events的程式碼,在同一個時間點,在同一個sequential block內會依序執行,但在不同的sequential block內就無法保證誰會先執行,若彼此依賴有相關,就有可能造成Race Condition。
3.#0的blocking會比較晚一點執行,可以暫時解決Race Condition所造成的問題,不過這不是一個推薦的coding style,建議找出Race Condition的真正原因,而改用nonblocking去解決。
4.#0並不是最後執行,只是在Inactive Events而已,比Active Events稍晚,但還是比Nonblocking Events早。
5.Sequential logic也是可以用blocking寫,只是很容易造成Race Condition,必須很小心的安排blocking的執行順序,不是一個推薦的coding style。
6.$display()只能觀察到blocking,無法觀察到nonblocking,若要觀察nonblocking,要使用$strobe()與$monitor()。
7.$fsdbDumpfile()與$fsdbDumpvars()比$strobe()與$monitor()還晚執行,所以在Debussy / Verdi總是觀察到nonblocking的值,會比使用$display()與$fwrite()早1個clock。
完整程式碼下載
nb_schedule1.7z
See Also
(筆記)如何使用blocking與nonblocking assignment? (SOC) (Verilog)
(原創) 深入探討blocking與nonblocking (SOC) (Verilog)
Reference
[1] Clifford E. Cummings 2000, Nonblocking Assignments in Verilog Synthesis, Coding Styles That Kills, Sunburst Design, Inc.
全文完。