mirror of
https://github.com/MoleskiCoder/EightBit.git
synced 2025-01-02 18:29:41 +00:00
Use C++20 co-routines as generators for opcode suite and test generation.
Signed-off-by: Adrian Conlon <adrian.conlon@gmail.com>
This commit is contained in:
parent
45405d5624
commit
9e9c15e289
@ -24,6 +24,7 @@
|
||||
<ProjectGuid>{894eb6ef-4c8d-4401-bbaa-aba05a021abb}</ProjectGuid>
|
||||
<RootNamespace>HarteTest_6502</RootNamespace>
|
||||
<ProjectName>HarteTest_6502</ProjectName>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
@ -94,7 +95,7 @@
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
<LanguageStandard>stdcpp20</LanguageStandard>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
@ -107,7 +108,7 @@
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
<LanguageStandard>stdcpp20</LanguageStandard>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<BufferSecurityCheck>false</BufferSecurityCheck>
|
||||
<ControlFlowGuard>false</ControlFlowGuard>
|
||||
@ -127,7 +128,7 @@
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
<LanguageStandard>stdcpp20</LanguageStandard>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
@ -140,7 +141,7 @@
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
<LanguageStandard>stdcpp20</LanguageStandard>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<BufferSecurityCheck>false</BufferSecurityCheck>
|
||||
<ControlFlowGuard>false</ControlFlowGuard>
|
||||
@ -162,6 +163,7 @@
|
||||
<ClCompile Include="element_t.cpp" />
|
||||
<ClCompile Include="opcode_test_suite_t.cpp" />
|
||||
<ClCompile Include="parser_t.cpp" />
|
||||
<ClCompile Include="processor_test_suite_t.cpp" />
|
||||
<ClCompile Include="ram_t.cpp" />
|
||||
<ClCompile Include="simdjson\simdjson.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader>
|
||||
@ -183,11 +185,13 @@
|
||||
<ItemGroup>
|
||||
<ClInclude Include="array_t.h" />
|
||||
<ClInclude Include="byte_t.h" />
|
||||
<ClInclude Include="co_generator_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="processor_test_suite_t.h" />
|
||||
<ClInclude Include="ram_t.h" />
|
||||
<ClInclude Include="simdjson\simdjson.h" />
|
||||
<ClInclude Include="state_t.h" />
|
||||
|
@ -56,6 +56,9 @@
|
||||
<ClCompile Include="parser_t.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="processor_test_suite_t.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="stdafx.h">
|
||||
@ -97,5 +100,11 @@
|
||||
<ClInclude Include="parser_t.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="processor_test_suite_t.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="co_generator_t.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -25,11 +25,11 @@ void TestRunner::lowerPOWER() {
|
||||
EightBit::Bus::lowerPOWER();
|
||||
}
|
||||
|
||||
void TestRunner::addActualCycle(const uint16_t address, const uint8_t value, const std::string action) {
|
||||
m_actualCycles.push_back({ address, value, action });
|
||||
void TestRunner::addActualCycle(const uint16_t address, const uint8_t value, std::string_view action) {
|
||||
m_actualCycles.push_back({ address, value, std::string(action) });
|
||||
}
|
||||
|
||||
void TestRunner::addActualCycle(const EightBit::register16_t address, const uint8_t value, const std::string action) {
|
||||
void TestRunner::addActualCycle(const EightBit::register16_t address, const uint8_t value, std::string_view action) {
|
||||
addActualCycle(address.word, value, action);
|
||||
}
|
||||
|
||||
@ -41,8 +41,8 @@ void TestRunner::addActualWriteCycle(const EightBit::register16_t address, const
|
||||
addActualCycle(address, value, "write");
|
||||
}
|
||||
|
||||
void TestRunner::dumpCycles(const std::string which, const actual_cycles_t& events) {
|
||||
m_messages.push_back(which);
|
||||
void TestRunner::dumpCycles(std::string_view which, const actual_cycles_t& events) {
|
||||
m_messages.push_back(std::string(which));
|
||||
dumpCycles(events);
|
||||
}
|
||||
|
||||
@ -55,8 +55,8 @@ void TestRunner::dumpCycle(const actual_cycle_t& cycle) {
|
||||
dumpCycle(std::get<0>(cycle), std::get<1>(cycle), std::get<2>(cycle));
|
||||
}
|
||||
|
||||
void TestRunner::dumpCycles(const std::string which, const cycles_t events) {
|
||||
m_messages.push_back(which);
|
||||
void TestRunner::dumpCycles(std::string_view which, const cycles_t events) {
|
||||
m_messages.push_back(std::string(which));
|
||||
dumpCycles(events);
|
||||
}
|
||||
|
||||
@ -84,7 +84,7 @@ void TestRunner::initialise() {
|
||||
os() << std::hex << std::uppercase;
|
||||
}
|
||||
|
||||
void TestRunner::raise(const std::string what, const uint16_t expected, const uint16_t actual) {
|
||||
void TestRunner::raise(std::string_view what, const uint16_t expected, const uint16_t actual) {
|
||||
os()
|
||||
<< std::setw(2) << std::setfill(' ')
|
||||
<< what
|
||||
@ -94,7 +94,7 @@ void TestRunner::raise(const std::string what, const uint16_t expected, const ui
|
||||
pushCurrentMessage();
|
||||
}
|
||||
|
||||
void TestRunner::raise(const std::string what, const uint8_t expected, const uint8_t actual) {
|
||||
void TestRunner::raise(std::string_view what, const uint8_t expected, const uint8_t actual) {
|
||||
os()
|
||||
<< std::setw(2) << std::setfill(' ')
|
||||
<< what
|
||||
@ -106,7 +106,7 @@ void TestRunner::raise(const std::string what, const uint8_t expected, const uin
|
||||
pushCurrentMessage();
|
||||
}
|
||||
|
||||
void TestRunner::raise(const std::string what, const std::string_view expected, const std::string_view actual) {
|
||||
void TestRunner::raise(std::string_view what, std::string_view expected, std::string_view actual) {
|
||||
os()
|
||||
<< std::setw(0) << std::setfill(' ')
|
||||
<< what
|
||||
@ -115,7 +115,7 @@ void TestRunner::raise(const std::string what, const std::string_view expected,
|
||||
pushCurrentMessage();
|
||||
}
|
||||
|
||||
bool TestRunner::check(const std::string what, const uint16_t address, const uint8_t expected, const uint8_t actual) {
|
||||
bool TestRunner::check(std::string_view 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;
|
||||
|
@ -45,23 +45,30 @@ private:
|
||||
|
||||
void pushCurrentMessage();
|
||||
|
||||
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, std::string_view expected, std::string_view actual);
|
||||
void raise(std::string_view what, uint16_t expected, uint16_t actual);
|
||||
void raise(std::string_view what, uint8_t expected, uint8_t actual);
|
||||
void raise(std::string_view what, std::string_view expected, std::string_view actual);
|
||||
|
||||
template<class T>
|
||||
bool check(std::string what, T expected, T actual) {
|
||||
bool check(std::string_view what, T expected, T actual) {
|
||||
const auto success = actual == expected;
|
||||
if (!success)
|
||||
raise(what, expected, actual);
|
||||
return success;
|
||||
}
|
||||
|
||||
bool check(std::string what, uint16_t address, uint8_t expected, uint8_t actual);
|
||||
bool check(std::string_view what, std::string_view expected, std::string_view actual) {
|
||||
const auto success = actual == expected;
|
||||
if (!success)
|
||||
raise(what, expected, actual);
|
||||
return success;
|
||||
}
|
||||
|
||||
bool check(std::string_view what, uint16_t address, uint8_t expected, uint8_t actual);
|
||||
|
||||
void addActualCycle(const actual_cycle_t& value);
|
||||
void addActualCycle(uint16_t address, uint8_t value, std::string action);
|
||||
void addActualCycle(EightBit::register16_t address, uint8_t value, std::string action);
|
||||
void addActualCycle(uint16_t address, uint8_t value, std::string_view action);
|
||||
void addActualCycle(EightBit::register16_t address, uint8_t value, std::string_view action);
|
||||
|
||||
void addActualReadCycle(EightBit::register16_t address, uint8_t value);
|
||||
void addActualWriteCycle(EightBit::register16_t address, uint8_t value);
|
||||
@ -78,11 +85,11 @@ private:
|
||||
pushCurrentMessage();
|
||||
}
|
||||
|
||||
void dumpCycles(std::string which, cycles_t cycles);
|
||||
void dumpCycles(std::string_view 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(std::string_view which, const actual_cycles_t& cycles);
|
||||
void dumpCycles(const actual_cycles_t& cycles);
|
||||
void dumpCycle(const actual_cycle_t& cycle);
|
||||
|
||||
|
245
M6502/HarteTest_6502/checker_t.cpp
Normal file
245
M6502/HarteTest_6502/checker_t.cpp
Normal file
@ -0,0 +1,245 @@
|
||||
#include "stdafx.h"
|
||||
#include "checker_t.h"
|
||||
|
||||
std::set<uint8_t> checker_t::m_undocumented_opcodes;
|
||||
bool checker_t::m_undocumented_opcodes_initialised = false;
|
||||
|
||||
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::addActualReadCycle(const EightBit::register16_t address, const uint8_t value) {
|
||||
addActualCycle(address, value, "read");
|
||||
}
|
||||
|
||||
void checker_t::addActualWriteCycle(const EightBit::register16_t address, const uint8_t value) {
|
||||
addActualCycle(address, value, "write");
|
||||
}
|
||||
|
||||
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
|
||||
<< " (" << EightBit::Disassembly::dump_Flags(expected) << ")"
|
||||
<< ", actual: " << (int)actual
|
||||
<< " (" << EightBit::Disassembly::dump_Flags(actual) << ")";
|
||||
pushCurrentMessage();
|
||||
}
|
||||
|
||||
void checker_t::raise(const std::string what, const std::string_view expected, const std::string_view actual) {
|
||||
os()
|
||||
<< std::setw(0) << std::setfill(' ')
|
||||
<< what
|
||||
<< ": expected: " << expected
|
||||
<< ", actual: " << actual;
|
||||
pushCurrentMessage();
|
||||
}
|
||||
//
|
||||
//bool TestRunner::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(const test_t test)
|
||||
: m_test(test) {}
|
||||
|
||||
void checker_t::initialiseState(EightBit::MOS6502& cpu, EightBit::Ram& ram) {
|
||||
|
||||
const auto initial = test().initial();
|
||||
|
||||
cpu.PC().word = initial.pc();
|
||||
cpu.S() = initial.s();
|
||||
cpu.A() = initial.a();
|
||||
cpu.X() = initial.x();
|
||||
cpu.Y() = initial.y();
|
||||
cpu.P() = initial.p();
|
||||
for (const auto entry : initial.ram()) {
|
||||
const byte_t byte(entry);
|
||||
ram.poke(byte.address(), byte.value());
|
||||
}
|
||||
}
|
||||
|
||||
void checker_t::initialise(EightBit::Bus& bus) {
|
||||
|
||||
seedUndocumentedOpcodes();
|
||||
|
||||
bus.ReadByte.connect([this, &bus](EightBit::EventArgs&) {
|
||||
addActualReadCycle(bus.ADDRESS(), bus.DATA());
|
||||
});
|
||||
|
||||
bus.WrittenByte.connect([this, &bus](EightBit::EventArgs&) {
|
||||
addActualWriteCycle(bus.ADDRESS(), bus.DATA());
|
||||
});
|
||||
|
||||
os() << std::hex << std::uppercase;
|
||||
}
|
||||
|
||||
//
|
||||
bool checker_t::checkState(EightBit::MOS6502& cpu, EightBit::Ram& ram) {
|
||||
|
||||
const auto expected_cycles = test().cycles();
|
||||
const auto actual_cycles = m_actualCycles;
|
||||
m_cycle_count_mismatch = expected_cycles.size() != actual_cycles.size();
|
||||
if (m_cycle_count_mismatch)
|
||||
return false;
|
||||
|
||||
size_t actual_idx = 0;
|
||||
for (const auto expected_cycle : expected_cycles) {
|
||||
const auto expected = cycle_t(expected_cycle);
|
||||
const auto actual = actual_cycles.at(actual_idx++); // actual could be less than expected
|
||||
check("Cycle address", expected.address(), std::get<0>(actual));
|
||||
check("Cycle value", expected.value(), std::get<1>(actual));
|
||||
check("Cycle action", expected.action(), std::string_view(std::get<2>(actual)));
|
||||
}
|
||||
|
||||
const auto final = test().final();
|
||||
const auto pc_good = check("PC", final.pc(), cpu.PC().word);
|
||||
const auto s_good = check("S", final.s(), cpu.S());
|
||||
const auto a_good = check("A", final.a(), cpu.A());
|
||||
const auto x_good = check("X", final.x(), cpu.X());
|
||||
const auto y_good = check("Y", final.y(), cpu.Y());
|
||||
const auto p_good = check("P", final.p(), cpu.P());
|
||||
|
||||
bool ram_problem = false;
|
||||
for (const auto entry : final.ram()) {
|
||||
const byte_t byte(entry);
|
||||
const auto ram_good = check("RAM", byte.address(), byte.value(), ram.peek(byte.address()));
|
||||
if (!ram_good && !ram_problem)
|
||||
ram_problem = true;
|
||||
}
|
||||
|
||||
return pc_good && s_good && a_good && x_good && y_good && p_good && !ram_problem;
|
||||
}
|
||||
//
|
||||
void checker_t::pushCurrentMessage() {
|
||||
m_messages.push_back(os().str());
|
||||
os().str("");
|
||||
}
|
||||
|
||||
//void TestRunner::disassemble(uint16_t address) {
|
||||
// try {
|
||||
// os() << m_disassembler.disassemble(address);
|
||||
// }
|
||||
// catch (const std::domain_error& error) {
|
||||
// os() << "Disassembly problem: " << error.what();
|
||||
// }
|
||||
// pushCurrentMessage();
|
||||
//}
|
||||
//
|
||||
//void TestRunner::check() {
|
||||
// initialise();
|
||||
// raisePOWER();
|
||||
// initialiseState();
|
||||
// const auto pc = CPU().PC().word;
|
||||
// const auto start_opcode = peek(pc);
|
||||
// m_cycles = CPU().step();
|
||||
// lowerPOWER();
|
||||
//
|
||||
// m_valid = checkState();
|
||||
//
|
||||
// m_undocumented = m_undocumented_opcodes.find(start_opcode) != m_undocumented_opcodes.end();
|
||||
// if (undocumented()) {
|
||||
// m_messages.push_back("Undocumented");
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// if (unimplemented()) {
|
||||
// m_messages.push_back("Unimplemented");
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// if (invalid() && implemented()) {
|
||||
//
|
||||
// disassemble(pc);
|
||||
//
|
||||
// const auto final = test().final();
|
||||
// raise("PC", final.pc(), CPU().PC().word);
|
||||
// raise("S", final.s(), CPU().S());
|
||||
// raise("A", final.a(), CPU().A());
|
||||
// raise("X", final.x(), CPU().X());
|
||||
// raise("Y", final.y(), CPU().Y());
|
||||
// raise("P", final.p(), CPU().P());
|
||||
//
|
||||
// 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);
|
||||
// }
|
||||
//}
|
||||
//
|
||||
void checker_t::seedUndocumentedOpcodes() {
|
||||
if (m_undocumented_opcodes_initialised) return;
|
||||
m_undocumented_opcodes = {
|
||||
0x02, 0x03, 0x04, 0x07, 0x0b, 0x0c, 0x0f,
|
||||
0x12, 0x13, 0x14, 0x17, 0x1a, 0x1b, 0x1c, 0x1f,
|
||||
0x22, 0x23, 0x27, 0x2b, 0x2f,
|
||||
0x32, 0x33, 0x34, 0x37, 0x3a, 0x3b, 0x3c, 0x3f,
|
||||
0x42, 0x43, 0x44, 0x47, 0x4b, 0x4f,
|
||||
0x52, 0x53, 0x54, 0x57, 0x5a, 0x5b, 0x5c, 0x5f,
|
||||
0x62, 0x63, 0x64, 0x67, 0x6b, 0x6f,
|
||||
0x72, 0x73, 0x74, 0x77, 0x7a, 0x7b, 0x7c, 0x7f,
|
||||
0x80, 0x82, 0x83, 0x87, 0x89, 0x8b, 0x8f,
|
||||
0x92, 0x93, 0x97, 0x9b, 0x9c, 0x9e, 0x9f,
|
||||
0xa3, 0xa7, 0xab, 0xaf,
|
||||
0xb2, 0xb3, 0xb7, 0xbb, 0xbf,
|
||||
0xc2, 0xc3, 0xc7, 0xcb, 0xcf,
|
||||
0xd2, 0xd3, 0xd4, 0xd7, 0xda, 0xdb, 0xdc, 0xdf,
|
||||
0xe2, 0xe3, 0xe7, 0xeb, 0xef,
|
||||
0xf1, 0xf2, 0xf3, 0xf4, 0xf7, 0xfa, 0xfb, 0xfc, 0xff,
|
||||
};
|
||||
m_undocumented_opcodes_initialised = true;
|
||||
}
|
107
M6502/HarteTest_6502/checker_t.h
Normal file
107
M6502/HarteTest_6502/checker_t.h
Normal file
@ -0,0 +1,107 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <set>
|
||||
|
||||
#include "cycles_t.h"
|
||||
#include "cycle_t.h"
|
||||
#include "test_t.h"
|
||||
|
||||
class checker_t {
|
||||
public:
|
||||
private:
|
||||
static std::set<uint8_t> m_undocumented_opcodes;
|
||||
static bool m_undocumented_opcodes_initialised;
|
||||
|
||||
const test_t m_test;
|
||||
|
||||
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;
|
||||
//bool m_undocumented = false;
|
||||
|
||||
[[nodiscard]] auto& os() { return m_os; }
|
||||
|
||||
void seedUndocumentedOpcodes();
|
||||
[[nodiscard]] bool checkState(EightBit::MOS6502& cpu, EightBit::Ram& ram);
|
||||
|
||||
void pushCurrentMessage();
|
||||
|
||||
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, std::string_view expected, std::string_view actual);
|
||||
|
||||
template<class T>
|
||||
bool check(std::string what, T expected, T actual) {
|
||||
const auto success = actual == expected;
|
||||
if (!success)
|
||||
raise(what, expected, actual);
|
||||
return success;
|
||||
}
|
||||
|
||||
bool check(std::string what, uint16_t address, uint8_t expected, uint8_t actual);
|
||||
|
||||
//void addActualCycle(const actual_cycle_t& value);
|
||||
void addActualCycle(uint16_t address, uint8_t value, std::string action);
|
||||
void addActualCycle(EightBit::register16_t address, uint8_t value, std::string action);
|
||||
|
||||
void addActualReadCycle(EightBit::register16_t address, uint8_t value);
|
||||
void addActualWriteCycle(EightBit::register16_t address, uint8_t value);
|
||||
|
||||
//void disassemble(uint16_t address);
|
||||
|
||||
template<class T>
|
||||
void dumpCycle(const uint16_t address, const uint8_t value, const T action) {
|
||||
os()
|
||||
<< std::setfill('0') << std::hex
|
||||
<< "Address: " << std::setw(4) << (int)address
|
||||
<< ", value: " << std::setw(2) << (int)value
|
||||
<< ", 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);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//[[nodiscard]] auto test() const noexcept { return m_test; }
|
||||
//[[nodiscard]] constexpr const auto& messages() const noexcept { return m_messages; }
|
||||
|
||||
//[[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 auto undocumented() const noexcept { return m_undocumented; }
|
||||
//[[nodiscard]] constexpr auto documented() const noexcept { return !undocumented(); }
|
||||
|
||||
//void check();
|
||||
|
||||
|
||||
public:
|
||||
checker_t(const test_t test);
|
||||
|
||||
[[nodiscard]] auto test() const noexcept { return m_test; }
|
||||
|
||||
|
||||
void initialise(EightBit::Bus& bus);
|
||||
void initialiseState(EightBit::MOS6502& cpu, EightBit::Ram& ram);
|
||||
|
||||
};
|
||||
|
57
M6502/HarteTest_6502/co_generator_t.h
Normal file
57
M6502/HarteTest_6502/co_generator_t.h
Normal file
@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include <coroutine>
|
||||
#include <utility>
|
||||
|
||||
// from https://www.scs.stanford.edu/~dm/blog/c++-coroutines.html
|
||||
|
||||
template<typename T>
|
||||
struct co_generator_t final {
|
||||
|
||||
struct promise_type;
|
||||
using handle_type = std::coroutine_handle<promise_type>;
|
||||
|
||||
struct promise_type {
|
||||
T value_;
|
||||
std::exception_ptr exception_;
|
||||
|
||||
co_generator_t get_return_object() {
|
||||
return co_generator_t(handle_type::from_promise(*this));
|
||||
}
|
||||
std::suspend_always initial_suspend() { return {}; }
|
||||
std::suspend_always final_suspend() noexcept { return {}; }
|
||||
void unhandled_exception() { exception_ = std::current_exception(); }
|
||||
template<std::convertible_to<T> From> // C++20 concept
|
||||
std::suspend_always yield_value(From&& from) {
|
||||
value_ = std::forward<From>(from);
|
||||
return {};
|
||||
}
|
||||
void return_void() {}
|
||||
};
|
||||
|
||||
handle_type h_;
|
||||
|
||||
co_generator_t(handle_type h) : h_(h) {}
|
||||
~co_generator_t() { h_.destroy(); }
|
||||
explicit operator bool() {
|
||||
fill();
|
||||
return !h_.done();
|
||||
}
|
||||
T operator()() {
|
||||
fill();
|
||||
full_ = false;
|
||||
return std::move(h_.promise().value_);
|
||||
}
|
||||
|
||||
private:
|
||||
bool full_ = false;
|
||||
|
||||
void fill() {
|
||||
if (!full_) {
|
||||
h_();
|
||||
if (h_.promise().exception_)
|
||||
std::rethrow_exception(h_.promise().exception_);
|
||||
full_ = true;
|
||||
}
|
||||
}
|
||||
};
|
@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "simdjson/simdjson.h"
|
||||
|
||||
@ -14,10 +14,8 @@ protected:
|
||||
|
||||
[[nodiscard]] auto raw() const noexcept { return m_raw; }
|
||||
|
||||
[[nodiscard]] auto at(std::string key) const noexcept { return raw()[key]; }
|
||||
[[nodiscard]] auto operator[](std::string key) const noexcept { return at(key); }
|
||||
[[nodiscard]] auto array_at(std::string key) const noexcept { return at(key).get_array(); }
|
||||
[[nodiscard]] auto integer_at(std::string key) const noexcept { return at(key).get_int64(); }
|
||||
|
||||
|
||||
[[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 at(key).get_int64(); }
|
||||
};
|
||||
|
@ -3,3 +3,8 @@
|
||||
|
||||
opcode_test_suite_t::opcode_test_suite_t(const std::string path) noexcept
|
||||
: parser_t(path) {}
|
||||
|
||||
co_generator_t<test_t> opcode_test_suite_t::generator() {
|
||||
for (const auto element : *this)
|
||||
co_yield test_t(element);
|
||||
}
|
||||
|
@ -3,14 +3,19 @@
|
||||
#include <string>
|
||||
|
||||
#include "parser_t.h"
|
||||
#include "co_generator_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(); }
|
||||
|
||||
co_generator_t<test_t> generator();
|
||||
};
|
||||
|
@ -7,5 +7,5 @@ parser_t::parser_t(const std::string path) noexcept
|
||||
: m_path(path) {}
|
||||
|
||||
void parser_t::load() {
|
||||
m_raw = m_parser.load(path());
|
||||
m_raw = m_parser.load(m_path);
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "simdjson/simdjson.h"
|
||||
|
||||
@ -11,13 +12,14 @@ private:
|
||||
// Therefore, it can only be used for one document at a time.
|
||||
static simdjson::dom::parser m_parser;
|
||||
|
||||
const std::string m_path;
|
||||
std::string m_path;
|
||||
simdjson::dom::element m_raw;
|
||||
|
||||
public:
|
||||
parser_t() noexcept {}
|
||||
parser_t(std::string path) noexcept;
|
||||
|
||||
[[nodiscard]] constexpr const auto& path() const noexcept { return m_path; }
|
||||
[[nodiscard]] constexpr std::string_view path() const noexcept { return m_path; }
|
||||
[[nodiscard]] const auto raw() const noexcept { return m_raw; }
|
||||
|
||||
virtual void load();
|
||||
|
13
M6502/HarteTest_6502/processor_test_suite_t.cpp
Normal file
13
M6502/HarteTest_6502/processor_test_suite_t.cpp
Normal file
@ -0,0 +1,13 @@
|
||||
#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) {
|
||||
}
|
||||
co_generator_t<opcode_test_suite_t> processor_test_suite_t::generator() {
|
||||
std::filesystem::path directory = location();
|
||||
for (const auto& entry : std::filesystem::directory_iterator{ directory })
|
||||
co_yield opcode_test_suite_t(entry.path().string());
|
||||
}
|
20
M6502/HarteTest_6502/processor_test_suite_t.h
Normal file
20
M6502/HarteTest_6502/processor_test_suite_t.h
Normal 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;
|
||||
|
||||
std::string_view location() const noexcept { return m_location; }
|
||||
|
||||
co_generator_t<opcode_test_suite_t> generator();
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "simdjson/simdjson.h"
|
||||
|
||||
@ -10,8 +10,8 @@
|
||||
|
||||
class state_t final : public element_t {
|
||||
private:
|
||||
[[nodiscard]] auto address_at(std::string key) const noexcept { return (uint16_t)integer_at(key); }
|
||||
[[nodiscard]] auto byte_at(std::string key) const noexcept { return (uint8_t)integer_at(key); }
|
||||
[[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); }
|
||||
|
||||
public:
|
||||
state_t(simdjson::dom::element input) noexcept;
|
||||
|
@ -7,10 +7,13 @@
|
||||
#include "TestRunner.h"
|
||||
#include "test_t.h"
|
||||
#include "opcode_test_suite_t.h"
|
||||
#include "processor_test_suite_t.h"
|
||||
|
||||
#define USE_COROUTINES
|
||||
|
||||
int main() {
|
||||
|
||||
std::filesystem::path location = "C:\\github\\spectrum\\libraries\\EightBit\\modules\\ProcessorTests\\6502\\v1";
|
||||
auto directory = std::string("C:\\github\\spectrum\\libraries\\EightBit\\modules\\ProcessorTests\\6502\\v1");
|
||||
|
||||
const auto start_time = std::chrono::steady_clock::now();
|
||||
|
||||
@ -21,6 +24,42 @@ int main() {
|
||||
TestRunner runner;
|
||||
runner.initialise();
|
||||
|
||||
#ifdef USE_COROUTINES
|
||||
|
||||
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();
|
||||
while (test_generator) {
|
||||
|
||||
const auto test = test_generator();
|
||||
|
||||
runner.check(test);
|
||||
if (runner.invalid()) {
|
||||
++invalid_opcode_count;
|
||||
if (runner.unimplemented())
|
||||
++unimplemented_opcode_count;
|
||||
if (runner.undocumented())
|
||||
++undocumented_opcode_count;
|
||||
std::cout << "** Failed: " << test.name() << "\n";
|
||||
for (const auto& message : runner.messages())
|
||||
std::cout << "**** " << message << "\n";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
std::filesystem::path location = directory;
|
||||
|
||||
for (const auto& entry : std::filesystem::directory_iterator{ location }) {
|
||||
|
||||
const auto path = entry.path();
|
||||
@ -48,6 +87,8 @@ int main() {
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
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();
|
||||
|
Loading…
Reference in New Issue
Block a user