阻塞賦值與非阻塞賦值


 轉載自https://www.cnblogs.com/yuphone/archive/2010/11/10/1874465.html

內容

阻塞賦值VS非阻塞賦值

有兩種賦值語句被用在always塊內:阻塞賦值與非阻塞賦值。關於阻塞與非阻塞復制有3條簡單的准則:

  • 將電路分為兩部分:寄存器電路和組合電路
  • 在寄存器電路中使用非阻塞賦值
  • 在組合電路中使用阻塞賦值

 

1 概覽

阻塞賦值 基本語法如下:

1
[var] = [expression];

當該條語句被執行時,右手邊的表達式將被賦給左手邊的變量,期間不允許其他語句的干擾。因此,就阻塞了其他語句,直到該條語句執行完畢為止。阻塞賦值的行為與C語言中的變量賦值類似。

 

非阻塞賦值 基本語法如下:

1
[var] <= [expression];

非阻塞賦值的行為非常令人難以琢磨。當always塊被激活(在time step的開始),右手邊的表達式被賦初值。當運行到always塊的結尾(即time step的結尾),運算所得的值被賦給左手邊的變量。

以x變量執行非阻塞賦值為例。因為Verilog模型的實際流程比較復雜,我們將非阻塞賦值的行為翻譯成一下幾個步驟:

  • 在alway塊的開始,x值傳遞給x_entry;
  • 右手邊的變量x的值被x_entry取代;
  • 左手邊變量x的值被x_exit取代;
  • 在always塊的結束,x_exit的值傳遞給x。

 

在下面的代碼片段內,上述四個步驟被呈現在代碼的注釋中。

1
2
3
4
5
always @*
begin                // x_entry = x
   y <= x & ...      // y       = x_entry & ...
   x <= ...          // x_exit  = ...
end                  // x       = x_exit

 

范例 為了了解阻塞賦值和非阻塞賦值的區別,我們用三輸入的的電路來做討論。

代碼1 使用阻塞賦值的電路

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module  and_blocking
(
   input       a,
   input       b,
   input       c,
   output  reg  y
); 
 
always @*
begin
   y = a;
   y = y & b;
   y = y & c;
end
 
endmodule

阻塞賦值的欣慰類似於C語言中的順序賦值。y最終得到的值為a & b & c。注意,此代碼僅用於示范,使用順序語義學來描述電路是比較差勁的行為。

 

下面給出的代碼,其中的阻塞賦值被替換為非阻塞賦值。代碼注釋詳細說明了y的賦值動作。

代碼2 使用非阻塞賦值的電路

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module  and_nonblocking
(
   input       a,
   input       b,
   input       c,
   output  reg  y
); 
 
always @*
begin                // y_entry = y
   y <= a;           // y_exit  = a
   y <= y & b;       // y_exit  = y_entry & b
   y <= y & c;       // y_exit  = y_entry & c
end                  // y       = y_exit
 
endmodule

注意always塊內的前2條語句將不會產生任何效果。上述always塊等價與:

1
2
always @*       
   y <= y & c;

 

2 組合電路

上一個小節的范例屬於極端的情況。除了缺省值,大部分的組合電路並不會多次賦值同一變量。阻塞賦值和非阻塞賦值都可以用於描述同一電路。然而,它們有一些微妙的區別。下面的范例用於解釋這些不同。讓我們以一位同或(異或非)電路為例。我們將詳細列出敏感列表中的變量。

代碼3 使用阻塞賦值的一位同或電路

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module  eq1_blocking
(
   input       i0,
   input       i1,
   output  reg  eq
);
 
reg  p0, p1;
 
always @(i1, i2)     // 只有i0和i1在敏感列表
begin                // 語句的順序非常重要
   p0 = ~i0 & i1;
   p1 = i0 & i1;
   eq = p0 | p1;
end
 
endmodule

注意到敏感列表僅包括i0和i1,。當其中之一變化時,always塊被激活,p0、p1和ep被順序運算,ep在第一個time step的結尾被更新。語句的順序非常重要。假設性我們移動最后面的語句到最前面。

