注意:学习笔记,重点记录,后续会进行修改,参考《从零开始写RISC-V处理器》。
跳转就是改变PC寄存器的值。又因为跳转与否需要在执行阶段才知道,所以当需要跳转时,则需要暂停流水线(正确来说是冲刷流水线。流水线是不可以暂停的,除非时钟不跑了)。
其中长方形表示的是时序逻辑电路,云状型表示的是组合逻辑电路。在执行阶段,当判断需要发生跳转时,发出跳转信号和跳转地址给ctrl(ctrl.v)模块。ctrl模块判断跳转信号有效后会给pc_reg、if_id和id_ex模块发出流水线暂停信号,并且还会给pc_reg模块发出跳转地址。在时钟上升沿到来时,if_id和id_ex模块如果检测到流水线暂停信号有效则送出NOP指令,从而使得整条流水线(译码阶段、执行阶段)流淌的都是NOP指令,已经取出的指令就会无效,这就是流水线冲刷机制。
ctrl.v的输入输出信号如下表所示:
序号 | 信号名 | 输入/输出 | 位宽(bits) | 说明 |
---|---|---|---|---|
1 | rst | 输入 | 1 | 复位信号 |
2 | jump_flag_i | 输入 | 1 | 跳转标志 |
3 | jump_addr_i | 输入 | 32 | 跳转地址 |
4 | hold_flag_ex_i | 输入 | 1 | 来自执行模块的暂停标志 |
5 | hold_flag_rib_i | 输入 | 1 | 来自总线模块的暂停标志 |
6 | jtag_halt_flag_i | 输入 | 1 | 来自jtag模块的暂停标志 |
7 | hold_flag_clint_i | 输入 | 1 | 来自中断模块的暂停标志 |
8 | hold_flag_o | 输出 | 3 | 暂停标志 |
9 | jump_flag_o | 输出 | 1 | 跳转标志 |
10 | jump_addr_o | 输出 | 32 | 跳转地址 |
对于跳转(跳转包含暂停流水线操作),是要冲刷整条流水线的,因为跳转后流水线上其他阶段的其他操作是无效的。对于其他模块的暂停信号,一种最简单的设计就是也冲刷整条流水线,但是这样的话MCU的效率就会低一些。另一种设计就是根据不同的暂停信号,暂停不同的流水线阶段。比如对于总线请求的暂停只需要暂停PC寄存器这一阶段就可以了,让流水线上的其他阶段继续工作。
...
always @ (*) begin
if (rst == `RstEnable) begin
hold_flag_o = `Hold_None;
jump_flag_o = `JumpDisable;
jump_addr_o = `ZeroWord;
//复位时,恢复默认值
end else begin
jump_addr_o = jump_addr_i;
//输出跳转地址直接等于输入跳转地址
jump_flag_o = jump_flag_i;
//输出跳转标志直接等于输入跳转标志
// 默认不暂停
hold_flag_o = `Hold_None;
// 按优先级处理不同模块的请求
if (jump_flag_i == `JumpEnable || hold_flag_ex_i == `HoldEnable || hold_flag_clint_i == `HoldEnable) begin
//对于跳转操作、来自执行阶段的暂停、来自中断模块的暂停则暂停整条流水线
// 暂停整条流水线
hold_flag_o = `Hold_Id;
end else if (hold_flag_rib_i == `HoldEnable) begin
//对于总线暂停,只需要暂停PC寄存器,让译码和执行阶段继续运行
// 暂停PC,即取指地址不变
hold_flag_o = `Hold_Pc;
end else if (jtag_halt_flag_i == `HoldEnable) begin
//对于jtag模块暂停,则暂停整条流水线
// 暂停整条流水线
hold_flag_o = `Hold_Id;
end else begin
hold_flag_o = `Hold_None;
end
end
end
...
跳转时只需要暂停流水线一个时钟周期,但是如果是多周期指令(比如除法指令),则需要暂停流水线多个时钟周期。