PIPE 管線實作與冒險 (Pipelined PIPE Implementation & Hazards)

Overview Table

主題 核心內容 書頁
SEQ+ 把 PC 更新移到 cycle 開頭(circuit retiming),為管線化鋪路 p.457-458
PIPE− 在 SEQ+ 各階段間插入管線暫存器 F/D/E/M/W;尚未處理冒險 p.458-463
Next PC 預測 branch 採 always taken(預測 valC);ret 不預測、直接 stall p.463-465
Data hazard decode 讀暫存器,write-back 才寫回 → 前 3 條指令的寫入都可能造成冒險 p.465-469
Stalling 把指令扣在 decode、對 execute 注入 bubble(動態 nop),最多停 3 cycle p.469-472
Forwarding 5 個轉發來源 → 2 個目的地(valA/valB),多數冒險免 stall p.472-475
Load/use hazard 記憶體讀取太晚,無法純轉發;load interlock = stall 1 cycle + forward p.475-477
Control hazard ret 注入 3 bubbles;branch 預測錯注入 2 bubbles(squash 誤抓指令) p.477-480
Exception 處理 stat 隨指令流動;最深(最靠後段)的例外優先;被取消指令的例外一併取消 p.480-483
各階段 HCL f_pcf_predPCd_valA(轉發優先序)、Stat、Set CC p.483-491
管線控制邏輯 4 種特殊情況的偵測條件 + F/D/E/M/W 的 stall/bubble 動作;組合 A/B p.491-500
效能分析 CPI = 1.0 + lp + mp + rp = 1.27;瓶頸在 branch 誤預測 p.500-504
未竟事項 多週期指令、cache miss(stall)、page fault(exception) p.504-506
4.6 總結 ISA 是抽象層;不必直接實作 ISA;superscalar 使 CPI < 1(改用 IPC) p.506-509

4.5.1 SEQ+:重排計算階段 (p.457-458)

SEQ 在 clock cycle 結尾計算新 PC;SEQ+ 改成在 cycle 開頭由上一條指令留下的狀態(pIcode、pCnd、pValM、pValC、pValP)動態算出「當前指令」的 PC。這種改變狀態表示但不改邏輯行為的轉換稱為 circuit retiming,常用於平衡管線各階段的延遲。

 SEQ:                          SEQ+:
 ┌────┐                        ┌──────────────────────────┐
 │ PC │◄─ New PC(cycle 結尾算)  │ pIcode pCnd pValM pValC  │
 └────┘                        │ pValP(上一 cycle 存的訊號) │
   由本 cycle 的                └───────────┬──────────────┘
   icode/Cnd/valC/valM/valP 算       cycle 開頭 → 選出目前 PC
SEQ+ 中沒有任何硬體暫存器存 PC——PC 是動態算出來的。這示範了一個原則:實作不必依照 ISA 的概念模型編碼狀態,只要能對任意程式產生正確的 programmer-visible state 即可(out-of-order 執行是此原則的極致)。

4.5.2-4.5.3 插入管線暫存器與訊號命名 (p.458-463)

在 SEQ+ 的階段間插入管線暫存器,得到初版 PIPE−(「−」表示效能還不到位)。管線暫存器(實際硬體元件):

暫存器 位置 內容
F fetch 之前 predPC(預測的 PC)
D fetch ↔ decode 最近抓取的指令資訊(stat, icode, ifun, rA, rB, valC, valP)
E decode ↔ execute 解碼結果與暫存器讀值(valC, valA, valB, dstE, dstM, srcA, srcB)
M execute ↔ memory 執行結果、分支條件 Cnd 與分支資訊(valE, valA, dstE, dstM)
W memory → 寫回回饋路徑 供寫回 register file 的結果與 ret 返回位址(valE, valM)

訊號命名規則(理解 PIPE 的關鍵):

