賈 工
先楫資深FAE工程師
12年產(chǎn)品研發(fā)經(jīng)驗(yàn),具有變頻器、伺服等工業(yè)產(chǎn)品開發(fā)經(jīng)驗(yàn),也負(fù)責(zé)過激光投影顯示系統(tǒng)開發(fā)、AI應(yīng)用開發(fā)、PYQT、Linux驅(qū)動開發(fā)等工作。
概 述
高速緩存(Cache)主要是為了解決CPU運(yùn)算速度與內(nèi)存(Memory)讀寫速度不匹配的矛盾而存在, 是CPU與存儲設(shè)備之間的臨時存貯器,容量小,但是交換速度比內(nèi)存快。內(nèi)置高速緩存通常對CPU的性能提升具有較大作用。
CPU要讀取一個數(shù)據(jù)時,首先從Cache中查找,如果找到就立即讀取并送給CPU處理;如果沒有找到,就用相對慢的速度從內(nèi)存中讀取并送給CPU處理,同時把這個數(shù)據(jù)所在的數(shù)據(jù)塊調(diào)入Cache中,可以使得以后對整塊數(shù)據(jù)的讀取都從Cache中進(jìn)行,不必再調(diào)用內(nèi)存。
這樣的讀取機(jī)制使CPU讀取Cache的命中率非常高(大多數(shù)CPU可達(dá)90%左右),也就是說CPU下一次要讀取的數(shù)據(jù)90%都在Cache中,只有大約10%需要從內(nèi)存讀取。HPM CPU訪問片上的Cache內(nèi)數(shù)據(jù)是零等待的,這大大節(jié)省了CPU直接讀取內(nèi)存數(shù)據(jù)的時間,使CPU讀取數(shù)據(jù)時基本無需等待??偟膩碚f,CPU讀取數(shù)據(jù)的順序是先Cache后存儲設(shè)備。
一、Cacheable Memory 相關(guān)概念
在訪問HPM片上ILM與DLM(Local Memory)時,芯片物理結(jié)構(gòu)決定了CPU不會使用Cache去緩存Local Memory的數(shù)據(jù)。訪問其它存儲設(shè)備如flash、sram、sdram等,則Cache可以發(fā)揮其緩存機(jī)制來加快訪問速度。在Cache生效的地址空間內(nèi),用戶可以設(shè)置Memory的物理存儲屬性來設(shè)置是否對指定的地址空間使用Cache。
PMA(Physical Memory Attributes)是指一段存儲地址空間的可讀寫、可執(zhí)行、可緩存等屬性。讀、寫、執(zhí)行等屬性容易理解,此處不贅述。下面介紹幾個其它屬性及相關(guān)的概念。(注意:HPM5300系列不支持PMA設(shè)置)
首先介紹一些Cache基本概念。
1. Cache Line/dirty/invalidate
Cache Line:一次最少緩存多少字節(jié)的數(shù)據(jù)是有要求的,通常以Cache Line為單位。HPM6000系列MCU Cache Line為64byte,HPM5300系列MCU Cache Line為32byte。在進(jìn)行PMA設(shè)置時,要求起始地址按Cache Line字節(jié)數(shù)對齊,大小為Cache Line大小的整數(shù)倍。聲明數(shù)組時最好也遵循此規(guī)則。
Dirty:表示某Cache Line的數(shù)據(jù)是否與Memory保持一致,如果只將數(shù)據(jù)寫入Cache而沒有寫入Memory,會將該Cache Line標(biāo)記為dirty。
Invalidate:將某地址范圍的Cache Line數(shù)據(jù)失效掉,當(dāng)Cache Line狀態(tài)被Invalidate時,不管讀取是否命中,CPU都會到Memory拿數(shù)據(jù)。
對Cache的標(biāo)準(zhǔn)操作包括 write-back,invalidate,flush。
Write-back表示把cache內(nèi)dirty的數(shù)據(jù)寫入Memory,invalidate表示忽略某地址范圍的Cache line,flush操作則先對某Cache Line 進(jìn)行write-back操作,再進(jìn)行invalidate操作。HPM SDK的hpm_l1c_drv.h文件提供了這3種操作的接口函數(shù)。
2. Bufferable
Bufferable是指MCU在寫入一片內(nèi)存區(qū)時,是否可使用Write buffer進(jìn)行加速。例如向sram內(nèi)寫入64個字節(jié):
1)不使用Bufferable:CPU等待64字節(jié)數(shù)據(jù)寫入完成后再去執(zhí)行其它指令;
2)使用Bufferable:CPU將64字節(jié)數(shù)據(jù)寫入Write buffer,不等Write buffer內(nèi)的數(shù)據(jù)寫入sram,CPU就去執(zhí)行其它指令;寫入動作則自動進(jìn)行直至完成。
3. Cacheable
Cacheable與 non-Cacheable,決定了CPU是否啟用緩存特性。如果啟用Cacheable特性,則HPM芯片上的內(nèi)存區(qū)域可以分區(qū)指定PMA,可選的屬性選項如下(詳細(xì)信息可參考先楫官方文檔HPM6200 UM 2.8章節(jié)):
Write-Back
Write-Back(與Write-Through互斥)是指向存儲設(shè)備內(nèi)寫數(shù)據(jù)命中時,CPU將數(shù)據(jù)寫入Cache,并不立馬向存儲設(shè)備寫入數(shù)據(jù),如下圖所示:數(shù)據(jù)先寫入到Cache內(nèi)(①),在Cache內(nèi)標(biāo)記該Cache Line為dirty,即表示該Cache Line內(nèi)容與Memory內(nèi)容不符;Cache內(nèi)數(shù)據(jù)寫入Memory(②),則在Cache Line被替換或手動執(zhí)行write-back操作或flush操作時(把dirty的數(shù)據(jù)寫入Memory)才執(zhí)行。
未命中時,則寫入Memory。是否寫入Cache 由xxx-Allocate決定。
Write-Through
Write-Through(與Write-Back互斥)是指向存儲設(shè)備內(nèi)寫數(shù)據(jù)時,無論命中與否,CPU都將數(shù)據(jù)寫入Memory。
命中時,數(shù)據(jù)同時寫入Cache 與Memory;
未命中時,數(shù)據(jù)寫入Memory,是否寫入Cache 由xxx-Allocate決定。
xxx-Allocate
xxx-Allocate則用于控制讀/寫未命中Cache時,是否要在Cache內(nèi)申請Cache Line用于緩存讀/寫的數(shù)據(jù)。例如:
Read-Allocate代表讀未命中時,CPU不只從Memory將數(shù)據(jù)讀入,還將數(shù)據(jù)在Cache放了一份,那么下次再讀的時候就不用去Memory讀了;
Write-Allocate代表寫未命中時,會在Cache內(nèi)分配Cache Line儲存寫入的數(shù)據(jù),那么下次讀的時候就可以從Cache讀了;具體是否寫入Memory取決于使用的是Write-Back還是Write-Through。
Non-Allocate和 Read-and-Write-Allocate就不再進(jìn)行解釋了。
/* Init noncachable memory */
externuint32_t__noncacheable_start__[];
externuint32_t__noncacheable_end__[];
start_addr = (uint32_t) __noncacheable_start__;
end_addr = (uint32_t) __noncacheable_end__;
length = end_addr - start_addr;
if(length > 0) {
/* Ensure the address and the length are power of 2 aligned */
assert((length & (length - 1U)) == 0U);
assert((start_addr & (length - 1U)) == 0U);
pmp_entry[index].pmp_addr= PMP_NAPOT_ADDR(start_addr, length);
pmp_entry[index].pmp_cfg.val= PMP_CFG(READ_EN, WRITE_EN, EXECUTE_EN, ADDR_MATCH_NAPOT, REG_UNLOCK);
pmp_entry[index].pma_addr= PMA_NAPOT_ADDR(start_addr, length);
pmp_entry[index].pma_cfg.val= PMA_CFG(ADDR_MATCH_NAPOT, MEM_TYPE_MEM_NON_CACHE_BUF, AMO_EN);
index++;
}
pmp_config(&pmp_entry[0], index);
以上代碼設(shè)置了__noncacheable_start__至__noncacheable_end__地址范圍內(nèi)的存儲區(qū)域PMA屬性為noncacheable,bufferable。
通過以上解釋,相信開發(fā)者可以看懂UM手冊內(nèi)的相關(guān)描述了,以HPM6200系列為例,User Manual v2.0 2.8章節(jié)的內(nèi)容對PMA有詳細(xì)描述。
二、HPM L1-Cache相關(guān)函數(shù)
HPM系列芯片L1-Cache分為 iCache與 dCache,指令緩存與數(shù)據(jù)緩存。開發(fā)者們經(jīng)常遇到的問題是開啟dCache導(dǎo)致的CPU拿到的數(shù)據(jù)與Memory內(nèi)數(shù)據(jù)不一致(Cache內(nèi)的數(shù)據(jù)與Memory不一致時,讀取命中Cache會發(fā)生這樣的結(jié)果)。因此,此處主要介紹 dCache相關(guān)函數(shù)。
打開hpm_l1c_drv.h文件即可看到先楫提供的Cache相關(guān)的函數(shù),部分如下:
*
* @brief D-cache disable
*/
voidl1c_dc_disable(void);
/*
* @brief D-cache enable
*/
voidl1c_dc_enable(void);
/*
* @brief D-cache invalidate by address
* @param[in] address Start address to be invalidated
* @param[in] size Size of memory to be invalidated
*/
voidl1c_dc_invalidate(uint32_taddress, uint32_tsize);
/*
* @brief D-cache writeback by address
* @param[in] address Start address to be writtenback
* @param[in] size Size of memory to be writtenback
*/
voidl1c_dc_writeback(uint32_taddress, uint32_tsize);
/*
* @brief D-cache invalidate and writeback by address
* @param[in] address Start address to be invalidated and writtenback
* @param[in] size Size of memory to be invalidted and writtenback
*/
voidl1c_dc_flush(uint32_taddress, uint32_tsize);
/*
* @brief D-cache fill and lock by address
* @param[in] address Start address to be filled and locked
* @param[in] size Size of memory to be filled and locked
*/
voidl1c_dc_fill_lock(uint32_taddress, uint32_tsize);
/*
* @brief Invalidate all icache and writeback all dcache
*/
voidl1c_fence_i(void);
/*
* @brief Invalidate all d-cache
*/
voidl1c_dc_invalidate_all(void);
/*
* @brief Writeback all d-cache
*/
voidl1c_dc_writeback_all(void);
/*
* @brief Flush all d-cache
*/
voidl1c_dc_flush_all(void);
l1c_dc_disable:關(guān)閉dCache。此函數(shù)特別有用,在debug時如果懷疑是Cache導(dǎo)致的問題,在main函數(shù)開始關(guān)閉dCache再次運(yùn)行即可排查是否是Cache導(dǎo)致的問題。注意,如果是用戶程序運(yùn)行過程中關(guān)閉dCache,需要在關(guān)閉前將執(zhí)行l(wèi)1c_dc_writeback_all,保證Cache數(shù)據(jù)寫入Memory。
l1c_dc_enable:開啟dCache。
l1c_dc_invalidate:將某地址范圍內(nèi)的Cache Line失效掉。無論某地址在Cache內(nèi)是否命中,CPU會從Memory內(nèi)拿數(shù)據(jù)。
l1c_dc_writeback:將Cache內(nèi)數(shù)據(jù)寫入某Memory地址。如果該地址在Cache內(nèi),則將該Cache Line寫入Memory,并清除dirty標(biāo)志。
l1c_dc_flush:該函數(shù)等于 l1c_dc_writeback + l1c_dc_invalidate,把數(shù)據(jù)寫入到Memory并標(biāo)記為invalidate,表示下次從Memory拿數(shù)據(jù)時不走Cache。
l1c_fence_i:將dCache內(nèi)的數(shù)據(jù)全部writeback,將iCache內(nèi)所有Cache Line invalidate。一般關(guān)閉Cache前會手動調(diào)用此函數(shù)。
l1c_dc_invalidate_all、l1c_dc_writeback_all、l1c_dc_flush_all:代表操作整個Cache中的Cache Line,具體含義不再贅述。
三、經(jīng)驗(yàn)分享
l1c_dc_writeback:一般非CPU的總線host,如DMA訪問某Memory地址前,通過l1c_dc_writeback將Cache內(nèi)的數(shù)據(jù)寫到Memory,保證DMA拿到的數(shù)據(jù)與CPU看到的數(shù)據(jù)是一致的。
例如I2C_DMA例程:
/* setup i2c dma tx */
#ifPLACE_BUFF_AT_CACHEABLE
if(l1c_dc_is_enabled()) {
/* cache writeback before DMA sent data */
l1c_dc_writeback((uint32_t)tx_buff, TEST_TRANSFER_DATA_IN_BYTE);
}
#endif
stat= i2c_tx_trigger_dma(TEST_I2C_DMA,
TEST_I2C_DMA_CH,
TEST_I2C,
core_local_mem_to_sys_address(BOARD_RUNNING_CORE, (uint32_t)tx_buff),
TEST_TRANSFER_DATA_IN_BYTE);
if(stat!= status_success) {
printf("i2c tx trigger dma failed\n");
while(1) {
}
}
在DMA將tx_buff數(shù)據(jù)搬到I2C的發(fā)送寄存器之前,進(jìn)行了writeback。
l1c_dc_invalidate:一般CPU在讀取Memory數(shù)據(jù)時,如果該數(shù)據(jù)被其它總線host如DMA操作過(一般是DMA搬了某些數(shù)據(jù)過去),為了能讀到Memory中的數(shù)據(jù)而不是Cache中的數(shù)據(jù),要在讀取之前對Cache Line進(jìn)行invalidate處理(多數(shù)開發(fā)者遇到的都是這個問題)。
例如I2C_DMA例程:
/* setup i2c dma rx */
stat= i2c_rx_trigger_dma(TEST_I2C_DMA,
TEST_I2C_DMA_CH,
TEST_I2C,
core_local_mem_to_sys_address(BOARD_RUNNING_CORE, (uint32_t)rx_buff),
TEST_TRANSFER_DATA_IN_BYTE);
i2c_master_start_dma_read(TEST_I2C, TEST_I2C_SLAVE_ADDRESS, TEST_TRANSFER_DATA_IN_BYTE);
i2c_handle_dma_transfer_complete(TEST_I2C);
#ifPLACE_BUFF_AT_CACHEABLE
if(l1c_dc_is_enabled()) {
/* cache invalidate after DMA receive data */
l1c_dc_invalidate((uint32_t)rx_buff, TEST_TRANSFER_DATA_IN_BYTE);
}
#endif
check_transfer_data();
在進(jìn)行check_transfer_data之前,先對數(shù)據(jù)進(jìn)行了l1c_dc_invalidate處理。
l1c_fence_i:一般在CPU關(guān)閉Cache之前,或程序跳轉(zhuǎn)之前(一般二級boot選擇好要執(zhí)行的固件進(jìn)行跳轉(zhuǎn)),為了保證所有dirty的Cache Line寫入到Memory中,會進(jìn)行l(wèi)1c_dc_writeback_all,然后等 l1c_dc_writeback_all執(zhí)行完畢后再跳轉(zhuǎn)。
例如tinyuf2例程:
voiduf2_board_app_jump(void)
{
fencei();
l1c_dc_disable();
l1c_ic_disable();
__asm("la a0, %0"::"i"(BOARD_FLASH_APP_START+ 4));
__asm("jr a0");
}
uf2_board_app_jump函數(shù)在跳轉(zhuǎn)前,執(zhí)行了fencei,本質(zhì)上就是l1c_fence_i。
另外,在執(zhí)行writeback操作期間中斷不可用,對實(shí)時性要求高的場景應(yīng)進(jìn)行合理規(guī)劃l1c_dc_writeback_all的使用。
四、文末小結(jié)
Cache能大幅提高程序運(yùn)行性能,但用不好Cache也會給開發(fā)者帶來各種“奇奇怪怪”的問題現(xiàn)象。在閱讀本文后,希望開發(fā)者對先楫的 L1-Cache有更深入的理解,使用先楫半導(dǎo)體高性能 MCU系列產(chǎn)品開發(fā)項目時,能更加得心應(yīng)手。
-
Cache
+關(guān)注
關(guān)注
0文章
129瀏覽量
28344 -
HPM
+關(guān)注
關(guān)注
1文章
23瀏覽量
7747 -
先楫半導(dǎo)體
+關(guān)注
關(guān)注
10文章
214瀏覽量
2120
發(fā)布評論請先 登錄
相關(guān)推薦
評論