double ppc_estimate_reciprocal(double v) { auto bits = get_float_bits(v); if (bits.mantissa == 0 && bits.exponent == 0) { return std::copysign(std::numeric_limits<double>::infinity(), v); } if (bits.exponent == bits.exponent_max) { if (bits.mantissa == 0) { return std::copysign(0.0, v); } return static_cast<float>(v); } if (bits.exponent < 895) { return std::copysign(std::numeric_limits<float>::max(), v); } if (bits.exponent > 1149) { return std::copysign(0.0, v); } int idx = (int)(bits.mantissa >> 37); bits.exponent = 0x7FD - bits.exponent; bits.mantissa = (int64_t)(fres_expected_base[idx / 1024] - (fres_expected_dec[idx / 1024] * (idx % 1024) + 1) / 2) << 29; return bits.v; }
static void mergeGeneric(cpu::Core *state, Instruction instr) { float d0, d1; if (flags & MergeValue0) { if (!is_signalling_nan(state->fpr[instr.frA].paired1)) { d0 = static_cast<float>(state->fpr[instr.frA].paired1); } else { d0 = truncate_double(state->fpr[instr.frA].paired1); } } else { if (!is_signalling_nan(state->fpr[instr.frA].paired0)) { d0 = static_cast<float>(state->fpr[instr.frA].paired0); } else { d0 = truncate_double(state->fpr[instr.frA].paired0); } } // When inserting a double-precision value into slot 1, the value is // truncated rather than rounded. double d1_double; if (flags & MergeValue1) { d1_double = state->fpr[instr.frB].paired1; } else { d1_double = state->fpr[instr.frB].paired0; } auto d1_bits = get_float_bits(d1_double); if (d1_bits.exponent >= 1151 && d1_bits.exponent < 2047) { d1 = std::numeric_limits<float>::max(); } else { d1 = truncate_double(d1_double); } state->fpr[instr.frD].paired0 = extend_float(d0); state->fpr[instr.frD].paired1 = extend_float(d1); // Don't leak any exceptions (inexact, overflow etc.) to later instructions. std::feclearexcept(FE_ALL_EXCEPT); if (instr.rc) { updateFloatConditionRegister(state); } }
// Reciprocal Square Root static void ps_rsqrte(cpu::Core *state, Instruction instr) { const double b0 = state->fpr[instr.frB].paired0; const double b1 = state->fpr[instr.frB].paired1; const bool vxsnan0 = is_signalling_nan(b0); const bool vxsnan1 = is_signalling_nan(b1); const bool vxsqrt0 = !vxsnan0 && std::signbit(b0) && !is_zero(b0); const bool vxsqrt1 = !vxsnan1 && std::signbit(b1) && !is_zero(b1); const bool zx0 = is_zero(b0); const bool zx1 = is_zero(b1); const uint32_t oldFPSCR = state->fpscr.value; state->fpscr.vxsnan |= vxsnan0 || vxsnan1; state->fpscr.vxsqrt |= vxsqrt0 || vxsqrt1; state->fpscr.zx |= zx0 || zx1; double d0, d1; bool write = true; if (((vxsnan0 || vxsqrt0) && state->fpscr.ve) || (zx0 && state->fpscr.ze)) { write = false; } else { d0 = ppc_estimate_reciprocal_root(b0); updateFPRF(state, d0); } if (((vxsnan1 || vxsqrt1) && state->fpscr.ve) || (zx1 && state->fpscr.ze)) { write = false; } else { d1 = ppc_estimate_reciprocal_root(b1); } if (write) { // ps_rsqrte behaves strangely when the result's magnitude is out of // range: ps0 keeps its double-precision exponent, while ps1 appears // to get an arbitrary value from the floating-point circuitry. The // details of how ps1's exponent is affected are unknown, but the // logic below works for double-precision inputs 0x7FE...FFF (maximum // normal) and 0x000...001 (minimum denormal). auto bits0 = get_float_bits(d0); bits0.mantissa &= UINT64_C(0xFFFFFE0000000); state->fpr[instr.frD].paired0 = bits0.v; auto bits1 = get_float_bits(d1); if (bits1.exponent == 0) { // Leave as zero (reciprocal square root can never be a denormal). } else if (bits1.exponent < 1151) { int8_t exponent8 = (bits1.exponent - 1023) & 0xFF; bits1.exponent = 1023 + exponent8; } else if (bits1.exponent < 2047) { bits1.exponent = 1022; } bits1.mantissa &= UINT64_C(0xFFFFFE0000000); state->fpr[instr.frD].paired1 = bits1.v; } updateFPSCR(state, oldFPSCR); if (instr.rc) { updateFloatConditionRegister(state); } }
bool runTests(const std::string &path) { uint32_t testsFailed = 0, testsPassed = 0; uint32_t baseAddress = mem::MEM2Base; Instruction bclr = encodeInstruction(InstructionID::bclr); bclr.bo = 0x1f; mem::write(baseAddress + 4, bclr.value); fs::FileSystem filesystem; fs::FolderEntry entry; fs::HostPath base = path; filesystem.mountHostFolder("/tests", base, fs::Permissions::Read); auto folder = filesystem.openFolder("/tests"); while (folder->read(entry)) { std::ifstream file(base.join(entry.name).path(), std::ifstream::in | std::ifstream::binary); cereal::BinaryInputArchive cerealInput(file); TestFile testFile; // Parse test file with cereal testFile.name = entry.name; cerealInput(testFile); // Run tests gLog->info("Checking {}", testFile.name); for (auto &test : testFile.tests) { bool failed = false; if (!TEST_FMADDSUB) { auto data = espresso::decodeInstruction(test.instr); switch (data->id) { case InstructionID::fmadd: case InstructionID::fmadds: case InstructionID::fmsub: case InstructionID::fmsubs: case InstructionID::fnmadd: case InstructionID::fnmadds: case InstructionID::fnmsub: case InstructionID::fnmsubs: failed = true; break; } if (failed) { continue; } } // Setup core state from test input cpu::CoreRegs *state = cpu::this_core::state(); memset(state, 0, sizeof(cpu::CoreRegs)); state->cia = 0; state->nia = baseAddress; state->xer = test.input.xer; state->cr = test.input.cr; state->fpscr = test.input.fpscr; state->ctr = test.input.ctr; for (auto i = 0; i < 4; ++i) { state->gpr[i + 3] = test.input.gpr[i]; state->fpr[i + 1].paired0 = test.input.fr[i]; } // Execute test mem::write(baseAddress, test.instr.value); cpu::jit::clearCache(); cpu::this_core::executeSub(); // Check XER (all bits) if (state->xer.value != test.output.xer.value) { gLog->error("Test failed, xer expected {:08X} found {:08X}", test.output.xer.value, state->xer.value); failed = true; } // Check Condition Register (all bits) if (state->cr.value != test.output.cr.value) { gLog->error("Test failed, cr expected {:08X} found {:08X}", test.output.cr.value, state->cr.value); failed = true; } // Check FPSCR if (TEST_FPSCR) { if (!TEST_FPSCR_FR) { state->fpscr.fr = 0; test.output.fpscr.fr = 0; } if (!TEST_FPSCR_UX) { state->fpscr.ux = 0; test.output.fpscr.ux = 0; } auto state_fpscr = state->fpscr.value; auto test_fpscr = test.output.fpscr.value; if (state_fpscr != test_fpscr) { gLog->error("Test failed, fpscr {:08X} found {:08X}", test.output.fpscr.value, state->fpscr.value); compareFPSCR(test.input.fpscr, state->fpscr, test.output.fpscr); failed = true; } } // Check CTR if (state->ctr != test.output.ctr) { gLog->error("Test failed, ctr expected {:08X} found {:08X}", test.output.ctr, state->ctr); failed = true; } // Check all GPR for (auto i = 0; i < 4; ++i) { auto reg = i + hwtest::GPR_BASE; auto value = state->gpr[reg]; auto expected = test.output.gpr[i]; if (value != expected) { gLog->error("Test failed, r{} expected {:08X} found {:08X}", reg, expected, value); failed = true; } } // Check all FPR for (auto i = 0; i < 4; ++i) { auto reg = i + hwtest::FPR_BASE; auto value = state->fpr[reg].value; auto expected = test.output.fr[i]; if (!is_nan(value) && !is_nan(expected) && !is_infinity(value) && !is_infinity(expected)) { double dval = value / expected; if (dval < 0.999 || dval > 1.001) { gLog->error("Test failed, f{} expected {:16f} found {:16f}", reg, expected, value); failed = true; } } else { if (is_nan(value) && is_nan(expected)) { auto bits = get_float_bits(value); bits.sign = get_float_bits(expected).sign; value = bits.v; } if (bit_cast<uint64_t>(value) != bit_cast<uint64_t>(expected)) { gLog->error("Test failed, f{} expected {:16X} found {:16X}", reg, bit_cast<uint64_t>(expected), bit_cast<uint64_t>(value)); failed = true; } } } if (failed) { Disassembly dis; // Print disassembly disassemble(test.instr, dis, baseAddress); gLog->debug(dis.text); // Print all test fields gLog->debug("{:08x} Input Hardware Interp", test.instr.value); for (auto field : dis.instruction->read) { printTestField(field, test.instr, &test.input, &test.output, state); } for (auto field : dis.instruction->write) { printTestField(field, test.instr, &test.input, &test.output, state); } for (auto field : dis.instruction->flags) { printTestField(field, test.instr, &test.input, &test.output, state); } gLog->debug(""); ++testsFailed; } else { ++testsPassed; } } } gLog->info("Passed: {}, Failed: {}", testsPassed, testsFailed); return true; }
int runTests(const std::string &path) { uint32_t testsFailed = 0, testsPassed = 0; auto baseAddress = cpu::VirtualAddress { 0x02000000u }; auto basePhysicalAddress = cpu::PhysicalAddress { 0x50000000u }; auto codeSize = 2048u; cpu::allocateVirtualAddress(baseAddress, codeSize); cpu::mapMemory(baseAddress, basePhysicalAddress, codeSize, cpu::MapPermission::ReadWrite); Instruction bclr = encodeInstruction(InstructionID::bclr); bclr.bo = 0x1f; mem::write(baseAddress.getAddress() + 4, bclr.value); auto ec = std::error_code { }; for (auto itr = std::filesystem::directory_iterator { "/tests", ec }; itr != end(itr); ++itr) { std::ifstream file(itr->path().string(), std::ifstream::in | std::ifstream::binary); cereal::BinaryInputArchive cerealInput(file); TestFile testFile; // Parse test file with cereal testFile.name = itr->path().filename().string(); cerealInput(testFile); // Run tests for (auto &test : testFile.tests) { bool failed = false; if (!TEST_FMADDSUB) { auto data = espresso::decodeInstruction(test.instr); switch (data->id) { case InstructionID::fmadd: case InstructionID::fmadds: case InstructionID::fmsub: case InstructionID::fmsubs: case InstructionID::fnmadd: case InstructionID::fnmadds: case InstructionID::fnmsub: case InstructionID::fnmsubs: failed = true; break; } if (failed) { continue; } } // Setup core state from test input cpu::CoreRegs *state = cpu::this_core::state(); memset(state, 0, sizeof(cpu::CoreRegs)); state->cia = 0; state->nia = baseAddress.getAddress(); state->xer = test.input.xer; state->cr = test.input.cr; state->fpscr = test.input.fpscr; state->ctr = test.input.ctr; for (auto i = 0; i < 4; ++i) { state->gpr[i + 3] = test.input.gpr[i]; state->fpr[i + 1].paired0 = test.input.fr[i]; } // Execute test mem::write(baseAddress.getAddress(), test.instr.value); cpu::clearInstructionCache(); cpu::this_core::executeSub(); // Check XER (all bits) if (state->xer.value != test.output.xer.value) { gLog->error("Test failed, xer expected {:08X} found {:08X}", test.output.xer.value, state->xer.value); failed = true; } // Check Condition Register (all bits) if (state->cr.value != test.output.cr.value) { gLog->error("Test failed, cr expected {:08X} found {:08X}", test.output.cr.value, state->cr.value); failed = true; } // Check FPSCR if (TEST_FPSCR) { if (!TEST_FPSCR_FR) { state->fpscr.fr = 0; test.output.fpscr.fr = 0; } if (!TEST_FPSCR_UX) { state->fpscr.ux = 0; test.output.fpscr.ux = 0; } auto state_fpscr = state->fpscr.value; auto test_fpscr = test.output.fpscr.value; if (state_fpscr != test_fpscr) { gLog->error("Test failed, fpscr {:08X} found {:08X}", test.output.fpscr.value, state->fpscr.value); compareFPSCR(test.input.fpscr, state->fpscr, test.output.fpscr); failed = true; } } // Check CTR if (state->ctr != test.output.ctr) { gLog->error("Test failed, ctr expected {:08X} found {:08X}", test.output.ctr, state->ctr); failed = true; } // Check all GPR for (auto i = 0; i < 4; ++i) { auto reg = i + hwtest::GPR_BASE; auto value = state->gpr[reg]; auto expected = test.output.gpr[i]; if (value != expected) { gLog->error("Test failed, r{} expected {:08X} found {:08X}", reg, expected, value); failed = true; } } // Check all FPR for (auto i = 0; i < 4; ++i) { auto reg = i + hwtest::FPR_BASE; auto value = state->fpr[reg].value; auto expected = test.output.fr[i]; if (!is_nan(value) && !is_nan(expected) && !is_infinity(value) && !is_infinity(expected)) { double dval = value / expected; if (dval < 0.999 || dval > 1.001) { gLog->error("Test failed, f{} expected {:16f} found {:16f}", reg, expected, value); failed = true; } } else { if (is_nan(value) && is_nan(expected)) { auto bits = get_float_bits(value); bits.sign = get_float_bits(expected).sign; value = bits.v; } if (bit_cast<uint64_t>(value) != bit_cast<uint64_t>(expected)) { gLog->error("Test failed, f{} expected {:16X} found {:16X}", reg, bit_cast<uint64_t>(expected), bit_cast<uint64_t>(value)); failed = true; } } } if (failed) { Disassembly dis; // Print disassembly disassemble(test.instr, dis, baseAddress.getAddress()); gLog->debug(dis.text); // Print all test fields gLog->debug("{:08x} Input Hardware Interp", test.instr.value); for (auto field : dis.instruction->read) { printTestField(field, test.instr, &test.input, &test.output, state); } for (auto field : dis.instruction->write) { printTestField(field, test.instr, &test.input, &test.output, state); } for (auto field : dis.instruction->flags) { printTestField(field, test.instr, &test.input, &test.output, state); } gLog->debug(""); ++testsFailed; } else { ++testsPassed; } } } if (testsFailed) { gLog->error("Failed {} of {} tests.", testsFailed, testsFailed + testsPassed); } return testsFailed; }