以下從兩個(gè)典型的例子以及多個(gè)角度去分析得到與驗(yàn)證阻塞賦值與非阻塞賦值的區(qū)別,以及各自的特點(diǎn)。
非阻塞賦值與阻塞賦值特性
1、非阻塞賦值的特性:
賦值語句的流程:
a)計(jì)算右邊的表達(dá)式得到結(jié)果
b)將結(jié)果賦值到左邊的變量
這個(gè)過程中允許來自任何其他Verilog語句的干擾,也就是說其他Verilog語句都可以在這個(gè)時(shí)候執(zhí)行。
2、阻塞賦值特性:
賦值語句的流程:
c)計(jì)算右邊的表達(dá)式得到結(jié)果
d)將結(jié)果賦值到左邊的變量
這個(gè)過程中不允許有來自任何其他Verilog語句的干擾,這就導(dǎo)致其他的Verilog語句會(huì)等待(阻塞)該賦值語句執(zhí)行完畢后才能被執(zhí)行,這就是阻塞的含義。
根據(jù)小梅哥視頻《07_例解阻塞賦值與非阻塞賦值》去分析
一、非阻塞賦值分析
1、非阻塞賦值源程序
/* 實(shí)驗(yàn)名稱:非阻塞賦值 */
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
從源碼中分析,根據(jù)非阻塞語句的特性, 假設(shè) a = 0,b = 1, c = 0;
當(dāng)rst_n為高時(shí),由于d<=a+b、out<=d+c; 是同時(shí)執(zhí)行,所以:
1、先右邊表達(dá)式的結(jié)果:a + b = 0 + 1 = 1、d + c = 0 + 0 = 0
2、再將結(jié)果賦值給左邊: d = a + 1就等于 1,out = d + c 就等于 0
2、RTL視圖
其實(shí)RTL視圖中也能看出來,假設(shè)a = 0,b = 1, c = 0; d寄存器為0,out寄存器為0 ;
那么當(dāng)一個(gè)時(shí)鐘周期到來:(以下兩步會(huì)同時(shí)執(zhí)行,即d與out寄存器會(huì)同時(shí)從加法器中取值)
1、Add0寄存器會(huì)將a(0) + b(1) = 1的值給d寄存器,d寄存器由0變?yōu)?
2、Add1寄存器會(huì)將d(0) + c(0) = 0的值給out寄存器,out寄存器仍是0
3、仿真測(cè)試源程序
`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 前一個(gè)時(shí)鐘周期的情況,由上圖可以看到,當(dāng)時(shí)鐘上升沿,采樣得到的 a、b、d、c都是等于0,而c是在時(shí)鐘上升沿之后才被拉高,所以要在下一個(gè)時(shí)鐘周期的上升沿才能采樣到c的狀態(tài),
如下圖:

然后我們?cè)偻罂纯础?span>

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

即圖中A上升沿時(shí):(以下兩步的采樣是在同一個(gè)時(shí)鐘上升沿同時(shí)進(jìn)行)
1、d的狀態(tài):舊狀態(tài)是0,經(jīng)過以下操作新的狀態(tài)是0
同時(shí)采樣a的狀態(tài)得到是0,b的狀態(tài)得到的是0。所以d的狀態(tài)變?yōu)?
2、out的狀態(tài):舊狀態(tài)是1,經(jīng)過以下操作新的狀態(tài)變?yōu)?
同時(shí)到d是0,c的狀態(tài)得到的是1,所以導(dǎo)致out變?yōu)?。
3、在時(shí)鐘為穩(wěn)定電平期間c變?yōu)?,b變?yōu)?,即在上升沿之后。
那么到了B上升沿時(shí):(以下兩步的采樣是在同一個(gè)時(shí)鐘上升沿同時(shí)進(jìn)行)
1、d的狀態(tài):舊狀態(tài)是0,經(jīng)過以下操作新的狀態(tài)是1
同時(shí)采樣a的狀態(tài)得到是0, b的狀態(tài)得到的是1。所以d的狀態(tài)變?yōu)?
2、out的狀態(tài):舊狀態(tài)是1,經(jīng)過以下操作新的狀態(tài)變?yōu)?
同時(shí)采樣到d是0,c的狀態(tài)得到的是0,所以導(dǎo)致out變?yōu)?。
當(dāng)?shù)紺上升沿的時(shí):(以下兩步的采樣是在同一個(gè)時(shí)鐘上升沿同時(shí)進(jìn)行)
1、d的狀態(tài):舊狀態(tài)是1,經(jīng)過以下操作新的狀態(tài)是1
同時(shí)采樣a的狀態(tài)得到是0,b的狀態(tài)得到的是1。所以d的狀態(tài)變?yōu)?
2、out的狀態(tài):舊狀態(tài)是0,經(jīng)過以下操作新的狀態(tài)變?yōu)?
同時(shí)采樣到d是1,c的狀態(tài)得到的是0,所以導(dǎo)致out變?yōu)?。
6、解決出現(xiàn)0的情況。
分析原因:
從RTL視圖中可以看到是因?yàn)槎嗔藥讉(gè)d寄存器導(dǎo)致out寄存器慢了一個(gè)時(shí)鐘。
從波形視圖中可以看到也是因?yàn)槎嗔薲,導(dǎo)致out寄存器出現(xiàn)了0狀態(tài)。
結(jié)論:
將d去掉即可,以后寫程序過程中盡量不要采用中間變量,避免出現(xiàn)多余的寄存器出現(xiàn),導(dǎo)致不同步。
源程序如下:
`timescale 1ns/1ns
/* 實(shí)驗(yàn)名稱:非阻塞賦值 */
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視圖:(從下圖可以看到,來一個(gè)時(shí)鐘周期,那么out直接就取Add0和Add1的值)

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

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

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

