Rocket Core Bootstrap
Specifications
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 sim.cc
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
0,
(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)[https://pastebin.com/zEfBp01P] 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 configure.ac, , change -O2 to -O0
autoconf
cd $ROCKETCHIP/riscv-tools/riscv-isa-sim
# add option -g to CXXFLAGS and CFLAGS in file configure.ac, change -O2 to -O0
autoconf
# build, then check; both should output 'not stripped'
cd $RISCV && ./build-spike-pk.sh
file $RISCV/bin/spike
file $RISCV/lib/libfesvr.so
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:
.text.start
: loads mhartid into a0, dtb address into a0, jumps to DRAM_BASE.text.hang
: infinite loop.rodata.dtb
: specifies the (dtb)[http://www.informit.com/articles/article.aspx?p=1647051&seqNum=5]
Disassembly
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 dtm.cc part of fesvr.
Details about individual parts of the sequence is in the (debug spec)[https://github.com/riscv/riscv-debug-spec/blob/master/riscv-debug-spec.pdf]. 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 commandread(DMI_HARTINFO)
// gives information about the hard currently selected by hartselwrite(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/dtm.cc:346, Debug Abstract Command Error #3 (EXCEPTION)ERROR: ../fesvr/dtm.cc:347, Should die, but allowing simulation to continue and fail.
This topic on hw-dev has notes about the debug.