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

概述

指令被编码为由一个或多个字节序列组成的二进制格式,一个处理器支持的指令指令的字节级编码称为它的指令集体系结构(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,表明程序执行的总体状态。它会指示是正常运行,还是出现了某种异常,例如当一条指令试图去读非法的内存地址时。
    在这里插入图片描述

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指令将返回地址入栈,然后跳到目的地址,ret指令从这样的调用中返回。
  • pushqpopq指令实现了入栈和出栈。
  • halt指令停止指令的执行,执行halt指令会导致处理器停止,并将状态码设置为HLT
    在这里插入图片描述

1.3 指令编码

1.2中的图4.2所给指令字节级编码,每条指令需要 1 ~ 10 个字节不等,且第一个字节表明指令的类型,这个字节分为两个部分,每部分 4 位:高 4 位是code部分(值为 0 ~ 0xB),低 4 位是function部分(function值只有在一组相关指令共用一个code时才有用),如下图所示:
在这里插入图片描述

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

1.4 Y86-64 异常

Y86-64状态码可以取以下值,1表示执行正常,2表示执行一条halt指令,3遇到非法读写,4表示遇到非法指令代码,其中2、3、4则为异常状态码。Y86-64的状态码为异常时,程序会停止(没有异常处理),一般完整的指令集定义,都会有异常处理程序
在这里插入图片描述

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

2.1 逻辑门

逻辑门类比于C语言的逻辑运算,而不是按位与、或、非。逻辑门总是活动的(active),一旦一个门的输入发生变化,在很短的时间内,输出也会相应地变化。
在这里插入图片描述

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

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

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

下图中的两个组合电路,第一个是异或,第二个是多路复用器(通常称为MUX)。
在这里插入图片描述
异或和多路复用器对应的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);
在这里插入图片描述

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

在这里插入图片描述

选择表达式可以是任意的布尔表达式,可以有任意多的情况。这就使得情况表达式能描述带复杂选择标准的、多路输入信号的块。图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”     
];

在这里插入图片描述

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,直到下一个时钟上升沿,这个状态就一直是寄存器的新输出。每当每个时钟到达上升沿时,值才会从寄存器的输入传送到输出
在这里插入图片描述

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

三、Y86-64的顺序实现

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 设置成下一条指令的地址。

3.2 SEQ硬件结构

3.3 SEQ的时序

3.4 SEQ阶段的实现

四、流水线的通用原理

五、Y86-64的流水线实现

猜你喜欢

转载自blog.csdn.net/qq_42570601/article/details/120850405
今日推荐