跳轉到內容

微處理器設計/暫存器重新命名

來自華夏公益教科書,開放的書籍,為開放的世界

在暫存器機器中,程式由操作值的指令組成。指令必須命名這些值,以便將它們彼此區分。一個典型的指令可能會說,“將 X 和 Y 相加,並將結果放入 Z 中”。在這個指令中,X、Y 和 Z 是儲存位置的名稱。

為了獲得緊湊的指令編碼,大多數處理器指令集都有一組小的專用位置,可以直接命名。例如,x86 指令集體系結構有 8 個整數暫存器,x86-64 指令集體系結構有 16 個,許多 RISC 微處理器有 32 個,而 IA-64 指令集體系結構有 128 個。在較小的處理器中,這些位置的名稱直接對應於暫存器檔案的元素。

不同的指令可能需要不同的時間;例如,處理器可能在單個主記憶體載入操作正在進行時執行數百條指令。在載入操作完成之前執行的較短指令將先完成,因此指令將按與原始程式順序不同的順序完成。大多數最近的高效能 CPU 中都使用亂序執行來實現其部分速度提升。

在可能的情況下,組合語言編譯器會嘗試將不同的指令分配給不同的暫存器。但是,由特定微處理器體系結構限制,彙編程式碼中可以使用有限的暫存器名稱。許多高效能 CPU 擁有比指令集中可以直接命名的物理暫存器更多的物理暫存器,因此它們在硬體中重新命名暫存器以實現額外的並行性。

資料冒險

[編輯 | 編輯原始碼]

當多條指令引用特定位置作為運算元時,無論是讀取(作為輸入)還是寫入(作為輸出),以與原始程式順序不同的順序執行這些指令會導致三種資料冒險。

讀後寫 (RAW) 錯誤
從暫存器或記憶體位置讀取必須返回程式順序中最後一個寫入的值,而不是其他寫入的值。這被稱為真依賴流依賴,需要指令按程式順序執行。
寫後寫 (WAW) 錯誤
對特定暫存器或記憶體位置的連續寫入必須使該位置包含第二次寫入的結果。如果需要,可以透過壓縮(同義詞:取消、廢除、 moot)第一次寫入來解決這個問題。WAW 依賴項也被稱為輸出依賴項
寫後讀 (WAR) 錯誤
從暫存器或記憶體位置讀取必須返回寫入該位置的最後一個先前值,而不是在讀取之後程式上寫入的值。這是一種錯誤依賴,可以透過暫存器重新命名來解決。WAR 依賴項也被稱為反依賴

與其延遲寫入直到所有讀取完成,不如維護該位置的兩個副本:舊值和新值。如果讀取在程式順序中領先,則可以向新值的寫入提供舊值,即使其他在寫入之後的讀取被提供新值。錯誤依賴被打破,並且創造了更多進行亂序執行的機會。當所有需要舊值的讀取都已滿足時,可以丟棄它。這就是暫存器重新命名的基本概念。

任何被讀取和寫入的東西都可以被重新命名。雖然最常討論的是通用暫存器和浮點暫存器,但標誌暫存器和狀態暫存器,甚至單個狀態位,也通常被重新命名。記憶體位置也可以被重新命名,雖然它不像暫存器重新命名那樣普遍。一個例子是 Transmeta Crusoe 處理器的門控儲存緩衝區,這是一種記憶體重新命名的形式。

如果程式避免立即重用暫存器,則無需暫存器重新命名。一些指令集(例如,IA-64)專門為此目的指定了非常大量的暫存器。但是,這種方法有其侷限性。

  • 編譯器很難在不大幅增加程式碼大小的情況下避免重用暫存器。例如,在迴圈中,連續迭代必須使用不同的暫存器,這需要在一個稱為迴圈展開(但請參閱暫存器旋轉)的過程中複製程式碼。
  • 大量的暫存器需要更多位來指定指令中作為運算元的暫存器,從而導致程式碼大小增加。
  • 許多指令集在歷史上指定了較少的暫存器數量,現在無法更改。

程式碼大小增加很重要,因為當程式程式碼更大時,指令快取未命中次數會更多,處理器會等待新指令而停頓。

架構暫存器與物理暫存器

[編輯 | 編輯原始碼]

機器語言程式指定對指令集體系結構 (ISA) 指定的有限的暫存器集進行讀寫。例如,DEC Alpha ISA 指定了 32 個整數暫存器,每個暫存器 64 位寬,以及 32 個浮點暫存器,每個暫存器 64 位寬。這些是架構暫存器。為執行 Alpha 指令集的處理器編寫的程式將指定操作,讀取和寫入這 64 個暫存器。如果程式設計師在偵錯程式中停止程式,他們可以觀察這 64 個暫存器的內容(以及一些狀態暫存器)以確定機器的執行進度。

實現此 ISA 的一個特定處理器,Alpha 21264,具有 80 個整數和 72 個浮點物理暫存器。在 Alpha 21264 晶片上,有 80 個物理獨立的位置可以儲存整數運算的結果,以及 72 個位置可以儲存浮點運算的結果。[1]

