ThreadX example for Erbium
This directory runs the standard ThreadX demo on one hart of Erbium, the
16-hart RISC-V platform of the OpenHW Foundation CORE-ET project, using the
RISC-V64 GNU port in machine mode. The public et-platform functional
simulator, erbium_emu, is the reference target.
Hardware used
| Item | Address | Notes |
|---|---|---|
| Hart 0 | - | RV64IMC, machine mode; other harts are parked |
| MRAM | 0x4000_0200 | 16 MiB window; the first 512 bytes belong to the boot flow |
| System registers | 0x0200_0000 | SystemConfig bit 6 enables the UART |
| UART0 | 0x0200_4000 | Original Shakti integration, polling, 115200 8N1 |
| Machine timer | 0x80F4_0200 | mtime, with mtimecmp at +0x08 |
| PLIC | 0xA000_0000 | Machine-mode context of hart 0; sources 1 to 6 |
Port notes
- Soft float. Erbium implements only part of the F extension: divide,
square root and conversions between single precision and 64-bit integers
raise an emulation exception. The example and the ThreadX library are
built for
rv64imc_zicsr_zifenceiwith thelp64ABI, so the port saves no FP state.csr.hrejects a build with F enabled. - No C library. The
riscv64-unknown-elftoolchain installed byscripts/install_riscv.shshipslp64dlibraries only, so the example links with-nostdlibandboard.cprovidesmemset. A missing runtime helper shows up as a link error, not as a mix of ABIs. - No WFI. The example never executes
wfi, like the Zephyr Erbium port.TX_USE_WFI_IDLEstays undefined, so the scheduler spins while idle, and parked harts and fatal-trap halts spin as well. - Trap vector. Erbium ignores
mtvecbase bits 11:1, so the trap vector must be 4 KiB aligned.trap_entryis placed in its own 4 KiB aligned section and used in direct mode. - PLIC. The silicon hardwires source priorities to 1 and thresholds to
0. The simulator models them as writable registers that reset to 0, so
plic_init()writes the hardwired values. Drivers register a callback and enable their source withplic_irq_enable(). - Clocks. The UART divisor assumes a 400 MHz input clock and the tick
assumes a 2 MHz
mtimerate, the values used by the Zephyr and NuttX Erbium ports. If the boot firmware programs other rates, defineERBIUM_UART_CLOCK_HZandERBIUM_MTIME_FREQ_HZwhen you build. - UART. The console only polls.
uart_init()sets the UART enable bit inSystemConfigwith a read-modify-write, which leaves the watchdog setting as the boot flow left it.
Building
Install the toolchain with scripts/install_riscv.sh, or put another
riscv64-unknown-elf GCC on PATH, then run:
./build.sh
This configures CMakeLists.txt with erbium_gnu.cmake, builds
libthreadx.a for the soft-float ABI and links build/demo_threadx.elf.
Set BUILD_DIR to build elsewhere.
Running on the simulator
Build erbium_emu from
et-platform. The example was
tested at revision 836a4ab600e93c3059bb58c898edbc37744cd8d0:
git clone https://github.com/aifoundry-org/et-platform.git
cd et-platform
git checkout 836a4ab600e93c3059bb58c898edbc37744cd8d0
cmake -S erbium-hal -B build-hal -DCMAKE_INSTALL_PREFIX="$PWD/install"
cmake --build build-hal --target install
cmake -S sw-sysemu -B build-emu -GNinja -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_CXX_FLAGS=-Wno-error=unused-result \
-DCMAKE_PREFIX_PATH="$PWD/install"
cmake --build build-emu --target erbium_emu
At that revision a release build with a distribution GCC that enables
_FORTIFY_SOURCE stops on an unused write() result in the UART model;
the CMAKE_CXX_FLAGS setting above keeps that warning from being an error.
Load the ELF and start hart 0 at the MRAM load address, bypassing the boot ROM:
erbium_emu -elf_load build/demo_threadx.elf -reset_pc 0x40000200 \
-minions 1 -single_thread -max_cycles 100000000 \
-uart_rx_file /dev/null -uart_tx_file uart.log
cat uart.log
The log starts with [UART0] : Erbium UART initialized, followed by lines
from all eight demo threads. The simulator ends with a max cycles reached
error once the cycle budget is spent; judge the run by the UART log, not by
the exit status. With the default clocks one ThreadX tick is 2,000,000
simulated cycles, so the 100,000,000-cycle run shows thread_0 five times.
The simulator verifies register accesses, the trap flow and the console. It does not verify baud timing or the timer rate of real silicon.
Simulator regression tests
With erbium_emu on PATH, run:
ERBIUM_EMU=erbium_emu ./test/run_simulator_tests.sh
The UART test checks that a long polling write does not lose ThreadX ticks. The PLIC test changes one source in timer context while a thread changes another, and checks that neither update is lost. The runner builds both images with CMake and Ninja and reports a failure if either check fails. These tests use the simulator's UART drain rate, not physical baud timing.
Files
| File | Purpose |
|---|---|
entry.S | Reset entry: parks other harts, sets gp, stack and .bss |
tx_initialize_low_level.S | Trap vector and _tx_initialize_low_level |
trap.c | Dispatches the timer, the PLIC and fatal exceptions |
board.c | board_init() and memset |
plic.c, plic.h | PLIC driver for the hart's machine-mode context |
hwtimer.c, hwtimer.h | Periodic tick from mtime and mtimecmp |
uart.c, uart.h | Polling console |
demo_threadx.c | The standard ThreadX demo |
link.lds | MRAM layout |
erbium_gnu.cmake | Toolchain file: rv64imc_zicsr_zifencei, lp64 |
CMakeLists.txt, build.sh | Build of the library and the demo |
test/ | Simulator tests for UART timekeeping and PLIC updates |