多周期处理器

多周期处理器设计与指令级并行中的相关性分析

Posted by CloudingYu on April 8, 2026

一、多周期处理器为什么值得讲

1.1 多周期是单周期和流水线之间的过渡形态

多周期处理器(Multi-cycle Processor)本身今天很少单独成为主流产品形态,但它在历史上非常重要,因为它是从单周期处理器(Single-cycle Processor)走向流水线处理器(Pipeline Processor)的中间站。

讲多周期的目的不是专门去设计一个多周期 CPU,而是理解:

  1. 单周期为什么浪费资源;
  2. 多周期怎样复用部件;
  3. 为什么流水线最终胜出。

1.2 评价 CPU 不能只看主频

评估 CPU 不能只看时钟周期变短了没有,还要看 CPI。总执行时间满足:

\[T = IC \times CPI \times t_{\text{cycle}}\]

其中:

  • $IC$:指令条数;
  • $CPI$:每条指令平均需要的周期数;
  • $t_{\text{cycle}}$:时钟周期长度。

例如:

  • 单周期:$t_{\text{cycle}} \approx 925\text{ps}$,但 $CPI=1$;
  • 多周期:$t_{\text{cycle}} \approx 325\text{ps}$,但 $CPI\approx 4.2$。

虽然时钟周期缩短了,但整体执行时间不一定更快,甚至可能更慢。别人告诉你”频率更高了”,不代表程序一定跑得更快。真正要看的是完整的执行时间模型。

1.3 为什么多周期的 CPI 不是 5

把一条指令粗略拆成取指、译码、执行、访存、写回五件事,但不是所有指令都真的走满五步。例如:

  • lw 往往需要完整五步;
  • R 型算术指令通常不需要访存,步数更少;
  • 分支指令可能更短。

因此平均 CPI 会低于 5,而不是机械地等于五个阶段数。


二、多周期数据通路的核心思想:部件复用

2.1 单周期的问题

单周期设计要求:所有指令在一个长周期里全部做完。这样会带来两个问题:

  1. 周期必须由最慢指令决定,导致很多简单指令被迫陪跑。
  2. 某些功能部件为了满足同周期内的并行需求,不得不重复配置,硬件代价高。

2.2 多周期的思路

多周期的根本改进是:把一条指令拆成多个阶段,不同阶段在不同周期里做,允许同一套硬件反复复用

例如:

  • 第一拍取指;
  • 第二拍译码并读寄存器;
  • 第三拍根据指令类型做 ALU 计算;
  • 若需要访存,再用下一拍访问 Memory;
  • 最后再写回寄存器。

这样一来:

  • 周期可以按单个子操作来定,不需要被整条最慢路径拖住;
  • ALU、Memory、MUX 等硬件都可以分时复用。

2.3 为什么要加锁存器

一旦指令被拆到多个周期执行,就必须把当前周期算出的中间结果暂时保存下来,供下一个周期继续使用。因此需要在关键位置增加一些小寄存器/锁存器,例如:

  • IR:锁住当前指令;
  • MDR:锁住从存储器读出的数据;
  • ALUOut:锁住 ALU 结果;
  • A/B:锁住寄存器堆输出的操作数。

这一步非常关键,因为它实际上已经在朝流水线方向走了。多周期其实就是流水线的前身——已经开始在通路里”先锁住结果,再往下传”了。


三、多周期控制:有限状态机 FSM

3.1 为什么要用状态机

单周期控制的特点是:一条指令一进来,所有控制信号一次性准备好。而多周期不是这样,它要求:

  • 到哪个阶段,就只发那个阶段需要的控制信号;
  • 还没轮到的部件保持不动;
  • 每个周期由当前状态决定下一步该做什么。

因此,多周期控制天然适合用有限状态机(Finite State Machine, FSM)来实现。

3.2 典型状态划分

多周期 CPU 的执行过程可概括为若干状态:

  1. S0:取指(Fetch)
  2. S1:译码/读寄存器(Decode)
  3. S2:执行/地址计算(Execute / Address Calculation)
  4. S3:访存(Memory Access)
  5. S4:写回(Write Back)

但不同指令会走不同的分支:

  • lw 会一路走到访存和写回;
  • sw 到访存即可结束;
  • R 型指令不用去 Memory;
  • 分支指令在判断后就可以转移。

3.3 Moore 型状态机

多周期控制常常写成 Moore 型 FSM:输出控制信号主要由当前状态决定。也就是说,一旦状态确定,本周期该打开哪些 MUX、该不要写寄存器、该不要读写存储器,就基本都定了。这种写法的优点是结构清楚,便于画状态转移图,每个状态干什么一目了然。

3.4 多周期设计的优缺点

优点:相比单周期,硬件复用更充分;周期缩短,结构比单周期更合理;控制逻辑清楚,适合作为教学和过渡架构。

缺点:吞吐率仍然低,一次只能认真服务一条指令;无法像流水线那样把多条指令重叠起来;现代高性能 CPU 几乎不会满足于这种吞吐。

多周期比单周期省资源,但比流水线差吞吐,所以它更多是历史过渡方案,而不是终点。


四、从多周期走向指令级并行

4.1 为什么还要继续优化

多周期已经把”一条指令内部”拆开了,但这还不够。真正影响性能的,是能不能让多条指令之间也并起来,这就引出指令级并行(Instruction-Level Parallelism, ILP)。

4.2 ILP 的直觉

如果两条指令互不依赖,那它们理论上可以重叠执行、调整顺序、在多功能部件上并发推进。但现实里,指令之间往往有依赖,因此 ILP 的核心问题变成了:哪些能挪,哪些绝对不能挪


五、三类相关性:真相关、名称相关、控制相关

5.1 真相关:RAW(Read After Write)

这是最本质的依赖,也叫数据相关(true dependence)。如果前一条指令先写,后一条指令再读这个结果,那么顺序不能乱:

\[\text{RAW}: \quad I:\text{write }x \;\rightarrow\; J:\text{read }x\]

RAW 体现的是真实数据流。后面的指令真的需要前面算出的值,因此不能随意重排、不能靠改名字解决——这是最”要命”的一类依赖。

5.2 名称相关:WAR / WAW

有些相关并不来自真实数据流,而只是因为大家碰巧用了同一个寄存器名,这就是名称相关(name dependence)。

两种典型形式:

  • WAR(Write After Read):前面要读,后面要写,如果写太早会把前面还没读的内容覆盖掉。
  • WAW(Write After Write):两条指令都要写同一个位置,若顺序错了,最终留下的值就错了。

这两类相关本质上都不是真的”数据必须这么流”,只是名字冲突。因此可以通过寄存器重命名(Register Renaming)消除。

5.3 控制相关(Control Dependence)

分支会带来控制相关。例如:

1
2
if (P1) S1;
if (P2) S2;

这时某些语句是否执行,取决于前面分支的判断结果。控制相关的麻烦在于:你不能轻易把本该在分支之后的指令搬到分支之前;也不能随意把本该只在某个分支里执行的动作挪到外面。

但控制相关并不像 RAW 那样是绝对铁律。只要你能保证程序结果不变,某些看似违反控制相关的变换也可以做,例如投机执行、分支预测后的提前执行等。


六、正确执行的真正标准:数据流与精确异常

6.1 不能只盯着顺序,要盯住数据流

CPU 优化并不要求”表面顺序一模一样”,真正必须守住的是:

  1. 数据流是否正确:该由哪条指令生产的数据,最后是不是仍被正确消费者拿到。
  2. 异常行为是否正确:出了错以后,机器暴露给程序员的状态是不是还能解释得通。

6.2 精确异常(Precise Exception)

所谓精确异常,就是一旦异常发生,体系结构层面要表现得像这样:

  • 出错指令之前的指令都已经完成;
  • 出错指令及其之后的指令都还没有对外留下错误副作用;
  • 系统可以清晰地知道”执行到哪儿了”。

这件事非常重要,因为现代 CPU 即使内部乱序、投机、并发,最终也要对软件呈现出一个可恢复、可调试、可中断的顺序语义。可以乱着干,但最后交账的时候必须交得明明白白。


七、循环展开:开发更多并行性的最常见办法

7.1 为什么先盯循环

程序里最值得挖掘 ILP 的地方,往往是循环。因为循环会重复执行类似指令,容易暴露出不同迭代之间的并行性。

7.2 循环展开(Loop Unrolling)的直觉

循环展开(Loop Unrolling)就是把原来一轮一轮执行的小循环体直接摊开,例如:

1
X[i] + X[i-1] + X[i-2] + ...

展开后:

  • 分支次数减少;
  • 可供调度的指令变多;
  • 编译器/硬件有机会把彼此独立的 load、add、store 重新排布。

7.3 为什么还要配合重命名与调度

光展开还不够。真正的效果来自三件事联动:

  1. 循环展开:把更多指令暴露出来;
  2. 寄存器重命名:消灭假相关;
  3. 指令调度:把能提前的计算、地址更新、访存合理错开。

这套组合拳的目标只有一个:把原本的 stall 挤掉,挖出更多可并行执行的指令

7.4 与 SIMD/向量处理器的联系

如果一个循环体里天然就是同构操作,那么再往前一步,就会走向向量处理器或 GPU 的 SIMD(Single Instruction, Multiple Data)思路。因此循环展开并不是一个孤立技巧,它其实是通往更大规模数据并行的早期入口。


八、核心主线总结

本章内容串联了两条主线:

  1. 单周期 → 多周期 → 流水线,理解数据通路和控制通路是怎么一步步演化的;
  2. 一条指令内部拆分,走到多条指令之间挖并行性,引出后面更复杂的乱序执行、重命名和调度问题。

重点总结:

  1. 多周期的核心是硬件复用 + 状态机控制
  2. 评估处理器要看 $IC \times CPI \times t_{\text{cycle}}$,不能只看主频。
  3. RAW 是真实数据依赖,不能乱动;WAR/WAW 主要是名字冲突,可以靠重命名消掉。
  4. 控制相关不等于绝对不能动,关键是保证数据流正确精确异常
  5. 开发 ILP 最常见的入口,就是对循环做展开、重命名和调度。

体系结构里很多高级技巧,归根结底都不是在”炫技”,而是在想办法让更多正确的工作更早发生,同时又不破坏程序语义。