Archipelago

Lec 5指令流水线——冒险消解与时序约束(Instruction Pipelining: Hazard Resolution, Timing Constraints)

讲义完成更新于 2026-09-29

Lec 5 指令流水线——冒险消解与时序约束(Instruction Pipelining: Hazard Resolution, Timing Constraints)

TL;DR

  • 流水线吞吐与三大冒险:流水线通过时钟频率切分提升吞吐,但结构冒险、RAW 数据依赖与控制流转移共同推高 CPI。
  • 数据冒险消解范式:通过硬件检测比较器触发流水线停顿(Stall),引入前递旁路(Forwarding)消解 ALU-ALU 气泡,仅留 Load-Use 延迟单周期停顿。
  • 分支与精确异常仲裁:遵循“老指令优先”原则,执行级分支误预测与后级异常处理拥有最高仲裁权,原子刷动作废年轻指令并重定向取指。

MIT 6.5900 Fall 2024 · Daniel Sanchez 主题:理想流水线与 CPI、三类冒险、数据冒险消解(停顿/旁路/推测)、控制冒险与跳转/分支、异常处理


一、理想流水线与吞吐

五级流水线 RISC-V:F(取指)/ D(译码)/ E(执行)/ M(访存)/ W(写回)。

稳态下:CPI = 1,IPC = 1,每条指令 5 周期延迟,在飞指令数 N=5N=5。

T=NLT = \frac{N}{L}

流水线提高时钟频率,但指令依赖可能增大 CPI。


二、三类冒险(Hazards)

流水线中指令可能这样相互作用:

  • 结构冒险(Structural Hazard):某指令需要的资源正被流水线中另一指令占用;
  • 数据冒险(Data Hazard):某指令依赖更早指令产生的值;
  • 控制冒险(Control Hazard):依赖关系在于计算下一个 PC(分支、中断)。

三、数据冒险及其消解

例:a1 ← a0 + 10; a3 ← a1 + 12 —— 第二条在第一条写回前读 a1,读到陈旧值。

三种策略:

  • 策略 1 停顿(Stall):冻结较早的流水级等待结果可用;
  • 策略 2 旁路(Bypass / Forwarding):结果一算出就尽快送到较早的流水级;
  • 策略 3 推测(Speculate):对依赖关系猜测——猜对无需特殊动作,猜错则 kill 并重启。

策略 1:停顿

当译码(D)级指令读取的寄存器是任一在飞指令(在 E/M/W)要写的目的寄存器时,停顿 D 与 F 级。

停顿控制逻辑:把译码级指令的源寄存器与未提交指令的目的寄存器比较。但并非每条指令都写/读寄存器,故需 **we(写使能)**与 re(读使能)。

源/目的寄存器表:

类型源目的
ALUrs1, rs2rd
ALUi / LWrs1rd
SWrs1, rs2—
BRANCHrs1, rs2—
JAL—rd
JALRrs1rd

停顿信号(部分):

stall=[(rs1D=rdE)weE+(rs1D=rdM)weM+(rs1D=rdW)weW]re1D+(rs2 同理)\text{stall} = \big[(rs1_D{=}rd_E)we_E + (rs1_D{=}rd_M)we_M + (rs1_D{=}rd_W)we_W\big]re1_D + (\text{rs2 同理})

Load/Store 冒险

M[(a1)+7] ← (a2); a4 ← M[(a3)+5] —— 若 (a1)+7=(a3)+5(a1)+7 = (a3)+5 则有数据冒险,但因存储系统一周期完成写而避免。Load/Store 冒险有时在流水线、有时在存储系统本身解决。

策略 2:旁路

每次停顿引入一个气泡 ⇒ CPI > 1。数据其实在 Execute 之后就可用,可加一条旁路通路把 ALU 输出送回其输入。

旁路信号:ASrc=(rs1D=rdE)⋅weE⋅re1D\text{ASrc} = (rs1_D{=}rd_E)\cdot we_E \cdot re1_D。但只有 ALU/ALUi 能从此旁路受益,Load 与 JAL 不能 → 把 weEwe_E 拆成 we-bypass 与 we-stall 两部分。

全旁路数据通路后,是否还需停顿?仍需——因为 Load 有两周期延迟:

stall=(rs1D=rdE)(opcodeE=LW)(rdE≠0)re1D+(rs2 同理)\text{stall} = (rs1_D{=}rd_E)(opcode_E{=}LW)(rd_E{\neq}0)re1_D + (\text{rs2 同理})


四、控制冒险

下一个 PC 需要什么

指令需要何时知道
JALopcode、offset、PC—
JALRopcode、offset、寄存器值—
条件分支(BEQ)opcode、offset、PC、寄存器(判条件)—
其他(算术)opcode、PC—

各项已知的阶段:PC → 取指;opcode、offset → 译码;寄存器值 → 译码;分支条件 ((rs1)==(rs2)) → 执行(或译码)。

推测下一个 PC 为 PC+4

一个好的猜测是 PC+4。猜错(下一条不是 PC+4)时需 kill。

流水化跳转:要 kill 一条已取指令,就在 IR 中插入 nop:

IRSrcD={nopopcodeD∈JAL,JALR IM其他\text{IRSrcD} = \begin{cases} \text{nop} & opcode_D \in {JAL, JALR} \ \text{IM} & \text{其他} \end{cases}

JAL/JALR 跳转后需 kill 紧随的一条指令(一个气泡)。

流水化条件分支与多级控制方程

在五级流水线中,条件分支的比较结果与目标地址计算默认直到**执行级(E)**结束时才能完全决出。而在执行级解析完成之前,取指级(F)和译码级(D)已经按照顺序预测 PC+4 分别取入了后续的两条指令(如 I3I_3 与 I4I_4)。

如果分支最终判定为 Taken(跳转成立),那么这两个在飞的推测执行指令就是错误的,硬件必须在下一个时钟上升沿将它们强制消解(Kill):

  1. 将译码级寄存器(IR_D)与执行级寄存器(IR_E)的内容清零或替换为 NOP(空操作);
  2. 此时,即使译码级检测到了与更早指令的数据冒险依赖,其发出的 stall 信号也必须被抑制,因为这批指令本身就已被废弃,决不能因为假停顿而冻结流水线;
  3. 将 PC 重定向到分支目标地址(PCbranch=PCE+immEPC_{\text{branch}} = PC_E + \text{imm}_E)。

控制通路的硬件多路选择逻辑严格遵循**“老指令优先”原则**(执行级老指令的控制决策无条件覆盖译码级新指令):

IRSrcD = case opcodeE: Taken branch → nop
                       else → (case opcodeD: JAL/JALR → nop; else → IM)
IRSrcE = case opcodeE: Taken branch → nop
                       else → stall·nop + !stall·IR_D
PCSrc  = case opcodeE: Taken branch → pc+imm (来自 E)
                       else → (case opcodeD: JAL → pc+imm(D); JALR → rs1+imm(D); else → pc+4)

上述控制方程中:

  • nop 代表将控制信号置零,实现无副作用的指令作废(Kill);
  • pc+imm 与 rs1+imm 代表跳转重定向(Restart);
  • pc+4 代表最基础的静态顺序推测(Speculate)。

减少分支代价

  • 在译码级解析:在译码级加一个额外比较器,可去掉一个气泡(流水线图与跳转相同);
  • 分支延迟槽(Branch Delay Slot):改变 ISA 语义,使跳转/分支后那条指令总被执行,给编译器机会填入有用指令(否则那里会是气泡);
  • 其他技术:分支预测(后续讲,可大幅降低分支代价)。

五、异常引起的控制冒险

  • 指令可能在不同流水级遭遇异常,须优先处理较早指令的异常;
  • 典型策略:记录异常,在到达提交点(修改体系结构状态之处)时处理第一个异常——在提交点更新 Cause/EPC、kill 各级、从处理程序 PC 取指;异步中断也在提交点注入。

六、为何 CPI > 1

  • 全旁路可能太贵:通常只提供常用通路,某些少用旁路会增加周期时间、抵消降 CPI 的好处;
  • Load 有两周期延迟:load 后一条不能用其结果;MIPS-I 定义了软件可见的 load 延迟槽(编译器调度独立指令或插 NOP),MIPS-II 移除;
  • 条件分支/跳转/异常引入气泡:kill 分支后的指令(无延迟槽时,如 MIPS)。

有软件可见延迟槽的机器可能执行大量编译器插入的 NOP。


流水线冒险消解与时序约束总结

  • 流水线靠提高时钟频率提速,但三类冒险(结构/数据/控制)会增大 CPI;
  • 数据冒险用停顿、旁路、推测消解:全旁路后只剩 Load-use 的一周期停顿;
  • 控制冒险用推测 PC+4 + kill 处理,分支可移到译码级解析或用延迟槽/分支预测降代价;
  • 异常作为控制冒险,用提交点统一处理 + 优先较早指令保证精确性。

下一讲:超标量与记分牌流水线

我的理解

五级经典流水线的设计精髓在于“优先级驱动的控制通路仲裁”。控制逻辑必须严格遵循“老指令优先(Older Instruction Priority)”原则:当执行级(E)分支误预测触发时,它必须原子覆盖译码级(D)产生的任何停顿或自旋请求,并强制作废年轻指令。任何硬件旁路网络与推测机制的引入,都在实质上以组合逻辑延迟(tcombot_{\text{combo}})和连线拥塞为代价去缩减 CPI;当临界路径受制于旁路 MUX 扇入时,盲目增加旁路反而会拖慢整机工作频率。