Skip to content

feat(riscv): bare-metal startup + linker script generators#89

Open
avrabe wants to merge 1 commit intofeat/riscv-i32-selectorfrom
feat/riscv-startup-mtvec
Open

feat(riscv): bare-metal startup + linker script generators#89
avrabe wants to merge 1 commit intofeat/riscv-i32-selectorfrom
feat/riscv-startup-mtvec

Conversation

@avrabe
Copy link
Copy Markdown
Contributor

@avrabe avrabe commented May 3, 2026

Track B3 — adds the runtime infrastructure that turns synth's RISC-V .text into a bootable firmware ELF. Stacked on #88.

What this adds

`startup.rs` — `RiscVStartupGenerator`

Emits C source (with inline RISC-V asm) that:

  1. Disables interrupts (mie/mip = 0)
  2. Sets sp/gp/s11 (linear-memory base) from linker symbols
  3. Installs `_trap_entry` into mtvec (direct mode)
  4. Optionally enables the FPU
  5. Copies .data from flash to RAM, zeroes .bss
  6. Calls main(); spins via wfi if it returns

Trap entry saves caller-saved registers, marshals mcause/mepc/mtval into a0/a1/a2, dispatches to a weak `synth_trap_handler` (firmware-overridable).

`linker_script.rs` — `RiscVLinkerScriptGenerator`

Emits a GNU-`ld` script that:

  • Places `.text._reset` at the entry point, then `.text._trap_entry` (64-byte aligned for vectored-mode compatibility), then general `.text*`.
  • Splits `.data` between flash (load) and RAM (run) for the startup copy loop.
  • Provides `_stack_top`, `_data_start/_end/_load`, `_bss_start/_end`, `__linear_memory_base/_end`, `__global_pointer$`.

Memory model

The wasm linear-memory base lives in s11 — chosen because the RV psABI marks it callee-saved, so all selector load/store sequences (`add tmp, s11, addr; lw rd, offset(tmp)`) work without per-callee re-loading.

Test summary

  • 16 new tests (8 startup, 8 linker_script). RISC-V tests now total 98 (was 82).
  • All workspace tests pass; clippy/fmt clean.

Out of scope (B4)

  • CLI `--link` integration with riscv64-unknown-elf-gcc
  • Calculator demo running on Renode RV32 firmware
  • Kiln-builtins-style host bridge for RV32

🤖 Generated with Claude Code

Track B3 — adds the runtime infrastructure that turns synth's RISC-V
.text into a bootable firmware ELF.

## startup.rs — RiscVStartupGenerator

Emits C source (with inline RISC-V asm) that:
1. Disables interrupts (mie, mip = 0)
2. Sets sp/gp/s11 (linear-memory base) from linker symbols
3. Installs `_trap_entry` into mtvec (direct mode)
4. Optionally enables the FPU via mstatus.FS = 1
5. Copies .data from flash to RAM
6. Zeroes .bss
7. Calls main(); spins via `wfi` if main returns

The trap entry saves the AAPCS-style caller-saved set (ra, t0..t6, a0..a7),
marshals mcause/mepc/mtval into a0/a1/a2, and dispatches to a weak C
function `synth_trap_handler` that the firmware can override.

`StartupConfig` exposes three knobs: external_irq_count (PLIC wiring),
trap_returns (mret vs spin in default), enable_fpu (mstatus FPU enable).

## linker_script.rs — RiscVLinkerScriptGenerator

Emits a GNU-`ld` script that:
- Places `.text._reset` at the entry point so the reset vector is at the
  start of flash.
- Aligns `.text._trap_entry` to 64 bytes (works for both mtvec direct and
  vectored modes — vectored requires 64-byte alignment).
- Splits `.data` between flash (load address) and RAM (run address) so
  the startup copy loop has well-defined bounds.
- Provides `_stack_top`, `_data_start`/`_data_end`/`_data_load`,
  `_bss_start`/`_bss_end`, `__linear_memory_base`/`__linear_memory_end`,
  and `__global_pointer$` (= `_data_start + 0x800`, the standard RV
  small-data anchor).
- `LinkerScriptConfig` configures flash/ram origins, linear-memory size,
  and stack size.

## Memory model

The wasm linear-memory base lives in **s11** — chosen because the RV
psABI marks it callee-saved, so all selector-emitted load/store sequences
(`add tmp, s11, addr; lw rd, offset(tmp)`) work without per-callee
re-loading. The startup code pre-loads s11 from `__linear_memory_base`,
which the linker script positions in RAM.

## Test summary

* 16 new tests (8 in startup, 8 in linker_script). Total RISC-V tests
  now 98 (was 82).
* All workspace tests still pass.
* `cargo clippy --workspace --all-targets -- -D warnings` clean.

## Out of scope (B4)

* CLI integration of `--link` for RISC-V (riscv64-unknown-elf-gcc invocation)
* PLIC wiring (external_irq_count is exposed but not yet emitted)
* Calculator demo running on Renode RV32 with full firmware
* Kiln-builtins-style host bridge for RV32

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant