RvRun banner

RvRun

23 devlogs
58h 58m 27s

RvRun is an emulator for RISC-V 64bit Linux programs. It is meant to run under Linux and can emulate over 100 instructions. Tested under Gentoo, Archlinux, and Ubuntu 25.10

Demo Repository

Loading README...

foo-V

Shipped this project!

Hours: 21.0
Cookies: 🍪 369
Multiplier: 17.56 cookies/hr

I built an emulator for the RISC-V unprivileged architecture, it emulates over a hundred instructions, 4 different extensions (Rv64IMF + Zicsr), and 6 different Linux system calls.

RISC-V is an open architecture developed by UC Berkley and other universities, “open” here does not mean open source, as there is no source, but it means anyone is allowed to create hardware for it without paying any license fees. Development of the ISA is also public. These characteristics resonate a lot with what I personally thought of flavortown, so I figured it would be an interesting project to do and ship.

RvRun was written in accordance to the RISC-V specification 1, as a matter of fact, I’d say I spent more time reading it than writing code. There are almost 700 pages, after all. I also tried following IEE-754-2008 2 whenever I could, but I must admit that was almost impossible, since it’s not exactly a free document.

Due to the very nature of RvRun, it cannot be used outside of Linux. For that reason, I added several asciinema recordings of it working. All of the .cast files are in the asciinema/ directory in the repository, but you can also watch them on the web. The URL for each example program is in the README, under the “Asciinema recordings” section.

RvRun does not have any dependencies, I implemented everything from scratch in C. Example programs were written in assembly.

Implementing floating point instructions was quite hard (as you might guess by my devlogs). It took Berkley to write a library to handle it, so doing everything myself wasn’t exactly the smartest of the choices. Regardless, I am very proud of my somewhat successful implementation.

The demo includes a pre-compiled binary for x86-64 Linux, and six different RISC-V 64bit binaries that can be emulated by RvRun. Simply download the RvRun-v1.1.zip file and unzip it. If you’d prefer, compilation is also supported with both clang and gcc, instructions on how to do that are in the README.
You can find the pre-compiled example programs in the examples/ directory, the repository only contains the code for them, but the release includes the ELF files too. Simply run rvrun examples/<PROGRAM>.elf to test it, instructions on how each example can be used are found in examples/README.md
If you want to write your own assembly programs and emulate them under RISC-V, use the riscv64-elf-linux-gnu-* toolchain for compilation. Keep in mind you’ll need to add the -march=rv64imf (or your assembler’s equivalent) argument to your assembling command, because even if you only use the instructions present in these extensions, there are other extensions (e.g. the C one) that can be added transparently to the programmer, and that are not supported by RvRun. You will also need to keep your programs statically linked, and without a libc.

foo-V

on commit 2607fc6:

  • Updated/Fixed README for new release
  • Added example/sin.asm program to serve as an example during voting.
  • Added asciinema recordings of example programs
  • Fixed small bug with include/riscv.h:insn2rs2()
  • Made EBREAK instruction also show floating point registers

RvRun has now almost ten thousand lines of SLOC, and even more lines of code. It’s also almost in the hundredth commit. It can support over a hundred instructions, and only somewhat lacks in system call support, although the support is still enough to do both normal and file I/O, and could easily be expanded for things such as internet connections, allowing entire servers to be emulated.
I am very happy with the results of this project, testings in Gentoo, Arch, and Ubuntu 25.10 went amazing, no bugs were found. I wish I had used a softfloat library while implementing the F extension, but that’s something I can work on the future, as the minor uncompliances with IEE-754 that RvRun has are completely irrelevant for any program worth emulating.
Not just that, but I managed to support multiple compilers while using compiler-dependent features for optimizations (e.g. __builtin_expect()), and the build system satisfies me a lot.
The v1.1 version has also been released as an unstable release, mostly due to the minor uncompliances previously mentioned, as every other feature seems to be reasonably stable to me, I have updated the demo to reflect on it, and will now fill in the shipping application.

