深入理解操作系统(10)第四章:处理器体系结构(2)Y86-64的顺序实现(包括:SEQ/指令的各阶段操作:取指,解码,执行,访存,回写,更新PC/序列指令/硬件结构/时序/阶段的实现/SEQ+)

1. Y86-64的顺序实现

现在我们已经有了实现Y86处理器所需要的部件。首先,我们讲一个称为SEQ的处理器。

SEQ(取的是“sequentia”处理器的意思)的处理器。

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

时钟周期也称为振荡周期,定义为时钟频率的倒数。
通常频率是100(1秒钟震荡100次),那么他的精度度或者说时钟周期就是10ms。

1.1 将处理组织成阶段

1.1.1 阶段序列

通常,处理一条指令包括很多操作。

我们将它们组织成某个特殊的阶段序列,使得即使指令的动作差异很大,但所有的指令都遵循统一的序列。

每一步的具体处理取决于正在执行的指令。

创建这么一个框架使我们能够设计一个能充分利用硬件的处理器。

1.1.2 一条指令执行的6个阶段

下面是关于各个阶段以及各阶段内执行操作的简略描述:

1. 取指(fetch):取指阶段从存储器读入指令,地址为程序计数器(PC)的值。
	从指令中抽取出指令指示符字节的两个四位部分,称为 icode(指令代码)和ifun(指令功能)。
	它可能取出一个寄存器指示符字节,指明一个或两个寄存器操作数指示符rA和rB。
	它还可能取出个四字节常数字vaC。它按顺序方式计算当前指令的下一条指令的地址valP。
	也就是说valP等于PC的值加上已取出指令的长度

2. 解码(decode): 解码阶段从寄存器文件读入最多两个操作数,得到值valA和或valB。
	通常,它读入指令rA和rB字段指明的寄存器,不过有些指令是读寄存器%esp的

3. 执行(execute):在执行阶段,算术/逻辑单元(ALU)要么执行指令指明的操作(根据ifun的值),
	计算存储器引用的有效地址,要么增加或减少栈指针。我们称得到的值为valE。
	在此,也可能设置条件码。
	对一条跳转指令来说,这个阶段会检验条件码和(ifun给出的)分支条件,看是不是应该选择分支。

4. 访存(memory):访存阶段可以将数据写入存储器,或者从存储器读出数据。
	读出的值为vaM

5. 写回(write back): 写回阶段最多可以写两个结果到寄存器文件。

6. 更新PC(PC update): 将PC设置成下一条指令的地址

处理器无限制地循环执行这些阶段,只有在遇到halt指令或一些错误情况时,才会停下来。
我们处理的错误情况包括非法存储器地址(程序地址或数据地址),以及非法指令。

1.1.3 一条指令要进行很多处理(远不止上面6个阶段)

从前面的讲述可以看出,执行一条指令是需要进行很多处理的。
不仅要执行指令所表明的操作还要计算地址、更新栈指针,以及确定下一条指令的地址。
幸好每条指令的整个流程都比较相似。因为我们想使硬件数量尽可能的少,并且最终将把它映射到一个二维的集成电路芯片的表面,

一个非常简单而一致的结构是非常重要的。
降低复杂度的一种方法是让不同的指令共享尽量多的硬件。

例如,我们的每个处理器设计都只含有一个算术/逻辑单元,根据所执行的指令类型的不同,它的使用方式也不同。在硬件上复制逻辑块的成本比软件中有重复代码的成本大得多,而且在硬件系统中处理许多特殊情况和特性要比用软件来处理困难得多。

1.1.4 Y86指令序列指令

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

在这里插入图片描述

后面的所有指令例子操作都是基于此,所以请认真看!!!

图4.16~图4.19中的表描述了不同Y86指令在各个阶段是怎样处理的。
要好好研究一下这些表,表中的这种格式很容易映射到硬件。

这些表中的每一行都描述了一个信号或存储状态的分配。

计算一个值并将结果放入寄存器中。rrmovl irmovl 指令例子。
图4.16
在这里插入图片描述

注释:
	符号 icode: ifun  icode(指令代码)和ifun(指令功能)
	rA rB表明寄存器指示符字节的两个组成部分
	符号[M1x]表示访问(读或者写)存储器位置M处的一个字节,而[M4x]表示访问四个字

1.1.5 subl 指令执行

作为一个例子,让我们来看看一条subl指令的处理过程,这条指令是图4.l5所示目标代码的第3行中的sub指令。

执行: 0x00c:6123 	subl %edx %ebx  //9 21
结果: %ebx = %ebx - %edx = 21 - 9 = 12

我们可以看到前面两条指令分别将寄存器%edx和%ebx初始化成9和21。
还能看到指令是位于地址0x00c,有两个字节,值分别为0x61和0x23。
这条指令的处理如下图所示,左边列出了处理一个OPl指令的通用的规则而右边列出的是对这条指令的计算
图4.151
在这里插入图片描述

注释:
	符号 icode: ifun表明指令字节的两个组成部分
		icode(指令代码)和ifun(指令功能) 
		这里是:M[0x00c]=6:1  6:1这个啥意思?没整明白???为啥是6:1
			61 表示:subl指令代码 看上个文章 图4.3 Y86指令集的功能码
	rA rB表明寄存器指示符字节的两个组成部分
	符号[M1x]表示访问(读或者写)存储器位置M处的一个字节

结果: ebx=12,并且三个条件码都设置成0 ZF=SF=OF=0

疑问:???

	这里是:M[0x00c]=6:1  6:1这个啥意思?没整明白???为啥是6:1
	61 表示:subl指令代码 看上个文章 图4.3 Y86指令集的功能码

1.1.6 rmmovl 和 mrmovl 处理

图4.17给出了存储器读写指令rmmmovl和mrmovl所需要的处理。
基本流程也和前面一样,不过是用ALU来加vaC和vaB,得到存储器操作的有效地址(位移量与基址寄存器值之和)。
在访存阶段将寄存器值valA写到存储器,或者从存储器中读出valM

图4.17
在这里插入图片描述

1.1.7 rmmovl 指令的执行

让我们来看看图4.15中目标代码的笫5行上rmmovl指令的处理情况。

0x014:404364000000	rmmovl %esp 100(%ebx)

可以看到,前面的指令已将寄存器‰esp初始化成了128,而%ebx仍然是subl指令(第三行)算出来的结果12。
我们可以看到,指令位于地址0x014,有六个字节。前两个的值为0x40和0x43,后四个是数字0x00000064(十进制数100)按字节反过来得到的数。
各个阶段的处理如下

图4.171
在这里插入图片描述

1.1.8 pushl 和 popl 指令

图4.18给出了处理pushl和popl指令所需的步骤。
它们可以算是最难实现的Y86指令了,因为们既涉及到访问存储器,又要增加或减少栈指针。
虽然这两条指令的流程比较相似,但是它们还是有很重要的区别的。
图4.18
在这里插入图片描述

pushl 指令开始时很像我们讲过的指令,但是在解码阶段,是用%esp作为第二个寄存器操数的标识符,将栈指针赋值为vaB。
在执行阶段,用ALU将栈指针减4。减过4的值就是存储器的地址,在写回阶段还会存回到%esp中。
将valE作为写操作的地址,是遵循了Y86(和IA32)的惯例,也就是在写之前, pushl应该先将栈指针减去4,即使栈指针的更新实际上是在存储器操作成之后才进行的

1.1.9 pushl 指令的执行

跟踪push指令的执行让我们来看看图4.15中目标代码的第6行上mmmoⅥ指令的处理情况。

0x01a:a028	pushl %edx

此时,寄存器%edx的值为9,而寄存器%esp的值为128。
我们还可以看到指令是位于地址0x01a,有两个字节,值分别为0xa0和0x28。
各个阶段的处理如下

图4.181
在这里插入图片描述

