流水线五阶段

所有的RISC架构都有以下几个关键的特性:

  • 所有数据操作都是对寄存器中的数据操作,通常会改变整个寄存器(每个寄存器为32位或64位)
  • 只有载入和存储操作会影响存储器,它们将数据从存储器移到寄存器或从寄存器移到存储器。载入和存储小于一个完整的寄存器的操作通常是有效的
  • 指令的格式相对固定,所有指令位宽通常相同。在RISC-V中,寄存器说明符rs1、rs2和rd总是固定在同一个位置编码从而简化了控制

RISC子集中的每条指令都可以在最多5个时钟周期内实现。这五个时钟周期如下(非流水化):

取指周期(IF)

将程序计数器发送到存储器,从存储器提取当前指令。程序计数器+4,将程序计数器更新到下一条顺序指令。其中NPC表示小一条只指令的地址。

1
2
IR := Mem[PC]
NPC := PC+4

指令译码/读寄存器周期(ID)

对指令进行译码,读取寄存器。其中rs1、rs2表示源寄存器,A、B表示两个临时寄存器。同时对指令中的偏移量进行符号扩展,将扩展后的值与PC相加,计算出可能的目标分支。指令译码与寄存器的读取是并行执行的,得益于固定字段译码技术。

1
2
3
A := Regs[rs1]
B := Regs[rs2]
Imm := sign-extended immediate field of IR

执行/有效地址周期(EX)

ALU对上一周期准备的操作数进行操作,根据指令类型执行三条指令之一。

  • 存储器引用:ALU将基址存储器和偏移量加到一起,形成有效地址。
1
ALUOutput := A + Imm
  • 寄存器-寄存器ALU指令:ALU对读自寄存器堆的值执行功能代码指定的操作。
1
ALUOutput := A func B
  • 寄存器-立即数ALU指令:ALU对读自寄存器堆的第一个值和符号扩展立即数执行由ALU操作码指定的操作。
1
ALUOuput :=  A op Imm
  • 分支指令:ALU将NPC加到Imm的符号扩展立即数,将该立即数左移两位,得到一个字偏移量,以计算分支的地址。这里仅考虑分支的一种形式(相等则跳转)。
1
2
ALUOutput := NPC + (Imm << 2)
Cond := (A==B)

在载入-存储操作体系结构中,有效地址与执行周期可以合并到一个时钟周期中,这是因为没有指令需要计算数据地址并对数据执行操作。

存储器访存周期(MEM)

  • 载入指令:使用上一周期计算的有效地址从存储器中读取数据。LMD是存储内存数据的临时寄存器。
1
LMD := MEM[ALUOutput]
  • 存储指令:使用上一周期计算的有效地址从寄存器堆的第二个寄存器(rs2)读取的数据写入数据
1
MEM[ALUOutput] := B
  • 分支指令:在分支条件成立时用计算出的分支地址替代PC,作为下一条指令的地址。
1
if (cond) PC := ALUOutput

写回周期(WB)

  • 寄存器-寄存器指令和寄存器-立即数指令:将ALU的输出值写回目的寄存器
1
Reg[rd] := ALUOutput
  • 加载指令:将LMD中的存储值写回到目的寄存器
1
Reg[rd] := LMD

流水线冒险

结构冒险

在重叠执行模式下,如果硬件无法同时支持指令的所有可能的组合方式,就会出现资源冲突,从而导致结构冒险。结构冒险最常见于某功能单元未能完全流水化的情况,例如仅有一个存储器端口的处理器会在不同指令的IF周期和MEM周期发生冲突。

结构冒险的解决方法是停顿(stall、流水线气泡)。例如当IF周期与MEM周期发生冲突时,IF阶段需要停顿。

停顿会导致流水线性能下降,低于理想性能。

RISC-V采用了一些策略避免了结构冒险。

数据冒险

根据流水线中的指令重叠,指令之间存在先后顺序,如果一条指令取决于先前指令的结果,就会出现数据冒险。数据冒险主要有三种:写后读(RAW)读后写(WAR)写后写(WAW)

例如,对于下面指令

1
2
3
4
5
add x1,x2,x3
sub x4,x1,x5
and x6,x1,x7
or x8,x1,x9
xor x10,x1,x11

add后的四条指令都依赖add指令的计算结果,只有当第一条指令WB阶段完成后,后续指令才能在各自的ID阶段读取到正确的值。因此,sub指令至少需要停顿2个周期,and指令至少需要停顿1个周期,or指令无需停顿(当WB写回发生在ID的前半部分,读取寄存器发生在ID的后半部分)。

为解决数据冒险,需要使用转发(forwarding)技术。该技术是将每一阶段的输出直接发送到前阶段的输入,后续指令无需等到WB阶段结束后才能得到正确值。例如在上述指令中

  • sub指令的x1作为ALU的一个输入,可以直接从add指令在EX阶段的ALU输出得到,即“ALU输出-ALU输入”转发。
  • and指令的x1作为ALU的一个输入,可以直接从add指令在MEM阶段的DM输出得到,即“DM输出-ALU输入”转发。

转发操作不仅限于ALU,可以推广到将结果直接传送给需要它的功能单元。例如:

