目录
前言
1. 指令处理的阶段
2. Y86-64指令处理详解
2.1 Opq、rrmovq、irmovq
2.2 rmmovq和mrmovq
2.3 push和pop
2.4 jXX、call和ret
3. SEQ硬件结构
3.1 取指阶段
3.2 译码阶段
3.3 执行阶段
3.4 访存阶段
3.5 写回阶段
3.6 更新PC
4.小结
构造了一个Y86-64指令集后,我们可以实现一个基于Y86-64指令的处理器,称为SEQ(sequential)处理器
这种处理器在一个时钟周期内只处理一条指令,因此效率很低,但理解处理器的顺序执行是之后实现高效的流水线化的处理器的基础
通常,对一条指令的处理包括很多操作,在这里,我们用一个统一的框架来描述。实现Y86-64指令所需要的计算可以被组织成六个阶段:
一般来说,处理器会循环往复地对指令执行这些阶段,只有在遇到halt指令或一些错误情况时,才会停下来——包括非法存储器地址(程序地址或数据地址),以及非法指令等
简单了解顺序处理的六个阶段后,针对不同的具体指令,这六个阶段的执行情况也有所不同
以下面的汇编代码和其对应的Y86-64指令为例来进行说明:
1 0x000: 30f20900000000000000 | irmovq $9, %rdx
2 0x00a: 30f31500000000000000 | irmovq $21, %rbx
3 0x014: 6123 | subq %rdx, %rbx #subtract
4 0x016: 30f48000000000000000 | irmovq $128, %rsp #problem
5 0x020: 40436400000000000000 | rmmovq %rsp, 100(%rbx) #store
6 0x02a: a02f | pushq %rdx #push
7 0x02c: b00f | popq %rax #problem
8 0x02e: 734000000000000000 | je done #not taken
9 0x037: 804103000000000000 | call proc #problem
10 0x040: | done:
11 0x040: 00 | halt
12 0x041: | proc:
13 0x041: 90 | ret #return
阶段 | Opq rA, rB | rrmovq rA, rB | irmovq V, rB |
取指 | icod:ifun ← M1[PC] rA:rB ← M1[PC + 1] valP ← PC + 2 |
icod:ifun ← M1[PC] rA:rB ← M1[PC + 1] valP ← PC + 2 |
icod:ifun ← M1[PC] rA:rB ← M1[PC + 1] valC ← M8[PC + 2] valP ← PC + 10 |
译码 | valA ← R[rA] valB ← R[rB] |
valA ← R[rA] |
|
执行 | valE ← valB OP valA Set CC |
valE ← 0 + valA |
valE ← 0 + valC |
访存 | |||
写回 | R[rB] ← valE | R[rB] ← valE | R[rB] ← valE |
更新PC | PC ← valP | PC ← valP | PC ← valP |
以代码的第三行指令subq来进行说明:
注:←表示将右边的数据写入箭头指向的符号内
因此对于这样一条指令的计算的具体操作为:
阶段 | Opq rA, rB | sbuq %rdx %rbx |
取指 | icod:ifun ← M1[PC] rA:rB ← M1[PC + 1] valP ← PC + 2 |
icode:ifun ← M1[0X014] = 6:1 rA:rB ← M1[0X015] = 2:3 valP ← 0x014 + 2 = 0x016 |
译码 | valA ← R[rA] valB ← R[rB] |
valA ← R[%rdx] = 9 valB ← R[%rbx] = 21 |
执行 | valE ← valB OP valA Set CC |
valE ← 21 - 9 = 12 ZF ← 0,SF ← 0,OF ← 0 |
访存 | ||
写回 | R[rB] ← valE | R[%rbx] ← valE = 12 |
更新PC | PC ← valP | PC ← valP = 0x016 |
同样,对于第四行的irmovq指令:
阶段 | Opq rA, rB | sbuq %rdx %rbx |
取指 | icod:ifun ← M1[PC] rA:rB ← M1[PC + 1] valP ← PC + 2 |
icode ifun ← M1[0X016] = 3:0 rA:rB ← M1[0X017] = f:4 valP ← 0x016 + 10 = 0x020 |
译码 | ||
执行 | valE ← 0 + valC |
valE ← 0 + 128 = 128 |
访存 | ||
写回 | R[rB] ← valE | R[%rsp] ← valE = 128 |
更新PC | PC ← valP | PC ← valP = 0x020 |
阶段 | rmmovq rA, D(rB) | mrmovq D(rB), rA |
取指 | icod:ifun ← M1[PC] rA:rB ← M1[PC + 1] valC ← M8[PC + 2] valP ← PC + 10 |
icod:ifun ← M1[PC] rA:rB ← M1[PC + 1] valC ← M8[PC + 2] valP ← PC + 10 |
译码 | valA ← R[rA] valB ← R[rB] |
valB ← R[rB] |
执行 | valE ← valB + valC |
valE ← valB + valC |
访存 | M8[valE] ← valA | valM ← M8[valE] |
写回 | ||
R[rA] ← valM | ||
更新PC | PC ← valP | PC ← valP |
以代码的第五行指令rmmovq来进行说明
注:这条指令是将128这个立即数传送到地址为112的内存中,这个地址的计算为100(偏移量)+12(基址)
因此对于这样一条指令的计算的具体操作为:
阶段 | rmmovq rA, D(rB) | rmmovq %rsp, 100(%rbx) |
取指 | icod:ifun ← M1[PC] rA:rB ← M1[PC + 1] valC ← M8[PC + 2] valP ← PC + 10 |
icode:ifun ← M1[0X020] = 4:0 rA:rB ← M1[0X021] = 4:3 alC ← M8[0x022] = 100 valP ← 0x020 + 10 = 0x02a |
译码 | valA ← R[rA] valB ← R[rB] |
valA ← R[%rsp] = 128 valB ← R[%rbx] = 12 |
执行 | valE ← valB + valC |
valE ← 12 + 100 = 112 |
访存 | M8[valE] ← valA | M8[112] ← 128 |
写回 | ||
更新PC | PC ← valP | PC ← valP = 0x02a |
阶段 | pushq rA | popq rA |
取指 | icod:ifun ← M1[PC] rA:rB ← M1[PC + 1] valP ← PC + 2 |
icod:ifun ← M1[PC] rA:rB ← M1[PC + 1] valP ← PC + 2 |
译码 | valA ← R[rA] valB ← R[%rsp] |
valA ← R[%rsp] valB ← R[%rsp] |
执行 | valE ← valB + (-8) |
valE ← valB + 8 |
访存 | M8[valE] ← valA | valM ← M8[valA] |
写回 | R[%rsp] ← valE | R[%rsp] ← valE R[rA] ← valM |
更新PC | PC ← valP | PC ← valP |
以代码的第六行指令pushq来进行说明
注:这条指令进行压栈,将栈指针减8,再把%rdx里的值入栈
对于这样一条指令的计算的具体操作为:
阶段 | pushq rA | pushq %rdx |
取指 | icod:ifun ← M1[PC] rA:rB ← M1[PC + 1] valP ← PC + 2 |
icod:ifun ← M1[0x02a] = a:0 rA:rB ← M1[0x02b] = 2:f valP ← 0x02a + 2 = 0x02c |
译码 | valA ← R[rA] valB ← R[%rsp] |
valA ← R[%rdx] = 9 valB← R[%rsp] = 128 |
执行 | valE ← valB + (-8) |
valE ← 128 + (-8) = 120 |
访存 | M8[valE] ← valA | M8[120] ← 9 |
写回 | R[%rsp] ← valE | R[%rsp] ← 120 |
更新PC | PC ← valP | PC ← valP = 0x02c |
popq指令和pushq的执行类似,除了在解码阶段要读两次栈指针以外
对于popq, 以代码的第七行指令popq为例:
阶段 | popq rA | popq %rax |
取指 | icod:ifun ← M1[PC] rA:rB ← M1[PC + 1] valP ← PC + 2 |
icod:ifun ← M1[0x02c] = b:0 rA:rB ← M1[0x02d] = 0:f valP ← 0x02c + 2 = 0x02e |
译码 | valA ← R[%rsp] valB ← R[%rsp] |
valA ← R[%rsp] = 120 valB ← R[%rsp] = 120 |
执行 | valE ← valB + 8 |
valE ← 120 + 8 |
访存 | valM ← M8[valA] | valM ← M8[120] = 9 |
写回 | R[%rsp] ← valE R[rA] ← valM |
R[%rsp] ← 128 R[%rax] ← 9 |
更新PC | PC ← valP | PC ← 0x02e |
译码阶段读两次栈指针的原因 :
第一次读将%rsp的值(也就是栈顶地址)读到valA里面,第二次读将%rsp的值读到valB里,其中valB参与栈指针的增减运算,这一过程发生在执行阶段,而在访存阶段用到了valA存的地址来找到出栈元素并放在valM中,写回阶段不仅要更新%rsp,还要将valM更新给rA。这样的设计增强了整体性,使得popq指令与其他指令更相似。
用没加过8的值作为内存读地址,保持了Y86-64(和x86-64)的惯例,popq应该首先读内存,然后再增加栈指针
阶段 | jXX Dest | call Dest | ret |
取指 | icod:ifun ← M1[PC] valC ← M8[PC + 1] valP ← PC + 9 |
icod:ifun ← M1[PC] valC ← M8[PC + 1] valP ← PC + 9 |
icod:ifun ← M1[PC] valP ← PC + 1 |
译码 | valB ← R[%rsp] |
valA ← R[%rsp] valB ← R[%rsp] |
|
执行 | Cnd ← Cond(CC, ifun) |
valE ← valB + (-8) | valE ← valB + 8 |
访存 | M8[valE] ← valP | valM ← M8[valA] | |
写回 | R[%rsp] ← valE | R[%rsp] ← valE | |
更新PC | PC ← Cnd ? valC : valP | PC ← valC | PC ← valM |
以代码的第八行指令je来进行说明
注:该指令表明当条件满足(这里是ZF = 1)就发生跳转
对于这样一条指令的计算的具体操作为:
阶段 | jXX Dest | je 0x040 |
取指 | icod:ifun ← M1[PC] valC ← M8[PC + 1] valP ← PC + 9 |
icod:ifun ← M1[0x02e] = 7:3 valC ← M8[0x02f] = 0x040 valP ← 0x02e + 9 = 0x037 |
译码 | ||
执行 | Cnd ← Cond(CC, ifun) |
Cnd ← Cond(< 0, 0, 0 > , 3) = 0 |
访存 | ||
写回 | ||
更新PC | PC ← Cnd ? valC : valP | PC ← 0 ? 0x040 : 0x037 = 0x037 |
指令call和指令ret与指令pushq和popq类似,第九行的call指令:
阶段 | call Dest | call 0x041 |
取指 | icod:ifun ← M1[PC] valC ← M8[PC + 1] valP ← PC + 9 |
icod:ifun ← M1[0x037] = 8:0 valC ← M8[0x038] = 0x041 valP ← 0x037 + 9 = 0x040 |
译码 | valB ← R[%rsp] |
valB ← R[%rsp] = 128 |
执行 | valE ← valB + (-8) | valE ← 128 + (-8) = 120 |
访存 | M8[valE] ← valP | M8[120] ← 0x040 |
写回 | R[%rsp] ← valE | R[%rsp] ← 120 |
更新PC | PC ← valC | PC ← 0x041 |
值得注意的是,访存阶段将函数返回的地址存在了新开辟的栈空间中,也就是将返回地址压栈,而更新PC时,PC指向的是跳转的地址——函数的地址
实现所有Y86指令所需要的计算可以被组织成六个基本阶段:
取指、译码、执行、访存、写回、更新PC
下图是SEQ的硬件结构,一种顺序实现
PC的值可能有三种情况:
以上是一个Y86-64处理器的完整设计,但是这种顺序结构会导致指令执行的速度很慢 ——时钟必须很慢才能满足所有操作在一个时钟周期内,为了提高处理器性能,之后会带来更高效地流水线设计