加入星計劃,您可以享受以下權(quán)益:

  • 創(chuàng)作內(nèi)容快速變現(xiàn)
  • 行業(yè)影響力擴散
  • 作品版權(quán)保護
  • 300W+ 專業(yè)用戶
  • 1.5W+ 優(yōu)質(zhì)創(chuàng)作者
  • 5000+ 長期合作伙伴
立即加入
  • 正文
  • 相關(guān)推薦
  • 電子產(chǎn)業(yè)圖譜
申請入駐 產(chǎn)業(yè)圖譜

Cortex-M7內(nèi)核的Cache是如何提升訪問效率的?且看硬核實測

2021/05/10
552
閱讀需 17 分鐘
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點資訊討論

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

上一篇文章 《實抓Flash信號波形來看i.MXRT的FlexSPI外設(shè)下AHB讀訪問情形(有預(yù)?。?里痞子衡抓取了Cache關(guān)閉但Prefetch開啟下的AHB讀訪問對應(yīng)的Flash端時序波形圖,我們知道了FlexSPI的Prefetch功能確實在一定程度上改善了Flash訪問效率,但是AHB RX Buffer最大僅1KB(對i.MXRT1050而言),不可拆分成更小粒度Buffer去緩存不同F(xiàn)lash地址處的數(shù)據(jù)(對于同一AHB master而言),這樣對于代碼中多個不同小數(shù)據(jù)塊重復(fù)的Flash空間訪問,Prefetch機制并沒有明顯提升訪問效率。

針對這種不連續(xù)Flash地址空間頻繁訪問低效情況,ARM Cortex-M7內(nèi)核給出了解決方案,那就是L1 Cache技術(shù),今天痞子衡就來繼續(xù)測一測開啟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內(nèi)核上存在,說白了,L1 Cache是專為高性能內(nèi)核配置的,而目前的i.MXRT1xxx系列微控制器都是基于Cortex-M7內(nèi)核。

下面是i.MXRT1050的內(nèi)核系統(tǒng)框圖,可以看到它集成了32KB D-Cache,Cache經(jīng)由AXI64總線連到SIM_M7和SIM_EMS模塊,最終轉(zhuǎn)成AHB總線連接到FlexSPI模塊,因此對于Flash的AHB讀訪問是可以受到D-Cache加速的。

關(guān)于D-Cache工作機制,可以在 ARM Cortex-M7 Processor Technical Reference Manual 手冊中找到詳細解釋。簡單地概括就是32KB D-Cache會被劃分成1024個Cache Line,每個Cache Line大小為32個字節(jié),四個Cache Line是一組(即所謂的4-way set associative),每一組Cache Line會有一個地址標簽,地址標簽用來記錄Cache所緩存的數(shù)據(jù)所在目標地址信息。

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

對目標地址空間的Cache策略控制主要是屬性配置(在內(nèi)核MPU模塊里)和開關(guān)控制(在內(nèi)核SCB模塊里),下面 BOARD_ConfigMPU() 函數(shù)即是典型的對FlexSPI地址映射空間所分配的Flash區(qū)域的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訪問造成的數(shù)據(jù)一致性問題,但沒有提升寫訪問性能)(Hit情形下)Write-Back模式: Cache line會被標為dirty,等到此行被invalidate時,才會執(zhí)行實際的寫操作,將Cache Line里面的數(shù)據(jù)寫到目標存儲器。(提升了寫訪問性能,但有隱患,如果 Cache 命中,此時僅 Cache 更新了,目標存儲器沒有更新,其他Master從目標存儲器里面讀出來的數(shù)據(jù)是錯誤的)(Miss情形下)Write-Allocate: 先把要寫的數(shù)據(jù)載入到Cache,然后再flush進目標存儲器。(Miss情形下)no-Write-Allocate: 直接寫入目標存儲器。

 

二、D-Cache實驗準備

參考文章 《實抓Flash信號波形來看i.MXRT的FlexSPI外設(shè)下AHB讀訪問情形(無緩存)》 里的第一小節(jié) 實驗準備,本次實驗需要做一樣的準備工作。

 

三、D-Cache實驗代碼

參考文章 《實抓Flash信號波形來看i.MXRT的FlexSPI外設(shè)下AHB讀訪問情形(無緩存)》 里的第二小節(jié) 實驗代碼,本次實驗代碼關(guān)于工程和鏈接文件方面是一樣的設(shè)置,但是具體測試函數(shù)改成如下ramfunc型函數(shù) test_cacheable_read()。關(guān)于D-Cache這次會有很多種不同測試,while(1)語句前的系統(tǒng)配置保持不變,while(1)里面的語句可根據(jù)實際測試情況去調(diào)整:

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

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

    // 根據(jù)測試需求,開/關(guān)FlexSPI的Prefetch特性

    while (1)
    {
        // 測試用例代碼,可按情況調(diào)整
    } 
}

為了便于分辨IO[1:0]上的數(shù)據(jù)去幫助分析本系列測試用例結(jié)果,我們需要拓展下特殊const數(shù)據(jù)區(qū).ahbRdBuffer設(shè)置如下:

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實驗結(jié)果

4.1 重做無緩存一文中的實驗

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

