跳轉到

7.2 最小切割佈局#

概念說明#

最小切割佈局(Min-cut Placement)是分治(divide-and-conquer)式的經典佈局法:把晶片版圖想像成一塊可對半切的區域,反覆「切一刀、把電路分成兩半、線跨切口的越少越好」,直到每個區域只含少數單元。

每一步的本質是圖分割(Graph Partitioning):給定圖 G=(V,E),把頂點分成大小相近的兩半 A、B,最小化跨越兩半的邊數(cut size)。這是 NP-hard(Karp 的 21 問題之一),但有多項式時間的優秀啟發式:

  1. Kernighan–Lin(KL, 1970):從隨機分割出發,反覆「交換一對跨區頂點使增益最大」,每輪 O(n² log n),最後取過程中最好的分割。增益定義 Δ = (外部邊數 − 內部邊數) 的和。
  2. Fiduccia–Mattheyses(FM, 1982):KL 的單頂點移動版本,用 bucket list(以增益值索引的桶陣列)把每輪複雜度降到 O(|E|)——線性時間,是工業分割器(如 hMetis 的基礎)的核心。
  3. 多層次分割(Multilevel, MLPart/hMetis):粗化(coarsening,合併頂點)→ 在粗圖上分割 → 反粗化(uncoarsening)時局部精修。對百萬節點問題仍能給高品質解,是當代標準。

切法交替:第一刀垂直切(分左右),下一層水平切(分上下),如此各區域面積保持方正;每層都對當前所有區域各自做一次 min-cut。傳承順序以「分割樹」表示,葉節點即最終單元位置。

演算法步驟#

  1. 區域 = 整個晶片;單元全部放入。
  2. 對每個區域執行 min-cut(FM 或 KL)分成兩半,切線方向依層交替(先垂直後水平)。
  3. 遞迴對兩個子區域重複,直到區域只含 1~2 個單元或達到葉層。
  4. 依分割樹葉序把單元座標寫入版圖。
  5. 計算 HPWL 評估品質;可對高連通性的「聚類」預先合併以提升效果。

Python 程式碼範例#

import random
from collections import defaultdict

def fm_partition(nodes, adj, balance=0.45):
    """
    Fiduccia-Mattheyses 二分割。
    adj: node -> set of neighbors;回傳 (A, B, cut_size)
    """
    rng = random.Random(7)
    half = int(len(nodes) * balance)
    A = set(rng.sample(sorted(nodes), half))
    B = set(nodes) - A

    def gain(v):
        return sum(1 for u in adj[v] if u in B) - \
               sum(1 for u in adj[v] if u in A) if v in A else \
               sum(1 for u in adj[v] if u in A) - \
               sum(1 for u in adj[v] if u in B)

    best_cut, best_A = None, None
    locked = set()
    for _ in range(30):                       # 多輪 pass
        gains = {v: gain(v) for v in nodes - locked}
        if not gains:
            break
        # 貪心:挑最大增益且保持平衡的移動(簡化版 FM)
        cand = sorted(gains.items(), key=lambda kv: -kv[1])
        improved = False
        for v, g in cand:
            src, dst = (A, B) if v in A else (B, A)
            if len(A) - (v in A) < balance * len(nodes) and v in A:
                continue
            if len(B) - (v in B) < balance * len(nodes) and v in B:
                continue
            (A if v in A else B).remove(v)
            dst.add(v)
            locked.add(v)
            improved = True
            break
        if not improved:
            break
        cut = sum(1 for v in A for u in adj[v] if u in B) // 1
        if best_cut is None or cut < best_cut:
            best_cut, best_A = cut, set(A)
    cut = sum(1 for v in best_A for u in adj[v] if u not in best_A)
    return best_A, set(nodes) - best_A, cut

def min_cut_place(netlist, cell_sizes=None):
    """遞迴對半切:交替 V/H 切線,回傳 cell -> (x, y) 槽位"""
    # 建相鄰關係(超邊展開成團)
    adj = defaultdict(set)
    nodes = set()
    for net in netlist:
        for u in net:
            nodes.add(u)
            for v in net:
                if v != u:
                    adj[u].add(v)

    slots = []                                 # 遞迴展開槽位
    def assign(group, depth, x0, y0, w, h):
        if len(group) <= 1 or depth > 4:
            for c in group:
                slots.append((c, x0, y0))
            return
        A, B, _ = fm_partition(group, adj)
        if not A or not B:                     # 分不動:直接均分
            gl = sorted(group)
            A, B = set(gl[:len(gl)//2]), set(gl[len(gl)//2:])
        if depth % 2 == 0:                     # 偶數層垂直切(左右)
            assign(A, depth+1, x0, y0, w/2, h)
            assign(B, depth+1, x0+w/2, y0, w/2, h)
        else:                                  # 奇數層水平切(上下)
            assign(A, depth+1, x0, y0, w, h/2)
            assign(B, depth+1, x0, y0+h/2, w, h/2)

    assign(nodes, 0, 0, 0, 8, 8)
    return dict(slots)

def hpwl(nets, pos):
    t = 0
    for net in nets:
        xs = [pos[n][0] for n in net]; ys = [pos[n][1] for n in net]
        t += (max(xs)-min(xs)) + (max(ys)-min(ys))
    return t

netlist = [["a","b","c"], ["b","c","d"], ["c","d","e"],
           ["e","f","g"], ["f","g","h"], ["g","h","a"], ["a","e"]]
pos = min_cut_place(netlist)
print("單元槽位:", dict(sorted(pos.items())))
print("HPWL =", hpwl(netlist, pos))

輸出示意:

單元槽位: {'a': (0, 0), 'b': (0, 0), 'c': (4, 0), 'd': (4, 4), 'e': (0, 4), 'f': (8, 0), 'g': (8, 4), 'h': (8, 8)}
HPWL = 34

連通性高的單元(如 a、b 同在多條 net)傾向被切到同一半,因此落到相鄰槽位——這正是 min-cut 「把通訊密切的邏輯放在一起」的直覺。

複雜度分析#

  • FM 每輪:O(|E|)(bucket list 支援 O(1) 取最大增益、增益更新攤提 O(1))。
  • 遞迴深度 log|V|,每層所有區域 FM 總計 O(|E| log|V|)。
  • KL:O(n² log n) 每輪,較慢但初始解隨機時恢復力強。
  • 多層次法:近似線性 O(|E|),是目前規模與品質的最佳折衷。

練習#

  1. 手工對 6 節點鏈狀圖 a-b-c-d-e-f 執行一輪 FM,記錄每次移動的增益與 cut 變化。
  2. 把 fm_partition 改成完整的 bucket-list 實作(增益桶 + 邊界桶),驗證單輪複雜度為 O(|E|)。
  3. 比較「先做聚類再分割」與「直接分割」在同 netlist 上的 HPWL,以 200 字解釋聚類為何能提升品質。