From 982bccf0c96d9afede0b388393e8d7922cb57917 Mon Sep 17 00:00:00 2001 From: "Adrian.Conlon" Date: Mon, 5 Jun 2017 23:24:08 +0100 Subject: [PATCH] First stab at adding Fuse Test runner. Signed-off-by: Adrian.Conlon --- EightBit.sln | 10 + Z80/fusetest_Z80/FuseExpectedTestResult.cpp | 48 + Z80/fusetest_Z80/FuseExpectedTestResult.h | 27 + Z80/fusetest_Z80/FuseExpectedTestResults.cpp | 20 + Z80/fusetest_Z80/FuseExpectedTestResults.h | 22 + Z80/fusetest_Z80/FuseMemoryDatum.cpp | 20 + Z80/fusetest_Z80/FuseMemoryDatum.h | 23 + Z80/fusetest_Z80/FuseRegisterState.cpp | 32 + Z80/fusetest_Z80/FuseRegisterState.h | 29 + Z80/fusetest_Z80/FuseTest.cpp | 25 + Z80/fusetest_Z80/FuseTest.h | 20 + Z80/fusetest_Z80/FuseTestEvent.cpp | 34 + Z80/fusetest_Z80/FuseTestEvent.h | 20 + Z80/fusetest_Z80/FuseTestEvents.cpp | 13 + Z80/fusetest_Z80/FuseTestEvents.h | 13 + Z80/fusetest_Z80/FuseTestRunner.cpp | 340 + Z80/fusetest_Z80/FuseTestRunner.h | 44 + Z80/fusetest_Z80/FuseTestSuite.cpp | 31 + Z80/fusetest_Z80/FuseTestSuite.h | 19 + Z80/fusetest_Z80/FuseTests.cpp | 20 + Z80/fusetest_Z80/FuseTests.h | 20 + Z80/fusetest_Z80/fuse-tests/README | 79 + Z80/fusetest_Z80/fuse-tests/tests.expected | 18913 ++++++++++++++++ Z80/fusetest_Z80/fuse-tests/tests.in | 9153 ++++++++ Z80/fusetest_Z80/fusetest_Z80.vcxproj | 194 + Z80/fusetest_Z80/fusetest_Z80.vcxproj.filters | 86 + Z80/fusetest_Z80/packages.config | 4 + Z80/fusetest_Z80/stdafx.cpp | 1 + Z80/fusetest_Z80/stdafx.h | 42 + Z80/fusetest_Z80/tests.cpp | 11 + 30 files changed, 29313 insertions(+) create mode 100644 Z80/fusetest_Z80/FuseExpectedTestResult.cpp create mode 100644 Z80/fusetest_Z80/FuseExpectedTestResult.h create mode 100644 Z80/fusetest_Z80/FuseExpectedTestResults.cpp create mode 100644 Z80/fusetest_Z80/FuseExpectedTestResults.h create mode 100644 Z80/fusetest_Z80/FuseMemoryDatum.cpp create mode 100644 Z80/fusetest_Z80/FuseMemoryDatum.h create mode 100644 Z80/fusetest_Z80/FuseRegisterState.cpp create mode 100644 Z80/fusetest_Z80/FuseRegisterState.h create mode 100644 Z80/fusetest_Z80/FuseTest.cpp create mode 100644 Z80/fusetest_Z80/FuseTest.h create mode 100644 Z80/fusetest_Z80/FuseTestEvent.cpp create mode 100644 Z80/fusetest_Z80/FuseTestEvent.h create mode 100644 Z80/fusetest_Z80/FuseTestEvents.cpp create mode 100644 Z80/fusetest_Z80/FuseTestEvents.h create mode 100644 Z80/fusetest_Z80/FuseTestRunner.cpp create mode 100644 Z80/fusetest_Z80/FuseTestRunner.h create mode 100644 Z80/fusetest_Z80/FuseTestSuite.cpp create mode 100644 Z80/fusetest_Z80/FuseTestSuite.h create mode 100644 Z80/fusetest_Z80/FuseTests.cpp create mode 100644 Z80/fusetest_Z80/FuseTests.h create mode 100644 Z80/fusetest_Z80/fuse-tests/README create mode 100644 Z80/fusetest_Z80/fuse-tests/tests.expected create mode 100644 Z80/fusetest_Z80/fuse-tests/tests.in create mode 100644 Z80/fusetest_Z80/fusetest_Z80.vcxproj create mode 100644 Z80/fusetest_Z80/fusetest_Z80.vcxproj.filters create mode 100644 Z80/fusetest_Z80/packages.config create mode 100644 Z80/fusetest_Z80/stdafx.cpp create mode 100644 Z80/fusetest_Z80/stdafx.h create mode 100644 Z80/fusetest_Z80/tests.cpp diff --git a/EightBit.sln b/EightBit.sln index c1f0b64..a686de2 100644 --- a/EightBit.sln +++ b/EightBit.sln @@ -13,6 +13,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Z80", "Z80\src\Z80.vcxproj" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test_Z80", "Z80\test\test_Z80.vcxproj", "{F8EE7116-0D75-4C82-BA9E-409F69F5D47C}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "fusetest_Z80", "Z80\fusetest_Z80\fusetest_Z80.vcxproj", "{618F7A88-A262-4071-A6F1-FEB3A181B541}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -61,6 +63,14 @@ Global {F8EE7116-0D75-4C82-BA9E-409F69F5D47C}.Release|x64.Build.0 = Release|x64 {F8EE7116-0D75-4C82-BA9E-409F69F5D47C}.Release|x86.ActiveCfg = Release|Win32 {F8EE7116-0D75-4C82-BA9E-409F69F5D47C}.Release|x86.Build.0 = Release|Win32 + {618F7A88-A262-4071-A6F1-FEB3A181B541}.Debug|x64.ActiveCfg = Debug|x64 + {618F7A88-A262-4071-A6F1-FEB3A181B541}.Debug|x64.Build.0 = Debug|x64 + {618F7A88-A262-4071-A6F1-FEB3A181B541}.Debug|x86.ActiveCfg = Debug|Win32 + {618F7A88-A262-4071-A6F1-FEB3A181B541}.Debug|x86.Build.0 = Debug|Win32 + {618F7A88-A262-4071-A6F1-FEB3A181B541}.Release|x64.ActiveCfg = Release|x64 + {618F7A88-A262-4071-A6F1-FEB3A181B541}.Release|x64.Build.0 = Release|x64 + {618F7A88-A262-4071-A6F1-FEB3A181B541}.Release|x86.ActiveCfg = Release|Win32 + {618F7A88-A262-4071-A6F1-FEB3A181B541}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Z80/fusetest_Z80/FuseExpectedTestResult.cpp b/Z80/fusetest_Z80/FuseExpectedTestResult.cpp new file mode 100644 index 0000000..bc25428 --- /dev/null +++ b/Z80/fusetest_Z80/FuseExpectedTestResult.cpp @@ -0,0 +1,48 @@ +#include "stdafx.h" +#include "FuseExpectedTestResult.h" + +#include + +/* +75 + 0 MC 0000 + 4 MR 0000 75 + 4 MC a169 + 7 MW a169 69 +0200 cf98 90d8 a169 0000 0000 0000 0000 0000 0000 0000 0001 0000 +00 01 0 0 0 0 7 +a169 69 -1 +*/ +void Fuse::ExpectedTestResult::read(std::ifstream& file) { + + finish = false; + do { + std::getline(file, description); + finish = file.eof(); + } while (description.empty() && !finish); + + if (finish) + return; + + events.read(file); + registerState.read(file); + + std::string line; + std::getline(file, line); + + if (!line.empty()) + throw std::logic_error("EOL swallow failure!!"); + + do { + auto before = file.tellg(); + std::getline(file, line); + if (!line.empty()) { + + file.seekg(before); + + MemoryDatum datum; + datum.read(file); + memoryData.push_back(datum); + } + } while (!line.empty()); +} \ No newline at end of file diff --git a/Z80/fusetest_Z80/FuseExpectedTestResult.h b/Z80/fusetest_Z80/FuseExpectedTestResult.h new file mode 100644 index 0000000..29bf4ee --- /dev/null +++ b/Z80/fusetest_Z80/FuseExpectedTestResult.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include + +#include "FuseTestEvents.h" +#include "FuseRegisterState.h" +#include "FuseMemoryDatum.h" + +namespace Fuse { + class ExpectedTestResult + { + public: + std::string description; + TestEvents events; + RegisterState registerState; + std::vector memoryData; + + bool finish; + + ExpectedTestResult() + : finish(false) {} + + void read(std::ifstream& file); + }; +} \ No newline at end of file diff --git a/Z80/fusetest_Z80/FuseExpectedTestResults.cpp b/Z80/fusetest_Z80/FuseExpectedTestResults.cpp new file mode 100644 index 0000000..224654b --- /dev/null +++ b/Z80/fusetest_Z80/FuseExpectedTestResults.cpp @@ -0,0 +1,20 @@ +#include "stdafx.h" +#include "FuseExpectedTestResults.h" + +void Fuse::ExpectedTestResults::read(std::ifstream& file) { + bool finished = false; + while (!file.eof()) { + ExpectedTestResult result; + result.read(file); + finished = result.finish; + if (!finished) + results[result.description] = result; + } +} + +void Fuse::ExpectedTestResults::read(std::string path) { + std::ifstream file; + file >> std::hex; + file.open(path); + read(file); +} diff --git a/Z80/fusetest_Z80/FuseExpectedTestResults.h b/Z80/fusetest_Z80/FuseExpectedTestResults.h new file mode 100644 index 0000000..916f41a --- /dev/null +++ b/Z80/fusetest_Z80/FuseExpectedTestResults.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include + +#include "FuseExpectedTestResult.h" + +namespace Fuse { + class ExpectedTestResults { + private: + std::map results; + + void read(std::ifstream& file); + + public: + void read(std::string path); + const std::map& container() const { + return results; + } + }; +} \ No newline at end of file diff --git a/Z80/fusetest_Z80/FuseMemoryDatum.cpp b/Z80/fusetest_Z80/FuseMemoryDatum.cpp new file mode 100644 index 0000000..871e8ef --- /dev/null +++ b/Z80/fusetest_Z80/FuseMemoryDatum.cpp @@ -0,0 +1,20 @@ +#include "stdafx.h" +#include "FuseMemoryDatum.h" + +void Fuse::MemoryDatum::read(std::ifstream& file) { + int begin; + file >> begin; + finish = (begin == -1); + if (finish) + return; + + address = begin; + int byte; + bool completed = false; + do { + file >> byte; + completed = (byte == -1); + if (!completed) + bytes.push_back(byte); + } while (!completed); +} \ No newline at end of file diff --git a/Z80/fusetest_Z80/FuseMemoryDatum.h b/Z80/fusetest_Z80/FuseMemoryDatum.h new file mode 100644 index 0000000..0cf4c33 --- /dev/null +++ b/Z80/fusetest_Z80/FuseMemoryDatum.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include +#include + +namespace Fuse { + class MemoryDatum { + private: + bool finish; + + public: + int address; + std::vector bytes; + + MemoryDatum() + : address(-1), + finish(false) {} + + bool finished() const { return finish; } + void read(std::ifstream& file); + }; +} \ No newline at end of file diff --git a/Z80/fusetest_Z80/FuseRegisterState.cpp b/Z80/fusetest_Z80/FuseRegisterState.cpp new file mode 100644 index 0000000..d04dccb --- /dev/null +++ b/Z80/fusetest_Z80/FuseRegisterState.cpp @@ -0,0 +1,32 @@ +#include "stdafx.h" +#include "FuseRegisterState.h" + +Fuse::RegisterState::RegisterState() +: registers(NUMBER_OF_REGISTERS) { +} + +void Fuse::RegisterState::read(std::ifstream& file) { + readExternal(file); + readInternal(file); +} + +void Fuse::RegisterState::readExternal(std::ifstream& file) { + for (int idx = 0; idx < registers.size(); ++idx) { + int input; + file >> input; + registers[idx].word = input; + } +} + +void Fuse::RegisterState::readInternal(std::ifstream& file) { + file >> i; + file >> r; + file >> iff1; + file >> iff2; + file >> im; + file >> halted; + + file >> std::dec; + file >> tstates; + file >> std::hex; +} diff --git a/Z80/fusetest_Z80/FuseRegisterState.h b/Z80/fusetest_Z80/FuseRegisterState.h new file mode 100644 index 0000000..4f36b65 --- /dev/null +++ b/Z80/fusetest_Z80/FuseRegisterState.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include + +#include "Memory.h" + +namespace Fuse { + class RegisterState { + public: + enum { + AF, BC, DE, HL, AF_, BC_, DE_, HL_, IX, IY, SP, PC, MEMPTR, NUMBER_OF_REGISTERS + }; + std::vector registers; + int i, r; + bool iff1, iff2; + int im; + bool halted; + int tstates; + + public: + RegisterState(); + + void read(std::ifstream& file); + void readInternal(std::ifstream& file); + void readExternal(std::ifstream& file); + }; +} \ No newline at end of file diff --git a/Z80/fusetest_Z80/FuseTest.cpp b/Z80/fusetest_Z80/FuseTest.cpp new file mode 100644 index 0000000..1a8ca4a --- /dev/null +++ b/Z80/fusetest_Z80/FuseTest.cpp @@ -0,0 +1,25 @@ +#include "stdafx.h" +#include "FuseTest.h" + +void Fuse::Test::read(std::ifstream& file) { + + finish = false; + do { + std::getline(file, description); + finish = file.eof(); + } while (description.empty() && !finish); + + if (finish) + return; + + registerState.read(file); + + bool complete = false; + do { + MemoryDatum memoryDatum; + memoryDatum.read(file); + complete = memoryDatum.finished(); + if (!complete) + memoryData.push_back(memoryDatum); + } while (!complete); +} diff --git a/Z80/fusetest_Z80/FuseTest.h b/Z80/fusetest_Z80/FuseTest.h new file mode 100644 index 0000000..35aa4cd --- /dev/null +++ b/Z80/fusetest_Z80/FuseTest.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include +#include + +#include "FuseMemoryDatum.h" +#include "FuseRegisterState.h" + +namespace Fuse { + class Test { + public: + std::string description; + RegisterState registerState; + std::vector memoryData; + bool finish = false; + + void read(std::ifstream& file); + }; +} \ No newline at end of file diff --git a/Z80/fusetest_Z80/FuseTestEvent.cpp b/Z80/fusetest_Z80/FuseTestEvent.cpp new file mode 100644 index 0000000..57cc271 --- /dev/null +++ b/Z80/fusetest_Z80/FuseTestEvent.cpp @@ -0,0 +1,34 @@ +#include "stdafx.h" +#include "FuseTestEvent.h" + +void Fuse::TestEvent::read(std::ifstream& file) { + + auto prior = file.tellg(); + + std::string line; + std::getline(file, line); + std::stringstream in(line); + + in >> cycles; + in >> specifier; + + in >> std::hex; + + valid = true; + if (specifier == "MR" || specifier == "MW") { + in >> address; + in >> value; + } else if (specifier == "MC" || specifier == "PC") { + in >> address; + value = -1; + } else if (specifier == "PR" || specifier == "PW") { + in >> address; + in >> value; + } else { + valid = false; + } + + if (!valid) { + file.seekg(prior); + } +} \ No newline at end of file diff --git a/Z80/fusetest_Z80/FuseTestEvent.h b/Z80/fusetest_Z80/FuseTestEvent.h new file mode 100644 index 0000000..8ea1c65 --- /dev/null +++ b/Z80/fusetest_Z80/FuseTestEvent.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +namespace Fuse { + class TestEvent + { + public: + bool valid; + std::string specifier; + int cycles, address, value; + + TestEvent() + : valid(false), + cycles(-1), address(-1), value(-1) { + } + + void read(std::ifstream& file); + }; +} \ No newline at end of file diff --git a/Z80/fusetest_Z80/FuseTestEvents.cpp b/Z80/fusetest_Z80/FuseTestEvents.cpp new file mode 100644 index 0000000..081393b --- /dev/null +++ b/Z80/fusetest_Z80/FuseTestEvents.cpp @@ -0,0 +1,13 @@ +#include "stdafx.h" +#include "FuseTestEvents.h" + +void Fuse::TestEvents::read(std::ifstream& file) { + bool complete = false; + do { + TestEvent event; + event.read(file); + complete = !event.valid; + if (!complete) + events.push_back(event); + } while (!complete); +} diff --git a/Z80/fusetest_Z80/FuseTestEvents.h b/Z80/fusetest_Z80/FuseTestEvents.h new file mode 100644 index 0000000..9463cdb --- /dev/null +++ b/Z80/fusetest_Z80/FuseTestEvents.h @@ -0,0 +1,13 @@ +#pragma once + +#include +#include "FuseTestEvent.h" + +namespace Fuse { + class TestEvents { + public: + std::vector events; + + void read(std::ifstream& file); + }; +} \ No newline at end of file diff --git a/Z80/fusetest_Z80/FuseTestRunner.cpp b/Z80/fusetest_Z80/FuseTestRunner.cpp new file mode 100644 index 0000000..6560691 --- /dev/null +++ b/Z80/fusetest_Z80/FuseTestRunner.cpp @@ -0,0 +1,340 @@ +#include "stdafx.h" +#include "FuseTestRunner.h" +#include "Disassembler.h" + +Fuse::TestRunner::TestRunner(const Test& test, const ExpectedTestResult& expected) +: m_test(test), + m_expected(expected), + m_memory(0xffff), + m_cpu(m_memory, m_ports), + m_failed(false), + m_unimplemented(false) { + m_memory.clear(); + m_cpu.initialise(); +} + +// + +void Fuse::TestRunner::initialise() { + initialiseRegisters(); + initialiseMemory(); +} + +void Fuse::TestRunner::initialiseRegisters() { + + const auto& testState = m_test.registerState; + const auto& inputRegisters = testState.registers; + + m_cpu.AF() = inputRegisters[Fuse::RegisterState::AF_]; + m_cpu.BC() = inputRegisters[Fuse::RegisterState::BC_]; + m_cpu.DE() = inputRegisters[Fuse::RegisterState::DE_]; + m_cpu.HL() = inputRegisters[Fuse::RegisterState::HL_]; + m_cpu.exx(); + m_cpu.exxAF(); + m_cpu.AF() = inputRegisters[Fuse::RegisterState::AF]; + m_cpu.BC() = inputRegisters[Fuse::RegisterState::BC]; + m_cpu.DE() = inputRegisters[Fuse::RegisterState::DE]; + m_cpu.HL() = inputRegisters[Fuse::RegisterState::HL]; + + m_cpu.IX() = inputRegisters[Fuse::RegisterState::IX]; + m_cpu.IY() = inputRegisters[Fuse::RegisterState::IY]; + + m_cpu.setStackPointer(inputRegisters[Fuse::RegisterState::SP]); + m_cpu.setProgramCounter(inputRegisters[Fuse::RegisterState::PC]); + + m_cpu.MEMPTR() = inputRegisters[Fuse::RegisterState::MEMPTR]; + + m_cpu.IV() = testState.i; + m_cpu.REFRESH() = testState.r; + m_cpu.IFF1() = testState.iff1; + m_cpu.IFF2() = testState.iff2; + m_cpu.IM() = testState.im; +} + +void Fuse::TestRunner::initialiseMemory() { + for (auto memoryDatum : m_test.memoryData) { + auto address = memoryDatum.address; + auto bytes = memoryDatum.bytes; + for (int i = 0; i < bytes.size(); ++i) { + m_memory.ADDRESS().word = address + i; + m_memory.reference() = bytes[i]; + } + } +} + +// + +void Fuse::TestRunner::check() { + checkregisters(); + checkMemory(); +} + +void Fuse::TestRunner::dumpDifference(const std::string& description, uint8_t actual, uint8_t expected) const { + std::cerr + << "**** " << description << ", Expected: " + << EightBit::Disassembler::hex(expected) + << ", Got: " + << EightBit::Disassembler::hex(actual) + << std::endl; +} + +void Fuse::TestRunner::dumpDifference( + const std::string& highDescription, + const std::string& lowDescription, + EightBit::register16_t actual, EightBit::register16_t expected) const { + + auto expectedHigh = expected.high; + auto expectedLow = expected.low; + + auto actualHigh = actual.high; + auto actualLow = actual.low; + + if (expectedHigh != actualHigh) + dumpDifference(highDescription, actualHigh, expectedHigh); + + if (expectedLow != actualLow) + dumpDifference(lowDescription, actualLow, expectedLow); +} + +void Fuse::TestRunner::checkregisters() { + + const auto& expectedState = m_expected.registerState; + const auto& expectedRegisters = expectedState.registers; + + auto af = m_cpu.AF().word == expectedRegisters[Fuse::RegisterState::AF].word; + auto bc = m_cpu.BC().word == expectedRegisters[Fuse::RegisterState::BC].word; + auto de = m_cpu.DE().word == expectedRegisters[Fuse::RegisterState::DE].word; + auto hl = m_cpu.HL().word == expectedRegisters[Fuse::RegisterState::HL].word; + m_cpu.exx(); + m_cpu.exxAF(); + auto af_ = m_cpu.AF().word == expectedRegisters[Fuse::RegisterState::AF_].word; + auto bc_ = m_cpu.BC().word == expectedRegisters[Fuse::RegisterState::BC_].word; + auto de_ = m_cpu.DE().word == expectedRegisters[Fuse::RegisterState::DE_].word; + auto hl_ = m_cpu.HL().word == expectedRegisters[Fuse::RegisterState::HL_].word; + + auto ix = m_cpu.IX().word == expectedRegisters[Fuse::RegisterState::IX].word; + auto iy = m_cpu.IY().word == expectedRegisters[Fuse::RegisterState::IY].word; + + auto sp = m_cpu.getStackPointer().word == expectedRegisters[Fuse::RegisterState::SP].word; + auto pc = m_cpu.getProgramCounter().word == expectedRegisters[Fuse::RegisterState::PC].word; + + auto memptr = m_cpu.MEMPTR().word == expectedRegisters[Fuse::RegisterState::MEMPTR].word; + + auto iv = m_cpu.IV() == expectedState.i; + auto refresh = m_cpu.REFRESH() == expectedState.r; + auto iff1 = m_cpu.IFF1() == expectedState.iff1; + auto iff2 = m_cpu.IFF2() == expectedState.iff2; + auto im = m_cpu.IM() == expectedState.im; + + // And back again, so the following works as expected... + m_cpu.exx(); + m_cpu.exxAF(); + + auto success = + af && bc && de && hl + && af_ && bc_ && de_ && hl_ + && ix && iy + && sp && pc + && iv && refresh + && iff1 && iff2 + && im + && memptr; + + if (!success) { + m_failed = true; + std::cerr << "**** Failed test (Register): " << m_test.description << std::endl; + + if (!af) { + auto expectedA = expectedRegisters[Fuse::RegisterState::AF].high; + auto gotA = m_cpu.A(); + if (expectedA != gotA) + dumpDifference("A", gotA, expectedA); + + auto expectedF = expectedRegisters[Fuse::RegisterState::AF].low; + auto gotF = m_cpu.F(); + if (expectedF != gotF) { + std::cerr + << "**** F, Expected: " + << EightBit::Disassembler::flags(expectedF) + << ", Got: " + << EightBit::Disassembler::flags(gotF) + << std::endl; + } + } + + if (!bc) { + auto expectedWord = expectedRegisters[Fuse::RegisterState::BC]; + auto actualWord = m_cpu.BC(); + dumpDifference("B", "C", actualWord, expectedWord); + } + + if (!de) { + auto expectedWord = expectedRegisters[Fuse::RegisterState::DE]; + auto actualWord = m_cpu.DE(); + dumpDifference("D", "E", actualWord, expectedWord); + } + + if (!hl) { + auto expectedWord = expectedRegisters[Fuse::RegisterState::HL]; + auto actualWord = m_cpu.HL(); + dumpDifference("H", "L", actualWord, expectedWord); + } + + if (!ix) { + auto expectedWord = expectedRegisters[Fuse::RegisterState::IX]; + auto actualWord = m_cpu.IX(); + dumpDifference("IXH", "IXL", actualWord, expectedWord); + } + + if (!iy) { + auto expectedWord = expectedRegisters[Fuse::RegisterState::IY]; + auto actualWord = m_cpu.IY(); + dumpDifference("IYH", "IYL", actualWord, expectedWord); + } + + if (!sp) { + auto expectedWord = expectedRegisters[Fuse::RegisterState::SP]; + auto actualWord = m_cpu.getStackPointer(); + dumpDifference("SPH", "SPL", actualWord, expectedWord); + } + + if (!pc) { + auto expectedWord = expectedRegisters[Fuse::RegisterState::PC]; + auto actualWord = m_cpu.getProgramCounter(); + dumpDifference("PCH", "PCL", actualWord, expectedWord); + } + + if (!memptr) { + auto expectedWord = expectedRegisters[Fuse::RegisterState::MEMPTR]; + auto actualWord = m_cpu.MEMPTR(); + dumpDifference("MEMPTRH", "MEMPTRL", actualWord, expectedWord); + } + + m_cpu.exxAF(); + m_cpu.exx(); + + if (!af_) { + auto expectedA_ = expectedRegisters[Fuse::RegisterState::AF_].high; + auto gotA_ = m_cpu.A(); + if (expectedA_ != gotA_) + dumpDifference("A'", gotA_, expectedA_); + + auto expectedF_ = expectedRegisters[Fuse::RegisterState::AF_].low; + auto gotF_ = m_cpu.F(); + if (expectedF_ != gotF_) { + std::cerr + << "**** F', Expected: " + << EightBit::Disassembler::flags(expectedF_) + << ", Got: " + << EightBit::Disassembler::flags(gotF_) + << std::endl; + } + } + + if (!bc_) { + auto expectedWord = expectedRegisters[Fuse::RegisterState::BC_]; + auto actualWord = m_cpu.BC(); + dumpDifference("B'", "C'", actualWord, expectedWord); + } + + if (!de_) { + auto expectedWord = expectedRegisters[Fuse::RegisterState::DE_]; + auto actualWord = m_cpu.DE(); + dumpDifference("D'", "E'", actualWord, expectedWord); + } + + if (!hl_) { + auto expectedWord = expectedRegisters[Fuse::RegisterState::HL_]; + auto actualWord = m_cpu.HL(); + dumpDifference("H'", "L'", actualWord, expectedWord); + } + + if (!iv) { + std::cerr + << "**** IV, Expected: " + << EightBit::Disassembler::hex((uint8_t)expectedState.i) + << ", Got: " + << EightBit::Disassembler::hex(m_cpu.IV()) + << std::endl; + } + + if (!refresh) { + std::cerr + << "**** R, Expected: " + << EightBit::Disassembler::hex((uint8_t)expectedState.r) + << ", Got: " + << EightBit::Disassembler::hex(m_cpu.REFRESH()) + << std::endl; + } + + if (!iff1) { + std::cerr + << "**** IFF1, Expected: " + << (bool)expectedState.iff1 + << ", Got: " + << m_cpu.IFF1() + << std::endl; + } + + if (!iff2) { + std::cerr + << "**** IFF2, Expected: " + << (bool)expectedState.iff2 + << ", Got: " + << m_cpu.IFF2() + << std::endl; + } + + if (!im) { + std::cerr + << "**** IM, Expected: " + << expectedState.im + << ", Got: " + << m_cpu.IM() + << std::endl; + } + } +} + +void Fuse::TestRunner::checkMemory() { + + bool first = true; + + for (auto memoryDatum : m_expected.memoryData) { + auto bytes = memoryDatum.bytes; + for (int i = 0; i < bytes.size(); ++i) { + auto expected = bytes[i]; + uint16_t address = memoryDatum.address + i; + auto actual = m_cpu.getMemory().peek(address); + if (expected != actual) { + m_failed = true; + if (first) { + first = false; + std::cerr << "**** Failed test (Memory): " << m_test.description << std::endl; + } + std::cerr + << "**** Difference: " + << "Address: " << EightBit::Disassembler::hex(address) + << " Expected: " << EightBit::Disassembler::hex(expected) + << " Actual: " << EightBit::Disassembler::hex(actual) + << std::endl; + } + } + } +} + +void Fuse::TestRunner::run() { + + initialise(); + auto allowedCycles = m_test.registerState.tstates; + try { + auto cycles = 0; + do { + cycles += m_cpu.step(); + } while (allowedCycles > cycles); + check(); + } catch (std::logic_error& error) { + m_unimplemented = true; + std::cerr << "**** Error: " << error.what() << std::endl; + } +} diff --git a/Z80/fusetest_Z80/FuseTestRunner.h b/Z80/fusetest_Z80/FuseTestRunner.h new file mode 100644 index 0000000..3842638 --- /dev/null +++ b/Z80/fusetest_Z80/FuseTestRunner.h @@ -0,0 +1,44 @@ +#pragma once + +#include "FuseTest.h" +#include "FuseExpectedTestResult.h" + +#include "Memory.h" +#include "InputOutput.h" +#include "Z80.h" + +namespace Fuse { + class TestRunner { + private: + const Test& m_test; + const ExpectedTestResult& m_expected; + + bool m_failed; + bool m_unimplemented; + + EightBit::Memory m_memory; + EightBit::InputOutput m_ports; + EightBit::Z80 m_cpu; + + void initialise(); + void initialiseRegisters(); + void initialiseMemory(); + + void check(); + void checkregisters(); + void checkMemory(); + + void dumpDifference(const std::string& description, uint8_t high, uint8_t low) const; + void dumpDifference( + const std::string& highDescription, + const std::string& lowDescription, + EightBit::register16_t actual, EightBit::register16_t expected) const; + + public: + TestRunner(const Test& test, const ExpectedTestResult& expected); + + void run(); + bool failed() const { return m_failed; } + bool unimplemented() const { return m_unimplemented; } + }; +} \ No newline at end of file diff --git a/Z80/fusetest_Z80/FuseTestSuite.cpp b/Z80/fusetest_Z80/FuseTestSuite.cpp new file mode 100644 index 0000000..365b7c0 --- /dev/null +++ b/Z80/fusetest_Z80/FuseTestSuite.cpp @@ -0,0 +1,31 @@ +#include "stdafx.h" +#include "FuseTestSuite.h" +#include "FuseTestRunner.h" + +Fuse::TestSuite::TestSuite(std::string path) { + m_tests.read(path + ".in"); + m_results.read(path + ".expected"); +} + +void Fuse::TestSuite::run() { + auto failedCount = 0; + auto unimplementedCount = 0; + for (auto test : m_tests.container()) { + + auto key = test.first; + std::cout << "** Checking: " << key << std::endl; + + auto input = test.second; + auto result = m_results.container().find(key)->second; + + Fuse::TestRunner runner(input, result); + + runner.run(); + if (runner.failed()) + ++failedCount; + if (runner.unimplemented()) + ++unimplementedCount; + } + std::cout << "+++ Failed test count: " << failedCount << std::endl; + std::cout << "+++ Unimplemented test count: " << unimplementedCount << std::endl; +} \ No newline at end of file diff --git a/Z80/fusetest_Z80/FuseTestSuite.h b/Z80/fusetest_Z80/FuseTestSuite.h new file mode 100644 index 0000000..c348e6c --- /dev/null +++ b/Z80/fusetest_Z80/FuseTestSuite.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +#include "FuseTests.h" +#include "FuseExpectedTestResults.h" + +namespace Fuse { + class TestSuite { + private: + Tests m_tests; + ExpectedTestResults m_results; + + public: + TestSuite(std::string path); + + void run(); + }; +} \ No newline at end of file diff --git a/Z80/fusetest_Z80/FuseTests.cpp b/Z80/fusetest_Z80/FuseTests.cpp new file mode 100644 index 0000000..8e20f92 --- /dev/null +++ b/Z80/fusetest_Z80/FuseTests.cpp @@ -0,0 +1,20 @@ +#include "stdafx.h" +#include "FuseTests.h" + +void Fuse::Tests::read(std::ifstream& file) { + bool finished = false; + while (!file.eof()) { + Test test; + test.read(file); + finished = test.finish; + if (!finished) + tests[test.description] = test; + } +} + +void Fuse::Tests::read(std::string path) { + std::ifstream file; + file >> std::hex; + file.open(path); + read(file); +} diff --git a/Z80/fusetest_Z80/FuseTests.h b/Z80/fusetest_Z80/FuseTests.h new file mode 100644 index 0000000..700f42f --- /dev/null +++ b/Z80/fusetest_Z80/FuseTests.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include +#include + +#include "FuseTest.h" + +namespace Fuse { + class Tests { + private: + std::map tests; + + void read(std::ifstream& file); + + public: + void read(std::string path); + const std::map& container() const { return tests; } + }; +} diff --git a/Z80/fusetest_Z80/fuse-tests/README b/Z80/fusetest_Z80/fuse-tests/README new file mode 100644 index 0000000..33c6309 --- /dev/null +++ b/Z80/fusetest_Z80/fuse-tests/README @@ -0,0 +1,79 @@ +File formats +============ + +tests.in +-------- + +Each test has the format: + + +AF BC DE HL AF' BC' DE' HL' IX IY SP PC MEMPTR +I R IFF1 IFF2 IM + + specifies whether the Z80 is halted. + specifies the number of tstates to run the test for, in + decimal; the number actually executed may be higher, as the final + instruction is allowed to complete. + +Then followed by lines specifying the initial memory setup. Each has +the format: + + ... -1 + +eg + +1234 56 78 9a -1 + +says to put 0x56 at 0x1234, 0x78 at 0x1235 and 0x9a at 0x1236. + +Finally, -1 to end the test. Blank lines may follow before the next test. + +tests.expected +-------------- + +Each test output starts with the test description, followed by a list +of 'events': each has the format + +