跳到主要內容

Side Effects of Pipeline


這幾天跟同事在實作 IC 的 ROM code 時遇到一個難纏的問題,後來發現與 ARM 的 pipeline 機制有關係,問題是這樣的,看看以下的程式碼(實際上作了什麼事不重要,主要在流程):

  1. int SDRM_BN_Cmp(SDRM_BIG_NUM *BN_Src1, SDRM_BIG_NUM *BN_Src2)
  2. {
  3. effecda8:       e92d4008        push    {r3, lr}
  4. effecdac:       e1a02000        mov     r2, r0
  5. effecdb0:       e1a0e001        mov     lr, r1
  6.         if (BN_Src1->Length >= BN_Src2->Length) {
  7. effecdb4:       e5903004        ldr     r3, [r0, #4]
  8. effecdb8:       e591c004        ldr     ip, [r1, #4]
  9. effecdbc:       e153000c        cmp     r3, ip
  10. effecdc0:       3a000005        bcc     effecddc <SDRM_BN_Cmp+0x34>
  11.                 return  SDRM_DWD_Cmp(BN_Src1->pData, BN_Src1->Length,
  12. effecdc4:       e590000c        ldr     r0, [r0, #12]
  13. effecdc8:       e1a01003        mov     r1, r3
  14. effecdcc:       e59e200c        ldr     r2, [lr, #12]
  15. effecdd0:       e1a0300c        mov     r3, ip
  16. effecdd4:       ebfffd04        bl      effec1ec <SDRM_DWD_Cmp>
  17. effecdd8:       e8bd8008        pop     {r3, pc}
  18.                                                          BN_Src2->pData, BN_Src2->Length);
  19.         }
  20.         else {
  21.                 return -SDRM_DWD_Cmp(BN_Src2->pData, BN_Src2->Length,
  22. effecddc:       e591000c        ldr     r0, [r1, #12]
  23. effecde0:       e1a0100c        mov     r1, ip
  24. effecde4:       e592200c        ldr     r2, [r2, #12]
  25. effecde8:       ebfffcff        bl      effec1ec <SDRM_DWD_Cmp>
  26. effecdec:       e2600000        rsb     r0, r0, #0
  27.                                                          BN_Src1->pData, BN_Src1->Length);
  28.         }
  29. }
一步一步單步執行都沒有什麼問題,但是一旦讓其 free run ,就會在我們的執行環境中將系統打掛,連 ICE 都連不上。進行了非常多的實驗,但總是非常隨機地死在不同情況。

後來請 IC designer 將 CPU 的行為以 RTL level 模擬,總算了解問題根源:ARM 的 pipeline 機制非常積極,即使在軟體流程上,0xeffecdd8 與 0xeffecddc 是屬於不同的 branch,但是當 CPU 執行到上述的 0xeffecdd8 時,0xeffecddc 的指令會在 r1 所指到的位址為 normal memory 時,發出 read transaction 到 bus 上,然而,r1 有可能在第一個 branch 中的函式中被修改。

上述行為在一般情況下不會有問題,因為這兩條 branch 必定只有一條會被執行,若走第一條 branch,而觸發 CPU 為 0xeffecddc 發出 read transaction,其實並不會修改任何 programmer-visible registers,並且會在發現不需要該筆 data 後,將其 flush 出 pipeline。另一方面,若跑下半部 branch 時,就不可能會修改到 r1 原本的值,於是 0xeffecddc 可讀到記憶體中正確的值。

但是好死不死,設定 MMU 的同事,為了貪圖方便,將我們程式所要執行的 normal memory 範圍用簡單的方式對映了 1MB,而實際上的 normal memory 只有 64KB 是有效的,若發出多餘 64KB 的位址,便會將我們的 CPU 咬死。針對這個問題,有兩種解法:
  1. 若發出無效的位址,硬體可獲得一個 garbage value,這樣就不會在 CPU 作無謂的 pipeline 動作時,打掛系統。
  2. 改寫 MMU 設定,將 normal memory 限定在 64KB,CPU 便不會在 r1 的值為非 normal memory 範圍時進行積極的 pipeline。
由於硬體修改曠日費時,所以我們...嗯,當然是選擇方法二嘍... 在工作中踩到 pipeline 的坑,還是第一次... :P

留言