No. 41 / project of 147 on the ladder

FreeRTOS port (compile-only)

introduces — First third-party RTOS code in the build - FreeRTOS-Kernel V11.1.0 links against our chip with a small chip-specific port adapter

harden statelast run2026-05-03
areaunknownμm²
signoff
  • DRCNOT RUN
  • LVSNOT RUN
  • antennaNOT RUN

P41 is the first rung where real third-party RTOS code lives in the build. Same RTL as P39+P40 - this rung is software-only.

Status: RTL pass. FreeRTOS-Kernel V11.1.0 links cleanly against our chip via a small port adapter. Compile-only; the P42 multi-task demo is the rung that asserts on actual scheduler behaviour in simulation.

What’s in the box

The build pulls FreeRTOS from an external $FREERTOS_KERNEL checkout (the same pattern we use for riscv-arch-test):

git clone --depth 1 --branch V11.1.0 \
  https://github.com/FreeRTOS/FreeRTOS-Kernel.git /tmp/FreeRTOS-Kernel
make -C projects/41_freertos_port/test \
  FREERTOS_KERNEL=/tmp/FreeRTOS-Kernel app

The resulting ELF is 30 KiB total (13 KiB text, 12 B data, 16 KiB BSS - dominated by FreeRTOS’s heap_4 allocator).

Three places the port had to deviate

32-bit mtime/mtimecmp. The upstream vPortSetupTimerInterrupt writes a 64-bit value to mtimecmp. Our DUT exposes only a 32-bit mtimecmp at 0x10000004; a 64-bit store would step on a non-existent 0x10000008. We compile the upstream default away with configMTIMECMP_BASE_ADDRESS = 0 and provide our own.

Custom timer ISR via the application hook. With portasmHAS_MTIME = 0 the upstream port routes every interrupt through freertos_risc_v_application_interrupt_handler. Ours dispatches on mcause: machine timer interrupts bump the deadline, call xTaskIncrementTick, and switch context if needed.

Freestanding libc shims. The Arch Linux riscv64-elf-gcc package ships only freestanding headers - no newlib. FreeRTOS includes <stdlib.h> (for size_t only) and <string.h> (for memset/memcpy/memcmp/strlen/strcmp/strncmp). We provide header shims under port/freestanding-shims/ and minimal byte-loop implementations in port/p41_libc.c.

What does not change

  • src/top.sv: identical to P39/P40 except mimpid = 0x41.
  • ISA: RV32IM + Zicsr + Zifencei + Zicntr.
  • Memory map: external RAM at 0, MMIO at 0x10000000.
  • Arch-test plugin: copied verbatim from P40.

Why compile-only is the right gate

FreeRTOS bring-up has a long chain of “did the port work?” questions we don’t want to answer all at once:

  1. Does the C compile against our headers?
  2. Does the assembly trap path link against our linker script symbols?
  3. Does the timer interrupt actually schedule?
  4. Do tasks yield correctly?
  5. Do queues / semaphores actually work?

P41 answers (1) and (2). P42 answers (3) and (4). A real demo at some future rung answers (5). Splitting these means each rung’s failure mode is narrow and diagnosable.

Harden result

NOT RUN. P40 and P41 are software-side rungs - the RTL is verbatim P39 except for mimpid. The expected harden shape is identical to P39’s; there’s no value in re-running it for P40/P41 specifically.

What just happened?

The trap-frame work in P40 was deliberate setup for this rung. FreeRTOS’s port shape - a 32-word context, a mcause-dispatch trap handler, IRQ save/restore primitives - is exactly what P40 built for its directed test. P41 ports those shapes onto an external code base without rework. That’s the win: P40 plus P41 plus the upstream port add up to a working FreeRTOS environment without any single rung biting off too much.