diff --git a/README.md b/README.md index 5eed713..af12f8b 100644 --- a/README.md +++ b/README.md @@ -153,6 +153,7 @@ int main(int argc, char *argv[]) { * [emulator](emulator) vm host which loads a binary and runs to completion, handling multiple ioreq types * [emulator-mini](emulator-mini) minimal vm host (shown above), with baked in bytecode * [emulator-parallel](emulator-parallel) parallel vm host running multiple vm instances concurrently, with baked in bytecode + * [emulator-arduino](emulator-arduino) vm host as Arduino sketch (tested on Arduino Uno ATmega328P, uses 9950 bytes of flash/1254 bytes RAM) * [apps/helloworld](apps/helloworld) C hello world program * [apps/sketch](apps/sketch) C Arduino/Wiring/Processing type program in `setup()` and `loop()` style * [apps/rust-hello](apps/rust-hello) Rust hello world program diff --git a/emulator-arduino/common/uvm32_common_custom.h b/emulator-arduino/common/uvm32_common_custom.h new file mode 100644 index 0000000..e5c0a35 --- /dev/null +++ b/emulator-arduino/common/uvm32_common_custom.h @@ -0,0 +1,10 @@ +// Definitions needed by both host and target + +// CSRs for exposed host functions +#define IOREQ_PRINT 0x13A +#define IOREQ_PRINTLN 0x13B +#define IOREQ_PRINTD 0x13C +#define IOREQ_PRINTX 0x13D +#define IOREQ_MILLIS 0x13F +#define IOREQ_PRINTC 0x140 + diff --git a/emulator-arduino/common/uvm32_sys.h b/emulator-arduino/common/uvm32_sys.h new file mode 100644 index 0000000..5b2992a --- /dev/null +++ b/emulator-arduino/common/uvm32_sys.h @@ -0,0 +1,5 @@ +// System provided IOREQs +#define IOREQ_HALT 0x138 +#define IOREQ_YIELD 0x139 + +#include "uvm32_common_custom.h" diff --git a/emulator-arduino/config.h b/emulator-arduino/config.h new file mode 100644 index 0000000..bb9df17 --- /dev/null +++ b/emulator-arduino/config.h @@ -0,0 +1,2 @@ +// Arduino cannot do -DUVM32_MEMORY_SIZE, so set this explicitly +#define UVM32_MEMORY_SIZE 600 diff --git a/emulator-arduino/emulator-arduino.ino b/emulator-arduino/emulator-arduino.ino new file mode 100644 index 0000000..269b443 --- /dev/null +++ b/emulator-arduino/emulator-arduino.ino @@ -0,0 +1,102 @@ +#include "config.h" +#include "uvm32.h" +#include "common/uvm32_common_custom.h" + +// Precompiled binary program to print integers +// This code expects to print via CSR 0x13C (IOREQ_PRINTD in common/uvm32_common_custom.h) +uint8_t rom[] = { + 0x23, 0x26, 0x11, 0x00, 0xef, 0x00, 0x80, 0x00, 0x73, 0x50, 0x80, 0x13, + 0x37, 0xf6, 0xff, 0xff, 0xb7, 0x17, 0x00, 0x00, 0x37, 0xe7, 0xff, 0xff, + 0x13, 0x05, 0xf0, 0x01, 0xb7, 0x45, 0x00, 0x00, 0x13, 0x06, 0xd6, 0xcc, + 0x93, 0x86, 0x37, 0x33, 0x13, 0x07, 0x77, 0xe6, 0x93, 0x87, 0x37, 0xb3, + 0x13, 0x08, 0xa0, 0x00, 0x63, 0xcc, 0xc6, 0x06, 0x93, 0x08, 0x07, 0x00, + 0x63, 0xc2, 0xe7, 0x06, 0x93, 0x03, 0x00, 0x00, 0x13, 0x03, 0x00, 0x00, + 0x13, 0x0e, 0x00, 0x00, 0x93, 0x0e, 0x00, 0x00, 0x93, 0x02, 0x00, 0x02, + 0x13, 0x8f, 0x02, 0xfe, 0x63, 0x6e, 0xe5, 0x03, 0x33, 0x0f, 0x73, 0x00, + 0x63, 0xea, 0xe5, 0x03, 0x33, 0x8e, 0xce, 0x03, 0xb3, 0x0e, 0x73, 0x40, + 0x73, 0x50, 0x90, 0x13, 0x13, 0x5e, 0xbe, 0x40, 0xb3, 0x8e, 0x1e, 0x01, + 0x33, 0x0e, 0xce, 0x00, 0x33, 0x83, 0xde, 0x03, 0x13, 0x53, 0xc3, 0x00, + 0xb3, 0x03, 0xce, 0x03, 0x93, 0xd3, 0xc3, 0x00, 0x93, 0x82, 0x12, 0x00, + 0x6f, 0xf0, 0x5f, 0xfc, 0x73, 0x90, 0x02, 0x14, 0x93, 0x88, 0x18, 0x09, + 0xe3, 0xd2, 0x17, 0xfb, 0x73, 0x10, 0x08, 0x14, 0x13, 0x06, 0x96, 0x19, + 0xe3, 0xd8, 0xc6, 0xf8, 0x37, 0x05, 0x00, 0x80, 0x13, 0x05, 0x05, 0x0c, + 0x73, 0x10, 0xb5, 0x13, 0x67, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x00 +}; + +// Create an identifier for our host handler +typedef enum { + F_PRINTD, + F_PRINTLN, + F_PRINTC, +} f_code_t; + +// Map VM ioreq IOREQ_PRINTD (0x13C) to F_PRINTD, tell VM to expect write of a U32 +const uvm32_mapping_t env[] = { + { IOREQ_PRINTD, F_PRINTD, IOREQ_TYP_U32_WR }, + { IOREQ_PRINTC, F_PRINTC, IOREQ_TYP_U32_WR }, + { IOREQ_PRINTLN, F_PRINTLN, IOREQ_TYP_BUF_TERMINATED_WR }, +}; + +uvm32_state_t vmst; +uvm32_evt_t evt; +bool isrunning = false; +uint32_t led_time = 0; +bool led_state = false; + +void setup(void) { + Serial.begin(115200); + pinMode(LED_BUILTIN, OUTPUT); + isrunning = false; +} + +void loop(void) { + // flash LED rapidly to show main loop is still running + if (millis() > led_time + 50) { + digitalWrite(LED_BUILTIN, led_state); + led_state = !led_state; + led_time = millis(); + } + + if (isrunning) { + uvm32_run(&vmst, &evt, 100); // num instructions before vm considered hung + + switch(evt.typ) { + case UVM32_EVT_END: + isrunning = false; + break; + case UVM32_EVT_IOREQ: // vm has paused to handle IOREQ + switch((f_code_t)evt.data.ioreq.code) { + case F_PRINTD: + Serial.println(evt.data.ioreq.val.u32); + break; + case F_PRINTC: + Serial.print((char)evt.data.ioreq.val.u32); + break; + case F_PRINTLN: + for (int i=0;ix +#define SETCSR( x, val ) { state->x = val; } +#define REG( x ) state->regs[x] +#define REGSET( x, val ) { state->regs[x] = val; } +#endif + +#ifndef MINIRV32_STEPPROTO +MINIRV32_DECORATE int32_t MiniRV32IMAStep(void *userdata, struct MiniRV32IMAState * state, uint8_t * image, uint32_t vProcAddress, uint32_t elapsedUs, int count ) +#else +MINIRV32_STEPPROTO +#endif +{ + uint32_t new_timer = CSR( timerl ) + elapsedUs; + if( new_timer < CSR( timerl ) ) CSR( timerh )++; + CSR( timerl ) = new_timer; + + // Handle Timer interrupt. + if( ( CSR( timerh ) > CSR( timermatchh ) || ( CSR( timerh ) == CSR( timermatchh ) && CSR( timerl ) > CSR( timermatchl ) ) ) && ( CSR( timermatchh ) || CSR( timermatchl ) ) ) + { + CSR( extraflags ) &= ~4; // Clear WFI + CSR( mip ) |= 1<<7; //MTIP of MIP // https://stackoverflow.com/a/61916199/2926815 Fire interrupt. + } + else + CSR( mip ) &= ~(1<<7); + + // If WFI, don't run processor. + if( CSR( extraflags ) & 4 ) + return 1; + + uint32_t trap = 0; + uint32_t rval = 0; + uint32_t pc = CSR( pc ); + uint32_t cycle = CSR( cyclel ); + + if( ( CSR( mip ) & (1<<7) ) && ( CSR( mie ) & (1<<7) /*mtie*/ ) && ( CSR( mstatus ) & 0x8 /*mie*/) ) + { + // Timer interrupt. + trap = 0x80000007; + pc -= 4; + } + else // No timer interrupt? Execute a bunch of instructions. + for( int icount = 0; icount < count; icount++ ) + { + uint32_t ir = 0; + rval = 0; + cycle++; + uint32_t ofs_pc = pc - MINIRV32_RAM_IMAGE_OFFSET; + + if( ofs_pc >= MINI_RV32_RAM_SIZE ) + { + trap = 1 + 1; // Handle access violation on instruction read. + break; + } + else if( ofs_pc & 3 ) + { + trap = 1 + 0; //Handle PC-misaligned access + break; + } + else + { + ir = MINIRV32_LOAD4( ofs_pc ); + uint32_t rdid = (ir >> 7) & 0x1f; + + switch( ir & 0x7f ) + { + case 0x37: // LUI (0b0110111) + rval = ( ir & 0xfffff000 ); + break; + case 0x17: // AUIPC (0b0010111) + rval = pc + ( ir & 0xfffff000 ); + break; + case 0x6F: // JAL (0b1101111) + { + int32_t reladdy = ((ir & 0x80000000)>>11) | ((ir & 0x7fe00000)>>20) | ((ir & 0x00100000)>>9) | ((ir&0x000ff000)); + if( reladdy & 0x00100000 ) reladdy |= 0xffe00000; // Sign extension. + rval = pc + 4; + pc = pc + reladdy - 4; + break; + } + case 0x67: // JALR (0b1100111) + { + uint32_t imm = ir >> 20; + int32_t imm_se = imm | (( imm & 0x800 )?0xfffff000:0); + rval = pc + 4; + pc = ( (REG( (ir >> 15) & 0x1f ) + imm_se) & ~1) - 4; + break; + } + case 0x63: // Branch (0b1100011) + { + uint32_t immm4 = ((ir & 0xf00)>>7) | ((ir & 0x7e000000)>>20) | ((ir & 0x80) << 4) | ((ir >> 31)<<12); + if( immm4 & 0x1000 ) immm4 |= 0xffffe000; + int32_t rs1 = REG((ir >> 15) & 0x1f); + int32_t rs2 = REG((ir >> 20) & 0x1f); + immm4 = pc + immm4 - 4; + rdid = 0; + switch( ( ir >> 12 ) & 0x7 ) + { + // BEQ, BNE, BLT, BGE, BLTU, BGEU + case 0: if( rs1 == rs2 ) pc = immm4; break; + case 1: if( rs1 != rs2 ) pc = immm4; break; + case 4: if( rs1 < rs2 ) pc = immm4; break; + case 5: if( rs1 >= rs2 ) pc = immm4; break; //BGE + case 6: if( (uint32_t)rs1 < (uint32_t)rs2 ) pc = immm4; break; //BLTU + case 7: if( (uint32_t)rs1 >= (uint32_t)rs2 ) pc = immm4; break; //BGEU + default: trap = (2+1); + } + break; + } + case 0x03: // Load (0b0000011) + { + uint32_t rs1 = REG((ir >> 15) & 0x1f); + uint32_t imm = ir >> 20; + int32_t imm_se = imm | (( imm & 0x800 )?0xfffff000:0); + uint32_t rsval = rs1 + imm_se; + + rsval -= MINIRV32_RAM_IMAGE_OFFSET; + if( rsval >= MINI_RV32_RAM_SIZE-3 ) + { + rsval += MINIRV32_RAM_IMAGE_OFFSET; + if( MINIRV32_MMIO_RANGE( rsval ) ) // UART, CLNT + { + MINIRV32_HANDLE_MEM_LOAD_CONTROL( rsval, rval ); + } + else + { + trap = (5+1); + rval = rsval; + } + } + else + { + switch( ( ir >> 12 ) & 0x7 ) + { + //LB, LH, LW, LBU, LHU + case 0: rval = MINIRV32_LOAD1_SIGNED( rsval ); break; + case 1: rval = MINIRV32_LOAD2_SIGNED( rsval ); break; + case 2: rval = MINIRV32_LOAD4( rsval ); break; + case 4: rval = MINIRV32_LOAD1( rsval ); break; + case 5: rval = MINIRV32_LOAD2( rsval ); break; + default: trap = (2+1); + } + } + break; + } + case 0x23: // Store 0b0100011 + { + uint32_t rs1 = REG((ir >> 15) & 0x1f); + uint32_t rs2 = REG((ir >> 20) & 0x1f); + uint32_t addy = ( ( ir >> 7 ) & 0x1f ) | ( ( ir & 0xfe000000 ) >> 20 ); + if( addy & 0x800 ) addy |= 0xfffff000; + addy += rs1 - MINIRV32_RAM_IMAGE_OFFSET; + rdid = 0; + + if( addy >= MINI_RV32_RAM_SIZE-3 ) + { + addy += MINIRV32_RAM_IMAGE_OFFSET; + if( MINIRV32_MMIO_RANGE( addy ) ) + { + MINIRV32_HANDLE_MEM_STORE_CONTROL( addy, rs2 ); + } + else + { + trap = (7+1); // Store access fault. + rval = addy; + } + } + else + { + switch( ( ir >> 12 ) & 0x7 ) + { + //SB, SH, SW + case 0: MINIRV32_STORE1( addy, rs2 ); break; + case 1: MINIRV32_STORE2( addy, rs2 ); break; + case 2: MINIRV32_STORE4( addy, rs2 ); break; + default: trap = (2+1); + } + } + break; + } + case 0x13: // Op-immediate 0b0010011 + case 0x33: // Op 0b0110011 + { + uint32_t imm = ir >> 20; + imm = imm | (( imm & 0x800 )?0xfffff000:0); + uint32_t rs1 = REG((ir >> 15) & 0x1f); + uint32_t is_reg = !!( ir & 0x20 ); + uint32_t rs2 = is_reg ? REG(imm & 0x1f) : imm; + + if( is_reg && ( ir & 0x02000000 ) ) + { + switch( (ir>>12)&7 ) //0x02000000 = RV32M + { + case 0: rval = rs1 * rs2; break; // MUL +#ifndef CUSTOM_MULH // If compiling on a system that doesn't natively, or via libgcc support 64-bit math. + case 1: rval = ((int64_t)((int32_t)rs1) * (int64_t)((int32_t)rs2)) >> 32; break; // MULH + case 2: rval = ((int64_t)((int32_t)rs1) * (uint64_t)rs2) >> 32; break; // MULHSU + case 3: rval = ((uint64_t)rs1 * (uint64_t)rs2) >> 32; break; // MULHU +#else + CUSTOM_MULH +#endif + case 4: if( rs2 == 0 ) rval = -1; else rval = ((int32_t)rs1 == INT32_MIN && (int32_t)rs2 == -1) ? rs1 : ((int32_t)rs1 / (int32_t)rs2); break; // DIV + case 5: if( rs2 == 0 ) rval = 0xffffffff; else rval = rs1 / rs2; break; // DIVU + case 6: if( rs2 == 0 ) rval = rs1; else rval = ((int32_t)rs1 == INT32_MIN && (int32_t)rs2 == -1) ? 0 : ((uint32_t)((int32_t)rs1 % (int32_t)rs2)); break; // REM + case 7: if( rs2 == 0 ) rval = rs1; else rval = rs1 % rs2; break; // REMU + } + } + else + { + switch( (ir>>12)&7 ) // These could be either op-immediate or op commands. Be careful. + { + case 0: rval = (is_reg && (ir & 0x40000000) ) ? ( rs1 - rs2 ) : ( rs1 + rs2 ); break; + case 1: rval = rs1 << (rs2 & 0x1F); break; + case 2: rval = (int32_t)rs1 < (int32_t)rs2; break; + case 3: rval = rs1 < rs2; break; + case 4: rval = rs1 ^ rs2; break; + case 5: rval = (ir & 0x40000000 ) ? ( ((int32_t)rs1) >> (rs2 & 0x1F) ) : ( rs1 >> (rs2 & 0x1F) ); break; + case 6: rval = rs1 | rs2; break; + case 7: rval = rs1 & rs2; break; + } + } + break; + } + case 0x0f: // 0b0001111 + rdid = 0; // fencetype = (ir >> 12) & 0b111; We ignore fences in this impl. + break; + case 0x73: // Zifencei+Zicsr (0b1110011) + { + uint32_t csrno = ir >> 20; + uint32_t microop = ( ir >> 12 ) & 0x7; + if( (microop & 3) ) // It's a Zicsr function. + { + int rs1imm = (ir >> 15) & 0x1f; + uint32_t rs1 = REG(rs1imm); + uint32_t writeval = rs1; + + // https://raw.githubusercontent.com/riscv/virtual-memory/main/specs/663-Svpbmt.pdf + // Generally, support for Zicsr + switch( csrno ) + { + case 0x340: rval = CSR( mscratch ); break; + case 0x305: rval = CSR( mtvec ); break; + case 0x304: rval = CSR( mie ); break; + case 0xC00: rval = cycle; break; + case 0x344: rval = CSR( mip ); break; + case 0x341: rval = CSR( mepc ); break; + case 0x300: rval = CSR( mstatus ); break; //mstatus + case 0x342: rval = CSR( mcause ); break; + case 0x343: rval = CSR( mtval ); break; + case 0xf11: rval = 0xff0ff0ff; break; //mvendorid + case 0x301: rval = 0x40401101; break; //misa (XLEN=32, IMA+X) + //case 0x3B0: rval = 0; break; //pmpaddr0 + //case 0x3a0: rval = 0; break; //pmpcfg0 + //case 0xf12: rval = 0x00000000; break; //marchid + //case 0xf13: rval = 0x00000000; break; //mimpid + //case 0xf14: rval = 0x00000000; break; //mhartid + default: + MINIRV32_OTHERCSR_READ( csrno, rval ); + break; + } + + switch( microop ) + { + case 1: writeval = rs1; break; //CSRRW + case 2: writeval = rval | rs1; break; //CSRRS + case 3: writeval = rval & ~rs1; break; //CSRRC + case 5: writeval = rs1imm; break; //CSRRWI + case 6: writeval = rval | rs1imm; break; //CSRRSI + case 7: writeval = rval & ~rs1imm; break; //CSRRCI + } + + switch( csrno ) + { + case 0x340: SETCSR( mscratch, writeval ); break; + case 0x305: SETCSR( mtvec, writeval ); break; + case 0x304: SETCSR( mie, writeval ); break; + case 0x344: SETCSR( mip, writeval ); break; + case 0x341: SETCSR( mepc, writeval ); break; + case 0x300: SETCSR( mstatus, writeval ); break; //mstatus + case 0x342: SETCSR( mcause, writeval ); break; + case 0x343: SETCSR( mtval, writeval ); break; + //case 0x3a0: break; //pmpcfg0 + //case 0x3B0: break; //pmpaddr0 + //case 0xf11: break; //mvendorid + //case 0xf12: break; //marchid + //case 0xf13: break; //mimpid + //case 0xf14: break; //mhartid + //case 0x301: break; //misa + default: + MINIRV32_OTHERCSR_WRITE( csrno, writeval ); + break; + } + } + else if( microop == 0x0 ) // "SYSTEM" 0b000 + { + rdid = 0; + if( ( ( csrno & 0xff ) == 0x02 ) ) // MRET + { + //https://raw.githubusercontent.com/riscv/virtual-memory/main/specs/663-Svpbmt.pdf + //Table 7.6. MRET then in mstatus/mstatush sets MPV=0, MPP=0, MIE=MPIE, and MPIE=1. La + // Should also update mstatus to reflect correct mode. + uint32_t startmstatus = CSR( mstatus ); + uint32_t startextraflags = CSR( extraflags ); + SETCSR( mstatus , (( startmstatus & 0x80) >> 4) | ((startextraflags&3) << 11) | 0x80 ); + SETCSR( extraflags, (startextraflags & ~3) | ((startmstatus >> 11) & 3) ); + pc = CSR( mepc ) -4; + } else { + switch (csrno) { + case 0: + trap = ( CSR( extraflags ) & 3) ? (11+1) : (8+1); // ECALL; 8 = "Environment call from U-mode"; 11 = "Environment call from M-mode" + break; + case 1: + trap = (3+1); break; // EBREAK 3 = "Breakpoint" + case 0x105: //WFI (Wait for interrupts) + CSR( mstatus ) |= 8; //Enable interrupts + CSR( extraflags ) |= 4; //Infor environment we want to go to sleep. + SETCSR( pc, pc + 4 ); + return 1; + default: + trap = (2+1); break; // Illegal opcode. + } + } + } + else + trap = (2+1); // Note micrrop 0b100 == undefined. + break; + } + case 0x2f: // RV32A (0b00101111) + { + uint32_t rs1 = REG((ir >> 15) & 0x1f); + uint32_t rs2 = REG((ir >> 20) & 0x1f); + uint32_t irmid = ( ir>>27 ) & 0x1f; + + rs1 -= MINIRV32_RAM_IMAGE_OFFSET; + + // We don't implement load/store from UART or CLNT with RV32A here. + + if( rs1 >= MINI_RV32_RAM_SIZE-3 ) + { + trap = (7+1); //Store/AMO access fault + rval = rs1 + MINIRV32_RAM_IMAGE_OFFSET; + } + else + { + rval = MINIRV32_LOAD4( rs1 ); + + // Referenced a little bit of https://github.com/franzflasch/riscv_em/blob/master/src/core/core.c + uint32_t dowrite = 1; + switch( irmid ) + { + case 2: //LR.W (0b00010) + dowrite = 0; + CSR( extraflags ) = (CSR( extraflags ) & 0x07) | (rs1<<3); + break; + case 3: //SC.W (0b00011) (Make sure we have a slot, and, it's valid) + rval = ( CSR( extraflags ) >> 3 != ( rs1 & 0x1fffffff ) ); // Validate that our reservation slot is OK. + dowrite = !rval; // Only write if slot is valid. + break; + case 1: break; //AMOSWAP.W (0b00001) + case 0: rs2 += rval; break; //AMOADD.W (0b00000) + case 4: rs2 ^= rval; break; //AMOXOR.W (0b00100) + case 12: rs2 &= rval; break; //AMOAND.W (0b01100) + case 8: rs2 |= rval; break; //AMOOR.W (0b01000) + case 16: rs2 = ((int32_t)rs2<(int32_t)rval)?rs2:rval; break; //AMOMIN.W (0b10000) + case 20: rs2 = ((int32_t)rs2>(int32_t)rval)?rs2:rval; break; //AMOMAX.W (0b10100) + case 24: rs2 = (rs2rval)?rs2:rval; break; //AMOMAXU.W (0b11100) + default: trap = (2+1); dowrite = 0; break; //Not supported. + } + if( dowrite ) MINIRV32_STORE4( rs1, rs2 ); + } + break; + } + default: trap = (2+1); // Fault: Invalid opcode. + } + + // If there was a trap, do NOT allow register writeback. + if( trap ) { + SETCSR( pc, pc ); + MINIRV32_POSTEXEC( pc, ir, trap ); + break; + } + + if( rdid ) + { + REGSET( rdid, rval ); // Write back register. + } + } + + MINIRV32_POSTEXEC( pc, ir, trap ); + + pc += 4; + } + + // Handle traps and interrupts. + if( trap ) + { + if( trap & 0x80000000 ) // If prefixed with 1 in MSB, it's an interrupt, not a trap. + { + SETCSR( mcause, trap ); + SETCSR( mtval, 0 ); + pc += 4; // PC needs to point to where the PC will return to. + } + else + { + SETCSR( mcause, trap - 1 ); + SETCSR( mtval, (trap > 5 && trap <= 8)? rval : pc ); + } + SETCSR( mepc, pc ); //TRICKY: The kernel advances mepc automatically. + //CSR( mstatus ) & 8 = MIE, & 0x80 = MPIE + // On an interrupt, the system moves current MIE into MPIE + SETCSR( mstatus, (( CSR( mstatus ) & 0x08) << 4) | (( CSR( extraflags ) & 3 ) << 11) ); + pc = (CSR( mtvec ) - 4); + + // If trapping, always enter machine mode. + CSR( extraflags ) |= 3; + + trap = 0; + pc += 4; + } + + if( CSR( cyclel ) > cycle ) CSR( cycleh )++; + SETCSR( cyclel, cycle ); + SETCSR( pc, pc ); + return 0; +} + +#endif + +#endif + + diff --git a/emulator-arduino/uvm32.cpp b/emulator-arduino/uvm32.cpp new file mode 100644 index 0000000..69da7c4 --- /dev/null +++ b/emulator-arduino/uvm32.cpp @@ -0,0 +1,200 @@ +#include "config.h" +#define MINIRV32_IMPLEMENTATION +#include "uvm32.h" +#include +#include + +#ifndef UVM32_MEMORY_SIZE +#error Define UVM32_MEMORY_SIZE +#endif + +#include "mini-rv32ima.h" + +#define X(name) #name, +static const char *errNames[] = { + LIST_OF_UVM32_ERRS +}; +#undef X + +static void setup_err_evt(uvm32_state_t *vmst, uvm32_evt_t *evt) { + evt->typ = UVM32_EVT_ERR; + evt->data.err.errcode = vmst->err; + evt->data.err.errstr = errNames[vmst->err]; +} + +static void setStatus(uvm32_state_t *vmst, uvm32_status_t newStatus) { + if (vmst->status == UVM32_STATUS_ERROR) { + // always stay in error state until a uvm32_reset() + return; + } else { + vmst->status = newStatus; + } +} + +static void setStatusErr(uvm32_state_t *vmst, uvm32_err_t err) { + setStatus(vmst, UVM32_STATUS_ERROR); + vmst->err = err; +} + +void uvm32_init(uvm32_state_t *vmst, const uvm32_mapping_t *mappings, uint32_t numMappings) { + vmst->status = UVM32_STATUS_PAUSED; + + memset(vmst->memory, 0x00, UVM32_MEMORY_SIZE); + // The core lives at the end of RAM. + vmst->core = (struct MiniRV32IMAState*)(vmst->memory + UVM32_MEMORY_SIZE - sizeof(struct MiniRV32IMAState)); + vmst->core->pc = MINIRV32_RAM_IMAGE_OFFSET; + // https://projectf.io/posts/riscv-cheat-sheet/ + // setup stack pointer + // la sp, _sstack + // addi sp,sp,-16 + vmst->core->regs[2] = (MINIRV32_RAM_IMAGE_OFFSET + UVM32_MEMORY_SIZE) - 16; + vmst->core->regs[10] = 0x00; //hart ID + vmst->core->regs[11] = 0; + vmst->core->extraflags |= 3; // Machine-mode. + + vmst->mappings = mappings; + vmst->numMappings = numMappings; +} + +bool uvm32_load(uvm32_state_t *vmst, uint8_t *rom, int len) { + // RAM needs at least image then MiniRV32IMAState (core) + if (len > UVM32_MEMORY_SIZE - sizeof(struct MiniRV32IMAState)) { + // too big + return false; + } + + memcpy(vmst->memory, rom, len); + return true; +} + +uint32_t uvm32_run(uvm32_state_t *vmst, uvm32_evt_t *evt, uint32_t instr_meter) { + uint32_t num_instr = 0; + if (vmst->status != UVM32_STATUS_PAUSED) { + setStatusErr(vmst, UVM32_ERR_NOTREADY); + setup_err_evt(vmst, evt); + return num_instr; + } + + setStatus(vmst, UVM32_STATUS_RUNNING); + + // run CPU until no longer in running state + while(vmst->status == UVM32_STATUS_RUNNING) { + uint64_t elapsedUs = 1; + if (0 != MiniRV32IMAStep(vmst, vmst->core, vmst->memory, 0, elapsedUs, 1)) { + setStatusErr(vmst, UVM32_ERR_INTERNAL_CORE); + setup_err_evt(vmst, evt); + } + + num_instr++; + + // check instruction meter, in case of hang/infinite loop + if (instr_meter-- == 0) { + setStatusErr(vmst, UVM32_ERR_HUNG); + setup_err_evt(vmst, evt); + return num_instr; + } + } + + if (vmst->status == UVM32_STATUS_ENDED) { + evt->typ = UVM32_EVT_END; + return num_instr; + } + + // an event is ready + if (vmst->status == UVM32_STATUS_PAUSED) { + // send back the built up event + memcpy(evt, &vmst->ioevt, sizeof(uvm32_evt_t)); + return num_instr; + } else { + if (vmst->status == UVM32_STATUS_ERROR) { + setup_err_evt(vmst, evt); + } else { + setStatusErr(vmst, UVM32_ERR_INTERNAL_STATE); + setup_err_evt(vmst, evt); + } + return num_instr; + } +} + +// Read C-string up to terminator and return len,ptr +static void get_safeptr_terminated(uvm32_state_t *vmst, uint32_t addr, uint8_t terminator, uvm32_evt_ioreq_buf_t *buf) { + uint32_t ptrstart = addr - MINIRV32_RAM_IMAGE_OFFSET; + uint32_t p = ptrstart; + if (p >= UVM32_MEMORY_SIZE) { + setStatusErr(vmst, UVM32_ERR_MEM_RD); + buf->ptr = NULL; + buf->len = 0; + return; + } + while(vmst->memory[p] != terminator) { + p++; + if (p >= UVM32_MEMORY_SIZE) { + setStatusErr(vmst, UVM32_ERR_MEM_RD); + buf->ptr = NULL; + buf->len = 0; + return; + } + } + buf->ptr = &vmst->memory[ptrstart]; + buf->len = p - ptrstart; +} + +static void get_safeptr(uvm32_state_t *vmst, uint32_t addr, uint32_t len, uvm32_evt_ioreq_buf_t *buf) { + uint32_t ptrstart = addr - MINIRV32_RAM_IMAGE_OFFSET; + if (ptrstart + len >= UVM32_MEMORY_SIZE) { + setStatusErr(vmst, UVM32_ERR_MEM_RD); + } + buf->ptr = &vmst->memory[ptrstart]; + buf->len = len; +} + +void uvm32_HandleOtherCSRWrite(void *userdata, uint16_t csrno, uint32_t value) { + uvm32_evt_ioreq_buf_t b; + uvm32_state_t *vmst = (uvm32_state_t *)userdata; + + switch(csrno) { + case IOREQ_HALT: + setStatus(vmst, UVM32_STATUS_ENDED); + break; + case IOREQ_YIELD: + vmst->ioevt.typ = UVM32_EVT_YIELD; + setStatus(vmst, UVM32_STATUS_PAUSED); + break; + + default: + // search in mappings + for (int i=0;inumMappings;i++) { + if (csrno == vmst->mappings[i].csr) { + // setup ioevt.data according to mapping typ + switch(vmst->mappings[i].typ) { + case IOREQ_TYP_VOID: + break; + case IOREQ_TYP_U32_WR: + vmst->ioevt.data.ioreq.val.u32 = value; + break; + case IOREQ_TYP_BUF_TERMINATED_WR: + get_safeptr_terminated(vmst, value, 0x00, &vmst->ioevt.data.ioreq.val.buf); + break; + case IOREQ_TYP_U32_RD: + get_safeptr(vmst, value, 4, &b); + vmst->ioevt.data.ioreq.val.u32p = (uint32_t *)b.ptr; + break; + } + vmst->ioevt.typ = UVM32_EVT_IOREQ; + vmst->ioevt.data.ioreq.code = vmst->mappings[i].code; + vmst->ioevt.data.ioreq.typ = vmst->mappings[i].typ; + setStatus(vmst, UVM32_STATUS_PAUSED); + return; + } + } + // no mapping found + setStatusErr(vmst, UVM32_ERR_BAD_CSR); + break; + } +} + +bool uvm32_hasEnded(const uvm32_state_t *vmst) { + return vmst->status == UVM32_STATUS_ENDED; +} + + diff --git a/emulator-arduino/uvm32.h b/emulator-arduino/uvm32.h new file mode 100644 index 0000000..4caaa7b --- /dev/null +++ b/emulator-arduino/uvm32.h @@ -0,0 +1,110 @@ +#ifndef UVM32_H +#define UVM32_H 1 + +#include +#include + +// "well-known" system IOREQ functions +#define IOREQ_HALT 0x138 +#define IOREQ_YIELD 0x139 + +#define LIST_OF_UVM32_ERRS \ + X(UVM32_ERR_NONE) \ + X(UVM32_ERR_NOTREADY) \ + X(UVM32_ERR_MEM_RD) \ + X(UVM32_ERR_MEM_WR) \ + X(UVM32_ERR_BAD_CSR) \ + X(UVM32_ERR_HUNG) \ + X(UVM32_ERR_INTERNAL_CORE) \ + X(UVM32_ERR_INTERNAL_STATE) \ + +#define X(name) name, +typedef enum { + LIST_OF_UVM32_ERRS +} uvm32_err_t; +#undef X + +typedef enum { + UVM32_EVT_ERR, + UVM32_EVT_IOREQ, + UVM32_EVT_YIELD, + UVM32_EVT_END, +} uvm32_evt_typ_t; + +typedef enum { + IOREQ_TYP_BUF_TERMINATED_WR, // data write from vm, NULL terminated string of bytes, in uvm32_evt_ioreq_t.val.buf + IOREQ_TYP_VOID, // no data + IOREQ_TYP_U32_WR, // data write from vm, in uvm32_evt_ioreq_t.val.u32 + IOREQ_TYP_U32_RD, // data read from vm, expects response in uvm32_evt_ioreq_t.val.u32p +} uvm32_ioreq_typ_t; + +typedef uint32_t uvm32_user_ioreq_code_t; + +// user supplied mapping from csr to typed ioreq +typedef struct { + uint32_t csr; + uvm32_user_ioreq_code_t code; + uvm32_ioreq_typ_t typ; +} uvm32_mapping_t; + +typedef struct { + uint8_t *ptr; + uint32_t len; +} uvm32_evt_ioreq_buf_t; + +typedef struct { + uvm32_ioreq_typ_t typ; + uvm32_user_ioreq_code_t code; + union { + uvm32_evt_ioreq_buf_t buf; + uint32_t u32; + uint32_t *u32p; + } val; +} uvm32_evt_ioreq_t; + +typedef struct { + uvm32_err_t errcode; + const char *errstr; +} uvm32_evt_err_t; + +typedef struct { + uvm32_evt_typ_t typ; + union { + uvm32_evt_ioreq_t ioreq; + uvm32_evt_err_t err; + } data; +} uvm32_evt_t; + +void uvm32_HandleOtherCSRWrite(void *userdata, uint16_t csrno, uint32_t value); +#define MINIRV32_DECORATE static +#define MINI_RV32_RAM_SIZE UVM32_MEMORY_SIZE +#define MINIRV32_POSTEXEC(pc, ir, retval) {if (retval > 0) return 3;} +#define MINIRV32_OTHERCSR_WRITE(csrno, value) uvm32_HandleOtherCSRWrite(userdata, csrno, value); +#ifndef MINIRV32_IMPLEMENTATION +#define MINIRV32_STEPPROTO +#endif +#include "mini-rv32ima.h" + +typedef enum { + UVM32_STATUS_PAUSED, + UVM32_STATUS_RUNNING, + UVM32_STATUS_ERROR, + UVM32_STATUS_ENDED, +} uvm32_status_t; + +typedef struct { + uvm32_status_t status; + uvm32_err_t err; + struct MiniRV32IMAState* core; // points at end of memory + uint8_t memory[UVM32_MEMORY_SIZE]; + uvm32_evt_t ioevt; // for building up in callbacks + const uvm32_mapping_t *mappings; + uint32_t numMappings; +} uvm32_state_t; + +void uvm32_init(uvm32_state_t *vmst, const uvm32_mapping_t *mappings, uint32_t numMappings); +bool uvm32_load(uvm32_state_t *vmst, uint8_t *rom, int len); +bool uvm32_hasEnded(const uvm32_state_t *vmst); +uint32_t uvm32_run(uvm32_state_t *vmst, uvm32_evt_t *evt, uint32_t instr_meter); + +#endif