不少同學(xué)很好奇在SOC環(huán)境里面C代碼是怎么執(zhí)行的?
是通過DPI實(shí)現(xiàn)SV和C的交互,然后用 SV的task將C的數(shù)據(jù)轉(zhuǎn)成對(duì)應(yīng)的總線數(shù)據(jù)下發(fā)到各個(gè)外設(shè)?
DPI 調(diào)用例子
是在verilog里面調(diào)用PLI獲取C里面的內(nèi)容?
PLI調(diào)用例子
還是通過TLM1.0 或者TLM2.0 完成C和verilog 的交互?
TLM2.0 使用例子
實(shí)際上,以上三種都不是!
在SOC驗(yàn)證環(huán)境中,需要仿真一顆芯片真實(shí)的工作狀態(tài)。通過上面三種手段驗(yàn)證不到CPU boot的過程,CPU處理 interrupt的過程,芯片進(jìn)出低功耗的過程。
在SOC環(huán)境中怎么模擬芯片的工作過程呢?
下面是一個(gè)典型的RISCV CPU。在這個(gè)系統(tǒng)中,CPU會(huì)通過指令總線獲取執(zhí)行的程序指令,然后通過數(shù)據(jù)總線訪問存儲(chǔ)的數(shù)據(jù)和外設(shè)等。
在下面的這個(gè)系統(tǒng)中,我們將RISCV的指令總線和數(shù)據(jù)總線作為兩個(gè)master 掛在AHB總線上,將程序指令存儲(chǔ)在SRAM中,當(dāng)SOC啟動(dòng)時(shí),會(huì)通過指令總線訪問SRAM獲取指令信息。
CPU中拿到指令后會(huì)進(jìn)行解碼,然后通過執(zhí)行單元執(zhí)行解碼的指令,如果需要用到外部的存儲(chǔ)數(shù)據(jù),則會(huì)通過數(shù)據(jù)總線訪問SRAM獲取存儲(chǔ)的數(shù)據(jù)內(nèi)容。如果解碼內(nèi)容配置外設(shè)寄存器,則通過數(shù)據(jù)總線訪問外設(shè),對(duì)外設(shè)進(jìn)行配置等等。
這里需要注意的是程序代碼放在SRAM里面,一些數(shù)據(jù)內(nèi)容也是分在SRAM中,假如中間不作分割,那么指令和數(shù)據(jù)會(huì)混在一起,導(dǎo)致RISCV在執(zhí)行程序的時(shí)候會(huì)跑飛。所以我們?cè)诤竺婢幾g指令的時(shí)候,需要對(duì)memory空間進(jìn)行分割。
通過上面的描述,我們大概清楚了CPU是怎么工作起來的,但是這個(gè)和我們的C程序有什么關(guān)系呢?
CPU執(zhí)行的是機(jī)器碼指令,這些指令是由一個(gè)個(gè)特定數(shù)據(jù)和擺放的格式?jīng)Q定的。
下面是32bit RISCV的部分指令格式。
在這里R,I S,B U,J分別代表6種不同的指令。
R-formatfor register-register arithmetic/logical operations
I-formatfor register-immediate arith/logical operations and loads
S-formatfor stores
B-formatfor branches
U-formatfor 20-bit upper immediate instructions
J-formatfor jumps
R是寄存器類型指令, I是立即數(shù)類型指令,S是存儲(chǔ)類指令,B是分支類指令,U是高20bit立即數(shù)指令,J是跳轉(zhuǎn)指令。
我們以簡(jiǎn)單的立即數(shù)加法運(yùn)算為例,比如我想做一個(gè) a0= s0+16 這樣一個(gè)立即數(shù)加法運(yùn)算,偽代碼就是 add a0,s0,16 。這個(gè)案例中我們用的是立即數(shù)類型的指令。
根據(jù)上述表格,該指令格式為
funct3,opcode又是什么呢?通過查詢 riscv 手冊(cè)可以查到以下結(jié)果。
這個(gè)立即數(shù)加法最終編譯成機(jī)器碼 0x01048513
將這個(gè)機(jī)器放在地址0x29a 的位置。
我們把這些機(jī)器碼放在SRAM里面,CPU看到拿到 0x01048513 就可以解碼出來這是一個(gè)a0=s0+16的立即數(shù)操作。
到這里,我們大概知道底層的CPU是怎么執(zhí)行的,現(xiàn)在要解決的問題是如何將C編譯成機(jī)器碼。
下面這個(gè)圖是C語言編譯成機(jī)器碼的過程。
C語言首先編譯成匯編語言,再由匯編語言編譯成機(jī)器指令,最后通過鏈接形成目標(biāo)機(jī)器指令。
我們以SOC3.0的環(huán)境為例子,看看這個(gè)過程怎么執(zhí)行的。
首先我們看SOC3.0的環(huán)境里面都有哪些文件。
crt0.S
"crt"代表"C runtime"(零表示"一切的開始")。
crt0是一組執(zhí)行啟動(dòng)例程,編譯到程序中,在調(diào)用程序的主函數(shù)之前執(zhí)行任何必要的初始化工作——它是一個(gè)基本的運(yùn)行時(shí)庫/運(yùn)行時(shí)系統(tǒng)。crt0的工作取決于程序的語言、編譯器、操作系統(tǒng)和C標(biāo)準(zhǔn)庫的實(shí)現(xiàn)。
在SOC3.0里面crt0.S 是匯編語言寫的。
這段代碼做什么事情呢?
異常處理程序:
default_exc_handler 是一個(gè)通用的異常處理程序,似乎被設(shè)置為各種異常的處理程序,比如外部中斷、非法指令和系統(tǒng)調(diào)用 (ecall)。
還有其他異常向量的占位符(nop指令),表明它們可能以類似的方式處理。
復(fù)位處理程序:
reset_handler 將所有寄存器設(shè)置為零,并通過加載堆棧起始地址來初始化堆棧。
清除BSS段:
它通過用零填充來清除BSS(由符號(hào)開始的塊)段。通常這樣做是為了確保所有未初始化的全局和靜態(tài)變量都以零值開始。
跳轉(zhuǎn)到主函數(shù):
最后,它跳轉(zhuǎn)到 main 函數(shù),并將 argc 和 argv 設(shè)置為零。
注意我們C 代碼的main 函數(shù)的命名不是天生就是這么命名的,是在這里給定的。
異常向量:
.vectors 部分定義了異常向量。它似乎對(duì)各種異常使用默認(rèn)的異常處理程序,還有一個(gè)重置向量指向 reset_handler。
2. C函數(shù)
以spi1_test test為例子
common.c ,這里定義了一些打印字符串和讀寫寄存器的函數(shù)。
spi1_test.c ,這里實(shí)現(xiàn)對(duì)spi1這個(gè)IP的配置及自我檢查。
3. 鏈接文件link.ld
我們?cè)谏衔恼f了,在SRAM里面如果不對(duì)程序存儲(chǔ)空間和數(shù)據(jù)存儲(chǔ)空間進(jìn)行分割,那么CPU執(zhí)行的時(shí)候很可能跑飛。為了組織內(nèi)存分配,需要一個(gè)鏈接文件進(jìn)行配置。
link.ld是一個(gè)鏈接腳本(linker script),用于指導(dǎo)鏈接器如何組織程序在內(nèi)存中的布局。具體來說,它包含了以下關(guān)鍵信息:
內(nèi)存布局:
內(nèi)存被劃分為兩個(gè)區(qū)域:rom(48 kB)和stack(16 kB)。
rom從地址0x00000000開始,stack從地址0x0000C000開始。
堆棧信息:
_min_stack設(shè)置為0x2000(8 kB),表示要保留的最小堆??臻g。
_stack_len是stack區(qū)域的長度。
_stack_start是堆棧的起始地址。
各個(gè)段:
.vectors 段包含中斷向量,位于rom的開頭。
.text 段包含程序代碼。構(gòu)造函數(shù)和析構(gòu)函數(shù)列表用于處理C++。
.rodata 段包含只讀數(shù)據(jù)。
.shbss 段在rom中對(duì)齊并放置。
.data 段包含已初始化的數(shù)據(jù)。
.bss 段包含未初始化的數(shù)據(jù)。
.stack 段確保堆棧有足夠的空間。
特殊段(NOLOAD):
.stack 段標(biāo)記為 NOLOAD,意味著它不會(huì)加載到最終的二進(jìn)制文件中。它只是為堆棧保留空間。
.stab 和 .stabstr 段也被定義,但標(biāo)記為 NOLOAD,表明它們是調(diào)試信息。
有了上面這些文件,我們看看Makefile 怎么將spi1_test.c 編譯成機(jī)器碼的。
第一步,通過riscv提供的工具鏈將C和匯編.S的文件編譯成目標(biāo)文件。
??--->
第二步,將生成的.o目標(biāo)文件鏈接成elf文件
ELF(Executable and Linkable Format)是一種用于可執(zhí)行文件、目標(biāo)文件、共享庫和核心轉(zhuǎn)儲(chǔ)文件的標(biāo)準(zhǔn)文件格式。它是一種二進(jìn)制文件格式,設(shè)計(jì)用于在多種操作系統(tǒng)上支持可執(zhí)行文件和可重定位代碼的交互性。
第三步,用elf文件生成 二進(jìn)制文件(機(jī)器碼) .bin文件
到這里我們已經(jīng)產(chǎn)生了機(jī)器碼的文件.bin,按理說CPU拿這個(gè)文件就可以執(zhí)行了。但是在我們環(huán)境里面,我們需要將機(jī)器碼放到verilog 的sram memory里面去。所以我們還做了第四步。
第四步,將.bin 文件轉(zhuǎn)換為一個(gè)包含二進(jìn)制數(shù)據(jù)的Verilog內(nèi)存初始化文件。
通過上面四步,我們實(shí)現(xiàn)了C到機(jī)器碼的轉(zhuǎn)換。
我們將生成的spi1_test.vmem 放到SOC環(huán)境中,sram的memory 通過readm 讀進(jìn)這些機(jī)器碼。然后通過仿真,就可以模擬SOC執(zhí)行的過程。
按理說到里面我們應(yīng)該結(jié)束今天的文章,但是當(dāng)我們回頭看看我們環(huán)境似乎還缺些什么。
沒錯(cuò)那就是機(jī)器碼的反標(biāo),當(dāng)我們debug cpu的時(shí)候,cpu記錄當(dāng)前執(zhí)行的指令和指令行數(shù),我們通過這些信息可以定位具體在操作哪條機(jī)器碼,但是我們?cè)趺礃硬胖喇?dāng)前的機(jī)器碼對(duì)應(yīng)是C代碼里面的拿哪段內(nèi)容呢?
這就需要機(jī)器碼的反標(biāo),在我們SOC3.0的環(huán)境里面,我們通過下面指令完成機(jī)器碼到C的反標(biāo)。
我們看看效果。
非常方便。
以上是我們文章的所有內(nèi)容,上述所列案例在我們SOC3.0里面都有,歡迎感興趣的小伙伴咨詢。
關(guān)于 SOC3.0,小伙伴們可以點(diǎn)這里。
SOC3.0有哪些東西?
或者直接聯(lián)系梨果。
審核編輯:劉清
-
寄存器
+關(guān)注
關(guān)注
31文章
5359瀏覽量
120790 -
soc
+關(guān)注
關(guān)注
38文章
4188瀏覽量
218604 -
Verilog
+關(guān)注
關(guān)注
28文章
1351瀏覽量
110187 -
DPI
+關(guān)注
關(guān)注
0文章
37瀏覽量
11524 -
SRAM存儲(chǔ)器
+關(guān)注
關(guān)注
0文章
88瀏覽量
13352
原文標(biāo)題:干貨,在SOC驗(yàn)證環(huán)境中,C代碼是怎么被執(zhí)行起來的?
文章出處:【微信號(hào):處芯積律,微信公眾號(hào):處芯積律】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論