以下内容摘自《步步惊芯——软核处理器内部设计分析》一书
上文中提到过流水线相关的问题,本节将对流水线相关的问题进行说明。流水线中经常有一些被称为“相关”的情况发生,它使得指令序列中下一条指令无法按照设计的时钟周期执行,这些“相关”会降低流水线的性能。流水线中的相关分为三种类型:
(1)结构相关:指令在执行的过程中,由于硬件资源满足不了指令执行的要求,发生硬件资源冲突而产生的相关。比如:指令和数据都共享一个存储器,在某个时钟周期内.流水线既要完成某条指令对存储器中数据的访问操作,又要完成后续指令的取指令操作,这样就会发生存储器访问冲突,产生结构相关。
(2)数据相关:指在流水线中执行的几条指令中,一条指令依赖于前面指令的执行结果。
(3)控制相关:指流水线中的分支指令或者其他需要改写PC的指令造成的相关。
结构相关、控制相关将在后续指令分析中讨论。本小节重点讨论流水线数据相关的有关情况,同时介绍OR1200中对数据相关的处理方法。流水线数据相关又分为三种情况:RAW、WAR、WAW。
- RAW:Read AfterWrite,假设指令j是指令i后面执行的指令,RAW表示指令i将数据写入寄存器后,指令j才能从这个寄存器读取数据。如果指令j在指令i写入寄存器前尝试读出该寄存器的内容,将得到不正确的数据。
- WAR:Write AfterRead,假设指令j是指令i后面执行的指令,WAR表示指令i读出数据后,指令j才能写这个寄存器。
- WAW:Write AfterWrite,假设指令j是指令i后面执行的指令,WAW表示指令i将数据写入寄存器后,指令j才能将数据写入这个寄存器。
……
l.add r4,r2,r3
l.sfeqi r4,0x1234
……
在指令l.add中会写r4,随后的指令l.sfeqi会读出r4的数据进行比较运算,通过前面的分析知道l.add在执行阶段的时序逻辑中写r4,此时l.sfeqi处于译码阶段的时序逻辑,而在l.sfeqi的译码阶段的组合逻辑中已经得到了r4的值,如果得到的是修改前的值,那么l.sfeqi使用这个r4的值进行比较运算就会出现错误。如图4.5所示。使用ModelSim仿真可以更加清晰地认识到这个问题,如图4.6所示。
图4.5 流水线数据相关的示例
图4.6 ModelSim仿真波形呈现的数据相关问题
解决这种问题的方法有三种:
(1)插入暂停周期:当检测到相关时,在流水线中插入一些暂停周期,如图4.7所示。
图4.7 在流水线中插入暂停周期消除数据相关
(2)编译器调度:编译器检测到相关后,可以改变部分指令的执行顺序,如图4.8所示。此时的流水线情况如图4.9所示。
4.8 编译器通过改变指令执行顺序消除相关
4.9 编译器调度改变指令的执行顺序
(3)数据前推:将计算结果从其产生处直接送到其他指令需要处或所有有需要的功能单元处,避免流水线暂停。在OR1200中l.add在执行阶段的组合逻辑过程中会得到要写入r4的新值,可以直接将该值送入下一条指令l.sfeqi的译码阶段,从而使得l.sfeqi在译码阶段得到r4的新值,如图4.10所示。
图4.10 数据前推解决流水线相关
OR1200就是采用数据前推的方法来解决流水线数据相关的问题。通过补充完善图4.3,添加部分信号使得可以完成数据前推的工作,如图4.11所示。主要是将WB_MUX的输出mux_out直接送入OPERAND_MUX模块的输入ex_forw,在OPERAND_MUX模块中参与ALU操作数的选择过程。
图4.11 mux_out输出到OPERAND_MUX的ex_forw实现数据前推
假设指令j是指令i后面执行的指令,在指令i执行阶段的组合逻辑中,WB_MUX将要写入目的寄存器的值通过mux_out输出到RF的dataw,同时也输出到OPERAND_MUX的ex_forw。
指令i处于执行阶段的组合逻辑输出的时候,指令j正处于译码阶段的组合逻辑输出,此时在CTRL模块中会判断指令j与其上一条指令i是否存在数据相关。代码如下:
or1200_ctrl.v
//此时的rf_addrw是指令i要写的目的寄存器序号,rfwb_op[0]为1,
//表示要写目的寄存器,反之不写目的寄存器,id_insn[20:16]是
//指令j需要的源寄存器rA的序号,所以此处就是判断rA是否是指
//令i要写入的寄存器
always @(rf_addrw or id_insn or rfwb_op or wbforw_valid or wb_rfaddrw)
if ((id_insn[20:16] == rf_addrw) && rfwb_op[0])
sel_a = `OR1200_SEL_EX_FORW;
else if ((id_insn[20:16] == wb_rfaddrw) && wbforw_valid)
sel_a = `OR1200_SEL_WB_FORW;
else
sel_a = `OR1200_SEL_RF;
//此时的rf_addrw是指令i要写的目的寄存器序号,rfwb_op[0]为1,
//表示要写目的寄存器,反之不写目的寄存器,id_insn[15:11]是
//指令j需要的源寄存器rB的序号,所以此处就是判断rB是否是指
//令i要写入的寄存器
always @(rf_addrw or sel_imm or id_insn or rfwb_op or wbforw_valid or wb_rfaddrw)
if (sel_imm)
sel_b = `OR1200_SEL_IMM;
else if ((id_insn[15:11] == rf_addrw) && rfwb_op[0])
sel_b = `OR1200_SEL_EX_FORW;
else if ((id_insn[15:11] == wb_rfaddrw) && wbforw_valid)
sel_b = `OR1200_SEL_WB_FORW;
else
sel_b = `OR1200_SEL_RF;
如果指令i要写目的寄存器,且指令i的目的寄存器就是指令j的源寄存器rA或者rB,则会使得sel_a或者sel_b为OR1200_SEL_EX_FORW,参考图4.11可知sel_a、sel_b都输出到OPERAND_MUX模块,后者会依据sel_a、sel_b的值选择ALU的操作数,代码如下:
or1200_operandmuxes.v
always @(ex_forw or wb_forw or rf_dataa or sel_a) begin
casez (sel_a) // synopsys parallel_case
//如果指令j需要读取的寄存器rA正是指令i要写的目的寄存器,
//那么就直接把要写入的值赋给muxed_a
`OR1200_SEL_EX_FORW:
muxed_a = ex_forw;
`OR1200_SEL_WB_FORW:
muxed_a = wb_forw;
default:
muxed_a = rf_dataa; //rf_dataa是从RF的rf_a中读出的寄存器值
endcase
end
//
// Forwarding logic for operand B register
//
always @(simm or ex_forw or wb_forw or rf_datab or sel_b) begin
casez (sel_b) // synopsys parallel_case
`OR1200_SEL_IMM:
muxed_b = simm;
//如果指令j需要读取的寄存器rB正是指令i要写的目的寄存器,
//那么就直接把要写入的值赋给muxed_b
`OR1200_SEL_EX_FORW:
muxed_b = ex_forw;
`OR1200_SEL_WB_FORW:
muxed_b = wb_forw;
default:
muxed_b = rf_datab; //rf_datab是从RF的rf_b中读出的寄存器值
endcase
end
而此时OPERAND_MUX模块的输入ex_forw正是WB_MUX的输出muxout,也就是指令i要写的目的寄存器的新值。从而实现了数据前推,解决了流水线数据相关问题。图4.12显示的是ModelSim仿真呈现的OR1200处理器数据相关的解决步骤。