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:
- It works with common-used tools : Spike and riscv64-unknown-elf-gcc
- It targets "bare-metal" RISC-V development
- It provides examples with both C and Assembler
- 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.
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
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.
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:
- Run the make utility on all source files
- 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:
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:
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
- the main function within the
main.s
file, then - executes an assembler jump,
jal c_function
- 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:
- Can other typical C libraries be included
- Can this be compiled with C17 or C23?
- Can I anchor a similar project in a C main file rather than an Assembler main file, and
- 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
- Stand-alone assembler programs
- Stand-alone C programs
- Assembler programs that call C functions
- C programs that call Assembler functions
- 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/
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.