mirror of
https://github.com/decaf-emu/decaf-emu.git
synced 2025-04-02 10:42:13 -04:00
320 lines
10 KiB
C++
320 lines
10 KiB
C++
#include "hardwaretests.h"
|
|
|
|
#include <cassert>
|
|
#include <cfenv>
|
|
#include <common/bit_cast.h>
|
|
#include <common/floatutils.h>
|
|
#include <common/log.h>
|
|
#include <common/strutils.h>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <libcpu/cpu.h>
|
|
#include <libcpu/mem.h>
|
|
#include <libcpu/espresso/espresso_disassembler.h>
|
|
#include <libcpu/espresso/espresso_instructionset.h>
|
|
#include <libcpu/espresso/espresso_registerformats.h>
|
|
|
|
using namespace espresso;
|
|
|
|
static const auto TEST_FPSCR = false;
|
|
static const auto TEST_FPSCR_FR = false;
|
|
static const auto TEST_FPSCR_UX = false;
|
|
static const auto TEST_FMADDSUB = false;
|
|
|
|
namespace hwtest
|
|
{
|
|
|
|
static void
|
|
printTestField(InstructionField field, Instruction instr, RegisterState *input, RegisterState *output, cpu::CoreRegs *state)
|
|
{
|
|
auto printGPR = [&](uint32_t reg) {
|
|
assert(reg >= GPR_BASE);
|
|
|
|
gLog->debug("r{:02d} = {:08X} {:08X} {:08X}", reg,
|
|
input->gpr[reg - GPR_BASE],
|
|
output->gpr[reg - GPR_BASE],
|
|
state->gpr[reg]);
|
|
};
|
|
|
|
auto printFPR = [&](uint32_t reg) {
|
|
assert(reg >= FPR_BASE);
|
|
|
|
gLog->debug("f{:02d} = {:16e} {:16e} {:16e}", reg,
|
|
input->fr[reg - FPR_BASE],
|
|
output->fr[reg - FPR_BASE],
|
|
state->fpr[reg].value);
|
|
|
|
gLog->debug(" {:16X} {:16X} {:16X}",
|
|
bit_cast<uint64_t>(input->fr[reg - FPR_BASE]),
|
|
bit_cast<uint64_t>(output->fr[reg - FPR_BASE]),
|
|
bit_cast<uint64_t>(state->fpr[reg].value));
|
|
};
|
|
|
|
switch (field) {
|
|
case InstructionField::rA:
|
|
printGPR(instr.rA);
|
|
break;
|
|
case InstructionField::rB:
|
|
printGPR(instr.rB);
|
|
break;
|
|
case InstructionField::rD:
|
|
printGPR(instr.rS);
|
|
break;
|
|
case InstructionField::rS:
|
|
printGPR(instr.rS);
|
|
break;
|
|
case InstructionField::frA:
|
|
printFPR(instr.frA);
|
|
break;
|
|
case InstructionField::frB:
|
|
printFPR(instr.frB);
|
|
break;
|
|
case InstructionField::frC:
|
|
printFPR(instr.frC);
|
|
break;
|
|
case InstructionField::frD:
|
|
printFPR(instr.frD);
|
|
break;
|
|
case InstructionField::frS:
|
|
printFPR(instr.frS);
|
|
break;
|
|
case InstructionField::XERC:
|
|
gLog->debug("xer.ca = {:08X} {:08X} {:08X}", input->xer.ca, output->xer.ca, state->xer.ca);
|
|
break;
|
|
case InstructionField::XERSO:
|
|
gLog->debug("xer.so = {:08X} {:08X} {:08X}", input->xer.so, output->xer.so, state->xer.so);
|
|
break;
|
|
case InstructionField::FPSCR:
|
|
gLog->debug("fpscr = {:08X} {:08x} {:08X}", input->fpscr.value, output->fpscr.value, state->fpscr.value);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
#define CompareFPSCRField(field) \
|
|
if (result.field != expected.field) { \
|
|
gLog->debug("fpscr." #field " = input {} expected {} found {}", input.field, expected.field, result.field); \
|
|
failed = true; \
|
|
}
|
|
|
|
// Compare all individual fields in fpscr
|
|
static bool
|
|
compareFPSCR(FloatingPointStatusAndControlRegister input,
|
|
FloatingPointStatusAndControlRegister expected,
|
|
FloatingPointStatusAndControlRegister result)
|
|
{
|
|
auto failed = false;
|
|
CompareFPSCRField(rn);
|
|
CompareFPSCRField(ni);
|
|
CompareFPSCRField(xe);
|
|
CompareFPSCRField(ze);
|
|
CompareFPSCRField(ue);
|
|
CompareFPSCRField(oe);
|
|
CompareFPSCRField(ve);
|
|
CompareFPSCRField(vxcvi);
|
|
CompareFPSCRField(vxsqrt);
|
|
CompareFPSCRField(vxsoft);
|
|
CompareFPSCRField(fprf);
|
|
CompareFPSCRField(fi);
|
|
CompareFPSCRField(fr);
|
|
CompareFPSCRField(vxvc);
|
|
CompareFPSCRField(vximz);
|
|
CompareFPSCRField(vxzdz);
|
|
CompareFPSCRField(vxidi);
|
|
CompareFPSCRField(vxisi);
|
|
CompareFPSCRField(vxsnan);
|
|
CompareFPSCRField(xx);
|
|
CompareFPSCRField(zx);
|
|
CompareFPSCRField(ux);
|
|
CompareFPSCRField(ox);
|
|
CompareFPSCRField(vx);
|
|
CompareFPSCRField(fex);
|
|
CompareFPSCRField(fx);
|
|
return failed;
|
|
}
|
|
|
|
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(disassemblyToText(dis));
|
|
|
|
// 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;
|
|
}
|
|
|
|
} // namespace hwtest
|