How can I compile C code to get a bare-metal skeleton of a minimal RISC-V assembly program?

Adrian picture Adrian · Jul 13, 2015 · Viewed 10.5k times · Source

I have the following simple C code:

   void main(){
    int A = 333;
    int B=244;
    int sum;
    sum = A + B;  
}

When I compile this with

$riscv64-unknown-elf-gcc code.c -o code.o

If I want to see the assembly code I use

$riscv64-unknown-elf-objdump -d code.o 

But when I explore the assembly code I see that this generates a lot of code which I assume is for Proxy Kernel support (I am a newbie to riscv). However, I do not want that this code has support for Proxy kernel, because the idea is to implement only this simple C code within an FPGA.

I read that riscv provides three types of compilation: Bare-metal mode, newlib proxy kernel and riscv Linux. According to previous research, the kind of compilation that I should do is bare metal mode. This is because I desire a minimum assembly without support for the operating system or kernel proxy. Assembly functions as a system call are not required.

However, I have not yet been able to find as I can compile a C code for get a skeleton of a minimal riscv assembly program. How can I compile the C code above in bare metal mode or for get a skeleton of a minimal riscv assembly code?

Answer

Chris picture Chris · Jul 13, 2015

Warning: this answer is somewhat out-of-date as of the latest RISC-V Privileged Spec v1.9, which includes the removal of the tohost Control/Status Register (CSR), which was a part of the non-standard Host-Target Interface (HTIF) which has since been removed. The current (as of 2016 Sep) riscv-tests instead perform a memory-mapped store to a tohost memory location, which in a tethered environment is monitored by the front-end server.


If you really and truly need/want to run RISC-V code bare-metal, then here are the instructions to do so. You lose a bunch of useful stuff, like printf or FP-trap software emulation, which the riscv-pk (proxy kernel) provides.

First things first - Spike boots up at 0x200. As Spike is the golden ISA simulator model, your core should also boot up at 0x200.

(cough, as of 2015 Jul 13, the "master" branch of riscv-tools (https://github.com/riscv/riscv-tools) is using an older pre-v1.7 Privileged ISA, and thus starts at 0x2000. This post will assume you are using v1.7+, which may require using the "new_privileged_isa" branch of riscv-tools).

So when you disassemble your bare-metal program, it better start at 0x200!!! If you want to run it on top of the proxy-kernel, it better start at 0x10000 (and if Linux, it’s something even larger…).

Now, if you want to run bare metal, you’re forcing yourself to write up the processor boot code. Yuck. But let’s punt on that and pretend that’s not necessary.

(You can also look into riscv-tests/env/p, for the “virtual machine” description for a physically addressed machine. You’ll find the linker script you need and some macros.h to describe some initial setup code. Or better yet, in riscv-tests/benchmarks/common.crt.S).


Anyways, armed with the above (confusing) knowledge, let’s throw that all away and start from scratch ourselves...

hello.s:
 .align 6
 .globl _start
 _start:
 # screw boot code, we're going minimalist
 # mtohost is the CSR in machine mode
 csrw mtohost, 1;
 1:
 j 1b

and link.ld:

 OUTPUT_ARCH( "riscv" )
 ENTRY( _start )
 SECTIONS
 {
 /* text: test code section */
 . = 0x200;
 .text :
 {
 *(.text)
 }
 /* data: Initialized data segment */
 .data :
 {
 *(.data)
 }
 /* End of uninitalized data segement */
 _end = .;
 }

Now to compile this…

riscv64-unknown-elf-gcc -nostdlib -nostartfiles -Tlink.ld -o hello hello.s

This compiles to (riscv64-unknown-elf-objdump -d hello):

 hello: file format elf64-littleriscv

 Disassembly of section .text:

 0000000000000200 <_start>:
 200: 7810d073 csrwi tohost,1
 204: 0000006f j 204 <_start+0x4>

And to run it:

spike hello

It’s a thing of beauty.

The link script places our code at 0x200. Spike will start at 0x200, and then write a #1 to the control/status register “tohost”, which tells Spike “stop running”. And then we spin on an address (1: j 1b) until the front-end server has gotten the message and kills us.

It may be possible to ditch the linker script if you can figure out how to tell the compiler to move <_start> to 0x200 on its own.


For other examples, you can peruse the following repositories:

The riscv-tests repository holds the RISC-V ISA tests that are very minimal (https://github.com/riscv/riscv-tests).

This Makefile has the compiler options: https://github.com/riscv/riscv-tests/blob/master/isa/Makefile

And many of the “virtual machine” description macros and linker scripts can be found in riscv-tests/env (https://github.com/riscv/riscv-test-env).

You can take a look at the “simplest” test at (riscv-tests/isa/rv64ui-p-simple.dump).

And you can check out riscv-tests/benchmarks/common for start-up and support code for running bare-metal.