mirror of
https://github.com/ringtailsoftware/uvm32.git
synced 2026-06-05 22:43:39 +00:00
uvm32 initial version
This commit is contained in:
commit
c9d30b6d28
34 changed files with 2088 additions and 0 deletions
21
LICENSE
Normal file
21
LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 Toby Jaffey <toby@ringtailsoftware.co.uk>
|
||||||
|
|
||||||
|
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.
|
||||||
12
Makefile
Normal file
12
Makefile
Normal file
|
|
@ -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)
|
||||||
|
|
||||||
161
README.md
Normal file
161
README.md
Normal file
|
|
@ -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 <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#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.
|
||||||
5
apps/Dockerfile
Normal file
5
apps/Dockerfile
Normal file
|
|
@ -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
|
||||||
15
apps/Makefile
Normal file
15
apps/Makefile
Normal file
|
|
@ -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)
|
||||||
|
|
||||||
10
apps/crt0.s
Normal file
10
apps/crt0.s
Normal file
|
|
@ -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
|
||||||
|
|
||||||
27
apps/helloworld/Makefile
Normal file
27
apps/helloworld/Makefile
Normal file
|
|
@ -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
|
||||||
|
|
||||||
7
apps/helloworld/helloworld.c
Normal file
7
apps/helloworld/helloworld.c
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
#define USE_MAIN
|
||||||
|
#include "uvm32_target.h"
|
||||||
|
|
||||||
|
void main(void) {
|
||||||
|
println("Hello world");
|
||||||
|
}
|
||||||
|
|
||||||
85
apps/linker.ld
Normal file
85
apps/linker.ld
Normal file
|
|
@ -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 = .;*/
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
235
apps/rust-hello/Cargo.lock
generated
Normal file
235
apps/rust-hello/Cargo.lock
generated
Normal file
|
|
@ -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"
|
||||||
19
apps/rust-hello/Cargo.toml
Normal file
19
apps/rust-hello/Cargo.toml
Normal file
|
|
@ -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"
|
||||||
8
apps/rust-hello/Makefile
Normal file
8
apps/rust-hello/Makefile
Normal file
|
|
@ -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
|
||||||
21
apps/rust-hello/build.rs
Normal file
21
apps/rust-hello/build.rs
Normal file
|
|
@ -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!");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
48
apps/rust-hello/src/main.rs
Normal file
48
apps/rust-hello/src/main.rs
Normal file
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
27
apps/sketch/Makefile
Normal file
27
apps/sketch/Makefile
Normal file
|
|
@ -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
|
||||||
|
|
||||||
18
apps/sketch/sketch.c
Normal file
18
apps/sketch/sketch.c
Normal file
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
12
apps/zig-mandel/Makefile
Normal file
12
apps/zig-mandel/Makefile
Normal file
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
51
apps/zig-mandel/build.zig
Normal file
51
apps/zig-mandel/build.zig
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
40
apps/zig-mandel/src/main.zig
Normal file
40
apps/zig-mandel/src/main.zig
Normal file
|
|
@ -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");
|
||||||
|
}
|
||||||
38
apps/zig-mandel/src/uvm.zig
Normal file
38
apps/zig-mandel/src/uvm.zig
Normal file
|
|
@ -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}));
|
||||||
|
}
|
||||||
|
|
||||||
10
common/uvm32_common_custom.h
Normal file
10
common/uvm32_common_custom.h
Normal file
|
|
@ -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
|
||||||
|
|
||||||
5
common/uvm32_sys.h
Normal file
5
common/uvm32_sys.h
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
// System provided IOREQs
|
||||||
|
#define IOREQ_HALT 0x138
|
||||||
|
#define IOREQ_YIELD 0x139
|
||||||
|
|
||||||
|
#include "uvm32_common_custom.h"
|
||||||
39
common/uvm32_target.h
Normal file
39
common/uvm32_target.h
Normal file
|
|
@ -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
|
||||||
|
|
||||||
17
common/uvm32_target_custom.h
Normal file
17
common/uvm32_target_custom.h
Normal file
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
5
emulator-mini/Makefile
Normal file
5
emulator-mini/Makefile
Normal file
|
|
@ -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
|
||||||
55
emulator-mini/emulator-mini.c
Normal file
55
emulator-mini/emulator-mini.c
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#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;
|
||||||
|
}
|
||||||
5
emulator-parallel/Makefile
Normal file
5
emulator-parallel/Makefile
Normal file
|
|
@ -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
|
||||||
74
emulator-parallel/emulator-parallel.c
Normal file
74
emulator-parallel/emulator-parallel.c
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#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<NUM_VM;i++) {
|
||||||
|
uvm32_init(&vmst[i], env, sizeof(env) / sizeof(env[0]));
|
||||||
|
uvm32_load(&vmst[i], rom, sizeof(rom));
|
||||||
|
}
|
||||||
|
|
||||||
|
while(numVmRunning > 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;
|
||||||
|
}
|
||||||
5
emulator/Makefile
Normal file
5
emulator/Makefile
Normal file
|
|
@ -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
|
||||||
166
emulator/emulator.c
Normal file
166
emulator/emulator.c
Normal file
|
|
@ -0,0 +1,166 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#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("<romfile>\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;
|
||||||
|
}
|
||||||
BIN
precompiled/mandel.bin
Executable file
BIN
precompiled/mandel.bin
Executable file
Binary file not shown.
538
uvm32/mini-rv32ima.h
Normal file
538
uvm32/mini-rv32ima.h
Normal file
|
|
@ -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 = (rs2<rval)?rs2:rval; break; //AMOMINU.W (0b11000)
|
||||||
|
case 28: rs2 = (rs2>rval)?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
|
||||||
|
|
||||||
|
|
||||||
193
uvm32/uvm32.c
Normal file
193
uvm32/uvm32.c
Normal file
|
|
@ -0,0 +1,193 @@
|
||||||
|
#define MINIRV32_IMPLEMENTATION
|
||||||
|
#include "uvm32.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#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;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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
116
uvm32/uvm32.h
Normal file
116
uvm32/uvm32.h
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
#ifndef UVM32_H
|
||||||
|
#define UVM32_H 1
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
// "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
|
||||||
Loading…
Add table
Add a link
Reference in a new issue