popl指令的执行与push的执行类似,除了在解码阶段要读两次栈指针以外。
这样做看上去是很多余,但是我们会看到让valA和valB都存放栈指针的值,会使后面的流程跟其他的指令更相似,增强了设计的整体一致性。
在执行阶段,用ALU给栈指针加4,但是用没加过4的原始值作为存储器操作的地址。
在写回阶段,要用加过4的栈指针更新栈指针寄存器,还要将寄存器rA更新为从存储器中读出的值。用没加过4的值作为存储器读地址,保持86(和IA32)的惯例,popl首先读存储器,然后再增加栈指针。

1.1.10 跳转jxx、cat和ret

图4.19表明了我们的三类控制转移指令的处理:各种跳转jxx、cat和ret。
可以看到,我们能用同前面指令一样的整体流程来实现这些指令。

图4.19
在这里插入图片描述

同对整数操作一样,我们能够以一种统一的方式处理所有的跳转指令。
因为它们的不同只在于判断是否要选择分支的时候。跳转指令在取指和解码阶段都和前面讲的其他指令类似不需要寄存器指示符字节以外。
在执行阶段,我们检查条件码和跳转条件来确定是否要选择产生出信号Bch。
在更新PC阶段,我们检查这个标志,如果这个标志为1,就将PC设为valC(跳转目标),如果为0,就设为vaIP(下一条指令的地址)。
我们的表示 x?a:b 类似于C中的条件表达当x非零时,它等于a,当x为零时,等于b。

1.1.11 je跳转指令的执行

跟踪je指令的执行:
让我们来看看图4.5中目标代码的第8行上j指令的处理情况。

0x01e:7328000000	je 	done

上个subl指令(第3行)已经将所有的条件码都置为了0,所以不会选择分支。
该指令位于地址0x0le,有5个字节。
第一个字节的值为0x73,而剩下的四个字节是数字0x00000028按字节反过来得到的数,也就是跳转的目标。
各个阶段的处理如下

图4.191
在这里插入图片描述

指令call和ret与pushl和popl类似,除了要将程序计数器的值入栈和出栈以外。
对指令call,我们要将valP,也就是call指令后紧跟着的那条指令的地址,压入栈中。
在更新PC阶段,将PC设为waC,也就是调用的目的地。
对指令ret,在更新PC阶段,我们将vaM,从栈中取出的值赋值给PC

1.1.12 ret 指令的执行

跟踪ret指令的执行让我们来看看图4.15中目标代码的第13行上ret指令的处理情况。

0x029:90	ret

指令的地址是0x029,只有一个字节的编码,0x90。
前面的call指令将%esp置为了124,并将返回地址0x0028存放在了存储器地址124。
各个阶段的处理如下

图4.192
在这里插入图片描述

1.2 SEQ硬件结构

1.2.1 SEQ抽象视图,顺序执行结构

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

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

图4.20给出了一个能执行这些计算的硬件结构的抽象表示。
图4.20
在这里插入图片描述
说明:

程序计数器放在寄存器中在图中左下角(标了“PC”)。
信息沿着线流动(多条线重合就用宽一点的灰线来表示),先向上再向右。
同各个阶段相关的硬件单元( hardware units)负责执行这些处理。
反馈线路向下回到右边包括要写到寄存器文件的更新值,以及更新的程序计数器值。

这张图省略了一些小的组合逻辑块还省略了所有用来操作各个硬件单元以及将相应的值路由到这些单元的控制逻辑。待会儿我们会详细讲述这个问题。我们从下往上画处理器和流程的方法似乎有点奇怪。在我们开始设计流水线化的处理器时,我们会解释这么画的原因硬件单元与各个处理阶段相关联。

1.2.2 硬件单元与各处理阶段关联

1. 取指:将程序计数器寄存器作为地址,指令存储器读取一个指令的字节。
	PC增加器(PCincrementer)计算valP,即增加了的程序计数器。
	
2. 解码:寄存器文件有两个读端口A和B,从这两个端口同时读寄存器值vaA和vaB

