diff --git a/.github/workflows/make-ci.yml b/.github/workflows/make-ci.yml index ba3c2a6..a84259b 100644 --- a/.github/workflows/make-ci.yml +++ b/.github/workflows/make-ci.yml @@ -13,7 +13,26 @@ jobs: steps: - uses: actions/checkout@v5 - - name: make - run: make - - name: make test + - name: Setup Node.js for xpm + uses: actions/setup-node@v4 + with: + node-version: '20' + - name: Install xpm and riscv-none-elf toolchain + shell: bash + run: | + set -euox pipefail + npm install --location=global xpm@latest + mkdir -p .ci-xpm + printf '%s\n' '{' \ + ' "name": "vbo-ci-xpm",' \ + ' "version": "0.0.0",' \ + ' "private": true,' \ + ' "xpack": {}' \ + '}' > .ci-xpm/package.json + ( cd .ci-xpm && xpm install @xpack-dev-tools/riscv-none-elf-gcc@14.2.0-3.1 --verbose ) + TOOLCHAIN_BIN="$HOME/.local/xPacks/@xpack-dev-tools/riscv-none-elf-gcc/14.2.0-3.1/.content/bin" + echo "$TOOLCHAIN_BIN" >> "$GITHUB_PATH" + echo "RV_PREFIX=riscv-none-elf-" >> "$GITHUB_ENV" + "$TOOLCHAIN_BIN/riscv-none-elf-gcc" --version + - name: make test # test incorporates build into it run: make test \ No newline at end of file diff --git a/.gitignore b/.gitignore index b7785b6..bfe1de7 100644 --- a/.gitignore +++ b/.gitignore @@ -71,4 +71,10 @@ test/**/* *.bin *.app -*.img \ No newline at end of file +*.img + +# latex +paper/**/* +!paper/**/*.tex +!paper/**/*.bib +!paper/**/*.pdf \ No newline at end of file diff --git a/Makefile b/Makefile index a4fab67..ff21ad5 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ SRC_MAIN = src/main.c $(SRC_COMMON) FLAGS = -Wall -Wextra -Werror -g -std=c11 -pedantic -# RISC-V cross toolchain (see docs/compiler.md) +# RISC-V cross toolchain (see the wiki) RV_PREFIX ?= ~/.local/xPacks/riscv-none-elf-gcc/xpack-riscv-none-elf-gcc-14.2.0-3/bin/riscv-none-elf- RV_GCC := $(RV_PREFIX)gcc RV_OBJCOPY:= $(RV_PREFIX)objcopy @@ -27,7 +27,7 @@ ORIGIN ?= 0x3000 define ensure_rv_toolchain @if ! command -v $(RV_GCC) >/dev/null 2>&1; then \ - echo "[Make] Missing toolchain: $(RV_GCC). See docs/compiler.md to install and add to PATH."; \ + echo "[Make] Missing toolchain: $(RV_GCC). See the wiki for info on how to install and add to PATH."; \ exit 1; \ fi @if ! command -v $(RV_OBJCOPY) >/dev/null 2>&1; then \ @@ -44,7 +44,7 @@ endif TEST_DIR = test -.PHONY: all build clean test distclean app image embed run demo clean-app test-integration test-all help +.PHONY: all build clean test app image embed run demo distclean test-integration test-all help all: build @@ -61,16 +61,26 @@ clean: rm -f $(TEST_DIR)/test_utils rm -f images/vbo_image.o rm -rf $(OUT_DIR) - distclean: clean -test: +test: build $(CC) $(INC) $(TEST_DIR)/test_utils.c src/utils.c src/riscv32i.c -o $(TEST_DIR)/test_utils $(FLAGS) $(TEST_DIR)/test_utils rm $(TEST_DIR)/test_utils -# Run both unit and integration tests (integration is skipped if toolchain is missing) -test-all: test test-integration + $(CC) $(INC) $(TEST_DIR)/test_riscv32i.c src/riscv32i.c -o $(TEST_DIR)/test_riscv32i $(FLAGS) + $(TEST_DIR)/test_riscv32i + rm $(TEST_DIR)/test_riscv32i + + @if ! command -v $(RV_GCC) >/dev/null 2>&1 || ! command -v $(RV_OBJCOPY) >/dev/null 2>&1; then \ + echo "[Test] Skipping integration test (toolchain not found). See the wiki"; \ + exit 0; \ + fi + @$(MAKE) --no-print-directory image + @out=$$($(BIN) $(APP_IMG)); echo "$$out" | grep -q "Hello from RV32I VM" && echo "OK" || (echo "FAIL"; exit 1) + + + # --- App build pipeline --- @@ -104,19 +114,6 @@ run: build image demo: embed run -clean-app: - rm -rf $(OUT_DIR) - -# Integration test (optional): requires toolchain, builds hello and checks output -test-integration: build - @if ! command -v $(RV_GCC) >/dev/null 2>&1 || ! command -v $(RV_OBJCOPY) >/dev/null 2>&1; then \ - echo "[Test] Skipping integration test (toolchain not found). See docs/compiler.md"; \ - exit 0; \ - fi - @$(MAKE) --no-print-directory image - @echo "[Test] Running integration test (hello)" - @out=$$($(BIN) $(APP_IMG)); echo "$$out" | grep -q "Hello from RV32I VM" && echo "[Test] OK" || (echo "[Test] FAIL"; exit 1) - help: @echo "Targets:" @echo " build - Build the VM" diff --git a/include/riscv32i.h b/include/riscv32i.h index 147ec19..579dec9 100644 --- a/include/riscv32i.h +++ b/include/riscv32i.h @@ -69,10 +69,10 @@ static inline int32_t rv32i_sext(uint32_t val, int bits) { // define instructions -void exec_riscv32i(uint32_t instruction); +void exec_rv32i(uint32_t instruction); // functions for all instructions, in the format: -// void exec_riscv32i_(uint32_t instruction); +// void exec_rv32i_(uint32_t instruction); /* canonical order: addi, slti[u], andi, ori, xori, slli, srli, srai, lui, auipc, // integer register-immediate @@ -171,46 +171,46 @@ enum // integer register-immediate -void exec_riscv32i_addi(uint32_t instruction); -void exec_riscv32i_slti(uint32_t instruction); -void exec_riscv32i_andi(uint32_t instruction); -void exec_riscv32i_ori(uint32_t instruction); -void exec_riscv32i_xori(uint32_t instruction); -void exec_riscv32i_slli(uint32_t instruction); -void exec_riscv32i_srli_srai(uint32_t instruction); -void exec_riscv32i_lui(uint32_t instruction); -void exec_riscv32i_auipc(uint32_t instruction); -void exec_riscv32i_sltiu(uint32_t instruction); +void exec_rv32i_addi(uint32_t instruction); +void exec_rv32i_slti(uint32_t instruction); +void exec_rv32i_andi(uint32_t instruction); +void exec_rv32i_ori(uint32_t instruction); +void exec_rv32i_xori(uint32_t instruction); +void exec_rv32i_slli(uint32_t instruction); +void exec_rv32i_srli_srai(uint32_t instruction); +void exec_rv32i_lui(uint32_t instruction); +void exec_rv32i_auipc(uint32_t instruction); +void exec_rv32i_sltiu(uint32_t instruction); // integer register-register -void exec_riscv32i_add_sub(uint32_t instruction); -void exec_riscv32i_slt(uint32_t instruction); -void exec_riscv32i_sltu(uint32_t instruction); -void exec_riscv32i_and(uint32_t instruction); -void exec_riscv32i_or(uint32_t instruction); -void exec_riscv32i_xor(uint32_t instruction); -void exec_riscv32i_sll(uint32_t instruction); -void exec_riscv32i_srl_sra(uint32_t instruction); +void exec_rv32i_add_sub(uint32_t instruction); +void exec_rv32i_slt(uint32_t instruction); +void exec_rv32i_sltu(uint32_t instruction); +void exec_rv32i_and(uint32_t instruction); +void exec_rv32i_or(uint32_t instruction); +void exec_rv32i_xor(uint32_t instruction); +void exec_rv32i_sll(uint32_t instruction); +void exec_rv32i_srl_sra(uint32_t instruction); // unconditional jumps -void exec_riscv32i_jal(uint32_t instruction); -void exec_riscv32i_jalr(uint32_t instruction); +void exec_rv32i_jal(uint32_t instruction); +void exec_rv32i_jalr(uint32_t instruction); // conditional branches -void exec_riscv32i_beq(uint32_t instruction); -void exec_riscv32i_bne(uint32_t instruction); -void exec_riscv32i_blt(uint32_t instruction); -void exec_riscv32i_bge(uint32_t instruction); -void exec_riscv32i_bltu(uint32_t instruction); -void exec_riscv32i_bgeu(uint32_t instruction); +void exec_rv32i_beq(uint32_t instruction); +void exec_rv32i_bne(uint32_t instruction); +void exec_rv32i_blt(uint32_t instruction); +void exec_rv32i_bge(uint32_t instruction); +void exec_rv32i_bltu(uint32_t instruction); +void exec_rv32i_bgeu(uint32_t instruction); // load and store -void exec_riscv32i_load(uint32_t instruction); -void exec_riscv32i_store(uint32_t instruction); +void exec_rv32i_load(uint32_t instruction); +void exec_rv32i_store(uint32_t instruction); // memory ordering -void exec_riscv32i_fence(uint32_t instruction); +void exec_rv32i_fence(uint32_t instruction); // environment call and breakpoints -void exec_riscv32i_system(uint32_t instruction); +void exec_rv32i_system(uint32_t instruction); // error handling -void exec_riscv32i_bad_opcode(uint32_t instruction); +void exec_rv32i_bad_opcode(uint32_t instruction); // diff --git a/include/rv32_syscalls.h b/include/rv32_syscalls.h index 7936e0e..cffd776 100644 --- a/include/rv32_syscalls.h +++ b/include/rv32_syscalls.h @@ -3,6 +3,8 @@ // Each wrapper issues an ecall with a7=syscall number and returns a0. // On error, the return value is a negative errno (Linux convention). +// ? for a real-world use, it'd be much more efficient to implement a c library (newlib?) + #ifndef RV32_SYSCALLS_H #define RV32_SYSCALLS_H diff --git a/src/main.c b/src/main.c index c3fbdba..c460c96 100644 --- a/src/main.c +++ b/src/main.c @@ -73,7 +73,7 @@ int main(int argc, const char* argv[]) break; } rv32i_reg[pc] += 4; // advance to next 32-bit instruction - exec_riscv32i(instr); + exec_rv32i(instr); } return 0; } diff --git a/src/riscv32i.c b/src/riscv32i.c index 9b4474a..acf3f1d 100644 --- a/src/riscv32i.c +++ b/src/riscv32i.c @@ -19,9 +19,13 @@ document version 20250508, accessed: 23/09/2025 #include "riscv32i.h" uint8_t rv32i_mem[RV32I_MEM_SIZE]; -uint32_t rv32i_reg[32 + 1]; +uint32_t rv32i_reg[32 + 1]; // 32 GP registers + 1 PC // little-endian byte-addressable memory helpers +/* TODO: improve readability, + TODO: handle misaligned accesses (raise exception?) +*/ + static inline uint32_t rv32i_mem_read8(uint32_t addr) { return rv32i_mem[addr & (RV32I_MEM_SIZE - 1)]; } @@ -65,14 +69,14 @@ static inline void rv32i_mem_write32(uint32_t addr, uint32_t val) { +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 31 20 19 15 14 12 11 7 6 0 */ -void exec_riscv32i_addi(uint32_t instruction) +void exec_rv32i_addi(uint32_t instruction) { uint32_t rd = RV32I_RD(instruction); uint32_t rs1 = RV32I_RS1(instruction); int32_t imm = RV32I_IMM_I(instruction); rv32i_write_reg(rd, rv32i_read_reg(rs1) + (uint32_t)imm); } -void exec_riscv32i_slti(uint32_t instruction) +void exec_rv32i_slti(uint32_t instruction) { uint32_t rd = RV32I_RD(instruction); uint32_t rs1 = RV32I_RS1(instruction); @@ -80,42 +84,42 @@ void exec_riscv32i_slti(uint32_t instruction) int32_t lhs = (int32_t)rv32i_read_reg(rs1); rv32i_write_reg(rd, (uint32_t)(lhs < imm)); } -void exec_riscv32i_andi(uint32_t instruction) +void exec_rv32i_andi(uint32_t instruction) { uint32_t rd = RV32I_RD(instruction); uint32_t rs1 = RV32I_RS1(instruction); int32_t imm = RV32I_IMM_I(instruction); rv32i_write_reg(rd, rv32i_read_reg(rs1) & (uint32_t)imm); } -void exec_riscv32i_ori(uint32_t instruction) +void exec_rv32i_ori(uint32_t instruction) { uint32_t rd = RV32I_RD(instruction); uint32_t rs1 = RV32I_RS1(instruction); int32_t imm = RV32I_IMM_I(instruction); rv32i_write_reg(rd, rv32i_read_reg(rs1) | (uint32_t)imm); } -void exec_riscv32i_xori(uint32_t instruction) +void exec_rv32i_xori(uint32_t instruction) { uint32_t rd = RV32I_RD(instruction); uint32_t rs1 = RV32I_RS1(instruction); int32_t imm = RV32I_IMM_I(instruction); rv32i_write_reg(rd, rv32i_read_reg(rs1) ^ (uint32_t)imm); } -void exec_riscv32i_sltiu(uint32_t instruction) +void exec_rv32i_sltiu(uint32_t instruction) { uint32_t rd = RV32I_RD(instruction); uint32_t rs1 = RV32I_RS1(instruction); uint32_t imm = (uint32_t)RV32I_IMM_I(instruction); rv32i_write_reg(rd, (uint32_t)(rv32i_read_reg(rs1) < imm)); } -void exec_riscv32i_slli(uint32_t instruction) +void exec_rv32i_slli(uint32_t instruction) { uint32_t rd = RV32I_RD(instruction); uint32_t rs1 = RV32I_RS1(instruction); uint32_t shamt = (instruction >> 20) & RV32I_SHAMT_MASK; // shamt encoded in imm[4:0] rv32i_write_reg(rd, rv32i_read_reg(rs1) << shamt); } -void exec_riscv32i_srli_srai(uint32_t instruction) +void exec_rv32i_srli_srai(uint32_t instruction) { uint32_t rd = RV32I_RD(instruction); uint32_t rs1 = RV32I_RS1(instruction); @@ -129,13 +133,13 @@ void exec_riscv32i_srli_srai(uint32_t instruction) } // SRLI, default rv32i_write_reg(rd, rv32i_read_reg(rs1) >> shamt); } -void exec_riscv32i_lui(uint32_t instruction) +void exec_rv32i_lui(uint32_t instruction) { uint32_t rd = RV32I_RD(instruction); int32_t imm = RV32I_IMM_U(instruction); rv32i_write_reg(rd, (uint32_t)imm); } -void exec_riscv32i_auipc(uint32_t instruction) +void exec_rv32i_auipc(uint32_t instruction) { uint32_t rd = RV32I_RD(instruction); int32_t imm = RV32I_IMM_U(instruction); @@ -150,7 +154,7 @@ void exec_riscv32i_auipc(uint32_t instruction) +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 31 25 24 20 19 15 14 12 11 7 6 0 */ -void exec_riscv32i_add_sub(uint32_t instruction) +void exec_rv32i_add_sub(uint32_t instruction) { uint32_t rd = RV32I_RD(instruction); uint32_t rs1 = RV32I_RS1(instruction); @@ -163,49 +167,49 @@ void exec_riscv32i_add_sub(uint32_t instruction) } rv32i_write_reg(rd, rv32i_read_reg(rs1) + rv32i_read_reg(rs2)); } -void exec_riscv32i_slt(uint32_t instruction) +void exec_rv32i_slt(uint32_t instruction) { uint32_t rd = RV32I_RD(instruction); uint32_t rs1 = RV32I_RS1(instruction); uint32_t rs2 = RV32I_RS2(instruction); rv32i_write_reg(rd, (uint32_t)((int32_t)rv32i_read_reg(rs1) < (int32_t)rv32i_read_reg(rs2))); } -void exec_riscv32i_sltu(uint32_t instruction) +void exec_rv32i_sltu(uint32_t instruction) { uint32_t rd = RV32I_RD(instruction); uint32_t rs1 = RV32I_RS1(instruction); uint32_t rs2 = RV32I_RS2(instruction); rv32i_write_reg(rd, (uint32_t)(rv32i_read_reg(rs1) < rv32i_read_reg(rs2))); } -void exec_riscv32i_and(uint32_t instruction) +void exec_rv32i_and(uint32_t instruction) { uint32_t rd = RV32I_RD(instruction); uint32_t rs1 = RV32I_RS1(instruction); uint32_t rs2 = RV32I_RS2(instruction); rv32i_write_reg(rd, rv32i_read_reg(rs1) & rv32i_read_reg(rs2)); } -void exec_riscv32i_or(uint32_t instruction) +void exec_rv32i_or(uint32_t instruction) { uint32_t rd = RV32I_RD(instruction); uint32_t rs1 = RV32I_RS1(instruction); uint32_t rs2 = RV32I_RS2(instruction); rv32i_write_reg(rd, rv32i_read_reg(rs1) | rv32i_read_reg(rs2)); } -void exec_riscv32i_xor(uint32_t instruction) +void exec_rv32i_xor(uint32_t instruction) { uint32_t rd = RV32I_RD(instruction); uint32_t rs1 = RV32I_RS1(instruction); uint32_t rs2 = RV32I_RS2(instruction); rv32i_write_reg(rd, rv32i_read_reg(rs1) ^ rv32i_read_reg(rs2)); } -void exec_riscv32i_sll(uint32_t instruction) +void exec_rv32i_sll(uint32_t instruction) { uint32_t rd = RV32I_RD(instruction); uint32_t rs1 = RV32I_RS1(instruction); uint32_t rs2 = RV32I_RS2(instruction); rv32i_write_reg(rd, rv32i_read_reg(rs1) << (rv32i_read_reg(rs2) & RV32I_SHAMT_MASK)); } -void exec_riscv32i_srl_sra(uint32_t instruction) +void exec_rv32i_srl_sra(uint32_t instruction) { uint32_t rd = RV32I_RD(instruction); uint32_t rs1 = RV32I_RS1(instruction); @@ -229,7 +233,7 @@ void exec_riscv32i_srl_sra(uint32_t instruction) ^imm[20] (31) ^imm[11] (20) */ -void exec_riscv32i_jal(uint32_t instruction) +void exec_rv32i_jal(uint32_t instruction) { uint32_t rd = RV32I_RD(instruction); int32_t imm = RV32I_IMM_J(instruction); @@ -239,7 +243,7 @@ void exec_riscv32i_jal(uint32_t instruction) // Target = old_pc + imm => (pc - 4) + imm rv32i_write_reg(pc, (rv32i_read_reg(pc) - 4) + (uint32_t)imm); } -void exec_riscv32i_jalr(uint32_t instruction) +void exec_rv32i_jalr(uint32_t instruction) { uint32_t rd = RV32I_RD(instruction); uint32_t rs1 = RV32I_RS1(instruction); @@ -256,11 +260,11 @@ void exec_riscv32i_jalr(uint32_t instruction) +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | imm[10:5] | rs2 | rs1 |func3| |imm4:1 | opcode | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - 30 25 24 20 19 15 14 12 10 8 7 0 + 30 25 24 20 19 15 14 12 10 7 6 0 ^imm[12] (31) ^imm[11] (7) */ -void exec_riscv32i_beq(uint32_t instruction) +void exec_rv32i_beq(uint32_t instruction) { uint32_t rs1 = RV32I_RS1(instruction); uint32_t rs2 = RV32I_RS2(instruction); @@ -270,67 +274,64 @@ void exec_riscv32i_beq(uint32_t instruction) rv32i_write_reg(pc, (rv32i_read_reg(pc) - 4) + (uint32_t)imm); } else { // not taken: pc already points to next instruction + // same for all below } } -void exec_riscv32i_bne(uint32_t instruction) +void exec_rv32i_bne(uint32_t instruction) { uint32_t rs1 = RV32I_RS1(instruction); uint32_t rs2 = RV32I_RS2(instruction); int32_t imm = RV32I_IMM_B(instruction); if (rv32i_read_reg(rs1) != rv32i_read_reg(rs2)) { rv32i_write_reg(pc, (rv32i_read_reg(pc) - 4) + (uint32_t)imm); - } else { - // not taken } } -void exec_riscv32i_blt(uint32_t instruction) +void exec_rv32i_blt(uint32_t instruction) { uint32_t rs1 = RV32I_RS1(instruction); uint32_t rs2 = RV32I_RS2(instruction); int32_t imm = RV32I_IMM_B(instruction); if ((int32_t)rv32i_read_reg(rs1) < (int32_t)rv32i_read_reg(rs2)) { rv32i_write_reg(pc, (rv32i_read_reg(pc) - 4) + (uint32_t)imm); - } else { - // not taken } } -void exec_riscv32i_bge(uint32_t instruction) +void exec_rv32i_bge(uint32_t instruction) { uint32_t rs1 = RV32I_RS1(instruction); uint32_t rs2 = RV32I_RS2(instruction); int32_t imm = RV32I_IMM_B(instruction); if ((int32_t)rv32i_read_reg(rs1) >= (int32_t)rv32i_read_reg(rs2)) { rv32i_write_reg(pc, (rv32i_read_reg(pc) - 4) + (uint32_t)imm); - } else { - // not taken - } + } } -void exec_riscv32i_bltu(uint32_t instruction) +void exec_rv32i_bltu(uint32_t instruction) { uint32_t rs1 = RV32I_RS1(instruction); uint32_t rs2 = RV32I_RS2(instruction); int32_t imm = RV32I_IMM_B(instruction); if (rv32i_read_reg(rs1) < rv32i_read_reg(rs2)) { rv32i_write_reg(pc, (rv32i_read_reg(pc) - 4) + (uint32_t)imm); - } else { - // not taken } } -void exec_riscv32i_bgeu(uint32_t instruction) +void exec_rv32i_bgeu(uint32_t instruction) { uint32_t rs1 = RV32I_RS1(instruction); uint32_t rs2 = RV32I_RS2(instruction); int32_t imm = RV32I_IMM_B(instruction); if (rv32i_read_reg(rs1) >= rv32i_read_reg(rs2)) { rv32i_write_reg(pc, (rv32i_read_reg(pc) - 4) + (uint32_t)imm); - } else { - // not taken } } // remaining instructions -void exec_riscv32i_load(uint32_t instruction) +/* ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| (operation-specific) | opcode | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +31 7 6 0 +*/ +void exec_rv32i_load(uint32_t instruction) { uint32_t funct3 = RV32I_FUNCT3(instruction); uint32_t rd = RV32I_RD(instruction); @@ -355,12 +356,12 @@ void exec_riscv32i_load(uint32_t instruction) rv32i_write_reg(rd, (uint32_t)rv32i_mem_read16(addr)); break; default: - exec_riscv32i_bad_opcode(instruction); + exec_rv32i_bad_opcode(instruction); break; } } -void exec_riscv32i_store(uint32_t instruction) +void exec_rv32i_store(uint32_t instruction) { uint32_t funct3 = RV32I_FUNCT3(instruction); uint32_t rs1 = RV32I_RS1(instruction); @@ -380,7 +381,7 @@ void exec_riscv32i_store(uint32_t instruction) rv32i_mem_write32(addr, val); break; default: - exec_riscv32i_bad_opcode(instruction); + exec_rv32i_bad_opcode(instruction); break; } } @@ -396,9 +397,7 @@ void exec_riscv32i_store(uint32_t instruction) 31 20 19 15 14 12 11 7 6 0 */ - - -void exec_riscv32i_fence(uint32_t instruction) { (void)instruction; } +void exec_rv32i_fence(uint32_t instruction) { (void)instruction; } static inline uint8_t* rv32i_mem_ptr(uint32_t addr) { return &rv32i_mem[addr & (RV32I_MEM_SIZE - 1)]; @@ -414,109 +413,124 @@ static size_t rv32i_copy_cstr(uint32_t addr, char* dst, size_t maxlen) { return i; } + + + + // minimal Linux-like syscall emulation for user programs -static void rv32i_do_ecall(void) { +// refer to include/rv32_syscalls.h for wrappers +// TODO: improve the readability + +static void rv32i_syscall_read(void) { + int fd = (int)rv32i_reg[10]; + uint32_t buf = rv32i_reg[11]; + size_t cnt = (size_t)rv32i_reg[12]; + if (buf >= RV32I_MEM_SIZE) { rv32i_reg[10] = (uint32_t)(-EFAULT); return; } + size_t max = RV32I_MEM_SIZE - buf; + if (cnt > max) cnt = max; + ssize_t r = read(fd, rv32i_mem_ptr(buf), cnt); + rv32i_reg[10] = (r >= 0) ? (uint32_t)r : (uint32_t)(-errno); +} + +static void rv32i_syscall_write(void) { + int fd = (int)rv32i_reg[10]; + uint32_t buf = rv32i_reg[11]; + size_t cnt = (size_t)rv32i_reg[12]; + if (buf >= RV32I_MEM_SIZE) { rv32i_reg[10] = (uint32_t)(-EFAULT); return; } + size_t max = RV32I_MEM_SIZE - buf; + if (cnt > max) cnt = max; + ssize_t r = write(fd, rv32i_mem_ptr(buf), cnt); + rv32i_reg[10] = (r >= 0) ? (uint32_t)r : (uint32_t)(-errno); +} + +static void rv32i_syscall_openat(void) { + int dirfd = (int)rv32i_reg[10]; + uint32_t path = rv32i_reg[11]; + int flags = (int)rv32i_reg[12]; + mode_t mode = (mode_t)rv32i_reg[13]; + char tmp[4096]; + rv32i_copy_cstr(path, tmp, sizeof(tmp)); + int r = openat(dirfd, tmp, flags, mode); + rv32i_reg[10] = (r >= 0) ? (uint32_t)r : (uint32_t)(-errno); +} + +static void rv32i_syscall_close(void) { + int fd = (int)rv32i_reg[10]; + int r = close(fd); + rv32i_reg[10] = (r == 0) ? 0u : (uint32_t)(-errno); +} + +static void rv32i_syscall_lseek(void) { + int fd = (int)rv32i_reg[10]; + off_t off = (off_t)(int32_t)rv32i_reg[11]; + int wh = (int)rv32i_reg[12]; + off_t r = lseek(fd, off, wh); + rv32i_reg[10] = (r >= 0) ? (uint32_t)r : (uint32_t)(-errno); +} + +static void rv32i_syscall_exit(void) { + int code = (int)rv32i_reg[10]; + exit(code); +} + +static void rv32i_syscall_gettimeofday(void) { + uint32_t tv = rv32i_reg[10]; + struct timeval host_tv; + int r = gettimeofday(&host_tv, NULL); + if (r == 0) { + if (tv + 8 <= RV32I_MEM_SIZE) { + uint32_t sec = (uint32_t)host_tv.tv_sec; + uint32_t usec = (uint32_t)host_tv.tv_usec; + rv32i_mem_write32(tv, sec); + rv32i_mem_write32(tv + 4, usec); + rv32i_reg[10] = 0; + } else { + rv32i_reg[10] = (uint32_t)(-EFAULT); + } + } else { + rv32i_reg[10] = (uint32_t)(-errno); + } +} + +static void rv32i_syscall_brk(void) { + static uint32_t program_break = 0; + uint32_t addr = rv32i_reg[10]; + if (addr == 0) { + rv32i_reg[10] = program_break; + } else { + if (addr >= RV32I_MEM_SIZE) addr = RV32I_MEM_SIZE - 1; + program_break = addr; + rv32i_reg[10] = program_break; + } +} + +// main ecall handler +// non-static for test cases +/* +a7 (x17) contains the syscall number +a0 (x10) contains the return value +other arguments in a1 (x11), a2 (x12), a3 (x13), a4 (x14), a5 (x15) +*/ +void rv32i_do_ecall(void) { uint32_t num = rv32i_reg[17]; // a7 switch (num) { - case 63: { // read - int fd = (int)rv32i_reg[10]; // a0 - uint32_t buf = rv32i_reg[11]; // a1 - size_t cnt = (size_t)rv32i_reg[12]; // a2 - if (buf >= RV32I_MEM_SIZE) { rv32i_reg[10] = (uint32_t)(-EFAULT); return; } - size_t max = RV32I_MEM_SIZE - buf; - if (cnt > max) cnt = max; - ssize_t r = read(fd, rv32i_mem_ptr(buf), cnt); - rv32i_reg[10] = (r >= 0) ? (uint32_t)r : (uint32_t)(-errno); - break; - } - case 64: { // write - int fd = (int)rv32i_reg[10]; - uint32_t buf = rv32i_reg[11]; - size_t cnt = (size_t)rv32i_reg[12]; - if (buf >= RV32I_MEM_SIZE) { rv32i_reg[10] = (uint32_t)(-EFAULT); return; } - size_t max = RV32I_MEM_SIZE - buf; - if (cnt > max) cnt = max; - ssize_t r = write(fd, rv32i_mem_ptr(buf), cnt); - rv32i_reg[10] = (r >= 0) ? (uint32_t)r : (uint32_t)(-errno); - break; - } - case 56: { // openat(dirfd, path, flags, mode) - int dirfd = (int)rv32i_reg[10]; - uint32_t path = rv32i_reg[11]; - int flags = (int)rv32i_reg[12]; - mode_t mode = (mode_t)rv32i_reg[13]; - char tmp[4096]; - rv32i_copy_cstr(path, tmp, sizeof(tmp)); - int r = openat(dirfd, tmp, flags, mode); - rv32i_reg[10] = (r >= 0) ? (uint32_t)r : (uint32_t)(-errno); - break; - } - case 57: { // close(fd) - int fd = (int)rv32i_reg[10]; - int r = close(fd); - rv32i_reg[10] = (r == 0) ? 0u : (uint32_t)(-errno); - break; - } - case 62: { // lseek (simplified, 32-bit offset) - // a0 fd, a1 offset (low), a2 whence - int fd = (int)rv32i_reg[10]; - off_t off = (off_t)(int32_t)rv32i_reg[11]; - int wh = (int)rv32i_reg[12]; - off_t r = lseek(fd, off, wh); - rv32i_reg[10] = (r >= 0) ? (uint32_t)r : (uint32_t)(-errno); - break; - } - case 93: { // exit(code) - int code = (int)rv32i_reg[10]; - exit(code); - break; - } - case 94: { // exit_group(code) - int code = (int)rv32i_reg[10]; - exit(code); - break; - } - case 169: { // gettimeofday(tv, tz) - uint32_t tv = rv32i_reg[10]; - // tz is ignored - struct timeval host_tv; - int r = gettimeofday(&host_tv, NULL); - if (r == 0) { - if (tv + 8 <= RV32I_MEM_SIZE) { - // write 32-bit tv_sec and tv_usec - uint32_t sec = (uint32_t)host_tv.tv_sec; - uint32_t usec = (uint32_t)host_tv.tv_usec; - rv32i_mem_write32(tv, sec); - rv32i_mem_write32(tv + 4, usec); - rv32i_reg[10] = 0; - } else { - rv32i_reg[10] = (uint32_t)(-EFAULT); - } - } else { - rv32i_reg[10] = (uint32_t)(-errno); - } - break; - } - case 214: { // brk - static uint32_t program_break = 0; - uint32_t addr = rv32i_reg[10]; - if (addr == 0) { - rv32i_reg[10] = program_break; - } else { - if (addr >= RV32I_MEM_SIZE) addr = RV32I_MEM_SIZE - 1; - program_break = addr; - rv32i_reg[10] = program_break; - } - break; - } + case 63: rv32i_syscall_read(); break; + case 64: rv32i_syscall_write(); break; + case 56: rv32i_syscall_openat(); break; + case 57: rv32i_syscall_close(); break; + case 62: rv32i_syscall_lseek(); break; + case 93: rv32i_syscall_exit(); break; + case 94: rv32i_syscall_exit(); break; + case 169: rv32i_syscall_gettimeofday(); break; + case 214: rv32i_syscall_brk(); break; default: - fprintf(stderr, "[RISCV32I] Unimplemented ecall %u at PC=0x%08X\n", num, rv32i_reg[pc]); + fprintf(stderr, "Unimplemented ecall %u at PC=0x%08X\n", num, rv32i_reg[pc]); rv32i_reg[10] = (uint32_t)(-ENOSYS); break; } } -void exec_riscv32i_system(uint32_t instruction) +void exec_rv32i_system(uint32_t instruction) { uint32_t funct3 = RV32I_FUNCT3(instruction); switch (funct3) @@ -528,14 +542,15 @@ void exec_riscv32i_system(uint32_t instruction) rv32i_do_ecall(); } else if (imm == 1) { // EBREAK - fprintf(stderr, "[RISCV32I] EBREAK at PC=0x%08X\n", rv32i_reg[pc]); + fprintf(stderr, "EBREAK at PC=0x%08X\n", rv32i_reg[pc]); exit(1); } else { - exec_riscv32i_bad_opcode(instruction); + exec_rv32i_bad_opcode(instruction); } break; } /* + // ! NOT IMPLEMENTED IN THIS PoC case F3_SYS_CSRRW: case F3_SYS_CSRRS: case F3_SYS_CSRRC: @@ -544,22 +559,30 @@ void exec_riscv32i_system(uint32_t instruction) case F3_SYS_CSRRCI: */ default: - exec_riscv32i_bad_opcode(instruction); + exec_rv32i_bad_opcode(instruction); break; } } -void exec_riscv32i_bad_opcode(uint32_t instruction) { - fprintf(stderr, "[RISCV32I] Bad/illegal opcode 0x%08X at PC=0x%08X.\n", instruction, rv32i_reg[pc]); +void exec_rv32i_bad_opcode(uint32_t instruction) { + fprintf(stderr, "Bad/illegal opcode 0x%08X at PC=0x%08X.\n", instruction, rv32i_reg[pc]); exit(2); } +// final executor function +/* instr: ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| funct7 | |func3| | opcode | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +31 25 24 15 14 11 10 7 6 0 - -void exec_riscv32i(uint32_t instruction) +? we could consider replacing the switch with a jump table to +? speed up decoding & make it less recognisable as a VM... +*/ +void exec_rv32i(uint32_t instruction) { uint32_t opcode = RV32I_OP(instruction); switch (opcode) @@ -570,40 +593,40 @@ void exec_riscv32i(uint32_t instruction) switch (funct3) { case F3_IMM_ADDI: - exec_riscv32i_addi(instruction); + exec_rv32i_addi(instruction); break; case F3_IMM_SLTI: - exec_riscv32i_slti(instruction); + exec_rv32i_slti(instruction); break; case F3_IMM_SLTIU: - exec_riscv32i_sltiu(instruction); + exec_rv32i_sltiu(instruction); break; case F3_IMM_ANDI: - exec_riscv32i_andi(instruction); + exec_rv32i_andi(instruction); break; case F3_IMM_ORI: - exec_riscv32i_ori(instruction); + exec_rv32i_ori(instruction); break; case F3_IMM_XORI: - exec_riscv32i_xori(instruction); + exec_rv32i_xori(instruction); break; case F3_IMM_SLLI: - exec_riscv32i_slli(instruction); + exec_rv32i_slli(instruction); break; case F3_IMM_SRLI_SRAI: - exec_riscv32i_srli_srai(instruction); + exec_rv32i_srli_srai(instruction); break; default: - exec_riscv32i_bad_opcode(instruction); + exec_rv32i_bad_opcode(instruction); break; } } break; case OP_LUI: - exec_riscv32i_lui(instruction); + exec_rv32i_lui(instruction); break; case OP_AUIPC: - exec_riscv32i_auipc(instruction); + exec_rv32i_auipc(instruction); break; case OP_REG: { @@ -611,40 +634,40 @@ void exec_riscv32i(uint32_t instruction) switch (funct3) { case F3_REG_ADD_SUB: - exec_riscv32i_add_sub(instruction); + exec_rv32i_add_sub(instruction); break; case F3_REG_SLT: - exec_riscv32i_slt(instruction); + exec_rv32i_slt(instruction); break; case F3_REG_SLTU: - exec_riscv32i_sltu(instruction); + exec_rv32i_sltu(instruction); break; case F3_REG_AND: - exec_riscv32i_and(instruction); + exec_rv32i_and(instruction); break; case F3_REG_OR: - exec_riscv32i_or(instruction); + exec_rv32i_or(instruction); break; case F3_REG_XOR: - exec_riscv32i_xor(instruction); + exec_rv32i_xor(instruction); break; case F3_REG_SLL: - exec_riscv32i_sll(instruction); + exec_rv32i_sll(instruction); break; case F3_REG_SRL_SRA: - exec_riscv32i_srl_sra(instruction); + exec_rv32i_srl_sra(instruction); break; default: - exec_riscv32i_bad_opcode(instruction); + exec_rv32i_bad_opcode(instruction); break; } } break; case OP_JAL: - exec_riscv32i_jal(instruction); + exec_rv32i_jal(instruction); break; case OP_JALR: - exec_riscv32i_jalr(instruction); + exec_rv32i_jalr(instruction); break; case OP_BRANCH: { @@ -652,47 +675,46 @@ void exec_riscv32i(uint32_t instruction) switch (funct3) { case F3_BRANCH_BEQ: - exec_riscv32i_beq(instruction); + exec_rv32i_beq(instruction); break; case F3_BRANCH_BNE: - exec_riscv32i_bne(instruction); + exec_rv32i_bne(instruction); break; case F3_BRANCH_BLT: - exec_riscv32i_blt(instruction); + exec_rv32i_blt(instruction); break; case F3_BRANCH_BGE: - exec_riscv32i_bge(instruction); + exec_rv32i_bge(instruction); break; case F3_BRANCH_BLTU: - exec_riscv32i_bltu(instruction); + exec_rv32i_bltu(instruction); break; case F3_BRANCH_BGEU: - exec_riscv32i_bgeu(instruction); + exec_rv32i_bgeu(instruction); break; default: - exec_riscv32i_bad_opcode(instruction); + exec_rv32i_bad_opcode(instruction); break; } } break; case OP_LOAD: - exec_riscv32i_load(instruction); + exec_rv32i_load(instruction); break; case OP_STORE: - exec_riscv32i_store(instruction); + exec_rv32i_store(instruction); break; case OP_FENCE: - exec_riscv32i_fence(instruction); + exec_rv32i_fence(instruction); break; case OP_SYSTEM: - // Could further decode for ECALL/EBREAK/CSR, keep minimal - exec_riscv32i_system(instruction); + exec_rv32i_system(instruction); break; default: - exec_riscv32i_bad_opcode(instruction); + exec_rv32i_bad_opcode(instruction); break; } - // keep x0 clamped to zero regardless of any helper misuse + // keep x0 clamped to zero as per spec rv32i_reg[0] = 0; } \ No newline at end of file diff --git a/src/utils.c b/src/utils.c index b8ad03c..157b843 100644 --- a/src/utils.c +++ b/src/utils.c @@ -58,31 +58,26 @@ void handle_interrupt(int signal) // little-endian word read/write on byte-addressable memory -static inline uint32_t load32(uint32_t addr) { - uint32_t a = addr & (RV32I_MEM_SIZE - 1); - return (uint32_t)rv32i_mem[a] - | ((uint32_t)rv32i_mem[(a + 1) & (RV32I_MEM_SIZE - 1)] << 8) - | ((uint32_t)rv32i_mem[(a + 2) & (RV32I_MEM_SIZE - 1)] << 16) - | ((uint32_t)rv32i_mem[(a + 3) & (RV32I_MEM_SIZE - 1)] << 24); -} -static inline void store32(uint32_t addr, uint32_t val) { +void mem_write(uint32_t addr, uint32_t val) +{ uint32_t a = addr & (RV32I_MEM_SIZE - 1); rv32i_mem[a] = (uint8_t)(val & 0xFF); rv32i_mem[(a + 1) & (RV32I_MEM_SIZE - 1)] = (uint8_t)((val >> 8) & 0xFF); rv32i_mem[(a + 2) & (RV32I_MEM_SIZE - 1)] = (uint8_t)((val >> 16) & 0xFF); rv32i_mem[(a + 3) & (RV32I_MEM_SIZE - 1)] = (uint8_t)((val >> 24) & 0xFF); } - -void mem_write(uint32_t address, uint32_t val) { store32(address, val); } -uint32_t mem_read(uint32_t address) { return load32(address); } +uint32_t mem_read(uint32_t addr) { + uint32_t a = addr & (RV32I_MEM_SIZE - 1); + return (uint32_t)rv32i_mem[a] + | ((uint32_t)rv32i_mem[(a + 1) & (RV32I_MEM_SIZE - 1)] << 8) + | ((uint32_t)rv32i_mem[(a + 2) & (RV32I_MEM_SIZE - 1)] << 16) + | ((uint32_t)rv32i_mem[(a + 3) & (RV32I_MEM_SIZE - 1)] << 24); +} static inline uint32_t be32_to_host(uint32_t x) { return ((x & 0x000000FFu) << 24) | ((x & 0x0000FF00u) << 8) | ((x & 0x00FF0000u) >> 8) | ((x & 0xFF000000u) >> 24); } -static inline uint32_t be16_to_host(uint32_t x) { - return ((x & 0x00FFu) << 8) | ((x & 0xFF00u) >> 8); -} void read_image_file(FILE* file) { diff --git a/test/test_riscv32i.c b/test/test_riscv32i.c new file mode 100644 index 0000000..48835ad --- /dev/null +++ b/test/test_riscv32i.c @@ -0,0 +1,43 @@ +// test_riscv32i.c + +#include +#include +#include +#include +#include "riscv32i.h" + +static void test_reg_helpers() { + rv32i_reg[x1] = 0xFADE; + rv32i_write_reg(x2, 0xCAFE); + rv32i_write_reg(x0, 0xDEAD); // x0 must stay zero + assert(rv32i_read_reg(x1) == 0xFADE); + assert(rv32i_reg[x2] == 0xCAFE); + assert(rv32i_read_reg(x0) == 0); +} + +static void test_imm_macros() { + uint32_t i = 0x00F50613; // addi x12, x10, 0xF5 + assert(RV32I_RD(i) == 12); + assert(RV32I_RS1(i) == 10); + int32_t imm = RV32I_IMM_I(i); + assert(imm == 15); +} + +static void test_syscall_brk() { + rv32i_reg[10] = 0x1000; + rv32i_reg[17] = 214; + extern void rv32i_do_ecall(void); + rv32i_do_ecall(); + assert(rv32i_reg[10] == 0x1000); + rv32i_reg[10] = 0; + rv32i_reg[17] = 214; + rv32i_do_ecall(); + assert(rv32i_reg[10] == 0x1000); // returns last break +} + +int main(void) { + test_reg_helpers(); + test_imm_macros(); + test_syscall_brk(); + return 0; +} diff --git a/test/test_utils.c b/test/test_utils.c index 850f564..b69e09a 100644 --- a/test/test_utils.c +++ b/test/test_utils.c @@ -44,37 +44,8 @@ static void test_read_image_buffer_basic() { assert(rv32i_mem[0x3007] == 0xDD); } - -/* -static void test_read_image_file() { - // construct a tiny big-endian image: origin + 3 words - FILE* f = tmpfile(); - assert(f != NULL); - - uint32_t origin = 0x3000; - uint32_t origin_be = swap16(origin); - size_t w = fwrite(&origin_be, sizeof(origin_be), 1, f); - assert(w == 1); - - const uint32_t data[3] = { 0x1122, 0x3344, 0xABCD }; - uint32_t data_be[3] = { swap32(data[0]), swap32(data[1]), swap32(data[2]) }; - w = fwrite(data_be, sizeof(uint32_t), 3, f); - assert(w == 3); - - int r = fseek(f, 0, SEEK_SET); - assert(r == 0); - - read_image_file(f); - fclose(f); - - assert(rv32i_mem[origin + 0] == data[0]); - assert(rv32i_mem[origin + 1] == data[1]); - assert(rv32i_mem[origin + 2] == data[2]); -}*/ - int main(void) { test_mem_rw_basic(); test_read_image_buffer_basic(); - // test_read_image_file(); return 0; } \ No newline at end of file