Skip to content

Instantly share code, notes, and snippets.

@FrankBuss
Last active August 27, 2025 05:28
Show Gist options
  • Select an option

  • Save FrankBuss/c974e59826d33e21d7cad54491ab50e8 to your computer and use it in GitHub Desktop.

Select an option

Save FrankBuss/c974e59826d33e21d7cad54491ab50e8 to your computer and use it in GitHub Desktop.

Revisions

  1. @Frank-Buss Frank-Buss revised this gist Dec 13, 2018. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions emulator.cpp
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,6 @@
    /*
    See https://gitlab.com/nedopc/npc5/blob/master/emu-rv32i.c for the latest version, with more features and less bugs :-)
    RISCV emulator for the RV32I architecture
    based on TinyEMU by Fabrice Bellard, see https://bellard.org/tinyemu/
    stripped down for RV32I only, all "gotos" removed, and fixed some bugs for the compliance test
  2. @Frank-Buss Frank-Buss revised this gist Nov 18, 2018. 1 changed file with 360 additions and 114 deletions.
    474 changes: 360 additions & 114 deletions emulator.cpp
    Original file line number Diff line number Diff line change
    @@ -1,19 +1,43 @@
    // RISCV emulator for the RV32I architecture
    // based on TinyEMU by Fabrice Bellard, see https://bellard.org/tinyemu/
    // stripped down for RV32I only, all "gotos" removed, and fixed some bugs for the compliance test
    // by Frank Buss, 2018
    //
    // requires libelf-dev:
    // sudo apt-get install libelf-dev
    //
    // compile:
    // g++ -O3 -Wall -lelf emulator.cpp -o emulator
    //
    // it is compatible to Spike for the command line arguments, which means you can run
    // the compliance test from https://github.com/riscv/riscv-compliance like this:
    // make RISCV_TARGET=spike RISCV_DEVICE=rv32i TARGET_SIM=/full/path/emulator variant
    //
    // original copyright:
    /*
    RISCV emulator for the RV32I architecture
    based on TinyEMU by Fabrice Bellard, see https://bellard.org/tinyemu/
    stripped down for RV32I only, all "gotos" removed, and fixed some bugs for the compliance test
    by Frank Buss, 2018
    Requires libelf-dev:
    sudo apt-get install libelf-dev
    Compile it like this:
    g++ -O3 -Wall -lelf emulator.cpp -o emulator
    It is compatible to Spike for the command line arguments, which means you can run
    the compliance test from https://github.com/riscv/riscv-compliance like this:
    make RISCV_TARGET=spike RISCV_DEVICE=rv32i TARGET_SIM=/full/path/emulator variant
    It is also compatible with qemu32, as it is used for Zephyr. You can compile the
    Zephyr examples for qemu like this:
    cd zephyr
    source zephyr-env.sh
    cd samples/synchronization
    mkdir build && cd build
    cmake -GNinja -DBOARD=qemu_riscv32 ..
    ninja
    After this you can run it with the emulator like this:
    /full/path/emulator zephyr/zephyr.elf
    original copyright:
    */

    /*
    * RISCV emulator
    *
    @@ -49,16 +73,36 @@
    #include <stdint.h>
    #include <string.h>
    #include <unistd.h>
    #include <time.h>
    #include <libelf.h>
    #include <gelf.h>

    // uncomment this for an instruction trace and other debug outputs
    //#define DEBUG_OUTPUT

    // memory mapped registers
    #define MTIME_ADDR 0x40000000
    #define MTIMECMP_ADDR 0x40000008
    #define UART_TX_ADDR 0x40002000

    // emulator RAM
    #define RAM_SIZE 0x8000
    #define RAM_SIZE 0x10000
    uint8_t ram[RAM_SIZE];

    // virtual start address
    // special memory mapped registers
    uint64_t mtime;
    uint64_t mtimecmp;

    // virtual start address for index 0 in the ram array
    uint32_t ram_start;

    // used when called from the compliance tests
    uint32_t begin_signature = 0;
    uint32_t end_signature = 0;

    // is set to false to exit the emulator
    bool machine_running = true;

    // privilege levels
    #define PRV_U 0
    #define PRV_S 1
    @@ -103,7 +147,6 @@ uint32_t satp;
    uint32_t scounteren;
    uint32_t load_res; /* for atomic LR/SC */


    // exception causes
    #define CAUSE_MISALIGNED_FETCH 0x0
    #define CAUSE_FAULT_FETCH 0x1
    @@ -120,8 +163,6 @@ uint32_t load_res; /* for atomic LR/SC */
    #define CAUSE_FETCH_PAGE_FAULT 0xc
    #define CAUSE_LOAD_PAGE_FAULT 0xd
    #define CAUSE_STORE_PAGE_FAULT 0xf

    /* Note: converted to correct bit position at runtime */
    #define CAUSE_INTERRUPT ((uint32_t)1 << 31)

    /* misa CSR */
    @@ -177,9 +218,6 @@ uint32_t load_res; /* for atomic LR/SC */
    #define MSTATUS_UXL_MASK ((uint64_t)3 << MSTATUS_UXL_SHIFT)
    #define MSTATUS_SXL_MASK ((uint64_t)3 << MSTATUS_SXL_SHIFT)

    /* Note: converted to correct bit position at runtime */
    #define CAUSE_INTERRUPT ((uint32_t)1 << 31)

    int ctz32(uint32_t a)
    {
    int i;
    @@ -225,33 +263,27 @@ uint32_t get_mstatus(uint32_t mask)

    void set_mstatus(uint32_t val)
    {
    uint32_t mod, mask;

    /* flush the TLBs if change of MMU config */
    mod = mstatus ^ val;
    if ((mod & (MSTATUS_MPRV | MSTATUS_SUM | MSTATUS_MXR)) != 0 ||
    ((mstatus & MSTATUS_MPRV) && (mod & MSTATUS_MPP) != 0)) {
    //tlb_flush_all(s);
    }
    fs = (val >> MSTATUS_FS_SHIFT) & 3;

    mask = MSTATUS_MASK & ~MSTATUS_FS;
    uint32_t mask = MSTATUS_MASK & ~MSTATUS_FS;
    mstatus = (mstatus & ~mask) | (val & mask);
    }

    void invalid_csr(uint32_t *pval, uint32_t csr)
    {
    /* the 'time' counter is usually emulated */
    if (csr != 0xc01 && csr != 0xc81) {
    //printf("csr_read: invalid CSR=0x%x\n", csr);
    #ifdef DEBUG_OUTPUT
    printf("csr_read: invalid CSR=0x%x\n", csr);
    #endif
    }
    *pval = 0;
    }

    /* return -1 if invalid CSR. 0 if OK. 'will_write' indicate that the
    csr will be written after (used for CSR access check) */
    int csr_read(uint32_t *pval, uint32_t csr,
    bool will_write)
    bool will_write)
    {
    uint32_t val;

    @@ -376,7 +408,8 @@ int csr_read(uint32_t *pval, uint32_t csr,
    break;
    default:
    invalid_csr(pval, csr);
    return -1;
    //return -1;
    return 0;
    }
    *pval = val;
    return 0;
    @@ -431,7 +464,6 @@ int csr_write(uint32_t csr, uint32_t val)
    satp = (val & (((uint32_t)1 << 22) - 1)) |
    (new_mode << 31);
    }
    //tlb_flush_all(s);
    return 2;

    case 0x300:
    @@ -474,7 +506,8 @@ int csr_write(uint32_t csr, uint32_t val)
    mip = (mip & ~mask) | (val & mask);
    break;
    default:
    return -1;
    return 0;
    // return -1;
    }
    return 0;
    }
    @@ -513,10 +546,15 @@ void handle_mret()


    void raise_exception(uint32_t cause,
    uint32_t tval)
    uint32_t tval)
    {
    bool deleg;
    uint32_t causel;

    // exit for Zephyr applications
    if (cause == CAUSE_ILLEGAL_INSTRUCTION) {
    machine_running = false;
    return;
    }

    if (priv <= PRV_S) {
    /* delegate the exception to the supervisor priviledge */
    @@ -528,12 +566,8 @@ void raise_exception(uint32_t cause,
    deleg = 0;
    }

    causel = cause & 0x7fffffff;
    if (cause & CAUSE_INTERRUPT)
    causel |= (uint32_t)1 << (XLEN - 1);

    if (deleg) {
    scause = causel;
    scause = cause;
    sepc = pc;
    stval = tval;
    mstatus = (mstatus & ~MSTATUS_SPIE) |
    @@ -544,7 +578,7 @@ void raise_exception(uint32_t cause,
    priv = PRV_S;
    next_pc = stvec;
    } else {
    mcause = causel;
    mcause = cause;
    mepc = pc;
    mtval = tval;
    mstatus = (mstatus & ~MSTATUS_MPIE) |
    @@ -608,9 +642,14 @@ uint32_t get_insn32(uint32_t pc)
    int target_read_u8(uint8_t *pval, uint32_t addr)
    {
    addr -= ram_start;
    if (addr > RAM_SIZE) return 1;
    uint8_t* p = ram + addr;
    *pval = p[0];
    if (addr > RAM_SIZE) {
    *pval = 0;
    printf("illegal read 8, PC: 0x%08x, address: 0x%08x\n", pc, addr + ram_start);
    return 1;
    } else {
    uint8_t* p = ram + addr;
    *pval = p[0];
    }
    return 0;
    }

    @@ -622,9 +661,14 @@ int target_read_u16(uint16_t *pval, uint32_t addr)
    return 1;
    }
    addr -= ram_start;
    if (addr > RAM_SIZE) return 1;
    uint8_t* p = ram + addr;
    *pval = p[0] | (p[1] << 8);
    if (addr > RAM_SIZE) {
    *pval = 0;
    printf("illegal read 16, PC: 0x%08x, address: 0x%08x\n", pc, addr + ram_start);
    return 1;
    } else {
    uint8_t* p = ram + addr;
    *pval = p[0] | (p[1] << 8);
    }
    return 0;
    }

    @@ -635,24 +679,42 @@ int target_read_u32(uint32_t *pval, uint32_t addr)
    pending_tval = addr;
    return 1;
    }
    addr -= ram_start;
    if (addr > RAM_SIZE) return 1;
    uint8_t* p = ram + addr;
    *pval = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
    if (addr == MTIMECMP_ADDR) {
    *pval = (uint32_t) mtimecmp;
    } else if (addr == MTIMECMP_ADDR + 4) {
    *pval = (uint32_t) (mtimecmp >> 32);
    } else if (addr == MTIME_ADDR) {
    *pval = (uint32_t) mtime;
    } else if (addr == MTIME_ADDR + 4) {
    *pval = (uint32_t) (mtime >> 32);
    } else {
    addr -= ram_start;
    if (addr > RAM_SIZE) {
    *pval = 0;
    printf("illegal read 32, PC: 0x%08x, address: 0x%08x\n", pc, addr + ram_start);
    return 1;
    } else {
    uint8_t* p = ram + addr;
    *pval = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
    }
    }
    return 0;
    }

    int target_write_u8(uint32_t addr, uint8_t val)
    {
    if (addr == 0xf0000000) {
    // test for UART output
    if (addr == UART_TX_ADDR) {
    // test for UART output, compatible with QEMU
    printf("%c", val);
    fflush(stdout);
    } else {
    addr -= ram_start;
    if (addr > RAM_SIZE - 1) return 1;
    uint8_t* p = ram + addr;
    p[0] = val & 0xff;
    if (addr > RAM_SIZE - 1) {
    printf("illegal write 8, PC: 0x%08x, address: 0x%08x\n", pc, addr + ram_start);
    return 1;
    } else {
    uint8_t* p = ram + addr;
    p[0] = val & 0xff;
    }
    }
    return 0;
    }
    @@ -665,46 +727,155 @@ int target_write_u16(uint32_t addr, uint16_t val)
    return 1;
    }
    addr -= ram_start;
    if (addr > RAM_SIZE - 2) return 1;
    uint8_t* p = ram + addr;
    p[0] = val & 0xff;
    p[1] = (val >> 8) & 0xff;
    if (addr > RAM_SIZE - 2) {
    printf("illegal write 16, PC: 0x%08x, address: 0x%08x\n", pc, addr + ram_start);
    return 1;
    } else {
    uint8_t* p = ram + addr;
    p[0] = val & 0xff;
    p[1] = (val >> 8) & 0xff;
    }
    return 0;
    }

    void write_mtimecmp(uint64_t value)
    {
    mtimecmp = value;
    mip &= ~MIP_MTIP;
    }

    int target_write_u32(uint32_t addr, uint32_t val)
    {
    if (addr & 3) {
    pending_exception = CAUSE_MISALIGNED_STORE;
    pending_tval = addr;
    return 1;
    }
    addr -= ram_start;
    if (addr > RAM_SIZE - 4) return 1;
    uint8_t* p = ram + addr;
    p[0] = val & 0xff;
    p[1] = (val >> 8) & 0xff;
    p[2] = (val >> 16) & 0xff;
    p[3] = (val >> 24) & 0xff;
    if (addr == MTIMECMP_ADDR) {
    write_mtimecmp((mtimecmp & 0xffffffff00000000ll) | val);
    } else if (addr == MTIMECMP_ADDR + 4) {
    write_mtimecmp((mtimecmp & 0xffffffffll) | (((uint64_t)val) << 32));
    } else {
    addr -= ram_start;
    if (addr > RAM_SIZE - 4) {
    return 1;
    } else {
    uint8_t* p = ram + addr;
    p[0] = val & 0xff;
    p[1] = (val >> 8) & 0xff;
    p[2] = (val >> 16) & 0xff;
    p[3] = (val >> 24) & 0xff;
    }
    }
    return 0;
    }

    int32_t div32(int32_t a, int32_t b)
    {
    if (b == 0) {
    return -1;
    } else if (a == ((int32_t)1 << (XLEN - 1)) && b == -1) {
    return a;
    } else {
    return a / b;
    }
    }

    bool machine_running = true;
    uint32_t divu32(uint32_t a, uint32_t b)
    {
    if (b == 0) {
    return -1;
    } else {
    return a / b;
    }
    }

    int32_t rem32(int32_t a, int32_t b)
    {
    if (b == 0) {
    return a;
    } else if (a == ((int32_t)1 << (XLEN - 1)) && b == -1) {
    return 0;
    } else {
    return a % b;
    }
    }

    uint32_t remu32(uint32_t a, uint32_t b)
    {
    if (b == 0) {
    return a;
    } else {
    return a % b;
    }
    }

    static uint32_t mulh32(int32_t a, int32_t b)
    {
    return ((int64_t)a * (int64_t)b) >> 32;
    }

    static uint32_t mulhsu32(int32_t a, uint32_t b)
    {
    return ((int64_t)a * (int64_t)b) >> 32;
    }

    static uint32_t mulhu32(uint32_t a, uint32_t b)
    {
    return ((int64_t)a * (int64_t)b) >> 32;
    }

    /*
    // dumps all registers, useful for in-depth debugging
    static void dump_regs()
    {
    printf("x0 zero: %08x\n", reg[0]);
    printf("x1 ra: %08x\n", reg[1]);
    printf("x2 sp: %08x\n", reg[2]);
    printf("x3 gp: %08x\n", reg[3]);
    printf("x4 tp: %08x\n", reg[4]);
    printf("x5 t0: %08x\n", reg[5]);
    printf("x6 t1: %08x\n", reg[6]);
    printf("x7 t2: %08x\n", reg[7]);
    printf("x8 s0: %08x\n", reg[8]);
    printf("x9 s1: %08x\n", reg[9]);
    printf("x10 a0: %08x\n", reg[10]);
    printf("x11 a1: %08x\n", reg[11]);
    printf("x12 a2: %08x\n", reg[12]);
    printf("x13 a3: %08x\n", reg[13]);
    printf("x14 a4: %08x\n", reg[14]);
    printf("x15 a5: %08x\n", reg[15]);
    printf("x16 a6: %08x\n", reg[16]);
    printf("x17 a7: %08x\n", reg[17]);
    printf("x18 s2: %08x\n", reg[18]);
    printf("x19 s3: %08x\n", reg[19]);
    printf("x20 s4: %08x\n", reg[20]);
    printf("x21 s5: %08x\n", reg[21]);
    printf("x22 s6: %08x\n", reg[22]);
    printf("x23 s7: %08x\n", reg[23]);
    printf("x24 s8: %08x\n", reg[24]);
    printf("x25 s9: %08x\n", reg[25]);
    printf("x26 s10: %08x\n", reg[26]);
    printf("x27 s11: %08x\n", reg[27]);
    printf("x28 t3: %08x\n", reg[28]);
    printf("x29 t4: %08x\n", reg[29]);
    printf("x30 t5: %08x\n", reg[30]);
    printf("x31 t6: %08x\n", reg[31]);
    }
    */

    void execute_instruction()
    {
    uint32_t opcode, rd, rs1, rs2, funct3;
    int32_t imm, cond, err;
    uint32_t addr, val, val2;


    opcode = insn & 0x7f;
    rd = (insn >> 7) & 0x1f;
    rs1 = (insn >> 15) & 0x1f;
    rs2 = (insn >> 20) & 0x1f;
    switch(opcode) {

    switch(opcode) {
    case 0x37: /* lui */
    if (rd != 0)
    reg[rd] = (int32_t)(insn & 0xfffff000);
    @@ -898,9 +1069,36 @@ void execute_instruction()
    val = reg[rs1];
    val2 = reg[rs2];
    if (imm == 1) {
    // mul, div and rem not implemented
    raise_exception(CAUSE_ILLEGAL_INSTRUCTION, insn);
    return;
    funct3 = (insn >> 12) & 7;
    switch(funct3) {
    case 0: /* mul */
    val = (int32_t)((int32_t)val * (int32_t)val2);
    break;
    case 1: /* mulh */
    val = (int32_t)mulh32(val, val2);
    break;
    case 2:/* mulhsu */
    val = (int32_t)mulhsu32(val, val2);
    break;
    case 3:/* mulhu */
    val = (int32_t)mulhu32(val, val2);
    break;
    case 4:/* div */
    val = div32(val, val2);
    break;
    case 5:/* divu */
    val = (int32_t)divu32(val, val2);
    break;
    case 6:/* rem */
    val = rem32(val, val2);
    break;
    case 7:/* remu */
    val = (int32_t)remu32(val, val2);
    break;
    default:
    raise_exception(CAUSE_ILLEGAL_INSTRUCTION, insn);
    return;
    }
    } else {
    if (imm & ~0x20) {
    raise_exception(CAUSE_ILLEGAL_INSTRUCTION, insn);
    @@ -1004,15 +1202,23 @@ void execute_instruction()
    }
    // compliance test specific: if bit 0 of gp (x3) is 0, it is a syscall,
    // otherwise it is the program end, with the exit code in the bits 31:1
    if (reg[3] & 1) {
    //printf("program end, result: %04x\n", reg[3] >> 1);
    machine_running = false;
    return;
    if (begin_signature) {
    if (reg[3] & 1) {
    #ifdef DEBUG_OUTPUT
    printf("program end, result: %04x\n", reg[3] >> 1);
    #endif
    machine_running = false;
    return;
    } else {
    #ifdef DEBUG_OUTPUT
    printf("syscall: %04x\n", reg[3]);
    #endif
    raise_exception(CAUSE_USER_ECALL + priv, 0);
    }
    } else {
    //printf("syscall: %04x\n", reg[3]);

    // on real hardware, an exception is raised, the I-ECALL-01 compliance test tests this as well
    raise_exception(CAUSE_USER_ECALL + priv, 0);
    return;
    }
    break;

    @@ -1033,6 +1239,9 @@ void execute_instruction()
    return;
    }
    break;
    case 0x105: /* wfi */
    // wait for interrupt: it is allowed to execute it as nop
    break;
    case 0x302: /* mret */
    {
    if ((insn & 0x000fff80) || (priv < PRV_M)) {
    @@ -1189,16 +1398,44 @@ void execute_instruction()
    }
    }

    // returns realtime in nanoseconds
    int64_t get_clock()
    {
    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    return ts.tv_sec * 1000000000LL + ts.tv_nsec;
    }


    void riscv_cpu_interp_x32()
    {
    pending_exception = -1;

    /* we use a single execution loop to keep a simple control flow
    for emscripten */
    while (machine_running) {
    if ((mip & mie) != 0) {
    // update timer, assuming 10 MHz clock (100 ns period) for the mtime counter
    mtime = get_clock() / 100ll;

    // for reproducible debug runs, you can use a fixed fixed increment per instruction
    //mtime += 10;

    // default value for next PC is next instruction, can be changed by branches or exceptions
    next_pc = pc + 4;

    // test for timer interrupt
    if (mtimecmp <= mtime) {
    mip |= MIP_MTIP;
    }
    if ((mip & mie) != 0 && (mstatus & MSTATUS_MIE)) {
    raise_interrupt();
    } else {
    // normal instruction execution
    insn = get_insn32(pc);
    insn_counter++;

    #ifdef DEBUG_OUTPUT
    printf("%08x, mtime: %08x, mtimecmp: %08x\n", pc, mtime, mtimecmp);
    #endif
    execute_instruction();
    }

    // test for misaligned fetches
    @@ -1208,40 +1445,29 @@ void riscv_cpu_interp_x32()

    // update current PC
    pc = next_pc;

    // default value for next PC is next instruction, can be changed by branchces or exceptions
    next_pc = pc + 4;

    /* fast path */
    insn = get_insn32(pc);
    insn_counter++;

    execute_instruction();

    if (pending_exception >= 0) {
    raise_exception(pending_exception, pending_tval);
    pending_exception = -1;
    }
    }

    #ifdef DEBUG_OUTPUT
    printf("done interp %lx int=%x mstatus=%lx prv=%d\n",
    (uint64_t)insn_counter, mip & mie, (uint64_t)mstatus,
    priv);
    #endif
    }

    int main(int argc, char** argv)
    {
    // automatic STDOUT flushing, no fflush needed
    setvbuf(stdout, NULL, _IONBF, 0);

    // parse command line
    char* elf_file = NULL;
    char* signature_file = NULL;
    const char* elf_file = NULL;
    const char* signature_file = NULL;
    for (int i = 1; i < argc; i++) {
    char* arg = argv[i];
    if (arg == strstr(arg, "+signature=")) {
    signature_file = arg + 11;
    //printf("signature file: %s\n", signature_file);
    } else if (arg[0] != '-') {
    elf_file = arg;
    //printf("ELF file: %s\n", elf_file);
    }
    }
    if (elf_file == NULL) {
    @@ -1252,11 +1478,13 @@ int main(int argc, char** argv)
    // open ELF file
    elf_version(EV_CURRENT);
    int fd = open(elf_file, O_RDONLY);
    if (fd == -1) {
    printf("can't open file %s\n", elf_file);
    return 1;
    }
    Elf *elf = elf_begin(fd, ELF_C_READ, NULL);

    // scan for symbol table
    uint32_t begin_signature = 0;
    uint32_t end_signature = 0;
    Elf_Scn *scn = NULL;
    GElf_Shdr shdr;
    while ((scn = elf_nextscn(elf, scn)) != NULL) {
    @@ -1274,23 +1502,41 @@ int main(int argc, char** argv)
    if (strcmp(name, "end_signature") == 0) {
    end_signature = sym.st_value;
    }

    // for compliance test
    if (strcmp(name, "_start") == 0) {
    ram_start = sym.st_value;
    }

    // for zephyr
    if (strcmp(name, "__reset") == 0) {
    ram_start = sym.st_value;
    }
    if (strcmp(name, "__irq_wrapper") == 0) {
    mtvec = sym.st_value;
    }
    }
    }
    }
    //printf("begin_signature: 0x%04x\n", begin_signature);
    //printf("end_signature: 0x%04x\n", end_signature);
    //printf("start: 0x%04x\n", ram_start);
    #ifdef DEBUG_OUTPUT
    printf("begin_signature: 0x%08x\n", begin_signature);
    printf("end_signature: 0x%08x\n", end_signature);
    printf("start: 0x%08x\n", ram_start);
    #endif

    // scan for program
    while ((scn = elf_nextscn(elf, scn)) != NULL) {
    gelf_getshdr(scn, &shdr);
    if (shdr.sh_type == SHT_PROGBITS) {
    Elf_Data *data = elf_getdata(scn, NULL);
    for (size_t i = 0; i < shdr.sh_size; i++) {
    ram[shdr.sh_addr + i - ram_start] = ((uint8_t *)data->d_buf)[i];
    if (shdr.sh_addr >= ram_start) {
    for (size_t i = 0; i < shdr.sh_size; i++) {
    ram[shdr.sh_addr + i - ram_start] = ((uint8_t *)data->d_buf)[i];
    }
    } else {
    #ifdef DEBUG_OUTPUT
    printf("ignoring section at address 0x%08x\n", (uint32_t) shdr.sh_addr);
    #endif
    }
    }
    }
    @@ -1300,7 +1546,7 @@ int main(int argc, char** argv)
    close(fd);

    // run program in emulator
    next_pc = ram_start;
    pc = ram_start;
    riscv_cpu_interp_x32();

    // write signature
  3. @Frank-Buss Frank-Buss created this gist Nov 11, 2018.
    1,321 changes: 1,321 additions & 0 deletions emulator.cpp
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,1321 @@
    // RISCV emulator for the RV32I architecture
    // based on TinyEMU by Fabrice Bellard, see https://bellard.org/tinyemu/
    // stripped down for RV32I only, all "gotos" removed, and fixed some bugs for the compliance test
    // by Frank Buss, 2018
    //
    // requires libelf-dev:
    // sudo apt-get install libelf-dev
    //
    // compile:
    // g++ -O3 -Wall -lelf emulator.cpp -o emulator
    //
    // it is compatible to Spike for the command line arguments, which means you can run
    // the compliance test from https://github.com/riscv/riscv-compliance like this:
    // make RISCV_TARGET=spike RISCV_DEVICE=rv32i TARGET_SIM=/full/path/emulator variant
    //
    // original copyright:
    /*
    * RISCV emulator
    *
    * Copyright (c) 2016 Fabrice Bellard
    *
    * Permission is hereby granted, free of charge, to any person obtaining a copy
    * of this software and associated documentation files (the "Software"), to deal
    * in the Software without restriction, including without limitation the rights
    * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    * copies of the Software, and to permit persons to whom the Software is
    * furnished to do so, subject to the following conditions:
    *
    * The above copyright notice and this permission notice shall be included in
    * all copies or substantial portions of the Software.
    *
    * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
    * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    * THE SOFTWARE.
    */

    #define XLEN 32

    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <stdint.h>
    #include <string.h>
    #include <unistd.h>
    #include <libelf.h>
    #include <gelf.h>

    // emulator RAM
    #define RAM_SIZE 0x8000
    uint8_t ram[RAM_SIZE];

    // virtual start address
    uint32_t ram_start;

    // privilege levels
    #define PRV_U 0
    #define PRV_S 1
    #define PRV_H 2
    #define PRV_M 3

    // CPU state
    uint32_t pc;
    uint32_t next_pc;
    uint32_t insn;
    uint32_t reg[32];

    uint8_t priv = PRV_M; /* see PRV_x */
    uint8_t fs; /* MSTATUS_FS value */
    uint8_t mxl; /* MXL field in MISA register */

    uint64_t insn_counter;
    int pending_exception; /* used during MMU exception handling */
    uint32_t pending_tval;

    /* CSRs */
    uint32_t mstatus;
    uint32_t mtvec;
    uint32_t mscratch;
    uint32_t mepc;
    uint32_t mcause;
    uint32_t mtval;
    uint32_t mhartid; /* ro */
    uint32_t misa;
    uint32_t mie;
    uint32_t mip;
    uint32_t medeleg;
    uint32_t mideleg;
    uint32_t mcounteren;

    uint32_t stvec;
    uint32_t sscratch;
    uint32_t sepc;
    uint32_t scause;
    uint32_t stval;
    uint32_t satp;
    uint32_t scounteren;
    uint32_t load_res; /* for atomic LR/SC */


    // exception causes
    #define CAUSE_MISALIGNED_FETCH 0x0
    #define CAUSE_FAULT_FETCH 0x1
    #define CAUSE_ILLEGAL_INSTRUCTION 0x2
    #define CAUSE_BREAKPOINT 0x3
    #define CAUSE_MISALIGNED_LOAD 0x4
    #define CAUSE_FAULT_LOAD 0x5
    #define CAUSE_MISALIGNED_STORE 0x6
    #define CAUSE_FAULT_STORE 0x7
    #define CAUSE_USER_ECALL 0x8
    #define CAUSE_SUPERVISOR_ECALL 0x9
    #define CAUSE_HYPERVISOR_ECALL 0xa
    #define CAUSE_MACHINE_ECALL 0xb
    #define CAUSE_FETCH_PAGE_FAULT 0xc
    #define CAUSE_LOAD_PAGE_FAULT 0xd
    #define CAUSE_STORE_PAGE_FAULT 0xf

    /* Note: converted to correct bit position at runtime */
    #define CAUSE_INTERRUPT ((uint32_t)1 << 31)

    /* misa CSR */
    #define MCPUID_SUPER (1 << ('S' - 'A'))
    #define MCPUID_USER (1 << ('U' - 'A'))
    #define MCPUID_I (1 << ('I' - 'A'))
    #define MCPUID_M (1 << ('M' - 'A'))
    #define MCPUID_A (1 << ('A' - 'A'))
    #define MCPUID_F (1 << ('F' - 'A'))
    #define MCPUID_D (1 << ('D' - 'A'))
    #define MCPUID_Q (1 << ('Q' - 'A'))
    #define MCPUID_C (1 << ('C' - 'A'))

    #define MIP_USIP (1 << 0)
    #define MIP_SSIP (1 << 1)
    #define MIP_HSIP (1 << 2)
    #define MIP_MSIP (1 << 3)
    #define MIP_UTIP (1 << 4)
    #define MIP_STIP (1 << 5)
    #define MIP_HTIP (1 << 6)
    #define MIP_MTIP (1 << 7)
    #define MIP_UEIP (1 << 8)
    #define MIP_SEIP (1 << 9)
    #define MIP_HEIP (1 << 10)
    #define MIP_MEIP (1 << 11)

    /* mstatus CSR */

    #define MSTATUS_SPIE_SHIFT 5
    #define MSTATUS_MPIE_SHIFT 7
    #define MSTATUS_SPP_SHIFT 8
    #define MSTATUS_MPP_SHIFT 11
    #define MSTATUS_FS_SHIFT 13
    #define MSTATUS_UXL_SHIFT 32
    #define MSTATUS_SXL_SHIFT 34

    #define MSTATUS_UIE (1 << 0)
    #define MSTATUS_SIE (1 << 1)
    #define MSTATUS_HIE (1 << 2)
    #define MSTATUS_MIE (1 << 3)
    #define MSTATUS_UPIE (1 << 4)
    #define MSTATUS_SPIE (1 << MSTATUS_SPIE_SHIFT)
    #define MSTATUS_HPIE (1 << 6)
    #define MSTATUS_MPIE (1 << MSTATUS_MPIE_SHIFT)
    #define MSTATUS_SPP (1 << MSTATUS_SPP_SHIFT)
    #define MSTATUS_HPP (3 << 9)
    #define MSTATUS_MPP (3 << MSTATUS_MPP_SHIFT)
    #define MSTATUS_FS (3 << MSTATUS_FS_SHIFT)
    #define MSTATUS_XS (3 << 15)
    #define MSTATUS_MPRV (1 << 17)
    #define MSTATUS_SUM (1 << 18)
    #define MSTATUS_MXR (1 << 19)
    #define MSTATUS_UXL_MASK ((uint64_t)3 << MSTATUS_UXL_SHIFT)
    #define MSTATUS_SXL_MASK ((uint64_t)3 << MSTATUS_SXL_SHIFT)

    /* Note: converted to correct bit position at runtime */
    #define CAUSE_INTERRUPT ((uint32_t)1 << 31)

    int ctz32(uint32_t a)
    {
    int i;
    if (a == 0)
    return 32;
    for(i = 0; i < 32; i++) {
    if ((a >> i) & 1)
    return i;
    }
    return 32;
    }

    #define SSTATUS_MASK0 (MSTATUS_UIE | MSTATUS_SIE | \
    MSTATUS_UPIE | MSTATUS_SPIE | \
    MSTATUS_SPP | \
    MSTATUS_FS | MSTATUS_XS | \
    MSTATUS_SUM | MSTATUS_MXR)
    #define SSTATUS_MASK SSTATUS_MASK0


    #define MSTATUS_MASK (MSTATUS_UIE | MSTATUS_SIE | MSTATUS_MIE | \
    MSTATUS_UPIE | MSTATUS_SPIE | MSTATUS_MPIE | \
    MSTATUS_SPP | MSTATUS_MPP | \
    MSTATUS_FS | \
    MSTATUS_MPRV | MSTATUS_SUM | MSTATUS_MXR)

    /* cycle and insn counters */
    #define COUNTEREN_MASK ((1 << 0) | (1 << 2))

    /* return the complete mstatus with the SD bit */
    uint32_t get_mstatus(uint32_t mask)
    {
    uint32_t val;
    bool sd;
    val = mstatus | (fs << MSTATUS_FS_SHIFT);
    val &= mask;
    sd = ((val & MSTATUS_FS) == MSTATUS_FS) |
    ((val & MSTATUS_XS) == MSTATUS_XS);
    if (sd)
    val |= (uint32_t)1 << (XLEN - 1);
    return val;
    }

    void set_mstatus(uint32_t val)
    {
    uint32_t mod, mask;

    /* flush the TLBs if change of MMU config */
    mod = mstatus ^ val;
    if ((mod & (MSTATUS_MPRV | MSTATUS_SUM | MSTATUS_MXR)) != 0 ||
    ((mstatus & MSTATUS_MPRV) && (mod & MSTATUS_MPP) != 0)) {
    //tlb_flush_all(s);
    }
    fs = (val >> MSTATUS_FS_SHIFT) & 3;

    mask = MSTATUS_MASK & ~MSTATUS_FS;
    mstatus = (mstatus & ~mask) | (val & mask);
    }

    void invalid_csr(uint32_t *pval, uint32_t csr)
    {
    /* the 'time' counter is usually emulated */
    if (csr != 0xc01 && csr != 0xc81) {
    //printf("csr_read: invalid CSR=0x%x\n", csr);
    }
    *pval = 0;
    }

    /* return -1 if invalid CSR. 0 if OK. 'will_write' indicate that the
    csr will be written after (used for CSR access check) */
    int csr_read(uint32_t *pval, uint32_t csr,
    bool will_write)
    {
    uint32_t val;

    if (((csr & 0xc00) == 0xc00) && will_write)
    return -1; /* read-only CSR */
    if (priv < ((csr >> 8) & 3))
    return -1; /* not enough priviledge */

    switch(csr) {
    case 0xc00: /* ucycle */
    case 0xc02: /* uinstret */
    {
    uint32_t counteren;
    if (priv < PRV_M) {
    if (priv < PRV_S)
    counteren = scounteren;
    else
    counteren = mcounteren;
    if (((counteren >> (csr & 0x1f)) & 1) == 0) {
    invalid_csr(pval, csr);
    return -1;
    }
    }
    }
    val = (int64_t)insn_counter;
    break;
    case 0xc80: /* mcycleh */
    case 0xc82: /* minstreth */
    {
    uint32_t counteren;
    if (priv < PRV_M) {
    if (priv < PRV_S)
    counteren = scounteren;
    else
    counteren = mcounteren;
    if (((counteren >> (csr & 0x1f)) & 1) == 0) {
    invalid_csr(pval, csr);
    return -1;
    }
    }
    }
    val = insn_counter >> 32;
    break;

    case 0x100:
    val = get_mstatus(SSTATUS_MASK);
    break;
    case 0x104: /* sie */
    val = mie & mideleg;
    break;
    case 0x105:
    val = stvec;
    break;
    case 0x106:
    val = scounteren;
    break;
    case 0x140:
    val = sscratch;
    break;
    case 0x141:
    val = sepc;
    break;
    case 0x142:
    val = scause;
    break;
    case 0x143:
    val = stval;
    break;
    case 0x144: /* sip */
    val = mip & mideleg;
    break;
    case 0x180:
    val = satp;
    break;
    case 0x300:
    val = get_mstatus((uint32_t)-1);
    break;
    case 0x301:
    val = misa;
    val |= (uint32_t)mxl << (XLEN - 2);
    break;
    case 0x302:
    val = medeleg;
    break;
    case 0x303:
    val = mideleg;
    break;
    case 0x304:
    val = mie;
    break;
    case 0x305:
    val = mtvec;
    break;
    case 0x306:
    val = mcounteren;
    break;
    case 0x340:
    val = mscratch;
    break;
    case 0x341:
    val = mepc;
    break;
    case 0x342:
    val = mcause;
    break;
    case 0x343:
    val = mtval;
    break;
    case 0x344:
    val = mip;
    break;
    case 0xb00: /* mcycle */
    case 0xb02: /* minstret */
    val = (int64_t)insn_counter;
    break;
    case 0xb80: /* mcycleh */
    case 0xb82: /* minstreth */
    val = insn_counter >> 32;
    break;
    case 0xf14:
    val = mhartid;
    break;
    default:
    invalid_csr(pval, csr);
    return -1;
    }
    *pval = val;
    return 0;
    }

    /* return -1 if invalid CSR, 0 if OK, 1 if the interpreter loop must be
    exited (e.g. XLEN was modified), 2 if TLBs have been flushed. */
    int csr_write(uint32_t csr, uint32_t val)
    {
    uint32_t mask;

    #if defined(DUMP_CSR)
    printf("csr_write: csr=0x%03x val=0x", csr);
    print_uint32_t(val);
    printf("\n");
    #endif
    switch(csr) {
    case 0x100: /* sstatus */
    set_mstatus((mstatus & ~SSTATUS_MASK) | (val & SSTATUS_MASK));
    break;
    case 0x104: /* sie */
    mask = mideleg;
    mie = (mie & ~mask) | (val & mask);
    break;
    case 0x105:
    stvec = val & ~3;
    break;
    case 0x106:
    scounteren = val & COUNTEREN_MASK;
    break;
    case 0x140:
    sscratch = val;
    break;
    case 0x141:
    sepc = val & ~1;
    break;
    case 0x142:
    scause = val;
    break;
    case 0x143:
    stval = val;
    break;
    case 0x144: /* sip */
    mask = mideleg;
    mip = (mip & ~mask) | (val & mask);
    break;
    case 0x180:
    /* no ASID implemented */
    {
    int new_mode;
    new_mode = (val >> 31) & 1;
    satp = (val & (((uint32_t)1 << 22) - 1)) |
    (new_mode << 31);
    }
    //tlb_flush_all(s);
    return 2;

    case 0x300:
    set_mstatus(val);
    break;
    case 0x301: /* misa */
    break;
    case 0x302:
    mask = (1 << (CAUSE_STORE_PAGE_FAULT + 1)) - 1;
    medeleg = (medeleg & ~mask) | (val & mask);
    break;
    case 0x303:
    mask = MIP_SSIP | MIP_STIP | MIP_SEIP;
    mideleg = (mideleg & ~mask) | (val & mask);
    break;
    case 0x304:
    mask = MIP_MSIP | MIP_MTIP | MIP_SSIP | MIP_STIP | MIP_SEIP;
    mie = (mie & ~mask) | (val & mask);
    break;
    case 0x305:
    mtvec = val & ~3;
    break;
    case 0x306:
    mcounteren = val & COUNTEREN_MASK;
    break;
    case 0x340:
    mscratch = val;
    break;
    case 0x341:
    mepc = val & ~1;
    break;
    case 0x342:
    mcause = val;
    break;
    case 0x343:
    mtval = val;
    break;
    case 0x344:
    mask = MIP_SSIP | MIP_STIP;
    mip = (mip & ~mask) | (val & mask);
    break;
    default:
    return -1;
    }
    return 0;
    }

    void handle_sret()
    {
    int spp, spie;
    spp = (mstatus >> MSTATUS_SPP_SHIFT) & 1;
    /* set the IE state to previous IE state */
    spie = (mstatus >> MSTATUS_SPIE_SHIFT) & 1;
    mstatus = (mstatus & ~(1 << spp)) |
    (spie << spp);
    /* set SPIE to 1 */
    mstatus |= MSTATUS_SPIE;
    /* set SPP to U */
    mstatus &= ~MSTATUS_SPP;
    priv = spp;
    next_pc = sepc;
    }

    void handle_mret()
    {
    int mpp, mpie;
    mpp = (mstatus >> MSTATUS_MPP_SHIFT) & 3;
    /* set the IE state to previous IE state */
    mpie = (mstatus >> MSTATUS_MPIE_SHIFT) & 1;
    mstatus = (mstatus & ~(1 << mpp)) |
    (mpie << mpp);
    /* set MPIE to 1 */
    mstatus |= MSTATUS_MPIE;
    /* set MPP to U */
    mstatus &= ~MSTATUS_MPP;
    priv = mpp;
    next_pc = mepc;
    }


    void raise_exception(uint32_t cause,
    uint32_t tval)
    {
    bool deleg;
    uint32_t causel;

    if (priv <= PRV_S) {
    /* delegate the exception to the supervisor priviledge */
    if (cause & CAUSE_INTERRUPT)
    deleg = (mideleg >> (cause & (XLEN - 1))) & 1;
    else
    deleg = (medeleg >> cause) & 1;
    } else {
    deleg = 0;
    }

    causel = cause & 0x7fffffff;
    if (cause & CAUSE_INTERRUPT)
    causel |= (uint32_t)1 << (XLEN - 1);

    if (deleg) {
    scause = causel;
    sepc = pc;
    stval = tval;
    mstatus = (mstatus & ~MSTATUS_SPIE) |
    (((mstatus >> priv) & 1) << MSTATUS_SPIE_SHIFT);
    mstatus = (mstatus & ~MSTATUS_SPP) |
    (priv << MSTATUS_SPP_SHIFT);
    mstatus &= ~MSTATUS_SIE;
    priv = PRV_S;
    next_pc = stvec;
    } else {
    mcause = causel;
    mepc = pc;
    mtval = tval;
    mstatus = (mstatus & ~MSTATUS_MPIE) |
    (((mstatus >> priv) & 1) << MSTATUS_MPIE_SHIFT);
    mstatus = (mstatus & ~MSTATUS_MPP) |
    (priv << MSTATUS_MPP_SHIFT);
    mstatus &= ~MSTATUS_MIE;
    priv = PRV_M;
    next_pc = mtvec;
    }
    }

    uint32_t get_pending_irq_mask()
    {
    uint32_t pending_ints, enabled_ints;

    pending_ints = mip & mie;
    if (pending_ints == 0)
    return 0;

    enabled_ints = 0;
    switch(priv) {
    case PRV_M:
    if (mstatus & MSTATUS_MIE)
    enabled_ints = ~mideleg;
    break;
    case PRV_S:
    enabled_ints = ~mideleg;
    if (mstatus & MSTATUS_SIE)
    enabled_ints |= mideleg;
    break;
    default:
    case PRV_U:
    enabled_ints = -1;
    break;
    }
    return pending_ints & enabled_ints;
    }

    int raise_interrupt()
    {
    uint32_t mask;
    int irq_num;

    mask = get_pending_irq_mask();
    if (mask == 0)
    return 0;
    irq_num = ctz32(mask);
    raise_exception(irq_num | CAUSE_INTERRUPT, 0);
    return -1;
    }

    uint32_t get_insn32(uint32_t pc)
    {
    uint32_t ptr = pc - ram_start;
    if (ptr > RAM_SIZE) return 1;
    uint8_t* p = ram + ptr;
    return p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
    }

    int target_read_u8(uint8_t *pval, uint32_t addr)
    {
    addr -= ram_start;
    if (addr > RAM_SIZE) return 1;
    uint8_t* p = ram + addr;
    *pval = p[0];
    return 0;
    }

    int target_read_u16(uint16_t *pval, uint32_t addr)
    {
    if (addr & 1) {
    pending_exception = CAUSE_MISALIGNED_LOAD;
    pending_tval = addr;
    return 1;
    }
    addr -= ram_start;
    if (addr > RAM_SIZE) return 1;
    uint8_t* p = ram + addr;
    *pval = p[0] | (p[1] << 8);
    return 0;
    }

    int target_read_u32(uint32_t *pval, uint32_t addr)
    {
    if (addr & 3) {
    pending_exception = CAUSE_MISALIGNED_LOAD;
    pending_tval = addr;
    return 1;
    }
    addr -= ram_start;
    if (addr > RAM_SIZE) return 1;
    uint8_t* p = ram + addr;
    *pval = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
    return 0;
    }

    int target_write_u8(uint32_t addr, uint8_t val)
    {
    if (addr == 0xf0000000) {
    // test for UART output
    printf("%c", val);
    fflush(stdout);
    } else {
    addr -= ram_start;
    if (addr > RAM_SIZE - 1) return 1;
    uint8_t* p = ram + addr;
    p[0] = val & 0xff;
    }
    return 0;
    }

    int target_write_u16(uint32_t addr, uint16_t val)
    {
    if (addr & 1) {
    pending_exception = CAUSE_MISALIGNED_STORE;
    pending_tval = addr;
    return 1;
    }
    addr -= ram_start;
    if (addr > RAM_SIZE - 2) return 1;
    uint8_t* p = ram + addr;
    p[0] = val & 0xff;
    p[1] = (val >> 8) & 0xff;
    return 0;
    }

    int target_write_u32(uint32_t addr, uint32_t val)
    {
    if (addr & 3) {
    pending_exception = CAUSE_MISALIGNED_STORE;
    pending_tval = addr;
    return 1;
    }
    addr -= ram_start;
    if (addr > RAM_SIZE - 4) return 1;
    uint8_t* p = ram + addr;
    p[0] = val & 0xff;
    p[1] = (val >> 8) & 0xff;
    p[2] = (val >> 16) & 0xff;
    p[3] = (val >> 24) & 0xff;
    return 0;
    }


    bool machine_running = true;

    void execute_instruction()
    {
    uint32_t opcode, rd, rs1, rs2, funct3;
    int32_t imm, cond, err;
    uint32_t addr, val, val2;


    opcode = insn & 0x7f;
    rd = (insn >> 7) & 0x1f;
    rs1 = (insn >> 15) & 0x1f;
    rs2 = (insn >> 20) & 0x1f;
    switch(opcode) {

    case 0x37: /* lui */
    if (rd != 0)
    reg[rd] = (int32_t)(insn & 0xfffff000);
    break;
    case 0x17: /* auipc */
    if (rd != 0)
    reg[rd] = (int32_t)(pc + (int32_t)(insn & 0xfffff000));
    break;
    case 0x6f: /* jal */
    imm = ((insn >> (31 - 20)) & (1 << 20)) |
    ((insn >> (21 - 1)) & 0x7fe) |
    ((insn >> (20 - 11)) & (1 << 11)) |
    (insn & 0xff000);
    imm = (imm << 11) >> 11;
    if (rd != 0)
    reg[rd] = pc + 4;
    next_pc = (int32_t)(pc + imm);
    break;
    case 0x67: /* jalr */
    imm = (int32_t)insn >> 20;
    val = pc + 4;
    next_pc = (int32_t)(reg[rs1] + imm) & ~1;
    if (rd != 0)
    reg[rd] = val;
    break;
    case 0x63:
    funct3 = (insn >> 12) & 7;
    switch(funct3 >> 1) {
    case 0: /* beq/bne */
    cond = (reg[rs1] == reg[rs2]);
    break;
    case 2: /* blt/bge */
    cond = ((int32_t)reg[rs1] < (int32_t)reg[rs2]);
    break;
    case 3: /* bltu/bgeu */
    cond = (reg[rs1] < reg[rs2]);
    break;
    default:
    raise_exception(CAUSE_ILLEGAL_INSTRUCTION, insn);
    return;
    }
    cond ^= (funct3 & 1);
    if (cond) {
    imm = ((insn >> (31 - 12)) & (1 << 12)) |
    ((insn >> (25 - 5)) & 0x7e0) |
    ((insn >> (8 - 1)) & 0x1e) |
    ((insn << (11 - 7)) & (1 << 11));
    imm = (imm << 19) >> 19;
    next_pc = (int32_t)(pc + imm);
    break;
    }
    break;
    case 0x03: /* load */
    funct3 = (insn >> 12) & 7;
    imm = (int32_t)insn >> 20;
    addr = reg[rs1] + imm;
    switch(funct3) {
    case 0: /* lb */
    {
    uint8_t rval;
    if (target_read_u8(&rval, addr)) {
    raise_exception(pending_exception, pending_tval);
    return;
    }
    val = (int8_t)rval;
    }
    break;
    case 1: /* lh */
    {
    uint16_t rval;
    if (target_read_u16(&rval, addr)) {
    raise_exception(pending_exception, pending_tval);
    return;
    }
    val = (int16_t)rval;
    }
    break;
    case 2: /* lw */
    {
    uint32_t rval;
    if (target_read_u32(&rval, addr)) {
    raise_exception(pending_exception, pending_tval);
    return;
    }
    val = (int32_t)rval;
    }
    break;
    case 4: /* lbu */
    {
    uint8_t rval;
    if (target_read_u8(&rval, addr)) {
    raise_exception(pending_exception, pending_tval);
    return;
    }
    val = rval;
    }
    break;
    case 5: /* lhu */
    {
    uint16_t rval;
    if (target_read_u16(&rval, addr)) {
    raise_exception(pending_exception, pending_tval);
    return;
    }
    val = rval;
    }
    break;
    default:
    raise_exception(CAUSE_ILLEGAL_INSTRUCTION, insn);
    return;
    }
    if (rd != 0)
    reg[rd] = val;
    break;
    case 0x23: /* store */
    funct3 = (insn >> 12) & 7;
    imm = rd | ((insn >> (25 - 5)) & 0xfe0);
    imm = (imm << 20) >> 20;
    addr = reg[rs1] + imm;
    val = reg[rs2];
    switch(funct3) {
    case 0: /* sb */
    if (target_write_u8(addr, val)) {
    raise_exception(pending_exception, pending_tval);
    return;
    }
    break;
    case 1: /* sh */
    if (target_write_u16(addr, val)) {
    raise_exception(pending_exception, pending_tval);
    return;
    }
    break;
    case 2: /* sw */
    if (target_write_u32(addr, val)) {
    raise_exception(pending_exception, pending_tval);
    return;
    }
    break;
    default:
    raise_exception(CAUSE_ILLEGAL_INSTRUCTION, insn);
    return;
    }
    break;
    case 0x13:
    funct3 = (insn >> 12) & 7;
    imm = (int32_t)insn >> 20;
    switch(funct3) {
    case 0: /* addi */
    val = (int32_t)(reg[rs1] + imm);
    break;
    case 1: /* slli */
    if ((imm & ~(XLEN - 1)) != 0) {
    raise_exception(CAUSE_ILLEGAL_INSTRUCTION, insn);
    return;
    }
    val = (int32_t)(reg[rs1] << (imm & (XLEN - 1)));
    break;
    case 2: /* slti */
    val = (int32_t)reg[rs1] < (int32_t)imm;
    break;
    case 3: /* sltiu */
    val = reg[rs1] < (uint32_t)imm;
    break;
    case 4: /* xori */
    val = reg[rs1] ^ imm;
    break;
    case 5: /* srli/srai */
    if ((imm & ~((XLEN - 1) | 0x400)) != 0) {
    raise_exception(CAUSE_ILLEGAL_INSTRUCTION, insn);
    return;
    }
    if (imm & 0x400)
    val = (int32_t)reg[rs1] >> (imm & (XLEN - 1));
    else
    val = (int32_t)((uint32_t)reg[rs1] >> (imm & (XLEN - 1)));
    break;
    case 6: /* ori */
    val = reg[rs1] | imm;
    break;
    default:
    case 7: /* andi */
    val = reg[rs1] & imm;
    break;
    }
    if (rd != 0)
    reg[rd] = val;
    break;
    case 0x33:
    imm = insn >> 25;
    val = reg[rs1];
    val2 = reg[rs2];
    if (imm == 1) {
    // mul, div and rem not implemented
    raise_exception(CAUSE_ILLEGAL_INSTRUCTION, insn);
    return;
    } else {
    if (imm & ~0x20) {
    raise_exception(CAUSE_ILLEGAL_INSTRUCTION, insn);
    return;
    }
    funct3 = ((insn >> 12) & 7) | ((insn >> (30 - 3)) & (1 << 3));
    switch(funct3) {
    case 0: /* add */
    val = (int32_t)(val + val2);
    break;
    case 0 | 8: /* sub */
    val = (int32_t)(val - val2);
    break;
    case 1: /* sll */
    val = (int32_t)(val << (val2 & (XLEN - 1)));
    break;
    case 2: /* slt */
    val = (int32_t)val < (int32_t)val2;
    break;
    case 3: /* sltu */
    val = val < val2;
    break;
    case 4: /* xor */
    val = val ^ val2;
    break;
    case 5: /* srl */
    val = (int32_t)((uint32_t)val >> (val2 & (XLEN - 1)));
    break;
    case 5 | 8: /* sra */
    val = (int32_t)val >> (val2 & (XLEN - 1));
    break;
    case 6: /* or */
    val = val | val2;
    break;
    case 7: /* and */
    val = val & val2;
    break;
    default:
    raise_exception(CAUSE_ILLEGAL_INSTRUCTION, insn);
    return;
    }
    }
    if (rd != 0)
    reg[rd] = val;
    break;
    case 0x73:
    funct3 = (insn >> 12) & 7;
    imm = insn >> 20;
    if (funct3 & 4)
    val = rs1;
    else
    val = reg[rs1];
    funct3 &= 3;
    switch(funct3) {
    case 1: /* csrrw */
    if (csr_read(&val2, imm, true)) {
    raise_exception(CAUSE_ILLEGAL_INSTRUCTION, insn);
    return;
    }
    val2 = (int32_t)val2;
    err = csr_write(imm, val);
    if (err < 0) {
    raise_exception(CAUSE_ILLEGAL_INSTRUCTION, insn);
    return;
    }
    if (rd != 0)
    reg[rd] = val2;
    if (err > 0) {
    //pc = pc + 4;
    }
    break;
    case 2: /* csrrs */
    case 3: /* csrrc */
    if (csr_read(&val2, imm, (rs1 != 0))) {
    raise_exception(CAUSE_ILLEGAL_INSTRUCTION, insn);
    return;
    }
    val2 = (int32_t)val2;
    if (rs1 != 0) {
    if (funct3 == 2)
    val = val2 | val;
    else
    val = val2 & ~val;
    err = csr_write(imm, val);
    if (err < 0) {
    raise_exception(CAUSE_ILLEGAL_INSTRUCTION, insn);
    return;
    }
    } else {
    err = 0;
    }
    if (rd != 0)
    reg[rd] = val2;
    break;
    case 0:
    switch(imm) {
    case 0x000: /* ecall */
    if (insn & 0x000fff80) {
    raise_exception(CAUSE_ILLEGAL_INSTRUCTION, insn);
    return;
    }
    // compliance test specific: if bit 0 of gp (x3) is 0, it is a syscall,
    // otherwise it is the program end, with the exit code in the bits 31:1
    if (reg[3] & 1) {
    //printf("program end, result: %04x\n", reg[3] >> 1);
    machine_running = false;
    return;
    } else {
    //printf("syscall: %04x\n", reg[3]);

    // on real hardware, an exception is raised, the I-ECALL-01 compliance test tests this as well
    raise_exception(CAUSE_USER_ECALL + priv, 0);
    }
    break;

    case 0x001: /* ebreak */
    if (insn & 0x000fff80) {
    raise_exception(CAUSE_ILLEGAL_INSTRUCTION, insn);
    return;
    }
    raise_exception(CAUSE_BREAKPOINT, 0);
    return;
    case 0x102: /* sret */
    {
    if ((insn & 0x000fff80) || (priv < PRV_S)) {
    raise_exception(CAUSE_ILLEGAL_INSTRUCTION, insn);
    return;
    }
    handle_sret();
    return;
    }
    break;
    case 0x302: /* mret */
    {
    if ((insn & 0x000fff80) || (priv < PRV_M)) {
    raise_exception(CAUSE_ILLEGAL_INSTRUCTION, insn);
    return;
    }
    handle_mret();
    return;
    }
    break;
    default:
    if ((imm >> 5) == 0x09) {
    /* sfence.vma */
    if ((insn & 0x00007f80) || (priv == PRV_U)) {
    raise_exception(CAUSE_ILLEGAL_INSTRUCTION, insn);
    return;
    }
    } else {
    raise_exception(CAUSE_ILLEGAL_INSTRUCTION, insn);
    return;
    }
    break;
    }
    break;
    default:
    raise_exception(CAUSE_ILLEGAL_INSTRUCTION, insn);
    return;
    }
    break;
    case 0x0f: /* misc-mem */
    funct3 = (insn >> 12) & 7;
    switch(funct3) {
    case 0: /* fence */
    if (insn & 0xf00fff80) {
    raise_exception(CAUSE_ILLEGAL_INSTRUCTION, insn);
    return;
    }
    break;
    case 1: /* fence.i */
    if (insn != 0x0000100f) {
    raise_exception(CAUSE_ILLEGAL_INSTRUCTION, insn);
    return;
    }
    break;
    default:
    raise_exception(CAUSE_ILLEGAL_INSTRUCTION, insn);
    return;
    }
    break;
    case 0x2f:
    funct3 = (insn >> 12) & 7;
    switch(funct3) {
    case 2:
    {
    uint32_t rval;

    addr = reg[rs1];
    funct3 = insn >> 27;
    switch(funct3) {
    case 2: /* lr.w */
    if (rs2 != 0) {
    raise_exception(CAUSE_ILLEGAL_INSTRUCTION, insn);
    return;
    }
    if (target_read_u32(&rval, addr)) {
    raise_exception(pending_exception, pending_tval);
    return;
    }
    val = (int32_t)rval;
    load_res = addr;
    break;
    case 3: /* sc.w */
    if (load_res == addr) {
    if (target_write_u32(addr, reg[rs2])) {
    raise_exception(pending_exception, pending_tval);
    return;
    }
    val = 0;
    } else {
    val = 1;
    }
    break;
    case 1: /* amiswap.w */
    case 0: /* amoadd.w */
    case 4: /* amoxor.w */
    case 0xc: /* amoand.w */
    case 0x8: /* amoor.w */
    case 0x10: /* amomin.w */
    case 0x14: /* amomax.w */
    case 0x18: /* amominu.w */
    case 0x1c: /* amomaxu.w */
    if (target_read_u32(&rval, addr)) {
    raise_exception(pending_exception, pending_tval);
    return;
    }
    val = (int32_t)rval;
    val2 = reg[rs2];
    switch(funct3) {
    case 1: /* amiswap.w */
    break;
    case 0: /* amoadd.w */
    val2 = (int32_t)(val + val2);
    break;
    case 4: /* amoxor.w */
    val2 = (int32_t)(val ^ val2);
    break;
    case 0xc: /* amoand.w */
    val2 = (int32_t)(val & val2);
    break;
    case 0x8: /* amoor.w */
    val2 = (int32_t)(val | val2);
    break;
    case 0x10: /* amomin.w */
    if ((int32_t)val < (int32_t)val2)
    val2 = (int32_t)val;
    break;
    case 0x14: /* amomax.w */
    if ((int32_t)val > (int32_t)val2)
    val2 = (int32_t)val;
    break;
    case 0x18: /* amominu.w */
    if ((uint32_t)val < (uint32_t)val2)
    val2 = (int32_t)val;
    break;
    case 0x1c: /* amomaxu.w */
    if ((uint32_t)val > (uint32_t)val2)
    val2 = (int32_t)val;
    break;
    default:
    raise_exception(CAUSE_ILLEGAL_INSTRUCTION, insn);
    return;
    }
    if (target_write_u32(addr, val2)) {
    raise_exception(pending_exception, pending_tval);
    return;
    }
    break;
    default:
    raise_exception(CAUSE_ILLEGAL_INSTRUCTION, insn);
    return;
    }
    }
    break;
    default:
    raise_exception(CAUSE_ILLEGAL_INSTRUCTION, insn);
    return;
    }
    if (rd != 0)
    reg[rd] = val;
    break;
    default:
    raise_exception(CAUSE_ILLEGAL_INSTRUCTION, insn);
    return;
    }
    }


    void riscv_cpu_interp_x32()
    {
    pending_exception = -1;

    /* we use a single execution loop to keep a simple control flow
    for emscripten */
    while (machine_running) {
    if ((mip & mie) != 0) {
    raise_interrupt();
    }

    // test for misaligned fetches
    if (next_pc & 3) {
    raise_exception(CAUSE_MISALIGNED_FETCH, next_pc);
    }

    // update current PC
    pc = next_pc;

    // default value for next PC is next instruction, can be changed by branchces or exceptions
    next_pc = pc + 4;

    /* fast path */
    insn = get_insn32(pc);
    insn_counter++;

    execute_instruction();

    if (pending_exception >= 0) {
    raise_exception(pending_exception, pending_tval);
    pending_exception = -1;
    }
    }

    printf("done interp %lx int=%x mstatus=%lx prv=%d\n",
    (uint64_t)insn_counter, mip & mie, (uint64_t)mstatus,
    priv);
    }

    int main(int argc, char** argv)
    {
    // parse command line
    char* elf_file = NULL;
    char* signature_file = NULL;
    for (int i = 1; i < argc; i++) {
    char* arg = argv[i];
    if (arg == strstr(arg, "+signature=")) {
    signature_file = arg + 11;
    //printf("signature file: %s\n", signature_file);
    } else if (arg[0] != '-') {
    elf_file = arg;
    //printf("ELF file: %s\n", elf_file);
    }
    }
    if (elf_file == NULL) {
    printf("missing ELF file\n");
    return 1;
    }

    // open ELF file
    elf_version(EV_CURRENT);
    int fd = open(elf_file, O_RDONLY);
    Elf *elf = elf_begin(fd, ELF_C_READ, NULL);

    // scan for symbol table
    uint32_t begin_signature = 0;
    uint32_t end_signature = 0;
    Elf_Scn *scn = NULL;
    GElf_Shdr shdr;
    while ((scn = elf_nextscn(elf, scn)) != NULL) {
    gelf_getshdr(scn, &shdr);
    if (shdr.sh_type == SHT_SYMTAB) {
    Elf_Data *data = elf_getdata(scn, NULL);
    int count = shdr.sh_size / shdr.sh_entsize;
    for (int i = 0; i < count; i++) {
    GElf_Sym sym;
    gelf_getsym(data, i, &sym);
    char* name = elf_strptr(elf, shdr.sh_link, sym.st_name);
    if (strcmp(name, "begin_signature") == 0) {
    begin_signature = sym.st_value;
    }
    if (strcmp(name, "end_signature") == 0) {
    end_signature = sym.st_value;
    }
    if (strcmp(name, "_start") == 0) {
    ram_start = sym.st_value;
    }
    }
    }
    }
    //printf("begin_signature: 0x%04x\n", begin_signature);
    //printf("end_signature: 0x%04x\n", end_signature);
    //printf("start: 0x%04x\n", ram_start);

    // scan for program
    while ((scn = elf_nextscn(elf, scn)) != NULL) {
    gelf_getshdr(scn, &shdr);
    if (shdr.sh_type == SHT_PROGBITS) {
    Elf_Data *data = elf_getdata(scn, NULL);
    for (size_t i = 0; i < shdr.sh_size; i++) {
    ram[shdr.sh_addr + i - ram_start] = ((uint8_t *)data->d_buf)[i];
    }
    }
    }

    // close ELF file
    elf_end(elf);
    close(fd);

    // run program in emulator
    next_pc = ram_start;
    riscv_cpu_interp_x32();

    // write signature
    if (signature_file) {
    FILE* sf = fopen(signature_file, "w");
    int size = end_signature - begin_signature;
    for (int i = 0; i < size / 16; i++) {
    for (int j = 0; j < 16; j++) {
    fprintf(sf, "%02x", ram[begin_signature + 15 - j - ram_start]);
    }
    begin_signature += 16;
    fprintf(sf, "\n");
    }
    fclose(sf);
    }

    return 0;
    }