#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; }