The attachment shows RvRun emulating examples/sin.asm, a program written in pure assembly that is able to calculate the sine of radians between 3.14 and -3.14 (numbers with bigger absolute values could also be calculated if it weren’t for the level of precision 32bit floats offer).

Attachment
0
foo-V

on commit 63b065a:

  • Finished implementing the F extension

This was a hell lot of work. I now realize I should have just used a softfloat library, and I’ll probably rewrite all of src/rv_f.c and include/rv_f.h (about 1400 lines) in the future. I imagine I did not follow the IEE-754 standard several times, but I don’t understand floating point math enough to see where. It is surprisingly difficult to handle all the different NaNs, subnormals, rounding modes, and other such things. Regardless, I’d like to ship my project one last time before flavortown ends, and the details I worry about are irrelevant to almost every application that RvRun can currently emulate.

I will now add example programs, update the README, and do a v1.1 unstable release, that will be my next (and unfortunately last) ship. I’m thinking of good example programs to add, and mathmetical functions like sine and cosine are currently my best candidates. I’d like to ship this project today, so it’s going to be a lot of work.

The output is RvRun running a program with several floating point instructions (showing it can fetch and decode all of them). Since dbg_log()’s were removed from insn_* instructions, there is no real output.

Attachment
0
foo-V

on commit 929deb5:

  • Added load/store floating-point instructions (FLW/FSW)
  • Added R-type format floating-point single-precision computational instructions (FADD/FSUB/FMUL/FDIV/FSQRT/FMIN/FMAX), aka, all computational instructions except for the fused ones.

Dealing with NaNs and other floating point details (many of which fell irrelevant) was really annoying. I am afraid I’m not complying to either the RISC-V specification or the IEE-754 one, but all the code seems fine. I’m planning on testing everything with the riscv test-suit, but this will probably be done after flavortown, as it would take too much time.

I now need to add fused multiply-add instructions, compare instructions, convert/move instructions, and classification instructions. They might seem a lot, but I am imagining that they will be actually easier to implement (and less boring too), except for the fused instructions, these ones will be hell.

Below is a screenshot of RvRun calculating divisions, multiplications, square roots, and other such things (The small imprecisions are expected). The dbg logs are not in the code, and were added just for this devlog

Attachment
0
foo-V

on commit 92433ef:

  • Added support for the Zicsr extension (required for F extension)
  • Added support for the FCSR register (part of the F extension)

While adding support for FCSR, I had to figure out a way to make so that floating point operations didn’t have to be made bit-by-bit, for that, I used the features from fenv.h. But there’s two problems with that: firstly, there’s no RMM (Round-to-Nearest, Ties-to-Max) support in fenv.h, nor in x86-64 CPUS at all. Secondly, x86-64 allows for some weird optimizations that treat subnormal values as zero (DAZ (Denormals-Are-Zero) and FTZ (Flush-To-Zero)). Basically, this means when the CPU finds a very very small number, it just pretends it’s zero, so it can run faster. It all sounds cool, until you realize that this violates IEE-754-2008, which I have to follow. I couldn’t find documentation that said if these were already turned on or off at the start of a program, so I just added code to turn it off (which was a pain to figure out). I never want to deal with floats again, but that’s too bad, because the next extension that will be added is the single-precision floating-point arithmetic one (aka “F”).

I will now start work on the “actual” instructions for the F extension. They’re quite a lot, and testing will be somewhat difficult, but I expect to have finished it by Monday.

As always, there’s simply not a lot of output to show. The Zicsr extension simply means emulated programs can now access CSR registers, and while technically challenging, it doesn’t change graphical output at all.

The screenshot shows RvRun running a program that writes and reads FCSR, firstly there’s the assembly code for the program (not currently in github), then, it’s output, and finally, RvRun’s logs

Attachment
0
foo-V

on commit ea0568d (not in commit order):

  • Added two more example programs, one computes numbers from the fibonacci sequence, and the other calculates the greatest common denominator of two numbers.
  • Removed dbg_log() calls from instructions. They degrade performance, and a few of them were actually wrong (specially when either rd, rs1, or rs2 were equal to eachother).
  • Changed the default log-level to 3 (Error)
  • Found and fixed a bug on src/rv_i.c:insn_bge(), where the emulated BGE instruction was testing for “Greater-than”, instead of “Greater-than-or-equal”
  • did some small refactoring on both RvRun’s code and the example programs code.

