Verilog -- 阻塞與非阻塞的仿真與綜合


Verilog -- 阻塞與非阻塞的仿真與綜合

參考 Clifford E. Cummings, Sunburst Design, Inc. "Nonblocking Assignments in Verilog Synthesis, CodingStyles That Kill!"
前段時間為了探究阻塞和非阻塞的進行過簡單測試,當時覺得對阻塞與非阻塞的理解已經可以了,直到發現了Sunburst的這篇論文(這個機構真的很nb,很多verilog,SV的經典教程都出自它),才算真的明白了阻塞和非阻塞的原理。

基本概念

RHS(right-hand-side): 指等式右邊的表達式或者變量(RHS expression or RHS variable)
LHS(left-hand-side):指等式左邊的表達式或者變量(RHS expression or RHS variable)

競爭條件:Verilog競爭條件發生在當兩個或多個語句被安排在相同的仿真時間步長中執行時,當語句執行的順序改變時,會產生不同的結果。

阻塞賦值:當沒有其它的Verilog描述可以打斷“阻塞賦值”時,操作將會估計RHS的值並完成賦值。“阻塞”即是說在當前的賦值完成前阻塞其它類型的賦值任務。

  • “阻塞賦值“可以看作一步進程(one-step process): 當沒有其它可以打斷賦值的描述時,估計等式右邊(RHS)的指並賦予左邊(LHS)。 在同一個always塊里面,阻塞賦值結果將一直持續下去直到賦值結束。

非阻塞賦值:在一個時間步(time step)的開始估計RHS expression的值並在這個時間步(time step)結束時用等式右邊的值更新取代LHS。在估算RHS expression和更新LHS expression的中間時間段,其它的對LHS expression的非阻塞賦值可以被執行。即是說“非阻塞賦值”從估計RHS開始並不阻礙執行其它的Verilog描述。

  • “非阻塞賦值”可以看作二步進程(one-step process):
      1. 在時間步開始估計RHS;
      1. 在時間步結束時更新LHS;

Verilog層積事件列(stratified event queue)

層積事件列(stratified event queue)是一個概念模型,每個仿真器都有不同的實現方式。“層積事件列”邏輯上划分為四個不同的隊列,分別用於當前的仿真時間和未來的仿真時間。

活躍事件列(Active Events)是(最多的被預備執行的Verilog事件)包括:

  • 阻塞賦值
  • 連續賦值
  • $display命令
  • 估出“非阻塞賦值”的RHS expressions
  • 計算初原元件(primitive)的輸入和更改實例(instance)的輸出

注意“非阻塞賦值”的LHS不在“活躍事件列”里更新。

非活躍事件列(Inactive event): #0延時的阻塞賦值。

非阻塞賦值更新事件列(Thenonblocking assign updates event queue)即是“非阻塞賦值”的LHS expression被安排更新賦值的那些事件。在一個仿真時間步(simulation time step)的開始,“RHS expression 的估值”與其它被激活事件是以任意的次序進行的。

monitor事件列是由那些被安排的“\(strobe”和“\)monitor”顯示命令帶來的。$strobe 和 $monitor 用於顯示一個仿真時間步結束時變量更新后的值(這時該仿真時間步里所有的賦值分配都已經完成)

注意:事件可以被加到任意的事件列里(由IEEE標准強制約束的),但是只可能從“活躍事件列”里被移出。其它事件列里的事件最終總是要成為“激活事件”的(或者提升為“活躍事件)。

示例:自觸發的always塊

---- Non-Self Triggered ------
module osc1 (clk);
output clk;
reg clk;
initial #10 clk = 0;
always @(clk) #10 clk = ~clk;
endmodule
----------------------------------------

------- Self Triggered ------
module osc2 (clk);
output clk;
reg clk;
initial #10 clk = 0;
always @(clk) #10 clk <= ~clk;
endmodule
---------------------------------------- 
  • 第一種寫法使用阻塞賦值。這樣的話RHS估值和LHS賦值是不被打斷地執行,此時不允許其他語句的干擾。阻塞賦值必須在@(clk)邊沿觸發到來時刻之前完成。即在邊沿事件之前,對clk的賦值已經完成。所以, 沒有“觸發事件”(@(clk))來觸發always塊里面的觸發事件。因此不能自觸發。
  • 第二種寫法使用非阻塞賦值,在第一個@(clk)觸發之后按照事件的執行順序可以做如下排序:
    • 10ns時進入觸發always塊,執行always中語句
    • 非阻塞賦值的RHS expression被估值,並且LHS值被送入“非阻塞賦值更新”事件列。
    • RHS估值完畢,等待LHS進行
    • 非阻塞LHS的值更新,同時又遇到了@(clk)觸發語句,always塊再次對clk的值變化產生反應。@(clk)再一次觸發。
    • 非阻塞賦值的RHS再次估值...
      因此可以進行自觸發。

