l 二級分支預測 :也就是通過移位寄存器(一級)和PHT表(二級),在PHT表中根據2位飽和計數器分析當前分支的跳轉情況。
參數: N —— 一級入口數目
W —— 移位寄存器的寬度
M —— 二級入口數目
對於 Gag :三個參數值爲別爲:1 W 2^W
Gap: 三個參數值爲別爲: 1 W M
Pag:三個參數值爲別爲: N W 2^W
Pap:三個參數值爲別爲: N W M
二位飽和計數預測模式
支持的預測方式是當 XX = 0X時不跳轉
XX = 1X時跳轉
預測不跳轉
預測跳轉
分支預測的各參數定義
1) 定義了分支預測的類型
enum bpred_class{ }
2) 定義了BTB(分支目的緩衝)表的結構
書中定義的BTB表含有三個部分:分支指令的地址,分支跳轉的目的地址,以及預測結果
這兒的結構中定義了四個部分:分支指令的地址;針對當前地址的OP;如果分支跳轉,那麼跳轉的目的地地址;以及形成BTB表的鏈表結構(雙向鏈表)
3) 定義了二位飽和計數的預測結構,考慮到二級分支預測也使用了二位飽和計數器,因此利用union定義了各部分:
class -說明是二級分支預測還是bimod預測;
然後如果是bimod,那麼需要定義一個計數器結構,即:bimod.size和bimod->table;
如果是二級分支預測,需要定義的類型爲:寄存器的個數,PHT表的個數,移位寄存器中保存的分支歷史長度;把分支歷史和分支地址是否XOR的標誌;指向歷史表的指針和指向PHT表中的指針;
4) 定義了分支預測器:
定義分支預測的類型,以及指向的相應的預測器元素;
定義了BTB表的組數,以及BTB組中的相聯程度,以及BTB表
定義了返回棧:返回棧的大小,返回棧的棧頂,而返回棧也是利用了BTB的結構
同時還定義了多個計數:方向預測的正確性(向前,向後——是否跳轉)、地址預測的正確性(不僅是向前向後正確,還要有地址的正確。等等結構。
這兒不僅預測分支指令,其中還包括了程序間調用的返回等。
5) 定義了預測更新的消息
然後是各函數的聲明
1) 創建分支預測器
2) 創建了分支方向預測器
3) 分支預測器配置的輸出
4) 分支預測狀態的輸出
5) 在數據庫(sdb)中存儲分支預測的信息
6) 在主要的地點存儲分支信息
7) 利用分支預測器處理下一條指令
8) 預防因爲前瞻執行導致的錯誤
9) 對分支預測器中若干元素的更新
10) 爲了調試導出分支預測器的信息。
各函數的具體說明
1) 創建一個分支預測器
看這個函數需要先看一下創建分支方向預測器的函數
根據class,首先創建分支方向預測器(包括二級、bimod等方向預測器)
然後繼續根據分支預測器的類型class,分配返回棧
首先看一下BTB表的結構,如果BTB表大小爲0或者不是2的指數倍,不行。然後根據BTB表的組×相聯 分配空間
接下來把各項BTB結構掛接成爲鏈表
在分配完BTB之後,分配返回棧結構。而返回棧其實就是一個數組
2) 創建一個分支方向預測器
首先分配一個結構大小的空間
然後是說明當前要創建的分支方向預測器的類型(class)
接下來是根據class按需要給其中的參數賦值:
如果是二級分支預測:參數有:
二級的大小(即PHT表的個數)
移位寄存器的大小(0<SIZE<=30)
是否需要分支指令地址與移位寄存器XOR
根據移位寄存器的大小分配合適的int空間
根據PHT表的大小分配合適的unsigned char空間
然後要在PHT表中初始化預測信息,這兒是初始爲1-2-1-2的格式
如果是2位飽和計數器的預測:參數都類似
3) 輸出分支方向預測器的配置信息
4) 輸出分支預測器的配置信息
5) 以及預測的一些信息
6) 然後是保存sdb中分支預測的信息
7) 關鍵地區的信息,不重要。
8) 預測一個分支的方向
首先給的參數的選擇的方向預測器的類型class和該分支的指令地址。函數處理過程中,首先讓分支指令地址右移2位,與移位寄存器做與 得到當前有效的
而l2index則是獲取當前移位寄存器中的值
反正現在知道l2index是獲取2位飽和計數器的值的索引的。
這兒是考慮瞭如果有多個移位寄存器時的情況,因爲l1size是移位寄存器的個數,不是移位寄存器的寬度。因此如果是Ga*的話,l1index=0。然後,l2index只是獲取瞭如果有多個移位寄存器的話的其中之一。
如果需要移位寄存器中的數和分支地址做XOR的話(也就是哪個什麼很精確的分支預測法),這兒考慮了。
是直接取得二位飽和計數器中的值,用到了一個BIMOD_HASH的宏,而在有很多2位的飽和計數器時,通過BIMOD_HASH宏獲取其中的一個,可以看出,是把多個2位計數器聽過HASH鏈表連接的。
9) 預測下一條指令的地址
注意,這兒是考慮了所有的指令,不僅僅是分支指令,即代碼中的獲取指令操作碼的宏。
在這兒的預測中,首先是預測一下分支的方向,得到pdir1的結果。然後是對分支指令的跳轉地址的預測。由於是預測條件分支,也就是說,非條件分支是不預測的。在這兒,先看一下對返回棧的處理。
先得到返回棧的棧頂。
然後如果指令的返回指令,即過程調用的return,那麼目的地址就是棧裏面的地址。
然後如果指令是函數調用指令,那麼則把返回地址值壓入棧中
然後如果兩者都不是,就先查找BTB表,由於BTB可能是組相聯,因此先找到組數,然後找具體的BTB入口。
如果在BTB中沒有找到相應的指令地址,那麼根據前面的pdir,分析是不是跳轉,即只能給出方向
10) 爲了防止分支預測出現錯誤,和推測執行情況下,程序的問題,在預測的分支處保存當前的返回棧地址
11) 對分支預測的各個參數的更新。
首先是判斷是不是分支指令。
如果是分支指令,而且地址也對,那麼預測正確數+1,否則是方向+1
這個函數是當分支指令執行完畢之後處理的。但不是很明白其中的stateful 預測是什麼意思。針對BTB表的處理是有的更新,沒有的話利用LRU替換。
//==============================================================================
//寫文件的人: wahaha_nescafe
//聯繫方式: [email protected]
//
//系統說明: 這是一個32位微處理器的模擬程序,屬於原型,實現的功能有限。
// 參考了MIPSR10000、POWPC620處理器的結構,以及體系結構
// 中的聖經《計算機體系結構——量化研究方法》。
// 實現了指令的4發射、亂序執行。
//版權說明: Copyright (C) 2004-2005 by wahaha_nescafe
// All Rights Not Reserved!
// (沒有版權,隨便折騰——不能用於商業目的)
//==============================================================================
#ifndef BPRED_H
#define BPRED_H
//******************************************************************************
//整個分支預測的說明
//在這兒將實現三種類型的分支預測:
// two_level : 二級分支預測
// gshare : gshare分支預測
// neural : 基於神經網絡的分支預測
//整個分支預測的分支配置 N, W, M, V
// N : 分支歷史寄存器的個數
// W : 分支歷史寄存器的長度
// M : 模式表的個數
// V : 每個模式表的表項數
// 注意:一般情況下,V = 2^W
//
// 對於下列幾種情況的配置說明:
// GAg (一個分支歷史寄存器,一個PHT表) : 1, W, 1, V
// GAp (一個分支歷史寄存器,多個PHT表) : 1, W, M, V
// PAg (多個分支歷史寄存器,一個PHT表) : N, W, 1, V
// PAp (多個分支歷史寄存器,多個PHT表) : N, W, M, V
//
//CPU利用分支預測的方法:
// 在取指階段,利用BTB進行分支地址的預測
// 在譯碼階段,進行分支方向的預測
// 這兩種預測算法之間沒有必然的聯繫
// 如果在譯碼階段發現利用BTB預測的是錯誤的,則譯碼階段有最高權利
//實現的是兩段分支預測方法(模仿了PowPC620)
//******************************************************************************
#include "config.h"
#include "system.h"
//******************************************************************************
//以下定義分支預測的類型
enum BPRED_TYPE
{
BPRED_2LEV = 1,
BPRED_GSHARE = 2,
BPRED_NEURAL = 3
};
//******************************************************************************
//以下定義BTB的結構
//先定義BTB表的替換策略
enum BPRED_BTB_POLICY
{
//利用計數器方法實現的LRU替換方法:
//每一塊都設置一個計數器,計數器的操作規則是:
// (1) 在調入或者替換出去一個塊時, 其計數器清“0”,而其它的計數器則加“1”。
// (2) 當訪問命中時,所有塊的計數值與命中塊的計數值要進行比較,
// 如果計數值小於命中塊的計數值, 則該塊的計數值加“1”;
// 如果塊的計數值大於命中塊的計數值,則數值不變。最後將命中塊的計數器清爲0。
// (3) 需要替換時,則選擇計數值最大的塊被替換。
BTB_LRU = 1,
//利用計數器方法實現的FIFO的替換方法:
//每一塊都設置一個計數器,計數器的操作規則是:
// (1) 在調入一個塊時,其計數器清“0”,而其他的計數器加“1”。
// (2) 在訪問命中時,不進行操作。
// (3) 需要替換時,選擇計數器最大的塊進行替換。
BTB_FIFO = 2,
BTB_RANDOM = 3
};
//******************************************************************************
//定義BTB表中的各個表項
struct bpred_btb_entry_t
{
bool_t valid; //該表項是不是有效
addr_t source_addr; //分支指令的地址
addr_t target_addr; //分支目標指令的地址
counter_t counter; //計數器,主要是用於BTB替換時處理的
struct bpred_btb_entry_t *next; //各個表項的鏈接指針
};
//定義BTB表中一個組的結構
struct bpred_btb_set_t
{
struct bpred_btb_entry_t* head; //形成表項的頭指針
struct bpred_btb_entry_t* tail; //形成表項的尾指針,幾乎沒用
};
//定義整個BTB表
struct bpred_btb_t
{
struct bpred_btb_set_t* set; //BTB中的組
enum BPRED_BTB_POLICY policy; //BTB中的組的塊使用的替換策略
word_t associative; //BTB的相聯度
word_t nsets; //BTB的組數
};
//分支地址預測器的結構
struct bpred_addr_t
{
struct bpred_btb_t* btb; //指向BTB表的指針
counter_t addr_hits; //找到相應地址的次數(找到的不一定是正確的)
counter_t lookups; //查找BTB表的次數(不一定能找到相應的表項)
counter_t pred_hits; //預測正確的次數
};
//******************************************************************************
//以下爲獲取指令地址在BTB中組的宏
#define BPRED_GET_BTB_SET(btb, addr) ( ((addr)>>2) & ((btb->nsets)-1) )
//******************************************************************************
//以下定義分支模式歷史表的結構
//分支歷史寄存器的結構
struct bpred_hreg_t
{
word_t reg; //分支歷史寄存器存放分支歷史的地方
};
//分支模式歷史表的結構
struct bpred_pht_t
{
byte_t* data;
};
//分支方向預測器的結構
struct bpred_dir_t
{
enum BPRED_TYPE type;
struct bpred_hreg_t* hreg; //指向分支歷史寄存器
word_t hreg_num; //分支歷史寄存器的個數
word_t hreg_width; //分支歷史寄存器的長度
struct bpred_pht_t* pht; //指向模式歷史表
word_t pht_num; //模式歷史表的個數
word_t pht_entry; //每個模式歷史表的入口個數
counter_t pred_taken; //預測跳轉的次數
counter_t actual_taken; //實際跳轉的次數
counter_t pred_ntaken; //預測不跳轉的次數
counter_t actual_ntaken; //實際不跳轉的次數
};
//******************************************************************************
//創建分支方向預測器和分支地址預測器
//創建分支方向預測器
struct bpred_dir_t* bpred_dir_create (
enum BPRED_TYPE type, //預測的類型
word_t hreg_num, //分支歷史寄存器的個數
word_t history_width, //分支歷史寄存器的長度
word_t pht_num, //PHT表的個數
word_t pht_entry_num //PHT表表項的個數
);
//創建一個分支地址預測器
struct bpred_addr_t* bpred_addr_create(
word_t btb_entry_num, //分支地址預測器中的btb表項的數目
word_t btb_associative, //BTB的相聯度
enum BPRED_BTB_POLICY policy //BTB的替換方法
);
//******************************************************************************
//以下爲讀取BTB中的某個entry
//如果該entry正好命中,則讀出來,否則爲NULL
static void bpred_read_btb_entry( struct bpred_addr_t*, //分支地址預測器
addr_t addr, //搜尋的指令地址
struct bpred_btb_entry_t** entry //表項
);
//******************************************************************************
//以下爲在BTB中的某個set中選擇將要被替換出來的entry
static void bpred_replaced_entry( struct bpred_addr_t*, //分支地址預測器
addr_t addr, //搜尋的指令地址
struct bpred_btb_entry_t** entry //表項
);
//******************************************************************************
//以下爲進行分支地址的預測
bool_t bpred_addr_pred(
struct bpred_addr_t* bp_addr, //接受的分支方向預測器
addr_t source_addr, //進行分支方向預測的分支指令地址
addr_t* result_addr //分支地址預測的結果
//如果在BTB表中找到了對應的分支指令地址,則說明
//這次分支是要跳轉的,然後查看當前的表項是不是有效
//有效就取出預測結果地址,作爲下一條PC
);
//******************************************************************************
//以下爲進行分支方向的預測
void bpred_dir_pred(
struct bpred_dir_t* bp_dir, //接受的分支方向預測器
addr_t source_addr, //進行分支方向預測的分支指令地址
long* dir_result //分支方向預測的結果
//對於二級分支預測,結果不外呼:00/01/10/11
//對於神經網絡,則有可能是正負數
);
//******************************************************************************
//以下爲分支預測失效後的分支部件的恢復
void bpred_update( struct bpred_dir_t* bp_dir, //分支方向預測器
struct bpred_addr_t* bp_addr, //分支地址預測器
addr_t source_addr, //該條分支指令的地址
addr_t result_addr //該條分支指令的目的地址
);
//******************************************************************************
//以下爲分支預測的初始化
void bpred_init(void);
//******************************************************************************
//分支預測的結束處理
void bpred_uninit(void);
//******************************************************************************
//分支預測的統計信息
void bpred_statistic( struct bpred_dir_t* bp_dir,
struct bpred_addr_t* bp_addr
);
#endif //BPRED_H
分支預測執行文件.c
//==============================================================================
//寫文件的人: wahaha_nescafe
//聯繫方式: [email protected]
//
//系統說明: 這是一個32位微處理器的模擬程序,屬於原型,實現的功能有限。
// 參考了MIPSR10000、POWPC620處理器的結構,以及體系結構
// 中的聖經《計算機體系結構——量化研究方法》。
// 實現了指令的4發射、亂序執行。
//版權說明: Copyright (C) 2004-2005 by wahaha_nescafe
// All Rights Not Reserved!
// (沒有版權,隨便折騰——不能用於商業目的)
//==============================================================================
#include <stdio.h>
#include <malloc.h>
#include <math.h>
#include "system.h"
#include "config.h"
#include "bpred.h"
//******************************************************************************
//創建分支方向預測器
/*
(1)函數名:
(2)接收參數:
type : 採用什麼類型的分支方向預測
hreg_num : 使用的分支歷史寄存器的個數
history_width : 使用的分支歷史寄存器的長度
pht_num : PHT表的個數
pht_entry_num : 每個PHT表的項數
(3)返回值:
(4)函數過程說明:
分支歷史寄存器的最大長度是32位
(5)修改於:
2005-8-28 0:30
(6)作者:
wahaha_nescafe
*/
struct bpred_dir_t*
bpred_dir_create ( enum BPRED_TYPE type, //預測的類型
word_t hreg_num, //分支歷史寄存器的個數
word_t history_width,//分支歷史寄存器的長度
word_t pht_num, //PHT表的個數
word_t pht_entry_num //PHT表入口的個數
)
{
struct bpred_dir_t* bpdir = NULL;
struct bpred_hreg_t* bphreg = NULL;
struct bpred_pht_t* bppht = NULL;
byte_t* tdata;
word_t i, j;
//分支歷史寄存器的個數是2的冪次
ASSERT(hreg_num>=1 && ((hreg_num & (hreg_num-1))==0));
//由於是利用一個無符號整數保存分支歷史因此分支歷史最大32位
ASSERT(history_width>=0 && history_width<=32);
//PHT表也是一樣的道理
ASSERT( (pht_num>=1) && ((pht_num & (pht_num-1))==0 ) );
ASSERT( (pht_entry_num>=1) && ((pht_entry_num & (pht_entry_num-1))==0 ) );
//給分支方向預測器分配空間
bpdir = (struct bpred_dir_t*)malloc(sizeof(struct bpred_dir_t));
if(!bpdir)
system_error("ERROR When create branch prediction predictor\n");
//得到分支方向預測器的類型
bpdir->type = type;
//分支歷史寄存器分配空間
bphreg = (struct bpred_hreg_t*)
malloc( sizeof(struct bpred_hreg_t) * hreg_num );
if(!bphreg)
system_error("ERROR When create branch history register\n");
//初始化分支歷史寄存器
for(i=0; i<hreg_num; i++)
(bphreg+i)->reg = 0;
bpdir->hreg = bphreg;
bpdir->hreg_num = hreg_num;
bpdir->hreg_width = history_width;
//分配分支模式歷史表的空間
bppht = (struct bpred_pht_t*)
malloc( sizeof(struct bpred_pht_t) * pht_num );
if(!bppht)
system_error("ERROR When create pht \n");
//然後要在每一個PHT表中分配存放預測數據的具體的空間
//先判斷類型,再for循環的效率高點
for(i=0; i<pht_num; i++)
{
//如果是神經網絡預測
if(type == BPRED_NEURAL)
{
//在這兒可以看出,基於神經網絡的權重最大爲-128-127
//使用時要進行無符號數和有符號數之間的轉換
tdata = (byte_t*)
malloc( sizeof(byte_t) * history_width * pht_entry_num );
if(!tdata)
system_error("ERROR When create data\n");
//然後需要初始化其中的數據
for(j=0; j<(history_width*pht_entry_num); j++)
{
(byte_t)(*(tdata+i)) = 0;
}
}
else
{
tdata = (byte_t*)malloc( sizeof(byte_t) * pht_entry_num );
if(!tdata)
system_error("ERROR When create data\n");
//然後需要初始化其中的數據
for(j=0; j<pht_entry_num; j++)
{
(byte_t)(*(tdata+i)) = 0;
}
}
(bppht+i)->data = tdata;
}
bpdir->pht = bppht;
bpdir->pht_num = pht_num;
bpdir->pht_entry = pht_entry_num;
bpdir->pred_taken = 0;
bpdir->actual_taken = 0;
bpdir->pred_ntaken = 0;
bpdir->actual_ntaken = 0;
return bpdir;
}
//******************************************************************************
//創建分支地址預測器
/*
(1)函數名:
(2)接收參數:
btb_entry_num : BTB表項的數目
btb_associative : BTB的相聯度
policy : BTB的替換方法
(3)返回值:
(4)函數過程說明:
這個BTB表可以使用數組實現的
(5)修改於:
2005-8-28 0:39
(6)作者:
wahaha_nescafe
*/
struct bpred_addr_t*
bpred_addr_create( word_t btb_entry_num, //BTB表項的數目
word_t btb_associative, //BTB的相聯度
enum BPRED_BTB_POLICY policy //BTB的替換方法
)
{
struct bpred_addr_t* bpaddr;
struct bpred_btb_t* bpbtb;
struct bpred_btb_set_t* bpset;
struct bpred_btb_entry_t* bpentry;
struct bpred_btb_entry_t* temp_bpentry;
word_t i, j;
word_t nsets;
//確保他們爲2的冪
ASSERT((btb_entry_num & (btb_entry_num-1))==0);
ASSERT((btb_associative & (btb_associative-1))==0);
//分配了分支地址預測器的空間
bpaddr = (struct bpred_addr_t*)malloc( sizeof(struct bpred_addr_t) );
if(!bpaddr)
system_error("ERROR When create address prediction \n");
bpaddr->addr_hits = 0;
bpaddr->lookups = 0;
bpaddr->pred_hits = 0;
//分配BTB表的空間
bpbtb = (struct bpred_btb_t*)malloc( sizeof(struct bpred_btb_t) );
if(!bpbtb)
system_error("ERROR When create address prediction \n");
bpaddr->btb = bpbtb;
//求出一共有多少組
nsets = btb_entry_num/btb_associative;
//分配組
bpset = (struct bpred_btb_set_t*)
malloc( sizeof(struct bpred_btb_set_t) * nsets );
if(!bpset)
system_error("ERROR When create address prediction set\n");
//在組中沒有建立連接的機制,訪問的話就是+1訪問類型的
//設置BTB表的屬性
bpbtb->set = bpset;
bpbtb->policy = policy;
bpbtb->associative = btb_associative;
bpbtb->nsets = nsets;
//在BTB表的每一個組內部進行處理
for(i=0; i<nsets; i++)
{
//每個組內部分配若干個表項
bpentry = (struct bpred_btb_entry_t*)
malloc( sizeof(struct bpred_btb_entry_t) * btb_associative );
if(!bpentry)
system_error("ERROR When create address prediction set\n");
//各個組的頭尾指針指好了
(bpset+i)->head = bpentry;
(bpset+i)->tail = bpentry + (btb_associative-1);
(bpset+i)->tail->next = NULL;
//組中的各個塊賦值
for(j=0; j<btb_associative; j++)
{
(bpentry+j)->valid = 0;
(bpentry+j)->source_addr = 0;
(bpentry+j)->target_addr = 0;
(bpentry+j)->counter = 0;
}
//組中各個塊連接成鏈表結構
j = 0;
while( j<(btb_associative-1) )
{
temp_bpentry = bpentry+1;
bpentry->next = temp_bpentry;
bpentry = temp_bpentry;
j++;
}
}
return bpaddr;
}
//******************************************************************************
//以下爲讀取BTB中的某個entry
/*
(1)函數名:
(2)接收參數:
bp_addr : 分支地址預測器
addr : 分支指令地址,即利用這個地址查找BTB表
entry : 尋找到的相應的塊
(3)返回值:
(4)函數過程說明:
如果在BTB中找到addr地址對應的項,就返回相應的entry
得到的entry存放在傳入的參數“entry”中
如果沒有找到,就返回NULL
因此,需要entry參數在傳進來的時候爲NULL,這樣才能進行判斷
(5)修改於:
2005-8-28 10:05
(6)作者:
wahaha_nescafe
*/
void
bpred_read_btb_entry( struct bpred_addr_t* bp_addr,
addr_t addr,
struct bpred_btb_entry_t** entry
)
{
byte_t n;
struct bpred_btb_t* bp_btb;
struct bpred_btb_set_t* bp_set;
struct bpred_btb_entry_t* bp_entry;
struct bpred_btb_entry_t* bp_entry2;
ASSERT(bp_addr!=NULL);
ASSERT((*entry)==NULL);
bp_addr->lookups++;
//得到BTB表
bp_btb = bp_addr->btb;
//得到BTB表內的某一個組
bp_set = (struct bpred_btb_set_t*)
(bp_btb->set + BPRED_GET_BTB_SET(bp_btb, addr));
//得到組內的entry
bp_entry = bp_set->head;
ASSERT(bp_entry!=NULL);
//在一個組內循環比較地址(如果組比較多,可以使用HASH方法擺放組)
for(n=0; n<bp_btb->associative; n++)
{
//發現了就break
if( (bp_entry->source_addr==addr) && (bp_entry->valid==1) )
break;
else
bp_entry = bp_entry->next;
}
//如果找到了我們所需要的entry
if(bp_entry)
{
bp_addr->addr_hits++;
//在一個組進行替換算法的處理
if(bp_btb->policy==BTB_LRU)
{
bp_entry2 = bp_set->head;
for(n=0; n<bp_btb->associative; n++)
{
//如果其他的計數器的值比較小,則需要加“1”
if( (bp_entry2->valid==1)
&& (bp_entry2->counter<bp_entry->counter) )
{
bp_entry2->counter++;
}
bp_entry2 = bp_entry2->next;
}
//找到的這個塊的計數器設置爲“0”
bp_entry->counter = 0;
}
//然後返回找到的塊
*entry = bp_entry;
}
//如果沒有找到塊
else
{
*entry = NULL;
}
return;
}
//******************************************************************************
//以下爲在BTB中的某個set中選擇將要被替換出來的entry
/*
(1)函數名:
(2)接收參數:
bp_addr : 分支地址預測器
addr : 分支指令地址
entry : 尋找到的相應的塊
(3)返回值:
(4)函數過程說明:
這個函數是在更新BTB表的時候使用的,在取指令週期,進行分支地址預測時,
不會使用這個函數。而更新BTB表則是在第四個週期即完成周期進行的
利用完成周期時處理的的分支指令地址,
此時的結果不可能爲NULL
(5)修改於:
2005-9-6 11:06
(6)作者:
wahaha_nescafe
*/
void
bpred_replaced_entry( struct bpred_addr_t* bp_addr,
addr_t addr,
struct bpred_btb_entry_t** entry
)
{
byte_t n;
byte_t nx;
long k;
long max;
struct bpred_btb_t* bp_btb;
struct bpred_btb_set_t* bp_set;
struct bpred_btb_entry_t* bp_entry;
struct bpred_btb_entry_t* bp_entry2;
ASSERT(bp_addr!=NULL);
ASSERT((*entry)==NULL);
bp_addr->lookups++;
//得到BTB表
bp_btb = bp_addr->btb;
//得到BTB表內的某一個組
bp_set = (bp_btb->set + BPRED_GET_BTB_SET(bp_btb, addr));
//得到組內的entry
bp_entry = bp_set->head;
ASSERT(bp_entry!=NULL);
for(n=0; n<bp_btb->associative; n++)
{
if(bp_entry->valid==0)
break;
else
bp_entry = bp_entry->next;
}
//如果有空位置
if(bp_entry)
{
if( (bp_btb->policy==BTB_LRU)||(bp_btb->policy==BTB_FIFO) )
{
bp_entry2 = bp_set->head;
ASSERT(bp_entry2!=NULL);
//因爲要新調進來塊
//因此對其他塊來說,計數器的值要變化
for(n=0; n<bp_btb->associative; n++)
{
if(bp_entry2->valid==1)
{
bp_entry2->counter++;
}
bp_entry2 = bp_entry2->next;
}
ASSERT(bp_entry2==NULL);
bp_entry->counter = 0;
}
*entry = bp_entry;
}
//如果沒有空位置
else if(bp_entry==NULL)
{
if(bp_btb->policy==BTB_RANDOM)
{
k = random(bp_btb->associative);
//得到將要被替換出去的塊
bp_entry = bp_set->head + k;
}
else if((bp_btb->policy==BTB_LRU)||(bp_btb->policy==BTB_FIFO) )
{
//尋找計數器最大的一個塊準備替換
bp_entry = bp_set->head;
max = bp_entry->counter;
nx = 0;
for(n=1; n<bp_btb->associative; n++)
{
bp_entry2 = bp_entry+1;
//此時可以不判斷valid的值,因爲這兒的前提是
//沒有空位置,也就是說valid都爲1
if((counter_t)max < bp_entry2->counter)
{
max = bp_entry2->counter;
nx = n;
}
bp_entry = bp_entry2;
}
//得到計數器最大的一個塊
bp_entry = bp_set->head+nx;
ASSERT(bp_entry!=NULL);
//現在得到了計數器最大的一個塊
//然後把這個塊的計數器值置爲0,其他塊的計數器值加“1”
bp_entry2 = bp_set->head;
for(n=0; n<bp_btb->associative; n++)
{
if(n!=nx)
{
bp_entry2->counter++;
}
bp_entry2 = bp_entry2->next;
}
bp_entry->counter = 0;
}
*entry = bp_entry;
}
ASSERT(*entry != NULL);
return ;
}
//******************************************************************************
//進行分支地址的預測
/*
(1)函數名:
(2)接收參數:
bp_addr : 分支地址預測器
source_addr : 源地址,分支指令地址
result_addr : 目標地址,即預測的分支跳轉地址
(3)返回值:
void
(4)函數過程說明:
進行分支地址的預測,這個是在取指令週期調用的
如果在BTB中找到對應的指令地址,則得到對應的目標地址
如果沒有找到,返回的目標地址是0xffffffff
這個處理方法不是很好!
2005-9-20 11:21
今天修改,得到返回值,即:沒有在BTB中找到目的地址,將返回FALSE
(5)修改於:
2005-8-28 10:35
(6)作者:
wahaha_nescafe
*/
bool_t
bpred_addr_pred( struct bpred_addr_t* bp_addr,
addr_t source_addr,
addr_t* result_addr
)
{
struct bpred_btb_entry_t* entry = NULL;
bool_t result = 0;
//獲取相應的entry
bpred_read_btb_entry(bp_addr, source_addr, &entry);
//如果找到了指令地址對應的entry
if(entry)
{
result = 1;
*result_addr = entry->target_addr;
}
else
{
result = 0;
*result_addr = 0xffffffff;
}
return result;
}
//******************************************************************************
//進行分支方向的預測
/*
(1)函數名:
(2)接收參數:
bp_dir : 分支方向預測器
source_addr : 分支指令地址
dir_result : 方向預測結果
(3)返回值:
(4)函數過程說明:
這個是在第二個週期即譯碼和發射週期調用的
根據分支指令的地址,查找PHT表進行預測,得到預測結果
對於2lev和gshare,預測的結果是00\01\10\11
對於neural,預測的結果則有可能是正數或負數
最後在得到結果後,要進行處理:跳轉爲1;不跳轉爲-1
在最後,還要處理,即跳轉爲“1”,不跳轉爲“-1”
(5)修改於:
2005-8-28 13:27
(6)作者:
wahaha_nescafe
*/
void
bpred_dir_pred( struct bpred_dir_t* bp_dir,
addr_t source_addr,
long* dir_result
)
{
struct bpred_hreg_t* bp_reg;
struct bpred_pht_t* bp_pht;
word_t tag;
byte_t* data;
word_t reg;
//首先根據指令地址選擇某一個分支歷史寄存器
bp_reg = bp_dir->hreg + ((source_addr>>2)&(bp_dir->hreg_num-1));
//然後選擇模式歷史表
bp_pht = bp_dir->pht + ((source_addr>>2)&(bp_dir->pht_num-1));
switch(bp_dir->type)
{
//一起處理了啊,簡單點
case BPRED_2LEV:
case BPRED_GSHARE:
{
//根據分支歷史寄存器中的值選取模式歷史表的一個二位飽和計數器
//首先得到分支歷史寄存器中的值,gshare類型的要做一個XOR
if(bp_dir->type == BPRED_GSHARE)
tag = source_addr ^ (bp_reg->reg);
else
tag = bp_reg->reg;
//然後選擇其中的某個二位飽和計數器
tag = tag & (bp_dir->pht_entry-1);
*((byte_t*)dir_result) = *(bp_pht->data + tag);
//最後進行轉化
if( (*dir_result) >=2 )
*dir_result = 1;
else
*dir_result = 0;
break;
}
case BPRED_NEURAL:
{
//此時需要利用指令地址選擇PHT表中的某一個向量
tag = (bp_dir->hreg_width)*((source_addr>>2)&(bp_dir->pht_entry-1));
data = bp_pht->data + tag;
*dir_result = 0;
reg = bp_reg->reg;
for(tag=0; (word_t)tag<bp_dir->hreg_width; tag++)
{
//這兒需要做正負數的變換
if((reg&1)==1)
*dir_result += (signed long) (*(data+tag));
else
*dir_result -= (signed long) (*(data+tag));
reg = reg>>1;
}
if( (*dir_result) >= 0)
*dir_result = 1;
else
*dir_result = 0;
break;
}
default:
{
ASSERT(-1);
break;
}
}
if(*dir_result == 1)
{
bp_dir->pred_taken++;
}
else if(*dir_result == 0)
{
bp_dir->pred_ntaken++;
}
return;
}
//******************************************************************************
//分支預測的更新工作
/*
(1)函數名:
(2)接收參數:
bp_dir : 分支方向預測器
bp_addr : 分支地址預測器
source_addr : 正在更新的分支指令的地址
result_addr : 正在更新的分支指令的目的地址
(3)返回值:
void
(4)函數過程說明:
分支預測的更新工作包括更新二個部分:
(1)更新BTB表
首先在BTB表中尋找是不是有這條分支指令對應的項
※如果有,則利用這個新的跳轉地址更新這個項
※如果沒有,則找一個地方,安排這條分支指令住下來
(2)更新PHT表以及分支歷史寄存器
首先根據分支指令地址和分支歷史寄存器找到PHT表的表項,
然後更新分支歷史寄存器,以及PHT表中的內容
(5)修改於:
2005-9-1 21:44
(6)作者:
wahaha_nescafe
*/
void
bpred_update( struct bpred_dir_t* bp_dir,
struct bpred_addr_t* bp_addr,
addr_t source_addr,
addr_t result_addr
)
{
struct bpred_btb_entry_t* btb_entry = NULL;
struct bpred_hreg_t* bp_reg = NULL;
struct bpred_pht_t* bp_pht = NULL;
word_t tag;
bool_t branch_taken;
long dir_result;
byte_t* data = NULL;
unsigned long reg = 0;
ASSERT( (bp_dir!=NULL) && (bp_addr!=NULL) );
//先看看這條分支指令有沒有跳轉成功
if( (source_addr+4) == result_addr )
{
branch_taken = 0;
bp_dir->actual_ntaken++;
}
else
{
branch_taken = 1;
bp_dir->actual_taken++;
}
//更新BTB表,首先得到BTB中被替換出去的塊,
bpred_replaced_entry( bp_addr, source_addr, &btb_entry );
btb_entry->valid = 1;
//進行信息統計
if( (btb_entry->source_addr == source_addr)
&& (btb_entry->target_addr == result_addr)
)
{
bp_addr->pred_hits++;
}
//然後在這個entry中寫入分支指令地址和跳轉地址
else
{
btb_entry->source_addr = source_addr;
btb_entry->target_addr = result_addr;
}
//之後是更新分支歷史寄存器和PHT表
//首先根據指令地址選擇某一個分支歷史寄存器
bp_reg = bp_dir->hreg + ((source_addr>>2)&(bp_dir->hreg_num-1));
//然後選擇模式歷史表
bp_pht = bp_dir->pht + ((source_addr>>2)&(bp_dir->pht_num-1));
switch(bp_dir->type)
{
//一起處理了啊,簡單點
case BPRED_2LEV:
case BPRED_GSHARE:
{
//根據分支歷史寄存器中的值選取模式歷史表的一個二位飽和計數器
if(bp_dir->type == BPRED_GSHARE)
tag = source_addr ^ (bp_reg->reg);
else
tag = bp_reg->reg;
tag = tag & (bp_dir->pht_entry-1);
//得到的dir_result 有可能是四個值:00/01/10/11
dir_result = *(bp_pht->data + tag);
//如果跳轉
if(branch_taken == 1)
{
if(dir_result<=2)
*(bp_pht->data + tag) = dir_result + 1;
}
else
{
if(dir_result>0)
*(bp_pht->data + tag) = dir_result - 1;
}
break;
}
case BPRED_NEURAL:
{
//此時需要利用指令地址選擇PHT表中的某一個向量
tag = (bp_dir->hreg_width)*((source_addr>>2)&(bp_dir->pht_entry-1));
data = bp_pht->data + tag;
dir_result = 0;
reg = bp_reg->reg;
for(tag=0; (unsigned long)tag<bp_dir->hreg_width; tag++)
{
if((reg&1)==1) //即原來的跳轉
{
if(branch_taken==1)
(*(data+tag))
= (((signed long) (*(data+tag))+1)>BPRED_NEURAL_MAX_EDGE)?
((signed long) (*(data+tag))) : ((signed long) (*(data+tag))+1) ;
else if(branch_taken==0)
(*(data+tag))
= (((signed long) (*(data+tag))-1)<BPRED_NEURAL_MIN_EDGE)?
((signed long) (*(data+tag))) : ((signed long) (*(data+tag))-1) ;
}
else if((reg&1)==0)
{
if(branch_taken==0)
(*(data+tag))
= (((signed long) (*(data+tag))+1)>BPRED_NEURAL_MAX_EDGE)?
((signed long) (*(data+tag))) : ((signed long) (*(data+tag))+1) ;
else if(branch_taken==1)
(*(data+tag))
= (((signed long) (*(data+tag))-1)<BPRED_NEURAL_MIN_EDGE)?
((signed long) (*(data+tag))) : ((signed long) (*(data+tag))-1) ;
}
reg = reg>>1;
}
break;
}
default:
{
ASSERT(-1);
break;
}
} //switch branch type
//然後就是更新分支歷史寄存器
if(branch_taken==1)
bp_reg->reg = (bp_reg->reg<<1)+1;
else
bp_reg->reg = (bp_reg->reg<<1);
return;
}
//******************************************************************************
//分支預測的初始化
/*
(1)函數名:
(2)接收參數:
void
(3)返回值:
void
(4)函數過程說明:
沒有特殊的過程,初始化系統中的分支地址預測和分支方向預測
(5)修改於:
2005-9-20 11:36
(6)作者:
wahaha_nescafe
*/
void bpred_init(void)
{
printf("%s\n", "branch prediction init>>>>>>>>>>");
SYS_bpred_addr = bpred_addr_create( BPRED_BTB_ENTRY,
BPRED_BTB_ASSOCIATIVE,
BPRED_BTB_REPLACEMENT
);
SYS_bpred_dir = bpred_dir_create( BPRED_PREDICTION_TYPE,
BPRED_HISTORY_NUM,
BPRED_HISTORY_WIDTH,
BPRED_PHT_NUM,
BPRED_PHT_ENTRY
);
return;
}
//******************************************************************************
//分支處理的結束
/*
(1)函數名:
(2)接收參數:
void
(3)返回值:
void
(4)函數過程說明:
沒有特殊的過程,釋放系統中的空間
(5)修改於:
2005-9-20 11:36
(6)作者:
wahaha_nescafe
*/
void bpred_uninit(void)
{
struct bpred_btb_set_t* btb_set = NULL;
struct bpred_btb_set_t* btb_set2 = NULL;
struct bpred_btb_entry_t* btb_entry = NULL;
struct bpred_btb_entry_t* btb_entry2 = NULL;
unsigned long set = 0;
unsigned long entry = 0;
unsigned long k = 0;
struct bpred_pht_t* pht = NULL;
ASSERT(SYS_bpred_addr);
ASSERT(SYS_bpred_dir);
//釋放分支地址預測
btb_set = SYS_bpred_addr->btb->set;
for(set=1; set<=SYS_bpred_addr->btb->nsets; set++)
{
btb_set2 = btb_set + 1;
btb_entry = btb_set->head;
for(entry=1; entry<=SYS_bpred_addr->btb->associative; entry++)
{
btb_entry2 = btb_entry->next;
free(btb_entry);
btb_entry = btb_entry2;
}
btb_set = btb_set2;
}
free(SYS_bpred_addr->btb->set);
free(SYS_bpred_addr->btb);
free(SYS_bpred_addr);
//釋放分支方向預測
pht = SYS_bpred_dir->pht;
for(k=1; k<=SYS_bpred_dir->pht_num; k++ , pht+=1)
free(pht->data);
free(SYS_bpred_dir->pht);
free(SYS_bpred_dir->hreg);
free(SYS_bpred_dir);
return;
}
//******************************************************************************
//統計系統中的信息
/*
(1)函數名:
(2)接收參數:
bp_dir : 系統中的分支方向預測器
bp_addr : 系統中的分支地址預測器
(3)返回值:
void
(4)函數過程說明:
統計一下信息而已,主要是統計命中次數和預測正確率
當然,也可以統計其他信息,暫時沒想到呢
(5)修改於:
2005-9-20 11:42
(6)作者:
wahaha_nescafe
*/
void bpred_statistic( struct bpred_dir_t* bp_dir,
struct bpred_addr_t* bp_addr
)
{
ASSERT(bp_dir && bp_addr);
printf("\n%s\n","BRANCH PREDICTION STATISTIC:");
printf("%s\n","branch address prediction:");
printf(" %s : %d\n","look up times ", bp_addr->lookups);
printf(" %s : %d\n","addr hit times", bp_addr->addr_hits);
printf(" %s : %d\n","pred hit times", bp_addr->pred_hits);
printf("%s\n","branch direction prediction:");
printf(" %s : %d\n","pred taken times ", bp_dir->pred_taken);
printf(" %s : %d\n","atcual taken times ", bp_dir->actual_taken);
printf("========END========\n");
return;
}