深入理解计算机系统(8)_处理器体系结构

深入理解计算机系统系列文章目录

第一章 计算机的基本组成
1. 内容概述
2. 计算机基本组成

第二章 计算机的指令和运算
3. 计算机指令
4. 程序的机器级表示
5. 计算机运算
6. 信息表示与处理

第三章 处理器设计
7. CPU
8. 处理器体系结构
9. 其他处理器

第四章 存储器和IO系统
9. 存储器的层次结构
10. 存储器和I/O系统



前言

一个处理器支持的指令和指令的字节级编码称为它的指令集体系结构(Instruction-SetArchitecture,ISA)。

ISA在编译器编写者和处理器设计人员之间提供了一个概念抽象层,编译器编写者只需要知道允许哪些指令,以及它们是如何编码的;
而处理器设计者必须建造出执行这些指令的处理器。

与一个时刻只执行一条指令相比,通过同时处理多条指令的不同部分,处理器可以获得更高的性能。


参考资料

《深入理解计算机系统》
《深入浅出计算机组成原理》


一、设计指令集

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

1. 组成

在这里插入图片描述

15个程序寄存器(寄存器%rsp被入栈、出栈、调用和返回指令作为栈指针。除此之外,寄存器没有固定的含义或固定值。)
3个一位的条件码:ZF、SF和OF+
程序计数器(PC)存放当前正在执行指令的地址。
Y86-64程序用虚拟地址来引用内存位置。
程序状态的最后一个部分是状态码Stat,它表明程序执行的总体状态

2. 指令集

4个movq(irmovq、rrmovq、mrmovq和rmmovq)分别显式地指明源和目的的格式,立即数(立、寄存器®或内存(m)。
–> 不允许从一个内存地址直接传送到另一个内存地址。
–> 另外,也不允许将立即数传送到内存。
4个整数操作指令,addq、subq、andq和xorq。
–> 只对寄存器数据进行操作,设置3个条件码ZF、SF和OF(零、符号和溢出)。
7个跳转指令(图4-2中的]XX)是jrnp、jle、jl、je、jne、jge和jg
6个条件传送指令(图4-2中的crnovXX):crnovle、crnovl、crnove、crnovne、crnovge和crnovg。
call指令将返回地址入栈,然后跳到目的地址。ret指令从这样的调用中返回。
pushq和popq指令实现了入栈和出栈
halt指令停止指令的执行,导致处理器停止,并将状态码设置为HLT

3. 指令编码

每条指令的第一个字节表明指令的类型。
高4位是代码(code)部分,低4位是功能(function)部分。
功能值只有在一组相关指令共用一个代码时才有用。

指令集的一个重要性质就是字节编码必须有唯一的解释。
任意一个字节序列要么是一个唯一的指令序列的编码,要么就不是一个合法的字节序列。
Y86-64就具有这个性质,因为每条指令的第一个字节有唯一的代码和功能组合,
给定这个字节,我们就可以决定所有其他附加字节的长度和含义。
这个性质保证了处理器可以无二义性地执行目标代码程序。

对于Y86-64,当遇到这些异常的时候,我们就简单地让处理器停止执行指令。
在这里插入图片描述
在更完整的设计中,处理器通常会调用一个异常处理程序(exceptionhandler),这个过程被指定用来处理遇到的某种类型的异常。

pushq指令会把栈指针减8,并且将一个寄存器值写入内存中。
因此,当执行pushq%rsp指令时,处理器的行为是不确定的,因为要入栈的寄存器会被同一条指令修改。
通常有两种不同的约定:
1)压入%rsp的原始值,
2)压入减去8的%rsp的值。

二、数字硬件设计

大多数现代电路技术都是用信号线上的高电压或低电压来表示不同的位值。
在当前的技术中,逻辑1是用1.0伏特左右的高电压表示的,而逻辑0是用o.0伏特左右的低电压表示的。
要实现一个数字系统需要三个主要的组成部分:

  1. 计算对位进行操作的函数的组合逻辑、
  2. 存储位的存储器单元,
  3. 以及控制存储器单元更新的时钟信号

1. 组合电路(combinationalcircuits)

逻辑门只对单个位的数进行操作,而不是整个字
逻辑门总是活动的(active)。一旦一个门的输入变化了,在很短的时间内,输出就会相应地变化
图

如何构建这些网有几个限制:

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

用HCL来写这个网的函数就是:

bool eq = (a && b) || (!a && !b);

