Rework host/target interface to use ecall and proper syscalls instead of CSRs

This commit is contained in:
Toby Jaffey 2025-12-08 02:44:38 +00:00
parent a305fce466
commit 751f068486
26 changed files with 360 additions and 364 deletions

View file

@ -78,7 +78,7 @@ If the bytecode attempts to execute more instructions than the the passed value
## Internals
uvm32 emulates a RISC-V 32bit CPU using [mini-rv32ima](https://github.com/cnlohr/mini-rv32ima). All IO from vm bytecode to the host is performed using [CSRs](https://five-embeddev.com/riscv-priv-isa-manual/Priv-v1.12/priv-csrs.html). Each "function" provided by the host requires a unique CSR value. A CSR passes a single `uint32_t` from bytecode to the host.
uvm32 emulates a RISC-V 32bit CPU using [mini-rv32ima](https://github.com/cnlohr/mini-rv32ima). All IO from vm bytecode to the host is performed using `ecall` syscalls. Each "function" provided by the host requires a unique syscall value. A syscall passes a single `uint32_t` from bytecode to the host and may receive a returned `uint32_t`. The host may treat the value as a pointer and modify memory.
uvm32 is always in one of 4 states, paused, running, ended or error.
@ -102,7 +102,7 @@ There are two system ioreqs used by uvm32, `halt()` and `yield()`.
`halt()` tells the host that the program has ended normally. `yield()` tells the host that the program requires more instructions to be executed.
New ioreqs can be added to the host via `uvm32_init()`.
Each ioreq maps a CSR number to a value understood by the host (`F_PRINTD` below) and has an associated type which tells the host how to interpret the data passed to the CSR.
Each ioreq maps a syscall number to a value understood by the host (`F_PRINTD` below) and has an associated type which tells the host how to interpret the data passed to the syscall.
Here is a full example of a working VM host from [apps/host-mini](apps/host-mini)
@ -116,7 +116,7 @@ Here is a full example of a working VM host from [apps/host-mini](apps/host-mini
#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)
// This code expects to print via syscall 0x13C (IOREQ_PRINTD in common/uvm32_common_custom.h)
uint8_t rom[] = {
0x23, 0x26, 0x11, 0x00, 0xef, 0x00, 0x00, 0x01, 0x73, 0x50, 0x80, 0x13,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x93, 0x07, 0x00, 0x00,
@ -131,7 +131,7 @@ typedef enum {
// Map VM ioreq IOREQ_PRINTD (0x13C) to F_PRINTD, tell VM to expect write of a U32
const uvm32_mapping_t env[] = {
{ .csr = IOREQ_PRINTD, .typ = IOREQ_TYP_U32_WR, .code = F_PRINTD },
{ .syscall = IOREQ_PRINTD, .typ = IOREQ_TYP_U32_WR, .code = F_PRINTD },
};
int main(int argc, char *argv[]) {

View file

@ -1,10 +0,0 @@
.section .initial_jump , "ax", %progbits
.global _start
.align 4
_start:
# sp is already setup by vm
sw ra,12(sp)
jal ra, main
csrwi 0x138,0 # halt
.section .data

View file

@ -10,7 +10,7 @@ CFLAGS+=-g -Os -march=rv32ima_zicsr -mabi=ilp32 -static
LDFLAGS:= -T ../linker.ld -nostdlib -Wl,--gc-sections
LIBS:= #-lgcc # needed for softfp
SRCS=${PROJECT}.c ../crt0.s
SRCS=${PROJECT}.c ../crt0.S
all:
${PREFIX}gcc -o ${PROJECT}.elf ${CFLAGS} ${LDFLAGS} ${SRCS} ${LIBS}

View file

@ -10,7 +10,7 @@ CFLAGS+=-g -Os -march=rv32ima_zicsr -mabi=ilp32 -static
LDFLAGS:= -T ../linker.ld -nostdlib -Wl,--gc-sections
LIBS:= #-lgcc # needed for softfp
SRCS=hello-asm.s
SRCS=hello-asm.S
all:
${PREFIX}gcc -o ${PROJECT}.elf ${CFLAGS} ${LDFLAGS} ${SRCS} ${LIBS}

View file

@ -1,9 +0,0 @@
.section .initial_jump , "ax", %progbits
.global _start
_start:
la a5, str
csrrw zero,0x13b,a5 # println
csrwi 0x138,0 # halt
str:
.ascii "Hi"

View file

@ -10,7 +10,7 @@ CFLAGS+=-g -Os -march=rv32ima_zicsr -mabi=ilp32 -static
LDFLAGS:= -T ../linker.ld -nostdlib -Wl,--gc-sections
LIBS:= #-lgcc # needed for softfp
SRCS=${PROJECT}.c ../crt0.s
SRCS=${PROJECT}.c ../crt0.S
all:
${PREFIX}gcc -o ${PROJECT}.elf ${CFLAGS} ${LDFLAGS} ${SRCS} ${LIBS}

View file

@ -2,6 +2,18 @@
#include "uvm32_target.h"
void main(void) {
println("Hello world");
for (int i=0;i<10;i++) {
printd(i);
}
#if 0
uint32_t c;
while(c = getc()) {
if (c != 0xFFFFFFFF) {
print("Got: ");
printx(c);
println("");
}
}
#endif
}

View file

@ -9,26 +9,27 @@ use core::panic::PanicInfo;
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
// startup code
global_asm!(include_str!("../../crt0.s"));
global_asm!(include_str!("../../crt0.S"));
fn println(message: &str) {
fn syscall(id: u32, n: u32) -> u32 {
let mut value;
unsafe {
asm!(
"csrw {i}, {x}",
i = const IOREQ_PRINTLN,
x = in(reg) message.as_ptr(),
asm!("ecall",
in("a0") n,
in("a7") id,
lateout("a1") value,
);
}
return value;
}
fn println(message: &str) {
let addr_value = message.as_ptr() as u32;
syscall(IOREQ_PRINTLN, addr_value);
}
fn printd(n: u32) {
unsafe {
asm!(
"csrw {i}, {x}",
i = const IOREQ_PRINTD,
x = in(reg) n,
);
}
syscall(IOREQ_PRINTD, n);
}
#[no_mangle]
@ -36,7 +37,7 @@ pub extern "C" fn main() {
for i in 0..10 {
printd(i);
}
println("Hello, world!");
println("Hello, world!\0");
}
#[panic_handler]

View file

@ -10,7 +10,7 @@ CFLAGS+=-g -Os -march=rv32ima_zicsr -mabi=ilp32 -static
LDFLAGS:= -T ../linker.ld -nostdlib -Wl,--gc-sections
LIBS:= #-lgcc # needed for softfp
SRCS=${PROJECT}.c ../crt0.s
SRCS=${PROJECT}.c ../crt0.S
all:
${PREFIX}gcc -o ${PROJECT}.elf ${CFLAGS} ${LDFLAGS} ${SRCS} ${LIBS}

View file

@ -4,35 +4,25 @@ const uvm32 = @cImport({
});
const std = @import("std");
pub inline fn syscall(id: u32, param: u32) u32 {
var val: u32 = undefined;
asm volatile ("ecall"
: [val] "={a1}" (val),
: [param] "{a0}" (param),
[id] "{a7}" (id),
: .{ .memory = true });
return val;
}
pub inline fn println(val: [:0]const u8) void {
asm volatile ("csrw " ++ std.fmt.comptimePrint("0x{x}", .{uvm32.IOREQ_PRINTLN}) ++ ", %[arg1]"
:
: [arg1] "r" (val.ptr),
);
}
pub inline fn printd(val: u32) void {
asm volatile ("csrw " ++ std.fmt.comptimePrint("0x{x}", .{uvm32.IOREQ_PRINTD}) ++ ", %[arg1]"
:
: [arg1] "r" (val),
);
}
pub inline fn printx(val: u32) void {
asm volatile ("csrw " ++ std.fmt.comptimePrint("0x{x}", .{uvm32.IOREQ_PRINTX}) ++ ", %[arg1]"
:
: [arg1] "r" (val),
);
}
pub inline fn printc(val: u32) void {
asm volatile ("csrw " ++ std.fmt.comptimePrint("0x{x}", .{uvm32.IOREQ_PRINTC}) ++ ", %[arg1]"
:
: [arg1] "r" (val),
);
_ = syscall(uvm32.IOREQ_PRINTLN, @intFromPtr(val.ptr));
}
pub inline fn yield() void {
asm volatile (std.fmt.comptimePrint("csrwi 0x{x}, 0", .{uvm32.IOREQ_YIELD}));
_ = syscall(uvm32.IOREQ_YIELD, 0);
}
pub inline fn printc(c:u8) void {
_ = syscall(uvm32.IOREQ_PRINTC, c);
}

View file

@ -4,70 +4,52 @@ const uvm32 = @cImport({
});
const std = @import("std");
pub inline fn syscall(id: u32, param: u32) u32 {
var val: u32 = undefined;
asm volatile ("ecall"
: [val] "={a1}" (val),
: [param] "{a0}" (param),
[id] "{a7}" (id),
: .{ .memory = true });
return val;
}
pub inline fn getch() ?u8 {
const key = syscall(uvm32.IOREQ_GETC, 0);
if (key == 0xFFFFFFFF) {
return null;
} else {
return @truncate(key);
}
}
pub inline fn millis() u32 {
return syscall(uvm32.IOREQ_MILLIS, 0);
}
// dupeZ would be better, but want to avoid using an allocator
var new_buf:[128]u8 = undefined;
// this is of course, unsafe...
var termination_buf:[128]u8 = undefined;
pub inline fn print(m: []const u8) void {
@memcpy(new_buf[0..m.len], m);
new_buf[m.len] = 0;
const s = new_buf[0..m.len :0];
asm volatile ("csrw " ++ std.fmt.comptimePrint("0x{x}", .{uvm32.IOREQ_PRINT}) ++ ", %[arg1]"
:
: [arg1] "r" (s.ptr),
);
@memcpy(termination_buf[0..m.len], m);
termination_buf[m.len] = 0;
const s = termination_buf[0..m.len :0];
_ = syscall(uvm32.IOREQ_PRINT, @intFromPtr(s.ptr));
}
pub inline fn println(val: [:0]const u8) void {
asm volatile ("csrw " ++ std.fmt.comptimePrint("0x{x}", .{uvm32.IOREQ_PRINTLN}) ++ ", %[arg1]"
:
: [arg1] "r" (val.ptr),
);
}
pub inline fn printd(val: u32) void {
asm volatile ("csrw " ++ std.fmt.comptimePrint("0x{x}", .{uvm32.IOREQ_PRINTD}) ++ ", %[arg1]"
:
: [arg1] "r" (val),
);
}
pub inline fn printx(val: u32) void {
asm volatile ("csrw " ++ std.fmt.comptimePrint("0x{x}", .{uvm32.IOREQ_PRINTX}) ++ ", %[arg1]"
:
: [arg1] "r" (val),
);
}
pub inline fn printc(val: u32) void {
asm volatile ("csrw " ++ std.fmt.comptimePrint("0x{x}", .{uvm32.IOREQ_PRINTC}) ++ ", %[arg1]"
:
: [arg1] "r" (val),
);
pub inline fn println(m: []const u8) void {
@memcpy(termination_buf[0..m.len], m);
termination_buf[m.len] = 0;
const s = termination_buf[0..m.len :0];
_ = syscall(uvm32.IOREQ_PRINTLN, @intFromPtr(s.ptr));
}
pub inline fn yield() void {
asm volatile (std.fmt.comptimePrint("csrwi 0x{x}, 0", .{uvm32.IOREQ_YIELD}));
_ = syscall(uvm32.IOREQ_YIELD, 0);
}
var millis_storage:u32 = 0;
pub inline fn millis() u32 {
asm volatile ("csrw " ++ std.fmt.comptimePrint("0x{x}", .{uvm32.IOREQ_MILLIS}) ++ ", %[arg1]"
:
: [arg1] "r" (&millis_storage),
);
return millis_storage;
}
var getch_storage:u32 = 0;
pub inline fn getch() ?u8 {
asm volatile ("csrw " ++ std.fmt.comptimePrint("0x{x}", .{uvm32.IOREQ_GETC}) ++ ", %[arg1]"
:
: [arg1] "r" (&getch_storage),
);
if (getch_storage <= 0xFF) {
return @truncate(getch_storage);
} else {
return null;
}
pub inline fn printc(c:u8) void {
_ = syscall(uvm32.IOREQ_PRINTC, c);
}

View file

@ -1,6 +1,6 @@
// Definitions needed by both host and target
// CSRs for exposed host functions
// syscalls for exposed host functions
#define IOREQ_PRINT 0x13A
#define IOREQ_PRINTLN 0x13B
#define IOREQ_PRINTD 0x13C

View file

@ -9,18 +9,30 @@ typedef int bool;
#define true 1
#define false 0
// Convenience macro for defining CSR helper functions
#define xstr(a) str(a)
#define str(a) #a
#define DEFINE_CSR_WRITE_FUNCTION(function_name, csr, typ) \
static void function_name(typ val) { \
asm volatile( ".option norvc\ncsrrw x0," xstr(csr) ", %0\n" : : "r" (val)); \
}
#define DEFINE_CSR_WRITE_FUNCTION_VOID(function_name, csr) \
static void function_name(void) { \
asm volatile( ".option norvc\ncsrwi " xstr(csr) ", 0"); \
static uint32_t syscall(uint32_t id, uint32_t param) {
register uint32_t a0 asm("a0") = (uint32_t)(param);
register uint32_t a1 asm("a1");
register uint32_t a7 asm("a7") = (uint32_t)(id);
asm volatile (
"ecall"
: "=r"(a1) // output
: "r"(a0), "r"(a7) // input
: "memory"
);
return a1;
}
#define syscall_cast(id, x) syscall((uint32_t)id, (uint32_t)x)
#define println(x) syscall_cast(IOREQ_PRINTLN, x)
#define print(x) syscall_cast(IOREQ_PRINT, x)
#define printd(x) syscall_cast(IOREQ_PRINTD, x)
#define printx(x) syscall_cast(IOREQ_PRINTX, x)
#define millis() syscall_cast(IOREQ_MILLIS, 0)
#define printc() syscall_cast(IOREQ_PRINTC, 0)
#define getc() syscall_cast(IOREQ_GETC, 0)
#define yield() syscall_cast(IOREQ_YIELD, 0)
#include "uvm32_common_custom.h"
#include "uvm32_target_custom.h"

View file

@ -1,17 +0,0 @@
// Define wrapper functions for target code to call CSRs
DEFINE_CSR_WRITE_FUNCTION(print, IOREQ_PRINT, const char *)
DEFINE_CSR_WRITE_FUNCTION(printd, IOREQ_PRINTD, uint32_t)
DEFINE_CSR_WRITE_FUNCTION(printx, IOREQ_PRINTX, uint32_t)
DEFINE_CSR_WRITE_FUNCTION(printc, IOREQ_PRINTC, char)
DEFINE_CSR_WRITE_FUNCTION(println, IOREQ_PRINTLN, const char *)
DEFINE_CSR_WRITE_FUNCTION_VOID(halt, IOREQ_HALT)
DEFINE_CSR_WRITE_FUNCTION_VOID(yield, IOREQ_YIELD)
DEFINE_CSR_WRITE_FUNCTION(millis_internal, IOREQ_MILLIS, uint32_t *)
static inline uint32_t millis(void) {
static uint32_t m;
millis_internal(&m);
return m;
}

View file

@ -3,25 +3,28 @@
#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)
// This code expects to print via syscall 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
0x23, 0x26, 0x11, 0x00, 0xef, 0x00, 0xc0, 0x00, 0x93, 0x08, 0x80, 0x13,
0x73, 0x00, 0x00, 0x00, 0x37, 0xf5, 0xff, 0xff, 0xb7, 0x15, 0x00, 0x00,
0x37, 0xe6, 0xff, 0xff, 0x93, 0x06, 0xf0, 0x01, 0x13, 0x07, 0xd5, 0xcc,
0x93, 0x87, 0x35, 0x33, 0x13, 0x08, 0x76, 0xe6, 0x93, 0x82, 0x35, 0xb3,
0x37, 0x43, 0x00, 0x00, 0x63, 0xc8, 0xe7, 0x08, 0x93, 0x03, 0x08, 0x00,
0x63, 0xca, 0x02, 0x07, 0x93, 0x08, 0x00, 0x00, 0x93, 0x05, 0x00, 0x00,
0x13, 0x0e, 0x00, 0x00, 0x93, 0x0e, 0x00, 0x00, 0x13, 0x06, 0x00, 0x02,
0x13, 0x05, 0x06, 0xfe, 0x63, 0xe2, 0xa6, 0x04, 0x33, 0x85, 0x15, 0x01,
0x63, 0x6e, 0xa3, 0x02, 0x13, 0x05, 0x00, 0x00, 0x33, 0x8e, 0xce, 0x03,
0xb3, 0x8e, 0x15, 0x41, 0x93, 0x08, 0x90, 0x13, 0x73, 0x00, 0x00, 0x00,
0x13, 0x5e, 0xbe, 0x40, 0xb3, 0x8e, 0x7e, 0x00, 0x33, 0x0e, 0xee, 0x00,
0xb3, 0x85, 0xde, 0x03, 0x93, 0xd5, 0xc5, 0x00, 0x33, 0x05, 0xce, 0x03,
0x93, 0x58, 0xc5, 0x00, 0x13, 0x06, 0x16, 0x00, 0x6f, 0xf0, 0xdf, 0xfb,
0x93, 0x08, 0x00, 0x14, 0x13, 0x05, 0x06, 0x00, 0x73, 0x00, 0x00, 0x00,
0x93, 0x83, 0x13, 0x09, 0xe3, 0xda, 0x72, 0xf8, 0x13, 0x05, 0xa0, 0x00,
0x93, 0x08, 0x00, 0x14, 0x73, 0x00, 0x00, 0x00, 0x13, 0x07, 0x97, 0x19,
0xe3, 0xdc, 0xe7, 0xf6, 0x37, 0x05, 0x00, 0x80, 0x13, 0x05, 0x05, 0x0e,
0x93, 0x08, 0xb0, 0x13, 0x73, 0x00, 0x00, 0x00, 0x67, 0x80, 0x00, 0x00,
0x00, 0x00, 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
@ -98,5 +101,5 @@ void loop(void) {
delay(2000);
}
return 0;
return;
}

View file

@ -24,7 +24,7 @@ static void setup_err_evt(uvm32_state_t *vmst, uvm32_evt_t *evt) {
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()
// always stay in error state until a uvm32_init()
return;
} else {
vmst->status = newStatus;
@ -47,7 +47,7 @@ void uvm32_init(uvm32_state_t *vmst, const uvm32_mapping_t *mappings, uint32_t n
// 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[2] = (MINIRV32_RAM_IMAGE_OFFSET + UVM32_MEMORY_SIZE - sizeof(struct MiniRV32IMAState)) - 16;
vmst->core->regs[10] = 0x00; //hart ID
vmst->core->regs[11] = 0;
vmst->core->extraflags |= 3; // Machine-mode.
@ -67,8 +67,45 @@ bool uvm32_load(uvm32_state_t *vmst, uint8_t *rom, int len) {
return true;
}
// 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;
}
#if 0
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;
}
#endif
uint32_t uvm32_run(uvm32_state_t *vmst, uvm32_evt_t *evt, uint32_t instr_meter) {
uint32_t num_instr = 0;
// uvm32_evt_ioreq_buf_t b;
if (vmst->status != UVM32_STATUS_PAUSED) {
setStatusErr(vmst, UVM32_ERR_NOTREADY);
setup_err_evt(vmst, evt);
@ -80,7 +117,65 @@ uint32_t uvm32_run(uvm32_state_t *vmst, uvm32_evt_t *evt, uint32_t instr_meter)
// 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)) {
uint32_t ret;
ret = MiniRV32IMAStep(vmst, vmst->core, vmst->memory, 0, elapsedUs, 1);
if (3 == ret) {
const uint32_t syscall = vmst->core->regs[17]; // a7
uint32_t value = vmst->core->regs[10]; // a0
bool syscall_valid = false;
// on exception we should jump to mtvec, but we handle directly
// and skip over the ecall instruction
vmst->core->pc += 4;
switch(syscall) {
// inbuilt syscalls
case IOREQ_HALT:
setStatus(vmst, UVM32_STATUS_ENDED);
syscall_valid = true;
break;
case IOREQ_YIELD:
vmst->ioevt.typ = UVM32_EVT_YIELD;
setStatus(vmst, UVM32_STATUS_PAUSED);
syscall_valid = true;
break;
// user defined syscalls
default:
// search in mappings
for (int i=0;i<vmst->numMappings;i++) {
if (syscall == vmst->mappings[i].syscall) {
// 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 = &vmst->core->regs[11]; // r1, //(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;
//#warning FIXME, retval
// vmst->core->regs[11] = 456; // r1
setStatus(vmst, UVM32_STATUS_PAUSED);
syscall_valid = true;
break; // stop searching
}
}
// no mapping found
if (!syscall_valid) {
setStatusErr(vmst, UVM32_ERR_BAD_SYSCALL);
}
break;
}
} else if (ret != 0) {
// unhandled exception
setStatusErr(vmst, UVM32_ERR_INTERNAL_CORE);
setup_err_evt(vmst, evt);
}
@ -116,83 +211,6 @@ uint32_t uvm32_run(uvm32_state_t *vmst, uvm32_evt_t *evt, uint32_t instr_meter)
}
}
// 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;i<vmst->numMappings;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;
}

View file

@ -13,7 +13,7 @@
X(UVM32_ERR_NOTREADY) \
X(UVM32_ERR_MEM_RD) \
X(UVM32_ERR_MEM_WR) \
X(UVM32_ERR_BAD_CSR) \
X(UVM32_ERR_BAD_SYSCALL) \
X(UVM32_ERR_HUNG) \
X(UVM32_ERR_INTERNAL_CORE) \
X(UVM32_ERR_INTERNAL_STATE) \
@ -40,9 +40,9 @@ typedef enum {
typedef uint32_t uvm32_user_ioreq_code_t;
// user supplied mapping from csr to typed ioreq
// user supplied mapping from syscall to typed ioreq
typedef struct {
uint32_t csr;
uint32_t syscall;
uvm32_user_ioreq_code_t code;
uvm32_ioreq_typ_t typ;
} uvm32_mapping_t;
@ -75,11 +75,9 @@ typedef struct {
} 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

View file

@ -5,12 +5,12 @@
#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)
// This code expects to print via syscall 0x13C (IOREQ_PRINTD in common/uvm32_common_custom.h)
uint8_t rom[] = {
0x23, 0x26, 0x11, 0x00, 0xef, 0x00, 0x00, 0x01, 0x73, 0x50, 0x80, 0x13,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x93, 0x07, 0x00, 0x00,
0x13, 0x07, 0xa0, 0x00, 0x73, 0x90, 0xc7, 0x13, 0x93, 0x87, 0x17, 0x00,
0xe3, 0x9c, 0xe7, 0xfe, 0x67, 0x80, 0x00, 0x00
0x23, 0x26, 0x11, 0x00, 0xef, 0x00, 0x00, 0x01, 0x93, 0x08, 0x80, 0x13,
0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x05, 0x00, 0x00,
0x93, 0x07, 0xa0, 0x00, 0x93, 0x08, 0xc0, 0x13, 0x73, 0x00, 0x00, 0x00,
0x13, 0x05, 0x15, 0x00, 0xe3, 0x1a, 0xf5, 0xfe, 0x67, 0x80, 0x00, 0x00
};
// Create an identifier for our host handler
@ -20,7 +20,7 @@ typedef enum {
// Map VM ioreq IOREQ_PRINTD (0x13C) to F_PRINTD, tell VM to expect write of a U32
const uvm32_mapping_t env[] = {
{ .csr = IOREQ_PRINTD, .typ = IOREQ_TYP_U32_WR, .code = F_PRINTD },
{ .syscall = IOREQ_PRINTD, .typ = IOREQ_TYP_U32_WR, .code = F_PRINTD },
};
int main(int argc, char *argv[]) {

View file

@ -13,12 +13,12 @@
#define SCHEDULE_RANDOM() scheduler_index = rand()%NUM_VM
// Precompiled binary program to print integers
// This code expects to print via CSR 0x13C (IOREQ_PRINTD in common/uvm32_common_custom.h)
// This code expects to print via syscall 0x13C (IOREQ_PRINTD in common/uvm32_common_custom.h)
uint8_t rom[] = {
0x23, 0x26, 0x11, 0x00, 0xef, 0x00, 0x00, 0x01, 0x73, 0x50, 0x80, 0x13,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x93, 0x07, 0x00, 0x00,
0x13, 0x07, 0xa0, 0x00, 0x73, 0x90, 0xc7, 0x13, 0x93, 0x87, 0x17, 0x00,
0xe3, 0x9c, 0xe7, 0xfe, 0x67, 0x80, 0x00, 0x00
0x23, 0x26, 0x11, 0x00, 0xef, 0x00, 0x00, 0x01, 0x93, 0x08, 0x80, 0x13,
0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x05, 0x00, 0x00,
0x93, 0x07, 0xa0, 0x00, 0x93, 0x08, 0xc0, 0x13, 0x73, 0x00, 0x00, 0x00,
0x13, 0x05, 0x15, 0x00, 0xe3, 0x1a, 0xf5, 0xfe, 0x67, 0x80, 0x00, 0x00
};
// Create an identifier for our host handler
@ -28,7 +28,7 @@ typedef enum {
// Map VM ioreq IOREQ_PRINTD to F_PRINTD, tell VM to expect write of a U32
const uvm32_mapping_t env[] = {
{ .csr = IOREQ_PRINTD, .typ = IOREQ_TYP_U32_WR, .code = F_PRINTD },
{ .syscall = IOREQ_PRINTD, .typ = IOREQ_TYP_U32_WR, .code = F_PRINTD },
};
int main(int argc, char *argv[]) {

View file

@ -22,15 +22,15 @@ typedef enum {
F_GETC,
} f_code_t;
// Map exposed ioreqs to CSRs
// Map exposed ioreqs to syscalls
const uvm32_mapping_t env[] = {
{ .csr = IOREQ_PRINTLN, .typ = IOREQ_TYP_BUF_TERMINATED_WR, .code = F_PRINTLN },
{ .csr = IOREQ_PRINT, .typ = IOREQ_TYP_BUF_TERMINATED_WR, .code = F_PRINT },
{ .csr = IOREQ_PRINTD, .typ = IOREQ_TYP_U32_WR, .code = F_PRINTD },
{ .csr = IOREQ_PRINTX, .typ = IOREQ_TYP_U32_WR, .code = F_PRINTX },
{ .csr = IOREQ_PRINTC, .typ = IOREQ_TYP_U32_WR, .code = F_PRINTC },
{ .csr = IOREQ_MILLIS, .typ = IOREQ_TYP_U32_RD, .code = F_MILLIS },
{ .csr = IOREQ_GETC, .typ = IOREQ_TYP_U32_RD, .code = F_GETC },
{ .syscall = IOREQ_PRINTLN, .typ = IOREQ_TYP_BUF_TERMINATED_WR, .code = F_PRINTLN },
{ .syscall = IOREQ_PRINT, .typ = IOREQ_TYP_BUF_TERMINATED_WR, .code = F_PRINT },
{ .syscall = IOREQ_PRINTD, .typ = IOREQ_TYP_U32_WR, .code = F_PRINTD },
{ .syscall = IOREQ_PRINTX, .typ = IOREQ_TYP_U32_WR, .code = F_PRINTX },
{ .syscall = IOREQ_PRINTC, .typ = IOREQ_TYP_U32_WR, .code = F_PRINTC },
{ .syscall = IOREQ_MILLIS, .typ = IOREQ_TYP_U32_RD, .code = F_MILLIS },
{ .syscall = IOREQ_GETC, .typ = IOREQ_TYP_U32_RD, .code = F_GETC },
};
void disableRawMode(void) {

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -66,8 +66,45 @@ bool uvm32_load(uvm32_state_t *vmst, uint8_t *rom, int len) {
return true;
}
// 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;
}
#if 0
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;
}
#endif
uint32_t uvm32_run(uvm32_state_t *vmst, uvm32_evt_t *evt, uint32_t instr_meter) {
uint32_t num_instr = 0;
// uvm32_evt_ioreq_buf_t b;
if (vmst->status != UVM32_STATUS_PAUSED) {
setStatusErr(vmst, UVM32_ERR_NOTREADY);
setup_err_evt(vmst, evt);
@ -79,7 +116,65 @@ uint32_t uvm32_run(uvm32_state_t *vmst, uvm32_evt_t *evt, uint32_t instr_meter)
// 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)) {
uint32_t ret;
ret = MiniRV32IMAStep(vmst, vmst->core, vmst->memory, 0, elapsedUs, 1);
if (3 == ret) {
const uint32_t syscall = vmst->core->regs[17]; // a7
uint32_t value = vmst->core->regs[10]; // a0
bool syscall_valid = false;
// on exception we should jump to mtvec, but we handle directly
// and skip over the ecall instruction
vmst->core->pc += 4;
switch(syscall) {
// inbuilt syscalls
case IOREQ_HALT:
setStatus(vmst, UVM32_STATUS_ENDED);
syscall_valid = true;
break;
case IOREQ_YIELD:
vmst->ioevt.typ = UVM32_EVT_YIELD;
setStatus(vmst, UVM32_STATUS_PAUSED);
syscall_valid = true;
break;
// user defined syscalls
default:
// search in mappings
for (int i=0;i<vmst->numMappings;i++) {
if (syscall == vmst->mappings[i].syscall) {
// 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 = &vmst->core->regs[11]; // r1, //(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;
//#warning FIXME, retval
// vmst->core->regs[11] = 456; // r1
setStatus(vmst, UVM32_STATUS_PAUSED);
syscall_valid = true;
break; // stop searching
}
}
// no mapping found
if (!syscall_valid) {
setStatusErr(vmst, UVM32_ERR_BAD_SYSCALL);
}
break;
}
} else if (ret != 0) {
// unhandled exception
setStatusErr(vmst, UVM32_ERR_INTERNAL_CORE);
setup_err_evt(vmst, evt);
}
@ -115,83 +210,6 @@ uint32_t uvm32_run(uvm32_state_t *vmst, uvm32_evt_t *evt, uint32_t instr_meter)
}
}
// 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;i<vmst->numMappings;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;
}

View file

@ -13,7 +13,7 @@
X(UVM32_ERR_NOTREADY) \
X(UVM32_ERR_MEM_RD) \
X(UVM32_ERR_MEM_WR) \
X(UVM32_ERR_BAD_CSR) \
X(UVM32_ERR_BAD_SYSCALL) \
X(UVM32_ERR_HUNG) \
X(UVM32_ERR_INTERNAL_CORE) \
X(UVM32_ERR_INTERNAL_STATE) \
@ -40,9 +40,9 @@ typedef enum {
typedef uint32_t uvm32_user_ioreq_code_t;
// user supplied mapping from csr to typed ioreq
// user supplied mapping from syscall to typed ioreq
typedef struct {
uint32_t csr;
uint32_t syscall;
uvm32_user_ioreq_code_t code;
uvm32_ioreq_typ_t typ;
} uvm32_mapping_t;
@ -75,11 +75,9 @@ typedef struct {
} 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