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



  大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家介紹的是實抓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主頁知乎主頁微信公眾號 平台上。

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM