From 40b4d52e905227849785f6520d0381f19657820a Mon Sep 17 00:00:00 2001 From: Steven Le Rouzic Date: Sun, 12 May 2024 11:24:16 +0200 Subject: Reorganize all this shit --- build.bat | 5 +- compile_flags.txt | 2 + emulator/elf.c | 133 +++++++++ emulator/elf.h | 6 + emulator/hart.c | 446 ++++++++++++++++++++++++++++ emulator/hart.h | 16 + emulator/hart_test.c | 251 ++++++++++++++++ emulator/main.c | 817 +-------------------------------------------------- 8 files changed, 864 insertions(+), 812 deletions(-) create mode 100644 compile_flags.txt create mode 100644 emulator/elf.c create mode 100644 emulator/elf.h create mode 100644 emulator/hart.c create mode 100644 emulator/hart.h create mode 100644 emulator/hart_test.c diff --git a/build.bat b/build.bat index f115f95..c9da4e4 100644 --- a/build.bat +++ b/build.bat @@ -1,6 +1,5 @@ -clang emulator/main.c -o emulator.exe -std=c11 -D_CRT_SECURE_NO_WARNINGS -REM clang linker/main.c -o linker.exe -std=c11 -D_CRT_SECURE_NO_WARNINGS +clang emulator/main.c -o emulator.exe -std=c11 -D_CRT_SECURE_NO_WARNINGS -I. +clang emulator/hart_test.c -o hart_test.exe -std=c11 -D_CRT_SECURE_NO_WARNINGS -I. clang --target=riscv32 -march=rv32i -e _main -nostdlib main.asm -o main.bin - diff --git a/compile_flags.txt b/compile_flags.txt new file mode 100644 index 0000000..e21b03c --- /dev/null +++ b/compile_flags.txt @@ -0,0 +1,2 @@ +-I. +-D_CRT_SECURE_NO_WARNINGS diff --git a/emulator/elf.c b/emulator/elf.c new file mode 100644 index 0000000..43409cc --- /dev/null +++ b/emulator/elf.c @@ -0,0 +1,133 @@ +#include +#include +#include +#include +#include +#include + +#define ET_NONE 0x00 +#define ET_REL 0x01 +#define ET_EXEC 0x02 +#define ET_DYN 0x03 +#define ET_CORE 0x04 + +struct Elf32Header +{ + uint8_t ident[16]; + uint16_t type; + uint16_t machine; + uint32_t version; + uint32_t entry; + uint32_t phoff; + uint32_t shoff; + uint32_t flags; + uint16_t ehsize; + uint16_t phentsize; + uint16_t phnum; + uint16_t shentsize; + uint16_t shnum; + uint16_t shstrndx; +}; + +#define PT_NULL 0x00000000 +#define PT_LOAD 0x00000001 +#define PT_DYNAMIC 0x00000002 +#define PT_INTERP 0x00000003 +#define PT_NOTE 0x00000004 +#define PT_SHLIB 0x00000005 +#define PT_PHDR 0x00000006 +#define PT_TLS 0x00000007 + +#define PF_X 0x1 +#define PF_W 0x2 +#define PF_R 0x4 + +struct Elf32ProgramHeader +{ + uint32_t type; + uint32_t offset; + uint32_t vaddr; + uint32_t paddr; + uint32_t filesz; + uint32_t memsz; + uint32_t flags; + uint32_t align; +}; + +bool load_elf(const char* file, char* mem, uint32_t mem_size, uint32_t* start_address) +{ + FILE* fp = fopen(file, "rb"); + if (fp == NULL) + { + printf("Couldn't open input file\n"); + return false; + } + + struct Elf32Header header; + size_t read = fread(&header, sizeof(struct Elf32Header), 1, fp); + if (read != 1) + { + printf("ELF header too small\n"); + return false; + } + + static uint8_t kValidIdent[7] = { + 0x7f, 0x45, 0x4c, 0x46, + 1, 1, 1 + }; + if (memcmp(kValidIdent, header.ident, 7) != 0) + { + printf("Invalid ELF header\n"); + return false; + } + + if (header.type != ET_EXEC) + { + printf("Can only link EXEC ELF files\n"); + return false; + } + + memset(mem, 0, mem_size); + + assert(header.phnum == 0 || header.phentsize == sizeof(struct Elf32ProgramHeader)); + for (uint32_t i = 0; i < header.phnum; ++i) + { + struct Elf32ProgramHeader pheader; + + fseek(fp, header.phoff + i * header.phentsize, SEEK_SET); + read = fread(&pheader, sizeof(struct Elf32ProgramHeader), 1, fp); + if (read != 1) + { + printf("Error reading program header at index %u\n", i); + return false; + } + + if (pheader.type == PT_LOAD) + { + if (pheader.paddr + pheader.memsz > mem_size) + { + printf("Memory not large enough for ELF file\n"); + return false; + } + + fseek(fp, pheader.offset, SEEK_SET); + read = fread(mem + pheader.paddr, pheader.filesz, 1, fp); + if (read != 1) + { + printf("Error when copying ELF segment\n"); + return false; + } + } + } + + if (header.entry > mem_size) + { + printf("Entry address out of bounds\n"); + return false; + } + + *start_address = header.entry; + fclose(fp); + + return true; +} diff --git a/emulator/elf.h b/emulator/elf.h new file mode 100644 index 0000000..2ba3739 --- /dev/null +++ b/emulator/elf.h @@ -0,0 +1,6 @@ +#pragma once + +#include +#include + +bool load_elf(const char* file, char* mem, uint32_t mem_size, uint32_t* start_address); diff --git a/emulator/hart.c b/emulator/hart.c new file mode 100644 index 0000000..cfca837 --- /dev/null +++ b/emulator/hart.c @@ -0,0 +1,446 @@ +#include "emulator/hart.h" + +#include +#include +#include +#include +#include + +static inline uint32_t sign_extend(uint32_t word, uint32_t size) +{ + const uint32_t mask = 1U << (size - 1); + return (word ^ mask) - mask; +} + +struct Instruction +{ + uint8_t opcode; + uint8_t rs1; + uint8_t rs2; + uint8_t rd; + uint8_t funct3; + uint8_t funct7; + uint32_t imm; +}; +typedef struct Instruction Instruction; + +static Instruction decode_r_type(uint32_t word) +{ + Instruction instruction = {0}; + instruction.opcode = word & 0x7F; + instruction.rd = (word >> 7) & 0x1F; + instruction.funct3 = (word >> 12) & 0x07; + instruction.rs1 = (word >> 15) & 0x1F; + instruction.rs2 = (word >> 20) & 0x1F; + instruction.funct7 = word >> 25; + return instruction; +}; + +static Instruction decode_i_type(uint32_t word) +{ + Instruction instruction = {0}; + instruction.opcode = word & 0x7F; + instruction.rd = (word >> 7) & 0x1F; + instruction.funct3 = (word >> 12) & 0x07; + instruction.rs1 = (word >> 15) & 0x1F; + instruction.imm = sign_extend(word >> 20, 12); + return instruction; +}; + +static Instruction decode_s_type(uint32_t word) +{ + Instruction instruction = {0}; + instruction.opcode = word & 0x7F; + instruction.funct3 = (word >> 12) & 0x07; + instruction.rs1 = (word >> 15) & 0x1F; + instruction.rs2 = (word >> 20) & 0x1F; + instruction.imm = sign_extend(((word >> 7) & 0x1F) | (word >> 25), 12); + return instruction; +}; + +static Instruction decode_b_type(uint32_t word) +{ + Instruction instruction = decode_s_type(word); + instruction.imm = ((instruction.imm << 11) & 0x800) | (instruction.imm & 0xfffff7ff); + return instruction; +}; + +static Instruction decode_u_type(uint32_t word) +{ + Instruction instruction = {0}; + instruction.opcode = word & 0x7F; + instruction.rd = (word >> 7) & 0x1F; + instruction.imm = word & 0xFFFFF000; + return instruction; +}; + +static Instruction decode_j_type(uint32_t word) +{ + Instruction instruction = {0}; + instruction.opcode = word & 0x7F; + instruction.rd = (word >> 7) & 0x1F; + instruction.imm = sign_extend( + ((word & 0x80000000) >> 11) | + ((word & 0x000FF000) >> 0) | + ((word & 0x00100000) >> 9) | + ((word & 0x7FE00000) >> 20), 21); + return instruction; +} + +static void execute_op_imm(Hart* hart, uint32_t instruction) +{ + const Instruction inst = decode_i_type(instruction); + if (inst.rd == 0) return; + + switch (inst.funct3) + { + case 0: // ADDI + hart->regs[inst.rd] = hart->regs[inst.rs1] + inst.imm; + break; + case 1: // SLLI + hart->regs[inst.rd] = hart->regs[inst.rs1] << (inst.imm & 0x1F); + break; + case 2: // SLTI + hart->regs[inst.rd] = (int32_t)hart->regs[inst.rs1] < (int32_t)inst.imm ? 1 : 0; + break; + case 3: // SLTIU + hart->regs[inst.rd] = hart->regs[inst.rs1] < inst.imm ? 1 : 0; + break; + case 4: // XORI + hart->regs[inst.rd] = hart->regs[inst.rs1] ^ inst.imm; + break; + case 5: // SRLI, SRAI + { + const uint32_t shamt = inst.imm & 0x1F; + uint32_t res = hart->regs[inst.rs1] >> shamt; + if ((inst.imm & 0x400) && shamt > 0) { res = sign_extend(res, 32 - shamt); } + hart->regs[inst.rd] = res; + break; + } + case 6: // ORI + hart->regs[inst.rd] = hart->regs[inst.rs1] | inst.imm; + break; + case 7: // ANDI + hart->regs[inst.rd] = hart->regs[inst.rs1] & inst.imm; + break; + default: + assert(!"Unhandled OP-IMM"); + } +} + +static void execute_op(Hart* hart, uint32_t instruction) +{ + const Instruction inst = decode_r_type(instruction); + if (inst.rd == 0) return; + + switch (inst.funct3) + { + case 0: // ADD, SUB + if (instruction & 0x40000000) + { + hart->regs[inst.rd] = hart->regs[inst.rs1] - hart->regs[inst.rs2]; + } + else + { + hart->regs[inst.rd] = hart->regs[inst.rs1] + hart->regs[inst.rs2]; + } + break; + case 1: // SLL + hart->regs[inst.rd] = hart->regs[inst.rs1] << (hart->regs[inst.rs2] & 0x1F); + break; + case 2: // SLT + hart->regs[inst.rd] = (int32_t)hart->regs[inst.rs1] < (int32_t)hart->regs[inst.rs2] ? 1 : 0; + break; + case 3: // SLTU + hart->regs[inst.rd] = hart->regs[inst.rs1] < hart->regs[inst.rs2] ? 1 : 0; + break; + case 4: // XOR + hart->regs[inst.rd] = hart->regs[inst.rs1] ^ hart->regs[inst.rs2]; + break; + case 5: // SRL, SRA + { + const uint32_t shamt = hart->regs[inst.rs2] & 0x1F; + uint32_t res = hart->regs[inst.rs1] >> shamt; + if ((instruction & 0x40000000) && shamt > 0) { res = sign_extend(res, 32 - shamt); } + hart->regs[inst.rd] = res; + break; + } + case 6: // OR + hart->regs[inst.rd] = hart->regs[inst.rs1] | hart->regs[inst.rs2]; + break; + case 7: // AND + hart->regs[inst.rd] = hart->regs[inst.rs1] & hart->regs[inst.rs2]; + break; + default: + assert(!"Unhandled OP-IMM"); + } +} + +static void execute_branch(Hart* hart, uint32_t instruction) +{ + const Instruction inst = decode_b_type(instruction); + const uint32_t r1 = hart->regs[inst.rs1]; + const uint32_t r2 = hart->regs[inst.rs2]; + bool take_branch = false; + + switch (inst.funct3) + { + case 0: take_branch = (r1 == r2); break; + case 1: take_branch = (r1 != r2); break; + case 4: take_branch = (r1 < r2); break; + case 5: take_branch = (r1 >= r2); break; + case 6: take_branch = ((int32_t)r1 < (int32_t)r2); break; + case 7: take_branch = ((int32_t)r1 >= (int32_t)r2); break; + } + + if (take_branch) + { + hart->pc += inst.imm; + } + else + { + hart->pc += 4; + } +} + +static inline uint32_t load_size(Hart* hart, uint32_t address, uint32_t size) +{ + if ((address & 0x80000000) == 0) + { + assert(address + size < hart->mem_size); + uint32_t value = 0; + memcpy(&value, hart->mem + address, size); + return value; + } + + return 0; +} + +static uint32_t load_byte(Hart* hart, uint32_t address) +{ + return load_size(hart, address, 1); +} + +static uint32_t load_half(Hart* hart, uint32_t address) +{ + return load_size(hart, address, 2); +} + +static uint32_t load_word(Hart* hart, uint32_t address) +{ + return load_size(hart, address, 4); +} + +static inline void store_size(Hart* hart, uint32_t address, uint32_t value, uint32_t size) +{ + if ((address & 0x80000000) == 0) + { + assert(address + size < hart->mem_size); + memcpy(hart->mem + address, &value, size); + } + else if (address == 0x80000000) + { + fwrite(&value, 1, size, stdout); + } +} + +static void store_byte(Hart* hart, uint32_t address, uint8_t value) +{ + store_size(hart, address, value, 1); +} + +static void store_half(Hart* hart, uint32_t address, uint16_t value) +{ + store_size(hart, address, value, 2); +} + +static void store_word(Hart* hart, uint32_t address, uint32_t value) +{ + store_size(hart, address, value, 4); +} + +static void execute_op_load(Hart* hart, uint32_t instruction) +{ + const Instruction inst = decode_i_type(instruction); + const uint32_t address = hart->regs[inst.rs1] + inst.imm; + + uint32_t value = 0; + + switch (inst.funct3 & 0x03) + { + case 0: + value = load_byte(hart, address); + if ((inst.funct3 & 0x40) == 0) + { + value = sign_extend(value, 8); + } + break; + case 1: + value = load_half(hart, address); + if ((inst.funct3 & 0x40) == 0) + { + value = sign_extend(value, 16); + } + break; + case 2: + value = load_byte(hart, address); + break; + default: + assert(!"Unhandled load size"); + break; + } + + if (inst.rd != 0) + { + hart->regs[inst.rd] = value; + } +} + +static void execute_op_store(Hart* hart, uint32_t instruction) +{ + const Instruction inst = decode_s_type(instruction); + const uint32_t address = hart->regs[inst.rs1] + inst.imm; + const uint32_t value = hart->regs[inst.rs2]; + + switch (inst.funct3 & 0x03) + { + case 0: store_byte(hart, address, value & 0xFF); break; + case 1: store_half(hart, address, value & 0xFFFF); break; + case 2: store_word(hart, address, value); break; + default: + assert(!"Unhandled store size"); + break; + } +} + +static void execute_misc_mem(Hart* hart, uint32_t instruction) +{ + const Instruction inst = decode_i_type(instruction); + + if (inst.funct3 == 0) + { + // FENCE + } + else + { + assert(!"Unhandled MISC-MEM instruction"); + } +} + +static void execute_system(Hart* hart, uint32_t instruction) +{ + const Instruction inst = decode_i_type(instruction); + + if (inst.funct3 == 0 && inst.rs1 == 0 && inst.rd == 0) + { + if (inst.imm == 0) + { + // ECALL + } + else if (inst.imm == 1) + { + // EBREAK + } + else + { + assert(!"Unhandled SYSTEM/PRIV instruction"); + } + } + else + { + assert(!"Unhandled SYSTEM instruction"); + } +} + +void execute(Hart* hart, uint32_t instruction) +{ + switch (instruction & 0x7f) + { + case 0x03: + execute_op_load(hart, instruction); + hart->pc += 4; + break; + case 0x0F: + execute_misc_mem(hart, instruction); + hart->pc += 4; + break; + case 0x13: + execute_op_imm(hart, instruction); + hart->pc += 4; + break; + case 0x17: // AUIPC + { + Instruction inst = decode_u_type(instruction); + if (inst.rd != 0) + { + hart->regs[inst.rd] = inst.imm + hart->pc; + } + hart->pc += 4; + break; + } + case 0x23: + execute_op_store(hart, instruction); + hart->pc += 4; + break; + case 0x33: + execute_op(hart, instruction); + hart->pc += 4; + break; + case 0x37: // LUI + { + Instruction inst = decode_u_type(instruction); + if (inst.rd != 0) + { + hart->regs[inst.rd] = inst.imm; + } + hart->pc += 4; + break; + } + case 0x63: + execute_branch(hart, instruction); + break; + case 0x67: // JALR + { + Instruction inst = decode_i_type(instruction); + assert(inst.funct3 == 0); + if (inst.rd != 0) + { + hart->regs[inst.rd] = hart->pc + 4; + } + hart->pc = (hart->regs[inst.rs1] + inst.imm) & 0xFFFFFFFE; + break; + } + case 0x6F: // JAL + { + Instruction inst = decode_j_type(instruction); + if (inst.rd != 0) + { + hart->regs[inst.rd] = hart->pc + 4; + } + hart->pc += inst.imm; + break; + } + case 0x73: + { + execute_system(hart, instruction); + hart->pc += 4; + break; + } + default: + assert(!"Unhandled opcode"); + } + + assert(hart->regs[0] == 0); +} + +void execute_from(Hart* hart, uint32_t start_address) +{ + hart->pc = start_address; + + while (true) + { + uint32_t instruction = load_word(hart, hart->pc); + execute(hart, instruction); + } +} + diff --git a/emulator/hart.h b/emulator/hart.h new file mode 100644 index 0000000..5e9d451 --- /dev/null +++ b/emulator/hart.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +struct Hart +{ + uint32_t pc; + uint32_t regs[32]; + char* mem; + uint32_t mem_size; +}; +typedef struct Hart Hart; + +void execute(Hart* hart, uint32_t instruction); +void execute_from(Hart* hart, uint32_t start_address); + diff --git a/emulator/hart_test.c b/emulator/hart_test.c new file mode 100644 index 0000000..5509dab --- /dev/null +++ b/emulator/hart_test.c @@ -0,0 +1,251 @@ +#include +#include +#include +#include + +#include "emulator/hart.h" + +static void test_addi() +{ + struct Hart hart = {0}; + + execute(&hart, 0x00500093); // addi x1, x0, 5 + assert(hart.regs[1] == 5); + + execute(&hart, 0xffe00093); // addi, x1, x0, -2 + assert(hart.regs[1] == 0xfffffffe); +} + +static void test_slti_sltiu() +{ + struct Hart hart = {0}; + + hart.regs[1] = 5; + + execute(&hart, 0x00f0b113); // sltiu x2, x1, 15 + assert(hart.regs[2] == 1); + + execute(&hart, 0x0050b113); // sltiu x2, x1, 15 + assert(hart.regs[2] == 0); + + execute(&hart, 0x0010b113); // sltiu x2, x1, 15 + assert(hart.regs[2] == 0); + + execute(&hart, 0x00f0a113); // slti x2, x1, 15 + assert(hart.regs[2] == 1); + + execute(&hart, 0x0050a113); // slti x2, x1, 15 + assert(hart.regs[2] == 0); + + execute(&hart, 0x0010a113); // slti x2, x1, 15 + assert(hart.regs[2] == 0); + + execute(&hart, 0xffb0a113); // slti x2, x1, -5 + assert(hart.regs[2] == 0); + + hart.regs[1] = (uint32_t)-20; + + execute(&hart, 0xffb0a113); // slti x2, x1, -5 + assert(hart.regs[2] == 1); +} + +static void test_andi_ori_xori() +{ + struct Hart hart = {0}; + + hart.regs[1] = 6; + + execute(&hart, 0x00c0c113); // xori x2, x1, 12 + assert(hart.regs[2] == 10); + + execute(&hart, 0x00c0e113); // ori x2, x1, 12 + assert(hart.regs[2] == 14); + + execute(&hart, 0x00c0f113); // andi x2, x1, 12 + assert(hart.regs[2] == 4); +} + +static void test_slli_srli_srai() +{ + struct Hart hart = {0}; + + hart.regs[1] = 6; + + execute(&hart, 0x00209113); // slli x2, x1, 2 + assert(hart.regs[2] == 24); + + execute(&hart, 0x0020d113); // srli x2, x1, 2 + assert(hart.regs[2] == 1); + + execute(&hart, 0x4020d113); // srai x2, x1, 2 + assert(hart.regs[2] == 1); + + hart.regs[1] = (uint32_t)-6; + + execute(&hart, 0x0020d113); // srli x2, x1, 2 + assert(hart.regs[2] == 0x3FFFFFFE); + + execute(&hart, 0x4020d113); // srai x2, x1, 2 + assert(hart.regs[2] == 0xFFFFFFFE); +} + +static void test_lui_auipc() +{ + struct Hart hart = {0}; + + hart.pc = 0; + execute(&hart, 0x0007b0b7); // lui x1, 503808 + assert(hart.regs[1] == 503808); + + hart.pc = 0; + execute(&hart, 0x0007b097); // auipc x1, 503808 + assert(hart.regs[1] == 503808); + + hart.pc = 12; + execute(&hart, 0x0007b097); // auipc x1, 503808 + assert(hart.regs[1] == 503820); +} + +static void test_op() +{ + struct Hart hart = {0}; + + hart.regs[1] = 3; + hart.regs[2] = 4; + hart.regs[4] = (uint32_t)-1; + + execute(&hart, 0x002081b3); // add, x3, x1, x2 + assert(hart.regs[3] == 7); + + execute(&hart, 0x402081b3); // sub x3, x1, x2 + assert(hart.regs[3] == 0xFFFFFFFF); + + execute(&hart, 0x0020a1b3); // slt x3, x1, x2 + assert(hart.regs[3] == 1); + + execute(&hart, 0x001121b3); // slt x3, x2, 1 + assert(hart.regs[3] == 0); + + execute(&hart, 0x001131b3); // sltu x3, x2, x1 + assert(hart.regs[3] == 0); + + execute(&hart, 0x0020b1b3); // sltu x3, x1, x2 + assert(hart.regs[3] == 1); + + execute(&hart, 0x0040a1b3); // slt x3, x1, x4 + assert(hart.regs[3] == 0); + + hart.regs[1] = 6; + hart.regs[2] = 12; + + execute(&hart, 0x0020e1b3); // or x3, x1, x2 + assert(hart.regs[3] == 14); + + execute(&hart, 0x0020c1b3); // xor x3, x1, x2 + assert(hart.regs[3] == 10); + + execute(&hart, 0x0020f1b3); // and x3, x1, x2 + assert(hart.regs[3] == 4); + + hart.regs[1] = 6; + hart.regs[2] = 2; + + execute(&hart, 0x002091b3); // sll x3, x1, x2 + assert(hart.regs[3] == 24); + + execute(&hart, 0x0020d1b3); // srl x3, x1, x2 + assert(hart.regs[3] == 1); + + execute(&hart, 0x4020d1b3); // sra x3, x1, x2 + assert(hart.regs[3] == 1); + + hart.regs[1] = (uint32_t)-6; + hart.regs[2] = 2; + + execute(&hart, 0x4020d1b3); // sra x3, x1, x2 + assert(hart.regs[3] == 0xFFFFFFFE); +} + +static void test_jal() +{ + struct Hart hart = {0}; + + hart.pc = 12; + + execute(&hart, 0x12c000ef); // jal x1, 300 + assert(hart.regs[1] == 16); + assert(hart.pc == 312); + + execute(&hart, 0xed5ff0ef); // jal x1, -300 + assert(hart.regs[1] == 316); + assert(hart.pc == 12); +} + +static void test_jalr() +{ + struct Hart hart = {0}; + + hart.pc = 12; + hart.regs[1] = 300; + + execute(&hart, 0x00a08167); // jalr x2, 10(x1) + assert(hart.regs[2] == 16); + assert(hart.pc == 310); + + execute(&hart, 0xff608167); // jalr x2, -10(x1) + assert(hart.regs[2] == 314); + assert(hart.pc == 290); +} + +static void test_branch() +{ + struct Hart hart = {0}; + + hart.pc = 100; + hart.regs[1] = 2; + hart.regs[2] = 0xFFFFFFFC; + + execute(&hart, 0x00208c63); // beq x1, x2, 24 + assert(hart.pc == 104); + + hart.pc = 100; + execute(&hart, 0x00209c63); // bne x1, x2, 24 + assert(hart.pc == 124); + + hart.pc = 100; + execute(&hart, 0x0020cc63); // blt x1, x2, 24 + assert(hart.pc == 124); + + hart.pc = 100; + execute(&hart, 0x0020dc63); // bge x1, x2, 24 + assert(hart.pc == 104); + + hart.pc = 100; + execute(&hart, 0x0020ec63); // bltu x1, x2, 24 + assert(hart.pc == 104); + + hart.pc = 100; + execute(&hart, 0x0020fc63); // bgeu x1, x2, 24 + assert(hart.pc == 124); +} + +void test() +{ + test_addi(); + test_slti_sltiu(); + test_andi_ori_xori(); + test_slli_srli_srai(); + test_lui_auipc(); + test_op(); + test_jal(); + test_jalr(); + test_branch(); +} + +int main(int argc, char* argv[]) +{ + test(); + return EXIT_SUCCESS; +} + +#include "emulator/hart.c" diff --git a/emulator/main.c b/emulator/main.c index ed875a3..a00a717 100644 --- a/emulator/main.c +++ b/emulator/main.c @@ -1,812 +1,12 @@ #include #include #include -#include -#include -#include -#define ET_NONE 0x00 -#define ET_REL 0x01 -#define ET_EXEC 0x02 -#define ET_DYN 0x03 -#define ET_CORE 0x04 - -struct Elf32Header -{ - uint8_t ident[16]; - uint16_t type; - uint16_t machine; - uint32_t version; - uint32_t entry; - uint32_t phoff; - uint32_t shoff; - uint32_t flags; - uint16_t ehsize; - uint16_t phentsize; - uint16_t phnum; - uint16_t shentsize; - uint16_t shnum; - uint16_t shstrndx; -}; - -#define PT_NULL 0x00000000 -#define PT_LOAD 0x00000001 -#define PT_DYNAMIC 0x00000002 -#define PT_INTERP 0x00000003 -#define PT_NOTE 0x00000004 -#define PT_SHLIB 0x00000005 -#define PT_PHDR 0x00000006 -#define PT_TLS 0x00000007 - -#define PF_X 0x1 -#define PF_W 0x2 -#define PF_R 0x4 - -struct Elf32ProgramHeader -{ - uint32_t type; - uint32_t offset; - uint32_t vaddr; - uint32_t paddr; - uint32_t filesz; - uint32_t memsz; - uint32_t flags; - uint32_t align; -}; - -bool load_elf(const char* file, char* mem, uint32_t mem_size, uint32_t* start_address) -{ - FILE* fp = fopen(file, "rb"); - if (fp == NULL) - { - printf("Couldn't open input file\n"); - return false; - } - - struct Elf32Header header; - size_t read = fread(&header, sizeof(struct Elf32Header), 1, fp); - if (read != 1) - { - printf("ELF header too small\n"); - return false; - } - - static uint8_t kValidIdent[7] = { - 0x7f, 0x45, 0x4c, 0x46, - 1, 1, 1 - }; - if (memcmp(kValidIdent, header.ident, 7) != 0) - { - printf("Invalid ELF header\n"); - return false; - } - - if (header.type != ET_EXEC) - { - printf("Can only link EXEC ELF files\n"); - return false; - } - - memset(mem, 0, mem_size); - - assert(header.phnum == 0 || header.phentsize == sizeof(struct Elf32ProgramHeader)); - for (uint32_t i = 0; i < header.phnum; ++i) - { - struct Elf32ProgramHeader pheader; - - fseek(fp, header.phoff + i * header.phentsize, SEEK_SET); - read = fread(&pheader, sizeof(struct Elf32ProgramHeader), 1, fp); - if (read != 1) - { - printf("Error reading program header at index %u\n", i); - return false; - } - - if (pheader.type == PT_LOAD) - { - if (pheader.paddr + pheader.memsz > mem_size) - { - printf("Memory not large enough for ELF file\n"); - return false; - } - - fseek(fp, pheader.offset, SEEK_SET); - read = fread(mem + pheader.paddr, pheader.filesz, 1, fp); - if (read != 1) - { - printf("Error when copying ELF segment\n"); - return false; - } - } - } - - if (header.entry > mem_size) - { - printf("Entry address out of bounds\n"); - return false; - } - - *start_address = header.entry; - fclose(fp); - - return true; -} - -inline uint32_t sign_extend(uint32_t word, uint32_t size) -{ - const uint32_t mask = 1U << (size - 1); - return (word ^ mask) - mask; -} - -struct Instruction -{ - uint8_t opcode; - uint8_t rs1; - uint8_t rs2; - uint8_t rd; - uint8_t funct3; - uint8_t funct7; - uint32_t imm; -}; - -struct Instruction decode_r_type(uint32_t word) -{ - struct Instruction instruction = {0}; - instruction.opcode = word & 0x7F; - instruction.rd = (word >> 7) & 0x1F; - instruction.funct3 = (word >> 12) & 0x07; - instruction.rs1 = (word >> 15) & 0x1F; - instruction.rs2 = (word >> 20) & 0x1F; - instruction.funct7 = word >> 25; - return instruction; -}; - -struct Instruction decode_i_type(uint32_t word) -{ - struct Instruction instruction = {0}; - instruction.opcode = word & 0x7F; - instruction.rd = (word >> 7) & 0x1F; - instruction.funct3 = (word >> 12) & 0x07; - instruction.rs1 = (word >> 15) & 0x1F; - instruction.imm = sign_extend(word >> 20, 12); - return instruction; -}; - -struct Instruction decode_s_type(uint32_t word) -{ - struct Instruction instruction = {0}; - instruction.opcode = word & 0x7F; - instruction.funct3 = (word >> 12) & 0x07; - instruction.rs1 = (word >> 15) & 0x1F; - instruction.rs2 = (word >> 20) & 0x1F; - instruction.imm = sign_extend(((word >> 7) & 0x1F) | (word >> 25), 12); - return instruction; -}; - -struct Instruction decode_b_type(uint32_t word) -{ - struct Instruction instruction = decode_s_type(word); - instruction.imm = ((instruction.imm << 11) & 0x800) | (instruction.imm & 0xfffff7ff); - return instruction; -}; - -struct Instruction decode_u_type(uint32_t word) -{ - struct Instruction instruction = {0}; - instruction.opcode = word & 0x7F; - instruction.rd = (word >> 7) & 0x1F; - instruction.imm = word & 0xFFFFF000; - return instruction; -}; - -struct Instruction decode_j_type(uint32_t word) -{ - struct Instruction instruction = {0}; - instruction.opcode = word & 0x7F; - instruction.rd = (word >> 7) & 0x1F; - instruction.imm = sign_extend( - ((word & 0x80000000) >> 11) | - ((word & 0x000FF000) >> 0) | - ((word & 0x00100000) >> 9) | - ((word & 0x7FE00000) >> 20), 21); - return instruction; -} - -struct Hart -{ - uint32_t pc; - uint32_t regs[32]; - char* mem; - uint32_t mem_size; -}; - -void execute_op_imm(struct Hart* hart, uint32_t instruction) -{ - const struct Instruction inst = decode_i_type(instruction); - if (inst.rd == 0) return; - - switch (inst.funct3) - { - case 0: // ADDI - hart->regs[inst.rd] = hart->regs[inst.rs1] + inst.imm; - break; - case 1: // SLLI - hart->regs[inst.rd] = hart->regs[inst.rs1] << (inst.imm & 0x1F); - break; - case 2: // SLTI - hart->regs[inst.rd] = (int32_t)hart->regs[inst.rs1] < (int32_t)inst.imm ? 1 : 0; - break; - case 3: // SLTIU - hart->regs[inst.rd] = hart->regs[inst.rs1] < inst.imm ? 1 : 0; - break; - case 4: // XORI - hart->regs[inst.rd] = hart->regs[inst.rs1] ^ inst.imm; - break; - case 5: // SRLI, SRAI - { - const uint32_t shamt = inst.imm & 0x1F; - uint32_t res = hart->regs[inst.rs1] >> shamt; - if ((inst.imm & 0x400) && shamt > 0) { res = sign_extend(res, 32 - shamt); } - hart->regs[inst.rd] = res; - break; - } - case 6: // ORI - hart->regs[inst.rd] = hart->regs[inst.rs1] | inst.imm; - break; - case 7: // ANDI - hart->regs[inst.rd] = hart->regs[inst.rs1] & inst.imm; - break; - default: - assert(!"Unhandled OP-IMM"); - } -} - -void execute_op(struct Hart* hart, uint32_t instruction) -{ - const struct Instruction inst = decode_r_type(instruction); - if (inst.rd == 0) return; - - switch (inst.funct3) - { - case 0: // ADD, SUB - if (instruction & 0x40000000) - { - hart->regs[inst.rd] = hart->regs[inst.rs1] - hart->regs[inst.rs2]; - } - else - { - hart->regs[inst.rd] = hart->regs[inst.rs1] + hart->regs[inst.rs2]; - } - break; - case 1: // SLL - hart->regs[inst.rd] = hart->regs[inst.rs1] << (hart->regs[inst.rs2] & 0x1F); - break; - case 2: // SLT - hart->regs[inst.rd] = (int32_t)hart->regs[inst.rs1] < (int32_t)hart->regs[inst.rs2] ? 1 : 0; - break; - case 3: // SLTU - hart->regs[inst.rd] = hart->regs[inst.rs1] < hart->regs[inst.rs2] ? 1 : 0; - break; - case 4: // XOR - hart->regs[inst.rd] = hart->regs[inst.rs1] ^ hart->regs[inst.rs2]; - break; - case 5: // SRL, SRA - { - const uint32_t shamt = hart->regs[inst.rs2] & 0x1F; - uint32_t res = hart->regs[inst.rs1] >> shamt; - if ((instruction & 0x40000000) && shamt > 0) { res = sign_extend(res, 32 - shamt); } - hart->regs[inst.rd] = res; - break; - } - case 6: // OR - hart->regs[inst.rd] = hart->regs[inst.rs1] | hart->regs[inst.rs2]; - break; - case 7: // AND - hart->regs[inst.rd] = hart->regs[inst.rs1] & hart->regs[inst.rs2]; - break; - default: - assert(!"Unhandled OP-IMM"); - } -} - -void execute_branch(struct Hart* hart, uint32_t instruction) -{ - const struct Instruction inst = decode_b_type(instruction); - const uint32_t r1 = hart->regs[inst.rs1]; - const uint32_t r2 = hart->regs[inst.rs2]; - bool take_branch = false; - - switch (inst.funct3) - { - case 0: take_branch = (r1 == r2); break; - case 1: take_branch = (r1 != r2); break; - case 4: take_branch = (r1 < r2); break; - case 5: take_branch = (r1 >= r2); break; - case 6: take_branch = ((int32_t)r1 < (int32_t)r2); break; - case 7: take_branch = ((int32_t)r1 >= (int32_t)r2); break; - } - - if (take_branch) - { - hart->pc += inst.imm; - } - else - { - hart->pc += 4; - } -} - -inline uint32_t load_size(struct Hart* hart, uint32_t address, uint32_t size) -{ - if ((address & 0x80000000) == 0) - { - assert(address + size < hart->mem_size); - uint32_t value = 0; - memcpy(&value, hart->mem + address, size); - return value; - } - - return 0; -} - -uint32_t load_byte(struct Hart* hart, uint32_t address) -{ - return load_size(hart, address, 1); -} - -uint32_t load_half(struct Hart* hart, uint32_t address) -{ - return load_size(hart, address, 2); -} - -uint32_t load_word(struct Hart* hart, uint32_t address) -{ - return load_size(hart, address, 4); -} - -inline void store_size(struct Hart* hart, uint32_t address, uint32_t value, uint32_t size) -{ - if ((address & 0x80000000) == 0) - { - assert(address + size < hart->mem_size); - memcpy(hart->mem + address, &value, size); - } - else if (address == 0x80000000) - { - fwrite(&value, 1, size, stdout); - } -} - -void store_byte(struct Hart* hart, uint32_t address, uint8_t value) -{ - store_size(hart, address, value, 1); -} - -void store_half(struct Hart* hart, uint32_t address, uint16_t value) -{ - store_size(hart, address, value, 2); -} - -void store_word(struct Hart* hart, uint32_t address, uint32_t value) -{ - store_size(hart, address, value, 4); -} - -void execute_op_load(struct Hart* hart, uint32_t instruction) -{ - const struct Instruction inst = decode_i_type(instruction); - const uint32_t address = hart->regs[inst.rs1] + inst.imm; - - uint32_t value = 0; - - switch (inst.funct3 & 0x03) - { - case 0: - value = load_byte(hart, address); - if ((inst.funct3 & 0x40) == 0) - { - value = sign_extend(value, 8); - } - break; - case 1: - value = load_half(hart, address); - if ((inst.funct3 & 0x40) == 0) - { - value = sign_extend(value, 16); - } - break; - case 2: - value = load_byte(hart, address); - break; - default: - assert(!"Unhandled load size"); - break; - } - - if (inst.rd != 0) - { - hart->regs[inst.rd] = value; - } -} - -void execute_op_store(struct Hart* hart, uint32_t instruction) -{ - const struct Instruction inst = decode_s_type(instruction); - const uint32_t address = hart->regs[inst.rs1] + inst.imm; - const uint32_t value = hart->regs[inst.rs2]; - - switch (inst.funct3 & 0x03) - { - case 0: store_byte(hart, address, value & 0xFF); break; - case 1: store_half(hart, address, value & 0xFFFF); break; - case 2: store_word(hart, address, value); break; - default: - assert(!"Unhandled store size"); - break; - } -} - -void execute_misc_mem(struct Hart* hart, uint32_t instruction) -{ - const struct Instruction inst = decode_i_type(instruction); - - if (inst.funct3 == 0) - { - // FENCE - } - else - { - assert(!"Unhandled MISC-MEM instruction"); - } -} - -void execute_system(struct Hart* hart, uint32_t instruction) -{ - const struct Instruction inst = decode_i_type(instruction); - - if (inst.funct3 == 0 && inst.rs1 == 0 && inst.rd == 0) - { - if (inst.imm == 0) - { - // ECALL - } - else if (inst.imm == 1) - { - // EBREAK - } - else - { - assert(!"Unhandled SYSTEM/PRIV instruction"); - } - } - else - { - assert(!"Unhandled SYSTEM instruction"); - } -} - -void execute(struct Hart* hart, uint32_t instruction) -{ - switch (instruction & 0x7f) - { - case 0x03: - execute_op_load(hart, instruction); - hart->pc += 4; - break; - case 0x0F: - execute_misc_mem(hart, instruction); - hart->pc += 4; - break; - case 0x13: - execute_op_imm(hart, instruction); - hart->pc += 4; - break; - case 0x17: // AUIPC - { - struct Instruction inst = decode_u_type(instruction); - if (inst.rd != 0) - { - hart->regs[inst.rd] = inst.imm + hart->pc; - } - hart->pc += 4; - break; - } - case 0x23: - execute_op_store(hart, instruction); - hart->pc += 4; - break; - case 0x33: - execute_op(hart, instruction); - hart->pc += 4; - break; - case 0x37: // LUI - { - struct Instruction inst = decode_u_type(instruction); - if (inst.rd != 0) - { - hart->regs[inst.rd] = inst.imm; - } - hart->pc += 4; - break; - } - case 0x63: - execute_branch(hart, instruction); - break; - case 0x67: // JALR - { - struct Instruction inst = decode_i_type(instruction); - assert(inst.funct3 == 0); - if (inst.rd != 0) - { - hart->regs[inst.rd] = hart->pc + 4; - } - hart->pc = (hart->regs[inst.rs1] + inst.imm) & 0xFFFFFFFE; - break; - } - case 0x6F: // JAL - { - struct Instruction inst = decode_j_type(instruction); - if (inst.rd != 0) - { - hart->regs[inst.rd] = hart->pc + 4; - } - hart->pc += inst.imm; - break; - } - case 0x73: - { - execute_system(hart, instruction); - hart->pc += 4; - break; - } - default: - assert(!"Unhandled opcode"); - } - - assert(hart->regs[0] == 0); -} - -void test_addi() -{ - struct Hart hart = {0}; - - execute(&hart, 0x00500093); // addi x1, x0, 5 - assert(hart.regs[1] == 5); - - execute(&hart, 0xffe00093); // addi, x1, x0, -2 - assert(hart.regs[1] == 0xfffffffe); -} - -void test_slti_sltiu() -{ - struct Hart hart = {0}; - - hart.regs[1] = 5; - - execute(&hart, 0x00f0b113); // sltiu x2, x1, 15 - assert(hart.regs[2] == 1); - - execute(&hart, 0x0050b113); // sltiu x2, x1, 15 - assert(hart.regs[2] == 0); - - execute(&hart, 0x0010b113); // sltiu x2, x1, 15 - assert(hart.regs[2] == 0); - - execute(&hart, 0x00f0a113); // slti x2, x1, 15 - assert(hart.regs[2] == 1); - - execute(&hart, 0x0050a113); // slti x2, x1, 15 - assert(hart.regs[2] == 0); - - execute(&hart, 0x0010a113); // slti x2, x1, 15 - assert(hart.regs[2] == 0); - - execute(&hart, 0xffb0a113); // slti x2, x1, -5 - assert(hart.regs[2] == 0); - - hart.regs[1] = (uint32_t)-20; - - execute(&hart, 0xffb0a113); // slti x2, x1, -5 - assert(hart.regs[2] == 1); -} - -void test_andi_ori_xori() -{ - struct Hart hart = {0}; - - hart.regs[1] = 6; - - execute(&hart, 0x00c0c113); // xori x2, x1, 12 - assert(hart.regs[2] == 10); - - execute(&hart, 0x00c0e113); // ori x2, x1, 12 - assert(hart.regs[2] == 14); - - execute(&hart, 0x00c0f113); // andi x2, x1, 12 - assert(hart.regs[2] == 4); -} - -void test_slli_srli_srai() -{ - struct Hart hart = {0}; - - hart.regs[1] = 6; - - execute(&hart, 0x00209113); // slli x2, x1, 2 - assert(hart.regs[2] == 24); - - execute(&hart, 0x0020d113); // srli x2, x1, 2 - assert(hart.regs[2] == 1); - - execute(&hart, 0x4020d113); // srai x2, x1, 2 - assert(hart.regs[2] == 1); - - hart.regs[1] = (uint32_t)-6; - - execute(&hart, 0x0020d113); // srli x2, x1, 2 - assert(hart.regs[2] == 0x3FFFFFFE); - - execute(&hart, 0x4020d113); // srai x2, x1, 2 - assert(hart.regs[2] == 0xFFFFFFFE); -} - -void test_lui_auipc() -{ - struct Hart hart = {0}; - - hart.pc = 0; - execute(&hart, 0x0007b0b7); // lui x1, 503808 - assert(hart.regs[1] == 503808); - - hart.pc = 0; - execute(&hart, 0x0007b097); // auipc x1, 503808 - assert(hart.regs[1] == 503808); - - hart.pc = 12; - execute(&hart, 0x0007b097); // auipc x1, 503808 - assert(hart.regs[1] == 503820); -} - -void test_op() -{ - struct Hart hart = {0}; - - hart.regs[1] = 3; - hart.regs[2] = 4; - hart.regs[4] = (uint32_t)-1; - - execute(&hart, 0x002081b3); // add, x3, x1, x2 - assert(hart.regs[3] == 7); - - execute(&hart, 0x402081b3); // sub x3, x1, x2 - assert(hart.regs[3] == 0xFFFFFFFF); - - execute(&hart, 0x0020a1b3); // slt x3, x1, x2 - assert(hart.regs[3] == 1); - - execute(&hart, 0x001121b3); // slt x3, x2, 1 - assert(hart.regs[3] == 0); - - execute(&hart, 0x001131b3); // sltu x3, x2, x1 - assert(hart.regs[3] == 0); - - execute(&hart, 0x0020b1b3); // sltu x3, x1, x2 - assert(hart.regs[3] == 1); - - execute(&hart, 0x0040a1b3); // slt x3, x1, x4 - assert(hart.regs[3] == 0); - - hart.regs[1] = 6; - hart.regs[2] = 12; - - execute(&hart, 0x0020e1b3); // or x3, x1, x2 - assert(hart.regs[3] == 14); - - execute(&hart, 0x0020c1b3); // xor x3, x1, x2 - assert(hart.regs[3] == 10); - - execute(&hart, 0x0020f1b3); // and x3, x1, x2 - assert(hart.regs[3] == 4); - - hart.regs[1] = 6; - hart.regs[2] = 2; - - execute(&hart, 0x002091b3); // sll x3, x1, x2 - assert(hart.regs[3] == 24); - - execute(&hart, 0x0020d1b3); // srl x3, x1, x2 - assert(hart.regs[3] == 1); - - execute(&hart, 0x4020d1b3); // sra x3, x1, x2 - assert(hart.regs[3] == 1); - - hart.regs[1] = (uint32_t)-6; - hart.regs[2] = 2; - - execute(&hart, 0x4020d1b3); // sra x3, x1, x2 - assert(hart.regs[3] == 0xFFFFFFFE); -} - -void test_jal() -{ - struct Hart hart = {0}; - - hart.pc = 12; - - execute(&hart, 0x12c000ef); // jal x1, 300 - assert(hart.regs[1] == 16); - assert(hart.pc == 312); - - execute(&hart, 0xed5ff0ef); // jal x1, -300 - assert(hart.regs[1] == 316); - assert(hart.pc == 12); -} - -void test_jalr() -{ - struct Hart hart = {0}; - - hart.pc = 12; - hart.regs[1] = 300; - - execute(&hart, 0x00a08167); // jalr x2, 10(x1) - assert(hart.regs[2] == 16); - assert(hart.pc == 310); - - execute(&hart, 0xff608167); // jalr x2, -10(x1) - assert(hart.regs[2] == 314); - assert(hart.pc == 290); -} - -void test_branch() -{ - struct Hart hart = {0}; - - hart.pc = 100; - hart.regs[1] = 2; - hart.regs[2] = 0xFFFFFFFC; - - execute(&hart, 0x00208c63); // beq x1, x2, 24 - assert(hart.pc == 104); - - hart.pc = 100; - execute(&hart, 0x00209c63); // bne x1, x2, 24 - assert(hart.pc == 124); - - hart.pc = 100; - execute(&hart, 0x0020cc63); // blt x1, x2, 24 - assert(hart.pc == 124); - - hart.pc = 100; - execute(&hart, 0x0020dc63); // bge x1, x2, 24 - assert(hart.pc == 104); - - hart.pc = 100; - execute(&hart, 0x0020ec63); // bltu x1, x2, 24 - assert(hart.pc == 104); - - hart.pc = 100; - execute(&hart, 0x0020fc63); // bgeu x1, x2, 24 - assert(hart.pc == 124); -} - -void test() -{ - test_addi(); - test_slti_sltiu(); - test_andi_ori_xori(); - test_slli_srli_srai(); - test_lui_auipc(); - test_op(); - test_jal(); - test_jalr(); - test_branch(); -} +#include "emulator/hart.h" +#include "emulator/elf.h" int main(int argc, char* argv[]) { - test(); - if (argc < 2) { printf("Usage: %s \n", argv[0]); @@ -824,15 +24,14 @@ int main(int argc, char* argv[]) } struct Hart hart = {0}; - hart.pc = start_address; hart.mem = mem; hart.mem_size = mem_size; - while (true) - { - uint32_t instruction = load_word(&hart, hart.pc); - execute(&hart, instruction); - } - + execute_from(&hart, start_address); + return EXIT_SUCCESS; } + +#include "emulator/hart.c" +#include "emulator/elf.c" + -- cgit