CSAPP:第四章——处理器体系结构(上)

文章目录

    • 概述
    • 一、Y86-64 指令体系结构
      • 1.1 程序员可见状态
      • 1.2 Y86-64 指令
      • 1.3 指令编码
      • 1.4 Y86-64 异常
    • 二、逻辑设计和硬件控制语言HCL
      • 2.1 逻辑门
      • 2.2 组合电路和HCL布尔表达式
      • 2.3 字级的组合电路和HCL整数表达式
      • 2.4 集合关系
      • 2.5 存储器和时钟
    • 三、Y86-64的顺序实现
      • 3.1 将处理组织成阶段
      • 3.2 SEQ硬件结构
      • 3.3 SEQ的时序
      • 3.4 SEQ阶段的实现
      • 3.5 SEQ小结

概述

指令被编码为由一个或多个字节序列组成的二进制格式,一个处理器支持的指令指令的字节级编码称为它的指令集体系结构(Instruction-Set Architecture,ISA),如LoongArch ISA指令addu12i.w的字节级编码(字节序列)是00000000001010010

一、Y86-64 指令体系结构

Y86-64指令集是本书作者自己定义的一个简单指令集,与X86-64 相比,Y86-64 指令集的数据类型、指令和寻址方式都要少一些。定义一个指令集体系结构包括定义各种状态单元、指令集和它们的编码、一组编程规范和异常事件处理。

1.1 程序员可见状态

Y86-64包括有以下可见的状态单元:

  • 15个程序寄存器。每个程序寄存器存储一个 64 位的字。寄存器%rsp 被入栈、出栈、调用和返回指令作为栈指针。
  • 31位的条件码,它们保存着最近的算术或逻辑指令所造成影响的有关信息。
  • 程序计数器(PC)存放当前正在执行指令的地址。
  • 内存从概念上来说就是一个很大的字节数组,保存着程序和数据。
  • 状态码Stat,表明程序执行的总体状态。它会指示是正常运行,还是出现了某种异常,例如当一条指令试图去读非法的内存地址时。
    CSAPP:第四章——处理器体系结构(上)_第1张图片

1.2 Y86-64 指令

以下是作者对Y86-64指令的定义,只包括8字节整数操作,寻址方式比较少,操作也少。图中左边是指令的汇编码表示,右边是字节编码:

  • movq指令分成了4个不同的指令:irmovqrrmovqmrmovrmmovq,分别显式的指明源和目的的格式。源可以是立即数(i)、寄存器(r)或内存(m),指令名字的第一个字母就表明了源的类型。目的可以是寄存器(r)或内存(m),指令名字的第二个字母指明了目的的类型。
  • 4个整数操作指令,见下图OPq指令,分别是addqsubqandqxorq,只对寄存器数据进行操作,这些指令会设置3个条件码ZFSFOF(零、符号和溢出)。
  • 7个跳转指令是jmpjlejljejnejgejg
  • 6个条件传送指令:cmovlecmovlcmovecmovnecmovgecmovg
  • call将call的下条指令地址值压到栈顶,然后将PC值设置为call后面跟的目的地址;ret将PC的设置为当前栈顶存放的值。
  • pushqpopq指令实现了入栈和出栈。
  • halt指令停止指令的执行,执行halt指令会导致处理器停止,并将状态码设置为HLT
    CSAPP:第四章——处理器体系结构(上)_第2张图片

1.3 指令编码

1.2中的图4.2所给指令字节级编码,每条指令需要 1 ~ 10 个字节不等,且第一个字节表明指令的类型,这个字节分为两个部分,每部分 4 位:高 4 位是code部分(值为 0 ~ 0xB),低 4 位是function部分(function值只有在一组相关指令共用一个code时才有用),如下图所示:
CSAPP:第四章——处理器体系结构(上)_第3张图片

