- Code Samples
- Basic C++ Start-up
- RISC-V Start-up Specifics
- C/C++ Start-up Specifics
- Dead End
- References
Code Samples
These code samples relate to this article:
Basic C++ Start-up
From processor reset to entering the main()
function some work
needs to be done to allow a C or C++ program to execute. You will find that generally the
device SDK/BSP will provide code or libraries to care of this. However, as RISC-V
gives you the a great chance to customize a CPU or build a custom SoC, you may also find it useful to
understand this bootstrapping process. I think you will agree it’s easier to understand C++ than assembly.
You will find an example of a stripped down bare-metal test program is in the scratchpad repo. The whole example is implemented in C++ so the high level program flow is easier to understand.
These are the relevant files for this post:
- baremetal-startup-cxx/src/startup.cpp : The start-up code.
- baremetal-startup-cxx/src/linker.lds : An example linker file. This is taken from SiFive’s HiFive RevB BSP.
NOTE - These examples are intended to give an overview for experimenting with RISC-V systems. They are not intended for production code.
RISC-V Start-up Specifics
The initial entry code needs to be in assembler, GCC’s extended
inline assembler
keyword is
used to embedded this in the C++ source file. The enter()
function must be placed in the location where
code execution starts by specifying the linker section. In this
example the .text.metal.init.enter
linker section is used to place
it at the entry point. Unlike platforms like ARM Cortex-M, the entry point is not specified by the RISC-V
specification, or in the interrupt vector table, but is a vendor specific configuration.
The function is marked naked
. At this point there is no stack and no variables (global or local) can be accessed.
- The stack pointer is configured with a location
_sp
from the linker script. This allows a nonnaked
C function to be called. - The global pointer is configured with a location
_global_pointer$
from the linker script. The GCC compiled code relies on this to access global variables. (see https://www.sifive.com/blog/all-aboard-part-3-linker-relaxation-in-riscv-toolchain)
This example is for a single core, so it does not try and identify the current hart but just proceeds to jump to the main startup function implemented in C++. See the freedom-metal entry code for a more complete example that considers multi-hart start-up.
C/C++ Start-up Specifics
Before any standard C or C++ code can be called the run time environment needs to be initialized. The linker defines a number of regions in memory that the start-up file uses to perform initialization.
- The
bss
region contains global variables with no initial value. The SRAM allocated to these variables is cleared to 0. - The
data
section contains global variables with initial values. These values are copied from read-only memory (FLASH/ROM) to SRAM. - The
itim
section is a code section that is to be moved to SRAM to improve performance. - The
init
array is a table of constructor function pointers to construct global variables. Beware the static initialization order.
Once those regions are initialized C/C++ code can be run, main()
is called.
Dead End
The main()
function should never exit, but in the event it does a busy loop is invoked. The wfi
instruction should put the core in low power mode when it’s idle - but that is implementation specific.
References
This C++ start-up code is based on examples in chapter 8 of Christopher Kormanyos’s Real Time C++, code examples for AVR, ARM, Renesas etc are on github.
A more complete start-up environment is given by SiFive in their Freedom Metal SDK. The linker script used here is from the Freedom E-SDK.
ARM’s CMSIS gives a good SoC independent minimal bare metal initialization examples for ARM Cortex-M devices. e.g. Cortex-M0 start-up. I find this an easier way to understand the start-up flow required for a C runtime environment.