邏輯合成:為什麼寫程式就能變出電路

modern%20workspace%20with%20laptop%2C%20smartphone...
發表時間:2026 年 10 月 25 日 | 更新日期:2026 年 10 月 25 日 | 編輯:雅寶社區編輯團隊
邏輯合成:為什麼寫程式就能變出電路 - 雅寶社區 · 頂客論壇

布林代數提供了化簡的法則,例如 A | (A & B) = A、A & (A | B) = A 等等。合成工具在把 RTL 轉成邏輯閘之後,就會大量套用這類化簡規則,把多餘的邏輯刪掉,讓電路更小、更快、更省電。可以說,邏輯合成的第一步,就是把你的程式碼「化約」成最小化的布林函數集合,然後再對應到實際的邏輯閘。

2-2 標準元件庫:合成結果的積木

合成工具不會無中生有地發明邏輯閘,它必須依照晶圓廠提供的「標準元件庫」(Standard Cell Library)來選擇可用的積木。標準元件庫裡通常包含:基本邏輯閘(AND、OR、NOT、NAND、NOR、XOR)、多工器(MUX)、正反器(D Flip-Flop)、栓鎖器(Latch)、緩衝器(Buffer)等。每個元件都會標示它的面積、延遲、功耗、驅動能力等參數。

合成器在做轉換時,會根據時序約束(Timing Constraints)與面積目標,從元件庫中挑選最合適的組合。例如同樣是實作一個 AND 功能,可以選用 2 輸入 AND 閘,也可以用 NAND 閘加上反相器兜出來;合成器會根據延遲與面積成本做取捨。這就是為什麼同一份 RTL 程式碼,換一個製程或換一組約束條件,合成出來的電路可能完全不同。

2-3 從行為描述到閘級網表