#define AHB_ADDR_START (0x60002400)
void test_cacheable_read(void)
{
    // 略去系統(tǒng)配置(I-Cache、Prefetch關(guān)閉,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] 中時,F(xiàn)lash端的時序波形圖都是如下同一個。因為有了D-Cache,現(xiàn)在我們看不到周期性的CS信號了,說明除了Flash新地址訪問是必須要通過FlexSPI外設(shè)去讀取Flash之外,其后的同一Flash地址的重復(fù)訪問都直接發(fā)生在D-Cache里了。

另外D-Cache起始緩存地址永遠是32字節(jié)對齊的地址處,并且一次緩存32byte的數(shù)據(jù)(因為D-Cache Line大小就是32byte),所以波形結(jié)果里看,起始地址都是0x60002400,一次讀取32byte數(shù)據(jù)(存在一個D-Cache Line里),因此之前不開D-Cache和Prefetch下的AHB Burst Read策略導(dǎo)致的訪問不同對齊地址的波形差異測試結(jié)果在這里就不存在了。

 
4.1.2 AHB_ADDR_START = 0x60002419

當實際代碼中要讀取的Flash數(shù)據(jù)會橫跨兩個相鄰32字節(jié)對齊的數(shù)據(jù)塊(0x60002400 - 0x6000241f, 0x60002420 - 0x6000243f),此時Flash端會出現(xiàn)兩次CS有效信號,每次均傳輸32byte數(shù)據(jù),D-Cache一直在持續(xù)作用,這次動用了兩個D-Cache Line(D-Cache總大小有32KB,共有1024個Cache Line),因此在Flash端我們還是看不到周期性CS信號。

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

當代碼循環(huán)讀取1KB數(shù)據(jù)時,波形圖上可以看到32個CS有效信號,每個CS有效期間傳輸32byte數(shù)據(jù),總計1KB數(shù)據(jù)的傳輸,D-Cache這次派出了32個 Cache Line,在Flash端我們依然看不到周期性CS信號。

 

4.2 重做有預(yù)取一文中的實驗

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

4.2.1 循環(huán)讀取首地址32字節(jié)對齊的1KB空間內(nèi)的任意長度數(shù)據(jù)塊,起始拷貝地址位于前31個字節(jié)內(nèi)

這種情況下,F(xiàn)lash端實際波形與 《實抓Flash信號波形來看i.MXRT的FlexSPI外設(shè)下AHB讀訪問情形(有預(yù)?。?中 4.1 里的測試結(jié)果差不多,這里就不再貼圖了。Prefetch機制做第一層緩存,D-Cache獲取Prefetch Buffer里的結(jié)果做二次緩存,唯一的差異是因為D-Cache的存在,緩存起始地址可能會發(fā)生變化(從八字節(jié)對齊變成了32字節(jié)對齊):

#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)
{
    // 略去系統(tǒng)配置(I-Cache關(guān)閉,Prefetch開啟,D-Cache開啟)
    while (1)
    {
        memcpy((void *)0x20200000, (void *)PREFETCH_TEST_START, testLen);
    } 
}
 
4.2.2 循環(huán)讀取大于1KB的數(shù)據(jù)塊或首地址非32字節(jié)對齊的1KB數(shù)據(jù)塊

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

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

 
4.2.3 循環(huán)讀取兩個不同數(shù)據(jù)塊(在首地址32字節(jié)對齊的兩個不同1KB空間內(nèi))

這種情況下,即使有D-Cache存在,第一次CS期間的Prefetch操作(即memcpy((void *)0x20200000, (void *)0x60002400, 0x100);引發(fā)的)還是被第二次CS的Prefetch操作打斷了(即memcpy((void *)0x20200400, (void *)0x60002800, 0x100);),但是第二次CS期間的Prefetch操作不會再被打斷,因為接下來while(1)循環(huán)回來的Flash數(shù)據(jù)訪問需求已經(jīng)緩存在D-Cache里:

void test_cacheable_read(void)
{
    // 略去系統(tǒng)配置(I-Cache關(guān)閉,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持續(xù)地被讀取呢?當然可以,我們知道D-Cache總大小是32KB,我們只要循環(huán)拷貝32KB以上數(shù)據(jù),D-Cache就開始hold不住了,這不,下面代碼就能讓我們看到久違的周期時序波形圖了(小心,F(xiàn)lash持續(xù)工作會多耗電的,哈哈)。

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

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

Arm

Arm

ARM公司是一家知識產(chǎn)權(quán)(IP)供應(yīng)商,主要為國際上其他的電子公司提供高性能RISC處理器、外設(shè)和系統(tǒng)芯片技術(shù)授權(quán)。目前,ARM公司的處理器內(nèi)核已經(jīng)成為便攜通訊、手持計算設(shè)備、多媒體數(shù)字消費品等方案的RISC標準。公司1990年11月由Acorn、Apple和VLSI合并而成。

ARM公司是一家知識產(chǎn)權(quán)(IP)供應(yīng)商,主要為國際上其他的電子公司提供高性能RISC處理器、外設(shè)和系統(tǒng)芯片技術(shù)授權(quán)。目前,ARM公司的處理器內(nèi)核已經(jīng)成為便攜通訊、手持計算設(shè)備、多媒體數(shù)字消費品等方案的RISC標準。公司1990年11月由Acorn、Apple和VLSI合并而成。收起

查看更多

相關(guān)推薦

電子產(chǎn)業(yè)圖譜

碩士畢業(yè)于蘇州大學電子信息學院,目前就職于恩智浦(NXP)半導(dǎo)體MCU系統(tǒng)部門,擔任嵌入式系統(tǒng)應(yīng)用工程師。痞子衡會定期分享嵌入式相關(guān)文章