4.处理器体系架构|
指令集"Y86-64"
4.1程序员可见的状态
RF:程序寄存器 CC:条件码 Start:程序状态(程序的执行状态) PC:程序计数器 DMEM:内存
15个64位内存的程序寄存器 Y86-64 相比x86,省略了%r16 特别的:%rsp:栈指针
movq:rr movq、ir movq、rm movq和 mr movq
movq前两个字母表示了源和目的格式
irmovq 源操作数是立即数(Immediate) 目的操作数是寄存器(register) rrmovq 源操作数是寄存器(register) 目的操作数是寄存器(register) rmmovq 源操作数是寄存器(register) 目的操作数是内存(memory) mrmovq 源操作数是内存(memory) 目的操作数是寄存器(register)
对上面数据传送指令进行编码 上图中每条指令第一个字节(8bit)(前两位)表明指令的类型,这个字节分为两部分,每部分占(4bit),高四位(4bit)表示指令代码,低四位(4bit)表示指令功能
下面为了方便给寄存器进行编号
寄存器编号(0-14)
0 %rax 1 %rcx 2 %rdx 3 %rbx 4 %rsp 5 %rbp 6 %rsi 7 %rdi 8 %r8 9 %r9 A %r10 B %r11 C %r12 D %r13 E %r14 F 五寄存器
irmovq,该指令的指令代码为3,指令功能为0 由于源操作数是立即数,因此用0xF进行填充
4.1.1 四条整数操作
对于整数的操作,我们定义了四条整数操作指令,和x86-64不同,我们定义的指令只允许对寄存器中的数据进行操作
以上四条指令属于同一类型,指令代码是一样的,不同的是指令的功能
Opq rA,rB 6|fn|rA|rB addq 6|0 subq 6|1 andq 6|2 xorq 6|3
以上四条指令属于同一类型,因此指令代码是一样的,不同的是指令的功能
4.1.2 七条跳转指令
对于跳转,我们定义了7条跳转指令,它们的跳转条件和x86-64中的跳转指令相同,都是根据条件码的某种组合来判断是否进行跳转
jxx Dest
jmp 7|0 jle 7|1 jl 7|2 je 7|3 jne 7|4 jge 7|5 jg 7|6
JMP Label 1 直接跳转 JMP *Operand 1 间接跳转 JE Label JZ ZF 相等 JNE Label JNZ ~ZF 不相等 JS Label SF 负数 JNS Label ~SF 非负数
JG JNL 大于(有符号>) JGE JNLE 大于等于(有符号) JL JNG 小于(有符号) JLE JNGE 小于等于(有符号) JA JNB 大于(无符号) JAE JNBE 大于等于(无符号) JB JNA 小于(无符号) JBE JNAE 小于等于(无符号)
4.1.3 六条数据传送指令
rrcomvq有相同的指令格式,只有条件码满足条件时才会更新目的寄存器的值
comve S,R 相等
cmovne S,R 不相等/非零
cmovs S,R 负数
cmovns S,R 非负数
cmovg S,R 大于(有符号)
cmovge S,R 大于等于(有符号)
cmovl S,R 小于(有符号)
cmovle S,R 小于等于(有符号)
cmova S,R 大于(无符号)
cmovae S,R 大于等于(无符号)
cmovb S,R 小于(无符号)
cmovbe S,R 小于等于(无符号)
cmovxx rA,rB rrmovq 2|0 comvle 2|1 comvl 2|2 cmove 2|3 cmovne 2|4 cmovge 2|5 cmovg 2|6
系统操作指令 halt 0 0 nop 1 0 call Dest 8 0 Dest ret 9 0 pushq rA A 0 rA F popq rA B 0 rA F
指令编码
在上面的操作指令中
- halt指令停止指令的执行,执行该指令会导致处理器停止,并将状态码设置为HLT
- nop 指令表示一个空操作
- call 和ret指令分别实现函数的调用和返回
- push 和pop 指令分别实现入栈和出栈的操作
指令编码 rmmovq %rsp,0x123456789abcd(%rdx)
rmmovq 源操作数是寄存器(register) 目的操作数是内存(memory)
根据前面rmmovq指令的编码定义,该指令二进制表示第一个字节为0x40; 指令操作数部分的寄存器%rsp对应的寄存器编号为0x4,基址寄存器rdx对应的编号为0x2 该指令二进制表示中的第二个字节为0x42
指令编码中偏移量占8个字节,因此我们需要在该偏移量前面补0来凑齐8个字节。 又由于我们采用小端法存储,因此我们还要对偏移量进行字节反序操作。最终我们得到该指令的二进制表示如下
40 42 cd ab 89 67 45 23 01 00
Y86-64的状态码
值 | 名字 | 含义 |
---|
1 | AOK | 正常操作 | 2 | HLT | 遇到halt指令 | 3 | ADR | 遇到非法地址 | 4 | INS | 遇到非法指令 |
Y86-64中c语言的汇编翻译
long sum(long *start,long count){
long sum=0;
while(count){
sum+=*start;
start ++;
count --;
}
return sum;
}
gcc -Og -S sum.c
得到
c语言代码的作用是计算一个数组的元素之和。其中指针start指向数组的起始位置,count用于表示数组的长度 可以看到,除了数据传送指令之外,Y86-64指令与x86-64的指令相差不大
看看上面的汇编代码是如何翻译成二进制指令的
阶段的概念
处理器在执行单条指令的时候,往往也包含着很多操作 过程组织成下面6个阶段
- 取址
处理器执行的所有指令都需要取址 在Y86-64指令系统中,指令的长度不是固定的,因此取址阶段需要根据指令代码判断指令是否含有寄存器指示符、是否含有常数来计算当前的指令长度
- 译码
在译码阶段中,处理器从寄存器文件中读取数据,寄存器文件有两个端口,可以支持同时进行两个读操作
- 执行
指令被正式执行的阶段,在该阶段中,算术逻辑单元(ALU)主要执行三类操作,执行算术逻辑单元,计算内存引用的有效地址,针对push和pop指令的运算
- 访存
对内存进行读写操作的阶段
- 写回
将执行结果写回到寄存器文件中
- 更新
将PC更新为下一条指令的地址
subq指令的各个阶段
看看上面这条subq指令各阶段包含的内容
如上图所示
- 取址阶段,根据指令代码来计算指令长度。
- 译码阶段,根据寄存器指示符来读取寄存器的值。
- 执行阶段,ALU 根据译码阶段读取到的操作数以及指令来执行具体的运算,并设置条件码寄存器。
- 访存阶段,由于减法指令不需要读写内存,因此该阶段无操作。
- 写回阶段,将 ALU 的运算结果写回寄存器。
- 更新阶段,更新程序计数器。
imovq指令的各个阶段
imovq指令的各个阶段 irmovq $8,%rsp
如前图所示
- 取址阶段,该指令既含有寄存器指示符字节,也含有常数字段。
- 译码阶段,该指令不需要从寄存器中读取数据,译码阶段无操作。(源是立即数$8)
- 执行阶段,虽然该指令仅仅传送数据,看似不需要 ALU,但由于 ALU 的输出端与寄存器的写入端相连,数据的传送还是需要经过 ALU,因此该指令将立即数加 0.
- 访存阶段,该指令不需要读写内存,因此该阶段无操作。
- 写回阶段,将 ALU 的运算结果写回寄存器。
- 更新阶段,更新程序计数器。
rrmovq指令的各个阶段
如前图所示
- 取址阶段,该指令既含有寄存器指示符字节,也含有常数字段。
- 译码阶段,从寄存器中读取数据。
- 执行阶段,ALU 根据偏移量和基址寄存器来计算访存地址。
- 访存阶段,将寄存器 rsp 的数值写入内存中。
- 写回阶段,由于内存地址由执行阶段得出并写入寄存器,因此写回阶段不需要进行操作。
- 更新阶段,更新程序计数器。
pushq指令的各个阶段
pushq %rdx | a| 0 |2| f| 上面这条pushq指令各阶段的内容如下
-
取址阶段,该指令含有寄存器指示符,不含常数,因此指令长度为2字节。 -
译码阶段,由于 pushq 指令要将寄存器 rdx 的值保存到栈上,因此该指令不仅需要读取寄存器 rdx 的值,还需要读取寄存器 rsp 的值。 -
执行阶段,ALU 计算内存地址。 -
访存阶段,将寄存器 rdx 的值写到栈上。 -
写回阶段,由于寄存器 rsp 指向的内存地址发生了变化,因此更新寄存器rsp的值。 -
更新阶段,更新程序计数器。
je指令的各个阶段
上图阶段内容如下
- 取址阶段,该指令含有常数字段,不含寄存器指示符字节,因此指令长度为 9字节。
- 译码阶段,不需要读取寄存器,无操作。
Cond的引入
- 执行阶段,标号为Cond 的硬件单元根据条件码和指令功能来判断是否执行跳转,该模块产生一个信号Cnd,若Cnd=1,则执行跳转;Cnd = 0 则不执行跳转。
- 访存阶段,无操作。
- 写回阶段,无操作。
- 更新阶段,若 Cnd = 1,将 PC 的值设为0x040;若 Cnd = 0,则将 PC 的值设为当前值加 9。
4.处理器体系架构||
取地址的硬件设计
-
在取址阶段中,取址操作一程序计数器PC的值作为起始地址 -
由于Y86-64指令集中最长的指令占10字节,取址操作每次从指令内存读取10个字节 -
10字节分为两部分
- 第一部分占1字节
- 第二部分占9字节
- 例如编码为 30 |f8 08 00 00 00 00 00 00 00 的指令,将被分为30和f008----
Split硬件单元处理第一部分(1Byte)
Split的硬件单元处理第一部分,它将这个字节分成两部分,每部分占4个比特位,使这个字节分为两个字段,分别为指令代码和指令功能。用icode和ifun 表示
根据icode可以确定当前指令的状态信息,例如指令的合法性,如果icode在0x0到0xB之间,那么它就是一条合法指令,此外通过icode可以判断当前指令是否包含寄存器指示符字节和常数字节
通过前面说过的判断结果就可以计算出当前指令的长度,例如一个指令既含寄存器指示符字节,又含常数字节,那它的长度就是10字节,如果既不含寄存器指示符字节,又不含常数字节,它的长度就是1字节
可以通过PC值加上当前的指令长度来计算内存中下一条指令的地址,用于后续的更新阶段
Align硬件单元处理第二部分
Align来产生寄存器字段和常数字段,该硬件单元通过信号need_regids来判断该指令是否包含寄存器指示符字节
- 若need_regids =1,说明该指令包含寄存器指示符字节,那么第一个字节将被分成两个部分,每个部分占4个bit,然后分别装入寄存器指示符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值加上当前指令长度 - 数据输入控制块,用于输入数据
|