Will now start work on the F extension, before shipping it again. Might add more system calls too.
The Zicsr extension is a pre-req for the F one, so I’ll work on that before too.

Attachment
0
AVD

Tagged your project as well cooked!

🔥 AVD marked your project as well cooked! As a prize for your nicely cooked project, look out for a bonus prize in the mail :)

foo-V

on commit 39cd6a0:

  • Refactored code to make it compatible with clang, and not just gcc
  • Started using __builtin_expect() feature from both gcc and clang to improve branch prediction in multiple places in the code, particularly, error handling in instructions (src/rv_i.c and src/rv_m.c), and the fetch-decode-exec cycle (src/main.c). Noted a small improvement in performance after running multiple times with the time command, should be of bigger impact if emulating big enough programs.

I’ll be adding example programs for the M extensions, most likely a fibbonaci computation (multiplication was needed for displaying the number), and an algorithm to computate GCD (greates common denominator) using Euclidian’s algorithm. After that, work on the F extension should be started.

There really isn’t any new output to be shown, so I decided to show how the v1.0 release could not be compiled by clang (minor change in the Makefile so clang is actually used), and how the compilation is sucessful in the current commit

Attachment
0
foo-V

on commit c6f8e8f:

  • I added division support, finishing work on the M extension.
  • I tried to inspect which system calls were made by libc, but I found out that not only they are not the same in all architectures, but libc may have instructions from extensions that were not specified in the -march argument while compiling. This sets my new goal not at running basic (non-freestanding) C programs, but at supporting Rv64GC.

Therefore, I will probably only do some small refactoring/optimizations before getting work started on the F and D extensions. I’m curious on how I can implement the floating point environment instructions, and if simply affecting the emulator’s environment is enough (after all, that’s considerably faster than saving/fetching bit masks and doing floating point operations by hand). Regardless, I probably won’t be coding in the next 4 days for personal reasons, so I’ll have lots of time to think about a good design.

It is also worth mentioning that these instructions were not included in my first ship (which while currently pending, was done at the 15th devlog). The binary on the v1.0 release only supports the base ISA (Rv64I). I’ll be making a second ship later this month, perhaps in one or two weeks, that supports all of these instructions, and maybe a few more system calls.

Attachment
0
foo-V

on commit 493c809:

  • I have started work on the M extension, added all the multiplication instructions. To get the upper XLEN bits of multiplications, I wanted to use the __int128_t non-standard feature that both gcc and clang have, but after trying for some time, I decided to just use inline assembly with both the imulq and mulq x86-64 instructions.
  • The next step is to add the division operations to finish implementing the M extension, then I’ll either do some optimizations/refactoring, or add more system calls, before working on the F and D extensions. I intend to inspect the system calls made by libc’s initialization so I can eventually get basic C programs running (64bit C only requires Rv64IMD)

I had some problems with shipping, but it should hopefully be fixed by now too! The people on slack are really helpful.

Attachment
0
foo-V

Shipped this project!

Hours: 37.53
Cookies: 🍪 681
Multiplier: 18.15 cookies/hr

I built a RISC-V emulator for 64bit Linux binaries. It attempts to fully comply to chapters 1, 2, and 4 of the unprivileged specification[1], aka the base ISA. It also implements a few Linux system calls, these were derived from their respective manual pages.

RvRun emerged from a time where I was learning RISC-V assembly, perhaps to no ones surprise, I’d say “run, run, run!” quite often, staring at the menancing screen, waiting for it to reply: “Segmentation fault”, and crash my program.
After some time, I became quite comfortable with it, perhaps too comfortable, and decided I should read the specification, and make an emulator that complied to it. I shouldn’t. When I first opened the 696 pages document, it was like a bucket of cold water had hit me, some chapters have entire sub-sub-sub-sections! regardless, through-out 2 months, RvRun was made, with a few on’s and off’s, but it is, finally, ready!

Most of the time spent on this project was probably reading, not coding! I read and re-read many many sections of the specification multiple times, I looked through the Linux Kernel source code, read multiple man pages (specially elf(5)), read ABI specifications, etc, etc. It was tho, surprisingly, and I am very proud of having finished it.

RvRun uses no libraries other than libc (available on all systems), everything was done from scratch.

[1] https://docs.riscv.org/reference/isa/_attachments/riscv-unprivileged.pdf

foo-V

on commit 0616547:

  • I did more touch-ups, added a –version option, re-wrote the README.md, added asciinema recordings, and tested the code under my system (gentoo), an archlinux VM, and a Ubuntu 25.10 VM. Couldn’t find any bugs, and I hope the people testing don’t find any either
  • I also added a pixel art! The resolution is pretty low, but I haven’t found a way to fix it. It took easily over 3 hours to do, as one may realize, I’m not very good when it comes to art.

The project has now had it’s first release (on github), and I’m going to ship it here, on flavortown, too!

Attachment
0
foo-V

on commit 5bbcc22:

  • I wrote three different example programs (echo, cat, and xxd), all in pure assembly. During testing, I found quite a lot of bugs and ways to improve RvRun, by the time I was writing xxd, no problems appeared.

I am now taking a look at packaging, I added install and uninstall targets to my Makefile, and will rewrite the README to better reflect where the project now is. I’m going to put a statically compiled binary on the github releases page, to make sure no problems can happen, it is about 15 times bigger than the dynamically compiled one, but still under 1Mb (there are no dependencies other than libc, after all), so no problems.

I also realized that I need a banner, so I’m thinking of making a pixelart for a logo.

Attachment
0
foo-V

on commit c1bf362:

  • Added argv and envp support for processes. Not sure how useful envp is, most programs don’t use it for compatability reasons, and it seems that it’s empty on my system (the process inherits it from RvRun). Regardless, the implementation is almost identical to the one of argv so I just went ahead and added it. Having argv support is extremely useful tho, it will be used on most, if not all of the example programs I write. (edit: Fixed the loading on fcad00d, was previously loading on argument register, but after inspecting libc’s startup code, I realized they’re supposed to be loaded right after SP. I think envp is still wrong but it wouldn’t usable anyways)
    I’m thinking of touching up the logging system, add some more info_log statements, maybe flush stdout/stderr on panics, etc.

I’m also thinking of what programs I should write as examples, they’ll probably be recreations of classical UNIX utilities, like cat(1), ls(1), echo(1), etc.

Attachment
0
foo-V

I had some problems with hacktime’s time tracking, but am finally back!
on commit fbcaefb:

  • I Finished implementing the I extension (aka the base specification), by adding Load, Store, Memory Ordering (FENCE), and Environment Breakpoint (EBREAK) instructions.
  • Made logs better (ABI names are now used for registers)
    I will now add argc/argv/envp support and write some example programs, before doing some final touch-ups required for a release. The projcet should be shipped by tomorrow!
Attachment
0
foo-V

on commit 4f3ebba:
Added a README, should have done this some time ago, but only now the project is starting to get usable. I’ll finish implementing the base ISA, perhaps add some system calls, and then release the version one.

Attachment
0
foo-V

on commit 1883f67:
RvRun can now interpret arguments, this might have been a bit late, but there are actual things to be set as arguments now. The currently added ones are:

  • –stdin=FILE, –stdout=FILE, and –stderr=FILE, these redirect the I/O of the emulated process,
    stdout and stderr always truncate FILE, I’ll add an option to append later
  • –log-file=FILE, and –log-level=LEVEL, these allow you to manipulate the logging of RvRun,
    FILE is where they’re written to (deafults to stderr), and LEVEL is which messages (aka DEBUG,
    WARN, etc) get written
    * –help, just shows you a help message, the standard stuff
    I’m using getopt_long() for these, with the GNU extension. I thought of enabling the POSIXLY_COMPATIBLE feature test macro for better compatibility, but RvRun is only meant to run on Linux anyways, so it just wasn’t worth it.
    The next thing I’ll do today is write a README.md, and then I’ll work on more instructions and system calls tomorrow.
