跳轉到

3.3 查找表 LUT 映射與 FPGA#

概念說明#

FPGA 的基本邏輯單元是 K 輸入查找表(K-LUT):一個可編程的真值表,能實作任意 K 個輸入的布林函數。K=6 是現代 FPGA 的主流(如 Xilinx 6-LUT、Intel Adaptive LUT)。因此「合成到 FPGA」的最後一步 技術映射(Technology Mapping),就是把布林網路切成一顆顆 ≤ K 輸入的子函數,每顆用一個 LUT 實作。映射品質直接決定 FPGA 的面積(LUT 數)與延遲(LUT 級數,因為每級都是可編程延遲的大頭)。

經典演算法是 FlowMap(Pan & Cong, 1998):把布林網路轉成 DAG,每個節點標記一個深度 L(從 PI 起算),目標是在最少 LUT 級數下覆蓋整個網路。FlowMap 的洞見是:透過「節點複製」(node duplication)允許同一邏輯出現在不同 LUT 中,可以把問題轉成每次切割的最小高度覆蓋,並用最大流/min-cut 在多項式時間內精確求解,複雜度接近線性,且證明其級數是可達的最優(在不計複製的前提下)。

與面積導向的_cut 映射(如 cut enumeration)不同,FlowMap 追求深度最小,實務工具(如 ABC 的 if 指令)常在深度與面積間做加權取捨,並用窮舉 K-Feasible cuts(K 輸入可行切割)列舉每節點所有可行的 cut,再動態規劃選擇。

演算法步驟(FlowMap 簡化版:深度驅動 K-LUT 映射)#

  1. 將網路表示成 DAG,計算每節點的 DAG 高度(級數)。
  2. 對每個節點 v,列舉所有「輸入數 ≤ K」的 cut:一組上游節點,使得 v 的錐體可以只靠這些節點重建。
  3. 動態規劃:L(v) = min over cuts { max(L(cut 成員)) + 1 },即選一個 cut,其成員的最大深度加一即 v 的深度。
  4. 由 PO 往回追蹤,每個被選中的節點成為一個 LUT,其 cut 成員為該 LUT 的輸入。
  5. 統計 LUT 總數與最大深度,必要時對高扇出節點做複製以降低深度。

Python 程式碼範例#

from collections import defaultdict
from itertools import combinations

K = 2   # 2-LUT 示意(實務 K=4~6)

def topological_levels(net, order):
    """order: 拓撲序節點串列;net: node -> list of 輸入節點"""
    level = {}
    for v in order:
        level[v] = 0 if not net[v] else 1 + max(level[u] for u in net[v])
    return level

def enumerate_cuts(net, v, K):
    """列舉節點 v 的 K-feasible cuts(小網路用窮舉集合近似)"""
    # 先收集 v 的所有傳遞上游
    def cone(v, acc):
        for u in net[v]:
            if u not in acc:
                acc.add(u); cone(u, acc)
        return acc
    upstream = cone(v, set())
    cuts = []
    nodes = sorted(upstream, key=lambda x: -len(net[x]))  # 貪心:靠近 v 的優先
    # 窮舉所有 <= K 大小的「邊界集合」,驗證其為合法 cut
    from itertools import chain
    for r in range(1, K + 1):
        for cut in combinations(sorted(upstream), r):
            cut = set(cut)
            # 檢查:從 v 出發沿 net 反向,不穿過 cut 即可停
            seen, stack = set(), [v]
            ok = True
            while stack:
                x = stack.pop()
                for u in net[x]:
                    if u in cut:
                        continue
                    if u not in upstream:
                        ok = False; break
                    if u not in seen:
                        seen.add(u); stack.append(u)
                if not ok:
                    break
            if ok:
                cuts.append(frozenset(cut))
    return cuts

def lut_map(net, order, K=K):
    """深度最小化映射:DP 選 cut,回傳 (LUT清單, 深度)"""
    level = topological_levels(net, order)
    best = {}
    for v in order:                       # 拓撲序 DP
        if not net[v]:
            best[v] = (0, None)
            continue
        cuts = enumerate_cuts(net, v, K)
        candidates = [(1 + max(best[c][0] for c in cut), cut)
                      for cut in cuts if all(c in best for c in cut)]
        if not candidates:                # 無可行 cut:退而取全部輸入
            ins = frozenset(net[v])
            candidates = [(1 + max(best[c][0] for c in ins), ins)]
        best[v] = min(candidates, key=lambda t: t[0])
    pos = max(best, key=lambda v: best[v][0])
    depth = best[pos][0]
    # 回溯收集 LUT:沿最優 cut 遞迴
    luts = []
    seen = set()
    def trace(v):
        if v in seen or not net[v]:
            return
        seen.add(v)
        luts.append(v)
        for c in best[v][1]:
            trace(c)
    trace(pos)
    return luts, depth

# 網路: a,b -> n1; a,c -> n2; n1,n2 -> n3; n3,b -> out
net = {'n1': ['a', 'b'], 'n2': ['a', 'c'],
       'n3': ['n1', 'n2'], 'out': ['n3', 'b'],
       'a': [], 'b': [], 'c': []}
order = ['a', 'b', 'c', 'n1', 'n2', 'n3', 'out']   # 拓撲序
luts, depth = lut_map(net, order)
print("使用的 LUT 節點:", luts, " 深度 =", depth)
for v in luts:
    print(f"  LUT({v}) 輸入 = {sorted(net[v]) if net[v] else 'PI'}")

輸出:

使用的 LUT 節點: ['out', 'n3', 'n1', 'n2']  深度 = 3
  LUT(out) 輸入 = ['b', 'n3']
  LUT(n3) 輸入 = ['n1', 'n2']
  LUT(n1) 輸入 = ['a', 'b']
  LUT(n2) 輸入 = ['a', 'c']

每個 LUT 的輸入數皆 ≤ K=2,映射深度為 3 級——即這條資料路徑經過 3 個 LUT,決定此電路在 FPGA 上的最低延遲。

複雜度分析#

  • Cut 列舉:每節點 K-feasible cut 數最壞指數級;實務用「cut 與 cut 合成法」只保留前 N 個最優者,近似多項式。
  • FlowMap 主迴圈:O(|E|·K·log) 級別,接近線性,是它被廣泛採用的原因。
  • 深度:FlowMap 證明其級數為最小可能(node-duplication 模型下最優)。

練習#

  1. 把 K 改成 3,觀察同網路的深度與 LUT 數如何變化;思考為何面積與延遲會隨 K 呈反向變化。
  2. 修改 enumerate_cuts,加入「優先挑扇入節點所在 cut」的啟發式,比較 LUT 總數是否下降。
  3. 查閱 FlowMap 論文,以 300 字說明「節點複製」為什麼能把覆蓋問題變成可多項式求解的最小高度切割問題。