diff --git a/Analyser/Static/Oric/Target.hpp b/Analyser/Static/Oric/Target.hpp index 66aead3dd..74e686dff 100644 --- a/Analyser/Static/Oric/Target.hpp +++ b/Analyser/Static/Oric/Target.hpp @@ -33,8 +33,14 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl, TargetPlatform::MSX) // DMK Format("do", result.disks, Disk::DiskImageHolder, TargetPlatform::DiskII) // DO Format("dsd", result.disks, Disk::DiskImageHolder, TargetPlatform::Acorn) // DSD - Format("dsk", result.disks, Disk::DiskImageHolder, TargetPlatform::AmstradCPC) // DSK (Amstrad CPC) + Format( "dsk", + result.disks, + Disk::DiskImageHolder, + TargetPlatform::AmstradCPC | TargetPlatform::Oric) // DSK (Amstrad CPC) Format("dsk", result.disks, Disk::DiskImageHolder, TargetPlatform::DiskII) // DSK (Apple II) Format("dsk", result.disks, Disk::DiskImageHolder, TargetPlatform::Macintosh) // DSK (Macintosh, floppy disk) Format("dsk", result.mass_storage_devices, MassStorage::HFV, TargetPlatform::Macintosh) // DSK (Macintosh, hard disk) diff --git a/Machines/Atari/2600/Cartridges/Cartridge.hpp b/Machines/Atari/2600/Cartridges/Cartridge.hpp index 60ffafd7b..90706fb04 100644 --- a/Machines/Atari/2600/Cartridges/Cartridge.hpp +++ b/Machines/Atari/2600/Cartridges/Cartridge.hpp @@ -75,7 +75,7 @@ template class Cartridge: cycles_since_6532_update_ += Cycles(cycles_run_for / 3); bus_extender_.advance_cycles(cycles_run_for / 3); - if(operation != CPU::MOS6502::BusOperation::Ready) { + if(isAccessOperation(operation)) { // give the cartridge a chance to respond to the bus access bus_extender_.perform_bus_operation(operation, address, value); diff --git a/Machines/Oric/Oric.cpp b/Machines/Oric/Oric.cpp index ed8df1aa5..df2050ba5 100644 --- a/Machines/Oric/Oric.cpp +++ b/Machines/Oric/Oric.cpp @@ -20,7 +20,7 @@ #include "../Utility/MemoryFuzzer.hpp" #include "../Utility/StringSerialiser.hpp" -#include "../../Processors/6502/6502.hpp" +#include "../../Processors/6502Esque/6502Selector.hpp" #include "../../Components/6522/6522.hpp" #include "../../Components/AY38910/AY38910.hpp" #include "../../Components/DiskII/DiskII.hpp" @@ -41,6 +41,7 @@ namespace Oric { using DiskInterface = Analyser::Static::Oric::Target::DiskInterface; +using Processor = Analyser::Static::Oric::Target::Processor; using AY = GI::AY38910::AY38910; using Speaker = Outputs::Speaker::LowpassSpeaker; @@ -205,7 +206,7 @@ class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler { Keyboard &keyboard_; }; -template class ConcreteMachine: +template class ConcreteMachine: public MachineTypes::TimedMachine, public MachineTypes::ScanProducer, public MachineTypes::AudioProducer, @@ -424,8 +425,8 @@ template class Co !tape_player_.get_tape()->is_at_end()) { uint8_t next_byte = tape_player_.get_next_byte(!ram_[tape_speed_address_]); - m6502_.set_value_of_register(CPU::MOS6502::A, next_byte); - m6502_.set_value_of_register(CPU::MOS6502::Flags, next_byte ? 0 : CPU::MOS6502::Flag::Zero); + m6502_.set_value_of_register(CPU::MOS6502Esque::A, next_byte); + m6502_.set_value_of_register(CPU::MOS6502Esque::Flags, next_byte ? 0 : CPU::MOS6502::Flag::Zero); *value = 0x60; // i.e. RTS } } else { @@ -656,7 +657,7 @@ template class Co const uint16_t basic_invisible_ram_top_ = 0xffff; const uint16_t basic_visible_ram_top_ = 0xbfff; - CPU::MOS6502::Processor m6502_; + CPU::MOS6502Esque::Processor m6502_; // RAM and ROM std::vector rom_, disk_rom_; @@ -759,13 +760,23 @@ using namespace Oric; Machine *Machine::Oric(const Analyser::Static::Target *target_hint, const ROMMachine::ROMFetcher &rom_fetcher) { auto *const oric_target = dynamic_cast(target_hint); - switch(oric_target->disk_interface) { - default: return new ConcreteMachine(*oric_target, rom_fetcher); - case DiskInterface::Microdisc: return new ConcreteMachine(*oric_target, rom_fetcher); - case DiskInterface::Pravetz: return new ConcreteMachine(*oric_target, rom_fetcher); - case DiskInterface::Jasmin: return new ConcreteMachine(*oric_target, rom_fetcher); - case DiskInterface::BD500: return new ConcreteMachine(*oric_target, rom_fetcher); + +#define DiskInterfaceSwitch(processor) \ + switch(oric_target->disk_interface) { \ + default: return new ConcreteMachine(*oric_target, rom_fetcher); \ + case DiskInterface::Microdisc: return new ConcreteMachine(*oric_target, rom_fetcher); \ + case DiskInterface::Pravetz: return new ConcreteMachine(*oric_target, rom_fetcher); \ + case DiskInterface::Jasmin: return new ConcreteMachine(*oric_target, rom_fetcher); \ + case DiskInterface::BD500: return new ConcreteMachine(*oric_target, rom_fetcher); \ } + + switch(oric_target->processor) { + case Processor::WDC65816: DiskInterfaceSwitch(CPU::MOS6502Esque::Type::TWDC65816); + case Processor::MOS6502: DiskInterfaceSwitch(CPU::MOS6502Esque::Type::T6502); + } + +#undef DiskInterfaceSwitch + } Machine::~Machine() {} diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index b86a62c7d..34ce90551 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -210,6 +210,12 @@ 4B4B1A3D200198CA00A0F866 /* KonamiSCC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4B1A3A200198C900A0F866 /* KonamiSCC.cpp */; }; 4B4DC8211D2C2425003C5BF8 /* Vic20.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4DC81F1D2C2425003C5BF8 /* Vic20.cpp */; }; 4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4DC8291D2C27A4003C5BF8 /* SerialBus.cpp */; }; + 4B4DEC06252BFA56004583AC /* 65816Base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4DEC05252BFA56004583AC /* 65816Base.cpp */; }; + 4B4DEC07252BFA56004583AC /* 65816Base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4DEC05252BFA56004583AC /* 65816Base.cpp */; }; + 4B4DEC08252BFA56004583AC /* 65816Base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4DEC05252BFA56004583AC /* 65816Base.cpp */; }; + 4B4F47652533EA64004245B8 /* suite-a.prg in Resources */ = {isa = PBXBuildFile; fileRef = 4B4F475E2533EA64004245B8 /* suite-a.prg */; }; + 4B4F477C253530B7004245B8 /* Jeek816Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4F477B253530B7004245B8 /* Jeek816Tests.swift */; }; + 4B4F478A25367EDC004245B8 /* 65816AddressingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4F478925367EDC004245B8 /* 65816AddressingTests.swift */; }; 4B50AF80242817F40099BBD7 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B50AF7F242817F40099BBD7 /* QuartzCore.framework */; }; 4B54C0BC1F8D8E790050900F /* KeyboardMachine.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B54C0BB1F8D8E790050900F /* KeyboardMachine.cpp */; }; 4B54C0BF1F8D8F450050900F /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B54C0BD1F8D8F450050900F /* Keyboard.cpp */; }; @@ -861,6 +867,8 @@ 4BF437EE209D0F7E008CBD6B /* SegmentParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF437EC209D0F7E008CBD6B /* SegmentParser.cpp */; }; 4BF437EF209D0F7E008CBD6B /* SegmentParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF437EC209D0F7E008CBD6B /* SegmentParser.cpp */; }; 4BF8D4C82516E27A00BBE21B /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BB8617024E22F4900A00E03 /* Accelerate.framework */; }; + 4BF8D4D5251C11DD00BBE21B /* 65816Storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF8D4D4251C11DD00BBE21B /* 65816Storage.cpp */; }; + 4BF8D4D6251C11DD00BBE21B /* 65816Storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF8D4D4251C11DD00BBE21B /* 65816Storage.cpp */; }; 4BFCA1241ECBDCB400AC40C1 /* AllRAMProcessor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFCA1211ECBDCAF00AC40C1 /* AllRAMProcessor.cpp */; }; 4BFCA1271ECBE33200AC40C1 /* TestMachineZ80.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BFCA1261ECBE33200AC40C1 /* TestMachineZ80.mm */; }; 4BFCA1291ECBE7A700AC40C1 /* zexall.com in Resources */ = {isa = PBXBuildFile; fileRef = 4BFCA1281ECBE7A700AC40C1 /* zexall.com */; }; @@ -1117,7 +1125,15 @@ 4B4DC8271D2C2470003C5BF8 /* C1540.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = C1540.hpp; sourceTree = ""; }; 4B4DC8291D2C27A4003C5BF8 /* SerialBus.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SerialBus.cpp; sourceTree = ""; }; 4B4DC82A1D2C27A4003C5BF8 /* SerialBus.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SerialBus.hpp; sourceTree = ""; }; + 4B4DEC04252BFA56004583AC /* 65816Implementation.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 65816Implementation.hpp; sourceTree = ""; }; + 4B4DEC05252BFA56004583AC /* 65816Base.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = 65816Base.cpp; sourceTree = ""; }; + 4B4DEC16252BFA9C004583AC /* 6502Selector.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6502Selector.hpp; sourceTree = ""; }; + 4B4DEC18252BFA9C004583AC /* 6502Esque.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6502Esque.hpp; sourceTree = ""; }; + 4B4DEC19252BFB5A004583AC /* LazyFlags.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = LazyFlags.hpp; sourceTree = ""; }; 4B4F2B7024DF99D4000DA6B0 /* CSScanTarget+CppScanTarget.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CSScanTarget+CppScanTarget.h"; sourceTree = ""; }; + 4B4F475E2533EA64004245B8 /* suite-a.prg */ = {isa = PBXFileReference; lastKnownFileType = file; path = "suite-a.prg"; sourceTree = ""; }; + 4B4F477B253530B7004245B8 /* Jeek816Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Jeek816Tests.swift; sourceTree = ""; }; + 4B4F478925367EDC004245B8 /* 65816AddressingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 65816AddressingTests.swift; sourceTree = ""; }; 4B50AF7F242817F40099BBD7 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 4B51F70920A521D700AFA2C1 /* Source.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Source.hpp; sourceTree = ""; }; 4B51F70A20A521D700AFA2C1 /* Observer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Observer.hpp; sourceTree = ""; }; @@ -1788,6 +1804,9 @@ 4BF4A2D91F534DB300B171F4 /* TargetPlatforms.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TargetPlatforms.hpp; sourceTree = ""; }; 4BF52672218E752E00313227 /* ScanTarget.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ScanTarget.hpp; path = ../../Outputs/ScanTarget.hpp; sourceTree = ""; }; 4BF6606A1F281573002CB053 /* ClockReceiver.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ClockReceiver.hpp; sourceTree = ""; }; + 4BF8D4CD251C0C9C00BBE21B /* 65816.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 65816.hpp; sourceTree = ""; }; + 4BF8D4D3251C0D9F00BBE21B /* 65816Storage.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 65816Storage.hpp; sourceTree = ""; }; + 4BF8D4D4251C11DD00BBE21B /* 65816Storage.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = 65816Storage.cpp; sourceTree = ""; }; 4BFCA1211ECBDCAF00AC40C1 /* AllRAMProcessor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AllRAMProcessor.cpp; sourceTree = ""; }; 4BFCA1221ECBDCAF00AC40C1 /* AllRAMProcessor.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AllRAMProcessor.hpp; sourceTree = ""; }; 4BFCA1251ECBE33200AC40C1 /* TestMachineZ80.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestMachineZ80.h; sourceTree = ""; }; @@ -1980,6 +1999,7 @@ 4B9F11CB22729B3500701480 /* OPCLOGR2.BIN */, 4B2530F2244E6773007980BF /* FM Synthesis */, 4BBF49B41ED2881600AB3669 /* FUSE */, + 4B4F475B2533EA64004245B8 /* jeek816 */, 4B670A822401CB8400D4E002 /* Patrik Rak Z80 Tests */, 4B9F11C72272375400701480 /* QL Startup */, 4B85322B227793CA00F26553 /* TOS Startup */, @@ -2451,6 +2471,32 @@ path = 1540; sourceTree = ""; }; + 4B4DEC15252BFA9C004583AC /* 6502Esque */ = { + isa = PBXGroup; + children = ( + 4B4DEC18252BFA9C004583AC /* 6502Esque.hpp */, + 4B4DEC16252BFA9C004583AC /* 6502Selector.hpp */, + 4B4DEC17252BFA9C004583AC /* Implementation */, + ); + path = 6502Esque; + sourceTree = ""; + }; + 4B4DEC17252BFA9C004583AC /* Implementation */ = { + isa = PBXGroup; + children = ( + 4B4DEC19252BFB5A004583AC /* LazyFlags.hpp */, + ); + path = Implementation; + sourceTree = ""; + }; + 4B4F475B2533EA64004245B8 /* jeek816 */ = { + isa = PBXGroup; + children = ( + 4B4F475E2533EA64004245B8 /* suite-a.prg */, + ); + path = jeek816; + sourceTree = ""; + }; 4B51F70820A521D700AFA2C1 /* Activity */ = { isa = PBXGroup; children = ( @@ -3391,7 +3437,6 @@ 4BB73EB51B587A5100552FC2 /* Clock SignalTests */ = { isa = PBXGroup; children = ( - 4BC0CB272446BC7B00A79DBB /* OPLTests.mm */, 4B85322922778E4200F26553 /* Comparative68000.hpp */, 4B90467222C6FA31000E2074 /* TestRunner68000.hpp */, 4B97ADC722C6FD9B00A22A41 /* 68000ArithmeticTests.mm */, @@ -3410,6 +3455,7 @@ 4BE90FFC22D5864800FB464D /* MacintoshVideoTests.mm */, 4BA91E1C216D85BA00F79557 /* MasterSystemVDPTests.mm */, 4B98A0601FFADCDE00ADF63B /* MSXStaticAnalyserTests.mm */, + 4BC0CB272446BC7B00A79DBB /* OPLTests.mm */, 4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */, 4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */, 4BE76CF822641ED300ACD6FA /* QLTests.mm */, @@ -3420,11 +3466,13 @@ 4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */, 4BC751B11D157E61006C31D9 /* 6522Tests.swift */, 4B1E85801D176468001EF87D /* 6532Tests.swift */, + 4B4F478925367EDC004245B8 /* 65816AddressingTests.swift */, 4BB73EB61B587A5100552FC2 /* AllSuiteATests.swift */, 4B049CDC1DA3C82F00322067 /* BCDTest.swift */, 4B3BA0C21D318AEB005DD7A7 /* C1540Tests.swift */, 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */, 4BBF49AE1ED2880200AB3669 /* FUSETests.swift */, + 4B4F477B253530B7004245B8 /* Jeek816Tests.swift */, 4B1414611B58888700E04248 /* KlausDormannTests.swift */, 4BD91D762401C2B8007BDC91 /* PatrikRakTests.swift */, 4B14145F1B58885000E04248 /* WolfgangLorenzTests.swift */, @@ -3485,6 +3533,8 @@ 4BFCA1221ECBDCAF00AC40C1 /* AllRAMProcessor.hpp */, 4B2C455C1EC9442600FC74DD /* RegisterSizes.hpp */, 4B1414561B58879D00E04248 /* 6502 */, + 4B4DEC15252BFA9C004583AC /* 6502Esque */, + 4BF8D4CC251C0C9C00BBE21B /* 65816 */, 4BFF1D332233778C00838EA1 /* 68000 */, 4B77069E1EC9045B0053B588 /* Z80 */, ); @@ -3892,6 +3942,26 @@ path = ../../ClockReceiver; sourceTree = ""; }; + 4BF8D4CC251C0C9C00BBE21B /* 65816 */ = { + isa = PBXGroup; + children = ( + 4BF8D4CD251C0C9C00BBE21B /* 65816.hpp */, + 4BF8D4D2251C0D9F00BBE21B /* Implementation */, + ); + path = 65816; + sourceTree = ""; + }; + 4BF8D4D2251C0D9F00BBE21B /* Implementation */ = { + isa = PBXGroup; + children = ( + 4B4DEC05252BFA56004583AC /* 65816Base.cpp */, + 4BF8D4D4251C11DD00BBE21B /* 65816Storage.cpp */, + 4B4DEC04252BFA56004583AC /* 65816Implementation.hpp */, + 4BF8D4D3251C0D9F00BBE21B /* 65816Storage.hpp */, + ); + path = Implementation; + sourceTree = ""; + }; 4BFDD7891F7F2DB4008579B9 /* Utility */ = { isa = PBXGroup; children = ( @@ -4001,7 +4071,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0700; - LastUpgradeCheck = 1130; + LastUpgradeCheck = 1200; ORGANIZATIONNAME = "Thomas Harte"; TargetAttributes = { 4B055A691FAE763F0060FFFF = { @@ -4239,6 +4309,7 @@ 4BB299051B587D8400A49093 /* arrb in Resources */, 4BB299DC1B587D8400A49093 /* stazx in Resources */, 4B670A9D2401CB8400D4E002 /* z80ccf.tap in Resources */, + 4B4F47652533EA64004245B8 /* suite-a.prg in Resources */, 4BB299C41B587D8400A49093 /* sbca in Resources */, 4BB298F41B587D8400A49093 /* adcay in Resources */, 4B44EBF51DC987AF00A7820C /* AllSuiteA.bin in Resources */, @@ -4468,6 +4539,7 @@ 4BDACBED22FFA5D20045EF7E /* ncr5380.cpp in Sources */, 4BC131772346DE9100E4FF3D /* StaticAnalyser.cpp in Sources */, 4B055ACF1FAE9B030060FFFF /* SoundGenerator.cpp in Sources */, + 4B4DEC08252BFA56004583AC /* 65816Base.cpp in Sources */, 4B894519201967B4007DE474 /* ConfidenceCounter.cpp in Sources */, 4B055AEE1FAE9BBF0060FFFF /* Keyboard.cpp in Sources */, 4B055AED1FAE9BA20060FFFF /* Z80Storage.cpp in Sources */, @@ -4489,6 +4561,7 @@ 4B9BE401203A0C0600FFAE60 /* MultiSpeaker.cpp in Sources */, 4B055AA61FAE85EF0060FFFF /* Parser.cpp in Sources */, 4B055AE91FAE9B990060FFFF /* 6502Base.cpp in Sources */, + 4BF8D4D6251C11DD00BBE21B /* 65816Storage.cpp in Sources */, 4B055AEF1FAE9BF00060FFFF /* Typer.cpp in Sources */, 4B89453F201967B4007DE474 /* StaticAnalyser.cpp in Sources */, 4B89453D201967B4007DE474 /* StaticAnalyser.cpp in Sources */, @@ -4749,6 +4822,7 @@ 4BEBFB4D2002C4BF000708CC /* MSXDSK.cpp in Sources */, 4BBFBB6C1EE8401E00C01E7A /* ZX8081.cpp in Sources */, 4B83348A1F5DB94B0097E338 /* IRQDelegatePortHandler.cpp in Sources */, + 4B4DEC06252BFA56004583AC /* 65816Base.cpp in Sources */, 4B894524201967B4007DE474 /* Tape.cpp in Sources */, 4B7136891F78725F008B8ED9 /* Shifter.cpp in Sources */, 4BDB61EB2032806E0048AF91 /* CSAtari2600.mm in Sources */, @@ -4771,6 +4845,7 @@ 4B55DD8320DF06680043F2E5 /* MachinePicker.swift in Sources */, 4B2A539F1D117D36003C6002 /* CSAudioQueue.m in Sources */, 4B89453E201967B4007DE474 /* StaticAnalyser.cpp in Sources */, + 4BF8D4D5251C11DD00BBE21B /* 65816Storage.cpp in Sources */, 4B0ACC2823775819008902D0 /* DMAController.cpp in Sources */, 4BC131702346DE5000E4FF3D /* StaticAnalyser.cpp in Sources */, 4B37EE821D7345A6006A09A4 /* BinaryDump.cpp in Sources */, @@ -4839,6 +4914,7 @@ 4B778F4D23A5F20F0000D260 /* StaticAnalyser.cpp in Sources */, 4B778F0423A5EBB00000D260 /* OricMFMDSK.cpp in Sources */, 4B3BA0CE1D318B44005DD7A7 /* C1540Bridge.mm in Sources */, + 4B4F477C253530B7004245B8 /* Jeek816Tests.swift in Sources */, 4B778F0F23A5EC560000D260 /* PCMTrack.cpp in Sources */, 4B778F1123A5EC650000D260 /* FileHolder.cpp in Sources */, 4B778EFC23A5EB8B0000D260 /* AcornADF.cpp in Sources */, @@ -4895,6 +4971,7 @@ 4B778F4123A5F19A0000D260 /* MemoryPacker.cpp in Sources */, 4B778F4423A5F1BE0000D260 /* CommodoreGCR.cpp in Sources */, 4B778EF923A5EB740000D260 /* MSA.cpp in Sources */, + 4B4DEC07252BFA56004583AC /* 65816Base.cpp in Sources */, 4B778F2323A5EDE40000D260 /* Tape.cpp in Sources */, 4B778F4F23A5F21C0000D260 /* StaticAnalyser.cpp in Sources */, 4B778EEF23A5D6680000D260 /* AsyncTaskQueue.cpp in Sources */, @@ -4935,6 +5012,7 @@ 4B778F5723A5F2BB0000D260 /* ZX8081.cpp in Sources */, 4B778F2F23A5F0B10000D260 /* ScanTarget.cpp in Sources */, 4BE90FFD22D5864800FB464D /* MacintoshVideoTests.mm in Sources */, + 4B4F478A25367EDC004245B8 /* 65816AddressingTests.swift in Sources */, 4B778F0B23A5EC150000D260 /* TapeUEF.cpp in Sources */, 4B778F0523A5EBB00000D260 /* ST.cpp in Sources */, 4B778F0C23A5EC150000D260 /* TZX.cpp in Sources */, @@ -5144,6 +5222,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -5202,6 +5281,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme index a98a6ccfd..9891a6c49 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme +++ b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme @@ -1,6 +1,6 @@ - - - - diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock SignalTests.xcscheme b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock SignalTests.xcscheme index df5ecac5c..446760744 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock SignalTests.xcscheme +++ b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock SignalTests.xcscheme @@ -1,6 +1,6 @@ - - - - diff --git a/OSBindings/Mac/Clock SignalTests/6502InterruptTests.swift b/OSBindings/Mac/Clock SignalTests/6502InterruptTests.swift index 691ef4613..5adede92f 100644 --- a/OSBindings/Mac/Clock SignalTests/6502InterruptTests.swift +++ b/OSBindings/Mac/Clock SignalTests/6502InterruptTests.swift @@ -14,38 +14,38 @@ class MOS6502InterruptTests: XCTestCase { override func setUp() { super.setUp() - // create a machine full of NOPs - machine = CSTestMachine6502(is65C02: false) + // Create a machine full of NOPs. + machine = CSTestMachine6502(processor: .processor6502) for c in 0...65535 { - machine.setValue(0xea, forAddress: UInt16(c)) + machine.setValue(0xea, forAddress: UInt32(c)) } - // set the IRQ vector to be 0x1234 + // Set the IRQ vector to 0x1234. machine.setValue(0x34, forAddress: 0xfffe) machine.setValue(0x12, forAddress: 0xffff) - // add a CLI + // Add a CLI. machine.setValue(0x58, forAddress: 0x4000) - // pick things off at 0x4000 - machine.setValue(0x4000, for: CSTestMachine6502Register.programCounter) + // Begin at 0x4000. + machine.setValue(0x4000, for: .programCounter) } func testIRQLine() { - // run for six cycles; check that no interrupt has occurred + // Run for six cycles; check that no interrupt has occurred. machine.runForNumber(ofCycles: 6) - XCTAssert(machine.value(for: .programCounter) == 0x4003, "No interrupt should have occurred with line low") + XCTAssertEqual(machine.value(for: .programCounter), 0x4003, "No interrupt should have occurred with line low") // enable the interrupt line, check that it was too late machine.irqLine = true machine.runForNumber(ofCycles: 2) - XCTAssert(machine.value(for: .programCounter) == 0x4004, "No interrupt should have occurred from interrupt raised between instructions") + XCTAssertEqual(machine.value(for: .programCounter), 0x4004, "No interrupt should have occurred from interrupt raised between instructions") // run for a further 7 cycles, confirm that the IRQ vector was jumped to machine.runForNumber(ofCycles: 6) - XCTAssert(machine.value(for: .programCounter) != 0x1234, "Interrupt routine should not yet have begun") + XCTAssertNotEqual(machine.value(for: .programCounter), 0x1234, "Interrupt routine should not yet have begun") machine.runForNumber(ofCycles: 1) - XCTAssert(machine.value(for: .programCounter) == 0x1234, "Interrupt routine should just have begun") + XCTAssertEqual(machine.value(for: .programCounter), 0x1234, "Interrupt routine should just have begun") } func testIFlagSet() { @@ -53,8 +53,8 @@ class MOS6502InterruptTests: XCTestCase { machine.irqLine = true machine.runForNumber(ofCycles: 11) - XCTAssert(machine.value(for: .programCounter) == 0x1234, "Interrupt routine should just have begun") - XCTAssert(machine.value(for: .flags) & 0x04 == 0x04, "Interrupt status flag should be set") + XCTAssertEqual(machine.value(for: .programCounter), 0x1234, "Interrupt routine should just have begun") + XCTAssertEqual(machine.value(for: .flags) & 0x04, 0x04, "Interrupt status flag should be set") } func testCLISEIFlagClear() { @@ -64,12 +64,13 @@ class MOS6502InterruptTests: XCTestCase { // run for four cycles; the CLI and SEI should have been performed machine.runForNumber(ofCycles: 4) - XCTAssert(machine.value(for: .programCounter) == 0x4002, "CLI/SEI pair should have been performed in their entirety") + XCTAssertEqual(machine.value(for: .programCounter), 0x4002, "CLI/SEI pair should have been performed in their entirety") // run for seven more cycles machine.runForNumber(ofCycles: 7) // interrupt should have taken place despite SEI - XCTAssert(machine.value(for: .programCounter) == 0x1234, "Interrupt routine should just have begun") + XCTAssertEqual(machine.value(for: .programCounter), 0x1234, "Interrupt routine should just have begun") } + } diff --git a/OSBindings/Mac/Clock SignalTests/6502TimingTests.swift b/OSBindings/Mac/Clock SignalTests/6502TimingTests.swift index 01f913a1f..10ff300b9 100644 --- a/OSBindings/Mac/Clock SignalTests/6502TimingTests.swift +++ b/OSBindings/Mac/Clock SignalTests/6502TimingTests.swift @@ -12,7 +12,7 @@ import XCTest class MOS6502TimingTests: XCTestCase, CSTestMachineTrapHandler { private var endTime: UInt32 = 0 - private let machine = CSTestMachine6502(is65C02: false) + private let machine = CSTestMachine6502(processor: .processor6502) func testImplied() { let code: [UInt8] = [ @@ -22,7 +22,7 @@ class MOS6502TimingTests: XCTestCase, CSTestMachineTrapHandler { 0x18, // [2] CLC 0x2a, // [2] ROL A ] - self.runTest(code, expectedRunLength: 10) + runTest(code, expectedRunLength: 10) } func testLDA() { @@ -40,7 +40,7 @@ class MOS6502TimingTests: XCTestCase, CSTestMachineTrapHandler { 0xb1, 0x00, // [5] LDA ($00), y (no wrap) 0xb1, 0x02, // [6] LDA ($01), y (wrap) ] - self.runTest(code, expectedRunLength: 48) + runTest(code, expectedRunLength: 48) } func testBIT() { @@ -48,7 +48,7 @@ class MOS6502TimingTests: XCTestCase, CSTestMachineTrapHandler { 0x24, 0x2a, // [3] BIT $2a 0x2c, 0x2a, 0x2b, // [4] BIT $2b2a ] - self.runTest(code, expectedRunLength: 7) + runTest(code, expectedRunLength: 7) } func testSTA() { @@ -64,7 +64,7 @@ class MOS6502TimingTests: XCTestCase, CSTestMachineTrapHandler { 0x91, 0x00, // [6] STA ($00), y (no wrap) 0x91, 0x02, // [6] STA ($01), y (wrap) ] - self.runTest(code, expectedRunLength: 49) + runTest(code, expectedRunLength: 49) } func testINC() { @@ -75,7 +75,7 @@ class MOS6502TimingTests: XCTestCase, CSTestMachineTrapHandler { 0xfe, 0x00, 0x00, // [7] INC $0000, x (no wrap) 0xfe, 0x02, 0x00, // [7] INC $0002, x (wrap) ] - self.runTest(code, expectedRunLength: 31) + runTest(code, expectedRunLength: 31) } func testJSR() { @@ -85,7 +85,7 @@ class MOS6502TimingTests: XCTestCase, CSTestMachineTrapHandler { 0x60, // [6] RTS ] machine.addTrapAddress(0x0203) - self.runTest(code, expectedRunLength: 12) + runTest(code, expectedRunLength: 12) } func testJMP() { @@ -94,7 +94,7 @@ class MOS6502TimingTests: XCTestCase, CSTestMachineTrapHandler { 0x00, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x0b, 0x02, // [3] JMP 020b ] - self.runTest(code, expectedRunLength: 8) + runTest(code, expectedRunLength: 8) } func testPHAPLA() { @@ -103,7 +103,7 @@ class MOS6502TimingTests: XCTestCase, CSTestMachineTrapHandler { 0x48, // [3] PHA 0x68, // [4] PLA ] - self.runTest(code, expectedRunLength: 10) + runTest(code, expectedRunLength: 10) } func testBCS() { @@ -130,7 +130,7 @@ class MOS6502TimingTests: XCTestCase, CSTestMachineTrapHandler { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ] - self.runTest(code, expectedRunLength: 14) + runTest(code, expectedRunLength: 14) } func testSnippet1() { @@ -138,7 +138,7 @@ class MOS6502TimingTests: XCTestCase, CSTestMachineTrapHandler { 0x8d, 0x08, 0x00, // [4] STA $0008 0xc6, 0xb4, // [5] DEC $B4 ] - self.runTest(code, expectedRunLength: 9) + runTest(code, expectedRunLength: 9) } func testSnippet2() { @@ -146,7 +146,7 @@ class MOS6502TimingTests: XCTestCase, CSTestMachineTrapHandler { 0x16, 0x16, // [6] ASL $16, x 0x46, 0x46, // [5] LSR $46 ] - self.runTest(code, expectedRunLength: 11) + runTest(code, expectedRunLength: 11) } func testSnippet3() { @@ -174,7 +174,7 @@ class MOS6502TimingTests: XCTestCase, CSTestMachineTrapHandler { 0x60, // [6] RTS ] machine.addTrapAddress(0x0203) - self.runTest(code, expectedRunLength: 66) + runTest(code, expectedRunLength: 66) } func testNOP() { @@ -194,10 +194,10 @@ class MOS6502TimingTests: XCTestCase, CSTestMachineTrapHandler { 0xe2, 0x00, // [2] NOP # 0xf4, 0x00, // [4] NOP zpg, x ] - self.runTest(code, expectedRunLength: 43) + runTest(code, expectedRunLength: 43) } - func runTest(_ code: [UInt8], expectedRunLength: UInt32) { + private func runTest(_ code: [UInt8], expectedRunLength: UInt32) { machine.trapHandler = self let immediateCode = Data(code) @@ -213,17 +213,17 @@ class MOS6502TimingTests: XCTestCase, CSTestMachineTrapHandler { machine.setValue(0xff, for: CSTestMachine6502Register.X) machine.setValue(0xfe, for: CSTestMachine6502Register.Y) - self.endTime = 0 - while self.endTime == 0 { + endTime = 0 + while endTime == 0 { machine.runForNumber(ofCycles: 10) } - XCTAssert(self.endTime == expectedRunLength, "Took \(self.endTime) cycles to perform rather than \(expectedRunLength)") + XCTAssertEqual(endTime, expectedRunLength, "Took \(endTime) cycles to perform rather than \(expectedRunLength)") } func testMachine(_ testMachine: CSTestMachine, didTrapAtAddress address: UInt16) { - if self.endTime == 0 { - self.endTime = (machine.timestamp / 2) - 1 + if endTime == 0 { + endTime = (machine.timestamp / 2) - 1 } } } diff --git a/OSBindings/Mac/Clock SignalTests/6532Tests.swift b/OSBindings/Mac/Clock SignalTests/6532Tests.swift index f599a25f5..f892372ca 100644 --- a/OSBindings/Mac/Clock SignalTests/6532Tests.swift +++ b/OSBindings/Mac/Clock SignalTests/6532Tests.swift @@ -11,7 +11,7 @@ import Foundation class MOS6532Tests: XCTestCase { - fileprivate func with6532(_ action: (MOS6532Bridge) -> ()) { + private func with6532(_ action: (MOS6532Bridge) -> ()) { let bridge = MOS6532Bridge() action(bridge) } diff --git a/OSBindings/Mac/Clock SignalTests/65816AddressingTests.swift b/OSBindings/Mac/Clock SignalTests/65816AddressingTests.swift new file mode 100644 index 000000000..6c9bcd669 --- /dev/null +++ b/OSBindings/Mac/Clock SignalTests/65816AddressingTests.swift @@ -0,0 +1,546 @@ +// +// WDC65816AddressingTests.swift +// Clock Signal +// +// Created by Thomas Harte on 13/10/2020. +// Copyright 2020 Thomas Harte. All rights reserved. +// + +import Foundation +import XCTest + +// This exactly transcribes the examples given in http://6502.org/tutorials/65c816opcodes.html#5 +// Quoted text is taken verbatim from that document. +class WDC65816AddressingTests: XCTestCase { + + // MARK: - Test Machines + + /// Returns a CSTestMachine6502 that is currently configured in native mode with 16-bit memory and index registers. + private func machine16() -> CSTestMachine6502 { + let machine = CSTestMachine6502(processor: .processor65816) + machine.setValue(0, for: .emulationFlag) + machine.setValue(0, for: .flags) + return machine + } + + /// Returns a CSTestMachine6502 that is currently configured in emulation mode. + private func machine8() -> CSTestMachine6502 { + return CSTestMachine6502(processor: .processor65816) + } + + // MARK: - Tests + + func testAbsoluteJMP() { + // "If the K register is $12, then JMP $FFFF jumps to $12FFFF" + let machine = machine16() + + machine.setValue(0x12, for: .programBank) + + // JMP $ffff + machine.setData(Data([0x4c, 0xff, 0xff]), atAddress: 0x120200) + + machine.setValue(0x200, for: .programCounter) + machine.runForNumber(ofCycles: 4) + + XCTAssertEqual(machine.value(for: .programCounter), 0) // i.e. 0xffff + 1 + XCTAssertEqual(machine.value(for: .programBank), 0x12) + } + + func testAbsolute() { + // "If the DBR is $12 and the m flag is 0, then LDA $FFFF loads the low byte of + // the data from address $12FFFF, and the high byte from address $130000" + + let machine = machine16() + + machine.setValue(0x12, for: .dataBank) + + machine.setValue(0xab, forAddress: 0x12ffff) + machine.setValue(0xcd, forAddress: 0x130000) + + // LDA $ffff; NOP + machine.setData(Data([0xad, 0xff, 0xff, 0xea]), atAddress: 0x200) + + machine.setValue(0x200, for: .programCounter) + machine.runForNumber(ofCycles: 6) + + XCTAssertEqual(machine.value(for: .A), 0xcdab) + } + + func testAbsoluteX() { + // "If the DBR is $12, the X register is $000A, and the m flag is 0, then + // LDA $FFFE,X loads the low byte of the data from address $130008, and + // the high byte from address $130009" + // + // "Note that this is one of the rare instances where emulation mode has + // different behavior than the 65C02 or NMOS 6502 ..." + + let machine = machine16() + + machine.setValue(0x12, for: .dataBank) + machine.setValue(0x0a, for: .X) + + machine.setValue(0xab, forAddress: 0x130008) + machine.setValue(0xcd, forAddress: 0x130009) + + // LDA $fffe, x; NOP + machine.setData(Data([0xbd, 0xfe, 0xff, 0xea]), atAddress: 0x200) + + machine.setValue(0x200, for: .programCounter) + machine.runForNumber(ofCycles: 7) + + XCTAssertEqual(machine.value(for: .A), 0xcdab) + } + + func testJMPAbsoluteIndirect() { + // "If the K register is $12 and + // * $000000 contains $34 + // * $00FFFF contains $56 + // then JMP ($FFFF) jumps to $123456" + + let machine = machine16() + + machine.setValue(0x12, for: .programBank) + machine.setValue(0x0a, for: .X) + + machine.setValue(0x34, forAddress: 0x0000) + machine.setValue(0x56, forAddress: 0xffff) + + // JMP ($ffff); NOP + machine.setData(Data([0x6c, 0xff, 0xff, 0xea]), atAddress: 0x120200) + + machine.setValue(0x200, for: .programCounter) + machine.runForNumber(ofCycles: 6) + + XCTAssertEqual(machine.value(for: .programCounter), 0x3456 + 1) + XCTAssertEqual(machine.value(for: .programBank), 0x12) + } + + func testIndirectAbsoluteX() { + // "If the K register is $12, the X register is $000A, and + // * $120008 contains $56 + // * $120009 contains $34 + //then JMP ($FFFE,X) jumps to $123456" + + let machine = machine16() + + machine.setValue(0x12, for: .programBank) + machine.setValue(0x0a, for: .X) + machine.setValue(0x56, forAddress: 0x120008) + machine.setValue(0x34, forAddress: 0x120009) + + // JMP ($fffe, x); NOP + machine.setData(Data([0x7c, 0xfe, 0xff, 0xea]), atAddress: 0x120200) + + machine.setValue(0x200, for: .programCounter) + machine.runForNumber(ofCycles: 7) + + XCTAssertEqual(machine.value(for: .programCounter), 0x3456 + 1) + XCTAssertEqual(machine.value(for: .programBank), 0x12) + } + + func testDirect8() { + // "If the D register is $FF00 and the e flag is 1 (note that + // this means the m flag must be 1), then LDA $FF loads the low + // byte of the data from address $00FFFF" + + let machine = machine8() + + machine.setValue(0xff00, for: .direct) + + machine.setValue(0xab, forAddress: 0xffff) + + // LDA $ff; NOP + machine.setData(Data([0xa5, 0xff, 0xea]), atAddress: 0x0200) + + machine.setValue(0x200, for: .programCounter) + machine.runForNumber(ofCycles: 4) + + XCTAssertEqual(machine.value(for: .A), 0xab) + } + + func testDirect16() { + // "If the D register is $FF00 and the m flag is 0 (note that + // this means the e flag must be 0), then LDA $FF loads the low + // byte of the data from address $00FFFF, and the high byte + // from address $000000" + + let machine = machine16() + + machine.setValue(0xff00, for: .direct) + + machine.setValue(0xab, forAddress: 0xffff) + machine.setValue(0xcd, forAddress: 0x0000) + + // LDA $ff; NOP + machine.setData(Data([0xa5, 0xff, 0xea]), atAddress: 0x0200) + + machine.setValue(0x200, for: .programCounter) + machine.runForNumber(ofCycles: 5) + + XCTAssertEqual(machine.value(for: .A), 0xcdab) + } + + func testDirextX8() { + // "If the D register is $FF00, the X register is $000A, and + // the e flag is 1 (note that this means the m flag must be 1), + // then LDA $FE,X loads the low byte of the data from + // address $00FF08" + + let machine = machine8() + + machine.setValue(0xff00, for: .direct) + machine.setValue(0x000a, for: .X) + + machine.setValue(0xab, forAddress: 0xff08) + + // LDA $fe, X; NOP + machine.setData(Data([0xb5, 0xfe, 0xea]), atAddress: 0x0200) + + machine.setValue(0x200, for: .programCounter) + machine.runForNumber(ofCycles: 5) + + XCTAssertEqual(machine.value(for: .A), 0xab) + } + + func testDirectX16() { + // "If the D register is $FF00, the X register is $000A, and the + // m flag is 0 (note that this means the e flag must be 0), then + // LDA $FE,X loads the low byte of the data from address $000008, + // and the high byte from address $000009" + let machine = machine16() + + machine.setValue(0xff00, for: .direct) + machine.setValue(0x000a, for: .X) + + machine.setValue(0xab, forAddress: 0x0008) + machine.setValue(0xcd, forAddress: 0x0009) + + // LDA $fe, X; NOP + machine.setData(Data([0xb5, 0xfe, 0xea]), atAddress: 0x0200) + + machine.setValue(0x200, for: .programCounter) + machine.runForNumber(ofCycles: 6) + + XCTAssertEqual(machine.value(for: .A), 0xcdab) + } + + func testDirectIndirect8() { + // "If the D register is $FF00 and the e flag is 1 (note this means the + // m flag must be 1), then for LDA ($FF), the address of the low byte of + // the pointer is $00FFFF and the address of the high byte is $00FF00. + // Furthermore, if the DBR is $12 and + // * $00FF00 contains $FF + // * $00FFFF contains $FF + // then LDA ($FF) loads the low byte of the data from address $12FFFF." + let machine = machine8() + + machine.setValue(0xff00, for: .direct) + machine.setValue(0x12, for: .dataBank) + + machine.setValue(0xff, forAddress: 0xff00) + machine.setValue(0xff, forAddress: 0xffff) + + machine.setValue(0xab, forAddress: 0x12ffff) + + // LDA ($ff); NOP + machine.setData(Data([0xb2, 0xff, 0xea]), atAddress: 0x0200) + + machine.setValue(0x200, for: .programCounter) + machine.runForNumber(ofCycles: 6) + + XCTAssertEqual(machine.value(for: .A), 0xab) + } + + func testDirectIndirect16() { + // "If the D register is $FF00 and the m flag is 0 (note this means the e + // flag must be 0), then for LDA ($FF), the address of the low byte of the + // pointer is $00FFFF and the address of the high byte is $000000. + // Furthermore, if the DBR is $12 and + // * $000000 contains $FF + // * $00FFFF contains $FF + // then LDA ($FF) loads the low byte of the data from address $12FFFF, and + // the high byte from $130000." + let machine = machine16() + + machine.setValue(0xff00, for: .direct) + machine.setValue(0x12, for: .dataBank) + + machine.setValue(0xff, forAddress: 0x0000) + machine.setValue(0xff, forAddress: 0xffff) + + machine.setValue(0xab, forAddress: 0x12ffff) + machine.setValue(0xcd, forAddress: 0x130000) + + // LDA ($ff); NOP + machine.setData(Data([0xb2, 0xff, 0xea]), atAddress: 0x0200) + + machine.setValue(0x200, for: .programCounter) + machine.runForNumber(ofCycles: 7) + + XCTAssertEqual(machine.value(for: .A), 0xcdab) + } + + func testDirectIndirectLong() { + // "If the D register is $FF00 and the m flag is 0, then for LDA [$FE], the + // address of the low byte of the pointer is $00FFFE, the address of the middle + // byte is $00FFFF, and the address of the high byte is $000000. Furthermore, if + // * $000000 contains $12 + // * $00FFFE contains $FF + // * $00FFFF contains $FF + // then LDA [$FE] loads the low byte of the data from address $12FFFF, and the + // high byte from $130000." + let machine = machine16() + + machine.setValue(0xff00, for: .direct) + + machine.setValue(0x12, forAddress: 0x0000) + machine.setValue(0xff, forAddress: 0xfffe) + machine.setValue(0xff, forAddress: 0xffff) + + machine.setValue(0xab, forAddress: 0x12ffff) + machine.setValue(0xcd, forAddress: 0x130000) + + // LDA [$fe]; NOP + machine.setData(Data([0xa7, 0xfe, 0xea]), atAddress: 0x0200) + + machine.setValue(0x200, for: .programCounter) + machine.runForNumber(ofCycles: 8) + + XCTAssertEqual(machine.value(for: .A), 0xcdab) + } + + func testDirectXIndirectX8() { + // "If the D register is $FF00, the X register is $000A, and the e flag is 1 (note + // that this means the m flag must be 1), then for LDA ($FE,X), the address of the + // low byte of the pointer is $00FF08 and the address of the high byte is $00FF09. + // Furthermore, if the DBR is $12 and + // * $00FF08 contains $FF + // * $00FF09 contains $FF + // then LDA ($FE,X) loads the low byte of the data from address $12FFFF." + let machine = machine8() + + machine.setValue(0xff00, for: .direct) + machine.setValue(0x000a, for: .X) + machine.setValue(0x12, for: .dataBank) + + machine.setValue(0xff, forAddress: 0xff08) + machine.setValue(0xff, forAddress: 0xff09) + + machine.setValue(0xab, forAddress: 0x12ffff) + + // LDA ($fe, x); NOP + machine.setData(Data([0xa1, 0xfe, 0xea]), atAddress: 0x0200) + + machine.setValue(0x200, for: .programCounter) + machine.runForNumber(ofCycles: 7) + + XCTAssertEqual(machine.value(for: .A), 0xab) + } + + func testDirectXIndirect16() { + // "If the D register is $FF00, the X register is $000A, and the m flag is 0 + // (note that this means the e flag must be 0), then for LDA ($FE,X), the address + // of the low byte of the pointer is $000008 and the address of the high byte + // is $000009. Furthermore, if the DBR is $12 and + // * $000008 contains $FF + // * $000009 contains $FF + // then LDA ($FE,X) loads the low byte of the data from address $12FFFF, and the + // high byte from $130000." + let machine = machine16() + + machine.setValue(0xff00, for: .direct) + machine.setValue(0x000a, for: .X) + machine.setValue(0x12, for: .dataBank) + + machine.setValue(0xff, forAddress: 0x0008) + machine.setValue(0xff, forAddress: 0x0009) + + machine.setValue(0xab, forAddress: 0x12ffff) + machine.setValue(0xcd, forAddress: 0x130000) + + // LDA ($fe, x); NOP + machine.setData(Data([0xa1, 0xfe, 0xea]), atAddress: 0x0200) + + machine.setValue(0x200, for: .programCounter) + machine.runForNumber(ofCycles: 8) + + XCTAssertEqual(machine.value(for: .A), 0xcdab) + } + + func testIndirectY8() { + // "If the D register is $FF00 and the e flag is 1 (note that this means the + // m flag must be 1), then for LDA ($FF),Y, the address of the low byte of the + // pointer is $00FFFF and the address of the high byte is $00FF00. + // Furthermore, if the DBR is $12, the Y register is $000A, and + // * $00FF00 contains $FF + // * $00FFFF contains $FE + // then LDA ($FF),Y loads the low byte of the data from address $130008." + // + // "this is one of the rare instances where emulation mode has + // different behavior than the 65C02 or NMOS 6502..." + let machine = machine8() + + machine.setValue(0xff00, for: .direct) + machine.setValue(0x000a, for: .Y) + machine.setValue(0x12, for: .dataBank) + + machine.setValue(0xff, forAddress: 0xff00) + machine.setValue(0xfe, forAddress: 0xffff) + + machine.setValue(0xab, forAddress: 0x130008) + + // LDA ($ff), y; NOP + machine.setData(Data([0xb1, 0xff, 0xea]), atAddress: 0x0200) + + machine.setValue(0x200, for: .programCounter) + machine.runForNumber(ofCycles: 7) + + XCTAssertEqual(machine.value(for: .A), 0xab) + } + + func testIndirectY16() { + // "If the D register is $FF00 and the m flag is 0 (note that this means the + // e flag must be 0), then for LDA ($FF),Y, the address of the low byte of the + // pointer is $00FFFF and the address of the high byte is $000000. + // Furthermore, if the DBR is $12, the Y register is $000A, and + // * $000000 contains $FF + // * $00FFFF contains $FE + // then LDA ($FF),Y loads the low byte of the data from address $130008, and the + // high byte from $130009." + let machine = machine16() + + machine.setValue(0xff00, for: .direct) + machine.setValue(0x000a, for: .Y) + machine.setValue(0x12, for: .dataBank) + + machine.setValue(0xff, forAddress: 0x0000) + machine.setValue(0xfe, forAddress: 0xffff) + + machine.setValue(0xab, forAddress: 0x130008) + machine.setValue(0xcd, forAddress: 0x130009) + + // LDA ($ff), y; NOP + machine.setData(Data([0xb1, 0xff, 0xea]), atAddress: 0x0200) + + machine.setValue(0x200, for: .programCounter) + machine.runForNumber(ofCycles: 8) + + XCTAssertEqual(machine.value(for: .A), 0xcdab) + } + + func testIndirectYLong() { + // "If the D register is $FF00 and the m flag is 0, then for LDA [$FE],Y, the address + // of the low byte of the pointer is $00FFFE, the address of the middle byte is $00FFFF, + // and the address of the high byte is $000000. Furthermore, if the Y register is $000A, and + // * $000000 contains $12 + // * $00FFFE contains $FC + // * $00FFFF contains $FF + // then LDA [$FE],Y loads the low byte of the data from address $130006, and the high byte + // from $130007." + let machine = machine16() + + machine.setValue(0xff00, for: .direct) + machine.setValue(0x000a, for: .Y) + machine.setValue(0x12, for: .dataBank) + + machine.setValue(0x12, forAddress: 0x0000) + machine.setValue(0xfc, forAddress: 0xfffe) + machine.setValue(0xff, forAddress: 0xffff) + + machine.setValue(0xab, forAddress: 0x130006) + machine.setValue(0xcd, forAddress: 0x130007) + + // LDA [$fe], y; NOP + machine.setData(Data([0xb7, 0xfe, 0xea]), atAddress: 0x0200) + + machine.setValue(0x200, for: .programCounter) + machine.runForNumber(ofCycles: 8) + + XCTAssertEqual(machine.value(for: .A), 0xcdab) + } + + func testAbsoluteLong() { + // "If the m flag is 0, then LDA $12FFFF loads the low byte of the data from address $12FFFF, + // and the high byte from address $130000." + let machine = machine16() + + machine.setValue(0xab, forAddress: 0x12ffff) + machine.setValue(0xcd, forAddress: 0x130000) + + // LDA $12ffff; NOP + machine.setData(Data([0xaf, 0xff, 0xff, 0x12, 0xea]), atAddress: 0x0200) + + machine.setValue(0x200, for: .programCounter) + machine.runForNumber(ofCycles: 7) + + XCTAssertEqual(machine.value(for: .A), 0xcdab) + } + + func testAbsoluteLongX() { + // "If the X register is $000A and the m flag is 0, then LDA $12FFFE,X loads the low byte of + // the data from address $130008, and the high byte from address $130009." + let machine = machine16() + + machine.setValue(0x000a, for: .X) + + machine.setValue(0xab, forAddress: 0x130008) + machine.setValue(0xcd, forAddress: 0x130009) + + // LDA $12fffe, x; NOP + machine.setData(Data([0xbf, 0xfe, 0xff, 0x12, 0xea]), atAddress: 0x0200) + + machine.setValue(0x200, for: .programCounter) + machine.runForNumber(ofCycles: 7) + + XCTAssertEqual(machine.value(for: .A), 0xcdab) + } + + func testStackS() { + // "If the S register is $FF10 and the m flag is 0, then LDA $FA,S loads the low byte + // of the data from address $00000A, and the high byte from address $00000B." + let machine = machine16() + + machine.setValue(0xff10, for: .stackPointer) + + machine.setValue(0xab, forAddress: 0x000a) + machine.setValue(0xcd, forAddress: 0x000b) + + // LDA $fa, s; NOP + machine.setData(Data([0xa3, 0xfa, 0xea]), atAddress: 0x0200) + + machine.setValue(0x200, for: .programCounter) + machine.runForNumber(ofCycles: 7) + + XCTAssertEqual(machine.value(for: .A), 0xcdab) + } + + func testIndirectStackSY() { + // "If the S register is $FF10 and the m flag is 0, then for LDA ($FA,S),Y, the address + // of the low byte of the pointer is $00000A and the address of the high byte is $00000B. + // Furthermore, if the DBR is $12, the Y register is $0050, and + // * $00000A contains $F0 + // * $00000B contains $FF + // then LDA ($FA,S),Y loads the low byte of the data from address $130040, and the high + // byte from $130041." + let machine = machine16() + + machine.setValue(0xff10, for: .stackPointer) + machine.setValue(0x0050, for: .Y) + machine.setValue(0x12, for: .dataBank) + + machine.setValue(0xf0, forAddress: 0x000a) + machine.setValue(0xff, forAddress: 0x000b) + + machine.setValue(0xab, forAddress: 0x130040) + machine.setValue(0xcd, forAddress: 0x130041) + + // LDA ($fa, s), y; NOP + machine.setData(Data([0xb3, 0xfa, 0xea]), atAddress: 0x0200) + + machine.setValue(0x200, for: .programCounter) + machine.runForNumber(ofCycles: 9) + + XCTAssertEqual(machine.value(for: .A), 0xcdab) + } + +} diff --git a/OSBindings/Mac/Clock SignalTests/68000ComparativeTests.mm b/OSBindings/Mac/Clock SignalTests/68000ComparativeTests.mm index 38aa41718..2f555f34c 100644 --- a/OSBindings/Mac/Clock SignalTests/68000ComparativeTests.mm +++ b/OSBindings/Mac/Clock SignalTests/68000ComparativeTests.mm @@ -91,12 +91,12 @@ Test68000() : processor(*this) { } - void will_perform(uint32_t address, uint16_t opcode) { + void will_perform(uint32_t, uint16_t) { --instructions_remaining_; if(!instructions_remaining_) comparitor(); } - HalfCycles perform_bus_operation(const CPU::MC68000::Microcycle &cycle, int is_supervisor) { + HalfCycles perform_bus_operation(const CPU::MC68000::Microcycle &cycle, int) { using Microcycle = CPU::MC68000::Microcycle; if(cycle.data_select_active()) { cycle.apply(&ram[cycle.host_endian_byte_address()]); diff --git a/OSBindings/Mac/Clock SignalTests/AllSuiteATests.swift b/OSBindings/Mac/Clock SignalTests/AllSuiteATests.swift index 5c0a13607..12076e958 100644 --- a/OSBindings/Mac/Clock SignalTests/AllSuiteATests.swift +++ b/OSBindings/Mac/Clock SignalTests/AllSuiteATests.swift @@ -13,7 +13,7 @@ class AllSuiteATests: XCTestCase { func testAllSuiteA() { if let filename = Bundle(for: type(of: self)).path(forResource: "AllSuiteA", ofType: "bin") { if let allSuiteA = try? Data(contentsOf: URL(fileURLWithPath: filename)) { - let machine = CSTestMachine6502(is65C02: false) + let machine = CSTestMachine6502(processor: .processor6502) machine.setData(allSuiteA, atAddress: 0x4000) machine.setValue(CSTestMachine6502JamOpcode, forAddress:0x45c0); // end @@ -27,4 +27,5 @@ class AllSuiteATests: XCTestCase { } } } + } diff --git a/OSBindings/Mac/Clock SignalTests/BCDTest.swift b/OSBindings/Mac/Clock SignalTests/BCDTest.swift index 6eb9850b0..235d201dd 100644 --- a/OSBindings/Mac/Clock SignalTests/BCDTest.swift +++ b/OSBindings/Mac/Clock SignalTests/BCDTest.swift @@ -14,7 +14,7 @@ class BCDTest: XCTestCase, CSTestMachineTrapHandler { func testBCD() { if let filename = Bundle(for: type(of: self)).path(forResource: "BCDTEST_beeb", ofType: nil) { if let bcdTest = try? Data(contentsOf: URL(fileURLWithPath: filename)) { - let machine = CSTestMachine6502(is65C02: false) + let machine = CSTestMachine6502(processor: .processor6502) machine.trapHandler = self machine.setData(bcdTest, atAddress: 0x2900) @@ -40,7 +40,7 @@ class BCDTest: XCTestCase, CSTestMachineTrapHandler { } } - fileprivate var output: String = "" + private var output: String = "" func testMachine(_ testMachine: CSTestMachine, didTrapAtAddress address: UInt16) { let machine6502 = testMachine as! CSTestMachine6502 @@ -48,4 +48,5 @@ class BCDTest: XCTestCase, CSTestMachineTrapHandler { let character = machine6502.value(for: .A) output.append(Character(UnicodeScalar(character)!)) } + } diff --git a/OSBindings/Mac/Clock SignalTests/Bridges/TestMachine6502.h b/OSBindings/Mac/Clock SignalTests/Bridges/TestMachine6502.h index 59b5f0aed..206701607 100644 --- a/OSBindings/Mac/Clock SignalTests/Bridges/TestMachine6502.h +++ b/OSBindings/Mac/Clock SignalTests/Bridges/TestMachine6502.h @@ -17,6 +17,16 @@ typedef NS_ENUM(NSInteger, CSTestMachine6502Register) { CSTestMachine6502RegisterA, CSTestMachine6502RegisterX, CSTestMachine6502RegisterY, + CSTestMachine6502RegisterEmulationFlag, + CSTestMachine6502RegisterDataBank, + CSTestMachine6502RegisterProgramBank, + CSTestMachine6502RegisterDirect, +}; + +typedef NS_ENUM(NSInteger, CSTestMachine6502Processor) { + CSTestMachine6502Processor6502, + CSTestMachine6502Processor65C02, + CSTestMachine6502Processor65816 }; extern const uint8_t CSTestMachine6502JamOpcode; @@ -25,13 +35,13 @@ extern const uint8_t CSTestMachine6502JamOpcode; - (nonnull instancetype)init NS_UNAVAILABLE; -- (nonnull instancetype)initIs65C02:(BOOL)is65C02; +- (nonnull instancetype)initWithProcessor:(CSTestMachine6502Processor)processor; -- (void)setData:(nonnull NSData *)data atAddress:(uint16_t)startAddress; +- (void)setData:(nonnull NSData *)data atAddress:(uint32_t)startAddress; - (void)runForNumberOfCycles:(int)cycles; -- (void)setValue:(uint8_t)value forAddress:(uint16_t)address; -- (uint8_t)valueForAddress:(uint16_t)address; +- (void)setValue:(uint8_t)value forAddress:(uint32_t)address; +- (uint8_t)valueForAddress:(uint32_t)address; - (void)setValue:(uint16_t)value forRegister:(CSTestMachine6502Register)reg; - (uint16_t)valueForRegister:(CSTestMachine6502Register)reg; diff --git a/OSBindings/Mac/Clock SignalTests/Bridges/TestMachine6502.mm b/OSBindings/Mac/Clock SignalTests/Bridges/TestMachine6502.mm index 635005613..c5f073a62 100644 --- a/OSBindings/Mac/Clock SignalTests/Bridges/TestMachine6502.mm +++ b/OSBindings/Mac/Clock SignalTests/Bridges/TestMachine6502.mm @@ -24,6 +24,10 @@ static CPU::MOS6502::Register registerForRegister(CSTestMachine6502Register reg) case CSTestMachine6502RegisterX: return CPU::MOS6502::Register::X; case CSTestMachine6502RegisterY: return CPU::MOS6502::Register::Y; case CSTestMachine6502RegisterStackPointer: return CPU::MOS6502::Register::StackPointer; + case CSTestMachine6502RegisterEmulationFlag: return CPU::MOS6502::Register::EmulationFlag; + case CSTestMachine6502RegisterDataBank: return CPU::MOS6502::Register::DataBank; + case CSTestMachine6502RegisterProgramBank: return CPU::MOS6502::Register::ProgramBank; + case CSTestMachine6502RegisterDirect: return CPU::MOS6502::Register::Direct; } } @@ -35,12 +39,20 @@ static CPU::MOS6502::Register registerForRegister(CSTestMachine6502Register reg) #pragma mark - Lifecycle -- (instancetype)initIs65C02:(BOOL)is65C02 { +- (instancetype)initWithProcessor:(CSTestMachine6502Processor)processor { self = [super init]; if(self) { - _processor = CPU::MOS6502::AllRAMProcessor::Processor( - is65C02 ? CPU::MOS6502::Personality::PWDC65C02 : CPU::MOS6502::Personality::P6502); + switch(processor) { + case CSTestMachine6502Processor6502: + _processor = CPU::MOS6502::AllRAMProcessor::Processor(CPU::MOS6502Esque::Type::T6502); + break; + case CSTestMachine6502Processor65C02: + _processor = CPU::MOS6502::AllRAMProcessor::Processor(CPU::MOS6502Esque::Type::TWDC65C02); + break; + case CSTestMachine6502Processor65816: + _processor = CPU::MOS6502::AllRAMProcessor::Processor(CPU::MOS6502Esque::Type::TWDC65816); + } } return self; @@ -52,13 +64,13 @@ static CPU::MOS6502::Register registerForRegister(CSTestMachine6502Register reg) #pragma mark - Accessors -- (uint8_t)valueForAddress:(uint16_t)address { +- (uint8_t)valueForAddress:(uint32_t)address { uint8_t value; _processor->get_data_at_address(address, 1, &value); return value; } -- (void)setValue:(uint8_t)value forAddress:(uint16_t)address { +- (void)setValue:(uint8_t)value forAddress:(uint32_t)address { _processor->set_data_at_address(address, 1, &value); } @@ -70,7 +82,7 @@ static CPU::MOS6502::Register registerForRegister(CSTestMachine6502Register reg) return _processor->get_value_of_register(registerForRegister(reg)); } -- (void)setData:(NSData *)data atAddress:(uint16_t)startAddress { +- (void)setData:(NSData *)data atAddress:(uint32_t)startAddress { _processor->set_data_at_address(startAddress, data.length, (const uint8_t *)data.bytes); } diff --git a/OSBindings/Mac/Clock SignalTests/Bridges/TestMachineZ80.mm b/OSBindings/Mac/Clock SignalTests/Bridges/TestMachineZ80.mm index 65ec87bc7..9ca773e84 100644 --- a/OSBindings/Mac/Clock SignalTests/Bridges/TestMachineZ80.mm +++ b/OSBindings/Mac/Clock SignalTests/Bridges/TestMachineZ80.mm @@ -23,7 +23,7 @@ class BusOperationHandler: public CPU::Z80::AllRAMProcessor::MemoryAccessDelegat public: BusOperationHandler(CSTestMachineZ80 *targetMachine) : target_(targetMachine) {} - void z80_all_ram_processor_did_perform_bus_operation(CPU::Z80::AllRAMProcessor &processor, CPU::Z80::PartialMachineCycle::Operation operation, uint16_t address, uint8_t value, HalfCycles time_stamp) { + void z80_all_ram_processor_did_perform_bus_operation(CPU::Z80::AllRAMProcessor &, CPU::Z80::PartialMachineCycle::Operation operation, uint16_t address, uint8_t value, HalfCycles time_stamp) { [target_ testMachineDidPerformBusOperation:operation address:address value:value timeStamp:time_stamp]; } @@ -77,7 +77,7 @@ struct PortAccessDelegateTopByte: public CPU::Z80::AllRAMProcessor::PortAccessDe }; struct PortAccessDelegate191: public CPU::Z80::AllRAMProcessor::PortAccessDelegate { - uint8_t z80_all_ram_processor_input(uint16_t port) final { return 191; } + uint8_t z80_all_ram_processor_input(uint16_t) final { return 191; } }; #pragma mark - Capture class diff --git a/OSBindings/Mac/Clock SignalTests/C1540Tests.swift b/OSBindings/Mac/Clock SignalTests/C1540Tests.swift index 5ce0246a2..7b8f4f39a 100644 --- a/OSBindings/Mac/Clock SignalTests/C1540Tests.swift +++ b/OSBindings/Mac/Clock SignalTests/C1540Tests.swift @@ -10,21 +10,21 @@ import XCTest class C1540Tests: XCTestCase { - fileprivate func with1540(_ action: (C1540Bridge) -> ()) { + private func with1540(_ action: (C1540Bridge) -> ()) { let bridge = C1540Bridge() action(bridge) } - fileprivate func transmit(_ c1540: C1540Bridge, value: Int) { + private func transmit(_ c1540: C1540Bridge, value: Int) { var shiftedValue = value c1540.dataLine = true c1540.run(forCycles: 256) - XCTAssert(c1540.dataLine == false, "Listener should have taken data line low for start of transmission") + XCTAssertFalse(c1540.dataLine, "Listener should have taken data line low for start of transmission") c1540.clockLine = true c1540.run(forCycles: 256) // this isn't time limited on real hardware - XCTAssert(c1540.dataLine == true, "Listener should have let data line go high again") + XCTAssertTrue(c1540.dataLine, "Listener should have let data line go high again") // set up for byte transfer c1540.clockLine = false @@ -47,7 +47,7 @@ class C1540Tests: XCTestCase { // check for acknowledgment c1540.dataLine = true c1540.run(forCycles: 1000) - XCTAssert(c1540.dataLine == false, "Listener should have acknowledged byte") + XCTAssertFalse(c1540.dataLine, "Listener should have acknowledged byte") } // MARK: EOI @@ -64,10 +64,11 @@ class C1540Tests: XCTestCase { // proceed 1 ms and check that the 1540 pulled the data line low $0.run(forCycles: 1000) - XCTAssert($0.dataLine == false, "Listener should have taken data line low") + XCTAssertFalse($0.dataLine, "Listener should have taken data line low") // transmit LISTEN #8 - self.transmit($0, value: 0x28) + transmit($0, value: 0x28) } } + } diff --git a/OSBindings/Mac/Clock SignalTests/Comparative68000.hpp b/OSBindings/Mac/Clock SignalTests/Comparative68000.hpp index b64ec0965..6c8c5472d 100644 --- a/OSBindings/Mac/Clock SignalTests/Comparative68000.hpp +++ b/OSBindings/Mac/Clock SignalTests/Comparative68000.hpp @@ -23,7 +23,7 @@ class ComparativeBusHandler: public CPU::MC68000::BusHandler { gzclose(trace); } - void will_perform(uint32_t address, uint16_t opcode) { + void will_perform(uint32_t address, uint16_t) { // Obtain the next line from the trace file. char correct_state[300] = "\n"; gzgets(trace, correct_state, sizeof(correct_state)); @@ -45,7 +45,7 @@ class ComparativeBusHandler: public CPU::MC68000::BusHandler { fprintf(stderr, "Diverges at line %d\n", line_count); fprintf(stderr, "Good: %s", correct_state); fprintf(stderr, "Bad: %s", local_state); - assert(false); + throw std::exception(); } } diff --git a/OSBindings/Mac/Clock SignalTests/EmuTOSTests.mm b/OSBindings/Mac/Clock SignalTests/EmuTOSTests.mm index 785da74c6..ad5dbb163 100644 --- a/OSBindings/Mac/Clock SignalTests/EmuTOSTests.mm +++ b/OSBindings/Mac/Clock SignalTests/EmuTOSTests.mm @@ -36,7 +36,7 @@ class EmuTOS: public ComparativeBusHandler { return m68000_.get_state(); } - HalfCycles perform_bus_operation(const CPU::MC68000::Microcycle &cycle, int is_supervisor) { + HalfCycles perform_bus_operation(const CPU::MC68000::Microcycle &cycle, int) { const uint32_t address = cycle.word_address(); uint32_t word_address = address; diff --git a/OSBindings/Mac/Clock SignalTests/Jeek816Tests.swift b/OSBindings/Mac/Clock SignalTests/Jeek816Tests.swift new file mode 100644 index 000000000..c40a2729a --- /dev/null +++ b/OSBindings/Mac/Clock SignalTests/Jeek816Tests.swift @@ -0,0 +1,44 @@ +// +// Jeek816Tests.swift +// Clock Signal +// +// Created by Thomas Harte on 12/10/2020. +// Copyright 2020 Thomas Harte. All rights reserved. +// + +import XCTest +import Foundation + +class Jeek816Tests: XCTestCase { + + func testJeek816() { + var machine: CSTestMachine6502! + + if let filename = Bundle(for: type(of: self)).path(forResource: "suite-a.prg", ofType: nil) { + if let testData = try? Data(contentsOf: URL(fileURLWithPath: filename)) { + machine = CSTestMachine6502(processor: .processor65816) + + let contents = testData.subdata(in: 0xe ..< testData.count) + machine.setData(contents, atAddress: 0x080d) + + machine.setValue(0x080d, for: .programCounter) + } + } + + if machine == nil { + NSException(name: NSExceptionName(rawValue: "Failed Test"), reason: "Couldn't load file \(name)", userInfo: nil).raise() + } + + // $874 is the failure stopping point and $85d is success. + while machine.value(for: .lastOperationAddress) != 0x0874 && machine.value(for: .lastOperationAddress) != 0x085d { + machine.runForNumber(ofCycles: 1000) + } + + // The test leaves $ff in $d7ff to indicate failure; $0000 to indicate success. + // If the tests failed, it'll leave a bitmap of failures in address $0401. + if machine.value(forAddress: 0xd7ff) != 0 { + NSException(name: NSExceptionName(rawValue: "Failed Test"), reason: "Failed tests with bitmap: \(String(format:"%02x", machine.value(forAddress: 0x401)))", userInfo: nil).raise() + } + } + +} diff --git a/OSBindings/Mac/Clock SignalTests/KlausDormannTests.swift b/OSBindings/Mac/Clock SignalTests/KlausDormannTests.swift index d04e74e1e..68c4c1642 100644 --- a/OSBindings/Mac/Clock SignalTests/KlausDormannTests.swift +++ b/OSBindings/Mac/Clock SignalTests/KlausDormannTests.swift @@ -11,10 +11,10 @@ import XCTest class KlausDormannTests: XCTestCase { - fileprivate func runTest(resource: String, is65C02: Bool) -> UInt16 { + private func runTest(resource: String, processor: CSTestMachine6502Processor) -> UInt16 { if let filename = Bundle(for: type(of: self)).path(forResource: resource, ofType: "bin") { if let functionalTest = try? Data(contentsOf: URL(fileURLWithPath: filename)) { - let machine = CSTestMachine6502(is65C02: is65C02) + let machine = CSTestMachine6502(processor: processor) machine.setData(functionalTest, atAddress: 0) machine.setValue(0x400, for: .programCounter) @@ -39,21 +39,25 @@ class KlausDormannTests: XCTestCase { return 0 } - /// Runs Klaus Dorman's 6502 tests. - func test6502() { + private func runTest6502(processor: CSTestMachine6502Processor) { func errorForTrapAddress(_ address: UInt16) -> String? { switch address { case 0x3399: return nil // success! + case 0x052a: return "TAX, DEX or LDA did not correctly set flags, or BEQ did not branch correctly" + case 0x05db: return "PLP did not affect N flag correctly" + case 0x26d2: return "ASL zpg,x produced incorrect flags" case 0x33a7: return "Decimal ADC result has wrong value" - case 0x3502: return "Binary SBC result has wrong value" case 0x33b9: return "Decimal SBC result has wrong value" case 0x33c0: return "Decimal SBC wrong carry flag" - case 0x36d1: return "BRK: unexpected BRK or IRQ" + case 0x3502: return "Binary SBC result has wrong value" + case 0x364a: return "JMP (addr) acted as JMP addr" case 0x36ac, 0x36f6: return "Improper JSR return address on stack" - case 0x36e5: return "BRK flag not set on stack" - case 0x26d2: return "ASL zpg,x produced incorrect flags" case 0x36c6: return "Unexpected RESET" + case 0x36d1: return "BRK: unexpected BRK or IRQ" + case 0x36e5: return "BRK flag not set on stack following BRK" + case 0x36ea: return "BRK did not set the I flag" + case 0x36fd: return "Wrong address put on stack by BRK" case 0: return "Didn't find tests" @@ -61,13 +65,12 @@ class KlausDormannTests: XCTestCase { } } - let destination = runTest(resource: "6502_functional_test", is65C02: false) + let destination = runTest(resource: "6502_functional_test", processor: processor) let error = errorForTrapAddress(destination) XCTAssert(error == nil, "Failed with error \(error!)") } - /// Runs Klaus Dorman's 65C02 tests. - func test65C02() { + private func runTest65C02(processor: CSTestMachine6502Processor) { func errorForTrapAddress(_ address: UInt16) -> String? { switch address { case 0x24f1: return nil // success! @@ -110,8 +113,31 @@ class KlausDormannTests: XCTestCase { } } - let destination = runTest(resource: "65C02_extended_opcodes_test", is65C02: true) + let destination = runTest(resource: "65C02_extended_opcodes_test", processor: processor) let error = errorForTrapAddress(destination) XCTAssert(error == nil, "Failed with error \(error!)") } + + + /// Runs Klaus Dormann's 6502 tests. + func test6502() { + runTest6502(processor: .processor6502) + } + + /// Runs Klaus Dormann's standard 6502 tests on a 65C02. + func test65C02As6502() { + runTest6502(processor: .processor65C02) + } + + /// Runs Klaus Dormann's standard 6502 tests on a 65816. + func test65816As6502() { + runTest6502(processor: .processor65816) + } + + /// Runs Klaus Dormann's 65C02 tests. + func test65C02() { + runTest65C02(processor: .processor65C02) + } + + /* Dormann's 65C02 tests aren't applicable to the 65816; e.g. they test BBR/BBS, which the 65816 doesn't implement. */ } diff --git a/OSBindings/Mac/Clock SignalTests/PatrikRakTests.swift b/OSBindings/Mac/Clock SignalTests/PatrikRakTests.swift index 0328d23eb..e15dfba9a 100644 --- a/OSBindings/Mac/Clock SignalTests/PatrikRakTests.swift +++ b/OSBindings/Mac/Clock SignalTests/PatrikRakTests.swift @@ -11,8 +11,8 @@ import Foundation class PatrikRakTests: XCTestCase, CSTestMachineTrapHandler { - fileprivate var done = false - fileprivate var output = "" + private var done = false + private var output = "" private func runTest(_ name: String) { if let filename = Bundle(for: type(of: self)).path(forResource: name, ofType: "tap") { @@ -124,4 +124,5 @@ class PatrikRakTests: XCTestCase, CSTestMachineTrapHandler { break } } + } diff --git a/OSBindings/Mac/Clock SignalTests/QLTests.mm b/OSBindings/Mac/Clock SignalTests/QLTests.mm index 505016cef..3df640dc2 100644 --- a/OSBindings/Mac/Clock SignalTests/QLTests.mm +++ b/OSBindings/Mac/Clock SignalTests/QLTests.mm @@ -39,7 +39,7 @@ class QL: public ComparativeBusHandler { return m68000_.get_state(); } - HalfCycles perform_bus_operation(const CPU::MC68000::Microcycle &cycle, int is_supervisor) { + HalfCycles perform_bus_operation(const CPU::MC68000::Microcycle &cycle, int) { const uint32_t address = cycle.word_address(); uint32_t word_address = address; diff --git a/OSBindings/Mac/Clock SignalTests/TestRunner68000.hpp b/OSBindings/Mac/Clock SignalTests/TestRunner68000.hpp index 0a912b3dc..b02798428 100644 --- a/OSBindings/Mac/Clock SignalTests/TestRunner68000.hpp +++ b/OSBindings/Mac/Clock SignalTests/TestRunner68000.hpp @@ -48,7 +48,7 @@ class RAM68000: public CPU::MC68000::BusHandler { ram_[1] = sp & 0xffff; } - void will_perform(uint32_t address, uint16_t opcode) { + void will_perform(uint32_t, uint16_t) { --instructions_remaining_; } @@ -80,7 +80,7 @@ class RAM68000: public CPU::MC68000::BusHandler { return &ram_[(address >> 1) % ram_.size()]; } - HalfCycles perform_bus_operation(const CPU::MC68000::Microcycle &cycle, int is_supervisor) { + HalfCycles perform_bus_operation(const CPU::MC68000::Microcycle &cycle, int) { const uint32_t word_address = cycle.word_address(); if(instructions_remaining_) duration_ += cycle.length; diff --git a/OSBindings/Mac/Clock SignalTests/WolfgangLorenzTests.swift b/OSBindings/Mac/Clock SignalTests/WolfgangLorenzTests.swift index 4ec9be87b..2f5bc3b21 100644 --- a/OSBindings/Mac/Clock SignalTests/WolfgangLorenzTests.swift +++ b/OSBindings/Mac/Clock SignalTests/WolfgangLorenzTests.swift @@ -11,206 +11,294 @@ import Foundation class WolfgangLorenzTests: XCTestCase, CSTestMachineTrapHandler { - func testWolfgangLorenzStart() { - self.runWolfgangLorenzTest(" start") + // MARK: - 6502 Tests + + func testStart() { + runTest(" start", processor: .processor6502) } - func testWolfgangLorenzLDA() { - self.runWolfgangLorenzTest("lda", suffixes: ["b", "z", "zx", "a", "ax", "ay", "ix", "iy"]) + func testLDA() { + runTest("lda", suffixes: ["b", "z", "zx", "a", "ax", "ay", "ix", "iy"], processor: .processor6502) } - func testWolfgangLorenzSTA() { - self.runWolfgangLorenzTest("sta", suffixes: ["z", "zx", "a", "ax", "ay", "ix", "iy"]) + func testSTA() { + runTest("sta", suffixes: ["z", "zx", "a", "ax", "ay", "ix", "iy"], processor: .processor6502) } - func testWolfgangLorenzLDX() { - self.runWolfgangLorenzTest("ldx", suffixes: ["b", "z", "zy", "a", "ay"]) + func testLDX() { + runTest("ldx", suffixes: ["b", "z", "zy", "a", "ay"], processor: .processor6502) } - func testWolfgangLorenzSTX() { - self.runWolfgangLorenzTest("stx", suffixes: ["z", "zy", "a"]) + func testSTX() { + runTest("stx", suffixes: ["z", "zy", "a"], processor: .processor6502) } - func testWolfgangLorenzLDY() { - self.runWolfgangLorenzTest("ldy", suffixes: ["b", "z", "zx", "a", "ax"]) + func testLDY() { + runTest("ldy", suffixes: ["b", "z", "zx", "a", "ax"], processor: .processor6502) } - func testWolfgangLorenzSTY() { - self.runWolfgangLorenzTest("sty", suffixes: ["z", "zx", "a"]) + func testSTY() { + runTest("sty", suffixes: ["z", "zx", "a"], processor: .processor6502) } - func testWolfgangLorenzTransfers() { - self.runWolfgangLorenzTest("taxn") - self.runWolfgangLorenzTest("tayn") - self.runWolfgangLorenzTest("txan") - self.runWolfgangLorenzTest("tyan") - self.runWolfgangLorenzTest("tsxn") - self.runWolfgangLorenzTest("txsn") + func testTransfers() { + testTransfers(processor: .processor6502) } - func testWolfgangLorenzStack() { - self.runWolfgangLorenzTest("phan") - self.runWolfgangLorenzTest("plan") - self.runWolfgangLorenzTest("phpn") - self.runWolfgangLorenzTest("plpn") + func testStack() { + testStack(processor: .processor6502) } - func testWolfgangLorenzIncsAndDecs() { - self.runWolfgangLorenzTest("inxn") - self.runWolfgangLorenzTest("inyn") - self.runWolfgangLorenzTest("dexn") - self.runWolfgangLorenzTest("deyn") - self.runWolfgangLorenzTest("incz") - self.runWolfgangLorenzTest("inczx") - self.runWolfgangLorenzTest("inca") - self.runWolfgangLorenzTest("incax") - self.runWolfgangLorenzTest("decz") - self.runWolfgangLorenzTest("deczx") - self.runWolfgangLorenzTest("deca") - self.runWolfgangLorenzTest("decax") + func testIncsAndDecs() { + testIncsAndDecs(processor: .processor6502) } - func testWolfgangLorenzASL() { - self.runWolfgangLorenzTest("asl", suffixes: ["n", "z", "zx", "a", "ax"]) + func testASL() { + runTest("asl", suffixes: ["n", "z", "zx", "a", "ax"], processor: .processor6502) } - func testWolfgangLorenzLSR() { - self.runWolfgangLorenzTest("lsr", suffixes: ["n", "z", "zx", "a", "ax"]) + func testLSR() { + runTest("lsr", suffixes: ["n", "z", "zx", "a", "ax"], processor: .processor6502) } - func testWolfgangLorenzROL() { - self.runWolfgangLorenzTest("rol", suffixes: ["n", "z", "zx", "a", "ax"]) + func testROL() { + runTest("rol", suffixes: ["n", "z", "zx", "a", "ax"], processor: .processor6502) } - func testWolfgangLorenzROR() { - self.runWolfgangLorenzTest("ror", suffixes: ["n", "z", "zx", "a", "ax"]) + func testROR() { + runTest("ror", suffixes: ["n", "z", "zx", "a", "ax"], processor: .processor6502) } - func testWolfgangLorenzAND() { - self.runWolfgangLorenzTest("and", suffixes: ["b", "z", "zx", "a", "ax", "ay", "ix", "iy"]) + func testAND() { + runTest("and", suffixes: ["b", "z", "zx", "a", "ax", "ay", "ix", "iy"], processor: .processor6502) } - func testWolfgangLorenzORA() { - self.runWolfgangLorenzTest("ora", suffixes: ["b", "z", "zx", "a", "ax", "ay", "ix", "iy"]) + func testORA() { + runTest("ora", suffixes: ["b", "z", "zx", "a", "ax", "ay", "ix", "iy"], processor: .processor6502) } - func testWolfgangLorenzEOR() { - self.runWolfgangLorenzTest("eor", suffixes: ["b", "z", "zx", "a", "ax", "ay", "ix", "iy"]) + func testEOR() { + runTest("eor", suffixes: ["b", "z", "zx", "a", "ax", "ay", "ix", "iy"], processor: .processor6502) } - func testWolfgangLorenzFlagManipulation() { - self.runWolfgangLorenzTest("clcn") - self.runWolfgangLorenzTest("secn") - self.runWolfgangLorenzTest("cldn") - self.runWolfgangLorenzTest("sedn") - self.runWolfgangLorenzTest("clin") - self.runWolfgangLorenzTest("sein") - self.runWolfgangLorenzTest("clvn") + func testFlagManipulation() { + testFlagManipulation(processor: .processor6502) } - func testWolfgangLorenzADC() { - self.runWolfgangLorenzTest("adc", suffixes: ["b", "z", "zx", "a", "ax", "ay", "ix", "iy"]) + func testADC() { + runTest("adc", suffixes: ["b", "z", "zx", "a", "ax", "ay", "ix", "iy"], processor: .processor6502) } - func testWolfgangLorenzSBC() { - self.runWolfgangLorenzTest("sbc", suffixes: ["b", "z", "zx", "a", "ax", "ay", "ix", "iy"]) + func testSBC() { + runTest("sbc", suffixes: ["b", "z", "zx", "a", "ax", "ay", "ix", "iy"], processor: .processor6502) } - func testWolfgangLorenzCompare() { - self.runWolfgangLorenzTest("cmp", suffixes: ["b", "z", "zx", "a", "ax", "ay", "ix", "iy"]) - self.runWolfgangLorenzTest("cpx", suffixes: ["b", "z", "a"]) - self.runWolfgangLorenzTest("cpy", suffixes: ["b", "z", "a"]) + func testCompare() { + testCompare(processor: .processor6502) } - func testWolfgangLorenzBIT() { - self.runWolfgangLorenzTest("bit", suffixes: ["z", "a"]) + func testBIT() { + runTest("bit", suffixes: ["z", "a"], processor: .processor6502) } - func testWolfgangLorenzFlow() { - self.runWolfgangLorenzTest("brkn") - self.runWolfgangLorenzTest("rtin") - self.runWolfgangLorenzTest("jsrw") - self.runWolfgangLorenzTest("rtsn") - self.runWolfgangLorenzTest("jmpw") - self.runWolfgangLorenzTest("jmpi") + func testFlow() { + testFlow(processor: .processor6502) } - func testWolfgangLorenzBranch() { - self.runWolfgangLorenzTest("beqr") - self.runWolfgangLorenzTest("bner") - self.runWolfgangLorenzTest("bmir") - self.runWolfgangLorenzTest("bplr") - self.runWolfgangLorenzTest("bcsr") - self.runWolfgangLorenzTest("bccr") - self.runWolfgangLorenzTest("bvsr") - self.runWolfgangLorenzTest("bvcr") + func testBranch() { + testBranch(processor: .processor6502) } - func testWolfgangLorenzNOP() { - self.runWolfgangLorenzTest("nop", suffixes: ["n", "b", "z", "zx", "a", "ax"]) + func testNOP() { + runTest("nop", suffixes: ["n", "b", "z", "zx", "a", "ax"], processor: .processor6502) } - func testWolfgangLorenzASO() { - self.runWolfgangLorenzTest("aso", suffixes: ["z", "zx", "a", "ax", "ay", "ix", "iy"]) + func testASO() { + runTest("aso", suffixes: ["z", "zx", "a", "ax", "ay", "ix", "iy"], processor: .processor6502) } - func testWolfgangLorenzRLA() { - self.runWolfgangLorenzTest("rla", suffixes: ["z", "zx", "a", "ax", "ay", "ix", "iy"]) + func testRLA() { + runTest("rla", suffixes: ["z", "zx", "a", "ax", "ay", "ix", "iy"], processor: .processor6502) } - func testWolfgangLorenzLSE() { - self.runWolfgangLorenzTest("lse", suffixes: ["z", "zx", "a", "ax", "ay", "ix", "iy"]) + func testLSE() { + runTest("lse", suffixes: ["z", "zx", "a", "ax", "ay", "ix", "iy"], processor: .processor6502) } - func testWolfgangLorenzRRA() { - self.runWolfgangLorenzTest("rra", suffixes: ["z", "zx", "a", "ax", "ay", "ix", "iy"]) + func testRRA() { + runTest("rra", suffixes: ["z", "zx", "a", "ax", "ay", "ix", "iy"], processor: .processor6502) } - func testWolfgangLorenzDCM() { - self.runWolfgangLorenzTest("dcm", suffixes: ["z", "zx", "a", "ax", "ay", "ix", "iy"]) + func testDCM() { + runTest("dcm", suffixes: ["z", "zx", "a", "ax", "ay", "ix", "iy"], processor: .processor6502) } - func testWolfgangLorenzINS() { - self.runWolfgangLorenzTest("ins", suffixes: ["z", "zx", "a", "ax", "ay", "ix", "iy"]) + func testINS() { + runTest("ins", suffixes: ["z", "zx", "a", "ax", "ay", "ix", "iy"], processor: .processor6502) } - func testWolfgangLorenzLAX() { - self.runWolfgangLorenzTest("lax", suffixes: ["z", "zy", "a", "ay", "ix", "iy"]) + func testLAX() { + runTest("lax", suffixes: ["z", "zy", "a", "ay", "ix", "iy"], processor: .processor6502) } - func testWolfgangLorenzAXS() { - self.runWolfgangLorenzTest("axs", suffixes: ["z", "zy", "a", "ix"]) + func testAXS() { + runTest("axs", suffixes: ["z", "zy", "a", "ix"], processor: .processor6502) } - func testWolfgangLorenzALR() { - self.runWolfgangLorenzTest("alrb") + func testALR() { + runTest("alrb", processor: .processor6502) } - func testWolfgangLorenzARR() { - self.runWolfgangLorenzTest("arrb") + func testARR() { + runTest("arrb", processor: .processor6502) } - func testWolfgangLorenzSBX() { - self.runWolfgangLorenzTest("sbxb") + func testSBX() { + runTest("sbxb", processor: .processor6502) } - func testWolfgangLorenzSHA() { - self.runWolfgangLorenzTest("sha", suffixes: ["ay", "iy"]) + func testSHA() { + runTest("sha", suffixes: ["ay", "iy"], processor: .processor6502) } - func testWolfgangLorenzSHX() { - self.runWolfgangLorenzTest("shxay") + func testSHX() { + runTest("shxay", processor: .processor6502) } - func testWolfgangLorenzSHY() { - self.runWolfgangLorenzTest("shyax") + func testSHY() { + runTest("shyax", processor: .processor6502) } - func testWolfgangLorenzSHS() { - self.runWolfgangLorenzTest("shsay") + func testSHS() { + runTest("shsay", processor: .processor6502) } - func testWolfgangLorenzLXA() { - self.runWolfgangLorenzTest("lxab") + func testLXA() { + runTest("lxab", processor: .processor6502) } - func testWolfgangLorenzANE() { - self.runWolfgangLorenzTest("aneb") + func testANE() { + runTest("aneb", processor: .processor6502) } - func testWolfgangLorenzANC() { - self.runWolfgangLorenzTest("ancb") + func testANC() { + runTest("ancb", processor: .processor6502) } - func testWolfgangLorenzLAS() { - self.runWolfgangLorenzTest("lasay") + func testLAS() { + runTest("lasay", processor: .processor6502) } - func testWolfgangLorenzSBCB() { - self.runWolfgangLorenzTest("sbcb(eb)") + func testSBCB() { + runTest("sbcb(eb)", processor: .processor6502) } - fileprivate func runWolfgangLorenzTest(_ name: String, suffixes: [String]) { + + // MARK: - 65816 Tests + + func testStart65816() { + runTest(" start", processor: .processor65816) + } + func testLDA65816() { + runTest("lda", suffixes: ["b", "z", "zx", "a", "ax", "ay", "ix", "iy"], processor: .processor65816) + } + func testSTA65816() { + runTest("sta", suffixes: ["z", "zx", "a", "ax", "ay", "ix", "iy"], processor: .processor65816) + } + func testLDX65816() { + runTest("ldx", suffixes: ["b", "z", "zy", "a", "ay"], processor: .processor65816) + } + func testSTX65816() { + runTest("stx", suffixes: ["z", "zy", "a"], processor: .processor65816) + } + func testLDY65816() { + runTest("ldy", suffixes: ["b", "z", "zx", "a", "ax"], processor: .processor65816) + } + func testSTY65816() { + runTest("sty", suffixes: ["z", "zx", "a"], processor: .processor65816) + } + func testTransfers65816() { + testTransfers(processor: .processor65816) + } + func testStack65816() { + testStack(processor: .processor65816) + } + func testIncsAndDecs65816() { + testIncsAndDecs(processor: .processor65816) + } + func testASL65816() { + runTest("asl", suffixes: ["n", "z", "zx", "a", "ax"], processor: .processor65816) + } + func testLSR65816() { + runTest("lsr", suffixes: ["n", "z", "zx", "a", "ax"], processor: .processor65816) + } + func testROL65816() { + runTest("rol", suffixes: ["n", "z", "zx", "a", "ax"], processor: .processor65816) + } + func testROR65816() { + runTest("ror", suffixes: ["n", "z", "zx", "a", "ax"], processor: .processor65816) + } + func testAND65816() { + runTest("and", suffixes: ["b", "z", "zx", "a", "ax", "ay", "ix", "iy"], processor: .processor65816) + } + func testORA65816() { + runTest("ora", suffixes: ["b", "z", "zx", "a", "ax", "ay", "ix", "iy"], processor: .processor65816) + } + func testEOR65816() { + runTest("eor", suffixes: ["b", "z", "zx", "a", "ax", "ay", "ix", "iy"], processor: .processor65816) + } + func testFlagManipulation65816() { + testFlagManipulation(processor: .processor65816) + } + /* + ADC and SBC tests don't apply, as the 65816 follows the 65C02 in setting flags based on the outcome of decimal + results, whereas Lorenzz's tests expect the original 6502 behaviour of setting flags based an intermediate value. + */ + func testCompare65816() { + testCompare(processor: .processor65816) + } + func testBIT65816() { + runTest("bit", suffixes: ["z", "a"], processor: .processor65816) + } + func testFlow65816() { + testFlow(processor: .processor65816) + } + func testBranch65816() { + testBranch(processor: .processor65816) + } + /* The NOP tests also don't apply; the 65816 has only one, well-defined NOP (well, not counting COP or WDM). */ + + + // MARK: - Collections + + func testTransfers(processor: CSTestMachine6502Processor) { + for test in ["taxn", "tayn", "txan", "tyan", "tsxn", "txsn"] { + runTest(test, processor: processor) + } + } + func testStack(processor: CSTestMachine6502Processor) { + for test in ["phan", "plan", "phpn", "plpn"] { + runTest(test, processor: processor) + } + } + func testIncsAndDecs(processor: CSTestMachine6502Processor) { + for test in ["inxn", "inyn", "dexn", "deyn", "incz", "inczx", "inca", "incax", "decz", "deczx", "deca", "decax"] { + runTest(test, processor: processor) + } + } + func testFlagManipulation(processor: CSTestMachine6502Processor) { + for test in ["clcn", "secn", "cldn", "sedn", "clin", "sein", "clvn"] { + runTest(test, processor: processor) + } + } + func testCompare(processor: CSTestMachine6502Processor) { + runTest("cmp", suffixes: ["b", "z", "zx", "a", "ax", "ay", "ix", "iy"], processor: processor) + runTest("cpx", suffixes: ["b", "z", "a"], processor: processor) + runTest("cpy", suffixes: ["b", "z", "a"], processor: processor) + } + func testFlow(processor: CSTestMachine6502Processor) { + for test in ["brkn", "rtin", "jsrw", "rtsn", "jmpw", "jmpi"] { + // JMP indirect is explicitly different on a 65C02 and 65816, not having + // the page wraparound bug. So run that test only on a plain 6502. + if test == "jmpi" && processor != .processor6502 { + continue; + } + + runTest(test, processor: processor) + } + } + func testBranch(processor: CSTestMachine6502Processor) { + for test in ["beqr", "bner", "bmir", "bplr", "bcsr", "bccr", "bvsr", "bvcr"] { + runTest(test, processor: processor) + } + } + + + // MARK: - Test Running + + fileprivate func runTest(_ name: String, suffixes: [String], processor: CSTestMachine6502Processor) { for suffix in suffixes { let testName = name + suffix - self.runWolfgangLorenzTest(testName) + runTest(testName, processor: processor) } } fileprivate var output: String = "" - fileprivate func runWolfgangLorenzTest(_ name: String) { + fileprivate func runTest(_ name: String, processor: CSTestMachine6502Processor) { var machine: CSTestMachine6502! if let filename = Bundle(for: type(of: self)).path(forResource: name, ofType: nil) { if let testData = try? Data(contentsOf: URL(fileURLWithPath: filename)) { - machine = CSTestMachine6502(is65C02: false) + machine = CSTestMachine6502(processor: processor) machine.trapHandler = self -// machine.logActivity = true output = "" let dataPointer = (testData as NSData).bytes.bindMemory(to: UInt8.self, capacity: testData.count) - let loadAddress = UInt16(dataPointer[0]) | (UInt16(dataPointer[1]) << 8) - let contents = testData.subdata(in: 2..<(testData.count - 2)) + let loadAddress = UInt32(dataPointer[0]) | (UInt32(dataPointer[1]) << 8) + let contents = testData.subdata(in: 2 ..< testData.count) machine.setData(contents, atAddress: loadAddress) + // Cf. http://www.softwolves.com/arkiv/cbm-hackers/7/7114.html for the steps being taken here. + + // Initialise memory locations as instructed. machine.setValue(0x00, forAddress: 0x0002) machine.setValue(0x00, forAddress: 0xa002) machine.setValue(0x80, forAddress: 0xa003) @@ -219,28 +307,39 @@ class WolfgangLorenzTests: XCTestCase, CSTestMachineTrapHandler { machine.setValue(0x48, forAddress: 0xfffe) machine.setValue(0xff, forAddress: 0xffff) - let irqHandler = Data(bytes: UnsafePointer([ + // Place the Commodore's default IRQ handler. + let irqHandler: [UInt8] = [ 0x48, 0x8a, 0x48, 0x98, 0x48, 0xba, 0xbd, 0x04, 0x01, 0x29, 0x10, 0xf0, 0x03, 0x6c, 0x16, 0x03, 0x6c, 0x14, 0x03 - ] as [UInt8]), count: 19) - machine.setData( irqHandler, atAddress: 0xff48) + ] + machine.setData(Data(irqHandler), atAddress: 0xff48) + // Set a couple of trap addresses to capture test output. machine.addTrapAddress(0xffd2) // print character machine.addTrapAddress(0xffe4) // scan keyboard + // Set a couple of test addresses that indicate failure. machine.addTrapAddress(0x8000) // exit machine.addTrapAddress(0xa474) // exit + // Ensure that any of those addresses return control. machine.setValue(0x60, forAddress:0xffd2) // 0x60 is RTS machine.setValue(0x60, forAddress:0xffe4) machine.setValue(0x60, forAddress:0x8000) machine.setValue(0x60, forAddress:0xa474) - machine.setValue(CSTestMachine6502JamOpcode, forAddress:0xe16f) // load + // Commodore's load routine resides at $e16f; this is used to spot the end of a test. + machine.setData(Data([0x4c, 0x6f, 0xe1]), atAddress: 0xe16f) - machine.setValue(0x0801, for: CSTestMachine6502Register.programCounter) - machine.setValue(0xfd, for: CSTestMachine6502Register.stackPointer) - machine.setValue(0x04, for: CSTestMachine6502Register.flags) + // Seed program entry. + machine.setValue(0x0801, for: .programCounter) + machine.setValue(0xfd, for: .stackPointer) + machine.setValue(0x04, for: .flags) + + // For consistency when debugging; otherwise immaterial. + machine.setValue(0x00, for: .A) + machine.setValue(0x00, for: .X) + machine.setValue(0x00, for: .Y) } } @@ -248,19 +347,16 @@ class WolfgangLorenzTests: XCTestCase, CSTestMachineTrapHandler { NSException(name: NSExceptionName(rawValue: "Failed Test"), reason: "Couldn't load file \(name)", userInfo: nil).raise() } - while !machine.isJammed { + while machine.value(for: .lastOperationAddress) != 0xe16f && !machine.isJammed { machine.runForNumber(ofCycles: 1000) } - let jammedPC = machine.value(for: CSTestMachine6502Register.lastOperationAddress) - if jammedPC != 0xe16f { - let hexAddress = String(format:"%04x", jammedPC) + if machine.isJammed { + let hexAddress = String(format:"%04x", machine.value(for: .lastOperationAddress)) NSException(name: NSExceptionName(rawValue: "Failed Test"), reason: "Processor jammed unexpectedly at \(hexAddress)", userInfo: nil).raise() } } -// MARK: MachineJamHandler - func petsciiToString(_ string: String) -> String { let petsciiToCharCommon: [String] = [ "?", "?", "?", "[RUN/STOP]", "?", "[WHT]", "?", "?", "[SHIFT DISABLE]", "[SHIFT ENABLE]", "?", "?", "?", "\r", "[TEXT MODE]", "?", @@ -316,7 +412,7 @@ class WolfgangLorenzTests: XCTestCase, CSTestMachineTrapHandler { testMachine6502.setValue(0x3, for:CSTestMachine6502Register.A) case 0x8000, 0xa474: - NSException(name: NSExceptionName(rawValue: "Failed test"), reason: self.petsciiToString(output), userInfo: nil).raise() + NSException(name: NSExceptionName(rawValue: "Failed test"), reason: petsciiToString(output), userInfo: nil).raise() case 0x0000: NSException(name: NSExceptionName(rawValue: "Failed test"), reason: "Execution hit 0000", userInfo: nil).raise() diff --git a/OSBindings/Mac/Clock SignalTests/Z80MachineCycleTests.swift b/OSBindings/Mac/Clock SignalTests/Z80MachineCycleTests.swift index e719e977c..42c5e6fb0 100644 --- a/OSBindings/Mac/Clock SignalTests/Z80MachineCycleTests.swift +++ b/OSBindings/Mac/Clock SignalTests/Z80MachineCycleTests.swift @@ -1247,4 +1247,5 @@ class Z80MachineCycleTests: XCTestCase { ] ) } + } diff --git a/OSBindings/Mac/Clock SignalTests/Z80MemptrTests.swift b/OSBindings/Mac/Clock SignalTests/Z80MemptrTests.swift index 0a8bd65e4..2f9a460cf 100644 --- a/OSBindings/Mac/Clock SignalTests/Z80MemptrTests.swift +++ b/OSBindings/Mac/Clock SignalTests/Z80MemptrTests.swift @@ -9,6 +9,7 @@ import XCTest class Z80MemptrTester: XCTestCase { + let machine = CSTestMachineZ80() private func test(program : [UInt8], initialValue : UInt16) -> UInt16 { diff --git a/OSBindings/Mac/Clock SignalTests/ZexallTests.swift b/OSBindings/Mac/Clock SignalTests/ZexallTests.swift index 714a571bb..c2a3208ac 100644 --- a/OSBindings/Mac/Clock SignalTests/ZexallTests.swift +++ b/OSBindings/Mac/Clock SignalTests/ZexallTests.swift @@ -11,8 +11,8 @@ import Foundation class ZexallTests: XCTestCase, CSTestMachineTrapHandler { - fileprivate var done = false - fileprivate var output = "" + private var done = false + private var output = "" private func runTest(_ name: String) { if let filename = Bundle(for: type(of: self)).path(forResource: name, ofType: "com") { @@ -177,4 +177,5 @@ class ZexallTests: XCTestCase, CSTestMachineTrapHandler { break } } + } diff --git a/OSBindings/Mac/Clock SignalTests/jeek816/Makefile b/OSBindings/Mac/Clock SignalTests/jeek816/Makefile new file mode 100644 index 000000000..5e2f27fdc --- /dev/null +++ b/OSBindings/Mac/Clock SignalTests/jeek816/Makefile @@ -0,0 +1,11 @@ + +F=suite-a + +all: $(F).prg + +$(F).prg: $(F).asm + acme --format plain -o $(F).prg -r $(F).r $(F).asm + +clean: + rm -f $(F).prg + rm -f *~ diff --git a/OSBindings/Mac/Clock SignalTests/jeek816/RUN b/OSBindings/Mac/Clock SignalTests/jeek816/RUN new file mode 100644 index 000000000..d40f7f1b0 --- /dev/null +++ b/OSBindings/Mac/Clock SignalTests/jeek816/RUN @@ -0,0 +1,2 @@ +xscpu64 -scpu64 /usr/local/lib64/vice/SCPU64/scpu64 suite-a.prg +#xscpu64.devel -scpu64 /usr/local/lib64/vice/SCPU64/scpu64 suite-a.prg diff --git a/OSBindings/Mac/Clock SignalTests/jeek816/license.txt b/OSBindings/Mac/Clock SignalTests/jeek816/license.txt new file mode 100644 index 000000000..3e374355c --- /dev/null +++ b/OSBindings/Mac/Clock SignalTests/jeek816/license.txt @@ -0,0 +1,24 @@ +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/OSBindings/Mac/Clock SignalTests/jeek816/readme.txt b/OSBindings/Mac/Clock SignalTests/jeek816/readme.txt new file mode 100644 index 000000000..e813527a4 --- /dev/null +++ b/OSBindings/Mac/Clock SignalTests/jeek816/readme.txt @@ -0,0 +1,23 @@ + +65C816 instruction set test + + +2017-12-13 J.E. Klasek j+816 AT klasek DOT at + + +ACME syntax, green border shows success. in case of failure red border +is shown and $0400 contains number of failed test and $0401 a bitmap +showing which tests actually failed. +If all tests fail on screen "f?" will be shown (corresponds to 6 failures) +and the bitmap %00111111 ($3F = '?') + +There are 6 tests (bit 5 to bit 0): + + STX $FFFF fails in 16 mode for X/Y if wrapping to location 0 + STY $FFFF fails in 16 mode for X/Y if wrapping to location 0 + LDX $FFFF,Y fails if wrapping to same bank + LDY $FFFF,X fails if wrapping to same bank + TRB $FFFF fails in 16 mode for A/M if wrapping to location 0 + TSB $FFFF fails in 16 mode for A/M if wrapping to location 0 + +------------------------------------------------------------------------------- diff --git a/OSBindings/Mac/Clock SignalTests/jeek816/suite-a.asm b/OSBindings/Mac/Clock SignalTests/jeek816/suite-a.asm new file mode 100644 index 000000000..bd291f75c --- /dev/null +++ b/OSBindings/Mac/Clock SignalTests/jeek816/suite-a.asm @@ -0,0 +1,218 @@ +!cpu 65816 + +; 2017-12-13 J.E. Klasek j+816 AT klasek DOT at + + + +videoram = $0400 +colorram = $d800 + +;------------------------------------------------------------------------------- + *=$07ff + !word $0801 + !word bend + !word 10 + !byte $9e + !text "2061", 0 +bend: !word 0 +;------------------------------------------------------------------------------- + + sei + lda #$17 + sta $d018 + lda #$35 + sta $01 + lda #$7f + sta $dc0d + sta $dd0d + lda $dc0d + lda $dd0d + ldx #0 +- + lda #$20 + sta videoram,x + sta videoram+$0100,x + sta videoram+$0200,x + sta videoram+$0300,x + lda #1 + sta colorram,x + sta colorram+$0100,x + sta colorram+$0200,x + sta colorram+$0300,x + inx + bne - + + jmp start +theend: + sep #$30 ; 8-bit for X/Y and A/M + !as + !rs + lda $040210 + cmp #$ff + bne error + + lda #5 + sta $d020 + ldx #0 ; success + stx $d7ff + jmp * +error + sta $0400 + lda $040211 ; failure map (which test failed) + sta $0401 + lda #10 + sta $d020 + ldx #$ff ; failure + stx $d7ff + jmp * + +;------------------------------------------------------------------------------- + + * = $1000 +start: +; EXPECTED FINAL RESULTS: $0210 = FF +; (any other number will be the +; test that failed) + +; initialize: + lda #$00 + sta $040210 + sta $040211 + + +test00: + +; setup cpu + clc + xce ; native mode + rep #$30 ; 16-bit for X/Y and A/M + !al + !rl + +; setup registers + lda #$0404 ; Data Bank Register register + pha ; akku in 16 bit + plb ; pull DBR twice + plb + ldy #$8888 ; change marker + tyx + tya + +; setup memory + lda #$5555 ; wrap marker + sta $048887 ; into bank 4, for LDX/LDY + lda #$7777 ; no-wrap marker + sta $058887 ; into bank 5, for LDX/LDY + +;--------------------------------------------------------------------- + + stz $0000 ; init wrap marker + lda #$7777 ; no-wrap marker + sta $050000 ; to start of bank 5 + + sty $ffff ; high byte of Y is where? + lda $0000 + bne + + lda $ffff ; fetch, does not wrap + cmp #$8888 + bne + + lda $050000 + cmp #$7788 ; write to bank 5 + beq ++ ++ inc $0210 ; fail counter + clc +++ + rol $0211 ; update failure map +;--------------------------------------------------------------------- + + stz $0000 ; init wrap marker + lda #$7777 ; no-wrap marker + sta $050000 ; to start of bank 5 + + tyx ; change marker + stx $ffff ; high byte of Y is where? + lda $0000 + bne + + lda $ffff ; fetch, does not wrap + cmp #$8888 + bne + + lda $050000 + cmp #$7788 ; write to bank 5 + beq ++ ++ inc $0210 ; fail counter + clc +++ + rol $0211 ; update failure map +;--------------------------------------------------------------------- + + ldy $ffff,x ; Y=5555 Y=7777 value for Y comes from which bank? + cpy #$7777 + beq + + inc $0210 ; fail counter + clc ++ + rol $0211 ; update failure map +;--------------------------------------------------------------------- + + txy ; reinitialize y + ldx $ffff,y ; X=5555 X=7777 value for X comes from which bank? + cpx #$7777 + beq + + inc $0210 ; fail counter + clc ++ + rol $0211 ; update failure map +;--------------------------------------------------------------------- + + stz $0000 ; init wrap marker + lda #$7777 ; no-wrap marker + sta $050000 ; to start of bank 5 + + lda #$7788 + inc $0000 ; $0000 = 1 + trb $ffff ; 88 77 & ^(88 77) -> 00 00 + lda $0000 + cmp #$0001 ; $0000 not reset by trb (does not wrap) + bne + + lda $050000 + cmp #$7700 ; $050001 reset by trb + beq ++ ++ inc $0210 ; fail counter + clc +++ + rol $0211 ; update failure map +;--------------------------------------------------------------------- + + lda #$7788 + sta $050000 ; 00 88 | 88 77 -> 88 ff + tsb $ffff ; set bits (which are already cleared) + lda $0000 + cmp #$0001 ; $0000 not set by tsb (does not wrap!) + bne + + lda $050000 + cmp #$77ff ; $050001 all bits set by tsb + beq ++ ++ inc $0210 ; fail counter + clc +++ + rol $0211 ; update failure map +;--------------------------------------------------------------------- + + +test00pass: + lda $0210 + eor #%0011111100000000 ; invert failure map + sta $0210 + bne + + dec $0210 ; 0 -> FF ++ + + lda #$0000 + pha + plb + plb ; program bank = 0 + sec + xce ; emulation mode + sep #$30 ; a/m, x/y 8 bit + + jmp theend diff --git a/OSBindings/Mac/Clock SignalTests/jeek816/suite-a.prg b/OSBindings/Mac/Clock SignalTests/jeek816/suite-a.prg new file mode 100644 index 000000000..3d0647382 Binary files /dev/null and b/OSBindings/Mac/Clock SignalTests/jeek816/suite-a.prg differ diff --git a/OSBindings/Mac/Clock SignalTests/jeek816/suite-a.r b/OSBindings/Mac/Clock SignalTests/jeek816/suite-a.r new file mode 100644 index 000000000..9b5e89c4e --- /dev/null +++ b/OSBindings/Mac/Clock SignalTests/jeek816/suite-a.r @@ -0,0 +1,220 @@ + +; ******** Source: suite-a.asm + 1 !cpu 65816 + 2 + 3 ; 2017-12-13 J.E. Klasek j+816 AT klasek DOT at + 4 + 5 + 6 + 7 videoram = $0400 + 8 colorram = $d800 + 9 + 10 ;------------------------------------------------------------------------------- + 11 *=$07ff + 12 07ff 0108 !word $0801 + 13 0801 0b08 !word bend + 14 0803 0a00 !word 10 + 15 0805 9e !byte $9e + 16 0806 3230363100 !text "2061", 0 + 17 080b 0000 bend: !word 0 + 18 ;------------------------------------------------------------------------------- + 19 + 20 080d 78 sei + 21 080e a917 lda #$17 + 22 0810 8d18d0 sta $d018 + 23 0813 a935 lda #$35 + 24 0815 8501 sta $01 + 25 0817 a97f lda #$7f + 26 0819 8d0ddc sta $dc0d + 27 081c 8d0ddd sta $dd0d + 28 081f ad0ddc lda $dc0d + 29 0822 ad0ddd lda $dd0d + 30 0825 a200 ldx #0 + 31 - + 32 0827 a920 lda #$20 + 33 0829 9d0004 sta videoram,x + 34 082c 9d0005 sta videoram+$0100,x + 35 082f 9d0006 sta videoram+$0200,x + 36 0832 9d0007 sta videoram+$0300,x + 37 0835 a901 lda #1 + 38 0837 9d00d8 sta colorram,x + 39 083a 9d00d9 sta colorram+$0100,x + 40 083d 9d00da sta colorram+$0200,x + 41 0840 9d00db sta colorram+$0300,x + 42 0843 e8 inx + 43 0844 d0e1 bne - + 44 + 45 0846 4c0010 jmp start + 46 theend: + 47 0849 e230 sep #$30 ; 8-bit for X/Y and A/M + 48 !as + 49 !rs + 50 084b af100204 lda $040210 + 51 084f c9ff cmp #$ff + 52 0851 d00d bne error + 53 + 54 0853 a905 lda #5 + 55 0855 8d20d0 sta $d020 + 56 0858 a200 ldx #0 ; success + 57 085a 8effd7 stx $d7ff + 58 085d 4c5d08 jmp * + 59 error + 60 0860 8d0004 sta $0400 + 61 0863 af110204 lda $040211 ; failure map (which test failed) + 62 0867 8d0104 sta $0401 + 63 086a a90a lda #10 + 64 086c 8d20d0 sta $d020 + 65 086f a2ff ldx #$ff ; failure + 66 0871 8effd7 stx $d7ff + 67 0874 4c7408 jmp * + 68 + 69 ;------------------------------------------------------------------------------- + 70 + 71 * = $1000 + 72 start: + 73 ; EXPECTED FINAL RESULTS: $0210 = FF + 74 ; (any other number will be the + 75 ; test that failed) + 76 + 77 ; initialize: + 78 1000 a900 lda #$00 + 79 1002 8f100204 sta $040210 + 80 1006 8f110204 sta $040211 + 81 + 82 + 83 test00: + 84 + 85 ; setup cpu + 86 100a 18 clc + 87 100b fb xce ; native mode + 88 100c c230 rep #$30 ; 16-bit for X/Y and A/M + 89 !al + 90 !rl + 91 + 92 ; setup registers + 93 100e a90404 lda #$0404 ; Data Bank Register register + 94 1011 48 pha ; akku in 16 bit + 95 1012 ab plb ; pull DBR twice + 96 1013 ab plb + 97 1014 a08888 ldy #$8888 ; change marker + 98 1017 bb tyx + 99 1018 98 tya + 100 + 101 ; setup memory + 102 1019 a95555 lda #$5555 ; wrap marker + 103 101c 8f878804 sta $048887 ; into bank 4, for LDX/LDY + 104 1020 a97777 lda #$7777 ; no-wrap marker + 105 1023 8f878805 sta $058887 ; into bank 5, for LDX/LDY + 106 + 107 ;--------------------------------------------------------------------- + 108 + 109 1027 9c0000 stz $0000 ; init wrap marker + 110 102a a97777 lda #$7777 ; no-wrap marker + 111 102d 8f000005 sta $050000 ; to start of bank 5 + 112 + 113 1031 8cffff sty $ffff ; high byte of Y is where? + 114 1034 ad0000 lda $0000 + 115 1037 d011 bne + + 116 1039 adffff lda $ffff ; fetch, does not wrap + 117 103c c98888 cmp #$8888 + 118 103f d009 bne + + 119 1041 af000005 lda $050000 + 120 1045 c98877 cmp #$7788 ; write to bank 5 + 121 1048 f004 beq ++ + 122 104a ee1002 + inc $0210 ; fail counter + 123 104d 18 clc + 124 ++ + 125 104e 2e1102 rol $0211 ; update failure map + 126 ;--------------------------------------------------------------------- + 127 + 128 1051 9c0000 stz $0000 ; init wrap marker + 129 1054 a97777 lda #$7777 ; no-wrap marker + 130 1057 8f000005 sta $050000 ; to start of bank 5 + 131 + 132 105b bb tyx ; change marker + 133 105c 8effff stx $ffff ; high byte of Y is where? + 134 105f ad0000 lda $0000 + 135 1062 d011 bne + + 136 1064 adffff lda $ffff ; fetch, does not wrap + 137 1067 c98888 cmp #$8888 + 138 106a d009 bne + + 139 106c af000005 lda $050000 + 140 1070 c98877 cmp #$7788 ; write to bank 5 + 141 1073 f004 beq ++ + 142 1075 ee1002 + inc $0210 ; fail counter + 143 1078 18 clc + 144 ++ + 145 1079 2e1102 rol $0211 ; update failure map + 146 ;--------------------------------------------------------------------- + 147 + 148 107c bcffff ldy $ffff,x ; Y=5555 Y=7777 value for Y comes from which bank? + 149 107f c07777 cpy #$7777 + 150 1082 f004 beq + + 151 1084 ee1002 inc $0210 ; fail counter + 152 1087 18 clc + 153 + + 154 1088 2e1102 rol $0211 ; update failure map + 155 ;--------------------------------------------------------------------- + 156 + 157 108b 9b txy ; reinitialize y + 158 108c beffff ldx $ffff,y ; X=5555 X=7777 value for X comes from which bank? + 159 108f e07777 cpx #$7777 + 160 1092 f004 beq + + 161 1094 ee1002 inc $0210 ; fail counter + 162 1097 18 clc + 163 + + 164 1098 2e1102 rol $0211 ; update failure map + 165 ;--------------------------------------------------------------------- + 166 + 167 109b 9c0000 stz $0000 ; init wrap marker + 168 109e a97777 lda #$7777 ; no-wrap marker + 169 10a1 8f000005 sta $050000 ; to start of bank 5 + 170 + 171 10a5 a98877 lda #$7788 + 172 10a8 ee0000 inc $0000 ; $0000 = 1 + 173 10ab 1cffff trb $ffff ; 88 77 & ^(88 77) -> 00 00 + 174 10ae ad0000 lda $0000 + 175 10b1 c90100 cmp #$0001 ; $0000 not reset by trb (does not wrap) + 176 10b4 d009 bne + + 177 10b6 af000005 lda $050000 + 178 10ba c90077 cmp #$7700 ; $050001 reset by trb + 179 10bd f004 beq ++ + 180 10bf ee1002 + inc $0210 ; fail counter + 181 10c2 18 clc + 182 ++ + 183 10c3 2e1102 rol $0211 ; update failure map + 184 ;--------------------------------------------------------------------- + 185 + 186 10c6 a98877 lda #$7788 + 187 10c9 8f000005 sta $050000 ; 00 88 | 88 77 -> 88 ff + 188 10cd 0cffff tsb $ffff ; set bits (which are already cleared) + 189 10d0 ad0000 lda $0000 + 190 10d3 c90100 cmp #$0001 ; $0000 not set by tsb (does not wrap!) + 191 10d6 d009 bne + + 192 10d8 af000005 lda $050000 + 193 10dc c9ff77 cmp #$77ff ; $050001 all bits set by tsb + 194 10df f004 beq ++ + 195 10e1 ee1002 + inc $0210 ; fail counter + 196 10e4 18 clc + 197 ++ + 198 10e5 2e1102 rol $0211 ; update failure map + 199 ;--------------------------------------------------------------------- + 200 + 201 + 202 test00pass: + 203 10e8 ad1002 lda $0210 + 204 10eb 49003f eor #%0011111100000000 ; invert failure map + 205 10ee 8d1002 sta $0210 + 206 10f1 d003 bne + + 207 10f3 ce1002 dec $0210 ; 0 -> FF + 208 + + 209 + 210 10f6 a90000 lda #$0000 + 211 10f9 48 pha + 212 10fa ab plb + 213 10fb ab plb ; program bank = 0 + 214 10fc 38 sec + 215 10fd fb xce ; emulation mode + 216 10fe e230 sep #$30 ; a/m, x/y 8 bit + 217 + 218 1100 4c4908 jmp theend diff --git a/OSBindings/Mac/Clock SignalTests/jeek816/test-816.txt b/OSBindings/Mac/Clock SignalTests/jeek816/test-816.txt new file mode 100644 index 000000000..8fd95a640 --- /dev/null +++ b/OSBindings/Mac/Clock SignalTests/jeek816/test-816.txt @@ -0,0 +1,147 @@ +65816 behavior on VICE and true hardware +======================================== + +Johann Klasek, j AT klasek at +2017-12-02 + + +Testenvironment: + + Hardware: + Tested on LTC64, a turbo card for the Commodore 64 + in-place of CPU 6510 (which moves to the card) + with a WDC's 65C816, 32 KByte ROM and 256 KByte RAM. + based on magazine article c't "C64 aufgemotzt", 1987/06 p. 94 + http://klasek.at/c64/ltc64 (german) + Redesigned and implemented by Christoph Egretzberger + and software integration by Johann Klasek + + Software: + Jamaica Monitor (Jammon) 4.1, native '816 + C64 ROMs (Kernal+BASIC) + +--------------------------------------------------------------------- +Case 1: Wrap-around for addressing mode "absolute" + for opcodes TSB, TRB, STX, STY, LDX, LDY + + VICE WDC +!e +a 1000 clc +xce native mode +rep #$30 16-bit for X/Y and A/M +lda #$0404 Data Bank Register register +pha akku in 16 bit +plb pull DBR twice +plb +lda #$7777 no-wrap marker +sta $050000 to bank 5 +sta $058887 +lda #$5555 unchanged marker +sta $048887 into bank 4 +ldy #$8888 change marker +tyx now in X and Y +sty $ffff high byte of Y is where? +ldy $ffff,x Y=5555 Y=7777 value for Y comes from which bank? +sty $0002 save as intermediate result, because we do more +lda $ffff A=7788 A=8888 fetch, does no wrap even on VICE +ldx $0000 X=2088 X=20b5 see if wrapping occured (from sty above) +eor #$ffff A=8877 A=7777 invert akku, 2nd part ... +trb $ffff M=0088 M=8888 reset bits (which are already cleared) +tsb $ffff M=88ff M=ffff set these bits giving a $ffff value +ldy $ffff Y=88ff Y=ffff back to ldy test +brk + +!d Jammon alternate display mode +d disassemble on current PC +m 04ffff show memory including 050000 +m 040000 start of bank 4 (wrap around area), + and intermediate result in 040002 +m 048887 X indexed wrap area +m 058887 X indexed no wrap area +z 1000 start single-stepping +z single step until BRK or ... +g go to BRK + +Results VICE WDC + +$050001 $77 $77 marker, never changed +$050000 $77 $ff modified by TRB/TSB +$04ffff $ff $ff modified by TRB/TSB +$040003 $55 $77 read value (low) from LDY $FFFF,X +$040002 $55 $77 read value (high) from LDY $FFFF,X + read value +$040000 $88 ?? (if no wrapping for STY $FFFF, TRB/TSB $FFFF) + only changed if wrapping +$058888 $77 $77 marker, never changed +$058887 $77 $77 marker, never changed +$048888 $55 $55 marker, never changed +$048887 $55 $55 marker, never changed + +A: $8877 $7777 value from $04ffff/$050000 after STY $FFFF +X: $??88 $???? value from $040000/$040001 with high byte of Y + in low byte of X after STY $FFFF +Y: $88ff $ffff value from TRB/TSB $FFFF + + + +--------------------------------------------------------------------- +Case 2: Wrap-around for Direct Page Indexed Addressing + in Emulation Mode + +### Variant 1 + +r +# set M X E to "1" +e +# set DBR to 00 + +a 1000 sec +xce emulation mode +lda #$20 set highbyte +xba +lda #$00 lowbyte 16-bit akku +tcd direct page address to $2000 +ldx #$20 index +lda #$88 marker for wrap +sta $2010 +lda #$77 marker for no-wrap +sta $2110 +lda $f0,x which marker? +stz $f0,x write (non-6502) +brk + +!d Jammon alternate display mode +d disassemble on PC +m 2010 show wrap area +m 2110 show no-wrap area +z 1000 start single-stepping +z single step until BRK or ... +g go to BRK + +Results VICE WDC + +$2010 00 00 wrap area: =00 from STZ +$2110 77 77 no-wrap area + +A: $2088 $2088 A low: 88 = wrap, 77 = no-wrap + + +### Variant 2 + +Based on variant 1: + +a 1005 lda #$01 direct page not aligned! +tcd direct page at $2001 +ldx #$1f correction to hit the + same memory locations + for monitoring + +g 1000 + +Results VICE WDC + +$2011 88 88 wrap area: =00 from STZ +$2111 00 00 no-wrap area + +A: $2077 $2077 A low: 88 = wrap, 77 = no-wrap + diff --git a/OSBindings/Qt/ClockSignal.pro b/OSBindings/Qt/ClockSignal.pro index 3e42b9a02..98bd28638 100644 --- a/OSBindings/Qt/ClockSignal.pro +++ b/OSBindings/Qt/ClockSignal.pro @@ -91,6 +91,7 @@ SOURCES += \ \ $$SRC/Processors/6502/Implementation/*.cpp \ $$SRC/Processors/6502/State/*.cpp \ + $$SRC/Processors/65816/Implementation/*.cpp \ $$SRC/Processors/68000/Implementation/*.cpp \ $$SRC/Processors/68000/State/*.cpp \ $$SRC/Processors/Z80/Implementation/*.cpp \ @@ -214,6 +215,10 @@ HEADERS += \ $$SRC/Processors/6502/*.hpp \ $$SRC/Processors/6502/Implementation/*.hpp \ $$SRC/Processors/6502/State/*.hpp \ + $$SRC/Processors/6502Esque/*.hpp \ + $$SRC/Processors/6502Esque/Implementation/*.hpp \ + $$SRC/Processors/65816/*.hpp \ + $$SRC/Processors/65816/Implementation/*.hpp \ $$SRC/Processors/68000/*.hpp \ $$SRC/Processors/68000/Implementation/*.hpp \ $$SRC/Processors/68000/State/*.hpp \ diff --git a/OSBindings/Qt/mainwindow.cpp b/OSBindings/Qt/mainwindow.cpp index 1f23266ff..04138e7fc 100644 --- a/OSBindings/Qt/mainwindow.cpp +++ b/OSBindings/Qt/mainwindow.cpp @@ -287,7 +287,7 @@ void MainWindow::launchMachine() { // Populate request text. QString requestText = romRequestBaseText; size_t index = 0; - for(const auto rom: missingRoms) { + for(const auto &rom: missingRoms) { requestText += "• "; requestText += rom.descriptive_name.c_str(); diff --git a/OSBindings/SDL/SConstruct b/OSBindings/SDL/SConstruct index 9dcfe4a70..c21979fa8 100644 --- a/OSBindings/SDL/SConstruct +++ b/OSBindings/SDL/SConstruct @@ -85,6 +85,7 @@ SOURCES += glob.glob('../../Outputs/OpenGL/Primitives/*.cpp') SOURCES += glob.glob('../../Processors/6502/Implementation/*.cpp') SOURCES += glob.glob('../../Processors/6502/State/*.cpp') +SOURCES += glob.glob('../../Processors/65816/Implementation/*.cpp') SOURCES += glob.glob('../../Processors/68000/Implementation/*.cpp') SOURCES += glob.glob('../../Processors/68000/State/*.cpp') SOURCES += glob.glob('../../Processors/Z80/Implementation/*.cpp') diff --git a/Processors/6502/6502.hpp b/Processors/6502/6502.hpp index d91b5a010..7b4eb55a8 100644 --- a/Processors/6502/6502.hpp +++ b/Processors/6502/6502.hpp @@ -13,24 +13,19 @@ #include #include +#include "../6502Esque/6502Esque.hpp" +#include "../6502Esque/Implementation/LazyFlags.hpp" #include "../RegisterSizes.hpp" #include "../../ClockReceiver/ClockReceiver.hpp" namespace CPU { namespace MOS6502 { -/* - The list of registers that can be accessed via @c set_value_of_register and @c set_value_of_register. -*/ -enum Register { - LastOperationAddress, - ProgramCounter, - StackPointer, - Flags, - A, - X, - Y -}; +// Adopt a bunch of things from MOS6502Esque. +using BusOperation = CPU::MOS6502Esque::BusOperation; +using BusHandler = CPU::MOS6502Esque::BusHandler; +using Register = CPU::MOS6502Esque::Register; +using Flag = CPU::MOS6502Esque::Flag; /* The list of 6502 variants supported by this implementation. @@ -48,76 +43,11 @@ enum Personality { #define has_bbrbbsrmbsmb(p) ((p) >= Personality::PRockwell65C02) #define has_stpwai(p) ((p) >= Personality::PWDC65C02) -/* - Flags as defined on the 6502; can be used to decode the result of @c get_value_of_register(Flags) or to form a value for - the corresponding set. -*/ -enum Flag: uint8_t { - Sign = 0x80, - Overflow = 0x40, - Always = 0x20, - Break = 0x10, - Decimal = 0x08, - Interrupt = 0x04, - Zero = 0x02, - Carry = 0x01 -}; - -/*! - Subclasses will be given the task of performing bus operations, allowing them to provide whatever interface they like - between a 6502 and the rest of the system. @c BusOperation lists the types of bus operation that may be requested. - - @c None is reserved for internal use. It will never be requested from a subclass. It is safe always to use the - isReadOperation macro to make a binary choice between reading and writing. -*/ -enum BusOperation { - Read, ReadOpcode, Write, Ready, None -}; - -/*! - Evaluates to `true` if the operation is a read; `false` if it is a write. -*/ -#define isReadOperation(v) (v == CPU::MOS6502::BusOperation::Read || v == CPU::MOS6502::BusOperation::ReadOpcode) - /*! An opcode that is guaranteed to cause the CPU to jam. */ extern const uint8_t JamOpcode; -/*! - A class providing empty implementations of the methods a 6502 uses to access the bus. To wire the 6502 to a bus, - machines should subclass BusHandler and then declare a realisation of the 6502 template, suplying their bus - handler. -*/ -class BusHandler { - public: - /*! - Announces that the 6502 has performed the cycle defined by operation, address and value. On the 6502, - all bus cycles take one clock cycle so the amoutn of time advanced is implicit. - - @param operation The type of bus cycle: read, read opcode (i.e. read, with sync active), - write or ready. - @param address The value of the address bus during this bus cycle. - @param value If this is a cycle that puts a value onto the data bus, *value is that value. If this is - a cycle that reads the bus, the bus handler should write a value to *value. Writing to *value during - a read cycle will produce undefined behaviour. - - @returns The number of cycles that passed in objective time while this 6502 bus cycle was ongoing. - On an archetypal machine this will be Cycles(1) but some architectures may choose not to clock the 6502 - during some periods; one way to simulate that is to have the bus handler return a number other than - Cycles(1) to describe lengthened bus cycles. - */ - Cycles perform_bus_operation([[maybe_unused]] CPU::MOS6502::BusOperation operation, [[maybe_unused]] uint16_t address, [[maybe_unused]] uint8_t *value) { - return Cycles(1); - } - - /*! - Announces completion of all the cycles supplied to a .run_for request on the 6502. Intended to allow - bus handlers to perform any deferred output work. - */ - void flush() {} -}; - #include "Implementation/6502Storage.hpp" /*! @@ -205,12 +135,12 @@ class ProcessorBase: public ProcessorStorage { can also nominate whether the processor includes support for the ready line. Declining to support the ready line can produce a minor runtime performance improvement. */ -template class Processor: public ProcessorBase { +template class Processor: public ProcessorBase { public: /*! Constructs an instance of the 6502 that will use @c bus_handler for all bus communications. */ - Processor(T &bus_handler) : ProcessorBase(personality), bus_handler_(bus_handler) {} + Processor(BusHandler &bus_handler) : ProcessorBase(personality), bus_handler_(bus_handler) {} /*! Runs the 6502 for a supplied number of cycles. @@ -227,7 +157,7 @@ template class Proce void set_ready_line(bool active); private: - T &bus_handler_; + BusHandler &bus_handler_; }; #include "Implementation/6502Implementation.hpp" diff --git a/Processors/6502/AllRAM/6502AllRAM.cpp b/Processors/6502/AllRAM/6502AllRAM.cpp index 43e202efe..372cc40da 100644 --- a/Processors/6502/AllRAM/6502AllRAM.cpp +++ b/Processors/6502/AllRAM/6502AllRAM.cpp @@ -11,28 +11,51 @@ #include #include +//#define BE_NOISY + using namespace CPU::MOS6502; namespace { -template class ConcreteAllRAMProcessor: public AllRAMProcessor, public BusHandler { +using Type = CPU::MOS6502Esque::Type; + +template class ConcreteAllRAMProcessor: public AllRAMProcessor, public BusHandler { public: - ConcreteAllRAMProcessor() : + ConcreteAllRAMProcessor(size_t memory_size) : + AllRAMProcessor(memory_size), mos6502_(*this) { mos6502_.set_power_on(false); } - inline Cycles perform_bus_operation(BusOperation operation, uint16_t address, uint8_t *value) { + inline Cycles perform_bus_operation(BusOperation operation, uint32_t address, uint8_t *value) { timestamp_ += Cycles(1); if(operation == BusOperation::ReadOpcode) { +#ifdef BE_NOISY + printf("[%04x] %02x a:%04x x:%04x y:%04x p:%02x s:%02x\n", address, memory_[address], + mos6502_.get_value_of_register(Register::A), + mos6502_.get_value_of_register(Register::X), + mos6502_.get_value_of_register(Register::Y), + mos6502_.get_value_of_register(Register::Flags) & 0xff, + mos6502_.get_value_of_register(Register::StackPointer) & 0xff); +#endif check_address_for_trap(address); } if(isReadOperation(operation)) { *value = memory_[address]; +#ifdef BE_NOISY +// if((address&0xff00) == 0x100) { + printf("%04x -> %02x\n", address, *value); +// } +#endif } else { memory_[address] = *value; +#ifdef BE_NOISY +// if((address&0xff00) == 0x100) { + printf("%04x <- %02x\n", address, *value); +// } +#endif } return Cycles(1); @@ -63,20 +86,21 @@ template class ConcreteAllRAMProcessor: public AllRAMP } private: - CPU::MOS6502::Processor mos6502_; + CPU::MOS6502Esque::Processor mos6502_; }; } -AllRAMProcessor *AllRAMProcessor::Processor(Personality personality) { -#define Bind(p) case p: return new ConcreteAllRAMProcessor