15个程序寄存器中每个都有一个范围在0~0xE之间的寄存器标识符(registerID),程序寄存器保存在CPU的寄存器文件中,这个寄存器文件就是一个小的、以寄存器ID作为地址的随机访问存储器,在编码过程中,当需要指明不应访问任何寄存器时,就用ID0xF来表示。
CSAPP:第四章——处理器体系结构(上)_第4张图片
指令集的一个重要性质就是字节编码必须有唯一的解释,任意一个字节序列要么是一个确定且唯一的指令序列的编码,要么就不是一个合法的字节序列。Y86-64就具有这个性质,因为每条指令的第一个字节有唯一的codefunction组合,给定这个字节,就可以决定所有其他附加字节的长度和含义。这个性质保证了处理器可以无二义性地执行目标代码程序,即使代码嵌入在程序的其他字节中,只要从序列的第一个字节开始处理,我们仍然可以很容易确定指令序列。反过来说,如果不知道一段代码序列的起始位置,我们就不能确定如何将字节序列划分成单独的指令,所以确定的字节序列 => 起始位置 + 指令的字节编码

1.4 Y86-64 异常

Y86-64状态码可以取以下值,1表示执行正常,2表示执行一条halt指令,3遇到非法读写,4表示遇到非法指令代码,其中2、3、4则为异常状态码。Y86-64的状态码为异常时,程序会停止(没有异常处理),一般完整的指令集定义,都会有异常处理程序
CSAPP:第四章——处理器体系结构(上)_第5张图片

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

2.1 逻辑门

逻辑门类比于C语言的逻辑运算,而不是按位与、或、非。逻辑门总是活动的(active),一旦一个门的输入发生变化,在很短的时间内,输出也会相应地变化。
CSAPP:第四章——处理器体系结构(上)_第6张图片

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

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

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

下图中的两个组合电路,第一个是异或,第二个是多路复用器(通常称为MUX)。
CSAPP:第四章——处理器体系结构(上)_第7张图片
异或和多路复用器对应的HCL表达式分别如下:

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

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

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

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

通过将逻辑门组合成大的网,可以构造出能计算更加复杂函数的组合电路,通常,我们设计能对数据字(word)进行操作的电路。下面组合电路是由64个2.2中的位相等(图4-10)组合电路构成,当且仅当 A 的每一位都和 B 的相应位相等时,输出才为 1,对应的HCL表达式为bool Eq = (A == B);
CSAPP:第四章——处理器体系结构(上)_第8张图片

下图字级多路复用器用HCL描述为word out = [s: A; 1: B;];,第二个选择表达式就是 1,表明如果前面没有情况被选中,那就选择这种情况。

CSAPP:第四章——处理器体系结构(上)_第9张图片

选择表达式可以是任意的布尔表达式,可以有任意多的情况。这就使得情况表达式能描述带复杂选择标准的、多路输入信号的块。图4-14电路根据控制信号 s1s0,从 4 个输入字 A、B、CD 中选择一个,将控制信号看作一个两位的二进制数。我们可以用 HCL 来表示这个电路,用布尔表达式描述控制位模式的不同组合:

word out = [
    !s1 && !s0: A; #00
    !s1: B;        #01,这里的分支是“!s1 && s0”
    !s0: C;        #10,这里的分支是“s1 && !s0”
    1: D;          #11,这里的分支是“s1 && s0”     
];

CSAPP:第四章——处理器体系结构(上)_第10张图片

2.4 集合关系

在处理器设计中,很多时候都需要将一个信号与许多可能匹配的信号做比较,以此来检测正在处理的某个指令代码是否属于某一类指令代码。举个例子,假设想从一个2位信号 code 中选择高位低位来产生四路复用器的控制信号 s1s0,对应的HCL表达式:

bool s1 = code == 2 || code == 3;
bool s0 = code == 1 || code == 3;

将其写成集合关系表述就是:

bool s1 = code in {2, 3};
bool s0 = code in {1, 3};

2.5 存储器和时钟

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

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

这里需要区分一下针对于组合电路所说的硬件寄存器和针对于机器级编程的程序寄存器

  • 在硬件中,寄存器直接将它的输入和输出线连接到电路的其他部分,用来存储计算的状态。
  • 在机器级编程中,寄存器代表的是 CPU 中为数不多的可寻址的字,这里的地址是寄存器 ID。这些字通常都保存在CPU的寄存器文件中,寄存器文件就是一个小的、以寄存器ID作为地址的随机访问存储器。

下图是硬件寄存器的工作方式,大多数时候,寄存器都保持在稳定状态(用 x 表示),产生的输出等于它的当前状态。当新的信号沿着寄存器前面的组合逻辑电路传播,这时,产生了一个新的寄存器输入(用 y 表示),但是只要时钟是低电位的,寄存器的输出就仍然保持不变。当时钟变成高电位时候,输入信号就加载到寄存器中,成为下一个状态 y,直到下一个时钟上升沿,这个状态就一直是寄存器的新输出。每当每个时钟到达上升沿时,值才会从寄存器的输入传送到输出
CSAPP:第四章——处理器体系结构(上)_第11张图片

下图是寄存器文件的工作方式,寄存器文件不是组合电路,因为它有内部存储。寄存器文件有两个读端口AB),还有一个写端口W),这样一个多端口随机访问存储器允许同时进行多个读和写操作。两个读端口有地址输入 srcAsrcB和对应的数据输出 valAvalB,写端口有地址输入 dstW,以及数据输入 valW。例如,读取$r3中的值时,将 src A 设为 3,在一段延迟之后,程序寄存器 %rbx 中存放的值就会出现在输出 valA 上。向寄存器文件写入值是由时钟信号控制的,控制方式类似于将值加载到时钟寄存器,每次时钟上升时,输入 valW 上的值会被写入输入 dstW 上的寄存器 ID 指示的程序寄存器,当 dstW 设为特殊的 ID0xF 时,不会写任何程序寄存器。
CSAPP:第四章——处理器体系结构(上)_第12张图片

三、Y86-64的顺序实现

描述一个称为 SEQ(“sequential” 顺序的)的处理器。

3.1 将处理组织成阶段

通常,处理一条指令包括很多操作,将它们组织成某个特殊的阶段序列,所有的指令都遵循统一的序列,即使某条指令在某个阶段不执行,也要遵循这个阶段序列,阶段序列如下:

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

以某个代码块中的pushq rA指令为例,说明各个阶段的具体工作,pushq rA表示将栈顶指针减8(%rsp = %rsp - 8),并将rA中的值存放在栈顶(%rsp),其指令的定义如下:

0x02a: a02f		pushq $rdx

CSAPP:第四章——处理器体系结构(上)_第13张图片

3.2 SEQ硬件结构

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

  • 白色方框表示时钟寄存器。PC 是 SEQ 中唯一的时钟寄存器。
  • 浅蓝色方框表示硬件。这包括内存、ALU等,在我们所有的处理器实现中,都会使用这一组基本的单元。我们把这些单元当做“黑盒子”,不关心它们的细节设计。
  • 控制逻辑块用灰色圆角矩形表示。这些块用来从一组信号源中进行选择,或者用来计算一些布尔函数。我们会非常详细地分析这些块,包括给出 HCL 描述。
  • 宽度为字长的数据连接用中等粗度的线表示。每条这样的线实际上都代表一簇 64 根线,并列地连在一起,将一个字从硬件的一个部分传送到另一部分。
  • 宽度为字节或更窄的数据连接用细线表示。根据线上要携带的值的类型,每条这样的线实际上都代表一簇 4 或 8 根线。
  • 单个位的连接用虚线来表示。这代表芯片上单元与块之间传递的控制值。

CSAPP:第四章——处理器体系结构(上)_第14张图片
硬件结构与各个处理阶段相关联:

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

3.3 SEQ的时序

一个时钟变化会引发一个经过组合逻辑的流,来执行整个指令。组合逻辑电路与时序电路不同,不存储任何信息,只是简单地响应输入信号,产生符合组合逻辑的输出,如果输入信号再次更新,则输出也会跟着变化,并不会将先前的输出存下来,时序电路则有专门的存储设备将输出保存下来。

SEQ 的时序实现包括组合逻辑两种存储设备:时钟寄存器(PC条件码寄存器),随机访问存储器(存储器文件指令内存数据内存);组合逻辑不需要任何时序或控制——只要输入变化了,值就通过逻辑门网络传播。可以将随机访问存储器看成是组合逻辑一样的操作,即当输入某个合法地址值,输出则是地址中存放的值。

有以下四个硬件单元的时序要进行明确的控制——PC条件码寄存器数据内存寄存器文件。这些单元通过一个时钟信号来控制,它触发将新值装载到寄存器以及将值写到随机访问存储器。每个时钟周期,PC 都会装载新的指令地址;只有在执行整数运算指令时,才会装载条件码寄存器;只有在执行 rmmovqpushqcall 指令时,才会写数据内存;寄存器文件的两个写端口允许每个时钟周期更新两个程序寄存器,不过我们可以用特殊的寄存器 ID 0xF作为端口地址,来表明此端口不应该执行写操作。

要控制处理器中活动的时序,只需要寄存器和内存的时钟控制,且所有的状态的更新都只在时钟上升开始下一个周期时同时发生。保持这样的等价性要遵循一个组织计算原则:从不回读,处理器从来不需要为了完成一条指令的执行而去读由该指令更新了的状态。看3.1图片中的push指令,如果我们对 pushq 指令的实现是先将 %rsp = %rsp - 8,然后再将更新后的 %rsp 值作为写操作的地址,这样就同组织计算原则相违背,因为指令从寄存器文件中读取了指令本身更新的栈指针。所以应该这样设计,3.1图片的执行阶段,将%rsp - 8产生的栈指针,作为信号valE,然后利用这个信号作为访存时的地址,最后在时钟上升时,将valE信号写入寄存器%rsp中。

以下是汇编代码,SEQ硬件处理其中的3、4行指令:
CSAPP:第四章——处理器体系结构(上)_第15张图片
跟踪SEQ的两个执行周期,每个周期开始时,状态单元(寄存器和内存)是根据前一条执行设置的,信号传播通过组合逻辑,创建出新的状态单元的值,在下一个周期开始时(时钟上升),这些值会被加载到状态单元。如下图中,当周期3开始时,上升前状态单元中的值是周期1组合逻辑创建,上升后状态单元被修改该为周期2组合逻辑创建;当周期3结束时,状态单元保持不变,周期四开始时(上升后),状态单元时周期3组合逻辑创建。
CSAPP:第四章——处理器体系结构(上)_第16张图片

3.4 SEQ阶段的实现

本节设计实现 SEQ 所需要的控制逻辑块的 HCL 描述中使用的名称和含义如下:
CSAPP:第四章——处理器体系结构(上)_第17张图片

1)取指阶段

取指阶段主要工作是指令内存硬件单元,以 PC 作为第一个字节(字节0)的地址,从指令内存中一次读出 10 个字节(Y86-64 最长的指令占10个字节,见1.2中的插图)。第一个字节被解释成指令字节(标为 Split 单元),分为两个 4 位的数,标号为 icodeifun ;或者使之等于从内存读出的值;或者当指令地址不合法时(由信号 imem_error 指明),使这些值对应于 nop 指令。根据 icode 的值,我们可以计算三个1位的信号(用虚线表示):

  • instr_valid: 这个字节是否是一个合法的 Y86-64 指令。
  • need_regids:这个 Y86-64 指令是否为一条带有寄存器指示值字节的指令。
  • need_valC:这个 Y86-64 指令是否包括一个常数字。
  • (当指令地址越界时会产生的)信号 instr_validimem_error 在访存阶段被用来产生状态码。

CSAPP:第四章——处理器体系结构(上)_第18张图片
从指令内存中读出的剩下 9 个字节是寄存器指示符字节和常数字的组合编码。标号为 Align 的硬件单元会处理这些字节,将它们放入寄存器字段和常数字中。当被计算出的信号 need_regids1 时,字节 1 被分开装入寄存器指示符 rArB 中,否则,这两个字段会被设为 0xF(NONE),表明这条指令没有指明寄存器。任何只有一个寄存器操作数的指令,寄存器指示值字节的另一个字段都设为 0xF,所以可以将信号 rArB 看成,要么放着我们想要访问的寄存器,要么表明不需要访问任何寄存器。Align单元还会根据信号 need_regids 的值来产生常数字 valC,没有寄存器时 valC1 ~ 8 字节,有寄存器是 2 ~ 9 字节。

PC 增加器硬件单元根据当前的 PC 以及两个信号 need_regidsneed_valC 的值,产生信号 valP。对于 PC 值pneed_regidsr 以及 need_valCi,增加器产生值 valP = p + 1 + r + 8i(仔细推敲这个公式,理解取值时每次从内存中读取10个字节,下一次读取从哪个位置开始)。need_regids 的 HCL 描述只是确定了 icode 的值是否为一条带有寄存器指示值字节的指令:
在这里插入图片描述

2)译码和写回阶段

译码阶段会从寄存器中读取最多两个操作数,写回阶段会将值写入到寄存器中,所以将这两个阶段放一起说明。寄存器文件有四个端口,它支持同时进行两个读(在端口 AB上)和两个写(在端口 EM 上)。每个端口都有一个地址连接和一个数据连接,地址连接是一个寄存器 ID,而数据连接是一组 64 根线路,既可以作为寄存器文件的输出字(对读端口来说),也可以作为它的输入字(对写端口来说)。两个读端口的地址输入位 srcAsrcB,而两个写端口的地址输入位 dstEdstM,如果某个地址端口上的值为特殊标识符 0xF,则表明不需要访问寄存器。
CSAPP:第四章——处理器体系结构(上)_第19张图片
根据指令代码 icode 以及寄存器指示值 rArB,可能还有执行阶段计算出的 Cnd 条件信号,可以确定出写端口dstEdstM和读端口srcAsrcB的寄存器 ID。在译码阶段会从读端口中读出的值为信号valAvalB,写回阶段会将信号valMvalE的值写入到寄存器。部分信号的HCL描述如下:

  • srcA的HCL描述:
    CSAPP:第四章——处理器体系结构(上)_第20张图片
  • srcA的HCL描述:
    CSAPP:第四章——处理器体系结构(上)_第21张图片

3)执行阶段

执行阶段包括算术/逻辑单元(ALU),这个单元根据 ALUfun 信号的设置,对输入 ALU AALU B 执行 ADDSUBTRACTANDEXCLUSIVE-OR 运算,最终的输出则是valE
CSAPP:第四章——处理器体系结构(上)_第22张图片
列出操作数 ALU B 在前面,后面是 ALU A,这样是为了保证 subq 指令是 valB 减去 valA。可以看到,根据指令的类型, ALU A 的值可以是 valAvalC,或者 -8+8。因此 ALU A 的HCL描述为:

word aluA =[
	icode in { IRRMOVQ, IOPQ } : valA;
	icode in { IIRMOVQ, IRMMOVQ, IMRMOVQ } : valC;
	icode in { ICALL, IPUSHQ } : -8;
	icode in { IRET, IPOPQ } : 8;
	# Other instructions don't need ALU
];

ALU可为整数运算指令执行操作,也可作为加法器执行操作,对于OPq(整数运算)指令,使用ifun字段中的编码执行操作,所以ALU控制信号 ALU fun的HCL描述为:
在这里插入图片描述
执行阶段还包括条件码寄存器,每次运行时,ALU 都会产生三个与条件码相关的信号——零、符号 和 溢出。不过,我们只希望在执行 OPq 指令时才设置条件码,因此产生了一个信号 set_cc 来控制是否该更新条件码寄存器:
在这里插入图片描述
标号为 cond 的硬件单元会根据条件码和功能码来确定是否进行条件分支或者条件数据传送( 1.3 中的图4.3),它产生信号 Cnd,用于设置条件传送的 dstE(图4.28),也用在条件分支的下一个 PC 逻辑中。对于其他指令,取决于指令的功能码和条件码的设置,Cnd 信号可以被设置为 1 或者 0,但是控制逻辑会忽略它。条件传送指令(简称cmovXX)的指令代码为IRRMOVQ,如图4.28所示,可以用执行阶段产生的Cnd信号实现这些指令。

4)访存阶段

访存阶段的任务就是读或者写程序数据。如下图,两个控制块产生内存地址Mem addr和内存输入数据Mem data(为写操作)的值,另外两个块产生表明应该执行读操作(Mem read)还是写操作(Mem write)的控制信号。
CSAPP:第四章——处理器体系结构(上)_第23张图片
从图中可以看出,当执行读操作时,数据内存产生值 valM,内存读写的地址总是 valEvalA,这个地址Mem addr用 HCL 描述就是:
CSAPP:第四章——处理器体系结构(上)_第24张图片
也可以从图中看到,内存写的数据总是valAvalP,所以Mem data的HCL表示为:

mem addr =[
	# Value from register
	icode in { IRRMOVQ, IPUSHQ } : valA;
	# Return PC
	icode == ICALL : valP;
	# Default: Don't write anything
];

计算Stat字段需要从几个阶段收集状态信息,访存阶段最后的功能是根据取值阶段产生的 icodeimem_errorinstr_valid 值以及数据内存产生的 dmem_error 信号,从指令执行的结果来计算状态码 Stat,用HCL表示为:

## Determine instruction status
word Stat = [
	imem_error || dmem_error : SADR;
	!instr_valid: SINS;
	icode == IHALT : SHLT;
	1 : SAOK;
];

5)更新PC阶段

SEQ 中最后一个阶段会产生 PC 的新值,依据指令的类型和是否选择分支,新的 PC 可能是 valCvalMvalP
CSAPP:第四章——处理器体系结构(上)_第25张图片
对于 PC 值pneed_regidsr 以及 need_valCi,增加器产生值 valP = p + 1 + r + 8i,用 HCL 来描述这个选择就是:

word new_pc [
	# Call. Use instruction constant
	icode == ICALL : valC;
	# Taken branch. Use instruction constant
	icode == IJXX && Cnd : valC;
	# Completion of RET instruction. Use value from stack
	icode == IRET : valM;
	# Default: Use incremented PC
	1 : valP;
];

3.5 SEQ小结

现在我们已经浏览了 y86-64 处理器的一个完整的设计。可以看到,通过将执行每条不同指令所需的步骤组织成一个统一的流程,就可以用很少量的各种硬件单元以及一个时钟来控制计算的顺序,从而实现整个处理器。不过这样一来,控制逻辑就必须要在这些单元之间路由信号,并根据指令类型和分支条件产生适当的控制信号。

SEQ 唯一的为题就是它太慢了。时钟必须非常慢,以使信号能在一个周期内传播所有的阶段。让我们来看看处理一条 ret 指令的例子,在时钟周期起始时,从更新过的 PC 开始,要从指令内存中读出指令,从寄存器文件中读出栈指针,ALU 将栈指针加 8,为了得到 PC 的下一个值,还要从内存中读出返回地址。所以这一切都必须在这个周期结束之前完成。

这种实现方法不能充分利用硬件单元,因为每个单元只在整个时钟周期的一部分时间内才被使用。我们会看到引入流水线能获得更好的性能。

你可能感兴趣的:(计算机系统原理,计算机体系结构,cpu,指令集,体系结构)