# 分支预测
分支预测即根据分支历史，提前预测指令跳转方向及跳转目标。分支预测器种类繁多，例如BTB（Branch Target Buffer）、BHT（Branch History Table）、RAS（Return Address Stack）等，但适用于五级流水的其实并不多。如设计概述中所介绍的，五级流水中，pfs级发出取指请求，fs级取回指令，ds级开始译码，并明确跳转指令的所有信息。也就表示分支预测只有一拍的空间可以利用，且无预译码，可依据的信息只有pc，跳转方向及跳转目标都只能靠猜。适用的实际上仅有BTB。不过对于较为特殊的jirl指令，即寄存器跳转，若直接采用BTB的机制，会使得命中率极低。因此，在BTB的基础上进行改造，由CAM表记录jirl指令的pc，并由RAS机制预测其跳转目标。

----

接下来介绍分支预测器的具体实现，分为两点：分支预测器的内部实现；分支预测器同流水线的交互。
## 分支预测器设计
分支预测器的逻辑可以分为两个部分，由PC进行预测跳转目标及跳转方向的预测部分；根据指令译码结果与分支预测器预测结果的比较，对分支预测器历史信息进行修正的修正部分。
### 预测部分
首先来看模块的接口。
- fetch_pc：取指pc
- fetch_en：取指使能
- ret_en：预测结果使能
- ret_pc：预测跳转目标
- taken：预测跳转方向
- ret_index：指示当前预测结果的对应项，用于修正
  
btb表有32项，包含四个内容：
- btb_pc：30位，用于和取指pc匹配
- btb_target：30位，存放跳转目标
- btb_counter：2位，由最高位指示跳转方向
- btb_valid：1位，当前项是否有效

对于jirl指令的预测逻辑由两部分组成，16项的CAM表以及8项的RAS。    
CAM表包含两个内容：
- ras_pc：30位，用于和取指pc匹配
- ras_valid：1位，当前项是否有效

RAS简单来说就是一个栈。存储30位的跳转目标，由ras_ptr指示栈顶。  

明确表项内容后，来看预测逻辑。fetch_en以及fetch_pc进入分支预测器时，首先缓存一拍。单看分支预测器，缓存插在什么位置其实都可以，只要有一拍的延迟即可，但考虑到赋值nextpc即fetch_pc的路径非常长，因此将缓存插在靠前的位置。缓存后的信号为fetch_en_r及fetch_pc_r，将其与两个CAM的PC部分，btb_pc和ras_pc进行匹配。该项有效且pc匹配则表示命中。命中项记录在btb_match_rd和ras_match_rd中。

两个表中有一项命中，则置起ret_en。若命中在btb CAM表中，ret_pc取对应项的btb_target即可，若命中在ras CAM表中，则取ras栈顶的跳转目标。理论上btb_match_rd和ras_match_rd为one-hot且只会命中在一个表中，因此不必使用优先级逻辑。ret_index同理，而taken在btb_match时，需要看对应项btb_counter最高位，而ras_match时，直接置为1。预测阶段不会对分支预测器的历史信息进行任何修正。

### 修正部分
在ds译码级，汇总分支预测信息以及译码得到的正确分支跳转信息。   
预测信息同上：
- ds_btb_target
- ds_btb_index
- ds_btb_taken
- ds_btb_en
  
正确的分支跳转信息如下：
- br_to_btb：表示当前指令是否为分支预测器所覆盖的跳转指令，当前结构的分支预测器覆盖所有跳转指令。
- br_taken：跳转方向
- br_target：跳转目标

译码级根据以上信息，稍加分析并传递至分支预测器，传递信息包括：
- operate_en：分支预测器修正操作使能。注意该信号需要在ds_ready_go、es_allowin和ds_valid为1时置起且无例外。只有ds当拍允许流动至下一级时才能发出修正操作。对于ds_ready_go，因为流水线中指令寄存器的依赖有可能导致ds阻塞，阻塞时指令所得到的寄存器值是错误的，即译码得到的分支跳转信息也是错误的。而es_allowin的介入使其操作使能仅维持一拍，避免重复操作。
- operate_pc：修正操作针对的分支指令的pc
- operate_index：修正操作针对的CAM表项
- pop_ras：为jirl指令时置起
- push_ras：为bl指令时置起
- add_entry：当前指令为分支指令但预测器未进行预测时置起。这里与上了一个额外条件br_taken，因为当pc在分支预测器中未命中时，顺序指令，也算是预测为untaken。br_taken不为1时，并不需要在预测器中建项
- delete_entry：当前指令不为分支指令但预测器进行预测时置起。这种情况出现的概率极低，只有在自修改代码中，可以不用考虑
- pre_error：当前指令为分支指令且预测器进行预测，但跳转目标不一致时置起
- pre_right：当前指令为分支指令且预测器进行预测，且跳转目标一致时置起
- target_error：当前指令为分支指令且预测器进行预测，且跳转目标一致，但跳转目标不一致时置起
- right_orien：正确的跳转方向
- right_target：正确的跳转目标

分支预测器会根据以上信息进行修正，首先由pop_ras即是否为jirl指令区分当前操作的是BTB部分还是RAS部分，针对BTB部分，操作类型包括：
- 建项：add_entry置起时。使能指定项，并填入operate_pc和right_target，以及初始化btb_counter为2'b10。对于填入项即index的选择，若BTB CAM表中还留有未使能项，则选择其中一项。若所有项都有效则选择btb_counter为2'b00的一项，即大概率不跳转的预测项，因为未在btb中命中的pc即预测为不跳转，两者效果是相同的，只不过该pc若下一次为跳转，则会重新建项，相当于将其btb_counter由2'b00直接转为2'b10，很有可能导致下一次的预测出错。但是这种情况出现的概率肯定比替换掉其他btb_counter的值导致下一次预测错的概率低。若所有项都有效且btb_counter都不为2'b00，则随机选择一项。
- 修正跳转目标：target_error置起时。重新填入right_target，并初始化btb_counter为2'b10。
- 修正跳转方向：pre_error或者pre_right置起时。根据right_orien调整btb_counter，若正确的分支方向为taken则累加，若为untaken则递减。

