From c4c99a299e39f54f1b7c14142c200942e05f6c7d Mon Sep 17 00:00:00 2001 From: "Enrico Fraccaroli (Galfurian)" Date: Wed, 11 Feb 2026 10:11:47 +0100 Subject: [PATCH 1/2] test(kernel): add FPU unit tests --- kernel/inc/devices/fpu.h | 4 + kernel/src/devices/fpu.c | 96 +++++++---------- kernel/src/klib/math.c | 19 ++++ kernel/src/tests/runner.c | 2 + kernel/src/tests/unit/test_fpu.c | 171 +++++++++++++++++++++++++++++++ lib/inc/math.h | 15 +++ lib/src/math.c | 19 ++++ 7 files changed, 267 insertions(+), 59 deletions(-) create mode 100644 kernel/src/tests/unit/test_fpu.c diff --git a/kernel/inc/devices/fpu.h b/kernel/inc/devices/fpu.h index 381b68d2..19966c23 100644 --- a/kernel/inc/devices/fpu.h +++ b/kernel/inc/devices/fpu.h @@ -129,5 +129,9 @@ void unswitch_fpu(void); /// @return 0 if fails, 1 if succeed. int fpu_install(void); +/// @brief Check if the FPU is properly initialized. +/// @return 1 if FPU is initialized, 0 otherwise. +int fpu_is_initialized(void); + /// @} /// @} diff --git a/kernel/src/devices/fpu.c b/kernel/src/devices/fpu.c index 8a2d1a22..e7588484 100644 --- a/kernel/src/devices/fpu.c +++ b/kernel/src/devices/fpu.c @@ -46,11 +46,8 @@ static inline void __enable_fpu(void) static inline void __disable_fpu(void) { size_t t; - __asm__ __volatile__("mov %%cr0, %0" : "=r"(t)); - t |= 1U << 3U; - __asm__ __volatile__("mov %0, %%cr0" ::"r"(t)); } @@ -73,7 +70,10 @@ static inline void __save_fpu(task_struct *proc) } /// Initialize the FPU. -static inline void __init_fpu(void) { __asm__ __volatile__("fninit"); } +static inline void __init_fpu(void) +{ + __asm__ __volatile__("fninit"); +} /// Kernel trap for FPU usage when FPU is disabled. /// @param f The interrupt stack frame. @@ -149,7 +149,7 @@ static inline void __invalid_opcode_handler(pt_regs_t *f) // Check if this is user mode or kernel mode if ((f->cs & 0x3) == 0x3) { // User mode - send SIGILL - task_struct *task = scheduler_get_current_process(); + task_struct *task = scheduler_get_current_process(); // Get the action for SIGILL. sigaction_t *action = &task->sighand.action[SIGILL - 1]; // If the user did not install a SIGILL handler, terminate immediately. @@ -169,59 +169,15 @@ static inline void __invalid_opcode_handler(pt_regs_t *f) } } -/// @brief Ensure basic FPU functionality works. -/// @details -/// For processors without a FPU, this tests that maths libraries link -/// correctly. Uses a relaxed tolerance for floating point comparisons to -/// account for optimization-related precision variations in Release builds. -/// @return 1 on success, 0 on failure. -static int __fpu_test(void) +void switch_fpu(void) { - double a = M_PI; - // First test. - for (int i = 0; i < 10000; i++) { - a = a * 1.123 + (a / 3); - a /= 1.111; - while (a > 100.0) { - a /= 3.1234563212; - } - while (a < 2.0) { - a += 1.1232132131; - } - } - - // Use relaxed comparison to handle Release build precision variations - // Expected: ~50.11095685350556294679336133413 - // Allow ±0.1 tolerance - double expected = 50.11095685350556294679336133413; - if ((a < (expected - 0.1)) || (a > (expected + 0.1))) { - pr_err("FPU test 1 failed: result %f not near expected %f\n", a, expected); - return 0; - } - - pr_debug("FPU test 1 passed: %f\n", a); - - // Second test. - a = M_PI; - for (int i = 0; i < 100; i++) { - a = a * 3 + (a / 3); - } - - // Second test: just verify it's a reasonable large number - // Expected: ~60957114488184560000000000000000000000000000000000000.0 - // But with precision changes in Release, just verify it's in the ballpark - if (a < 1e40) { - pr_err("FPU test 2 failed: result %e too small\n", a); - return 0; - } - - pr_debug("FPU test 2 passed: %e\n", a); - return 1; + __save_fpu(scheduler_get_current_process()); } -void switch_fpu(void) { __save_fpu(scheduler_get_current_process()); } - -void unswitch_fpu(void) { __restore_fpu(scheduler_get_current_process()); } +void unswitch_fpu(void) +{ + __restore_fpu(scheduler_get_current_process()); +} int fpu_install(void) { @@ -260,9 +216,31 @@ int fpu_install(void) isr_install_handler(FLOATING_POINT_ERR, &__sigfpe_handler, "floating point error"); pr_debug(" FLOATING_POINT_ERR handler installed.\n"); - pr_debug("fpu_install: Running FPU test...\n"); - int result = __fpu_test(); - pr_debug("fpu_install: FPU test result: %d\n", result); + pr_debug("fpu_install: FPU initialization completed successfully.\n"); + + return 1; +} - return result; +int fpu_is_initialized(void) +{ + size_t cr0, cr4; + // Read CR0 + __asm__ __volatile__("mov %%cr0, %0" : "=r"(cr0)); + // Read CR4 + __asm__ __volatile__("mov %%cr4, %0" : "=r"(cr4)); + // Check CR0: EM bit (2) should be 0, MP bit (1) should be 1 + if ((cr0 & (1U << 2U)) != 0) { + return 0; // EM bit set, FPU emulation enabled + } + if ((cr0 & (1U << 1U)) == 0) { + return 0; // MP bit not set + } + // Check CR4: OSFXSR (9) and OSXMMEXCPT (10) should be set + if ((cr4 & (1U << 9U)) == 0) { + return 0; // OSFXSR not set + } + if ((cr4 & (1U << 10U)) == 0) { + return 0; // OSXMMEXCPT not set + } + return 1; } diff --git a/kernel/src/klib/math.c b/kernel/src/klib/math.c index 331dc8e5..426c575d 100644 --- a/kernel/src/klib/math.c +++ b/kernel/src/klib/math.c @@ -134,6 +134,25 @@ double logx(double x, double y) return ln(x) / ln(y); } +double log(double x) +{ + return ln(x); +} + +double sin(double x) +{ + double out; + __asm__ __volatile__("fldl %1; fsin" : "=t"(out) : "m"(x)); + return out; +} + +double cos(double x) +{ + double out; + __asm__ __volatile__("fldl %1; fcos" : "=t"(out) : "m"(x)); + return out; +} + /// Max power for forward and reverse projections. #define MAXPOWTWO 4.503599627370496000E+15 diff --git a/kernel/src/tests/runner.c b/kernel/src/tests/runner.c index 1a80a6ae..1d6e94f2 100644 --- a/kernel/src/tests/runner.c +++ b/kernel/src/tests/runner.c @@ -41,6 +41,7 @@ extern void test_buddy(void); extern void test_page(void); extern void test_memory_adversarial(void); extern void test_dma(void); +extern void test_fpu(void); /// @brief Test registry - one entry per subsystem. static const test_entry_t test_functions[] = { @@ -57,6 +58,7 @@ static const test_entry_t test_functions[] = { {test_page, "Page Structure Subsystem" }, {test_dma, "DMA Zone/Allocation Tests" }, {test_memory_adversarial, "Memory Adversarial/Error Tests"}, + {test_fpu, "FPU Subsystem" }, }; static const int num_tests = sizeof(test_functions) / sizeof(test_entry_t); diff --git a/kernel/src/tests/unit/test_fpu.c b/kernel/src/tests/unit/test_fpu.c new file mode 100644 index 00000000..d1f6a140 --- /dev/null +++ b/kernel/src/tests/unit/test_fpu.c @@ -0,0 +1,171 @@ +/// @file test_fpu.c +/// @brief FPU unit tests. +/// @copyright (c) 2014-2024 This file is distributed under the MIT License. +/// See LICENSE.md for details. + +// Setup the logging for this file (do this before any other include). +#include "sys/kernel_levels.h" // Include kernel log levels. +#define __DEBUG_HEADER__ "[TUNIT ]" ///< Change header. +#define __DEBUG_LEVEL__ LOGLEVEL_NOTICE ///< Set log level. +#include "io/debug.h" // Include debugging functions. + +#include "devices/fpu.h" +#include "math.h" +#include "tests/test.h" +#include "tests/test_utils.h" + +/// @brief Tollerance for floating point comparisons. +#define EPSILON 1e-10 + +/// @brief Check if two floating point numbers are equal within a tolerance. +/// @param a First value. +/// @param b Second value. +/// @param epsilon Tolerance for comparison. +/// @return 1 if |a - b| <= epsilon, 0 otherwise. +static inline int check_float_equality(double a, double b, double epsilon) +{ + return (fabs(a - b) <= epsilon) ? 1 : 0; +} + +/// @brief Test that FPU is initialized. +TEST(fpu_initialized) +{ + TEST_SECTION_START("FPU initialization check"); + // Check that FPU is properly initialized after boot + ASSERT(fpu_is_initialized() == 1); + TEST_SECTION_END(); +} + +/// @brief Test basic floating point addition. +TEST(fpu_addition) +{ + TEST_SECTION_START("FPU addition"); + double a = 3.14; + double b = 2.86; + double result = a + b; + ASSERT(check_float_equality(result, 6.0, EPSILON)); + TEST_SECTION_END(); +} + +/// @brief Test basic floating point multiplication. +TEST(fpu_multiplication) +{ + TEST_SECTION_START("FPU multiplication"); + double a = 3.0; + double b = 4.0; + double result = a * b; + ASSERT(check_float_equality(result, 12.0, EPSILON)); + TEST_SECTION_END(); +} + +/// @brief Test floating point division. +TEST(fpu_division) +{ + TEST_SECTION_START("FPU division"); + double a = 10.0; + double b = 2.0; + double result = a / b; + ASSERT(check_float_equality(result, 5.0, EPSILON)); + TEST_SECTION_END(); +} + +/// @brief Test floating point square root. +TEST(fpu_sqrt) +{ + TEST_SECTION_START("FPU square root"); + double a = 9.0; + double result = sqrt(a); + ASSERT(check_float_equality(result, 3.0, EPSILON)); + TEST_SECTION_END(); +} + +/// @brief Test floating point sine function. +TEST(fpu_sin) +{ + TEST_SECTION_START("FPU sine"); + double a = 0.0; + double result = sin(a); + ASSERT(check_float_equality(result, 0.0, EPSILON)); + TEST_SECTION_END(); +} + +/// @brief Test floating point cosine function. +TEST(fpu_cos) +{ + TEST_SECTION_START("FPU cosine"); + double a = 0.0; + double result = cos(a); + ASSERT(check_float_equality(result, 1.0, EPSILON)); + TEST_SECTION_END(); +} + +/// @brief Test floating point power function. +TEST(fpu_pow) +{ + TEST_SECTION_START("FPU power"); + double a = 2.0; + double b = 3.0; + double result = pow(a, b); + ASSERT(check_float_equality(result, 8.0, EPSILON)); + TEST_SECTION_END(); +} + +/// @brief Test floating point logarithm. +TEST(fpu_log) +{ + TEST_SECTION_START("FPU logarithm"); + double a = 1.0; + double result = log(a); + ASSERT(check_float_equality(result, 0.0, EPSILON)); + TEST_SECTION_END(); +} + +/// @brief Test floating point exponential. +TEST(fpu_exp) +{ + TEST_SECTION_START("FPU exponential"); + double a = 0.0; + double result = exp(a); + ASSERT(check_float_equality(result, 1.0, EPSILON)); + TEST_SECTION_END(); +} + +/// @brief Test floating point precision with PI. +TEST(fpu_pi_precision) +{ + TEST_SECTION_START("FPU PI precision"); + double pi = M_PI; + // Check that PI is approximately 3.14159 + ASSERT(pi > 3.141 && pi < 3.142); + TEST_SECTION_END(); +} + +/// @brief Test floating point loop accumulation. +TEST(fpu_loop_accumulation) +{ + TEST_SECTION_START("FPU loop accumulation"); + double sum = 0.0; + for (int i = 1; i <= 10; i++) { + sum += 1.0 / i; + } + // Harmonic series H_10 ≈ 2.9289682539682538 + ASSERT(sum > 2.9 && sum < 2.95); + TEST_SECTION_END(); +} + +/// @brief Run all FPU tests. +void test_fpu(void) +{ + test_fpu_initialized(); + test_fpu_addition(); + test_fpu_multiplication(); + test_fpu_division(); + test_fpu_sqrt(); + test_fpu_sin(); + test_fpu_cos(); + test_fpu_pow(); + test_fpu_log(); + test_fpu_exp(); + test_fpu_pi_precision(); + test_fpu_loop_accumulation(); +} \ No newline at end of file diff --git a/lib/inc/math.h b/lib/inc/math.h index 70aa36ca..8854abbe 100644 --- a/lib/inc/math.h +++ b/lib/inc/math.h @@ -153,6 +153,21 @@ double ln(double x); /// @return Return the result. double logx(double x, double y); +/// @brief Natural logarithm function (alias for ln). +/// @param x Topic of the logarithm function. +/// @return Return the result. +double log(double x); + +/// @brief Sine function. +/// @param x Angle in radians. +/// @return Sine of x. +double sin(double x); + +/// @brief Cosine function. +/// @param x Angle in radians. +/// @return Cosine of x. +double cos(double x); + /// @brief Breaks x into an integral and a fractional part, both parts have the same sign as x. /// @param x The value we want to break. /// @param intpart Where we store the integer part. diff --git a/lib/src/math.c b/lib/src/math.c index 331dc8e5..426c575d 100644 --- a/lib/src/math.c +++ b/lib/src/math.c @@ -134,6 +134,25 @@ double logx(double x, double y) return ln(x) / ln(y); } +double log(double x) +{ + return ln(x); +} + +double sin(double x) +{ + double out; + __asm__ __volatile__("fldl %1; fsin" : "=t"(out) : "m"(x)); + return out; +} + +double cos(double x) +{ + double out; + __asm__ __volatile__("fldl %1; fcos" : "=t"(out) : "m"(x)); + return out; +} + /// Max power for forward and reverse projections. #define MAXPOWTWO 4.503599627370496000E+15 From 0cfbd44e0942374326960bc14981c8ac4a659022 Mon Sep 17 00:00:00 2001 From: "Enrico Fraccaroli (Galfurian)" Date: Wed, 11 Feb 2026 10:15:40 +0100 Subject: [PATCH 2/2] test(kernel): use adaptive epsilon for FPU tests - Add macheps() function to compute unit in last place (ULP) - Replace fixed EPSILON with adaptive precision based on expected values - Makes floating-point comparisons mathematically correct and robust --- kernel/src/klib/math.c | 17 +++++++++++++++++ kernel/src/tests/unit/test_fpu.c | 21 +++++++++------------ lib/inc/math.h | 6 ++++++ lib/src/math.c | 17 +++++++++++++++++ 4 files changed, 49 insertions(+), 12 deletions(-) diff --git a/kernel/src/klib/math.c b/kernel/src/klib/math.c index 426c575d..6aa6243a 100644 --- a/kernel/src/klib/math.c +++ b/kernel/src/klib/math.c @@ -180,3 +180,20 @@ double modf(double x, double *intpart) // Signed fractional part. return (x - (*intpart)); } + +double macheps(double x) +{ + static const double base = 2.0; + double eps; + + if (isnan(x)) + eps = x; + else { + eps = (x == 0.0) ? 1.0 : x; + + while ((x + eps / base) != x) + eps /= base; + } + + return (eps); +} diff --git a/kernel/src/tests/unit/test_fpu.c b/kernel/src/tests/unit/test_fpu.c index d1f6a140..8def6b24 100644 --- a/kernel/src/tests/unit/test_fpu.c +++ b/kernel/src/tests/unit/test_fpu.c @@ -14,9 +14,6 @@ #include "tests/test.h" #include "tests/test_utils.h" -/// @brief Tollerance for floating point comparisons. -#define EPSILON 1e-10 - /// @brief Check if two floating point numbers are equal within a tolerance. /// @param a First value. /// @param b Second value. @@ -43,7 +40,7 @@ TEST(fpu_addition) double a = 3.14; double b = 2.86; double result = a + b; - ASSERT(check_float_equality(result, 6.0, EPSILON)); + ASSERT(check_float_equality(result, 6.0, macheps(6.0))); TEST_SECTION_END(); } @@ -54,7 +51,7 @@ TEST(fpu_multiplication) double a = 3.0; double b = 4.0; double result = a * b; - ASSERT(check_float_equality(result, 12.0, EPSILON)); + ASSERT(check_float_equality(result, 12.0, macheps(12.0))); TEST_SECTION_END(); } @@ -65,7 +62,7 @@ TEST(fpu_division) double a = 10.0; double b = 2.0; double result = a / b; - ASSERT(check_float_equality(result, 5.0, EPSILON)); + ASSERT(check_float_equality(result, 5.0, macheps(5.0))); TEST_SECTION_END(); } @@ -75,7 +72,7 @@ TEST(fpu_sqrt) TEST_SECTION_START("FPU square root"); double a = 9.0; double result = sqrt(a); - ASSERT(check_float_equality(result, 3.0, EPSILON)); + ASSERT(check_float_equality(result, 3.0, macheps(3.0))); TEST_SECTION_END(); } @@ -85,7 +82,7 @@ TEST(fpu_sin) TEST_SECTION_START("FPU sine"); double a = 0.0; double result = sin(a); - ASSERT(check_float_equality(result, 0.0, EPSILON)); + ASSERT(check_float_equality(result, 0.0, macheps(0.0))); TEST_SECTION_END(); } @@ -95,7 +92,7 @@ TEST(fpu_cos) TEST_SECTION_START("FPU cosine"); double a = 0.0; double result = cos(a); - ASSERT(check_float_equality(result, 1.0, EPSILON)); + ASSERT(check_float_equality(result, 1.0, macheps(1.0))); TEST_SECTION_END(); } @@ -106,7 +103,7 @@ TEST(fpu_pow) double a = 2.0; double b = 3.0; double result = pow(a, b); - ASSERT(check_float_equality(result, 8.0, EPSILON)); + ASSERT(check_float_equality(result, 8.0, macheps(8.0))); TEST_SECTION_END(); } @@ -116,7 +113,7 @@ TEST(fpu_log) TEST_SECTION_START("FPU logarithm"); double a = 1.0; double result = log(a); - ASSERT(check_float_equality(result, 0.0, EPSILON)); + ASSERT(check_float_equality(result, 0.0, macheps(0.0))); TEST_SECTION_END(); } @@ -126,7 +123,7 @@ TEST(fpu_exp) TEST_SECTION_START("FPU exponential"); double a = 0.0; double result = exp(a); - ASSERT(check_float_equality(result, 1.0, EPSILON)); + ASSERT(check_float_equality(result, 1.0, macheps(1.0))); TEST_SECTION_END(); } diff --git a/lib/inc/math.h b/lib/inc/math.h index 8854abbe..1d4ec7ea 100644 --- a/lib/inc/math.h +++ b/lib/inc/math.h @@ -173,3 +173,9 @@ double cos(double x); /// @param intpart Where we store the integer part. /// @return the fractional part. double modf(double x, double *intpart); + +/// @brief Compute the unit in the last place (ULP) for a floating-point number. +/// @param x The value to compute ULP for. +/// @return The ULP of x, which represents the smallest representable difference +/// at the scale of x. +double macheps(double x); diff --git a/lib/src/math.c b/lib/src/math.c index 426c575d..6aa6243a 100644 --- a/lib/src/math.c +++ b/lib/src/math.c @@ -180,3 +180,20 @@ double modf(double x, double *intpart) // Signed fractional part. return (x - (*intpart)); } + +double macheps(double x) +{ + static const double base = 2.0; + double eps; + + if (isnan(x)) + eps = x; + else { + eps = (x == 0.0) ? 1.0 : x; + + while ((x + eps / base) != x) + eps /= base; + } + + return (eps); +}