一、Y86-64指令集体系架构
指令集体系架构定义了状态单元、指令集、编码、规范及异常事件处理。
1.1 Y86-64指令集
Y86-64是Intel x86-64得一个简单版本。
Y86-64处理器的状态包括:
-程序寄存器,除了%r15的64位寄存器;
-条件码,由算术或逻辑指令产生的标识,包括ZF、SF与OF;
-程序计数器【Program Counter,PC】记录下一条指令的地址;
-程序状态【Program Status,Stat】指示程序的正常操作或错误状态;
-内存,按字节编址的储存阵列,按小端顺序储存。
在Y86-64处理器的状态之上,规定了一系列的指令集,包括
-处理器停止指令halt,无操作数;
-空指令nop,无操作数;
-寄存器传送指令rrmovq,操作数为寄存器;
-立即数寄存器传送指令irmovq,操作数为立即数与寄存器;
-寄存器内存传送指令rmmovq,操作数为寄存器与地址;
-内存寄存器传送指令mrmovq,操作数为寄地址与寄存器;
-运算指令OPq,操作数为寄存器;
-跳转指令jC,操作数为地址;
-条件传送指令cmovC,操作数为寄存器;
-调用指令call,操作数为地址;
-返回指令ret,无操作数;
-压栈指令pushq,操作数为寄存器;
-弹栈指令popq,操作数为寄存器。
1.2 Y86-64指令编码
指令需要1到10个字节不等,其第一个字节表征着主要功能,其高4位为指令码,低4位为功能码,例如addq编码为60
,subq编码为61
,其均是代码号为0x0110
的运算指令;jmp编码为70
,jle编码为71
,其均是代码号为0x0111
的跳转指令。
在指令的操作数中包含寄存器,每一个程序寄存器都有一个与之相对应的0x0到0xE之间的标识符,形如
在运算指令中,加运算的编码为60
,减运算的编码为61
,和运算的编码为62
,异或运算的编码为63
。加法指令的通用格式为
addq rA, rB
当rA,rB取%rax与%rsi时,其寄存器编码分别为0与6,那么指令编码表示为60 06
。
在传送指令中,寄存器传送指令的指令码为2,立即数寄存器传送指令的指令码为3,寄存器内存传送指令的指令码为4,内存寄存器传送指令的指令码为5;操作数中的立即数或内存地址均占8个字节,例如
irmovq $0xabcd, %rax
其编码为30 f2 cd ab 00 00 00 00 00 00
,其中,立即数寄存器传送指令的操作数高4位为0xF,即不使用寄存器。
在跳转指令中,跳转的操作数为8字节的目的地址,其对目的地址进行绝对编码,相对于x86-64的相对编码。
在条件指令中,无条件的功能码为0,小于等于为1,小于为2,等于为3,不等于为4,大于等于为5,大于为6;其与传送指令类型结合,形成条件传送指令;或与跳转指令结合,形成条件跳转指令。
在栈指令中,Y86-64的程序栈与x86-64相似,由%rsp指向栈顶,栈操作包括压栈与弹栈,编码分别为A0
与B0
,假设对%rax进行栈操作,那么压栈与弹栈代码为
pushq %rax
popq %rax
其编码为a0 0f
与b0 0f
。
在调用指令中,调用指令编码为80
,调用的地址操作数使用绝对编码,返回指令的编码为90
。
此外,还有空指令编码为10
与停止指令00
。
在状态条件中,包括正常操作,编码为1;遇到停止指令,编码为2;遇到错误地址,编码为3;遇到无效指令,编码为4,用于stat的赋值。当stat = 1时,程序继续执行,否则程序停止。
1.3 Y86-64程序
给定C函数代码如下
long sum(long *start, long count){
long sum = 0;
while (count){
sum += *start;
start++;
count--;
}
return sum;
}
其x86-64的汇编代码为
sum:
movl $0, %eax
jmp .L2
.L3:
addq (%rdi), %rax
addq $8, %rdi
subq $1, %rsi
.L2:
testq %rsi, %rsi
jne .L3
rep ret
其Y86-64的汇编代码为
sum:
irmovq $8, %r8
irmovq $1, %r9
xorq %rax, %rax
andq %rsi, %rsi
jmp test
loop:
mrmovq (%rdi), %r10
addq %r10, %rax
addq %r8, %rdi
subq %r9, %rsi
test:
jne loop
ret
可以看出,x86-64与Y86-64遵循通用的模式,但是依然有一定的不同:
-Y86-64的算术指令不允许使用立即数,必须将立即数加载到寄存器;
-Y86-64不允许直接对内存的值进行操作,需要先加载到寄存器;
-Y86-64必须隐式的设置条件码。
二、逻辑设计与硬件控制
2.1 位级组合电路与HCL布尔表达式
将很多的逻辑门组合成一个网,构建的计算块称为组合电路。硬件描述语言【Hardware Description Language,HDL】用于描述电路的结构,形成了组合电路的表达式。
考虑同或网络,形如
其HCL表达式为
bool s = (a && b) || (!a && !b);
其实现了位级相等的判断。
一种简单而有用的组合电路是多路复用器【multiplexor,MUX】,其可以根据输入控制信号的值,从一组不同的数据信号中选出一个,典型的形如
其HCL表达式为
bool out = (s && a) || (!s && b);
其在 s = 0 s = 0 s=0时输出 b b b,而在 s = 1 s = 1 s=1时输出 a a a。
2.2 字级组合电路与HCL整数表达式
通过将逻辑门组合成更大的网,可以构造出能计算更复杂函数的组合电路。执行字级计算的组合电路根据输入字的各个位,用逻辑门计算输出字的各个位。例如判断64位字 A A A与 B B B是否相等,形如
其在 A A A与 B B B的每一位都相等时,输出才为1。在HCL中,所有字级的信号声明均为int,并不指定字的大小。其HCL表达式为
bool eq = (A == B)
字级的多路复用电路根据输入位 s s s产生字级 o u t out out,在HCL的表达式为
[
select_1: expr_1;
select_2: expr_2;
...
select_k: expr_k;
]
考虑位级输入位 s s s与字级输入 A A A与 B B B,在 s = 1 s = 1 s=1时输出 A A A,其组合电路为
其HCL可以描述为
word Out = [
s: A;
1: B;
]
其中,第二个表达式为1,表明前面没有情况被选中的情况下的默认情况。
处理器的算术逻辑单元【arithmetic and logic unit,ALU】是一种重要的组合电路,其抽象组合电路为
其HCL可以描述为
word Out = [
(s == 0): X + Y;
(s == 1): X - Y;
(s == 2): X & Y;
(s == 3): X ^ Y;
]
2.3 储存器与时钟
为了引入按位存储信息的设备,需要使用时序电路,由时钟周期信号控制了加载时间。
时钟寄存器,储存时钟信号控制寄存器加载值;随机访问储存器,简称内存,用地址选择读写字,包括虚拟内存系统与程序寄存器文件。
寄存器文件有两个读端口、一个写端口,允许同时进行多个读和写操作。读端口包括地址输入scrA和scrB,在读入地址输入后从数据输出valA和valB输出数据。写端口包括地址输出dstW与数据输入valW,寄存器文件需要的地址为寄存器标识。当输入地址无效时,寄存器文件的error标识会置为1。
三、Y86-64的顺序实现
Y86-64的顺序【Sequential,SEQ】处理器在时钟周期上处理完整指令的所有步骤。
3.1 SEQ处理阶段
SEQ处理一条指令可以分为如下阶段:
-取指:从内存中读取指令字节;
-译码:从程序寄存器中读入操作;
-执行:算术逻辑单元执行计算;
-访存:从内存中读出或写入数据;
-写回:更新程序寄存器;
-更新:将PC设置为下一指令的地址。
处理器无限循环上述的执行上述各个阶段,并在发生异常时停止。
算术指令OPq rA, rB
的编码为6Fn rArB
。其中,Fn表示功能码,rA表示寄存器A的标识符,rB表示寄存器B的标识符,那么该指令的阶段可以描述为:
icode:ifun = M[PC]
rA:rB = M[PC + 1]
valP = PC + 2
valA = R[rA]
valB = R[rB]
valE = valB OP valA
set CC
R[rB] = valE
PC = valP
寄存器内存移动指令rmmovq rA, Dst(rB)
的编码为40 rArB Dst0 Dst1 Dst2 Dst3 Dst4 Dst5 Dst6 Dst7
。其中,Dst表示目标地址,长度为8Byte,那么该指令的阶段可以描述为:
icode:ifun = M[PC]
rA:rB = M[PC + 1]
valC = M[PC + 2]
valP = PC + 10
valA = R[rA]
valB = R[rB]
valE = valB + valC
M[valE] = valA
PC = valP
弹栈指令popq rA
的编码为b0 rAf
。该指令的阶段可以描述为:
icode:ifun = M[PC]
rA:rB = M[PC + 1]
valP = PC + 2
valA = R[%rsp]
valB = R[%rsp]
valE = valB + 8
valM = M[valA];
R[%rsp] = valE
R[rA] = valM
PC = valP
条件传送指令cmovC rA, rB
的编码为2fn rArB
。该指令的阶段可以描述为:
icode:ifun = M[PC]
rA:rB = M[PC + 1]
valP = PC + 2
valA = R[rA]
valB = 0x0
valE = valA + valB
if !Cond(CC, ifun)
rB = 0xF
R[rB] = valE
PC = valP
条件传送指令在不满足条件时取端口值为0xF来取消数据写入寄存器。
条件跳转指令jC Dst
的编码为7fn Dst0 Dst1 Dst2 Dst3 Dst4 Dst5 Dst6 Dst7
。该指令的阶段可以描述为:
icode:ifun = M[PC]
valC = M[PC + 1]
valP = PC + 9
Cnd = Cond(CC, ifun)
PC = Cnd? valC : valP
其计算下一地址与目的地址,并根据条件码与分支条件做出选择。
调用指令call Dse
的编码为80 Dst0 Dst1 Dst2 Dst3 Dst4 Dst5 Dst6 Dst7
。该指令的阶段可以描述为:
icode:ifun = M[PC]
valC = M[PC + 1]
valP = PC + 9
valB = R[%rsp]
valE = valB + -0x8
M[valE] = valP
R[%rsp] = valE
PC = valC
返回指令ret
的编码为90
。该指令的阶段可以描述为:
icode:ifun = M[PC]
valA = R[%rsp]
valB = R[%rsp]
valE = valB + 8
valM = M[valA]
R[%rsp] = valE
PC = valM
3.2 SEQ取指控制逻辑
取指阶段的硬件结构如下
其中,指令的最高位字节Split产生了指令功能码icode与ifun,instr_valid用于检测指令的合法性,need_valC与need_regids则根据指令检查寄存器指示字节与常数;Align根据need_valC与need_regids选择不同位输出到rA、rB或valC;valP用于产生新的PC值。
取指控制逻辑的HCL描述为
int icode = [
imem_error: INOP;
1: imem_icode;
];
int ifun = [
imem_error: FNONE;
1: imem_ifun;
];
bool need_regids = icode in {
IRRMOVQ, IOPQ, IPUSHQ, IPOPQ, IIRMOVQ, IRMMOVQ, IMRMOVQ
};
bool instr_valid = icode in {
INOP, IHALT, IRRMOVQ, IIRMOVQ. IRMMOVQ, IMRMOVQ, IOPQ, IJXX, ICALL, IRENT, IPUSHQ, IPOPQ
};
3.3 SEQ译码写回控制逻辑
译码与写回阶段均建立在寄存器之上,硬件结构如下
寄存器文件包括读端口A与B,及写端口E与M,通过地址srcA、srcB、dstM、dstE控制;信号值Cnd由执行阶段计算出,标明了是否满足条件转移。
译码逻辑控制的HCL描述为
int srcA = [
icode in {IRRMOVQ, IRMMOVQ, IOPQ, IPUSHQ}: rA;
icode in {IPOPQ, IRET}: RRSP;
1: RNONE;
];
写回逻辑控制的HCL描述为
int dstE = [
icode in {IRRMOVQ} && Cnd: rB;
icode in {IRRMOVQ, IOPQ} :rB;
icode in {IPUSH, IPOPQ, ICALL, IRET}: RRSP;
1: RNONE;
];
3.4 SEQ执行控制逻辑
执行阶段的硬件结构如下
其中,ALU单元用于运算并生成条件码;CC是包含三个条件码的寄存器;cond用于计算条件跳转或转移的标识。
执行逻辑控制的HCL描述为
int aluA = [
icode in {IRRMOVQ, IOPQ}: valA;
icode in {IIRMOVQ, IRMMOVQ, IMRMOVQ}: valC;
icode in {ICALL, IPUSHQ}: -8;
icode in {IRET, IPOPQ}: 8;
];
int alufun = [
icode == IOPQ : ifun;
1 : ALUADD;
];
3.5 SEQ访存控制逻辑
访存阶段的硬件结构如下
其中,状态码stat描述了当前指令的执行情况。
访存逻辑控制的HCL描述为
int mem_addr = [
icode in {IRMMOVQ, IPUSHQ, ICALL, IMRMOVQ}: valE;
icode in {IPOPQ, IRET}: valA;
];
bool mem_read = icode in {
IMRMOVQ, IMRMOVQ, IPOPQ, IRET
}
3.6 SEQ更新控制逻辑
更新阶段的硬件结构如下
其根据指令选择新的PC地址。
更新逻辑控制的HCL描述为
int new_pc = [
icode == ICALL : valC;
icode == IJXX && Cnd : valC;
icode == IRET : valM;
1 : valP;
];
四、Y86-64的流水线实现
4.1 流水线系统
在SEQ结构中,指令的执行按照阶段顺序发生,一次只能处理一个操作,这导致硬件单元在一个时钟周期内的仅一部分被使用,且时钟也必须足够慢。为此,使用流水线结构提高系统的吞吐量与轻微的延迟。
在流水线系统中,在顺序系统中插入寄存器,可以使得在同一时间,不同指令的不同阶段进行运算,可以显著的提高吞吐量,但是也存在如下局限:
-当个阶段具有不一致的延迟时,流水线的吞吐量受到花费时间最长的阶段限制;
-当尝试加深流水线时,将结果载入寄存器的时间会显著影响性能;
-程序中普遍具有操作依赖前一个操作的结果,称为数据相关,如果结果没有及时反馈,流水线将改变系统的行为,形成数据错误的数据冒险或跳转错误的控制冒险。
4.2 流水线结构的实现
在SEQ结构中,将PC更新阶段移动到时钟周期开始,在某一指令的更新阶段完成并到达取指阶段,得到下一条指令地址的同时进入下一条指令的更新阶段,使得处理器成为流水线系统,称为SEQ+ 结构。在SEQ+结构中,PC将不再存储在寄存器中,而由其他信息直接决定。那么SEQ+各阶段为:
-取指:选择PC,读取指令,并计算新的PC值,标记为F;
-译码:从程序寄存器中读入操作,标记为D;
-执行:算术逻辑单元执行计算,标记为E;
-访存:从内存中读出或写入数据,标记为M;
-写回:更新程序寄存器,标记W。
如果在各个阶段的硬件之间添加流水线寄存器,保存指令执行的中间值,使得信号重新排列,称为PIPE- 结构。由于数据相关,在各个阶段之间需要形成反馈路径,包括:
-PC值预测的备选值需要前一指令决定;
-分支信息跳转由前一指令决定;
-寄存器的读取需要寄存器及时更新,由前一指令决定;
-返回点由内存中读取需要内存的即使更新,由前一指令决定。
在当前指令完成取值后,没有足够的时间决定下一指令的地址,会进行预测。预测的策略包括:
-非转移指令,预测为valP,永久可靠;
-调用指令与无条件转移指令,预测为valC,永久可靠;
-条件转移指令,预测为valC,正确率大约为60%;
-返回指令,不预测,暂停等待ret指令到达写回阶段。
当条件转移预测错误时需要进行还原,需要访问失败的PC。
4.3 流水线结构的高级实现
在PIPE-结构中,依然存在着数据冒险和控制冒险的问题。如果使用动态插入气泡暂停流水线,会降低流水线的速度。
一种解决数据冒险的方法是数据转发,其将指令生成的值直接传递到译码阶段。考虑如下代码
0x00: irmovq $10, %rdx
0x0a: irmovq $3, %rax
0x14: nop
0x15: nop
0x16: addq %rdx, %eax
0x18: halt
那么流水线执行阶段形如
其中,第6周期执行的代码为
W:
W_dstE = %rax
W_valE = 3
D:
src_A = %rdx
src_B = %rax
0x00的写回对%rax写的同时,0x16的译码阶段对%rax读,为此,在%rax保存到W寄存器的同时转发提供给译码阶段,形成旁路路径。在有多重转发发生时,使用各个指令的各个阶段中,阶段最早从流水线阶段获取的匹配值。当使用数据转发依然可能出现冒险时,将指令暂停在取指和译码阶段,并在硬件的执行阶段持续注入气泡,称为加载/使用冒险。使用数据转发的流水线HCL描述为
int d_valA = [
D_icode in { ICALL, IJXX}: D_valP;
d_srcA == e_dstE : e_valE;
d_srcA == M_dstM : m_valM;
d_srcA == M_dstE : M_valE;
d_srcA == M_dstM : W_valM;
d_srcA == W_dstE : W_valE;
1 : d_valA
]
在控制冒险中,仅取出预测分支的两条指令,使得其只进行到译码阶段,并在预测错误时用气泡代替原指令的剩余阶段,此时不会对任何可见状态产生影响。考虑如下代码
0x00: xorq %rax, %rax
0x02: jne 0x16
0x0b: irmovq $1, %rax
0x15: halt
0x16: irmovq $2, %rdx
0x20: irmovq $3, %rbx
假设其预测错误,那么流水线执行阶段形如
其在第4周期内,0x02的执行阶段得到了预测错误的情况,并将0x16与0x20指令的第5周期及后续阶段置为气泡。即在预测错误的周期中,保持取值、访存与写回阶段的硬件执行不变,而在译码与执行阶段的硬件注入气泡。
在返回指令中,返回指令后一指令将被暂停在取指阶段,并不断地注入气泡,直到返回指令完成访存阶段,得到了返回地址,再释放暂停,后一指令完成取指阶段。
在流水线的数据冒险、控制冒险、返回指令三种特殊情况中,分别描述了暂停与气泡的使用情况,其HCL描述为
bool F_stall = E_icode in { IMRMOVQ, IPOPQ} && E_dstM in { d_srcA, d_srcB} ||
IRET in { D_icode, E_icode, M_icode};
boll D_stall = E_icode in { IMRMOVQ, IPOPQ} && E_dstM in { d_srcA, d_srcB};
bool D_bubble = (E_icode == IJXX && !e_Cnd) || IRET in { D_icode, E_icode, M_icode};
bool E_bubble = (E_icode == IJXX && !e_Cnd) || E_icode in { IMRMOVQ, IPOPQ } &&
E_dstM in { d_srcA, d_srcB };
此外,还可能发生两种组合特殊情况:控制冒险分支出现返回指令与数据冒险出现返回指令。
在控制冒险与返回指令同时发生时,控制冒险会在指令进入执行阶段发现预测错误,并在访存阶段向错误预测的执行阶段与译码阶段使用气泡替换原指令;返回指令在位于译码阶段时,其会将其下一指令的取指寄存器暂停,并在执行阶段向无指令的译码阶段插入气泡。组合来看控制冒险在发现预测错误的下一周期内,分别有:访存阶段硬件执行条件跳转指令,执行阶段硬件执行返回指令,译码阶段为气泡,取指阶段将返回指令下一指令暂停取指寄存器。在控制冒险的机制下,执行阶段与译码阶段会被注入气泡,由此使错误预测的返回指令取消;同时正确分支的指令进入取指阶段,而被暂停的错误指令未被执行,也被取消掉。故这种特殊情况的处理是正确的。
在数据冒险与返回指令同时发生时,位于译码阶段的返回指令将要使用位于执行阶段的运算的目的寄存器,产生数据冒险。当译码阶段对执行阶段产生数据冒险时,会将指令暂停在译码阶段,并在执行阶段插入气泡,直到前一运算指令将运算结果保存,释放寄存器,再释放暂停。组合来看,运算完成执行阶段,进入下一周期内有:访存阶段硬件为返回指令机制下暂停的访存寄存器,译码阶段硬件或为返回机制的气泡,或为加载/使用冒险机制的返回指令使用暂停的取指寄存器,运算阶段为加载/使用冒险机制的气泡。实际上,期望返回指令暂停在译码阶段,这种情况下流水线机制需要特殊处理。
在完成所有的处理后,形成PIPE结构处理器,形如
五、处理器的性能
5.1 异常处理
处理器中很多事情都会导致异常控制流,在Y86-64中,包括:
-halt指令;
-非法指令码与功能码组合的指令;
-非法地址的访问。
在处理器流水线中,当指令导致以尝试,流水线控制逻辑必须禁止条件码寄存器与数据内存的更新。流水线寄存器中增加了状态字段,以保证维护异常。
取指、访存与写回阶段的stat的HCL描述分别为
int f_stat = [
imem_error : SADR;
!instr_valid : SINS;
f_icode == IHALT : SHLT;
1 : SAOK;
];
int m_stat = [
dmem_error : SADR;
1 : M_stat
];
int Stat = [
W_stat == SBUB : SAOK;
1 : W_stat;
]
5.2 性能评估
通过计算指令所需要的平均时钟周期数的估计来量化处理器的性能,称为平均执行周期数【Cycle Per Instruction,CPI】。PIPE在每周期都取一条新指令,但又存在停顿或取消分支的情况,故其CPI略大于1。考虑 C C C为时钟周期, I I I为完成的指令数, B = C − I B = C - I B=C−I为插入的气泡个数,那么有 C P I = 1 + B / I CPI = 1 + B / I CPI=1+B/I平均处罚分别来自于数据加载冒险、控制冒险与返回等待。