3. 执行:执行阶段会根据指令的类型,将算术逻辑单元(ALU)用于不同的目的。
	a.对整数操作它要执行指令所指定的运算
	b.对其他指令,它会作为一个加法器来计算增加或减少栈指针
	c.或者计算有效地址
	d.或者只是简单地加0,将一个输入传递到输出
	e.条件码寄存器(CC)有三个条件码位。ALU负责计算条件码的新值。
	f.当执行一条跳转指令时会根据条件码和跳转类型来计算分支信号Bch

4. 访存:在执行访存操作时,数据存储器(data memory)读出或写入一个存储器字。
	指令和数据存储器访问的是相同的存储器位置,但是用于不同的目的

5. 写回:寄存器文件有两个写端口。
	端口E用来写ALU计算出来的值,而端口M用来写从数据存储器中读出的值

1.2.3 SEQ硬件结构,顺序执行

图4.21更详细地给出了实现SEQ所需要的硬件(虽然到分析每个阶段时,我们才会看到完整的细节)。
我们看到一组和前面一样的硬件单元,但是现在线路看得更清楚了。

图4.21
在这里插入图片描述

1.2.4 硬件作图惯例

在上幅图以及我们其他的硬件图中,都使用的是下面的作图惯例。

1. 用带淡点的浅灰色方框表示硬件单元。
	如:存储器、ALU等等。
	在我们所有的处理器实现中,都会使用这一组基本的单元。
	我们把这些单元看成“黑盒子”,不关心它们的细节设计。
	
2. 控制逻辑块是用灰色圆角矩形表示的。
	如: ALU
	这些块用来从一组信号源中进行选择,或者用来计算一些布尔函数。
	我们会详细分析这些块的,包括详细说明HCL描述
	
3. 线路的名字在白色圆角方框中说明。
	它们只是线路的标识,而不是什么硬件元素。

4. 宽度为字长的数据连接用中等粗度的线表示。
	每条这样的线实际上都代表一簇32根线并列地连在一起,将字从硬件的一部分传送到另外一部分。

5. 宽度为字节或更窄的数据连接用细线表示。
	根据线上要携带的值的类型,每条这样的线实际上都代表一簇4根或8根线

6. 单个位的连接用点线来表示。
	这代表芯片上单元与块之间传递的控制值

1.2.5 标识顺序实现中的不同计算步骤

图4.22

在这里插入图片描述

1.3 SEQ的时序

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

1.3.1 两种存储器设备

1. 时钟控制的寄存器(程序计数器和条件码寄存器)
2. 随机访问存储器(寄存器文件、指令存储器和数据存储器)。

1.3.2 组合逻辑

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

正如我们提到过的那样,我们将读随机访问存储器看成和组合逻辑一样的操作,根据地址输入产生输出字。
因为我们的指令存储器只用来读指令,因此我们可以将这个单元看成组合逻辑

1.3.3 四个硬件单元需要定序

现在还剩四个硬件单元需要对它们的定序( sequencing)进行明确的控制:

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

这些单元是通过一个时钟信号来控制的,它触发将新值装载到寄存器以及将值写到随机访问存储器。每个时钟周期,程序计数器都会装载新的指令地址。只有在执行整数运算指令时,才会装载条件码寄存器。只有在执行 rmmovl、push或cl指令时,才会写数据存储器。
寄存器文件的两个写端口允许每个时钟周期更新两个程序寄存器,不过我们可以用特殊的寄存器ID8作为端口地址,来表明在此端口不应该执行写操作

控制我们处理器中活动的定序( sequencing),只需要寄存器和存储器的时钟控制。

我们的硬件获得了就好像图4.16~图4.19中那些赋值顺序执行一样的效果,即使所有的状态更新实际上同时发生,且只在时钟上升开始下一个周期时。之所以能保持这样的等价性,是由于Y86指令集的本质。

1.3.4 例子说明