1
2
3
4
5
6
always @(i1, i2)    
begin              
   eq = p0 | p1;
   p0 = ~i0 & i1;
   p1 = i0 & i1;
end

在第一條語句中,由於p0和p1還沒有被指定新的值,因此先前被激活的值將會被用到。而先前的值將意味着鎖存器的存在,故此代碼是不正確的。

 

下面將使用非阻塞賦值替換阻塞賦值。

代碼4 使用非阻塞賦值的一位同或電路

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module  eq1_nonblocking
(
   input       i0,
   input       i1,
   output  reg  eq
);
 
reg  p0, p1;
 
always @(i1, i2, p0, p1)       // p0、p1也在敏感列表中
                               // 語句的順序不重要     
begin                          // p0_entry = p0; p1_entry = p1            
   p0 <= ~i0 & i1;             // p0_exit = ~i0 & ~i1
   p1 <= i0 & i1;              // p1_exit = i0 & i1
   eq <= p0 | p1;              // eq_exit = p0_entry | p1_entry
end                            // eq = eq_exit; p0 = p0_exit; p1 = p1_exit                         
 
endmodule

注意p0和p1也包括在敏感列表中。當i0或i1變化時,always塊被激活;在第一個time step的結尾,p0和p1被賦以新值。既然ep取決於p0和p1(p0_entry和p1_entry)的舊值,那么其值在第一個time step的結尾保持不變。當當前的time step執行完畢,always塊重新被激活,因為p0和p1發生了變化(這便於為何p0和p1也要置於敏感列表之中的原因)。注意語句的順序不影響結果。

 

3 存儲單元

使用非阻塞賦值來引用存儲器。例如,D觸發器:

1
2
always @(posede clk)
   q <= d;

當然也可以用阻塞賦值來引用D觸發器,如下:

1
2
always @(posede clk)
   q = d;

雖然在單個D-FF情況下面,上面的代碼工作正常,但是當多個寄存器互相動作的時候,這里就出現許多微妙的問題。

考慮兩個寄存器在每個時鍾周期交換數據。使用阻塞賦值,代碼為:

1
2
3
4
5
always @(posede clk)
   a = b;
   
always @(posede clk)
   b = a;

在clk的上升沿,兩個always塊都被激活,並行操作。這兩個操作應該在同一time step結束。根據Verilog的標准,兩個always塊的執行可以以任何順序列入。若第一個always塊先執行,則由於阻塞賦值的緣故a立即得到b的值。當第二個always塊執行的時候,b得到a的刷新值,及b的原始值,因此b保持不變。類似的,若第二個always塊先執行,a得到的也是其初始值。這就是Verilog中的競爭冒險(race condition)。從Verilog的角度看,兩種結果都是有效的。

下面我們修改代碼中的阻塞賦值為非阻塞賦值。

1
2
3
4
5
6
7
8
9
always @(posede clk)
begin         // b_entry = b
   a <= b;    // a_exit  = b_entry
end           // a       = a_exit
   
always @(posede clk)
begin         // a_entry = a
   b <= a;    // b_exit  = a_entry
end           // b       = b_exit

通過注釋我們看到,因為輸入(entry)值都被用於賦值,所以無論執行的順序,a和b都得到正確的值。

因此為了避免競爭冒險,我們使用非阻塞賦值來引用D-FF和觸發器。

 

4 混用阻塞和非阻塞賦值的時序電路

在同一個always塊內,有可能混用阻塞賦值和非阻塞賦值。下面我們使用簡單的例程來解釋不同組合的行為,以加強對賦值的理解。

圖4.1 通過混合賦值來推斷電路

圖4.1 通過混合賦值來引用電路

 

考慮圖4.1(b)所示的原理圖。當時鍾上升沿來臨之時,a、b與運算所得的值被存入D-FF。基於前面的講解,我們可以將存儲和組合電路分配到兩段代碼中。如代碼4.1所示。

代碼4.1 兩段實現

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
module  ab_dff_2seg
(
   input  clk,
   input  a,
   input  b,
   output  reg  q
);
 
reg  q_next;
 
// D-FF
always @( posedge  clk)
   q <= q_next;
 
// 組合電路
always @*
   q_next = a & b;
 
endmodule

 

我們可以變換一下,將兩段組合在一起,使用單個always塊來描述電路。下面通過六次嘗試,來描述阻塞和非阻塞賦值的不同組合的區別。如代碼4.2所示。

代碼4.2 混合賦值例程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
module  ab_dff_mix
(
   input  clk,
   input  a,
   input  b,
   output  reg  q0,
   output  reg  q1,
   output  reg  q2,
   output  reg  q3,
   output  reg  q4,
   output  reg  q5
);
 
reg  ab0, ab1, ab2, ab3, ab4, ab5;
 
// 嘗試0
always @( posedge  clk)
begin
   ab0 = a & b;
   q0 <= ab0;
end
 
// 嘗試1
always @( posedge  clk)
begin                    // ab1_entry = ab1; q1_entry = q1
   ab1 <= a & b;         // ab1_exit = a & b
   q1  <= ab1;           // q1_exit = ab1_entry
end                      // ab1 = ab1_exit; q1 = q1_exit
 
// 嘗試2
always @( posedge  clk)
begin
   ab2 = a & b;
   q2  = ab2;
end
 
// 嘗試3(調換嘗試1的順序)
always @( posedge  clk)
begin
   q0 <= ab0;
   ab0 = a & b;
end
 
// 嘗試4(調換嘗試2的順序)
always @( posedge  clk)
begin                    // ab4_entry = ab4; q4_entry = q4
   q4  <= ab4;           // q4_exit = ab4_entry
   ab4 <= a & b;         // ab4_exit = a&b
end                      // ab4 = ab4_exit; q4 = q4_exite
 
// 嘗試5(調換嘗試3的順序)
always @( posedge  clk)
begin
   q5  = ab5;
   ab5 = a & b;
end
 
endmodule

在嘗試0中,起初賦值給ab0和q0將引用兩個寄存器,一個用於存儲寄存器ab0,另一個用於存儲寄存器q0。因為ab0在塊賦值時被立即更新,所以q0得到了a&b的值。對應的原理圖如圖4.1(a)所示。由於ab0在always塊外沒有被使用,因此寄存器ab0的輸出就不是必需存在的,即相應的寄存器可以被移除。這樣,結果電路就如圖4.1(b)所示,也就是所需的電路。
在嘗試1中,對ab1使用了非阻塞賦值,對應的闡述寫到了注釋里面。注意q1得到的是ab1_entry而非ab1_exit。而ab1_entry是先前存儲的ab值,即對應一個寄存器的輸出。相應的原理圖如圖4.1(c)所示。一個不確定的輸入緩存被引用,同時a&b的值延遲一個時鍾周期后才被存儲到q1中。

在嘗試2中,ab2和q2都是用了阻塞賦值。該代碼所引用的電路,與嘗試1等同,如圖4.1(a)和(b)所示。由於使用阻塞賦值來引用D-FF,有可能產生競爭冒險,因此不推薦使用這種類型的代碼。

出於演示的目的,讓我們來測試一下調換嘗試0、1和2的賦值順序會發生什么。其結果代碼如嘗試4、5和6所示。在嘗試3中,ab3未更新便被使用,因此q3得到的是先前激活塊所產生的值。所引用的電路如4.1(c)所示。而嘗試4,交換語句的順序不影響綜合的效果,因此等同於嘗試1。嘗試5中,由於ab5未更新值便被使用,因此q5得到的寄存器a&b的值,等同於嘗試3。

簡而言之,只有嘗試0描述的電路正確且可靠。在嘗試0中,我們可以將ab0移除,合並代碼如下:

1
2
3
4
5
// 嘗試0
always @( posedge  clk)
begin
   q0 <= a & b;
end


免責聲明!

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



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