痞子衡嵌入式:實抓Flash訊號波形來看i.MXRT的FlexSPI外設下AHB讀訪問情形(全加速)

痞子衡發表於2021-05-08

  大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家介紹的是實抓Flash訊號波形來看i.MXRT的FlexSPI外設下AHB讀訪問情形

  上一篇文章 《實抓Flash訊號波形來看i.MXRT的FlexSPI外設下AHB讀訪問情形(有預取)》 裡痞子衡抓取了Cache關閉但Prefetch開啟下的AHB讀訪問對應的Flash端時序波形圖,我們知道了FlexSPI的Prefetch功能確實在一定程度上改善了Flash訪問效率,但是AHB RX Buffer最大僅1KB(對i.MXRT1050而言),不可拆分成更小粒度Buffer去快取不同Flash地址處的資料(對於同一AHB master而言),這樣對於程式碼中多個不同小資料塊重複的Flash空間訪問,Prefetch機制並沒有明顯提升訪問效率。

  針對這種不連續Flash地址空間頻繁訪問低效情況,ARM Cortex-M7核心給出瞭解決方案,那就是L1 Cache技術,今天痞子衡就來繼續測一測開啟L1 Cache下的Flash AHB讀訪問情形(本文主要針對D-Cache):

一、Cortex-M7的Cache功能

  對於Cortex-M系列家族(M0+/M3/M4/M7/M23/M33/M35P/M55)來說,L1 Cache僅在Cortex-M7和Cortex-M55核心上存在,說白了,L1 Cache是專為高效能核心配置的,而目前的i.MXRT1xxx系列微控制器都是基於Cortex-M7核心。

  下面是i.MXRT1050的核心系統框圖,可以看到它整合了32KB D-Cache,Cache經由AXI64匯流排連到SIM_M7和SIM_EMS模組,最終轉成AHB匯流排連線到FlexSPI模組,因此對於Flash的AHB讀訪問是可以受到D-Cache加速的。

  關於D-Cache工作機制,可以在 ARM Cortex-M7 Processor Technical Reference Manual 手冊中找到詳細解釋。簡單地概括就是32KB D-Cache會被劃分成1024個Cache Line,每個Cache Line大小為32個位元組,四個Cache Line是一組(即所謂的4-way set associative),每一組Cache Line會有一個地址標籤,地址標籤用來記錄Cache所快取的資料所在目標地址資訊。

  L1 D-Cache使能時,對目標儲存器的AHB讀訪問總共有兩大類:Hit(要訪問的資料在Cache裡面)、Miss(要訪問的資料不在Cache裡面),Hit沒什麼好說的,直接從Cache裡取資料就行了;Miss後則會先把資料從目標儲存器中讀到Cache裡,然後再從Cache讀出資料(這就是所謂的Read-Allocate,實際上有另一個名詞Read-Through與之對應,Read-Through即直接從目標儲存器中讀出資料,一般是Cache不使能時的行為)。

  對目標地址空間的Cache策略控制主要是屬性配置(在核心MPU模組裡)和開關控制(在核心SCB模組裡),下面 BOARD_ConfigMPU() 函式即是典型的對FlexSPI地址對映空間所分配的Flash區域的Cache屬性配置,這個程式碼裡將0x60000000開始的64MB空間屬性配成了Normal Memory,不共享,Cache使能並且寫訪問行為是Write-Back(寫訪問還有另一種策略Write-Through),讀訪問行為不用配置(固定Read-Allocate)。

