指令流水线

🔥 高优先级
真题练习

流水线的考题包含 三大点(重要) 和 两小点

  • 三大点:1. 在流水线中指令执行的 各个阶段 的细节。2. 时空图 的画法。3. 流水线冒险的种类和处理方法
  • 两小点:1. 单周期和多周期处理器的概念。 2. 流水线指标。

指令执行阶段

指令的执行由很多按照时序进行的微操作组成,从逻辑角度进行划分,指令执行可以被分为 五个阶段

  1. 取指(IF):从指令存储器获取指令并更新程序计数器。
  2. 译码(ID):解析指令、读取源寄存器并生成控制信号。
  3. 执行(EX):执行计算或分支逻辑,生成中间结果。
  4. 访存(MEM):执行内存读写操作,非内存指令为空。
  5. 写回(WB):将结果写回寄存器,非写回指令为空。

采用这五个阶段的流水线也叫做 五阶段流水线

取指

取指(IF)阶段的主要任务包括:

  • 从指令存储器获取指令:根据程序计数器(PC)的值,从指令存储器(Instruction Memory)中读取当前指令。
  • 更新程序计数器(PC):增加 PC 的值,指向下一条指令的地址。
  • 传递指令:将读取的指令存储到 IF/ID 流水线寄存器,供后续 ID 阶段使用。

译码

译码(ID)阶段的主要任务包括:

  • 解析指令:解码指令的操作码和操作数,确定指令类型(如 ADD、LW、BEQ 等)。
  • 读取源寄存器:从寄存器文件中读取指令所需的源寄存器值。例如:
    • 例如,对于 ADD R1, R2, R3,需要读取 R2 和 R3 的值。
  • 生成控制信号:根据指令类型生成后续阶段(如 EX、MEM、WB)所需的控制信号(如 ALU 操作、内存读写、写回使能等)。

执行

执行(EX)阶段的主要任务包括:

  • 执行计算操作:根据指令类型,使用算术逻辑单元(ALU)执行计算,如加法、减法、逻辑运算或地址计算。
    • 例如,对于 ADD R1, R2, R3,计算 R2 + R3。
  • 处理分支条件:对于分支指令,比较寄存器值并确定是否跳转,更新 PC(如果分支预测错误,可能冲刷水线)。
    • 例如,对于 BEQ R1, R2, label,比较 R1 和 R2 是否相等。
  • 传递结果:将计算结果(如 ALU 输出或内存地址)存储到 EX/MEM 流水线寄存器,供后续阶段使用。

访存

访存(MEM)阶段的主要任务包括:

  • 执行内存操作:对于加载(load)或存储(store)指令,访问数据存储器(Data Memory)以读取或写入数据。
    • 对于 MOV R1, 0(R2),从地址 R2 + 0 读取内存数据到 R1。
  • 传递数据:将内存读取的数据(对于加载指令)或 ALU 计算结果(对于非内存指令)存储到 MEM/WB 流水线寄存器。
  • 空操作(对于非内存指令):对于非加载/存储指令(如 ADD R1, R2, R3BEQ),MEM 阶段不执行实际操作,仅将 EX 阶段的结果传递到 WB 阶段。

写回

  • 将结果写回寄存器:将指令的最终结果(如 ALU 计算结果或加载的内存数据)写入目标寄存器。
    • 例如:ADD R1, R2, R3 将 R2 + R3 的结果写入 R1。
  • 空操作(对于无需写回的指令):对于不写回寄存器的指令,WB 阶段不执行实际操作
    • 例如,JUMP label 不包含写寄存器操作。
补充

访存和写回阶段是否一定出现

在标准的 五阶段流水线 设计中,所有五个阶段在流水线结构上都会“出现”(即指令会按顺序通过这些阶段),但 MEM 和 WB 阶段对于某些指令可能是“空操作”,不执行实际功能。IF、ID 和 EX 阶段则是每条指令都必须执行的实际操作。

