Skip to content

Instantly share code, notes, and snippets.



Last active Jan 12, 2018
What would you like to do?
Rocket Boot

Rocket Core Bootstrap


User-Level ISA

Priviledge ISA

Running 'Hello, World!' on Spike

#include <stdio.h>    
int main(void) {
  printf("Hello, World!\n");
  return 0;

Does not appear to require a custom linker script.

$ riscv64-unknown-elf-gcc -std=gnu99 -o hello.o -c hello.c
$ riscv64-unknown-elf-gcc hello.o  -o hello 
$ spike $RISCV/riscv64-unknown-elf/bin/pk hello 
Hello, World!

Spike defaults to booting at PC 0x1000. Then jumps to 0x80000000.

core   0: 0x0000000000001000 (0x00000297) auipc   t0, 0x0      // <--- t0 = 0x0
core   0: 0x0000000000001004 (0x02028593) addi    a1, t0, 32   // <--- a1 = 0x1020, 32 is constant, reset_vec_size
core   0: 0x0000000000001008 (0xf1402573) csrr    a0, mhartid
core   0: 0x000000000000100c (0x0182b283) ld      t0, 24(t0)   // <--- t0 = 0x8000000
core   0: 0x0000000000001010 (0x00028067) jr      t0
core   0: 0x0000000080000000 (0x1e80006f) j       pc + 0x1e8

Source code for the reset vector in

 uint32_t reset_vec[reset_vec_size] = {
     0x297,                                      // auipc  t0,0x0
     0x28593 + (reset_vec_size * 4 << 20),       // addi   a1, t0, &dtb
     0xf1402573,                                 // csrr   a0, mhartid
     get_core(0)->xlen == 32 ?
       0x0182a283u :                             // lw     t0,24(t0)
       0x0182b283u,                              // ld     t0,24(t0)
     0x28067,                                    // jr     t0
     (uint32_t) (start_pc & 0xffffffff),
     (uint32_t) (start_pc >> 32)

The default values are in encoding.h.

#define DEFAULT_RSTVEC     0x00001000
#define CLINT_BASE         0x02000000
#define CLINT_SIZE         0x000c0000
#define EXT_IO_BASE        0x40000000
#define DRAM_BASE          0x80000000

The simulator can generate a dts (Device Tree Source) file. Look (here)[] for an example.

The memory space at 0x80000000 is populated by the front-end server (running on host PC).

htif_t::run() is entry point for front end server, the function is called by spike.

htif_t::load_program() first loads the proxy kernel (pk) using the path to binary (string variable) given by spike argv. Function elfloader calls memif_write (in spike) to populate dram/memory with the proxy kernel. Finally, tohost_addr and fromhost_addr are read from from the loaded binary.

000000008000a000 fromhost
000000008000a008 tohost

htif_t::run() is the front end server main loop. Commands are read from tohost_addr. After the command address tohost_address is cleared. Data is sent back to cpu by polling fromhost_queue and writing to address fromhost_addr. This interface is undocumented. More information can be found here.

Tips to run spike with debugging symbols enabled

cd $ROCKETCHIP/riscv-tools/riscv-fesvr
# add option -g to CXXFLAGS and CFLAGS in file, , change -O2 to -O0

cd $ROCKETCHIP/riscv-tools/riscv-isa-sim
# add option -g to CXXFLAGS and CFLAGS in file, change -O2 to -O0

# build, then check; both should output 'not stripped'
cd $RISCV && ./
file $RISCV/bin/spike
file $RISCV/lib/

First stage bootloader (bootrom.img)

The first stage bootloader is located here: $ROCKETCHIP/bootrom. The assembly source is compiled with gcc, not gas. The following gcc compile options are used.

Makefile gives the steps to convert the generated object file to a binary image.

The bootloader defines three sections:


The following two commands are helpful:

# display assembly contents of all executable sections
riscv64-unknown-elf-objdump -d hello
# shows the symbol table
riscv64-unknown-elf-objdump -t hello

Running 'Hello, World!' on C Emulator

The C emulator will link to dtm_t from the front end server.

TODO: Publish information about how to debug using CLion.

To help debug, the output of the C emulator can be disassembled: cat log | spike-dasm > log.dasm

RocketChip bringup notes

What happens before anything happens?

SimDTM (C model) drives dmiClock and dmiReset. The model (class dtm_t) is implemented in part of fesvr.

Details about individual parts of the sequence is in the (debug spec)[]. Information here is taken from that document.

The first few operations are DMI requests from the debug module to the DMI stub in FESVR.

  • read(DMI_ABSTRACTS) // get status of the abstract command
  • read(DMI_HARTINFO) // gives information about the hard currently selected by hartsel
  • write(DMU_DMCONTROL) // enable the debugger

Next, the debugger selects a hart (roughly means cpu) and subsequently performs a halt operation.

dtm_t::get_xlen, is done using an abstract command (section 3.5 of debug spec). Two commands are supported: 'access register command' and 'quick access'. Access register gives the debugger access to CPU registers and program buffers. Quick access executes the program buffer (CHECKME).

A debugger can write small programs to the program buffer.

When a program buffer is present, a debugger can access the system bus by having a RISC-V hart perform the access it requires.

After xlen is read, the hart is resumed and htif_t:run() begins.

htif_t::start calls load_program. The function reads the elf file from disk, parse its data structures, and calls write_chunk to xfer data to the simulated model. To xfer data to the simulated model write_chunk first halts, then resumes the selected hart. The function is a good example of sending program code to be executed by the target cpu.

I quickly hit this error:

ERROR: ../fesvr/, Debug Abstract Command Error #3 (EXCEPTION)ERROR: ../fesvr/, Should die, but allowing simulation to continue and fail.

This topic on hw-dev has notes about the debug.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.