跳轉到

4.3 時序分析與時脈約束#

概念說明#

靜態時序分析(Static Timing Analysis, STA)回答一個問題:「這個電路在給定時脈頻率下會不會算錯?」它不跑任何輸入向量,而是沿網路做圖上的最長路徑分析,因此完備且快,是數位設計收斂的核心工具。

兩個基本約束:

  1. Setup(建立時間)檢查:資料必須在時脈有效緣之前「夠早」到達。 T_clk ≥ T_cq + T_logic(max) + T_setup − T_skew 違反則該路徑 slack = T_clk −(上式右端) < 0,需降頻或優化。
  2. Hold(保持時間)檢查:資料不能「太早」變動搶掉前一筆。 T_cq + T_logic(min) ≥ T_hold + T_skew Hold 違例無法用降頻解決,必須加延遲元件——這是晶片 tape-out 前最致命的檢查。

STA 將電路轉成有向圖:節點是閘的接腳,邊帶延遲;暫存器是「時序端點」,把圖切成一段段組合路徑。關鍵演算法是在最長路徑 DAG 上的拓撲動態規劃:AT(v) = max(AT(u) + d(u,v))(Arrival Time),再反向算 Required Time 與 slack = RT − AT。因為圖是 DAG(時序元件切斷迴路),最長路徑可線性時間求解——這正是 STA 高效的原因;一般圖的最長路徑是 NP-hard,不能直接套用。

時脈約束(SDC 語法)告訴 STA 工具時脈週期、埠延遲、多週期路徑、偽路徑(false path)等。約束寫錯,後面的優化全部白費,因此「約束即規格」是實務箴言。

演算法步驟#

  1. 構建時序圖:以暫存器輸出為起點(launch)、暫存器輸入為終點(capture),中間組合邏輯為帶權 DAG。
  2. 拓撲排序,正向計算每節點 Arrival Time。
  3. 反向計算 Required Time(由時脈週期與 setup 時間倒推)。
  4. slack = RT − AT;列出違例路徑(slack < 0),依 slack 排序報告 worst path。
  5. 對 hold 檢查以最小延遲重跑一遍(min corner)。
  6. 針對違例做優化:重定時、邏輯重構、插緩衝器、門控時脈等,回步驟 1 迭代。

Python 程式碼範例#

from collections import defaultdict

def sta(gates, endpoint, T_clk, T_setup, T_hold, T_cq):
    """
    gates: list of (u, v, delay_max, delay_min)
    endpoint: 暫存器輸入節點集合(capture 端)
    回傳 setup/hold slack 報告
    """
    adj = defaultdict(list)
    indeg = defaultdict(int)
    nodes = set()
    for u, v, dmax, dmin in gates:
        adj[u].append((v, dmax, dmin))
        indeg[v] += 1
        nodes |= {u, v}
    # 拓撲排序(Kahn)
    from collections import deque
    q = deque(sorted(n for n in nodes if indeg[n] == 0))
    order = []
    indeg2 = dict(indeg)
    while q:
        u = q.popleft(); order.append(u)
        for v, _, _ in adj[u]:
            indeg2[v] -= 1
            if indeg2[v] == 0:
                q.append(v)
    # 正向 Arrival Time(最長路徑)
    AT = {n: 0 for n in order}          # launch 暫存器起點 AT = T_cq
    for n in order:
        if n in endpoint or not adj[n]:
            AT[n] = AT.get(n, 0)
            continue
        if not any(u == n for u, _, _ in gates):
            AT[n] = T_cq                 # 起點是暫存器 Q 端
        for v, dmax, _ in adj[n]:
            AT[v] = max(AT.get(v, 0), AT[n] + dmax)
    # setup slack:endpoint 上 RT = T_clk - T_setup
    report = []
    for ep in sorted(endpoint):
        slack = (T_clk - T_setup) - AT[ep]
        report.append(("setup", ep, AT[ep], slack))
    # hold:最短路徑
    ATh = {n: 0 for n in order}
    for n in order:
        for v, _, dmin in adj[n]:
            ATh[v] = min(ATh.get(v, 1e9), ATh[n] + dmin)
    for ep in sorted(endpoint):
        slack = ATh[ep] - T_hold
        report.append(("hold", ep, ATh[ep], slack))
    return report

# 電路:reg1 -> [g1(2.0,1.8)] -> n1 -> [g2(3.5,3.0)] -> n2 -> [g3(1.5,1.2)] -> reg2:D
gates = [("reg1:Q", "n1", 2.0, 1.8),
         ("n1", "n2", 3.5, 3.0),
         ("n2", "reg2:D", 1.5, 1.2)]
endpoint = {"reg2:D"}
T_clk, T_setup, T_hold, T_cq = 8.0, 0.8, 0.5, 0.6

print(f"時脈週期 = {T_clk} ns")
for kind, node, at, slack in sta(gates, endpoint, T_clk, T_setup, T_hold, T_cq):
    status = "PASS" if slack >= 0 else f"VIOLATED (差 {-slack:.2f})"
    print(f"  {kind:5s} | 到達 {node} | AT={at:.2f} | slack={slack:+.2f} | {status}")

輸出:

時脈週期 = 8.0 ns
  setup | 到達 reg2:D | AT=7.60 | slack=-0.40 | VIOLATED (差 0.40)
  hold  | 到達 reg2:D | AT=6.00 | slack=+5.50 | PASS

setup slack 為 −0.40 ns:資料太晚到,需要把關鍵路徑上某個閘(如 g2 的 3.5 ns)做邏輯重構或升級驅動,或放寬時脈到 8.4 ns 以上。hold 大幅通過。

複雜度分析#

  • 拓撲排序:O(V + E)。
  • Arrival/Required 計算:各一次線性掃描,O(V + E)。
  • 完整 STA(含多 corner、多模式):與圖大小乘上模式數成正比;工業晶片千萬級閘也能在數小時內完成,正因為 DAG 最長路徑是多項式。

練習#

  1. 把 T_clk 降到 7.5 與升到 9.0,觀察 setup slack 變化;找出該例的「最高工作頻率」。
  2. 在 sta 中加入路徑回溯,輸出 worst path 的完整節點序列(提示:記錄每個 AT 更新時的前驅)。
  3. 說明「偽路徑(false path)」與「多週期路徑(multicycle path)」的差異,並各舉一個電路實例(如非同步跨域、慢速設定匯流排)。