引言
目前,靈動(dòng)微控制器產(chǎn)品體系中,適配了MicroPython的,有MM32F3(MM32F3273G9P,Arm Cortex-M3)和MM32F5(MM32F5277E9P,ArmChina STAR-MC1),從官方數(shù)據(jù)來(lái)看,使用星辰處理器(STAR-MC1)的MM32F5對(duì)指令的處理效率要高于使用Cortex-M3處理器的MM32F3。如圖x所示。
圖x 星辰處理器同其他Arm處理器內(nèi)核對(duì)比
然而,同一份MicroPython的啟動(dòng)過(guò)程,在使用同樣主頻(120MHz)的情況下,在MM32F5平臺(tái)上運(yùn)行,總是莫名其妙地慢好多。。。昨天跟同事Hao聊SDK的樣例工程對(duì)Cache問(wèn)題的處理策略時(shí),偶然意識(shí)到,早期為MM32F5適配MicroPython的時(shí)候沒(méi)有考慮過(guò)Cache,隨即趕緊翻出來(lái)MicroPython的代碼,果然沒(méi)開(kāi)。好吧,更新MicroPython項(xiàng)目下MM32F5平臺(tái)的啟動(dòng)代碼,替換來(lái)自于SDK中的system_mm32f5277e.c
和system_mm32f5277e.h
文件。
/*
* Copyright 2022 MindMotion Microelectronics Co., Ltd.
* All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "hal_device_registers.h"
#if defined (__VTOR_PRESENT) && (__VTOR_PRESENT == 1U)
extern uint32_t __VECTOR_TABLE;
#endif
void SystemInit(void)
{
#if defined (__FPU_PRESENT ) && (__FPU_PRESENT == 1U)
#if defined(__FPU_USED) && (__FPU_USED == 1u)
SCB- >CPACR |= (SCB_CPACR_CP10_MASK | SCB_CPACR_CP11_MASK); /* set CP10, CP11 Full Access */
#endif
#endif /* __FPU_PRESENT */
#if defined (__ICACHE_PRESENT )&& (__ICACHE_PRESENT == 1U)
#ifndef ICACHE_DISABLED
if (SCB- >CLIDR & SCB_CLIDR_IC_Msk)
{
SCB_EnableICache();
}
#endif /* DCACHE_DISABLED */
#endif /* __ICACHE_PRESENT */
#if defined (__DCACHE_PRESENT) && (__DCACHE_PRESENT == 1U)
#ifndef DCACHE_DISABLED
if (SCB- >CLIDR & SCB_CLIDR_IC_Msk)
{
SCB_EnableDCache();
}
#endif /* DCACHE_DISABLED */
#endif /* __DCACHE_PRESENT */
}
/* EOF. */
其中,如果沒(méi)有調(diào)用SCB_EnableICache()
和SCB_EnableDCache()
函數(shù),默認(rèn)關(guān)閉ICache和DCache,配置宏__ICACHE_PRESENT
和__DCACHE_PRESENT
來(lái)自于MM32F5270的芯片頭文件mm32f5277e.h
。
#define __STAR_REV 0x0100U /* Core revision r1p0 */
#define __SAUREGION_PRESENT 0U /* SAU regions present */
#define __MPU_PRESENT 1U /* MPU present */
#define __VTOR_PRESENT 1U /* VTOR present */
#define __NVIC_PRIO_BITS 3U /* Number of Bits used for Priority Levels */
#define __Vendor_SysTickConfig 0U /* Set to 1 if different SysTick Config is used */
#define __FPU_PRESENT 1U /* FPU present */
#define __DSP_PRESENT 1U /* DSP extension present */
#define __ICACHE_PRESENT 1U /* Define if an ICACHE is present or not */
#define __DCACHE_PRESENT 1U /* Define if an DCACHE is present or not */
編譯,驗(yàn)證。找來(lái)兩塊之前下載了MicroPython固件的MM32F5開(kāi)發(fā)板,向其中一塊板子下載啟用ICache和DCache之后的新固件。運(yùn)行程序后,同使用之前版本固件的程序相比,果然有明顯的提升。
圖中板子運(yùn)行程序啟動(dòng)MicroPython啟動(dòng)流程后,執(zhí)行文件系統(tǒng)中Python源文件,閃爍小燈。圖中下方的板子使用了啟用Cache的程序,明顯先完成MicroPython啟動(dòng)過(guò)程,先開(kāi)始執(zhí)行閃爍小燈的程序。
提交更新到代碼倉(cāng)庫(kù)。Bingo!
commit 46372ed15d5769b25775a209c56d62c4cfc3ac5d (HEAD - > master, origin/master, origin/HEAD)
Author: Andrew SU < suyong_yq@126.com >
Date: Wed Jun 14 11:02:06 2023 +0800
update the startup code to enable the icache for mm32f5.
- this fix would accelerate the speed of running the instruction
sequence on mm32f5, which has the icache and dcache integrated.
Signed-off-by: Andrew SU < suyong_yq@126.com >
啟用Cache后,之前在MM32F5微控制器平臺(tái)上運(yùn)行MicroPython小概率會(huì)出現(xiàn)hardfault的問(wèn)題也得到的緩解,頗有“治好了某人多年老寒腿”的趕腳。^v^。
Cache的工作原理
Cache主要解決高速的CPU訪問(wèn)低速的存儲(chǔ)器均衡速度差的問(wèn)題,Cache通過(guò)預(yù)取數(shù)據(jù)/命令的機(jī)制,低速但整塊地從低速存儲(chǔ)器中取數(shù)據(jù)塊,然后快速但串行地向CPU送數(shù)據(jù)流。高速的處理器大多使用哈佛結(jié)構(gòu),即使用指令總線和數(shù)據(jù)總線分別取指令和數(shù)據(jù),對(duì)應(yīng)有ICache和DCache。
Cache能夠有效工作基于幾個(gè)基本前提:
- 空間局部性:在最近的未來(lái),使用到的信息和當(dāng)前使用的信息在空間上會(huì)是鄰近的。這個(gè)因?yàn)閿?shù)據(jù)大部分都是連續(xù)存儲(chǔ)的。所以主存當(dāng)中的數(shù)據(jù)都是成塊傳輸?shù)紺ache當(dāng)中。
- 時(shí)間局部性:在最近的未來(lái),使用到的信息可能是當(dāng)前正在使用的信息。由于CPU本質(zhì)上一個(gè)死循環(huán),里面還有很多小循環(huán)的運(yùn)行和操作,所以當(dāng)前使用到的數(shù)據(jù)很可能會(huì)在循環(huán)當(dāng)中,這樣當(dāng)前的數(shù)據(jù)有可能會(huì)在將來(lái)在再被調(diào)用一次。
另外,關(guān)于Cache還有其余部分需要了解:
- Cache與主存的映射方式:解決主存內(nèi)數(shù)據(jù)塊和Cache當(dāng)中數(shù)據(jù)塊的對(duì)應(yīng)關(guān)系。
- 替換算法:Cache小,主存大。如果Cache中數(shù)據(jù)存滿了之后,如何操作。
- Cache寫策略:如果CPU修改了Cache中的副本,如何確保Cache中的數(shù)據(jù)和主存中的母本數(shù)據(jù)保持一致。
關(guān)于Cache的工作原理,以及設(shè)計(jì)機(jī)制,可參考計(jì)算機(jī)專業(yè)考研四大專業(yè)課之《計(jì)算機(jī)體系結(jié)構(gòu)》,以及參考文獻(xiàn)中的《一文搞懂Cache基本原理》。
需要關(guān)閉DCache的情況
在微控制器系統(tǒng)中,有時(shí)會(huì)需要直接使用內(nèi)存里的數(shù)據(jù)同外設(shè)交互,而不進(jìn)入CPU,例如使用DMA(另一個(gè)總線主機(jī)AHB Master,但不需要Cache功能)相關(guān),這種場(chǎng)景下,使用Cache的意義就不大了,甚至可能會(huì)出現(xiàn)數(shù)據(jù)不一致的風(fēng)險(xiǎn),此時(shí),就需要關(guān)閉Cache才能讓系統(tǒng)正常工作。具體來(lái)說(shuō),ICache可以繼續(xù)啟用,畢竟指令都是送到CPU中執(zhí)行,但DCache需要關(guān)掉,否則通過(guò)CPU寫入到內(nèi)存的數(shù)據(jù)未能及時(shí)同步物理內(nèi)存時(shí)(基于Cache的寫策略),啟動(dòng)DMA時(shí)搬運(yùn)的數(shù)據(jù)不一定是實(shí)際需要傳送的數(shù)據(jù)。另外,所有使用到“內(nèi)嵌”DMA的外設(shè)模塊的工程中,也需要小心謹(jǐn)慎地使用DCache,例如一些USB外設(shè)、ENET外設(shè)、顯示加速器、以數(shù)據(jù)塊為操作單元的加速計(jì)算模塊等。
關(guān)ICache的情況雖然不多,但也存在,例如在涉及IAP應(yīng)用中,從存放指令的介質(zhì)中擦除指令、寫入新指令后,再讀指令,實(shí)際的新指令可能尚未替換到ICache中存放的舊指令,導(dǎo)致程序執(zhí)行錯(cuò)誤。
魚(yú)和熊掌都想要
關(guān)閉DCache之后,CPU讀數(shù)據(jù)的速度會(huì)明顯慢很多,例如本文一開(kāi)始展現(xiàn)的情況。怎樣才能提升訪問(wèn)訪問(wèn)速度的同時(shí),又能確保數(shù)據(jù)一致性呢?總不會(huì)人為頻繁地開(kāi)關(guān)Cache吧(很多微控制器對(duì)啟動(dòng)Cache的時(shí)機(jī)也有特別要求,需要在運(yùn)行應(yīng)用程序的一開(kāi)始就要開(kāi)啟)。這里有兩種可能的思路,供大家參考:
- 使用內(nèi)存保護(hù)單元MPU
- 使用內(nèi)存隔離/同步指令
這兩種方法,分別是在空間上和時(shí)間上對(duì)數(shù)據(jù)進(jìn)行隔離,控制僅在必要的空間上或時(shí)間上啟用和關(guān)閉Cache。
使用內(nèi)存保護(hù)單元MPU
MPU(Memory Protection Unit)內(nèi)存保護(hù)單元在ARMv7-M架構(gòu)下被引入。在 ARMv7-M架構(gòu)下,Cortex-M3和Cortex-M4處理器對(duì) MPU 都是選配的,不是必須的。ARMv8-M架構(gòu)下繼續(xù)沿用了MPU,星辰處理器STAR-MC1就使用了ARMv8-M。
MPU是一個(gè)可以編程的設(shè)備模塊,可用來(lái)定義內(nèi)存空間的屬性,比如特權(quán)指令和非特權(quán)指令,以及Cache是否可訪問(wèn)。ARMv7-M通常支持8個(gè)region,每個(gè)region 代表一段連續(xù)的區(qū)域。
關(guān)于MPU的用法,可參見(jiàn)參考文獻(xiàn)中的《ARM-MPU內(nèi)存保護(hù)單元詳解》和《Armv8-M Architecture Reference Manual》。
使用內(nèi)存隔離/同步指令
ARM的指令集中,有內(nèi)存隔離指令DMB(Data Memory Barrier)、DSB(Data Synchronization Barrier)和ISB(Instruction Synchronization Barrier):
- 數(shù)據(jù)存儲(chǔ)器隔離。DMB 指令保證: 僅當(dāng)所有在它前面的存儲(chǔ)器訪問(wèn)操作都執(zhí)行完畢后,才提交(commit)在它后面的存儲(chǔ)器訪問(wèn)操作。
- 數(shù)據(jù)同步隔離。比 DMB 嚴(yán)格: 僅當(dāng)所有在它前面的存儲(chǔ)器訪問(wèn)操作都執(zhí)行完畢后,才執(zhí)行在它后面的指令(亦即任何指令都要等待存儲(chǔ)器訪問(wèn)操作——譯者注)。
- 指令同步隔離。最嚴(yán)格:它會(huì)清洗流水線,以保證所有它前面的指令都執(zhí)行完畢之后,才執(zhí)行它后面的指令。
在一些ARM程序代碼中,會(huì)用到__DSB() 指令,特別是在一些中斷處理函數(shù)中。例如:
//中斷定時(shí)器PIT中斷處理函數(shù)
void PIT_LED_HANDLER(void)
{
/* Clear interrupt flag.*/
PIT_ClearStatusFlags(PIT, kPIT_Chnl_0, kPIT_TimerFlag);
pitIsrFlag = true;
__DSB();
}
程序通過(guò)中斷信號(hào)進(jìn)入中斷處理函數(shù)時(shí),首先應(yīng)當(dāng)清除相應(yīng)的中斷標(biāo)志位,但有些CPU的時(shí)鐘太快,快于中斷使用的時(shí)鐘,就會(huì)出現(xiàn)清除中斷標(biāo)志的動(dòng)作還未完成,CPU就又一次重新進(jìn)入同一個(gè)中斷處理函數(shù),導(dǎo)致死循環(huán),__DSB() 指令的作用就是避免上述情況的發(fā)生。
總結(jié)
本文從修復(fù)MicroPython啟動(dòng)程序在MM32F5微控制器上比較慢的問(wèn)題,體驗(yàn)了星辰處理器中Cache的作用。簡(jiǎn)單介紹了Cache的工作原理和機(jī)制,重點(diǎn)介紹了使用Cache可能存在的風(fēng)險(xiǎn),并進(jìn)一步探討了如何能用到Cache高速存取的同時(shí)避免數(shù)據(jù)不一致的情況。
評(píng)論