05.2 ff 區塊與時脈重置:把慣例提升為語言#
同步設計:五十年的共識#
在談 always_ff 之前,先回顧它背後的理論根基。Mealy 與 Moore 在 1950 年代發表的有限狀態機理論,奠定了「狀態 + 輸入 → 下一狀態與輸出」的抽象模型。數位電路的同步設計學派在 1960–70 年代成型:把整個電路的時間切成由一個全域時脈驅動的離散節拍,所有狀態都存在 edge-triggered 正反器(flip-flop)中,所有組合邏輯都必須在一個 clock period 內穩定。這個模型簡化了 timing 分析(只需要檢查 setup/hold,不需要考慮任意組合迴圈的傳播時間),成為 ASIC 與 FPGA 設計的主流典範。
同步設計的代價是時脈樹(clock tree)。在 ASIC 流程中,clock 要送到數十萬甚至上千萬個 flip-flop,clock tree synthesis(CTS)在 placement 之後進行,必須平衡每個葉節點的 skew——skew 控制不好,setup 與 hold violation 會同時出現。CTS 是 ASIC backend 中成本極高的一步,clock network 的功耗也常佔整顆晶片功耗的三到五成。這一切都建立在「每一個 flip-flop 的時脈確實連到同一棵樹」的前提上。因此,「時脈與重置訊號必須與資料訊號嚴格區分」不是潔癖,而是整個同步設計與後端流程的基石。
Verilog 時代的痛:什麼都是 logic#
Verilog 沒有型別系統幫你做這個區分。經典的 Verilog-2001 寫法:
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
q <= 0;
end else begin
q <= d;
end
end
這段程式碼有三個潛在的人為錯誤,而工具全部不報錯:
- 把
posedge clk打成posedge clck——如果剛好有個 wire 叫clck,工具照單全收,每個 flip-flop 掛上錯誤時脈。 - 把
negedge rst_n打成posedge rst_n——非同步重置極性寫反,模擬上重置行為相反,合成上工具推論出不同的 reset cell,可能到 STA 或 gate-level 模擬才爆。 if (!rst_n)忘了寫,或寫成if (rst_n)——重置極性在事件列表與 if 條件中不一致,是最經典的 gate-level 前後不一致來源。
SystemVerilog 的 always_ff 只是加了一個意圖宣告(工具會檢查塊內只能有一次賦值等),對上述問題幫助有限。真正的解法是型別。
Veryl 的作法:clock 與 reset 是型別#
Veryl 把這個慣例提升為語言層級:clock 與 reset 是獨立的型別。埠宣告必須寫明:
module Counter (
clk : input clock,
rst : input reset,
cnt : output logic<8>,
) {
var r_cnt: logic<8>;
always_ff (clk, rst) {
if_reset {
r_cnt = 0;
} else {
r_cnt = r_cnt + 1;
}
}
assign cnt = r_cnt;
}
幾個重點:
clk的型別是clock而非logic。如果有人想把一條資料訊號接到時脈埠上,或是把時脈接到資料埠,編譯器直接報錯。型別系統替你守住了「時脈訊號必須來自時脈來源」這條鐵律。always_ff (clk, rst)中,clk必須是clock型、rst必須是reset型。不存在「拼錯時脈名字」的問題——拼錯的名字如果不是一個clock型的訊號,編譯不過。if_reset是語言內建的「重置分支」。它取代了手寫的if (!rst_n),因此「事件列表極性」與「if 條件極性」永遠一致,因為極性根本不寫在程式碼裡,而是由rst的型別(見下一章)決定。這正是把 1990 年代 coding guideline 中「重置極性寫法一致性」那條人肉準則變成語言保證。
範例:shift register#
module ShiftReg (
clk : input clock,
rst : input reset,
din : input logic,
dout : output logic,
) {
var r_ff: logic<4>;
always_ff (clk, rst) {
if_reset {
r_ff = 0;
} else {
r_ff = {din, r_ff[3:1]};
}
}
assign dout = r_ff[0];
}
注意 Veryl 的串接語法:a << b 等價於 SystemVerilog 的 {a, b}。串接運算子 << / >> 分別對應位元向高位與低位拼接,這在後續章節會反覆出現。
非同步重置寫起來一樣簡單#
在 Verilog 中,同步重置與非同步重置的程式碼差異就在事件列表有沒有 or negedge rst_n,一個字的差異決定了完全不同的電路與 timing 需求(下一章詳談)。在 Veryl 中,這個差異完全隱藏在 reset 型別裡:reset 型的重置埠,其同步/非同步與極性由型別或全域設定決定,always_ff 與 if_reset 的寫法完全相同。工程師寫一次行為描述,reset 策略成為一個可以全域切換的配置選項,而非散佈在數萬行程式碼中的字面量。
小結#
同步設計理論從 Mealy/Moore 機到 CTS 主導的 ASIC backend,走了七十年,其核心不變量始終是「時脈與重置是特殊的、受控的訊號」。Verilog 把它們當成普通 wire,Veryl 用型別把它們標記出來。always_ff (clk, rst) + if_reset 這對組合,讓「時脈/重置慣例」從檢查表上的待辦事項變成編譯器強制執行的不變量。下一章我們深入 reset 型別家族,看同步與非同步重置這場真實的工程論戰。