1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-10-25 09:27:01 +00:00

Compare commits

...

170 Commits

Author SHA1 Message Date
Thomas Harte
11228dc265 Merge pull request #937 from TomHarte/XKeySyms
Eliminate magic constants in Qt/X11 keyboard code.
2021-05-05 22:21:31 -04:00
Thomas Harte
ef50967793 Limit X11 linkage to Linux. 2021-05-05 22:17:24 -04:00
Thomas Harte
5f6c08b7e0 Avoid partial struct instantiation. 2021-05-05 22:00:50 -04:00
Thomas Harte
6cb23ec5be Tidy up and comment. 2021-05-05 21:58:54 -04:00
Thomas Harte
1bae70bcf8 Correct capitalisation. 2021-05-05 21:49:01 -04:00
Thomas Harte
9820591ba4 Corrects enum references. 2021-05-05 21:46:34 -04:00
Thomas Harte
77071b3c69 Adds KeySym -> key lookup. 2021-05-05 21:41:59 -04:00
Thomas Harte
335e839b31 Wrangles a single working call to XKeysymToKeycode. 2021-05-05 21:35:08 -04:00
Thomas Harte
6fe947b8b9 Fix class name, add constructor. 2021-05-05 19:17:23 -04:00
Thomas Harte
22b29e77a7 Add keyboard.cpp/h to the Qt project. 2021-05-05 19:06:25 -04:00
Thomas Harte
4858cfce6b Starts to factor out the keyboard mapper.
The more easily to clarify as to #includes, etc, and to allow for a relevant constructor.
2021-05-05 18:56:10 -04:00
Thomas Harte
8da3e91f5e Merge branch 'master' into XKeySyms 2021-05-03 22:23:55 -04:00
Thomas Harte
012235bfeb Merge pull request #936 from TomHarte/Style
Correct minor style errors.
2021-05-03 22:23:27 -04:00
Thomas Harte
052e284c33 Add overt fallthrough. 2021-05-03 22:17:43 -04:00
Thomas Harte
32e3dd71b1 Be overt in empty std::string construction. 2021-05-03 22:17:32 -04:00
Thomas Harte
95f4272919 Make sure size_t is visible. 2021-05-03 22:17:25 -04:00
Thomas Harte
00679b6135 t may be unused, per the if constexpr. 2021-05-03 22:17:19 -04:00
Thomas Harte
2c18bb4508 Make it overt that this can't return without a value. 2021-05-03 22:17:12 -04:00
Thomas Harte
0cf1c9040a Add missing fallthrough declaration. 2021-05-03 22:17:06 -04:00
Thomas Harte
9196341482 Retrenches: it seems nativeVirtualKey does what I want.
Hooray!
2021-05-03 21:45:53 -04:00
Thomas Harte
685140a4c2 Correct Qt -> QT. 2021-05-03 21:18:14 -04:00
Thomas Harte
1465b0ee4d Shunt X11 code to bottom of file, to avoid #include interference. 2021-05-03 21:15:20 -04:00
Thomas Harte
0bf6b765d3 Further namespace/name corrections. 2021-05-03 21:11:47 -04:00
Thomas Harte
4774676e2a Correct keypad symbols, push X11 into a namespace. 2021-05-03 21:09:01 -04:00
Thomas Harte
9c29655da2 Add x11extras as per use of <QX11Info>. 2021-05-03 20:43:22 -04:00
Thomas Harte
c8ab18f2b6 Add overt fallthrough. 2021-05-03 20:38:50 -04:00
Thomas Harte
8ebce466db Be overt in empty std::string construction. 2021-05-03 20:35:23 -04:00
Thomas Harte
1b39b17125 Make sure size_t is visible. 2021-05-03 20:33:25 -04:00
Thomas Harte
5a46853075 t may be unused, per the if constexpr. 2021-05-03 20:32:16 -04:00
Thomas Harte
48ad4d4c4c Make it overt that this can't return without a value. 2021-05-03 20:31:39 -04:00
Thomas Harte
056a036712 Add missing fallthrough declaration. 2021-05-03 20:31:13 -04:00
Thomas Harte
70eaa79108 Makes an attempt to use X11 KeySyms.
Rather than hard-coding a mapping.
2021-05-03 18:51:58 -04:00
Thomas Harte
c906dc3c0a Merge pull request #935 from TomHarte/OricJoystick
Adds Altai-style joystick support for the Oric.
2021-04-29 20:15:42 -04:00
Thomas Harte
d1dcb41b6f Adds Altai-style joystick support. 2021-04-29 18:29:29 -04:00
Thomas Harte
96ac86a757 Merge pull request #934 from TomHarte/OricTapes
Relaxes Oric .tap signature check.
2021-04-29 18:14:36 -04:00
Thomas Harte
4919786825 Relaxes Oric .tap signature check. 2021-04-29 18:00:02 -04:00
Thomas Harte
24b4185714 Merge pull request #933 from TomHarte/SpectrumJoystick
Adds ZX Spectrum joystick support.
2021-04-28 21:08:36 -04:00
Thomas Harte
ad10d0037a Inverts the Game Controller Framework value of the y axis. 2021-04-28 20:31:35 -04:00
Thomas Harte
b6554c8255 Adds joystick support. 2021-04-28 20:19:01 -04:00
Thomas Harte
01dc83d0d6 Merge pull request #932 from MaddTheSane/xcodemaintenance
Xcode maintenance.
2021-04-27 19:53:51 -04:00
C.W. Betts
2fd08789ab Xcode maintenance. 2021-04-27 12:50:26 -06:00
Thomas Harte
bc9e529995 Merge pull request #931 from TomHarte/FieldName
This field is counted in half-cycles.
2021-04-26 21:33:38 -04:00
Thomas Harte
708c24cc57 This field is counted in half-cycles. 2021-04-26 21:20:32 -04:00
Thomas Harte
7fb3048257 Update AllDisk and AllTape. 2021-04-26 21:04:25 -04:00
Thomas Harte
9319f0525a Merge pull request #930 from TomHarte/SZX
Adds SZX support.
2021-04-26 20:57:06 -04:00
Thomas Harte
b7a62e0121 Adds SZX support.
Tweaking exposed Spectrum state object as relevant.
2021-04-26 20:47:28 -04:00
Thomas Harte
bd5dd9b9a3 Merge pull request #929 from TomHarte/SpectrumSnapshots
Adds loading of state snapshots for the ZX Spectrum
2021-04-26 17:44:02 -04:00
Thomas Harte
3348167c46 Ensures AY registers are conveyed. 2021-04-26 17:39:11 -04:00
Thomas Harte
700c505974 Ensures the ZX Spectrum properly reports its display type. 2021-04-25 21:16:22 -04:00
Thomas Harte
d403036d86 Reduce bounce at Spectrum startup. 2021-04-25 20:56:57 -04:00
Thomas Harte
5e08d7db39 Carries through paging state; avoids file rereads. 2021-04-25 20:46:49 -04:00
Thomas Harte
c34cb310a8 Switches to more straightforward handler for .z80-style compression. 2021-04-25 18:07:36 -04:00
Thomas Harte
8d86aa69bc Adds an assert to check handling of compressed data. 2021-04-25 18:02:31 -04:00
Thomas Harte
cc41ccc5f1 Adds RAM deserialisation. 2021-04-25 17:55:52 -04:00
Thomas Harte
e6252fe0ed Sneaks up towards loading RAM. 2021-04-25 17:34:43 -04:00
Thomas Harte
03577de675 Adds an empty vessel for .z80 support. 2021-04-25 16:54:34 -04:00
Thomas Harte
205518ba75 Switch to more efficient copy. 2021-04-25 16:51:07 -04:00
Thomas Harte
2510064218 Completes state object.
Subject to not yet dealing with last_fetches_ and last_contended_access_ correctly. Thought required.
2021-04-25 14:20:40 -04:00
Thomas Harte
0ef2806970 Adds just enough to ensure that border state gets through. 2021-04-25 14:16:35 -04:00
Thomas Harte
d80f03e369 Corrects longstanding deviation from naming convention. 2021-04-25 14:11:36 -04:00
Thomas Harte
fd271d920b Adds capture and forwarding of border colour. 2021-04-25 14:00:12 -04:00
Thomas Harte
2bbf8bc9fa Ensures 16/48kb snapshots are properly copied into place. 2021-04-25 13:27:11 -04:00
Thomas Harte
9b65d56ed0 Resolves potential flaw in POPping here. 2021-04-25 13:26:53 -04:00
Thomas Harte
a5098a60ec Attempts to get in-SNA software to start. 2021-04-25 13:18:26 -04:00
Thomas Harte
0ebd900e40 Baby steps: apply Z80 state.
As far as it currently is. Since SNA is leaving the PC at the default of 0x0000, this currently has no visible effect.
2021-04-25 13:03:24 -04:00
Thomas Harte
7aeb17ac92 Corrects HeaderDoc/etc directive. 2021-04-25 13:01:23 -04:00
Thomas Harte
cc78bfb229 Forwards most of the Z80 state. 2021-04-25 13:00:43 -04:00
Thomas Harte
485c2a866c Without yet a struct for Spectrum states, at least checks general wiring. 2021-04-24 23:38:00 -04:00
Thomas Harte
5b419ca5bf Add State folder to Scons and Qt projects. 2021-04-24 23:25:08 -04:00
Thomas Harte
14ae579fca Add further note to future self. 2021-04-24 23:19:41 -04:00
Thomas Harte
1c2ea0d7fe unique_ptr makes more sense here. 2021-04-24 23:19:30 -04:00
Thomas Harte
e7a9ae18a1 Introduce further default state. 2021-04-24 23:18:00 -04:00
Thomas Harte
d61f478a39 Basic sketch for state snapshots: an extra field on Target.
I think it doesn't make sense for states to own a target as that complicates the concept of Media. Plus they're distinct because it makes sense to have only one per Target. Let's see how this pans out.
2021-04-24 23:17:47 -04:00
Thomas Harte
9cc747b3e2 Resolves potential source of errors: specifying incorrect table size.
(Having made exactly this mistake with the ZX Spectrum)
2021-04-24 12:10:28 -04:00
Thomas Harte
2f223f7db2 Spectrum emulation is no longer +2a/+3 specific. 2021-04-23 22:55:54 -04:00
Thomas Harte
17f11a3be3 Merge pull request #928 from TomHarte/ContentionTests
Add timing tests, fix +3 discrepancy.
2021-04-23 22:54:34 -04:00
Thomas Harte
37dcf61130 Add timing tests, fix +3 discrepancy. 2021-04-23 22:29:57 -04:00
Thomas Harte
856ebfacca Merge pull request #927 from TomHarte/SimplifiedTiming
Moves horizontal sync on the 48kb.
2021-04-21 19:50:40 -04:00
Thomas Harte
9731fdd33b Moves horizontal sync on the 48kb. 2021-04-21 19:46:44 -04:00
Thomas Harte
5ea605ccf7 Merge pull request #926 from TomHarte/SimplifiedTiming
Attempts more cleanly to express ZX Spectrum timing.
2021-04-21 19:46:23 -04:00
Thomas Harte
d0c789ff9a Locks declarative form of contention closer to regular expressions. 2021-04-21 19:37:36 -04:00
Thomas Harte
9baa861742 Simplifies timing calculation expression. 2021-04-21 19:18:07 -04:00
Thomas Harte
30a1a53c97 Merge pull request #925 from TomHarte/ZXROMSpeed
Corrects timing error in Spectrum 48kb and 128kb ROM accesses.
2021-04-21 18:54:16 -04:00
Thomas Harte
bdb1b7e77c Reinstate the +2 as the default Spectrum. 2021-04-21 18:49:39 -04:00
Thomas Harte
9293bcbc88 Exclude the ROM from contention on 48kb and 128kb models. 2021-04-21 18:49:18 -04:00
Thomas Harte
c481f475e7 Merge pull request #923 from TomHarte/STStartup
Resolves failure of ST to startup
2021-04-20 22:43:55 -04:00
Thomas Harte
ef01471e17 Ensures the DMA controller remains clocked. 2021-04-20 22:34:13 -04:00
Thomas Harte
73c8157197 Retain 6850 time tracking at all times. 2021-04-20 22:26:43 -04:00
Thomas Harte
af1dc2d3b2 Switches to correct non-value sentinel. 2021-04-20 21:56:58 -04:00
Thomas Harte
8f6b3feee1 Merge pull request #921 from TomHarte/Plus2aDefault
Switches default machine back to +2a.
2021-04-19 22:15:48 -04:00
Thomas Harte
a20f5528b7 Switches default machine back to +2a. 2021-04-19 22:04:49 -04:00
Thomas Harte
f48876d80e Merge pull request #920 from TomHarte/AppleIIVirtual
Disambiguates `reset_all_keys`.
2021-04-19 22:03:01 -04:00
Thomas Harte
db52f13c32 Disambiguates reset_all_keys. 2021-04-19 21:49:06 -04:00
Thomas Harte
2590769d3f Merge pull request #919 from TomHarte/XcodeProjectTweaks
Increases warnings, cleans up a touch.
2021-04-19 21:33:04 -04:00
Thomas Harte
5667dcac36 Increases warnings, cleans up a touch. 2021-04-19 21:28:12 -04:00
Thomas Harte
bec71ead39 Merge pull request #918 from TomHarte/macOS13
Reintroduces macOS 10.13 support.
2021-04-19 21:12:57 -04:00
Thomas Harte
e4d9022d37 Returns deployment target to 10.13. 2021-04-19 20:57:56 -04:00
Thomas Harte
572be48f38 Attempts to add an early exit for non-Metal Macs.
This will be necessary only prior to 10.14.
2021-04-19 20:55:25 -04:00
Thomas Harte
6f4ccebfa1 Merge pull request #917 from TomHarte/InterruptAddress
Put the program counter on the bus during interrupt acknowledge.
2021-04-19 20:08:22 -04:00
Thomas Harte
77fcf52d27 Purely style: remove some redundant nullptrs. 2021-04-19 18:53:00 -04:00
Thomas Harte
79c2bc1fd7 Put the program counter on the bus during interrupt acknowledge. 2021-04-19 18:43:50 -04:00
Thomas Harte
76370d9418 Merge pull request #916 from TomHarte/OffByOne
Corrects off-by-one timing errors in the ZX Spectrum.
2021-04-18 20:25:13 -04:00
Thomas Harte
7bac18bd65 Address bus load time is not + 1/2. 2021-04-18 18:41:24 -04:00
Thomas Harte
704737144a Corrects all interrupt timing for sign and off-by-one errors. 2021-04-18 18:40:44 -04:00
Thomas Harte
2a9c73a1d3 Merge pull request #915 from TomHarte/SpectrumSDLOptions
Adds display of Spectrum command-line options.
2021-04-18 12:08:02 -04:00
Thomas Harte
e87e851401 Add a redundant but idiomatic initial value. 2021-04-18 11:56:22 -04:00
Thomas Harte
80d4846a27 Respond with 0xff during an interrupt acknowledge. 2021-04-18 11:56:00 -04:00
Thomas Harte
9fd53c9c91 Adds the ZX Spectrum to ::AllMachines. 2021-04-17 23:06:37 -04:00
Thomas Harte
53eae873d8 Merge pull request #913 from TomHarte/LowerModelTiming
Brings timings into line with WoS specs.
2021-04-16 22:45:54 -04:00
Thomas Harte
93422f4b1c Brings timings into line with WoS specs. 2021-04-16 22:40:51 -04:00
Thomas Harte
06cedb2e50 Merge pull request #912 from TomHarte/128kDecoding
Corrects Spectrum 128kb partial decoding.
2021-04-16 22:02:25 -04:00
Thomas Harte
7fdb1d848b Corrects Spectrum 128kb partial decoding. 2021-04-16 21:54:52 -04:00
Thomas Harte
246fd9442f Merge pull request #911 from TomHarte/48kbSpectrum
Adds the 48kb and 128kb Spectrums.
2021-04-15 22:25:07 -04:00
Thomas Harte
eb99a64b29 Adds new Spectrum models to Qt UI. 2021-04-15 22:20:34 -04:00
Thomas Harte
d7954a4cb1 Tweaks timing a little. 2021-04-15 21:51:49 -04:00
Thomas Harte
ef636da866 Attempts 48/128kb floating bus behaviour. 2021-04-15 21:19:21 -04:00
Thomas Harte
fa18b06dbf Correct get_floating_value to be consistent in out-of-bounds behaviour. 2021-04-15 21:13:36 -04:00
Thomas Harte
349b9ce502 Don't post contended accesses other than on the +2a/+3.
Those machines have an actual latch for this stuff, the others don't.
2021-04-15 21:13:06 -04:00
Thomas Harte
b2cf121410 Regresses default to the more-compatible +2. 2021-04-15 19:31:45 -04:00
Thomas Harte
71cf63bd35 Corrects internal cycle contention. 2021-04-15 19:17:11 -04:00
Thomas Harte
d1bb3aada4 Attempts to complete the in-machine application of contention. 2021-04-15 18:57:34 -04:00
Thomas Harte
b4214c6e08 Blocks off the AY from inputs in 48kb mode. 2021-04-15 18:04:16 -04:00
Thomas Harte
f5c7746493 Extends fast loading support to the just-introduced models. 2021-04-15 17:31:42 -04:00
Thomas Harte
f10ec80153 Gets started on different video timings. 2021-04-14 22:23:27 -04:00
Thomas Harte
0af405aa46 Starts working in the 48kb and 128kb Spectrums. 2021-04-14 21:37:10 -04:00
Thomas Harte
cf481effa6 Merge pull request #910 from TomHarte/FastContention
Establishes that the 48/128kb contention patterns can be derived from my partial machine cycles alone.
2021-04-14 20:21:52 -04:00
Thomas Harte
a1511f9600 Establishes that the 48/128kb contention patterns can be derived from my partial machine cycles alone. 2021-04-14 20:15:40 -04:00
Thomas Harte
325e2b3941 Merge pull request #902 from TomHarte/Z80Lines
Spell out, test and correct Z80 bus activity.
2021-04-13 22:22:26 -04:00
Thomas Harte
7017324d60 r_step is obsolete now that I know that [DD/FD]CB don't have a refresh cycle. 2021-04-13 22:17:30 -04:00
Thomas Harte
deb5d69ac7 Consolidates macros. 2021-04-13 22:11:28 -04:00
Thomas Harte
68a04f4e6a Adds IN/OUT I/D [R] to complete tests. 2021-04-13 22:00:24 -04:00
Thomas Harte
0d61902b10 Adds CP[I/D/IR/DR] tests. 2021-04-13 20:03:11 -04:00
Thomas Harte
3eec210b30 Adds LDI/LDD/LDIR/LDDR tests. 2021-04-13 20:00:29 -04:00
Thomas Harte
5998f3b35b Corrects LD[I/D/IR/DR] timing.
Macro cleanup to come.
2021-04-13 20:00:18 -04:00
Thomas Harte
869567fdd9 Corrects EX (SP), HL breakdown. 2021-04-13 19:45:48 -04:00
Thomas Harte
2e70b5eb9f Advances to EX (SP), HL, leaving only [LD/CP/IN/OT][I/D]{R}. 2021-04-13 19:45:29 -04:00
Thomas Harte
8a3bfb8672 Adds an IN/OUT test. 2021-04-13 17:55:51 -04:00
Thomas Harte
06f1e64177 Advances to IO. 2021-04-12 21:41:20 -04:00
Thomas Harte
b42780173a Establishes that there really is no Read4 and Read4Pre distinction.
Will finish these unit tests, then clean up.
2021-04-12 20:54:10 -04:00
Thomas Harte
36c8821c4c Reaches the halfway point in tests. 2021-04-12 17:29:03 -04:00
Thomas Harte
947de2d54a Switches five-cycle read to a post hoc pause. 2021-04-12 17:17:08 -04:00
Thomas Harte
9347fe5f44 Advances to next failing test: LD (ii+n), n. 2021-04-12 17:11:58 -04:00
Thomas Harte
e82367def3 Switches to test-conformant behaviour for (IX/IY+n) opcode fetches. 2021-04-11 23:01:00 -04:00
Thomas Harte
9cde7c12ba Shifts responsibility for refresh into the fetch-decode-execute sequence. 2021-04-11 22:50:24 -04:00
Thomas Harte
015556cc91 Switch (ii+n) to Read4Pre. 2021-04-11 10:26:14 -04:00
Thomas Harte
47c5a243aa Restructures, the better to explore errors. 2021-04-10 21:32:42 -04:00
Thomas Harte
070e359d82 Introduces failing test for BIT b, (ii+n). 2021-04-10 18:00:23 -04:00
Thomas Harte
b397059d5e Moves read time in Read4Pre. 2021-04-10 17:54:20 -04:00
Thomas Harte
400f54e508 Introduces failing test for bit b, (hl). 2021-04-10 12:04:48 -04:00
Thomas Harte
e0736435f8 Makes assumption that the address bus just holds its value during an internal operation. 2021-04-10 12:00:53 -04:00
Thomas Harte
b09c5538c6 Adds failing test for simple (ii+n) tests. 2021-04-09 21:28:35 -04:00
Thomas Harte
ce3d2913bf Advances to 9 source table rows tested out of 37. 2021-04-09 20:38:17 -04:00
Thomas Harte
87202a2a27 Add two further tests, add checking of collected data size for all tests. 2021-04-09 18:32:03 -04:00
Thomas Harte
818a4dff25 Corrects ADD HL, dd test.
Or, at least, likely corrects. The bus cycle breakdown in the Z80 data sheet implies these accesses should come after completion of the refresh cycle, not during its long tail, so I think +1 is correct.
2021-04-08 22:23:15 -04:00
Thomas Harte
eacffa49f5 Exposes IR during 'internal' operations. 2021-04-08 22:22:26 -04:00
Thomas Harte
9e506c3206 Adds failing ADD hl, dd test. 2021-04-08 22:19:22 -04:00
Thomas Harte
29cf80339a Corrects too-short buffer. 2021-04-08 22:15:03 -04:00
Thomas Harte
50f53f7d97 Adds INC/DEC rr and LD SP, HL tests. 2021-04-08 22:14:53 -04:00
Thomas Harte
73fbd89c85 Correct opcodes, ability to terminate on a single-cycle contention. 2021-04-08 22:09:33 -04:00
Thomas Harte
f74fa06f2d Introduces failing test for LD [A/I/R], [A/I/R]. 2021-04-08 20:28:55 -04:00
Thomas Harte
ee989ab762 Fills in the rest of the simple two-byte instructions. 2021-04-08 20:13:52 -04:00
Thomas Harte
818655a9b6 Starts on two-bus-cycle instructions, correcting validators. 2021-04-08 20:01:46 -04:00
Thomas Harte
57a7e0834f Corrects sampling of MREQ. 2021-04-08 19:21:35 -04:00
Thomas Harte
cd787486d2 Tests all of the single-byte, no-access opcodes. 2021-04-07 22:07:52 -04:00
Thomas Harte
67fd6787a6 Builds what I think I need to validate Z80 address, MREQ, IOREQ and RFSH. 2021-04-07 21:57:40 -04:00
Thomas Harte
627b96f73c Merge branch 'master' into Z80Lines 2021-04-07 21:02:15 -04:00
Thomas Harte
25b8c4c062 Provide clearer failure case. 2021-04-03 21:04:44 -04:00
Thomas Harte
1be88a5308 Remove first draft. 2021-04-02 07:39:22 -04:00
Thomas Harte
294280a94e Spells out everything except interrupt acknowledge. 2021-04-02 07:38:06 -04:00
Thomas Harte
32aebfebe0 Starts spelling out meaning of the Z80's partial machine cycles. 2021-04-02 07:37:56 -04:00
67 changed files with 4227 additions and 898 deletions

View File

