ARM3级流水和5级流水为什么都是PC=PC+8

首先介绍前置知识
arm7采用三级流水
(1)取指(fetch)
取指级的任务是从程序存储器中读取指令。
(2)译码(decode)
译码级完成对指令的分析,并为下一个周期准备数据路径需要的控制信号。在这一级,指令占用译码逻辑,不占用数据通路。
(3)执行(excute)
完成指令要求的操作,并根据需要将结果写回寄存器。指令占用数据路径,寄存器堆被读取,操作数在桶行移位器中被移位。运算器产生运算结果并回写到目的寄存器中,运算器根据指令需求和运输结果更改状态寄存器的条件位。

arm9采用五级流水
(1)取指(fetch)

从存储器中取出指令,并将其放入指令流水线。

(2)译码(decode)

指令被译码,从寄存器堆中读取寄存器操作数。在寄存器堆中有3个操作数读端口,因此大多数ARM指令能在1个周期内读取其操作数。

(3)执行(execute)

将其中一个操作数移位,并在ALU中产生结果。如果指令是Load或Store指令,则在ALU中计算存储器的地址。

(4)缓冲/数据(buffer/data)

如果需要则访问数据存储器,否则ALU只是简单地缓冲一个时钟周期,以便是所有的指令具有同样的流水线流程。

(5)回写(write-back)寄存器堆

------------------------------------------------

注意,arm7中执行和取指是隔了一级译码级,那一级不读取数据,当前PC=原PC+8, 正常
arm9中执行和取指之间的译码级不再老实,译码级已经开始从寄存器堆中读取寄存器操作数,这样的话,
读取到得就是PC+4,不是PC+8,执行级又不会再读一次,那将导致执行级的PC还是=PC+4
为了保持向下兼容,在ARM9的5级流水线上,取指级增加的PC值被直接送到译码级的寄存器,穿过了两级之间的流水线寄存器,这样译码级得到的PC值就是下一条指令的PC+4,等于当前指令的PC+8,
等到了执行级时,PC寄存器的值=当前指令地址+8

当使用指令STR或STM对R15进行保存时,保存的可能是当前指令地址加8或当前指令地址加12。
到底是哪种方式,取决于芯片的具体设计方式。当然,在同一个芯片中,只能采用一种方式。要么保存当前指令地址加8,要么保存当前指令地址加12。程序开发人员应尽量避免使用STR或STM指令来对R15进行操作。当不可避免要使用这种方式时,可以先通过一小段程序来确定所使用的芯片是使用哪种方式实现的。例如:
SUB R1,PC, #4    ;R1中存放STR指令地址
STR PC,[R0]      ;用STR指令将PC保存到R0指向的地址单元中,
                            ;PC=STR指令地址+偏移量(偏移量为8或者12)。
LDR R0,[R0]    ;读取STR指令地址+偏移量的值
SUB R0,R0,R1    ; STR指令地址+偏移量的值减去STR指令的地址,
                            ;得到偏移量值(8或者12)。

 

=============================

 

ARM7采用三级流水线的冯·诺伊曼结构,ARM9采用五级流水线的哈佛结构。

ARM7流水线包括取指(fetch)、译码(decode)、执行(excute)。ARM7流水线在译码阶段不读取操作数寄存器,因此执行阶段的PC值和取指阶段的PC值关系为:PC(excute)=PC(fetch)+8。

 

ARM9流水线包括取指(fetch)、译码(decode)、执行(excute)、缓冲/数据(buffer/data)、回写(write-back)寄存器堆。ARM9流水线在译码阶段已经开始读取操作数寄存器,因此译码阶段的PC值和取指阶段的PC值关系为:PC(decode)=PC(fetch)+4。因此执行阶段的PC值和译码阶段的PC值关系为:PC(excute)=PC(decode)+4。

 

为了保证ARM9流水线和ARM7流水线兼容,ARM9流水线将取指阶段的PC值跨过取指和译码流水线寄存器,直接送往译码阶段寄存器,这样仍然保证执行阶段的PC值和取指阶段的PC值关系为:PC(excute)=PC(fetch)+8。
   
 以下面uboot中的start.S的最开始的汇编代码为例来进行解释:


00000000 <_start>:
   0:   ea000014   b   58
   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

(1)取指:

PC总是指向将要读取的指令的地址(即我们常说的,指向下一条指令的地址),而当前PC=4,

所以去取物理地址为4对对应的指令“ldr pc, [pc, #20]”,其对应二进制代码为e59ff014。

此处取指完之后,自动更新PC的值,即PC=PC+4(单个指令占4字节,所以加4)=4+4=8

指令周期Cycle2

(1)译指:翻译指令e59ff014;

(2)同时再去取指:

PC总是指向将要读取的指令的地址(即我们常说的,指向下一条指令的地址),而当前PC=8,

所以去物理地址为8所对应的指令“ldr pc, [pc, #20]” 其对应二进制代码为e59ff014。

此处取指完之后,自动更新PC的值,即PC=PC+4=8+4=12=0xc

指令周期Cycle3

(1)执行(指令):执行“e59ff014”,即“ldr pc,[pc, #20]”所对表达的含义,即

PC

= PC + 20

= 12 + 20

= 32

= 0x20

此处,只是计算出待会要赋值给PC的值是0x20,这个0x20还只是放在执行单元中内部的缓冲中。

(2)译指:翻译e59ff014。

(3)取指:

此步骤由于是和上面(1)中的执行同步做的,所以,未受到影响,继续取指,而取指的那一时刻,PC为上一Cycle

更新后的值,即PC=0xc,所以是去取物理地址为0xc所对应的指令” ldr pc, [pc, #20]”,对应二进制为e59ff014。

你可能感兴趣的:(【ARM】)