Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions ports/risc-v64/gnu/example_build/erbium/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
cmake_minimum_required(VERSION 3.13 FATAL_ERROR)

project(demo_threadx_erbium LANGUAGES C ASM)
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON)

set(ERBIUM_DIR ${CMAKE_CURRENT_LIST_DIR})
set(THREADX_ROOT ${CMAKE_CURRENT_LIST_DIR}/../../../../..)

add_subdirectory(${THREADX_ROOT} threadx)

add_executable(demo_threadx
${ERBIUM_DIR}/entry.S
${ERBIUM_DIR}/tx_initialize_low_level.S
${ERBIUM_DIR}/board.c
${ERBIUM_DIR}/hwtimer.c
${ERBIUM_DIR}/plic.c
${ERBIUM_DIR}/trap.c
${ERBIUM_DIR}/uart.c
${ERBIUM_DIR}/demo_threadx.c
)
set_target_properties(demo_threadx PROPERTIES
OUTPUT_NAME "demo_threadx.elf"
LINK_DEPENDS ${ERBIUM_DIR}/link.lds
)

target_include_directories(demo_threadx PRIVATE ${ERBIUM_DIR})

target_link_libraries(demo_threadx PRIVATE threadx)

target_compile_options(demo_threadx PRIVATE
-Wall
-ffreestanding
-ffunction-sections
-fdata-sections
)

target_link_options(demo_threadx PRIVATE
-T${ERBIUM_DIR}/link.lds
-nostdlib
-Wl,--gc-sections
-Wl,-Map=${CMAKE_CURRENT_BINARY_DIR}/demo_threadx.map
)

function(add_erbium_test name source handler)
add_executable(${name}
${ERBIUM_DIR}/entry.S
${ERBIUM_DIR}/tx_initialize_low_level.S
${ERBIUM_DIR}/board.c
${ERBIUM_DIR}/hwtimer.c
${ERBIUM_DIR}/plic.c
${ERBIUM_DIR}/uart.c
${source}
${handler}
)
target_include_directories(${name} PRIVATE ${ERBIUM_DIR})
target_link_libraries(${name} PRIVATE threadx)
target_compile_options(${name} PRIVATE -ffreestanding)
target_link_options(${name} PRIVATE
-T${ERBIUM_DIR}/link.lds
-nostdlib
-Wl,--gc-sections
)
set_target_properties(${name} PROPERTIES LINK_DEPENDS ${ERBIUM_DIR}/link.lds)
endfunction()

add_erbium_test(test_uart_ticks ${ERBIUM_DIR}/test/test_uart_ticks.c ${ERBIUM_DIR}/trap.c)
add_erbium_test(test_plic_race ${ERBIUM_DIR}/test/test_plic_race.c ${ERBIUM_DIR}/test/test_plic_trap.c)
140 changes: 140 additions & 0 deletions ports/risc-v64/gnu/example_build/erbium/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# 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_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:

```bash
./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](https://github.com/aifoundry-org/et-platform). The example was
tested at revision `836a4ab600e93c3059bb58c898edbc37744cd8d0`:

```bash
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:

```bash
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:

```bash
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 |

## References

* [Erbium processor](https://github.com/openhwfoundation/core-et-erbium)
* [UART registers](https://github.com/openhwfoundation/core-et-erbium/blob/325b32b7efaa2c2ab9c91001c89d3f49a4740826/doc/uart.md)
* [CPU memory map](https://github.com/openhwfoundation/core-et-erbium/blob/325b32b7efaa2c2ab9c91001c89d3f49a4740826/doc/cpu_mm.md)
* [Platform interrupts](https://github.com/openhwfoundation/core-et-erbium/blob/325b32b7efaa2c2ab9c91001c89d3f49a4740826/doc/interrupts.md)
* [CPU subsystem](https://github.com/openhwfoundation/core-et-erbium/blob/325b32b7efaa2c2ab9c91001c89d3f49a4740826/doc/cpu_subsystem.md)
* [et-platform simulator](https://github.com/aifoundry-org/et-platform)
49 changes: 49 additions & 0 deletions ports/risc-v64/gnu/example_build/erbium/board.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/***************************************************************************
* Copyright (c) 2026 Eclipse ThreadX contributors
*
* This program and the accompanying materials are made available under the
* terms of the MIT License which is available at
* https://opensource.org/licenses/MIT.
*
* AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5.5).
* The AI-generated portions may be considered public domain (CC0-1.0)
* and not subject to the project's licence. The human contributor has
* reviewed and verified that the code is correct.
*
* SPDX-License-Identifier: MIT and CC0-1.0
**************************************************************************/

#include "plic.h"
#include "hwtimer.h"
#include "uart.h"
#include <stdint.h>
#include <stddef.h>

/* The example links without a C library; ThreadX needs memset. */
void *memset(void *des, int c, size_t n)
{
unsigned char *target = des;

for (size_t i = 0; i < n; i++)
target[i] = (unsigned char)c;
return des;
}

int board_init(void)
{
int ret;

ret = plic_init();
if (ret)
return ret;

ret = uart_init();
if (ret)
return ret;

ret = hwtimer_init();
if (ret)
return ret;

return 0;
}
28 changes: 28 additions & 0 deletions ports/risc-v64/gnu/example_build/erbium/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/bin/bash
##############################################################################
# Copyright (c) 2026 Eclipse ThreadX contributors
#
# This program and the accompanying materials are made available under the
# terms of the MIT License which is available at
# https://opensource.org/licenses/MIT.
#
# AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5.5).
# The AI-generated portions may be considered public domain (CC0-1.0)
# and not subject to the project's licence. The human contributor has
# reviewed and verified that the code is correct.
#
# SPDX-License-Identifier: MIT and CC0-1.0
##############################################################################

# Build the ThreadX library and the Erbium demo, build/demo_threadx.elf.

set -eu

SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
BUILD_DIR=${BUILD_DIR:-"${SCRIPT_DIR}/build"}

cmake -S "${SCRIPT_DIR}" -B "${BUILD_DIR}" -GNinja \
-DCMAKE_TOOLCHAIN_FILE="${SCRIPT_DIR}/erbium_gnu.cmake"
cmake --build "${BUILD_DIR}" --target demo_threadx

printf 'Built %s\n' "${BUILD_DIR}/demo_threadx.elf"
66 changes: 66 additions & 0 deletions ports/risc-v64/gnu/example_build/erbium/csr.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/***************************************************************************
* Copyright (c) 2026 Eclipse ThreadX contributors
*
* This program and the accompanying materials are made available under the
* terms of the MIT License which is available at
* https://opensource.org/licenses/MIT.
*
* AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5.5).
* The AI-generated portions may be considered public domain (CC0-1.0)
* and not subject to the project's licence. The human contributor has
* reviewed and verified that the code is correct.
*
* SPDX-License-Identifier: MIT and CC0-1.0
**************************************************************************/

/* Machine-mode CSR definitions and helpers for the Erbium example. */

#ifndef RISCV_CSR_H
#define RISCV_CSR_H

/* The Erbium FPU implements only part of the F extension: single-precision
divide, square root and conversions to and from 64-bit integers raise an
emulation exception that this example does not handle. Build for the
integer ISA with the soft-float lp64 ABI. */
#ifdef __riscv_flen
#error "The Erbium example requires a soft-float build: use -march=rv64imc_zicsr_zifencei -mabi=lp64."
#endif

/* Machine Status Register, mstatus */
#define MSTATUS_MIE (1L << 3) /* machine-mode interrupt enable */
#define MSTATUS_MPIE (1L << 7) /* previous MIE */
#define MSTATUS_MPP_M (3L << 11) /* previous mode: machine */

/* Machine Interrupt Enable, mie */
#define MIE_MTIE (1L << 7) /* machine timer */
#define MIE_MEIE (1L << 11) /* machine external */

#ifndef __ASSEMBLER__

#include <stdint.h>

static inline uintptr_t riscv_get_core(void)
{
uintptr_t x;
__asm__ volatile("csrr %0, mhartid" : "=r" (x));
return x;
}

/* Clear mstatus.MIE and return whether it was set. */
static inline int riscv_mintr_save(void)
{
uintptr_t x;
__asm__ volatile("csrrci %0, mstatus, %1" : "=r" (x) : "i" (MSTATUS_MIE) : "memory");
return (x & MSTATUS_MIE) != 0;
}

/* Set mstatus.MIE again if riscv_mintr_save found it set. */
static inline void riscv_mintr_restore(int enabled)
{
if (enabled)
__asm__ volatile("csrsi mstatus, %0" : : "i" (MSTATUS_MIE) : "memory");
}

#endif /* __ASSEMBLER__ */

#endif /* RISCV_CSR_H */
Loading