针对RAS部分，操作类型包括：
- 建项：add_entry置起时。使能指定项，填入operate_pc即可。相当于预测pc的分支类型。index的选择同BTB部分。
- 进栈：当push_ras置起且当前RAS非满时，存入当前pc的下一条指令，并将指针上移。即当前为bl函数跳转指令，其返回地址为bl的下一条指令。RAS指针指向的是空项。
- 出栈：当pop_ras置起且当前RAS非空时，指针直接下移一项即可。

## 分支预测器与流水线交互逻辑

首先简单介绍取指逻辑，该部分内容会在后续逐步展开。来看几个关键信号，inst_valid指示当前取指pc是否有效，该信号直接送往icache等待接收。inst_valid主要看fs_allowin信号，即pfs指令允许向后流动时，其取指pc即为有效，能够避免相同pc取指请求的重复发出。nextpc即为取指pc，当前仅关注其最为常规的pc来源seq_pc，顺序取指pc。当inst_valid为1时，并不代表取指请求即刻便能发出，还需要等待icache空闲，由inst_addr_ok表示（取指请求的其他去向tlb、btb不需要等待）。当inst_valid && inst_addr_ok为1，取指请求发出，pfs_ready_go置起，pfs向fs流动。    
fetch_en和fetch_pc发送至分支预测器后，下一拍得到预测结果（btb_en/btb_ret_pc/btb_taken）。当btb_en && btb_taken为1时，fetch_btb_target置起，分支跳转，将nextpc纠正至btb_ret_pc。这段逻辑中有一点需要注意，分支预测器返回的结果仅维持一拍，但pfs并不一定能够向后流动，因此需要缓存分支预测器的返回结果，避免信息丢失。分支预测器的返回结果缓存在btb_lock_buffer中，由btb_lock_en指示缓存是否有效。缓存条件即为分支返回结果且pfs无法向后流动（btb_en && !pfs_ready_go），缓存释放条件为下一次取指重新发起。而nextpc的维护以及fs向ds传递分支预测结果时都将采用btb_ret_pc_t、btb_ret_pc_t、btb_ret_pc_t、btb_ret_pc_t信号，由分支预测器返回信息和缓存的信息相或得到，两者同一时刻仅有一个有效。    
译码级，根据译码和分支预测的结果，能够知道下一条指令取指的方向是否正确。取指级有两个方面的工作需要完成：修正分支预测的历史信息；若预测错误则需纠正取指方向。此处主要讲述第二方面的内容。若译码级检测到分支预测错误，则会置起btb_pre_error_flush信号，由该信号修正取指方向，该工作可继续细分为两个内容
- 取消掉下一条进入译码级的指令，及错误预测导致错误取指的指令    
两种情况需要考虑：
    - 当该分支指令向后流动的那一拍，即（btb_pre_error_flush && es_allowin）为1时，若下一拍下一条指令紧接着进入译码级，则可直接取消。不过可以用更粗犷的方法，即下一拍的ds_valid直接为0，无需识别是否有指令进入。
    - 同样的条件，若下一拍无指令进入，即当拍fs_to_ds_valid为0，则置起branch_slot_cancel触发器。该触发器为1时，会等待下一条指令进入，当检测fs_to_ds_valid为1时，将其取消，并恢复branch_slot_cancel至0。
- 纠正nextpc。当译码级发现分支预测错误，会将正确的跳转目标（btb_pre_error_flush_target）和使能信号（btb_pre_error_flush）通过br_bus传递至取指阶段，修正nextpc。
需要考虑两个问题：
    - 当nextpc修正信息向前传递时，pfs并不一定处于能够取指的状态，例如inst_addr_ok不为1。此时若译码级不阻塞，且pfs不将修正信息存入buffer，会导致信息丢失。
    - 译码级会记录分支预测错误，并取消掉错误预测的分支指令的下一条指令。若pfs在取完预测错误的分支指令后始终处于阻塞状态，之后直接被修正至正确的跳转方向，则会导致正确的指令被取消掉。    

    针对上述两个问题，可在pfs中构建一个状态机解决。br_target_inst_req_state表示状态。当发现预测错误后，根据流水线状态，进入不同的状态。
    - !fs_valid && !inst_addr_ok：预测错误分支指令取出后未进行任何取指，因为fs级为空，inst_addr_ok从未置起。且当前也不处于可取指的状态。进入br_target_inst_req_wait_slot状态。
    - !inst_addr_ok && fs_valid：已有一条错误取指的指令在fs中，但pfs并不处于能够取指的状态。进入br_target_inst_req_wait_br_target状态。
    - inst_addr_ok && !fs_valid：预测错误分支指令取出后未进行任何取指，当前处于可取指的状态，但当前的指令会在译码级取消。进入br_target_inst_req_wait_br_target状态。     
 
    br_target_inst_req_wait_slot状态会待取指请求发出后切换至br_target_inst_req_wait_br_target状态，该状态便会将nextpc修正至存储btb_pre_error_flush_target的pc buffer（br_target_inst_req_buffer）。当取指请求再次发出后，状态切换至空状态br_target_inst_req_empty。

    构建状态机后，还需要注意一个问题。也许pfs处于即刻能够发出取指请求的状态。可粗略的判断fs级是否有指令，便改变next_pc同时置起inst_valid，即使当前无法取指(inst_addr_ok为0)也没有关系。

