Skip to main content

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​

ItemAddressNotes
Hart 0-RV64IMC, machine mode; other harts are parked
MRAM0x4000_020016 MiB window; the first 512 bytes belong to the boot flow
System registers0x0200_0000SystemConfig bit 6 enables the UART
UART00x0200_4000Original Shakti integration, polling, 115200 8N1
Machine timer0x80F4_0200mtime, with mtimecmp at +0x08
PLIC0xA000_0000Machine-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_zifencei with the lp64 ABI, so the port saves no FP state. csr.h rejects a build with F enabled.
  • No C library. The riscv64-unknown-elf toolchain installed by scripts/install_riscv.sh ships lp64d libraries only, so the example links with -nostdlib and board.c provides memset. 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_IDLE stays undefined, so the scheduler spins while idle, and parked harts and fatal-trap halts spin as well.
  • Trap vector. Erbium ignores mtvec base bits 11:1, so the trap vector must be 4 KiB aligned. trap_entry is 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 with plic_irq_enable().
  • Clocks. The UART divisor assumes a 400 MHz input clock and the tick assumes a 2 MHz mtime rate, the values used by the Zephyr and NuttX Erbium ports. If the boot firmware programs other rates, define ERBIUM_UART_CLOCK_HZ and ERBIUM_MTIME_FREQ_HZ when you build.
  • UART. The console only polls. uart_init() sets the UART enable bit in SystemConfig with 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​

FilePurpose
entry.SReset entry: parks other harts, sets gp, stack and .bss
tx_initialize_low_level.STrap vector and _tx_initialize_low_level
trap.cDispatches the timer, the PLIC and fatal exceptions
board.cboard_init() and memset
plic.c, plic.hPLIC driver for the hart's machine-mode context
hwtimer.c, hwtimer.hPeriodic tick from mtime and mtimecmp
uart.c, uart.hPolling console
demo_threadx.cThe standard ThreadX demo
link.ldsMRAM layout
erbium_gnu.cmakeToolchain file: rv64imc_zicsr_zifencei, lp64
CMakeLists.txt, build.shBuild of the library and the demo
test/Simulator tests for UART timekeeping and PLIC updates

References​