Add system for memory mapping a block of memory from the host.

This commit is contained in:
Toby Jaffey 2025-12-11 02:36:46 +00:00
parent 8802b4c268
commit b55c2bc88a
9 changed files with 253 additions and 1 deletions

View file

@ -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. 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 ## syscall ABI
All communication between bytecode and the vm host is performed via syscalls. All communication between bytecode and the vm host is performed via syscalls.

View file

@ -3,4 +3,6 @@
#define UVM32_SYSCALL_YIELD 0x1000001 #define UVM32_SYSCALL_YIELD 0x1000001
#define UVM32_SYSCALL_STACKPROTECT 0x1000002 #define UVM32_SYSCALL_STACKPROTECT 0x1000002
#define UVM32_EXTRAM_BASE 0x10000000
#include "uvm32_common_custom.h" #include "uvm32_common_custom.h"

View file

@ -3,7 +3,8 @@ TESTS = \
stackoverflow \ stackoverflow \
custom_syscall \ custom_syscall \
syscall_args \ syscall_args \
meter meter \
extram
RUNCMD = $(foreach TEST,${TESTS},make -C ${TEST} &&) RUNCMD = $(foreach TEST,${TESTS},make -C ${TEST} &&)
CLEANCMD = $(foreach TEST,${TESTS},make -C ${TEST} clean &&) CLEANCMD = $(foreach TEST,${TESTS},make -C ${TEST} clean &&)

33
test/extram/Makefile Normal file
View file

@ -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)

13
test/extram/rom/Makefile Normal file
View file

@ -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

31
test/extram/rom/rom.c Normal file
View file

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

99
test/extram/test/tests.c Normal file
View file

@ -0,0 +1,99 @@
#include <string.h>
#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));
}

View file

@ -69,6 +69,10 @@ void uvm32_init(uvm32_state_t *vmst) {
UVM32_MEMSET(vmst, 0x00, sizeof(uvm32_state_t)); UVM32_MEMSET(vmst, 0x00, sizeof(uvm32_state_t));
vmst->status = UVM32_STATUS_PAUSED; vmst->status = UVM32_STATUS_PAUSED;
vmst->extramLen = 0;
vmst->extram = UVM32_NULL;
vmst->extramDirty = false;
vmst->core.pc = MINIRV32_RAM_IMAGE_OFFSET; vmst->core.pc = MINIRV32_RAM_IMAGE_OFFSET;
// https://projectf.io/posts/riscv-cheat-sheet/ // https://projectf.io/posts/riscv-cheat-sheet/
// setup stack pointer // 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; const uint32_t min_instrs = 1;
uint32_t orig_instr_meter = instr_meter; uint32_t orig_instr_meter = instr_meter;
vmst->extramDirty = false;
if (instr_meter < min_instrs) { if (instr_meter < min_instrs) {
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; 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;
}

View file

@ -55,6 +55,11 @@ typedef struct {
#define MINIRV32_DECORATE static #define MINIRV32_DECORATE static
#define MINI_RV32_RAM_SIZE UVM32_MEMORY_SIZE #define MINI_RV32_RAM_SIZE UVM32_MEMORY_SIZE
#define MINIRV32_POSTEXEC(pc, ir, retval) {if (retval > 0) return retval;} #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 #ifndef MINIRV32_IMPLEMENTATION
#define MINIRV32_STEPPROTO #define MINIRV32_STEPPROTO
#endif #endif
@ -74,6 +79,9 @@ typedef struct {
uint8_t memory[UVM32_MEMORY_SIZE]; uint8_t memory[UVM32_MEMORY_SIZE];
uvm32_evt_t ioevt; // for building up in callbacks uvm32_evt_t ioevt; // for building up in callbacks
uint8_t *stack_canary; uint8_t *stack_canary;
uint32_t extramLen;
uint32_t *extram; // all accesses are 32bit
bool extramDirty;
} uvm32_state_t; } uvm32_state_t;
void uvm32_init(uvm32_state_t *vmst); void uvm32_init(uvm32_state_t *vmst);
@ -93,6 +101,7 @@ typedef enum {
RET RET
} uvm32_arg_t; } uvm32_arg_t;
// syscall parameter handling
uint32_t uvm32_getval(uvm32_state_t *vmst, uvm32_evt_t *evt, uvm32_arg_t); 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); 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); 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); 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); 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 #endif