diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 5ced38bd7..1744ac269 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 4B0333AF2094081A0050B93D /* AppleDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0333AD2094081A0050B93D /* AppleDSK.cpp */; }; 4B0333B02094081A0050B93D /* AppleDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0333AD2094081A0050B93D /* AppleDSK.cpp */; }; 4B049CDD1DA3C82F00322067 /* BCDTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B049CDC1DA3C82F00322067 /* BCDTest.swift */; }; + 4B04C899285E3DC800AA8FD6 /* 65816ComparativeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B04C898285E3DC800AA8FD6 /* 65816ComparativeTests.mm */; }; 4B051C912669C90B00CA44E8 /* ROMCatalogue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B051C5826670A9300CA44E8 /* ROMCatalogue.cpp */; }; 4B051C922669C90B00CA44E8 /* ROMCatalogue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B051C5826670A9300CA44E8 /* ROMCatalogue.cpp */; }; 4B051C93266D9D6900CA44E8 /* ROMImages in Resources */ = {isa = PBXBuildFile; fileRef = 4BC9DF441D044FCA00F44158 /* ROMImages */; }; @@ -1110,6 +1111,7 @@ 4B047075201ABC180047AB0D /* Cartridge.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Cartridge.hpp; sourceTree = ""; }; 4B049CDC1DA3C82F00322067 /* BCDTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BCDTest.swift; sourceTree = ""; }; 4B04B65622A58CB40006AB58 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = ""; }; + 4B04C898285E3DC800AA8FD6 /* 65816ComparativeTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = 65816ComparativeTests.mm; sourceTree = ""; }; 4B051C5826670A9300CA44E8 /* ROMCatalogue.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ROMCatalogue.cpp; sourceTree = ""; }; 4B051C5926670A9300CA44E8 /* ROMCatalogue.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ROMCatalogue.hpp; sourceTree = ""; }; 4B051C94266EF50200CA44E8 /* AppleIIController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppleIIController.swift; sourceTree = ""; }; @@ -4230,6 +4232,7 @@ children = ( 4B85322922778E4200F26553 /* Comparative68000.hpp */, 4B90467222C6FA31000E2074 /* TestRunner68000.hpp */, + 4B04C898285E3DC800AA8FD6 /* 65816ComparativeTests.mm */, 4B90467522C6FD6E000E2074 /* 68000ArithmeticTests.mm */, 4B9D0C4A22C7D70900DE1AD3 /* 68000BCDTests.mm */, 4B90467322C6FADD000E2074 /* 68000BitwiseTests.mm */, @@ -6053,6 +6056,7 @@ 4B778EF023A5D68C0000D260 /* 68000Storage.cpp in Sources */, 4B7752B228217EAE0073E2C5 /* StaticAnalyser.cpp in Sources */, 4B7752BC28217F1D0073E2C5 /* Disk.cpp in Sources */, + 4B04C899285E3DC800AA8FD6 /* 65816ComparativeTests.mm in Sources */, 4B01A6881F22F0DB001FD6E3 /* Z80MemptrTests.swift in Sources */, 4B778EFA23A5EB790000D260 /* DMK.cpp in Sources */, 4BEE1EC122B5E2FD000A26A6 /* Encoder.cpp in Sources */, diff --git a/OSBindings/Mac/Clock SignalTests/65816ComparativeTests.mm b/OSBindings/Mac/Clock SignalTests/65816ComparativeTests.mm new file mode 100644 index 000000000..8f1d49be4 --- /dev/null +++ b/OSBindings/Mac/Clock SignalTests/65816ComparativeTests.mm @@ -0,0 +1,302 @@ +// +// 65816ComparativeTests.m +// Clock SignalTests +// +// Created by Thomas Harte on 18/06/2022. +// Copyright © 2022 Thomas Harte. All rights reserved. +// + +#include "65816.hpp" + +#import +#include +#include +#include +#include + +namespace { + +struct StopException {}; + +struct BusHandler: public CPU::MOS6502Esque::BusHandler { + // Use a map to store RAM contents, in order to preserve initialised state. + std::unordered_map ram; + std::unordered_map inventions; + + Cycles perform_bus_operation(CPU::MOS6502Esque::BusOperation operation, uint32_t address, uint8_t *value) { + // Record the basics of the operation. + auto &cycle = cycles.emplace_back(); + cycle.operation = operation; + cycle.extended_bus = processor.get_extended_bus_output(); + + // Perform the operation, and fill in the cycle's value. + using BusOperation = CPU::MOS6502Esque::BusOperation; + auto ram_value = ram.find(address); + switch(operation) { + case BusOperation::ReadOpcode: + if(initial_pc && (*initial_pc != address || !allow_pc_repetition)) { + cycles.pop_back(); + pc_overshoot = -1; + throw StopException(); + } + initial_pc = address; + [[fallthrough]]; + + case BusOperation::Read: + case BusOperation::ReadProgram: + case BusOperation::ReadVector: + cycle.address = address; + if(ram_value != ram.end()) { + cycle.value = *value = ram_value->second; + } else { + cycle.value = *value = uint8_t(rand() >> 8); + inventions[address] = ram[address] = *cycle.value; + } + break; + + case BusOperation::Write: + cycle.address = address; + cycle.value = ram[address] = *value; + break; + + case BusOperation::Ready: + case BusOperation::None: + throw StopException(); + break; + + case BusOperation::InternalOperationWrite: + cycle.value = *value = ram_value->second; + [[fallthrough]]; + + case BusOperation::InternalOperationRead: + cycle.address = address; + break; + + default: assert(false); + } + + // Don't occupy any bonus time. + return Cycles(1); + } + + void setup(uint8_t opcode) { + ram.clear(); + inventions.clear(); + cycles.clear(); + pc_overshoot = 0; + initial_pc = std::nullopt; + + // For MVP or MVN, keep tracking fetches via the same location. + // For other instructions, don't. That's to avoid endless loops + // for flow control that happens to jump back to where it began. + allow_pc_repetition = opcode == 0x54 || opcode == 0x44; + + using Register = CPU::MOS6502Esque::Register; + const uint32_t pc = + processor.get_value_of_register(Register::ProgramCounter) | + (processor.get_value_of_register(Register::ProgramBank) << 16); + inventions[pc] = ram[pc] = opcode; + } + + int pc_overshoot = 0; + std::optional initial_pc; + bool allow_pc_repetition = false; + + struct Cycle { + CPU::MOS6502Esque::BusOperation operation; + std::optional address; + std::optional value; + int extended_bus; + }; + std::vector cycles; + + CPU::WDC65816::Processor processor; + + BusHandler() : processor(*this) { + // Never run the official reset procedure. + processor.set_power_on(false); + } +}; + +template void print_registers(FILE *file, const Processor &processor, int pc_offset) { + using Register = CPU::MOS6502Esque::Register; + fprintf(file, "\"pc\": %d, ", (processor.get_value_of_register(Register::ProgramCounter) + pc_offset) & 65535); + fprintf(file, "\"s\": %d, ", processor.get_value_of_register(Register::StackPointer)); + fprintf(file, "\"p\": %d, ", processor.get_value_of_register(Register::Flags)); + fprintf(file, "\"a\": %d, ", processor.get_value_of_register(Register::A)); + fprintf(file, "\"x\": %d, ", processor.get_value_of_register(Register::X)); + fprintf(file, "\"y\": %d, ", processor.get_value_of_register(Register::Y)); + fprintf(file, "\"dbr\": %d, ", processor.get_value_of_register(Register::DataBank)); + fprintf(file, "\"d\": %d, ", processor.get_value_of_register(Register::Direct)); + fprintf(file, "\"pbr\": %d, ", processor.get_value_of_register(Register::ProgramBank)); + fprintf(file, "\"e\": %d, ", processor.get_value_of_register(Register::EmulationFlag)); +} + +void print_ram(FILE *file, const std::unordered_map &data) { + fprintf(file, "\"ram\": ["); + bool is_first = true; + for(const auto &pair: data) { + if(!is_first) fprintf(file, ", "); + is_first = false; + fprintf(file, "[%d, %d]", pair.first, pair.second); + } + fprintf(file, "]"); +} + +} + +// MARK: - New test generator. + +@interface TestGenerator : NSObject +@end + +@implementation TestGenerator + +- (void)generate { + BusHandler handler; + + // Make tests repeatable, at least for any given instance of + // the runtime. + srand(65816); + + NSString *const tempDir = NSTemporaryDirectory(); + NSLog(@"Outputting to %@", tempDir); + + for(int operation = 0; operation < 512; operation++) { + const bool is_emulated = operation & 256; + const uint8_t opcode = operation & 255; + + NSString *const targetName = [NSString stringWithFormat:@"%@%02x.%c.json", tempDir, opcode, is_emulated ? 'e' : 'n']; + FILE *const target = fopen(targetName.UTF8String, "wt"); + + bool is_first_test = true; + fprintf(target, "["); + for(int test = 0; test < 10'000; test++) { + if(!is_first_test) fprintf(target, ",\n"); + is_first_test = false; + + // Ensure processor's next action is an opcode fetch. + handler.processor.restart_operation_fetch(); + + // Randomise most of the processor state... + using Register = CPU::MOS6502Esque::Register; + handler.processor.set_value_of_register(Register::A, rand() >> 8); + handler.processor.set_value_of_register(Register::Flags, rand() >> 8); + handler.processor.set_value_of_register(Register::X, rand() >> 8); + handler.processor.set_value_of_register(Register::Y, rand() >> 8); + handler.processor.set_value_of_register(Register::ProgramCounter, rand() >> 8); + handler.processor.set_value_of_register(Register::StackPointer, rand() >> 8); + handler.processor.set_value_of_register(Register::DataBank, rand() >> 8); + handler.processor.set_value_of_register(Register::ProgramBank, rand() >> 8); + handler.processor.set_value_of_register(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(Register::EmulationFlag, is_emulated); + + // Establish the opcode. + handler.setup(opcode); + + // Dump initial state. + fprintf(target, "{ \"name\": \"%02x %c %d\", ", opcode, is_emulated ? 'e' : 'n', test + 1); + fprintf(target, "\"initial\": {"); + print_registers(target, handler.processor, 0); + + // Run to the second opcode fetch. + try { + handler.processor.run_for(Cycles(100)); + } catch (const StopException &) {} + + // Dump all inventions as initial memory state. + print_ram(target, handler.inventions); + + // Dump final state. + fprintf(target, "}, \"final\": {"); + print_registers(target, handler.processor, handler.pc_overshoot); + print_ram(target, handler.ram); + fprintf(target, "}, "); + + // Append cycles. + fprintf(target, "\"cycles\": ["); + + bool is_first_cycle = true; + for(const auto &cycle: handler.cycles) { + if(!is_first_cycle) fprintf(target, ","); + is_first_cycle = false; + + bool vda = false; + bool vpa = false; + bool vpb = false; + bool read = false; + bool wait = false; + using BusOperation = CPU::MOS6502Esque::BusOperation; + switch(cycle.operation) { + case BusOperation::Read: read = vda = true; break; + case BusOperation::ReadOpcode: read = vda = vpa = true; break; + case BusOperation::ReadProgram: read = vpa = true; break; + case BusOperation::ReadVector: read = vpb = vda = true; break; + case BusOperation::InternalOperationRead: read = true; break; + + case BusOperation::Write: vda = true; break; + case BusOperation::InternalOperationWrite: break; + + case BusOperation::None: + case BusOperation::Ready: wait = true; break; + + default: + 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, "["); + if(cycle.address) { + fprintf(target, "%d, ", *cycle.address); + } else { + fprintf(target, "null, "); + } + if(cycle.value) { + fprintf(target, "%d, ", *cycle.value); + } else { + fprintf(target, "null, "); + } + 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' : '-') + ); + } + + // Terminate object. + fprintf(target, "]}"); + } + + fprintf(target, "]"); + fclose(target); + } +} + +@end + +// MARK: - Existing test evaluator. + +@interface WDC65816ComparativeTests : XCTestCase +@end + +@implementation WDC65816ComparativeTests + +// A generator for tests; not intended to be a permanent fixture. +//- (void)testGenerate { +// [[[TestGenerator alloc] init] generate]; +//} + +@end diff --git a/OSBindings/Mac/Clock SignalTests/65816kromTests.swift b/OSBindings/Mac/Clock SignalTests/65816kromTests.swift index 294e6bca2..154ea68d2 100644 --- a/OSBindings/Mac/Clock SignalTests/65816kromTests.swift +++ b/OSBindings/Mac/Clock SignalTests/65816kromTests.swift @@ -84,11 +84,7 @@ class Krom65816Tests: XCTestCase { cpuState += String(format: "A:%04x ", machine.value(for: .A)) cpuState += String(format: "X:%04x ", machine.value(for: .X)) cpuState += String(format: "Y:%04x ", machine.value(for: .Y)) - if emulationFlag { - cpuState += String(format: "S:01%02x ", machine.value(for: .stackPointer)) - } else { - cpuState += String(format: "S:%04x ", machine.value(for: .stackPointer)) - } + cpuState += String(format: "S:%04x ", machine.value(for: .stackPointer)) cpuState += String(format: "D:%04x ", machine.value(for: .direct)) cpuState += String(format: "DB:%02x ", machine.value(for: .dataBank)) diff --git a/Processors/6502Esque/6502Esque.hpp b/Processors/6502Esque/6502Esque.hpp index 8f5c75589..84edc52d2 100644 --- a/Processors/6502Esque/6502Esque.hpp +++ b/Processors/6502Esque/6502Esque.hpp @@ -77,7 +77,7 @@ enum BusOperation { /// 65816: indicates that a read was signalled with VPA. ReadProgram, /// 6502: never signalled. - /// 65816: indicates that a read was signalled with VPB. + /// 65816: indicates that a read was signalled with VPB and VDA. ReadVector, /// 6502: never signalled. /// 65816: indicates that a read was signalled, but neither VDA nor VPA were active. diff --git a/Processors/6502Esque/Implementation/LazyFlags.hpp b/Processors/6502Esque/Implementation/LazyFlags.hpp index 756d7a779..2169d8c24 100644 --- a/Processors/6502Esque/Implementation/LazyFlags.hpp +++ b/Processors/6502Esque/Implementation/LazyFlags.hpp @@ -64,7 +64,7 @@ struct LazyFlags { } uint8_t get() const { - return carry | overflow | (inverse_interrupt ^ Flag::Interrupt) | (negative_result & 0x80) | (zero_result ? 0 : Flag::Zero) | Flag::Always | decimal; + return carry | overflow | (inverse_interrupt ^ Flag::Interrupt) | (negative_result & 0x80) | (zero_result ? 0 : Flag::Zero) | Flag::Always | Flag::Break | decimal; } LazyFlags() { diff --git a/Processors/65816/65816.hpp b/Processors/65816/65816.hpp index e65b7cd45..d9390113e 100644 --- a/Processors/65816/65816.hpp +++ b/Processors/65816/65816.hpp @@ -45,7 +45,8 @@ class ProcessorBase: protected ProcessorStorage { inline bool get_is_resetting() const; /*! - Returns the current state of all lines not ordinarily pushed to the BusHandler. + Returns the current state of all lines not ordinarily pushed to the BusHandler, + as listed in the ExtendedBusOutput enum. */ inline int get_extended_bus_output(); @@ -54,6 +55,12 @@ class ProcessorBase: protected ProcessorStorage { */ 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(); + void set_value_of_register(Register r, uint16_t value); uint16_t get_value_of_register(Register r) const; }; diff --git a/Processors/65816/Implementation/65816Base.cpp b/Processors/65816/Implementation/65816Base.cpp index f61b62248..5dff89928 100644 --- a/Processors/65816/Implementation/65816Base.cpp +++ b/Processors/65816/Implementation/65816Base.cpp @@ -14,7 +14,10 @@ uint16_t ProcessorBase::get_value_of_register(Register r) const { switch (r) { case Register::ProgramCounter: return registers_.pc; case Register::LastOperationAddress: return last_operation_pc_; - case Register::StackPointer: return registers_.s.full & (registers_.emulation_flag ? 0xff : 0xffff); + case Register::StackPointer: + return + (registers_.s.full & (registers_.emulation_flag ? 0xff : 0xffff)) | + (registers_.emulation_flag ? 0x100 : 0x000); case Register::Flags: return get_flags(); case Register::A: return registers_.a.full; case Register::X: return registers_.x.full; diff --git a/Processors/65816/Implementation/65816Implementation.hpp b/Processors/65816/Implementation/65816Implementation.hpp index f3128e550..ea07667fd 100644 --- a/Processors/65816/Implementation/65816Implementation.hpp +++ b/Processors/65816/Implementation/65816Implementation.hpp @@ -9,7 +9,7 @@ template void Processor::run_for(const Cycles cycles) { #define perform_bus(address, value, operation) \ - bus_address_ = address; \ + bus_address_ = address & 0xff'ffff; \ bus_value_ = value; \ bus_operation_ = operation @@ -99,6 +99,14 @@ template void Processor void Processor void Processor void Processor void Processor void Processor void Processor void Processor void Processor> registers_.m_shift) & Flag::Overflow; break; case BITimm: @@ -828,7 +842,10 @@ template void Processor void Processor mask) result -= adjustment; \ - borrow = (result > mask) ? carry : 0; \ - result &= (carry - 1); + int result = registers_.flags.carry; + uint16_t partials = 0; + +#define nibble(mask, adjustment, carry) \ + result += (a & mask) + (data_buffer_.value & mask); \ + partials += result & mask; \ + result -= ((result - carry) >> 16) & adjustment; \ + result &= (carry & ~(result >> 1)) | (carry - 1); + + // i.e. add the next nibble to that in the accumulator, with carry, and + // store it to result. Keep a copy for the partials. + // + // If result is less than carry, subtract adjustment. + // + // Allow onward carry if the bit immediately above this nibble is 1, and + // the current partial result is positive. nibble(0x000f, 0x0006, 0x00010); nibble(0x00f0, 0x0060, 0x00100); @@ -922,9 +946,9 @@ template void Processor> (1 + registers_.m_shift))&0x40; + registers_.flags.overflow = (( (partials ^ registers_.a.full) & (partials ^ data_buffer_.value) ) >> (1 + registers_.m_shift))&0x40; registers_.flags.set_nz(uint16_t(result), registers_.m_shift); - registers_.flags.carry = ((borrow >> 16)&1)^1; + registers_.flags.carry = (result >> (8 + registers_.m_shift))&1; LDA(result); break; @@ -941,10 +965,10 @@ template void Processor= limit) result = ((result + (adjustment)) & (carry - 1)) + carry; +#define nibble(mask, limit, adjustment, carry) \ + result += (a & mask) + (data_buffer_.value & mask); \ + partials += result & mask; \ + if(result >= limit) result = ((result + adjustment) & (carry - 1)) + carry; nibble(0x000f, 0x000a, 0x0006, 0x00010); nibble(0x00f0, 0x00a0, 0x0060, 0x00100); @@ -953,8 +977,7 @@ template void Processor> (1 + registers_.m_shift))&0x40; - + registers_.flags.overflow = (( (partials ^ registers_.a.full) & (partials ^ data_buffer_.value) ) >> (1 + registers_.m_shift))&0x40; } else { result = int(a + data_buffer_.value + registers_.flags.carry); registers_.flags.overflow = (( (uint16_t(result) ^ registers_.a.full) & (uint16_t(result) ^ data_buffer_.value) ) >> (1 + registers_.m_shift))&0x40; @@ -1065,3 +1088,10 @@ int ProcessorBase::get_extended_bus_output() { (registers_.mx_flags[1] ? ExtendedBusOutput::IndexSize : 0) | (registers_.emulation_flag ? ExtendedBusOutput::Emulation : 0); } + +void ProcessorBase::restart_operation_fetch() { + // Find a OperationMoveToNextProgram, so that the main loop can make + // relevant decisions. + next_op_ = micro_ops_.data(); + while(*next_op_ != OperationMoveToNextProgram) ++next_op_; +} diff --git a/Processors/65816/Implementation/65816Storage.cpp b/Processors/65816/Implementation/65816Storage.cpp index 2978cb2b7..5249be667 100644 --- a/Processors/65816/Implementation/65816Storage.cpp +++ b/Processors/65816/Implementation/65816Storage.cpp @@ -195,8 +195,8 @@ struct CPU::WDC65816::ProcessorStorageConstructor { if(!is8bit) target(CycleFetchIncrementData); // Data low. target(CycleFetchData); // Data [high]. - if(!is8bit) target(CycleFetchDataThrowaway); // 16-bit: reread final byte of data. - else target(CycleStoreDataThrowaway); // 8-bit rewrite final byte of data. + target(CycleStoreOrFetchDataThrowaway); // Native mode: reread final byte of data. + // Emulated mode: rewrite final byte of data. target(OperationPerform); // Perform operation within the data buffer. @@ -320,7 +320,7 @@ struct CPU::WDC65816::ProcessorStorageConstructor { target(OperationCopyPBRToData); // Copy PBR to the data register. target(CyclePush); // PBR. - target(CycleAccessStack); // IO. + target(CycleFetchPreviousThrowaway); // IO. target(CycleFetchPC); // New PBR. @@ -416,10 +416,10 @@ struct CPU::WDC65816::ProcessorStorageConstructor { // 10a. Direct; d. // (That's zero page in 6502 terms) static void direct(AccessType type, bool is8bit, const std::function &target) { - target(CycleFetchIncrementPC); // DO. + target(CycleFetchIncrementPC); // DO. target(OperationConstructDirect); - target(CycleFetchPCThrowaway); // IO. + target(CycleFetchPreviousPCThrowaway); // IO. read_write(type, is8bit, target); } @@ -430,7 +430,7 @@ struct CPU::WDC65816::ProcessorStorageConstructor { target(CycleFetchIncrementPC); // DO. target(OperationConstructDirect); - target(CycleFetchPCThrowaway); // IO. + target(CycleFetchPreviousPCThrowaway); // IO. read_modify_write(is8bit, target); } @@ -440,9 +440,9 @@ struct CPU::WDC65816::ProcessorStorageConstructor { target(CycleFetchIncrementPC); // DO. target(OperationConstructDirectIndexedIndirect); - target(CycleFetchPCThrowaway); // IO. + target(CycleFetchPreviousPCThrowaway); // IO. - target(CycleFetchPCThrowaway); // IO. + target(CycleFetchPreviousPCThrowaway); // IO. target(CycleFetchIncrementData); // AAL. target(CycleFetchData); // AAH. @@ -458,7 +458,7 @@ struct CPU::WDC65816::ProcessorStorageConstructor { target(CycleFetchIncrementPC); // DO. target(OperationConstructDirect); - target(CycleFetchPCThrowaway); // IO. + target(CycleFetchPreviousPCThrowaway); // IO. target(CycleFetchIncrementData); // AAL. target(CycleFetchData); // AAH. @@ -473,13 +473,17 @@ struct CPU::WDC65816::ProcessorStorageConstructor { target(CycleFetchIncrementPC); // DO. target(OperationConstructDirect); - target(CycleFetchPCThrowaway); // IO. + target(CycleFetchPreviousPCThrowaway); // IO. target(CycleFetchIncrementData); // AAL. target(CycleFetchData); // AAH. target(OperationCopyDataToInstruction); - target(OperationConstructAbsoluteYRead); + if(type == AccessType::Read) { + target(OperationConstructAbsoluteYRead); // Calculate data address, potentially skipping the next fetch. + } else { + target(OperationConstructAbsoluteY); // Calculate data address. + } target(CycleFetchIncorrectDataAddress); // IO. read_write(type, is8bit, target); @@ -490,7 +494,7 @@ struct CPU::WDC65816::ProcessorStorageConstructor { target(CycleFetchIncrementPC); // DO. target(OperationConstructDirect); - target(CycleFetchPCThrowaway); // IO. + target(CycleFetchPreviousPCThrowaway); // IO. target(CycleFetchIncrementData); // AAL. target(CycleFetchIncrementData); // AAH. @@ -506,7 +510,7 @@ struct CPU::WDC65816::ProcessorStorageConstructor { target(CycleFetchIncrementPC); // DO. target(OperationConstructDirectLong); - target(CycleFetchPCThrowaway); // IO. + target(CycleFetchPreviousPCThrowaway); // IO. target(CycleFetchIncrementData); // AAL. target(CycleFetchIncrementData); // AAH. @@ -522,9 +526,9 @@ struct CPU::WDC65816::ProcessorStorageConstructor { target(CycleFetchIncrementPC); // DO. target(OperationConstructDirectX); - target(CycleFetchPCThrowaway); // IO. + target(CycleFetchPreviousPCThrowaway); // IO. - target(CycleFetchPCThrowaway); // IO. + target(CycleFetchPreviousPCThrowaway); // IO. read_write(type, is8bit, target); } @@ -534,9 +538,9 @@ struct CPU::WDC65816::ProcessorStorageConstructor { target(CycleFetchIncrementPC); // DO. target(OperationConstructDirectX); - target(CycleFetchPCThrowaway); // IO. + target(CycleFetchPreviousPCThrowaway); // IO. - target(CycleFetchPCThrowaway); // IO. + target(CycleFetchPreviousPCThrowaway); // IO. read_modify_write(is8bit, target); } @@ -546,9 +550,9 @@ struct CPU::WDC65816::ProcessorStorageConstructor { target(CycleFetchIncrementPC); // DO. target(OperationConstructDirectY); - target(CycleFetchPCThrowaway); // IO. + target(CycleFetchPreviousPCThrowaway); // IO. - target(CycleFetchPCThrowaway); // IO. + target(CycleFetchPreviousPCThrowaway); // IO. read_write(type, is8bit, target); } @@ -563,7 +567,7 @@ struct CPU::WDC65816::ProcessorStorageConstructor { static void immediate_rep_sep(AccessType, bool, const std::function &target) { target(CycleFetchIncrementPC); // IDL. - target(CycleFetchPCThrowaway); // "Add 1 cycle for REP and SEP" + target(CycleFetchPreviousPCThrowaway); // "Add 1 cycle for REP and SEP" target(OperationPerform); } @@ -594,34 +598,34 @@ struct CPU::WDC65816::ProcessorStorageConstructor { // 20. Relative; r. static void relative(AccessType, bool, const std::function &target) { - target(CycleFetchIncrementPC); // Offset. + target(CycleFetchIncrementPC); // Offset. - target(OperationPerform); // The branch instructions will all skip one or three - // of the next cycles, depending on the effect of - // the jump. It'll also calculate the correct target - // address, placing it into the data buffer. + target(OperationPerform); // The branch instructions will all skip one or three + // of the next cycles, depending on the effect of + // the jump. It'll also calculate the correct target + // address, placing it into the data buffer. - target(CycleFetchPCThrowaway); // IO. - target(CycleFetchPCThrowaway); // IO. + target(CycleFetchPreviousPCThrowaway); // IO. + target(CycleFetchPreviousPCThrowaway); // IO. - target(OperationCopyDataToPC); // Install the address that was calculated above. + target(OperationCopyDataToPC); // Install the address that was calculated above. } // 21. Relative long; rl. static void relative_long(AccessType, bool, const std::function &target) { - target(CycleFetchIncrementPC); // Offset low. - target(CycleFetchIncrementPC); // Offset high. - target(CycleFetchPCThrowaway); // IO. + target(CycleFetchIncrementPC); // Offset low. + target(CycleFetchIncrementPC); // Offset high. + target(CycleFetchPreviousPCThrowaway); // IO. - target(OperationPerform); // [BRL] + target(OperationPerform); // [BRL] } // 22a. Stack; s, abort/irq/nmi/res. // // Combined here with reset, which is the same sequence but with a different stack access. static void stack_exception_impl(AccessType, bool, const std::function &target, MicroOp stack_op) { - target(CycleFetchPCThrowaway); // IO. - target(CycleFetchPCThrowaway); // IO. + target(CycleFetchPreviousPCThrowaway); // IO. + target(CycleFetchPreviousPCThrowaway); // IO. target(OperationPrepareException); // Populates the data buffer; if the exception is a // reset then switches to the reset tail program. @@ -686,27 +690,27 @@ struct CPU::WDC65816::ProcessorStorageConstructor { // 22e. Stack; s, PEI. static void stack_pei(AccessType, bool, const std::function &target) { - target(CycleFetchIncrementPC); // DO. + target(CycleFetchIncrementPC); // DO. target(OperationConstructDirect); - target(CycleFetchPCThrowaway); // IO. + target(CycleFetchPreviousPCThrowaway); // IO. - target(CycleFetchIncrementData); // AAL. - target(CycleFetchData); // AAH. - target(CyclePush); // AAH. - target(CyclePush); // AAL. + target(CycleFetchIncrementData); // AAL. + target(CycleFetchData); // AAH. + target(CyclePush); // AAH. + target(CyclePush); // AAL. } // 22f. Stack; s, PER. static void stack_per(AccessType, bool, const std::function &target) { - target(CycleFetchIncrementPC); // Offset low. - target(CycleFetchIncrementPC); // Offset high. - target(CycleFetchPCThrowaway); // IO. + target(CycleFetchIncrementPC); // Offset low. + target(CycleFetchIncrementPC); // Offset high. + target(CycleFetchPreviousPCThrowaway); // IO. target(OperationConstructPER); - target(CyclePush); // AAH. - target(CyclePush); // AAL. + target(CyclePush); // AAH. + target(CyclePush); // AAL. } // 22g. Stack; s, RTI. @@ -724,20 +728,20 @@ struct CPU::WDC65816::ProcessorStorageConstructor { // 22h. Stack; s, RTS. static void stack_rts(AccessType, bool, const std::function &target) { - target(CycleFetchPCThrowaway); // IO. - target(CycleFetchPCThrowaway); // IO. + target(CycleFetchPCThrowaway); // IO. + target(CycleFetchPCThrowaway); // IO. - target(CyclePull); // PCL. - target(CyclePull); // PCH. - target(CycleAccessStack); // IO. + target(CyclePull); // PCL. + target(CyclePull); // PCH. + target(CycleFetchPreviousThrowaway); // IO. - target(OperationPerform); // [RTS] + target(OperationPerform); // [RTS] } // 22i. Stack; s, RTL. static void stack_rtl(AccessType, bool, const std::function &target) { - target(CycleFetchIncrementPC); // IO. - target(CycleFetchIncrementPC); // IO. + target(CycleFetchPCThrowaway); // IO. + target(CycleFetchPCThrowaway); // IO. target(CyclePull); // New PCL. target(CyclePull); // New PCH. @@ -771,8 +775,8 @@ struct CPU::WDC65816::ProcessorStorageConstructor { // 23. Stack Relative; d, s. static void stack_relative(AccessType type, bool is8bit, const std::function &target) { - target(CycleFetchIncrementPC); // SO. - target(CycleFetchPCThrowaway); // IO. + target(CycleFetchIncrementPC); // SO. + target(CycleFetchPreviousPCThrowaway); // IO. target(OperationConstructStackRelative); read_write(type, is8bit, target); @@ -780,13 +784,13 @@ struct CPU::WDC65816::ProcessorStorageConstructor { // 24. Stack Relative Indirect Indexed (d, s), y. static void stack_relative_indexed_indirect(AccessType type, bool is8bit, const std::function &target) { - target(CycleFetchIncrementPC); // SO. - target(CycleFetchPCThrowaway); // IO. + target(CycleFetchIncrementPC); // SO. + target(CycleFetchPreviousPCThrowaway); // IO. target(OperationConstructStackRelative); - target(CycleFetchIncrementData); // AAL. - target(CycleFetchData); // AAH. - target(CycleFetchDataThrowaway); // IO. + target(CycleFetchIncrementData); // AAL. + target(CycleFetchData); // AAH. + target(CycleFetchDataThrowaway); // IO. target(OperationConstructStackRelativeIndexedIndirect); read_write(type, is8bit, target); diff --git a/Processors/65816/Implementation/65816Storage.hpp b/Processors/65816/Implementation/65816Storage.hpp index 01bfe57cf..bac7fda8b 100644 --- a/Processors/65816/Implementation/65816Storage.hpp +++ b/Processors/65816/Implementation/65816Storage.hpp @@ -13,6 +13,10 @@ enum MicroOp: uint8_t { CycleFetchPC, /// Fetches a byte from the program counter without incrementing it, and throws it away. CycleFetchPCThrowaway, + /// Fetches a byte from (PC - 1), and throws it away; useful for IO cycles that immediately follow incremented PCs. + CycleFetchPreviousPCThrowaway, + /// Fetches from whichever address was used in the last bus cycle, and throws away the result. + CycleFetchPreviousThrowaway, /// The same as CycleFetchIncrementPC but indicates valid program address rather than valid data address. CycleFetchOpcode, @@ -37,8 +41,9 @@ enum MicroOp: uint8_t { /// Stores a byte from the data buffer. CycleStoreData, - /// Stores the most recent byte placed into the data buffer without removing it. - CycleStoreDataThrowaway, + /// Emulated mode: stores the most recent byte placed into the data buffer without removing it; + /// Native mode: performs CycleFetchDataThrowaway. + CycleStoreOrFetchDataThrowaway, /// Stores a byte to the data address from the data buffer and increments the data address. CycleStoreIncrementData, /// Stores a byte to the data address from the data buffer and decrements the data address. @@ -271,7 +276,7 @@ struct ProcessorStorage { // Registers. RegisterPair16 a; RegisterPair16 x, y; - RegisterPair16 s; + RegisterPair16 s; uint16_t pc; // Flags aplenty.