以下文字描述了兩種型別的暫存器重新命名,它們的區別在於儲存資料以備執行單元使用的電路。

在所有重新命名方案中,機器將指令流中引用的架構暫存器轉換為標籤。當架構暫存器可能由 3 到 5 位指定時,標籤通常是 6 到 8 位的數字。重新命名檔案必須為每個週期重新命名的每個指令的每個輸入提供一個讀取埠,以及為每個週期重新命名的每個指令的每個輸出提供一個寫入埠。由於暫存器檔案的大小通常隨埠數量的平方而增長,因此重新命名檔案通常在物理上很大,並且會消耗大量的功率。

標籤索引暫存器檔案風格中,有一個大型的用於資料值的暫存器檔案,包含一個暫存器,對應於每個標籤。例如,如果機器有 80 個物理暫存器,那麼它將使用 7 位標籤。在這種情況下,48 個可能的標籤值是未使用的。

在這種風格中,當將指令發出到執行單元時,源暫存器的標籤被髮送到物理暫存器檔案,在那裡,對應於這些標籤的值被讀取併發送到執行單元。

預約站風格中,有很多小的關聯暫存器檔案,通常在每個執行單元的輸入處都有一個。問題佇列中每條指令的每個運算元在這些暫存器檔案之一中都有一個存放值的位置。

在這種風格中,當將指令發出到執行單元時,對應於問題佇列條目的暫存器檔案條目被讀取並轉發到執行單元。

架構暫存器檔案或退休暫存器檔案 (RRF)
機器的已提交暫存器狀態。按邏輯暫存器編號索引的 RAM。通常在結果從重排序緩衝區退休或提交時寫入。
未來檔案
機器最推測的暫存器狀態。按邏輯暫存器編號索引的 RAM。
活動暫存器檔案
英特爾 P6 組對未來檔案的術語。
歷史緩衝區
通常與未來檔案結合使用。包含已覆蓋暫存器的“舊”值。如果生產者仍在執行,它可能是按歷史緩衝區編號索引的 RAM。在分支預測錯誤後,必須使用歷史緩衝區中的結果——要麼複製它們,要麼停用未來檔案查詢,並透過邏輯暫存器編號以“內容定址記憶體”(CAM)的形式索引歷史緩衝區。
重排序緩衝區 (ROB)
一個根據每個操作依次(迴圈)索引的結構,用於正在執行的指令。它與歷史緩衝區不同,因為重排序緩衝區通常位於未來檔案(如果存在)之後,並位於體系結構暫存器檔案之前。

重排序緩衝區可以是無資料的或有資料的。

在 Willamette 的 ROB 中,ROB 項指向物理暫存器檔案 (PRF) 中的暫存器,還包含其他簿記資訊。這也是 Andy Glew 在伊利諾伊州與 HaRRM 一起完成的第一個亂序設計。

在 P6 的 ROB 中,ROB 項包含資料;沒有單獨的 PRF。ROB 中的資料值在退役時從 ROB 複製到 RRF。

但是,有一個小細節:如果 ROB 項中存在時間區域性性(即,如果馮·諾依曼指令序列中緊密相鄰的指令在時間上緊密相鄰地寫回,則可能在 ROB 項上執行寫組合,因此與單獨的 ROB/PRF 相比,埠更少)。目前尚不清楚這是否會造成影響,因為 PRF 應該是銀行化的。

ROB 通常沒有關聯邏輯,當然 Andy Glew 設計的任何 ROB 都沒有 CAM(見上文)。然而,設計師 Keith Diefendorff 多年來一直堅持認為 ROB 具有複雜的關聯邏輯。另一方面,第一個 ROB 提議可能利用了 CAM。

標籤索引暫存器檔案方案

[編輯 | 編輯原始碼]

在重新命名階段,每個引用的體系結構暫存器(用於讀或寫)都在體系結構索引的重對映檔案中查詢。該檔案返回一個標籤和一個就緒位。如果有一個排隊的指令將寫入該暫存器,但尚未執行,則該標籤為非就緒狀態。對於讀運算元,此標籤在指令中代替體系結構暫存器。對於每個暫存器寫入,從一個空閒標籤 FIFO 中提取一個新標籤,並將一個新的對映寫入重對映檔案,以便將來讀取體系結構暫存器的指令將引用此新標籤。該標籤被標記為非就緒狀態,因為該指令尚未執行。為該體系結構暫存器分配的先前物理暫存器與指令一起儲存在重排序緩衝區中,重排序緩衝區是一個 FIFO,用於在解碼和畢業階段按程式順序儲存指令。

然後將指令放置在各種釋出佇列中。當指令執行時,其結果的標籤會被廣播,釋出佇列將這些標籤與非就緒源運算元的標籤進行匹配。匹配意味著運算元已就緒。重對映檔案也匹配這些標籤,以便它可以將相應的物理暫存器標記為就緒狀態。當釋出佇列中指令的所有運算元都就緒時,該指令就可以釋出了。釋出佇列選擇就緒指令在每個週期傳送到各種功能單元。非就緒指令保留在釋出佇列中。從釋出佇列中取消排序的指令可能會使它們變大且功耗高。

