第 6 章 - 指令解碼
2025-3-23
| 2025-4-23
本文字數 3675閱讀時長 10 分鐘
type
status
date
slug
summary
tags
category
icon
password
  • 在 pipeline 中,decode stage 的任務是將指令中的資訊提取出來,CPU 使用這些資訊控制後續的 pipeline 來執行這條指令
  • 影響 decode 的複雜度因素有:
    • 指令集的複雜度:
      • CISC vs. RISC
    • 每個 cycle 可以 decode 的指令個數:
      • 每個 cycle 可以 decode N 條指令,那就需要 N 個 decode 電路

6.1 - 指令緩存

  • 現代處理器可以在 fetch stage 從 I-Cache 讀出大於每個 cycle 可以 decode 指令個數的指令,因此需要在 fetch stage 和 decode stage 之間加一個 buffer,用來將 I-Cache 讀出的所有指令保存起來,這個 buffer 就稱為 Instruction Buffer
  • Fetch stage 最終會輸出兩個主要的內容給 Instruction Buffer:
    • 從 I-Cache 讀出的 N 條指令 (並非所有指令都是有效的)
    • 有效的指令個數
      • 1 instruction fetch 位址不是 cache aligned 時,或是 fetch group 中包含預測為 taken 的指令,會導致 fetch stage 沒辦法寫入 N 條指令進 Instruction Buffer
      • 此時需要告知 Instruction Buffer,有效的指令個數
  • 現在 superscalar CPU 需要 Instruction Buffer 的原因:
    • Superscalar CPU 可以每個 cycle 可以 fetch 的指令個數大於每個 cycle 可以 decode 的指令個數,這樣即使在發生 I-Cache miss 時,Instruction Buffer 中仍有可能還有保存尚未 decode 的指令,因此不需要 stall pipeline,可以繼續 decode 指令,增加 CPU 的性能
    • Superscalar CPU 中即使每個 cycle 可以 decode 的指令個數與每個 cycle 所 fetch 的指令個數相等,在 decode stage 仍會有一些特殊的指令需要處理,導致在 fetch stage 所 fetch 的指令沒有辦法全部被 decode
      • 如 ARM 的 multiply-accumulate 指令 (UMAAL RdLo, RdHi, Rn, Rm),會有兩個 destination registers,為了減少對 register renaming 的影響,會將其拆分成兩條普通的指令,每條指令只有一個 destination register
      • 因此,如果在 decode stage 沒有特別的處理,會導致 decode 的指令個數大於 fetch 指令的個數,但後續的 pipeline 都是依照原先的指令個數來設計的,不可能因為這些不常見的指令而增加後續 pipeline 的處理能力 (因為會增加硬體面積,且使用率也不高)
      • 為了解決此問題,就需要加入 Instruction Buffer,讓 multiply-accumulate 後面的指令,可以等到下一個 cycle 再 decode
  • 由於 Instruction Buffer 可以在一個 cycle 內寫入多條的指令,也可以讀出多條的指令,因此也是一個 multi-port 的 FIFIO;但在實際設計上,並不會使用真的 multi-port 的 SRAM 來實現這樣的 FIFO,而是會採用 interleaving 的方式 (參考:2.3.1 - True Multi-port),使用多個 single-port 的 SRAM 來實現,從而避免使用 multi-port SRAM 所導致的硬體速度上的限制

6.2 - 一般情況

  • ARM 的 CPSR,只有 4 個 bits (N、C、Z、V),但其他的暫存器都是 32 bits 的;如果將 CPSR 跟其他的暫存器統一對待,會造成很多暫存器無法有效的被利用,因為 32 bits 的暫存器,只存了 4 bits 的資料
    • 因此,一般都是將 CPSR 單獨處理,對 CPSR 單獨使用一套 register renaming 的流程,這樣就可以根據 CPSR 的特性來訂製 register renaming 的流程
    • 且考慮到條件執行的指令只是少部份,所以所使用的 register file 可以很小,例如只需只用 16 個 physical registers 就足夠了
    • 指令所攜帶的 source registers 和 destination registers 的個數直接決定了 register renaming 電路在實現上的難易度:
      • 像是 register renaming mapping tables 的 ports 數、指令間相關性檢查電路的複雜度
    • 由於 RISC 架構的指令比較整齊劃一,很容易解析出指令中的 opcode 和 operands,在 decode stage 產生的 pipeline 控制訊號也比較少,因此 RISC 架構的 instruction decoding 通常都可以在 1 個 cycle 完成
    • 一般情況下,RISC 處理器在 decode stage 完成的任務可以概括為:
      • What type:例如指令是算術指令還是分支指令
      • What operation:例如當指令是算術指令時,是進行什麼運算;是分支指令時,它的跳轉條件是什麼樣的
      • What resource:例如對算術指令時來說,其 source 和 destination registers 是哪些,有沒有 immediate value

6.3 - 特殊情況

  • 即使在 RISC 指令集中,也存在一些特殊的指令;這些指令不能按照一般的方法處理
    • 例如:ARM 的 LDM / STM 指令,需要多個 cycles 才能完成;而且它們的 source 和 destination registers 有多個,如果在 superscalar CPU 中對它們跟普通指令一樣來處理的話,會需要增加 register renaming mapping table、issue queue 和 ROB 所需的 ports 數,增加硬體的面積,並降低處理效能
  • 因此在 superscalar CPU 中,並不會直接處理 LDM / STM 這樣的指令,而是會將其轉換為多條普通的指令 (µops),每條普通的指令就是一般的 load / store 指令,這樣就可以用普通指令的方式來處理

6.3.1 - 分支指令的處理

  • 先前提到,採用 checkpoint 的方式對 mis-prediction 的分支指令恢復 CPU 的狀態,為了減少分支指令編號分配電路的複雜度,需要限制每個 cycle 能 decode 的分支指令個數,例如每個 cycle 只能 decode 一條分支指令 (參考:link)
  • 但是,每個 cycle 從 Instruction Buffer 讀取的指令中,有可能存在多條的分支指令,需要在 decode stage 做特別的處理
    • 簡單的作法:
      • 遇到分支指令時,就不在同個 cycle decode 這條分支指令後面的指令,而是將它們 stall 到下個 cycle
      • 這個功能只需更新 Instruction Buffer 的 pointer 即可實現
      • notion image
      • 原本從 Instruction Buffer 中讀取指令:ADDBRSUBBR
      • Cycle 1 只 decode ADDBR,Instruction Buffer pointer + 2
      • SUBBR stall 到 Cycle 2 才被 decoded,Instruction Buffer pointer + 2
    • 限制每個 cycle 最多只能 decode 一條分支指令,降低了分支指令編號分配電路的複雜度
      • 雖然效能會下降一點,但是比較容易實現,是一種折衷的方法
    • P.S. 採用 ROB 來恢復發生 mis-prediction 時 CPU 的狀態,就不再需要分支指令編號分配電路了,因此也不用限制每個 cycle 最多只能 decode 一條分支指令
  • 在 decode stage 還有另外一項重要的任務,就是位分支預測是否正確進行初步的檢查
    • 越早發現 mis-prediction,penalty 越小
    • 對於直接跳轉類型的指令,由於在 decode stage 就可以計算出其要跳轉的位址,因此可以在 decode stage 對這些分支指令的目標位址是否預測正確進行檢查
      • 如果發現 mis-prediction,可以直接使用正確的位址來 fetch instruction
  • 分支指令在 decode stage 是無法得到實際跳轉的方向的 (除了如:jmp 這種 unconditional branch 的指令),因此在 decode stage 無法對分支預測的方向進行檢查,需要等到後續的 pipeline stage 階段才能完成

6.3.2 - 乘累加/乘法指令的處理

  • 乘法和乘累加指令在 MIPS 架構中是一種特殊的指令;指令中包含兩個 destination registers (Hi registerLow register)
    • 這兩個暫存器並不屬於通用暫存器,因此需要特別處理
  • 在 superscalar CPU 中,需要對每條指令都進行 register renaming:
    • 將指令的 source registers 變為對應的 physical registers
    • 並為 destination register 分配一個 physical register
  • 大部分的指令都只有一個 destination register,但這種指令確有兩個 destination registers;此外,如果直接將此乘累加/乘法指令直接加進 ROB 中,則 ROB 也需要能夠存放兩個 destination registers,增加了 ROB 的面積,但這些增加的部份大部分都是沒有被使用的,造成了資源的浪費
  • 可以採用下列的兩種方式來處理:
      1. Hi registerLow register 分配為 MIPS CPU 的第 33、34 個通用暫存器,這種分配過程只在 CPU 內部進行,register renaming mapping table 也同樣要支援新的通用暫存器
      1. 將乘法/乘累加指令拆成兩條指令:
          • 乘法指令:{Hi, Lo} = Rs x Rt
            • 可以拆解為如下的兩條指令:
              • Hi = Rs x Rt
              • Lo = Rs x Rt
            • 這兩條指令經過 register renaming 並寫進 ROB 中時,會佔用兩個 ROB entries
            • 實際上,這兩條指令只使用一個乘法器就足夠了,只要這個乘法操作在 pipeline 的 execution stage 計算完畢,這兩條指令就同時完成了
          • 乘累加指令:{Hi, Lo} = {Hi, Lo} + Rs x Rt
            • 需要讀取四個 source registers (Rs, Rt, Hi, Lo),同時也有兩個 destination registers (Hi, Lo),正好是兩倍的普通指令,則可以將乘累加指令拆分成兩條普通指令:
              • Lo = {Hi, Lo}
              • Hi = Rs x Rt
            • 在 CPU 內部,乘累加指令仍然是以一個完整的指令來完成運算,因此指令的拆分只是更有利於 register renaming,以及便於在 ROB 中的存放,每條被拆分的指令並不是單獨進行運算的
  • 此外,也需要對 issue queue 做特殊的處理,將乘法指令和乘累加指令使用一個 FU (Function Unit),這個 FU 的 issue queue 和其他的有所不同,它包含了四個 source registers、兩個 destination registers
  • 在 decode stage 拆分的兩條指令,經過 register renaming 後,就要寫入 ROB 和 issue queue 中 (i.e. Dispatch),此時:
    • 寫進 ROB 中依舊是以兩條指令的方式寫入,佔據 ROB 兩個 entries
    • 寫進 issue queue 時則是將兩條指令進行融合 (fusion),這兩條指令在 issue queue 中又變為了一條完整的乘累加或乘法指令,這樣就能夠保證 FU 在執行時,能夠執行一個完整的乘累加或乘法指令
  • N-way 的 superscalar CPU 中,每個 cycle 可以 decode 和 register rename N 條指令,但如果 decode 的 N 條指令中包含乘法和乘累加指令,那麼就會 decode 出 > N 條的指令,register renaming 不可能特別為了極少出現的情況而浪費其硬體面積,此問題可以透過下列兩種方法來解決:
      1. 在 decode stage 和 register renaming stage 新增一個 buffer,暫存 decode stage 所產生的指令;在 register renaming stage,每個 cycle 都從此 buffer 讀取指令來處理即可
          • 指令經過 decode 後會得到很多的資訊,例如 pipeline 的控制訊號等,導致這個 buffer 需要的 bits 數會很大,在一定程度上增加了硬體的面積
      1. 限制每個 cycle decode 的指令數,一旦在 decode stage 發現乘累加指令 (e.g. MADD 指令,會被拆分為 MADD1MADD2 指令),那麼只有 MADD1 以及在其之前的指令可以進行 decode,MADD2 以及在其之後的指令需要等到下個 cycle 才可以被 decoded
        1. notion image
          • Cycle 1:ADDMADD1 (MADD 被拆分,因此MADD2 必須等待)
          • Cycle 2:MADD2SUB、MSUB1 (MSUB 被拆分,因此MSUB2 必須等待)
          • Cycle 3:MSUB2
          • 這樣的作法雖然會降低 CPU 的性能,但考慮到乘累加指令的使用頻率並不高,這種方式易於實現,因此是一種可以接受的折衷方案

