# 利用管線增加效能 ## 重點一 管線範例 ### Pipeline * 不會改善單一工作的latency,因為單一指令執行時間還是一樣,但會增進整體工作的throughput * **藉由讓多個工作同時在管線中使用不同的資源來提升硬體使用率** * pipeline是由single cycle machine改的,因此硬體也不可共用 * 理想情況下,獲得的加速約等於stage數 * 實際上因為下列情況無法達到理想加速 1. stage分割不平衡 2. pipeline register有延遲時間 3. data hazard和control hazard 4. 填滿和清空管線的這段時間硬體使用率不到100% :::success :bulb: 優點: pipeline can overlap the execution of multiple instructions to improve performance(throughput) ::: :::info :warning: 缺點: 實作難、硬體成本高 ::: ### 專為管線化設計的指令集--MIPS 1. **所有MIPS指令都有相同的長度** * 這個規定可以讓管線的IF、ID變得簡單 2. **只有少數幾種指令格式,且每個指令的來源暫存器都在相同的位置** * 這個規定讓我們可以在ID階段對指令進行解碼的同時讀取暫存器檔案 3. **MIPS中只有載入指令或儲存指令會存取記憶體** * 可以在EXE階段計算記憶體位置,並在MEM階段存取記憶體 * 若非如此,則這兩階段必須合併 4. **運算元必須在記憶體中對齊** ### 理想管線的執行時間 * 假設有S個stage N個指令,則需要S+(N-1)個clock cycle通過管線 * 第一個指令在第S個clock執行完畢,而之後每一個clock都有一個指令完成,故N個指令要S+(N-1)個clock cycle * <font color="red">pipeline的理想CPI = 1</font> ## 重點二 管線資料路徑 ### 管線資料路徑的設計 * 和multicycle machine相似 * 盡量平衡各步驟的執行時間 * 限制一個stage只能使用一個主要功能單元 * 加入管線暫存器,儲存上一個階段的結果以供下一個階段使用 ### 五個stage * IF: 指令擷取 * ID: 指令解碼與讀取暫存器 * EXE: 執行運算或計算記憶體的位址 * MEM: 資料記憶體存取 * WB: 寫回 ### 管線的效能 * <font color="red">減少最長stage時間才能改善pipeline的效能</font> * <font color="red">管線化只改善了throughput,不能減少個別指令的執行時間</font> ## 重點三 管線控制單元 * RegFile被設計成在前半周期寫入(WB),後半周期讀取(ID),這樣讀取到的才會是最新的值,避免同步問題 * control signal 在 ID 階段產生 * <font color="red">把RegDst移到EXE stage</font>,這樣前兩個stage就可以完全不使用control signal * EXE stage * **RegDst** * ALUop1 * ALUop2 * ALUsrc * MEM stage * MemWrite * MemRead * Branch * WB stage * MemtoReg * RegWrite :::info 在single/multi cycle machine中,需要edge triggered 因為在一個cycle time中,記憶體位址和要寫入的資料可能發生改變 讓data memory edge triggered可以避免一筆資料寫入兩個不同的位址 ::: :::info 在pipeline中data memory可以不用edge triggered,因為pipeline register會將資訊存好,下一個clock來的時候只有一個記憶體位址和要寫入的資料可以進到data memory ::: :::info PCsrc會在MEM stage生成,決定branch要不要跳 事實上可以移到EXE stage就生成 **好處是**: 當發生control hazard時可以少暫停一個cycle **壞處是**: 把生成PCsrc 的 AND gate 移到 EXE stage時可能會增加EXE stage的時間,進而增加clock cycle time而影響效能 ::: ## 重點四 Hazard * structural hazards * 硬體資源不夠多,導致在同一時間內要執行多個指令卻無法執行 * data hazards * 管線中某一指令需要用到前面指令尚未產生的結果(data dependency),也就是執行的指令所需要的資料還無法取得 * control hazards * 當分支指令還沒決定要不要跳的時候,後續的指令已進入管線中。 ## 重點五 Structural hazards ### 解決方法 1. 加入足夠多的硬體使得在任一clock cycle中,不會有不同指令使用相同階段的硬體資源 2. Stall * 讓會發生structural hazard的指令錯開,以避免在同一個clock cycle使用相同硬體資源 ## 重點六 Data hazards ### 軟體方式解決 * 利用compiler插入足夠多的nop指令 * 讓有data hazard的兩個指令距離大於2 * 利用compiler重排指令順序 * 讓有data hazard的兩個指令距離大於2 * compiler不一定能在不影響程式的正確性下,找的到可以消除data hazard的指令順序,最後還是要靠插入nop解決 ### 硬體方式解決 * 不是load-use的data hazard可用forwarding來解決 * <font color="red">load-use data hazard只能用stall 1 cycle + forwarding來解決</font> :::info RegFile前半周做寫、後半周做讀也可以消除可能的data hazard ::: ### hazard detection(硬體方式) * 引入forwarding unit (combination circuit) :::info hazard detection unit在ID stage forwarding unit在EX stage ::: **偵測步驟** 1. 偵測目前指令是否要寫入暫存器 2. 偵測目前指令的rd與他之後指令的rs、rt是否相同 * EX hazard (偵測目前指令與下一個指令是否有data hazard) ```MIPS sub $s2, $s1, $s3 (MEM) and $s12, $s5, $s2 (EX) ``` ```clike=1 //目前指令與下一個指令有data hazard if(EX/MEM.RegWrite && EX/MEM.Rd != 0 && EX/MEM.Rd==ID/EX.Rs) Forward A = 10 //MEM時從EX/MEM送給下一個指令的Rs if(EX/MEM.RegWrite && EX/MEM.Rd != 0 && EX/MEM.Rd==ID/EX.Rt) Forward B = 10 //MEM時從EX/MEM送給下一個指令的Rt ``` * MEM hazard (偵測目前指令與下下一個指令是否有data hazard) ```MIPS sub $s2, $s1, $s3 (WB) and $s12, $s2, $s5 (MEM) or $s13, $s2, $s6 (EX) ``` ``` clike=1 //目前指令與下下一個指令有data hazard if(MEM/WB.RegWrite && MEM/WB.Rd != 0 && MEM/WB.Rd==ID/EX.Rs) Forward A = 01 //WB時從MEM/WB送給下下一個指令的Rs if(MEM/WB.RegWrite && MEM/WB.Rd != 0 && MEM/WB.Rd==ID/EX.Rt) Forward B = 01 //WB時從MEM/WB送給下下一個指令的Rt ``` * **上面的MEM hazard detection code會有問題** * 當MEM階段和EX階段沒有data hazard,WB階段才和EX階段有data hazard * MEM和EX有,WB和EX就沒有,因為EX需要最新資料應該要來自MEM <font color="red">修正如下</font> ``` clike=1 //目前指令(WB)與下下一個指令(EX)有data hazard //則下一個指令(MEM)和下下一個指令(EX)不能有data hazard if(MEM/WB.RegWrite && MEM/WB.Rd != 0 && NOT(EX/MEM.RegWrite && EX/MEM.Rd != 0 && EX/MEM.Rd==ID/EX.Rs) && MEM/WB.Rd==ID/EX.Rs) Forward A = 01 //WB時從MEM/WB送給下下一個指令的Rs if(MEM/WB.RegWrite && MEM/WB.Rd != 0 && NOT(EX/MEM.RegWrite && EX/MEM.Rd != 0 && EX/MEM.Rd==ID/EX.Rt) && MEM/WB.Rd==ID/EX.Rt) Forward B = 01 //WB時從MEM/WB送給下下一個指令的Rt ``` :::warning :warning: :warning: :warning: :warning: :warning: :warning: 如果沒有要求rd != 0則會發生錯誤 ``` MIPS sub $0, $3, $4 ($3=500 $4=200) add $2, $1, $0 ``` 1. R-type指令,會寫入暫存器 2. EX/MEM.rd==ID/EX.rt 3. ForwardB = 10 在MEM stage從EX/MEM送給rt 在上面的情況會把 300 forward到add的$0 但$0只能是constant 0 所以會發生錯誤 ::: ### load-use data hazard * 引入hazard detection unit in ID stage * <font color="red">會在EX和ID階段偵測</font>連續指令是否存在有load-use data hazard * 如果有的話會暫停IF ID兩個stage的指令更新 * 暫停實際上是將該stage的**控制信號清成0(flush)**,並且**暫停更新PC** (IF保留)**和IF/ID** (ID保留) **的內容** **偵測步驟** 1. MemRead = 1? (lw?) 2. **EX stage中的rt是否和ID中的rs rt相同** (lw要看rt) * lw在ID stage結束後,結果存到ID/EX,下一個clock cycle走到EX後hazard detection unit偵測到load-use ``` MIPS lw $2, 20($1) and $4, $2, $5 or $8, $2, $6 ``` ```clike=1 if(ID/EX.MemRead && (ID/EX.Rt==IF/ID.rs)or(ID/EX.Rt==IF/ID.rt)) stall pipeline ``` :::info 若沒有forwarding unit,則load-use要暫停2個clock ::: ### Full Forwarding Path 1. EX/MEM - ALU 2. MEM/WB - ALU 3. MEM/WB - Data Memory * lw後面接著sw,使用相同的rt欄位 4. EX/MEM - RegFile(後方的XOR) * 用來比較兩暫存器是否相等 ## 重點七 資料相依 ### RAW (True dependency) Read After Write: 讀在寫之後,先寫再讀 ``` MIPS add s0, s1, s2 sub t1, s0, t2 ``` > 這裡sub要讀的s0暫存器要等前一個指令add寫入,讀到的才會是正確的值。 ### WAR (Anti-dependency) Write After Read: 寫在讀之後,先讀再寫 ```MIPS add s0, s1, s2 sub s1, t1, t2 ``` ### WAW (Output dependency) Write After Write: 寫在寫之後,先寫再寫 ```MIPS ``` :::success 在MIPS 5 stage pipeline只有RAW(Read After Write)會有data hazard ::: 可以透過重排指令來避免load-use的stall,進而增加管線效能,但前提是指令間必須沒有data dependency ## 重點八 Control hazards ### 解決control hazard的方法 1. stall(硬體) / 插nop(軟體) 2. branch prediction (硬體) 3. delay slot (軟體) 4. Predict (軟體) **<只能減少>** * 只能減少部分由高階語言轉成組合語言所產生的branch指令(讓branch數量變少) ex: if-then else * for/while loop產生的branch指令無法消除 5. 提早到ID決定branch要不要跳 **<只能減少>** ### 減少分支延遲 * MIPS將決定branch會不會跳移到ID stage * 將原本在EX stage用來計算跳躍目的位址的adder移到ID stage * 在RegFile後面加上XOR來比較兩個來源暫存器是否相等 * 為了清除IF階段的指令,還要在main control上加一條IF.flush的控制信號線 * 再加一條forwarding path,可以從EX/MEM送到ID stage供branch比較暫存器是否相等 :::info 移到ID stage決定要不要跳反而會多出R-type+branch這種要stall 1次的情形 而lw+branch從stall 1次變成了要stall 2次,但好處是猜錯的時候penalty變小 (flush 3 stage -> 1 stage) ::: ### Dynamic brench prediction * Branch Prediction Buffer(Branch History Table) * 由分支指令位址較低的部分作為索引的小型記憶體 * 這個記憶體包含一個位元來檢查目前這個branch上次有沒有跳(每個beq會有各自的紀錄) * 可以幫助預測branch會不會跳 * 放在ID stage * 猜錯的時候才會更新 * Branch Target Buffer * 會把之前跳過的**目的位址**或**目的位址的指令**存起來(類似cache) * 可以幫助預測跳躍目的位址 * 放在IF stage * 1-bit prediction * 2-bit prediction * steady state下準確率會比 1-bit prediction好 * correlating predictor * 同時使用**現在這個branch和其他所有的branch**的執行資訊來判斷 * tourment predictor * 在每個branch使用多個預測器,看哪個效果好就用哪個的結果 ### delay branch * software base solution * 由compiler和assembler達成 * 將不論分支是否成立都會執行到的指令放到delay branch slot中而延後執行後面的指令 ::: info 不論分支條件是否成立都會執行到的指令稱為安全指令,並且必須確保多執行到安全指令不會影響程式的正確性 ::: #### 安排安全指令到delay slot的三個方法 1. from before * 分支指令以上都是找安全指令的範圍 2. form target (往上跳的branch) * 若from before找不到,且<font color="red">分支經常發生</font> * 分支指令到分支跳躍目的位址之間都是找安全指令的範圍 * <font color="red">複製</font>安全指令到delay slot 3. from fall through (往下跳的branch) * 若from before找不到,且<font color="red">分支不常發生</font> * 分支指令到分支跳躍目的位址之間都是找安全指令的範圍 * <font color="red">移動</font>安全指令到delay slot :::info delay branch是一個簡單而有效率的方法 隨著pipeline stage數增加及每個周期分發的指令數增加,branch的delay越來越長 單一個delay slot已經不夠使用,取而代之的是代價更高但更具彈性的動態方案(branch prediction) ::: ## 重點九 進階的管線 ### Multiple Issue 透過複製pipeline元件,讓一個clock cycle執行多個指令,增加平行度 4-way multiple-issue -> CPI = 0.25 / IPC = 4 ### Static (compiler) * compiler配對沒有資料相依的指令,配成issue slot * 重排指令 ### Dynamic (CPU) CPU在runtime判斷指令流,解決hazard 把執行完的指令先存下來,確定沒有問題再寫入,有問題的話移除計算結果 ### Speculation 猜指令是否沒有相依 * sw -> lw (runtime才能計算) * beq是否跳 ### Loop unrolling 把迴圈解開,增加平行度 Register renaming: a[i] = a[i] + 1 實際上每一個a[i]都是獨立的 ### Super pipeline * increase the depth of pipeline to increase clock rate ### VLIW (Very Long Instruction Word) * 一種指令集,可以將多個彼此獨立的指令打包成一個長指令(VLIW) * static multiple issue ### Superscalar * 進階的管線,可以讓處理器在一個cycle time執行多個指令 * dynamic multiple issue ## 重點十 管線資料路徑例外的處理 ### 例外(exception)和中斷(interrupt) * exception: 從<font color="red">處理器外部</font>發生了非預期的事件而擾了程式的執行 * interrupt: 從<font color="red">處理器內部</font>發生了非預期的事件而擾了程式的執行 ### 如何處理例外 * 當exception產生時,機器會將引發exception的指令存到EPC(Exception Program Counter)中,然後將控制權轉到位於特定記憶體位址的OS中 * 完成excption的處理後,OS可以選擇 * 利用EPC得知原程式應該從何處繼續執行下去 * 終止原程式 ### 如何得知例外原因 1. MIPS用原因暫存器(cause register)來儲存資料並透過暫存器的欄位來指出發出exception的原因 2. vectored interrupt,控制權轉換的位址是由exception的原因決定,OS透過被觸發的位址來得知exception的原因 ### pipeline例外處理的步驟 1. Flush IF ID EXE 2. 盡可能的讓造成例外之前的指令執行完畢 3. 將造成例外的指令的記憶體位址(PC+4)存到EPC中 * 因為IF階段取得指令後是把PC+4沿著管線往後傳 4. 呼叫OS來處理 5. 返回使用者程式碼 ::: info 在EX stage加入cause register和EPC EPC是存放PC+4,所以要取得真正造成exception的指令要先 -4 ::: ### 非精確中斷