第四章 处理器体系结构

处理器体系结构

4.1 Y86-64 指令集体系结构

定义一个指令集体系结构(例如 Y86-64)包括定义各种状态单元、指令集和它们的编码、一组编程规范和异常事件处理。

4.1.1 程序员可见的状态

如图 4-1 所示 Y86-64 程序中的每条指令都会读取或修改处理器状态的某些部分, 有 15 个程序寄存器,寄存 %rsp 被入栈、出栈、调用和返回指令作为栈指针。除此之外,寄存器没有固定的含义或固定值。有 3 个一位的条件码:ZF、SF 和 OF 它们保存着最近的算术或逻辑指令所造成影响的有关信息。程序计数器(PC)存放当前正在执行指令的地址。

内存从概念上来说就是一个很大的字节数组,保存着程序和数据。

程序状态的最后一个部分是状态码 Stat 它表明程序执行的总体状态。


图 4-1 Y86-64 程序员可见状态

4.1.2 Y86-64 指令

图 4-2 给出了 Y86-64 ISA 中各个指令的简单描述。这个指令集就是我们处理器实现的目标。我们只有 8 字节数据,所以称之为“字(word)” 不会有任何歧义。


图 4-2 Y86-64 指令集。指令编码长度从 1 个字节到 10 个字节不等。一条指令含有一个单字节的指令指示符,可能含有一个单字节的寄存器指示符,还可能含有一个 8 字节的常数字。字段 fn 指明是某个整数操作(OPq)、数据传送条件(cmovXX)或是分支条件(jXX)。所有的数值都用十六进制表示

4.1.3 指令编码

每条指令的第一个字节表明指令的类型。这个字节分为两个部分,每部分 4位:高 4 位是代码(code)部分,低 4 位是功能(function)部分。如图 4-2 所示,代码值为 0~0xB 功能值只有在一组相关指令共用一个代码时才有用。图 4-3 给出了整数操作、分支和条件传送指令的具体编码。可以观察到,rrmovq 与条件传送有同样的指令代码。可以把它看作是一个“无条件传送”, 就好像 jmp 指令是无条件跳转一样,它们的功能代码都是 0。


图 4-3 Y86-64 指令集的功能码。这些代码指明是某个整数操作、分支条件还是数据传送条件。这些指令是图 4-2 中所示的 OPq、jxx 和 cmovXX

如图 4-4 所示,15 个程序寄存器中每个都有一个相对应的范围在 0 到 0xE 之间的寄存器标识符(register ID)。Y86-64中的寄存器编号定义跟 x86-64中的相同。程序寄存器存在CPU 中的一个寄存器文件中,这个寄存器文件就是一个小的、以寄存器 ID 作为地址的随机访问存储器。在指令编码中以及在我们的硬件设计中,当需要指明不应访问任何寄存器时,就用 ID 值 OxF 来表示。


图 4-4 Y86-64 程序寄存器标识符。15 个程序寄存器中每个都有一个相对应的标识符(ID), 范围为0 OxE 如果指令中某个寄存器字段的 ID 值为 OxF, 就表明此处没有寄存器操作数

4.1.4


图 4-5 Y86-64 状态码。在我们的设计中,任何 AOK 以外的代码都会使处理器停止

4.1.5

图 4-6 给出了下面这个 C 函数的 x86-64 和 Y86-64 汇编代码:

1
2
3
4
5
6
7
8
9
long sum(long *start, long count){
long sum = 0;
while (count) {
sum += *start;
start++;
count--;
}
return sum;
}


图 4_6 Y86-64 汇编程序与 x86-64 汇编程序比较。Sum 函数计算一个整数数组的和。Y86-64 代码与 x86-64 代码遵循了相同的通用模式


图 4-7 用 Y86-64 汇编代码编写的一个例子程序。调用 函数来计算一个具有 4 个元素的数组的和

4.1.6 一些 Y86-64 指令的详情

大多数 Y86-64 指令是以一种直接明了的方式修改程序状态的,所以定义每条指令想要达到的结果并不困难。不过,两个特别的指令的组合需要特别注意一下。pmshq 指令会把栈指针减 8, 并且将一个寄存器值写人内存中。因此,当执行 pushq %rsp 指令时,处理器的行为是不确定的,因为要人栈的寄存器会被同一条指令修改。通常有两种不同的约定 1)压人%rsp 的原始值,2)压人减去 8的 %rsp 的值。

