Skip to content

Instantly share code, notes, and snippets.

@FrankBuss
Last active November 13, 2024 14:40
Show Gist options
  • Save FrankBuss/c974e59826d33e21d7cad54491ab50e8 to your computer and use it in GitHub Desktop.
Save FrankBuss/c974e59826d33e21d7cad54491ab50e8 to your computer and use it in GitHub Desktop.
RISC-V emulator (RV32I only) in one C++ file
/*
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
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
*
* 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 <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 0x10000
uint8_t ram[RAM_SIZE];
// 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
#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
#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)
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)
{
fs = (val >> MSTATUS_FS_SHIFT) & 3;
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) {
#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)
{
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;
return 0;
}
*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);
}
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 0;
// 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;
// exit for Zephyr applications
if (cause == CAUSE_ILLEGAL_INSTRUCTION) {
machine_running = false;
return;
}
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;
}
if (deleg) {
scause = cause;
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 = cause;
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) {
*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;
}
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) {
*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;
}
int target_read_u32(uint32_t *pval, uint32_t addr)
{
if (addr & 3) {
pending_exception = CAUSE_MISALIGNED_LOAD;
pending_tval = addr;
return 1;
}
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 == UART_TX_ADDR) {
// test for UART output, compatible with QEMU
printf("%c", val);
} else {
addr -= ram_start;
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;
}
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) {
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;
}
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;
}
}
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) {
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) {
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);
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 (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 {
// 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;
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 0x105: /* wfi */
// wait for interrupt: it is allowed to execute it as nop
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;
}
}
// 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()
{
/* we use a single execution loop to keep a simple control flow
for emscripten */
while (machine_running) {
// 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
if (next_pc & 3) {
raise_exception(CAUSE_MISALIGNED_FETCH, next_pc);
}
// update current PC
pc = next_pc;
}
#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
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;
} else if (arg[0] != '-') {
elf_file = arg;
}
}
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);
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
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;
}
// 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;
}
}
}
}
#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);
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
}
}
}
// close ELF file
elf_end(elf);
close(fd);
// run program in emulator
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;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment