前面介绍的单周期处理器采用串行方式执行指令,同一时刻仅有一条指令处于执行状态,导致各功能部件的利用率较低。现代计算机普遍采用指令流水线技术,使多条指令在CPU的不同功能部件中并发执行,从而显著提升硬件资源的并行利用率和程序的整体执行效率。
5.6.1 指令流水线的基本概念
提升处理器并行性的主要途径有两类:
- 时间上的并行,将一个任务分解为多个子阶段,各阶段由专用功能部件依次处理,并允许多个任务在不同阶段同时推进,即
流水线技术。 - 空间上的并行,在一个处理器内配置多个相同的功能部件,使其并行工作,称为
超标量处理器。
一条指令的执行过程可划分为若干有序阶段,每个阶段由特定的功能部件完成。若将这些阶段视为流水线的各级(或称流水段),则整个指令执行流程便构成一条指令流水线。
典型的五段流水线将指令执行划分为以下阶段:
取指(IF):从指令存储器或缓存中读取指令。译码/读寄存器(ID):对指令进行译码,并从寄存器堆中读取操作数。执行/计算地址(EX):执行算术逻辑运算或计算有效地址。访存(MEM):访问主存储器,完成数据的读或写操作。写回(WB):将执行结果写回寄存器堆。
通过重叠执行,可在第 条指令处于译码阶段时,启动第 条指令的取指阶段,从而实现多条指令在不同流水段中的并行推进。
在理想情况下(无冒险、无停顿),每个时钟周期均有一条新指令进入流水线,同时有一条指令完成执行,此时每条指令的平均时钟周期数(CPI)趋近于1。
为便于高效实现指令流水线,指令集应具备以下特征:
- 指令长度统一:简化取指与译码逻辑,避免因变长指令导致取指周期不确定。
- 指令格式规整:确保源操作数寄存器字段位置固定,支持在译码前预取操作数。
- 采用LOAD/STORE架构:仅允许加载(LOAD)和存储(STORE)指令访问主存,其他指令仅操作寄存器,有利于流水段功能划分与调度。
- 数据与指令按边界对齐存放:确保单次访存即可获取完整操作数,避免跨周期访问,保障流水段的原子性与时序规整性。
5.6.2 流水线的基本实现
1. 流水线设计的原则
在单周期实现中,尽管并非所有指令都需要完整经历全部5个阶段,但时钟周期必须以执行时间最长的指令路径为基准。因此,单周期CPU的时钟频率受限于数据通路中的最长路径。
流水线设计遵循以下原则:
- 流水段的数量以最复杂指令所需的功能段数为准;
- 每个流水段的时长以最耗时的操作为准。
例如,某条指令的各阶段延迟如下:1.取指200ps;2.译码100ps;3.执行150ps;4.访存200ps;5.写回100ps,该指令在单周期处理器中的总执行时间为750ps。按流水线设计原则,时钟周期需要取各段的最大延迟,即200ps。因此,每条指令从进入流水线到流出需经历5个周期,总延迟为1000ps,大于单周期实现的750ps。这表明:流水线并不能缩短单条指令的执行延迟。含 条指令的程序,单周期处理器总耗时为 ,而流水线处理器总耗时为 。当 较大时,流水线的吞吐率显著更高,整体执行效率大幅提升。
2. 流水线的逻辑结构
每个流水段之后都需设置一个流水段寄存器,用于锁存该段的输出结果,确保其能在下一个时钟周期供下一流水段使用。所有寄存器和数据存储器均采用统一时钟CLK同步:每来一个时钟脉冲,各段处理完成的数据便锁存至段尾寄存器,作为后续段的输入;同时,当前段接收前一段经寄存器传递过来的数据,从而实现指令在流水线中的逐级推进。
一条指令依次流经IF、ID、EX、MEM、WB五个流水段。当第一条指令进入WB段时,各流水段分别包含一条不同的指令,此时流水线达到满载状态,最多可同时有5条指令处于不同的执行阶段。
NOTE流水段寄存器本身也引入一定时延。但在考试中,若无明确说明,则可忽略寄存器时延。
3. 流水线的时空图表示
流水线的执行过程常用时空图直观表示。
横轴表示时间(单位为时钟周期 ),纵轴表示流水段(空间)。指令 在时刻0进入流水线,于时刻 完成;指令 在时刻 进入流水线,于时刻 完成;以此类推,从时刻 起,每个周期结束时均有一条指令完成。例如,到时刻 时,已有 至 共6条指令完成执行。相比之下,若采用单周期实现(每条指令需约3.75个时钟周期,因 ),在 内仅能完成约2~3条指令。可见,流水线通过重叠执行,显著提升了指令吞吐率。
值得注意的是,流水线的高效性依赖于连续、无中断的指令流。而程序执行天然具有顺序性和连续性,因此非常适合采用流水线技术。
4. 流水线的吞吐率分析
流水线的吞吐率(Throughput,TP)是指单位时间内流水线完成的任务数(或输出结果的数量),是衡量流水线性能的重要指标。其基本定义式为
其中, 为任务总数, 为完成这 个任务所需的总时间。
设流水线共有 段,时钟周期为 。在理想条件下(任务连续输入、无阻塞),完成 个任务所需的时间为 ,因此吞吐率可表示为 。当任务数量 趋于无穷大时,启动阶段(前 个周期)的影响可忽略不计,此时吞吐率达到理论最大值 。
5.6.3 MIPS指令集的流水段分析
每条MIPS指令的前两个功能段相同:
- 取指(IFetch):从指令存储器中取出指令并计算PC+4。
- 寄存器/译码(Reg/Dec):从寄存器堆中读取操作数并对指令进行译码。
后续功能段则根据具体指令类型有所不同。
1. R型指令的功能段划分
R型指令属于寄存器-寄存器型(RR型)指令,其操作数和结果均位于通用寄存器中。典型的R型指令从寄存器Rs和Rt读取源操作数,在ALU中完成指定运算,并将结果写入目的寄存器Rd。R型指令在流水线中经过IFetch和Reg/Dec阶段后,进入:
- 执行(Exec):在ALU中完成运算。
- 写回(Write):将ALU的结果写入寄存器堆中的Rd。
2. I型指令的功能段划分
I型指令包含16位立即数,用于立即数运算、内存访问或条件分支,是RISC处理器实现常量操作和地址计算的重要手段。I型运算类指令先对16位立即数进行符号扩展(或零扩展),再与Rs的内容在ALU中运算,结果写入寄存器Rt。其功能段划分与R型指令的完全相同。
3. lw指令的功能段划分
lw 指令的功能为 ,即从内存中读取一个字并写入寄存器Rt。它将Rs的值与符号扩展(Sign Extension)后的16位立即数相加,形成有效地址,再从该地址读取数据。如图5.24所示,lw指令在流水线中经过 IFetch和Reg/Dec阶段后,进入:
- 执行(Exec):计算内存地址 。
- 访存(Mem):从数据存储器中读取一个字。
- 写回(Write):将读取的数据写入寄存器堆中的 Rt。
4. sw指令的功能段划分
sw 指令的功能为 ,即将寄存器 Rt中的数据写入内存。sw指令在流水线中经过 IFetch和 Reg/Dec阶段后,进入:
- 执行 (Exec): 计算内存地址 。
- 访存(Mem):将Rt中的数据写入数据存储器中指定地址。
5. beq指令的功能段划分
beq 指令的功能为:若 ,则 。它比较两个寄存器的值,若相等,则转移至目标地址,否则顺序执行。目标地址由当前PC加4后,再加上符号扩展并左移2位的16位立即数得到。beq指令在流水线中经过IFetch和Reg/Dec阶段后,进入:
- 执行(Exec):比较Rs与Rt,并计算分支目标地址 。
- 访存(Mem):若比较结果为相等,则将目标地址写入PC。
需要注意的是,beq指令的Mem段并非真正的内存访问,而是将写PC操作安排在该段,以便与lw、sw等指令对齐。由于写PC的延迟小于存储器访问,因此可在Mem段完成。
6. j指令的功能段划分
j指令是无条件转移指令,其功能是直接将目标地址送入PC。除两个公共功能段外,j指令仅需一个功能段用于更新PC,该操作可合并到Exec段完成。具体如下:
- 执行(Exec):计算目标地址并更新PC。
从上述分析可见,lw指令最复杂,需5个功能段。为统一流水线结构,其他指令通过插入“空”段(不执行实际操作的阶段)对齐至5段。插入空段需遵循两个原则:
- 每条指令对任一功能部件至多使用一次(如同一条指令不能多次使用寄存器的写口)。
- 相同功能部件必须在固定阶段使用(如寄存器写回总在第5阶段)。
因此,R型和I型运算指令在Write前插入空Mem段,使其Write 段与lw指令对齐;sw和beq 指令在 Exec 后插入空 Write 段;j指令插入空 Mem 和 Write段。通过上述对齐,所有指令均适配于5个功能段,因此该处理器可采用5段流水线设计。
5.6.4 流水线的冒险与处理
在指令流水线中,某些情况可能导致后续指令无法正确执行,从而引起流水线阻塞,这种现象称为流水线冒险。根据成因不同,可分为结构冒险、数据冒险和控制冒险三种类型。
不同类型指令在各流水段的操作如表5.3所示。
表5.3 不同类型指令在各流水段中的操作
| 指令 | 流水段 | IF | ID | EX | MEM | WB |
|---|---|---|---|---|---|---|
| ALU | 取指 | 译码读寄存器堆 | 执行 | - | 结果写回寄存器堆 | |
| 取/存 | 取指 | 译码读寄存器堆 | 计算访存有效地址 | 访存(读/写) | 将读出的数据写入寄存器堆/— | |
| 转移 | 取指 | 译码读寄存器堆 | 计算转移目的地址,设置条件码 | 若条件成立,将转移目的地址送PC | - |
这几类指令将在下面介绍流水线冲突时涉及。
1. 结构冒险
结构冒险(又称资源冲突)是指不同指令在同一时刻争用同一功能部件所引发的冲突,其本质是硬件资源的物理限制。例如,在指令与数据共享同一存储器的系统中,第 条LOAD指令在第4个时钟周期处于MEM段(访问数据存储器),而第 条指令在同一周期处于IF段(取指令),两者同时访存,引发冲突。此时可暂停后续指令的取指操作一个周期,如表5.4所示。当然,若第 条指令不是访存指令,则其在MEM段不访问存储器,也就不会发生访存冲突。
表5.4 用暂停后续指令的方法解决访存冲突
| 时钟周期 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|---|---|---|---|---|---|---|---|---|---|
| LOAD指令 | IF | ID | EX | MEM | WB | ||||
| 指令i+1 | IF | ID | EX | MEM | WB | ||||
| 指令i+2 | IF | ID | EX | MEM | WB | ||||
| 指令i+3 | 停顿 | IF | ID | EX | MEM | WB | |||
| 指令i+4 | IF | ID | EX | MEM |
解决结构冒险的主要方法有:
- 遵循功能部件使用原则:确保每个功能部件在每条指令中至多使用一次,且总在固定阶段使用(如寄存器写回操作统一安排在WB段),可避免部分结构冲突。
- 增加硬件资源:例如,将寄存器堆的读口与写口分离,支持在一个周期的前半拍写、后半拍读;或将指令存储器与数据存储器分离。现代处理器的L1Cache通常采用指令Cache与数据Cache分离的设计,从根本上消除了取指与数据访存之间的资源竞争。
2. 数据冒险
数据冒险又称数据相关,其根本原因是:后面指令用到前面指令的结果时,前面指令的结果还未产生或写回。在按序发射、按序完成的流水线中,所有数据冒险都是因为前一条指令写结果之前,后面指令就需要读取而造成的,称为写后读(Read After Write, RAW)冲突。
NOTE在非乱序执行①的流水线中(统考常涉及这种方式),只可能出现RAW冲突。
例如,考虑下列两条指令:
I1 add R1, R2, R3 # (R2)+(R3)→R1I2 sub R4, R1, R5 # (R1)-(R5)→R4在RAW冲突中,I2的源操作数R1正是I1的目的操作数。在非流水线中,I1先写入R1,I2再读取R1,顺序自然成立。但在流水线中,I2在ID流水段就要读取R1,而I1要到WB段才将结果写回寄存器堆,导致I2读取的是R1的旧值,如表5.5所示。
表5.5 add和sub指令发生写后读(RAW)冲突
| 指令 | 时钟周期 | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|---|
| add | IF | ID | EX | MEM | WB(写R1) | ||
| sub | IF | ID(读R1) | EX | MEM | WB |
可采用以下方法解决RAW冲突。
(1) 延迟执行相关指令
将数据相关的指令及其后续指令都暂停若干时钟周期,直至前一条指令的结果可被安全读取。可分为软件插入空操作(nop)指令和硬件自动插入气泡(阻塞)两种方法。
由表5.5可见,add指令在第5个时钟周期才将结果写回R1,而sub指令在第3个时钟周期就需读取R1,发生RAW冲突。若不采取措施,sub指令将使用错误的旧值。为此,可让sub指令延迟3个时钟周期,使其ID段发生在add指令的WB段之后,如表5.6所示。
表5.6 用延迟相关指令的办法来解决RAW冲突
| 指令 | 时钟周期 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|---|---|---|---|---|---|---|---|---|---|---|
| add | IF | ID | EX | MEM | WB | |||||
| sub | 阻塞 | 阻塞 | 阻塞 | IF | ID | EX | MEM | WB |
此外,若寄存器堆支持在一个时钟周期的前半个时钟周期写入、后半个时钟周期读出,则add指令在WB段写入的值可在同一个时钟周期被sub指令在ID段读取。此时,add指令的WB段与sub指令的ID段可重叠执行,从而仅需延迟2个时钟周期。
(2) 采用转发(旁路)技术
设置相关转发通路,使后续指令无须等待前一条指令将计算结果写回寄存器堆,而是将其在执行阶段生成的中间结果直接转发至ALU的输入端。如表5.7所示,add指令在EX段结束时已计算出R1的新值,并暂存于EX/MEM流水段寄存器中。当sub指令进入EX段时,其所需的R1值可直接从该流水段寄存器转发至ALU,从而确保使用的是最新结果。
表5.7 用转发技术来解决RAW冲突
| 指令 | 时钟周期 | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|---|
| add | IF | ID | EX | MEM | WB | ||
| sub | IF | ID | EX | MEM | WB |
增加转发通路后,相邻的两条运算类指令之间,以及相隔一条无关指令的两个运算类指令之间的数据相关所引发的RAW冲突,均可通过转发有效消除。
(3) load-use数据冒险的处理
若load 指令与其后紧邻的运算类指令存在数据相关,则无法通过转发技术解决,这种情况称为 load-use 数据冒险。考虑以下两条指令:
I1 load r2, 12(r1) # M[(r1)+12]→(r2)I2 add r4, r3, r2 # (r3)+(r2)→(r4)load 指令在 MEM 段结束时才从存储器读出数据,并暂存于MEM/WB 流水段寄存器;而紧随其后的add指令在其EX段(与load 指令的MEM段处于同一周期)就需要R2的值。由于此时load 指令尚未完成访存,结果不可用,因此add指令只能读取R2的旧值。
对于 load-use 数据冒险,最简单的做法是由编译器在 add 指令前插入一条 nop 指令。这样,add 指令的EX段就能通过转发机制,从MEM/WB 流水段寄存器中获取 load 指令的最新结果,如表5.8所示。当然,最好的办法是在程序编译时进行优化,通过调整指令顺序以避免 load-use 相关的发生。
表5.8 用延迟加转发技术来解决load-use冲突
| 指令 | 时钟周期 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|---|
| load | IF | ID | EX | MEM | WB | |||
| add | 阻塞 | IF | ID | EX | MEM | WB |
3. 控制冒险
指令通常按顺序执行,但在遇到转移、返回、中断或异常等事件时,程序计数器(PC)的值会被修改,导致流水线断流,这种现象称为控制冒险(又称控制冲突)。
对于由分支指令引起的冲突,最简单的处理方法是推迟后续指令的执行。通常将因流水线阻塞产生的延迟时钟周期数称为延迟损失时间片 。在下列指令中,假设R2存放常数N,R1的初值为1。bne指令在EX段完成条件计算,但直到MEM段结束(第5个时钟周期末)才确定是否更新PC,因此从分支指令进入流水线到转移决策完成,共产生3个时钟周期延迟(记 )。为避免错误执行后续指令,可在分支指令后插入 条nop指令,如表5.9所示。
I1 loop: add R1, R1, 1 # (R1)+1→R1I2 bne R1, R2, loop # if (R1)!=(R2) goto loop表5.9 用插入空操作的办法解决控制冲突
| 指令 | 时钟周期 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| add | IF | ID | EX | MEM | WB | ||||||
| bne | IF | ID | EX | MEM | WB | ||||||
| add | IF | ID | EX | MEM | WB |
解决控制冒险的主要方法包括:
- 延迟分支处理:对于由分支指令引起的冲突,可由软件在分支指令后插入若干nop指令,或由硬件自动阻塞(插入气泡)。插入nop指令的数量等于分支延迟周期数。
- 分支预测技术:尽早生成转移目标地址并预测转移方向,以减少流水线清空。
静态预测采用简单规则,总是预测转移发生或不发生;动态预测根据程序运行时的转移历史动态调整预测策略,准确率更高。若预测错误,则需清空已进入流水线的错误路径指令,并从正确目标地址重新取指;若分支延迟周期数为3,此时将损失3个时钟周期。
NOTECache缺失、中断或异常的发生也会引起流水线阻塞。
5.6.5 高级流水线技术
有两种主要策略可用于提升指令级并行度:一是多发射技术,通过配置多个内部功能部件,使流水线在每个时钟周期能同时处理多条指令,处理器一次可发射多条指令进入流水线执行;二是超流水线技术,通过增加流水线级数,使更多指令在流水线中重叠执行。
1. 超标量流水线技术(动态多发射)
每个时钟周期可并发发射多条独立指令,为此需配置多个功能部件。在简单的超标量处理器中,指令按顺序发射。但为了提升并行性能,多数现代超标量处理器结合动态调度技术(如动态分支预测等),支持乱序执行,即指令的执行顺序可不同于程序顺序。
2. 超长指令字技术(静态多发射)
由编译器挖掘指令间的并行性,并将多条可并行执行的指令打包成一条超长指令字,其中包含多个操作码字段,分别控制不同的处理部件。由于并行性由软件静态确定,控制相对简单。
3. 超流水线技术
超流水线通过进一步细分流水段来缩短时钟周期,从而提高主频和指令吞吐率。然而,流水级数增加会带来更大的流水段寄存器开销和更高的控制复杂度,因此流水线深度并非越多越好。
在理想情况下:超流水线CPU在流水线充满后,每个时钟周期完成一条指令,CPI=1,但主频更高;多发射CPU每个时钟周期可完成多条指令,,但硬件成本更高、控制更复杂。
