biriscv 处理器 npcg 设计
前言
biriscv 的 issue 单元一旦接受到错误的指令,将会把错误的信号,包括目标 pc 发射到前端,提示前端取这个 pc 对应的指令。
前端需要把这个 pc 发射给 icache 单元,返回数据后,将这个 pc 的预测信息 next_taken 和 pc、inst 对齐,一块儿发给 decode 单元,实际上这就是 fetch 单元在做的事情。
但是一旦 npc 做成了两个周期,为了适配 npc 的工作,并能将 npc 流水线化,就得构思出一种架构。
boom 的做法是,在 F0 阶段将 pc 一起发射到 icache、bpd 中,同时 bpd 的单周期预测器立即给出预测信息并锁存以供下一个周期使用:
1 | |
boom 的单周期预测器这样做很自然,但是我要在 biriscv 中将 npc 改成 2 周期预测器,这就很不自然了。
biriscv 中 fetch 中的数据对齐
T0
处理器刚启动的时候,biriscv 中的 branch_request_i 拉高,同时会有一个 vec_pc(branch_pc_w) 输送到 pc_f_q。与此同时,active_q 也将在下一个周期被拉高。
同时通往 npc 的输出信号 pc_f_o,pc_accept_o 也会有值,npc 会在本周期立即给出预测信息。
T1
到了下一个周期的时候,active_q 被激活,pc_f_q 也锁存了有效数据。此时 icache 就可以访问内存了:
1 | |
与此同时,为了将icache 返回的数据对齐发送到 decode 模块,这里要对 icache 的请求信号进行保存:
1 | |
pred_d_q 保存的是 vec_pc 的预测信息,如何得到这个信息呢?这个信息实际上是将 vec_pc 送到 npc 中得到的。也就是说,输入信息 {vec_pc},输出信息 {vec_pc+8,next_taken}。可以看出,需要对齐的信息是 vec_pc、next_taken。
T2
此时,数据从 icache 返回,如果没有返回,说明 icache 未命中,只需要等待几个周期即可。返回的时候,pc_d_q、pred_d_q 已经准备好,这时候和 icache 返回的数据 instr 对齐发射到 decode 单元。
对齐的数据是:
- vec
- vec 作为 npc 输入得到的 taken 信息
- vec 作为 icache 的返回数据
biriscv 中的做法就是将 vec 输入到 npc,本周期返回的 taken 数据直接用,没有跨周期,这很容易实现。但是要将 npc 做成双周期的,就得设计一下各个信号的时序了。
biriscv 的性能如下:
1 | |
改造成功的标志应该是分支预测正确率和 coremark 分数应该相差很小。
npcg
我的做法是,给 fetch 单元加了新的一级流水线 npcg,意思就是专门用于产生 next_pc 的单元。这其实是将 fetch 单元的 pc 产生单元解耦到 npcg 中了。
T0
当 branch_request_i 信号拉高的时候,将 branch_pc_i 的数据送进 npc 以及 pc_r,此时 fetch 单元不接收数据。
T1
此时 pc_r 已经将 pc 锁存,并通过组合电路将锁存器的 pc 拉到 fetch 单元,而且此时 vec 作为 npc 输入信号的输出信号 vec+8,next_taken 已经输出。
这些输出信号继续存入到锁存器:
1 | |
对于 npc,输出的信号继续进去 npc 进行预测。
T2
这个周期,fetch 单元就接收数据了:
1 | |
T3
这一个周期 fetch 的指令缓存已经保存好了 npcg 发射的信息,包括一个 pc,以及和这个 pc 一起出现的 next_taken。
因此这里就有一个问题,next_taken 应该和进入 npc 的信号 pc 对齐,不应该和 next_taken 一起输出的 pc 对齐。
这里应该怎么做呢?这要从 npcg 说起。
将 branch_pc_i 输出到 npc,这样就能获取 branch_pc_i 作为输入的 next_taken。同时,将 branch_pc_i 输入到 pc_r,到了下一个周期就得到了:
- pc_r:branch_pc_i
- branch_pc_i 作为 npc 输入的输出 next_taken 信息
同时 npc 也立即输入了 branch_pc_i + 8。
同时将这两个信号直接送给 fetch 单元,这样自然就是对齐的。
运行了一下,性能明显下降:
1 | |