SimpleScalar中分支預測與實例源代碼

支持的分支預測類型
l 二級分支預測 :也就是通過移位寄存器(一級)和PHT表(二級),在PHT表中根據2位飽和計數器分析當前分支的跳轉情況。
參數:   N —— 一級入口數目
        W —— 移位寄存器的寬度
        M —— 二級入口數目
對於 Gag :三個參數值爲別爲:1  W  2^W
    Gap: 三個參數值爲別爲: 1  W  M
(只有一個移位寄存器,因此N爲1,又因爲當只有一個PHT表,因此,Gag中M是2^w,此時,通過移位寄存器,查找PHT表,得到分支情況;而在Gap中,不同的分支有不同的PHT表,因此需要利用分支指令的地址獲取多個PHT表中的其中一個,因此M = K*2^w,其中的K應該是設定的PHT表的個數,而且,K應該是2的指數級)
    Pag:三個參數值爲別爲: N  W  2^W
    Pap:三個參數值爲別爲: N  W  M
(此時,將同時利用分支指令的地址索引移位寄存器和PHT表,如果是PAG,那麼,利用分支指令地址索引多個移位寄存器,獲取的值再去索引PHT表,獲取跳轉情況,如果是PAP,則同時利用分支指令地址索引移位寄存器和PHT表,得到跳轉情況)。
  二位飽和計數預測模式
支持的預測方式是當  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替換。 

以下是我寫的一個模擬器的代碼中的分支預測代碼

分支預測頭文件.h
//==============================================================================
//寫文件的人:  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;
}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章