访存(MEM)阶段 在流水线结构中存在,但对于非加载/存储指令(如算术、逻辑、分支等),MEM 阶段是空操作,不执行实际内存访问。

写回(WB)阶段 在流水线结构中存在,但对于不需要写回寄存器的指令(如存储、分支、跳转等),WB 阶段是空操作,不执行实际写回。

单周期处理器

单周期处理器是一种 CPU 设计,其中 每条指令的执行都在一个时钟周期内完成。这意味着从指令的取出、译码、执行到结果写回等所有步骤,都必须在一个统一的时钟周期内完成。这个时钟周期的长度必须足以容纳执行时间最长的指令。

单指令流水线设计简单,控制逻辑简单,便于实现和理解。但是其 时钟周期由最慢的指令决定,导致效率低下。

多周期处理器

单周期处理器 中,不对指令的执行过程进行拆分,指令的执行的粒度则是指令本身,如下图所示:

这样会导致 CPU 的执行效率很低,因为下一条指令必须等待上一条指令完全执行结束后,才能执行下一条指令。

这节谈到的 多周期处理器 就是对以上方式的优化:在 CPU 中将指令的执行过程拆分为多个阶段,每个阶段由不同的部件执行,每个阶段用一个时钟周期完成。不同阶段可以并行执行,形成流水线的结构。

物理结构

指令存储器
Add
IR
PC
NPC
寄存器
A
B
Imm
ALU
MUX
MUX
4
ALU 输出
Zero?
Cond
MDR
MUX
MUX
16
32
分支选择
取指
IF
指令译码/读寄存器
ID
执行/计算地址
EX
访问存储器
MEM
写回
WB

上图为流水线的 物理结构,指令执行的 五个阶段 由 CPU 中不同的部件处理,下一阶段的执行部件的执行结果依赖上一个阶段的输入,不同阶段的部件可以并行工作。这样 CPU 中不同部件的利用率就得到了提高,CPU 执行指令的吞吐也会因此提高。

逻辑结构

当然,上图中包含太多具体的器件,流水线的 逻辑结构 如下图所示:

IF/ID
ID/EX
EX/MEM
取指
IF
译码
ID
执行
EX
MEM/WB
访存
MEM
写回
WB
CLK

每个流水段后面都要增加一个 流水段寄存器,用于锁存本段处理完的所有数据,以保证本段的执行结果能在下个时钟周期给下一流水段使。

各种寄存器和数据存储器均采用统一时钟 CLK 进行同步,每来一个时钟,各段处理完的数据都将锁存到段尾的流水段寄存器中,作为后段的输入。同时,当前段也会收到前段通过流水段寄存器传递过来的数据。

执行时序图

在多周期流水线中,指令执行的 并行粒度 从整条指令 降低到 指令执行的各个阶段。这种细粒度的并行使得不同指令的执行阶段可以重叠(overlap),从而显著缩短所有指令的总体执行时间。

为了清晰地描述流水线的执行过程,通常需要通过图形化的方式来展示指令的执行情况。常见的表示方法有以下两种:

  • 常规画法:以横坐标表示时钟周期,纵坐标表示不同的指令。这种方式直观地展示每条指令在各个时钟周期中的执行阶段,便于理解指令间的并行关系和流水线的整体流程。
  • 时空图:一种更抽象的表示方法,结合时间和指令的执行阶段,通常以时间为横轴,执行阶段或资源占用为纵轴,展现指令在流水线中的动态流动和阶段重叠情况。

流水线的冒险和处理

上图中的指令流水线是一种理想情况,然后在实际情况中,情况不会这么简单。指令的流水线执行必须满足两个前提:

第一个前提是指令重叠执行时不会存在任何流水线资源冲突问题,即流水线的各段在同一个时钟周期内不会使用相同的数据通路资源。

第二个前提是指令通过流水线方式指令的结果与串行执行的结果应该相同。

违背以上前提的指令流水线调度方式即发生了 冒险,这些冒险总共可以分为三类:

  • 第一种是 结构冒险,是指令在重叠执行的过程中,硬件资源满足不了指令重叠执行的要求,发生硬件资源冲突而产生的冲突。
  • 第二种是 数据冒险,是指在同时重叠执行的几条指令中,一条指令依赖于前面指令执行结果数据,但是又得不到时发生的冲突。
  • 第三种是 控制冒险,它是指流水线中的分支指令或者其他需要改写 PC 的指令造成的冲突。
提示

流水线冒险和冲突

在计算机体系结构中,“冒险”(Hazard)和 “冲突”(Conflict)这两个术语在描述流水线执行中遇到的问题时,经常可以互换使用,但它们之间存在细微的差别。更准确地说,“冒险”是一个更广泛的概念(冒险并不一定出错),而“冲突”则表示已经发生了错误。

结构冒险

结构冒险是由于 CPU 的 硬件资源有限 而引起的。当两条或多条指令需要使用同一硬件资源时,就会发生结构冒险。

IM
DM
ALU
Reg
Reg
IM
DM
ALU
Reg
Reg
IM
DM
ALU
Reg
Reg
IM
DM
ALU
Reg
Reg
Time (clock cycles)
Instr 0
Instr 1
Instruction Order
Instr 2
Instr 3
能否同时对寄存器
进行读和写?
是否能同时对寄存器
进行读和写?
0
1
2
3
4
5
6
7

上图中画出了不同指令在每个时钟周期所需要使用到的硬件结构,其中指令 0 和指令 1 在第 4 个时钟周期分别需要读和写寄存器,但是 CPU 的架构却并不一定支持这种场景。同样,指令 0 和指令 3 在第 3 个时钟周期分别需要写和读存储器,存储器架构也不一定支持这种场景。若硬件不支持上述场景的话,指令间就发生了结构冒险。

处理方法 也很简单,主要分为两种:

  • 资源重复:既然结构冒险是资源受限所导致的,我们就增加硬件资源的数量,这样不同的指令在同一个时钟周期就可以去访问不同的硬件资源了。
  • 流水线停顿:如果指令 A 和指令 B 发生了结构冒险,那么我们就推迟指令 B 的执行,直到两者不发生结构冒险,如下图所示。
IM
DM
ALU
R2
R1
IM
DM
ALU
R2
R3
IM
DM
ALU
R1
Reg
IM
DM
ALU
Reg
Reg
Time (clock cycles)
Instr 0
Instr 1
Instruction Order
Instr 2
Instr 3
0
1
2
3
4
5
6
7
8
9
10
气泡
气泡
气泡
气泡
气泡
气泡
气泡
气泡
通过流水线停顿的方式解决冲突

数据冒险

数据冒险是由 指令之间的依赖性 引起的。一条指令可能需要使用另一条指令的结果,如果这些指令过早地进入流水线,它们可能会尝试在数据准备好之前使用数据。

数据冒险可以分为三类:

  • 写后读(RAW, Read After Write):下一条指令的源操作数恰好是上一条指令的目的操作数,正常的逻辑是上一条指令写完该寄存器下一条指令才能读,如果下一条指令在上一条指令写完前就读了,就发生了 RAW 数据冒险。
  • 读后写(WAR, Write After Read):下一条指令的目的操作数恰好是上一条指令的源操作数,正常的逻辑是上一条指令读完下一条指令才能写,如果下一条指令在上一条指令读完前就写了,就发生了 WAR 数据冒险。
  • 写后写(WAW, Write After Write):两个指令写入同一个数据项,正常的逻辑是下一条指令比上一条指令更晚写,如果出现了相反的情况,就发生了 WAW 数据冒险。
