Table of contents
  1. Introduction
  2. So what is RISC-V?
  3. QEMU and RISC-V toolchain setup
  4. Hello, RISC-V!
  5. What’s next?


Welcome to part one of RISC-V from scratch! Throughout RISC-V from scratch we will explore various low-level concepts (compilation and linking, primitive runtimes, assembly, and more), typically through the lens of RISC-V and its ecosystem. I am a web developer by trade, and as such I’m not exposed to these things on a daily basis. However, I think they are very interesting - hence this series! Join me on a very much unstructured journey into the depths of all things low-level.

In this first post, we’ll talk a little bit about what RISC-V is and why it’s important, set up a RISC-V toolchain, and finish up with building and running a simple C program on emulated RISC-V hardware.

So what is RISC-V?

RISC-V is an open-source, free-to-use ISA that began as a project at UC-Berkeley in 2010. The free-to-use aspect has been instrumental in its success and is quite a stark contrast to many other architectures. Take ARM for example - in order to create an ARM-compatible processor, you must pay an upfront fee of $1M - $10M as well as a 0.5% - 2% royalty fee per-chip. This free and open model makes RISC-V an attractive option to many groups of people - hardware startups who can’t foot the bill to create an ARM or other licensing-required processor, academic institutions, and (obviously) the open-source community.

RISC-V’s meteoric rise in popularity hasn’t gone unnoticed. ARM launched a now-taken down website that attempted (rather unsuccessfully) to highlight supposed benefits of ARM over RISC-V. RISC-V is backed by a ton of major companies, including Google, Nvidia, and Western Digital.

QEMU and RISC-V toolchain setup

We won’t be able to run any code on a RISC-V processor until we have an environment to do it in. Fortunately, we don’t need a physical RISC-V processor to do this - we’ll instead be using qemu. To install qemu, follow the instructions for your operating system here. I’m using MacOS, so for me this was as easy as:

# Also available via MacPorts - `sudo port install qemu`
brew install qemu

The instance of qemu we just installed comes with a few machines (specified via the qemu-system-riscv32 -machine option) ready to go, which is a nice convenience.

Next, let’s install a RISC-V compatible copy of OpenOCD and the RISC-V toolchain.

  1. Download prebuilt versions of the RISC-V OpenOCD and the RISC-V toolchain from here:
  2. Move and extract these files into a directory of your choosing. I elected to create one called ~/usys/riscv for this and other RISC-V toolchain / QEMU needs. Remember the directory you choose as we’ll be using it both in this post and in the next.
mkdir -p ~/usys/riscv
cd ~/Downloads
cp openocd-<date>-<platform>.tar.gz ~/usys/riscv
cp riscv64-unknown-elf-gcc-<date>-<platform>.tar.gz ~/usys/riscv
cd ~/usys/riscv
tar -xvf openocd-<date>-<platform>.tar.gz
tar -xvf riscv64-unknown-elf-gcc-<date>-<platform>.tar.gz
  1. Set the RISCV_OPENOCD_PATH and RISCV_PATH environment variables so other programs can find our toolchain. This may look different depending on your OS and shell - I had to add these exports to my ~/.zshenv file.
# I put these two exports directly in my ~/.zshenv file
# If you use a different shell or OS you may have to do something else.
export RISCV_OPENOCD_PATH="$HOME/usys/riscv/openocd-<date>-<version>"
export RISCV_PATH="$HOME/usys/riscv/riscv64-unknown-elf-gcc-<date>-<version>"
# Reload .zshenv with our new environment variables.  
# Restarting your shell will have a similar effect.
source ~/.zshenv
  1. We’ll also create a symbolic link into /usr/local/bin for this executable so that we can run it without specifying the full path to ~/usys/riscv/riscv64-unknown-elf-gcc-<date>-<version>/bin/riscv64-unknown-elf-gcc whenever we want to use it.
# Symbolically link our gcc executable into /usr/local/bin.  
# Repeat this process for any other executables you want to quickly access.
ln -s ~/usys/riscv/riscv64-unknown-elf-gcc-8.2.0-<date>-<version>/bin/riscv64-unknown-elf-gcc /usr/local/bin

Et voilà, we have a working RISC-V toolchain! All our executables, such as riscv64-unknown-elf-gcc, riscv64-unknown-elf-gdb, riscv64-unknown-elf-ld, etc, are located in ~/usys/riscv/riscv64-unknown-elf-gcc-<date>-<version>/bin/.

Hello, RISC-V!

Update to this section as of May 26, 2019:

Unfortunately, due to a bug introduced in RISC-V QEMU, running the freedom-e-sdk “hello world” program via QEMU no longer works. A patch has been introduced to address the issue, but for now you can feel free to skip this section. The freedom-e-sdk is not necessary for future posts in this series. I will keep a watch on this issue and update this post after it’s fixed.

For more information, see this comment:

Now that we have our toolchain setup, let’s run an example RISC-V program. I previously linked a SiFive repository called freedom-e-sdk, which provides various programs we can try out. Begin by recursively cloning this repository:

cd ~/wherever/you/want/to/clone/this
git clone --recursive
cd freedom-e-sdk

As is tradition, let’s start with the “Hello, world” program provided by freedom-e-sdk. We’ll use the Makefile they provide to compile this program in debug mode against the “sifive-hifive1” target:

make PROGRAM=hello TARGET=sifive-hifive1 CONFIGURATION=debug software

And finish by running it in QEMU:

qemu-system-riscv32 -nographic -machine sifive_e -kernel software/hello/debug/hello.elf
Hello, World!

What’s next?

This is a great start, but my goal with these blog posts is to truly shave the yak, and while we have confirmed that we have a working toolchain, there is a lot of magic hidden by the niceties of the freedom-e-sdk examples. Note that we didn’t have to set up any linker files or startup code - SiFive’s provided board-support linker scripts, various Makefiles, and the freedom-metal library take care of this for us.

In part two of this series we’ll break free from the freedom-e-sdk and make our own way. We’ll use dtc to examine the hardware layout of a qemu virtual machine, craft and examine a linker script, create a basic runtime to set up our stack, learn about some basic RISC-V assembly, and more.

The next post in this series has been posted, click here to check it out.