HCL表达式很清楚地表明了组合逻辑电路和C语言中逻辑表达式的对应之处。
它们都是用布尔操作来对输入进行计算的函数。
值得注意的是,这两种表达计算的方法之间有以下区别:

  • 因为组合电路是由一系列的逻辑门组成,它的属性是输出会持续地响应输人的变化。
    如果电路的输入变化了,在一定的延迟之后,输出也会相应地变化。
    相比之下,C表达式只会在程序执行过程中被遇到时才进行求值。
  • C的逻辑表达式允许参数是任意整数,0表示FALSE,其他任何值都表示TRUE。
    而逻辑门只对位值0和1进行操作。
  • C的逻辑表达式有个属性就是它们可能只被部分求值。
    如果一个AND或OR操作的结果只用对第一个参数求值就能确定,那么就不会对第二个参数求值了。
    例如下面的C表达式:
    (a && !a) && func(b, c)
    
    这里函数func是不会被调用的,因为表达式(a&&!a)求值为0。
    而组合逻辑没有部分求值这条规则,逻辑门只是简单地响应输入的变化。

在这里插入图片描述
算术/逻辑单元(ALU)是一种很重要的组合电路,图4-15是它的一个抽象的图示。
这个电路有三个输入:标号为A和B的两个数据输入,以及一个控制输入。
根据控制输入的设置,电路会对数据输人执行不同的算术或逻辑操作。
可以看到,这个ALU中画的四个操作对应千Y86-64指令集支持的四种不同的整数操作,而控制值和这些操作的功能码相对应(图4-3)。
我们还注意到减法的操作数顺序,是输入B减去输入A。
之所以这样做,是为了使这个顺序与subq指令的参数顺序一致

2. 存储器和时钟

组合电路从本质上讲,不存储任何信息。
相反,它们只是简单地响应输入信号,产生等千输入的某个函数的输出。
为了产生时序电路(sequentialcircuit),也就是有状态并且在这个状态上进行计算的系统,
我们必须引入按位存储信息的设备。

存储设备都是由同一个时钟控制的,时钟是一个周期性信号,决定什么时候要把新值加载到设备中。
考虑两类存储器设备:

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

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

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

下面的图展示了一个典型的寄存器文件:
在这里插入图片描述
寄存器文件有两个读端口(A和B),还有一个写端口(W)。
这样一个多端口随机访问存储器允许同时进行多个读和写操作。
图中所示的寄存器文件中,电路可以读两个程序寄存器的值,同时更新第三个寄存器的状态。
每个端口都有一个地址输入,表明该选择哪个程序寄存器,另外还有一个数据输出或对应该程序寄存器的输入值。
地址是用图4-4中编码表示的寄存器标识符。
两个读端口有地址输入srcA和srcB("sourceA"和“sourceB"的缩写)和数据输出valA和valB("valueA"和“valueB"的缩写)。
写端口有地址输入dstW("destinationW"的缩写),以及数据输入valW("valueW"的缩写)。
虽然寄存器文件不是组合电路,因为它有内部存储。
不过,在我们的实现中,从寄存器文件读数据就好像它是一个以地址为输入、数据为输出的一个组合逻辑块。
当srcA或srcB被设成某个寄存器ID时,在一段延迟之后,存储在相应程序寄存器的值就会出现在valA或valB上。
例如,将srcA设为3’就会读出程序寄存器%rbx的值,然后这个值就会出现在输出valA上。
向寄存器文件写入字是由时钟信号控制的,控制方式类似于将值加载到时钟寄存器。
每次时钟上升时,输入valW上的值会被写入输入dstW上的寄存器ID指示的程序寄存器。
当dstW设为特殊的ID值OxF时,不会写任何程序寄存器。由于寄存器文件既可以读也可以写,一个很自然的问题就是“如果我们试图同时读和写同一个寄存器会发生什么?"
答案简单明了:如果更新一个寄存器,同时在读端口上用同一个寄存器ID,我们会看到一个从旧值到新值的变化。
当我们把这个寄存器文件加入到处理器设计中,我们保证会考虑到这个属性的。
处理器有一个随机访问存储器来存储程序数据,如下图所示:
在这里插入图片描述
这个内存有一个地址输入,一个写的数据输入,以及一个读的数据输出。
同寄存器文件一样,从内存中读的操作方式类似于组合逻辑:
如果我们在输入address上提供一个地址,并将write控制信号设置为0,
那么在经过一些延迟之后,存储在那个地址上的值会出现在输出data上。
如果地址超出了范围,error信号会设置为1,否则就设置为0。
写内存是由时钟控制的:我们将address设置为期望的地址,将datain设置为期望的值,而write设置为1。
然后当我们控制时钟时,只要地址是合法的,就会更新内存中指定的位置。
对千读操作来说,如果地址是不合法的,error信号会被设置为1。
这个信号是由组合逻辑产生的,因为所需要的边界检查纯粹就是地址输入的函数,不涉及保存任何状态。