4.2 逻辑设计和硬件控制语言 HCL

在硬件设计中,用电子电路来计算对位进行运算的函数,以及在各种存储器单元中存储位。大多数现代电路技术都是用信号线上的髙电压或低电压来表示不同的位值。在当前的技术中,逻辑 1 是用 1.0 伏特左右的高电压表示的,而逻辑 0 是用 0.0 伏特左右低电压表示的。要实现一个数字系统需要三个主要的组成部分:计算对位进行操作的函数的组合逻辑、存储位的存储器单元,以及控制存储器单元更新的时钟信号。

4.2.1 逻辑门

逻辑门是数字电路的基本计算单元。它们产生的输出,等于它们输人位值的某个布尔函数。图 4-9 是布尔函数 AND、OR 和 NOT 的标准符号,C 语言中运算符(2.1.8 节)的逻辑门下面是对应的 HCL 表达式:AND 用 && 表示,OR 用||表示,而 NOT 用!表示。

逻辑门总是活动的(active), —旦一个门的输人变化了,在很短的时间内,输出就会相应地变化。


图 4-9 逻辑门类型。每个门产生的输出等于它输人的某个布尔函数

4.2.2 组合电路和 HCL 布尔表达式

将很多的逻辑门组合成一个网,就能构建计算块(computational block), 称为组合电路(combinational circuits)。如何构建这些网有几个限制:

  • 每个逻辑门的输入必须连接到下述选项之一:1)一个系统输入(称为主输人), 2)某个存储器单元的输出,3)某个逻辑门的输出。
  • 两个或多个逻辑门的输出不能连接在一起。否则它们可能会使线上的信号矛盾,可能会导致一个不合法的电压或电路故障。
  • 这个网必须是无环的。也就是在网中不能有路径经过一系列的门而形成一个回路,
    这样的回路会导致该网络计算的函数有歧义。

4.2.3 字级的组合电路和 HCL 整数表达式

通过将逻辑门组合成大的网,可以构造出能计算更加复杂函数的组合电路。通常,我们设计能对数据字(word)进行操作的电路。有一些位级信号,代表一个整数或一些控制模式。例如,我们的处理器设计将包含有很多字,字的大小的范围为 4 位到 64 位,代表整数、地址、指令代码和寄存器标识符。

执行字级计算的组合电路根据输入字的各个位,用逻辑门来计算输出字的各个位。例如
图 4-12 中的一个组合电路,它测试两个 64 位字 A 和 B 是否相等。也就是,当且仅当 A 的每一位都和 B 的相应位相等时,输出才为 1。这个电路是用 64 个图 4-10 中所示的单个相等电路实现的。这些单个位电路的输出用一个 AND门连起来,形成了这个电路的输出。


图 4-12 字级相等测试电路。当字 A 的每一位与字 B 中相应的位均相等时,输出等于 1。字级相等是 HCL 中的一个操作

在 HCL 中,我们将所有字级的信号都声明为 int,不指定字的大小。这样做是为了
简单。在全功能的硬件描述语言中,每个字都可以声明为有特定的位数。HCL 允许比较
字是否相等,因此图 4-12 所示的电路的函数可以在字级上表达成
bool Eq = (A == B) ;
这里参数 A 和 B 是 int 型的。注意我们使用和 C 语言中一样的语法习惯,‘=’ 表示赋
值,而‘==’ 是相等运算符。

4.2.4 集合关系

在处理器设计中,很多时候都需要将一个信号与许多可能匹配的信号做比较,以此来检测正在处理的某个指令代码是否属于某一类指令代码。

4.2.5 存储器和时钟

组合电路从本质上讲,不存储任何信息。相反,它们只是简单地响应输人信号,产生等
于输人的某个函数的输出。为了产生时序电路(sequential circuit), 也就是有状态并且在这个状态上进行计算的系统,我们必须引入按位存储信息的设备。存储设备都是由同一个时钟控制的,时钟是一个周期性信号,决定什么时候要把新值加载到设备中。考虑两类存储器设备:

  • 时钟寄存器(简称寄存器)存储单个位或字。时钟信号控制寄存器加载输入值。
  • 随机访问存储器(简称内存)存储多个字,用地址来选择该读或该写哪个字。随机访问存储器的例子包括:1)处理器的虚拟内存系统,硬件和操作系统软件结合起来使处理器可以 在一个很大的地址空间内访问任意的字;2)寄存器文件,在此,寄存器标识符作为地址。在 IA32 或 Y86-64 处理器中,寄存器文件有 15 个程序寄存器(%rax~%rl4)。

正如我们看到的那样,在说到硬件和机器级编程时,“寄存器”这个词是两个有细微差别的事情。在硬件中,寄存器直接将它的输人和输出线连接到电路的其他部分。在机器级编程中,寄存器代表的是 CPU 中为数不多的可寻址的字,这里的地址是寄存器 ID。这些字通常都存在寄存器文件中,虽然我们会看到硬件有时可以直接将一个字从一个指令传送到另一个指令,以避免先写寄存器文件再读出来的延迟。需要避免歧义时,我们会分别称呼这两类寄存器为“硬件寄存器”和“程序寄存器”。

图 4-16 更详细地说明了一个硬件寄存器以及它是如何工作的。大多数时候,寄存器都保持在稳定状态(用 x 表示), 产生的输出等于它的当前状态。信号沿着寄存器前面的组合逻辑传播,这时,产生了一个新的寄存器输人(用 y 表示), 但只要时钟是低电位的,寄存器的输出就仍然保持不变。当时钟变成高电位的时候,输人信号就加载到寄存器中,成为下一个状态 y。 直到下一个时钟上升沿,这个状态就一直是寄存器的新输出。关键是寄存器是作为电路不同部分中的组合逻辑之间的屏障。每当每个时钟到达上升沿时,值才会从寄存器的输人传送到输出a 我们的 Y86-64 处理器会用时钟寄存器保存程序计数器(PC)、条件代码(CC)和程序状态(Stat)。


图 4-16 寄存器操作。寄存器输出会一直保持在当前寄存器状态上,直到时钟信号
上升。当时钟上升时,寄存器输入上的值会成为新的寄存器状态

下面的图展示了一个典型的寄存器文件:

寄存器文件有两个读端口(A 和 B), 还有一个写端口(W)。这样一个多端口随机访问存储器允许同时进行多个读和写操作。图中所示的寄存器文件中,电路可以读两个程序寄存器的值,同时更新第三个寄存器的状态,每个端口都有一个地址输人,表明该选择哪个程序寄存器,另外还有一个数据输出或对应该程序寄存器的输入值。地址是用图 4-4 中编码表,示的寄存器标识符。两个读端口有地址输人 sreA 和 srcB(“source A”和 “source B”的缩写)和数据输出 valA 和 valB(“valueA” 和“valueB”的缩写)。 写端口有地址输人dstW( “destination W”的缩写), 以及数据输人 vaiw( “value W”的缩写)。

虽然寄存器文件不是组合电路,因为它有内部存储。不过,在我们的实现中,从寄存器文件读数据就好像它是一个以地址为输人、数据为输出的一个组合逻辑块。当 srcA 或srcB 被设成某个寄存器 ID 时,在一段延迟之后,存储在相应程序寄存器的值就会出现在valA 或 valB 上。例如,将 srcA设为 3, 就会读出程序寄存器%rbx 的值,然后这个值就会出现在输出 valA 上。

向寄存器文件写入字是由时钟信号控制的,控制方式类似于将值加载到时钟寄存器。每次时钟上升时,输人 valW 上的值会被写人输入 dstW 上的寄存器 ID 指示的程序寄存器。当dstW 设为特殊的 ID 值 OxF 时,不会写任何程序寄存器。由于寄存器文件既可以读也可以写,一个很自然的问题就是“如果我们试图同时读和写同一个寄存器会发生什么?”答案简单明了:如果更新一个寄存器,同时在读端口上用同一个寄存器 ID,我们会看到一个从旧值到新值的变化。当我们把这个寄存器文件加入到处理器设计中,我们保证会考虑到这个属的。

处理器有一个随机访问存储器来存储程序数据,如下图所示:

这个内存有一个地址输人,一个写的数据输入,以及一个读的数据输出。同寄存器文件一样,从内存中读的操作方式类似于组合逻辑:如果我们在输入 address 上提供一个地址,
并将 write 控制信号设置为 0, 那么在经过一些延迟之后,存储在那个地址上的值会出现在输出 data 上。如果地址超出了范围,error 信号会设置为 1,否则就设置为 0。写内存是由时钟控制的:我们将 address 设置为期望的地址,将 data in 设置为期望的值,而 write 设置为 1。然后当我们控制时钟时,只要地址是合法的,就会更新内存中指定的位置。对于读操作来说,如果地址是不合法的,error 信号会被设置为 1。这个信号是由组合逻辑产生的,因为所需要的边界检查纯粹就是地址输人的函数,不涉及保存任何状态。

