跳轉到

1.2 晶片設計流程與演算法角色#

概念說明#

要理解 EDA 演算法,必須先了解晶片從想法到量產的完整流程。這條流程可以想像成一道「抽象層級不斷下降」的階梯:

系統規格 (Specification)
    ↓  高階合成 (HLS)
RTL (Register Transfer Level, Verilog/VHDL)
    ↓  邏輯合成 (Logic Synthesis)
閘級網表 (Gate-level Netlist)
    ↓  實體設計 (Physical Design: 佈局規劃→佈局→時脈樹→繞線)
版圖 (Layout, GDSII)
    ↓  物理驗證 (DRC/LVS) 與簽核 (Signoff)
交付晶圓廠 (Tape-out)

每個向下的箭頭都由一個(或一組)EDA 演算法驅動。以下逐段說明各階段與其核心演算法。

各階段與核心演算法#

1. 高階合成(HLS)#

把 C/C++/SystemC 描述的演算法轉成 RTL。核心問題是排程(Scheduling)(哪些運算放在哪個時脈週期)與資源分配(Allocation)(需要幾個乘法器、加法器)。常用演算法:列表排程(List Scheduling)、力導向排程(Force-Directed Scheduling)。

2. 邏輯合成#

把 RTL 轉成最佳化的邏輯閘網表。核心演算法:

  • 兩層邏輯最小化:Quine-McCluskey、Espresso 啟發式化簡(第2章)。
  • 多級邏輯優化:因式分解、技術映射(Technology Mapping)(第3章)。
  • 技術映射:把與邏輯無關的網表映射到製程庫中的實際閘(如 FA、AOI21),常用「覆蓋(Covering)」動態規劃。

3. 驗證(與合成並行)#

  • 模擬(Simulation):事件驅動演算法(第5.1節)。
  • 等價檢查(LEC):BDD 或 SAT 為基礎(第6章)。
  • 模型檢查:符號模型檢查(第5.3節)。

4. 實體設計(Physical Design)#

這是演算法最密集的階段,依序為:

步驟 問題 代表演算法
佈局規劃 Floorplanning 決定大區塊位置 模擬退火、B* 樹
佈局 Placement 放置數百萬標準胞 最小切割、力導向、解析法(ePlace)
時脈樹合成 CTS 均衡時脈延遲 H-tree、DME 演算法
繞線 Routing 在金屬層拉線 Lee 迷宮、A*、Pattern Routing
簽核 Signoff 時序、功耗、IR 壓降 STA、靜態分析

5. 物理驗證#

DRC(設計規則檢查)本質是幾何計算問題,使用掃描線演算法在 O(n log n) 內找出重疊與間距違規;LVS(佈局與網表比對)是圖同構(Graph Isomorphism)問題的近似版本。

演算法步驟:以「合成到佈局」的資料流為例#

  1. RTL 經過剖析得到運算樹。
  2. 邏輯合成做布林優化(用 BDD/SAT 表示與化簡)。
  3. 技術映射挑選庫單元,產生閘級網表。
  4. 佈局工具讀入網表,以連接圖為輸入,最小化線長。
  5. 繞線工具在多層網格圖上做最短路徑搜尋。
  6. STA 在時序圖上做最長路徑分析。

可以看到一條主軸:每個階段都把設計變成一種圖,然後套用該圖上的最佳化演算法。

Python 程式碼範例:簡單的 STA(靜態時序分析)#

靜態時序分析是簽核階段最重要的演算法:在 DAG 上求最長路徑,判斷時脈週期是否夠長。下面用 Python 實作一個迷你 STA:

from collections import defaultdict

class TimingGraph:
    """節點 = 閘的輸出接腳, 邊 = (延遲, 目標節點)"""
    def __init__(self):
        self.edges = defaultdict(list)  # u -> [(delay, v), ...]
        self.nodes = set()

    def add_edge(self, u, v, delay):
        self.edges[u].append((delay, v))
        self.nodes |= {u, v}

    def longest_paths(self):
        """DAG 最長路徑: 拓撲排序 + DP, O(V+E)"""
        indeg = {n: 0 for n in self.nodes}
        for u in self.edges:
            for _, v in self.edges[u]:
                indeg[v] += 1
        q = [n for n in self.nodes if indeg[n] == 0]
        topo = []
        while q:
            u = q.pop()
            topo.append(u)
            for _, v in self.edges[u]:
                indeg[v] -= 1
                if indeg[v] == 0:
                    q.append(v)
        arrive = {n: 0.0 for n in self.nodes}
        for u in topo:
            for d, v in self.edges[u]:
                arrive[v] = max(arrive[v], arrive[u] + d)
        return arrive

# 電路: in -> g1(0.8) -> g2(1.2) -> FF ; in -> g3(0.5) -> FF
tg = TimingGraph()
tg.add_edge("in", "g1", 0.8)
tg.add_edge("in", "g3", 0.5)
tg.add_edge("g1", "g2", 1.2)
tg.add_edge("g2", "FF", 0.3)
tg.add_edge("g3", "FF", 0.3)

arr = tg.longest_paths()
clock_period = 2.0
slack = clock_period - arr["FF"]
print("到達時間:", {k: round(v, 2) for k, v in arr.items()})
print(f"Slack = {slack:.2f} -> {'MET' if slack >= 0 else 'VIOLATED'}")

輸出:

到達時間: {'in': 0.0, 'g1': 0.8, 'g3': 0.5, 'g2': 2.0, 'FF': 2.3}
Slack = -0.30 -> VIOLATED

臨界路徑(critical path)是 in→g1→g2→FF,延遲 2.3ns 超過 2ns 週期,工具會回報違規並提示設計者優化(例如換更快的閘或重組邏輯)。這正是 EDA「分析—回饋」迴圈的具體實現。

複雜度分析#

  • DAG 最長路徑(STA):O(V+E);但含迴路的圖求最長路徑是 NP-hard,這就是 STA 必須在同步電路(DAG)上進行的原因。
  • 邏輯合成中的布林優化:許多子問題(如最小 DNF)是 NP-hard,工業工具採用啟發式。

練習#

  1. 在 TimingGraph 中加入反向邊會發生什麼?請說明為什麼含迴路時「拓撲排序 + DP」失效。
  2. 實作「臨界路徑回溯」:記錄每個節點的前驅,輸出臨界路徑上的閘名稱清單。
  3. 說明為什麼佈局問題是 NP-hard?(提示:可歸約自圖分割或二次指派問題)