ADD R1, R2, R3
MOV R4, R1
%3CmxGraphModel%3E%3Croot%3E%3CmxCell%20id%3D%220%22%2F%3E%3CmxCell%20id%3D%221%22%20parent%3D%220%22%2F%3E%3CmxCell%20id%3D%222%22%20value%3D%22%26lt%3Bfont%20style%3D%26quot%3Bfont-size%3A%2016px%3B%26quot%3B%26gt%3BADD%20%26lt%3Bfont%20style%3D%26quot%3Bcolor%3A%20rgb(255%2C%200%2C%200)%3B%26quot%3B%26gt%3BR1%26lt%3B%2Ffont%26gt%3B%2C%20R2%2C%20R3%26lt%3B%2Ffont%26gt%3B%22%20style%3D%22text%3Bhtml%3D1%3Balign%3Dcenter%3BverticalAlign%3Dmiddle%3BwhiteSpace%3Dwrap%3Brounded%3D0%3B%22%20vertex%3D%221%22%20parent%3D%221%22%3E%3CmxGeometry%20x%3D%2260%22%20y%3D%22120%22%20width%3D%22190%22%20height%3D%2240%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%223%22%20value%3D%22%26lt%3Bfont%20style%3D%26quot%3Bfont-size%3A%2016px%3B%26quot%3B%26gt%3BMOV%20R4%2C%20%26lt%3Bfont%20style%3D%26quot%3Bcolor%3A%20rgb(255%2C%200%2C%200)%3B%26quot%3B%26gt%3BR1%26lt%3B%2Ffont%26gt%3B%26lt%3B%2Ffont%26gt%3B%22%20style%3D%22text%3Bhtml%3D1%3Balign%3Dcenter%3BverticalAlign%3Dmiddle%3BwhiteSpace%3Dwrap%3Brounded%3D0%3B%22%20vertex%3D%221%22%20parent%3D%221%22%3E%3CmxGeometry%20x%3D%2260%22%20y%3D%22190%22%20width%3D%22190%22%20height%3D%2240%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3C%2Froot%3E%3C%2FmxGraphModel%3E
上一条指令的目标寄存器与
下一条指令的源寄存器相同
ADD R1, R2, R3
MOV R1, 3
%3CmxGraphModel%3E%3Croot%3E%3CmxCell%20id%3D%220%22%2F%3E%3CmxCell%20id%3D%221%22%20parent%3D%220%22%2F%3E%3CmxCell%20id%3D%222%22%20value%3D%22%26lt%3Bfont%20style%3D%26quot%3Bfont-size%3A%2016px%3B%26quot%3B%26gt%3BADD%20%26lt%3Bfont%20style%3D%26quot%3Bcolor%3A%20rgb(255%2C%200%2C%200)%3B%26quot%3B%26gt%3BR1%26lt%3B%2Ffont%26gt%3B%2C%20R2%2C%20R3%26lt%3B%2Ffont%26gt%3B%22%20style%3D%22text%3Bhtml%3D1%3Balign%3Dcenter%3BverticalAlign%3Dmiddle%3BwhiteSpace%3Dwrap%3Brounded%3D0%3B%22%20vertex%3D%221%22%20parent%3D%221%22%3E%3CmxGeometry%20x%3D%2260%22%20y%3D%22120%22%20width%3D%22190%22%20height%3D%2240%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%223%22%20value%3D%22%26lt%3Bfont%20style%3D%26quot%3Bfont-size%3A%2016px%3B%26quot%3B%26gt%3BMOV%20R4%2C%20%26lt%3Bfont%20style%3D%26quot%3Bcolor%3A%20rgb(255%2C%200%2C%200)%3B%26quot%3B%26gt%3BR1%26lt%3B%2Ffont%26gt%3B%26lt%3B%2Ffont%26gt%3B%22%20style%3D%22text%3Bhtml%3D1%3Balign%3Dcenter%3BverticalAlign%3Dmiddle%3BwhiteSpace%3Dwrap%3Brounded%3D0%3B%22%20vertex%3D%221%22%20parent%3D%221%22%3E%3CmxGeometry%20x%3D%2260%22%20y%3D%22190%22%20width%3D%22190%22%20height%3D%2240%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3C%2Froot%3E%3C%2FmxGraphModel%3E
前后指令的目标寄存器相同
ADD R1, R2, R3
MOV R3, 3
%3CmxGraphModel%3E%3Croot%3E%3CmxCell%20id%3D%220%22%2F%3E%3CmxCell%20id%3D%221%22%20parent%3D%220%22%2F%3E%3CmxCell%20id%3D%222%22%20value%3D%22%26lt%3Bfont%20style%3D%26quot%3Bfont-size%3A%2016px%3B%26quot%3B%26gt%3BADD%20%26lt%3Bfont%20style%3D%26quot%3Bcolor%3A%20rgb(255%2C%200%2C%200)%3B%26quot%3B%26gt%3BR1%26lt%3B%2Ffont%26gt%3B%2C%20R2%2C%20R3%26lt%3B%2Ffont%26gt%3B%22%20style%3D%22text%3Bhtml%3D1%3Balign%3Dcenter%3BverticalAlign%3Dmiddle%3BwhiteSpace%3Dwrap%3Brounded%3D0%3B%22%20vertex%3D%221%22%20parent%3D%221%22%3E%3CmxGeometry%20x%3D%2260%22%20y%3D%22120%22%20width%3D%22190%22%20height%3D%2240%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%223%22%20value%3D%22%26lt%3Bfont%20style%3D%26quot%3Bfont-size%3A%2016px%3B%26quot%3B%26gt%3BMOV%20R4%2C%20%26lt%3Bfont%20style%3D%26quot%3Bcolor%3A%20rgb(255%2C%200%2C%200)%3B%26quot%3B%26gt%3BR1%26lt%3B%2Ffont%26gt%3B%26lt%3B%2Ffont%26gt%3B%22%20style%3D%22text%3Bhtml%3D1%3Balign%3Dcenter%3BverticalAlign%3Dmiddle%3BwhiteSpace%3Dwrap%3Brounded%3D0%3B%22%20vertex%3D%221%22%20parent%3D%221%22%3E%3CmxGeometry%20x%3D%2260%22%20y%3D%22190%22%20width%3D%22190%22%20height%3D%2240%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3C%2Froot%3E%3C%2FmxGraphModel%3E
上一条指令的源寄存器与
下一条指令的目标寄存器相同
RAW
WAW
WAR
提示

