3.4. 为何ARM7中PC=PC+8
此处解释为何ARM7中,CPU地址,即PC,为何有PC=PC+8这一说法:
众所周知,AMR7,是三级流水线,其细节见图:
首先,对于ARM7对应的流水线的执行情况,如下面这个图所示:
然后对于三级流水线举例如下:
从上图,其实很容易看出,第一条指令:
add r0, r1,$5
执行的时候,此时PC已经指向第三条指令:
cmp r2,#3
的地址了,所以,是PC=PC+8.
ARM7的三条流水线,PC=PC+8,很好理解,但是AMR9中,是五级流水线,为何还是PC=PC+8,而不是
PC
=PC+(5-1)*4
=PC + 16,
呢?
下面就需要好好解释一番了。
具体解释之前,先贴上ARM7和ARM9的流水线的区别和联系:
下面开始对为何ARM9也是PC=PC+8进行解释。
先列出ARM9的五级流水线的示例:
举例分析为何PC=PC+8
然后我们以下面uboot中的start.S的最开始的汇编代码为例来进行解释:
00000000 <_start>: 0: ea000014 b 58 <reset> 4: e59ff014 ldr pc, [pc, #20] ; 20 <_undefined_instruction> 8: e59ff014 ldr pc, [pc, #20] ; 24 <_software_interrupt> c: e59ff014 ldr pc, [pc, #20] ; 28 <_prefetch_abort> 10: e59ff014 ldr pc, [pc, #20] ; 2c <_data_abort> 14: e59ff014 ldr pc, [pc, #20] ; 30 <_not_used> 18: e59ff014 ldr pc, [pc, #20] ; 34 <_irq> 1c: e59ff014 ldr pc, [pc, #20] ; 38 <_fiq>
00000020 <_undefined_instruction>:
20: 00000120 .word 0x00000120
下面对每一个指令周期,CPU做了哪些事情,分别详细进行阐述:
在看下面具体解释之前,有一句话要牢记,那就是:
PC不是指向你正在运行的指令,而是
PC始终指向你要取的指令的地址
认识清楚了这个前提,后面的举例讲解,就容易懂了。
- 指令周期Cycle1
- 取指
PC总是指向将要读取的指令的地址(即我们常说的,指向下一条指令的地址),而当前PC=4,
所以去取物理地址为4对对应的指令
ldr pc, [pc, #20]
其对应二进制代码为e59ff014。
此处取指完之后,自动更新PC的值,即PC=PC+4(单个指令占4字节,所以加4)=4+4=8
- 取指
- 指令周期Cycle2
- 译指
翻译指令e59ff014
- 同时再去取指
PC总是指向将要读取的指令的地址(即我们常说的,指向下一条指令的地址),而当前PC=8,
所以去物理地址为8所对应的指令“ldr pc, [pc, #20]” 其对应二进制代码为e59ff014。
此处取指完之后,自动更新PC的值,即PC=PC+4=8+4=12=0xc
- 译指
- 指令周期Cycle3
- 执行(指令)
执行“e59ff014”,即
ldr pc, [pc, #20]
所对表达的含义,即PC
= PC + 20
= 12 + 20
= 32
= 0x20
此处,只是计算出待会要赋值给PC的值是0x20,这个0x20还只是放在执行单元中内部的缓冲中。
- 译指
翻译e59ff014
- 取指
此步骤由于是和上面(1)中的执行同步做的,所以,未受到影响,继续取指,而取指的那一时刻,PC为上一Cycle更新后的值,即PC=0xc,所以是去取物理地址为0xc所对应的指令
ldr pc, [pc, #20]
对应二进制为e59ff014
- 执行(指令)
其实,分析到这里,大家就可以看出:
在Cycle3的时候,PC的值,刚好已经在Cycle1和Cycle2,分别加了4,所以Cycle3的时候,PC=PC+8,而同样道理,对于任何一条指令的,都是在Cycle3,指令的Execute执行阶段,如果用到PC的值,那么PC那一时刻,就是PC=PC+8。
所以,此处虽然是五级流水线,但是却不是PC=PC+16,而是PC=PC+8。
进一步地,我们发现,其实PC=PC+N的N,是和指令的执行阶段所处于流水线的深度有关,即此处指令的执行Execute阶段,是五级流水线中的第三个,而这个第三阶段的Execute和指令的第一个阶段的Fetch取指,相差的值是 3 -1 =2,即两个CPU的Cycle,而每个Cycle都会导致PC=+PC+4,所以,指令到了Execute阶段,才会发现,此时PC已经变成PC=PC+8了。
回过头来反观ARM7的三级流水线,也是同样的道理,指令的Execute执行阶段,是处于指令的第三个阶段,同理,在指令计算数据的时候,如果用到PC,就会发现此时PC=PC+8。
同理,假如ARM9的五级流水线,把指令的Execute执行阶段,设计在了第四个阶段,那么就是PC=PC+(第4阶段-1)*4个字节 = PC= PC+12了。
用图来说明PC=PC+8个过程
对于上面的文字的分析过程,可能看起来不是太容易理解,所以,下面这里通过图表来表示具体的流程,就更容易看懂了。其中,下图,是以ARM9的五级流水线的内部架构图为基础,而编辑的出来用于说明为何ARM9的五级流水线,也是PC=PC+8:
对于上图中的,第一个指令在执行的时候,是使用到了PC的值,其实,我们可以看到,
对于指令在执行中,不论是否用到PC的值,PC都会按照既定逻辑,没一个cycle,自动增加4的,套用《非诚勿扰2》中的经典对白,即为:
你(指令执行的时候)用,
或者不用,
PC就在那里,
自动增4
所以,经过两个cycle的增4,就到了指令执行的时候,此时PC已经增加了8了,即使你指令执行的时候,没有用到PC的值,其也还是已经加了8了。而一般来说,大多数的指令,肯定也都是没有用到PC的,但是其实任何指令执行的那一时刻,也已经是PC=PC+8,而多数指令没有用到,所以很多人没有注意到这点罢了。
![]() | PC(execute)=PC(fetch)+ 8 |
---|---|
对于PC=PC+8中的两个PC,其实含义不完全一样.其更准确的表达,应该是这样: PC(execute)=PC(fetch)+ 8 其中: PC(fetch):当前正在执行的指令,就是之前取该指令时候的PC的值 PC(execute):当前指令执行的计算中,如果用到PC,则此时PC的值。 |
![]() | 不同阶段的PC值的关系 |
---|---|
对应地,在ARM7的三级流水线(取指,译指,执行)和ARM9的五级流水线(取指,译指,执行,存储,写回)中,可以这么说: PC, 总是指向当前正在被取指的指令的地址, PC-4,总是指向当前正在被译指的指令的地址, PC-8,总是指向当前的那条指令,即我们一般说的,正在被执行的指令的地址。 |
【总结】
ARM7的三级流水线,PC=PC+8,
ARM9的五级流水线,也是PC=PC+8,
根本的原因是,两者的流水线设计中,指令的Execute执行阶段,都是处于流水线的第三级。
所以使得PC=PC+8。
类似地,可以推导出:
假设,Execute阶段处于流水线中的第E阶段,每条指令是T个字节,那么
PC
= PC + N*T
= PC + (E - 1) * T
此处ARM7和ARM9:
Execute阶段都是第3阶段 ⇒ E=3
每条指令是4个字节 ⇒ T=4
所以:
PC
=PC + N* T
=PC + (3 -1 ) * 4
= PC + 8
![]() | 关于直接改变PC的值,会导致流水线清空的解释 |
---|---|
把PC的值直接赋值为0x20。而PC值更改,直接导致流水线的清空,即导致下一个cycle中的,对应的流水线中的其他几个步骤,包括接下来的同一个Cycle中的取指的工作被取消。在PC跳转到0x20的位置之后,流水线重新计算,重新一步步地按照流水线的逻辑,去一点点执行。当然要保证当前指令的执行完成,即执行之后,还有两个cycle,分别做的Memory和Write,会继续执行完成。 |