Add (non-passing) Z80 HarteTest

This commit is contained in:
Adrian Conlon
2026-02-28 14:18:40 +00:00
parent 55c7afd25f
commit 345a15552a
27 changed files with 250107 additions and 0 deletions

View File

@@ -0,0 +1,184 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>18.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{93f0d716-a83d-4afd-af9d-4f947d1c9f02}</ProjectGuid>
<RootNamespace>HarteTestZ80</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v145</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v145</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v145</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v145</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<IncludePath>..\inc;..\..\inc;C:\Libraries\boost_1_88_0;$(IncludePath)</IncludePath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<IncludePath>..\inc;..\..\inc;C:\Libraries\boost_1_88_0;$(IncludePath)</IncludePath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<IncludePath>..\inc;..\..\inc;C:\Libraries\boost_1_88_0;$(IncludePath)</IncludePath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<IncludePath>..\inc;..\..\inc;C:\Libraries\boost_1_88_0;$(IncludePath)</IncludePath>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp20</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp20</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp20</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp20</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="array_t.h" />
<ClInclude Include="byte_t.h" />
<ClInclude Include="checker_t.h" />
<ClInclude Include="cycles_t.h" />
<ClInclude Include="cycle_t.h" />
<ClInclude Include="element_t.h" />
<ClInclude Include="opcode_test_suite_t.h" />
<ClInclude Include="parser_t.h" />
<ClInclude Include="ports_t.h" />
<ClInclude Include="port_t.h" />
<ClInclude Include="processor_test_suite_t.h" />
<ClInclude Include="ram_t.h" />
<ClInclude Include="simdjson\simdjson.h" />
<ClInclude Include="state_t.h" />
<ClInclude Include="stdafx.h" />
<ClInclude Include="TestRunner.h" />
<ClInclude Include="test_t.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="checker_t.cpp" />
<ClCompile Include="opcode_test_suite_t.cpp" />
<ClCompile Include="parser_t.cpp" />
<ClCompile Include="processor_test_suite_t.cpp" />
<ClCompile Include="simdjson\simdjson.cpp" />
<ClCompile Include="stdafx.cpp" />
<ClCompile Include="TestRunner.cpp" />
<ClCompile Include="tests.cpp" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\M6502\src\M6502.vcxproj">
<Project>{d8726a1b-bbfe-47ef-9860-26b90140ba66}</Project>
</ProjectReference>
<ProjectReference Include="..\..\src\EightBit.vcxproj">
<Project>{a9c24bd9-0cb4-4c84-b09b-46b815f9da47}</Project>
</ProjectReference>
<ProjectReference Include="..\src\Z80.vcxproj">
<Project>{01974f81-2750-45af-b845-2c903a54a334}</Project>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@@ -0,0 +1,92 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="array_t.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="byte_t.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="checker_t.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="cycle_t.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="cycles_t.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="element_t.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="opcode_test_suite_t.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="parser_t.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="processor_test_suite_t.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ram_t.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="state_t.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="stdafx.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="test_t.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="TestRunner.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="simdjson\simdjson.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="port_t.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ports_t.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="checker_t.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="opcode_test_suite_t.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="parser_t.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="processor_test_suite_t.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="stdafx.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="TestRunner.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="tests.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="simdjson\simdjson.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,23 @@
#include "stdafx.h"
#include "TestRunner.h"
TestRunner::TestRunner() {}
EightBit::MemoryMapping TestRunner::mapping(const uint16_t address) noexcept {
return { RAM(), 0x0000, 0xffff, EightBit::MemoryMapping::AccessLevel::ReadWrite };
}
void TestRunner::raisePOWER() noexcept {
EightBit::Bus::raisePOWER();
CPU().raisePOWER();
CPU().raiseRESET();
CPU().raiseINT();
CPU().raiseNMI();
}
void TestRunner::lowerPOWER() noexcept {
CPU().lowerPOWER();
EightBit::Bus::lowerPOWER();
}
void TestRunner::initialise() {}

View File