以上的中文名词由于翻译关系可能有些绕,建议大家优先记住英文表示。如果上一条指令叫做 A,下一条指令叫做 B,A 写 B 读并且发生了冒险,就叫做写后读(Read After Write),其他冒险命名以此类推。

关键点在于理解流水线调度执行的结果应该与串行执行相同,如果关于某些数据的 读/写 逻辑出现了与串行执行不一致的地方,就发生了数据冲突。

处理方法

数据冒险的 处理方法 如下所示:

  • 流水线停顿(Pipeline Stall):暂停流水线直到数据准备好。
    • 流水线停顿也可以处理结构冒险,是一种比较万能的方案。
  • 数据前推(Data Forwarding):设置相关专用通路,直接将前一条指令的结果传递给需要它的下一条指令,不等结果写回寄存器。
  • 重新排序指令(Instruction Reordering):编译器在编译时对指令进行重新排序,以减少数据冒险。
流水线停顿

流水线停顿即当检测到数据冒险时,暂停后续指令(如 I2)的执行,插入 “气泡”(bubble,即空操作),让流水线等待,直到依赖的数据(如 I1 的结果)准备好。

假设 I1 和 I2 存在数据冲突,通过流水线停顿,我们可以将 I2 的译码阶段(ID)放到 I1 的写回(WB)之后,这样即可解决数据冲突:

IF
ID
EX
MEM1
MEM2
WB
IF
ID
EX
WB
ADD R1, [R2], [R3]
MOV R1, 3
WAW 冒险
流水线停顿
解决冒险
IF
ID
EX
MEM1
MEM2
WB
IF
ID
EX
WB
ADD R1, (R2), (R3)
MOV R1, 3
IF
ID
EX
WB
IF
ID
EX
WB
ADD R1, R2, R3
MOV R4, R1
RAW 冒险
流水线停顿
解决冒险
IF
ID
EX
WB
IF
ID
EX
WB
ADD R1, (R2), (R3)
MOV R4, R1
指令 2 在指令 1 写入 R1 之前就写入了 R1
指令 2 在指令 1 写入 R1 之前就读取了 R1
将下一条指令的 ID 放在上一条指令的 WB 之后
将下一条指令的 ID 放在上一条指令的 WB 之后
IF
ID
EX
MEM1
MEM2
WB
IF
ID
EX
WB
ADD R1, [R2+1], [R3+2]
MOV R3, 3
WAR 冒险
流水线停顿
解决冒险
指令 2 在指令 1 读取 R3 之前就写入了 R3
将下一条指令的 ID 放在上一条指令的 WB 之后
IF
ID
EX
MEM1
MEM2
WB
IF
ID
EX
WB
ADD R1, [R2+1], [R3+2]
MOV R3, 3
旁路转发

旁路技术也称为数据前递(Data Forwarding),是一种用于 解决 RAW(Read After Write) 数据冒险的硬件优化技术。通过 在流水线阶段之间直接传递数据,旁路技术避免或减少因数据依赖导致的流水线停顿,从而提高流水线效率。

举个实际例子:

I1: ADD R1, R2, R3  ; R1 = R2 + R3
I2: SUB R4, R1, R5  ; R4 = R1 - R5

I1 和 I2 存在 RAW 冲突,若使用流水线停顿,需要插入气泡,等待 I1 的 WB 阶段执行完再执行 I2 的 ID 阶段。

若设置相关转发通路,不等前一条指令把计算结果写回寄存器,下一条指令也不再从寄存器读,而将 数据通路中生成的中间数据直接转发 到 ALU 的输入端。

指令 I1 在 EX 段结束时已得到 R1 的新值,被存放到 EX/MEM 流水段寄存器中,因此可以直接从该流水段寄存器中取出数据返送到 ALU 的输入端,这样,在指令 I2 执行时 ALU 中用的就是 R1 的新值,并且无需等待 I1 完成 WB 阶段。

可以建立的旁路路径有以下几种:

  1. EX→EX
    • 适用于执行阶段产生的 ALU 结果,直接转发给下一条指令使用。例如 add → add。
  2. M→EX
    • 适用于 前一条指令在 M 阶段产生结果,而当前指令在 EX 阶段需要使用该结果的情况。
    • 例如:load 指令通常在 M 阶段才从内存中取出数据,如果下一条指令依赖该值,就需要从 M→EX 转发(但这在 Load-Use 中仍可能来不及,需要阻塞 1 周期)。
  3. WB→EX
    • 当前指令依赖的是 更早之前 指令的写回结果,此时只能从 WB 段取值转发给当前指令的 EX 阶段。

但是需要注意的是,旁路技术无法简单粗暴地解决所有 RAW 数据冒险,比如 Load-Use 数据冒险。

装入-使用(Load-Use)冒险 是 RAW(写后读) 数据冒险的一种特殊情况,专门出现在 load 指令后紧跟使用其结果的指令 中。

这种冒险发生在以下场景:

  • 指令 I1:从内存中加载一个值到寄存器(如 load r1, 0(r2));
  • 紧接着的指令 I2:立即使用这个寄存器值(如 add r3, r1, r4);
  • 然而,在 I2 进入执行阶段(EX)时,I1 还未完成内存访问(M 阶段),也就 还没有得到实际数据,导致 I2 使用了 尚未就绪的数据

