第六章指令流水线¶
引言:
MIPS CPU实现方案
单周期方案
- 性能受限于最慢的指令
- 结构简单,实现容易
多周期方案
-
传统多周期
-
提升性能,复用器件
-
异步控制,变长指令周期
-
指令流水线
-
多指令并行,提升性能
- 部件并发
虽然单周期MIPS CPU性能存在问题,,但其结构简单,实现容易,所以在早期小指令集系统中经常使用这种结构。
为进一步提升性能,复用器件,又引入了传统的多周期MIPS CPU,这种架构采用异步控制方式,指令周期变长,每条指令需要多少时钟周期就安排多少时钟周期,设计该类CPU时采用了状态机的方法,一条指令经历多少状态就安排多少时钟周期,如|w指令需要5个时钟周期,R型运算指令需要4个时钟周期,分支指令3个时钟周期,这种变长指令周期模式可一定程度上提升程序执行的速率。
而最新的流水线结构也是一种多周期方案,该方案采用多指令并行技术,同一时刻有多条指令并行,分别处在取指,译码,执行,访存,写回的不同阶段,多个部件完全并发,可有效提升性能,也就是我们下面要介绍的方法。
1.1 流水线的执行效率¶
- 流水线的性能指标
- 高级流水线技术
- 三种处理器的性能对比
- 袁书配套习题以及袁书网站习题
- 软硬件接口
- csapp
- 九曲阑干
- 博客
- 清华计组ppt
- 零壹流水线大题
- 考研流水线真题
https://zhuanlan.zhihu.com/p/447682231?utm_psn=178001273715204915
《计算机组成》 陆俊林 - 随笔分类 - houhaibushihai - 博客园
| 多周期 | 单周期 | 流水线 | 超(深)流水线 | 多发射流水线 | |
|---|---|---|---|---|---|
| 理想情况下CPI | >1 | =1 | =1 | =1 | <1 |
指令流水线一般把一个RISC指令的指令周期分成五部分,称作五个流水段(也称功能段),这五个流水段分别为
- 取指(
IF,ifetch):从指令存储器或Cache中取指令 - 译码/读寄存器(
ID,Instruction Decode):操作控制器对指令进行译码,同时从根据寄存器号从寄存器堆中取出操作数。 - 执行/计算地址(
EX,Execute):执行运算操作或计算地址。 - 访存(
MEM,Memory Access):对存储器进行读写操作。 - 写回(
WB,Write Back):将指令执行结果写回寄存器堆。
流水线指令集的设计
- 长度尽量一致,有利于简化取指令和指令译码操作
- MIPS指令32位,下址计算方便:PC+4
- X86指令从1字节到17字节不等,使取指部件及其复杂
- 指令格式少,且源寄存器位置相同,有利于在指令未知时就可取操作数
- MIPS指令的Rs和Rt位置一定,在指令译码时就可读Rs和Rt的值
- 若位置随指令不同而不同,则需先确定指令后才能取寄存器编号
- load / Store指令才能访问存储器,有利于减少操作步骤,规整流水线
Iw/sw指令的地址计算和运算指令的执行步骤规整在同一个周期- X86运算类指令操作数可为内存数据,需计算地址、访存、执行
- 内存中"对齐"存放,有利于减少访存次数和流水线的规整
总之,规整、简单和一致等特性有利于指令的流水线执行, 流水线执行方式能大大提高指令吞吐率,现代计算机都采用流水线方式!
流水线设计的原则是
- 指令流水段个数以最复杂指令所用的功能段个数为准
- 流水段的长度以最复杂的操作所花的时间为准。


时钟信号定义: 所有信号在一个时钟周期内从状态单元1经组合逻辑到达状态单元2,信号到达状态单元2所需的时间决定了时钟周期的长度。
1.2 流水线处理器的实现¶
先对每条指令的执行过程进行分析,以确定流水线每个功能段的功能和执行时间。每条指令前两个功能段都一样,它们的功能如下。Ifetch:取指并计算PC十4;Reg/Dec:寄存器取数并译码。后面的功能段随各指令功能的不同而不同。
Load指令的五个阶段¶
\
- Ifetch (取指) : 取指令并计算PC+4
- Reg/Dec (取数和译码) : 取数同时译码
- Exec (执行) : 计算内存单元地址
- Mem (读存储器) : 从数据存储器中读
- Wr(写寄存器): 将数据写到寄存器中
R-type指令和I-型运算类指令的4个阶段
¶

- Ifetch: 取指令并计算PC+4
- Reg/Dec: 从寄存器取数,同时指令在译码器进行译码
- Exec:
R-type指令在ALU中对操作数(Rs和Rt内容)进行计算,I-型带立即数的运算类指令涉及对16 位立即数进行符号扩展或零扩展,然后和Rs的内容进行运算 - Wr: ALU计算的结果写到寄存器
下面介绍如何规整该指令
含R-type和 Load 指令的流水线
上述流水线有个问题: 两条指令试图同时写寄存器,因为
- Load在第5阶段用寄存器写口
- R-type在第4 阶段用寄存器写口
把一个功能部件同时被多条指令使用的现象称为结构冒险(
Struture Hazard)为了流水线能顺利工作,规定:
每个功能部件每条指令只能用一次(如:写口不能用两次或以上)
每个功能部件必须在相同的阶段被使用(如:写口总是在第五阶段被使用)
可以用以下两种方法解决上述结构冒险问题!
- 解决方案: 在流水线中插入“
Bubble”(气泡)
插入“
Bubble”到流水线中,以禁止同一周期有两次写寄存器。缺点:
- 控制逻辑复杂
- 第5周期没有指令被完成(CPI不是1,而实际上是2)
因此该方案不可行!
- 解决方案: R-type的Wr操作延后一个周期执行
加一个
NOP阶段以延迟“写”操作: 把“写”操作安排在第5阶段, 这样使R-Type的Mem阶段为空NOP
Store指令的四个阶段¶


- Ifetch:取指令并计算PC+4
- Reg/Dec: 从寄存器取数,同时指令在译码器进行译码
- Exec:16位立即数符号扩展后与寄存器值相加,计算主存地址
- Mem:将寄存器读出的数据写到主存

Wr: 加一个空的写阶段,使流水线更规整!
Beq指令的四个阶段¶


- Ifetch: 取指令并计算PC+4
- Reg/Dec:从寄存器取数,同时指令在译码器进行译码
- Exec: 执行阶段
- ALU中比较两个寄存器的大小(做减法)
- Adder中计算转移地址
- Mem: 如果比较相等, 则:转移目标地址写到PC
Info
- Wr: 加一个空写阶段,使流水线更规整 ,使流水线更规整!
- 这里Beq指令与多周期通路有什么不同?\ 多周期通路中,在 Reg/Dec阶段投机进行了转移地址的计算!可以减少Branch指令的时钟数
- 为什么流水线中不进行“投机”计算\ 因为,流水线中所有指令的执行阶段一样多,Branch指令无需节省时钟,因为有比它更复杂的指令。
j指令的功能段划分¶
\(j\)指令是无条件转移指令,其功能是直接将目标地址送 PC 中。所以,其功能段的划分很简单,除了两个公共的功能段外,就只有一个功能段WrPC。
Info
对以上各指令功能段的分析可看出,最复杂的是lw 指令,它有5 个功能段,其他指令都可以通过加入“空”功能段来向lw 指令靠齐。在插入“空”段时,应遵循以下两个原则:
- 每个功能部件每条指令只能用一次(如寄存器写口不能用两次或以上);
- 每个功能部件必须在相同的阶段被使用(如寄存器写口总是在第 5 阶段被使用)。
因此,
- R-型指令、I-型运算类指令需在 Write 之前加一个空的“Mem”段,使得其 Write段和 lw 指令的Write 对齐,都在第5段;
- sw 指令和 beq 指令在第4 个功能段后加一个空的“Write”段;
- j 指令则在后面添加两个空段“Mem”和“Write”。
这样,所有指令都有5 个功能段。因此,该处理器的指令流水线可以设计成 5 个流水段。
通过上述指令的分析,得出执行这些指令的流水线数据通路的基本框架,如下图所示:

Note
图中给出了数据通路用到的所有控制信号,用点虚线连到所控制的功能部件。可以看出,
- PC和各个流水段寄存器都没有写使能信号 ,这是因为每个时钟都会改变PC的值,所以PC不需要写使能控制信号;
- 每个流水段寄存器在每个时钟都会写入一次,因此, 流水段寄存器也不需要写使能控制信号 。
- 此外,前两个流水段的功能每条指令都相同,是 公共流水段,因此,也不需要控制信号 。
其余段的控制信号如下
\(Exec\)段的控制信号有以下5个。
- \(ExtOp\)(扩展器操作):1-符号扩展,0-零扩展。
- \(ALUsrc\)(\(ALU\)的 \(B\) 口来源):1-来源于扩展器,0-来源于 \(busB\)。
- \(ALUOp\)(用于辅助局部 \(ALU\)控制逻辑来决定\(ALUctr\)的操作信号):3 位编码。
- \(RegDst\)(指定目的寄存器):\(1-Rd,0-Rt\)。
- \(R-type\)(区分是否为\(R-\)型指令):\(1-R-\)型指令,\(0-\)非 \(R-\)型指令。
\(Mem\) 段的控制信号有以下两个。
- \(MemWr\)(数据存储器\(DM\)的写信号):\(sw\) 指令时为1,其他指令为0。
- \(Branch\)(是否为分支指令):分支指令时为1,其他指令为 0。
\(Wr\)段的控制信号有两个。
- \(MemtoReg\)(寄存器的写入源):\(1:DM\) 输出,\(0:ALU\)输出
- \(RegWr\)(寄存器堆写信号):结果写寄存器的指令都为1,其他指令为0。
1.3 各流水段的功能、功能部件、保存到流水段寄存器的信息和控制信号取值¶
Ifetch(IF)段功能:¶
将 PC 的值作为地址到指令存储器IM(Instruction Memory)中取指令,并计算 PC+4,送PC输入端。这些功能由取指部件(IUnit)来完成

取指和ID(取数译码)阶段有哪些控制信号?
不需控制信号,因为每条指令执行功能一样,是确定的,无需根据指令的不同来控制执行不同的操作!
Info
对上图功能的解读
假定当前指令地址为12,则当时钟CIk 的下降沿到来时,在PC输入端的值12 经过“Clk-to-Q”时延后,被送到 IM 的地址输入端 Adr,并同时送加法器。 然后
- 在IM 中经过一个存取时间后,指令被送到IM的输出端。
- 在加法器中 PC与“4”相加后送到一个多路选择器 MUX
- 若是顺序执行,则下一个时钟到来时 PC 为 16。
- 当执行到分支指令或无条件转移指令时,PC 的值可能被修改,当需要转移时,可控制选择转移目标地址送 PC。
IF段执行的结果被送到 IF/ID 寄存器的输入端,下一个时钟到来时,在 IF/ID 寄存器输入端的信息开始送到 ID段继续被处理。
那么,IF/ID寄存器中需要保存的结果是哪些呢?
- 从 IM中取出的指令要被继续处理,因而,需要保存在IF/ID寄存器中;
- 此外,如果当前指令是分支指令,则PC+4的值在后面的流水段中需要用来计算相对转移地址,所以,PC+4 的值也需要保存在 IF/ID 寄存器中。
多路选择器MUX的控制端, 由在Mem 段产生的 Branch 信号和 Zero标志来控制
- Branch 信号只有在对分支指令 beq译码后才取值为1,其他情况下,Branch总是0,所以,在IF 阶段 Branch=0,因而,此时,多路选择器的输出为 PC+4。
- 在执行 beq 指令时,在
Mem段将得到转移目标地址,此时,若Zero=1 则将 转移目标地址 选择送到 PC 的输入端
译码、取数 Reg/Dec(ID)段¶
【ID 流水段的功能】:根据指令中的Rs和Rt的值到寄存器堆中取出相应寄存器的值,同时对指令中的操作码op 字段进行译码,生成相应的控制信号。
Tip
- 寄存器堆可看成是寄存器读口和寄存器写口两个功能部件。ID段的功能由寄存器读口和控制器完成。
- ID段执行的结果被送到 ID/Ex 寄存器的输入端,下一个时钟到来时,在 ID/Ex 寄存器输入端的信息开始送到Ex段继续被处理。
- 因此ID/EX寄存器保存的信息包括\(R[Rs]、R[Rt]、Rt、Rd、imm16、func、PC+4\)等。
- 因为指令中需要的信息已被保存,所以指令本身就不再需要保存在ID/Ex寄存器中。

执行部件(Exec Unit)的执行¶
Note
该段的功能由具体指令确定,不同指令经ID段译码后得到不同的控制信号,用来控制执行部件进行不同的操作。\ 一般来说,执行部件有以下功能
- 计算内存地址
- 计算转移目标地址
- 一般ALU计算

