Background
I'm trying to understand the boot process for a simple RISC-V computer in detail. I'm not interested in getting all the way to an operating system: I just want to know what happens between poweron and running a bare-metal program to write some data to the UART device.
Since booting is hardware-specific, I picked the SiFive HiFive1 RevB because:
- It's relatively cheap
- It has both real hardware and can be emulated via QEMU's
sifive_emachine
(However, if there is a more appropriate board, I would also like to know)
My goal is to understand all of the code involved in the boot process. I am particularly confused about the role of the following:
- Firmware
- The bootloader
- QEMU's
-biosflag - QEMU's
-kernelflag
Desired outcome
As proof that I understand all the pieces going into the boot process, I would like to write a program which writes "Hello World" to the UART0 device, and then halts (either with a busy loop, or somehow by powering off the board).
Ideally, I would write all the pieces needed to make this happen in RISC-V assembly. I would also like to avoid using any high level languages, toolchains, and linkers.
A working version for the QEMU virt machine
I have a simple program which works as expected on QEMU's virt machine.
It's a binary file test.bin of RISC-V instructions produced by a python script--it is not produced by gcc or another compiler.
Here is the contents of the file according to objdump:
> objdump -b binary --architecture=riscv -D test.bin
test.bin: file format binary
Disassembly of section .data:
0000000000000000 <.data>:
0: 00100093 li ra,1
4: 01c09093 slli ra,ra,0x1c
8: 01300113 li sp,19
c: 00c11113 slli sp,sp,0xc
10: 04800113 li sp,72
14: 00208023 sb sp,0(ra)
18: 06500113 li sp,101
1c: 00208023 sb sp,0(ra)
20: 06c00113 li sp,108
24: 00208023 sb sp,0(ra)
28: 06c00113 li sp,108
2c: 00208023 sb sp,0(ra)
30: 06f00113 li sp,111
34: 00208023 sb sp,0(ra)
...snip
This generates the UART0 address (0x10000000) of the virt machine and places it in register R1.
It then simply writes each byte of "Hello, World" to that address.
I run this file as follows:
> qemu-system-riscv32 -nographic -machine virt -bios test.bin
Hello, World!
QEMU 7.1.0 monitor - type 'help' for more information
(qemu) quit
The final two lines of output here are because QEMU appears to hang, and I use C-a c; quit to exit.
I can also run the image using the -kernel flag:
> qemu-system-riscv32 -nographic -machine virt -kernel test.bin
Hello, World!
Hello, World!
Hello, World!
...
In this case, it loops printing "Hello World" until I quit.
I assume this is because the virt machine has a default bios which sets up handlers for illegal instructions and restarts my program: since test.bin does not busy loop at the end, this triggers the handler and restarts.
Question 1
- If I use
-bios test.bin, is my code the first code running when the CPU boots, or does some other code run first? (For example "firmware") - Is there a "firmware stage" that happens before the BIOS?
- What does the default
-biosfor QEMU'svirtmachine do?
Porting to the sifive_e machine
I expected that I could just port this code to the sifive_e machine by changing the UART0 address to 0x10013000
Here is the objdump for the modified code sifive_e.bin, which computes the UART address by adding together R1 and R2:
0: 00100093 li ra,1
4: 01c09093 slli ra,ra,0x1c
8: 01300113 li sp,19
c: 00c11113 slli sp,sp,0xc
10: 002080b3 add ra,ra,sp
14: 04800113 li sp,72
18: 00208023 sb sp,0(ra)
1c: 06500113 li sp,101
20: 00208023 sb sp,0(ra)
However, when I run this as follows:
qemu-system-riscv32 -nographic -machine sifive_e -bios sifive_e.bin
QEMU produces no output and appears to hang. This is also the case if I run it like this:
qemu-system-riscv32 -nographic -machine sifive_e -kernel sifive_e.bin
Question 2
- What is missing to make this example work on the
sifive_emachine? - Where can I find documentation on the boot process for
sifive_e? - Does the boot process of the QEMU
sifive_emachine really correspond to that of theHiFive1 RevBboard?