三、顺序操作

每个时钟周期执行一条指令
SEQ("sequential"顺序的)处理器。
每个时钟周期上,SEQ执行处理一条完整指令所需的所有步骤。
不过,这需要一个很长的时钟周期时间,因此时钟周期频率会低到不可接受。
我们开发SEQ的目标就是提供实现最终目的的第一步,我们的最终目的是实现一个高效的、流水线化的处理器

1. 具体阶段

通常,处理一条指令包括很多操作。
将它们组织成某个特殊的阶段序列,即使指令的动作差异很大,但所有的指令都遵循统一的序列。
每一步的具体处理取决于正在执行的指令。
下面是关千各个阶段以及各阶段内执行操作的简略描述:

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

执行一条指令是需要进行很多处理的。
我们不仅必须执行指令所表明的操作,还必须计算地址、更新栈指针,以及确定下一条指令的地址。
降低复杂度的一种方法是让不同的指令共享尽量多的硬件。
我们面临的一个挑战是将每条不同指令所需要的计算放入到上述那个通用框架中。

2. SEQ硬件结构

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

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

3. SEQ的时序

SEQ的实现包括组合逻辑和两种存储器设备:
时钟寄存器(程序计数器和条件码寄存器),随机访问存储器(寄存器文件、指令内存和数据内存)。
组合逻辑不需要任何时序或控制只要输入变化了,值就通过逻辑门网络传播。
正如提到过的那样,我们也将读随机访问存储器看成和组合逻辑一样的操作,根据地址输入产生输出字。
对于较小的存储器来说(例如寄存器文件),这是一个合理的假设,
而对于较大的电路来说,可以用特殊的时钟电路来模拟这个效果。
由千指令内存只用来读指令,因此我们可以将这个单元看成是组合逻辑。
现在还剩四个硬件单元需要对它们的时序进行明确的控制程序计数器、条件码寄存器、数据内存和寄存器文件。
这些单元通过一个时钟信号来控制,它触发将新值装载到寄存器以及将值写到随机访间存储器。
每个时钟周期,程序计数器都会装载新的指令地址。
只有在执行整数运算指令时,才会装载条件码寄存器。
只有在执行rmrnovq、pushq或call指令时,才会写数据内存。
寄存器文件的两个写端口允许每个时钟周期更新两个程序寄存器,不过我们可以用特殊的寄存器IDOxF作为端口地址,来表明在此端口不应该执行写操作。
要控制处理器中活动的时序,只需要寄存器和内存的时钟控制。
硬件获得了如图4-18~图4-21的表中所示的那些赋值顺序执行一样的效果,即使所有的状态更新实际上同时发生,且只在时钟上升开始下一个周期时。
之所以能保持这样的等价性,是由于Y86-64指令集的本质,因为我们遵循以下原则组织计算:
原则:从不回读
处理器从来不需要为了完成一条指令的执行而去读由该指令更新了的状态。
这条原则对实现的成功来说至关重要。
为了说明问题,假设我们对pushq指令的实现是先将%rsp减8’再将更新后的%rsp值作为写操作的地址。
这种方法同前面所说的那个原则柜违背。
为了执行内存操作,它需要先从寄存器文件中读更新过的栈指针。
然而,我们的实现(图4-20)产生出减后的栈指针值,作为信号valE,然后再用这个信号既作为寄存器写的数据,也作为内存写的地址。
因此,在时钟上升开始下一个周期时,处理器就可以同时执行寄存器写和内存写了。

四、流水线化的处理器

每条指令分为5步,每个步骤由一个独立的硬件部分或阶段(stage)来处理。
每个时钟周期有一条新指令进入流水线
处理器可以同时执行五条指令的不同阶段。
冒险就是一条指令的位置或操作数依赖于其他仍在流水线中的指令。

在流水线化的系统中,待执行的任务被划分成了若干个独立的阶段。
流水线化的一个重要特性就是提高了系统的吞吐量(throughput),也就是单位时间内服务的顾客总数,不过它也会轻微地增加延迟Clatency),也就是服务一个用户所需要的时间。
例如,自助餐厅里的一个只需要甜点的顾客,能很快通过一个非流水线化的系统,只在甜点阶段停留。
但是在流水线化的系统中,这个顾客如果试图直接去甜点阶段就有可能招致其他顾客的愤怒了 。

1. 流水线操作

