4.3 時序分析與時脈約束#
概念說明#
靜態時序分析(Static Timing Analysis, STA)回答一個問題:「這個電路在給定時脈頻率下會不會算錯?」它不跑任何輸入向量,而是沿網路做圖上的最長路徑分析,因此完備且快,是數位設計收斂的核心工具。
兩個基本約束:
- Setup(建立時間)檢查:資料必須在時脈有效緣之前「夠早」到達。
T_clk ≥ T_cq + T_logic(max) + T_setup − T_skew違反則該路徑 slack = T_clk −(上式右端) < 0,需降頻或優化。 - Hold(保持時間)檢查:資料不能「太早」變動搶掉前一筆。
T_cq + T_logic(min) ≥ T_hold + T_skewHold 違例無法用降頻解決,必須加延遲元件——這是晶片 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)等。約束寫錯,後面的優化全部白費,因此「約束即規格」是實務箴言。
演算法步驟#
- 構建時序圖:以暫存器輸出為起點(launch)、暫存器輸入為終點(capture),中間組合邏輯為帶權 DAG。
- 拓撲排序,正向計算每節點 Arrival Time。
- 反向計算 Required Time(由時脈週期與 setup 時間倒推)。
- slack = RT − AT;列出違例路徑(slack < 0),依 slack 排序報告 worst path。
- 對 hold 檢查以最小延遲重跑一遍(min corner)。
- 針對違例做優化:重定時、邏輯重構、插緩衝器、門控時脈等,回步驟 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 最長路徑是多項式。
練習#
- 把 T_clk 降到 7.5 與升到 9.0,觀察 setup slack 變化;找出該例的「最高工作頻率」。
- 在
sta中加入路徑回溯,輸出 worst path 的完整節點序列(提示:記錄每個 AT 更新時的前驅)。 - 說明「偽路徑(false path)」與「多週期路徑(multicycle path)」的差異,並各舉一個電路實例(如非同步跨域、慢速設定匯流排)。