基于matlab的pcm系统仿真_深入理解基于RISC-V ISS Spike的仿真系统:探索Spike,pk和fesrv...
Spike is named after the golden spike used to celebrate the completion of the US transcontinental railway.
一些同學初接觸RISC-V,總逃脫不了被Rocketchip, Chisel, Spike ISS俘虜的命運。今天我們就來說說Spike。歷經千辛萬苦clone repo,編譯安裝,并跟著說明文檔寫了一個hello world程序、然后編譯、最后敲下spike pk hello 之后終于在終端上見到了朝思暮想的hello world字符。平復一下自己激動的心情之后你是否在想:RISC-V的目標代碼為什么能在我的x86機子的linux系統的終端上打印字符串?這中間到底發生了什么?
簡單來說,這就是一個仿真的過程,Spike作為一個指令集的模擬器,配合其他工具如pk、fesrv完成系統的模擬。Spike本身使用C++實現,屬于ESL中Untimed model。Spike是個RISC-V指令集的仿真器,所以Spike只能吃RISC-V的目標代碼。那目標代碼是準備運行在什么機器上呢?RISC-V的生態碎片化很厲害,不能像x86系統那樣有明確的spec,甚至無法和ARM相比:ARM指令集以及系統留給你發揮的余地并不多。指令集支持哪些部分這個問題倒不大,告訴Spike就行了。memory map怎么定義的?有沒有MMU?是否是跑在linux上的?這個關系就很大了。簡單地說,spike可以運行下面三種類型的代碼:
- bare metal
- 使用newlib
- 使用glibc
1. software stack
讓我們先看看RISC-V software stack的定義(先不考慮用于支持virtualization的hypervisor模式):
對于bare metal來說,顧名思義,程序代碼在RISC-V硬件上裸奔,直接用spike去仿真就可以,不去加載pk。這時候并不存在AEE,或者說不存在現成的AEE,如果需要ABI的功能需要你自己實現一個AEE。這個時候硬件上的代碼怎么和外界打交道?比如輸出點字符什么的(如打印在終端上或者記錄在文件里)?很遺憾,無法直接做到。但是我們可以把GDB接上去看看程序的運行狀態。而且需要注意的是編譯代碼的時候需要指定各個segment的地址,并把這些信息傳遞給spike,否則spike是無法知道你code里訪問的memory到底多大、在哪里。
看來定義一個系統是非常有必要的了。pk就是做了這樣一件事情,pk(proxy kernel)是個輕量級的內核,已經定義好了RAM的基地址,也綁定了一些基本的library (newlib),是software stack里AEE的位置。所以我們也無需自己再去準備link的script了。這個時候就不能使用bare metal的工具了,該使用riscv64-unknown-elf里的一套工具,看得出來,pk輕量級系統還是64位的(當然也有32位的,就看你當時編譯pk工具指定哪個32位還是64位了)。pk提供了一些IO能力了,我們就有辦法和外界交互了。只是這些IO system call并不是pk自己處理,而是把它們導到host OS去處理。這么說吧,做了模擬器該做的事,所以pk也是模擬器的一部分。甚至pk中包含了一個boot loader,很容易理解,畢竟不能讓機器上來就運行我們寫的application級別的程序,作為一個系統來講,很多初始化的工作要做。另外既然要和host打交道,那么需要一個RISC-V target和host直接的橋梁,這既是fesvr(frontend server)的功能了。
glibc很容易理解,提供了OS的接口,這時候ABI訪問的就是OS了,作為寫application,和在x86上的linux上寫沒什么區別。這時候工具就要使用riscv64-unknown-linux-gnu這一套。當然IO相關功能也是由linux來提供,至于具體是怎么定義的,則是OS來操心的:OS需要知道你的機器spec的每一個細節,編譯OS時候必須給出來的。
2. spike, pk, fesrv三基友
我們重點談談使用pk的spike仿真原理。為了簡單起見,我們假定host是x86 linux。下面談談spike,pk,fesrv這三基友是個什么關系。它們之間簡單的連接關系如下圖。
Application的目標代碼是RISC-V毫無疑問。pk作為RISC-V的輕量級操作系統,也應該是RISC-V目標代碼。Spike在Host上運行,毫無疑問是host的目標代碼。而fesrv調用host的系統服務,所以只能是host的目標代碼(這里是x86 linux)。那假設RISC-V程序里的一個系統調用,怎么轉成x86 linux上的系統調用呢?
咱們來看一個例子,寫一個Hello world:
#include <stdio.h>int main() {printf("Hello World!n");return 0; }我們可以看到里面用到了printf,表明我們用到了系統調用。我們使用riscv64-unknown-elf-gcc把它編譯成RISC-V的目標文件hello_world。
2.1. device tree與boot loader
Spike還有一個輸入,就是device tree,就是讓Spike知道當前的target硬件的一些特性。可以通過下面的命令看一下當前使用的device(就是SoC系統)的一些配置:
$spike --dump-dts hello_world得到下面的輸出:
/dts-v1/;/ {#address-cells = <2>;#size-cells = <2>;compatible = "ucbbar,spike-bare-dev";model = "ucbbar,spike-bare";cpus {#address-cells = <1>;#size-cells = <0>;timebase-frequency = <10000000>;CPU0: cpu@0 {device_type = "cpu";reg = <0>;status = "okay";compatible = "riscv";riscv,isa = "rv64imafdc";mmu-type = "riscv,sv48";clock-frequency = <1000000000>;CPU0_intc: interrupt-controller {#interrupt-cells = <1>;interrupt-controller;compatible = "riscv,cpu-intc";};};};memory@80000000 {device_type = "memory";reg = <0x0 0x80000000 0x0 0x80000000>;};soc {#address-cells = <2>;#size-cells = <2>;compatible = "ucbbar,spike-bare-soc", "simple-bus";ranges;clint@2000000 {compatible = "riscv,clint0";interrupts-extended = <&CPU0_intc 3 &CPU0_intc 7 >;reg = <0x0 0x2000000 0x0 0xc0000>;};};htif {compatible = "ucb,htif0";}; };這其實就是pk這個虛擬的RISC-V SoC的配置了,因為我們的hello world是通過riscv64-unknown-elf-gcc編譯出來的,表明我們使用了pk支持的device。
上電后(reset后)開始執行0x80000000地址上的代碼,其實就是boot loader了,這里是pk的代碼(可以通過objdump得到):
0000000080000000 <_ftext>: 80000000: 1e80006f j 800001e8 <do_reset>00000000800001e8 <do_reset>: 800001e8: 00000093 li ra, 0 800001ec: 00000113 li sp, 0 800001f0: 00000193 li gp, 0 800001f4: 00000213 li tp, 0 ... 800002a8: 00070463 beqz a4,800002b0 <do_reset + 0xc8> 800002ac: 6f60206f j 800029a2 <init_first_hart> 800002b0: 00800613 li a2,8 800002b4: 30461073 csrw mie, a2具體細節不必贅述,最終會跳到我們的Application:hello_world的入口。
2.2. 黑魔法HTIF
輾轉騰挪,printf的函數調用最終會傳遞給pk來處理,黑魔法來了,在pk.c中有這樣的代碼:
static size_t parse_args(arg_buf* args) {long r = frontend_syscall(SYS_getmainvars, va2pa(args), sizeof(*args), 0, 0, 0, 0, 0);//......在frontend.c中實現frontend_syscall:
long frontend_syscall(long n, uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5, uint64_t a6) {static volatile uint64_t magic_mem[8];static spinlock_t lock = SPINLOCK_INIT;spinlock_lock(&lock);magic_mem[0] = n;magic_mem[1] = a0;magic_mem[2] = a1;magic_mem[3] = a2;magic_mem[4] = a3;magic_mem[5] = a4;magic_mem[6] = a5;magic_mem[7] = a6;htif_syscall((uintptr_t)magic_mem);long ret = magic_mem[0];spinlock_unlock(&lock);return ret; }關鍵部分是htif_syscall,我們又來到machine/htif.c里:
void htif_syscall(uintptr_t arg) {do_tohost_fromhost(0, 0, arg); }static void do_tohost_fromhost(uintptr_t dev, uintptr_t cmd, uintptr_t data) {spinlock_lock(&htif_lock);__set_tohost(dev, cmd, data);while (1) {uint64_t fh = fromhost;if (fh) {if (FROMHOST_DEV(fh) == dev && FROMHOST_CMD(fh) == cmd) {fromhost = 0;break;}__check_fromhost();}}spinlock_unlock(&htif_lock); }static void __set_tohost(uintptr_t dev, uintptr_t cmd, uintptr_t data) {while (tohost)__check_fromhost();tohost = TOHOST_CMD(dev, cmd, data); }在htif.h里define了TOHOST_CMD:
# define TOHOST_CMD(dev, cmd, payload) ({ if ((dev) || (cmd)) __builtin_trap(); (payload); }) #endif看來pk和fesrv之間是靠HTIF來傳遞信息。在fesrv的http://htif.cc里:
//......std::map<std::string, uint64_t> symbols = load_elf(path.c_str(), &preload_aware_memif, &entry);if (symbols.count("tohost") && symbols.count("fromhost")) {tohost_addr = symbols["tohost"];fromhost_addr = symbols["fromhost"];} else {fprintf(stderr, "warning: tohost and fromhost symbols not in ELF; can't communicate with targetn");} //......看到這里,答案呼之欲出了:fesrv知道數據交換的內存地址,看來這個黑魔法就是fesrv基于risc-v仿真也是運行在host內存里的便利,直接通過內存數據讀寫來交換數據。至于如何去查詢target是否需要系統服務,服務哪些target,這些在http://htif.cc里有具體實現,不再贅述。
總結
以上是生活随笔為你收集整理的基于matlab的pcm系统仿真_深入理解基于RISC-V ISS Spike的仿真系统:探索Spike,pk和fesrv...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C语言数理逻辑题目,数学逻辑推理题整理,
- 下一篇: linux windows文件 编码_M