阻塞賦值與非阻塞賦值


 

     過程賦值:用於對reg型變量賦值,改變寄存器的值或為以后排定改變。

     語法

{阻塞性(blocking)賦值}

     RegisterLValue = [ TimingControl] Expression;

{非阻塞性(non-blocking)賦值}

RegisterLValue <= [ TimingControl] Expression;

     阻塞:在本語句中“右式計算”和“左式更新”完全完成之后,才開始執行下一條語句;

     非阻塞:當前語句的執行不會阻塞下一語句的執行。

     過程賦值右邊的表達式在賦值執行的時候算出。如果沒有內部賦值延時,左邊的寄存器由於阻塞性賦值將立即更新,而非阻塞性賦值則下一個仿真周期才更新左邊的寄存器。如果有內部賦值延時,左邊的寄存器只在發生內部賦值延時后更新。

     例如對於阻塞性賦值:

  • 當執行賦值語句時,算出右邊的表達式的值,但左邊的值不更新,直到產生定時控制事件或延時(稱為“內部賦值延時”)。
  • 直到左邊被更新后(即經過內部賦值延時后)阻塞性賦值才完成。begin-end塊中的下一個語句直到此時才開始執行。

    結合編程語句區分如下:

    • 非阻塞(non-blocking) 賦值語句(b<= a):     

       - 塊內的賦值語句同時賦值;    

       - b的值被賦成新值a的操作, 是與塊內其他賦值語句同時完成的;    

       - 建議在可綜合風格的模塊中使用非阻塞賦值。

     • 阻塞(blocking) 賦值語句(b = a):    

       - 完成該賦值語句后才能做下一句的操作;    

       - b的值立刻被賦成新值a;    

       - 硬件沒有對應的電路,因而綜合結果未知。

     阻塞賦值和非阻塞賦值如果使用不當會存在冒險和競爭現象,必須按照下面兩條准則:

     1)在描述組合邏輯的always塊中使用阻塞賦值,則綜合組合邏輯的電路結構;

     2)在描述時序邏輯的always塊中使用非阻塞賦值,則綜合時序邏輯的電路結構。

     在時鍾沿觸發的always塊中,如果用非阻塞賦值語句對reg型變量賦值;或者當reg型變量經過多次循環其值仍保持不變,則會在綜合中生成觸發器。若不想生成觸發器,而希望用reg型變量生成組合邏輯,則應使用電平觸發。在組合邏輯中,阻塞賦值只與電平有關,往往和觸發沿沒有關系,可以將其看成並行執行的;在時序邏輯中,非阻塞賦值是並行執行的;因此,優秀的HDL設計,其內部語句也是並行執行的。

 

     非阻塞賦值與阻塞賦值示例:

     1. 非阻塞賦值方式

 1 module nonblocking(input clk, 
 2                    input a,
 3                    output reg c); 
 4 reg b;
 5 
 6 always @(posedge clk) begin
 7     b <= a;
 8     c <= b;
 9 end
10 
11 endmodule
non-blocking

     編譯結果顯示使用了兩個寄存器。Clock "clk" Internal fmax is restricted to 420.17 MHz between source register "b" and destination register "c~reg0" (the specified clock operates at the specified fMAX between the specified source pin or register and the specified destination pin or register). 器件內部的fmax(Internal fmax)分析器件中同步元件(如寄存器)到同步元件之間的延時,然后計算出最高頻率。

     RTL原理圖如下所示:

 

     波形功能和時序仿真分別如下所示:

     非阻塞賦值在塊結束時才完成賦值操作。c的值比b的值落后一個時鍾周期(功能仿真表現為b→c,時序仿真表現為b→c~reg0)。時序仿真中,第一個時鍾沿15.743ns后(時鍾周期 + tco = 10ns+5.743ns)輸出c。

 

     2. 阻塞賦值方式

 1 module blocking(input clk, 
 2                 input a,
 3                 output reg c); 
 4 reg b;
 5 
 6 always @(posedge clk) begin
 7     b = a;  //兩句交換,則等效於非阻塞賦值方式
 8     c = b;
 9 end