1
2
ld x4,0(x1)
sd x4,12(x1)

sd指令需要ld指令的结果,因此可以将ld指令MEM阶段的DM输出作为sd指令MEM阶段的DM输入。

并非所有潜在的数据冒险都可以通过转发解决。有时不得不产生停顿,比如:

1
2
3
4
ld x1,0(x2)
sub x4,x1,x5
and x6,x1,x7
or x8,x1,x9

ld指令后的三条指令都依赖于其运算结果,运算结果x1ld流水线的MEM阶段的DM输出。而对于sub指令,在其流水线的EX阶段就需要x1作为ALU输入,此时ld还未完成MEM周期,必须停顿。andor则分别需要承受来自于上一个阶段地停顿。

判断是否可以利用转发技术消除停顿的关键是:当存在数据依赖时,比较结果的产生时间(阶段)与结果的需求时间(阶段),仅当需求在结果产生后转发技术才能有效地消除停顿。

控制冒险

分支指令及其它改变程序计数器的指令实现流水化时可能导致控制冒险。分支在五级流水线中通常会导致一个周期的停顿。

降低流水线分支代价通常有以下几种机制:

  1. 冻结(stall)和冲刷(flush)流水线:当遇到分支等情况时,停止将新指令传入流水线,暂停当前正在执行的指令,并保存流水线状态(冻结)。当确定分支后,如果流水线中的指令不正确,则将未处理完的指令全部丢弃(冲刷)。
  2. 预测未命中:即将每个分支都看作未选中分支,继续提取分支指令后的指令,当分支被选中时,再将已提取的指令转为空操作,重新开始在目标地址提取指令。
  3. 预测命中:即将每个分支都看作选中分支,遇到分支指令后就提取目标地址的指令。这种机制在RISC-V中是可行的,因为分支的目标地址可以在ID阶段计算出来。
  4. 分支延迟。RISC-V不采用该机制,略。

控制冒险会导致停顿,因此会降低流水线密度。

上述预测未命中与预测命中的机制是固定的,即每遇到分支指令要么全部预测命中,要么全部预测不命中。有一种更加积极的方式来预测分支,分为以下两类:

  • 静态分支预测:依赖编译时可用信息来预测分支
  • 动态分支预测:依赖运行时的信息来预测分支

动态分支预测使用分支预测缓冲区,使用1个bit记录某一个分支是否被选中。分支预测缓冲区记录当前分支的预测信息(分支选中/分支未选中)。如果预测错误,则将该bit翻转。

简单的1位预测机制在性能上存在短板:

分支 未选中 未选中 未选中 未选中 选中 未选中 未选中
分支预测缓冲区 0 0 0 0 0 1 0
预测正确

当分支总是未选中时,在其被选中时将会出现两次预测错误。因此,经常使用2bit预测机制。

当然并不是位数越高越好,当分支情况如下时,预测位数越高,预测错误越多。

分支 未选中 未选中 未选中 选中 选中 选中 选中

实现流水化

五级流水线要求每一个时钟周期都是活动状态,数据路径的流水化要求必须将流水线之间的数值放在寄存器中,在每个流水级之间的寄存器称为流水线寄存器或流水线锁存器。这些流水线寄存器用于从一个流水级向下一个流水级传送数据和控制,直到在某一级流水线中使用后不在需要这些值为止。

与非流水化的五阶段相比,流水化的五级流水线有三点改进。

处理停顿

在数据冒险中可以看到只有载入指令产生的RAW冒险有将导致停顿,其余的冒险都可以用转发解决。如果存在一个载入指令导致的RAW冲突,当需要该载入数据的指令位于ID级,该载入数据位于EX级,下表描述了所有的情景。

ID/EX的操作码字段 IF/ID的操作码字段 匹配操作数字段
载入 寄存器-寄存器ALU ID/EX.IR[rt] = IF/ID.IR[rs1]
载入 寄存器-寄存器ALU ID/EX.IR[rt] = IF/ID.IR[rs2]
载入 载入、存储、ALU立即数或分支 ID/EX.IR[rt] = IF/ID.IR[rs1]

IF/ID寄存器保存着ID中指令的状态,它可能用到载入结果,ID/EX寄存器中保存着EX中指令的状态,它是载入指令。一旦检测到冒险,控制单元插入流水线停顿(将ID/EX流水线寄存器中的控制部分改为全0),并防止IF和ID级中的指令继续前进(停止发射)。

实现转发逻辑

所有的转发在逻辑上都是从ALU或数据存储器的输出到ALU的输入、数据存储器的输入或零检测单元,因此我们可以对比EX/MEM和MEM/WB级中所包含的IR的目标寄存器和ID/EX和EX/MEM级中所包含的IR的源寄存器,以此来实现转发。

在ALU的输入端需要添加多路选择器,实现输入来自寄存器堆或者转发结果的选择。

处理分支

通常采用分支预测的方法来解决控制冒险,为了减小预测错误的代价,分支的目标地址应当尽可能先计算出来,因此可以在ID级增加一个加法器,用于计算目标分支。此时分支的目标地址将在ID计算出,而是否命中分支的条件在EX计算出。

参考