跳轉至內容

微處理器設計/冒險

來自Wikibooks,開放世界中的開放書籍

冒險是指微控制器操作中出現的錯誤,由流水線處理器中多個階段同時執行導致。

冒險有三種類型:資料冒險、控制冒險和結構冒險。

資料冒險

[編輯 | 編輯原始碼]

資料冒險是由嘗試同時訪問或修改資料引起的。在MIPS設計中,結果在同一時鐘週期內被寫回暫存器檔案,而另一個指令解碼階段正在讀取暫存器檔案。資料冒險有三種基本型別

寫後讀 (RAW)
在這些冒險中,讀取過程發生在寫入過程之後,儘管這兩個過程發生在同一個時鐘週期內。如果寫入過程需要很長時間,則在讀取發生時可能無法完成,這將產生不正確的資料。
讀後寫 (WAR)
在WAR冒險中,先前指令的寫入將不會在後續讀取指令之前完成。這意味著下一個讀取的值將是先前的值,而不是正確的值。
寫後寫 (WAW)
當兩個程序試圖同時寫入資料儲存單元時,就會發生WAW冒險。如果這發生在一個時鐘週期內,則中間值之間沒有時間讀取。如果指令亂序執行,則暫存器中可能保留了不正確的值。

競爭條件

[編輯 | 編輯原始碼]

如果未明確考慮資料冒險,則可能會出現競爭條件,其中處理器的正確執行取決於時序。如果事件按正確的順序和時間發生,則可能不會出現問題。但是,在競爭條件下,事件很可能發生亂序或在不同的時間間隔內發生,這將導致問題。

控制冒險

[編輯 | 編輯原始碼]

控制冒險發生在處理分支指令時。當分支指令在流水線中傳輸時,指令獲取模組將繼續從指令儲存器中讀取順序指令。問題在於,由於分支的存在,後續指令可能會亂序執行,這會導致問題。

結構冒險

[編輯 | 編輯原始碼]

當兩個單獨的指令試圖同時訪問特定的硬體模組時,就會發生結構冒險。

修復冒險

[編輯 | 編輯原始碼]

有許多方法可以避免或消除冒險。

停頓,或流水線中的“氣泡”,發生在控制單元檢測到將發生冒險時。發生這種情況時,控制單元會停止指令獲取機制,並改為將NOP放入流水線。這樣,敏感指令將被迫單獨執行,而不會有任何其他指令同時被處理。

在這張圖片中,我們可以看到在資料冒險發生的地方繪製了“氣泡”。氣泡表示指令在流水線中停頓,直到先前的指令完成。一旦先前的指令完成,停頓的指令將繼續移動。

請注意,在這張圖片中,黃色指令在ID階段停止了2個週期,而紅色指令繼續執行。

當一條指令的結果要作為下一條指令的ALU輸入時,我們可以使用轉發將資料直接從ALU輸出移動到下一個週期的ALU輸入,在該資料寫入暫存器之前。這樣,我們就可以避免在這些情況下需要停頓,但代價是需要新增額外的轉發單元來控制這種機制。

暫存器重新命名

[編輯 | 編輯原始碼]

暫存器可以重新命名或重新編號,而不是使用固定的暫存器編號。考慮以下ADD指令

add R1, R2, R1

我們正在將R1和R2中的值相加,並將結果儲存回R1。如果名稱“R1”指向兩個不同的物理儲存區域,即值從一個位置“舊R1”讀取,並寫入一個新的儲存區域“新R1”。

暫存器重新命名可用於防止由亂序執行(OOOE)引起的冒險。


推測執行

[編輯 | 編輯原始碼]

在分支期間,通常可以“猜測”分支的結果。透過猜測目標,可以推測性地執行指令。如果猜測錯誤,則需要清空流水線,這與停頓花費的時間相同。但是,如果猜測正確,則不會浪費時間,並且處理器將照常繼續執行。

猜測分支將採取哪種方式的過程是一個複雜的話題,超出了本書當前的範圍。

分支延遲

[編輯 | 編輯原始碼]

分支延遲是在分支之後在彙編原始碼中編寫的指令,旨在無論分支是否被執行都執行。如果沒有可以不依賴於分支執行的指令,則應插入NOP。某些彙編器能夠以這種方式重新排列程式碼,儘管其他使用此技術的彙編器需要程式設計師手動處理分支延遲。

分支預測

[編輯 | 編輯原始碼]

分支預測方案中,ISA中的所有指令或大多數指令可以根據某些條件有條件地執行。換句話說,指令將從記憶體中載入,解碼,然後處理器將確定是否執行它。例如,在分支的情況下,如果分支朝另一個方向進行,則可以關閉分支之後的流水線中的指令。分支預測與推測執行密切相關。

分支預測

[編輯 | 編輯原始碼]

分支預測是對分支指令將採取的方向進行猜測的行為。通常,分支預測器根據暫存器值和過去的分支歷史來做出這些決定。例如,在一個大型迴圈中,特定的程式可能會多次分支回迴圈頂部,然後迴圈終止。考慮此高階虛擬碼

while(condition)
    do this
end

大致轉換為此彙編虛擬碼

top of loop:
compare condition and 0.
branch to end of loop if equal
do this
branch to top of loop
bottom of loop:

此迴圈將持續重複,直到條件標誌為0。這段程式碼可能會迴圈很多次,才會在最後一次退出。在這樣的 while 結構中,它每次都會執行分支,除了最後一次,並且它只在最後一次不執行分支。因此,假設我們遇到的每個分支都會被執行,這可以提高我們的推測執行的準確性。

示例:迴圈最佳化

在現代處理器中,分支預測會經常檢視最近分支的歷史記錄,以確定如何猜測未來分支的結果。考慮以下具有巢狀條件的迴圈結構

while(loop condition)
   if(branch condition)
      do this
   else
      do that
 end

如果我們從統計上知道分支條件將有 90% 的時間為假 (0),並且迴圈條件將有近 100% 的時間為真 (1)。我們可以將其分解成彙編虛擬碼

1) compare loop condition and 0
2) branch to end of loop if equal
3) compare branch condition and 0
4) branch to branch true if not equal
5) do that
6) branch to end of if
7) branch true
8) do this
9) end of if
10)branch to top of loop

如果我們檢視此迴圈結構,我們可以看到第 10 行的分支在大多數情況下都會被執行。我們還可以看到,第 4 行的分支僅在分支條件為 1 時才會發生。我們知道分支條件僅有 10% 的時間為真,因此此迴圈的分支預測效果不佳。在這種情況下,更好的迴圈應該是

while(loop condition)
   if(not branch condition)
      do this
   else
      do that
 end

以便條件中的分支有 90% 的時間被執行,從而使分支預測器更準確。

分支預測器通常像一個計數器一樣工作。每次執行分支時,計數器都會遞增;每次不執行分支時,計數器都會遞減。考慮一個 2 位預測器。如果預測器為 0 或 1,則不執行分支;但如果預測器為 2 或 3,則執行分支。

一個具有 4 個階段的 2 位分支預測器。

我們可以像上圖所示那樣,將分支預測器視為一個有限狀態機 (FSM)。此 FSM 有 4 個階段,對應以下“猜測”

  • q0:強執行
  • q1:弱執行
  • q2:弱不執行
  • q3:強不執行

此圖中的零表示未執行分支,而 1 表示執行分支。如果執行了許多分支,則狀態會向右移動。如果不執行分支,則狀態會向左移動。

華夏公益教科書