JOVANA
Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

正反器、暫存器與時脈

你已經教會一根導線如何運算,現在該教它記憶了。到目前為止,你搭建的每一個電路都是組合邏輯——輸出瞬間跟隨輸入,沒有過去,沒有未來,只是一道不停的回聲。真實的晶片需要狀態:一個知道自己數到哪裡的計數器,一個保存著你最後寫入值的暫存器。這份狀態由兩個協同運作的新概念帶來——擷取一個位元的正反器,以及那道穩定的心跳、告訴每個正反器何時去看的時脈。本指南為你的硬體加入記憶與節奏,再引入那場安靜的時序握手——建立時間與保持時間——它讓整個系統值得信賴。

為硬體加入記憶:狀態

問自己一個樸素的問題:電路是怎麼記住任何東西的?純組合邏輯做不到——接上幾個邏輯閘,輸出不過是*此刻*輸入的一個即時函數。切斷輸入,答案就消失了,就像一台沒有記憶鍵的計算機。要計數、要保住一個結果、要等待下一條指令,硬體需要一種辦法,在成因消失之後仍然守住一個值

那個被存住的值有一個名字:狀態。帶狀態的電路就是時序邏輯——它的輸出不僅取決於此刻的輸入,還取決於之前發生過什麼。想想電燈開關和門鈴的區別:門鈴只在你按著的時候響(組合邏輯),而開關會*停留*在你撥到的位置(狀態)。本指南的全部工作,就是用邏輯閘搭出那個開關,並給它一個有紀律的時刻去改變。

重新認識正反器

狀態的最小單位是一個位元,而守住它的元件就是正反器——具體來說是D型正反器,數位設計裡的主力。把它想成一台帶快門的相機。鏡頭一直看著場景(也就是D,即*資料*輸入),但照片只在快門喀嚓一聲的那一瞬間才更新。D型正反器正是如此:D上是什麼值,都會在那個銳利的一刻被擷取,然後穩穩地保持在輸出 Q 上,直到下一次喀嚓。

那聲「喀嚓」就是時脈邊緣——時脈線從0跳到1的那一刻。在兩次邊緣之間,D盡可以隨便抖動,正反器一概不理。只有在上升緣,Q才會對 D 拍下一張全新的快照。這就是從組合邏輯躍遷到時序邏輯的那一步:輸出從此按時刻表改變,而不再連續不斷地變。

module dff(input clk, input d, output reg q);
  always @(posedge clk)   // at each rising clock edge...
    q <= d;               // ...Q snapshots D, then holds
endmodule
一個 D 型正反器:一個位元的記憶。在每個時脈上升緣,q取走 d 的值,並一直保持到下一個邊緣。

暫存器:守住一個值

一個正反器只守一個位元——拿來當旗標位元還行,存一個數字就沒用了。把N個正反器並排疊在一起,全都共享同一個[[clock-signal|時脈]],你就得到了一個暫存器:一排快門同時喀嚓,在同一瞬間擷取一個 N 位元的值。一個8位元暫存器,無非就是八個 D 型正反器、把它們的時脈接腳接到一起。邊緣一到,八個一起同時給各自的位元拍下快照——整個位元組作為一體落定。

在 Verilog 裡,你很少真去手工接八個正反器。你只要宣告一個向量,讓合成替你搭出這一排。下面這段程式碼描述了一個8位元暫存器:和單個位元一樣的 `posedge` 擷取,只是更寬。有一條提醒值得你帶過新手的第一天:`reg` 只是 Verilog 給「程序性指定變數」起的名字,並不是記憶的保證——只有當你像這樣在時脈邊緣上給一個 `reg` 指定值時,它才會真正變成一個正反器。把同一個 `reg` 放在組合邏輯區塊裡指定值,合成出來的就是純粹的邏輯閘,根本沒有任何儲存。

module reg8(input clk, input [7:0] d, output reg [7:0] q);
  always @(posedge clk)
    q <= d;        // all 8 bits captured together on the edge
