JOVANA
Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

時序裕度與關鍵路徑:讀懂時序報告

你已經懂了 setup 與 hold 的規則——現在來認識工程師每天真正在用的那個數字。時序裕度(slack)是一個單一數值,告訴你一條路徑過或不過、差多少、以及該先修哪一條。本篇我們把這些規則化成一份逼真的 STA 報告,逐行拆解:時脈路徑、資料路徑、單元延遲、繞線延遲,以及最底下那個關鍵的 slack 值。讀完之後,你打開一份時序報告就能在幾秒內找到關鍵路徑,並清楚知道它要你做什麼。

從規則到數字:抵達時間與所需時間

想像一位快遞員,必須在收件人出發前往機場之前把包裹送到城市另一端。有兩個時刻很重要:包裹實際出現在門口的時間,以及它必須抵達的截止時間。若快遞員趕在截止前到達,皆大歡喜,還留有一點餘裕;若遲到了,收件人已經離開,這趟遞送就失敗了。數位時序講的正是這個故事,只是在一顆晶片上被重複了數百萬次。包裹是沿著時序路徑一路傳遞的邏輯值;門口是正反器的資料輸入端;而截止時間則由時脈邊緣加上該正反器的 setup 需求所決定。

我們給這兩個時刻正式的名字。[[ic-arrival-time|抵達時間]]是資料訊號實際到達某一點的時刻——也就是它從發射正反器出發後,一路上累積的所有延遲總和。[[ic-required-time|所需時間]]則是該訊號還能被安全擷取的最晚到達時刻。對一個一般的 setup 檢查而言,所需時間大致是 `(下一個時脈邊緣)−(setup 時間)−(時脈不確定量)`:資料必須在擷取邊緣之前一小段就已穩定安靜下來,而不是剛好踩在邊緣上。

Slack:決定一切的那個數字

一旦你有了抵達時間與所需時間,整個過或不過的問題就濃縮成一道減法。[[slack|時序裕度]]等於所需時間減去抵達時間。 這就是它的全部定義,值得牢牢記住,因為這個數字你往後整個職涯的每一次設計審查都會被問到。

slack = required_time - arrival_time

  slack > 0   ->  PASS, with margin to spare   (data arrived early)
  slack = 0   ->  exactly on the edge          (zero margin)
  slack < 0   ->  VIOLATION                     (data arrived too late)

Example (setup check at a capturing flop):
  required_time = 2.000 ns   (edge 2.0 ns, setup already folded in)
  arrival_time  = 1.870 ns
  ----------------------------------------------
  slack         = 2.000 - 1.870 = +0.130 ns     ->  PASS (+130 ps margin)

Flip one delay so the data lands late:
  arrival_time  = 2.090 ns
  slack         = 2.000 - 2.090 = -0.090 ns     ->  FAIL (-90 ps, setup violation)
三行看懂 slack:正值是餘裕,零是臨界邊緣,負值則是你必須修掉的違規。

正的 slack 代表訊號早到了——這條路徑有餘裕,你可以拿去換更高的時脈頻率、更低的電壓,或更小更便宜的單元。零 slack 代表這條路徑剛好踩在臨界邊緣:任何額外延遲都會把它推向失敗。負的 slack 是一個違規——若是 setup 檢查就明確稱為 [[ic-setup-violation|setup 違規]]——其大小正好告訴你這條路徑需要變快多少。−90 ps 的 slack 表示你必須從這條路徑上至少削掉 90 皮秒(或把時脈放寬同樣的量),晶片才能跑在目標頻率上。

關鍵路徑不過就是最差的 slack

真實的一個區塊不是只有一條路徑,而是數百萬條,每條都有自己的 slack。[[critical-path|關鍵路徑]]就是整個設計中 slack *最小*(最負,或最不正)的那條。它就是瓶頸——最接近失敗的那一串邏輯,因此也是決定整顆晶片能跑多快的那條。把其他所有路徑都加強也沒用;唯獨關鍵路徑訂下了速度上限,就像單線道上最慢的那輛車,決定了後方每一輛車的步調。

正因如此,時序收斂是一場關於*排序輕重*的遊戲,而不是追求完美。工具會把每個終點依 slack 排序,先把最糟的幾條交給你。你修好關鍵路徑後,第二糟的路徑就成了新的關鍵路徑,如此反覆。它與頻率的關係非常直接:若你最差的 setup slack 是 +130 ps、時脈週期為 2.0 ns(500 MHz),你就有 130 ps 的餘裕,原則上可把週期壓到約 1.87 ns,slack 才會歸零。若最差 slack 是 −90 ps,你就*超支*了,這條路徑修好(或時脈放慢)之前,晶片無法達到 500 MHz。

逐行讀懂一份真實的 STA 報告

這一切都由[[static-timing-analysis|靜態時序分析]](STA)算出,這個工具不需模擬任何一組測試向量就能檢查每一條路徑——它純粹把延遲加總。它的主要輸出就是時序報告,一旦你能流暢地讀懂一份,你幾乎能除錯任何時序問題。下面是一份精簡但逼真的單條路徑 setup 報告。它分為兩半:資料抵達路徑(資料實際如何傳遞)與資料所需路徑(截止時間,由時脈構築)。從上讀到下即可。

Startpoint: u_ctrl/state_reg[2]  (rising clk, launched by CLK)
Endpoint:   u_alu/result_reg[7]  (rising clk, captured by CLK)
Path Group: CLK
Path Type:  max  (setup)

  Point                                   Incr   Path     Type
  -------------------------------------------------------------
  clock CLK (rise edge)                   0.000   0.000
  clock source latency                    0.000   0.000
  clock network delay (propagated)        0.182   0.182          <- clock path
  u_ctrl/state_reg[2]/CK (DFFX1)          0.000   0.182   r
  u_ctrl/state_reg[2]/Q  (DFFX1)          0.094   0.276   r      <- clk-to-Q
  net: state[2]  (fanout=3)               0.041   0.317   r      <- net delay
  u_and0/Y  (AND2X2)                      0.063   0.380   r      <- cell delay
  net: n12      (fanout=1)                0.018   0.398   r
  u_add1/CO (FADDX1)                      0.121   0.519   r
  net: carry    (fanout=2)                0.029   0.548   r
  u_mux2/Y  (MUX2X1)                      0.072   0.620   r
  net: result_pre[7] (fanout=1)           0.022   0.642   r
  u_alu/result_reg[7]/D (DFFX1)           0.000   0.642   r
  data arrival time                               0.642          <= ARRIVAL
  -------------------------------------------------------------
  clock CLK (rise edge)                   2.000   2.000
  clock source latency                    0.000   2.000
  clock network delay (propagated)        0.205   2.205          <- capture clk
  clock uncertainty                      -0.050   2.155
  u_alu/result_reg[7]/CK (DFFX1)          0.000   2.155   r
  library setup time (DFFX1)             -0.061   2.094          <- setup
  data required time                              2.094          <= REQUIRED
  -------------------------------------------------------------
  data required time                              2.094
  data arrival time                              -0.642
  -------------------------------------------------------------
  slack (MET)                                     1.452          <= SLACK
一份 setup 路徑報告。上半是資料抵達路徑,下半是由時脈推導的所需路徑,最後一行是 所需 − 抵達 = slack。

Incr 欄讀成「這一個元件加了多少延遲」,把 Path 欄讀成「到此為止的累計總和」。資料路徑從發射時脈邊緣(時間 0)開始,先等時脈穿過網路(`clock network delay`,0.182 ns)抵達發射正反器,接著付出 `state_reg[2]` 的 clk-to-Q(0.094 ns)——也就是正反器輸出在其時脈邊緣後真正改變所需的時間。此後訊號在單元延遲(閘:那顆 AND、全加器的進位、那顆 MUX)與繞線延遲(連接它們的導線,在現代製程上佔比龐大且持續攀升)之間交替。當它抵達 `result_reg[7]/D` 時的累計總和,就是抵達時間:0.642 ns

下半部構築截止時間。它從*下一個*時脈邊緣(2.000 ns——這是一條週期 2 ns 的單週期路徑)開始,先等時脈抵達*擷取*正反器(0.205 ns),再減去兩道安全餘裕:時脈不確定量(0.050 ns,涵蓋抖動與偏斜估計)以及正反器的程式庫 setup 時間(0.061 ns,資料必須在邊緣前保持安靜的窗口)。結果就是所需時間:2.094 ns。最後那道減法——2.094 − 0.642——得出 slack = +1.452 ns,標籤 `(MET)` 確認此檢查以充裕餘裕通過。

從報告到修正:收掉一條失敗的路徑

假設同一份報告回來時顯示的是 `slack (VIOLATED) -0.115` 而非通過。負 slack 並不神祕——它是你超支了 115 ps 的預算,而路徑報告本身就是那張逐項列出錢花到哪去的帳單。收掉它是一個有條理的循環,且永遠從最糟的路徑開始:

  1. 找出最糟的路徑。 把所有終點依 slack 排序,打開最負的那一條的報告。這就是你當前的關鍵路徑;其餘暫且不理。
  2. 讀懂帳單。 掃描資料路徑的 Incr 欄,找出最大的貢獻者。是某顆慢閘?一條高扇出的長導線?還是邏輯鏈太深(兩個正反器之間的閘級數過多)?
  3. 對症下藥。 慢閘 → 放大單元或改用更快的 Vt 種類。長導線 → 改善擺放或插入緩衝器。邏輯太深 → 重整邏輯或重新計時(移動暫存器),讓正反器之間的閘級數變少。
  4. 重跑 STA 並檢查新的 slack。 把 −0.115 變成 +0.010 的修正就收掉了這條路徑——但它可能拉長了某條鄰近路徑。新的最糟路徑就成了下一個目標。
  5. 反覆進行,直到所有路徑群組(以及所有製程角與模式)的最差 slack 都 ≥ 0 為止。這就是時序收斂。

還有一根槓桿完全位於資料路徑之外:[[timing-constraint|時序約束]]本身。報告中所需那一半的每個數字,都源自你寫下的約束——時脈週期、它的不確定量、輸入/輸出延遲。若某條路徑只差一根頭髮、而約束又過度悲觀(例如你重複計入了不確定量裡已含的餘裕),那麼收緊*約束*而非動*矽片*,會是所有解法中最便宜的。但絕不要為了讓報告變綠就放寬一個真實的約束:那只會藏起一個違規,而矽片會在全速運轉時樂於把它揭露出來。