diff --git a/README.md b/README.md index a5e5aff..1d9b777 100644 --- a/README.md +++ b/README.md @@ -167,6 +167,23 @@ stateDiagram At boot, the whole memory is zeroed. The user program is placed at the start. The stack pointer is set to the end of memory and grows downwards. No heap region is setup and all code is in RAM. +## ExtRAM + +A single block of external RAM may be memory mapped into the VM at any time using: + + uvm32_extram(uvm32_state_t *vmst, uint32_t *ram, uint32_t len) + +The `ram` region must be 32bit aligned, as all accesses will be 32bit words. The `len` is given in bytes. + +From inside the VM, the memory is available from address `0x10000000` + + uint32_t *p = (uint32_t *)UVM32_EXTRAM_BASE; + p[0] = 0xDEADBEEF; + +When the external RAM is written to by the VM, the dirty flag will be set. The flag is automatically cleared on the next call to `uvm32_run()`. The flag can be checked using: + + bool uvm32_extramDirty(uvm32_state_t *vmst) + ## syscall ABI All communication between bytecode and the vm host is performed via syscalls. diff --git a/common/uvm32_sys.h b/common/uvm32_sys.h index 7228d57..0829864 100644 --- a/common/uvm32_sys.h +++ b/common/uvm32_sys.h @@ -3,4 +3,6 @@ #define UVM32_SYSCALL_YIELD 0x1000001 #define UVM32_SYSCALL_STACKPROTECT 0x1000002 +#define UVM32_EXTRAM_BASE 0x10000000 + #include "uvm32_common_custom.h" diff --git a/test/Makefile b/test/Makefile index a2befe3..ac178c9 100644 --- a/test/Makefile +++ b/test/Makefile @@ -3,7 +3,8 @@ TESTS = \ stackoverflow \ custom_syscall \ syscall_args \ - meter + meter \ + extram RUNCMD = $(foreach TEST,${TESTS},make -C ${TEST} &&) CLEANCMD = $(foreach TEST,${TESTS},make -C ${TEST} clean &&) diff --git a/test/extram/Makefile b/test/extram/Makefile new file mode 100644 index 0000000..6533b02 --- /dev/null +++ b/test/extram/Makefile @@ -0,0 +1,33 @@ +C_COMPILER=gcc + +UNITY_ROOT=../unity + +CFLAGS=-std=c99 +CFLAGS += -Wall +CFLAGS += -Werror +CFLAGS += -DUVM32_MEMORY_SIZE=16384 + +SUITE_NAME=tests + +TARGET_BASE1=test1 +TARGET1 = $(TARGET_BASE1) +SRC_FILES1=$(UNITY_ROOT)/src/unity.c test/${SUITE_NAME}.c test/test_runners/${SUITE_NAME}_Runner.c ../../uvm32/uvm32.c +INC_DIRS=-I$(UNITY_ROOT)/src -I../../uvm32/ -I../../common -Irom + +.PHONY: rom + +default: $(SRC_FILES1) rom + @$(C_COMPILER) $(CFLAGS) $(INC_DIRS) $(SYMBOLS) $(SRC_FILES1) rom/rom-header.c -o $(TARGET1) + @ ./$(TARGET1) + +rom: + @(cd rom && make) + +test/test_runners/${SUITE_NAME}_Runner.c: test/${SUITE_NAME}.c + @mkdir -p test/test_runners + @ruby $(UNITY_ROOT)/auto/generate_test_runner.rb test/${SUITE_NAME}.c test/test_runners/${SUITE_NAME}_Runner.c + +clean: + rm -rf $(TARGET1) test/test_runners + (cd rom && make clean) + diff --git a/test/extram/rom/Makefile b/test/extram/rom/Makefile new file mode 100644 index 0000000..4698f22 --- /dev/null +++ b/test/extram/rom/Makefile @@ -0,0 +1,13 @@ +TOPDIR=../../../ +PROJECT:=$(shell basename ${PWD}) +SRCS=${PROJECT}.c ${TOPDIR}/apps/crt0.S +all: all_common + @# Convert ROM to C file and header + @xxd -i ${PROJECT}.bin > ${PROJECT}-header.c + @echo "extern unsigned char ${PROJECT}_bin[]; extern int ${PROJECT}_bin_len;" > ${PROJECT}-header.h + +test: test_common +clean: clean_common + rm -f ${PROJECT}-header.h ${PROJECT}-header.c + +include ${TOPDIR}/apps/makefile.common diff --git a/test/extram/rom/rom.c b/test/extram/rom/rom.c new file mode 100644 index 0000000..bbd627f --- /dev/null +++ b/test/extram/rom/rom.c @@ -0,0 +1,31 @@ +#include "uvm32_target.h" +#include "../shared.h" + +void main(void) { + switch(syscall(SYSCALL_PICKTEST, 0, 0)) { + case TEST1: { + uint32_t *p = (uint32_t *)UVM32_EXTRAM_BASE; + + // read memory and print via syscall + printdec(*p); + // modify memory + *p = *p * 2; + } break; + case TEST2: { + uint32_t *p = (uint32_t *)UVM32_EXTRAM_BASE; + printdec(p[32]); // past the end + } break; + case TEST3: { + uint32_t *p = (uint32_t *)UVM32_EXTRAM_BASE; + p[32] = 1234; // past the end + } break; + case TEST4: { + uint32_t *p = (uint32_t *)UVM32_EXTRAM_BASE; + p[0] = 1234; // good write + yield(0); + } break; + + } + +} + diff --git a/test/extram/test/tests.c b/test/extram/test/tests.c new file mode 100644 index 0000000..d961f7c --- /dev/null +++ b/test/extram/test/tests.c @@ -0,0 +1,99 @@ +#include +#include "unity.h" +#include "uvm32.h" +#include "../common/uvm32_common_custom.h" + +#include "rom-header.h" +#include "../shared.h" + +static uvm32_state_t vmst; +static uvm32_evt_t evt; + +uint32_t extram[32]; + +void setUp(void) { + // runs before each test + uvm32_init(&vmst); + uvm32_load(&vmst, rom_bin, rom_bin_len); + memset(extram, 0x00, sizeof(extram)); + uvm32_extram(&vmst, extram, sizeof(extram)); +} + +void tearDown(void) { +} + +void test_extram_access(void) { + extram[0] = 1234; + + // run the vm + uvm32_run(&vmst, &evt, 100); + + TEST_ASSERT_EQUAL(false, uvm32_extramDirty(&vmst)); + + // check for picktest syscall + TEST_ASSERT_EQUAL(evt.typ, UVM32_EVT_SYSCALL); + TEST_ASSERT_EQUAL(evt.data.syscall.code, SYSCALL_PICKTEST); + uvm32_setval(&vmst, &evt, RET, TEST1); + + uvm32_run(&vmst, &evt, 100); + // check for printdec of val + TEST_ASSERT_EQUAL(evt.typ, UVM32_EVT_SYSCALL); + TEST_ASSERT_EQUAL(evt.data.syscall.code, UVM32_SYSCALL_PRINTDEC); + TEST_ASSERT_EQUAL(1234, uvm32_getval(&vmst, &evt, ARG0)); + + // run vm to completion + uvm32_run(&vmst, &evt, 100); + TEST_ASSERT_EQUAL(evt.typ, UVM32_EVT_END); + + TEST_ASSERT_EQUAL(true, uvm32_extramDirty(&vmst)); + TEST_ASSERT_EQUAL(1234*2, extram[0]); +} + +void test_extram_out_of_bounds_rd(void) { + // run the vm + uvm32_run(&vmst, &evt, 100); + TEST_ASSERT_EQUAL(false, uvm32_extramDirty(&vmst)); + + // check for picktest syscall + TEST_ASSERT_EQUAL(evt.typ, UVM32_EVT_SYSCALL); + TEST_ASSERT_EQUAL(evt.data.syscall.code, SYSCALL_PICKTEST); + uvm32_setval(&vmst, &evt, RET, TEST2); + + uvm32_run(&vmst, &evt, 100); + TEST_ASSERT_EQUAL(evt.typ, UVM32_EVT_ERR); + TEST_ASSERT_EQUAL(evt.data.err.errcode, UVM32_ERR_MEM_RD); + TEST_ASSERT_EQUAL(false, uvm32_extramDirty(&vmst)); +} + +void test_extram_out_of_bounds_wr(void) { + // run the vm + uvm32_run(&vmst, &evt, 100); + TEST_ASSERT_EQUAL(false, uvm32_extramDirty(&vmst)); + + // check for picktest syscall + TEST_ASSERT_EQUAL(evt.typ, UVM32_EVT_SYSCALL); + TEST_ASSERT_EQUAL(evt.data.syscall.code, SYSCALL_PICKTEST); + uvm32_setval(&vmst, &evt, RET, TEST3); + + uvm32_run(&vmst, &evt, 100); + TEST_ASSERT_EQUAL(evt.typ, UVM32_EVT_ERR); + TEST_ASSERT_EQUAL(evt.data.err.errcode, UVM32_ERR_MEM_WR); + TEST_ASSERT_EQUAL(false, uvm32_extramDirty(&vmst)); +} + +void test_extram_out_of_bounds_dirty_flag(void) { + // run the vm + uvm32_run(&vmst, &evt, 100); + TEST_ASSERT_EQUAL(false, uvm32_extramDirty(&vmst)); + + // check for picktest syscall + TEST_ASSERT_EQUAL(evt.typ, UVM32_EVT_SYSCALL); + TEST_ASSERT_EQUAL(evt.data.syscall.code, SYSCALL_PICKTEST); + uvm32_setval(&vmst, &evt, RET, TEST4); + + uvm32_run(&vmst, &evt, 100); + TEST_ASSERT_EQUAL(true, uvm32_extramDirty(&vmst)); + uvm32_run(&vmst, &evt, 100); + TEST_ASSERT_EQUAL(false, uvm32_extramDirty(&vmst)); +} + diff --git a/uvm32/uvm32.c b/uvm32/uvm32.c index 7f6b641..a2439a4 100644 --- a/uvm32/uvm32.c +++ b/uvm32/uvm32.c @@ -69,6 +69,10 @@ void uvm32_init(uvm32_state_t *vmst) { UVM32_MEMSET(vmst, 0x00, sizeof(uvm32_state_t)); vmst->status = UVM32_STATUS_PAUSED; + vmst->extramLen = 0; + vmst->extram = UVM32_NULL; + vmst->extramDirty = false; + vmst->core.pc = MINIRV32_RAM_IMAGE_OFFSET; // https://projectf.io/posts/riscv-cheat-sheet/ // setup stack pointer @@ -141,6 +145,8 @@ uint32_t uvm32_run(uvm32_state_t *vmst, uvm32_evt_t *evt, uint32_t instr_meter) const uint32_t min_instrs = 1; uint32_t orig_instr_meter = instr_meter; + vmst->extramDirty = false; + if (instr_meter < min_instrs) { instr_meter = min_instrs; } @@ -320,3 +326,40 @@ uvm32_evt_syscall_buf_t uvm32_getbuf_fixed(uvm32_state_t *vmst, uvm32_evt_t *evt return scb; } +uint32_t uvm32_extramLoad(void *userdata, uint32_t addr) { + uvm32_state_t *vmst = (uvm32_state_t *)userdata; + addr -= UVM32_EXTRAM_BASE; + if (vmst->extram != UVM32_NULL) { + if (addr < vmst->extramLen) { + return ((uint32_t *)vmst->extram)[addr / 4]; + } else { + setStatusErr(vmst, UVM32_ERR_MEM_RD); + } + } + return 0; +} + +uint32_t uvm32_extramStore(void *userdata, uint32_t addr, uint32_t val ) { + uvm32_state_t *vmst = (uvm32_state_t *)userdata; + addr -= UVM32_EXTRAM_BASE; + if (vmst->extram != UVM32_NULL) { + if (addr < vmst->extramLen) { + ((uint32_t *)vmst->extram)[addr / 4] = val; + vmst->extramDirty = true; + } else { + setStatusErr(vmst, UVM32_ERR_MEM_WR); + } + } + return 0; +} + +void uvm32_extram(uvm32_state_t *vmst, uint32_t *ram, uint32_t len) { + vmst->extram = ram; + vmst->extramLen = len; +} + +bool uvm32_extramDirty(uvm32_state_t *vmst) { + return vmst->extramDirty; +} + + diff --git a/uvm32/uvm32.h b/uvm32/uvm32.h index 6c6fa8e..3b63f46 100644 --- a/uvm32/uvm32.h +++ b/uvm32/uvm32.h @@ -55,6 +55,11 @@ typedef struct { #define MINIRV32_DECORATE static #define MINI_RV32_RAM_SIZE UVM32_MEMORY_SIZE #define MINIRV32_POSTEXEC(pc, ir, retval) {if (retval > 0) return retval;} +uint32_t uvm32_extramLoad(void *userdata, uint32_t addr); +uint32_t uvm32_extramStore(void *userdata, uint32_t addr, uint32_t val); +#define MINIRV32_HANDLE_MEM_LOAD_CONTROL( addy, rval ) rval = uvm32_extramLoad(userdata, addy); +#define MINIRV32_HANDLE_MEM_STORE_CONTROL( addy, val ) if( uvm32_extramStore(userdata, addy, val) ) return val; + #ifndef MINIRV32_IMPLEMENTATION #define MINIRV32_STEPPROTO #endif @@ -74,6 +79,9 @@ typedef struct { uint8_t memory[UVM32_MEMORY_SIZE]; uvm32_evt_t ioevt; // for building up in callbacks uint8_t *stack_canary; + uint32_t extramLen; + uint32_t *extram; // all accesses are 32bit + bool extramDirty; } uvm32_state_t; void uvm32_init(uvm32_state_t *vmst); @@ -93,6 +101,7 @@ typedef enum { RET } uvm32_arg_t; +// syscall parameter handling uint32_t uvm32_getval(uvm32_state_t *vmst, uvm32_evt_t *evt, uvm32_arg_t); const char *uvm32_getcstr(uvm32_state_t *vmst, uvm32_evt_t *evt, uvm32_arg_t); void uvm32_setval(uvm32_state_t *vmst, uvm32_evt_t *evt, uvm32_arg_t, uint32_t val); @@ -100,5 +109,9 @@ uvm32_evt_syscall_buf_t uvm32_getbuf(uvm32_state_t *vmst, uvm32_evt_t *evt, uvm3 uvm32_evt_syscall_buf_t uvm32_getbuf_fixed(uvm32_state_t *vmst, uvm32_evt_t *evt, uvm32_arg_t argPtr, uint32_t len); void uvm32_clearError(uvm32_state_t *vmst); +// external RAM +void uvm32_extram(uvm32_state_t *vmst, uint32_t *ram, uint32_t len); +bool uvm32_extramDirty(uvm32_state_t *vmst); // cleared by uvm32_run() + #endif