rv32emu next - ccc-sp/riscv2os GitHub Wiki
rv32emu-next -- RISC-V 虛擬機實作
install
guest@localhost:~/rv32emu-next$ su root
Password:
root@localhost:/home/guest/rv32emu-next# sudo apt install libsdl2-dev
Reading package lists... Done
Building dependency tree
Reading state information... Done
libsdl2-dev is already the newest version (2.0.8+dfsg1-1ubuntu1.18.04.4).
0 upgraded, 0 newly installed, 0 to remove and 416 not upgraded.
make & run
guest@localhost:~/rv32emu-next$ make
CC c_map.o
CC riscv.o
CC io.o
CC elf.o
CC main.o
CC syscall.o
CC syscall_sdl.o
LD build/rv32emu
guest@localhost:~/rv32emu-next$ make check
(cd build; ../build/rv32emu hello.elf)
Hello World!
inferior exit code 0
(cd build; ../build/rv32emu puzzle.elf)
success in 2005 trials
inferior exit code 0
工作流程
- IO 以及記憶體、虛擬機初始化。
run()orrun_and_trace()根據不同情況決定每個 cycle 要執行多少個指令後再呼叫rv_step()。rv_step()拿出指令並判斷其類型 (load, jump, store, branch...) 後呼叫op(),讓其做 dispatch 和後續動作。
-
rv_step()達到 cycle 目標之前重複以下動作:- 將指令從 pc 指向的記憶體位置讀取出來。
- 讀取出來之後,將指令交給 op handler: op() 進行處理。
- op()
在
riscv.c的第 787 行處有預先定義好 RV32I 各類指令的 opcode (前五碼)。
static const opcode_t opcodes[] = { // 000 001 010 011 100 101 110 111 op_load, op_load_fp, NULL, op_misc_mem, op_op_imm, op_auipc, NULL, NULL, // 00 op_store, op_store_fp, NULL, op_amo, op_op, op_lui, NULL, NULL, // 01 op_madd, op_msub, op_nmsub, op_nmadd, op_fp, NULL, NULL, NULL, // 10 op_branch, op_jalr, NULL, op_jal, op_system, NULL, NULL, NULL, // 11 };只定義前 5 碼是因為 RV32I opcode 的後兩碼都是固定的 (
xxxxx11),我們也可以在rv_step()中看到待執行指令inst的預處理:// standard uncompressed instruction if ((inst & 3) == 3) { const uint32_t index = (inst & INST_6_2) >> 2;簡單來說就是判斷
inst是否屬於 RV32I 指令(末兩碼是否為11。),如果是的話我們就將末兩碼移除並且做right_shift。補充 1 :
INST_6_2定義在riscv_private.h中,其值為0b00000000000000000000000001111100。補充 2 : 除了 RVC 指令集外,其他合法 RISC-V 指令集的 OPCODE 末兩碼都是
11。題外話: 在上面的原始碼中就有大量的 bitwise 操作,再次凸顯它的重要性。
-
op()op其實只是函式指標,透過rv_step()指定指令的 handler 後,再去做相關操作。 這邊以op_op_imm()這個關於整數操作的 handler 舉例:static bool op_op_imm(struct riscv_t *rv, uint32_t inst) { // i-type decode const int32_t imm = dec_itype_imm(inst); const uint32_t rd = dec_rd(inst); const uint32_t rs1 = dec_rs1(inst); const uint32_t funct3 = dec_funct3(inst); // dispatch operation type switch (funct3) { case 0: // ADDI rv->X[rd] = (int32_t)(rv->X[rs1]) + imm; break; case 1: // SLLI rv->X[rd] = rv->X[rs1] << (imm & 0x1f); break; case 2: // SLTI rv->X[rd] = ((int32_t)(rv->X[rs1]) < imm) ? 1 : 0; break; case 3: // SLTIU rv->X[rd] = (rv->X[rs1] < (uint32_t) imm) ? 1 : 0; break; case 4: // XORI rv->X[rd] = rv->X[rs1] ^ imm; break; case 5: if (imm & ~0x1f) { // SRAI rv->X[rd] = ((int32_t) rv->X[rs1]) >> (imm & 0x1f); } else { // SRLI rv->X[rd] = rv->X[rs1] >> (imm & 0x1f); } break; case 6: // ORI rv->X[rd] = rv->X[rs1] | imm; break; case 7: // ANDI rv->X[rd] = rv->X[rs1] & imm; break; default: rv_except_illegal_inst(rv); return false; } // step over instruction rv->PC += 4; // enforce zero register if (rd == rv_reg_zero) rv->X[rv_reg_zero] = 0; return true; }
handler 會將傳入指令做解碼,也就是將傳入的 inst根據上圖的 I type 切割成四塊:- funct3
- imm
- rs1
- rd
funct3會決定要做哪一種操作,如: ADDI, SLLI, ORI... 。 假設現在要做的是 ADDI 操作,模擬器就會按照 RISC-V 中 ADDI 指令所定義的行為執行。
定義: 常數部分為 sign-extended 12-bit ,會將 12-bit 做 sign-extension 成 32-bit 後,再與 rs1 暫存器做加法運算,將結果寫入 rd 暫存器,addi rd, rs1, 0 可被用來當做 mov 指令。
將結果寫回相關暫存器後, handler 會將 Program counter 指到下一個記憶體位置後回傳結果。