賈 工
先楫資深FAE工程師
12年產品研發經驗,具有變頻器、伺服等工業產品開發經驗,也負責過激光投影顯示系統開發、AI應用開發、PYQT、Linux驅動開發等工作。
概 述
高速緩存(Cache)主要是為了解決CPU運算速度與內存(Memory)讀寫速度不匹配的矛盾而存在, 是CPU與存儲設備之間的臨時存貯器,容量小,但是交換速度比內存快。內置高速緩存通常對CPU的性能提升具有較大作用。
CPU要讀取一個數據時,首先從Cache中查找,如果找到就立即讀取并送給CPU處理;如果沒有找到,就用相對慢的速度從內存中讀取并送給CPU處理,同時把這個數據所在的數據塊調入Cache中,可以使得以后對整塊數據的讀取都從Cache中進行,不必再調用內存。
這樣的讀取機制使CPU讀取Cache的命中率非常高(大多數CPU可達90%左右),也就是說CPU下一次要讀取的數據90%都在Cache中,只有大約10%需要從內存讀取。HPM CPU訪問片上的Cache內數據是零等待的,這大大節省了CPU直接讀取內存數據的時間,使CPU讀取數據時基本無需等待??偟膩碚f,CPU讀取數據的順序是先Cache后存儲設備。
一、Cacheable Memory 相關概念
在訪問HPM片上ILM與DLM(Local Memory)時,芯片物理結構決定了CPU不會使用Cache去緩存Local Memory的數據。訪問其它存儲設備如flash、sram、sdram等,則Cache可以發揮其緩存機制來加快訪問速度。在Cache生效的地址空間內,用戶可以設置Memory的物理存儲屬性來設置是否對指定的地址空間使用Cache。
PMA(Physical Memory Attributes)是指一段存儲地址空間的可讀寫、可執行、可緩存等屬性。讀、寫、執行等屬性容易理解,此處不贅述。下面介紹幾個其它屬性及相關的概念。(注意:HPM5300系列不支持PMA設置)
首先介紹一些Cache基本概念。
1. Cache Line/dirty/invalidate
Cache Line:一次最少緩存多少字節的數據是有要求的,通常以Cache Line為單位。HPM6000系列MCU Cache Line為64byte,HPM5300系列MCU Cache Line為32byte。在進行PMA設置時,要求起始地址按Cache Line字節數對齊,大小為Cache Line大小的整數倍。聲明數組時最好也遵循此規則。
Dirty:表示某Cache Line的數據是否與Memory保持一致,如果只將數據寫入Cache而沒有寫入Memory,會將該Cache Line標記為dirty。
Invalidate:將某地址范圍的Cache Line數據失效掉,當Cache Line狀態被Invalidate時,不管讀取是否命中,CPU都會到Memory拿數據。
對Cache的標準操作包括 write-back,invalidate,flush。
Write-back表示把cache內dirty的數據寫入Memory,invalidate表示忽略某地址范圍的Cache line,flush操作則先對某Cache Line 進行write-back操作,再進行invalidate操作。HPM SDK的hpm_l1c_drv.h文件提供了這3種操作的接口函數。
2. Bufferable
Bufferable是指MCU在寫入一片內存區時,是否可使用Write buffer進行加速。例如向sram內寫入64個字節:
1)不使用Bufferable:CPU等待64字節數據寫入完成后再去執行其它指令;
2)使用Bufferable:CPU將64字節數據寫入Write buffer,不等Write buffer內的數據寫入sram,CPU就去執行其它指令;寫入動作則自動進行直至完成。
3. Cacheable
Cacheable與 non-Cacheable,決定了CPU是否啟用緩存特性。如果啟用Cacheable特性,則HPM芯片上的內存區域可以分區指定PMA,可選的屬性選項如下(詳細信息可參考先楫官方文檔HPM6200 UM 2.8章節):
Write-Back
Write-Back(與Write-Through互斥)是指向存儲設備內寫數據命中時,CPU將數據寫入Cache,并不立馬向存儲設備寫入數據,如下圖所示:數據先寫入到Cache內(①),在Cache內標記該Cache Line為dirty,即表示該Cache Line內容與Memory內容不符;Cache內數據寫入Memory(②),則在Cache Line被替換或手動執行write-back操作或flush操作時(把dirty的數據寫入Memory)才執行。
未命中時,則寫入Memory。是否寫入Cache 由xxx-Allocate決定。
Write-Through
Write-Through(與Write-Back互斥)是指向存儲設備內寫數據時,無論命中與否,CPU都將數據寫入Memory。
命中時,數據同時寫入Cache 與Memory;
未命中時,數據寫入Memory,是否寫入Cache 由xxx-Allocate決定。
xxx-Allocate
xxx-Allocate則用于控制讀/寫未命中Cache時,是否要在Cache內申請Cache Line用于緩存讀/寫的數據。例如:
Read-Allocate代表讀未命中時,CPU不只從Memory將數據讀入,還將數據在Cache放了一份,那么下次再讀的時候就不用去Memory讀了;
Write-Allocate代表寫未命中時,會在Cache內分配Cache Line儲存寫入的數據,那么下次讀的時候就可以從Cache讀了;具體是否寫入Memory取決于使用的是Write-Back還是Write-Through。
Non-Allocate和 Read-and-Write-Allocate就不再進行解釋了。
/* 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);
以上代碼設置了__noncacheable_start__至__noncacheable_end__地址范圍內的存儲區域PMA屬性為noncacheable,bufferable。
通過以上解釋,相信開發者可以看懂UM手冊內的相關描述了,以HPM6200系列為例,User Manual v2.0 2.8章節的內容對PMA有詳細描述。
二、HPM L1-Cache相關函數
HPM系列芯片L1-Cache分為 iCache與 dCache,指令緩存與數據緩存。開發者們經常遇到的問題是開啟dCache導致的CPU拿到的數據與Memory內數據不一致(Cache內的數據與Memory不一致時,讀取命中Cache會發生這樣的結果)。因此,此處主要介紹 dCache相關函數。
打開hpm_l1c_drv.h文件即可看到先楫提供的Cache相關的函數,部分如下:
*
* @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:關閉dCache。此函數特別有用,在debug時如果懷疑是Cache導致的問題,在main函數開始關閉dCache再次運行即可排查是否是Cache導致的問題。注意,如果是用戶程序運行過程中關閉dCache,需要在關閉前將執行l1c_dc_writeback_all,保證Cache數據寫入Memory。
l1c_dc_enable:開啟dCache。
l1c_dc_invalidate:將某地址范圍內的Cache Line失效掉。無論某地址在Cache內是否命中,CPU會從Memory內拿數據。
l1c_dc_writeback:將Cache內數據寫入某Memory地址。如果該地址在Cache內,則將該Cache Line寫入Memory,并清除dirty標志。
l1c_dc_flush:該函數等于 l1c_dc_writeback + l1c_dc_invalidate,把數據寫入到Memory并標記為invalidate,表示下次從Memory拿數據時不走Cache。
l1c_fence_i:將dCache內的數據全部writeback,將iCache內所有Cache Line invalidate。一般關閉Cache前會手動調用此函數。
l1c_dc_invalidate_all、l1c_dc_writeback_all、l1c_dc_flush_all:代表操作整個Cache中的Cache Line,具體含義不再贅述。
三、經驗分享
l1c_dc_writeback:一般非CPU的總線host,如DMA訪問某Memory地址前,通過l1c_dc_writeback將Cache內的數據寫到Memory,保證DMA拿到的數據與CPU看到的數據是一致的。
例如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數據搬到I2C的發送寄存器之前,進行了writeback。
l1c_dc_invalidate:一般CPU在讀取Memory數據時,如果該數據被其它總線host如DMA操作過(一般是DMA搬了某些數據過去),為了能讀到Memory中的數據而不是Cache中的數據,要在讀取之前對Cache Line進行invalidate處理(多數開發者遇到的都是這個問題)。
例如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();
在進行check_transfer_data之前,先對數據進行了l1c_dc_invalidate處理。
l1c_fence_i:一般在CPU關閉Cache之前,或程序跳轉之前(一般二級boot選擇好要執行的固件進行跳轉),為了保證所有dirty的Cache Line寫入到Memory中,會進行l1c_dc_writeback_all,然后等 l1c_dc_writeback_all執行完畢后再跳轉。
例如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函數在跳轉前,執行了fencei,本質上就是l1c_fence_i。
另外,在執行writeback操作期間中斷不可用,對實時性要求高的場景應進行合理規劃l1c_dc_writeback_all的使用。
四、文末小結
Cache能大幅提高程序運行性能,但用不好Cache也會給開發者帶來各種“奇奇怪怪”的問題現象。在閱讀本文后,希望開發者對先楫的 L1-Cache有更深入的理解,使用先楫半導體高性能 MCU系列產品開發項目時,能更加得心應手。
-
Cache
+關注
關注
0文章
129瀏覽量
28330 -
HPM
+關注
關注
1文章
23瀏覽量
7741 -
先楫半導體
+關注
關注
10文章
214瀏覽量
2102
發布評論請先 登錄
相關推薦
評論