10 
11 endmodule
blocking

     編譯結果顯示使用了1個寄存器,所以時序列表中沒有Clock Setup: 'clk'一項。

     RTL原理圖如下所示:

 

     波形功能和時序仿真分別如下所示:

     阻塞賦值在該語句結束時就完成賦值操作。在一個塊語句中,如果有多條阻塞賦值語句,在前面的賦值語句沒有完成之前,后面的語句就不能被執行,就像被阻塞了一樣,因此稱為阻塞賦值方式。可以看到,b被優化掉了,因為這里c的值與b的值一樣!

     在阻塞賦值語句中,賦值次序非常重要,而在非阻塞賦值語句中,賦值的次序並不重要。

     仿真器首先按照仿真時間對事件進行排序,然后再在當前仿真時間里按照事件的優先級順序進行排序。活躍事件是優先級最高的事件。在活躍事件之間,它們的執行順序是隨機的。阻塞賦值(=)、連續賦值(assign)以及非阻塞賦值的右式計算等都屬於活躍事件。

 

     下面再通過一個典型案例,進一步說明阻塞賦值和非阻塞賦值的區別。

    【例】數組Data[0]、Data[1]、Data[2]和Data[3]都是4bit的數據。找到它們當中最小的數據,並將該數據的索引輸出到LidMin中(類似 “冒泡排序”),且在一個時鍾周期內完成。首先將Lid_Min設置一個任意初始值,然后將Data[0]~Data[3]與Data[Lid_Min]進行比較,每比較一個數,就將較小的索引暫存在Lid_Min中,然后再進行下一次比較。當4組數據比較完成之后,最小的數據索引就會保留在Lid_Min中。例如,若4個數據中Data[2]最小,則LidMin的值為2。

 1 module Bubble_Up(input Rst_n,
 2                  input Clk,
 3                  input [5:0] Data [0:3],  //需要SystemVerilog extensions(通常端口不可用數組型)
 4                  output reg [1:0] Lid_Min );
 5 always @(posedge Clk or negedge Rst_n) begin
 6         if (~Rst_n) begin
 7                 Lid_Min <= 2'd0;
 8             end
 9         else
10             begin    //begin…end中為非阻塞賦值
11                 if (Data[0] <= Data[Lid_Min]) begin
12                         Lid_Min <= 2'd0;    
13                     end
14                 if (Data[1] <= Data[Lid_Min]) begin
15                         Lid_Min <= 2'd1;
16                     end
17                 if (Data[2] <= Data[Lid_Min]) begin
18                         Lid_Min <= 2'd2;
19                     end
20                 if (Data[3] <= Data[Lid_Min]) begin
21                         Lid_Min <= 2'd3;
22                     end
23             end
24     end
25 
26 endmodule
Bubble_Up

    【注】SystemVerilog extensions設置:Settings --> Analysis & Synthesis Settings-->Verilog HDL Input --> Verilog Version--> SystemVerilog-2005。

     以上代碼中使用了非阻塞賦值,但仿真波形結果並不正確,如下圖所示。圖中的Data[0]~Data[3]分別為11、3、10和12,Lid_Min的初始值為0。Lid_Min結果應為1,因為Data[1]最小。

     原因如下:

     在時鍾上升沿到來后,且Rst_n信號無效時開始執行后續4個語句,假設此時Lid_Min為0,Data[0]~Data[3]分別為11、3、10和12:

     第一句的if為真,因此執行Lid_Min <= 2’d0,而此時Lid_Min並未立刻被賦值,而是調度到事件隊列中等待執行,這是非阻塞賦值的特點。

     第二句的if為真,因此執行Lid_Min <= 2’d1,這是Lid_Min也沒有立刻被賦值為1,而是調度到事件隊列中等待執行。當前的Lid_Min還是0,沒有發生任何變化。

     同樣,第三句的if也為真,因此執行Lid_Min <= 2’d2,將更新事件調度到事件隊列中等待執行。當前的Lid_Min還是0。

     而第四句的if為假,因此直接跳過Lid_Min <= 2’d3,這時跳出always語句,等待下一個時鍾上升沿。

     在以上的always語句執行完成以后,仿真時間沒有前進。這時存在於事件隊列中當前仿真時間上的3個被調度的非阻塞更新事件開始執行,它們分別將Lid_Min更新為0、1和2。

     按照Verilog語言的規范,這3個更新事件屬於同一仿真時間內的事件,它們之間的執行順序隨機,這就產生了不確定性。一般的仿真器在實現的時候是根據它們被調度的先后順序執行的,事件隊列就像一個存放事件的FIFO,它是分層事件隊列的一部分,如圖所示:

     這3個事件在同一仿真時間被一一執行,而真正起作用的時最后一個更新事件,因此在仿真的時候得到的最終結果時Lid_Min為2。

     然而我們想要得到的結果是,在每個if語句判斷並執行完成以后,Lid_Min先暫存這個中間值,再進行下一次比較,也就是說在進行下一次比較之前,這個Lid_Min必須被更新,而這點正是阻塞賦值的特點。將代碼段”else”內部改為阻塞賦值(<=→=),仿真波形如圖所示:

 

     在仿真過程中,”else”段第二句的if為真,執行Lid_Min = 2'd1,根據阻塞賦值的特點,Lid_Min被立刻賦值為1。執行第三句if時if為假,直接跳過Lid_Min = 2'd2不執行,同樣也跳過Lid_Min = 2'd3不執行。Lid_Min被最終賦值為1,這正是我們想要的結果。

     仿真采用Cyclone II系列EP2C5F256C6器件。編譯結果顯示,非阻塞賦值占用39個LE(logic elements),阻塞賦值占用80個LE,兩者均占用2個寄存器(registers)。

     為了使代碼看起來更簡潔,也可使用for語句改寫”else”段代碼如下:

1 begin
2     for (i = 2'd0; i <= 2'd3; i = i + 2'd1) begin
3             if (Data[i] <= Data[Lid_Min]) begin
4                     Lid_Min = i;
5                 end
6         end
7 end
for loop

需要注意的是,for語句的電路功能比較難理解,其展開形式往往更具可讀性。

 

 


免責聲明!

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



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