mirror of
https://github.com/ringtailsoftware/uvm32.git
synced 2026-06-05 22:43:39 +00:00
Add system for memory mapping a block of memory from the host.
This commit is contained in:
parent
8802b4c268
commit
b55c2bc88a
9 changed files with 253 additions and 1 deletions
17
README.md
17
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.
|
||||
|
|
|
|||
|
|
@ -3,4 +3,6 @@
|
|||
#define UVM32_SYSCALL_YIELD 0x1000001
|
||||
#define UVM32_SYSCALL_STACKPROTECT 0x1000002
|
||||
|
||||
#define UVM32_EXTRAM_BASE 0x10000000
|
||||
|
||||
#include "uvm32_common_custom.h"
|
||||
|
|
|
|||
|
|
@ -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 &&)
|
||||
|
|
|
|||
33
test/extram/Makefile
Normal file
33
test/extram/Makefile
Normal 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
13
test/extram/rom/Makefile
Normal 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
31
test/extram/rom/rom.c
Normal 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
99
test/extram/test/tests.c
Normal 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));
|
||||
}
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue