目录
为什么要分析
F阶段
D阶段
E阶段
M阶段
更新PC阶段
本书提供了SEQ和PIPE的HCL描述,完整的研究它们是很值得的。本文来分析SEQ的HCL代码。分析的方式就是以下面中的几个指令(rrmovq、ret、pushq)为例,跟着HCL控制逻辑的六个阶段走一遍。
确定icode和ifun,这个都一样,分析写在注释里了。
################ 取指令阶段 ###################################
# 确定指令码(即前四位)
word icode = [
imem_error: INOP; # 如果这条指令的icode取失败了,对于这个字节就什么都不做了,视为nop
1: imem_icode;
];
# 确定指令函数(即后四位,jXX、cmovXX和OPq才有区别,其余都默认为0)
word ifun = [
imem_error: FNONE; # 如果这条指令的icode取失败了,ifun自然也取不出来,icode视为INOP,ifun理应为默认值,即0
1: imem_ifun;
];
然后是确定指令是否有效,把你这个处理器能处理的全部指令放进集合:
bool instr_valid = icode in
{ INOP, IHALT, IRRMOVQ, IIRMOVQ, IRMMOVQ, IMRMOVQ,
IOPQ, IJXX, ICALL, IRET, IPUSHQ, IPOPQ };
对于rrmovq来说,需要寄存器参数、无需常数参数。所以在下面两个集合中,分别是:有 、无。
对于ret来说,不需要寄存器参数,不需要常数参数,所以是:无、无。
对于pushq来说,需要寄存器参数,不需要常数参数,所以是:有、无。
bool need_regids =
icode in { IRRMOVQ, IOPQ, IPUSHQ, IPOPQ,
IIRMOVQ, IRMMOVQ, IMRMOVQ };
bool need_valC =
icode in { IIRMOVQ, IRMMOVQ, IMRMOVQ, IJXX, ICALL };
接下看看译码阶段。
对于rrmovq来说,只有一个读源,是rA。所以若icode是IRRMOVQ,那么srcA=rA,srcB=RNONE(RNONE的值为0xF,表明不需要访问寄存器 )
对于ret来说,有两个读源,读出来两个%rsp。我猜应该是为了后续代码简便所以弄了个备份。此处待补,我认为一个读源就可以实现ret。
对于pushq来说,有两个读源,是rA和%rsp(因为稍后%rsp要自减8,所以得先直到%rsp现在存的是几)。所以若icode是IPUSHQ,那么srcA=rA,srcB=RRSP。
################ 译码阶段(确定寄存器的两个读源和两个写目标) ######################
word srcA = [
icode in { IRRMOVQ, IRMMOVQ, IOPQ, IPUSHQ } : rA;
icode in { IPOPQ, IRET } : RRSP;
1 : RNONE; # Don't need register
];
word srcB = [
icode in { IOPQ, IRMMOVQ, IMRMOVQ } : rB;
icode in { IPUSHQ, IPOPQ, ICALL, IRET } : RRSP;
1 : RNONE; # Don't need register
];
再看两个写的目标:
rrmovq 就是把R[rB]的值改为R[rA],只有一个写目标,那为什么多了个“&& cnd”?
(提示:考虑rrmovq与cmov完全等价。)
其实,如果忽略条件传送指令,那么确实IRRMOVQ可以和下一行合并。至于如何考虑条件传送指令,这在练习4.24有解答。
ret 会把%rsp修改(自增8)
pushq会把%rsp修改(自减8)
word dstE = [
icode in { IRRMOVQ } && Cnd : rB;
icode in { IIRMOVQ, IOPQ} : rB;
icode in { IPUSHQ, IPOPQ, ICALL, IRET } : RRSP;
1 : RNONE; # Don't write any register
];
word dstM = [
icode in { IMRMOVQ, IPOPQ } : rA;
1 : RNONE; # Don't write any register
];
如果忽略条件传送指令,那么确实IRRMOVQ可以和下一行合并 如何考虑条件传送指令,这在练习4.24有解答。 练习4.24的答案
译码阶段分析完成。
接下来看执行阶段。
对于 rrmovq,要在该阶段执行的内容是valE=R[rA]。则第一个参数是valA(就是R[srcA],srcA就是rA),第二个参数是0,做加法,结果存到变量valE中。
对于ret,要在该阶段执行的内容是valE=R[%rsp]+8。则第一个参数是立即数8,第二个参数是valA(%rsp的值),做加法。
对于pushq,要在该阶段执行的内容是valE=R[%rsp]-8。则第一个参数是立即数-8,第二个参数是valA(%rsp的值),做加法。
################ 执行阶段 ###################################
# 给计算单元的第一个参数赋值
word aluA = [
icode in { IRRMOVQ, IOPQ } : valA;
icode in { IIRMOVQ, IRMMOVQ, IMRMOVQ } : valC;
icode in { ICALL, IPUSHQ } : -8;
icode in { IRET, IPOPQ } : 8;
# Other instructions don't need ALU
];
# 给计算单元的第二个参数赋值
word aluB = [
icode in { IRMMOVQ, IMRMOVQ, IOPQ, ICALL,
IPUSHQ, IRET, IPOPQ } : valB;
icode in { IRRMOVQ, IIRMOVQ } : 0;
# Other instructions don't need ALU
];
下面这两个变量很简单,只有OPq的四个指令的两个参数之间所做的运算是需要自定义的,其他一律都是加法。且只有OPq指令执行完后更新条件码。
# 确定计算单元给两个参数做什么运算
word alufun = [
icode == IOPQ : ifun;
1 : ALUADD;
];
# 该指令是否更新条件码?
bool set_cc = icode in { IOPQ };
访存只干一件事:要么把valE写进内存某处,要么将内存某处的东西写入变量valM。
究竟是读还是写,与究竟在处理哪个指令有关。
对于 rrmovq,无需访存,既不读也不写。
对于ret,需要从内存(栈)中得到返回位置,所以是读。
对于pushq,压栈,是写。
################ 访存阶段 ###################################
## Set read control signal
bool mem_read = icode in { IMRMOVQ, IPOPQ, IRET };
## Set write control signal
bool mem_write = icode in { IRMMOVQ, IPUSHQ, ICALL };
刚刚提到的“某处”,就是mem_addr变量,其值就取上文出现过的变量(如valA、valB、valC、valE等) 。
对于ret,从rsp所指向的内存位置读,所以mem_addr=valA。
对于pushq,在rsp所指向的内存位置开始写,所以mem_addr=valE。写什么?valA
## Select memory address
word mem_addr = [
icode in { IRMMOVQ, IPUSHQ, ICALL, IMRMOVQ } : valE;
icode in { IPOPQ, IRET } : valA;
# Other instructions don't need address
];
## Select memory input data
word mem_data = [
# Value from register
icode in { IRMMOVQ, IPUSHQ } : valA;
# Return PC
icode == ICALL : valP;
# Default: Don't write anything
];
此处补充一个小特例
4.15答案
访存阶段的最后还要设置Stat,Stat是干什么的,看书:
## Determine instruction status
word Stat = [
imem_error || dmem_error : SADR;
!instr_valid: SINS;
icode == IHALT : SHLT;
1 : SAOK;
];
Stat的用途以及计算方法
这是最后一个阶段, Program Counter Update,看我的注释即可:
################ Program Counter Update ############################
# 即找出来下一条指令在哪取
word new_pc = [
# 如果该条指令是call,那么下一条指令的地址就是call后的那个立即数,即valC
icode == ICALL : valC;
# 如果该条指令是条件跳转指令,那么在分支函数=1时跳转到valC,否则是默认
icode == IJXX && Cnd : valC;
# 如果该条指令是返回,那么下一条指令的地址是刚刚从栈中取出的地址valM
icode == IRET : valM;
# 默认值:紧邻的下一条指令地址,即valP
1 : valP;
];
# 本文件结束
分析完毕。