Back to Blog

RISC-V Kernel Boot Flow: From Reset Vector to Linker Script Memory Layout

RISC-V AI Assistant 2026-09-12 02:22:32 11 views

A practical guide to RISC-V startup code, linker script memory layout, and common boot-time pitfalls translated from the 21ic community.

A recent post on the 21ic RISC-V forum provides a compact but useful walkthrough of the early boot process on RISC-V MCUs and application processors. We have translated and reorganized it here for English-speaking developers bringing up RISC-V hardware.

First Instruction After Reset

Unlike ARM Cortex-M, which expects a vector table at address zero, RISC-V resets to a single fixed entry point defined by the linker script. That entry is typically address 0x00000000 for small MCUs, or the start of Flash/ROM for application processors. The first routine is conventionally named _start and is written in assembly.

What _start Must Do

A minimal RISC-V startup file performs the following steps before calling main:

  1. Load the stack pointer from __stack_top, exported by the linker script.
  2. Copy the initialized .data section from its Load Memory Address (LMA) in Flash to its Virtual Memory Address (VMA) in RAM.
  3. Zero the .bss section in RAM.
  4. Optionally configure the Floating-Point Unit and set the PLIC base address.
  5. Jump to main.

If step 3 is forgotten, uninitialized global variables retain whatever values were in RAM at power-on, producing intermittent failures that are extremely difficult to debug.

Linker Script Memory Layout

Modern RISC-V toolchains such as riscv-none-elf-gcc and gcc-riscv64-unknown-elf define memory regions with the MEMORY command. A typical production layout looks like this:

MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 256K RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 64K } SECTIONS { .text : { (.text) } > FLASH .rodata : { (.rodata) } > FLASH .data : AT(_data_lma) { _data_vma = .; (.data) _edata = .; } > RAM .bss : { _bss_start = .; (.bss) *(COMMON) _bss_end = .; } > RAM }

Key points:

Common Pitfalls

  1. Weak symbol for _start. If __attribute__((weak)) is used but no implementation is provided, the linker may choose an unintended entry point.
  2. Stack size too small. A stack overflow in RISC-V startup code rarely produces a clean fault before main runs.
  3. LMA/VMA mismatch. A wrong AT directive causes .data to contain Flash addresses at runtime, corrupting variables.
  4. Missing startup files. Using -nostartfiles without providing your own _start routine leaves the reset vector empty.

Verification Workflow

Before blaming hardware for a runaway program, verify the image layout:

riscv-none-elf-readelf -S firmware.elf riscv-none-elf-objdump -d firmware.elf | less

Compare the VMA and LMA columns with the linker script and the map file. In an OpenOCD plus GDB session, place a breakpoint at _start and confirm that sp is loaded with __stack_top before stepping into the C runtime.

Why This Matters for WCH and SpacemiT Bring-Up

This generic RISC-V boot sequence applies directly to the WCH CH32V003, CH32V307, CH32V317, CH32H417, and CH32L103 families, as well as to the SpacemiT K1 and K3 bootloaders. Whether you are writing a bare-metal motor controller on a CH32V003 or porting a custom RTOS to K1, the linker script is the contract between the hardware memory map and the compiler output. Getting it right early prevents weeks of chasing phantom memory corruption later.

Source: RISC-V内核启动流程详解:从复位入口到链接脚本内存布局调优
Tags: RISC-Vbootlinker-scriptstartupCH32VK1K3bare-metalembedded

Have questions about this topic?

Start a Discussion Get a Quote