Baremetal Multicore in Spike

The purpose of this tutorial is to show you how to run a baremetal program in Spike, which is the RISC-V simulator. Baremetal means that it does not rely on a kernel like riscv-pk. This tutorial assumes you are working in a Linux environment.

Getting RISC-V Tools

Please follow the steps described on this github: https://github.com/riscv/riscv-tools

Make sure to always set the RISCV environment variable and ammend your PATH variable if you would like to be able to access all RISC-V tools. For me I added a lines to my ~/.bashrc file so that everything is set up when I open a terminal window:

export RISCV="/path/to/install/riscv/toolchain"
export PATH=$RISCV/bin:$PATH

Compiling Some Code

Now to get started on coding, download this tarball. A short overview of what is in here:

Once you have extracted this code you can run make inside the hello folder. This will create a couple of files. The file a.out is the file that we will run in a minute.

Modifying Spike

In order to have output in Spike without having to deal with the RISC-V front-end server. I decided to add a custom CSR (control and status register) which outputs a character corresponding to the value that is written to the register. So when you write to the CSR, using the csrrw instruction, it will print the character corresponding to the value it contains (see the output_char function in hello.c as an example).

To achieve this I made a couple of changes to the spike source code, which you can find in this diff file. It adds the definition of the CSR to encoding.h and how to handle writes in processor.cc.

You need to apply these changes to the source code in the riscv-tools that you built earlier. Go to the source code directory of your riscv-tools checkout. Go to the sub-folder named riscv-isa-sim and make your changes. Then go into the build directory and type make and after that make install.

Running Your Code

Going back to the directory where you build the bare metal hello world program. You can now execute:

spike -p2 a.out

This should print two lines. One saying "Core 0" and the other saying "Core 1". You can change the number after the -p to denote how many cores you would like to run with.

Debugging Your Code

Now you will probably want to start modifying the C code to do what you want. So it is useful to know how to debug your code.

When running your code you can add the -d flag to Spike, which enters a debug session. Every time you press enter it will execute an instruction and print which instruction that is. For more options on debugging you can see the README.md file on the riscv-isa-sim repository.

Besides the Spike debugger, it is also useful to see the instructions of your compiled code. You can use make dump to get the dump of your program. This way you can see the program counter values that are produced by Spike and compare them with those in your program.

Interleaving Instructions

As you may have noticed, the print statements look very clean for a multi-threaded application. All of core 0's characters are print before core 1 start printing. This is not really what you expect if two threads are running in parallel. What I would expect is that the characters are interleaved.

The reason for this is that Spike by default runs 5000 instructions on core 0 and then runs 5000 instructions on core 1. To make it so that the instructions of the two cores are interleaved, we need to make a small adjustment to Spike's source-code. In riscv-tools/riscv-isa-sim/riscv/sim.h look for the line that says static const size_t INTERLEAVE = 5000; . Change 5000 to 1 and then go to the risv-tools/riscv-isa-sim/build/ directory. In the build directory, run the make command and then the make install command.

If you rerun our hello world application again, the letters will be interleaved as we expected earlier.