在基於流水線(pipeline)的微處理器中,分支預測單元(Branch Predictor Unit)是一個重要的功能部件,它負責收集和分析分支/跳轉指令的執行結果,當處理後續分支/跳轉指令時,BPU將根據已有的統計結果和當前分支跳轉指令的參數,預測其執行結果,進而爲流水線取指提供決策依據,從而提升流水線效率。算法
本文將針對分支預測單元的設計思路進行討論。在進行設計前,首先須要說明使用分支預測技術的緣由及其現實意義。緩存
在傳統的流水線中,處理分支跳轉指令時,目標地址每每須要推遲到指令的執行階段才能運算得出,在此以前處理器沒法及時得知下一條指令的取指地址,所以沒法繼續取指。一種直接的解決方法是在識別分支指令後,令取指流水級及相關的流水級暫停(stall),等待分支目標地址計算完成後再繼續取指。這將浪費若干流水線時鐘週期,從而下降性能;另外一種方法是引入延遲槽(Delay Slot)機制,使得分支指令緊接的下一條指令流入流水線並執行。該指令將先於分支以前執行,所以可做爲初始化相關指令。然而在某些狀況下,延遲槽指令仍然只能簡單地用nop指令填充,一樣會形成性能損失。架構
爲便於說明,引入經典四級流水線模型的一個實例。下圖顯示了分支指令產生流水線氣泡週期的過程。(注:在本例中不考慮延遲槽機制。)app
(附圖:經典的四級流水線中分支指令引起的流水線暫停示意圖。左上紫色方塊表明分支指令。分支指令目標地址在執行階段便可得出。採用上述方法將浪費一個流水線時鐘週期)框架
爲改進以上方法, 咱們考慮引進一種靜態分支預測機制,即預測分支跳轉指令必定不跳轉(not taken),則上述狀況將成爲:默認分支跳轉指令以後的指令也流入pipeline,在若干流水級後,分支指令的地址將被計算得出,此時才判斷以前流入流水線的指令是否爲實際目標地址所指向的指令,若真,流水線能夠繼續運行而沒必要暫停;但若假,則以前流入的指令都無效,相關流水級將被沖刷(flush),處理器只能從新從正確的地址開始取值,這將一樣下降流水線的效率。性能
能夠看到,採用靜態預測後,在處理分支/跳轉指令時,流水線在必定機率下將不會因分支而暫停,這就下降了出現流水線氣泡週期的可能性。然而,在對性能要求較高的場合,此方法仍舊不能使人滿意。編碼
下圖顯示了採用靜態預測後,因爲預測失敗形成流水線flush,進而產生流水線氣泡的過程。spa
(附圖:灰色方塊指令處在譯碼階段時,分支指令處於執行階段,得出分支目標地址,而且發現灰色方塊指令不是目標地址所指向的指令(應爲藍色方塊指令),因此預測失敗,灰色方塊指令被取消,並在下一週期產生流水線氣泡)設計
爲進一步提升效率,咱們考慮動態分支預測機制。動態預測基於對分支歷史的記錄統計,預測出分支跳轉的「方向」和「目標地址」。若處理器按照預測結果取指,則一旦預測結果與實際跳轉結果相同,以前流入流水線的指令將徹底有效,流水線將維持運行。處理器按照預測出的「方向」和「目標地址」進行取值的行爲稱爲預測取值(Speculative Fetch),執行按照預測結果取出的指令的行爲稱爲預測執行(Speculative Execution)3d
隨着預測算法的不斷改進,當前分支預測的準確率不斷向1逼近,分支預測技術有效提升了流水線的運行效率,被大量運用到主流微處理器架構中。例如,目前新興的RISC指令集,如openRISC、RISC-V等,基本都取消了延遲槽設計,這種進步得益於分支預測精度的提升。
分支預測的理論依據可參考相關資料,本文再也不贅述,接下來重點討論分支預測單元的設計相關問題。
1、分支預測須要解決的問題
(1)、預測分支是否發生,即預測「方向」的問題;
(2)、預測分支指令設置的取值地址,即預測「目標地址」的問題。
2、分支預測單元的設計實現
常見的分支預測機制主要可分爲一級結構和兩級結構。對於兩級預測器,常見的算法包括gshare和gselect算法,這種算法考慮到分支指令的上下文執行歷史,精度相對較高,但實現相對複雜,本文不予討論。對於一級預測器,其設計是將多個飽和計數器和分支目標緩存器組織成一維向量表,利用分支/跳轉指令PC值的hash映射值尋址一維向量表,取出預測結果,或根據執行狀況維護狀態機。這種預測器結構最爲簡單,本文將針對這種結構作詳細的討論。
2.1 分支方向的預測
對於分支方向的預測,本文采起一種基於「方向慣性」的原理,即對於一條具體的分支指令,若該指令屢次發生跳轉,則認爲後續執行該指令時仍會發生跳轉,反之亦然。若指令預測方向與實際方向相反,則使得預測方向逐步向相反方向運動。
本文采用2bit的飽和計數器,用於寄存4種狀態。根據預測結果和實際執行結果,計數器的狀態機轉移圖以下。
圖中跳轉(taken)和不跳轉(not taken)兩種狀態分別被進一步細分爲強(strong)和弱(weak)共四種狀態,並規定strongly taken和weakly taken爲「跳轉」、strongly not taken和weakly not taken爲「不跳轉」,且每當預測出錯後,計數器會以相反方向更改狀態。
爲便於飽和計數器的實現,首先對四種狀態進行二進制編碼。
SCS_STRONGLY_TAKEN | 2‘b11 |
SCS_WEAKLY_TAKEN | 2'b10 |
SCS_WEAKLY_NOT_TAKEN | 2'b01 |
SCS_STRONGLY_NOT_TAKEN | 2'b00 |
根據編碼,在進行狀態切換時,咱們只需簡單地在不溢出的狀況下,對計數器進行自增或自減操做:當taken時,若計數值自增後不溢出,則自增;當not taken時,若計數值自減後不溢出,則自減。
同時,易知當計數值最高位爲1時,預測結果爲taken;當計數值最高位爲0時,預測結果爲not taken。
以上僅討論了針對單個分支指令的預測。對於多條位於不一樣地址的分支指令,首先將若干飽和計數器組織成一維向量表,而後利用每條指令的PC值對該一維向量表進行尋址,這樣就找到了每條分支指令對單一飽和計數器的映射關係。
2.2 分支目標地址的預測
爲了簡化設計,本文主要討論基於分支目標緩存(Branch Target Buffer)技術的預測器。BTB使用容量有限的緩存寄存最近執行的分支指令的目標地址。對於後續分支指令,預測時直接取出對應表項中寄存的地址做爲目標地址預測值。當分支指令被執行後,將實際的目標地址回寫入BTB中,爲下次預測提供依據。
本設計中,使用使用分支指令PC值的hash映射值尋址BTB表項,使得每條分支跳轉指令都能與BTB表項創建起映射關係。
其中,咱們定義PC值的hash映射規則以下:
∫ V(PC) → V(hash), f(p) ; f(p) = p & 1111111111b (即取出PC值低10位做爲對應的hash映射值)
這種算法一樣被用於2.1中所描述的飽和計數器表的尋址。
2.3 組織結構
綜上所述,分支預測器的整體框架以下,能夠看出,該預測器具備極其簡單的結構:
3、硬件描述語言實現
經過以上討論,容易使用Verilog HDL實現分支預測器。 這裏僅對飽和計數器進行非溢出的遞增或遞減,利用計數值的最高位判斷是否發生跳轉,1爲taken,0爲not taken。
1 module bpu 2 #( 3 parameter PCW = 30, // The width of valid PC 4 parameter BTBW = 10, // The width of btb address 5 ) 6 (/*AUTOARG*/ 7 // Outputs 8 pre_taken_o, pre_target_o, 9 // Inputs 10 clk, rst_n, pc_i, set_i, set_pc_i, set_taken_i, set_target_i 11 ); 12 13 // Ports 14 input clk; 15 input rst_n; 16 input [PCW-1:0] pc_i; // PC of current branch instruction 17 input set_i; 18 input [PCW-1:0] set_pc_i; 19 input set_taken_i; 20 input [PCW-1:0] set_target_i; 21 output reg pre_taken_o; 22 output reg [PCW-1:0] pre_target_o; 23 24 // Local Parameters 25 localparam SCS_STRONGLY_TAKEN = 2'b11; 26 localparam SCS_WEAKLY_TAKEN = 2'b10; 27 localparam SCS_WEAKLY_NOT_TAKEN = 2'b01; 28 localparam SCS_STRONGLY_NOT_TAKEN = 2'b00; 29 30 wire bypass; 31 wire [BTBW-1:0] tb_entry; 32 wire [BTBW-1:0] set_tb_entry; 33 34 // PC Address hash mapping 35 assign tb_entry = pc_i[BTBW-1:0]; 36 assign set_tb_entry = set_pc_i[BTBW-1:0]; 37 assign bypass = set_i && set_pc_i == pc_i; 38 39 // Saturating counters 40 reg [1:0] counter[(1<<BTBW)-1:0]; 41 generate begin :counter 42 integer entry; 43 always @(posedge clk or negedge rst_n) 44 if(!rst_n) 45 for(entry=0; entry < (1<<BTBW); entry=entry+1) // reset BTB entries 46 counter[entry] <= 2'b00; 47 else if(set_i && set_taken_i && counter[set_tb_entry] != SCS_STRONGLY_TAKEN) begin 48 counter[set_tb_entry] <= counter[set_tb_entry] + 2'b01; 49 else if(set_i && !set_taken_i && counter[set_tb_entry] != SCS_STRONGLY_NOT_TAKEN) begin 50 counter[set_tb_entry] <= counter[set_tb_entry] - 2'b01; 51 end 52 endgenerate 53 54 always @(posedge clk) 55 pre_taken_o <= bypass ? set_taken_i : counter[tb_entry][1]; 56 57 // BTB vectors 58 reg [PCW-1:0] btb[(1<<BTBW)-1:0]; 59 60 generate begin :btb_rst 61 integer entry; 62 always @(posedge clk or negedge rst_n) 63 if(!rst_n) 64 for(entry=0; entry < (1<<BTBW); entry=entry+1) begin // reset BTB entries 65 btb[entry] <= {PCW{1'b0}}; 66 end 67 endgenerate 68 69 always @(posedge clk) 70 pre_target_o <= bypass ? set_pc_i : btb[tb_entry]; 71 72 always @(posedge clk) 73 if( set_i ) 74 btb[set_tb_entry] <= set_target_i; 75 76 endmodule
對該實現須要作以下說明:
(1)、BPU在每一個時鐘週期內完成預測和更新操做。pc_i端口輸入待預測的指令PC值,set_i端口指示是否在下一個時鐘週期到來時更新預測器。
(2)、考慮到特殊的指令流狀況,該實現添加了旁路(bypass)機制。對於具體的處理器實現,旁路可能永不發生,所以能夠去掉這部分實現。
(3)、對於在BTB中沒有記錄的分支指令,BPU默認預測目標地址輸出爲0(BTB在此以前進行過置零復位)。對於具體的處理器實現,可考慮將分支指令的下一條指令的PC值做爲目標地址的預測值。
4、總結
經過討論,咱們提出了基於飽和計數器和BTB的分支預測單元的設計思路,並最終利用Verilog實現了該分支預測器的原型。該實如今面積上具備必定優點,可運用於微處理器實驗和驗證等場合;同時,該設計也存在一些不足之處:
(1)、在求PC值的hash映射值時,僅簡單地取PC低10bit的數據做爲BTB的索引,這將致使PC值高位相同的分支指令的記錄狀態互相混淆,從而下降預測精度;
(2)、該設計僅考慮分支指令的全局狀態,而沒有跟蹤分支指令具體的上下文環境;
(3)、對於帶條件判斷的分支指令、或寄存器直接/間接尋址的跳轉指令,因爲其操做數保存在寄存器中,而寄存器的值每每是不斷變化的,這將致使對分支目標地址的預測精度下降。
===================================================
本博文僅供參考,多有疏漏之處,歡迎提出寶貴意見。