• 正文
  • 相關(guān)推薦
申請(qǐng)入駐 產(chǎn)業(yè)圖譜

從XIP切到TCM執(zhí)行,還能再提升Cortex-M7性能嗎?

2020/06/11
242
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點(diǎn)資訊討論

大家好,我是痞子衡,是正經(jīng)搞技術(shù)的痞子。今天痞子衡給大家分享的是 i.MXRT 上進(jìn)一步提升代碼執(zhí)行性能的經(jīng)驗(yàn)。

今天跟大家聊的這個(gè)話題還是跟痞子衡最近這段時(shí)間參與的一個(gè)基于 i.MXRT1170 的大項(xiàng)目有關(guān),痞子衡在做其中的開(kāi)機(jī)動(dòng)畫(huà)功能,之前寫(xiě)過(guò)一篇文章 《降低刷新率是定位 LCD 花屏顯示問(wèn)題的第一大法》 介紹了開(kāi)機(jī)動(dòng)畫(huà)功能的實(shí)現(xiàn)以及 LCD 顯示注意事項(xiàng),在此功能上,痞子衡想進(jìn)一步測(cè)試從芯片上電到 LCD 屏顯示第一幅完整圖像的時(shí)間,這個(gè)時(shí)間我們暫且稱為 1st UI 時(shí)間,該時(shí)間的長(zhǎng)短對(duì)項(xiàng)目有重要意義。

痞子衡分別測(cè)試了代碼在 XIP 執(zhí)行下和在 TCM 里執(zhí)行下的 1st UI 時(shí)間,得到的結(jié)果竟然是 XIP 執(zhí)行比 TCM 執(zhí)行還要快 50ms,這是怎么回事?這完全顛覆了我們的理解,i.MXRT 上 TCM 是與內(nèi)核同頻的,F(xiàn)lash 速度遠(yuǎn)低于 TCM。如果是 XIP 執(zhí)行,即使有 I-Cache 加速,也最多與 TCM 執(zhí)行一樣快,怎么可能做到比 TCM 執(zhí)行快這么多。于是痞子衡便開(kāi)始深挖這個(gè)奇怪的現(xiàn)象,然后發(fā)現(xiàn)了進(jìn)一步提升代碼執(zhí)行性能的秘密。

一、引出計(jì)時(shí)差異問(wèn)題

痞子衡的開(kāi)機(jī)動(dòng)畫(huà)程序是基于 SDK_2.x.x_MIMXRT1170-EVKboardsevkmimxrt1170jpeg_examplessd_jpeg 例程的,只是去了 SD 卡和 libjpeg 庫(kù)相關(guān)代碼。工程有兩個(gè) build,一個(gè)是 TCM 里執(zhí)行(即 debug),另一個(gè)是 XIP 執(zhí)行(即 flexspi_nor_debug)。

項(xiàng)目板上的 Flash 型號(hào)是 MX25UW51345G,痞子衡將其配成 Octal mode, DDR, 166MHz 用于啟動(dòng)。項(xiàng)目板上還有兩個(gè) LED 燈,痞子衡在 LED 燈上飛了兩根線,連同 POR 引腳一起連上示波器,用于精確測(cè)量 1st UI 各部分時(shí)間組成。

?

示波器通道 1 連接 POR 引腳,表明 1st UI 時(shí)間起點(diǎn);通道 2 連接 LED1 GPIO,表明 ROM 啟動(dòng)時(shí)間(進(jìn)入用戶 APP 的時(shí)間點(diǎn));通道 3 連接 LED2 GPIO,做兩次電平變化,分別是 1st 圖像幀開(kāi)始和結(jié)束的時(shí)間點(diǎn)。翻轉(zhuǎn) LED GPIO 代碼位置如下:

void?light_led(uint32_t?ledIdx,?uint8_t?ledVal);

void?SystemInit?(void)?{
????//?將 LED1 置 1,標(biāo)示 ROM 啟動(dòng)時(shí)間
????light_led(1,?1);

????SCB->CPACR?|=?((3UL?<<?10*2)?|?(3UL?<<?11*2));

????//?...
}

void?APP_InitDisplay(void)
{
????//?...

????g_dc.ops->enableLayer(&g_dc,?0);

????//?將 LED2 置 1,標(biāo)示 1st 圖像幀開(kāi)始時(shí)間點(diǎn)
????light_led(2,?1);
}

int?main(void)
{
????BOARD_ConfigMPU();
????BOARD_InitBootPins();
????BOARD_BootClockRUN();
????BOARD_ResetDisplayMix();

????APP_InitDisplay();

????while?(1)
?{
?????//?...
?}
}

static?void?APP_BufferSwitchOffCallback(void?*param,?void?*switchOffBuffer)
{
????s_newFrameShown?=?true;

????//?將 LED2 置 0,標(biāo)示 1st 圖像幀結(jié)束時(shí)間點(diǎn)
????light_led(2,?0);
}