在Ex流水段中,各类指令的功能分别描述如下:
| 指令类型 | 功能 |
|---|---|
| \(R-\)型指令 | 在 \(ALU\)中对 \(busA\) 和 \(busB\)传递来的数据执行相应的运算,运算结果送 \(ALUout\),并产生相应的标志信息\(Zero\) 和 \(Overflow\)。 |
| \(I-\)型运算类指令 | 在\(ALU\)中对立即数扩展后的数据与 \(busA\)传递来的数据执行相应的运算,运算结果送\(ALUout\),并产生相应的标志信息\(Zero\) 和 \(Overflow\)。 |
| \(lw/sw\)指令 | 在\(ALU\)中对立即数符号扩展后的数据与\(busA\)传递来的数据执行加法运算,得到存储器地址送 \(ALUout\)。 |
| \(beq\)指令 | 在\(ALU\)中对\(busA\)和\(busB\)传递来的数据执行减法运算,得到\(Zero\)标志;同时,对立即数进行符号扩展后,在加法器中与“\(PC+4\)”相加,得到转移目标地址。 |
| \(j\)指令 | 生成转移目标地址,即($PC<31:28> |
eg:如果有些信息例如\(Load\)指令生成的目的寄存器\(Rt\)的信息没有传递下去,会怎么样?
其他指令的\(Rt\)作为目的地址,指令执行错误!
执行流程及控制信号如下
| 指令类型 | 执行流程 | 控制信号 |
|---|---|---|
| \(R-\)型指令 | + \(add\)、\(sub\)、\(subu\)、\(slt\) 和 \(sltu\) 都是\(R-\)型指令,它们在\(ALU\)中由\(ALUctr\) 控制分别执行 \(add\)、\(sub\)、\(subu\)、\(slt\) 和 \(sltu\) 运算,\(ALUctr\)操作控制信号由局部\(ALU\)控制器根据\(func\)字段产生。 + \(R-\)型指令的目的寄存器是 \(R_d\),\(ALU\)的操作数来自 \(busA\) 和\(busB\),不需要扩展操作。最终,将\(ALU\)得到的结果,包括\(Overflow\)标志和\(Zero\)标志一起被送到 \(Ex/Mem\) 寄存器的输入端。 |
\(RegDst=1\),\(ExtOp=x\),\(ALUsrc=0\),\(ALUOp=xxx\),\(R-type=1\) |
| \(I-\)型运算类指令 | + \(ori\)和 \(addiu\) 是\(I-\)型运算类指令,它们在 \(ALU\)中由 \(ALUctr\) 控制分别执行 \(or\) 和 \(addu\)运算,\(ALUctr\) 操作控制信号由主控制器根据指令操作码\(op\)字段产生。 + \(I-\)型运算类指令的目的寄存器是\(Rt\),\(ALU\)的操作数来自于 \(busA\)和扩展器的输出,逻辑运算进行 零扩展 ,而算术运算则为 符号扩展。与 \(R-\)型指令一样,最终将\(ALU\)中得到的结果,包括 \(Overflow\) 标志和 \(Zero\) 标志一起被送到\(Ex/Mem\)寄存器的输入端。 |
+ \(ori\)指令的控制信号取值为 \(RegDst=0\),\(ExtOp\)=0,\(ALUsrc =1\),\(ALUOp=or\),\(R-type = 0\)。 + \(addiu\)指令的控制信号取值为\(RegDst =0\),\(ExtOp=1\),\(ALUsrc=1\),\(ALUOp=addu\),\(R-type=0\) |
| \(lw\)指令 | + \(lw\)指令的执行首先要在\(ALU\)中进行地址计算,\(ALU\)的操作数来自 \(busA\)和扩展器输出,采用符号扩展,在 \(ALU\)中由 \(ALUctr\) 控制执行 \(addu\) 运算,目的寄存器是 \(R_t\)。 + 最终在 \(ALU\)中得到的存储器地址被送到 \(Ex/Mem\) 寄存器的输入端。 |
\(RegDst=0\),\(ExtOp\)=1,\(ALUsrc=1\),\(ALUOp=addu\),\(R-type=0\) |
| \(sw\)指令 | 同\(lw\) 指令一样,需要进行存储器地址计算并送下一级流水线寄存器。因为该指令不会写结果到寄存器,所以,\(RegDst\)的取值可任意,不会影响结果。 | \(RegDst=x\),\(ExtOp\)=1,\(ALUsrc=1\),\(ALUOp=addu\),\(R-type=0\) |
| \(beq\)指令 | + \(beq\) 指令需要比较寄存器\(R_s\)和\(R_t\)的值,通过在\(ALU\)中做减法生成\(Zero\)标志来实现比较。因此,\(ALU\)两个操作数来源是 \(busA\)和 \(busB\),\(ALUctr\) 操作控制信号为 \(subu\); + 同时,将\(imm16\) 送到扩展器,然后在\(ExtOp\)的控制下进行符号扩展,扩展结果左移两位,再和\(PC+4\) 相加,生成相对转移目标地址。 + 执行阶段生成的\(Zero\)标志和转移目标地址被送到\(Ex/Mem\) 寄存器的输入端。因为不改变任何寄存器的值,所以控制信号 \(RegDst\) 的值任意。 |
\(RegDst=x\),\(ExtOp=1\),\(ALUsrc = 0\),\(ALUOp = subu\),\(R-type=0\) |
| \(j\)指令 | 只要形成一个32 位的转移目标地址($PC<31:28> |
Mem段¶
该段功能由具体指令确定。该流水段有Branch和MemWr两个控制信号。
| 指令种类 | 该段执行功能 | 控制信号 |
|---|---|---|
| R-型指令或I-型运算类指令 | “空”操作,只要把相应信息继续传递到下一个流水段即可。 | Branch=0,MemWr=0 |
| lw 指令 | 取数操作,在Ex段得到的地址被送到 DM 的读地址端RA,经过一段存取时间,数据从 DM 的输出端 Do 送到 Mem/Wr 寄存器的输入端。 | Branch=0,MemWr=0 |
| sw 指令 | 存数操作。在 Ex 段得到的地址被送到数据存储器 DM 的写地址端 WA,同时把Ex/Mem 寄存器送来的要存的数据 R[Rt]送DM 的输入端 Di,经过一段存取时间后,数据被存入 DM 中。 | Branch=0,MemWr=1 |
| beq 或j指令 | 将Ex段生成的转移目标地址更新到 PC 中;若是 beq指令,则 Branch=1,MemWr=0。此时,若在Ex段生成的Zero为1,则会控制PC输入端的多路选择器选择将转移目标地址送 PC的输入端。 |
eg:Load指令的存储器读(Mem)周期

Wr段¶
寄存器写口是Wr段的主要功能部件,寄存器堆RFile的写地址端口Rw 来源于 Mem/Wr 寄存器的目的寄存器输出,写数据端口Di 来源于一个多路选择器的输出,写使能信号WE由溢出标志 Overflow和控制信号RegWr 共同确定。Wr流水段的功能也由具体指令确定,各条指令在Wr段的执行流程和控制信号取值如下。
| 指令种类 | 该段执行功能 | 控制信号 |
|---|---|---|
| R-型指令或I-型运算类指令 | 选择将ALU 的输出结果送寄存器堆的输入端 Di,目的寄存器送写地址端 Rw | MemtoReg=0,RegWr=1 |
| lw 指令 | 选择将 DM 读出结果送寄存器堆的输入端 Di,目的寄存器送写地址端 Rw | MemtoReg=1,RegWr=1 |
| sw、beq 或j指令 | 任何寄存器的值都不改变,即不能写寄存器 | MemtoReg=x,RegWr=0 |
eg:Load指令的存储器读(Mem)周期


1.4 流水线冒险及其处理¶

【引例】

- Branch指令何时确定是否转移?转移目标地址在第几周期计算出来?
Answer
第六周期得到Zero和转移地址、第七周期控制转移地址送到PC输入端、第八周期开始才能根据转移地址取指令
- Load指令何时能把数据写到寄存器?第几周期开始写数据?
Answer
第五周期写入、第六周期开始才能使用
- 如果Branch指令执行结果是需要转移(称为taken),则流水线会怎样?
Answer

虽然Beq指令在第四周期取出,但: 目标地址在第七周期才被送到PC的输入端,第八周期才能取出目标地址处的指令执行
结果:在取目标指令之前 ,已有三条指令被取出 ,取错了三条指令!
这种现象称为控制冒险(Control Hazard ) (注:也称为分支冒险或转移冒险(Branch Hazard) )
- 如果后面R-Type的操作数是load指令目标寄存器的内容,则流水线怎样?
Answer

尽管Load指令在第一周期就被取出,但:
- 数据在第五周期结束才被写入寄存器;
- 在第六周期时,写入的数据才能被用结果:
在Load指令结果有效前,已经有三条指令被取出(如果随后的指令要用到Load的数据的话,就需要延迟三条指令才能用!)
这种现象被称为 数据冒险(Data Hazard)或数据相关(Data Dependency )
1.4.1 结构冒险(Structural Hazard)现象¶

Danger
- 如果只有一个存储器,则在Load指令取数据同时又取指令的话,则发生冲突!
- 如果不对寄存器堆的写口和读口独立设置的话,则发生冲突!
结构冒险也称为硬件资源冲突:同一个执行部件被多条指令使用。
Note
【解决方法】:
为了避免结构冒险,规定流水线数据通路中功能部件的设置原则为:
- 根据前面提到过的功能段划分原则(一个部件每条指令只能使用一次,且只能在特定时钟周期使用),可以避免一部分结构冒险,如:ALU总在第三阶段被用!
- 通过设置多个独立的部件来避免硬件资源冲突:
- 对于寄存器访问冲突,可将寄存器读口和写口独立开来,利用时钟上升沿和下降沿两次触发,使得前半周期使用写口进行寄存器写,后半周期使用读口进行寄存器读;
- 对于存储器访存冲突,可把指令存储器IM 和数据存储器 DM分开,从而使指令和数据的访问各自独立
Note
对处理器中的每个阶段和功能部件进行合理划分,以确保每个功能部件不会在同一时钟周期内被多条指令竞争使用。例如下图的第五个时钟周期,在这个时钟周期中有五个指令都在执行,但是由于各自处于不同的流水阶段,并且使用的都不是同一个部件,因此可以避免多个指令同时竞争同一个功能部件。

如何理解避免结构冒险的基本做法是使每个指令在相同流水段中使用相同的部件
有同学对这句话理解不了,如上图,例如Load指令在第四个时钟周期即它的MEM阶段使用DM;同样在第四周期,下一条指令Instr 1 正处于EXE阶段,仅使用ALU;Instr 2正处于ID阶段,只是用到了寄存器堆的读口;由此可见,若遵守每条指令在相同流水段使用相同部件,这样可以保证在同一个时钟周期下,由于各指令分别处于不同的流水段,因此不会使用到同一个部件。
1.4.2 数据冒险(Data Hazard)现象¶

数据冒险(data hazard)也称为数据相关(data dependency),引起数据冒险的原因在于后面指令用到前面指令结果时前面指令结果还没有产生。
举例说明:以下指令序列中 ,寄存器r1会发生数据冒险

补充:三类数据冒险现象
RAW:写后读(基本流水线中经常发生,如上例)表示当前指令将数据写⼊寄存器后,下⼀条指令才能从该寄存器读取数据。否则,先读后写, 读到的就是错误(旧)数据。
WAR:读后写(基本流水线中不会发生,多个功能部件时会发生)表示当前指令读出数据后,下⼀条指令才能写该寄存器。否则,先写后读,读到的就是错误(新) 数据。
WAW:写后写(基本流水线中不会发生,多个功能部件时会发生)表示当前指令写⼊寄存器后,下⼀条指令才能写该寄存器。否则,下⼀条指令在当前指令之前 写,将使寄存器的值不是最新值。
本讲介绍基本流水线,所以仅考虑RAW冒险, 在按序执行的流水线中,只可能出现RAW冲突!
方案1:在硬件上采取措施,使相关指令延迟执行(插入气泡)
硬件上通过阻塞(stall)方式阻止后续指令执行,延迟到有新值以后!这种做法称为流水线阻塞,也称为“气泡Bubble”

方案2:软件上插入无关指令
最差的做法:由编译器插入三条NOP指令,浪费三条指令的空间和时间

方案3:利用DataPath中的中间数据(转发、数据旁路)
仔细观察后发现:流水段寄存器中已有需要的值r1!

硬件上的改动以支持“转发”技术
- 加MUX,使流水段寄存器值返送ALU输入端
- 假定流水段寄存器能读出新写入的值 (否则,需要更多的转发数据)

Load-use数据冒险¶

Load指令最早在哪个流水线寄存器中开始有后续指令需要的值?
实际上,在第四周期结束时,数据在流水段寄存器中已经有值。采用数据转发技术可以使load指令后面第二条指令得到所需的值,但不能解决load指令和随后的第一条指令间的数据冒险,要延迟执行一条指令!这种load指令和随后指令间的数据冒险,称为“装入-使用数据冒险(load- use Data Hazard)”

方案1:硬件阻止指令执行来解决load-use 用硬件阻塞一个周期(指令被重复执行一次)

硬件阻塞的具体原理(可不看)
- 判断什么条件下需要阻塞,注意观察检测点与阻塞点

- 阻塞前的情况

检测“阻塞”过程中:
1) sub指令在IF/ID段寄存器中,并正被译码/取数,控制信号和Rs/Rt的值将被写到ID/EX段寄存器
2) and指令地址在PC中,正被取出,取出的指令将被写到IF/ID段寄存器中
在阻塞点,必须将上述两条指令的执行结果清除,并延迟一个周期执行这两条指令
延迟一个周期执行后面的指令,相当于把阻塞点前面一个周期的状态再保持一个周期
lw指令还是继续正常执行下去 想想看,如何做到继续保持状态?
- 阻塞后的情况