(); - switch(personality) { +AllRAMProcessor *AllRAMProcessor::Processor(Type type) { +#define Bind(p) case p: return new ConcreteAllRAMProcessor

(type == Type::TWDC65816 ? 16*1024*1024 : 64*1024); + switch(type) { default: - Bind(Personality::P6502) - Bind(Personality::PNES6502) - Bind(Personality::PSynertek65C02) - Bind(Personality::PWDC65C02) - Bind(Personality::PRockwell65C02) + Bind(Type::T6502) + Bind(Type::TNES6502) + Bind(Type::TSynertek65C02) + Bind(Type::TWDC65C02) + Bind(Type::TRockwell65C02) + Bind(Type::TWDC65816) } #undef Bind } diff --git a/Processors/6502/AllRAM/6502AllRAM.hpp b/Processors/6502/AllRAM/6502AllRAM.hpp index 41dc61245..ad4c51cfe 100644 --- a/Processors/6502/AllRAM/6502AllRAM.hpp +++ b/Processors/6502/AllRAM/6502AllRAM.hpp @@ -9,7 +9,7 @@ #ifndef MOS6502AllRAM_cpp #define MOS6502AllRAM_cpp -#include "../6502.hpp" +#include "../../6502Esque/6502Selector.hpp" #include "../../AllRAMProcessor.hpp" namespace CPU { @@ -17,9 +17,8 @@ namespace MOS6502 { class AllRAMProcessor: public ::CPU::AllRAMProcessor { - public: - static AllRAMProcessor *Processor(Personality personality); + static AllRAMProcessor *Processor(CPU::MOS6502Esque::Type type); virtual ~AllRAMProcessor() {} virtual void run_for(const Cycles cycles) = 0; @@ -30,7 +29,7 @@ class AllRAMProcessor: virtual void set_value_of_register(Register r, uint16_t value) = 0; protected: - AllRAMProcessor() : ::CPU::AllRAMProcessor(65536) {} + AllRAMProcessor(size_t memory_size) : ::CPU::AllRAMProcessor(memory_size) {} }; } diff --git a/Processors/6502/Implementation/6502Base.cpp b/Processors/6502/Implementation/6502Base.cpp index bc80e6db0..ef50dc040 100644 --- a/Processors/6502/Implementation/6502Base.cpp +++ b/Processors/6502/Implementation/6502Base.cpp @@ -8,8 +8,6 @@ #include "../6502.hpp" -#include - using namespace CPU::MOS6502; const uint8_t CPU::MOS6502::JamOpcode = 0xf2; diff --git a/Processors/6502/Implementation/6502Implementation.hpp b/Processors/6502/Implementation/6502Implementation.hpp index 773edbb00..f5f2a6512 100644 --- a/Processors/6502/Implementation/6502Implementation.hpp +++ b/Processors/6502/Implementation/6502Implementation.hpp @@ -13,16 +13,6 @@ */ template void Processor::run_for(const Cycles cycles) { - static uint8_t throwaway_target; - - // These plus program below act to give the compiler permission to update these values - // without touching the class storage (i.e. it explicitly says they need be completely up - // to date in this stack frame only); which saves some complicated addressing - RegisterPair16 nextAddress = next_address_; - BusOperation nextBusOperation = next_bus_operation_; - uint16_t busAddress = bus_address_; - uint8_t *busValue = bus_value_; - #define checkSchedule() \ if(!scheduled_program_counter_) {\ if(interrupt_requests_) {\ @@ -42,9 +32,9 @@ template void Proces #define bus_access() \ interrupt_requests_ = (interrupt_requests_ & ~InterruptRequestFlags::IRQ) | irq_request_history_; \ - irq_request_history_ = irq_line_ & inverse_interrupt_flag_; \ - number_of_cycles -= bus_handler_.perform_bus_operation(nextBusOperation, busAddress, busValue); \ - nextBusOperation = BusOperation::None; \ + irq_request_history_ = irq_line_ & flags_.inverse_interrupt; \ + number_of_cycles -= bus_handler_.perform_bus_operation(next_bus_operation_, bus_address_, bus_value_); \ + next_bus_operation_ = BusOperation::None; \ if(number_of_cycles <= Cycles(0)) break; checkSchedule(); @@ -54,12 +44,12 @@ template void Proces // Deal with a potential RDY state, if this 6502 has anything connected to ready. while(uses_ready_line && ready_is_active_ && number_of_cycles > Cycles(0)) { - number_of_cycles -= bus_handler_.perform_bus_operation(BusOperation::Ready, busAddress, busValue); + number_of_cycles -= bus_handler_.perform_bus_operation(BusOperation::Ready, bus_address_, bus_value_); } // Deal with a potential STP state, if this 6502 implements STP. while(has_stpwai(personality) && stop_is_active_ && number_of_cycles > Cycles(0)) { - number_of_cycles -= bus_handler_.perform_bus_operation(BusOperation::Ready, busAddress, busValue); + number_of_cycles -= bus_handler_.perform_bus_operation(BusOperation::Ready, bus_address_, bus_value_); if(interrupt_requests_ & InterruptRequestFlags::Reset) { stop_is_active_ = false; checkSchedule(); @@ -69,8 +59,8 @@ template void Proces // Deal with a potential WAI state, if this 6502 implements WAI. while(has_stpwai(personality) && wait_is_active_ && number_of_cycles > Cycles(0)) { - number_of_cycles -= bus_handler_.perform_bus_operation(BusOperation::Ready, busAddress, busValue); - interrupt_requests_ |= (irq_line_ & inverse_interrupt_flag_); + number_of_cycles -= bus_handler_.perform_bus_operation(BusOperation::Ready, bus_address_, bus_value_); + interrupt_requests_ |= (irq_line_ & flags_.inverse_interrupt); if(interrupt_requests_ & InterruptRequestFlags::NMI || irq_line_) { wait_is_active_ = false; checkSchedule(); @@ -79,7 +69,7 @@ template void Proces } if((!uses_ready_line || !ready_is_active_) && (!has_stpwai(personality) || (!wait_is_active_ && !stop_is_active_))) { - if(nextBusOperation != BusOperation::None) { + if(next_bus_operation_ != BusOperation::None) { bus_access(); } @@ -88,10 +78,10 @@ template void Proces const MicroOp cycle = *scheduled_program_counter_; scheduled_program_counter_++; -#define read_op(val, addr) nextBusOperation = BusOperation::ReadOpcode; busAddress = addr; busValue = &val; val = 0xff -#define read_mem(val, addr) nextBusOperation = BusOperation::Read; busAddress = addr; busValue = &val; val = 0xff -#define throwaway_read(addr) nextBusOperation = BusOperation::Read; busAddress = addr; busValue = &throwaway_target; throwaway_target = 0xff -#define write_mem(val, addr) nextBusOperation = BusOperation::Write; busAddress = addr; busValue = &val +#define read_op(val, addr) next_bus_operation_ = BusOperation::ReadOpcode; bus_address_ = addr; bus_value_ = &val; val = 0xff +#define read_mem(val, addr) next_bus_operation_ = BusOperation::Read; bus_address_ = addr; bus_value_ = &val; val = 0xff +#define throwaway_read(addr) next_bus_operation_ = BusOperation::Read; bus_address_ = addr; bus_value_ = &bus_throwaway_; bus_throwaway_ = 0xff +#define write_mem(val, addr) next_bus_operation_ = BusOperation::Write; bus_address_ = addr; bus_value_ = &val switch(cycle) { @@ -155,37 +145,37 @@ template void Proces case OperationBRKPickVector: if(is_65c02(personality)) { - nextAddress.full = 0xfffe; + next_address_.full = 0xfffe; } else { // NMI can usurp BRK-vector operations on the pre-C 6502s. - nextAddress.full = (interrupt_requests_ & InterruptRequestFlags::NMI) ? 0xfffa : 0xfffe; + next_address_.full = (interrupt_requests_ & InterruptRequestFlags::NMI) ? 0xfffa : 0xfffe; interrupt_requests_ &= ~InterruptRequestFlags::NMI; } continue; - case OperationNMIPickVector: nextAddress.full = 0xfffa; continue; - case OperationRSTPickVector: nextAddress.full = 0xfffc; continue; - case CycleReadVectorLow: read_mem(pc_.halves.low, nextAddress.full); break; - case CycleReadVectorHigh: read_mem(pc_.halves.high, nextAddress.full+1); break; + case OperationNMIPickVector: next_address_.full = 0xfffa; continue; + case OperationRSTPickVector: next_address_.full = 0xfffc; continue; + case CycleReadVectorLow: read_mem(pc_.halves.low, next_address_.full); break; + case CycleReadVectorHigh: read_mem(pc_.halves.high, next_address_.full+1); break; case OperationSetIRQFlags: - inverse_interrupt_flag_ = 0; - if(is_65c02(personality)) decimal_flag_ = false; + flags_.inverse_interrupt = 0; + if(is_65c02(personality)) flags_.decimal = 0; continue; case OperationSetNMIRSTFlags: - if(is_65c02(personality)) decimal_flag_ = false; + if(is_65c02(personality)) flags_.decimal = 0; continue; - case CyclePullPCL: s_++; read_mem(pc_.halves.low, s_ | 0x100); break; - case CyclePullPCH: s_++; read_mem(pc_.halves.high, s_ | 0x100); break; - case CyclePullA: s_++; read_mem(a_, s_ | 0x100); break; - case CyclePullX: s_++; read_mem(x_, s_ | 0x100); break; - case CyclePullY: s_++; read_mem(y_, s_ | 0x100); break; - case CyclePullOperand: s_++; read_mem(operand_, s_ | 0x100); break; - case OperationSetFlagsFromOperand: set_flags(operand_); continue; - case OperationSetOperandFromFlagsWithBRKSet: operand_ = get_flags() | Flag::Break; continue; - case OperationSetOperandFromFlags: operand_ = get_flags(); continue; - case OperationSetFlagsFromA: zero_result_ = negative_result_ = a_; continue; - case OperationSetFlagsFromX: zero_result_ = negative_result_ = x_; continue; - case OperationSetFlagsFromY: zero_result_ = negative_result_ = y_; continue; + case CyclePullPCL: s_++; read_mem(pc_.halves.low, s_ | 0x100); break; + case CyclePullPCH: s_++; read_mem(pc_.halves.high, s_ | 0x100); break; + case CyclePullA: s_++; read_mem(a_, s_ | 0x100); break; + case CyclePullX: s_++; read_mem(x_, s_ | 0x100); break; + case CyclePullY: s_++; read_mem(y_, s_ | 0x100); break; + case CyclePullOperand: s_++; read_mem(operand_, s_ | 0x100); break; + case OperationSetFlagsFromOperand: set_flags(operand_); continue; + case OperationSetOperandFromFlagsWithBRKSet: operand_ = flags_.get() | Flag::Break; continue; + case OperationSetOperandFromFlags: operand_ = flags_.get(); continue; + case OperationSetFlagsFromA: flags_.set_nz(a_); continue; + case OperationSetFlagsFromX: flags_.set_nz(x_); continue; + case OperationSetFlagsFromY: flags_.set_nz(y_); continue; case CycleIncrementPCAndReadStack: pc_.full++; throwaway_read(s_ | 0x100); break; case CycleReadPCLFromAddress: read_mem(pc_.halves.low, address_.full); break; @@ -216,17 +206,17 @@ template void Proces // MARK: - Bitwise - case OperationORA: a_ |= operand_; negative_result_ = zero_result_ = a_; continue; - case OperationAND: a_ &= operand_; negative_result_ = zero_result_ = a_; continue; - case OperationEOR: a_ ^= operand_; negative_result_ = zero_result_ = a_; continue; + case OperationORA: a_ |= operand_; flags_.set_nz(a_); continue; + case OperationAND: a_ &= operand_; flags_.set_nz(a_); continue; + case OperationEOR: a_ ^= operand_; flags_.set_nz(a_); continue; // MARK: - Load and Store - case OperationLDA: a_ = negative_result_ = zero_result_ = operand_; continue; - case OperationLDX: x_ = negative_result_ = zero_result_ = operand_; continue; - case OperationLDY: y_ = negative_result_ = zero_result_ = operand_; continue; - case OperationLAX: a_ = x_ = negative_result_ = zero_result_ = operand_; continue; - case OperationCopyOperandToA: a_ = operand_; continue; + case OperationLDA: flags_.set_nz(a_ = operand_); continue; + case OperationLDX: flags_.set_nz(x_ = operand_); continue; + case OperationLDY: flags_.set_nz(y_ = operand_); continue; + case OperationLAX: flags_.set_nz(a_ = x_ = operand_); continue; + case OperationCopyOperandToA: a_ = operand_; continue; case OperationSTA: operand_ = a_; continue; case OperationSTX: operand_ = x_; continue; @@ -240,43 +230,43 @@ template void Proces case OperationLXA: a_ = x_ = (a_ | 0xee) & operand_; - negative_result_ = zero_result_ = a_; + flags_.set_nz(a_); continue; // MARK: - Compare case OperationCMP: { const uint16_t temp16 = a_ - operand_; - negative_result_ = zero_result_ = uint8_t(temp16); - carry_flag_ = ((~temp16) >> 8)&1; + flags_.set_nz(uint8_t(temp16)); + flags_.carry = ((~temp16) >> 8)&1; } continue; case OperationCPX: { const uint16_t temp16 = x_ - operand_; - negative_result_ = zero_result_ = uint8_t(temp16); - carry_flag_ = ((~temp16) >> 8)&1; + flags_.set_nz(uint8_t(temp16)); + flags_.carry = ((~temp16) >> 8)&1; } continue; case OperationCPY: { const uint16_t temp16 = y_ - operand_; - negative_result_ = zero_result_ = uint8_t(temp16); - carry_flag_ = ((~temp16) >> 8)&1; + flags_.set_nz(uint8_t(temp16)); + flags_.carry = ((~temp16) >> 8)&1; } continue; // MARK: - BIT, TSB, TRB case OperationBIT: - zero_result_ = operand_ & a_; - negative_result_ = operand_; - overflow_flag_ = operand_&Flag::Overflow; + flags_.zero_result = operand_ & a_; + flags_.negative_result = operand_; + flags_.overflow = operand_ & Flag::Overflow; continue; case OperationBITNoNV: - zero_result_ = operand_ & a_; + flags_.zero_result = operand_ & a_; continue; case OperationTRB: - zero_result_ = operand_ & a_; + flags_.zero_result = operand_ & a_; operand_ &= ~a_; continue; case OperationTSB: - zero_result_ = operand_ & a_; + flags_.zero_result = operand_ & a_; operand_ |= a_; continue; @@ -295,8 +285,8 @@ template void Proces operand_++; [[fallthrough]]; case OperationSBC: - if(decimal_flag_ && has_decimal_mode(personality)) { - const uint16_t notCarry = carry_flag_ ^ 0x1; + if(flags_.decimal && has_decimal_mode(personality)) { + const uint16_t notCarry = flags_.carry ^ 0x1; const uint16_t decimalResult = uint16_t(a_) - uint16_t(operand_) - notCarry; uint16_t temp16; @@ -305,17 +295,17 @@ template void Proces temp16 = (temp16&0x0f) | ((temp16 > 0x0f) ? 0xfff0 : 0x00); temp16 += (a_&0xf0) - (operand_&0xf0); - overflow_flag_ = ( ( (decimalResult^a_)&(~decimalResult^operand_) )&0x80) >> 1; - negative_result_ = uint8_t(temp16); - zero_result_ = uint8_t(decimalResult); + flags_.overflow = ( ( (decimalResult^a_)&(~decimalResult^operand_) )&0x80) >> 1; + flags_.negative_result = uint8_t(temp16); + flags_.zero_result = uint8_t(decimalResult); if(temp16 > 0xff) temp16 -= 0x60; - carry_flag_ = (temp16 > 0xff) ? 0 : Flag::Carry; + flags_.carry = (temp16 > 0xff) ? 0 : Flag::Carry; a_ = uint8_t(temp16); if(is_65c02(personality)) { - negative_result_ = zero_result_ = a_; + flags_.set_nz(a_); read_mem(operand_, address_.full); break; } @@ -326,30 +316,30 @@ template void Proces [[fallthrough]]; case OperationADC: - if(decimal_flag_ && has_decimal_mode(personality)) { - const uint16_t decimalResult = uint16_t(a_) + uint16_t(operand_) + uint16_t(carry_flag_); + if(flags_.decimal && has_decimal_mode(personality)) { + const uint16_t decimalResult = uint16_t(a_) + uint16_t(operand_) + uint16_t(flags_.carry); - uint8_t low_nibble = (a_ & 0xf) + (operand_ & 0xf) + carry_flag_; + uint8_t low_nibble = (a_ & 0xf) + (operand_ & 0xf) + flags_.carry; if(low_nibble >= 0xa) low_nibble = ((low_nibble + 0x6) & 0xf) + 0x10; uint16_t result = uint16_t(a_ & 0xf0) + uint16_t(operand_ & 0xf0) + uint16_t(low_nibble); - negative_result_ = uint8_t(result); - overflow_flag_ = (( (result^a_)&(result^operand_) )&0x80) >> 1; + flags_.negative_result = uint8_t(result); + flags_.overflow = (( (result^a_)&(result^operand_) )&0x80) >> 1; if(result >= 0xa0) result += 0x60; - carry_flag_ = (result >> 8) ? 1 : 0; + flags_.carry = (result >> 8) ? 1 : 0; a_ = uint8_t(result); - zero_result_ = uint8_t(decimalResult); + flags_.zero_result = uint8_t(decimalResult); if(is_65c02(personality)) { - negative_result_ = zero_result_ = a_; + flags_.set_nz(a_); read_mem(operand_, address_.full); break; } } else { - const uint16_t result = uint16_t(a_) + uint16_t(operand_) + uint16_t(carry_flag_); - overflow_flag_ = (( (result^a_)&(result^operand_) )&0x80) >> 1; - negative_result_ = zero_result_ = a_ = uint8_t(result); - carry_flag_ = (result >> 8)&1; + const uint16_t result = uint16_t(a_) + uint16_t(operand_) + uint16_t(flags_.carry); + flags_.overflow = (( (result^a_)&(result^operand_) )&0x80) >> 1; + flags_.set_nz(a_ = uint8_t(result)); + flags_.carry = (result >> 8)&1; } // fix up in case this was INS @@ -359,99 +349,99 @@ template void Proces // MARK: - Shifts and Rolls case OperationASL: - carry_flag_ = operand_ >> 7; + flags_.carry = operand_ >> 7; operand_ <<= 1; - negative_result_ = zero_result_ = operand_; + flags_.set_nz(operand_); continue; case OperationASO: - carry_flag_ = operand_ >> 7; + flags_.carry = operand_ >> 7; operand_ <<= 1; a_ |= operand_; - negative_result_ = zero_result_ = a_; + flags_.set_nz(a_); continue; case OperationROL: { - const uint8_t temp8 = uint8_t((operand_ << 1) | carry_flag_); - carry_flag_ = operand_ >> 7; - operand_ = negative_result_ = zero_result_ = temp8; + const uint8_t temp8 = uint8_t((operand_ << 1) | flags_.carry); + flags_.carry = operand_ >> 7; + flags_.set_nz(operand_ = temp8); } continue; case OperationRLA: { - const uint8_t temp8 = uint8_t((operand_ << 1) | carry_flag_); - carry_flag_ = operand_ >> 7; + const uint8_t temp8 = uint8_t((operand_ << 1) | flags_.carry); + flags_.carry = operand_ >> 7; operand_ = temp8; a_ &= operand_; - negative_result_ = zero_result_ = a_; + flags_.set_nz(a_); } continue; case OperationLSR: - carry_flag_ = operand_ & 1; + flags_.carry = operand_ & 1; operand_ >>= 1; - negative_result_ = zero_result_ = operand_; + flags_.set_nz(operand_); continue; case OperationLSE: - carry_flag_ = operand_ & 1; + flags_.carry = operand_ & 1; operand_ >>= 1; a_ ^= operand_; - negative_result_ = zero_result_ = a_; + flags_.set_nz(a_); continue; case OperationASR: a_ &= operand_; - carry_flag_ = a_ & 1; + flags_.carry = a_ & 1; a_ >>= 1; - negative_result_ = zero_result_ = a_; + flags_.set_nz(a_); continue; case OperationROR: { - const uint8_t temp8 = uint8_t((operand_ >> 1) | (carry_flag_ << 7)); - carry_flag_ = operand_ & 1; - operand_ = negative_result_ = zero_result_ = temp8; + const uint8_t temp8 = uint8_t((operand_ >> 1) | (flags_.carry << 7)); + flags_.carry = operand_ & 1; + flags_.set_nz(operand_ = temp8); } continue; case OperationRRA: { - const uint8_t temp8 = uint8_t((operand_ >> 1) | (carry_flag_ << 7)); - carry_flag_ = operand_ & 1; + const uint8_t temp8 = uint8_t((operand_ >> 1) | (flags_.carry << 7)); + flags_.carry = operand_ & 1; operand_ = temp8; } continue; case OperationDecrementOperand: operand_--; continue; case OperationIncrementOperand: operand_++; continue; - case OperationCLC: carry_flag_ = 0; continue; - case OperationCLI: inverse_interrupt_flag_ = Flag::Interrupt; continue; - case OperationCLV: overflow_flag_ = 0; continue; - case OperationCLD: decimal_flag_ = 0; continue; + case OperationCLC: flags_.carry = 0; continue; + case OperationCLI: flags_.inverse_interrupt = Flag::Interrupt; continue; + case OperationCLV: flags_.overflow = 0; continue; + case OperationCLD: flags_.decimal = 0; continue; - case OperationSEC: carry_flag_ = Flag::Carry; continue; - case OperationSEI: inverse_interrupt_flag_ = 0; continue; - case OperationSED: decimal_flag_ = Flag::Decimal; continue; + case OperationSEC: flags_.carry = Flag::Carry; continue; + case OperationSEI: flags_.inverse_interrupt = 0; continue; + case OperationSED: flags_.decimal = Flag::Decimal; continue; - case OperationINC: operand_++; negative_result_ = zero_result_ = operand_; continue; - case OperationDEC: operand_--; negative_result_ = zero_result_ = operand_; continue; - case OperationINA: a_++; negative_result_ = zero_result_ = a_; continue; - case OperationDEA: a_--; negative_result_ = zero_result_ = a_; continue; - case OperationINX: x_++; negative_result_ = zero_result_ = x_; continue; - case OperationDEX: x_--; negative_result_ = zero_result_ = x_; continue; - case OperationINY: y_++; negative_result_ = zero_result_ = y_; continue; - case OperationDEY: y_--; negative_result_ = zero_result_ = y_; continue; + case OperationINC: operand_++; flags_.set_nz(operand_); continue; + case OperationDEC: operand_--; flags_.set_nz(operand_); continue; + case OperationINA: a_++; flags_.set_nz(a_); continue; + case OperationDEA: a_--; flags_.set_nz(a_); continue; + case OperationINX: x_++; flags_.set_nz(x_); continue; + case OperationDEX: x_--; flags_.set_nz(x_); continue; + case OperationINY: y_++; flags_.set_nz(y_); continue; + case OperationDEY: y_--; flags_.set_nz(y_); continue; case OperationANE: a_ = (a_ | 0xee) & operand_ & x_; - negative_result_ = zero_result_ = a_; + flags_.set_nz(a_); continue; case OperationANC: a_ &= operand_; - negative_result_ = zero_result_ = a_; - carry_flag_ = a_ >> 7; + flags_.set_nz(a_); + flags_.carry = a_ >> 7; continue; case OperationLAS: a_ = x_ = s_ = s_ & operand_; - negative_result_ = zero_result_ = a_; + flags_.set_nz(a_); continue; // MARK: - Addressing Mode Work @@ -464,36 +454,36 @@ template void Proces } case CycleAddXToAddressLow: - nextAddress.full = address_.full + x_; - address_.halves.low = nextAddress.halves.low; - if(address_.halves.high != nextAddress.halves.high) { + next_address_.full = address_.full + x_; + address_.halves.low = next_address_.halves.low; + if(address_.halves.high != next_address_.halves.high) { page_crossing_stall_read(); break; } continue; case CycleAddXToAddressLowRead: - nextAddress.full = address_.full + x_; - address_.halves.low = nextAddress.halves.low; + next_address_.full = address_.full + x_; + address_.halves.low = next_address_.halves.low; page_crossing_stall_read(); break; case CycleAddYToAddressLow: - nextAddress.full = address_.full + y_; - address_.halves.low = nextAddress.halves.low; - if(address_.halves.high != nextAddress.halves.high) { + next_address_.full = address_.full + y_; + address_.halves.low = next_address_.halves.low; + if(address_.halves.high != next_address_.halves.high) { page_crossing_stall_read(); break; } continue; case CycleAddYToAddressLowRead: - nextAddress.full = address_.full + y_; - address_.halves.low = nextAddress.halves.low; + next_address_.full = address_.full + y_; + address_.halves.low = next_address_.halves.low; page_crossing_stall_read(); break; #undef page_crossing_stall_read case OperationCorrectAddressHigh: - address_.full = nextAddress.full; + address_.full = next_address_.full; continue; case CycleIncrementPCFetchAddressLowFromOperand: pc_.full++; @@ -548,7 +538,7 @@ template void Proces throwaway_read(operand_); break; - case OperationIncrementPC: pc_.full++; continue; + case OperationIncrementPC: pc_.full++; continue; case CycleFetchOperandFromAddress: read_mem(operand_, address_.full); break; case CycleWriteOperandToAddress: write_mem(operand_, address_.full); break; @@ -560,25 +550,25 @@ template void Proces scheduled_program_counter_ = operations_[size_t(OperationsSlot::DoBRA)]; \ } - case OperationBPL: BRA(!(negative_result_&0x80)); continue; - case OperationBMI: BRA(negative_result_&0x80); continue; - case OperationBVC: BRA(!overflow_flag_); continue; - case OperationBVS: BRA(overflow_flag_); continue; - case OperationBCC: BRA(!carry_flag_); continue; - case OperationBCS: BRA(carry_flag_); continue; - case OperationBNE: BRA(zero_result_); continue; - case OperationBEQ: BRA(!zero_result_); continue; + case OperationBPL: BRA(!(flags_.negative_result&0x80)); continue; + case OperationBMI: BRA(flags_.negative_result&0x80); continue; + case OperationBVC: BRA(!flags_.overflow); continue; + case OperationBVS: BRA(flags_.overflow); continue; + case OperationBCC: BRA(!flags_.carry); continue; + case OperationBCS: BRA(flags_.carry); continue; + case OperationBNE: BRA(flags_.zero_result); continue; + case OperationBEQ: BRA(!flags_.zero_result); continue; case OperationBRA: BRA(true); continue; #undef BRA case CycleAddSignedOperandToPC: - nextAddress.full = uint16_t(pc_.full + int8_t(operand_)); - pc_.halves.low = nextAddress.halves.low; - if(nextAddress.halves.high != pc_.halves.high) { - uint16_t halfUpdatedPc = pc_.full; - pc_.full = nextAddress.full; - throwaway_read(halfUpdatedPc); + next_address_.full = uint16_t(pc_.full + int8_t(operand_)); + pc_.halves.low = next_address_.halves.low; + if(next_address_.halves.high != pc_.halves.high) { + const uint16_t half_updated_pc = pc_.full; + pc_.full = next_address_.full; + throwaway_read(half_updated_pc); break; } else if(is_65c02(personality)) { // 65C02 modification to all branches: a branch that is taken but requires only a single cycle @@ -610,31 +600,31 @@ template void Proces // MARK: - Transfers - case OperationTXA: zero_result_ = negative_result_ = a_ = x_; continue; - case OperationTYA: zero_result_ = negative_result_ = a_ = y_; continue; - case OperationTXS: s_ = x_; continue; - case OperationTAY: zero_result_ = negative_result_ = y_ = a_; continue; - case OperationTAX: zero_result_ = negative_result_ = x_ = a_; continue; - case OperationTSX: zero_result_ = negative_result_ = x_ = s_; continue; + case OperationTXA: flags_.set_nz(a_ = x_); continue; + case OperationTYA: flags_.set_nz(a_ = y_); continue; + case OperationTXS: s_ = x_; continue; + case OperationTAY: flags_.set_nz(y_ = a_); continue; + case OperationTAX: flags_.set_nz(x_ = a_); continue; + case OperationTSX: flags_.set_nz(x_ = s_); continue; case OperationARR: - if(decimal_flag_) { + if(flags_.decimal) { a_ &= operand_; uint8_t unshiftedA = a_; - a_ = uint8_t((a_ >> 1) | (carry_flag_ << 7)); - zero_result_ = negative_result_ = a_; - overflow_flag_ = (a_^(a_ << 1))&Flag::Overflow; + a_ = uint8_t((a_ >> 1) | (flags_.carry << 7)); + flags_.set_nz(a_); + flags_.overflow = (a_^(a_ << 1))&Flag::Overflow; if((unshiftedA&0xf) + (unshiftedA&0x1) > 5) a_ = ((a_ + 6)&0xf) | (a_ & 0xf0); - carry_flag_ = ((unshiftedA&0xf0) + (unshiftedA&0x10) > 0x50) ? 1 : 0; - if(carry_flag_) a_ += 0x60; + flags_.carry = ((unshiftedA&0xf0) + (unshiftedA&0x10) > 0x50) ? 1 : 0; + if(flags_.carry) a_ += 0x60; } else { a_ &= operand_; - a_ = uint8_t((a_ >> 1) | (carry_flag_ << 7)); - negative_result_ = zero_result_ = a_; - carry_flag_ = (a_ >> 6)&1; - overflow_flag_ = (a_^(a_ << 1))&Flag::Overflow; + a_ = uint8_t((a_ >> 1) | (flags_.carry << 7)); + flags_.set_nz(a_); + flags_.carry = (a_ >> 6)&1; + flags_.overflow = (a_^(a_ << 1))&Flag::Overflow; } continue; @@ -642,15 +632,15 @@ template void Proces x_ &= a_; uint16_t difference = x_ - operand_; x_ = uint8_t(difference); - negative_result_ = zero_result_ = x_; - carry_flag_ = ((difference >> 8)&1)^1; + flags_.set_nz(x_); + flags_.carry = ((difference >> 8)&1)^1; continue; } if(has_stpwai(personality) && (stop_is_active_ || wait_is_active_)) { break; } - if(uses_ready_line && ready_line_is_enabled_ && (is_65c02(personality) || isReadOperation(nextBusOperation))) { + if(uses_ready_line && ready_line_is_enabled_ && (is_65c02(personality) || isReadOperation(next_bus_operation_))) { ready_is_active_ = true; break; } @@ -660,11 +650,6 @@ template void Proces } cycles_left_to_run_ = number_of_cycles; - next_address_ = nextAddress; - next_bus_operation_ = nextBusOperation; - bus_address_ = busAddress; - bus_value_ = busValue; - bus_handler_.flush(); } @@ -691,13 +676,13 @@ void ProcessorBase::set_power_on(bool active) { } void ProcessorBase::set_irq_line(bool active) { - irq_line_ = active ? Flag::Interrupt : 0; + irq_line_ = active ? MOS6502Esque::Flag::Interrupt : 0; } void ProcessorBase::set_overflow_line(bool active) { // a leading edge will set the overflow flag if(active && !set_overflow_line_is_enabled_) - overflow_flag_ = Flag::Overflow; + flags_.overflow = MOS6502Esque::Flag::Overflow; set_overflow_line_is_enabled_ = active; } @@ -709,14 +694,9 @@ void ProcessorBase::set_nmi_line(bool active) { } uint8_t ProcessorStorage::get_flags() const { - return carry_flag_ | overflow_flag_ | (inverse_interrupt_flag_ ^ Flag::Interrupt) | (negative_result_ & 0x80) | (zero_result_ ? 0 : Flag::Zero) | Flag::Always | decimal_flag_; + return flags_.get(); } void ProcessorStorage::set_flags(uint8_t flags) { - carry_flag_ = flags & Flag::Carry; - negative_result_ = flags & Flag::Sign; - zero_result_ = (~flags) & Flag::Zero; - overflow_flag_ = flags & Flag::Overflow; - inverse_interrupt_flag_ = (~flags) & Flag::Interrupt; - decimal_flag_ = flags & Flag::Decimal; + flags_.set(flags); } diff --git a/Processors/6502/Implementation/6502Storage.cpp b/Processors/6502/Implementation/6502Storage.cpp index e87d6140a..8795e2e74 100644 --- a/Processors/6502/Implementation/6502Storage.cpp +++ b/Processors/6502/Implementation/6502Storage.cpp @@ -76,12 +76,6 @@ using namespace CPU::MOS6502; #define JAM {CycleFetchOperand, OperationScheduleJam} ProcessorStorage::ProcessorStorage(Personality personality) { - // only the interrupt flag is defined upon reset but get_flags isn't going to - // mask the other flags so we need to do that, at least - carry_flag_ &= Flag::Carry; - decimal_flag_ &= Flag::Decimal; - overflow_flag_ &= Flag::Overflow; - const InstructionList operations_6502[] = { /* 0x00 BRK */ Program(CycleIncPCPushPCH, CyclePushPCL, OperationBRKPickVector, OperationSetOperandFromFlagsWithBRKSet, CyclePushOperand, OperationSetIRQFlags, CycleReadVectorLow, CycleReadVectorHigh), /* 0x01 ORA x, ind */ IndexedIndirectRead(OperationORA), diff --git a/Processors/6502/Implementation/6502Storage.hpp b/Processors/6502/Implementation/6502Storage.hpp index 037943b70..5984e4c27 100644 --- a/Processors/6502/Implementation/6502Storage.hpp +++ b/Processors/6502/Implementation/6502Storage.hpp @@ -6,9 +6,6 @@ // Copyright 2017 Thomas Harte. All rights reserved. // -#ifndef MOS6502Storage_h -#define MOS6502Storage_h - /*! A repository for all the internal state of a CPU::MOS6502::Processor; extracted into a separate base class in order to remove it from visibility within the main 6502.hpp. @@ -231,7 +228,7 @@ class ProcessorStorage { */ RegisterPair16 pc_, last_operation_pc_; uint8_t a_, x_, y_, s_ = 0; - uint8_t carry_flag_, negative_result_, zero_result_, decimal_flag_, overflow_flag_, inverse_interrupt_flag_ = 0; + MOS6502Esque::LazyFlags flags_; /* Temporary state for the micro programs. @@ -246,6 +243,7 @@ class ProcessorStorage { BusOperation next_bus_operation_ = BusOperation::None; uint16_t bus_address_; uint8_t *bus_value_; + static inline uint8_t bus_throwaway_; /*! Gets the flags register. @@ -270,7 +268,7 @@ class ProcessorStorage { enum InterruptRequestFlags: uint8_t { Reset = 0x80, - IRQ = Flag::Interrupt, + IRQ = MOS6502Esque::Flag::Interrupt, NMI = 0x20, PowerOn = 0x10, @@ -288,5 +286,3 @@ class ProcessorStorage { // Allow state objects to capture and apply state. friend struct State; }; - -#endif /* _502Storage_h */ diff --git a/Processors/6502Esque/6502Esque.hpp b/Processors/6502Esque/6502Esque.hpp new file mode 100644 index 000000000..953333b44 --- /dev/null +++ b/Processors/6502Esque/6502Esque.hpp @@ -0,0 +1,155 @@ +// +// 6502Esque.hpp +// Clock Signal +// +// Created by Thomas Harte on 28/09/2020. +// Copyright © 2020 Thomas Harte. All rights reserved. +// + +#ifndef m6502Esque_h +#define m6502Esque_h + +#include "../../ClockReceiver/ClockReceiver.hpp" + +/* + This file defines how the CPU-controlled part of a bus looks for the 6502 and + for other processors with a sufficiently-similar bus. + + I'm not yet a big fan of the name I've used here, and I'm still on the fence + about what to do when eventually I get around to the 6800 and/or 6809, which have + very similar bus characteristics. + + So: this is _very_ provisional stuff. +*/ +namespace CPU { +namespace MOS6502Esque { + +/* + The list of registers that can be accessed via @c set_value_of_register and @c set_value_of_register. +*/ +enum Register { + LastOperationAddress, + ProgramCounter, + StackPointer, + Flags, + A, + X, + Y, + + // These exist on a 65816 only. + EmulationFlag, + DataBank, + ProgramBank, + Direct +}; + +/* + Flags as defined on the 6502; can be used to decode the result of @c get_value_of_register(Flags) or to form a value for + the corresponding set. +*/ +enum Flag: uint8_t { + Sign = 0x80, + Overflow = 0x40, + Always = 0x20, + Break = 0x10, + Decimal = 0x08, + Interrupt = 0x04, + Zero = 0x02, + Carry = 0x01, + + // These are available on a 65816 only. + MemorySize = 0x20, + IndexSize = 0x10, +}; + +/*! + Bus handlers will be given the task of performing bus operations, allowing them to provide whatever interface they like + between a 6502-esque chip and the rest of the system. @c BusOperation lists the types of bus operation that may be requested. +*/ +enum BusOperation { + /// 6502: indicates that a read was signalled. + /// 65816: indicates that a read was signalled with VDA. + Read, + /// 6502: indicates that a read was signalled with SYNC. + /// 65816: indicates that a read was signalled with VDA and VPA. + ReadOpcode, + /// 6502: never signalled. + /// 65816: indicates that a read was signalled with VPA. + ReadProgram, + /// 6502: never signalled. + /// 65816: indicates that a read was signalled with VPB. + ReadVector, + /// 6502: never signalled. + /// 65816: indicates that a read was signalled, but neither VDA nor VPA were active. + InternalOperationRead, + + /// 6502: indicates that a write was signalled. + /// 65816: indicates that a write was signalled with VDA. + Write, + /// 6502: never signalled. + /// 65816: indicates that a write was signalled, but neither VDA nor VPA were active. + InternalOperationWrite, + + /// All processors: indicates that the processor is paused due to the RDY input. + /// 65C02 and 65816: indicates a WAI is ongoing. + Ready, + + /// 65C02 and 65816: indicates a STP condition. + None, +}; + +/*! + For a machine watching only the RWB line, evaluates to @c true if the operation should be treated as a read; @c false otherwise. +*/ +#define isReadOperation(v) (v <= CPU::MOS6502Esque::InternalOperationRead) + +/*! + For a machine watching only the RWB line, evaluates to @c true if the operation is any sort of write; @c false otherwise. +*/ +#define isWriteOperation(v) (v == CPU::MOS6502Esque::Write || v == CPU::MOS6502Esque::InternalOperationWrite) + +/*! + Evaluates to @c true if the operation actually expects a response; @c false otherwise. +*/ +#define isAccessOperation(v) ((v < CPU::MOS6502Esque::Ready) && (v != CPU::MOS6502Esque::InternalOperationRead) && (v != CPU::MOS6502Esque::InternalOperationWrite)) + +/*! + A class providing empty implementations of the methods a 6502 uses to access the bus. To wire the 6502 to a bus, + machines should subclass BusHandler and then declare a realisation of the 6502 template, suplying their bus + handler. +*/ +template class BusHandler { + public: + using AddressType = addr_t; + + /*! + Announces that the 6502 has performed the cycle defined by operation, address and value. On the 6502, + all bus cycles take one clock cycle so the amoutn of time advanced is implicit. + + @param operation The type of bus cycle: read, read opcode (i.e. read, with sync active), + write or ready. + @param address The value of the address bus during this bus cycle. + @param value If this is a cycle that puts a value onto the data bus, *value is that value. If this is + a cycle that reads the bus, the bus handler should write a value to *value. Writing to *value during + a read cycle will produce undefined behaviour. + + @returns The number of cycles that passed in objective time while this 6502 bus cycle was ongoing. + On an archetypal machine this will be Cycles(1) but some architectures may choose not to clock the 6502 + during some periods; one way to simulate that is to have the bus handler return a number other than + Cycles(1) to describe lengthened bus cycles. + */ + Cycles perform_bus_operation([[maybe_unused]] BusOperation operation, [[maybe_unused]] addr_t address, [[maybe_unused]] uint8_t *value) { + return Cycles(1); + } + + /*! + Announces completion of all the cycles supplied to a .run_for request on the 6502. Intended to allow + bus handlers to perform any deferred output work. + */ + void flush() {} +}; + +} +} + +#endif /* m6502Esque_h */ diff --git a/Processors/6502Esque/6502Selector.hpp b/Processors/6502Esque/6502Selector.hpp new file mode 100644 index 000000000..d91932c17 --- /dev/null +++ b/Processors/6502Esque/6502Selector.hpp @@ -0,0 +1,46 @@ +// +// 6502Selector.hpp +// Clock Signal +// +// Created by Thomas Harte on 28/09/2020. +// Copyright © 2020 Thomas Harte. All rights reserved. +// + +#ifndef _502Selector_h +#define _502Selector_h + +#include "../6502/6502.hpp" +#include "../65816/65816.hpp" + +namespace CPU { +namespace MOS6502Esque { + +enum class Type { + TNES6502, // the NES's 6502, which is like a 6502 but lacks decimal mode (though it retains the decimal flag) + T6502, // the original [NMOS] 6502, replete with various undocumented instructions + TSynertek65C02, // a 6502 extended with BRA, P[H/L][X/Y], STZ, TRB, TSB and the (zp) addressing mode and a few other additions + TRockwell65C02, // like the Synertek, but with BBR, BBS, RMB and SMB + TWDC65C02, // like the Rockwell, but with STP and WAI + TWDC65816, // the slightly 16-bit follow-up to the 6502 +}; + +/* + Machines that can use either a 6502 or a 65816 can use CPU::MOS6502Esque::Processor in order to select the proper + class in much the same way that a raw user of CPU::MOS6502::Processor would set the personality. Just provide one + of the type enums as above as the appropriate template parameter. +*/ + +template class Processor: + public CPU::MOS6502::Processor { + using CPU::MOS6502::Processor::Processor; +}; + +template class Processor: + public CPU::WDC65816::Processor { + using CPU::WDC65816::Processor::Processor; +}; + +} +} + +#endif /* _502Selector_h */ diff --git a/Processors/6502Esque/Implementation/LazyFlags.hpp b/Processors/6502Esque/Implementation/LazyFlags.hpp new file mode 100644 index 000000000..756d7a779 --- /dev/null +++ b/Processors/6502Esque/Implementation/LazyFlags.hpp @@ -0,0 +1,83 @@ +// +// LazyFlags.hpp +// Clock Signal +// +// Created by Thomas Harte on 05/10/2020. +// Copyright © 2020 Thomas Harte. All rights reserved. +// + +#ifndef LazyFlags_h +#define LazyFlags_h + +#include "../6502Esque.hpp" + +namespace CPU { +namespace MOS6502Esque { + +struct LazyFlags { + /// Bit 7 is set if the negative flag is set; otherwise it is clear. + uint8_t negative_result = 0; + + /// Non-zero if the zero flag is clear, zero if it is set. + uint8_t zero_result = 0; + + /// Contains Flag::Carry. + uint8_t carry = 0; + + /// Contains Flag::Decimal. + uint8_t decimal = 0; + + /// Contains Flag::Overflow. + uint8_t overflow = 0; + + /// Contains Flag::Interrupt, complemented. + uint8_t inverse_interrupt = 0; + + /// Sets N and Z flags per the 8-bit value @c value. + void set_nz(uint8_t value) { + zero_result = negative_result = value; + } + + /// Sets N and Z flags per the 8- or 16-bit value @c value; @c shift should be 0 to indicate an 8-bit value or 8 to indicate a 16-bit value. + void set_nz(uint16_t value, int shift) { + negative_result = uint8_t(value >> shift); + zero_result = uint8_t(value | (value >> shift)); + } + + /// Sets the Z flag per the 8- or 16-bit value @c value; @c shift should be 0 to indicate an 8-bit value or 8 to indicate a 16-bit value. + void set_z(uint16_t value, int shift) { + zero_result = uint8_t(value | (value >> shift)); + } + + /// Sets the N flag per the 8- or 16-bit value @c value; @c shift should be 0 to indicate an 8-bit value or 8 to indicate a 16-bit value. + void set_n(uint16_t value, int shift) { + negative_result = uint8_t(value >> shift); + } + + void set(uint8_t flags) { + carry = flags & Flag::Carry; + negative_result = flags & Flag::Sign; + zero_result = (~flags) & Flag::Zero; + overflow = flags & Flag::Overflow; + inverse_interrupt = (~flags) & Flag::Interrupt; + decimal = flags & Flag::Decimal; + } + + uint8_t get() const { + return carry | overflow | (inverse_interrupt ^ Flag::Interrupt) | (negative_result & 0x80) | (zero_result ? 0 : Flag::Zero) | Flag::Always | decimal; + } + + LazyFlags() { + // Only the interrupt flag is defined upon reset but get_flags isn't going to + // mask the other flags so we need to do that, at least. + carry &= Flag::Carry; + decimal &= Flag::Decimal; + overflow &= Flag::Overflow; + } +}; + + +} +} + +#endif /* LazyFlags_h */ diff --git a/Processors/6502Esque/README.md b/Processors/6502Esque/README.md new file mode 100644 index 000000000..dfb38ccb4 --- /dev/null +++ b/Processors/6502Esque/README.md @@ -0,0 +1,5 @@ +# 6502Esque + +This folder contains common code for CPUs for a 6502-esque bus interface; it also contains a special template, the 6502Selector, which allows a consumer to select between the 6502-esque chips by enum. + +If you know exactly which processor you want, feel free to ignore this folder entirely; just go straight to the 6502, 65816 or whatever. diff --git a/Processors/65816/65816.hpp b/Processors/65816/65816.hpp new file mode 100644 index 000000000..503b65e44 --- /dev/null +++ b/Processors/65816/65816.hpp @@ -0,0 +1,90 @@ +// +// 65816.hpp +// Clock Signal +// +// Created by Thomas Harte on 23/09/2020. +// Copyright © 2020 Thomas Harte. All rights reserved. +// + +#ifndef WDC65816_hpp +#define WDC65816_hpp + +#include +#include +#include + +#include "../RegisterSizes.hpp" +#include "../../ClockReceiver/ClockReceiver.hpp" +#include "../6502Esque/6502Esque.hpp" +#include "../6502Esque/Implementation/LazyFlags.hpp" + +namespace CPU { +namespace WDC65816 { + +using BusOperation = CPU::MOS6502Esque::BusOperation; +using Register = CPU::MOS6502Esque::Register; +using Flag = CPU::MOS6502Esque::Flag; + +enum ExtendedBusOutput { + Emulation = (1 << 0), + MemorySize = (1 << 1), + IndexSize = (1 << 2), + MemoryLock = (1 << 3), +}; + +#include "Implementation/65816Storage.hpp" + +class ProcessorBase: protected ProcessorStorage { + public: + inline void set_power_on(bool); + inline void set_irq_line(bool); + inline void set_nmi_line(bool); + inline void set_reset_line(bool); + inline void set_abort_line(bool); + inline bool get_is_resetting() const; + + /*! + Returns the current state of all lines not ordinarily pushed to the BusHandler. + */ + inline int get_extended_bus_output(); + + /*! + Provided for symmetry with the 6502; a 65816 is never jammed. + */ + inline bool is_jammed() const; + + void set_value_of_register(Register r, uint16_t value); + uint16_t get_value_of_register(Register r) const; +}; + +template class Processor: public ProcessorBase { + public: + /*! + Constructs an instance of the 6502 that will use @c bus_handler for all bus communications. + */ + Processor(BusHandler &bus_handler) : bus_handler_(bus_handler) {} + + /*! + Runs the 6502 for a supplied number of cycles. + + @param cycles The number of cycles to run the 6502 for. + */ + void run_for(const Cycles cycles); + + /*! + Sets the current level of the RDY line. + + @param active @c true if the line is logically active; @c false otherwise. + */ + void set_ready_line(bool active); + + private: + BusHandler &bus_handler_; +}; + +#include "Implementation/65816Implementation.hpp" + +} +} + +#endif /* WDC65816_hpp */ diff --git a/Processors/65816/Implementation/65816Base.cpp b/Processors/65816/Implementation/65816Base.cpp new file mode 100644 index 000000000..cb7d5e1ce --- /dev/null +++ b/Processors/65816/Implementation/65816Base.cpp @@ -0,0 +1,44 @@ +// +// 65816Base.cpp +// Clock Signal +// +// Created by Thomas Harte on 28/09/2020. +// Copyright © 2020 Thomas Harte. All rights reserved. +// + +#include "../65816.hpp" + +using namespace CPU::WDC65816; + +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; + case Register::Flags: return get_flags(); + case Register::A: return registers_.a.full; + case Register::X: return registers_.x.full; + case Register::Y: return registers_.y.full; + case Register::EmulationFlag: return registers_.emulation_flag; + case Register::DataBank: return registers_.data_bank >> 16; + case Register::ProgramBank: return registers_.program_bank >> 16; + case Register::Direct: return registers_.direct; + default: return 0; + } +} + +void ProcessorBase::set_value_of_register(Register r, uint16_t value) { + switch (r) { + case Register::ProgramCounter: registers_.pc = value; break; + case Register::StackPointer: registers_.s.full = value; break; + case Register::Flags: set_flags(uint8_t(value)); break; + case Register::A: registers_.a.full = value; break; + case Register::X: registers_.x.full = value; break; + case Register::Y: registers_.y.full = value; break; + case Register::EmulationFlag: set_emulation_mode(value); break; + case Register::DataBank: registers_.data_bank = uint32_t(value & 0xff) << 16; break; + case Register::ProgramBank: registers_.program_bank = uint32_t(value &0xff) << 16; break; + case Register::Direct: registers_.direct = value; break; + default: break; + } +} diff --git a/Processors/65816/Implementation/65816Implementation.hpp b/Processors/65816/Implementation/65816Implementation.hpp new file mode 100644 index 000000000..39180736c --- /dev/null +++ b/Processors/65816/Implementation/65816Implementation.hpp @@ -0,0 +1,1016 @@ +// +// 65816Implementation.hpp +// Clock Signal +// +// Created by Thomas Harte on 27/09/2020. +// Copyright © 2020 Thomas Harte. All rights reserved. +// + +template void Processor::run_for(const Cycles cycles) { + +#define perform_bus(address, value, operation) \ + bus_address_ = address; \ + bus_value_ = value; \ + bus_operation_ = operation + +#define read(address, value) perform_bus(address, value, MOS6502Esque::Read) +#define write(address, value) perform_bus(address, value, MOS6502Esque::Write) + +#define m_flag() registers_.mx_flags[0] +#define x_flag() registers_.mx_flags[1] + +#define x() (registers_.x.full & registers_.x_masks[1]) +#define y() (registers_.y.full & registers_.x_masks[1]) +#define stack_address() ((registers_.s.full & registers_.e_masks[1]) | (0x0100 & registers_.e_masks[0])) + + Cycles number_of_cycles = cycles + cycles_left_to_run_; + while(number_of_cycles > Cycles(0)) { + // Wait for ready to be inactive before proceeding. + while(uses_ready_line && ready_line_ && number_of_cycles > Cycles(0)) { + number_of_cycles -= bus_handler_.perform_bus_operation(BusOperation::Ready, static_cast(bus_address_), &bus_throwaway_); + } + + // Process for as much time is left and/or until ready is signalled. + while((!uses_ready_line || !ready_line_) && number_of_cycles > Cycles(0)) { + const MicroOp operation = *next_op_; + ++next_op_; + +#ifndef NDEBUG + // As a sanity check. + bus_value_ = nullptr; +#endif + + switch(operation) { + + // + // Scheduling. + // + + case OperationMoveToNextProgram: { + // The exception program will determine the appropriate way to respond + // based on the pending exception if one exists; otherwise just do a + // standard fetch-decode-execute. + const size_t slot = size_t(selected_exceptions_ ? OperationSlot::Exception : OperationSlot::FetchDecodeExecute); + active_instruction_ = &instructions[slot]; + + next_op_ = µ_ops_[active_instruction_->program_offsets[0]]; + instruction_buffer_.clear(); + data_buffer_.clear(); + last_operation_pc_ = registers_.pc; + memory_lock_ = false; + } continue; + + case OperationDecode: { + active_instruction_ = &instructions[instruction_buffer_.value]; + + const auto size_flag = registers_.mx_flags[active_instruction_->size_field]; + next_op_ = µ_ops_[active_instruction_->program_offsets[size_flag]]; + instruction_buffer_.clear(); + } continue; + + // + // PC fetches. + // + + case CycleFetchOpcode: + perform_bus(registers_.pc | registers_.program_bank, instruction_buffer_.next_input(), MOS6502Esque::ReadOpcode); + ++registers_.pc; + break; + + case CycleFetchIncrementPC: + perform_bus(registers_.pc | registers_.program_bank, instruction_buffer_.next_input(), MOS6502Esque::ReadProgram); + ++registers_.pc; + break; + + case CycleFetchPC: + perform_bus(registers_.pc | registers_.program_bank, instruction_buffer_.next_input(), MOS6502Esque::ReadProgram); + break; + + case CycleFetchPCThrowaway: + perform_bus(registers_.pc | registers_.program_bank, &bus_throwaway_, MOS6502Esque::InternalOperationRead); + break; + + // + // Data fetches and stores. + // + +#define increment_data_address() data_address_ = (data_address_ & ~data_address_increment_mask_) + ((data_address_ + 1) & data_address_increment_mask_) +#define decrement_data_address() data_address_ = (data_address_ & ~data_address_increment_mask_) + ((data_address_ - 1) & data_address_increment_mask_) + + + case CycleFetchData: + read(data_address_, data_buffer_.next_input()); + break; + + case CycleFetchDataThrowaway: + perform_bus(data_address_, &bus_throwaway_, MOS6502Esque::InternalOperationRead); + break; + + case CycleFetchIncorrectDataAddress: + perform_bus(incorrect_data_address_, &bus_throwaway_, MOS6502Esque::InternalOperationRead); + break; + + case CycleFetchIncrementData: + read(data_address_, data_buffer_.next_input()); + increment_data_address(); + break; + + case CycleFetchVector: + perform_bus(data_address_, data_buffer_.next_input(), MOS6502Esque::ReadVector); + break; + + case CycleFetchIncrementVector: + perform_bus(data_address_, data_buffer_.next_input(), MOS6502Esque::ReadVector); + increment_data_address(); + break; + + case CycleStoreData: + write(data_address_, data_buffer_.next_output()); + break; + + case CycleStoreDataThrowaway: + perform_bus(data_address_, data_buffer_.preview_output(), MOS6502Esque::InternalOperationWrite); + break; + + case CycleStoreIncrementData: + write(data_address_, data_buffer_.next_output()); + increment_data_address(); + break; + + case CycleStoreDecrementData: + write(data_address_, data_buffer_.next_output_descending()); + decrement_data_address(); + break; + + case CycleFetchBlockX: + read(((instruction_buffer_.value & 0xff00) << 8) | x(), data_buffer_.any_byte()); + break; + + case CycleFetchBlockY: + perform_bus(((instruction_buffer_.value & 0xff00) << 8) | y(), &bus_throwaway_, MOS6502Esque::InternalOperationRead); + break; + + case CycleStoreBlockY: + write(((instruction_buffer_.value & 0xff00) << 8) | x(), data_buffer_.any_byte()); + break; + +#undef increment_data_address +#undef decrement_data_address + + // + // Stack accesses. + // + +#define stack_access(value, operation) \ + bus_address_ = stack_address(); \ + bus_value_ = value; \ + bus_operation_ = operation; + + case CyclePush: + stack_access(data_buffer_.next_output_descending(), MOS6502Esque::Write); + --registers_.s.full; + break; + + case CyclePullIfNotEmulation: + if(registers_.emulation_flag) { + continue; + } + [[fallthrough]]; + + case CyclePull: + ++registers_.s.full; + stack_access(data_buffer_.next_input(), MOS6502Esque::Read); + break; + + case CycleAccessStack: + stack_access(&bus_throwaway_, MOS6502Esque::InternalOperationRead); + break; + +#undef stack_access + + // + // Memory lock control. + // + + case OperationSetMemoryLock: + memory_lock_ = true; + continue; + + // + // STP and WAI. + // + + case CycleRepeatingNone: + if(pending_exceptions_ & required_exceptions_) { + continue; + } else { + --next_op_; + perform_bus(0xffffff, nullptr, (required_exceptions_ & IRQ) ? MOS6502Esque::Ready : MOS6502Esque::None); + } + break; + + // + // Data movement. + // + + case OperationCopyPCToData: + data_buffer_.size = 2; + data_buffer_.value = registers_.pc; + continue; + + case OperationCopyInstructionToData: + data_buffer_ = instruction_buffer_; + continue; + + case OperationCopyDataToInstruction: + instruction_buffer_ = data_buffer_; + data_buffer_.clear(); + continue; + + case OperationCopyAToData: + data_buffer_.value = registers_.a.full & registers_.m_masks[1]; + data_buffer_.size = 2 - m_flag(); + continue; + + case OperationCopyDataToA: + registers_.a.full = (registers_.a.full & registers_.m_masks[0]) + (data_buffer_.value & registers_.m_masks[1]); + continue; + + case OperationCopyPBRToData: + data_buffer_.size = 1; + data_buffer_.value = registers_.program_bank >> 16; + continue; + + case OperationCopyDataToPC: + registers_.pc = uint16_t(data_buffer_.value); + continue; + + // + // Address construction. + // + + case OperationConstructAbsolute: + data_address_ = instruction_buffer_.value + registers_.data_bank; + data_address_increment_mask_ = 0xff'ff'ff; + continue; + + case OperationConstructAbsolute16: + data_address_ = instruction_buffer_.value; + data_address_increment_mask_ = 0x00'ff'ff; + continue; + + case OperationConstructAbsoluteLong: + data_address_ = instruction_buffer_.value; + data_address_increment_mask_ = 0xff'ff'ff; + continue; + + // Used for JMP and JSR (absolute, x). + case OperationConstructAbsoluteIndexedIndirect: + data_address_ = registers_.program_bank + ((instruction_buffer_.value + x()) & 0xffff); + data_address_increment_mask_ = 0x00'ff'ff; + continue; + + case OperationConstructAbsoluteLongX: + data_address_ = instruction_buffer_.value + x(); + data_address_increment_mask_ = 0xff'ff'ff; + continue; + + case OperationConstructAbsoluteXRead: + case OperationConstructAbsoluteX: + data_address_ = instruction_buffer_.value + x() + registers_.data_bank; + incorrect_data_address_ = (data_address_ & 0xff) | (instruction_buffer_.value & 0xff00) + registers_.data_bank; + + // If the incorrect address isn't actually incorrect, skip its usage. + if(operation == OperationConstructAbsoluteXRead && data_address_ == incorrect_data_address_) { + ++next_op_; + } + data_address_increment_mask_ = 0xff'ff'ff; + continue; + + case OperationConstructAbsoluteYRead: + case OperationConstructAbsoluteY: + data_address_ = instruction_buffer_.value + y() + registers_.data_bank; + incorrect_data_address_ = (data_address_ & 0xff) + (instruction_buffer_.value & 0xff00) + registers_.data_bank; + + // If the incorrect address isn't actually incorrect, skip its usage. + if(operation == OperationConstructAbsoluteYRead && data_address_ == incorrect_data_address_) { + ++next_op_; + } + data_address_increment_mask_ = 0xff'ff'ff; + continue; + + case OperationConstructDirect: + data_address_ = (registers_.direct + instruction_buffer_.value) & 0xffff; + data_address_increment_mask_ = 0x00'ff'ff; + if(!(registers_.direct&0xff)) { + // If the low byte is 0 and this is emulation mode, incrementing + // is restricted to the low byte. + data_address_increment_mask_ = registers_.e_masks[1]; + ++next_op_; + } + continue; + + case OperationConstructDirectLong: + data_address_ = (registers_.direct + instruction_buffer_.value) & 0xffff; + data_address_increment_mask_ = 0x00'ff'ff; + if(!(registers_.direct&0xff)) { + ++next_op_; + } + continue; + + case OperationConstructDirectIndirect: + data_address_ = registers_.data_bank + data_buffer_.value; + data_address_increment_mask_ = 0xff'ff'ff; + data_buffer_.clear(); + continue; + + case OperationConstructDirectIndexedIndirect: + data_address_ = registers_.data_bank + ( + ((registers_.direct + x() + instruction_buffer_.value) & registers_.e_masks[1]) + + (registers_.direct & registers_.e_masks[0]) + ) & 0xffff; + data_address_increment_mask_ = 0x00'ff'ff; + + if(!(registers_.direct&0xff)) { + ++next_op_; + } + continue; + + case OperationConstructDirectIndirectIndexedLong: + data_address_ = y() + data_buffer_.value; + data_address_increment_mask_ = 0xff'ff'ff; + data_buffer_.clear(); + continue; + + case OperationConstructDirectIndirectLong: + data_address_ = data_buffer_.value; + data_address_increment_mask_ = 0xff'ff'ff; + data_buffer_.clear(); + continue; + + // TODO: confirm incorrect_data_address_ below. + + case OperationConstructDirectX: + data_address_ = ( + (registers_.direct & registers_.e_masks[0]) + + ((instruction_buffer_.value + registers_.direct + x()) & registers_.e_masks[1]) + ) & 0xffff; + data_address_increment_mask_ = 0x00'ff'ff; + + incorrect_data_address_ = (registers_.direct & 0xff00) + (data_address_ & 0x00ff); + if(!(registers_.direct&0xff)) { + ++next_op_; + } + continue; + + case OperationConstructDirectY: + data_address_ = ( + (registers_.direct & registers_.e_masks[0]) + + ((instruction_buffer_.value + registers_.direct + y()) & registers_.e_masks[1]) + ) & 0xffff; + data_address_increment_mask_ = 0x00'ff'ff; + + incorrect_data_address_ = (registers_.direct & 0xff00) + (data_address_ & 0x00ff); + if(!(registers_.direct&0xff)) { + ++next_op_; + } + continue; + + case OperationConstructStackRelative: + data_address_ = (registers_.s.full + instruction_buffer_.value) & 0xffff; + data_address_increment_mask_ = 0x00'ff'ff; + continue; + + case OperationConstructStackRelativeIndexedIndirect: + data_address_ = registers_.data_bank + data_buffer_.value + y(); + data_address_increment_mask_ = 0xff'ff'ff; + data_buffer_.clear(); + continue; + + case OperationConstructPER: + data_buffer_.value = instruction_buffer_.value + registers_.pc; + data_buffer_.size = 2; + continue; + + case OperationPrepareException: { + // Put the proper exception vector into the data address, put the flags and PC + // into the data buffer (possibly also PBR), and skip an instruction if in + // emulation mode. + // + // I've assumed here that interrupts, BRKs and COPs can be usurped similarly + // to a 6502 but may not have the exact details correct. E.g. if IRQ has + // become inactive since the decision was made to start an interrupt, should + // that turn into a BRK? + // + // Also: priority here is a guess. + + bool is_brk = false; + + if(pending_exceptions_ & (Reset | PowerOn)) { + pending_exceptions_ &= ~(Reset | PowerOn); + data_address_ = 0xfffc; + set_reset_state(); + } else if(pending_exceptions_ & NMI) { + pending_exceptions_ &= ~NMI; + data_address_ = registers_.emulation_flag ? 0xfffa : 0xffea; + } else if(pending_exceptions_ & IRQ & registers_.flags.inverse_interrupt) { + pending_exceptions_ &= ~IRQ; + data_address_ = registers_.emulation_flag ? 0xfffe : 0xffee; + } else if(pending_exceptions_ & Abort) { + // Special case: restore registers from start of instruction. + registers_ = abort_registers_copy_; + + pending_exceptions_ &= ~Abort; + data_address_ = registers_.emulation_flag ? 0xfff8 : 0xffe8;; + } else { + is_brk = active_instruction_ == instructions; // Given that BRK has opcode 00. + if(is_brk) { + data_address_ = registers_.emulation_flag ? 0xfffe : 0xffe6; + } else { + // Implicitly: COP. + data_address_ = registers_.emulation_flag ? 0xfff4 : 0xffe8; + } + } + + data_buffer_.value = uint32_t((registers_.pc << 8) | get_flags()); + if(registers_.emulation_flag) { + if(is_brk) data_buffer_.value |= Flag::Break; + data_buffer_.size = 3; + ++next_op_; + } else { + data_buffer_.value |= registers_.program_bank << 24; + data_buffer_.size = 4; + registers_.program_bank = 0; + } + + registers_.flags.inverse_interrupt = 0; + } continue; + + // + // Performance. + // + +#define LD(dest, src, masks) dest.full = (dest.full & masks[0]) | (src & masks[1]) +#define m_top() (instruction_buffer_.value >> registers_.m_shift) & 0xff +#define x_top() (registers_.x.full >> registers_.x_shift) & 0xff +#define y_top() (registers_.y.full >> registers_.x_shift) & 0xff +#define a_top() (registers_.a.full >> registers_.m_shift) & 0xff + + case OperationPerform: + switch(active_instruction_->operation) { + + // + // Loads, stores and transfers (and NOP, and XBA). + // + + case LDA: + LD(registers_.a, data_buffer_.value, registers_.m_masks); + registers_.flags.set_nz(registers_.a.full, registers_.m_shift); + break; + + case LDX: + LD(registers_.x, data_buffer_.value, registers_.x_masks); + registers_.flags.set_nz(registers_.x.full, registers_.x_shift); + break; + + case LDY: + LD(registers_.y, data_buffer_.value, registers_.x_masks); + registers_.flags.set_nz(registers_.y.full, registers_.x_shift); + break; + + case PLB: + registers_.data_bank = (data_buffer_.value & 0xff) << 16; + registers_.flags.set_nz(uint8_t(data_buffer_.value)); + break; + + case PLD: + registers_.direct = uint16_t(data_buffer_.value); + registers_.flags.set_nz(uint16_t(data_buffer_.value), 8); + break; + + case PLP: + set_flags(uint8_t(data_buffer_.value)); + break; + + case STA: + data_buffer_.value = registers_.a.full & registers_.m_masks[1]; + data_buffer_.size = 2 - m_flag(); + break; + + case STZ: + data_buffer_.value = 0; + data_buffer_.size = 2 - m_flag(); + break; + + case STX: + data_buffer_.value = registers_.x.full & registers_.x_masks[1]; + data_buffer_.size = 2 - x_flag(); + break; + + case STY: + data_buffer_.value = registers_.y.full & registers_.x_masks[1]; + data_buffer_.size = 2 - m_flag(); + break; + + case PHB: + data_buffer_.value = registers_.data_bank >> 16; + data_buffer_.size = 1; + break; + + case PHK: + data_buffer_.value = registers_.program_bank >> 16; + data_buffer_.size = 1; + break; + + case PHD: + data_buffer_.value = registers_.direct; + data_buffer_.size = 2; + break; + + case PHP: + data_buffer_.value = get_flags(); + data_buffer_.size = 1; + + if(registers_.emulation_flag) { + // On the 6502, the break flag is set during a PHP. + data_buffer_.value |= Flag::Break; + } + break; + + case NOP: break; + + // The below attempt to obey the 8/16-bit mixed transfer rules + // as documented in https://softpixel.com/~cwright/sianse/docs/65816NFO.HTM + // (and make reasonable guesses as to the N flag). + + case TXS: + registers_.s = registers_.x.full & registers_.x_masks[1]; + break; + + case TSX: + LD(registers_.x, registers_.s.full, registers_.x_masks); + registers_.flags.set_nz(registers_.x.full, registers_.x_shift); + break; + + case TXY: + LD(registers_.y, registers_.x.full, registers_.x_masks); + registers_.flags.set_nz(registers_.y.full, registers_.x_shift); + break; + + case TYX: + LD(registers_.x, registers_.y.full, registers_.x_masks); + registers_.flags.set_nz(registers_.x.full, registers_.x_shift); + break; + + case TAX: + LD(registers_.x, registers_.a.full, registers_.x_masks); + registers_.flags.set_nz(registers_.x.full, registers_.x_shift); + break; + + case TAY: + LD(registers_.y, registers_.a.full, registers_.x_masks); + registers_.flags.set_nz(registers_.y.full, registers_.x_shift); + break; + + case TXA: + LD(registers_.a, registers_.x.full, registers_.m_masks); + registers_.flags.set_nz(registers_.a.full, registers_.m_shift); + break; + + case TYA: + LD(registers_.a, registers_.y.full, registers_.m_masks); + registers_.flags.set_nz(registers_.a.full, registers_.m_shift); + break; + + case TCD: + registers_.direct = registers_.a.full; + registers_.flags.set_nz(registers_.a.full, 8); + break; + + case TDC: + registers_.a.full = registers_.direct; + registers_.flags.set_nz(registers_.a.full, 8); + break; + + case TCS: + registers_.s.full = registers_.a.full; + // No need to worry about byte masking here; for the stack it's handled as the emulation runs. + break; + + case TSC: + registers_.a.full = stack_address(); + registers_.flags.set_nz(registers_.a.full, 8); + break; + + case XBA: { + const uint8_t a_low = registers_.a.halves.low; + registers_.a.halves.low = registers_.a.halves.high; + registers_.a.halves.high = a_low; + registers_.flags.set_nz(registers_.a.halves.low); + } break; + + // + // Jumps and returns. + // + + case JML: + registers_.program_bank = instruction_buffer_.value & 0xff0000; + [[fallthrough]]; + + case JMP: + registers_.pc = uint16_t(instruction_buffer_.value); + break; + + case JMPind: + registers_.pc = uint16_t(data_buffer_.value); + break; + + case RTS: + registers_.pc = uint16_t(data_buffer_.value + 1); + break; + + case JSL: + registers_.program_bank = instruction_buffer_.value & 0xff0000; + [[fallthrough]]; + + case JSR: + data_buffer_.value = registers_.pc; + data_buffer_.size = 2; + + registers_.pc = uint16_t(instruction_buffer_.value); + break; + + case RTI: + registers_.pc = uint16_t(data_buffer_.value >> 8); + set_flags(uint8_t(data_buffer_.value)); + + if(!registers_.emulation_flag) { + registers_.program_bank = (data_buffer_.value & 0xff000000) >> 8; + } + break; + + // + // Block moves. + // + + case MVP: + registers_.data_bank = (instruction_buffer_.value & 0xff) << 16; + --registers_.x.full; + --registers_.y.full; + --registers_.a.full; + if(registers_.a.full) registers_.pc -= 3; + break; + + case MVN: + registers_.data_bank = (instruction_buffer_.value & 0xff) << 16; + ++registers_.x.full; + ++registers_.y.full; + --registers_.a.full; + if(registers_.a.full) registers_.pc -= 3; + break; + + // + // Flag manipulation. + // + + case CLC: registers_.flags.carry = 0; break; + case CLI: registers_.flags.inverse_interrupt = Flag::Interrupt; break; + case CLV: registers_.flags.overflow = 0; break; + case CLD: registers_.flags.decimal = 0; break; + + case SEC: registers_.flags.carry = Flag::Carry; break; + case SEI: registers_.flags.inverse_interrupt = 0; break; + case SED: registers_.flags.decimal = Flag::Decimal; break; + + case REP: + set_flags(uint8_t(get_flags() &~ instruction_buffer_.value)); + break; + + case SEP: + set_flags(uint8_t(get_flags() | instruction_buffer_.value)); + break; + + case XCE: { + const bool old_emulation_flag = registers_.emulation_flag; + set_emulation_mode(registers_.flags.carry); + registers_.flags.carry = old_emulation_flag; + } break; + + // + // Increments and decrements. + // + + case INC: + ++data_buffer_.value; + registers_.flags.set_nz(uint16_t(data_buffer_.value), registers_.m_shift); + break;; + + case DEC: + --data_buffer_.value; + registers_.flags.set_nz(uint16_t(data_buffer_.value), registers_.m_shift); + break; + + case INX: { + const uint16_t x_inc = registers_.x.full + 1; + LD(registers_.x, x_inc, registers_.x_masks); + registers_.flags.set_nz(registers_.x.full, registers_.x_shift); + } break; + + case DEX: { + const uint16_t x_dec = registers_.x.full - 1; + LD(registers_.x, x_dec, registers_.x_masks); + registers_.flags.set_nz(registers_.x.full, registers_.x_shift); + } break; + + case INY: { + const uint16_t y_inc = registers_.y.full + 1; + LD(registers_.y, y_inc, registers_.x_masks); + registers_.flags.set_nz(registers_.y.full, registers_.x_shift); + } break; + + case DEY: { + const uint16_t y_dec = registers_.y.full - 1; + LD(registers_.y, y_dec, registers_.x_masks); + registers_.flags.set_nz(registers_.y.full, registers_.x_shift); + } break; + + // + // Bitwise operations. + // + + case AND: + registers_.a.full &= data_buffer_.value | registers_.m_masks[0]; + registers_.flags.set_nz(registers_.a.full, registers_.m_shift); + break; + + case EOR: + registers_.a.full ^= data_buffer_.value; + registers_.flags.set_nz(registers_.a.full, registers_.m_shift); + break; + + case ORA: + registers_.a.full |= data_buffer_.value; + registers_.flags.set_nz(registers_.a.full, registers_.m_shift); + break; + + case BIT: + registers_.flags.set_n(uint16_t(data_buffer_.value), registers_.m_shift); + registers_.flags.set_z(uint16_t(data_buffer_.value & registers_.a.full), registers_.m_shift); + registers_.flags.overflow = data_buffer_.value & Flag::Overflow; + break; + + case BITimm: + registers_.flags.set_z(data_buffer_.value & registers_.a.full, registers_.m_shift); + break; + + case TRB: + registers_.flags.set_z(data_buffer_.value & registers_.a.full, registers_.m_shift); + data_buffer_.value &= ~registers_.a.full; + break; + + case TSB: + registers_.flags.set_z(data_buffer_.value & registers_.a.full, registers_.m_shift); + data_buffer_.value |= registers_.a.full; + break; + + // + // Branches. + // + +#define BRA(condition) \ + if(!(condition)) { \ + next_op_ += 3; \ + } else { \ + data_buffer_.size = 2; \ + data_buffer_.value = uint32_t(registers_.pc + int8_t(instruction_buffer_.value)); \ + \ + if((registers_.pc & 0xff00) == (instruction_buffer_.value & 0xff00)) { \ + ++next_op_; \ + } \ + } + + case BPL: BRA(!(registers_.flags.negative_result&0x80)); break; + case BMI: BRA(registers_.flags.negative_result&0x80); break; + case BVC: BRA(!registers_.flags.overflow); break; + case BVS: BRA(registers_.flags.overflow); break; + case BCC: BRA(!registers_.flags.carry); break; + case BCS: BRA(registers_.flags.carry); break; + case BNE: BRA(registers_.flags.zero_result); break; + case BEQ: BRA(!registers_.flags.zero_result); break; + case BRA: BRA(true); break; + +#undef BRA + + case BRL: + registers_.pc += int16_t(instruction_buffer_.value); + break; + + // + // Shifts and rolls. + // + + case ASL: + registers_.flags.carry = uint8_t(data_buffer_.value >> (7 + registers_.m_shift)); + data_buffer_.value <<= 1; + registers_.flags.set_nz(uint16_t(data_buffer_.value), registers_.m_shift); + break; + + case LSR: + registers_.flags.carry = uint8_t(data_buffer_.value & 1); + data_buffer_.value >>= 1; + registers_.flags.set_nz(uint16_t(data_buffer_.value), registers_.m_shift); + break; + + case ROL: + data_buffer_.value = (data_buffer_.value << 1) | registers_.flags.carry; + registers_.flags.carry = uint8_t(data_buffer_.value >> (8 + registers_.m_shift)); + registers_.flags.set_nz(uint16_t(data_buffer_.value), registers_.m_shift); + break; + + case ROR: { + const uint8_t next_carry = data_buffer_.value & 1; + data_buffer_.value = (data_buffer_.value >> 1) | (uint32_t(registers_.flags.carry) << (7 + registers_.m_shift)); + registers_.flags.carry = next_carry; + registers_.flags.set_nz(uint16_t(data_buffer_.value), registers_.m_shift); + } break; + + // + // Arithmetic. + // + +#define cp(v, shift, masks) {\ + const uint32_t temp32 = (v.full & masks[1]) - (data_buffer_.value & masks[1]); \ + registers_.flags.set_nz(uint16_t(temp32), shift); \ + registers_.flags.carry = ((~temp32) >> (8 + shift))&1; \ +} + + case CMP: cp(registers_.a, registers_.m_shift, registers_.m_masks); break; + case CPX: cp(registers_.x, registers_.x_shift, registers_.x_masks); break; + case CPY: cp(registers_.y, registers_.x_shift, registers_.x_masks); break; + +#undef cp + + case SBC: + if(registers_.flags.decimal) { + // I've yet to manage to find a rational way to map this to an ADC, + // hence the yucky repetition of code here. + const uint16_t a = registers_.a.full & registers_.m_masks[1]; + unsigned int result = 0; + unsigned int borrow = registers_.flags.carry ^ 1; + +#define nibble(mask, adjustment, carry) \ + result += (a & mask) - (data_buffer_.value & mask) - borrow; \ + if(result > mask) result -= adjustment; \ + borrow = (result > mask) ? carry : 0; \ + result &= (carry - 1); + + nibble(0x000f, 0x0006, 0x00010); + nibble(0x00f0, 0x0060, 0x00100); + nibble(0x0f00, 0x0600, 0x01000); + nibble(0xf000, 0x6000, 0x10000); + +#undef nibble + + registers_.flags.overflow = ~(( (result ^ registers_.a.full) & (result ^ 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; + LD(registers_.a, result, registers_.m_masks); + + break; + } + + data_buffer_.value = ~data_buffer_.value & registers_.m_masks[1]; + [[fallthrough]]; + + case ADC: { + int result; + const uint16_t a = registers_.a.full & registers_.m_masks[1]; + + if(registers_.flags.decimal) { + result = registers_.flags.carry; + +#define nibble(mask, limit, adjustment, carry) \ + result += (a & mask) + (data_buffer_.value & mask); \ + if(result >= limit) result = ((result + (adjustment)) & (carry - 1)) + carry; + + nibble(0x000f, 0x000a, 0x0006, 0x00010); + nibble(0x00f0, 0x00a0, 0x0060, 0x00100); + nibble(0x0f00, 0x0a00, 0x0600, 0x01000); + nibble(0xf000, 0xa000, 0x6000, 0x10000); + +#undef nibble + + } 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; + registers_.flags.set_nz(uint16_t(result), registers_.m_shift); + registers_.flags.carry = (result >> (8 + registers_.m_shift))&1; + LD(registers_.a, result, registers_.m_masks); + } break; + + // + // STP and WAI + // + + case STP: + required_exceptions_ = Reset; + break; + + case WAI: + required_exceptions_ = Reset | IRQ | NMI; + break; + } + continue; + } + +#undef LD +#undef m_top +#undef x_top +#undef y_top +#undef a_top + + // Store a selection as to the exceptions, if any, that would be honoured after this cycle if the + // next thing is a MoveToNextProgram. + selected_exceptions_ = pending_exceptions_ & (registers_.flags.inverse_interrupt | PowerOn | Reset | NMI); + number_of_cycles -= bus_handler_.perform_bus_operation(bus_operation_, static_cast(bus_address_), bus_value_); + } + } + +#undef read +#undef write +#undef bus_operation +#undef x +#undef y +#undef m_flag +#undef x_flag +#undef stack_address + + cycles_left_to_run_ = number_of_cycles; + bus_handler_.flush(); +} + +void ProcessorBase::set_power_on(bool active) { + if(active) { + pending_exceptions_ |= PowerOn; + } else { + pending_exceptions_ &= ~PowerOn; + selected_exceptions_ &= ~PowerOn; + } +} + +void ProcessorBase::set_irq_line(bool active) { + if(active) { + pending_exceptions_ |= IRQ; + } else { + pending_exceptions_ &= ~IRQ; + } +} + +void ProcessorBase::set_reset_line(bool active) { + if(active) { + pending_exceptions_ |= Reset; + } else { + pending_exceptions_ &= ~Reset; + } +} + +void ProcessorBase::set_nmi_line(bool active) { + // This is edge triggered. + if(active) { + pending_exceptions_ |= NMI; + } +} + +void ProcessorBase::set_abort_line(bool active) { + // Take a copy of register state now to restore at the beginning of the exception + // if abort has gone active, preparing to regress the program counter. + if(active) { + pending_exceptions_ |= Abort; + abort_registers_copy_ = registers_; + abort_registers_copy_.pc = last_operation_pc_; + } else { + pending_exceptions_ &= ~Abort; + } +} + +template void Processor::set_ready_line(bool active) { + assert(uses_ready_line); + ready_line_ = active; +} + +// The 65816 can't jam. +bool ProcessorBase::is_jammed() const { return false; } + +bool ProcessorBase::get_is_resetting() const { + return pending_exceptions_ & (Reset | PowerOn); +} + +int ProcessorBase::get_extended_bus_output() { + return + (memory_lock_ ? ExtendedBusOutput::MemoryLock : 0) | + (registers_.mx_flags[0] ? ExtendedBusOutput::MemorySize : 0) | + (registers_.mx_flags[1] ? ExtendedBusOutput::IndexSize : 0) | + (registers_.emulation_flag ? ExtendedBusOutput::Emulation : 0); +} diff --git a/Processors/65816/Implementation/65816Storage.cpp b/Processors/65816/Implementation/65816Storage.cpp new file mode 100644 index 000000000..f254806f9 --- /dev/null +++ b/Processors/65816/Implementation/65816Storage.cpp @@ -0,0 +1,1100 @@ +// +// 65816Storage.cpp +// Clock Signal +// +// Created by Thomas Harte on 23/09/2020. +// Copyright © 2020 Thomas Harte. All rights reserved. +// + +#include "../65816.hpp" + +#include +#include +#include + +using namespace CPU::WDC65816; + +struct CPU::WDC65816::ProcessorStorageConstructor { + // Establish that a storage constructor needs access to ProcessorStorage. + ProcessorStorage &storage_; + ProcessorStorageConstructor(ProcessorStorage &storage) : storage_(storage) {} + + enum class AccessType { + Read, Write + }; + + constexpr static AccessType access_type_for_operation(Operation operation) { + switch(operation) { + case ADC: case AND: case BIT: case CMP: + case CPX: case CPY: case EOR: case ORA: + case SBC: + + case LDA: case LDX: case LDY: + + // The access type for everything else is arbitrary; they're + // not relevantly either read or write. + default: + return AccessType::Read; + + case STA: case STX: case STY: case STZ: + return AccessType::Write; + } + } + + typedef void (* Generator)(AccessType, bool, const std::function&); + using GeneratorKey = std::tuple; + using PatternTable = std::map>; + PatternTable installed_patterns; + + int opcode = 0; + enum class AccessMode { + Mixed, + Always8Bit, + Always16Bit + }; + + void install(Generator generator, Operation operation, AccessMode access_mode = AccessMode::Mixed) { + // Determine the access type implied by this operation. + const AccessType access_type = access_type_for_operation(operation); + + // Install the bus pattern. + const auto map_entry = install(generator, access_type); + const size_t micro_op_location_8 = map_entry->second.first; + const size_t micro_op_location_16 = map_entry->second.second; + + // Fill in the proper table entries and increment the opcode pointer. + storage_.instructions[opcode].program_offsets[0] = (access_mode == AccessMode::Always8Bit) ? uint16_t(micro_op_location_8) : uint16_t(micro_op_location_16); + storage_.instructions[opcode].program_offsets[1] = (access_mode == AccessMode::Always16Bit) ? uint16_t(micro_op_location_16) : uint16_t(micro_op_location_8); + storage_.instructions[opcode].operation = operation; + + ++opcode; + } + + void set_exception_generator(Generator generator) { + const auto map_entry = install(generator); + storage_.instructions[size_t(ProcessorStorage::OperationSlot::Exception)].program_offsets[0] = + storage_.instructions[size_t(ProcessorStorage::OperationSlot::Exception)].program_offsets[1] = uint16_t(map_entry->second.first); + storage_.instructions[size_t(ProcessorStorage::OperationSlot::Exception)].operation = JMPind; + } + + void install_fetch_decode_execute() { + storage_.instructions[size_t(ProcessorStorage::OperationSlot::FetchDecodeExecute)].program_offsets[0] = + storage_.instructions[size_t(ProcessorStorage::OperationSlot::FetchDecodeExecute)].program_offsets[1] = uint16_t(storage_.micro_ops_.size()); + storage_.micro_ops_.push_back(CycleFetchOpcode); + storage_.micro_ops_.push_back(OperationDecode); + } + + private: + + PatternTable::iterator install(Generator generator, AccessType access_type = AccessType::Read) { + // Check whether this access type + addressing mode generator has already been generated. + const auto key = std::make_pair(access_type, generator); + const auto map_entry = installed_patterns.find(key); + + // If it wasn't found, generate it now in both 8- and 16-bit variants. + // Otherwise, get the location of the existing entries. + if(map_entry != installed_patterns.end()) { + return map_entry; + } + + // Generate 8-bit steps. + const size_t micro_op_location_8 = storage_.micro_ops_.size(); + (*generator)(access_type, true, [this] (MicroOp op) { + this->storage_.micro_ops_.push_back(op); + }); + storage_.micro_ops_.push_back(OperationMoveToNextProgram); + + // Generate 16-bit steps. + size_t micro_op_location_16 = storage_.micro_ops_.size(); + (*generator)(access_type, false, [this] (MicroOp op) { + this->storage_.micro_ops_.push_back(op); + }); + storage_.micro_ops_.push_back(OperationMoveToNextProgram); + + // Minor optimisation: elide the steps if 8- and 16-bit steps are equal. + bool are_equal = true; + size_t c = 0; + while(true) { + if(storage_.micro_ops_[micro_op_location_8 + c] != storage_.micro_ops_[micro_op_location_16 + c]) { + are_equal = false; + break; + } + if(storage_.micro_ops_[micro_op_location_8 + c] == OperationMoveToNextProgram) break; + ++c; + } + + if(are_equal) { + storage_.micro_ops_.resize(micro_op_location_16); + micro_op_location_16 = micro_op_location_8; + } + + // Insert into the map. + auto [iterator, _] = installed_patterns.insert(std::make_pair(key, std::make_pair(micro_op_location_8, micro_op_location_16))); + return iterator; + } + + public: + + /* + Code below is structured to ease translation from Table 5-7 of the 2018 + edition of the WDC 65816 datasheet. + + In each case the relevant addressing mode is described here via a generator + function that will spit out the correct MicroOps based on access type + (i.e. read, write or read-modify-write) and data size (8- or 16-bit). + + That leads up to being able to declare the opcode map by addressing mode + and operation alone. + + Things the generators can assume before they start: + + 1) the opcode has already been fetched and decoded, and the program counter incremented; + 2) the data buffer is empty; and + 3) the data address is undefined. + */ + + // Performs the closing 8- or 16-bit read or write common to many modes below. + static void read_write(AccessType type, bool is8bit, const std::function &target) { + if(type == AccessType::Write) { + target(OperationPerform); // Perform operation to fill the data buffer. + if(!is8bit) target(CycleStoreIncrementData); // Data low. + target(CycleStoreData); // Data [high]. + } else { + if(!is8bit) target(CycleFetchIncrementData); // Data low. + target(CycleFetchData); // Data [high]. + target(OperationPerform); // Perform operation from the data buffer. + } + } + + static void read_modify_write(bool is8bit, const std::function &target) { + target(OperationSetMemoryLock); // Set the memory lock output until the end of this instruction. + + 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(OperationPerform); // Perform operation within the data buffer. + + if(!is8bit) target(CycleStoreDecrementData); // Data high. + target(CycleStoreData); // Data [low]. + } + + // 1a. Absolute; a. + static void absolute(AccessType type, bool is8bit, const std::function &target) { + target(CycleFetchIncrementPC); // AAL. + target(CycleFetchIncrementPC); // AAH. + target(OperationConstructAbsolute); // Calculate data address. + + read_write(type, is8bit, target); + } + + // 1b. Absolute; a, JMP. + static void absolute_jmp(AccessType, bool, const std::function &target) { + target(CycleFetchIncrementPC); // New PCL.] + target(CycleFetchPC); // New PCH. + target(OperationPerform); // [JMP] + } + + // 1c. Absolute; a, JSR. + static void absolute_jsr(AccessType, bool, const std::function &target) { + target(CycleFetchIncrementPC); // New PCL. + target(CycleFetchPC); // New PCH. + target(CycleFetchPCThrowaway); // IO + target(OperationPerform); // [JSR] + target(CyclePush); // PCH + target(CyclePush); // PCL + } + + // 1d. Absolute; a, read-modify-write. + static void absolute_rmw(AccessType, bool is8bit, const std::function &target) { + target(CycleFetchIncrementPC); // AAL. + target(CycleFetchIncrementPC); // AAH. + target(OperationConstructAbsolute); // Calculate data address. + + read_modify_write(is8bit, target); + } + + // 2a. Absolute Indexed Indirect; (a, x), JMP. + static void absolute_indexed_indirect_jmp(AccessType, bool, const std::function &target) { + target(CycleFetchIncrementPC); // AAL. + target(CycleFetchPC); // AAH. + target(CycleFetchPCThrowaway); // IO. + target(OperationConstructAbsoluteIndexedIndirect); // Calculate data address. + target(CycleFetchIncrementData); // New PCL + target(CycleFetchData); // New PCH. + target(OperationPerform); // [JMP] + } + + // 2b. Absolute Indexed Indirect; (a, x), JSR. + static void absolute_indexed_indirect_jsr(AccessType, bool, const std::function &target) { + target(CycleFetchIncrementPC); // AAL. + + target(OperationCopyPCToData); // Prepare to push. + target(CyclePush); // PCH + target(CyclePush); // PCL + + target(CycleFetchPC); // AAH. + target(CycleFetchPCThrowaway); // IO. + + target(OperationConstructAbsoluteIndexedIndirect); // Calculate data address. + target(CycleFetchIncrementData); // New PCL + target(CycleFetchData); // New PCH. + target(OperationPerform); // ['JSR' (actually: JMPind will do)] + } + + // 3a. Absolute Indirect; (a), JML. + static void absolute_indirect_jml(AccessType, bool, const std::function &target) { + target(CycleFetchIncrementPC); // New AAL. + target(CycleFetchPC); // New AAH. + + target(OperationConstructAbsolute16); // Calculate data address. + target(CycleFetchIncrementData); // New PCL + target(CycleFetchIncrementData); // New PCH + target(CycleFetchData); // New PBR + + target(OperationPerform); // [JML] + } + + // 3b. Absolute Indirect; (a), JMP. + static void absolute_indirect_jmp(AccessType, bool, const std::function &target) { + target(CycleFetchIncrementPC); // New AAL. + target(CycleFetchPC); // New AAH. + + target(OperationConstructAbsolute16); // Calculate data address. + target(CycleFetchIncrementData); // New PCL + target(CycleFetchData); // New PCH + + target(OperationPerform); // [JMP] + } + + // 4a. Absolute long; al. + static void absolute_long(AccessType type, bool is8bit, const std::function &target) { + target(CycleFetchIncrementPC); // AAL. + target(CycleFetchIncrementPC); // AAH. + target(CycleFetchIncrementPC); // AAB. + + target(OperationConstructAbsoluteLong); // Calculate data address. + + read_write(type, is8bit, target); + } + + // 4b. Absolute long; al, JMP. + static void absolute_long_jmp(AccessType, bool, const std::function &target) { + target(CycleFetchIncrementPC); // New PCL. + target(CycleFetchIncrementPC); // New PCH. + target(CycleFetchPC); // New PBR. + + target(OperationPerform); // ['JMP' (though it's JML in internal terms)] + } + + // 4c. Absolute long al, JSL. + static void absolute_long_jsl(AccessType, bool, const std::function &target) { + target(CycleFetchIncrementPC); // New PCL. + target(CycleFetchIncrementPC); // New PCH. + + target(OperationCopyPBRToData); // Copy PBR to the data register. + target(CyclePush); // PBR. + target(CycleAccessStack); // IO. + + target(CycleFetchIncrementPC); // New PBR. + + target(OperationConstructAbsolute); // Calculate data address. + target(OperationPerform); // [JSL] + + target(CyclePush); // PCH + target(CyclePush); // PCL + } + + // 5. Absolute long, X; al, x. + static void absolute_long_x(AccessType type, bool is8bit, const std::function &target) { + target(CycleFetchIncrementPC); // AAL. + target(CycleFetchIncrementPC); // AAH. + target(CycleFetchIncrementPC); // AAB. + + target(OperationConstructAbsoluteLongX); // Calculate data address. + + read_write(type, is8bit, target); + } + + // 6a. Absolute, X; a, x. + static void absolute_x(AccessType type, bool is8bit, const std::function &target) { + target(CycleFetchIncrementPC); // AAL. + target(CycleFetchIncrementPC); // AAH. + + if(type == AccessType::Read) { + target(OperationConstructAbsoluteXRead); // Calculate data address, potentially skipping the next fetch. + } else { + target(OperationConstructAbsoluteX); // Calculate data address. + } + target(CycleFetchIncorrectDataAddress); // Do the dummy read if necessary; OperationConstructAbsoluteX + // will skip this if it isn't required. + + read_write(type, is8bit, target); + } + + // 6b. Absolute, X; a, x, read-modify-write. + static void absolute_x_rmw(AccessType, bool is8bit, const std::function &target) { + target(CycleFetchIncrementPC); // AAL. + target(CycleFetchIncrementPC); // AAH. + + target(OperationConstructAbsoluteX); // Calculate data address. + target(CycleFetchIncorrectDataAddress); // Perform dummy read. + + read_modify_write(is8bit, target); + } + + // 7. Absolute, Y; a, y. + static void absolute_y(AccessType type, bool is8bit, const std::function &target) { + target(CycleFetchIncrementPC); // AAL. + target(CycleFetchIncrementPC); // AAH. + + if(type == AccessType::Read) { + target(OperationConstructAbsoluteYRead); // Calculate data address, potentially skipping the next fetch. + } else { + target(OperationConstructAbsoluteY); // Calculate data address. + } + target(CycleFetchIncorrectDataAddress); // Do the dummy read if necessary; OperationConstructAbsoluteX + // will skip this if it isn't required. + + read_write(type, is8bit, target); + } + + // 8. Accumulator; A. + static void accumulator(AccessType, bool, const std::function &target) { + target(CycleFetchPCThrowaway); // IO. + + // TODO: seriously consider a-specific versions of all relevant operations; + // the cost of interpreting three things here is kind of silly. + target(OperationCopyAToData); + target(OperationPerform); + target(OperationCopyDataToA); + } + + // 9a. Block Move Negative [and] + // 9b. Block Move Positive. + // + // These don't fit the general model very well at all, hence the specialised fetch and store cycles. + static void block_move(AccessType, bool, const std::function &target) { + target(CycleFetchIncrementPC); // DBA. + target(CycleFetchIncrementPC); // SBA. + + target(CycleFetchBlockX); // SRC Data. + target(CycleStoreBlockY); // Dest Data. + + target(CycleFetchBlockY); // IO. + target(CycleFetchBlockY); // IO. + + target(OperationPerform); // [MVN or MVP] + } + + // 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(OperationConstructDirect); + target(CycleFetchPCThrowaway); // IO. + + read_write(type, is8bit, target); + } + + // 10b. Direct; d, read-modify-write. + // (That's zero page in 6502 terms) + static void direct_rmw(AccessType, bool is8bit, const std::function &target) { + target(CycleFetchIncrementPC); // DO. + + target(OperationConstructDirect); + target(CycleFetchPCThrowaway); // IO. + + read_modify_write(is8bit, target); + } + + // 11. Direct Indexed Indirect; (d, x). + static void direct_indexed_indirect(AccessType type, bool is8bit, const std::function &target) { + target(CycleFetchIncrementPC); // DO. + + target(OperationConstructDirectIndexedIndirect); + target(CycleFetchPCThrowaway); // IO. + + target(CycleFetchPCThrowaway); // IO. + + target(CycleFetchIncrementData); // AAL + target(CycleFetchData); // AAH + + target(OperationCopyDataToInstruction); + target(OperationConstructAbsolute); + + read_write(type, is8bit, target); + } + + // 12. Direct Indirect; (d). + static void direct_indirect(AccessType type, bool is8bit, const std::function &target) { + target(CycleFetchIncrementPC); // DO. + + target(OperationConstructDirect); + target(CycleFetchPCThrowaway); // IO. + + target(CycleFetchIncrementData); // AAL. + target(CycleFetchData); // AAH. + + target(OperationConstructDirectIndirect); + + read_write(type, is8bit, target); + } + + // 13. Direct Indirect Indexed; (d), y. + static void direct_indirect_indexed(AccessType type, bool is8bit, const std::function &target) { + target(CycleFetchIncrementPC); // DO. + + target(OperationConstructDirect); + target(CycleFetchPCThrowaway); // IO. + + target(CycleFetchIncrementData); // AAL. + target(CycleFetchData); // AAH. + + target(OperationCopyDataToInstruction); + target(OperationConstructAbsoluteYRead); + target(CycleFetchIncorrectDataAddress); // IO. + + read_write(type, is8bit, target); + } + + // 14. Direct Indirect Indexed Long; [d], y. + static void direct_indirect_indexed_long(AccessType type, bool is8bit, const std::function &target) { + target(CycleFetchIncrementPC); // DO. + + target(OperationConstructDirect); + target(CycleFetchPCThrowaway); // IO. + + target(CycleFetchIncrementData); // AAL. + target(CycleFetchIncrementData); // AAH. + target(CycleFetchData); // AAB. + + target(OperationConstructDirectIndirectIndexedLong); + + read_write(type, is8bit, target); + } + + // 15. Direct Indirect Long; [d]. + static void direct_indirect_long(AccessType type, bool is8bit, const std::function &target) { + target(CycleFetchIncrementPC); // DO. + + target(OperationConstructDirectLong); + target(CycleFetchPCThrowaway); // IO. + + target(CycleFetchIncrementData); // AAL. + target(CycleFetchIncrementData); // AAH. + target(CycleFetchData); // AAB. + + target(OperationConstructDirectIndirectLong); + + read_write(type, is8bit, target); + } + + // 16a. Direct, X; d, x. + static void direct_x(AccessType type, bool is8bit, const std::function &target) { + target(CycleFetchIncrementPC); // DO. + + target(OperationConstructDirectX); + target(CycleFetchPCThrowaway); // IO. + + target(CycleFetchPCThrowaway); // IO. + + read_write(type, is8bit, target); + } + + // 16b. Direct, X; d, x, read-modify-write. + static void direct_x_rmw(AccessType, bool is8bit, const std::function &target) { + target(CycleFetchIncrementPC); // DO. + + target(OperationConstructDirectX); + target(CycleFetchPCThrowaway); // IO. + + target(CycleFetchPCThrowaway); // IO. + + read_modify_write(is8bit, target); + } + + // 17. Direct, Y; d, y. + static void direct_y(AccessType type, bool is8bit, const std::function &target) { + target(CycleFetchIncrementPC); // DO. + + target(OperationConstructDirectY); + target(CycleFetchPCThrowaway); // IO. + + target(CycleFetchPCThrowaway); // IO. + + read_write(type, is8bit, target); + } + + // 18. Immediate; #. + static void immediate(AccessType, bool is8bit, const std::function &target) { + if(!is8bit) target(CycleFetchIncrementPC); // IDL. + target(CycleFetchIncrementPC); // ID [H]. + target(OperationCopyInstructionToData); + target(OperationPerform); + } + + static void immediate_rep_sep(AccessType, bool, const std::function &target) { + target(CycleFetchIncrementPC); // IDL. + target(CycleFetchPCThrowaway); // "Add 1 cycle for REP and SEP" + target(OperationPerform); + } + + // 19a. Implied; i. + static void implied(AccessType, bool, const std::function &target) { + target(CycleFetchPCThrowaway); // IO + target(OperationPerform); + } + + // 19b. Implied; i; XBA. + static void implied_xba(AccessType, bool, const std::function &target) { + target(CycleFetchPCThrowaway); // IO + target(CycleFetchPCThrowaway); // IO + target(OperationPerform); + } + + // 19c. Stop the Clock; also + // 19d. Wait for interrupt. + static void stp_wai(AccessType, bool, const std::function &target) { + target(OperationPerform); // Establishes the termination condition. + target(CycleFetchPCThrowaway); // IO + target(CycleFetchPCThrowaway); // IO + target(CycleRepeatingNone); // This will first check whether the STP/WAI exit + // condition has occurred; if not then it'll issue + // a BusOperation::None and then reschedule itself. + } + + // 20. Relative; r. + static void relative(AccessType, bool, const std::function &target) { + 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(CycleFetchPCThrowaway); // IO + target(CycleFetchPCThrowaway); // IO + + 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(OperationPerform); // [BRL] + } + + // 22a. Stack; s, abort/irq/nmi/res. + static void stack_exception(AccessType, bool, const std::function &target) { + target(CycleFetchPCThrowaway); // IO + target(CycleFetchPCThrowaway); // IO + + target(OperationPrepareException); // Populates the data buffer; this skips a micro-op if + // in emulation mode. + + target(CyclePush); // PBR [skipped in emulation mode] + target(CyclePush); // PCH + target(CyclePush); // PCL + target(CyclePush); // P + + target(CycleFetchIncrementVector); // AAVL + target(CycleFetchVector); // AAVH + + target(OperationPerform); // Jumps to the vector address. + } + + // 22b. Stack; s, PLx. + static void stack_pull(AccessType, bool is8bit, const std::function &target) { + target(CycleFetchPCThrowaway); // IO + target(CycleFetchPCThrowaway); // IO + + if(!is8bit) target(CyclePull); // REG low. + target(CyclePull); // REG [high]. + + target(OperationPerform); + } + + // 22c. Stack; s, PHx. + static void stack_push(AccessType, bool is8bit, const std::function &target) { + target(CycleFetchPCThrowaway); // IO + + target(OperationPerform); + + if(!is8bit) target(CyclePush); // REG high. + target(CyclePush); // REG [low]. + } + + // 22d. Stack; s, PEA. + static void stack_pea(AccessType, bool, const std::function &target) { + target(CycleFetchIncrementPC); // AAL + target(CycleFetchIncrementPC); // AAH + target(CyclePush); // AAH + target(CyclePush); // AAL + } + + // 22e. Stack; s, PEI. + static void stack_pei(AccessType, bool, const std::function &target) { + target(CycleFetchIncrementPC); // DO + + target(OperationConstructDirect); + target(CycleFetchPCThrowaway); // IO + + 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(OperationConstructPER); + + target(CyclePush); // AAH + target(CyclePush); // AAL + } + + // 22g. Stack; s, RTI. + static void stack_rti(AccessType, bool, const std::function &target) { + target(CycleFetchPCThrowaway); // IO + target(CycleFetchPCThrowaway); // IO + + target(CyclePull); // P + target(CyclePull); // New PCL + target(CyclePull); // New PCH + target(CyclePullIfNotEmulation); // PBR + + target(OperationPerform); // [RTI] — to unpack the fields above. + } + + // 22h. Stack; s, RTS. + static void stack_rts(AccessType, bool, const std::function &target) { + target(CycleFetchPCThrowaway); // IO + target(CycleFetchPCThrowaway); // IO + + target(CyclePull); // PCL + target(CyclePull); // PCH + target(CycleAccessStack); // IO + + target(OperationPerform); // [RTS] + } + + // 22i. Stack; s, RTL. + static void stack_rtl(AccessType, bool, const std::function &target) { + target(CycleFetchIncrementPC); // IO + target(CycleFetchIncrementPC); // IO + + target(CyclePull); // New PCL + target(CyclePull); // New PCH + target(CyclePull); // New PBR + + target(OperationPerform); // [JML, to perform the RTL] + } + + // 22j. Stack; s, BRK/COP. + static void brk_cop(AccessType, bool, const std::function &target) { + target(CycleFetchIncrementPC); // Signature. + + target(OperationPrepareException); // Populates the data buffer; this skips a micro-op if + // in emulation mode. + + target(CyclePush); // PBR [skipped in emulation mode] + target(CyclePush); // PCH + target(CyclePush); // PCL + target(CyclePush); // P + + target(CycleFetchIncrementVector); // AAVL + target(CycleFetchVector); // AAVH + + target(OperationPerform); // Jumps to the vector address. + } + + // 23. Stack Relative; d, s. + static void stack_relative(AccessType type, bool is8bit, const std::function &target) { + target(CycleFetchIncrementPC); // SO + target(CycleFetchPCThrowaway); // IO + + target(OperationConstructStackRelative); + read_write(type, is8bit, target); + } + + // 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(OperationConstructStackRelative); + target(CycleFetchIncrementData); // AAL + target(CycleFetchData); // AAH + target(CycleFetchDataThrowaway); // IO. + + target(OperationConstructStackRelativeIndexedIndirect); + read_write(type, is8bit, target); + } +}; + +ProcessorStorage::ProcessorStorage() { + set_reset_state(); + micro_ops_.reserve(1024); + + ProcessorStorageConstructor constructor(*this); + using AccessMode = ProcessorStorageConstructor::AccessMode; + + // Install the instructions. +#define op(x, ...) constructor.install(&ProcessorStorageConstructor::x, __VA_ARGS__) + + /* 0x00 BRK s */ op(brk_cop, JMPind); + /* 0x01 ORA (d, x) */ op(direct_indexed_indirect, ORA); + /* 0x02 COP s */ op(brk_cop, JMPind); + /* 0x03 ORA d, s */ op(stack_relative, ORA); + /* 0x04 TSB d */ op(direct_rmw, TSB); + /* 0x05 ORA d */ op(direct, ORA); + /* 0x06 ASL d */ op(direct_rmw, ASL); + /* 0x07 ORA [d] */ op(direct_indirect_long, ORA); + /* 0x08 PHP s */ op(stack_push, PHP); + /* 0x09 ORA # */ op(immediate, ORA); + /* 0x0a ASL A */ op(accumulator, ASL); + /* 0x0b PHD s */ op(stack_push, PHD, AccessMode::Always8Bit); + /* 0x0c TSB a */ op(absolute_rmw, TSB); + /* 0x0d ORA a */ op(absolute, ORA); + /* 0x0e ASL a */ op(absolute_rmw, ASL); + /* 0x0f ORA al */ op(absolute_long, ORA); + + /* 0x10 BPL r */ op(relative, BPL); + /* 0x11 ORA (d), y */ op(direct_indirect_indexed, ORA); + /* 0x12 ORA (d) */ op(direct_indirect, ORA); + /* 0x13 ORA (d, s), y */ op(stack_relative_indexed_indirect, ORA); + /* 0x14 TRB d */ op(absolute_rmw, TRB); + /* 0x15 ORA d, x */ op(direct_x, ORA); + /* 0x16 ASL d, x */ op(direct_x_rmw, ASL); + /* 0x17 ORA [d], y */ op(direct_indirect_indexed_long, ORA); + /* 0x18 CLC i */ op(implied, CLC); + /* 0x19 ORA a, y */ op(absolute_y, ORA); + /* 0x1a INC A */ op(accumulator, INC); + /* 0x1b TCS i */ op(implied, TCS); + /* 0x1c TRB a */ op(absolute_rmw, TRB); + /* 0x1d ORA a, x */ op(absolute_x, ORA); + /* 0x1e ASL a, x */ op(absolute_x_rmw, ASL); + /* 0x1f ORA al, x */ op(absolute_long_x, ORA); + + /* 0x20 JSR a */ op(absolute_jsr, JSR); + /* 0x21 AND (d, x) */ op(direct_indexed_indirect, AND); + /* 0x22 JSL al */ op(absolute_long_jsl, JSL); + /* 0x23 AND d, s */ op(stack_relative, AND); + /* 0x24 BIT d */ op(direct, BIT); + /* 0x25 AND d */ op(direct, AND); + /* 0x26 ROL d */ op(direct_rmw, ROL); + /* 0x27 AND [d] */ op(direct_indirect_long, AND); + /* 0x28 PLP s */ op(stack_pull, PLP); + /* 0x29 AND # */ op(immediate, AND); + /* 0x2a ROL A */ op(accumulator, ROL); + /* 0x2b PLD s */ op(stack_pull, PLD, AccessMode::Always8Bit); + /* 0x2c BIT a */ op(absolute, BIT); + /* 0x2d AND a */ op(absolute, AND); + /* 0x2e ROL a */ op(absolute_rmw, ROL); + /* 0x2f AND al */ op(absolute_long, AND); + + /* 0x30 BMI r */ op(relative, BMI); + /* 0x31 AND (d), y */ op(direct_indirect_indexed, AND); + /* 0x32 AND (d) */ op(direct_indirect, AND); + /* 0x33 AND (d, s), y */ op(stack_relative_indexed_indirect, AND); + /* 0x34 BIT d, x */ op(direct_x, BIT); + /* 0x35 AND d, x */ op(direct_x, AND); + /* 0x36 ROL d, x */ op(direct_x_rmw, ROL); + /* 0x37 AND [d], y */ op(direct_indirect_indexed_long, AND); + /* 0x38 SEC i */ op(implied, SEC); + /* 0x39 AND a, y */ op(absolute_y, AND); + /* 0x3a DEC A */ op(accumulator, DEC); + /* 0x3b TSC i */ op(implied, TSC); + /* 0x3c BIT a, x */ op(absolute_x, BIT); + /* 0x3d AND a, x */ op(absolute_x, AND); + /* 0x3e ROL a, x */ op(absolute_x_rmw, ROL); + /* 0x3f AND al, x */ op(absolute_long_x, AND); + + /* 0x40 RTI s */ op(stack_rti, RTI); + /* 0x41 EOR (d, x) */ op(direct_indexed_indirect, EOR); + /* 0x42 WDM i */ op(implied, NOP); + /* 0x43 EOR d, s */ op(stack_relative, EOR); + /* 0x44 MVP xyc */ op(block_move, MVP); + /* 0x45 EOR d */ op(direct, EOR); + /* 0x46 LSR d */ op(direct_rmw, LSR); + /* 0x47 EOR [d] */ op(direct_indirect_long, EOR); + /* 0x48 PHA s */ op(stack_push, STA); + /* 0x49 EOR # */ op(immediate, EOR); + /* 0x4a LSR A */ op(accumulator, LSR); + /* 0x4b PHK s */ op(stack_push, PHK, AccessMode::Always8Bit); + /* 0x4c JMP a */ op(absolute_jmp, JMP); + /* 0x4d EOR a */ op(absolute, EOR); + /* 0x4e LSR a */ op(absolute_rmw, LSR); + /* 0x4f EOR al */ op(absolute_long, EOR); + + /* 0x50 BVC r */ op(relative, BVC); + /* 0x51 EOR (d), y */ op(direct_indirect_indexed, EOR); + /* 0x52 EOR (d) */ op(direct_indirect, EOR); + /* 0x53 EOR (d, s), y */ op(stack_relative_indexed_indirect, EOR); + /* 0x54 MVN xyc */ op(block_move, MVN); + /* 0x55 EOR d, x */ op(direct_x, EOR); + /* 0x56 LSR d, x */ op(direct_x_rmw, LSR); + /* 0x57 EOR [d], y */ op(direct_indirect_indexed_long, EOR); + /* 0x58 CLI i */ op(implied, CLI); + /* 0x59 EOR a, y */ op(absolute_y, EOR); + /* 0x5a PHY s */ op(stack_push, STY); + /* 0x5b TCD i */ op(implied, TCD); + /* 0x5c JMP al */ op(absolute_long_jmp, JML); // [sic]; this updates PBR so it's JML. + /* 0x5d EOR a, x */ op(absolute_x, EOR); + /* 0x5e LSR a, x */ op(absolute_x_rmw, LSR); + /* 0x5f EOR al, x */ op(absolute_long_x, EOR); + + /* 0x60 RTS s */ op(stack_rts, RTS); + /* 0x61 ADC (d, x) */ op(direct_indexed_indirect, ADC); + /* 0x62 PER s */ op(stack_per, NOP, AccessMode::Always16Bit); + /* 0x63 ADC d, s */ op(stack_relative, ADC); + /* 0x64 STZ d */ op(direct, STZ); + /* 0x65 ADC d */ op(direct, ADC); + /* 0x66 ROR d */ op(direct_rmw, ROR); + /* 0x67 ADC [d] */ op(direct_indirect_long, ADC); + /* 0x68 PLA s */ op(stack_pull, LDA); + /* 0x69 ADC # */ op(immediate, ADC); + /* 0x6a ROR A */ op(accumulator, ROR); + /* 0x6b RTL s */ op(stack_rtl, JML); + /* 0x6c JMP (a) */ op(absolute_indirect_jmp, JMPind); + /* 0x6d ADC a */ op(absolute, ADC); + /* 0x6e ROR a */ op(absolute_rmw, ROR); + /* 0x6f ADC al */ op(absolute_long, ADC); + + /* 0x70 BVS r */ op(relative, BVS); + /* 0x71 ADC (d), y */ op(direct_indirect_indexed, ADC); + /* 0x72 ADC (d) */ op(direct_indirect, ADC); + /* 0x73 ADC (d, s), y */ op(stack_relative_indexed_indirect, ADC); + /* 0x74 STZ d, x */ op(direct_x, STZ); + /* 0x75 ADC d, x */ op(direct_x, ADC); + /* 0x76 ROR d, x */ op(direct_x_rmw, ROR); + /* 0x77 ADC [d], y */ op(direct_indirect_indexed_long, ADC); + /* 0x78 SEI i */ op(implied, SEI); + /* 0x79 ADC a, y */ op(absolute_y, ADC); + /* 0x7a PLY s */ op(stack_pull, LDY); + /* 0x7b TDC i */ op(implied, TDC); + /* 0x7c JMP (a, x) */ op(absolute_indexed_indirect_jmp, JMPind); + /* 0x7d ADC a, x */ op(absolute_x, ADC); + /* 0x7e ROR a, x */ op(absolute_x_rmw, ROR); + /* 0x7f ADC al, x */ op(absolute_long_x, ADC); + + /* 0x80 BRA r */ op(relative, BRA); + /* 0x81 STA (d, x) */ op(direct_indexed_indirect, STA); + /* 0x82 BRL rl */ op(relative_long, BRL); + /* 0x83 STA d, s */ op(stack_relative, STA); + /* 0x84 STY d */ op(direct, STY); + /* 0x85 STA d */ op(direct, STA); + /* 0x86 STX d */ op(direct, STX); + /* 0x87 STA [d] */ op(direct_indirect_long, STA); + /* 0x88 DEY i */ op(implied, DEY); + /* 0x89 BIT # */ op(immediate, BITimm); + /* 0x8a TXA i */ op(implied, TXA); + /* 0x8b PHB s */ op(stack_push, PHB, AccessMode::Always8Bit); + /* 0x8c STY a */ op(absolute, STY); + /* 0x8d STA a */ op(absolute, STA); + /* 0x8e STX a */ op(absolute, STX); + /* 0x8f STA al */ op(absolute_long, STA); + + /* 0x90 BCC r */ op(relative, BCC); + /* 0x91 STA (d), y */ op(direct_indirect_indexed, STA); + /* 0x92 STA (d) */ op(direct_indirect, STA); + /* 0x93 STA (d, s), y */ op(stack_relative_indexed_indirect, STA); + /* 0x94 STY d, x */ op(direct_x, STY); + /* 0x95 STA d, x */ op(direct_x, STA); + /* 0x96 STX d, y */ op(direct_y, STX); + /* 0x97 STA [d], y */ op(direct_indirect_indexed_long, STA); + /* 0x98 TYA i */ op(implied, TYA); + /* 0x99 STA a, y */ op(absolute_y, STA); + /* 0x9a TXS i */ op(implied, TXS); + /* 0x9b TXY i */ op(implied, TXY); + /* 0x9c STZ a */ op(absolute, STZ); + /* 0x9d STA a, x */ op(absolute_x, STA); + /* 0x9e STZ a, x */ op(absolute_x, STZ); + /* 0x9f STA al, x */ op(absolute_long_x, STA); + + /* 0xa0 LDY # */ op(immediate, LDY); + /* 0xa1 LDA (d, x) */ op(direct_indexed_indirect, LDA); + /* 0xa2 LDX # */ op(immediate, LDX); + /* 0xa3 LDA d, s */ op(stack_relative, LDA); + /* 0xa4 LDY d */ op(direct, LDY); + /* 0xa5 LDA d */ op(direct, LDA); + /* 0xa6 LDX d */ op(direct, LDX); + /* 0xa7 LDA [d] */ op(direct_indirect_long, LDA); + /* 0xa8 TAY i */ op(implied, TAY); + /* 0xa9 LDA # */ op(immediate, LDA); + /* 0xaa TAX i */ op(implied, TAX); + /* 0xab PLB s */ op(stack_pull, PLB, AccessMode::Always8Bit); + /* 0xac LDY a */ op(absolute, LDY); + /* 0xad LDA a */ op(absolute, LDA); + /* 0xae LDX a */ op(absolute, LDX); + /* 0xaf LDA al */ op(absolute_long, LDA); + + /* 0xb0 BCS r */ op(relative, BCS); + /* 0xb1 LDA (d), y */ op(direct_indirect_indexed, LDA); + /* 0xb2 LDA (d) */ op(direct_indirect, LDA); + /* 0xb3 LDA (d, s), y */ op(stack_relative_indexed_indirect, LDA); + /* 0xb4 LDY d, x */ op(direct_x, LDY); + /* 0xb5 LDA d, x */ op(direct_x, LDA); + /* 0xb6 LDX d, y */ op(direct_y, LDX); + /* 0xb7 LDA [d], y */ op(direct_indirect_indexed_long, LDA); + /* 0xb8 CLV i */ op(implied, CLV); + /* 0xb9 LDA a, y */ op(absolute_y, LDA); + /* 0xba TSX i */ op(implied, TSX); + /* 0xbb TYX i */ op(implied, TYX); + /* 0xbc LDY a, x */ op(absolute_x, LDY); + /* 0xbd LDA a, x */ op(absolute_x, LDA); + /* 0xbe LDX a, y */ op(absolute_y, LDX); + /* 0xbf LDA al, x */ op(absolute_long_x, LDA); + + /* 0xc0 CPY # */ op(immediate, CPY); + /* 0xc1 CMP (d, x) */ op(direct_indexed_indirect, CMP); + /* 0xc2 REP # */ op(immediate_rep_sep, REP); + /* 0xc3 CMP d, s */ op(stack_relative, CMP); + /* 0xc4 CPY d */ op(direct, CPY); + /* 0xc5 CMP d */ op(direct, CMP); + /* 0xc6 DEC d */ op(direct_rmw, DEC); + /* 0xc7 CMP [d] */ op(direct_indirect_long, CMP); + /* 0xc8 INY i */ op(implied, INY); + /* 0xc9 CMP # */ op(immediate, CMP); + /* 0xca DEX i */ op(implied, DEX); + /* 0xcb WAI i */ op(stp_wai, WAI); + /* 0xcc CPY a */ op(absolute, CPY); + /* 0xcd CMP a */ op(absolute, CMP); + /* 0xce DEC a */ op(absolute_rmw, DEC); + /* 0xcf CMP al */ op(absolute_long, CMP); + + /* 0xd0 BNE r */ op(relative, BNE); + /* 0xd1 CMP (d), y */ op(direct_indirect_indexed, CMP); + /* 0xd2 CMP (d) */ op(direct_indirect, CMP); + /* 0xd3 CMP (d, s), y */ op(stack_relative_indexed_indirect, CMP); + /* 0xd4 PEI s */ op(stack_pei, NOP, AccessMode::Always16Bit); + /* 0xd5 CMP d, x */ op(direct_x, CMP); + /* 0xd6 DEC d, x */ op(direct_x_rmw, DEC); + /* 0xd7 CMP [d], y */ op(direct_indirect_indexed_long, CMP); + /* 0xd8 CLD i */ op(implied, CLD); + /* 0xd9 CMP a, y */ op(absolute_y, CMP); + /* 0xda PHX s */ op(stack_push, STX); + /* 0xdb STP i */ op(stp_wai, STP); + /* 0xdc JML (a) */ op(absolute_indirect_jml, JML); + /* 0xdd CMP a, x */ op(absolute_x, CMP); + /* 0xde DEC a, x */ op(absolute_x_rmw, DEC); + /* 0xdf CMP al, x */ op(absolute_long_x, CMP); + + /* 0xe0 CPX # */ op(immediate, CPX); + /* 0xe1 SBC (d, x) */ op(direct_indexed_indirect, SBC); + /* 0xe2 SEP # */ op(immediate_rep_sep, SEP); + /* 0xe3 SBC d, s */ op(stack_relative, SBC); + /* 0xe4 CPX d */ op(direct, CPX); + /* 0xe5 SBC d */ op(direct, SBC); + /* 0xe6 INC d */ op(direct_rmw, INC); + /* 0xe7 SBC [d] */ op(direct_indirect_long, SBC); + /* 0xe8 INX i */ op(implied, INX); + /* 0xe9 SBC # */ op(immediate, SBC); + /* 0xea NOP i */ op(implied, NOP); + /* 0xeb XBA i */ op(implied_xba, XBA); + /* 0xec CPX a */ op(absolute, CPX); + /* 0xed SBC a */ op(absolute, SBC); + /* 0xee INC a */ op(absolute_rmw, INC); + /* 0xef SBC al */ op(absolute_long, SBC); + + /* 0xf0 BEQ r */ op(relative, BEQ); + /* 0xf1 SBC (d), y */ op(direct_indirect_indexed, SBC); + /* 0xf2 SBC (d) */ op(direct_indirect, SBC); + /* 0xf3 SBC (d, s), y */ op(stack_relative_indexed_indirect, SBC); + /* 0xf4 PEA s */ op(stack_pea, NOP, AccessMode::Always16Bit); + /* 0xf5 SBC d, x */ op(direct_x, SBC); + /* 0xf6 INC d, x */ op(direct_x_rmw, INC); + /* 0xf7 SBC [d], y */ op(direct_indirect_indexed_long, SBC); + /* 0xf8 SED i */ op(implied, SED); + /* 0xf9 SBC a, y */ op(absolute_y, SBC); + /* 0xfa PLX s */ op(stack_pull, LDX); + /* 0xfb XCE i */ op(implied, XCE); + /* 0xfc JSR (a, x) */ op(absolute_indexed_indirect_jsr, JMPind); // [sic] + /* 0xfd SBC a, x */ op(absolute_x, SBC); + /* 0xfe INC a, x */ op(absolute_x_rmw, INC); + /* 0xff SBC al, x */ op(absolute_long_x, SBC); +#undef op + + constructor.set_exception_generator(&ProcessorStorageConstructor::stack_exception); + constructor.install_fetch_decode_execute(); + + // Find any OperationMoveToNextProgram. + next_op_ = micro_ops_.data(); + while(*next_op_ != OperationMoveToNextProgram) ++next_op_; + + // This is primarily to keep tabs, in case I want to pick a shorter form for the instruction table. + assert(micro_ops_.size() < 1024); +} + +void ProcessorStorage::set_reset_state() { + registers_.data_bank = 0; + registers_.program_bank = 0; + registers_.direct = 0; + registers_.flags.decimal = 0; + registers_.flags.inverse_interrupt = 0; + set_emulation_mode(true); +} + +void ProcessorStorage::set_emulation_mode(bool enabled) { + if(registers_.emulation_flag == enabled) { + return; + } + registers_.emulation_flag = enabled; + + if(enabled) { + set_m_x_flags(true, true); + registers_.x.halves.high = registers_.y.halves.high = 0; + registers_.e_masks[0] = 0xff00; + registers_.e_masks[1] = 0x00ff; + } else { + registers_.e_masks[0] = 0x0000; + registers_.e_masks[1] = 0xffff; + registers_.s.halves.high = 1; // To pretend it was 1 all along; this implementation actually ignores + // the top byte while in emulation mode. + } +} + +void ProcessorStorage::set_m_x_flags(bool m, bool x) { + // true/1 => 8bit for both flags. + registers_.mx_flags[0] = m; + registers_.mx_flags[1] = x; + + registers_.m_masks[0] = m ? 0xff00 : 0x0000; + registers_.m_masks[1] = m ? 0x00ff : 0xffff; + registers_.m_shift = m ? 0 : 8; + + registers_.x_masks[0] = x ? 0xff00 : 0x0000; + registers_.x_masks[1] = x ? 0x00ff : 0xffff; + registers_.x_shift = x ? 0 : 8; +} + +uint8_t ProcessorStorage::get_flags() const { + uint8_t result = registers_.flags.get(); + + if(!registers_.emulation_flag) { + result &= ~(Flag::MemorySize | Flag::IndexSize); + result |= registers_.mx_flags[0] * Flag::MemorySize; + result |= registers_.mx_flags[1] * Flag::IndexSize; + } + + return result; +} + +void ProcessorStorage::set_flags(uint8_t value) { + registers_.flags.set(value); + + if(!registers_.emulation_flag) { + set_m_x_flags(value & Flag::MemorySize, value & Flag::IndexSize); + } +} diff --git a/Processors/65816/Implementation/65816Storage.hpp b/Processors/65816/Implementation/65816Storage.hpp new file mode 100644 index 000000000..3dc0c55a4 --- /dev/null +++ b/Processors/65816/Implementation/65816Storage.hpp @@ -0,0 +1,374 @@ +// +// 65816Implementation.hpp +// Clock Signal +// +// Created by Thomas Harte on 23/09/2020. +// Copyright © 2020 Thomas Harte. All rights reserved. +// + +enum MicroOp: uint8_t { + /// Fetches a byte from the program counter to the instruction buffer and increments the program counter. + CycleFetchIncrementPC, + /// Fetches a byte from the program counter without incrementing it. + CycleFetchPC, + /// Fetches a byte from the program counter without incrementing it, and throws it away. + CycleFetchPCThrowaway, + /// The same as CycleFetchIncrementPC but indicates valid program address rather than valid data address. + CycleFetchOpcode, + + /// Fetches a byte from the data address to the data buffer. + CycleFetchData, + /// Fetches a byte from the data address to the data buffer and increments the data address. + CycleFetchIncrementData, + /// Fetches from the address formed by the low byte of the data address and the high byte + /// of the instruction buffer, throwing the result away. + CycleFetchIncorrectDataAddress, + /// Fetches a byte from the data address and throws it away. + CycleFetchDataThrowaway, + /// Fetches a byte from the data address to the data buffer, signalling VPB . + CycleFetchVector, + /// Fetches a byte from the data address to the data buffer and increments the data address, signalling VPB. + CycleFetchIncrementVector, + + // Dedicated block-move cycles; these use the data buffer as an intermediary. + CycleFetchBlockX, + CycleFetchBlockY, + CycleStoreBlockY, + + /// Stores a byte from the data buffer. + CycleStoreData, + /// Stores the most recent byte placed into the data buffer without removing it. + CycleStoreDataThrowaway, + /// 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. + CycleStoreDecrementData, + + /// Pushes a single byte from the data buffer to the stack. + CyclePush, + /// Fetches from the current stack location and throws the result away. + CycleAccessStack, + /// Pulls a single byte to the data buffer from the stack. + CyclePull, + /// Performs as CyclePull if the 65816 is not in emulation mode; otherwise skips itself. + CyclePullIfNotEmulation, + + /// Issues a BusOperation::None and regresses the micro-op counter until an established + /// STP or WAI condition is satisfied. + CycleRepeatingNone, + + /// Sets the data address by copying the final two bytes of the instruction buffer and + /// using the data register as a high byte. + OperationConstructAbsolute, + + /// Constructs a strictly 16-bit address from the instruction buffer. + OperationConstructAbsolute16, + + /// Sets the data address by copying the entire instruction buffer. + OperationConstructAbsoluteLong, + + /// Sets the data address to the 16-bit result of adding x to the value in the instruction buffer. + OperationConstructAbsoluteIndexedIndirect, + + /// Sets the data address to the 24-bit result of adding x to the low 16-bits of the value in the + /// instruction buffer and retaining the highest 8-bits as specified. + OperationConstructAbsoluteLongX, + + /// Calculates an a, x address; if: + /// there was no carry into the top byte of the address; and + /// the process or in emulation or 8-bit index mode; + /// then it also skips the next micro-op. + OperationConstructAbsoluteXRead, + + /// Calculates an a, x address. + OperationConstructAbsoluteX, + + // These are analogous to the X versions above. + OperationConstructAbsoluteY, + OperationConstructAbsoluteYRead, + + /// Constructs the current direct address using the value in the instruction buffer. + /// Skips the next micro-op if the low byte of the direct register is 0. + OperationConstructDirect, + + /// Exactly like OperationConstructDirect, but doesn't retain any single-byte wrapping + /// behaviour in emulation mode. + OperationConstructDirectLong, + + /// Constructs the current direct indexed indirect address using the data bank, + /// direct and x registers plus the value currently in the instruction buffer. + /// Skips the next micro-op if the low byte of the direct register is 0. + OperationConstructDirectIndexedIndirect, + + /// Constructs the current direct indexed indirect address using the value + /// currently in the data buffer. + OperationConstructDirectIndirect, + + /// Adds y to the low 16-bits currently in the instruction buffer and appends a high 8-bits + /// also from the instruction buffer. + OperationConstructDirectIndirectIndexedLong, + + /// Uses the 24-bit address currently in the instruction buffer. + OperationConstructDirectIndirectLong, + + /// Adds the x register to the direct register to produce a 16-bit address; + /// skips the next micro-op if the low byte of the direct register is 0. + OperationConstructDirectX, + + /// Adds the y register to the direct register to produce a 16-bit address; + /// skips the next micro-op if the low byte of the direct register is 0. + OperationConstructDirectY, + + /// Adds the instruction buffer to the program counter, making a 16-bit result, + /// *and stores it into the data buffer*. + OperationConstructPER, + + /// Adds the stack pointer to the instruction buffer to produce a 16-bit address. + OperationConstructStackRelative, + + /// Adds y to the value in the instruction buffer to produce a 16-bit result and + /// prefixes the current data bank. + OperationConstructStackRelativeIndexedIndirect, + + /// Performs whatever operation goes with this program. + OperationPerform, + + /// Copies the current program counter to the data buffer. + OperationCopyPCToData, + OperationCopyDataToPC, + OperationCopyInstructionToData, + OperationCopyDataToInstruction, + + /// Copies the current PBR to the data buffer. + OperationCopyPBRToData, + + /// Copies A to the data buffer. + OperationCopyAToData, + + /// Copies the data buffer to A. + OperationCopyDataToA, + + /// Fills the data buffer with three or four bytes, depending on emulation mode, containing the program + /// counter, flags and possibly the program bank. Also puts the appropriate vector address into the + /// address register. + OperationPrepareException, + + /// Sets the memory lock output for the rest of this instruction. + OperationSetMemoryLock, + + /// Complete this set of micr-ops. + OperationMoveToNextProgram, + + /// Inspects the instruction buffer and thereby selects the next set of micro-ops to schedule. + OperationDecode, +}; + +enum Operation: uint8_t { + // These perform the named operation using the value in the data buffer; + // they are implicitly AccessType::Read. + ADC, AND, BIT, CMP, CPX, CPY, EOR, ORA, SBC, BITimm, + + // These load the respective register from the data buffer; + // they are implicitly AccessType::Read. + LDA, LDX, LDY, + PLB, PLD, PLP, // LDA, LDX and LDY can be used for PLA, PLX, PLY. + + // These move the respective register (or value) to the data buffer; + // they are implicitly AccessType::Write. + STA, STX, STY, STZ, + PHB, PHP, PHD, PHK, + + // These modify the value in the data buffer as part of a read-modify-write. + INC, DEC, ASL, LSR, ROL, ROR, TRB, TSB, + + // These merely decrement A, increment or decrement X and Y, and regress + // the program counter only if appropriate. + MVN, MVP, + + // These use a value straight from the instruction buffer. + REP, SEP, + + BCC, BCS, BEQ, BMI, BNE, BPL, BRA, BVC, BVS, BRL, + + // These are all implicit. + CLC, CLD, CLI, CLV, DEX, DEY, INX, INY, NOP, SEC, SED, SEI, + TAX, TAY, TCD, TCS, TDC, TSC, TSX, TXA, TXS, TXY, TYA, TYX, + XCE, XBA, + + STP, WAI, + + // These unpack values from the data buffer, which has been filled + // from the stack. + RTI, + + /// Loads the PC with the contents of the data buffer. + JMPind, + + /// Loads the PC with the contents of the instruction bufer. + JMP, + + /// Loads the PC and PBR with the operand from the instruction buffer. + JML, + + /// Loads the PC with the operand from the instruction buffer, placing + /// the current PC into the data buffer. + JSR, + + /// Loads the PC and the PBR with the operand from the instruction buffer, + /// placing the old PC into the data buffer (and only the PC; PBR not included). + JSL, + + /// Loads the PC with the contents of the data buffer + 1. + RTS, +}; + +struct ProcessorStorageConstructor; + +struct ProcessorStorage { + ProcessorStorage(); + + // Frustratingly, there is not quite enough space in 16 bits to store both + // the program offset and the operation as currently defined. + struct Instruction { + /// Pointers into micro_ops_ for: [0] = 16-bit operation; [1] = 8-bit operation. + uint16_t program_offsets[2] = {0xffff, 0xffff}; + /// The operation to perform upon an OperationPerform. + Operation operation = NOP; + /// An index into the mx field indicating which of M or X affects whether this is an 8-bit or 16-bit field. + /// So the program to perform is that at @c program_offsets[mx_flags[size_field]] + uint8_t size_field = 0; + }; + Instruction instructions[256 + 2]; // Arranged as: + // 256 entries: instructions; + // the entry for 'exceptions' (i.e. reset, irq, nmi); and + // the entry for fetch-decode-execute. + + enum class OperationSlot { + Exception = 256, + FetchDecodeExecute + }; + + // A helper for testing. + uint16_t last_operation_pc_; + Instruction *active_instruction_; + Cycles cycles_left_to_run_; + + // All registers are boxed up into a struct so that they can be stored and restored in support of abort. + struct Registers { + // Registers. + RegisterPair16 a; + RegisterPair16 x, y; + RegisterPair16 s; + uint16_t pc; + + // Flags aplenty. + MOS6502Esque::LazyFlags flags; + uint8_t mx_flags[2] = {1, 1}; // [0] = m; [1] = x. In both cases either `0` or `1`; `1` => 8-bit. + uint16_t m_masks[2] = {0xff00, 0x00ff}; // [0] = src mask; [1] = dst mask. + uint16_t x_masks[2] = {0xff00, 0x00ff}; // [0] = src mask; [1] = dst mask. + uint16_t e_masks[2] = {0xff00, 0x00ff}; + int m_shift = 0; + int x_shift = 0; + bool emulation_flag = true; + + // I.e. the offset for direct addressing (outside of emulation mode). + uint16_t direct = 0; + + // Banking registers are all stored with the relevant byte + // shifted up bits 16–23. + uint32_t data_bank = 0; // i.e. DBR. + uint32_t program_bank = 0; // i.e. PBR. + } registers_, abort_registers_copy_; + + // The next bus transaction. + uint32_t bus_address_ = 0; + uint8_t *bus_value_ = nullptr; + static inline uint8_t bus_throwaway_ = 0; + BusOperation bus_operation_ = BusOperation::None; + + // A bitfield for various exceptions. + static constexpr int PowerOn = 1 << 0; + static constexpr int Reset = 1 << 1; + static constexpr int IRQ = Flag::Interrupt; // This makes masking a lot easier later on; this is 1 << 2. + static constexpr int NMI = 1 << 3; + static constexpr int Abort = 1 << 4; + + static constexpr int default_exceptions = PowerOn; + int pending_exceptions_ = default_exceptions; + int selected_exceptions_ = default_exceptions; + + bool ready_line_ = false; + bool memory_lock_ = false; + + // Just to be safe. + static_assert(PowerOn != IRQ); + static_assert(Reset != IRQ); + static_assert(NMI != IRQ); + static_assert(Abort != IRQ); + + /// Sets the required exception flags necessary to exit a STP or WAI. + int required_exceptions_ = 0; + BusOperation stp_wai_bus_operation_ = BusOperation::None; + + /// Defines a four-byte buffer which can be cleared or filled in single-byte increments from least significant byte + /// to most significant. + struct Buffer { + uint32_t value = 0; + int size = 0; + int read = 0; + + void clear() { + value = 0; + size = 0; + read = 0; + } + + uint8_t *next_input() { + uint8_t *const next = byte(size); + ++size; + return next; + } + + uint8_t *next_output() { + uint8_t *const next = byte(read); + ++read; + return next; + } + + uint8_t *preview_output() { + return byte(read); + } + + uint8_t *next_output_descending() { + --size; + return byte(size); + } + + uint8_t *any_byte() { + return reinterpret_cast(&value); + } + + private: + uint8_t *byte(int pointer) { + assert(pointer >= 0 && pointer < 4); + #if TARGET_RT_BIG_ENDIAN + return reinterpret_cast(&value) + (3 ^ pointer); + #else + return reinterpret_cast(&value) + pointer; + #endif + } + }; + Buffer instruction_buffer_, data_buffer_; + uint32_t data_address_; + uint32_t data_address_increment_mask_ = 0xffff; + uint32_t incorrect_data_address_; + + std::vector micro_ops_; + MicroOp *next_op_ = nullptr; + + void set_reset_state(); + void set_emulation_mode(bool); + void set_m_x_flags(bool m, bool x); + uint8_t get_flags() const; + void set_flags(uint8_t); +}; diff --git a/Processors/AllRAMProcessor.cpp b/Processors/AllRAMProcessor.cpp index 7a283d926..5cb7a21ef 100644 --- a/Processors/AllRAMProcessor.cpp +++ b/Processors/AllRAMProcessor.cpp @@ -15,14 +15,14 @@ AllRAMProcessor::AllRAMProcessor(std::size_t memory_size) : traps_(memory_size, false), timestamp_(0) {} -void AllRAMProcessor::set_data_at_address(uint16_t startAddress, std::size_t length, const uint8_t *data) { - std::size_t endAddress = std::min(startAddress + length, size_t(65536)); - std::memcpy(&memory_[startAddress], data, endAddress - startAddress); +void AllRAMProcessor::set_data_at_address(size_t start_address, std::size_t length, const uint8_t *data) { + const size_t end_address = std::min(start_address + length, memory_.size()); + memcpy(&memory_[start_address], data, end_address - start_address); } -void AllRAMProcessor::get_data_at_address(uint16_t startAddress, std::size_t length, uint8_t *data) { - std::size_t endAddress = std::min(startAddress + length, size_t(65536)); - std::memcpy(data, &memory_[startAddress], endAddress - startAddress); +void AllRAMProcessor::get_data_at_address(size_t start_address, std::size_t length, uint8_t *data) { + const size_t end_address = std::min(start_address + length, memory_.size()); + memcpy(data, &memory_[start_address], end_address - start_address); } HalfCycles AllRAMProcessor::get_timestamp() { diff --git a/Processors/AllRAMProcessor.hpp b/Processors/AllRAMProcessor.hpp index 117b85711..0ad2f31c5 100644 --- a/Processors/AllRAMProcessor.hpp +++ b/Processors/AllRAMProcessor.hpp @@ -21,8 +21,8 @@ class AllRAMProcessor { public: AllRAMProcessor(std::size_t memory_size); HalfCycles get_timestamp(); - void set_data_at_address(uint16_t startAddress, std::size_t length, const uint8_t *data); - void get_data_at_address(uint16_t startAddress, std::size_t length, uint8_t *data); + void set_data_at_address(size_t startAddress, size_t length, const uint8_t *data); + void get_data_at_address(size_t startAddress, size_t length, uint8_t *data); class TrapHandler { public: diff --git a/Processors/Z80/AllRAM/Z80AllRAM.hpp b/Processors/Z80/AllRAM/Z80AllRAM.hpp index c8f160a20..85f4f04dc 100644 --- a/Processors/Z80/AllRAM/Z80AllRAM.hpp +++ b/Processors/Z80/AllRAM/Z80AllRAM.hpp @@ -29,7 +29,7 @@ class AllRAMProcessor: } struct PortAccessDelegate { - virtual uint8_t z80_all_ram_processor_input(uint16_t port) { return 0xff; } + virtual uint8_t z80_all_ram_processor_input(uint16_t) { return 0xff; } }; inline void set_port_access_delegate(PortAccessDelegate *delegate) { port_delegate_ = delegate;