關於verilog裡阻塞與非阻塞賦值的個人理解
最近在做數字的東西,因此一直在學習verilog的語法,看的是夏宇聞老師的《verilog數字系統設計教程》這本書,在看到第14章深入理解阻塞與非阻塞賦值的不同時,結合書後面的誓言RISC_CPU,關於時序問題,產生了一些疑問,因此寫了一個簡單的程式,探索一下相關的內容,文筆拙劣,理解也並不完全正確,想寫出來與大家分享一下,希望能夠得到一些指點。
先引用書上的兩個例子:
1.採用阻塞賦值,不能自行觸發的振盪器
module osc(clk); output clk; reg clk; initial #10 clk = 0; always @(clk) begin $display("At%tns, be excuted.", $time);//just for test. #10; clk = ~clk; end endmodule
在initial塊中,經過10個單位的延遲,clk被立即阻塞賦值為0。當clk電平從不定態變為0的事件發生時,使always塊的@(clk)條件觸發,經過10個單位時間的延遲,計算RHS表示式(~clk)得到1,並立即更新LHS的值,clk立即被賦予1。由於在此期間不允許其他語句的干擾,即使always迴圈回到判斷觸發條件@(clk),由於此時clk電平已經為1,無法感知從0到1曾經發生過的變化,所以就阻塞在那裡,只有等待clk變為0才能進入該always塊,因此,這是一個不能自觸發的振盪器,不能產生時鐘波形。
2. 採用非阻塞賦值的自觸發振盪器
與上例相比,只是賦值方式有"="變為"<="。@(clk)的第一次觸發之後,非阻塞賦值的RHS表示式便計算出來,並把值賦給LHS的事件安排在更新事件佇列中。在非阻塞賦值更新事件佇列被啟用之前,又遇到@(clk)觸發語句,並且always塊再次對clk的變化產生反應。當非阻塞LHS的值在同一時刻被更新時,@(clk)再一次觸發。該例是自觸發方式,雖例中的程式碼能產生週期時鐘訊號,但在編寫模擬測試模組時,不推薦該寫法。module osc(clk); output clk; reg clk; initial #10 clk = 0; always @(clk) begin $display("At%tns, be excuted.", $time);//just for test. #10; clk <= ~clk; end endmodule
關於阻塞與非阻塞的實踐:
1. 一個簡單的時鐘分頻模組
module delayornot(fetch, clk, rst); output fetch; input clk, rst; reg fetch; integer i; always @(posedge clk or negedge rst) begin if (!rst) begin fetch <= 0; i <= 0; end else begin if (i == 4)// divided by 5. begin fetch <= ~fetch; i <= 0; end else i <= i+1; end end endmodule
2. 測試模組
module delay_tb();
wire fetch;
reg clk, rst;
reg [3:0] num1;
reg [3:0] num2;
reg [3:0] num3;
initial
begin
num1 = 4'b0;
num2 = 4'b0;
num3 = 4'b0;
clk = 0;
rst = 1;
#80;
rst = 0;
#80;
rst = 1;
#500;
end
always #40 clk = ~clk;
delayornot mydelay(fetch, clk, rst);
always @(posedge fetch)
begin
num1 <= num1 + 1;
end
always @(posedge fetch)
begin
if (fetch)
num2 <= num2 + 1;
end
always @(posedge clk)
begin
$display("At%tns, be excuted.",$time);//just for test.
if (fetch)
begin
num3 <= num3 + 1;
end
end
3. 模擬結果
從模擬圖中可以看到,delayornot模組實際上是一個5分頻的模組,fetch訊號採用非阻塞賦值方式。
在當fetch訊號上升沿到來時,由於num1和num2所在always塊觸發條件為fetch的上升沿,因此在fetch上升沿,觸發了num1和num2所在的always塊,因此各自加1。而num3所在的always塊觸發條件為clk訊號的上升沿,注意圖中fetch訊號上升沿到來的位置。此時,clk訊號為上升沿,因此觸發了num3的always塊。但注意,此時,由於fetch訊號在其模組中為非阻塞賦值,即等fetch訊號相應always塊結束時,才進行賦值操作。因此,此時fetch訊號為低電平,即if (fetch)不成立,因此num3未進行加1操作。num3在下一個clk上升沿時才加1,並且在fetch下降時來臨時依然加1,原因類似。
可能有人會疑問,那為什麼num2中的if (fetch)就成立了呢?因為,num2所在always塊檢測的是fetch訊號的上升沿,當其上升沿到來時,fetch已經為高電平,因此條件成立。verilog裡很重要的一點是,若不加#10等延時命令,則指令執行往往有概念上的先後,而並無實質上的先後。通俗的講,fetch訊號上升沿到來時,num1和num2所在always塊才被觸發,而num3所在always塊檢測的是clk的上升沿,也就是說在fetch訊號被賦值之前該always塊就已經被觸發執行了,因此與fetch訊號的上升沿無關。
4. 修改num3所在always塊程式碼
always @(*)
begin
$display("At%tns, be excuted.",$time);//just for test.
if (fetch)
begin
num3 <= num3 + 1;
end
end
always @(*)的作用與always @(fetch or num3)的作用一致。(*)裡面的敏感變數由綜合器根據always塊裡面的輸入變數自動新增。
此時,進行模擬,發現modelsim報錯。# ** Error: (vsim-3601) Iteration limit reached at time 520 ns.根據$display語句在螢幕的輸出發現,程式在520ns的時候一直在觸發always塊。從上面的波形圖可以發現,520ns為fetch訊號第一次出現上升沿的位置。此時,通過程式碼可以發現,fetch訊號變化則觸發該always塊,並且if (fetch)成立,因此num3的值發生變化,進而又觸發該always塊,陷入死迴圈。
如果把程式碼中的非阻塞賦值改為阻塞賦值,則波形圖如圖所示。為何不會出現死迴圈,因為它被阻塞了,原理參考於開篇所給出的採用阻塞賦值不能自行觸發振盪器。
此時由於觸發條件為fetch訊號或者num3的變化,所以num3與num1和num2同步。如果這裡採用always @(posedge clk)以及阻塞賦值的話,則不會同步。
5. 針對出現死迴圈的問題
module osc(clk);
output clk;
reg clk;
initial #10 clk = 0;
always @(clk)
begin
$display("At%tns, be excuted.", $time);//just for test.
//#10;
clk <= ~clk;
end
endmodule
修改採用非阻塞賦值的自觸發振盪器的程式碼,將#10註釋掉。模擬發現modelsim報同樣的錯誤。# ** Error: (vsim-3601) Iteration limit reached at time 10 ns.
在同一時間,一直進入該always塊,陷入死迴圈,因而報錯。而加上#10延時命令,就可以避免該問題。