ARM的三級流水線結構(二)
3.4. 為何ARM7中PC=PC+8
此處解釋為何ARM7中,CPU地址,即PC,為何有PC=PC+8這一說法:
眾所周知,AMR7,是三級流水線,其細節見圖:
圖 3.1. AMR7三級流水線
首先,對於ARM7對應的流水線的執行情況,如下面這個圖所示:
圖 3.2. ARM7三級流水線狀態
然後對於三級流水線舉例如下:
圖 3.3. ARM7三級流水線示例
從上圖,其實很容易看出,第一條指令:
add r0, r1,$5
執行的時候,此時PC已經指向第三條指令:
cmp r2,#3
的地址了,所以,是PC=PC+8.
3.4.1. 為何ARM9和ARM7一樣,也是PC=PC+8
ARM7的三條流水線,PC=PC+8,很好理解,但是AMR9中,是五級流水線,為何還是PC=PC+8,而不是
PC
=PC+(5-1)*4
=PC + 16,
呢?
下面就需要好好解釋一番了。
具體解釋之前,先貼上ARM7和ARM9的流水線的區別和聯絡:
圖 3.4. ARM7三級流水線 vs ARM9五級流水線
圖 3.5. ARM7三級流水線到ARM9五級流水線的對映
下面開始對為何ARM9也是PC=PC+8進行解釋。
先列出ARM9的五級流水線的示例:
圖 3.6. 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:
圖 3.7. 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,會繼續執行完成。 |
http://www.crifan.com/files/doc/docbook/uboot_starts_analysis/release/htmls/why_arm7_pc_8.html