在这里插入图片描述
让我们把注意力放到计算流水线上来,这里的“顾客”就是指令,每个阶段完成指令执行的一部分。
图4-32a给出了一个很简单的非流水线化的硬件系统例子。它是由一些执行计算的逻辑以及一个保存计算结果的寄存器组成的。
时钟信号控制在每个特定的时间间隔加载寄存器。

在现代逻辑设计中,电路延迟以微微秒或皮秒(picosecond,简写成“ps”),也就是10-12秒为单位来计算。
在这个例子中,我们假设组合逻辑需要300ps,而加载寄存器需要20ps。

图4-32还给出了一种时序图,称为流水线图(pipelinediagram)。
在图中,时间从左向右流动。从上到下写着一组操作(在此称为I1、I2和I3)。
实心的长方形表示这些指令执行的时间。
这个实现中,在开始下一条指令之前必须完成前一个。
因此,这些方框在垂直方向上并没有相互重叠。
下面这个公式给出了运行这个系统的最大吞吐量:
公示图

tu4-33

在稳定状态下,三个阶段都应该是活动的,每个时钟周期,一条指令离开系统,一条新的进入。
代价是增加了一些硬件,以及延迟的少量增加(360/320=1.12)。
延迟变大是由于增加的流水线寄存器的时间开销。

流水线阶段之间的指令转移是由时钟信号来控制的。
在这里插入图片描述
每隔120ps,信号从0上升至1,开始下一组流水线阶段的计算从这个对流水线操作详细的描述中,我们可以看到减缓时钟不会影响流水线的行为。
信号传播到流水线寄存器的输入,但是直到时钟上升时才会改变寄存器的状态。
另一方面,如果时钟运行得太快,就会有灾难性的后果。值可能会来不及通过组合逻辑,因此当时钟上升时,寄存器的输入还不是合法的值。

2. 局限性

  1. 不一致的划分
    在这里插入图片描述
    运行时钟的速率是由最慢的阶段的延迟限制的。
    系统的吞吐量受最慢阶段的速度所限制

  2. 流水线过深,收益反而下降
    在这里插入图片描述
    虽然我们将每个计算时钟的时间缩短了两倍,但是由于通过流水线寄存器的延迟,吞吐量并没有加倍。

3. 带反馈的流水线系统

每对相邻的指令之间都有数据相关
图4-38
当我们将流水线技术引入Y86-64处理器时,必须正确处理反馈的影响。很明显,像图4-38中的例子那样改变系统的行为是不可接收的。
我们必须以某种方式来处理指令间的数据和控制相关,以使得到的行为与ISA定义的模型相符。

五、水线实现

1. 水线冒险

  1. 用暂停来避免数据冒险
  2. 用转发来避免数据冒险
    PIPE-的设计是在译码阶段从寄存器文件中读入源操作数,但是对这些源寄存器的写有可能要在写回阶段才能进行。
    与其暂停直到写完成,不如简单地将要写的值传到流水线寄存器E作为源操作数。
    译码阶段逻辑发现,寄存器%rax是操作数valB的源寄存器,而在写端口E上还有一个对%rax的未进行的写。
    它只要简单地将提供到端口E的数据字(信号W_valE)作为操作数valB的值,就能避免暂停。
    这种将结果值直接从一个流水线阶段传到较早阶段的技术称为数据转发(dataforwarding,或简称转发,有时称为旁路(bypassing))。
    它使得prog2的指令能通过流水线而不需要任何暂停。
    数据转发需要在基本的硬件结构中增加一些额外的数据连接和控制逻辑。
  3. 加载/使用数据冒险
    有一类数据冒险不能单纯用转发来解决,因为内存读在流水线发生的比较晚。
    可以将暂停和转发结合起来,避免加载/使用数据冒险。
    这个需要修改控制逻辑,但是可以使用现有的旁路路径。
    这种用暂停来处理加载/使用冒险的方法称为加载互锁Cloadinterlock)。
    加载互锁和转发技术结合起来足以处理所有可能类型的数据冒险。
    因为只有加载互锁会降低流水线的吞吐量,我们几乎可以实现每个时钟周期发射一条新指令的吞吐量目标。
  4. 避免控制冒险
    当处理器无法根据处千取指阶段的当前指令来确定下一条指令的地址时,就会出现控制冒险。
    在我们的流水线化处理器中,控制冒险只会发生在ret指令和跳转指令。
    而且,后一种情况只有在条件跳转方向预测错误时才会造成麻烦.

2. 异常处理

处理器中很多事情都会导致异常控制流,此时,程序执行的正常流程被破坏掉。
异常可以由程序执行从内部产生,也可以由某个外部信号从外部产生。

