大家好,我是痞子衡,是正經(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機(jī)制并沒有明顯提升訪問效率。
針對這種不連續(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工作機(jī)制,可以在 ARM Cortex-M7 Processor Technical Reference Manual 手冊中找到詳細(xì)解釋。簡單地概括就是32KB D-Cache會被劃分成1024個Cache Line,每個Cache Line大小為32個字節(jié),四個Cache Line是一組(即所謂的4-way set associative),每一組Cache Line會有一個地址標(biāo)簽,地址標(biāo)簽用來記錄Cache所緩存的數(shù)據(jù)所在目標(biāo)地址信息。
L1 D-Cache使能時,對目標(biāo)存儲器的AHB讀訪問總共有兩大類:Hit(要訪問的數(shù)據(jù)在Cache里面)、Miss(要訪問的數(shù)據(jù)不在Cache里面),Hit沒什么好說的,直接從Cache里取數(shù)據(jù)就行了;Miss后則會先把數(shù)據(jù)從目標(biāo)存儲器中讀到Cache里,然后再從Cache讀出數(shù)據(jù)(這就是所謂的Read-Allocate,實際上有另一個名詞Read-Through與之對應(yīng),Read-Through即直接從目標(biāo)存儲器中讀出數(shù)據(jù),一般是Cache不使能時的行為)。
對目標(biāo)地址空間的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模式: 直接寫到目標(biāo)存儲器中并且也在Cache里更新(無多Master訪問造成的數(shù)據(jù)一致性問題,但沒有提升寫訪問性能)(Hit情形下)Write-Back模式: Cache line會被標(biāo)為dirty,等到此行被invalidate時,才會執(zhí)行實際的寫操作,將Cache Line里面的數(shù)據(jù)寫到目標(biāo)存儲器。(提升了寫訪問性能,但有隱患,如果 Cache 命中,此時僅 Cache 更新了,目標(biāo)存儲器沒有更新,其他Master從目標(biāo)存儲器里面讀出來的數(shù)據(jù)是錯誤的)(Miss情形下)Write-Allocate: 先把要寫的數(shù)據(jù)載入到Cache,然后再flush進(jìn)目標(biāo)存儲器。(Miss情形下)no-Write-Allocate: 直接寫入目標(biāo)存儲器。
二、D-Cache實驗準(zhǔn)備
參考文章 《實抓Flash信號波形來看i.MXRT的FlexSPI外設(shè)下AHB讀訪問情形(無緩存)》 里的第一小節(jié) 實驗準(zhǔn)備,本次實驗需要做一樣的準(zhǔn)備工作。
三、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]
當(dāng) 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起始緩存地址永遠(yuǎn)是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
當(dāng)實際代碼中要讀取的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
當(dāng)代碼循環(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機(jī)制做第一層緩存,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āng)然可以,我們知道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讀訪問情形痞子衡便介紹完畢了,掌聲在哪里~~~