面对以上问题,解决方案也很简单,即 插入气泡(Bubble)或阻塞(Stall)一个周期

  • 等待 load 指令完成 M 阶段;
  • 然后在下一个周期使用转发线路,从 M→WB 寄存器转发值。
冒险处理实例

下面通过一个实际的例子说明在如何在题目中画出 解决了冒险的指令流水线

假设高级语言一条赋值语句被汇编微如下四条指令:

I1    LOAD  R1, [a]
I2    LOAD  R2, [b]
I3     ADD  R1, R2
I4   STORE  R1, [x]

其中 I3I1 之间存在 WAW 数据冒险,
I3I2 之间存在 RAW 数据冒险,
I4I3 之间存在 WAR 数据冒险。

我们可以直接通过流水线停顿解决数据冒险:假设指令 A 和 B 发生了数据冲突,指令 A 在前,指令 B 在后,那么将 B 的 ID 放在 A 的 WB 之后就可以简单粗暴地解决冲突,在考试中画流水线都应采用这种方式。

解决冲突后,四条指令对应的流水线执行如下图所示:

Instruction
Clock Cycle
1
2
3
4
5
6
7
8
I1
I2
I3
I4
IF
ID
EX
M
WB
IF
ID
EX
M
WB
IF
9
10
11
12
13
ID
EX
M
WB
IF
ID
EX
M
WB
14

控制冒险

控制冒险是由分支和跳转指令引起的。因为 CPU 需要在执行分支和跳转指令后,才能知道下一条要执行的指令在哪里,这导致了流水线的暂停或者无效的指令进入流水线。

以下举例说明控制冒险是如何发生的:

100: ADD R1, R2, R3    ; R1 = R2 + R3
104: BEQ R1, #0, 200   ; 如果 R1 等于 0,则跳转到地址 200
108: SUB R4, R5, R6    ; R4 = R5 - R6
112: MUL R7, R8, R9    ; R7 = R8 * R9
...
200: OR R10, R11, R12 ; R10 = R11 | R12

在 BEQ 指令的 EX 阶段完成之前,流水线已经开始取下一条指令(地址 108 的 SUB 指令)。问题在于,如果 BEQ 指令的条件成立,应该跳转到地址 200,而不是继续执行地址 108 的指令。这就产生了 控制冒险

IF
ID
EX
MEM
IF
ID
EX
ADD R1, R2, R3
BEQ R1, #0, 200
控制冒险
IF
ID
IF
ADD R1, R2, R3
BEQ R1, #0, 200
错误,应该跳转到 200
IF
ID
EX
WB
IF
ID
EX
ADD R1, R2, R3
BEQ R1, #0, 200
/
/
/
Bubble
Bubble
WB
MEM
OR R10, R11, R12
IF
流水线停顿

控制冲突的 处理方法 主要包含以下几种:

  • 流水线停顿(Pipeline Stall/Bubble):在条件跳转指令之后,停止后续指令的执行,插入空操作。
  • 分支预测(Branch Prediction):预测分支的结果(跳转或不跳转),并提前取指。如果预测正确,则可以避免停顿;如果预测错误,则需要清空流水线并重新取指。
  • 延迟分支(Delayed Branch):编译器或处理器对代码进行优化,将分支指令后的一些不依赖于分支结果的指令先执行,从而减少因分支预测错误造成的开销。

性能指标

吞吐率

流水线的 吞吐率(Throughput) 是流水线在单位时间内完成的任务数量。

吞吐率 TP 的计算公式为:

TP=nTk\text{TP} = \frac{n}{T_k}

其中, nn 是任务数, TkT_k 是处理完 nn 个任务所用的总时间。

设时钟周期为 TcT_c ,流水线的段数为 kk 。在理想无阻塞的情况下,一条 kk 段流水线完成 nn 个任务需要 k+n1k + n - 1 个时钟周期,得出流水线的吞吐率为:

TP=n(k+n1)×Tc\text{TP} = \frac{n}{(k + n - 1) \times T_c}

加速比

加速比 衡量流水线系统相对于非流水线系统(串行执行)的性能提升。它表示流水线化后完成相同任务所需时间的减少倍数。

假设:

  • 非流水线执行 nn 个任务的总时间为 Tserial=nkTcT_{serial} = n \cdot k \cdot T_c
  • 流水线执行 nn 个任务的总时间为 Tpipeline=(k+n1)TcT_{pipeline} = (k + n - 1) \cdot T_c

加速比 SS 定义为

S=TserialTpipeline=nkk+n1S = \frac{T_{serial}}{T_{pipeline}} = \frac{nk}{k + n - 1}

在理想情况下,当任务数 nn 很大时,可以近似有

SkS \approx k

最大加速比接近流水线阶段数 kk

高级流水线

高级流水线通过提升流水线的并行程度来提升流水线的执行效率。指令级并行(ILP)的提升主要有两种策略:多发射技术 通过多个功能单元并行处理指令,允许一次发射多条指令到流水线;超流水线技术 通过增加流水线级数,使更多指令在流水线中重叠执行。

以下介绍三种相关技术:超标量流水线超流水线超长指令字

  • 超标量流水线
0
1
2
3
4
5
6
7
8
9
10
11
12
IF
ID
EX
MEM
WB

超标量流水线是一种能够在单个时钟周期内并行执行多条指令的处理器设计技术,通过 多个并行的执行单元(如 ALU、FPU 等),处理器可以同时处理多条指令,从而提高指令吞吐量。

指令获取和解码后,处理器动态分析指令之间的依赖关系。如果指令之间没有数据或控制依赖,处理器会将它们分配到不同的执行单元并行执行。

  • 超流水线技术
0
1
2
3
4
5
6
7
8
9
10
11
12
IF
ID
EX
MEM
WB

超流水线技术将指令 执行过程细分为更多、更小的阶段,从而缩短每个阶段的时间,允许更高的时钟频率。

传统流水线可能有 5 个阶段(如取指、解码、执行、访存、写回),超流水线可能细分为 10 个或更多阶段。每个阶段处理时间减少,处理器可以以更高频率运行。

  • 超长指令字

超长指令字(VLIW, Very Long Instruction Word) 是一种通过 编译器在编译阶段完成指令级并行调度,在一条“超长指令”中同时包含多条可并行执行的子指令,从而提升 ILP 的处理器架构技术。

一条 VLIW 指令本质上是多个“操作槽位(slot)”的组合,例如:

| ALU_op | MUL_op | LOAD_op | BRANCH_op |

处理器在 一个时钟周期内同时执行这些互不相关的操作

取指令包
IF
译码指令包
ID
执行指令1
EX
访存
MEM
写回
WB
执行指令2
EX
执行指令3
EX
取指令包
IF
译码指令包
ID
执行指令1
EX
访存
MEM
写回
WB
执行指令2
EX
执行指令3
EX
取指令包
IF
译码指令包
ID
执行指令1
EX
访存
MEM
写回
WB
执行指令2
EX
执行指令3
EX

超长指令字的 核心思想 是:

由编译器负责分析数据相关、控制相关和资源冲突,提前决定哪些指令能并行。

处理器硬件不再需要复杂的:

  • 乱序执行(Out-of-Order)
  • 寄存器重命名(Rename)
  • 动态相关性检测

从而大幅简化硬件复杂度和功耗。


最后再对比一下三种高级流水线的思路差异:

技术提升 ILP 的方式并行发生在哪里控制复杂度
超标量并行多发射同一拍多条指令极高
超流水线拆细流水级不同拍高度重叠
超长指令字编译期打包并行一条指令内并行极低

一句话总结本质差异:

  • 超标量:硬件很“聪明”
  • 超流水线:时钟切得很“细”
  • VLIW:编译器很“聪明”