6.3.3 - 前/後變址指令的處理

  • ARM 指令集中還有一種前/後變址 (pre-index/post-index) 指令,能夠在一條指令中完成兩個任務
    • 例如:ldr $r2, [$r1, #4]!
      • Load mem[$r1 + 4] to $r2
      • $r1 = $r1 + 4
    • 相當於此 load 指令有兩個 destination registers,會給 register renaming 以及後續的過程帶來麻煩
    • 因此在 superscalar CPU 實現時,仍舊會在 decode stage 將這條 load 指令,拆成兩條普通的指令:
      • ldr $r2, [$r1, #4]
      • add $r1, $r1, 4
    • 經過拆分後,一樣會導致在 decode stage 得到的指令個數 > N 條的指令,可以參考 6.3.2 節的解決方法

6.3.4 - LDM/STM 指令的處理

  • ARM LDM/STM 指令:
    • LDM/STM <Rn>{!}, <register list>
      • STM:將多個暫存器的值,存到記憶體的一段連續位址
      • LTM:將記憶體一段連續位址的值,載入到多個暫存器
    • 例如:ldm $r5!, {$r0 ~ $r3}
      • 這條指令在 superscalar CPU 內部中,會被拆成四條普通的 load 指令和一條普通的 add 指令
        • notion image

6.3.5 - 條件執行指令的處理

  • ARM 的條件執行指令,會檢查 CPSR 的值決定該條指令是否執行;在 ARM CPU 中,本質就是將 CPSR 也當作一個 source / destination registers 來看待
    • 當一條指令會更新 CPSR 時,CPSR 就會被當作為一個 destination register
      • E.g. subs 指令
    • 當一條指令需要條件執行時,CPSR 就會被當作為一個 source register
      • E.g. addeq 指令
      notion image
  • 對於 out-of-order CPU 來說,由於指令是亂序執行的,在條件執行指令被執行的當下,CPSR 的值有可能不是最新的;如果不加以處理,就會發生錯誤,例如:
    • notion image
    • inst4 被提前到 inst2 之前執行並更新了 CPSR,那麼當 inst2inst3 執行並讀取 CPSR 時,就會錯誤的使用到 inst4 所更新的狀態了
    • inst6 被提前到 inst4 之前執行,此時讀取的 CPSR 值內容有可能是來自 inst1,而不是 intn4
  • 因此,當一條指令要更新 CPSR 時 (e.g. 上述範例中的 inst1inst4),需要使用一個新的 CPSR 來保存這條指令的狀態,並給這個 CPSR 一個新的名字;與之對應的指令 (e.g. 上述範例中的 inst2, inst3inst5, inst6),都會使用新的 CPSR 來作為其 source register
    • 透過 register renaming 的方法,就可以消除 ARM CPU 中條件執行指令所帶來的問題
  • Decode
  • Pipeline
  • 第 7 章 - 暫存器重命名第 4 章 - 分支預測 (Part II)
    Loading...