?

上圖是痞子衡抓到的波形(30Hz,XIP),痞子衡一共做了四次測(cè)試,分別是 30Hz LCD 刷新率下的 XIP/TCM 以及 60Hz LCD 刷新率下的 XIP/TCM,結(jié)果如下表所示。表中的 Init Time 一欄表示的是開(kāi)機(jī)動(dòng)畫(huà)程序代碼執(zhí)行時(shí)間(從 SystemInit()函數(shù)開(kāi)始執(zhí)行到 APP_InitDisplay()函數(shù)結(jié)束的時(shí)間),可以看到 TCM 執(zhí)行比 XIP 執(zhí)行慢近 50ms,這便是奇怪問(wèn)題所在。

代碼位置 LCD 刷新率 POR Time Boot Time Init Time Launch Time
XIP 30Hz 3.414ms 10.082ms 34.167ms + 153ms 32.358ms
TCM 30Hz 3.414ms 10.854ms 33.852ms + 203ms 32.384ms
XIP 60Hz 3.414ms 9.972ms 18.142ms + 153ms 16.166ms
TCM 60Hz 3.414ms 10.92ms 17.92ms + 203ms 16.104ms

二、定位計(jì)時(shí)差異問(wèn)題

對(duì)于開(kāi)機(jī)動(dòng)畫(huà)代碼,XIP 執(zhí)行比 TCM 執(zhí)行快這個(gè)結(jié)果,痞子衡是不相信的,于是痞子衡便用二分法逐步查找,發(fā)現(xiàn)時(shí)間差異是 BOARD_InitLcdPanel()函數(shù)里的 DelayMs()調(diào)用引起的,這些人為插入的延時(shí)是 LCD 屏控制器手冊(cè)里的要求,總延時(shí)時(shí)間應(yīng)該是 153ms,但是這個(gè)函數(shù)的執(zhí)行在 XIP 下(153ms)和 TCM 里(203ms)時(shí)間不同。

static?void?BOARD_InitLcdPanel(void)
{
????//?...

#if?(DEMO_PANEL?==??DEMO_PANEL_TM103XDKP13)
????//?...

????/*?Power?LCD?on?*/????
????GPIO_PinWrite(LCD_RESET_GPIO,?LCD_RESET_GPIO_PIN,?1);
????DelayMs(2);
????GPIO_PinWrite(LCD_RESET_GPIO,?LCD_RESET_GPIO_PIN,?0);
????DelayMs(5);
????GPIO_PinWrite(LCD_RESET_GPIO,?LCD_RESET_GPIO_PIN,?1);
????DelayMs(6);
????GPIO_PinWrite(LCD_STBYB_GPIO,?LCD_STBYB_GPIO_PIN,?1);
????DelayMs(140);
#endif
????//?...
}

所以現(xiàn)在的問(wèn)題就是為何在 TCM 里執(zhí)行 DelayMs(153)需要 203ms,而 XIP 執(zhí)行下是精確的。讓我們進(jìn)一步查看 DelayMs()函數(shù)的原型,這個(gè)函數(shù)其實(shí)調(diào)用的是 SDK_DelayAtLeastUs()函數(shù),SDK_DelayAtLeastUs()函數(shù)從命名上看就很有意思,AtLeast 即保證軟延時(shí)一定能滿足用戶設(shè)置的時(shí)間,但也可能超過(guò)這個(gè)時(shí)間。為何是 AtLeast 設(shè)計(jì),其實(shí)這里就涉及到 Cortex-M7 內(nèi)核一個(gè)很重要的特性 - 指令雙發(fā)射,軟件延時(shí)的本質(zhì)是靠 CPU 執(zhí)行指令來(lái)消耗時(shí)間,但是 CPU 拿指令到底是單發(fā)射還是雙發(fā)射有一定的不確定性,因此無(wú)法做到精確,如果以全雙發(fā)射來(lái)計(jì)算,就能得出最小延時(shí)時(shí)間。

#define?DelayMs??????????????????VIDEO_DelayMs

#if?defined(__ICCARM__)
static?void?DelayLoop(uint32_t?count)
{
????__ASM?volatile("????MOV????R0,?%0"?:?:?"r"(count));
????__ASM?volatile(
????????"loop:??????????????????????????n"
????????"????SUBS???R0,?R0,?#1??????????n"
????????"????CMP????R0,?#0??????????????n"
????????"????BNE????loop????????????????n");
}
#endif

void?SDK_DelayAtLeastUs(uint32_t?delay_us,?uint32_t?coreClock_Hz)
{
????assert(0U?!=?delay_us);
????uint64_t?count?=?USEC_TO_COUNT(delay_us,?coreClock_Hz);
????assert(count?<=?UINT32_MAX);

#if?(__CORTEX_M?==?7)
????count?=?count?/?3U?*?2U;
#else
????count?=?count?/?4;
#endif
????DelayLoop(count);
}