在阻塞点,必须将上述两条指令的执行结果清除,并延迟一个周期执行这两条指令
① 将ID/EX段寄存器中所有控制信号清0
② IF/ID寄存器中的信息不变, 寄存器中的信息不变,sub指令重新译码执行
③ PC中的值不变,and指令重新被取出执行
方案2:软件上插入NOP指令来解决Ioad-use
用软件插入一条NOP指令!(有些处理器不支持硬件阻塞处理)
例如:MIPS1 处理器没有硬件阻塞处理,而由编译器(或汇编程序员)来处理。

方案3:编译器进行指令顺序调整来解决Ioad-use

1.4.3 控制冒险(Control Hazard)现象¶
正常情况下,指令在流⽔线中总是按顺序执⾏,当遇到改变指令执⾏顺序的情况时,流⽔线中指令的正常执⾏会被阻塞。这种由于发生了指令执行顺序改变而引起的流水线阻塞称为控制冒险。各类转移指令(包括调用、 返回指令等)的执行,以及异常和中断的出现都会改变指令执行顺序,因而都可能会引发控制冒险。

如图,假定beq指令的地址为12,条件满足时其转移目标地址为1000。分支指令beq 的转移目标地址计算操作在Ex 段,并在 Mem 段由标志Zero 和控制信号 Branch 来控制,以确定是否将PC 的值更新为转移目标地址。因此,在该例中,只有当 beq 指令执行到第5时钟结束才能将转移目标地址1000 送到PC的输入端,在第6时钟到来后,取出1000号单元开始的指令送流水线中执行。此时,紧接在 beq后面的第 16、20 和 24 单元的指令已在流水线中被执行了一部分。显然,正确的执行流程应该是第12单元中的beq指令执行完后转移到第1000单元执行,因此,如果不采取相应措施,则指令流水线的执行便发生问题。通常把由于流水线阻塞而带来的延迟执行周期数称为延迟损失时间片C。显然,图中的延迟损失时间片C=3。
解决方法1:插入气泡或插入空操作指令nop
假设延迟损失时间片为C,则在数据通路中检测到分支指令时,就在分支指令后插入C 个气泡,或在编译时在分支指令后填入C 条 nop 指令。这两种方式效率极低,一般不采用!
解决方法2:对转移指令进⾏分⽀预测,尽早⽣成转移⽬标地址
- 简单(静态)预测:总是预测条件不满⾜,即继续执⾏分⽀指令的后续指令
具体做法以及如何减少错误预测代价(别看)
- 基本做法
- 总预测条件不满足(not taken),即:继续执行分支指令的后续指令
可加启发式规则: 在特定情况下总是预测满足(taken),其他情况总是预测不满足 ,其他情况总是预测不满足
- 预测失败时,需把流水线中 ,需把流水线中三条错误预测指令丢弃掉
将三条丢弃指令的控制信号值设置为0,使其后续过程中执行nop操作 (注:涉及到当时在IF、ID和EX三个阶段的指令)
- 性能 :如果转移概率是50%,则预测正确率仅有50%
- 预测错误的代价
• 预测错误的代价与何时能确定是否转移有关。越早确定代价越少 。
• 可以把“是否转移”的确定工作提前,而不要等到MEM阶段才确定
那最早可以提前到哪个阶段呢?
- 缩短分支延迟,减少错误预测代价
- 可以通过调整“转移地址计算”和“分支条件判断”操作到ID阶段来缩短延迟
- 将转移地址生成从MEM阶段移到ID阶段,可以吗?为什么? (是可能的:IF/ID流水段寄存器中已经有PC的值和立即数)
- 将“判0”操作(即条件判断操作)从EX阶段移到ID阶段,可以吗?为什么?
- (用逻辑运算(如,先按位异或,再结果各位相或 ,再结果各位相或)来直接比较Rs和Rt的值)
- (简单判断用逻辑运算 复杂判断可以用专门指令生成条件码)
- (许多条件判断都很简单)
- 预测错误的检测和处理(称为“冲刷、冲洗” -- Flush)
- 当Branch=1并且Zero=1时,(即转移条件满足)发生转移(taken)
- 增加控制信号:IF.Flush=Branch and Zero,取值为1时,说明预测失败
- 预测失败(即转移条件满足)时,完成以下两件事 (延迟损失时间片 (延迟损失时间片C=1时):
- ① 将转移目标地址 ->PC
- ② 清除IF段中取出的指令,即:将IF/ID中的指令字清0,转变为nop指令
- 原来要清除三条指令,调整后只需要清除一条指令 ,因而只延迟一个时钟周期 ,每次预测错误减少了两个周期的代价!

- 动态分支预测方法(现在几乎所有的处理器都采用动态预测(dynamic predictor))
简单的静态分支预测方法的预测成功率不高,应考虑动态预测 ,动态预测根据程序执⾏的历史情况,进⾏动态预测调整,有较⾼的预测准确率。
更详细的信息(别看)
- 动态预测基本思想:
- 利用最近转移发生的情况,来预测下一次可能发生的转移
- 预测后,在实际发生时验证并调整预测
- 转移发生的历史情况记录在BHT中
- 分支历史记录表BHT(Branch History Table)
- 分支目标缓冲BTB(Branch Target Buffer)
- 每个表项由分支指令地址低位作索引,故在IF阶段就可以取到预测位
- 低位地址相同的分支指令共享一个表项,所以,可能取的是其他分支指令的预测位。会不会有问题? - 由于仅用于预测,所以不影响执行结果

查找时发现都不相 等,则“未命中”, 未命中说明什么?
说明以前没有执行过该分支指令
- 命中时: 根据预测位,选择“转移取”还是“顺序取”
- 未命中时: 加入新项,并填入指令地址和转移目标地址、初始化预测位
动态预测基本方法
- 采用一位预测位:总是按上次实际发生的情况来预测下次
- 1表示最近一次发生过转移(taken),0表示未发生(not taken)
- 预测时,若为1,则预测下次taken,若为0,则预测下次not taken
- 实际执行时,若预测错,则该位取反,否则,该位不变

- 缺点:当连续两次的分支情况发生改变时,预测错误
例如,循环迭代分支时 ,循环迭代分支时,第一次和最后一次会发生预测错误 ,因为循环的第一次和最后一次都会改变分支情况,(因为第一次需要转移,最后一次循环结束说明不用转移),而在循环中间的各次,总是会发生分支(需要条件转移),按上次的实际情况预测时 ,都不会出错。

预测位初始为0,外循环只有最后一次预测错误 ;跳出内循环时预测位变为1,再进入内循环时 ,第一次总是预测错误 ,并且任何一次循环的最后一次总是预测错误,因此,总共有1+2×(N–1)次预测错误。 N越大准确率越高!
- 采用二位预测位
- 用2位组合四种情况来表示预测和实际转移情况
- 按照预测状态图进行预测和调整


- 在连续两次分支发生不同时,只会有一次预测错误
采用比较多的是二位预测位,也有采用二位以上预测位 。 如:Pentium 4 的BTB2采用4位预测位
解决方法3:分支延迟时间片的调度,属于静态调度技术,由编译程序重排指令顺序来实现
更详细的信息(不看)
基本思想: 把分支指令前面的与分支指令无关的指令调到分支指令后面执行,以填充延迟时间片(也称分支延迟槽 Branch Delay slot),不够时用nop操作填充

- 其他方法
- 预取转移成功和不成功两个控制流⽅向上的⽬标指令。
- 加快和提前形成条件码。(简单静态预测里有介绍)
- 提⾼转移⽅向的猜准率。
1.4.4 异常和中断引起的控制冒险¶
异常和中断的出现会改变程序的执行流程,使得流水线执行发生阻塞。与分支冒险一样,当某条指令执行过程中发现异常或中断时,可能它后面的多条指令已经被取到流水线中正在执行。
例如,ALU 运算类指令发现“溢出”时,已经到Exec 阶段的结束了,此时,它后面已有两条指令进人了流水线。
流水线数据通路处理异常的示例
设指令add r1,r2,r3产生了溢出 (记住:MIPS异常处理程序的首地址为0x8000 0180)
处理思路:
- 清除add指令以及后面的所有已在流水线中的指令
- 关中断(将中断允许触发器清0)
- 保存PC或PC+4(断点) 到 EPC
- 0x8000 0180送PC(从0x8000 0180处开始取指令)

异常处理:
- IF.Flush使IF段指令在IF/ID寄存器中清为0,变成nop指令
- ID.Flush与数据冒险阻塞检测信号相或(or)后,使ID段指令的控制信号清0
- EX.Flush使EX段指令的控制信号清0
- 关中断,并将断点(可能是PC、可能是PC+4)保存到EPC中
- 将0x8000 0180作为PC的一个输入,并控制PC输入端的多路选择器
问题:会发生将溢出结果写到寄存器 r1中去的情况吗 ?
不会!EX.Flush使EX段指令的控制信号清 0 ( 主要保证RegWr清0),避免了在WB阶段写寄存器r1的情况。

流水线中同时有 5条指令,到底是哪一条发生异常?
• 根据异常发生的流水段可确定是哪条指令,因为各类异常发生的流水段不同 因为各类异常发生的流水段不同
- “溢出 ”在EXE段检出
- “无效指令 ”在ID段检出
- “除数为0”在ID段段检出
- “无效指令地址 ”在IF段检出
- “无效数据地址 ”在Load/Store指令的EXE段检出
外部中断与特定指令无关,如何确定处理点?
可在IF段或WB段中进行中断查询,需要保证当前WB段的指令能正确完成,并在有中断发生时,确保下个时钟开始执行中断服务程序
- 若放在IF 中检测,因为可在取指令前进行,若发现有中断请求发生,则能确保在该时钟周期就开始执行中断服务程序,并让已经在流水线中的指令继续执行完,不需要进行指令冲刷;
- 若放在Wr阶段进行,则需要将刚执行完的指令后面几条指令从流水线中清除掉。
检测到异常时,指令已经取出多条,当前PC的值已不是断点,怎么办?
指令地址存放在流水段 R,可把这个地址送到EPC保存,以实现精确中断
(非精确中断不能提供准确的断点 ,而由操作系统来确定哪条指令发生了异常 )
一个时钟周期内可能有多个异常,该先处理哪个?
- 异常:检出异常后,存到专门寄存器中(前面指令优先级高 )
- 中断:在中断查询程序或中断优先级排队电路中按顺序查询

对于五段流水线处理器,任何一个时钟周期都有5条活动的指令,因而很可能在一个时钟周期内同时有多条指令发生异常或中断,不同流水段发生不同类型的异常。
例如,在Ex阶段 add 指令发生“溢出”的同时,ID阶段的指令发生了“无效指令”,Mem 阶段的lw指令发生了“缺页”,并且又发生了外设I/O中断请求。
上述这种情况下,显然应该先响应和处理lw 指令的“缺页”异常。显然,排在前面的指令发生异常的响应优先级高,因此,优先级确定原则是,在同一个时钟周期内的指令序列中,最先执行的指令所产生的异常最先被响应,外部中断请求最后响应,即,对同时在5个指令流水段中发生的异常进行排序时,其顺序为 Wr>Mem>Ex>ID>IF。
系统中只有一个EPC,多个中断发生时 ,一个EPC不够放多个断点,怎么办?
总是把优先级最高的送到EPC中
在异常处理过程中,又发生了新的异常或中断 ,怎么办
利用中断屏蔽和中断嵌套机制来处理
1.4.5 Cache缺失处理引起的流水线阻塞¶
- 在使用Cache的系统中,数据通路中的IM和DM分别是Code Cache和Data Cache
- CPU执行指令过程中,取指令或取数据时 ,如果发生缺失,则指令执行被阻塞
- Cache缺失的检测 : Cache中有相应的检测线路(地址高位与Cache标志比较)
-
阻塞处理过程
-
冻结所有临时寄存器和程序员可见寄存器的内容(即:使整个计算机阻塞)
- 由一个单独的控制器处理Cache缺失
- 把发生缺失的指令地址(PC- 4)所在的主存块首址送到主存
- 启动一次“主存块读”操作, 并等待主存完成一个主存块(Cache行)的读操作
- 把读出的一个主存块写到Cache对应表项的数据区 (若对应表项全满的话 ,还要考虑淘汰掉一个已在Cache中的主存块)
- 把地址高位部分(标记)写到Cache的“tag”字段,并置“有效位”
- 重新执行指令的第一步:“取指令”
- 若是读数据缺失,其处理过程和指令缺失类似
- 从主存读出数据后,从“取数”那一步开始重新执行就可以了
- 若是写数据缺失,则要考虑用哪种“写策略”解决“一致性”问题
Note
- 比数据相关或分支指令引起的流水线阻塞简单:只要保持所有寄存器不变 只要保持所有寄存器不变
- 与中断引起的阻塞处理不同:不需要程序切换
1.4.6 TLB缺失和缺页引起的流水线阻塞¶
-
TLB缺失处理
-
当TLB中没有一项的虚页号与要找的虚页号相等时,发生TLB miss
- TLB miss说明可能发生以下两种情况之一:
- 页在内存中:只要把主存中的页表项装载到TLB中
-
页不在内存中(缺页):OS从磁盘调入一页,并更新主存页表和TLB
-
缺页(page fault)处理
-
当主存页表的页表项中“valid”位为“0”时,发生page fault
- Page fault是一种“故障”异常,按以下过程处理 (MIPS异常处理)
- 在Cause寄存器置相应位为“1”
- 发生缺页的指令地址(PC- 4)送EPC
- 0x8000 0180(异常查询程序入口)送PC ,执行OS的异常查询程序,取出Cause寄存器中相应的位分析,得知发生了“缺页”,转到“缺页处理程序”执行
- page fault一定要在发生缺失的存储器操作时钟周期内捕获到,并在下个时钟转到异常处理,否则,会发生错误。
例:lw $1, 0($1) ,若没有及时捕获“异常”而使$1改变,则再重新执行该指令时 ,所读的内存单元地址被改变,发生严重错误 ,发生严重错误!
缺页异常处理时需要考虑的细节
缺页异常结束后,回到哪里继续执行 ?
- 指令缺页:重新执行发生缺页的指令
- 数据缺页:
- 简单指令(仅一次访存):强迫指令结束,重新执行缺页指令
- 复杂指令(多次访存):可能会发生多次缺页,指令被中止在中间某个阶段,缺页处理后 ,从中间阶段继续执行;因而 ,需要能够保存和恢复中间机器状态
异常发生后,又发生新的异常 ,怎么办?
- 在发现异常、转到异常处理程序中,若在保存正在运行进程的状态时又发生新的异常,则因为要处理新的异常 ,会把原来进程的状态和保存的返回断点破坏掉,所以,应该有一种机制来“禁止”响应新的异常处理
- 通过“中断/异常允许”状态位(或“中断/异常允许”触发器)来实现
- “中断/异常允许”状态位置1,则“开中断”(允许异常),清0则“关中断 ” ( 禁止异常)
- 也可由OS通过管态指令来设置该位的状态
流水线冒险对程序性能的影响¶
- 结构冒险对浮点运算的性能影响较大,因为浮点运算单元不能有效被流水 ,可能造成运算单元的资源冲突
- 控制冒险更多出现在整数运算程序中,因为分支指令对应于循环或选择结构 ,大多由整数运算结果决定分支
- 数据冒险在整数运算程序和浮点运算程序中都一样
- 浮点程序中的数据冒险容易通过编译器优化调度来解决
- 分支指令少
- 数据访问模式较规则
- 整数程序的数据冒险不容易通过编译优化调度解决
- 分支指令多
- 数据访问模式不规则
- 过多使用指针
- 浮点程序中的数据冒险容易通过编译器优化调度来解决
1.5 高级流水线¶
流水线开发了指令流内部隐藏着的并行性。这种并行性被称为指令级并行。主要存在着两种增加指令级并行的策略。
- 第一种是增加流水线的级数以能将更多指令的执行过程重叠起来。使用前面的洗衣店的类比实例,假设洗衣过程比其他的过程相比所消耗的时间要更长,我们可以将传统的洗衣机分成三个机器分别进行清洗、漂洗和旋转洗涤。这样原来的4级流水线就变成了6级流水线。无论对处理器还是洗衣机,为了获得最大的加速比,我们需要调整流水线中的其他步骤,从而使各个步骤具有相同的长度,这点适用于洗衣店,同样适用于处理器。由于更多的操作在并行执行,所以并行度的开发程度更高。由于时钟周期的缩短,性能有了显著的提高。
- 第二种策略是设计更多的内部元件从而能够在一个流水线的每级中发射多条指令。通常我们称这种技术为多发射。例如,一个多发射的洗衣店可能用3台洗衣机和3台烘干机来代替我们家用的一台洗衣机和一台烘干机。当然这时你需要招募更多的助手,从而在相同时间内能整理并保存以前三倍的衣服。接下来的工作就是保证机器一直工作并将负载传递给下一个流水线阶段。
<strong>在一个流水线阶段中发射多条指令使得指令的执行效率能够超过时钟频率,也就是说,达到CPI值小于1</strong>。有时我们使用它的倒数IPC,即一个周期内完成的指令数(instructions perclock cycle),特别是CPI值小于1的时候!因此,一个运行于6GHz的四路的多发射微处理器在峰值情况下每秒可以执行240亿条指令,也就可以达到最理想的0.25的CPI值,即IPC为4。如果假设它是5级流水线的话,任何一个时刻处理器内部都同时存在着20条指令。当今的高端微处理器试图在每周期发射3~8条指令。但如果指令流中的指令间存在依赖关系或不符合特定的标准,那么将面临很多限制条件。<strong>有两种实现多发射的方法,它们之间的主要区别在于编译器和硬件之间的分工。</strong>由于分工<strong>主要在于某些决定是在编译期(即编译时)决定还是在运行期(即执行时)动态决定</strong>,所以这两种方法有时称为<strong>静态多发射和动态多发射</strong>。
Note
王道讲错的地方:

此外上个图容易误导以为超流水线的CPI<1
在原有流水线基础上,随着技术升级又产生了有两种增加指令级并行的策略:
-
一种是多发射技术,它通过采用多个内部功能部件,使流水线功能段能同时处理多条指令,处理机一次可以发射多条指令进入流水线执行。 其代表技术为 1.超标量处理器 2.超长指令字处理器
-
另一种是超流水线技术,它通过增加流水线级数来使更多的指令同时在流水线中重叠执行。 其代表技术为 超流水处理器

1.5.1 实现多发射技术的基础—推测¶
推测技术:由编译器或处理器猜测指令执行结果,并以此来调整指令执行顺序, 使指令的执行能达到最大可能的并行
- 指令打包的决策依赖于“推测”的结果
- 可根据指令间的相关性来进行推测 ----> 与前面指令不相关的指令可以提前执行
- 可对分支指令进行推测 ------> 可提前执行分支目标处的指令
- 预测仅是“猜测”,有可能推测错误,故需有推测错误检测和回滚机制
- 推测错误时,会增加额外开销

1.5.2 静态多发射流水线---超长指令字技术¶
主要思想:
- 将多个相互无依赖的指令封装到一条超长的指令字中
- CPU中有对应数量的ALU完成相应的指令操作
- 指令之间的依赖性和调度由编译器来完成

编译器在编译时,进行相关性分析和静态分支预测,以静态完成“指令打包”或“冒险处理”
- 指令打包(将同时发射的多条指令合并到一个长指令中)
- 将一个周期内发射的多个指令看成一条多个操作的长指令,称为一个“发射包”
- “静态多发射”最初被称为“超长指令字”(VLIW-Very Long Instruction Word)
- 在同一个周期内发射的指令类型是受限制的,由硬件决定 (例如,若数据通路仅提供一套ALU运算部件和存储访问部件,则只能是一条ALU指令/分支指令、一条Load/Store指令)
-
冒险处理(主要是数据冒险和控制冒险)
-
做法1:完全由编译器通过代码调度和插入nop指令来消除所有冒险,无需硬件实现冒险检测和流水线阻塞
- 做法2:由编译器通过静态分支预测和代码调度来消除同时发射指令间内部依赖 ,由硬件检测数据冒险并进行流水线阻塞
即:保证打包指令内部不会出现冒险!
Note
- VLIW的设计思想与RISC类似,即采用简单的指令和硬件。不同之处在于,VLIW并行执行多条指令。VLIW架构中,指令级并行的发现与指令执行顺序的调度(硬件中最困难的部分)完全交由编译器完成。这样,硬件可以尽可能的保持简单,并采用流水线,从而使得CPU的设计简化,并达到较高的主频。在某种程度上,可以将VLIW架构看作是多条RISC流水线并行起来的结果。
- VLIW实现的功能与超标量处理器也有相似之处 —— 一次发射并完成多个操作,但他们之间有一个重要的区别:VLIW硬件不负责发现并发执行多个操作的工作。对于VLIW实现,很长的指令字已经对并发操作进行了编码,这通常是由编译器来完成的。与RISC或CISC的高度超标量实现相比,这种显式编码极大地降低了硬件的复杂性。
示例1:
超长指令ADD R1,R2,R3,R4,R5,R6,R7,R8。 在此指令中将R1,R2放入第一个ALU计算,将R3,R4放入第而个ALU计算,将R5,R6放入第三个ALU计算,将 R7,R8放入第四个ALU计算。相当于可以同时支持四组寄存器计算操作。


实例2:MIPS ISA 指令集的静态多发射----2发射处理器

要使原来的MIPS处理器能够同时处理两条流水线,数据通路需要做哪些改进?
-
同时要能取并译码两条指令,怎么办?
-
将两条指令打包成64位长指令,前面为ALU/Branch,后面为lw/sw
- 没有配对指令时, 就用nop指令代替
-
将64位长指令中的两个操作码同时送到控制器(指令译码器)进行译码
-
两条指令同时要读两个寄存器或写寄存器 (和lw配对时) ,怎么办?
-
增加两个读口和一个写口
-
两条指令同时要使用ALU进行运算 ,怎么办?
-
增加一个ALU(包括2组输入总线和1组输出总线)

扩展:看一下CISC、RISC和VLIW的区别
注意,实际上的代码不会是这样的,这里只是说明三种架构的区别。
function( int a) {
int b;
a = a + b;
}


1.5.3 动态多发射处理器---超标量处理器¶
主要应用在多核CPU场景,如下CPU有4个核,每个核有自己的一套流水线执行部件,那么可以4条指令流水线共同工作。


硬件在执行时动态完成指令打包或冒险处理
- 通常被称为超标量处理器(Superscalar)
- 在一个周期内执行一条以上指令
- 与VLIW处理器的不同点:
| 是否需要指令打包 | 移植性 | |
|---|---|---|
| VLIW(超长指令字)处理器 | 编译器依赖性强:编译器必须知道处理器的具体架构,以便正确地将指令打包。因此,编译结果与处理器的具体结构紧密相关。 | 可移植性差:由于不同处理器可能有不同的功能单元和并行度,同一个程序在不同VLIW处理器上执行时,可能需要重新编译以适应新的处理器架构。 |
| 超标量处理器 | 硬件决定指令发射:硬件有能力动态决定在每个周期内可以执行哪些指令。这意味着,编译器只需要做一些基本的指令顺序调整,而无需担心具体的指令打包。 | 编译后的代码可移植性强:由于硬件负责指令发射和并行执行,编译后的代码可以在不同的超标量处理器上执行,而不需要重新编译。这增强了代码的可移植性和通用性。 |
多数超标量处理器都结合动态流水线调度(Dynamic pipeline scheduling)技术
- 通过指令相关性检测和动态分支预测等手段,投机性地不按指令顺序执行,当发生流水线阻塞时,可以到后面找指令来执行
sub指令可以提前执行,不需等lw和addu指令执行完
如果不将sub调到前面,可能要等很长时间 (lw指令的访存操作耗时较长!),从而影响slti指令的执行
动态流水线的一个重要的思想:在等待解决阻塞时,到后面找指令提前执行!
- 动态流水线的通用模型:
- 一个指令预取和译码单元:有序发射
- 多个并列执行的功能单元:乱序执行
- 一个提交单元:有序提交

扩展知识(不用看)
功能单元的性能
- 功能:用来执行特定类型的操作
- 性能:每个功能单元具有基本的操作性能,用两个周期数来刻画
- 执行周期(Latency):完成特定操作所花的周期数
- 发射时间(Issue Time):连续、独立操作之间的最短周期数
以下是Pentium III 算术功能部件的性能

从上述图中看出,
- 整数加、整数乘、浮点加、装入、存储这五种部件是流水化的
- 浮点乘部件是部分流水化
- 整数除和浮点除是完全没有流水化
从上述图中能否看出:
- 整数加法和乘法、浮点数加法和乘法是重要的操作
- 除法相对来说不太常用, 而且本身难以实现流水线
编译器可以依据数据依赖关系来调度代码 ,为什么还要超标量处理器来动态调度 ?
- 并不是所有阻塞都能事先确定,动态调度可在阻塞时 ,提前执行无关指令 - 例如,Cache缺失是不可预见的阻塞
- 动态分支预测需要根据执行的真实情况进行预测
- 采用动态调度使得硬件将处理器细节屏蔽起来 (不同处理器的发射宽度 、流水线延时等可能不同,流水线的结构也会影响循环展开的深度。通过动态调度使得处理器细节被屏蔽起来,软件发行商无需针对同 一指令集的不同处理器发行相应的编译器,并且,以前的代码也可在新的处理器 上运行,无需重新编译 )
理解程序的性能: 高性能微处理器并不能持续进行多条指令的发射,原因有:
- 指令间的高度依赖关系限制了指令之间的并行执行,特别是隐含依赖关系的存在。例如,使用指针的代码段 ,使用指针的代码段,存在隐含依赖。
- 分支指令预测错误。
- 内存访问引起的阻塞 (Cache缺失、缺页等)使得流水线难以满负荷运转 。
1.5.3 超流水处理器¶
-
超流水线(super-pipelining)技术,通过增加流水线级数来使更多的指令同时在流水线中重叠执行。将指令的执行过程划分为更多的级数,每个级数处理的工作量较少。这样可以在更高的时钟频率下运行,因为每个阶段的延迟时间减少了,着重于时间上并行
-
增加吞吐量:通过细分级数,可以同时处理更多的指令阶段,提高整体指令吞吐量。
-
超流水线并没有改变CPI的值,CPI还是1,但是,因为理想情况下流水线的加速比与流水段的数目成正比,因此,流水段越多,时钟周期越短,指令吞吐率越高。
为什么CPU流水线设计的级越长,完成一条指令的速度就越快?
假设一条工厂的流水线,需要给零件拧4种不同规格的螺丝,每种螺丝2个,工人拧一个螺丝需要5秒(工人拧螺丝的时候流水线需要暂停等待),流水线移动一格需要5秒。
对比一下流水线分4级,每个工人拧2个同规格的螺丝,和流水线分8级,每个工人拧1个螺丝相比:
- 4级流水:完成一个工件的总时间:(5+5+5)×4=60秒;平均每15秒钟产出一个工件。
- 8级流水:完成一个工件的总时间:(5+5)×8=80秒;平均每10秒钟产出一个工件。
流水线的移动速度并没有加快,但是通过更多级的流水线,可以使得流水线的暂停等待时间更短,从1/15 Hz提高到1/10 Hz,频率更高。
- 超流水线的性能比普通流水线好,但是有以下缺点
- 流水线级数越多,用于流水段寄存器的开销就越大,因而流水线级数是有限制的,不可能无限增加。
- 在超长流水线中,分支预测失败会导致更多的指令需要被清除和重新执行,从而增加延迟和性能损失
- 流水线中的气泡(bubble,空闲周期)会因为依赖关系或资源冲突而导致效率下降。
以上原因,也就是什么Pentium IV具有31级的流水线,指令的执行效率却赶不上只有14级流水线的Pentium M
下图是具有14级流水线的Pentium M处理器,由14个流水段组成的流水线示意图