邏輯合成的本質,是一個「分階段轉換」的過程,大致可以分成以下幾個步驟:

  • 解析(Parsing):讀取 RTL 程式碼,建立抽象語法樹(AST),並進行語意檢查。
  • elaboration:展開參數、展開 generate 區塊、解析模組間的連接關係,把階層式設計攤平成完整的電路模型。
  • 行為合成:把 always 區塊、if-else、case 敘述等行為描述,轉換成有限狀態機(FSM)、資料路徑(Datapath)與控制邏輯。
  • 邏輯最佳化:套用布林化簡、常數傳遞、死碼消除、共同子運算式共享等手法,降低邏輯複雜度。
  • 技術對映(Technology Mapping):把最佳化後的邏輯函數,對應到標準元件庫中實際存在的閘。
  • 輸出網表:產生閘級網表檔案(常見格式如 Verilog netlist、DEF、EDIF 等),交給後續實體設計使用。
  • 這整個流程,其實就是把「人類意圖」一層層翻譯成「機器結構」的過程。中間每一個步驟都有大量演算法在運作,而每一步都有對應的最佳化目標:面積小、速度快、功耗低。這也解釋了為什麼合成是一門需要經驗的技術——同樣的程式碼,不同的寫法與約束,結果可以天差地遠。

    三、Verilog 與 VHDL:用程式描述電路

    硬體描述語言(HDL)是邏輯合成的輸入語言。最常見的兩種是 Verilog(以及其後繼 SystemVerilog)與 VHDL。它們看起來像程式語言,但語意上其實是在「描述硬體結構與行為」。這是很多初學者最容易卡關的地方:你寫的不是指令,而是電路。

    3-1 可合成與不可合成的語法

    並非所有 HDL 語法都能被合成。HDL 同時被用在模擬與合成兩個用途,有些語法只是為了模擬方便而存在,例如 $display、$finish、initial 區塊中的延遲控制、#10 時間延遲、force/release 等等。這些在合成時會被忽略或直接報錯。

    可合成的語法,基本上必須能被對應到具體的硬體結構:

    assign:連續賦值,通常對應組合邏輯。

  • always @(*):組合邏輯區塊,內含 if-else 或 case。
  • always @(posedge clk):時序邏輯區塊,對應正反器。
  • module 實例化:模組階層連接。

    generate:根據參數展開重複結構。

    function / task:可合成,但有限制。

    合成工具通常會提供 lint 檢查或合成警告,告訴你哪些語法無法對應到硬體。養成「先想電路、再寫程式」的習慣,可以大幅減少這類問題。

    3-2 組合邏輯的寫法

    組合邏輯的輸出只取決於當下的輸入,沒有記憶功能。最常見的寫法有兩種:使用 assign 的連續賦值,以及使用 always @(*) 區塊搭配 if-else 或 case。例如一個 4 對 1 多工器,可以寫成:

    assign y = sel[1] ? (sel[0] ? d3 : d2) : (sel[0] ? d1 : d0);

    也可以用 case 敘述來描述:

    always @(*) begin

    case (sel)

    2'b00: y = d0;

    2'b01: y = d1;

    2'b10: y = d2;

    2'b11: y = d3;

    endcase

    end

    兩種寫法合成的電路通常會非常相似,合成器會把它們都轉成一組多工器或邏輯閘網路。實務上建議:簡單的組合邏輯用 assign,複雜的決策逻辑用 always @(*),並且要記得把所有分支寫滿,避免產生非預期的栓鎖器(Latch)。

    3-3 時序邏輯的寫法

    時序邏輯會用到正反器,最典型的就是 always @(posedge clk) 區塊。一個簡單的 D 型正反器可以寫成:

    always @(posedge clk) begin

    if (!rst_n)

    q <= 1'b0;

    else

    q <= d;

    end

    這段程式碼會被合成器辨識成一個帶有非同步重置的 D 正反器。合成器會根據程式碼中的觸發條件(posedge clk)與重置邏輯,對應到標準元件庫中的正反器元件。值得注意的是,時序邏輯區塊內通常建議使用非阻塞賦值(<=),組合邏輯區塊內使用阻塞賦值(=),這是業界廣泛遵循的 coding style,可以避免模擬與合成結果不一致的問題。

    更複雜的狀態機通常會被拆成三個 always 區塊:一個描述狀態暫存器(時序邏輯)、一個描述次態邏輯(組合邏輯)、一個描述輸出邏輯(組合或時序)。這樣的寫法結構清楚,合成結果也比較可預期。

    四、邏輯合成工具實際在做什麼

    理解了 HDL 的角色之後,我們可以更進一步看看合成工具內部的運作。合成器不是單純的翻譯機,它同時扮演「最佳化器」的角色。同樣的 RTL,經過不同策略的合成,可能得到面積差 30%、頻率差 20% 的結果。

    4-1 最佳化的四個主要手法

    合成工具的最佳化大致可以分成以下幾個層面:

  • 邏輯化簡:利用布林代數與卡諾圖概念,把邏輯函數化到最簡。例如把 A & 1 簡化成 A,把相同的子運算式合併。
  • 資源共享:當同一個運算單元在不同時間點被使用時,合成器可以把它們共用,降低面積。例如兩個加法器在互斥的條件下運作,可以合併成一個。
  • 管線化與重定時(Retiming):合成器可以搬移正反器的位置,讓關鍵路徑變短、頻率提高。這是進階最佳化手法,通常需要搭配時序約束。
  • 技術對映與面積/速度取捨:根據約束條件,選擇合適的標準元件。例如關鍵路徑上選用驅動能力較強但面積較大的閘,非關鍵路徑則選用小面積元件。
  • 這些最佳化手法彼此會互相牽制。提高頻率通常會增加面積與功耗,降低面積可能會拉長延遲。合成工程師的工作,就是設定適當的約束條件,讓工具在多重目標之間找到平衡點。

    4-2 約束條件與時序分析

    合成工具需要知道「你的電路要跑多快」,這個資訊是透過時序約束(Timing Constraints,常見格式為 SDC)來提供的。最基本的約束包括:

    時脈定義:create_clock,指定時脈週期與波形。

  • 輸入/輸出延遲:set_input_delay、set_output_delay,描述外部電路的時序關係。
  • 時序例外:set_false_path、set_multicycle_path,告訴工具哪些路徑不需要分析。
  • 面積與功耗目標:部分工具支援設定最大面積或功耗上限。

    合成完成後,工具會產生時序報告,列出每條路徑的 Slack(餘裕)。Slack 為負代表時序不滿足,需要回頭修改 RTL 或調整約束。這個「合成—分析—修正」的迴圈,是 IC 設計日常中最常見的工作模式。

    五、寫程式與寫電路的思維差異

    對軟體工程師來說,學習 HDL 最大的障礙往往不是語法,而是思維方式。軟體程式是「依序執行」的指令序列,硬體描述則是「同時存在」的電路結構。這個差異會導致許多初學者寫出「看起來會動、合成後卻爆炸」的程式碼。

    5-1 並行思維與硬體推論

    在 Verilog 中,多個 always 區塊與 assign 敘述是同時運作的,它們描述的是並行的硬體。當你寫下:

    assign y1 = a & b;

    assign y2 = c | d;

    這兩行不是先後執行,而是兩個同時存在的邏輯閘。同樣地,兩個 always @(posedge clk) 區塊也是兩個獨立的時序邏輯,合成器會把它們變成兩組正反器。這種「所寫即所見」的對應關係,是硬體描述語言最重要的特性。

    因此,在寫 RTL 時,你應該在腦中想像電路的樣子:這裡需要一個多工器、那裡需要一組暫存器、這條路徑會不會太長。這種「硬體推論」的能力,需要透過不斷練習與閱讀合成報告來累積。

    5-2 常見的地雷與最佳實踐

    以下整理幾個初學者最常踩到的地雷:

  • 組合邏輯產生 Latch:在 always @(*) 區塊中,若 if 沒有 else、case 沒有 default,合成器會推論出栓鎖器來「記住」先前的值。多數情況下這不是你要的,會造成時序問題。
  • 阻塞與非阻塞賦值混用:在同一個 always 區塊中混用 = 與 <= 會導致模擬與合成結果不一致。請遵守「組合邏輯用 =、時序邏輯用 <=」的原則。
  • 敏感度清單不完整:使用 always @(a or b) 時若漏掉某個輸入,模擬結果會與合成結果不同。建議一律使用 always @(*) 或 always_comb。
  • 非預期的回授路徑:組合邏輯區塊中若變數同時出現在等號兩邊,可能產生震盪或非預期回授,合成工具通常會警告。
  • 初始化與重置:FPGA 與 ASIC 對暫存器初始值的處理不同,撰寫時應明確使用重置訊號,避免依賴初始值。
  • 時脈域交叉:跨時脈域的訊號必須經過同步器處理,否則會產生亞穩態(Metastability)問題。合成工具不會幫你解決這個問題。
  • 要寫出好的可合成 RTL,建議養成以下習慣:先畫電路架構圖再寫程式、使用 lint 工具檢查、閱讀合成報告中的警告、以及針對關鍵模組進行合成與時序驗證。這些習慣能讓你在設計早期就發現問題,避免後段除錯的痛苦。

    六、結論:程式與電路之間的橋樑

    回到最初的問題:為什麼寫程式就能變出電路?答案其實不神秘——因為硬體描述語言本質上就是一種「描述電路結構與行為」的語言,而邏輯合成工具則是把這種描述轉換成實際邏輯閘網路的翻譯器與最佳化器。從布林代數的化簡、標準元件庫的對映、到時序約束下的最佳化,每一個環節都有嚴謹的理論與演算法支撐。

    對學習者而言,理解邏輯合成的最大收穫,不只是知道工具怎麼運作,更是建立起「以硬體角度思考」的能力。當你能夠在寫下一行 RTL 時,同時想像它會變成什麼樣的邏輯閘、佔多少面積、跑多快,你就真正跨過了軟體與硬體之間的門檻。這也是為什麼業界常說:好的 RTL 設計師,腦中隨時都有一張電路圖。

    下一次當你按下合成按鈕,看著工具吐出成千上萬行的閘級網表時,不妨想想這背後的旅程:從一行行為描述,到布林函數,到邏輯閘,到標準元件,最後成為晶片上真實存在的電路。這正是電子設計自動化最迷人的地方,也是現代半導體產業得以持續推進的關鍵基礎。

    🏠 返回首頁