為硬體加入記憶:狀態
問自己一個樸素的問題:電路是怎麼記住任何東西的?純組合邏輯做不到——接上幾個邏輯閘,輸出不過是*此刻*輸入的一個即時函數。切斷輸入,答案就消失了,就像一台沒有記憶鍵的計算機。要計數、要保住一個結果、要等待下一條指令,硬體需要一種辦法,在成因消失之後仍然守住一個值。
那個被存住的值有一個名字:狀態。帶狀態的電路就是時序邏輯——它的輸出不僅取決於此刻的輸入,還取決於之前發生過什麼。想想電燈開關和門鈴的區別:門鈴只在你按著的時候響(組合邏輯),而開關會*停留*在你撥到的位置(狀態)。本指南的全部工作,就是用邏輯閘搭出那個開關,並給它一個有紀律的時刻去改變。
重新認識正反器
狀態的最小單位是一個位元,而守住它的元件就是正反器——具體來說是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暫存器:守住一個值
一個正反器只守一個位元——拿來當旗標位元還行,存一個數字就沒用了。把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
endmodulealways @(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
建立時間與保持時間:那場時序握手
這是你第一次真正嚐到時序的滋味,而它遠沒有傳說中那麼嚇人。正反器沒法可靠地擷取一個還在變動的值——快門需要場景穩住片刻。具體來說,D輸入必須在時脈邊緣前後的一個小視窗裡保持穩定:邊緣之前一點點,邊緣之後一點點。
這個安靜視窗的兩半各有名字。**建立時間**是 D 在邊緣之前必須保持穩定的時長;**保持時間**是它在邊緣之後必須繼續穩定的時長。違反其中任何一個——讓 D 在這個視窗裡變動——擷取就不再有保證:正反器可能只是鎖住了一個錯誤(但乾淨)的值,也可能落入一種模糊、半解析的狀態,叫做*亞穩態*,此時 Q 徘徊在0和1之間,要經過一段無法預測的延遲才會安定下來。把它想成一張在運動中按下快門拍出的照片:拖影、不可靠、沒法用。
這些你通常不用手算——靜態時序分析替你檢查設計裡的每一個正反器,並報出裕量(剩下的那點時序餘地)。但直覺才是整座地基:相機喀嚓的那一刻,資料必須靜止。把時脈撥得太快,訊號就來不及在限定時間內安定下來——而這恰恰是為什麼每一塊晶片都有一個最高時脈頻率。
同步設計的紀律
所有這些零件,最終匯聚成一個專業習慣:同步設計。這條規則說起來很簡單,卻值得近乎虔誠地遵守——只用一個時脈,讓設計裡的每一個正反器都在它的同一個邊緣上擷取。一道共享的心跳,整塊晶片便齊步向前,一拍走一步。
為什麼要這麼嚴?因為它讓時序可被分析。只用一個時脈,「每個訊號都按時到了嗎?」這個問題,在每個時脈週期裡都有一個乾淨的答案,而時序分析就能機械地把整塊晶片驗證一遍。一旦混進了亂七八糟的時脈,或是任意對訊號邊緣做出反應的邏輯(這叫*非同步*設計),就幾乎沒法驗證了,並且會以那種只在某些晶片上、某些日子裡、某些溫度下才冒出來的臭蟲而臭名昭著。
於是,整塊同步晶片的心智模型是這樣的:一團團組合邏輯在計算下一個值,被一排排暫存器隔開,這些暫存器在時脈邊緣一起拍下快照。計算、擷取、再來——在週期裡計算,在邊緣處擷取。正是這道心跳,把一堆邏輯閘變成了一台穿行於時間之中的機器。