Lec 5指令流水线——冒险消解与时序约束(Instruction Pipelining: Hazard Resolution, Timing Constraints)
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 周期延迟,在飞指令数 。
流水线提高时钟频率,但指令依赖可能增大 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(读使能)。
源/目的寄存器表:
| 类型 | 源 | 目的 |
|---|---|---|
| ALU | rs1, rs2 | rd |
| ALUi / LW | rs1 | rd |
| SW | rs1, rs2 | — |
| BRANCH | rs1, rs2 | — |
| JAL | — | rd |
| JALR | rs1 | rd |
停顿信号(部分):
Load/Store 冒险
M[(a1)+7] ← (a2); a4 ← M[(a3)+5] —— 若 则有数据冒险,但因存储系统一周期完成写而避免。Load/Store 冒险有时在流水线、有时在存储系统本身解决。
策略 2:旁路
每次停顿引入一个气泡 ⇒ CPI > 1。数据其实在 Execute 之后就可用,可加一条旁路通路把 ALU 输出送回其输入。
旁路信号:。但只有 ALU/ALUi 能从此旁路受益,Load 与 JAL 不能 → 把 拆成 we-bypass 与 we-stall 两部分。
全旁路数据通路后,是否还需停顿?仍需——因为 Load 有两周期延迟:
四、控制冒险
下一个 PC 需要什么
| 指令 | 需要 | 何时知道 |
|---|---|---|
| JAL | opcode、offset、PC | — |
| JALR | opcode、offset、寄存器值 | — |
| 条件分支(BEQ) | opcode、offset、PC、寄存器(判条件) | — |
| 其他(算术) | opcode、PC | — |
各项已知的阶段:PC → 取指;opcode、offset → 译码;寄存器值 → 译码;分支条件 ((rs1)==(rs2)) → 执行(或译码)。
推测下一个 PC 为 PC+4
一个好的猜测是 PC+4。猜错(下一条不是 PC+4)时需 kill。
流水化跳转:要 kill 一条已取指令,就在 IR 中插入 nop:
JAL/JALR 跳转后需 kill 紧随的一条指令(一个气泡)。
流水化条件分支与多级控制方程
在五级流水线中,条件分支的比较结果与目标地址计算默认直到**执行级(E)**结束时才能完全决出。而在执行级解析完成之前,取指级(F)和译码级(D)已经按照顺序预测 PC+4 分别取入了后续的两条指令(如 与 )。
如果分支最终判定为 Taken(跳转成立),那么这两个在飞的推测执行指令就是错误的,硬件必须在下一个时钟上升沿将它们强制消解(Kill):
- 将译码级寄存器(
IR_D)与执行级寄存器(IR_E)的内容清零或替换为NOP(空操作); - 此时,即使译码级检测到了与更早指令的数据冒险依赖,其发出的
stall信号也必须被抑制,因为这批指令本身就已被废弃,决不能因为假停顿而冻结流水线; - 将 PC 重定向到分支目标地址()。
控制通路的硬件多路选择逻辑严格遵循**“老指令优先”原则**(执行级老指令的控制决策无条件覆盖译码级新指令):
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)产生的任何停顿或自旋请求,并强制作废年轻指令。任何硬件旁路网络与推测机制的引入,都在实质上以组合逻辑延迟()和连线拥塞为代价去缩减 CPI;当临界路径受制于旁路 MUX 扇入时,盲目增加旁路反而会拖慢整机工作频率。