endmodule
一個8位元暫存器:八個 D 型正反器共享一個時脈。整個位元組在每個上升緣被一起擷取。

always @(posedge clk)

這一行就是同步設計的心跳,所以它值得單開一節。`always @(posedge clk)` 告訴工具:*這個區塊裡的硬體只在 `clk` 的上升緣更新。*你在這裡寫下的一切都會變成時序邏輯——它會得到正反器。`posedge` 這個關鍵字是相機的快門;`clk` 就是那根按著穩定節拍去按快門的手指。

在時脈驅動的區塊裡,要用非阻塞指定 `<=`,而不是 `=`。直覺是這樣的:區塊裡每一個 `<=` 都用值去讀自己的右邊,然後所有左邊在邊緣處一起更新——這恰恰是真實正反器的行為,一齊拍下快照。在這裡用 `=`(阻塞指定),你描述的就成了一份按部就班的食譜,而那正是 HDL 想要幫你戒掉的軟體思維。

always @(posedge clk) begin
  q1 <= d;     // all right-hand sides are read FIRST (old values),
  q2 <= q1;    // then all left-hand sides update together at the edge
end
非阻塞指定 <= 模擬的是真實正反器的行為:q2 取到的是舊的 q1,而不是新的。這搭出來的是一個兩級移位,而不是塌成一級。

建立時間與保持時間:那場時序握手

這是你第一次真正嚐到時序的滋味,而它遠沒有傳說中那麼嚇人。正反器沒法可靠地擷取一個還在變動的值——快門需要場景穩住片刻。具體來說,D輸入必須在時脈邊緣前後的一個小視窗裡保持穩定:邊緣之前一點點,邊緣之後一點點。

這個安靜視窗的兩半各有名字。**建立時間**是 D 在邊緣之前必須保持穩定的時長;**保持時間**是它在邊緣之後必須繼續穩定的時長。違反其中任何一個——讓 D 在這個視窗裡變動——擷取就不再有保證:正反器可能只是鎖住了一個錯誤(但乾淨)的值,也可能落入一種模糊、半解析的狀態,叫做*亞穩態*,此時 Q 徘徊在0和1之間,要經過一段無法預測的延遲才會安定下來。把它想成一張在運動中按下快門拍出的照片:拖影、不可靠、沒法用。

這些你通常不用手算——靜態時序分析替你檢查設計裡的每一個正反器,並報出裕量(剩下的那點時序餘地)。但直覺才是整座地基:相機喀嚓的那一刻,資料必須靜止。把時脈撥得太快,訊號就來不及在限定時間內安定下來——而這恰恰是為什麼每一塊晶片都有一個最高時脈頻率。

同步設計的紀律

所有這些零件,最終匯聚成一個專業習慣:同步設計。這條規則說起來很簡單,卻值得近乎虔誠地遵守——只用一個時脈,讓設計裡的每一個正反器都在它的同一個邊緣上擷取。一道共享的心跳,整塊晶片便齊步向前,一拍走一步。

為什麼要這麼嚴?因為它讓時序可被分析。只用一個時脈,「每個訊號都按時到了嗎?」這個問題,在每個時脈週期裡都有一個乾淨的答案,而時序分析就能機械地把整塊晶片驗證一遍。一旦混進了亂七八糟的時脈,或是任意對訊號邊緣做出反應的邏輯(這叫*非同步*設計),就幾乎沒法驗證了,並且會以那種只在某些晶片上、某些日子裡、某些溫度下才冒出來的臭蟲而臭名昭著。

於是,整塊同步晶片的心智模型是這樣的:一團團組合邏輯在計算下一個值,被一排排暫存器隔開,這些暫存器在時脈邊緣一起拍下快照。計算、擷取、再來——在週期裡計算,在邊緣處擷取。正是這道心跳,把一堆邏輯閘變成了一台穿行於時間之中的機器。