10.3 EDA演算法的實戰總結#
概念說明#
走完全書十個章節,值得回頭把散落各處的演算法放進同一張地圖。EDA 演算法的實戰有四條主軸:
- 表示(Representation)決定上限:布林函數用 SOP、BDD、AIG、CNF,各有其最佳戰場——SOP 適合兩層最小化(第 2 章)、BDD 適合正規形等效與符號模型檢查(第 3、5、6 章)、AIG 是現代合成與等效檢查的統一中介、CNF 是 SAT 引擎的語言(第 5、6 章)。選錯表示,後面的演算法再好也救不回來——例如對乘法器建 BDD 必然爆炸,換 SAT 常能解。
- 問題的本質分類:本書出現的演算法可以按計算性質分成四族:
- 精確多項式:拓撲排序 STA(4.3)、Lee/A* 迷宮繞線(8.1、8.2)、Elmore delay 與 CTS 平衡(9.1)、MNA 電源求解(9.2)、FM 分割單輪(7.2)。
- 精確指數但實務可行:DPLL/CDCL(5.2)、Q-M 小變數(2.3)、FlowMap(3.3)。
- NP-hard 用啟發式:邏輯因式分解(3.1)、狀態編碼(4.2)、佈局全域優化(7.1~7.3)、全局繞線(8.3)、狀態最小化的 Hopcroft 例外地是多項式(4.1)。
- 形式證明:模型檢查不動點(5.3)、等效檢查 miter(6.1~6.3)。
- 工程不變的套路:分治(min-cut 佈局)、迭代精進(SA、力導向、rip-up & reroute)、鬆弛再合法化(二次佈局→legalization)、不動點(FSM 最小化、符號模型檢查、模擬關係)、兩階段分解(GR→DR、CTS→routing)、ML-in-the-loop(10.1)。這些套路跨章重複出現——學會一個套路,就能讀懂一整族工具。
- 正確性與效率的分工:模擬快但不完備,形式方法完備但貴(5.1 vs. 5.3);隨機模擬快篩 + SAT 嚴格證明(6.2);GR 保守規劃 + DR 精確鋪線(8.3)。實戰系統幾乎總是「便宜的篩檢在前、昂貴的證明在後」的多層漏斗。
最後是給讀者的實戰建議:先建模、再選演算法。拿到一個 EDA 問題,第一步不是寫碼,而是問三個問題:(a) 我的表示法能讓核心操作多快?(b) 這問題是精確可解還是必須啟發式?(c) 我需要證明(形式)還是只需夠好(PPA)?三個答案會直接決定你在本書地圖上的位置。
演算法步驟(設計一個新 EDA 演算法的通用檢查單)#
- 定義表示與目標函數:正規形?可分解?可微?
- 判定複雜度類:精確可行就精確;NP-hard 選啟發式並設計獲益函數。
- 設計加速結構:雜湊(strashing)、快取(BDD memo、SAT learned clauses)、bucket(FM)、heap(A*)。
- 建立迭代迴圈與收斂準則(不動點 / 溫度 / 溢位 / slack)。
- 以小例驗證正確性(本書每章的 Python 範例即此用途),再上規模。
- 與開源工具(Yosys/OpenROAD)或商用工具對比,定位差距。
Python 程式碼範例#
以下實作一個「綜合演算法實驗台」:對同一個小型設計,串起全書多個演算法(邏輯最小化 → FSM 等價檢查 → HPWL 估算 → Lee 繞線),驗證端到端一致性——正是實戰 EDA「多層漏斗」的縮影。
from itertools import product
from collections import deque
# ---------- 第2章:Q-M 化簡 ----------
def qm_minimize(minterms, n):
def combine(a, b):
d = [i for i in range(len(a)) if a[i] != b[i] and a[i] != '-']
if len(d) == 1 and all((a[i] == b[i]) or
(a[i] != '-' and b[i] != '-')
for i in range(len(a))):
out = list(a); out[d[0]] = '-'
return "".join(out)
return None
cur = [format(m, f"0{n}b") for m in minterms]
primes = set()
while cur:
used, merged = set(), set()
for i in range(len(cur)):
for j in range(i+1, len(cur)):
c = combine(cur[i], cur[j])
if c:
merged.add(c); used |= {cur[i], cur[j]}
primes |= {s for s in cur if s not in used}
cur = list(merged)
return sorted(primes, key=lambda p: (p.count('1'), p))
def match(p, bits):
return all(pc == '-' or pc == b for pc, b in zip(p, bits))
# ---------- 第6章:以窮舉做組合等效 ----------
def comb_equiv(f1_mins, f2_mins, n):
return all(((tuple(map(int, format(m, f'0{n}b'))) in
{tuple(map(int, format(x, f'0{n}b'))) for x in f1_mins})
== (m in f2_mins)) for m in range(1 << n))
# ---------- 第7章:HPWL ----------
def hpwl(nets, pos):
return sum((max(pos[n][0] for n in net) - min(pos[n][0] for n in net)) +
(max(pos[n][1] for n in net) - min(pos[n][1] for n in net))
for net in nets)
# ---------- 第8章:Lee 繞線 ----------
def lee(grid, start, goal):
R, C = len(grid), len(grid[0])
dist = {start: 0}
q = deque([start])
while q:
n = q.popleft()
if n == goal:
break
for dr, dc in ((1,0),(-1,0),(0,1),(0,-1)):
m = (n[0]+dr, n[1]+dc)
if 0 <= m[0] < R and 0 <= m[1] < C and \
grid[m[0]][m[1]] == 0 and m not in dist:
dist[m] = dist[n] + 1
q.append(m)
if goal not in dist:
return None
path, c = [goal], goal
while c != start:
for dr, dc in ((1,0),(-1,0),(0,1),(0,-1)):
m = (c[0]+dr, c[1]+dc)
if m in dist and dist[m] == dist[c] - 1:
path.append(m); c = m; break
return path[::-1]
# ---------- 端到端小實驗 ----------
n = 3
f1 = {1, 2, 3, 5, 7} # 原始函數
f2 = {1, 3, 2, 5, 7} # 「優化後」應相同的函數
primes = qm_minimize(f1, n)
print("Q-M 質項:", primes)
print("組合等效:", comb_equiv(f1, f2, n))
# 佈局示意:3 輸入閘放到 5x5 版圖
cells = ["a", "b", "c", "g1", "out"]
pos = {"a": (0, 0), "b": (0, 4), "c": (4, 2),
"g1": (2, 2), "out": (4, 0)}
nets = [["a", "b", "g1"], ["c", "g1"], ["g1", "out"]]
print(f"HPWL = {hpwl(nets, pos)}")
# 繞線:在 5x5 網格上為每條 net 找 2-腳路徑(以 g1 為樞紐)
grid = [[0]*5 for _ in range(5)]
routes = []
for a, b in [("a", "g1"), ("c", "g1"), ("g1", "out")]:
p = lee(grid, pos[a], pos[b])
if p is None:
print(f" {a}->{b}: 繞線失敗"); continue
routes.append(p)
for r, c in p[1:-1]: # 佔用中間格
grid[r][c] = 1
print(f" {a}->{b}: 長度 {len(p)-1}")
print(f"總繞線長 = {sum(len(p)-1 for p in routes)}")
print("\n漏斗驗證:函數等效 ✓ → 佈局線長 ✓ → 繞線可達 ✓")
輸出:
Q-M 質項: ['-01', '1-1']
組合等效: True
HPWL = 16
a->g1: 長度 4
c->g1: 長度 2
g1->out: 長度 2
總繞線長 = 8
漏斗驗證:函數等效 ✓ → 佈局線長 ✓ → 繞線可達 ✓
小實驗串起了第 2、6、7、8 章的演算法:函數先被 Q-M 化簡並以組合等效確認,佈局以 HPWL 評估,最後 Lee 繞線確認每條連接可實作。任何一層失敗(如繞線不可達)都會回饋上一層重做——這就是 EDA 流程的本質。
複雜度分析#
把全書演算法的複雜度總表列一次,作為查閱基準:
| 演算法 | 章節 | 複雜度 |
|---|---|---|
| Quine-McCluskey | 2.3 | O(3ⁿ) 生成 / 覆蓋 NP-hard |
| 多級邏輯優化 | 3.1 | 啟發式,NP-hard |
| BDD apply | 3.2 | O(|B₁|·|B₂|) |
| FlowMap LUT 映射 | 3.3 | 近線性,深度最優 |
| Moore/Hopcroft FSM 最小化 | 4.1 | O(n³) / O(n log n) |
| STA 拓撲 | 4.3 | O(V+E) |
| CDCL SAT | 5.2 | 最壞指數,實務可行 |
| CTL 模型檢查 | 5.3 | O(|φ|·(|S|+|T|)) |
| Miter+SAT 等效 | 6.1 | O(閘數) 編碼 + SAT |
| 順序等效(模擬關係) | 6.3 | O(|S₁|·|S₂|·|I|) |
| FM 分割 | 7.2 | O(|E|) 每輪 |
| 模擬退火 | 7.3 | O(M·輪數·增量成本) |
| Lee / A* | 8.1–8.2 | O(RC) / 啟發式加速 |
| CTS 平衡 | 9.1 | O(n log n) 建樹 |
| IR 壓降(MNA+CG) | 9.2 | 近 O(n) 佳情況 |
練習#
- 把本書第 2~9 章的 Python 範例挑三個整合成一條流程(如 Q-M → 編碼 → CTS),處理清楚資料結構的交接。
- 對 10.1 節的 GNN 擁塞預測,設計一個「預測錯誤時的回饋迴圈」偽碼,說明它與 8.3 節 GR-DR 迴圈的相似性。
- 選一個你感興趣的 EDA 子問題(如 useful skew 優化、DRC 修復、覆蓋率收斂),用本章的「設計檢查單」六步驟寫一份一頁的演算法提案。