Verilog語言設計增加延時的正確方法

在設計仿真激勵文件時,爲了滿足和外部芯片接口的時序要求,經常會用到延時賦值語句,由於不同的延時賦值語句在仿真過程中行爲不同,會產生不同的激勵輸出,如果不認真區分不同表達式引起的差異,就可能產生錯誤的激勵,無法保證仿真結果的正確,本文就是區分各種延時賦值語句的差異,並給出比較結果;

1:阻塞式左延時賦值語句
舉例說明如下:

module adder_t1 (co, sum, a, b, ci);
output co;
output [3:0] sum;
input [3:0] a, b;
input ci;
reg co;
reg [3:0] sum;
always @(a or b or ci)
#12 {co, sum} = a + b + ci; // 在15ns時a發生變化,啓動該塊,但是等到27ns時才執行後面的語句,所以是最新的結果
endmodule

分析:上面例子是希望在輸入信號變化後12ns再更新輸出結果,假設在15ns時a發生變化,在27ns時,結果將被更新,但是如果在15ns到24ns這一段時間,a,b,ci又發生了變化,在27ns時,結果將按照最新的a,b,ci進行計算並被更新

如果將程序修改成如下格式,仿真的結果不變:

module adder_t7a (co, sum, a, b, ci);
output co;
output [3:0] sum;
input [3:0] a, b;
input ci;
reg co;
reg [3:0] sum;
reg [4:0] tmp;
always @(a or b or ci)
begin
#12 tmp = a + b + ci; // 在15ns a發生變化時,啓動該always塊,但是12ns後即第27ns時才執行 
 //tmp = a + b + ci tmp才被賦值,因此賦值的是最近的a,b,ci變化的值
{co, sum} = tmp;
end
endmodule

如果將程序做如下修改:

module adder_t7b (co, sum, a, b, ci);
output co;
output [3:0] sum;
input [3:0] a, b;
input ci;
reg co;
reg [3:0] sum;
reg [4:0] tmp;
always @(a or b or ci)
begin
tmp = a + b + ci;  // 在15ns a發生變化時,啓動該always塊,執行該語句,獲得當前tem ,然後就開始執行下一句 #12
#12 {co, sum} = tmp; // 所以tmp的值是15ns的值,再過12ns即27ns時賦值給輸出。因此,中間的變換被忽視
end
//15~27之間執行該語句塊,只有執行完該語句塊之後纔是開始執行下一個語句塊
endmodule

仿真的結果如下圖所示:從15ns到27ns之間的變化被忽視

結論:阻塞式賦值語句是一句一句執行的,一句沒有執行完,下一句絕不會執行。正因爲如此,在此例中在延時12個ns的時間裏,不作任何處理,tmp值保持不變(2’b10),而且對敏感變量的變化不作反應。 不要將延時放在阻塞式賦值語句的左側,這是一種不好的代碼設計方式

2:阻塞式右延時賦值語句
看下面的例子:

module adder_t6 (co, sum, a, b, ci);
output co;
output [3:0] sum;
input [3:0] a, b;
input ci;
reg co;
reg [3:0] sum;
always @(a or b or ci)
{co, sum} = #12 a + b + ci; // 先計算a + b + ci的值,過12ns後賦值爲輸出;賦值語句是從右往左執行
endmodule

它的仿真結果同adder_t7b,賦值語句是從右往左執行,信號從15ns到27ns之間的變化被忽視;即 同:

tmp = a + b + ci;  

#12 {co, sum} = tmp;

下面兩個例子的仿真結果和adder_t6相同:

module adder_t11a (co, sum, a, b, ci);
output co;
output [3:0] sum;
input [3:0] a, b;
input ci;
reg co;
reg [3:0] sum;
reg [4:0] tmp;
always @(a or b or ci)
begin
tmp = #12 a + b + ci;
{co, sum} = tmp;
end
endmodule
module adder_t11b (co, sum, a, b, ci);
output co;
output [3:0] sum;
input [3:0] a, b;
input ci;
reg co;
reg [3:0] sum;
reg [4:0] tmp;
always @(a or b or ci)
begin
tmp = a + b + ci;
{co, sum} = #12 tmp;
end
endmodule

結論:不要將延時放在阻塞式賦值語句的右側,這是一種不好的代碼設計方式

3:非阻塞式左延時賦值語句
看例子:

module adder_t2 (co, sum, a, b, ci);
output co;
output [3:0] sum;
input [3:0] a, b;
input ci;
reg co;
reg [3:0] sum;
always @(a or b or ci)
#12 {co, sum} <= a + b + ci;
endmodule

它的仿真結果同adder_t1,在27ns時,結果將按照最新的a,b,ci進行計算並被更新;由於首先要執行#12,然後才能執行非阻塞賦值,所以能對信號進行實時跟蹤! 

結論:不要將延時放在非阻塞式賦值語句的左側,這是一種不好的代碼設計方式;

4:非阻塞式右延時賦值語句
看例子:

module adder_t3 (co, sum, a, b, ci);
output co;
output [3:0] sum;
input [3:0] a, b;
input ci;
reg co;
reg [3:0] sum;
always @(a or b or ci)
{co, sum} <= #12 a + b + ci;
endmodule

非阻塞賦值,不會阻止下一條語句的執行,輸出結果能隨時跟蹤輸入信號的變化

結論:使用非阻塞式右延時賦值語句可以,輸出結果能夠跟隨輸入的變化,建議使用

5:非阻塞式右延時多重賦值語句
看例子:

module adder_t9c (co, sum, a, b, ci);
output co;
output [3:0] sum;
input [3:0] a, b;
input ci;
reg co;
reg [3:0] sum;
reg [4:0] tmp;
always @(a or b or ci or tmp)
begin
tmp <= #12 a + b + ci;
{co, sum} <= tmp;
end
endmodule

非阻塞賦值,不會阻止下一條語句的執行,輸出結果能隨時跟蹤輸入信號的變化;這與下面的例子結果是相同的:

module adder_t9d (co, sum, a, b, ci);
output co;
output [3:0] sum;
input [3:0] a, b;
input ci;
reg co;
reg [3:0] sum;
reg [4:0] tmp;
always @(a or b or ci or tmp)
begin
tmp <= a + b + ci;
{co, sum} <= #12 tmp;
end
endmodule

6:連續賦值語句
看例子:

module adder_t4 (co, sum, a, b, ci);
output co;
output [3:0] sum;
input [3:0] a, b;
input ci;
assign #12 {co, sum} = a + b + ci;
endmodule
module adder_t5 (co, sum, a, b, ci);
output co;
output [3:0] sum;
input [3:0] a, b;
input ci;
assign {co, sum} = #12 a + b + ci;
endmodule

輸出結果能隨時跟蹤輸入信號的變化;

結論:非阻塞語句和連續賦值語句無論怎麼添加延時語句,其輸出都會隨着輸入的變化而跟蹤變化!這裏要特別注意阻塞語句的延時添加,一般不在阻塞賦值語句中添加延時

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章