@@ -57,6 +57,11 @@
#include "../../Storage/MassStorage/Formats/DAT.hpp"
#include "../../Storage/MassStorage/Formats/HFV.hpp"
// State Snapshots
#include "../../Storage/State/SNA.hpp"
#include "../../Storage/State/SZX.hpp"
#include "../../Storage/State/Z80.hpp"
// Tapes
#include "../../Storage/Tape/Formats/CAS.hpp"
#include "../../Storage/Tape/Formats/CommodoreTAP.hpp"
@@ -73,15 +78,23 @@
using namespace Analyser::Static;
static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::IntType &potential_platforms) {
Media result;
namespace {
std::string get_extension(const std::string &name) {
// Get the extension, if any; it will be assumed that extensions are reliable, so an extension is a broad-phase
// test as to file format.
std::string::size_type final_dot = file_name.find_last_of(".");
if(final_dot == std::string::npos) return result;
std::string extension = file_name.substr(final_dot + 1);
std::string::size_type final_dot = name.find_last_of(".");
if(final_dot == std::string::npos) return name;
std::string extension = name.substr(final_dot + 1);
std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
return extension;
}
}
static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::IntType &potential_platforms) {
Media result;
const std::string extension = get_extension(file_name);
#define InsertInstance(list, instance, platforms) \
list.emplace_back(instance);\
@@ -199,14 +212,35 @@ Media Analyser::Static::GetMedia(const std::string &file_name) {
TargetList Analyser::Static::GetTargets(const std::string &file_name) {
TargetList targets;
const std::string extension = get_extension(file_name);
// Check whether the file directly identifies a target; if so then just return that.
#define Format(ext, class) \
if(extension == ext) { \
try { \
auto target = Storage::State::class::load(file_name); \
if(target) { \
targets.push_back(std::move(target)); \
return targets; \
} \
} catch(...) {} \
}
Format("sna", SNA);
Format("szx", SZX);
Format("z80", Z80);
#undef TryInsert
// Otherwise:
//
// Collect all disks, tapes ROMs, etc as can be extrapolated from this file, forming the
// union of all platforms this file might be a target for.
TargetPlatform::IntType potential_platforms = 0;
Media media = GetMediaAndPlatforms(file_name, potential_platforms);
// Hand off to platform-specific determination of whether these things are actually compatible and,
// if so, how to load them.
// Hand off to platform-specific determination of whether these
// things are actually compatible and, if so, how to load them.
#define Append(x) if(potential_platforms & TargetPlatform::x) {\
auto new_targets = x::GetTargets(media, file_name, potential_platforms);\
std::move(new_targets.begin(), new_targets.end(), std::back_inserter(targets));\

View File

@@ -15,6 +15,7 @@
#include "../../Storage/Disk/Disk.hpp"
#include "../../Storage/MassStorage/MassStorageDevice.hpp"
#include "../../Storage/Tape/Tape.hpp"
#include "../../Reflection/Struct.hpp"
#include <memory>
#include <string>
@@ -23,8 +24,10 @@
namespace Analyser {
namespace Static {
struct State;
/*!
A list of disks, tapes and cartridges.
A list of disks, tapes and cartridges, and possibly a state snapshot.
*/
struct Media {
std::vector<std::shared_ptr<Storage::Disk::Disk>> disks;
@@ -48,13 +51,16 @@ struct Media {
};
/*!
A list of disks, tapes and cartridges plus information about the machine to which to attach them and its configuration,
and instructions on how to launch the software attached, plus a measure of confidence in this target's correctness.
Describes a machine and possibly its state; conventionally subclassed to add other machine-specific configuration fields and any
necessary instructions on how to launch any software provided, plus a measure of confidence in this target's correctness.
*/
struct Target {
Target(Machine machine) : machine(machine) {}
virtual ~Target() {}
// This field is entirely optional.
std::unique_ptr<Reflection::Struct> state;
Machine machine;
Media media;
float confidence = 0.0f;

View File

@@ -19,11 +19,15 @@ namespace ZXSpectrum {
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(Model,
SixteenK,
FortyEightK,
OneTwoEightK,
Plus2,
Plus2a,
Plus3,
);
Model model = Model::Plus2a;
Model model = Model::Plus2;
bool should_hold_enter = false;
Target(): Analyser::Static::Target(Machine::ZXSpectrum) {

View File

@@ -129,8 +129,8 @@ ClockingHint::Preference ACIA::preferred_clocking() const {
// because it's unclear when the interrupt might come.
if(bits_incoming_ && receive_interrupt_enabled_) return ClockingHint::Preference::RealTime;
// No clocking required then.
return ClockingHint::Preference::None;
// Real-time clocking not required then.
return ClockingHint::Preference::JustInTime;
}
bool ACIA::get_interrupt_line() const {

View File

@@ -208,7 +208,7 @@ void MFP68901::run_for(HalfCycles time) {
}
HalfCycles MFP68901::get_next_sequence_point() {
return HalfCycles(-1);
return HalfCycles::max();
}
// MARK: - Timers

View File

@@ -12,6 +12,8 @@
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
#include "../../Concurrency/AsyncTaskQueue.hpp"
#include "../../Reflection/Struct.hpp"
namespace GI {
namespace AY38910 {
@@ -162,6 +164,8 @@ template <bool is_stereo> class AY38910: public ::Outputs::Speaker::SampleSource
uint8_t a_left_ = 255, a_right_ = 255;
uint8_t b_left_ = 255, b_right_ = 255;
uint8_t c_left_ = 255, c_right_ = 255;
friend struct State;
};
/*!
@@ -192,6 +196,29 @@ struct Utility {
};
struct State: public Reflection::StructImpl<State> {
uint8_t registers[16]{};
uint8_t selected_register = 0;
// TODO: all audio-production thread state.
State() {
if(needs_declare()) {
DeclareField(registers);
DeclareField(selected_register);
}
}
template <typename AY> void apply(AY &target) {
// Establish emulator-thread state
for(uint8_t c = 0; c < 16; c++) {
target.select_register(c);
target.set_register_value(registers[c]);
}
target.select_register(selected_register);
}
};
}
}

View File

@@ -151,7 +151,7 @@ const uint16_t *CharacterMapper::sequence_for_character(char character) const {
#undef SHIFT
#undef X
return table_lookup_sequence_for_character(key_sequences, sizeof(key_sequences), character);
return table_lookup_sequence_for_character(key_sequences, character);
}
bool CharacterMapper::needs_pause_after_key(uint16_t key) const {

View File

@@ -16,6 +16,7 @@ void Keyboard::perform_command(const Command &command) {
switch(command.type) {
case Command::Type::Reset:
modifiers_ = 0xffff;
[[fallthrough]];
case Command::Type::Flush: {
std::lock_guard lock_guard(keys_mutex_);
pending_events_.clear();

View File

@@ -48,7 +48,6 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
public MachineTypes::MappedKeyboardMachine,
public MachineTypes::JoystickMachine,
public CPU::MOS6502::BusHandler,
public Inputs::Keyboard,
public Configurable::Device,
public Activity::Source,
public Apple::II::Card::Delegate {
@@ -66,7 +65,11 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
uint8_t *ram_, *aux_ram_;
};
CPU::MOS6502::Processor<(model == Analyser::Static::AppleII::Target::Model::EnhancedIIe) ? CPU::MOS6502::Personality::PSynertek65C02 : CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_;
using Processor = CPU::MOS6502::Processor<
(model == Analyser::Static::AppleII::Target::Model::EnhancedIIe) ? CPU::MOS6502::Personality::PSynertek65C02 : CPU::MOS6502::Personality::P6502,
ConcreteMachine,
false>;
Processor m6502_;
VideoBusHandler video_bus_handler_;
Apple::II::Video::Video<VideoBusHandler, is_iie()> video_;
int cycles_into_current_line_ = 0;
@@ -91,16 +94,6 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
uint8_t ram_[65536], aux_ram_[65536];
std::vector<uint8_t> rom_;
uint8_t keyboard_input_ = 0x00;
bool key_is_down_ = false;
uint8_t get_keyboard_input() {
if(string_serialiser_) {
return string_serialiser_->head() | 0x80;
} else {
return keyboard_input_;
}
}
Concurrency::DeferringAsyncTaskQueue audio_queue_;
Audio::Toggle audio_toggle_;
@@ -258,16 +251,99 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
state.region_20_40.write ? &aux_ram_[0x2000] : &ram_[0x2000]);
}
// MARK - typing
std::unique_ptr<Utility::StringSerialiser> string_serialiser_;
// MARK: - Keyboard and typing.
// MARK - Joysticks.
struct Keyboard: public Inputs::Keyboard {
Keyboard(Processor *m6502) : m6502_(m6502) {}
void reset_all_keys() final {
open_apple_is_pressed = closed_apple_is_pressed = key_is_down = false;
}
bool set_key_pressed(Key key, char value, bool is_pressed) final {
// If no ASCII value is supplied, look for a few special cases.
switch(key) {
case Key::Left: value = 0x08; break;
case Key::Right: value = 0x15; break;
case Key::Down: value = 0x0a; break;
case Key::Up: value = 0x0b; break;
case Key::Backspace: value = 0x7f; break;
case Key::Enter: value = 0x0d; break;
case Key::Tab: value = '\t'; break;
case Key::Escape: value = 0x1b; break;
case Key::LeftOption:
case Key::RightMeta:
open_apple_is_pressed = is_pressed;
return true;
case Key::RightOption:
case Key::LeftMeta:
closed_apple_is_pressed = is_pressed;
return true;
case Key::F1: case Key::F2: case Key::F3: case Key::F4:
case Key::F5: case Key::F6: case Key::F7: case Key::F8:
case Key::F9: case Key::F10: case Key::F11: case Key::F12:
case Key::PrintScreen:
case Key::ScrollLock:
case Key::Pause:
case Key::Insert:
case Key::Home:
case Key::PageUp:
case Key::PageDown:
case Key::End:
// Accept a bunch non-symbolic other keys, as
// reset, in the hope that the user can find
// at least one usable key.
m6502_->set_reset_line(is_pressed);
return true;
default:
if(!value) {
return false;
}
// Prior to the IIe, the keyboard could produce uppercase only.
if(!is_iie()) value = char(toupper(value));
break;
}
if(is_pressed) {
keyboard_input = uint8_t(value | 0x80);
key_is_down = true;
} else {
if((keyboard_input & 0x7f) == value) {
key_is_down = false;
}
}
return true;
}
uint8_t get_keyboard_input() {
if(string_serialiser) {
return string_serialiser->head() | 0x80;
} else {
return keyboard_input;
}
}
// The IIe has three keys that are wired directly to the same input as the joystick buttons.
bool open_apple_is_pressed = false;
bool closed_apple_is_pressed = false;
uint8_t keyboard_input = 0x00;
bool key_is_down = false;
std::unique_ptr<Utility::StringSerialiser> string_serialiser;
private:
Processor *const m6502_;
};
Keyboard keyboard_;
// MARK: - Joysticks.
JoystickPair joysticks_;
// The IIe has three keys that are wired directly to the same input as the joystick buttons.
bool open_apple_is_pressed_ = false;
bool closed_apple_is_pressed_ = false;
public:
ConcreteMachine(const Analyser::Static::AppleII::Target &target, const ROMMachine::ROMFetcher &rom_fetcher):
m6502_(*this),
@@ -276,7 +352,8 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
audio_toggle_(audio_queue_),
speaker_(audio_toggle_),
language_card_(*this),
auxiliary_switches_(*this) {
auxiliary_switches_(*this),
keyboard_(&m6502_) {
// The system's master clock rate.
constexpr float master_clock = 14318180.0;
@@ -435,18 +512,18 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
default: break;
case 0xc000:
*value = get_keyboard_input();
*value = keyboard_.get_keyboard_input();
break;
case 0xc001: case 0xc002: case 0xc003: case 0xc004: case 0xc005: case 0xc006: case 0xc007:
case 0xc008: case 0xc009: case 0xc00a: case 0xc00b: case 0xc00c: case 0xc00d: case 0xc00e: case 0xc00f:
*value = (*value & 0x80) | (get_keyboard_input() & 0x7f);
*value = (*value & 0x80) | (keyboard_.get_keyboard_input() & 0x7f);
break;
case 0xc061: // Switch input 0.
*value &= 0x7f;
if(
joysticks_.button(0) ||
(is_iie() && open_apple_is_pressed_)
(is_iie() && keyboard_.open_apple_is_pressed)
)
*value |= 0x80;
break;
@@ -454,7 +531,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
*value &= 0x7f;
if(
joysticks_.button(1) ||
(is_iie() && closed_apple_is_pressed_)
(is_iie() && keyboard_.closed_apple_is_pressed)
)
*value |= 0x80;
break;
@@ -476,7 +553,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
} break;
// The IIe-only state reads follow...
#define IIeSwitchRead(s) *value = get_keyboard_input(); if(is_iie()) *value = (*value & 0x7f) | (s ? 0x80 : 0x00);
#define IIeSwitchRead(s) *value = keyboard_.get_keyboard_input(); if(is_iie()) *value = (*value & 0x7f) | (s ? 0x80 : 0x00);
case 0xc011: IIeSwitchRead(language_card_.state().bank2); break;
case 0xc012: IIeSwitchRead(language_card_.state().read); break;
case 0xc013: IIeSwitchRead(auxiliary_switches_.switches().read_auxiliary_memory); break;
@@ -559,15 +636,15 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
break;
case 0xc010:
keyboard_input_ &= 0x7f;
if(string_serialiser_) {
if(!string_serialiser_->advance())
string_serialiser_.reset();
keyboard_.keyboard_input &= 0x7f;
if(keyboard_.string_serialiser) {
if(!keyboard_.string_serialiser->advance())
keyboard_.string_serialiser.reset();
}
// On the IIe, reading C010 returns additional key info.
if(is_iie() && isReadOperation(operation)) {
*value = (key_is_down_ ? 0x80 : 0x00) | (keyboard_input_ & 0x7f);
*value = (keyboard_.key_is_down ? 0x80 : 0x00) | (keyboard_.keyboard_input & 0x7f);
}
break;
@@ -683,81 +760,16 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
m6502_.run_for(cycles);
}
void reset_all_keys() final {
open_apple_is_pressed_ = closed_apple_is_pressed_ = key_is_down_ = false;
}
bool prefers_logical_input() final {
return true;
}
bool set_key_pressed(Key key, char value, bool is_pressed) final {
// If no ASCII value is supplied, look for a few special cases.
switch(key) {
case Key::Left: value = 0x08; break;
case Key::Right: value = 0x15; break;
case Key::Down: value = 0x0a; break;
case Key::Up: value = 0x0b; break;
case Key::Backspace: value = 0x7f; break;
case Key::Enter: value = 0x0d; break;
case Key::Tab: value = '\t'; break;
case Key::Escape: value = 0x1b; break;
case Key::LeftOption:
case Key::RightMeta:
open_apple_is_pressed_ = is_pressed;
return true;
case Key::RightOption:
case Key::LeftMeta:
closed_apple_is_pressed_ = is_pressed;
return true;
case Key::F1: case Key::F2: case Key::F3: case Key::F4:
case Key::F5: case Key::F6: case Key::F7: case Key::F8:
case Key::F9: case Key::F10: case Key::F11: case Key::F12:
case Key::PrintScreen:
case Key::ScrollLock:
case Key::Pause:
case Key::Insert:
case Key::Home:
case Key::PageUp:
case Key::PageDown:
case Key::End:
// Accept a bunch non-symbolic other keys, as
// reset, in the hope that the user can find
// at least one usable key.
m6502_.set_reset_line(is_pressed);
return true;
default:
if(!value) {
return false;
}
// Prior to the IIe, the keyboard could produce uppercase only.
if(!is_iie()) value = char(toupper(value));
break;
}
if(is_pressed) {
keyboard_input_ = uint8_t(value | 0x80);
key_is_down_ = true;
} else {
if((keyboard_input_ & 0x7f) == value) {
key_is_down_ = false;
}
}
return true;
}
Inputs::Keyboard &get_keyboard() final {
return *this;
return keyboard_;
}
void type_string(const std::string &string) final {
string_serialiser_ = std::make_unique<Utility::StringSerialiser>(string, true);
keyboard_.string_serialiser = std::make_unique<Utility::StringSerialiser>(string, true);
}
bool can_type(char c) const final {

View File

@@ -252,7 +252,7 @@ void DMAController::set_component_prefers_clocking(ClockingHint::Source *, Clock
}
ClockingHint::Preference DMAController::preferred_clocking() const {
return (fdc_.preferred_clocking() == ClockingHint::Preference::None) ? ClockingHint::Preference::None : ClockingHint::Preference::RealTime;
return (fdc_.preferred_clocking() == ClockingHint::Preference::None) ? ClockingHint::Preference::JustInTime : ClockingHint::Preference::RealTime;
}
void DMAController::set_activity_observer(Activity::Observer *observer) {

View File

@@ -151,5 +151,5 @@ const uint16_t *CharacterMapper::sequence_for_character(char character) const {
#undef SHIFT
#undef X
return table_lookup_sequence_for_character(key_sequences, sizeof(key_sequences), character);
return table_lookup_sequence_for_character(key_sequences, character);
}

View File

@@ -145,7 +145,7 @@ const uint16_t *CharacterMapper::sequence_for_character(char character) const {
#undef SHIFT
#undef X
return table_lookup_sequence_for_character(key_sequences, sizeof(key_sequences), character);
return table_lookup_sequence_for_character(key_sequences, character);
}
bool CharacterMapper::needs_pause_after_key(uint16_t key) const {

View File

@@ -20,6 +20,7 @@
#include "MediaTarget.hpp"
#include "MouseMachine.hpp"
#include "ScanProducer.hpp"
#include "StateProducer.hpp"
#include "TimedMachine.hpp"
#endif /* MachineTypes_h */

View File

@@ -127,5 +127,5 @@ const uint16_t *CharacterMapper::sequence_for_character(char character) const {
#undef SHIFT
#undef X
return table_lookup_sequence_for_character(key_sequences, sizeof(key_sequences), character);
return table_lookup_sequence_for_character(key_sequences, character);
}

View File

@@ -40,6 +40,45 @@
#include <memory>
#include <vector>
namespace {
/*!
Provides an Altai-style joystick.
*/
class Joystick: public Inputs::ConcreteJoystick {
public:
Joystick() :
ConcreteJoystick({
Input(Input::Up),
Input(Input::Down),
Input(Input::Left),
Input(Input::Right),
Input(Input::Fire)
}) {}
void did_set_input(const Input &digital_input, bool is_active) final {
#define APPLY(b) if(is_active) state_ &= ~b; else state_ |= b;
switch(digital_input.type) {
default: return;
case Input::Right: APPLY(0x02); break;
case Input::Left: APPLY(0x01); break;
case Input::Down: APPLY(0x08); break;
case Input::Up: APPLY(0x10); break;
case Input::Fire: APPLY(0x20); break;
}
#undef APPLY
}
uint8_t get_state() {
return state_;
}
private:
uint8_t state_ = 0xff;
};
}
namespace Oric {
using DiskInterface = Analyser::Static::Oric::Target::DiskInterface;
@@ -140,7 +179,12 @@ class TapePlayer: public Storage::Tape::BinaryTapePlayer {
class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
public:
VIAPortHandler(Concurrency::DeferringAsyncTaskQueue &audio_queue, AY &ay8910, Speaker &speaker, TapePlayer &tape_player, Keyboard &keyboard) :
audio_queue_(audio_queue), ay8910_(ay8910), speaker_(speaker), tape_player_(tape_player), keyboard_(keyboard) {}
audio_queue_(audio_queue), ay8910_(ay8910), speaker_(speaker), tape_player_(tape_player), keyboard_(keyboard)
{
// Attach a couple of joysticks.
joysticks_.emplace_back(new Joystick);
joysticks_.emplace_back(new Joystick);
}
/*!
Reponds to the 6522's control line output change signal; on an Oric A2 is connected to
@@ -165,6 +209,7 @@ class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
} else {
update_ay();
ay8910_.set_data_input(value);
porta_output_ = value;
}
}
@@ -176,7 +221,10 @@ class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
uint8_t column = ay8910_.get_port_output(false) ^ 0xff;
return keyboard_.query_column(column) ? 0x08 : 0x00;
} else {
return ay8910_.get_data_output();
uint8_t result = ay8910_.get_data_output();
if(porta_output_ & 0x40) result &= static_cast<Joystick *>(joysticks_[0].get())->get_state();
if(porta_output_ & 0x80) result &= static_cast<Joystick *>(joysticks_[1].get())->get_state();
return result;
}
}
@@ -193,12 +241,17 @@ class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
audio_queue_.perform();
}
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() {
return joysticks_;
}
private:
void update_ay() {
speaker_.run_for(audio_queue_, cycles_since_ay_update_.flush<Cycles>());
}
bool ay_bdir_ = false;
bool ay_bc1_ = false;
uint8_t porta_output_ = 0xff;
HalfCycles cycles_since_ay_update_;
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
@@ -206,12 +259,15 @@ class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
Speaker &speaker_;
TapePlayer &tape_player_;
Keyboard &keyboard_;
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
};
template <Analyser::Static::Oric::Target::DiskInterface disk_interface, CPU::MOS6502Esque::Type processor_type> class ConcreteMachine:
public MachineTypes::TimedMachine,
public MachineTypes::ScanProducer,
public MachineTypes::AudioProducer,
public MachineTypes::JoystickMachine,
public MachineTypes::MediaTarget,
public MachineTypes::MappedKeyboardMachine,
public Configurable::Device,
@@ -731,8 +787,13 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface, CPU::MOS
}
}
// MARK - typing
// MARK: - typing
std::unique_ptr<Utility::StringSerialiser> string_serialiser_;
// MARK: - Joysticks
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
return via_port_handler_.get_joysticks();
}
};
}

View File

@@ -275,13 +275,14 @@ const uint16_t *CharacterMapper::sequence_for_character(char character) const {
switch(machine_) {
case Machine::ZX80:
return table_lookup_sequence_for_character(zx80_key_sequences, sizeof(zx80_key_sequences), character);
return table_lookup_sequence_for_character(zx80_key_sequences, character);
case Machine::ZX81:
return table_lookup_sequence_for_character(zx81_key_sequences, sizeof(zx81_key_sequences), character);
return table_lookup_sequence_for_character(zx81_key_sequences, character);
default:
case Machine::ZXSpectrum:
return table_lookup_sequence_for_character(spectrum_key_sequences, sizeof(zx81_key_sequences), character);
return table_lookup_sequence_for_character(spectrum_key_sequences, character);
}
}

View File

@@ -32,7 +32,7 @@ class Machine {
class Options: public Reflection::StructImpl<Options>, public Configurable::QuickloadOption<Options> {
friend Configurable::QuickloadOption<Options>;
public:
bool automatic_tape_motor_control;
bool automatic_tape_motor_control = true;
Options(Configurable::OptionsType type):
Configurable::QuickloadOption<Options>(type == Configurable::OptionsType::UserFriendly),

View File

@@ -0,0 +1,53 @@
//
// State.hpp
// Clock Signal
//
// Created by Thomas Harte on 25/04/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef State_hpp
#define State_hpp
#include "../../../Reflection/Struct.hpp"
#include "../../../Processors/Z80/State/State.hpp"
#include "Video.hpp"
#include "../../../Components/AY38910/AY38910.hpp"
namespace Sinclair {
namespace ZXSpectrum {
struct State: public Reflection::StructImpl<State> {
CPU::Z80::State z80;
Video::State video;
// In 16kb or 48kb mode, RAM will be 16kb or 48kb and represent
// memory in standard linear order. In 128kb mode, RAM will be
// 128kb with the first 16kb representing bank 0, the next bank 1, etc.
std::vector<uint8_t> ram;
// Meaningful for 128kb machines only.
uint8_t last_7ffd = 0;
GI::AY38910::State ay;
// Meaningful for the +2a and +3 only.
uint8_t last_1ffd = 0;
State() {
if(needs_declare()) {
DeclareField(z80);
DeclareField(video);
DeclareField(ram);
DeclareField(last_7ffd);
DeclareField(last_1ffd);
DeclareField(ay);
}
}
};
}
}
#endif /* State_h */

View File

@@ -12,13 +12,18 @@
#include "../../../Outputs/CRT/CRT.hpp"
#include "../../../ClockReceiver/ClockReceiver.hpp"
#include "../../../Reflection/Struct.hpp"
#include <algorithm>
namespace Sinclair {
namespace ZXSpectrum {
namespace Video {
enum class VideoTiming {
Plus3
enum class Timing {
FortyEightK,
OneTwoEightK,
Plus3,
};
/*
@@ -45,11 +50,11 @@ enum class VideoTiming {
*/
template <VideoTiming timing> class Video {
template <Timing timing> class Video {
private:
struct Timings {
// Number of cycles per line. Will be 224 or 228.
int cycles_per_line;
int half_cycles_per_line;
// Number of lines comprising a whole frame. Will be 311 or 312.
int lines_per_frame;
@@ -61,75 +66,56 @@ template <VideoTiming timing> class Video {
// Number of cycles after first pixel fetch at which interrupt is first signalled.
int interrupt_time;
// Contention to apply, in half-cycles, as a function of number of half cycles since
// Contention to apply, in whole cycles, as a function of number of whole cycles since
// contention began.
int delays[16];
int delays[8];
constexpr Timings(int cycles_per_line, int lines_per_frame, int contention_leadin, int contention_duration, int interrupt_offset, const int *delays) noexcept :
half_cycles_per_line(cycles_per_line * 2),
lines_per_frame(lines_per_frame),
contention_leadin(contention_leadin * 2),
contention_duration(contention_duration * 2),
interrupt_time((cycles_per_line * lines_per_frame - interrupt_offset - contention_leadin) * 2),
delays{ delays[0] * 2, delays[1] * 2, delays[2] * 2, delays[3] * 2, delays[4] * 2, delays[5] * 2, delays[6] * 2, delays[7] * 2}
{}
};
static constexpr Timings get_timings() {
// Amstrad gate array timings, classic statement:
//
// Contention begins 14361 cycles "after interrupt" and follows the pattern [1, 0, 7, 6 5 4, 3, 2].
// The first four bytes of video are fetched at 1436514368 cycles, in the order [pixels, attribute, pixels, attribute].
//
// For my purposes:
//
// Video fetching always begins at 0. Since there are 311*228 = 70908 cycles per frame, and the interrupt
// should "occur" (I assume: begin) 14365 before that, it should actually begin at 70908 - 14365 = 56543.
//
// Contention begins four cycles before the first video fetch, so it begins at 70904. I don't currently
// know whether the four cycles is true across all models, so it's given here as convention_leadin.
//
// ... except that empirically that all seems to be two cycles off. So maybe I misunderstand what the
// contention patterns are supposed to indicate relative to MREQ? It's frustrating that all documentation
// I can find is vaguely in terms of contention patterns, and what they mean isn't well-defined in terms
// of regular Z80 signalling.
constexpr Timings result = {
.cycles_per_line = 228 * 2,
.lines_per_frame = 311,
if constexpr (timing == Timing::Plus3) {
constexpr int delays[] = {1, 0, 7, 6, 5, 4, 3, 2};
return Timings(228, 311, 6, 129, 14361, delays);
}
// i.e. video fetching begins five cycles after the start of the
// contended memory pattern below; that should put a clear two
// cycles between a Z80 access and the first video fetch.
.contention_leadin = 5 * 2,
.contention_duration = 129 * 2,
if constexpr (timing == Timing::OneTwoEightK) {
constexpr int delays[] = {6, 5, 4, 3, 2, 1, 0, 0};
return Timings(228, 311, 4, 128, 14361, delays);
}
// i.e. interrupt is first signalled 14368 cycles before the first video fetch.
.interrupt_time = (228*311 - 14368) * 2,
.delays = {
2, 1,
0, 0,
14, 13,
12, 11,
10, 9,
8, 7,
6, 5,
4, 3,
}
};
return result;
if constexpr (timing == Timing::FortyEightK) {
constexpr int delays[] = {6, 5, 4, 3, 2, 1, 0, 0};
return Timings(224, 312, 4, 128, 14335, delays);
}
}
// TODO: how long is the interrupt line held for?
static constexpr int interrupt_duration = 48;
// Interrupt should be held for 32 cycles.
static constexpr int interrupt_duration = 64;
public:
void run_for(HalfCycles duration) {
constexpr auto timings = get_timings();
constexpr int sync_line = (timings.interrupt_time / timings.cycles_per_line) + 1;
constexpr int sync_line = (timings.interrupt_time / timings.half_cycles_per_line) + 1;
constexpr int sync_position = 166 * 2;
constexpr int sync_position = (timing == Timing::FortyEightK) ? 164 * 2 : 166 * 2;
constexpr int sync_length = 17 * 2;
constexpr int burst_position = sync_position + 40;
constexpr int burst_length = 17;
int cycles_remaining = duration.as<int>();
while(cycles_remaining) {
int line = time_into_frame_ / timings.cycles_per_line;
int offset = time_into_frame_ % timings.cycles_per_line;
const int cycles_this_line = std::min(cycles_remaining, timings.cycles_per_line - offset);
int line = time_into_frame_ / timings.half_cycles_per_line;
int offset = time_into_frame_ % timings.half_cycles_per_line;
const int cycles_this_line = std::min(cycles_remaining, timings.half_cycles_per_line - offset);
const int end_offset = offset + cycles_this_line;
if(!offset) {
@@ -236,9 +222,14 @@ template <VideoTiming timing> class Video {
if(offset >= burst_position && offset < burst_position+burst_length && end_offset > offset) {
const int burst_duration = std::min(burst_position + burst_length, end_offset) - offset;
crt_.output_colour_burst(burst_duration, 116, is_alternate_line_);
if constexpr (timing >= Timing::OneTwoEightK) {
crt_.output_colour_burst(burst_duration, 116, is_alternate_line_);
// The colour burst phase above is an empirical guess. I need to research further.
} else {
crt_.output_default_colour_burst(burst_duration);
}
offset += burst_duration;
// The colour burst phase above is an empirical guess. I need to research further.
}
if(offset >= burst_position+burst_length && end_offset > offset) {
@@ -248,7 +239,7 @@ template <VideoTiming timing> class Video {
}
cycles_remaining -= cycles_this_line;
time_into_frame_ = (time_into_frame_ + cycles_this_line) % (timings.cycles_per_line * timings.lines_per_frame);
time_into_frame_ = (time_into_frame_ + cycles_this_line) % (timings.half_cycles_per_line * timings.lines_per_frame);
}
}
@@ -259,14 +250,64 @@ template <VideoTiming timing> class Video {
crt_.output_level(duration);
}
static constexpr int half_cycles_per_line() {
if constexpr (timing == Timing::FortyEightK) {
// TODO: determine real figure here, if one exists.
// The source I'm looking at now suggests that the theoretical
// ideal of 224*2 ignores the real-life effects of separate
// crystals, so I've nudged this experimentally.
return 224*2 - 1;
} else {
return 227*2;
}
}
static constexpr HalfCycles frame_duration() {
const auto timings = get_timings();
return HalfCycles(timings.half_cycles_per_line * timings.lines_per_frame);
}
HalfCycles time_since_interrupt() {
const auto timings = get_timings();
if(time_into_frame_ >= timings.interrupt_time) {
return HalfCycles(time_into_frame_ - timings.interrupt_time);
} else {
return HalfCycles(time_into_frame_) + frame_duration() - HalfCycles(timings.interrupt_time);
}
}
void set_time_since_interrupt(const HalfCycles time) {
// Advance using run_for to ensure that all proper CRT interactions occurred.
const auto timings = get_timings();
const auto target = (time + timings.interrupt_time) % frame_duration();
const auto now = HalfCycles(time_into_frame_);
// Maybe this is easy?
if(target == now) return;
// Is the time within this frame?
if(time > now) {
run_for(target - time);
return;
}
// Then it's necessary to finish this frame and run into the next.
run_for(frame_duration() - now + time);
}
public:
Video() :
crt_(227 * 2, 2, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red2Green2Blue2)
crt_(half_cycles_per_line(), 2, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red2Green2Blue2)
{
// Show only the centre 80% of the TV frame.
crt_.set_display_type(Outputs::Display::DisplayType::RGB);
crt_.set_visible_area(Outputs::Display::Rect(0.1f, 0.1f, 0.8f, 0.8f));
// Get the CRT roughly into phase.
//
// TODO: this is coupled to an assumption about the initial CRT. Fix.
const auto timings = get_timings();
crt_.output_blank(timings.lines_per_frame*timings.half_cycles_per_line - timings.interrupt_time);
}
void set_video_source(const uint8_t *source) {
@@ -290,7 +331,7 @@ template <VideoTiming timing> class Video {
}
// If not, it'll be in the next batch.
return timings.interrupt_time + timings.cycles_per_line * timings.lines_per_frame - time_into_frame_;
return timings.interrupt_time + timings.half_cycles_per_line * timings.lines_per_frame - time_into_frame_;
}
/*!
@@ -305,21 +346,22 @@ template <VideoTiming timing> class Video {
@returns How many cycles the [ULA/gate array] would delay the CPU for if it were to recognise that contention
needs to be applied in @c offset half-cycles from now.
*/
int access_delay(HalfCycles offset) const {
HalfCycles access_delay(HalfCycles offset) const {
constexpr auto timings = get_timings();
const int delay_time = (time_into_frame_ + offset.as<int>() + timings.contention_leadin) % (timings.cycles_per_line * timings.lines_per_frame);
const int delay_time = (time_into_frame_ + offset.as<int>() + timings.contention_leadin) % (timings.half_cycles_per_line * timings.lines_per_frame);
assert(!(delay_time&1));
// Check for a time within the no-contention window.
if(delay_time >= (191*timings.cycles_per_line + timings.contention_duration)) {
if(delay_time >= (191*timings.half_cycles_per_line + timings.contention_duration)) {
return 0;
}
const int time_into_line = delay_time % timings.cycles_per_line;
const int time_into_line = delay_time % timings.half_cycles_per_line;
if(time_into_line >= timings.contention_duration) {
return 0;
}
return timings.delays[time_into_line & 15];
return HalfCycles(timings.delays[(time_into_line >> 1) & 7]);
}
/*!
@@ -327,20 +369,24 @@ template <VideoTiming timing> class Video {
*/
uint8_t get_floating_value() const {
constexpr auto timings = get_timings();
const int line = time_into_frame_ / timings.cycles_per_line;
if(line >= 192) return 0xff;
const uint8_t out_of_bounds = (timing == Timing::Plus3) ? last_contended_access_ : 0xff;
const int time_into_line = time_into_frame_ % timings.cycles_per_line;
const int line = time_into_frame_ / timings.half_cycles_per_line;
if(line >= 192) {
return out_of_bounds;
}
const int time_into_line = time_into_frame_ % timings.half_cycles_per_line;
if(time_into_line >= 256 || (time_into_line&8)) {
return last_contended_access_;
return out_of_bounds;
}
// The +2a and +3 always return the low bit as set.
if constexpr (timing == VideoTiming::Plus3) {
return last_fetches_[(time_into_line >> 1) & 3] | 1;
const uint8_t value = last_fetches_[(time_into_line >> 1) & 3];
if constexpr (timing == Timing::Plus3) {
return value | 1;
}
return last_fetches_[(time_into_line >> 1) & 3];
return value;
}
/*!
@@ -349,7 +395,7 @@ template <VideoTiming timing> class Video {
bus is accessed when the gate array isn't currently reading.
*/
void set_last_contended_area_access([[maybe_unused]] uint8_t value) {
if constexpr (timing == VideoTiming::Plus3) {
if constexpr (timing == Timing::Plus3) {
last_contended_access_ = value | 1;
}
}
@@ -358,6 +404,7 @@ template <VideoTiming timing> class Video {
Sets the current border colour.
*/
void set_border_colour(uint8_t colour) {
border_byte_ = colour;
border_colour_ = palette[colour];
}
@@ -376,11 +423,17 @@ template <VideoTiming timing> class Video {
crt_.set_display_type(type);
}
/*! Gets the display type. */
Outputs::Display::DisplayType get_display_type() const {
return crt_.get_display_type();
}
private:
int time_into_frame_ = 0;
Outputs::CRT::CRT crt_;
const uint8_t *memory_ = nullptr;
uint8_t border_colour_ = 0;
uint8_t border_byte_ = 0;
uint8_t *pixel_target_ = nullptr;
int attribute_address_ = 0;
@@ -393,6 +446,8 @@ template <VideoTiming timing> class Video {
uint8_t last_fetches_[4] = {0xff, 0xff, 0xff, 0xff};
uint8_t last_contended_access_ = 0xff;
friend struct State;
#define RGB(r, g, b) (r << 4) | (g << 2) | b
static constexpr uint8_t palette[] = {
RGB(0, 0, 0), RGB(0, 0, 2), RGB(2, 0, 0), RGB(2, 0, 2),
@@ -403,6 +458,41 @@ template <VideoTiming timing> class Video {
#undef RGB
};
struct State: public Reflection::StructImpl<State> {
uint8_t border_colour = 0;
int half_cycles_since_interrupt = 0;
bool flash = 0;
int flash_counter = 0;
bool is_alternate_line = false;
State() {
if(needs_declare()) {
DeclareField(border_colour);
DeclareField(half_cycles_since_interrupt);
DeclareField(flash);
DeclareField(flash_counter);
DeclareField(is_alternate_line);
}
}
template <typename Video> State(const Video &source) : State() {
border_colour = source.border_byte_;
flash = source.flash_mask_;
flash_counter = source.flash_counter_;
is_alternate_line = source. is_alternate_line_;
half_cycles_since_interrupt = source.time_since_interrupt().template as<int>();
}
template <typename Video> void apply(Video &target) {
target.set_border_colour(border_colour);
target.flash_mask_ = flash ? 0xff : 0x00;
target.flash_counter_ = flash_counter;
target.is_alternate_line_ = is_alternate_line;
target.set_time_since_interrupt(HalfCycles(half_cycles_since_interrupt));
}
};
}
}
}

View File

@@ -8,9 +8,9 @@
#include "ZXSpectrum.hpp"
#include "State.hpp"
#include "Video.hpp"
#define LOG_PREFIX "[Spectrum] "
#include "../Keyboard/Keyboard.hpp"
#include "../../../Activity/Source.hpp"
#include "../../MachineTypes.hpp"
@@ -24,7 +24,9 @@
// just grab the CPC's version of an FDC.
#include "../../AmstradCPC/FDC.hpp"
#define LOG_PREFIX "[Spectrum] "
#include "../../../Outputs/Log.hpp"
#include "../../../Outputs/Speaker/Implementation/CompoundSource.hpp"
#include "../../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#include "../../../Outputs/Speaker/Implementation/SampleSource.hpp"
@@ -39,12 +41,76 @@
#include "../../../ClockReceiver/JustInTime.hpp"
#include "../../../Processors/Z80/State/State.hpp"
#include "../Keyboard/Keyboard.hpp"
#include <array>
namespace {
/*!
Provides a simultaneous Kempston and Interface 2-style joystick.
*/
class Joystick: public Inputs::ConcreteJoystick {
public:
Joystick() :
ConcreteJoystick({
Input(Input::Up),
Input(Input::Down),
Input(Input::Left),
Input(Input::Right),
Input(Input::Fire)
}) {}
void did_set_input(const Input &digital_input, bool is_active) final {
#define APPLY_KEMPSTON(b) if(is_active) kempston_ |= b; else kempston_ &= ~b;
#define APPLY_SINCLAIR(b) if(is_active) sinclair_ &= ~b; else sinclair_ |= b;
switch(digital_input.type) {
default: return;
case Input::Right:
APPLY_KEMPSTON(0x01);
APPLY_SINCLAIR(0x0208);
break;
case Input::Left:
APPLY_KEMPSTON(0x02);
APPLY_SINCLAIR(0x0110);
break;
case Input::Down:
APPLY_KEMPSTON(0x04);
APPLY_SINCLAIR(0x0404);
break;
case Input::Up:
APPLY_KEMPSTON(0x08);
APPLY_SINCLAIR(0x0802);
break;
case Input::Fire:
APPLY_KEMPSTON(0x10);
APPLY_SINCLAIR(0x1001);
break;
}
#undef APPLY_KEMPSTON
#undef APPLY_SINCLAIR
}
/// @returns The value that a Kempston joystick interface would report if this joystick
/// were plugged into it.
uint8_t get_kempston() {
return kempston_;
}
/// @returns The value that a Sinclair interface would report if this joystick
/// were plugged into it via @c port (which should be either 0 or 1, for ports 1 or 2).
uint8_t get_sinclair(int port) {
return uint8_t(sinclair_ >> (port * 8));
}
private:
uint8_t kempston_ = 0x00;
uint16_t sinclair_ = 0xffff;
};
}
namespace Sinclair {
namespace ZXSpectrum {
@@ -58,6 +124,7 @@ template<Model model> class ConcreteMachine:
public CPU::Z80::BusHandler,
public Machine,
public MachineTypes::AudioProducer,
public MachineTypes::JoystickMachine,
public MachineTypes::MappedKeyboardMachine,
public MachineTypes::MediaTarget,
public MachineTypes::ScanProducer,
@@ -81,14 +148,39 @@ template<Model model> class ConcreteMachine:
// With only the +2a and +3 currently supported, the +3 ROM is always
// the one required.
const auto roms =
rom_fetcher({ {"ZXSpectrum", "the +2a/+3 ROM", "plus3.rom", 64 * 1024, 0x96e3c17a} });
std::vector<ROMMachine::ROM> rom_names;
const std::string machine = "ZXSpectrum";
switch(model) {
case Model::SixteenK:
case Model::FortyEightK:
rom_names.emplace_back(machine, "the 48kb ROM", "48.rom", 16 * 1024, 0xddee531f);
break;
case Model::OneTwoEightK:
rom_names.emplace_back(machine, "the 128kb ROM", "128.rom", 32 * 1024, 0x2cbe8995);
break;
case Model::Plus2:
rom_names.emplace_back(machine, "the +2 ROM", "plus2.rom", 32 * 1024, 0xe7a517dc);
break;
case Model::Plus2a:
case Model::Plus3: {
const std::initializer_list<uint32_t> crc32s = { 0x96e3c17a, 0xbe0d9ec4 };
rom_names.emplace_back(machine, "the +2a/+3 ROM", "plus3.rom", 64 * 1024, crc32s);
} break;
}
const auto roms = rom_fetcher(rom_names);
if(!roms[0]) throw ROMMachine::Error::MissingROMs;
memcpy(rom_.data(), roms[0]->data(), std::min(rom_.size(), roms[0]->size()));
// Register for sleeping notifications.
tape_player_.set_clocking_hint_observer(this);
// Attach a couple of joysticks.
joysticks_.emplace_back(new Joystick);
joysticks_.emplace_back(new Joystick);
// Set up initial memory map.
update_memory_map();
set_video_address();
@@ -103,6 +195,29 @@ template<Model model> class ConcreteMachine:
duration_to_press_enter_ = Cycles(5 * clock_rate());
keyboard_.set_key_state(ZX::Keyboard::KeyEnter, true);
}
// Install state if supplied.
if(target.state) {
const auto state = static_cast<State *>(target.state.get());
state->z80.apply(z80_);
state->video.apply(*video_.last_valid());
state->ay.apply(ay_);
// If this is a 48k or 16k machine, remap source data from its original
// linear form to whatever the banks end up being; otherwise copy as is.
if(model <= Model::FortyEightK) {
const size_t num_banks = std::min(size_t(48*1024), state->ram.size()) >> 14;
for(size_t c = 0; c < num_banks; c++) {
memcpy(&write_pointers_[c + 1][(c+1) * 0x4000], &state->ram[c * 0x4000], 0x4000);
}
} else {
memcpy(ram_.data(), state->ram.data(), std::min(ram_.size(), state->ram.size()));
port1ffd_ = state->last_1ffd;
port7ffd_ = state->last_7ffd;
update_memory_map();
}
}
}
~ConcreteMachine() {
@@ -110,7 +225,7 @@ template<Model model> class ConcreteMachine:
}
static constexpr unsigned int clock_rate() {
// constexpr unsigned int ClockRate = 3'500'000;
constexpr unsigned int OriginalClockRate = 3'500'000;
constexpr unsigned int Plus3ClockRate = 3'546'875; // See notes below; this is a guess.
// Notes on timing for the +2a and +3:
@@ -137,7 +252,7 @@ template<Model model> class ConcreteMachine:
// the Spectrum is a PAL machine with a fixed colour phase relationship. For
// this emulator's world, that's a first!
return Plus3ClockRate;
return model < Model::OneTwoEightK ? OriginalClockRate : Plus3ClockRate;
}
// MARK: - TimedMachine.
@@ -181,6 +296,10 @@ template<Model model> class ConcreteMachine:
video_->set_display_type(display_type);
}
Outputs::Display::DisplayType get_display_type() const override {
return video_->get_display_type();
}
// MARK: - BusHandler.
forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
@@ -189,18 +308,93 @@ template<Model model> class ConcreteMachine:
const uint16_t address = cycle.address ? *cycle.address : 0x0000;
// Apply contention if necessary.
if(
is_contended_[address >> 14] &&
cycle.operation >= PartialMachineCycle::ReadOpcodeStart &&
cycle.operation <= PartialMachineCycle::WriteStart) {
// Assumption here: the trigger for the ULA inserting a delay is the falling edge
if constexpr (model >= Model::Plus2a) {
// Model applied: the trigger for the ULA inserting a delay is the falling edge
// of MREQ, which is always half a cycle into a read or write.
//
// TODO: somehow provide that information in the PartialMachineCycle?
if(
is_contended_[address >> 14] &&
cycle.operation >= PartialMachineCycle::ReadOpcodeStart &&
cycle.operation <= PartialMachineCycle::WriteStart) {
const HalfCycles delay = video_.last_valid()->access_delay(video_.time_since_flush() + HalfCycles(1));
advance(cycle.length + delay);
return delay;
const auto delay = video_.last_valid()->access_delay(video_.time_since_flush());
advance(cycle.length + delay);
return delay;
}
} else {
switch(cycle.operation) {
case CPU::Z80::PartialMachineCycle::Input:
case CPU::Z80::PartialMachineCycle::Output:
case CPU::Z80::PartialMachineCycle::Read:
case CPU::Z80::PartialMachineCycle::Write:
case CPU::Z80::PartialMachineCycle::ReadOpcode:
case CPU::Z80::PartialMachineCycle::Interrupt:
// For these, carry on into the actual handler, below.
break;
// For anything else that isn't listed below, just advance
// time and conclude here.
default:
advance(cycle.length);
return HalfCycles(0);
case CPU::Z80::PartialMachineCycle::InputStart:
case CPU::Z80::PartialMachineCycle::OutputStart: {
// The port address is loaded prior to IOREQ being visible; a contention
// always occurs if it is in the $4000$8000 range regardless of current
// memory mapping.
HalfCycles delay;
HalfCycles time = video_.time_since_flush();
if((address & 0xc000) == 0x4000) {
for(int c = 0; c < ((address & 1) ? 4 : 2); c++) {
const auto next_delay = video_.last_valid()->access_delay(time);
delay += next_delay;
time += next_delay + 2;
}
} else {
if(!(address & 1)) {
delay = video_.last_valid()->access_delay(time + HalfCycles(2));
}
}
advance(cycle.length + delay);
return delay;
} break;
case PartialMachineCycle::ReadOpcodeStart:
case PartialMachineCycle::ReadStart:
case PartialMachineCycle::WriteStart: {
// These all start by loading the address bus, then set MREQ
// half a cycle later.
if(is_contended_[address >> 14]) {
const auto delay = video_.last_valid()->access_delay(video_.time_since_flush());
advance(cycle.length + delay);
return delay;
}
} break;
case PartialMachineCycle::Internal: {
// Whatever's on the address bus will remain there, without IOREQ or
// MREQ interceding, for this entire bus cycle. So apply contentions
// all the way along.
if(is_contended_[address >> 14]) {
const auto half_cycles = cycle.length.as<int>();
assert(!(half_cycles & 1));
HalfCycles time = video_.time_since_flush();
HalfCycles delay;
for(int c = 0; c < half_cycles; c += 2) {
const auto next_delay = video_.last_valid()->access_delay(time);
delay += next_delay;
time += next_delay + 2;
}
advance(cycle.length + delay);
return delay;
}
} break;
}
}
// For all other machine cycles, model the action as happening at the end of the machine cycle;
@@ -214,7 +408,7 @@ template<Model model> class ConcreteMachine:
// Fast loading: ROM version.
//
// The below patches over part of the 'LD-BYTES' routine from the 48kb ROM.
if(use_fast_tape_hack_ && address == 0x056b && read_pointers_[0] == &rom_[0xc000]) {
if(use_fast_tape_hack_ && address == 0x056b && read_pointers_[0] == &rom_[classic_rom_offset()]) {
// Stop pressing enter, if neccessry.
if(duration_to_press_enter_ > Cycles(0)) {
duration_to_press_enter_ = Cycles(0);
@@ -226,12 +420,24 @@ template<Model model> class ConcreteMachine:
break;
}
}
[[fallthrough]];
case PartialMachineCycle::Read:
if constexpr (model == Model::SixteenK) {
// Assumption: with nothing mapped above 0x8000 on the 16kb Spectrum,
// read the floating bus.
if(address >= 0x8000) {
*cycle.value = video_->get_floating_value();
break;
}
}
*cycle.value = read_pointers_[address >> 14][address];
if(is_contended_[address >> 14]) {
video_->set_last_contended_area_access(*cycle.value);
if constexpr (model >= Model::Plus2a) {
if(is_contended_[address >> 14]) {
video_->set_last_contended_area_access(*cycle.value);
}
}
break;
@@ -243,12 +449,17 @@ template<Model model> class ConcreteMachine:
write_pointers_[address >> 14][address] = *cycle.value;
// Fill the floating bus buffer if this write is within the contended area.
if(is_contended_[address >> 14]) {
video_->set_last_contended_area_access(*cycle.value);
if constexpr (model >= Model::Plus2a) {
// Fill the floating bus buffer if this write is within the contended area.
if(is_contended_[address >> 14]) {
video_->set_last_contended_area_access(*cycle.value);
}
}
break;
// Partial port decodings here and in ::Input are as documented
// at https://worldofspectrum.org/faq/reference/ports.htm
case PartialMachineCycle::Output:
// Test for port FE.
if(!(address&1)) {
@@ -263,55 +474,70 @@ template<Model model> class ConcreteMachine:
}
// Test for classic 128kb paging register (i.e. port 7ffd).
if((address & 0xc002) == 0x4000) {
if (
(model >= Model::OneTwoEightK && model <= Model::Plus2 && (address & 0x8002) == 0x0000) ||
(model >= Model::Plus2a && (address & 0xc002) == 0x4000)
) {
port7ffd_ = *cycle.value;
update_memory_map();
// Set the proper video base pointer.
set_video_address();
// Potentially lock paging, _after_ the current
// port values have taken effect.
disable_paging_ |= *cycle.value & 0x20;
}
// Test for +2a/+3 paging (i.e. port 1ffd).
if((address & 0xf002) == 0x1000) {
port1ffd_ = *cycle.value;
update_memory_map();
update_video_base();
if constexpr (model >= Model::Plus2a) {
if((address & 0xf002) == 0x1000) {
port1ffd_ = *cycle.value;
update_memory_map();
update_video_base();
if constexpr (model == Model::Plus3) {
fdc_->set_motor_on(*cycle.value & 0x08);
if constexpr (model == Model::Plus3) {
fdc_->set_motor_on(*cycle.value & 0x08);
}
}
}
if((address & 0xc002) == 0xc000) {
// Select AY register.
update_audio();
GI::AY38910::Utility::select_register(ay_, *cycle.value);
}
if((address & 0xc002) == 0x8000) {
// Write to AY register.
update_audio();
GI::AY38910::Utility::write_data(ay_, *cycle.value);
// Route to the AY if one is fitted.
if constexpr (model >= Model::OneTwoEightK) {
switch(address & 0xc002) {
case 0xc000:
// Select AY register.
update_audio();
GI::AY38910::Utility::select_register(ay_, *cycle.value);
break;
case 0x8000:
// Write to AY register.
update_audio();
GI::AY38910::Utility::write_data(ay_, *cycle.value);
break;
}
}
// Check for FDC accesses.
if constexpr (model == Model::Plus3) {
switch(address) {
switch(address & 0xf002) {
default: break;
case 0x3ffd: case 0x2ffd:
case 0x3000: case 0x2000:
fdc_->write((address >> 12) & 1, *cycle.value);
break;
}
}
break;
case PartialMachineCycle::Input:
case PartialMachineCycle::Input: {
bool did_match = false;
*cycle.value = 0xff;
if(!(address&32)) {
did_match = true;
*cycle.value &= static_cast<Joystick *>(joysticks_[0].get())->get_kempston();
}
if(!(address&1)) {
did_match = true;
// Port FE:
//
// address b8+: mask of keyboard lines to select
@@ -321,6 +547,10 @@ template<Model model> class ConcreteMachine:
*cycle.value &= keyboard_.read(address);
*cycle.value &= tape_player_.get_input() ? 0xbf : 0xff;
// Add Joystick input on top.
if(!(address&0x1000)) *cycle.value &= static_cast<Joystick *>(joysticks_[0].get())->get_sinclair(0);
if(!(address&0x0800)) *cycle.value &= static_cast<Joystick *>(joysticks_[1].get())->get_sinclair(1);
// If this read is within 200 cycles of the previous,
// count it as an adjacent hit; if 20 of those have
// occurred then start the tape motor.
@@ -339,27 +569,46 @@ template<Model model> class ConcreteMachine:
}
}
if((address & 0xc002) == 0xc000) {
// Read from AY register.
update_audio();
*cycle.value &= GI::AY38910::Utility::read(ay_);
if constexpr (model >= Model::OneTwoEightK) {
if((address & 0xc002) == 0xc000) {
did_match = true;
// Read from AY register.
update_audio();
*cycle.value &= GI::AY38910::Utility::read(ay_);
}
}
// Check for a floating bus read; these are particularly arcane
// on the +2a/+3. See footnote to https://spectrumforeveryone.com/technical/memory-contention-floating-bus/
// and, much more rigorously, http://sky.relative-path.com/zx/floating_bus.html
if(!disable_paging_ && (address & 0xf003) == 0x0001) {
*cycle.value &= video_->get_floating_value();
if constexpr (model >= Model::Plus2a) {
// Check for a +2a/+3 floating bus read; these are particularly arcane.
// See footnote to https://spectrumforeveryone.com/technical/memory-contention-floating-bus/
// and, much more rigorously, http://sky.relative-path.com/zx/floating_bus.html
if(!disable_paging_ && (address & 0xf003) == 0x0001) {
*cycle.value &= video_->get_floating_value();
}
}
if constexpr (model == Model::Plus3) {
switch(address) {
switch(address & 0xf002) {
default: break;
case 0x3ffd: case 0x2ffd:
case 0x3000: case 0x2000:
*cycle.value &= fdc_->read((address >> 12) & 1);
break;
}
}
if constexpr (model <= Model::Plus2) {
if(!did_match) {
*cycle.value = video_->get_floating_value();
}
}
} break;
case PartialMachineCycle::Interrupt:
// At least one piece of Spectrum software, Escape from M.O.N.J.A.S. explicitly
// assumes that a 0xff value will be on the bus during an interrupt acknowledgment.
// I wasn't otherwise aware that this value is reliable.
*cycle.value = 0xff;
break;
}
@@ -480,6 +729,7 @@ template<Model model> class ConcreteMachine:
auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly); // OptionsType is arbitrary, but not optional.
options->automatic_tape_motor_control = use_automatic_tape_motor_control_;
options->quickload = allow_fast_tape_hack_;
options->output = get_video_signal_configurable();
return options;
}
@@ -568,13 +818,21 @@ template<Model model> class ConcreteMachine:
set_memory(2, 2);
set_memory(3, port7ffd_ & 7);
}
// Potentially lock paging, _after_ the current
// port values have taken effect.
disable_paging_ = port7ffd_ & 0x20;
}
void set_memory(int bank, uint8_t source) {
is_contended_[bank] = (source >= 4 && source < 8);
if constexpr (model >= Model::Plus2a) {
is_contended_[bank] = source >= 4 && source < 8;
} else {
is_contended_[bank] = source < 0x80 && source & 1;
}
pages_[bank] = source;
uint8_t *read = (source < 0x80) ? &ram_[source * 16384] : &rom_[(source & 0x7f) * 16384];
uint8_t *const read = (source < 0x80) ? &ram_[source * 16384] : &rom_[(source & 0x7f) * 16384];
const auto offset = bank*16384;
read_pointers_[bank] = read - offset;
@@ -607,8 +865,15 @@ template<Model model> class ConcreteMachine:
}
// MARK: - Video.
static constexpr VideoTiming video_timing = VideoTiming::Plus3;
JustInTimeActor<Video<video_timing>> video_;
using VideoType =
std::conditional_t<
model <= Model::FortyEightK, Video::Video<Video::Timing::FortyEightK>,
std::conditional_t<
model <= Model::Plus2, Video::Video<Video::Timing::OneTwoEightK>,
Video::Video<Video::Timing::Plus3>
>
>;
JustInTimeActor<VideoType> video_;
// MARK: - Keyboard.
Sinclair::ZX::Keyboard::Keyboard keyboard_;
@@ -695,11 +960,33 @@ template<Model model> class ConcreteMachine:
return true;
}
static constexpr int classic_rom_offset() {
switch(model) {
case Model::SixteenK:
case Model::FortyEightK:
return 0x0000;
case Model::OneTwoEightK:
case Model::Plus2:
return 0x4000;
case Model::Plus2a:
case Model::Plus3:
return 0xc000;
}
}
// MARK: - Disc.
JustInTimeActor<Amstrad::FDC, Cycles> fdc_;
// MARK: - Automatic startup.
Cycles duration_to_press_enter_;
// MARK: - Joysticks
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
return joysticks_;
}
};
@@ -712,8 +999,12 @@ Machine *Machine::ZXSpectrum(const Analyser::Static::Target *target, const ROMMa
const auto zx_target = dynamic_cast<const Analyser::Static::ZXSpectrum::Target *>(target);
switch(zx_target->model) {
case Model::Plus2a: return new ConcreteMachine<Model::Plus2a>(*zx_target, rom_fetcher);
case Model::Plus3: return new ConcreteMachine<Model::Plus3>(*zx_target, rom_fetcher);
case Model::SixteenK: return new ConcreteMachine<Model::SixteenK>(*zx_target, rom_fetcher);
case Model::FortyEightK: return new ConcreteMachine<Model::FortyEightK>(*zx_target, rom_fetcher);
case Model::OneTwoEightK: return new ConcreteMachine<Model::OneTwoEightK>(*zx_target, rom_fetcher);
case Model::Plus2: return new ConcreteMachine<Model::Plus2>(*zx_target, rom_fetcher);
case Model::Plus2a: return new ConcreteMachine<Model::Plus2a>(*zx_target, rom_fetcher);
case Model::Plus3: return new ConcreteMachine<Model::Plus3>(*zx_target, rom_fetcher);
}
return nullptr;

View File

@@ -31,7 +31,7 @@ class Machine {
friend Configurable::DisplayOption<Options>;
friend Configurable::QuickloadOption<Options>;
public:
bool automatic_tape_motor_control;
bool automatic_tape_motor_control = true;
Options(Configurable::OptionsType type) :
Configurable::DisplayOption<Options>(type == Configurable::OptionsType::UserFriendly ? Configurable::Display::RGB : Configurable::Display::CompositeColour),

View File

@@ -0,0 +1,26 @@
//
// StateProducer.hpp
// Clock Signal
//
// Created by Thomas Harte on 24/04/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef State_h
#define State_h
#include <memory>
#include "../Analyser/Static/StaticAnalyser.hpp"
namespace MachineTypes {
struct StateProducer {
// TODO.
// virtual bool get_state(Analyser::Static::State *, [[maybe_unused]] bool advance_to_simple = false) {
// return false;
// }
};
};
#endif /* State_h */

View File

@@ -182,6 +182,7 @@ std::vector<std::string> Machine::AllMachines(Type type, bool long_names) {
AddName(Oric);
AddName(Vic20);
AddName(ZX8081);
AddName(ZXSpectrum);
}
#undef AddName

View File

@@ -131,13 +131,3 @@ bool Typer::type_next_character() {
return true;
}
// MARK: - Character mapper
const uint16_t *CharacterMapper::table_lookup_sequence_for_character(const KeySequence *sequences, std::size_t length, char character) const {
std::size_t ucharacter = size_t((unsigned char)character);
if(ucharacter >= (length / sizeof(KeySequence))) return nullptr;
if(sequences[ucharacter][0] == MachineTypes::MappedKeyboardMachine::KeyNotMapped) return nullptr;
return sequences[ucharacter];
}

View File

@@ -44,11 +44,15 @@ class CharacterMapper {
typedef uint16_t KeySequence[16];
/*!
Provided in the base class as a convenience: given the lookup table of key sequences @c sequences,
with @c length entries, returns the sequence for character @c character if it exists; otherwise
returns @c nullptr.
Provided in the base class as a convenience: given the C array of key sequences @c sequences,
returns the sequence for character @c character if it exists; otherwise returns @c nullptr.
*/
const uint16_t *table_lookup_sequence_for_character(const KeySequence *sequences, std::size_t length, char character) const;
template <typename Collection> const uint16_t *table_lookup_sequence_for_character(const Collection &sequences, char character) const {
std::size_t ucharacter = size_t((unsigned char)character);
if(ucharacter >= sizeof(sequences) / sizeof(KeySequence)) return nullptr;
if(sequences[ucharacter][0] == MachineTypes::MappedKeyboardMachine::KeyNotMapped) return nullptr;
return sequences[ucharacter];
}
};
/*!

View File

@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objectVersion = 50;
objects = {
/* Begin PBXBuildFile section */
@@ -173,6 +173,8 @@
4B2A539F1D117D36003C6002 /* CSAudioQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A53911D117D36003C6002 /* CSAudioQueue.m */; };
4B2B3A4B1F9B8FA70062DABF /* Typer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2B3A471F9B8FA70062DABF /* Typer.cpp */; };
4B2B3A4C1F9B8FA70062DABF /* MemoryFuzzer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2B3A481F9B8FA70062DABF /* MemoryFuzzer.cpp */; };
4B2B946526377C0200E7097C /* SZX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2B946326377C0200E7097C /* SZX.cpp */; };
4B2B946626377C0200E7097C /* SZX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2B946326377C0200E7097C /* SZX.cpp */; };
4B2BF19123DCC6A200C3AD60 /* BD500.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7BA03523CEB86000B98D9E /* BD500.cpp */; };
4B2BF19223DCC6A800C3AD60 /* STX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7BA03323C58B1E00B98D9E /* STX.cpp */; };
4B2BF19623E10F0100C3AD60 /* CSHighPrecisionTimer.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B2BF19523E10F0000C3AD60 /* CSHighPrecisionTimer.m */; };
@@ -478,6 +480,11 @@
4B89453D201967B4007DE474 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B894516201967B4007DE474 /* StaticAnalyser.cpp */; };
4B89453E201967B4007DE474 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B894517201967B4007DE474 /* StaticAnalyser.cpp */; };
4B89453F201967B4007DE474 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B894517201967B4007DE474 /* StaticAnalyser.cpp */; };
4B8DD3682633B2D400B3C866 /* SpectrumVideoContentionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B8DD3672633B2D400B3C866 /* SpectrumVideoContentionTests.mm */; };
4B8DD3862634D37E00B3C866 /* SNA.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8DD3842634D37E00B3C866 /* SNA.cpp */; };
4B8DD3872634D37E00B3C866 /* SNA.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8DD3842634D37E00B3C866 /* SNA.cpp */; };
4B8DD39726360DDF00B3C866 /* Z80.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8DD39526360DDF00B3C866 /* Z80.cpp */; };
4B8DD39826360DDF00B3C866 /* Z80.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8DD39526360DDF00B3C866 /* Z80.cpp */; };
4B8DF4D825465B7500F3433C /* IIgsMemoryMapTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B8DF4D725465B7500F3433C /* IIgsMemoryMapTests.mm */; };
4B8DF4F9254E36AE00F3433C /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8DF4F7254E36AD00F3433C /* Video.cpp */; };
4B8DF4FA254E36AE00F3433C /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8DF4F7254E36AD00F3433C /* Video.cpp */; };
@@ -917,6 +924,7 @@
4BDA00E022E644AF00AC3CD0 /* CSROMReceiverView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BDA00DF22E644AF00AC3CD0 /* CSROMReceiverView.m */; };
4BDA00E422E663B900AC3CD0 /* NSData+CRC32.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BDA00E222E663B900AC3CD0 /* NSData+CRC32.m */; };
4BDA00E622E699B000AC3CD0 /* CSMachine.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A53961D117D36003C6002 /* CSMachine.mm */; };
4BDA8235261E8E000021AA19 /* Z80ContentionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BDA8234261E8E000021AA19 /* Z80ContentionTests.mm */; };
4BDACBEC22FFA5D20045EF7E /* ncr5380.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BDACBEA22FFA5D20045EF7E /* ncr5380.cpp */; };
4BDACBED22FFA5D20045EF7E /* ncr5380.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BDACBEA22FFA5D20045EF7E /* ncr5380.cpp */; };
4BDB61EB2032806E0048AF91 /* CSAtari2600.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539A1D117D36003C6002 /* CSAtari2600.mm */; };
@@ -1009,7 +1017,7 @@
4B047075201ABC180047AB0D /* Cartridge.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Cartridge.hpp; sourceTree = "<group>"; };
4B049CDC1DA3C82F00322067 /* BCDTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BCDTest.swift; sourceTree = "<group>"; };
4B04B65622A58CB40006AB58 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
4B05401D219D1618001BF69C /* ScanTarget.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ScanTarget.cpp; path = ../../Outputs/ScanTarget.cpp; sourceTree = "<group>"; };
4B05401D219D1618001BF69C /* ScanTarget.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ScanTarget.cpp; sourceTree = "<group>"; };
4B055A6A1FAE763F0060FFFF /* Clock Signal Kiosk */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "Clock Signal Kiosk"; sourceTree = BUILT_PRODUCTS_DIR; };
4B055A771FAE78210060FFFF /* SDL2.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SDL2.framework; path = ../../../../Library/Frameworks/SDL2.framework; sourceTree = SOURCE_ROOT; };
4B055A7C1FAE84A50060FFFF /* main.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = main.cpp; sourceTree = "<group>"; };
@@ -1057,8 +1065,8 @@
4B0CCC431C62D0B3001CAC5F /* CRT.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRT.hpp; sourceTree = "<group>"; };
4B0E04E81FC9E5DA00F43484 /* CAS.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = CAS.cpp; sourceTree = "<group>"; };
4B0E04E91FC9E5DA00F43484 /* CAS.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CAS.hpp; sourceTree = "<group>"; };
4B0E04F81FC9FA3000F43484 /* 9918.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = 9918.hpp; path = 9918/9918.hpp; sourceTree = "<group>"; };
4B0E04F91FC9FA3100F43484 /* 9918.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = 9918.cpp; path = 9918/9918.cpp; sourceTree = "<group>"; };
4B0E04F81FC9FA3000F43484 /* 9918.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 9918.hpp; sourceTree = "<group>"; };
4B0E04F91FC9FA3100F43484 /* 9918.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = 9918.cpp; sourceTree = "<group>"; };
4B0E61051FF34737002A9DBD /* MSX.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = MSX.cpp; path = Parsers/MSX.cpp; sourceTree = "<group>"; };
4B0E61061FF34737002A9DBD /* MSX.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = MSX.hpp; path = Parsers/MSX.hpp; sourceTree = "<group>"; };
4B0F1BB02602645900B85C66 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; };
@@ -1075,13 +1083,13 @@
4B0F1C1B2604EA1000B85C66 /* Keyboard.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Keyboard.cpp; sourceTree = "<group>"; };
4B0F1C212605996900B85C66 /* ZXSpectrumTAP.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ZXSpectrumTAP.cpp; sourceTree = "<group>"; };
4B0F1C222605996900B85C66 /* ZXSpectrumTAP.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ZXSpectrumTAP.hpp; sourceTree = "<group>"; };
4B0F1C3D26095AC600B85C66 /* FDC.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = FDC.hpp; path = AmstradCPC/FDC.hpp; sourceTree = "<group>"; };
4B0F1C3D26095AC600B85C66 /* FDC.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = FDC.hpp; sourceTree = "<group>"; };
4B0F94FC208C1A1600FE41D9 /* NIB.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = NIB.cpp; sourceTree = "<group>"; };
4B0F94FD208C1A1600FE41D9 /* NIB.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = NIB.hpp; sourceTree = "<group>"; };
4B0F9500208C42A300FE41D9 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Target.hpp; path = AppleII/Target.hpp; sourceTree = "<group>"; };
4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMSegmentEventSourceTests.mm; sourceTree = "<group>"; };
4B12C0EB1FCFA98D005BFD93 /* Keyboard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Keyboard.cpp; path = MSX/Keyboard.cpp; sourceTree = "<group>"; };
4B12C0EC1FCFA98D005BFD93 /* Keyboard.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Keyboard.hpp; path = MSX/Keyboard.hpp; sourceTree = "<group>"; };
4B12C0EB1FCFA98D005BFD93 /* Keyboard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Keyboard.cpp; sourceTree = "<group>"; };
4B12C0EC1FCFA98D005BFD93 /* Keyboard.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Keyboard.hpp; sourceTree = "<group>"; };
4B1414501B58848C00E04248 /* ClockSignal-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ClockSignal-Bridging-Header.h"; sourceTree = "<group>"; };
4B14145F1B58885000E04248 /* WolfgangLorenzTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WolfgangLorenzTests.swift; sourceTree = "<group>"; };
4B1414611B58888700E04248 /* KlausDormannTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KlausDormannTests.swift; sourceTree = "<group>"; };
@@ -1090,14 +1098,14 @@
4B14978D1EE4B4D200CE2596 /* CSZX8081.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSZX8081.h; sourceTree = "<group>"; };
4B14978E1EE4B4D200CE2596 /* CSZX8081.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSZX8081.mm; sourceTree = "<group>"; };
4B1497971EE4B97F00CE2596 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/ZX8081Options.xib"; sourceTree = SOURCE_ROOT; };
4B1558BE1F844ECD006E9A97 /* BitReverse.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = BitReverse.cpp; path = Data/BitReverse.cpp; sourceTree = "<group>"; };
4B1558BF1F844ECD006E9A97 /* BitReverse.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = BitReverse.hpp; path = Data/BitReverse.hpp; sourceTree = "<group>"; };
4B1558BE1F844ECD006E9A97 /* BitReverse.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = BitReverse.cpp; sourceTree = "<group>"; };
4B1558BF1F844ECD006E9A97 /* BitReverse.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = BitReverse.hpp; sourceTree = "<group>"; };
4B15A9FA208249BB005E6C8D /* StaticAnalyser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = StaticAnalyser.cpp; path = AppleII/StaticAnalyser.cpp; sourceTree = "<group>"; };
4B15A9FB208249BB005E6C8D /* StaticAnalyser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = AppleII/StaticAnalyser.hpp; sourceTree = "<group>"; };
4B1667F61FFF1E2400A16032 /* Konami.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Konami.hpp; path = MSX/Cartridges/Konami.hpp; sourceTree = "<group>"; };
4B1667F91FFF215E00A16032 /* ASCII16kb.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ASCII16kb.hpp; path = MSX/Cartridges/ASCII16kb.hpp; sourceTree = "<group>"; };
4B1667FA1FFF215E00A16032 /* ASCII8kb.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ASCII8kb.hpp; path = MSX/Cartridges/ASCII8kb.hpp; sourceTree = "<group>"; };
4B1667FB1FFF215F00A16032 /* KonamiWithSCC.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = KonamiWithSCC.hpp; path = MSX/Cartridges/KonamiWithSCC.hpp; sourceTree = "<group>"; };
4B1667F61FFF1E2400A16032 /* Konami.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Konami.hpp; sourceTree = "<group>"; };
4B1667F91FFF215E00A16032 /* ASCII16kb.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ASCII16kb.hpp; sourceTree = "<group>"; };
4B1667FA1FFF215E00A16032 /* ASCII8kb.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ASCII8kb.hpp; sourceTree = "<group>"; };
4B1667FB1FFF215F00A16032 /* KonamiWithSCC.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = KonamiWithSCC.hpp; sourceTree = "<group>"; };
4B17B58920A8A9D9007CCA8F /* StringSerialiser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = StringSerialiser.cpp; sourceTree = "<group>"; };
4B17B58A20A8A9D9007CCA8F /* StringSerialiser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = StringSerialiser.hpp; sourceTree = "<group>"; };
4B1B58F4246CC4E8009C171E /* State.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = State.cpp; sourceTree = "<group>"; };
@@ -1136,16 +1144,18 @@
4B2B3A481F9B8FA70062DABF /* MemoryFuzzer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MemoryFuzzer.cpp; sourceTree = "<group>"; };
4B2B3A491F9B8FA70062DABF /* MemoryFuzzer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MemoryFuzzer.hpp; sourceTree = "<group>"; };
4B2B3A4A1F9B8FA70062DABF /* Typer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Typer.hpp; sourceTree = "<group>"; };
4B2B946326377C0200E7097C /* SZX.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SZX.cpp; sourceTree = "<group>"; };
4B2B946426377C0200E7097C /* SZX.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SZX.hpp; sourceTree = "<group>"; };
4B2BF19423E10F0000C3AD60 /* CSHighPrecisionTimer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSHighPrecisionTimer.h; sourceTree = "<group>"; };
4B2BF19523E10F0000C3AD60 /* CSHighPrecisionTimer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSHighPrecisionTimer.m; sourceTree = "<group>"; };
4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TapePRG.cpp; sourceTree = "<group>"; };
4B2BFC5E1D613E0200BA3AA9 /* TapePRG.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TapePRG.hpp; sourceTree = "<group>"; };
4B2BFDB01DAEF5FF001A68B8 /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Video.cpp; path = Oric/Video.cpp; sourceTree = "<group>"; };
4B2BFDB11DAEF5FF001A68B8 /* Video.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Video.hpp; path = Oric/Video.hpp; sourceTree = "<group>"; };
4B2BFDB01DAEF5FF001A68B8 /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Video.cpp; sourceTree = "<group>"; };
4B2BFDB11DAEF5FF001A68B8 /* Video.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Video.hpp; sourceTree = "<group>"; };
4B2C45411E3C3896002A2389 /* cartridge.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = cartridge.png; sourceTree = "<group>"; };
4B2C455C1EC9442600FC74DD /* RegisterSizes.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RegisterSizes.hpp; sourceTree = "<group>"; };
4B2E2D9B1C3A070400138695 /* Electron.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Electron.cpp; path = Electron/Electron.cpp; sourceTree = "<group>"; };
4B2E2D9C1C3A070400138695 /* Electron.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Electron.hpp; path = Electron/Electron.hpp; sourceTree = "<group>"; };
4B2E2D9B1C3A070400138695 /* Electron.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Electron.cpp; sourceTree = "<group>"; };
4B2E2D9C1C3A070400138695 /* Electron.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Electron.hpp; sourceTree = "<group>"; };
4B2E86B525D7490E0024F1E9 /* ReactiveDevice.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ReactiveDevice.cpp; sourceTree = "<group>"; };
4B2E86B625D7490E0024F1E9 /* ReactiveDevice.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ReactiveDevice.hpp; sourceTree = "<group>"; };
4B2E86BC25D74F160024F1E9 /* Mouse.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Mouse.cpp; sourceTree = "<group>"; };
@@ -1158,8 +1168,8 @@
4B302183208A550100773308 /* DiskII.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DiskII.cpp; sourceTree = "<group>"; };
4B30512B1D989E2200B4FED8 /* Drive.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Drive.cpp; sourceTree = "<group>"; };
4B30512C1D989E2200B4FED8 /* Drive.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Drive.hpp; sourceTree = "<group>"; };
4B30512E1D98ACC600B4FED8 /* Plus3.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Plus3.cpp; path = Electron/Plus3.cpp; sourceTree = "<group>"; };
4B30512F1D98ACC600B4FED8 /* Plus3.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Plus3.hpp; path = Electron/Plus3.hpp; sourceTree = "<group>"; };
4B30512E1D98ACC600B4FED8 /* Plus3.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Plus3.cpp; sourceTree = "<group>"; };
4B30512F1D98ACC600B4FED8 /* Plus3.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Plus3.hpp; sourceTree = "<group>"; };
4B31B88F1FBFBCD800C140D5 /* Configurable.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Configurable.hpp; sourceTree = "<group>"; };
4B322DF31F5A26BF004EB04C /* 6502Implementation.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = 6502Implementation.hpp; sourceTree = "<group>"; };
4B322DF41F5A2714004EB04C /* 6502Storage.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = 6502Storage.hpp; sourceTree = "<group>"; };
@@ -1170,10 +1180,10 @@
4B322E051F5A30F5004EB04C /* Z80Implementation.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Z80Implementation.hpp; sourceTree = "<group>"; };
4B37EE801D7345A6006A09A4 /* BinaryDump.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BinaryDump.cpp; sourceTree = "<group>"; };
4B37EE811D7345A6006A09A4 /* BinaryDump.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = BinaryDump.hpp; sourceTree = "<group>"; };
4B38F3461F2EC11D00D9235D /* AmstradCPC.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = AmstradCPC.cpp; path = AmstradCPC/AmstradCPC.cpp; sourceTree = "<group>"; };
4B38F3471F2EC11D00D9235D /* AmstradCPC.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = AmstradCPC.hpp; path = AmstradCPC/AmstradCPC.hpp; sourceTree = "<group>"; };
4B3940E51DA83C8300427841 /* AsyncTaskQueue.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = AsyncTaskQueue.cpp; path = ../../Concurrency/AsyncTaskQueue.cpp; sourceTree = "<group>"; };
4B3940E61DA83C8300427841 /* AsyncTaskQueue.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = AsyncTaskQueue.hpp; path = ../../Concurrency/AsyncTaskQueue.hpp; sourceTree = "<group>"; };
4B38F3461F2EC11D00D9235D /* AmstradCPC.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AmstradCPC.cpp; sourceTree = "<group>"; };
4B38F3471F2EC11D00D9235D /* AmstradCPC.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AmstradCPC.hpp; sourceTree = "<group>"; };
4B3940E51DA83C8300427841 /* AsyncTaskQueue.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AsyncTaskQueue.cpp; sourceTree = "<group>"; };
4B3940E61DA83C8300427841 /* AsyncTaskQueue.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AsyncTaskQueue.hpp; sourceTree = "<group>"; };
4B3AF7D02413470E00873C0B /* Enum.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Enum.hpp; sourceTree = "<group>"; };
4B3AF7D12413472200873C0B /* Struct.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Struct.hpp; sourceTree = "<group>"; };
4B3BA0C21D318AEB005DD7A7 /* C1540Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = C1540Tests.swift; sourceTree = "<group>"; };
@@ -1232,8 +1242,8 @@
4B4518A81F76022000926311 /* DiskImageImplementation.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DiskImageImplementation.hpp; sourceTree = "<group>"; };
4B47F6C4241C87A100ED06F7 /* Struct.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Struct.cpp; sourceTree = "<group>"; };
4B49F0A823346F7A0045E6A6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/MacintoshOptions.xib"; sourceTree = SOURCE_ROOT; };
4B4A762E1DB1A3FA007AAE2E /* AY38910.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = AY38910.cpp; path = AY38910/AY38910.cpp; sourceTree = "<group>"; };
4B4A762F1DB1A3FA007AAE2E /* AY38910.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = AY38910.hpp; path = AY38910/AY38910.hpp; sourceTree = "<group>"; };
4B4A762E1DB1A3FA007AAE2E /* AY38910.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AY38910.cpp; sourceTree = "<group>"; };
4B4A762F1DB1A3FA007AAE2E /* AY38910.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AY38910.hpp; sourceTree = "<group>"; };
4B4B1A3A200198C900A0F866 /* KonamiSCC.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = KonamiSCC.cpp; sourceTree = "<group>"; };
4B4B1A3B200198C900A0F866 /* KonamiSCC.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = KonamiSCC.hpp; sourceTree = "<group>"; };
4B4DC81F1D2C2425003C5BF8 /* Vic20.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Vic20.cpp; sourceTree = "<group>"; };
@@ -1254,14 +1264,14 @@
4B51F70920A521D700AFA2C1 /* Source.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Source.hpp; sourceTree = "<group>"; };
4B51F70A20A521D700AFA2C1 /* Observer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Observer.hpp; sourceTree = "<group>"; };
4B54C0BB1F8D8E790050900F /* KeyboardMachine.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = KeyboardMachine.cpp; sourceTree = "<group>"; };
4B54C0BD1F8D8F450050900F /* Keyboard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Keyboard.cpp; path = Oric/Keyboard.cpp; sourceTree = "<group>"; };
4B54C0BE1F8D8F450050900F /* Keyboard.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Keyboard.hpp; path = Oric/Keyboard.hpp; sourceTree = "<group>"; };
4B54C0C01F8D91CD0050900F /* Keyboard.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Keyboard.hpp; path = AmstradCPC/Keyboard.hpp; sourceTree = "<group>"; };
4B54C0C11F8D91CD0050900F /* Keyboard.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Keyboard.cpp; path = AmstradCPC/Keyboard.cpp; sourceTree = "<group>"; };
4B54C0BD1F8D8F450050900F /* Keyboard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Keyboard.cpp; sourceTree = "<group>"; };
4B54C0BE1F8D8F450050900F /* Keyboard.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Keyboard.hpp; sourceTree = "<group>"; };
4B54C0C01F8D91CD0050900F /* Keyboard.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Keyboard.hpp; sourceTree = "<group>"; };
4B54C0C11F8D91CD0050900F /* Keyboard.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Keyboard.cpp; sourceTree = "<group>"; };
4B54C0C31F8D91D90050900F /* Keyboard.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Keyboard.hpp; sourceTree = "<group>"; };
4B54C0C41F8D91D90050900F /* Keyboard.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Keyboard.cpp; sourceTree = "<group>"; };
4B54C0C61F8D91E50050900F /* Keyboard.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Keyboard.cpp; path = Electron/Keyboard.cpp; sourceTree = "<group>"; };
4B54C0C71F8D91E50050900F /* Keyboard.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Keyboard.hpp; path = Electron/Keyboard.hpp; sourceTree = "<group>"; };
4B54C0C61F8D91E50050900F /* Keyboard.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Keyboard.cpp; sourceTree = "<group>"; };
4B54C0C71F8D91E50050900F /* Keyboard.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Keyboard.hpp; sourceTree = "<group>"; };
4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachineDocument.swift; sourceTree = "<group>"; };
4B55DD8020DF06680043F2E5 /* MachinePicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachinePicker.swift; sourceTree = "<group>"; };
4B55DD8220DF06680043F2E5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MachinePicker.xib; sourceTree = "<group>"; };
@@ -1275,10 +1285,10 @@
4B5D5C9625F56FC7001B4623 /* Spectrum.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Spectrum.hpp; path = Parsers/Spectrum.hpp; sourceTree = "<group>"; };
4B5FADB81DE3151600AEC565 /* FileHolder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FileHolder.cpp; sourceTree = "<group>"; };
4B5FADB91DE3151600AEC565 /* FileHolder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FileHolder.hpp; sourceTree = "<group>"; };
4B5FADBE1DE3BF2B00AEC565 /* Microdisc.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Microdisc.cpp; path = Oric/Microdisc.cpp; sourceTree = "<group>"; };
4B5FADBF1DE3BF2B00AEC565 /* Microdisc.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Microdisc.hpp; path = Oric/Microdisc.hpp; sourceTree = "<group>"; };
4B622AE3222E0AD5008B59F2 /* DisplayMetrics.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = DisplayMetrics.cpp; path = ../../Outputs/DisplayMetrics.cpp; sourceTree = "<group>"; };
4B622AE4222E0AD5008B59F2 /* DisplayMetrics.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = DisplayMetrics.hpp; path = ../../Outputs/DisplayMetrics.hpp; sourceTree = "<group>"; };
4B5FADBE1DE3BF2B00AEC565 /* Microdisc.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Microdisc.cpp; sourceTree = "<group>"; };
4B5FADBF1DE3BF2B00AEC565 /* Microdisc.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Microdisc.hpp; sourceTree = "<group>"; };
4B622AE3222E0AD5008B59F2 /* DisplayMetrics.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DisplayMetrics.cpp; sourceTree = "<group>"; };
4B622AE4222E0AD5008B59F2 /* DisplayMetrics.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DisplayMetrics.hpp; sourceTree = "<group>"; };
4B643F381D77AD1900D431D6 /* CSStaticAnalyser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSStaticAnalyser.h; path = StaticAnalyser/CSStaticAnalyser.h; sourceTree = "<group>"; };
4B643F391D77AD1900D431D6 /* CSStaticAnalyser.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = CSStaticAnalyser.mm; path = StaticAnalyser/CSStaticAnalyser.mm; sourceTree = "<group>"; };
4B643F3C1D77AE5C00D431D6 /* CSMachine+Target.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CSMachine+Target.h"; sourceTree = "<group>"; };
@@ -1314,7 +1324,7 @@
4B6ED2EF208E2F8A0047B343 /* WOZ.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = WOZ.hpp; sourceTree = "<group>"; };
4B7041271F92C26900735E45 /* JoystickMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = JoystickMachine.hpp; sourceTree = "<group>"; };
4B70412A1F92C2A700735E45 /* Joystick.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Joystick.hpp; sourceTree = "<group>"; };
4B70EF6A1FFDCDF400A3494E /* ROMSlotHandler.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = ROMSlotHandler.hpp; path = MSX/ROMSlotHandler.hpp; sourceTree = "<group>"; };
4B70EF6A1FFDCDF400A3494E /* ROMSlotHandler.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ROMSlotHandler.hpp; sourceTree = "<group>"; };
4B7136841F78724F008B8ED9 /* Encoder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Encoder.cpp; sourceTree = "<group>"; };
4B7136851F78724F008B8ED9 /* Encoder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Encoder.hpp; sourceTree = "<group>"; };
4B7136871F78725F008B8ED9 /* Shifter.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Shifter.cpp; sourceTree = "<group>"; };
@@ -1329,13 +1339,13 @@
4B74CF802312FA9C00500CE8 /* HFV.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = HFV.cpp; sourceTree = "<group>"; };
4B74CF83231370BC00500CE8 /* MacintoshVolume.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = MacintoshVolume.cpp; path = Encodings/MacintoshVolume.cpp; sourceTree = "<group>"; };
4B74CF84231370BC00500CE8 /* MacintoshVolume.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = MacintoshVolume.hpp; path = Encodings/MacintoshVolume.hpp; sourceTree = "<group>"; };
4B77069C1EC904570053B588 /* Z80.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Z80.hpp; path = Z80/Z80.hpp; sourceTree = "<group>"; };
4B77069C1EC904570053B588 /* Z80.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Z80.hpp; sourceTree = "<group>"; };
4B770A961FE9EE770026DC70 /* CompoundSource.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CompoundSource.hpp; sourceTree = "<group>"; };
4B7913CA1DFCD80E00175A82 /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Video.cpp; path = Electron/Video.cpp; sourceTree = "<group>"; };
4B7913CB1DFCD80E00175A82 /* Video.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Video.hpp; path = Electron/Video.hpp; sourceTree = "<group>"; };
4B7913CA1DFCD80E00175A82 /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Video.cpp; sourceTree = "<group>"; };
4B7913CB1DFCD80E00175A82 /* Video.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Video.hpp; sourceTree = "<group>"; };
4B79A4FE1FC9082300EEDAD5 /* TypedDynamicMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TypedDynamicMachine.hpp; sourceTree = "<group>"; };
4B79A4FF1FC913C900EEDAD5 /* MSX.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = MSX.cpp; path = MSX/MSX.cpp; sourceTree = "<group>"; };
4B79A5001FC913C900EEDAD5 /* MSX.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = MSX.hpp; path = MSX/MSX.hpp; sourceTree = "<group>"; };
4B79A4FF1FC913C900EEDAD5 /* MSX.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MSX.cpp; sourceTree = "<group>"; };
4B79A5001FC913C900EEDAD5 /* MSX.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MSX.hpp; sourceTree = "<group>"; };
4B79E4411E3AF38600141F11 /* cassette.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = cassette.png; sourceTree = "<group>"; };
4B79E4421E3AF38600141F11 /* floppy35.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = floppy35.png; sourceTree = "<group>"; };
4B79E4431E3AF38600141F11 /* floppy525.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = floppy525.png; sourceTree = "<group>"; };
@@ -1343,13 +1353,13 @@
4B7A90E42041097C008514A2 /* ColecoVision.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ColecoVision.cpp; sourceTree = "<group>"; };
4B7A90EB20410A85008514A2 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = "<group>"; };
4B7A90EC20410A85008514A2 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; };
4B7BA02E23C2B19B00B98D9E /* Jasmin.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Jasmin.cpp; path = Oric/Jasmin.cpp; sourceTree = "<group>"; };
4B7BA02F23C2B19B00B98D9E /* Jasmin.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Jasmin.hpp; path = Oric/Jasmin.hpp; sourceTree = "<group>"; };
4B7BA02E23C2B19B00B98D9E /* Jasmin.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Jasmin.cpp; sourceTree = "<group>"; };
4B7BA02F23C2B19B00B98D9E /* Jasmin.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Jasmin.hpp; sourceTree = "<group>"; };
4B7BA03223C58B1E00B98D9E /* STX.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = STX.hpp; sourceTree = "<group>"; };
4B7BA03323C58B1E00B98D9E /* STX.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = STX.cpp; sourceTree = "<group>"; };
4B7BA03523CEB86000B98D9E /* BD500.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = BD500.cpp; path = Oric/BD500.cpp; sourceTree = "<group>"; };
4B7BA03623CEB86000B98D9E /* BD500.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = BD500.hpp; path = Oric/BD500.hpp; sourceTree = "<group>"; };
4B7BA03823CEB8D200B98D9E /* DiskController.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = DiskController.hpp; path = Oric/DiskController.hpp; sourceTree = "<group>"; };
4B7BA03523CEB86000B98D9E /* BD500.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BD500.cpp; sourceTree = "<group>"; };
4B7BA03623CEB86000B98D9E /* BD500.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = BD500.hpp; sourceTree = "<group>"; };
4B7BA03823CEB8D200B98D9E /* DiskController.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DiskController.hpp; sourceTree = "<group>"; };
4B7BA03E23D55E7900B98D9E /* CRC.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRC.hpp; sourceTree = "<group>"; };
4B7BA03F23D55E7900B98D9E /* LFSR.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = LFSR.hpp; sourceTree = "<group>"; };
4B7F188C2154825D00388727 /* MasterSystem.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MasterSystem.cpp; sourceTree = "<group>"; };
@@ -1378,8 +1388,8 @@
4B8805EF1DCFC99C003085B1 /* Acorn.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Acorn.hpp; path = Parsers/Acorn.hpp; sourceTree = "<group>"; };
4B8805F21DCFD22A003085B1 /* Commodore.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Commodore.cpp; path = Parsers/Commodore.cpp; sourceTree = "<group>"; };
4B8805F31DCFD22A003085B1 /* Commodore.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Commodore.hpp; path = Parsers/Commodore.hpp; sourceTree = "<group>"; };
4B8805F51DCFF6C9003085B1 /* Commodore.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Commodore.cpp; path = Data/Commodore.cpp; sourceTree = "<group>"; };
4B8805F61DCFF6C9003085B1 /* Commodore.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Commodore.hpp; path = Data/Commodore.hpp; sourceTree = "<group>"; };
4B8805F51DCFF6C9003085B1 /* Commodore.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Commodore.cpp; sourceTree = "<group>"; };
4B8805F61DCFF6C9003085B1 /* Commodore.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Commodore.hpp; sourceTree = "<group>"; };
4B8805F91DCFF807003085B1 /* Oric.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Oric.cpp; path = Parsers/Oric.cpp; sourceTree = "<group>"; };
4B8805FA1DCFF807003085B1 /* Oric.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Oric.hpp; path = Parsers/Oric.hpp; sourceTree = "<group>"; };
4B89449220194A47007DE474 /* CSStaticAnalyser+TargetVector.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "CSStaticAnalyser+TargetVector.h"; path = "StaticAnalyser/CSStaticAnalyser+TargetVector.h"; sourceTree = "<group>"; };
@@ -1426,6 +1436,13 @@
4B894540201967D6007DE474 /* Machines.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Machines.hpp; sourceTree = "<group>"; };
4B8A7E85212F988200F2BBC6 /* DeferredQueue.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DeferredQueue.hpp; sourceTree = "<group>"; };
4B8D287E1F77207100645199 /* TrackSerialiser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TrackSerialiser.hpp; sourceTree = "<group>"; };
4B8DD3672633B2D400B3C866 /* SpectrumVideoContentionTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = SpectrumVideoContentionTests.mm; sourceTree = "<group>"; };
4B8DD375263481BB00B3C866 /* StateProducer.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = StateProducer.hpp; sourceTree = "<group>"; };
4B8DD3842634D37E00B3C866 /* SNA.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SNA.cpp; sourceTree = "<group>"; };
4B8DD3852634D37E00B3C866 /* SNA.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SNA.hpp; sourceTree = "<group>"; };
4B8DD3912635A72F00B3C866 /* State.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = State.hpp; sourceTree = "<group>"; };
4B8DD39526360DDF00B3C866 /* Z80.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Z80.cpp; sourceTree = "<group>"; };
4B8DD39626360DDF00B3C866 /* Z80.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Z80.hpp; sourceTree = "<group>"; };
4B8DF4D62546561300F3433C /* MemoryMap.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MemoryMap.hpp; sourceTree = "<group>"; };
4B8DF4D725465B7500F3433C /* IIgsMemoryMapTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = IIgsMemoryMapTests.mm; sourceTree = "<group>"; };
4B8DF4ED254B840B00F3433C /* AppleClock.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AppleClock.hpp; sourceTree = "<group>"; };
@@ -1519,8 +1536,8 @@
4B9D0C4E22C7E0CF00DE1AD3 /* 68000RollShiftTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = 68000RollShiftTests.mm; sourceTree = "<group>"; };
4B9F11C82272375400701480 /* qltrace.txt.gz */ = {isa = PBXFileReference; lastKnownFileType = archive.gzip; path = qltrace.txt.gz; sourceTree = "<group>"; };
4B9F11CB22729B3500701480 /* OPCLOGR2.BIN */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = OPCLOGR2.BIN; path = "68000 Coverage/OPCLOGR2.BIN"; sourceTree = "<group>"; };
4BA0F68C1EEA0E8400E9489E /* ZX8081.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ZX8081.cpp; path = Data/ZX8081.cpp; sourceTree = "<group>"; };
4BA0F68D1EEA0E8400E9489E /* ZX8081.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ZX8081.hpp; path = Data/ZX8081.hpp; sourceTree = "<group>"; };
4BA0F68C1EEA0E8400E9489E /* ZX8081.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ZX8081.cpp; sourceTree = "<group>"; };
4BA0F68D1EEA0E8400E9489E /* ZX8081.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ZX8081.hpp; sourceTree = "<group>"; };
4BA141C12073100800A31EC9 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
4BA61EAE1D91515900B3C876 /* NSData+StdVector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+StdVector.h"; sourceTree = "<group>"; };
4BA61EAF1D91515900B3C876 /* NSData+StdVector.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSData+StdVector.mm"; sourceTree = "<group>"; };
@@ -1837,8 +1854,8 @@
4BBB70A3202011C2002FE009 /* MultiMediaTarget.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MultiMediaTarget.cpp; sourceTree = "<group>"; };
4BBB70A6202014E2002FE009 /* MultiProducer.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MultiProducer.cpp; sourceTree = "<group>"; };
4BBB70A7202014E2002FE009 /* MultiProducer.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MultiProducer.hpp; sourceTree = "<group>"; };
4BBC951C1F368D83008F4C34 /* i8272.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = i8272.cpp; path = 8272/i8272.cpp; sourceTree = "<group>"; };
4BBC951D1F368D83008F4C34 /* i8272.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = i8272.hpp; path = 8272/i8272.hpp; sourceTree = "<group>"; };
4BBC951C1F368D83008F4C34 /* i8272.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = i8272.cpp; sourceTree = "<group>"; };
4BBC951D1F368D83008F4C34 /* i8272.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = i8272.hpp; sourceTree = "<group>"; };
4BBF49AE1ED2880200AB3669 /* FUSETests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FUSETests.swift; sourceTree = "<group>"; };
4BBF990E1C8FBA6F0075DAFB /* Flywheel.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Flywheel.hpp; sourceTree = "<group>"; };
4BBFBB6A1EE8401E00C01E7A /* ZX8081.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ZX8081.cpp; path = Parsers/ZX8081.cpp; sourceTree = "<group>"; };
@@ -1902,14 +1919,14 @@
4BCE005F227D39AB000CA200 /* Video.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Video.hpp; sourceTree = "<group>"; };
4BCE1DEF25D4C3FA00AE7A2B /* Bus.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Bus.cpp; sourceTree = "<group>"; };
4BCE1DF025D4C3FA00AE7A2B /* Bus.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Bus.hpp; sourceTree = "<group>"; };
4BCF1FA21DADC3DD0039D2E7 /* Oric.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Oric.cpp; path = Oric/Oric.cpp; sourceTree = "<group>"; };
4BCF1FA31DADC3DD0039D2E7 /* Oric.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Oric.hpp; path = Oric/Oric.hpp; sourceTree = "<group>"; };
4BCF1FA21DADC3DD0039D2E7 /* Oric.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Oric.cpp; sourceTree = "<group>"; };
4BCF1FA31DADC3DD0039D2E7 /* Oric.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Oric.hpp; sourceTree = "<group>"; };
4BD060A51FE49D3C006E14BE /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Speaker.hpp; sourceTree = "<group>"; };
4BD0FBC2233706A200148981 /* CSApplication.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CSApplication.m; sourceTree = "<group>"; };
4BD191D9219113B80042E144 /* OpenGL.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OpenGL.hpp; sourceTree = "<group>"; };
4BD191F22191180E0042E144 /* ScanTarget.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ScanTarget.cpp; sourceTree = "<group>"; };
4BD191F32191180E0042E144 /* ScanTarget.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ScanTarget.hpp; sourceTree = "<group>"; };
4BD388411FE34E010042B588 /* 9918Base.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = 9918Base.hpp; path = 9918/Implementation/9918Base.hpp; sourceTree = "<group>"; };
4BD388411FE34E010042B588 /* 9918Base.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = 9918Base.hpp; sourceTree = "<group>"; };
4BD388872239E198002D14B5 /* 68000Tests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = 68000Tests.mm; sourceTree = "<group>"; };
4BD424DD2193B5340097291A /* TextureTarget.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TextureTarget.cpp; sourceTree = "<group>"; };
4BD424DE2193B5340097291A /* TextureTarget.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TextureTarget.hpp; sourceTree = "<group>"; };
@@ -1917,23 +1934,24 @@
4BD424E22193B5820097291A /* Rectangle.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Rectangle.cpp; sourceTree = "<group>"; };
4BD424E32193B5830097291A /* Rectangle.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Rectangle.hpp; sourceTree = "<group>"; };
4BD424E42193B5830097291A /* Shader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Shader.hpp; sourceTree = "<group>"; };
4BD468F51D8DF41D0084958B /* 1770.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = 1770.cpp; path = 1770/1770.cpp; sourceTree = "<group>"; };
4BD468F61D8DF41D0084958B /* 1770.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = 1770.hpp; path = 1770/1770.hpp; sourceTree = "<group>"; };
4BD468F51D8DF41D0084958B /* 1770.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = 1770.cpp; sourceTree = "<group>"; };
4BD468F61D8DF41D0084958B /* 1770.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 1770.hpp; sourceTree = "<group>"; };
4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PCMTrackTests.mm; sourceTree = "<group>"; };
4BD5D2672199148100DDF17D /* ScanTargetGLSLFragments.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ScanTargetGLSLFragments.cpp; sourceTree = "<group>"; };
4BD601A920D89F2A00CBCE57 /* Log.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Log.hpp; path = ../../Outputs/Log.hpp; sourceTree = "<group>"; };
4BD601A920D89F2A00CBCE57 /* Log.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Log.hpp; sourceTree = "<group>"; };
4BD61663206B2AC700236112 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/QuickLoadOptions.xib"; sourceTree = SOURCE_ROOT; };
4BD67DC9209BE4D600AB2146 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = "<group>"; };
4BD67DCA209BE4D600AB2146 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; };
4BD67DCE209BF27B00AB2146 /* Encoder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Encoder.cpp; sourceTree = "<group>"; };
4BD67DCF209BF27B00AB2146 /* Encoder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Encoder.hpp; sourceTree = "<group>"; };
4BD9137D1F311BC5009BCF85 /* i8255.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = i8255.hpp; path = 8255/i8255.hpp; sourceTree = "<group>"; };
4BD9137D1F311BC5009BCF85 /* i8255.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = i8255.hpp; sourceTree = "<group>"; };
4BD91D762401C2B8007BDC91 /* PatrikRakTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PatrikRakTests.swift; sourceTree = "<group>"; };
4BDA00D922E60EE300AC3CD0 /* ROMRequester.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ROMRequester.xib; sourceTree = "<group>"; };
4BDA00DE22E644AF00AC3CD0 /* CSROMReceiverView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CSROMReceiverView.h; sourceTree = "<group>"; };
4BDA00DF22E644AF00AC3CD0 /* CSROMReceiverView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CSROMReceiverView.m; sourceTree = "<group>"; };
4BDA00E222E663B900AC3CD0 /* NSData+CRC32.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+CRC32.m"; sourceTree = "<group>"; };
4BDA00E322E663B900AC3CD0 /* NSData+CRC32.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+CRC32.h"; sourceTree = "<group>"; };
4BDA8234261E8E000021AA19 /* Z80ContentionTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = Z80ContentionTests.mm; sourceTree = "<group>"; };
4BDACBEA22FFA5D20045EF7E /* ncr5380.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ncr5380.cpp; sourceTree = "<group>"; };
4BDACBEB22FFA5D20045EF7E /* ncr5380.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ncr5380.hpp; sourceTree = "<group>"; };
4BDB3D8522833321002D3CEE /* Keyboard.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Keyboard.hpp; sourceTree = "<group>"; };
@@ -1954,22 +1972,22 @@
4BE3231620532BED006EF799 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
4BE34437238389E10058E78F /* AtariSTVideoTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AtariSTVideoTests.mm; sourceTree = "<group>"; };
4BE76CF822641ED300ACD6FA /* QLTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = QLTests.mm; sourceTree = "<group>"; };
4BE845201F2FF7F100A5EA22 /* CRTC6845.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CRTC6845.hpp; path = 6845/CRTC6845.hpp; sourceTree = "<group>"; };
4BE845201F2FF7F100A5EA22 /* CRTC6845.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRTC6845.hpp; sourceTree = "<group>"; };
4BE8EB5425C0E9D40040BC40 /* Disassembler.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Disassembler.hpp; sourceTree = "<group>"; };
4BE8EB5525C0EA490040BC40 /* Sizes.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Sizes.hpp; sourceTree = "<group>"; };
4BE8EB6425C750B50040BC40 /* DAT.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DAT.cpp; sourceTree = "<group>"; };
4BE8EB6525C750B50040BC40 /* DAT.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DAT.hpp; sourceTree = "<group>"; };
4BE90FFC22D5864800FB464D /* MacintoshVideoTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MacintoshVideoTests.mm; sourceTree = "<group>"; };
4BE9A6B01EDE293000CBCB47 /* zexdoc.com */ = {isa = PBXFileReference; lastKnownFileType = file; name = zexdoc.com; path = Zexall/zexdoc.com; sourceTree = "<group>"; };
4BEA525D1DF33323007E74F2 /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Tape.cpp; path = Electron/Tape.cpp; sourceTree = "<group>"; };
4BEA525F1DF333D8007E74F2 /* Tape.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Tape.hpp; path = Electron/Tape.hpp; sourceTree = "<group>"; };
4BEA52601DF3343A007E74F2 /* Interrupts.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Interrupts.hpp; path = Electron/Interrupts.hpp; sourceTree = "<group>"; };
4BEA52611DF339D7007E74F2 /* SoundGenerator.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SoundGenerator.cpp; path = Electron/SoundGenerator.cpp; sourceTree = "<group>"; };
4BEA52621DF339D7007E74F2 /* SoundGenerator.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = SoundGenerator.hpp; path = Electron/SoundGenerator.hpp; sourceTree = "<group>"; };
4BEA525D1DF33323007E74F2 /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Tape.cpp; sourceTree = "<group>"; };
4BEA525F1DF333D8007E74F2 /* Tape.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Tape.hpp; sourceTree = "<group>"; };
4BEA52601DF3343A007E74F2 /* Interrupts.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Interrupts.hpp; sourceTree = "<group>"; };
4BEA52611DF339D7007E74F2 /* SoundGenerator.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SoundGenerator.cpp; sourceTree = "<group>"; };
4BEA52621DF339D7007E74F2 /* SoundGenerator.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SoundGenerator.hpp; sourceTree = "<group>"; };
4BEBFB4B2002C4BF000708CC /* MSXDSK.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MSXDSK.cpp; sourceTree = "<group>"; };
4BEBFB4C2002C4BF000708CC /* MSXDSK.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MSXDSK.hpp; sourceTree = "<group>"; };
4BEBFB4F2002DB30000708CC /* DiskROM.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = DiskROM.cpp; path = MSX/DiskROM.cpp; sourceTree = "<group>"; };
4BEBFB502002DB30000708CC /* DiskROM.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = DiskROM.hpp; path = MSX/DiskROM.hpp; sourceTree = "<group>"; };
4BEBFB4F2002DB30000708CC /* DiskROM.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DiskROM.cpp; sourceTree = "<group>"; };
4BEBFB502002DB30000708CC /* DiskROM.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DiskROM.hpp; sourceTree = "<group>"; };
4BEDA3B425B25563000C2DBD /* Decoder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Decoder.cpp; sourceTree = "<group>"; };
4BEDA3B525B25563000C2DBD /* Decoder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Decoder.hpp; sourceTree = "<group>"; };
4BEDA3B625B25563000C2DBD /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
@@ -2003,7 +2021,7 @@
4BF437ED209D0F7E008CBD6B /* SegmentParser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = SegmentParser.hpp; sourceTree = "<group>"; };
4BF437F0209D112F008CBD6B /* Sector.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Sector.hpp; sourceTree = "<group>"; };
4BF4A2D91F534DB300B171F4 /* TargetPlatforms.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TargetPlatforms.hpp; sourceTree = "<group>"; };
4BF52672218E752E00313227 /* ScanTarget.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ScanTarget.hpp; path = ../../Outputs/ScanTarget.hpp; sourceTree = "<group>"; };
4BF52672218E752E00313227 /* ScanTarget.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ScanTarget.hpp; sourceTree = "<group>"; };
4BF6606A1F281573002CB053 /* ClockReceiver.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ClockReceiver.hpp; sourceTree = "<group>"; };
4BF8D4CD251C0C9C00BBE21B /* 65816.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 65816.hpp; sourceTree = "<group>"; };
4BF8D4D3251C0D9F00BBE21B /* 65816Storage.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 65816Storage.hpp; sourceTree = "<group>"; };
@@ -2155,8 +2173,7 @@
4B0CCC431C62D0B3001CAC5F /* CRT.hpp */,
4BBF99071C8FBA6F0075DAFB /* Internals */,
);
name = CRT;
path = ../../Outputs/CRT;
path = CRT;
sourceTree = "<group>";
};
4B0E04F71FC9F2C800F43484 /* 9918 */ = {
@@ -2166,7 +2183,7 @@
4B0E04F81FC9FA3000F43484 /* 9918.hpp */,
4BD388431FE34E060042B588 /* Implementation */,
);
name = 9918;
path = 9918;
sourceTree = "<group>";
};
4B0F1BAF2602645900B85C66 /* ZXSpectrum */ = {
@@ -2206,6 +2223,7 @@
4B0F1BFA260300D900B85C66 /* ZXSpectrum.cpp */,
4B0F1BFB260300D900B85C66 /* ZXSpectrum.hpp */,
4B0F1C092603BA5F00B85C66 /* Video.hpp */,
4B8DD3912635A72F00B3C866 /* State.hpp */,
);
path = ZXSpectrum;
sourceTree = "<group>";
@@ -2274,7 +2292,7 @@
4B1667FB1FFF215F00A16032 /* KonamiWithSCC.hpp */,
4B1667F61FFF1E2400A16032 /* Konami.hpp */,
);
name = Cartridges;
path = Cartridges;
sourceTree = "<group>";
};
4B1B58F3246CC4E8009C171E /* State */ = {
@@ -2283,8 +2301,7 @@
4B1B58F4246CC4E8009C171E /* State.cpp */,
4B1B58F5246CC4E8009C171E /* State.hpp */,
);
name = State;
path = Z80/State;
path = State;
sourceTree = "<group>";
};
4B1B58FC246E19FD009C171E /* State */ = {
@@ -2431,7 +2448,7 @@
4BEA525F1DF333D8007E74F2 /* Tape.hpp */,
4B7913CB1DFCD80E00175A82 /* Video.hpp */,
);
name = Electron;
path = Electron;
sourceTree = "<group>";
};
4B302181208A550100773308 /* DiskII */ = {
@@ -2465,8 +2482,7 @@
4B322DFD1F5A2981004EB04C /* Z80AllRAM.cpp */,
4B322DFE1F5A2981004EB04C /* Z80AllRAM.hpp */,
);
name = AllRAM;
path = Z80/AllRAM;
path = AllRAM;
sourceTree = "<group>";
};
4B322DFF1F5A2981004EB04C /* Implementation */ = {
@@ -2478,8 +2494,7 @@
4B322E051F5A30F5004EB04C /* Z80Implementation.hpp */,
4B322E021F5A29D5004EB04C /* Z80Storage.hpp */,
);
name = Implementation;
path = Z80/Implementation;
path = Implementation;
sourceTree = "<group>";
};
4B366DFD1B5C165F0026627B /* Outputs */ = {
@@ -2496,6 +2511,7 @@
4BD060A41FE49D3C006E14BE /* Speaker */,
);
name = Outputs;
path = ../../Outputs;
sourceTree = "<group>";
};
4B38F3491F2EC12000D9235D /* AmstradCPC */ = {
@@ -2507,7 +2523,7 @@
4B54C0C01F8D91CD0050900F /* Keyboard.hpp */,
4B0F1C3D26095AC600B85C66 /* FDC.hpp */,
);
name = AmstradCPC;
path = AmstradCPC;
sourceTree = "<group>";
};
4B3940E81DA83C8700427841 /* Concurrency */ = {
@@ -2517,6 +2533,7 @@
4B3940E61DA83C8300427841 /* AsyncTaskQueue.hpp */,
);
name = Concurrency;
path = ../../Concurrency;
sourceTree = "<group>";
};
4B3AF7CF2413470E00873C0B /* Reflection */ = {
@@ -2667,7 +2684,7 @@
4B4A762E1DB1A3FA007AAE2E /* AY38910.cpp */,
4B4A762F1DB1A3FA007AAE2E /* AY38910.hpp */,
);
name = AY38910;
path = AY38910;
sourceTree = "<group>";
};
4B4B1A39200198C900A0F866 /* KonamiSCC */ = {
@@ -2838,6 +2855,7 @@
4B8805F81DCFF6CD003085B1 /* Data */,
4BAB62AA1D3272D200DF5BA0 /* Disk */,
4B6AAEA1230E3E1D0078E864 /* MassStorage */,
4B8DD3832634D37E00B3C866 /* State */,
4B69FB3A1C4D908A00B5F0AA /* Tape */,
);
name = Storage;
@@ -2967,7 +2985,7 @@
4B322DFF1F5A2981004EB04C /* Implementation */,
4B1B58F3246CC4E8009C171E /* State */,
);
name = Z80;
path = Z80;
sourceTree = "<group>";
};
4B79A4FC1FC8FF9800EEDAD5 /* MSX */ = {
@@ -2982,7 +3000,7 @@
4B70EF6A1FFDCDF400A3494E /* ROMSlotHandler.hpp */,
4B1667F81FFF1E2900A16032 /* Cartridges */,
);
name = MSX;
path = MSX;
sourceTree = "<group>";
};
4B7A90E22041097C008514A2 /* ColecoVision */ = {
@@ -3103,7 +3121,7 @@
4B8805F61DCFF6C9003085B1 /* Commodore.hpp */,
4BA0F68D1EEA0E8400E9489E /* ZX8081.hpp */,
);
name = Data;
path = Data;
sourceTree = "<group>";
};
4B8944E2201967B4007DE474 /* Analyser */ = {
@@ -3244,6 +3262,19 @@
path = AmstradCPC;
sourceTree = "<group>";
};
4B8DD3832634D37E00B3C866 /* State */ = {
isa = PBXGroup;
children = (
4B2B946326377C0200E7097C /* SZX.cpp */,
4B2B946426377C0200E7097C /* SZX.hpp */,
4B8DD3842634D37E00B3C866 /* SNA.cpp */,
4B8DD3852634D37E00B3C866 /* SNA.hpp */,
4B8DD39526360DDF00B3C866 /* Z80.cpp */,
4B8DD39626360DDF00B3C866 /* Z80.hpp */,
);
path = State;
sourceTree = "<group>";
};
4B8DF4EC254B840B00F3433C /* AppleClock */ = {
isa = PBXGroup;
children = (
@@ -3937,8 +3968,12 @@
4BB73EB51B587A5100552FC2 /* Clock SignalTests */ = {
isa = PBXGroup;
children = (
4B85322922778E4200F26553 /* Comparative68000.hpp */,
4B90467222C6FA31000E2074 /* TestRunner68000.hpp */,
4BC9E1ED1D23449A003FCEE4 /* 6502InterruptTests.swift */,
4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */,
4BC751B11D157E61006C31D9 /* 6522Tests.swift */,
4B1E85801D176468001EF87D /* 6532Tests.swift */,
4B4F478925367EDC004245B8 /* 65816AddressingTests.swift */,
4B8DF5132550D62900F3433C /* 65816kromTests.swift */,
4B90467522C6FD6E000E2074 /* 68000ArithmeticTests.mm */,
4B9D0C4A22C7D70900DE1AD3 /* 68000BCDTests.mm */,
4B90467322C6FADD000E2074 /* 68000BitwiseTests.mm */,
@@ -3947,45 +3982,43 @@
4BC5C3DF22C994CC00795658 /* 68000MoveTests.mm */,
4B9D0C4E22C7E0CF00DE1AD3 /* 68000RollShiftTests.mm */,
4BD388872239E198002D14B5 /* 68000Tests.mm */,
4BB73EB61B587A5100552FC2 /* AllSuiteATests.swift */,
4B924E981E74D22700B76AF1 /* AtariStaticAnalyserTests.mm */,
4BE34437238389E10058E78F /* AtariSTVideoTests.mm */,
4B049CDC1DA3C82F00322067 /* BCDTest.swift */,
4B3BA0C41D318B44005DD7A7 /* Bridges */,
4B3BA0C21D318AEB005DD7A7 /* C1540Tests.swift */,
4B85322922778E4200F26553 /* Comparative68000.hpp */,
4BB2A9AE1E13367E001A5C23 /* CRCTests.mm */,
4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */,
4BFF1D3C2235C3C100838EA1 /* EmuTOSTests.mm */,
4BBF49AE1ED2880200AB3669 /* FUSETests.swift */,
4B8DF4D725465B7500F3433C /* IIgsMemoryMapTests.mm */,
4BB73EB81B587A5100552FC2 /* Info.plist */,
4B4F477B253530B7004245B8 /* Jeek816Tests.swift */,
4B1414611B58888700E04248 /* KlausDormannTests.swift */,
4BEE1EBF22B5E236000A26A6 /* MacGCRTests.mm */,
4BE90FFC22D5864800FB464D /* MacintoshVideoTests.mm */,
4BA91E1C216D85BA00F79557 /* MasterSystemVDPTests.mm */,
4B98A0601FFADCDE00ADF63B /* MSXStaticAnalyserTests.mm */,
4BC0CB272446BC7B00A79DBB /* OPLTests.mm */,
4BD91D762401C2B8007BDC91 /* PatrikRakTests.swift */,
4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */,
4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */,
4B3F76B825A1635300178AEC /* PowerPCDecoderTests.mm */,
4BE76CF822641ED300ACD6FA /* QLTests.mm */,
4B8DD3672633B2D400B3C866 /* SpectrumVideoContentionTests.mm */,
4B1414631B588A1100E04248 /* Test Binaries */,
4B90467222C6FA31000E2074 /* TestRunner68000.hpp */,
4B2AF8681E513FC20027EE29 /* TIATests.mm */,
4B1D08051E0F7A1100763741 /* TimeTests.mm */,
4BEE4BD325A26E2B00011BD2 /* x86DecoderTests.mm */,
4BB73EB81B587A5100552FC2 /* Info.plist */,
4BC9E1ED1D23449A003FCEE4 /* 6502InterruptTests.swift */,
4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */,
4BC751B11D157E61006C31D9 /* 6522Tests.swift */,
4B1E85801D176468001EF87D /* 6532Tests.swift */,
4B4F478925367EDC004245B8 /* 65816AddressingTests.swift */,
4B8DF5132550D62900F3433C /* 65816kromTests.swift */,
4BB73EB61B587A5100552FC2 /* AllSuiteATests.swift */,
4B049CDC1DA3C82F00322067 /* BCDTest.swift */,
4B3BA0C21D318AEB005DD7A7 /* C1540Tests.swift */,
4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */,
4BBF49AE1ED2880200AB3669 /* FUSETests.swift */,
4B4F477B253530B7004245B8 /* Jeek816Tests.swift */,
4B1414611B58888700E04248 /* KlausDormannTests.swift */,
4BD91D762401C2B8007BDC91 /* PatrikRakTests.swift */,
4B14145F1B58885000E04248 /* WolfgangLorenzTests.swift */,
4BEE4BD325A26E2B00011BD2 /* x86DecoderTests.mm */,
4BDA8234261E8E000021AA19 /* Z80ContentionTests.mm */,
4B08A2741EE35D56008B7065 /* Z80InterruptTests.swift */,
4BDDBA981EF3451200347E61 /* Z80MachineCycleTests.swift */,
4B01A6871F22F0DB001FD6E3 /* Z80MemptrTests.swift */,
4BFCA12A1ECBE7C400AC40C1 /* ZexallTests.swift */,
4B3BA0C41D318B44005DD7A7 /* Bridges */,
4B1414631B588A1100E04248 /* Test Binaries */,
);
path = "Clock SignalTests";
sourceTree = "<group>";
@@ -4012,6 +4045,7 @@
4B92294222B04A3D00A1458F /* MouseMachine.hpp */,
4BDCC5F81FB27A5E001220C5 /* ROMMachine.hpp */,
4B046DC31CFE651500E9E45E /* ScanProducer.hpp */,
4B8DD375263481BB00B3C866 /* StateProducer.hpp */,
4BC57CD32434282000FBC404 /* TimedMachine.hpp */,
4B38F3491F2EC12000D9235D /* AmstradCPC */,
4BCE0048227CE8CA000CA200 /* Apple */,
@@ -4051,8 +4085,7 @@
4BB8616C24E22DC500A00E03 /* BufferingScanTarget.hpp */,
4BB8616D24E22DC500A00E03 /* BufferingScanTarget.cpp */,
);
name = ScanTargets;
path = ../../Outputs/ScanTargets;
path = ScanTargets;
sourceTree = "<group>";
};
4BBB70A1202011C2002FE009 /* Implementation */ = {
@@ -4080,7 +4113,7 @@
4BBC951C1F368D83008F4C34 /* i8272.cpp */,
4BBC951D1F368D83008F4C34 /* i8272.hpp */,
);
name = 8272;
path = 8272;
sourceTree = "<group>";
};
4BBF49B41ED2881600AB3669 /* FUSE */ = {
@@ -4297,7 +4330,7 @@
4B2BFDB11DAEF5FF001A68B8 /* Video.hpp */,
4B7BA03823CEB8D200B98D9E /* DiskController.hpp */,
);
name = Oric;
path = Oric;
sourceTree = "<group>";
};
4BD060A41FE49D3C006E14BE /* Speaker */ = {
@@ -4306,8 +4339,7 @@
4BD060A51FE49D3C006E14BE /* Speaker.hpp */,
4B8EF6051FE5AF830076CCDD /* Implementation */,
);
name = Speaker;
path = ../../Outputs/Speaker;
path = Speaker;
sourceTree = "<group>";
};
4BD191D5219113B80042E144 /* OpenGL */ = {
@@ -4320,8 +4352,7 @@
4B961408222760E0001A7BF2 /* Screenshot.hpp */,
4BD424DC2193B5340097291A /* Primitives */,
);
name = OpenGL;
path = ../../Outputs/OpenGL;
path = OpenGL;
sourceTree = "<group>";
};
4BD388431FE34E060042B588 /* Implementation */ = {
@@ -4329,7 +4360,7 @@
children = (
4BD388411FE34E010042B588 /* 9918Base.hpp */,
);
name = Implementation;
path = Implementation;
sourceTree = "<group>";
};
4BD424DC2193B5340097291A /* Primitives */ = {
@@ -4351,7 +4382,7 @@
4BD468F51D8DF41D0084958B /* 1770.cpp */,
4BD468F61D8DF41D0084958B /* 1770.hpp */,
);
name = 1770;
path = 1770;
sourceTree = "<group>";
};
4BD67DC8209BE4D600AB2146 /* DiskII */ = {
@@ -4381,7 +4412,7 @@
children = (
4BD9137D1F311BC5009BCF85 /* i8255.hpp */,
);
name = 8255;
path = 8255;
sourceTree = "<group>";
};
4BDA00DB22E60EE900AC3CD0 /* ROMRequester */ = {
@@ -4443,7 +4474,7 @@
children = (
4BE845201F2FF7F100A5EA22 /* CRTC6845.hpp */,
);
name = 6845;
path = 6845;
sourceTree = "<group>";
};
4BE9A6B21EDE294200CBCB47 /* Zexall */ = {
@@ -4653,7 +4684,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0700;
LastUpgradeCheck = 1200;
LastUpgradeCheck = 1240;
ORGANIZATIONNAME = "Thomas Harte";
TargetAttributes = {
4B055A691FAE763F0060FFFF = {
@@ -4682,7 +4713,7 @@
};
};
buildConfigurationList = 4BB73E991B587A5100552FC2 /* Build configuration list for PBXProject "Clock Signal" */;
compatibilityVersion = "Xcode 3.2";
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
@@ -5207,9 +5238,11 @@
4B1B58F7246CC4E8009C171E /* State.cpp in Sources */,
4B0ACC03237756F6008902D0 /* Line.cpp in Sources */,
4B055AB11FAE86070060FFFF /* Tape.cpp in Sources */,
4B2B946626377C0200E7097C /* SZX.cpp in Sources */,
4BEDA43225B3C700000C2DBD /* Executor.cpp in Sources */,
4BC1317B2346DF2B00E4FF3D /* MSA.cpp in Sources */,
4B894533201967B4007DE474 /* 6502.cpp in Sources */,
4B8DD3872634D37E00B3C866 /* SNA.cpp in Sources */,
4B055AA91FAE85EF0060FFFF /* CommodoreGCR.cpp in Sources */,
4B055ADB1FAE9B460060FFFF /* 6560.cpp in Sources */,
4B17B58C20A8A9D9007CCA8F /* StringSerialiser.cpp in Sources */,
@@ -5243,6 +5276,7 @@
4B055AC81FAE9AFB0060FFFF /* C1540.cpp in Sources */,
4B055A8F1FAE85A90060FFFF /* FileHolder.cpp in Sources */,
4B055A911FAE85B50060FFFF /* Cartridge.cpp in Sources */,
4B8DD39826360DDF00B3C866 /* Z80.cpp in Sources */,
4B894525201967B4007DE474 /* Tape.cpp in Sources */,
4B055ACD1FAE9B030060FFFF /* Keyboard.cpp in Sources */,
4B055AB21FAE860F0060FFFF /* CommodoreTAP.cpp in Sources */,
@@ -5339,6 +5373,7 @@
4BE211FF253FC80900435408 /* StaticAnalyser.cpp in Sources */,
4B0F1BE22602FF9C00B85C66 /* ZX8081.cpp in Sources */,
4B8334951F5E25B60097E338 /* C1540.cpp in Sources */,
4B8DD39726360DDF00B3C866 /* Z80.cpp in Sources */,
4BEDA40C25B2844B000C2DBD /* Decoder.cpp in Sources */,
4B89453C201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
4B595FAD2086DFBA0083CAA8 /* AudioToggle.cpp in Sources */,
@@ -5480,6 +5515,7 @@
4BEBFB4D2002C4BF000708CC /* MSXDSK.cpp in Sources */,
4BBFBB6C1EE8401E00C01E7A /* ZX8081.cpp in Sources */,
4B83348A1F5DB94B0097E338 /* IRQDelegatePortHandler.cpp in Sources */,
4B8DD3862634D37E00B3C866 /* SNA.cpp in Sources */,
4B4DEC06252BFA56004583AC /* 65816Base.cpp in Sources */,
4B894524201967B4007DE474 /* Tape.cpp in Sources */,
4B7136891F78725F008B8ED9 /* Shifter.cpp in Sources */,
@@ -5497,6 +5533,7 @@
4BEE0A701D72496600532C7B /* PRG.cpp in Sources */,
4BB307BB235001C300457D33 /* 6850.cpp in Sources */,
4BF437EE209D0F7E008CBD6B /* SegmentParser.cpp in Sources */,
4B2B946526377C0200E7097C /* SZX.cpp in Sources */,
4BC131762346DE9100E4FF3D /* StaticAnalyser.cpp in Sources */,
4B8334861F5DA3780097E338 /* 6502Storage.cpp in Sources */,
4B8FE2271DA1DE2D0090D3CE /* NSBundle+DataResource.m in Sources */,
@@ -5640,6 +5677,7 @@
4B4DEC07252BFA56004583AC /* 65816Base.cpp in Sources */,
4B778F2323A5EDE40000D260 /* Tape.cpp in Sources */,
4B778F4F23A5F21C0000D260 /* StaticAnalyser.cpp in Sources */,
4B8DD3682633B2D400B3C866 /* SpectrumVideoContentionTests.mm in Sources */,
4B778EEF23A5D6680000D260 /* AsyncTaskQueue.cpp in Sources */,
4B778F1223A5EC720000D260 /* CRT.cpp in Sources */,
4B778EF423A5DB3A0000D260 /* C1540.cpp in Sources */,
@@ -5649,6 +5687,7 @@
4B9D0C4B22C7D70A00DE1AD3 /* 68000BCDTests.mm in Sources */,
4B778F5E23A5F3230000D260 /* Oric.cpp in Sources */,
4B3BA0C31D318AEC005DD7A7 /* C1540Tests.swift in Sources */,
4BDA8235261E8E000021AA19 /* Z80ContentionTests.mm in Sources */,
4B778F1A23A5ED320000D260 /* Video.cpp in Sources */,
4B778F3B23A5F1650000D260 /* KeyboardMachine.cpp in Sources */,
4B778F2E23A5F09E0000D260 /* IRQDelegatePortHandler.cpp in Sources */,
@@ -5815,7 +5854,6 @@
buildSettings = {
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "c++17";
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_IDENTITY = "-";
@@ -5836,7 +5874,6 @@
buildSettings = {
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "c++17";
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_IDENTITY = "-";
@@ -5860,8 +5897,7 @@
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_CXX_LANGUAGE_STANDARD = "c++17";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
@@ -5904,13 +5940,14 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_PARAMETER = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MACOSX_DEPLOYMENT_TARGET = 10.13;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 5.0;
};
name = Debug;
};
@@ -5919,8 +5956,7 @@
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_CXX_LANGUAGE_STANDARD = "c++17";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
@@ -5943,7 +5979,7 @@
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_IDENTITY = "Mac Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
@@ -5957,12 +5993,13 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_PARAMETER = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = NO;
MACOSX_DEPLOYMENT_TARGET = 10.13;
MTL_FAST_MATH = YES;
SDKROOT = macosx;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 5.0;
};
name = Release;
};
@@ -5970,14 +6007,16 @@
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_CXX_LANGUAGE_STANDARD = "c++17";
CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES;
CLANG_ENABLE_MODULES = YES;
CLANG_WARN_ASSIGN_ENUM = YES;
CLANG_WARN_ATOMIC_IMPLICIT_SEQ_CST = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES;
CLANG_WARN_SEMICOLON_BEFORE_METHOD_BODY = YES;
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = DV3346VVUN;
ENABLE_HARDENED_RUNTIME = YES;
@@ -5990,13 +6029,17 @@
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
GCC_WARN_FOUR_CHARACTER_CONSTANTS = YES;
GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS = YES;
GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
GCC_WARN_SHADOW = YES;
GCC_WARN_SIGN_COMPARE = YES;
GCC_WARN_UNKNOWN_PRAGMAS = YES;
GCC_WARN_UNUSED_LABEL = YES;
INFOPLIST_FILE = "Clock Signal/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MTL_TREAT_WARNINGS_AS_ERRORS = YES;
OTHER_CPLUSPLUSFLAGS = (
"$(OTHER_CFLAGS)",
@@ -6007,7 +6050,6 @@
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Clock Signal/ClockSignal-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
};
name = Debug;
};
@@ -6015,14 +6057,16 @@
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_CXX_LANGUAGE_STANDARD = "c++17";
CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES;
CLANG_ENABLE_MODULES = YES;
CLANG_WARN_ASSIGN_ENUM = YES;
CLANG_WARN_ATOMIC_IMPLICIT_SEQ_CST = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES;
CLANG_WARN_SEMICOLON_BEFORE_METHOD_BODY = YES;
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = DV3346VVUN;
ENABLE_HARDENED_RUNTIME = YES;
@@ -6037,13 +6081,17 @@
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
GCC_WARN_FOUR_CHARACTER_CONSTANTS = YES;
GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS = YES;
GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
GCC_WARN_SHADOW = YES;
GCC_WARN_SIGN_COMPARE = YES;
GCC_WARN_UNKNOWN_PRAGMAS = YES;
GCC_WARN_UNUSED_LABEL = YES;
INFOPLIST_FILE = "Clock Signal/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MTL_TREAT_WARNINGS_AS_ERRORS = YES;
OTHER_CPLUSPLUSFLAGS = (
"$(OTHER_CFLAGS)",
@@ -6053,7 +6101,6 @@
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Clock Signal/ClockSignal-Bridging-Header.h";
SWIFT_VERSION = 5.0;
};
name = Release;
};
@@ -6069,7 +6116,11 @@
DEVELOPMENT_TEAM = "";
ENABLE_HARDENED_RUNTIME = NO;
INFOPLIST_FILE = "Clock SignalTests/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
"@loader_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.0;
PRODUCT_BUNDLE_IDENTIFIER = "TH.Clock-SignalTests";
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -6094,7 +6145,11 @@
ENABLE_HARDENED_RUNTIME = NO;
GCC_OPTIMIZATION_LEVEL = 2;
INFOPLIST_FILE = "Clock SignalTests/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
"@loader_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.0;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "TH.Clock-SignalTests";

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1200"
LastUpgradeVersion = "1240"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1200"
LastUpgradeVersion = "1240"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1200"
LastUpgradeVersion = "1240"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@@ -11,12 +11,24 @@ import Cocoa
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
private var failedMetalCheck = false
func applicationDidFinishLaunching(_ notification: Notification) {
// Insert code here to initialize your application.
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application.
// Check for at least one Metal-capable GPU; this check
// will become unnecessary if/when the minimum OS version
// that this project supports reascends to 10.14.
if (MTLCopyAllDevices().count == 0) {
self.failedMetalCheck = true
let alert = NSAlert()
alert.messageText = "This application requires a Metal-capable GPU"
alert.addButton(withTitle: "Exit")
alert.runModal()
let application = notification.object as! NSApplication
application.terminate(self)
}
}
private var hasShownOpenDocument = false