@@ -0,0 +1,27 @@
#pragma once
#include <Bus.h>
#include <Ram.h>
#include <Z80.h>
class TestRunner final : public EightBit::Bus {
private:
EightBit::Ram m_ram = 0x10000;
EightBit::InputOutput m_ports;
EightBit::Z80 m_cpu = { *this, m_ports };
protected:
EightBit::MemoryMapping mapping(uint16_t address) noexcept final;
public:
TestRunner();
void raisePOWER() noexcept final;
void lowerPOWER() noexcept final;
void initialise() final;
[[nodiscard]] constexpr auto& RAM() noexcept { return m_ram; }
[[nodiscard]] constexpr auto& ports() noexcept { return m_ports; }
[[nodiscard]] constexpr auto& CPU() noexcept { return m_cpu; }
};

View File

@@ -0,0 +1,61 @@
#pragma once
#include "simdjson/simdjson.h"
class array_t {
private:
const simdjson::dom::array m_raw;
protected:
[[nodiscard]] simdjson::dom::array raw() const noexcept { return m_raw; }
public:
[[nodiscard]] auto begin() const noexcept { return raw().begin(); }
[[nodiscard]] auto end() const noexcept { return raw().end(); }
[[nodiscard]] auto size() const noexcept { return raw().size(); }
[[nodiscard]] auto at(size_t idx) const noexcept { return raw().at(idx); }
[[nodiscard]] auto operator[](size_t idx) const noexcept { return at(idx); }
protected:
[[nodiscard]] std::optional<int64_t> maybe_integer_at(size_t idx) const noexcept {
auto possible = at(idx);
if (possible.has_value() && possible.is_int64())
return std::optional<int64_t>(possible.get_int64());
return std::optional<int64_t>();
}
[[nodiscard]] std::optional<std::string_view> maybe_string_at(size_t idx) const noexcept {
auto possible = at(idx);
if (possible.has_value() && possible.is_string())
return std::optional<std::string_view>(possible.get_string());
return std::optional<std::string_view>();
}
template<typename T>
[[nodiscard]] auto maybe_cast_integer_at(size_t idx) const noexcept {
auto original = maybe_integer_at(idx);
if (original.has_value())
return std::optional<T>(T(original.value()));
return std::optional<T>();
}
[[nodiscard]] auto integer_at(size_t idx) const noexcept { return maybe_integer_at(idx).value(); }
[[nodiscard]] auto string_at(size_t idx) const noexcept { return maybe_string_at(idx).value(); }
public:
array_t(const simdjson::dom::array input) noexcept
: m_raw(input) {}
[[nodiscard]] auto maybe_address_at(size_t idx) const noexcept {
return maybe_cast_integer_at<uint16_t>(idx);
}
[[nodiscard]] auto maybe_byte_at(size_t idx) const noexcept {
return maybe_cast_integer_at<uint8_t>(idx);
}
[[nodiscard]] auto address_at(size_t idx) const noexcept { return maybe_address_at(idx).value(); }
[[nodiscard]] auto byte_at(size_t idx) const noexcept { return maybe_byte_at(idx).value(); }
};

View File

@@ -0,0 +1,16 @@
#pragma once
#include <cstdint>
#include "simdjson/simdjson.h"
#include "array_t.h"
class byte_t : public array_t {
public:
byte_t(const simdjson::dom::array input) noexcept
: array_t(input) {}
[[nodiscard]] auto address() const noexcept { return address_at(0); }
[[nodiscard]] auto value() const noexcept { return byte_at(1); }
};

View File

@@ -0,0 +1,384 @@
#include "stdafx.h"
#include "checker_t.h"
#include "port_t.h"
void checker_t::addActualCycle(const uint16_t address, const uint8_t value, const std::string& action) {
m_actualCycles.push_back({ address, value, action });
}
void checker_t::addActualCycle(const EightBit::register16_t address, const uint8_t value, const std::string& action) {
addActualCycle(address.word, value, action);
}
void checker_t::dumpCycles(const std::string which, const actual_cycles_t& events) {
m_messages.push_back(which);
dumpCycles(events);
}
void checker_t::dumpCycles(const actual_cycles_t& cycles) {
for (const auto& cycle : cycles)
dumpCycle(cycle);
}
void checker_t::dumpCycle(const actual_cycle_t& cycle) {
dumpCycle(std::get<0>(cycle), std::get<1>(cycle), std::get<2>(cycle));
}
void checker_t::dumpCycles(const std::string which, const cycles_t events) {
m_messages.push_back(which);
dumpCycles(events);
}
void checker_t::dumpCycles(const cycles_t cycles) {
for (const auto cycle : cycles)
dumpCycle(cycle_t(cycle));
}
void checker_t::dumpCycle(const cycle_t cycle) {
dumpCycle(cycle.address(), cycle.value(), cycle.action());
}
void checker_t::raise(const std::string& what, const uint16_t expected, const uint16_t actual) {
os()
<< std::setw(2) << std::setfill(' ')
<< what
<< std::setw(4) << std::setfill('0')
<< ": expected: " << (int)expected
<< ", actual: " << (int)actual;
pushCurrentMessage();
}
void checker_t::raise(const std::string& what, const uint8_t expected, const uint8_t actual) {
os()
<< std::setw(2) << std::setfill(' ')
<< what
<< std::setfill('0')
<< ": expected: " << (int)expected
<< ", actual: " << (int)actual;
pushCurrentMessage();
}
void checker_t::raiseFlags(const std::string& what, const uint8_t expected, const uint8_t actual) {
os()
<< std::setw(2) << std::setfill(' ')
<< what
<< std::setfill('0')
<< ": expected: " << EightBit::Disassembler::flags(expected)
<< ", actual: " << EightBit::Disassembler::flags(actual);
pushCurrentMessage();
}
void checker_t::raise(const std::string& what, const std::string& expected, const std::string& actual) {
os()
<< std::setw(0) << std::setfill(' ')
<< what
<< ": expected: " << expected
<< ", actual: " << actual;
pushCurrentMessage();
}
bool checker_t::check(const std::string& what, const uint16_t address, const uint8_t expected, const uint8_t actual) {
const auto success = actual == expected;
if (!success) {
os() << what << ": " << std::setw(4) << std::setfill('0') << (int)address;
raise(os().str(), expected, actual);
}
return success;
}
checker_t::checker_t(TestRunner& runner)
: m_runner(runner) {}
void checker_t::initialiseState(test_t test) {
initialiseState(test.initial(), test.ports());
}
void checker_t::initialiseState(const state_t initial, std::optional<ports_t> ports) {
auto& cpu = runner().CPU();
cpu.PC() = initial.pc();
cpu.SP() = initial.sp();
// Alternate register set, then switch to the main set
cpu.AF() = initial.af_();
cpu.BC() = initial.bc_();
cpu.DE() = initial.de_();
cpu.HL() = initial.hl_();
cpu.exx();
cpu.exxAF();
// Now we're in the main register set, so set it up
cpu.AF() = { initial.f(), initial.a() };
cpu.BC() = { initial.c(), initial.b() };
cpu.DE() = { initial.e(), initial.d() };
cpu.HL() = { initial.l(), initial.h() };
// miscellaneous registers
cpu.IV() = initial.i();
cpu.REFRESH() = initial.r();
cpu.IM() = initial.im();
cpu.Q() = initial.q();
cpu.IFF1() = initial.iff1();
cpu.IFF2() = initial.iff2();
cpu.MEMPTR() = initial.wz();
cpu.IX() = initial.ix();
cpu.IY() = initial.iy();
auto& ram = runner().RAM();
for (const auto entry : initial.ram()) {
const byte_t byte{ entry };
const auto address = byte.address();
const auto value = byte.value();
ram.poke(address, value);
}
if (!ports.has_value())
return;
for (const auto entry : ports.value()) {
const port_t port{ entry };
const auto address = port.address();
const auto value = port.value();
const auto type = port.type();
if (type == "r") {
runner().ports().writeInputPort(address, value);
} else if (type == "w") {
runner().ports().writeOutputPort(address, value);
} else {
throw std::out_of_range("Unknown port state type");
}
}
}
void checker_t::initialise() {
auto& bus = runner();
auto& cpu = bus.CPU();
cpu.Ticked.connect([this](EightBit::EventArgs&) {
auto& bus = runner();
auto& cpu = bus.CPU();
const std::string read = EightBit::Device::lowered(cpu.RD()) ? "r" : "-";
const std::string write = EightBit::Device::lowered(cpu.WR()) ? "w" : "-";
const std::string memory = EightBit::Device::lowered(cpu.MREQ()) ? "m" : "-";
const std::string io = EightBit::Device::lowered(cpu.IORQ()) ? "i" : "-";
addActualCycle(bus.ADDRESS(), bus.DATA(), read + write + memory + io);
});
os() << std::hex << std::uppercase;
}
//
bool checker_t::checkState(test_t test) {
auto& cpu = runner().CPU();
auto& ram = runner().RAM();
const auto& expected_cycles = test.cycles();
const auto& actual_cycles = m_actualCycles;
size_t actual_idx = 0;
for (const auto expected_cycle : expected_cycles) {
if (actual_idx >= actual_cycles.size()) {
m_cycle_count_mismatch = true;
return false; // more expected cycles than actual
}
const cycle_t expected{ expected_cycle };
const auto& actual = actual_cycles[actual_idx++];
const auto expected_address = expected.address();
const auto actual_address = std::get<0>(actual);
check("Cycle address", expected_address, actual_address);
const auto maybe_expected_value = expected.value();
if (maybe_expected_value.has_value()) {
const auto expected_value = maybe_expected_value.value();
const auto actual_value = std::get<1>(actual);
check("Cycle value", expected_value, actual_value);
}
const auto expected_action = expected.action();
const auto& actual_action = std::get<2>(actual);
check("Cycle action", std::string(expected_action), actual_action);
}
if (actual_idx < actual_cycles.size()) {
m_cycle_count_mismatch = true;
return false; // less expected cycles than actual
}
if (!m_messages.empty())
return false;
const auto final = test.final();
const auto pc_good = check("PC", final.pc(), cpu.PC().word);
const auto sp_good = check("SP", final.sp(), cpu.SP().word);
const auto a_good = check("A", final.a(), cpu.A());
const auto f_good = check("F", final.f(), cpu.F());
const auto af_good = a_good && f_good;
const auto b_good = check("B", final.b(), cpu.B());
const auto c_good = check("C", final.c(), cpu.C());
const auto bc_good = b_good && c_good;
const auto d_good = check("D", final.d(), cpu.D());
const auto e_good = check("E", final.e(), cpu.E());
const auto de_good = d_good && e_good;
const auto h_good = check("H", final.h(), cpu.H());
const auto l_good = check("L", final.l(), cpu.L());
const auto hl_good = h_good && l_good;
cpu.exxAF();
cpu.exx();
const auto af_alt_good = check("AF'", final.af_(), cpu.AF().word);
const auto bc_alt_good = check("BC'", final.bc_(), cpu.BC().word);
const auto de_alt_good = check("DE'", final.de_(), cpu.DE().word);
const auto hl_alt_good = check("DE'", final.de_(), cpu.DE().word);
const auto i_good = check("I", final.i(), cpu.IV());
const auto r_good = check("R", final.r(), (uint8_t)cpu.REFRESH());
const auto im_good = check("IM", final.im(), (uint8_t)cpu.IM());
const auto q_good = check("Q", final.im(), (uint8_t)cpu.IM());
const auto iff1_good = check("IFF1", final.iff1(), (uint8_t)cpu.IFF1());
const auto iff2_good = check("IFF2", final.iff2(), (uint8_t)cpu.IFF2());
const auto iff_good = iff1_good && iff2_good;
const auto memptr_good = check("MEMPTR", final.wz(), cpu.MEMPTR().word);
const auto ix_good = check("IX", final.ix(), cpu.IX().word);
const auto iy_good = check("IY", final.iy(), cpu.IY().word);
bool ram_problem = false;
for (const auto entry : final.ram()) {
const auto byte = byte_t{ entry };
const auto address = byte.address();
const auto value = byte.value();
if (!check("RAM", address, value, ram.peek(address)))
ram_problem = true;
}
return
pc_good && sp_good
&& af_good && bc_good && de_good && hl_good
&& af_alt_good && bc_alt_good && de_alt_good && hl_alt_good
&& i_good && r_good
&& im_good
&& q_good
&& iff_good
&& memptr_good
&& ix_good && iy_good
&& !ram_problem;
}
//
void checker_t::pushCurrentMessage() {
m_messages.push_back(os().str());
os().str("");
}
std::string checker_t::disassemble(uint16_t address) {
return m_disassembler.disassemble(runner().CPU(), address);
}
void checker_t::add_disassembly(uint16_t address) {
try {
os() << disassemble(address);
}
catch (const std::domain_error& error) {
os() << "Disassembly problem: " << error.what();
}
pushCurrentMessage();
}
void checker_t::check(test_t test) {
auto& cpu = runner().CPU();
m_messages.clear();
m_actualCycles.clear();
runner().raisePOWER();
initialiseState(test);
const auto pc = cpu.PC().word;
m_cycles = cpu.step();
runner().lowerPOWER();
m_valid = checkState(test);
if (unimplemented()) {
m_messages.push_back("Unimplemented");
return;
}
if (invalid() && implemented()) {
add_disassembly(pc);
const auto final = test.final();
raise("PC", final.pc(), cpu.PC().word);
raise("SP", final.sp(), cpu.SP().word);
raise("A", final.a(), cpu.A());
raiseFlags("F", final.a(), cpu.A());
raise("B", final.b(), cpu.B());
raise("C", final.c(), cpu.C());
raise("D", final.d(), cpu.D());
raise("E", final.e(), cpu.E());
raise("H", final.h(), cpu.H());
raise("L", final.l(), cpu.L());
cpu.exx();
cpu.exxAF();
raise("'AF", final.af_(), cpu.AF().word);
raise("'BC", final.bc_(), cpu.BC().word);
raise("'DE", final.de_(), cpu.DE().word);
raise("'HL", final.hl_(), cpu.HL().word);
raise("I", final.i(), cpu.IV());
raise("R", final.r(), cpu.REFRESH());
raise("IM", final.im(), (uint8_t)cpu.IM());
raise("Q", final.q(), cpu.Q());
raise("IFF1", final.iff1(), (uint8_t)cpu.IFF1());
raise("IFF2", final.iff2(), (uint8_t)cpu.IFF2());
raise("WZ", final.wz(), cpu.MEMPTR().word);
raise("IX", final.ix(), cpu.IX().word);
raise("IY", final.iy(), cpu.IY().word);
os()
<< std::dec << std::setfill(' ')
<< "Stepped cycles: " << cycles()
<< ", expected events: " << test.cycles().size()
<< ", actual events: " << m_actualCycles.size();
pushCurrentMessage();
dumpCycles("-- Expected cycles", test.cycles());
dumpCycles("-- Actual cycles", m_actualCycles);
}
}