/* MPU configuration. */
void BOARD_ConfigMPU(void)
{
    /* Disable I cache and D cache */
    SCB_DisableICache();
    SCB_DisableDCache();

    /* Disable MPU */
    ARM_MPU_Disable();

    /* Region 0 setting: Instruction access disabled, No data access permission. */
    MPU->RBAR = ARM_MPU_RBAR(0, 0x00000000U);
    MPU->RASR = ARM_MPU_RASR(1, ARM_MPU_AP_NONE, 2, 0, 0, 0, 0, ARM_MPU_REGION_SIZE_4GB);

    /* Region 2 setting: Memory with Device type, not shareable,  non-cacheable. */
    MPU->RBAR = ARM_MPU_RBAR(2, 0x60000000U);
    MPU->RASR = ARM_MPU_RASR(0, ARM_MPU_AP_FULL, 2, 0, 0, 0, 0, ARM_MPU_REGION_SIZE_512MB);

#if defined(XIP_EXTERNAL_FLASH) && (XIP_EXTERNAL_FLASH == 1)
    /* Region 3 setting: Memory with Normal type, not shareable, cacheable, outer/inner write back. */
    MPU->RBAR = ARM_MPU_RBAR(3, 0x60000000U);
    MPU->RASR = ARM_MPU_RASR(0, ARM_MPU_AP_RO, 0, 0, 1, 1, 0, ARM_MPU_REGION_SIZE_64MB);
#endif

    /* Enable MPU */
    ARM_MPU_Enable(MPU_CTRL_PRIVDEFENA_Msk);

    /* Enable I cache and D cache */
    SCB_EnableDCache();
    SCB_EnableICache();
}

  最後再提一下跟本文主題不相干的Cache使能下寫訪問行為策略:

  • (Hit情形下)Write-Through模式: 直接寫到目標儲存器中並且也在Cache裡更新(無多Master訪問造成的資料一致性問題,但沒有提升寫訪問效能)
  • (Hit情形下)Write-Back模式: Cache line會被標為dirty,等到此行被invalidate時,才會執行實際的寫操作,將Cache Line裡面的資料寫到目標儲存器。(提升了寫訪問效能,但有隱患,如果 Cache 命中,此時僅 Cache 更新了,目標儲存器沒有更新,其他Master從目標儲存器裡面讀出來的資料是錯誤的)
  • (Miss情形下)Write-Allocate: 先把要寫的資料載入到Cache,然後再flush進目標儲存器。
  • (Miss情形下)no-Write-Allocate: 直接寫入目標儲存器。

二、D-Cache實驗準備

  參考文章 《實抓Flash訊號波形來看i.MXRT的FlexSPI外設下AHB讀訪問情形(無快取)》 裡的第一小節 實驗準備,本次實驗需要做一樣的準備工作。

三、D-Cache實驗程式碼

  參考文章 《實抓Flash訊號波形來看i.MXRT的FlexSPI外設下AHB讀訪問情形(無快取)》 裡的第二小節 實驗程式碼,本次實驗程式碼關於工程和連結檔案方面是一樣的設定,但是具體測試函式改成如下ramfunc型函式 test_cacheable_read()。關於D-Cache這次會有很多種不同測試,while(1)語句前的系統配置保持不變,while(1)裡面的語句可根據實際測試情況去調整:

#if (defined(__ICCARM__))
#pragma optimize = none
__ramfunc 
#endif
void test_cacheable_read(void)
{
    // 系統配置
    /* Disable L1 I-Cache*/
    SCB_DisableICache();

    /* Enable L1 D-Cache*/
    SCB_EnableDCache();
    SCB_CleanInvalidateDCache();

    // 根據測試需求,開/關FlexSPI的Prefetch特性

    while (1)
    {
        // 測試用例程式碼,可按情況調整
    } 
}

  為了便於分辨IO[1:0]上的資料去幫助分析本系列測試用例結果,我們需要擴充下特殊const資料區.ahbRdBuffer設定如下:

const uint8_t ahbRdBlock1[1024] @ ".ahbRdBuffer1" = {
    // 正順序
    0x00, 0x01, 0x02, 0x03, 0x10, 0x11, 0x12, 0x13,
    0x20, 0x21, 0x22, 0x23, 0x30, 0x31, 0x32, 0x33,
    // 倒順序
    0x33, 0x32, 0x31, 0x30, 0x23, 0x22, 0x21, 0x20,
    0x13, 0x12, 0x11, 0x10, 0x03, 0x02, 0x01, 0x00,
    // 正插序
    0x01, 0x00, 0x03, 0x02, 0x11, 0x10, 0x13, 0x12, 
    0x21, 0x20, 0x23, 0x22, 0x31, 0x30, 0x33, 0x32, 
    // 倒插序
    0x32, 0x33, 0x30, 0x31, 0x22, 0x23, 0x20, 0x21, 
    0x12, 0x13, 0x10, 0x11, 0x02, 0x03, 0x00, 0x01, 
};

