Background Facts:
- register分成两种,一种是用户可用的, 像是%rax, %rsp, %rbp. 另一种是CPU用来执行指令时暂时储存用的, 比如valC, valA, dstE这类. 在Pipeline CPU中的两个Stage之间那一排可叫做Stage Register, 也属于第二种register.
- 这门课里的CPU只能做binary运算, 比如只能计算两个数的合值. 有时如果只有一个数但还是要计算的话,第二个数可用0.
- 指令(instruction)和数据都会存在内存里,都有自己的memory address.
- PC这个register存的是即将运行指令的指令的地址,一般可通过当前指令的地址加上指令的长度计算出.
- Assembly只涉及CPU的registers和memory之间的互动.
每个Y86的指令都要经历5个Stage才算完成了一次执行. Fetch, Decode, Execute, Memory, WriteBack, 这五个Stage不论是在Sequential CPU还是Pipeline CPU里都要按照这个顺序进行, 而且每个Stage都有可能要用到其之前Stage产生的结果, 比如更新了某个register的值.
Fetch
- 根据PC知道即将执行的指令在哪里,然后这个解析指令,获取icode,ifun, rA, rB, valC.
- icode代表这个指令的主要功能, 是指令的编号, 根据它可知道这个指令的格式和长度. 那ifun是干什么的呢? ifun不是每个指令都会用到的, 假如说我给每种不同的算术运算一个不同的icode, 那么我就多了一打指令, 因此这里加入了ifun来对同一种指令来进一步区分. 比如说icode为6的都是算术运算, 那可能ifun为1的是加法, 2是减法, 两个数字可以确定一个具体的指令.
- rA, rB是两个register的编号, 他们告诉你这个指令会用到哪个register, 但不是所有指令都用两个, 有些甚至一个都不用, 这种情况r A或者rB的值为F(十进制为15).
- 有些指令会用到一个额外的常数, 比如irmovq和call, 这个常数便是valC, 要注意valC的endianness.
- 计算新的PC的值,即当前PC加上指令的长度.
Decode
- decode会看icode, ifun来决定会用到哪些registers.
- 设置srcA, srcB, valA, valB, dstE, dstM.
- srcA和srcB只负责将rA和rB的值复制过来.
- valA会读取srcA对应的register的值, valB则独取srcB. 只有valA和valB接下来在Execute会用到. 而且valA可以在Memory使用, 而valB不行.
- dstE和dstM会设置Write Back要用到的destination register.
- 注意, 不是每个register都会用到, 取决于指令.
- 有些指令里会默默地用到%rsp这个register, 而且不会在rA, rB指明. 这时不要忘记将%rsp放入srcA或srcB.
Execute
- 使用valA, valB, valC进行一次算术运算, 并将结果写入valE.
- 运算时valB放在前面. ex. valE = valB - valA
- 运算时也可以使用常数. ex. valE = valB - 8
- 有时候其实并不需要发生运算, 但还是需要valE,这是一般会进行一次加0运算. ex. valE = valC + 0.
- 运算结束后会设置CC的值. CC的值之后会结合条件生成一个boolean给条件型指令来参考. ex. jle, jge, cmovle.
Memory
- 从内存中独取一个值写入valM. ex. valM = M[address].
- 将一个值写入内存. ex. M[address] = valE.
- 涉及stack的指令会在这里从stack上读或写.
WriteBack
- 非常的直白
- 把valE的值写入dstE对应的register,再把valM的值写入dstM对应的register
最后还有一个算不上stage的stage叫PC Update. 在sequential CPU里PC在Write Back之后更新, 而Pipeline CPU会在Fetch开始前先“预测”下一个PC, 之后会在对应章节详述.