View File

@@ -0,0 +1,102 @@
#pragma once
#include <cstdint>
#include <optional>
#include <Disassembler.h>
#include "cycles_t.h"
#include "cycle_t.h"
#include "test_t.h"
#include "TestRunner.h"
class checker_t {
private:
TestRunner& m_runner;
EightBit::Disassembler m_disassembler = { m_runner };
std::ostringstream m_os;
std::vector<std::string> m_messages;
typedef std::tuple<uint16_t, uint8_t, std::string> actual_cycle_t;
typedef std::vector<actual_cycle_t> actual_cycles_t;
actual_cycles_t m_actualCycles;
bool m_cycle_count_mismatch = false;
int m_cycles = 0;
bool m_valid = true;
[[nodiscard]] constexpr auto& os() noexcept { return m_os; }
[[nodiscard]] constexpr auto& runner() noexcept { return m_runner; }
[[nodiscard]] bool checkState(test_t test);
void pushCurrentMessage();
void raise(const std::string& what, uint16_t expected, uint16_t actual);
void raise(const std::string& what, uint8_t expected, uint8_t actual);
void raiseFlags(const std::string& what, uint8_t expected, uint8_t actual);
void raise(const std::string& what, const std::string& expected, const std::string& actual);
template<class T>
constexpr bool check(const std::string& what, T expected, T actual) noexcept {
const auto success = actual == expected;
if (!success)
raise(what, expected, actual);
return success;
}
bool check(const std::string& what, uint16_t address, uint8_t expected, uint8_t actual);
void addActualCycle(uint16_t address, uint8_t value, const std::string& action);
void addActualCycle(EightBit::register16_t address, uint8_t value, const std::string& action);
void add_disassembly(uint16_t address);
template<class T>
void dumpCycle(const uint16_t address, const std::optional<uint8_t> value, const T action) {
if (value.has_value())
m_os
<< std::setfill('0') << std::hex
<< "Address: " << std::setw(4) << (int)address
<< ", value: " << std::setw(2) << (int)value.value()
<< ", action: " << action;
else
m_os
<< std::setfill('0') << std::hex
<< "Address: " << std::setw(4) << (int)address
<< " "
<< ", action: " << action;
pushCurrentMessage();
}
void dumpCycles(std::string which, cycles_t cycles);
void dumpCycles(cycles_t cycles);
void dumpCycle(cycle_t cycle);
void dumpCycles(std::string which, const actual_cycles_t& cycles);
void dumpCycles(const actual_cycles_t& cycles);
void dumpCycle(const actual_cycle_t& cycle);
void initialiseState(test_t test);
void initialiseState(state_t state, std::optional<ports_t> ports);
public:
checker_t(TestRunner& runner);
[[nodiscard]] constexpr auto cycles() const noexcept { return m_cycles; }
[[nodiscard]] constexpr auto valid() const noexcept { return m_valid; }
[[nodiscard]] constexpr auto invalid() const noexcept { return !valid(); }
[[nodiscard]] constexpr auto unimplemented() const noexcept { return invalid() && m_cycle_count_mismatch && (cycles() == 1); }
[[nodiscard]] constexpr auto implemented() const noexcept { return !unimplemented(); }
[[nodiscard]] constexpr const auto& messages() const noexcept { return m_messages; }
void initialise();
[[nodiscard]] std::string disassemble(uint16_t address);
void check(test_t test);
};

View File

@@ -0,0 +1,17 @@
#pragma once
#include <string_view>
#include "simdjson/simdjson.h"
#include "byte_t.h"
class cycle_t final : public array_t {
public:
cycle_t(const simdjson::dom::array input) noexcept
: array_t(input) {}
[[nodiscard]] auto address() const noexcept { return address_at(0); }
[[nodiscard]] auto value() const noexcept { return maybe_byte_at(1); }
[[nodiscard]] auto action() const noexcept { return string_at(2); }
};

View File

@@ -0,0 +1,5 @@
#pragma once
#include "array_t.h"
typedef array_t cycles_t; // Intended to indicate that cycles_t is meant to hold cycle_t objects

View File

@@ -0,0 +1,24 @@
#pragma once
#include <string_view>
#include "simdjson/simdjson.h"
class element_t {
private:
simdjson::dom::element m_raw;
protected:
[[nodiscard]] auto raw() const noexcept { return m_raw; }
public:
element_t() noexcept {}
element_t(const simdjson::dom::element input) noexcept
: m_raw(input) {}
[[nodiscard]] auto at(std::string_view key) const noexcept { return raw()[key]; }
[[nodiscard]] auto operator[](std::string_view key) const noexcept { return at(key); }
[[nodiscard]] auto array_at(std::string_view key) const noexcept { return at(key).get_array(); }
[[nodiscard]] auto integer_at(std::string_view key) const noexcept { return int64_t(at(key)); }
};

View File

@@ -0,0 +1,10 @@
#include "stdafx.h"
#include "opcode_test_suite_t.h"
opcode_test_suite_t::opcode_test_suite_t(const std::string path) noexcept
: parser_t(path) {}
EightBit::co_generator_t<test_t> opcode_test_suite_t::generator() const {
for (const auto element : *this)
co_yield test_t(element);
}

View File

@@ -0,0 +1,23 @@
#pragma once
#include <string>
#include <co_generator_t.h>
#include "parser_t.h"
#include "test_t.h"
class opcode_test_suite_t final : public parser_t {
private:
[[nodiscard]] auto array() const noexcept { return raw().get_array(); }
public:
opcode_test_suite_t() noexcept {}
opcode_test_suite_t(std::string path) noexcept;
[[nodiscard]] auto begin() const noexcept { return array().begin(); }
[[nodiscard]] auto end() const noexcept { return array().end(); }
[[nodiscard]] auto size() const noexcept { return array().size(); }
[[nodiscard]] EightBit::co_generator_t<test_t> generator() const;
};

View File

@@ -0,0 +1,11 @@
#include "stdafx.h"
#include "parser_t.h"
simdjson::dom::parser parser_t::m_parser;
parser_t::parser_t(const std::string path) noexcept
: m_path(path) {}
void parser_t::load() {
m_raw = m_parser.load(m_path);
}

View File

@@ -0,0 +1,26 @@
#pragma once
#include <string>
#include <string_view>
#include "simdjson/simdjson.h"
class parser_t {
private:
// N.B.
// The parser must be kept for the lifetime of any parsed data.
// Therefore, it can only be used for one document at a time.
static simdjson::dom::parser m_parser;
std::string m_path;
simdjson::dom::element m_raw;
public:
parser_t() noexcept {}
parser_t(std::string path) noexcept;
[[nodiscard]] constexpr std::string_view path() const noexcept { return m_path; }
[[nodiscard]] const auto raw() const noexcept { return m_raw; }
virtual void load();
};

View File

@@ -0,0 +1,15 @@
#pragma once
#include <cstdint>
#include "simdjson/simdjson.h"
#include "byte_t.h"
class port_t : public byte_t {
public:
port_t(const simdjson::dom::array input) noexcept
: byte_t(input) {}
[[nodiscard]] auto type() const noexcept { return string_at(2); }
};

View File

@@ -0,0 +1,5 @@
#pragma once
#include "array_t.h"
typedef array_t ports_t; // Intended to indicate that ports_t is meant to hold port_t objects

View File

@@ -0,0 +1,14 @@
#include "stdafx.h"
#include "processor_test_suite_t.h"
#include <filesystem>
processor_test_suite_t::processor_test_suite_t(std::string location) noexcept
: m_location(location) {
}
EightBit::co_generator_t<opcode_test_suite_t> processor_test_suite_t::generator() const {
std::filesystem::path directory = location();
for (const auto& entry : std::filesystem::directory_iterator{ directory })
co_yield opcode_test_suite_t(entry.path().string());
}

View File

@@ -0,0 +1,20 @@
#pragma once
#include <string>
#include <string_view>
#include <co_generator_t.h>
#include "opcode_test_suite_t.h"
class processor_test_suite_t final {
private:
std::string m_location;
public:
processor_test_suite_t(std::string location) noexcept;
[[nodiscard]] constexpr std::string_view location() const noexcept { return m_location; }
[[nodiscard]] EightBit::co_generator_t<opcode_test_suite_t> generator() const;
};

View File

@@ -0,0 +1,5 @@
#pragma once
#include "array_t.h"
typedef array_t ram_t; // Intended to indicate that ram_t is meant to hold byte_t objects

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,56 @@
#pragma once
#include <cstdint>
#include <string_view>
#include "simdjson/simdjson.h"
#include "element_t.h"
#include "ram_t.h"
class state_t final : public element_t {
public:
state_t(const simdjson::dom::element input) noexcept
: element_t(input) {}
[[nodiscard]] auto address_at(std::string_view key) const noexcept { return uint16_t(integer_at(key)); }
[[nodiscard]] auto byte_at(std::string_view key) const noexcept { return uint8_t(integer_at(key)); }
[[nodiscard]] auto pc() const noexcept { return address_at("pc"); }
[[nodiscard]] auto sp() const noexcept { return address_at("sp"); }
[[nodiscard]] auto a() const noexcept { return byte_at("a"); }
[[nodiscard]] auto f() const noexcept { return byte_at("f"); }
[[nodiscard]] auto b() const noexcept { return byte_at("b"); }
[[nodiscard]] auto c() const noexcept { return byte_at("c"); }
[[nodiscard]] auto d() const noexcept { return byte_at("d"); }
[[nodiscard]] auto e() const noexcept { return byte_at("e"); }
[[nodiscard]] auto h() const noexcept { return byte_at("h"); }
[[nodiscard]] auto l() const noexcept { return byte_at("l"); }
[[nodiscard]] auto af_() const noexcept { return address_at("af_"); }
[[nodiscard]] auto bc_() const noexcept { return address_at("bc_"); }
[[nodiscard]] auto de_() const noexcept { return address_at("de_"); }
[[nodiscard]] auto hl_() const noexcept { return address_at("hl_"); }
[[nodiscard]] auto i() const noexcept { return byte_at("i"); }
[[nodiscard]] auto r() const noexcept { return byte_at("r"); }
[[nodiscard]] auto im() const noexcept { return byte_at("im"); }
[[nodiscard]] auto ei() const noexcept { return byte_at("ei"); }
[[nodiscard]] auto p() const noexcept { return byte_at("p"); }
[[nodiscard]] auto q() const noexcept { return byte_at("q"); }
[[nodiscard]] auto iff1() const noexcept { return byte_at("iff1"); }
[[nodiscard]] auto iff2() const noexcept { return byte_at("iff2"); }
[[nodiscard]] auto wz() const noexcept { return address_at("wz"); }
[[nodiscard]] auto ix() const noexcept { return address_at("ix"); }
[[nodiscard]] auto iy() const noexcept { return address_at("iy"); }
[[nodiscard]] auto ram() const noexcept { return ram_t(array_at("ram")); }
};

View File

@@ -0,0 +1 @@
#include "stdafx.h"

View File

@@ -0,0 +1,28 @@
#pragma once
#define SIMDJSON_DISABLE_DEPRECATED_API
#include <cassert>
#include <chrono>
#include <cstdint>
#include <exception>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <map>
#include <memory>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include <Bus.h>
#include <Ram.h>
#include <Z80.h>
#include <Disassembler.h>
#include <co_generator_t.h>
#include "simdjson/simdjson.h"

View File

@@ -0,0 +1,31 @@
#pragma once
#include <optional>
#include "simdjson/simdjson.h"
#include "element_t.h"
#include "cycles_t.h"
#include "ports_t.h"
#include "state_t.h"
class test_t final : public element_t {
public:
test_t() noexcept {}
test_t(const simdjson::dom::element input) noexcept
: element_t(input) {}
[[nodiscard]] auto name() const noexcept { return at("name"); }
[[nodiscard]] auto initial() const noexcept { return state_t(at("initial")); }
[[nodiscard]] auto final() const noexcept { return state_t(at("final")); }
[[nodiscard]] auto cycles() const noexcept { return cycles_t(array_at("cycles")); }
[[nodiscard]] auto ports() const noexcept {
std::optional<ports_t> returned;
auto possible = array_at("ports");
if (possible.has_value())
returned.emplace(ports_t(possible.value()));
return returned;;
}
};

View File

@@ -0,0 +1,81 @@
#include "stdafx.h"
#include <chrono>
#include <iostream>
#include <filesystem>
#include "TestRunner.h"
#include "checker_t.h"
#include "test_t.h"
#include "opcode_test_suite_t.h"
#include "processor_test_suite_t.h"
int main() {
std::string directory = "C:\\github\\spectrum\\libraries\\EightBit\\modules\\z80\\v2";
const auto start_time = std::chrono::steady_clock::now();
int unimplemented_opcode_count = 0;
int invalid_opcode_count = 0;
TestRunner runner;
runner.initialise();
checker_t checker(runner);
checker.initialise();
processor_test_suite_t m6502_tests(directory);
auto opcode_generator = m6502_tests.generator();
while (opcode_generator) {
auto opcode = opcode_generator();
const auto path = std::filesystem::path(opcode.path());
std::cout << "Processing: " << path.filename() << "\n";
opcode.load();
auto test_generator = opcode.generator();
std::vector<std::string_view> test_names;
while (test_generator) {
const auto test = test_generator();
checker.check(test);
if (checker.invalid()) {
std::cout << "** Failed: " << test.name() << "\n";
++invalid_opcode_count;
// Was it just unimplemented?
if (checker.unimplemented())
++unimplemented_opcode_count;
// Let's see if we had any successes!
if (!test_names.empty()) {
std::cout << "**** The follow test variations succeeeded\n";
for (const auto& test_name : test_names)
std::cout << "****** " << test_name << "\n";
}
// OK, we've attempted an implementation, how did it fail?
for (const auto& message : checker.messages())
std::cout << "**** " << message << "\n";
// I'm not really interested in the remaining tests for this opcode
break;
}
test_names.push_back(test.name().get_string().value());
}
}
const auto finish_time = std::chrono::steady_clock::now();
const auto elapsed_time = finish_time - start_time;
const auto seconds = std::chrono::duration_cast<std::chrono::duration<double>>(elapsed_time).count();
std::cout
<< "Elapsed time: " << seconds << " seconds"
<< ", unimplemented opcode count: " << unimplemented_opcode_count
<< ", invalid opcode count: " << (invalid_opcode_count - unimplemented_opcode_count)
<< std::endl;
}