diff --git a/cputest/CMakeLists.txt b/cputest/CMakeLists.txt index d12a3eb..bcca2e5 100644 --- a/cputest/CMakeLists.txt +++ b/cputest/CMakeLists.txt @@ -1,5 +1,6 @@ add_hwtest(MODULE cputest TEST cr FILES cr.cpp) add_hwtest(MODULE cputest TEST fctiwz FILES fctiwz.cpp) +add_hwtest(MODULE cputest TEST fprf FILES fprf.cpp) add_hwtest(MODULE cputest TEST frsp FILES frsp.cpp) add_hwtest(MODULE cputest TEST load FILES load.cpp) add_hwtest(MODULE cputest TEST ni FILES ni.cpp) diff --git a/cputest/fprf.cpp b/cputest/fprf.cpp new file mode 100644 index 0000000..a0e6a73 --- /dev/null +++ b/cputest/fprf.cpp @@ -0,0 +1,183 @@ +#include +#include +#include +#include "common/BitUtils.h" +#include "common/hwtests.h" + +template +struct TestInstance +{ + T input; + u32 arith_fprf; + u32 compare_fprf; +}; + +static void TestArith(TestInstance test) +{ + double temp; + u64 fprf; + asm volatile("fadd %0, %1, %2" : "=f"(temp) : "f"(test.input), "f"(-0.0)); + asm volatile("mffs %0" : "=f"(fprf)); + fprf >>= 12; + fprf &= 0x1F; + + const u32 expected = test.arith_fprf; + + DO_TEST(fprf == expected, "Bad arith FPRF for 0x{:016x}:\n" + " got 0x{:x}\n" + "expected 0x{:x}", + Common::BitCast(test.input), fprf, expected); +} + +static void TestArith(TestInstance test) +{ + float temp; + u64 fprf; + asm volatile("fadds %0, %1, %2" : "=f"(temp) : "f"(test.input), "f"(-0.0f)); + asm volatile("mffs %0" : "=f"(fprf)); + fprf >>= 12; + fprf &= 0x1F; + + const u32 expected = test.arith_fprf; + + DO_TEST(fprf == expected, "Bad arith FPRF for 0x{:08x}:\n" + " got 0x{:x}\n" + "expected 0x{:x}", + Common::BitCast(test.input), fprf, expected); +} + +static void TestCompare(TestInstance test, bool c) +{ + // Force the C bit (top bit of FPRF) to a certain value + double temp; + const double value_for_setting_c = c ? std::numeric_limits::quiet_NaN() : 0.0; + asm volatile("fadd %0, %1, %2" : "=f"(temp) : "f"(value_for_setting_c), "f"(0.0)); + + u64 fprf; + u32 cr; + asm volatile("fcmpo cr0, %0, %1" :: "f"(test.input), "f"(-0.0)); + asm volatile("mffs %0" : "=f"(fprf)); + asm volatile("mfcr %0" : "=r"(cr)); + fprf >>= 12; + fprf &= 0x1F; + cr >>= 28; + + u32 expected = test.compare_fprf; + + DO_TEST(cr == expected, "Bad compare CR for 0x{:016x}:\n" + " got 0x{:x}\n" + "expected 0x{:x}", + Common::BitCast(test.input), cr, expected); + + if (c) + expected |= 0x10; + + DO_TEST(fprf == expected, "Bad compare FPRF for 0x{:016x}:\n" + " got 0x{:x}\n" + "expected 0x{:x}", + Common::BitCast(test.input), fprf, expected); +} + +static void TestCompare(TestInstance test, bool c) +{ + // Force the C bit (top bit of FPRF) to a certain value + float temp; + const float value_for_setting_c = c ? std::numeric_limits::quiet_NaN() : 0.0f; + asm volatile("fadds %0, %1, %2" : "=f"(temp) : "f"(value_for_setting_c), "f"(0.0f)); + + u64 fprf; + u32 cr; + asm volatile("fcmpo cr0, %0, %1" :: "f"(test.input), "f"(-0.0f)); + asm volatile("mffs %0" : "=f"(fprf)); + asm volatile("mfcr %0" : "=r"(cr)); + fprf >>= 12; + fprf &= 0x1F; + cr >>= 28; + + u32 expected = test.compare_fprf; + + DO_TEST(cr == expected, "Bad compare CR for 0x{:08x}:\n" + " got 0x{:x}\n" + "expected 0x{:x}", + Common::BitCast(test.input), cr, expected); + + if (c) + expected |= 0x10; + + DO_TEST(fprf == expected, "Bad compare FPRF for 0x{:08x}:\n" + " got 0x{:x}\n" + "expected 0x{:x}", + Common::BitCast(test.input), fprf, expected); +} + +// Floating-Point Result Flags test for double-precision operations +static void FprfDoubleTest() +{ + START_TEST(); + + TestInstance values[] = { + {std::numeric_limits::quiet_NaN(), 0b10001, 0b0001}, + {-std::numeric_limits::infinity(), 0b01001, 0b1000}, + {-1.0, 0b01000, 0b1000}, + {-std::numeric_limits::denorm_min(), 0b11000, 0b1000}, + {-0.0, 0b10010, 0b0010}, + {0.0, 0b00010, 0b0010}, + {std::numeric_limits::denorm_min(), 0b10100, 0b0100}, + {1.0, 0b00100, 0b0100}, + {std::numeric_limits::infinity(), 0b00101, 0b0100}, + }; + + // Ensure FPSCR[NI] is not set (we want operations to be able to output denormals) + asm("mtfsfi 7, 0"); + + for (size_t i = 0; i < sizeof(values) / sizeof(values[0]); i++) + { + TestArith(values[i]); + TestCompare(values[i], false); + TestCompare(values[i], true); + } + END_TEST(); +} + +// Floating-Point Result Flags test for single-precision operations +static void FprfSingleTest() +{ + START_TEST(); + + TestInstance values[] = { + {std::numeric_limits::quiet_NaN(), 0b10001, 0b0001}, + {-std::numeric_limits::infinity(), 0b01001, 0b1000}, + {-1.0f, 0b01000, 0b1000}, + {-std::numeric_limits::denorm_min(), 0b11000, 0b1000}, + {-0.0f, 0b10010, 0b0010}, + {0.0f, 0b00010, 0b0010}, + {std::numeric_limits::denorm_min(), 0b10100, 0b0100}, + {1.0f, 0b00100, 0b0100}, + {std::numeric_limits::infinity(), 0b00101, 0b0100}, + }; + + // Ensure FPSCR[NI] is not set (we want operations to be able to output denormals) + asm("mtfsfi 7, 0"); + + for (size_t i = 0; i < sizeof(values) / sizeof(values[0]); i++) + { + TestArith(values[i]); + TestCompare(values[i], false); + TestCompare(values[i], true); + } + END_TEST(); +} + +int main() +{ + network_init(); + WPAD_Init(); + + FprfDoubleTest(); + FprfSingleTest(); + + network_printf("Shutting down...\n"); + network_shutdown(); + + return 0; +}