Select A 區塊:只有 call 在 memory 階段、jump 在 execute 階段需要 valP,且這些指令都不需要暫存器 A 埠的讀值,因此把 valP 與 A 埠讀值合併為單一訊號 valA 帶下去,減少管線暫存器狀態與接線(取代 SEQ 的「Data」區塊)。

管線圖慣例:硬體圖由下(fetch)往上(write-back)畫,指令由下往上流,使同一時刻管線內的指令順序與程式列表順序一致。

4.5.4 Next PC 預測 (p.463-465)

目標:每個 clock cycle 發射(issue)一條新指令 → 吞吐量 1 instr/cycle。抓完指令就得立刻知道下一條位址,但:

策略(branch prediction,幾乎所有處理器都用某種形式):

fetch 階段兩個區塊:

      F 暫存器(predPC)──┐
  M_valA(誤預測分支的 valP)──┼──► Select PC ──► f_pc ──► instruction memory
  W_valM(ret 的返回位址) ──┘
  Predict PC:jXX/call → valC;其他 → valP → 存入 F.predPC
高效能處理器在取指單元內建硬體返回位址堆疊:call 時壓入返回位址,ret 時彈出作為預測,準確率極高(此堆疊不屬於 programmer-visible state)(p.465 Aside)。

4.5.5 管線冒險 (Pipeline Hazards) (p.465-480)

相依 (dependency) 可能導致管線算錯時稱為冒險 (hazard):data hazard(前面指令的結果被後面指令當運算元)與 control hazard(前面指令決定後面指令的位置:jump/call/ret)。

Y86-64 各類狀態的冒險分析(p.471 Aside,系統化分析範例):

狀態 是否有冒險 原因
Program registers decode 讀、write-back 寫,不同階段
Program counter 控制冒險 預測正確就沒事;誤預測與 ret 需特殊處理
Memory 無(假設無自修改碼) 讀寫都在 memory 階段;自修改碼才會與 fetch 衝突
Condition codes execute 寫;cmov 在 execute 讀、jXX 在 memory 讀,前面的 OPq 必已完成
Status register 以 stat 隨指令流動處理 見 4.5.6

Data hazard 的產生(prog1–prog4)

decode 階段讀 register file,但結果要三個 cycle 後(通過 write-back)才寫回 → 前面 1~3 條指令寫入的暫存器若被當作來源,讀到的就是舊值。中間隔 ≥3 條無關指令(prog1)才安全;隔 2、1、0 條(prog2/3/4)各有 1~2 個運算元讀錯。

解法一:Stalling(停頓)

控制邏輯偵測到 execute/memory/write-back 中有指令將更新來源暫存器時,把 decode 的指令扣住(D 與 F 保持不變,PC 固定使下一指令重複被抓),同時對 execute 注入 bubbleBubble = 動態產生的 nop:不改暫存器、記憶體、CC、狀態。prog2 停 1 cycle、prog4 停 3 cycle——正確但吞吐量大減(常見「寫後緊接著用」會停到 3 cycle)。

 prog4 (無 nop):
 irmovq $10,%rdx   F D E M W
 irmovq $3,%rax      F D E M W
   bubble                 E M W        ← 每 stall 一次
   bubble                   E M W        就對 E 注入一個 bubble
   bubble                     E M W
 addq %rdx,%rax        F D D D D E M W  ← decode 重複 3 次
 halt                    F F F F D E M W ← fetch 跟著重複

解法二:Forwarding(轉發/bypassing)

與其等寫回,不如把「即將寫入的值」直接從後段送回 decode 當運算元。5 個轉發來源 × 2 個目的地(valA、valB):

資料字 暫存器 ID 來源說明
e_valE e_dstE ALU 輸出(execute 階段剛算出)
m_valM M_dstM 記憶體讀出值(memory 階段)
M_valE M_dstE 在 memory 階段、待寫入 E 埠的值
W_valM W_dstM 在 write-back、待寫入 M 埠的值
W_valE W_dstE 在 write-back、待寫入 E 埠的值