Attachment
0
foo-V

on commit74aaa7b :
I Added all the W-type instructions, some other ones I had forgotten, and added support for file descriptor related system calls, like openat(), close(), read(), and write(). There is no open() system call on RISC-V, it is only a libc wrapper around open() (no __NR_open macro in include/uapi/asm-generic/unitsd.h under the linux kernel source code).
I am working on passing options to the emulator, writing a README, and making some examples, I’ll probably do that over the weekend. After that, there shouldn’t be that much work other than simple repetitive stuff (aka adding more instructions and more system calls)
I forgot to do the devlog earlier, so about one hour of the time being logged was actually spent adding flag support, haha.

Attachment
0
foo-V

on commit 1eb5f81:
I implemented ECALL, and can now emulate both exit(2) and exit_group(2) (they are both the same, as I don’t have thread support yet), with now a proper way to decide when a process exits and when it doesn’t, I implemented an actual Von Neumann cycle too.
I am going to iterate back between finishing the implementation of all Rv64I instructions, and supporting more system calls (most likely, file descriptor related ones). My objective is to support all the instructions from Rv64IMFD, and a handful of system calls, in about 2 weeks and a half.

Attachment
0
foo-V

on commit b264055:
I am finally back! I did some refactoring on src/proc.c yesterday, and implemented the I-type from Rv32I instructions today, I am ignoring the W variations that Rv64I adds for simplicity, I’d like to cover the most important instructions first.
I will probably start basic work on ecall next, so that I can do a real von neumann execution cycle by having an exit system call, after that, I’ll go on with the U-type, B-type, and J-type instructions, before going back to the things I ignored, and continuing with all the system calls.
I will probably change the license to GPLv2, too, and perhaps make some artwork.

Attachment
0
foo-V

on commit 6423204:
I can now execute all R instructions from Rv64I, except for the “*W” ones. I am downloading and building https://github.com/riscv/riscv-opcodes.git at compile time, setting this in the Makefile was a bit of a pain but I eventually got it. I will now work on some logging/debugging functions, so that I can test the instructions I add properly, and then work on the Register-Immediate instructions.
I’m not sure if I’m going to “speedrun” a hello-world, or try to support most instructions from the base ISA first, but they should both lead to the same outcome.
All of this is being done while I read the specification, and while it can get quite tedious, I feel way better in RISC-V now, I might even write my next project in raw assembly.

Attachment
0
foo-V

on commit 9611fd6:
I can now load and store values into memory, it respects the permissions set when loading the ELF binary, and fail gracefully both when trying to access out of bound memory or when doing invalid operations (aka writing to a read-only address). Unaligned memory access is also supported, as RISC-V allows that, even if the host machine does not support it. I decided to use C11 to allow for the _Generic macro, and enabled the _GNU_SOURCE macro to use some extensions (they weren’t needed, but I’ll be using other ones further down the road anyways).
I will now implement instruction fetching, and later the decoding

Attachment
0
foo-V

on commit 0e626b8:
I ended up spending the whole day thinking about how to support both 32bit and 64bit applications, I went over C11’s _Generic, void pointers, wider types + type casts, etc, but ended up realizing the best choice was to only support one. Some time was thrown away, but at the very least, I think I made a really good and important design decision, one I could not go back later.

Attachment
0
foo-V

on commit d7c2c71:
Finished implementing the loading of ELF segments, it felt a bit tricky at first but it turns out the format is beautifully designed.
I am using a linked list to represent the segments, it saves the RISC-V addresses it maps, a pointer to the memory that represents it, and the flags (Read/Write/Exec). I might change this design by using mmap, but I’m not sure how I would mark the address space as executable or not (since it should be executable by the emulator, not by the host OS itself). But it seems to work now, therefore I’ll leave this to a future refactor when I have a better idea of how the project as a whole should be designed.

Attachment
0
foo-V

Started working on my project, created the directory structure, added a license, and a Makefile. Will add README further down the road.

Had some problems making GNU Make automatically deduce prerequisites, but it eventually worked.

Attachment
0