Skip to main content Skip to local navigation

Getting Started with RISC-V: Spike Simulator

Screen recording of running the Spike simulator on sample files from the RISCV-ASM-SPIKE GitHub Project by Ilya Sotnikov.
Running the Spike simulator on sample files from the RISCV-ASM-SPIKE GitHub Project by Ilya Sotnikov.

The Spike simulator is a command-line application for RISC-V development. It allows for both 32-bit and 64-bit simulations and supports multiple variants of RISC-V types. It's an alternative to using the Segger emSim simulator, which has both a graphical mode and a command line mode and. It's also more up-to-date and widely-used than the RVS simulator that we have been using at York University for a few years. There are plenty of other simulators and emulators, too, for those looking for options.

Spike is typically used with a special interpreter called pk. It can also be used stand-alone. I'm particularly interested in using it for automating of exercises in class from within Virtual Programming Lab. The riscv-asm-spike GitHub repository by Ilya Sotnikov is relevant to me for this as it serves as a model for "bare-metal" (operating system free) RISC-V compiling and simulation using both non-interpreted Spike and the common bare-metal GCC cross-compiler for RISC-V.

This project has a few really nice things going for it:

  1. It works with common-used tools : Spike and riscv64-unknown-elf-gcc
  2. It targets "bare-metal" RISC-V development
  3. It provides examples with both C and Assembler
  4. It works

This last point can't be overstated. There are a lot of elements to compiling and simulating that require deep knowledge of the underlying tools and targets -- none of this is obvious and having a working model is really helpful to get off the ground.

