1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > 深入理解计算机系统 --- 处理器体系结构

深入理解计算机系统 --- 处理器体系结构

时间:2022-04-28 18:33:21

相关推荐

深入理解计算机系统 --- 处理器体系结构

本章目的:简要介绍处理器硬件的设计,研究一个硬件系统执行某种ISA指令的方式

到目前为止,我们看到的计算机系统只限于机器语言程序级

我们知道处理器必须执行一系列指令,每条指令执行某个简单操作

指令被编码为一个或多个字节序列组成的二进制格式

一个处理器支持的指令和指令的字节级编码称为它的指令集结构(ISA)

不同处理器家族,都有不同的ISA,一个程序编译成一种机器上运行,就不能在另一种机器上运行

ISA在编译器编写者和处理器设计人员之间提供了一个概念抽象层

编译器编写者只需要知道允许哪些指令

而处理器设计者必须构建出执行这些指令的处理器

理解处理器如何工作能帮助理解整个计算机系统如何工作

Y86-64是一个指令体系结构(ISA),它是写这本书的作者出的指令集

目的是为了让我们更加清晰的了解ISA

就像在读编译原理一样,作者会教你做个编译器是一样的道理

4.处理器体系结构

4.1 Y86-64指令集体系结构

定义一个指令集体系结构,包括定义各种状态单元、指令集和他们的编码、一组编程规范和异常事件处理

4.1.1程序员可见的状态

Y86-64程序中的每条指令都会读取或修改处理器状态的某些部分

这称为程序员可见状态

这里的”程序员“既可以是汇编代码写程序的人,也可以是产生机器代码的编译器

在处理器实现中,只要我们保证机器级程序能够访问程序员可见状态

就不需要完全按照ISA暗示的方式来表示和组织这个处理器状态

上图,程序计数器(PC)存放当前正在执行指令的地址

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

Y86-64程序用虚拟地址来引用内存位置

硬件和操作系统软件联合起来将虚拟地址翻译成实际或物理地址

指明数据实际存在内存中哪个地方

状态码Stat,表明程序执行的总体状态,它会指示是正常运行,还是出现了某种异常

例如一条指令试图去读非法的内存地址

4.1.2 Y86-64指令

ATT格式,第一个操作数是目标操作数,第二个操作数是源操作数

上图给出了Y86-64 ISA 中各个指令的简单描述

这个指令集就是我们处理器实现的目标

Y86-64指令集基本上是x86-64指令集的一个子集

它只包括8字节整数操作,寻址方式较少,操作也较少

左边是指令的汇编码表示,右边是字节编码

下面是Y86-64指令的一些细节

4.1.3 指令编码

每条指令需要1-10个字节不等,这取决于需要哪些字段

每条指令的第一个字节表面指令的类型,这个字节分为两个部分

每部分4位:高4位是代码部分,低4位是功能部分

15个程序寄存器中每个都有一个对应的范围在0到0xE之间的寄存器标识符(register ID)

编号跟x860-64中的相同

例如用16进制来表示指令: rmmovq %rsp,0x123456789abcd(%rdx) 的字节编码

从图4-2中可以看到,rmmovq 第一个字节为40,源寄存器%rsp应该编码放在RA字段

而基质寄存器%rdx应该编码放在Rb字段,据图4-4中的寄存器编号,得到寄存器指示符字节42,最后偏移量编码放在8字节常数字中,0x123456789abcd前面填充上0变成8字节

将它们都连接起来得到的指令编码为: 40 42 cd ab 89 67 45 23 01 00

指令集一个重要性质就是字节编码必须有唯一的解释

任意一个字节序列要么是一个唯一的指令序列的编码

要么就不是一个合法的字节序列

如果不知道一段代码序列的起始位置,我们就不能准确的确定怎样将序列划分成单独的指令

4.1.4 Y86-64 异常

对Y86-64来说,程序员可见状态包括状态码Stat,它描述程序运行的总体状态

对于Y86-64,当遇到这些异常的时候,就简单地让处理器停止执行指令

在更完整的设计中,处理器通常会调用一个异常处理程序(exception handler)

这个过程被指定用来处理遇到的某种类型的异常

4.1.5 Y86-64 程序

这个程序程序包括数据,指令

伪指令指明应该将代码或数据放在什么位置,以及如何对齐

这个程序详细说明了栈的放置,数据初始化,程序初始化和程序结束等问题

“.”开头的词是汇编器伪指令,告诉汇编器调整地址,以便在那里产生代码或插入一些数据

伪指令 .pos 0 告诉汇编器应该从0地址处开始产生代码,这个地址是所有Y86-64程序的起点

接下来的一条指令(第三行)初始化栈指针

结尾处声明了标号 Stack,并用一个. Pos 伪指令指明地址200,因此栈会从这个地址开始

向低地址增长

我们必须保证栈不会增长得太大以至于覆盖了代码或其他程序数据

标号array表明了这个数组的起始,并在8字节边界处对齐(用 .align 伪指令指定)

16-19行为“main”过程,在过程中对那个4字数组调用了sun函数,然后停止

4.1.6 一些Y86-64 指令的详情

大多数Y86-64 指令时以一种直接明了的方式修改程序状态的

所以定义每条指令想要达到的结果并不困难

Pushq 指令会把栈指针减8,并且将一个寄存器值写入内存中

因此当执行pushq %rsp 指令时,处理器行为是不确定的

因为入栈的寄存器会被同一条指令修改,通常有两种不同的约定:

1.压入%rsp的原始值

2.压入减去8的%rsp的值

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

在硬件设计中,用电子电路来计算对位进行运算的函数

以及在各种存储器单元中存储位

大多数现代电路技术都是用信号线上的高电压或低电压来表示不同的位值

在当前技术中,逻辑1是用1.0伏特左右的高电压表示,逻辑0使用0.0伏特左右的低电压表示的

要实现一个数字系统需要三个主要的组成部分:

1.计算机对位进行操作的函数的组成逻辑

2.存储位的存储器单元

3.控制存储器单元更新的时钟信号

4.2.1 逻辑门

逻辑门是数字电路的基本计算单元,它们产生的输出,等于它们输入位值的某个布尔函数

后边很多地方都用这个图

C语言中运算符的逻辑门下面是对应的HCL表达式:

AND 用 && OR 用 || NOT 用 !

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

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

将很多的逻辑门组合成一个网,就能构建计算块(computational block),称为组合电路

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

图4-11 多路复用器,根据输入控制信号的值,从一组不同的数据信号选出一个:

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

通过将逻辑门组合成大的网,可以构造出能计算更加复杂函数的组合电路

我们的处理器设计将包含很多字,字的大小的范围为 4位到64位

代表 整数、地址、指令代码和寄存器标识符

执行字级计算的组合电路根据输入字的各个位,用逻辑门来计算出字的各个位

例如,测试两个64位字A和字B是否相等,仅当A的每个位都和B的相应位相等时

输出才为1

4.2.5 存储器和时钟

组合电路从本质上讲,不存储任何信息,它只是简单地响应输入信号,产生等于输入的某个函数的输出

为了产生 时序电路 ,也就是有状态并且在这个状态上进行计算的系统

我们必须引入按位存储信息的设备

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

在硬件中,寄存器直接将它的输入和输出线连接到电路的其他部分

在机器级编程中,寄存器代表的是CPU中为数不多的可寻址的字,这里地址是寄存器ID

这些字通常都存在寄存器文件中

上图说明了一个硬件寄存器以及它是如何工作的

大多数时候,寄存器都保持在稳定状态(x表示),产生的输出等于它的当前状态

信号沿着前面的组合逻辑传播,这时,产生了一个新的寄存器输入(y表示),但只要时钟是低电位的,寄存器的输出就仍然保持不变

当时钟变成高电位的时候,输入信号就加载到寄存器中,成为了下一个状态y

