這幾天跟同事在實作 IC 的 ROM code 時遇到一個難纏的問題,後來發現與 ARM 的 pipeline 機制有關係,問題是這樣的,看看以下的程式碼(實際上作了什麼事不重要,主要在流程):
- int SDRM_BN_Cmp(SDRM_BIG_NUM *BN_Src1, SDRM_BIG_NUM *BN_Src2)
- {
- effecda8: e92d4008 push {r3, lr}
- effecdac: e1a02000 mov r2, r0
- effecdb0: e1a0e001 mov lr, r1
- if (BN_Src1->Length >= BN_Src2->Length) {
- effecdb4: e5903004 ldr r3, [r0, #4]
- effecdb8: e591c004 ldr ip, [r1, #4]
- effecdbc: e153000c cmp r3, ip
- effecdc0: 3a000005 bcc effecddc <SDRM_BN_Cmp+0x34>
- return SDRM_DWD_Cmp(BN_Src1->pData, BN_Src1->Length,
- effecdc4: e590000c ldr r0, [r0, #12]
- effecdc8: e1a01003 mov r1, r3
- effecdcc: e59e200c ldr r2, [lr, #12]
- effecdd0: e1a0300c mov r3, ip
- effecdd4: ebfffd04 bl effec1ec <SDRM_DWD_Cmp>
- effecdd8: e8bd8008 pop {r3, pc}
- BN_Src2->pData, BN_Src2->Length);
- }
- else {
- return -SDRM_DWD_Cmp(BN_Src2->pData, BN_Src2->Length,
- effecddc: e591000c ldr r0, [r1, #12]
- effecde0: e1a0100c mov r1, ip
- effecde4: e592200c ldr r2, [r2, #12]
- effecde8: ebfffcff bl effec1ec <SDRM_DWD_Cmp>
- effecdec: e2600000 rsb r0, r0, #0
- BN_Src1->pData, BN_Src1->Length);
- }
- }
後來請 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 咬死。針對這個問題,有兩種解法:
- 若發出無效的位址,硬體可獲得一個 garbage value,這樣就不會在 CPU 作無謂的 pipeline 動作時,打掛系統。
- 改寫 MMU 設定,將 normal memory 限定在 64KB,CPU 便不會在 r1 的值為非 normal memory 範圍時進行積極的 pipeline。
由於硬體修改曠日費時,所以我們...嗯,當然是選擇方法二嘍... 在工作中踩到 pipeline 的坑,還是第一次... :P
留言
張貼留言