SimpleScalar的分支预测模拟器为sim-bpred
SimpleScalar分支预测的实现方法:先进行分支方向探测,即是否采取分支(当然跳转指令和调用返回指令不用作这一步),接着是生成分支地址,对于调用返回指令,直接在RAS上作相关操作,普通分支指令则要利用BTB来进行地址探测,命中则生成地址。然后对两步综合,地址命中且分支预测为采取,返回分支目标地址;地址不命中且分支预测为采取,返回1;只要分支预测为不采取,就返回0。
重点分析针对条件分支指令的方向探测方法,主要有6种,三种静态:taken,not tanken,perfect;三种动态:bimod,2-level,comb。静态的方法顾名思义,只是perfect这种,按它的原薏是不预测,直接把真正采取的下一条指令填入npc,而且它确实不需要调用pred-lookup函数,但这种方法有时性能不如bimod,另外在sim-bpred中也没加入这种方法,只是在sim-outorder中有实现。
对于三种动态方法,分别说明如下:
bimod是最普通的,即采用一个2bit宽的分支方向预测表,按分支地址查找,2bit分支预测器的判断和更新与课本上的一致。这种方式只有一个参数,就是分支预测表的长度。
2-level要复杂一些,它采用两级表格式,第一级是分支历史表,存放各组分支历史寄存器的值,第二级是全局/局部分支模式表,(全局或局部应是由表长相对于分支历史寄存器的长决定),它存放各分支历史模式的2bit预测器。在判断时用当前分支指令对应的历史寄存器值去索引二级表得到相应预测器值。更新时,把当前分支的方向左移入历史寄存器,并对使用过的2bit预测器作更新。它有四个参数,前三个是一级表长度,二级表长度,历史寄存器宽度,最后一个是异或标志。如果为1,则将历史寄存器的值与当前分支指令地址异或,用其结果再去索引二级模式表。
comb方式组合了以上两种方法,它再加入了一个meta表,这个表类似bimod的预测表,只是它预测的是采取bimod还是2-level,也采用2-bit预测器,被采取的预测方法被定为第一方向,未被采取的定为第二方向。更新时,如果第一方向与第二方向不同则更新meta表,否则只更新两种方法各自的表即可。它共有三组参数,前两组即bimod和two-level的参数,第三组是关于meta表长度的说明。至于BTB的更新与cache的更新方式相同。在现代处理器中分支预测是必不可少的,也是设计中比较复杂的部分。
在SimpleScalar的乱序执行模拟sim-outorder中,控制指令的Next_PC在流水线的ruu_dispatch阶段计算出来,分支预测则是在取指阶段完成,预测的下一指令PC值信息跟随当前指令流入流水线。分支预测的模式有四种:
BPred2Level: 两级分支预测模式
BPred2bit:简单的两位直接映射预测模式,(分支目标缓存BTB)
BPredTaken:分支发生静态预测模拟
BPredNotTaken: 分支不发生静态预测模拟
另外可以使用两级分支预测和两位预测联合的复合预测模式(BPredComb)。
在sim-bpred.c中检查了输入的动态预测方法选项,并创建了相应的分支预测器。
sim_check_options(struct opt_odb_t *odb, int argc, char **argv)函数中:
if (!mystricmp(pred_type, "taken"))
{//创建预测器实例
bpred = bpred_create(BPredTaken, 0, 0, 0, 0, 0, 0, 0, 0, 0);
}
在sim_main函数中:
while (TRUE)
{
regs.regs_R[MD_REG_ZERO] = 0;
#ifdef TARGET_ALPHA
regs.regs_F.d[MD_REG_ZERO] = 0.0;
#endif
/* 获取下一条指令*/
MD_FETCH_INST(inst, mem, regs.regs_PC);
/* 指令计数 */
sim_num_insn++;
/* set default reference address and access mode */
addr = 0; is_write = FALSE;
/* set default fault - none */
fault = md_fault_none;
/* 对指令译码 */
MD_SET_OPCODE(op, inst);
/* 执行指令 */
switch (op)
{
#define DEFINST(OP,MSK,NAME,OPFORM,RES,FLAGS,O1,O2,I1,I2,I3) \
case OP: \
SYMCAT(OP,_IMPL); \
break;
#define DEFLINK(OP,MSK,NAME,MASK,SHIFT) \
case OP: \
panic("attempted to execute a linking opcode");
#define CONNECT(OP)
#define DECLARE_FAULT(FAULT) \
{ fault = (FAULT); break; }
#include "machine.def"
default:
panic("attempted to execute a bogus opcode");
}
if (fault != md_fault_none)
fatal("fault (%d) detected @ 0x%08p", fault, regs.regs_PC);
if (MD_OP_FLAGS(op) & F_MEM)
{
sim_num_refs++;
if (MD_OP_FLAGS(op) & F_STORE)
is_write = TRUE;
}
if (MD_OP_FLAGS(op) & F_CTRL)
{
md_addr_t pred_PC;
struct bpred_update_t update_rec;
sim_num_branches++;
if (pred)// 如果分支预测器创建成功
{
/* 获取预测的下条指令的地址 */
pred_PC = bpred_lookup(pred,
/* 分支地址*/regs.regs_PC,
/*目的地址 */target_PC,
/* 指令操作码 */op,
/* call? */MD_IS_CALL(op),
/* return? */MD_IS_RETURN(op),
/* stash an update ptr */&update_rec,
/* stash return stack ptr */&stack_idx);
/* 判断从分支预测器返回的下条指令地址是否合法 */
if (!pred_PC)// 不合法,(当返回0时,表示采用分支不转移预测)
{
/* 分支不转移,pc直接加一 */
pred_PC = regs.regs_PC + sizeof(md_inst_t);
}
/* 根据指令执行的实际结果,来更新分支预测器*/
bpred_update(pred,
/* 分支地址 */regs.regs_PC,
/* resolved branch target */regs.regs_NPC,
/* 分支是否转移 */regs.regs_NPC != (regs.regs_PC +
sizeof(md_inst_t)),
/* pred taken? */pred_PC != (regs.regs_PC +
sizeof(md_inst_t)),
/* correct pred? */pred_PC == regs.regs_NPC,
/* opcode */op,
/* predictor update pointer */&update_rec);
}
}
/* check for DLite debugger entry condition */
if (dlite_check_break(regs.regs_NPC,
is_write ? ACCESS_WRITE : ACCESS_READ,
addr, sim_num_insn, sim_num_insn))
dlite_main(regs.regs_PC, regs.regs_NPC, sim_num_insn, ®s, mem);
/* go to the next instruction */
regs.regs_PC = regs.regs_NPC;
regs.regs_NPC += sizeof(md_inst_t);
/* finish early? */
if (max_insts && sim_num_insn >= max_insts)
return;
}