举个例子来说明一下这条原则,我们可以看到有些指令(整数运算)会设置条件码,有些指令(跳转指令)会读取条件码,但没有指令必须既设置又读取条件码。

虽然要到时钟上升开始下个周期时,才会设置条件码,但是在任何指令试图读之前,它们都会更新好的。

下面这段代码是汇编代码,左边列出的是指令地址,图4.23给出了SEQ硬件是如何处理其中第3和第4行指令的

1	0x000: irmovl $0x100, %ebx
2 	0x006: irmovl $0x200, %edx
3	0x00c: addl %edx,%ebx
4	0x00e: je dest
5	0x013: rmmov1 %ebx,0(%edx)
6 	0x019:  dest halt

图4.23
在这里插入图片描述

说明1:

每个周期开始时,根据前一条指令设置状态元素(程序计数器,条件码寄存器,寄存器文件及数据存储器)。
信号传播到组合逻辑时,创建出新的状态元素的值。在下一个周期开始时,这些值会被加载到状态元素中。

说明2:
标号为1~4的各个图给出了四个状态元素,还有组合逻辑,以及状态元素之间的连接。
组合逻辑被条件码寄存器环绕着,因为有的组合逻辑(例如ALU)产生输入到条件码寄存器,而其他部分(例如分支计算和PC选择逻辑)又将条件码寄存器作为输入。
图中寄存器文件和数据存储器有分离的读连接和写连接,因为读操作沿着这些单元传播,就好像它们是组合逻辑,而写操作是由时钟控制的。

1.4 SEQ阶段的实现

本节会设计实现SEQ所需要的控制逻辑块的HCL描述。

1.4.1 HCL描述

图4.24

在这里插入图片描述

1.4.2 取指阶段

图4.25
在这里插入图片描述

取指阶段包括指令存储器硬件单元。

以PC作为第一个字节(字节0)的地址,这个单元一次从存储器读出六个字节。
第一个字节被当成指令字节,(被标号为“Split”的单元)分为两个四位的量 icode和ifun。
根据 icode的值,我们可以计算三个一位的信号(用虚线表小)

如图4.25所示,从指令存储器中读出的剩下五个字节是寄存器指示符字节和常数字的组合编码。
标号为“ Align”的硬件单元会处理这些字节,将它们放入寄存器字段和常数字中。当被计算的信号need regids为1时,字节1被分开装入寄存器指示符rA和rB中。否则,这两个字段会被设为8( RNONE),表明这条指令没有指明寄存器。
回想一下(图4.2),任何只有一个寄存器操作数的指令寄存器指示值字节的另一个字段都设为8( RNONE)。因此,我们可以将信号rA和B看成,要么放着我们想要访问的寄存器,要么表明不需要访问任何寄存器。标号为“Aign”的单元还产生常数字valC。根据信号 need_regids的值,要么根据字节14来产生vaC,要么根据字节25来产生PC增加器( Incrementer)硬件单元根据当前的PC以及两个信号 need_regids和 need valc的值,产生信号vaP。

1.4.3 解码和写回阶段

图4.26
在这里插入图片描述

图4.26给出了SEQ中实现解码和写回阶段的逻辑的详细情况。
这两个阶段联系在一起是因为它们都要访问寄存器文件。

寄存器文件有四个端口支持同时进行两个读(在端口A和B上)和两个写(在端口E和M上)。每个端口都有地址连接和数据连接,地址连接是一个寄存器ID,而数据连接是一组32根线路,既可以作为寄存器文件的输出字(对读端口来说),也可以作为它的输入字(对写端口来说)。如果某个地址端口上的值为特殊标识符8( RNONE),则表明不需要访问寄存器。

1.4.4 执行阶段

图4.27
在这里插入图片描述

执行阶段包括算术逻辑单元(ALU)。
这个单元根据 alufun信号的设置,对输入aluA和aluB执行ADD、 SUBTRACT、AND或 EXCLUSIVE-OR运算。

1.4.5 访存阶段

图4.28
在这里插入图片描述

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

