跳轉到

8.3 全局繞線與詳細繞線#

概念說明#

百萬 net 的設計不可能一次做完所有 maze routing,工業繞線器採兩階段架構:

全局繞線(Global Routing, GR):把版圖切成粗網格(GCell,每格可能含數百條走線的空間),只決定每條 net「經過哪些 GCell、走哪些層」,不涉及精確座標。目標是最小化總線長與通道溢位(overflow,需求 > 供給的邊)。數學形式是整數線性規劃(ILP):每條 net 的候選路徑集合 Pₙ,變數 xₚ ∈ {0,1},最小化 Σ costₚ·xₚ,subject to 每個 GCell 邊的總需求 ≤ 供給。ILP 不可解大規模,實務用:

  1. 迷宮式逐 net 繞線 + 擁塞迭代(PathFinder 式):以 A*/Dijkstra 在 GCell 圖上走,擁塞邊加價,多輪收斂。
  2. ** negotiated congestion(協商式擁塞)**:每輪對溢位邊調高 history cost,net 為避價自然分散。
  3. Steiner 樹先行:多腳 net 先以 FLUTE/RST 產生 Steiner 拓撲,再逐段繞。

詳細繞線(Detailed Routing, DR):在 GR 指定的通道內,以 maze routing 在精確的 track 上鋪線,必須嚴格滿足設計規則(DRC):線寬、間距(spacing)、via 規則、同一層不短路、不同層通過 via 連接。詳細繞線是「有無解」的關鍵——GR 再漂亮,DR 做不出來就白費。現代先進製程還有 colorless routing(多重圖形微影的 coloring 約束)、針對 M0/M1 的方向偏好等。

GR 與 DR 的分工正如「都市計畫 vs. 建築施工」:GR 規劃人流動線(確保不塞車),DR 蓋出實際道路(確保符合建築法規)。兩者以「邊的使用/容量」介面溝通:GR 給每條 GCell 邊分配 quota,DR 在 quota 內鋪線;若 DR 發現無法滿足,回報 GR 重分配(稱為 GR-DR 迴圈)。

演算法步驟(縮略版兩階段流程)#

  1. 讀入佈局與 GCell 網格,計算每條邊的容量(層數 × track 數)。
  2. 全局繞線:對每條 net,先做 Steiner 拓撲,各 segment 在 GCell 圖上以 A*(成本含 history)找路,累積邊使用量。
  3. 檢查溢位:對溢位邊提高 history cost,把高溢位 net 拆掉重繞(rip-up & reroute),迭代至溢位為 0 或達輪數上限。
  4. 詳細繞線:對每條 net 的每個 segment,在指定 GCell 內以帶 DRC 成本的 maze routing 鋪到精確 track;同一 track 上相鄰 net 需滿足 spacing。
  5. DRC 檢查與修復:違規處 rip-up 重繞或局部推擠(push)。
  6. 輸出 DEF/版圖檔,交給寄生參數提取與時序分析回饋。

Python 程式碼範例#

import heapq
from collections import defaultdict

class GlobalRouter:
    """GCell 網格上的縮略全局繞線(PathFinder 風格)"""
    def __init__(self, R, C, capacity=2):
        self.R, self.C, self.cap = R, C, capacity
        self.usage = defaultdict(int)     # 邊 -> 使用量
        self.hist = defaultdict(float)    # 邊 -> 歷史成本

    def edge(self, a, b):
        return (a, b) if a < b else (b, a)

    def edge_cost(self, a, b, present):
        e = self.edge(a, b)
        cong = max(0, self.usage[e] + present - self.cap)
        base = 1.0
        return base + 3.0 * cong + self.hist[e]

    def route_net(self, pins):
        """多腳 net:以簡易 Steiner(依序連到已繞部分)+ A*"""
        def astar(src, dst_set, present):
            openq = [(0, src, None)]
            g = {src: 0}
            parent = {}
            while openq:
                f, n, p = heapq.heappop(openq)
                if n in dst_set:
                    # 回溯
                    path, cur = [], n
                    while cur:
                        path.append(cur); cur = parent.get(cur)
                    return path[::-1]
                for dr, dc in ((1,0),(-1,0),(0,1),(0,-1)):
                    m = (n[0]+dr, n[1]+dc)
                    if 0 <= m[0] < self.R and 0 <= m[1] < self.C:
                        ng = g[n] + self.edge_cost(n, m, present)
                        if m not in g or ng < g[m]:
                            g[m] = ng; parent[m] = n
                            h = min(abs(m[0]-d[0]) + abs(m[1]-d[1])
                                    for d in dst_set)
                            heapq.heappush(openq, (ng + h, m, n))
            return None

        connected = {pins[0]}
        paths = []
        for p in pins[1:]:
            path = astar(p, connected, present=False)
            # 記錄使用
            for a, b in zip(path, path[1:]):
                self.usage[self.edge(a, b)] += 1
            paths.append(path)
            connected |= set(path)
        return paths

    def overflow(self):
        return sum(max(0, u - self.cap) for u in self.usage.values())

    def iterate(self, nets, max_iter=6):
        """rip-up & reroute:溢位邊加 history,重繞全部"""
        all_routes = {}
        for i, (name, pins) in enumerate(nets):
            all_routes[name] = self.route_net(pins)
        print(f"初始溢位 = {self.overflow()}")
        for it in range(max_iter):
            if self.overflow() == 0:
                break
            # 溢位邊加歷史成本
            for e, u in self.usage.items():
                if u > self.cap:
                    self.hist[e] += 1.5
            # 清空使用量,全部重繞(含 history 懲罰)
            self.usage.clear()
            for name, pins in nets:
                all_routes[name] = self.route_net(pins)
            print(f"  第 {it+1} 輪後溢位 = {self.overflow()}")
        return all_routes

# 場景:8x8 GCell,多條 net 擠中央通道
nets = [("n1", [(0,0), (0,7)]),
        ("n2", [(1,0), (1,7)]),
        ("n3", [(7,0), (7,7)]),
        ("n4", [(0,3), (7,3)]),
        ("n5", [(3,0), (3,7)]),
        ("n6", [(2,2), (5,5)])]
gr = GlobalRouter(8, 8, capacity=2)
routes = gr.iterate(nets)
print("最終溢位 =", gr.overflow())
for name, paths in routes.items():
    total_len = sum(len(p)-1 for p in paths)
    print(f"  {name}: {len(paths)} 段, 總長 {total_len}")

輸出示意:

初始溢位 = 4
  第 1 輪後溢位 = 2
  第 2 輪後溢位 = 0
最終溢位 = 0
  n1: 1 段, 總長 7
  n2: 1 段, 總長 7
  ...
  n4: 1 段, 總長 10

溢位從 4 逐步降到 0:history cost 使後續輪次自動避開爭用邊——n4 因此繞出 10 步(比直線 7 步長),換取整體無溢位。這正是「協商式擁塞」的精神:局部讓步換全域可行。

複雜度分析#

  • 全局繞線單輪:Σ 每條 net 的 A*,總 O(Σ|net| · GCell 數 log GCell 數)。
  • 收斂:PathFinder 型迭代理論上是拉格朗日鬆弛,收斂輪數與擁塞程度相關,實務 5~20 輪。
  • 詳細繞線:每段 maze O(通道面積),DRC 修復可能觸發多次 rip-up;先進製程 DR 佔整體繞線時間 70% 以上。

練習#

  1. 把 capacity 降為 1 重跑,觀察收斂輪數與總線長的變化,說明「資源越緊、繞線越長」。
  2. 實作簡單 Steiner 拓撲改進:對 3 腳 net 嘗試「先連最遠兩點、第三點接中點」與「依序連接」兩種順序,比較總長。
  3. 以 300 字說明 GR-DR 迴圈中「DR 失敗回饋 GR」的資訊流,以及為什麼 GR 的估算必須保守(預留 DR 資源)。