diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 75269411a..383793397 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -62,6 +62,7 @@ class ConcreteMachine: set_clock_rate(2000000); speaker_.set_input_rate(2000000 / SoundGenerator::clock_rate_divider); + speaker_.set_high_frequency_cutoff(7000); std::vector rom_names = {"basic.rom", "os.rom"}; if(target.has_adfs) { diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index cc515ee85..5e9773ab7 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -150,7 +150,6 @@ 4B2A332D1DB86821002876E3 /* OricOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B2A332B1DB86821002876E3 /* OricOptions.xib */; }; 4B2A539F1D117D36003C6002 /* CSAudioQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A53911D117D36003C6002 /* CSAudioQueue.m */; }; 4B2A53A01D117D36003C6002 /* CSMachine.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A53961D117D36003C6002 /* CSMachine.mm */; }; - 4B2AF8691E513FC20027EE29 /* TIATests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2AF8681E513FC20027EE29 /* TIATests.mm */; }; 4B2B3A4B1F9B8FA70062DABF /* Typer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2B3A471F9B8FA70062DABF /* Typer.cpp */; }; 4B2B3A4C1F9B8FA70062DABF /* MemoryFuzzer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2B3A481F9B8FA70062DABF /* MemoryFuzzer.cpp */; }; 4B2BFC5F1D613E0200BA3AA9 /* TapePRG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */; }; @@ -198,7 +197,6 @@ 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 */; }; - 4B50730A1DDFCFDF00C48FBD /* ArrayBuilderTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B5073091DDFCFDF00C48FBD /* ArrayBuilderTests.mm */; }; 4B54C0BC1F8D8E790050900F /* KeyboardMachine.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B54C0BB1F8D8E790050900F /* KeyboardMachine.cpp */; }; 4B54C0BF1F8D8F450050900F /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B54C0BD1F8D8F450050900F /* Keyboard.cpp */; }; 4B54C0C21F8D91CD0050900F /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B54C0C11F8D91CD0050900F /* Keyboard.cpp */; }; @@ -248,6 +246,8 @@ 4B83348A1F5DB94B0097E338 /* IRQDelegatePortHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334891F5DB94B0097E338 /* IRQDelegatePortHandler.cpp */; }; 4B83348C1F5DB99C0097E338 /* 6522Base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B83348B1F5DB99C0097E338 /* 6522Base.cpp */; }; 4B8334951F5E25B60097E338 /* C1540.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334941F5E25B60097E338 /* C1540.cpp */; }; + 4B85322D227793CB00F26553 /* etos192uk.trace.txt.gz in Resources */ = {isa = PBXBuildFile; fileRef = 4B85322C227793CA00F26553 /* etos192uk.trace.txt.gz */; }; + 4B85322F2277ABDE00F26553 /* tos100.trace.txt.gz in Resources */ = {isa = PBXBuildFile; fileRef = 4B85322E2277ABDD00F26553 /* tos100.trace.txt.gz */; }; 4B86E25B1F8C628F006FAA45 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B86E2591F8C628F006FAA45 /* Keyboard.cpp */; }; 4B8805F01DCFC99C003085B1 /* Acorn.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805EE1DCFC99C003085B1 /* Acorn.cpp */; }; 4B8805F41DCFD22A003085B1 /* Commodore.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805F21DCFD22A003085B1 /* Commodore.cpp */; }; @@ -308,6 +308,9 @@ 4B98A1CE1FFADEC500ADF63B /* MSX ROMs in Resources */ = {isa = PBXBuildFile; fileRef = 4B98A1CD1FFADEC400ADF63B /* MSX ROMs */; }; 4B9BE400203A0C0600FFAE60 /* MultiSpeaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B9BE3FE203A0C0600FFAE60 /* MultiSpeaker.cpp */; }; 4B9BE401203A0C0600FFAE60 /* MultiSpeaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B9BE3FE203A0C0600FFAE60 /* MultiSpeaker.cpp */; }; + 4B9F11C92272375400701480 /* qltrace.txt.gz in Resources */ = {isa = PBXBuildFile; fileRef = 4B9F11C82272375400701480 /* qltrace.txt.gz */; }; + 4B9F11CA2272433900701480 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B69FB451C4D950F00B5F0AA /* libz.tbd */; }; + 4B9F11CC22729B3600701480 /* OPCLOGR2.BIN in Resources */ = {isa = PBXBuildFile; fileRef = 4B9F11CB22729B3500701480 /* OPCLOGR2.BIN */; }; 4BA0F68E1EEA0E8400E9489E /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BA0F68C1EEA0E8400E9489E /* ZX8081.cpp */; }; 4BA61EB01D91515900B3C876 /* NSData+StdVector.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BA61EAF1D91515900B3C876 /* NSData+StdVector.mm */; }; 4BA91E1D216D85BA00F79557 /* MasterSystemVDPTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BA91E1C216D85BA00F79557 /* MasterSystemVDPTests.mm */; }; @@ -618,6 +621,7 @@ 4BCF1FA41DADC3DD0039D2E7 /* Oric.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF1FA21DADC3DD0039D2E7 /* Oric.cpp */; }; 4BD191F42191180E0042E144 /* ScanTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD191F22191180E0042E144 /* ScanTarget.cpp */; }; 4BD191F52191180E0042E144 /* ScanTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD191F22191180E0042E144 /* ScanTarget.cpp */; }; + 4BD388882239E198002D14B5 /* 68000Tests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BD388872239E198002D14B5 /* 68000Tests.mm */; }; 4BD3A30B1EE755C800B5B501 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD3A3091EE755C800B5B501 /* Video.cpp */; }; 4BD424DF2193B5340097291A /* TextureTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD424DD2193B5340097291A /* TextureTarget.cpp */; }; 4BD424E02193B5340097291A /* TextureTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD424DD2193B5340097291A /* TextureTarget.cpp */; }; @@ -638,6 +642,7 @@ 4BDB61EB2032806E0048AF91 /* CSAtari2600.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539A1D117D36003C6002 /* CSAtari2600.mm */; }; 4BDB61EC203285AE0048AF91 /* Atari2600OptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FE21F1DA19D7C0090D3CE /* Atari2600OptionsPanel.swift */; }; 4BDDBA991EF3451200347E61 /* Z80MachineCycleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDDBA981EF3451200347E61 /* Z80MachineCycleTests.swift */; }; + 4BE76CF922641ED400ACD6FA /* QLTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BE76CF822641ED300ACD6FA /* QLTests.mm */; }; 4BE7C9181E3D397100A5496D /* TIA.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE7C9161E3D397100A5496D /* TIA.cpp */; }; 4BE9A6B11EDE293000CBCB47 /* zexdoc.com in Resources */ = {isa = PBXBuildFile; fileRef = 4BE9A6B01EDE293000CBCB47 /* zexdoc.com */; }; 4BEA525E1DF33323007E74F2 /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA525D1DF33323007E74F2 /* Tape.cpp */; }; @@ -661,6 +666,9 @@ 4BFDD78C1F7F2DB4008579B9 /* ImplicitSectors.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFDD78B1F7F2DB4008579B9 /* ImplicitSectors.cpp */; }; 4BFE7B871FC39BF100160B38 /* StandardOptions.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFE7B851FC39BF100160B38 /* StandardOptions.cpp */; }; 4BFE7B881FC39D8900160B38 /* StandardOptions.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFE7B851FC39BF100160B38 /* StandardOptions.cpp */; }; + 4BFF1D3922337B0300838EA1 /* 68000Storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFF1D3822337B0300838EA1 /* 68000Storage.cpp */; }; + 4BFF1D3A22337B0300838EA1 /* 68000Storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BFF1D3822337B0300838EA1 /* 68000Storage.cpp */; }; + 4BFF1D3D2235C3C100838EA1 /* EmuTOSTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BFF1D3C2235C3C100838EA1 /* EmuTOSTests.mm */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -869,7 +877,6 @@ 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 = ""; }; - 4B5073091DDFCFDF00C48FBD /* ArrayBuilderTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ArrayBuilderTests.mm; sourceTree = ""; }; 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 = ""; }; 4B54C0BB1F8D8E790050900F /* KeyboardMachine.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = KeyboardMachine.cpp; sourceTree = ""; }; @@ -958,6 +965,9 @@ 4B83348E1F5DBA6E0097E338 /* 6522Storage.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = 6522Storage.hpp; path = Implementation/6522Storage.hpp; sourceTree = ""; }; 4B8334911F5E24FF0097E338 /* C1540Base.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = C1540Base.hpp; path = Implementation/C1540Base.hpp; sourceTree = ""; }; 4B8334941F5E25B60097E338 /* C1540.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = C1540.cpp; path = Implementation/C1540.cpp; sourceTree = ""; }; + 4B85322922778E4200F26553 /* Comparative68000.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Comparative68000.hpp; sourceTree = ""; }; + 4B85322C227793CA00F26553 /* etos192uk.trace.txt.gz */ = {isa = PBXFileReference; lastKnownFileType = archive.gzip; path = etos192uk.trace.txt.gz; sourceTree = ""; }; + 4B85322E2277ABDD00F26553 /* tos100.trace.txt.gz */ = {isa = PBXFileReference; lastKnownFileType = archive.gzip; path = tos100.trace.txt.gz; sourceTree = ""; }; 4B86E2591F8C628F006FAA45 /* Keyboard.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Keyboard.cpp; sourceTree = ""; }; 4B86E25A1F8C628F006FAA45 /* Keyboard.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Keyboard.hpp; sourceTree = ""; }; 4B8805EE1DCFC99C003085B1 /* Acorn.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Acorn.cpp; path = Parsers/Acorn.cpp; sourceTree = ""; }; @@ -1035,6 +1045,8 @@ 4B98A1CD1FFADEC400ADF63B /* MSX ROMs */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "MSX ROMs"; sourceTree = ""; }; 4B9BE3FE203A0C0600FFAE60 /* MultiSpeaker.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MultiSpeaker.cpp; sourceTree = ""; }; 4B9BE3FF203A0C0600FFAE60 /* MultiSpeaker.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MultiSpeaker.hpp; sourceTree = ""; }; + 4B9F11C82272375400701480 /* qltrace.txt.gz */ = {isa = PBXFileReference; lastKnownFileType = archive.gzip; path = qltrace.txt.gz; sourceTree = ""; }; + 4B9F11CB22729B3500701480 /* OPCLOGR2.BIN */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = OPCLOGR2.BIN; path = "68000 Coverage/OPCLOGR2.BIN"; sourceTree = ""; }; 4BA0F68C1EEA0E8400E9489E /* ZX8081.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ZX8081.cpp; path = Data/ZX8081.cpp; sourceTree = ""; }; 4BA0F68D1EEA0E8400E9489E /* ZX8081.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ZX8081.hpp; path = Data/ZX8081.hpp; sourceTree = ""; }; 4BA141C12073100800A31EC9 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = ""; }; @@ -1375,6 +1387,7 @@ 4BD191F22191180E0042E144 /* ScanTarget.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ScanTarget.cpp; sourceTree = ""; }; 4BD191F32191180E0042E144 /* ScanTarget.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ScanTarget.hpp; sourceTree = ""; }; 4BD388411FE34E010042B588 /* 9918Base.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = 9918Base.hpp; path = 9918/Implementation/9918Base.hpp; sourceTree = ""; }; + 4BD388872239E198002D14B5 /* 68000Tests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = 68000Tests.mm; sourceTree = ""; }; 4BD3A3091EE755C800B5B501 /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Video.cpp; path = ZX8081/Video.cpp; sourceTree = ""; }; 4BD3A30A1EE755C800B5B501 /* Video.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Video.hpp; path = ZX8081/Video.hpp; sourceTree = ""; }; 4BD424DD2193B5340097291A /* TextureTarget.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TextureTarget.cpp; sourceTree = ""; }; @@ -1404,6 +1417,7 @@ 4BE3231520532AA7006EF799 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = ""; }; 4BE3231620532BED006EF799 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = ""; }; 4BE3231720532CC0006EF799 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = ""; }; + 4BE76CF822641ED300ACD6FA /* QLTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = QLTests.mm; sourceTree = ""; }; 4BE7C9161E3D397100A5496D /* TIA.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TIA.cpp; sourceTree = ""; }; 4BE7C9171E3D397100A5496D /* TIA.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TIA.hpp; sourceTree = ""; }; 4BE845201F2FF7F100A5EA22 /* CRTC6845.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CRTC6845.hpp; path = 6845/CRTC6845.hpp; sourceTree = ""; }; @@ -1459,6 +1473,11 @@ 4BFDD78B1F7F2DB4008579B9 /* ImplicitSectors.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ImplicitSectors.cpp; sourceTree = ""; }; 4BFE7B851FC39BF100160B38 /* StandardOptions.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = StandardOptions.cpp; sourceTree = ""; }; 4BFE7B861FC39BF100160B38 /* StandardOptions.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = StandardOptions.hpp; sourceTree = ""; }; + 4BFF1D342233778C00838EA1 /* 68000.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000.hpp; sourceTree = ""; }; + 4BFF1D37223379D500838EA1 /* 68000Storage.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000Storage.hpp; sourceTree = ""; }; + 4BFF1D3822337B0300838EA1 /* 68000Storage.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = 68000Storage.cpp; sourceTree = ""; }; + 4BFF1D3B2235714900838EA1 /* 68000Implementation.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = 68000Implementation.hpp; sourceTree = ""; }; + 4BFF1D3C2235C3C100838EA1 /* EmuTOSTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = EmuTOSTests.mm; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -1486,6 +1505,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 4B9F11CA2272433900701480 /* libz.tbd in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1551,13 +1571,16 @@ 4B1414631B588A1100E04248 /* Test Binaries */ = { isa = PBXGroup; children = ( + 4B85322B227793CA00F26553 /* TOS Startup */, 4B9252CD1E74D28200B76AF1 /* Atari ROMs */, 4B44EBF81DC9898E00A7820C /* BCDTEST_beeb */, 4B98A1CD1FFADEC400ADF63B /* MSX ROMs */, 4B018B88211930DE002A3937 /* 65C02_extended_opcodes_test.bin */, 4B44EBF61DC9883B00A7820C /* 6502_functional_test.bin */, 4B44EBF41DC987AE00A7820C /* AllSuiteA.bin */, + 4B9F11CB22729B3500701480 /* OPCLOGR2.BIN */, 4BBF49B41ED2881600AB3669 /* FUSE */, + 4B9F11C72272375400701480 /* QL Startup */, 4BB297E41B587D8300A49093 /* Wolfgang Lorenz 6502 test suite */, 4BE9A6B21EDE294200CBCB47 /* Zexall */, ); @@ -2238,6 +2261,15 @@ name = Implementation; sourceTree = ""; }; + 4B85322B227793CA00F26553 /* TOS Startup */ = { + isa = PBXGroup; + children = ( + 4B85322E2277ABDD00F26553 /* tos100.trace.txt.gz */, + 4B85322C227793CA00F26553 /* etos192uk.trace.txt.gz */, + ); + path = "TOS Startup"; + sourceTree = ""; + }; 4B86E2581F8C628F006FAA45 /* Inputs */ = { isa = PBXGroup; children = ( @@ -2435,6 +2467,14 @@ path = Implementation; sourceTree = ""; }; + 4B9F11C72272375400701480 /* QL Startup */ = { + isa = PBXGroup; + children = ( + 4B9F11C82272375400701480 /* qltrace.txt.gz */, + ); + path = "QL Startup"; + sourceTree = ""; + }; 4BAB62AA1D3272D200DF5BA0 /* Disk */ = { isa = PBXGroup; children = ( @@ -2817,13 +2857,16 @@ 4BB73EB51B587A5100552FC2 /* Clock SignalTests */ = { isa = PBXGroup; children = ( - 4B5073091DDFCFDF00C48FBD /* ArrayBuilderTests.mm */, + 4B85322922778E4200F26553 /* Comparative68000.hpp */, + 4BD388872239E198002D14B5 /* 68000Tests.mm */, 4B924E981E74D22700B76AF1 /* AtariStaticAnalyserTests.mm */, 4BB2A9AE1E13367E001A5C23 /* CRCTests.mm */, + 4BFF1D3C2235C3C100838EA1 /* EmuTOSTests.mm */, 4BA91E1C216D85BA00F79557 /* MasterSystemVDPTests.mm */, 4B98A0601FFADCDE00ADF63B /* MSXStaticAnalyserTests.mm */, 4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */, 4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */, + 4BE76CF822641ED300ACD6FA /* QLTests.mm */, 4B2AF8681E513FC20027EE29 /* TIATests.mm */, 4B1D08051E0F7A1100763741 /* TimeTests.mm */, 4BB73EB81B587A5100552FC2 /* Info.plist */, @@ -2886,11 +2929,12 @@ 4BB73EDD1B587CA500552FC2 /* Processors */ = { isa = PBXGroup; children = ( - 4B1414561B58879D00E04248 /* 6502 */, - 4B77069E1EC9045B0053B588 /* Z80 */, - 4B2C455C1EC9442600FC74DD /* RegisterSizes.hpp */, 4BFCA1211ECBDCAF00AC40C1 /* AllRAMProcessor.cpp */, 4BFCA1221ECBDCAF00AC40C1 /* AllRAMProcessor.hpp */, + 4B2C455C1EC9442600FC74DD /* RegisterSizes.hpp */, + 4B1414561B58879D00E04248 /* 6502 */, + 4BFF1D332233778C00838EA1 /* 68000 */, + 4B77069E1EC9045B0053B588 /* Z80 */, ); name = Processors; path = ../../Processors; @@ -3196,6 +3240,25 @@ path = Utility; sourceTree = ""; }; + 4BFF1D332233778C00838EA1 /* 68000 */ = { + isa = PBXGroup; + children = ( + 4BFF1D342233778C00838EA1 /* 68000.hpp */, + 4BFF1D36223379D500838EA1 /* Implementation */, + ); + path = 68000; + sourceTree = ""; + }; + 4BFF1D36223379D500838EA1 /* Implementation */ = { + isa = PBXGroup; + children = ( + 4BFF1D37223379D500838EA1 /* 68000Storage.hpp */, + 4BFF1D3822337B0300838EA1 /* 68000Storage.cpp */, + 4BFF1D3B2235714900838EA1 /* 68000Implementation.hpp */, + ); + path = Implementation; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -3285,7 +3348,7 @@ }; 4BB73E9D1B587A5100552FC2 = { CreatedOnToolsVersion = 7.0; - LastSwiftMigration = 0900; + LastSwiftMigration = 1020; SystemCapabilities = { com.apple.Sandbox = { enabled = 1; @@ -3294,12 +3357,12 @@ }; 4BB73EB11B587A5100552FC2 = { CreatedOnToolsVersion = 7.0; - LastSwiftMigration = 0900; + LastSwiftMigration = 1020; TestTargetID = 4BB73E9D1B587A5100552FC2; }; 4BB73EBC1B587A5100552FC2 = { CreatedOnToolsVersion = 7.0; - LastSwiftMigration = 0900; + LastSwiftMigration = 1020; TestTargetID = 4BB73E9D1B587A5100552FC2; }; }; @@ -3408,6 +3471,7 @@ 4BB298FB1B587D8400A49093 /* ancb in Resources */, 4BB299431B587D8400A49093 /* dcma in Resources */, 4BB298FD1B587D8400A49093 /* andax in Resources */, + 4B85322D227793CB00F26553 /* etos192uk.trace.txt.gz in Resources */, 4BB299401B587D8400A49093 /* cpya in Resources */, 4BB299BE1B587D8400A49093 /* rraix in Resources */, 4BB299E41B587D8400A49093 /* tayn in Resources */, @@ -3484,6 +3548,7 @@ 4BB2997D1B587D8400A49093 /* ldxay in Resources */, 4BB299D71B587D8400A49093 /* staax in Resources */, 4B98A1CE1FFADEC500ADF63B /* MSX ROMs in Resources */, + 4B9F11CC22729B3600701480 /* OPCLOGR2.BIN in Resources */, 4BB2990C1B587D8400A49093 /* asoax in Resources */, 4BB299191B587D8400A49093 /* bita in Resources */, 4BB2992A1B587D8400A49093 /* cia2ta in Resources */, @@ -3501,6 +3566,7 @@ 4BB299C41B587D8400A49093 /* sbca in Resources */, 4BB298F41B587D8400A49093 /* adcay in Resources */, 4B44EBF51DC987AF00A7820C /* AllSuiteA.bin in Resources */, + 4B85322F2277ABDE00F26553 /* tos100.trace.txt.gz in Resources */, 4BB299C61B587D8400A49093 /* sbcay in Resources */, 4BB299601B587D8400A49093 /* insa in Resources */, 4BB299951B587D8400A49093 /* mmufetch in Resources */, @@ -3602,6 +3668,7 @@ 4BB299A41B587D8400A49093 /* oraz in Resources */, 4BB299611B587D8400A49093 /* insax in Resources */, 4BB299351B587D8400A49093 /* cmpix in Resources */, + 4B9F11C92272375400701480 /* qltrace.txt.gz in Resources */, 4BB299041B587D8400A49093 /* aneb in Resources */, 4BB299BB1B587D8400A49093 /* rraa in Resources */, 4BB299091B587D8400A49093 /* aslz in Resources */, @@ -3687,6 +3754,7 @@ 4B05401F219D1618001BF69C /* ScanTarget.cpp in Sources */, 4B055AE81FAE9B7B0060FFFF /* FIRFilter.cpp in Sources */, 4B055A901FAE85A90060FFFF /* TimedEventLoop.cpp in Sources */, + 4BFF1D3A22337B0300838EA1 /* 68000Storage.cpp in Sources */, 4B055AC71FAE9AEE0060FFFF /* TIA.cpp in Sources */, 4B055AD21FAE9B0B0060FFFF /* Keyboard.cpp in Sources */, 4B89451B201967B4007DE474 /* ConfidenceSummary.cpp in Sources */, @@ -3967,6 +4035,7 @@ 4B8334821F5D9FF70097E338 /* PartialMachineCycle.cpp in Sources */, 4BD424E72193B5830097291A /* Rectangle.cpp in Sources */, 4B1B88C0202E3DB200B67DFF /* MultiConfigurable.cpp in Sources */, + 4BFF1D3922337B0300838EA1 /* 68000Storage.cpp in Sources */, 4B54C0BC1F8D8E790050900F /* KeyboardMachine.cpp in Sources */, 4BB73EA21B587A5100552FC2 /* AppDelegate.swift in Sources */, 4B894534201967B4007DE474 /* AddressMapper.cpp in Sources */, @@ -3979,6 +4048,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 4BFF1D3D2235C3C100838EA1 /* EmuTOSTests.mm in Sources */, 4B1E85811D176468001EF87D /* 6532Tests.swift in Sources */, 4BDDBA991EF3451200347E61 /* Z80MachineCycleTests.swift in Sources */, 4B98A05F1FFAD62400ADF63B /* CSROMFetcher.mm in Sources */, @@ -3989,18 +4059,18 @@ 4B7BC7F51F58F27800D1B1B4 /* 6502AllRAM.cpp in Sources */, 4B08A2751EE35D56008B7065 /* Z80InterruptTests.swift in Sources */, 4BFCA1241ECBDCB400AC40C1 /* AllRAMProcessor.cpp in Sources */, - 4B50730A1DDFCFDF00C48FBD /* ArrayBuilderTests.mm in Sources */, 4BBF49AF1ED2880200AB3669 /* FUSETests.swift in Sources */, - 4B2AF8691E513FC20027EE29 /* TIATests.mm in Sources */, 4B3BA0CE1D318B44005DD7A7 /* C1540Bridge.mm in Sources */, 4B3BA0D11D318B44005DD7A7 /* TestMachine6502.mm in Sources */, 4B92EACA1B7C112B00246143 /* 6502TimingTests.swift in Sources */, 4BB73EB71B587A5100552FC2 /* AllSuiteATests.swift in Sources */, 4B01A6881F22F0DB001FD6E3 /* Z80MemptrTests.swift in Sources */, 4B121F9B1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm in Sources */, + 4BD388882239E198002D14B5 /* 68000Tests.mm in Sources */, 4BA91E1D216D85BA00F79557 /* MasterSystemVDPTests.mm in Sources */, 4B98A0611FFADCDE00ADF63B /* MSXStaticAnalyserTests.mm in Sources */, 4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */, + 4BE76CF922641ED400ACD6FA /* QLTests.mm in Sources */, 4B3BA0CF1D318B44005DD7A7 /* MOS6522Bridge.mm in Sources */, 4BC751B21D157E61006C31D9 /* 6522Tests.swift in Sources */, 4BFCA12B1ECBE7C400AC40C1 /* ZexallTests.swift in Sources */, @@ -4313,8 +4383,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Clock Signal/ClockSignal-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -4355,8 +4424,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "TH.Clock-Signal"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Clock Signal/ClockSignal-Bridging-Header.h"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; }; name = Release; }; @@ -4372,8 +4440,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Clock SignalTests/Bridges/Clock SignalTests-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Clock Signal.app/Contents/MacOS/Clock Signal"; }; name = Debug; @@ -4389,8 +4456,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "TH.Clock-SignalTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Clock SignalTests/Bridges/Clock SignalTests-Bridging-Header.h"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Clock Signal.app/Contents/MacOS/Clock Signal"; }; name = Release; @@ -4403,8 +4469,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "TH.Clock-SignalUITests"; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; TEST_TARGET_NAME = "Clock Signal"; USES_XCTRUNNER = YES; }; @@ -4418,8 +4483,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "TH.Clock-SignalUITests"; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; TEST_TARGET_NAME = "Clock Signal"; USES_XCTRUNNER = YES; }; diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme index ffcbbd1c6..9c2dca9ce 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme +++ b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme @@ -26,6 +26,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + disableMainThreadChecker = "YES" codeCoverageEnabled = "YES" shouldUseLaunchSchemeArgsEnv = "YES"> diff --git a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift index 733701706..f2f724865 100644 --- a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift @@ -49,7 +49,7 @@ class MachineDocument: fileprivate var bestEffortUpdater: CSBestEffortUpdater? override var windowNibName: NSNib.Name? { - return NSNib.Name(rawValue: "MachineDocument") + return "MachineDocument" } override func windowControllerDidLoadNib(_ aController: NSWindowController) { @@ -64,7 +64,7 @@ class MachineDocument: func windowDidUpdate(_ notification: Notification) { if self.shouldShowNewMachinePanel { self.shouldShowNewMachinePanel = false - Bundle.main.loadNibNamed(NSNib.Name(rawValue: "MachinePicker"), owner: self, topLevelObjects: nil) + Bundle.main.loadNibNamed("MachinePicker", owner: self, topLevelObjects: nil) self.machinePicker?.establishStoredOptions() self.windowControllers[0].window?.beginSheet(self.machinePickerPanel!, completionHandler: nil) } @@ -80,7 +80,7 @@ class MachineDocument: // attach an options panel if one is available if let optionsPanelNibName = self.optionsPanelNibName { - Bundle.main.loadNibNamed(NSNib.Name(rawValue: optionsPanelNibName), owner: self, topLevelObjects: nil) + Bundle.main.loadNibNamed(optionsPanelNibName, owner: self, topLevelObjects: nil) self.optionsPanel.machine = machine self.optionsPanel?.establishStoredOptions() showOptions(self) @@ -372,7 +372,7 @@ class MachineDocument: func setupActivityDisplay() { var leds = machine.leds if leds.count > 0 { - Bundle.main.loadNibNamed(NSNib.Name(rawValue: "Activity"), owner: self, topLevelObjects: nil) + Bundle.main.loadNibNamed("Activity", owner: self, topLevelObjects: nil) showActivity(nil) // Inspect the activity panel for indicators. diff --git a/OSBindings/Mac/Clock SignalTests/68000 Coverage/OPCLOGR2.BIN b/OSBindings/Mac/Clock SignalTests/68000 Coverage/OPCLOGR2.BIN new file mode 100755 index 000000000..bd8475049 Binary files /dev/null and b/OSBindings/Mac/Clock SignalTests/68000 Coverage/OPCLOGR2.BIN differ diff --git a/OSBindings/Mac/Clock SignalTests/68000 Coverage/readme.txt b/OSBindings/Mac/Clock SignalTests/68000 Coverage/readme.txt new file mode 100644 index 000000000..771d9a011 --- /dev/null +++ b/OSBindings/Mac/Clock SignalTests/68000 Coverage/readme.txt @@ -0,0 +1,5 @@ +This file provides a record of which opcodes are valid and which are invalid on a +real 68000. It was generated by AtariZoll and made available via +http://www.atari-forum.com/viewtopic.php?f=68&t=26820 . + +No licence was specified. \ No newline at end of file diff --git a/OSBindings/Mac/Clock SignalTests/68000Tests.mm b/OSBindings/Mac/Clock SignalTests/68000Tests.mm new file mode 100644 index 000000000..9d8fa3f73 --- /dev/null +++ b/OSBindings/Mac/Clock SignalTests/68000Tests.mm @@ -0,0 +1,518 @@ +// +// 68000Tests.m +// Clock SignalTests +// +// Created by Thomas Harte on 13/03/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#import + +#include +#include + +#include "68000.hpp" + +/*! + Provides a 68000 with 64kb of RAM in its low address space; + /RESET will put the supervisor stack pointer at 0xFFFF and + begin execution at 0x0400. +*/ +class RAM68000: public CPU::MC68000::BusHandler { + public: + RAM68000() : m68000_(*this) { + ram_.resize(256*1024); + + // Setup the /RESET vector. + ram_[0] = 0; + ram_[1] = 0xffff; + ram_[2] = 0; + ram_[3] = 0x1000; + } + + void set_program(const std::vector &program) { + memcpy(&ram_[0x1000 >> 1], program.data(), program.size() * sizeof(uint16_t)); + } + + void will_perform(uint32_t address, uint16_t opcode) { + --instructions_remaining_; + } + + void run_for_instructions(int count) { + instructions_remaining_ = count; + while(instructions_remaining_) { + run_for(HalfCycles(2)); + } + } + + void run_for(HalfCycles cycles) { + m68000_.run_for(cycles); + } + + uint16_t *ram_at(uint32_t address) { + return &ram_[address >> 1]; + } + + HalfCycles perform_bus_operation(const CPU::MC68000::Microcycle &cycle, int is_supervisor) { + const uint32_t word_address = cycle.word_address(); + + using Microcycle = CPU::MC68000::Microcycle; + if(cycle.data_select_active()) { + if(cycle.operation & Microcycle::InterruptAcknowledge) { + cycle.value->halves.low = 10; + } else { + switch(cycle.operation & (Microcycle::SelectWord | Microcycle::SelectByte | Microcycle::Read)) { + default: break; + + case Microcycle::SelectWord | Microcycle::Read: + cycle.value->full = ram_[word_address]; + printf("r %04x from %08x \n", cycle.value->full, *cycle.address); + break; + case Microcycle::SelectByte | Microcycle::Read: + cycle.value->halves.low = ram_[word_address] >> cycle.byte_shift(); + break; + case Microcycle::SelectWord: + printf("w %08x of %04x\n", *cycle.address, cycle.value->full); + ram_[word_address] = cycle.value->full; + break; + case Microcycle::SelectByte: + ram_[word_address] = (cycle.value->full & cycle.byte_mask()) | (ram_[word_address] & (0xffff ^ cycle.byte_mask())); + break; + } + } + } + + return HalfCycles(0); + } + + CPU::MC68000::Processor::State get_processor_state() { + return m68000_.get_state(); + } + + void set_processor_state(const CPU::MC68000::Processor::State &state) { + m68000_.set_state(state); + } + + CPU::MC68000::Processor &processor() { + return m68000_; + } + + private: + CPU::MC68000::Processor m68000_; + std::vector ram_; + int instructions_remaining_; +}; + +class CPU::MC68000::ProcessorStorageTests { + public: + ProcessorStorageTests(const CPU::MC68000::ProcessorStorage &storage, const char *coverage_file_name) { + false_valids_ = [NSMutableSet set]; + false_invalids_ = [NSMutableSet set]; + + FILE *source = fopen(coverage_file_name, "rb"); + + // The file format here is [2 bytes opcode][2 ASCII characters:VA for valid, IN for invalid]... + // The file terminates with four additional bytes that begin with two zero bytes. + // + // The version of the file I grabbed seems to cover all opcodes, making their enumeration + // arguably redundant; the code below nevertheless uses the codes from the file. + // + // Similarly, I'm testing for exactly the strings VA or IN to ensure no further + // types creep into any updated version of the table that I then deal with incorrectly. + uint16_t last_observed = 0; + while(true) { + // Fetch opcode number. + uint16_t next_opcode = fgetc(source) << 8; + next_opcode |= fgetc(source); + if(next_opcode < last_observed) break; + last_observed = next_opcode; + + // Determine whether it's meant to be valid. + char type[3]; + type[0] = fgetc(source); + type[1] = fgetc(source); + type[2] = '\0'; + + // TEMPORARY: factor out A- and F-line exceptions. + if((next_opcode&0xf000) == 0xa000) continue; + if((next_opcode&0xf000) == 0xf000) continue; + + if(!strcmp(type, "VA")) { + // Test for validity. + if(!storage.instructions[next_opcode].micro_operations) { + [false_invalids_ addObject:@(next_opcode)]; + } + continue; + } + + if(!strcmp(type, "IN")) { + // Test for invalidity. + if(storage.instructions[next_opcode].micro_operations) { + [false_valids_ addObject:@(next_opcode)]; + } + continue; + } + + assert(false); + } + + fclose(source); + } + + NSSet *false_valids() const { + return false_valids_; + } + + NSSet *false_invalids() const { + return false_invalids_; + } + + private: + NSMutableSet *false_invalids_; + NSMutableSet *false_valids_; +}; + +@interface NSSet (CSHexDump) + +- (NSString *)hexDump; + +@end + +@implementation NSSet (CSHexDump) + +- (NSString *)hexDump { + NSMutableArray *components = [NSMutableArray array]; + + for(NSNumber *number in [[self allObjects] sortedArrayUsingSelector:@selector(compare:)]) { + [components addObject:[NSString stringWithFormat:@"%04x", number.intValue]]; + } + + return [components componentsJoinedByString:@" "]; +} + +@end + + +@interface M68000Tests : XCTestCase +@end + +@implementation M68000Tests { + std::unique_ptr _machine; +} + +- (void)setUp { + _machine.reset(new RAM68000()); +} + +- (void)tearDown { + _machine.reset(); +} + +- (void)testABCD { + for(int d = 0; d < 100; ++d) { + _machine.reset(new RAM68000()); + _machine->set_program({ + 0xc100 // ABCD D0, D0 + }); + + auto state = _machine->get_processor_state(); + const uint8_t bcd_d = ((d / 10) * 16) + (d % 10); + state.data[0] = bcd_d; + _machine->set_processor_state(state); + + _machine->run_for_instructions(1); + + state = _machine->get_processor_state(); + const uint8_t double_d = (d * 2) % 100; + const uint8_t bcd_double_d = ((double_d / 10) * 16) + (double_d % 10); + XCTAssert(state.data[0] == bcd_double_d, "%02x + %02x = %02x; should equal %02x", bcd_d, bcd_d, state.data[0], bcd_double_d); + } +} + +- (void)testDivideByZero { + _machine->set_program({ + 0x7000, // MOVE #0, D0; location 0x400 + 0x3200, // MOVE D0, D1; location 0x402 + + 0x82C0, // DIVU; location 0x404 + + /* Next instruction would be at 0x406 */ + }); + + auto state = _machine->get_processor_state(); + state.supervisor_stack_pointer = 0x1000; + _machine->set_processor_state(state); + + _machine->run_for_instructions(4); + state = _machine->get_processor_state(); + + XCTAssert(state.supervisor_stack_pointer == 0x1000 - 6, @"Exception information should have been pushed to stack."); + + const uint16_t *stack_top = _machine->ram_at(state.supervisor_stack_pointer); + XCTAssert(stack_top[1] == 0x0000 && stack_top[2] == 0x1006, @"Return address should point to instruction after DIVU."); +} + +- (void)testMOVE { + _machine->set_program({ + 0x303c, 0xfb2e, // MOVE #fb2e, D0 + 0x3200, // MOVE D0, D1 + + 0x3040, // MOVEA D0, A0 + 0x3278, 0x1000, // MOVEA.w (0x1000), A1 + + 0x387c, 0x1000, // MOVE #$1000, A4 + 0x2414, // MOVE.l (A4), D2 + }); + + // run_for_instructions technically runs up to the next instruction + // fetch; therefore run for '1' to get past the implied RESET. + _machine->run_for_instructions(1); + + // Perform MOVE #fb2e, D0 + _machine->run_for_instructions(1); + auto state = _machine->get_processor_state(); + XCTAssert(state.data[0] == 0xfb2e); + + // Perform MOVE D0, D1 + _machine->run_for_instructions(1); + state = _machine->get_processor_state(); + XCTAssert(state.data[1] == 0xfb2e); + + // Perform MOVEA D0, A0 + _machine->run_for_instructions(1); + state = _machine->get_processor_state(); + XCTAssert(state.address[0] == 0xfffffb2e, "A0 was %08x instead of 0xfffffb2e", state.address[0]); + + // Perform MOVEA.w (0x1000), A1 + _machine->run_for_instructions(1); + state = _machine->get_processor_state(); + XCTAssert(state.address[1] == 0x0000303c, "A1 was %08x instead of 0x0000303c", state.address[1]); + + // Perform MOVE #$400, A4; MOVE.l (A4), D2 + _machine->run_for_instructions(2); + state = _machine->get_processor_state(); + XCTAssert(state.address[4] == 0x1000, "A4 was %08x instead of 0x00001000", state.address[4]); + XCTAssert(state.data[2] == 0x303cfb2e, "D2 was %08x instead of 0x303cfb2e", state.data[2]); +} + +- (void)testVectoredInterrupt { + _machine->set_program({ + 0x46fc, 0x2000, // MOVE.w #$2000, SR + 0x4e71, // NOP + 0x4e71, // NOP + 0x4e71, // NOP + 0x4e71, // NOP + 0x4e71, // NOP + }); + + // Set the vector that will be supplied back to the start of the + // program; this will ensure no further exceptions following + // the interrupt. + const auto vector = _machine->ram_at(40); + vector[0] = 0x0000; + vector[1] = 0x1004; + + _machine->run_for_instructions(3); + _machine->processor().set_interrupt_level(1); + _machine->run_for_instructions(1); + + const auto state = _machine->processor().get_state(); + XCTAssert(state.program_counter == 0x1008); // i.e. the interrupt happened, the instruction performed was the one at 1004, and therefore + // by the wonders of prefetch the program counter is now at 1008. +} + +- (void)testOpcodeCoverage { + // Perform an audit of implemented instructions. + CPU::MC68000::ProcessorStorageTests storage_tests( + _machine->processor(), + [[NSBundle bundleForClass:[self class]] pathForResource:@"OPCLOGR2" ofType:@"BIN"].UTF8String + ); + + // This is a list of instructions nominated as valid with OPCLOGR2.BIN but with no obvious decoding — + // the disassemblers I tried couldn't figure them out, and I didn't spot them anywhere in the PRM. + NSSet *const undecodables = [NSSet setWithArray:@[ + // These look like malformed MOVEs. + @(0x2e7d), @(0x2e7e), @(0x2e7f), @(0x2efd), @(0x2efe), @(0x2eff), @(0x2f7d), @(0x2f7e), + @(0x2f7f), @(0x2fc0), @(0x2fc1), @(0x2fc2), @(0x2fc3), @(0x2fc4), @(0x2fc5), @(0x2fc6), + @(0x2fc7), @(0x2fc8), @(0x2fc9), @(0x2fca), @(0x2fcb), @(0x2fcc), @(0x2fcd), @(0x2fce), + @(0x2fcf), @(0x2fd0), @(0x2fd1), @(0x2fd2), @(0x2fd3), @(0x2fd4), @(0x2fd5), @(0x2fd6), + @(0x2fd7), @(0x2fd8), @(0x2fd9), @(0x2fda), @(0x2fdb), @(0x2fdc), @(0x2fdd), @(0x2fde), + @(0x2fdf), @(0x2fe0), @(0x2fe1), @(0x2fe2), @(0x2fe3), @(0x2fe4), @(0x2fe5), @(0x2fe6), + @(0x2fe7), @(0x2fe8), @(0x2fe9), @(0x2fea), @(0x2feb), @(0x2fec), @(0x2fed), @(0x2fee), + @(0x2fef), @(0x2ff0), @(0x2ff1), @(0x2ff2), @(0x2ff3), @(0x2ff4), @(0x2ff5), @(0x2ff6), + @(0x2ff7), @(0x2ff8), @(0x2ff9), @(0x2ffa), @(0x2ffb), @(0x2ffc), @(0x2ffd), @(0x2ffe), + @(0x2fff), + + @(0x3e7d), @(0x3e7e), @(0x3e7f), @(0x3efd), @(0x3efe), @(0x3eff), @(0x3f7d), @(0x3f7e), + @(0x3f7f), @(0x3fc0), @(0x3fc1), @(0x3fc2), @(0x3fc3), @(0x3fc4), @(0x3fc5), @(0x3fc6), + @(0x3fc7), @(0x3fc8), @(0x3fc9), @(0x3fca), @(0x3fcb), @(0x3fcc), @(0x3fcd), @(0x3fce), + @(0x3fcf), @(0x3fd0), @(0x3fd1), @(0x3fd2), @(0x3fd3), @(0x3fd4), @(0x3fd5), @(0x3fd6), + @(0x3fd7), @(0x3fd8), @(0x3fd9), @(0x3fda), @(0x3fdb), @(0x3fdc), @(0x3fdd), @(0x3fde), + @(0x3fdf), @(0x3fe0), @(0x3fe1), @(0x3fe2), @(0x3fe3), @(0x3fe4), @(0x3fe5), @(0x3fe6), + @(0x3fe7), @(0x3fe8), @(0x3fe9), @(0x3fea), @(0x3feb), @(0x3fec), @(0x3fed), @(0x3fee), + @(0x3fef), @(0x3ff0), @(0x3ff1), @(0x3ff2), @(0x3ff3), @(0x3ff4), @(0x3ff5), @(0x3ff6), + @(0x3ff7), @(0x3ff8), @(0x3ff9), @(0x3ffa), @(0x3ffb), @(0x3ffc), @(0x3ffd), @(0x3ffe), + @(0x3fff), + + @(0x46c8), @(0x46c9), @(0x46ca), @(0x46cb), @(0x46cc), @(0x46cd), @(0x46ce), @(0x46cf), + @(0x46fd), @(0x46fe), @(0x46ff), @(0x47c0), @(0x47c1), @(0x47c2), @(0x47c3), @(0x47c4), + @(0x47c5), @(0x47c6), @(0x47c7), @(0x47c8), @(0x47c9), @(0x47ca), @(0x47cb), @(0x47cc), + @(0x47cd), @(0x47ce), @(0x47cf), @(0x47d8), @(0x47d9), @(0x47da), @(0x47db), @(0x47dc), + @(0x47dd), @(0x47de), @(0x47df), @(0x47e0), @(0x47e1), @(0x47e2), @(0x47e3), @(0x47e4), + @(0x47e5), @(0x47e6), @(0x47e7), @(0x47fc), @(0x47fd), @(0x47fe), @(0x47ff), @(0x4e80), + @(0x4e81), @(0x4e82), @(0x4e83), @(0x4e84), @(0x4e85), @(0x4e86), @(0x4e87), @(0x4e88), + @(0x4e89), @(0x4e8a), @(0x4e8b), @(0x4e8c), @(0x4e8d), @(0x4e8e), @(0x4e8f), @(0x4e98), + @(0x4e99), @(0x4e9a), @(0x4e9b), @(0x4e9c), @(0x4e9d), @(0x4e9e), @(0x4e9f), @(0x4ea0), + @(0x4ea1), @(0x4ea2), @(0x4ea3), @(0x4ea4), @(0x4ea5), @(0x4ea6), @(0x4ea7), @(0x4ebc), + @(0x4ebd), @(0x4ebe), @(0x4ebf), @(0x4ec0), @(0x4ec1), @(0x4ec2), @(0x4ec3), @(0x4ec4), + @(0x4ec5), @(0x4ec6), @(0x4ec7), @(0x4ec8), @(0x4ec9), @(0x4eca), @(0x4ecb), @(0x4ecc), + @(0x4ecd), @(0x4ece), @(0x4ecf), @(0x4ed8), @(0x4ed9), @(0x4eda), @(0x4edb), @(0x4edc), + @(0x4edd), @(0x4ede), @(0x4edf), @(0x4ee0), @(0x4ee1), @(0x4ee2), @(0x4ee3), @(0x4ee4), + @(0x4ee5), @(0x4ee6), @(0x4ee7), @(0x4efc), @(0x4efd), @(0x4efe), @(0x4eff), @(0x4f88), + @(0x4f89), @(0x4f8a), @(0x4f8b), @(0x4f8c), @(0x4f8d), @(0x4f8e), @(0x4f8f), @(0x4fbd), + @(0x4fbe), @(0x4fbf), @(0x4fc0), @(0x4fc1), @(0x4fc2), @(0x4fc3), @(0x4fc4), @(0x4fc5), + @(0x4fc6), @(0x4fc7), @(0x4fc8), @(0x4fc9), @(0x4fca), @(0x4fcb), @(0x4fcc), @(0x4fcd), + @(0x4fce), @(0x4fcf), @(0x4fd8), @(0x4fd9), @(0x4fda), @(0x4fdb), @(0x4fdc), @(0x4fdd), + @(0x4fde), @(0x4fdf), @(0x4fe0), @(0x4fe1), @(0x4fe2), @(0x4fe3), @(0x4fe4), @(0x4fe5), + @(0x4fe6), @(0x4fe7), @(0x4ffc), @(0x4ffd), @(0x4ffe), @(0x4fff), + + @(0x50fa), @(0x50fb), @(0x50fc), @(0x50fd), @(0x50fe), @(0x50ff), @(0x51fa), @(0x51fb), + @(0x51fc), @(0x51fd), @(0x51fe), @(0x51ff), @(0x52fa), @(0x52fb), @(0x52fc), @(0x52fd), + @(0x52fe), @(0x52ff), @(0x53fa), @(0x53fb), @(0x53fc), @(0x53fd), @(0x53fe), @(0x53ff), + @(0x54fa), @(0x54fb), @(0x54fc), @(0x54fd), @(0x54fe), @(0x54ff), @(0x55fa), @(0x55fb), + @(0x55fc), @(0x55fd), @(0x55fe), @(0x55ff), @(0x56fa), @(0x56fb), @(0x56fc), @(0x56fd), + @(0x56fe), @(0x56ff), @(0x57fa), @(0x57fb), @(0x57fc), @(0x57fd), @(0x57fe), @(0x57ff), + @(0x58fa), @(0x58fb), @(0x58fc), @(0x58fd), @(0x58fe), @(0x58ff), @(0x59fa), @(0x59fb), + @(0x59fc), @(0x59fd), @(0x59fe), @(0x59ff), @(0x5afa), @(0x5afb), @(0x5afc), @(0x5afd), + @(0x5afe), @(0x5aff), @(0x5bfa), @(0x5bfb), @(0x5bfc), @(0x5bfd), @(0x5bfe), @(0x5bff), + @(0x5cfa), @(0x5cfb), @(0x5cfc), @(0x5cfd), @(0x5cfe), @(0x5cff), @(0x5dfa), @(0x5dfb), + @(0x5dfc), @(0x5dfd), @(0x5dfe), @(0x5dff), @(0x5eba), @(0x5ebb), @(0x5ebc), @(0x5ebd), + @(0x5ebe), @(0x5ebf), @(0x5efa), @(0x5efb), @(0x5efc), @(0x5efd), @(0x5efe), @(0x5eff), + @(0x5fba), @(0x5fbb), @(0x5fbc), @(0x5fbd), @(0x5fbe), @(0x5fbf), @(0x5ffa), @(0x5ffb), + @(0x5ffc), @(0x5ffd), @(0x5ffe), @(0x5fff), + + // These are almost MOVEQs if only bit 8 weren't set. + @(0x71c8), @(0x71c9), @(0x71ca), @(0x71cb), @(0x71cc), @(0x71cd), @(0x71ce), @(0x71cf), + @(0x71d8), @(0x71d9), @(0x71da), @(0x71db), @(0x71dc), @(0x71dd), @(0x71de), @(0x71df), + @(0x71e8), @(0x71e9), @(0x71ea), @(0x71eb), @(0x71ec), @(0x71ed), @(0x71ee), @(0x71ef), + @(0x71f8), @(0x71f9), @(0x71fa), @(0x71fb), @(0x71fc), @(0x71fd), @(0x71fe), @(0x71ff), + @(0x73c8), @(0x73c9), @(0x73ca), @(0x73cb), @(0x73cc), @(0x73cd), @(0x73ce), @(0x73cf), + @(0x73d8), @(0x73d9), @(0x73da), @(0x73db), @(0x73dc), @(0x73dd), @(0x73de), @(0x73df), + @(0x73e8), @(0x73e9), @(0x73ea), @(0x73eb), @(0x73ec), @(0x73ed), @(0x73ee), @(0x73ef), + @(0x73f8), @(0x73f9), @(0x73fa), @(0x73fb), @(0x73fc), @(0x73fd), @(0x73fe), @(0x73ff), + @(0x75c8), @(0x75c9), @(0x75ca), @(0x75cb), @(0x75cc), @(0x75cd), @(0x75ce), @(0x75cf), + @(0x75d8), @(0x75d9), @(0x75da), @(0x75db), @(0x75dc), @(0x75dd), @(0x75de), @(0x75df), + @(0x75e8), @(0x75e9), @(0x75ea), @(0x75eb), @(0x75ec), @(0x75ed), @(0x75ee), @(0x75ef), + @(0x75f8), @(0x75f9), @(0x75fa), @(0x75fb), @(0x75fc), @(0x75fd), @(0x75fe), @(0x75ff), + @(0x77c0), @(0x77c1), @(0x77c2), @(0x77c3), @(0x77c4), @(0x77c5), @(0x77c6), @(0x77c7), + @(0x77c8), @(0x77c9), @(0x77ca), @(0x77cb), @(0x77cc), @(0x77cd), @(0x77ce), @(0x77cf), + @(0x77d0), @(0x77d1), @(0x77d2), @(0x77d3), @(0x77d4), @(0x77d5), @(0x77d6), @(0x77d7), + @(0x77d8), @(0x77d9), @(0x77da), @(0x77db), @(0x77dc), @(0x77dd), @(0x77de), @(0x77df), + @(0x77e0), @(0x77e1), @(0x77e2), @(0x77e3), @(0x77e4), @(0x77e5), @(0x77e6), @(0x77e7), + @(0x77e8), @(0x77e9), @(0x77ea), @(0x77eb), @(0x77ec), @(0x77ed), @(0x77ee), @(0x77ef), + @(0x77f0), @(0x77f1), @(0x77f2), @(0x77f3), @(0x77f4), @(0x77f5), @(0x77f6), @(0x77f7), + @(0x77f8), @(0x77f9), @(0x77fa), @(0x77fb), @(0x77fc), @(0x77fd), @(0x77fe), @(0x77ff), + @(0x79c8), @(0x79c9), @(0x79ca), @(0x79cb), @(0x79cc), @(0x79cd), @(0x79ce), @(0x79cf), + @(0x79d8), @(0x79d9), @(0x79da), @(0x79db), @(0x79dc), @(0x79dd), @(0x79de), @(0x79df), + @(0x79e8), @(0x79e9), @(0x79ea), @(0x79eb), @(0x79ec), @(0x79ed), @(0x79ee), @(0x79ef), + @(0x79f8), @(0x79f9), @(0x79fa), @(0x79fb), @(0x79fc), @(0x79fd), @(0x79fe), @(0x79ff), + @(0x7bc8), @(0x7bc9), @(0x7bca), @(0x7bcb), @(0x7bcc), @(0x7bcd), @(0x7bce), @(0x7bcf), + @(0x7bd8), @(0x7bd9), @(0x7bda), @(0x7bdb), @(0x7bdc), @(0x7bdd), @(0x7bde), @(0x7bdf), + @(0x7be8), @(0x7be9), @(0x7bea), @(0x7beb), @(0x7bec), @(0x7bed), @(0x7bee), @(0x7bef), + @(0x7bf8), @(0x7bf9), @(0x7bfa), @(0x7bfb), @(0x7bfc), @(0x7bfd), @(0x7bfe), @(0x7bff), + @(0x7dc8), @(0x7dc9), @(0x7dca), @(0x7dcb), @(0x7dcc), @(0x7dcd), @(0x7dce), @(0x7dcf), + @(0x7dd8), @(0x7dd9), @(0x7dda), @(0x7ddb), @(0x7ddc), @(0x7ddd), @(0x7dde), @(0x7ddf), + @(0x7de8), @(0x7de9), @(0x7dea), @(0x7deb), @(0x7dec), @(0x7ded), @(0x7dee), @(0x7def), + @(0x7df8), @(0x7df9), @(0x7dfa), @(0x7dfb), @(0x7dfc), @(0x7dfd), @(0x7dfe), @(0x7dff), + @(0x7f40), @(0x7f41), @(0x7f42), @(0x7f43), @(0x7f44), @(0x7f45), @(0x7f46), @(0x7f47), + @(0x7f48), @(0x7f49), @(0x7f4a), @(0x7f4b), @(0x7f4c), @(0x7f4d), @(0x7f4e), @(0x7f4f), + @(0x7f50), @(0x7f51), @(0x7f52), @(0x7f53), @(0x7f54), @(0x7f55), @(0x7f56), @(0x7f57), + @(0x7f58), @(0x7f59), @(0x7f5a), @(0x7f5b), @(0x7f5c), @(0x7f5d), @(0x7f5e), @(0x7f5f), + @(0x7f60), @(0x7f61), @(0x7f62), @(0x7f63), @(0x7f64), @(0x7f65), @(0x7f66), @(0x7f67), + @(0x7f68), @(0x7f69), @(0x7f6a), @(0x7f6b), @(0x7f6c), @(0x7f6d), @(0x7f6e), @(0x7f6f), + @(0x7f70), @(0x7f71), @(0x7f72), @(0x7f73), @(0x7f74), @(0x7f75), @(0x7f76), @(0x7f77), + @(0x7f78), @(0x7f79), @(0x7f7a), @(0x7f7b), @(0x7f7c), @(0x7f7d), @(0x7f7e), @(0x7f7f), + @(0x7f80), @(0x7f81), @(0x7f82), @(0x7f83), @(0x7f84), @(0x7f85), @(0x7f86), @(0x7f87), + @(0x7f88), @(0x7f89), @(0x7f8a), @(0x7f8b), @(0x7f8c), @(0x7f8d), @(0x7f8e), @(0x7f8f), + @(0x7f90), @(0x7f91), @(0x7f92), @(0x7f93), @(0x7f94), @(0x7f95), @(0x7f96), @(0x7f97), + @(0x7f98), @(0x7f99), @(0x7f9a), @(0x7f9b), @(0x7f9c), @(0x7f9d), @(0x7f9e), @(0x7f9f), + @(0x7fa0), @(0x7fa1), @(0x7fa2), @(0x7fa3), @(0x7fa4), @(0x7fa5), @(0x7fa6), @(0x7fa7), + @(0x7fa8), @(0x7fa9), @(0x7faa), @(0x7fab), @(0x7fac), @(0x7fad), @(0x7fae), @(0x7faf), + @(0x7fb0), @(0x7fb1), @(0x7fb2), @(0x7fb3), @(0x7fb4), @(0x7fb5), @(0x7fb6), @(0x7fb7), + @(0x7fb8), @(0x7fb9), @(0x7fba), @(0x7fbb), @(0x7fbc), @(0x7fbd), @(0x7fbe), @(0x7fbf), + @(0x7fc0), @(0x7fc1), @(0x7fc2), @(0x7fc3), @(0x7fc4), @(0x7fc5), @(0x7fc6), @(0x7fc7), + @(0x7fc8), @(0x7fc9), @(0x7fca), @(0x7fcb), @(0x7fcc), @(0x7fcd), @(0x7fce), @(0x7fcf), + @(0x7fd0), @(0x7fd1), @(0x7fd2), @(0x7fd3), @(0x7fd4), @(0x7fd5), @(0x7fd6), @(0x7fd7), + @(0x7fd8), @(0x7fd9), @(0x7fda), @(0x7fdb), @(0x7fdc), @(0x7fdd), @(0x7fde), @(0x7fdf), + @(0x7fe0), @(0x7fe1), @(0x7fe2), @(0x7fe3), @(0x7fe4), @(0x7fe5), @(0x7fe6), @(0x7fe7), + @(0x7fe8), @(0x7fe9), @(0x7fea), @(0x7feb), @(0x7fec), @(0x7fed), @(0x7fee), @(0x7fef), + @(0x7ff0), @(0x7ff1), @(0x7ff2), @(0x7ff3), @(0x7ff4), @(0x7ff5), @(0x7ff6), @(0x7ff7), + @(0x7ff8), @(0x7ff9), @(0x7ffa), @(0x7ffb), @(0x7ffc), @(0x7ffd), @(0x7ffe), @(0x7fff), + + @(0xbe7d), @(0xbe7e), @(0xbe7f), @(0xbefd), @(0xbefe), @(0xbeff), @(0xbf7a), @(0xbf7b), + @(0xbf7c), @(0xbf7d), @(0xbf7e), @(0xbf7f), @(0xbffd), @(0xbffe), @(0xbfff), + + // + @(0xc6c8), @(0xc6c9), @(0xc6ca), @(0xc6cb), @(0xc6cc), @(0xc6cd), @(0xc6ce), @(0xc6cf), + @(0xc6fd), @(0xc6fe), @(0xc6ff), @(0xc7c8), @(0xc7c9), @(0xc7ca), @(0xc7cb), @(0xc7cc), + @(0xc7cd), @(0xc7ce), @(0xc7cf), @(0xc7fd), @(0xc7fe), @(0xc7ff), @(0xce88), @(0xce89), + @(0xce8a), @(0xce8b), @(0xce8c), @(0xce8d), @(0xce8e), @(0xce8f), @(0xcebd), @(0xcebe), + @(0xcebf), @(0xcec8), @(0xcec9), @(0xceca), @(0xcecb), @(0xcecc), @(0xcecd), @(0xcece), + @(0xcecf), @(0xcefd), @(0xcefe), @(0xceff), @(0xcf80), @(0xcf81), @(0xcf82), @(0xcf83), + @(0xcf84), @(0xcf85), @(0xcf86), @(0xcf87), @(0xcfba), @(0xcfbb), @(0xcfbc), @(0xcfbd), + @(0xcfbe), @(0xcfbf), @(0xcfc8), @(0xcfc9), @(0xcfca), @(0xcfcb), @(0xcfcc), @(0xcfcd), + @(0xcfce), @(0xcfcf), @(0xcffd), @(0xcffe), @(0xcfff), + + // These are from the Bcc/BRA/BSR page. + @(0xd0fd), @(0xd0fe), @(0xd0ff), @(0xd1fd), @(0xd1fe), @(0xd1ff), @(0xd2fd), @(0xd2fe), + @(0xd2ff), @(0xd3fd), @(0xd3fe), @(0xd3ff), @(0xd4fd), @(0xd4fe), @(0xd4ff), @(0xd5fd), + @(0xd5fe), @(0xd5ff), @(0xd6fd), @(0xd6fe), @(0xd6ff), @(0xd7fd), @(0xd7fe), @(0xd7ff), + @(0xd8fd), @(0xd8fe), @(0xd8ff), @(0xd9fd), @(0xd9fe), @(0xd9ff), @(0xdafd), @(0xdafe), + @(0xdaff), @(0xdbfd), @(0xdbfe), @(0xdbff), @(0xdcfd), @(0xdcfe), @(0xdcff), @(0xddfd), + @(0xddfe), @(0xddff), @(0xdebd), @(0xdebe), @(0xdebf), @(0xdefd), @(0xdefe), @(0xdeff), + @(0xdfba), @(0xdfbb), @(0xdfbc), @(0xdfbd), @(0xdfbe), @(0xdfbf), @(0xdffd), @(0xdffe), + @(0xdfff), + + // The E line is for shifts and rolls; none of the those listed below appear to nominate valid + // addressing modes. + @(0xe6c0), @(0xe6c1), @(0xe6c2), @(0xe6c3), @(0xe6c4), @(0xe6c5), @(0xe6c6), @(0xe6c7), + @(0xe6c8), @(0xe6c9), @(0xe6ca), @(0xe6cb), @(0xe6cc), @(0xe6cd), @(0xe6ce), @(0xe6cf), + @(0xe6fa), @(0xe6fb), @(0xe6fc), @(0xe6fd), @(0xe6fe), @(0xe6ff), @(0xe7c0), @(0xe7c1), + @(0xe7c2), @(0xe7c3), @(0xe7c4), @(0xe7c5), @(0xe7c6), @(0xe7c7), @(0xe7c8), @(0xe7c9), + @(0xe7ca), @(0xe7cb), @(0xe7cc), @(0xe7cd), @(0xe7ce), @(0xe7cf), @(0xe7fa), @(0xe7fb), + @(0xe7fc), @(0xe7fd), @(0xe7fe), @(0xe7ff), @(0xeec0), @(0xeec1), @(0xeec2), @(0xeec3), + @(0xeec4), @(0xeec5), @(0xeec6), @(0xeec7), @(0xeec8), @(0xeec9), @(0xeeca), @(0xeecb), + @(0xeecc), @(0xeecd), @(0xeece), @(0xeecf), @(0xeed0), @(0xeed1), @(0xeed2), @(0xeed3), + @(0xeed4), @(0xeed5), @(0xeed6), @(0xeed7), @(0xeed8), @(0xeed9), @(0xeeda), @(0xeedb), + @(0xeedc), @(0xeedd), @(0xeede), @(0xeedf), @(0xeee0), @(0xeee1), @(0xeee2), @(0xeee3), + @(0xeee4), @(0xeee5), @(0xeee6), @(0xeee7), @(0xeee8), @(0xeee9), @(0xeeea), @(0xeeeb), + @(0xeeec), @(0xeeed), @(0xeeee), @(0xeeef), @(0xeef0), @(0xeef1), @(0xeef2), @(0xeef3), + @(0xeef4), @(0xeef5), @(0xeef6), @(0xeef7), @(0xeef8), @(0xeef9), @(0xeefa), @(0xeefb), + @(0xeefc), @(0xeefd), @(0xeefe), @(0xeeff), @(0xefc0), @(0xefc1), @(0xefc2), @(0xefc3), + @(0xefc4), @(0xefc5), @(0xefc6), @(0xefc7), @(0xefc8), @(0xefc9), @(0xefca), @(0xefcb), + @(0xefcc), @(0xefcd), @(0xefce), @(0xefcf), @(0xefd0), @(0xefd1), @(0xefd2), @(0xefd3), + @(0xefd4), @(0xefd5), @(0xefd6), @(0xefd7), @(0xefd8), @(0xefd9), @(0xefda), @(0xefdb), + @(0xefdc), @(0xefdd), @(0xefde), @(0xefdf), @(0xefe0), @(0xefe1), @(0xefe2), @(0xefe3), + @(0xefe4), @(0xefe5), @(0xefe6), @(0xefe7), @(0xefe8), @(0xefe9), @(0xefea), @(0xefeb), + @(0xefec), @(0xefed), @(0xefee), @(0xefef), @(0xeff0), @(0xeff1), @(0xeff2), @(0xeff3), + @(0xeff4), @(0xeff5), @(0xeff6), @(0xeff7), @(0xeff8), @(0xeff9), @(0xeffa), @(0xeffb), + @(0xeffc), @(0xeffd), @(0xeffe), @(0xefff) + ]]; + + NSSet *const falseValids = storage_tests.false_valids(); + NSSet *const falseInvalids = storage_tests.false_invalids(); + + XCTAssert(!falseValids.count, "%@ opcodes should be invalid but aren't: %@", @(falseValids.count), falseValids.hexDump); + + NSMutableSet *const decodedUndecodables = [undecodables mutableCopy]; + [decodedUndecodables minusSet:falseInvalids]; + XCTAssert(!decodedUndecodables.count, "This test considers these undecodable but they were decoded: %@", decodedUndecodables.hexDump); + + NSMutableSet *const trimmedInvalids = [falseInvalids mutableCopy]; + [trimmedInvalids minusSet:undecodables]; + XCTAssert(!trimmedInvalids.count, "%@ opcodes should be valid but aren't: %@", @(trimmedInvalids.count), trimmedInvalids.hexDump); + +// XCTAssert(!falseInvalids.count, "%@ opcodes should be valid but aren't: %@", @(falseInvalids.count), falseInvalids.hexDump); +} + +@end diff --git a/OSBindings/Mac/Clock SignalTests/ArrayBuilderTests.mm b/OSBindings/Mac/Clock SignalTests/ArrayBuilderTests.mm deleted file mode 100644 index 8303a0cb3..000000000 --- a/OSBindings/Mac/Clock SignalTests/ArrayBuilderTests.mm +++ /dev/null @@ -1,140 +0,0 @@ -// -// ArrayBuilderTests.m -// Clock Signal -// -// Created by Thomas Harte on 19/11/2016. -// Copyright 2016 Thomas Harte. All rights reserved. -// - -#import - -#include "ArrayBuilder.hpp" - -static NSData *inputData, *outputData; - -static void setData(bool is_input, uint8_t *data, size_t size) -{ - NSData *dataObject = [NSData dataWithBytes:data length:size]; - if(is_input) inputData = dataObject; else outputData = dataObject; -} - -@interface ArrayBuilderTests : XCTestCase -@end - -@implementation ArrayBuilderTests - -+ (void)setUp -{ - inputData = nil; - outputData = nil; -} - -- (void)assertMonotonicForInputSize:(size_t)inputSize outputSize:(size_t)outputSize -{ - XCTAssert(inputData != nil, @"Should have received some input data"); - XCTAssert(outputData != nil, @"Should have received some output data"); - - XCTAssert(inputData.length == inputSize, @"Input data should be %lu bytes long, was %lu", inputSize, (unsigned long)inputData.length); - XCTAssert(outputData.length == outputSize, @"Output data should be %lu bytes long, was %lu", outputSize, (unsigned long)outputData.length); - - if(inputData.length == inputSize && outputData.length == outputSize) - { - uint8_t *input = (uint8_t *)inputData.bytes; - uint8_t *output = (uint8_t *)outputData.bytes; - - for(int c = 0; c < inputSize; c++) XCTAssert(input[c] == c, @"Input item %d should be %d, was %d", c, c, input[c]); - for(int c = 0; c < outputSize; c++) XCTAssert(output[c] == c + 0x80, @"Output item %d should be %d, was %d", c, c+0x80, output[c]); - } -} - -- (std::function)emptyFlushFunction -{ - return [=] (uint8_t *input, size_t input_size, uint8_t *output, size_t output_size) {}; -} - -- (void)testSingleWriteSingleFlush -{ - Outputs::CRT::ArrayBuilder arrayBuilder(200, 100, setData); - - uint8_t *input = arrayBuilder.get_input_storage(5); - uint8_t *output = arrayBuilder.get_output_storage(3); - - for(int c = 0; c < 5; c++) input[c] = c; - for(int c = 0; c < 3; c++) output[c] = c + 0x80; - - arrayBuilder.flush(self.emptyFlushFunction); - arrayBuilder.submit(); - - [self assertMonotonicForInputSize:5 outputSize:3]; -} - -- (void)testDoubleWriteSingleFlush -{ - Outputs::CRT::ArrayBuilder arrayBuilder(200, 100, setData); - uint8_t *input; - uint8_t *output; - - input = arrayBuilder.get_input_storage(2); - output = arrayBuilder.get_output_storage(2); - - for(int c = 0; c < 2; c++) input[c] = c; - for(int c = 0; c < 2; c++) output[c] = c + 0x80; - - input = arrayBuilder.get_input_storage(2); - output = arrayBuilder.get_output_storage(2); - - for(int c = 0; c < 2; c++) input[c] = c+2; - for(int c = 0; c < 2; c++) output[c] = c+2 + 0x80; - - arrayBuilder.flush(self.emptyFlushFunction); - arrayBuilder.submit(); - - [self assertMonotonicForInputSize:4 outputSize:4]; -} - -- (void)testSubmitWithoutFlush -{ - Outputs::CRT::ArrayBuilder arrayBuilder(200, 100, setData); - - arrayBuilder.get_input_storage(5); - arrayBuilder.get_input_storage(8); - arrayBuilder.get_output_storage(6); - arrayBuilder.get_input_storage(12); - arrayBuilder.get_output_storage(3); - - arrayBuilder.submit(); - - XCTAssert(inputData.length == 0, @"No input data should have been received; %lu bytes were received", (unsigned long)inputData.length); - XCTAssert(outputData.length == 0, @"No output data should have been received; %lu bytes were received", (unsigned long)outputData.length); - - arrayBuilder.flush(self.emptyFlushFunction); - arrayBuilder.submit(); - - XCTAssert(inputData.length == 25, @"All input data should have been received; %lu bytes were received", (unsigned long)inputData.length); - XCTAssert(outputData.length == 9, @"All output data should have been received; %lu bytes were received", (unsigned long)outputData.length); -} - -- (void)testSubmitContinuity -{ - Outputs::CRT::ArrayBuilder arrayBuilder(200, 100, setData); - - arrayBuilder.get_input_storage(5); - arrayBuilder.get_output_storage(5); - - arrayBuilder.flush(self.emptyFlushFunction); - - uint8_t *input = arrayBuilder.get_input_storage(5); - uint8_t *output = arrayBuilder.get_output_storage(5); - - arrayBuilder.submit(); - - for(int c = 0; c < 5; c++) input[c] = c; - for(int c = 0; c < 5; c++) output[c] = c + 0x80; - - arrayBuilder.flush(self.emptyFlushFunction); - arrayBuilder.submit(); - - [self assertMonotonicForInputSize:5 outputSize:5]; -} - -@end diff --git a/OSBindings/Mac/Clock SignalTests/Comparative68000.hpp b/OSBindings/Mac/Clock SignalTests/Comparative68000.hpp new file mode 100644 index 000000000..b64ec0965 --- /dev/null +++ b/OSBindings/Mac/Clock SignalTests/Comparative68000.hpp @@ -0,0 +1,59 @@ +// +// Comparative68000.hpp +// Clock SignalTests +// +// Created by Thomas Harte on 29/04/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#ifndef Comparative68000_hpp +#define Comparative68000_hpp + +#include + +#include "68000.hpp" + +class ComparativeBusHandler: public CPU::MC68000::BusHandler { + public: + ComparativeBusHandler(const char *trace_name) { + trace = gzopen(trace_name, "rt"); + } + + ~ComparativeBusHandler() { + gzclose(trace); + } + + void will_perform(uint32_t address, uint16_t opcode) { + // Obtain the next line from the trace file. + char correct_state[300] = "\n"; + gzgets(trace, correct_state, sizeof(correct_state)); + ++line_count; + + // Generate state locally. + const auto state = get_state(); + char local_state[300]; + sprintf(local_state, "%04x: %02x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n", + address, + state.status, + state.data[0], state.data[1], state.data[2], state.data[3], state.data[4], state.data[5], state.data[6], state.data[7], + state.address[0], state.address[1], state.address[2], state.address[3], state.address[4], state.address[5], state.address[6], + (state.status & 0x2000) ? state.supervisor_stack_pointer : state.user_stack_pointer + ); + + // Check that the two coincide. + if(strcmp(correct_state, local_state)) { + fprintf(stderr, "Diverges at line %d\n", line_count); + fprintf(stderr, "Good: %s", correct_state); + fprintf(stderr, "Bad: %s", local_state); + assert(false); + } + } + + virtual CPU::MC68000::ProcessorState get_state() = 0; + + private: + int line_count = 0; + gzFile trace; +}; + +#endif /* Comparative68000_hpp */ diff --git a/OSBindings/Mac/Clock SignalTests/EmuTOSTests.mm b/OSBindings/Mac/Clock SignalTests/EmuTOSTests.mm new file mode 100644 index 000000000..f81457706 --- /dev/null +++ b/OSBindings/Mac/Clock SignalTests/EmuTOSTests.mm @@ -0,0 +1,120 @@ +// +// EmuTOSTests.m +// Clock SignalTests +// +// Created by Thomas Harte on 10/03/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#import + +#include +#include + +#include "68000.hpp" +#include "Comparative68000.hpp" +#include "CSROMFetcher.hpp" + +class EmuTOS: public ComparativeBusHandler { + public: + EmuTOS(const std::vector &emuTOS, const char *trace_name) : ComparativeBusHandler(trace_name), m68000_(*this) { + assert(!(emuTOS.size() & 1)); + emuTOS_.resize(emuTOS.size() / 2); + + for(size_t c = 0; c < emuTOS_.size(); ++c) { + emuTOS_[c] = (emuTOS[c << 1] << 8) | emuTOS[(c << 1) + 1]; + } + } + + void run_for(HalfCycles cycles) { + m68000_.run_for(cycles); + } + + CPU::MC68000::ProcessorState get_state() override { + return m68000_.get_state(); + } + + HalfCycles perform_bus_operation(const CPU::MC68000::Microcycle &cycle, int is_supervisor) { + const uint32_t address = cycle.word_address(); + uint32_t word_address = address; + + // As much about the Atari ST's memory map as is relevant here: the ROM begins + // at 0xfc0000, and the first eight bytes are mirrored to the first four memory + // addresses in order for /RESET to work properly. RAM otherwise fills the first + // 512kb of the address space. Trying to write to ROM raises a bus error. + + const bool is_rom = (word_address >= (0xfc0000 >> 1) && word_address < (0xff0000 >> 1)) || word_address < 4; + const bool is_ram = word_address < ram_.size(); + const bool is_peripheral = !is_rom && !is_ram; + + uint16_t *const base = is_rom ? emuTOS_.data() : ram_.data(); + if(is_rom) { + word_address %= emuTOS_.size(); + } else { + word_address %= ram_.size(); + } + + using Microcycle = CPU::MC68000::Microcycle; + if(cycle.data_select_active()) { + uint16_t peripheral_result = 0xffff; + if(is_peripheral) { + switch(address & 0x7ff) { + // A hard-coded value for TIMER B. + case (0xa21 >> 1): + peripheral_result = 0x00000001; + break; + } + } + + switch(cycle.operation & (Microcycle::SelectWord | Microcycle::SelectByte | Microcycle::Read)) { + default: break; + + case Microcycle::SelectWord | Microcycle::Read: + cycle.value->full = is_peripheral ? peripheral_result : base[word_address]; + break; + case Microcycle::SelectByte | Microcycle::Read: + cycle.value->halves.low = (is_peripheral ? peripheral_result : base[word_address]) >> cycle.byte_shift(); + break; + case Microcycle::SelectWord: + base[word_address] = cycle.value->full; + break; + case Microcycle::SelectByte: + base[word_address] = (cycle.value->halves.low << cycle.byte_shift()) | (base[word_address] & (0xffff ^ cycle.byte_mask())); + break; + } + } + + return HalfCycles(0); + } + + private: + CPU::MC68000::Processor m68000_; + + std::vector emuTOS_; + std::array ram_; +}; + +@interface EmuTOSTests : XCTestCase +@end + +@implementation EmuTOSTests { + std::unique_ptr _machine; +} + +- (void)testImage:(NSString *)image trace:(NSString *)trace length:(int)length { + const auto roms = CSROMFetcher()("AtariST", { image.UTF8String }); + NSString *const traceLocation = [[NSBundle bundleForClass:[self class]] pathForResource:trace ofType:@"trace.txt.gz"]; + _machine.reset(new EmuTOS(*roms[0], traceLocation.UTF8String)); + _machine->run_for(HalfCycles(length)); +} + +- (void)testEmuTOSStartup { + [self testImage:@"etos192uk.img" trace:@"etos192uk" length:313490]; + // TODO: assert that machine is now STOPped. +} + +- (void)testTOSStartup { + [self testImage:@"tos100.img" trace:@"tos100" length:54011091]; +} + +@end diff --git a/OSBindings/Mac/Clock SignalTests/QL Startup/qltrace.txt.gz b/OSBindings/Mac/Clock SignalTests/QL Startup/qltrace.txt.gz new file mode 100644 index 000000000..6f06c2b0a Binary files /dev/null and b/OSBindings/Mac/Clock SignalTests/QL Startup/qltrace.txt.gz differ diff --git a/OSBindings/Mac/Clock SignalTests/QLTests.mm b/OSBindings/Mac/Clock SignalTests/QLTests.mm new file mode 100644 index 000000000..ad6f0c389 --- /dev/null +++ b/OSBindings/Mac/Clock SignalTests/QLTests.mm @@ -0,0 +1,112 @@ +// +// EmuTOSTests.m +// Clock SignalTests +// +// Created by Thomas Harte on 10/03/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#import + +#include +#include + +#include +#include + +#include + +#include "68000.hpp" +#include "Comparative68000.hpp" +#include "CSROMFetcher.hpp" + +class QL: public ComparativeBusHandler { + public: + QL(const std::vector &rom, const char *trace_name) : ComparativeBusHandler(trace_name), m68000_(*this) { + assert(!(rom.size() & 1)); + rom_.resize(rom.size() / 2); + + for(size_t c = 0; c < rom_.size(); ++c) { + rom_[c] = (rom[c << 1] << 8) | rom[(c << 1) + 1]; + } + } + + void run_for(HalfCycles cycles) { + m68000_.run_for(cycles); + } + + CPU::MC68000::ProcessorState get_state() override { + return m68000_.get_state(); + } + + HalfCycles perform_bus_operation(const CPU::MC68000::Microcycle &cycle, int is_supervisor) { + const uint32_t address = cycle.word_address(); + uint32_t word_address = address; + + // QL memory map: ROM is in the lowest area; RAM is from 0x20000. + const bool is_rom = word_address < rom_.size(); + const bool is_ram = word_address >= 0x10000 && word_address < 0x10000+ram_.size(); + const bool is_peripheral = !is_ram && !is_rom; + + uint16_t *const base = is_rom ? rom_.data() : ram_.data(); + if(is_rom) { + word_address %= rom_.size(); + } + if(is_ram) { + word_address %= ram_.size(); + } + + using Microcycle = CPU::MC68000::Microcycle; + if(cycle.data_select_active()) { + uint16_t peripheral_result = 0xffff; + + switch(cycle.operation & (Microcycle::SelectWord | Microcycle::SelectByte | Microcycle::Read)) { + default: break; + + case Microcycle::SelectWord | Microcycle::Read: + cycle.value->full = is_peripheral ? peripheral_result : base[word_address]; + break; + case Microcycle::SelectByte | Microcycle::Read: + cycle.value->halves.low = (is_peripheral ? peripheral_result : base[word_address]) >> cycle.byte_shift(); + break; + case Microcycle::SelectWord: + assert(!(is_rom && !is_peripheral)); + if(!is_peripheral) base[word_address] = cycle.value->full; + break; + case Microcycle::SelectByte: + assert(!(is_rom && !is_peripheral)); + if(!is_peripheral) base[word_address] = (cycle.value->halves.low << cycle.byte_shift()) | (base[word_address] & (0xffff ^ cycle.byte_mask())); + break; + } + } + + return HalfCycles(0); + } + + private: + CPU::MC68000::Processor m68000_; + + std::vector rom_; + std::array ram_; +}; + +@interface QLTests : XCTestCase +@end + +@implementation QLTests { + std::unique_ptr _machine; +} + +/*! + Tests the progression of Clock Signal's 68000 through the Sinclair QL's ROM against a known-good trace. +*/ +- (void)testStartup { + const auto roms = CSROMFetcher()("SinclairQL", {"js.rom"}); + NSString *const traceLocation = [[NSBundle bundleForClass:[self class]] pathForResource:@"qltrace" ofType:@".txt.gz"]; + _machine.reset(new QL(*roms[0], traceLocation.UTF8String)); + + // This is how many cycles it takes to exhaust the supplied trace file. + _machine->run_for(HalfCycles(23923180)); +} + +@end diff --git a/OSBindings/Mac/Clock SignalTests/TOS Startup/etos192uk.trace.txt.gz b/OSBindings/Mac/Clock SignalTests/TOS Startup/etos192uk.trace.txt.gz new file mode 100644 index 000000000..386be16ff Binary files /dev/null and b/OSBindings/Mac/Clock SignalTests/TOS Startup/etos192uk.trace.txt.gz differ diff --git a/OSBindings/Mac/Clock SignalTests/TOS Startup/tos100.trace.txt.gz b/OSBindings/Mac/Clock SignalTests/TOS Startup/tos100.trace.txt.gz new file mode 100644 index 000000000..e069b5f48 Binary files /dev/null and b/OSBindings/Mac/Clock SignalTests/TOS Startup/tos100.trace.txt.gz differ diff --git a/OSBindings/Mac/Clock SignalTests/Z80MachineCycleTests.swift b/OSBindings/Mac/Clock SignalTests/Z80MachineCycleTests.swift index 4295e6cf3..e719e977c 100644 --- a/OSBindings/Mac/Clock SignalTests/Z80MachineCycleTests.swift +++ b/OSBindings/Mac/Clock SignalTests/Z80MachineCycleTests.swift @@ -24,6 +24,7 @@ class Z80MachineCycleTests: XCTestCase { case .portRead: opName = "i" case .portWrite: opName = "o" case .internalOperation: opName = "iop" + default: opName = "?" } return "\(opName) \(length)" } @@ -35,7 +36,7 @@ class Z80MachineCycleTests: XCTestCase { // Create a machine and install the supplied program at address 0, setting the PC to run from there let machine = CSTestMachineZ80() machine.setValue(0x0000, for: .programCounter) - machine.setData(Data(bytes: program), atAddress: 0x0000) + machine.setData(Data(_: program), atAddress: 0x0000) // Figure out the total number of cycles implied by the bus cycles var totalCycles: Int32 = 0 diff --git a/OSBindings/Mac/Clock SignalTests/Z80MemptrTests.swift b/OSBindings/Mac/Clock SignalTests/Z80MemptrTests.swift index 6fc3c5ce5..7ef2513f4 100644 --- a/OSBindings/Mac/Clock SignalTests/Z80MemptrTests.swift +++ b/OSBindings/Mac/Clock SignalTests/Z80MemptrTests.swift @@ -14,7 +14,7 @@ class Z80MemptrTests: XCTestCase { private func test(program : [UInt8], length : Int32, initialValue : UInt16) -> UInt16 { // Create a machine and install the supplied program at address 0, setting the PC to run from there machine.setValue(0x0000, for: .programCounter) - machine.setData(Data(bytes: program), atAddress: 0x0000) + machine.setData(Data(_: program), atAddress: 0x0000) // Set the initial value of memptr, run for the requested number of cycles, // return the new value @@ -316,7 +316,7 @@ class Z80MemptrTests: XCTestCase { let program: [UInt8] = [ 0xed, 0xa1 ] - machine.setData(Data(bytes: program), atAddress: 0x0000) + machine.setData(Data(_: program), atAddress: 0x0000) machine.setValue(0, for: .memPtr) for c in 1 ..< 65536 { @@ -332,7 +332,7 @@ class Z80MemptrTests: XCTestCase { let program: [UInt8] = [ 0xed, 0xa9 ] - machine.setData(Data(bytes: program), atAddress: 0x0000) + machine.setData(Data(_: program), atAddress: 0x0000) machine.setValue(0, for: .memPtr) for c in 1 ..< 65536 { diff --git a/OSBindings/SDL/SConstruct b/OSBindings/SDL/SConstruct index d0322e048..2bc56fea6 100644 --- a/OSBindings/SDL/SConstruct +++ b/OSBindings/SDL/SConstruct @@ -67,6 +67,7 @@ SOURCES += glob.glob('../../Outputs/OpenGL/*.cpp') SOURCES += glob.glob('../../Outputs/OpenGL/Primitives/*.cpp') SOURCES += glob.glob('../../Processors/6502/Implementation/*.cpp') +SOURCES += glob.glob('../../Processors/68000/Implementation/*.cpp') SOURCES += glob.glob('../../Processors/Z80/Implementation/*.cpp') SOURCES += glob.glob('../../SignalProcessing/*.cpp') diff --git a/Outputs/Log.hpp b/Outputs/Log.hpp index d24d2262b..88beab326 100644 --- a/Outputs/Log.hpp +++ b/Outputs/Log.hpp @@ -23,8 +23,8 @@ #include #include -#define PADHEX(n) std::hex << std::setw(n) << std::right << std::setfill('0') -#define PADDEC(n) std::dec << std::setw(n) << std::right << std::setfill('0') +#define PADHEX(n) std::hex << std::setfill('0') << std::setw(n) +#define PADDEC(n) std::dec << std::setfill('0') << std::setw(n) #define LOG(x) std::cout << x << std::endl #define LOGNBR(x) std::cout << x diff --git a/Processors/6502/Implementation/6502Implementation.hpp b/Processors/6502/Implementation/6502Implementation.hpp index 03637ca72..c32f544e2 100644 --- a/Processors/6502/Implementation/6502Implementation.hpp +++ b/Processors/6502/Implementation/6502Implementation.hpp @@ -28,7 +28,7 @@ template void Proces // 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 - RegisterPair nextAddress = next_address_; + RegisterPair16 nextAddress = next_address_; BusOperation nextBusOperation = next_bus_operation_; uint16_t busAddress = bus_address_; uint8_t *busValue = bus_value_; @@ -147,8 +147,8 @@ template void Proces } case CycleIncPCPushPCH: pc_.full++; // deliberate fallthrough - case CyclePushPCH: push(pc_.bytes.high); break; - case CyclePushPCL: push(pc_.bytes.low); break; + case CyclePushPCH: push(pc_.halves.high); break; + case CyclePushPCL: push(pc_.halves.low); break; case CyclePushOperand: push(operand_); break; case CyclePushA: push(a_); break; case CyclePushX: push(x_); break; @@ -175,8 +175,8 @@ template void Proces continue; case OperationNMIPickVector: nextAddress.full = 0xfffa; continue; case OperationRSTPickVector: nextAddress.full = 0xfffc; continue; - case CycleReadVectorLow: read_mem(pc_.bytes.low, nextAddress.full); break; - case CycleReadVectorHigh: read_mem(pc_.bytes.high, nextAddress.full+1); break; + case CycleReadVectorLow: read_mem(pc_.halves.low, nextAddress.full); break; + case CycleReadVectorHigh: read_mem(pc_.halves.high, nextAddress.full+1); break; case OperationSetIRQFlags: inverse_interrupt_flag_ = 0; if(is_65c02(personality)) decimal_flag_ = false; @@ -185,8 +185,8 @@ template void Proces if(is_65c02(personality)) decimal_flag_ = false; continue; - case CyclePullPCL: s_++; read_mem(pc_.bytes.low, s_ | 0x100); break; - case CyclePullPCH: s_++; read_mem(pc_.bytes.high, s_ | 0x100); break; + 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; @@ -198,11 +198,11 @@ template void Proces case OperationSetFlagsFromX: zero_result_ = negative_result_ = x_; continue; case OperationSetFlagsFromY: zero_result_ = negative_result_ = y_; continue; - case CycleIncrementPCAndReadStack: pc_.full++; throwaway_read(s_ | 0x100); break; - case CycleReadPCLFromAddress: read_mem(pc_.bytes.low, address_.full); break; - case CycleReadPCHFromAddressLowInc: address_.bytes.low++; read_mem(pc_.bytes.high, address_.full); break; - case CycleReadPCHFromAddressFixed: if(!address_.bytes.low) address_.bytes.high++; read_mem(pc_.bytes.high, address_.full); break; - case CycleReadPCHFromAddressInc: address_.full++; read_mem(pc_.bytes.high, address_.full); break; + case CycleIncrementPCAndReadStack: pc_.full++; throwaway_read(s_ | 0x100); break; + case CycleReadPCLFromAddress: read_mem(pc_.halves.low, address_.full); break; + case CycleReadPCHFromAddressLowInc: address_.halves.low++; read_mem(pc_.halves.high, address_.full); break; + case CycleReadPCHFromAddressFixed: if(!address_.halves.low) address_.halves.high++; read_mem(pc_.halves.high, address_.full); break; + case CycleReadPCHFromAddressInc: address_.full++; read_mem(pc_.halves.high, address_.full); break; case CycleReadAndIncrementPC: { uint16_t oldPC = pc_.full; @@ -244,10 +244,10 @@ template void Proces case OperationSTY: operand_ = y_; continue; case OperationSTZ: operand_ = 0; continue; case OperationSAX: operand_ = a_ & x_; continue; - case OperationSHA: operand_ = a_ & x_ & (address_.bytes.high+1); continue; - case OperationSHX: operand_ = x_ & (address_.bytes.high+1); continue; - case OperationSHY: operand_ = y_ & (address_.bytes.high+1); continue; - case OperationSHS: s_ = a_ & x_; operand_ = s_ & (address_.bytes.high+1); continue; + case OperationSHA: operand_ = a_ & x_ & (address_.halves.high+1); continue; + case OperationSHX: operand_ = x_ & (address_.halves.high+1); continue; + case OperationSHY: operand_ = y_ & (address_.halves.high+1); continue; + case OperationSHS: s_ = a_ & x_; operand_ = s_ & (address_.halves.high+1); continue; case OperationLXA: a_ = x_ = (a_ | 0xee) & operand_; @@ -475,28 +475,28 @@ template void Proces case CycleAddXToAddressLow: nextAddress.full = address_.full + x_; - address_.bytes.low = nextAddress.bytes.low; - if(address_.bytes.high != nextAddress.bytes.high) { + address_.halves.low = nextAddress.halves.low; + if(address_.halves.high != nextAddress.halves.high) { page_crossing_stall_read(); break; } continue; case CycleAddXToAddressLowRead: nextAddress.full = address_.full + x_; - address_.bytes.low = nextAddress.bytes.low; + address_.halves.low = nextAddress.halves.low; page_crossing_stall_read(); break; case CycleAddYToAddressLow: nextAddress.full = address_.full + y_; - address_.bytes.low = nextAddress.bytes.low; - if(address_.bytes.high != nextAddress.bytes.high) { + address_.halves.low = nextAddress.halves.low; + if(address_.halves.high != nextAddress.halves.high) { page_crossing_stall_read(); break; } continue; case CycleAddYToAddressLowRead: nextAddress.full = address_.full + y_; - address_.bytes.low = nextAddress.bytes.low; + address_.halves.low = nextAddress.halves.low; page_crossing_stall_read(); break; @@ -507,37 +507,37 @@ template void Proces continue; case CycleIncrementPCFetchAddressLowFromOperand: pc_.full++; - read_mem(address_.bytes.low, operand_); + read_mem(address_.halves.low, operand_); break; case CycleAddXToOperandFetchAddressLow: operand_ += x_; - read_mem(address_.bytes.low, operand_); + read_mem(address_.halves.low, operand_); break; case CycleFetchAddressLowFromOperand: - read_mem(address_.bytes.low, operand_); + read_mem(address_.halves.low, operand_); break; case CycleIncrementOperandFetchAddressHigh: operand_++; - read_mem(address_.bytes.high, operand_); + read_mem(address_.halves.high, operand_); break; case CycleIncrementPCReadPCHLoadPCL: // deliberate fallthrough pc_.full++; case CycleReadPCHLoadPCL: { uint16_t oldPC = pc_.full; - pc_.bytes.low = operand_; - read_mem(pc_.bytes.high, oldPC); + pc_.halves.low = operand_; + read_mem(pc_.halves.high, oldPC); } break; case CycleReadAddressHLoadAddressL: - address_.bytes.low = operand_; pc_.full++; - read_mem(address_.bytes.high, pc_.full); + address_.halves.low = operand_; pc_.full++; + read_mem(address_.halves.high, pc_.full); break; case CycleLoadAddressAbsolute: { uint16_t nextPC = pc_.full+1; pc_.full += 2; - address_.bytes.low = operand_; - read_mem(address_.bytes.high, nextPC); + address_.halves.low = operand_; + read_mem(address_.halves.high, nextPC); } break; case OperationLoadAddressZeroPage: @@ -583,8 +583,8 @@ template void Proces case CycleAddSignedOperandToPC: nextAddress.full = static_cast(pc_.full + (int8_t)operand_); - pc_.bytes.low = nextAddress.bytes.low; - if(nextAddress.bytes.high != pc_.bytes.high) { + 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); @@ -598,7 +598,7 @@ template void Proces continue; case CycleFetchFromHalfUpdatedPC: { - uint16_t halfUpdatedPc = static_cast(((pc_.bytes.low + (int8_t)operand_) & 0xff) | (pc_.bytes.high << 8)); + uint16_t halfUpdatedPc = static_cast(((pc_.halves.low + (int8_t)operand_) & 0xff) | (pc_.halves.high << 8)); throwaway_read(halfUpdatedPc); } break; diff --git a/Processors/6502/Implementation/6502Storage.hpp b/Processors/6502/Implementation/6502Storage.hpp index fd7b54b5f..bc06c0e2c 100644 --- a/Processors/6502/Implementation/6502Storage.hpp +++ b/Processors/6502/Implementation/6502Storage.hpp @@ -205,7 +205,7 @@ class ProcessorStorage { /* Storage for the 6502 registers; F is stored as individual flags. */ - RegisterPair pc_, last_operation_pc_; + 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; @@ -213,7 +213,7 @@ class ProcessorStorage { Temporary state for the micro programs. */ uint8_t operation_, operand_; - RegisterPair address_, next_address_; + RegisterPair16 address_, next_address_; /* Temporary storage allowing a common dispatch point for calling perform_bus_operation; diff --git a/Processors/68000/68000.hpp b/Processors/68000/68000.hpp new file mode 100644 index 000000000..1f539a252 --- /dev/null +++ b/Processors/68000/68000.hpp @@ -0,0 +1,294 @@ +// +// 68000.hpp +// Clock Signal +// +// Created by Thomas Harte on 08/03/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#ifndef MC68000_h +#define MC68000_h + +#include +#include +#include +#include +#include +#include + +#include "../../ClockReceiver/ClockReceiver.hpp" +#include "../RegisterSizes.hpp" + +namespace CPU { +namespace MC68000 { + +/*! + A microcycle is an atomic unit of 68000 bus activity — it is a single item large enough + fully to specify a sequence of bus events that occur without any possible interruption. + + Concretely, a standard read cycle breaks down into at least two microcycles: + + 1) a 4 half-cycle length microcycle in which the address strobe is signalled; and + 2) a 4 half-cycle length microcycle in which at least one of the data strobes is + signalled, and the data bus is sampled. + + That is, assuming DTack were signalled when microcycle (1) ended. If not then additional + wait state microcycles would fall between those two parts. + + The 68000 data sheet defines when the address becomes valid during microcycle (1), and + when the address strobe is actually asserted. But those timings are fixed. So simply + telling you that this was a microcycle during which the address trobe was signalled is + sufficient fully to describe the bus activity. + + (Aside: see the 68000 template's definition for options re: implicit DTack; if your + 68000 owner can always predict exactly how long it will hold DTack following observation + of an address-strobing microcycle, it can just supply those periods for accounting and + avoid the runtime cost of actual DTack emulation. But such as the bus allows.) +*/ +struct Microcycle { + /// A NewAddress cycle is one in which the address strobe is initially low but becomes high; + /// this correlates to states 0 to 5 of a standard read/write cycle. + static const int NewAddress = 1 << 0; + + /// A SameAddress cycle is one in which the address strobe is continuously asserted, but neither + /// of the data strobes are. + static const int SameAddress = 1 << 1; + + /// A Reset cycle is one in which the RESET output is asserted. + static const int Reset = 1 << 2; + + /// Indicates that the address and both data select strobes are active. + static const int SelectWord = 1 << 3; + + /// Indicates that the address strobe and exactly one of the data strobes are active; you can determine + /// which by inspecting the low bit of the provided address. The RW line indicates a read. + static const int SelectByte = 1 << 4; + + /// If set, indicates a read. Otherwise, a write. + static const int Read = 1 << 5; + + /// Contains the value of line FC0 if it is not implicit via InterruptAcknowledge. + static const int IsData = 1 << 6; + + /// Contains the value of line FC1 if it is not implicit via InterruptAcknowledge. + static const int IsProgram = 1 << 7; + + /// The interrupt acknowledge cycle is that during which the 68000 seeks to obtain the vector for + /// an interrupt it plans to observe. Noted on a real 68000 by all FCs being set to 1. + static const int InterruptAcknowledge = 1 << 8; + + /// Represents the state of the 68000's valid memory address line — indicating whether this microcycle + /// is synchronised with the E clock to satisfy a valid peripheral address request. + static const int IsPeripheral = 1 << 9; + + /// Contains a valid combination of the various static const int flags, describing the operation + /// performed by this Microcycle. + int operation = 0; + + /// Describes the duration of this Microcycle. + HalfCycles length = HalfCycles(4); + + /*! + For expediency, this provides a full 32-bit byte-resolution address — e.g. + if reading indirectly via an address register, this will indicate the full + value of the address register. + + The receiver should ignore bits 0 and 24+. Use word_address() automatically + to obtain the only the 68000's real address lines, giving a 23-bit address + at word resolution. + */ + const uint32_t *address = nullptr; + + /*! + If this is a write cycle, dereference value to get the value loaded onto + the data bus. + + If this is a read cycle, write the value on the data bus to it. + + Otherwise, this value is undefined. + + Byte values are provided via @c value.halves.low. @c value.halves.high is undefined. + This is true regardless of whether the upper or lower byte of a word is being + accessed. + + Word values occupy the entirety of @c value.full. + */ + RegisterPair16 *value = nullptr; + + /// @returns @c true if two Microcycles are equal; @c false otherwise. + bool operator ==(const Microcycle &rhs) const { + if(value != rhs.value) return false; + if(address != rhs.address) return false; + if(length != rhs.length) return false; + if(operation != rhs.operation) return false; + return true; + } + + // Various inspectors. + + /*! @returns true if any data select line is active; @c false otherwise. */ + inline bool data_select_active() const { + return bool(operation & (SelectWord | SelectByte | InterruptAcknowledge)); + } + + /*! + @returns 0 if this byte access wants the low part of a 16-bit word; 8 if it wants the high part. + */ + inline unsigned int byte_shift() const { + return (((*address) & 1) << 3) ^ 8; + } + + /*! + Obtains the mask to apply to a word that will leave only the byte this microcycle is selecting. + + @returns 0x00ff if this byte access wants the low part of a 16-bit word; 0xff00 if it wants the high part. + */ + inline uint16_t byte_mask() const { + return uint16_t(0xff00) >> (((*address) & 1) << 3); + } + + /*! + Obtains the mask to apply to a word that will leave only the byte this microcycle **isn't** selecting. + i.e. this is the part of a word that should be untouched by this microcycle. + + @returns 0xff00 if this byte access wants the low part of a 16-bit word; 0x00ff if it wants the high part. + */ + inline uint16_t untouched_byte_mask() const { + return uint16_t(uint16_t(0xff) << (((*address) & 1) << 3)); + } + + /*! + Assuming this cycle is a byte write, mutates @c destination by writing the byte to the proper upper or + lower part, retaining the other half. + */ + uint16_t write_byte(uint16_t destination) const { + return uint16_t((destination & untouched_byte_mask()) | (value->halves.low << byte_shift())); + } + + /*! + @returns non-zero if this is a byte read and 68000 LDS is asserted. + */ + inline int lower_data_select() const { + return (operation & SelectByte) & ((*address & 1) << 3); + } + + /*! + @returns non-zero if this is a byte read and 68000 UDS is asserted. + */ + inline int upper_data_select() const { + return (operation & SelectByte) & ~((*address & 1) << 3); + } + + /*! + @returns the address being accessed at the precision a 68000 supplies it — + only 24 address bit precision, with the low bit shifted out. So it's the + 68000 address at word precision: address 0 is the first word in the address + space, address 1 is the second word (i.e. the third and fourth bytes) in + the address space, etc. + */ + uint32_t word_address() const { + return (address ? (*address) & 0x00fffffe : 0) >> 1; + } +}; + +/*! + This is the prototype for a 68000 bus handler; real bus handlers can descend from this + in order to get default implementations of any changes that may occur in the expected interface. +*/ +class BusHandler { + public: + /*! + Provides the bus handler with a single Microcycle to 'perform'. + + FC0 and FC1 are provided inside the microcycle as the IsData and IsProgram + flags; FC2 is provided here as is_supervisor — it'll be either 0 or 1. + */ + HalfCycles perform_bus_operation(const Microcycle &cycle, int is_supervisor) { + return HalfCycles(0); + } + + void flush() {} + + /*! + Provides information about the path of execution if enabled via the template. + */ + void will_perform(uint32_t address, uint16_t opcode) {} +}; + +#include "Implementation/68000Storage.hpp" + +class ProcessorBase: public ProcessorStorage { +}; + +struct ProcessorState { + uint32_t data[8]; + uint32_t address[7]; + uint32_t user_stack_pointer, supervisor_stack_pointer; + uint32_t program_counter; + uint16_t status; + + // TODO: More state needed to indicate current instruction, the processor's + // progress through it, and anything it has fetched so far. +// uint16_t current_instruction; +}; + +template class Processor: public ProcessorBase { + public: + Processor(T &bus_handler) : ProcessorBase(), bus_handler_(bus_handler) {} + + void run_for(HalfCycles duration); + + using State = ProcessorState; + /// @returns The current processor state. + State get_state(); + + /// Sets the processor to the supplied state. + void set_state(const State &); + + /// Sets the DTack line — @c true for active, @c false for inactive. + inline void set_dtack(bool dtack) { + dtack_ = dtack; + } + + /// Sets the VPA (valid peripheral address) line — @c true for active, @c false for inactive. + inline void set_is_peripheral_address(bool is_peripheral_address) { + is_peripheral_address_ = is_peripheral_address; + } + + /// Sets the bus error line — @c true for active, @c false for inactive. + void set_bus_error(bool bus_error) { + bus_error_ = bus_error; + } + + /// Sets the interrupt lines, IPL0, IPL1 and IPL2. + void set_interrupt_level(int interrupt_level) { + bus_interrupt_level_ = interrupt_level; + } + + /// Sets the bus request line. + /// This are of functionality is TODO. + void set_bus_request(bool bus_request) { + bus_request_ = bus_request; + } + + /// Sets the bus acknowledge line. + /// This are of functionality is TODO. + void set_bus_acknowledge(bool bus_acknowledge) { + bus_acknowledge_ = bus_acknowledge; + } + + /// Sets the halt line. + void set_halt(bool halt) { + halt_ = halt; + } + + private: + T &bus_handler_; +}; + +#include "Implementation/68000Implementation.hpp" + +} +} + +#endif /* MC68000_h */ diff --git a/Processors/68000/Implementation/68000Implementation.hpp b/Processors/68000/Implementation/68000Implementation.hpp new file mode 100644 index 000000000..2244a524d --- /dev/null +++ b/Processors/68000/Implementation/68000Implementation.hpp @@ -0,0 +1,2064 @@ +// +// 68000Implementation.hpp +// Clock Signal +// +// Created by Thomas Harte on 10/03/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#define get_ccr() \ + ( \ + (carry_flag_ ? 0x0001 : 0x0000) | \ + (overflow_flag_ ? 0x0002 : 0x0000) | \ + (zero_result_ ? 0x0000 : 0x0004) | \ + (negative_flag_ ? 0x0008 : 0x0000) | \ + (extend_flag_ ? 0x0010 : 0x0000) \ + ) + +#define get_status() \ + ( \ + get_ccr() | \ + (interrupt_level_ << 8) | \ + (trace_flag_ ? 0x8000 : 0x0000) | \ + (is_supervisor_ << 13) \ + ) + +#define set_ccr(x) \ + carry_flag_ = (x) & 0x0001; \ + overflow_flag_ = (x) & 0x0002; \ + zero_result_ = ((x) & 0x0004) ^ 0x0004; \ + negative_flag_ = (x) & 0x0008; \ + extend_flag_ = (x) & 0x0010; + +#define set_status(x) \ + set_ccr(x) \ + interrupt_level_ = ((x) >> 8) & 7; \ + trace_flag_ = (x) & 0x8000; \ + set_is_supervisor(!!(((x) >> 13) & 1)); + +#define get_bus_code() \ + ((active_step_->microcycle.operation & Microcycle::IsProgram) ? 0x02 : 0x01) | \ + (is_supervisor_ << 2) | \ + (active_program_ ? 0x08 : 0) | \ + ((active_step_->microcycle.operation & Microcycle::Read) ? 0x10 : 0) + +template void Processor::run_for(HalfCycles duration) { + const HalfCycles remaining_duration = duration + half_cycles_left_to_run_; + + // This loop counts upwards rather than downwards because it simplifies calculation of + // E as and when required. + HalfCycles cycles_run_for; + while(cycles_run_for < remaining_duration) { + /* + PERFORM THE CURRENT BUS STEP'S MICROCYCLE. + */ + switch(execution_state_) { + default: + case ExecutionState::Executing: + // Check for entry into the halted state. + if(halt_ && active_step_[0].microcycle.operation & Microcycle::NewAddress) { + execution_state_ = ExecutionState::Halted; + continue; + } + + if(active_step_->microcycle.data_select_active()) { + // TODO: if valid peripheral address is asserted, substitute a + // synhronous bus access. + + // Check whether the processor needs to await DTack. + if(!dtack_is_implicit && !dtack_ && !bus_error_) { + execution_state_ = ExecutionState::WaitingForDTack; + dtack_cycle_ = active_step_->microcycle; + dtack_cycle_.length = HalfCycles(2); + dtack_cycle_.operation &= ~(Microcycle::SelectByte | Microcycle::SelectWord); + continue; + } + + // Check for bus error. + if(bus_error_ && !is_starting_interrupt_) { + const auto offending_address = *active_step_->microcycle.address; + active_program_ = nullptr; + active_micro_op_ = long_exception_micro_ops_; + active_step_ = active_micro_op_->bus_program; + populate_bus_error_steps(2, get_status(), get_bus_code(), offending_address); + } + } + + // Check for an address error. Which I have assumed happens before the microcycle that + // would nominate the new address. + if( + (active_step_[0].microcycle.operation & Microcycle::NewAddress) && + (active_step_[1].microcycle.operation & Microcycle::SelectWord) && + *active_step_->microcycle.address & 1) { + const auto offending_address = *active_step_->microcycle.address; + active_program_ = nullptr; + active_micro_op_ = long_exception_micro_ops_; + active_step_ = active_micro_op_->bus_program; + populate_bus_error_steps(3, get_status(), get_bus_code(), offending_address); + } + + // Perform the microcycle if it is of non-zero length. If this is an operation that + // would normally strobe one of the data selects and VPA is active, it will also need + // stretching. + if(active_step_->microcycle.length != HalfCycles(0)) { + if(is_peripheral_address_ && active_step_->microcycle.data_select_active()) { + auto cycle_copy = active_step_->microcycle; + cycle_copy.operation |= Microcycle::IsPeripheral; + + // Extend length by: (i) distance to next E low, plus (ii) difference between + // current length and a whole E cycle. + cycle_copy.length = HalfCycles(20); // i.e. one E cycle in length. + cycle_copy.length += (e_clock_phase_ + cycles_run_for) % 10; + + cycles_run_for += + cycle_copy.length + + bus_handler_.perform_bus_operation(cycle_copy, is_supervisor_); + } else { + cycles_run_for += + active_step_->microcycle.length + + bus_handler_.perform_bus_operation(active_step_->microcycle, is_supervisor_); + } + } + + /* + PERFORM THE BUS STEP'S ACTION. + */ + switch(active_step_->action) { + default: + std::cerr << "Unimplemented 68000 bus step action: " << int(active_step_->action) << std::endl; + return; + break; + + case BusStep::Action::None: break; + + case BusStep::Action::IncrementEffectiveAddress0: effective_address_[0].full += 2; break; + case BusStep::Action::IncrementEffectiveAddress1: effective_address_[1].full += 2; break; + case BusStep::Action::DecrementEffectiveAddress0: effective_address_[0].full -= 2; break; + case BusStep::Action::DecrementEffectiveAddress1: effective_address_[1].full -= 2; break; + case BusStep::Action::IncrementProgramCounter: program_counter_.full += 2; break; + + case BusStep::Action::AdvancePrefetch: + prefetch_queue_.halves.high = prefetch_queue_.halves.low; + break; + } + + // Move to the next bus step. + ++ active_step_; + break; + + case ExecutionState::Stopped: + // If an interrupt (TODO: or reset) has finally arrived that will be serviced, + // exit the STOP. + if(bus_interrupt_level_ > interrupt_level_) { + // TODO: schedule interrupt right here. + execution_state_ = ExecutionState::BeginInterrupt; + continue; + } + + // Otherwise continue being stopped. + cycles_run_for += + stop_cycle_.length + + bus_handler_.perform_bus_operation(stop_cycle_, is_supervisor_); + continue; + + case ExecutionState::WaitingForDTack: + // If DTack or bus error has been signalled, stop waiting. + if(dtack_ || bus_error_) { + execution_state_ = ExecutionState::Executing; + continue; + } + + // Otherwise, signal another cycle of wait. + cycles_run_for += + dtack_cycle_.length + + bus_handler_.perform_bus_operation(dtack_cycle_, is_supervisor_); + continue; + + case ExecutionState::Halted: + if(!halt_) { + execution_state_ = ExecutionState::Executing; + continue; + } + + cycles_run_for += + stop_cycle_.length + + bus_handler_.perform_bus_operation(stop_cycle_, is_supervisor_); + continue; + + case ExecutionState::BeginInterrupt: + active_program_ = nullptr; + active_micro_op_ = interrupt_micro_ops_; + execution_state_ = ExecutionState::Executing; + active_step_ = active_micro_op_->bus_program; + is_starting_interrupt_ = true; + break; + } + +#ifdef LOG_TRACE + if(!(active_step_->microcycle.operation & Microcycle::IsProgram)) { + switch(active_step_->microcycle.operation & (Microcycle::SelectWord | Microcycle::SelectByte | Microcycle::Read)) { + default: break; + + case Microcycle::SelectWord | Microcycle::Read: + printf("[%08x -> %04x] ", *active_step_->microcycle.address, active_step_->microcycle.value->full); + break; + case Microcycle::SelectByte | Microcycle::Read: + printf("[%08x -> %02x] ", *active_step_->microcycle.address, active_step_->microcycle.value->halves.low); + break; + case Microcycle::SelectWord: + printf("{%04x -> %08x} ", active_step_->microcycle.value->full, *active_step_->microcycle.address); + break; + case Microcycle::SelectByte: + printf("{%02x -> %08x} ", active_step_->microcycle.value->halves.low, *active_step_->microcycle.address); + break; + } + } +#endif + + /* + FIND THE NEXT MICRO-OP IF UNKNOWN. + */ + if(active_step_->is_terminal()) { + while(true) { + // If there are any more micro-operations available, just move onwards. + if(active_micro_op_ && !active_micro_op_->is_terminal()) { + ++active_micro_op_; + } else { + // Either the micro-operations for this instruction have been exhausted, or + // no instruction was ongoing. Either way, do a standard instruction operation. + + if(bus_interrupt_level_ > interrupt_level_) { + execution_state_ = ExecutionState::BeginInterrupt; + break; + } + + if(trace_flag_) { + // The user has set the trace bit in the status register. + active_program_ = nullptr; + active_micro_op_ = short_exception_micro_ops_; + populate_trap_steps(9, get_status()); + } else { +#ifdef LOG_TRACE + std::cout << std::setfill('0'); + std::cout << (extend_flag_ ? 'x' : '-') << (negative_flag_ ? 'n' : '-') << (zero_result_ ? '-' : 'z'); + std::cout << (overflow_flag_ ? 'v' : '-') << (carry_flag_ ? 'c' : '-') << '\t'; + for(int c = 0; c < 8; ++ c) std::cout << "d" << c << ":" << std::setw(8) << data_[c].full << " "; + for(int c = 0; c < 8; ++ c) std::cout << "a" << c << ":" << std::setw(8) << address_[c].full << " "; + if(is_supervisor_) { + std::cout << "usp:" << std::setw(8) << std::setfill('0') << stack_pointers_[0].full << " "; + } else { + std::cout << "ssp:" << std::setw(8) << std::setfill('0') << stack_pointers_[1].full << " "; + } + std::cout << '\n'; +#endif + + decoded_instruction_.full = prefetch_queue_.halves.high.full; +#ifdef LOG_TRACE + std::cout << std::hex << (program_counter_.full - 4) << ": " << std::setw(4) << decoded_instruction_.full << '\t'; +#endif + + if(signal_will_perform) { + bus_handler_.will_perform(program_counter_.full - 4, decoded_instruction_.full); + } + + if(instructions[decoded_instruction_.full].micro_operations) { + if(instructions[decoded_instruction_.full].requires_supervisor && !is_supervisor_) { + // A privilege violation has been detected. + active_program_ = nullptr; + active_micro_op_ = short_exception_micro_ops_; + populate_trap_steps(8, get_status()); + } else { + // Standard instruction dispatch. + active_program_ = &instructions[decoded_instruction_.full]; + active_micro_op_ = active_program_->micro_operations; + } + } else { + // The opcode fetched isn't valid. + active_program_ = nullptr; + active_micro_op_ = short_exception_micro_ops_; + + // The vector used depends on whether this is a vanilla unrecognised instruction, + // or one on the A or F lines. + switch(decoded_instruction_.full >> 12) { + default: populate_trap_steps(4, get_status()); break; + case 0xa: populate_trap_steps(10, get_status()); break; + case 0xf: populate_trap_steps(11, get_status()); break; + } + } + } + } + + auto bus_program = active_micro_op_->bus_program; + switch(active_micro_op_->action) { + default: + std::cerr << "Unhandled 68000 micro op action " << std::hex << active_micro_op_->action << " within instruction " << decoded_instruction_.full << std::endl; + break; + + case int(MicroOp::Action::None): break; + + case int(MicroOp::Action::PerformOperation): +#define sub_overflow() ((result ^ destination) & (destination ^ source)) +#define add_overflow() ((result ^ destination) & ~(destination ^ source)) + switch(active_program_->operation) { + /* + ABCD adds the lowest bytes form the source and destination using BCD arithmetic, + obeying the extend flag. + */ + case Operation::ABCD: { + // Pull out the two halves, for simplicity. + const uint8_t source = active_program_->source->halves.low.halves.low; + const uint8_t destination = active_program_->destination->halves.low.halves.low; + + // Perform the BCD add by evaluating the two nibbles separately. + int result = (destination & 0xf) + (source & 0xf) + (extend_flag_ ? 1 : 0); + if(result > 0x09) result += 0x06; + result += (destination & 0xf0) + (source & 0xf0); + if(result > 0x99) result += 0x60; + + // Set all flags essentially as if this were normal addition. + zero_result_ |= result & 0xff; + extend_flag_ = carry_flag_ = result & ~0xff; + negative_flag_ = result & 0x80; + overflow_flag_ = add_overflow() & 0x80; + + // Store the result. + active_program_->destination->halves.low.halves.low = uint8_t(result); + } break; + + // ADD and ADDA add two quantities, the latter sign extending and without setting any flags; + // ADDQ and SUBQ act as ADD and SUB, but taking the second argument from the instruction code. +#define addop(a, b, x) a + b + (x ? 1 : 0) +#define subop(a, b, x) a - b - (x ? 1 : 0) +#define z_set(a, b) a = b +#define z_or(a, b) a |= b + +#define addsubb(a, b, dest, op, overflow, x, zero_op) \ + const int source = a; \ + const int destination = b; \ + const auto result = op(destination, source, x); \ +\ + dest = uint8_t(result); \ + zero_op(zero_result_, dest); \ + extend_flag_ = carry_flag_ = result & ~0xff; \ + negative_flag_ = result & 0x80; \ + overflow_flag_ = overflow() & 0x80; + +#define addsubw(a, b, dest, op, overflow, x, zero_op) \ + const int source = a; \ + const int destination = b; \ + const auto result = op(destination, source, x); \ +\ + dest = uint16_t(result); \ + zero_op(zero_result_, dest); \ + extend_flag_ = carry_flag_ = result & ~0xffff; \ + negative_flag_ = result & 0x8000; \ + overflow_flag_ = overflow() & 0x8000; + +#define addsubl(a, b, dest, op, overflow, x, zero_op) \ + const uint64_t source = a; \ + const uint64_t destination = b; \ + const auto result = op(destination, source, x); \ +\ + dest = uint32_t(result); \ + zero_op(zero_result_, dest); \ + extend_flag_ = carry_flag_ = result >> 32; \ + negative_flag_ = result & 0x80000000; \ + overflow_flag_ = overflow() & 0x80000000; + +#define addb(a, b, dest, x, z) addsubb(a, b, dest, addop, add_overflow, x, z) +#define subb(a, b, dest, x, z) addsubb(a, b, dest, subop, sub_overflow, x, z) +#define addw(a, b, dest, x, z) addsubw(a, b, dest, addop, add_overflow, x, z) +#define subw(a, b, dest, x, z) addsubw(a, b, dest, subop, sub_overflow, x, z) +#define addl(a, b, dest, x, z) addsubl(a, b, dest, addop, add_overflow, x, z) +#define subl(a, b, dest, x, z) addsubl(a, b, dest, subop, sub_overflow, x, z) + +#define no_extend(op, a, b, c) op(a, b, c, 0, z_set) +#define extend(op, a, b, c) op(a, b, c, extend_flag_, z_or) + +#define q() (((decoded_instruction_.full >> 9)&7) ? ((decoded_instruction_.full >> 9)&7) : 8) + + case Operation::ADDb: { + no_extend( addb, + active_program_->source->halves.low.halves.low, + active_program_->destination->halves.low.halves.low, + active_program_->destination->halves.low.halves.low); + } break; + + case Operation::ADDXb: { + extend( addb, + active_program_->source->halves.low.halves.low, + active_program_->destination->halves.low.halves.low, + active_program_->destination->halves.low.halves.low); + } break; + + case Operation::ADDQb: { + no_extend( addb, + q(), + active_program_->destination->halves.low.halves.low, + active_program_->destination->halves.low.halves.low); + } break; + + case Operation::ADDw: { + no_extend( addw, + active_program_->source->halves.low.full, + active_program_->destination->halves.low.full, + active_program_->destination->halves.low.full); + } break; + + case Operation::ADDXw: { + extend( addw, + active_program_->source->halves.low.full, + active_program_->destination->halves.low.full, + active_program_->destination->halves.low.full); + } break; + + case Operation::ADDQw: { + no_extend( addw, + q(), + active_program_->destination->halves.low.full, + active_program_->destination->halves.low.full); + } break; + + case Operation::ADDl: { + no_extend( addl, + active_program_->source->full, + active_program_->destination->full, + active_program_->destination->full); + } break; + + case Operation::ADDXl: { + extend( addl, + active_program_->source->full, + active_program_->destination->full, + active_program_->destination->full); + } break; + + case Operation::ADDQl: { + no_extend( addl, + q(), + active_program_->destination->full, + active_program_->destination->full); + } break; + + case Operation::SUBb: { + no_extend( subb, + active_program_->source->halves.low.halves.low, + active_program_->destination->halves.low.halves.low, + active_program_->destination->halves.low.halves.low); + } break; + + case Operation::SUBXb: { + extend( subb, + active_program_->source->halves.low.halves.low, + active_program_->destination->halves.low.halves.low, + active_program_->destination->halves.low.halves.low); + } break; + + case Operation::SUBQb: { + no_extend( subb, + q(), + active_program_->destination->halves.low.halves.low, + active_program_->destination->halves.low.halves.low); + } break; + + case Operation::SUBw: { + no_extend( subw, + active_program_->source->halves.low.full, + active_program_->destination->halves.low.full, + active_program_->destination->halves.low.full); + } break; + + case Operation::SUBXw: { + extend( subw, + active_program_->source->halves.low.full, + active_program_->destination->halves.low.full, + active_program_->destination->halves.low.full); + } break; + + case Operation::SUBQw: { + no_extend( subw, + q(), + active_program_->destination->halves.low.full, + active_program_->destination->halves.low.full); + } break; + + case Operation::SUBl: { + no_extend( subl, + active_program_->source->full, + active_program_->destination->full, + active_program_->destination->full); + } break; + + case Operation::SUBXl: { + extend( subl, + active_program_->source->full, + active_program_->destination->full, + active_program_->destination->full); + } break; + + case Operation::SUBQl: { + no_extend( subl, + q(), + active_program_->destination->full, + active_program_->destination->full); + } break; + + case Operation::ADDQAl: + active_program_->destination->full += q(); + break; + + case Operation::SUBQAl: + active_program_->destination->full -= q(); + break; + +#undef addl +#undef addw +#undef addb +#undef subl +#undef subw +#undef subb +#undef addsubl +#undef addsubw +#undef addsubb +#undef q +#undef z_set +#undef z_or +#undef no_extend +#undef extend +#undef addop +#undef subop + + + case Operation::ADDAw: + active_program_->destination->full += int16_t(active_program_->source->halves.low.full); + break; + + case Operation::ADDAl: + active_program_->destination->full += active_program_->source->full; + break; + + case Operation::SUBAw: + active_program_->destination->full -= int16_t(active_program_->source->halves.low.full); + break; + + case Operation::SUBAl: + active_program_->destination->full -= active_program_->source->full; + break; + + + // BRA: alters the program counter, exclusively via the prefetch queue. + case Operation::BRA: { + const int8_t byte_offset = int8_t(prefetch_queue_.halves.high.halves.low); + + // A non-zero offset byte branches by just that amount; otherwise use the word + // after as an offset. In both cases, treat as signed. + if(byte_offset) { + program_counter_.full = (program_counter_.full + byte_offset); + } else { + program_counter_.full += int16_t(prefetch_queue_.halves.low.full); + } + program_counter_.full -= 2; + } break; + + // Two BTSTs: set the zero flag according to the value of the destination masked by + // the bit named in the source modulo the operation size. + case Operation::BTSTb: + zero_result_ = active_program_->destination->full & (1 << (active_program_->source->full & 7)); + break; + + case Operation::BTSTl: + zero_result_ = active_program_->destination->full & (1 << (active_program_->source->full & 31)); + break; + + case Operation::BCLRb: + zero_result_ = active_program_->destination->full & (1 << (active_program_->source->full & 7)); + active_program_->destination->full &= ~(1 << (active_program_->source->full & 7)); + break; + + case Operation::BCLRl: + zero_result_ = active_program_->destination->full & (1 << (active_program_->source->full & 31)); + active_program_->destination->full &= ~(1 << (active_program_->source->full & 31)); + + // Clearing in the top word requires an extra four cycles. + active_step_->microcycle.length = HalfCycles(8 + ((active_program_->source->full & 31) / 16) * 4); + break; + + case Operation::BCHGl: + zero_result_ = active_program_->destination->full & (1 << (active_program_->source->full & 31)); + active_program_->destination->full ^= 1 << (active_program_->source->full & 31); + active_step_->microcycle.length = HalfCycles(4 + (((active_program_->source->full & 31) / 16) * 4)); + break; + + case Operation::BCHGb: + zero_result_ = active_program_->destination->halves.low.halves.low & (1 << (active_program_->source->full & 7)); + active_program_->destination->halves.low.halves.low ^= 1 << (active_program_->source->full & 7); + break; + + case Operation::BSETl: + zero_result_ = active_program_->destination->full & (1 << (active_program_->source->full & 31)); + active_program_->destination->full |= 1 << (active_program_->source->full & 31); + active_step_->microcycle.length = HalfCycles(4 + (((active_program_->source->full & 31) / 16) * 4)); + break; + + case Operation::BSETb: + zero_result_ = active_program_->destination->halves.low.halves.low & (1 << (active_program_->source->full & 7)); + active_program_->destination->halves.low.halves.low |= 1 << (active_program_->source->full & 7); + break; + + // Bcc: ordinarily evaluates the relevant condition and displacement size and then: + // if condition is false, schedules bus operations to get past this instruction; + // otherwise applies the offset and schedules bus operations to refill the prefetch queue. + // + // Special case: the condition code is 1, which is ordinarily false. In that case this + // is the trailing step of a BSR. + case Operation::Bcc: { + // Grab the 8-bit offset. + const int8_t byte_offset = int8_t(prefetch_queue_.halves.high.halves.low); + + // Check whether this is secretly BSR. + const bool is_bsr = ((decoded_instruction_.full >> 8) & 0xf) == 1; + + // Test the conditional, treating 'false' as true. + const bool should_branch = is_bsr || evaluate_condition(decoded_instruction_.full >> 8); + + // Schedule something appropriate, by rewriting the program for this instruction temporarily. + if(should_branch) { + if(byte_offset) { + program_counter_.full = (program_counter_.full + byte_offset); + } else { + program_counter_.full += int16_t(prefetch_queue_.halves.low.full); + } + program_counter_.full -= 2; + bus_program = is_bsr ? bsr_bus_steps_ : branch_taken_bus_steps_; + } else { + if(byte_offset) { + bus_program = branch_byte_not_taken_bus_steps_; + } else { + bus_program = branch_word_not_taken_bus_steps_; + } + } + } break; + + case Operation::DBcc: { + // Decide what sort of DBcc this is. + if(!evaluate_condition(decoded_instruction_.full >> 8)) { + -- active_program_->source->halves.low.full; + const auto target_program_counter = program_counter_.full + int16_t(prefetch_queue_.halves.low.full) - 2; + + if(active_program_->source->halves.low.full == 0xffff) { + // This DBcc will be ignored as the counter has underflowed. + // Schedule n np np np and continue. Assumed: the first np + // is from where the branch would have been if taken? + bus_program = dbcc_condition_false_no_branch_steps_; + dbcc_false_address_ = target_program_counter; + } else { + // Take the branch. Change PC and schedule n np np. + bus_program = dbcc_condition_false_branch_steps_; + program_counter_.full = target_program_counter; + } + } else { + // This DBcc will be ignored as the condition is true; + // perform nn np np and continue. + bus_program = dbcc_condition_true_steps_; + } + } break; + + case Operation::Scc: { + active_program_->destination->halves.low.halves.low = + evaluate_condition(decoded_instruction_.full >> 8) ? 0xff : 0x00; + } break; + + /* + CLRs: store 0 to the destination, set the zero flag, and clear + negative, overflow and carry. + */ + case Operation::CLRb: + active_program_->destination->halves.low.halves.low = 0; + negative_flag_ = overflow_flag_ = carry_flag_ = zero_result_ = 0; + break; + + case Operation::CLRw: + active_program_->destination->halves.low.full = 0; + negative_flag_ = overflow_flag_ = carry_flag_ = zero_result_ = 0; + break; + + case Operation::CLRl: + active_program_->destination->full = 0; + negative_flag_ = overflow_flag_ = carry_flag_ = zero_result_ = 0; + break; + + /* + CMP.b, CMP.l and CMP.w: sets the condition flags (other than extend) based on a subtraction + of the source from the destination; the result of the subtraction is not stored. + */ + case Operation::CMPb: { + const uint8_t source = active_program_->source->halves.low.halves.low; + const uint8_t destination = active_program_->destination->halves.low.halves.low; + const int result = destination - source; + + zero_result_ = result & 0xff; + carry_flag_ = result & ~0xff; + negative_flag_ = result & 0x80; + overflow_flag_ = sub_overflow() & 0x80; + } break; + + case Operation::CMPw: { + const uint16_t source = active_program_->source->halves.low.full; + const uint16_t destination = active_program_->destination->halves.low.full; + const int result = destination - source; + + zero_result_ = result & 0xffff; + carry_flag_ = result & ~0xffff; + negative_flag_ = result & 0x8000; + overflow_flag_ = sub_overflow() & 0x8000; + } break; + + case Operation::CMPl: { + const auto source = uint64_t(active_program_->source->full); + const auto destination = uint64_t(active_program_->destination->full); + const auto result = destination - source; + + zero_result_ = uint32_t(result); + carry_flag_ = result >> 32; + negative_flag_ = result & 0x80000000; + overflow_flag_ = sub_overflow() & 0x80000000; + } break; + + // JMP: copies the source to the program counter. + case Operation::JMP: + program_counter_.full = active_program_->source->full; + break; + + /* + MOVE.b, MOVE.l and MOVE.w: move the least significant byte or word, or the entire long word, + and set negative, zero, overflow and carry as appropriate. + */ + case Operation::MOVEb: + zero_result_ = active_program_->destination->halves.low.halves.low = active_program_->source->halves.low.halves.low; + negative_flag_ = zero_result_ & 0x80; + overflow_flag_ = carry_flag_ = 0; + break; + + case Operation::MOVEw: + zero_result_ = active_program_->destination->halves.low.full = active_program_->source->halves.low.full; + negative_flag_ = zero_result_ & 0x8000; + overflow_flag_ = carry_flag_ = 0; + break; + + case Operation::MOVEl: + zero_result_ = active_program_->destination->full = active_program_->source->full; + negative_flag_ = zero_result_ & 0x80000000; + overflow_flag_ = carry_flag_ = 0; + break; + + /* + MOVE.q: a single byte is moved from the current instruction, and sign extended. + */ + case Operation::MOVEq: + zero_result_ = active_program_->destination->full = prefetch_queue_.halves.high.halves.low; + negative_flag_ = zero_result_ & 0x80; + overflow_flag_ = carry_flag_ = 0; + active_program_->destination->full |= negative_flag_ ? 0xffffff00 : 0; + break; + + /* + MOVEA.l: move the entire long word; + MOVEA.w: move the least significant word and sign extend it. + Neither sets any flags. + */ + case Operation::MOVEAw: + active_program_->destination->halves.low.full = active_program_->source->halves.low.full; + active_program_->destination->halves.high.full = (active_program_->destination->halves.low.full & 0x8000) ? 0xffff : 0; + break; + + case Operation::MOVEAl: + active_program_->destination->full = active_program_->source->full; + break; + + /* + Status word moves and manipulations. + */ + + case Operation::MOVEtoSR: + set_status(active_program_->source->full); + break; + + case Operation::MOVEfromSR: + active_program_->destination->halves.low.full = get_status(); + break; + + case Operation::MOVEtoCCR: + set_ccr(active_program_->source->full); + break; + + case Operation::EXTbtow: + active_program_->destination->halves.low.halves.high = + (active_program_->destination->halves.low.halves.low & 0x80) ? 0xff : 0x00; + overflow_flag_ = carry_flag_ = 0; + zero_result_ = active_program_->destination->halves.low.full; + negative_flag_ = zero_result_ & 0x8000; + break; + + case Operation::EXTwtol: + active_program_->destination->halves.high.full = + (active_program_->destination->halves.low.full & 0x8000) ? 0xffff : 0x0000; + overflow_flag_ = carry_flag_ = 0; + zero_result_ = active_program_->destination->full; + negative_flag_ = zero_result_ & 0x80000000; + break; + +#define and_op(a, b) a &= b +#define or_op(a, b) a |= b +#define eor_op(a, b) a ^= b + +#define apply(op, func) {\ + auto status = get_status(); \ + op(status, prefetch_queue_.halves.high.full); \ + func(status); \ + program_counter_.full -= 2; \ +} + +#define apply_sr(op) apply(op, set_status) +#define apply_ccr(op) apply(op, set_ccr) + + case Operation::ANDItoSR: apply_sr(and_op); break; + case Operation::EORItoSR: apply_sr(eor_op); break; + case Operation::ORItoSR: apply_sr(or_op); break; + + case Operation::ANDItoCCR: apply_ccr(and_op); break; + case Operation::EORItoCCR: apply_ccr(eor_op); break; + case Operation::ORItoCCR: apply_ccr(or_op); break; + +#undef apply_ccr +#undef apply_sr +#undef apply +#undef eor_op +#undef or_op +#undef and_op + + /* + Multiplications. + */ + + case Operation::MULU: { + active_program_->destination->full = + active_program_->destination->halves.low.full * active_program_->source->halves.low.full; + carry_flag_ = overflow_flag_ = 0; // TODO: "set if overflow". + zero_result_ = active_program_->destination->full; + negative_flag_ = zero_result_ & 0x80000000; + + // TODO: optimise the below? + int number_of_ones = 0; + auto source = active_program_->source->halves.low.full; + while(source) { + source >>= 1; + ++number_of_ones; + } + + // Time taken = 38 cycles + 2 cycles per 1 in the source. + active_step_->microcycle.length = HalfCycles(4 * number_of_ones + 38*2); + } break; + + case Operation::MULS: { + active_program_->destination->full = + int16_t(active_program_->destination->halves.low.full) * int16_t(active_program_->source->halves.low.full); + carry_flag_ = overflow_flag_ = 0; // TODO: "set if overflow". + zero_result_ = active_program_->destination->full; + negative_flag_ = zero_result_ & 0x80000000; + + // TODO: optimise the below? + int number_of_pairs = 0; + int source = active_program_->source->halves.low.full; + int bit = 0; + while(source | bit) { + number_of_pairs += (bit ^ source) & 1; + bit = source & 1; + source >>= 1; + } + + // Time taken = 38 cycles + 2 cycles per 1 in the source. + active_step_->microcycle.length = HalfCycles(4 * number_of_pairs + 38*2); + } break; + + /* + Divisions. + */ + +#define announce_divide_by_zero() \ + active_program_ = nullptr; \ + active_micro_op_ = short_exception_micro_ops_; \ + bus_program = active_micro_op_->bus_program; \ + \ + populate_trap_steps(5, get_status()); \ + bus_program->microcycle.length = HalfCycles(8); \ + \ + program_counter_.full -= 2; + + case Operation::DIVU: { + // An attempt to divide by zero schedules an exception. + if(!active_program_->source->halves.low.full) { + // Schedule a divide-by-zero exception. + announce_divide_by_zero(); + break; + } + + uint32_t dividend = active_program_->destination->full; + uint32_t divisor = active_program_->source->halves.low.full; + const auto quotient = dividend / divisor; + + carry_flag_ = 0; + + // If overflow would occur, appropriate flags are set and the result is not written back. + if(quotient >= 65536) { + overflow_flag_ = 1; + // TODO: is what should happen to the other flags known? + active_step_->microcycle.length = HalfCycles(3*2*2); + break; + } + + const uint16_t remainder = uint16_t(dividend % divisor); + active_program_->destination->halves.high.full = remainder; + active_program_->destination->halves.low.full = quotient; + + overflow_flag_ = 0; + zero_result_ = quotient; + negative_flag_ = zero_result_ & 0x8000; + + // Calculate cost; this is based on the flowchart in yacht.txt. + // I could actually calculate the division result here, since this is + // a classic divide algorithm, but would rather that errors produce + // incorrect timing only, not incorrect timing plus incorrect results. + int cycles_expended = 6; // Covers the nn n to get into the loop. + + divisor <<= 16; + for(int c = 0; c < 15; ++c) { + if(dividend & 0x80000000) { + dividend = (dividend << 1) - divisor; + cycles_expended += 4; // Easy; just the fixed nn iteration cost. + } else { + dividend <<= 1; + + // Yacht.txt, and indeed a real microprogram, would just subtract here + // and test the sign of the result, but this is easier to follow: + if (dividend >= divisor) { + dividend -= divisor; + cycles_expended += 6; // i.e. the original nn plus one further n before going down the MSB=0 route. + } else { + cycles_expended += 8; // The costliest path (since in real life it's a subtraction and then a step + // back from there) — all costs accrue. So the fixed nn loop plus another n, + // plus another one. + } + } + } + active_step_->microcycle.length = HalfCycles(cycles_expended * 2); + } break; + + case Operation::DIVS: { + // An attempt to divide by zero schedules an exception. + if(!active_program_->source->halves.low.full) { + // Schedule a divide-by-zero exception. + announce_divide_by_zero() + break; + } + + int32_t dividend = int32_t(active_program_->destination->full); + int32_t divisor = int16_t(active_program_->source->halves.low.full); + const auto quotient = dividend / divisor; + + int cycles_expended = 12; // Covers the nn nnn n to get beyond the sign test. + if(dividend < 0) { + cycles_expended += 2; // An additional microycle applies if the dividend is negative. + } + + // Check for overflow. If it exists, work here is already done. + if(quotient > 32767 || quotient < -32768) { + overflow_flag_ = 1; + active_step_->microcycle.length = HalfCycles(3*2*2); + break; + } + + overflow_flag_ = 0; + zero_result_ = quotient; + negative_flag_ = zero_result_ & 0x8000; + + // TODO: check sign rules here; am I necessarily giving the remainder the correct sign? + // (and, if not, am I counting it in the correct direction?) + const uint16_t remainder = uint16_t(dividend % divisor); + active_program_->destination->halves.high.full = remainder; + active_program_->destination->halves.low.full = quotient; + + // Algorithm here: there is a fixed three-microcycle cost per bit set + // in the unsigned quotient; there is an additional microcycle for + // every bit that is set. Also, since the possibility of overflow + // was already dealt with, it's now a smaller number. + int positive_quotient = abs(quotient); + for(int c = 0; c < 15; ++c) { + if(positive_quotient & 0x8000) cycles_expended += 2; + } + + // There's then no way to terminate the loop that isn't at least six cycles long. + cycles_expended += 6; + + if(divisor < 0) { + cycles_expended += 2; + } else if(dividend < 0) { + cycles_expended += 4; + } + active_step_->microcycle.length = HalfCycles(cycles_expended * 2); + } break; + +#undef announce_divide_by_zero + + /* + MOVEP: move words and long-words a byte at a time. + */ + + case Operation::MOVEPtoMw: + // Write pattern is nW+ nw, which should write the low word of the source in big-endian form. + destination_bus_data_[0].halves.high.full = active_program_->source->halves.low.halves.high; + destination_bus_data_[0].halves.low.full = active_program_->source->halves.low.halves.low; + break; + + case Operation::MOVEPtoMl: + // Write pattern is nW+ nWr+ nw+ nwr, which should write the source in big-endian form. + destination_bus_data_[0].halves.high.full = active_program_->source->halves.high.halves.high; + source_bus_data_[0].halves.high.full = active_program_->source->halves.high.halves.low; + destination_bus_data_[0].halves.low.full = active_program_->source->halves.low.halves.high; + source_bus_data_[0].halves.low.full = active_program_->source->halves.low.halves.low; + break; + + case Operation::MOVEPtoRw: + // Read pattern is nRd+ nrd. + active_program_->source->halves.low.halves.high = destination_bus_data_[0].halves.high.full; + active_program_->source->halves.low.halves.low = destination_bus_data_[0].halves.low.full; + break; + + case Operation::MOVEPtoRl: + // Read pattern is nRd+ nR+ nrd+ nr. + active_program_->source->halves.high.halves.high = destination_bus_data_[0].halves.high.full; + active_program_->source->halves.high.halves.low = source_bus_data_[0].halves.high.full; + active_program_->source->halves.low.halves.high = destination_bus_data_[0].halves.low.full; + active_program_->source->halves.low.halves.low = source_bus_data_[0].halves.low.full; + break; + + /* + MOVEM: multi-word moves. + */ + +#define setup_movem(words_per_reg, base) \ + /* Count the number of long words to move. */ \ + size_t total_to_move = 0; \ + auto mask = next_word_; \ + while(mask) { \ + total_to_move += mask&1; \ + mask >>= 1; \ + } \ + \ + /* Twice that many words plus one will need to be moved */ \ + bus_program = base + (64 - total_to_move*words_per_reg)*2; \ + \ + /* Fill in the proper addresses and targets. */ \ + const auto mode = (decoded_instruction_.full >> 3) & 7; \ + uint32_t start_address; \ + if(mode <= 4) { \ + start_address = active_program_->destination_address->full; \ + } else { \ + start_address = effective_address_[1].full; \ + } \ + \ + auto step = bus_program; \ + uint32_t *address_storage = precomputed_addresses_; \ + mask = next_word_; \ + int offset = 0; + +#define inc_action(x, v) x += v +#define dec_action(x, v) x -= v + +#define write_address_sequence_long(action, l) \ + while(mask) { \ + if(mask&1) { \ + address_storage[0] = start_address; \ + action(start_address, 2); \ + address_storage[1] = start_address; \ + action(start_address, 2); \ + \ + step[0].microcycle.address = step[1].microcycle.address = address_storage; \ + step[2].microcycle.address = step[3].microcycle.address = address_storage + 1; \ + \ + const auto target = (offset > 7) ? &address_[offset&7] : &data_[offset]; \ + step[l].microcycle.value = step[l+1].microcycle.value = &target->halves.high; \ + step[(l^2)].microcycle.value = step[(l^2)+1].microcycle.value = &target->halves.low; \ + \ + address_storage += 2; \ + step += 4; \ + } \ + mask >>= 1; \ + action(offset, 1); \ + } + +#define write_address_sequence_word(action) \ + while(mask) { \ + if(mask&1) { \ + address_storage[0] = start_address; \ + action(start_address, 2); \ + \ + step[0].microcycle.address = step[1].microcycle.address = address_storage; \ + \ + const auto target = (offset > 7) ? &address_[offset&7] : &data_[offset]; \ + step[0].microcycle.value = step[1].microcycle.value = &target->halves.low; \ + \ + ++ address_storage; \ + step += 2; \ + } \ + mask >>= 1; \ + action(offset, 1); \ + } + + case Operation::MOVEMtoRl: { + setup_movem(2, movem_read_steps_); + + // Everything for move to registers is based on an incrementing + // address; per M68000PRM: + // + // "[If using the postincrement addressing mode then] the incremented address + // register contains the address of the last operand loaded plus the operand length. + // If the addressing register is also loaded from memory, the memory value is ignored + // and the register is written with the postincremented effective address." + // + // The latter part is dealt with by MicroOp::Action::MOVEMtoRComplete, which also + // does any necessary sign extension. + write_address_sequence_long(inc_action, 0); + + // MOVEM to R always reads one word too many. + address_storage[0] = start_address; + step[0].microcycle.address = step[1].microcycle.address = address_storage; + step[0].microcycle.value = step[1].microcycle.value = &throwaway_value_; + movem_final_address_ = start_address; + } break; + + case Operation::MOVEMtoRw: { + setup_movem(1, movem_read_steps_); + write_address_sequence_word(inc_action); + + // MOVEM to R always reads one word too many. + address_storage[0] = start_address; + step[0].microcycle.address = step[1].microcycle.address = address_storage; + step[0].microcycle.value = step[1].microcycle.value = &throwaway_value_; + movem_final_address_ = start_address; + } break; + + case Operation::MOVEMtoMl: { + setup_movem(2, movem_write_steps_); + + // MOVEM to M counts downwards and enumerates the registers in reverse order + // if subject to the predecrementing mode; otherwise it counts upwards and + // operates exactly as does MOVEM to R. + // + // Note also: "The MC68000 and MC68010 write the initial register value + // (not decremented) [when writing a register that is providing + // pre-decrementing addressing]." + // + // Hence the decrementing register (if any) is updated + // by MicroOp::Action::MOVEMtoMComplete. + if(mode == 4) { + offset = 15; + start_address -= 2; + write_address_sequence_long(dec_action, 2); + movem_final_address_ = start_address + 2; + } else { + write_address_sequence_long(inc_action, 0); + } + } break; + + case Operation::MOVEMtoMw: { + setup_movem(1, movem_write_steps_); + + if(mode == 4) { + offset = 15; + start_address -= 2; + write_address_sequence_word(dec_action); + movem_final_address_ = start_address + 2; + } else { + write_address_sequence_word(inc_action); + } + } break; + +#undef setup_movem +#undef write_address_sequence_long +#undef write_address_sequence_word +#undef inc_action +#undef dec_action + + // TRAP, which is a nicer form of ILLEGAL. + case Operation::TRAP: { + // Select the trap steps as next; the initial microcycle should be 4 cycles long. + bus_program = trap_steps_; + populate_trap_steps((decoded_instruction_.full & 15) + 32, get_status()); + bus_program->microcycle.length = HalfCycles(8); + + // The program counter to push is actually one slot ago. + program_counter_.full -= 2; + } break; + + case Operation::TRAPV: { + if(overflow_flag_) { + // Select the trap steps as next; the initial microcycle should be 4 cycles long. + bus_program = trap_steps_; + populate_trap_steps(7, get_status()); + bus_program->microcycle.length = HalfCycles(0); + program_counter_.full -= 4; + } + } break; + + case Operation::CHK: { + const bool is_under = int16_t(active_program_->destination->halves.low.full) < 0; + const bool is_over = int16_t(active_program_->destination->halves.low.full) > int16_t(active_program_->source->halves.low.full); + + // No exception is the default course of action; deviate only if an + // exception is necessary. + if(is_under || is_over) { + negative_flag_ = is_under ? 1 : 0; + + bus_program = trap_steps_; + populate_trap_steps(6, get_status()); + if(is_under) { + bus_program->microcycle.length = HalfCycles(16); + } else { + bus_program->microcycle.length = HalfCycles(8); + } + + // The program counter to push is two slots ago as whatever was the correct prefetch + // to continue without an exception has already happened, just in case. + program_counter_.full -= 4; + } + } break; + + /* + NEGs: negatives the destination, setting the zero, + negative, overflow and carry flags appropriate, and extend. + + NB: since the same logic as SUB is used to calculate overflow, + and SUB calculates `destination - source`, the NEGs deliberately + label 'source' and 'destination' differently from Motorola. + */ + case Operation::NEGb: { + const int destination = 0; + const int source = active_program_->destination->halves.low.halves.low; + const auto result = destination - source; + active_program_->destination->halves.low.halves.low = result; + + zero_result_ = result & 0xff; + extend_flag_ = carry_flag_ = result & ~0xff; + negative_flag_ = result & 0x80; + overflow_flag_ = sub_overflow() & 0x80; + } break; + + case Operation::NEGw: { + const int destination = 0; + const int source = active_program_->destination->halves.low.full; + const auto result = destination - source; + active_program_->destination->halves.low.full = result; + + zero_result_ = result & 0xffff; + extend_flag_ = carry_flag_ = result & ~0xffff; + negative_flag_ = result & 0x8000; + overflow_flag_ = sub_overflow() & 0x8000; + } break; + + case Operation::NEGl: { + const uint64_t destination = 0; + const uint64_t source = active_program_->destination->full; + const auto result = destination - source; + active_program_->destination->full = uint32_t(result); + + zero_result_ = uint_fast32_t(result); + extend_flag_ = carry_flag_ = result >> 32; + negative_flag_ = result & 0x80000000; + overflow_flag_ = sub_overflow() & 0x80000000; + } break; + + /* + NEGXs: NEG, with extend. + */ + case Operation::NEGXb: { + const int source = active_program_->destination->halves.low.halves.low; + const int destination = 0; + const auto result = destination - source - (extend_flag_ ? 1 : 0); + active_program_->destination->halves.low.halves.low = result; + + zero_result_ = result & 0xff; + extend_flag_ = carry_flag_ = result & ~0xff; + negative_flag_ = result & 0x80; + overflow_flag_ = sub_overflow() & 0x80; + } break; + + case Operation::NEGXw: { + const int source = active_program_->destination->halves.low.full; + const int destination = 0; + const auto result = destination - source - (extend_flag_ ? 1 : 0); + active_program_->destination->halves.low.full = result; + + zero_result_ = result & 0xffff; + extend_flag_ = carry_flag_ = result & ~0xffff; + negative_flag_ = result & 0x8000; + overflow_flag_ = sub_overflow() & 0x8000; + } break; + + case Operation::NEGXl: { + const uint64_t source = active_program_->destination->full; + const uint64_t destination = 0; + const auto result = destination - source - (extend_flag_ ? 1 : 0); + active_program_->destination->full = uint32_t(result); + + zero_result_ = uint_fast32_t(result); + extend_flag_ = carry_flag_ = result >> 32; + negative_flag_ = result & 0x80000000; + overflow_flag_ = sub_overflow() & 0x80000000; + } break; + + /* + The no-op. + */ + case Operation::None: + break; + + /* + LINK and UNLINK help with stack frames, allowing a certain + amount of stack space to be allocated or deallocated. + */ + + case Operation::LINK: + // Make space for the new long-word value, and set up + // the proper target address for the stack operations to follow. + address_[7].full -= 4; + effective_address_[1].full = address_[7].full; + + // The current value of the address register will be pushed. + destination_bus_data_[0].full = active_program_->source->full; + + // The address register will then contain the bottom of the stack, + // and the stack pointer will be offset. + active_program_->source->full = address_[7].full; + address_[7].full += int16_t(prefetch_queue_.halves.low.full); + break; + + case Operation::UNLINK: + address_[7].full = effective_address_[1].full; + active_program_->destination->full = destination_bus_data_[0].full; + break; + + /* + TAS: sets zero and negative depending on the current value of the destination, + and sets the high bit. + */ + + case Operation::TAS: + overflow_flag_ = carry_flag_ = 0; + zero_result_ = active_program_->destination->halves.low.halves.low; + negative_flag_ = active_program_->destination->halves.low.halves.low & 0x80; + active_program_->destination->halves.low.halves.low |= 0x80; + break; + + /* + Bitwise operators: AND, OR and EOR. All three clear the overflow and carry flags, + and set zero and negative appropriately. + */ +#define op_and(x, y) x &= y +#define op_or(x, y) x |= y +#define op_eor(x, y) x ^= y + +#define bitwise(source, dest, sign_mask, operator) \ + operator(dest, source); \ + overflow_flag_ = carry_flag_ = 0; \ + zero_result_ = dest; \ + negative_flag_ = dest & sign_mask; + +#define andx(source, dest, sign_mask) bitwise(source, dest, sign_mask, op_and) +#define eorx(source, dest, sign_mask) bitwise(source, dest, sign_mask, op_eor) +#define orx(source, dest, sign_mask) bitwise(source, dest, sign_mask, op_or) + +#define op_bwl(name, op) \ + case Operation::name##b: op(active_program_->source->halves.low.halves.low, active_program_->destination->halves.low.halves.low, 0x80); break; \ + case Operation::name##w: op(active_program_->source->halves.low.full, active_program_->destination->halves.low.full, 0x8000); break; \ + case Operation::name##l: op(active_program_->source->full, active_program_->destination->full, 0x80000000); break; + + op_bwl(AND, andx); + op_bwl(EOR, eorx); + op_bwl(OR, orx); + +#undef op_bwl +#undef orx +#undef eorx +#undef andx +#undef bitwise +#undef op_eor +#undef op_or +#undef op_and + + // NOTs: take the logical inverse, affecting the negative and zero flags. + case Operation::NOTb: + active_program_->destination->halves.low.halves.low ^= 0xff; + zero_result_ = active_program_->destination->halves.low.halves.low; + negative_flag_ = zero_result_ & 0x80; + break; + + case Operation::NOTw: + active_program_->destination->halves.low.full ^= 0xffff; + zero_result_ = active_program_->destination->halves.low.full; + negative_flag_ = zero_result_ & 0x8000; + break; + + case Operation::NOTl: + active_program_->destination->full ^= 0xffffffff; + zero_result_ = active_program_->destination->full; + negative_flag_ = zero_result_ & 0x80000000; + break; + +#define sbcd() \ + /* Perform the BCD arithmetic by evaluating the two nibbles separately. */ \ + int result = (destination & 0xf) - (source & 0xf) - (extend_flag_ ? 1 : 0); \ + if(result > 0x09) result -= 0x06; \ + result += (destination & 0xf0) - (source & 0xf0); \ + if(result > 0x99) result -= 0x60; \ +\ + /* Set all flags essentially as if this were normal subtraction. */ \ + zero_result_ |= result & 0xff; \ + extend_flag_ = carry_flag_ = result & ~0xff; \ + negative_flag_ = result & 0x80; \ + overflow_flag_ = sub_overflow() & 0x80; \ +\ + /* Store the result. */ \ + active_program_->destination->halves.low.halves.low = uint8_t(result); + + /* + SBCD subtracts the lowest byte of the source from that of the destination using + BCD arithmetic, obeying the extend flag. + */ + case Operation::SBCD: { + const uint8_t source = active_program_->source->halves.low.halves.low; + const uint8_t destination = active_program_->destination->halves.low.halves.low; + sbcd(); + } break; + + /* + NBCD is like SBCD except that the result is 0 - destination rather than + destination - source. + */ + case Operation::NBCD: { + const uint8_t source = active_program_->destination->halves.low.halves.low; + const uint8_t destination = 0; + sbcd(); + } break; + + // EXG and SWAP exchange/swap words or long words. + + case Operation::EXG: { + const auto temporary = active_program_->source->full; + active_program_->source->full = active_program_->destination->full; + active_program_->destination->full = temporary; + } break; + + case Operation::SWAP: { + const auto temporary = active_program_->destination->halves.low.full; + active_program_->destination->halves.low.full = active_program_->destination->halves.high.full; + active_program_->destination->halves.high.full = temporary; + + zero_result_ = active_program_->destination->full; + negative_flag_ = temporary & 0x8000; + overflow_flag_ = carry_flag_ = 0; + } break; + + /* + Shifts and rotates. + */ +#define set_neg_zero_overflow(v, m) \ + zero_result_ = (v); \ + negative_flag_ = zero_result_ & (m); \ + overflow_flag_ = (value ^ zero_result_) & (m); + +#define decode_shift_count() \ + int shift_count = (decoded_instruction_.full & 32) ? data_[(decoded_instruction_.full >> 9) & 7].full&63 : ( ((decoded_instruction_.full >> 9)&7) ? ((decoded_instruction_.full >> 9)&7) : 8) ; \ + active_step_->microcycle.length = HalfCycles(4 * shift_count); + +#define set_flags_b(t) set_flags(active_program_->destination->halves.low.halves.low, 0x80, t) +#define set_flags_w(t) set_flags(active_program_->destination->halves.low.full, 0x8000, t) +#define set_flags_l(t) set_flags(active_program_->destination->full, 0x80000000, t) + +#define lsl(destination, size) {\ + decode_shift_count(); \ + const auto value = destination; \ +\ + if(!shift_count) { \ + carry_flag_ = 0; \ + } else { \ + destination = value << shift_count; \ + extend_flag_ = carry_flag_ = value & ((1 << (size - 1)) >> (shift_count - 1)); \ + } \ +\ + set_neg_zero_overflow(destination, 1 << (size - 1)); \ +} + + case Operation::ASLm: { + const auto value = active_program_->destination->halves.low.full; + active_program_->destination->halves.low.full = value >> 1; + extend_flag_ = carry_flag_ = value & 1; + set_neg_zero_overflow(active_program_->destination->halves.low.full, 0x8000); + } break; + case Operation::ASLb: lsl(active_program_->destination->halves.low.halves.low, 8); break; + case Operation::ASLw: lsl(active_program_->destination->halves.low.full, 16); break; + case Operation::ASLl: lsl(active_program_->destination->full, 32); break; + + + +#define asr(destination, size) {\ + decode_shift_count(); \ + const auto value = destination; \ +\ + if(!shift_count) { \ + carry_flag_ = 0; \ + } else { \ + destination = \ + (value >> shift_count) | \ + ((value & (1 << (size - 1)) ? 0xffffffff : 0x000000000) << (size - shift_count)); \ + extend_flag_ = carry_flag_ = value & (1 << (shift_count - 1)); \ + } \ +\ + set_neg_zero_overflow(destination, 1 << (size - 1)); \ +} + + case Operation::ASRm: { + const auto value = active_program_->destination->halves.low.full; + active_program_->destination->halves.low.full = (value&0x80) | (value >> 1); + extend_flag_ = carry_flag_ = value & 1; + set_neg_zero_overflow(active_program_->destination->halves.low.full, 0x8000); + } break; + case Operation::ASRb: asr(active_program_->destination->halves.low.halves.low, 8); break; + case Operation::ASRw: asr(active_program_->destination->halves.low.full, 16); break; + case Operation::ASRl: asr(active_program_->destination->full, 32); break; + + +#undef set_neg_zero_overflow +#define set_neg_zero_overflow(v, m) \ + zero_result_ = (v); \ + negative_flag_ = zero_result_ & (m); \ + overflow_flag_ = 0; + +#undef set_flags +#define set_flags(v, m, t) \ + zero_result_ = v; \ + negative_flag_ = zero_result_ & (m); \ + overflow_flag_ = 0; \ + carry_flag_ = value & (t); + + case Operation::LSLm: { + const auto value = active_program_->destination->halves.low.full; + active_program_->destination->halves.low.full = value >> 1; + extend_flag_ = carry_flag_ = value & 1; + set_neg_zero_overflow(active_program_->destination->halves.low.full, 0x8000); + } break; + case Operation::LSLb: lsl(active_program_->destination->halves.low.halves.low, 8); break; + case Operation::LSLw: lsl(active_program_->destination->halves.low.full, 16); break; + case Operation::LSLl: lsl(active_program_->destination->full, 32); break; + +#define lsr(destination, size) {\ + decode_shift_count(); \ + const auto value = destination; \ +\ + if(!shift_count) { \ + carry_flag_ = 0; \ + } else { \ + destination = value >> shift_count; \ + extend_flag_ = carry_flag_ = value & (1 << (shift_count - 1)); \ + } \ +\ + set_neg_zero_overflow(destination, 1 << (size - 1)); \ +} + + case Operation::LSRm: { + const auto value = active_program_->destination->halves.low.full; + active_program_->destination->halves.low.full = value >> 1; + extend_flag_ = carry_flag_ = value & 1; + set_neg_zero_overflow(active_program_->destination->halves.low.full, 0x8000); + } break; + case Operation::LSRb: lsr(active_program_->destination->halves.low.halves.low, 8); break; + case Operation::LSRw: lsr(active_program_->destination->halves.low.full, 16); break; + case Operation::LSRl: lsr(active_program_->destination->full, 32); break; + +#define rol(destination, size) { \ + decode_shift_count(); \ + const auto value = destination; \ + \ + if(!shift_count) { \ + carry_flag_ = 0; \ + } else { \ + shift_count &= (size - 1); \ + destination = \ + (value << shift_count) | \ + (value >> (size - shift_count)); \ + carry_flag_ = destination & 1; \ + } \ + \ + set_neg_zero_overflow(destination, 1 << (size - 1)); \ + } + + case Operation::ROLm: { + const auto value = active_program_->destination->halves.low.full; + active_program_->destination->halves.low.full = (value << 1) | (value >> 15); + carry_flag_ = active_program_->destination->halves.low.full & 1; + set_neg_zero_overflow(active_program_->destination->halves.low.full, 0x8000); + } break; + case Operation::ROLb: rol(active_program_->destination->halves.low.halves.low, 8); break; + case Operation::ROLw: rol(active_program_->destination->halves.low.full, 16); break; + case Operation::ROLl: rol(active_program_->destination->full, 32); break; + + +#define ror(destination, size) { \ + decode_shift_count(); \ + const auto value = destination; \ + \ + if(!shift_count) { \ + carry_flag_ = 0; \ + } else { \ + shift_count &= (size - 1); \ + destination = \ + (value >> shift_count) | \ + (value << (size - shift_count)); \ + carry_flag_ = destination & (1 << (size - 1)); \ + } \ + \ + set_neg_zero_overflow(destination, 1 << (size - 1)); \ + } + + case Operation::RORm: { + const auto value = active_program_->destination->halves.low.full; + active_program_->destination->halves.low.full = (value >> 1) | (value << 15); + carry_flag_ = active_program_->destination->halves.low.full & 0x8000; + set_neg_zero_overflow(active_program_->destination->halves.low.full, 0x8000); + } break; + case Operation::RORb: ror(active_program_->destination->halves.low.halves.low, 8); break; + case Operation::RORw: ror(active_program_->destination->halves.low.full, 16); break; + case Operation::RORl: ror(active_program_->destination->full, 32); break; + + +#define roxl(destination, size) { \ + decode_shift_count(); \ + const auto value = destination; \ +\ + if(!shift_count) { \ + carry_flag_ = extend_flag_; \ + } else { \ + shift_count %= (size + 1); \ + destination = \ + (value << shift_count) | \ + (value >> (size + 1 - shift_count)) | \ + ((extend_flag_ ? (1 << (size - 1)) : 0) >> (size - shift_count)); \ + carry_flag_ = extend_flag_ = destination & 1; \ + } \ + \ + set_neg_zero_overflow(destination, 1 << (size - 1)); \ +} + + case Operation::ROXLm: { + const auto value = active_program_->destination->halves.low.full; + active_program_->destination->halves.low.full = (value << 1) | (extend_flag_ ? 0x0001 : 0x0000); + extend_flag_ = value & 0x8000; + set_flags_w(0x8000); + } break; + case Operation::ROXLb: roxl(active_program_->destination->halves.low.halves.low, 8); break; + case Operation::ROXLw: roxl(active_program_->destination->halves.low.full, 16); break; + case Operation::ROXLl: roxl(active_program_->destination->full, 32); break; + + +#define roxr(destination, size) { \ + decode_shift_count(); \ + const auto value = destination; \ +\ + if(!shift_count) { \ + carry_flag_ = extend_flag_; \ + } else { \ + shift_count %= (size + 1); \ + destination = \ + (value >> shift_count) | \ + (value << (size + 1 - shift_count)) | \ + ((extend_flag_ ? 1 : 0) << (size - shift_count)); \ + carry_flag_ = extend_flag_ = destination & 1; \ + } \ + \ + set_neg_zero_overflow(destination, 1 << (size - 1)); \ +} + + case Operation::ROXRm: { + const auto value = active_program_->destination->halves.low.full; + active_program_->destination->halves.low.full = (value >> 1) | (extend_flag_ ? 0x8000 : 0x0000); + extend_flag_ = value & 0x0001; + set_flags_w(0x0001); + } break; + case Operation::ROXRb: roxr(active_program_->destination->halves.low.halves.low, 8); break; + case Operation::ROXRw: roxr(active_program_->destination->halves.low.full, 16); break; + case Operation::ROXRl: roxr(active_program_->destination->full, 32); break; + +#undef roxr +#undef roxl +#undef ror +#undef rol +#undef asr +#undef lsr +#undef lsl + +#undef set_flags +#undef decode_shift_count +#undef set_flags_b +#undef set_flags_w +#undef set_flags_l +#undef set_neg_zero_overflow + /* + RTE and RTR share an implementation. + */ + case Operation::RTE_RTR: + // If this is RTR, patch out the is_supervisor bit. + if(decoded_instruction_.full == 0x4e77) { + source_bus_data_[0].full = + (source_bus_data_[0].full & ~(1 << 13)) | + (is_supervisor_ << 13); + } + set_status(source_bus_data_[0].full); + break; + + /* + TSTs: compare to zero. + */ + + case Operation::TSTb: + carry_flag_ = overflow_flag_ = 0; + zero_result_ = active_program_->source->halves.low.halves.low; + negative_flag_ = zero_result_ & 0x80; + break; + + case Operation::TSTw: + carry_flag_ = overflow_flag_ = 0; + zero_result_ = active_program_->source->halves.low.full; + negative_flag_ = zero_result_ & 0x8000; + break; + + case Operation::TSTl: + carry_flag_ = overflow_flag_ = 0; + zero_result_ = active_program_->source->full; + negative_flag_ = zero_result_ & 0x80000000; + break; + + case Operation::STOP: + set_status(prefetch_queue_.halves.low.full); + execution_state_ = ExecutionState::Stopped; + break; + + /* + Development period debugging. + */ + default: + std::cerr << "Should do something with program operation " << int(active_program_->operation) << std::endl; + break; + } +#undef sub_overflow +#undef add_overflow + break; + + case int(MicroOp::Action::MOVEMtoRComplete): { + // If this was a word-sized move, perform sign extension. + if(active_program_->operation == Operation::MOVEMtoRw) { + auto mask = next_word_; + int offset = 0; + while(mask) { + if(mask&1) { + const auto target = (offset > 7) ? &address_[offset&7] : &data_[offset]; + target->halves.high.full = (target->halves.low.full & 0x8000) ? 0xffff : 0x0000; + } + mask >>= 1; + ++offset; + } + } + + // If the post-increment mode was used, overwrite the source register. + const auto mode = (decoded_instruction_.full >> 3) & 7; + if(mode == 3) { + const auto reg = decoded_instruction_.full & 7; + address_[reg] = movem_final_address_; + } + } break; + + case int(MicroOp::Action::MOVEMtoMComplete): { + const auto mode = (decoded_instruction_.full >> 3) & 7; + if(mode == 4) { + const auto reg = decoded_instruction_.full & 7; + address_[reg] = movem_final_address_; + } + } break; + + case int(MicroOp::Action::PrepareJSR): { + const auto mode = (decoded_instruction_.full >> 3) & 7; + // Determine the proper resumption address. + switch(mode) { + case 2: destination_bus_data_[0].full = program_counter_.full - 2; break; /* (An) */ + default: + destination_bus_data_[0].full = program_counter_.full; /* Everything other than (An) */ + break; + } + address_[7].full -= 4; + effective_address_[1].full = address_[7].full; + } break; + + case int(MicroOp::Action::PrepareBSR): + destination_bus_data_[0].full = (decoded_instruction_.full & 0xff) ? program_counter_.full - 2 : program_counter_.full; + address_[7].full -= 4; + effective_address_[1].full = address_[7].full; + break; + + case int(MicroOp::Action::PrepareRTS): + effective_address_[0].full = address_[7].full; + address_[7].full += 4; + break; + + case int(MicroOp::Action::PrepareRTE_RTR): + precomputed_addresses_[0] = address_[7].full + 2; + precomputed_addresses_[1] = address_[7].full; + precomputed_addresses_[2] = address_[7].full + 4; + address_[7].full += 6; + break; + + case int(MicroOp::Action::PrepareINT): + // The INT sequence uses the same storage as the TRAP steps, so this'll get + // the necessary stack work set up. + populate_trap_steps(0, get_status()); + + // Mutate neessary internal state — effective_address_[0] is exposed + // on the data bus as the accepted interrupt number during the interrupt + // acknowledge cycle, with the low bit set since a real 68000 uses the lower + // data strobe to collect the corresponding vector byte. + accepted_interrupt_level_ = interrupt_level_ = bus_interrupt_level_; + effective_address_[0].full = 1 | (accepted_interrupt_level_ << 1); + break; + + case int(MicroOp::Action::PrepareINTVector): + // Bus error => spurious interrupt. + if(bus_error_) { + effective_address_[0].full = 24 << 2; + break; + } + + // Valid peripheral address => autovectored interrupt. + if(is_peripheral_address_) { + effective_address_[0].full = (24 + accepted_interrupt_level_) << 2; + break; + } + + // Otherwise, the vector is whatever we were just told it is. + effective_address_[0].full = source_bus_data_[0].halves.low.halves.low << 2; + + // Let bus error go back to causing exceptions. + is_starting_interrupt_ = false; + break; + + case int(MicroOp::Action::CopyNextWord): + next_word_ = prefetch_queue_.halves.low.full; + break; + + // Increments and decrements. +#define Adjust(op, quantity) \ + case int(op) | MicroOp::SourceMask: active_program_->source_address->full += quantity; break; \ + case int(op) | MicroOp::DestinationMask: active_program_->destination_address->full += quantity; break; \ + case int(op) | MicroOp::SourceMask | MicroOp::DestinationMask: \ + active_program_->destination_address->full += quantity; \ + active_program_->source_address->full += quantity; \ + break; + + Adjust(MicroOp::Action::Decrement1, -1); + Adjust(MicroOp::Action::Decrement2, -2); + Adjust(MicroOp::Action::Decrement4, -4); + Adjust(MicroOp::Action::Increment1, 1); + Adjust(MicroOp::Action::Increment2, 2); + Adjust(MicroOp::Action::Increment4, 4); + +#undef Adjust + + case int(MicroOp::Action::SignExtendWord): + if(active_micro_op_->action & MicroOp::SourceMask) { + active_program_->source->halves.high.full = + (active_program_->source->halves.low.full & 0x8000) ? 0xffff : 0x0000; + } + if(active_micro_op_->action & MicroOp::DestinationMask) { + active_program_->destination->halves.high.full = + (active_program_->destination->halves.low.full & 0x8000) ? 0xffff : 0x0000; + } + break; + + case int(MicroOp::Action::SignExtendByte): + if(active_micro_op_->action & MicroOp::SourceMask) { + active_program_->source->full = (active_program_->source->full & 0xff) | + (active_program_->source->full & 0x80) ? 0xffffff : 0x000000; + } + if(active_micro_op_->action & MicroOp::DestinationMask) { + active_program_->destination->full = (active_program_->destination->full & 0xff) | + (active_program_->destination->full & 0x80) ? 0xffffff : 0x000000; + } + break; + + // 16-bit offset addressing modes. + + case int(MicroOp::Action::CalcD16PC) | MicroOp::SourceMask: + // The address the low part of the prefetch queue was read from was two bytes ago, hence + // the subtraction of 2. + effective_address_[0] = int16_t(prefetch_queue_.halves.low.full) + program_counter_.full - 2; + break; + + case int(MicroOp::Action::CalcD16PC) | MicroOp::DestinationMask: + effective_address_[1] = int16_t(prefetch_queue_.halves.low.full) + program_counter_.full - 2; + break; + + case int(MicroOp::Action::CalcD16PC) | MicroOp::SourceMask | MicroOp::DestinationMask: + // Similar logic applies here to above, but the high part of the prefetch queue was four bytes + // ago rather than merely two. + effective_address_[0] = int16_t(prefetch_queue_.halves.high.full) + program_counter_.full - 4; + effective_address_[1] = int16_t(prefetch_queue_.halves.low.full) + program_counter_.full - 2; + break; + + case int(MicroOp::Action::CalcD16An) | MicroOp::SourceMask: + effective_address_[0] = int16_t(prefetch_queue_.halves.low.full) + active_program_->source_address->full; + break; + + case int(MicroOp::Action::CalcD16An) | MicroOp::DestinationMask: + effective_address_[1] = int16_t(prefetch_queue_.halves.low.full) + active_program_->destination_address->full; + break; + + case int(MicroOp::Action::CalcD16An) | MicroOp::SourceMask | MicroOp::DestinationMask: + effective_address_[0] = int16_t(prefetch_queue_.halves.high.full) + active_program_->source_address->full; + effective_address_[1] = int16_t(prefetch_queue_.halves.low.full) + active_program_->destination_address->full; + break; + +#define CalculateD8AnXn(data, source, target) {\ + const auto register_index = (data.full >> 12) & 7; \ + const RegisterPair32 &displacement = (data.full & 0x8000) ? address_[register_index] : data_[register_index]; \ + target.full = int8_t(data.halves.low) + source; \ +\ + if(data.full & 0x800) { \ + target.full += displacement.full; \ + } else { \ + target.full += int16_t(displacement.halves.low.full); \ + } \ +} + case int(MicroOp::Action::CalcD8AnXn) | MicroOp::SourceMask: { + CalculateD8AnXn(prefetch_queue_.halves.low, active_program_->source_address->full, effective_address_[0]); + } break; + + case int(MicroOp::Action::CalcD8AnXn) | MicroOp::DestinationMask: { + CalculateD8AnXn(prefetch_queue_.halves.low, active_program_->destination_address->full, effective_address_[1]); + } break; + + case int(MicroOp::Action::CalcD8AnXn) | MicroOp::SourceMask | MicroOp::DestinationMask: { + CalculateD8AnXn(prefetch_queue_.halves.high, active_program_->source_address->full, effective_address_[0]); + CalculateD8AnXn(prefetch_queue_.halves.low, active_program_->destination_address->full, effective_address_[1]); + } break; + + case int(MicroOp::Action::CalcD8PCXn) | MicroOp::SourceMask: { + CalculateD8AnXn(prefetch_queue_.halves.low, program_counter_.full - 2, effective_address_[0]); + } break; + + case int(MicroOp::Action::CalcD8PCXn) | MicroOp::DestinationMask: { + CalculateD8AnXn(prefetch_queue_.halves.low, program_counter_.full - 2, effective_address_[1]); + } break; + + case int(MicroOp::Action::CalcD8PCXn) | MicroOp::SourceMask | MicroOp::DestinationMask: { + CalculateD8AnXn(prefetch_queue_.halves.high, program_counter_.full - 4, effective_address_[0]); + CalculateD8AnXn(prefetch_queue_.halves.low, program_counter_.full - 2, effective_address_[1]); + } break; + +#undef CalculateD8AnXn + + case int(MicroOp::Action::AssembleWordAddressFromPrefetch) | MicroOp::SourceMask: + effective_address_[0] = int16_t(prefetch_queue_.halves.low.full); + break; + + case int(MicroOp::Action::AssembleWordAddressFromPrefetch) | MicroOp::DestinationMask: + effective_address_[1] = int16_t(prefetch_queue_.halves.low.full); + break; + + case int(MicroOp::Action::AssembleLongWordAddressFromPrefetch) | MicroOp::SourceMask: + effective_address_[0] = prefetch_queue_.full; + break; + + case int(MicroOp::Action::AssembleLongWordAddressFromPrefetch) | MicroOp::DestinationMask: + effective_address_[1] = prefetch_queue_.full; + break; + + case int(MicroOp::Action::AssembleWordDataFromPrefetch) | MicroOp::SourceMask: + source_bus_data_[0] = prefetch_queue_.halves.low.full; + break; + + case int(MicroOp::Action::AssembleWordDataFromPrefetch) | MicroOp::DestinationMask: + destination_bus_data_[0] = prefetch_queue_.halves.low.full; + break; + + case int(MicroOp::Action::AssembleLongWordDataFromPrefetch) | MicroOp::SourceMask: + source_bus_data_[0] = prefetch_queue_.full; + break; + + case int(MicroOp::Action::AssembleLongWordDataFromPrefetch) | MicroOp::DestinationMask: + destination_bus_data_[0] = prefetch_queue_.full; + break; + + case int(MicroOp::Action::CopyToEffectiveAddress) | MicroOp::SourceMask: + effective_address_[0] = *active_program_->source_address; + break; + + case int(MicroOp::Action::CopyToEffectiveAddress) | MicroOp::DestinationMask: + effective_address_[1] = *active_program_->destination_address; + break; + + case int(MicroOp::Action::CopyToEffectiveAddress) | MicroOp::SourceMask | MicroOp::DestinationMask: + effective_address_[0] = *active_program_->source_address; + effective_address_[1] = *active_program_->destination_address; + break; + } + + // If we've got to a micro-op that includes bus steps, break out of this loop. + if(!active_micro_op_->is_terminal()) { + active_step_ = bus_program; + if(!active_step_->is_terminal()) + break; + } + } + } + } + + e_clock_phase_ = (e_clock_phase_ + cycles_run_for) % 10; + half_cycles_left_to_run_ = remaining_duration - cycles_run_for; +} + +template ProcessorState Processor::get_state() { + write_back_stack_pointer(); + + State state; + memcpy(state.data, data_, sizeof(state.data)); + memcpy(state.address, address_, sizeof(state.address)); + state.user_stack_pointer = stack_pointers_[0].full; + state.supervisor_stack_pointer = stack_pointers_[1].full; + state.program_counter = program_counter_.full; + + state.status = get_status(); + + return state; +} + +template void Processor::set_state(const ProcessorState &state) { + memcpy(data_, state.data, sizeof(state.data)); + memcpy(address_, state.address, sizeof(state.address)); + + set_status(state.status); + + stack_pointers_[0].full = state.user_stack_pointer; + stack_pointers_[1].full = state.supervisor_stack_pointer; + address_[7] = stack_pointers_[is_supervisor_]; +} + +#undef get_status +#undef set_status +#undef set_ccr +#undef get_ccr diff --git a/Processors/68000/Implementation/68000Storage.cpp b/Processors/68000/Implementation/68000Storage.cpp new file mode 100644 index 000000000..ea226e863 --- /dev/null +++ b/Processors/68000/Implementation/68000Storage.cpp @@ -0,0 +1,3584 @@ +// +// 68000Storage.cpp +// Clock Signal +// +// Created by Thomas Harte on 08/03/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#include "../68000.hpp" + +#include +#include +#include +#include +#include +#include + +namespace CPU { +namespace MC68000 { + +#define Dn 0x00 +#define An 0x01 +#define Ind 0x02 +#define PostInc 0x03 +#define PreDec 0x04 +#define d16An 0x05 +#define d8AnXn 0x06 +#define XXXw 0x10 +#define XXXl 0x11 +#define d16PC 0x12 +#define d8PCXn 0x13 +#define Imm 0x14 + +struct ProcessorStorageConstructor { + ProcessorStorageConstructor(ProcessorStorage &storage) : storage_(storage) {} + + using BusStep = ProcessorStorage::BusStep; + + /*! + */ + int calc_action_for_mode(int mode) const { + using Action = ProcessorBase::MicroOp::Action; + switch(mode & 0xff) { + default: assert(false); + case d16PC: return int(Action::CalcD16PC); + case d8PCXn: return int(Action::CalcD8PCXn); + case d16An: return int(Action::CalcD16An); + case d8AnXn: return int(Action::CalcD8AnXn); + } + } + + int address_assemble_for_mode(int mode) const { + using Action = ProcessorBase::MicroOp::Action; + assert((mode & 0xff) == XXXw || (mode & 0xff) == XXXl); + return int(((mode & 0xff) == XXXw) ? Action::AssembleWordAddressFromPrefetch : Action::AssembleLongWordAddressFromPrefetch); + } + + int address_action_for_mode(int mode) const { + using Action = ProcessorBase::MicroOp::Action; + switch(mode & 0xff) { + default: assert(false); + case d16PC: return int(Action::CalcD16PC); + case d8PCXn: return int(Action::CalcD8PCXn); + case d16An: return int(Action::CalcD16An); + case d8AnXn: return int(Action::CalcD8AnXn); + case XXXw: return int(Action::AssembleWordAddressFromPrefetch); + case XXXl: return int(Action::AssembleLongWordAddressFromPrefetch); + } + } + + int combined_mode(int mode, int reg, bool collapse_an_dn = false, bool collapse_postinc = false) { + if(collapse_an_dn && mode == An) mode = Dn; + if(collapse_postinc && mode == PostInc) mode = Ind; + return (mode == 7) ? (0x10 | reg) : mode; + } + + int data_assemble_for_mode(int mode) const { + using Action = ProcessorBase::MicroOp::Action; + assert((mode & 0xff) == XXXw || (mode & 0xff) == XXXl); + return int(((mode & 0xff) == XXXw) ? Action::AssembleWordDataFromPrefetch : Action::AssembleLongWordDataFromPrefetch); + } + + int byte_inc(int reg) const { + using Action = ProcessorBase::MicroOp::Action; + // Special case: stack pointer byte accesses adjust by two. + return int((reg == 7) ? Action::Increment2 : Action::Increment1); + } + + int byte_dec(int reg) const { + using Action = ProcessorBase::MicroOp::Action; + // Special case: stack pointer byte accesses adjust by two. + return int((reg == 7) ? Action::Decrement2 : Action::Decrement1); + } + + int increment_action(bool is_long_word_access, bool is_byte_access, int reg) const { + using Action = ProcessorBase::MicroOp::Action; + if(is_long_word_access) return int(Action::Increment4); + if(is_byte_access) return byte_inc(reg); + return int(Action::Increment2); + } + + int decrement_action(bool is_long_word_access, bool is_byte_access, int reg) const { + using Action = ProcessorBase::MicroOp::Action; + if(is_long_word_access) return int(Action::Decrement4); + if(is_byte_access) return byte_dec(reg); + return int(Action::Decrement2); + } + +#define pseq(x, m) ((((m)&0xff) == d8AnXn) || (((m)&0xff) == d8PCXn) ? "n " x : x) + + /*! + Installs BusSteps that implement the described program into the relevant + instance storage, returning the offset within @c all_bus_steps_ at which + the generated steps begin. + + @param access_pattern A string describing the bus activity that occurs + during this program. This should follow the same general pattern as + those in yacht.txt; full description below. + + @param addresses A vector of the addresses to place on the bus coincident + with those acess steps that require them. + + @param read_full_words @c true to indicate that read and write operations are + selecting a full word; @c false to signal byte accesses only. + + @discussion + The access pattern is defined to correlate closely to that in yacht.txt; it is + a space-separated sequence of the following actions: + + * n: no operation for four cycles; data bus is not used; + * nn: no operation for eight cycles; data bus is not used; + * r: a 'replaceable'-length no operation; data bus is not used and no guarantees are + made about the length of the cycle other than that when it reaches the interpreter, + it is safe to alter the length and leave it altered; + * np: program fetch; reads from the PC and adds two to it, advancing the prefetch queue; + * nW: write MSW of something onto the bus; + * nw: write LSW of something onto the bus; + * nR: read MSW of something from the bus into the source latch; + * nr: read LSW of soemthing from the bus into the source latch; + * nRd: read MSW of something from the bus into the destination latch; + * nrd: read LSW of soemthing from the bus into the destination latch; + * nS: push the MSW of something onto the stack **and then** decrement the pointer; + * ns: push the LSW of something onto the stack **and then** decrement the pointer; + * nU: pop the MSW of something from the stack; + * nu: pop the LSW of something from the stack; + * nV: fetch a vector's MSW; + * nv: fetch a vector's LSW; + * i: acquire interrupt vector in an IACK cycle; + * nF: fetch the SSPs MSW; + * nf: fetch the SSP's LSW; + * _: hold the reset line active for the usual period. + * tas: perform the final 6 cycles of a TAS: like an n nw but with the address strobe active for the entire period. + * int: the interrupt acknowledge cycle. + + Quite a lot of that is duplicative, implying both something about internal + state and something about what's observable on the bus, but it's helpful to + stick to that document's coding exactly for easier debugging. + + np fetches will fill the prefetch queue, attaching an action to both the + step that precedes them and to themselves. The SSP fetches will go straight + to the SSP. + + Other actions will by default act via effective_address_ and bus_data_. + The user should fill in the steps necessary to get data into or extract + data from those. + + nr/nw-type operations may have a + or - suffix; if such a suffix is attached + then the corresponding effective address will be incremented or decremented + by two after the cycle has completed. + */ + size_t assemble_program(std::string access_pattern, const std::vector &addresses = {}, bool read_full_words = true) { + auto address_iterator = addresses.begin(); + using Action = BusStep::Action; + + std::vector steps; + std::stringstream stream(access_pattern); + + // Tokenise the access pattern by splitting on spaces. + std::string token; + while(stream >> token) { + ProcessorBase::BusStep step; + + // Check for a plus-or-minus suffix. + int post_adjustment = 0; + if(token.back() == '-' || token.back() == '+') { + if(token.back() == '-') { + post_adjustment = -1; + } + + if(token.back() == '+') { + post_adjustment = 1; + } + + token.pop_back(); + } + + // Do nothing (possibly twice). + if(token == "n" || token == "nn") { + if(token.size() == 2) { + step.microcycle.length = HalfCycles(8); + } + steps.push_back(step); + continue; + } + + // Do nothing, but with a length that definitely won't map it to the other do-nothings. + if(token == "r"){ + step.microcycle.length = HalfCycles(0); + steps.push_back(step); + continue; + } + + // Fetch SSP. + if(token == "nF" || token == "nf") { + step.microcycle.operation = Microcycle::NewAddress | Microcycle::Read | Microcycle::IsProgram; // IsProgram is a guess. + step.microcycle.address = &storage_.effective_address_[0].full; + step.microcycle.value = isupper(token[1]) ? &storage_.stack_pointers_[1].halves.high : &storage_.stack_pointers_[1].halves.low; + steps.push_back(step); + + step.microcycle.operation = Microcycle::SameAddress | Microcycle::Read | Microcycle::IsProgram | Microcycle::SelectWord; + step.action = Action::IncrementEffectiveAddress0; + steps.push_back(step); + + continue; + } + + // Fetch exception vector. + if(token == "nV" || token == "nv") { + step.microcycle.operation = Microcycle::NewAddress | Microcycle::Read | Microcycle::IsProgram; // IsProgram is a guess. + step.microcycle.address = &storage_.effective_address_[0].full; + step.microcycle.value = isupper(token[1]) ? &storage_.program_counter_.halves.high : &storage_.program_counter_.halves.low; + steps.push_back(step); + + step.microcycle.operation = Microcycle::SameAddress | Microcycle::Read | Microcycle::IsProgram | Microcycle::SelectWord; + step.action = Action::IncrementEffectiveAddress0; + steps.push_back(step); + + continue; + } + + // Fetch from the program counter into the prefetch queue. + if(token == "np") { + step.microcycle.operation = Microcycle::NewAddress | Microcycle::Read | Microcycle::IsProgram; + step.microcycle.address = &storage_.program_counter_.full; + step.microcycle.value = &storage_.prefetch_queue_.halves.low; + step.action = Action::AdvancePrefetch; + steps.push_back(step); + + step.microcycle.operation = Microcycle::SameAddress | Microcycle::Read | Microcycle::IsProgram | Microcycle::SelectWord; + step.action = Action::IncrementProgramCounter; + steps.push_back(step); + + continue; + } + + // The reset cycle. + if(token == "_") { + step.microcycle.length = HalfCycles(248); + step.microcycle.operation = Microcycle::Reset; + steps.push_back(step); + + continue; + } + + // A standard read or write. + if(token == "nR" || token == "nr" || token == "nW" || token == "nw" || token == "nRd" || token == "nrd" || token == "nWr" || token == "nwr") { + const bool is_read = tolower(token[1]) == 'r'; + const bool use_source_storage = (token == "nR" || token == "nr" || token == "nWr" || token == "nwr"); + RegisterPair32 *const scratch_data = use_source_storage ? &storage_.source_bus_data_[0] : &storage_.destination_bus_data_[0]; + + assert(address_iterator != addresses.end()); + + step.microcycle.operation = Microcycle::NewAddress | (is_read ? Microcycle::Read : 0); + step.microcycle.address = *address_iterator; + step.microcycle.value = isupper(token[1]) ? &scratch_data->halves.high : &scratch_data->halves.low; + steps.push_back(step); + + step.microcycle.operation = Microcycle::SameAddress | (is_read ? Microcycle::Read : 0) | (read_full_words ? Microcycle::SelectWord : Microcycle::SelectByte); + if(post_adjustment) { + // nr and nR should affect address 0; nw, nW, nrd and nRd should affect address 1. + if(tolower(token[1]) == 'r' && token.size() == 2) { + step.action = (post_adjustment > 0) ? Action::IncrementEffectiveAddress0 : Action::DecrementEffectiveAddress0; + } else { + step.action = (post_adjustment > 0) ? Action::IncrementEffectiveAddress1 : Action::DecrementEffectiveAddress1; + + } + } + steps.push_back(step); + ++address_iterator; + + continue; + } + + // The completing part of a TAS. + if(token == "tas") { + RegisterPair32 *const scratch_data = &storage_.destination_bus_data_[0]; + + assert(address_iterator != addresses.end()); + + step.microcycle.length = HalfCycles(9); + step.microcycle.operation = Microcycle::SameAddress; + step.microcycle.address = *address_iterator; + step.microcycle.value = &scratch_data->halves.low; + steps.push_back(step); + + step.microcycle.length = HalfCycles(3); + step.microcycle.operation = Microcycle::SameAddress | Microcycle::SelectByte; + steps.push_back(step); + ++address_iterator; + + continue; + } + + // A stack write. + if(token == "nS" || token == "ns") { + step.microcycle.operation = Microcycle::NewAddress; + step.microcycle.address = &storage_.effective_address_[1].full; + step.microcycle.value = isupper(token[1]) ? &storage_.destination_bus_data_[0].halves.high : &storage_.destination_bus_data_[0].halves.low; + steps.push_back(step); + + step.microcycle.operation = Microcycle::SameAddress | Microcycle::SelectWord; + step.action = Action::DecrementEffectiveAddress1; + steps.push_back(step); + + continue; + } + + // A stack read. + if(token == "nU" || token == "nu") { + RegisterPair32 *const scratch_data = &storage_.source_bus_data_[0]; + + step.microcycle.operation = Microcycle::NewAddress | Microcycle::Read; + step.microcycle.address = &storage_.effective_address_[0].full; + step.microcycle.value = isupper(token[1]) ? &scratch_data->halves.high : &scratch_data->halves.low; + steps.push_back(step); + + step.microcycle.operation = Microcycle::SameAddress | Microcycle::Read | Microcycle::SelectWord; + step.action = Action::IncrementEffectiveAddress0; + steps.push_back(step); + + continue; + } + + // Interrupt acknowledge. + if(token == "int") { + step.microcycle.operation = Microcycle::InterruptAcknowledge | Microcycle::NewAddress; + step.microcycle.address = &storage_.effective_address_[0].full; // The selected interrupt should be in bits 1–3; but 0 should be set. + step.microcycle.value = &storage_.source_bus_data_[0].halves.low; + steps.push_back(step); + + step.microcycle.operation = Microcycle::InterruptAcknowledge | Microcycle::SameAddress | Microcycle::SelectByte; + steps.push_back(step); + + continue; + } + + std::cerr << "MC68000 program builder; Unknown access token " << token << std::endl; + assert(false); + } + + // Add a final 'ScheduleNextProgram' sentinel. + BusStep end_program; + end_program.action = Action::ScheduleNextProgram; + steps.push_back(end_program); + + // If the new steps already exist, just return the existing index to them; + // otherwise insert them. + const auto position = std::search(storage_.all_bus_steps_.begin(), storage_.all_bus_steps_.end(), steps.begin(), steps.end()); + if(position != storage_.all_bus_steps_.end()) { + return size_t(position - storage_.all_bus_steps_.begin()); + } + + const auto start = storage_.all_bus_steps_.size(); + std::copy(steps.begin(), steps.end(), std::back_inserter(storage_.all_bus_steps_)); + return start; + +/* + // If the new steps already exist, just return the existing index to them; + // otherwise insert them. A lookup table of steps to start positions within + // all_bus_steps_ is maintained to shorten setup time here + auto potential_locations = locations_by_bus_step_[steps.front()]; + for(auto index: potential_locations) { + if(index + steps.size() >= storage_.all_bus_steps_.size()) continue; + + if(std::equal( + storage_.all_bus_steps_.begin() + ssize_t(index), + storage_.all_bus_steps_.begin() + ssize_t(index + steps.size()), + steps.begin())) { + return index; + } + } + + // Copy to the end, and update potential_locations. + const auto start = storage_.all_bus_steps_.size(); + std::copy(steps.begin(), steps.end(), std::back_inserter(storage_.all_bus_steps_)); + auto index = start; + for(const auto &step: steps) { + locations_by_bus_step_[step].push_back(index); + ++index; + } + + return start;*/ + } + + /*! + Walks through the sequence of bus steps beginning at @c start, replacing the value supplied for each write + encountered with the respective value from @c values. + */ + void replace_write_values(BusStep *start, const std::vector &values) { + assert(replace_write_values(start, values.begin()) == values.end()); + } + + /*! + Walks through the sequence of micro-ops beginning at @c start, replacing the value supplied for each write + encountered in each micro-op's bus steps with the respective value from @c values. + */ + void replace_write_values(ProcessorBase::MicroOp *start, const std::vector &values) { + auto value = values.begin(); + while(!start->is_terminal()) { + value = replace_write_values(start->bus_program, value); + ++start; + } + assert(value == values.end()); + } + + /*! + Disassembles the instruction @c instruction and inserts it into the + appropriate lookup tables. + + install_instruction acts, in effect, in the manner of a disassembler. So this class is + formulated to run through all potential 65536 instuction encodings and attempt to + disassemble each, rather than going in the opposite direction. + + This has two benefits: + + (i) which addressing modes go with which instructions falls out automatically; + (ii) it is a lot easier during the manual verification stage of development to work + from known instructions to their disassembly rather than vice versa; especially + (iii) given that there are plentiful disassemblers against which to test work in progress. + */ + void install_instructions() { + enum class Decoder { + ABCD_SBCD, // Maps source and desintation registers and a register/memory selection bit to an ABCD or SBCD. + ADD_SUB, // Maps a register and a register and mode to an ADD or SUB. + ADDA_SUBA, // Maps a destination register and a source mode and register to an ADDA or SUBA. + ADDQ_SUBQ, // Maps a register and a mode to an ADDQ or SUBQ. + ADDX_SUBX, // Maps source and destination registers, and register/memory mode to an ADDX or SUBX. + + AND_OR_EOR, // Maps a source register, operation mode and destination register and mode to an AND, OR or EOR. + + BRA, // Maps to a BRA. All fields are decoded at runtime. + Bcc_BSR, // Maps to a Bcc or BSR. Other than determining the type of operation, fields are decoded at runtime. + + BTST, // Maps a source register and a destination register and mode to a BTST. + BTSTIMM, // Maps a destination mode and register to a BTST #. + + BCLR, // Maps a source register and a destination register and mode to a BCLR. + BCLRIMM, // Maps a destination mode and register to a BCLR #. + + CLR_NEG_NEGX_NOT, // Maps a destination mode and register to a CLR, NEG, NEGX or NOT. + + CMP, // Maps a destination register and a source mode and register to a CMP. + CMPI, // Maps a destination mode and register to a CMPI. + CMPA, // Maps a destination register and a source mode and register to a CMPA. + CMPM, // Maps to a CMPM. + + EORI_ORI_ANDI_SUBI_ADDI, // Maps a mode and register to one of EORI, ORI, ANDI, SUBI or ADDI. + + JMP, // Maps a mode and register to a JMP. + JSR, // Maps a mode and register to a JSR. + + LEA, // Maps a destination register and a source mode and register to an LEA. + + MOVE, // Maps a source mode and register and a destination mode and register to a MOVE. + MOVEtoSRCCR, // Maps a source mode and register to a MOVE to SR or MOVE to CCR. + MOVEfromSR_NBCD, // Maps a source mode and register to a MOVE fom SR. + MOVEq, // Maps a destination register to a MOVEQ. + + MULU_MULS, // Maps a destination register and a source mode and register to a MULU or MULS. + DIVU_DIVS, // Maps a destination register and a source mode and register to a DIVU or DIVS. + + RESET, // Maps to a RESET. + + ASLR_LSLR_ROLR_ROXLRr, // Maps a destination register to a AS[L/R], LS[L/R], RO[L/R], ROX[L/R]; shift quantities are + // decoded at runtime. + ASLR_LSLR_ROLR_ROXLRm, // Maps a destination mode and register to a memory-based AS[L/R], LS[L/R], RO[L/R], ROX[L/R]. + + MOVEM, // Maps a mode and register as if they were a 'destination' and sets up bus steps with a suitable + // hole for the runtime part to install proper MOVEM activity. + MOVEP, // Maps a data register, address register and operation mode to a MOVEP. + + RTE_RTR, // Maps to an RTE/RTR. + + Scc_DBcc, // Maps a mode and destination register to either a DBcc or Scc. + + TST, // Maps a mode and register to a TST. + + RTS, // Maps to an RST. + + MOVEUSP, // Maps a direction and register to a MOVE [to/from] USP. + + TRAP, // Maps to a TRAP. + TRAPV, // Maps to a TRAPV. + CHK, // Maps to a CHK. + + NOP, // Maps to a NOP. + + EXG, // Maps source and destination registers and an operation mode to an EXG. + EXT_SWAP, // Maps a source register to a SWAP or EXT. + + EORI_ORI_ANDI_SR, // Maps to an EORI, ORI or ANDI to SR/CCR. + + BCHG_BSET, // Maps a mode and register, and possibly a source register, to a BCHG or BSET. + + TAS, // Maps a mode and register to a TAS. + + PEA, // Maps a mode and register to a PEA. + + LINK, // Maps a register to a LINK. + UNLINK, // Maps a register to an UNLINK. + + STOP, // Maps to a STOP. + }; + + using Operation = ProcessorStorage::Operation; + using Action = ProcessorStorage::MicroOp::Action; + using MicroOp = ProcessorBase::MicroOp; + struct PatternMapping { + uint16_t mask, value; + Operation operation; + Decoder decoder; + }; + + /* + Inspired partly by 'wrm' (https://github.com/wrm-za I assume); the following + table draws from the M68000 Programmer's Reference Manual, currently available at + https://www.nxp.com/files-static/archives/doc/ref_manual/M68000PRM.pdf + + After each line is the internal page number on which documentation of that + instruction mapping can be found, followed by the page number within the PDF + linked above. + + NB: a vector is used to allow easy iteration. + */ + const std::vector mappings = { + {0xf1f0, 0xc100, Operation::ABCD, Decoder::ABCD_SBCD}, // 4-3 (p107) + {0xf1f0, 0x8100, Operation::SBCD, Decoder::ABCD_SBCD}, // 4-171 (p275) + {0xffc0, 0x4800, Operation::NBCD, Decoder::MOVEfromSR_NBCD}, // 4-142 (p246) + + {0xf0c0, 0xc000, Operation::ANDb, Decoder::AND_OR_EOR}, // 4-15 (p119) + {0xf0c0, 0xc040, Operation::ANDw, Decoder::AND_OR_EOR}, // 4-15 (p119) + {0xf0c0, 0xc080, Operation::ANDl, Decoder::AND_OR_EOR}, // 4-15 (p119) + + {0xf0c0, 0x8000, Operation::ORb, Decoder::AND_OR_EOR}, // 4-150 (p254) + {0xf0c0, 0x8040, Operation::ORw, Decoder::AND_OR_EOR}, // 4-150 (p254) + {0xf0c0, 0x8080, Operation::ORl, Decoder::AND_OR_EOR}, // 4-150 (p254) + + {0xf0c0, 0xb000, Operation::EORb, Decoder::AND_OR_EOR}, // 4-100 (p204) + {0xf0c0, 0xb040, Operation::EORw, Decoder::AND_OR_EOR}, // 4-100 (p204) + {0xf0c0, 0xb080, Operation::EORl, Decoder::AND_OR_EOR}, // 4-100 (p204) + + {0xffc0, 0x0600, Operation::ADDb, Decoder::EORI_ORI_ANDI_SUBI_ADDI}, // 4-9 (p113) + {0xffc0, 0x0640, Operation::ADDw, Decoder::EORI_ORI_ANDI_SUBI_ADDI}, // 4-9 (p113) + {0xffc0, 0x0680, Operation::ADDl, Decoder::EORI_ORI_ANDI_SUBI_ADDI}, // 4-9 (p113) + + {0xffc0, 0x0200, Operation::ANDb, Decoder::EORI_ORI_ANDI_SUBI_ADDI}, // 4-18 (p122) + {0xffc0, 0x0240, Operation::ANDw, Decoder::EORI_ORI_ANDI_SUBI_ADDI}, // 4-18 (p122) + {0xffc0, 0x0280, Operation::ANDl, Decoder::EORI_ORI_ANDI_SUBI_ADDI}, // 4-18 (p122) + + {0xffc0, 0x0000, Operation::ORb, Decoder::EORI_ORI_ANDI_SUBI_ADDI}, // 4-153 (p257) + {0xffc0, 0x0040, Operation::ORw, Decoder::EORI_ORI_ANDI_SUBI_ADDI}, // 4-153 (p257) + {0xffc0, 0x0080, Operation::ORl, Decoder::EORI_ORI_ANDI_SUBI_ADDI}, // 4-153 (p257) + + {0xffc0, 0x0a00, Operation::EORb, Decoder::EORI_ORI_ANDI_SUBI_ADDI}, // 4-102 (p206) + {0xffc0, 0x0a40, Operation::EORw, Decoder::EORI_ORI_ANDI_SUBI_ADDI}, // 4-102 (p206) + {0xffc0, 0x0a80, Operation::EORl, Decoder::EORI_ORI_ANDI_SUBI_ADDI}, // 4-102 (p206) + + {0xffc0, 0x0400, Operation::SUBb, Decoder::EORI_ORI_ANDI_SUBI_ADDI}, // 4-179 (p283) + {0xffc0, 0x0440, Operation::SUBw, Decoder::EORI_ORI_ANDI_SUBI_ADDI}, // 4-179 (p283) + {0xffc0, 0x0480, Operation::SUBl, Decoder::EORI_ORI_ANDI_SUBI_ADDI}, // 4-179 (p283) + + {0xf000, 0x1000, Operation::MOVEb, Decoder::MOVE}, // 4-116 (p220) + {0xf000, 0x2000, Operation::MOVEl, Decoder::MOVE}, // 4-116 (p220) + {0xf000, 0x3000, Operation::MOVEw, Decoder::MOVE}, // 4-116 (p220) + + {0xffc0, 0x46c0, Operation::MOVEtoSR, Decoder::MOVEtoSRCCR}, // 6-19 (p473) + {0xffc0, 0x44c0, Operation::MOVEtoCCR, Decoder::MOVEtoSRCCR}, // 4-123 (p227) + {0xffc0, 0x40c0, Operation::MOVEfromSR, Decoder::MOVEfromSR_NBCD}, // 6-17 (p471) + + {0xf1c0, 0xb000, Operation::CMPb, Decoder::CMP}, // 4-75 (p179) + {0xf1c0, 0xb040, Operation::CMPw, Decoder::CMP}, // 4-75 (p179) + {0xf1c0, 0xb080, Operation::CMPl, Decoder::CMP}, // 4-75 (p179) + + {0xf1c0, 0xb0c0, Operation::CMPw, Decoder::CMPA}, // 4-77 (p181) + {0xf1c0, 0xb1c0, Operation::CMPl, Decoder::CMPA}, // 4-77 (p181) + + {0xffc0, 0x0c00, Operation::CMPb, Decoder::CMPI}, // 4-79 (p183) + {0xffc0, 0x0c40, Operation::CMPw, Decoder::CMPI}, // 4-79 (p183) + {0xffc0, 0x0c80, Operation::CMPl, Decoder::CMPI}, // 4-79 (p183) + + {0xf1f8, 0xb108, Operation::CMPb, Decoder::CMPM}, // 4-81 (p185) + {0xf1f8, 0xb148, Operation::CMPw, Decoder::CMPM}, // 4-81 (p185) + {0xf1f8, 0xb188, Operation::CMPl, Decoder::CMPM}, // 4-81 (p185) + +// {0xff00, 0x6000, Operation::BRA, Decoder::BRA}, // 4-55 (p159) TODO: confirm that this really, really is just a special case of Bcc. + {0xf000, 0x6000, Operation::Bcc, Decoder::Bcc_BSR}, // 4-25 (p129) and 4-59 (p163) + + {0xf1c0, 0x41c0, Operation::MOVEAl, Decoder::LEA}, // 4-110 (p214) + {0xffc0, 0x4840, Operation::MOVEAl, Decoder::PEA}, // 4-159 (p263) + + {0xf100, 0x7000, Operation::MOVEq, Decoder::MOVEq}, // 4-134 (p238) + + {0xffff, 0x4e70, Operation::None, Decoder::RESET}, // 6-83 (p537) + + {0xffc0, 0x4ec0, Operation::JMP, Decoder::JMP}, // 4-108 (p212) + {0xffc0, 0x4e80, Operation::JMP, Decoder::JSR}, // 4-109 (p213) + {0xffff, 0x4e75, Operation::JMP, Decoder::RTS}, // 4-169 (p273) + + {0xf0c0, 0x9000, Operation::SUBb, Decoder::ADD_SUB}, // 4-174 (p278) + {0xf0c0, 0x9040, Operation::SUBw, Decoder::ADD_SUB}, // 4-174 (p278) + {0xf0c0, 0x9080, Operation::SUBl, Decoder::ADD_SUB}, // 4-174 (p278) + + {0xf0c0, 0xd000, Operation::ADDb, Decoder::ADD_SUB}, // 4-4 (p108) + {0xf0c0, 0xd040, Operation::ADDw, Decoder::ADD_SUB}, // 4-4 (p108) + {0xf0c0, 0xd080, Operation::ADDl, Decoder::ADD_SUB}, // 4-4 (p108) + + {0xf1c0, 0xd0c0, Operation::ADDAw, Decoder::ADDA_SUBA}, // 4-7 (p111) + {0xf1c0, 0xd1c0, Operation::ADDAl, Decoder::ADDA_SUBA}, // 4-7 (p111) + {0xf1c0, 0x90c0, Operation::SUBAw, Decoder::ADDA_SUBA}, // 4-177 (p281) + {0xf1c0, 0x91c0, Operation::SUBAl, Decoder::ADDA_SUBA}, // 4-177 (p281) + + {0xf1c0, 0x5000, Operation::ADDQb, Decoder::ADDQ_SUBQ}, // 4-11 (p115) + {0xf1c0, 0x5040, Operation::ADDQw, Decoder::ADDQ_SUBQ}, // 4-11 (p115) + {0xf1c0, 0x5080, Operation::ADDQl, Decoder::ADDQ_SUBQ}, // 4-11 (p115) + + {0xf1c0, 0x5100, Operation::SUBQb, Decoder::ADDQ_SUBQ}, // 4-181 (p285) + {0xf1c0, 0x5140, Operation::SUBQw, Decoder::ADDQ_SUBQ}, // 4-181 (p285) + {0xf1c0, 0x5180, Operation::SUBQl, Decoder::ADDQ_SUBQ}, // 4-181 (p285) + + {0xf1f0, 0xd100, Operation::ADDXb, Decoder::ADDX_SUBX}, // 4-14 (p118) + {0xf1f0, 0xd140, Operation::ADDXw, Decoder::ADDX_SUBX}, // 4-14 (p118) + {0xf1f0, 0xd180, Operation::ADDXl, Decoder::ADDX_SUBX}, // 4-14 (p118) + + {0xf1f0, 0x9100, Operation::SUBXb, Decoder::ADDX_SUBX}, // 4-184 (p288) + {0xf1f0, 0x9140, Operation::SUBXw, Decoder::ADDX_SUBX}, // 4-184 (p288) + {0xf1f0, 0x9180, Operation::SUBXl, Decoder::ADDX_SUBX}, // 4-184 (p288) + + {0xf1c0, 0x0100, Operation::BTSTb, Decoder::BTST}, // 4-62 (p166) + {0xffc0, 0x0800, Operation::BTSTb, Decoder::BTSTIMM}, // 4-63 (p167) + + {0xf1c0, 0x0180, Operation::BCLRb, Decoder::BCLR}, // 4-31 (p135) + {0xffc0, 0x0880, Operation::BCLRb, Decoder::BCLRIMM}, // 4-32 (p136) + + {0xf0c0, 0x50c0, Operation::Scc, Decoder::Scc_DBcc}, // Scc: 4-173 (p276); DBcc: 4-91 (p195) + + {0xffc0, 0x4200, Operation::CLRb, Decoder::CLR_NEG_NEGX_NOT}, // 4-73 (p177) + {0xffc0, 0x4240, Operation::CLRw, Decoder::CLR_NEG_NEGX_NOT}, // 4-73 (p177) + {0xffc0, 0x4280, Operation::CLRl, Decoder::CLR_NEG_NEGX_NOT}, // 4-73 (p177) + {0xffc0, 0x4400, Operation::NEGb, Decoder::CLR_NEG_NEGX_NOT}, // 4-144 (p248) + {0xffc0, 0x4440, Operation::NEGw, Decoder::CLR_NEG_NEGX_NOT}, // 4-144 (p248) + {0xffc0, 0x4480, Operation::NEGl, Decoder::CLR_NEG_NEGX_NOT}, // 4-144 (p248) + {0xffc0, 0x4000, Operation::NEGXb, Decoder::CLR_NEG_NEGX_NOT}, // 4-146 (p250) + {0xffc0, 0x4040, Operation::NEGXw, Decoder::CLR_NEG_NEGX_NOT}, // 4-146 (p250) + {0xffc0, 0x4080, Operation::NEGXl, Decoder::CLR_NEG_NEGX_NOT}, // 4-146 (p250) + {0xffc0, 0x4600, Operation::NOTb, Decoder::CLR_NEG_NEGX_NOT}, // 4-148 (p250) + {0xffc0, 0x4640, Operation::NOTw, Decoder::CLR_NEG_NEGX_NOT}, // 4-148 (p250) + {0xffc0, 0x4680, Operation::NOTl, Decoder::CLR_NEG_NEGX_NOT}, // 4-148 (p250) + + {0xf1d8, 0xe100, Operation::ASLb, Decoder::ASLR_LSLR_ROLR_ROXLRr}, // 4-22 (p126) + {0xf1d8, 0xe140, Operation::ASLw, Decoder::ASLR_LSLR_ROLR_ROXLRr}, // 4-22 (p126) + {0xf1d8, 0xe180, Operation::ASLl, Decoder::ASLR_LSLR_ROLR_ROXLRr}, // 4-22 (p126) + {0xffc0, 0xe1c0, Operation::ASLm, Decoder::ASLR_LSLR_ROLR_ROXLRm}, // 4-22 (p126) + + {0xf1d8, 0xe000, Operation::ASRb, Decoder::ASLR_LSLR_ROLR_ROXLRr}, // 4-22 (p126) + {0xf1d8, 0xe040, Operation::ASRw, Decoder::ASLR_LSLR_ROLR_ROXLRr}, // 4-22 (p126) + {0xf1d8, 0xe080, Operation::ASRl, Decoder::ASLR_LSLR_ROLR_ROXLRr}, // 4-22 (p126) + {0xffc0, 0xe0c0, Operation::ASRm, Decoder::ASLR_LSLR_ROLR_ROXLRm}, // 4-22 (p126) + + {0xf1d8, 0xe108, Operation::LSLb, Decoder::ASLR_LSLR_ROLR_ROXLRr}, // 4-113 (p217) + {0xf1d8, 0xe148, Operation::LSLw, Decoder::ASLR_LSLR_ROLR_ROXLRr}, // 4-113 (p217) + {0xf1d8, 0xe188, Operation::LSLl, Decoder::ASLR_LSLR_ROLR_ROXLRr}, // 4-113 (p217) + {0xffc0, 0xe3c0, Operation::LSLm, Decoder::ASLR_LSLR_ROLR_ROXLRm}, // 4-113 (p217) + + {0xf1d8, 0xe008, Operation::LSRb, Decoder::ASLR_LSLR_ROLR_ROXLRr}, // 4-113 (p217) + {0xf1d8, 0xe048, Operation::LSRw, Decoder::ASLR_LSLR_ROLR_ROXLRr}, // 4-113 (p217) + {0xf1d8, 0xe088, Operation::LSRl, Decoder::ASLR_LSLR_ROLR_ROXLRr}, // 4-113 (p217) + {0xffc0, 0xe2c0, Operation::LSRm, Decoder::ASLR_LSLR_ROLR_ROXLRm}, // 4-113 (p217) + + {0xf1d8, 0xe118, Operation::ROLb, Decoder::ASLR_LSLR_ROLR_ROXLRr}, // 4-160 (p264) + {0xf1d8, 0xe158, Operation::ROLw, Decoder::ASLR_LSLR_ROLR_ROXLRr}, // 4-160 (p264) + {0xf1d8, 0xe198, Operation::ROLl, Decoder::ASLR_LSLR_ROLR_ROXLRr}, // 4-160 (p264) + {0xffc0, 0xe7c0, Operation::ROLm, Decoder::ASLR_LSLR_ROLR_ROXLRm}, // 4-160 (p264) + + {0xf1d8, 0xe018, Operation::RORb, Decoder::ASLR_LSLR_ROLR_ROXLRr}, // 4-160 (p264) + {0xf1d8, 0xe058, Operation::RORw, Decoder::ASLR_LSLR_ROLR_ROXLRr}, // 4-160 (p264) + {0xf1d8, 0xe098, Operation::RORl, Decoder::ASLR_LSLR_ROLR_ROXLRr}, // 4-160 (p264) + {0xffc0, 0xe6c0, Operation::RORm, Decoder::ASLR_LSLR_ROLR_ROXLRm}, // 4-160 (p264) + + {0xf1d8, 0xe110, Operation::ROXLb, Decoder::ASLR_LSLR_ROLR_ROXLRr}, // 4-163 (p267) + {0xf1d8, 0xe150, Operation::ROXLw, Decoder::ASLR_LSLR_ROLR_ROXLRr}, // 4-163 (p267) + {0xf1d8, 0xe190, Operation::ROXLl, Decoder::ASLR_LSLR_ROLR_ROXLRr}, // 4-163 (p267) + {0xffc0, 0xe5c0, Operation::ROXLm, Decoder::ASLR_LSLR_ROLR_ROXLRm}, // 4-163 (p267) + + {0xf1d8, 0xe010, Operation::ROXRb, Decoder::ASLR_LSLR_ROLR_ROXLRr}, // 4-163 (p267) + {0xf1d8, 0xe050, Operation::ROXRw, Decoder::ASLR_LSLR_ROLR_ROXLRr}, // 4-163 (p267) + {0xf1d8, 0xe090, Operation::ROXRl, Decoder::ASLR_LSLR_ROLR_ROXLRr}, // 4-163 (p267) + {0xffc0, 0xe4c0, Operation::ROXRm, Decoder::ASLR_LSLR_ROLR_ROXLRm}, // 4-163 (p267) + + {0xffc0, 0x48c0, Operation::MOVEMtoMl, Decoder::MOVEM}, // 4-128 (p232) + {0xffc0, 0x4880, Operation::MOVEMtoMw, Decoder::MOVEM}, // 4-128 (p232) + {0xffc0, 0x4cc0, Operation::MOVEMtoRl, Decoder::MOVEM}, // 4-128 (p232) + {0xffc0, 0x4c80, Operation::MOVEMtoRw, Decoder::MOVEM}, // 4-128 (p232) + + {0xf1f8, 0x0108, Operation::MOVEPtoRw, Decoder::MOVEP}, // 4-133 (p237) + {0xf1f8, 0x0148, Operation::MOVEPtoRl, Decoder::MOVEP}, // 4-133 (p237) + {0xf1f8, 0x0188, Operation::MOVEPtoMw, Decoder::MOVEP}, // 4-133 (p237) + {0xf1f8, 0x01c8, Operation::MOVEPtoMl, Decoder::MOVEP}, // 4-133 (p237) + + {0xffc0, 0x4a00, Operation::TSTb, Decoder::TST}, // 4-192 (p296) + {0xffc0, 0x4a40, Operation::TSTw, Decoder::TST}, // 4-192 (p296) + {0xffc0, 0x4a80, Operation::TSTl, Decoder::TST}, // 4-192 (p296) + + {0xf1c0, 0xc0c0, Operation::MULU, Decoder::MULU_MULS}, // 4-139 (p243) + {0xf1c0, 0xc1c0, Operation::MULS, Decoder::MULU_MULS}, // 4-136 (p240) + + {0xf1c0, 0x80c0, Operation::DIVU, Decoder::DIVU_DIVS}, // 4-97 (p201) + {0xf1c0, 0x81c0, Operation::DIVS, Decoder::DIVU_DIVS}, // 4-93 (p197) + + {0xfff0, 0x4e60, Operation::MOVEAl, Decoder::MOVEUSP}, // 6-21 (p475) + + {0xfff0, 0x4e40, Operation::TRAP, Decoder::TRAP}, // 4-188 (p292) + {0xffff, 0x4e76, Operation::TRAPV, Decoder::TRAPV}, // 4-191 (p295) + {0xf1c0, 0x4180, Operation::CHK, Decoder::CHK}, // 4-69 (p173) + + {0xffff, 0x4e77, Operation::RTE_RTR, Decoder::RTE_RTR}, // 4-168 (p272) [RTR] + {0xffff, 0x4e73, Operation::RTE_RTR, Decoder::RTE_RTR}, // 6-84 (p538) [RTE] + + {0xffff, 0x4e71, Operation::None, Decoder::NOP}, // 8-13 (p469) + + {0xf1f8, 0xc140, Operation::EXG, Decoder::EXG}, // 4-105 (p209) + {0xf1f8, 0xc148, Operation::EXG, Decoder::EXG}, // 4-105 (p209) + {0xf1f8, 0xc188, Operation::EXG, Decoder::EXG}, // 4-105 (p209) + + {0xfff8, 0x4840, Operation::SWAP, Decoder::EXT_SWAP}, // 4-185 (p289) + + {0xffff, 0x027c, Operation::ANDItoSR, Decoder::EORI_ORI_ANDI_SR}, + {0xffff, 0x023c, Operation::ANDItoCCR, Decoder::EORI_ORI_ANDI_SR}, + {0xffff, 0x0a7c, Operation::EORItoSR, Decoder::EORI_ORI_ANDI_SR}, + {0xffff, 0x0a3c, Operation::EORItoCCR, Decoder::EORI_ORI_ANDI_SR}, + {0xffff, 0x007c, Operation::ORItoSR, Decoder::EORI_ORI_ANDI_SR}, + {0xffff, 0x003c, Operation::ORItoCCR, Decoder::EORI_ORI_ANDI_SR}, + + {0xf1c0, 0x0140, Operation::BCHGb, Decoder::BCHG_BSET}, // 4-28 (p132) + {0xffc0, 0x0840, Operation::BCHGb, Decoder::BCHG_BSET}, // 4-29 (p133) + {0xf1c0, 0x01c0, Operation::BSETb, Decoder::BCHG_BSET}, // 4-57 (p161) + {0xffc0, 0x08c0, Operation::BSETb, Decoder::BCHG_BSET}, // 4-58 (p162) + + {0xffc0, 0x4ac0, Operation::TAS, Decoder::TAS}, // 4-186 (p290) + + {0xfff8, 0x4880, Operation::EXTbtow, Decoder::EXT_SWAP}, // 4-106 (p210) + {0xfff8, 0x48c0, Operation::EXTwtol, Decoder::EXT_SWAP}, // 4-106 (p210) + + {0xfff8, 0x4e50, Operation::LINK, Decoder::LINK}, // 4-111 (p215) + {0xfff8, 0x4e58, Operation::UNLINK, Decoder::UNLINK}, // 4-194 (p298) + + {0xffff, 0x4e72, Operation::STOP, Decoder::STOP}, // 6-85 (p539) + }; + + std::vector micro_op_pointers(65536, std::numeric_limits::max()); + + // The arbitrary_base is used so that the offsets returned by assemble_program into + // storage_.all_bus_steps_ can be retained and mapped into the final version of + // storage_.all_bus_steps_ at the end. + BusStep arbitrary_base; + +#define op(...) storage_.all_micro_ops_.emplace_back(__VA_ARGS__) +#define seq(...) &arbitrary_base + assemble_program(__VA_ARGS__) +#define ea(n) &storage_.effective_address_[n].full +#define a(n) &storage_.address_[n].full + +#define bw(x) (x) +#define bw2(x, y) (((x) << 8) | (y)) +#define l(x) (0x10000 | (x)) +#define l2(x, y) (0x10000 | ((x) << 8) | (y)) + + // Perform a linear search of the mappings above for this instruction. + for(ssize_t instruction = 65535; instruction >= 0; --instruction) { +#ifndef NDEBUG + int hits = 0; +#endif + for(const auto &mapping: mappings) { + if((instruction & mapping.mask) == mapping.value) { + auto operation = mapping.operation; + const auto micro_op_start = storage_.all_micro_ops_.size(); + + // The following fields are used commonly enough to be worth pulling out here. + const int ea_register = instruction & 7; + const int ea_mode = (instruction >> 3) & 7; + const int data_register = (instruction >> 9) & 7; + const int op_mode = (instruction >> 6)&7; + const bool op_mode_high_bit = !!(op_mode&4); + + // These are almost always true; they're non-const so that they can be corrected + // by the few deviations. + bool is_byte_access = (op_mode&3) == 0; + bool is_long_word_access = (op_mode&3) == 2; + +#define dec(n) decrement_action(is_long_word_access, is_byte_access, n) +#define inc(n) increment_action(is_long_word_access, is_byte_access, n) + + switch(mapping.decoder) { + case Decoder::STOP: { + storage_.instructions[instruction].requires_supervisor = true; + op(Action::None, seq("n")); + op(Action::PerformOperation); + } break; + + case Decoder::LINK: { + storage_.instructions[instruction].set_source(storage_, An, ea_register); + op(Action::PerformOperation, seq("np nW+ nw np", { ea(1), ea(1) })); + } break; + + case Decoder::UNLINK: { + storage_.instructions[instruction].set_destination(storage_, An, ea_register); + op(int(Action::CopyToEffectiveAddress) | MicroOp::DestinationMask, seq("nRd+ nrd np", { ea(1), ea(1) })); + op(Action::PerformOperation); + } break; + + case Decoder::TAS: { + const int mode = combined_mode(ea_mode, ea_register); + storage_.instructions[instruction].set_destination(storage_, ea_mode, ea_register); + switch(mode) { + default: continue; + + case Dn: // TAS Dn + op(Action::PerformOperation, seq("np")); + break; + + case Ind: // TAS (An) + case PostInc: // TAS (An)+ + op(Action::None, seq("nrd", { a(ea_register) }, false)); + op(Action::PerformOperation, seq("tas np", { a(ea_register) }, false)); + if(mode == PostInc) { + op(byte_inc(ea_register) | MicroOp::DestinationMask); + } + break; + + case PreDec: // TAS -(An) + op(byte_dec(ea_register) | MicroOp::DestinationMask, seq("n nrd", { a(ea_register) }, false)); + op(Action::PerformOperation, seq("tas np", { a(ea_register) }, false)); + break; + + case XXXl: // TAS (xxx).l + op(Action::None, seq("np")); + case XXXw: // TAS (xxx).w + case d16An: // TAS (d16, An) + case d8AnXn: // TAS (d8, An, Xn) + op(address_action_for_mode(mode) | MicroOp::DestinationMask, seq("np nrd", { ea(1) }, false)); + op(Action::PerformOperation, seq("tas np", { ea(1) }, false)); + break; + } + } break; + + case Decoder::BCHG_BSET: { + const int mode = combined_mode(ea_mode, ea_register); + + // Operations on a register are .l; all others are the default .b. + if(ea_mode == Dn) { + operation = (operation == Operation::BSETb) ? Operation::BSETl : Operation::BCHGl; + } + + storage_.instructions[instruction].set_destination(storage_, ea_mode, ea_register); + + if(instruction & 0x100) { + // The bit is nominated by a register. + storage_.instructions[instruction].set_source(storage_, Dn, data_register); + } else { + // The bit is nominated by a constant, that will be obtained right here. + storage_.instructions[instruction].set_source(storage_, Imm, 0); + op(int(Action::AssembleWordDataFromPrefetch) | MicroOp::SourceMask, seq("np")); + } + + switch(mode) { + default: continue; + + case Dn: // [BCHG/BSET].l Dn, Dn + // Execution length depends on the selected bit, so allow flexible time for that. + op(Action::None, seq("np")); + op(Action::PerformOperation, seq("r")); + break; + + case Ind: // [BCHG/BSET].b Dn, (An) + case PostInc: // [BCHG/BSET].b Dn, (An)+ + op(Action::None, seq("nrd np", { a(ea_register) }, false)); + op(Action::PerformOperation, seq("nw", { a(ea_register) }, false)); + if(mode == PostInc) { + op(byte_inc(ea_register) | MicroOp::DestinationMask); + } + break; + + case PreDec: // [BCHG/BSET].b Dn, -(An) + op(byte_dec(ea_register) | MicroOp::DestinationMask, seq("n nrd np", { a(ea_register) }, false)); + op(Action::PerformOperation, seq("nw", { a(ea_register) }, false)); + break; + + case XXXl: // [BCHG/BSET].b Dn, (xxx).l + op(Action::None, seq("np")); + case XXXw: // [BCHG/BSET].b Dn, (xxx).w + case d16An: // [BCHG/BSET].b Dn, (d16, An) + case d8AnXn: // [BCHG/BSET].b Dn, (d8, An, Xn) + op(address_action_for_mode(mode) | MicroOp::DestinationMask, seq(pseq("np nrd np", mode), { ea(1) }, false)); + op(Action::PerformOperation, seq("nw", { ea(1) }, false)); + break; + } + } break; + + case Decoder::EORI_ORI_ANDI_SR: { + // The source used here is always the high word of the prefetch queue. + storage_.instructions[instruction].requires_supervisor = !(instruction & 0x40); + op(Action::None, seq("np nn nn")); + op(Action::PerformOperation, seq("np np")); + } break; + + case Decoder::EXT_SWAP: { + storage_.instructions[instruction].set_destination(storage_, Dn, ea_register); + op(Action::PerformOperation, seq("np")); + } break; + + case Decoder::EXG: { + switch((instruction >> 3)&31) { + default: continue; + + case 0x08: + storage_.instructions[instruction].set_source(storage_, Dn, data_register); + storage_.instructions[instruction].set_destination(storage_, Dn, ea_register); + break; + + case 0x09: + storage_.instructions[instruction].set_source(storage_, An, data_register); + storage_.instructions[instruction].set_destination(storage_, An, ea_register); + break; + + case 0x11: + storage_.instructions[instruction].set_source(storage_, Dn, data_register); + storage_.instructions[instruction].set_destination(storage_, An, ea_register); + break; + } + + op(Action::PerformOperation, seq("np")); + } break; + + case Decoder::NOP: { + op(Action::None, seq("np")); + } break; + + case Decoder::RTE_RTR: { + storage_.instructions[instruction].requires_supervisor = instruction == 0x4e73; + + // TODO: something explicit to ensure the nR nr nr is exclusively linked. + op(Action::PrepareRTE_RTR, seq("nR nr nr", { &storage_.precomputed_addresses_[0], &storage_.precomputed_addresses_[1], &storage_.precomputed_addresses_[2] } )); + op(Action::PerformOperation, seq("np np")); + op(); + } break; + + case Decoder::AND_OR_EOR: { + const bool to_ea = op_mode_high_bit; + const bool is_eor = (instruction >> 12) == 0xb; + + // Weed out illegal operation modes. + if(op_mode == 7) continue; + + const int mode = combined_mode(ea_mode, ea_register); + + if(to_ea) { + storage_.instructions[instruction].set_destination(storage_, ea_mode, ea_register); + storage_.instructions[instruction].set_source(storage_, Dn, data_register); + + // Only EOR takes Dn as a destination effective address. + if(!is_eor && mode == Dn) continue; + + switch(is_long_word_access ? l(mode) : bw(mode)) { + default: continue; + + case bw(Dn): // EOR.bw Dn, Dn + op(Action::PerformOperation, seq("np")); + break; + + case l(Dn): // EOR.l Dn, Dn + op(Action::PerformOperation, seq("np nn")); + break; + + case bw(Ind): // [AND/OR/EOR].bw Dn, (An) + case bw(PostInc): // [AND/OR/EOR].bw Dn, (An)+ + op(Action::None, seq("nr", { a(ea_register) }, !is_byte_access)); + op(Action::PerformOperation, seq("np nw", { a(ea_register) }, !is_byte_access)); + if(mode == PostInc) { + op(inc(ea_register) | MicroOp::DestinationMask); + } + break; + + case bw(PreDec): // [AND/OR/EOR].bw Dn, -(An) + op(dec(ea_register) | MicroOp::SourceMask, seq("n nrd", { a(ea_register) }, !is_byte_access)); + op(Action::PerformOperation, seq("np nw", { a(ea_register) }, !is_byte_access)); + break; + + case l(PreDec): // [AND/OR/EOR].l Dn, -(An) + op(int(Action::Decrement4) | MicroOp::DestinationMask, seq("n")); + case l(Ind): // [AND/OR/EOR].l Dn, (An) + case l(PostInc): // [AND/OR/EOR].l Dn, (An)+ + op(int(Action::CopyToEffectiveAddress) | MicroOp::DestinationMask, seq("nRd+ nrd", { ea(1), ea(1) })); + op(Action::PerformOperation, seq("np nw- nW", { ea(1), ea(1) })); + if(mode == PostInc) { + op(int(Action::Increment4) | MicroOp::DestinationMask); + } + break; + + case bw(XXXl): // [AND/OR/EOR].bw Dn, (xxx).l + op(Action::None, seq("np")); + case bw(XXXw): // [AND/OR/EOR].bw Dn, (xxx).w + case bw(d16An): // [AND/OR/EOR].bw Dn, (d16, An) + case bw(d8AnXn): // [AND/OR/EOR].bw Dn, (d8, An, Xn) + op(address_action_for_mode(mode) | MicroOp::DestinationMask, seq(pseq("np nrd", mode), { ea(1) }, !is_byte_access)); + op(Action::PerformOperation, seq("np nw", { ea(1) }, !is_byte_access)); + break; + + case l(XXXl): // [AND/OR/EOR].l Dn, (xxx).l + op(Action::None, seq("np")); + case l(XXXw): // [AND/OR/EOR].l Dn, (xxx).w + case l(d16An): // [AND/OR/EOR].l Dn, (d16, An) + case l(d8AnXn): // [AND/OR/EOR].l Dn, (d8, An, Xn) + op(address_action_for_mode(mode) | MicroOp::DestinationMask, seq(pseq("np nRd+ nrd", mode), { ea(1), ea(1) })); + op(Action::PerformOperation, seq("np nw- nW", { ea(1), ea(1) })); + break; + } + } else { + // EORs can be to EA only. + if(is_eor) continue; + + storage_.instructions[instruction].set_source(storage_, ea_mode, ea_register); + storage_.instructions[instruction].set_destination(storage_, Dn, data_register); + + switch(is_long_word_access ? l(mode) : bw(mode)) { + default: continue; + + case bw(Dn): // [AND/OR].bw Dn, Dn + op(Action::PerformOperation, seq("np")); + break; + + case l(Dn): // [AND/OR].l Dn, Dn + op(Action::PerformOperation, seq("np nn")); + break; + + case bw(Ind): // [AND/OR].bw (An), Dn + case bw(PostInc): // [AND/OR].bw (An)+, Dn + op(Action::None, seq("nr", { a(ea_register) }, !is_byte_access)); + op(Action::PerformOperation, seq("np")); + if(mode == PostInc) { + op(inc(ea_register) | MicroOp::SourceMask); + } + break; + + case bw(PreDec): // [AND/OR].bw -(An), Dn + op(dec(ea_register) | MicroOp::SourceMask, seq("n nr", { a(ea_register) }, !is_byte_access)); + op(Action::PerformOperation, seq("np")); + break; + + case l(PreDec): // [AND/OR].l -(An), Dn + op(int(Action::Decrement4) | MicroOp::SourceMask, seq("n")); + case l(Ind): // [AND/OR].l (An), Dn, + case l(PostInc): // [AND/OR].l (An)+, Dn + op(int(Action::CopyToEffectiveAddress) | MicroOp::SourceMask, seq("nR+ nr", { ea(0), ea(0) })); + op(Action::PerformOperation, seq("np n")); + if(mode == PostInc) { + op(int(Action::Increment4) | MicroOp::SourceMask); + } + break; + + case bw(XXXl): // [AND/OR].bw (xxx).l, Dn + op(Action::None, seq("np")); + case bw(XXXw): // [AND/OR].bw (xxx).w, Dn + case bw(d16An): // [AND/OR].bw (d16, An), Dn + case bw(d16PC): // [AND/OR].bw (d16, PC), Dn + case bw(d8AnXn): // [AND/OR].bw (d8, An, Xn), Dn + case bw(d8PCXn): // [AND/OR].bw (d8, PX, Xn), Dn + op(address_action_for_mode(mode) | MicroOp::SourceMask, seq(pseq("np nr", mode), { ea(0) }, !is_byte_access)); + op(Action::PerformOperation, seq("np")); + break; + + case l(XXXl): // [AND/OR].bw (xxx).l, Dn + op(Action::None, seq("np")); + case l(XXXw): // [AND/OR].bw (xxx).w, Dn + case l(d16An): // [AND/OR].l (d16, An), Dn + case l(d16PC): // [AND/OR].l (d16, PC), Dn + case l(d8AnXn): // [AND/OR].l (d8, An, Xn), Dn + case l(d8PCXn): // [AND/OR].l (d8, PX, Xn), Dn + op(address_action_for_mode(mode) | MicroOp::SourceMask, seq(pseq("np nR+ nr", mode), { ea(0), ea(0) })); + op(Action::PerformOperation, seq("np n")); + break; + + case bw(Imm): // [AND/OR].bw #, Dn + op(int(Action::AssembleWordDataFromPrefetch) | MicroOp::SourceMask, seq("np")); + op(Action::PerformOperation, seq("np")); + break; + + case l(Imm): // [AND/OR].l #, Dn + op(Action::None, seq("np")); + op(int(Action::AssembleLongWordDataFromPrefetch) | MicroOp::SourceMask, seq("np")); + op(Action::PerformOperation, seq("np nn")); + break; + } + } + } break; + + case Decoder::DIVU_DIVS: { + storage_.instructions[instruction].set_source(storage_, ea_mode, ea_register); + storage_.instructions[instruction].set_destination(storage_, Dn, data_register); + + const int mode = combined_mode(ea_mode, ea_register); + switch(mode) { + default: continue; + + case Dn: // [DIVU/DIVS] Dn, Dn + op(Action::PerformOperation, seq("r")); + op(Action::None, seq("np")); + break; + + case Ind: // [DIVU/DIVS] (An), Dn + case PostInc: // [DIVU/DIVS] (An)+, Dn + op(Action::None, seq("nr", { a(ea_register) })); + op(Action::PerformOperation, seq("r np")); + if(mode == PostInc) { + op(int(Action::Increment2) | MicroOp::SourceMask); + } + break; + + case PreDec: // [DIVU/DIVS] -(An), Dn + op(int(Action::Decrement2) | MicroOp::SourceMask, seq("nr", { a(ea_register) })); + op(Action::PerformOperation, seq("r np")); + break; + + case XXXl: // [DIVU/DIVS] (XXX).l, Dn + op(Action::None, seq("np")); + case XXXw: // [DIVU/DIVS] (XXX).w, Dn + case d16An: // [DIVU/DIVS] (d16, An), Dn + case d16PC: // [DIVU/DIVS] (d16, PC), Dn + case d8AnXn: // [DIVU/DIVS] (d8, An, Xn), Dn + case d8PCXn: // [DIVU/DIVS] (d8, PC, Xn), Dn + op(address_action_for_mode(mode) | MicroOp::SourceMask, seq("np nr", { ea(0) })); + op(Action::PerformOperation, seq("r np")); + break; + + case Imm: // [DIVU/DIVS] #, Dn + // DEVIATION FROM YACHT.TXT. It shows an additional np, which is incorrect. + op(int(Action::AssembleWordDataFromPrefetch) | MicroOp::SourceMask, seq("np")); + op(Action::PerformOperation, seq("r np")); + break; + } + } break; + + case Decoder::MULU_MULS: { + storage_.instructions[instruction].set_source(storage_, ea_mode, ea_register); + storage_.instructions[instruction].set_destination(storage_, Dn, data_register); + + const int mode = combined_mode(ea_mode, ea_register); + switch(mode) { + default: continue; + + case Dn: // [MULU/MULS] Dn, Dn + op(Action::None, seq("np")); + op(Action::PerformOperation, seq("r")); + break; + + case Ind: // [MULU/MULS] (An), Dn + case PostInc: // [MULU/MULS] (An)+, Dn + op(Action::None, seq("nr np", { a(ea_register) })); + op(Action::PerformOperation, seq("r")); + if(mode == PostInc) { + op(int(Action::Increment2) | MicroOp::SourceMask); + } + break; + + case PreDec: // [MULU/MULS] -(An), Dn + op(int(Action::Decrement2) | MicroOp::SourceMask, seq("n nr np", { a(ea_register) })); + op(Action::PerformOperation, seq("r")); + break; + + case XXXl: // [MULU/MULS] (XXX).l, Dn + op(Action::None, seq("np")); + case XXXw: // [MULU/MULS] (XXX).w, Dn + case d16An: // [MULU/MULS] (d16, An), Dn + case d16PC: // [MULU/MULS] (d16, PC), Dn + case d8AnXn: // [MULU/MULS] (d8, An, Xn), Dn + case d8PCXn: // [MULU/MULS] (d8, PX, Xn), Dn + op(address_action_for_mode(mode) | MicroOp::SourceMask, seq(pseq("n np nr np", mode), { ea(0) })); + op(Action::PerformOperation, seq("r")); + break; + + case Imm: // [MULU/MULS] #, Dn + // DEVIATION FROM YACHT.TXT. It shows an additional np, which is incorrect. + op(int(Action::AssembleWordDataFromPrefetch) | MicroOp::SourceMask, seq("np np")); + op(Action::PerformOperation, seq("r")); + break; + } + } break; + + case Decoder::EORI_ORI_ANDI_SUBI_ADDI: { + const int mode = combined_mode(ea_mode, ea_register); + + // Source is always something cribbed from the instruction stream; + // destination is going to be in the write address unit. + storage_.instructions[instruction].source = &storage_.source_bus_data_[0]; + if(mode == Dn) { + storage_.instructions[instruction].destination = &storage_.data_[ea_register]; + } else { + storage_.instructions[instruction].destination = &storage_.destination_bus_data_[0]; + storage_.instructions[instruction].destination_address = &storage_.address_[ea_register]; + } + + switch(is_long_word_access ? l(mode) : bw(mode)) { + default: continue; + + case bw(Dn): // [EORI/ORI/ANDI/SUBI/ADDI].bw #, Dn + op(int(Action::AssembleWordDataFromPrefetch) | MicroOp::SourceMask, seq("np np")); + op(Action::PerformOperation); + break; + + case l(Dn): // [EORI/ORI/ANDI/SUBI/ADDI].l #, Dn + op(Action::None, seq("np")); + op(int(Action::AssembleLongWordDataFromPrefetch) | MicroOp::SourceMask, seq("np np nn")); + op(Action::PerformOperation); + break; + + case bw(Ind): // [EORI/ORI/ANDI/SUBI/ADDI].bw #, (An) + case bw(PostInc): // [EORI/ORI/ANDI/SUBI/ADDI].bw #, (An)+ + op( int(Action::AssembleWordDataFromPrefetch) | MicroOp::SourceMask, + seq("np nrd np", { a(ea_register) }, !is_byte_access)); + op(Action::PerformOperation, seq("nw", { a(ea_register) }, !is_byte_access)); + if(mode == PostInc) { + op(inc(ea_register) | MicroOp::DestinationMask); + } + break; + + case l(Ind): // [EORI/ORI/ANDI/SUBI/ADDI].l #, (An) + case l(PostInc): // [EORI/ORI/ANDI/SUBI/ADDI].l #, (An)+ + op(int(Action::CopyToEffectiveAddress) | MicroOp::DestinationMask, seq("np")); + op( int(Action::AssembleLongWordDataFromPrefetch) | MicroOp::SourceMask, + seq("np nRd+ nrd np", { ea(1), ea(1) })); + op(Action::PerformOperation, seq("nw- nW", { ea(1), ea(1) })); + if(mode == PostInc) { + op(int(Action::Increment4) | MicroOp::DestinationMask); + } + break; + + case bw(PreDec): // [EORI/ORI/ANDI/SUBI/ADDI].bw #, -(An) + op(dec(ea_register) | MicroOp::DestinationMask); + op( int(Action::AssembleWordDataFromPrefetch) | MicroOp::SourceMask, + seq("np n nrd np", { a(ea_register) }, !is_byte_access)); + op(Action::PerformOperation, seq("nw", { a(ea_register) }, !is_byte_access)); + break; + + case l(PreDec): // [EORI/ORI/ANDI/SUBI/ADDI].l #, -(An) + op(int(Action::Decrement4) | MicroOp::DestinationMask); + op(int(Action::CopyToEffectiveAddress) | MicroOp::DestinationMask, seq("np")); + op( int(Action::AssembleLongWordDataFromPrefetch) | MicroOp::SourceMask, + seq("np n nRd+ nrd np", { ea(1), ea(1) })); + op(Action::PerformOperation, seq("nw- nW", { ea(1), ea(1) })); + break; + + case bw(XXXw): // [EORI/ORI/ANDI/SUBI/ADDI].bw #, (xxx).w + case bw(d8AnXn): // [EORI/ORI/ANDI/SUBI/ADDI].bw #, (d8, An, Xn) + case bw(d16An): // [EORI/ORI/ANDI/SUBI/ADDI].bw #, (d16, An) + op(int(Action::AssembleWordDataFromPrefetch) | MicroOp::SourceMask, seq("np")); + op( address_action_for_mode(mode) | MicroOp::DestinationMask, + seq(pseq("np nrd np", mode), { ea(1) }, !is_byte_access)); + op(Action::PerformOperation, seq("nw", { ea(1) }, !is_byte_access)); + break; + + case l(XXXw): // [EORI/ORI/ANDI/SUBI/ADDI].l #, (xxx).w + case l(d8AnXn): // [EORI/ORI/ANDI/SUBI/ADDI].l #, (d8, An, Xn) + case l(d16An): // [EORI/ORI/ANDI/SUBI/ADDI].l #, (d16, An) + op(Action::None, seq("np")); + op(int(Action::AssembleLongWordDataFromPrefetch) | MicroOp::SourceMask, seq("np")); + op( address_action_for_mode(mode) | MicroOp::DestinationMask, + seq(pseq("np nRd+ nrd np", mode), { ea(1), ea(1) })); + op(Action::PerformOperation, seq("nw- nW", { ea(1), ea(1) })); + break; + + case bw(XXXl): // [EORI/ORI/ANDI/SUBI/ADDI].bw #, (xxx).l + op(int(Action::AssembleWordDataFromPrefetch) | MicroOp::SourceMask, seq("np np")); + op( int(Action::AssembleLongWordAddressFromPrefetch) | MicroOp::DestinationMask, + seq("np nrd np", { ea(1) }, !is_byte_access)); + op(Action::PerformOperation, seq("nw", { ea(1) }, !is_byte_access)); + break; + + case l(XXXl): // [EORI/ORI/ANDI/SUBI/ADDI].l #, (xxx).l + op(Action::None, seq("np")); + op(int(Action::AssembleLongWordDataFromPrefetch) | MicroOp::SourceMask, seq("np np")); + op( int(Action::AssembleLongWordAddressFromPrefetch) | MicroOp::DestinationMask, + seq("np nRd+ nrd np", { ea(1), ea(1) })); + op(Action::PerformOperation, seq("nw- nW", { ea(1), ea(1) })); + break; + } + } break; + + case Decoder::ADD_SUB: { + // ADD and SUB definitely always involve a data register and an arbitrary addressing mode; + // which direction they operate in depends on bit 8. + const bool reverse_source_destination = !(instruction & 256); + + const int mode = combined_mode(ea_mode, ea_register); + + if(reverse_source_destination) { + storage_.instructions[instruction].destination = &storage_.data_[data_register]; + storage_.instructions[instruction].source = &storage_.source_bus_data_[0]; + storage_.instructions[instruction].source_address = &storage_.address_[ea_register]; + + // Perform [ADD/SUB].blw , Dn + switch(is_long_word_access ? l(mode) : bw(mode)) { + default: continue; + + case bw(Dn): // ADD/SUB.bw Dn, Dn + storage_.instructions[instruction].source = &storage_.data_[ea_register]; + op(Action::PerformOperation, seq("np")); + break; + + case l(Dn): // ADD/SUB.l Dn, Dn + storage_.instructions[instruction].source = &storage_.data_[ea_register]; + op(Action::PerformOperation, seq("np nn")); + break; + + case bw(An): // ADD/SUB.bw An, Dn + // Address registers can't provide single bytes. + if(is_byte_access) continue; + storage_.instructions[instruction].source = &storage_.address_[ea_register]; + op(Action::PerformOperation, seq("np")); + break; + + case l(An): // ADD/SUB.l An, Dn + storage_.instructions[instruction].source = &storage_.address_[ea_register]; + op(Action::PerformOperation, seq("np nn")); + break; + + case bw(Ind): // ADD/SUB.bw (An), Dn + case bw(PostInc): // ADD/SUB.bw (An)+, Dn + op(Action::None, seq("nr np", { a(ea_register) }, !is_byte_access)); + if(ea_mode == PostInc) { + op(inc(ea_register) | MicroOp::SourceMask); + } + op(Action::PerformOperation); + break; + + case l(Ind): // ADD/SUB.l (An), Dn + case l(PostInc): // ADD/SUB.l (An)+, Dn + op( int(Action::CopyToEffectiveAddress) | MicroOp::SourceMask, + seq("nR+ nr np n", { ea(0), ea(0) })); + if(mode == PostInc) { + op(int(Action::Increment4) | MicroOp::SourceMask); + } + op(Action::PerformOperation); + break; + + case bw(PreDec): // ADD/SUB.bw -(An), Dn + op( dec(ea_register) | MicroOp::SourceMask, + seq("n nr np", { a(ea_register) }, !is_byte_access)); + op(Action::PerformOperation); + break; + + case l(PreDec): // ADD/SUB.l -(An), Dn + op(int(Action::Decrement4) | MicroOp::SourceMask); + op( int(Action::CopyToEffectiveAddress) | MicroOp::SourceMask, + seq("n nR+ nr np n", { ea(0), ea(0) })); + op(Action::PerformOperation); + break; + + case bw(XXXl): // ADD/SUB.bw (xxx).l, Dn + op(Action::None, seq("np")); + case bw(XXXw): // ADD/SUB.bw (xxx).w, Dn + case bw(d16PC): // ADD/SUB.bw (d16, PC), Dn + case bw(d8PCXn): // ADD/SUB.bw (d8, PC, Xn), Dn + case bw(d16An): // ADD/SUB.bw (d16, An), Dn + case bw(d8AnXn): // ADD/SUB.bw (d8, An, Xn), Dn + op( address_action_for_mode(mode) | MicroOp::SourceMask, + seq(pseq("np nr np", mode), { ea(0) }, !is_byte_access)); + op(Action::PerformOperation); + break; + + case l(XXXl): // ADD/SUB.l (xxx).l, Dn + op(Action::None, seq("np")); + case l(XXXw): // ADD/SUB.l (xxx).w, Dn + case l(d16PC): // ADD/SUB.l (d16, PC), Dn + case l(d8PCXn): // ADD/SUB.l (d8, PC, Xn), Dn + case l(d16An): // ADD/SUB.l (d16, An), Dn + case l(d8AnXn): // ADD/SUB.l (d8, An, Xn), Dn + op( address_action_for_mode(mode) | MicroOp::SourceMask, + seq(pseq("np nR+ nr np n", mode), { ea(0), ea(0) })); + op(Action::PerformOperation); + break; + + case bw(Imm): // ADD/SUB.bw #, Dn + op(int(Action::AssembleWordDataFromPrefetch) | MicroOp::SourceMask, seq("np np")); + op(Action::PerformOperation); + break; + + case l(Imm): // ADD/SUB.l #, Dn + op(Action::None, seq("np")); + op(int(Action::AssembleLongWordDataFromPrefetch) | MicroOp::SourceMask, seq("np np nn")); + op(Action::PerformOperation); + break; + } + } else { + storage_.instructions[instruction].source = &storage_.data_[data_register]; + + const auto destination_register = ea_register; + storage_.instructions[instruction].destination = &storage_.destination_bus_data_[0]; + storage_.instructions[instruction].destination_address = &storage_.address_[destination_register]; + + // Perform [ADD/SUB].blw Dn, + switch(is_long_word_access ? l(mode) : bw(mode)) { + default: continue; + + case bw(Ind): // ADD/SUB.bw Dn, (An) + case bw(PostInc): // ADD/SUB.bw Dn, (An)+ + op(Action::None, seq("nrd np", { a(destination_register) }, !is_byte_access)); + op(Action::PerformOperation, seq("nw", { a(destination_register) }, !is_byte_access)); + if(ea_mode == PostInc) { + op(inc(destination_register) | MicroOp::DestinationMask); + } + break; + + case l(Ind): // ADD/SUB.l Dn, (An) + case l(PostInc): // ADD/SUB.l Dn, (An)+ + op(int(Action::CopyToEffectiveAddress) | MicroOp::DestinationMask, seq("nRd+ nrd np", { ea(1), ea(1) })); + op(Action::PerformOperation, seq("nw- nW", { ea(1), ea(1) })); + if(ea_mode == PostInc) { + op(int(Action::Increment4) | MicroOp::DestinationMask); + } + break; + + case bw(PreDec): // ADD/SUB.bw Dn, -(An) + op( dec(destination_register) | MicroOp::DestinationMask, + seq("n nrd np", { a(destination_register) }, !is_byte_access)); + op(Action::PerformOperation, seq("nw", { a(destination_register) }, !is_byte_access)); + break; + + case l(PreDec): // ADD/SUB.l Dn, -(An) + op( int(Action::Decrement4) | MicroOp::DestinationMask); + op( int(Action::CopyToEffectiveAddress) | MicroOp::DestinationMask, + seq("n nRd+ nrd np", { ea(1), ea(1) })); + op( Action::PerformOperation, + seq("nw- nW", { ea(1), ea(1) })); + break; + + case bw(XXXl): // ADD/SUB.bw Dn, (xxx).l + op(Action::None, seq("np")); + case bw(XXXw): // ADD/SUB.bw Dn, (xxx).w + case bw(d16An): // ADD/SUB.bw (d16, An), Dn + case bw(d8AnXn): // ADD/SUB.bw (d8, An, Xn), Dn + op( address_action_for_mode(mode) | MicroOp::DestinationMask, + seq(pseq("np nrd np", mode), { ea(1) }, !is_byte_access)); + op(Action::PerformOperation, seq("nw", { ea(1) }, !is_byte_access)); + break; + + case l(XXXl): // ADD/SUB.l Dn, (xxx).l + op(Action::None, seq("np")); + case l(XXXw): // ADD/SUB.l Dn, (xxx).w + case l(d16An): // ADD/SUB.l (d16, An), Dn + case l(d8AnXn): // ADD/SUB.l (d8, An, Xn), Dn + op( address_action_for_mode(mode) | MicroOp::DestinationMask, + seq(pseq("np nRd+ nrd np", mode), { ea(1), ea(1) })); + op(Action::PerformOperation, seq("nw- nW", { ea(1), ea(1) })); + break; + } + } + } break; + + case Decoder::ADDA_SUBA: { + storage_.instructions[instruction].set_destination(storage_, 1, data_register); + storage_.instructions[instruction].set_source(storage_, ea_mode, ea_register); + + const int mode = combined_mode(ea_mode, ea_register); + is_long_word_access = op_mode_high_bit; + + switch(is_long_word_access ? l(mode) : bw(mode)) { + default: continue; + + case bw(Dn): // ADDA/SUBA.w Dn, An + case bw(An): // ADDA/SUBA.w An, An + case l(Dn): // ADDA/SUBA.l Dn, An + case l(An): // ADDA/SUBA.l An, An + op(Action::PerformOperation, seq("np nn")); + break; + + case bw(Ind): // ADDA/SUBA.w (An), An + case bw(PostInc): // ADDA/SUBA.w (An)+, An + op(Action::None, seq("nr np nn", { a(ea_register) })); + if(ea_mode == PostInc) { + op(int(Action::Increment2) | MicroOp::SourceMask); + } + op(Action::PerformOperation); + break; + + case l(Ind): // ADDA/SUBA.l (An), An + case l(PostInc): // ADDA/SUBA.l (An)+, An + op(int(Action::CopyToEffectiveAddress) | MicroOp::SourceMask, seq("nR+ nr np n", { ea(0), ea(0) })); + if(mode == PostInc) { + op(int(Action::Increment4) | MicroOp::SourceMask); + } + op(Action::PerformOperation); + break; + + case bw(PreDec): // ADDA/SUBA.w -(An), An + op(int(Action::Decrement2) | MicroOp::SourceMask); + op(Action::None, seq("n nr np nn", { a(ea_register) })); + op(Action::PerformOperation); + break; + + case l(PreDec): // ADDA/SUBA.l -(An), An + op(int(Action::Decrement4) | MicroOp::SourceMask); + op(int(Action::CopyToEffectiveAddress) | MicroOp::SourceMask, seq("n nR+ nr np n", { ea(0), ea(0) })); + op(Action::PerformOperation); + break; + + case bw(XXXl): // ADDA/SUBA.w (xxx).l, An + op(Action::None, seq("np")); + case bw(XXXw): // ADDA/SUBA.w (xxx).w, An + case bw(d16An): // ADDA/SUBA.w (d16, An), An + case bw(d8AnXn): // ADDA/SUBA.w (d8, An, Xn), An + case bw(d16PC): // ADDA/SUBA.w (d16, PC), An + case bw(d8PCXn): // ADDA/SUBA.w (d8, PC, Xn), An + op( address_action_for_mode(mode) | MicroOp::SourceMask, + seq(pseq("np nr np nn", mode), { ea(0) })); + op(Action::PerformOperation); + break; + + case l(XXXl): // ADDA/SUBA.l (xxx).l, An + op(Action::None, seq("np")); + case l(XXXw): // ADDA/SUBA.l (xxx).w, An + case l(d16An): // ADDA/SUBA.l (d16, An), An + case l(d8AnXn): // ADDA/SUBA.l (d8, An, Xn), An + case l(d16PC): // ADDA/SUBA.l (d16, PC), An + case l(d8PCXn): // ADDA/SUBA.l (d8, PC, Xn), An + op( address_action_for_mode(mode) | MicroOp::SourceMask, + seq(pseq("np nR+ nr np n", mode), { ea(0), ea(0) })); + op(Action::PerformOperation); + break; + + case bw(Imm): // ADDA/SUBA.w #, An + op(int(Action::AssembleWordDataFromPrefetch) | MicroOp::SourceMask, seq("np np nn")); + op(Action::PerformOperation); + break; + + case l(Imm): // ADDA/SUBA.l #, Dn + op(Action::None, seq("np")); + op(int(Action::AssembleLongWordDataFromPrefetch) | MicroOp::SourceMask, seq("np np nn")); + op(Action::PerformOperation); + break; + } + } break; + + case Decoder::ADDQ_SUBQ: { + storage_.instructions[instruction].set_destination(storage_, ea_mode, ea_register); + + const int mode = combined_mode(ea_mode, ea_register); + + // If the destination is an address register then byte mode isn't allowed, and + // flags shouldn't be affected (so, a different operation is used). + if(mode == An) { + if(is_byte_access) continue; + switch(operation) { + default: break; + case Operation::ADDQl: // TODO: should the adds be distinguished? If so, how? + case Operation::ADDQw: operation = Operation::ADDQAl; break; + case Operation::SUBQl: + case Operation::SUBQw: operation = Operation::SUBQAl; break; + } + } + + switch(is_long_word_access ? l(mode) : bw(mode)) { + default: continue; + + case bw(Dn): // [ADD/SUB]Q.bw #, Dn + op(Action::PerformOperation, seq("np")); + break; + + case l(Dn): // [ADD/SUB]Q.l #, Dn + case l(An): // [ADD/SUB]Q.l #, An + case bw(An): // [ADD/SUB]Q.bw #, An + op(Action::PerformOperation, seq("np nn")); + break; + + case bw(Ind): // [ADD/SUB]Q.bw #, (An) + case bw(PostInc): // [ADD/SUB]Q.bw #, (An)+ + op(Action::None, seq("nrd np", { a(ea_register) }, !is_byte_access)); + op(Action::PerformOperation, seq("nw", { a(ea_register) }, !is_byte_access)); + if(mode == PostInc) { + op(inc(ea_register) | MicroOp::DestinationMask); + } + break; + + case l(PreDec): // [ADD/SUB]Q.l #, -(An) + op(int(Action::Decrement4) | MicroOp::DestinationMask, seq("n")); + case l(Ind): // [ADD/SUB]Q.l #, (An) + case l(PostInc): // [ADD/SUB]Q.l #, (An)+ + op(int(Action::CopyToEffectiveAddress) | MicroOp::DestinationMask, seq("nRd+ nrd np", { ea(1), ea(1) })); + op(Action::PerformOperation, seq("nw- nW", { ea(1), ea(1) })); + if(mode == PostInc) { + op(int(Action::Increment4) | MicroOp::DestinationMask); + } + break; + + case bw(PreDec): // [ADD/SUB]Q.bw #, -(An) + op( dec(ea_register) | MicroOp::DestinationMask, + seq("n nrd np", { a(ea_register) }, !is_byte_access)); + op(Action::PerformOperation, seq("nw", { a(ea_register) }, !is_byte_access)); + break; + + case bw(XXXl): // [ADD/SUB]Q.bw #, (xxx).l + op(Action::None, seq("np")); + case bw(XXXw): // [ADD/SUB]Q.bw #, (xxx).w + case bw(d16An): // [ADD/SUB]Q.bw #, (d16, An) + case bw(d8AnXn): // [ADD/SUB]Q.bw #, (d8, An, Xn) + op(address_action_for_mode(mode) | MicroOp::DestinationMask, seq(pseq("np nrd np", mode), { ea(1) }, !is_byte_access)); + op(Action::PerformOperation, seq("nw", { ea(1) }, !is_byte_access)); + break; + + case l(XXXl): // [ADD/SUB]Q.l #, (xxx).l + op(Action::None, seq("np")); + case l(XXXw): // [ADD/SUB]Q.l #, (xxx).w + case l(d16An): // [ADD/SUB]Q.l #, (d16, An) + case l(d8AnXn): // [ADD/SUB]Q.l #, (d8, An, Xn) + op(address_action_for_mode(mode) | MicroOp::DestinationMask, seq(pseq("np nRd+ nrd np", mode), { ea(1), ea(1) })); + op(Action::PerformOperation, seq("nw- nW", { ea(1), ea(1) })); + break; + } + } break; + + case Decoder::ADDX_SUBX: { + if(instruction & 0x8) { + // Use predecrementing address registers. + storage_.instructions[instruction].set_source(storage_, Ind, ea_register); + storage_.instructions[instruction].set_destination(storage_, Ind, data_register); + + if(is_long_word_access) { + // Access order is very atypical here: it's lower parts each for both words, + // and then also a lower-part-first write. + op(int(Action::Decrement2) | MicroOp::SourceMask | MicroOp::DestinationMask); + op( int(Action::CopyToEffectiveAddress) | MicroOp::SourceMask | MicroOp::DestinationMask, + seq("n nr- nR nrd- nRd+", { ea(0), ea(0), ea(1), ea(1) })); + op(int(Action::Decrement2) | MicroOp::SourceMask | MicroOp::DestinationMask); + op(Action::PerformOperation, seq("nw- np nW", { ea(1), ea(1) })); + } else { + const int source_dec = dec(ea_register); + const int destination_dec = dec(data_register); + + int first_action; + if(source_dec == destination_dec) { + first_action = int(Action::Decrement4) | MicroOp::SourceMask | MicroOp::DestinationMask; + } else { + op(source_dec | MicroOp::SourceMask); + first_action = destination_dec | MicroOp::DestinationMask; + } + + op(first_action, seq("n nr nrd np", { ea(0), ea(1) }, !is_byte_access)); + op(Action::PerformOperation, seq("nw", { ea(1) }, !is_byte_access)); + } + } else { + // Use data registers. + storage_.instructions[instruction].set_source(storage_, Dn, ea_register); + storage_.instructions[instruction].set_destination(storage_, Dn, data_register); + + if(is_long_word_access) { + op(Action::PerformOperation, seq("np nn")); + } else { + op(Action::PerformOperation, seq("np")); + } + } + + } break; + + // This decoder actually decodes nothing; it just schedules a PerformOperation followed by an empty step. + case Decoder::Bcc_BSR: { + const int condition = (instruction >> 8) & 0xf; + if(condition == 1) { + // This is BSR, which is unconditional and means pushing a return address to the stack first. + + // Push the return address to the stack. + op(Action::PrepareBSR, seq("n nW+ nw", { ea(1), ea(1) })); + } + + // This is Bcc. + op(Action::PerformOperation); + op(); // The above looks terminal, but will be dynamically reprogrammed. + } break; + + // A little artificial, there's nothing really to decode for BRA. + case Decoder::BRA: { + op(Action::PerformOperation, seq("n np np")); + } break; + + // Decodes a BTST, potential mutating the operation into a BTSTl, + // or a BCLR. + case Decoder::BCLR: + case Decoder::BTST: { + const bool is_bclr = mapping.decoder == Decoder::BCLR; + + storage_.instructions[instruction].set_source(storage_, 0, data_register); + storage_.instructions[instruction].set_destination(storage_, ea_mode, ea_register); + + const int mode = combined_mode(ea_mode, ea_register); + switch(mode) { + default: continue; + + case Dn: // BTST.l Dn, Dn + if(is_bclr) { + operation = Operation::BCLRl; + op(Action::None, seq("np")); + op(Action::PerformOperation, seq("r")); + } else { + operation = Operation::BTSTl; + op(Action::PerformOperation, seq("np n")); + } + break; + + case Ind: // BTST.b Dn, (An) + case PostInc: // BTST.b Dn, (An)+ + op(Action::None, seq("nrd np", { a(ea_register) }, false)); + op(Action::PerformOperation, is_bclr ? seq("nw", { a(ea_register) }, false) : nullptr); + if(mode == PostInc) { + op(byte_inc(ea_register) | MicroOp::DestinationMask); + } + break; + + case PreDec: // BTST.b Dn, -(An) + op(byte_dec(ea_register) | MicroOp::DestinationMask, seq("n nrd np", { a(ea_register) }, false)); + op(Action::PerformOperation, is_bclr ? seq("nw", { a(ea_register) }, false) : nullptr); + break; + + case XXXl: // BTST.b Dn, (xxx).l + op(Action::None, seq("np")); + case XXXw: // BTST.b Dn, (xxx).w + case d16An: // BTST.b Dn, (d16, An) + case d8AnXn: // BTST.b Dn, (d8, An, Xn) + case d16PC: // BTST.b Dn, (d16, PC) + case d8PCXn: // BTST.b Dn, (d8, PC, Xn) + // PC-relative addressing isn't support for BCLR. + if((mode == d16PC || mode == d8PCXn) && is_bclr) continue; + + op( address_action_for_mode(mode) | MicroOp::DestinationMask, + seq(pseq("np nrd np", mode), { ea(1) }, false)); + op(Action::PerformOperation, is_bclr ? seq("nw", { ea(1) }, false) : nullptr); + break; + + case Imm: // BTST.b Dn, # + if(is_bclr) continue; + + /* Yacht.txt doesn't cover this; this is a guess. */ + op(int(Action::AssembleWordDataFromPrefetch) | MicroOp::DestinationMask, seq("np")); + op(Action::PerformOperation, seq("np")); + break; + } + } break; + + case Decoder::BCLRIMM: + case Decoder::BTSTIMM: { + const bool is_bclr = mapping.decoder == Decoder::BCLRIMM; + + storage_.instructions[instruction].source = &storage_.source_bus_data_[0]; + storage_.instructions[instruction].set_destination(storage_, ea_mode, ea_register); + + const int mode = combined_mode(ea_mode, ea_register); + switch(mode) { + default: continue; + + case Dn: // BTST.l #, Dn + if(is_bclr) { + operation = Operation::BCLRl; + op(int(Action::AssembleWordDataFromPrefetch) | MicroOp::SourceMask, seq("np np")); + op(Action::PerformOperation, seq("r")); + } else { + operation = Operation::BTSTl; + op(int(Action::AssembleWordDataFromPrefetch) | MicroOp::SourceMask, seq("np np n")); + op(Action::PerformOperation); + } + break; + + case Ind: // BTST.b #, (An) + case PostInc: // BTST.b #, (An)+ + op(int(Action::AssembleWordDataFromPrefetch) | MicroOp::SourceMask, seq("np nrd np", { a(ea_register) }, false)); + op(Action::PerformOperation, is_bclr ? seq("nw", { a(ea_register) }, false) : nullptr); + if(mode == PostInc) { + op(byte_inc(ea_register) | MicroOp::DestinationMask); + } + break; + + case PreDec: // BTST.b #, -(An) + op(int(Action::AssembleWordDataFromPrefetch) | MicroOp::SourceMask, seq("np")); + op(byte_dec(ea_register) | MicroOp::DestinationMask, seq("n nrd np", { a(ea_register) }, false)); + op(Action::PerformOperation, is_bclr ? seq("nw", { a(ea_register) }, false) : nullptr); + break; + + case XXXw: // BTST.b #, (xxx).w + case d16An: // BTST.b #, (d16, An) + case d8AnXn: // BTST.b #, (d8, An, Xn) + case d16PC: // BTST.b #, (d16, PC) + case d8PCXn: // BTST.b #, (d8, PC, Xn) + // PC-relative addressing isn't support for BCLR. + if((mode == d16PC || mode == d8PCXn) && is_bclr) continue; + + op(int(Action::AssembleWordDataFromPrefetch) | MicroOp::SourceMask, seq("np")); + op( address_action_for_mode(mode) | MicroOp::DestinationMask, + seq(pseq("np nrd np", mode), { ea(1) }, false)); + op(Action::PerformOperation, is_bclr ? seq("nw", { ea(1) }, false) : nullptr); + break; + + case XXXl: // BTST.b #, (xxx).l + op( int(Action::AssembleWordDataFromPrefetch) | MicroOp::SourceMask, seq("np np")); + op( int(Action::AssembleLongWordAddressFromPrefetch) | MicroOp::DestinationMask, + seq("np nrd np", { ea(1) }, false)); + op(Action::PerformOperation, is_bclr ? seq("nw", { ea(1) }, false) : nullptr); + break; + } + } break; + + // Decodes the format used by ABCD and SBCD. + case Decoder::ABCD_SBCD: { + if(instruction & 8) { + storage_.instructions[instruction].source = &storage_.source_bus_data_[0]; + storage_.instructions[instruction].destination = &storage_.destination_bus_data_[0]; + storage_.instructions[instruction].source_address = &storage_.address_[ea_register]; + storage_.instructions[instruction].destination_address = &storage_.address_[data_register]; + + const int source_dec = dec(ea_register); + const int destination_dec = dec(data_register); + + int first_action = source_dec | MicroOp::SourceMask | MicroOp::DestinationMask; + if(source_dec != destination_dec) { + first_action = source_dec | MicroOp::SourceMask; + op(destination_dec | MicroOp::DestinationMask); + } + + op( first_action, + seq("n nr nrd np nw", { a(ea_register), a(data_register), a(data_register) }, false)); + op(Action::PerformOperation); + } else { + storage_.instructions[instruction].source = &storage_.data_[ea_register]; + storage_.instructions[instruction].destination = &storage_.data_[data_register]; + + op(Action::PerformOperation, seq("np n")); + } + } break; + + case Decoder::ASLR_LSLR_ROLR_ROXLRr: { + storage_.instructions[instruction].set_destination(storage_, 0, ea_register); + + // All further decoding occurs at runtime; that's also when the proper number of + // no-op cycles will be scheduled. + if(((instruction >> 6) & 3) == 2) { + op(Action::None, seq("np nn")); + } else { + op(Action::None, seq("np n")); + } + + // Use a no-op bus cycle that can have its length filled in later. + op(Action::PerformOperation, seq("r")); + } break; + + case Decoder::ASLR_LSLR_ROLR_ROXLRm: { + storage_.instructions[instruction].set_destination(storage_, ea_mode, ea_register); + + const int mode = combined_mode(ea_mode, ea_register); + switch(mode) { + default: continue; + + case Ind: // AS(L/R)/LS(L/R)/RO(L/R)/ROX(L/R).w (An) + case PostInc: // AS(L/R)/LS(L/R)/RO(L/R)/ROX(L/R).w (An)+ + op(Action::None, seq("nrd np", { a(ea_register) })); + op(Action::PerformOperation, seq("nw", { a(ea_register) })); + if(ea_mode == PostInc) { + op(int(Action::Increment2) | MicroOp::DestinationMask); + } + break; + + case PreDec: // AS(L/R)/LS(L/R)/RO(L/R)/ROX(L/R).w -(An) + op(int(Action::Decrement2) | MicroOp::DestinationMask, seq("n nrd np", { a(ea_register) })); + op(Action::PerformOperation, seq("nw", { a(ea_register) })); + break; + + case XXXl: // AS(L/R)/LS(L/R)/RO(L/R)/ROX(L/R).w (xxx).l + op(Action::None, seq("np")); + case XXXw: // AS(L/R)/LS(L/R)/RO(L/R)/ROX(L/R).w (xxx).w + case d16An: // AS(L/R)/LS(L/R)/RO(L/R)/ROX(L/R).w (d16, An) + case d8AnXn: // AS(L/R)/LS(L/R)/RO(L/R)/ROX(L/R).w (d8, An, Xn) + op( address_action_for_mode(mode) | MicroOp::DestinationMask, + seq(pseq("np nrd np", mode), { ea(1) })); + op(Action::PerformOperation, seq("nw", { ea(1) })); + break; + } + } break; + + case Decoder::CLR_NEG_NEGX_NOT: { + storage_.instructions[instruction].set_destination(storage_, ea_mode, ea_register); + + const int mode = combined_mode(ea_mode, ea_register); + switch(is_long_word_access ? l(mode) : bw(mode)) { + default: continue; + + case bw(Dn): // [CLR/NEG/NEGX/NOT].bw Dn + case l(Dn): // [CLR/NEG/NEGX/NOT].l Dn + op(Action::PerformOperation, seq("np")); + break; + + case bw(Ind): // [CLR/NEG/NEGX/NOT].bw (An) + case bw(PostInc): // [CLR/NEG/NEGX/NOT].bw (An)+ + op(Action::None, seq("nrd", { a(ea_register) }, !is_byte_access)); + op(Action::PerformOperation, seq("np nw", { a(ea_register) }, !is_byte_access)); + if(ea_mode == PostInc) { + op(inc(ea_register) | MicroOp::DestinationMask); + } + break; + + case l(Ind): // [CLR/NEG/NEGX/NOT].l (An) + case l(PostInc): // [CLR/NEG/NEGX/NOT].l (An)+ + op(int(Action::CopyToEffectiveAddress) | MicroOp::DestinationMask, seq("nRd+ nrd", { ea(1), ea(1) })); + op(Action::PerformOperation, seq("np nw- nW", { ea(1), ea(1) })); + if(ea_mode == PostInc) { + op(int(Action::Increment4) | MicroOp::DestinationMask); + } + break; + + case bw(PreDec): // [CLR/NEG/NEGX/NOT].bw -(An) + op( dec(ea_register) | MicroOp::DestinationMask, + seq("nrd", { a(ea_register) }, !is_byte_access)); + op(Action::PerformOperation, seq("np nw", { a(ea_register) }, !is_byte_access)); + break; + + case l(PreDec): // [CLR/NEG/NEGX/NOT].l -(An) + op(int(Action::Decrement4) | MicroOp::DestinationMask); + op(int(Action::CopyToEffectiveAddress) | MicroOp::DestinationMask, + seq("n nRd+ nrd", { ea(1), ea(1) })); + op(Action::PerformOperation, seq("np nw- nW", { ea(1), ea(1) })); + break; + + case bw(XXXl): // [CLR/NEG/NEGX/NOT].bw (xxx).l + op(Action::None, seq("np")); + case bw(XXXw): // [CLR/NEG/NEGX/NOT].bw (xxx).w + case bw(d16An): // [CLR/NEG/NEGX/NOT].bw (d16, An) + case bw(d8AnXn): // [CLR/NEG/NEGX/NOT].bw (d8, An, Xn) + op( address_action_for_mode(mode) | MicroOp::DestinationMask, + seq(pseq("np nrd", mode), { ea(1) }, !is_byte_access)); + op(Action::PerformOperation, seq("np nw", { ea(1) }, !is_byte_access)); + break; + + case l(XXXl): // [CLR/NEG/NEGX/NOT].l (xxx).l + op(Action::None, seq("np")); + case l(XXXw): // [CLR/NEG/NEGX/NOT].l (xxx).w + case l(d16An): // [CLR/NEG/NEGX/NOT].l (d16, An) + case l(d8AnXn): // [CLR/NEG/NEGX/NOT].l (d8, An, Xn) + op( address_action_for_mode(mode) | MicroOp::DestinationMask, + seq(pseq("np nRd+ nrd", mode), { ea(1), ea(1) })); + op(Action::PerformOperation, seq("np nw- nW", { ea(1), ea(1) })); + break; + } + } break; + + case Decoder::CMP: { + storage_.instructions[instruction].destination = &storage_.data_[data_register]; + storage_.instructions[instruction].set_source(storage_, ea_mode, ea_register); + + // Byte accesses are not allowed with address registers. + if(is_byte_access && ea_mode == An) { + continue; + } + + const int mode = combined_mode(ea_mode, ea_register); + switch(is_long_word_access ? l(mode) : bw(mode)) { + default: continue; + + case bw(Dn): // CMP.bw Dn, Dn + case l(Dn): // CMP.l Dn, Dn + case bw(An): // CMP.w An, Dn + case l(An): // CMP.l An, Dn + op(Action::PerformOperation, seq("np")); + break; + + case bw(Ind): // CMP.bw (An), Dn + case bw(PostInc): // CMP.bw (An)+, Dn + op(Action::None, seq("nr np", { a(ea_register) }, !is_byte_access)); + if(mode == PostInc) { + op(inc(ea_register) | MicroOp::SourceMask); + } + op(Action::PerformOperation); + break; + + case l(Ind): // CMP.l (An), Dn + case l(PostInc): // CMP.l (An)+, Dn + op(int(Action::CopyToEffectiveAddress) | MicroOp::SourceMask, seq("nR+ nr np n", { ea(0), ea(0) })); + if(mode == PostInc) { + op(int(Action::Increment4) | MicroOp::SourceMask); + } + op(Action::PerformOperation); + break; + + case bw(PreDec): // CMP.bw -(An), Dn + op( dec(ea_register) | MicroOp::SourceMask, + seq("n nr np", { a(ea_register) }, !is_byte_access)); + op(Action::PerformOperation); + break; + + case l(PreDec): // CMP.l -(An), Dn + op(int(Action::Decrement4) | MicroOp::SourceMask); + op(int(Action::CopyToEffectiveAddress) | MicroOp::SourceMask, seq("n nR+ nr np n", { ea(0), ea(0) })); + op(Action::PerformOperation); + break; + + case bw(XXXl): // CMP.bw (xxx).l, Dn + op(Action::None, seq("np")); + case bw(XXXw): // CMP.bw (xxx).w, Dn + case bw(d16An): // CMP.bw (d16, An), Dn + case bw(d8AnXn): // CMP.bw (d8, An, Xn), Dn + case bw(d16PC): // CMP.bw (d16, PC), Dn + case bw(d8PCXn): // CMP.bw (d8, PC, Xn), Dn + op( address_action_for_mode(mode) | MicroOp::SourceMask, + seq(pseq("np nr np", mode), { ea(0) }, !is_byte_access)); + op(Action::PerformOperation); + break; + + case l(XXXl): // CMP.l (xxx).l, Dn + op(Action::None, seq("np")); + case l(XXXw): // CMP.l (xxx).w, Dn + case l(d16An): // CMP.l (d16, An), Dn + case l(d8AnXn): // CMP.l (d8, An, Xn), Dn + case l(d16PC): // CMP.l (d16, PC), Dn + case l(d8PCXn): // CMP.l (d8, PC, Xn), Dn + op( address_action_for_mode(mode) | MicroOp::SourceMask, + seq(pseq("np nR+ nr np n", mode), { ea(0), ea(0) })); + op(Action::PerformOperation); + break; + + case bw(Imm): // CMP.br #, Dn + storage_.instructions[instruction].source = &storage_.prefetch_queue_; + op(Action::PerformOperation, seq("np np")); + break; + + case l(Imm): // CMP.l #, Dn + storage_.instructions[instruction].source = &storage_.prefetch_queue_; + op(Action::None, seq("np")); + op(Action::PerformOperation, seq("np np n")); + break; + } + } break; + + case Decoder::CMPA: { + // Only operation modes 011 and 111 are accepted, and long words are selected + // by the top bit. + if(((op_mode)&3) != 3) continue; + is_long_word_access = op_mode == 7; + + storage_.instructions[instruction].set_source(storage_, ea_mode, ea_register); + storage_.instructions[instruction].destination = &storage_.address_[data_register]; + + const int mode = combined_mode(ea_mode, ea_register, true); + switch(is_long_word_access ? l(mode) : bw(mode)) { + default: continue; + + case bw(Dn): // CMPA.w [An/Dn], An + case l(Dn): // CMPA.l [An/Dn], An + op(Action::PerformOperation, seq("np n")); + break; + + case bw(Ind): // CMPA.w (An), An + case bw(PostInc): // CMPA.w (An)+, An + op(Action::None, seq("nr", { a(ea_register) })); + op(Action::PerformOperation, seq("np n")); + if(mode == PostInc) { + op(int(Action::Increment2) | MicroOp::SourceMask); + } + break; + + case l(Ind): // CMPA.l (An), An + case l(PostInc): // CMPA.l (An)+, An + op(int(Action::CopyToEffectiveAddress) | MicroOp::SourceMask, seq("nR+ nr", { ea(0), ea(0) })); + op(Action::PerformOperation, seq("np n")); + if(mode == PostInc) { + op(int(Action::Increment4) | MicroOp::SourceMask); + } + break; + + case bw(PreDec): // CMPA.w -(An), An + op(int(Action::Decrement2) | MicroOp::SourceMask, seq("n nr", { a(ea_register) })); + op(Action::PerformOperation, seq("np n")); + break; + + case l(PreDec): // CMPA.l -(An), An + op(int(Action::Decrement4) | MicroOp::SourceMask); + op(int(Action::CopyToEffectiveAddress) | MicroOp::SourceMask, seq("nR+ nr", { ea(0), ea(0) })); + op(Action::PerformOperation, seq("np n")); + break; + + case bw(XXXl): // CMPA.w (xxx).l, An + op(Action::None, seq("np")); + case bw(XXXw): // CMPA.w (xxx).w, An + case bw(d16PC): // CMPA.w (d16, PC), An + case bw(d8PCXn): // CMPA.w (d8, PC, Xn), An + case bw(d16An): // CMPA.w (d16, An), An + case bw(d8AnXn): // CMPA.w (d8, An, Xn), An + op(address_action_for_mode(mode) | MicroOp::SourceMask, seq(pseq("np nr", mode), { ea(0) })); + op(Action::PerformOperation, seq("np n")); + break; + + case l(XXXl): // CMPA.l (xxx).l, An + op(Action::None, seq("np")); + case l(XXXw): // CMPA.l (xxx).w, An + case l(d16PC): // CMPA.l (d16, PC), An + case l(d8PCXn): // CMPA.l (d8, PC, Xn), An + case l(d16An): // CMPA.l (d16, An), An + case l(d8AnXn): // CMPA.l (d8, An, Xn), An + op(address_action_for_mode(mode) | MicroOp::SourceMask, seq(pseq("np nR+ nr", mode), { ea(0), ea(0) })); + op(Action::PerformOperation, seq("np n")); + break; + + case bw(Imm): // CMPA.w #, An + storage_.instructions[instruction].source = &storage_.prefetch_queue_; + op(Action::PerformOperation, seq("np np n")); + break; + + case l(Imm): // CMPA.l #, An + storage_.instructions[instruction].source = &storage_.prefetch_queue_; + op(Action::None, seq("np")); + op(Action::PerformOperation, seq("np np n")); + break; + } + } break; + + case Decoder::CMPI: { + if(ea_mode == An) continue; + + const auto destination_mode = ea_mode; + const auto destination_register = ea_register; + + storage_.instructions[instruction].source = &storage_.source_bus_data_[0]; + storage_.instructions[instruction].set_destination(storage_, destination_mode, destination_register); + + const int mode = combined_mode(destination_mode, destination_register); + switch(is_long_word_access ? l(mode) : bw(mode)) { + default: continue; + + case bw(Dn): // CMPI.bw #, Dn + storage_.instructions[instruction].source = &storage_.prefetch_queue_; + op(Action::PerformOperation, seq("np np")); + break; + + case l(Dn): // CMPI.l #, Dn + storage_.instructions[instruction].source = &storage_.prefetch_queue_; + op(Action::None, seq("np")); + op(Action::PerformOperation, seq("np np n")); + break; + + case bw(Ind): // CMPI.bw #, (An) + case bw(PostInc): // CMPI.bw #, (An)+ + op(int(Action::AssembleWordDataFromPrefetch) | MicroOp::SourceMask, seq("np nrd np", { a(destination_register) }, !is_byte_access)); + if(mode == PostInc) { + op(inc(destination_register) | MicroOp::DestinationMask); + } + op(Action::PerformOperation); + break; + + case l(Ind): // CMPI.l #, (An) + case l(PostInc): // CMPI.l #, (An)+ + op(int(Action::CopyToEffectiveAddress) | MicroOp::DestinationMask, seq("np")); + op(int(Action::AssembleLongWordDataFromPrefetch) | MicroOp::SourceMask, seq("np nRd+ nrd np", { ea(1), ea(1) })); + if(mode == PostInc) { + op(int(Action::Increment4) | MicroOp::DestinationMask); + } + op(Action::PerformOperation); + break; + + case bw(PreDec): // CMPI.bw #, -(An) + op(int(Action::AssembleWordDataFromPrefetch) | MicroOp::SourceMask, seq("np n")); + op(dec(destination_register) | MicroOp::DestinationMask, seq("nrd np", { a(destination_register) }, !is_byte_access)); + op(Action::PerformOperation); + break; + + case l(PreDec): // CMPI.l #, -(An) + op(int(Action::Decrement4) | MicroOp::DestinationMask, seq("np")); + op(int(Action::AssembleLongWordDataFromPrefetch) | MicroOp::SourceMask, seq("np n")); + op(int(Action::CopyToEffectiveAddress) | MicroOp::DestinationMask, seq("nRd+ nrd np", { ea(1), ea(1) })); + op(Action::PerformOperation); + break; + + case bw(XXXw): // CMPI.bw #, (xxx).w + case bw(d16An): // CMPI.bw #, (d16, An) + case bw(d8AnXn): // CMPI.bw #, (d8, An, Xn) + op(int(Action::AssembleWordDataFromPrefetch) | MicroOp::SourceMask, seq("np")); + op( address_action_for_mode(mode) | MicroOp::DestinationMask, + seq(pseq("np nrd np", mode), { ea(1) }, !is_byte_access)); + op(Action::PerformOperation); + break; + + case l(d16An): // CMPI.l #, (d16, An) + case l(d8AnXn): // CMPI.l #, (d8, An, Xn) + op(int(Action::CopyToEffectiveAddress) | MicroOp::DestinationMask, seq("np")); + op(int(Action::AssembleLongWordDataFromPrefetch) | MicroOp::SourceMask, seq("np")); + op( calc_action_for_mode(mode) | MicroOp::DestinationMask, + seq(pseq("np nRd+ nrd np", mode), { ea(1), ea(1) })); + op(Action::PerformOperation); + break; + + case l(XXXw): // CMPI.l #, (xxx).w + op(Action::None, seq("np")); + op(int(Action::AssembleLongWordDataFromPrefetch) | MicroOp::SourceMask, seq("np np")); + op(int(Action::AssembleWordAddressFromPrefetch) | MicroOp::DestinationMask, seq("nRd+ nrd np", { ea(1), ea(1) })); + op(Action::PerformOperation); + break; + + case bw(XXXl): // CMPI.bw #, (xxx).l + op(int(Action::AssembleWordDataFromPrefetch) | MicroOp::SourceMask, seq("np np")); + op(int(Action::AssembleLongWordAddressFromPrefetch) | MicroOp::DestinationMask, seq("np nrd np", { ea(1) }, !is_byte_access)); + op(Action::PerformOperation); + break; + + case l(XXXl): // CMPI.l #, (xxx).l + op(Action::None, seq("np")); + op(int(Action::AssembleLongWordDataFromPrefetch) | MicroOp::SourceMask, seq("np np")); + op(int(Action::AssembleLongWordAddressFromPrefetch) | MicroOp::DestinationMask, seq("np nRd+ nrd np", { ea(1), ea(1) })); + op(Action::PerformOperation); + break; + } + } break; + + case Decoder::CMPM: { + storage_.instructions[instruction].set_source(storage_, 1, ea_register); + storage_.instructions[instruction].set_destination(storage_, 1, data_register); + + const bool is_byte_operation = operation == Operation::CMPb; + + switch(operation) { + default: continue; + + case Operation::CMPb: // CMPM.b, (An)+, (An)+ + case Operation::CMPw: { // CMPM.w, (An)+, (An)+ + op(Action::None, seq("nr nrd np", { a(data_register), a(ea_register) }, !is_byte_operation)); + op(Action::PerformOperation); + + const int source_inc = inc(ea_register); + const int destination_inc = inc(data_register); + if(destination_inc == source_inc) { + op(destination_inc | MicroOp::SourceMask | MicroOp::DestinationMask); + } else { + op(destination_inc | MicroOp::DestinationMask); + op(source_inc | MicroOp::SourceMask); + } + } break; + + case Operation::CMPl: + op( int(Action::CopyToEffectiveAddress) | MicroOp::SourceMask | MicroOp::DestinationMask, + seq("nR+ nr nRd+ nrd np", {ea(0), ea(0), ea(1), ea(1)})); + op(Action::PerformOperation); + op(int(Action::Increment4) | MicroOp::SourceMask | MicroOp::DestinationMask); + break; + } + } break; + + case Decoder::Scc_DBcc: { + if(ea_mode == 1) { + // This is a DBcc. Decode as such. + operation = Operation::DBcc; + storage_.instructions[instruction].source = &storage_.data_[ea_register]; + + // Jump straight into deciding what steps to take next, + // which will be selected dynamically. + op(Action::PerformOperation); + op(); + } else { + // This is an Scc. + + // Scc is inexplicably a read-modify-write operation. + storage_.instructions[instruction].set_source(storage_, ea_mode, ea_register); + storage_.instructions[instruction].set_destination(storage_, ea_mode, ea_register); + + const int mode = combined_mode(ea_mode, ea_register); + switch(mode) { + default: continue; + + case Dn: + op(Action::PerformOperation, seq("np")); + // TODO: if condition true, an extra n. + break; + + case Ind: + case PostInc: + op(Action::PerformOperation, seq("nr np nw", { a(ea_register), a(ea_register) }, false)); + if(mode == PostInc) { + op(inc(ea_register) | MicroOp::DestinationMask); + } + break; + + case PreDec: + op(dec(ea_register) | MicroOp::DestinationMask); + op(Action::PerformOperation, seq("n nr np nw", { a(ea_register), a(ea_register) }, false)); + break; + + case XXXw: + op(Action::None, seq("np")); + case XXXl: + case d16An: + case d8AnXn: + op(address_action_for_mode(mode) | MicroOp::DestinationMask, seq(pseq("np nrd", mode), { ea(1) } , false)); + op(Action::PerformOperation, seq("np nw", { ea(1) } , false)); + break; + } + } + + } break; + + case Decoder::JSR: { + storage_.instructions[instruction].source = &storage_.effective_address_[0]; + const int mode = combined_mode(ea_mode, ea_register); + switch(mode) { + default: continue; + case Ind: // JSR (An) + storage_.instructions[instruction].source = &storage_.address_[ea_register]; + op(Action::PrepareJSR); + op(Action::PerformOperation, seq("np nW+ nw np", { ea(1), ea(1) })); + break; + + case XXXl: // JSR (xxx).L + op(Action::None, seq("np")); + case XXXw: // JSR (xxx).W + case d16PC: // JSR (d16, PC) + case d16An: // JSR (d16, An) + op(Action::PrepareJSR); + op(address_action_for_mode(mode) | MicroOp::SourceMask); + op(Action::PerformOperation, seq("n np nW+ nw np", { ea(1), ea(1) })); + break; + + case d8PCXn: // JSR (d8, PC, Xn) + case d8AnXn: // JSR (d8, An, Xn) + op(Action::PrepareJSR); + op(calc_action_for_mode(mode) | MicroOp::SourceMask); + op(Action::PerformOperation, seq("n nn np nW+ nw np", { ea(1), ea(1) })); + break; + } + } break; + + case Decoder::RTS: { + storage_.instructions[instruction].source = &storage_.source_bus_data_[0]; + op(Action::PrepareRTS, seq("nU nu")); + op(Action::PerformOperation, seq("np np")); + } break; + + case Decoder::JMP: { + storage_.instructions[instruction].source = &storage_.effective_address_[0]; + const int mode = combined_mode(ea_mode, ea_register); + switch(mode) { + default: continue; + case Ind: // JMP (An) + storage_.instructions[instruction].source = &storage_.address_[ea_register]; + op(Action::PerformOperation, seq("np np")); + break; + + case XXXw: // JMP (xxx).W + case d16PC: // JMP (d16, PC) + case d16An: // JMP (d16, An) + op(address_action_for_mode(mode) | MicroOp::SourceMask); + op(Action::PerformOperation, seq("n np np")); + break; + + case d8PCXn: // JMP (d8, PC, Xn) + case d8AnXn: // JMP (d8, An, Xn) + op(calc_action_for_mode(mode) | MicroOp::SourceMask); + op(Action::PerformOperation, seq("n nn np np")); + break; + + case XXXl: // JMP (xxx).L + op(Action::None, seq("np")); + op(address_assemble_for_mode(mode) | MicroOp::SourceMask); + op(Action::PerformOperation, seq("np np")); + break; + } + } break; + + case Decoder::PEA: { + const int mode = combined_mode(ea_mode, ea_register); + storage_.instructions[instruction].source = + (mode == Ind) ? + &storage_.address_[ea_register] : + &storage_.effective_address_[0]; + + storage_.instructions[instruction].destination = &storage_.destination_bus_data_[0]; + storage_.instructions[instruction].destination_address = &storage_.address_[7]; + + // Common to all modes: decrement A7. + op(int(Action::Decrement4) | MicroOp::DestinationMask); + + switch(mode) { + default: continue; + + case Ind: // PEA (An) + op(int(Action::CopyToEffectiveAddress) | MicroOp::DestinationMask); + op(Action::PerformOperation, seq("np nW+ nw", { ea(1), ea(1) })); + break; + + case XXXl: // PEA (XXX).l + case XXXw: // PEA (XXX).w + op(int(Action::CopyToEffectiveAddress) | MicroOp::DestinationMask, (mode == XXXl) ? seq("np") : nullptr); + op(address_assemble_for_mode(mode) | MicroOp::SourceMask); + op(Action::PerformOperation, seq("np nW+ nw np", { ea(1), ea(1) })); + break; + + case d16An: // PEA (d16, An) + case d16PC: // PEA (d16, PC) + case d8AnXn: // PEA (d8, An, Xn) + case d8PCXn: // PEA (d8, PC, Xn) + op(int(Action::CopyToEffectiveAddress) | MicroOp::DestinationMask); + op(calc_action_for_mode(mode) | MicroOp::SourceMask, seq(pseq("np", mode))); + op(Action::PerformOperation, seq(pseq("np nW+ nw", mode), { ea(1), ea(1) })); + break; + } + } break; + + case Decoder::LEA: { + storage_.instructions[instruction].set_destination(storage_, An, data_register); + + const int mode = combined_mode(ea_mode, ea_register); + storage_.instructions[instruction].source_address = &storage_.address_[ea_register]; + storage_.instructions[instruction].source = + (mode == Ind) ? + &storage_.address_[ea_register] : + &storage_.effective_address_[0]; + + switch(mode) { + default: continue; + case Ind: // LEA (An), An (i.e. MOVEA) + op(Action::PerformOperation, seq("np")); + break; + + case XXXl: // LEA (xxx).L, An + op(Action::None, seq("np")); + case XXXw: // LEA (xxx).W, An + case d16An: // LEA (d16, An), An + case d16PC: // LEA (d16, PC), An + op(address_action_for_mode(mode) | MicroOp::SourceMask, seq("np np")); + op(Action::PerformOperation); + break; + + case d8AnXn: // LEA (d8, An, Xn), An + case d8PCXn: // LEA (d8, PC, Xn), An + op(calc_action_for_mode(mode) | MicroOp::SourceMask, seq("n np n np")); + op(Action::PerformOperation); + break; + } + } break; + + case Decoder::MOVEfromSR_NBCD: { + storage_.instructions[instruction].set_destination(storage_, ea_mode, ea_register); + + is_byte_access = operation == Operation::NBCD; + + const int mode = combined_mode(ea_mode, ea_register); + switch(mode) { + default: continue; + + case Dn: // MOVE SR, Dn + op(Action::PerformOperation, seq("np n")); + break; + + case Ind: // MOVE SR, (An) + case PostInc: // MOVE SR, (An)+ + op(Action::None, seq("nrd", { a(ea_register) }, !is_byte_access)); + op(Action::PerformOperation, seq("np nw", { a(ea_register) }, !is_byte_access)); + if(mode == PostInc) { + op(int(Action::Increment2) | MicroOp::DestinationMask); + } + break; + + case PreDec: // MOVE SR, -(An) + op(int(Action::Decrement2) | MicroOp::DestinationMask, seq("n nrd", { a(ea_register) }, !is_byte_access)); + op(Action::PerformOperation, seq("np nw", { a(ea_register) }, !is_byte_access)); + break; + + case XXXl: // MOVE SR, (xxx).l + op(Action::None, seq("np")); + case XXXw: // MOVE SR, (xxx).w + case d16An: // MOVE SR, (d16, An) + case d8AnXn: // MOVE SR, (d8, An, Xn) + op(address_action_for_mode(mode) | MicroOp::DestinationMask, seq(pseq("np nrd", mode), { ea(1) }, !is_byte_access)); + op(Action::PerformOperation, seq("np nw", { ea(1) }, !is_byte_access)); + break; + } + } break; + + case Decoder::MOVEtoSRCCR: { + if(ea_mode == An) continue; + storage_.instructions[instruction].set_source(storage_, ea_mode, ea_register); + storage_.instructions[instruction].requires_supervisor = (operation == Operation::MOVEtoSR); + + /* DEVIATION FROM YACHT.TXT: it has all of these reading an extra word from the PC; + this looks like a mistake so I've padded with nil cycles in the middle. */ + const int mode = combined_mode(ea_mode, ea_register); + switch(mode) { + default: continue; + + case Dn: // MOVE Dn, SR + op(Action::PerformOperation, seq("nn np")); + break; + + case Ind: // MOVE (An), SR + case PostInc: // MOVE (An)+, SR + op(Action::None, seq("nr nn nn np", { a(ea_register) })); + if(mode == PostInc) { + op(int(Action::Increment2) | MicroOp::SourceMask); + } + op(Action::PerformOperation); + break; + + case PreDec: // MOVE -(An), SR + op(Action::Decrement2, seq("n nr nn nn np", { a(ea_register) })); + op(Action::PerformOperation); + break; + + case XXXl: // MOVE (xxx).L, SR + op(Action::None, seq("np")); + case XXXw: // MOVE (xxx).W, SR + case d16PC: // MOVE (d16, PC), SR + case d8PCXn: // MOVE (d8, PC, Xn), SR + case d16An: // MOVE (d16, An), SR + case d8AnXn: // MOVE (d8, An, Xn), SR + op(address_action_for_mode(mode) | MicroOp::SourceMask, seq(pseq("np nr nn nn np", mode), { ea(0) })); + op(Action::PerformOperation); + break; + + case Imm: // MOVE #, SR + storage_.instructions[instruction].source = &storage_.prefetch_queue_; + op(int(Action::PerformOperation), seq("np nn nn np")); + break; + } + } break; + + case Decoder::MOVEq: { + storage_.instructions[instruction].destination = &storage_.data_[data_register]; + op(Action::PerformOperation, seq("np")); + } break; + + case Decoder::MOVEP: { + storage_.instructions[instruction].set_destination(storage_, An, ea_register); + storage_.instructions[instruction].set_source(storage_, Dn, data_register); + + switch(operation) { + default: continue; + + // Both of the MOVEP to memory instructions perform their operation first — it will + // break up the source value into 8-bit chunks for the write section. + case Operation::MOVEPtoMw: + op(Action::PerformOperation); + op(int(Action::CalcD16An) | MicroOp::DestinationMask, seq("np nW+ nw np", { ea(1), ea(1) }, false)); + break; + + case Operation::MOVEPtoMl: + op(Action::PerformOperation); + op(int(Action::CalcD16An) | MicroOp::DestinationMask, seq("np nW+ nWr+ nw+ nwr np", { ea(1), ea(1), ea(1), ea(1) }, false)); + break; + + case Operation::MOVEPtoRw: + op(int(Action::CalcD16An) | MicroOp::DestinationMask, seq("np nRd+ nrd np", { ea(1), ea(1) }, false)); + op(Action::PerformOperation); + break; + + case Operation::MOVEPtoRl: + op(int(Action::CalcD16An) | MicroOp::DestinationMask, seq("np nRd+ nR+ nrd+ nr np", { ea(1), ea(1), ea(1), ea(1) }, false)); + op(Action::PerformOperation); + break; + } + } break; + + case Decoder::MOVEM: { + // For the sake of commonality, both to R and to M will evaluate their addresses + // as if they were destinations. + storage_.instructions[instruction].set_destination(storage_, ea_mode, ea_register); + + // Standard prefix: acquire the register selection flags and fetch the next program + // word to replace them. + op(Action::CopyNextWord, seq("np")); + + // Do whatever is necessary to calculate the proper start address. + const int mode = combined_mode(ea_mode, ea_register); + const bool is_to_m = (operation == Operation::MOVEMtoMl || operation == Operation::MOVEMtoMw); + switch(mode) { + default: continue; + + case Ind: + case PreDec: + case PostInc: { + // Deal with the illegal combinations. + if(mode == PostInc && is_to_m) continue; + if(mode == PreDec && !is_to_m) continue; + } break; + + case XXXl: + op(Action::None, seq("np")); + case XXXw: + case d16An: + case d8AnXn: + case d16PC: + case d8PCXn: + // PC-relative addressing is permitted for moving to registers only. + if((mode == d16PC || mode == d8PCXn) && is_to_m) continue; + op(address_action_for_mode(mode) | MicroOp::DestinationMask, seq(pseq("np", mode))); + break; + } + + // Standard suffix: perform the MOVEM, which will mean evaluating the + // register selection flags and substituting the necessary reads or writes. + op(Action::PerformOperation); + + // A final program fetch will cue up the next instruction. + op(is_to_m ? Action::MOVEMtoMComplete : Action::MOVEMtoRComplete, seq("np")); + } break; + + case Decoder::MOVEUSP: { + storage_.instructions[instruction].requires_supervisor = true; + + // Observation here: because this is a privileged instruction, the user stack pointer + // definitely isn't currently [copied into] A7. + if(instruction & 0x8) { + // Transfer FROM the USP. + storage_.instructions[instruction].source = &storage_.stack_pointers_[0]; + storage_.instructions[instruction].set_destination(storage_, An, ea_register); + } else { + // Transfer TO the USP. + storage_.instructions[instruction].set_source(storage_, An, ea_register); + storage_.instructions[instruction].destination = &storage_.stack_pointers_[0]; + } + + op(Action::PerformOperation, seq("np")); + } break; + + // Decodes the format used by most MOVEs and all MOVEAs. + case Decoder::MOVE: { + const int destination_mode = (instruction >> 6) & 7; + + storage_.instructions[instruction].set_source(storage_, ea_mode, ea_register); + storage_.instructions[instruction].set_destination(storage_, destination_mode, data_register); + + // These don't come from the usual place. + is_byte_access = mapping.operation == Operation::MOVEb; + is_long_word_access = mapping.operation == Operation::MOVEl; + + const int combined_source_mode = combined_mode(ea_mode, ea_register, true, true); + const int combined_destination_mode = combined_mode(destination_mode, data_register, true, true); + const int mode = is_long_word_access ? + l2(combined_source_mode, combined_destination_mode) : + bw2(combined_source_mode, combined_destination_mode); + + // If the move is to an address register, switch the MOVE to a MOVEA. + // Also: there are no byte moves to address registers. + if(destination_mode == An) { + if(is_byte_access) { + continue; + } + operation = is_long_word_access ? Operation::MOVEAl : Operation::MOVEAw; + } + + // ... there are also no byte moves from address registers. + if(ea_mode == An && is_byte_access) continue; + + switch(mode) { + + // + // MOVE , Dn + // MOVEA , An + // + + case l2(Dn, Dn): // MOVE[A].l [An/Dn], [An/Dn] + case bw2(Dn, Dn): // MOVE[A].bw [An/Dn], [An/Dn] + op(Action::PerformOperation, seq("np")); + break; + + case l2(PreDec, Dn): // MOVE[A].l -(An), [An/Dn] + op(dec(ea_register) | MicroOp::SourceMask, seq("n")); + case l2(Ind, Dn): // MOVE[A].l (An)[+], [An/Dn] + op(int(Action::CopyToEffectiveAddress) | MicroOp::SourceMask, seq("nR+ nr np", { ea(0), ea(0) })); + op(Action::PerformOperation); + break; + + case bw2(Ind, Dn): // MOVE[A].bw (An)[+], [An/Dn] + op(Action::None, seq("nr np", { a(ea_register) }, !is_byte_access)); + op(Action::PerformOperation); + break; + + case bw2(PreDec, Dn): // MOVE[A].bw -(An), [An/Dn] + op(dec(ea_register) | MicroOp::SourceMask, seq("n nr np", { a(ea_register) }, !is_byte_access)); + op(Action::PerformOperation); + break; + + case l2(XXXl, Dn): // MOVE[A].l (xxx).L, [An/Dn] + op(Action::None, seq("np")); + case l2(XXXw, Dn): // MOVE[A].l (xxx).W, [An/Dn] + case l2(d16An, Dn): // MOVE[A].l (d16, An), [An/Dn] + case l2(d8AnXn, Dn): // MOVE[A].l (d8, An, Xn), [An/Dn] + case l2(d16PC, Dn): // MOVE[A].l (d16, PC), [An/Dn] + case l2(d8PCXn, Dn): // MOVE[A].l (d8, PC, Xn), [An/Dn] + op( address_action_for_mode(combined_source_mode) | MicroOp::SourceMask, + seq(pseq("np nR+ nr np", combined_source_mode), { ea(0), ea(0) })); + op(Action::PerformOperation); + break; + + case bw2(XXXl, Dn): // MOVE[A].bw (xxx).L, [An/Dn] + op(Action::None, seq("np")); + case bw2(XXXw, Dn): // MOVE[A].bw (xxx).W, [An/Dn] + case bw2(d16An, Dn): // MOVE[A].bw (d16, An), [An/Dn] + case bw2(d8AnXn, Dn): // MOVE[A].bw (d8, An, Xn), [An/Dn] + case bw2(d16PC, Dn): // MOVE[A].bw (d16, PC), [An/Dn] + case bw2(d8PCXn, Dn): // MOVE[A].bw (d8, PC, Xn), [An/Dn] + op( address_action_for_mode(combined_source_mode) | MicroOp::SourceMask, + seq(pseq("np nr np", combined_source_mode), { ea(0) }, + !is_byte_access)); + op(Action::PerformOperation); + break; + + case l2(Imm, Dn): // MOVE[A].l #, [An/Dn] + storage_.instructions[instruction].source = &storage_.prefetch_queue_; + op(Action::None, seq("np")); + op(int(Action::PerformOperation), seq("np np")); + break; + + case bw2(Imm, Dn): // MOVE[A].bw #, [An/Dn] + storage_.instructions[instruction].source = &storage_.prefetch_queue_; + op(int(Action::PerformOperation), seq("np np")); + break; + + // + // MOVE , (An)[+] + // + + case l2(Dn, Ind): // MOVE.l [An/Dn], (An)[+] + op(int(Action::CopyToEffectiveAddress) | MicroOp::DestinationMask); + op(Action::PerformOperation, seq("nW+ nw np", { ea(1), ea(1) })); + break; + + case bw2(Dn, Ind): // MOVE.bw [An/Dn], (An)[+] + op(Action::PerformOperation, seq("nw np", { a(data_register) }, !is_byte_access)); + break; + + case l2(PreDec, Ind): // MOVE.l -(An), (An)[+] + op(dec(ea_register) | MicroOp::SourceMask, seq("n")); + case l2(Ind, Ind): // MOVE.l (An)[+], (An)[+] + op( int(Action::CopyToEffectiveAddress) | MicroOp::DestinationMask | MicroOp::SourceMask, + seq("nR+ nr", { ea(0), ea(0) })); + op(Action::PerformOperation, seq("nW+ nw np", { ea(1), ea(1) })); + break; + + case bw2(Ind, Ind): // MOVE.bw (An)[+], (An)[+] + op(Action::None, seq("nr", { a(ea_register) }, !is_byte_access)); + op(Action::PerformOperation, seq("nw np", { a(data_register) }, !is_byte_access)); + break; + + case bw2(PreDec, Ind): // MOVE.bw -(An), (An)[+] + op(dec(ea_register) | MicroOp::SourceMask, seq("n nr", { a(ea_register) }, !is_byte_access)); + op(Action::PerformOperation, seq("nw np", { a(data_register) }, !is_byte_access)); + break; + + case l2(d16An, Ind): // MOVE.bw (d16, An), (An)[+] + case l2(d8AnXn, Ind): // MOVE.bw (d8, An, Xn), (An)[+] + case l2(d16PC, Ind): // MOVE.bw (d16, PC), (An)[+] + case l2(d8PCXn, Ind): // MOVE.bw (d8, PC, Xn), (An)[+] + op( int(Action::CopyToEffectiveAddress) | MicroOp::DestinationMask); + op( calc_action_for_mode(combined_source_mode) | MicroOp::SourceMask, + seq(pseq("np nR+ nr", combined_source_mode), { ea(0), ea(0) })); + op( Action::PerformOperation, + seq("nW+ nw np", { ea(1), ea(1) })); + break; + + case bw2(XXXl, Ind): // MOVE.bw (xxx).l, (An)[+] + op( Action::None, seq("np")); + case bw2(XXXw, Ind): // MOVE.bw (xxx).W, (An)[+] + case bw2(d16An, Ind): // MOVE.bw (d16, An), (An)[+] + case bw2(d8AnXn, Ind): // MOVE.bw (d8, An, Xn), (An)[+] + case bw2(d16PC, Ind): // MOVE.bw (d16, PC), (An)[+] + case bw2(d8PCXn, Ind): // MOVE.bw (d8, PC, Xn), (An)[+] + op( address_action_for_mode(combined_source_mode) | MicroOp::SourceMask, + seq(pseq("np nr", combined_source_mode), { ea(0) }, !is_byte_access)); + op( Action::PerformOperation, + seq("nw np", { a(data_register) }, !is_byte_access)); + break; + + case l2(XXXl, Ind): // MOVE.l (xxx).l, (An)[+] + case l2(XXXw, Ind): // MOVE.l (xxx).W, (An)[+] + op( int(Action::CopyToEffectiveAddress) | MicroOp::DestinationMask, + (combined_source_mode == XXXl) ? seq("np") : nullptr); + op( address_assemble_for_mode(combined_source_mode) | MicroOp::SourceMask, + seq("np nR+ nr", { ea(0), ea(0) })); + op( Action::PerformOperation, + seq("nW+ nw np", { ea(1), ea(1) })); + break; + + case l2(Imm, Ind): // MOVE.l #, (An)[+] + storage_.instructions[instruction].source = &storage_.prefetch_queue_; + op( int(Action::CopyToEffectiveAddress) | MicroOp::DestinationMask, seq("np") ); + op( Action::PerformOperation, seq("np nW+ nw np", { ea(1), ea(1) }) ); + break; + + case bw2(Imm, Ind): // MOVE.bw #, (An)[+] + storage_.instructions[instruction].source = &storage_.prefetch_queue_; + op(Action::PerformOperation, seq("np nw np", { a(data_register) }, !is_byte_access) ); + break; + + // + // MOVE , -(An) + // + + case bw2(Dn, PreDec): // MOVE.bw [An/Dn], -(An) + op(Action::PerformOperation); + op( dec(data_register) | MicroOp::DestinationMask, + seq("np nw", { a(data_register) }, !is_byte_access)); + break; + + case l2(Dn, PreDec): // MOVE.l [An/Dn], -(An) + op(Action::PerformOperation); + op( int(Action::Decrement2) | MicroOp::DestinationMask, seq("np") ); + op( int(Action::CopyToEffectiveAddress) | MicroOp::DestinationMask, seq("nw- nW", { ea(1), ea(1) } )); + op( int(Action::Decrement2) | MicroOp::DestinationMask ); + break; + + // TODO: verify when/where the predecrement occurs everywhere below; it may affect + // e.g. MOVE.w -(A6), -(A6) + + case bw2(PreDec, PreDec): // MOVE.bw -(An), -(An) + op(dec(ea_register) | MicroOp::SourceMask, seq("n")); + case bw2(Ind, PreDec): // MOVE.bw (An)[+], -(An) + op(dec(data_register) | MicroOp::DestinationMask, seq("nr", { a(ea_register) }, !is_byte_access)); + op(Action::PerformOperation, seq("np nw", { a(data_register) }, !is_byte_access)); + break; + + case l2(PreDec, PreDec): // MOVE.l -(An), -(An) + op(dec(ea_register) | MicroOp::SourceMask, seq("n")); + case l2(Ind, PreDec): // MOVE.l (An)[+], -(An) + op(int(Action::CopyToEffectiveAddress) | MicroOp::SourceMask, seq("nR+ nr", { ea(0), ea(0) } )); + op(Action::PerformOperation); + op(int(Action::Decrement2) | MicroOp::DestinationMask, seq("np") ); + op(int(Action::CopyToEffectiveAddress) | MicroOp::DestinationMask, seq("nw- nW", { ea(1), ea(1) } )); + op(int(Action::Decrement2) | MicroOp::DestinationMask ); + break; + + case bw2(XXXl, PreDec): // MOVE.bw (xxx).l, -(An) + op(Action::None, seq("np")); + case bw2(XXXw, PreDec): // MOVE.bw (xxx).w, -(An) + case bw2(d16An, PreDec): // MOVE.bw (d16, An), -(An) + case bw2(d8AnXn, PreDec): // MOVE.bw (d8, An, Xn), -(An) + case bw2(d16PC, PreDec): // MOVE.bw (d16, PC), -(An) + case bw2(d8PCXn, PreDec): // MOVE.bw (d8, PC, Xn), -(An) + op( address_action_for_mode(combined_source_mode) | MicroOp::SourceMask, + seq(pseq("np nr", combined_source_mode), { ea(0) }, !is_byte_access )); + op(Action::PerformOperation); + op(dec(data_register) | MicroOp::DestinationMask, seq("np nw", { a(data_register) }, !is_byte_access)); + break; + + case l2(XXXl, PreDec): // MOVE.l (xxx).w, -(An) + op(Action::None, seq("np")); + case l2(XXXw, PreDec): // MOVE.l (xxx).l, -(An) + case l2(d16An, PreDec): // MOVE.l (d16, An), -(An) + case l2(d8AnXn, PreDec): // MOVE.l (d8, An, Xn), -(An) + case l2(d16PC, PreDec): // MOVE.l (d16, PC), -(An) + case l2(d8PCXn, PreDec): // MOVE.l (d8, PC, Xn), -(An) + op( address_action_for_mode(combined_source_mode) | MicroOp::SourceMask, + seq(pseq("np nR+ nr", combined_source_mode), { ea(0), ea(0) } )); + op(Action::PerformOperation); + op(int(Action::Decrement2) | MicroOp::DestinationMask, seq("np") ); + op(int(Action::CopyToEffectiveAddress) | MicroOp::DestinationMask, seq("nw- nW", { ea(1), ea(1) })); + op(int(Action::Decrement2) | MicroOp::DestinationMask); + break; + + case bw2(Imm, PreDec): // MOVE.bw #, -(An) + op(int(Action::AssembleWordDataFromPrefetch) | MicroOp::SourceMask, seq("np")); + op(Action::PerformOperation); + op(dec(data_register) | MicroOp::DestinationMask, seq("np nw", { a(data_register) }, !is_byte_access)); + break; + + case l2(Imm, PreDec): // MOVE.l #, -(An) + op(Action::None, seq("np")); + op(int(Action::AssembleLongWordDataFromPrefetch) | MicroOp::SourceMask, seq("np")); + op(Action::PerformOperation); + op(int(Action::Decrement2) | MicroOp::DestinationMask, seq("np") ); + op(int(Action::CopyToEffectiveAddress) | MicroOp::DestinationMask, seq("nw- nW", { ea(1), ea(1) })); + op(int(Action::Decrement2) | MicroOp::DestinationMask); + break; + + // + // MOVE , (d16, An) + // MOVE , (d8, An, Xn) + // MOVE , (d16, PC) + // MOVE , (d8, PC, Xn) + // + +#define bw2d(s) bw2(s, d16An): case bw2(s, d8AnXn) +#define l2d(s) l2(s, d16An): case l2(s, d8AnXn) + + case bw2d(Dn): // MOVE.bw [An/Dn], (d16, An)/(d8, An, Xn) + op(calc_action_for_mode(combined_destination_mode) | MicroOp::DestinationMask, seq(pseq("np", combined_destination_mode))); + op(Action::PerformOperation, seq("nw np", { ea(1) }, !is_byte_access)); + break; + + case l2d(Dn): // MOVE.l [An/Dn], (d16, An)/(d8, An, Xn) + op(calc_action_for_mode(combined_destination_mode) | MicroOp::DestinationMask, seq(pseq("np", combined_destination_mode))); + op(Action::PerformOperation, seq("nW+ nw np", { ea(1), ea(1) })); + break; + + case bw2d(Ind): // MOVE.bw (An)[+], (d16, An)/(d8, An, Xn) + op(calc_action_for_mode(combined_destination_mode) | MicroOp::DestinationMask, seq("nr", { a(ea_register) }, !is_byte_access)); + op(Action::PerformOperation, seq(pseq("np nw np", combined_destination_mode), { ea(1) }, !is_byte_access)); + break; + + case l2d(Ind): // MOVE.l (An)[+], (d16, An)/(d8, An, Xn) + op(int(Action::CopyToEffectiveAddress) | MicroOp::SourceMask); + op(calc_action_for_mode(combined_destination_mode) | MicroOp::DestinationMask, seq("nR+ nr", { ea(0), ea(0) })); + op(Action::PerformOperation, seq(pseq("np nW+ nw np", combined_destination_mode), { ea(1), ea(1) })); + break; + + case bw2d(PreDec): // MOVE.bw -(An), (d16, An)/(d8, An, Xn) + op(dec(ea_register) | MicroOp::SourceMask, seq("n nr", { a(ea_register) }, !is_byte_access)); + op(Action::PerformOperation); + op(calc_action_for_mode(combined_destination_mode) | MicroOp::DestinationMask, seq("np nw np", { ea(1) }, !is_byte_access)); + break; + + case l2d(PreDec): // MOVE.l -(An), (d16, An)/(d8, An, Xn) + op(dec(ea_register) | MicroOp::SourceMask, seq("n")); + op(int(Action::CopyToEffectiveAddress) | MicroOp::SourceMask, seq("nR+ nr", { ea(0), ea(0) })); + op(Action::PerformOperation); + op(calc_action_for_mode(combined_destination_mode) | MicroOp::DestinationMask, seq("np nW+ nw np", { ea(1), ea(1) })); + break; + + case bw2d(XXXl): // MOVE.bw (xxx).l, (d16, An)/(d8, An, Xn) + op(Action::None, seq("np")); + case bw2d(XXXw): // MOVE.bw (xxx).w, (d16, An)/(d8, An, Xn) + case bw2d(d16An): // MOVE.bw (d16, An), (d16, An)/(d8, An, Xn) + case bw2d(d8AnXn): // MOVE.bw (d8, An, Xn), (d16, An)/(d8, An, Xn) + case bw2d(d16PC): // MOVE.bw (d16, PC), (d16, An)/(d8, An, Xn) + case bw2d(d8PCXn): // MOVE.bw (d8, PC, xn), (d16, An)/(d8, An, Xn) + op(address_action_for_mode(combined_source_mode) | MicroOp::SourceMask, seq(pseq("np nr", combined_source_mode), { ea(0) }, !is_byte_access)); + op(Action::PerformOperation); + op(calc_action_for_mode(combined_destination_mode) | MicroOp::DestinationMask, seq(pseq("np nw np", combined_destination_mode), { ea(1) }, !is_byte_access)); + break; + + case l2d(XXXl): // MOVE.l (xxx).l, (d16, An)/(d8, An, Xn) + op(Action::None, seq("np")); + case l2d(XXXw): // MOVE.l (xxx).w, (d16, An)/(d8, An, Xn) + case l2d(d16An): // MOVE.l (d16, An), (d16, An)/(d8, An, Xn) + case l2d(d8AnXn): // MOVE.l (d8, An, Xn), (d16, An)/(d8, An, Xn) + case l2d(d16PC): // MOVE.l (d16, PC), (d16, An)/(d8, An, Xn) + case l2d(d8PCXn): // MOVE.l (d8, PC, xn), (d16, An)/(d8, An, Xn) + op(address_action_for_mode(combined_source_mode) | MicroOp::SourceMask, seq(pseq("np nR+ nr", combined_source_mode), { ea(0), ea(0) })); + op(Action::PerformOperation); + op(calc_action_for_mode(combined_destination_mode) | MicroOp::DestinationMask, seq(pseq("np nW+ nw np", combined_destination_mode), { ea(1), ea(1) })); + break; + + case bw2d(Imm): // MOVE.bw #, (d16, An)/(d8, An, Xn) + op(int(Action::AssembleWordDataFromPrefetch) | MicroOp::SourceMask, seq("np")); + op(calc_action_for_mode(combined_destination_mode) | MicroOp::DestinationMask, seq(pseq("np", combined_destination_mode))); + op(Action::PerformOperation, seq("nw np", { ea(1) }, !is_byte_access)); + break; + + case l2d(Imm): // MOVE.l #, (d16, An)/(d8, An, Xn) + op(Action::None, seq("np")); + op(int(Action::AssembleLongWordDataFromPrefetch) | MicroOp::SourceMask, seq("np")); + op(calc_action_for_mode(combined_destination_mode) | MicroOp::DestinationMask, seq(pseq("np", combined_destination_mode))); + op(Action::PerformOperation, seq("nW+ nw np", { ea(1), ea(1) })); + break; + +#undef bw2d +#undef l2d + + // + // MOVE , (xxx).W + // MOVE , (xxx).L + // + + case bw2(Dn, XXXl): // MOVE.bw [An/Dn], (xxx).L + op(Action::None, seq("np")); + case bw2(Dn, XXXw): // MOVE.bw [An/Dn], (xxx).W + op(address_assemble_for_mode(combined_destination_mode) | MicroOp::DestinationMask, seq("np")); + op(Action::PerformOperation, seq("nw np", { ea(1) }, !is_byte_access)); + break; + + case l2(Dn, XXXl): // MOVE.l [An/Dn], (xxx).L + op(Action::None, seq("np")); + case l2(Dn, XXXw): // MOVE.l [An/Dn], (xxx).W + op(address_assemble_for_mode(combined_destination_mode) | MicroOp::DestinationMask, seq("np")); + op(Action::PerformOperation, seq("nW+ nw np", { ea(1), ea(1) })); + break; + + case bw2(Ind, XXXw): // MOVE.bw (An)[+], (xxx).W + op( address_assemble_for_mode(combined_destination_mode) | MicroOp::DestinationMask, + seq("nr np", { a(ea_register) }, !is_byte_access)); + op(Action::PerformOperation, seq("nw np", { ea(1) })); + break; + + case bw2(Ind, XXXl): // MOVE.bw (An)[+], (xxx).L + op(Action::None, seq("nr np", { a(ea_register) }, !is_byte_access)); + op(Action::PerformOperation, seq("nw np np", { &storage_.prefetch_queue_.full })); + break; + + case l2(Ind, XXXw): // MOVE.l (An)[+], (xxx).W + op( int(Action::CopyToEffectiveAddress) | MicroOp::SourceMask, + seq("nR+ nr", { ea(0), ea(0) })); + op( address_assemble_for_mode(combined_destination_mode) | MicroOp::DestinationMask, + seq("np", { a(ea_register) }, !is_byte_access)); + op(Action::PerformOperation, seq("nW+ nw np", { ea(1), ea(1) })); + break; + + case l2(Ind, XXXl): // MOVE (An)[+], (xxx).L + op(int(Action::CopyToEffectiveAddress) | MicroOp::SourceMask, seq("nR+ nr np", { ea(0), ea(0) })); + op(address_assemble_for_mode(combined_destination_mode)); + op(Action::PerformOperation, seq("nW+ nw np np", { ea(1), ea(1) })); + break; + + case bw2(PreDec, XXXw): // MOVE.bw -(An), (xxx).W + op( dec(ea_register) | MicroOp::SourceMask); + op( address_assemble_for_mode(combined_destination_mode) | MicroOp::DestinationMask, + seq("n nr np", { a(ea_register) }, !is_byte_access)); + op(Action::PerformOperation, seq("nw np", { ea(1) }, !is_byte_access)); + break; + + case bw2(PreDec, XXXl): // MOVE.bw -(An), (xxx).L + op(dec(ea_register) | MicroOp::SourceMask, seq("n nr np", { a(ea_register) }, !is_byte_access)); + op(address_assemble_for_mode(combined_destination_mode) | MicroOp::DestinationMask); + op(Action::PerformOperation, seq("nw np np", { ea(1) }, !is_byte_access)); + break; + + case l2(PreDec, XXXw): // MOVE.l -(An), (xxx).W + op( dec(ea_register) | MicroOp::SourceMask); + op( int(Action::CopyToEffectiveAddress) | MicroOp::SourceMask, + seq("n nR+ nr", { ea(0), ea(0) } )); + op( address_assemble_for_mode(combined_destination_mode) | MicroOp::DestinationMask, seq("np")); + op( Action::PerformOperation, + seq("np nW+ nw np", { ea(1), ea(1) })); + break; + + case l2(PreDec, XXXl): // MOVE.l -(An), (xxx).L + op( dec(ea_register) | MicroOp::SourceMask); + op( int(Action::CopyToEffectiveAddress) | MicroOp::SourceMask, + seq("n nR+ nr np", { ea(0), ea(0) } )); + op( address_assemble_for_mode(combined_destination_mode) | MicroOp::DestinationMask, seq("np")); + op( Action::PerformOperation, + seq("nW+ nw np np", { ea(1), ea(1) })); + break; + + case bw2(d16PC, XXXw): // MOVE.bw (d16, PC), (xxx).w + case bw2(d16An, XXXw): // MOVE.bw (d16, An), (xxx).w + case bw2(d8PCXn, XXXw): // MOVE.bw (d8, PC, Xn), (xxx).w + case bw2(d8AnXn, XXXw): // MOVE.bw (d8, An, Xn), (xxx).w + op(calc_action_for_mode(combined_source_mode) | MicroOp::SourceMask, seq(pseq("np nr", combined_source_mode), { ea(0) }, !is_byte_access)); + op(Action::PerformOperation); + op(int(Action::AssembleWordAddressFromPrefetch) | MicroOp::DestinationMask, seq("np nw np", { ea(1) }, !is_byte_access)); + break; + + case bw2(d16PC, XXXl): // MOVE.bw (d16, PC), (xxx).l + case bw2(d16An, XXXl): // MOVE.bw (d16, An), (xxx).l + case bw2(d8PCXn, XXXl): // MOVE.bw (d8, PC, Xn), (xxx).l + case bw2(d8AnXn, XXXl): // MOVE.bw (d8, An, Xn), (xxx).l + op(calc_action_for_mode(combined_source_mode) | MicroOp::SourceMask, seq(pseq("np np nr", combined_source_mode), { ea(0) }, !is_byte_access)); + op(Action::PerformOperation); + op(int(Action::AssembleLongWordAddressFromPrefetch) | MicroOp::DestinationMask, seq("np nw np", { ea(1) }, !is_byte_access)); + break; + + case l2(d16PC, XXXw): // MOVE.l (d16, PC), (xxx).w + case l2(d16An, XXXw): // MOVE.l (d16, An), (xxx).w + case l2(d8PCXn, XXXw): // MOVE.l (d8, PC, Xn), (xxx).w + case l2(d8AnXn, XXXw): // MOVE.l (d8, An, Xn), (xxx).w + op(calc_action_for_mode(combined_source_mode) | MicroOp::SourceMask, seq(pseq("np nR+ nr", combined_source_mode), { ea(0), ea(0) })); + op(Action::PerformOperation); + op(int(Action::AssembleWordAddressFromPrefetch) | MicroOp::DestinationMask, seq("np nW+ nw np", { ea(1), ea(1) })); + break; + + case l2(d16PC, XXXl): // MOVE.l (d16, PC), (xxx).l + case l2(d16An, XXXl): // MOVE.l (d16, An), (xxx).l + case l2(d8PCXn, XXXl): // MOVE.l (d8, PC, Xn), (xxx).l + case l2(d8AnXn, XXXl): // MOVE.l (d8, An, Xn), (xxx).l + op(calc_action_for_mode(combined_source_mode) | MicroOp::SourceMask, seq(pseq("np np nR+ nr", combined_source_mode), { ea(0), ea(0) })); + op(Action::PerformOperation); + op(int(Action::AssembleLongWordAddressFromPrefetch) | MicroOp::DestinationMask, seq("np nW+ nw np", { ea(1), ea(1) })); + break; + + case bw2(Imm, XXXw): // MOVE.bw #, (xxx).w + storage_.instructions[instruction].source = &storage_.destination_bus_data_[0]; + op(int(Action::AssembleWordDataFromPrefetch) | MicroOp::DestinationMask, seq("np")); + op(Action::PerformOperation); + op(int(Action::AssembleWordAddressFromPrefetch) | MicroOp::DestinationMask, seq("np nw np", { ea(1) }, !is_byte_access)); + break; + + case bw2(Imm, XXXl): // MOVE.bw #, (xxx).l + storage_.instructions[instruction].source = &storage_.destination_bus_data_[0]; + op(int(Action::AssembleWordDataFromPrefetch) | MicroOp::DestinationMask, seq("np np")); + op(Action::PerformOperation); + op(int(Action::AssembleLongWordAddressFromPrefetch) | MicroOp::DestinationMask, seq("np nw np", { ea(1) }, !is_byte_access)); + break; + + case l2(Imm, XXXw): // MOVE.l #, (xxx).w + storage_.instructions[instruction].source = &storage_.destination_bus_data_[0]; + op(int(Action::None), seq("np")); + op(int(Action::AssembleLongWordDataFromPrefetch) | MicroOp::DestinationMask, seq("np")); + op(Action::PerformOperation); + op(int(Action::AssembleWordAddressFromPrefetch) | MicroOp::DestinationMask, seq("np nW+ nw np", { ea(1), ea(1) })); + break; + + case l2(Imm, XXXl): // MOVE.l #, (xxx).l + storage_.instructions[instruction].source = &storage_.destination_bus_data_[0]; + op(int(Action::None), seq("np")); + op(int(Action::AssembleLongWordDataFromPrefetch) | MicroOp::DestinationMask, seq("np np")); + op(Action::PerformOperation); + op(int(Action::AssembleLongWordAddressFromPrefetch) | MicroOp::DestinationMask, seq("np nW+ nw np", { ea(1), ea(1) })); + break; + + case bw2(XXXw, XXXw): // MOVE.bw (xxx).w, (xxx).w + op(int(Action::AssembleWordAddressFromPrefetch) | MicroOp::SourceMask, seq("np nr", { ea(0) }, !is_byte_access)); + op(Action::PerformOperation, seq("np")); + op(int(Action::AssembleWordAddressFromPrefetch) | MicroOp::DestinationMask, seq("nw np", { ea(1) }, !is_byte_access)); + break; + + case bw2(XXXl, XXXw): // MOVE.bw (xxx).l, (xxx).w + op(int(Action::None), seq("np")); + op(int(Action::AssembleLongWordAddressFromPrefetch) | MicroOp::SourceMask, seq("np nr", { ea(0) }, !is_byte_access)); + op(Action::PerformOperation); + op(int(Action::AssembleWordAddressFromPrefetch) | MicroOp::DestinationMask, seq("np nw np", { ea(1) }, !is_byte_access)); + break; + + case bw2(XXXw, XXXl): // MOVE.bw (xxx).w, (xxx).L + op(int(Action::AssembleWordAddressFromPrefetch) | MicroOp::SourceMask, seq("np nr", { ea(0) }, !is_byte_access)); + op(Action::PerformOperation, seq("np")); + op(int(Action::AssembleLongWordAddressFromPrefetch) | MicroOp::DestinationMask, seq("nw np np", { ea(1) }, !is_byte_access)); + break; + + case bw2(XXXl, XXXl): // MOVE.bw (xxx).l, (xxx).l + op(int(Action::None), seq("np")); + op(int(Action::AssembleLongWordAddressFromPrefetch) | MicroOp::SourceMask, seq("np nr", { ea(0) }, !is_byte_access)); + op(Action::PerformOperation, seq("np")); + op(int(Action::AssembleLongWordAddressFromPrefetch) | MicroOp::DestinationMask, seq("nw np np", { ea(1) }, !is_byte_access)); + break; + + case l2(XXXw, XXXw): // MOVE.l (xxx).w (xxx).w + op(int(Action::AssembleWordAddressFromPrefetch) | MicroOp::SourceMask, seq("np nR+ nr", { ea(0), ea(0) })); + op(Action::PerformOperation); + op(int(Action::AssembleWordAddressFromPrefetch) | MicroOp::DestinationMask, seq("np nW+ nw np", { ea(1), ea(1) })); + break; + + case l2(XXXl, XXXw): // MOVE.l (xxx).l, (xxx).w + op(int(Action::None), seq("np")); + op(int(Action::AssembleLongWordAddressFromPrefetch) | MicroOp::SourceMask, seq("np nR+ nr", { ea(0), ea(0) })); + op(Action::PerformOperation); + op(int(Action::AssembleWordAddressFromPrefetch) | MicroOp::DestinationMask, seq("np nW+ nw np", { ea(1), ea(1) })); + break; + + case l2(XXXl, XXXl): // MOVE.l (xxx).l, (xxx).l + op(int(Action::None), seq("np")); + case l2(XXXw, XXXl): // MOVE.l (xxx).w (xxx).l + op(address_action_for_mode(combined_source_mode) | MicroOp::SourceMask, seq("np nR+ nr", { ea(0), ea(0) })); + op(Action::PerformOperation, seq("np")); + op(int(Action::AssembleLongWordAddressFromPrefetch) | MicroOp::DestinationMask, seq("nW+ nw np np", { ea(1), ea(1) })); + break; + + // + // Default + // + + default: continue; + } + + // If any post-incrementing was involved, do the post increment(s). + if(ea_mode == PostInc || destination_mode == PostInc) { + const int ea_inc = inc(ea_register); + const int destination_inc = inc(data_register); + + // If there are two increments, and both can be done simultaneously, do that. + // Otherwise do one or two individually. + if(ea_mode == destination_mode && ea_inc == destination_inc) { + op(ea_inc | MicroOp::SourceMask | MicroOp::DestinationMask); + } else { + if(ea_mode == PostInc) { + op(ea_inc | MicroOp::SourceMask); + } + if(destination_mode == PostInc) { + op(destination_inc | MicroOp::DestinationMask); + } + } + } + } break; + + case Decoder::RESET: + storage_.instructions[instruction].requires_supervisor = true; + op(Action::None, seq("nn _ np")); + break; + + case Decoder::TRAP: { + // TRAP involves some oddly-sequenced stack writes, so is calculated + // at runtime; also the same sequence is used for illegal instructions. + // So the entirety is scheduled at runtime. + op(Action::PerformOperation); + op(); + } break; + + case Decoder::TRAPV: { + op(Action::None, seq("np")); + op(Action::PerformOperation); + op(); + } break; + + case Decoder::CHK: { + storage_.instructions[instruction].set_destination(storage_, Dn, data_register); + storage_.instructions[instruction].set_source(storage_, ea_mode, ea_register); + + const int mode = combined_mode(ea_mode, ea_register); + switch(mode) { + default: continue; + + case Dn: // CHK Dn, Dn + op(Action::None, seq("np")); + break; + + case Ind: // CHK (An), Dn + case PostInc: // CHK (An)+, Dn + op(Action::None, seq("nr np", { a(ea_register) })); + if(mode == PostInc) { + op(int(Action::Increment2) | MicroOp::SourceMask); + } + break; + + case PreDec: // CHK (An)-, Dn + op(int(Action::Decrement2) | MicroOp::SourceMask, seq("n nr np", { a(ea_register) })); + break; + + case XXXl: // CHK (xxx).l, Dn + op(Action::None, seq("np")); + case XXXw: // CHK (xxx).w, Dn + case d16An: // CHK (d16, An), Dn + case d16PC: // CHK (d16, PC), Dn + case d8AnXn: // CHK (d8, An, Xn), Dn + case d8PCXn: // CHK (d8, PC, Xn), Dn + op(address_action_for_mode(mode) | MicroOp::SourceMask, seq(pseq("np nr", mode), { ea(0) })); + break; + + case Imm: // CHK #, Dn + op(int(Action::AssembleWordDataFromPrefetch) | MicroOp::SourceMask, seq("np np")); + break; + } + + // The nn n here is correct if no exception is issued; otherwise this sequence will + // be replaced. + op(Action::PerformOperation, seq("nn n")); + } break; + + case Decoder::TST: { + storage_.instructions[instruction].set_source(storage_, ea_mode, ea_register); + + const int mode = combined_mode(ea_mode, ea_register); + switch(is_long_word_access ? l(mode) : bw(mode)) { + default: continue; + + case bw(Dn): // TST.bw Dn + case l(Dn): // TST.l Dn + op(Action::PerformOperation, seq("np")); + break; + + case bw(PreDec): // TST.bw -(An) + op(dec(ea_register) | MicroOp::SourceMask, seq("n")); + case bw(Ind): // TST.bw (An) + case bw(PostInc): // TST.bw (An)+ + op(Action::None, seq("nr", { a(ea_register) }, !is_byte_access)); + op(Action::PerformOperation, seq("np")); + if(mode == PostInc) { + op(inc(ea_register) | MicroOp::SourceMask); + } + break; + + case l(PreDec): // TST.l -(An) + op(int(Action::Decrement4) | MicroOp::SourceMask, seq("n")); + case l(Ind): // TST.l (An) + case l(PostInc): // TST.l (An)+ + op(int(Action::CopyToEffectiveAddress) | MicroOp::SourceMask, seq("nR+ nr", { ea(0), ea(0) })); + op(Action::PerformOperation, seq("np")); + if(mode == PostInc) { + op(int(Action::Increment4) | MicroOp::SourceMask); + } + break; + + case bw(XXXl): // TST.bw (xxx).l + op(Action::None, seq("np")); + case bw(XXXw): // TST.bw (xxx).w + case bw(d16An): // TST.bw (d16, An) + case bw(d8AnXn): // TST.bw (d8, An, Xn) + op(address_action_for_mode(mode) | MicroOp::SourceMask, seq(pseq("np nr", mode), { ea(0) }, !is_byte_access)); + op(Action::PerformOperation, seq("np")); + break; + + case l(XXXl): // TST.l (xxx).l + op(Action::None, seq("np")); + case l(XXXw): // TST.l (xxx).w + case l(d16An): // TST.l (d16, An) + case l(d8AnXn): // TST.l (d8, An, Xn) + op(address_action_for_mode(mode) | MicroOp::SourceMask, seq(pseq("np nR+ nr", mode), { ea(0), ea(0) })); + op(Action::PerformOperation, seq("np")); + break; + } + } break; + + default: + std::cerr << "Unhandled decoder " << int(mapping.decoder) << std::endl; + continue; + } + + // Add a terminating micro operation if necessary. + if(!storage_.all_micro_ops_.back().is_terminal()) { + storage_.all_micro_ops_.emplace_back(); + } + + // Ensure that steps that weren't meant to look terminal aren't terminal. + for(auto index = micro_op_start; index < storage_.all_micro_ops_.size() - 1; ++index) { + if(storage_.all_micro_ops_[index].is_terminal()) { + storage_.all_micro_ops_[index].bus_program = seq(""); + } + } + + // Install the operation and make a note of where micro-ops begin. + storage_.instructions[instruction].operation = operation; + micro_op_pointers[size_t(instruction)] = size_t(micro_op_start); + + // Don't search further through the list of possibilities, unless this is a debugging build, + // in which case verify there are no double mappings. +#ifndef NDEBUG + ++hits; + assert(hits == 1); +#else + break; +#endif + } + } + +#undef inc +#undef dec + } + + // Throw in the interrupt program. + const auto interrupt_pointer = storage_.all_micro_ops_.size(); + + // WORKAROUND FOR THE BE68000 MAIN LOOP. Hopefully temporary. + op(Action::None, seq("")); + + // Perform a single write and then a cycle that will obtain an interrupt vector, or else dictate an autovector or a spurious interrupt. + op(Action::PrepareINT, seq("n nn nw int", { &storage_.precomputed_addresses_[0] })); + + // The reset of the standard trap steps occur here; PrepareINT will set them up according to the vector received. + op(Action::PrepareINTVector, seq("nn n nw nW nV nv np np", { &storage_.precomputed_addresses_[1], &storage_.precomputed_addresses_[2] })); + + // Terminate the sequence. + op(); + +#undef Dn +#undef An +#undef Ind +#undef PostDec +#undef PreDec +#undef d16An +#undef d8AnXn +#undef XXXw +#undef XXXl +#undef d16PC +#undef d8PCXn +#undef Imm + +#undef bw +#undef l +#undef source_dest + +#undef ea +#undef a +#undef seq +#undef op +#undef pseq + + /*! + Iterates through the micro-sequence beginning at @c start, finalising bus_program + pointers that have been transiently stored as relative to @c arbitrary_base. + */ + const auto link_operations = [this](MicroOp *start, BusStep *arbitrary_base) { + while(!start->is_terminal()) { + const auto offset = size_t(start->bus_program - arbitrary_base); + assert(offset >= 0 && offset < storage_.all_bus_steps_.size()); + start->bus_program = &storage_.all_bus_steps_[offset]; + ++start; + } + }; + + // Finalise micro-op and program pointers. + for(size_t instruction = 0; instruction < 65536; ++instruction) { + if(micro_op_pointers[instruction] != std::numeric_limits::max()) { + storage_.instructions[instruction].micro_operations = &storage_.all_micro_ops_[micro_op_pointers[instruction]]; + link_operations(storage_.instructions[instruction].micro_operations, &arbitrary_base); + } + } + + // Link up the interrupt micro ops. + storage_.interrupt_micro_ops_ = &storage_.all_micro_ops_[interrupt_pointer]; + link_operations(storage_.interrupt_micro_ops_, &arbitrary_base); + + printf("%lu total steps\n", storage_.all_bus_steps_.size()); + } + + private: + ProcessorStorage &storage_; + + std::vector::const_iterator replace_write_values(BusStep *start, std::vector::const_iterator value) { + while(!start->is_terminal()) { + // Look for any bus step that writes. Then replace its value, and that of the cycle before it. + if(start->microcycle.data_select_active() && !(start->microcycle.operation & Microcycle::Read) && !(start->microcycle.operation & Microcycle::InterruptAcknowledge)) { + start[0].microcycle.value = start[-1].microcycle.value = *value; + ++value; + } + + ++start; + } + + return value; + } + +/* struct BusStepOrderer { + bool operator()( BusStep const& lhs, BusStep const& rhs ) const { + int action_diff = int(lhs.action) - int(rhs.action); + if(action_diff < 0) { + return true; + } + if(action_diff > 0) { + return false; + } + + return + std::make_tuple(lhs.microcycle.value, lhs.microcycle.address, lhs.microcycle.length, lhs.microcycle.operation) < + std::make_tuple(rhs.microcycle.value, rhs.microcycle.address, rhs.microcycle.length, rhs.microcycle.operation); + } + }; + std::map, BusStepOrderer> locations_by_bus_step_;*/ +}; + +} +} + +CPU::MC68000::ProcessorStorage::ProcessorStorage() { + ProcessorStorageConstructor constructor(*this); + + // Create the special programs. + const size_t reset_offset = constructor.assemble_program("n n n n n nn nF nf nV nv np np"); + + const size_t branch_taken_offset = constructor.assemble_program("n np np"); + const size_t branch_byte_not_taken_offset = constructor.assemble_program("nn np"); + const size_t branch_word_not_taken_offset = constructor.assemble_program("nn np np"); + const size_t bsr_offset = constructor.assemble_program("np np"); + + const size_t dbcc_condition_true_offset = constructor.assemble_program("nn np np"); + const size_t dbcc_condition_false_no_branch_offset = constructor.assemble_program("n nr np np", { &dbcc_false_address_ }); + const size_t dbcc_condition_false_branch_offset = constructor.assemble_program("n np np"); + // That nr in dbcc_condition_false_no_branch_offset is to look like an np from the wrong address. + + // The reads steps needs to be 32 long-word reads plus an overflow word; the writes just the long words. + // Addresses and data sources/targets will be filled in at runtime, so anything will do here. + std::string movem_reads_pattern, movem_writes_pattern; + std::vector addresses; + for(auto c = 0; c < 64; ++c) { + movem_reads_pattern += "nr "; + movem_writes_pattern += "nw "; + addresses.push_back(nullptr); + } + movem_reads_pattern += "nr"; + addresses.push_back(nullptr); + const size_t movem_read_offset = constructor.assemble_program(movem_reads_pattern, addresses); + const size_t movem_write_offset = constructor.assemble_program(movem_writes_pattern, addresses); + + // Target addresses and values will be filled in by TRAP/illegal too. + const size_t trap_offset = constructor.assemble_program("r nw nw nW nV nv np np", { &precomputed_addresses_[0], &precomputed_addresses_[1], &precomputed_addresses_[2] }); + const size_t bus_error_offset = + constructor.assemble_program( + "nn nw nw nW nw nw nw nW nV nv np np", + { + &precomputed_addresses_[0], + &precomputed_addresses_[1], + &precomputed_addresses_[2], + &precomputed_addresses_[3], + &precomputed_addresses_[4], + &precomputed_addresses_[5], + &precomputed_addresses_[6] + } + ); + + // Chuck in the proper micro-ops for handling an exception. + const auto short_exception_offset = all_micro_ops_.size(); + all_micro_ops_.emplace_back(ProcessorBase::MicroOp::Action::None); + all_micro_ops_.emplace_back(); + + const auto long_exception_offset = all_micro_ops_.size(); + all_micro_ops_.emplace_back(ProcessorBase::MicroOp::Action::None); + all_micro_ops_.emplace_back(); + + // Install operations. +#ifndef NDEBUG + const std::clock_t start = std::clock(); +#endif + constructor.install_instructions(); +#ifndef NDEBUG + std::cout << "Construction took " << double(std::clock() - start) / double(CLOCKS_PER_SEC / 1000) << "ms" << std::endl; +#endif + + // Realise the special programs as direct pointers. + reset_bus_steps_ = &all_bus_steps_[reset_offset]; + + branch_taken_bus_steps_ = &all_bus_steps_[branch_taken_offset]; + branch_byte_not_taken_bus_steps_ = &all_bus_steps_[branch_byte_not_taken_offset]; + branch_word_not_taken_bus_steps_ = &all_bus_steps_[branch_word_not_taken_offset]; + bsr_bus_steps_ = &all_bus_steps_[bsr_offset]; + + dbcc_condition_true_steps_ = &all_bus_steps_[dbcc_condition_true_offset]; + dbcc_condition_false_no_branch_steps_ = &all_bus_steps_[dbcc_condition_false_no_branch_offset]; + dbcc_condition_false_no_branch_steps_[1].microcycle.operation |= Microcycle::IsProgram; + dbcc_condition_false_no_branch_steps_[2].microcycle.operation |= Microcycle::IsProgram; + dbcc_condition_false_branch_steps_ = &all_bus_steps_[dbcc_condition_false_branch_offset]; + + movem_read_steps_ = &all_bus_steps_[movem_read_offset]; + movem_write_steps_ = &all_bus_steps_[movem_write_offset]; + + // Link the trap steps but also fill in the program counter as the source + // for its parts, and use the computed addresses. + // + // Order of output is: PC.l, SR, PC.h. + trap_steps_ = &all_bus_steps_[trap_offset]; + constructor.replace_write_values(trap_steps_, { &program_counter_.halves.low, &destination_bus_data_[0].halves.low, &program_counter_.halves.high }); + + // Fill in the same order of writes for the interrupt micro-ops, though it divides the work differently. + constructor.replace_write_values(interrupt_micro_ops_, { &program_counter_.halves.low, &destination_bus_data_[0].halves.low, &program_counter_.halves.high }); + + // Link the bus error exception steps and fill in the proper sources. + bus_error_steps_ = &all_bus_steps_[bus_error_offset]; + constructor.replace_write_values(bus_error_steps_, { + &program_counter_.halves.low, + &destination_bus_data_[0].halves.low, + &program_counter_.halves.high, + &decoded_instruction_, + &effective_address_[0].halves.low, + &destination_bus_data_[0].halves.high, + &effective_address_[0].halves.high + }); + + // Also relink the RTE and RTR bus steps to collect the program counter. + // + // Assumed order of input: PC.h, SR, PC.l (i.e. the opposite of TRAP's output). + for(const int instruction: { 0x4e73, 0x4e77 }) { + auto steps = instructions[instruction].micro_operations[0].bus_program; + steps[0].microcycle.value = steps[1].microcycle.value = &program_counter_.halves.high; + steps[4].microcycle.value = steps[5].microcycle.value = &program_counter_.halves.low; + } + + // Setup the stop cycle. + stop_cycle_.length = HalfCycles(2); + + // Complete linkage of the exception micro program. + short_exception_micro_ops_ = &all_micro_ops_[short_exception_offset]; + short_exception_micro_ops_->bus_program = trap_steps_; + + long_exception_micro_ops_ = &all_micro_ops_[long_exception_offset]; + long_exception_micro_ops_->bus_program = bus_error_steps_; + + // Set initial state. + active_step_ = reset_bus_steps_; + effective_address_[0] = 0; + is_supervisor_ = 1; + interrupt_level_ = 7; + address_[7] = 0x00030000; +} + +void CPU::MC68000::ProcessorStorage::write_back_stack_pointer() { + stack_pointers_[is_supervisor_] = address_[7]; +} + +void CPU::MC68000::ProcessorStorage::set_is_supervisor(bool is_supervisor) { + const int new_is_supervisor = is_supervisor ? 1 : 0; + if(new_is_supervisor != is_supervisor_) { + stack_pointers_[is_supervisor_] = address_[7]; + is_supervisor_ = new_is_supervisor; + address_[7] = stack_pointers_[is_supervisor_]; + } +} diff --git a/Processors/68000/Implementation/68000Storage.hpp b/Processors/68000/Implementation/68000Storage.hpp new file mode 100644 index 000000000..ddf55e3a4 --- /dev/null +++ b/Processors/68000/Implementation/68000Storage.hpp @@ -0,0 +1,498 @@ +// +// 68000Storage.hpp +// Clock Signal +// +// Created by Thomas Harte on 08/03/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#ifndef MC68000Storage_h +#define MC68000Storage_h + +class ProcessorStorage { + public: + ProcessorStorage(); + + protected: + RegisterPair32 data_[8]; + RegisterPair32 address_[8]; + RegisterPair32 program_counter_; + + RegisterPair32 stack_pointers_[2]; // [0] = user stack pointer; [1] = supervisor; the values from here + // are copied into/out of address_[7] upon mode switches. + + RegisterPair32 prefetch_queue_; // Each word will go into the low part of the word, then proceed upward. + + enum class ExecutionState { + /// The normal mode, this means the 68000 is expending processing effort. + Executing, + + /// The 68000 is in a holding loop, waiting for either DTack or to be notified of a bus error. + WaitingForDTack, + + /// Occurs after executing a STOP instruction; the processor will idle waiting for an interrupt or reset. + Stopped, + + /// Occurs at the end of the current bus cycle after detection of the HALT input, continuing until + /// HALT is no longer signalled. + Halted, + + /// Signals a transition from some other straight directly to cueing up an interrupt. + BeginInterrupt, + } execution_state_ = ExecutionState::Executing; + Microcycle dtack_cycle_; + Microcycle stop_cycle_; + + // Various status bits. + int is_supervisor_; + int interrupt_level_; + uint_fast32_t zero_result_; // The zero flag is set if this value is zero. + uint_fast32_t carry_flag_; // The carry flag is set if this value is non-zero. + uint_fast32_t extend_flag_; // The extend flag is set if this value is non-zero. + uint_fast32_t overflow_flag_; // The overflow flag is set if this value is non-zero. + uint_fast32_t negative_flag_; // The negative flag is set if this value is non-zero. + uint_fast32_t trace_flag_; // The trace flag is set if this value is non-zero. + + // Bus inputs. + int bus_interrupt_level_ = 0; + bool dtack_ = false; + bool is_peripheral_address_ = false; + bool bus_error_ = false; + bool bus_request_ = false; + bool bus_acknowledge_ = false; + bool halt_ = false; + + int accepted_interrupt_level_ = 0; + bool is_starting_interrupt_ = false; + + // Generic sources and targets for memory operations; + // by convention: [0] = source, [1] = destination. + RegisterPair32 effective_address_[2]; + RegisterPair32 source_bus_data_[1]; + RegisterPair32 destination_bus_data_[1]; + + HalfCycles half_cycles_left_to_run_; + HalfCycles e_clock_phase_; + + enum class Operation { + None, + ABCD, SBCD, NBCD, + + ADDb, ADDw, ADDl, + ADDQb, ADDQw, ADDQl, + ADDAw, ADDAl, + ADDQAw, ADDQAl, + ADDXb, ADDXw, ADDXl, + + SUBb, SUBw, SUBl, + SUBQb, SUBQw, SUBQl, + SUBAw, SUBAl, + SUBQAw, SUBQAl, + SUBXb, SUBXw, SUBXl, + + MOVEb, MOVEw, MOVEl, MOVEq, + MOVEAw, MOVEAl, + + MOVEtoSR, MOVEfromSR, + MOVEtoCCR, + + ORItoSR, ORItoCCR, + ANDItoSR, ANDItoCCR, + EORItoSR, EORItoCCR, + + BTSTb, BTSTl, + BCLRl, BCLRb, + CMPb, CMPw, CMPl, + TSTb, TSTw, TSTl, + + JMP, + BRA, Bcc, + DBcc, + Scc, + + CLRb, CLRw, CLRl, + NEGXb, NEGXw, NEGXl, + NEGb, NEGw, NEGl, + + ASLb, ASLw, ASLl, ASLm, + ASRb, ASRw, ASRl, ASRm, + LSLb, LSLw, LSLl, LSLm, + LSRb, LSRw, LSRl, LSRm, + ROLb, ROLw, ROLl, ROLm, + RORb, RORw, RORl, RORm, + ROXLb, ROXLw, ROXLl, ROXLm, + ROXRb, ROXRw, ROXRl, ROXRm, + + MOVEMtoRl, MOVEMtoRw, + MOVEMtoMl, MOVEMtoMw, + + MOVEPtoRl, MOVEPtoRw, + MOVEPtoMl, MOVEPtoMw, + + ANDb, ANDw, ANDl, + EORb, EORw, EORl, + NOTb, NOTw, NOTl, + ORb, ORw, ORl, + + MULU, MULS, + DIVU, DIVS, + + RTE_RTR, + + TRAP, TRAPV, + CHK, + + EXG, SWAP, + + BCHGl, BCHGb, + BSETl, BSETb, + + TAS, + + EXTbtow, EXTwtol, + + LINK, UNLINK, + + STOP, + }; + + /*! + Bus steps are sequences of things to communicate to the bus. + Standard behaviour is: (i) perform microcycle; (ii) perform action. + */ + struct BusStep { + Microcycle microcycle; + enum class Action { + None, + + /// Performs effective_address_[0] += 2. + IncrementEffectiveAddress0, + + /// Performs effective_address_[1] += 2. + IncrementEffectiveAddress1, + + /// Performs effective_address_[0] -= 2. + DecrementEffectiveAddress0, + + /// Performs effective_address_[1] -= 2. + DecrementEffectiveAddress1, + + /// Performs program_counter_ += 2. + IncrementProgramCounter, + + /// Copies prefetch_queue_[1] to prefetch_queue_[0]. + AdvancePrefetch, + + /*! + Terminates an atomic program; if nothing else is pending, schedules the next instruction. + This action is special in that it usurps any included microcycle. So any Step with this + as its action acts as an end-of-list sentinel. + */ + ScheduleNextProgram + + } action = Action::None; + + inline bool operator ==(const BusStep &rhs) const { + if(action != rhs.action) return false; + return microcycle == rhs.microcycle; + } + + inline bool is_terminal() const { + return action == Action::ScheduleNextProgram; + } + }; + + /*! + A micro-op is: (i) an action to take; and (ii) a sequence of bus operations + to perform after taking the action. + + NOTE: this therefore has the opposite order of behaviour compared to a BusStep, + the action occurs BEFORE the bus operations, not after. + + A nullptr bus_program terminates a sequence of micro operations; the is_terminal + test should be used to query for that. The action on the final operation will + be performed. + */ + struct MicroOp { + enum class Action: int { + None, + + /// Does whatever this instruction says is the main operation. + PerformOperation, + + /* + All of the below will honour the source and destination masks + in deciding where to apply their actions. + */ + + /// Subtracts 1 from the [source/destination]_address. + Decrement1, + /// Subtracts 2 from the [source/destination]_address. + Decrement2, + /// Subtracts 4 from the [source/destination]_address. + Decrement4, + + /// Adds 1 from the [source/destination]_address. + Increment1, + /// Adds 2 from the [source/destination]_address. + Increment2, + /// Adds 4 from the [source/destination]_address. + Increment4, + + /// Copies the source and/or destination to effective_address_. + CopyToEffectiveAddress, + + /// Peeking into the end of the prefetch queue, calculates the proper target of (d16,An) addressing. + CalcD16An, + + /// Peeking into the end of the prefetch queue, calculates the proper target of (d8,An,Xn) addressing. + CalcD8AnXn, + + /// Peeking into the prefetch queue, calculates the proper target of (d16,PC) addressing, + /// adjusting as though it had been performed after the proper PC fetches. The source + /// and destination mask flags affect only the destination of the result. + CalcD16PC, + + /// Peeking into the prefetch queue, calculates the proper target of (d8,An,Xn) addressing, + /// adjusting as though it had been performed after the proper PC fetches. The source + /// and destination mask flags affect only the destination of the result. + CalcD8PCXn, + + /// Sets the high word according to the MSB of the low word. + SignExtendWord, + + /// Sets the high three bytes according to the MSB of the low byte. + SignExtendByte, + + /// From the next word in the prefetch queue assembles a sign-extended long word in either or + /// both of effective_address_[0] and effective_address_[1]. + AssembleWordAddressFromPrefetch, + + /// From the next word in the prefetch queue assembles a 0-padded 32-bit long word in either or + /// both of bus_data_[0] and bus_data_[1]. + AssembleWordDataFromPrefetch, + + /// Copies the next two prefetch words into one of the effective_address_. + AssembleLongWordAddressFromPrefetch, + + /// Copies the next two prefetch words into one of the bus_data_. + AssembleLongWordDataFromPrefetch, + + /// Copies the low part of the prefetch queue into next_word_. + CopyNextWord, + + /// Performs write-back of post-increment address and/or sign extensions as necessary. + MOVEMtoRComplete, + + /// Performs write-back of pre-decrement address. + MOVEMtoMComplete, + + // (i) inspects the prefetch queue to determine the length of this instruction and copies the next PC to destination_bus_data_; + // (ii) copies the stack pointer minus 4 to effective_address_[1]; + // (iii) decrements the stack pointer by four. + PrepareJSR, + PrepareBSR, + + // (i) copies the stack pointer to effective_address_[0]; + // (ii) increments the stack pointer by four. + PrepareRTS, + + // (i) fills in the proper stack addresses to the bus steps for this micro-op; and + // (ii) adjusts the stack pointer appropriately. + PrepareRTE_RTR, + + // Performs the necessary status word substitution for the current interrupt level, + // and does the first part of initialising the trap steps. + PrepareINT, + + // Observes the bus_error_, valid_peripheral_address_ and/or the value currently in + // source_bus_data_ to determine an interrupt vector, and fills in the final trap + // steps detail appropriately. + PrepareINTVector, + }; + static const int SourceMask = 1 << 30; + static const int DestinationMask = 1 << 29; + int action = int(Action::None); + + BusStep *bus_program = nullptr; + + MicroOp() {} + MicroOp(int action) : action(action) {} + MicroOp(int action, BusStep *bus_program) : action(action), bus_program(bus_program) {} + + MicroOp(Action action) : MicroOp(int(action)) {} + MicroOp(Action action, BusStep *bus_program) : MicroOp(int(action), bus_program) {} + + inline bool is_terminal() const { + return bus_program == nullptr; + } + }; + + /*! + A program represents the implementation of a particular opcode, as a sequence + of micro-ops and, separately, the operation to perform plus whatever other + fields the operation requires. + */ + struct Program { + MicroOp *micro_operations = nullptr; + RegisterPair32 *source = nullptr; + RegisterPair32 *destination = nullptr; + RegisterPair32 *source_address = nullptr; + RegisterPair32 *destination_address = nullptr; + Operation operation; + bool requires_supervisor = false; + + void set_source(ProcessorStorage &storage, int mode, int reg) { + source_address = &storage.address_[reg]; + switch(mode) { + case 0: source = &storage.data_[reg]; break; + case 1: source = &storage.address_[reg]; break; + default: source = &storage.source_bus_data_[0]; break; + } + } + + void set_destination(ProcessorStorage &storage, int mode, int reg) { + destination_address = &storage.address_[reg]; + switch(mode) { + case 0: destination = &storage.data_[reg]; break; + case 1: destination = &storage.address_[reg]; break; + default: destination = &storage.destination_bus_data_[0]; break; + } + } + }; + + // Storage for all the sequences of bus steps and micro-ops used throughout + // the 68000. + std::vector all_bus_steps_; + std::vector all_micro_ops_; + + // A lookup table from instructions to implementations. + Program instructions[65536]; + + // Special steps and programs for exception handlers. + BusStep *reset_bus_steps_; + MicroOp *long_exception_micro_ops_; // i.e. those that leave 14 bytes on the stack — bus error and address error. + MicroOp *short_exception_micro_ops_; // i.e. those that leave 6 bytes on the stack — everything else (other than interrupts). + MicroOp *interrupt_micro_ops_; + + // Special micro-op sequences and storage for conditionals. + BusStep *branch_taken_bus_steps_; + BusStep *branch_byte_not_taken_bus_steps_; + BusStep *branch_word_not_taken_bus_steps_; + BusStep *bsr_bus_steps_; + + uint32_t dbcc_false_address_; + BusStep *dbcc_condition_true_steps_; + BusStep *dbcc_condition_false_no_branch_steps_; + BusStep *dbcc_condition_false_branch_steps_; + + BusStep *movem_read_steps_; + BusStep *movem_write_steps_; + + BusStep *trap_steps_; + BusStep *bus_error_steps_; + + // Current bus step pointer, and outer program pointer. + Program *active_program_ = nullptr; + MicroOp *active_micro_op_ = nullptr; + BusStep *active_step_ = nullptr; + RegisterPair16 decoded_instruction_ = 0; + uint16_t next_word_ = 0; + + /// Copies address_[7] to the proper stack pointer based on current mode. + void write_back_stack_pointer(); + + /// Sets or clears the supervisor flag, ensuring the stack pointer is properly updated. + void set_is_supervisor(bool); + + // Transient storage for MOVEM, TRAP and others. + uint32_t precomputed_addresses_[65]; + RegisterPair16 throwaway_value_; + uint32_t movem_final_address_; + + /*! + Evaluates the conditional described by @c code and returns @c true or @c false to + indicate the result of that evaluation. + */ + inline bool evaluate_condition(uint8_t code) { + switch(code & 0xf) { + default: + case 0x00: return true; // true + case 0x01: return false; // false + case 0x02: return zero_result_ && !carry_flag_; // high + case 0x03: return !zero_result_ || carry_flag_; // low or same + case 0x04: return !carry_flag_; // carry clear + case 0x05: return carry_flag_; // carry set + case 0x06: return zero_result_; // not equal + case 0x07: return !zero_result_; // equal + case 0x08: return !overflow_flag_; // overflow clear + case 0x09: return overflow_flag_; // overflow set + case 0x0a: return !negative_flag_; // positive + case 0x0b: return negative_flag_; // negative + case 0x0c: // greater than or equal + return (negative_flag_ && overflow_flag_) || (!negative_flag_ && !overflow_flag_); + case 0x0d: // less than + return (negative_flag_ && !overflow_flag_) || (!negative_flag_ && overflow_flag_); + case 0x0e: // greater than + return zero_result_ && ((negative_flag_ && overflow_flag_) || (!negative_flag_ && !overflow_flag_)); + case 0x0f: // less than or equal + return !zero_result_ || (negative_flag_ && !overflow_flag_) || (!negative_flag_ && overflow_flag_); + } + } + + /*! + Fills in the appropriate addresses and values to complete the TRAP steps — those + representing a short-form exception — and mutates the status register as if one + were beginning. + */ + inline void populate_trap_steps(uint32_t vector, uint16_t status) { + // Fill in the status word value. + destination_bus_data_[0].full = status; + + // Switch to supervisor mode, disable the trace bit. + set_is_supervisor(true); + trace_flag_ = 0; + + // Pick a vector. + effective_address_[0].full = vector << 2; + + // Schedule the proper stack activity. + precomputed_addresses_[0] = address_[7].full - 2; // PC.l + precomputed_addresses_[1] = address_[7].full - 6; // status word (in destination_bus_data_[0]) + precomputed_addresses_[2] = address_[7].full - 4; // PC.h + address_[7].full -= 6; + + // Set the default timing. + trap_steps_->microcycle.length = HalfCycles(8); + } + + inline void populate_bus_error_steps(uint32_t vector, uint16_t status, uint16_t bus_status, RegisterPair32 faulting_address) { + // Fill in the status word value. + destination_bus_data_[0].halves.low.full = status; + destination_bus_data_[0].halves.high.full = bus_status; + effective_address_[1] = faulting_address; + + // Switch to supervisor mode, disable the trace bit. + set_is_supervisor(true); + trace_flag_ = 0; + + // Pick a vector. + effective_address_[0].full = vector << 2; + + // Schedule the proper stack activity. + precomputed_addresses_[0] = address_[7].full - 2; // PC.l + precomputed_addresses_[1] = address_[7].full - 6; // status word + precomputed_addresses_[2] = address_[7].full - 4; // PC.h + precomputed_addresses_[3] = address_[7].full - 8; // current instruction + precomputed_addresses_[4] = address_[7].full - 10; // fault address.l + precomputed_addresses_[5] = address_[7].full - 14; // bus cycle status word + precomputed_addresses_[6] = address_[7].full - 12; // fault address.h + address_[7].full -= 14; + } + + private: + friend class ProcessorStorageConstructor; + friend class ProcessorStorageTests; +}; + +#endif /* MC68000Storage_h */ diff --git a/Processors/RegisterSizes.hpp b/Processors/RegisterSizes.hpp index 766e74dbb..2cb9af172 100644 --- a/Processors/RegisterSizes.hpp +++ b/Processors/RegisterSizes.hpp @@ -13,16 +13,27 @@ namespace CPU { -union RegisterPair { - RegisterPair(uint16_t v) : full(v) {} +template union RegisterPair { + RegisterPair(Full v) : full(v) {} RegisterPair() {} - uint16_t full; + Full full; +#pragma pack(push, 1) +#if TARGET_RT_BIG_ENDIAN struct { - uint8_t low, high; - } bytes; + Half high, low; + } halves; +#else + struct { + Half low, high; + } halves; +#endif +#pragma pack(pop) }; +typedef RegisterPair RegisterPair16; +typedef RegisterPair RegisterPair32; + } #endif /* RegisterSizes_hpp */ diff --git a/Processors/Z80/Implementation/Z80Base.cpp b/Processors/Z80/Implementation/Z80Base.cpp index 04cdc5f09..0dddf500d 100644 --- a/Processors/Z80/Implementation/Z80Base.cpp +++ b/Processors/Z80/Implementation/Z80Base.cpp @@ -23,38 +23,38 @@ uint16_t ProcessorBase::get_value_of_register(Register r) { case Register::A: return a_; case Register::Flags: return get_flags(); case Register::AF: return static_cast((a_ << 8) | get_flags()); - case Register::B: return bc_.bytes.high; - case Register::C: return bc_.bytes.low; + case Register::B: return bc_.halves.high; + case Register::C: return bc_.halves.low; case Register::BC: return bc_.full; - case Register::D: return de_.bytes.high; - case Register::E: return de_.bytes.low; + case Register::D: return de_.halves.high; + case Register::E: return de_.halves.low; case Register::DE: return de_.full; - case Register::H: return hl_.bytes.high; - case Register::L: return hl_.bytes.low; + case Register::H: return hl_.halves.high; + case Register::L: return hl_.halves.low; case Register::HL: return hl_.full; - case Register::ADash: return afDash_.bytes.high; - case Register::FlagsDash: return afDash_.bytes.low; + case Register::ADash: return afDash_.halves.high; + case Register::FlagsDash: return afDash_.halves.low; case Register::AFDash: return afDash_.full; - case Register::BDash: return bcDash_.bytes.high; - case Register::CDash: return bcDash_.bytes.low; + case Register::BDash: return bcDash_.halves.high; + case Register::CDash: return bcDash_.halves.low; case Register::BCDash: return bcDash_.full; - case Register::DDash: return deDash_.bytes.high; - case Register::EDash: return deDash_.bytes.low; + case Register::DDash: return deDash_.halves.high; + case Register::EDash: return deDash_.halves.low; case Register::DEDash: return deDash_.full; - case Register::HDash: return hlDash_.bytes.high; - case Register::LDash: return hlDash_.bytes.low; + case Register::HDash: return hlDash_.halves.high; + case Register::LDash: return hlDash_.halves.low; case Register::HLDash: return hlDash_.full; - case Register::IXh: return ix_.bytes.high; - case Register::IXl: return ix_.bytes.low; + case Register::IXh: return ix_.halves.high; + case Register::IXl: return ix_.halves.low; case Register::IX: return ix_.full; - case Register::IYh: return iy_.bytes.high; - case Register::IYl: return iy_.bytes.low; + case Register::IYh: return iy_.halves.high; + case Register::IYl: return iy_.halves.low; case Register::IY: return iy_.full; - case Register::R: return ir_.bytes.low; - case Register::I: return ir_.bytes.high; + case Register::R: return ir_.halves.low; + case Register::I: return ir_.halves.high; case Register::Refresh: return ir_.full; case Register::IFF1: return iff1_ ? 1 : 0; @@ -76,38 +76,38 @@ void ProcessorBase::set_value_of_register(Register r, uint16_t value) { case Register::AF: a_ = static_cast(value >> 8); // deliberate fallthrough... case Register::Flags: set_flags(static_cast(value)); break; - case Register::B: bc_.bytes.high = static_cast(value); break; - case Register::C: bc_.bytes.low = static_cast(value); break; + case Register::B: bc_.halves.high = static_cast(value); break; + case Register::C: bc_.halves.low = static_cast(value); break; case Register::BC: bc_.full = value; break; - case Register::D: de_.bytes.high = static_cast(value); break; - case Register::E: de_.bytes.low = static_cast(value); break; + case Register::D: de_.halves.high = static_cast(value); break; + case Register::E: de_.halves.low = static_cast(value); break; case Register::DE: de_.full = value; break; - case Register::H: hl_.bytes.high = static_cast(value); break; - case Register::L: hl_.bytes.low = static_cast(value); break; + case Register::H: hl_.halves.high = static_cast(value); break; + case Register::L: hl_.halves.low = static_cast(value); break; case Register::HL: hl_.full = value; break; - case Register::ADash: afDash_.bytes.high = static_cast(value); break; - case Register::FlagsDash: afDash_.bytes.low = static_cast(value); break; + case Register::ADash: afDash_.halves.high = static_cast(value); break; + case Register::FlagsDash: afDash_.halves.low = static_cast(value); break; case Register::AFDash: afDash_.full = value; break; - case Register::BDash: bcDash_.bytes.high = static_cast(value); break; - case Register::CDash: bcDash_.bytes.low = static_cast(value); break; + case Register::BDash: bcDash_.halves.high = static_cast(value); break; + case Register::CDash: bcDash_.halves.low = static_cast(value); break; case Register::BCDash: bcDash_.full = value; break; - case Register::DDash: deDash_.bytes.high = static_cast(value); break; - case Register::EDash: deDash_.bytes.low = static_cast(value); break; + case Register::DDash: deDash_.halves.high = static_cast(value); break; + case Register::EDash: deDash_.halves.low = static_cast(value); break; case Register::DEDash: deDash_.full = value; break; - case Register::HDash: hlDash_.bytes.high = static_cast(value); break; - case Register::LDash: hlDash_.bytes.low = static_cast(value); break; + case Register::HDash: hlDash_.halves.high = static_cast(value); break; + case Register::LDash: hlDash_.halves.low = static_cast(value); break; case Register::HLDash: hlDash_.full = value; break; - case Register::IXh: ix_.bytes.high = static_cast(value); break; - case Register::IXl: ix_.bytes.low = static_cast(value); break; + case Register::IXh: ix_.halves.high = static_cast(value); break; + case Register::IXl: ix_.halves.low = static_cast(value); break; case Register::IX: ix_.full = value; break; - case Register::IYh: iy_.bytes.high = static_cast(value); break; - case Register::IYl: iy_.bytes.low = static_cast(value); break; + case Register::IYh: iy_.halves.high = static_cast(value); break; + case Register::IYl: iy_.halves.low = static_cast(value); break; case Register::IY: iy_.full = value; break; - case Register::R: ir_.bytes.low = static_cast(value); break; - case Register::I: ir_.bytes.high = static_cast(value); break; + case Register::R: ir_.halves.low = static_cast(value); break; + case Register::I: ir_.halves.high = static_cast(value); break; case Register::Refresh: ir_.full = value; break; case Register::IFF1: iff1_ = !!value; break; diff --git a/Processors/Z80/Implementation/Z80Implementation.hpp b/Processors/Z80/Implementation/Z80Implementation.hpp index cc24de790..141147b52 100644 --- a/Processors/Z80/Implementation/Z80Implementation.hpp +++ b/Processors/Z80/Implementation/Z80Implementation.hpp @@ -90,7 +90,7 @@ template < class T, break; case MicroOp::DecodeOperation: refresh_addr_ = ir_; - ir_.bytes.low = (ir_.bytes.low & 0x80) | ((ir_.bytes.low + current_instruction_page_->r_step) & 0x7f); + ir_.halves.low = (ir_.halves.low & 0x80) | ((ir_.halves.low + current_instruction_page_->r_step) & 0x7f); pc_.full += pc_increment_ & static_cast(halt_mask_); scheduled_program_counter_ = current_instruction_page_->instructions[operation_ & halt_mask_]; flag_adjustment_history_ <<= 1; @@ -108,13 +108,12 @@ template < class T, case MicroOp::Move16: *static_cast(operation->destination) = *static_cast(operation->source); break; case MicroOp::AssembleAF: - temp16_.bytes.high = a_; - temp16_.bytes.low = get_flags(); + temp16_.halves.high = a_; + temp16_.halves.low = get_flags(); break; case MicroOp::DisassembleAF: - a_ = temp16_.bytes.high; - set_flags(temp16_.bytes.low); - // + a_ = temp16_.halves.high; + set_flags(temp16_.halves.low); break; // MARK: - Logical @@ -179,8 +178,8 @@ template < class T, // MARK: - Flow control case MicroOp::DJNZ: - bc_.bytes.high--; - if(!bc_.bytes.high) { + bc_.halves.high--; + if(!bc_.halves.high) { advance_operation(); } break; @@ -460,10 +459,10 @@ template < class T, case MicroOp::ExAFAFDash: { const uint8_t a = a_; const uint8_t f = get_flags(); - set_flags(afDash_.bytes.low); - a_ = afDash_.bytes.high; - afDash_.bytes.high = a; - afDash_.bytes.low = f; + set_flags(afDash_.halves.low); + a_ = afDash_.halves.high; + afDash_.halves.high = a; + afDash_.halves.low = f; } break; case MicroOp::EXX: { @@ -554,13 +553,13 @@ template < class T, #undef CPxR_STEP #define INxR_STEP(dir) \ - bc_.bytes.high--; \ + bc_.halves.high--; \ hl_.full += dir; \ \ - sign_result_ = zero_result_ = bit53_result_ = bc_.bytes.high; \ + sign_result_ = zero_result_ = bit53_result_ = bc_.halves.high; \ subtract_flag_ = (temp8_ >> 6) & Flag::Subtract; \ \ - const int next_bc = bc_.bytes.low + dir; \ + const int next_bc = bc_.halves.low + dir; \ int summation = temp8_ + (next_bc&0xff); \ \ if(summation > 0xff) { \ @@ -571,18 +570,18 @@ template < class T, half_carry_result_ = 0; \ } \ \ - summation = (summation&7) ^ bc_.bytes.high; \ + summation = (summation&7) ^ bc_.halves.high; \ set_parity(summation); \ set_did_compute_flags(); case MicroOp::INDR: { INxR_STEP(-1); - REPEAT(bc_.bytes.high); + REPEAT(bc_.halves.high); } break; case MicroOp::INIR: { INxR_STEP(1); - REPEAT(bc_.bytes.high); + REPEAT(bc_.halves.high); } break; case MicroOp::IND: { @@ -598,13 +597,13 @@ template < class T, #undef INxR_STEP #define OUTxR_STEP(dir) \ - bc_.bytes.high--; \ + bc_.halves.high--; \ hl_.full += dir; \ \ - sign_result_ = zero_result_ = bit53_result_ = bc_.bytes.high; \ + sign_result_ = zero_result_ = bit53_result_ = bc_.halves.high; \ subtract_flag_ = (temp8_ >> 6) & Flag::Subtract; \ \ - int summation = temp8_ + hl_.bytes.low; \ + int summation = temp8_ + hl_.halves.low; \ if(summation > 0xff) { \ carry_result_ = Flag::Carry; \ half_carry_result_ = Flag::HalfCarry; \ @@ -612,12 +611,12 @@ template < class T, carry_result_ = half_carry_result_ = 0; \ } \ \ - summation = (summation&7) ^ bc_.bytes.high; \ + summation = (summation&7) ^ bc_.halves.high; \ set_parity(summation); \ set_did_compute_flags(); case MicroOp::OUT_R: - REPEAT(bc_.bytes.high); + REPEAT(bc_.halves.high); break; case MicroOp::OUTD: { @@ -638,7 +637,7 @@ template < class T, const uint8_t result = *static_cast(operation->source) & (1 << ((operation_ >> 3)&7)); if(current_instruction_page_->is_indexed || ((operation_&0x07) == 6)) { - bit53_result_ = memptr_.bytes.high; + bit53_result_ = memptr_.halves.high; } else { bit53_result_ = *static_cast(operation->source); } diff --git a/Processors/Z80/Implementation/Z80Storage.cpp b/Processors/Z80/Implementation/Z80Storage.cpp index b82a16370..e18836614 100644 --- a/Processors/Z80/Implementation/Z80Storage.cpp +++ b/Processors/Z80/Implementation/Z80Storage.cpp @@ -86,48 +86,48 @@ ProcessorStorage::ProcessorStorage() { #define Read5Inc(addr, val) Read5(addr, val), Inc16(addr) #define WriteInc(addr, val) Write3(addr, val), {MicroOp::Increment16, &addr.full} -#define Read16Inc(addr, val) ReadInc(addr, val.bytes.low), ReadInc(addr, val.bytes.high) -#define Read16(addr, val) ReadInc(addr, val.bytes.low), Read3(addr, val.bytes.high) +#define Read16Inc(addr, val) ReadInc(addr, val.halves.low), ReadInc(addr, val.halves.high) +#define Read16(addr, val) ReadInc(addr, val.halves.low), Read3(addr, val.halves.high) -#define Write16(addr, val) WriteInc(addr, val.bytes.low), Write3(addr, val.bytes.high) +#define Write16(addr, val) WriteInc(addr, val.halves.low), Write3(addr, val.halves.high) #define INDEX() {MicroOp::IndexedPlaceHolder}, ReadInc(pc_, temp8_), InternalOperation(10), {MicroOp::CalculateIndexAddress, &index} #define FINDEX() {MicroOp::IndexedPlaceHolder}, ReadInc(pc_, temp8_), {MicroOp::CalculateIndexAddress, &index} #define INDEX_ADDR() (add_offsets ? memptr_ : index) -#define Push(x) {MicroOp::Decrement16, &sp_.full}, Write3(sp_, x.bytes.high), {MicroOp::Decrement16, &sp_.full}, Write3(sp_, x.bytes.low) -#define Pop(x) Read3(sp_, x.bytes.low), {MicroOp::Increment16, &sp_.full}, Read3(sp_, x.bytes.high), {MicroOp::Increment16, &sp_.full} +#define Push(x) {MicroOp::Decrement16, &sp_.full}, Write3(sp_, x.halves.high), {MicroOp::Decrement16, &sp_.full}, Write3(sp_, x.halves.low) +#define Pop(x) Read3(sp_, x.halves.low), {MicroOp::Increment16, &sp_.full}, Read3(sp_, x.halves.high), {MicroOp::Increment16, &sp_.full} -#define Push8(x) {MicroOp::Decrement16, &sp_.full}, Write3(sp_, x.bytes.high), {MicroOp::Decrement16, &sp_.full}, Write5(sp_, x.bytes.low) -#define Pop7(x) Read3(sp_, x.bytes.low), {MicroOp::Increment16, &sp_.full}, Read4(sp_, x.bytes.high), {MicroOp::Increment16, &sp_.full} +#define Push8(x) {MicroOp::Decrement16, &sp_.full}, Write3(sp_, x.halves.high), {MicroOp::Decrement16, &sp_.full}, Write5(sp_, x.halves.low) +#define Pop7(x) Read3(sp_, x.halves.low), {MicroOp::Increment16, &sp_.full}, Read4(sp_, x.halves.high), {MicroOp::Increment16, &sp_.full} /* The following are actual instructions */ #define NOP Sequence(BusOp(Refresh(4))) #define JP(cc) StdInstr(Read16Inc(pc_, temp16_), {MicroOp::cc, nullptr}, {MicroOp::Move16, &temp16_.full, &pc_.full}) -#define CALL(cc) StdInstr(ReadInc(pc_, temp16_.bytes.low), {MicroOp::cc, conditional_call_untaken_program_.data()}, Read4Inc(pc_, temp16_.bytes.high), Push(pc_), {MicroOp::Move16, &temp16_.full, &pc_.full}) +#define CALL(cc) StdInstr(ReadInc(pc_, temp16_.halves.low), {MicroOp::cc, conditional_call_untaken_program_.data()}, Read4Inc(pc_, temp16_.halves.high), Push(pc_), {MicroOp::Move16, &temp16_.full, &pc_.full}) #define RET(cc) Instr(6, {MicroOp::cc, nullptr}, Pop(memptr_), {MicroOp::Move16, &memptr_.full, &pc_.full}) #define JR(cc) StdInstr(ReadInc(pc_, temp8_), {MicroOp::cc, nullptr}, InternalOperation(10), {MicroOp::CalculateIndexAddress, &pc_.full}, {MicroOp::Move16, &memptr_.full, &pc_.full}) #define RST() Instr(6, {MicroOp::CalculateRSTDestination}, Push(pc_), {MicroOp::Move16, &memptr_.full, &pc_.full}) #define LD(a, b) StdInstr({MicroOp::Move8, &b, &a}) #define LD_GROUP(r, ri) \ - LD(r, bc_.bytes.high), LD(r, bc_.bytes.low), LD(r, de_.bytes.high), LD(r, de_.bytes.low), \ - LD(r, index.bytes.high), LD(r, index.bytes.low), \ + LD(r, bc_.halves.high), LD(r, bc_.halves.low), LD(r, de_.halves.high), LD(r, de_.halves.low), \ + LD(r, index.halves.high), LD(r, index.halves.low), \ StdInstr(INDEX(), Read3(INDEX_ADDR(), temp8_), {MicroOp::Move8, &temp8_, &ri}), \ LD(r, a_) #define READ_OP_GROUP(op) \ - StdInstr({MicroOp::op, &bc_.bytes.high}), StdInstr({MicroOp::op, &bc_.bytes.low}), \ - StdInstr({MicroOp::op, &de_.bytes.high}), StdInstr({MicroOp::op, &de_.bytes.low}), \ - StdInstr({MicroOp::op, &index.bytes.high}), StdInstr({MicroOp::op, &index.bytes.low}), \ + StdInstr({MicroOp::op, &bc_.halves.high}), StdInstr({MicroOp::op, &bc_.halves.low}), \ + StdInstr({MicroOp::op, &de_.halves.high}), StdInstr({MicroOp::op, &de_.halves.low}), \ + StdInstr({MicroOp::op, &index.halves.high}), StdInstr({MicroOp::op, &index.halves.low}), \ StdInstr(INDEX(), Read3(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \ StdInstr({MicroOp::op, &a_}) #define READ_OP_GROUP_D(op) \ - StdInstr({MicroOp::op, &bc_.bytes.high}), StdInstr({MicroOp::op, &bc_.bytes.low}), \ - StdInstr({MicroOp::op, &de_.bytes.high}), StdInstr({MicroOp::op, &de_.bytes.low}), \ - StdInstr({MicroOp::op, &index.bytes.high}), StdInstr({MicroOp::op, &index.bytes.low}), \ + StdInstr({MicroOp::op, &bc_.halves.high}), StdInstr({MicroOp::op, &bc_.halves.low}), \ + StdInstr({MicroOp::op, &de_.halves.high}), StdInstr({MicroOp::op, &de_.halves.low}), \ + StdInstr({MicroOp::op, &index.halves.high}), StdInstr({MicroOp::op, &index.halves.low}), \ StdInstr(INDEX(), Read4Pre(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \ StdInstr({MicroOp::op, &a_}) @@ -135,19 +135,19 @@ ProcessorStorage::ProcessorStorage() { #define RMWI(x, op, ...) StdInstr(Read4(INDEX_ADDR(), x), {MicroOp::op, &x}, Write3(INDEX_ADDR(), x)) #define MODIFY_OP_GROUP(op) \ - StdInstr({MicroOp::op, &bc_.bytes.high}), StdInstr({MicroOp::op, &bc_.bytes.low}), \ - StdInstr({MicroOp::op, &de_.bytes.high}), StdInstr({MicroOp::op, &de_.bytes.low}), \ - StdInstr({MicroOp::op, &index.bytes.high}), StdInstr({MicroOp::op, &index.bytes.low}), \ + StdInstr({MicroOp::op, &bc_.halves.high}), StdInstr({MicroOp::op, &bc_.halves.low}), \ + StdInstr({MicroOp::op, &de_.halves.high}), StdInstr({MicroOp::op, &de_.halves.low}), \ + StdInstr({MicroOp::op, &index.halves.high}), StdInstr({MicroOp::op, &index.halves.low}), \ RMW(temp8_, op), \ StdInstr({MicroOp::op, &a_}) #define IX_MODIFY_OP_GROUP(op) \ - RMWI(bc_.bytes.high, op), \ - RMWI(bc_.bytes.low, op), \ - RMWI(de_.bytes.high, op), \ - RMWI(de_.bytes.low, op), \ - RMWI(hl_.bytes.high, op), \ - RMWI(hl_.bytes.low, op), \ + RMWI(bc_.halves.high, op), \ + RMWI(bc_.halves.low, op), \ + RMWI(de_.halves.high, op), \ + RMWI(de_.halves.low, op), \ + RMWI(hl_.halves.high, op), \ + RMWI(hl_.halves.low, op), \ RMWI(temp8_, op), \ RMWI(a_, op) @@ -166,7 +166,7 @@ ProcessorStorage::ProcessorStorage() { #define SBC16(d, s) StdInstr(InternalOperation(8), InternalOperation(6), {MicroOp::SBC16, &s.full, &d.full}) void ProcessorStorage::install_default_instruction_set() { - MicroOp conditional_call_untaken_program[] = Sequence(ReadInc(pc_, temp16_.bytes.high)); + MicroOp conditional_call_untaken_program[] = Sequence(ReadInc(pc_, temp16_.halves.high)); copy_program(conditional_call_untaken_program, conditional_call_untaken_program_); assemble_base_page(base_page_, hl_, false, cb_page_); @@ -225,12 +225,12 @@ void ProcessorStorage::install_default_instruction_set() { }; MicroOp irq_mode2_program[] = { { MicroOp::BeginIRQ }, - BusOp(IntAckStart(7, temp16_.bytes.low)), - BusOp(IntWait(temp16_.bytes.low)), - BusOp(IntAckEnd(temp16_.bytes.low)), + BusOp(IntAckStart(7, temp16_.halves.low)), + BusOp(IntWait(temp16_.halves.low)), + BusOp(IntAckEnd(temp16_.halves.low)), BusOp(Refresh(4)), Push(pc_), - { MicroOp::Move8, &ir_.bytes.high, &temp16_.bytes.high }, + { MicroOp::Move8, &ir_.halves.high, &temp16_.halves.high }, Read16(temp16_, pc_), { MicroOp::MoveToNextProgram } }; @@ -253,27 +253,27 @@ void ProcessorStorage::assemble_ed_page(InstructionPage &target) { NOP_ROW(), /* 0x10 */ NOP_ROW(), /* 0x20 */ NOP_ROW(), /* 0x30 */ - /* 0x40 IN B, (C); 0x41 OUT (C), B */ IN_OUT(bc_.bytes.high), + /* 0x40 IN B, (C); 0x41 OUT (C), B */ IN_OUT(bc_.halves.high), /* 0x42 SBC HL, BC */ SBC16(hl_, bc_), /* 0x43 LD (nn), BC */ StdInstr(Read16Inc(pc_, memptr_), Write16(memptr_, bc_)), /* 0x44 NEG */ StdInstr({MicroOp::NEG}), /* 0x45 RETN */ StdInstr(Pop(pc_), {MicroOp::RETN}), - /* 0x46 IM 0 */ StdInstr({MicroOp::IM}), /* 0x47 LD I, A */ Instr(6, {MicroOp::Move8, &a_, &ir_.bytes.high}), - /* 0x48 IN C, (C); 0x49 OUT (C), C */ IN_OUT(bc_.bytes.low), + /* 0x46 IM 0 */ StdInstr({MicroOp::IM}), /* 0x47 LD I, A */ Instr(6, {MicroOp::Move8, &a_, &ir_.halves.high}), + /* 0x48 IN C, (C); 0x49 OUT (C), C */ IN_OUT(bc_.halves.low), /* 0x4a ADC HL, BC */ ADC16(hl_, bc_), /* 0x4b LD BC, (nn) */ StdInstr(Read16Inc(pc_, temp16_), Read16(temp16_, bc_)), /* 0x4c NEG */ StdInstr({MicroOp::NEG}), /* 0x4d RETI */ StdInstr(Pop(pc_), {MicroOp::RETN}), - /* 0x4e IM 0/1 */ StdInstr({MicroOp::IM}), /* 0x4f LD R, A */ Instr(6, {MicroOp::Move8, &a_, &ir_.bytes.low}), - /* 0x50 IN D, (C); 0x51 OUT (C), D */ IN_OUT(de_.bytes.high), + /* 0x4e IM 0/1 */ StdInstr({MicroOp::IM}), /* 0x4f LD R, A */ Instr(6, {MicroOp::Move8, &a_, &ir_.halves.low}), + /* 0x50 IN D, (C); 0x51 OUT (C), D */ IN_OUT(de_.halves.high), /* 0x52 SBC HL, DE */ SBC16(hl_, de_), /* 0x53 LD (nn), DE */ StdInstr(Read16Inc(pc_, memptr_), Write16(memptr_, de_)), /* 0x54 NEG */ StdInstr({MicroOp::NEG}), /* 0x55 RETN */ StdInstr(Pop(pc_), {MicroOp::RETN}), - /* 0x56 IM 1 */ StdInstr({MicroOp::IM}), /* 0x57 LD A, I */ Instr(6, {MicroOp::Move8, &ir_.bytes.high, &a_}, {MicroOp::SetAFlags}), - /* 0x58 IN E, (C); 0x59 OUT (C), E */ IN_OUT(de_.bytes.low), + /* 0x56 IM 1 */ StdInstr({MicroOp::IM}), /* 0x57 LD A, I */ Instr(6, {MicroOp::Move8, &ir_.halves.high, &a_}, {MicroOp::SetAFlags}), + /* 0x58 IN E, (C); 0x59 OUT (C), E */ IN_OUT(de_.halves.low), /* 0x5a ADC HL, DE */ ADC16(hl_, de_), /* 0x5b LD DE, (nn) */ StdInstr(Read16Inc(pc_, temp16_), Read16(temp16_, de_)), /* 0x5c NEG */ StdInstr({MicroOp::NEG}), /* 0x5d RETN */ StdInstr(Pop(pc_), {MicroOp::RETN}), - /* 0x5e IM 2 */ StdInstr({MicroOp::IM}), /* 0x5f LD A, R */ Instr(6, {MicroOp::Move8, &ir_.bytes.low, &a_}, {MicroOp::SetAFlags}), - /* 0x60 IN H, (C); 0x61 OUT (C), H */ IN_OUT(hl_.bytes.high), + /* 0x5e IM 2 */ StdInstr({MicroOp::IM}), /* 0x5f LD A, R */ Instr(6, {MicroOp::Move8, &ir_.halves.low, &a_}, {MicroOp::SetAFlags}), + /* 0x60 IN H, (C); 0x61 OUT (C), H */ IN_OUT(hl_.halves.high), /* 0x62 SBC HL, HL */ SBC16(hl_, hl_), /* 0x63 LD (nn), HL */ StdInstr(Read16Inc(pc_, memptr_), Write16(memptr_, hl_)), /* 0x64 NEG */ StdInstr({MicroOp::NEG}), /* 0x65 RETN */ StdInstr(Pop(pc_), {MicroOp::RETN}), /* 0x66 IM 0 */ StdInstr({MicroOp::IM}), /* 0x67 RRD */ StdInstr(Read3(hl_, temp8_), InternalOperation(8), {MicroOp::RRD}, Write3(hl_, temp8_)), - /* 0x68 IN L, (C); 0x69 OUT (C), L */ IN_OUT(hl_.bytes.low), + /* 0x68 IN L, (C); 0x69 OUT (C), L */ IN_OUT(hl_.halves.low), /* 0x6a ADC HL, HL */ ADC16(hl_, hl_), /* 0x6b LD HL, (nn) */ StdInstr(Read16Inc(pc_, temp16_), Read16(temp16_, hl_)), /* 0x6c NEG */ StdInstr({MicroOp::NEG}), /* 0x6d RETN */ StdInstr(Pop(pc_), {MicroOp::RETN}), /* 0x6e IM 0/1 */ StdInstr({MicroOp::IM}), /* 0x6f RLD */ StdInstr(Read3(hl_, temp8_), InternalOperation(8), {MicroOp::RLD}, Write3(hl_, temp8_)), @@ -316,7 +316,7 @@ void ProcessorStorage::assemble_ed_page(InstructionPage &target) { #undef NOP_ROW } -void ProcessorStorage::assemble_cb_page(InstructionPage &target, RegisterPair &index, bool add_offsets) { +void ProcessorStorage::assemble_cb_page(InstructionPage &target, RegisterPair16 &index, bool add_offsets) { #define OCTO_OP_GROUP(m, x) m(x), m(x), m(x), m(x), m(x), m(x), m(x), m(x) #define CB_PAGE(m, p) m(RLC), m(RRC), m(RL), m(RR), m(SLA), m(SRA), m(SLL), m(SRL), OCTO_OP_GROUP(p, BIT), OCTO_OP_GROUP(m, RES), OCTO_OP_GROUP(m, SET) @@ -343,7 +343,7 @@ void ProcessorStorage::assemble_cb_page(InstructionPage &target, RegisterPair &i #undef CB_PAGE } -void ProcessorStorage::assemble_base_page(InstructionPage &target, RegisterPair &index, bool add_offsets, InstructionPage &cb_page) { +void ProcessorStorage::assemble_base_page(InstructionPage &target, RegisterPair16 &index, bool add_offsets, InstructionPage &cb_page) { #define INC_DEC_LD(r) \ StdInstr({MicroOp::Increment8, &r}), \ StdInstr({MicroOp::Decrement8, &r}), \ @@ -360,14 +360,14 @@ void ProcessorStorage::assemble_base_page(InstructionPage &target, RegisterPair /* 0x02 LD (BC), A */ StdInstr({MicroOp::SetAddrAMemptr, &bc_.full}, Write3(bc_, a_)), /* 0x03 INC BC; 0x04 INC B; 0x05 DEC B; 0x06 LD B, n */ - INC_INC_DEC_LD(bc_, bc_.bytes.high), + INC_INC_DEC_LD(bc_, bc_.halves.high), /* 0x07 RLCA */ StdInstr({MicroOp::RLCA}), /* 0x08 EX AF, AF' */ StdInstr({MicroOp::ExAFAFDash}), /* 0x09 ADD HL, BC */ ADD16(index, bc_), /* 0x0a LD A, (BC) */ StdInstr({MicroOp::Move16, &bc_.full, &memptr_.full}, Read3(memptr_, a_), Inc16(memptr_)), /* 0x0b DEC BC; 0x0c INC C; 0x0d DEC C; 0x0e LD C, n */ - DEC_INC_DEC_LD(bc_, bc_.bytes.low), + DEC_INC_DEC_LD(bc_, bc_.halves.low), /* 0x0f RRCA */ StdInstr({MicroOp::RRCA}), /* 0x10 DJNZ */ Instr(6, ReadInc(pc_, temp8_), {MicroOp::DJNZ}, InternalOperation(10), {MicroOp::CalculateIndexAddress, &pc_.full}, {MicroOp::Move16, &memptr_.full, &pc_.full}), @@ -375,7 +375,7 @@ void ProcessorStorage::assemble_base_page(InstructionPage &target, RegisterPair /* 0x12 LD (DE), A */ StdInstr({MicroOp::SetAddrAMemptr, &de_.full}, Write3(de_, a_)), /* 0x13 INC DE; 0x14 INC D; 0x15 DEC D; 0x16 LD D, n */ - INC_INC_DEC_LD(de_, de_.bytes.high), + INC_INC_DEC_LD(de_, de_.halves.high), /* 0x17 RLA */ StdInstr({MicroOp::RLA}), /* 0x18 JR */ StdInstr(ReadInc(pc_, temp8_), InternalOperation(10), {MicroOp::CalculateIndexAddress, &pc_.full}, {MicroOp::Move16, &memptr_.full, &pc_.full}), @@ -383,21 +383,21 @@ void ProcessorStorage::assemble_base_page(InstructionPage &target, RegisterPair /* 0x1a LD A, (DE) */ StdInstr({MicroOp::Move16, &de_.full, &memptr_.full}, Read3(memptr_, a_), Inc16(memptr_)), /* 0x1b DEC DE; 0x1c INC E; 0x1d DEC E; 0x1e LD E, n */ - DEC_INC_DEC_LD(de_, de_.bytes.low), + DEC_INC_DEC_LD(de_, de_.halves.low), /* 0x1f RRA */ StdInstr({MicroOp::RRA}), /* 0x20 JR NZ */ JR(TestNZ), /* 0x21 LD HL, nn */ StdInstr(Read16Inc(pc_, index)), /* 0x22 LD (nn), HL */ StdInstr(Read16Inc(pc_, memptr_), Write16(memptr_, index)), /* 0x23 INC HL; 0x24 INC H; 0x25 DEC H; 0x26 LD H, n */ - INC_INC_DEC_LD(index, index.bytes.high), + INC_INC_DEC_LD(index, index.halves.high), /* 0x27 DAA */ StdInstr({MicroOp::DAA}), /* 0x28 JR Z */ JR(TestZ), /* 0x29 ADD HL, HL */ ADD16(index, index), /* 0x2a LD HL, (nn) */ StdInstr(Read16Inc(pc_, temp16_), Read16(temp16_, index)), /* 0x2b DEC HL; 0x2c INC L; 0x2d DEC L; 0x2e LD L, n */ - DEC_INC_DEC_LD(index, index.bytes.low), + DEC_INC_DEC_LD(index, index.halves.low), /* 0x2f CPL */ StdInstr({MicroOp::CPL}), /* 0x30 JR NC */ JR(TestNC), /* 0x31 LD SP, nn */ StdInstr(Read16Inc(pc_, sp_)), @@ -418,29 +418,29 @@ void ProcessorStorage::assemble_base_page(InstructionPage &target, RegisterPair /* 0x3f CCF */ StdInstr({MicroOp::CCF}), /* 0x40 LD B, B; 0x41 LD B, C; 0x42 LD B, D; 0x43 LD B, E; 0x44 LD B, H; 0x45 LD B, L; 0x46 LD B, (HL); 0x47 LD B, A */ - LD_GROUP(bc_.bytes.high, bc_.bytes.high), + LD_GROUP(bc_.halves.high, bc_.halves.high), /* 0x48 LD C, B; 0x49 LD C, C; 0x4a LD C, D; 0x4b LD C, E; 0x4c LD C, H; 0x4d LD C, L; 0x4e LD C, (HL); 0x4f LD C, A */ - LD_GROUP(bc_.bytes.low, bc_.bytes.low), + LD_GROUP(bc_.halves.low, bc_.halves.low), /* 0x50 LD D, B; 0x51 LD D, C; 0x52 LD D, D; 0x53 LD D, E; 0x54 LD D, H; 0x55 LD D, L; 0x56 LD D, (HL); 0x57 LD D, A */ - LD_GROUP(de_.bytes.high, de_.bytes.high), + LD_GROUP(de_.halves.high, de_.halves.high), /* 0x58 LD E, B; 0x59 LD E, C; 0x5a LD E, D; 0x5b LD E, E; 0x5c LD E, H; 0x5d LD E, L; 0x5e LD E, (HL); 0x5f LD E, A */ - LD_GROUP(de_.bytes.low, de_.bytes.low), + LD_GROUP(de_.halves.low, de_.halves.low), /* 0x60 LD H, B; 0x61 LD H, C; 0x62 LD H, D; 0x63 LD H, E; 0x64 LD H, H; 0x65 LD H, L; 0x66 LD H, (HL); 0x67 LD H, A */ - LD_GROUP(index.bytes.high, hl_.bytes.high), + LD_GROUP(index.halves.high, hl_.halves.high), /* 0x68 LD L, B; 0x69 LD L, C; 0x6a LD L, D; 0x6b LD L, E; 0x6c LD L, H; 0x6d LD H, L; 0x6e LD L, (HL); 0x6f LD L, A */ - LD_GROUP(index.bytes.low, hl_.bytes.low), + LD_GROUP(index.halves.low, hl_.halves.low), - /* 0x70 LD (HL), B */ StdInstr(INDEX(), Write3(INDEX_ADDR(), bc_.bytes.high)), - /* 0x71 LD (HL), C */ StdInstr(INDEX(), Write3(INDEX_ADDR(), bc_.bytes.low)), - /* 0x72 LD (HL), D */ StdInstr(INDEX(), Write3(INDEX_ADDR(), de_.bytes.high)), - /* 0x73 LD (HL), E */ StdInstr(INDEX(), Write3(INDEX_ADDR(), de_.bytes.low)), - /* 0x74 LD (HL), H */ StdInstr(INDEX(), Write3(INDEX_ADDR(), hl_.bytes.high)), // neither of these stores parts of the index register; - /* 0x75 LD (HL), L */ StdInstr(INDEX(), Write3(INDEX_ADDR(), hl_.bytes.low)), // they always store exactly H and L. + /* 0x70 LD (HL), B */ StdInstr(INDEX(), Write3(INDEX_ADDR(), bc_.halves.high)), + /* 0x71 LD (HL), C */ StdInstr(INDEX(), Write3(INDEX_ADDR(), bc_.halves.low)), + /* 0x72 LD (HL), D */ StdInstr(INDEX(), Write3(INDEX_ADDR(), de_.halves.high)), + /* 0x73 LD (HL), E */ StdInstr(INDEX(), Write3(INDEX_ADDR(), de_.halves.low)), + /* 0x74 LD (HL), H */ StdInstr(INDEX(), Write3(INDEX_ADDR(), hl_.halves.high)), // neither of these stores parts of the index register; + /* 0x75 LD (HL), L */ StdInstr(INDEX(), Write3(INDEX_ADDR(), hl_.halves.low)), // they always store exactly H and L. /* 0x76 HALT */ StdInstr({MicroOp::HALT}), /* 0x77 LD (HL), A */ StdInstr(INDEX(), Write3(INDEX_ADDR(), a_)), @@ -478,16 +478,16 @@ void ProcessorStorage::assemble_base_page(InstructionPage &target, RegisterPair /* 0xc7 RST 00h */ RST(), /* 0xc8 RET Z */ RET(TestZ), /* 0xc9 RET */ StdInstr(Pop(pc_)), /* 0xca JP Z */ JP(TestZ), /* 0xcb [CB page] */StdInstr(FINDEX(), {MicroOp::SetInstructionPage, &cb_page}), - /* 0xcc CALL Z */ CALL(TestZ), /* 0xcd CALL */ StdInstr(ReadInc(pc_, temp16_.bytes.low), Read4Inc(pc_, temp16_.bytes.high), Push(pc_), {MicroOp::Move16, &temp16_.full, &pc_.full}), + /* 0xcc CALL Z */ CALL(TestZ), /* 0xcd CALL */ StdInstr(ReadInc(pc_, temp16_.halves.low), Read4Inc(pc_, temp16_.halves.high), Push(pc_), {MicroOp::Move16, &temp16_.full, &pc_.full}), /* 0xce ADC A, n */ StdInstr(ReadInc(pc_, temp8_), {MicroOp::ADC8, &temp8_}), /* 0xcf RST 08h */ RST(), /* 0xd0 RET NC */ RET(TestNC), /* 0xd1 POP DE */ StdInstr(Pop(de_)), - /* 0xd2 JP NC */ JP(TestNC), /* 0xd3 OUT (n), A */StdInstr(ReadInc(pc_, temp16_.bytes.low), {MicroOp::Move8, &a_, &temp16_.bytes.high}, Output(temp16_, a_)), + /* 0xd2 JP NC */ JP(TestNC), /* 0xd3 OUT (n), A */StdInstr(ReadInc(pc_, temp16_.halves.low), {MicroOp::Move8, &a_, &temp16_.halves.high}, Output(temp16_, a_)), /* 0xd4 CALL NC */ CALL(TestNC), /* 0xd5 PUSH DE */ Instr(6, Push(de_)), /* 0xd6 SUB n */ StdInstr(ReadInc(pc_, temp8_), {MicroOp::SUB8, &temp8_}), /* 0xd7 RST 10h */ RST(), /* 0xd8 RET C */ RET(TestC), /* 0xd9 EXX */ StdInstr({MicroOp::EXX}), - /* 0xda JP C */ JP(TestC), /* 0xdb IN A, (n) */StdInstr(ReadInc(pc_, temp16_.bytes.low), {MicroOp::Move8, &a_, &temp16_.bytes.high}, Input(temp16_, a_)), + /* 0xda JP C */ JP(TestC), /* 0xdb IN A, (n) */StdInstr(ReadInc(pc_, temp16_.halves.low), {MicroOp::Move8, &a_, &temp16_.halves.high}, Input(temp16_, a_)), /* 0xdc CALL C */ CALL(TestC), /* 0xdd [DD page] */StdInstr({MicroOp::SetInstructionPage, &dd_page_}), /* 0xde SBC A, n */ StdInstr(ReadInc(pc_, temp8_), {MicroOp::SBC8, &temp8_}), /* 0xdf RST 18h */ RST(), diff --git a/Processors/Z80/Implementation/Z80Storage.hpp b/Processors/Z80/Implementation/Z80Storage.hpp index 76f63894e..bc1a89cd7 100644 --- a/Processors/Z80/Implementation/Z80Storage.hpp +++ b/Processors/Z80/Implementation/Z80Storage.hpp @@ -118,10 +118,10 @@ class ProcessorStorage { void install_default_instruction_set(); uint8_t a_; - RegisterPair bc_, de_, hl_; - RegisterPair afDash_, bcDash_, deDash_, hlDash_; - RegisterPair ix_, iy_, pc_, sp_; - RegisterPair ir_, refresh_addr_; + RegisterPair16 bc_, de_, hl_; + RegisterPair16 afDash_, bcDash_, deDash_, hlDash_; + RegisterPair16 ix_, iy_, pc_, sp_; + RegisterPair16 ir_, refresh_addr_; bool iff1_ = false, iff2_ = false; int interrupt_mode_ = 0; uint16_t pc_increment_ = 1; @@ -153,7 +153,7 @@ class ProcessorStorage { bool wait_line_ = false; uint8_t operation_; - RegisterPair temp16_, memptr_; + RegisterPair16 temp16_, memptr_; uint8_t temp8_; const MicroOp *scheduled_program_counter_ = nullptr; @@ -214,7 +214,7 @@ class ProcessorStorage { void assemble_fetch_decode_execute(InstructionPage &target, int length); void assemble_ed_page(InstructionPage &target); - void assemble_cb_page(InstructionPage &target, RegisterPair &index, bool add_offsets); - void assemble_base_page(InstructionPage &target, RegisterPair &index, bool add_offsets, InstructionPage &cb_page); + void assemble_cb_page(InstructionPage &target, RegisterPair16 &index, bool add_offsets); + void assemble_base_page(InstructionPage &target, RegisterPair16 &index, bool add_offsets, InstructionPage &cb_page); }; diff --git a/ROMImages/AtariST/doc/announce.txt b/ROMImages/AtariST/doc/announce.txt new file mode 100644 index 000000000..d3c6ae046 --- /dev/null +++ b/ROMImages/AtariST/doc/announce.txt @@ -0,0 +1,174 @@ +Dear Atari Community! + + +We are happy to announce a new public release of EmuTOS: + +EmuTOS 0.9.10 -- December 23, 2018 + + + +INTRODUCTION + +EmuTOS is a single-user single-tasking operating system for 32-bit Atari +computers, clones and emulators. It can be used as a replacement for the +TOS images typically needed by emulators and can also run on some real +hardware, including the Atari ST(e), TT, and Falcon, and the FireBee. It +can even run on non-Atari hardware such as Amiga and ColdFire Evaluation +Boards. All the source code is open and free, licensed under the GNU +General Public License (GPL). + + + +CHANGES SINCE RELEASE 0.9.4 + +For a quick summary of changes by release since release 0.9.4, please +refer to doc/changelog.txt. + +For a detailed list of all changes since the project started, refer to +the Git repository. + + + +DESCRIPTION + +EmuTOS is basically made up of the following: + +- The BIOS, which is the basic input output system +- The XBIOS, which provides the interface to the hardware +- The BDOS, which are the high level OS routines, often known as GEMDOS +- The VDI, the virtual device interface, i.e. the screen driver +- The AES, the application environment services or window manager +- The EmuDesk desktop, which is the graphical shell to the user +- EmuCON2, the command-line interpreter + +The BIOS and XBIOS code is our own development. It is written from +scratch and implements all relevant TOS 3 BIOS & XBIOS functionality, +and a bit more, e.g. hard disk access. See doc/status.txt for details. + +The GEMDOS part is based on Digital Research's GEMDOS sources, which were +made available under GPL license in 1999 by Caldera. + +The graphical parts like VDI and AES are now more or less fully +implemented up to TOS v1.04 level. They work in all the graphics modes +of the original Atari ST, with some extensions. For example, systems with +VIDEL support 256 colours and 640x480 screen resolution. Some emulators +can patch EmuTOS to work with much bigger screen resolutions. + +The desktop is now almost as nice as the one in TOS 2 or higher (although +there is still work to be done). Of course you are always free to use a +more advanced desktop replacement like TeraDesk. + +EmuCON2 is a basic but useful command-line interpreter, written from scratch +by Roger Burrows in 2013 to replace the original CLI. + +Since EmuTOS just implements TOS functionality, you might want to use +MiNT on top of it in order to run more modern software. EmuTOS is not +an alternative to MiNT, but it's the only free base OS to boot MiNT. + + + +EMULATION AND FUTURE PLATFORMS + +EmuTOS and MiNT cooperate well. Both can utilize the Native Features +(NatFeats) interface for emulators: + https://github.com/aranym/aranym/wiki/natfeats-about + +EmuTOS uses this new standard interface for all the relevant native +functions supported by an emulator on which it's running. This interface +proxies the calls to the underlying host OS so that these features don't +need to be emulated. This is both faster and can provide features that +would be infeasible on a real machine. It may allow using modern graphics +cards, provide fast native filesystem access and enable you to use +networking with all bells and whistles - and many other things you might +not have even dreamed of. + +The ARAnyM emulator has the most extensive support for NatFeats. +The Hatari emulator supports the basic NatFeats facilities. + + + +HARDWARE + +Making EmuTOS run natively on a new hardware platform is more or less just +a question of driver support for EmuTOS. The same for MiNT, if you'd like +to have it running on top of EmuTOS. + +This is the currently supported original Atari hardware: +- CPU support for M68000, M68030 +- FPU detection +- 68030 MMU and cache +- Memory controller (both ST and Falcon) +- TT-RAM +- Monitor type detection (mono, RGB or VGA) +- DMA controller +- WD 1772 / AJAX Floppy disk controller +- MFP, MFP #2 +- PSG +- ST shifter +- STe shifter +- TT shifter +- VIDEL +- ACIAs, IKBD protocol, mouse +- MegaST Real-Time Clock (set clock not tested) +- NVRAM (including RTC) +- Blitter +- Microwire +- SCC +- IDE +- SCSI +- ACSI + +EmuTOS also supports the following Atari-compatible hardware: +- CPU support for M68010, M68020, M68040, M68060, ColdFire V4e, and Apollo 68080 +- ICD AdSCSI Plus ST Real-Time Clock +- MonSTer expansion card +- Nova/ET4000 graphics card +- SD/MMC +- The Native Features interface to some degree + +Currently unsupported hardware features: +- DSP + +EmuTOS is also available on some non-Atari hardware: +- Amiga (floppy or ROM for any Amiga, including MapROM support) +- ColdFire Evaluation Boards (M5484LITE, M5485EVB) + + + +AVAILABILITY + +The EmuTOS home page is: + + http://emutos.sourceforge.net/ + +The project home is on SourceForge: + + http://sourceforge.net/projects/emutos/ + +The latest releases can be downloaded from: + + http://sourceforge.net/projects/emutos/files/emutos/ + +Development snapshots allow you to test the current development progress: + + http://sourceforge.net/projects/emutos/files/snapshots/ + +The latest sources are always available on GitHub: + + https://github.com/emutos/emutos + +If you are just curious or would like to help us develop this nice little +OS, you are invited to subscribe to our mailing list for developers at: + + https://lists.sourceforge.net/lists/listinfo/emutos-devel + + +We hope that you like EmuTOS. If you have any suggestions or comments, we +always appreciate hearing both the good and the bad things about it. + + +The EmuTOS development team. + +-- +Originally written by Martin Doering +http://emutos.sourceforge.net/ diff --git a/ROMImages/AtariST/doc/authors.txt b/ROMImages/AtariST/doc/authors.txt new file mode 100644 index 000000000..936761e3c --- /dev/null +++ b/ROMImages/AtariST/doc/authors.txt @@ -0,0 +1,254 @@ +EmuTOS source code consists of several parts, and includes code taken from +other projects - Many thanks to them and to their authors for releasing the +code under GPL. + +The 'historical' authors - those who wrote the code before the start of the +EmuTOS project - are mentioned in the individual files they've authored. +Major 'historical' parts are: +- BDOS, VDI - both come from the latest known GEMDOS version from + Digital Research (later versions seem to have been developed by Atari). +- AES, desktop - The C source code for GEM comes from the x86 version. +- Some GEM assembler files come from the AES for the Apple LISA. + +All these historical parts were released under the General Public License +by Caldera, Inc. in mid april 2000 (?) (For the record, Caldera bought it +from Novell in 1997 along with DR-DOS; later Caldera disappeared and this +is the copyright notice that refers to Lineo) + +Minor borrowed stuff: +- the printf and memcpy stuff is inspired by the Minix kernel and library; +- the processor/FPU detection is taken from the MiNT kernel; +- "Bug" includes parts of the original gettext source code; +- some low-level hardware stuff comes from the Linux kernel; + +While the main Amiga support was written from scratch by the EmuTOS team, +some advanced code (FastRAM support for ROM versions) has been borrowed from the +AROS project. Due to license incompatibilities, that code is shipped in the +source archive, but not compiled into the official binaries. + +The following is a list of 'recent' contributors - individuals involved in the +EmuTOS project. In this project virtually everybody modifies every file; +nevertheless here is an attempt at identifying who's guilty of what: + +Roger Burrows (RFB) +- Current project admin +- Support for SD/MMC Cards on the FireBee +- SCSI support +- Improvements to IDE, CompactFlash, ACSI, and other mass-storage support +- FAT16 partitions up to 2 GB (inspired by Didier Méquignon's BDOS fork) +- Full support for Falcon video hardware +- Real Falcon 030 support (cache, MMU, SCC, HD floppy) +- Real TT 030 support (video, MFP2) +- Blitter support for horizontal lines/filled rectangles/raster graphics +- Desktop and file selector improvements +- EmuCON2 +- Tools: erd (EmuTOS Resource Decompiler) & draft (deletes items from desktop resource) +- Various bugfixes and cleanups + +Vincent Rivière (VRI) +- Many improvements to the build and configuration process +- Moved project from CVS to Git, and from SourceForge to GitHub +- Implemented automatic builds via Travis CI +- Patches for compiling with GCC 4.x +- ColdFire CPU and FireBee support +- Initial IDE driver +- Big improvements to FastRAM/Alt-RAM handling +- Amiga support +- ColdFire Evaluation Boards support +- Apollo 68080 support +- Various bug fixes and cleanups + +Thomas Huth (THH) +- Lots of bugfixes & cleanups all over the place +- Integration and maintenance of the AES and GEM-Desktop +- XBIOS DMA sound functions + +Petr Stehlik (PES) +- BIOS disk interface, BDOS filesystem +- Falcon and ARAnyM support + +Laurent Vogel (LVL) +- Original ST hardware (MFP, ACIA, parport, sound, floppy, ACSI) +- Makefile tricks and tools +- NLS support + +Martin Doering (MAD) +- Original project initiator (but retired many years ago) +- Memory setup, VT52 console, Line A, mouse +- Virtually everything not modified later by the others + + +Thanks to all current and previous translators, who have helped us keep +EmuTOS multi-lingual: + +- Czech translation + Bohdan Milar + Petr Stehlik + Pavel Salač + Jan Krupka + +- Finnish translation + Eero Tamminen + +- French translation + Laurent Vogel + Vincent Rivière + +- German translation + Thomas Huth + +- Greek translation + George Nakos + Christos Tziotzis + +- Italian translation + Lodovico Zanier + +- Spanish translation + Gabriel Huertas + David Gálvez + Jordi Mestres Ruiz + +- Russian translation + Dima Sobolev + + +Thanks also to all mailing list contributors for their help, and +especially: + +Stanislav Opichal (SOP) +- FreeMiNT kernel bootstrap via BOOTSTRAP NatFeat + +Frank Naumann +- FreeMiNT + +Ctirad Fertr , +Milan Jurik +- The ARanyM team + +Johan Klockars +- fVDI + +Henk Robbers +- XaAES, AHCC + +Jacques-Etienne Rahon "Kevin Flynn" +- Extensive demo testing on STeeM + +Patrice Mandin and Markus Oberhumer +- Hints and patches for compiling EmuTOS with GCC 3.x + +Eero Tamminen +- Many bug reports, extensive testing, testcases supply +- Many documentation updates +- Finnish keyboard mapping +- Hatari debug symbols +- Static source analysis and cleanup +- Line-A implementation + +Gerhard Stoll +- Improved our nvmaccess() function +- TOS hypertext + +Roger Crettol +- Found and fixed a bug in GEMDOS Pterm() function +- Support for swiss german keyboard +- Some EmuCON improvements + +David Savinkoff +- Bug fixes for the BIOS parallel port code +- Improved Isqrt() function +- Other various bugfixes + +Olivier Landemarre +- Renamed internal VDI functions to avoid name conflicts + +Jean-François Del Nero +- Improved memory detection on cold boot +- Tested the EmuTOS ROM on real STe hardware +- Various bugfixes +- Invaluable HxC Floppy Emulator for tests on real hardware + +David Gálvez +- XHNewCookie() implementation + +Fredi Aschwanden +and all the ACP team +- Tests on the FireBee hardware + +James Boulton +- floprw() fix for reading multiple sectors + +Stephen Leary +- Fixed support for IDE slave device + +Miro Kropáček +- Experimental 68040 MMU support + +WongCK on Atari-Forum +- Tests on real Falcon 030 + +Michaël Gibs on English Amiga Board +- Tests on Amiga 1200 with Blizzard 1260 accelerator + +Amiman99 on English Amiga Board +- Tests on Amiga 1000 + +Radoslaw Kujawa +- Compilation fix on 64-bit build systems + +Hampa Hug +- Fixed ACSI bugs + +Markus Fröschle +- Tests on the FireBee and BaS_gcc support +- Inspired the support for the blitter +- Fix various AES & VDI bugs + +Christian Zietz +- ROM tests on real ST/STe hardware +- Fix floppy/ACSI bug +- Fix memory detection to support STs as well as STes +- Support for extended MBR partitions +- Add IDE 'twisted cable' support +- IDE performance improvements +- Fix cold boot problems caused by some ST MMUs +- Fix screen corruption after a reset on some Mega(STe) systems +- ET4000/Nova support +- Miscellaneous bug fixes + +Jo Even Skarstein +- Support for Norwegian & Swedish keyboards +- Support for MonSTer add-on board + +Thorsten Otto +- Make EmuTOS more compatible with Atari TOS in several areas +- Add check_read_byte() workaround for ARAnyM-JIT +- Fixes to 68040 PMMU setup +- Inspired the support for window/desktop background configuration +- Found many bugs in desktop shortcut handling +- Help with resource manipulation programs +- Miscellaneous bug fixes +- Lots of source code cleanup + +Apollo Team: Gunnar von Boehn, Philippe Flype, Simo Koivukoski, +pisklak, guibrush, TuKo, and all other members... +- Apollo 68080 and Amiga support + +Steven Seagal +- Steem SSE support + +Stefan Niestegge +- ROM tests on real hardware: STe, Falcon and Amiga 600 + +Ingo Uhlemann +- ROM tests on real TT hardware + +Stefan Haubenthal +- EmuTOS packaging on Aminet: http://aminet.net/package/misc/emu/emutos + +Christian Quante +- Various desktop bug fixes & improvements + +Keli Hlodversson +- Replaced form_alert() icons with more TOS-like ones diff --git a/ROMImages/AtariST/doc/bugs.txt b/ROMImages/AtariST/doc/bugs.txt new file mode 100644 index 000000000..bebf96f71 --- /dev/null +++ b/ROMImages/AtariST/doc/bugs.txt @@ -0,0 +1,59 @@ +AES/VDI/Line-A bugs: +- The right side of outline characters are clipped e.g. in "LaserChess", + "Diamond miner" & "Minigolf" games, and in vditext tester: + https://sourceforge.net/p/emutos/mailman/message/29276993/ + This is due to a bug somewhere in the ugly text_blt() assembler + function in vdi_tblit.S. +- Thick arcs going partly outside of screen have incorrectly + drawn pixels at top of screen in vdiline tester. +- In "MathMaze" and "The Ultimate Minesweeper", game win and score + dialogs leave left/bottom outline on screen when they close. +- Dialog box at the end of Glücksrad game is missing text from + the dialog button (and the button width isn't correct) +- Line-A polygons are one pixel short at both sides. This is + because clc_flit() function does it for VDI (where perimeter + is drawn separately). It is visible e.g. in "Japlish" game. + +Video problems: +- Omega's XiTec presentations "swing.prg" throws privilege exception + on exit. Both TOS v3 & EmuTOS survive that OK, but in EmuTOS both + screen and mouse acceleration are messed up: EmuTOS exception restore + is missing videomode & mouse reset. + +Atari Falcon / TOS v4 compatibility bugs: +- Escape Paint icons don't show in image operations window and their + place in toolbar window is inverted on mouse click. +- Falcon FalcAMP button icons aren't visible as EmuTOS doesn't support + new style RSC files with CICONs. + +Problems that also occur in Atari TOS: +- VDI: when drawing a wide polyline with squared ends and more than one + segment, if the width of the line is greater than twice the length of + an ending segment, the end will have a bump rather than being square. + This is because wideline segments are joined by using filled circles + whose radius is half the width of the line: the bump is a protruding + part of the circle that joins the end segment to the previous one. + +Links to programs listed above: +- Diamond Miner: + http://www.atarimania.com/game-atari-st-diamond-miner_31993.html +- Escape Paint: + http://www.pouet.net/prod.php?which=25328 +- FalcAMP: + http://deunstg.free.fr/sct1/falcamp/ +- Glücksrad: + http://www.atarimania.com/game-atari-st-glucksrad-st_22001.html +- Japlish: + http://www.ntrautanen.fi/marko/arkisto.atari.org/sivut/menu_pelit.htm +- Laserchess: + http://www.atarimania.com/game-atari-st-laserchess_31257.html +- Minigolf (GFA): + http://eerott.mbnet.fi/hatari/sources/minigolf.zip +- Swing: + http://www.pouet.net/prod.php?which=52370 +- The Ultimate Minesweeper: + http://www.pouet.net/prod.php?which=28904 +- VDI line/text tester: + http://eerott.mbnet.fi/hatari/programs.shtml#vditest + +(Links missing to: mathmaze.) diff --git a/ROMImages/AtariST/doc/changelog.txt b/ROMImages/AtariST/doc/changelog.txt new file mode 100644 index 000000000..d3246e237 --- /dev/null +++ b/ROMImages/AtariST/doc/changelog.txt @@ -0,0 +1,361 @@ +CHANGES BETWEEN RELEASE 0.9.9.1 AND RELEASE 0.9.10 + +Major changes: +- AES: Avoid unnecessary redraws by AES window manager +- AES: Fix shutdown bug in shel_write() +- BDOS: Improve BDOS write file performance +- BDOS: Improve BDOS sector caching algorithm +- BDOS: Avoid unnecessary directory sector writes in BDOS +- BDOS: Improve Fsnext() performance +- BIOS: Add SCSI support for TT and Falcon +- BIOS: Implement support for ET4000 graphics card +- BIOS: Implement automatic verify for floppy writes +- BIOS: Improve IDE data transfer speed +- BIOS: Improve TT RAM size detection for Storm cards +- BIOS: Fix reboot loop if Ctrl+Alt+Del held down +- EmuCON: Allow resolution change in EmuCON +- EmuDesk: Clean up if EmuDesk terminates abnormally +- EmuDesk: Fix bug in EmuDesk copy function +- EmuDesk: Fix EmuDesk out-of-sequence redraws +- EmuDesk: Make EmuDesk menu for icon/text selection like Atari TOS +- VDI: Improve the appearance of VDI curved lines + +Other changes: +- AES: Do not set the scrap directory in appl_init() +- AES: Do not validate the path supplied to scrp_write() +- AES: Fix appl_tplay() +- AES: Fix appl_trecord() +- AES: Fix bug in setting application directory +- AES: Fix file selector bug +- AES: Handle SHADOWED correctly for form_center() +- AES: Make form_center() behave like Atari TOS +- AES: Preserve DTA address across shel_find() +- BDOS: Increase max length of fully-qualified filename +- BIOS: Fix bug in VT52 emulation +- BIOS: Fix bug in rsconf handling for SCC +- BIOS: Fix bugs in keyboard mouse emulation +- BIOS: Fix end-of-partition test in Rwabs() +- BIOS: Fix screen corruption on some (Mega)STe systems +- BIOS: Improve FAT12/FAT16/FAT32 detection +- BIOS: Increase default keyboard auto-repeat speed +- BIOS: Remove IDE delay on Amiga +- BIOS: Remove unneeded delay when accessing the FDC +- EmuDesk: Allow 'Show item' to handle multiple items +- EmuDesk: Fix bug in EmuDesk change resolution handling +- EmuDesk: Fix bug in EmuDesk copy process when disk is full +- EmuDesk: Fix display bug in EmuDesk initialisation +- EmuDesk: Fix EmuDesk mouse cursor initialisation +- EmuDesk: Fix label bug when formatting floppy +- EmuDesk: Fix 'name conflict' bug in copy/move folders +- LineA: Fix bug that affected Aegis Animator +- LineA: Implement early abort for lineA seedfill() +- VDI: Fix bug in v_opnvwk() +- VDI: Fix contourfill() for 8 planes +- VDI: Fix design bug in VDI workstation creation +- XBIOS: Fix crash if Vsetscreen() sets TrueColor mode +- XBIOS: Improve performance of Flopver() +- The usual source code cleanup and minor bug fixes + + +CHANGES BETWEEN RELEASE 0.9.9 AND RELEASE 0.9.9.1 + +There was only one change, to fix a major bug in EmuDesk: if a desktop +shortcut for a file/folder was dragged to the trash or a disk icon or +an open window, then all the folders at the same level as the selected +file/folder were included in the operation, causing unwanted deletes/ +moves/copies. + + +CHANGES BETWEEN RELEASE 0.9.8 AND RELEASE 0.9.9 + +Major changes: +- AES: Allow mouse cursors to be loaded at boot time +- EmuDesk: Add 'Desktop configuration' dialog +- EmuDesk: Allow configuration of window/desktop backgrounds +- EmuDesk: Allow desktop window file mask to be specified +- EmuDesk: Omit unused desktop menu items +- EmuDesk: Open new window with Alt+doubleclick on folder +- General: Automatically build snapshot releases when a commit is pushed +- VDI: Add blitter support for horizontal line drawing +- VDI: Add blitter support for filled rectangle drawing +- VDI: Add blitter support for raster graphics + +Other changes: +- AES: Add growbox/shrinkbox effects to form_dial() +- AES: Allow AES USERDEFs to clobber a2/d2 (improve compatibility) +- AES: Call dsptch() on every AES call (improve responsiveness) +- AES: Ensure all DAs see AC_CLOSE before app exits +- AES: Fix problem with mouse clicks being ignored +- AES: Improve mouse cursor images +- AES: Only wait for interrupts when nobody is ready to run +- AES: Replace icons used in alerts +- BIOS: Do not use stack until memory is initialized +- BIOS: Ensure ST MMU register contains valid value +- BIOS: Ensure GetBPB() returns NULL for non-FAT partitions +- BIOS: Fix Mega STe boot problem +- BIOS: Fix XHDrvMap() to return correct value +- BIOS: Fix bug in memset/bzero clearing only 16MB at most +- BIOS: Implement XHDOSLimits (read only) +- BIOS: Amiga/Vampire V2: do not enable Fast IDE by default +- EmuDesk: Add blitter menu item to desktop +- EmuDesk: Add support for desktop drag-and-drop in window +- EmuDesk: Allow any character as date separator +- EmuDesk: Allow copy/move to desktop shortcut for a folder +- EmuDesk: Always issue alert if no windows are available +- EmuDesk: Do not open desktop directory if error occurs +- EmuDesk: Dragging to desktop shortcut for program now launches it +- EmuDesk: Fix 'Install application' bug w/ desktop shortcut +- EmuDesk: Fix alignment of desktop icons on a grid +- EmuDesk: Fix bug: desktop didn't open window for empty drives +- EmuDesk: Fix default dir for programs launched from desktop +- EmuDesk: Fix tail passed by desktop to shel_write() +- EmuDesk: Highlight file shortcut when dropping file on it +- EmuDesk: Improve launching of programs via desktop shortcut +- EmuDesk: Include wildcard spec in desktop window name +- EmuDesk: Make the desktop shel_write() the full pathname +- VDI: Add support for lineA TextBlt write modes 4-19 +- VDI: Fix VDI crash when running MiNT + memory protect +- VDI: Fix crash when font scaling in lineA text_blt() +- VDI: Handle bad rotation value in gdp_justified() +- VDI: Translate text_blt() high level code to C +- XBIOS: Fix EsetColor() when color < 0 +- The usual source code cleanup and minor bug fixes + + +CHANGES BETWEEN RELEASE 0.9.7 AND RELEASE 0.9.8 + +Major changes: +- Amiga: New boot floppy target +- Amiga: Rewrite floppy routines +- Amiga: Support multiple video modes +- BIOS: Autodetect IDE interface with twisted cable at run-time +- EmuDesk: Add support for desktop shortcuts +- EmuDesk: Add support for formatting floppies +- EmuDesk: Add support for user-assignable desktop icons + +Other changes: +- AES: Adjust file selector scroll bar width according to resolution +- AES: Allocate Alt-RAM instead of ST-RAM where possible +- AES: Do not use shel_find() to find autorun program +- AES: Fix bug in rsrc_load() that affected PixArt4 +- AES: Fix error message if autorun program is not found +- AES: Fix possible data corruption when launching accessories +- AES: Increase min height of slider in file selector +- Amiga: Add support for IKBD keyboard/mouse/joysticks on RS-232 +- Amiga: Fix interlaced display with fast CPU +- Amiga: Add target to build ROM optimized for Vampire V2 +- Amiga: Add XBIOS joystick support +- Amiga: Improve IDE performance on Vampire V2 +- Amiga: Improve IDE support +- Amiga: Add proper floppy media change support +- BDOS: Allow environment to be allocated in Alt-RAM +- BDOS: Fix bug in updating date when month rolls over +- BDOS: Fix Fsfirst(): wrong name format in DTA for label +- BDOS: Speed up Dfree() for 16-bit FATs +- BIOS: Add movep emulation for 68060 +- BIOS: Enable data cache on 68040 & 68060 +- BIOS: Enable instruction & branch caches on 68060 +- BIOS: Fix ACSI bug: non-word-aligned transfers failed +- BIOS: Fix bug in IDE detection of slower devices +- BIOS: Fix crash with unaligned IDE R/W buffer on 68000 +- BIOS: Fix floppy bug: non-word-aligned I/Os failed +- BIOS: Improve IDE performance +- BIOS: Improve mediachange detection +- ColdFire: Add RAM TOS target for ColdFire Evaluation Boards +- EmuDesk: Add documentation for new features +- EmuDesk: Add read-only indicator for show-as-text display +- EmuDesk: Allocate Alt-RAM instead of ST-RAM where possible +- EmuDesk: Fix various bugs in desktop copy/move +- EmuDesk: Handle desktop move/copy of folder to itself +- EmuDesk: Holding Control at startup now bypasses all initialisation files +- EmuDesk: Lookup desktop shortcuts directly in menu +- EmuDesk: Make alt-X open the root of X in a window +- EmuDesk: Make desktop keyboard shortcuts use Ctrl modifier +- EmuDesk: Make desktop shortcut keys work for all keyboards +- EmuDesk: Split preferences dialog to allow longer text +- General: Allow EmuTOS static RAM to be allocated in Alt-RAM +- The usual source code cleanup and minor bug fixes + + +CHANGES BETWEEN RELEASE 0.9.6 AND RELEASE 0.9.7 + +Major changes: +- BIOS: add support for extended MBR partitions +- BIOS: add support for MonSTer board +- BIOS: configure & size ST-RAM on TT +- BIOS: add support for Eiffel on CAN bus on ColdFire EVB +- BIOS: add _5MS cookie to support FreeMiNT on non-Atari hardware +- BIOS: add support for Apollo Core 68080 +- BDOS: set archive flag when file is created/modified +- EmuDesk: allow disk delete via desktop File menu item +- EmuDesk: implement desktop 'Install devices' +- EmuDesk: implement desktop 'Install icon' +- EmuDesk: implement desktop 'Remove desktop icon' +- EmuDesk: rewrite 'Install application' +- EmuCON2: provide a standalone version of EmuCON2 + +Other changes: +- AES: allow autorun program to start in character mode +- AES: fix bug when File Selector accesses empty drive +- AES: fix loop in file selector if filemask is too long +- AES: fix bug: the file selector modified the DTA pointer +- AES: rewrite wildcmp() to fix bug +- BDOS: fix GEMDOS standard handle numbers +- BDOS: rewrite Fsfirst/Fsnext to fix design problem +- BDOS: use single pool for all internal memory requests +- BDOS: fix I/O status for redirected character devices +- BDOS: fix date stamp in . and .. directory entries +- BDOS: fix return code for Fsfirst() +- BDOS: make EmuTOS respect user-assigned FRB +- BDOS: make ctl-C interrupt Cconin +- BDOS: return EOF indicator on redirected char devices +- BDOS: validate attribute bits passed to Fattrib() +- BDOS: validate handles for Fseek()/Fread()/Fwrite()/Fdatime() +- BIOS: add Norwegian & Swedish keyboard support +- BIOS: add support for byte-swapped IDE cable (disabled by default) +- BIOS: allow configuration of max logical sector size +- BIOS: fix VDI->hardware colour calculation +- BIOS: fix os_conf value and usage in multilanguage ROMs +- BIOS: improve performance of Rwabs() on floppy disks +- BIOS: make Ikbdws()/Midiws() handle 'cnt' like Atari TOS +- BIOS: set density for read/write/format of HD floppies +- BIOS: fix boot on Amiga with 68000 CPU +- BIOS: fix RAM size with BaS_gcc on ColdFire EVB +- BIOS: fix _FPU cookie for 68060 without FPU +- BIOS: fix values returned by VgetRGB()/vq_color() +- EmuDesk: make desktop shift-click behave the same as TOS +- EmuDesk: prompt if folder name conflict during move/copy +- EmuDesk: make many desktop and AES dialogs more concise +- EmuDesk: fix desktop icon drag and drop positioning +- EmuDesk: allow 'Too many windows' alert to be issued +- EmuDesk: always issue extra alert if deleting entire disk +- EmuDesk: always keep part of the mover gadget onscreen +- EmuDesk: avoid unnecessary window refreshes +- EmuDesk: handle name conflict during copy like Atari TOS +- EmuDesk: support additional keys during "Show file" +- EmuDesk: add copyright year in EmuDesk about dialog +- General: display total RAM on welcome screen +- General: fix _drvbits tests for drives > P +- VDI: fix rectangle drawing errors +- VDI: fix bug: v_bar() draws perimeter wrongly +- VDI: fix vq_curaddress(), vs_curaddress() +- Lots of source code cleanup and minor bug fixes + + +CHANGES BETWEEN RELEASE 0.9.5 AND RELEASE 0.9.6 + +Major changes: +- AES: fix pattern problem in window title line +- AES: prevent crash when NVDI is installed +- BDOS: fix bug: memory allocated by a TSR could be freed +- BDOS: implement etv_term() +- BIOS: clean up pending IRQ from flopunlk(), fixes some ACSI problems +- BIOS: clear data cache after DMA read, fixes ACSI problem on TT +- BIOS: do not clear the free ST-RAM on startup +- BIOS: enable MIDI input +- BIOS: initialise DMA sound matrix on Falcon +- BIOS: fix Flopxxx XBIOS calls to work with FastRAM +- BIOS: fix floppy motor-on problem during initialisation +- BIOS: fix memory bank detection to work on ST and STe +- BIOS: prevent reset vector being called on cold reset +- EmuCON2: add 'mode' command +- EmuCON2: fix EmuCON crash if system call is intercepted +- EmuDesk: allow TT desktop to select TT medium res +- EmuDesk: fix bug: copy/move could target the wrong folder +- EmuDesk: fix display of numeric values in desktop dialogs +- EmuDesk: fix rubber-banding for text-mode desktop windows +- EmuDesk: hide Shutdown if the machine can't shutdown +- EmuDesk: improve desktop move performance by using rename if possible +- EmuDesk: change menu bar to be more like Atari TOS +- General: fix EmuTOS to run on real TT hardware +- General: merge boot.prg + ramtos.img into emutos.prg +- VDI: fully implement VDI support for TT video + +Other changes: +- AES: clean up if program fails to issue appl_exit() +- AES: fix loop when deleting non-existent object +- AES: fix handling of Delete key by objc_edit() +- AES: fix value returned by evnt_button()/evnt_multi() +- AES: reset the default drive on resolution change +- BDOS: fix volume label handling to conform to TOS standards +- BIOS: add new cookie _MCF to the cookiejar +- BIOS: add support for RTC on ICD AdSCSI Plus board +- BIOS: add support for TT MFP (MFP #2) +- BIOS: add support to run "reset-resident" code +- BIOS: allow EmuTOS floppy to boot other floppies +- BIOS: clear system variables if EmuTOS loads into RAM +- BIOS: fix console font height with Hatari extended video modes +- BIOS: fix ide_identify() on Amiga +- BIOS: fix NVRAM year on TT +- BIOS: fix return codes for dmasound functions on ST +- BIOS: fix return codes for TT shifter functions +- BIOS: fix some NVRAM reset problems +- BIOS: fix sound volume on TT +- BIOS: fix _screenpt processing for TT, Falcon +- BIOS: flush the data cache before a warm or cold reset +- BIOS: initialize the IKBD clock on first boot only +- BIOS: rewrite MegaST(e) real time clock handler +- EmuCON2: fix EmuCON welcome message for ST-Low +- EmuDesk: add 'No sort' to desktop sort options +- EmuDesk: add desktop shortcuts for scroll-by-page +- EmuDesk: ensure desktop menu bar fits within screen +- EmuDesk: fix display of volume label in disk info dialog +- EmuDesk: improve EMUDESK.INF error checking +- EmuDesk: show the emulated machine name on Hatari even with --natfeats yes +- General: always use STOP instruction in 'wait for interrupt' loops +- General: create valid filesystem with hidden EmuTOS image on auto-booting floppy +- General: do not wait for a key at EMUTOS.PRG startup +- General: pretend to be TOS 1.04 in 192k ROMs +- General: use country id as part of emutos.prg/emutos.st name +- VDI: fix v_curtext() +- VDI: implement vq_curaddress() +- VDI: improve performance of cursor display routine +- VDI: rewrite vr_trnfm() to fix bugs and save space +- Lots of source code cleanup and minor bug fixes + + +CHANGES BETWEEN RELEASE 0.9.4 AND RELEASE 0.9.5 + +Major changes: +- AES/BIOS: implement critical error handler +- BDOS: fix file not found issues with Steem hard disk emulation +- BDOS: implement Pexec mode 7 +- BIOS: add alt-arrow support (mouse actions via keyboard) +- BIOS: add dual keyboard support (for Greek/Russian keyboards) +- BIOS: allow user to specify boot partition at startup +- BIOS: allow EmuTOS to recover from program exceptions in user programs +- BIOS: auto-detect multiple IDE interfaces +- BIOS: fix detection of C: drive with Steem +- BIOS: fix early stack initialization on Amiga +- EmuDesk: improve text object alignment for translated strings +- VDI: add line-A flood fill; all line-A opcodes are now supported + +Other changes: +- AES: increase button spacing in alerts +- AES: increase AES stack size for ColdFire machines +- BDOS: evaluate TPAsize flags in Pexec processing +- BDOS: fix bug in cross-directory rename +- BIOS: use interrupts for serial console instead of polling +- BIOS: fix FPU detection: 68881/68882 are now differentiated correctly +- BIOS: fix delay_loop() timing for M548X machines +- BIOS: fix key repeat bug when entering values via alt-keypad +- BIOS: implement XHInqDriver() XHDI function +- BIOS: fix some XHDI return codes (EDRIVE and EWRPRO) +- BIOS: add explicit delay for parallel port strobe (fixes printing on fast systems) +- BIOS: fix nationality code in ROM header +- EmuCON2: translate text (note: some country codes use English by choice) +- EmuDesk: allow folders being displayed in an open window to be moved/deleted +- EmuDesk: allow desktop "rubber-banding" in all directions +- EmuDesk: display year as 4 digits where possible +- EmuDesk: use _IDT cookie for EmuDesk date/time formatting +- EmuDesk: fix bug in "sort by date" for directory display +- EmuDesk: allocate screen objects dynamically +- General: convert source code repository to Git +- General: implement error checking for translated alerts +- General: replace "make release-version" with "make version" +- VDI: ignore lineA variable 'multifill' for linea_rect() and linea_polygon() +- VDI: speed up drawing of horizontal lines by v_pline() +- VDI: fix lineA rectangle fill bugs +- VDI: fix gap in circle drawn by v_arc() +- VDI: fix VDI wideline display in 320x480 resolution +- Lots of source code cleanup and minor bug fixes diff --git a/ROMImages/AtariST/doc/emudesk.txt b/ROMImages/AtariST/doc/emudesk.txt new file mode 100644 index 000000000..300d88c8f --- /dev/null +++ b/ROMImages/AtariST/doc/emudesk.txt @@ -0,0 +1,295 @@ +A brief user's guide to the newer features of EmuDesk, the EmuTOS desktop +========================================================================= + +The current version of EmuDesk is based on the TOS 1 desktop, but with +many improvements inspired by the TOS 2/3/4 desktop, including: + +1) new menu items + . set file mask + . install icon + . install application + . install devices + . remove desktop icon + . desktop configuration + . blitter +Due to space limitations, the implementation of the above is somewhat +restricted in the 192K ROMs (see the detailed descriptions below). If +you make any changes to the desktop using the above features, you must +save the desktop to preserve the changes. + +2) other new features + . user-assignable icons + . user-assignable mouse cursors + . open disk window via keyboard shortcut + . desktop shortcuts +Due to space limitations, desktop shortcuts are not available in the +192K ROMs. Desktop shortcuts are preserved when you save the desktop. + + +Set file mask +============= +192K ROMs: +This is not available. + +Other ROMs: +This is used to change the file mask of the currently-topped window, to +control which files are displayed within the window. Note that folders +are always displayed; the mask affects the display of files only. The +default file mask when a window is opened is "*.*" + + +Install icon +============ +192K ROMs: +This may be used to associate a specific icon with a desktop item (disk +or trash). You may select an existing desktop item and click on "Install +icon...", or you may click on "Install icon..." with no item selected. +If you click on a window icon (file or folder), it will be ignored. + +Other ROMs: +This may be used to associate a specific icon with a desktop item (disk +or trash), or a window item (file or folder). You may select an existing +icon and click on "Install icon...", or you may click on "Install icon..." +with no item selected. In the latter case, you'll get a dialog requesting +you to select the type of icon (desktop or window). + +. Installing a desktop icon + You may select the type (drive or trash), the label (displayed beneath + it on the desktop), and the icon shape (use the up & down arrows to + scroll through the available shapes). In addition, for drives you can + select the drive letter. + +. Installing a window icon + If you pre-selected an icon, you may only change the shape of the icon + for that specific file or folder. If you did not pre-select an item, + you can select the files that the icon will apply to (standard TOS + wildcards may be used), the type of item (file or folder), and the icon + shape. In either case, to change the icon shape, use the up & down + arrows to scroll through the available shapes. + + +Install application +=================== +The basic purpose of "Install application..." is to link an application +to data files with a specified extension. After you have done this, when +you use the desktop to open a file with the specified extension, the +corresponding application is launched. For example, you could associate +all .TXT files with a text editor; then, double-clicking on a .TXT file +would automatically launch the editor. + +In addition, you can assign a function key to an application; pressing +the function key at the desktop will then launch the application. + +Finally, you can set "autoboot" for one application (only): this will +launch that application during system initialisation, immediately before +the desktop itself runs. + +To use "Install application...", highlight one or more applications and +click on "Install application...". In the dialog box, the application +name of the first application selected will be prefilled. The following +fields and buttons specify in detail how the application is run: + +. Arguments + If you need to pass information (in addition to the name of the data + file) to the application when it starts, you may specify it here. This + is typically only relevant to utility programs, and the information + needed will be in the application documentation. In most cases, you + should leave this blank. + +. Document type + This specifies the extension to associate with this application, for + example TXT or RSC, and is required. Wildcards are allowed. + +. Install as F__ + This specifies the function key that will launch the application; + values from 1 to 20 are allowed (11-20 are shift-F1 through shift-F10). + Leaving this blank is valid, and means that no function key will launch + the application. + +. Boot status + Select "Auto" to autoboot this application (see above). Since only one + autoboot application is allowed, if you set "Auto" for an application, + EmuTOS will automatically disable "Auto" for any existing autoboot + application. + +. Application type + Selecting TOS or TTP will launch the program in character mode; GEM or + GTP will launch the application in graphics mode. The appropriate + value will be prefilled according to the type of application selected, + and should not normally be changed. + +. Default dir + This specifies the default directory when the application is launched: + either the directory of the application itself, or the top window (i.e. + the directory of the data file). The one to choose depends on the + specific application. If the application has supporting files (such as + resource or help files), it typically will look for them in the default + directory. For such an application, you will need to specify a default + directory of "Application". Otherwise, specify "Window". + +. Parameter + When a program is launched due to it being an installed application, + the desktop provides the application with the name of the data file + that caused the launch: this is known as a parameter. In most cases, + the application expects that the full path of the data file will be + provided. Some (usually older) programs may expect the filename only. + Unless the application's documentation indicates otherwise, you should + normally try "Full path" first; if that does not work, you can try + "File name", although that may require you to modify the "Default dir" + specified above. + +At the bottom of the dialog box are the following exit buttons: + +. Install + Installs the application. You must save the desktop afterwards if you + want the change to be saved across boots. + +. Remove + Removes an existing installed application. You must save the desktop + afterwards if you want the change to be saved across boots. + +. Skip + Skips installing/removing the current application, and moves on to the + next one you specified. If you only specified one application, this + is the same as Cancel. + +. Cancel + Skip installing/removing all remaining applications. + + +Install devices +=============== +This automatically installs icons for all devices that are currently +known to GEMDOS (have an entry in _drvbits) and that do not currently +have an icon. If the device is A: or B:, a floppy icon is installed; +otherwise a hard disk icon is installed. + + +Remove desktop icon +=================== +This is used to remove a disk or trash icon. Highlight the icon you +wish to remove, and click on "Remove desktop icon". + + +Desktop configuration +===================== +192K ROMs: +This is not available. + +Other ROMs: +This is a simplified version of the corresponding Atari TOS menu item. +It allows you to specify the default directory and input parameter for +all applications that are not installed applications. See "Install +application" above, under 'Default dir' and 'Parameter', for further +information about these options. + + +Blitter +======= +This item allows you to enable or disable the use of the blitter by the +desktop. The item is greyed-out if the system does not have a blitter. + + +User-assignable icons +===================== +When EmuDesk starts, it looks for a file called EMUICON.RSC in the root +of the boot drive. This file should be a standard Atari resource file, +with at least eight icons. All icons in the file must be 32x32-pixel +monochrome icons. If the file is found, these icons are used for the +desktop and window displays; if not found, a standard set of eight +builtin icons is used instead. The builtin icons (or the first eight +of the loaded icons, if EMUICON.RSC is in use) have the following usage: + 0 hard drive + 1 floppy drive + 2 folder + 3 trash + 4 printer + 5 removable disk + 6 generic application icon + 7 generic document icon +Icons 8 and above can be used as you wish. + +Note that, for historical reasons, these assignments are different from +those used by Atari TOS, so if you have an equivalent RSC file that works +with Atari TOS, you will need to move the icons around to get the same +desktop display. + +A default EMUICON.RSC file (currently containing 41 icons) is shipped +with the release; the first 8 icons are the same as the builtin ones. +Also shipped is the corresponding EMUICON.DEF file for use by a resource +editor. You should be aware that each icon consumes about 300 bytes of +RAM, so if you are short of memory, avoid putting too many icons in +EMUICON.RSC. + + +User-assignable mouse cursors +============================= +When the AES starts, it looks for a file called EMUCURS.RSC in the root +of the boot drive. This file should be a standard Atari resource file, +containing 8 ICONBLKs; each ICONBLK is a container for a mouse cursor. +If the file is found, these cursors are used instead of the builtin +cursors. The usage is as described for the AES graf_mouse() call: + 0 arrow + 1 text cursor / i-beam + 2 busy bee / hourglass + 3 pointing hand + 4 flat hand + 5 thin cross + 6 thick cross + 7 outline cross + +A default EMUCURS.RSC file is shipped with the release; the mouse cursors +in it are the same as the builtin ones. Also shipped is the corresponding +EMUCURS.DEF file for use by a resource editor. + +NOTE: Because the mouse cursors are not really ICONBLKs (though they are +stored as such within the resource), editing them with a standard resource +editor is difficult. Thorsten Otto's ORCS resource editor has special +support for mouse cursors and is the recommended tool for modifying them. + + +Open disk window via keyboard shortcut +====================================== +You may now use a keyboard shortcut to display the root directory of a +drive in a new window. To display drive X, hold the Alt key down and +type X, e.g. Alt-C displays drive C, Alt-D displays drive D, and so on. +As in TOS2/3/4, these shortcuts are permanently assigned and cannot be +changed by the user. + +NOTE: unlike TOS2/3/4, shortcuts with the Ctrl modifier do NOT update +the drive assigned to the currently topped window; instead, they are +assigned to menu item shortcuts. At the moment, these assignments are +also permanent. + + +Desktop shortcuts +================= +You may now drag a file or folder to the desktop to create a desktop icon +that is a shortcut to the original file/folder: manipulating the icon +will have the same effect as manipulating the original file or folder. +For example, it may be opened, copied, or moved or deleted; it may have +an "Info/rename" performed on it. Currently, by design, the shortcut is +NOT updated automatically if the original file or folder is moved or +deleted. + +The name and shape of the shortcut icon itself may be modified by the +"Install icon" menu item; this does not change the name of the file or +folder that the icon points to. The shortcut icon may be deleted by the +"Remove icon" menu item. To preserve shortcut information across boots, +you must save the desktop. + +You may drag a file or folder to a desktop shortcut, with the following +results: +. dragging documents to a desktop shortcut for a folder will copy (or + move, if the control key is held down) them to the folder +. dragging documents to a desktop shortcut for a program will launch the + program, passing the full pathname of the first document +. dragging documents to a desktop shortcut for a non-executable file will + do nothing + +If you open a desktop shortcut that points to a file or folder that no +longer exists, an alert will be issued, giving you the choice of removing +the shortcut, locating the desired file or folder, or cancelling the +action. If you choose locate, a file selector will be displayed to +allow you to choose the desired file or folder, and the shortcut will be +updated with the new information. diff --git a/ROMImages/AtariST/doc/incompatible.txt b/ROMImages/AtariST/doc/incompatible.txt new file mode 100644 index 000000000..32f35cf79 --- /dev/null +++ b/ROMImages/AtariST/doc/incompatible.txt @@ -0,0 +1,227 @@ +Programs incompatible with EmuTOS +================================= +This is a list of programs that have program bugs or shortcomings that +prevent them from running properly with EmuTOS, and whose problem has +been definitively identified. It is mainly intended to prevent these +programs from being added to 'bugs.txt' in the future. + + +Program: STOS programs +---------------------- +Error 1: joystick and/or keyboard input doesn't work. + +Bug analysis: +STOS Basic compiler routines check for input using undocumented and +TOS-specific locations. Programs using these routines work only with +specific TOS versions, and not with EmuTOS. + +Workaround: +Use version of the program that has been fixed to work with modern TOS +versions. + +Error 2: STOS error message "Error #046, press any key" during startup. + +Bug analysis: +This is caused by a divide-by-zero error in vsm_height() when the +program is run from the AUTO folder. VDI initialisation does not occur +until after AUTO-folder programs have been run, so if a VDI function is +called by an AUTO-folder program, internal variables have not been +initialised. STOS tries to initialise these variables itself, based on +a built-in table of TOS-specific locations. These locations are invalid +for EmuTOS, so the required variables remain zero, causing the error. + +Workaround: +Move the program from the AUTO folder to the root of the drive, and run +it from there. While STOS programs will then start, most of them will +remain unusable due to error 1 above. + + +Program: old game using fixed addresses +--------------------------------------- +Error: panic during game startup. + +Bug analysis: +Several old, floppy-only games load their data into fixed memory +addresses, which won't work when EmuTOS has loaded something else +there. This can be detected by tracing programs' OS calls with Hatari +(--trace os_all) and checking the used addresses. For example, under +older EmuTOS versions, the Gods game demo (from an ST Format cover disk) +overwrote itself with its game data because of this, and crashed. + +Workarounds: +In some games this can be worked around by either using the cartridge +version of EmuTOS (which uses less memory) or by using a custom high-RAM +build of EmuTOS, that uses higher RAM addresses for loading programs and +for memory allocation: +make 512 UNIQUE=uk DEF="-DSTATIC_ALT_RAM_ADDRESS=0x00080000 -DCONF_WITH_FRB=0" + +However, this doesn't help with programs which also expect undocumented, +OS internal functions and variables to be at certain locations. The +best workaround is to use a version of the game that has been fixed to +run from HD and with modern TOS versions. + + +Program: Awele v1.01 +-------------------- +Error: mono desktop colours are inverted after exiting program. + +Bug analysis: +This version of Awele was compiled with PureC and linked with a very +early version of Windform. During WinDOM initialisation, malloc() is +called to allocate an area to save the palette in. However, although +malloc() returns the pointer in A0, the WinDOM code assumes it is in D0. +As a result, an area of low memory is pointed to, which is overwritten +during Awele execution. At program termination, the palette is restored +from the overwritten area, resulting in the error seen. + +Workaround: +Use v1.02 of the game. + + +Program: Cameleon +----------------- +Error 1: program exits immediately when 'Start game' is selected. + +Bug analysis: +The program requires an STe. In order to determine whether it is +running on an STe, it checks the contents of location 0x995 (hardcoded). +On Atari TOS, this is where TOS initialisation happens to store the _MCH +cookie but this is *not* how Atari says you should locate it (and it's +not where EmuTOS stores it). + +Error 2: program crashes with a Trace exception on any exit. + +Bug analysis: +During startup, the program switches to supervisor state via the Super() +system call. Subsequently, the supervisor stack overwrites the program's +user stack. On exit, the user stack pointer is restored and during this +a corrupted value is loaded into the SR, causing a trace excpetion. + + +Program: (VDI) Invaders and Anduril +----------------------------------- +Error: keys to move an object are ignored (in Invaders, the '-' key; in +Anduril, the 'h' & 'j' keys) + +Bug analysis: +Both programs were written by "M.Dheus" who found that the most recent +key input from the keyboard was stored at offset 0x6d from the address +returned by Kbdvbase(), and used that to read the keyboard. This was +never documented by Atari, but was apparently true for all versions of +TOS 1. However it is not true for TOS 2, 3, or 4 (or EmuTOS). + + +Program: Ramses +--------------- +Error: panic + +Bug analysis: +Program calls the Line A initialization $A00A and gets the routine +vectors in a2. It gets the address of _v_hide_c, then starts doing +undocumented things with the bytes of the actual routine: + https://sourceforge.net/p/emutos/mailman/message/30605378/ + + +Program: STVidPlay +------------------ +Error: "Error in getting file location" + +Bug analysis: +Program looks for a specific 2-byte sequence in the hard disk driver +code pointed to by hdv_rw ($476). If it doesn't find that sequence +within bytes 6-48 (or thereabouts) of the start of the driver, it +gives the error message. + + +Program: Cubase Lite +-------------------- +Error: panic + +Bug analysis: +On TOS 1.62 etv_timer vector is a delegate to an internal private +function. Cubase Lite tries to guess the address of that private +function in an undocumented way, which crashes on EmuTOS. (Somebody +could write a loader or TSR to change the etv_timer function so that +Cubase will not crash.) + + +Program: Reservoir Gods games (Bugger, Bunion, SkyFall, Sworm) +-------------------------------------------------------------- +Error: panic + +Bug analysis: +Games use an undocumented TOS4 vector for keyboard input instead of +accessing kbdvec correctly. This causes EmuTOS to panic. + +Workaround: +This can be worked around with the following hack.prg: + https://sourceforge.net/p/emutos/mailman/message/26841274/ + + +Program: OMIKRON.BASIC V3.01 (interpreter) +------------------------------------------ +Error: Panic (bus error) during start + +Bug analysis: +The program relies on undocumented internal TOS variables at several +points. First, it expects A0 upon return from Mediach (BIOS function) +to point to wplatch (floppy write protect latch variable). On EmuTOS +A0 is 0 and hence a bus error occurs when the program wants to modify +that variable. Second, it parses the bytes of the 0xA00A (hide cursor) +line-A routine to get the address of a variable reflecting the internal +state of the mouse cursor. This is done with the same code used in +"Ramses" (see above). This also fails on EmuTOS, resulting in another +bus error. There may be more accesses to undocumented variables. + + +Program: STSpeech v2.0 +---------------------- +Error: panics due to stack corruption + +Bug analysis: +The program installs a custom Timer A interrupt handler, and calls the +XBIOS from it. If the Timer A interrupt happens to occur just when an +unrelated BIOS/XBIOS call is manipulating _savptr (saving registers), +then the nested XBIOS call inside the Timer A handler will trash that +BIOS/XBIOS save area, possibly modifying the stack pointer. In the +"Hitchhiker's Guide to the BIOS", Atari documented a workaround for this, +but STSpeech does not use it. + +Workaround: +Because this problem is timing-dependent, it does not show up on Atari +TOS, and only shows up in EmuTOS if BigDOS is installed (BigDOS issues +many XBIOS calls). Use program without BigDOS, or anything else doing +a lot of XBIOS calls. + + +Program: Protracker v2 STE (Equinox version) +-------------------------------------------- +Error: crash when "DISK OP." button is selected + +Bug analysis: +The program relies on a TOS-specific code sequence, as follows: +1. it searches the ROM (pointed to by location 4) for the first word + equal to 0x47e +2. when found, it uses the longword immediately before as a pointer to + an address; in TOS2, this is a pointer to the mediachange handler +3. it stores the long at offset 0x1c from that address in its data + segment; in TOS2, this is a pointer to (I think) two bytes of + status for the floppy drives +Subsequently, when "DISK OP." is selected, the stored long is used as a +pointer. In TOS2, the value stored is $4216; in EmuTOS, it's zero, +resulting in a crash. + + +Program: Spectrum 512 +--------------------- +Error: crash during program initialisation + +Bug analysis: +The program relies on a TOS-specific code sequence, as follows: +1. it searches the VBL handler (pointed to by location $70) for the + first word containing a value of 0x6100 +2. when found, it uses the word immediately following as an index to + generate an address, and starts searching at that address for a + word containing a value of 0x8606 +Under EmuTOS, the address generated is a nonsense address which happens +to be odd, causing an immediate address error. diff --git a/ROMImages/AtariST/doc/license.txt b/ROMImages/AtariST/doc/license.txt new file mode 100644 index 000000000..82fa1daad --- /dev/null +++ b/ROMImages/AtariST/doc/license.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/ROMImages/AtariST/doc/status.txt b/ROMImages/AtariST/doc/status.txt new file mode 100644 index 000000000..596913c7d --- /dev/null +++ b/ROMImages/AtariST/doc/status.txt @@ -0,0 +1,583 @@ +This file documents the status of the individual parts of EmuTOS. + +Here is a quick list of supported emulators/hardware: +This table should be updated regularly. When indicating failure, +if possible add a line telling in which version it did run. +Unless otherwise specified in 'details', systems were tested using +a ROM version of EmuTOS. + + system | ok? | who | date | details + --------------------+-------+-----+-------------+---------------- + Emulators | | | | + ARAnyM 1.0.2 | yes | VRI | 17 Dec 2018 | + Hatari v2.1.0 | yes | VRI | 16 Dec 2018 | + Pacifist v0.48 | yes | LVL | winter 2001 | + SainT v2.40 | yes | (1) | 10 Dec 2017 | 192k ROM only + STeem SSE 3.9.4 | yes | VRI | 16 Dec 2018 | + STonC v0.8.1 | yes | LVL | 9 Feb 2003 | + STonX 0.6.7.6 | yes | THH | 14 Nov 2008 | + TOSBox 1.10a | no | ? | < Sep 2002 | + WinSTon V0.1r2 | no | ? | < Sep 2002 | + WinUAE 4.1.0 | yes | VRI | 19 Dec 2018 | + --------------------+-------+-----+-------------+---------------- + Atari & compatibles | | | | + STfm | yes | (1) | 23 Nov 2017 | + Mega ST | yes | (8) | 7 Dec 2018 | tested with PRG + STe | yes | (1) | 23 Nov 2017 | + Mega STe | yes | (2) | 26 Jun 2004 | only RAMTOS tested + TT030 | yes | RFB | 30 Nov 2018 | tested with PRG + Falcon030 | yes | (7) | 31 Dec 2016 | + Falcon030 | yes | RFB | 29 Nov 2018 | tested with PRG + Falcon030 + CT60 | yes | RFB | 29 Nov 2018 | tested with PRG + Suska III-C (2K15B) | yes | (5) | 23 Apr 2016 | + --------------------+-------+-----+-------------+---------------- + Other systems | | | | + Amiga Blizzard 1260 | yes | (3) | Aug 2012 | tested with BlizKick + Amiga 500 + Vampire | yes | VRI | 21 Dec 2018 | + Amiga 600 + Vampire | yes | (6) | Mar 2017 | + Amiga 1000 | yes | (4) | Jul 2012 | + FireBee | yes | VRI | 18 Dec 2018 | + M5484LITE | yes | VRI | 18 Dec 2018 | + + (1) reported by Christian Zietz + (2) reported to LVL by Frédéric Pécourt + (3) reported by Michaël Gibs + (4) reported by amiman99 + (5) reported by Markus Fröschle + (6) reported by Flype + (7) reported by Stefan Niestegge + (8) reported by Claude Labelle + +Now let's talk about the different subsystems, and what is implemented. +NOTE: the information in the following table may be somewhat dated. For +example, most GEMDOS/BIOS/XBIOS functions are known to work without +problems. + + +This is what the first field of the following table means: + +? status unknown +- Not yet implemented +> Partially implemented +X Fully implemented and untested +t Fully implemented and partially tested +T tested and working on an emulator or real hardware + + + Hardware initialization + ---------------------------------------------------------------------------- + + T CPU setting, tested on: 68000 (real & emu), 68030 (real & emu), 68040 (emu), + 68060 (real), 68080 (real), V4e (real) + T FPU + T 68030 MMU and cache initialization + T Memory controller (both ST and Falcon) + T DMA controller + T WD 1772 / AJAX Floppy disk controller + T MFP, MFP#2 + T PSG + T ST shifter + T STe shifter + T TT shifter + T VIDEL + T ACIAs, IKBD protocol + t MegaST Real-Time Clock (set clock not tested) + T NVRAM (including RTC) + T Blitter + T Microwire + t DMA sound + - DSP + T SCC + T IDE + T ACSI + T SCSI + T SD/MMC + T NatFeats (a framework for native features on emulators) + + + BOOT sequence + ---------------------------------------------------------------------------- + + T configure memory + T execute reset routine + T detect monitor type + T detect graphics resolution + T detect processor type, FPU type and hardware features + T setup a cookie jar with system cookies + ... + T init floppy drives + T boot floppy + t boot DMA (note it does not work with e.g. AHDI) + T execute reset-resident prgs: undocumented TOS feature, disabled by default + T run AUTO prgs + T run 'command.prg' + T run the default shell, EmuCON + T run the GEM desktop + + + BIOS devices + ---------------------------------------------------------------------------- + + t 0 PRN: parallel port + t 1 AUX: default serial port + T 2 CON: console (screen) + T 3 MIDI + T 4 IKBD + T 5 raw screen + T 6 ST-compatible serial port + T 7 SCC channel B + T 8 TT-MFP serial port + T 9 SCC channel A + + + ACIA interrupt handlers + ---------------------------------------------------------------------------- + + - midierr + - ikbderr + t midi input + T ikbd key events + T IKBD clock + T mouse + t joysticks + + + BIOS Functions + ---------------------------------------------------------------------------- + + T 0x00 Getmpb + T 0x01 Bconstat + T 0x02 Bconin + T 0x03 Bconout + T 0x04 Rwabs + T 0x05 Setexc + T 0x06 Tickcal + T 0x07 Getbpb + T 0x08 Bcostat + T 0x09 Mediach + T 0x0a Drvmap + T 0x0b Kbshift + + + XBIOS Functions + ---------------------------------------------------------------------------- + +All XBIOS versions: + X 0x00 Initmous + - 0x01 Ssbrk (useless - will not be implemented) + T 0x02 Physbase + T 0x03 Logbase + T 0x04 Getrez + T 0x05 Setscreen + T 0x06 Setpalette + T 0x07 Setcolor + T 0x08 Floprd + T 0x09 Flopwr + T 0x0a Flopfmt + - 0x0b Dbmsg (useless - will not be implemented) + T 0x0c Midiws + X 0x0d Mfpint + X 0x0e Iorec + T 0x0f Rsconf + T 0x10 Keytbl + T 0x11 Random + T 0x12 Protobt + T 0x13 Flopver + - 0x14 Scrdmp + T 0x15 Cursconf + T 0x16 Settime + T 0x17 Gettime + T 0x18 Bioskeys + T 0x19 Ikbdws + T 0x1a Jdisint + T 0x1b Jenabint + T 0x1c Giaccess + T 0x1d Offgibit + T 0x1e Ongibit + T 0x1f Xbtimer + T 0x20 Dosound + - 0x21 Setprt (useless - will not be implemented) + X 0x22 Kbdvbase + T 0x23 Kbrate + - 0x24 Prtblk (useless - will not be implemented) + T 0x25 Vsync + T 0x26 Supexec + - 0x27 Puntaes (useless - will not be implemented) + +TOS v1.02: + T 0x29 Floprate + T 0x40 Blitmode + +TOS v2.0: + t 0x2a DMAread + t 0x2b DMAwrite + t 0x2c Bconmap + +TOS v3.00: + T 0x2e NVMaccess + + t 0x50 EsetShift (for TT shifter only) + t 0x51 EgetShift (for TT shifter only) + t 0x52 EsetBank (for TT shifter only) + t 0x53 EsetColor (for TT shifter only) + t 0x54 EsetPalette (for TT shifter only) + t 0x55 EgetPalette (for TT shifter only) + t 0x56 EsetGray (for TT shifter only) + t 0x57 EsetSmear (for TT shifter only) + +TOS v4.00: + t 0x58 Vsetmode (for Falcon Videl only) + t 0x59 Vmontype (for Falcon Videl only) + t 0x5a VsetSync (for Falcon Videl only) + t 0x5b VgetSize (for Falcon Videl only) + + t 0x5d VsetRGB (for Falcon Videl only) + t 0x5e VgetRGB (for Falcon Videl only) + + - 0x96 VsetMask (for Falcon Videl only) + +2nd bit in _SND is set: + t 0x80 LockSnd + t 0x81 UnlockSnd + t 0x82 Soundcmd + t 0x83 Setbuffer + t 0x84 Setmode + t 0x85 Settracks + t 0x86 Setmontracks + t 0x87 Setinterrupt + t 0x8c Sndstatus + +3rd bit in _SND is set: + t 0x88 Buffoper + t 0x8a Gpio + t 0x8b Devconnect + t 0x8d Buffptr + +3&4 bits in _SND are set: + t 0x89 Dsptristate + +5th bit in _SND is set: + - 0x60-0x7F, 32 Dsp_* functions + +TOS v4 extended XBIOS functionality: + - 16-bit Videl resolution setting + + + GEMDOS Functions + ---------------------------------------------------------------------------- + +All GEMDOS versions: + T 0x00 Pterm0 + T 0x01 Cconin + T 0x02 Cconout + T 0x03 Cauxin + T 0x04 Cauxout + T 0x05 Cprnout + T 0x06 Crawio + T 0x07 Crawin + T 0x08 Cnecin + T 0x09 Cconws + T 0x0a Cconrs + T 0x0b Cconis + + T 0x0e Dsetdrv + + T 0x10 Cconos + T 0x11 Cprnos + T 0x12 Cauxis + T 0x13 Cauxos + + T 0x19 Dgetdrv + T 0x1a Fsetdta + + T 0x20 Super + + T 0x2a Tgetdate + T 0x2b Tsetdate + T 0x2c Tgettime + T 0x2d Tsettime + + T 0x2f Fgetdta + T 0x30 Sversion + T 0x31 Ptermres + + T 0x36 Dfree + + T 0x39 Dcreate + T 0x3a Ddelete + T 0x3b Dsetpath + T 0x3c Fcreate + T 0x3d Fopen + T 0x3e Fclose + T 0x3f Fread + T 0x40 Fwrite + T 0x41 Fdelete + T 0x42 Fseek + T 0x43 Fattrib + + T 0x45 Fdup + T 0x46 Fforce + T 0x47 Dgetpath + T 0x48 Malloc + T 0x49 Mfree + T 0x4a Mshrink + T 0x4b Pexec + T 0x4c Pterm + + T 0x4e Fsfirst + T 0x4f Fsnext + + T 0x56 Frename + T 0x57 Fdatime + +GEMDOS v0.19 (TOS v2): + T 0x14 Maddalt + T 0x44 Mxalloc + (and Pexec mode 6) + + + Line-A functions + ---------------------------------------------------------------------------- + T $0 - Initialization + t $1 - Put pixel + t $2 - Get pixel + t $3 - Arbitrary line + t $4 - Horizontal line + t $5 - Filled rectangle + t $6 - Filled polygon (see bugs.txt) + t $7 - Bit block transfer (may miss options not needed by VDI) + t $8 - Text block transfer + T $9 - Show mouse + T $A - Hide mouse + t $B - Transform mouse + t $C - Undraw sprite + t $D - Draw sprite + t $E - Copy raster form + t $F - Seedfill + + + VDI functions + ---------------------------------------------------------------------------- + + All TOS 1.0 calls are implemented. + + T v_opnwk + X v_clswk + T v_opnvwk + T v_clsvwk + T v_clrwk + - v_updwk + > vst_load_fonts (needs GDOS or equivalent) + > vst_unload_fonts (needs GDOS or equivalent) + t vs_clip + T v_pline + T v_pmarker + T v_gtext + T v_fillarea + - v_cellarray (not supported by any current VDI driver) + T v_contourfill + T vr_recfl + T v_bar + T v_arc + T v_pieslice + T v_circle + T v_ellipse + T v_ellarc + T v_ellpie + X v_rbox + T v_rfbox + T v_justified + T vswr_mode + > vs_color + T vsl_type + X vsl_udsty + T vsl_width + > vsl_color + T vsl_ends + T vsm_type + T vsm_height + > vsm_color + T vst_height + T vst_point + T vst_rotation + X vst_font + > vst_color + T vst_effects + T vst_alignment + T vsf_interior + T vsf_style + t vsf_color + T vsf_perimeter + X vsf_udpat + t vro_cpyfm + > vrt_cpyfm + T vr_trnfm + > v_get_pixel + X vsin_mode + X vrq_locator + X vsm_locator + - vrq_valuator + - vsm_valuator + X vrq_choice + X vsm_choice + X vrq_string + X vsm_string + X vsc_form + X vex_timv + T v_show_c + T v_hide_c + X vq_mouse + T vex_butv + T vex_motv + T vex_curv + X vq_key_s + t vq_extnd + > vq_color + > vql_attributes + > vqm_attributes + > vqf_attributes + > vqt_attributes + > vqt_extent + X vqt_width + X vqt_name + - vq_cellarray (not supported by any current VDI driver) + X vqin_mode + X vqt_fontinfo + T vq_chcells + T v_exit_cur + T v_enter_cur + T v_curup + T v_curdown + T v_curright + T v_curleft + T v_curhome + T v_eeos + T v_eeol + T vs_curaddress + T v_curtext + T v_rvon + T v_rvoff + T vq_curaddress + T vq_tabstatus + - v_hardcopy + T v_dspcur (Atari docs are incorrect for this call) + T v_rmcur (Atari docs are incorrect for this call) + - v_form_adv + - v_output_window + - v_clear_disp_list + - v_bit_image + - vs_palette + - vqp_films + - vqp_state + - vsp_state + - vsp_save + - vsp_message + - vqp_error + - v_meta_extents + - v_write_meta + - vm_filename + +TOS v4 extended VDI functionality: + - 16-bit support for graphics functions (for now, use fVDI) + + + AES functions + ---------------------------------------------------------------------------- + +All AES versions: + t appl_init + X appl_read + X appl_write + t appl_find + t appl_tplay + X appl_trecord + X appl_yield (PC-GEM call) + t appl_exit + X evnt_keybd + t evnt_button + X evnt_mouse + t evnt_mesag + X evnt_timer + t evnt_multi + X evnt_dclick + t menu_bar + t menu_icheck + X menu_ienable + X menu_tnormal + X menu_text + t menu_register + X menu_unregister (PC-GEM call) + X menu_click (PC-GEM call) + X objc_add + X objc_delete + t objc_draw + X objc_find + t objc_offset + X objc_order + X objc_edit + t objc_change + t form_do + t form_dial + t form_alert + X form_error + t form_center + X form_keybd + X form_button + t graf_rubberbox + X graf_dragbox + X graf_mbox + T graf_growbox + T graf_shrinkbox + X graf_watchbox + X graf_slidebox + t graf_handle + t graf_mouse + t graf_mkstate + X scrp_read + X scrp_write + X scrp_clear (PC-GEM call) + X fsel_input + t wind_create + t wind_open + t wind_close + t wind_delete + t wind_get + X wind_set + X wind_find + t wind_update + t wind_calc + t rsrc_load + t rsrc_free + t rsrc_gaddr + X rsrc_saddr + t rsrc_obfix + X shel_read + X shel_write + X shel_get + X shel_put + T shel_find + X shel_envrn + X shel_rdef (PC-GEM call) + X shel_wdef (PC-GEM call) + +AES v1.40 (TOS >= v1.04): + T fsel_exinput + t wind_new + +AES v3.30 (TOS > v3.06): + - menu_attach + - menu_istart + - menu_popup + - menu_settings + +AES v3.40 (TOS >= v4): + - objc_sysvar (3D look) + +TOS v4 extended AES functionality: + - RSC file color icon support + + + Misc desktop functions + ---------------------------------------------------------------------------- + + - Cartridge file system support (useless - will not be implemented) diff --git a/ROMImages/AtariST/doc/todo.txt b/ROMImages/AtariST/doc/todo.txt new file mode 100644 index 000000000..7aa0fbc50 --- /dev/null +++ b/ROMImages/AtariST/doc/todo.txt @@ -0,0 +1,25 @@ +General + +BIOS/XBIOS +- implement missing XBIOS calls (e.g. Falcon DSP functions) +- reduce the number of 'extern' in .c files +- check that RAM disks work +- misc. TODOs in floppy.c +- implement full XHDI 1.30 +- let EmuTOS set up correct MMU tree on 68040 (?) + +BDOS (GEMDOS) +- move mem-only routines out of proc.c into umem.c or iumem.c + +VDI +- The linemask for dashed lines is not calculated correct, depending on + the internal calculations of increase in x direction, like in original TOS. + +AES +- Implement AES v3.x functions (TOS v3 menu functions and v4 3D look) + +DESK +- Add a dialog for configuring the NVRAM (keyboard, languages, ...) +- Support for loading DESKTOP.INF (needs remapping of the icon indexes) + +CLI diff --git a/ROMImages/AtariST/doc/xhdi.txt b/ROMImages/AtariST/doc/xhdi.txt new file mode 100644 index 000000000..8f1287017 --- /dev/null +++ b/ROMImages/AtariST/doc/xhdi.txt @@ -0,0 +1,22 @@ +FUNCTION XHDI VERSION SUPPORTED BY EMUTOS +---------------------------------------------------------------------- +XHGETVERSION 0 ALL YES +XHINQTARGET 1 ALL YES +XHRESERVE 2 ALL NO +XHLOCK 3 ALL NO +XHSTOP 4 ALL NO +XHEJECT 5 ALL NO +XHDRVMAP 6 ALL YES +XHINQDEV 7 ALL YES +XHINQDRIVER 8 ALL YES +XHNEWCOOKIE 9 OPTIONAL YES +XHREADWRITE 10 ALL YES +XHINQTARGET2 11 >= 1.01 YES +XHINQDEV2 12 >= 1.10 YES +XHDRIVERSPECIAL 13 OPTIONAL NO +XHGETCAPACITY 14 OPTIONAL YES +XHMEDIUMCHANGED 15 OPTIONAL NO +XHMINTINFO 16 OPTIONAL NO +XHDOSLIMITS 17 OPTIONAL YES +XHLASTACCESS 18 >= 1.25 NO +XHREACCESS 19 >= 1.25 NO diff --git a/ROMImages/AtariST/etos192uk.img b/ROMImages/AtariST/etos192uk.img new file mode 100644 index 000000000..4df163713 Binary files /dev/null and b/ROMImages/AtariST/etos192uk.img differ diff --git a/ROMImages/AtariST/readme.txt b/ROMImages/AtariST/readme.txt new file mode 100644 index 000000000..2a40ac2d3 --- /dev/null +++ b/ROMImages/AtariST/readme.txt @@ -0,0 +1,7 @@ +THIS EMULATOR DOES NOT EMULATE THE ATARI ST. + +Included here are Atari ST-targeted versions of EmuTOS, being a significant chunk of freely available and redistributable 68000 code that can be used to test the 68000-in-progress. + +EmuTOS is distributed under the GPL. See its licence and other information within the doc/ subdirectory. + +It was obtained via http://emutos.sourceforge.net/en/ \ No newline at end of file diff --git a/ROMImages/SinclairQL/js.rom b/ROMImages/SinclairQL/js.rom new file mode 100644 index 000000000..a63c3d3da Binary files /dev/null and b/ROMImages/SinclairQL/js.rom differ diff --git a/ROMImages/SinclairQL/readme.txt b/ROMImages/SinclairQL/readme.txt new file mode 100644 index 000000000..0c6246d6b --- /dev/null +++ b/ROMImages/SinclairQL/readme.txt @@ -0,0 +1,5 @@ +Supplied files: + +js.rom + +This file is copyright Amstrad but non-profit redistribution is permitted. \ No newline at end of file