iT邦幫忙

0

[gem5][simple-rv-vp] 建立一個簡單的 fs 環境,並運行簡易 firmware

  • 分享至 

  • xImage
  •  

系列文章 : [gem5] 從零開始的 gem5 學習筆記

這一篇文章,來嘗試從頭開始自己組裝一個 RISC-V 的 virtual platform,並且在這個 virtual plaform 裡面,運行一個簡單的 firmware。

這個 virtual platform 希望是越簡單越好 ( 甚至沒有 PLIC ),希望可以藉由這個 platform 來跑跑看其他簡單的實驗,或是拿來 trace 其他 model 的程式碼。



如何運行簡單的範例

git clone git@github.com:TommyWu-fdgkhdkgh/simple-riscv-vp.git 
cd simple-riscv-vp

# install riscv tool chain
make install/riscv64-unknown-elf/gcc

# 安裝一些 gem5 需要的東西
make gem5/apt-install

# 把 gem5 這個專案給 clone 下來
make gem5/clone

# 安裝 gem5 需要的 python 套件
make gem5/venv

# 編譯 gem5.opt
make gem5/build-opt

# 編譯 gem5.debug
make gem5/build-debug

# 運行 gem5 的 hello world,看一下編譯有沒有成功
make gem5/run-opt/hello-world

# 編譯 firmware
make firmware/build

# 運行 firmware
gem5/run-opt/simple-riscv-vp/firmware

# 用 minor CPU model 來運行 firmware
gem5/run-opt/simple-riscv-vp/firmware CPU_TYPE=minor


# 在另外一個 terminal,連進正在運行的 gem5
make gem5/term/run


假如有運行成功的話,預期 terminal 會輸出下面的訊息。
可以藉由輸入 m 來觸發一個 timer interrupt

==== m5 terminal: Terminal 0 ====
start simple firmware!
============ menu ============
a : calculate a big number and insert mtime interrupt
m : set mtime cmp
==============================
m : set mtime cmp !
handle_interrupt !
timer interrupt !


virtual platform configuration

SimpleRiscvPlatform

{ https://github.com/TommyWu-fdgkhdkgh/simple-riscv-vp/blob/main/simple-riscv-vp.py }

class SimpleRiscvPlatform(SimplePlatform):
    def __init__(self):
        super(SimpleRiscvPlatform, self).__init__()

SimplePlatform

基本上就是什麼也不做的 Platform。

可以看一下其他人寫的 platform, 例如 HiFive



Q: 在 platform 裡面, postConsoleInt, clearConsoleInt 有什麼用處 ?

在 gem5 裡面,有一些 model 不是透過 IntSourcePin 去發 interrupt,而是直接透過 platform 指標去發 interrupt。



Q: 為什麼要自己額外弄一個 SimplePlatform

希望有一個什麼也不做的 platform,原本是想直接用 class Platform,可惜它是 abstract,不能直接被使用,只能被當作 base class



        # RTCCLK (Set to 100MHz for faster simulation)
        self.rtc = RiscvRTC(frequency=Frequency("100MHz"))

定期 ( 頻率可以設定 ) 發訊號出來。



        self.clint = Clint(pio_addr=0x2000000, num_threads=1)
        self.clint.int_pin = self.rtc.int_pin

接受來自 rtc 的訊號,並以該頻率去遞增 mtime CSR。


Q: mtime CSR 是怎麼被讀取的 ?

大致上的順序

  • CPU 會去找 RiscvSystem
  • RiscvSystem 再去找 Clint
  • mtime 的資訊會存在 Clint 裡面

Q: Clint 是怎麼接上 CPU 的 ?


Q: 為什麼不用 port,而是用這樣的方式進行連接呢 ?
不知道 QQ。
情感上,我比較喜歡 SystemC 那樣,用 pin / port 接在一起的感覺。



        self.terminal = Terminal()
        self.uart = SimpleUart(pio_addr=0x10000000)


cpu.createThreads

{ https://github.com/TommyWu-fdgkhdkgh/gem5/blob/stable/src/cpu/BaseCPU.py#L233 }

create hardware thread, 絕大部分的情況下,thread 的數量為 1



cpu.createInterruptController

{ https://github.com/TommyWu-fdgkhdkgh/gem5/blob/stable/src/cpu/BaseCPU.py#L176 }

建立符合 thread 數量的 ArchInterrupts。在 RISC-V,會是 RiscvInterrupts



cpu.isa[0].riscv_type

# note :
#     cpu.ArchISA is a class
#     cpu.isa is an object array
#     cpu.isa[0] is the isa for the first thread
#         ( we almost only use one thread for a CPU model )
system.cpu.isa[0].riscv_type = "RV32"

設定 riscv_type,可以是 RV32 或是 RV64



RiscvBareMetal

system.workload = RiscvBareMetal(wait_for_remote_gdb=False)
system.workload.bootloader = args.firmware_path

Workload : 解析 ELF,拿到 ELF 的相關資訊 ( e.g. entry point )



simple firmware boot flow

firmware/boot.S

{ https://github.com/TommyWu-fdgkhdkgh/simple-riscv-vp/blob/main/firmware/boot.S }

.global _start
_start:
    csrr   t0, mhartid
    lui    t1, 0
    beq    t0, t1, 2f

1:  wfi
    j      1b
  • 假如 hartid 是 0 的話,就可以跳出去,繼續運行
  • 假如 hartid 不是 0,卡在 wfi ( wait for interrupt ) 的無窮迴圈裡面


2:
    # initialize global pointer
    la gp, _gp
  
    # initialize stack pointer
    la sp, stack_top
    call   start

初始化 stack pointer 後,就跳到 C function : start



// trap entry
.globl trap_entry
.align 4
trap_entry:
  # make room to save registers.
  addi sp, sp, -256

  # save the registers.
  sw ra, 0(sp)
  sw sp, 8(sp)
  sw gp, 16(sp)
  sw tp, 24(sp)
  sw t0, 32(sp)
  sw t1, 40(sp)
…
  sw s10, 200(sp)
  sw s11, 208(sp)
  sw t3, 216(sp)
  sw t4, 224(sp)
  sw t5, 232(sp)
  sw t6, 240(sp)

  call handle_trap

  # restore registers.
  lw ra, 0(sp)
  lw sp, 8(sp)
  lw gp, 16(sp)
  # not this, in case we moved CPUs: ld tp, 24(sp)
  lw t0, 32(sp)
  lw t1, 40(sp)
  lw t2, 48(sp)
  lw s0, 56(sp)
… 
  lw t3, 216(sp)
  lw t4, 224(sp)
  lw t5, 232(sp)
  lw t6, 240(sp)

  addi sp, sp, 256

  mret

當 trap 發生的時候,把 register 存進 stack 裡面,等到 handle_trap 處理完後,再把狀態恢復回來。



firmware/main.c/start

{ https://github.com/TommyWu-fdgkhdkgh/simple-riscv-vp/blob/main/firmware/main.c }

  // set up mtvec
  w_mtvec((uint32_t)trap_entry);

把 mtvec 設為 trap_entry,這樣當 trap ( interrupt, exception, ecall ) 發生的時候,就會跳來這裡。


  // init mstatus.MPP to M-mode
  tmp = r_mstatus();
  tmp |= MSTATUS_MPP_M;
  w_mstatus(tmp);

把 MPP 設為 M-mode,這樣當 mret 的時候,特權級就還是 M-mode


  // init mstatus.MPIE
  tmp = r_mstatus();
  tmp |= MSTATUS_MPIE;
  w_mstatus(tmp); 

把 MPIE 打開,這樣當 mret 之後,就會啟用 interrupt。


  // mepc 
  w_mepc((uint32_t)main); 

把 mepc 設為 main,這樣當 mret 之後,會跳到 main function。


  // mret to main
  asm volatile("mret");

利用 mret 跳到 main function。



firmware/uart.c

{ https://github.com/TommyWu-fdgkhdkgh/simple-riscv-vp/blob/main/firmware/uart.c }

void uart_putchar(char c) {
  *(volatile uint32_t *)SIMPLE_UART_ADDR = (uint32_t) c;
}

char uart_getchar(void) {
  return *(volatile uint32_t *)SIMPLE_UART_ADDR;
}

因為我這邊是用 gem5 的 SimpleUart model,這個 model 不會去發 interrupt,而是要我們自己去 polling。

當我們想要從 uart 拿資料時,就是瘋狂去讀取他的 register 就是了。




圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言