From 7642349be93be4a1acf62e9c5ca831f918ba1c3c Mon Sep 17 00:00:00 2001 From: Steven Le Rouzic Date: Sun, 12 May 2024 00:33:10 +0200 Subject: Load ELF into emulator --- .gitignore | 1 + build.bat | 6 +- emulator/main.c | 800 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ main.asm | 9 + main.c | 643 --------------------------------------------- 5 files changed, 815 insertions(+), 644 deletions(-) create mode 100644 emulator/main.c create mode 100644 main.asm delete mode 100644 main.c diff --git a/.gitignore b/.gitignore index 1feae78..871d882 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ *.exe +*.bin diff --git a/build.bat b/build.bat index 593aa28..f115f95 100644 --- a/build.bat +++ b/build.bat @@ -1,2 +1,6 @@ -clang main.c -o riscv.exe -std=c11 +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 --target=riscv32 -march=rv32i -e _main -nostdlib main.asm -o main.bin + diff --git a/emulator/main.c b/emulator/main.c new file mode 100644 index 0000000..d48c049 --- /dev/null +++ b/emulator/main.c @@ -0,0 +1,800 @@ +#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]; +}; + +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; + } +} + +uint32_t load_byte(struct Hart* hart, uint32_t address) +{ + return 0; +} + +uint32_t load_half(struct Hart* hart, uint32_t address) +{ + return 0; +} + +uint32_t load_word(struct Hart* hart, uint32_t address) +{ + return 0; +} + +void store_byte(struct Hart* hart, uint32_t address, uint8_t value) {} +void store_half(struct Hart* hart, uint32_t address, uint16_t value) {} +void store_word(struct Hart* hart, uint32_t address, uint32_t value) {} + +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(); +} + +int main(int argc, char* argv[]) +{ + test(); + + if (argc < 2) + { + printf("Usage: %s \n", argv[0]); + return EXIT_FAILURE; + } + + uint32_t mem_size = 16 * 1024 * 1024; + char* mem = (char*)malloc(mem_size); + uint32_t start_address = 0; + + if (!load_elf(argv[1], mem, mem_size, &start_address)) + { + printf("Error loading ELF into memory\n"); + return EXIT_FAILURE; + } + + struct Hart hart = {0}; + hart.pc = start_address; + + while (true) + { + uint32_t instruction = *(uint32_t*)(mem + hart.pc); + execute(&hart, instruction); + printf("pc=%x x1=%d x2=%d x3=%d x4=%d\n", + hart.pc, hart.regs[1], hart.regs[2], hart.regs[3], hart.regs[4]); + fgetc(stdout); + } + + return EXIT_SUCCESS; +} diff --git a/main.asm b/main.asm new file mode 100644 index 0000000..e90d6a2 --- /dev/null +++ b/main.asm @@ -0,0 +1,9 @@ +.section .text +.global _main +_main: + li x1, 5 + addi x2, x1, 45 + +loop: + addi x2, x2, 1 + j loop diff --git a/main.c b/main.c deleted file mode 100644 index 223403a..0000000 --- a/main.c +++ /dev/null @@ -1,643 +0,0 @@ -#include -#include -#include -#include -#include - -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]; -}; - -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; - } -} - -uint32_t load_byte(struct Hart* hart, uint32_t address) -{ - return 0; -} - -uint32_t load_half(struct Hart* hart, uint32_t address) -{ - return 0; -} - -uint32_t load_word(struct Hart* hart, uint32_t address) -{ - return 0; -} - -void store_byte(struct Hart* hart, uint32_t address, uint8_t value) {} -void store_half(struct Hart* hart, uint32_t address, uint16_t value) {} -void store_word(struct Hart* hart, uint32_t address, uint32_t value) {} - -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(); -} - -int main(int argc, char* argv[]) -{ - test(); - return EXIT_SUCCESS; -} -- cgit