04-处理器体系结构
本节主要总结学习了Y86-64的指令流水。相比X86-64,Y86精简指令集便于学习。Y86-64有15个程序寄存器,相比X86-64,省略了%r15
以简化指令的编码。每个程序寄存器存储一个64位的字。寄存器%rsp
被入栈、出栈、调用和返回指令作为栈指针。除此之外寄存器没有固定的含义或固定值。有3个一位的条件码:ZF、SF和OF,他们保存着最近的算术或逻辑指令的所造成影响的有关信息。程序计数器PC存放当前正在执行的指令的地址。内存从概念上来说就是一个很大的字节数组,保存着程序和数据。程序状态的状态码表明程序执行的总体状态,指示程序正常运行还是出现异常。
我们主要讲解一下相关Y86指令的执行流程,那么相比于x86-64的指令集,Y86-64做了简化,movq
指令被分成了rrmovq
、irmovq
、rmmovq
和mrmovq
四种。movq
前面的两个祖母分别表示了源和目的的格式。
指令编码
可以看到一个指令,前两个位置是一个字节,高四位表示指令代码,低4位表示指令功能,关于前两个位置(一字节)的部分描述如下:
操作数如果是寄存器,则直接填其编号,寄存器对应编号如下:
整数操作指令:
跳转操作指令:
传送操作指令:
其他系统操作指令:
关于指令编码,举个例子,在Y86-64汇编代码中,对如下汇编代码是如何翻译成二进制表示的呢?
rmmovq %rsp, 0x123456789abcd(%rdx)
根据rmmovq
指令的编码定义,该指令二进制表示中的第一个字节为0x40
,寄存器%rsp
编号为0x4
;基址寄存器%rdx
编号为0x2
;因此该指令二进制表示第二个字节为0x42
。指令编码中偏移量占8个字节,因此我们需要在该偏移量前面补0来凑齐8个字节。即:
00 01 23 45 67 89 ab cd
又由于我们采用小端法存储,因此还要对偏移量进行字节反序操作即:
cd ab 89 67 45 23 01 00
最终我们得到该指令的二进制表示如下:
以上则为指令编码二进制表示,那么关于Y86-64状态码:
可以看到在Y86-64中,程序代码于x86-64不同点主要在于数据传送指令:
处理器执行指令流程:
阶段:
接下来主要说明几个指令的执行流程,如subq
运算指令、irmovq
传送指令、rmmovq
传送指令、pushq
压栈指令、je
跳转指令等
sub指令各个阶段:
irmovq指令的各个阶段:
rmmovq指令的各个阶段:
push指令的各个阶段:
je指令的各个阶段:
硬件设计
取址阶段硬件设计
在取址阶段中,取址操作以程序计数器(PC)的值为起始地址。由于Y86-64指令 集中最长的指令占10字节,为了保证每次取址操作至少能够获得一条完整的指令, 取址操作每次从指令内存中读取10
各字节。接下来将这10
字节分为两部分,第一部分占1
字节,第二部分占9
字节。例如编码为30 f8 08 00 00 00 00 00 00 00
的指令,将被分成30
和f80800000000000000
。
随后名为Split
的硬件单元处理第一部分。它将这个字节又分成两部分,每部分占4
个比特位,使这个字节分为两个字段,分别为指令代码和指令功能,用icode
和ifun
表示。根据icode
可以确定当前指令的状态信息,例如指令的合法性。如果icode
在0x0
到0xB
之间,那么它就是一条合法指令。此外,通过icode
还可以判断当前指令是否包含寄存器指示符字节和常数字节。
通过前面说的判断结果,就可以计算出当前指令的长度。例如一个指令既含寄存器指示符字节,又含常数字节,那它的长度就是10
字节;如果既不含寄存器指示符字节,又不含常数字节,那它的长度就是1
字节。与此同时,还可以通过将PC
值加上当前的指令长度来计算内存中下一条指令的地址,用于后续的更新阶段。
在前面我们处理了一条指令中的头一个字节,对于剩下的9
个字节,我们通过名为Align
的硬件单元来产生寄存器字段和常数字段。该硬件单元通过信号need_regids
来判断该指令是否包含寄存器指示符字节。若need_regids = 1
,说明该指令包含寄存器指示符字节,那么第一个字节将被分成两部分,每部分占4
个比特位,然后分别装入寄存器指示符rA
和rB
中;若need_regids = 0
,说明该指令不包含寄存器指示符字节,此时将rA
、rB
这两个字段设置为0xF
。此外,若该指令含有常数,Align
单元还将产生常数字段valC
。当need_regids = 1
时,valC
被设为指令的第2
到9
字节;当need_regids = 0
时,valC
被设为指令的第1
到8
字节。
译码阶段硬件设计
译码阶段的操作是从寄存器文件中读取数据,在Y86-64处理器中寄存器文件有两个读端口,它支持同时进行两个读操作,两个读端口的地址输入为srcA
和srcB
,从寄存器文件中读出的数值通过valA
和valB
输出。读端口的srcA
和srcB
用于产生寄存器的ID,这需要寄存器指示符rA
及rB
。由于某些指令,例如push
指令,该指令的寄存器指示符中只含有目的寄存器的ID,但执行压栈操作时,还需要获得栈顶指针rsp
的值。因此srcA
和srcB
不仅需要传入rA
和rB
,还需要传入指令代码icode
。
执行阶段硬件设计
执行阶段的核心部件ALU
根据指令功能ifun
来判断要对输入的操作数进行何种运算。每次运行时,ALU
都会产生三个与条件码相关的信号—:零、符号、溢出。我们只希望ALU
在执行算术逻辑指令时才设置条件码,而计算内存引用地址以及栈操作时不要设置条件码。因此我们使用Set_CC
信号根据指令代码icode
来控制是否需要设置条件码。此外,我们使用名为
Cond的硬件单元来控制跳转操作。
Cond会根据指令功能和条件码寄存器来产生
Cnd信号。对于跳转指令,如果
Cnd = 1,就执行跳转;如果
Cnd = 0`,则不执行跳转。
访存阶段硬件设计
该阶段的硬件设计主要包含以下四个控制块:
- 读控制块,用于进行读操作。
- 写控制块,用于进行写操作。
- 内存地址控制块,用于产生内存地址。
- 数据输入控制块,用于输入数据。
除此之外,访存阶段的最后还将根据icode
判断出的指令有效性以及内存状况产生instr_valid
和imem_error
信号来计算状态码。
写回阶段硬件设计
首先为寄存器文件系统添加M
和E
,这两个写端口,对应的地址输入为dstE
和dstM
。需要注意的是,当执行条件传送指令 (cmov) 时,写入操作还要根据执行阶段计算出的cnd
信号,当条件不满足条件时,以将目的寄存器设置为0xF
来禁止写入寄存器文件。
更新阶段硬件设计
在更新阶段,PC
的值有以下三种情况:
- 当前执行的指令是函数调用指令
call
,此使将PC
值设为call
指令的常数字段。 - 当前执行的指令是函数返回指令
ret
,此使将PC
值设为ret
指令在访存阶段从内存中读出的返回地址。 - 当前执行的指令是跳转指令
jxx
,此使若满足跳转条件(cnd = 1),则新的PC值等于跳转指令的常数字段;若不满足跳转条件(cnd = 0),则新的PC
值等于当前PC
值加上当前指令长度。 - 数据输入控制块,用于输入数据。