1. 程式人生 > >【CPU微架構設計】利用Verilog設計基於飽和計數器和BTB的分支預測器

【CPU微架構設計】利用Verilog設計基於飽和計數器和BTB的分支預測器

  在基於流水線(pipeline)的微處理器中,分支預測單元(Branch Predictor Unit)是一個重要的功能部件,它負責收集和分析分支/跳轉指令的引數和執行結果,當處理新的分支/跳轉指令時,BPU將根據已有的統計結果和當前分支跳轉指令的引數,預測其執行結果,為流水線取指提供決策依據,進而提高流水線效率。

  下面討論提出分支預測機制的主要原因和實際意義:

  在流水線處理分支跳轉指令時,目標地址往往需要推遲到指令的執行階段才能運算得出,在此之前處理器無法及時得知下一條指令的取指地址,因此無法繼續取指。一種解決方法是在識別分支指令後,令取指流水級及相關的流水級暫停(stall),等待分支目標地址計算完成後再繼續取指。這將浪費若干流水線時鐘週期,從而降低效能。採用這種方法的流水線暫停效果示意圖如下:

 

(附圖:經典的四級流水線中分支指令引發的流水線暫停示意圖,坐上紫色方塊代表分支指令。由於採用簡單的理想流水線,分支指令目標地址在執行階段即可得出,這將浪費一個流水線時鐘週期)

  為改進以上方法, 我們考慮引進一種靜態分支預測機制,即預測分支跳轉指令一定不跳轉(not taken),則上述情況將成為:預設分支跳轉指令之後的指令也流入pipeline,在若干流水級後,分支指令的地址將被計算得出,此時才判斷之前流入流水線的指令是否為實際目標地址所指向的指令,若真,流水線可以繼續執行而不必暫停;但若假,則之前流入的指令都無效,相關流水級將被沖刷(flush),處理器只能重新從正確的地址開始取值,這將同樣降低流水線的效率。

  可以看到,採用靜態預測後,在處理分支/跳轉指令時,流水線在一定概率下將不會因分支而暫停,這就降低了出現流水線氣泡週期的可能性。然而,在對效能要求較高的場合,此方法仍舊不能令人滿意。

 

  為進一步提高效率,我們考慮動態分支預測機制。動態預測基於對分支歷史的記錄統計,預測出分支跳轉的“方向”和“目標地址”。若處理器按照預測結果取指,則一旦預測結果與實際跳轉結果相同,之前流入流水線的指令將完全有效,流水線將維持執行。處理器按照預測出的“方向”和“目標地址”進行取值的行為稱為預測取值(Speculative Fetch),執行按照預測結果取出的指令的行為稱為預測執行(Speculative Execution)

  隨著預測演算法的不斷改進,當前分支預測的準確率不斷向1逼近,分支預測技術有效提高了流水線的執行效率,被大量運用到主流微處理器中。

 

一、分支預測需要解決的問題

  (1)、預測分支是否發生,即預測“方向”的問題;

  (2)、預測分支指令設定的取值地址,即預測“目標地址”的問題。

 

二、分支預測單元的設計實現

  常見的分支預測機制主要可分為一級結構和兩級結構。對於兩級預測器,常見的演算法包括gsharegselect演算法,這種演算法考慮到分支指令的上下文執行歷史,精度相對較高,但實現相對複雜,本文不予討論。對於一級預測器,其設計是將多個飽和計數器和BPU組織成一維向量表,利用分支/跳轉指令PC值的hash對映值定址一維向量表。這種預測器結構最為簡單,本文將針對這種結構做詳細的討論。

 

 2.1 分支方向的預測

  本文采用2bit的飽和計數器,用於寄存4種狀態。根據預測結果和實際執行結果,計數器的值將按照如下所示狀態機轉移圖轉移。

  圖中跳轉(taken)和不跳轉(not taken)兩種狀態分別被進一步細分為強(strong)和弱(weak)共四種狀態,並規定strongly taken和weakly taken為“跳轉”、strongly not taken和weakly not taken為“不跳轉”,且每當預測出錯後,計數器會以相反方向更改狀態,這種做法的本質其實是一種“阻尼式”的切換。

 

 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.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值作為目標地址的預測值。

 

四、總結

  通過討論,我們提出了基於飽和計數器和BTB的分支預測單元的設計思路,並最終利用Verilog實現了該分支預測器的原型。該實現在面積上具有一定優勢,可運用於微處理器實驗和驗證等方面;同時,該設計也存在一些不足之處:

  (1)、在求PC值的hash對映值時,僅簡單地取PC低10bit的資料作為BTB的索引,這將導致PC值高位相同的分支指令的記錄狀態互相混淆,從而降低預測精度;

  (2)、該設計僅考慮分支指令的全域性狀態,而沒有跟蹤分支指令具體的上下文環境;

  (3)、對於帶條件判斷的分支指令、或暫存器直接/間接定址的跳轉指令,由於其運算元儲存在暫存器中,而暫存器的值往往是不斷變化的,這將導致對分支目標地址的預測精度降低。

 

===================================================

本博文僅供參考,難免有錯誤或疏漏之處,歡迎提出寶貴意見。