const uint8_t ahbRdBlock2[1024] @ ".ahbRdBuffer2" = {
    // 倒插序
    0x32, 0x33, 0x30, 0x31, 0x22, 0x23, 0x20, 0x21, 
    0x12, 0x13, 0x10, 0x11, 0x02, 0x03, 0x00, 0x01, 
    // 正插序
    0x01, 0x00, 0x03, 0x02, 0x11, 0x10, 0x13, 0x12, 
    0x21, 0x20, 0x23, 0x22, 0x31, 0x30, 0x33, 0x32,
    // 倒順序
    0x33, 0x32, 0x31, 0x30, 0x23, 0x22, 0x21, 0x20,
    0x13, 0x12, 0x11, 0x10, 0x03, 0x02, 0x01, 0x00,
    // 正順序
    0x00, 0x01, 0x02, 0x03, 0x10, 0x11, 0x12, 0x13,
    0x20, 0x21, 0x22, 0x23, 0x30, 0x31, 0x32, 0x33,
};

// 在工程連結檔案中
keep{ section .ahbRdBuffer1, section .ahbRdBuffer2 };
place at address mem:0x60002400 { readonly section .ahbRdBuffer1 };
place at address mem:0x60002800 { readonly section .ahbRdBuffer2 };

四、D-Cache實驗結果

4.1 重做無快取一文中的實驗

  現在讓我們在開啟D-Cache的情況下重新做文章 《實抓Flash訊號波形來看i.MXRT的FlexSPI外設下AHB讀訪問情形(無快取)》 中全部實驗:

#define AHB_ADDR_START (0x60002400)
void test_cacheable_read(void)
{
    // 略去系統配置(I-Cache、Prefetch關閉,D-Cache開啟)
    while (1)
    {
        SDK_DelayAtLeastUs(10, SystemCoreClock);
        for (uint32_t i = 1; i <= 8; i++)
        {   
            SDK_DelayAtLeastUs(2, SystemCoreClock);
            memcpy((void *)0x20200000, (void *)AHB_ADDR_START, i);
        }
    } 
}
4.1.1 AHB_ADDR_START 取值 [0x60002400 - 0x60002418]

  當 AHB_ADDR_START 取值範圍在 [0x60002400 - 0x60002418] 中時,Flash端的時序波形圖都是如下同一個。因為有了D-Cache,現在我們看不到週期性的CS訊號了,說明除了Flash新地址訪問是必須要通過FlexSPI外設去讀取Flash之外,其後的同一Flash地址的重複訪問都直接發生在D-Cache裡了。

  另外D-Cache起始快取地址永遠是32位元組對齊的地址處,並且一次快取32byte的資料(因為D-Cache Line大小就是32byte),所以波形結果裡看,起始地址都是0x60002400,一次讀取32byte資料(存在一個D-Cache Line裡),因此之前不開D-Cache和Prefetch下的AHB Burst Read策略導致的訪問不同對齊地址的波形差異測試結果在這裡就不存在了。

4.1.2 AHB_ADDR_START = 0x60002419

  當實際程式碼中要讀取的Flash資料會橫跨兩個相鄰32位元組對齊的資料塊(0x60002400 - 0x6000241f, 0x60002420 - 0x6000243f),此時Flash端會出現兩次CS有效訊號,每次均傳輸32byte資料,D-Cache一直在持續作用,這次動用了兩個D-Cache Line(D-Cache總大小有32KB,共有1024個Cache Line),因此在Flash端我們還是看不到週期性CS訊號。

4.1.3 追加實驗,從0x60002400處讀取1KB

  當程式碼迴圈讀取1KB資料時,波形圖上可以看到32個CS有效訊號,每個CS有效期間傳輸32byte資料,總計1KB資料的傳輸,D-Cache這次派出了32個 Cache Line,在Flash端我們依然看不到週期性CS訊號。

4.2 重做有預取一文中的實驗

  現在讓我們在開啟D-Cache的情況下重新做文章 《實抓Flash訊號波形來看i.MXRT的FlexSPI外設下AHB讀訪問情形(有預取)》 中全部實驗:

4.2.1 迴圈讀取首地址32位元組對齊的1KB空間內的任意長度資料塊,起始拷貝地址位於前31個位元組內

  這種情況下,Flash端實際波形與 《實抓Flash訊號波形來看i.MXRT的FlexSPI外設下AHB讀訪問情形(有預取)》 中 4.1 裡的測試結果差不多,這裡就不再貼圖了。Prefetch機制做第一層快取,D-Cache獲取Prefetch Buffer裡的結果做二次快取,唯一的差異是因為D-Cache的存在,快取起始地址可能會發生變化(從八位元組對齊變成了32位元組對齊):

#define PREFETCH_TEST_ALIGNMENT  (7) // 可取值 0 - 31
#define PREFETCH_TEST_START      (0x60002400 + PREFETCH_TEST_ALIGNMENT)
uint32_t testLen = 0x1;  // 可取值 1 - (1KB-PREFETCH_TEST_ALIGNMENT)
void test_cacheable_read(void)
{
    // 略去系統配置(I-Cache關閉,Prefetch開啟,D-Cache開啟)
    while (1)
    {
        memcpy((void *)0x20200000, (void *)PREFETCH_TEST_START, testLen);
    } 
}
4.2.2 迴圈讀取大於1KB的資料塊或首地址非32位元組對齊的1KB資料塊

  這種情況下,Flash端會有兩次完整的1KB Prefetch操作,第一次Prefetch操作讀取了0x60002400處的1KB,第二次Prefetch操作讀取了0x60002800處的1KB。因為有D-Cache的存在,第二次Prefetch操作有了足夠時間去完成,不用額外插入軟延時去避免其被while(1)迴圈回來的下一次訪問需求打斷了:

void test_cacheable_read(void)
{
    // 略去系統配置(I-Cache關閉,Prefetch開啟,D-Cache開啟)
    while (1)
    {
        memcpy((void *)0x20200001, (void *)0x60002401, 0x400);
    } 
}

4.2.3 迴圈讀取兩個不同資料塊(在首地址32位元組對齊的兩個不同1KB空間內)

  這種情況下,即使有D-Cache存在,第一次CS期間的Prefetch操作(即memcpy((void *)0x20200000, (void *)0x60002400, 0x100);引發的)還是被第二次CS的Prefetch操作打斷了(即memcpy((void *)0x20200400, (void *)0x60002800, 0x100);),但是第二次CS期間的Prefetch操作不會再被打斷,因為接下來while(1)迴圈回來的Flash資料訪問需求已經快取在D-Cache裡:

void test_cacheable_read(void)
{
    // 略去系統配置(I-Cache關閉,Prefetch開啟,D-Cache開啟)
    while (1)
    {
        memcpy((void *)0x20200000, (void *)0x60002400, 0x100);
        memcpy((void *)0x20200400, (void *)0x60002800, 0x100);
    } 
}

4.3 如何在D-Cache使能的情況下看到週期性CS訊號

  前面測試了那麼多種情況,我們有沒有可能在Flash端看到週期性CS訊號呢,即Flash持續地被讀取呢?當然可以,我們知道D-Cache總大小是32KB,我們只要迴圈拷貝32KB以上資料,D-Cache就開始hold不住了,這不,下面程式碼就能讓我們看到久違的週期時序波形圖了(小心,Flash持續工作會多耗電的,哈哈)。

void test_cacheable_read(void)
{
    // 略去系統配置(I-Cache關閉,Prefetch開啟,D-Cache開啟)
    while (1)
    {
        memcpy((void *)0x20200000, (void *)0x60002400, 0x8000 + 1);
    } 
}

  至此,實抓Flash訊號波形來看i.MXRT的FlexSPI外設下AHB讀訪問情形痞子衡便介紹完畢了,掌聲在哪裡~~~

歡迎訂閱

文章會同時釋出到我的 部落格園主頁CSDN主頁知乎主頁微信公眾號 平臺上。

微信搜尋"痞子衡嵌入式"或者掃描下面二維碼,就可以在手機上第一時間看了哦。

相關文章