系列文章目录
文章目录
前言
本系列是为了记录在学习《手把手教你设计CPU——RISC-V处理器》的过程中的一些新的以及思考,同时把一些内容进行了简化便于理解,如果小伙伴对处理器感兴趣的话务必拜读一下,能够让你对处理器架构有一个大体的了解。
一、交付、取消、冲刷的简介
在处理器流水线中,交付和取消是确保高效且正确指令执行的关键机制,同时这两者的产生也会与流水线冲刷相联系。
1.1 交付(Commit)
交付是指一条指令在经过预测执行后,确认其指令是正确的,并被真正执行,进而影响处理器的状态。其发生在一个确定的阶段(通常在执行之后),当分支跳跃指令的预测被证实正确时,表示该指令能够真正的被运行,因此进行交付。因此其作用在于确保指令正确执行,允许流水线继续处理后续指令,提升处理效率。
1.2 取消(Cancel)
取消是指当一条指令的预测执行结果错误时,所有相关后续指令被终止执行。其与交付是一个相对的概念,在分支预测错误时触发,取消会导致预测执行的指令结果被丢弃,不会影响处理器状态。
1.3 冲刷
冲刷指的是处理器流水线冲刷 。 当处理器流水线需要将没有“交忖”的后续指令全都“取消”掉时,就会造成“流水线冲刷” ,就像水流一样将流水线重新冲刷干净,之后重新开始取新的指令。
二、处理器中交付的常用策略
处理器“交忖”的实现非常依赖、具体的微架构,常见的实现策略简述如下。
- 无论有多少个流水线,交付总是一条一条指令的运行,理论上讲只有前面的交付了后面的才可以交付。
- 影响指令“交付”的因素通常包括以下情形。
- 中断,异常,分支预测指令。
这些情形往往会造成流水线冲刷,即将后续所有的指令流都取消掉。 - 条件码(Conditional Code)
因此对于每条指令,只有其条件码满足条件为真才会“交付”,否则会被“取消”。
- 处理器通过性能架构可以选择一个周期“交付” 一条指令或者一个周期“交 付”多条指令。
- 在不同的微架构中,"交付”可以在不同的流水线位置完成 。
- 在“执行”阶段进行“交付
- 在“写回”阶段进行“交付”
- 在重排序交付队列( Re-Order Commit Queue )中进行
对于高性能的超标量处理器而言,往往是乱序执行乱序写回,写回往往会使用 ROB或者纯物理寄存器的方式,因此,往往会配备一个较深的重排序交付队列 用来缓存乱序执行的指令信息,并对其按序进行“交付" 。
三、E200对于交付功能的简化
主要简化方向有以下几点:
- 指令没有条件码,因此不会出现因为条件不行而取消的现象 。
- 所有的运算指令都不会产生异常。因为其在很多处理上都取消了运行中的异常,比如出发操作上的被除数为0。
综上,在该处理器中只会出现两种取消的情况:
- 分支预测指令错误预测造成的后续指令流取消。
- 中断和异常造成的后续指令流取消。
四、E200交付功能硬件的实现
下面是E200内部交付硬件存在的位置:
需要注意对于分支预测错误的分支指令自身和遭遇了中断或者异常的指令自身而言, 仍然是属于成功“交付”的指令,因为它们自身己经被真正执行且对处理器的状态真正地产生了影响。
4.1 分支预测指令的处理
在该模块中,这些条件跳转指令需要经过“比较”运算才能确定最终是否真的需要跳转,而“比较 ”运算需由“执行”阶段的ALU完成。下面是该模块的输入输出端口:
端口名称 | 方向 | 功能描述 |
---|---|---|
bjp_i_valid | 输入 | 分支预测指令的有效性信号 |
bjp_i_ready | 输出 | 表示接收方已准备好接收分支预测指令 |
bjp_i_rs1 | 输入 | 第一个操作数寄存器值 |
bjp_i_rs2 | 输入 | 第二个操作数寄存器值 |
bjp_i_imm | 输入 | 立即数,用于计算分支目标地址 |
bjp_i_pc | 输入 | 当前指令的程序计数器值 |
bjp_i_info | 输入 | 包含分支预测相关信息 |
bjp_o_valid | 输出 | 提交结果的有效性信号 |
bjp_o_ready | 输入 | 表示接收方已准备好接收提交结果 |
bjp_o_wbck_wdat | 输出 | JAL和JALR指令的写回数据 |
bjp_o_wbck_err | 输出 | 写回操作的错误指示 |
bjp_o_cmt_bjp | 输出 | 表示分支预测指令已被处理 |
bjp_o_cmt_mret | 输出 | 机器返回指令的提交确认 |
bjp_o_cmt_dret | 输出 | 调试返回指令的提交确认 |
bjp_o_cmt_fencei | 输出 | FENCE.I同步指令的提交确认 |
bjp_o_cmt_prdt | 输出 | 表示分支预测的准确性 |
bjp_o_cmt_rslv | 输出 | 表示分支预测的最终状态 |
bjp_req_alu_op1 | 输出 | 提供给ALU的第一个操作数 |
bjp_req_alu_op2 | 输出 | 提供给ALU的第二个操作数 |
bjp_req_alu_cmp_eq | 输出 | 等于比较操作符指示 |
bjp_req_alu_cmp_ne | 输出 | 不等于比较操作符指示 |
bjp_req_alu_cmp_lt | 输出 | 小于比较操作符指示 |
bjp_req_alu_cmp_gt | 输出 | 大于比较操作符指示 |
bjp_req_alu_cmp_ltu | 输出 | 小于无符号比较操作符指示 |
bjp_req_alu_cmp_gtu | 输出 | 大于无符号比较操作符指示 |
bjp_req_alu_add | 输出 | 加法操作请求指示 |
bjp_req_alu_cmp_res | 输入 | ALU的比较操作结果 |
bjp_req_alu_add_res | 输入 | ALU的加法操作结果 |
下面是代码中对于处理类型的判断,可以看出一共有6种类型:
- beq (两个整数操作数等于则跳转)
- bne (两个整数不相等则 跳转)
- blt (第一个有符号数小于第 二 个有符号数则跳转)
- bltu (第一个无符号数小于第二个无符号数则跳转)
- bge (第一个有符号数大于等于第二个有符号数则跳转)
- bgeu (第一 个无符号数大于等于第 二个无符号数则跳转)
下面是将每一个类型从info总线中提取出来。
assign bjp_req_alu_cmp_eq = bjp_i_info [`E203_DECINFO_BJP_BEQ ];
assign bjp_req_alu_cmp_ne = bjp_i_info [`E203_DECINFO_BJP_BNE ];
assign bjp_req_alu_cmp_lt = bjp_i_info [`E203_DECINFO_BJP_BLT ];
assign bjp_req_alu_cmp_gt = bjp_i_info [`E203_DECINFO_BJP_BGT ];
assign bjp_req_alu_cmp_ltu = bjp_i_info [`E203_DECINFO_BJP_BLTU ];
assign bjp_req_alu_cmp_gtu = bjp_i_info [`E203_DECINFO_BJP_BGTU ];
将预测和真实的跳转结果发送给支付模块。
- 如果是无条件跳转(JMP)指令则一定会跳
- 如果是条件跳转(Conditional Branch)则会使用ALU运算数据通路进行比较运算的结果
assign bjp_o_cmt_prdt = bjp_i_bprdt;
assign bjp_o_cmt_rslv = jump ? 1'b1 : bjp_req_alu_cmp_res;
然后在ALU模块中使用异或操作进行比较,使用加法器进行大小的比较(下面是异或的代码):
//
// Impelment the XOR-er
//
// The XOR-er will be reused to handle the XOR and compare op
wire [`E203_XLEN-1:0] xorer_in1;
wire [`E203_XLEN-1:0] xorer_in2;
wire xorer_op =
op_xor
// The compare eq or ne instruction
| (op_cmp_eq | op_cmp_ne);
// Make sure to use logic-gating to gateoff the
assign xorer_in1 = {`E203_XLEN{xorer_op}} & misc_op1;
assign xorer_in2 = {`E203_XLEN{xorer_op}} & misc_op2;
wire [`E203_XLEN-1:0] xorer_res = xorer_in1 ^ xorer_in2;
// The OR and AND is too light-weight, so no need to gate off
wire [`E203_XLEN-1:0] orer_res = misc_op1 | misc_op2;
wire [`E203_XLEN-1:0] ander_res = misc_op1 & misc_op2;
4.2 交付模块的实现
ALU在计算出是否需要跳转的结果之后,发送给“交付”模块 。根据预测以及实际的跳转结果来判断是否需要进行流水线冲刷,交付模块的输入输出端口如下所示:
端口名称 | 方向 | 功能描述 |
---|---|---|
cmt_i_valid | 输入 | 提交信息有效性信号,用于指示指令是否有效 |
cmt_i_ready | 输出 | 提交信息接收准备信号,表示接收方准备接收指令 |
cmt_i_rv32 | 输入 | 用于指示是否是32位指令集 |
cmt_i_dret | 输入 | 调试返回指令信号,用于表示执行的是dret指令 |
cmt_i_mret | 输入 | 机器返回指令信号,用于表示执行的是mret指令 |
cmt_i_fencei | 输入 | FENCE.I指令信号,用于同步指令执行 |
cmt_i_bjp | 输入 | 分支预测指令信号,用于表示分支指令是否预测 |
cmt_i_bjp_prdt | 输入 | 分支预测的预测值,指示预测是true还是false |
cmt_i_bjp_rslv | 输入 | 分支预测的解决值,指示最终的分支预测结果 |
cmt_i_pc | 输入 | 提交指令时的程序计数器值,用于定位指令地址 |
cmt_i_imm | 输入 | 分支指令的立即数值,用于分支计算 |
csr_epc_r | 输入 | 当前异常程序计数器值,用于处理中断或异常 |
csr_dpc_r | 输入 | 当前调试程序计数器值,用于调试目的 |
nonalu_excpirq_flush_req_raw | 输入 | 原始的异常或中断清除请求信号 |
brchmis_flush_ack | 输入 | 分支错误清除确认信号,用于表示分支错误已清除 |
brchmis_flush_req | 输出 | 分支错误清除请求信号,用于触发分支错误清除 |
brchmis_flush_add_op1 | 输出 | 分支错误清除操作数1,用于计算清除地址操作数 |
brchmis_flush_add_op2 | 输出 | 分支错误清除操作数2,用于计算清除地址操作数 |
brchmis_flush_pc | 输出 | 分支错误清除程序计数器,用于指示分支错误地址 |
cmt_mret_ena | 输出 | 机器返回启用信号,用于触发机器返回指令执行 |
cmt_dret_ena | 输出 | 调试返回启用信号,用于触发调试返回指令执行 |
cmt_fencei_ena | 输出 | FENCE.I启用信号,用于同步指令的执行 |
下面表示的是是否需要流水线冲刷,这里比较预测值和实际值是否相等。
wire brchmis_need_flush = (
(cmt_i_bjp & (cmt_i_bjp_prdt ^ cmt_i_bjp_rslv))
...
这里是对冲刷结果的目标地址进行分析,如果是预测了需要跳转,但是实际结果显示不需要跳转,的新PC指向此跳转指令的下一个指令(通过位数分别设置为 PC+4 以及PC+2 );如果是预测了不需要跳转,但是实际结果显示需要跳转,则重新取指的新 PC 指向此跳转指令跳转目标地址。
assign brchmis_flush_pc =
(cmt_i_fencei | (cmt_i_bjp & cmt_i_bjp_prdt)) ? (cmt_i_pc + (cmt_i_rv32 ? `E203_PC_SIZE'd4 : `E203_PC_SIZE'd2)) :
(cmt_i_bjp & (~cmt_i_bjp_prdt)) ? (cmt_i_pc + cmt_i_imm[`E203_PC_SIZE-1:0]) :
cmt_i_dret ? csr_dpc_r :
csr_epc_r ;
五、多周期指令的交付
多周期指令会导致其写回要在多个周期后完成,但是交付依旧在执行阶段进行交付。
六、总结
这里我依旧放书中的原话: