From 6a59bfbcd8057c30d965d09eec66119ebab1437b Mon Sep 17 00:00:00 2001 From: Adrian Conlon Date: Sun, 10 Oct 2021 21:26:30 +0100 Subject: [PATCH] First stab at using the Harte randomised processor tests. Some failures detected in the M6502 run. Signed-off-by: Adrian Conlon --- M6502/HarteTest_6502/HarteTest_6502.vcxproj | 189 ++++++++++++++++++ .../HarteTest_6502.vcxproj.filters | 56 ++++++ M6502/HarteTest_6502/TestRunner.cpp | 138 +++++++++++++ M6502/HarteTest_6502/TestRunner.h | 50 +++++ M6502/HarteTest_6502/json_t.cpp | 53 +++++ M6502/HarteTest_6502/json_t.h | 24 +++ M6502/HarteTest_6502/opcode_test_suite_t.cpp | 27 +++ M6502/HarteTest_6502/opcode_test_suite_t.h | 21 ++ M6502/HarteTest_6502/state_t.cpp | 40 ++++ M6502/HarteTest_6502/state_t.h | 37 ++++ M6502/HarteTest_6502/stdafx.cpp | 1 + M6502/HarteTest_6502/stdafx.h | 14 ++ M6502/HarteTest_6502/test_t.cpp | 48 +++++ M6502/HarteTest_6502/test_t.h | 38 ++++ M6502/HarteTest_6502/tests.cpp | 32 +++ 15 files changed, 768 insertions(+) create mode 100644 M6502/HarteTest_6502/HarteTest_6502.vcxproj create mode 100644 M6502/HarteTest_6502/HarteTest_6502.vcxproj.filters create mode 100644 M6502/HarteTest_6502/TestRunner.cpp create mode 100644 M6502/HarteTest_6502/TestRunner.h create mode 100644 M6502/HarteTest_6502/json_t.cpp create mode 100644 M6502/HarteTest_6502/json_t.h create mode 100644 M6502/HarteTest_6502/opcode_test_suite_t.cpp create mode 100644 M6502/HarteTest_6502/opcode_test_suite_t.h create mode 100644 M6502/HarteTest_6502/state_t.cpp create mode 100644 M6502/HarteTest_6502/state_t.h create mode 100644 M6502/HarteTest_6502/stdafx.cpp create mode 100644 M6502/HarteTest_6502/stdafx.h create mode 100644 M6502/HarteTest_6502/test_t.cpp create mode 100644 M6502/HarteTest_6502/test_t.h create mode 100644 M6502/HarteTest_6502/tests.cpp diff --git a/M6502/HarteTest_6502/HarteTest_6502.vcxproj b/M6502/HarteTest_6502/HarteTest_6502.vcxproj new file mode 100644 index 0000000..29ecc21 --- /dev/null +++ b/M6502/HarteTest_6502/HarteTest_6502.vcxproj @@ -0,0 +1,189 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {894eb6ef-4c8d-4401-bbaa-aba05a021abb} + HarteTest_6502 + 10.0 + HarteTest_6502 + + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + ..\inc;..\..\inc;C:\Libraries\boost_1_77_0;$(IncludePath) + + + false + ..\inc;..\..\inc;C:\Libraries\boost_1_77_0;$(IncludePath) + + + true + ..\inc;..\..\inc;C:\Libraries\boost_1_77_0;$(IncludePath) + C:\Libraries\boost_1_77_0\lib64-msvc-14.2;$(LibraryPath) + + + false + ..\inc;..\..\inc;C:\Libraries\boost_1_77_0;$(IncludePath) + C:\Libraries\boost_1_77_0\lib64-msvc-14.2;$(LibraryPath) + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + Use + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + Use + + + Console + true + true + true + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + Use + + + Console + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + Use + + + Console + true + true + true + + + + + + + + Create + Create + Create + Create + + + + + + + + + + + + + + + + {a9c24bd9-0cb4-4c84-b09b-46b815f9da47} + + + {d8726a1b-bbfe-47ef-9860-26b90140ba66} + + + + + + \ No newline at end of file diff --git a/M6502/HarteTest_6502/HarteTest_6502.vcxproj.filters b/M6502/HarteTest_6502/HarteTest_6502.vcxproj.filters new file mode 100644 index 0000000..9cf163c --- /dev/null +++ b/M6502/HarteTest_6502/HarteTest_6502.vcxproj.filters @@ -0,0 +1,56 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/M6502/HarteTest_6502/TestRunner.cpp b/M6502/HarteTest_6502/TestRunner.cpp new file mode 100644 index 0000000..8c78cf1 --- /dev/null +++ b/M6502/HarteTest_6502/TestRunner.cpp @@ -0,0 +1,138 @@ +#include "stdafx.h" +#include "TestRunner.h" + +#include +#include + +TestRunner::TestRunner(const test_t& test) +: m_test(test) {} + +EightBit::MemoryMapping TestRunner::mapping(uint16_t address) noexcept { + return { RAM(), 0x0000, 0xffff, EightBit::MemoryMapping::AccessLevel::ReadWrite }; +} + +void TestRunner::raisePOWER() { + EightBit::Bus::raisePOWER(); + CPU().raisePOWER(); + CPU().raiseRESET(); + CPU().raiseINT(); + CPU().raiseNMI(); + CPU().raiseSO(); + CPU().raiseRDY(); +} + +void TestRunner::lowerPOWER() { + CPU().lowerPOWER(); + EightBit::Bus::lowerPOWER(); +} + +void TestRunner::addActualEvent(test_t::action action, uint16_t address, uint8_t value) { + m_actualEvents.push_back( { address, value, action } ); +} + +void TestRunner::initialise() { + + ReadByte.connect([this](EightBit::EventArgs&) { + addActualEvent(test_t::action::read, ADDRESS().word, DATA()); + }); + + WrittenByte.connect([this](EightBit::EventArgs&) { + addActualEvent(test_t::action::write, ADDRESS().word, DATA()); + }); + +} + +void TestRunner::raise(std::string what, uint16_t expected, uint16_t actual) { + std::cout + << "** Failure: " << what + << ": expected: " << EightBit::Disassembly::dump_WordValue(expected) + << ", actual: " << EightBit::Disassembly::dump_WordValue(actual) + << "\n"; +} + +void TestRunner::raise(std::string what, uint8_t expected, uint8_t actual) { + std::cout + << "** Failure: " << what + << ": expected: " << EightBit::Disassembly::dump_ByteValue(expected) + << "(" << EightBit::Disassembly::dump_Flags(expected) << ")" + << ", actual: " << EightBit::Disassembly::dump_ByteValue(actual) + << "(" << EightBit::Disassembly::dump_Flags(actual) << ")" + << "\n"; +} + +void TestRunner::raise(std::string what, test_t::action expected, test_t::action actual) { + std::cout + << "** Failure: " << what + << ": expected: " << test_t::to_string(expected) + << ", actual: " << test_t::to_string(actual) + << "\n"; +} + +void TestRunner::initialiseState() { + + const auto& starting = test().initial_state(); + + CPU().PC().word = starting.pc(); + CPU().S() = starting.s(); + CPU().A() = starting.a(); + CPU().X() = starting.x(); + CPU().Y() = starting.y(); + CPU().P() = starting.p(); + const auto& ram = starting.ram(); + for (const auto& entry : ram) { + const auto [address, value] = entry; + RAM().poke(address, value); + } + + m_actualEvents.clear(); +} + +void TestRunner::verifyState() { + + const auto& finished = test().final_state(); + + const auto& expected_events = test().cycles(); + const auto& actual_events = m_actualEvents; + if (expected_events.size() != actual_events.size()) { + //std::cout << "** event count mismatch" << "\n"; + return; + } + + for (int i = 0; i < expected_events.size(); ++i) { + const auto& expected = expected_events[i]; + const auto [expectedAddress, expectedContents, expectedAction] = expected; + const auto& actual = actual_events.at(i); // actual could be less than expected + const auto [actualAddress, actualContents, actualAction] = actual; + check("Event action", expectedAction, actualAction); + check("Event address", expectedAddress, actualAddress); + check("Event contents", expectedContents, actualContents); + } + + const auto pc_good = check("PC", finished.pc(), CPU().PC().word); + const auto s_good = check("S", finished.s(), CPU().S()); + const auto a_good = check("A", finished.a(), CPU().A()); + const auto x_good = check("X", finished.x(), CPU().X()); + const auto y_good = check("Y", finished.y(), CPU().Y()); + const auto p_good = check("P", finished.p(), CPU().P()); + + const auto& ram = finished.ram(); + bool ram_problem = false; + for (const auto& entry : ram) { + const auto [address, value] = entry; + const auto ram_good = check("RAM: " + EightBit::Disassembly::dump_WordValue(address), value, RAM().peek(address)); + if (!ram_good && !ram_problem) + ram_problem = true; + } + + const auto good = pc_good && s_good && a_good && x_good && y_good && p_good && !ram_problem; + std::cout << (good ? "+" : "-"); +} + +void TestRunner::run() { + initialise(); + raisePOWER(); + initialiseState(); + const int cycles = CPU().step(); + verifyState(); + lowerPOWER(); +} diff --git a/M6502/HarteTest_6502/TestRunner.h b/M6502/HarteTest_6502/TestRunner.h new file mode 100644 index 0000000..31c85d1 --- /dev/null +++ b/M6502/HarteTest_6502/TestRunner.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include + +#include "test_t.h" + +class TestRunner final : public EightBit::Bus { +private: + EightBit::Ram m_ram = 0x10000; + EightBit::MOS6502 m_cpu = { *this }; + const test_t& m_test; + + test_t::events_t m_actualEvents; + + void initialiseState(); + void verifyState(); + + void raise(std::string what, uint16_t expected, uint16_t actual); + void raise(std::string what, uint8_t expected, uint8_t actual); + void raise(std::string what, test_t::action expected, test_t::action actual); + + template + bool check(std::string what, T expected, T actual) { + const auto success = actual == expected; + if (!success) + raise(what, expected, actual); + return success; + } + + void addActualEvent(test_t::action action, uint16_t address, uint8_t value); + +protected: + virtual EightBit::MemoryMapping mapping(uint16_t address) noexcept final; + +public: + TestRunner(const test_t& test); + + virtual void raisePOWER() final; + virtual void lowerPOWER() final; + + virtual void initialise() final; + + constexpr auto& RAM() noexcept { return m_ram; } + constexpr auto& CPU() noexcept { return m_cpu; } + constexpr const auto& test() const noexcept { return m_test; } + + void run(); +}; diff --git a/M6502/HarteTest_6502/json_t.cpp b/M6502/HarteTest_6502/json_t.cpp new file mode 100644 index 0000000..cf6c135 --- /dev/null +++ b/M6502/HarteTest_6502/json_t.cpp @@ -0,0 +1,53 @@ +#include "stdafx.h" +#include "json_t.h" +#include + +const boost::json::value& json_t::get_value(const boost::json::object& object, std::string key) { + auto* value = object.if_contains(key); + assert(value != nullptr); + return *value; +} + +int64_t json_t::get_int64(const boost::json::value& value) { + assert(value.is_number()); + assert(value.is_int64()); + return value.get_int64(); +} + +uint16_t json_t::get_uint16(const boost::json::value& value) { + return static_cast(get_int64(value)); +} + +uint8_t json_t::get_uint8(const boost::json::value& value) { + return static_cast(get_int64(value)); +} + +int64_t json_t::get_int64(const boost::json::object& object, std::string key) { + return get_int64(get_value(object, key)); +} + +uint16_t json_t::get_uint16(const boost::json::object& object, std::string key) { + return static_cast(get_int64(object, key)); +} + +uint8_t json_t::get_uint8(const boost::json::object& object, std::string key) { + return static_cast(get_int64(object, key)); +} + +const boost::json::array& json_t::get_array(const boost::json::value& value) { + assert(value.is_array()); + return value.get_array(); +} + +const boost::json::array& json_t::get_array(const boost::json::object& object, std::string key) { + return get_array(get_value(object, key)); +} + +const boost::json::string& json_t::get_string(const boost::json::value& value) { + assert(value.is_string()); + return value.get_string(); +} + +const boost::json::string& json_t::get_string(const boost::json::object& object, std::string key) { + return get_string(get_value(object, key)); +} diff --git a/M6502/HarteTest_6502/json_t.h b/M6502/HarteTest_6502/json_t.h new file mode 100644 index 0000000..3d1c00e --- /dev/null +++ b/M6502/HarteTest_6502/json_t.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +#include + +class json_t { +protected: + static const boost::json::value& get_value(const boost::json::object& object, std::string key); + + static int64_t get_int64(const boost::json::value& value); + static uint16_t get_uint16(const boost::json::value& value); + static uint8_t get_uint8(const boost::json::value& value); + + static int64_t get_int64(const boost::json::object& object, std::string key); + static uint16_t get_uint16(const boost::json::object& object, std::string key); + static uint8_t get_uint8(const boost::json::object& object, std::string key); + + static const boost::json::array& get_array(const boost::json::value& value); + static const boost::json::array& get_array(const boost::json::object& object, std::string key); + + static const boost::json::string& get_string(const boost::json::value& value); + static const boost::json::string& get_string(const boost::json::object& object, std::string key); +}; diff --git a/M6502/HarteTest_6502/opcode_test_suite_t.cpp b/M6502/HarteTest_6502/opcode_test_suite_t.cpp new file mode 100644 index 0000000..b7fb336 --- /dev/null +++ b/M6502/HarteTest_6502/opcode_test_suite_t.cpp @@ -0,0 +1,27 @@ +#include "stdafx.h" +#include "opcode_test_suite_t.h" + +#include +#include +#include + +std::string opcode_test_suite_t::read(std::string path) { + std::ifstream file(path, std::ios::in | std::ios::binary); + const auto size = std::filesystem::file_size(path); + std::string result(size, '\0'); + file.read(result.data(), size); + return result; +} + +opcode_test_suite_t::opcode_test_suite_t(std::string path) +: m_path(path) {} + +const boost::json::array& opcode_test_suite_t::get_array() const noexcept { + assert(raw().is_array()); + return raw().get_array(); +} + +void opcode_test_suite_t::load() { + const auto contents = read(path()); + m_raw = boost::json::parse(contents); +} diff --git a/M6502/HarteTest_6502/opcode_test_suite_t.h b/M6502/HarteTest_6502/opcode_test_suite_t.h new file mode 100644 index 0000000..6f2c1b1 --- /dev/null +++ b/M6502/HarteTest_6502/opcode_test_suite_t.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +class opcode_test_suite_t final { +private: + static std::string read(std::string path); + + std::string m_path; + boost::json::value m_raw; + +public: + opcode_test_suite_t(std::string path); + + constexpr const auto& path() const noexcept { return m_path; } + constexpr const auto& raw() const noexcept { return m_raw; } + const boost::json::array& get_array() const noexcept; + + void load(); +}; diff --git a/M6502/HarteTest_6502/state_t.cpp b/M6502/HarteTest_6502/state_t.cpp new file mode 100644 index 0000000..70e538b --- /dev/null +++ b/M6502/HarteTest_6502/state_t.cpp @@ -0,0 +1,40 @@ +#include "stdafx.h" +#include "state_t.h" +#include + +void state_t::initialise(const boost::json::object& serialised) { + + assert(!initialised()); + + m_pc = get_uint16(serialised, "pc"); + m_s = get_uint8(serialised, "s"); + m_a = get_uint8(serialised, "a"); + m_x = get_uint8(serialised, "x"); + m_y = get_uint8(serialised, "y"); + m_p = get_uint8(serialised, "p"); + + const auto& ram_entries = get_array(serialised, "ram"); + for (const auto& ram_entry : ram_entries) { + assert(ram_entry.is_array()); + const auto& ram_entry_array = ram_entry.as_array(); + assert(ram_entry_array.size() == 2); + const auto address = get_uint16(ram_entry_array[0]); + const auto value = get_uint8(ram_entry_array[1]); + m_ram[address] = value; + } + + m_initialised = true; +} + +state_t::state_t() {} + +state_t::state_t(const boost::json::object& serialised) { + initialise(serialised); + assert(initialised()); +} + +state_t::state_t(const boost::json::value& serialised) { + assert(serialised.is_object()); + initialise(serialised.get_object()); + assert(initialised()); +} diff --git a/M6502/HarteTest_6502/state_t.h b/M6502/HarteTest_6502/state_t.h new file mode 100644 index 0000000..8d03d17 --- /dev/null +++ b/M6502/HarteTest_6502/state_t.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include + +#include "json_t.h" + +class state_t final : public json_t { +private: + bool m_initialised = false; + + uint16_t m_pc = 0xffff; + uint8_t m_s = 0xff; + uint8_t m_a = 0xff; + uint8_t m_x = 0xff; + uint8_t m_y = 0xff; + uint8_t m_p = 0xff; + std::unordered_map m_ram; + + constexpr auto initialised() const noexcept { return m_initialised; } + + void initialise(const boost::json::object& serialised); + +public: + state_t(); + state_t(const boost::json::object& serialised); + state_t(const boost::json::value& serialised); + + constexpr auto pc() const noexcept { return m_pc; } + constexpr auto s() const noexcept { return m_s; } + constexpr auto a() const noexcept { return m_a; } + constexpr auto x() const noexcept { return m_x; } + constexpr auto y() const noexcept { return m_y; } + constexpr auto p() const noexcept { return m_p; } + constexpr const auto& ram() const noexcept { return m_ram; } +}; diff --git a/M6502/HarteTest_6502/stdafx.cpp b/M6502/HarteTest_6502/stdafx.cpp new file mode 100644 index 0000000..1577c4e --- /dev/null +++ b/M6502/HarteTest_6502/stdafx.cpp @@ -0,0 +1 @@ +#include "stdafx.h" \ No newline at end of file diff --git a/M6502/HarteTest_6502/stdafx.h b/M6502/HarteTest_6502/stdafx.h new file mode 100644 index 0000000..4cfb9a1 --- /dev/null +++ b/M6502/HarteTest_6502/stdafx.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include diff --git a/M6502/HarteTest_6502/test_t.cpp b/M6502/HarteTest_6502/test_t.cpp new file mode 100644 index 0000000..5816f3a --- /dev/null +++ b/M6502/HarteTest_6502/test_t.cpp @@ -0,0 +1,48 @@ +#include "stdafx.h" +#include "test_t.h" +#include +#include + +test_t::action test_t::to_action(std::string value) { + if (value == "read") + return action::read; + if (value == "write") + return action::write; + throw new std::out_of_range("Unknown action"); +} + +std::string test_t::to_string(action value) { + if (value == action::read) + return "read"; + if (value == action::write) + return "write"; + throw new std::out_of_range("Unknown action"); +} + +void test_t::initialise(const boost::json::object& serialised) { + + m_name = get_string(serialised, "name"); + m_initial_state = state_t(get_value(serialised, "initial")); + m_final_state = state_t(get_value(serialised, "final")); + + const auto& cycles_array = get_array(serialised, "cycles"); + m_cycles.reserve(cycles_array.size()); + + for (const auto& cycles_entry : cycles_array) { + const auto& cycle_array = get_array(cycles_entry); + assert(cycle_array.size() == 3); + const auto address = get_uint16(cycle_array[0]); + const auto contents = get_uint8(cycle_array[1]); + const auto action = to_action((std::string)get_string(cycle_array[2])); + m_cycles.push_back( { address, contents, action } ); + } +} + +test_t::test_t(const boost::json::object& serialised) { + initialise(serialised); +} + +test_t::test_t(const boost::json::value& serialised) { + assert(serialised.is_object()); + initialise(serialised.get_object()); +} diff --git a/M6502/HarteTest_6502/test_t.h b/M6502/HarteTest_6502/test_t.h new file mode 100644 index 0000000..8c78b99 --- /dev/null +++ b/M6502/HarteTest_6502/test_t.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "state_t.h" +#include "json_t.h" + +class test_t final : public json_t { +public: + enum class action { read, write }; + + typedef std::tuple event_t; // address, contents, action + typedef std::vector events_t; + + static action to_action(std::string value); + static std::string to_string(action value); + +private: + std::string m_name; + state_t m_initial_state; + state_t m_final_state; + events_t m_cycles; + + void initialise(const boost::json::object& serialised); + +public: + test_t(const boost::json::object& serialised); + test_t(const boost::json::value& serialised); + + constexpr const auto& name() const noexcept { return m_name; } + constexpr const auto& initial_state() const noexcept { return m_initial_state; } + constexpr const auto& final_state() const noexcept { return m_final_state; } + constexpr const auto& cycles() const noexcept { return m_cycles; } +}; \ No newline at end of file diff --git a/M6502/HarteTest_6502/tests.cpp b/M6502/HarteTest_6502/tests.cpp new file mode 100644 index 0000000..6ea5809 --- /dev/null +++ b/M6502/HarteTest_6502/tests.cpp @@ -0,0 +1,32 @@ +#include "stdafx.h" + +#include +#include + +#include "TestRunner.h" +#include "test_t.h" +#include "opcode_test_suite_t.h" + + +int main() { + std::filesystem::path location = "C:\\github\\spectrum\\libraries\\EightBit\\modules\\ProcessorTests\\6502\\v1"; + + for (const auto& entry : std::filesystem::directory_iterator{ location }) { + + const auto path = entry.path(); + std::cout << "** path: " << path << std::endl; + + opcode_test_suite_t opcode(path.string()); + opcode.load(); + const auto& opcode_test_array = opcode.get_array(); + for (const auto& opcode_test_element : opcode_test_array) { + + const auto opcode_test = test_t(opcode_test_element); + + TestRunner runner(opcode_test); + runner.run(); + } + + std::cout << "\n"; + } +} \ No newline at end of file