An experimental 32-bit [0] RISC-V emulator written in plain C [1], with a focus on porting the result to a HLSL pixel shader.
The resulting HLSL (Unity CG) can be found in _Nix/
, read on to learn more.
A version of this shader running in VRChat can be found in this world:
https://vrchat.com/home/world/wrld_8126d9ef-eba5-4d49-9867-9e3c4f0b290d
The C version was initially based on takahirox/riscv-rust (see for example instruction decoder generation using instructions.txt
and parse_ins.pl
), which is a great resource for learning about RISC-V in general, aside from the official specs of course.
[0] GPUs only really support 32-bit integer math (in the use-case I want to put this in anyway)
[1] The elf loader ('elfy') is written in Rust, because I was too lazy to do it myself in C (Rust uses the 'elf' crate) and it doesn't need porting anyway.
This repo makes heavy use of submodules, so make sure to use git clone --recursive
on initial cloning (may take a long time) and run git submodule update --recursive
after a new pull.
To build any of the code included, you will probably need the following:
- A C compiler (gcc, clang)
- A rust compiler
- Perl 5
- A device tree compiler (dtc)
Pay no mind to the somewhat weird style of C please, it is mostly because of the need to port to HLSL.
To build, remove the 'rvc' binary and 'src/main.o', then run 'make rvc' from the top level directory.
To run the riscv-tests, use test.sh
. test.sh all --clean
can be used to run the full supported test suite.
The shader version is a direct port of the C one. It can be found as part of a Unity project in _Nix/
.
This is not a drag-and-drop prefab by any means, it will require careful setup and integration into a world to be useful.
The biggest pitfall is probably the required Perl support. The shader code uses perlpp and a valid runtime environment (I recommend Strawberry Perl for Windows). See the file AutoImport.cs
for more information, you will need to set a valid path in there. Technically, the already created *.h files can be used as-is, but you won't be able to meaningfully change any code without it.
The approach uses a Custom Render Texture and some (really messy) VRChat Udon scripts to manage the emulator during runtime. They will probably require fixing up for use in your own world.
The NixDebug.cs
(Udon) script needs to be placed on a camera pointing at a camera loop reading in from the 'display' shader. This provides a translation layer between the 128bit-per-pixel integer texture used in the CRT and the Color(float, float, float, float)
struct available in Unity/C#.
NixControl.cs
is built with support for my Dial prefab in mind, but can be changed to support any other control input too of course.
For both the target toolchain (riscv32-gnu-linux-
) and the initramfs, buildroot is used - this is not a submodule, but an extracted and slightly modified version instead.
To build, run make buildroot-2022.02.1/build.marker
in the top level directory. The toolchain will be made in buildroot-2022.02.1/output/host
and the rootfs in buildroot-2022.02.1/output/images
. This will take a long time.
Enter the 'mprv' subdirectory. Run 'make'. Requires the toolchain to be built.
Enter the 'bare_metal_test' subdirectory. Run 'make'. Requires the toolchain to be built.
This tool is responsible for converting binary images (not ELF!) into texture files you can import into Unity, which can then be run by the shader version.
Enter the 'toimg' subdirectory. Run cargo build --release
. Use ./target/release/toimg <binary> 2048 2048
to create an image file. The second '2048' specifies the height, which may be smaller than '2048'. On the first run, the program will display the optimal size to give for running a second time. This is not necessary, but will produce smaller images.
This is a test payload written in rust. It demonstrates the ability to run rust code natively on the emulator (both C and shader version). Build by running make rust_payload.bin
in the top-level directory.
NOTE: This requires a custom rust toolchain, as the default one does not have a 'rv32ima' target, only 'rv32imac' which is not supported. A demonstrative patch for rustc can be found in rust-target-rv32ima.patch
.
To build the linux payload, run make linux_payload.bin
in the top level directory. This requires the toolchain and initramfs to be built.
Note that this uses a custom fork of the linux kernel that includes some custom rvc-specific patches. See the forked repo: https://github.com/pimaker/linux-rvc
Because.
MIT. See LICENSE for more.