如图4.28所示,两个控制块产生存储器地址和存储器输入数据(为写操作)的值。
另外两个块产生表明应该执行读操作还是写操作的控制信号。

1.4.6 跟新PC阶段

图4.29
在这里插入图片描述

取决于指令的类型和是否要选择分支,新的PC可能是valC、valM或valP.

1.5 SEQ小结

现在我们已经浏览过Y86处理器的一个完整的设计。
seq框架:

我们可以看到,通过将执行每条不同指令所需的步骤组织成一个统一的流程,
就可以用很少量的各种硬件单元以及一个时钟来控制计算的顺序,从而实现整个处理器。

不过这样一来,控制逻辑就必须要在这些单元之间路由信号,并根据指令类型和分支条件产生适当的控制信号。
问题:

SEQ唯一的问题就是它太慢了。
时钟必须非常慢,以使信号能在一个周期内传播过所有的阶段。

让我们来看看处理一条ret指令的例子。
在时钟周期起始时,从更新过的PC开始,要从指令存储器中读出指令,从寄存器文件中读出栈指针,ALU要减小栈指针,为了得到程序计数器的下一个值还要从存储器中读出返回地址。所有这一切都必须在这个周期结束之前完成。这种实现方法不能充分利用硬件单元,因为每个单元只在整个时钟周期的一部分时间内才被使用。
解决:

我们会看到引入流水线能获得更好的性能

1.6 SEQ+重新安排计算阶段

1.6.1 SEQ+

作为到流水线化的设计的一个中间步骤,我们将重新排列这六个阶段的顺序,

使得更新PC阶段在一个周期开始时执行,而不是结束时才执行,这样产生的处理器设计称为SEQ+,
因为它扩展了基本的SEQ处理器。

这种做法看上去有些奇怪,因为确定新的PC值需要检测执行阶段中的分支条件(对条件转移来说),或者读访存阶段中的返回值(对ret指令来说)。

如图4.30
在这里插入图片描述

我们能移动PC阶段,使得它的逻辑在时钟开始时活动,计算当前指令的PC值。

然后这个PC值就可以输入到取指阶段,剩下的处理就和前面讲过的一样继续下去。在时钟周期结束之前,组合逻辑会产生计算新的PC值所需要的所有的信号。

现在PC阶段的任务变成了为当前指令选择PC值,而不是为下一条指令计算更新了的PC。

图4.31给出了SEQ+硬件的一个更为详细的说明
我们可以看到,它包括与我们在SEQ中用到的(图421)一样的硬件单元和控制块,只不过PC逻辑移到了底部。
图4.31
在这里插入图片描述

1.6.2 SEQ 和 SEQ+ PC的比较

图4.311
在这里插入图片描述

对控制逻辑的惟一修改就是重新定义了PC的计算,使它使用以前的状态值。

1.6.3 电路重定时

我们看到,两个块之间惟一的区别就是,将保存着处理器状态的寄存器从PC计算的后面移到了前面。

这个例子是一种很常见的改进,称为电路重定时( circuit retiming)。

重定时改变了一个系统的状态表示,但是并不改变它的逻辑行为。通常用来平衡一个系统中各个部分之间的延迟。

1.6.4 SEQ+中的PC在哪里?

SEQ+有一个很奇怪的特色,那就是没有硬件寄存器来存放程序计数器。
相反,是根据从前一条指令保存下来的一些状态信息来动态地计算PC的。

这就是一个小小的例证,证明我们可以以一种与ISA隐含着的概念模型不同的方式来实现处理器,只要处理器能正确执行任意的机器语言程序我们不需要按照程序员可见的状态表明的方式来对状态进行编码,只要处理器能对任意程序员可见的状态(例如,程序计数器)产生正确的值。在创建流水线化的设计中,我们会更多地使用到这条原则。
5.7节中描迷的乱序( out-of-order)处理技术,以一种完全不同于机器级程序中发生顺序的次序来执行指令,将这一思想发挥到了极致。

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值