不知道大家會不會好奇 begin, end 是做什麼的?只是單純要去區分各個區塊,就像是 C 語言的 { }
嗎?
其實沒有那麼簡單!過去我們都是使用 Sequential Block ,所以我們的程式碼才會由上而下執行。還有一種叫做 Parallel Block ,內部的程式碼都是同時進行。
先從我們熟悉的 Sequential Block 說起。這種程式區塊有三個特性:
舉例來說:
initial begin
#5 <statement 1>
#1 <statement 2>
#3 <statement 3>
#2 <statement 4>
end
這段程式碼就是屬於 Sequential Block ,因為他們被 begin, end 包起來。再來看看執行順序,<statement 1> 先經過 5 個時間單位再執行,接著再換 <statement 2> 延遲後執行,以此類推。最終,<statement 4> 相對程式區塊開始時間後 11 個時間單位才被執行。
這類型程式相信大家在前面的案例中,已經見過許多!
再來就是我們不熟悉的 Parallel Block 。這種程式區塊有四個特性:
一樣用上面的例子來說明:
initial fork
#5 <statement 1>
#1 <statement 2>
#3 <statement 3>
#2 <statement 4>
join
這段程式碼就是屬於 Parallel Block ,因為他們被 fork, join 包起來。再來看看執行順序,<statement 1> 先經過 5 個時間單位再執行,但是 <statement 2> 只需經過 1 個時間單位的延遲就可以執行,因此 <statement 2> 較 <statement 1> 更早被執行,以此類推。
Parallel Block 有一個需要克服的問題就是 Race Condition ,一旦有數條具相互關係的程式碼出現,我們無法預期結果為何,因為在現實中,中央處理器 (CPU) 一次只能處理一個指令,因此誰會先被執行,是無法預期的。舉一個例子:
reg x, y, z;
initial fork
x = y;
y = z;
z = x;
join
Nested Blocks 翻成中文是「巢狀區塊」,意思是我們可以在區塊中放置其他區塊。舉例來說:
initial begin
<statement>
<statement>
fork
<statement>
<statement>
join
end
這個功能強化了電路設計的彈行,也許我們的電路想要有一部分是平行處理的,那麼我們就可以將兩種 Blocks 混合做使用,而不是只使用單一種電路。
為什麼我們需要對程式區塊命名?有一個主要的原因是我們有可能需要終止一個區塊的運行。
在 C 語言中,我們想要結束一個程式區塊的運行,會透過關鍵字 break 來執行這個動作,但是 break 只能強制停止它所在的程式區塊。在 Verilog 中,可以透過關鍵字 disable 來停止程式區塊,而且我們可以強至其他程式區塊的運行,相比軟體設計的 break , Verilog 的 disable 更為強大。
先從區塊命名開始,簡單來說就是在 begin 或是 fork 後面加上區塊名字,舉個例子:
initial fork: block1
<statement>
<statement>
join
always begin: block2
<statement>
<statement>
end
有了名字,我們就可以來終止區塊運行。假設我們要比對兩個 reg 所儲存的數值是否一樣,我們可以這麼做:
reg [3:0] a, b;
integer i;
initial begin: block1
a = 4'b1010; b = 4'b1110;
i = 0;
while (i < 4) begin
if (a[i] == b[i])
$display("a[%1d] = b[%1d] = %1d", i, i, a[i]);
else begin
$display("a[%1d] = %1d, b[%1d] = %1d", i, a[i], i, b[i]);
disable block1;
end
i = i + 1;
end
$display("Same!!");
end
在這個例子中,我們可以透過 disable 來決定是否要繼續執行迴圈。如果繼續執行迴圈雖然也沒有問題,但是我們會花費不必要的資源和時間,因此 disable 在這裡扮演很重要的角色。
到目前為止,我們建構了較完整的程式區塊相關概念,相信大家能更加靈活的設計不同種電路。