4.3 Y86-64 的顺序实现

现在已经有了实现 Y86-64 处理器所需要的部件。首先,我们描述一个称为 SEQ(“se-quemial” 顺序的)的处理器。每个时钟周期上,SEQ 执行处理一条完整指令所需的所有步骤。不过,这需要一个很长的时钟周期时间,因此时钟周期频率会低到不可接受。我们开发 SEQ 的目标就是提供实现最终目的的第一步,我们的最终目的是实现一个高效的、流水线化的处理器。

4.3.1 将处理组织成阶段

通常,处理一条指令包括很多操作。将它们组织成某个特殊的阶段序列,即使指令的动作差异很大,但所有的指令都遵循统一的序列。每一步的具体处理取决于正在执行的指令。创建这样一个框架,我们就能够设计一个充分利用硬件的处理器。下面是关于各个阶段以及各阶段内执行操作的简略描述:

  • 取指(fetch): 取指阶段从内存读取指令字节,地址为程序计数器(PC)的值。从指令中抽取出指令指示符字节的两个四位部分,称为 icode(指令代码)和 ifun(指令功能)。 它可能取出一个寄存器指示符字节,指明一个或两个寄存器操作数指示符rA 和 rB。它还可能取出一个四字节常数字 valC。它按顺序方式计算当前指令的下一条指令的地址 valP。也就是说,valP 等于 PC 的值加上已取出指令的长度。
  • 译码(decode): 译码阶段从寄存器文件读人最多两个操作数,得到值 valA 和/或 valB。通常,它读人指令 rA 和 rB 字段指明的寄存器,不过有些指令是读寄存器%rsp 的。
  • 执行(execute): 在执行阶段,算术/逻辑单元(ALU)要么执行指令指明的操作(根据 ifun 的值), 计算内存引用的有效地址,要么增加或减少栈指针。得到的值我们称为 valE。在此,也可能设置条件码。对一条条件传送指令来说,这个阶段会检验条件码和传送条件(由 ifun 给出), 如果条件成立,则更新目标寄存器。同样,对一条跳转指令来说,这个阶段会决定是不是应该选择分支。
  • 访存(memory): 访存阶段可以将数据写入内存,或者从内存读出数据。读出的值为 valM。
  • 写回(write back): 写回阶段最多可以写两个结果到寄存器文件。
  • 更新 PC(PC update): 将 PC 设置成下一条指令的地址。

处理器无限循环,执行这些阶段。在我们简化的实现中,发生任何异常时,处理器就会停止:它执行 halt 指令或非法指令,或它试图读或者写非法地址。在更完整的设计中,处理器会进人异常处理模式,开始执行由异常的类型决定的特殊代码。

我们面临的一个挑战是将每条不同指令所需要的计算放人到上述那个通用框架中。我
们会使用图 4-17 中所示的代码来描述不同 Y86-64 指令的处理。


图 4-17 Y86-64 指令序列示例。我们会跟踪这些指令通过各个阶段的处理

图 4-18 给出了对 OPq(整数和逻辑运算)、 rrmovq(寄存器-寄存器传送)和 irmovq(立即数-寄存器传送)类型的指令所需的处理。


图 4-18 Y86-64 指令 OPq、rrmovq 和 irmovq 在顺序实现中的计算。这些指令计算了一个值,并将结果存放在寄存器中。符号 icode:ifun 表明指令字节的两个组成部分,而 rA:rB 表明寄存器指示符字节的两个组成部分。符号 Mj[x]表示访问(读或者写)内存位置 x 处的一个字节,而M8[x]表示访问八个字节

整数操作指令的处理遵循上面列出的通用模式。在取指阶段,我们不需要常数字,所以 valP 就计算为 PC+2,在译码阶段,我们要读两个操作数。在执行阶段,它们和功能指示符 ifun —起再提供给 ALU,这样一来 valE 就成为了指令结果。这个计算是用表达式 valB OP valA 来表达的,这里 0P 代表 ifun 指定的操作。要注意两个参数的顺序–这个顺序与 Y86-64C和 X86-64)的习惯是一致的。例如,指令 subq%rax, %rdx 计算的是R[%rdx]-R[%rax]的值。这些指令在访存阶段什么也不做,而在写回阶段,valE 被写人寄存器 rB, 然后 PC 设为 valP, 整个指令的执行就结束了。