View File

@@ -592,6 +592,66 @@
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>sna</string>
</array>
<key>CFBundleTypeName</key>
<string>ZX Spectrum SNA snapshot</string>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSTypeIsPackage</key>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>z80</string>
</array>
<key>CFBundleTypeName</key>
<string>ZX Spectrum Z80 snapshot</string>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSTypeIsPackage</key>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>szx</string>
</array>
<key>CFBundleTypeName</key>
<string>ZX Spectrum SZX snapshot</string>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSTypeIsPackage</key>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>
</array>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>

View File

@@ -368,7 +368,7 @@ API_AVAILABLE(macos(11.0))
return self;
}
-(void)update {
- (void)update {
// Update buttons.
for(CSGCJoystickButton *button in _buttons) {
// This assumes that the values provided by GCDeviceButtonInput are
@@ -379,7 +379,11 @@ API_AVAILABLE(macos(11.0))
float val = axis.axis.value;
val += 1;
val /= 2;
axis.position = val;
if(axis.type == CSJoystickAxisTypeY) {
axis.position = 1 - val;
} else {
axis.position = val;
}
}
for(CSGCJoystickHat *hat in _hats) {
// This assumes that the values provided by GCDeviceDirectionPad are

View File

@@ -63,6 +63,10 @@ typedef NS_ENUM(NSInteger, CSMachineOricDiskInterface) {
};
typedef NS_ENUM(NSInteger, CSMachineSpectrumModel) {
CSMachineSpectrumModelSixteenK,
CSMachineSpectrumModelFortyEightK,
CSMachineSpectrumModelOneTwoEightK,
CSMachineSpectrumModelPlus2,
CSMachineSpectrumModelPlus2a,
CSMachineSpectrumModelPlus3,
};

View File

@@ -194,8 +194,12 @@
using Target = Analyser::Static::ZXSpectrum::Target;
auto target = std::make_unique<Target>();
switch(model) {
case CSMachineSpectrumModelPlus2a: target->model = Target::Model::Plus2a; break;
case CSMachineSpectrumModelPlus3: target->model = Target::Model::Plus3; break;
case CSMachineSpectrumModelSixteenK: target->model = Target::Model::SixteenK; break;
case CSMachineSpectrumModelFortyEightK: target->model = Target::Model::FortyEightK; break;
case CSMachineSpectrumModelOneTwoEightK: target->model = Target::Model::OneTwoEightK; break;
case CSMachineSpectrumModelPlus2: target->model = Target::Model::Plus2; break;
case CSMachineSpectrumModelPlus2a: target->model = Target::Model::Plus2a; break;
case CSMachineSpectrumModelPlus3: target->model = Target::Model::Plus3; break;
}
_targets.push_back(std::move(target));
}

View File

@@ -18,7 +18,7 @@
<windowStyleMask key="styleMask" titled="YES" documentModal="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="196" y="240" width="590" height="316"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1440"/>
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
<rect key="frame" x="0.0" y="0.0" width="590" height="316"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
@@ -584,11 +584,11 @@ Gw
</tabViewItem>
<tabViewItem label="ZX81" identifier="zx81" id="Wnn-nQ-gZ6">
<view key="view" id="bmd-gL-gzT">
<rect key="frame" x="10" y="7" width="400" height="76"/>
<rect key="frame" x="10" y="7" width="400" height="186"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="5aO-UX-HnX">
<rect key="frame" x="108" y="47" width="116" height="25"/>
<rect key="frame" x="108" y="157" width="116" height="25"/>
<popUpButtonCell key="cell" type="push" title="Unexpanded" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="7QC-Ij-hES" id="d3W-Gl-3Mf">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@@ -602,7 +602,7 @@ Gw
</popUpButtonCell>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="8tU-73-XEE">
<rect key="frame" x="18" y="53" width="87" height="16"/>
<rect key="frame" x="18" y="163" width="87" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory Size:" id="z4b-oR-Yl2">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@@ -622,24 +622,28 @@ Gw
</tabViewItem>
<tabViewItem label="ZX Spectrum" identifier="spectrum" id="HQv-oF-k8b">
<view key="view" id="bMx-F6-JUb">
<rect key="frame" x="10" y="7" width="400" height="76"/>
<rect key="frame" x="10" y="7" width="400" height="186"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gFZ-d4-WFv">
<rect key="frame" x="67" y="47" width="62" height="25"/>
<popUpButtonCell key="cell" type="push" title="+2a" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="21" imageScaling="axesIndependently" inset="2" selectedItem="Fo7-NL-Kv5" id="tYs-sA-oek">
<rect key="frame" x="67" y="157" width="76" height="25"/>
<popUpButtonCell key="cell" type="push" title="16kb" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" tag="16" imageScaling="axesIndependently" inset="2" selectedItem="Fo7-NL-Kv5" id="tYs-sA-oek">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="8lt-dk-zPr">
<items>
<menuItem title="+2a" state="on" tag="21" id="Fo7-NL-Kv5"/>
<menuItem title="16kb" tag="16" id="Fo7-NL-Kv5"/>
<menuItem title="48kb" tag="48" id="xks-Rv-Umd"/>
<menuItem title="128kb" tag="128" id="w8h-lY-JLX"/>
<menuItem title="+2" tag="2" id="Vvu-ua-pjg"/>
<menuItem title="+2a" state="on" tag="21" id="bFk-nC-Txe"/>
<menuItem title="+3" tag="3" id="jwx-fZ-vXp"/>
</items>
</menu>
</popUpButtonCell>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="fJ3-ma-Byy">
<rect key="frame" x="18" y="53" width="46" height="16"/>
<rect key="frame" x="18" y="163" width="46" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="JId-Tp-LrE">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>

View File

@@ -298,6 +298,10 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
case "spectrum":
var model: CSMachineSpectrumModel = .plus2a
switch spectrumModelTypeButton.selectedItem!.tag {
case 16: model = .sixteenK
case 48: model = .fortyEightK
case 128: model = .oneTwoEightK
case 2: model = .plus2
case 21: model = .plus2a
case 3: model = .plus3
default: break

View File

@@ -0,0 +1,156 @@
//
// SpectrumVideoContentionTests.cpp
// Clock SignalTests
//
// Created by Thomas Harte on 23/4/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#import <XCTest/XCTest.h>
#include "../../../Machines/Sinclair/ZXSpectrum/Video.hpp"
@interface SpectrumVideoContentionTests : XCTestCase
@end
@implementation SpectrumVideoContentionTests
struct ContentionAnalysis {
int time_after_interrupt = 0;
int contended_lines = 0;
int contention_length = 0;
int line_length = 0;
int total_lines = 0;
HalfCycles pattern[8];
};
template <Sinclair::ZXSpectrum::VideoTiming video_timing> ContentionAnalysis analyse() {
Sinclair::ZXSpectrum::Video<video_timing> video;
ContentionAnalysis analysis;
// Advance to the start of the first interrupt.
while(video.get_interrupt_line()) {
video.run_for(HalfCycles(1));
}
while(!video.get_interrupt_line()) {
video.run_for(HalfCycles(1));
}
// Count half cycles until first non-zero contended time.
while(video.access_delay(HalfCycles(0)) == HalfCycles(0)) {
video.run_for(HalfCycles(2));
analysis.time_after_interrupt += 2;
}
// Grab the contention pattern.
for(int c = 0; c < 8; c++) {
analysis.pattern[c] = video.access_delay(HalfCycles(0));
video.run_for(HalfCycles(2));
}
// Figure out how long contention goes on for.
int c = 0;
analysis.contention_length = 16; // For the 16 just skipped.
do {
analysis.contention_length += 2;
video.run_for(HalfCycles(2));
c++;
} while(analysis.pattern[c&7] == video.access_delay(HalfCycles(0)));
// Look for next start of contention to determine line length.
analysis.line_length = analysis.contention_length;
while(video.access_delay(HalfCycles(0)) == HalfCycles(0)) {
video.run_for(HalfCycles(2));
analysis.line_length += 2;
}
// Count contended lines.
analysis.contended_lines = 1;
while(video.access_delay(HalfCycles(0)) == analysis.pattern[0]) {
video.run_for(HalfCycles(analysis.line_length));
++analysis.contended_lines;
}
// Count total lines.
analysis.total_lines = analysis.contended_lines;
while(video.access_delay(HalfCycles(0)) != analysis.pattern[0]) {
video.run_for(HalfCycles(analysis.line_length));
++analysis.total_lines;
}
return analysis;
}
- (void)test48k {
const auto analysis = analyse<Sinclair::ZXSpectrum::VideoTiming::FortyEightK>();
// Check time from interrupt.
XCTAssertEqual(analysis.time_after_interrupt, 14335*2);
// Check contention pattern.
XCTAssertEqual(analysis.pattern[0], HalfCycles(6 * 2));
XCTAssertEqual(analysis.pattern[1], HalfCycles(5 * 2));
XCTAssertEqual(analysis.pattern[2], HalfCycles(4 * 2));
XCTAssertEqual(analysis.pattern[3], HalfCycles(3 * 2));
XCTAssertEqual(analysis.pattern[4], HalfCycles(2 * 2));
XCTAssertEqual(analysis.pattern[5], HalfCycles(1 * 2));
XCTAssertEqual(analysis.pattern[6], HalfCycles(0 * 2));
XCTAssertEqual(analysis.pattern[7], HalfCycles(0 * 2));
// Check line length and count.
XCTAssertEqual(analysis.contention_length, 128*2);
XCTAssertEqual(analysis.line_length, 224*2);
XCTAssertEqual(analysis.contended_lines, 192);
XCTAssertEqual(analysis.total_lines, 312);
}
- (void)test128k {
const auto analysis = analyse<Sinclair::ZXSpectrum::VideoTiming::OneTwoEightK>();
// Check time from interrupt.
XCTAssertEqual(analysis.time_after_interrupt, 14361*2);
// Check contention pattern.
XCTAssertEqual(analysis.pattern[0], HalfCycles(6 * 2));
XCTAssertEqual(analysis.pattern[1], HalfCycles(5 * 2));
XCTAssertEqual(analysis.pattern[2], HalfCycles(4 * 2));
XCTAssertEqual(analysis.pattern[3], HalfCycles(3 * 2));
XCTAssertEqual(analysis.pattern[4], HalfCycles(2 * 2));
XCTAssertEqual(analysis.pattern[5], HalfCycles(1 * 2));
XCTAssertEqual(analysis.pattern[6], HalfCycles(0 * 2));
XCTAssertEqual(analysis.pattern[7], HalfCycles(0 * 2));
// Check line length and count.
XCTAssertEqual(analysis.contention_length, 128*2);
XCTAssertEqual(analysis.line_length, 228*2);
XCTAssertEqual(analysis.contended_lines, 192);
XCTAssertEqual(analysis.total_lines, 311);
}
- (void)testPlus3 {
const auto analysis = analyse<Sinclair::ZXSpectrum::VideoTiming::Plus3>();
// Check time from interrupt.
XCTAssertEqual(analysis.time_after_interrupt, 14361*2);
// Check contention pattern.
XCTAssertEqual(analysis.pattern[0], HalfCycles(1 * 2));
XCTAssertEqual(analysis.pattern[1], HalfCycles(0 * 2));
XCTAssertEqual(analysis.pattern[2], HalfCycles(7 * 2));
XCTAssertEqual(analysis.pattern[3], HalfCycles(6 * 2));
XCTAssertEqual(analysis.pattern[4], HalfCycles(5 * 2));
XCTAssertEqual(analysis.pattern[5], HalfCycles(4 * 2));
XCTAssertEqual(analysis.pattern[6], HalfCycles(3 * 2));
XCTAssertEqual(analysis.pattern[7], HalfCycles(2 * 2));
// Check line length and count.
XCTAssertEqual(analysis.contention_length, 130*2); // By the manner used for detection above,
// the first obviously missing contention spot
// will be after 130 cycles, not the textbook 129,
// because cycle 130 is a delay of 0 either way.
XCTAssertEqual(analysis.line_length, 228*2);
XCTAssertEqual(analysis.contended_lines, 192);
XCTAssertEqual(analysis.total_lines, 311);
}
@end

File diff suppressed because it is too large Load Diff

View File

@@ -12,7 +12,13 @@ CONFIG += object_parallel_to_source
INCLUDEPATH += $$[QT_INSTALL_PREFIX]/src/3rdparty/zlib
LIBS += -lz
# Add flags (i) to identify that this is a Qt build; and
# If targetting X11, link against that.
linux {
QT += x11extras
LIBS += -lX11
}
# Add flags (i) to identify that this is a Qt build; and
# (ii) to disable asserts in release builds.
DEFINES += TARGET_QT
QMAKE_CXXFLAGS_RELEASE += -DNDEBUG
@@ -129,14 +135,16 @@ SOURCES += \
$$SRC/Storage/MassStorage/Encodings/*.cpp \
$$SRC/Storage/MassStorage/Formats/*.cpp \
$$SRC/Storage/MassStorage/SCSI/*.cpp \
$$SRC/Storage/State/*.cpp \
$$SRC/Storage/Tape/*.cpp \
$$SRC/Storage/Tape/Formats/*.cpp \
$$SRC/Storage/Tape/Parsers/*.cpp \
\
main.cpp \
mainwindow.cpp \
scantargetwidget.cpp \
timer.cpp
main.cpp \
mainwindow.cpp \
scantargetwidget.cpp \
timer.cpp \
keyboard.cpp
HEADERS += \
$$SRC/Activity/*.hpp \
@@ -269,16 +277,18 @@ HEADERS += \
$$SRC/Storage/MassStorage/Encodings/*.hpp \
$$SRC/Storage/MassStorage/Formats/*.hpp \
$$SRC/Storage/MassStorage/SCSI/*.hpp \
$$SRC/Storage/State/*.hpp \
$$SRC/Storage/Tape/*.hpp \
$$SRC/Storage/Tape/Formats/*.hpp \
$$SRC/Storage/Tape/Parsers/*.hpp \
\
audiobuffer.h \
functionthread.h \
mainwindow.h \
scantargetwidget.h \
settings.h \
timer.h
audiobuffer.h \
functionthread.h \
mainwindow.h \
scantargetwidget.h \
settings.h \
keyboard.h \
timer.h
FORMS += \
mainwindow.ui

202
OSBindings/Qt/keyboard.cpp Normal file
View File

@@ -0,0 +1,202 @@
#include "keyboard.h"
#include <QDebug>
#include <QGuiApplication>
// Qt is the worst.
//
// Assume your keyboard has a key labelled both . and >, as on US and UK keyboards. Call it the dot key.
// Perform the following:
// 1. press dot key;
// 2. press shift key;
// 3. release dot key;
// 4. release shift key.
//
// Per empirical testing, and key repeat aside, on both macOS and Ubuntu 19.04 that sequence will result in
// _three_ keypress events, but only _two_ key release events. You'll get presses for Qt::Key_Period, Qt::Key_Greater
// and Qt::Key_Shift. You'll get releases only for Qt::Key_Greater and Qt::Key_Shift.
//
// How can you detect at runtime that Key_Greater and Key_Period are the same physical key?
//
// You can't. On Ubuntu they have the same values for QKeyEvent::nativeScanCode(), which are unique to the key,
// but they have different ::nativeVirtualKey()s.
//
// On macOS they have the same ::nativeScanCode() only because on macOS [almost] all keys have the same
// ::nativeScanCode(). So that's not usable. They have the same ::nativeVirtualKey()s, but since that isn't true
// on Ubuntu, that's also not usable.
//
// So how can you track the physical keys on a keyboard via Qt?
//
// You can't. Qt is the worst. SDL doesn't have this problem, including in X11, but I don't want the non-Qt dependency.
#ifdef Q_OS_LINUX
#define HAS_X11
#endif
#ifdef HAS_X11
#include <QX11Info>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#endif
KeyboardMapper::KeyboardMapper() {
#ifdef HAS_X11
struct DesiredMapping {
KeySym source = 0;
Inputs::Keyboard::Key destination;
};
using Key = Inputs::Keyboard::Key;
constexpr DesiredMapping mappings[] = {
{XK_Escape, Key::Escape},
{XK_F1, Key::F1}, {XK_F2, Key::F2}, {XK_F3, Key::F3}, {XK_F4, Key::F4}, {XK_F5, Key::F5},
{XK_F6, Key::F6}, {XK_F7, Key::F7}, {XK_F8, Key::F8}, {XK_F9, Key::F9}, {XK_F10, Key::F10},
{XK_F11, Key::F11}, {XK_F12, Key::F12},
{XK_Sys_Req, Key::PrintScreen},
{XK_Scroll_Lock, Key::ScrollLock},
{XK_Pause, Key::Pause},
{XK_grave, Key::BackTick},
{XK_1, Key::k1}, {XK_2, Key::k2}, {XK_3, Key::k3}, {XK_4, Key::k4}, {XK_5, Key::k5},
{XK_6, Key::k6}, {XK_7, Key::k7}, {XK_8, Key::k8}, {XK_9, Key::k9}, {XK_0, Key::k0},
{XK_minus, Key::Hyphen},
{XK_equal, Key::Equals},
{XK_BackSpace, Key::Backspace},
{XK_Tab, Key::Tab},
{XK_Q, Key::Q}, {XK_W, Key::W}, {XK_E, Key::E}, {XK_R, Key::R}, {XK_T, Key::T},
{XK_Y, Key::Y}, {XK_U, Key::U}, {XK_I, Key::I}, {XK_O, Key::O}, {XK_P, Key::P},
{XK_bracketleft, Key::OpenSquareBracket},
{XK_bracketright, Key::CloseSquareBracket},
{XK_backslash, Key::Backslash},
{XK_Caps_Lock, Key::CapsLock},
{XK_A, Key::A}, {XK_S, Key::S}, {XK_D, Key::D}, {XK_F, Key::F}, {XK_G, Key::G},
{XK_H, Key::H}, {XK_J, Key::J}, {XK_K, Key::K}, {XK_L, Key::L},
{XK_semicolon, Key::Semicolon},
{XK_apostrophe, Key::Quote},
{XK_Return, Key::Enter},
{XK_Shift_L, Key::LeftShift},
{XK_Z, Key::Z}, {XK_X, Key::X}, {XK_C, Key::C}, {XK_V, Key::V},
{XK_B, Key::B}, {XK_N, Key::N}, {XK_M, Key::M},
{XK_comma, Key::Comma},
{XK_period, Key::FullStop},
{XK_slash, Key::ForwardSlash},
{XK_Shift_R, Key::RightShift},
{XK_Control_L, Key::LeftControl},
{XK_Control_R, Key::RightControl},
{XK_Alt_L, Key::LeftOption},
{XK_Alt_R, Key::RightOption},
{XK_Meta_L, Key::LeftMeta},
{XK_Meta_R, Key::RightMeta},
{XK_space, Key::Space},
{XK_Left, Key::Left}, {XK_Right, Key::Right}, {XK_Up, Key::Up}, {XK_Down, Key::Down},
{XK_Insert, Key::Insert},
{XK_Delete, Key::Delete},
{XK_Home, Key::Home},
{XK_End, Key::End},
{XK_Num_Lock, Key::NumLock},
{XK_KP_Divide, Key::KeypadSlash},
{XK_KP_Multiply, Key::KeypadAsterisk},
{XK_KP_Delete, Key::KeypadDelete},
{XK_KP_7, Key::Keypad7}, {XK_KP_8, Key::Keypad8}, {XK_KP_9, Key::Keypad9}, {XK_KP_Add, Key::KeypadPlus},
{XK_KP_4, Key::Keypad4}, {XK_KP_5, Key::Keypad5}, {XK_KP_6, Key::Keypad6}, {XK_KP_Subtract, Key::KeypadMinus},
{XK_KP_1, Key::Keypad1}, {XK_KP_2, Key::Keypad2}, {XK_KP_3, Key::Keypad3}, {XK_KP_Enter, Key::KeypadEnter},
{XK_KP_0, Key::Keypad0},
{XK_KP_Decimal, Key::KeypadDecimalPoint},
{XK_KP_Equal, Key::KeypadEquals},
{XK_Help, Key::Help},
{}
};
// Extra level of nonsense here:
//
// (1) assume a PC-esque keyboard, with a close-to-US/UK layout;
// (2) from there, use any of the X11 KeySyms I'd expect to be achievable from each physical key to
// look up the X11 KeyCode;
// (3) henceforth, map from X11 KeyCode to the Inputs::Keyboard::Key.
const DesiredMapping *mapping = mappings;
while(mapping->source != 0) {
const auto code = XKeysymToKeycode(QX11Info::display(), mapping->source);
keyByKeySym[code] = mapping->destination;
++mapping;
}
#endif
}
std::optional<Inputs::Keyboard::Key> KeyboardMapper::keyForEvent(QKeyEvent *event) {
#ifdef HAS_X11
if(QGuiApplication::platformName() == QLatin1String("xcb")) {
const auto key = keyByKeySym.find(event->nativeScanCode());
if(key == keyByKeySym.end()) return std::nullopt;
return key->second;
}
#endif
// Fall back on a limited, faulty adaptation.
#define BIND2(qtKey, clkKey) case Qt::qtKey: return Inputs::Keyboard::Key::clkKey;
#define BIND(key) BIND2(Key_##key, key)
switch(event->key()) {
default: return {};
BIND(Escape);
BIND(F1); BIND(F2); BIND(F3); BIND(F4); BIND(F5); BIND(F6);
BIND(F7); BIND(F8); BIND(F9); BIND(F10); BIND(F11); BIND(F12);
BIND2(Key_Print, PrintScreen);
BIND(ScrollLock); BIND(Pause);
BIND2(Key_AsciiTilde, BackTick);
BIND2(Key_1, k1); BIND2(Key_2, k2); BIND2(Key_3, k3); BIND2(Key_4, k4); BIND2(Key_5, k5);
BIND2(Key_6, k6); BIND2(Key_7, k7); BIND2(Key_8, k8); BIND2(Key_9, k9); BIND2(Key_0, k0);
BIND2(Key_Minus, Hyphen);
BIND2(Key_Plus, Equals);
BIND(Backspace);
BIND(Tab); BIND(Q); BIND(W); BIND(E); BIND(R); BIND(T); BIND(Y);
BIND(U); BIND(I); BIND(O); BIND(P);
BIND2(Key_BraceLeft, OpenSquareBracket);
BIND2(Key_BraceRight, CloseSquareBracket);
BIND(Backslash);
BIND(CapsLock); BIND(A); BIND(S); BIND(D); BIND(F); BIND(G);
BIND(H); BIND(J); BIND(K); BIND(L);
BIND(Semicolon);
BIND2(Key_Apostrophe, Quote);
BIND2(Key_QuoteDbl, Quote);
// TODO: something to hash?
BIND2(Key_Return, Enter);
BIND2(Key_Shift, LeftShift);
BIND(Z); BIND(X); BIND(C); BIND(V);
BIND(B); BIND(N); BIND(M);
BIND(Comma);
BIND2(Key_Period, FullStop);
BIND2(Key_Slash, ForwardSlash);
// Omitted: right shift.
BIND2(Key_Control, LeftControl);
BIND2(Key_Alt, LeftOption);
BIND2(Key_Meta, LeftMeta);
BIND(Space);
BIND2(Key_AltGr, RightOption);
BIND(Left); BIND(Right); BIND(Up); BIND(Down);
BIND(Insert); BIND(Home); BIND(PageUp); BIND(Delete); BIND(End); BIND(PageDown);
BIND(NumLock);
}
#undef BIND
#undef BIND2
}

18
OSBindings/Qt/keyboard.h Normal file
View File

@@ -0,0 +1,18 @@
#ifndef KEYBOARD_H
#define KEYBOARD_H
#include <QKeyEvent>
#include <map>
#include <optional>
#include "../../Inputs/Keyboard.hpp"
class KeyboardMapper {
public:
KeyboardMapper();
std::optional<Inputs::Keyboard::Key> keyForEvent(QKeyEvent *);
private:
std::map<quint32, Inputs::Keyboard::Key> keyByKeySym;
};
#endif // MAINWINDOW_H

View File

@@ -851,176 +851,10 @@ void MainWindow::keyReleaseEvent(QKeyEvent *event) {
processEvent(event);
}
// Qt is the worst.
//
// Assume your keyboard has a key labelled both . and >, as they do on US and UK keyboards. Call it the dot key.
// Perform the following:
// 1. press dot key;
// 2. press shift key;
// 3. release dot key;
// 4. release shift key.
//
// Per empirical testing, and key repeat aside, on both macOS and Ubuntu 19.04 that sequence will result in
// _three_ keypress events, but only _two_ key release events. You'll get presses for Qt::Key_Period, Qt::Key_Greater
// and Qt::Key_Shift. You'll get releases only for Qt::Key_Greater and Qt::Key_Shift.
//
// How can you detect at runtime that Key_Greater and Key_Period are the same physical key?
//
// You can't. On Ubuntu they have the same values for QKeyEvent::nativeScanCode(), which are unique to the key,
// but they have different ::nativeVirtualKey()s.
//
// On macOS they have the same ::nativeScanCode() only because on macOS [almost] all keys have the same
// ::nativeScanCode(). So that's not usable. They have the same ::nativeVirtualKey()s, but since that isn't true
// on Ubuntu, that's also not usable.
//
// So how can you track the physical keys on a keyboard via Qt?
//
// You can't. Qt is the worst. SDL doesn't have this problem, including in X11, but I'm not sure I want the extra
// dependency. I may need to reassess.
std::optional<Inputs::Keyboard::Key> MainWindow::keyForEvent(QKeyEvent *event) {
// Workaround for X11: assume PC-esque mapping from ::nativeScanCode to symbols.
//
// Yucky, ugly, harcoded yuck. TODO: work out how `xmodmap -pke` seems to derive these codes at runtime.
if(QGuiApplication::platformName() == QLatin1String("xcb")) {
#define BIND(code, key) case code: return Inputs::Keyboard::Key::key;
switch(event->nativeScanCode()) {
default: qDebug() << "Unmapped" << event->nativeScanCode(); return {};
BIND(1, Escape);
BIND(67, F1); BIND(68, F2); BIND(69, F3); BIND(70, F4); BIND(71, F5);
BIND(72, F6); BIND(73, F7); BIND(74, F8); BIND(75, F9); BIND(76, F10);
BIND(95, F11); BIND(96, F12);
BIND(107, PrintScreen);
BIND(78, ScrollLock);
BIND(127, Pause);
BIND(49, BackTick);
BIND(10, k1); BIND(11, k2); BIND(12, k3); BIND(13, k4); BIND(14, k5);
BIND(15, k6); BIND(16, k7); BIND(17, k8); BIND(18, k9); BIND(19, k0);
BIND(20, Hyphen);
BIND(21, Equals);
BIND(22, Backspace);
BIND(23, Tab);
BIND(24, Q); BIND(25, W); BIND(26, E); BIND(27, R); BIND(28, T);
BIND(29, Y); BIND(30, U); BIND(31, I); BIND(32, O); BIND(33, P);
BIND(34, OpenSquareBracket);
BIND(35, CloseSquareBracket);
BIND(51, Backslash);
BIND(66, CapsLock);
BIND(38, A); BIND(39, S); BIND(40, D); BIND(41, F); BIND(42, G);
BIND(43, H); BIND(44, J); BIND(45, K); BIND(46, L);
BIND(47, Semicolon);
BIND(48, Quote);
BIND(36, Enter);
BIND(50, LeftShift);
BIND(52, Z); BIND(53, X); BIND(54, C); BIND(55, V);
BIND(56, B); BIND(57, N); BIND(58, M);
BIND(59, Comma);
BIND(60, FullStop);
BIND(61, ForwardSlash);
BIND(62, RightShift);
BIND(105, LeftControl);
BIND(204, LeftOption);
BIND(205, LeftMeta);
BIND(65, Space);
BIND(108, RightOption);
BIND(113, Left); BIND(114, Right); BIND(111, Up); BIND(116, Down);
BIND(118, Insert);
BIND(119, Delete);
BIND(110, Home);
BIND(115, End);
BIND(77, NumLock);
BIND(106, KeypadSlash);
BIND(63, KeypadAsterisk);
BIND(91, KeypadDelete);
BIND(79, Keypad7); BIND(80, Keypad8); BIND(81, Keypad9); BIND(86, KeypadPlus);
BIND(83, Keypad4); BIND(84, Keypad5); BIND(85, Keypad6); BIND(82, KeypadMinus);
BIND(87, Keypad1); BIND(88, Keypad2); BIND(89, Keypad3); BIND(104, KeypadEnter);
BIND(90, Keypad0);
BIND(129, KeypadDecimalPoint);
BIND(125, KeypadEquals);
BIND(146, Help);
}
#undef BIND
}
// Fall back on a limited, faulty adaptation.
#define BIND2(qtKey, clkKey) case Qt::qtKey: return Inputs::Keyboard::Key::clkKey;
#define BIND(key) BIND2(Key_##key, key)
switch(event->key()) {
default: return {};
BIND(Escape);
BIND(F1); BIND(F2); BIND(F3); BIND(F4); BIND(F5); BIND(F6);
BIND(F7); BIND(F8); BIND(F9); BIND(F10); BIND(F11); BIND(F12);
BIND2(Key_Print, PrintScreen);
BIND(ScrollLock); BIND(Pause);
BIND2(Key_AsciiTilde, BackTick);
BIND2(Key_1, k1); BIND2(Key_2, k2); BIND2(Key_3, k3); BIND2(Key_4, k4); BIND2(Key_5, k5);
BIND2(Key_6, k6); BIND2(Key_7, k7); BIND2(Key_8, k8); BIND2(Key_9, k9); BIND2(Key_0, k0);
BIND2(Key_Minus, Hyphen);
BIND2(Key_Plus, Equals);
BIND(Backspace);
BIND(Tab); BIND(Q); BIND(W); BIND(E); BIND(R); BIND(T); BIND(Y);
BIND(U); BIND(I); BIND(O); BIND(P);
BIND2(Key_BraceLeft, OpenSquareBracket);
BIND2(Key_BraceRight, CloseSquareBracket);
BIND(Backslash);
BIND(CapsLock); BIND(A); BIND(S); BIND(D); BIND(F); BIND(G);
BIND(H); BIND(J); BIND(K); BIND(L);
BIND(Semicolon);
BIND2(Key_Apostrophe, Quote);
BIND2(Key_QuoteDbl, Quote);
// TODO: something to hash?
BIND2(Key_Return, Enter);
BIND2(Key_Shift, LeftShift);
BIND(Z); BIND(X); BIND(C); BIND(V);
BIND(B); BIND(N); BIND(M);
BIND(Comma);
BIND2(Key_Period, FullStop);
BIND2(Key_Slash, ForwardSlash);
// Omitted: right shift.
BIND2(Key_Control, LeftControl);
BIND2(Key_Alt, LeftOption);
BIND2(Key_Meta, LeftMeta);
BIND(Space);
BIND2(Key_AltGr, RightOption);
BIND(Left); BIND(Right); BIND(Up); BIND(Down);
BIND(Insert); BIND(Home); BIND(PageUp); BIND(Delete); BIND(End); BIND(PageDown);
BIND(NumLock);
}
#undef BIND
#undef BIND2
}
bool MainWindow::processEvent(QKeyEvent *event) {
if(!machine) return true;
const auto key = keyForEvent(event);
const auto key = keyMapper.keyForEvent(event);
if(!key) return true;
const bool isPressed = event->type() == QEvent::KeyPress;
@@ -1258,9 +1092,13 @@ void MainWindow::start_spectrum() {
using Target = Analyser::Static::ZXSpectrum::Target;
auto target = std::make_unique<Target>();
switch(ui->oricModelComboBox->currentIndex()) {
default: target->model = Target::Model::Plus2a; break;
case 1: target->model = Target::Model::Plus3; break;
switch(ui->spectrumModelComboBox->currentIndex()) {
default: target->model = Target::Model::SixteenK; break;
case 1: target->model = Target::Model::FortyEightK; break;
case 2: target->model = Target::Model::OneTwoEightK; break;
case 3: target->model = Target::Model::Plus2; break;
case 4: target->model = Target::Model::Plus2a; break;
case 5: target->model = Target::Model::Plus3; break;
}
launchTarget(std::move(target));

View File

@@ -12,6 +12,7 @@
#include "timer.h"
#include "ui_mainwindow.h"
#include "functionthread.h"
#include "keyboard.h"
#include "../../Analyser/Static/StaticAnalyser.hpp"
#include "../../Machines/Utility/MachineForTarget.hpp"
@@ -144,7 +145,7 @@ class MainWindow : public QMainWindow, public Outputs::Speaker::Speaker::Delegat
QMenu *inputMenu = nullptr;
std::optional<Inputs::Keyboard::Key> keyForEvent(QKeyEvent *);
KeyboardMapper keyMapper;
void register_led(const std::string &) override;
void set_led_status(const std::string &, bool) override;

View File

@@ -551,6 +551,26 @@
</item>
<item row="0" column="1">
<widget class="QComboBox" name="spectrumModelComboBox">
<item>
<property name="text">
<string>16kb</string>
</property>
</item>
<item>
<property name="text">
<string>48kb</string>
</property>
</item>
<item>
<property name="text">
<string>128kb</string>
</property>
</item>
<item>
<property name="text">
<string>+2</string>
</property>
</item>
<item>
<property name="text">
<string>+2a</string>

View File

@@ -125,6 +125,7 @@ SOURCES += glob.glob('../../Storage/MassStorage/*.cpp')
SOURCES += glob.glob('../../Storage/MassStorage/Encodings/*.cpp')
SOURCES += glob.glob('../../Storage/MassStorage/Formats/*.cpp')
SOURCES += glob.glob('../../Storage/MassStorage/SCSI/*.cpp')
SOURCES += glob.glob('../../Storage/State/*.cpp')
SOURCES += glob.glob('../../Storage/Tape/*.cpp')
SOURCES += glob.glob('../../Storage/Tape/Formats/*.cpp')
SOURCES += glob.glob('../../Storage/Tape/Parsers/*.cpp')

View File

@@ -33,18 +33,18 @@ uint16_t ProcessorBase::get_value_of_register(Register r) const {
case Register::L: return hl_.halves.low;
case Register::HL: return hl_.full;
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_.halves.high;
case Register::CDash: return bcDash_.halves.low;
case Register::BCDash: return bcDash_.full;
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_.halves.high;
case Register::LDash: return hlDash_.halves.low;
case Register::HLDash: return hlDash_.full;
case Register::ADash: return af_dash_.halves.high;
case Register::FlagsDash: return af_dash_.halves.low;
case Register::AFDash: return af_dash_.full;
case Register::BDash: return bc_dash_.halves.high;
case Register::CDash: return bc_dash_.halves.low;
case Register::BCDash: return bc_dash_.full;
case Register::DDash: return de_dash_.halves.high;
case Register::EDash: return de_dash_.halves.low;
case Register::DEDash: return de_dash_.full;
case Register::HDash: return hl_dash_.halves.high;
case Register::LDash: return hl_dash_.halves.low;
case Register::HLDash: return hl_dash_.full;
case Register::IXh: return ix_.halves.high;
case Register::IXl: return ix_.halves.low;
@@ -86,18 +86,18 @@ void ProcessorBase::set_value_of_register(Register r, uint16_t value) {
case Register::L: hl_.halves.low = uint8_t(value); break;
case Register::HL: hl_.full = value; break;
case Register::ADash: afDash_.halves.high = uint8_t(value); break;
case Register::FlagsDash: afDash_.halves.low = uint8_t(value); break;
case Register::AFDash: afDash_.full = value; break;
case Register::BDash: bcDash_.halves.high = uint8_t(value); break;
case Register::CDash: bcDash_.halves.low = uint8_t(value); break;
case Register::BCDash: bcDash_.full = value; break;
case Register::DDash: deDash_.halves.high = uint8_t(value); break;
case Register::EDash: deDash_.halves.low = uint8_t(value); break;
case Register::DEDash: deDash_.full = value; break;
case Register::HDash: hlDash_.halves.high = uint8_t(value); break;
case Register::LDash: hlDash_.halves.low = uint8_t(value); break;
case Register::HLDash: hlDash_.full = value; break;
case Register::ADash: af_dash_.halves.high = uint8_t(value); break;
case Register::FlagsDash: af_dash_.halves.low = uint8_t(value); break;
case Register::AFDash: af_dash_.full = value; break;
case Register::BDash: bc_dash_.halves.high = uint8_t(value); break;
case Register::CDash: bc_dash_.halves.low = uint8_t(value); break;
case Register::BCDash: bc_dash_.full = value; break;
case Register::DDash: de_dash_.halves.high = uint8_t(value); break;
case Register::EDash: de_dash_.halves.low = uint8_t(value); break;
case Register::DEDash: de_dash_.full = value; break;
case Register::HDash: hl_dash_.halves.high = uint8_t(value); break;
case Register::LDash: hl_dash_.halves.low = uint8_t(value); break;
case Register::HLDash: hl_dash_.full = value; break;
case Register::IXh: ix_.halves.high = uint8_t(value); break;
case Register::IXl: ix_.halves.low = uint8_t(value); break;

View File

@@ -82,24 +82,25 @@ template < class T,
}
number_of_cycles_ -= operation->machine_cycle.length;
last_request_status_ = request_status_;
// TODO: eliminate this conditional if all bus cycles have an address filled in.
last_address_bus_ = operation->machine_cycle.address ? *operation->machine_cycle.address : 0xdead;
number_of_cycles_ -= bus_handler_.perform_machine_cycle(operation->machine_cycle);
if(uses_bus_request && bus_request_line_) goto do_bus_acknowledge;
break;
case MicroOp::MoveToNextProgram:
advance_operation();
break;
case MicroOp::DecodeOperation:
case MicroOp::IncrementR:
refresh_addr_ = ir_;
ir_.halves.low = (ir_.halves.low & 0x80) | ((ir_.halves.low + current_instruction_page_->r_step) & 0x7f);
ir_.halves.low = (ir_.halves.low & 0x80) | ((ir_.halves.low + 1) & 0x7f);
break;
case MicroOp::DecodeOperation:
pc_.full += pc_increment_ & uint16_t(halt_mask_);
scheduled_program_counter_ = current_instruction_page_->instructions[operation_ & halt_mask_];
flag_adjustment_history_ <<= 1;
break;
case MicroOp::DecodeOperationNoRChange:
refresh_addr_ = ir_;
pc_.full += pc_increment_ & uint16_t(halt_mask_);
scheduled_program_counter_ = current_instruction_page_->instructions[operation_ & halt_mask_];
break;
case MicroOp::Increment8NoFlags: ++ *static_cast<uint8_t *>(operation->source); break;
case MicroOp::Increment16: ++ *static_cast<uint16_t *>(operation->source); break;
@@ -460,17 +461,17 @@ template < class T,
case MicroOp::ExAFAFDash: {
const uint8_t a = a_;
const uint8_t f = get_flags();
set_flags(afDash_.halves.low);
a_ = afDash_.halves.high;
afDash_.halves.high = a;
afDash_.halves.low = f;
set_flags(af_dash_.halves.low);
a_ = af_dash_.halves.high;
af_dash_.halves.high = a;
af_dash_.halves.low = f;
} break;
case MicroOp::EXX: {
uint16_t temp;
swap(de_, deDash_);
swap(bc_, bcDash_);
swap(hl_, hlDash_);
swap(de_, de_dash_);
swap(bc_, bc_dash_);
swap(hl_, hl_dash_);
} break;
#undef swap
@@ -927,7 +928,7 @@ template < class T,
return wait_line_;
}
#define isTerminal(n) (n == MicroOp::MoveToNextProgram || n == MicroOp::DecodeOperation || n == MicroOp::DecodeOperationNoRChange)
#define isTerminal(n) (n == MicroOp::MoveToNextProgram || n == MicroOp::DecodeOperation)
template < class T,
bool uses_bus_request,

View File

@@ -20,14 +20,14 @@ ProcessorStorage::ProcessorStorage() {
#define ReadOpcodeWait(f) PartialMachineCycle(PartialMachineCycle::ReadOpcodeWait, HalfCycles(2), &pc_.full, &operation_, f)
#define ReadOpcodeEnd() PartialMachineCycle(PartialMachineCycle::ReadOpcode, HalfCycles(1), &pc_.full, &operation_, false)
#define Refresh(len) PartialMachineCycle(PartialMachineCycle::Refresh, HalfCycles(len), &refresh_addr_.full, nullptr, false)
#define Refresh() PartialMachineCycle(PartialMachineCycle::Refresh, HalfCycles(4), &refresh_addr_.full, nullptr, false)
#define ReadStart(addr, val) PartialMachineCycle(PartialMachineCycle::ReadStart, HalfCycles(3), &addr.full, &val, false)
#define ReadWait(l, addr, val, f) PartialMachineCycle(PartialMachineCycle::ReadWait, HalfCycles(l), &addr.full, &val, f)
#define ReadWait(addr, val) PartialMachineCycle(PartialMachineCycle::ReadWait, HalfCycles(2), &addr.full, &val, true)
#define ReadEnd(addr, val) PartialMachineCycle(PartialMachineCycle::Read, HalfCycles(3), &addr.full, &val, false)
#define WriteStart(addr, val) PartialMachineCycle(PartialMachineCycle::WriteStart,HalfCycles(3), &addr.full, &val, false)
#define WriteWait(l, addr, val, f) PartialMachineCycle(PartialMachineCycle::WriteWait, HalfCycles(l), &addr.full, &val, f)
#define WriteWait(addr, val) PartialMachineCycle(PartialMachineCycle::WriteWait, HalfCycles(2), &addr.full, &val, true)
#define WriteEnd(addr, val) PartialMachineCycle(PartialMachineCycle::Write, HalfCycles(3), &addr.full, &val, false)
#define InputStart(addr, val) PartialMachineCycle(PartialMachineCycle::InputStart, HalfCycles(3), &addr.full, &val, false)
@@ -38,41 +38,24 @@ ProcessorStorage::ProcessorStorage() {
#define OutputWait(addr, val, f) PartialMachineCycle(PartialMachineCycle::OutputWait, HalfCycles(2), &addr.full, &val, f)
#define OutputEnd(addr, val) PartialMachineCycle(PartialMachineCycle::Output, HalfCycles(3), &addr.full, &val, false)
#define IntAckStart(length, val) PartialMachineCycle(PartialMachineCycle::InterruptStart, HalfCycles(length), nullptr, &val, false)
#define IntWait(val) PartialMachineCycle(PartialMachineCycle::InterruptWait, HalfCycles(2), nullptr, &val, true)
#define IntAckEnd(val) PartialMachineCycle(PartialMachineCycle::Interrupt, HalfCycles(3), nullptr, &val, false)
#define IntAckStart(length, val) PartialMachineCycle(PartialMachineCycle::InterruptStart, HalfCycles(length), &pc_.full, &val, false)
#define IntWait(val) PartialMachineCycle(PartialMachineCycle::InterruptWait, HalfCycles(2), &pc_.full, &val, true)
#define IntAckEnd(val) PartialMachineCycle(PartialMachineCycle::Interrupt, HalfCycles(3), &pc_.full, &val, false)
// A wrapper to express a bus operation as a micro-op
#define BusOp(op) {MicroOp::BusOperation, nullptr, nullptr, op}
// Compound bus operations, as micro-ops
// Read3 is a standard read cycle: 1.5 cycles, then check the wait line, then 1.5 cycles;
// Read4 is a four-cycle read that has to do something to calculate the address: 1.5 cycles, then an extra wait cycle, then check the wait line, then 1.5 cycles;
// Read4Pre is a four-cycle read that has to do something after reading: 1.5 cycles, then check the wait line, then an extra wait cycle, then 1.5 cycles;
// Read5 is a five-cycle read: 1.5 cycles, two wait cycles, check the wait line, 1.5 cycles.
#define Read3(addr, val) BusOp(ReadStart(addr, val)), BusOp(ReadWait(2, addr, val, true)), BusOp(ReadEnd(addr, val))
#define Read4(addr, val) BusOp(ReadStart(addr, val)), BusOp(ReadWait(2, addr, val, false)), BusOp(ReadWait(2, addr, val, true)), BusOp(ReadEnd(addr, val))
#define Read4Pre(addr, val) BusOp(ReadStart(addr, val)), BusOp(ReadWait(2, addr, val, true)), BusOp(ReadWait(2, addr, val, false)), BusOp(ReadEnd(addr, val))
#define Read5(addr, val) BusOp(ReadStart(addr, val)), BusOp(ReadWait(4, addr, val, false)), BusOp(ReadWait(2, addr, val, true)), BusOp(ReadEnd(addr, val))
#define Write3(addr, val) BusOp(WriteStart(addr, val)), BusOp(WriteWait(2, addr, val, true)), BusOp(WriteEnd(addr, val))
#define Write5(addr, val) BusOp(WriteStart(addr, val)), BusOp(WriteWait(4, addr, val, false)), BusOp(WriteWait(2, addr, val, true)), BusOp(WriteEnd(addr, val))
#define Input(addr, val) BusOp(InputStart(addr, val)), BusOp(InputWait(addr, val, false)), BusOp(InputWait(addr, val, true)), BusOp(InputEnd(addr, val))
#define Output(addr, val) BusOp(OutputStart(addr, val)), BusOp(OutputWait(addr, val, false)), BusOp(OutputWait(addr, val, true)), BusOp(OutputEnd(addr, val))
#define InternalOperation(len) {MicroOp::BusOperation, nullptr, nullptr, {PartialMachineCycle::Internal, HalfCycles(len), nullptr, nullptr, false}}
#define InternalOperation(len) {MicroOp::BusOperation, nullptr, nullptr, {PartialMachineCycle::Internal, HalfCycles(len), &last_address_bus_, nullptr, false}}
#define Read(addr, val) BusOp(ReadStart(addr, val)), BusOp(ReadWait(addr, val)), BusOp(ReadEnd(addr, val))
#define Write(addr, val) BusOp(WriteStart(addr, val)), BusOp(WriteWait(addr, val)), BusOp(WriteEnd(addr, val))
#define Input(addr, val) BusOp(InputStart(addr, val)), BusOp(InputWait(addr, val, false)), BusOp(InputWait(addr, val, true)), BusOp(InputEnd(addr, val))
#define Output(addr, val) BusOp(OutputStart(addr, val)), BusOp(OutputWait(addr, val, false)), BusOp(OutputWait(addr, val, true)), BusOp(OutputEnd(addr, val))
/// A sequence is a series of micro-ops that ends in a move-to-next-program operation.
#define Sequence(...) { __VA_ARGS__, {MicroOp::MoveToNextProgram} }
/// An instruction is the part of an instruction that follows instruction fetch; it should include two or more refresh cycles and then the work of the instruction.
#define Instr(r, ...) Sequence(BusOp(Refresh(r)), __VA_ARGS__)
/// A standard instruction is one with the most normal timing: two cycles of refresh, then the work.
#define StdInstr(...) Instr(4, __VA_ARGS__)
// Assumption made: those instructions that are rated with an opcode fetch greater than four cycles spend the extra time
// providing a lengthened refresh cycle. I assume this because the CPU doesn't have foresight and presumably spends the
// normal refresh time decoding. So if it gets to cycle four and realises it has two more cycles of work, I have assumed
@@ -82,65 +65,60 @@ ProcessorStorage::ProcessorStorage() {
#define Inc16(r) {(&r == &pc_) ? MicroOp::IncrementPC : MicroOp::Increment16, &r.full}
#define Inc8NoFlags(r) {MicroOp::Increment8NoFlags, &r}
#define ReadInc(addr, val) Read3(addr, val), Inc16(addr)
#define Read4Inc(addr, val) Read4(addr, val), Inc16(addr)
#define Read5Inc(addr, val) Read5(addr, val), Inc16(addr)
#define WriteInc(addr, val) Write3(addr, val), {MicroOp::Increment16, &addr.full}
#define ReadInc(addr, val) Read(addr, val), Inc16(addr)
#define WriteInc(addr, val) Write(addr, val), {MicroOp::Increment16, &addr.full}
#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 Read16(addr, val) ReadInc(addr, val.halves.low), Read(addr, val.halves.high)
#define Write16(addr, val) WriteInc(addr, val.halves.low), Write3(addr, val.halves.high)
#define Write16(addr, val) WriteInc(addr, val.halves.low), Write(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.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.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}
#define Push(x) {MicroOp::Decrement16, &sp_.full}, Write(sp_, x.halves.high), {MicroOp::Decrement16, &sp_.full}, Write(sp_, x.halves.low)
#define Pop(x) Read(sp_, x.halves.low), {MicroOp::Increment16, &sp_.full}, Read(sp_, x.halves.high), {MicroOp::Increment16, &sp_.full}
/* The following are actual instructions */
#define NOP Sequence(BusOp(Refresh(4)))
#define NOP { {MicroOp::MoveToNextProgram} }
#define JP(cc) StdInstr(Read16Inc(pc_, memptr_), {MicroOp::cc, nullptr}, {MicroOp::Move16, &memptr_.full, &pc_.full})
#define CALL(cc) StdInstr(ReadInc(pc_, memptr_.halves.low), {MicroOp::cc, conditional_call_untaken_program_.data()}, Read4Inc(pc_, memptr_.halves.high), Push(pc_), {MicroOp::Move16, &memptr_.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 JP(cc) Sequence(Read16Inc(pc_, memptr_), {MicroOp::cc}, {MicroOp::Move16, &memptr_.full, &pc_.full})
#define CALL(cc) Sequence(ReadInc(pc_, memptr_.halves.low), {MicroOp::cc, conditional_call_untaken_program_.data()}, ReadInc(pc_, memptr_.halves.high), InternalOperation(2), Push(pc_), {MicroOp::Move16, &memptr_.full, &pc_.full})
#define RET(cc) Sequence(InternalOperation(2), {MicroOp::cc}, Pop(memptr_), {MicroOp::Move16, &memptr_.full, &pc_.full})
#define JR(cc) Sequence(ReadInc(pc_, temp8_), {MicroOp::cc}, InternalOperation(10), {MicroOp::CalculateIndexAddress, &pc_.full}, {MicroOp::Move16, &memptr_.full, &pc_.full})
#define RST() Sequence(InternalOperation(2), {MicroOp::CalculateRSTDestination}, Push(pc_), {MicroOp::Move16, &memptr_.full, &pc_.full})
#define LD(a, b) Sequence({MicroOp::Move8, &b, &a})
#define LD_GROUP(r, ri) \
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}), \
Sequence(INDEX(), Read(INDEX_ADDR(), temp8_), {MicroOp::Move8, &temp8_, &ri}), \
LD(r, a_)
#define READ_OP_GROUP(op) \
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_})
Sequence({MicroOp::op, &bc_.halves.high}), Sequence({MicroOp::op, &bc_.halves.low}), \
Sequence({MicroOp::op, &de_.halves.high}), Sequence({MicroOp::op, &de_.halves.low}), \
Sequence({MicroOp::op, &index.halves.high}), Sequence({MicroOp::op, &index.halves.low}), \
Sequence(INDEX(), Read(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \
Sequence({MicroOp::op, &a_})
#define READ_OP_GROUP_D(op) \
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_})
Sequence({MicroOp::op, &bc_.halves.high}), Sequence({MicroOp::op, &bc_.halves.low}), \
Sequence({MicroOp::op, &de_.halves.high}), Sequence({MicroOp::op, &de_.halves.low}), \
Sequence({MicroOp::op, &index.halves.high}), Sequence({MicroOp::op, &index.halves.low}), \
Sequence(INDEX(), Read(INDEX_ADDR(), temp8_), InternalOperation(2), {MicroOp::op, &temp8_}), \
Sequence({MicroOp::op, &a_})
#define RMW(x, op, ...) StdInstr(INDEX(), Read4Pre(INDEX_ADDR(), x), {MicroOp::op, &x}, Write3(INDEX_ADDR(), x))
#define RMWI(x, op, ...) StdInstr(Read4(INDEX_ADDR(), x), {MicroOp::op, &x}, Write3(INDEX_ADDR(), x))
#define RMW(x, op, ...) Sequence(INDEX(), Read(INDEX_ADDR(), x), InternalOperation(2), {MicroOp::op, &x}, Write(INDEX_ADDR(), x))
#define RMWI(x, op, ...) Sequence(Read(INDEX_ADDR(), x), InternalOperation(2), {MicroOp::op, &x}, Write(INDEX_ADDR(), x))
#define MODIFY_OP_GROUP(op) \
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}), \
Sequence({MicroOp::op, &bc_.halves.high}), Sequence({MicroOp::op, &bc_.halves.low}), \
Sequence({MicroOp::op, &de_.halves.high}), Sequence({MicroOp::op, &de_.halves.low}), \
Sequence({MicroOp::op, &index.halves.high}), Sequence({MicroOp::op, &index.halves.low}), \
RMW(temp8_, op), \
StdInstr({MicroOp::op, &a_})
Sequence({MicroOp::op, &a_})
#define IX_MODIFY_OP_GROUP(op) \
RMWI(bc_.halves.high, op), \
@@ -153,18 +131,18 @@ ProcessorStorage::ProcessorStorage() {
RMWI(a_, op)
#define IX_READ_OP_GROUP(op) \
StdInstr(Read4(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \
StdInstr(Read4(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \
StdInstr(Read4(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \
StdInstr(Read4(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \
StdInstr(Read4(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \
StdInstr(Read4(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \
StdInstr(Read4(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_}), \
StdInstr(Read4(INDEX_ADDR(), temp8_), {MicroOp::op, &temp8_})
Sequence(Read(INDEX_ADDR(), temp8_), InternalOperation(2), {MicroOp::op, &temp8_}), \
Sequence(Read(INDEX_ADDR(), temp8_), InternalOperation(2), {MicroOp::op, &temp8_}), \
Sequence(Read(INDEX_ADDR(), temp8_), InternalOperation(2), {MicroOp::op, &temp8_}), \
Sequence(Read(INDEX_ADDR(), temp8_), InternalOperation(2), {MicroOp::op, &temp8_}), \
Sequence(Read(INDEX_ADDR(), temp8_), InternalOperation(2), {MicroOp::op, &temp8_}), \
Sequence(Read(INDEX_ADDR(), temp8_), InternalOperation(2), {MicroOp::op, &temp8_}), \
Sequence(Read(INDEX_ADDR(), temp8_), InternalOperation(2), {MicroOp::op, &temp8_}), \
Sequence(Read(INDEX_ADDR(), temp8_), InternalOperation(2), {MicroOp::op, &temp8_})
#define ADD16(d, s) StdInstr(InternalOperation(8), InternalOperation(6), {MicroOp::ADD16, &s.full, &d.full})
#define ADC16(d, s) StdInstr(InternalOperation(8), InternalOperation(6), {MicroOp::ADC16, &s.full, &d.full})
#define SBC16(d, s) StdInstr(InternalOperation(8), InternalOperation(6), {MicroOp::SBC16, &s.full, &d.full})
#define ADD16(d, s) Sequence(InternalOperation(8), InternalOperation(6), {MicroOp::ADD16, &s.full, &d.full})
#define ADC16(d, s) Sequence(InternalOperation(8), InternalOperation(6), {MicroOp::ADC16, &s.full, &d.full})
#define SBC16(d, s) Sequence(InternalOperation(8), InternalOperation(6), {MicroOp::SBC16, &s.full, &d.full})
void ProcessorStorage::install_default_instruction_set() {
MicroOp conditional_call_untaken_program[] = Sequence(ReadInc(pc_, memptr_.halves.high));
@@ -175,11 +153,8 @@ void ProcessorStorage::install_default_instruction_set() {
assemble_base_page(fd_page_, iy_, true, fdcb_page_);
assemble_ed_page(ed_page_);
fdcb_page_.r_step = 0;
fd_page_.is_indexed = true;
fdcb_page_.is_indexed = true;
ddcb_page_.r_step = 0;
dd_page_.is_indexed = true;
ddcb_page_.is_indexed = true;
@@ -202,9 +177,10 @@ void ProcessorStorage::install_default_instruction_set() {
BusOp(ReadOpcodeStart()),
BusOp(ReadOpcodeWait(true)),
BusOp(ReadOpcodeEnd()),
BusOp(Refresh(6)),
BusOp(Refresh()),
InternalOperation(2),
Push(pc_),
{ MicroOp::JumpTo66, nullptr, nullptr},
{ MicroOp::JumpTo66 },
{ MicroOp::MoveToNextProgram }
};
MicroOp irq_mode0_program[] = {
@@ -212,14 +188,14 @@ void ProcessorStorage::install_default_instruction_set() {
BusOp(IntAckStart(5, operation_)),
BusOp(IntWait(operation_)),
BusOp(IntAckEnd(operation_)),
{ MicroOp::DecodeOperationNoRChange }
{ MicroOp::DecodeOperation }
};
MicroOp irq_mode1_program[] = {
{ MicroOp::BeginIRQ },
BusOp(IntAckStart(7, operation_)), // 7 half cycles (including +
BusOp(IntWait(operation_)), // [potentially 2 half cycles] +
BusOp(IntAckEnd(operation_)), // Implicitly 3 half cycles +
BusOp(Refresh(4)), // 4 half cycles +
BusOp(Refresh()), // 4 half cycles +
Push(pc_), // 12 half cycles = 26 half cycles = 13 cycles
{ MicroOp::Move16, &temp16_.full, &pc_.full },
{ MicroOp::MoveToNextProgram }
@@ -229,7 +205,7 @@ void ProcessorStorage::install_default_instruction_set() {
BusOp(IntAckStart(7, temp16_.halves.low)),
BusOp(IntWait(temp16_.halves.low)),
BusOp(IntAckEnd(temp16_.halves.low)),
BusOp(Refresh(4)),
BusOp(Refresh()),
Push(pc_),
{ MicroOp::Move8, &ir_.halves.high, &temp16_.halves.high },
Read16(temp16_, pc_),
@@ -244,8 +220,8 @@ void ProcessorStorage::install_default_instruction_set() {
}
void ProcessorStorage::assemble_ed_page(InstructionPage &target) {
#define IN_C(r) StdInstr({MicroOp::Move16, &bc_.full, &memptr_.full}, Input(bc_, r), {MicroOp::SetInFlags, &r})
#define OUT_C(r) StdInstr(Output(bc_, r), {MicroOp::SetOutFlags})
#define IN_C(r) Sequence({MicroOp::Move16, &bc_.full, &memptr_.full}, Input(bc_, r), {MicroOp::SetInFlags, &r})
#define OUT_C(r) Sequence(Output(bc_, r), {MicroOp::SetOutFlags})
#define IN_OUT(r) IN_C(r), OUT_C(r)
#define NOP_ROW() NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP
@@ -255,58 +231,58 @@ void ProcessorStorage::assemble_ed_page(InstructionPage &target) {
NOP_ROW(), /* 0x20 */
NOP_ROW(), /* 0x30 */
/* 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_.halves.high}),
/* 0x42 SBC HL, BC */ SBC16(hl_, bc_), /* 0x43 LD (nn), BC */ Sequence(Read16Inc(pc_, memptr_), Write16(memptr_, bc_)),
/* 0x44 NEG */ Sequence({MicroOp::NEG}), /* 0x45 RETN */ Sequence(Pop(pc_), {MicroOp::RETN}),
/* 0x46 IM 0 */ Sequence({MicroOp::IM}), /* 0x47 LD I, A */ Sequence(InternalOperation(2), {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_, memptr_), Read16(memptr_, 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_.halves.low}),
/* 0x4a ADC HL, BC */ ADC16(hl_, bc_), /* 0x4b LD BC, (nn) */ Sequence(Read16Inc(pc_, memptr_), Read16(memptr_, bc_)),
/* 0x4c NEG */ Sequence({MicroOp::NEG}), /* 0x4d RETI */ Sequence(Pop(pc_), {MicroOp::RETN}),
/* 0x4e IM 0/1 */ Sequence({MicroOp::IM}), /* 0x4f LD R, A */ Sequence(InternalOperation(2), {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_.halves.high, &a_}, {MicroOp::SetAFlags}),
/* 0x52 SBC HL, DE */ SBC16(hl_, de_), /* 0x53 LD (nn), DE */ Sequence(Read16Inc(pc_, memptr_), Write16(memptr_, de_)),
/* 0x54 NEG */ Sequence({MicroOp::NEG}), /* 0x55 RETN */ Sequence(Pop(pc_), {MicroOp::RETN}),
/* 0x56 IM 1 */ Sequence({MicroOp::IM}), /* 0x57 LD A, I */ Sequence(InternalOperation(2), {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_, memptr_), Read16(memptr_, 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_.halves.low, &a_}, {MicroOp::SetAFlags}),
/* 0x5a ADC HL, DE */ ADC16(hl_, de_), /* 0x5b LD DE, (nn) */ Sequence(Read16Inc(pc_, memptr_), Read16(memptr_, de_)),
/* 0x5c NEG */ Sequence({MicroOp::NEG}), /* 0x5d RETN */ Sequence(Pop(pc_), {MicroOp::RETN}),
/* 0x5e IM 2 */ Sequence({MicroOp::IM}), /* 0x5f LD A, R */ Sequence(InternalOperation(2), {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_)),
/* 0x62 SBC HL, HL */ SBC16(hl_, hl_), /* 0x63 LD (nn), HL */ Sequence(Read16Inc(pc_, memptr_), Write16(memptr_, hl_)),
/* 0x64 NEG */ Sequence({MicroOp::NEG}), /* 0x65 RETN */ Sequence(Pop(pc_), {MicroOp::RETN}),
/* 0x66 IM 0 */ Sequence({MicroOp::IM}), /* 0x67 RRD */ Sequence(Read(hl_, temp8_), InternalOperation(8), {MicroOp::RRD}, Write(hl_, temp8_)),
/* 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_, memptr_), Read16(memptr_, 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_)),
/* 0x70 IN (C) */ IN_C(temp8_), /* 0x71 OUT (C), 0 */ StdInstr({MicroOp::SetZero}, Output(bc_, temp8_), {MicroOp::SetOutFlags}),
/* 0x72 SBC HL, SP */ SBC16(hl_, sp_), /* 0x73 LD (nn), SP */ StdInstr(Read16Inc(pc_, memptr_), Write16(memptr_, sp_)),
/* 0x74 NEG */ StdInstr({MicroOp::NEG}), /* 0x75 RETN */ StdInstr(Pop(pc_), {MicroOp::RETN}),
/* 0x76 IM 1 */ StdInstr({MicroOp::IM}), /* 0x77 XX */ NOP,
/* 0x6a ADC HL, HL */ ADC16(hl_, hl_), /* 0x6b LD HL, (nn) */ Sequence(Read16Inc(pc_, memptr_), Read16(memptr_, hl_)),
/* 0x6c NEG */ Sequence({MicroOp::NEG}), /* 0x6d RETN */ Sequence(Pop(pc_), {MicroOp::RETN}),
/* 0x6e IM 0/1 */ Sequence({MicroOp::IM}), /* 0x6f RLD */ Sequence(Read(hl_, temp8_), InternalOperation(8), {MicroOp::RLD}, Write(hl_, temp8_)),
/* 0x70 IN (C) */ IN_C(temp8_), /* 0x71 OUT (C), 0 */ Sequence({MicroOp::SetZero}, Output(bc_, temp8_), {MicroOp::SetOutFlags}),
/* 0x72 SBC HL, SP */ SBC16(hl_, sp_), /* 0x73 LD (nn), SP */ Sequence(Read16Inc(pc_, memptr_), Write16(memptr_, sp_)),
/* 0x74 NEG */ Sequence({MicroOp::NEG}), /* 0x75 RETN */ Sequence(Pop(pc_), {MicroOp::RETN}),
/* 0x76 IM 1 */ Sequence({MicroOp::IM}), /* 0x77 XX */ NOP,
/* 0x78 IN A, (C); 0x79 OUT (C), A */ IN_OUT(a_),
/* 0x7a ADC HL, SP */ ADC16(hl_, sp_), /* 0x7b LD SP, (nn) */ StdInstr(Read16Inc(pc_, memptr_), Read16(memptr_, sp_)),
/* 0x7c NEG */ StdInstr({MicroOp::NEG}), /* 0x7d RETN */ StdInstr(Pop(pc_), {MicroOp::RETN}),
/* 0x7e IM 2 */ StdInstr({MicroOp::IM}), /* 0x7f XX */ NOP,
/* 0x7a ADC HL, SP */ ADC16(hl_, sp_), /* 0x7b LD SP, (nn) */ Sequence(Read16Inc(pc_, memptr_), Read16(memptr_, sp_)),
/* 0x7c NEG */ Sequence({MicroOp::NEG}), /* 0x7d RETN */ Sequence(Pop(pc_), {MicroOp::RETN}),
/* 0x7e IM 2 */ Sequence({MicroOp::IM}), /* 0x7f XX */ NOP,
NOP_ROW(), /* 0x80 ... 0x8f */
NOP_ROW(), /* 0x90 ... 0x9f */
/* 0xa0 LDI */ StdInstr(Read3(hl_, temp8_), Write5(de_, temp8_), {MicroOp::LDI}),
/* 0xa1 CPI */ StdInstr(Read3(hl_, temp8_), InternalOperation(10), {MicroOp::CPI}),
/* 0xa2 INI */ Instr(6, Input(bc_, temp8_), Write3(hl_, temp8_), {MicroOp::INI}),
/* 0xa3 OTI */ Instr(6, Read3(hl_, temp8_), {MicroOp::OUTI}, Output(bc_, temp8_)),
/* 0xa0 LDI */ Sequence(Read(hl_, temp8_), Write(de_, temp8_), InternalOperation(4), {MicroOp::LDI}),
/* 0xa1 CPI */ Sequence(Read(hl_, temp8_), InternalOperation(10), {MicroOp::CPI}),
/* 0xa2 INI */ Sequence(InternalOperation(2), Input(bc_, temp8_), Write(hl_, temp8_), {MicroOp::INI}),
/* 0xa3 OTI */ Sequence(InternalOperation(2), Read(hl_, temp8_), {MicroOp::OUTI}, Output(bc_, temp8_)),
NOP, NOP, NOP, NOP,
/* 0xa8 LDD */ StdInstr(Read3(hl_, temp8_), Write5(de_, temp8_), {MicroOp::LDD}),
/* 0xa9 CPD */ StdInstr(Read3(hl_, temp8_), InternalOperation(10), {MicroOp::CPD}),
/* 0xaa IND */ Instr(6, Input(bc_, temp8_), Write3(hl_, temp8_), {MicroOp::IND}),
/* 0xab OTD */ Instr(6, Read3(hl_, temp8_), {MicroOp::OUTD}, Output(bc_, temp8_)),
/* 0xa8 LDD */ Sequence(Read(hl_, temp8_), Write(de_, temp8_), InternalOperation(4), {MicroOp::LDD}),
/* 0xa9 CPD */ Sequence(Read(hl_, temp8_), InternalOperation(10), {MicroOp::CPD}),
/* 0xaa IND */ Sequence(InternalOperation(2), Input(bc_, temp8_), Write(hl_, temp8_), {MicroOp::IND}),
/* 0xab OTD */ Sequence(InternalOperation(2), Read(hl_, temp8_), {MicroOp::OUTD}, Output(bc_, temp8_)),
NOP, NOP, NOP, NOP,
/* 0xb0 LDIR */ StdInstr(Read3(hl_, temp8_), Write5(de_, temp8_), {MicroOp::LDIR}, InternalOperation(10)),
/* 0xb1 CPIR */ StdInstr(Read3(hl_, temp8_), InternalOperation(10), {MicroOp::CPIR}, InternalOperation(10)),
/* 0xb2 INIR */ Instr(6, Input(bc_, temp8_), Write3(hl_, temp8_), {MicroOp::INIR}, InternalOperation(10)),
/* 0xb3 OTIR */ Instr(6, Read3(hl_, temp8_), {MicroOp::OUTI}, Output(bc_, temp8_), {MicroOp::OUT_R}, InternalOperation(10)),
/* 0xb0 LDIR */ Sequence(Read(hl_, temp8_), Write(de_, temp8_), InternalOperation(4), {MicroOp::LDIR}, InternalOperation(10)),
/* 0xb1 CPIR */ Sequence(Read(hl_, temp8_), InternalOperation(10), {MicroOp::CPIR}, InternalOperation(10)),
/* 0xb2 INIR */ Sequence(InternalOperation(2), Input(bc_, temp8_), Write(hl_, temp8_), {MicroOp::INIR}, InternalOperation(10)),
/* 0xb3 OTIR */ Sequence(InternalOperation(2), Read(hl_, temp8_), {MicroOp::OUTI}, Output(bc_, temp8_), {MicroOp::OUT_R}, InternalOperation(10)),
NOP, NOP, NOP, NOP,
/* 0xb8 LDDR */ StdInstr(Read3(hl_, temp8_), Write5(de_, temp8_), {MicroOp::LDDR}, InternalOperation(10)),
/* 0xb9 CPDR */ StdInstr(Read3(hl_, temp8_), InternalOperation(10), {MicroOp::CPDR}, InternalOperation(10)),
/* 0xba INDR */ Instr(6, Input(bc_, temp8_), Write3(hl_, temp8_), {MicroOp::INDR}, InternalOperation(10)),
/* 0xbb OTDR */ Instr(6, Read3(hl_, temp8_), {MicroOp::OUTD}, Output(bc_, temp8_), {MicroOp::OUT_R}, InternalOperation(10)),
/* 0xb8 LDDR */ Sequence(Read(hl_, temp8_), Write(de_, temp8_), InternalOperation(4), {MicroOp::LDDR}, InternalOperation(10)),
/* 0xb9 CPDR */ Sequence(Read(hl_, temp8_), InternalOperation(10), {MicroOp::CPDR}, InternalOperation(10)),
/* 0xba INDR */ Sequence(InternalOperation(2), Input(bc_, temp8_), Write(hl_, temp8_), {MicroOp::INDR}, InternalOperation(10)),
/* 0xbb OTDR */ Sequence(InternalOperation(2), Read(hl_, temp8_), {MicroOp::OUTD}, Output(bc_, temp8_), {MicroOp::OUT_R}, InternalOperation(10)),
NOP, NOP, NOP, NOP,
NOP_ROW(), /* 0xc0 */
NOP_ROW(), /* 0xd0 */
@@ -346,77 +322,77 @@ void ProcessorStorage::assemble_cb_page(InstructionPage &target, RegisterPair16
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}), \
StdInstr(ReadInc(pc_, r))
Sequence({MicroOp::Increment8, &r}), \
Sequence({MicroOp::Decrement8, &r}), \
Sequence(ReadInc(pc_, r))
#define INC_INC_DEC_LD(rf, r) \
Instr(8, {MicroOp::Increment16, &rf.full}), INC_DEC_LD(r)
Sequence(InternalOperation(4), {MicroOp::Increment16, &rf.full}), INC_DEC_LD(r)
#define DEC_INC_DEC_LD(rf, r) \
Instr(8, {MicroOp::Decrement16, &rf.full}), INC_DEC_LD(r)
Sequence(InternalOperation(4), {MicroOp::Decrement16, &rf.full}), INC_DEC_LD(r)
InstructionTable base_program_table = {
/* 0x00 NOP */ NOP, /* 0x01 LD BC, nn */ StdInstr(Read16Inc(pc_, bc_)),
/* 0x02 LD (BC), A */ StdInstr({MicroOp::SetAddrAMemptr, &bc_.full}, Write3(bc_, a_)),
/* 0x00 NOP */ NOP, /* 0x01 LD BC, nn */ Sequence(Read16Inc(pc_, bc_)),
/* 0x02 LD (BC), A */ Sequence({MicroOp::SetAddrAMemptr, &bc_.full}, Write(bc_, a_)),
/* 0x03 INC BC; 0x04 INC B; 0x05 DEC B; 0x06 LD B, n */
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_)),
/* 0x07 RLCA */ Sequence({MicroOp::RLCA}),
/* 0x08 EX AF, AF' */ Sequence({MicroOp::ExAFAFDash}), /* 0x09 ADD HL, BC */ ADD16(index, bc_),
/* 0x0a LD A, (BC) */ Sequence({MicroOp::Move16, &bc_.full, &memptr_.full}, Read(memptr_, a_), Inc16(memptr_)),
/* 0x0b DEC BC; 0x0c INC C; 0x0d DEC C; 0x0e LD C, n */
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}),
/* 0x11 LD DE, nn */ StdInstr(Read16Inc(pc_, de_)),
/* 0x12 LD (DE), A */ StdInstr({MicroOp::SetAddrAMemptr, &de_.full}, Write3(de_, a_)),
/* 0x0f RRCA */ Sequence({MicroOp::RRCA}),
/* 0x10 DJNZ */ Sequence(InternalOperation(2), ReadInc(pc_, temp8_), {MicroOp::DJNZ}, InternalOperation(10), {MicroOp::CalculateIndexAddress, &pc_.full}, {MicroOp::Move16, &memptr_.full, &pc_.full}),
/* 0x11 LD DE, nn */ Sequence(Read16Inc(pc_, de_)),
/* 0x12 LD (DE), A */ Sequence({MicroOp::SetAddrAMemptr, &de_.full}, Write(de_, a_)),
/* 0x13 INC DE; 0x14 INC D; 0x15 DEC D; 0x16 LD D, n */
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}),
/* 0x17 RLA */ Sequence({MicroOp::RLA}),
/* 0x18 JR */ Sequence(ReadInc(pc_, temp8_), InternalOperation(10), {MicroOp::CalculateIndexAddress, &pc_.full}, {MicroOp::Move16, &memptr_.full, &pc_.full}),
/* 0x19 ADD HL, DE */ ADD16(index, de_),
/* 0x1a LD A, (DE) */ StdInstr({MicroOp::Move16, &de_.full, &memptr_.full}, Read3(memptr_, a_), Inc16(memptr_)),
/* 0x1a LD A, (DE) */ Sequence({MicroOp::Move16, &de_.full, &memptr_.full}, Read(memptr_, a_), Inc16(memptr_)),
/* 0x1b DEC DE; 0x1c INC E; 0x1d DEC E; 0x1e LD E, n */
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)),
/* 0x1f RRA */ Sequence({MicroOp::RRA}),
/* 0x20 JR NZ */ JR(TestNZ), /* 0x21 LD HL, nn */ Sequence(Read16Inc(pc_, index)),
/* 0x22 LD (nn), HL */ Sequence(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.halves.high),
/* 0x27 DAA */ StdInstr({MicroOp::DAA}),
/* 0x27 DAA */ Sequence({MicroOp::DAA}),
/* 0x28 JR Z */ JR(TestZ), /* 0x29 ADD HL, HL */ ADD16(index, index),
/* 0x2a LD HL, (nn) */ StdInstr(Read16Inc(pc_, memptr_), Read16(memptr_, index)),
/* 0x2a LD HL, (nn) */ Sequence(Read16Inc(pc_, memptr_), Read16(memptr_, index)),
/* 0x2b DEC HL; 0x2c INC L; 0x2d DEC L; 0x2e LD L, n */
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_)),
/* 0x32 LD (nn), A */ StdInstr(Read16Inc(pc_, temp16_), {MicroOp::SetAddrAMemptr, &temp16_.full}, Write3(temp16_, a_)),
/* 0x33 INC SP */ Instr(8, {MicroOp::Increment16, &sp_.full}),
/* 0x34 INC (HL) */ StdInstr(INDEX(), Read4Pre(INDEX_ADDR(), temp8_), {MicroOp::Increment8, &temp8_}, Write3(INDEX_ADDR(), temp8_)),
/* 0x35 DEC (HL) */ StdInstr(INDEX(), Read4Pre(INDEX_ADDR(), temp8_), {MicroOp::Decrement8, &temp8_}, Write3(INDEX_ADDR(), temp8_)),
/* 0x36 LD (HL), n */ StdInstr(ReadInc(pc_, temp8_), Write3(INDEX_ADDR(), temp8_)),
/* 0x37 SCF */ StdInstr({MicroOp::SCF}),
/* 0x2f CPL */ Sequence({MicroOp::CPL}),
/* 0x30 JR NC */ JR(TestNC), /* 0x31 LD SP, nn */ Sequence(Read16Inc(pc_, sp_)),
/* 0x32 LD (nn), A */ Sequence(Read16Inc(pc_, temp16_), {MicroOp::SetAddrAMemptr, &temp16_.full}, Write(temp16_, a_)),
/* 0x33 INC SP */ Sequence(InternalOperation(4), {MicroOp::Increment16, &sp_.full}),
/* 0x34 INC (HL) */ Sequence(INDEX(), Read(INDEX_ADDR(), temp8_), InternalOperation(2), {MicroOp::Increment8, &temp8_}, Write(INDEX_ADDR(), temp8_)),
/* 0x35 DEC (HL) */ Sequence(INDEX(), Read(INDEX_ADDR(), temp8_), InternalOperation(2), {MicroOp::Decrement8, &temp8_}, Write(INDEX_ADDR(), temp8_)),
/* 0x36 LD (HL), n */ Sequence(ReadInc(pc_, temp8_), Write(INDEX_ADDR(), temp8_)),
/* 0x37 SCF */ Sequence({MicroOp::SCF}),
/* 0x38 JR C */ JR(TestC),
/* 0x39 ADD HL, SP */ ADD16(index, sp_),
/* 0x3a LD A, (nn) */ StdInstr(Read16Inc(pc_, memptr_), Read3(memptr_, a_), Inc16(memptr_)),
/* 0x3b DEC SP */ Instr(8, {MicroOp::Decrement16, &sp_.full}),
/* 0x3a LD A, (nn) */ Sequence(Read16Inc(pc_, memptr_), Read(memptr_, a_), Inc16(memptr_)),
/* 0x3b DEC SP */ Sequence(InternalOperation(4), {MicroOp::Decrement16, &sp_.full}),
/* 0x3c INC A; 0x3d DEC A; 0x3e LD A, n */
INC_DEC_LD(a_),
/* 0x3f CCF */ StdInstr({MicroOp::CCF}),
/* 0x3f CCF */ Sequence({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_.halves.high, bc_.halves.high),
@@ -436,14 +412,14 @@ void ProcessorStorage::assemble_base_page(InstructionPage &target, RegisterPair1
/* 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.halves.low, hl_.halves.low),
/* 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_)),
/* 0x70 LD (HL), B */ Sequence(INDEX(), Write(INDEX_ADDR(), bc_.halves.high)),
/* 0x71 LD (HL), C */ Sequence(INDEX(), Write(INDEX_ADDR(), bc_.halves.low)),
/* 0x72 LD (HL), D */ Sequence(INDEX(), Write(INDEX_ADDR(), de_.halves.high)),
/* 0x73 LD (HL), E */ Sequence(INDEX(), Write(INDEX_ADDR(), de_.halves.low)),
/* 0x74 LD (HL), H */ Sequence(INDEX(), Write(INDEX_ADDR(), hl_.halves.high)), // neither of these stores parts of the index register;
/* 0x75 LD (HL), L */ Sequence(INDEX(), Write(INDEX_ADDR(), hl_.halves.low)), // they always store exactly H and L.
/* 0x76 HALT */ Sequence({MicroOp::HALT}),
/* 0x77 LD (HL), A */ Sequence(INDEX(), Write(INDEX_ADDR(), a_)),
/* 0x78 LD A, B; 0x79 LD A, C; 0x7a LD A, D; 0x7b LD A, E; 0x7c LD A, H; 0x7d LD A, L; 0x7e LD A, (HL); 0x7f LD A, A */
LD_GROUP(a_, a_),
@@ -472,45 +448,45 @@ void ProcessorStorage::assemble_base_page(InstructionPage &target, RegisterPair1
/* 0xb8 CP B; 0xb9 CP C; 0xba CP D; 0xbb CP E; 0xbc CP H; 0xbd CP L; 0xbe CP (HL); 0xbf CP A */
READ_OP_GROUP(CP8),
/* 0xc0 RET NZ */ RET(TestNZ), /* 0xc1 POP BC */ StdInstr(Pop(bc_)),
/* 0xc2 JP NZ */ JP(TestNZ), /* 0xc3 JP nn */ StdInstr(Read16(pc_, memptr_), {MicroOp::Move16, &memptr_.full, &pc_.full}),
/* 0xc4 CALL NZ */ CALL(TestNZ), /* 0xc5 PUSH BC */ Instr(6, Push(bc_)),
/* 0xc6 ADD A, n */ StdInstr(ReadInc(pc_, temp8_), {MicroOp::ADD8, &temp8_}),
/* 0xc0 RET NZ */ RET(TestNZ), /* 0xc1 POP BC */ Sequence(Pop(bc_)),
/* 0xc2 JP NZ */ JP(TestNZ), /* 0xc3 JP nn */ Sequence(Read16(pc_, memptr_), {MicroOp::Move16, &memptr_.full, &pc_.full}),
/* 0xc4 CALL NZ */ CALL(TestNZ), /* 0xc5 PUSH BC */ Sequence(InternalOperation(2), Push(bc_)),
/* 0xc6 ADD A, n */ Sequence(ReadInc(pc_, temp8_), {MicroOp::ADD8, &temp8_}),
/* 0xc7 RST 00h */ RST(),
/* 0xc8 RET Z */ RET(TestZ), /* 0xc9 RET */ StdInstr(Pop(memptr_), {MicroOp::Move16, &memptr_.full, &pc_.full}),
/* 0xca JP Z */ JP(TestZ), /* 0xcb [CB page] */StdInstr(FINDEX(), {MicroOp::SetInstructionPage, &cb_page}),
/* 0xcc CALL Z */ CALL(TestZ), /* 0xcd CALL */ StdInstr(ReadInc(pc_, memptr_.halves.low), Read4Inc(pc_, memptr_.halves.high), Push(pc_), {MicroOp::Move16, &memptr_.full, &pc_.full}),
/* 0xce ADC A, n */ StdInstr(ReadInc(pc_, temp8_), {MicroOp::ADC8, &temp8_}),
/* 0xc8 RET Z */ RET(TestZ), /* 0xc9 RET */ Sequence(Pop(memptr_), {MicroOp::Move16, &memptr_.full, &pc_.full}),
/* 0xca JP Z */ JP(TestZ), /* 0xcb [CB page] */Sequence(FINDEX(), {MicroOp::SetInstructionPage, &cb_page}),
/* 0xcc CALL Z */ CALL(TestZ), /* 0xcd CALL */ Sequence(ReadInc(pc_, memptr_.halves.low), ReadInc(pc_, memptr_.halves.high), InternalOperation(2), Push(pc_), {MicroOp::Move16, &memptr_.full, &pc_.full}),
/* 0xce ADC A, n */ Sequence(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_, memptr_.halves.low), {MicroOp::Move8, &a_, &memptr_.halves.high}, Output(memptr_, a_), Inc8NoFlags(memptr_.halves.low)),
/* 0xd4 CALL NC */ CALL(TestNC), /* 0xd5 PUSH DE */ Instr(6, Push(de_)),
/* 0xd6 SUB n */ StdInstr(ReadInc(pc_, temp8_), {MicroOp::SUB8, &temp8_}),
/* 0xd0 RET NC */ RET(TestNC), /* 0xd1 POP DE */ Sequence(Pop(de_)),
/* 0xd2 JP NC */ JP(TestNC), /* 0xd3 OUT (n), A */Sequence(ReadInc(pc_, memptr_.halves.low), {MicroOp::Move8, &a_, &memptr_.halves.high}, Output(memptr_, a_), Inc8NoFlags(memptr_.halves.low)),
/* 0xd4 CALL NC */ CALL(TestNC), /* 0xd5 PUSH DE */ Sequence(InternalOperation(2), Push(de_)),
/* 0xd6 SUB n */ Sequence(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_, memptr_.halves.low), {MicroOp::Move8, &a_, &memptr_.halves.high}, Input(memptr_, a_), Inc16(memptr_)),
/* 0xdc CALL C */ CALL(TestC), /* 0xdd [DD page] */StdInstr({MicroOp::SetInstructionPage, &dd_page_}),
/* 0xde SBC A, n */ StdInstr(ReadInc(pc_, temp8_), {MicroOp::SBC8, &temp8_}),
/* 0xd8 RET C */ RET(TestC), /* 0xd9 EXX */ Sequence({MicroOp::EXX}),
/* 0xda JP C */ JP(TestC), /* 0xdb IN A, (n) */Sequence(ReadInc(pc_, memptr_.halves.low), {MicroOp::Move8, &a_, &memptr_.halves.high}, Input(memptr_, a_), Inc16(memptr_)),
/* 0xdc CALL C */ CALL(TestC), /* 0xdd [DD page] */Sequence({MicroOp::SetInstructionPage, &dd_page_}),
/* 0xde SBC A, n */ Sequence(ReadInc(pc_, temp8_), {MicroOp::SBC8, &temp8_}),
/* 0xdf RST 18h */ RST(),
/* 0xe0 RET PO */ RET(TestPO), /* 0xe1 POP HL */ StdInstr(Pop(index)),
/* 0xe2 JP PO */ JP(TestPO), /* 0xe3 EX (SP), HL */StdInstr(Pop7(memptr_), Push8(index), {MicroOp::Move16, &memptr_.full, &index.full}),
/* 0xe4 CALL PO */ CALL(TestPO), /* 0xe5 PUSH HL */ Instr(6, Push(index)),
/* 0xe6 AND n */ StdInstr(ReadInc(pc_, temp8_), {MicroOp::And, &temp8_}),
/* 0xe0 RET PO */ RET(TestPO), /* 0xe1 POP HL */ Sequence(Pop(index)),
/* 0xe2 JP PO */ JP(TestPO), /* 0xe3 EX (SP), HL */Sequence(Pop(memptr_), InternalOperation(2), Push(index), InternalOperation(4), {MicroOp::Move16, &memptr_.full, &index.full}),
/* 0xe4 CALL PO */ CALL(TestPO), /* 0xe5 PUSH HL */ Sequence(InternalOperation(2), Push(index)),
/* 0xe6 AND n */ Sequence(ReadInc(pc_, temp8_), {MicroOp::And, &temp8_}),
/* 0xe7 RST 20h */ RST(),
/* 0xe8 RET PE */ RET(TestPE), /* 0xe9 JP (HL) */ StdInstr({MicroOp::Move16, &index.full, &pc_.full}),
/* 0xea JP PE */ JP(TestPE), /* 0xeb EX DE, HL */StdInstr({MicroOp::ExDEHL}),
/* 0xec CALL PE */ CALL(TestPE), /* 0xed [ED page] */StdInstr({MicroOp::SetInstructionPage, &ed_page_}),
/* 0xee XOR n */ StdInstr(ReadInc(pc_, temp8_), {MicroOp::Xor, &temp8_}),
/* 0xe8 RET PE */ RET(TestPE), /* 0xe9 JP (HL) */ Sequence({MicroOp::Move16, &index.full, &pc_.full}),
/* 0xea JP PE */ JP(TestPE), /* 0xeb EX DE, HL */Sequence({MicroOp::ExDEHL}),
/* 0xec CALL PE */ CALL(TestPE), /* 0xed [ED page] */Sequence({MicroOp::SetInstructionPage, &ed_page_}),
/* 0xee XOR n */ Sequence(ReadInc(pc_, temp8_), {MicroOp::Xor, &temp8_}),
/* 0xef RST 28h */ RST(),
/* 0xf0 RET p */ RET(TestP), /* 0xf1 POP AF */ StdInstr(Pop(temp16_), {MicroOp::DisassembleAF}),
/* 0xf2 JP P */ JP(TestP), /* 0xf3 DI */ StdInstr({MicroOp::DI}),
/* 0xf4 CALL P */ CALL(TestP), /* 0xf5 PUSH AF */ Instr(6, {MicroOp::AssembleAF}, Push(temp16_)),
/* 0xf6 OR n */ StdInstr(ReadInc(pc_, temp8_), {MicroOp::Or, &temp8_}),
/* 0xf0 RET p */ RET(TestP), /* 0xf1 POP AF */ Sequence(Pop(temp16_), {MicroOp::DisassembleAF}),
/* 0xf2 JP P */ JP(TestP), /* 0xf3 DI */ Sequence({MicroOp::DI}),
/* 0xf4 CALL P */ CALL(TestP), /* 0xf5 PUSH AF */ Sequence(InternalOperation(2), {MicroOp::AssembleAF}, Push(temp16_)),
/* 0xf6 OR n */ Sequence(ReadInc(pc_, temp8_), {MicroOp::Or, &temp8_}),
/* 0xf7 RST 30h */ RST(),
/* 0xf8 RET M */ RET(TestM), /* 0xf9 LD SP, HL */Instr(8, {MicroOp::Move16, &index.full, &sp_.full}),
/* 0xfa JP M */ JP(TestM), /* 0xfb EI */ StdInstr({MicroOp::EI}),
/* 0xfc CALL M */ CALL(TestM), /* 0xfd [FD page] */StdInstr({MicroOp::SetInstructionPage, &fd_page_}),
/* 0xfe CP n */ StdInstr(ReadInc(pc_, temp8_), {MicroOp::CP8, &temp8_}),
/* 0xf8 RET M */ RET(TestM), /* 0xf9 LD SP, HL */Sequence(InternalOperation(4), {MicroOp::Move16, &index.full, &sp_.full}),
/* 0xfa JP M */ JP(TestM), /* 0xfb EI */ Sequence({MicroOp::EI}),
/* 0xfc CALL M */ CALL(TestM), /* 0xfd [FD page] */Sequence({MicroOp::SetInstructionPage, &fd_page_}),
/* 0xfe CP n */ Sequence(ReadInc(pc_, temp8_), {MicroOp::CP8, &temp8_}),
/* 0xff RST 38h */ RST(),
};
@@ -518,7 +494,7 @@ void ProcessorStorage::assemble_base_page(InstructionPage &target, RegisterPair1
// The indexed version of 0x36 differs substantially from the non-indexed by building index calculation into
// the cycle that fetches the final operand. So patch in a different microprogram if building an indexed table.
InstructionTable copy_table = {
StdInstr(FINDEX(), Read5Inc(pc_, temp8_), Write3(INDEX_ADDR(), temp8_))
Sequence(FINDEX(), ReadInc(pc_, temp8_), InternalOperation(4), Write(INDEX_ADDR(), temp8_))
};
std::memcpy(&base_program_table[0x36], &copy_table[0], sizeof(copy_table[0]));
}
@@ -528,19 +504,27 @@ void ProcessorStorage::assemble_base_page(InstructionPage &target, RegisterPair1
}
void ProcessorStorage::assemble_fetch_decode_execute(InstructionPage &target, int length) {
/// The fetch-decode-execute sequence for a regular four-clock M1 cycle.
const MicroOp normal_fetch_decode_execute[] = {
BusOp(ReadOpcodeStart()),
BusOp(ReadOpcodeWait(true)),
BusOp(ReadOpcodeEnd()),
{ MicroOp::IncrementR },
BusOp(Refresh()),
{ MicroOp::DecodeOperation }
};
/// The concluding fetch-decode-execute of a [dd/fd]cb nn oo sequence, i.e. an (IX+n) or (IY+n) operation.
/// Per the observed 48kb/128kb Spectrum timings, this appears not to include a refresh cycle. So I've also
/// taken a punt on it not incrementing R.
const MicroOp short_fetch_decode_execute[] = {
BusOp(ReadOpcodeStart()),
BusOp(ReadOpcodeWait(false)),
BusOp(ReadOpcodeWait(true)),
BusOp(ReadOpcodeEnd()),
{ MicroOp::DecodeOperation }
BusOp(ReadStart(pc_, operation_)),
BusOp(ReadWait(pc_, operation_)),
BusOp(ReadEnd(pc_, operation_)),
InternalOperation(4),
{ MicroOp::DecodeOperation },
};
copy_program((length == 4) ? normal_fetch_decode_execute : short_fetch_decode_execute, target.fetch_decode_execute);
target.fetch_decode_execute_data = target.fetch_decode_execute.data();
}

View File

@@ -16,8 +16,8 @@ class ProcessorStorage {
struct MicroOp {
enum Type {
BusOperation,
IncrementR,
DecodeOperation,
DecodeOperationNoRChange,
MoveToNextProgram,
Increment8NoFlags,
@@ -121,7 +121,6 @@ class ProcessorStorage {
std::vector<MicroOp> all_operations;
std::vector<MicroOp> fetch_decode_execute;
MicroOp *fetch_decode_execute_data = nullptr;
uint8_t r_step = 1;
bool is_indexed = false;
};
@@ -130,7 +129,7 @@ class ProcessorStorage {
uint8_t a_;
RegisterPair16 bc_, de_, hl_;
RegisterPair16 afDash_, bcDash_, deDash_, hlDash_;
RegisterPair16 af_dash_, bc_dash_, de_dash_, hl_dash_;
RegisterPair16 ix_, iy_, pc_, sp_;
RegisterPair16 ir_, refresh_addr_;
bool iff1_ = false, iff2_ = false;
@@ -149,6 +148,8 @@ class ProcessorStorage {
// that knowledge of what the last opcode did is necessary to get bits 5 & 3
// correct for SCF and CCF.
uint16_t last_address_bus_ = 0; // The value most recently put out on the address bus.
HalfCycles number_of_cycles_;
enum Interrupt: uint8_t {

View File

@@ -19,10 +19,10 @@ State::State(const ProcessorBase &src): State() {
registers.bc = src.bc_.full;
registers.de = src.de_.full;
registers.hl = src.hl_.full;
registers.afDash = src.afDash_.full;
registers.bcDash = src.bcDash_.full;
registers.deDash = src.deDash_.full;
registers.hlDash = src.hlDash_.full;
registers.af_dash = src.af_dash_.full;
registers.bc_dash = src.bc_dash_.full;
registers.de_dash = src.de_dash_.full;
registers.hl_dash = src.hl_dash_.full;
registers.ix = src.ix_.full;
registers.iy = src.iy_.full;
registers.ir = src.ir_.full;
@@ -108,10 +108,10 @@ void State::apply(ProcessorBase &target) {
target.bc_.full = registers.bc;
target.de_.full = registers.de;
target.hl_.full = registers.hl;
target.afDash_.full = registers.afDash;
target.bcDash_.full = registers.bcDash;
target.deDash_.full = registers.deDash;
target.hlDash_.full = registers.hlDash;
target.af_dash_.full = registers.af_dash;
target.bc_dash_.full = registers.bc_dash;
target.de_dash_.full = registers.de_dash;
target.hl_dash_.full = registers.hl_dash;
target.ix_.full = registers.ix;
target.iy_.full = registers.iy;
target.ir_.full = registers.ir;
@@ -179,10 +179,10 @@ State::Registers::Registers() {
DeclareField(bc);
DeclareField(de);
DeclareField(hl);
DeclareField(afDash);
DeclareField(bcDash);
DeclareField(deDash);
DeclareField(hlDash);
DeclareField(af_dash); // TODO: is there any disadvantage to declaring these for reflective
DeclareField(bc_dash); // purposes as AF', BC', etc?
DeclareField(de_dash);
DeclareField(hl_dash);
DeclareField(ix);
DeclareField(iy);
DeclareField(ir);

View File

@@ -31,7 +31,7 @@ struct State: public Reflection::StructImpl<State> {
uint8_t a;
uint8_t flags;
uint16_t bc, de, hl;
uint16_t afDash, bcDash, deDash, hlDash;
uint16_t af_dash, bc_dash, de_dash, hl_dash;
uint16_t ix, iy, ir;
uint16_t program_counter, stack_pointer;
uint16_t memptr;
@@ -60,25 +60,25 @@ struct State: public Reflection::StructImpl<State> {
obviously doesn't.
*/
struct ExecutionState: public Reflection::StructImpl<ExecutionState> {
bool is_halted;
bool is_halted = false;
uint8_t requests;
uint8_t last_requests;
uint8_t temp8;
uint8_t operation;
uint16_t temp16;
unsigned int flag_adjustment_history;
uint16_t pc_increment;
uint16_t refresh_address;
uint8_t requests = 0;
uint8_t last_requests = 0;
uint8_t temp8 = 0;
uint8_t operation = 0;
uint16_t temp16 = 0;
unsigned int flag_adjustment_history = 0;
uint16_t pc_increment = 1;
uint16_t refresh_address = 0;
ReflectableEnum(Phase,
UntakenConditionalCall, Reset, IRQMode0, IRQMode1, IRQMode2,
NMI, FetchDecode, Operation
);
Phase phase;
int half_cycles_into_step;
int steps_into_phase;
Phase phase = Phase::FetchDecode;
int half_cycles_into_step = 0;
int steps_into_phase = 0;
uint16_t instruction_page = 0;
ExecutionState();

View File

@@ -68,29 +68,50 @@ enum Flag: uint8_t {
*/
struct PartialMachineCycle {
enum Operation {
/// The final half cycle of the opcode fetch part of an M1 cycle.
ReadOpcode = 0,
/// The 1.5 cycles of a read cycle.
Read,
/// The 1.5 cycles of a write cycle.
Write,
/// The 1.5 cycles of an input cycle.
Input,
/// The 1.5 cycles of an output cycle.
Output,
/// The 1.5 cycles of an interrupt acknowledgment.
Interrupt,
/// The two-cycle refresh part of an M1 cycle.
Refresh,
/// A period with no changes in bus signalling.
Internal,
/// A bus acknowledgement cycle.
BusAcknowledge,
/// A wait state within an M1 cycle.
ReadOpcodeWait,
/// A wait state within a read cycle.
ReadWait,
/// A wait state within a write cycle.
WriteWait,
/// A wait state within an input cycle.
InputWait,
/// A wait state within an output cycle.
OutputWait,
/// A wait state within an interrupt acknowledge cycle.
InterruptWait,
/// The first 1.5 cycles of an M1 bus cycle, up to the sampling of WAIT.
ReadOpcodeStart,
/// The first 1.5 cycles of a read cycle, up to the sampling of WAIT.
ReadStart,
/// The first 1.5 cycles of a write cycle, up to the sampling of WAIT.
WriteStart,
/// The first 1.5 samples of an input bus cycle, up to the sampling of WAIT.
InputStart,
/// The first 1.5 samples of an output bus cycle, up to the sampling of WAIT.
OutputStart,
/// The first portion of an interrupt acknowledgement — 2.5 or 3.5 cycles, depending on interrupt mode.
InterruptStart,
};
/// The operation being carried out by the Z80. See the various getters below for better classification.
@@ -125,6 +146,230 @@ struct PartialMachineCycle {
return operation >= Operation::ReadOpcodeWait && operation <= Operation::InterruptWait;
}
enum Line {
CLK = 1 << 0,
MREQ = 1 << 1,
IOREQ = 1 << 2,
RD = 1 << 3,
WR = 1 << 4,
RFSH = 1 << 5,
M1 = 1 << 6,
BUSACK = 1 << 7,
};
/// @returns A C-style array of the bus state at the beginning of each half cycle in this
/// partial machine cycle. Each element is a combination of bit masks from the Line enum;
/// bit set means line active, bit clear means line inactive. For the CLK line set means high.
///
/// @discussion This discrete sampling is prone to aliasing errors. Beware.
const uint8_t *bus_state() const {
switch(operation) {
//
// M1 cycle
//
case Operation::ReadOpcodeStart: {
static constexpr uint8_t states[] = {
Line::CLK | Line::M1,
Line::M1 | Line::MREQ | Line::RD,
Line::CLK | Line::M1 | Line::MREQ | Line::RD,
};
return states;
}
case Operation::ReadOpcode:
case Operation::ReadOpcodeWait: {
static constexpr uint8_t states[] = {
Line::M1 | Line::MREQ | Line::RD,
Line::CLK | Line::M1 | Line::MREQ | Line::RD,
};
return states;
}
case Operation::Refresh: {
static constexpr uint8_t states[] = {
Line::CLK | Line::RFSH | Line::MREQ,
Line::RFSH,
Line::CLK | Line::RFSH | Line::MREQ,
Line::RFSH | Line::MREQ,
Line::CLK | Line::RFSH,
Line::RFSH,
Line::CLK | Line::RFSH,
Line::RFSH,
};
return states;
}
//
// Read cycle.
//
case Operation::ReadStart: {
static constexpr uint8_t states[] = {
Line::CLK,
Line::RD | Line::MREQ,
Line::CLK | Line::RD | Line::MREQ,
};
return states;
}
case Operation::ReadWait: {
static constexpr uint8_t states[] = {
Line::MREQ | Line::RD,
Line::CLK | Line::MREQ | Line::RD,
Line::MREQ | Line::RD,
Line::CLK | Line::MREQ | Line::RD,
Line::MREQ | Line::RD,
Line::CLK | Line::MREQ | Line::RD,
};
return states;
}
case Operation::Read: {
static constexpr uint8_t states[] = {
Line::MREQ | Line::RD,
Line::CLK | Line::MREQ | Line::RD,
0,
};
return states;
}
//
// Write cycle.
//
case Operation::WriteStart: {
static constexpr uint8_t states[] = {
Line::CLK,
Line::MREQ,
Line::CLK | Line::MREQ,
};
return states;
}
case Operation::WriteWait: {
static constexpr uint8_t states[] = {
Line::MREQ,
Line::CLK | Line::MREQ,
Line::MREQ,
Line::CLK | Line::MREQ,
Line::MREQ,
Line::CLK | Line::MREQ,
};
return states;
}
case Operation::Write: {
static constexpr uint8_t states[] = {
Line::MREQ | Line::WR,
Line::CLK | Line::MREQ | Line::WR,
0,
};
return states;
}
//
// Input cycle.
//
case Operation::InputStart: {
static constexpr uint8_t states[] = {
Line::CLK,
0,
Line::CLK | Line::IOREQ | Line::RD,
};
return states;
}
case Operation::InputWait: {
static constexpr uint8_t states[] = {
Line::IOREQ | Line::RD,
Line::CLK | Line::IOREQ | Line::RD,
};
return states;
}
case Operation::Input: {
static constexpr uint8_t states[] = {
Line::IOREQ | Line::RD,
Line::CLK | Line::IOREQ | Line::RD,
0,
};
return states;
}
//
// Output cycle.
//
case Operation::OutputStart: {
static constexpr uint8_t states[] = {
Line::CLK,
0,
Line::CLK | Line::IOREQ | Line::WR,
};
return states;
}
case Operation::OutputWait: {
static constexpr uint8_t states[] = {
Line::IOREQ | Line::WR,
Line::CLK | Line::IOREQ | Line::WR,
};
return states;
}
case Operation::Output: {
static constexpr uint8_t states[] = {
Line::IOREQ | Line::WR,
Line::CLK | Line::IOREQ | Line::WR,
0,
};
return states;
}
//
// TODO: Interrupt acknowledge.
//
//
// Bus acknowldge.
//
case Operation::BusAcknowledge: {
static constexpr uint8_t states[] = {
Line::CLK | Line::BUSACK,
Line::BUSACK,
};
return states;
}
//
// Internal.
//
case Operation::Internal: {
static constexpr uint8_t states[] = {
Line::CLK, 0,
Line::CLK, 0,
Line::CLK, 0,
Line::CLK, 0,
Line::CLK, 0,
};
return states;
}
default: break;
}
return nullptr;
}
PartialMachineCycle(const PartialMachineCycle &rhs) noexcept;
PartialMachineCycle(Operation operation, HalfCycles length, uint16_t *address, uint8_t *value, bool was_requested) noexcept;
PartialMachineCycle() noexcept;

View File

@@ -22,7 +22,7 @@ It currently contains emulations of the:
* Oric 1/Atmos;
* Sega Master System;
* Sinclair ZX80/81; and
* Sinclair ZX Spectrum +2a/+3.
* Sinclair ZX Spectrum.
## Single-step Loading

Binary file not shown.

BIN
ROMImages/ZXSpectrum/48.rom Normal file

Binary file not shown.

Binary file not shown.

View File

@@ -9,3 +9,6 @@ material but retain that copyright"."
With that in mind, Amstrad have kindly given their permission for the redistribution of their copyrighted material but retain that copyright. Material expected here, copyright Amstrad:
plus3.rom — the +2a/+3 ROM file, 64kb in size.
plus2.rom the +2 ROM file, 32kb in size.
128.rom the 128kb ROM file, 32kb in size.
48.rom the 16/48kb ROM file, 16kb in size.

View File

@@ -12,6 +12,7 @@
#include <cassert>
#include <cstdarg>
#include <cstring>
#include <cstddef>
#include <string>
#include <typeindex>
#include <typeinfo>
@@ -318,12 +319,12 @@ template <typename Owner> class StructImpl: public Struct {
if(iterator != contents_.end()) {
return iterator->first;
} else {
return "";
return std::string();
}
}
private:
template <typename Type> bool declare_reflectable(Type *t, const std::string &name) {
template <typename Type> bool declare_reflectable([[maybe_unused]] Type *t, const std::string &name) {
if constexpr (std::is_base_of<Reflection::Struct, Type>::value) {
Reflection::Struct *const str = static_cast<Reflection::Struct *>(t);
declare_emplace(str, name);

View File

@@ -43,7 +43,7 @@ class FileHolder final {
Rewrite opens the file for rewriting; none of the original content is preserved; whatever
the caller outputs will replace the existing file.
@raises ErrorCantOpen if the file cannot be opened.
@throws ErrorCantOpen if the file cannot be opened.
*/
FileHolder(const std::string &file_name, FileMode ideal_mode = FileMode::ReadWrite);

79
Storage/State/SNA.cpp Normal file
View File

@@ -0,0 +1,79 @@
//
// SNA.cpp
// Clock Signal
//
// Created by Thomas Harte on 24/04/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "SNA.hpp"
#include "../FileHolder.hpp"
#include "../../Analyser/Static/ZXSpectrum/Target.hpp"
#include "../../Machines/Sinclair/ZXSpectrum/State.hpp"
using namespace Storage::State;
std::unique_ptr<Analyser::Static::Target> SNA::load(const std::string &file_name) {
// Make sure the file is accessible and appropriately sized.
FileHolder file(file_name);
if(file.stats().st_size != 48*1024 + 0x1b) {
return nullptr;
}
// SNAs are always for 48kb machines.
using Target = Analyser::Static::ZXSpectrum::Target;
auto result = std::make_unique<Target>();
result->model = Target::Model::FortyEightK;
// Prepare to populate ZX Spectrum state.
auto *const state = new Sinclair::ZXSpectrum::State();
result->state = std::unique_ptr<Reflection::Struct>(state);
// Comments below: [offset] [contents]
// 00 I
const uint8_t i = file.get8();
// 01 HL'; 03 DE'; 05 BC'; 07 AF'
state->z80.registers.hl_dash = file.get16le();
state->z80.registers.de_dash = file.get16le();
state->z80.registers.bc_dash = file.get16le();
state->z80.registers.af_dash = file.get16le();
// 09 HL; 0B DE; 0D BC; 0F IY; 11 IX
state->z80.registers.hl = file.get16le();
state->z80.registers.de = file.get16le();
state->z80.registers.bc = file.get16le();
state->z80.registers.iy = file.get16le();
state->z80.registers.ix = file.get16le();
// 13 IFF2 (in bit 2)
const uint8_t iff = file.get8();
state->z80.registers.iff1 = state->z80.registers.iff2 = iff & 4;
// 14 R
const uint8_t r = file.get8();
state->z80.registers.ir = uint16_t((i << 8) | r);
// 15 AF; 17 SP; 19 interrupt mode
state->z80.registers.flags = file.get8();
state->z80.registers.a = file.get8();
state->z80.registers.stack_pointer = file.get16le();
state->z80.registers.interrupt_mode = file.get8();
// 1A border colour
state->video.border_colour = file.get8();
// 1B 48kb RAM contents
state->ram = file.read(48*1024);
// To establish program counter, point it to a RET that
// I know is in the 16/48kb ROM. This avoids having to
// try to do a pop here, given that the true program counter
// might currently be in the ROM.
state->z80.registers.program_counter = 0x1d83;
return result;
}

24
Storage/State/SNA.hpp Normal file
View File

@@ -0,0 +1,24 @@
//
// SNA.hpp
// Clock Signal
//
// Created by Thomas Harte on 24/04/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Storage_State_SNA_hpp
#define Storage_State_SNA_hpp
#include "../../Analyser/Static/StaticAnalyser.hpp"
namespace Storage {
namespace State {
struct SNA {
static std::unique_ptr<Analyser::Static::Target> load(const std::string &file_name);
};
}
}
#endif /* Storage_State_SNA_hpp */

196
Storage/State/SZX.cpp Normal file
View File

@@ -0,0 +1,196 @@
//
// SZX.cpp
// Clock Signal
//
// Created by Thomas Harte on 26/04/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "SZX.hpp"
#include "../FileHolder.hpp"
#include "../../Analyser/Static/ZXSpectrum/Target.hpp"
#include "../../Machines/Sinclair/ZXSpectrum/State.hpp"
#define LOG_PREFIX "[SZX] "
#include "../../Outputs/Log.hpp"
#include <zlib.h>
using namespace Storage::State;
std::unique_ptr<Analyser::Static::Target> SZX::load(const std::string &file_name) {
FileHolder file(file_name);
// Construct a target with a Spectrum state.
using Target = Analyser::Static::ZXSpectrum::Target;
auto result = std::make_unique<Target>();
auto *const state = new Sinclair::ZXSpectrum::State();
result->state = std::unique_ptr<Reflection::Struct>(state);
// Check signature and major version number.
if(!file.check_signature("ZXST")) {
return nullptr;
}
const uint8_t major_version = file.get8();
[[maybe_unused]] const uint8_t minor_version = file.get8();
if(major_version > 1) {
return nullptr;
}
// Check for a supported machine type.
const uint8_t machine_type = file.get8();
switch(machine_type) {
default: return nullptr;
case 0: result->model = Target::Model::SixteenK; break;
case 1: result->model = Target::Model::FortyEightK; break;
case 2: result->model = Target::Model::OneTwoEightK; break;
case 3: result->model = Target::Model::Plus2; break;
case 4: result->model = Target::Model::Plus2a; break;
case 5: result->model = Target::Model::Plus3; break;
}
// Consequential upon selected machine...
switch(result->model) {
case Target::Model::SixteenK: state->ram.resize(16 * 1024); break;
case Target::Model::FortyEightK: state->ram.resize(48 * 1024); break;
default:
state->ram.resize(128 * 1024);
break;
}
const uint8_t file_flags = file.get8();
[[maybe_unused]] const bool uses_late_timings = file_flags & 1;
// Now parse all included blocks.
while(true) {
const uint32_t blockID = file.get32le();
const uint32_t size = file.get32le();
if(file.eof()) break;
const auto location = file.tell();
#define BLOCK(str) str[0] | (str[1] << 8) | (str[2] << 16) | (str[3] << 24)
switch(blockID) {
default:
LOG("Unhandled block " << char(blockID) << char(blockID >> 8) << char(blockID >> 16) << char(blockID >> 24));
break;
// ZXSTZ80REGS
case BLOCK("Z80R"): {
state->z80.registers.flags = file.get8();
state->z80.registers.a = file.get8();
state->z80.registers.bc = file.get16le();
state->z80.registers.de = file.get16le();
state->z80.registers.hl = file.get16le();
state->z80.registers.af_dash = file.get16le();
state->z80.registers.bc_dash = file.get16le();
state->z80.registers.de_dash = file.get16le();
state->z80.registers.hl_dash = file.get16le();
state->z80.registers.ix = file.get16le();
state->z80.registers.iy = file.get16le();
state->z80.registers.stack_pointer = file.get16le();
state->z80.registers.program_counter = file.get16le();
const uint8_t i = file.get8();
const uint8_t r = file.get8();
state->z80.registers.ir = uint16_t((i << 8) | r);
state->z80.registers.iff1 = file.get8();
state->z80.registers.iff2 = file.get8();
state->z80.registers.interrupt_mode = file.get8();
state->video.half_cycles_since_interrupt = int(file.get32le()) * 2;
// SZX includes a count of remaining cycles that interrupt should be asserted for
// because it supports hardware that might cause an interrupt other than the display.
// This emulator doesn't, so this field can be ignored.
[[maybe_unused]] uint8_t remaining_interrupt_cycles = file.get8();
const uint8_t flags = file.get8();
state->z80.execution_state.is_halted = flags & 2;
// TODO: bit 0 indicates that the last instruction was an EI, or an invalid
// DD or FD. I assume I'm supposed to use that to conclude an interrupt
// verdict but I'm unclear what the effect of an invalid DD or FD is so
// have not yet implemented this.
state->z80.registers.memptr = file.get16le();
} break;
// ZXSTAYBLOCK
case BLOCK("AY\0\0"): {
// This applies to 48kb machines with AY boxes only. This emulator
// doesn't currently support those.
[[maybe_unused]] const uint8_t interface_type = file.get8();
state->ay.selected_register = file.get8();
file.read(state->ay.registers, 16);
} break;
// ZXSTRAMPAGE
case BLOCK("RAMP"): {
const uint16_t flags = file.get16le();
const uint8_t page = file.get8();
std::vector<uint8_t> contents;
if(flags & 1) {
// ZLib compression is applied.
contents.resize(16 * 1024);
const std::vector<uint8_t> source = file.read(size - 3);
uLongf output_length;
uncompress(contents.data(), &output_length, source.data(), source.size());
assert(output_length == contents.size());
} else {
// Data is raw.
contents = file.read(16 * 1024);
}
switch(result->model) {
case Target::Model::SixteenK:
case Target::Model::FortyEightK: {
size_t address = 0;
switch(page) {
default: break;
case 5: address = 0x4000; break;
case 2: address = 0x8000; break;
case 0: address = 0xc000; break;
}
if(address > 0 && (address - 0x4000) <= state->ram.size()) {
memcpy(&state->ram[address - 0x4000], contents.data(), 0x4000);
}
} break;
default:
if(page < 8) {
memcpy(&state->ram[page * 0x4000], contents.data(), 0x4000);
}
break;
}
} break;
// ZXSTSPECREGS
case BLOCK("SPCR"): {
state->video.border_colour = file.get8();
state->last_7ffd = file.get8();
state->last_1ffd = file.get8();
// TODO: use last write to FE, at least.
} break;
}
#undef BLOCK
// Advance to the next block.
file.seek(location + size, SEEK_SET);
}
return result;
}

24
Storage/State/SZX.hpp Normal file
View File

@@ -0,0 +1,24 @@
//
// SZX.hpp
// Clock Signal
//
// Created by Thomas Harte on 26/04/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Storage_State_SZX_hpp
#define Storage_State_SZX_hpp
#include "../../Analyser/Static/StaticAnalyser.hpp"
namespace Storage {
namespace State {
struct SZX {
static std::unique_ptr<Analyser::Static::Target> load(const std::string &file_name);
};
}
}
#endif /* Storage_State_SZX_hpp */

209
Storage/State/Z80.cpp Normal file
View File

@@ -0,0 +1,209 @@
//
// Z80.cpp
// Clock Signal
//
// Created by Thomas Harte on 25/04/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "Z80.hpp"
#include "../FileHolder.hpp"
#include "../../Analyser/Static/ZXSpectrum/Target.hpp"
#include "../../Machines/Sinclair/ZXSpectrum/State.hpp"
using namespace Storage::State;
namespace {
std::vector<uint8_t> read_memory(Storage::FileHolder &file, size_t size, bool is_compressed) {
if(!is_compressed) {
return file.read(size);
}
std::vector<uint8_t> result(size);
size_t cursor = 0;
while(cursor != size) {
const uint8_t next = file.get8();
// If the next byte definitely doesn't, or can't,
// start an ED ED sequence then just take it.
if(next != 0xed || cursor == size - 1) {
result[cursor] = next;
++cursor;
continue;
}
// Grab the next byte. If it's not ED then write
// both and continue.
const uint8_t after = file.get8();
if(after != 0xed) {
result[cursor] = next;
result[cursor+1] = after;
cursor += 2;
continue;
}
// An ED ED has begun, so grab the RLE sequence.
const uint8_t count = file.get8();
const uint8_t value = file.get8();
memset(&result[cursor], value, count);
cursor += count;
}
return result;
}
}
std::unique_ptr<Analyser::Static::Target> Z80::load(const std::string &file_name) {
FileHolder file(file_name);
// Construct a target with a Spectrum state.
using Target = Analyser::Static::ZXSpectrum::Target;
auto result = std::make_unique<Target>();
auto *const state = new Sinclair::ZXSpectrum::State();
result->state = std::unique_ptr<Reflection::Struct>(state);
// Read version 1 header.
state->z80.registers.a = file.get8();
state->z80.registers.flags = file.get8();
state->z80.registers.bc = file.get16le();
state->z80.registers.hl = file.get16le();
state->z80.registers.program_counter = file.get16le();
state->z80.registers.stack_pointer = file.get16le();
state->z80.registers.ir = file.get16be(); // Stored I then R.
// Bit 7 of R is stored separately; likely this relates to an
// optimisation in the Z80 emulator that for some reason was
// exported into its file format.
const uint8_t raw_misc = file.get8();
const uint8_t misc = (raw_misc == 0xff) ? 1 : raw_misc;
state->z80.registers.ir = uint16_t((state->z80.registers.ir & ~0x80) | ((misc&1) << 7));
state->z80.registers.de = file.get16le();
state->z80.registers.bc_dash = file.get16le();
state->z80.registers.de_dash = file.get16le();
state->z80.registers.hl_dash = file.get16le();
state->z80.registers.af_dash = file.get16be(); // Stored A' then F'.
state->z80.registers.iy = file.get16le();
state->z80.registers.ix = file.get16le();
state->z80.registers.iff1 = bool(file.get8());
state->z80.registers.iff2 = bool(file.get8());
// Ignored from the next byte:
//
// bit 2 = 1 => issue 2 emulation
// bit 3 = 1 => double interrupt frequency (?)
// bit 45 => video synchronisation (to do with emulation hackery?)
// bit 67 => joystick type
state->z80.registers.interrupt_mode = file.get8() & 3;
// If the program counter is non-0 then this is a version 1 snapshot,
// which means it's definitely a 48k image.
if(state->z80.registers.program_counter) {
result->model = Target::Model::FortyEightK;
state->ram = read_memory(file, 48*1024, misc & 0x20);
return result;
}
// This was a version 1 or 2 snapshot, so keep going...
const uint16_t bonus_header_size = file.get16le();
if(bonus_header_size != 23 && bonus_header_size != 54 && bonus_header_size != 55) {
return nullptr;
}
state->z80.registers.program_counter = file.get16le();
const uint8_t model = file.get8();
switch(model) {
default: return nullptr;
case 0: result->model = Target::Model::FortyEightK; break;
case 3: result->model = Target::Model::OneTwoEightK; break;
case 7:
case 8: result->model = Target::Model::Plus3; break;
case 12: result->model = Target::Model::Plus2; break;
case 13: result->model = Target::Model::Plus2a; break;
}
state->last_7ffd = file.get8();
file.seek(1, SEEK_CUR);
if(file.get8() & 0x80) {
// The 'hardware modify' bit, which inexplicably does this:
switch(result->model) {
default: break;
case Target::Model::FortyEightK: result->model = Target::Model::SixteenK; break;
case Target::Model::OneTwoEightK: result->model = Target::Model::Plus2; break;
case Target::Model::Plus3: result->model = Target::Model::Plus2a; break;
}
}
state->ay.selected_register = file.get8();
file.read(state->ay.registers, 16);
if(bonus_header_size != 23) {
// More Z80, the emulator, lack of encapsulation to deal with here.
const uint16_t low_t_state = file.get16le();
const uint16_t high_t_state = file.get8();
switch(result->model) {
case Target::Model::SixteenK:
case Target::Model::FortyEightK:
state->video.half_cycles_since_interrupt = ((17471 - low_t_state) + (high_t_state * 17472)) * 2;
break;
default:
state->video.half_cycles_since_interrupt = ((17726 - low_t_state) + (high_t_state * 17727)) * 2;
break;
}
// Skip: Spectator flag, MGT, Multiface and other ROM flags.
file.seek(5, SEEK_CUR);
// Skip: highly Z80-the-emulator-specific stuff about user-defined joystick.
file.seek(20, SEEK_CUR);
// Skip: Disciple/Plus D stuff.
file.seek(3, SEEK_CUR);
if(bonus_header_size == 55) {
state->last_1ffd = file.get8();
}
}
// Grab RAM.
switch(result->model) {
case Target::Model::SixteenK: state->ram.resize(16 * 1024); break;
case Target::Model::FortyEightK: state->ram.resize(48 * 1024); break;
default: state->ram.resize(128 * 1024); break;
}
while(true) {
const uint16_t block_size = file.get16le();
const uint8_t page = file.get8();
const auto location = file.tell();
if(file.eof()) break;
const auto data = read_memory(file, 16384, block_size != 0xffff);
if(result->model == Target::Model::SixteenK || result->model == Target::Model::FortyEightK) {
switch(page) {
default: break;
case 4: memcpy(&state->ram[0x4000], data.data(), 16384); break;
case 5: memcpy(&state->ram[0x8000], data.data(), 16384); break;
case 8: memcpy(&state->ram[0x0000], data.data(), 16384); break;
}
} else {
if(page >= 3 && page <= 10) {
memcpy(&state->ram[(page - 3) * 0x4000], data.data(), 16384);
}
}
assert(location + block_size == file.tell());
file.seek(location + block_size, SEEK_SET);
}
return result;
}

24
Storage/State/Z80.hpp Normal file
View File

@@ -0,0 +1,24 @@
//
// Z80.hpp
// Clock Signal
//
// Created by Thomas Harte on 25/04/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Storage_State_Z80_hpp
#define Storage_State_Z80_hpp
#include "../../Analyser/Static/StaticAnalyser.hpp"
namespace Storage {
namespace State {
struct Z80 {
static std::unique_ptr<Analyser::Static::Target> load(const std::string &file_name);
};
}
}
#endif /* Storage_State_Z80_hpp */

View File

@@ -15,9 +15,19 @@ using namespace Storage::Tape;
OricTAP::OricTAP(const std::string &file_name) :
file_(file_name)
{
// check the file signature
if(!file_.check_signature("\x16\x16\x16\x24", 4))
throw ErrorNotOricTAP;
// Check for a sequence of at least three 0x16s followed by a 0x24.
while(true) {
const uint8_t next = file_.get8();
if(next != 0x16 && next != 0x24) {
throw ErrorNotOricTAP;
}
if(next == 0x24) {
if(file_.tell() < 4) {
throw ErrorNotOricTAP;
}
break;
}
}
// then rewind and start again
virtual_reset();

View File

@@ -40,8 +40,8 @@ enum Type: IntType {
Acorn = AcornAtom | AcornElectron | BBCMaster | BBCModelA | BBCModelB,
ZX8081 = ZX80 | ZX81,
AllCartridge = Atari2600 | AcornElectron | Coleco | MSX,
AllDisk = Acorn | AmstradCPC | Commodore | Oric | MSX, // TODO: | AtariST
AllTape = Acorn | AmstradCPC | Commodore | Oric | ZX80 | ZX81 | MSX | ZXSpectrum,
AllDisk = Acorn | AmstradCPC | Commodore | Oric | MSX | ZXSpectrum | Macintosh | AtariST | DiskII,
AllTape = Acorn | AmstradCPC | Commodore | Oric | ZX8081 | MSX | ZXSpectrum,
};
class TypeDistinguisher {