1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-11-26 23:52:26 +00:00

Merge pull request #1152 from TomHarte/New6502TestGenerator

Generalise 65816 test generator to handle all 6502esques.
This commit is contained in:
Thomas Harte 2023-08-18 11:28:57 -04:00 committed by GitHub
commit 3bd931937f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 120 additions and 55 deletions

View File

@ -14,20 +14,27 @@
#include <vector> #include <vector>
#include <unordered_map> #include <unordered_map>
#include "6502Selector.hpp"
namespace { namespace {
struct StopException {}; struct StopException {};
struct BusHandler: public CPU::MOS6502Esque::BusHandler<uint32_t> { template <CPU::MOS6502Esque::Type type>
// Use a map to store RAM contents, in order to preserve initialised state. struct BusHandler: public CPU::MOS6502Esque::BusHandlerT<type> {
std::unordered_map<uint32_t, uint8_t> ram; using AddressType = typename CPU::MOS6502Esque::BusHandlerT<type>::AddressType;
std::unordered_map<uint32_t, uint8_t> inventions;
Cycles perform_bus_operation(CPU::MOS6502Esque::BusOperation operation, uint32_t address, uint8_t *value) { // Use a map to store RAM contents, in order to preserve initialised state.
std::unordered_map<AddressType, uint8_t> ram;
std::unordered_map<AddressType, uint8_t> inventions;
Cycles perform_bus_operation(CPU::MOS6502Esque::BusOperation operation, AddressType address, uint8_t *value) {
// Record the basics of the operation. // Record the basics of the operation.
auto &cycle = cycles.emplace_back(); auto &cycle = cycles.emplace_back();
cycle.operation = operation; cycle.operation = operation;
cycle.extended_bus = processor.get_extended_bus_output(); if constexpr (has_extended_bus_output(type)) {
cycle.extended_bus = processor.get_extended_bus_output();
}
// Perform the operation, and fill in the cycle's value. // Perform the operation, and fill in the cycle's value.
using BusOperation = CPU::MOS6502Esque::BusOperation; using BusOperation = CPU::MOS6502Esque::BusOperation;
@ -92,25 +99,27 @@ struct BusHandler: public CPU::MOS6502Esque::BusHandler<uint32_t> {
allow_pc_repetition = opcode == 0x54 || opcode == 0x44; allow_pc_repetition = opcode == 0x54 || opcode == 0x44;
using Register = CPU::MOS6502Esque::Register; using Register = CPU::MOS6502Esque::Register;
const uint32_t pc = const auto pc =
processor.value_of(Register::ProgramCounter) | AddressType(
(processor.value_of(Register::ProgramBank) << 16); processor.value_of(Register::ProgramCounter) |
(processor.value_of(Register::ProgramBank) << 16)
);
inventions[pc] = ram[pc] = opcode; inventions[pc] = ram[pc] = opcode;
} }
int pc_overshoot = 0; int pc_overshoot = 0;
std::optional<uint32_t> initial_pc; std::optional<AddressType> initial_pc;
bool allow_pc_repetition = false; bool allow_pc_repetition = false;
struct Cycle { struct Cycle {
CPU::MOS6502Esque::BusOperation operation; CPU::MOS6502Esque::BusOperation operation;
std::optional<uint32_t> address; std::optional<AddressType> address;
std::optional<uint8_t> value; std::optional<uint8_t> value;
int extended_bus; int extended_bus = 0;
}; };
std::vector<Cycle> cycles; std::vector<Cycle> cycles;
CPU::WDC65816::Processor<BusHandler, false> processor; CPU::MOS6502Esque::Processor<type, BusHandler<type>, false> processor;
BusHandler() : processor(*this) { BusHandler() : processor(*this) {
// Never run the official reset procedure. // Never run the official reset procedure.
@ -118,7 +127,7 @@ struct BusHandler: public CPU::MOS6502Esque::BusHandler<uint32_t> {
} }
}; };
template <typename Processor> void print_registers(FILE *file, const Processor &processor, int pc_offset) { template <bool has_emulation, typename Processor> void print_registers(FILE *file, const Processor &processor, int pc_offset) {
using Register = CPU::MOS6502Esque::Register; using Register = CPU::MOS6502Esque::Register;
fprintf(file, "\"pc\": %d, ", (processor.value_of(Register::ProgramCounter) + pc_offset) & 65535); fprintf(file, "\"pc\": %d, ", (processor.value_of(Register::ProgramCounter) + pc_offset) & 65535);
fprintf(file, "\"s\": %d, ", processor.value_of(Register::StackPointer)); fprintf(file, "\"s\": %d, ", processor.value_of(Register::StackPointer));
@ -126,13 +135,16 @@ template <typename Processor> void print_registers(FILE *file, const Processor &
fprintf(file, "\"a\": %d, ", processor.value_of(Register::A)); fprintf(file, "\"a\": %d, ", processor.value_of(Register::A));
fprintf(file, "\"x\": %d, ", processor.value_of(Register::X)); fprintf(file, "\"x\": %d, ", processor.value_of(Register::X));
fprintf(file, "\"y\": %d, ", processor.value_of(Register::Y)); fprintf(file, "\"y\": %d, ", processor.value_of(Register::Y));
fprintf(file, "\"dbr\": %d, ", processor.value_of(Register::DataBank)); if constexpr (has_emulation) {
fprintf(file, "\"d\": %d, ", processor.value_of(Register::Direct)); fprintf(file, "\"dbr\": %d, ", processor.value_of(Register::DataBank));
fprintf(file, "\"pbr\": %d, ", processor.value_of(Register::ProgramBank)); fprintf(file, "\"d\": %d, ", processor.value_of(Register::Direct));
fprintf(file, "\"e\": %d, ", processor.value_of(Register::EmulationFlag)); fprintf(file, "\"pbr\": %d, ", processor.value_of(Register::ProgramBank));
fprintf(file, "\"e\": %d, ", processor.value_of(Register::EmulationFlag));
}
} }
void print_ram(FILE *file, const std::unordered_map<uint32_t, uint8_t> &data) { template <typename IntT>
void print_ram(FILE *file, const std::unordered_map<IntT, uint8_t> &data) {
fprintf(file, "\"ram\": ["); fprintf(file, "\"ram\": [");
bool is_first = true; bool is_first = true;
for(const auto &pair: data) { for(const auto &pair: data) {
@ -143,22 +155,18 @@ void print_ram(FILE *file, const std::unordered_map<uint32_t, uint8_t> &data) {
fprintf(file, "]"); fprintf(file, "]");
} }
}
// MARK: - New test generator. // MARK: - New test generator.
@interface TestGenerator : NSObject
@end
@implementation TestGenerator template <CPU::MOS6502Esque::Type type> void generate() {
BusHandler<type> handler;
- (void)generate { constexpr bool has_emulation = has(type, CPU::MOS6502Esque::Register::EmulationFlag);
BusHandler handler;
NSString *const tempDir = NSTemporaryDirectory(); NSString *const tempDir = NSTemporaryDirectory();
NSLog(@"Outputting to %@", tempDir); NSLog(@"Outputting to %@", tempDir);
for(int operation = 0; operation < 512; operation++) { for(int operation = 0; operation < (has_emulation ? 512 : 256); operation++) {
// Make tests repeatable, at least for any given instance of // Make tests repeatable, at least for any given instance of
// the runtime. // the runtime.
srand(65816 + operation); srand(65816 + operation);
@ -166,7 +174,10 @@ void print_ram(FILE *file, const std::unordered_map<uint32_t, uint8_t> &data) {
const bool is_emulated = operation & 256; const bool is_emulated = operation & 256;
const uint8_t opcode = operation & 255; const uint8_t opcode = operation & 255;
NSString *const targetName = [NSString stringWithFormat:@"%@%02x.%c.json", tempDir, opcode, is_emulated ? 'e' : 'n']; NSString *const targetName =
has_emulation ?
[NSString stringWithFormat:@"%@%02x.%c.json", tempDir, opcode, is_emulated ? 'e' : 'n'] :
[NSString stringWithFormat:@"%@%02x.json", tempDir, opcode];
FILE *const target = fopen(targetName.UTF8String, "wt"); FILE *const target = fopen(targetName.UTF8String, "wt");
bool is_first_test = true; bool is_first_test = true;
@ -186,21 +197,28 @@ void print_ram(FILE *file, const std::unordered_map<uint32_t, uint8_t> &data) {
handler.processor.set_value_of(Register::Y, rand() >> 8); handler.processor.set_value_of(Register::Y, rand() >> 8);
handler.processor.set_value_of(Register::ProgramCounter, rand() >> 8); handler.processor.set_value_of(Register::ProgramCounter, rand() >> 8);
handler.processor.set_value_of(Register::StackPointer, rand() >> 8); handler.processor.set_value_of(Register::StackPointer, rand() >> 8);
handler.processor.set_value_of(Register::DataBank, rand() >> 8);
handler.processor.set_value_of(Register::ProgramBank, rand() >> 8);
handler.processor.set_value_of(Register::Direct, rand() >> 8);
// ... except for emulation mode, which is a given. if(has_emulation) {
// And is set last to ensure proper internal state is applied. handler.processor.set_value_of(Register::DataBank, rand() >> 8);
handler.processor.set_value_of(Register::EmulationFlag, is_emulated); handler.processor.set_value_of(Register::ProgramBank, rand() >> 8);
handler.processor.set_value_of(Register::Direct, rand() >> 8);
// ... except for emulation mode, which is a given.
// And is set last to ensure proper internal state is applied.
handler.processor.set_value_of(Register::EmulationFlag, is_emulated);
}
// Establish the opcode. // Establish the opcode.
handler.setup(opcode); handler.setup(opcode);
// Dump initial state. // Dump initial state.
fprintf(target, "{ \"name\": \"%02x %c %d\", ", opcode, is_emulated ? 'e' : 'n', test + 1); if(has_emulation) {
fprintf(target, "{ \"name\": \"%02x %c %d\", ", opcode, is_emulated ? 'e' : 'n', test + 1);
} else {
fprintf(target, "{ \"name\": \"%02x %d\", ", opcode, test + 1);
}
fprintf(target, "\"initial\": {"); fprintf(target, "\"initial\": {");
print_registers(target, handler.processor, 0); print_registers<has_emulation>(target, handler.processor, 0);
// Run to the second opcode fetch. // Run to the second opcode fetch.
try { try {
@ -212,7 +230,7 @@ void print_ram(FILE *file, const std::unordered_map<uint32_t, uint8_t> &data) {
// Dump final state. // Dump final state.
fprintf(target, "}, \"final\": {"); fprintf(target, "}, \"final\": {");
print_registers(target, handler.processor, handler.pc_overshoot); print_registers<has_emulation>(target, handler.processor, handler.pc_overshoot);
print_ram(target, handler.ram); print_ram(target, handler.ram);
fprintf(target, "}, "); fprintf(target, "}, ");
@ -247,12 +265,6 @@ void print_ram(FILE *file, const std::unordered_map<uint32_t, uint8_t> &data) {
assert(false); assert(false);
} }
using ExtendedBusOutput = CPU::WDC65816::ExtendedBusOutput;
const bool emulation = cycle.extended_bus & ExtendedBusOutput::Emulation;
const bool memory_size = cycle.extended_bus & ExtendedBusOutput::MemorySize;
const bool index_size = cycle.extended_bus & ExtendedBusOutput::IndexSize;
const bool memory_lock = cycle.extended_bus & ExtendedBusOutput::MemoryLock;
fprintf(target, "["); fprintf(target, "[");
if(cycle.address) { if(cycle.address) {
fprintf(target, "%d, ", *cycle.address); fprintf(target, "%d, ", *cycle.address);
@ -264,16 +276,31 @@ void print_ram(FILE *file, const std::unordered_map<uint32_t, uint8_t> &data) {
} else { } else {
fprintf(target, "null, "); fprintf(target, "null, ");
} }
fprintf(target, "\"%c%c%c%c%c%c%c%c\"]",
vda ? 'd' : '-', if(has_emulation) {
vpa ? 'p' : '-', using ExtendedBusOutput = CPU::WDC65816::ExtendedBusOutput;
vpb ? 'v' : '-', const bool emulation = cycle.extended_bus & ExtendedBusOutput::Emulation;
wait ? '-' : (read ? 'r' : 'w'), const bool memory_size = cycle.extended_bus & ExtendedBusOutput::MemorySize;
wait ? '-' : (emulation ? 'e' : '-'), const bool index_size = cycle.extended_bus & ExtendedBusOutput::IndexSize;
wait ? '-' : (memory_size ? 'm' : '-'), const bool memory_lock = cycle.extended_bus & ExtendedBusOutput::MemoryLock;
wait ? '-' : (index_size ? 'x' : '-'),
wait ? '-' : (memory_lock ? 'l' : '-') fprintf(target, "\"%c%c%c%c%c%c%c%c\"]",
); vda ? 'd' : '-',
vpa ? 'p' : '-',
vpb ? 'v' : '-',
wait ? '-' : (read ? 'r' : 'w'),
wait ? '-' : (emulation ? 'e' : '-'),
wait ? '-' : (memory_size ? 'm' : '-'),
wait ? '-' : (index_size ? 'x' : '-'),
wait ? '-' : (memory_lock ? 'l' : '-')
);
} else {
if(read) {
fprintf(target, "\"read\"]");
} else {
fprintf(target, "\"write\"]");
}
}
} }
// Terminate object. // Terminate object.
@ -285,7 +312,7 @@ void print_ram(FILE *file, const std::unordered_map<uint32_t, uint8_t> &data) {
} }
} }
@end }
// MARK: - Existing test evaluator. // MARK: - Existing test evaluator.
@ -296,7 +323,7 @@ void print_ram(FILE *file, const std::unordered_map<uint32_t, uint8_t> &data) {
// A generator for tests; not intended to be a permanent fixture. // A generator for tests; not intended to be a permanent fixture.
//- (void)testGenerate { //- (void)testGenerate {
// [[[TestGenerator alloc] init] generate]; // generate<CPU::MOS6502Esque::Type::TWDC65816>();
//} //}
@end @end

View File

@ -124,6 +124,12 @@ class ProcessorBase: public ProcessorStorage {
@returns @c true if the 6502 is jammed; @c false otherwise. @returns @c true if the 6502 is jammed; @c false otherwise.
*/ */
inline bool is_jammed() const; inline bool is_jammed() const;
/*!
FOR TESTING PURPOSES ONLY: forces the processor into a state where
the next thing it intends to do is fetch a new opcode.
*/
inline void restart_operation_fetch();
}; };
/*! /*!

View File

@ -728,3 +728,8 @@ void ProcessorBase::set_value_of(Register r, uint16_t value) {
default: break; default: break;
} }
} }
void ProcessorBase::restart_operation_fetch() {
scheduled_program_counter_ = nullptr;
next_bus_operation_ = BusOperation::None;
}

View File

@ -9,6 +9,7 @@
#ifndef _502Selector_h #ifndef _502Selector_h
#define _502Selector_h #define _502Selector_h
#include "6502Esque.hpp"
#include "../6502/6502.hpp" #include "../6502/6502.hpp"
#include "../65816/65816.hpp" #include "../65816/65816.hpp"
@ -45,6 +46,32 @@ template <typename BusHandler, bool uses_ready_line> class Processor<Type::TWDC6
template <Type processor_type> class BusHandlerT: public BusHandler<uint16_t> {}; template <Type processor_type> class BusHandlerT: public BusHandler<uint16_t> {};
template <> class BusHandlerT<Type::TWDC65816>: public BusHandler<uint32_t> {}; template <> class BusHandlerT<Type::TWDC65816>: public BusHandler<uint32_t> {};
/*
Query for implemented registers.
*/
constexpr bool has(Type processor_type, Register r) {
switch(r) {
case Register::LastOperationAddress:
case Register::ProgramCounter:
case Register::StackPointer:
case Register::Flags:
case Register::A:
case Register::X:
case Register::Y:
return true;
case Register::EmulationFlag:
case Register::DataBank:
case Register::ProgramBank:
case Register::Direct:
return processor_type == Type::TWDC65816;
}
}
constexpr bool has_extended_bus_output(Type processor_type) {
return processor_type == Type::TWDC65816;
}
} }
#endif /* _502Selector_h */ #endif /* _502Selector_h */