It was while watching Bryan Cantrill’s presentation “The Soul of a New Machine”1 that my interest for RISC-V was piqued. I vaguely remember looking at RISC-V a while ago but at the time hardware wasn’t readily available unless you had an FPGA to run it on. Nowadays there’s ample choice of both 32-bit and 64-bit hardware to buy.
No RISC, no fun⌗
First off, a very brief introduction to RISC-V and the different extensions which are available. This’ll help navigate the swaths of cores out there.
RISC-V is a 32/64/128-bit instruction set architecture (ISA) which is completely open and free to use and implement by anyone, originally developed in Berkeley. As the name implies it’s a reduced instruction set computer (RISC) as opposed to the complex instruction set computer (CISC) architectures. There are two “frozen” standards (i.e. ratified and final): the unprivileged/userland ISA and the privileged ISA. On top of this anyone is free to develop their own extensions provided these work without conflict with the base ISA. It’s this feature of RISC-V that makes it an attractive ISA to get familiar with because of this modularity you can build small 32-bit embedded systems (RV32E) or a general purpose server computer (RV64IMAFD, or RV64G in short). Knowledge of this ISA is going to become very helpful I reckon. This could also allow for developing extensions which provide mitigations against certain types of attacks (such as ROP) right in the ISA itself. I do have concerns however whether these extensions will create a fragmented ecosystem.
Currently the most common extensions are:
|M||Integer multiplication and division|
Here’s the complete list of extensions along with the version and status. Also I’d recommend reading this general description of RISC-V: Instruction Sets Want to be Free. As well as this presentation from Berkeley Architecture Research, with a focus on QEMU support for RISC-V (and some mentions of Spike, see below).
Nitty gritty basics⌗
I merely wanted to get some hands on experience and learn the RISC-V assembly and I didn’t want to spend any time/money (yet) on buying a hardware board or on setting up a complete SDK or RTOS build environment (although Zephyr does seem like an interesting project to look into at some point).
Turns out, there’s actually a really neat simulator developed by the RISC-V foundation called Spike. This simulator defaults to RV64IMAFDC (or RV64GC) and it works really well with the RISC-V proxy kernel (pk) to run your code (provided you’re not doing any fancy IO operations).
pk is really a “lightweight execution environment for statically linked ELF binaries”, which is just perfect for my use case.
If you’re on macOS you can install the required toolchain using homebrew:
brew tap riscv/riscv brew install riscv-tools
On OpenBSD you can install the toolchain from packages:
spike and pk should be build manually, though the upstream repositories provide the build instructions for OpenBSD too!
With a simple
hello.c invoke the cross-compiler (I use
-g to keep all interesting bits for inspection and debugging):
riscv64-unknown-elf-gcc -g hello.c -o hello
and run the binary on the proxy kernel in the Spike simulator:
nazca ~ % file hello hello: ELF 64-bit LSB executable, UCB RISC-V, version 1 (SYSV), statically linked, with debug_info, not stripped nazca ~ % spike /usr/local/Cellar/riscv-pk/master/bin/pk hello bbl loader Hello RISC-V! nazca ~ %
Spike can do so much more than simply running the proxy kernel, please checkout the upstream documentation for additional features such as testing new instructions and interactive debugging.
.global _start # Provide program starting address to linker # Setup the parameters to print hello world # and then call Linux to do it. _start: addi a0, x0, 1 # 1 = StdOut la a1, helloworld # load address of helloworld addi a2, x0, 13 # length of our string addi a7, x0, 64 # linux write system call ecall # Call linux to output the string # Setup the parameters to exit the program # and then call Linux to do it. addi a0, x0, 0 # Use 0 return code addi a7, x0, 93 # Service command code 93 terminates ecall # Call linux to terminate the program .data helloworld: .ascii "Hello World!\n"
Compile and run it with spike:
nazca ~ % riscv64-unknown-elf-as -march=rv64imac -o hello.o hello.s nazca ~ % riscv64-unknown-elf-ld -o hello hello.o nazca ~ % spike /usr/local/Cellar/riscv-pk/master/bin/pk hello bbl loader Hello World!
With a test environment set up and the reference card in hand it’s time to dig in!