decode 的 Sel+Fwd A(兼併 valP)與 Fwd B 比較 d_srcA/d_srcB 與各目的暫存器 ID 決定是否轉發。用 e_valE 沒有時序問題:decode 只需在 cycle 結尾前備妥 valA/valB,ALU 輸出在那之前已有效。加上轉發後的完整設計即 PIPE

Load/use hazard 與 load interlock

記憶體讀取發生在 memory 階段(太晚),mrmovq/popq 讀出的值無法轉發給緊接其後在 decode 的指令——那需要值「倒流時光」。解法:stall 1 cycle + forwarding 的組合,稱為 load interlock:

 mrmovq 0(%rdx),%rax   F D E M W
   bubble                    E M W
 addq %rbx,%rax          F D D E M W   ← decode 停 1 cycle,
                               ▲          cycle 8 由 m_valM 轉發 %rax

Load interlock + forwarding 足以處理所有 data hazard;只有 load interlock 會損失吞吐量。

Control hazard

只有 ret 與(誤預測的)jump 會發生:

 xorq %rax,%rax        F D E M W
 jne target (不跳)       F D E M W
 irmovq $2,%rdx (目標)     F D          ← cycle 4 發現誤預測
   bubble                      E M W     ← squash
 irmovq $3,%rbx (目標+1)     F
   bubble                    D E M W     ← squash
 irmovq $1,%rax (正確路徑)     F D E M W

4.5.6 例外處理 (Exception Handling) (p.480-483)

Y86-64 三種內部例外:(1) halt;(2) 非法 icode/ifun 組合(SINS);(3) 取指或資料讀寫存取非法位址(SADR)。目標:excepting instruction 之前的指令全部完成、之後的指令不得影響 programmer-visible state,處理器停機並設定狀態碼。

三個微妙之處與對策:

問題 情境 對策
多重例外同時發生 fetch 有 halt、memory 同時報位址錯 管線中越深(越後段)的指令優先——它在程式順序上較早
誤抓指令引發例外 分支預測錯抓到 .byte 0xFF(SINS),之後才發現不該執行 指令被 squash 時其例外狀態一併取消
後面指令先改狀態 pushq 在 memory 報 SADR 時,後面 addq 正在 execute 要改 CC memory/write-back 有例外時停用 set_cc、對 memory 注入 bubble 禁止寫記憶體、stall W

機制:每個管線暫存器都帶 stat 欄位,例外發生時只記在該指令的 stat 裡、照常流動;到 write-back 時控制邏輯才偵測並停機。因為指令依程式順序到達 write-back,最先發生例外的指令必最先到達 write-back——排序自動正確。

4.5.7 PIPE 各階段實作 (p.483-491)

多數區塊與 SEQ 相同,只是訊號加上前綴(如 SEQ 的 srcA → PIPE 的 d_srcA,輸入改用 D_icodeD_rA)。完整 HCL 見 Web Aside ARCH:HCL(p.508)。

PC 選擇與預測(fetch):

word f_pc = [
    M_icode == IJXX && !M_Cnd : M_valA;  # 誤預測分支 → 抓 fall-through
    W_icode == IRET : W_valM;            # ret 完成 → 返回位址
    1 : F_predPC;                        # 預設:預測值
];
word f_predPC = [
    f_icode in { IJXX, ICALL } : f_valC;
    1 : f_valP;
];

狀態計算拆成兩段:fetch 可偵測 imem_error、非法指令、halt(→ f_stat);非法資料位址須延到 memory 階段(→ m_stat)。

Decode 轉發邏輯與優先序(整個 PIPE 最關鍵的 HCL):

