REQUIREMENTS:

-A version of GNU/Linux installed upon your host machine as the default Operating System, or through a Virtual Machine. I am using Debian, and the instructions for this lesson will cover this distribution specifically.

-Git installed upon your GNU/Linux instance (“sudo apt-get install git”).

COMPLETION TIME:

~ 1 – 2 hours

Good morning, students! 🙂

Today we are going to cover a brief introduction of the RISC-V instruction set architecture, and then dive into the process of booting a functioning Linux kernel upon our machine. By doing this, we will learn about how and why the RISC-V ISA was designed the way that it is, and also how we can get software to properly interface with it. (A special thanks to Quan Nguyen for authoring the original “riscv-tools” tutorial that the first and third sections are based upon!)

Building the RISC-V Tools

If you have a terminal window on your host machine ready to go (or on your VM), please create a new directory for RISC-V (I’ve called mine “risc_v”).

mkdir risc_v

Then, go ahead an change directories so that we’re inside of it.

cd risc_v

We’re going to begin our deep-dive into RISC-V by downloading tools for compiling software and simulating it on the ISA. After we do this, I’ll provide a high-level explanation about the different compiler targets and which one we’re going to use. For now, download the “riscv-tools” using the following command:

git clone https://github.com/riscv/riscv-tools.git

If you’ve got SSH enabled on your machine, you can use this command instead:

git clone git@github.com:riscv/riscv-tools.git

This should create a new “riscv-tools” directory inside of your “risc_v” root directory. Once the repository has finished downloading, we now need to update the submodules that exist inside of it. First, let’s change directories, so that we can update it!

cd riscv-tools

Then, the following command should take care of everything. This may take a while… so be patient! 🙂

git submodule update --init --recursive

Whew! 😀

Now that you have all of the required files in order to build the tools, we now need to make sure we have the right packages installed upon our GNU/Linux installation. This one-liner will do the trick for Debian-based distributions.

sudo apt-get install autoconf automake autotools-dev curl libmpc-dev libmpfr-dev libgmp-dev libusb-1.0-0-dev gawk build-essential bison flex texinfo gperf libtool patchutils bc zlib1g-dev device-tree-compiler pkg-config

There are also one-liners for Fedora-based distributions, which you can substitute by copying and pasting from the repository.

Once you’ve managed to get all of the needed packages installed, we need to create an environment variable for our installation. This will make it easier for us to utilize these tools in the future regardless of where we’re located inside of our terminal session. For now, create a “build” folder with the following command:

mkdir build

Now, let’s go inside of it:

cd build

Let’s also verify that we’re inside of the directory:

pwd

The terminal should echo the current directory we’re inside of as the command’s response. If everything looks okay, we’re ready to create our environment variable!

export RISCV=$(pwd)

This should set the “RISCV” environment variable to the path of our build folder. But… just to be sure… let’s verify that this is correct!

echo $RISCV

The terminal window should now print the path of the “build” folder in response. If not, repeat the last couple of steps to double-check if something’s amiss. 😉

Now it’s time to build our tools! Let’s get back to the root of our “riscv-tools” directory by ascending a level in our directory hierarchy.

cd ..

This should take us one directory up, back into the root directory of our “riscv-tools” folder.

At this point, I’d like to talk about the compiler targets that are available for our tools, which will also lead us into a proper introduction regarding the RISC-V ISA. By default, installing the tools without any configuration will provide us with a riscv64-unknown-elf toolchain for any software that we wish to compile. That means we’re targeting a 64-bit architecture, with no operating system, and outputting an ELF file. We can configure the tools to build us a 32-bit toolchain instead, and have been provided with a shell script to automate this process for us: “build-rv32ima.sh”. For our purposes, though, we’re going to stick with a 64-bit target, which we invoke with the following, simple command. 😉

./build.sh

This will take quite a long time… so, while everything is being built, why don’t we learn more about the RISC-V ISA? 😀

The RISC-V Instruction Set Architecture

If any readers have prior experience with other ISAs (x86-32, x86-64, MIPS, ARM, et.al.) you’ll understand that some are simpler than others, and there’s a reason for that. The personal computing industry came about because of improvements in the design of the microprocessor; improvements that made them easier to manufacture, and also to provide a more pleasant computing experience. No longer did computers need to be tall, monolithic entities (see PDP-11), or even the size of an entire room (as they were during their infancy!) They could instead be provided inside of a form factor that was both easy to interact with, and easy to handle.

