处理器体系结构
指令集Y86-64寄存器
Y86-64处理器每个时钟周期执行一条完整的Y86-64指令。
X86-64寄存器有
程序员可见的状态的寄存器:
Y86-64的状态类似于x86-64。有15个程序寄存器:%rax,%rcx,%rdx,%rbx,%rsp,%rbp,%rsi,%rdi和%r8到%r14。这里省略了%r15以简化指令的编码。
其中%rsp被入栈、出栈、调用和返回指令作为栈指针。
有3个一位的条件码:ZF,SF和OF,它们保存着最近的算术或逻辑指令所造成影响的有关信息。
程序计数器(PC)存放当前正在执行指令的地址。
Y86-64程序用虚拟地址来引用内存位置。
程序状态的最后一个部分是状态码Stat,它表明程序执行的总体状态。它会指示是正常运行,还是出现了异常。
Y86-64指令
Y86-64指令集基本上是x86-64指令集的一个子集。它包括8字节整数操作,寻址方式较少,操作也较少。
Y86-64的一些细节
X86-64的movq指令分成了4个不同的指令:irmovq,rrmovq,mrmovq和rmmovq分别显示地指明源和目的的格式。源可以是立即数(i),寄存器(r)或内存(m)。指令名字的第一个字母表明了源的类型。目的可以是寄存器(r)或内存(m)。指令民资的第二个字母表明了目的的类型。在决定如何实现数据传送时,显示地指令数据传送这4中类型是很有帮助的。
两个内存传送指令中的内存引用方法是简单的基址和偏移量形式。在地址计算中,我们不支持第二变址寄存器(second index register)和任何寄存器值的伸缩(scaling)。
同x86-64一样,我们不允许一个内存地址直接传送到另一个内存地址。另外,也不允许将立即数传送到内存。
有4个整数操作指令,Opq。它们是addq,subq,andq和xorq。它们只对寄存器数据进行操作,而x86-64还允许对内存数据进行这些操作。这些指令会设置3个条件码ZF,SF和OF(零,符号和溢出)。
7个跳转指令(jxx)是jmp,jle,jl,je,jne,jge和jg。根据分支指令的类型和条件代码的设置来选择分支。
有6个传送指令(cmovXX):cmovele,cmovl,cmove,cmovne,cmovge和cmovg。这些指令的格式与寄存器-寄存器传送指令rrmovq一样,但是只有当条件码满足所需要的约束时,才会更新目的寄存器的值。
call指令将返回地址入栈,然后跳到目的地址。ret指令从这样的调用中返回。
Pushq和popq指令实现了入栈和出栈,就像在x86-64中一样。
halt指令停止指令的执行。X86-64中有一个与之相当的指令hlt。X86-64的应用程序不允许使用这条指令,因为它会导致整个系统暂停运行。对于Y86-64来说,执行halt指令会导致处理器停止,并将状态码设置为HLT。
指令编码
每条指令需要1~10个字节不等,这取决于需要哪些字段。每条指令的第一个字节表明指令的类型。这个字节分为两个部分,每部分4位:高4位是代码(code)部分,低4位是功能(function)部分。功能值只有在一组相关指令公用一个代码时才有用。
15个程序寄存器中每个都有一个相应的范围在0到0xE之间的寄存器标识符(register ID)。Y86-64中的寄存器编号跟x86-64中的相同。程序寄存器存在CPU中的一个寄存器文件中,这个寄存器文件就是一个小的,以寄存器ID作为地址的随机访问存储器。在指令编码中以及在我们的硬件设计中,当需要指令不应访问任何寄存器时,就用ID值0xF来表示
有的指令只有一个字节长,而有的需要操作数的指令编码就更长一些。首先,可能有附加的寄存器指示符字节(register specifier byte),指定一个或两个寄存器。这些寄存器字段称为rA和rB。根据指令类型,指令可以指定用于数据源和目的寄存器,或是用于地址计算的基址寄存器。没有寄存器操作数的指令,例如分支指令和call指令,就没有寄存器指示符字节。哪些只需要一个寄存器操作的指令(irmovq,pushq和popq)将另一个寄存器指示符设为0xF。
有些指令需要一个附加的4字节常数字(constant word)。这个字能作为irmovq的立即数数据,rmmovq和mrmovq的地址指示符的偏移量,以及分支指令和调用指令的目的地址。
分支指令和调用指令的目的是一个绝对地址,不想IA32中那样使用PC(程序计数器)相对寻址方式。处理器使用PC相对寻址方式,分支指令的编码会更简洁。
同IA32一样,所有整数采用小端法编码。
Eg:rmmovq %rsp,0x12345678abcd(%rdx)的字节编码。从上图中偶尔们可以看到rmmovq的第一个字节为40。源寄存器%rsp应该编码放在rA字段中,而基地址寄存器%rdx应该编码放在rB字段中。得到寄存器指示符42。最后,偏移量放在8字节的常数字中。首先在0x123456789abcd的前面填充上0变成8个字节,变成字节序列00 01 23 45 67 89 ab cd。写成按字节反序就是cd ab 89 67 45 23 01 00。将它们都连接起来就得到指令的编码4041cdab896745230100。
Y86-64异常
对于Y86-64来说,程序员课件的状态包括状态码Stat,它描述程序执行的总体状态。这个代码可能的值如下图:
代码值1,命名为AOK,表示程序执行正常,
代码值2,命名HLT,表示处理器执行了一条halt指令
代码值3,命名为ADR,表示处理器试图从一个非法内存地址读或者向一个非法内存地址写。
代码值,命名为INS,表示遇到了非法的指令代码。
Y86-64程序
X86-64程序是由GCC编译器产生的。Y86-64代码与之类似但有以下不同点:
Y86-64将常数加载到寄存器,因为它在算术指令中不能使用立即数。
要实现从内存读取一个数值并将其余一个寄存器相机,Y86-64代码需要两条指令,而x86-64只需要一条addq指令。
程序的完整编码
Y86-64的顺序实现
取值(fetch):取指阶段从内存读取指令字节,地址为程序计数器PC的值。从指令中抽取出指令指示符字节的两个四位部分,称为icode(指令代码)和ifun(指令功能)。它可能取出一个寄存器指示符字节,指明一个或两个寄存器操作数指示符rA和rB。它还可能取出一个四字节常数字valC。它按顺序方式计算当前指令的下一条指令的地址valP。也就是说,valP等于PC的值加上已取出指令的长度。
译码(decode):译码阶段从寄存器文件读入最多两个操作数,得到值valA和/或valB。通常,它读入指令rA和rB字段指明的寄存器,不过有些指令时寄存器%rsp的。
执行(execute):在执行阶段,算术/逻辑单元(ALU)要么执行指令指明的操作(根据ifun的值),计算内存引用的有效地址,要么增加或减少栈指针。得到值,我们称为valE。在此,也可以设置条件码。对一条条件传送指令来说,这个阶段会检查条件码和传送条件,如果条件成立,则更新目标寄存器,同样,对一条跳转指令来说,这个阶段会决定是不是应该选择分支。
访存(memory):访存阶段可以将数据写入内存,或者从内存读出数据。读出的值为valM。
写回(write back):写回阶段最多可以写两个结果到寄存器文件。
更新PC(PC update):将PC设置成下一条指令的地址。
SEQ硬件结构
实现所有Y86-64指令需要的计算可以被组织成6个基本阶段:取值,译码,执行,访存,写回和更新PC。下图给出了一个能执行这些操作的抽象表示:
取值:将程序计数器寄存器作为地址,指令内存读取指令字节。PC增加器(PC incrementer)计算valP,即增加了的程序计数器。
译码:寄存器文件有两个读端口A和B,从这两个端口同时读寄存器值valA和valB。
执行:执行阶段会根据指令的类型,将算术/逻辑单元(ALU)用于不同的目的。对整数操作,它要执行指令所指定的运算。对其他指令,它会作为一个加法器来计算增加或减少栈指针,或者计算有效地址,或者只是简单地加0,将一个输入传递到输出。条件码寄存器(CC)有三个条件码位。ALU负责计算条件码的新值。当执行条件传送指令时,根据条件码和传送条件来计算决定是否更新目标寄存器。同样,当执行一条跳转指令时,会根据条件码和跳转类型来计算分支信号Cnd。
访存:在执行访存操作时,数据内存读出或写入一个内存字。指令和数据内存访问的是相同的内存位置,但是用于不同的目的。
写回:寄存器文件有两个写端口。端口E用来写ALU计算出来的值,而端口M用来写从数据内存中读出的值。
PC更新:程序计数器的新值选择自:valP,下一条指令的地址;valC,调用指令或跳转指令指定的目标地址;valM,从内存读取的返回地址。
取指阶段
译码和写回阶段
执行阶段
访存阶段
访问主存
Movq A,%rax
地址请A的内容被加载到寄存器%rax中。CPU芯片上称为总线接口(bus interface)的电路在总线上发起读事务。读事务是由三个步骤组成的。首先,CPU将地址A放到系统总线上。I/O桥将信号传递到内存总线。接下来,主存感觉到内存总线上的地址信号,从内存总线读地址,从DRAM取出数据字,并将数据写到内存总线。I/O桥将内存总线信号翻译成系统总线信号,然后沿着系统总线传递。最后CPU感觉到系统总线上的数据,从总线上读数据,并将数据复制到寄存器%rax。
总线结构示例
访问磁盘
CPU使用一种称为内存映射I/O(memory-mapped I/O)的技术来向I/O设备发射命令。在使用内存映射I/O的系统中,地址空间中有一块地址是为与I/O设备通信保留的。每个这样的地址称为一个I/O端口(I/Oport)。当一个设备连接到总线时,它与一个或多个端口相连(或它被映射到一个或多个端口)。
来看一个简单的例子,假设磁盘控制器映射到端口Oxa0。随后,CPU可能通过执行三个对地址Oxa0的存储指令,发起磁盘读:第一个指令是发送一个命令字,告诉磁盘发起一个读,同时还发送了其他的参数,例如当读完成时,是否中断CPU。第二个指令指令应该读的逻辑块号。第三条指令指明应该存储磁盘扇区内容的主存地址。
在磁盘控制器收到CPU的读命令之后,它将逻辑块号翻译成一个扇区地址,读该扇区的内容,然后将这些内容直接传送到主存,不需要CPU的干涉。设备以自己执行读或者写总线事务而不需要CPU干涉的过程,称为直接内存访问(Direct Memory Access,DMA)。这种数据传送称为DMA传送(DMA transfer)。
在DMA传送完成,磁盘扇区的内容被安全地存储在主存中以后,磁盘控制器通过给CPU发送一个中断信号来通知CPU。基本思想是中断会发信号到CPU芯片的一个外部引脚上。这会导致CPU暂停它当前正在做的工作,跳转到一个操作系统例程。这个程序会记录下I/O已经完成,然后将控制返回到CPU被中断地地方。