summaryrefslogtreecommitdiff
path: root/ports/risc-v32/gnu
diff options
context:
space:
mode:
authorFrédéric Desbiens <[email protected]>2026-06-08 10:01:32 +0200
committerGitHub <[email protected]>2026-06-08 10:01:32 +0200
commit87ab09cce305eb9cd4aacac4e8c62af72f665bff (patch)
treedd062ebbea5235d1921c64b396bf7e4ce79e9e15 /ports/risc-v32/gnu
parent9ab0cf9683f832cdb48e2099533a5b06a3afcd64 (diff)
parent730b61874bc5cf40768987605ae5187fde0fa1e2 (diff)
Merge pull request #544 from eclipse-threadx/devv6.5.1.202602_rel
Merging changes for the v.6.5.1.202602 release
Diffstat (limited to 'ports/risc-v32/gnu')
-rw-r--r--ports/risc-v32/gnu/CMakeLists.txt22
-rw-r--r--ports/risc-v32/gnu/example_build/core_v_mcu/CMakeLists.txt69
-rw-r--r--ports/risc-v32/gnu/example_build/core_v_mcu/README.md504
-rw-r--r--ports/risc-v32/gnu/example_build/core_v_mcu/bsp/fll.c209
-rw-r--r--ports/risc-v32/gnu/example_build/core_v_mcu/bsp/gpio.c128
-rw-r--r--ports/risc-v32/gnu/example_build/core_v_mcu/bsp/i2c_master.c153
-rw-r--r--ports/risc-v32/gnu/example_build/core_v_mcu/bsp/irq.c50
-rw-r--r--ports/risc-v32/gnu/example_build/core_v_mcu/bsp/string.c64
-rw-r--r--ports/risc-v32/gnu/example_build/core_v_mcu/bsp/system_core_v_mcu.c132
-rw-r--r--ports/risc-v32/gnu/example_build/core_v_mcu/bsp/temp_sensor.c46
-rw-r--r--ports/risc-v32/gnu/example_build/core_v_mcu/bsp/timer_irq.c58
-rw-r--r--ports/risc-v32/gnu/example_build/core_v_mcu/bsp/uart_driver.c169
-rwxr-xr-xports/risc-v32/gnu/example_build/core_v_mcu/build.sh26
-rw-r--r--ports/risc-v32/gnu/example_build/core_v_mcu/crt0.S48
-rw-r--r--ports/risc-v32/gnu/example_build/core_v_mcu/demo_threadx.c134
-rwxr-xr-xports/risc-v32/gnu/example_build/core_v_mcu/deploy.sh142
-rw-r--r--ports/risc-v32/gnu/example_build/core_v_mcu/gdb_init6
-rw-r--r--ports/risc-v32/gnu/example_build/core_v_mcu/include/bits.h59
-rw-r--r--ports/risc-v32/gnu/example_build/core_v_mcu/include/csr.h75
-rw-r--r--ports/risc-v32/gnu/example_build/core_v_mcu/include/fll.h93
-rw-r--r--ports/risc-v32/gnu/example_build/core_v_mcu/include/gpio.h68
-rw-r--r--ports/risc-v32/gnu/example_build/core_v_mcu/include/i2c_master.h38
-rw-r--r--ports/risc-v32/gnu/example_build/core_v_mcu/include/io.h61
-rw-r--r--ports/risc-v32/gnu/example_build/core_v_mcu/include/irq.h64
-rw-r--r--ports/risc-v32/gnu/example_build/core_v_mcu/include/memory_map.h39
-rw-r--r--ports/risc-v32/gnu/example_build/core_v_mcu/include/properties.h43
-rw-r--r--ports/risc-v32/gnu/example_build/core_v_mcu/include/pulp_mem_map.h103
-rw-r--r--ports/risc-v32/gnu/example_build/core_v_mcu/include/string.h25
-rw-r--r--ports/risc-v32/gnu/example_build/core_v_mcu/include/system_core_v_mcu.h29
-rw-r--r--ports/risc-v32/gnu/example_build/core_v_mcu/include/temp_sensor.h29
-rw-r--r--ports/risc-v32/gnu/example_build/core_v_mcu/include/timer.h120
-rw-r--r--ports/risc-v32/gnu/example_build/core_v_mcu/include/timer_irq.h26
-rw-r--r--ports/risc-v32/gnu/example_build/core_v_mcu/include/tx_user.h22
-rw-r--r--ports/risc-v32/gnu/example_build/core_v_mcu/include/uart_driver.h27
-rwxr-xr-xports/risc-v32/gnu/example_build/core_v_mcu/install_deps.sh78
-rw-r--r--ports/risc-v32/gnu/example_build/core_v_mcu/link.ld65
-rw-r--r--ports/risc-v32/gnu/example_build/core_v_mcu/openocd-nexys-Ashling-Opella-LD.cfg41
-rw-r--r--ports/risc-v32/gnu/example_build/core_v_mcu/tests/CMakeLists.txt28
-rw-r--r--ports/risc-v32/gnu/example_build/core_v_mcu/tests/mock/mmio_mock.c18
-rw-r--r--ports/risc-v32/gnu/example_build/core_v_mcu/tests/mock/mmio_mock.h45
-rw-r--r--ports/risc-v32/gnu/example_build/core_v_mcu/tests/test_irq.c66
-rw-r--r--ports/risc-v32/gnu/example_build/core_v_mcu/tests/test_timer.c53
-rw-r--r--ports/risc-v32/gnu/example_build/core_v_mcu/tx_initialize_low_level.S63
-rw-r--r--ports/risc-v32/gnu/example_build/core_v_mcu/vectors.S25
-rw-r--r--ports/risc-v32/gnu/example_build/cva6_ariane/board.c42
-rw-r--r--ports/risc-v32/gnu/example_build/cva6_ariane/build_libthreadx.sh17
-rw-r--r--ports/risc-v32/gnu/example_build/cva6_ariane/csr.h360
-rw-r--r--ports/risc-v32/gnu/example_build/cva6_ariane/demo_threadx.c404
-rw-r--r--ports/risc-v32/gnu/example_build/cva6_ariane/entry.S69
-rw-r--r--ports/risc-v32/gnu/example_build/cva6_ariane/hwtimer.c35
-rw-r--r--ports/risc-v32/gnu/example_build/cva6_ariane/hwtimer.h24
-rw-r--r--ports/risc-v32/gnu/example_build/cva6_ariane/link.lds49
l---------ports/risc-v32/gnu/example_build/cva6_ariane/plic.c1
l---------ports/risc-v32/gnu/example_build/cva6_ariane/plic.h1
-rw-r--r--ports/risc-v32/gnu/example_build/cva6_ariane/trap.c77
-rw-r--r--ports/risc-v32/gnu/example_build/cva6_ariane/tx_initialize_low_level.S163
-rw-r--r--ports/risc-v32/gnu/example_build/cva6_ariane/uart.c893
-rw-r--r--ports/risc-v32/gnu/example_build/cva6_ariane/uart.h961
-rw-r--r--ports/risc-v32/gnu/example_build/qemu_virt/CMakeLists.txt45
-rwxr-xr-xports/risc-v32/gnu/example_build/qemu_virt/build_libthreadx.sh32
l---------[-rw-r--r--]ports/risc-v32/gnu/example_build/qemu_virt/csr.h344
-rw-r--r--ports/risc-v32/gnu/example_build/qemu_virt/demo_threadx.c16
-rw-r--r--ports/risc-v32/gnu/example_build/qemu_virt/entry.S72
-rw-r--r--ports/risc-v32/gnu/example_build/qemu_virt/entry.s58
-rw-r--r--ports/risc-v32/gnu/example_build/qemu_virt/link.lds4
l---------[-rw-r--r--]ports/risc-v32/gnu/example_build/qemu_virt/plic.c73
l---------[-rw-r--r--]ports/risc-v32/gnu/example_build/qemu_virt/plic.h50
-rw-r--r--ports/risc-v32/gnu/example_build/qemu_virt/test/threadx_test_tx_gnu_riscv32_qemu.py295
l---------[-rw-r--r--]ports/risc-v32/gnu/example_build/qemu_virt/trap.c68
l---------[-rw-r--r--]ports/risc-v32/gnu/example_build/qemu_virt/uart.c103
l---------[-rw-r--r--]ports/risc-v32/gnu/example_build/qemu_virt/uart.h24
-rwxr-xr-xports/risc-v32/gnu/example_build/xuantie_smartl_fpga/build_libthreadx.sh11
-rwxr-xr-xports/risc-v32/gnu/example_build/xuantie_smartl_fpga/build_threadx_sample.sh11
-rw-r--r--ports/risc-v32/gnu/example_build/xuantie_smartl_fpga/components/chip_riscv_dummy/include/dw_uart.h11
-rwxr-xr-xports/risc-v32/gnu/example_build/xuantie_smartl_fpga/components/csi/csi2/include/drv/list.h11
-rw-r--r--ports/risc-v32/gnu/example_build/xuantie_smartl_fpga/demo_threadx.c11
-rw-r--r--ports/risc-v32/gnu/inc/tx_port.h273
-rw-r--r--ports/risc-v32/gnu/src/tx_initialize_low_level.S2
-rw-r--r--ports/risc-v32/gnu/src/tx_thread_context_restore.S32
-rw-r--r--ports/risc-v32/gnu/src/tx_thread_context_save.S28
-rw-r--r--ports/risc-v32/gnu/src/tx_thread_schedule.S22
-rw-r--r--ports/risc-v32/gnu/src/tx_thread_system_return.S18
82 files changed, 7121 insertions, 952 deletions
diff --git a/ports/risc-v32/gnu/CMakeLists.txt b/ports/risc-v32/gnu/CMakeLists.txt
index 9357c697..05000757 100644
--- a/ports/risc-v32/gnu/CMakeLists.txt
+++ b/ports/risc-v32/gnu/CMakeLists.txt
@@ -1,19 +1,7 @@
+include(${CMAKE_CURRENT_LIST_DIR}/../../../cmake/threadx_riscv_port.cmake)
-target_sources(${PROJECT_NAME}
- PRIVATE
- # {{BEGIN_TARGET_SOURCES}}
- ${CMAKE_CURRENT_LIST_DIR}/src/tx_initialize_low_level.S
- ${CMAKE_CURRENT_LIST_DIR}/src/tx_thread_context_restore.S
- ${CMAKE_CURRENT_LIST_DIR}/src/tx_thread_context_save.S
- ${CMAKE_CURRENT_LIST_DIR}/src/tx_thread_interrupt_control.S
- ${CMAKE_CURRENT_LIST_DIR}/src/tx_thread_schedule.S
- ${CMAKE_CURRENT_LIST_DIR}/src/tx_thread_stack_build.S
- ${CMAKE_CURRENT_LIST_DIR}/src/tx_thread_system_return.S
- ${CMAKE_CURRENT_LIST_DIR}/src/tx_timer_interrupt.S
- # {{END_TARGET_SOURCES}}
-)
-
-target_include_directories(${PROJECT_NAME}
- PUBLIC
- ${CMAKE_CURRENT_LIST_DIR}/inc
+threadx_add_riscv_port(
+ SRC_DIR ${CMAKE_CURRENT_LIST_DIR}/src
+ INC_DIR ${CMAKE_CURRENT_LIST_DIR}/inc
+ EXAMPLE_DIR ${CMAKE_CURRENT_LIST_DIR}/example_build/qemu_virt
)
diff --git a/ports/risc-v32/gnu/example_build/core_v_mcu/CMakeLists.txt b/ports/risc-v32/gnu/example_build/core_v_mcu/CMakeLists.txt
new file mode 100644
index 00000000..662914d1
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/core_v_mcu/CMakeLists.txt
@@ -0,0 +1,69 @@
+cmake_minimum_required(VERSION 3.15 FATAL_ERROR)
+
+project(demo_threadx_core_v_mcu LANGUAGES C ASM)
+set(CMAKE_C_STANDARD 99)
+set(CMAKE_C_STANDARD_REQUIRED ON)
+
+set(CORE_V_MCU_DIR ${CMAKE_CURRENT_LIST_DIR})
+set(THREADX_ROOT ${CMAKE_CURRENT_LIST_DIR}/../../../../..)
+set(TX_USER_FILE ${CORE_V_MCU_DIR}/include/tx_user.h)
+
+set(THREADX_ARCH "risc-v32")
+set(THREADX_TOOLCHAIN "gnu")
+
+message(STATUS "Toolchain file: ${CMAKE_TOOLCHAIN_FILE}")
+message(STATUS "C compiler: ${CMAKE_C_COMPILER}")
+
+add_subdirectory(${THREADX_ROOT} threadx)
+target_include_directories(threadx PRIVATE ${CORE_V_MCU_DIR}/include)
+
+set(SRCS
+ ${CORE_V_MCU_DIR}/crt0.S
+ ${CORE_V_MCU_DIR}/vectors.S
+ ${CORE_V_MCU_DIR}/tx_initialize_low_level.S
+ ${CORE_V_MCU_DIR}/bsp/irq.c
+ ${CORE_V_MCU_DIR}/bsp/timer_irq.c
+ ${CORE_V_MCU_DIR}/bsp/fll.c
+ ${CORE_V_MCU_DIR}/bsp/gpio.c
+ ${CORE_V_MCU_DIR}/bsp/uart_driver.c
+ ${CORE_V_MCU_DIR}/bsp/i2c_master.c
+ ${CORE_V_MCU_DIR}/bsp/temp_sensor.c
+ ${CORE_V_MCU_DIR}/bsp/system_core_v_mcu.c
+ ${CORE_V_MCU_DIR}/bsp/string.c
+ ${CORE_V_MCU_DIR}/demo_threadx.c
+)
+
+add_executable(demo_threadx ${SRCS})
+set_target_properties(demo_threadx PROPERTIES OUTPUT_NAME "demo_threadx.elf")
+
+target_include_directories(demo_threadx PRIVATE
+ ${CORE_V_MCU_DIR}/include
+ ${THREADX_ROOT}/common/inc
+ ${THREADX_ROOT}/ports/risc-v32/gnu/inc
+)
+
+target_link_libraries(demo_threadx PRIVATE threadx gcc)
+
+target_link_options(demo_threadx PRIVATE
+ -T${CORE_V_MCU_DIR}/link.ld
+ -nostartfiles
+ -nodefaultlibs
+ -Wl,--gc-sections
+ -Wl,-Map=${CMAKE_CURRENT_BINARY_DIR}/demo_threadx.map
+)
+
+target_compile_options(demo_threadx PRIVATE
+ -Os
+ -g
+ -Wall
+ -Wextra
+ -ffreestanding
+ -ffunction-sections
+ -fdata-sections
+ -DTX_INCLUDE_USER_DEFINE_FILE
+)
+
+add_custom_command(TARGET demo_threadx POST_BUILD
+ COMMAND ${OBJCOPY} -O binary $<TARGET_FILE:demo_threadx> ${CMAKE_CURRENT_BINARY_DIR}/demo_threadx.bin
+ COMMENT "Generating demo_threadx.bin"
+)
diff --git a/ports/risc-v32/gnu/example_build/core_v_mcu/README.md b/ports/risc-v32/gnu/example_build/core_v_mcu/README.md
new file mode 100644
index 00000000..41f45dab
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/core_v_mcu/README.md
@@ -0,0 +1,504 @@
+# ThreadX Port for CORE-V MCU
+
+This directory contains the Board Support Package (BSP) and demo application for
+running Eclipse ThreadX on the
+[CORE-V MCU](https://github.com/openhwgroup/core-v-mcu) — an open-source 32-bit
+RISC-V SoC based on the CV32E40P core (RV32IMC). The reference hardware is a
+[Digilent Nexys A7](https://digilent.com/reference/programmable-logic/nexys-a7/start)
+FPGA board running the synthesised CORE-V MCU bitstream.
+
+---
+
+## Table of Contents
+
+1. [Hardware overview](#1-hardware-overview)
+2. [Software prerequisites](#2-software-prerequisites)
+3. [Directory structure](#3-directory-structure)
+4. [Architecture notes](#4-architecture-notes)
+5. [Memory map](#5-memory-map)
+6. [Building](#6-building)
+7. [Flashing and debugging](#7-flashing-and-debugging)
+8. [BSP API reference](#8-bsp-api-reference)
+9. [Unit tests](#9-unit-tests)
+10. [Adapting to a different board](#10-adapting-to-a-different-board)
+11. [Setup and deployment scripts](#11-setup-and-deployment-scripts)
+
+---
+
+## 1. Hardware overview
+
+| Item | Detail |
+|------|--------|
+| CPU | CV32E40P — RV32IMC, in-order, 4-stage pipeline |
+| ISA | `rv32imc_zicsr` (integer, compressed, multiply/divide, CSR instructions) |
+| L2 SRAM | 512 KB at `0x1c000000` (code + data + stack) |
+| Interrupt controller | PULP APB interrupt controller (32 lines, **not** a standard CLINT) |
+| Timer | PULP FC Timer at `0x1A10B000`, 32 kHz reference clock |
+| UART | UDMA UART channel 0 at `0x1A102080` |
+| GPIO | PULP GPIO controller at `0x1A101000` |
+| FLL | Frequency Locked Loop — 5 MHz input (FPGA), 50 MHz output |
+| Debug probe | [Ashling Opella LD](https://www.ashling.com/ashling-opella-ld/) via JTAG |
+
+The CV32E40P does **not** implement the standard RISC-V CLINT (`mip`/`mie` per-source
+masking). Individual interrupt lines are enabled and cleared through PULP APB
+registers, and `mstatus.MIE` (bit 3) acts as the single global enable.
+
+---
+
+## 2. Software prerequisites
+
+Run `install_deps.sh` to install all dependencies in one step:
+
+```bash
+bash install_deps.sh
+```
+
+Or install individually:
+
+| Tool | Minimum version | Notes |
+|------|----------------|-------|
+| `riscv-none-elf-gcc` | 15.x | xPack riscv-none-elf-gcc — run `bash install_deps.sh` |
+| CMake | 3.15 | `apt install cmake` |
+| Ninja | 1.10 | `apt install ninja-build` |
+| OpenOCD | 0.12 | `apt install openocd` |
+| GDB | any multiarch | `apt install gdb-multiarch` |
+
+> **Note:** Use the [xPack riscv-none-elf-gcc](https://github.com/xpack-dev-tools/riscv-none-elf-gcc-xpack)
+> toolchain — a cross-platform (Linux/macOS/Windows) multilib bare-metal
+> toolchain analogous to `arm-none-eabi-gcc`. Its multilib includes
+> rv32imc/ilp32 (soft-float) libgcc required for the CV32E40P. Do **not**
+> use `riscv32-unknown-elf-gcc` (riscv-collab) — that release is built
+> `--disable-multilib --with-abi=ilp32d` and cannot link soft-float objects.
+
+---
+
+## 3. Directory structure
+
+```
+core_v_mcu/
+├── bsp/ Board Support Package
+│ ├── fll.c Frequency Locked Loop driver
+│ ├── gpio.c GPIO driver
+│ ├── irq.c PULP APB interrupt controller driver
+│ ├── string.c Freestanding memset/memcpy shim
+│ ├── system_core_v_mcu.c Top-level BSP init and ISR dispatcher
+│ ├── timer_irq.c PULP FC Timer driver
+│ └── uart_driver.c UDMA UART driver
+├── include/ BSP headers
+│ ├── bits.h Bit-manipulation macros
+│ ├── csr.h CSR access macros
+│ ├── fll.h
+│ ├── gpio.h
+│ ├── io.h MMIO read/write inlines
+│ ├── irq.h
+│ ├── memory_map.h Peripheral base addresses
+│ ├── properties.h Clock/frequency constants
+│ ├── pulp_mem_map.h PULP peripheral register offsets
+│ ├── string.h Freestanding string shim header
+│ ├── system_core_v_mcu.h
+│ ├── timer.h
+│ ├── timer_irq.h
+│ ├── tx_user.h ThreadX compile-time configuration
+│ └── uart_driver.h
+├── tests/ Host-compiled unit tests (no cross-compiler needed)
+│ ├── mock/
+│ │ ├── mmio_mock.c Replaces io.h MMIO inlines with a software map
+│ │ └── mmio_mock.h
+│ ├── test_irq.c Tests for irq.c
+│ ├── test_timer.c Tests for timer_irq.c
+│ └── CMakeLists.txt
+├── crt0.S C runtime startup (clear BSS, call main)
+├── vectors.S 32-entry vectored interrupt table
+├── tx_initialize_low_level.S ThreadX platform init (mtvec, stack pointer)
+├── link.ld Linker script
+├── demo_threadx.c Demo application (blinky + UART heartbeat)
+├── CMakeLists.txt Build definition
+├── build.sh One-shot build script
+├── gdb_init GDB startup script
+└── openocd-nexys-Ashling-Opella-LD.cfg OpenOCD configuration
+```
+
+The ThreadX kernel sources are **not** duplicated here; they are referenced from the
+repository root via `THREADX_ROOT`.
+
+---
+
+## 4. Architecture notes
+
+### 4.1 Interrupt model
+
+The CV32E40P uses a PULP/PULPissimo-style interrupt controller rather than the
+standard RISC-V CLINT. Key differences:
+
+- **32 direct interrupt lines** connect to the core. `mcause` for an external
+ interrupt is `0x80000000 | N`, where `N` (bits 4–0) is the line number.
+- **Per-line masking** is done through APB registers in the PULP IRQ controller
+ (`IRQ_REG_MASK_SET_OFFSET` / `IRQ_REG_MASK_CLEAR_OFFSET`) — not through the `mie`
+ CSR.
+- **Global enable** is `mstatus.MIE` (bit 3). ThreadX manages this itself: the
+ scheduler sets MIE just before entering the WFI idle loop in
+ `_tx_thread_schedule`.
+
+ISR dispatch flow:
+
+```
+CPU takes interrupt
+ → PC jumps to mtvec base (vectored mode, bit 0 = 1)
+ → vectors.S entry for line N → tx_trap_entry
+ → tx_trap_entry saves registers, calls _tx_thread_context_save
+ → tx_trap_handler(mcause, mepc, mtval) dispatches via isr_table[mcause & 0x1f]
+ → ThreadX timer ISR (line 10) calls _tx_timer_interrupt
+ → _tx_thread_context_restore (never returns to tx_trap_entry)
+```
+
+### 4.2 `mtvec` alignment
+
+The CV32E40P requires the vector table base to be **256-byte aligned**. The linker
+script places `.vectors` at `0x1c000800`, which satisfies this. `mtvec` is written
+as `0x1c000801` (base + vectored-mode bit).
+
+### 4.3 ThreadX timer tick
+
+- Reference clock: 32 768 Hz (ARCHI_REF_CLOCK)
+- Tick rate: 100 Hz → compare value 327 (~0.1 % drift)
+- The timer auto-reloads on compare match (cycle mode)
+- `_tx_timer_interrupt` is registered in `isr_table[10]` during `system_init()`
+
+### 4.4 FLL and clock
+
+On the Nexys A7 FPGA, the FLL input is 5 MHz (`ARCHI_FPGA_FREQUENCY`). After
+`pi_fll_init()` the system clock is 50 MHz (`DEFAULT_SYSTEM_CLOCK`). The UART baud
+rate divisor is computed from this 50 MHz clock.
+
+### 4.5 Freestanding build
+
+The port builds with `-ffreestanding -nodefaultlibs` — no newlib or libc.
+`bsp/string.c` provides minimal `memset` and `memcpy` for ThreadX internal use.
+
+---
+
+## 5. Memory map
+
+| Region | Start | Size | Contents |
+|--------|-------|------|----------|
+| Vector table | `0x1c000800` | 128 B | 32 × 4-byte jump instructions to `tx_trap_entry` |
+| `.text` (boot address) | `0x1c000880` | — | Code + read-only data |
+| `.data` | after `.text` | — | Initialised data |
+| `.bss` | after `.data` | — | Zero-initialised data |
+| ThreadX free memory | after `.bss` | — | Heap pools, thread stacks |
+| Stack | top of L2 | 8 KB | Initial/idle stack grows downward from `0x1c080000` |
+
+L2 total: 512 KB (`0x1c000000`–`0x1c07ffff`).
+
+Peripheral base addresses (see `include/memory_map.h`):
+
+| Peripheral | Address |
+|------------|---------|
+| PULP IRQ controller | `0x1A109800` |
+| PULP FC Timer | `0x1A10B000` |
+| PULP GPIO | `0x1A101000` |
+| UDMA UART ch0 | `0x1A102080` |
+| FLL | `0x1A100000` |
+
+---
+
+## 6. Building
+
+### Quick start
+
+```bash
+cd ports/risc-v32/gnu/example_build/core_v_mcu
+bash build.sh
+```
+
+The script configures and builds with Ninja. The output is:
+
+```
+build/demo_threadx.elf — ELF with debug symbols
+build/demo_threadx.bin — raw binary for flashing
+build/demo_threadx.map — linker map
+```
+
+### Manual CMake invocation
+
+```bash
+THREADX_ROOT=$(realpath ../../../../..)
+cmake -B build -G Ninja \
+ -DCMAKE_TOOLCHAIN_FILE="${THREADX_ROOT}/cmake/riscv-none-elf-rv32imc.cmake" \
+ .
+cmake --build build
+```
+
+### Toolchain file
+
+`${THREADX_ROOT}/cmake/riscv-none-elf-rv32imc.cmake` sets:
+
+```
+CMAKE_C_COMPILER riscv-none-elf-gcc
+CMAKE_ASM_COMPILER riscv-none-elf-gcc
+CMAKE_OBJCOPY riscv-none-elf-objcopy
+CMAKE_C_FLAGS -march=rv32imc_zicsr -mabi=ilp32 -mcmodel=medlow
+```
+
+---
+
+## 7. Flashing and debugging
+
+### 7.1 Quick deploy (recommended)
+
+Use `deploy.sh` to automate the entire OpenOCD + GDB workflow in one command:
+
+```bash
+# Flash and run (leave target running after GDB exits):
+bash deploy.sh
+
+# Build first, then flash and run:
+bash deploy.sh --build
+
+# Flash and stop at main for interactive debugging:
+bash deploy.sh --debug
+
+# Override the ELF or the OpenOCD config:
+bash deploy.sh --elf path/to/my.elf --openocd-cfg path/to/other.cfg
+```
+
+> **First time only:** run `bash ../../../../../samplex-fd/OpenHW/scripts/setup_opella.sh`
+> (or the equivalent path) once to install the udev rule for the Opella LD.
+> See [Section 11](#11-setup-and-deployment-scripts) for details.
+
+### 7.2 Manual procedure
+
+#### Connect hardware
+
+1. Connect the Ashling Opella LD to the Nexys A7 JTAG header.
+2. Connect the USB-UART cable (CORE-V MCU UART0 mapped to USB micro-B on Nexys A7).
+3. Power on the board.
+
+#### Start OpenOCD
+
+```bash
+openocd -f openocd-nexys-Ashling-Opella-LD.cfg
+```
+
+Expected output ends with `Ready for Remote Connections`. OpenOCD listens on
+port 3333 (GDB) and 4444 (telnet).
+
+#### Flash and run with GDB
+
+In a second terminal:
+
+```bash
+riscv-none-elf-gdb --command=gdb_init
+```
+
+`gdb_init` connects to OpenOCD, loads the ELF, sets a breakpoint at `main`, and
+continues. You should see the LED on pin 5 blinking at 1 Hz and the UART printing
+`ThreadX on CORE-V MCU` followed by periodic `.` heartbeats at 115 200 baud.
+
+#### UART monitor
+
+```bash
+minicom -b 115200 -D /dev/ttyUSBx # adjust device as needed
+```
+
+#### Useful GDB commands
+
+```
+(gdb) info threads — list ThreadX threads
+(gdb) thread 2 — switch to a thread
+(gdb) bt — backtrace
+(gdb) monitor halt — halt via OpenOCD
+(gdb) monitor resume — resume via OpenOCD
+```
+
+---
+
+## 8. BSP API reference
+
+### `system_core_v_mcu.h`
+
+```c
+void system_init(void);
+```
+Initialises FLL, UART, IRQ controller, and FC Timer. Must be called once from
+`main()` before `tx_kernel_enter()`.
+
+### `uart_driver.h`
+
+```c
+int uart_init(uint8_t uart_id, uint32_t baudrate, uint32_t periph_freq);
+void uart_write_byte(uint8_t uart_id, uint8_t c);
+int uart_read_byte(uint8_t uart_id); /* returns -1 if no data */
+void uart_write_str(uint8_t uart_id, const char *str);
+void uart_write_buf(uint8_t uart_id, const uint8_t *buf, uint32_t len);
+```
+
+`uart_id` 0 is the console UART. `uart_read_byte` is non-blocking.
+
+### `gpio.h`
+
+```c
+void gpio_init(void);
+void gpio_set_output(uint32_t pin_mask); /* configure pins as outputs */
+void gpio_set(uint32_t pin_mask); /* drive high */
+void gpio_clear(uint32_t pin_mask); /* drive low */
+void gpio_toggle(uint32_t pin_mask);
+```
+
+`pin_mask` is a bitmask where bit N corresponds to GPIO pin N.
+
+### `irq.h`
+
+```c
+void pulp_irq_init(void);
+void irq_enable(uint32_t irq_id);
+void irq_disable(uint32_t irq_id);
+void irq_clint_enable(void); /* set mstatus.MIE */
+void irq_clint_disable(void); /* clear mstatus.MIE */
+```
+
+`irq_id` values are defined as `IRQ_FC_EVT_*` in `irq.h`. The timer uses
+`IRQ_FC_EVT_TIMER0_LO` (= 10).
+
+### `timer_irq.h`
+
+```c
+void timer_irq_init(uint32_t ticks);
+void timer_irq_set_timeout(uint32_t ticks, bool enable);
+uint32_t timer_irq_cycle_get_32(void);
+```
+
+### Registering a custom ISR
+
+Add a function pointer to `isr_table` before calling `tx_kernel_enter()`:
+
+```c
+extern void (*isr_table[32])(void);
+
+static void my_irq_handler(void) { /* ... */ }
+
+/* In main() or tx_application_define(), before enabling the line: */
+isr_table[MY_IRQ_LINE] = my_irq_handler;
+irq_enable(MY_IRQ_LINE);
+```
+
+---
+
+## 9. Unit tests
+
+The tests compile for the **host** (no cross-compiler required) using a software
+MMIO mock that backs the peripheral register space with a `uint32_t` array.
+
+```bash
+cd tests
+cmake -B build -G Ninja
+cmake --build build
+./build/tx_core_v_mcu_tests
+```
+
+Expected output:
+
+```
+irq_tests: PASS
+timer_tests: PASS
+```
+
+The mock is enabled by defining `CORE_V_MCU_MMIO_MOCK` before including `io.h`.
+`mmio_mock.c` provides `mock_reg_read` / `mock_reg_write` helpers for assertions.
+
+---
+
+## 10. Adapting to a different board
+
+To port to a different CORE-V MCU carrier board:
+
+1. **Clock frequencies** — update `ARCHI_REF_CLOCK`, `ARCHI_FPGA_FREQUENCY`, and
+ `DEFAULT_SYSTEM_CLOCK` in `include/properties.h`.
+
+2. **Memory layout** — adjust `ORIGIN` and `LENGTH` for `L2` in `link.ld` and
+ update `__boot_address` if the boot ROM routes differently.
+
+3. **mtvec base** — if `.vectors` moves, update the address in `link.ld`. The
+ 256-byte alignment requirement is a hard CV32E40P constraint.
+
+4. **Debug probe** — replace `openocd-nexys-Ashling-Opella-LD.cfg` with a config
+ matching your probe (e.g., `interface jlink` for a J-Link).
+
+5. **GPIO pin** — `DEMO_LED_PIN` in `demo_threadx.c` selects the LED. Change to
+ match your board's LED assignment.
+
+---
+
+## 11. Setup and deployment scripts
+
+### 11.1 `setup_opella.sh` — one-time host setup
+
+Located in `samplex-fd/OpenHW/scripts/setup_opella.sh`. Run **once per machine**
+to grant user-space access to the Ashling Opella LD USB device.
+
+```bash
+# Native Linux:
+sudo bash samplex-fd/OpenHW/scripts/setup_opella.sh
+
+# WSL (also attempts usbipd-win USB forwarding):
+bash samplex-fd/OpenHW/scripts/setup_opella.sh
+```
+
+What it does:
+
+| Step | Native Linux | WSL |
+|------|-------------|-----|
+| Write `/etc/udev/rules.d/99-ashling-opella.rules` | ✓ | ✓ |
+| Add user to `plugdev` group | ✓ | ✓ |
+| Reload udev | ✓ | ✓ |
+| `usbipd.exe attach --wsl` (v3: `wsl attach`) | — | automatic |
+
+> **WSL — one-time bind (Windows admin required):**
+> Before the first attach, run this once in an **elevated (Run as Administrator) PowerShell** on the Windows host:
+> ```powershell
+> usbipd bind --hardware-id 0b6b:0040
+> ```
+> After binding, `setup_opella.sh` handles subsequent attaches automatically.
+
+> **WSL — if attach fails with "not USBIP capable" (usbipd-win v3 + Ubuntu 22.04+):**
+> Ubuntu 22.04 and 24.04 enable systemd by default. usbipd-win **v3** has a
+> known bug where it incorrectly reports the kernel as not USBIP capable when
+> systemd is running. **Upgrade usbipd-win to v4 or later:**
+> <https://github.com/dorssel/usbipd-win/releases>
+>
+> If you are on an older WSL2 kernel without USBIP support, first run:
+> ```powershell
+> wsl --update
+> wsl --shutdown
+> ```
+
+After running, **re-plug the probe**. On WSL, re-run the script after each WSL restart.
+
+### 11.2 `deploy.sh` — flash and run
+
+```bash
+# Flash and run (leave target running):
+bash deploy.sh
+
+# Build first, then flash:
+bash deploy.sh --build
+
+# Flash and stop at main for interactive debug session:
+bash deploy.sh --debug
+```
+
+The script starts OpenOCD in the background, waits for it to become ready,
+then drives `riscv-none-elf-gdb` to load the ELF. OpenOCD is always
+stopped on exit (including Ctrl-C).
+
+---
+
+## Licence
+
+New files in this directory are © 2026 Eclipse ThreadX contributors, MIT.
+
+Files derived from
+[core-v-freertos](https://github.com/openhwgroup/core-v-freertos) retain their
+original Apache-2.0 copyright notices (ETH Zurich, GreenWaves Technologies, Wind
+River Systems, SiFive Inc.) and are dual-licensed `Apache-2.0 AND MIT` (or
+`(Apache-2.0 OR BSD-2-Clause) AND MIT` for `crt0.S`). See individual file headers.
diff --git a/ports/risc-v32/gnu/example_build/core_v_mcu/bsp/fll.c b/ports/risc-v32/gnu/example_build/core_v_mcu/bsp/fll.c
new file mode 100644
index 00000000..46dce857
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/core_v_mcu/bsp/fll.c
@@ -0,0 +1,209 @@
+/***************************************************************************/
+/* Copyright 2020 GreenWaves Technologies
+ * Copyright (C) 2026 Eclipse ThreadX contributors
+ *
+ * Derived from core-v-freertos (https://github.com/openhwgroup/core-v-freertos)
+ * Original work licensed under the Apache License, Version 2.0.
+ * See https://www.apache.org/licenses/LICENSE-2.0
+ * Modifications licensed under MIT (https://opensource.org/licenses/MIT).
+ *
+ * AI Disclosure: Some portions generated or modified by Copilot (Sonnet 4.6).
+ *
+ * SPDX-License-Identifier: Apache-2.0 AND MIT
+ ***************************************************************************/
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "bits.h"
+#include "csr.h"
+#include "fll.h"
+#include "irq.h"
+#include "properties.h"
+#include "system_core_v_mcu.h"
+
+#define FLL_LOG2_MAXDCO 29U
+#define FLL_LOG2_REFCLK ARCHI_REF_CLOCK_LOG2
+#define FLL_LOG2_MAXM (FLL_LOG2_MAXDCO - FLL_LOG2_REFCLK)
+
+static volatile uint32_t flls_frequency[FLL_NUM];
+
+static uint32_t fll_max_u32(uint32_t a, uint32_t b)
+{
+ return (a > b) ? a : b;
+}
+
+static uint32_t fll_fl1(uint32_t value)
+{
+ return (uint32_t)(31U - (uint32_t)__builtin_clz(value));
+}
+
+static uint32_t fll_get_mult_div_from_frequency(uint32_t freq, uint32_t *mult,
+ uint32_t *div)
+{
+ uint32_t fref = FLL_REF_CLK;
+ uint32_t log2_m;
+ uint32_t d;
+ uint32_t m;
+ uint32_t fres;
+
+ if ((freq == 0U) || (mult == NULL) || (div == NULL))
+ {
+ if (mult != NULL)
+ {
+ *mult = 0U;
+ }
+ if (div != NULL)
+ {
+ *div = 0U;
+ }
+ return 0U;
+ }
+
+ if (freq > fref)
+ {
+ log2_m = fll_fl1(freq) - fll_fl1(fref);
+ }
+ else
+ {
+ log2_m = 0U;
+ }
+
+ if (FLL_LOG2_MAXM > log2_m)
+ {
+ d = fll_max_u32(1U, (FLL_LOG2_MAXM - log2_m) >> 1U);
+ }
+ else
+ {
+ d = 1U;
+ }
+
+ m = (freq << d) / fref;
+ fres = (fref * m + (1UL << (d - 1U))) >> d;
+
+ *mult = m;
+ *div = d + 1U;
+ return fres;
+}
+
+static uint32_t fll_get_frequency_from_mult_div(uint32_t mult, uint32_t div)
+{
+ uint32_t fref = FLL_REF_CLK;
+
+ if (div == 0U)
+ {
+ return fref * mult;
+ }
+
+ return (fref * mult) >> (div - 1U);
+}
+
+int pi_fll_set_frequency(fll_type_t which_fll, uint32_t frequency, int check)
+{
+ uint32_t mult;
+ uint32_t div;
+ uint32_t reg1;
+ uint32_t saved_irq;
+
+ (void)check;
+
+ if ((uint32_t)which_fll >= FLL_NUM)
+ {
+ return -1;
+ }
+
+ if (frequency == 0U)
+ {
+ return -1;
+ }
+
+ saved_irq = irq_clint_disable();
+ (void)fll_get_mult_div_from_frequency(frequency, &mult, &div);
+
+ reg1 = FLL_CTRL[which_fll].FLL_CONF1;
+ reg1 &= ~FLL_CTRL_CONF1_MULTI_FACTOR_MASK;
+ reg1 |= REG_SET(FLL_CTRL_CONF1_MULTI_FACTOR, mult);
+ reg1 &= ~FLL_CTRL_CONF1_CLK_OUT_DIV_MASK;
+ reg1 |= REG_SET(FLL_CTRL_CONF1_CLK_OUT_DIV, div);
+ FLL_CTRL[which_fll].FLL_CONF1 = reg1;
+
+ flls_frequency[which_fll] = frequency;
+ if (which_fll == FLL_SOC)
+ {
+ system_core_clock = frequency;
+ }
+
+ if ((saved_irq & MSTATUS_IE) != 0U)
+ {
+ (void)irq_clint_enable();
+ }
+
+ return (int)frequency;
+}
+
+void pi_fll_init(fll_type_t which_fll, uint32_t ret_state)
+{
+ uint32_t reg1;
+
+ if ((uint32_t)which_fll >= FLL_NUM)
+ {
+ return;
+ }
+
+ if (ret_state != 0U)
+ {
+ (void)pi_fll_get_frequency(which_fll, 1U);
+ return;
+ }
+
+ reg1 = FLL_CTRL[which_fll].FLL_CONF1;
+ if (REG_GET(FLL_CTRL_CONF1_MODE, reg1) == 0U)
+ {
+ uint32_t reg2 = FLL_CTRL[which_fll].FLL_CONF2;
+ uint32_t regint = FLL_CTRL[which_fll].FLL_INTEGRATOR;
+
+ reg2 &= ~FLL_CTRL_CONF2_ASSERT_CYCLES_MASK;
+ reg2 |= REG_SET(FLL_CTRL_CONF2_ASSERT_CYCLES, 0x6U);
+ reg2 &= ~FLL_CTRL_CONF2_LOCK_TOLERANCE_MASK;
+ reg2 |= REG_SET(FLL_CTRL_CONF2_LOCK_TOLERANCE, 0x50U);
+ FLL_CTRL[which_fll].FLL_CONF2 = reg2;
+
+ regint &= ~FLL_CTRL_INTEGRATOR_INT_PART_MASK;
+ regint |= REG_SET(FLL_CTRL_INTEGRATOR_INT_PART, 332U);
+ FLL_CTRL[which_fll].FLL_INTEGRATOR = regint;
+
+ reg1 &= ~FLL_CTRL_CONF1_OUTPUT_LOCK_EN_MASK;
+ reg1 |= REG_SET(FLL_CTRL_CONF1_OUTPUT_LOCK_EN, 1U);
+ reg1 &= ~FLL_CTRL_CONF1_MODE_MASK;
+ reg1 |= REG_SET(FLL_CTRL_CONF1_MODE, 1U);
+ FLL_CTRL[which_fll].FLL_CONF1 = reg1;
+ }
+
+ if (flls_frequency[which_fll] != 0U)
+ {
+ (void)pi_fll_set_frequency(which_fll, flls_frequency[which_fll], 0);
+ }
+ else
+ {
+ flls_frequency[which_fll] = fll_get_frequency_from_mult_div(
+ REG_GET(FLL_CTRL_CONF1_MULTI_FACTOR, reg1),
+ REG_GET(FLL_CTRL_CONF1_CLK_OUT_DIV, reg1));
+ }
+}
+
+int pi_fll_get_frequency(fll_type_t which_fll, uint8_t real)
+{
+ if ((uint32_t)which_fll >= FLL_NUM)
+ {
+ return -1;
+ }
+
+ if (real != 0U)
+ {
+ flls_frequency[which_fll] = fll_get_frequency_from_mult_div(
+ FLL_CTRL[which_fll].FLL_STATUS,
+ REG_GET(FLL_CTRL_CONF1_CLK_OUT_DIV, FLL_CTRL[which_fll].FLL_CONF1));
+ }
+
+ return (int)flls_frequency[which_fll];
+}
diff --git a/ports/risc-v32/gnu/example_build/core_v_mcu/bsp/gpio.c b/ports/risc-v32/gnu/example_build/core_v_mcu/bsp/gpio.c
new file mode 100644
index 00000000..ddeba5b5
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/core_v_mcu/bsp/gpio.c
@@ -0,0 +1,128 @@
+/***************************************************************************
+ * 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 Copilot (Sonnet 4.6).
+ * 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 <stdint.h>
+
+#include "pulp_mem_map.h"
+#include "io.h"
+#include "gpio.h"
+
+/* APB SOC CTRL pad-mux interface (apb_soc_ctrl v1.0.0):
+ * Each IO pad has a dedicated 32-bit register at base + 0x400 + pad_index*4.
+ * PWDATA[1:0] = mux function (0=sys, 1=perio, 2=apbio/GPIO, 3=fpgaio)
+ * PWDATA[13:8] = pad config (drive/pull, optional)
+ *
+ * FUNC_C (2) routes the pad to the GPIO (apbio) controller.
+ * For the standard GPIO pins: io_pad = gpio_pin + GPIO_IO_PAD_OFFSET (=7).
+ * Verified in pad_control.sv: apbio[N] ↔ io_pad[N+7] when pad_mux[N+7]==2. */
+#define APB_SOC_PADMUX_BASE 0x400U
+#define PINMUX_FUNC_C 2U
+#define GPIO_IO_PAD_OFFSET 7U
+
+static void gpio_pinmux_set_func_c(uint32_t pin_mask)
+{
+ for (uint32_t gpio_pin = 0U; gpio_pin < 32U; gpio_pin++)
+ {
+ if ((pin_mask & (1UL << gpio_pin)) == 0U)
+ {
+ continue;
+ }
+ uint32_t io_pad = gpio_pin + GPIO_IO_PAD_OFFSET;
+ uintptr_t reg = PULP_APB_SOC_CTRL_ADDR + APB_SOC_PADMUX_BASE
+ + io_pad * 4U;
+ writew(PINMUX_FUNC_C, reg);
+ }
+}
+
+/* Iterate over every set bit in pin_mask and call writew(pin_number, addr).
+ * The apb_gpiov2 SETGPIO/CLRGPIO/TOGGPIO/SETDIR registers are all
+ * pin-indexed: the written word encodes a pin number, not a bitmask. */
+static void gpio_foreach_pin(uint32_t pin_mask, uintptr_t reg_addr, uint32_t extra_bits)
+{
+ for (uint32_t pin = 0U; pin < 32U; pin++)
+ {
+ if ((pin_mask & (1UL << pin)) != 0U)
+ {
+ writew(pin | extra_bits, reg_addr);
+ }
+ }
+}
+
+void gpio_init(void)
+{
+ /* GPIO clocking is handled by platform boot code on CORE-V MCU. */
+}
+
+void gpio_set_output(uint32_t pin_mask)
+{
+ /* Route physical pads to the GPIO controller via the pad-mux (FUNC_C). */
+ gpio_pinmux_set_func_c(pin_mask);
+
+ /* Configure each pin as push-pull output via SETDIR. */
+ gpio_foreach_pin(pin_mask,
+ (uintptr_t)(PULP_GPIO_ADDR + GPIO_SETDIR_OFFSET),
+ GPIO_DIR_OUTPUT);
+}
+
+void gpio_toggle(uint32_t pin_mask)
+{
+ gpio_foreach_pin(pin_mask,
+ (uintptr_t)(PULP_GPIO_ADDR + GPIO_TOGGPIO_OFFSET),
+ 0U);
+}
+
+void gpio_set(uint32_t pin_mask)
+{
+ gpio_foreach_pin(pin_mask,
+ (uintptr_t)(PULP_GPIO_ADDR + GPIO_SETGPIO_OFFSET),
+ 0U);
+}
+
+void gpio_clear(uint32_t pin_mask)
+{
+ gpio_foreach_pin(pin_mask,
+ (uintptr_t)(PULP_GPIO_ADDR + GPIO_CLRGPIO_OFFSET),
+ 0U);
+}
+
+void gpio_setpinmux(uint8_t io_pad, uint8_t mux)
+{
+ uintptr_t reg = PULP_APB_SOC_CTRL_ADDR + APB_SOC_PADMUX_BASE
+ + (uintptr_t)io_pad * 4U;
+ writew((uint32_t)mux & 0x3U, reg);
+}
+
+uint8_t gpio_getpinmux(uint8_t io_pad)
+{
+ uintptr_t reg = PULP_APB_SOC_CTRL_ADDR + APB_SOC_PADMUX_BASE
+ + (uintptr_t)io_pad * 4U;
+ return (uint8_t)(readw(reg) & 0x3U);
+}
+
+void gpio_pin_set_dir(uint8_t pin, uint8_t dir)
+{
+ uint32_t val = (uint32_t)pin;
+ if (dir != 0U)
+ {
+ val |= GPIO_DIR_OUTPUT;
+ }
+ writew(val, (uintptr_t)(PULP_GPIO_ADDR + GPIO_SETDIR_OFFSET));
+}
+
+uint32_t gpio_pin_read_status(uint8_t pin)
+{
+ writew((uint32_t)pin, (uintptr_t)(PULP_GPIO_ADDR + GPIO_SETSEL_OFFSET));
+ return readw((uintptr_t)(PULP_GPIO_ADDR + GPIO_RDSTAT_OFFSET));
+}
diff --git a/ports/risc-v32/gnu/example_build/core_v_mcu/bsp/i2c_master.c b/ports/risc-v32/gnu/example_build/core_v_mcu/bsp/i2c_master.c
new file mode 100644
index 00000000..eb1461b9
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/core_v_mcu/bsp/i2c_master.c
@@ -0,0 +1,153 @@
+/***************************************************************************
+ * 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 Copilot (Sonnet 4.6).
+ * 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 <stddef.h>
+#include <stdint.h>
+
+#include "bits.h"
+#include "i2c_master.h"
+#include "io.h"
+#include "pulp_mem_map.h"
+
+#define UDMA_I2C_RX_SADDR_OFFSET 0x00U
+#define UDMA_I2C_RX_SIZE_OFFSET 0x04U
+#define UDMA_I2C_RX_CFG_OFFSET 0x08U
+#define UDMA_I2C_TX_SADDR_OFFSET 0x10U
+#define UDMA_I2C_TX_SIZE_OFFSET 0x14U
+#define UDMA_I2C_TX_CFG_OFFSET 0x18U
+#define UDMA_I2C_STATUS_OFFSET 0x20U
+#define UDMA_I2C_SETUP_OFFSET 0x24U
+
+#define UDMA_CTRL_CG_OFFSET 0x00U
+#define UDMA_CFG_EN_BIT BIT(4)
+#define UDMA_CFG_PENDING_BIT BIT(5)
+#define UDMA_I2C_STATUS_AL_BIT BIT(1)
+#define UDMA_I2C_SETUP_RESET_BIT BIT(0)
+#define I2C_MASTER_COUNT 2U
+#define I2C_TIMEOUT_ITERATIONS 1000000U
+
+#define I2C_CMD_CFG 0xE0U
+#define I2C_CMD_START 0x00U
+#define I2C_CMD_STOP 0x20U
+#define I2C_CMD_RD_ACK 0x40U
+#define I2C_CMD_RD_NACK 0x60U
+#define I2C_CMD_WR 0x80U
+#define I2C_CMD_WAIT 0xA0U
+#define I2C_CMD_RPT 0xC0U
+
+static uint8_t s_cmd_buf[32];
+static uint8_t s_clkdiv[I2C_MASTER_COUNT];
+
+static uintptr_t i2c_master_base(uint8_t id)
+{
+ return (id == 0U) ? (uintptr_t)UDMA_CH_ADDR_I2CM0 : (uintptr_t)UDMA_CH_ADDR_I2CM1;
+}
+
+int i2c_master_init(uint8_t id, uint32_t i2c_freq, uint32_t periph_freq)
+{
+ uint32_t cg;
+ uint32_t clk_div;
+ uintptr_t base;
+
+ if ((id >= I2C_MASTER_COUNT) || (i2c_freq == 0U) || (periph_freq == 0U))
+ {
+ return -1;
+ }
+
+ cg = readw((uintptr_t)(UDMA_CH_ADDR_CTRL + UDMA_CTRL_CG_OFFSET));
+ cg |= (id == 0U) ? UDMA_CTRL_I2CM0_CLKEN : UDMA_CTRL_I2CM1_CLKEN;
+ writew(cg, (uintptr_t)(UDMA_CH_ADDR_CTRL + UDMA_CTRL_CG_OFFSET));
+
+ clk_div = periph_freq / i2c_freq;
+ if (clk_div == 0U)
+ {
+ clk_div = 1U;
+ }
+ else if (clk_div > 255U)
+ {
+ clk_div = 255U;
+ }
+ s_clkdiv[id] = (uint8_t)clk_div;
+
+ base = i2c_master_base(id);
+ writew(UDMA_I2C_SETUP_RESET_BIT, base + UDMA_I2C_SETUP_OFFSET);
+ writew(0U, base + UDMA_I2C_SETUP_OFFSET);
+ writew(0U, base + UDMA_I2C_RX_CFG_OFFSET);
+ writew(0U, base + UDMA_I2C_TX_CFG_OFFSET);
+
+ return 0;
+}
+
+int i2c_master_read_reg(uint8_t id, uint8_t dev_addr7, uint8_t reg_addr,
+ uint8_t *buf, uint8_t len)
+{
+ uintptr_t base;
+ uint32_t timeout;
+ uint32_t cmd_len;
+ uint32_t status;
+
+ if ((id >= I2C_MASTER_COUNT) || (dev_addr7 > 0x7FU) || (buf == NULL) || (len == 0U))
+ {
+ return -1;
+ }
+
+ cmd_len = 0U;
+ s_cmd_buf[cmd_len++] = I2C_CMD_CFG;
+ s_cmd_buf[cmd_len++] = 0U;
+ s_cmd_buf[cmd_len++] = s_clkdiv[id];
+ s_cmd_buf[cmd_len++] = I2C_CMD_START;
+ s_cmd_buf[cmd_len++] = I2C_CMD_WR;
+ s_cmd_buf[cmd_len++] = (uint8_t)(dev_addr7 << 1U);
+ s_cmd_buf[cmd_len++] = I2C_CMD_WR;
+ s_cmd_buf[cmd_len++] = reg_addr;
+ s_cmd_buf[cmd_len++] = I2C_CMD_START;
+ s_cmd_buf[cmd_len++] = I2C_CMD_WR;
+ s_cmd_buf[cmd_len++] = (uint8_t)((dev_addr7 << 1U) | 1U);
+ if (len > 1U)
+ {
+ s_cmd_buf[cmd_len++] = I2C_CMD_RPT;
+ s_cmd_buf[cmd_len++] = (uint8_t)(len - 1U);
+ s_cmd_buf[cmd_len++] = I2C_CMD_RD_ACK;
+ }
+ s_cmd_buf[cmd_len++] = I2C_CMD_RD_NACK;
+ s_cmd_buf[cmd_len++] = I2C_CMD_STOP;
+ s_cmd_buf[cmd_len++] = I2C_CMD_WAIT;
+ s_cmd_buf[cmd_len++] = 0U;
+
+ base = i2c_master_base(id);
+ writew((uint32_t)(uintptr_t)buf, base + UDMA_I2C_RX_SADDR_OFFSET);
+ writew((uint32_t)len, base + UDMA_I2C_RX_SIZE_OFFSET);
+ writew(UDMA_CFG_EN_BIT, base + UDMA_I2C_RX_CFG_OFFSET);
+
+ writew((uint32_t)(uintptr_t)s_cmd_buf, base + UDMA_I2C_TX_SADDR_OFFSET);
+ writew(cmd_len, base + UDMA_I2C_TX_SIZE_OFFSET);
+ writew(UDMA_CFG_EN_BIT, base + UDMA_I2C_TX_CFG_OFFSET);
+
+ for (timeout = 0U; timeout < I2C_TIMEOUT_ITERATIONS; ++timeout)
+ {
+ status = readw(base + UDMA_I2C_STATUS_OFFSET);
+ if ((status & UDMA_I2C_STATUS_AL_BIT) != 0U)
+ {
+ return -1;
+ }
+ if (((readw(base + UDMA_I2C_TX_CFG_OFFSET) & UDMA_CFG_PENDING_BIT) == 0U) &&
+ ((readw(base + UDMA_I2C_RX_CFG_OFFSET) & UDMA_CFG_PENDING_BIT) == 0U))
+ {
+ return 0;
+ }
+ }
+
+ return -1;
+}
diff --git a/ports/risc-v32/gnu/example_build/core_v_mcu/bsp/irq.c b/ports/risc-v32/gnu/example_build/core_v_mcu/bsp/irq.c
new file mode 100644
index 00000000..cb79cc06
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/core_v_mcu/bsp/irq.c
@@ -0,0 +1,50 @@
+/***************************************************************************/
+/* Copyright (C) 2019 ETH Zurich and University of Bologna
+ * Copyright (C) 2026 Eclipse ThreadX contributors
+ *
+ * Derived from core-v-freertos (https://github.com/openhwgroup/core-v-freertos)
+ * Original work licensed under the Apache License, Version 2.0.
+ * See https://www.apache.org/licenses/LICENSE-2.0
+ * Modifications licensed under MIT (https://opensource.org/licenses/MIT).
+ *
+ * AI Disclosure: Some portions generated or modified by Copilot (Sonnet 4.6).
+ *
+ * SPDX-License-Identifier: Apache-2.0 AND MIT
+ ***************************************************************************/
+
+#include <stdint.h>
+
+#include "pulp_mem_map.h"
+#include "io.h"
+#include "irq.h"
+#include "csr.h"
+
+void irq_mask(uint32_t mask)
+{
+ writew(mask, (uintptr_t)(PULP_FC_IRQ_ADDR + IRQ_REG_MASK_OFFSET));
+}
+
+void irq_enable(uint32_t mask)
+{
+ writew(mask, (uintptr_t)(PULP_FC_IRQ_ADDR + IRQ_REG_MASK_SET_OFFSET));
+}
+
+void irq_disable(uint32_t mask)
+{
+ writew(mask, (uintptr_t)(PULP_FC_IRQ_ADDR + IRQ_REG_MASK_CLEAR_OFFSET));
+}
+
+uint32_t irq_clint_disable(void)
+{
+ return (uint32_t)csr_read_clear(CSR_MSTATUS, MSTATUS_IE);
+}
+
+uint32_t irq_clint_enable(void)
+{
+ return (uint32_t)csr_read_set(CSR_MSTATUS, MSTATUS_IE);
+}
+
+void pulp_irq_init(void)
+{
+ irq_disable(0xFFFFFFFFUL);
+}
diff --git a/ports/risc-v32/gnu/example_build/core_v_mcu/bsp/string.c b/ports/risc-v32/gnu/example_build/core_v_mcu/bsp/string.c
new file mode 100644
index 00000000..7ff699e7
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/core_v_mcu/bsp/string.c
@@ -0,0 +1,64 @@
+/***************************************************************************
+ * 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 Copilot (Sonnet 4.6).
+ * 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 <stddef.h>
+#include <stdint.h>
+
+#include "string.h"
+
+void *memcpy(void *dest, const void *src, size_t n)
+{
+ size_t index;
+ uint8_t *dst = (uint8_t *)dest;
+ const uint8_t *source = (const uint8_t *)src;
+
+ for (index = 0U; index < n; ++index)
+ {
+ dst[index] = source[index];
+ }
+
+ return dest;
+}
+
+void *memset(void *dest, int c, size_t n)
+{
+ size_t index;
+ uint8_t *dst = (uint8_t *)dest;
+ uint8_t value = (uint8_t)c;
+
+ for (index = 0U; index < n; ++index)
+ {
+ dst[index] = value;
+ }
+
+ return dest;
+}
+
+int memcmp(const void *lhs, const void *rhs, size_t n)
+{
+ size_t index;
+ const uint8_t *left = (const uint8_t *)lhs;
+ const uint8_t *right = (const uint8_t *)rhs;
+
+ for (index = 0U; index < n; ++index)
+ {
+ if (left[index] != right[index])
+ {
+ return (left[index] < right[index]) ? -1 : 1;
+ }
+ }
+
+ return 0;
+}
diff --git a/ports/risc-v32/gnu/example_build/core_v_mcu/bsp/system_core_v_mcu.c b/ports/risc-v32/gnu/example_build/core_v_mcu/bsp/system_core_v_mcu.c
new file mode 100644
index 00000000..f142c95d
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/core_v_mcu/bsp/system_core_v_mcu.c
@@ -0,0 +1,132 @@
+/***************************************************************************
+ * 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 Copilot (Sonnet 4.6).
+ * 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 <stddef.h>
+#include <stdint.h>
+
+#include "csr.h"
+#include "fll.h"
+#include "gpio.h"
+#include "irq.h"
+#include "properties.h"
+#include "system_core_v_mcu.h"
+#include "timer_irq.h"
+#include "tx_api.h"
+#include "uart_driver.h"
+
+extern void _tx_timer_interrupt(void);
+
+volatile uint32_t system_core_clock = DEFAULT_SYSTEM_CLOCK;
+volatile uint32_t last_trap_mcause;
+void (*isr_table[32])(void);
+
+static uint32_t uart_console_ready;
+
+static void uart_write_hex32(uint32_t value)
+{
+ static const char hex[] = "0123456789ABCDEF";
+ int shift;
+
+ uart_write_str(0U, "0x");
+ for (shift = 28; shift >= 0; shift -= 4)
+ {
+ uart_write_byte(0U, (uint8_t)hex[(value >> (uint32_t)shift) & 0xFU]);
+ }
+}
+
+void tx_undefined_irq_handler(void)
+{
+ if (uart_console_ready != 0U)
+ {
+ uart_write_str(0U, "\r\nUndefined IRQ\r\n");
+ }
+
+ /* MISRA deviation: intentional infinite loop used as a fault sink. */
+ for (;;)
+ {
+ }
+}
+
+void tx_timer_irq_handler(void)
+{
+ _tx_timer_interrupt();
+}
+
+void system_init(void)
+{
+ uint32_t i;
+
+ for (i = 0U; i < 32U; ++i)
+ {
+ isr_table[i] = tx_undefined_irq_handler;
+ }
+
+ isr_table[7U] = tx_timer_irq_handler;
+
+ for (i = 0U; i < ARCHI_NB_FLL; ++i)
+ {
+ pi_fll_init((fll_type_t)i, 0U);
+ }
+
+ pulp_irq_init();
+ (void)timer_irq_init(ARCHI_SOC_FREQUENCY / (uint32_t)TX_TIMER_TICKS_PER_SECOND);
+ /* CV32E40P routes the FC Timer LO to irq_i[7] (MTI, bit 7), which maps
+ * to mip[7]. IRQ_MASK in cv32e40p_cs_registers forces mie[10] to zero,
+ * so bit 7 is the correct enable bit. mcause will be 0x80000007. */
+ (void)csr_read_set(CSR_MIE, BIT(7));
+
+ gpio_init();
+ if (uart_init(0U, 115200U, ARCHI_FPGA_FREQUENCY) == 0)
+ {
+ uart_console_ready = 1U;
+ }
+}
+
+void tx_trap_handler(uint32_t mcause, uint32_t mepc, uint32_t mtval)
+{
+ last_trap_mcause = mcause;
+
+ if ((mcause & 0x80000000UL) != 0UL)
+ {
+ uint32_t irq_id = mcause & 0x1FUL;
+
+ if (isr_table[irq_id] != NULL)
+ {
+ isr_table[irq_id]();
+ }
+ else
+ {
+ tx_undefined_irq_handler();
+ }
+ }
+ else
+ {
+ if (uart_console_ready != 0U)
+ {
+ uart_write_str(0U, "\r\nTrap mcause=");
+ uart_write_hex32(mcause);
+ uart_write_str(0U, " mepc=");
+ uart_write_hex32(mepc);
+ uart_write_str(0U, " mtval=");
+ uart_write_hex32(mtval);
+ uart_write_str(0U, "\r\n");
+ }
+
+ /* MISRA deviation: intentional infinite loop used as a fault sink. */
+ for (;;)
+ {
+ }
+ }
+}
diff --git a/ports/risc-v32/gnu/example_build/core_v_mcu/bsp/temp_sensor.c b/ports/risc-v32/gnu/example_build/core_v_mcu/bsp/temp_sensor.c
new file mode 100644
index 00000000..6d70e943
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/core_v_mcu/bsp/temp_sensor.c
@@ -0,0 +1,46 @@
+/***************************************************************************
+ * 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 Copilot (Sonnet 4.6).
+ * 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 <stdint.h>
+
+#include "i2c_master.h"
+#include "properties.h"
+#include "temp_sensor.h"
+
+#define ADT7420_I2C_ID 1U
+#define ADT7420_ADDR7 0x4BU
+#define ADT7420_TEMP_REG 0x00U
+#define ADT7420_I2C_FREQ_HZ 200000U
+
+void temp_sensor_init(void)
+{
+ (void)i2c_master_init(ADT7420_I2C_ID, ADT7420_I2C_FREQ_HZ, ARCHI_FPGA_FREQUENCY);
+}
+
+int16_t temp_read_celsius_x10(void)
+{
+ uint8_t buf[2];
+ int16_t raw;
+ int16_t value;
+
+ if (i2c_master_read_reg(ADT7420_I2C_ID, ADT7420_ADDR7, ADT7420_TEMP_REG, &buf[0], 2U) != 0)
+ {
+ return INT16_MIN;
+ }
+
+ raw = (int16_t)(((uint16_t)buf[0] << 8U) | (uint16_t)buf[1]);
+ value = (int16_t)(raw >> 3);
+ return (int16_t)(((int32_t)value * 5) / 8);
+}
diff --git a/ports/risc-v32/gnu/example_build/core_v_mcu/bsp/timer_irq.c b/ports/risc-v32/gnu/example_build/core_v_mcu/bsp/timer_irq.c
new file mode 100644
index 00000000..9a085d9f
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/core_v_mcu/bsp/timer_irq.c
@@ -0,0 +1,58 @@
+/***************************************************************************/
+/* Copyright (C) 2019 ETH Zurich and University of Bologna
+ * Copyright (C) 2026 Eclipse ThreadX contributors
+ *
+ * Derived from core-v-freertos (https://github.com/openhwgroup/core-v-freertos)
+ * Original work licensed under the Apache License, Version 2.0.
+ * See https://www.apache.org/licenses/LICENSE-2.0
+ * Modifications licensed under MIT (https://opensource.org/licenses/MIT).
+ *
+ * AI Disclosure: Some portions generated or modified by Copilot (Sonnet 4.6).
+ *
+ * SPDX-License-Identifier: Apache-2.0 AND MIT
+ ***************************************************************************/
+
+#include <stdint.h>
+
+#include "pulp_mem_map.h"
+#include "io.h"
+#include "timer.h"
+#include "timer_irq.h"
+
+static uint32_t last_count;
+
+int timer_irq_init(uint32_t ticks)
+{
+ (void)timer_irq_set_timeout(ticks);
+
+ writew(TIMER_CFG_LO_ENABLE_MASK |
+ TIMER_CFG_LO_RESET_MASK |
+ TIMER_CFG_LO_MODE_MASK |
+ TIMER_CFG_LO_IRQEN_MASK,
+ (uintptr_t)(PULP_FC_TIMER_ADDR + TIMER_CFG_LO_OFFSET));
+
+ last_count = 0U;
+ return 0;
+}
+
+int timer_irq_set_timeout(uint32_t ticks)
+{
+ writew(1U, (uintptr_t)(PULP_FC_TIMER_ADDR + TIMER_RESET_LO_OFFSET));
+ writew(ticks, (uintptr_t)(PULP_FC_TIMER_ADDR + TIMER_CMP_LO_OFFSET));
+ last_count = 0U;
+ return 0;
+}
+
+uint32_t timer_irq_clock_elapsed(void)
+{
+ uint32_t current = timer_irq_cycle_get_32();
+ uint32_t elapsed = current - last_count;
+
+ last_count = current;
+ return elapsed;
+}
+
+uint32_t timer_irq_cycle_get_32(void)
+{
+ return readw((uintptr_t)(PULP_FC_TIMER_ADDR + TIMER_CNT_LO_OFFSET));
+}
diff --git a/ports/risc-v32/gnu/example_build/core_v_mcu/bsp/uart_driver.c b/ports/risc-v32/gnu/example_build/core_v_mcu/bsp/uart_driver.c
new file mode 100644
index 00000000..33b1ff15
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/core_v_mcu/bsp/uart_driver.c
@@ -0,0 +1,169 @@
+/***************************************************************************
+ * 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 Copilot (Sonnet 4.6).
+ * 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 <stddef.h>
+#include <stdint.h>
+
+#include "bits.h"
+#include "io.h"
+#include "pulp_mem_map.h"
+#include "uart_driver.h"
+
+#define UDMA_CTRL_CG_OFFSET 0x00U
+#define UDMA_CTRL_UART0_CLKEN BIT(0)
+
+#define UDMA_UART_RX_SADDR_OFFSET 0x00U
+#define UDMA_UART_RX_SIZE_OFFSET 0x04U
+#define UDMA_UART_RX_CFG_OFFSET 0x08U
+#define UDMA_UART_TX_SADDR_OFFSET 0x10U
+#define UDMA_UART_TX_SIZE_OFFSET 0x14U
+#define UDMA_UART_TX_CFG_OFFSET 0x18U
+#define UDMA_UART_STATUS_OFFSET 0x20U
+#define UDMA_UART_SETUP_OFFSET 0x24U
+#define UDMA_UART_VALID_OFFSET 0x30U
+#define UDMA_UART_DATA_OFFSET 0x34U
+
+#define UDMA_CFG_EN_BIT BIT(4)
+/* EN (bit 4) only; DATASIZE=0 for 8-bit transfers; 0x12 would set DATASIZE=1
+ * (16-bit) and cause the EN bit to never clear for single-byte buffers. */
+#define UDMA_UART_TX_CFG_ENABLE 0x10U
+#define UDMA_UART_WORDLEN_8_BITS 3U
+#define UDMA_UART_TX_ENABLE_BIT BIT(8)
+#define UDMA_UART_RX_ENABLE_BIT BIT(9)
+#define UDMA_UART_RX_POLLING_BIT BIT(4)
+#define UDMA_UART_RX_CLEAN_BIT BIT(5)
+#define UDMA_UART_DIV_SHIFT 16U
+#define UDMA_UART_VALID_DATA_BIT BIT(0)
+#define UDMA_UART_STATUS_TX_BUSY BIT(0)
+#define UDMA_UART_MAX_TRANSFER 0xFFFFU
+
+static uint8_t uart_tx_byte[N_UART];
+
+static uintptr_t uart_base(uint8_t uart_id)
+{
+ return (uintptr_t)(UDMA_CH_ADDR_UART + ((uintptr_t)uart_id * (uintptr_t)UDMA_CH_SIZE));
+}
+
+int uart_init(uint8_t uart_id, uint32_t baudrate, uint32_t periph_freq)
+{
+ uint32_t clk_div;
+ uint32_t setup;
+ uint32_t cg;
+
+ if ((uart_id >= N_UART) || (baudrate == 0U) || (periph_freq == 0U))
+ {
+ return -1;
+ }
+
+ cg = readw((uintptr_t)(UDMA_CH_ADDR_CTRL + UDMA_CTRL_CG_OFFSET));
+ cg |= (uint32_t)(UDMA_CTRL_UART0_CLKEN << uart_id);
+ writew(cg, (uintptr_t)(UDMA_CH_ADDR_CTRL + UDMA_CTRL_CG_OFFSET));
+
+ clk_div = periph_freq / baudrate;
+ if (clk_div == 0U)
+ {
+ clk_div = 1U;
+ }
+
+ setup = (clk_div << UDMA_UART_DIV_SHIFT) |
+ UDMA_UART_TX_ENABLE_BIT |
+ UDMA_UART_RX_ENABLE_BIT |
+ UDMA_UART_RX_POLLING_BIT |
+ UDMA_UART_RX_CLEAN_BIT |
+ (UDMA_UART_WORDLEN_8_BITS << 1U);
+ writew(setup, uart_base(uart_id) + UDMA_UART_SETUP_OFFSET);
+
+ writew(0U, uart_base(uart_id) + UDMA_UART_RX_SADDR_OFFSET);
+ writew(0U, uart_base(uart_id) + UDMA_UART_RX_SIZE_OFFSET);
+ writew(0U, uart_base(uart_id) + UDMA_UART_RX_CFG_OFFSET);
+
+ return 0;
+}
+
+void uart_write_byte(uint8_t uart_id, uint8_t c)
+{
+ if (uart_id >= N_UART)
+ {
+ return;
+ }
+
+ uart_tx_byte[uart_id] = c;
+ uart_write_buf(uart_id, &uart_tx_byte[uart_id], 1U);
+}
+
+int uart_read_byte(uint8_t uart_id)
+{
+ if (uart_id >= N_UART)
+ {
+ return -1;
+ }
+
+ if ((readw(uart_base(uart_id) + UDMA_UART_VALID_OFFSET) & UDMA_UART_VALID_DATA_BIT) == 0U)
+ {
+ return -1;
+ }
+
+ return (int)(readw(uart_base(uart_id) + UDMA_UART_DATA_OFFSET) & 0xFFU);
+}
+
+void uart_write_str(uint8_t uart_id, const char *str)
+{
+ if (str == NULL)
+ {
+ return;
+ }
+
+ while (*str != '\0')
+ {
+ uart_write_byte(uart_id, (uint8_t)*str);
+ ++str;
+ }
+}
+
+void uart_write_buf(uint8_t uart_id, const uint8_t *buf, uint32_t len)
+{
+ uintptr_t base_addr;
+ const uint8_t *current;
+ uint32_t remaining;
+
+ if ((uart_id >= N_UART) || (buf == NULL) || (len == 0U))
+ {
+ return;
+ }
+
+ base_addr = uart_base(uart_id);
+ current = buf;
+ remaining = len;
+
+ while (remaining > 0U)
+ {
+ uint32_t chunk = (remaining > UDMA_UART_MAX_TRANSFER) ? UDMA_UART_MAX_TRANSFER : remaining;
+
+ while ((readw(base_addr + UDMA_UART_STATUS_OFFSET) & UDMA_UART_STATUS_TX_BUSY) != 0U)
+ {
+ }
+
+ writew((uint32_t)(uintptr_t)current, base_addr + UDMA_UART_TX_SADDR_OFFSET);
+ writew(chunk, base_addr + UDMA_UART_TX_SIZE_OFFSET);
+ writew(UDMA_UART_TX_CFG_ENABLE, base_addr + UDMA_UART_TX_CFG_OFFSET);
+
+ while ((readw(base_addr + UDMA_UART_TX_CFG_OFFSET) & UDMA_CFG_EN_BIT) != 0U)
+ {
+ }
+
+ current += chunk;
+ remaining -= chunk;
+ }
+}
diff --git a/ports/risc-v32/gnu/example_build/core_v_mcu/build.sh b/ports/risc-v32/gnu/example_build/core_v_mcu/build.sh
new file mode 100755
index 00000000..b610ac42
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/core_v_mcu/build.sh
@@ -0,0 +1,26 @@
+#!/bin/bash
+##############################################################################
+# Copyright (c) 2024 Microsoft Corporation
+# 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.
+#
+# SPDX-License-Identifier: MIT
+##############################################################################
+
+set -e
+
+XPACK_GCC_DIR=/opt/xpack-riscv-none-elf-gcc-15.2.0-1
+if [ -d "${XPACK_GCC_DIR}/bin" ]; then
+ export PATH="${XPACK_GCC_DIR}/bin:${PATH}"
+fi
+
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+THREADX_ROOT="${SCRIPT_DIR}/../../../../.."
+cmake -B "${SCRIPT_DIR}/build" \
+ -G Ninja \
+ -DCMAKE_TOOLCHAIN_FILE="${THREADX_ROOT}/cmake/riscv-none-elf-rv32imc.cmake" \
+ "${SCRIPT_DIR}"
+cmake --build "${SCRIPT_DIR}/build"
diff --git a/ports/risc-v32/gnu/example_build/core_v_mcu/crt0.S b/ports/risc-v32/gnu/example_build/core_v_mcu/crt0.S
new file mode 100644
index 00000000..3c761bbe
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/core_v_mcu/crt0.S
@@ -0,0 +1,48 @@
+/***************************************************************************/
+/* Copyright 2020 ETH Zurich
+ * Copyright (c) 2017 SiFive Inc. All rights reserved.
+ * Copyright (c) 2019 ETH Zürich and University of Bologna
+ * Copyright (C) 2026 Eclipse ThreadX contributors
+ *
+ * Derived from core-v-freertos (https://github.com/openhwgroup/core-v-freertos)
+ * Original work licensed under the Apache License, Version 2.0 (Apache-2.0)
+ * or the BSD 2-Clause License (BSD-2-Clause) at your option.
+ * See https://www.apache.org/licenses/LICENSE-2.0
+ * Modifications licensed under MIT (https://opensource.org/licenses/MIT).
+ *
+ * AI Disclosure: Some portions generated or modified by Copilot (Sonnet 4.6).
+ *
+ * SPDX-License-Identifier: (Apache-2.0 OR BSD-2-Clause) AND MIT
+ ***************************************************************************/
+
+ .section .text.start
+ .global _start
+ .type _start, @function
+
+_start:
+ .option push
+ .option norelax
+1: auipc gp, %pcrel_hi(__global_pointer$)
+ addi gp, gp, %pcrel_lo(1b)
+ .option pop
+
+ la sp, __stack_top
+
+ la a0, __vector_start
+ ori a0, a0, 1
+ csrw mtvec, a0
+
+ la t0, __bss_start
+ la t1, __bss_end
+2:
+ bgeu t0, t1, 3f
+ sw zero, 0(t0)
+ addi t0, t0, 4
+ j 2b
+3:
+ call main
+4:
+ wfi
+ j 4b
+
+ .size _start, .-_start
diff --git a/ports/risc-v32/gnu/example_build/core_v_mcu/demo_threadx.c b/ports/risc-v32/gnu/example_build/core_v_mcu/demo_threadx.c
new file mode 100644
index 00000000..c7daac3c
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/core_v_mcu/demo_threadx.c
@@ -0,0 +1,134 @@
+/***************************************************************************
+ * 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 Copilot (Sonnet 4.6).
+ * 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 <stdint.h>
+
+#include "gpio.h"
+#include "system_core_v_mcu.h"
+#include "tx_api.h"
+#include "uart_driver.h"
+
+#define DEMO_STACK_SIZE 1024U
+#define DEMO_BYTE_POOL_SIZE 4096U
+/*
+ * LED[0] on Nexys A7 = IO pad 11 = GPIO/apbio pin 4 (MUX=2).
+ * Verified in the quick-start guide and the 2022 nexys-pin-table.csv used to
+ * synthesise the pre-built bitfile (io_pad = gpio_pin + 7).
+ */
+#define DEMO_LED_PIN 4U
+#define DEMO_LED_MASK (1UL << DEMO_LED_PIN)
+
+/* Assemble the version banner string at compile time from tx_api.h constants. */
+#define _TX_STR(x) #x
+#define TX_STR(x) _TX_STR(x)
+#define TX_VERSION_STRING \
+ "v" TX_STR(THREADX_MAJOR_VERSION) \
+ "." TX_STR(THREADX_MINOR_VERSION) \
+ "." TX_STR(THREADX_PATCH_VERSION) \
+ "." TX_STR(THREADX_BUILD_VERSION)
+
+static const char banner[] =
+ "\r\nEclipse ThreadX for OpenHW CORE-V MCU " TX_VERSION_STRING "\r\n"
+ "Copyright (c) 2026 Eclipse ThreadX Contributors\r\n\r\n";
+
+static TX_THREAD blinky_thread;
+static TX_THREAD uart_thread;
+static TX_BYTE_POOL byte_pool;
+static uint8_t memory_area[DEMO_BYTE_POOL_SIZE];
+
+static void demo_error_loop(void)
+{
+ /* MISRA deviation: intentional infinite loop used as a fault sink. */
+ for (;;)
+ {
+ }
+}
+
+static void blinky_thread_entry(ULONG arg)
+{
+ TX_PARAMETER_NOT_USED(arg);
+
+ /* Configure LED[0] as push-pull output (sets pad-mux to FUNC_C = 2). */
+ gpio_set_output((uint32_t)DEMO_LED_MASK);
+
+ for (;;)
+ {
+ gpio_set((uint32_t)DEMO_LED_MASK);
+ tx_thread_sleep(100U); /* 1 s on (100 ticks × 10 ms) */
+ gpio_clear((uint32_t)DEMO_LED_MASK);
+ tx_thread_sleep(100U); /* 1 s off */
+ }
+}
+
+static void uart_thread_entry(ULONG arg)
+{
+ TX_PARAMETER_NOT_USED(arg);
+
+ uart_write_str(0U, banner);
+ for (;;)
+ {
+ uart_write_str(0U, ".");
+ tx_thread_sleep(100U);
+ }
+}
+
+void tx_application_define(void *first_unused_memory)
+{
+ CHAR *stack_ptr = TX_NULL;
+ UINT status;
+
+ TX_PARAMETER_NOT_USED(first_unused_memory);
+
+ status = tx_byte_pool_create(&byte_pool, "demo pool", memory_area, DEMO_BYTE_POOL_SIZE);
+ if (status != TX_SUCCESS)
+ {
+ demo_error_loop();
+ }
+
+ status = tx_byte_allocate(&byte_pool, (VOID **)&stack_ptr, DEMO_STACK_SIZE, TX_NO_WAIT);
+ if (status != TX_SUCCESS)
+ {
+ demo_error_loop();
+ }
+
+ status = tx_thread_create(&blinky_thread, "blinky", blinky_thread_entry, 0U,
+ stack_ptr, DEMO_STACK_SIZE, 1U, 1U,
+ TX_NO_TIME_SLICE, TX_AUTO_START);
+ if (status != TX_SUCCESS)
+ {
+ demo_error_loop();
+ }
+
+ status = tx_byte_allocate(&byte_pool, (VOID **)&stack_ptr, DEMO_STACK_SIZE, TX_NO_WAIT);
+ if (status != TX_SUCCESS)
+ {
+ demo_error_loop();
+ }
+
+ status = tx_thread_create(&uart_thread, "uart", uart_thread_entry, 0U,
+ stack_ptr, DEMO_STACK_SIZE, 2U, 2U,
+ TX_NO_TIME_SLICE, TX_AUTO_START);
+ if (status != TX_SUCCESS)
+ {
+ demo_error_loop();
+ }
+}
+
+int main(void)
+{
+ system_init();
+ tx_kernel_enter();
+ return 0;
+}
diff --git a/ports/risc-v32/gnu/example_build/core_v_mcu/deploy.sh b/ports/risc-v32/gnu/example_build/core_v_mcu/deploy.sh
new file mode 100755
index 00000000..2e22ec1e
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/core_v_mcu/deploy.sh
@@ -0,0 +1,142 @@
+#!/usr/bin/env 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 Copilot (Sonnet 4.6).
+# * The AI-generated portions may be considered public domain (CC0-1.0)
+# * and not subject to the project's licence.
+# *
+# * SPDX-License-Identifier: MIT AND CC0-1.0
+# ***************************************************************************/
+
+# deploy.sh -- build (optionally), flash, and run/debug the CORE-V MCU target
+# via the Ashling Opella LD debug probe.
+#
+# Usage:
+# bash deploy.sh [options]
+#
+# Options:
+# --build Run build.sh before flashing (default: skip).
+# --debug After loading, stop at main and leave GDB attached
+# (default: run free and exit GDB).
+# --elf <path> Override the ELF to flash
+# (default: build/demo_threadx.elf).
+# --openocd-cfg <p> Override the OpenOCD config file.
+#
+# Prerequisites:
+# * riscv64-unknown-elf-gdb and openocd must be on PATH.
+# * Run setup_opella.sh once to configure udev rules.
+# * The Nexys A7 board must be powered on.
+
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+# Prefer a bare-metal RISC-V GDB; fall back to gdb-multiarch (Ubuntu apt default)
+if command -v riscv64-unknown-elf-gdb >/dev/null 2>&1; then
+ GDB="riscv64-unknown-elf-gdb"
+elif command -v gdb-multiarch >/dev/null 2>&1; then
+ GDB="gdb-multiarch"
+else
+ GDB="riscv64-unknown-elf-gdb" # will produce a clear error below
+fi
+OPENOCD_LOG="/tmp/openocd_demo_threadx.log"
+OPENOCD_PID=""
+OPENOCD_TIMEOUT=10
+
+DEFAULT_ELF="${SCRIPT_DIR}/build/demo_threadx.elf"
+DEFAULT_CFG="${SCRIPT_DIR}/openocd-nexys-Ashling-Opella-LD.cfg"
+
+OPT_BUILD=0
+OPT_DEBUG=0
+OPT_ELF=""
+OPT_CFG=""
+
+usage() { grep '^# ' "$0" | sed 's/^# //' | head -30; exit 0; }
+
+while [[ $# -gt 0 ]]; do
+ case "$1" in
+ --build) OPT_BUILD=1; shift ;;
+ --debug) OPT_DEBUG=1; shift ;;
+ --elf) OPT_ELF="$2"; shift 2 ;;
+ --openocd-cfg) OPT_CFG="$2"; shift 2 ;;
+ -h|--help) usage ;;
+ *) echo "[ERROR] Unknown option: $1" >&2; usage ;;
+ esac
+done
+
+ELF="${OPT_ELF:-${DEFAULT_ELF}}"
+CFG="${OPT_CFG:-${DEFAULT_CFG}}"
+
+info() { echo "[INFO] $*"; }
+die() { echo "[ERROR] $*" >&2; exit 1; }
+
+stop_openocd() {
+ if [ -n "${OPENOCD_PID}" ] && kill -0 "${OPENOCD_PID}" 2>/dev/null; then
+ info "Stopping OpenOCD (PID ${OPENOCD_PID}) ..."
+ kill "${OPENOCD_PID}" 2>/dev/null || true
+ wait "${OPENOCD_PID}" 2>/dev/null || true
+ fi
+}
+
+trap stop_openocd EXIT INT TERM
+
+if [ "${OPT_BUILD}" -eq 1 ]; then
+ info "Running build.sh ..."
+ bash "${SCRIPT_DIR}/build.sh"
+fi
+
+[ -f "${ELF}" ] || die "ELF not found: ${ELF} (run with --build to build first)"
+[ -f "${CFG}" ] || die "OpenOCD config not found: ${CFG}"
+command -v openocd >/dev/null 2>&1 || die "openocd not found on PATH"
+command -v "${GDB}" >/dev/null 2>&1 || die "${GDB} not found on PATH"
+
+info "Starting OpenOCD with config: ${CFG}"
+info "Log: ${OPENOCD_LOG}"
+openocd -f "${CFG}" >"${OPENOCD_LOG}" 2>&1 &
+OPENOCD_PID=$!
+
+info "Waiting for OpenOCD to be ready (up to ${OPENOCD_TIMEOUT}s) ..."
+ELAPSED=0
+while ! grep -q "Ready for Remote Connections" "${OPENOCD_LOG}" 2>/dev/null; do
+ sleep 1
+ ELAPSED=$((ELAPSED + 1))
+ if [ "${ELAPSED}" -ge "${OPENOCD_TIMEOUT}" ]; then
+ echo "--- OpenOCD log ---" && cat "${OPENOCD_LOG}"
+ die "OpenOCD did not become ready within ${OPENOCD_TIMEOUT}s"
+ fi
+ if ! kill -0 "${OPENOCD_PID}" 2>/dev/null; then
+ echo "--- OpenOCD log ---" && cat "${OPENOCD_LOG}"
+ die "OpenOCD exited unexpectedly"
+ fi
+done
+info "OpenOCD is ready."
+
+GDB_INIT_FILE="/tmp/gdb_deploy_demo_threadx.gdb"
+printf 'set confirm off
+set remotetimeout 60
+file %s
+target extended-remote localhost:3333
+load
+' "${ELF}" > "${GDB_INIT_FILE}"
+
+if [ "${OPT_DEBUG}" -eq 0 ]; then
+ info "Flashing and running (detached) ..."
+ printf 'monitor resume\ndisconnect\nquit\n' >> "${GDB_INIT_FILE}"
+ info "ELF: ${ELF}"
+ riscv64-unknown-elf-size "${ELF}"
+ "${GDB}" --batch --command="${GDB_INIT_FILE}" 2>&1 \
+ | grep -v "keep_alive() was not invoked" \
+ | grep -v "GDB alive packet not sent"
+ info "Flash complete. Application is running on the target."
+ info "Serial console: minicom -b 115200 -D /dev/ttyUSBx"
+else
+ info "Flashing and stopping at main for interactive debug ..."
+ printf 'break main\ncontinue\n' >> "${GDB_INIT_FILE}"
+ info "ELF: ${ELF}"
+ riscv64-unknown-elf-size "${ELF}"
+ "${GDB}" --command="${GDB_INIT_FILE}"
+fi
diff --git a/ports/risc-v32/gnu/example_build/core_v_mcu/gdb_init b/ports/risc-v32/gnu/example_build/core_v_mcu/gdb_init
new file mode 100644
index 00000000..718a20f1
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/core_v_mcu/gdb_init
@@ -0,0 +1,6 @@
+set arch riscv:rv32
+target extended-remote localhost:3333
+file build/demo_threadx.elf
+load
+break main
+continue
diff --git a/ports/risc-v32/gnu/example_build/core_v_mcu/include/bits.h b/ports/risc-v32/gnu/example_build/core_v_mcu/include/bits.h
new file mode 100644
index 00000000..08871387
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/core_v_mcu/include/bits.h
@@ -0,0 +1,59 @@
+/***************************************************************************/
+/* Copyright (c) 2011-2014, Wind River Systems, Inc.
+ * Copyright 2020 ETH Zurich
+ * Copyright (C) 2026 Eclipse ThreadX contributors
+ *
+ * Derived from core-v-freertos (https://github.com/openhwgroup/core-v-freertos)
+ * Original work licensed under the Apache License, Version 2.0.
+ * See https://www.apache.org/licenses/LICENSE-2.0
+ * Modifications licensed under MIT (https://opensource.org/licenses/MIT).
+ *
+ * AI Disclosure: Some portions generated or modified by Copilot (Sonnet 4.6).
+ *
+ * SPDX-License-Identifier: Apache-2.0 AND MIT
+ ***************************************************************************/
+
+#ifndef BITS_H
+#define BITS_H
+
+#include <stdbool.h>
+#include <stdint.h>
+
+/* Helper to pass an integer as a pointer or vice versa. */
+#define POINTER_TO_UINT(x) ((uintptr_t)(x))
+#define UINT_TO_POINTER(x) ((void *)(uintptr_t)(x))
+#define POINTER_TO_INT(x) ((intptr_t)(x))
+#define INT_TO_POINTER(x) ((void *)(intptr_t)(x))
+
+#if !(defined(__CHAR_BIT__) && defined(__SIZEOF_LONG__))
+#error Missing required predefined macros for BITS_PER_LONG calculation
+#endif
+
+#define BITS_PER_LONG (__CHAR_BIT__ * __SIZEOF_LONG__)
+#define GENMASK(h, l) \
+ (((~0UL) - (1UL << (l)) + 1UL) & (~0UL >> (BITS_PER_LONG - 1UL - (h))))
+
+#define KB(x) ((x) << 10)
+#define MB(x) (KB(x) << 10)
+#define GB(x) (MB(x) << 10)
+
+#define KHZ(x) ((x) * 1000)
+#define MHZ(x) (KHZ(x) * 1000)
+
+#ifndef BIT
+#ifdef _ASMLANGUAGE
+#define BIT(n) (1 << (n))
+#else
+#define BIT(n) (1UL << (n))
+#endif
+#endif
+
+#define WRITE_BIT(var, bit, set) \
+ ((var) = ((set) != 0) ? ((var) | BIT(bit)) : ((var) & ~BIT(bit)))
+
+#define BIT_MASK(n) (BIT(n) - 1UL)
+
+#define REG_SET(FIELD, v) (((uint32_t)(v) << FIELD##_SHIFT) & FIELD##_MASK)
+#define REG_GET(FIELD, v) (((uint32_t)(v) & FIELD##_MASK) >> FIELD##_SHIFT)
+
+#endif /* BITS_H */
diff --git a/ports/risc-v32/gnu/example_build/core_v_mcu/include/csr.h b/ports/risc-v32/gnu/example_build/core_v_mcu/include/csr.h
new file mode 100644
index 00000000..69c7763e
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/core_v_mcu/include/csr.h
@@ -0,0 +1,75 @@
+/***************************************************************************/
+/* Copyright (C) 2020 ETH Zurich and University of Bologna
+ * Copyright (C) 2026 Eclipse ThreadX contributors
+ *
+ * Derived from core-v-freertos (https://github.com/openhwgroup/core-v-freertos)
+ * Original work licensed under the Apache License, Version 2.0.
+ * See https://www.apache.org/licenses/LICENSE-2.0
+ * Modifications licensed under MIT (https://opensource.org/licenses/MIT).
+ *
+ * AI Disclosure: Some portions generated or modified by Copilot (Sonnet 4.6).
+ *
+ * SPDX-License-Identifier: Apache-2.0 AND MIT
+ ***************************************************************************/
+
+#ifndef CSR_H
+#define CSR_H
+
+#include "bits.h"
+
+#define CSR_MSTATUS 0x300
+#define CSR_MISA 0x301
+#define CSR_MIE 0x304
+#define CSR_MTVEC 0x305
+#define CSR_MSCRATCH 0x340
+#define CSR_MEPC 0x341
+#define CSR_MCAUSE 0x342
+#define CSR_MTVAL 0x343
+#define CSR_MIP 0x344
+#define CSR_PMPCFG0 0x3A0
+#define CSR_PMPADDR0 0x3B0
+#define CSR_MVENDORID 0xF11
+#define CSR_MARCHID 0xF12
+#define CSR_MIMPID 0xF13
+#define CSR_MHARTID 0xF14
+
+#define MSTATUS_IE BIT(3)
+
+#define __CSR_EXPAND(x) #x
+
+#ifndef csr_read
+#define csr_read(csr) \
+ ({ \
+ register unsigned long __val; \
+ __asm__ volatile("csrr %0, " __CSR_EXPAND(csr) : "=r"(__val) : : "memory"); \
+ __val; \
+ })
+#endif
+
+#ifndef csr_write
+#define csr_write(csr, val) \
+ ({ \
+ unsigned long __val = (unsigned long)(val); \
+ __asm__ volatile("csrw " __CSR_EXPAND(csr) ", %0" : : "rK"(__val) : "memory"); \
+ })
+#endif
+
+#ifndef csr_read_clear
+#define csr_read_clear(csr, val) \
+ ({ \
+ unsigned long __val = (unsigned long)(val); \
+ __asm__ volatile("csrrc %0, " __CSR_EXPAND(csr) ", %1" : "=r"(__val) : "rK"(__val) : "memory"); \
+ __val; \
+ })
+#endif
+
+#ifndef csr_read_set
+#define csr_read_set(csr, val) \
+ ({ \
+ unsigned long __val = (unsigned long)(val); \
+ __asm__ volatile("csrrs %0, " __CSR_EXPAND(csr) ", %1" : "=r"(__val) : "rK"(__val) : "memory"); \
+ __val; \
+ })
+#endif
+
+#endif /* CSR_H */
diff --git a/ports/risc-v32/gnu/example_build/core_v_mcu/include/fll.h b/ports/risc-v32/gnu/example_build/core_v_mcu/include/fll.h
new file mode 100644
index 00000000..011268df
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/core_v_mcu/include/fll.h
@@ -0,0 +1,93 @@
+/***************************************************************************/
+/* Copyright 2020 GreenWaves Technologies
+ * Copyright (C) 2026 Eclipse ThreadX contributors
+ *
+ * Derived from core-v-freertos (https://github.com/openhwgroup/core-v-freertos)
+ * Original work licensed under the Apache License, Version 2.0.
+ * See https://www.apache.org/licenses/LICENSE-2.0
+ * Modifications licensed under MIT (https://opensource.org/licenses/MIT).
+ *
+ * AI Disclosure: Some portions generated or modified by Copilot (Sonnet 4.6).
+ *
+ * SPDX-License-Identifier: Apache-2.0 AND MIT
+ ***************************************************************************/
+
+#ifndef FLL_H
+#define FLL_H
+
+#include <stdint.h>
+
+#include "properties.h"
+#include "pulp_mem_map.h"
+
+#define FLL_STATUS_OFFSET 0x000U
+#define FLL_CONF1_OFFSET 0x004U
+#define FLL_CONF2_OFFSET 0x008U
+#define FLL_INTEGRATOR_OFFSET 0x00CU
+
+typedef struct
+{
+ volatile uint32_t FLL_STATUS;
+ volatile uint32_t FLL_CONF1;
+ volatile uint32_t FLL_CONF2;
+ volatile uint32_t FLL_INTEGRATOR;
+} fll_ctrl_t;
+
+typedef enum _fll_type
+{
+ FLL_SOC = 0,
+ FLL_PERI = 1,
+ FLL_CLUSTER = 2
+} fll_type_t;
+
+typedef enum
+{
+ PI_FREQ_DOMAIN_FC = 0,
+ PI_FREQ_DOMAIN_CL = 1,
+ PI_FREQ_DOMAIN_PERIPH = 2
+} pi_freq_domain_e;
+
+#define FLL_CTRL_STATUS_MULTI_FACTOR_MASK 0xFFFFU
+#define FLL_CTRL_STATUS_MULTI_FACTOR_SHIFT 0U
+#define FLL_CTRL_CONF1_MULTI_FACTOR_MASK 0xFFFFU
+#define FLL_CTRL_CONF1_MULTI_FACTOR_SHIFT 0U
+#define FLL_CTRL_CONF1_DCO_INPUT_MASK 0x03FF0000U
+#define FLL_CTRL_CONF1_DCO_INPUT_SHIFT 16U
+#define FLL_CTRL_CONF1_CLK_OUT_DIV_MASK 0x3C000000U
+#define FLL_CTRL_CONF1_CLK_OUT_DIV_SHIFT 26U
+#define FLL_CTRL_CONF1_OUTPUT_LOCK_EN_MASK 0x40000000U
+#define FLL_CTRL_CONF1_OUTPUT_LOCK_EN_SHIFT 30U
+#define FLL_CTRL_CONF1_MODE_MASK 0x80000000U
+#define FLL_CTRL_CONF1_MODE_SHIFT 31U
+#define FLL_CTRL_CONF2_LOOPGAIN_MASK 0xFU
+#define FLL_CTRL_CONF2_LOOPGAIN_SHIFT 0U
+#define FLL_CTRL_CONF2_DEASSERT_CYCLES_MASK 0x3F0U
+#define FLL_CTRL_CONF2_DEASSERT_CYCLES_SHIFT 4U
+#define FLL_CTRL_CONF2_ASSERT_CYCLES_MASK 0xFC00U
+#define FLL_CTRL_CONF2_ASSERT_CYCLES_SHIFT 10U
+#define FLL_CTRL_CONF2_LOCK_TOLERANCE_MASK 0x0FFF0000U
+#define FLL_CTRL_CONF2_LOCK_TOLERANCE_SHIFT 16U
+#define FLL_CTRL_CONF2_CONF_CLK_SEL_MASK 0x20000000U
+#define FLL_CTRL_CONF2_CONF_CLK_SEL_SHIFT 29U
+#define FLL_CTRL_CONF2_OPEN_LOOP_MASK 0x40000000U
+#define FLL_CTRL_CONF2_OPEN_LOOP_SHIFT 30U
+#define FLL_CTRL_CONF2_DITHERING_MASK 0x80000000U
+#define FLL_CTRL_CONF2_DITHERING_SHIFT 31U
+#define FLL_CTRL_INTEGRATOR_FRACT_PART_MASK 0x0000FFC0U
+#define FLL_CTRL_INTEGRATOR_FRACT_PART_SHIFT 6U
+#define FLL_CTRL_INTEGRATOR_INT_PART_MASK 0x03FF0000U
+#define FLL_CTRL_INTEGRATOR_INT_PART_SHIFT 16U
+#define FLL_CTRL_SOC_FLL_CONV_MASK 0x1U
+#define FLL_CTRL_SOC_FLL_CONV_SHIFT 0U
+#define FLL_CTRL_CLUSTER_FLL_CONV_MASK 0x2U
+#define FLL_CTRL_CLUSTER_FLL_CONV_SHIFT 1U
+
+#define FLL_NUM ARCHI_NB_FLL
+#define FLL_REF_CLK ARCHI_REF_CLOCK
+#define FLL_CTRL ((volatile fll_ctrl_t *)PULP_FLL_ADDR)
+
+void pi_fll_init(fll_type_t which_fll, uint32_t ret_state);
+int pi_fll_set_frequency(fll_type_t which_fll, uint32_t frequency, int check);
+int pi_fll_get_frequency(fll_type_t which_fll, uint8_t real);
+
+#endif /* FLL_H */
diff --git a/ports/risc-v32/gnu/example_build/core_v_mcu/include/gpio.h b/ports/risc-v32/gnu/example_build/core_v_mcu/include/gpio.h
new file mode 100644
index 00000000..da5f6008
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/core_v_mcu/include/gpio.h
@@ -0,0 +1,68 @@
+/***************************************************************************
+ * 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 Copilot (Sonnet 4.6).
+ * 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
+ **************************************************************************/
+
+#ifndef GPIO_H
+#define GPIO_H
+
+#include <stdint.h>
+
+/* apb_gpiov2 register map (pin-indexed interface)
+ *
+ * Offset Name Access Description
+ * 0x000 SETGPIO W PWDATA[6:0] = pin number → drive pin high
+ * 0x004 CLRGPIO W PWDATA[6:0] = pin number → drive pin low
+ * 0x008 TOGGPIO W PWDATA[6:0] = pin number → toggle pin
+ * 0x010 PIN0 R bits [31:0] = sampled input state of pins 0-31
+ * 0x020 OUT0 R/W bits [31:0] = output state bitmask, pins 0-31
+ * 0x030 SETSEL W select pin for RDSTAT
+ * 0x034 RDSTAT R status of selected pin
+ * 0x038 SETDIR W PWDATA[6:0]=pin, PWDATA[25:24]=dir
+ * dir[0]=1 → push-pull output; dir[1]=1 → open-drain
+ * 0x03C SETINT W interrupt configuration
+ * 0x040 INTACK W interrupt acknowledge
+ */
+#define GPIO_SETGPIO_OFFSET 0x000U
+#define GPIO_CLRGPIO_OFFSET 0x004U
+#define GPIO_TOGGPIO_OFFSET 0x008U
+#define GPIO_PIN0_OFFSET 0x010U
+#define GPIO_OUT0_OFFSET 0x020U
+#define GPIO_SETSEL_OFFSET 0x030U
+#define GPIO_RDSTAT_OFFSET 0x034U
+#define GPIO_SETDIR_OFFSET 0x038U
+#define GPIO_SETINT_OFFSET 0x03CU
+#define GPIO_INTACK_OFFSET 0x040U
+
+/* Direction field in SETDIR: bits [25:24] of the written word.
+ * 0b01 = push-pull output enable; 0b10 = open-drain; 0b00 = input */
+#define GPIO_DIR_OUTPUT (1U << 24U)
+
+void gpio_init(void);
+void gpio_set_output(uint32_t pin_mask);
+void gpio_toggle(uint32_t pin_mask);
+void gpio_set(uint32_t pin_mask);
+void gpio_clear(uint32_t pin_mask);
+
+/* Pad-mux control (APB SOC CTRL v1.0.0).
+ * Each IO pad has a 32-bit register at SOC_CTRL_BASE + 0x400 + pad*4.
+ * bits[1:0] = mux function: 0=sys, 1=perio, 2=apbio/GPIO, 3=fpgaio. */
+void gpio_setpinmux(uint8_t io_pad, uint8_t mux);
+uint8_t gpio_getpinmux(uint8_t io_pad);
+
+/* Single-pin direction and status (for CLI use).
+ * dir: 0 = input, 1 = push-pull output. */
+void gpio_pin_set_dir(uint8_t pin, uint8_t dir);
+uint32_t gpio_pin_read_status(uint8_t pin);
+
+#endif /* GPIO_H */
diff --git a/ports/risc-v32/gnu/example_build/core_v_mcu/include/i2c_master.h b/ports/risc-v32/gnu/example_build/core_v_mcu/include/i2c_master.h
new file mode 100644
index 00000000..4a478b30
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/core_v_mcu/include/i2c_master.h
@@ -0,0 +1,38 @@
+/***************************************************************************
+ * 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 Copilot (Sonnet 4.6).
+ * 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
+ **************************************************************************/
+
+#ifndef I2C_MASTER_H
+#define I2C_MASTER_H
+
+#include <stdint.h>
+
+/* i2c_master_init: enable I2C channel clock gate, configure clock divider.
+ * id: 0 or 1 (I2CM0 or I2CM1)
+ * i2c_freq: desired I2C clock in Hz (e.g. 200000)
+ * periph_freq: UDMA peripheral clock in Hz (5000000 on CORE-V FPGA)
+ * Returns 0 on success, -1 on bad params. */
+int i2c_master_init(uint8_t id, uint32_t i2c_freq, uint32_t periph_freq);
+
+/* i2c_master_read_reg: read len bytes from register reg_addr of 7-bit addressed device.
+ * id: I2CM channel (0 or 1)
+ * dev_addr7: 7-bit I2C device address
+ * reg_addr: register address to read from
+ * buf: output buffer (must be in RAM, not stack if possible)
+ * len: number of bytes to read (1..255)
+ * Returns 0 on success, -1 on timeout or arbitration loss. */
+int i2c_master_read_reg(uint8_t id, uint8_t dev_addr7, uint8_t reg_addr,
+ uint8_t *buf, uint8_t len);
+
+#endif /* I2C_MASTER_H */
diff --git a/ports/risc-v32/gnu/example_build/core_v_mcu/include/io.h b/ports/risc-v32/gnu/example_build/core_v_mcu/include/io.h
new file mode 100644
index 00000000..02364544
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/core_v_mcu/include/io.h
@@ -0,0 +1,61 @@
+/***************************************************************************/
+/* Copyright 2020 ETH Zurich
+ * Copyright (C) 2026 Eclipse ThreadX contributors
+ *
+ * Derived from core-v-freertos (https://github.com/openhwgroup/core-v-freertos)
+ * Original work licensed under the Apache License, Version 2.0.
+ * See https://www.apache.org/licenses/LICENSE-2.0
+ * Modifications licensed under MIT (https://opensource.org/licenses/MIT).
+ *
+ * AI Disclosure: Some portions generated or modified by Copilot (Sonnet 4.6).
+ *
+ * SPDX-License-Identifier: Apache-2.0 AND MIT
+ ***************************************************************************/
+
+#ifndef IO_H
+#define IO_H
+
+#include <stdint.h>
+
+#ifndef CORE_V_MCU_MMIO_MOCK
+static inline void writeb(uint8_t val, uintptr_t addr)
+{
+ __asm__ volatile("sb %0, 0(%1)" : : "r"(val), "r"((volatile uint8_t *)addr));
+}
+
+static inline void writeh(uint16_t val, uintptr_t addr)
+{
+ __asm__ volatile("sh %0, 0(%1)" : : "r"(val), "r"((volatile uint16_t *)addr));
+}
+
+static inline void writew(uint32_t val, uintptr_t addr)
+{
+ __asm__ volatile("sw %0, 0(%1)" : : "r"(val), "r"((volatile uint32_t *)addr));
+}
+
+static inline uint8_t readb(const uintptr_t addr)
+{
+ uint8_t val;
+
+ __asm__ volatile("lb %0, 0(%1)" : "=r"(val) : "r"((const volatile uint8_t *)addr));
+ return val;
+}
+
+static inline uint16_t readh(const uintptr_t addr)
+{
+ uint16_t val;
+
+ __asm__ volatile("lh %0, 0(%1)" : "=r"(val) : "r"((const volatile uint16_t *)addr));
+ return val;
+}
+
+static inline uint32_t readw(const uintptr_t addr)
+{
+ uint32_t val;
+
+ __asm__ volatile("lw %0, 0(%1)" : "=r"(val) : "r"((const volatile uint32_t *)addr));
+ return val;
+}
+#endif /* CORE_V_MCU_MMIO_MOCK */
+
+#endif /* IO_H */
diff --git a/ports/risc-v32/gnu/example_build/core_v_mcu/include/irq.h b/ports/risc-v32/gnu/example_build/core_v_mcu/include/irq.h
new file mode 100644
index 00000000..aafe6226
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/core_v_mcu/include/irq.h
@@ -0,0 +1,64 @@
+/***************************************************************************/
+/* Copyright (C) 2019 ETH Zurich and University of Bologna
+ * Copyright (C) 2026 Eclipse ThreadX contributors
+ *
+ * Derived from core-v-freertos (https://github.com/openhwgroup/core-v-freertos)
+ * Original work licensed under the Apache License, Version 2.0.
+ * See https://www.apache.org/licenses/LICENSE-2.0
+ * Modifications licensed under MIT (https://opensource.org/licenses/MIT).
+ *
+ * AI Disclosure: Some portions generated or modified by Copilot (Sonnet 4.6).
+ *
+ * SPDX-License-Identifier: Apache-2.0 AND MIT
+ ***************************************************************************/
+
+#ifndef IRQ_H
+#define IRQ_H
+
+#include "pulp_mem_map.h"
+#include "io.h"
+#include "bits.h"
+
+#define IRQ_REG_MASK_OFFSET 0x000U
+#define IRQ_REG_MASK_SET_OFFSET 0x004U
+#define IRQ_REG_MASK_CLEAR_OFFSET 0x008U
+#define IRQ_REG_INT_OFFSET 0x00CU
+#define IRQ_REG_INT_SET_OFFSET 0x010U
+#define IRQ_REG_INT_CLEAR_OFFSET 0x014U
+#define IRQ_REG_ACK_OFFSET 0x018U
+#define IRQ_REG_ACK_SET_OFFSET 0x01CU
+#define IRQ_REG_ACK_CLEAR_OFFSET 0x020U
+#define IRQ_REG_FIFO_OFFSET 0x024U
+
+#define IRQ_FC_EVT_SW0 BIT(0)
+#define IRQ_FC_EVT_SW1 BIT(1)
+#define IRQ_FC_EVT_SW2 BIT(2)
+#define IRQ_FC_EVT_SW3 BIT(3)
+#define IRQ_FC_EVT_SW4 BIT(4)
+#define IRQ_FC_EVT_SW5 BIT(5)
+#define IRQ_FC_EVT_SW6 BIT(6)
+#define IRQ_FC_EVT_SW7 BIT(7)
+#define IRQ_FC_EVT_DMA_PE_EVT BIT(8)
+#define IRQ_FC_EVT_DMA_PE_IRQ BIT(9)
+#define IRQ_FC_EVT_TIMER0_LO BIT(10)
+#define IRQ_FC_EVT_TIMER0_HI BIT(11)
+#define IRQ_FC_EVT_PF BIT(12)
+#define IRQ_FC_EVT_CLK_REF BIT(14)
+#define IRQ_FC_EVT_GPIO BIT(15)
+#define IRQ_FC_EVT_ADV_TIMER0 BIT(17)
+#define IRQ_FC_EVT_ADV_TIMER1 BIT(18)
+#define IRQ_FC_EVT_ADV_TIMER2 BIT(19)
+#define IRQ_FC_EVT_ADV_TIMER3 BIT(20)
+#define IRQ_FC_EVT_SOC_EVT BIT(26)
+#define IRQ_FC_EVT_QUIRQE_ERROR BIT(29)
+#define IRQ_FC_EVT_PERIPH0 BIT(30)
+#define IRQ_FC_EVT_PERIPH1 BIT(31)
+
+void irq_mask(uint32_t mask);
+void irq_enable(uint32_t mask);
+void irq_disable(uint32_t mask);
+uint32_t irq_clint_disable(void);
+uint32_t irq_clint_enable(void);
+void pulp_irq_init(void);
+
+#endif /* IRQ_H */
diff --git a/ports/risc-v32/gnu/example_build/core_v_mcu/include/memory_map.h b/ports/risc-v32/gnu/example_build/core_v_mcu/include/memory_map.h
new file mode 100644
index 00000000..f3048367
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/core_v_mcu/include/memory_map.h
@@ -0,0 +1,39 @@
+/***************************************************************************/
+/* Copyright (C) 2019 ETH Zurich, University of Bologna and GreenWaves Technologies
+ * Copyright (C) 2026 Eclipse ThreadX contributors
+ *
+ * Derived from core-v-freertos (https://github.com/openhwgroup/core-v-freertos)
+ * Original work licensed under the Apache License, Version 2.0.
+ * See https://www.apache.org/licenses/LICENSE-2.0
+ * Modifications licensed under MIT (https://opensource.org/licenses/MIT).
+ *
+ * AI Disclosure: Some portions generated or modified by Copilot (Sonnet 4.6).
+ *
+ * SPDX-License-Identifier: Apache-2.0 AND MIT
+ ***************************************************************************/
+
+#ifndef MEMORY_MAP_H
+#define MEMORY_MAP_H
+
+#include "pulp_mem_map.h"
+
+#define L2_BASE 0x1C000000UL
+#define L2_SIZE 0x80000UL
+#define L2_SHARED_ADDR L2_BASE
+#define L2_SHARED_SIZE L2_SIZE
+#define ROM_ADDR 0x1A000000UL
+#define ROM_SIZE 0x00002000UL
+
+#define SOC_PERIPHERALS_ADDR PULP_SOC_PERIPHERALS_ADDR
+#define SOC_FLL_ADDR PULP_FLL_ADDR
+#define GPIO_ADDR PULP_GPIO_ADDR
+#define UDMA_CTRL_ADDR PULP_UDMA_ADDR
+#define APB_SOC_CTRL_ADDR PULP_APB_SOC_CTRL_ADDR
+#define ADV_TIMER_ADDR PULP_ADV_TIMER_ADDR
+#define SOC_EU_ADDR PULP_SOC_EU_ADDR
+#define FC_IRQ_ADDR PULP_FC_IRQ_ADDR
+#define FC_TIMER_ADDR PULP_FC_TIMER_ADDR
+#define FC_HWPE_ADDR PULP_FC_HWPE_ADDR
+#define STDOUT_ADDR PULP_STDOUT_ADDR
+
+#endif /* MEMORY_MAP_H */
diff --git a/ports/risc-v32/gnu/example_build/core_v_mcu/include/properties.h b/ports/risc-v32/gnu/example_build/core_v_mcu/include/properties.h
new file mode 100644
index 00000000..98473696
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/core_v_mcu/include/properties.h
@@ -0,0 +1,43 @@
+/***************************************************************************/
+/* Copyright (C) 2019 ETH Zurich, University of Bologna and GreenWaves Technologies
+ * Copyright (C) 2026 Eclipse ThreadX contributors
+ *
+ * Derived from core-v-freertos (https://github.com/openhwgroup/core-v-freertos)
+ * Original work licensed under the Apache License, Version 2.0.
+ * See https://www.apache.org/licenses/LICENSE-2.0
+ * Modifications licensed under MIT (https://opensource.org/licenses/MIT).
+ *
+ * AI Disclosure: Some portions generated or modified by Copilot (Sonnet 4.6).
+ *
+ * SPDX-License-Identifier: Apache-2.0 AND MIT
+ ***************************************************************************/
+
+#ifndef PROPERTIES_H
+#define PROPERTIES_H
+
+#define PULP
+
+/* Peripheral bus clock (clk_per): PER_CLK_PERIOD_NS=200 → 5 MHz */
+#define ARCHI_FPGA_FREQUENCY 5000000U
+/* FC/SOC clock (clk_soc): FC_CLK_PERIOD_NS=100 → 10 MHz */
+#define ARCHI_SOC_FREQUENCY 10000000U
+#define ARCHI_NUM_TIMER 1U
+#define ARCHI_NUM_FLL 2U
+
+#define ARCHI_REF_CLOCK_LOG2 15U
+#define ARCHI_REF_CLOCK (1U << ARCHI_REF_CLOCK_LOG2)
+
+#define ARCHI_NB_FLL 3U
+
+#define __RT_FLL_CL 2U
+#define __RT_FLL_PERIPH 1U
+#define __RT_FLL_FC 0U
+
+#define __RT_FREQ_DOMAIN_FC 0U
+#define __RT_FREQ_DOMAIN_CL 2U
+#define __RT_FREQ_DOMAIN_PERIPH 1U
+#define RT_FREQ_NB_DOMAIN 3U
+
+#define DEFAULT_SYSTEM_CLOCK 50000000U
+
+#endif /* PROPERTIES_H */
diff --git a/ports/risc-v32/gnu/example_build/core_v_mcu/include/pulp_mem_map.h b/ports/risc-v32/gnu/example_build/core_v_mcu/include/pulp_mem_map.h
new file mode 100644
index 00000000..32757d62
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/core_v_mcu/include/pulp_mem_map.h
@@ -0,0 +1,103 @@
+/***************************************************************************/
+/* Copyright (C) 2019 ETH Zurich and University of Bologna
+ * Copyright (C) 2026 Eclipse ThreadX contributors
+ *
+ * Derived from core-v-freertos (https://github.com/openhwgroup/core-v-freertos)
+ * Original work licensed under the Apache License, Version 2.0.
+ * See https://www.apache.org/licenses/LICENSE-2.0
+ * Modifications licensed under MIT (https://opensource.org/licenses/MIT).
+ *
+ * AI Disclosure: Some portions generated or modified by Copilot (Sonnet 4.6).
+ *
+ * SPDX-License-Identifier: Apache-2.0 AND MIT
+ ***************************************************************************/
+
+#ifndef PULP_MEM_MAP_H
+#define PULP_MEM_MAP_H
+
+#ifndef PULP_SOC_PERIPHERALS_ADDR
+#define PULP_SOC_PERIPHERALS_ADDR 0x1A100000UL
+#endif
+
+#ifndef PULP_FC_TIMER_SIZE
+#define PULP_FC_TIMER_SIZE 0x00000800UL
+#endif
+
+#ifndef PULP_FLL_OFFSET
+#define PULP_FLL_OFFSET 0x00000000UL
+#endif
+#ifndef PULP_GPIO_OFFSET
+#define PULP_GPIO_OFFSET 0x00001000UL
+#endif
+#ifndef PULP_UDMA_OFFSET
+#define PULP_UDMA_OFFSET 0x00002000UL
+#endif
+#ifndef PULP_APB_SOC_CTRL_OFFSET
+#define PULP_APB_SOC_CTRL_OFFSET 0x00004000UL
+#endif
+#ifndef PULP_ADV_TIMER_OFFSET
+#define PULP_ADV_TIMER_OFFSET 0x00005000UL
+#endif
+#ifndef PULP_SOC_EU_OFFSET
+#define PULP_SOC_EU_OFFSET 0x00006000UL
+#endif
+#ifndef PULP_FC_IRQ_OFFSET
+#define PULP_FC_IRQ_OFFSET 0x00009800UL
+#endif
+#ifndef PULP_FC_TIMER_OFFSET
+#define PULP_FC_TIMER_OFFSET 0x0000B000UL
+#endif
+#ifndef PULP_FC_HWPE_OFFSET
+#define PULP_FC_HWPE_OFFSET 0x0000C000UL
+#endif
+#ifndef PULP_STDOUT_OFFSET
+#define PULP_STDOUT_OFFSET 0x0000F000UL
+#endif
+#ifndef PULP_DEBUG_OFFSET
+#define PULP_DEBUG_OFFSET 0x00010000UL
+#endif
+
+#ifndef PULP_FLL_ADDR
+#define PULP_FLL_ADDR (PULP_SOC_PERIPHERALS_ADDR + PULP_FLL_OFFSET)
+#endif
+#ifndef PULP_GPIO_ADDR
+#define PULP_GPIO_ADDR (PULP_SOC_PERIPHERALS_ADDR + PULP_GPIO_OFFSET)
+#endif
+#ifndef PULP_UDMA_ADDR
+#define PULP_UDMA_ADDR (PULP_SOC_PERIPHERALS_ADDR + PULP_UDMA_OFFSET)
+#endif
+#ifndef PULP_APB_SOC_CTRL_ADDR
+#define PULP_APB_SOC_CTRL_ADDR (PULP_SOC_PERIPHERALS_ADDR + PULP_APB_SOC_CTRL_OFFSET)
+#endif
+#ifndef PULP_ADV_TIMER_ADDR
+#define PULP_ADV_TIMER_ADDR (PULP_SOC_PERIPHERALS_ADDR + PULP_ADV_TIMER_OFFSET)
+#endif
+#ifndef PULP_SOC_EU_ADDR
+#define PULP_SOC_EU_ADDR (PULP_SOC_PERIPHERALS_ADDR + PULP_SOC_EU_OFFSET)
+#endif
+#ifndef PULP_FC_IRQ_ADDR
+#define PULP_FC_IRQ_ADDR (PULP_SOC_PERIPHERALS_ADDR + PULP_FC_IRQ_OFFSET)
+#endif
+#ifndef PULP_FC_TIMER_ADDR
+#define PULP_FC_TIMER_ADDR (PULP_SOC_PERIPHERALS_ADDR + PULP_FC_TIMER_OFFSET)
+#endif
+#ifndef PULP_FC_HWPE_ADDR
+#define PULP_FC_HWPE_ADDR (PULP_SOC_PERIPHERALS_ADDR + PULP_FC_HWPE_OFFSET)
+#endif
+#ifndef PULP_STDOUT_ADDR
+#define PULP_STDOUT_ADDR (PULP_SOC_PERIPHERALS_ADDR + PULP_STDOUT_OFFSET)
+#endif
+
+#define PULP_FLL_AREA_SIZE 0x00000010UL
+
+/* UDMA */
+#define UDMA_CH_ADDR_CTRL (PULP_UDMA_ADDR)
+#define UDMA_CH_ADDR_UART (PULP_UDMA_ADDR + 0x80U)
+#define UDMA_CH_ADDR_I2CM0 (PULP_UDMA_ADDR + 5U * UDMA_CH_SIZE)
+#define UDMA_CH_ADDR_I2CM1 (PULP_UDMA_ADDR + 6U * UDMA_CH_SIZE)
+#define UDMA_CTRL_I2CM0_CLKEN BIT(4)
+#define UDMA_CTRL_I2CM1_CLKEN BIT(5)
+#define UDMA_CH_SIZE (0x80U)
+#define N_UART (2U)
+
+#endif /* PULP_MEM_MAP_H */
diff --git a/ports/risc-v32/gnu/example_build/core_v_mcu/include/string.h b/ports/risc-v32/gnu/example_build/core_v_mcu/include/string.h
new file mode 100644
index 00000000..b5600000
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/core_v_mcu/include/string.h
@@ -0,0 +1,25 @@
+/***************************************************************************
+ * 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 Copilot (Sonnet 4.6).
+ * 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
+ **************************************************************************/
+
+#ifndef STRING_H
+#define STRING_H
+
+#include <stddef.h>
+
+void *memcpy(void *dest, const void *src, size_t n);
+void *memset(void *dest, int c, size_t n);
+int memcmp(const void *lhs, const void *rhs, size_t n);
+
+#endif /* STRING_H */
diff --git a/ports/risc-v32/gnu/example_build/core_v_mcu/include/system_core_v_mcu.h b/ports/risc-v32/gnu/example_build/core_v_mcu/include/system_core_v_mcu.h
new file mode 100644
index 00000000..4b445e31
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/core_v_mcu/include/system_core_v_mcu.h
@@ -0,0 +1,29 @@
+/***************************************************************************
+ * 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 Copilot (Sonnet 4.6).
+ * 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
+ **************************************************************************/
+
+#ifndef SYSTEM_CORE_V_MCU_H
+#define SYSTEM_CORE_V_MCU_H
+
+#include <stdint.h>
+
+extern void (*isr_table[32])(void);
+extern volatile uint32_t system_core_clock;
+
+void system_init(void);
+void tx_trap_handler(uint32_t mcause, uint32_t mepc, uint32_t mtval);
+void tx_timer_irq_handler(void);
+void tx_undefined_irq_handler(void);
+
+#endif /* SYSTEM_CORE_V_MCU_H */
diff --git a/ports/risc-v32/gnu/example_build/core_v_mcu/include/temp_sensor.h b/ports/risc-v32/gnu/example_build/core_v_mcu/include/temp_sensor.h
new file mode 100644
index 00000000..e94c589a
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/core_v_mcu/include/temp_sensor.h
@@ -0,0 +1,29 @@
+/***************************************************************************
+ * 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 Copilot (Sonnet 4.6).
+ * 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
+ **************************************************************************/
+
+#ifndef TEMP_SENSOR_H
+#define TEMP_SENSOR_H
+
+#include <stdint.h>
+
+/* Read ADT7420 temperature sensor on I2CM1 (Nexys A7 on-board sensor).
+ * Returns temperature in tenths of degrees Celsius (e.g. 213 = 21.3°C).
+ * Returns INT16_MIN on I2C error. */
+int16_t temp_read_celsius_x10(void);
+
+/* Initialize the temperature sensor I2C channel. Call once at startup. */
+void temp_sensor_init(void);
+
+#endif /* TEMP_SENSOR_H */
diff --git a/ports/risc-v32/gnu/example_build/core_v_mcu/include/timer.h b/ports/risc-v32/gnu/example_build/core_v_mcu/include/timer.h
new file mode 100644
index 00000000..ecfc0480
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/core_v_mcu/include/timer.h
@@ -0,0 +1,120 @@
+/***************************************************************************/
+/* Copyright (C) 2019 ETH Zurich and University of Bologna
+ * Copyright (C) 2026 Eclipse ThreadX contributors
+ *
+ * Derived from core-v-freertos (https://github.com/openhwgroup/core-v-freertos)
+ * Original work licensed under the Apache License, Version 2.0.
+ * See https://www.apache.org/licenses/LICENSE-2.0
+ * Modifications licensed under MIT (https://opensource.org/licenses/MIT).
+ *
+ * AI Disclosure: Some portions generated or modified by Copilot (Sonnet 4.6).
+ *
+ * SPDX-License-Identifier: Apache-2.0 AND MIT
+ ***************************************************************************/
+
+#ifndef TIMER_H
+#define TIMER_H
+
+#include "bits.h"
+
+#define TIMER_CFG_LO_OFFSET 0x0U
+#define TIMER_CFG_HI_OFFSET 0x4U
+#define TIMER_CNT_LO_OFFSET 0x8U
+#define TIMER_CNT_HI_OFFSET 0xCU
+#define TIMER_CMP_LO_OFFSET 0x10U
+#define TIMER_CMP_HI_OFFSET 0x14U
+#define TIMER_START_LO_OFFSET 0x18U
+#define TIMER_START_HI_OFFSET 0x1CU
+#define TIMER_RESET_LO_OFFSET 0x20U
+#define TIMER_RESET_HI_OFFSET 0x24U
+
+#define TIMER_CFG_LO_ENABLE_BIT 0U
+#define TIMER_CFG_LO_ENABLE_WIDTH 1U
+#define TIMER_CFG_LO_ENABLE_MASK 0x1U
+#define TIMER_CFG_LO_RESET_BIT 1U
+#define TIMER_CFG_LO_RESET_WIDTH 1U
+#define TIMER_CFG_LO_RESET_MASK 0x2U
+#define TIMER_CFG_LO_IRQEN_BIT 2U
+#define TIMER_CFG_LO_IRQEN_WIDTH 1U
+#define TIMER_CFG_LO_IRQEN_MASK 0x4U
+#define TIMER_CFG_LO_IEM_BIT 3U
+#define TIMER_CFG_LO_IEM_WIDTH 1U
+#define TIMER_CFG_LO_IEM_MASK 0x8U
+#define TIMER_CFG_LO_MODE_BIT 4U
+#define TIMER_CFG_LO_MODE_WIDTH 1U
+#define TIMER_CFG_LO_MODE_MASK 0x10U
+#define TIMER_CFG_LO_ONE_S_BIT 5U
+#define TIMER_CFG_LO_ONE_S_WIDTH 1U
+#define TIMER_CFG_LO_ONE_S_MASK 0x20U
+#define TIMER_CFG_LO_PEN_BIT 6U
+#define TIMER_CFG_LO_PEN_WIDTH 1U
+#define TIMER_CFG_LO_PEN_MASK 0x40U
+#define TIMER_CFG_LO_CCFG_BIT 7U
+#define TIMER_CFG_LO_CCFG_WIDTH 1U
+#define TIMER_CFG_LO_CCFG_MASK 0x80U
+#define TIMER_CFG_LO_PVAL_BIT 8U
+#define TIMER_CFG_LO_PVAL_WIDTH 8U
+#define TIMER_CFG_LO_PVAL_MASK 0xFF00U
+#define TIMER_CFG_LO_CASC_BIT 31U
+#define TIMER_CFG_LO_CASC_WIDTH 1U
+#define TIMER_CFG_LO_CASC_MASK 0x80000000UL
+
+#define TIMER_CFG_HI_ENABLE_BIT 0U
+#define TIMER_CFG_HI_ENABLE_WIDTH 1U
+#define TIMER_CFG_HI_ENABLE_MASK 0x1U
+#define TIMER_CFG_HI_RESET_BIT 1U
+#define TIMER_CFG_HI_RESET_WIDTH 1U
+#define TIMER_CFG_HI_RESET_MASK 0x2U
+#define TIMER_CFG_HI_IRQEN_BIT 2U
+#define TIMER_CFG_HI_IRQEN_WIDTH 1U
+#define TIMER_CFG_HI_IRQEN_MASK 0x4U
+#define TIMER_CFG_HI_IEM_BIT 3U
+#define TIMER_CFG_HI_IEM_WIDTH 1U
+#define TIMER_CFG_HI_IEM_MASK 0x8U
+#define TIMER_CFG_HI_MODE_BIT 4U
+#define TIMER_CFG_HI_MODE_WIDTH 1U
+#define TIMER_CFG_HI_MODE_MASK 0x10U
+#define TIMER_CFG_HI_ONE_S_BIT 5U
+#define TIMER_CFG_HI_ONE_S_WIDTH 1U
+#define TIMER_CFG_HI_ONE_S_MASK 0x20U
+#define TIMER_CFG_HI_PEN_BIT 6U
+#define TIMER_CFG_HI_PEN_WIDTH 1U
+#define TIMER_CFG_HI_PEN_MASK 0x40U
+#define TIMER_CFG_HI_CLKCFG_BIT 7U
+#define TIMER_CFG_HI_CLKCFG_WIDTH 1U
+#define TIMER_CFG_HI_CLKCFG_MASK 0x80U
+
+#define TIMER_CNT_LO_CNT_LO_BIT 0U
+#define TIMER_CNT_LO_CNT_LO_WIDTH 32U
+#define TIMER_CNT_LO_CNT_LO_MASK 0xFFFFFFFFUL
+#define TIMER_CNT_HI_CNT_HI_BIT 0U
+#define TIMER_CNT_HI_CNT_HI_WIDTH 32U
+#define TIMER_CNT_HI_CNT_HI_MASK 0xFFFFFFFFUL
+#define TIMER_CMP_LO_CMP_LO_BIT 0U
+#define TIMER_CMP_LO_CMP_LO_WIDTH 32U
+#define TIMER_CMP_LO_CMP_LO_MASK 0xFFFFFFFFUL
+#define TIMER_CMP_HI_CMP_HI_BIT 0U
+#define TIMER_CMP_HI_CMP_HI_WIDTH 32U
+#define TIMER_CMP_HI_CMP_HI_MASK 0xFFFFFFFFUL
+
+#define TIMER_START_LO_STRT_LO_BIT 0U
+#define TIMER_START_LO_STRT_LO_WIDTH 1U
+#define TIMER_START_LO_STRT_LO_MASK 0x1U
+#define TIMER_START_HI_STRT_HI_BIT 0U
+#define TIMER_START_HI_STRT_HI_WIDTH 1U
+#define TIMER_START_HI_STRT_HI_MASK 0x1U
+#define TIMER_RESET_LO_RST_LO_BIT 0U
+#define TIMER_RESET_LO_RST_LO_WIDTH 1U
+#define TIMER_RESET_LO_RST_LO_MASK 0x1U
+#define TIMER_RESET_HI_RST_HI_BIT 0U
+#define TIMER_RESET_HI_RST_HI_WIDTH 1U
+#define TIMER_RESET_HI_RST_HI_MASK 0x1U
+
+struct pulp_timer
+{
+ unsigned int current_time;
+ unsigned int flags;
+ void *base;
+};
+
+#endif /* TIMER_H */
diff --git a/ports/risc-v32/gnu/example_build/core_v_mcu/include/timer_irq.h b/ports/risc-v32/gnu/example_build/core_v_mcu/include/timer_irq.h
new file mode 100644
index 00000000..11e0d76a
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/core_v_mcu/include/timer_irq.h
@@ -0,0 +1,26 @@
+/***************************************************************************
+ * 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 Copilot (Sonnet 4.6).
+ * 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
+ **************************************************************************/
+
+#ifndef TIMER_IRQ_H
+#define TIMER_IRQ_H
+
+#include <stdint.h>
+
+int timer_irq_init(uint32_t ticks);
+int timer_irq_set_timeout(uint32_t ticks);
+uint32_t timer_irq_clock_elapsed(void);
+uint32_t timer_irq_cycle_get_32(void);
+
+#endif /* TIMER_IRQ_H */
diff --git a/ports/risc-v32/gnu/example_build/core_v_mcu/include/tx_user.h b/ports/risc-v32/gnu/example_build/core_v_mcu/include/tx_user.h
new file mode 100644
index 00000000..cc104032
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/core_v_mcu/include/tx_user.h
@@ -0,0 +1,22 @@
+/***************************************************************************
+ * 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 Copilot (Sonnet 4.6).
+ * 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
+ **************************************************************************/
+
+#ifndef TX_USER_H
+#define TX_USER_H
+
+#define TX_TIMER_TICKS_PER_SECOND 100U
+#define TX_INCLUDE_USER_DEFINE_FILE
+
+#endif /* TX_USER_H */
diff --git a/ports/risc-v32/gnu/example_build/core_v_mcu/include/uart_driver.h b/ports/risc-v32/gnu/example_build/core_v_mcu/include/uart_driver.h
new file mode 100644
index 00000000..c1771093
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/core_v_mcu/include/uart_driver.h
@@ -0,0 +1,27 @@
+/***************************************************************************
+ * 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 Copilot (Sonnet 4.6).
+ * 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
+ **************************************************************************/
+
+#ifndef UART_DRIVER_H
+#define UART_DRIVER_H
+
+#include <stdint.h>
+
+int uart_init(uint8_t uart_id, uint32_t baudrate, uint32_t periph_freq);
+void uart_write_byte(uint8_t uart_id, uint8_t c);
+int uart_read_byte(uint8_t uart_id);
+void uart_write_str(uint8_t uart_id, const char *str);
+void uart_write_buf(uint8_t uart_id, const uint8_t *buf, uint32_t len);
+
+#endif /* UART_DRIVER_H */
diff --git a/ports/risc-v32/gnu/example_build/core_v_mcu/install_deps.sh b/ports/risc-v32/gnu/example_build/core_v_mcu/install_deps.sh
new file mode 100755
index 00000000..d0192665
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/core_v_mcu/install_deps.sh
@@ -0,0 +1,78 @@
+#!/usr/bin/env 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.
+# *
+# * SPDX-License-Identifier: MIT
+# ***************************************************************************/
+
+# install_deps.sh -- Install Linux build/debug dependencies for the CORE-V MCU ThreadX port.
+#
+# Supported: Ubuntu 20.04 / 22.04 / 24.04 and Debian derivatives.
+# Run as a regular user; sudo is invoked internally where needed.
+#
+# Installs:
+# - xPack riscv-none-elf-gcc (multilib bare-metal toolchain, rv32imc/ilp32 capable)
+# - cmake, ninja-build, gdb-multiarch, openocd, usbutils
+#
+# Usage:
+# bash install_deps.sh
+
+set -euo pipefail
+
+die() { echo "[ERROR] $*" >&2; exit 1; }
+info() { echo "[INFO] $*"; }
+
+command -v apt-get >/dev/null 2>&1 || die "This script requires apt-get (Ubuntu/Debian)."
+
+# ---------------------------------------------------------------------------
+# xPack riscv-none-elf-gcc
+# Cross-platform multilib toolchain (Linux/macOS/Windows).
+# Includes rv32imc/ilp32 (soft-float) libgcc — required for CV32E40P.
+# See: https://github.com/xpack-dev-tools/riscv-none-elf-gcc-xpack
+# ---------------------------------------------------------------------------
+XPACK_VERSION="15.2.0-1"
+XPACK_TARBALL="xpack-riscv-none-elf-gcc-${XPACK_VERSION}-linux-x64.tar.gz"
+XPACK_URL="https://github.com/xpack-dev-tools/riscv-none-elf-gcc-xpack/releases/download/v${XPACK_VERSION}/${XPACK_TARBALL}"
+XPACK_INSTALL_DIR="/opt/xpack-riscv-none-elf-gcc-${XPACK_VERSION}"
+XPACK_BIN="${XPACK_INSTALL_DIR}/bin"
+
+APT_PACKAGES=(
+ cmake
+ ninja-build
+ gdb-multiarch
+ openocd
+ usbutils
+ git
+)
+
+info "Updating package lists ..."
+sudo apt-get update -qq
+
+info "Installing apt packages: ${APT_PACKAGES[*]}"
+sudo apt-get install -y "${APT_PACKAGES[@]}"
+
+if [ -x "${XPACK_BIN}/riscv-none-elf-gcc" ]; then
+ info "xPack riscv-none-elf-gcc ${XPACK_VERSION} already installed at ${XPACK_INSTALL_DIR}"
+else
+ info "Downloading xPack riscv-none-elf-gcc ${XPACK_VERSION} ..."
+ wget --no-verbose "${XPACK_URL}" -O "/tmp/${XPACK_TARBALL}"
+ sudo mkdir -p "${XPACK_INSTALL_DIR}"
+ sudo tar xzf "/tmp/${XPACK_TARBALL}" -C /opt
+ rm "/tmp/${XPACK_TARBALL}"
+ info "Installed to ${XPACK_INSTALL_DIR}"
+fi
+
+info ""
+info "Add the toolchain to your PATH:"
+info " export PATH=${XPACK_BIN}:\$PATH"
+info ""
+info "All dependencies installed."
+info ""
+info "Next steps:"
+info " 1. Run scripts/setup_opella.sh to configure udev rules for the Ashling Opella LD."
+info " 2. (WSL only) Follow the usbipd-win instructions printed by setup_opella.sh."
+
diff --git a/ports/risc-v32/gnu/example_build/core_v_mcu/link.ld b/ports/risc-v32/gnu/example_build/core_v_mcu/link.ld
new file mode 100644
index 00000000..8ffb174e
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/core_v_mcu/link.ld
@@ -0,0 +1,65 @@
+OUTPUT_ARCH(riscv)
+ENTRY(_start)
+
+MEMORY
+{
+ L2 (rwx) : ORIGIN = 0x1c000000, LENGTH = 0x80000
+}
+
+SECTIONS
+{
+ PROVIDE(__boot_address = 0x1c000880);
+ __stack_size = DEFINED(__stack_size) ? __stack_size : 0x2000;
+ PROVIDE(__stack_size = __stack_size);
+
+ .vectors 0x1c000800 :
+ {
+ __vector_start = .;
+ KEEP(*(.vectors))
+ . = __vector_start + 0x80;
+ __vector_end = .;
+ } > L2
+
+ .text __boot_address :
+ {
+ *(.text.start)
+ *(.text .text.*)
+ *(.rodata .rodata.*)
+ . = ALIGN(4);
+ } > L2
+
+ .data :
+ {
+ __data_begin = .;
+ __sdata_begin = .;
+ *(.sdata .sdata.*)
+ *(.data .data.*)
+ . = ALIGN(4);
+ __data_end = .;
+ } > L2
+
+ .bss (NOLOAD) :
+ {
+ __bss_start = .;
+ *(.sbss .sbss.*)
+ *(.bss .bss.*)
+ *(COMMON)
+ . = ALIGN(4);
+ __bss_end = .;
+ _end = .;
+ } > L2
+
+ PROVIDE(__tx_free_memory_start = __bss_end);
+ PROVIDE(__global_pointer$ = MIN(__sdata_begin + 0x800,
+ MAX(__data_begin + 0x800, __bss_end - 0x800)));
+
+ __stack_top = ORIGIN(L2) + LENGTH(L2);
+
+ ASSERT(SIZEOF(.vectors) <= 128, "Vector table exceeds reserved 128 bytes")
+
+ /DISCARD/ :
+ {
+ *(.eh_frame)
+ *(.note.*)
+ }
+}
diff --git a/ports/risc-v32/gnu/example_build/core_v_mcu/openocd-nexys-Ashling-Opella-LD.cfg b/ports/risc-v32/gnu/example_build/core_v_mcu/openocd-nexys-Ashling-Opella-LD.cfg
new file mode 100644
index 00000000..db43eae5
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/core_v_mcu/openocd-nexys-Ashling-Opella-LD.cfg
@@ -0,0 +1,41 @@
+interface ftdi
+ftdi_device_desc "Opella-LD Debug Probe"
+ftdi_vid_pid 0x0B6B 0x0040
+ftdi_tdo_sample_edge falling
+ftdi_layout_init 0x0A68 0xFF7B
+ftdi_channel 0
+ftdi_layout_signal JTAGOE -ndata 0x0010
+ftdi_layout_signal nTRST -data 0x0020
+ftdi_layout_signal nSRST -data 0x0040
+ftdi_layout_signal SWD_EN -data 0x0100
+ftdi_layout_signal SWDIO_OE -data 0x0200
+ftdi_layout_signal LED -ndata 0x0800
+transport select jtag
+
+
+reset_config none
+
+adapter_khz 1000
+
+set _CHIPNAME riscv
+
+jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id 0x10001C05
+
+set _TARGETNAME $_CHIPNAME.cpu
+target create $_TARGETNAME riscv -chain-position $_TARGETNAME
+
+gdb_report_data_abort enable
+gdb_report_register_access_error enable
+
+riscv set_reset_timeout_sec 120
+riscv set_command_timeout_sec 120
+
+# prefer to use sba for system bus access
+riscv set_prefer_sba on
+
+# dump jtag chain
+scan_chain
+
+init
+halt
+echo "Ready for Remote Connections" \ No newline at end of file
diff --git a/ports/risc-v32/gnu/example_build/core_v_mcu/tests/CMakeLists.txt b/ports/risc-v32/gnu/example_build/core_v_mcu/tests/CMakeLists.txt
new file mode 100644
index 00000000..6726981f
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/core_v_mcu/tests/CMakeLists.txt
@@ -0,0 +1,28 @@
+cmake_minimum_required(VERSION 3.15 FATAL_ERROR)
+project(core_v_mcu_tests C)
+set(CMAKE_C_STANDARD 99)
+set(CMAKE_C_STANDARD_REQUIRED ON)
+
+set(CORE_V_MCU_DIR ${CMAKE_CURRENT_LIST_DIR}/..)
+
+add_executable(test_irq
+ ${CMAKE_CURRENT_LIST_DIR}/test_irq.c
+ ${CMAKE_CURRENT_LIST_DIR}/mock/mmio_mock.c
+)
+target_include_directories(test_irq PRIVATE
+ ${CMAKE_CURRENT_LIST_DIR}/mock
+ ${CORE_V_MCU_DIR}/include
+)
+
+add_executable(test_timer
+ ${CMAKE_CURRENT_LIST_DIR}/test_timer.c
+ ${CMAKE_CURRENT_LIST_DIR}/mock/mmio_mock.c
+)
+target_include_directories(test_timer PRIVATE
+ ${CMAKE_CURRENT_LIST_DIR}/mock
+ ${CORE_V_MCU_DIR}/include
+)
+
+enable_testing()
+add_test(NAME irq_tests COMMAND test_irq)
+add_test(NAME timer_tests COMMAND test_timer)
diff --git a/ports/risc-v32/gnu/example_build/core_v_mcu/tests/mock/mmio_mock.c b/ports/risc-v32/gnu/example_build/core_v_mcu/tests/mock/mmio_mock.c
new file mode 100644
index 00000000..7852d5f1
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/core_v_mcu/tests/mock/mmio_mock.c
@@ -0,0 +1,18 @@
+/***************************************************************************
+ * 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 Copilot (Sonnet 4.6).
+ * 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 "mmio_mock.h"
+
+uint32_t mock_mmio[MOCK_MMIO_SIZE];
diff --git a/ports/risc-v32/gnu/example_build/core_v_mcu/tests/mock/mmio_mock.h b/ports/risc-v32/gnu/example_build/core_v_mcu/tests/mock/mmio_mock.h
new file mode 100644
index 00000000..c8839e1e
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/core_v_mcu/tests/mock/mmio_mock.h
@@ -0,0 +1,45 @@
+/***************************************************************************
+ * 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 Copilot (Sonnet 4.6).
+ * 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
+ **************************************************************************/
+
+#ifndef MMIO_MOCK_H
+#define MMIO_MOCK_H
+
+#include <stdint.h>
+#include <string.h>
+
+#define CORE_V_MCU_MMIO_MOCK 1
+#define MOCK_MMIO_SIZE 1024U
+
+extern uint32_t mock_mmio[MOCK_MMIO_SIZE];
+
+static inline void writew(uint32_t val, uintptr_t addr)
+{
+ if (addr < (uintptr_t)(MOCK_MMIO_SIZE * 4U))
+ {
+ mock_mmio[addr / 4U] = val;
+ }
+}
+
+static inline uint32_t readw(const uintptr_t addr)
+{
+ if (addr < (uintptr_t)(MOCK_MMIO_SIZE * 4U))
+ {
+ return mock_mmio[addr / 4U];
+ }
+
+ return 0U;
+}
+
+#endif /* MMIO_MOCK_H */
diff --git a/ports/risc-v32/gnu/example_build/core_v_mcu/tests/test_irq.c b/ports/risc-v32/gnu/example_build/core_v_mcu/tests/test_irq.c
new file mode 100644
index 00000000..72c17f49
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/core_v_mcu/tests/test_irq.c
@@ -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 Copilot (Sonnet 4.6).
+ * 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 <assert.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "mmio_mock.h"
+
+static uint32_t mock_mstatus = 0U;
+#define csr_read(csr) (mock_mstatus)
+#define csr_write(csr, val) do { mock_mstatus = (uint32_t)(val); } while (0)
+#define csr_read_clear(csr, val) ({ uint32_t _v = mock_mstatus; mock_mstatus &= ~(uint32_t)(val); _v; })
+#define csr_read_set(csr, val) ({ uint32_t _v = mock_mstatus; mock_mstatus |= (uint32_t)(val); _v; })
+
+#include "bits.h"
+#define PULP_SOC_PERIPHERALS_ADDR 0U
+#define PULP_FC_IRQ_OFFSET 0U
+#include "irq.h"
+#include "../../bsp/irq.c"
+
+static void test_irq_enable(void)
+{
+ memset(mock_mmio, 0, sizeof(mock_mmio));
+ irq_enable(BIT(10));
+ assert(mock_mmio[IRQ_REG_MASK_SET_OFFSET / 4U] == BIT(10));
+ printf("test_irq_enable: PASS\n");
+}
+
+static void test_irq_disable(void)
+{
+ memset(mock_mmio, 0, sizeof(mock_mmio));
+ irq_disable(BIT(10));
+ assert(mock_mmio[IRQ_REG_MASK_CLEAR_OFFSET / 4U] == BIT(10));
+ printf("test_irq_disable: PASS\n");
+}
+
+static void test_irq_clint_enable(void)
+{
+ mock_mstatus = 0U;
+ (void)irq_clint_enable();
+ assert((mock_mstatus & 0x8U) != 0U);
+ printf("test_irq_clint_enable: PASS\n");
+}
+
+int main(void)
+{
+ test_irq_enable();
+ test_irq_disable();
+ test_irq_clint_enable();
+ printf("All IRQ tests passed.\n");
+ return 0;
+}
diff --git a/ports/risc-v32/gnu/example_build/core_v_mcu/tests/test_timer.c b/ports/risc-v32/gnu/example_build/core_v_mcu/tests/test_timer.c
new file mode 100644
index 00000000..3e1499e7
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/core_v_mcu/tests/test_timer.c
@@ -0,0 +1,53 @@
+/***************************************************************************
+ * 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 Copilot (Sonnet 4.6).
+ * 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 <assert.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "mmio_mock.h"
+#define PULP_SOC_PERIPHERALS_ADDR 0U
+#define PULP_FC_TIMER_OFFSET 0U
+
+#include "bits.h"
+#include "timer.h"
+#include "timer_irq.h"
+#include "../../bsp/timer_irq.c"
+
+static void test_timer_irq_init(void)
+{
+ uint32_t cfg;
+ int ret;
+
+ memset(mock_mmio, 0, sizeof(mock_mmio));
+ ret = timer_irq_init(327U);
+ assert(ret == 0);
+ assert(mock_mmio[TIMER_CMP_LO_OFFSET / 4U] == 327U);
+
+ cfg = mock_mmio[TIMER_CFG_LO_OFFSET / 4U];
+ assert((cfg & TIMER_CFG_LO_ENABLE_MASK) != 0U);
+ assert((cfg & TIMER_CFG_LO_IRQEN_MASK) != 0U);
+ assert((cfg & TIMER_CFG_LO_CCFG_MASK) != 0U);
+ assert((cfg & TIMER_CFG_LO_MODE_MASK) != 0U);
+ printf("test_timer_irq_init: PASS\n");
+}
+
+int main(void)
+{
+ test_timer_irq_init();
+ printf("All timer tests passed.\n");
+ return 0;
+}
diff --git a/ports/risc-v32/gnu/example_build/core_v_mcu/tx_initialize_low_level.S b/ports/risc-v32/gnu/example_build/core_v_mcu/tx_initialize_low_level.S
new file mode 100644
index 00000000..3c5a3888
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/core_v_mcu/tx_initialize_low_level.S
@@ -0,0 +1,63 @@
+/***************************************************************************
+ * 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 Copilot (Sonnet 4.6).
+ * 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
+ **************************************************************************/
+
+ .section .data
+ .global __tx_free_memory_start
+__tx_free_memory_start:
+
+ .section .text.trap_entry
+ .align 4
+ .global tx_trap_entry
+ .extern _tx_thread_context_save
+ .extern _tx_thread_context_restore
+ .extern tx_trap_handler
+
+tx_trap_entry:
+ addi sp, sp, -128
+ sw ra, 112(sp)
+ call _tx_thread_context_save
+ csrr a0, mcause
+ csrr a1, mepc
+ csrr a2, mtval
+ addi sp, sp, -4
+ sw ra, 0(sp)
+ call tx_trap_handler
+ lw ra, 0(sp)
+ addi sp, sp, 4
+ call _tx_thread_context_restore
+1:
+ wfi
+ j 1b
+
+ .section .text
+ .global _tx_initialize_low_level
+ .weak _tx_initialize_low_level
+ .extern _tx_thread_system_stack_ptr
+ .extern _tx_initialize_unused_memory
+ .extern __vector_start
+
+_tx_initialize_low_level:
+ la t0, _tx_thread_system_stack_ptr
+ sw sp, 0(t0)
+
+ la t0, __tx_free_memory_start
+ la t1, _tx_initialize_unused_memory
+ sw t0, 0(t1)
+
+ la t0, __vector_start
+ ori t0, t0, 1
+ csrw mtvec, t0
+
+ ret
diff --git a/ports/risc-v32/gnu/example_build/core_v_mcu/vectors.S b/ports/risc-v32/gnu/example_build/core_v_mcu/vectors.S
new file mode 100644
index 00000000..f6a57d7a
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/core_v_mcu/vectors.S
@@ -0,0 +1,25 @@
+/***************************************************************************
+ * 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 Copilot (Sonnet 4.6).
+ * 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
+ **************************************************************************/
+
+ .section .vectors, "ax"
+ .option norvc
+ .align 2
+ .extern tx_trap_entry
+ .global __vector_start
+
+__vector_start:
+ .rept 32
+ jal zero, tx_trap_entry
+ .endr
diff --git a/ports/risc-v32/gnu/example_build/cva6_ariane/board.c b/ports/risc-v32/gnu/example_build/cva6_ariane/board.c
new file mode 100644
index 00000000..b1c7e5c5
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/cva6_ariane/board.c
@@ -0,0 +1,42 @@
+/***************************************************************************
+ * Copyright (c) 2026 Quintauris
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: MIT
+ **************************************************************************/
+
+#include "plic.h"
+#include "hwtimer.h"
+#include "uart.h"
+#include <stdint.h>
+#include <stddef.h>
+
+void *memset(const void *des, int c,size_t n)
+{
+ if((des == NULL) || n <=0)
+ return (void*)des;
+ char* t = (char*)des;
+ int i;
+ for(i=0;i<n;i++)
+ t[i]=c;
+ return t;
+}
+
+
+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;
+}
diff --git a/ports/risc-v32/gnu/example_build/cva6_ariane/build_libthreadx.sh b/ports/risc-v32/gnu/example_build/cva6_ariane/build_libthreadx.sh
new file mode 100644
index 00000000..23f707dc
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/cva6_ariane/build_libthreadx.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+##############################################################################
+# Copyright (c) 2024 Microsoft Corporation
+# 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.
+#
+# SPDX-License-Identifier: MIT
+##############################################################################
+
+
+pushd ../../../../../
+cmake -Bbuild -GNinja -DCMAKE_TOOLCHAIN_FILE=cmake/riscv32_gnu.cmake -DSOFT_FLOAT=1 .
+cmake --build ./build/
+popd
diff --git a/ports/risc-v32/gnu/example_build/cva6_ariane/csr.h b/ports/risc-v32/gnu/example_build/cva6_ariane/csr.h
new file mode 100644
index 00000000..1bffcf39
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/cva6_ariane/csr.h
@@ -0,0 +1,360 @@
+/***************************************************************************
+ * Copyright (c) 2026 Quintauris
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: MIT
+ **************************************************************************/
+
+
+#ifndef RISCV_CSR_H
+#define RISCV_CSR_H
+
+
+// Machine Status Register, mstatus
+#define MSTATUS_MPP_MASK (3L << 11) // previous mode.
+#define MSTATUS_MPP_M (3L << 11)
+#define MSTATUS_MPP_S (1L << 11)
+#define MSTATUS_MPP_U (0L << 11)
+#define MSTATUS_MIE (1L << 3) // machine-mode interrupt enable.
+#define MSTATUS_MPIE (1L << 7)
+#define MSTATUS_FS (1L << 13)
+
+// Machine-mode Interrupt Enable
+#define MIE_MTIE (1L << 7)
+#define MIE_MSIE (1L << 3)
+#define MIE_MEIE (1L << 11)
+#define MIE_STIE (1L << 5) // supervisor timer
+#define MIE_SSIE (1L << 1)
+#define MIE_SEIE (1L << 9)
+
+// Supervisor Status Register, sstatus
+#define SSTATUS_SPP (1L << 8) // Previous mode, 1=Supervisor, 0=User
+#define SSTATUS_SPIE (1L << 5) // Supervisor Previous Interrupt Enable
+#define SSTATUS_UPIE (1L << 4) // User Previous Interrupt Enable
+#define SSTATUS_SIE (1L << 1) // Supervisor Interrupt Enable
+#define SSTATUS_UIE (1L << 0) // User Interrupt Enable
+#define SSTATUS_SPIE (1L << 5)
+#define SSTATUS_UPIE (1L << 4)
+
+// Supervisor Interrupt Enable
+#define SIE_SEIE (1L << 9) // external
+#define SIE_STIE (1L << 5) // timer
+#define SIE_SSIE (1L << 1) // software
+
+#ifndef __ASSEMBLER__
+
+#include <stdint.h>
+
+static inline uint32_t riscv_get_core()
+{
+ uint32_t x;
+ asm volatile("csrr %0, mhartid" : "=r" (x) );
+ return x;
+}
+
+static inline uint64_t riscv_get_mstatus()
+{
+ uint32_t x;
+ uint32_t xh;
+ asm volatile("csrr %0, mstatus" : "=r" (x) );
+ asm volatile("csrr %0, mstatush" : "=r" (xh) );
+ return ((uint64_t)(x)) + ((uint64_t)(xh)) << 32;
+}
+
+static inline void riscv_writ_mstatus(uint64_t x)
+{
+ asm volatile("csrw mstatus, %0" : : "r" ((uint32_t)x));
+ asm volatile("csrw mstatush, %0" : : "r" ((uint32_t)(x >> 32)));
+}
+
+// machine exception program counter, holds the
+// instruction address to which a return from
+// exception will go.
+static inline void riscv_writ_mepc(uint32_t x)
+{
+ asm volatile("csrw mepc, %0" : : "r" (x));
+}
+
+static inline uint32_t riscv_get_sstatus()
+{
+ uint32_t x;
+ asm volatile("csrr %0, sstatus" : "=r" (x) );
+ return x;
+}
+
+static inline void riscv_writ_sstatus(uint32_t x)
+{
+ asm volatile("csrw sstatus, %0" : : "r" (x));
+}
+
+// Supervisor Interrupt Pending
+static inline uint32_t riscv_get_sip()
+{
+ uint32_t x;
+ asm volatile("csrr %0, sip" : "=r" (x) );
+ return x;
+}
+
+static inline void riscv_writ_sip(uint32_t x)
+{
+ asm volatile("csrw sip, %0" : : "r" (x));
+}
+
+static inline uint32_t riscv_get_sie()
+{
+ uint32_t x;
+ asm volatile("csrr %0, sie" : "=r" (x) );
+ return x;
+}
+
+static inline void riscv_writ_sie(uint32_t x)
+{
+ asm volatile("csrw sie, %0" : : "r" (x));
+}
+
+static inline uint32_t riscv_get_mie()
+{
+ uint32_t x;
+ asm volatile("csrr %0, mie" : "=r" (x) );
+ return x;
+}
+
+static inline void riscv_writ_mie(uint32_t x)
+{
+ asm volatile("csrw mie, %0" : : "r" (x));
+}
+
+// supervisor exception program counter, holds the
+// instruction address to which a return from
+// exception will go.
+static inline void riscv_writ_sepc(uint32_t x)
+{
+ asm volatile("csrw sepc, %0" : : "r" (x));
+}
+
+static inline uint32_t riscv_get_sepc()
+{
+ uint32_t x;
+ asm volatile("csrr %0, sepc" : "=r" (x) );
+ return x;
+}
+
+// Machine Exception Delegation
+static inline uint32_t riscv_get_medeleg()
+{
+ uint32_t x;
+ asm volatile("csrr %0, medeleg" : "=r" (x) );
+ return x;
+}
+
+static inline void riscv_writ_medeleg(uint32_t x)
+{
+ asm volatile("csrw medeleg, %0" : : "r" (x));
+}
+
+// Machine Interrupt Delegation
+static inline uint32_t riscv_get_mideleg()
+{
+ uint32_t x;
+ asm volatile("csrr %0, mideleg" : "=r" (x) );
+ return x;
+}
+
+static inline void riscv_writ_mideleg(uint32_t x)
+{
+ asm volatile("csrw mideleg, %0" : : "r" (x));
+}
+
+// Supervisor Trap-Vector Base Address
+// low two bits are mode.
+static inline void riscv_writ_stvec(uint32_t x)
+{
+ asm volatile("csrw stvec, %0" : : "r" (x));
+}
+
+static inline uint32_t riscv_get_stvec()
+{
+ uint32_t x;
+ asm volatile("csrr %0, stvec" : "=r" (x) );
+ return x;
+}
+
+static inline uint32_t riscv_get_stimecmp()
+{
+ uint32_t x;
+ asm volatile("csrr %0, 0x14d" : "=r" (x) );
+ return x;
+}
+
+static inline void riscv_writ_stimecmp(uint32_t x)
+{
+ asm volatile("csrw 0x14d, %0" : : "r" (x));
+}
+
+static inline uint32_t riscv_get_menvcfg()
+{
+ uint32_t x;
+ asm volatile("csrr %0, 0x30a" : "=r" (x) );
+ return x;
+}
+
+static inline void riscv_writ_menvcfg(uint32_t x)
+{
+ asm volatile("csrw 0x30a, %0" : : "r" (x));
+}
+
+static inline void riscv_writ_pmpcfg0(uint32_t x)
+{
+ asm volatile("csrw pmpcfg0, %0" : : "r" (x));
+}
+
+static inline void riscv_writ_pmpaddr0(uint32_t x)
+{
+ asm volatile("csrw pmpaddr0, %0" : : "r" (x));
+}
+
+static inline void riscv_writ_satp(uint32_t x)
+{
+ asm volatile("csrw satp, %0" : : "r" (x));
+}
+
+static inline uint32_t riscv_get_satp()
+{
+ uint32_t x;
+ asm volatile("csrr %0, satp" : "=r" (x) );
+ return x;
+}
+
+static inline uint32_t riscv_get_scause()
+{
+ uint32_t x;
+ asm volatile("csrr %0, scause" : "=r" (x) );
+ return x;
+}
+
+static inline uint32_t riscv_get_stval()
+{
+ uint32_t x;
+ asm volatile("csrr %0, stval" : "=r" (x) );
+ return x;
+}
+
+static inline void riscv_writ_mcounteren(uint32_t x)
+{
+ asm volatile("csrw mcounteren, %0" : : "r" (x));
+}
+
+static inline uint32_t riscv_get_mcounteren()
+{
+ uint32_t x;
+ asm volatile("csrr %0, mcounteren" : "=r" (x) );
+ return x;
+}
+
+// machine-mode cycle counter
+static inline uint64_t riscv_get_time()
+{
+ uint32_t x;
+ uint32_t xh;
+ asm volatile("csrr %0, time" : "=r" (x) );
+ asm volatile("csrr %0, timeh" : "=r" (xh) );
+ return ((uint64_t)(x)) + ((uint64_t)(xh)) << 32;
+}
+
+static inline void riscv_sintr_on()
+{
+ uint32_t sstatus = riscv_get_sstatus();
+ sstatus |= SSTATUS_SIE;
+ riscv_writ_sstatus(sstatus);
+}
+
+static inline void riscv_sintr_off()
+{
+ uint32_t sstatus = riscv_get_sstatus();
+ sstatus &= (~SSTATUS_SIE);
+ riscv_writ_sstatus(sstatus);
+}
+
+static inline int riscv_sintr_get()
+{
+ uint32_t x = riscv_get_sstatus();
+ return (x & SSTATUS_SIE) != 0;
+}
+
+static inline void riscv_sintr_restore(int x)
+{
+ if(x)
+ riscv_sintr_on();
+ else
+ riscv_sintr_off();
+}
+
+static inline void riscv_mintr_on()
+{
+ uint32_t mstatus = riscv_get_mstatus();
+ mstatus |= MSTATUS_MIE;
+ riscv_writ_mstatus(mstatus);
+}
+
+static inline void riscv_mintr_off()
+{
+ uint32_t mstatus = riscv_get_mstatus();
+ mstatus &= (~MSTATUS_MIE);
+ riscv_writ_mstatus(mstatus);
+}
+
+static inline int riscv_mintr_get()
+{
+ uint32_t x = riscv_get_mstatus();
+ return (x & MSTATUS_MIE) != 0;
+}
+
+static inline void riscv_mintr_restore(int x)
+{
+ if(x)
+ riscv_mintr_on();
+ else
+ riscv_mintr_off();
+}
+
+static inline uint32_t riscv_get_sp()
+{
+ uint32_t x;
+ asm volatile("mv %0, sp" : "=r" (x) );
+ return x;
+}
+
+// read and write tp, the thread pointer, which xv6 uses to hold
+// this core's hartid (core number), the index into cpus[].
+static inline uint32_t riscv_get_tp()
+{
+ uint32_t x;
+ asm volatile("mv %0, tp" : "=r" (x) );
+ return x;
+}
+
+static inline void riscv_writ_tp(uint32_t x)
+{
+ asm volatile("mv tp, %0" : : "r" (x));
+}
+
+static inline uint32_t riscv_get_ra()
+{
+ uint32_t x;
+ asm volatile("mv %0, ra" : "=r" (x) );
+ return x;
+}
+
+// flush the TLB.
+static inline void sfence_vma()
+{
+ // the zero, zero means flush all TLB entries.
+ asm volatile("sfence.vma zero, zero");
+}
+
+#endif // __ASSEMBLER__
+
+#endif
diff --git a/ports/risc-v32/gnu/example_build/cva6_ariane/demo_threadx.c b/ports/risc-v32/gnu/example_build/cva6_ariane/demo_threadx.c
new file mode 100644
index 00000000..adc56f4a
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/cva6_ariane/demo_threadx.c
@@ -0,0 +1,404 @@
+/***************************************************************************/
+/* Copyright (c) 2024 Microsoft Corporation */
+/* 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. */
+/* */
+/* SPDX-License-Identifier: MIT */
+/***************************************************************************/
+
+/* This is a small demo of the high-performance ThreadX kernel. It includes examples of eight
+ threads of different priorities, using a message queue, semaphore, mutex, event flags group,
+ byte pool, and block pool. */
+
+#include "tx_api.h"
+#include "uart.h"
+
+#define DEMO_STACK_SIZE 1024
+#define DEMO_BYTE_POOL_SIZE 9120
+#define DEMO_BLOCK_POOL_SIZE 100
+#define DEMO_QUEUE_SIZE 100
+
+char *_to_str(ULONG val)
+{
+ static char buf[11]; /* 10 digits max + '\0' */
+ char *p = buf + sizeof(buf) - 1;
+
+ *p = '\0';
+ do {
+ *--p = '0' + (val % 10);
+ val /= 10;
+ } while (val);
+
+ return p;
+}
+
+/* Define the ThreadX object control blocks... */
+
+TX_THREAD thread_0;
+TX_THREAD thread_1;
+TX_THREAD thread_2;
+TX_THREAD thread_3;
+TX_THREAD thread_4;
+TX_THREAD thread_5;
+TX_THREAD thread_6;
+TX_THREAD thread_7;
+TX_QUEUE queue_0;
+TX_SEMAPHORE semaphore_0;
+TX_MUTEX mutex_0;
+TX_EVENT_FLAGS_GROUP event_flags_0;
+TX_BYTE_POOL byte_pool_0;
+TX_BLOCK_POOL block_pool_0;
+
+
+/* Define the counters used in the demo application... */
+
+ULONG thread_0_counter;
+ULONG thread_1_counter;
+ULONG thread_1_messages_sent;
+ULONG thread_2_counter;
+ULONG thread_2_messages_received;
+ULONG thread_3_counter;
+ULONG thread_4_counter;
+ULONG thread_5_counter;
+ULONG thread_6_counter;
+ULONG thread_7_counter;
+
+
+/* Define thread prototypes. */
+
+void thread_0_entry(ULONG thread_input);
+void thread_1_entry(ULONG thread_input);
+void thread_2_entry(ULONG thread_input);
+void thread_3_and_4_entry(ULONG thread_input);
+void thread_5_entry(ULONG thread_input);
+void thread_6_and_7_entry(ULONG thread_input);
+
+
+/* Define main entry point. */
+
+int main()
+{
+
+ /* Enter the ThreadX kernel. */
+ tx_kernel_enter();
+}
+
+
+/* Define what the initial system looks like. */
+
+void tx_application_define(void *first_unused_memory)
+{
+ CHAR *pointer = TX_NULL;
+
+ /* Create a byte memory pool from which to allocate the thread stacks. */
+ tx_byte_pool_create(&byte_pool_0, "byte pool 0", first_unused_memory, DEMO_BYTE_POOL_SIZE);
+
+ /* Put system definition stuff in here, e.g. thread creates and other assorted
+ create information. */
+
+ /* Allocate the stack for thread 0. */
+ tx_byte_allocate(&byte_pool_0, (VOID **) &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);
+
+ /* Create the main thread. */
+ tx_thread_create(&thread_0, "thread 0", thread_0_entry, 0,
+ pointer, DEMO_STACK_SIZE,
+ 1, 1, TX_NO_TIME_SLICE, TX_AUTO_START);
+
+
+ /* Allocate the stack for thread 1. */
+ tx_byte_allocate(&byte_pool_0, (VOID **) &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);
+
+ /* Create threads 1 and 2. These threads pass information through a ThreadX
+ message queue. It is also interesting to note that these threads have a time
+ slice. */
+ tx_thread_create(&thread_1, "thread 1", thread_1_entry, 1,
+ pointer, DEMO_STACK_SIZE,
+ 16, 16, 4, TX_AUTO_START);
+
+ /* Allocate the stack for thread 2. */
+ tx_byte_allocate(&byte_pool_0, (VOID **) &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);
+
+ tx_thread_create(&thread_2, "thread 2", thread_2_entry, 2,
+ pointer, DEMO_STACK_SIZE,
+ 16, 16, 4, TX_AUTO_START);
+
+ /* Allocate the stack for thread 3. */
+ tx_byte_allocate(&byte_pool_0, (VOID **) &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);
+
+ /* Create threads 3 and 4. These threads compete for a ThreadX counting semaphore.
+ An interesting thing here is that both threads share the same instruction area. */
+ tx_thread_create(&thread_3, "thread 3", thread_3_and_4_entry, 3,
+ pointer, DEMO_STACK_SIZE,
+ 8, 8, TX_NO_TIME_SLICE, TX_AUTO_START);
+
+ /* Allocate the stack for thread 4. */
+ tx_byte_allocate(&byte_pool_0, (VOID **) &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);
+
+ tx_thread_create(&thread_4, "thread 4", thread_3_and_4_entry, 4,
+ pointer, DEMO_STACK_SIZE,
+ 8, 8, TX_NO_TIME_SLICE, TX_AUTO_START);
+
+ /* Allocate the stack for thread 5. */
+ tx_byte_allocate(&byte_pool_0, (VOID **) &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);
+
+ /* Create thread 5. This thread simply pends on an event flag which will be set
+ by thread_0. */
+ tx_thread_create(&thread_5, "thread 5", thread_5_entry, 5,
+ pointer, DEMO_STACK_SIZE,
+ 4, 4, TX_NO_TIME_SLICE, TX_AUTO_START);
+
+ /* Allocate the stack for thread 6. */
+ tx_byte_allocate(&byte_pool_0, (VOID **) &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);
+
+ /* Create threads 6 and 7. These threads compete for a ThreadX mutex. */
+ tx_thread_create(&thread_6, "thread 6", thread_6_and_7_entry, 6,
+ pointer, DEMO_STACK_SIZE,
+ 8, 8, TX_NO_TIME_SLICE, TX_AUTO_START);
+
+ /* Allocate the stack for thread 7. */
+ tx_byte_allocate(&byte_pool_0, (VOID **) &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);
+
+ tx_thread_create(&thread_7, "thread 7", thread_6_and_7_entry, 7,
+ pointer, DEMO_STACK_SIZE,
+ 8, 8, TX_NO_TIME_SLICE, TX_AUTO_START);
+
+ /* Allocate the message queue. */
+ tx_byte_allocate(&byte_pool_0, (VOID **) &pointer, DEMO_QUEUE_SIZE*sizeof(ULONG), TX_NO_WAIT);
+
+ /* Create the message queue shared by threads 1 and 2. */
+ tx_queue_create(&queue_0, "queue 0", TX_1_ULONG, pointer, DEMO_QUEUE_SIZE*sizeof(ULONG));
+
+ /* Create the semaphore used by threads 3 and 4. */
+ tx_semaphore_create(&semaphore_0, "semaphore 0", 1);
+
+ /* Create the event flags group used by threads 1 and 5. */
+ tx_event_flags_create(&event_flags_0, "event flags 0");
+
+ /* Create the mutex used by thread 6 and 7 without priority inheritance. */
+ tx_mutex_create(&mutex_0, "mutex 0", TX_NO_INHERIT);
+
+ /* Allocate the memory for a small block pool. */
+ tx_byte_allocate(&byte_pool_0, (VOID **) &pointer, DEMO_BLOCK_POOL_SIZE, TX_NO_WAIT);
+
+ /* Create a block memory pool to allocate a message buffer from. */
+ tx_block_pool_create(&block_pool_0, "block pool 0", sizeof(ULONG), pointer, DEMO_BLOCK_POOL_SIZE);
+
+ /* Allocate a block and release the block memory. */
+ tx_block_allocate(&block_pool_0, (VOID **) &pointer, TX_NO_WAIT);
+
+ /* Release the block back to the pool. */
+ tx_block_release(pointer);
+}
+
+
+
+/* Define the test threads. */
+
+void thread_0_entry(ULONG thread_input)
+{
+
+UINT status;
+
+
+ /* This thread simply sits in while-forever-sleep loop. */
+ while(1)
+ {
+ puts("[Thread] : thread_0_entry is here!");
+
+ /* Increment the thread counter. */
+ thread_0_counter++;
+
+ /* Sleep for 10 ticks. */
+ tx_thread_sleep(10);
+
+ /* Set event flag 0 to wakeup thread 5. */
+ status = tx_event_flags_set(&event_flags_0, 0x1, TX_OR);
+
+ /* Check status. */
+ if (status != TX_SUCCESS)
+ break;
+ }
+}
+
+
+void thread_1_entry(ULONG thread_input)
+{
+
+UINT status;
+
+ /* This thread simply sends messages to a queue shared by thread 2. */
+ while(1)
+ {
+ puts("[Thread] : thread_1_entry is here!");
+ /* Increment the thread counter. */
+ thread_1_counter++;
+
+ /* Send message to queue 0. */
+ status = tx_queue_send(&queue_0, &thread_1_messages_sent, TX_WAIT_FOREVER);
+
+ /* Check completion status. */
+ if (status != TX_SUCCESS) {
+ puts("[Thread 1] ERROR: Failed to send message!");
+ break;
+ }
+
+ /* Increment the message sent. */
+ thread_1_messages_sent++;
+ }
+}
+
+
+
+void thread_2_entry(ULONG thread_input)
+{
+
+ULONG received_message;
+UINT status;
+
+ /* This thread retrieves messages placed on the queue by thread 1. */
+ while(1)
+ {
+ puts("[Thread] : thread_2_entry is here!");
+ /* Increment the thread counter. */
+ thread_2_counter++;
+
+ /* Retrieve a message from the queue. */
+ status = tx_queue_receive(&queue_0, &received_message, TX_WAIT_FOREVER);
+
+ /* Check completion status and make sure the message is what we
+ expected. */
+ if ((status != TX_SUCCESS) || (received_message != thread_2_messages_received)){
+ puts("[Thread 2] ERROR: Failed to receive message ! Expected # ");
+ uart_puts(_to_str(thread_2_messages_received));
+ puts(", but got # ");
+ uart_puts(_to_str(received_message));
+ break;
+ }
+
+ /* Otherwise, all is okay. Increment the received message count. */
+ thread_2_messages_received++;
+ }
+}
+
+
+void thread_3_and_4_entry(ULONG thread_input)
+{
+
+UINT status;
+
+
+ /* This function is executed from thread 3 and thread 4. As the loop
+ below shows, these function compete for ownership of semaphore_0. */
+ while(1)
+ {
+ puts("[Thread] : thread_3_and_4_entry is here!");
+
+
+ /* Increment the thread counter. */
+ if (thread_input == 3)
+ thread_3_counter++;
+ else
+ thread_4_counter++;
+
+ /* Get the semaphore with suspension. */
+ status = tx_semaphore_get(&semaphore_0, TX_WAIT_FOREVER);
+
+ /* Check status. */
+ if (status != TX_SUCCESS)
+ break;
+
+ /* Sleep for 2 ticks to hold the semaphore. */
+ tx_thread_sleep(2);
+
+ /* Release the semaphore. */
+ status = tx_semaphore_put(&semaphore_0);
+
+ /* Check status. */
+ if (status != TX_SUCCESS)
+ break;
+ }
+}
+
+
+void thread_5_entry(ULONG thread_input)
+{
+
+UINT status;
+ULONG actual_flags;
+
+
+ /* This thread simply waits for an event in a forever loop. */
+ while(1)
+ {
+ puts("[Thread] : thread_5_entry is here!");
+
+ /* Increment the thread counter. */
+ thread_5_counter++;
+
+ /* Wait for event flag 0. */
+ status = tx_event_flags_get(&event_flags_0, 0x1, TX_OR_CLEAR,
+ &actual_flags, TX_WAIT_FOREVER);
+
+ /* Check status. */
+ if ((status != TX_SUCCESS) || (actual_flags != 0x1))
+ break;
+ }
+}
+
+
+void thread_6_and_7_entry(ULONG thread_input)
+{
+
+UINT status;
+
+
+ /* This function is executed from thread 6 and thread 7. As the loop
+ below shows, these function compete for ownership of mutex_0. */
+ while(1)
+ {
+ puts("[Thread] : thread_6_and_7_entry is here!");
+
+ /* Increment the thread counter. */
+ if (thread_input == 6)
+ thread_6_counter++;
+ else
+ thread_7_counter++;
+
+ /* Get the mutex with suspension. */
+ status = tx_mutex_get(&mutex_0, TX_WAIT_FOREVER);
+
+ /* Check status. */
+ if (status != TX_SUCCESS)
+ break;
+
+ /* Get the mutex again with suspension. This shows
+ that an owning thread may retrieve the mutex it
+ owns multiple times. */
+ status = tx_mutex_get(&mutex_0, TX_WAIT_FOREVER);
+
+ /* Check status. */
+ if (status != TX_SUCCESS)
+ break;
+
+ /* Sleep for 2 ticks to hold the mutex. */
+ tx_thread_sleep(2);
+
+ /* Release the mutex. */
+ status = tx_mutex_put(&mutex_0);
+
+ /* Check status. */
+ if (status != TX_SUCCESS)
+ break;
+
+ /* Release the mutex again. This will actually
+ release ownership since it was obtained twice. */
+ status = tx_mutex_put(&mutex_0);
+
+ /* Check status. */
+ if (status != TX_SUCCESS)
+ break;
+ }
+}
diff --git a/ports/risc-v32/gnu/example_build/cva6_ariane/entry.S b/ports/risc-v32/gnu/example_build/cva6_ariane/entry.S
new file mode 100644
index 00000000..f9645bc7
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/cva6_ariane/entry.S
@@ -0,0 +1,69 @@
+/***************************************************************************/
+/* Copyright (c) 2024 Microsoft Corporation */
+/* 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. */
+/* */
+/* SPDX-License-Identifier: MIT */
+/***************************************************************************/
+
+
+.section .text
+.align 4
+.global _start
+.extern main
+.extern _sysstack_start
+.extern _bss_start
+.extern _bss_end
+_start:
+ csrr t0, mhartid
+ bne t0, zero, 1f
+ li x1, 0
+ li x2, 0
+ li x3, 0
+ li x4, 0
+ li x5, 0
+ li x6, 0
+ li x7, 0
+ li x8, 0
+ li x9, 0
+ li x10, 0
+ li x11, 0
+ li x12, 0
+ li x13, 0
+ li x14, 0
+ li x15, 0
+ li x16, 0
+ li x17, 0
+ li x18, 0
+ li x19, 0
+ li x20, 0
+ li x21, 0
+ li x22, 0
+ li x23, 0
+ li x24, 0
+ li x25, 0
+ li x26, 0
+ li x27, 0
+ li x28, 0
+ li x29, 0
+ li x30, 0
+ li x31, 0
+ la t0, _sysstack_start
+ li t1, 0x1000
+ add sp, t0, t1
+ la t0, _bss_start
+ la t1, _bss_end
+_bss_clean_start:
+ bgeu t0, t1, _bss_clean_end
+ sb zero, 0(t0)
+ addi t0, t0, 1
+ j _bss_clean_start
+_bss_clean_end:
+ call main
+1:
+ /* todo smp */
+ wfi
+ j 1b
diff --git a/ports/risc-v32/gnu/example_build/cva6_ariane/hwtimer.c b/ports/risc-v32/gnu/example_build/cva6_ariane/hwtimer.c
new file mode 100644
index 00000000..b5335cf3
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/cva6_ariane/hwtimer.c
@@ -0,0 +1,35 @@
+/***************************************************************************
+ * Copyright (c) 2026 Quintauris
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: MIT
+ **************************************************************************/
+
+#include "tx_port.h"
+#include "csr.h"
+#include "hwtimer.h"
+
+#define CLINT (0x02000000L)
+#define CLINT_TIME (CLINT+0xBFF8)
+#define CLINT_TIMECMP(hart_id) (CLINT+0x4000+8*(hart_id))
+
+
+int hwtimer_init(void)
+{
+ int hart = riscv_get_core();
+ uint64_t time = *((uint64_t*)CLINT_TIME);
+ *((uint64_t*)CLINT_TIMECMP(hart)) = time + TICKNUM_PER_TIMER;
+ return 0;
+}
+
+int hwtimer_handler(void)
+{
+ int hart = riscv_get_core();
+ uint64_t time = *((uint64_t*)CLINT_TIME);
+ *((uint64_t*)CLINT_TIMECMP(hart)) = time + TICKNUM_PER_TIMER;
+ return 0;
+}
+
diff --git a/ports/risc-v32/gnu/example_build/cva6_ariane/hwtimer.h b/ports/risc-v32/gnu/example_build/cva6_ariane/hwtimer.h
new file mode 100644
index 00000000..9e4a066d
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/cva6_ariane/hwtimer.h
@@ -0,0 +1,24 @@
+
+/***************************************************************************
+ * Copyright (c) 2026 Quintauris
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: MIT
+ **************************************************************************/
+
+#ifndef RISCV_HWTIMER_H
+#define RISCV_HWTIMER_H
+
+#include <stdint.h>
+
+#define TICKNUM_PER_SECOND 50000000
+#define TICKNUM_PER_TIMER (TICKNUM_PER_SECOND / 100)
+
+int hwtimer_init(void);
+
+int hwtimer_handler(void);
+
+#endif
diff --git a/ports/risc-v32/gnu/example_build/cva6_ariane/link.lds b/ports/risc-v32/gnu/example_build/cva6_ariane/link.lds
new file mode 100644
index 00000000..619080e7
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/cva6_ariane/link.lds
@@ -0,0 +1,49 @@
+OUTPUT_ARCH( "riscv" )
+ENTRY( _start )
+
+SECTIONS
+{
+ /*
+ * ensure that entry.S / _entry is at 0x80000000,
+ * where arianes's -kernel jumps.
+ */
+ . = 0x80000000;
+
+ .text : {
+ *(.text .text.*)
+ . = ALIGN(0x1000);
+ PROVIDE(etext = .);
+ }
+
+ .rodata : {
+ . = ALIGN(16);
+ *(.srodata .srodata.*) /* do not need to distinguish this from .rodata */
+ . = ALIGN(16);
+ *(.rodata .rodata.*)
+ }
+
+ .data : {
+ . = ALIGN(16);
+ *(.sdata .sdata.*) /* do not need to distinguish this from .data */
+ . = ALIGN(16);
+ *(.data .data.*)
+ }
+
+ .bss : {
+ . = ALIGN(16);
+ _bss_start = .;
+ *(.sbss .sbss.*) /* do not need to distinguish this from .bss */
+ . = ALIGN(16);
+ *(.bss .bss.*)
+ _bss_end = .;
+ }
+
+ .stack : {
+ . = ALIGN(4096);
+ _sysstack_start = .;
+ . += 0x1000;
+ _sysstack_end = .;
+ }
+
+ PROVIDE(_end = .);
+}
diff --git a/ports/risc-v32/gnu/example_build/cva6_ariane/plic.c b/ports/risc-v32/gnu/example_build/cva6_ariane/plic.c
new file mode 120000
index 00000000..b247f4fe
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/cva6_ariane/plic.c
@@ -0,0 +1 @@
+../../../../risc-v_common/example_build/plic/plic.c \ No newline at end of file
diff --git a/ports/risc-v32/gnu/example_build/cva6_ariane/plic.h b/ports/risc-v32/gnu/example_build/cva6_ariane/plic.h
new file mode 120000
index 00000000..f776a3d2
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/cva6_ariane/plic.h
@@ -0,0 +1 @@
+../../../../risc-v_common/example_build/plic/plic.h \ No newline at end of file
diff --git a/ports/risc-v32/gnu/example_build/cva6_ariane/trap.c b/ports/risc-v32/gnu/example_build/cva6_ariane/trap.c
new file mode 100644
index 00000000..520b74a5
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/cva6_ariane/trap.c
@@ -0,0 +1,77 @@
+/***************************************************************************/
+/* Copyright (c) 2024 Microsoft Corporation */
+/* 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. */
+/* */
+/* SPDX-License-Identifier: MIT */
+/***************************************************************************/
+
+#include "csr.h"
+#include <stdint.h>
+#include "uart.h"
+#include "hwtimer.h"
+#include "plic.h"
+#include <tx_port.h>
+#include <tx_api.h>
+
+#define OS_IS_INTERUPT(mcause) (mcause & 0x80000000u)
+#define OS_IS_EXCEPTION(mcause) (~(OS_IS_INTERUPT))
+#define OS_IS_TICK_INT(mcause) (mcause == 0x80000007u)
+#define OS_IS_SOFT_INT(mcause) (mcause == 0x80000003u)
+#define OS_IS_EXT_INT(mcause) (mcause == 0x8000000bu)
+#define OS_IS_TRAP_USER(mcause) (mcause == 0x0000000bu)
+extern void _tx_timer_interrupt(void);
+
+extern int uart_putc(int ch);
+
+static void print_hex(uintptr_t val)
+{
+ char digits[] = "0123456789ABCDEF";
+ uart_putc('0');
+ uart_putc('x');
+ for(int i = (sizeof(uintptr_t)*2) - 1; i >= 0; i--) {
+ int d = (val >> (i*4)) & 0xF;
+ uart_putc(digits[d]);
+ }
+ uart_putc('\n');
+}
+
+void trap_handler(uintptr_t mcause, uintptr_t mepc, uintptr_t mtval)
+{
+ if(OS_IS_INTERUPT(mcause))
+ {
+ if(OS_IS_TICK_INT(mcause))
+ {
+ hwtimer_handler();
+ _tx_timer_interrupt();
+ }
+ else if(OS_IS_EXT_INT(mcause))
+ {
+ int ret = plic_irq_intr();
+ if(ret)
+ {
+ puts("[INTERRUPT]: handler irq error!");
+ while(1) ;
+ }
+ }
+ else
+ {
+ puts("[INTERRUPT]: now can't deal with the interrupt!");
+ while(1) ;
+ }
+ }
+ else
+ {
+ puts("[EXCEPTION] : Unkown Error!!");
+ puts("mcause:");
+ print_hex(mcause);
+ puts("mepc:");
+ print_hex(mepc);
+ puts("mtval:");
+ print_hex(mtval);
+ while(1) ;
+ }
+}
diff --git a/ports/risc-v32/gnu/example_build/cva6_ariane/tx_initialize_low_level.S b/ports/risc-v32/gnu/example_build/cva6_ariane/tx_initialize_low_level.S
new file mode 100644
index 00000000..16910f76
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/cva6_ariane/tx_initialize_low_level.S
@@ -0,0 +1,163 @@
+/***************************************************************************
+ * Copyright (c) 2026 Quintauris
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: MIT
+ **************************************************************************/
+
+#include "csr.h"
+#include "tx_port.h"
+
+ .section .text
+ .align 4
+/**************************************************************************/
+/* */
+/* FUNCTION RELEASE */
+/* */
+/* trap_entry RISC-V32/GNU */
+/* 6.4.x */
+/* AUTHOR */
+/* */
+/* Francisco Merino, Quintauris */
+/* */
+/* DESCRIPTION */
+/* */
+/* This function is responsible for riscv processor trap handle */
+/* It will do the contex save and call c trap_handler and do contex */
+/* load */
+/* */
+/* INPUT */
+/* */
+/* None */
+/* */
+/* OUTPUT */
+/* */
+/* None */
+/* */
+/* CALLS */
+/* */
+/* trap_handler */
+/* */
+/* CALLED BY */
+/* */
+/* hardware exception */
+/* */
+/**************************************************************************/
+
+
+/**************************************************************************/
+/**************************************************************************/
+/** */
+/** ThreadX Component */
+/** */
+/** Initialize */
+/** */
+/**************************************************************************/
+/**************************************************************************/
+ .global trap_entry
+ .extern trap_handler
+ .extern _tx_thread_context_restore
+ trap_entry:
+#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double)
+ addi sp, sp, -65*4 // Allocate space for all registers - with floating point enabled
+#else
+ addi sp, sp, -32*4 // Allocate space for all registers - without floating point enabled
+#endif
+
+ sw x1, 28*4(sp) // Store RA, 28*4(because call will override ra [ra is a calle register in riscv])
+
+ call _tx_thread_context_save
+
+ csrr a0, mcause
+ csrr a1, mepc
+ csrr a2, mtval
+ addi sp, sp, -4
+ sw ra, 0(sp)
+ call trap_handler
+ lw ra, 0(sp)
+ addi sp, sp, 4
+ call _tx_thread_context_restore
+ // it will nerver return
+_err:
+ wfi
+ j _err
+ .section .text
+/**************************************************************************/
+/* */
+/* FUNCTION RELEASE */
+/* */
+/* _tx_initialize_low_level RISC-V32/GNU */
+/* 6.4.x */
+/* AUTHOR */
+/* */
+/* Francisco Merino, Quintauris */
+/* */
+/* DESCRIPTION */
+/* */
+/* This function is responsible for any low-level processor */
+/* initialization, including setting up interrupt vectors, setting */
+/* up a periodic timer interrupt source, saving the system stack */
+/* pointer for use in ISR processing later, and finding the first */
+/* available RAM memory address for tx_application_define. */
+/* */
+/* INPUT */
+/* */
+/* None */
+/* */
+/* OUTPUT */
+/* */
+/* None */
+/* */
+/* CALLS */
+/* */
+/* None */
+/* */
+/* CALLED BY */
+/* */
+/* _tx_initialize_kernel_enter ThreadX entry function */
+/* */
+/* RELEASE HISTORY */
+/* */
+/* DATE NAME DESCRIPTION */
+/* */
+/* 11-03-2026 Francisco Merino Adapted for RV32 from RV64 port */
+/* */
+/**************************************************************************/
+/* VOID _tx_initialize_low_level(VOID)
+{ */
+ .global _tx_initialize_low_level
+ .weak _tx_initialize_low_level
+ .extern _end
+ .extern board_init
+_tx_initialize_low_level:
+
+ .section .text
+
+ la t0, _tx_thread_system_stack_ptr
+ sw sp, 0(t0) // Save system stack pointer
+
+ la t0, _end // Pickup first free address
+ la t1, _tx_initialize_unused_memory
+ sw t0, 0(t1) // Save unused memory address
+ li t0, MSTATUS_MIE
+ csrrc zero, mstatus, t0 // clear MSTATUS_MIE bit
+ li t0, (MSTATUS_MPP_M | MSTATUS_MPIE )
+ csrrs zero, mstatus, t0 // set MSTATUS_MPP, MPIE bit
+ li t0, (MIE_MTIE | MIE_MSIE | MIE_MEIE)
+ csrrs zero, mie, t0 // set mie
+#ifdef __riscv_flen
+ li t0, MSTATUS_FS
+ csrrs zero, mstatus, t0 // set MSTATUS_FS bit to open f/d isa in riscv
+ fscsr x0
+#endif
+ addi sp, sp, -4
+ sw ra, 0(sp)
+ call board_init
+ lw ra, 0(sp)
+ addi sp, sp, 4
+ la t0, trap_entry
+ csrw mtvec, t0
+ ret
diff --git a/ports/risc-v32/gnu/example_build/cva6_ariane/uart.c b/ports/risc-v32/gnu/example_build/cva6_ariane/uart.c
new file mode 100644
index 00000000..c92c67e6
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/cva6_ariane/uart.c
@@ -0,0 +1,893 @@
+/*******************************************************************************
+ * Copyright (c) 2020 Thales.
+ * Copyright 2019-2020 Microchip FPGA Embedded Systems Solutions.
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ */
+// Additional contributions by:
+// Sebastien Jacq - sjthales on github.com
+// Anjali Gedam - anjaliigedam on github.com
+//
+// Description: Driver for UART Ip of the CVA6 platform
+//
+// =========================================================================== //
+// Revisions :
+// Date Version Author Description
+// 2020-10-06 0.1 S.Jacq modification of the Test for CVA6 softcore
+// 2021-07-12 0.2 Anjali G modification for the CVA6 FPGA
+// =========================================================================== //
+
+
+#include "uart.h"
+
+//#include "../plic/plic.h"
+//#include "../hw_platform.h"
+#define FPGA_UART_0_FREQUENCY 50000000
+#define FPGA_UART_0_BASE (UART_TypeDef *)0x10000000
+#define STDIO_BAUD_RATE UART_115200_BAUD
+
+/*******************************************************************************
+ * Defines
+ */
+#define TX_COMPLETE 0u
+#define TX_FIFO_SIZE 16u
+
+#define FCR_TRIG_LEVEL_MASK 0xC0u
+
+#define IIRF_MASK 0x0Fu
+
+#define INVALID_INTERRUPT 0u
+#define INVALID_IRQ_HANDLER ((uart_irq_handler_t) 0)
+#define NULL_HANDLER ((uart_irq_handler_t) 0)
+
+#define UART_DATA_READY ((uint8_t) 0x01)
+
+
+/*******************************************************************************
+ * Possible values for Interrupt Identification Register Field.
+ */
+#define IIRF_MODEM_STATUS 0x00u
+#define IIRF_THRE 0x02u
+//#define IIRF_MMI 0x03u
+#define IIRF_RX_DATA 0x04u
+#define IIRF_RX_LINE_STATUS 0x06u
+#define IIRF_DATA_TIMEOUT 0x0Cu
+
+
+//#define PLIC_DEMO 1
+// Source/portable/GCC/RISC-V/portASM.S:290:freertos_risc_v_application_interrupt_handler (weak function : User application can add there own override)
+
+uart_instance_t g_uart_0 = { .hw_reg = FPGA_UART_0_BASE };
+
+/*******************************************************************************
+ * Global initialization for all modes
+ */
+static void global_init
+(
+ uart_instance_t * this_uart,
+ uint32_t baud_rate,
+ uint8_t line_config
+)
+{
+
+ /* disable interrupts */
+ this_uart->hw_reg->IER = 0u;
+
+ /* FIFO configuration */
+ this_uart->hw_reg->FCR = 0u;
+
+ /* clear receiver FIFO */
+ this_uart->hw_reg->FCR = FIFO_RX_TRIGGER_LEVEL_14_MASK | CLEAR_RX_FIFO_MASK | CLEAR_TX_FIFO_MASK | RXRDY_TXRDYN_EN_MASK;
+
+ /* clear transmitter FIFO */
+ this_uart->hw_reg->FCR |= CLEAR_TX_FIFO_MASK;
+
+ /* set default READY mode : Mode 0*/
+ /* enable RXRDYN and TXRDYN pins. The earlier FCR write to set the TX FIFO
+ * trigger level inadvertently disabled the FCR_RXRDY_TXRDYN_EN bit. */
+ this_uart->hw_reg->FCR |= RXRDY_TXRDYN_EN_MASK;
+
+ this_uart->hw_reg->MCR = 0u;
+
+
+
+ /*
+ * Configure baud rate divisors. This uses the fractional baud rate divisor
+ * where possible to provide the most accurate baud rat possible.
+ */
+ config_baud_divisors(this_uart, baud_rate);
+
+ /* set the line control register (bit length, stop bits, parity) */
+ this_uart->hw_reg->LCR = line_config;
+
+ /* Instance setup */
+ this_uart->baudrate = baud_rate;
+ this_uart->lineconfig = line_config;
+ this_uart->tx_buff_size = TX_COMPLETE;
+ this_uart->tx_buffer = (const uint8_t*)0;
+ this_uart->tx_idx = 0u;
+
+ /* Default handlers for MSS UART interrupts */
+ this_uart->rx_handler = NULL_HANDLER;
+ this_uart->tx_handler = NULL_HANDLER;
+ this_uart->linests_handler = NULL_HANDLER;
+ this_uart->modemsts_handler = NULL_HANDLER;
+
+ /* Initialize the sticky status */
+ this_uart->status = 0u;
+}
+
+/*******************************************************************************
+ * Public Functions
+ *******************************************************************************/
+/***************************************************************************//**
+ * See uart.h for details of how to use this function.
+ */
+void
+UART_init
+(
+ uart_instance_t* this_uart,
+ uint32_t baud_rate,
+ uint8_t line_config
+)
+{
+ /* Perform generic initialization */
+ global_init(this_uart, baud_rate, line_config);
+
+
+ /* set default tx handler for automated TX using interrupt in USART mode */
+ this_uart->tx_handler = default_tx_handler;
+}
+
+
+
+/***************************************************************************//**
+ * See uart.h for details of how to use this function.
+ */
+void
+UART_polled_tx
+(
+ uart_instance_t * this_uart,
+ const uint8_t * pbuff,
+ uint32_t tx_size
+)
+{
+ uint32_t char_idx = 0u;
+ // uint32_t size_sent;
+ uint8_t status;
+ //uint32_t temp_tx_size = tx_size;
+
+ //ASSERT(pbuff != ( (uint8_t*)0));
+ //ASSERT(tx_size > 0u);
+
+ if ((pbuff != ((uint8_t*)0)) && (tx_size > 0u))
+ {
+ /* Remain in this loop until the entire input buffer
+ * has been transferred to the UART.
+ */
+ do
+ {
+ /* Wait until TX FIFO is empty. */
+ do
+ {
+ status = this_uart->hw_reg->LSR;
+ // this_uart->status |= status;
+ }while (0u == (status & UART_THRE));
+
+
+ /* Check if TX FIFO is empty. */
+ // if (status & UART_THRE)
+ //{
+ // uint32_t fill_size = TX_FIFO_SIZE;
+
+ /* Calculate the number of bytes to transmit. */
+ //if (temp_tx_size < TX_FIFO_SIZE)
+ //{
+ // fill_size = temp_tx_size;
+ //}
+
+ /* Fill the TX FIFO with the calculated the number of bytes. */
+ //for (size_sent = 0u; size_sent < fill_size; ++size_sent)
+ //{
+ /* Send next character in the buffer. */
+ this_uart->hw_reg->THR = pbuff[char_idx];
+ char_idx++;
+ //}
+
+ /* Calculate the number of bytes remaining(not transmitted yet)*/
+ //temp_tx_size -= size_sent;
+ //}
+ }while (char_idx < tx_size);
+ }
+}
+
+
+/***************************************************************************//**
+ * See uart.h for details of how to use this function.
+ */
+void
+UART_polled_tx_string
+(
+ uart_instance_t * this_uart,
+ const uint8_t * p_sz_string
+)
+{
+ uint32_t char_idx = 0u;
+ //uint32_t fill_size;
+ uint8_t data_byte;
+ volatile uint8_t status;
+
+ //ASSERT(p_sz_string != ((uint8_t*)0));
+
+ if (p_sz_string != ((uint8_t*)0))
+ {
+ /* Get the first data byte from the input buffer */
+ data_byte = p_sz_string[char_idx];
+
+ /* First check for the NULL terminator byte.
+ * Then remain in this loop until the entire string in the input buffer
+ * has been transferred to the UART.
+ */
+ while (0u != data_byte)
+ {
+ /* Wait until TX FIFO is empty. */
+ do
+ {
+ status = this_uart->hw_reg->LSR;
+ // this_uart->status |= status;
+ }while (0u == (status & UART_THRE));
+
+
+ /* Send bytes from the input buffer until the TX FIFO is full
+ * or we reach the NULL terminator byte.
+ */
+ //fill_size = 0u;
+
+ // while ((0u != data_byte) && (fill_size < TX_FIFO_SIZE))
+ //{
+ /* Send the data byte */
+ this_uart->hw_reg->THR = data_byte;
+ //++fill_size;
+ char_idx++;
+ /* Get the next data byte from the input buffer */
+ data_byte = p_sz_string[char_idx];
+ //}
+ }
+ }
+}
+
+/***************************************************************************//**
+ * See uart.h for details of how to use this function.
+ */
+void
+UART_irq_tx
+(
+ uart_instance_t * this_uart,
+ const uint8_t * pbuff,
+ uint32_t tx_size
+)
+{
+ //ASSERT(pbuff != ((uint8_t*)0));
+ //ASSERT(tx_size > 0u);
+
+ if ((tx_size > 0u) && (pbuff != ((uint8_t*)0)))
+ {
+ /*Initialize the transmit info for the UART instance with the arguments*/
+ this_uart->tx_buffer = pbuff;
+ this_uart->tx_buff_size = tx_size;
+ this_uart->tx_idx = 0u;
+
+ /* assign default handler for data transfer */
+ this_uart->tx_handler = default_tx_handler;
+
+ /* enables TX interrupt */
+ this_uart->hw_reg->IER |= ETBEI_MASK;
+ enable_irq(this_uart);
+ }
+}
+
+/***************************************************************************//**
+ * See uart.h for details of how to use this function.
+ */
+int8_t
+UART_tx_complete
+(
+ uart_instance_t * this_uart
+)
+{
+ int8_t ret_value = 0;
+ uint8_t status = 0u;
+
+ /* Read the Line Status Register and update the sticky record. */
+ status = this_uart->hw_reg->LSR;
+ this_uart->status |= status;
+
+ if ((TX_COMPLETE == this_uart->tx_buff_size) &&
+ ((status & UART_TEMT) != 0u))
+ {
+ ret_value = (int8_t)1;
+ }
+
+ return ret_value;
+}
+
+
+/***************************************************************************//**
+ * See uart.h for details of how to use this function.
+ */
+size_t
+UART_get_rx
+(
+ uart_instance_t * this_uart,
+ uint8_t * rx_buff,
+ size_t buff_size
+)
+{
+ size_t rx_size = 0u;
+ uint8_t status = 0u;
+
+ //ASSERT(rx_buff != ((uint8_t*)0));
+ //ASSERT(buff_size > 0u);
+ //printf("UART_get_rx() called buff_size = %d\n", buff_size);
+
+ if ((rx_buff != (uint8_t*)0) && (buff_size > 0u))
+ {
+ status = this_uart->hw_reg->LSR;
+ this_uart->status |= status;
+ //printf("UART_get_rx() LSR status = %d\n", status);
+
+ while (((status & UART_DATA_READY) != 0u) && (rx_size < buff_size))
+ {
+ rx_buff[rx_size] = this_uart->hw_reg->RBR;
+ ++rx_size;
+ status = this_uart->hw_reg->LSR;
+ //printf("1 UART_get_rx() LSR status = %d\n", status);
+ this_uart->status |= status;
+ //printf("2 UART_get_rx() LSR status = %d\n", status);
+
+ }
+ }
+
+ return rx_size;
+}
+
+/***************************************************************************//**
+ * See uart.h for details of how to use this function.
+ */
+void
+UART_enable_irq
+(
+ uart_instance_t * this_uart,
+ uart_irq_t irq_mask
+)
+{
+ //ASSERT(UART_INVALID_IRQ > irq_mask);
+
+ enable_irq(this_uart);
+
+ if (UART_INVALID_IRQ > irq_mask)
+ {
+ /* irq_mask encoding: 1- enable
+ * bit 0 - Receive Data Available Interrupt
+ * bit 1 - Transmitter Holding Register Empty Interrupt
+ * bit 2 - Receiver Line Status Interrupt
+ * bit 3 - Modem Status Interrupt
+ */
+ this_uart->hw_reg->IER |= ((uint8_t)(((uint32_t)irq_mask &
+ (uint32_t)IIRF_MASK)));
+
+ }
+}
+
+
+/***************************************************************************//**
+ * See uart.h for details of how to use this function.
+ */
+void
+UART_set_rx_handler
+(
+ uart_instance_t * this_uart,
+ uart_irq_handler_t handler,
+ uart_rx_trig_level_t trigger_level
+)
+{
+ //ASSERT(handler != INVALID_IRQ_HANDLER );
+ //ASSERT(trigger_level < UART_FIFO_INVALID_TRIG_LEVEL);
+#ifdef PLIC_DEMO
+ PLIC_init();
+#endif
+
+ if ((handler != INVALID_IRQ_HANDLER) &&
+ (trigger_level < UART_FIFO_INVALID_TRIG_LEVEL))
+ {
+ //printf("UART DEBUG1\n");
+ this_uart->rx_handler = handler;
+
+ /* Set the receive interrupt trigger level. */
+ this_uart->hw_reg->FCR = (this_uart->hw_reg->FCR &
+ (uint8_t)(~((uint8_t)FCR_TRIG_LEVEL_MASK))) |
+ (uint8_t)trigger_level;
+
+ /* Enable receive interrupt. */
+ this_uart->hw_reg->IER |= ERBFI_MASK;
+ //printf("UART Enable IRQ\n");
+ enable_irq(this_uart);
+ }
+}
+
+/***************************************************************************//**
+ * See uart.h for details of how to use this function.
+ */
+void
+UART_set_tx_handler
+(
+ uart_instance_t * this_uart,
+ uart_irq_handler_t handler
+)
+{
+ //ASSERT(handler != INVALID_IRQ_HANDLER);
+
+ if (handler != INVALID_IRQ_HANDLER)
+ {
+ this_uart->tx_handler = handler;
+
+ /* Make TX buffer info invalid */
+ this_uart->tx_buffer = (const uint8_t*)0;
+ this_uart->tx_buff_size = 0u;
+
+ /* Enable transmitter holding register Empty interrupt. */
+ this_uart->hw_reg->IER |= ETBEI_MASK;
+ enable_irq(this_uart);
+ }
+}
+
+/***************************************************************************//**
+ * See uart.h for details of how to use this function.
+ */
+void
+UART_set_modemstatus_handler
+(
+ uart_instance_t * this_uart,
+ uart_irq_handler_t handler
+)
+{
+ //ASSERT(handler != INVALID_IRQ_HANDLER);
+
+ if (handler != INVALID_IRQ_HANDLER)
+ {
+ this_uart->modemsts_handler = handler;
+
+ /* Enable modem status interrupt. */
+ this_uart->hw_reg->IER |= EDSSI_MASK;
+ enable_irq(this_uart);
+ }
+}
+
+
+/***************************************************************************//**
+ * See uart.h for details of how to use this function.
+ */
+size_t
+UART_fill_tx_fifo
+(
+ uart_instance_t * this_uart,
+ const uint8_t * tx_buffer,
+ size_t tx_size
+)
+{
+ uint8_t status = 0u;
+ uint32_t size_sent = 0u;
+
+ //ASSERT(tx_buffer != ( (uint8_t*)0));
+ //ASSERT(tx_size > 0);
+
+ /* Fill the UART's Tx FIFO until the FIFO is full or the complete input
+ * buffer has been written. */
+ if ((tx_buffer != ((uint8_t*)0)) && (tx_size > 0u))
+ {
+ status = this_uart->hw_reg->LSR;
+ this_uart->status |= status;
+
+ if (status & UART_THRE)
+ {
+ uint32_t fill_size = TX_FIFO_SIZE;
+
+ if (tx_size < TX_FIFO_SIZE)
+ {
+ fill_size = tx_size;
+ }
+
+ /* Fill up FIFO */
+ for (size_sent = 0u; size_sent < fill_size; size_sent++)
+ {
+ /* Send next character in the buffer. */
+ this_uart->hw_reg->THR = tx_buffer[size_sent];
+ }
+ }
+ }
+
+ return size_sent;
+}
+
+/***************************************************************************//**
+ * See uart.h for details of how to use this function.
+ */
+uint8_t
+UART_get_rx_status
+(
+ uart_instance_t * this_uart
+)
+{
+ uint8_t status = UART_INVALID_PARAM;
+
+ /*
+ * Extract UART receive error status.
+ * Bit 1 - Overflow error status
+ * Bit 2 - Parity error status
+ * Bit 3 - Frame error status
+ * Bit 4 - Break interrupt indicator
+ * Bit 7 - FIFO data error status
+ */
+ this_uart->status |= (this_uart->hw_reg->LSR);
+ status = (this_uart->status & STATUS_ERROR_MASK);
+
+ //printf("UART_get_rx_status = %d", status);
+ //printf("UART_get_rx_LSR = %d", this_uart->hw_reg->LSR);
+
+
+ /* Clear the sticky status after reading */
+ this_uart->status = 0u;
+
+ return status;
+}
+
+/***************************************************************************//**
+ * See uart.h for details of how to use this function.
+ */
+uint8_t
+UART_get_modem_status
+(
+ const uart_instance_t * this_uart
+)
+{
+ uint8_t status = UART_INVALID_PARAM;
+
+ /*
+ * Extract UART modem status and place in lower bits of "status".
+ * Bit 0 - Delta Clear to Send Indicator
+ * Bit 1 - Delta Clear to Receive Indicator
+ * Bit 2 - Trailing edge of Ring Indicator detector
+ * Bit 3 - Delta Data Carrier Detect indicator
+ * Bit 4 - Clear To Send
+ * Bit 5 - Data Set Ready
+ * Bit 6 - Ring Indicator
+ * Bit 7 - Data Carrier Detect
+ */
+ status = this_uart->hw_reg->MSR;
+ //printf("UART_get_modem_status = %d", status);
+
+ return status;
+}
+
+
+/***************************************************************************//**
+ * UART_get_tx_status.
+ * See uart.h for details of how to use this function.
+ */
+uint8_t
+UART_get_tx_status
+(
+ uart_instance_t * this_uart
+)
+{
+ uint8_t status = UART_TX_BUSY;
+
+ /* Read the Line Status Register and update the sticky record. */
+ status = this_uart->hw_reg->LSR;
+ this_uart->status |= status;
+
+ /*
+ * Extract the transmit status bits from the UART's Line Status Register.
+ * Bit 5 - Transmitter Holding Register/FIFO Empty (THRE) status.
+ (If = 1, TX FIFO is empty)
+ * Bit 6 - Transmitter Empty (TEMT) status.
+ (If = 1, both TX FIFO and shift register are empty)
+ */
+ status &= (UART_THRE | UART_TEMT);
+
+ return status;
+}
+
+/***************************************************************************//**
+ * See uart.h for details of how to use this function.
+ */
+void
+UART_set_break
+(
+ uart_instance_t * this_uart
+)
+{
+ /* set break character on Tx line */
+ this_uart->hw_reg->LCR |= SB_MASK;
+}
+
+
+/***************************************************************************//**
+ * See uart.h for details of how to use this function.
+ */
+void
+UART_clear_break
+(
+ uart_instance_t * this_uart
+)
+{
+ /* remove break character from Tx line */
+ this_uart->hw_reg->LCR &= ~SB_MASK;
+}
+
+/***************************************************************************//**
+ * Configure baud divisors using fractional baud rate if possible.
+ */
+static void
+config_baud_divisors
+(
+ uart_instance_t * this_uart,
+ uint32_t baudrate
+)
+{
+ uint32_t baud_value;
+ uint32_t baud_value_by_64;
+ uint32_t baud_value_by_128;
+// uint32_t fractional_baud_value;
+ /* Note that we may need to set this as 64 bits for high frequencies */
+ uint32_t pclk_freq;
+
+ this_uart->baudrate = baudrate;
+
+ /* Use the system clock value from hw_platform.h */
+ pclk_freq = FPGA_UART_0_FREQUENCY;
+
+ /*
+ * Compute baud value based on requested baud rate and PCLK frequency.
+ * The baud value is computed using the following equation:
+ * baud_value = PCLK_Frequency / (baud_rate * 16)
+ */
+ baud_value_by_128 = (uint32_t)((8UL * pclk_freq) / baudrate);
+ baud_value_by_64 = baud_value_by_128 / 2u;
+ baud_value = baud_value_by_64 / 64u;
+// fractional_baud_value = baud_value_by_64 - (baud_value * 64u);
+// fractional_baud_value += (baud_value_by_128 - (baud_value * 128u))
+ // - (fractional_baud_value * 2u);
+
+ /* //ASSERT if integer baud value fits in 16-bit. */
+ //ASSERT(baud_value <= UINT16_MAX);
+
+ if (baud_value <= (uint32_t)UINT16_MAX)
+ {
+
+ /*
+ * Use Fractional baud rate divisors
+ */
+ /* set divisor latch */
+ this_uart->hw_reg->LCR = DLAB_MASK;
+
+ /* msb of baud value */
+ this_uart->hw_reg->DLM = (uint8_t)(baud_value >> 8);
+ /* lsb of baud value */
+ this_uart->hw_reg->DLL = (uint8_t)baud_value;
+
+ /* reset divisor latch */
+ this_uart->hw_reg->LCR = 0;
+ }
+}
+
+/***************************************************************************//**
+ * Interrupt service routine triggered by any MSS UART interrupt. This routine
+ * will call the handler function appropriate to the interrupt from the
+ * handlers previously registered with the driver through calls to the
+ * UART_set_*_handler() functions, or it will call the default_tx_handler()
+ * function in response to transmit interrupts if UART_irq_tx() is used to
+ * transmit data.
+ */
+static void __attribute__ ((unused))
+uart_isr
+(
+ uart_instance_t * this_uart
+)
+{
+ //printf("uart_isr called\n");
+ uint8_t iirf;
+
+ iirf = this_uart->hw_reg->IIR & IIRF_MASK;
+
+ switch (iirf)
+ {
+ case IIRF_MODEM_STATUS: /* Modem status interrupt */
+ {
+ //ASSERT(NULL_HANDLER != this_uart->modemsts_handler);
+ if (NULL_HANDLER != this_uart->modemsts_handler)
+ {
+ (*(this_uart->modemsts_handler))(this_uart);
+ }
+ }
+ break;
+
+ case IIRF_THRE: /* Transmitter Holding Register Empty */
+ {
+ //ASSERT(NULL_HANDLER != this_uart->tx_handler);
+ if (NULL_HANDLER != this_uart->tx_handler)
+ {
+ (*(this_uart->tx_handler))(this_uart);
+ }
+ }
+ break;
+
+ case IIRF_RX_DATA: /* Received Data Available */
+ case IIRF_DATA_TIMEOUT: /* Received Data Timed-out */
+ {
+ //ASSERT(NULL_HANDLER != this_uart->rx_handler);
+ if (NULL_HANDLER != this_uart->rx_handler)
+ {
+ (*(this_uart->rx_handler))(this_uart);
+ }
+ }
+ break;
+
+ case IIRF_RX_LINE_STATUS: /* Line Status Interrupt */
+ {
+ //ASSERT(NULL_HANDLER != this_uart->linests_handler);
+ if (NULL_HANDLER != this_uart->linests_handler)
+ {
+ (*(this_uart->linests_handler))(this_uart);
+ }
+ }
+ break;
+
+ default:
+ {
+ //ASSERT(INVALID_INTERRUPT); /*Alternative case has been considered*/
+ }
+ break;
+ }
+}
+
+
+
+/***************************************************************************//**
+ * See uart.h for details of how to use this function.
+ */
+static void
+default_tx_handler
+(
+ uart_instance_t * this_uart
+)
+{
+ uint8_t status;
+
+ //ASSERT(( (uint8_t*)0 ) != this_uart->tx_buffer);
+ //ASSERT(0u < this_uart->tx_buff_size);
+
+ if ((((uint8_t*)0 ) != this_uart->tx_buffer) &&
+ (0u < this_uart->tx_buff_size))
+ {
+ /* Read the Line Status Register and update the sticky record. */
+ status = this_uart->hw_reg->LSR;
+ this_uart->status |= status;
+
+ /*
+ * This function should only be called as a result of a THRE interrupt.
+ * Verify that this is true before proceeding to transmit data.
+ */
+ if (status & UART_THRE)
+ {
+ uint32_t cnt;
+ uint32_t fill_size = TX_FIFO_SIZE;
+ uint32_t tx_remain = this_uart->tx_buff_size - this_uart->tx_idx;
+
+ /* Calculate the number of bytes to transmit. */
+ if (tx_remain < TX_FIFO_SIZE)
+ {
+ fill_size = tx_remain;
+ }
+
+ /* Fill the TX FIFO with the calculated the number of bytes. */
+ for (cnt = 0u; cnt < fill_size; ++cnt)
+ {
+ /* Send next character in the buffer. */
+ this_uart->hw_reg->THR = this_uart->tx_buffer[this_uart->tx_idx];
+ ++this_uart->tx_idx;
+ }
+ }
+
+ /* Flag Tx as complete if all data has been pushed into the Tx FIFO. */
+ if (this_uart->tx_idx == this_uart->tx_buff_size)
+ {
+ this_uart->tx_buff_size = TX_COMPLETE;
+
+ /* disables TX interrupt */
+ this_uart->hw_reg->IER &= ~ETBEI_MASK;
+ }
+ }
+}
+
+
+static void __attribute__ ((unused))
+enable_irq
+(
+ const uart_instance_t * this_uart
+)
+{
+
+#ifdef PLIC_DEMO
+ PLIC_IRQn_Type plic_num = 0;
+#endif
+
+ if (&g_uart_0 == this_uart )
+ {
+ //printf("enable_irq() UART assign plic_num = %d\n",plic_num);
+#ifdef PLIC_DEMO
+ plic_num = UART_0_PLIC_IRQHandler;
+#endif
+
+ }
+ else
+ {
+ //printf("ASSERT(0) called\n");
+ //ASSERT(0); /*Alternative case has been considered*/
+ }
+
+#ifdef PLIC_DEMO
+ /* Enable UART instance interrupt in PLIC. */
+ PLIC_EnableIRQ(plic_num);
+#endif
+}
+
+static void __attribute__ ((unused))
+disable_irq
+(
+ const uart_instance_t * this_uart
+)
+{
+#ifdef PLIC_DEMO
+ PLIC_IRQn_Type plic_num = 0;
+#endif
+
+ if (&g_uart_0 == this_uart )
+ {
+#ifdef PLIC_DEMO
+ plic_num = UART_0_PLIC_IRQHandler;
+#endif
+ }
+ else
+ {
+ //ASSERT(0); /*Alternative case has been considered*/
+ }
+
+ /* Disable UART instance interrupt in PLIC. */
+#ifdef PLIC_DEMO
+ PLIC_DisableIRQ(plic_num);
+#endif
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////
+
+static uart_instance_t * const gp_my_uart = &g_uart_0;
+
+int uart_init(void)
+{
+ UART_init(gp_my_uart, STDIO_BAUD_RATE, ( UART_DATA_8_BITS | UART_NO_PARITY | UART_ONE_STOP_BIT ) );
+
+ return 0;
+}
+
+int uart_putc(int ch)
+{
+ UART_polled_tx(gp_my_uart, (const uint8_t *)&ch, 1);
+}
+
+int uart_puts(const char* str)
+{
+ UART_polled_tx_string(gp_my_uart, str);
+} \ No newline at end of file
diff --git a/ports/risc-v32/gnu/example_build/cva6_ariane/uart.h b/ports/risc-v32/gnu/example_build/cva6_ariane/uart.h
new file mode 100644
index 00000000..0a835727
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/cva6_ariane/uart.h
@@ -0,0 +1,961 @@
+/***************************************************************************
+ * Copyright (c) 2024 Microsoft Corporation
+ * 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.
+ *
+ * SPDX-License-Identifier: MIT
+ **************************************************************************/
+
+#ifndef RISCV_UART_H
+#define RISCV_UART_H
+
+#define UART0 0x10000000L
+#define UART0_IRQ 10
+
+#define puts(str) uart_puts(str"\n\r")
+int uart_init(void);
+int uart_putc(int ch);
+int uart_puts(const char* str);
+#endif
+
+
+/*******************************************************************************
+ * Copyright (c) 2020 Thales.
+ * Copyright 2019-2020 Microchip FPGA Embedded Systems Solutions.
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ *
+ */
+// Additional contributions by:
+// Sebastien Jacq - sjthales on github.com
+// Anjali Gedam - anjaliigedam on github.com
+//
+// Description: Driver header for UART Ip of the CVA6 platform
+//
+// =========================================================================== //
+// Revisions :
+// Date Version Author Description
+// 2020-10-06 0.1 S.Jacq modification of the Test for CVA6 softcore
+// 2021-07-12 0.2 Anjali G modification for the CVA6 FPGA
+// =========================================================================== //
+
+#ifndef __UART1_H_
+#define __UART1_H_ 1
+
+#include <stddef.h>
+#include <stdint.h>
+
+
+/***************************************************************************//**
+ Baud rates
+ ==========
+ The following definitions are used to specify standard baud rates as a
+ parameter to the UART_init() function.
+
+ | Constant | Description |
+ |----------------------|------------------|
+ | UART_110_BAUD | 110 baud rate |
+ | UART_300_BAUD | 300 baud rate |
+ | UART_600_BAUD | 600 baud rate |
+ | UART_1200_BAUD | 1200 baud rate |
+ | UART_2400_BAUD | 2400 baud rate |
+ | UART_4800_BAUD | 4800 baud rate |
+ | UART_9600_BAUD | 9600 baud rate |
+ | UART_19200_BAUD | 19200 baud rate |
+ | UART_38400_BAUD | 38400 baud rate |
+ | UART_57600_BAUD | 57600 baud rate |
+ | UART_115200_BAUD | 115200 baud rate |
+ | UART_230400_BAUD | 230400 baud rate |
+ | UART_460800_BAUD | 460800 baud rate |
+ | UART_921600_BAUD | 921600 baud rate |
+
+ */
+#define UART_110_BAUD 110U
+#define UART_300_BAUD 300U
+#define UART_600_BAUD 600U
+#define UART_1200_BAUD 1200U
+#define UART_2400_BAUD 2400U
+#define UART_4800_BAUD 4800U
+#define UART_9600_BAUD 9600U
+#define UART_19200_BAUD 19200U
+#define UART_38400_BAUD 38400U
+#define UART_57600_BAUD 57600U
+#define UART_115200_BAUD 115200U
+#define UART_230400_BAUD 230400U
+#define UART_460800_BAUD 460800U
+#define UART_921600_BAUD 921600U
+
+/***************************************************************************//**
+ Data Bits Length
+ ================
+ The following defines are used to build the value of the UART_init()
+ function line_config parameter.
+
+ | Constant | Description |
+ |----------------------|----------------------------|
+ | UART_DATA_5_BITS | 5 bits of data transmitted |
+ | UART_DATA_6_BITS | 6 bits of data transmitted |
+ | UART_DATA_7_BITS | 7 bits of data transmitted |
+ | UART_DATA_8_BITS | 8 bits of data transmitted |
+
+ */
+#define UART_DATA_5_BITS ((uint8_t) 0x00)
+#define UART_DATA_6_BITS ((uint8_t) 0x01)
+#define UART_DATA_7_BITS ((uint8_t) 0x02)
+#define UART_DATA_8_BITS ((uint8_t) 0x03)
+
+/***************************************************************************//**
+ Parity
+ ======
+ The following defines are used to build the value of the UART_init()
+ function line_config parameter.
+
+ | Constant | Description |
+ |-------------------------|--------------------------|
+ | UART_NO_PARITY | No parity |
+ | UART_ODD_PARITY | Odd Parity |
+ | UART_EVEN_PARITY | Even parity |
+ | UART_STICK_PARITY_0 | Stick parity bit to zero |
+ | UART_STICK_PARITY_1 | Stick parity bit to one |
+ */
+#define UART_NO_PARITY ((uint8_t) 0x00)
+#define UART_ODD_PARITY ((uint8_t) 0x08)
+#define UART_EVEN_PARITY ((uint8_t) 0x18)
+#define UART_STICK_PARITY_0 ((uint8_t) 0x38)
+#define UART_STICK_PARITY_1 ((uint8_t) 0x28)
+
+/***************************************************************************//**
+ Number of Stop Bits
+ ===================
+ The following defines are used to build the value of the UART_init()
+ function line_config parameter.
+
+ | Constant | Description |
+ |---------------------------|--------------------------|
+ | UART_ONE_STOP_BIT | One stop bit |
+ | UART_ONEHALF_STOP_BIT | One and a half stop bits |
+ | UART_TWO_STOP_BITS | Two stop bits |
+
+ */
+#define UART_ONE_STOP_BIT ((uint8_t) 0x00)
+#define UART_ONEHALF_STOP_BIT ((uint8_t) 0x04)
+#define UART_TWO_STOP_BITS ((uint8_t) 0x04)
+
+/***************************************************************************//**
+ Receiver Error Status
+ =====================
+ The following defines are used to determine the UART receiver error type.
+ These bit mask constants are used with the return value of the
+ UART_get_rx_status() function to find out if any errors occurred while
+ receiving data.
+
+
+ | Constant | Description |
+ |------------------------|--------------------------------------------|
+ | UART_NO_ERROR | No error bit mask (0x00) |
+ | UART_OVERUN_ERROR | Overrun error bit mask (0x02) |
+ | UART_PARITY_ERROR | Parity error bit mask (0x04) |
+ | UART_FRAMING_ERROR | Framing error bit mask (0x08) |
+ | UART_BREAK_ERROR | Break error bit mask (0x10) |
+ | UART_FIFO_ERROR | FIFO error bit mask (0x80) |
+ | UART_INVALID_PARAM | Invalid function parameter bit mask (0xFF) |
+ */
+#define UART_INVALID_PARAM ((uint8_t)0xFF)
+#define UART_NO_ERROR ((uint8_t)0x00 )
+#define UART_OVERUN_ERROR ((uint8_t)0x02)
+#define UART_PARITY_ERROR ((uint8_t)0x04)
+#define UART_FRAMING_ERROR ((uint8_t)0x08)
+#define UART_BREAK_ERROR ((uint8_t)0x10)
+#define UART_FIFO_ERROR ((uint8_t)0x80)
+
+
+/*******************************************************************************
+ * Receiver error status mask.
+ */
+#define STATUS_ERROR_MASK ( UART_OVERUN_ERROR | UART_PARITY_ERROR | \
+ UART_FRAMING_ERROR | UART_BREAK_ERROR | \
+ UART_FIFO_ERROR)
+
+/***************************************************************************//**
+ Transmitter Status
+ ==================
+ The following definitions are used to determine the UART transmitter status.
+ These bit mask constants are used with the return value of the
+ UART_get_tx_status() function to find out the status of the transmitter.
+
+ | Constant | Description |
+ |------------------|----------------------------------------------------|
+ | UART_TX_BUSY | Transmitter busy (0x00) |
+ | UART_THRE | Transmitter holding register empty bit mask (0x20) |
+ | UART_TEMT | Transmitter empty bit mask (0x40) |
+
+ */
+#define UART_TX_BUSY ((uint8_t) 0x00)
+#define UART_THRE ((uint8_t) 0x20)
+#define UART_TEMT ((uint8_t) 0x40)
+
+/***************************************************************************//**
+ Modem Status
+ ============
+ The following defines are used to determine the modem status. These bit
+ mask constants are used with the return value of the
+ UART_get_modem_status() function to find out the modem status of
+ the UART.
+
+ | Constant | Description |
+ |---------------|-------------------------------------------------|
+ | UART_DCTS | Delta clear to send bit mask (0x01) |
+ | UART_DDSR | Delta data set ready bit mask (0x02) |
+ | UART_TERI | Trailing edge of ring indicator bit mask (0x04) |
+ | UART_DDCD | Delta data carrier detect bit mask (0x08) |
+ | UART_CTS | Clear to send bit mask (0x10) |
+ | UART_DSR | Data set ready bit mask (0x20) |
+ | UART_RI | Ring indicator bit mask (0x40) |
+ | UART_DCD | Data carrier detect bit mask (0x80) |
+ */
+#define UART_DCTS ((uint8_t) 0x01)
+#define UART_DDSR ((uint8_t) 0x02)
+#define UART_TERI ((uint8_t) 0x04)
+#define UART_DDCD ((uint8_t) 0x08)
+#define UART_CTS ((uint8_t) 0x10)
+#define UART_DSR ((uint8_t) 0x20)
+#define UART_RI ((uint8_t) 0x40)
+#define UART_DCD ((uint8_t) 0x80)
+
+/***************************************************************************//**
+ This typedef specifies the irq_mask parameter for the UART_enable_irq()
+ and UART_disable_irq() functions. The driver defines a set of bit masks
+ that are used to build the value of the irq_mask parameter. A bitwise OR of
+ these bit masks is used to enable or disable multipleUART interrupts.
+ */
+typedef uint16_t uart_irq_t;
+
+/***************************************************************************//**
+ UART Interrupts
+ =====================
+ The following defines specify the interrupt masks to enable and disable
+ UART interrupts. They are used to build the value of the irq_mask parameter
+ for the UART_enable_irq() and UART_disable_irq() functions. A bitwise
+ OR of these constants is used to enable or disable multiple interrupts.
+
+
+ | Constant | Description |
+ |--------------------|---------------------------------------------------------------|
+ | UART_RBF_IRQ | Receive Data Available Interrupt bit mask (0x001) |
+ | UART_TBE_IRQ | Transmitter Holding Register Empty interrupt bit mask (0x002) |
+ | UART_LS_IRQ | Receiver Line Status interrupt bit mask (0x004) |
+ | UART_MS_IRQ | Modem Status interrupt bit mask (0x008) |
+
+ */
+#define UART_RBF_IRQ 0x001
+#define UART_TBE_IRQ 0x002
+#define UART_LS_IRQ 0x004
+#define UART_MS_IRQ 0x008
+//#define UART_RTO_IRQ 0x010
+//#define UART_NACK_IRQ 0x020
+//#define UART_PIDPE_IRQ 0x040
+//#define UART_LINB_IRQ 0x080
+//#define UART_LINS_IRQ 0x100
+#define UART_INVALID_IRQ UINT16_MAX
+
+/***************************************************************************//**
+ This enumeration specifies the receiver FIFO trigger level. This is the number
+ of bytes that must be received before the UART generates a receive data
+ available interrupt. It provides the allowed values for the
+ UART_set_rx_handler() function trigger_level parameter.
+ */
+typedef enum {
+ UART_FIFO_SINGLE_BYTE = 0x00,
+ UART_FIFO_FOUR_BYTES = 0x40,
+ UART_FIFO_EIGHT_BYTES = 0x80,
+ UART_FIFO_FOURTEEN_BYTES = 0xC0,
+ UART_FIFO_INVALID_TRIG_LEVEL
+
+} uart_rx_trig_level_t;
+
+
+
+
+/***************************************************************************//**
+ UART instance type.
+ This is type definition for UART instance. You need to create and
+ maintain a record of this type. This holds all data regarding the UART
+ instance
+ */
+typedef struct uart_instance uart_instance_t;
+
+/***************************************************************************//**
+ Interrupt handler prototype.
+ This typedef specifies the function prototype for UART interrupt handlers.
+ All interrupt handlers registered with the UART driver must be of this type.
+ The interrupt handlers are registered with the driver through the
+ UART_set_rx_handler(), UART_set_tx_handler(),
+ UART_set_rxstatus_handler(), and UART_set_modemstatus_handler()
+ functions.
+ The this_uart parameter is a pointer to either g_uart0 or g_uart1 to
+ identify the UART to associate with the handler function.
+ */
+typedef void (*uart_irq_handler_t)( uart_instance_t * this_uart );
+
+
+/*******************************************************************************
+ Register Bit definitions
+ */
+
+/* Line Control register bit definitions */
+#define SB 6u /* Set break */
+#define DLAB 7u /* Divisor latch access bit */
+
+/* Line Control register bit masks */
+#define SB_MASK (0x01u << SB) /* Set break */
+#define DLAB_MASK (0x01u << DLAB) /* Divisor latch access bit */
+
+/* FIFO Control register bit definitions */
+#define ENABLE_FIFO 0u /* Enable FIFO */
+#define CLEAR_RX_FIFO 1u /* Clear receiver FIFO */
+#define CLEAR_TX_FIFO 2u /* Clear transmitter FIFO */
+#define DMA_MODE 3u /* DMA mode */
+#define FIFO64_ENABLE 5u /* FIFO64 enable */
+#define RDYMODE 3u /* Mode 0 or Mode 1 for TXRDY and RXRDY */
+#define FIFO_RX_TRIGGER 6u /* FIFO RX trigger */
+
+/* FIFO Control register bit MASKS */
+#define RXRDY_TXRDYN_EN_MASK (0x01u << 0u) /* Enable TXRDY and RXRDY signals */
+#define CLEAR_RX_FIFO_MASK (0x01u << 1u) /* Clear receiver FIFO */
+#define CLEAR_TX_FIFO_MASK (0x01u << 2u) /* Clear transmitter FIFO */
+#define DMA_MODE_MASK (0x01u << 3u) /* DMA mode */
+#define FIFO64_ENABLE_MASK (0x01u << 5u) /* FIFO64 enable */
+#define RDYMODE_MASK (0x01u << 3u) /* Mode 0 or Mode 1 for TXRDY and RXRDY */
+
+#define FIFO_RX_TRIGGER_LEVEL_4_MASK (0x01u << 6u)
+#define FIFO_RX_TRIGGER_LEVEL_8_MASK (0x02u << 6u)
+#define FIFO_RX_TRIGGER_LEVEL_14_MASK (0x03u << 6u)
+
+#define FIFO64_RX_TRIGGER_LEVEL_16_MASK (0x01u << 6u)
+#define FIFO64_RX_TRIGGER_LEVEL_32_MASK (0x02u << 6u)
+#define FIFO64_RX_TRIGGER_LEVEL_56_MASK (0x03u << 6u)
+
+/* Modem Control register bit definitions */
+//#define LOOP 4u /* Local loopback */
+//#define RLOOP 5u /* Remote loopback */
+//#define ECHO 6u /* Automatic echo */
+
+/* Modem Control register bit MASKS */
+//#define LOOP_MASK (0x01u << 4u) /* Local loopback */
+//#define RLOOP_MASK (0x01u << 5u) /* Remote loopback & Automatic echo*/
+//#define ECHO_MASK (0x01u << 6u) /* Automatic echo */
+
+/* Line Status register bit definitions */
+#define DR 0u /* Data ready */
+#define THRE 5u /* Transmitter holding register empty */
+#define TEMT 6u /* Transmitter empty */
+
+/* Line Status register bit MASKS */
+#define DR_MASK (0x01u << 0u) /* Data ready */
+#define THRE_MASK (0x01u << 5u) /* Transmitter holding register empty */
+#define TEMT_MASK (0x01u << 6u) /* Transmitter empty */
+
+/* Interrupt Enable register bit definitions */
+#define ERBFI 0u /* Enable receiver buffer full interrupt */
+#define ETBEI 1u /* Enable transmitter buffer empty interrupt */
+#define ELSI 2u /* Enable line status interrupt */
+#define EDSSI 3u /* Enable modem status interrupt */
+
+/* Interrupt Enable register bit MASKS */
+#define ERBFI_MASK (0x01u << 0u) /* Enable receiver buffer full interrupt */
+#define ETBEI_MASK (0x01u << 1u) /* Enable transmitter buffer empty interrupt */
+#define ELSI_MASK (0x01u << 2u) /* Enable line status interrupt */
+#define EDSSI_MASK (0x01u << 3u) /* Enable modem status interrupt */
+
+/* Multimode register 0 bit definitions */
+#define ELIN 3u /* Enable LIN header detection */
+#define ETTG 5u /* Enable transmitter time guard */
+#define ERTO 6u /* Enable receiver time-out */
+#define EFBR 7u /* Enable fractional baud rate mode */
+
+/* Multimode register 0 bit MASKS */
+#define ELIN_MASK (0x01u << 3u) /* Enable LIN header detection */
+#define ETTG_MASK (0x01u << 5u) /* Enable transmitter time guard */
+#define ERTO_MASK (0x01u << 6u) /* Enable receiver time-out */
+#define EFBR_MASK (0x01u << 7u) /* Enable fractional baud rate mode */
+
+/* Multimode register 1 bit definitions */
+#define E_MSB_RX 0u /* MSB / LSB first for receiver */
+#define E_MSB_TX 1u /* MSB / LSB first for transmitter */
+#define EIRD 2u /* Enable IrDA modem */
+#define EIRX 3u /* Input polarity for IrDA modem */
+#define EITX 4u /* Output polarity for IrDA modem */
+#define EITP 5u /* Output pulse width for IrDA modem */
+
+/* Multimode register 1 bit MASKS */
+#define E_MSB_RX_MASK (0x01u << 0u) /* MSB / LSB first for receiver */
+#define E_MSB_TX_MASK (0x01u << 1u) /* MSB / LSB first for transmitter */
+#define EIRD_MASK (0x01u << 2u) /* Enable IrDA modem */
+#define EIRX_MASK (0x01u << 3u) /* Input polarity for IrDA modem */
+#define EITX_MASK (0x01u << 4u) /* Output polarity for IrDA modem */
+#define EITP_MASK (0x01u << 5u) /* Output pulse width for IrDA modem */
+
+/* Multimode register 2 bit definitions */
+//#define EERR 0u /* Enable ERR / NACK during stop time */
+//#define EAFM 1u /* Enable 9-bit address flag mode */
+//#define EAFC 2u /* Enable address flag clear */
+//#define ESWM 3u /* Enable single wire half-duplex mode */
+
+/* Multimode register 2 bit MASKS */
+//#define EERR_MASK (0x01u << 0u) /* Enable ERR / NACK during stop time */
+//#define EAFM_MASK (0x01u << 1u) /* Enable 9-bit address flag mode */
+//#define EAFC_MASK (0x01u << 2u) /* Enable address flag clear */
+//#define ESWM_MASK (0x01u << 3u) /* Enable single wire half-duplex mode */
+
+/* Multimode Interrupt Enable register and
+ Multimode Interrupt Identification register definitions */
+//#define ERTOI 0u /* Enable receiver timeout interrupt */
+//#define ENACKI 1u /* Enable NACK / ERR interrupt */
+//#define EPID_PEI 2u /* Enable PID parity error interrupt */
+//#define ELINBI 3u /* Enable LIN break interrupt */
+//#define ELINSI 4u /* Enable LIN sync detection interrupt */
+
+/* Multimode Interrupt Enable register and
+ Multimode Interrupt Identification register MASKS */
+//#define ERTOI_MASK (0x01u << 0u) /* Enable receiver timeout interrupt */
+//#define ENACKI_MASK (0x01u << 1u) /* Enable NACK / ERR interrupt */
+//#define EPID_PEI_MASK (0x01u << 2u) /* Enable PID parity error interrupt */
+//#define ELINBI_MASK (0x01u << 3u) /* Enable LIN break interrupt */
+//#define ELINSI_MASK (0x01u << 4u) /* Enable LIN sync detection interrupt */
+
+typedef struct
+{
+ union
+ {
+ volatile const uint8_t RBR;
+ volatile uint8_t THR;
+ volatile uint8_t DLL;
+ uint32_t RESERVED0;
+ };
+
+ union
+ {
+ volatile uint8_t DLM;
+ volatile uint8_t IER;
+ uint32_t RESERVED1;
+ };
+
+ union
+ {
+ volatile uint8_t IIR;
+ volatile uint8_t FCR;
+ uint32_t RESERVED2;
+ };
+
+ volatile uint8_t LCR;
+ uint8_t RESERVED3[3];
+
+ volatile uint8_t MCR;
+ uint8_t RESERVED4[3];
+
+ volatile const uint8_t LSR;
+ uint8_t RESERVED5[3];
+
+ volatile const uint8_t MSR;
+ uint8_t RESERVED6[3];
+
+ volatile uint8_t SR;
+ uint8_t RESERVED7[7];
+
+} UART_TypeDef;
+
+
+/***************************************************************************//**
+ uart_instance.
+ There is one instance of this structure for each instance of the
+ microprocessor subsystem's UARTs. Instances of this structure are used to
+ identify a specific UART. A pointer to an initialized instance of the
+ uart_instance_t structure is passed as the first parameter to
+ UART driver functions to identify which UART should perform the
+ requested operation.
+ */
+struct uart_instance{
+ /* CMSIS related defines identifying the UART hardware. */
+ UART_TypeDef * hw_reg; /*!< Pointer to UART registers. */
+ uint32_t baudrate; /*!< Operating baud rate. */
+ uint8_t lineconfig; /*!< Line configuration parameters. */
+ uint8_t status; /*!< Sticky line status. */
+
+ /* transmit related info (used with interrupt driven transmit): */
+ const uint8_t * tx_buffer; /*!< Pointer to transmit buffer. */
+ uint32_t tx_buff_size; /*!< Transmit buffer size. */
+ uint32_t tx_idx; /*!< Index within transmit buffer of next byte to transmit.*/
+
+ /* line status interrupt handler:*/
+ uart_irq_handler_t linests_handler; /*!< Pointer to user registered line status handler. */
+ /* receive interrupt handler:*/
+ uart_irq_handler_t rx_handler; /*!< Pointer to user registered receiver handler. */
+ /* transmit interrupt handler:*/
+ uart_irq_handler_t tx_handler; /*!< Pointer to user registered transmit handler. */
+ /* modem status interrupt handler:*/
+ uart_irq_handler_t modemsts_handler; /*!< Pointer to user registered modem status handler. */
+
+
+
+};
+
+extern uart_instance_t g_uart_0;
+
+static void default_tx_handler(uart_instance_t * this_uart);
+static void enable_irq(const uart_instance_t * this_uart);
+
+static void disable_irq(const uart_instance_t * this_uart);
+
+static void config_baud_divisors
+(
+ uart_instance_t * this_uart,
+ uint32_t baudrate
+);
+
+/***************************************************************************//**
+ The UART_init() function initializes and configures the
+ UART with the configuration passed as a parameter. The configuration
+ parameters are the baud_rate which is used to generate the baud value and the
+ line_config which is used to specify the line configuration (bit length,
+ stop bits and parity).
+ @param this_uart
+ The this_uart parameter is a pointer to an uart_instance_t
+ structure identifying the UART hardware block that will perform
+ the requested function.
+ @param baud_rate
+ The baud_rate parameter specifies the baud rate. It can be specified for
+ common baud rates using the following defines:
+ - UART_110_BAUD
+ - UART_300_BAUD
+ - UART_600_BAUD
+ - UART_1200_BAUD
+ - UART_2400_BAUD
+ - UART_4800_BAUD
+ - UART_9600_BAUD
+ - UART_19200_BAUD
+ - UART_38400_BAUD
+ - UART_57600_BAUD
+ - UART_115200_BAUD
+ - UART_230400_BAUD
+ - UART_460800_BAUD
+ - UART_921600_BAUD
+
+ Alternatively, any nonstandard baud rate can be specified by simply passing
+ the actual required baud rate as the value for this parameter.
+ @param line_config
+ The line_config parameter is the line configuration specifying the bit length,
+ number of stop bits and parity settings.
+
+ This is a bitwise OR of one value from each of the following groups of
+ allowed values:
+
+ One of the following to specify the transmit/receive data bit length:
+ - UART_DATA_5_BITS
+ - UART_DATA_6_BITS,
+ - UART_DATA_7_BITS
+ - UART_DATA_8_BITS
+
+ One of the following to specify the parity setting:
+ - UART_NO_PARITY
+ - UART_EVEN_PARITY
+ - UART_ODD_PARITY
+ - UART_STICK_PARITY_0
+ - UART_STICK_PARITY_1
+
+ One of the following to specify the number of stop bits:
+ - UART_ONE_STOP_BIT
+ - UART_ONEHALF_STOP_BIT
+ - UART_TWO_STOP_BITS
+ @return
+ This function does not return a value.
+ Example:
+ @code
+ #include "uart.h"
+ int main(void)
+ {
+ UART_init(&g_uart0_lo,
+ UART_57600_BAUD,
+ UART_DATA_8_BITS | UART_NO_PARITY | UART_ONE_STOP_BIT);
+
+ return(0);
+ }
+ @endcode
+ */
+void
+UART_init_ariane
+(
+ uart_instance_t* this_uart,
+ uint32_t baud_rate,
+ uint8_t line_config
+);
+
+/***************************************************************************//**
+ The function UART_polled_tx() is used to transmit data. It transfers the
+ contents of the transmitter data buffer, passed as a function parameter, into
+ the UART's hardware transmitter FIFO. It returns when the full content of the
+ transmit data buffer has been transferred to the UART's transmit FIFO. It is
+ safe to release or reuse the memory used as the transmitter data buffer once
+ this function returns.
+ Note: This function reads the UART's line status register (LSR) to poll
+ for the active state of the transmitter holding register empty (THRE) bit
+ before transferring data from the data buffer to the transmitter FIFO. It
+ transfers data to the transmitter FIFO in blocks of 16 bytes or less and
+ allows the FIFO to empty before transferring the next block of data.
+ Note: The actual transmission over the serial connection will still be
+ in progress when this function returns. Use the UART_get_tx_status()
+ function if you need to know when the transmitter is empty.
+ @param this_uart
+ The this_uart parameter is a pointer to an uart_instance_t
+ structure identifying the UART hardware block that will perform
+ the requested function.
+ @param pbuff
+ The pbuff parameter is a pointer to a buffer containing the data to
+ be transmitted.
+ @param tx_size
+ The tx_size parameter specifies the size, in bytes, of the data to
+ be transmitted.
+ @return
+ This function does not return a value.
+ Example:
+ @code
+ #include "uart.h"
+ int main(void)
+ {
+ uint8_t message[12] = "Hello World";
+ UART_init(&g_uart0_lo,
+ UART_57600_BAUD,
+ SS_UART_DATA_8_BITS | UART_NO_PARITY | UART_ONE_STOP_BIT);
+ UART_polled_tx(&g_uart0_lo, message, sizeof(message));
+
+ return(0);
+ }
+ @endcode
+ */
+void
+UART_polled_tx
+(
+ uart_instance_t * this_uart,
+ const uint8_t * pbuff,
+ uint32_t tx_size
+);
+
+/***************************************************************************//**
+ The function UART_polled_tx_string() is used to transmit a NULL ('\0')
+ terminated string. It transfers the text string, from the buffer starting at
+ the address pointed to by p_sz_string into the UART's hardware transmitter
+ FIFO. It returns when the complete string has been transferred to the UART's
+ transmit FIFO. It is safe to release or reuse the memory used as the string
+ buffer once this function returns.
+ Note: This function reads the UART's line status register (LSR) to poll
+ for the active state of the transmitter holding register empty (THRE) bit
+ before transferring data from the data buffer to the transmitter FIFO. It
+ transfers data to the transmitter FIFO in blocks of 16 bytes or less and
+ allows the FIFO to empty before transferring the next block of data.
+ Note: The actual transmission over the serial connection will still be
+ in progress when this function returns. Use the UART_get_tx_status()
+ function if you need to know when the transmitter is empty.
+ @param this_uart
+ The this_uart parameter is a pointer to an uart_instance_t
+ structure identifying the UART hardware block that will perform
+ the requested function.
+ @param p_sz_string
+ The p_sz_string parameter is a pointer to a buffer containing the NULL
+ ('\0') terminated string to be transmitted.
+ @return
+ This function does not return a value.
+ Example:
+ @code
+ #include "uart.h"
+ int main(void)
+ {
+ uint8_t message[12] = "Hello World";
+ UART_init(&g_uart0_lo,
+ UART_57600_BAUD,
+ UART_DATA_8_BITS | UART_NO_PARITY | UART_ONE_STOP_BIT);
+
+ UART_polled_tx_string(&g_uart0_lo, message);
+
+ return(0);
+ }
+ @endcode
+ */
+void
+UART_polled_tx_string
+(
+ uart_instance_t * this_uart,
+ const uint8_t * p_sz_string
+);
+
+/***************************************************************************//**
+ The function UART_irq_tx() is used to initiate an interrupt-driven
+ transmit. It returns immediately after making a note of the transmit buffer
+ location and enabling transmit interrupts both at the UART and the PolarFire
+ SoC Core Complex PLIC level. This function takes a pointer via the pbuff
+ parameter to a memory buffer containing the data to transmit. The memory
+ buffer specified through this pointer must remain allocated and contain the
+ data to transmit until the transmit completion has been detected through calls
+ to function UART_tx_complete(). The actual transmission over the serial
+ connection is still in progress until calls to the UART_tx_complete()
+ function indicate transmit completion.
+ Note: The UART_irq_tx() function enables both the transmit holding
+ register empty (THRE) interrupt in the UART and the UART instance
+ interrupt in the PolarFire SoC Core Complex PLIC as part of its implementation.
+ Note: The UART_irq_tx() function assigns an internal default transmit
+ interrupt handler function to the UART's THRE interrupt. This interrupt
+ handler overrides any custom interrupt handler that you may have previously
+ registered using the UART_set_tx_handler() function.
+ Note: The UART_irq_tx() function's default transmit interrupt
+ handler disables the UART's THRE interrupt when all of the data has
+ been transferred to the UART's transmit FIFO.
+ @param this_uart
+ The this_uart parameter is a pointer to an uart_instance_t
+ structure identifying the UART hardware block that will perform
+ the requested function.
+ @param pbuff
+ The pbuff parameter is a pointer to a buffer containing the data
+ to be transmitted.
+ @param tx_size
+ The tx_size parameter specifies the size, in bytes, of the data
+ to be transmitted.
+ @return
+ This function does not return a value.
+ Example:
+ @code
+ #include "uart.h"
+ int main(void)
+ {
+ uint8_t tx_buff[10] = "abcdefghi";
+
+ UART_init(&g_uart0_lo,
+ UART_57600_BAUD,
+ UART_DATA_8_BITS | UART_NO_PARITY | UART_ONE_STOP_BIT);
+
+ UART_irq_tx(&g_uart0_lo, tx_buff, sizeof(tx_buff));
+
+ while(0 == UART_tx_complete(&g_uart0_lo))
+ {
+ ;
+ }
+ return(0);
+ }
+ @endcode
+ */
+void
+UART_irq_tx
+(
+ uart_instance_t * this_uart,
+ const uint8_t * pbuff,
+ uint32_t tx_size
+);
+
+/***************************************************************************//**
+ The UART_tx_complete() function is used to find out if the
+ interrupt-driven transmit previously initiated through a call to
+ UART_irq_tx() is complete. This is typically used to find out when it is
+ safe to reuse or release the memory buffer holding transmit data.
+ Note: The transfer of all of the data from the memory buffer to the UART's
+ transmit FIFO and the actual transmission over the serial connection are both
+ complete when a call to the UART_tx_complete() function indicates transmit
+ completion.
+ @param this_uart
+ The this_uart parameter is a pointer to an uart_instance_t
+ structure identifying the UART hardware block that will perform
+ the requested function.
+ @return
+ This function return a non-zero value if transmit has completed, otherwise
+ it returns zero.
+ Example:
+ See the UART_irq_tx() function for an example that uses the
+ UART_tx_complete() function.
+ */
+int8_t
+UART_tx_complete
+(
+ uart_instance_t * this_uart
+);
+
+/***************************************************************************//**
+ The UART_get_rx() function reads the content of the UART receiver's FIFO
+ and stores it in the receive buffer that is passed via the rx_buff function
+ parameter. It copies either the full contents of the FIFO into the receive
+ buffer, or just enough data from the FIFO to fill the receive buffer,
+ dependent upon the size of the receive buffer passed by the buff_size
+ parameter. The UART_get_rx() function returns the number of bytes copied
+ into the receive buffer .This function is non-blocking and will return 0
+ immediately if no data has been received.
+ Note: The UART_get_rx() function reads and accumulates the receiver
+ status of the UART instance before reading each byte from the receiver's
+ data register/FIFO. This allows the driver to maintain a sticky record of any
+ receiver errors that occur as the UART receives each data byte; receiver
+ errors would otherwise be lost after each read from the receiver's data
+ register. A call to the UART_get_rx_status() function returns any receiver
+ errors accumulated during the execution of the UART_get_rx() function.
+ Note: If you need to read the error status for each byte received, set
+ the buff_size to 1 and read the receive line error status for each byte
+ using the UART_get_rx_status() function.
+ The UART_get_rx() function can be used in polled mode, where it is called
+ at regular intervals to find out if any data has been received, or in
+ interrupt driven-mode, where it is called as part of a receive handler that is
+ called by the driver as a result of data being received.
+ Note: In interrupt driven mode you should call the UART_get_rx()
+ function as part of the receive handler function that you register with
+ the UART driver through a call to UART_set_rx_handler().
+ @param this_uart
+ The this_uart parameter is a pointer to an uart_instance_t
+ structure identifying the UART hardware block that will perform
+ the requested function.
+ @param rx_buff
+ The rx_buff parameter is a pointer to a buffer where the received
+ data is copied.
+ @param buff_size
+ The buff_size parameter specifies the size of the receive buffer in bytes.
+ @return
+ This function returns the number of bytes that were copied into the
+ rx_buff buffer. It returns 0 if no data has been received.
+ Polled mode example:
+ @code
+ int main( void )
+ {
+ uint8_t rx_buff[RX_BUFF_SIZE];
+ uint32_t rx_idx = 0;
+ UART_init(&g_uart0_lo,
+ UART_57600_BAUD,
+ UART_DATA_8_BITS | UART_NO_PARITY | UART_ONE_STOP_BIT);
+
+ while(1)
+ {
+ rx_size = UART_get_rx(&g_uart0_lo, rx_buff, sizeof(rx_buff));
+ if(rx_size > 0)
+ {
+ process_rx_data(rx_buff, rx_size);
+ }
+ task_a();
+ task_b();
+ }
+ return 0;
+ }
+ @endcode
+ Interrupt driven example:
+ @code
+ int main( void )
+ {
+ UART_init(&g_uart1,
+ UART_57600_BAUD,
+ UART_DATA_8_BITS | UART_NO_PARITY | UART_ONE_STOP_BIT);
+
+ UART_set_rx_handler(&g_uart1,
+ uart1_rx_handler,
+ UART_FIFO_SINGLE_BYTE);
+
+ while(1)
+ {
+ task_a();
+ task_b();
+ }
+ return 0;
+ }
+ void uart1_rx_handler(uart_instance_t * this_uart)
+ {
+ uint8_t rx_buff[RX_BUFF_SIZE];
+ uint32_t rx_idx = 0;
+ rx_size = UART_get_rx(this_uart, rx_buff, sizeof(rx_buff));
+ process_rx_data(rx_buff, rx_size);
+ }
+ @endcode
+ */
+size_t
+UART_get_rx
+(
+ uart_instance_t * this_uart,
+ uint8_t * rx_buff,
+ size_t buff_size
+);
+
+/***************************************************************************//**
+ The UART_set_rx_handler() function is used to register a receive handler
+ function that is called by the driver when a UART receive data available (RDA)
+ interrupt occurs. You must create and register the receive handler function
+ to suit your application and it must include a call to the UART_get_rx()
+ function to actually read the received data.
+ Note: The UART_set_rx_handler() function enables both the RDA
+ interrupt in the UART instance. It also enables the corresponding
+ UART instance interrupt in the PolarFire SoC Core Complex PLIC as part
+ of its implementation.
+ Note: You can disable the RDA interrupt when required by calling the
+ UART_disable_irq() function. This is your choice and is dependent upon
+ your application.
+
+ @param this_uart
+ The this_uart parameter is a pointer to an uart_instance_t
+ structure identifying the UART hardware block that will perform
+ the requested function.
+ @param handler
+ The handler parameter is a pointer to a receive interrupt handler function
+ provided by your application that will be called as a result of a UART RDA
+ interrupt. This handler function must be of type uart_irq_handler_t.
+ @param trigger_level
+ The trigger_level parameter is the receive FIFO trigger level. This
+ specifies the number of bytes that must be received before the UART
+ triggers an RDA interrupt.
+ @return
+ This function does not return a value.
+ Example:
+ @code
+ #include "uart.h"
+ #define RX_BUFF_SIZE 64
+ uint8_t g_rx_buff[RX_BUFF_SIZE];
+ void uart0_rx_handler(uart_instance_t * this_uart)
+ {
+ UART_get_rx(this_uart, &g_rx_buff[g_rx_idx], sizeof(g_rx_buff));
+ }
+ int main(void)
+ {
+ UART_init(&g_uart0_lo,
+ UART_57600_BAUD,
+ UART_DATA_8_BITS | UART_NO_PARITY | UART_ONE_STOP_BIT);
+
+ UART_set_rx_handler(&g_uart0_lo,
+ uart0_rx_handler,
+ UART_FIFO_SINGLE_BYTE);
+
+ while(1)
+ {
+ ;
+ }
+ return(0);
+ }
+ @endcode
+ */
+void
+UART_set_rx_handler
+(
+ uart_instance_t * this_uart,
+ uart_irq_handler_t handler,
+ uart_rx_trig_level_t trigger_level
+);
+
+
+#endif /*__UART_H_*/
+
diff --git a/ports/risc-v32/gnu/example_build/qemu_virt/CMakeLists.txt b/ports/risc-v32/gnu/example_build/qemu_virt/CMakeLists.txt
new file mode 100644
index 00000000..43e757ac
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/qemu_virt/CMakeLists.txt
@@ -0,0 +1,45 @@
+set(QEMU_DEMO_DIR ${CMAKE_CURRENT_LIST_DIR})
+
+add_executable(kernel.elf EXCLUDE_FROM_ALL
+ ${QEMU_DEMO_DIR}/demo_threadx.c
+ ${QEMU_DEMO_DIR}/entry.S
+ ${QEMU_DEMO_DIR}/uart.c
+ ${QEMU_DEMO_DIR}/plic.c
+ ${QEMU_DEMO_DIR}/hwtimer.c
+ ${QEMU_DEMO_DIR}/trap.c
+ ${QEMU_DEMO_DIR}/board.c
+ ${QEMU_DEMO_DIR}/tx_initialize_low_level.S
+)
+
+target_link_libraries(kernel.elf PRIVATE threadx)
+
+target_include_directories(kernel.elf PRIVATE
+ ${CMAKE_SOURCE_DIR}/common/inc
+ ${CMAKE_SOURCE_DIR}/ports/${THREADX_ARCH}/${THREADX_TOOLCHAIN}/inc
+ ${QEMU_DEMO_DIR}
+)
+
+target_link_options(kernel.elf PRIVATE
+ -T${QEMU_DEMO_DIR}/link.lds
+ -nostartfiles
+ -Wl,-Map=kernel.map
+)
+
+# QEMU/GDB functional test runner. Optional: skipped silently if the
+# host has no Python 3 interpreter on PATH.
+find_package(Python3 COMPONENTS Interpreter)
+if(Python3_FOUND)
+ add_custom_target(check-functional-riscv32
+ COMMAND ${Python3_EXECUTABLE}
+ ${QEMU_DEMO_DIR}/test/threadx_test_tx_gnu_riscv32_qemu.py
+ --elf $<TARGET_FILE:kernel.elf>
+ --qemu qemu-system-riscv32
+ --gdb gdb
+ DEPENDS kernel.elf
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+ COMMENT "Running RISC-V32 QEMU/GDB functional test runner..."
+ )
+else()
+ message(STATUS
+ "Python3 not found; check-functional-riscv32 target unavailable.")
+endif()
diff --git a/ports/risc-v32/gnu/example_build/qemu_virt/build_libthreadx.sh b/ports/risc-v32/gnu/example_build/qemu_virt/build_libthreadx.sh
index ba6b4022..d0e092da 100755
--- a/ports/risc-v32/gnu/example_build/qemu_virt/build_libthreadx.sh
+++ b/ports/risc-v32/gnu/example_build/qemu_virt/build_libthreadx.sh
@@ -1,5 +1,35 @@
#!/bin/bash
+##############################################################################
+# Copyright (c) 2024 Microsoft Corporation
+# 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.
+#
+# SPDX-License-Identifier: MIT
+##############################################################################
+
+printf "y\n" | rm -rf ../../../../../build/
+rm -f kernel.elf
+
pushd ../../../../../
cmake -Bbuild -GNinja -DCMAKE_TOOLCHAIN_FILE=cmake/riscv32_gnu.cmake .
cmake --build ./build/
-popd \ No newline at end of file
+popd
+
+riscv32-unknown-elf-gcc \
+ -march=rv32gc -mabi=ilp32d \
+ -mcmodel=medany -O0 -g3 -Wall \
+ -ffunction-sections -fdata-sections \
+ -I../../../../../common/inc \
+ -I../../inc \
+ entry.S \
+ tx_initialize_low_level.S \
+ board.c uart.c hwtimer.c plic.c trap.c demo_threadx.c \
+ -L../../../../../build -lthreadx \
+ -T link.lds -nostartfiles \
+ -o kernel.elf
+
+
+qemu-system-riscv32 -nographic -smp 1 -bios none -m 128M -machine virt -kernel kernel.elf
diff --git a/ports/risc-v32/gnu/example_build/qemu_virt/csr.h b/ports/risc-v32/gnu/example_build/qemu_virt/csr.h
index 9f986bac..b5df6f12 100644..120000
--- a/ports/risc-v32/gnu/example_build/qemu_virt/csr.h
+++ b/ports/risc-v32/gnu/example_build/qemu_virt/csr.h
@@ -1,343 +1 @@
-/***************************************************************************
- * Copyright (c) 2025 10xEngineers
- *
- * 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.
- *
- * SPDX-License-Identifier: MIT
- **************************************************************************/
-
-
-#ifndef RISCV_CSR_H
-#define RISCV_CSR_H
-
-
-// Machine Status Register, mstatus
-#define MSTATUS_MPP_MASK (3L << 11) // previous mode.
-#define MSTATUS_MPP_M (3L << 11)
-#define MSTATUS_MPP_S (1L << 11)
-#define MSTATUS_MPP_U (0L << 11)
-#define MSTATUS_MIE (1L << 3) // machine-mode interrupt enable.
-#define MSTATUS_MPIE (1L << 7)
-#define MSTATUS_FS (1L << 13)
-
-// Machine-mode Interrupt Enable
-#define MIE_MTIE (1L << 7)
-#define MIE_MSIE (1L << 3)
-#define MIE_MEIE (1L << 11)
-#define MIE_STIE (1L << 5) // supervisor timer
-#define MIE_SSIE (1L << 1)
-#define MIE_SEIE (1L << 9)
-
-// Supervisor Status Register, sstatus
-#define SSTATUS_SPP (1L << 8) // Previous mode, 1=Supervisor, 0=User
-#define SSTATUS_SPIE (1L << 5) // Supervisor Previous Interrupt Enable
-#define SSTATUS_UPIE (1L << 4) // User Previous Interrupt Enable
-#define SSTATUS_SIE (1L << 1) // Supervisor Interrupt Enable
-#define SSTATUS_UIE (1L << 0) // User Interrupt Enable
-#define SSTATUS_SPIE (1L << 5)
-#define SSTATUS_UPIE (1L << 4)
-
-// Supervisor Interrupt Enable
-#define SIE_SEIE (1L << 9) // external
-#define SIE_STIE (1L << 5) // timer
-#define SIE_SSIE (1L << 1) // software
-
-#ifndef __ASSEMBLER__
-
-#include <stdint.h>
-
-static inline uint32_t riscv_get_core()
-{
- uint32_t x;
- asm volatile("csrr %0, mhartid" : "=r" (x) );
- return x;
-}
-
-static inline uint32_t riscv_get_mstatus()
-{
- uint32_t x;
- asm volatile("csrr %0, mstatus" : "=r" (x) );
- return x;
-}
-
-static inline void riscv_writ_mstatus(uint32_t x)
-{
- asm volatile("csrw mstatus, %0" : : "r" (x));
-}
-
-static inline void riscv_writ_mepc(uint32_t x)
-{
- asm volatile("csrw mepc, %0" : : "r" (x));
-}
-
-static inline uint32_t riscv_get_sstatus()
-{
- uint32_t x;
- asm volatile("csrr %0, sstatus" : "=r" (x) );
- return x;
-}
-
-static inline void riscv_writ_sstatus(uint32_t x)
-{
- asm volatile("csrw sstatus, %0" : : "r" (x));
-}
-
-static inline uint32_t riscv_get_sip()
-{
- uint32_t x;
- asm volatile("csrr %0, sip" : "=r" (x) );
- return x;
-}
-
-static inline void riscv_writ_sip(uint32_t x)
-{
- asm volatile("csrw sip, %0" : : "r" (x));
-}
-
-static inline uint32_t riscv_get_sie()
-{
- uint32_t x;
- asm volatile("csrr %0, sie" : "=r" (x) );
- return x;
-}
-
-static inline void riscv_writ_sie(uint32_t x)
-{
- asm volatile("csrw sie, %0" : : "r" (x));
-}
-
-static inline uint32_t riscv_get_mie()
-{
- uint32_t x;
- asm volatile("csrr %0, mie" : "=r" (x) );
- return x;
-}
-
-static inline void riscv_writ_mie(uint32_t x)
-{
- asm volatile("csrw mie, %0" : : "r" (x));
-}
-
-static inline void riscv_writ_sepc(uint32_t x)
-{
- asm volatile("csrw sepc, %0" : : "r" (x));
-}
-
-static inline uint32_t riscv_get_sepc()
-{
- uint32_t x;
- asm volatile("csrr %0, sepc" : "=r" (x) );
- return x;
-}
-
-static inline uint32_t riscv_get_medeleg()
-{
- uint32_t x;
- asm volatile("csrr %0, medeleg" : "=r" (x) );
- return x;
-}
-
-static inline void riscv_writ_medeleg(uint32_t x)
-{
- asm volatile("csrw medeleg, %0" : : "r" (x));
-}
-
-static inline uint32_t riscv_get_mideleg()
-{
- uint32_t x;
- asm volatile("csrr %0, mideleg" : "=r" (x) );
- return x;
-}
-
-static inline void riscv_writ_mideleg(uint32_t x)
-{
- asm volatile("csrw mideleg, %0" : : "r" (x));
-}
-
-static inline void riscv_writ_stvec(uint32_t x)
-{
- asm volatile("csrw stvec, %0" : : "r" (x));
-}
-
-static inline uint32_t riscv_get_stvec()
-{
- uint32_t x;
- asm volatile("csrr %0, stvec" : "=r" (x) );
- return x;
-}
-
-static inline uint32_t riscv_get_stimecmp()
-{
- uint32_t x;
- asm volatile("csrr %0, 0x14d" : "=r" (x) );
- return x;
-}
-
-static inline void riscv_writ_stimecmp(uint32_t x)
-{
- asm volatile("csrw 0x14d, %0" : : "r" (x));
-}
-
-static inline uint32_t riscv_get_menvcfg()
-{
- uint32_t x;
- asm volatile("csrr %0, 0x30a" : "=r" (x) );
- return x;
-}
-
-static inline void riscv_writ_menvcfg(uint32_t x)
-{
- asm volatile("csrw 0x30a, %0" : : "r" (x));
-}
-
-static inline void riscv_writ_pmpcfg0(uint32_t x)
-{
- asm volatile("csrw pmpcfg0, %0" : : "r" (x));
-}
-
-static inline void riscv_writ_pmpaddr0(uint32_t x)
-{
- asm volatile("csrw pmpaddr0, %0" : : "r" (x));
-}
-
-static inline void riscv_writ_satp(uint32_t x)
-{
- asm volatile("csrw satp, %0" : : "r" (x));
-}
-
-static inline uint32_t riscv_get_satp()
-{
- uint32_t x;
- asm volatile("csrr %0, satp" : "=r" (x) );
- return x;
-}
-
-static inline uint32_t riscv_get_scause()
-{
- uint32_t x;
- asm volatile("csrr %0, scause" : "=r" (x) );
- return x;
-}
-
-static inline uint32_t riscv_get_stval()
-{
- uint32_t x;
- asm volatile("csrr %0, stval" : "=r" (x) );
- return x;
-}
-
-static inline void riscv_writ_mcounteren(uint32_t x)
-{
- asm volatile("csrw mcounteren, %0" : : "r" (x));
-}
-
-static inline uint32_t riscv_get_mcounteren()
-{
- uint32_t x;
- asm volatile("csrr %0, mcounteren" : "=r" (x) );
- return x;
-}
-
-static inline uint32_t riscv_get_time()
-{
- uint32_t x;
- asm volatile("csrr %0, time" : "=r" (x) );
- return x;
-}
-
-static inline void riscv_sintr_on()
-{
- uint32_t sstatus = riscv_get_sstatus();
- sstatus |= SSTATUS_SIE;
- riscv_writ_sstatus(sstatus);
-}
-
-static inline void riscv_sintr_off()
-{
- uint32_t sstatus = riscv_get_sstatus();
- sstatus &= (~SSTATUS_SIE);
- riscv_writ_sstatus(sstatus);
-}
-
-static inline int riscv_sintr_get()
-{
- uint32_t x = riscv_get_sstatus();
- return (x & SSTATUS_SIE) != 0;
-}
-
-static inline void riscv_sintr_restore(int x)
-{
- if(x)
- riscv_sintr_on();
- else
- riscv_sintr_off();
-}
-
-static inline void riscv_mintr_on()
-{
- uint32_t mstatus = riscv_get_mstatus();
- mstatus |= MSTATUS_MIE;
- riscv_writ_mstatus(mstatus);
-}
-
-static inline void riscv_mintr_off()
-{
- uint32_t mstatus = riscv_get_mstatus();
- mstatus &= (~MSTATUS_MIE);
- riscv_writ_mstatus(mstatus);
-}
-
-static inline int riscv_mintr_get()
-{
- uint32_t x = riscv_get_mstatus();
- return (x & MSTATUS_MIE) != 0;
-}
-
-static inline void riscv_mintr_restore(int x)
-{
- if(x)
- riscv_mintr_on();
- else
- riscv_mintr_off();
-}
-
-static inline uint32_t riscv_get_sp()
-{
- uint32_t x;
- asm volatile("mv %0, sp" : "=r" (x) );
- return x;
-}
-
-// read and write tp, the thread pointer, which xv6 uses to hold
-// this core's hartid (core number), the index into cpus[].
-static inline uint32_t riscv_get_tp()
-{
- uint32_t x;
- asm volatile("mv %0, tp" : "=r" (x) );
- return x;
-}
-
-static inline void riscv_writ_tp(uint32_t x)
-{
- asm volatile("mv tp, %0" : : "r" (x));
-}
-
-static inline uint32_t riscv_get_ra()
-{
- uint32_t x;
- asm volatile("mv %0, ra" : "=r" (x) );
- return x;
-}
-
-// flush the TLB.
-static inline void sfence_vma()
-{
- // the zero, zero means flush all TLB entries.
- asm volatile("sfence.vma zero, zero");
-}
-
-#endif // __ASSEMBLER__
-
-#endif
+../../../../risc-v_common/inc/csr.h \ No newline at end of file
diff --git a/ports/risc-v32/gnu/example_build/qemu_virt/demo_threadx.c b/ports/risc-v32/gnu/example_build/qemu_virt/demo_threadx.c
index f21dbb26..8288e749 100644
--- a/ports/risc-v32/gnu/example_build/qemu_virt/demo_threadx.c
+++ b/ports/risc-v32/gnu/example_build/qemu_virt/demo_threadx.c
@@ -1,3 +1,14 @@
+/***************************************************************************/
+/* Copyright (c) 2024 Microsoft Corporation */
+/* 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. */
+/* */
+/* SPDX-License-Identifier: MIT */
+/***************************************************************************/
+
/* This is a small demo of the high-performance ThreadX kernel. It includes examples of eight
threads of different priorities, using a message queue, semaphore, mutex, event flags group,
byte pool, and block pool. */
@@ -9,6 +20,7 @@
#define DEMO_BYTE_POOL_SIZE 9120
#define DEMO_BLOCK_POOL_SIZE 100
#define DEMO_QUEUE_SIZE 100
+float fpu_test_val = 0.0f;
char *_to_str(ULONG val)
{
@@ -201,7 +213,7 @@ UINT status;
thread_0_counter++;
/* Sleep for 10 ticks. */
- tx_thread_sleep(10);
+ tx_thread_sleep(1);
/* Set event flag 0 to wakeup thread 5. */
status = tx_event_flags_set(&event_flags_0, 0x1, TX_OR);
@@ -363,6 +375,8 @@ UINT status;
if (status != TX_SUCCESS)
break;
+ /* FPU Test */
+ fpu_test_val += 1.1f;
/* Get the mutex again with suspension. This shows
that an owning thread may retrieve the mutex it
owns multiple times. */
diff --git a/ports/risc-v32/gnu/example_build/qemu_virt/entry.S b/ports/risc-v32/gnu/example_build/qemu_virt/entry.S
new file mode 100644
index 00000000..598a072e
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/qemu_virt/entry.S
@@ -0,0 +1,72 @@
+/***************************************************************************/
+/* Copyright (c) 2024 Microsoft Corporation */
+/* 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. */
+/* */
+/* SPDX-License-Identifier: MIT */
+/***************************************************************************/
+
+
+.section .text.boot, "ax"
+.align 4
+.global _start
+.extern main
+.extern _sysstack_start
+.extern _bss_start
+.extern _bss_end
+_start:
+ csrr t0, mhartid
+ bne t0, zero, 1f
+ li x1, 0
+ li x2, 0
+.option push
+.option norelax
+ la gp, __global_pointer$ /* x3 = gp; norelax keeps this load absolute */
+.option pop
+ li x4, 0
+ li x5, 0
+ li x6, 0
+ li x7, 0
+ li x8, 0
+ li x9, 0
+ li x10, 0
+ li x11, 0
+ li x12, 0
+ li x13, 0
+ li x14, 0
+ li x15, 0
+ li x16, 0
+ li x17, 0
+ li x18, 0
+ li x19, 0
+ li x20, 0
+ li x21, 0
+ li x22, 0
+ li x23, 0
+ li x24, 0
+ li x25, 0
+ li x26, 0
+ li x27, 0
+ li x28, 0
+ li x29, 0
+ li x30, 0
+ li x31, 0
+ la t0, _sysstack_start
+ li t1, 0x1000
+ add sp, t0, t1
+ la t0, _bss_start
+ la t1, _bss_end
+_bss_clean_start:
+ bgeu t0, t1, _bss_clean_end
+ sb zero, 0(t0)
+ addi t0, t0, 1
+ j _bss_clean_start
+_bss_clean_end:
+ call main
+1:
+ /* todo smp */
+ wfi
+ j 1b
diff --git a/ports/risc-v32/gnu/example_build/qemu_virt/entry.s b/ports/risc-v32/gnu/example_build/qemu_virt/entry.s
deleted file mode 100644
index 9b202ca1..00000000
--- a/ports/risc-v32/gnu/example_build/qemu_virt/entry.s
+++ /dev/null
@@ -1,58 +0,0 @@
-
-.section .text
-.align 4
-.global _start
-.extern main
-.extern _sysstack_start
-.extern _bss_start
-.extern _bss_end
-_start:
- csrr t0, mhartid
- bne t0, zero, 1f
- li x1, 0
- li x2, 0
- li x3, 0
- li x4, 0
- li x5, 0
- li x6, 0
- li x7, 0
- li x8, 0
- li x9, 0
- li x10, 0
- li x11, 0
- li x12, 0
- li x13, 0
- li x14, 0
- li x15, 0
- li x16, 0
- li x17, 0
- li x18, 0
- li x19, 0
- li x20, 0
- li x21, 0
- li x22, 0
- li x23, 0
- li x24, 0
- li x25, 0
- li x26, 0
- li x27, 0
- li x28, 0
- li x29, 0
- li x30, 0
- li x31, 0
- la t0, _sysstack_start
- li t1, 0x1000
- add sp, t0, t1
- la t0, _bss_start
- la t1, _bss_end
-_bss_clean_start:
- bgeu t0, t1, _bss_clean_end
- sb zero, 0(t0)
- addi t0, t0, 1
- j _bss_clean_start
-_bss_clean_end:
- call main
-1:
- /* todo smp */
- wfi
- j 1b
diff --git a/ports/risc-v32/gnu/example_build/qemu_virt/link.lds b/ports/risc-v32/gnu/example_build/qemu_virt/link.lds
index 522f90d9..d2ef5f28 100644
--- a/ports/risc-v32/gnu/example_build/qemu_virt/link.lds
+++ b/ports/risc-v32/gnu/example_build/qemu_virt/link.lds
@@ -10,6 +10,7 @@ SECTIONS
. = 0x80000000;
.text : {
+ KEEP(*(.text.boot)) /* entry.s _start — must be first at 0x80000000 */
*(.text .text.*)
. = ALIGN(0x1000);
PROVIDE(etext = .);
@@ -24,6 +25,9 @@ SECTIONS
.data : {
. = ALIGN(16);
+ /* Centre __global_pointer$ in the small-data window so the +/-2 KiB
+ reach of GP-relative addressing covers the .sdata/.sbss area. */
+ PROVIDE( __global_pointer$ = . + 0x800 );
*(.sdata .sdata.*) /* do not need to distinguish this from .data */
. = ALIGN(16);
*(.data .data.*)
diff --git a/ports/risc-v32/gnu/example_build/qemu_virt/plic.c b/ports/risc-v32/gnu/example_build/qemu_virt/plic.c
index 01e5c71a..b247f4fe 100644..120000
--- a/ports/risc-v32/gnu/example_build/qemu_virt/plic.c
+++ b/ports/risc-v32/gnu/example_build/qemu_virt/plic.c
@@ -1,72 +1 @@
-#include "plic.h"
-#include <stddef.h>
-irq_callback callbacks[MAX_CALLBACK_NUM];
-
-void plic_irq_enable(int irqno)
-{
- int hart = riscv_get_core();
- *(uint32_t*)PLIC_MENABLE(hart) = (*(uint32_t*)PLIC_MENABLE(hart) | (1 << irqno));
- return;
-}
-
-void plic_irq_disable(int irqno)
-{
- int hart = riscv_get_core();
- *(uint32_t*)PLIC_MENABLE(hart) = (*(uint32_t*)PLIC_MENABLE(hart) & (~(1 << irqno)));
- return;
-}
-
-void plic_prio_set(int irqno, int prio)
-{
- PLIC_SET_PRIO(irqno, prio);
-}
-
-int plic_prio_get(int irqno)
-{
- return PLIC_GET_PRIO(irqno);
-}
-
-int plic_register_callback(int irqno, irq_callback callback)
-{
- if(!(irqno >=0 && irqno < MAX_CALLBACK_NUM))
- return -1;
- callbacks[irqno] = callback;
- return 0;
-}
-
-int plic_unregister_callback(int irqno)
-{
- return plic_register_callback(irqno, NULL);
-}
-
-int plic_init(void)
-{
- for(int i=0;i<MAX_CALLBACK_NUM;i++)
- {
- callbacks[i] = NULL;
- }
- return 0;
-}
-
-int plic_claim(void)
-{
- int hart = riscv_get_core();
- return (*(uint32_t*)PLIC_MCLAIM(hart));
-}
-
-void plic_complete(int irqno)
-{
- int hart = riscv_get_core();
- *(uint32_t*)(PLIC_MCOMPLETE(hart)) = (uint32_t)irqno;
- return;
-}
-
-int plic_irq_intr(void)
-{
- int ret = -1;
- int irqno = plic_claim();
- if(callbacks[irqno] != NULL)
- ret = (callbacks[irqno])(irqno);
- plic_complete(irqno);
- return ret;
-}
+../../../../risc-v_common/example_build/plic/plic.c \ No newline at end of file
diff --git a/ports/risc-v32/gnu/example_build/qemu_virt/plic.h b/ports/risc-v32/gnu/example_build/qemu_virt/plic.h
index f505e608..f776a3d2 100644..120000
--- a/ports/risc-v32/gnu/example_build/qemu_virt/plic.h
+++ b/ports/risc-v32/gnu/example_build/qemu_virt/plic.h
@@ -1,49 +1 @@
-/***************************************************************************
- * Copyright (c) 2025 10xEngineers
- *
- * 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.
- *
- * SPDX-License-Identifier: MIT
- **************************************************************************/
-
-#ifndef RISCV_PLIC_H
-#define RISCV_PLIC_H
-
-#include "csr.h"
-#include <stdint.h>
-
-#define PLIC 0x0c000000L
-#define PLIC_PRIORITY (PLIC + 0x0)
-#define PLIC_PENDING (PLIC + 0x1000)
-#define PLIC_MENABLE(hart) (PLIC + 0x2000 + (hart)*0x100)
-#define PLIC_SENABLE(hart) (PLIC + 0x2080 + (hart)*0x100)
-#define PLIC_MPRIORITY(hart) (PLIC + 0x200000 + (hart)*0x2000)
-#define PLIC_SPRIORITY(hart) (PLIC + 0x201000 + (hart)*0x2000)
-#define PLIC_MCLAIM(hart) (PLIC + 0x200004 + (hart)*0x2000)
-#define PLIC_SCLAIM(hart) (PLIC + 0x201004 + (hart)*0x2000)
-#define PLIC_MCOMPLETE(hart) (PLIC + 0x200004 + (hart)*0x2000)
-#define PLIC_SCOMPLETE(hart) (PLIC + 0x201004 + (hart)*0x2000)
-
-
-#define PLIC_GET_PRIO(irqno) (*(uint32_t *)(PLIC_PRIORITY + (irqno)*4))
-#define PLIC_SET_PRIO(irqno, prio) (*(uint32_t *)(PLIC_PRIORITY + (irqno)*4) = (prio))
-
-#define MAX_CALLBACK_NUM 128
-typedef int (*irq_callback)(int irqno);
-
-void plic_irq_enable(int irqno);
-void plic_irq_disable(int irqno);
-int plic_prio_get(int irqno);
-void plic_prio_set(int irqno, int prio);
-int plic_register_callback(int irqno, irq_callback callback);
-int plic_unregister_callback(int irqno);
-int plic_init(void);
-int plic_claim(void);
-void plic_complete(int irqno);
-
-int plic_irq_intr(void);
-
-#endif
-
+../../../../risc-v_common/example_build/plic/plic.h \ No newline at end of file
diff --git a/ports/risc-v32/gnu/example_build/qemu_virt/test/threadx_test_tx_gnu_riscv32_qemu.py b/ports/risc-v32/gnu/example_build/qemu_virt/test/threadx_test_tx_gnu_riscv32_qemu.py
new file mode 100644
index 00000000..6123cd73
--- /dev/null
+++ b/ports/risc-v32/gnu/example_build/qemu_virt/test/threadx_test_tx_gnu_riscv32_qemu.py
@@ -0,0 +1,295 @@
+##############################################################################
+# Copyright (c) 2024 Microsoft Corporation
+# 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.
+#
+# SPDX-License-Identifier: MIT
+##############################################################################
+
+import subprocess
+import sys
+import os
+import argparse
+import socket
+import select
+
+def print_content(content):
+ """Prints content using os.write to handle non-blocking stdout robustly."""
+ try:
+ msg = f"{content}\n".encode('utf-8')
+ total_len = len(msg)
+ written = 0
+ fd = sys.stdout.fileno()
+ while written < total_len:
+ try:
+ n = os.write(fd, msg[written:])
+ written += n
+ except BlockingIOError:
+ select.select([], [fd], [])
+ except Exception:
+ pass
+
+def get_free_port():
+ """Finds a free TCP port."""
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
+ s.bind(('', 0))
+ return s.getsockname()[1]
+
+def run_qemu_test(elf_path, qemu_bin, gdb_bin):
+ """
+ Runs a test cycle using QEMU and GDB.
+ """
+ print(f"Testing ELF: {elf_path}")
+ print(f"QEMU: {qemu_bin}")
+ print(f"GDB: {gdb_bin}")
+
+ # Find a free port for GDB connection
+ gdb_port = get_free_port()
+ print(f"Using GDB port: {gdb_port}")
+
+ # 1. Start QEMU in the background
+ qemu_cmd = [
+ qemu_bin,
+ "-M", "virt",
+ "-nographic",
+ "-bios", "none", # Disable default OpenSBI to avoid overlap at 0x80000000
+ "-kernel", elf_path,
+ "-gdb", f"tcp::{gdb_port}", "-S",
+ "-monitor", "none", # Disable monitor to avoid clutter
+ "-serial", "stdio" # Redirect serial output to stdio so we can see it
+ ]
+
+ print(f"Starting QEMU: {' '.join(qemu_cmd)}")
+ qemu_process = subprocess.Popen(
+ qemu_cmd,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ text=True
+ )
+
+ if qemu_process.poll() is not None:
+ print("QEMU failed to start.")
+ print(qemu_process.stderr.read())
+ return False
+
+ # 2. Create a GDB command file
+ # We use a defined command for the timer interrupt to perform the check automatically
+ gdb_cmds = """
+file {elf}
+target remote :{port}
+set pagination off
+set confirm off
+
+# Setup Breakpoints
+break tx_application_define
+break thread_0_entry
+break thread_6_and_7_entry
+break _tx_timer_interrupt
+
+# Execute to Application Definition
+continue
+
+# Inspect mstatus once thread_0 has started (FS bits should be observable;
+# kept as a smoke check, the lazy-save logic itself is targeted by a
+# follow-up PR).
+continue
+print/x $mstatus
+
+# Verify FPU Logic and Register State exercised by thread_6/7
+continue
+finish
+step
+step
+step
+print/x $mstatus
+info registers float
+print fpu_test_val
+
+# Await Timer Interrupt
+continue
+print "Hit Timer Interrupt"
+
+# Verify MEPC Integrity - Save State
+print/x $mepc
+set $saved_pc = $mepc
+
+# Verify System Timer Before ISR
+set $clock_before = _tx_timer_system_clock
+print $clock_before
+
+# Configure Time-Slice Test Conditions
+set _tx_timer_time_slice = 1
+set _tx_timer_expired_time_slice = 0
+set $ts_handler_called = 0
+
+# Set Breakpoint at Time-Slice Handler with Auto-Continue
+tbreak _tx_thread_time_slice
+commands
+ set $ts_handler_called = 1
+ continue
+end
+
+# Set Breakpoint at ISR Return Address
+set $ret_addr = $ra
+tbreak *$ret_addr
+continue
+
+# Verify Time-Slice Handler Was Called
+if $ts_handler_called == 1
+ print "SUCCESS: Time-slice handler called."
+else
+ print "FAILURE: Time-slice handler NOT called."
+end
+
+# Verify System Timer Increment (Monotonicity)
+set $clock_after = _tx_timer_system_clock
+print $clock_after
+
+if $clock_after > $clock_before
+ print "SUCCESS: System timer incremented."
+else
+ print "FAILURE: System timer did not increment."
+end
+
+# Verify Preemption Logic (Thread Priority)
+#
+# We are now stopped at the return address from _tx_timer_interrupt,
+# after _tx_thread_time_slice has had a chance to update
+# _tx_thread_execute_ptr but before trap_handler returns into
+# _tx_thread_context_restore. At this point, a pending preemption is
+# observable directly by comparing current_ptr (interrupted thread)
+# and execute_ptr (thread chosen by the scheduler).
+set $curr_ptr = _tx_thread_current_ptr
+set $exec_ptr = _tx_thread_execute_ptr
+if $curr_ptr != 0 && $exec_ptr != 0
+ set $curr_prio = $curr_ptr->tx_thread_priority
+ set $exec_prio = $exec_ptr->tx_thread_priority
+ printf "PREEMPT_CHECK current_prio=%d execute_prio=%d\\n", $curr_prio, $exec_prio
+ if $exec_prio < $curr_prio
+ printf "PREEMPT_VERIFIED_OK\\n"
+ else
+ printf "PREEMPT_VERIFIED_FAIL_NOT_OBSERVED\\n"
+ end
+else
+ printf "PREEMPT_VERIFIED_FAIL_NULL\\n"
+end
+
+quit
+""".format(port=gdb_port, elf=elf_path)
+
+ gdb_cmd_file = "test_cmds.gdb"
+ with open(gdb_cmd_file, "w") as f:
+ f.write(gdb_cmds)
+
+ # 3. Run GDB
+ gdb_cmd = [
+ gdb_bin,
+ "--batch",
+ "-x", gdb_cmd_file
+ ]
+
+ print_content(f"Starting GDB: {' '.join(gdb_cmd)}")
+
+ # Cap the GDB session to 30 s so a wedged batch script (e.g. a
+ # `continue` that never hits its breakpoint) cannot hang CI.
+ GDB_TIMEOUT_S = 30
+
+ try:
+ gdb_process = subprocess.run(
+ gdb_cmd,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ text=True,
+ timeout=GDB_TIMEOUT_S,
+ )
+
+ print_content("GDB Output:")
+ print_content(gdb_process.stdout)
+ if gdb_process.stderr:
+ print_content("GDB Error Output:")
+ print_content(gdb_process.stderr)
+
+ except subprocess.TimeoutExpired as e:
+ print_content(
+ f"FAILURE: GDB session exceeded {GDB_TIMEOUT_S}s timeout; "
+ "likely stuck on a `continue` that never matched a breakpoint."
+ )
+ if e.stdout:
+ print_content("GDB Output (partial):")
+ print_content(e.stdout if isinstance(e.stdout, str)
+ else e.stdout.decode(errors='replace'))
+ if e.stderr:
+ print_content("GDB Error Output (partial):")
+ print_content(e.stderr if isinstance(e.stderr, str)
+ else e.stderr.decode(errors='replace'))
+ return False
+
+ except Exception as e:
+ print_content(f"An error occurred during test execution: {e}")
+ return False
+
+ finally:
+ # 4. Clean up
+ print_content("Stopping QEMU...")
+ qemu_process.terminate()
+ try:
+ qemu_process.wait(timeout=2)
+ except subprocess.TimeoutExpired:
+ print_content("QEMU did not terminate gracefully, killing it forcefullly.")
+ qemu_process.kill()
+
+ # Verify results
+ stdout = gdb_process.stdout
+ timer_hit = "Breakpoint 4, _tx_timer_interrupt" in stdout
+ fpu_verified = False
+ preemption_verified = "PREEMPT_VERIFIED_OK" in stdout
+
+ if "Breakpoint 3, thread_6_and_7_entry" in stdout:
+ if "1.10" in stdout or "fpu_test_val" in stdout:
+ print_content("SUCCESS: FPU instructions executed and registers inspected.")
+ fpu_verified = True
+ else:
+ print_content("FAILURE: Hit thread, but failed to inspect FPU. Output does not contain expected value.")
+
+ if timer_hit:
+ print_content("SUCCESS: Timer Interrupt verified! Hit _tx_timer_interrupt.")
+ else:
+ print_content("FAILURE: Did not hit timer interrupt.")
+
+ if preemption_verified:
+ print_content("SUCCESS: Preemption verified (higher-priority thread "
+ "preempted a lower-priority one).")
+ else:
+ if "PREEMPT_VERIFIED_FAIL_INVERTED" in stdout:
+ print_content("FAILURE: Preemption inverted -- lower priority "
+ "thread scheduled over higher priority one.")
+ elif "PREEMPT_VERIFIED_FAIL_NULL" in stdout:
+ print_content("FAILURE: Preemption check saw NULL thread pointers.")
+ elif "PREEMPT_VERIFIED_FAIL_NOT_OBSERVED" in stdout:
+ print_content("FAILURE: Preemption was not observed within the "
+ "loop budget.")
+ else:
+ print_content("FAILURE: Preemption check did not run to completion.")
+
+ if timer_hit and fpu_verified and preemption_verified:
+ return True
+ else:
+ return False
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(description="Run ThreadX QEMU/GDB Test")
+ parser.add_argument("--elf", required=True, help="Path to the kernel ELF file")
+ parser.add_argument("--qemu", default="qemu-system-riscv32", help="Path to QEMU binary")
+ parser.add_argument("--gdb", default="riscv-none-elf-gdb", help="Path to GDB binary")
+
+ args = parser.parse_args()
+
+ success = run_qemu_test(args.elf, args.qemu, args.gdb)
+
+ if success:
+ sys.exit(0)
+ else:
+ sys.exit(1)
diff --git a/ports/risc-v32/gnu/example_build/qemu_virt/trap.c b/ports/risc-v32/gnu/example_build/qemu_virt/trap.c
index a2733e02..bdce3305 100644..120000
--- a/ports/risc-v32/gnu/example_build/qemu_virt/trap.c
+++ b/ports/risc-v32/gnu/example_build/qemu_virt/trap.c
@@ -1,67 +1 @@
-#include "csr.h"
-#include <stdint.h>
-#include "uart.h"
-#include "hwtimer.h"
-#include "plic.h"
-#include <tx_port.h>
-#include <tx_api.h>
-
-#define OS_IS_INTERUPT(mcause) (mcause & 0x80000000u)
-#define OS_IS_EXCEPTION(mcause) (~(OS_IS_INTERUPT))
-#define OS_IS_TICK_INT(mcause) (mcause == 0x80000007u)
-#define OS_IS_SOFT_INT(mcause) (mcause == 0x80000003u)
-#define OS_IS_EXT_INT(mcause) (mcause == 0x8000000bu)
-#define OS_IS_TRAP_USER(mcause) (mcause == 0x0000000bu)
-extern void _tx_timer_interrupt(void);
-
-extern int uart_putc(int ch);
-
-static void print_hex(uintptr_t val)
-{
- char digits[] = "0123456789ABCDEF";
- uart_putc('0');
- uart_putc('x');
- for(int i = (sizeof(uintptr_t)*2) - 1; i >= 0; i--) {
- int d = (val >> (i*4)) & 0xF;
- uart_putc(digits[d]);
- }
- uart_putc('\n');
-}
-
-void trap_handler(uintptr_t mcause, uintptr_t mepc, uintptr_t mtval)
-{
- // uart_puts("DEBUG : threadx/ports/risc-v32/gnu/example_build/qemu_virt/trap.c, trap_handler\n");
- if(OS_IS_INTERUPT(mcause))
- {
- if(OS_IS_TICK_INT(mcause))
- {
- hwtimer_handler();
- _tx_timer_interrupt();
- }
- else if(OS_IS_EXT_INT(mcause))
- {
- int ret = plic_irq_intr();
- if(ret)
- {
- puts("[INTERRUPT]: handler irq error!");
- while(1) ;
- }
- }
- else
- {
- puts("[INTERRUPT]: now can't deal with the interrupt!");
- while(1) ;
- }
- }
- else
- {
- puts("[EXCEPTION] : Unkown Error!!");
- puts("mcause:");
- print_hex(mcause);
- puts("mepc:");
- print_hex(mepc);
- puts("mtval:");
- print_hex(mtval);
- while(1) ;
- }
-}
+../../../../risc-v_common/example_build/trap/trap_qemu.c \ No newline at end of file
diff --git a/ports/risc-v32/gnu/example_build/qemu_virt/uart.c b/ports/risc-v32/gnu/example_build/qemu_virt/uart.c
index a175b7d2..1666c8c8 100644..120000
--- a/ports/risc-v32/gnu/example_build/qemu_virt/uart.c
+++ b/ports/risc-v32/gnu/example_build/qemu_virt/uart.c
@@ -1,102 +1 @@
-#include "uart.h"
-#include "csr.h"
-#include "plic.h"
-#include <stdint.h>
-
-// the UART control registers are memory-mapped
-// at address UART0. this macro returns the
-// address of one of the registers.
-#define Reg(reg) ((volatile unsigned char *)(UART0 + (reg)))
-
-// the UART control registers.
-// some have different meanings for
-// read vs write.
-// see http://byterunner.com/16550.html
-#define RHR 0 // receive holding register (for input bytes)
-#define THR 0 // transmit holding register (for output bytes)
-#define IER 1 // interrupt enable register
-#define IER_RX_ENABLE (1<<0)
-#define IER_TX_ENABLE (1<<1)
-#define FCR 2 // FIFO control register
-#define FCR_FIFO_ENABLE (1<<0)
-#define FCR_FIFO_CLEAR (3<<1) // clear the content of the two FIFOs
-#define ISR 2 // interrupt status register
-#define LCR 3 // line control register
-#define LCR_EIGHT_BITS (3<<0)
-#define LCR_BAUD_LATCH (1<<7) // special mode to set baud rate
-#define LSR 5 // line status register
-#define LSR_RX_READY (1<<0) // input is waiting to be read from RHR
-#define LSR_TX_IDLE (1<<5) // THR can accept another character to send
-
-#define ReadReg(reg) (*(Reg(reg)))
-#define WriteReg(reg, v) (*(Reg(reg)) = (v))
-
-int uart_init(void)
-{
- // disable interrupts.
- WriteReg(IER, 0x00);
-
- // special mode to set baud rate.
- WriteReg(LCR, LCR_BAUD_LATCH);
-
- // LSB for baud rate of 38.4K.
- WriteReg(0, 0x03);
-
- // MSB for baud rate of 38.4K.
- WriteReg(1, 0x00);
-
- // leave set-baud mode,
- // and set word length to 8 bits, no parity.
- WriteReg(LCR, LCR_EIGHT_BITS);
-
- // reset and enable FIFOs.
- WriteReg(FCR, FCR_FIFO_ENABLE | FCR_FIFO_CLEAR);
-
- // enable transmit and receive interrupts.
- // WriteReg(IER, IER_TX_ENABLE | IER_RX_ENABLE);
-
- //enable UART0 in PLIC
- plic_irq_enable(UART0_IRQ);
-
- //set UART0 priority in PLIC
- plic_prio_set(UART0_IRQ, 1);
-
- //register callback for UART0
- //plic_register_callback(UART0_IRQ, uart_intr);
- puts("[UART0] : Uart Init Done, this is Test output!");
- return 0;
-}
-
-void uart_putc_nolock(int ch)
-{
- // wait for Transmit Holding Empty to be set in LSR.
- while((ReadReg(LSR) & LSR_TX_IDLE) == 0)
- ;
- WriteReg(THR, ch);
- return;
-}
-
-int uart_putc(int ch)
-{
- int intr_enable = riscv_mintr_get();
- riscv_mintr_off();
- uart_putc_nolock(ch);
- riscv_mintr_restore(intr_enable);
- return 1;
-}
-
-int uart_puts(const char* str)
-{
- int i;
- int intr_enable = riscv_mintr_get();
- riscv_mintr_off();
- for(i=0;str[i]!=0;i++)
- {
- uart_putc_nolock(str[i]);
- }
- uart_putc_nolock('\n');
- riscv_mintr_restore(intr_enable);
- return i;
-}
-
-
+../../../../risc-v_common/example_build/uart/uart_qemu_ns16550.c \ No newline at end of file
diff --git a/ports/risc-v32/gnu/example_build/qemu_virt/uart.h b/ports/risc-v32/gnu/example_build/qemu_virt/uart.h
index debfd9df..aba5ae7b 100644..120000
--- a/ports/risc-v32/gnu/example_build/qemu_virt/uart.h
+++ b/ports/risc-v32/gnu/example_build/qemu_virt/uart.h
@@ -1,23 +1 @@
-/***************************************************************************
- * Copyright (c) 2024 Microsoft Corporation
- * Copyright (c) 2026-present 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.
- *
- * SPDX-License-Identifier: MIT
- **************************************************************************/
-
-#ifndef RISCV_UART_H
-#define RISCV_UART_H
-
-#define UART0 0x10000000L
-#define UART0_IRQ 10
-
-#define puts uart_puts
-int uart_init(void);
-int uart_putc(int ch);
-void uart_putc_nolock(int ch);
-int uart_puts(const char* str);
-#endif
+../../../../risc-v_common/example_build/uart/uart_qemu_ns16550.h \ No newline at end of file
diff --git a/ports/risc-v32/gnu/example_build/xuantie_smartl_fpga/build_libthreadx.sh b/ports/risc-v32/gnu/example_build/xuantie_smartl_fpga/build_libthreadx.sh
index ff9bb930..ff983efe 100755
--- a/ports/risc-v32/gnu/example_build/xuantie_smartl_fpga/build_libthreadx.sh
+++ b/ports/risc-v32/gnu/example_build/xuantie_smartl_fpga/build_libthreadx.sh
@@ -1,4 +1,15 @@
#!/bin/bash
+##############################################################################
+# Copyright (c) 2024 Microsoft Corporation
+# 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.
+#
+# SPDX-License-Identifier: MIT
+##############################################################################
+
pushd ../../../../../
rm -rf build
diff --git a/ports/risc-v32/gnu/example_build/xuantie_smartl_fpga/build_threadx_sample.sh b/ports/risc-v32/gnu/example_build/xuantie_smartl_fpga/build_threadx_sample.sh
index ec91e36a..cc0b6a67 100755
--- a/ports/risc-v32/gnu/example_build/xuantie_smartl_fpga/build_threadx_sample.sh
+++ b/ports/risc-v32/gnu/example_build/xuantie_smartl_fpga/build_threadx_sample.sh
@@ -1,4 +1,15 @@
#!/bin/bash
+##############################################################################
+# Copyright (c) 2024 Microsoft Corporation
+# 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.
+#
+# SPDX-License-Identifier: MIT
+##############################################################################
+
bash build_libthreadx.sh
diff --git a/ports/risc-v32/gnu/example_build/xuantie_smartl_fpga/components/chip_riscv_dummy/include/dw_uart.h b/ports/risc-v32/gnu/example_build/xuantie_smartl_fpga/components/chip_riscv_dummy/include/dw_uart.h
index e69de29b..054d0844 100644
--- a/ports/risc-v32/gnu/example_build/xuantie_smartl_fpga/components/chip_riscv_dummy/include/dw_uart.h
+++ b/ports/risc-v32/gnu/example_build/xuantie_smartl_fpga/components/chip_riscv_dummy/include/dw_uart.h
@@ -0,0 +1,11 @@
+/***************************************************************************/
+/* Copyright (c) 2024 Microsoft Corporation */
+/* 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. */
+/* */
+/* SPDX-License-Identifier: MIT */
+/***************************************************************************/
+
diff --git a/ports/risc-v32/gnu/example_build/xuantie_smartl_fpga/components/csi/csi2/include/drv/list.h b/ports/risc-v32/gnu/example_build/xuantie_smartl_fpga/components/csi/csi2/include/drv/list.h
index 9f65050c..5a09070c 100755
--- a/ports/risc-v32/gnu/example_build/xuantie_smartl_fpga/components/csi/csi2/include/drv/list.h
+++ b/ports/risc-v32/gnu/example_build/xuantie_smartl_fpga/components/csi/csi2/include/drv/list.h
@@ -1,3 +1,14 @@
+/***************************************************************************/
+/* Copyright (c) 2024 Microsoft Corporation */
+/* 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. */
+/* */
+/* SPDX-License-Identifier: MIT */
+/***************************************************************************/
+
#ifndef AOS_LIST_H
#define AOS_LIST_H
diff --git a/ports/risc-v32/gnu/example_build/xuantie_smartl_fpga/demo_threadx.c b/ports/risc-v32/gnu/example_build/xuantie_smartl_fpga/demo_threadx.c
index 3df2c251..f8a6d82c 100644
--- a/ports/risc-v32/gnu/example_build/xuantie_smartl_fpga/demo_threadx.c
+++ b/ports/risc-v32/gnu/example_build/xuantie_smartl_fpga/demo_threadx.c
@@ -1,3 +1,14 @@
+/***************************************************************************/
+/* Copyright (c) 2024 Microsoft Corporation */
+/* 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. */
+/* */
+/* SPDX-License-Identifier: MIT */
+/***************************************************************************/
+
/* This is a small demo of the high-performance ThreadX kernel. It includes examples of eight
threads of different priorities, using a message queue, semaphore, mutex, event flags group,
byte pool, and block pool. */
diff --git a/ports/risc-v32/gnu/inc/tx_port.h b/ports/risc-v32/gnu/inc/tx_port.h
index ed014675..baf14be9 100644
--- a/ports/risc-v32/gnu/inc/tx_port.h
+++ b/ports/risc-v32/gnu/inc/tx_port.h
@@ -47,228 +47,109 @@
#ifndef TX_PORT_H
#define TX_PORT_H
-#ifndef __ASSEMBLER__
-
-/* Include for memset. */
-#include <string.h>
-
+/* Include shared RISC-V32 port definitions common to all toolchain ports. */
+#include "../../common/tx_port_riscv32_common.h"
-/* Determine if the optional ThreadX user define file should be used. */
-#ifdef TX_INCLUDE_USER_DEFINE_FILE
-/* Yes, include the user defines in tx_user.h. The defines in this file may
- alternately be defined on the command line. */
-#include "tx_user.h"
-#endif /* TX_INCLUDE_USER_DEFINE_FILE */
-
-#endif /* __ASSEMBLER__ */
-
-
-/* Define ThreadX basic types for this port. */
-
-#define VOID void
+/* Define automated coverage test extensions for the ThreadX regression test. */
#ifndef __ASSEMBLER__
-typedef char CHAR;
-typedef unsigned char UCHAR;
-typedef int INT;
-typedef unsigned int UINT;
-typedef long LONG;
-typedef unsigned long ULONG;
-typedef unsigned long long ULONG64;
-typedef short SHORT;
-typedef unsigned short USHORT;
-#define ULONG64_DEFINED
-#endif /* __ASSEMBLER__ */
-
-
-
-/* Define the priority levels for ThreadX. Legal values range
- from 32 to 1024 and MUST be evenly divisible by 32. */
-
-#ifndef TX_MAX_PRIORITIES
-#define TX_MAX_PRIORITIES 32
+typedef unsigned int TEST_FLAG;
+extern TEST_FLAG threadx_byte_allocate_loop_test;
+extern TEST_FLAG threadx_byte_release_loop_test;
+extern TEST_FLAG threadx_mutex_suspension_put_test;
+extern TEST_FLAG threadx_mutex_suspension_priority_test;
+#ifndef TX_TIMER_PROCESS_IN_ISR
+extern TEST_FLAG threadx_delete_timer_thread;
#endif
-
-/* Define the minimum stack for a ThreadX thread on this processor. If the size supplied during
- thread creation is less than this value, the thread create call will return an error. */
-
-#ifndef TX_MINIMUM_STACK
-#define TX_MINIMUM_STACK 1024 /* Minimum stack size for this port */
+extern void abort_and_resume_byte_allocating_thread(void);
+extern void abort_all_threads_suspended_on_mutex(void);
+extern void suspend_lowest_priority(void);
+#ifndef TX_TIMER_PROCESS_IN_ISR
+extern void delete_timer_thread(void);
#endif
+extern TEST_FLAG test_stack_analyze_flag;
+extern TEST_FLAG test_initialize_flag;
+extern TEST_FLAG test_forced_mutex_timeout;
-/* Define the system timer thread's default stack size and priority. These are only applicable
- if TX_TIMER_PROCESS_IN_ISR is not defined. */
-
-#ifndef TX_TIMER_THREAD_STACK_SIZE
-#define TX_TIMER_THREAD_STACK_SIZE 1024 /* Default timer thread stack size */
-#endif
-
-#ifndef TX_TIMER_THREAD_PRIORITY
-#define TX_TIMER_THREAD_PRIORITY 0 /* Default timer thread priority */
-#endif
-
-
-/* Define various constants for the ThreadX RISC-V port. */
-
-#define TX_INT_DISABLE 0x00000000 /* Disable interrupts value */
-#define TX_INT_ENABLE 0x00000008 /* Enable interrupt value */
-
-
-/* Define the clock source for trace event entry time stamp. The following two item are port specific.
- For example, if the time source is at the address 0x0a800024 and is 16-bits in size, the clock
- source constants would be:
-
-#define TX_TRACE_TIME_SOURCE *((ULONG *) 0x0a800024)
-#define TX_TRACE_TIME_MASK 0x0000FFFFUL
-
-*/
-
-#ifndef TX_TRACE_TIME_SOURCE
-#define TX_TRACE_TIME_SOURCE ++_tx_trace_simulated_time
-#endif
-#ifndef TX_TRACE_TIME_MASK
-#define TX_TRACE_TIME_MASK 0xFFFFFFFFUL
-#endif
-
-
-/* Define the port specific options for the _tx_build_options variable. This variable indicates
- how the ThreadX library was built. */
-
-#define TX_PORT_SPECIFIC_BUILD_OPTIONS 0
-
-
-/* Define the in-line initialization constant so that modules with in-line
- initialization capabilities can prevent their initialization from being
- a function call. */
-
-#define TX_INLINE_INITIALIZATION
+#ifdef TX_REGRESSION_TEST
+#define TX_BYTE_ALLOCATE_EXTENSION if (threadx_byte_allocate_loop_test == ((TEST_FLAG) 1)) \
+ { \
+ pool_ptr -> tx_byte_pool_owner = TX_NULL; \
+ threadx_byte_allocate_loop_test = ((TEST_FLAG) 0); \
+ }
-/* Determine whether or not stack checking is enabled. By default, ThreadX stack checking is
- disabled. When the following is defined, ThreadX thread stack checking is enabled. If stack
- checking is enabled (TX_ENABLE_STACK_CHECKING is defined), the TX_DISABLE_STACK_FILLING
- define is negated, thereby forcing the stack fill which is necessary for the stack checking
- logic. */
-
-#ifdef TX_ENABLE_STACK_CHECKING
-#undef TX_DISABLE_STACK_FILLING
-#endif
-
-
-/* Define the TX_THREAD control block extensions for this port. The main reason
- for the multiple macros is so that backward compatibility can be maintained with
- existing ThreadX kernel awareness modules. */
-
-#define TX_THREAD_EXTENSION_0
-#define TX_THREAD_EXTENSION_1
-#define TX_THREAD_EXTENSION_2
-#define TX_THREAD_EXTENSION_3
-
-
-/* Define the port extensions of the remaining ThreadX objects. */
-
-#define TX_BLOCK_POOL_EXTENSION
-#define TX_BYTE_POOL_EXTENSION
-#define TX_EVENT_FLAGS_GROUP_EXTENSION
-#define TX_MUTEX_EXTENSION
-#define TX_QUEUE_EXTENSION
-#define TX_SEMAPHORE_EXTENSION
-#define TX_TIMER_EXTENSION
-
-
-/* Define the user extension field of the thread control block. Nothing
- additional is needed for this port so it is defined as white space. */
-
-#ifndef TX_THREAD_USER_EXTENSION
-#define TX_THREAD_USER_EXTENSION
-#endif
-
-
-/* Define the macros for processing extensions in tx_thread_create, tx_thread_delete,
- tx_thread_shell_entry, and tx_thread_terminate. */
-
-#define TX_THREAD_CREATE_EXTENSION(thread_ptr)
-#define TX_THREAD_DELETE_EXTENSION(thread_ptr)
-#define TX_THREAD_COMPLETED_EXTENSION(thread_ptr)
-#define TX_THREAD_TERMINATED_EXTENSION(thread_ptr)
-
-
-/* Define the ThreadX object creation extensions for the remaining objects. */
-
-#define TX_BLOCK_POOL_CREATE_EXTENSION(pool_ptr)
-#define TX_BYTE_POOL_CREATE_EXTENSION(pool_ptr)
-#define TX_EVENT_FLAGS_GROUP_CREATE_EXTENSION(group_ptr)
-#define TX_MUTEX_CREATE_EXTENSION(mutex_ptr)
-#define TX_QUEUE_CREATE_EXTENSION(queue_ptr)
-#define TX_SEMAPHORE_CREATE_EXTENSION(semaphore_ptr)
-#define TX_TIMER_CREATE_EXTENSION(timer_ptr)
+#define TX_BYTE_RELEASE_EXTENSION if (threadx_byte_release_loop_test == ((TEST_FLAG) 1)) \
+ { \
+ threadx_byte_release_loop_test = ((TEST_FLAG) 0); \
+ abort_and_resume_byte_allocating_thread(); \
+ }
+#define TX_MUTEX_PUT_EXTENSION_1 if (threadx_mutex_suspension_put_test == ((TEST_FLAG) 1)) \
+ { \
+ threadx_mutex_suspension_put_test = ((TEST_FLAG) 0); \
+ abort_all_threads_suspended_on_mutex(); \
+ }
-/* Define the ThreadX object deletion extensions for the remaining objects. */
+#define TX_MUTEX_PUT_EXTENSION_2 if (test_forced_mutex_timeout == ((TEST_FLAG) 1)) \
+ { \
+ test_forced_mutex_timeout = ((TEST_FLAG) 0); \
+ _tx_thread_wait_abort(mutex_ptr -> tx_mutex_suspension_list); \
+ }
-#define TX_BLOCK_POOL_DELETE_EXTENSION(pool_ptr)
-#define TX_BYTE_POOL_DELETE_EXTENSION(pool_ptr)
-#define TX_EVENT_FLAGS_GROUP_DELETE_EXTENSION(group_ptr)
-#define TX_MUTEX_DELETE_EXTENSION(mutex_ptr)
-#define TX_QUEUE_DELETE_EXTENSION(queue_ptr)
-#define TX_SEMAPHORE_DELETE_EXTENSION(semaphore_ptr)
-#define TX_TIMER_DELETE_EXTENSION(timer_ptr)
+#define TX_MUTEX_PRIORITY_CHANGE_EXTENSION if (threadx_mutex_suspension_priority_test == ((TEST_FLAG) 1)) \
+ { \
+ threadx_mutex_suspension_priority_test = ((TEST_FLAG) 0); \
+ suspend_lowest_priority(); \
+ }
+#ifndef TX_TIMER_PROCESS_IN_ISR
-/* Define ThreadX interrupt lockout and restore macros for protection on
- access of critical kernel information. The restore interrupt macro must
- restore the interrupt posture of the running thread prior to the value
- present prior to the disable macro. In most cases, the save area macro
- is used to define a local function save area for the disable and restore
- macros. */
+#define TX_TIMER_INITIALIZE_EXTENSION(a) if (threadx_delete_timer_thread == ((TEST_FLAG) 1)) \
+ { \
+ threadx_delete_timer_thread = ((TEST_FLAG) 0); \
+ delete_timer_thread(); \
+ (a) = ((UINT) 1); \
+ }
-/* Expose helper used to perform an atomic read/modify/write of mstatus.
- The helper composes and returns the posture per ThreadX contract. */
-#ifndef __ASSEMBLER__
-UINT _tx_thread_interrupt_control(UINT new_posture);
#endif
-#ifdef TX_DISABLE_INLINE
-
-#define TX_INTERRUPT_SAVE_AREA register UINT interrupt_save;
-
-#define TX_DISABLE __asm__ volatile("csrrci %0, mstatus, 8" : "=r" (interrupt_save) :: "memory");
-#define TX_RESTORE { \
- unsigned long _temp_mstatus; \
- __asm__ volatile( \
- "csrc mstatus, 8\n" \
- "andi %0, %1, 8\n" \
- "csrs mstatus, %0" \
- : "=&r" (_temp_mstatus) \
- : "r" (interrupt_save) \
- : "memory"); \
+#define TX_THREAD_STACK_ANALYZE_EXTENSION if (test_stack_analyze_flag == ((TEST_FLAG) 1)) \
+ { \
+ thread_ptr -> tx_thread_id = ((TEST_FLAG) 0); \
+ test_stack_analyze_flag = ((TEST_FLAG) 0); \
+ } \
+ else if (test_stack_analyze_flag == ((TEST_FLAG) 2)) \
+ { \
+ stack_ptr = thread_ptr -> tx_thread_stack_start; \
+ test_stack_analyze_flag = ((TEST_FLAG) 0); \
+ } \
+ else if (test_stack_analyze_flag == ((TEST_FLAG) 3)) \
+ { \
+ *stack_ptr = TX_STACK_FILL; \
+ test_stack_analyze_flag = ((TEST_FLAG) 0); \
+ } \
+ else \
+ { \
+ test_stack_analyze_flag = ((TEST_FLAG) 0); \
}
-#else
-
-#define TX_INTERRUPT_SAVE_AREA register UINT interrupt_save;
-
-#define TX_DISABLE interrupt_save = _tx_thread_interrupt_control(TX_INT_DISABLE);
-#define TX_RESTORE _tx_thread_interrupt_control(interrupt_save);
-
-#endif /* TX_DISABLE_INLINE */
-
+#define TX_INITIALIZE_KERNEL_ENTER_EXTENSION if (test_initialize_flag == ((TEST_FLAG) 1)) \
+ { \
+ test_initialize_flag = ((TEST_FLAG) 0); \
+ return; \
+ }
-/* Define the interrupt lockout macros for each ThreadX object. */
+#endif /* TX_REGRESSION_TEST */
-#define TX_BLOCK_POOL_DISABLE TX_DISABLE
-#define TX_BYTE_POOL_DISABLE TX_DISABLE
-#define TX_EVENT_FLAGS_GROUP_DISABLE TX_DISABLE
-#define TX_MUTEX_DISABLE TX_DISABLE
-#define TX_QUEUE_DISABLE TX_DISABLE
-#define TX_SEMAPHORE_DISABLE TX_DISABLE
+#endif /* __ASSEMBLER__ */
/* Define the version ID of ThreadX. This may be utilized by the application. */
@@ -276,7 +157,7 @@ UINT _tx_thread_interrupt_control(UIN
#ifndef __ASSEMBLER__
#ifdef TX_THREAD_INIT
CHAR _tx_version_id[] =
- "(c) 2024 Microsoft Corp. (c) 2026-present Eclipse ThreadX contributors. * ThreadX RISC-V32/GNU Version 6.5.0.202601 *";
+ "(c) 2024 Microsoft Corp. (c) 2026-present Eclipse ThreadX contributors. * ThreadX RISC-V32/GNU Version 6.5.1.202602 *";
#else
extern CHAR _tx_version_id[];
#endif /* TX_THREAD_INIT */
diff --git a/ports/risc-v32/gnu/src/tx_initialize_low_level.S b/ports/risc-v32/gnu/src/tx_initialize_low_level.S
index 703466bd..2940f215 100644
--- a/ports/risc-v32/gnu/src/tx_initialize_low_level.S
+++ b/ports/risc-v32/gnu/src/tx_initialize_low_level.S
@@ -62,7 +62,7 @@ __tx_free_memory_start:
/**************************************************************************/
/* VOID _tx_initialize_low_level(VOID)
{ */
- .global _tx_initialize_low_level
+ .weak _tx_initialize_low_level
.weak _tx_initialize_low_level
_tx_initialize_low_level:
diff --git a/ports/risc-v32/gnu/src/tx_thread_context_restore.S b/ports/risc-v32/gnu/src/tx_thread_context_restore.S
index 73a07f61..d3d449e4 100644
--- a/ports/risc-v32/gnu/src/tx_thread_context_restore.S
+++ b/ports/risc-v32/gnu/src/tx_thread_context_restore.S
@@ -29,6 +29,7 @@
/* AUTHOR */
/* */
/* Akif Ejaz, 10xEngineers */
+/* Wei-Chen Lai, National Cheng Kung University */
/* */
/* DESCRIPTION */
/* */
@@ -82,7 +83,13 @@ _tx_thread_context_restore:
/* Just recover the saved registers and return to the point of
interrupt. */
- /* Recover floating point registers. */
+ /* Recover floating point registers only if saved mstatus.FS was not Off. */
+#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double)
+ lw t1, 29*4(sp) // Pickup saved mstatus
+ srli t1, t1, 13
+ andi t1, t1, 0x3
+ beqz t1, _tx_thread_skip_fp_restore // Skip if FS was Off
+#endif
#if defined(__riscv_float_abi_single)
flw f0, 31*4(sp) // Recover ft0
flw f1, 32*4(sp) // Recover ft1
@@ -130,6 +137,7 @@ _tx_thread_context_restore:
lw t0, 63*4(sp) // Recover fcsr
csrw fcsr, t0 // Restore fcsr
#endif
+_tx_thread_skip_fp_restore:
/* Recover standard registers. */
@@ -149,7 +157,7 @@ _tx_thread_context_restore:
/* Clear MPP/MPIE/MIE bits in t1 then set desired values. */
li t2, 0x1888 // MPP(0x1800) | MPIE(0x80) | MIE(0x08)
- li t3, 0x1800 // Set MPP to Machine mode (bits 12:11)
+ li t3, 0x1880 // Set MPP=Machine (0x1800) + MPIE=1 (0x80); mret sets MIE=MPIE
/* Construct new mstatus in t1: clear mask bits, set MPP/MPIE and optionally FP bit,
preserve everything except the bits we will modify. */
@@ -222,7 +230,13 @@ _tx_thread_no_preempt_restore:
lw sp, 8(t1) // Switch back to thread's stack
- /* Recover floating point registers. */
+ /* Recover floating point registers only if saved mstatus.FS was not Off. */
+#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double)
+ lw t3, 29*4(sp) // Pickup saved mstatus
+ srli t3, t3, 13
+ andi t3, t3, 0x3
+ beqz t3, _tx_thread_no_preempt_skip_fp_restore // Skip if FS was Off
+#endif
#if defined(__riscv_float_abi_single)
flw f0, 31*4(sp) // Recover ft0
flw f1, 32*4(sp) // Recover ft1
@@ -270,6 +284,7 @@ _tx_thread_no_preempt_restore:
lw t0, 63*4(sp) // Recover fcsr
csrw fcsr, t0 // Restore fcsr
#endif
+_tx_thread_no_preempt_skip_fp_restore:
/* Recover the saved context and return to the point of interrupt. */
@@ -284,7 +299,7 @@ _tx_thread_no_preempt_restore:
csrr t1, mstatus
li t2, 0x1888 // MPP(0x1800) | MPIE(0x80) | MIE(0x08)
- li t3, 0x1800 // Set MPP to Machine mode
+ li t3, 0x1880 // Set MPP=Machine (0x1800) + MPIE=1 (0x80); mret sets MIE=MPIE
li t4, ~0x1888 // Clear mask for MPP/MPIE/MIE
and t1, t1, t4
or t1, t1, t3
@@ -331,7 +346,13 @@ _tx_thread_preempt_restore:
ori t3, zero, 1 // Build interrupt stack type
sw t3, 0(t0) // Store stack type
- /* Store floating point preserved registers. */
+ /* Store floating point preserved registers only if saved mstatus.FS was not Off. */
+#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double)
+ lw t3, 29*4(t0) // Pickup saved mstatus
+ srli t3, t3, 13
+ andi t3, t3, 0x3
+ beqz t3, _tx_thread_preempt_skip_fp_restore // Skip if FS was Off
+#endif
#ifdef __riscv_float_abi_single
fsw f8, 39*4(t0) // Store fs0
fsw f9, 40*4(t0) // Store fs1
@@ -359,6 +380,7 @@ _tx_thread_preempt_restore:
fsd f26, 57*4(t0) // Store fs10
fsd f27, 58*4(t0) // Store fs11
#endif
+_tx_thread_preempt_skip_fp_restore:
/* Store standard preserved registers. */
diff --git a/ports/risc-v32/gnu/src/tx_thread_context_save.S b/ports/risc-v32/gnu/src/tx_thread_context_save.S
index 66402934..0a0c0c15 100644
--- a/ports/risc-v32/gnu/src/tx_thread_context_save.S
+++ b/ports/risc-v32/gnu/src/tx_thread_context_save.S
@@ -29,6 +29,7 @@
/* AUTHOR */
/* */
/* Akif Ejaz, 10xEngineers */
+/* Wei-Chen Lai, National Cheng Kung University */
/* */
/* DESCRIPTION */
/* */
@@ -96,6 +97,15 @@ _tx_thread_context_save:
sw t5, 14*4(sp) // Store t5
sw t6, 13*4(sp) // Store t6
+ /* Save mstatus and skip FP state if FS is Off. */
+ csrr t0, mstatus
+ sw t0, 29*4(sp)
+#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double)
+ srli t1, t0, 13
+ andi t1, t1, 0x3
+ beqz t1, _tx_thread_skip_fpu_save
+#endif
+
/* Save floating point registers. */
#if defined(__riscv_float_abi_single)
fsw f0, 31*4(sp) // Store ft0
@@ -144,14 +154,11 @@ _tx_thread_context_save:
csrr t0, fcsr
sw t0, 63*4(sp) // Store fcsr
#endif
+_tx_thread_skip_fpu_save:
csrr t0, mepc
sw t0, 30*4(sp) // Save it on the stack
- /* Save mstatus. */
- csrr t0, mstatus
- sw t0, 29*4(sp)
-
la t1, _tx_thread_current_ptr // Pickup address of current thread ptr
lw t2, 0(t1) // Pickup current thread pointer
beqz t2, _tx_thread_idle_system_save // If NULL, idle system was interrupted
@@ -190,6 +197,15 @@ _tx_thread_nested_save:
sw t5, 14*4(sp) // Store t5
sw t6, 13*4(sp) // Store t6
+ /* Save mstatus and skip FP state if FS is Off. */
+ csrr t0, mstatus
+ sw t0, 29*4(sp)
+#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double)
+ srli t1, t0, 13
+ andi t1, t1, 0x3
+ beqz t1, _tx_thread_skip_nested_fpu_save
+#endif
+
/* Save floating point registers. */
#if defined(__riscv_float_abi_single)
fsw f0, 31*4(sp) // Store ft0
@@ -238,13 +254,11 @@ _tx_thread_nested_save:
csrr t0, fcsr
sw t0, 63*4(sp) // Store fcsr
#endif
+_tx_thread_skip_nested_fpu_save:
csrr t0, mepc
sw t0, 30*4(sp) // Save it on stack
- csrr t0, mstatus
- sw t0, 29*4(sp)
-
/* Call the ISR execution exit function if enabled. */
#ifdef TX_ENABLE_EXECUTION_CHANGE_NOTIFY
call _tx_execution_isr_enter // Call the ISR execution enter function
diff --git a/ports/risc-v32/gnu/src/tx_thread_schedule.S b/ports/risc-v32/gnu/src/tx_thread_schedule.S
index 1c235a2d..79bfd51a 100644
--- a/ports/risc-v32/gnu/src/tx_thread_schedule.S
+++ b/ports/risc-v32/gnu/src/tx_thread_schedule.S
@@ -133,7 +133,12 @@ _tx_thread_ready_to_run:
lw t0, 0(sp) // Pickup stack type
beqz t0, _tx_thread_solicited_return // If 0, solicited return
- /* Recover floating point registers. */
+ /* Recover floating point registers only if saved mstatus.FS was not Off. */
+#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double)
+ lw t1, 29*4(sp) // Pickup saved mstatus
+ srli t1, t1, 13
+ andi t1, t1, 0x3
+ beqz t1, _tx_thread_schedule_skip_fp_restore // Skip if FS was Off
#if defined(__riscv_float_abi_single)
flw f0, 31*4(sp) // Recover ft0
flw f1, 32*4(sp) // Recover ft1
@@ -205,9 +210,10 @@ _tx_thread_ready_to_run:
lw t0, 63*4(sp) // Recover fcsr
csrw fcsr, t0 // Restore fcsr
#endif
+_tx_thread_schedule_skip_fp_restore:
+#endif
/* Recover standard registers. */
-
lw t0, 30*4(sp) // Recover mepc
csrw mepc, t0 // Setup mepc
@@ -256,7 +262,12 @@ _tx_thread_ready_to_run:
_tx_thread_solicited_return:
- /* Recover floating point registers. */
+ /* Recover floating point registers only if saved mstatus.FS was not Off. */
+#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double)
+ lw t1, 14*4(sp) // Pickup saved mstatus
+ srli t1, t1, 13
+ andi t1, t1, 0x3
+ beqz t1, _tx_thread_schedule_solicited_skip_fp_restore // Skip if FS was Off
#if defined(__riscv_float_abi_single)
flw f8, 15*4(sp) // Recover fs0
flw f9, 16*4(sp) // Recover fs1
@@ -288,10 +299,11 @@ _tx_thread_solicited_return:
lw t0, 27*4(sp) // Recover fcsr
csrw fcsr, t0 // Restore fcsr
#endif
+_tx_thread_schedule_solicited_skip_fp_restore:
+#endif
/* Recover standard registers. */
-
- lw t0, 14*4(sp) // Recover mstatus
+ lw t0, 14*4(sp) // Reload saved mstatus (t0 may hold fcsr if FP was restored)
csrw mstatus, t0 // Restore mstatus
lw ra, 13*4(sp) // Recover return address
diff --git a/ports/risc-v32/gnu/src/tx_thread_system_return.S b/ports/risc-v32/gnu/src/tx_thread_system_return.S
index 4090e7b2..20ff25f2 100644
--- a/ports/risc-v32/gnu/src/tx_thread_system_return.S
+++ b/ports/risc-v32/gnu/src/tx_thread_system_return.S
@@ -69,7 +69,17 @@ _tx_thread_system_return:
addi sp, sp, -16*4 // Allocate space on the stack - without floating point enabled
#endif
- /* Store floating point preserved registers. */
+ sw zero, 0(sp) // Solicited stack type (store early so slot is always written)
+
+ /* Read and save mstatus first; use it to guard FP register save. */
+ csrr t0, mstatus // Pickup current mstatus
+ sw t0, 14*4(sp) // Save mstatus
+
+ /* Store floating point preserved registers — only if FS != Off. */
+#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double)
+ srli t1, t0, 13 // Shift FS field to bits [1:0]
+ andi t1, t1, 0x3 // Isolate FS bits
+ beqz t1, _tx_thread_system_return_skip_fp // Skip FP save if FS == Off
#if defined(__riscv_float_abi_single)
fsw f8, 15*4(sp) // Store fs0
fsw f9, 16*4(sp) // Store fs1
@@ -101,8 +111,9 @@ _tx_thread_system_return:
csrr t0, fcsr
sw t0, 27*4(sp) // Store fcsr
#endif
+_tx_thread_system_return_skip_fp:
+#endif
- sw zero, 0(sp) // Solicited stack type
sw ra, 13*4(sp) // Save return address
sw s0, 12*4(sp) // Save s0
sw s1, 11*4(sp) // Save s1
@@ -116,9 +127,6 @@ _tx_thread_system_return:
sw s9, 3*4(sp) // Save s9
sw s10, 2*4(sp) // Save s10
sw s11, 1*4(sp) // Save s11
- csrr t0, mstatus // Pickup mstatus
- sw t0, 14*4(sp) // Save mstatus
-
/* Lockout interrupts. will be enabled in _tx_thread_schedule */