已釋出的指令從標籤索引的物理暫存器檔案(繞過剛剛廣播的運算元)中讀取,然後執行。執行結果被寫入標籤索引的物理暫存器檔案,以及廣播到每個功能單元前面的旁路網路。畢業將先前寫入的體系結構暫存器的標籤放入空閒佇列,以便可以將其重新用於新解碼的指令。

異常或分支預測錯誤會導致重對映檔案透過狀態快照和在按順序的畢業前佇列中迴圈遍歷先前標籤的組合,備份到最後一個有效指令的重對映狀態。由於需要這種機制,並且由於它可以恢復任何重對映狀態(不僅僅是當前正在畢業的指令之前的狀態),因此可以在分支到達畢業之前處理分支預測錯誤,從而有可能隱藏分支預測錯誤的延遲。這是 MIPS R10000、Alpha 21264 和 AMD Athlon 的 FP 部分中使用的重新命名樣式。

保留站方案

[編輯 | 編輯原始碼]

在重新命名階段,每個用於讀取的體系結構暫存器都在未來檔案和重新命名檔案兩者中查詢。未來檔案讀取提供了該暫存器的值,如果還沒有未完成的指令寫入它(即,它已就緒)。當指令被放置在釋出佇列中時,從未來檔案讀取的值被寫入保留站中相應的條目。指令中的暫存器寫入會導致一個新的非就緒標籤被寫入重新命名檔案。標籤號通常按指令順序依次分配——不需要空閒標籤 FIFO。

與標籤索引方案一樣,釋出佇列等待非就緒運算元檢視匹配的標籤廣播。與標籤索引方案不同,匹配的標籤會導致相應的廣播值被寫入釋出佇列條目的保留站。

已釋出的指令從保留站讀取其引數,繞過剛剛廣播的運算元,然後執行。如前所述,保留站暫存器檔案通常很小,可能只有八個條目。

執行結果被寫入重排序緩衝區,寫入保留站(如果釋出佇列條目具有匹配的標籤),以及寫入未來檔案,如果這是最後一個針對該體系結構暫存器的指令(在這種情況下,暫存器被標記為就緒)。

畢業將值從重排序緩衝區複製到體系結構暫存器檔案。體系結構暫存器檔案的唯一用途是從異常和分支預測錯誤中恢復。

在畢業時識別到的異常和分支預測錯誤會導致體系結構檔案被複制到未來檔案,以及所有在重新命名檔案中標記為就緒的暫存器。通常沒有辦法重建某個指令(解碼和畢業之間的某個指令)的未來檔案的狀態,因此通常沒有辦法儘早從分支預測錯誤中恢復。這是 AMD K7 和 K8 設計的整數部分中使用的樣式。

方案之間的比較

[編輯 | 編輯原始碼]

在這兩種方案中,指令按順序插入釋出佇列,但按亂序刪除。如果佇列不折疊空槽,那麼它們將要麼具有許多未使用的條目,要麼需要某種可變優先順序編碼,用於同時準備就緒的多條指令。摺疊空洞的佇列具有更簡單的優先順序編碼,但需要簡單但大量的電路來推動指令透過佇列。

保留站從重新命名到執行具有更好的延遲,因為重新命名階段直接找到暫存器值,而不是找到物理暫存器號,然後使用它來找到值。這種延遲表現為分支預測錯誤延遲的組成部分。

保留站從指令釋出到執行也具有更好的延遲,因為每個本地暫存器檔案都比標籤索引方案的大型中央檔案更小。標籤生成和異常處理在保留站方案中也更簡單,如下所述。

保留站使用的物理暫存器檔案通常與它們服務的釋出佇列並行摺疊未使用的條目,這使得這些暫存器檔案在總量上更大,功耗更高,並且比標籤索引方案中使用的更簡單的暫存器檔案更復雜。更糟糕的是,每個保留站中的每個條目都可以由每個結果匯流排寫入,因此具有每個功能單元 8 個釋出佇列條目的保留站機器通常具有 9 倍於等效標籤索引機器的旁路網路數量。因此,結果轉發消耗的功耗和麵積遠高於標籤索引設計。

此外,保留站方案有四個地方(未來檔案、保留站、重排序緩衝區和體系結構檔案)可以儲存結果值,而標籤索引方案只有一個(物理暫存器檔案)。由於從功能單元廣播到所有這些儲存位置的結果必須到達機器中比標籤索引方案更多的位置,因此此功能消耗的功耗、面積和時間更多。儘管如此,在配備了非常準確的分支預測方案且執行延遲是主要關注點的機器中,保留站可以工作得非常好。

  1. 實際上,還有更多位置,但這些額外位置與暫存器重新命名操作無關。)
華夏公益教科書