以下從兩個典型的例子以及多個角度去分析得到與驗證阻塞賦值與非阻塞賦值的區別,以及各自的特點。
非阻塞賦值與阻塞賦值特性
1、非阻塞賦值的特性:
賦值語句的流程:
a)計算右邊的表達式得到結果
b)將結果賦值到左邊的變量
這個過程中允許來自任何其他Verilog語句的干擾,也就是說其他Verilog語句都可以在這個時候執行。
2、阻塞賦值特性:
賦值語句的流程:
c)計算右邊的表達式得到結果
d)將結果賦值到左邊的變量
這個過程中不允許有來自任何其他Verilog語句的干擾,這就導致其他的Verilog語句會等待(阻塞)該賦值語句執行完畢后才能被執行,這就是阻塞的含義。
根據小梅哥視頻《07_例解阻塞賦值與非阻塞賦值》去分析
一、非阻塞賦值分析
1、非阻塞賦值源程序
/* 實驗名稱:非阻塞賦值 */
module mytest(clk, rst_n, a, b, c, out);
input clk, rst_n, a, b, c;
output reg [1:0] out;
reg[1:0] d;
always@(posedge clk or negedge rst_n)
if(!rst_n)
out <= 2'b0;
else begin
d <= a + b;
out <= d + c;
end
endmodule
從源碼中分析,根據非阻塞語句的特性, 假設 a = 0,b = 1, c = 0;
當rst_n為高時,由于d<=a+b、out<=d+c; 是同時執行,所以:
1、先右邊表達式的結果:a + b = 0 + 1 = 1、d + c = 0 + 0 = 0
2、再將結果賦值給左邊: d = a + 1就等于 1,out = d + c 就等于 0
2、RTL視圖
其實RTL視圖中也能看出來,假設a = 0,b = 1, c = 0; d寄存器為0,out寄存器為0 ;
那么當一個時鐘周期到來:(以下兩步會同時執行,即d與out寄存器會同時從加法器中取值)
1、Add0寄存器會將a(0) + b(1) = 1的值給d寄存器,d寄存器由0變為1
2、Add1寄存器會將d(0) + c(0) = 0的值給out寄存器,out寄存器仍是0
3、仿真測試源程序
`timescale 1ns/1ns
`define clock_period 20
module mytest_tb;
reg clk, rst_n, a, b, c;
wire [1:0] out;
mytest u1(clk, rst_n, a, b, c, out);
initial clk = 1;
always#(`clock_period / 2) clk = ~clk;
initial begin
rst_n = 1'b0;
a = 0;
b = 0;
c = 0;
#(`clock_period * 200 + 1);
rst_n = 1'b1;
#(`clock_period * 20);
a = 0; b = 0; c = 0;
#(`clock_period * 200);
a = 0; b = 0; c = 1;
#(`clock_period * 200);
a = 0; b = 1; c = 0;
#(`clock_period * 200);
a = 0; b = 1; c = 1;
#(`clock_period * 200);
a = 1; b = 0; c = 0;
#(`clock_period * 200);
a = 1; b = 0; c = 1;
#(`clock_period * 200);
a = 1; b = 1; c = 0;
#(`clock_period * 200);
a = 1; b = 1; c = 1;
#(`clock_period * 200);
#(`clock_period * 200);
$stop;
end
endmodule
4、波形圖

5、波形分析

這是 out 從 0 變成 1 前一個時鐘周期的情況,由上圖可以看到,當時鐘上升沿,采樣得到的 a、b、d、c都是等于0,而c是在時鐘上升沿之后才被拉高,所以要在下一個時鐘周期的上升沿才能采樣到c的狀態,
如下圖:

然后我們再往后看看。