阻塞和非阻塞的綜合問題

一般都建議使用阻塞邏輯描述組合邏輯,非阻塞邏輯描述時序邏輯。
但阻塞賦值就一定不能描述時序邏輯嗎?答案是否定的,如果仔細安排阻塞賦值的順序,也是可以進行時序電路的描述,並能通過仿真和綜合(或者可以通過綜合)。

以一個三級流水線為例:

首先我們直到,采用非阻塞賦值,仿真和綜合肯定沒問題。那么如果使用阻塞賦值呢?

always @(posedge clk) begin
    q1 = d; 
    q2 = q1;
    q3 = q2;
  end

采用上面這種方式,肯定不是我們要的結果,而是將會只綜合出一個寄存器。這肯定是不可行的。
而如果換一下順序:

always @(posedge clk) begin
    q3 = d2; 
    q2 = q1;
    q1 = q;
  end

阻塞賦值被仔細地排序,以使仿真能夠像寄存器一樣正確地工作。這樣的寫法無論是仿真還是綜合都是正確的,但是不建議這樣做。
另外,如果將一個always塊拆分成三個寫,也就是:

always @(posedge clk) q1=d;
always @(posedge clk) q2=q1;
always @(posedge clk) q3=q2;

這樣Verilog標准允許以任意的次序來仿真執行3個always塊,這也許會使得該流水線仿真結果產生錯誤,因為這產生了Verilog競爭條件。由不同的always塊執行順序會產生不同的結果。盡管這樣,它的綜合結果將是正確的!這就意味着綜合前仿真綜合后仿真不匹配。

另外,在組合邏輯中,可以用非阻塞賦值來描述嗎?
答案是肯定的,但是也需要一番波折,還是以一個例子來看:

always @(a or b or c or d) begin 
   tmp1 <= a & b; 
   tmp2 <= c & d; 
   y    <= tmp1 | tmp2; 
 end

上面這段組合邏輯采用非阻塞賦值來描述,可以發現,y的值沒法被正確更新,因為當abcd改變時tmp與y賦值的RHS同時估值,所以y的LHS更新時使用的還是舊的tmp,導致功能不正確。要解決這一問題也不麻煩:

always @(a or b or c or d or tmp1 or tmp2) begin 
   tmp1 <= a & b; 
   tmp2 <= c & d; 
   y    <= tmp1 | tmp2; 
 end

在“非阻塞賦值更新事件隊列”中當非阻塞賦值更新LHS變量時,always塊將會“自觸發”並使用最新的tmp1和tmp2來更新y輸出。現在y輸出值正確了,但時代價是因為增加了兩條 passes貫穿整個always塊。使用太多的pass來貫穿always塊等於降低仿真器的性能。因此雖然能獲得正確功能,但不建議使用。

非阻塞賦值和$display

非阻塞賦值在$display命令之后才被更新賦值,因此,display如果緊跟非阻塞賦值則無效

initial $monitor("\$monitor: a = %b", a);
initial begin
 $strobe ("\$strobe : a = %b", a); 
 a = 0; 
 a <= 1;
 $display ("\$display: a = %b", a);
 #1 $finish;
 end

輸出:
\(display: a = 0 \)monitor: a = 1
$strobe : a = 1

0延時

在層積事件列中可以發現,#0延時有一個專門的非活躍事件列,因此,零延遲#0 使得賦值事件處於“非激活事件列”,也就是非阻塞賦值LHS更新之前。

initial begin
 a = 0;
 b = 1;
 a <= b;
 b <= a;
 $monitor ("%0dns: \$monitor: a=%b b=%b", $stime, a, b);
 $display ("%0dns: \$display: a=%b b=%b", $stime, a, b);
 $strobe ("%0dns: \$strobe : a=%b b=%b\n", $stime, a, b);
 #0 $display ("%0dns: #0 : a=%b b=%b", $stime, a, b); 
end

結果:
0ns: $display: a=0 b=1
0ns: #0 : a=0 b=1
0ns: $monitor: a=1 b=0
0ns: $strobe : a=1 b=0

從上面可以看到,使用#0延時后,display語句進入非活躍事件,在非阻塞賦值之前執行。

建議

  • 當為時序邏輯建模,使用“非阻塞賦值”。
  • 當為鎖存器(latch)建模,使用“非阻塞賦值”。
  • 當用always塊為組合邏輯建模,使用“阻塞賦值”
  • 當在同一個always塊里面既為組合邏輯又為時序邏輯建模,使用“非阻塞賦值”。
  • 不要在同一個always塊里面混合使用“阻塞賦值”和“非阻塞賦值”。
  • 不要在兩個或兩個以上always塊里面對同一個變量進行賦值。
  • 使用$strobe以顯示已被“非阻塞賦值”的值。
  • 不要使用#0延遲的賦值。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM