Implemented lazy FPU, GP relaxation, and QEMU automation for GNU port in arch/risc-v32 (#513)

This PR adds three functional improvements to the RISC-V 32-bit GNU port.

Lazy FPU stacking (tx_thread_context_save.S, tx_thread_context_restore.S, tx_thread_schedule.S): FP register save/restore is now skipped whenmstatus.FS is Off, reducing context switch overhead for threads that do not use floating point.

GP relaxation (cmake/riscv32_gnu.cmake, entry.s, link.lds): Enables the -mrelax compiler flag and defines __global_pointer$ in the linker script.The entry stub initializes gp at startup. gp is not saved or restored during context switches.

WFI in idle loop (tx_thread_schedule.S): The scheduler issues wfi when no thread is ready, replacing busy-waiting with a low-power sleep.

A Python/QEMU/GDB functional test runner is added under ports/risc-v32/gnu/example_build/qemu_virt/test/. It validates context switching, FPUcontext preservation, timer interrupts, and preemption. To run:

cd ports/risc-v32/gnu/example_build/qemu_virt
make check-functional-riscv32

Tested on QEMU virt machine (rv32gc).

Co-authored-by: Wei-Chen Lai Winstonllllai@users.noreply.github.com
Co-authored-by: Frédéric Desbiens frederic.desbiens@eclipse-foundation.org
Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com
This commit is contained in:
Wei-Chen Lai
2026-05-25 16:43:24 -04:00
committed by GitHub
co-authored by Wei-Chen Lai Winstonllllai@users.noreply.github.com Frédéric Desbiens frederic.desbiens@eclipse-foundation.org Copilot 223556219+Copilot@users.noreply.github.com
parent 5b1b740a66
commit e031a3d506
11 changed files with 410 additions and 20 deletions
+1
View File
@@ -4,6 +4,7 @@
.tmp/
_deps/
build/
build_qemu/
Debug/
CMakeFiles/
CMakeScripts/
+1 -1
View File
@@ -7,7 +7,7 @@ if(DEFINED SOFT_FLOAT)
set(ARCH_FLAGS "-g -march=rv32ima_zicsr -mabi=ilp32 -mcmodel=medany")
set(CACHE{SOFT_FLOAT} FORCE 1)
else()
set(ARCH_FLAGS "-g -march=rv32gc -mabi=ilp32d -mcmodel=medany")
set(ARCH_FLAGS "-g -march=rv32gc -mabi=ilp32d -mcmodel=medany -mrelax")
endif()
set(CFLAGS "${ARCH_FLAGS}")
set(ASFLAGS "${ARCH_FLAGS}")
+4
View File
@@ -17,3 +17,7 @@ target_include_directories(${PROJECT_NAME}
PUBLIC
${CMAKE_CURRENT_LIST_DIR}/inc
)
if(EXISTS ${CMAKE_CURRENT_LIST_DIR}/example_build/qemu_virt/CMakeLists.txt)
add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/example_build/qemu_virt)
endif()
@@ -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/azrtos_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()
@@ -9,6 +9,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 +202,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 +364,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. */
@@ -1,5 +1,5 @@
.section .text
.section .text.boot, "ax"
.align 4
.global _start
.extern main
@@ -11,7 +11,10 @@ _start:
bne t0, zero, 1f
li x1, 0
li x2, 0
li x3, 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
@@ -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.*)
@@ -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. */
@@ -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. */
@@ -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. */
@@ -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
+16 -6
View File
@@ -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
@@ -204,11 +209,11 @@ _tx_thread_ready_to_run:
fld f31, 62*4(sp) // Recover ft11
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
li t0, 0x1880 // Prepare mstatus: MPP=Machine(0x1800) | MPIE(0x80)
@@ -256,7 +261,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
@@ -287,11 +297,11 @@ _tx_thread_solicited_return:
fld f27, 26*4(sp) // Recover fs11
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
csrw mstatus, t0 // Restore mstatus
lw ra, 13*4(sp) // Recover return address