我们的指令集体系结构包括三种不同的内部产生的异常:

  1. halt指令,
  2. 有非法指令和功能码组合的指令,
  3. 取指或数据读写试图访问一个非法地址。

正确处理异常是任何微处理器设计中很有挑战性的一方面。
异常可能出现在不可预测的时间,需要明确地中断通过处理器流水线的指令流。

在一个流水线化的系统中,异常处理包括一些细节问题。

  1. 可能同时有多条指令会引起异常。
    例如,在一个流水线操作的周期内,取指阶段中有ha辽指令,而数据内存会报告访存阶段中的指令数据地址越界。
    我们必须确定处理器应该向操作系统报告哪个异常。
    基本原则是:由流水线中最深的指令引起的异常,优先级最高。

    在上面那个例子中,应该报告访存阶段中指令的地址越界。
    就机器语言程序来说,访存阶段中的指令本来应该在取指阶段中的指令开始之前就结束的,
    所以,只应该向操作系统报告这个异常。

  2. 当首先取出一条指令,开始执行时,导致了一个异常,而后来由千分支预测错误,取消了该指令。

  3. 因为流水线化的处理器会在不同的阶段更新系统状态的不同部分。
    有可能会出现这样的情况,一条指令导致了一个异常,它后面的指令在异常指令完成之前改变了部分状态。
    一般地,通过在流水线结构中加入异常处理逻辑,
    我们既能够从各个异常中做出正确的选择,也能够避免出现由千分支预测错误取出的指令造成的异常。
    这就是为什么我们会在每个流水线寄存器中包括一个状态码sta七(图4-41和图4-52)。
    如果一条指令在其处理中于某个阶段产生了一个异常,这个状态字段就被设置成指示异常的种类。
    异常状态和该指令的其他信息一起沿着流水线传播,直到它到达写回阶段。
    在此,流水线控制逻辑发现出现了异常,并停止执行。
    为了避免异常指令之后的指令更新任何程序员可见的状态,
    当处千访存或写回阶段中的指令导致异常时,流水线控制逻辑必须禁止更新条件码寄存器或是数据内存。

让我们来看看这种处理异常的方法是怎样解决刚才提到的那些细节问题的。
当流水线中有一个或多个阶段出现异常时,信息只是简单地存放在流水线寄存器的状态字段中。
异常事件不会对流水线中的指令流有任何影响,
除了会禁止流水线中后面的指令更新程序员可见的状态(条件码寄存器和内存),直到异常指令到达最后的流水线阶段。
因为指令到达写回阶段的顺序与它们在非流水线化的处理器中执行的顺序相同,
所以我们可以保证第一条遇到异常的指令会第一个到达写回阶段,此时程序执行会停止,流水线寄存器W中的状态码会被记录为程序状态。
如果取出了某条指令,过后又取消了,那么所有关于这条指令的异常状态信息也都会被取消。
所有导致异常的指令后面的指令都不能改变程序员可见的状态。
携带指令的异常状态以及所有其他信息通过流水线的简单原则是处理异常的简单而可靠的机制。


总结

我们已经看到,指令集体系结构,即ISA,在处理器行为(就指令集合及其编码而言)和如何实现处理器之间提供了一层抽象。
ISA提供了程序执行的一种顺序说明,也就是一条指令执行完了,下一条指令才会开始。
从IA32指令开始,大大简化数据类型、地址模式和指令编码,我们定义了Y86-64指令集。
得到的ISA既有RISC指令集的属性,也有CISC指令集的属性。

然后,将不同指令组织放到五个阶段中处理,在此,根据被执行的指令的不同,每个阶段中的操作也不相同。
据此,我们构造了SEQ处理器,其中每个时钟周期执行一条指令,它会通过所有五个阶段。

流水线化通过让不同的阶段并行操作,改进了系统的吞吐抵性能。
在任意一个给定的时刻,多条指令被不同的阶段处理。
在引入这种并行性的过程中,我们必须非常小心,以提供与程序的顺序执行相同的程序级行为。
通过重新调整SEQ各个部分的顺序,引入流水线,我们得到SEQ+,接着添加流水线寄存器,创建出PIPE一流水线。
然后,添加了转发逻辑,加速了将结果从一条指令发送到另一条指令,从而提高了流水线的性能。
有几种特殊情况需要额外的流水线控制逻辑来暂停或取消一些流水线阶段。

我们的设计中包括了一些基本的异常处理机制,在此,保证只有到异常指令之前的指令会影响程序员可见的状态。
实现完整的异常处理远比此更具挑战性。在采用了更深流水线和更多并行性的系统中,要想正确处理异常就更加复杂了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值