summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAkif Ejaz <[email protected]>2026-05-19 20:03:23 +0500
committerGitHub <[email protected]>2026-05-19 11:03:23 -0400
commitc1e3678797d5249268756287a33e5aca729a7631 (patch)
tree6279923a90fd3ad0206df4b167b46246bf71e2d0
parent0dd6d28afb54c17eae485fa8e7c9e6a7999353ff (diff)
Add QEMU based CI regression test infra for RV32 and RV64 (#526)
Added a QEMU virt-machine BSP and CTest infrastructure to run the ThreadX regression suite on both RISC-V 32-bit and 64-bit targets in CI. New components: - BSP (entry, trap, PLIC, CLINT timer, UART, linker script) targeting QEMU virt machine for RV32 and RV64 - CMake build system with Ninja, supporting multiple build configs - CI scripts: install_riscv.sh (toolchain + QEMU), build_tx_riscv.sh, test_tx_riscv.sh - GitHub Actions workflow job for RISC-V regression gating Port fixes: - RV32 tx_thread_context_restore.S: set MPIE alongside MPP (0x1800 → 0x1880) so mret re-enables interrupts - RV32/RV64 tx_port.h: add TX_REGRESSION_TEST extension macros needed by the test harness - RV32/RV64 example_build scripts: add compile and QEMU launch steps Regression test portability fixes: - Block memory tests: increase pool sizes (320 → 340) to accommodate larger RISC-V block-header alignment - Byte memory test: replace hardcoded offsets with BYTE_POOL_OVERHEAD macro for portable pool-size computation - Event flag timeout test: make counter tolerance unconditional, removing linux-only guard Signed-off-by: Akif Ejaz <[email protected]>
-rw-r--r--.github/workflows/regression_test.yml19
-rw-r--r--common/src/tx_trace_object_register.c4
-rw-r--r--common/src/tx_trace_object_unregister.c2
-rwxr-xr-xports/risc-v32/gnu/example_build/qemu_virt/build_libthreadx.sh21
-rw-r--r--ports/risc-v32/gnu/inc/tx_port.h98
-rw-r--r--ports/risc-v32/gnu/src/tx_thread_context_restore.S2
-rwxr-xr-xports/risc-v64/gnu/example_build/qemu_virt/build_libthreadx.sh18
-rw-r--r--ports/risc-v64/gnu/inc/tx_port.h98
-rwxr-xr-xscripts/build_tx_riscv.sh15
-rwxr-xr-xscripts/install_riscv.sh32
-rwxr-xr-xscripts/test_tx_riscv.sh19
-rw-r--r--test/tx/cmake/riscv/CMakeLists.txt79
-rw-r--r--test/tx/cmake/riscv/bsp/CMakeLists.txt25
-rw-r--r--test/tx/cmake/riscv/bsp/board.c30
-rw-r--r--test/tx/cmake/riscv/bsp/csr.h84
-rw-r--r--test/tx/cmake/riscv/bsp/entry.S75
-rw-r--r--test/tx/cmake/riscv/bsp/hwtimer.c33
-rw-r--r--test/tx/cmake/riscv/bsp/hwtimer.h22
-rw-r--r--test/tx/cmake/riscv/bsp/link.lds60
-rw-r--r--test/tx/cmake/riscv/bsp/plic.c87
-rw-r--r--test/tx/cmake/riscv/bsp/plic.h46
-rw-r--r--test/tx/cmake/riscv/bsp/printf.c168
-rw-r--r--test/tx/cmake/riscv/bsp/syscalls.c128
-rw-r--r--test/tx/cmake/riscv/bsp/trap.c84
-rw-r--r--test/tx/cmake/riscv/bsp/tx_initialize_low_level.S100
-rw-r--r--test/tx/cmake/riscv/bsp/uart.c79
-rw-r--r--test/tx/cmake/riscv/bsp/uart.h22
-rw-r--r--test/tx/cmake/riscv/regression/CMakeLists.txt165
-rwxr-xr-xtest/tx/cmake/riscv/run.sh151
-rw-r--r--test/tx/regression/threadx_block_memory_basic_test.c8
-rw-r--r--test/tx/regression/threadx_block_memory_error_detection_test.c8
-rw-r--r--test/tx/regression/threadx_block_memory_suspension_test.c4
-rw-r--r--test/tx/regression/threadx_block_memory_suspension_timeout_test.c4
-rw-r--r--test/tx/regression/threadx_block_memory_thread_terminate_test.c4
-rw-r--r--test/tx/regression/threadx_byte_memory_basic_test.c23
-rw-r--r--test/tx/regression/threadx_event_flag_suspension_timeout_test.c17
36 files changed, 1796 insertions, 38 deletions
diff --git a/.github/workflows/regression_test.yml b/.github/workflows/regression_test.yml
index c55a2c56..72e4198b 100644
--- a/.github/workflows/regression_test.yml
+++ b/.github/workflows/regression_test.yml
@@ -41,6 +41,23 @@ jobs:
cmake_path: ./test/smp/cmake
result_affix: SMP
skip_deploy: true
+ riscv:
+ permissions:
+ contents: read
+ issues: read
+ checks: write
+ pull-requests: write
+ pages: write
+ id-token: write
+ uses: ./.github/workflows/regression_template.yml
+ with:
+ install_script: ./scripts/install_riscv.sh
+ build_script: ./scripts/build_tx_riscv.sh
+ test_script: ./scripts/test_tx_riscv.sh
+ cmake_path: ./test/tx/cmake/riscv
+ result_affix: RISC-V
+ skip_deploy: true
+ skip_coverage: true
deploy:
permissions:
contents: read
@@ -49,7 +66,7 @@ jobs:
pull-requests: write
pages: write
id-token: write
- needs: [tx, smp]
+ needs: [tx, smp, riscv]
uses: ./.github/workflows/regression_template.yml
with:
skip_test: true
diff --git a/common/src/tx_trace_object_register.c b/common/src/tx_trace_object_register.c
index f7cd4a4a..741896a5 100644
--- a/common/src/tx_trace_object_register.c
+++ b/common/src/tx_trace_object_register.c
@@ -85,7 +85,7 @@ TX_TRACE_OBJECT_ENTRY *entry_ptr;
/* Trace buffer is enabled, proceed. */
/* Pickup the total entries. */
- entries = _tx_trace_total_registry_entries;
+ entries = (UINT) _tx_trace_total_registry_entries;
/* Determine if there are available entries in the registry. */
if (_tx_trace_available_registry_entries != ((ULONG) 0))
@@ -98,7 +98,7 @@ TX_TRACE_OBJECT_ENTRY *entry_ptr;
loop_break = TX_FALSE;
/* Loop to find available entry. */
- i = _tx_trace_registry_search_start;
+ i = (UINT) _tx_trace_registry_search_start;
do
{
diff --git a/common/src/tx_trace_object_unregister.c b/common/src/tx_trace_object_unregister.c
index 2166c973..5d0330a5 100644
--- a/common/src/tx_trace_object_unregister.c
+++ b/common/src/tx_trace_object_unregister.c
@@ -78,7 +78,7 @@ TX_TRACE_OBJECT_ENTRY *entry_ptr;
/* Registry is setup, proceed. */
/* Pickup the total entries. */
- entries = _tx_trace_total_registry_entries;
+ entries = (UINT) _tx_trace_total_registry_entries;
/* Loop to find available entry. */
for (i = ((ULONG) 0); i < entries; i++)
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..f3d80644 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,24 @@
#!/bin/bash
+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/inc/tx_port.h b/ports/risc-v32/gnu/inc/tx_port.h
index ed014675..5348a02e 100644
--- a/ports/risc-v32/gnu/inc/tx_port.h
+++ b/ports/risc-v32/gnu/inc/tx_port.h
@@ -271,6 +271,104 @@ UINT _tx_thread_interrupt_control(UIN
#define TX_SEMAPHORE_DISABLE TX_DISABLE
+/* Define automated coverage test extensions for the ThreadX regression test. */
+
+#ifndef __ASSEMBLER__
+
+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
+
+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;
+
+
+#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); \
+ }
+
+#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 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_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 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); \
+ }
+
+#endif
+
+#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); \
+ }
+
+#define TX_INITIALIZE_KERNEL_ENTER_EXTENSION if (test_initialize_flag == ((TEST_FLAG) 1)) \
+ { \
+ test_initialize_flag = ((TEST_FLAG) 0); \
+ return; \
+ }
+
+#endif /* TX_REGRESSION_TEST */
+
+#endif /* __ASSEMBLER__ */
+
+
/* Define the version ID of ThreadX. This may be utilized by the application. */
#ifndef __ASSEMBLER__
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..5fa80437 100644
--- a/ports/risc-v32/gnu/src/tx_thread_context_restore.S
+++ b/ports/risc-v32/gnu/src/tx_thread_context_restore.S
@@ -284,7 +284,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(0x80) so mret re-enables MIE
li t4, ~0x1888 // Clear mask for MPP/MPIE/MIE
and t1, t1, t4
or t1, t1, t3
diff --git a/ports/risc-v64/gnu/example_build/qemu_virt/build_libthreadx.sh b/ports/risc-v64/gnu/example_build/qemu_virt/build_libthreadx.sh
index 24a4c3ac..35f7ac5a 100755
--- a/ports/risc-v64/gnu/example_build/qemu_virt/build_libthreadx.sh
+++ b/ports/risc-v64/gnu/example_build/qemu_virt/build_libthreadx.sh
@@ -1,6 +1,24 @@
#!/bin/bash
+printf "y\n" | rm -rf ../../../../../build/
+rm -f kernel.elf
pushd ../../../../../
cmake -Bbuild -GNinja -DCMAKE_TOOLCHAIN_FILE=cmake/riscv64_gnu.cmake .
cmake --build ./build/
popd
+
+riscv64-unknown-elf-gcc \
+ -march=rv64gc -mabi=lp64d \
+ -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-riscv64 -nographic -smp 1 -bios none -m 128M -machine virt -kernel kernel.elf
diff --git a/ports/risc-v64/gnu/inc/tx_port.h b/ports/risc-v64/gnu/inc/tx_port.h
index c8f5b312..2f3bac1b 100644
--- a/ports/risc-v64/gnu/inc/tx_port.h
+++ b/ports/risc-v64/gnu/inc/tx_port.h
@@ -280,6 +280,104 @@ UINT _tx_thread_interrupt_control(UIN
#define TX_SEMAPHORE_DISABLE TX_DISABLE
+/* Define automated coverage test extensions for the ThreadX regression test. */
+
+#ifndef __ASSEMBLER__
+
+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
+
+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;
+
+
+#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); \
+ }
+
+#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 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_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 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); \
+ }
+
+#endif
+
+#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); \
+ }
+
+#define TX_INITIALIZE_KERNEL_ENTER_EXTENSION if (test_initialize_flag == ((TEST_FLAG) 1)) \
+ { \
+ test_initialize_flag = ((TEST_FLAG) 0); \
+ return; \
+ }
+
+#endif /* TX_REGRESSION_TEST */
+
+#endif /* __ASSEMBLER__ */
+
+
/* Define the version ID of ThreadX. This may be utilized by the application. */
#ifndef __ASSEMBLER__
diff --git a/scripts/build_tx_riscv.sh b/scripts/build_tx_riscv.sh
new file mode 100755
index 00000000..bad952df
--- /dev/null
+++ b/scripts/build_tx_riscv.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+# Build RISC-V regression tests for both RV32 and RV64.
+# Usage: build_tx_riscv.sh [all|<config>]
+
+SCRIPT_DIR="$(dirname "$(realpath "$0")")"
+RUN_SH="${SCRIPT_DIR}/../test/tx/cmake/riscv/run.sh"
+
+ARGS="${@:-all}"
+
+echo "=== Building RISC-V32 ==="
+"$RUN_SH" riscv32 build $ARGS
+
+echo ""
+echo "=== Building RISC-V64 ==="
+"$RUN_SH" riscv64 build $ARGS
diff --git a/scripts/install_riscv.sh b/scripts/install_riscv.sh
new file mode 100755
index 00000000..9c2f2cdb
--- /dev/null
+++ b/scripts/install_riscv.sh
@@ -0,0 +1,32 @@
+#!/bin/bash
+# Install RISC-V bare-metal cross-compiler toolchain and QEMU for CI.
+set -e
+
+RELEASE_TAG="2026.04.26"
+BASE_URL="https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/${RELEASE_TAG}"
+# Use ubuntu-24.04 binaries to match ubuntu-latest runners.
+RV32_TARBALL="riscv32-elf-ubuntu-24.04-gcc.tar.xz"
+RV64_TARBALL="riscv64-elf-ubuntu-24.04-gcc.tar.xz"
+
+echo "=== Installing QEMU and build tools ==="
+sudo apt-get update -qq
+sudo apt-get install -y -qq qemu-system-misc ninja-build cmake
+
+echo "=== Downloading RISC-V GCC toolchain (${RELEASE_TAG}) ==="
+
+# Both tarballs extract into riscv/ with non-overlapping prefixes
+# (riscv32-unknown-elf-* and riscv64-unknown-elf-*).
+for tarball in "$RV32_TARBALL" "$RV64_TARBALL"; do
+ echo "Downloading ${tarball} ..."
+ wget --no-verbose "${BASE_URL}/${tarball}" -O "/tmp/${tarball}"
+ sudo tar xJf "/tmp/${tarball}" -C /opt
+ rm "/tmp/${tarball}"
+done
+
+TOOLCHAIN_BIN=/opt/riscv/bin
+echo "$TOOLCHAIN_BIN" >> "$GITHUB_PATH"
+
+echo "=== Verifying installation ==="
+"$TOOLCHAIN_BIN/riscv32-unknown-elf-gcc" --version | head -1
+"$TOOLCHAIN_BIN/riscv64-unknown-elf-gcc" --version | head -1
+qemu-system-riscv64 --version | head -1
diff --git a/scripts/test_tx_riscv.sh b/scripts/test_tx_riscv.sh
new file mode 100755
index 00000000..7d35c53f
--- /dev/null
+++ b/scripts/test_tx_riscv.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+# Run RISC-V regression tests for both RV32 and RV64 on QEMU.
+# Usage: test_tx_riscv.sh [all|<config>]
+
+SCRIPT_DIR="$(dirname "$(realpath "$0")")"
+RUN_SH="${SCRIPT_DIR}/../test/tx/cmake/riscv/run.sh"
+
+ARGS="${@:-all}"
+
+exit_code=0
+
+echo "=== Testing RISC-V32 ==="
+CTEST_PARALLEL_LEVEL=4 "$RUN_SH" riscv32 test $ARGS || exit_code=$?
+
+echo ""
+echo "=== Testing RISC-V64 ==="
+CTEST_PARALLEL_LEVEL=4 "$RUN_SH" riscv64 test $ARGS || exit_code=$?
+
+exit $exit_code
diff --git a/test/tx/cmake/riscv/CMakeLists.txt b/test/tx/cmake/riscv/CMakeLists.txt
new file mode 100644
index 00000000..0352622d
--- /dev/null
+++ b/test/tx/cmake/riscv/CMakeLists.txt
@@ -0,0 +1,79 @@
+cmake_minimum_required(VERSION 3.13 FATAL_ERROR)
+cmake_policy(SET CMP0054 NEW)
+cmake_policy(SET CMP0057 NEW)
+
+project(threadx_riscv_test LANGUAGES C ASM)
+
+# Build configurations (same defines as Linux tests, minus coverage instrumentation)
+set(BUILD_CONFIGURATIONS default_build disable_notify_callbacks_build
+ stack_checking_build stack_checking_rand_fill_build trace_build)
+set(CMAKE_CONFIGURATION_TYPES
+ ${BUILD_CONFIGURATIONS}
+ CACHE STRING "list of supported configuration types" FORCE)
+set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
+ ${CMAKE_CONFIGURATION_TYPES})
+list(GET CMAKE_CONFIGURATION_TYPES 0 BUILD_TYPE)
+if((NOT CMAKE_BUILD_TYPE) OR (NOT ("${CMAKE_BUILD_TYPE}" IN_LIST
+ CMAKE_CONFIGURATION_TYPES)))
+ set(CMAKE_BUILD_TYPE
+ "${BUILD_TYPE}"
+ CACHE STRING "Build Type of the project" FORCE)
+endif()
+
+message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
+message(STATUS "Using toolchain file: ${CMAKE_TOOLCHAIN_FILE}.")
+message(STATUS "THREADX_ARCH: ${THREADX_ARCH}")
+
+# Per-configuration compile definitions
+set(default_build -DTX_QUEUE_MESSAGE_MAX_SIZE=32)
+set(disable_notify_callbacks_build -DTX_QUEUE_MESSAGE_MAX_SIZE=32 -DTX_DISABLE_NOTIFY_CALLBACKS)
+set(stack_checking_build -DTX_QUEUE_MESSAGE_MAX_SIZE=32 -DTX_ENABLE_STACK_CHECKING)
+set(stack_checking_rand_fill_build -DTX_QUEUE_MESSAGE_MAX_SIZE=32 -DTX_ENABLE_STACK_CHECKING -DTX_ENABLE_RANDOM_NUMBER_STACK_FILLING)
+set(trace_build -DTX_QUEUE_MESSAGE_MAX_SIZE=32 -DTX_ENABLE_EVENT_TRACE)
+
+add_compile_options(
+ -std=c99
+ -O0
+ -g3
+ -fdiagnostics-color
+ -Werror
+ -ffunction-sections
+ -fdata-sections
+ -DTX_REGRESSION_TEST
+ -DTEST_STACK_SIZE_PRINTF=4096
+ -DEXTERNAL_EXIT
+ ${${CMAKE_BUILD_TYPE}}
+)
+
+enable_testing()
+
+# Add ThreadX library (uses the RISC-V toolchain set by CMAKE_TOOLCHAIN_FILE)
+add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../../../.. threadx)
+
+# Add BSP
+add_subdirectory(bsp)
+
+# Add regression tests
+add_subdirectory(regression)
+
+# Strict warning flags for the ThreadX library build
+target_compile_options(
+ threadx
+ PRIVATE -Werror
+ -Wall
+ -Wextra
+ -pedantic
+ -fmessage-length=0
+ -fsigned-char
+ -ffunction-sections
+ -fdata-sections
+ -Wunused
+ -Wuninitialized
+ -Wmissing-declarations
+ -Wconversion
+ -Wpointer-arith
+ -Wlogical-op
+ -Waggregate-return
+ -Wfloat-equal
+ $<$<COMPILE_LANGUAGE:C>:-include$<SEMICOLON>stdlib.h>
+)
diff --git a/test/tx/cmake/riscv/bsp/CMakeLists.txt b/test/tx/cmake/riscv/bsp/CMakeLists.txt
new file mode 100644
index 00000000..a6b8171a
--- /dev/null
+++ b/test/tx/cmake/riscv/bsp/CMakeLists.txt
@@ -0,0 +1,25 @@
+cmake_minimum_required(VERSION 3.13 FATAL_ERROR)
+
+# BSP library for RISC-V QEMU virt regression tests.
+# Provides board init, UART, CLINT timer, PLIC, trap handler,
+# newlib syscall stubs, and low-level ThreadX initialization.
+
+project(riscv_bsp LANGUAGES C ASM)
+
+set(BSP_DIR ${CMAKE_CURRENT_LIST_DIR})
+
+add_library(riscv_bsp
+ ${BSP_DIR}/entry.S
+ ${BSP_DIR}/tx_initialize_low_level.S
+ ${BSP_DIR}/board.c
+ ${BSP_DIR}/trap.c
+ ${BSP_DIR}/hwtimer.c
+ ${BSP_DIR}/plic.c
+ ${BSP_DIR}/uart.c
+ ${BSP_DIR}/syscalls.c
+ ${BSP_DIR}/printf.c
+)
+
+target_include_directories(riscv_bsp PUBLIC ${BSP_DIR})
+target_link_libraries(riscv_bsp PUBLIC azrtos::threadx)
+target_compile_definitions(riscv_bsp PUBLIC EXTERNAL_EXIT)
diff --git a/test/tx/cmake/riscv/bsp/board.c b/test/tx/cmake/riscv/bsp/board.c
new file mode 100644
index 00000000..0891200e
--- /dev/null
+++ b/test/tx/cmake/riscv/bsp/board.c
@@ -0,0 +1,30 @@
+/***************************************************************************
+ * Copyright (c) 2026 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
+ **************************************************************************/
+
+#include "plic.h"
+#include "hwtimer.h"
+#include "uart.h"
+#include <stdint.h>
+#include <stddef.h>
+
+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/test/tx/cmake/riscv/bsp/csr.h b/test/tx/cmake/riscv/bsp/csr.h
new file mode 100644
index 00000000..f3d15f82
--- /dev/null
+++ b/test/tx/cmake/riscv/bsp/csr.h
@@ -0,0 +1,84 @@
+/***************************************************************************
+ * Copyright (c) 2026 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)
+#define MSTATUS_MPP_M (3L << 11)
+#define MSTATUS_MPP_S (1L << 11)
+#define MSTATUS_MPP_U (0L << 11)
+#define MSTATUS_MIE (1L << 3)
+#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)
+#define MIE_SSIE (1L << 1)
+#define MIE_SEIE (1L << 9)
+
+#ifndef __ASSEMBLER__
+
+#include <stdint.h>
+
+static inline uintptr_t riscv_get_core(void)
+{
+ uintptr_t x;
+ __asm__ volatile("csrr %0, mhartid" : "=r" (x));
+ return x;
+}
+
+static inline uintptr_t riscv_get_mstatus(void)
+{
+ uintptr_t x;
+ __asm__ volatile("csrr %0, mstatus" : "=r" (x));
+ return x;
+}
+
+static inline void riscv_writ_mstatus(uintptr_t x)
+{
+ __asm__ volatile("csrw mstatus, %0" : : "r" (x));
+}
+
+static inline void riscv_mintr_on(void)
+{
+ uintptr_t mstatus = riscv_get_mstatus();
+ mstatus |= MSTATUS_MIE;
+ riscv_writ_mstatus(mstatus);
+}
+
+static inline void riscv_mintr_off(void)
+{
+ uintptr_t mstatus = riscv_get_mstatus();
+ mstatus &= (~MSTATUS_MIE);
+ riscv_writ_mstatus(mstatus);
+}
+
+static inline int riscv_mintr_get(void)
+{
+ uintptr_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();
+}
+
+#endif /* __ASSEMBLER__ */
+
+#endif /* RISCV_CSR_H */
diff --git a/test/tx/cmake/riscv/bsp/entry.S b/test/tx/cmake/riscv/bsp/entry.S
new file mode 100644
index 00000000..2f16c052
--- /dev/null
+++ b/test/tx/cmake/riscv/bsp/entry.S
@@ -0,0 +1,75 @@
+/***************************************************************************
+ * Copyright (c) 2026 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
+ **************************************************************************/
+
+/* Boot code for RISC-V QEMU virt regression tests.
+ Works for both RV32 and RV64 (instructions are width-agnostic). */
+
+.section .text.init
+.align 4
+.global _start
+.extern main
+.extern _sysstack_end
+.extern _bss_start
+.extern _bss_end
+
+_start:
+ csrr t0, mhartid
+ bne t0, zero, _secondary_halt
+
+ /* Zero all general-purpose registers. */
+ 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
+
+ /* Set up stack pointer from linker symbol. */
+ la sp, _sysstack_end
+
+ /* Clear BSS. */
+ la t0, _bss_start
+ la t1, _bss_end
+_bss_clean:
+ bgeu t0, t1, _bss_done
+ sb zero, 0(t0)
+ addi t0, t0, 1
+ j _bss_clean
+_bss_done:
+ call main
+
+_secondary_halt:
+ wfi
+ j _secondary_halt
diff --git a/test/tx/cmake/riscv/bsp/hwtimer.c b/test/tx/cmake/riscv/bsp/hwtimer.c
new file mode 100644
index 00000000..99821f1e
--- /dev/null
+++ b/test/tx/cmake/riscv/bsp/hwtimer.c
@@ -0,0 +1,33 @@
+/***************************************************************************
+ * Copyright (c) 2026 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
+ **************************************************************************/
+
+#include "csr.h"
+#include "hwtimer.h"
+#include <tx_port.h>
+
+#define CLINT (0x02000000L)
+#define CLINT_TIME (CLINT + 0xBFF8)
+#define CLINT_TIMECMP(id) (CLINT + 0x4000 + 8 * (id))
+
+int hwtimer_init(void)
+{
+ uintptr_t hart = riscv_get_core();
+ uint64_t time = *((volatile uint64_t *)CLINT_TIME);
+ *((volatile uint64_t *)CLINT_TIMECMP(hart)) = time + TICKNUM_PER_TIMER;
+ return 0;
+}
+
+int hwtimer_handler(void)
+{
+ uintptr_t hart = riscv_get_core();
+ uint64_t time = *((volatile uint64_t *)CLINT_TIME);
+ *((volatile uint64_t *)CLINT_TIMECMP(hart)) = time + TICKNUM_PER_TIMER;
+ return 0;
+}
diff --git a/test/tx/cmake/riscv/bsp/hwtimer.h b/test/tx/cmake/riscv/bsp/hwtimer.h
new file mode 100644
index 00000000..e6b9efa4
--- /dev/null
+++ b/test/tx/cmake/riscv/bsp/hwtimer.h
@@ -0,0 +1,22 @@
+/***************************************************************************
+ * Copyright (c) 2026 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_HWTIMER_H
+#define RISCV_HWTIMER_H
+
+#include <stdint.h>
+
+#define TICKNUM_PER_SECOND 10000000
+#define TICKNUM_PER_TIMER (TICKNUM_PER_SECOND / 10)
+
+int hwtimer_init(void);
+int hwtimer_handler(void);
+
+#endif /* RISCV_HWTIMER_H */
diff --git a/test/tx/cmake/riscv/bsp/link.lds b/test/tx/cmake/riscv/bsp/link.lds
new file mode 100644
index 00000000..42f08fb6
--- /dev/null
+++ b/test/tx/cmake/riscv/bsp/link.lds
@@ -0,0 +1,60 @@
+/***************************************************************************
+ * Linker script for RISC-V QEMU virt regression tests.
+ * Provides 128 MB RAM starting at 0x80000000 (QEMU virt default).
+ * System stack is 16 KB to accommodate test framework overhead.
+ **************************************************************************/
+
+OUTPUT_ARCH( "riscv" )
+ENTRY( _start )
+
+MEMORY
+{
+ RAM (rwx) : ORIGIN = 0x80000000, LENGTH = 128M
+}
+
+SECTIONS
+{
+ . = 0x80000000;
+
+ .text : {
+ KEEP(*(.text.init))
+ *(.text .text.*)
+ . = ALIGN(0x1000);
+ PROVIDE(etext = .);
+ } > RAM
+
+ .rodata : {
+ . = ALIGN(16);
+ *(.srodata .srodata.*)
+ . = ALIGN(16);
+ *(.rodata .rodata.*)
+ } > RAM
+
+ .data : {
+ . = ALIGN(16);
+ *(.sdata .sdata.*)
+ . = ALIGN(16);
+ *(.data .data.*)
+ } > RAM
+
+ .bss : {
+ . = ALIGN(16);
+ _bss_start = .;
+ *(.sbss .sbss.*)
+ . = ALIGN(16);
+ *(.bss .bss.*)
+ *(COMMON)
+ _bss_end = .;
+ } > RAM
+
+ .stack : {
+ . = ALIGN(4096);
+ _sysstack_start = .;
+ . += 0x4000;
+ _sysstack_end = .;
+ } > RAM
+
+ PROVIDE(_end = .);
+
+ ASSERT(. <= ORIGIN(RAM) + LENGTH(RAM), "Image exceeds available RAM")
+}
diff --git a/test/tx/cmake/riscv/bsp/plic.c b/test/tx/cmake/riscv/bsp/plic.c
new file mode 100644
index 00000000..a2b44fec
--- /dev/null
+++ b/test/tx/cmake/riscv/bsp/plic.c
@@ -0,0 +1,87 @@
+/***************************************************************************
+ * Copyright (c) 2026 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
+ **************************************************************************/
+
+#include "plic.h"
+#include <stddef.h>
+
+irq_callback callbacks[MAX_CALLBACK_NUM];
+
+void plic_irq_enable(int irqno)
+{
+ uintptr_t hart = riscv_get_core();
+ volatile uint32_t *reg = (volatile uint32_t *)(PLIC_MENABLE(hart) + (irqno / 32) * 4);
+ *reg = *reg | (1U << (irqno % 32));
+}
+
+void plic_irq_disable(int irqno)
+{
+ uintptr_t hart = riscv_get_core();
+ volatile uint32_t *reg = (volatile uint32_t *)(PLIC_MENABLE(hart) + (irqno / 32) * 4);
+ *reg = *reg & ~(1U << (irqno % 32));
+}
+
+void plic_prio_set(int irqno, int prio)
+{
+ PLIC_SET_PRIO(irqno, prio);
+}
+
+int plic_prio_get(int irqno)
+{
+ return (int)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)
+{
+ uintptr_t hart = riscv_get_core();
+ return (int)(*(uint32_t *)PLIC_MCLAIM(hart));
+}
+
+void plic_complete(int irqno)
+{
+ uintptr_t hart = riscv_get_core();
+ *(uint32_t *)(PLIC_MCOMPLETE(hart)) = (uint32_t)irqno;
+}
+
+int plic_irq_intr(void)
+{
+ int ret = -1;
+ int irqno = plic_claim();
+ if (irqno <= 0 || irqno >= MAX_CALLBACK_NUM) {
+ plic_complete(irqno);
+ return 0; // spurious or out-of-range, not an error
+ }
+ if (callbacks[irqno] != NULL)
+ ret = (callbacks[irqno])(irqno);
+
+ plic_complete(irqno);
+ return ret;
+}
diff --git a/test/tx/cmake/riscv/bsp/plic.h b/test/tx/cmake/riscv/bsp/plic.h
new file mode 100644
index 00000000..e2734ea6
--- /dev/null
+++ b/test/tx/cmake/riscv/bsp/plic.h
@@ -0,0 +1,46 @@
+/***************************************************************************
+ * Copyright (c) 2026 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 /* RISCV_PLIC_H */
diff --git a/test/tx/cmake/riscv/bsp/printf.c b/test/tx/cmake/riscv/bsp/printf.c
new file mode 100644
index 00000000..4aa185cc
--- /dev/null
+++ b/test/tx/cmake/riscv/bsp/printf.c
@@ -0,0 +1,168 @@
+/***************************************************************************
+ * Copyright (c) 2026 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
+ **************************************************************************/
+
+/* Minimal printf/puts for RISC-V QEMU virt regression tests.
+ Avoids linking newlib's stdio which is compiled with mcmodel=medlow
+ and causes relocation overflow on RV64 (DRAM at 0x80000000).
+ Only the format specifiers used by the test suite are supported:
+ %s, %d, %u, %lu, %ld, %%, and plain strings. */
+
+#include <stdarg.h>
+#include <stddef.h>
+
+extern int _write(int fd, const char *buf, int count);
+
+static void print_str(const char *s)
+{
+ const char *p = s;
+ while (*p)
+ p++;
+ _write(1, s, (int)(p - s));
+}
+
+static void print_unsigned(unsigned long val)
+{
+ char buf[21];
+ int i = 0;
+
+ if (val == 0)
+ {
+ _write(1, "0", 1);
+ return;
+ }
+
+ while (val > 0)
+ {
+ buf[i++] = (char)('0' + (int)(val % 10u));
+ val /= 10u;
+ }
+
+ /* Reverse. */
+ for (int j = i - 1; j >= 0; j--)
+ _write(1, &buf[j], 1);
+}
+
+static void print_hex(unsigned long val)
+{
+ static const char hex[] = "0123456789abcdef";
+ char buf[17];
+ int i = 0;
+
+ if (val == 0)
+ {
+ _write(1, "0", 1);
+ return;
+ }
+
+ while (val > 0)
+ {
+ buf[i++] = hex[val & 0xfu];
+ val >>= 4;
+ }
+
+ for (int j = i - 1; j >= 0; j--)
+ _write(1, &buf[j], 1);
+}
+
+static void print_signed(long val)
+{
+ if (val < 0)
+ {
+ _write(1, "-", 1);
+ print_unsigned((unsigned long)(-val));
+ }
+ else
+ {
+ print_unsigned((unsigned long)val);
+ }
+}
+
+int printf(const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+
+ const char *p = fmt;
+ const char *seg = p;
+
+ while (*p)
+ {
+ if (*p == '%')
+ {
+ /* Flush preceding literal segment. */
+ if (p > seg)
+ _write(1, seg, (int)(p - seg));
+
+ p++;
+ /* Check for 'l' length modifier. */
+ int is_long = 0;
+ if (*p == 'l')
+ {
+ is_long = 1;
+ p++;
+ }
+
+ switch (*p)
+ {
+ case 's':
+ print_str(va_arg(ap, const char *));
+ break;
+ case 'd':
+ if (is_long)
+ print_signed(va_arg(ap, long));
+ else
+ print_signed((long)va_arg(ap, int));
+ break;
+ case 'u':
+ if (is_long)
+ print_unsigned(va_arg(ap, unsigned long));
+ else
+ print_unsigned((unsigned long)va_arg(ap, unsigned int));
+ break;
+ case 'x':
+ if (is_long)
+ print_hex(va_arg(ap, unsigned long));
+ else
+ print_hex((unsigned long)va_arg(ap, unsigned int));
+ break;
+ case '%':
+ _write(1, "%", 1);
+ break;
+ default:
+ /* Unknown specifier — print as-is. */
+ _write(1, "%", 1);
+ if (is_long)
+ _write(1, "l", 1);
+ _write(1, p, 1);
+ break;
+ }
+ p++;
+ seg = p;
+ }
+ else
+ {
+ p++;
+ }
+ }
+
+ /* Flush remaining literal segment. */
+ if (p > seg)
+ _write(1, seg, (int)(p - seg));
+
+ va_end(ap);
+ return 0;
+}
+
+int puts(const char *s)
+{
+ print_str(s);
+ _write(1, "\n", 1);
+ return 0;
+}
diff --git a/test/tx/cmake/riscv/bsp/syscalls.c b/test/tx/cmake/riscv/bsp/syscalls.c
new file mode 100644
index 00000000..802fe8af
--- /dev/null
+++ b/test/tx/cmake/riscv/bsp/syscalls.c
@@ -0,0 +1,128 @@
+/***************************************************************************
+ * Copyright (c) 2026 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
+ **************************************************************************/
+
+/* Newlib system call stubs for RISC-V QEMU virt regression tests.
+ Provides _write (UART output for printf), _exit (QEMU test finisher),
+ and other minimal stubs needed by newlib. */
+
+#include <sys/stat.h>
+#include <errno.h>
+#include <stdint.h>
+
+#define UART0_THR (*(volatile unsigned char *)0x10000000L)
+#define UART0_LSR (*(volatile unsigned char *)0x10000005L)
+#define LSR_TX_IDLE (1 << 5)
+
+/* QEMU virt sifive_test device for clean exit. */
+#define VIRT_TEST (*(volatile uint32_t *)0x100000L)
+#define VIRT_TEST_PASS 0x5555u
+#define VIRT_TEST_FAIL 0x3333u
+
+/* Called by testcontrol.c when EXTERNAL_EXIT is defined. */
+__attribute__((noreturn)) void external_exit(unsigned int code)
+{
+ if (code == 0)
+ VIRT_TEST = VIRT_TEST_PASS;
+ else
+ VIRT_TEST = (code << 16) | VIRT_TEST_FAIL;
+
+ /* Should not reach here, but halt if it does. */
+ while (1)
+ ;
+}
+
+int _write(int fd, const char *buf, int count)
+{
+ (void)fd;
+ for (int i = 0; i < count; i++)
+ {
+ while ((UART0_LSR & LSR_TX_IDLE) == 0)
+ ;
+ UART0_THR = (unsigned char)buf[i];
+ }
+ return count;
+}
+
+extern char _end[];
+static char *heap_ptr = 0;
+
+void *_sbrk(int incr)
+{
+ if (heap_ptr == 0)
+ heap_ptr = _end;
+
+ char *prev = heap_ptr;
+ heap_ptr += incr;
+ return prev;
+}
+
+int _close(int fd)
+{
+ (void)fd;
+ errno = EBADF;
+ return -1;
+}
+
+int _fstat(int fd, struct stat *st)
+{
+ (void)fd;
+ st->st_mode = S_IFCHR;
+ return 0;
+}
+
+int _isatty(int fd)
+{
+ (void)fd;
+ return 1;
+}
+
+int _lseek(int fd, int offset, int whence)
+{
+ (void)fd;
+ (void)offset;
+ (void)whence;
+ errno = ESPIPE;
+ return -1;
+}
+
+int _read(int fd, char *buf, int count)
+{
+ (void)fd;
+ (void)buf;
+ (void)count;
+ return 0;
+}
+
+__attribute__((noreturn)) void _exit(int code)
+{
+ external_exit((unsigned int)code);
+}
+
+/* Override newlib's exit() to avoid pulling in __call_exitprocs and
+ __stdio_exit_handler (compiled with mcmodel=medlow). */
+__attribute__((noreturn)) void exit(int code)
+{
+ external_exit((unsigned int)code);
+}
+
+/* Minimal rand/srand to avoid pulling newlib's version which uses
+ _impure_ptr (compiled with mcmodel=medlow, unreachable on RV64). */
+static unsigned long rand_seed = 1;
+
+int rand(void)
+{
+ rand_seed = rand_seed * 1103515245UL + 12345UL;
+ return (int)((rand_seed >> 16) & 0x7fff);
+}
+
+void srand(unsigned int seed)
+{
+ rand_seed = seed;
+}
diff --git a/test/tx/cmake/riscv/bsp/trap.c b/test/tx/cmake/riscv/bsp/trap.c
new file mode 100644
index 00000000..0ac7990a
--- /dev/null
+++ b/test/tx/cmake/riscv/bsp/trap.c
@@ -0,0 +1,84 @@
+/***************************************************************************
+ * Copyright (c) 2026 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
+ **************************************************************************/
+
+/* Trap handler for RISC-V QEMU virt regression tests.
+ Calls test_interrupt_dispatch() BEFORE _tx_timer_interrupt() to match
+ the Linux port's behavior (generate_test_file.sh injection order). */
+
+#include "csr.h"
+#include <stdint.h>
+#include <stdio.h>
+#include "uart.h"
+#include "hwtimer.h"
+#include "plic.h"
+#include <tx_port.h>
+#include <tx_api.h>
+#include <tx_trace.h>
+
+/* Interrupt bit is the MSB of mcause */
+#define MCAUSE_INT_BIT ((uintptr_t)1 << (sizeof(uintptr_t) * 8 - 1))
+
+#define OS_IS_INTERUPT(mcause) ((mcause) & MCAUSE_INT_BIT)
+#define OS_IS_TICK_INT(mcause) ((mcause) == (MCAUSE_INT_BIT | 7u))
+#define OS_IS_SOFT_INT(mcause) ((mcause) == (MCAUSE_INT_BIT | 3u))
+#define OS_IS_EXT_INT(mcause) ((mcause) == (MCAUSE_INT_BIT | 11u))
+
+extern void _tx_timer_interrupt(void);
+extern void test_interrupt_dispatch(void) __attribute__((weak));
+extern void _exit(int code) __attribute__((noreturn));
+
+void trap_handler(uintptr_t mcause, uintptr_t mepc, uintptr_t mtval)
+{
+ (void)mepc;
+ (void)mtval;
+
+ if (OS_IS_INTERUPT(mcause))
+ {
+ if (OS_IS_TICK_INT(mcause))
+ {
+ hwtimer_handler();
+
+ /* Trace ISR enter event (matches Linux port behavior). */
+ _tx_trace_isr_enter_insert(0);
+
+ /* Call test ISR dispatch BEFORE timer interrupt (matches Linux). */
+ if (test_interrupt_dispatch)
+ test_interrupt_dispatch();
+
+ _tx_timer_interrupt();
+
+ /* Trace ISR exit event. */
+ _tx_trace_isr_exit_insert(0);
+ }
+ else if (OS_IS_EXT_INT(mcause))
+ {
+ int ret = plic_irq_intr();
+ if (ret)
+ {
+ uart_puts("[INTERRUPT]: handler irq error!");
+ while (1)
+ ;
+ }
+ }
+ else
+ {
+ uart_puts("[INTERRUPT]: unhandled interrupt!");
+ while (1)
+ ;
+ }
+ }
+ else
+ {
+ uart_puts("[EXCEPTION]: unhandled exception!");
+ printf(" mcause=0x%lx mepc=0x%lx mtval=0x%lx\n",
+ (unsigned long)mcause, (unsigned long)mepc, (unsigned long)mtval);
+ _exit(1);
+ }
+}
diff --git a/test/tx/cmake/riscv/bsp/tx_initialize_low_level.S b/test/tx/cmake/riscv/bsp/tx_initialize_low_level.S
new file mode 100644
index 00000000..8d474d66
--- /dev/null
+++ b/test/tx/cmake/riscv/bsp/tx_initialize_low_level.S
@@ -0,0 +1,100 @@
+/***************************************************************************
+ * Copyright (c) 2026 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
+ **************************************************************************/
+
+/* Trap entry and low-level init for RISC-V QEMU virt regression tests.
+ Supports both RV32 and RV64 via __riscv_xlen conditionals. */
+
+#include "csr.h"
+
+#if __riscv_xlen == 64
+#define STORE sd
+#define LOAD ld
+#define REGBYTES 8
+#else
+#define STORE sw
+#define LOAD lw
+#define REGBYTES 4
+#endif
+
+ .section .text
+ .align 4
+
+/**************************************************************************/
+/* trap_entry — saves context, calls C trap_handler, restores context. */
+/**************************************************************************/
+ .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 * REGBYTES)
+#else
+ addi sp, sp, -(32 * REGBYTES)
+#endif
+
+ STORE x1, (28 * REGBYTES)(sp)
+
+ call _tx_thread_context_save
+
+ csrr a0, mcause
+ csrr a1, mepc
+ csrr a2, mtval
+ addi sp, sp, -REGBYTES
+ STORE ra, 0(sp)
+ call trap_handler
+ LOAD ra, 0(sp)
+ addi sp, sp, REGBYTES
+
+ call _tx_thread_context_restore
+
+_trap_err:
+ wfi
+ j _trap_err
+
+/**************************************************************************/
+/* _tx_initialize_low_level — hardware init for QEMU virt. */
+/**************************************************************************/
+ .section .text
+ .global _tx_initialize_low_level
+ .extern _end
+ .extern board_init
+
+_tx_initialize_low_level:
+
+ la t0, _tx_thread_system_stack_ptr
+ STORE sp, 0(t0)
+
+ la t0, _end
+ la t1, _tx_initialize_unused_memory
+ STORE t0, 0(t1)
+
+ li t0, MSTATUS_MIE
+ csrrc zero, mstatus, t0
+ li t0, (MSTATUS_MPP_M | MSTATUS_MPIE)
+ csrrs zero, mstatus, t0
+ li t0, (MIE_MTIE | MIE_MSIE | MIE_MEIE)
+ csrrs zero, mie, t0
+
+#ifdef __riscv_flen
+ li t0, MSTATUS_FS
+ csrrs zero, mstatus, t0
+ fscsr x0
+#endif
+
+ addi sp, sp, -REGBYTES
+ STORE ra, 0(sp)
+ call board_init
+ LOAD ra, 0(sp)
+ addi sp, sp, REGBYTES
+
+ la t0, trap_entry
+ csrw mtvec, t0
+ ret
diff --git a/test/tx/cmake/riscv/bsp/uart.c b/test/tx/cmake/riscv/bsp/uart.c
new file mode 100644
index 00000000..c1a6b64b
--- /dev/null
+++ b/test/tx/cmake/riscv/bsp/uart.c
@@ -0,0 +1,79 @@
+/***************************************************************************
+ * Copyright (c) 2026 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
+ **************************************************************************/
+
+#include "uart.h"
+#include "csr.h"
+#include "plic.h"
+#include <stdint.h>
+
+#define Reg(reg) ((volatile unsigned char *)(UART0 + (reg)))
+
+#define RHR 0
+#define THR 0
+#define IER 1
+#define IER_RX_ENABLE (1<<0)
+#define IER_TX_ENABLE (1<<1)
+#define FCR 2
+#define FCR_FIFO_ENABLE (1<<0)
+#define FCR_FIFO_CLEAR (3<<1)
+#define LCR 3
+#define LCR_EIGHT_BITS (3<<0)
+#define LCR_BAUD_LATCH (1<<7)
+#define LSR 5
+#define LSR_RX_READY (1<<0)
+#define LSR_TX_IDLE (1<<5)
+
+#define ReadReg(reg) (*(Reg(reg)))
+#define WriteReg(reg, v) (*(Reg(reg)) = (v))
+
+int uart_init(void)
+{
+ WriteReg(IER, 0x00);
+ WriteReg(LCR, LCR_BAUD_LATCH);
+ WriteReg(0, 0x03);
+ WriteReg(1, 0x00);
+ WriteReg(LCR, LCR_EIGHT_BITS);
+ WriteReg(FCR, FCR_FIFO_ENABLE | FCR_FIFO_CLEAR);
+
+ plic_irq_enable(UART0_IRQ);
+ plic_prio_set(UART0_IRQ, 1);
+
+ return 0;
+}
+
+void uart_putc_nolock(int ch)
+{
+ while ((ReadReg(LSR) & LSR_TX_IDLE) == 0)
+ ;
+ WriteReg(THR, ch);
+}
+
+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;
+}
diff --git a/test/tx/cmake/riscv/bsp/uart.h b/test/tx/cmake/riscv/bsp/uart.h
new file mode 100644
index 00000000..765ec38f
--- /dev/null
+++ b/test/tx/cmake/riscv/bsp/uart.h
@@ -0,0 +1,22 @@
+/***************************************************************************
+ * Copyright (c) 2026 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_UART_H
+#define RISCV_UART_H
+
+#define UART0 0x10000000L
+#define UART0_IRQ 10
+
+int uart_init(void);
+int uart_putc(int ch);
+void uart_putc_nolock(int ch);
+int uart_puts(const char *str);
+
+#endif /* RISCV_UART_H */
diff --git a/test/tx/cmake/riscv/regression/CMakeLists.txt b/test/tx/cmake/riscv/regression/CMakeLists.txt
new file mode 100644
index 00000000..5e381b89
--- /dev/null
+++ b/test/tx/cmake/riscv/regression/CMakeLists.txt
@@ -0,0 +1,165 @@
+cmake_minimum_required(VERSION 3.13 FATAL_ERROR)
+cmake_policy(SET CMP0057 NEW)
+
+project(riscv_regression_test LANGUAGES C ASM)
+
+set(SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/../../../regression)
+set(BSP_DIR ${CMAKE_CURRENT_LIST_DIR}/../bsp)
+
+# Determine QEMU arch from THREADX_ARCH
+if(THREADX_ARCH STREQUAL "risc-v64")
+ set(QEMU_SYSTEM "qemu-system-riscv64")
+ set(XLEN_TAG "rv64")
+else()
+ set(QEMU_SYSTEM "qemu-system-riscv32")
+ set(XLEN_TAG "rv32")
+endif()
+
+set(regression_test_cases
+ ${SOURCE_DIR}/threadx_block_memory_basic_test.c
+ ${SOURCE_DIR}/threadx_block_memory_error_detection_test.c
+ ${SOURCE_DIR}/threadx_block_memory_information_test.c
+ ${SOURCE_DIR}/threadx_block_memory_prioritize_test.c
+ ${SOURCE_DIR}/threadx_block_memory_suspension_test.c
+ ${SOURCE_DIR}/threadx_block_memory_suspension_timeout_test.c
+ ${SOURCE_DIR}/threadx_block_memory_thread_terminate_test.c
+ ${SOURCE_DIR}/threadx_byte_memory_basic_test.c
+ ${SOURCE_DIR}/threadx_byte_memory_information_test.c
+ ${SOURCE_DIR}/threadx_byte_memory_prioritize_test.c
+ ${SOURCE_DIR}/threadx_byte_memory_suspension_test.c
+ ${SOURCE_DIR}/threadx_byte_memory_suspension_timeout_test.c
+ ${SOURCE_DIR}/threadx_byte_memory_thread_contention_test.c
+ ${SOURCE_DIR}/threadx_byte_memory_thread_terminate_test.c
+ ${SOURCE_DIR}/threadx_event_flag_basic_test.c
+ ${SOURCE_DIR}/threadx_event_flag_information_test.c
+ ${SOURCE_DIR}/threadx_event_flag_isr_set_clear_test.c
+ ${SOURCE_DIR}/threadx_event_flag_isr_wait_abort_test.c
+ ${SOURCE_DIR}/threadx_event_flag_single_thread_terminate_test.c
+ ${SOURCE_DIR}/threadx_event_flag_suspension_consume_test.c
+ ${SOURCE_DIR}/threadx_event_flag_suspension_different_bits_consume_test.c
+ ${SOURCE_DIR}/threadx_event_flag_suspension_different_bits_test.c
+ ${SOURCE_DIR}/threadx_event_flag_suspension_test.c
+ ${SOURCE_DIR}/threadx_event_flag_suspension_timeout_test.c
+ ${SOURCE_DIR}/threadx_event_flag_thread_terminate_test.c
+ ${SOURCE_DIR}/threadx_interrupt_control_test.c
+ ${SOURCE_DIR}/threadx_mutex_basic_test.c
+ ${SOURCE_DIR}/threadx_mutex_delete_test.c
+ ${SOURCE_DIR}/threadx_mutex_information_test.c
+ ${SOURCE_DIR}/threadx_mutex_nested_priority_inheritance_test.c
+ ${SOURCE_DIR}/threadx_mutex_no_preemption_test.c
+ ${SOURCE_DIR}/threadx_mutex_preemption_test.c
+ ${SOURCE_DIR}/threadx_mutex_priority_inheritance_test.c
+ ${SOURCE_DIR}/threadx_mutex_proritize_test.c
+ ${SOURCE_DIR}/threadx_mutex_suspension_timeout_test.c
+ ${SOURCE_DIR}/threadx_mutex_thread_terminate_test.c
+ ${SOURCE_DIR}/threadx_queue_basic_eight_word_test.c
+ ${SOURCE_DIR}/threadx_queue_basic_four_word_test.c
+ ${SOURCE_DIR}/threadx_queue_basic_one_word_test.c
+ ${SOURCE_DIR}/threadx_queue_basic_sixteen_word_test.c
+ ${SOURCE_DIR}/threadx_queue_basic_two_word_test.c
+ ${SOURCE_DIR}/threadx_queue_basic_max_message_size_test.c
+ ${SOURCE_DIR}/threadx_queue_empty_suspension_test.c
+ ${SOURCE_DIR}/threadx_queue_flush_no_suspension_test.c
+ ${SOURCE_DIR}/threadx_queue_flush_test.c
+ ${SOURCE_DIR}/threadx_queue_front_send_test.c
+ ${SOURCE_DIR}/threadx_queue_full_suspension_test.c
+ ${SOURCE_DIR}/threadx_queue_information_test.c
+ ${SOURCE_DIR}/threadx_queue_prioritize.c
+ ${SOURCE_DIR}/threadx_queue_suspension_timeout_test.c
+ ${SOURCE_DIR}/threadx_queue_thread_terminate_test.c
+ ${SOURCE_DIR}/threadx_semaphore_basic_test.c
+ ${SOURCE_DIR}/threadx_semaphore_ceiling_put_test.c
+ ${SOURCE_DIR}/threadx_semaphore_delete_test.c
+ ${SOURCE_DIR}/threadx_semaphore_information_test.c
+ ${SOURCE_DIR}/threadx_semaphore_non_preemption_test.c
+ ${SOURCE_DIR}/threadx_semaphore_preemption_test.c
+ ${SOURCE_DIR}/threadx_semaphore_prioritize.c
+ ${SOURCE_DIR}/threadx_semaphore_thread_terminate_test.c
+ ${SOURCE_DIR}/threadx_semaphore_timeout_test.c
+ ${SOURCE_DIR}/threadx_thread_basic_execution_test.c
+ ${SOURCE_DIR}/threadx_thread_basic_time_slice_test.c
+ ${SOURCE_DIR}/threadx_thread_completed_test.c
+ ${SOURCE_DIR}/threadx_thread_create_preemption_threshold_test.c
+ ${SOURCE_DIR}/threadx_thread_delayed_suspension_test.c
+ ${SOURCE_DIR}/threadx_thread_information_test.c
+ ${SOURCE_DIR}/threadx_thread_multi_level_preemption_threshold_test.c
+ ${SOURCE_DIR}/threadx_thread_multiple_non_current_test.c
+ ${SOURCE_DIR}/threadx_thread_multiple_sleep_test.c
+ ${SOURCE_DIR}/threadx_thread_multiple_suspension_test.c
+ ${SOURCE_DIR}/threadx_thread_multiple_time_slice_test.c
+ ${SOURCE_DIR}/threadx_thread_preemptable_suspension_test.c
+ ${SOURCE_DIR}/threadx_thread_preemption_change_test.c
+ ${SOURCE_DIR}/threadx_thread_priority_change.c
+ ${SOURCE_DIR}/threadx_thread_relinquish_test.c
+ ${SOURCE_DIR}/threadx_thread_reset_test.c
+ ${SOURCE_DIR}/threadx_thread_simple_sleep_non_clear_test.c
+ ${SOURCE_DIR}/threadx_thread_simple_sleep_test.c
+ ${SOURCE_DIR}/threadx_thread_simple_suspend_test.c
+ ${SOURCE_DIR}/threadx_thread_sleep_for_100ticks_test.c
+ ${SOURCE_DIR}/threadx_thread_sleep_terminate_test.c
+ ${SOURCE_DIR}/threadx_thread_stack_checking_test.c
+ ${SOURCE_DIR}/threadx_thread_terminate_delete_test.c
+ ${SOURCE_DIR}/threadx_thread_time_slice_change_test.c
+ ${SOURCE_DIR}/threadx_thread_wait_abort_and_isr_test.c
+ ${SOURCE_DIR}/threadx_thread_wait_abort_test.c
+ ${SOURCE_DIR}/threadx_time_get_set_test.c
+ ${SOURCE_DIR}/threadx_timer_activate_deactivate_test.c
+ ${SOURCE_DIR}/threadx_timer_deactivate_accuracy_test.c
+ ${SOURCE_DIR}/threadx_timer_information_test.c
+ ${SOURCE_DIR}/threadx_timer_large_timer_accuracy_test.c
+ ${SOURCE_DIR}/threadx_timer_multiple_accuracy_test.c
+ ${SOURCE_DIR}/threadx_timer_multiple_test.c
+ ${SOURCE_DIR}/threadx_timer_simple_test.c
+ # threadx_trace_basic_test.c excluded: references tx_saved_posture
+ # (the Linux port's interrupt save variable) which is named
+ # interrupt_save on RISC-V.
+)
+
+# Tests that provide their own main() and must NOT link with testcontrol.
+set(standalone_test_cases
+ ${SOURCE_DIR}/threadx_initialize_kernel_setup_test.c
+)
+
+# Build testcontrol.c as a static library (no patched tx_initialize_low_level
+# needed — the BSP provides it directly with test_interrupt_dispatch hook).
+add_library(riscv_test_utility ${SOURCE_DIR}/testcontrol.c)
+target_link_libraries(riscv_test_utility PUBLIC riscv_bsp azrtos::threadx)
+target_compile_definitions(riscv_test_utility PUBLIC
+ CTEST BATCH_TEST EXTERNAL_EXIT TEST_STACK_SIZE_PRINTF=4096)
+# Bare-metal newlib needs explicit stdlib.h for rand() used in some tests.
+target_compile_options(riscv_test_utility PUBLIC -include stdlib.h)
+
+# Helper function to register a test executable with QEMU + CTest.
+function(register_riscv_test test_name)
+ target_link_options(${test_name} PRIVATE
+ -T ${BSP_DIR}/link.lds -nostartfiles
+ -Wl,--gc-sections)
+ add_test(
+ NAME ${CMAKE_BUILD_TYPE}::${XLEN_TAG}::${test_name}
+ COMMAND ${QEMU_SYSTEM} -nographic -smp 1 -bios none -m 128M
+ -machine virt -kernel $<TARGET_FILE:${test_name}>
+ )
+ set_tests_properties(${CMAKE_BUILD_TYPE}::${XLEN_TAG}::${test_name}
+ PROPERTIES TIMEOUT 120)
+endfunction()
+
+# Regular tests (linked with testcontrol).
+foreach(test_case ${regression_test_cases})
+ get_filename_component(test_name ${test_case} NAME_WE)
+ add_executable(${test_name} ${test_case})
+ target_link_libraries(${test_name} PRIVATE
+ -Wl,--whole-archive riscv_bsp -Wl,--no-whole-archive
+ riscv_test_utility)
+ register_riscv_test(${test_name})
+endforeach()
+
+# Standalone tests (provide their own main, no testcontrol).
+foreach(test_case ${standalone_test_cases})
+ get_filename_component(test_name ${test_case} NAME_WE)
+ add_executable(${test_name} ${test_case})
+ target_link_libraries(${test_name} PRIVATE
+ -Wl,--whole-archive riscv_bsp -Wl,--no-whole-archive
+ azrtos::threadx)
+ target_compile_options(${test_name} PRIVATE -include stdlib.h)
+ register_riscv_test(${test_name})
+endforeach()
diff --git a/test/tx/cmake/riscv/run.sh b/test/tx/cmake/riscv/run.sh
new file mode 100755
index 00000000..890efa45
--- /dev/null
+++ b/test/tx/cmake/riscv/run.sh
@@ -0,0 +1,151 @@
+#!/bin/bash
+
+set -e
+
+# RISC-V regression test build/test runner.
+# Usage: run.sh <arch> <build|test> [all|<config> ...]
+# arch: riscv32 or riscv64
+
+function help() {
+ echo "Usage: $0 <arch> <command> [all|<build_configuration> ...]"
+ echo ""
+ echo " arch: riscv32 | riscv64"
+ echo " command: build | test"
+ echo ""
+ echo "Available build configurations:"
+ for build in ${build_configurations[*]}; do
+ echo " $build"
+ done
+ exit 1
+}
+
+function validate() {
+ for build in ${build_configurations[*]}; do
+ if [ "$1" == "$build" ]; then
+ return
+ fi
+ done
+ help
+}
+
+function generate() {
+ local arch=$1
+ local build=$2
+ local toolchain_file
+ local build_dir
+
+ if [ "$arch" == "riscv32" ]; then
+ toolchain_file="${REPO_ROOT}/cmake/riscv32_gnu.cmake"
+ else
+ toolchain_file="${REPO_ROOT}/cmake/riscv64_gnu.cmake"
+ fi
+
+ build_dir="build/${arch}_${build}"
+ cmake -B"$build_dir" -GNinja \
+ -DCMAKE_TOOLCHAIN_FILE="$toolchain_file" \
+ -DCMAKE_BUILD_TYPE="$build" \
+ .
+}
+
+function build() {
+ local arch=$1
+ local build=$2
+ cmake --build "build/${arch}_${build}"
+}
+
+function run_test() {
+ local arch=$1
+ local build=$2
+ local parallel_jobs=$3
+
+ pushd "build/${arch}_${build}"
+ [ -z "${CTEST_PARALLEL_LEVEL}" ] && parallel="-j${parallel_jobs}"
+ if [ -z "${CTEST_REPEAT_FAIL}" ]; then
+ repeat_fail=2
+ else
+ repeat_fail=${CTEST_REPEAT_FAIL}
+ fi
+ ctest $parallel --timeout 1000 \
+ -O "${arch}_${build}.txt" \
+ -T test --no-compress-output \
+ --test-output-size-passed 4194304 \
+ --test-output-size-failed 4194304 \
+ --output-on-failure \
+ --repeat until-pass:${repeat_fail} \
+ --output-junit "${arch}_${build}.xml"
+ popd
+}
+
+# Determine repo root and script directory
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+REPO_ROOT="$(cd "${SCRIPT_DIR}/../../../.." && pwd)"
+cd "$SCRIPT_DIR"
+
+# Parse build configurations from CMakeLists.txt
+result=$(sed -n "/(BUILD_CONFIGURATIONS/,/)/p" CMakeLists.txt | sed ':label;N;s/\n/ /;b label' | grep -Pzo "[a-zA-Z0-9_]*build[a-zA-Z0-9_]*\s*" | tr -d '\0')
+IFS=' '
+read -ra build_configurations <<< "$result"
+
+if [ $# -lt 2 ]; then
+ help
+fi
+
+arch=$1
+shift
+
+if [ "$arch" != "riscv32" ] && [ "$arch" != "riscv64" ]; then
+ echo "Error: arch must be 'riscv32' or 'riscv64'"
+ help
+fi
+
+command=$1
+shift
+
+if [ "$#" == "0" ]; then
+ builds=${build_configurations[0]}
+elif [ "$*" == "all" ]; then
+ builds=${build_configurations[@]}
+else
+ for item in $*; do
+ validate "$item"
+ done
+ builds=$*
+fi
+
+if [ "$command" == "build" ]; then
+ for item in $builds; do
+ generate "$arch" "$item"
+ echo ""
+ done
+
+ for item in $builds; do
+ echo "Building ${arch} ${item}"
+ build "$arch" "$item"
+ echo ""
+ done
+elif [ "$command" == "test" ]; then
+ cores=$(nproc)
+ if [ -z "${CTEST_PARALLEL_LEVEL}" ]; then
+ build_counts=$(echo $builds | wc -w)
+ parallel_jobs=$(($cores / $build_counts))
+ parallel_jobs=$(($parallel_jobs + 2))
+ pids=""
+ for item in $builds; do
+ echo "Testing ${arch} ${item}"
+ run_test "$arch" "$item" "$parallel_jobs" &
+ pids+=" $!"
+ done
+ exit_code=0
+ for p in $pids; do
+ wait $p || exit_code=$?
+ done
+ exit $exit_code
+ else
+ for item in $builds; do
+ echo "Testing ${arch} ${item}"
+ run_test "$arch" "$item" "$parallel_jobs"
+ done
+ fi
+else
+ help
+fi
diff --git a/test/tx/regression/threadx_block_memory_basic_test.c b/test/tx/regression/threadx_block_memory_basic_test.c
index 12e301d1..b2dccbff 100644
--- a/test/tx/regression/threadx_block_memory_basic_test.c
+++ b/test/tx/regression/threadx_block_memory_basic_test.c
@@ -217,8 +217,8 @@ CHAR *pointer;
}
/* Create block pools 0 and 1. */
- status = tx_block_pool_create(&pool_0, "pool 0", 100, pointer, 320);
- pointer = pointer + 320;
+ status = tx_block_pool_create(&pool_0, "pool 0", 100, pointer, 340);
+ pointer = pointer + 340;
/* Check status. */
if (status != TX_SUCCESS)
@@ -228,8 +228,8 @@ CHAR *pointer;
test_control_return(1);
}
- status = tx_block_pool_create(&pool_1, "pool 1", 100, pointer, 320);
- pointer = pointer + 320;
+ status = tx_block_pool_create(&pool_1, "pool 1", 100, pointer, 340);
+ pointer = pointer + 340;
/* Check status. */
if (status != TX_SUCCESS)
diff --git a/test/tx/regression/threadx_block_memory_error_detection_test.c b/test/tx/regression/threadx_block_memory_error_detection_test.c
index 7b7f2f61..e06c4d17 100644
--- a/test/tx/regression/threadx_block_memory_error_detection_test.c
+++ b/test/tx/regression/threadx_block_memory_error_detection_test.c
@@ -49,8 +49,8 @@ INT status;
pointer = pointer + TEST_STACK_SIZE_PRINTF;
/* Create block pool 0. */
- status = tx_block_pool_create(&pool_0, "pool 0", 100, pointer, 320);
- pointer = pointer + 320;
+ status = tx_block_pool_create(&pool_0, "pool 0", 100, pointer, 340);
+ pointer = pointer + 340;
#ifndef TX_DISABLE_ERROR_CHECKING /* skip this test and pretend it passed */
@@ -128,8 +128,8 @@ INT i;
#ifndef TX_DISABLE_ERROR_CHECKING /* skip this test and pretend it passed */
- status = tx_block_pool_create(&pool_1, "pool 1", 100, pointer, 320);
- pointer = pointer + 320;
+ status = tx_block_pool_create(&pool_1, "pool 1", 100, pointer, 340);
+ pointer = pointer + 340;
/* Attempt to create a pool with an invalid size. */
status = _txe_block_pool_create(&pool_2, "pool 2", 100, pointer, 320, 777777);
diff --git a/test/tx/regression/threadx_block_memory_suspension_test.c b/test/tx/regression/threadx_block_memory_suspension_test.c
index da14f454..3b49f5d9 100644
--- a/test/tx/regression/threadx_block_memory_suspension_test.c
+++ b/test/tx/regression/threadx_block_memory_suspension_test.c
@@ -83,8 +83,8 @@ CHAR *pointer;
}
/* Create block pool. */
- status = tx_block_pool_create(&pool_0, "pool 0", 100, pointer, 320);
- pointer = pointer + 320;
+ status = tx_block_pool_create(&pool_0, "pool 0", 100, pointer, 340);
+ pointer = pointer + 340;
/* Check status. */
if (status != TX_SUCCESS)
diff --git a/test/tx/regression/threadx_block_memory_suspension_timeout_test.c b/test/tx/regression/threadx_block_memory_suspension_timeout_test.c
index dc460662..a2be3c6c 100644
--- a/test/tx/regression/threadx_block_memory_suspension_timeout_test.c
+++ b/test/tx/regression/threadx_block_memory_suspension_timeout_test.c
@@ -84,8 +84,8 @@ CHAR *pointer;
}
/* Create block pool. */
- status = tx_block_pool_create(&pool_0, "pool 0", 100, pointer, 320);
- pointer = pointer + 320;
+ status = tx_block_pool_create(&pool_0, "pool 0", 100, pointer, 340);
+ pointer = pointer + 340;
/* Check status. */
if (status != TX_SUCCESS)
diff --git a/test/tx/regression/threadx_block_memory_thread_terminate_test.c b/test/tx/regression/threadx_block_memory_thread_terminate_test.c
index 6064891d..c568d9a2 100644
--- a/test/tx/regression/threadx_block_memory_thread_terminate_test.c
+++ b/test/tx/regression/threadx_block_memory_thread_terminate_test.c
@@ -69,8 +69,8 @@ CHAR *pointer;
}
/* Create block pool. */
- status = tx_block_pool_create(&pool_0, "pool 0", 100, pointer, 320);
- pointer = pointer + 320;
+ status = tx_block_pool_create(&pool_0, "pool 0", 100, pointer, 340);
+ pointer = pointer + 340;
/* Check status. */
if (status != TX_SUCCESS)
diff --git a/test/tx/regression/threadx_byte_memory_basic_test.c b/test/tx/regression/threadx_byte_memory_basic_test.c
index 36f41331..d153ccaf 100644
--- a/test/tx/regression/threadx_byte_memory_basic_test.c
+++ b/test/tx/regression/threadx_byte_memory_basic_test.c
@@ -4,6 +4,15 @@
#include <stdio.h>
#include "tx_api.h"
+/* Per-allocation overhead in byte pools: next pointer + ALIGN_TYPE alignment marker. */
+#define BYTE_POOL_OVERHEAD (sizeof(UCHAR *) + sizeof(ALIGN_TYPE))
+
+/* Pool size for pool_0: exactly fits 3 allocations of 24 bytes with no room for a 4th. */
+#define POOL_0_SIZE (ULONG)(3u * (24u + BYTE_POOL_OVERHEAD) + BYTE_POOL_OVERHEAD)
+
+/* Pool size for pool_4: must accommodate 3 allocations of 84 bytes (tightest pattern). */
+#define POOL_4_SIZE (ULONG)(3u * (84u + BYTE_POOL_OVERHEAD) + BYTE_POOL_OVERHEAD + 16u)
+
typedef struct BYTE_MEMORY_TEST_STRUCT
{
ULONG first;
@@ -256,8 +265,8 @@ CHAR *pointer;
}
/* Create byte pools 0 and 1. */
- status = tx_byte_pool_create(&pool_0, "pool 0", pointer, 108);
- pointer = pointer + 108;
+ status = tx_byte_pool_create(&pool_0, "pool 0", pointer, POOL_0_SIZE);
+ pointer = pointer + POOL_0_SIZE;
/* Check status. */
if (status != TX_SUCCESS)
@@ -279,9 +288,9 @@ CHAR *pointer;
}
/* Test for search pointer issue on wrapped seach with prior block to search pointer merged. */
- status = tx_byte_pool_create(&pool_4, "pool 4", pointer, 300);
+ status = tx_byte_pool_create(&pool_4, "pool 4", pointer, POOL_4_SIZE);
pool_4_memory = pointer;
- pointer = pointer + 300;
+ pointer = pointer + POOL_4_SIZE;
/* Check status. */
if (status != TX_SUCCESS)
@@ -865,7 +874,7 @@ UCHAR *save_search;
}
/* Move the search pointer to the third block to exercise that code in the byte search algorithm. */
- pool_0.tx_byte_pool_search = (UCHAR *) pointer_3-8;
+ pool_0.tx_byte_pool_search = (UCHAR *) pointer_3 - BYTE_POOL_OVERHEAD;
/* Now allocate the block again. */
status = tx_byte_allocate(&pool_0, (VOID **) &pointer_2, 24, TX_NO_WAIT);
@@ -885,7 +894,7 @@ UCHAR *save_search;
status += tx_byte_release(pointer_1);
/* Move the search pointer to the third block to exercise that code in the byte search algorithm. */
- pool_0.tx_byte_pool_search = (UCHAR *) pointer_3-8;
+ pool_0.tx_byte_pool_search = (UCHAR *) pointer_3 - BYTE_POOL_OVERHEAD;
/* Allocate a large block to force the search pointer update. */
status = tx_byte_allocate(&pool_0, (VOID **) &pointer_3, 88, TX_NO_WAIT);
@@ -961,7 +970,7 @@ UCHAR *save_search;
}
/* Create pool 4. */
- status = tx_byte_pool_create(&pool_4, "pool 4", pool_4_memory, 300);
+ status = tx_byte_pool_create(&pool_4, "pool 4", pool_4_memory, POOL_4_SIZE);
/* Check status. */
if (status != TX_SUCCESS)
diff --git a/test/tx/regression/threadx_event_flag_suspension_timeout_test.c b/test/tx/regression/threadx_event_flag_suspension_timeout_test.c
index 149cb58f..f4ee3995 100644
--- a/test/tx/regression/threadx_event_flag_suspension_timeout_test.c
+++ b/test/tx/regression/threadx_event_flag_suspension_timeout_test.c
@@ -178,17 +178,12 @@ UINT status;
/* Sleep for 63 ticks. */
tx_thread_sleep(63);
- /* Check the run counters. */
- if (((thread_1_counter != 32)
-#ifdef __linux__
- && (thread_1_counter != 33) /* Depending on the starting time, thread 1 can run either 32 or 33 rounds. */
-#endif
- ) ||
- ((thread_2_counter != 13)
-#ifdef __linux__
- && (thread_2_counter != 14) /* When CPU starves, the thread 2 can run 14 ronuds. */
-#endif
- ))
+ /* Check the run counters. Depending on the starting time relative to
+ the tick boundary, thread 1 can run either 32 or 33 rounds and
+ thread 2 can run 13 or 14 rounds. This variance applies to any
+ platform with non-deterministic tick alignment (Linux, QEMU, etc.). */
+ if (((thread_1_counter != 32) && (thread_1_counter != 33)) ||
+ ((thread_2_counter != 13) && (thread_2_counter != 14)))
{
/* Event flag error. */