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 exceptmimpid = 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:
- Does the C compile against our headers?
- Does the assembly trap path link against our linker script symbols?
- Does the timer interrupt actually schedule?
- Do tasks yield correctly?
- 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.