首先我們看到 01 與 01 直接夾著 0,那么01對應的應該是如下幾行的測試代碼,從代碼可以看到不應該有00的存在。
a = 0; b = 0; c = 1;
#(`clock_period * 200);
a = 0; b = 1; c = 0;
#(`clock_period * 200);
因為c變為0,b變為1是在時鐘周期的上升沿之后才變化,這里暫時不考慮是在上升沿或下降沿時變化。
而out和d的狀態因為過程中需要采樣和賦值操作,會有一個邏輯延遲的現象,也就是說也是在時鐘上升沿之后才會變化。以下為后仿真的波形圖,根據接近實際狀況。

即圖中A上升沿時:(以下兩步的采樣是在同一個時鐘上升沿同時進行)
1、d的狀態:舊狀態是0,經過以下操作新的狀態是0
同時采樣a的狀態得到是0,b的狀態得到的是0。所以d的狀態變為0
2、out的狀態:舊狀態是1,經過以下操作新的狀態變為1
同時到d是0,c的狀態得到的是1,所以導致out變為1。
3、在時鐘為穩定電平期間c變為0,b變為1,即在上升沿之后。
那么到了B上升沿時:(以下兩步的采樣是在同一個時鐘上升沿同時進行)
1、d的狀態:舊狀態是0,經過以下操作新的狀態是1
同時采樣a的狀態得到是0, b的狀態得到的是1。所以d的狀態變為1
2、out的狀態:舊狀態是1,經過以下操作新的狀態變為0
同時采樣到d是0,c的狀態得到的是0,所以導致out變為0。
當到C上升沿的時:(以下兩步的采樣是在同一個時鐘上升沿同時進行)
1、d的狀態:舊狀態是1,經過以下操作新的狀態是1
同時采樣a的狀態得到是0,b的狀態得到的是1。所以d的狀態變為1
2、out的狀態:舊狀態是0,經過以下操作新的狀態變為1
同時采樣到d是1,c的狀態得到的是0,所以導致out變為0。
6、解決出現0的情況。
分析原因:
從RTL視圖中可以看到是因為多了幾個d寄存器導致out寄存器慢了一個時鐘。
從波形視圖中可以看到也是因為多了d,導致out寄存器出現了0狀態。
結論:
將d去掉即可,以后寫程序過程中盡量不要采用中間變量,避免出現多余的寄存器出現,導致不同步。
源程序如下:
`timescale 1ns/1ns
/* 實驗名稱:非阻塞賦值 */
module mytest(clk, rst_n, a, b, c, out);
input clk, rst_n, a, b, c;
output reg [1:0] out;
//reg[1:0] d;
always@(posedge clk or negedge rst_n)
if(!rst_n)
out <= 2'b0;
else begin
//d <= a + b;
//out <= d + c;
out <= a + b + c;
end
endmodule
RTL視圖:(從下圖可以看到,來一個時鐘周期,那么out直接就取Add0和Add1的值)

波形圖:(下圖比較小,可以對比上面的波形圖)

二、阻塞賦值分析
1、源程序(為了更加清晰的理解,我修改了視頻作者的源碼)
`timescale 1ns/1ns
/* 實驗名稱:非阻塞賦值 */
module mytest(clk, rst_n, a, b, c, out);
input clk, rst_n, a, b, c;
output reg [1:0] out;
reg[1:0] d;
reg run; // 這個是用來指示當前執行的位置
always@(posedge clk or negedge rst_n)
if(!rst_n)
out = 2'b0;
else begin
d = a + b;
run =#1 1; // 準備執行out賦值時
out = d + c;
run =#1 0; // 執行out賦值后
end
endmodule
從源碼中分析,根據阻塞語句的特性, 假設 a = 0,b = 1, c = 0;
當rst_n為高時,由于d=a+b、out=d+c; 是順序執行,所以:
1、先執行:d = a + b = 0 + 1 = 1
2、在執行:out = d + c = 1 + 0 = 1;
2、RTL視圖(沒看錯,就和去掉了d寄存器的非阻塞賦值代碼生成的電路一樣)

從這里就能看出與非阻塞賦值的不同之處,此處不再解釋。
3、仿真測試程序
與非阻塞仿真測試程序一樣,略。
4、波形圖

5、波形圖分析

根據代碼我們知道run信號是執行完d = a + b 之后為高電平,執行完 out = d + c 為低電平。
通過run信號知道,每次都是在out變化之后才出現低電平。因為如果是同時執行的話,速度非常快,由于run原先就是低電平,變為高電平和變為低電平都是延時1ns,也就是說同時執行1ns之后run依然會是低電平,如下圖,同樣的延時,改為非阻塞的方式,out一直為低。

A時鐘上升沿到來:(以下三個步驟為順序執行)
1、d的狀態:當前狀態0,經過采樣,新的狀態更新為0
同時采樣a的狀態為0、 b的狀態為0,d = a + b = 0 + 0 = 0;
2、run信號:
被拉高
3、out的狀態:當前狀態0,經過采樣,新的狀態更新為0
同時采樣d的狀態0、c的狀態為0,out = d + c = 0 + 0 = 0;
4、run信號:
被拉低
B時鐘上升沿到來:(以下三個步驟為順序執行)
1、d的狀態:當前狀態0,經過采樣,新的狀態更新為0
同時采樣a的狀態為0、 b的狀態為0,d = a + b = 0 + 0 = 0;
2、run信號:
被拉高
3、out的狀態:當前狀態0,經過采樣,新的狀態更新為1
同時采樣d的狀態0、c的狀態為1,out = d + c = 0 + 1 = 1;
4、run信號:
被拉低
C時鐘上升沿到來:(以下三個步驟為順序執行)
1、d的狀態:當前狀態0,經過采樣,新的狀態更新為0
同時采樣a的狀態為0、 b的狀態為0,d = a + b = 0 + 0 = 0;
2、run信號:
被拉高
3、out的狀態:當前狀態1,經過采樣,新的狀態更新為1
同時采樣d的狀態0、c的狀態為1,out = d + c = 0 + 1 = 1;
4、run信號:
被拉低
通過以上步驟可以發現,執行的時間、采樣的時間不一樣,會導致不一樣的結果。
另外一個實驗:根據網上的一篇文章的例子,然后再通過看波形的方式去驗證。
http://blog.163.com/xiaoting_hu/blog/static/50464772201361162838112/
一、非阻塞賦值分析
1、非阻塞賦值例程
/* 實驗名稱:阻塞與非阻塞賦值差異實驗
* 程序功能:非阻塞賦值 - 觀看 RTL-View 以及波形 */
module mytest(o_y1, o_y2, i_clk, i_rst);
output reg o_y1, o_y2;
input wire i_clk, i_rst;
//異步復位 alwaysA
always@(posedge i_clk or negedge i_rst)
if(!i_rst)
o_y1 <= 0; // 低電平復位
else
o_y1 <= o_y2;
//異步復位 alwaysB
always@(posedge i_clk or negedge i_rst)
if(!i_rst)
o_y2 <= 1; // 低電平復位
else
o_y2 <= o_y1;
endmodule
2、RTL 視圖

3、仿真源程序
`timescale 1ns/1ns
`define clock_period 20
module mytest_tb;
reg clk, rst;
wire y1, y2;
mytest u1(y1, y2, clk, rst);
initial clk = 1'b1;
always #(`clock_period / 2) clk = ~clk;
initial begin
rst = 1'b0; // 復位
#(`clock_period * 5);
rst = 1'b1; // 開始執行
#(`clock_period * 20);
rst = 1'b0;
#(`clock_period * 5);
rst = 1'b1;
#(`clock_period * 20);
rst = 1'b0;
#(`clock_period * 5);
rst = 1'b1;
#(`clock_period * 20);
$stop;
end
endmodule
4、波形圖

5、波形分析

//異步復位 alwaysA
always@(posedge i_clk or negedge i_rst)
if(!i_rst)
o_y1 <= 0; // 低電平復位
else
o_y1 <= o_y2;
//異步復位 alwaysB
always@(posedge i_clk or negedge i_rst)
if(!i_rst)
o_y2 <= 1; // 低電平復位
else
o_y2 <= o_y1;
當 i_rst == 0 :o_y1 被賦值為 0, o_y2 被賦值為 1,由于非阻塞賦值特性,所以無先后順序
當 i_rst == 1 :由于兩個 always 塊會被同時執行。即 fpga 會在同一個時鐘上升沿進行采樣,
第一個時鐘周期:
alwaysA 會對 o_y2 采樣得到為高電平,所以給 o_y1 賦值為高電平。
alwaysB 會對 o_y1 采樣得到為低電平,所以給 o_y2 賦值為低電平。
第二個時鐘周期:
alwaysA 會對 o_y2 采樣,因為在第一個周期中 o_y2 因 o_y1 而賦值為低電平,所以采樣得到為低電平,所以給 o_y1 賦值為低電平。
alwaysB 會對 o_y1 采樣,因為在第一個周期中 o_y1 因 o_y2 而賦值為高電平,所以采樣得到為高電平,所以給 o_y2 賦值為高電平。
之后的時鐘周期都是根據這樣的規律進行變化,所以我們看到的波形 o_y1、o_y2 是相反狀態。
二、阻塞賦值分析
1、阻塞賦值源程序
/* 實驗名稱:阻塞與非阻塞賦值差異實驗
* 程序功能:阻塞賦值 - 觀看 RTL-View 以及波形
*/
module mytest(o_y1, o_y2, i_clk, i_rst);
output reg o_y1, o_y2;
input wire i_clk, i_rst;
//異步復位
always@(posedge i_clk or negedge i_rst)
if(!i_rst)
o_y1 = 0; // 低電平復位
else
o_y1 = o_y2;
//異步復位
always@(posedge i_clk or negedge i_rst)
if(!i_rst)
o_y2 = 1; // 低電平復位
else
o_y2 = o_y1;
endmodule
2、RTL 視圖(居然和阻塞一樣的 RTL 視圖)

3、仿真源程序
和非阻塞仿真源程序一樣。略
3、波形圖

4、波形分析

//異步復位alwaysA
always@(posedge i_clk or negedge i_rst)
if(!i_rst)
o_y1 = 0; // 低電平復位
else
o_y1 = o_y2;
//異步復位 alwaysB
always@(posedge i_clk or negedge i_rst)
if(!i_rst)
o_y2 = 1; // 低電平復位
else
o_y2 = o_y1;
當 i_rst == 0 :o_y1 被賦值為 0, o_y2 被賦值為 1, 由于阻塞賦值特性,所以有先后順序,但無法確定誰先誰后。
當 i_rst == 1 :由于阻塞賦值的特性,在執行阻塞賦值語句時其他語句均不能得到執行。由于兩個 always 塊會被同時執行,而且 o_y1 和 o_y2 的取值是互相影響,所以 o_y1、o_y2 值取決于那個 always 塊的賦值語句先執行。從波形來看,明顯是 alwaysA 最新得到執行。
第一個時鐘周期:
alwaysA 會對 o_y2 采樣得到為高電平,所以給 o_y1 賦值為高電平。
alwaysB 會對 o_y1 采樣,因在 alwaysA 程序塊 o_y1 因 o_y2 而賦值為高電平,所以給 o_y2 賦值為高電平。
第二個時鐘周期:
alwaysA 會對 o_y2 采樣,因為在第一個周期中 o_y2 因 o_y1 而賦值為高電平,所以采樣得到為高電平,所以給 o_y1 賦值為高電平。
alwaysB 會對 o_y1 采樣,因在 alwaysA 程序塊 o_y1 因 o_y2 而賦值為高電平,所以給 o_y2 賦值為高電平。
也就是說 o_y1 = o_y2; o_y2 = o_y1; 是順序執行的,所以我們看到的波形 o_y1、o_y2 都是高電平狀態.
最后附上非阻塞賦值的一個很有意思的例子。