數位電路大致上分成 2 種,組合電路 (Combinational Circuit) 和循序電路 (Sequential Circuit)。
當一個電路通電,組合電路就開始運作,馬上就可以得到正確的輸出,當外部輸入相同,則每次的輸出都會一樣,就像上一篇文章提到的多工器等。但是循序電路的運作方式比較不一樣,即便外部輸入都是相同的,循序電路的輸出會根據前一次的運算結果而有不同。
「計數器」是一個循序電路中很經典的例子。以一個倒數計數器來說,第一次的運算結果是 N ,下一次會變成 N-1,直到變成 0 為止。但是我們該怎麼區分現在是進到了計數器中的第幾個狀態呢?簡單來說,我們會根據時間來決定當前的結果,時間在電路設計中有一個更專業的術語叫做「時脈」。
試著用 Verilog 來實作 10 ~ 0 的倒數計數器吧!
先假設我們有變數 clk
, reset
, cnt
,而 reset
是用來告知硬體中的暫存器需要被初始化。
module DCounter (
input clk,
input reset
);
reg [3 : 0] cnt;
// reset counter
// counter body
endmodule
因為計數器需要被告知從何處開始倒數,所以我們來處理初始化的部分。Reset 的方式可以分成 2 種:同步 (synchronous) 和非同步 (asynchronous) 。同步的意思是即便我們接收到 reset 訊號,我們也要根據時脈的數值,才可以決定是否重置。非同步的意思則是不論時脈的數值為何,當我們接收到 reset 訊號,就會執行重置。以下我們示範「非同步重置」。
// reset counter
always @(posedge reset) cnt = 4'd10;
此處以
posedge reset
作為 reset 啟動的條件,意思是當 reset 訊號從低電位變為高電位的瞬間,變數cnt
會被初始化為10
。
接著,要來實作計數器的主體。當計數器數到 0 時,計數器會回到 10 ,其餘情形計數器會做「遞減」。
// counter body
always @(posedge clk) begin
if (cnt == 4'd0) cnt <= 4'd10;
else cnt <= cnt - 1;
end
特別注意的是此處的「賦值」使用的是
<=
,和之前使用的=
較為不同。這個議題叫做blocking
和nonblocking
,我們會在下一篇文章提到。若使用錯誤,結果會完全不同。