4.3.1 SEQ 硬件结构

实现所有 Y86-64 指令所需要的计算可以被组织成 6 个基本阶段:取指、译码、执行、访存、写回和更新 PC,图 4-22 给出了一个能执行这些计算的硬件结构的抽象表示。


图 4-22 SEQ的抽象视图,一种顺序实现。指令执行过程中的信息处理沿着顺时针方向的流程进行,从用程序计数器(PC)取指令开始,如图中左下角所示

硬件单元与各个处理阶段相关联:

  • 取指:将程序计数器寄存器作为地址,指令内存读取指令的字节。PC 增加器(PCincre-menter)计算 valP,即增加了的程序计数器。
  • 译码:寄存器文件有两个读端口 A 和 B,从这两个端口同时读寄存器值 valA 和 valB。
  • 执行:执行阶段会根据指令的类型,将算术/逻辑单元(ALU)用于不同的目的。对整数操作,它要执行指令所指定的运算。对其他指令,它会作为一个加法器来计算增加或减少栈指针,或者计算有效地址,或者只是简单地加 0, 将一个输入传递到输出。
    条件码寄存器(CC)有三个条件码位。ALU 负责计算条件码的新值。当执行条件传送
    指令时,根据条件码和传送条件来计算决定是否更新目标寄存器。同样,当执行一条跳转
    指令时,会根据条件码和跳转类型来计算分支信号 Cnd。
  • 访存:在执行访存操作时,数据内存读出或写人一个内存字。指令和数据内存访问的是相同的内存位置,但是用于不同的目的。
  • 写回:寄存器文件有两个写端口。端口 E 用来写 ALU 计算出来的值,而端口 M 用来写从数据内存中读出的值。
  • PC 更新:程序计数器的新值选择自:valP,下一条指令的地址;vaic,调用指令或跳转指令指定的目标地址;valM, 从内存读取的返回地址。

图 4-23 更详细地给出了实现 SEQ 所需要的硬件


图 4-23 SEQ的硬件结构,一种顺序实现。有些控制信号以及寄存器和控制字连接没有画出来

4. 3. 3 SEQ 的时序

原则:从不回读
处理器从来不需要为 了完成一条指令的执行而去读由该指令更新了的状态。

4.3.4 SEQ 阶段的实现

我们没有讲的那部分 SEQ 的 HCL 描述,是不同整数和布尔信号的定义,它们可以作为 HCL 操作的参数。其中包括不同硬件信号的名字,以及不同指令代码、功能码、寄存器名字、ALIJ 操作和状态码的常数值。只列出了那些在控制逻辑中必须被显式引用的常数。图 4-26 列出了我们使用的常数。按照习惯,常数值都是大写的。


图 4-26 HCL 描述中使用的常数值。这些值表示的是指令、功能码、寄存器 ID、ALU 操作和状态码的编码

4.4 流水线的通用原理

4.4.1 计算流水线

图 4-32a 给出了一个很简单的非流水线化的硬件系统例子。在这个例子中,我们假设组合逻辑需要 300ps,而加载寄存器需要20ps,图 4-32 还给出了一种时序图,称为流水线图(pipeline diagram),在图中,时从左向右流动。


图 4-32 非流水线化的计算硬件。每个 320ps 的周期内,系统用300ps 计算组合逻辑函数,20ps 将结果存到输出寄存器中

假设将系统执行的计算分成三个阶段(A、B 和 C), 每个阶段需要 lOOps, 如图 4-33 所示。然后在各个阶段之间放上流水线寄存器(pipeline register), 这样每条指令都会照三步经过这个系统,从头到尾需要三个完整的时钟周期。如图 4-33 中的流水线图所示,只要 II 从 A 进入 B, 就可以让 12 进人阶段 A 了,依此类推。在稳定状态下,三个阶都应该是活动的,每个时钟周期,一条指令离开系统,一条新的进人。从流水线图中第三个时
钟周期就能看出这一点,此时,II 是在阶段 C,I2在阶段B,而I3 是在阶段 A,在这个系统中,我们将时钟周期设为 100+20=120ps得到的吞吐量大约为 8.33GIPS,因为处理一条指令需要 3 个时钟周期,所以这条流水线的延迟就是 3 * 120=360ps。

图 4-33 三阶段流水线化的计算硬件。计算被划分为三个阶段 A、B 和 C。每经过一个 120ps 的周期,每条指令就行进通过一个阶段

4.4.2 流水线操作的详细说明

为了更好地理解流水线是怎样工作的,让我们来详细看看流水线计算的时序和操作。图 4-34 给出了前面我们看到过的三阶段流水线(图 4-33)的流水线图。就像流水线图上方指明的那样,流水线阶段之间的指令转移是由时钟信号来控制的。每隔 120ps,信号从 0 上升至 1,开始下一组流水线阶段的计算。


图 4-34 三阶段流水线的时序。时钟信号的上.升锻控制指令从一个流水线阶段移动到下一个阶段

4.4.3 流水线的局限性

图 4-33 的例子给出了一个理想的流水线化的系统,在这个系统中,我们可以将计算分成三个相互独立的阶段,每个阶段需要的时间是原来逻辑需要时间的三分之一。不幸的是,会出现其他一些因素,降低流水线的效率。

  1. 不一致的划分
  2. 流水线过深,收益反而下降

4.4.4 带反馈的流水线系统

4.5 Y86-64 的流水线实现

4.5.1 SEQ+: 重新安排计算阶段

作为实现流水线化设计的一个过渡步骤,我们必须稍微调整一下 SEQ 中五个阶段的顺序,使得更新 PC 阶段在一个时钟周期开始时执行,而不是结束时才执行。只需要对整体硬件结构做最小的改动,对于流水线阶段中的活动的时序,它能工作得更好。我们称这种修改过的设计为”SEQ+”。

图 4-40 给出了 SEQ+硬件的一个更为详细的说明《 可以看到,其中的硬件单元和控制块与我们在 SEQ 中用到的(图 443)—样,只不过 PC 逻辑从上面(在时钟周期结束时活动)移到了下面(在时钟周期开始时活动)。


图 4-40 SEQ+的硬件结构。将 PC 计算从时钟周期结束时移到了开始时,使之更适合于流水线

4.5.5 流水线冒险

这些相关有两种形式:1)数据相关,下一条指令会用到这一条指令计算出的结果;2)控制相关,一条指令要确定下一条指令的位置,例如在执行跳转、调用或返回指令时。这些相关可能会导致流水线产生计算错误,称为冒险(hazard)。同相关一样,冒险也可以分为两类:数据冒险(data hazard)和控制险(control hazard)。

  1. 用暂停来避免数据冒险
    暂停(stalling)是避免冒险的一种常用技术,暂停时,处理器会停止流水线中一条或多条指令,直到冒险条件不再满足。让一条指令停顿在译码阶段,直到产生它的源操作数的指令通过了写回阶段,这样我们的处理器就能避免数据冒险。

  2. 用转发来避免数据冒险
    PIPE-的设计是在译码阶段从寄存器文件中读入源操作数,但是对这些源寄存器的写有可能要在写回阶段才能进行。与其暂停直到写完成,不如简单地将要写的值传到流水线寄存器 E 作为源操作数。

  3. 加载/使用数据冒险
    我们可以将暂停和转发结合起来,避免加载/使用数据冒险。

对控制冒险的讨论表明,通过慎重考虑流水线的控制逻辑,控制冒险是可以被处理的。在出现特殊情况时,暂停和往流水线中插人气泡的技术可以动态调整流水线的流程。

4.5.6 异常处理

4.5.7 PIPE 各阶段的实现

4.5.8 流水线控制逻辑

现在准备创建流水线控制逻辑,完成我们的 PIPE 设计。这个逻辑必须处理下面 4 种控制情况,这些情况是其他机制(例如数据转发和分支预测)不能处理的:

  • 加载/使用冒险:在一条从内存中读出一个值的指令和一条使用该值的指令之间,流水线必须暂停一个周期。处理 ret: 流水线必须暂停直到 ret 指令到达写回阶段。
  • 预测错误的分支:在分支逻辑发现不应该选择分支之前,分支目标处的几条指令已经进人流水线了。必须取消这些指令,并从跳转指令后面的那条指令开始取指。
  • 异常:当一条指令导致异常,我们想要禁止后面的指令更新程序员可见的状态,并且在异常指令到达写回阶段时,停止执行。
  1. 特殊控制情况所期望的处理
  2. 发现特殊控制条件
  3. 流水线控制机制
  4. 控制条件的组合
  5. 控制逻辑实现

4.5.9 性能分析

4.5.10未完成的工作

  1. 多周期指令
  2. 与存储系统的接口