五级流水线:不同指令怎样在同一拍推进

多周期处理器在一条指令完成以后才取下一条。若一条指令正在访问数据存储器,取指和运算部件可能空闲。流水线把不同指令分配到不同阶段,在同一拍分别完成工作;阶段之间的寄存器保存每条指令自己的数据与控制信息。

中心问题是:同时存在多条尚未完成的指令时,怎样既增加完成速率,又保持程序规定的结果?本篇先研究没有依赖和分支的短程序,再用累计求和程序检查结果。证据等级是“教学时序模型”;周期长度的例子另标为“手算”。数据冒险的具体修复在第 10 篇,错误路径恢复在第 11 篇。

阶段寄存器保存的是一条指令的执行上下文

五级分别是 IF 取指、ID 译码与读寄存器、EX 运算或算地址、MEM 数据访存、WB 写回。每拍结束,阶段寄存器把本拍产出的信息交给下一阶段。即使某条 ALU 指令不访问内存,它仍穿过本模型的 MEM 位置,避免和较早的 load 争抢写回位置。

CS61C 流水线总结强调,阶段寄存器有时需要携带下下级才使用的信息。例如目标寄存器 rd 在 WB 才真正参与写回;不能到了 WB 再从当前 IF 的指令字提取 rd,那时正在取的已经是另一条指令。

1
2
3
PC -> IF | IF/ID | ID | ID/EX | EX | EX/MEM | MEM | MEM/WB | WB
指令/PC 操作数、rd 结果/地址、rd 写回值、rd
控制位 store数据 写回使能

图中的 | 表示阶段边界,不表示零成本的分隔线。新增寄存器带来 clk-to-q 与 setup 要求,选择器和前递网络也会增加组合路径。流水线变深不能自动换来同比例的频率提升。

本模型的 Packet 给每个动态实例分配序号。循环再次执行相同 PC 时,序号仍递增。这样阶段表可以区分“同一条静态指令的下一次执行”和“某条指令因等待在同一阶段停留”。阶段有效位用有无 Packet 表示,空槽不会写寄存器或内存。

六条指令为何需要十拍

fill_drain.hex 包含六条彼此独立的 addi,分别把常量写入 x1 到 x6。没有分支、访存或 RAW 依赖。简化阶段表如下,完整输出见 fill-drain-stages.txt

周期 IF ID EX MEM WB
1 I0
2 I1 I0
3 I2 I1 I0
4 I3 I2 I1 I0
5 I4 I3 I2 I1 I0
6 I5 I4 I3 I2 I1
7 I5 I4 I3 I2
8 I5 I4 I3
9 I5 I4
10 I5

从第一条 IF 所在拍计到最后一条 WB 所在拍,N 条指令需要 N+4 拍。第一条到第五拍才完成,之后每拍完成一条。最后一条在第六拍取指,取指停止后还要四拍才能排空。

填充四拍与末尾排空四拍是对同一张重叠执行表的两种观察位置,不能把二者都加在 N 上而得到 N+8。流水线并没有额外执行八拍空操作。对于这个六条指令的有限区间,平均 CPI 为 10/6;N 增大以后才逐渐接近理想的 1。

吞吐量提高与单条延迟是两个问题

题设假定一条单周期指令耗时 800 ps,五级流水线的每拍为 200 ps,均不来自实际器件测量。单条流水线指令需要五拍,因此无等待时延迟是 1000 ps,比单周期更长。进入稳定状态后却能每 200 ps 完成一条,完成速率更高。

六条独立指令的总时间为单周期 6×800=4800 ps、流水线 10×200=2000 ps。把 N 改成 1,流水线是 1000 ps,单周期是 800 ps。相同设计在不同长度工作负载上的总体收益可以不同,不应拿稳定吞吐量代替单次响应延迟。

物理上,流水线周期通常由最慢阶段的组合延迟和阶段寄存器开销共同决定。若五段延迟严重不均衡,只把它们画成五个等宽方框并不能使时钟恰好缩短为五分之一。本实验没有综合或时序分析工具,200 ps 只用于解释量纲。

同一份机器码仍须提交相同结果

累计模型位于 examples/computer-architecture/pipeline/。Python 的五级执行器从 .hex 取真实指令字,独立解码、计算与访存。C 参考解释器从同一份文件顺序执行。验证程序只在两者结束后比较提交事件,不把 C 轨迹当作流水线执行输入。

下载 pipeline-lab.zip,从解压目录执行:

1
2
3
4
python3 examples/computer-architecture/pipeline/src/verify.py
python3 examples/computer-architecture/pipeline/src/run.py \
examples/computer-architecture/pipeline/programs/fill_drain.hex \
--cycles /tmp/fill-drain.jsonl

完整核查记录为 verification.json,其中包含输入和 C 源码 SHA-256。当前 41 个程序、七组五级配置共 287 次差分相等,覆盖正常完成与执行错误,也包括 20 个固定随机种子。这个数量表达所运行的案例范围,不表达 ISA 已被穷举验证。

相等的对象包含每次提交的原始 PC、指令字、操作、目标寄存器及值或内存地址及值、分支结果与下一 PC,最后还有 halt 状态。只比较最终 x3=25 会漏掉很多错误:中间某次错误写入可能被后来的正确写入覆盖;错误路径也可能写了最终未观察的地址。逐事件差分更容易定位首次分歧。

求和程序实际执行 29 条指令。在默认前递、静态不跳转、数据存储器一拍的配置下,它需要 46 拍:理想基线 29+4=33,五次 load-use 各等待一拍,四次误预测各损失两个取指机会,得到 33+5+8=46。求和的完整阶段记录见 sum-stages.txt。这些罚时属于该模型,不能直接套给其他处理器。

计数边界也是实验的一部分

模型把 store 的实际内存写入放在 MEM,把它的提交记录放在 WB,因此阶段表仍以最后 WB 为结束。若另一个实现把 store 在 MEM 的完成直接当作测试终点,末尾只有 store 的短程序就可能少记一拍。比较之前必须固定开始、结束和何时计入提交。

本模型同拍的顺序是先让 WB 写寄存器,再让 ID 读取;前递会在 EX 重新选择较新的值。MEM 长延迟会冻结上游,已到 WB 的较老指令仍可完成。所有结果只适用于这里声明的顺序、端口与时序规则。下一篇将用反例说明为什么“每条指令单独执行正确”仍不足以保证重叠执行正确。

练习与验收

把独立 addi 数量改为十条,先手算总拍数再运行。答案是十四拍,平均 CPI 为 1.4;稳态每拍一条与有限区间平均 CPI 大于 1 可以同时成立。

若把第三阶段的组合延迟从 150 ps 增加到 260 ps,而其他阶段都不超过 200 ps,寄存器与裕量另计 30 ps,应如何估算新周期?答案是至少 290 ps,而不是沿用 200 ps。若指令数和罚时不变,总周期数相同也会变慢。

验收时应能把任一动态指令沿五级表追踪到底,解释 rd、store 数据和有效位为何跨拍保存,复现六条十拍,并说明求和为何没有达到 N+4。这个实验验证的是功能一致性与教学调度规则;没有证明实际电路可在某个 GHz 工作。

阅读衔接

前一篇:计算机体系结构 08:多周期执行

参考资料

  • CS61C Pipeline Summary:阶段寄存器、吞吐与单条延迟的区分。
  • CS61C Data Hazards:本模型写后读与前递时序的教学依据。
  • pipeline/src/core.pyfill_drain.forward.stages.mdsum.forward.stages.md:本篇具体执行与计数证据,已包含在附件中。