From c9d30b6d28adbd7453a82b6bcc974900de77eba8 Mon Sep 17 00:00:00 2001 From: Toby Jaffey Date: Sat, 6 Dec 2025 16:44:23 +0000 Subject: [PATCH] uvm32 initial version --- LICENSE | 21 + Makefile | 12 + README.md | 161 ++++++++ apps/Dockerfile | 5 + apps/Makefile | 15 + apps/crt0.s | 10 + apps/helloworld/Makefile | 27 ++ apps/helloworld/helloworld.c | 7 + apps/linker.ld | 85 ++++ apps/rust-hello/Cargo.lock | 235 +++++++++++ apps/rust-hello/Cargo.toml | 19 + apps/rust-hello/Makefile | 8 + apps/rust-hello/build.rs | 21 + apps/rust-hello/src/main.rs | 48 +++ apps/sketch/Makefile | 27 ++ apps/sketch/sketch.c | 18 + apps/zig-mandel/Makefile | 12 + apps/zig-mandel/build.zig | 51 +++ apps/zig-mandel/src/main.zig | 40 ++ apps/zig-mandel/src/uvm.zig | 38 ++ common/uvm32_common_custom.h | 10 + common/uvm32_sys.h | 5 + common/uvm32_target.h | 39 ++ common/uvm32_target_custom.h | 17 + emulator-mini/Makefile | 5 + emulator-mini/emulator-mini.c | 55 +++ emulator-parallel/Makefile | 5 + emulator-parallel/emulator-parallel.c | 74 ++++ emulator/Makefile | 5 + emulator/emulator.c | 166 ++++++++ precompiled/mandel.bin | Bin 0 -> 204 bytes uvm32/mini-rv32ima.h | 538 ++++++++++++++++++++++++++ uvm32/uvm32.c | 193 +++++++++ uvm32/uvm32.h | 116 ++++++ 34 files changed, 2088 insertions(+) create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 apps/Dockerfile create mode 100644 apps/Makefile create mode 100644 apps/crt0.s create mode 100644 apps/helloworld/Makefile create mode 100644 apps/helloworld/helloworld.c create mode 100644 apps/linker.ld create mode 100644 apps/rust-hello/Cargo.lock create mode 100644 apps/rust-hello/Cargo.toml create mode 100644 apps/rust-hello/Makefile create mode 100644 apps/rust-hello/build.rs create mode 100644 apps/rust-hello/src/main.rs create mode 100644 apps/sketch/Makefile create mode 100644 apps/sketch/sketch.c create mode 100644 apps/zig-mandel/Makefile create mode 100644 apps/zig-mandel/build.zig create mode 100644 apps/zig-mandel/src/main.zig create mode 100644 apps/zig-mandel/src/uvm.zig create mode 100644 common/uvm32_common_custom.h create mode 100644 common/uvm32_sys.h create mode 100644 common/uvm32_target.h create mode 100644 common/uvm32_target_custom.h create mode 100644 emulator-mini/Makefile create mode 100644 emulator-mini/emulator-mini.c create mode 100644 emulator-parallel/Makefile create mode 100644 emulator-parallel/emulator-parallel.c create mode 100644 emulator/Makefile create mode 100644 emulator/emulator.c create mode 100755 precompiled/mandel.bin create mode 100644 uvm32/mini-rv32ima.h create mode 100644 uvm32/uvm32.c create mode 100644 uvm32/uvm32.h diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..bbf49e4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Toby Jaffey + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9853501 --- /dev/null +++ b/Makefile @@ -0,0 +1,12 @@ +all: + (cd emulator && make) + (cd emulator-mini && make) + (cd emulator-parallel && make) + #(cd apps && make) # do not build apps by default, as they require a variety of dev tools + +clean: + (cd emulator && make clean) + (cd emulator-mini && make clean) + (cd emulator-parallel && make clean) + (cd apps && make clean) + diff --git a/README.md b/README.md new file mode 100644 index 0000000..7a19236 --- /dev/null +++ b/README.md @@ -0,0 +1,161 @@ +# 🤖 uvm32 + +uvm32 is a minimalist, dependency-free virtual machine sandbox designed for microcontrollers and other resource-constrained devices. Single C file, no dynamic memory allocations, asynchronous design, pure C99. + +## Features + +* Bytecode example apps written in C, Zig and Rust +* Non-blocking design, preventing misbehaving bytecode from stalling the host +* No assumptions about host IO capabilities (no stdio) +* Simple, opinionated execution model +* Safe minimalistic FFI +* Small enough for "if this then that" scripts/plugins, capable enough for much more + +Check out the [apps](apps) folder for more. + +## Quickstart + + make + emulator/emulator precompiled/mandel.bin + +Build one of the sample apps (requires docker for C, or Zig, or Rust) + + cd apps/helloworld && make + +Run the app + + ./emulator ../apps/helloworld/helloworld.bin + +## Quickstart API + +```c +uint8_t bytecode[] = { /* ... */ }; // some compiled bytecode +uvm32_state_t vmst; // execution state of the vm +uvm32_evt_t evt; // events passed from vm to host + +uvm32_init(&vmst, NULL, 0); // setup vm and pass in handlers for host functions +uvm32_load(&vmst, bytecode, sizeof(bytecode)); // load the bytecode +uvm32_run(&vmst, &evt, 100); // run up to 100 instructions + +switch(evt.typ) { + // check why the vm stopped executing +} +``` + +## Operation + +Once loaded with bytecode, uvm32's state is advanced by calling `uvm32_run()`. + + uint32_t uvm32_run(uvm32_state_t *vmst, uvm32_evt_t *evt, uint32_t instr_meter) + +`uvm32_run()` will execute until the bytecode requests some IO activity from the host. +These IO activities are called "ioreqs" and are the only way for bytecode to communicate with the host. +If the bytecode attempts to execute more instructions than the the passed value of `instr_meter` it is assumed to have crashed and an error is reported. + +(As with a watchdog on an embedded system, the `yield()` bytecode function tells the host that the code requires more time to complete and has not hung) + +`uvm32_run()` always returns an event. There are four possible events: + +* `UVM32_EVT_END` the program has ended +* `UVM32_EVT_ERR` the program has encountered an error +* `UVM32_EVT_YIELD` the program has called `yield()` signifying that it requires more instructions to be executed, but has not crashed/hung +* `UVM32_EVT_IOREQ` the program requests some IO via the host + +## 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 is always in one of 4 states, paused, running, ended or error. + +```mermaid +stateDiagram + [*] --> UVM32_STATUS_PAUSED : uvm32_init() + UVM32_STATUS_PAUSED-->UVM32_STATUS_RUNNING : uvm32_run() + UVM32_STATUS_RUNNING --> UVM32_STATUS_PAUSED : ioreq event + UVM32_STATUS_RUNNING --> UVM32_STATUS_ENDED : halt() + UVM32_STATUS_RUNNING --> UVM32_STATUS_ERROR +``` + + +## ioreqs + +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. + +Here is a full example of a working VM host from [apps/emulator-mini](apps/emulator-mini) + +-- + + #include + #include + #include + #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, 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 + }; + + // Create an identifier for our host handler + typedef enum { + F_PRINTD, + } 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[] = { + { .csr = IOREQ_PRINTD, .typ = IOREQ_TYP_U32_WR, .code = F_PRINTD }, + }; + + int main(int argc, char *argv[]) { + uvm32_state_t vmst; + uvm32_evt_t evt; + bool isrunning = true; + + uvm32_init(&vmst, env, sizeof(env) / sizeof(env[0])); + uvm32_load(&vmst, rom, sizeof(rom)); + + while(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: + // Type of F_PRINTD is IOREQ_TYP_U32_WR, so expect value in evt.data.ioreq.val.u32 + printf("%d\n", evt.data.ioreq.val.u32); + break; + } + break; + default: + break; + } + } + + return 0; + } + +## Samples + + * [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 + * [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 + * [apps/zig-mandel](apps/zig-mandel) Zig ASCII mandelbrot generator program + +## License + +This project is licensed under the MIT License. Feel free to use in research, products and embedded devices. diff --git a/apps/Dockerfile b/apps/Dockerfile new file mode 100644 index 0000000..0e5d18c --- /dev/null +++ b/apps/Dockerfile @@ -0,0 +1,5 @@ +FROM ubuntu:25.04 +ENV LANG C.UTF-8 +ENV LC_ALL C.UTF-8 +RUN apt-get -y update +RUN apt-get install -y gcc-riscv64-unknown-elf build-essential diff --git a/apps/Makefile b/apps/Makefile new file mode 100644 index 0000000..2e08ac2 --- /dev/null +++ b/apps/Makefile @@ -0,0 +1,15 @@ +DOCKER_IMAGE=riscv-dev + +all: + docker build -t ${DOCKER_IMAGE} . + (cd sketch && make) + (cd helloworld && make) + (cd zig-mandel && make) + (cd rust-hello && make) + +clean: + (cd sketch && make clean) + (cd helloworld && make clean) + (cd zig-mandel && make clean) + (cd rust-hello && make clean) + diff --git a/apps/crt0.s b/apps/crt0.s new file mode 100644 index 0000000..3209b9d --- /dev/null +++ b/apps/crt0.s @@ -0,0 +1,10 @@ +.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 + diff --git a/apps/helloworld/Makefile b/apps/helloworld/Makefile new file mode 100644 index 0000000..8946223 --- /dev/null +++ b/apps/helloworld/Makefile @@ -0,0 +1,27 @@ +PROJECT:=helloworld + +DOCKER_IMAGE=riscv-dev +DOCKER_CMD:=docker run --rm -v ${PWD}../../../:/data -w /data/apps/${PROJECT} ${DOCKER_IMAGE} +PREFIX:=${DOCKER_CMD} riscv64-unknown-elf- +CFLAGS+=-I../../common +CFLAGS+=-fno-stack-protector +CFLAGS+=-static-libgcc -fdata-sections -ffunction-sections +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 + +all: + ${PREFIX}gcc -o ${PROJECT}.elf ${CFLAGS} ${LDFLAGS} ${SRCS} ${LIBS} + $(PREFIX)objcopy ${PROJECT}.elf -O binary ${PROJECT}.bin + +disasm: all + $(PREFIX)objdump -S -d -f ${PROJECT}.elf + +test: all + ../../emulator/emulator ${PWD}/${PROJECT}.bin + +clean: + rm -f ${PROJECT}.o ${PROJECT}.elf ${PROJECT}.bin + diff --git a/apps/helloworld/helloworld.c b/apps/helloworld/helloworld.c new file mode 100644 index 0000000..975bddd --- /dev/null +++ b/apps/helloworld/helloworld.c @@ -0,0 +1,7 @@ +#define USE_MAIN +#include "uvm32_target.h" + +void main(void) { + println("Hello world"); +} + diff --git a/apps/linker.ld b/apps/linker.ld new file mode 100644 index 0000000..57d28b8 --- /dev/null +++ b/apps/linker.ld @@ -0,0 +1,85 @@ +/*__heap_size = 0x100; */ +__stack_size = 0x100; + +ENTRY(_start) + +SECTIONS +{ + . = 0x80000000; +/* + .header : ALIGN( 16 ) + { + LONG( 0 ) + LONG( 0 ) + } +*/ + .text : ALIGN(16) { + __TEXT_BEGIN__ = .; + *(.initial_jump) + *(.entry.text) + *(.init.literal) + *(.init) + *(.text) + *(.literal .text .literal.* .text.* .stub) + *(.out_jump.literal.*) + *(.out_jump.*) + __TEXT_END__ = .; + } + + /* If we're on a newer compiler */ + /DISCARD/ : + { + *(.interp) + *(.dynsym) + *(.dynstr) + *(.header) + } : phdr + + .data : ALIGN(16) { + __DATA_BEGIN__ = .; + *(.rodata) + *(.rodata.*) + *(.gnu.linkonce.r.*) + *(.rodata1) + *(.dynsbss) + *(.gnu.linkonce.sb.*) + *(.scommon) + *(.gnu.linkonce.sb2.*) + *(.sbss) + *(.sbss.*) + *(.sbss2) + *(.sbss2.*) + *(.dynbss) + *(.data) + *(.data.*) + *(.got) + *(.got.*) + __DATA_END__ = .; + } + + .bss : ALIGN( 16 ) { + __BSS_BEGIN__ = .; + *(.bss) /* Tricky: BSS needs to be allocated but not sent. GCC Will not populate these for calculating data size */ + *(.bss.*) + __BSS_END__ = .; + } + +/* + .heap : ALIGN( 16 ) { + _sheap = .; + . = . + __heap_size; + _eheap = .; + } +*/ +/* + .stack : ALIGN( 16 ) { + _estack = .; + . = . + __stack_size; + _sstack = .; + } +*/ +/* _sstack = .;*/ + +} + + diff --git a/apps/rust-hello/Cargo.lock b/apps/rust-hello/Cargo.lock new file mode 100644 index 0000000..97fd693 --- /dev/null +++ b/apps/rust-hello/Cargo.lock @@ -0,0 +1,235 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "bindgen" +version = "0.71.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", +] + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "libc" +version = "0.2.178" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "panic-halt" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a513e167849a384b7f9b746e517604398518590a9142f4846a32e3c2a4de7b11" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "rust-hello" +version = "0.0.1" +dependencies = [ + "bindgen", + "panic-halt", +] + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "syn" +version = "2.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" diff --git a/apps/rust-hello/Cargo.toml b/apps/rust-hello/Cargo.toml new file mode 100644 index 0000000..b79c17e --- /dev/null +++ b/apps/rust-hello/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "rust-hello" +version = "0.0.1" +edition = "2021" + +[dependencies] +panic-halt = "1.0.0" + +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" +opt-level = "z" # Optimize for size +lto = true +strip = true + +[build-dependencies] +bindgen = "0.71.0" diff --git a/apps/rust-hello/Makefile b/apps/rust-hello/Makefile new file mode 100644 index 0000000..9422f4a --- /dev/null +++ b/apps/rust-hello/Makefile @@ -0,0 +1,8 @@ +all: + cargo build -r --target riscv32im-unknown-none-elf && docker run -v `pwd`:/data -w /data --rm riscv-dev riscv64-unknown-elf-objcopy target/riscv32im-unknown-none-elf/release/rust-hello -O binary rust-hello.bin + +test: all + ../../emulator/emulator rust-hello.bin + +clean: + rm -rf rust-hello.bin target diff --git a/apps/rust-hello/build.rs b/apps/rust-hello/build.rs new file mode 100644 index 0000000..5a8597a --- /dev/null +++ b/apps/rust-hello/build.rs @@ -0,0 +1,21 @@ +use std::env; +use std::path::PathBuf; + +fn main() { + // linker + println!("cargo:rustc-link-arg-bin=rust-hello=-T../linker.ld"); + + let bindings = bindgen::Builder::default() + .header("../../common/uvm32_sys.h") + .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) + .generate() + .expect("Unable to generate bindings"); + + // Write the bindings to the $OUT_DIR/bindings.rs file. + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + bindings + .write_to_file(out_path.join("bindings.rs")) + .expect("Couldn't write bindings!"); +} + + diff --git a/apps/rust-hello/src/main.rs b/apps/rust-hello/src/main.rs new file mode 100644 index 0000000..fc5c879 --- /dev/null +++ b/apps/rust-hello/src/main.rs @@ -0,0 +1,48 @@ +#![no_std] +#![no_main] + +use core::arch::global_asm; +use core::arch::asm; +use core::panic::PanicInfo; + +// fetch IOREQ definitions from C header +include!(concat!(env!("OUT_DIR"), "/bindings.rs")); + +// startup code +global_asm!(include_str!("../../crt0.s")); + +fn println(message: &str) { + unsafe { + asm!( + "csrw {i}, {x}", + i = const IOREQ_PRINTLN, + x = in(reg) message.as_ptr(), + ); + } +} + +fn printd(n: u32) { + unsafe { + asm!( + "csrw {i}, {x}", + i = const IOREQ_PRINTD, + x = in(reg) n, + ); + } +} + +#[no_mangle] +pub extern "C" fn main() { + for i in 0..10 { + printd(i); + } + println("Hello, world!"); +} + +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + //println("Something went wrong"); + loop { + continue; + } +} diff --git a/apps/sketch/Makefile b/apps/sketch/Makefile new file mode 100644 index 0000000..9da16a2 --- /dev/null +++ b/apps/sketch/Makefile @@ -0,0 +1,27 @@ +PROJECT:=sketch + +DOCKER_IMAGE=riscv-dev +DOCKER_CMD:=docker run --rm -v ${PWD}../../../:/data -w /data/apps/${PROJECT} ${DOCKER_IMAGE} +PREFIX:=${DOCKER_CMD} riscv64-unknown-elf- +CFLAGS+=-I../../common +CFLAGS+=-fno-stack-protector +CFLAGS+=-static-libgcc -fdata-sections -ffunction-sections +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 + +all: + ${PREFIX}gcc -o ${PROJECT}.elf ${CFLAGS} ${LDFLAGS} ${SRCS} ${LIBS} + $(PREFIX)objcopy ${PROJECT}.elf -O binary ${PROJECT}.bin + +disasm: all + $(PREFIX)objdump -S -d -f ${PROJECT}.elf + +test: all + ../../emulator/emulator ${PWD}/${PROJECT}.bin + +clean: + rm -f ${PROJECT}.o ${PROJECT}.elf ${PROJECT}.bin + diff --git a/apps/sketch/sketch.c b/apps/sketch/sketch.c new file mode 100644 index 0000000..cf249fe --- /dev/null +++ b/apps/sketch/sketch.c @@ -0,0 +1,18 @@ +#include "uvm32_target.h" + +uint32_t count; + +bool loop(void) { + printd(count); + if (count++ >= 10) { + return false; + } else { + return true; + } +} + +void setup(void) { + count = 0; +} + + diff --git a/apps/zig-mandel/Makefile b/apps/zig-mandel/Makefile new file mode 100644 index 0000000..814f75a --- /dev/null +++ b/apps/zig-mandel/Makefile @@ -0,0 +1,12 @@ +all: + # zig's objcopy is broken, so use external tool + # https://ziggit.dev/t/addobjcopy-producing-zero-padding-at-start-of-binary/13384 + zig build && docker run -v `pwd`:/data -w /data --rm riscv-dev riscv64-unknown-elf-objcopy zig-out/bin/mandel -O binary mandel.bin + +clean: + rm -rf mandel.bin zig-out .zig-cache + +test: all + ../../emulator/emulator mandel.bin + + diff --git a/apps/zig-mandel/build.zig b/apps/zig-mandel/build.zig new file mode 100644 index 0000000..0d1fbd9 --- /dev/null +++ b/apps/zig-mandel/build.zig @@ -0,0 +1,51 @@ +const std = @import("std"); +const CrossTarget = @import("std").zig.CrossTarget; +const Target = @import("std").Target; +const Feature = @import("std").Target.Cpu.Feature; + +pub fn build(b: *std.Build) void { + const features = Target.riscv.Feature; + var disabled_features = Feature.Set.empty; + var enabled_features = Feature.Set.empty; + + // disable all CPU extensions + disabled_features.addFeature(@intFromEnum(features.a)); + disabled_features.addFeature(@intFromEnum(features.c)); + disabled_features.addFeature(@intFromEnum(features.d)); + disabled_features.addFeature(@intFromEnum(features.e)); + disabled_features.addFeature(@intFromEnum(features.f)); + // except multiply + enabled_features.addFeature(@intFromEnum(features.m)); + + const target = b.resolveTargetQuery(.{ + .cpu_arch = Target.Cpu.Arch.riscv32, + .os_tag = Target.Os.Tag.freestanding, + .abi = Target.Abi.none, + .cpu_model = .{ .explicit = &std.Target.riscv.cpu.generic_rv32}, + .cpu_features_sub = disabled_features, + .cpu_features_add = enabled_features + }); + + const exe = b.addExecutable(.{ + .name = "mandel", + .root_module = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = .ReleaseSmall, + }), + }); + + b.installArtifact(exe); + + exe.addAssemblyFile(b.path("../crt0.s")); + exe.setLinkerScript(b.path("../linker.ld")); + exe.addIncludePath(b.path("../../common")); + + const bin = b.addObjCopy(exe.getEmittedBin(), .{ + .format = .bin, + }); + bin.step.dependOn(&exe.step); + + const copy_bin = b.addInstallBinFile(bin.getOutput(), "mandel.bin"); + b.default_step.dependOn(©_bin.step); +} diff --git a/apps/zig-mandel/src/main.zig b/apps/zig-mandel/src/main.zig new file mode 100644 index 0000000..145e1bb --- /dev/null +++ b/apps/zig-mandel/src/main.zig @@ -0,0 +1,40 @@ +const uvm = @import("uvm.zig"); + +fn mandel() void { + const xmin: i32 = -8601; + const xmax: i32 = 2867; + const ymin: i32 = -4915; + const ymax: i32 = 4915; + const maxiter: usize = 32; + const dx: i32 = @divTrunc((xmax - xmin), 79); + const dy: i32 = @divTrunc((ymax - ymin), 24); + var cy = ymin; + + while (cy <= ymax) { + var cx = xmin; + while (cx <= xmax) { + var x: i32 = 0; + var y: i32 = 0; + var x2: i32 = 0; + var y2: i32 = 0; + var iter: usize = 0; + while (iter < maxiter) : (iter += 1) { + if (x2 + y2 > 16384) break; + y = ((x * y) >> 11) + cy; + x = x2 - y2 + cx; + x2 = (x * x) >> 12; + y2 = (y * y) >> 12; + uvm.yield(); + } + uvm.printc(' ' + @as(u8, @intCast(iter))); + cx += dx; + } + uvm.printc('\n'); + cy += dy; + } +} + +export fn main() void { + mandel(); + uvm.println("Hello world"); +} diff --git a/apps/zig-mandel/src/uvm.zig b/apps/zig-mandel/src/uvm.zig new file mode 100644 index 0000000..e488c09 --- /dev/null +++ b/apps/zig-mandel/src/uvm.zig @@ -0,0 +1,38 @@ +const uvm32 = @cImport({ + @cDefine("USE_MAIN", "1"); + @cInclude("uvm32_target.h"); +}); +const std = @import("std"); + +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 yield() void { + asm volatile (std.fmt.comptimePrint("csrwi 0x{x}, 0", .{uvm32.IOREQ_YIELD})); +} + diff --git a/common/uvm32_common_custom.h b/common/uvm32_common_custom.h new file mode 100644 index 0000000..e5c0a35 --- /dev/null +++ b/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/common/uvm32_sys.h b/common/uvm32_sys.h new file mode 100644 index 0000000..5b2992a --- /dev/null +++ b/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/common/uvm32_target.h b/common/uvm32_target.h new file mode 100644 index 0000000..2a9edf2 --- /dev/null +++ b/common/uvm32_target.h @@ -0,0 +1,39 @@ +// Common to all target code + +#include "uvm32_sys.h" + +// Basic types +typedef long uint32_t; +typedef char uint8_t; +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"); \ + } + +#include "uvm32_common_custom.h" +#include "uvm32_target_custom.h" + +// provide main, with setup()/loop() flow +void setup(void); +bool loop(void); + +#ifndef USE_MAIN +void main(void) { + setup(); + while(loop()) { + yield(); + } +} +#endif + diff --git a/common/uvm32_target_custom.h b/common/uvm32_target_custom.h new file mode 100644 index 0000000..8aa3e12 --- /dev/null +++ b/common/uvm32_target_custom.h @@ -0,0 +1,17 @@ +// 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; +} + diff --git a/emulator-mini/Makefile b/emulator-mini/Makefile new file mode 100644 index 0000000..37a865e --- /dev/null +++ b/emulator-mini/Makefile @@ -0,0 +1,5 @@ +all: + gcc -Wall -DUVM32_MEMORY_SIZE=512 -I../uvm32 -o emulator-mini ../uvm32/uvm32.c emulator-mini.c + +clean: + rm -f emulator-mini diff --git a/emulator-mini/emulator-mini.c b/emulator-mini/emulator-mini.c new file mode 100644 index 0000000..dce277a --- /dev/null +++ b/emulator-mini/emulator-mini.c @@ -0,0 +1,55 @@ +#include +#include +#include +#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, 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 +}; + +// Create an identifier for our host handler +typedef enum { + F_PRINTD, +} 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[] = { + { .csr = IOREQ_PRINTD, .typ = IOREQ_TYP_U32_WR, .code = F_PRINTD }, +}; + +int main(int argc, char *argv[]) { + uvm32_state_t vmst; + uvm32_evt_t evt; + bool isrunning = true; + + uvm32_init(&vmst, env, sizeof(env) / sizeof(env[0])); + uvm32_load(&vmst, rom, sizeof(rom)); + + while(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: + // Type of F_PRINTD is IOREQ_TYP_U32_WR, so expect value in evt.data.ioreq.val.u32 + printf("%d\n", evt.data.ioreq.val.u32); + break; + } + break; + default: + break; + } + } + + return 0; +} diff --git a/emulator-parallel/Makefile b/emulator-parallel/Makefile new file mode 100644 index 0000000..35aaa30 --- /dev/null +++ b/emulator-parallel/Makefile @@ -0,0 +1,5 @@ +all: + gcc -Wall -DUVM32_MEMORY_SIZE=512 -I../uvm32 -o emulator-parallel ../uvm32/uvm32.c emulator-parallel.c + +clean: + rm -f emulator-parallel diff --git a/emulator-parallel/emulator-parallel.c b/emulator-parallel/emulator-parallel.c new file mode 100644 index 0000000..699258d --- /dev/null +++ b/emulator-parallel/emulator-parallel.c @@ -0,0 +1,74 @@ +#include +#include +#include +#include "uvm32.h" +#include "../common/uvm32_common_custom.h" + +// Run multiple scheduled VMs in parallel until all have ended +#define NUM_VM 4 // number of vms +#define SCHEDULE SCHEDULE_RANDOM // scheduling algorithm + +// scheduling algorithms +#define SCHEDULE_ROUNDROBIN() scheduler_index = (scheduler_index + 1) % NUM_VM +#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) +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 +}; + +// Create an identifier for our host handler +typedef enum { + F_PRINTD, +} f_code_t; + +// 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 }, +}; + +int main(int argc, char *argv[]) { + uvm32_state_t vmst[NUM_VM]; + uvm32_evt_t evt; + int numVmRunning = NUM_VM; + int scheduler_index = 0; + + for (int i=0;i 0) { + if (uvm32_hasEnded(&vmst[scheduler_index])) { + // this vm has already completed, pick another + SCHEDULE(); + continue; + } + uvm32_run(&vmst[scheduler_index], &evt, 100); // num instructions before vm considered hung + + switch(evt.typ) { + case UVM32_EVT_END: + printf("[VM %d ended]\n", scheduler_index); + numVmRunning--; + break; + case UVM32_EVT_IOREQ: // vm has paused to handle IOREQ + switch((f_code_t)evt.data.ioreq.code) { + case F_PRINTD: + // Type of F_PRINTD is IOREQ_TYP_U32_WR, so expect value in evt.data.ioreq.val.u32 + printf("[VM %d]: %d\n", scheduler_index, evt.data.ioreq.val.u32); + break; + } + break; + default: + break; + } + + SCHEDULE(); + } + + return 0; +} diff --git a/emulator/Makefile b/emulator/Makefile new file mode 100644 index 0000000..a7763c3 --- /dev/null +++ b/emulator/Makefile @@ -0,0 +1,5 @@ +all: + gcc -Wall -Werror -pedantic -std=c99 -DUVM32_MEMORY_SIZE=16384 -I../uvm32 -o emulator ../uvm32/uvm32.c emulator.c + +clean: + rm -f emulator diff --git a/emulator/emulator.c b/emulator/emulator.c new file mode 100644 index 0000000..d342558 --- /dev/null +++ b/emulator/emulator.c @@ -0,0 +1,166 @@ +#include +#include +#include + +#include "uvm32.h" + +#include "../common/uvm32_common_custom.h" + +// ioreqs exposed to vm environement +typedef enum { + F_PRINT, + F_PRINTD, + F_PRINTX, + F_PRINTC, + F_PRINTLN, + F_MILLIS, +} f_code_t; + +// Map exposed ioreqs to CSRs +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 }, +}; + +static uint8_t *read_file(const char* filename, int *len) { + FILE* f = fopen(filename, "rb"); + uint8_t *buf = NULL; + + if (f == NULL) { + fprintf(stderr, "error: can't open file '%s'.\n", filename); + return NULL; + } + + fseek(f, 0, SEEK_END); + size_t file_size = ftell(f); + rewind(f); + + if (NULL == (buf = malloc(file_size))) { + fclose(f); + return NULL; + } + + size_t result = fread(buf, sizeof(uint8_t), file_size, f); + if (result != file_size) { + fprintf(stderr, "error: while reading file '%s'\n", filename); + free(buf); + fclose(f); + return NULL; + } + + *len = file_size; + return buf; +} + +void hexdump(const uint8_t *p, int len) { + while(len--) { + printf("%02x", *p++); + } +} + + +int main(int argc, char *argv[]) { + uvm32_state_t vmst; + + argc--; + argv++; + + if (argc < 1) { + printf("\n"); + return 1; + } + + int romlen = 0; + uint8_t *rom = read_file(argv[0], &romlen); + if (NULL == rom) { + printf("file read failed!\n"); + return 1; + } + + uvm32_init(&vmst, env, sizeof(env) / sizeof(env[0])); + if (!uvm32_load(&vmst, rom, romlen)) { + printf("load failed!\n"); + return 1; + } + + uvm32_evt_t evt; + bool isrunning = true; + uint32_t total_instrs = 0; + uint32_t num_ioreqs = 0; + + while(isrunning) { + total_instrs += uvm32_run(&vmst, &evt, 100); // num instructions before vm considered hung + num_ioreqs++; + + switch(evt.typ) { + case UVM32_EVT_END: + printf("UVM32_EVT_END\n"); + isrunning = false; + break; + case UVM32_EVT_YIELD: + //printf("UVM32_EVT_YIELD\n"); + // program has paused, but no ioreq + break; + case UVM32_EVT_ERR: + printf("UVM32_EVT_ERR '%s' (%d)\n", evt.data.err.errstr, (int)evt.data.err.errcode); + isrunning = false; + break; + case UVM32_EVT_IOREQ: + switch((f_code_t)evt.data.ioreq.code) { + case F_PRINT: + printf("%.*s", evt.data.ioreq.val.buf.len, evt.data.ioreq.val.buf.ptr); + break; + case F_PRINTLN: + printf("%.*s\n", evt.data.ioreq.val.buf.len, evt.data.ioreq.val.buf.ptr); + break; + case F_PRINTD: + printf("%d\n", evt.data.ioreq.val.u32); + break; + case F_PRINTC: + printf("%c", evt.data.ioreq.val.u32); + break; + case F_PRINTX: + printf("%08x", evt.data.ioreq.val.u32); + break; + case F_MILLIS: { + static uint32_t t = 0; + *evt.data.ioreq.val.u32p = t; + t++; + } break; + default: // catch any others + switch(evt.data.ioreq.typ) { + case IOREQ_TYP_BUF_TERMINATED_WR: + printf("IOREQ_TYP_BUF_TERMINATED_WR code=%d val=", evt.data.ioreq.code); + hexdump(evt.data.ioreq.val.buf.ptr, evt.data.ioreq.val.buf.len); + printf("\n"); + break; + case IOREQ_TYP_VOID: + printf("IOREQ_TYP_VOID code=%d\n", evt.data.ioreq.code); + break; + case IOREQ_TYP_U32_WR: + printf("IOREQ_TYP_U32_WR code=%d val=%d (0x%08x)\n", evt.data.ioreq.code, evt.data.ioreq.val.u32, evt.data.ioreq.val.u32); + break; + case IOREQ_TYP_U32_RD: + printf("IOREQ_TYP_U32_RD code=%d\n", evt.data.ioreq.code); + *evt.data.ioreq.val.u32p = 123456; + break; + } + break; + } + break; + default: + printf("Bad evt %d\n", evt.typ); + return 1; + break; + } + } + + printf("Executed total of %d instructions and %d ioreqs\n", (int)total_instrs, (int)num_ioreqs); + + free(rom); + return 0; +} diff --git a/precompiled/mandel.bin b/precompiled/mandel.bin new file mode 100755 index 0000000000000000000000000000000000000000..2f7f7402c46bcd9c3e9eceac5616bffe2033233e GIT binary patch literal 204 zcmY#Z6J&VL(7;d}&>(F7?f?Jn;tUMt&;S1yX8pjp-IaksnC;q`$!+Gw!tCYGCbye! z7Uo#MkbLGC+hh)QhU7!f*(Ngs)qpS`1H)txW@2Cx?q~X!ocEO3n7^1I`4y1vJIB14 zuh^kDV1lr4+&+iReR7P(eCHU9o9{6T2OnnG%zTb{^5w$}lbeJX@;}7?DW1S2GPy&7 z^YJC|-^BtPBEoFbBp=^6_QRZ&p+T6Hm8V!>t8jV)0~mOu=H%onl;;=aq%Z&g0eM7` literal 0 HcmV?d00001 diff --git a/uvm32/mini-rv32ima.h b/uvm32/mini-rv32ima.h new file mode 100644 index 0000000..531648e --- /dev/null +++ b/uvm32/mini-rv32ima.h @@ -0,0 +1,538 @@ +// Copyright 2022 Charles Lohr, you may use this file or any portions herein under any of the BSD, MIT, or CC0 licenses. + +#ifndef _MINI_RV32IMAH_H +#define _MINI_RV32IMAH_H + +/** + To use mini-rv32ima.h for the bare minimum, the following: + + #define MINI_RV32_RAM_SIZE ram_amt + #define MINIRV32_IMPLEMENTATION + + #include "mini-rv32ima.h" + + Though, that's not _that_ interesting. You probably want I/O! + + + Notes: + * There is a dedicated CLNT at 0x10000000. + * There is free MMIO from there to 0x12000000. + * You can put things like a UART, or whatever there. + * Feel free to override any of the functionality with macros. +*/ + +#ifndef MINIRV32_DECORATE + #define MINIRV32_DECORATE static +#endif + +#ifndef MINIRV32_RAM_IMAGE_OFFSET + #define MINIRV32_RAM_IMAGE_OFFSET 0x80000000 +#endif + +#ifndef MINIRV32_MMIO_RANGE + #define MINIRV32_MMIO_RANGE(n) (0x10000000 <= (n) && (n) < 0x12000000) +#endif + +#ifndef MINIRV32_POSTEXEC + #define MINIRV32_POSTEXEC(...); +#endif + +#ifndef MINIRV32_HANDLE_MEM_STORE_CONTROL + #define MINIRV32_HANDLE_MEM_STORE_CONTROL(...); +#endif + +#ifndef MINIRV32_HANDLE_MEM_LOAD_CONTROL + #define MINIRV32_HANDLE_MEM_LOAD_CONTROL(...); +#endif + +#ifndef MINIRV32_OTHERCSR_WRITE + #define MINIRV32_OTHERCSR_WRITE(...); +#endif + +#ifndef MINIRV32_OTHERCSR_READ + #define MINIRV32_OTHERCSR_READ(...); +#endif + +#ifndef MINIRV32_CUSTOM_MEMORY_BUS + #define MINIRV32_STORE4( ofs, val ) *(uint32_t*)(image + ofs) = val + #define MINIRV32_STORE2( ofs, val ) *(uint16_t*)(image + ofs) = val + #define MINIRV32_STORE1( ofs, val ) *(uint8_t*)(image + ofs) = val + #define MINIRV32_LOAD4( ofs ) *(uint32_t*)(image + ofs) + #define MINIRV32_LOAD2( ofs ) *(uint16_t*)(image + ofs) + #define MINIRV32_LOAD1( ofs ) *(uint8_t*)(image + ofs) + #define MINIRV32_LOAD2_SIGNED( ofs ) *(int16_t*)(image + ofs) + #define MINIRV32_LOAD1_SIGNED( ofs ) *(int8_t*)(image + ofs) +#endif + +// As a note: We quouple-ify these, because in HLSL, we will be operating with +// uint4's. We are going to uint4 data to/from system RAM. +// +// We're going to try to keep the full processor state to 12 x uint4. +struct MiniRV32IMAState +{ + uint32_t regs[32]; + + uint32_t pc; + uint32_t mstatus; + uint32_t cyclel; + uint32_t cycleh; + + uint32_t timerl; + uint32_t timerh; + uint32_t timermatchl; + uint32_t timermatchh; + + uint32_t mscratch; + uint32_t mtvec; + uint32_t mie; + uint32_t mip; + + uint32_t mepc; + uint32_t mtval; + uint32_t mcause; + + // Note: only a few bits are used. (Machine = 3, User = 0) + // Bits 0..1 = privilege. + // Bit 2 = WFI (Wait for interrupt) + // Bit 3+ = Load/Store reservation LSBs. + uint32_t extraflags; +}; + +#ifndef MINIRV32_STEPPROTO +MINIRV32_DECORATE int32_t MiniRV32IMAStep(void *userdata, struct MiniRV32IMAState * state, uint8_t * image, uint32_t vProcAddress, uint32_t elapsedUs, int count ); +#endif + +#ifdef MINIRV32_IMPLEMENTATION + +#ifndef MINIRV32_CUSTOM_INTERNALS +#define CSR( x ) state->x +#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/uvm32/uvm32.c b/uvm32/uvm32.c new file mode 100644 index 0000000..02c3eba --- /dev/null +++ b/uvm32/uvm32.c @@ -0,0 +1,193 @@ +#define MINIRV32_IMPLEMENTATION +#include "uvm32.h" +#include +#include + +#ifndef UVM32_MEMORY_SIZE +#error Define UVM32_MEMORY_SIZE +#endif + +#include "mini-rv32ima.h" + +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/uvm32/uvm32.h b/uvm32/uvm32.h new file mode 100644 index 0000000..dc9b3a8 --- /dev/null +++ b/uvm32/uvm32.h @@ -0,0 +1,116 @@ +#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" + +#define X(name) #name, +static const char *errNames[] = { + LIST_OF_UVM32_ERRS +}; +#undef X + +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