Compiling executable programs that will actually simulate is complex as it involves a lot of variables, including selecting the right architecture ("-march") and right application binary interface (-mabi). This is touched on in a SiFive blog post. You then also have to figure out where in memory the executable will sit, thus involving a cousin of the compiler, the linker. None of this is straight-forward and are huge roadblocks for anyone who just wants to get started with compiling a few RISC-V programs just to get their feet wet. (By the way, if you're just wanting to tinker, you should just try Compiler Explorer first and set it to RISC-V.)

Getting started: simplest case

If you've installed the Spike and riscv64-unknown-elf-gcc tools on your computer and they're in the standard directories and the paths to the executables are defined then you should be able to download the riscv-asm-spike GitHub repository by Ilya Sotnikov, unpack it and type

make run

from the terminal to get it to work. You'll get something that looks like the screen capture at the top of this blog page.

What the ELF main file looks like to a text editor.  Note the ELF string.
What the ELF main file looks like to a text editor. Note the ELF string.

Getting started: simulate an assembler-only program

At minimum you will need to access a working set of the Spike and riscv64-unknown-elf-gcc applications and that you have a copy of Sotnikov's repository on your computer. I'll assume that that's the case for you.

Next, navigate to the minimal directory. Then type

make clean

followed by

reading the elf file with readelf -f main.
reading the elf file with readelf -f main.
make all

You now have an "ELF" binary file called main. This is an ELF binary file, internally, it looks like the black text in the figure. The text file is impossible for a human to understand directly. Instead, we can interpret it using the readelf and objdump utilities. You can use readelf with the -h flag to get basic information about it:

riscv64-elf-readelf -d main

as shown in the image here.

Sample of the contents from the object dump for the main ELF file.
Sample of the contents from the object dump for the main ELF file.

What's nice about this is that it provides contextual information, including telling you if this has been compiled as either 32-bit or 64-bit. In this case, it's 64-bit. Also, we can see the "entry point" for the code. It's at address 0x80002000.

If you want to see more details you can use the -a flag:

riscv64-elf-readelf -a main 

Alternatively, you can use the object dump utility to disassemble the executable. This will recreate the assembler file directly from executable code. It will include important information that wasn't in the original assembler program, including memory locations. Here is the command to use:

riscv64-elf-objdump -D main

And you can see, there are multiple memory sections defined, and each defined memory location has both an address (left column) and contents (middle column), both formatted in hexadecimal. On the right hand side you can see the contexts has been interpreted into Assembler.

From a student perspective, this is an invaluable, albeit initially confusing, set of information. Yes, it's pretty intimidating, but it's key to understanding what is going on inside the processor.

You can now run the executable through the Spike simulator. In my case I haven't defined the location of the Spike executable in my terminal path, so I need to specify its path explicitly:

/Users/jamessmith/riscv_spike/install/install/bin/spike --isa=rv64imafdc main

And the output? Well, it's nothing. Why? Because, really, this assembler program doesn't do much except set things up. It defines the tohost and fromhost sections, as well as the start symbol. It really doesn't do much else.

Note that I've specified the rv64imafdc architecture (ISA) flag. If you don't you'll get an error message because Spike defaults to wanting 32-bit RISC-V ELF files. The error will look like this:

Error: cannot execute 64-bit program on RV32 hart

Next up is the more interesting code found in Example.c.

You want to do the following:

  1. Run the make utility on all source files
  2. Run the resulting executable in the target folder through Spike

So, go to top folder in riscv-asm-spike-main where Makefile is found. Then type

make clean
make all
cd target 

And this will will look like this:

Make clean, make all and then change directory to the target folder where the executable main ELF file will be.

Next, run the simulator on the main executable file: [path to spike]/spike --isa=rv64imafdc main

In my case the last command is:

/Users/jamessmith/riscv_spike/install/install/bin/spike --isa=rv64imafdc main

and the result should be this:

The output from the simulator.
The output from the simulator.

While it looks simple, what's going on under the hood is really, really important. The Makefile actually compiles multiple C and Assembler files and combines them into a single main ELF executable. It appears to define

  1. the main function within the main.s file, then
  2. executes an assembler jump, jal c_function
  3. to theC function, c_function, defined in Example.c

The Example.c file defines a pair of Assembler functions, print_hex_ln() and print_str_ln(). These two functions are define within the lib.s assembler file.

What's Next: Including standard libraries, a main file in C and Unity

The next thing I believe that I will need to investigate and blog about will be:

  1. Can other typical C libraries be included
  2. Can this be compiled with C17 or C23?
  3. Can I anchor a similar project in a C main file rather than an Assembler main file, and
  4. Can I link in the Unity unit tester.

The Example.c file has stdint.h included. I'd like to see if I can include other libraries like stdlib.h. Then, given that the Makefile uses C11, I'd like to see if C17 or C23 can be used instead. While it's good to have a working Assembler main function, I think that maintaining future projects for RISCV will be more straight-forward if there is a C version used instead. And, finally, good software projects should have testing utilities baked in. I like the Unity unit test framework for C for this and have had success with it in the past.

After that: Leveraging this repository for new projects

I'm planning to use the configurations in this repository as examples for

  1. Stand-alone assembler programs
  2. Stand-alone C programs
  3. Assembler programs that call C functions
  4. C programs that call Assembler functions
  5. C programs that call separate C functions

I'm especially interested in the last two points to create interactive exercises for my students within Virtual Programming Lab. I'll leverage the Unity unit test framework for C in a similar manner to what I posted here and here.


Updates

August 4, 2024: simulator list added: https://www.riscfive.com/risc-v-simulators/


a pen

James Andrew Smith is a Professional Engineer and Associate Professor in the Electrical Engineering and Computer Science Department of York University's Lassonde School, with degrees in Electrical and Mechanical Engineering from the University of Alberta and McGill University.  Previously a program director in biomedical engineering, his research background spans robotics, locomotion, human birth and engineering education. While on sabbatical in 2018-19 with his wife and kids he lived in Strasbourg, France and he taught at the INSA Strasbourg and Hochschule Karlsruhe and wrote about his personal and professional perspectives.  James is a proponent of using social media to advocate for justice, equity, diversity and inclusion as well as evidence-based applications of research in the public sphere. You can find him on Twitter. Originally from Québec City, he now lives in Toronto, Canada.