However… by making the most basic functionality of a computer less cumbersome… there begin to arise opportunities to extend that functionality beyond its current limits. If those extensions then become normalized, and businesses, regardless of size, start to demand these features as standard fare… well, chips now need to be capable of supporting a myriad of different instructions, even legacy instructions that may or may not be utilized as frequently as they once were. What I have just described is the gist of the story behind the x86 instruction set architecture. It is this compounding of complexity that also gave rise to what we call Moore’s Law, which is a trend that engineers and computer scientists used to predict what computing technology would look like years down the road, and make design decisions accordingly. As of now, that law has currently ended, and with good cause. 🙂

Since we no longer have the ability to scale microprocessor manufacturing with the same radical velocity that we had in times past, engineers and computer scientists now need a more efficient way of developing computers. Because the complexity of the ISA also dictates the complexity of the hardware underneath it, the need has arisen for a more concise computing model from top to bottom. Enter the RISC-V instruction set architecture.

Conceived and developed at UC Berkeley, the RISC-V ISA is the brainchild of industry veterans and champions of its RISC ancestry. The base Integer (I) version of the instruction set, RV32I, and RV64I, contain instructions for load-store operations, basic arithmetic, and other operations you’d find commonplace in previous ISAs. Where RISC-V differs, however, is in its modularity! Whereas previous ISAs have had to consolidate every needed feature inside of the set, the RISC-V ISA allows the user to pick and choose which instructions they need on a case-by-case basis. As a result, hardware designers can design chips without having to accommodate for instructions that will never be used!

The modularity of the RISC-V ISA has provided an additional advantage to the architects of its design by giving them the time needed to think over the efficiency of future extensions before performing a freeze. Once a particular extension is frozen, hardware developers know that it won’t change, and therefore, not compromise a design utilizing it.

Beyond the base instruction set are the Multiply/Divide (M), Atomic (A), Single-Precision Floating-Point (F), Double-Precision Floating-Point (D), Compressed Instructions (C), and others that are also being actively developed. The first four mentioned, plus the base version of the RISC-V ISA (IMAFD) are also referred to as “G”, for “General”. Thus, if you ever see some RTL floating around the web that claims to have “RV64GC” support, that means it supports the Base Integer, Multiply/Divide, Atomic, Single and Double-Precision Floating-Point extensions, and Compressed instructions as well. By tapping into the flexibility of this ISA, we can compile programs that only utilize certain portions of the instruction set, reducing their size, and promoting slimmer hardware design underneath.

Compiling Your First RISC-V Program

When our tools have finally finished building, go ahead and change directories to verify that the process completed.

cd build

Go ahead and perform a check of the directory’s contents with:

ls

Your terminal window should output a list of all files and directories located within your current location. Among them should be the “riscv64-unknown-elf” directory (our toolchain), as well as the “bin” directory which contains our compiled binaries. For this next step, we’re going to want to append our PATH environment variable with our compiled RISC-V toolchain.

export PATH=$PATH:$RISCV/bin

The good news is that, even if we make a mistake here, our system’s PATH variable will reset upon rebooting. Perform an:

echo $PATH

To verify that our RISC-V tools have now been added to our system’s PATH variable.

Now for the fun stuff! 🙂

Next, we’re going to create a C program file in order to test our toolchain. Enter the following:

echo -e '#include

int main(void) { printf("Hello world!\

"); return 0; }' > hello.c

This redirected echo statement should create a new “hello.c” file inside of your build directory. If you want, you can create a “test” folder here, if you’d like to keep things organized. I’ve omitted that optional step because you should be able to try that out by yourself now. 🙂

Now it’s time to compile our little program! We can do so by simply invoking:

riscv64-unknown-elf-gcc -o hello hello.c

Notice that we’re able to call the “riscv64-unknown-elf-gcc” without being in the same directory that it’s located! That’s because our PATH environment variable is now aware of our toolchain’s current location. This is one of the types of automation provided to us by our Linux environment.

We should now have a binary in our directory named “hello”. Go ahead and try to run it on your machine with:

./hello

If you’re using a computer with a different ISA and operating system than GNU/Linux, you’re probably going to get a:

./hello: cannot execute binary file: Exec format error

This is to be expected. Our program has been compiled for the riscv64 platform, and thus, will not run on our system unless it is compatible. “But wait…!” you say, “How do we run it?!”

And there, my friend, is the beauty of simulation!

Our RISC-V tools also include “spike”, an ISA simulator for RISC-V that will allow us to run programs in user space as if it were running on RISC-V hardware! Neat, huh? 😀

Let’s invoke it with the following command:

spike pk hello

If everything has gone according to plan, we should see “Hello world!” appear on the command line. If not, double-back and check to make sure you’ve followed the previous steps correctly.

What the Heck’s a “Proxy Kernel” Anyway?

Funny you should ask. 😛

If you notice inside of our “hello.c” file, we use the “printf()” function in order to access the standard output of our operating system. The thing is, because we’re running a user space program upon a simulator that’s meant to act like bare hardware, we do not have the ability to make system calls! Oh, no! What do we do?

That, my friend, is where the “pk” argument comes into play. The letters are short for “proxy kernel”. Our proxy kernel acts as an intermediary so that system calls made by the C standard library are intercepted and accounted for. As a result, you have a user space program that is able to “talk to” hardware the way it normally would. Our proxy kernel, however, has its limits, which we will get into later on.

So, now that we’ve covered the basics of downloading, building, and testing a toolchain, is there anything else we can do to demonstrate the power of the RISC-V ISA? Why… yes! Yes, there is! 🙂

Booting Our First Kernel

First off, go ahead and pat yourself on the back if you’ve made it this far. Congratulations! 🙂

Now… it’s time to download more stuff. 😦

Change directories (“cd ..”) back to the root of your “risc_v” directory. We’re now going to download a Software Development Kit for RISC-V development boards published by SiFive (which is actually composed of members who developed the original ISA!) This will require us to clone yet another repository:

git clone https://github.com/sifive/freedom-u-sdk.git

And for SSH users:

git clone git@github.com:sifive/freedom-u-sdk.git

As soon as that completes, “cd” into the “freedom-u-sdk” directory that’s been created. We need to download and update our submodules… again. >_>

git submodule update --init --recursive

While this process is doing its thing, go ahead and pour yourself a glass of $BEVERAGE. My preference is soda. (That’s some sys-admin humor for those of you who don’t get it! *wink*)

Depending upon the speed of your machine (or the number of resources you’ve allocated to your VM) this may take a while. Thankfully, building and booting a Linux kernel upon our simulator is a fairly painless process. But…! Before we move any further, we need to change something we set earlier…

Remember when I said you needed to set a “RISCV” environment variable? Well… if you keep that around while you attempt to build the SDK, you’ll run into some catastrophic build errors! Yikes! Let’s avoid that by entering the following command:

unset RISCV

If we echo our “$RISCV” environment variable now, we should get a blank line returned from our terminal. Good! That means it worked.

Now it’s time to build the SDK and get Linux running! Start out the build process by invoking the following:

make all -j$(nproc)

The last part of that statement, “-j$(nproc)”, sets the number of threads for our build process to the maximum available for our host system (or VM, if you’re running one). Once again, drink some $BEVERAGE while you’re waiting, or eat some $FOOD if you’re hungry. 😉

*time passes*

Once the build process has completed, we can invoke a Linux kernel session by entering:

make sim

Into the terminal. The SiFive logo should immediately display as some ASCII text, as well as a series of print statements demonstrating that our kernel is in the process of booting. When the boot has finished, you should be presented with a prompt that says:

Welcome to Buildroot buildroot login:

Type “root” for the login prompt, and “sifive” for the password prompt. If everything checks out, the terminal window should present you with a blank command prompt. Quickly type “pwd” to confirm your location within the file system.

# pwd pwd /root #

You’ll notice that the simulator prints the command you entered as well as the appropriate response. As indicated, we are currently residing within the “/root” directory of our kernel. If we “cd ..” up one directory, we should now exist within the “/”, or root directory, of our file system. We can confirm our location by typing in the “ls” command:

# ls ls bin etc lib linuxrc mnt proc run sys usr dev init lib64 media opt root sbin tmp var #

As you can see, we get all of the available directories as output. From here on, you can navigate the file system of our kernel, play around with some commands, or you can exit by typing “Ctrl+c” twice. If you choose to exit, you should be returned back to your original terminal prompt and in the directory of the SDK.

Whew! 😀

Closing Remarks

We’ve covered a lot of ground today! You’ve learned how to install the RISC-V toolchain, compile software with it, and run a program successfully upon an ISA simulator. You’ve also learned how to boot Linux! Awesome! These new tools are the foundation we will build on top of as we start to consider writing our own operating system for the RISC-V platform. For now, though, I think I’ll settle for another glass of $BEVERAGE. 😉

Cheers!

Homework:

Read the entire “Introduction” section of “Three Easy Pieces“. Begin to consider what we’ve talked about with the RISC-V ISA and where it fits in. What are some challenges you foresee if we were to start developing an operating system right now?