|
作者:唐思超 來源:嵌入式資訊精選 隨著微處理器市場競爭加劇,RISC-V指令集越來越受到關(guān)注。雖然RISC-V并非第一個開源的指令集(ISA),卻是第一個可依據(jù)實際應(yīng)用場景靈活選擇指令集的指令集架構(gòu)。RISC-V指令集架構(gòu)可以滿足從高性能服務(wù)器CPU直至超低功耗傳感器內(nèi)嵌CPU的全部應(yīng)用場景。 通常情況下,一款處理器的啟動代碼基本采用匯編語言設(shè)計。其原因包括:
本文將解決前述問題,展示一種使用C語言為RISC-V處理器設(shè)計啟動代碼的方法。 為了更清晰地討論問題并最大程度的便于讀者理解某些流程,本文以芯來科技基于RV32IMC指令集的N205系列內(nèi)核作為目標(biāo)處理器,從N205內(nèi)核的對標(biāo)架構(gòu)——來自ARM的Cortex-M內(nèi)核在IAR EmbeddedWorkbench for ARM[1](后文簡稱IAR)環(huán)境下的C語言啟動代碼切入,逐步引入并實現(xiàn)SEGGER Embedded Studio[2](后文簡稱SES)環(huán)境下N205系列內(nèi)核的C語言啟動代碼。 一、Cortex-M內(nèi)核在IAR環(huán)境下的C語言啟動代碼 Cortex系列內(nèi)核是ARM公司迄今為止最成功的系列產(chǎn)品,包括A、R、M三類,其中M系列主要針對微控制器市場。 Cortex-M內(nèi)核具有以下特點(diǎn):
代碼段1所示內(nèi)容是Cortex-M內(nèi)核在IAR環(huán)境下使用C語言開發(fā)的啟動代碼。 【代碼段-1】 #pragma language=extended ?--snip--voidResetISR(void); ?--snip--externvoid __iar_program_start(void); ?staticunsigned long pulStack[64] @'.noinit'; ?typedefunion ?{ void (*pfnHandler)(void); unsigned long ulPtr;}uVectorEntry;__rootconst uVectorEntry __vector_table[] @'.intvec' = ?{ { .ulPtr = (unsigned long)pulStack +sizeof(pulStack) }, ? ResetISR, --snip--};--snip--voidResetISR(void){ __iar_program_start();}此處對上述代碼做簡要分析: ?是IAR的#pragma指導(dǎo)符。 ?是復(fù)位函數(shù)聲明,復(fù)位函數(shù)是處理器復(fù)位后首先執(zhí)行的代碼,有時也稱為復(fù)位入口函數(shù)。 ?是IAR系統(tǒng)函數(shù)聲明,__iar_program_start是IAR的系統(tǒng)函數(shù),主要作用是執(zhí)行C運(yùn)行環(huán)境初始化并調(diào)用系統(tǒng)主函數(shù)main。 ?使用IAR @操作符定義系統(tǒng)棧區(qū)。 ?聲明向量表的聯(lián)合類型。 ?使用IAR對象屬性聲明__root及@操作符定義向量表,其中,第一個元素?保存了棧底地址,后續(xù)元素均為函數(shù)地址。 從上述分析過程可以看出啟動代碼的必要工作包括定義棧區(qū)、定義并初始化向量表,定義并實現(xiàn)系統(tǒng)復(fù)位函數(shù),初始化棧指針或棧寄存器等。依據(jù)處理器的架構(gòu)不同,上述操作中某些過程需要由軟件完成,有些則由硬件自動加載。 另外,有關(guān)IAR的指導(dǎo)符、對象屬屬性等內(nèi)容不屬于本文討論范疇,有需要可自行查閱。這里給出兩點(diǎn)提示:IAR環(huán)境的編譯系統(tǒng)為IAR自行開發(fā),故示例代碼中的指導(dǎo)符號不適用于GCC;某些指導(dǎo)符會因IAR環(huán)境版本不同而有所差異。 二、在SES環(huán)境下實現(xiàn)RISC-V內(nèi)核C語言啟動代碼的必要知識 前文提到,RISC-V是指令集而不是具體的設(shè)計實現(xiàn),這與之前討論的Cortex-M系列內(nèi)核有很大不同。簡單地說,不同廠商基于同種Cortex-M內(nèi)核的處理器,僅從內(nèi)核的層面來看可能沒有太大差異,但不同廠商開發(fā)的具有相同指令集的RISC-V處理器則各有千秋:一方面是相同功能的具體實現(xiàn)可能不同;另一方面,不同廠商可以實現(xiàn)不同的指令擴(kuò)展。 這里對比Cortex-M內(nèi)核,列舉RISC-V處理器的一些特點(diǎn):不同廠商中斷控制器的實現(xiàn)各有特色;中斷響應(yīng)時,處理器硬件不會保存上下文,需要軟件完成該功能;向量表依據(jù)廠商不同而有明顯差異,可能向量表的首地址保存的是指令而非地址。 在不同廠商的Cortex-M內(nèi)核處理器間作切換時,由于處理器內(nèi)核的一致性,啟動代碼幾乎無需改動,因而使用匯編或者C語言來設(shè)計啟動代碼似乎差異不大,但要降低在不同廠商的RISC-V處理器間切換的復(fù)雜度,使用C語言開發(fā)啟動代碼是一種有效途徑。 前文曾提到啟動代碼的必要工作包括定義棧區(qū)、定義并初始化向量表,定義并實現(xiàn)系統(tǒng)復(fù)位函數(shù),初始化棧指針或棧寄存器等。在前述Cortex-M內(nèi)核的C啟動代碼中,IAR提供了接口__iar_program_start,該接口隱藏了幾乎所有細(xì)節(jié)。在SES環(huán)境下并沒有這樣的接口可供使用,為了實現(xiàn)RISC-V處理器的C語言啟動代碼,需要如下的編譯器及鏈接器相關(guān)知識。 (1)GCC內(nèi)聯(lián)匯編 RISC-V處理器中的CSR寄存器需要特殊的指令才能進(jìn)行訪問,C編譯器無法產(chǎn)生類似的指令,故C語言啟動代碼中仍然需要插入數(shù)條匯編指令。為了實現(xiàn)匯編指令與C語言的交互,需要使用GCC內(nèi)聯(lián)匯編,實例介紹如下:
其中:? asm為GCC內(nèi)聯(lián)匯編關(guān)鍵字,volatile為修飾符;? 雙引號引用的匯編指令列表,如有多條指令,可以使用'\n'分割;其中%0代表輸入操作數(shù)列表中的第一個值;? 可選的輸出操作數(shù)列表;? 可選的輸入操作數(shù)列表,此處'r'代表使用編譯器自動分配的寄存器來存儲變量vector_base;? 可選的受影響寄存器列表。 (2)section與初始化 簡單來講,將目標(biāo)文件中的sections鏈接起來就是可執(zhí)行文件。在默認(rèn)情況下,編譯器會創(chuàng)建標(biāo)準(zhǔn)sections。表1是標(biāo)準(zhǔn)section的簡單介紹。 表1 標(biāo)準(zhǔn)section概要 通過表1可以看出,程序的可執(zhí)行代碼存放于.text section,已初始化的全局和靜態(tài)變量存放于.data section。 一個典型的SoC系統(tǒng)通常包含兩類存儲器,即ROM和RAM。對于當(dāng)今的處理器來說,這兩部分通常是Flash和SRAM。系統(tǒng)掉電情況下,SRAM中是無法保存數(shù)據(jù)的,因此C語言中的變量初始值需要保存于Flash中。系統(tǒng)上電后,由初始化代碼將初始化數(shù)據(jù)從Flash拷貝到SRAM的目標(biāo)地址。如前所述,這是初始化代碼的重要工作之一。 接下來將闡述如何從Flash中找到初始化數(shù)據(jù)的位置并在C語言中引用。 (3)鏈接器變量的C語言訪問 從鏈接器的觀點(diǎn)看,初始值在Flash中的存放地址稱為LMA(加載存儲地址),對應(yīng)變量在SRAM的運(yùn)行時地址稱為VMA(虛擬存儲地址)。鏈接器腳本是用來描述處理器存儲器分布、各section 及標(biāo)準(zhǔn)section的包含關(guān)系、相應(yīng)LMA及VMA地址或存放區(qū)域等的文件。 代碼段2是一個標(biāo)準(zhǔn)鏈接器腳本的片段。這里通過這個片段來講述鏈接器變量的C語言訪問。 【代碼段-2】 MEMORY{ --snip--}SECTIONS{ --snip-- __data_load_start__ = ALIGN(__srodata_end__ ,4); .data ALIGN(__RAM_segment_start__ , 4) :AT(ALIGN(__srodata_end__ , 4)) { __data_start__ = .; *(.data .data.*) } __data_end__ = __data_start__ +SIZEOF(.data); __data_size__ = SIZEOF(.data); __data_load_end__ = __data_load_start__ +SIZEOF(.data); --snip--}在代碼段2中,定義了鏈接器腳本變量__data_load_start__、__data_start__及__data_end__。 其中:
在C語言中訪問這些變量有以下兩種方法: 將鏈接器腳本變量聲明為數(shù)據(jù)類型,例如在C語言文件中聲明extern uint32_t __data_load_start__;通過&__data_load_start__獲取變量的值; 將鏈接器腳本變量聲明為數(shù)組,例如在C語言文件中聲明externuint32_t __data_load_start__[];通過__data_load_start__獲取變量的值。 (4)函數(shù)屬性 在通常情況下,編譯器會為每個函數(shù)自動產(chǎn)生序言和結(jié)尾序列,即在函數(shù)的頭部進(jìn)行一些入棧操作,在函數(shù)的末尾進(jìn)行對應(yīng)的出棧操作。一個明顯的問題就是在C語言啟動代碼中,復(fù)位函數(shù)執(zhí)行時可能棧指針或棧寄存器還沒有進(jìn)行初始化,這時的棧操作極有可能會導(dǎo)致處理器訪問非法地址而使程序崩潰。此外,如前文所提到的RISC-V處理器的復(fù)位入口可能保存的是跳轉(zhuǎn)指令而不是地址,短的跳轉(zhuǎn)地址可以保證用一條指令完成跳轉(zhuǎn)。 鑒于上述原因,需要使用相關(guān)的函數(shù)屬性來通知編譯器剔除默認(rèn)的函數(shù)序列并指定section,如下形式的復(fù)位函數(shù)定義可滿足該要求:
三、RISC-V內(nèi)核的C語言啟動代碼實例 前面內(nèi)容介紹了相關(guān)背景知識和技術(shù)手段,下面通過一個實際的框架程序來展示RISC-V處理器的C語言啟動代碼。其中,代碼段3是C語言啟動代碼的實現(xiàn),代碼段4是向量表。代碼中的所有關(guān)鍵點(diǎn)前文均有介紹,在此不在贅述。 【代碼段-3】 #include'riscv_encoding.h' #include<stdint.h>--snip--externuint32_t __data_load_start__; --snip--externuint32_t __bss_start__;--snip--externvoid (*const vector_base[])(void);externvoid main(void);--snip--conststruct { uint32_t* load; uint32_t* start; uint32_t* end;}dsection[3] = { --snip--};conststruct { uint32_t* start; uint32_t* end;}bsection[3] = { --snip--};void __attribute__((section('.init'),naked)) reset_handler() { register uint32_t *src, *dst; --snip-- /* 嵌入?yún)R編 */ asm volatile('csrw 0x307,%0'::'r'(vector_base));--snip-- asm volatile('la gp, __sdata_start__+0x800'); asm volatile('la sp,__stack_end__');--snip-- /* 進(jìn)行系統(tǒng)時鐘初始化等 */ init(); /* 將數(shù)據(jù)的初始化值拷貝至RAM */ if(&__vectors_load_start__ !=&__RAM_segment_start__){ for(uint8_t idx = 0; idx < 3; idx++){ src=dsection[idx].load; dst=dsection[idx].start; while(dst < dsection[idx].end){ *dst=*src; dst++; src++; } } } /* 將.bss區(qū)域清零 */ for(uint8_t idx=0;idx < 3;idx++){ dst=bsection[idx].start; while(dst<bsection[idx].end){ *dst=0U; dst++; } } /* 調(diào)用主函數(shù) */ main(); }--snip--【代碼段-4】
四、結(jié) 語 #defineCSR_MTVT 0x307#defineSTR(R) #R#defineXSTR(R) STR(R) /*asm volatile('csrw 0x307, %0'::'r'(vector_base)); */asmvolatile('csrw 'XSTR(CSR_MTVT)',%0'::'r'(vector_base));作者簡介: 唐思超,現(xiàn)任北京知存科技有限公司軟件開發(fā)經(jīng)理,負(fù)責(zé)人工智能芯片工具鏈及嵌入式開發(fā),具有14年硬件電路設(shè)計及軟件開發(fā)經(jīng)驗,擅長處理器、編譯系統(tǒng)及操作系統(tǒng)的相關(guān)設(shè)計開發(fā)及底層機(jī)制的綜合運(yùn)用。 |
|
|
來自: 西北望msm66g9f > 《編程》