word d_valA = [
    D_icode in { ICALL, IJXX } : D_valP; # 合併 valP
    d_srcA == e_dstE : e_valE;   # 1. execute 轉發
    d_srcA == M_dstM : m_valM;   # 2. memory(valM 優先於 valE)
    d_srcA == M_dstE : M_valE;   # 3.
    d_srcA == W_dstM : W_valM;   # 4. write-back
    d_srcA == W_dstE : W_valE;   # 5.
    1 : d_rvalA;                 # 無轉發:用暫存器讀值
];
優先序 = 越早的管線階段越優先(execute > memory > write-back),因為它持有程式順序上「最新」寫該暫存器的指令。順序寫錯,某些程式會執行錯誤(如連續兩條 irmovq 寫同一暫存器,必須拿到後者的值)。同一階段內 valM 優先於 valE 只影響 popq %rsp——唯一會同時對同一暫存器發出兩個寫入的指令。

Write-back:整體狀態 Stat 由 W_stat 決定;W 中若是 bubble 屬正常運作:

word Stat = [ W_stat == SBUB : SAOK; 1 : W_stat; ];

Execute:與 SEQ 相同,另把 e_valE/e_dstE 送回 decode 作轉發來源;Set CC 增加輸入 m_statW_stat,後段有例外指令時抑制 CC 更新。

Memory:SEQ 的「Mem. data」區塊消失(valP/valA 的選擇已被 decode 的 Sel+Fwd A 取代);M、W 的多個值回送前段供轉發與管線控制使用。

4.5.8 管線控制邏輯 (p.491-500)

forwarding + 預測解決不了的 4 種特殊情況與偵測條件(Fig 4.64):

情況 偵測條件(HCL)
Processing ret IRET in { D_icode, E_icode, M_icode }
Load/use hazard E_icode in { IMRMOVQ, IPOPQ } && E_dstM in { d_srcA, d_srcB }
Mispredicted branch E_icode == IJXX && !e_Cnd
Exception m_stat in { SADR, SINS, SHLT } || W_stat in { SADR, SINS, SHLT }

管線暫存器的低階機制:每個管線暫存器加 stallbubble 兩個控制輸入(兩者同時為 1 視為錯誤):

 stall=0, bubble=0 → Normal:clock 上升載入輸入(state=y)
 stall=1, bubble=0 → Stall :維持原狀態(state=x)——扣住指令
 stall=0, bubble=1 → Bubble:重設為 nop 組態(state=nop)
   (D 的 reset:icode=INOP;E 的 reset:icode=INOP 且
    dstE/dstM/srcA/srcB = RNONE)

各情況的控制動作(Fig 4.66):

情況 F D E M W
Processing ret stall bubble normal normal normal
Load/use hazard stall stall bubble normal normal
Mispredicted branch normal bubble bubble normal normal
Exception(到達 M/W) (set_cc=0) bubble stall

控制條件的組合(常見設計 bug:漏掉多條件同時成立):多數條件互斥(load/use 需 execute 是載入指令、誤預測需是 jump,不可能同時),只剩兩種組合:

組合 B 是「popping to the stack pointer」這類看似無意義的指令組合為何必須分析的經典案例:硬體出廠後幾乎無法修正,設計必須對所有指令組合系統化驗證(optest/jtest/cmtest/htest/ctest/etest 測試腳本;形式化驗證曾證明 PIPE 與 SEQ 對任意程式效果相同)(p.501-502 Asides)。

控制訊號 HCL(範例):

bool F_stall =
    E_icode in { IMRMOVQ, IPOPQ } && E_dstM in { d_srcA, d_srcB } ||
    IRET in { D_icode, E_icode, M_icode };

bool D_bubble =
    (E_icode == IJXX && !e_Cnd) ||       # 誤預測分支
    !(E_icode in { IMRMOVQ, IPOPQ } && E_dstM in { d_srcA, d_srcB }) &&
      IRET in { D_icode, E_icode, M_icode };   # ret 但排除組合 B

控制邏輯結構(Fig 4.68):輸入 W_stat, m_stat, M_icode, e_Cnd, E_dstM, E_icode, d_srcA, d_srcB, D_icode,輸出 F_stall, D_stall, D_bubble, E_bubble, M_bubble, W_stall, set_cc;其餘控制訊號恆為 0。

4.5.9 效能分析:CPI (p.500-504)

CPI(cycles per instruction) = 管線平均吞吐量的倒數(以 cycle 計)。觀察 execute 階段:處理 Ci 條指令與 Cb 個 bubbles,共約 Ci+Cb 個 cycle:

CPI=Ci+CbCi=1.0+CbCi=1.0+lp+mp+rp

bubble 數:load/use = 1、誤預測分支 = 2ret = 3。每項 penalty = 指令頻率 × 條件頻率 × bubbles:

原因 名稱 指令頻率 條件頻率 Bubbles Product
Load/use lp 0.25 0.20 1 0.05
Mispredict mp 0.20 0.40 2 0.16
Return rp 0.02 1.00 3 0.06
合計 0.27

CPI = 1.27。改善方向應鎖定 mispredicted branch(佔 0.27 中的 0.16):條件分支常見、always-taken 常猜錯、每次取消 2 條指令。(若改用 BTFNT 65% 成功率:mp = 0.20×0.35×2 = 0.14,CPI = 1.25——Practice 4.43。)

4.5.10 未竟事項 (p.504-506)

PIPE 也假設程式不自我修改(self-modifying code);若指令寫入之後會被取指的記憶體區域,instruction/data memory 共用位址空間會產生額外冒險,實際系統或以複雜機制偵測、或直接禁止。

4.6 Summary (p.506-509)

Exam/Test Patterns

情境/關鍵字 答案
decode 讀運算元,哪些指令可能造成 data hazard? 前面 1~3 條指令(位於 E/M/W 者)寫入的暫存器
bubble 是什麼? 動態產生的 nop:不改暫存器/記憶體/CC/狀態
轉發來源有幾個?目的地? 5 個(e_valE, m_valM, M_valE, W_valM, W_valE)→ 2 個(valA, valB)
轉發優先序原則? 最早管線階段優先(execute > memory > write-back);同階段 valM > valE(只影響 popq %rsp)
為何 load/use 不能純轉發? m_valM 在 memory 階段才產生,晚於下一條指令的 decode → 需 load interlock(stall 1 cycle + forward)
load/use 偵測條件? E_icode ∈ {IMRMOVQ, IPOPQ} && E_dstM ∈ {d_srcA, d_srcB}
誤預測分支何時偵測、代價? jump 在 execute 時看 e_Cnd;取消(squash)2 條誤抓指令 = 2 bubbles
ret 的處理與代價? 不預測返回位址;stall 至 ret 過 write-back,3 bubbles;fetch 其實重複抓下一條再換成 bubble
三種控制情況的 F/D/E 動作? ret: stall/bubble/normal;load-use: stall/stall/bubble;mispredict: normal/bubble/bubble
組合 B(load/use + ret)怎麼處理? 只採 load/use 的動作(D 用 stall,不可 bubble+stall);ret 處理延後 1 cycle
多重例外同時發生誰優先? **管線中最深(最靠後段)**的指令——程式順序上較早
誤抓指令引發例外怎辦? 隨指令被 squash 一併取消(stat 跟著指令走,到 write-back 才生效)
例外時如何保護 programmer-visible state? M/W 有例外 → set_cc=0、M 注入 bubble 禁寫記憶體、W stall 停機
M_stat vs m_stat? M_stat = 管線暫存器 M 的欄位;m_stat = memory 階段算出(含 dmem_error)——偵測例外要用 m_stat
CPI 怎麼算? CPI=1.0+lp+mp+rp;每項 = 指令頻率 × 條件頻率 × bubble 數;書例 = 1.0+0.05+0.16+0.06 = 1.27
CPI 改善優先目標? mispredicted branch(0.16/0.27);可改預測策略(BTFNT 65%)或用條件搬移取代條件跳轉
cache miss vs page fault 在管線的處理? miss:硬體 stall(3~20 cycles);page fault:exception → OS 載入後重新執行該指令
stall=1 與 bubble=1 同時? 錯誤狀態(設計上禁止)——正是組合 B bug 的根源