Create a gist now

Instantly share code, notes, and snippets.

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