5、波形圖分析

根據(jù)代碼我們知道run信號(hào)是執(zhí)行完d = a + b 之后為高電平,執(zhí)行完 out = d + c 為低電平。
通過run信號(hào)知道,每次都是在out變化之后才出現(xiàn)低電平。因?yàn)槿绻峭瑫r(shí)執(zhí)行的話,速度非常快,由于run原先就是低電平,變?yōu)楦唠娖胶妥優(yōu)榈碗娖蕉际茄訒r(shí)1ns,也就是說同時(shí)執(zhí)行1ns之后run依然會(huì)是低電平,如下圖,同樣的延時(shí),改為非阻塞的方式,out一直為低。

A時(shí)鐘上升沿到來:(以下三個(gè)步驟為順序執(zhí)行)
1、d的狀態(tài):當(dāng)前狀態(tài)0,經(jīng)過采樣,新的狀態(tài)更新為0
同時(shí)采樣a的狀態(tài)為0、 b的狀態(tài)為0,d = a + b = 0 + 0 = 0;
2、run信號(hào):
被拉高
3、out的狀態(tài):當(dāng)前狀態(tài)0,經(jīng)過采樣,新的狀態(tài)更新為0
同時(shí)采樣d的狀態(tài)0、c的狀態(tài)為0,out = d + c = 0 + 0 = 0;
4、run信號(hào):
被拉低
B時(shí)鐘上升沿到來:(以下三個(gè)步驟為順序執(zhí)行)
1、d的狀態(tài):當(dāng)前狀態(tài)0,經(jīng)過采樣,新的狀態(tài)更新為0
同時(shí)采樣a的狀態(tài)為0、 b的狀態(tài)為0,d = a + b = 0 + 0 = 0;
2、run信號(hào):
被拉高
3、out的狀態(tài):當(dāng)前狀態(tài)0,經(jīng)過采樣,新的狀態(tài)更新為1
同時(shí)采樣d的狀態(tài)0、c的狀態(tài)為1,out = d + c = 0 + 1 = 1;
4、run信號(hào):
被拉低
C時(shí)鐘上升沿到來:(以下三個(gè)步驟為順序執(zhí)行)
1、d的狀態(tài):當(dāng)前狀態(tài)0,經(jīng)過采樣,新的狀態(tài)更新為0
同時(shí)采樣a的狀態(tài)為0、 b的狀態(tài)為0,d = a + b = 0 + 0 = 0;
2、run信號(hào):
被拉高
3、out的狀態(tài):當(dāng)前狀態(tài)1,經(jīng)過采樣,新的狀態(tài)更新為1
同時(shí)采樣d的狀態(tài)0、c的狀態(tài)為1,out = d + c = 0 + 1 = 1;
4、run信號(hào):
被拉低
通過以上步驟可以發(fā)現(xiàn),執(zhí)行的時(shí)間、采樣的時(shí)間不一樣,會(huì)導(dǎo)致不一樣的結(jié)果。
另外一個(gè)實(shí)驗(yàn):根據(jù)網(wǎng)上的一篇文章的例子,然后再通過看波形的方式去驗(yàn)證。
http://blog.163.com/xiaoting_hu/blog/static/50464772201361162838112/
一、非阻塞賦值分析
1、非阻塞賦值例程
/* 實(shí)驗(yàn)名稱:阻塞與非阻塞賦值差異實(shí)驗(yàn)
* 程序功能:非阻塞賦值 - 觀看 RTL-View 以及波形 */
module mytest(o_y1, o_y2, i_clk, i_rst);
output reg o_y1, o_y2;
input wire i_clk, i_rst;
//異步復(fù)位 alwaysA
always@(posedge i_clk or negedge i_rst)
if(!i_rst)
o_y1 <= 0; // 低電平復(fù)位
else
o_y1 <= o_y2;
//異步復(fù)位 alwaysB
always@(posedge i_clk or negedge i_rst)
if(!i_rst)
o_y2 <= 1; // 低電平復(fù)位
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; // 復(fù)位
#(`clock_period * 5);
rst = 1'b1; // 開始執(zhí)行
#(`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、波形分析

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

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

4、波形分析

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