直到下一个时钟上升沿,这个状态就一直是寄存器的新输出

每当每个时钟到达上升沿时,值才会从寄存器的输入传送到输出

寄存器文件有两个读端口(A和B),还有一个写端口(W)

这样一个多端口随机访问存储器允许同时进行多个读和写操作

图所示的寄存器文件中,电路可以读两个程序寄存器的值,同时更新第三个寄存器的状态

每个端口都有一个地址输入,表明选择哪个程序寄存器

还有一个数据输出或对应该程序寄存器的输入值

虽然寄存器文件不是组合电路,因为它有内部存储,不过在我们的实现中,

从寄存器文件读数据就好像它是一个以地址为输入、数据为输出的一个组合逻辑块

例如,将srcA设为3,就会读出程序寄存器%rbx的值,然后这个值就会出现在输出valA上

4.3 Y86-64指令的顺序实现

首先,我们描述一个成为SEQ(“sequential”顺序的)的处理器

每个时钟周期上,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设置成下一条指令的地址

处理器无限循环,执行这些阶段,在我们简化的实现中,发生任何异常,处理器就会停止

在完整的设计中,处理器会进入异常模式,开始执行由异常的类型决定的特殊代码

执行一条指令是需要进行很多处理的,不仅必须执行指令表明的操作,还必须计算地址、更新栈指针,以及确定下一条地址

整数操作指令的处理遵循上面列出的通用模式

在取指阶段,不需要常数字,所以 valP 就计算为 PC+2

在译码阶段,需要读两个操作数

在执行阶段,它们和功能指示符ifun一起再提供给ALU,这样一来valE就成为了指令结果

这个计算是用表达式valB OP valA 来表达的,这里OP代表ifun指令的操作

在访存阶段,什么也不做

在写回阶段,valE被写入寄存器Rb,然后PC设为valP,整个指令的执行就结束了

看一下subq指令执行过程

据图4-17,第三行,指令位于0x014 地址,占两个字节,值为 0x61 和 0x23

4.3.2 SEQ硬件结构

实现所有Y86-64指令所需要的计算可以被组织成6个基本阶段:

取指,译码,执行、访存、写回、更新PC

从程序计数器PC(左下角),信息沿着线流动,先向上再向右

同各个阶段相关的硬件单元(hardwareunits)负责执行这些处理

在右边,反馈线路向下,包括要写到寄存器文件的更新值,以及更新的程序计数器值

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

取指: 将程序计数器寄存器作为地址,指令内存读取指令的字节,PC增加器计算valP

即增加了的程序计数器

译码: 寄存器文件有两个读端口A和B,从这两个端口同时读寄存器值valA和valB

执行: 执行阶段会根据指令的类型,将算术/逻辑单元(ALU)用于不同的目的

对整数操作,它要执行指令所指定的运算,对其他指令,它会作为一个加法器来计算增加或减少栈指针,或者计算有效地址,或者只是简单地加0,将一个输入传递到输出

条件码寄存器(CC)有三个条件码位,ALU负责计算条件码的新值

当执行条件传送指令时,根据条件码和传送条件来计算决定是否更新目标寄存器

同样,当执行一条跳转指令时,会根据条件码和跳转类型来计算分支信号Cnd

访存: 在执行访存操作时,数据内存读出或写入一个内存字

指令和数据内存访问的是相同的内存地址,但是用于不同的目的

写回: 寄存器文件有两个写端口,端口E用来写ALU计算出来的值

而端口M用来写从数据内存中读出的值

PC更新: 程序计数器的新值选择自:valP,下一条指令的地址

ValC,调用指令或跳转指令指定的目标地址

ValM,从内存读取的返回地址

4.3.3 SEQ的时序

SEQ的实现包括组合逻辑和两种存储器设备:

时钟寄存器(程序计数器和条件码寄存器)

随机访问存储器(寄存器文件、指令内存和数据内存)

组合逻辑不需要任何时序或控制,只要输入变化了,值就通过逻辑门网络传播

我们也将 读随机访问存储器 看成和组合逻辑一样的操作,根据地址输入产生输出字

由于指令内存只用来读指令,因此我们可以将这个单元看成是组合逻辑

还剩四个逻辑单元需要对它们的时序进行明确的控制

程序计数器、条件码寄存器、数据内存和寄存器文件

这些单元通过一个时钟信号来控制,它触发将新值装载到寄存器以及将值写到随机访问存储器,每个时钟周期,程序计数器都会装载新的指令地址

只有在执行整数运算指令时,才会装载条件码寄存器

只有在执行rmmovq、pushq或call指令时,才会写数据内存

要控制处理器中活动的时序,只需要寄存器和内存的时序控制

原则:从不回读

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

不同颜色的代码表面电路信号是如何与正在被执行的不同指令相联系的

实在太烧脑!

4.3.4 SEQ阶段的实现

我们没有讲那部分SEQ的HCL描述,是不同整数和布尔信号的定义,它们可以作为HCL操作的参数

1.取指阶段

取指阶段包括指令内存硬件单元,以PC作为第一个字节(字节0)的地址

这个单元一次从内存读出10个字节

第一个字节被解释成指令字节,标号为“Split”的单元,分为两个4位的数

然后标号为“icode”和“ifun”的控制逻辑块计算指令的功能码

或者使之等于从内存读出的值,或者当前指令地址不合法(由信号imem_error指明)

根据icode的值,我们可以计算三个一位的信号:

Instr_valid: 这个字节对应于一个合法的Y86-64指令吗?这个信号用来发现不合法的指令

Need_regids: 这个指令包括一个寄存器指示符字节吗?

Need_valc: 这个指令包括一个常数字吗?

信号instr_valid 和 imem_error在访存阶段被用来产生状态码

2.译码和写回阶段

把这两个阶段联系在一起是因为他们都要访问寄存器文件

寄存器文件有四个端口,支持同时进行两个读(A,B)和两个写(E,M)

每个端口都有一个地址连接和一个数据连接

地址连接是一个寄存器ID,数据连接是一组64根线路,既可以作为寄存器文件的输出字(读端口)也可以作为它的输入字(写端口)

根据指令代码icode以及寄存器指示值Ra和Rb,可能还会根据执行阶段计算出的Cnd条件信号,图底部的四个块产生出四个不同的寄存器文件的寄存器ID

寄存器ID srcA表明应该读哪个寄存器以及产生valA

3.执行阶段

执行阶段包括算术/逻辑单元(ALU)

这个单元根据alufun信号的设置,对输入aluB和aluB执行

ADD,SUBTRACT,AND,EXCLUSVEOR运算

4.访存阶段

访存阶段的任务就是读或者写程序数据

如图所示,两个控制块产生内存地址和内存输入数据(写操作)的值

另外两个块产生表明应该执行读操作还是写操作的控制信号

当执行写操作时,数据内存产生值valM

5.更新PC阶段

SEQ中最后一个阶段会产生程序计数器的新值

新的PC可能是valC、valM、valP

4.4 流水线的通用原理

在流水线化的系统中,待执行的任务被划分成了若干个独立的阶段

流水线化的一个重要的特性就是提高了系统的吞吐量(throughput)

不过它也会轻微地增加延迟(latency)

4.4.1 计算流水线

每个阶段完成指令执行的一部分,

下图给出了一个很简单的非流水线化的硬件系统例子

它是由一些执行计算的逻辑以及一个保存计算结果的寄存器组成

时钟信号控制在每个特定的时间间隔加载寄存器

图中的计算块使用组合逻辑实现的,信号会穿过一系列逻辑门,在一定时间延迟后输出就成为了输入的某个函数

假设组合逻辑需要300ps,而加载寄存器需要20ps

图中的流水线图时间从左向右流动,从上到下一组操作(I1-3),实心长方形表示指令执行时间

在这个实现中,开始下一条指令之前必须完成前一个

在各个阶段之间放上流水线寄存器(pipeline register),这样每条指令都会按照三步经过这个系统,从头到尾需要三个完整的时间周期

上图的流水线所示,只要I1从A进入B,就可以让I2进入阶段A了,依此类推

在稳定状况下,三个阶段都应该是活动的,每个始终周期,一条指令离开系统,一条新的进入

这样,我们将系统吞吐量提高了两倍多

代价是增加了一些硬件,以及延迟的少量增加,延迟变大是由于增加的流水线寄存器的时间开销

4.5 流水线的实现

首先,对顺序的SEQ处理器做点小改动,将PC计算挪到取指阶段,然后再各个阶段之间加上流水线寄存器

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

稍微调整一下SEQ中五个阶段的顺序,使得更新PC阶段在一个时钟周期开始时执行,而不是结束时执行

4.5.2 插入流水线寄存器

要在SEQ+的各个阶段之间插入流水线寄存器,并对信号重新排列

得到PIPE- 处理器,PIPE-抽象结构 (图4-40)

流水线寄存器在图中用黑色方框表示,每个寄存器包括不同的字段,用白色方框表示

可以看到 PIPE-使用了与顺序设计SEQ几乎一样的硬件单元,但是有流水线寄存器分隔开这些阶段

4.5.3 对信号进行重新排列和标号

顺序是先SEQ和SEQ+在一个时刻只处理一条指令,因此诸如valC,srcA,valE这样的信号值有唯一的值

在流水线化的设计中,与各个指令相关的这些值由多个版本,会随着指令一起流过系统

采用命名机制,存储在流水线寄存器中的信号可以唯一地被标识

4.5.4 预测下一个PC

流水线化设计的谜底就是每个时钟周期都发射一条新指令,也就是说每个时钟周期都有一条指令进入执行阶段并最终完成

要达到这个目的也就意味着吞吐量是每个时钟周期一条指令

要做到这一点必须在取出当前指令之后,马上确定下一条指令的位置

不幸的是,如果取出的指令是条件分支指令,要到几个周期后,也就是指令通过执行阶段之后,才能知道是否要选择分支,如果是ret,要到指令通过访存阶段,才能确定返回地址

因此,通过预测PC的下一个值,在大多数情况下,我们能达到每个时钟周期发射一条新指令的目的

4.5.5 流水线冒险

PIPE-结构是创建一个流水线化的Y86-64处理器的好开端

当相邻指令存在相关联会导致出现问题

1.数据相关,下一条指令会用到这一条指令计算出的结果

2.控制相关,一条指令要确定下一条指令的位置

这些可能会导致流水线产生计算错误,称为:毛线

冒险也可以分为两类: 数据冒险,控制冒险

1.用暂停来避免数据冒险

暂停时避免毛线的一种常用数据,暂停时,处理器会停止流水线中一条或多条指令

知道冒险条件不再满足

让上一条指令停顿在译码阶段,知道产生它的源操作数的指令通过了写回阶段

这样就能避免数据冒险

2.用转发来避免数据冒险

PIPE- 的设计是在译码阶段从寄存器文件中读入源操作数,但是对些源寄存器的写有可能要在写回阶段才能进行

与其暂停直到写完成,不如简单地将要写的值传到流水线寄存器E作为源操作数

这种将结果直接从一个流水线阶段传到较早阶段的的技术称为:数据转发

3.加载/使用数据冒险

4.避免控制冒险

4.5.6 异常处理

处理器中很多事情会导致异常控制流,此时,程序执行的正常流程被破坏掉

异常可以由程序执行从内部产生,也可以由某个外部信号从外部产生

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

1.halt指令

2.有非法指令和功能码组合的指令

3.取指或数据读写试图访问一个非法地址

我们把导致异常的指令称为异常指令(excepting instruction)

一般地,通过在流水线结构中加入异常处理逻辑,我们既能够从各个异常中做出正确的选择,也能够避免出现由分支预测错误取出的指令造成异常

PIPE- 各阶段的实现

小结

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。