void?VIDEO_DelayMs(uint32_t?ms)
{
????SDK_DelayAtLeastUs(ms?*?1000U,?SystemCoreClock);
}

分析到現(xiàn)在,問(wèn)題已經(jīng)轉(zhuǎn)化成為何 XIP 下執(zhí)行指令雙發(fā)射概率比 TCM 里執(zhí)行指令雙發(fā)射概率更大,關(guān)于這個(gè)現(xiàn)象并沒(méi)有在 ARM 官方文檔里查找到相關(guān)信息,DelayLoop()循環(huán)里只是 3 條指令,XIP 下執(zhí)行肯定是在 Cache line 里,這跟在 TCM 里執(zhí)行并沒(méi)有什么區(qū)別。讓我們?cè)偃タ纯磧蓚€(gè)工程的 map 文件,找到 DelayLoop()函數(shù)鏈接地址,這個(gè)函數(shù)在兩個(gè)測(cè)試工程下鏈接地址對(duì)齊不一樣,這意味著測(cè)試條件不完全相同,或許這是一個(gè)解決問(wèn)題的線索。

XIP 執(zhí)行工程(flexspi_nor_debug),DelayLoop()函數(shù)地址 8 字節(jié)對(duì)齊:

*******************************************************************************
*** ENTRY LIST
***

Entry                       Address   Size  Type      Object
-----                       -------   ----  ----      ------
DelayLoop               0x3000'3169    0xa  Code  Lc  fsl_common.o [1]

TCM 執(zhí)行工程(debug 工程),DelayLoop()函數(shù)地址 4 字節(jié)對(duì)齊:

*******************************************************************************
*** ENTRY LIST
***

Entry                       Address   Size  Type      Object
-----                       -------   ----  ----      ------
DelayLoop                    0x314d    0xa  Code  Lc  fsl_common.o [1]

三、找到計(jì)時(shí)差異本質(zhì)

前面找到 DelayLoop()函數(shù)鏈接地址差異是一個(gè)線索,那我們就針對(duì)這個(gè)線索做測(cè)試,不再讓鏈接器自動(dòng)分配 DelayLoop()函數(shù)地址,改為在鏈接文件里指定地址去鏈接,下面代碼是 IAR 環(huán)境下的示例,我們使用 debug 工程(即在 TCM 執(zhí)行)來(lái)做測(cè)試。

C 源文件中在 DelayLoop()函數(shù)定義前加#pragma location = ".myFunc",即將該函數(shù)定義為 .myFunc 的段,然后在鏈接文件 icf 中用 place at 語(yǔ)句指定 .myFunc 段到固定地址 m_text_func_start 處開(kāi)始鏈接:

#if?defined(__ICCARM__)
#pragma?location?=?".myFunc"
static?void?DelayLoop(uint32_t?count)
{
????//?...
}
#endif
define symbol m_text_func_start        = 0x00004000;

place at address mem: m_text_func_start     { readonly section .myFunc };

define symbol m_text_start             = 0x00002400;
define symbol m_text_end               = 0x0003FFFF;

place in TEXT_region                        { readonly };

根據(jù)鏈接起始地址 m_text_func_start 的不同,我們得到了不同的結(jié)果,如下表所示。至此真相大白,造成 DelayMs()函數(shù)執(zhí)行時(shí)間不同的根本原因不是 XIP/TCM 執(zhí)行差異,而是鏈接地址對(duì)齊差異,8 字節(jié)對(duì)齊的函數(shù)更容易觸發(fā) CM7 指令雙發(fā)射,相比 4 字節(jié)對(duì)齊的函數(shù)在性能上能提升 24.8% 。

m_text_func_start 值 鏈接地址對(duì)齊 函數(shù)調(diào)用語(yǔ)句 實(shí)際執(zhí)行時(shí)間
0x00004000 8n 字節(jié) DelayMs(100) 100ms
0x00004002 2 字節(jié),未能鏈接 N/A N/A
0x00004004 4 字節(jié) DelayMs(100) 133ms
0x00004008 8 字節(jié) DelayMs(100) 100ms

現(xiàn)在我們得到了一個(gè)有趣的結(jié)論,Cortex-M7 上將函數(shù)鏈接到 8 字節(jié)對(duì)齊的地址有利于指令雙發(fā)射,這就是進(jìn)一步提升代碼執(zhí)行性能的秘密。

至此,i.MXRT 上進(jìn)一步提升代碼執(zhí)行性能的經(jīng)驗(yàn)痞子衡便介紹完畢了,掌聲在哪里~~~

相關(guān)推薦

登錄即可解鎖
  • 海量技術(shù)文章
  • 設(shè)計(jì)資源下載
  • 產(chǎn)業(yè)鏈客戶資源
  • 寫(xiě)文章/發(fā)需求
立即登錄

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