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

Compare commits

..

1151 Commits

Author SHA1 Message Date
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
8a6985c2e8 Merge pull request #909 from TomHarte/BackToFive
Tweaks video timing, again.
2021-04-06 21:16:50 -04:00
Thomas Harte
60e8273de2 Tweaks video timing, again. 2021-04-06 21:04:54 -04:00
Thomas Harte
aa8ce5c1ac Merge pull request #908 from TomHarte/ZXSpectrumInterrupts
Better indicate ZX Spectrum interrupt timing.
2021-04-06 13:49:25 -04:00
Thomas Harte
dd28246f9f Better indicate interrupt timing. 2021-04-06 12:06:13 -04:00
Thomas Harte
dc25a60b9b Merge pull request #907 from TomHarte/TMSSequencePoints
Makes the TMS a sequence-point-generating JustInTimeActor.
2021-04-06 12:03:22 -04:00
Thomas Harte
094d623485 Updates unit tests. 2021-04-05 21:33:04 -04:00
Thomas Harte
1266bbb224 Makes the TMS a sequence-point-generating JustInTimeActor. 2021-04-05 21:02:37 -04:00
Thomas Harte
bd1ea5740a Merge pull request #906 from TomHarte/LoadingImprovements
Attempts to improve ZX fast-loading compatibility
2021-04-05 19:20:46 -04:00
Thomas Harte
3e04b51122 Walks back pretty names. Probably a bad idea. 2021-04-05 17:26:18 -04:00
Thomas Harte
76f2aba51a Makes use of pretty names in descriptions optional. 2021-04-05 17:24:16 -04:00
Thomas Harte
fd88071c0a Remove further detritus. 2021-04-05 17:21:26 -04:00
Thomas Harte
16bfe1a55c Resolves use-after-return memory error. 2021-04-04 22:45:56 -04:00
Thomas Harte
90c3d6a1e8 Attempts a later interception of tape loading. 2021-04-04 22:39:30 -04:00
Thomas Harte
18d6197d6c Makes provision for pretty-printed key names.
i.e. keys that don't fit C++ naming rules.
2021-04-04 22:20:35 -04:00
Thomas Harte
27eddf6dff Merge pull request #905 from TomHarte/JustInTimeOric
Adopts JustInTimeActor in the Oric.
2021-04-04 20:57:52 -04:00
Thomas Harte
57b32d9537 Avoid adding additional threading constraints. 2021-04-04 20:48:15 -04:00
Thomas Harte
837b9499d5 Translates Oric video and Disk II into JustInTimeActors. 2021-04-04 20:43:16 -04:00
Thomas Harte
c2fde2b147 Merge pull request #900 from TomHarte/SpeccyTiming
Further tweaks Spectrum timing.
2021-04-04 20:19:54 -04:00
Thomas Harte
f26bf4b9e4 Splitting here isn't achieving anything. 2021-04-04 19:52:38 -04:00
Thomas Harte
1da51bee6c 14368 and six seem to be the proper numbers, per my comprehension of Patrick Rak. 2021-04-04 19:52:19 -04:00
Thomas Harte
5a66956221 Merge branch 'master' into SpeccyTiming 2021-04-04 19:12:37 -04:00
Thomas Harte
91d973c4a9 Merge pull request #904 from TomHarte/JITElectron
Moves the Electron to JustInTimeActor video.
2021-04-04 19:12:06 -04:00
Thomas Harte
fa79589db8 Minor style improvements. 2021-04-04 18:59:46 -04:00
Thomas Harte
e52649f74d Normalises logging. 2021-04-04 17:39:49 -04:00
Thomas Harte
d77ddaf4fa Switches the Electron to JustInTimeActor video.
Also reorders template parameters; I think that specifying a different time base is likely to be more common than using a divider.
2021-04-04 17:33:49 -04:00
Thomas Harte
9ff392279a Merge pull request #895 from TomHarte/JITSleeper
Works `ClockingHint` logic into `JustInTimeActor`.
2021-04-04 17:11:05 -04:00
Thomas Harte
448d9dc3e1 Correct article. 2021-04-04 16:14:47 -04:00
Thomas Harte
afb4e6d37d Merge branch 'master' into JITSleeper 2021-04-04 15:37:19 -04:00
Thomas Harte
158122fbf4 Determine TargetTimeScale automatically. 2021-04-04 15:37:07 -04:00
Thomas Harte
417ece2386 Adds a couple of TODOs and some further documentation. 2021-04-04 00:25:22 -04:00
Thomas Harte
77b241af4f Eliminates unused RealTimeActor, provides more feedback from +=, gets specific as to nodiscards. 2021-04-03 21:26:43 -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
Thomas Harte
14663bd06b I think 3 is what I'm aiming for here.
But this probably isn't correct for IO cycles.
2021-04-02 07:36:57 -04:00
Thomas Harte
68abd197aa 'Dock' is a common noun here. 2021-04-02 07:11:28 -04:00
Thomas Harte
18fd21eae7 Merge pull request #901 from TomHarte/ReadMeClarity
Clarifies the object in the 'Single-click Loading' readme section
2021-04-01 23:25:01 -04:00
Thomas Harte
3296347370 Hit this point even harder. 2021-04-01 23:22:47 -04:00
Thomas Harte
28c9463e0d Clarify object. 2021-04-01 23:18:20 -04:00
Thomas Harte
044ac949ba Rearrange fields. 2021-04-01 12:44:00 -04:00
Thomas Harte
87317f5673 Improve documentation, pin down read/write times. 2021-04-01 12:38:58 -04:00
Thomas Harte
5e21a49841 Merge pull request #898 from TomHarte/LoadingImprovements
Include AF' in Z80 state.
2021-03-31 23:08:43 -04:00
Thomas Harte
687c05365e Flushes before set_last_contended_area_access. 2021-03-31 22:52:41 -04:00
Thomas Harte
4f80523828 Tweaks contended timing. 2021-03-31 22:51:20 -04:00
Thomas Harte
76299a2add Include AF' in Z80 state. 2021-03-29 22:58:52 -04:00
Thomas Harte
48f794dc2d Merge pull request #897 from TomHarte/LoadingImprovements
Corrects Spectrum TAP `is_at_end`.
2021-03-29 15:27:06 -04:00
Thomas Harte
51b8dcd011 Fixes is_at_end — must be at end of file and have finished final block. 2021-03-28 23:25:29 -04:00
Thomas Harte
acdbd88b9e Merge pull request #896 from TomHarte/FastLoadUponInsert
Ensure CPC and Spectrum update fast-tape flag upon media insertion.
2021-03-28 11:44:01 -04:00
Thomas Harte
00a3a3c724 Merge pull request #894 from TomHarte/AYCleanup
Uses `GI::AY38910::Utility` far and wide.
2021-03-28 11:43:43 -04:00
Thomas Harte
729edeac6c Ensure CPC and Spectrum update fast-tape flag upon media insertion. 2021-03-27 18:08:46 -04:00
Thomas Harte
faaa4961ed Attempts to rely on JustInTimeActor's built-in ClockingHint::Observer. 2021-03-26 23:54:08 -04:00
Thomas Harte
7937cc2d0f Imputes ClockingHint::Observer logic into JustInTimeActor. 2021-03-26 23:44:15 -04:00
Thomas Harte
8a11a5832c Uses GI::AY38910::Utility far and wide. 2021-03-26 23:19:47 -04:00
Thomas Harte
53ba0e67d1 Revert change to screenshot destination.
For a sandboxed app, there's a lot more to it than this.
2021-03-25 22:44:18 -04:00
Thomas Harte
e8825aeada Merge pull request #893 from TomHarte/DesktopScreenshots
Switches to a more compact macOS machine picker
2021-03-25 22:42:56 -04:00
Thomas Harte
e90e30e766 Enables start by double-click. 2021-03-25 17:53:07 -04:00
Thomas Harte
9f6bb325e6 Fixes longstanding issue with initial target for input. 2021-03-25 17:48:48 -04:00
Thomas Harte
6e2c65435a Tweaks cell height slightly further. 2021-03-25 17:44:46 -04:00
Thomas Harte
052ab44f1c Adds a title and adjusts aspect ratio. 2021-03-25 17:37:40 -04:00
Thomas Harte
daa5679241 Don't allow cell editing, lock size. 2021-03-25 16:48:11 -04:00
Thomas Harte
e055668554 With no space constraint, this can be 'ZX Spectrum'. 2021-03-25 16:27:12 -04:00
Thomas Harte
c96829c29e Adds a table view to control tab selection.
This should allow the new machine dialogue to retain a sensible width from here onwards.
2021-03-25 16:25:11 -04:00
Thomas Harte
c88abed2dc Merge branch 'master' into DesktopScreenshots 2021-03-24 21:47:40 -04:00
Thomas Harte
e42b6cb3c8 Merge pull request #892 from TomHarte/FixedFloatingBus
Attempts to implement proper floating bus behaviour.
2021-03-24 21:44:01 -04:00
Thomas Harte
465ecc4a78 Attempts to implement proper floating bus behaviour.
As per http://sky.relative-path.com/zx/floating_bus.html
2021-03-24 20:23:33 -04:00
Thomas Harte
ae4ccdf5e6 Merge branch 'master' into DesktopScreenshots 2021-03-24 18:40:20 -04:00
Thomas Harte
6bdaa54aaf Bumps copyright year. 2021-03-23 17:46:32 -04:00
Thomas Harte
3543a25168 Merge pull request #890 from TomHarte/SpectrumPolish
Adds Spectrum polish
2021-03-23 17:23:15 -04:00
Thomas Harte
03ef81b07c Attempts to reduce initial bounce. 2021-03-23 17:12:00 -04:00
Thomas Harte
0ac11fc39e Add floating bus. 2021-03-23 17:09:42 -04:00
Thomas Harte
3d0503a35e Adds a genuine Spectrum mapping, tweaks timing. 2021-03-23 16:59:43 -04:00
Thomas Harte
ad8cb52f11 Improves const correctness. 2021-03-23 16:59:26 -04:00
Thomas Harte
496a294c71 Adds clocking observers for the tape and FDC. 2021-03-23 16:38:04 -04:00
Thomas Harte
465c74ab86 Adds the Spectrum side of typing.
The character mapper itself needs some Spectrum logic.
2021-03-23 10:44:43 -04:00
Thomas Harte
4e8f82a39c Adds ZX Spectrum activity indicators. 2021-03-23 10:32:22 -04:00
Thomas Harte
584a5ad7fb Maps HFE files to the Spectrum. 2021-03-23 10:30:30 -04:00
Thomas Harte
0ab85cce20 Merge pull request #888 from TomHarte/SpectrumShots
Adds mention of the ZX Spectrum.
2021-03-23 08:01:41 -04:00
Thomas Harte
44e5caf803 Adds mention of the ZX Spectrum. 2021-03-23 08:00:10 -04:00
Thomas Harte
04291e9a86 Merge pull request #883 from TomHarte/ZXSpectrum
Adds the ZX Spectrum +2a and +3 as emulated machines.
2021-03-22 23:28:04 -04:00
Thomas Harte
d0776b58cf Tweaks timing empirically. 2021-03-22 23:20:49 -04:00
Thomas Harte
6da099d7e1 Ensures the enter key is cleared before fast-loading tapes have loaded. 2021-03-22 22:42:10 -04:00
Thomas Harte
60e77785e8 Makes an attempt to provide the necessary hook for floating bus behaviour. 2021-03-22 22:34:28 -04:00
Thomas Harte
19cd6a55d3 Rejigs the way video is counted to orient it around fetch times. 2021-03-22 22:18:38 -04:00
Thomas Harte
08432dd94b Adds automatic media starts. 2021-03-22 20:12:03 -04:00
Thomas Harte
cc3c3663f6 Makes minor style improvements. 2021-03-22 19:55:03 -04:00
Thomas Harte
b76c923ff4 Adds detection of Spectrum-bootable disks. 2021-03-22 19:53:51 -04:00
Thomas Harte
3c1131a84b Attempts to implement the +3. 2021-03-22 19:36:05 -04:00
Thomas Harte
a3cd953415 Fixes Spectrum machine selection. 2021-03-22 19:28:12 -04:00
Thomas Harte
c0abdf1b86 Factors out the CPC's simple FDC adaptor. 2021-03-22 19:12:10 -04:00
Thomas Harte
3ef2715eee Implements the ROM version of fast loading. 2021-03-22 19:04:38 -04:00
Thomas Harte
4a12d7086d Makes another guess at total colour phase. 2021-03-22 17:24:38 -04:00
Thomas Harte
a6b75b8637 Attempts improvements to video fetch timing. Alas, a lot of guess work here. 2021-03-22 15:59:03 -04:00
Thomas Harte
bdb3bce8d6 Corrects semantics on contended-timing calculation. 2021-03-22 15:48:51 -04:00
Thomas Harte
a26716919c Switches to an is-in-video test that allows for video memory being paged twice.
This is trivially possible even in plain 128kb mode.
2021-03-22 15:46:02 -04:00
Thomas Harte
8dbc7649aa Adds note-to-self re: FDC. 2021-03-22 09:15:00 -04:00
Thomas Harte
42a9dc7c2b Minimises video flushing, moves it to the proper time. 2021-03-22 09:02:49 -04:00
Thomas Harte
7965772745 Moves contention delays up to the time of MREQ going active. 2021-03-21 23:04:20 -04:00
Thomas Harte
f37f89a7d3 Merge branch 'master' into ZXSpectrum 2021-03-21 22:44:37 -04:00
Thomas Harte
d987e5a9d7 Merge pull request #887 from TomHarte/ZX80Wait
Ensures no signalling to wait by a ZX80, ever.
2021-03-21 22:44:11 -04:00
Thomas Harte
fcba0cc3d6 Merge pull request #886 from TomHarte/AppleIIgsWarnings
Resolves GCC warnings from dangling Apple IIgs work.
2021-03-21 22:40:56 -04:00
Thomas Harte
c097ed348a Ensures no signalling to wait by a ZX80, ever. 2021-03-21 22:38:50 -04:00
Thomas Harte
0f9ab53ea0 Resolves GCC warnings from dangling Apple IIgs work. 2021-03-21 22:36:18 -04:00
Thomas Harte
21b1dab4a5 Adds the ZX Spectrum to Qt's New... menu. 2021-03-21 22:35:46 -04:00
Thomas Harte
dd7419282d Resolves GCC warnings from dangling Apple IIgs work. 2021-03-21 22:25:14 -04:00
Thomas Harte
7562917740 Adds the Spectrum to the macOS New... menu. 2021-03-21 21:50:50 -04:00
Thomas Harte
3925eee575 Attempts more relaxed decoding of AY accesses. 2021-03-21 21:03:35 -04:00
Thomas Harte
6482303063 Reduces code duplication slightly. 2021-03-21 20:34:58 -04:00
Thomas Harte
388b136980 Relaxes test for a valid TAP. 2021-03-21 20:31:09 -04:00
Thomas Harte
9ce1dbaebb Switches to partial decoding for paging registers; permits video address changes after paging is locked. 2021-03-21 20:23:00 -04:00
Thomas Harte
064667c0c3 Corrects asymmetrical flash, ensures consistent burst phase. 2021-03-21 20:22:27 -04:00
Thomas Harte
58be770eaa Factors out some boilerplate.
When I'm confident this is correct, I can fix up the other call sites.
2021-03-21 00:14:48 -04:00
Thomas Harte
1b0f45649e Improves contended timing.
Still not quite on the money, but this was an overt bug.
2021-03-21 00:00:18 -04:00
Thomas Harte
42bfabbe8c The implication seems to be of a fixed phase swing.
I'm enquiring further.
2021-03-20 23:46:13 -04:00
Thomas Harte
986c4006a6 Corrected: PAL machines can now be overt in terms of odd/even colour burst. 2021-03-20 23:45:49 -04:00
Thomas Harte
07a63d62dd Adds some quick arithmetic on the clock speed. 2021-03-20 11:19:44 -04:00
Thomas Harte
26911a16e8 Lengthens sync, better to conform to PAL; experiments with fixed-phase colour burst.
I need to get hold of real documentation here.
2021-03-20 10:38:21 -04:00
Thomas Harte
cf9a5d595b Completes piping of audio. 2021-03-19 23:33:46 -04:00
Thomas Harte
09a6a1905b Implements TAP support. 2021-03-19 23:29:09 -04:00
Thomas Harte
2ad2b4384b Introduces a container for ZX Spectrum-style TAPs. 2021-03-19 23:01:49 -04:00
Thomas Harte
7729f1f3d0 Attempts automatic Spectrum tape control. 2021-03-19 22:43:48 -04:00
Thomas Harte
7d59ff6d8f Builds in a colour burst, producing colour composite. 2021-03-19 22:25:37 -04:00
Thomas Harte
2ee478b4c4 Goes some way towards wiring up Spectrum options. 2021-03-19 22:17:20 -04:00
Thomas Harte
bb0d35e3d0 Minor formatting/layout fixes. 2021-03-19 22:17:03 -04:00
Thomas Harte
84774a7910 Update Qt and SDL build files. 2021-03-19 11:19:10 -04:00
Thomas Harte
a482ce1546 Adds a tape player. 2021-03-19 11:12:50 -04:00
Thomas Harte
a35e1f4fbe Starts to make formal Spectrum accommodations. 2021-03-19 11:06:09 -04:00
Thomas Harte
2371048ad1 Formally separates keyboard code.
With an eye to formalising the Spectrum/ZX81/ZX80 differences.
2021-03-19 10:36:08 -04:00
Thomas Harte
93b9ea67e6 Takes a run at contended timings. 2021-03-19 08:49:56 -04:00
Thomas Harte
60a0f8e824 Merge pull request #885 from TomHarte/MasterSystemBlue
Tweak Master System blue scale.
2021-03-19 08:48:47 -04:00
Thomas Harte
b3fc64d4f2 Merge pull request #884 from MaddTheSane/master
Minor pokes to the test files code.
2021-03-19 08:40:16 -04:00
Thomas Harte
650b9a139b Tweak Master System blue scale. 2021-03-19 08:38:21 -04:00
C.W. Betts
5758693b7d Minor pokes to the test files code. 2021-03-19 02:19:49 -06:00
Thomas Harte
f8c9ef2950 Add necessary header for memset. 2021-03-19 00:00:59 -04:00
Thomas Harte
69ca2e8803 Update Xcode project. 2021-03-18 23:52:35 -04:00
Thomas Harte
87fac15cc4 This is going to remain purely a template; no .cpp. 2021-03-18 23:51:45 -04:00
Thomas Harte
2d51924a3c Wires up Spectrum keyboard.
The machine now appears to be fully interactive and functional. Timing and media aside, that is.
2021-03-18 23:51:21 -04:00
Thomas Harte
c3d96b30d7 Factors out a little of the ZX81's keyboard logic. 2021-03-18 23:45:57 -04:00
Thomas Harte
44240773ef Corrects address generation, ink/paper selection.
Seemingly to give a correct +2a boot. Time to add a keyboard and find out, I guess.
2021-03-18 23:30:48 -04:00
Thomas Harte
ed587a4db5 Provides a better no-port-here value. 2021-03-18 23:14:39 -04:00
Thomas Harte
020a04006e Adds flashing, randomises initial RAM contents. 2021-03-18 23:07:51 -04:00
Thomas Harte
622a8abf7f Takes a stab at pixel output. 2021-03-18 22:57:10 -04:00
Thomas Harte
871bac6c8a Marks out and approximately centres a pixel region. 2021-03-18 22:41:20 -04:00
Thomas Harte
fe3e8f87e7 Takes a shot at an all-border output. 2021-03-18 22:29:24 -04:00
Thomas Harte
87fc7c02e8 Provides a base pointer for video output. 2021-03-18 22:04:41 -04:00
Thomas Harte
f2620e6afb Adds a CRT. Not yet clocked. 2021-03-18 21:54:42 -04:00
Thomas Harte
ab2ad70885 Takes a run at interrupts. 2021-03-18 21:29:52 -04:00
Thomas Harte
135134acfd Adds a shell for video emulation. 2021-03-18 12:47:48 -04:00
Thomas Harte
5664e81d48 It appears the +2a and +3 have a different clock rate. 2021-03-18 12:41:24 -04:00
Thomas Harte
c353923557 This can be constexpr. 2021-03-18 12:40:59 -04:00
Thomas Harte
b830d62850 Adds quick notes on port FE. 2021-03-18 12:32:54 -04:00
Thomas Harte
17f551e89d Attempts a full audio wiring. 2021-03-18 12:23:54 -04:00
Thomas Harte
4a4da90d56 Implements some of the memory map, and instantiates audio objects. 2021-03-18 12:14:48 -04:00
Thomas Harte
404c1f06e6 Insert missing space. 2021-03-18 10:44:01 -04:00
Thomas Harte
730bfcd1fd Stumbles towards a memory map. 2021-03-18 10:43:51 -04:00
Thomas Harte
97249b0edd Slow walks further towards a functioning Spectrum. 2021-03-18 10:18:17 -04:00
Thomas Harte
5a1bda1d82 Performs boilerplate towards a ZX Spectrum class. 2021-03-17 23:38:55 -04:00
Thomas Harte
b7d6b8efcf Fix Xcode project. 2021-03-17 23:27:34 -04:00
Thomas Harte
9bec91c2b9 Correct further namespace references. 2021-03-17 22:56:03 -04:00
Thomas Harte
3d1775d853 Correct namespace. 2021-03-17 22:52:23 -04:00
Thomas Harte
814c057570 Update further path references. 2021-03-17 22:46:25 -04:00
Thomas Harte
b63ca16ce2 Attempts to hatch a Sinclair namespace. 2021-03-17 22:40:29 -04:00
Thomas Harte
0ddf09ac0f Adds the +2a/+3 ROM. 2021-03-17 22:16:57 -04:00
Thomas Harte
e53586df1d Adds tape-file static analysis for a hypothetical ZX Spectrum. 2021-03-17 22:09:44 -04:00
Thomas Harte
54491b35ef Merge branch 'master' into ZXSpectrum 2021-03-17 12:39:20 -04:00
Thomas Harte
b447f5f174 Starts adding the Spectrum to the static analyser. 2021-03-17 12:38:37 -04:00
Thomas Harte
39a105b48a Merge pull request #879 from TomHarte/CPCTapes
Slightly Improves CPC tape loading times
2021-03-16 22:09:15 -04:00
Thomas Harte
cdc19c6990 Adds TODO. 2021-03-15 11:39:15 -04:00
Thomas Harte
397704a1e6 Withdraws published quick-load option for the CPC. 2021-03-15 11:37:23 -04:00
Thomas Harte
1a5dafae00 Slightly neatens. 2021-03-15 11:37:03 -04:00
Thomas Harte
d368dae94a Adds tape motor LED. 2021-03-12 23:09:51 -05:00
Thomas Harte
54e2eb0948 Shortens wasted typing. 2021-03-12 23:04:45 -05:00
Thomas Harte
7d778bc328 Formally introduces fast tape support as an option.
It doesn't feel that fast yet though.
2021-03-12 22:57:02 -05:00
Thomas Harte
7a8317ad81 It seems a full CRC is in play. 2021-03-12 22:45:48 -05:00
Thomas Harte
a32a2f36be Advances to correctly reading bytes.
Something is still amiss though. Maybe I'm supposed to update the checksum?
2021-03-12 19:15:35 -05:00
Thomas Harte
064fe7658c Adds necessary interface to inherit a CPC tape-speed byte. 2021-03-12 18:43:20 -05:00
Thomas Harte
cd215ef521 Stumbles towards supporting fast tape loading.
Right now: in a non-optional manner.
2021-03-12 18:42:17 -05:00
Thomas Harte
14c5e038e2 Merge pull request #881 from Cacodemon345/patch-1
Fix compilation on GCC 10
2021-03-12 16:02:10 -05:00
Cacodemon345
82717b39bb Fix compilation on GCC 10 2021-03-13 01:27:29 +06:00
Thomas Harte
f190a1395a Enables detection of CPC-format tape data.
It turns out that the Spectrum's timings are its alone; speed autodetection added.
2021-03-10 22:02:10 -05:00
Thomas Harte
4eaf3440bd Add note to self. 2021-03-07 21:21:58 -05:00
Thomas Harte
f985248902 Add header for memcpy. 2021-03-07 21:20:35 -05:00
Thomas Harte
5c90744f0c More minor style improvements. 2021-03-07 20:49:40 -05:00
Thomas Harte
e9177bbb2a Makes an attempt to parse headers. 2021-03-07 20:49:09 -05:00
Thomas Harte
ab5e4ca9c7 Factors proceed_to_symbol upwards. 2021-03-07 20:48:51 -05:00
Thomas Harte
40516c9cec Minor style improvements: some local consts, and overrides. 2021-03-07 15:56:58 -05:00
Thomas Harte
d93d380c88 Adds bit-level Spectrum-style tape parsing.
More to do, obviously.
2021-03-07 15:51:25 -05:00
Thomas Harte
8a1c6978de Merge pull request #877 from TomHarte/MissingConstraints
Corrects minor macOS layout constraint issues.
2021-03-07 13:12:02 -05:00
Thomas Harte
6839e9e3b3 Ensures no double definition of NDEBUG. 2021-03-07 12:52:54 -05:00
Thomas Harte
83cbbe09c6 Adds missing constraints; eliminates magic constants. 2021-03-07 12:52:39 -05:00
Thomas Harte
166ddab5e0 Merge pull request #876 from TomHarte/SafeQuickboot
Makes absolutely sure not to try to use quickboot workaround for Mac 128kb/512kb.
2021-03-06 22:40:35 -05:00
Thomas Harte
67408521cd Makes absolutely sure not to try to use quickboot workaround for Mac 128kb/512kb.
Albeit that it should be harmless; it's just seeding RAM.
2021-03-06 22:34:35 -05:00
Thomas Harte
f05260b839 ZX80/1: fix initial key state, wait line when NMI disabled. 2021-03-06 21:59:45 -05:00
Thomas Harte
62949d2f8b Merge pull request #875 from TomHarte/InitialSelection
Ensures machine selection carries over sessions.
2021-03-06 21:38:41 -05:00
Thomas Harte
2f18f40697 Ensures machine selection carries over sessions. 2021-03-06 21:32:35 -05:00
Thomas Harte
eea4c1f148 Wires up machineSelectionTabs. 2021-03-06 21:31:08 -05:00
Thomas Harte
63a792f434 Merge pull request #844 from TomHarte/AppleIIgs
Adds incomplete Apple IIgs emulation.
2021-03-06 21:27:39 -05:00
Thomas Harte
7b164de6fd Reenables interrupts. 2021-03-06 18:53:39 -05:00
Thomas Harte
26ad760904 Withdraws the Apple IIgs tab item.
Also makes some Swift style changes while I'm here: I'm pervasively assuming that all these objects exist, might as well be upfront about it.
2021-03-06 18:53:09 -05:00
Thomas Harte
24e68166c6 Minor clean-ups of my temporary cruft. 2021-03-06 17:11:06 -05:00
Thomas Harte
b72474f418 Reduces debugging shout outs a touch. 2021-03-03 20:53:05 -05:00
Thomas Harte
38046d49aa Increases debugging noise. 2021-03-03 20:52:14 -05:00
Thomas Harte
4601421aa6 This conditional is gone. 2021-03-03 20:52:01 -05:00
Thomas Harte
86fd47545d Silences. 2021-03-03 20:51:33 -05:00
Thomas Harte
c8471eb993 Adds various asserts, some comments. 2021-03-03 20:47:45 -05:00
Thomas Harte
83d0cfc24e Improves commentary. 2021-03-03 20:33:28 -05:00
Thomas Harte
cbf5a79ee8 Takes a swing at improper key repeat. 2021-02-28 16:46:09 -05:00
Thomas Harte
2f45e07d82 Further consolidates region map, now that shadowing is orthogonal. 2021-02-28 15:22:36 -05:00
Thomas Harte
496b6b5cfc Introduces a further 128 bits of storage to eliminate the conditional in IsShadowed. 2021-02-28 15:14:32 -05:00
Thomas Harte
8604b1786e Simplifies banks $02+ to a single region. 2021-02-27 23:34:51 -05:00
Thomas Harte
267e28e012 Adds various bits of debugging detritus. 2021-02-27 22:27:57 -05:00
Thomas Harte
631a8a7421 Adds bitset header. 2021-02-27 22:13:49 -05:00
Thomas Harte
7dcb0553e4 Switches to a target-centric view of shadowing. 2021-02-27 22:13:10 -05:00
Thomas Harte
2a7ea9f57c Merge branch 'master' into AppleIIgs 2021-02-26 21:31:18 -05:00
Thomas Harte
e2b20568c6 Merge pull request #873 from TomHarte/Mac128kb
Fixes 400kb drive PWM interpretation; enables Mac 128kb and 512kb.
2021-02-26 21:29:57 -05:00
Thomas Harte
4f5eb4d71b Adds the Mac 128k & 512k as Qt options. 2021-02-26 21:25:11 -05:00
Thomas Harte
a1df8452ce Add the 128kb and 512kb Macintoshes as selectable options in macOS. 2021-02-26 21:22:54 -05:00
Thomas Harte
9781460c41 Thanks to a hint from the MAME guys: finally completes Macintosh 128kb and 512kb emulation (!) 2021-02-26 21:22:35 -05:00
Thomas Harte
55c9d152e9 Slightly smarter: this does branchless shadowing without additional storage. 2021-02-24 18:46:41 -05:00
Thomas Harte
71a107fe75 Silences the IWM again, for now. 2021-02-23 21:57:19 -05:00
Thomas Harte
6cf9099ce1 Don't clear the mouse data full flag until both registers have been read. 2021-02-23 21:57:02 -05:00
Thomas Harte
e6dc39f6f0 Makes an attempt at mouse event transmission. 2021-02-19 22:48:15 -05:00
Thomas Harte
f6466fd657 Remove temporary hackery. 2021-02-19 22:47:50 -05:00
Thomas Harte
28ce675c96 Takes a further stab at ::CommandDataIsValid. 2021-02-19 22:22:14 -05:00
Thomas Harte
3d91b0a31b Fixes keyboard data return.
Input sort of works now! Except that key repeat is way out of control.
2021-02-19 21:55:06 -05:00
Thomas Harte
5d1970d201 Adds a hacky different guess at how register access might work. 2021-02-19 21:46:18 -05:00
Thomas Harte
72d7901c88 Takes a shot at the keyboard data full flag.
Just a guess. But likely?
2021-02-19 20:06:12 -05:00
Thomas Harte
60cfec6a65 Amongst ever more cruft, adds a couple of extra asserts. 2021-02-18 22:49:48 -05:00
Thomas Harte
2e9065b34c Increases number of fixed initial values. 2021-02-18 22:48:53 -05:00
Thomas Harte
992ee6d631 Don't zero out the program bank until after it has headed stackward. 2021-02-17 22:08:08 -05:00
Thomas Harte
772093c311 Add missing header. 2021-02-16 22:51:57 -05:00
Thomas Harte
e42843cca0 This may temporarily exhaust my wit for asserts. 2021-02-16 22:47:46 -05:00
Thomas Harte
3336a123f8 Asserts even more overtly. 2021-02-16 22:33:28 -05:00
Thomas Harte
bd54e30748 Adds workaround for Sweet 16, which can produce bad data. 2021-02-16 22:21:10 -05:00
Thomas Harte
35be402354 Improve sanity check. 2021-02-16 19:47:25 -05:00
Thomas Harte
28bd620e7f Adds joystick support to the IIgs. 2021-02-16 19:39:22 -05:00
Thomas Harte
96f2d802d9 Adds a safeguard against undefined behaviour in the debugger. 2021-02-16 19:17:54 -05:00
Thomas Harte
b117df3367 Factors out joystick logic. 2021-02-16 19:17:32 -05:00
Thomas Harte
fa8236741d Takes a shot at an ADB mouse. 2021-02-15 20:49:16 -05:00
Thomas Harte
e16d5f33d1 Adds service requests. The microcontroller now appears to consume keyboard events. 2021-02-15 20:33:10 -05:00
Thomas Harte
2a45e7a8d4 Slows timer X, to what may or may not be correct. 2021-02-15 16:40:27 -05:00
Thomas Harte
f8f0ff0fae Add timer X counting.
Still no interrupts.
2021-02-15 16:29:25 -05:00
Thomas Harte
f5dcff2f29 Honours interrupt vector. 2021-02-15 15:05:56 -05:00
Thomas Harte
e773b331cd Implements register 2 listen. 2021-02-15 15:05:46 -05:00
Thomas Harte
99c21925f4 Makes attempt at keyboard mapping. 2021-02-15 15:00:12 -05:00
Thomas Harte
eccf5ca043 Makes first effort to wire up the ADB vertical blank input.
However: looking at the disassembly, I'm not sure it really is wired to INTR. So work to do.
2021-02-14 22:20:58 -05:00
Thomas Harte
24af62a3e5 Sets a default handler of 1. 2021-02-14 22:20:07 -05:00
Thomas Harte
52cf15c3e6 Attempts to route out modifier state. 2021-02-14 21:15:31 -05:00
Thomas Harte
a791680e6f Implements set_status as per advice. 2021-02-14 21:04:20 -05:00
Thomas Harte
a3e98907ca Removes temporary printf. 2021-02-14 21:03:54 -05:00
Thomas Harte
6e53b4c507 Corrects centralised ADB decoder.
I still think it's appropriate to do this in only a single place, given that using it is optional.
2021-02-14 20:41:05 -05:00
Thomas Harte
52c38e72f6 Starts seeking to automate register 3 handling.
Immediate pitfall: byte capture on the bus side isn't working correctly.
2021-02-14 20:37:33 -05:00
Thomas Harte
a51d143c35 Corrects reactive-device transmission logic.
Albeit that I'm still not properly responding to register 3 stuff, so the ADB bus needn't believe anything is out there. Also, without VSYNC being piped to the microcontroller it may well just not be polling anyway.
2021-02-14 18:54:22 -05:00
Thomas Harte
17e9305282 Starts adding a keyboard. 2021-02-13 23:16:45 -05:00
Thomas Harte
c284b34003 Resolves inability of ADB microcontroller to read its own ROM (!) 2021-02-13 17:53:40 -05:00
Thomas Harte
2ab3bba695 Attempts GLU register latching, restoring expected startup sequence. 2021-02-13 17:38:42 -05:00
Thomas Harte
2c4dcf8843 Edges towards implementing an ADB device. 2021-02-12 21:50:24 -05:00
Thomas Harte
ea40b2c982 Takes a stab at implementing device response. 2021-02-12 18:56:43 -05:00
Thomas Harte
adfdfa205f Starts to establish the means by which I'll implement ADB devices. 2021-02-12 18:42:12 -05:00
Thomas Harte
e83b2120ce Tidies up, allows Operations and AddressingModes to be posted directly to ostreams. 2021-02-10 21:46:56 -05:00
Thomas Harte
33abdc95aa Adds a helper for decoding ADB commands.
Still very noticeably to do: any sort of standard part for devices to respond to the bus.
2021-02-10 21:39:33 -05:00
Thomas Harte
6ca8aa99fc Commit SDL and Qt project files; improve commenting. 2021-02-10 21:28:32 -05:00
Thomas Harte
17bac4c8cf Starts to formalise the ADB bus. 2021-02-10 21:24:31 -05:00
Thomas Harte
46bd20b5e0 Attempts to simplify ADB bit parsing.
On-line output still looks reasonable, albeit that the microcontroller suddenly seems to be interested in devices F and 3 rather than 2 and 3.
2021-02-08 22:08:49 -05:00
Thomas Harte
3c7f9a43ad Merge branch 'AppleIIgs' of github.com:TomHarte/CLK into AppleIIgs 2021-02-08 18:43:27 -05:00
Thomas Harte
82312d3b59 Provide a more convincing version of port output. 2021-02-08 18:14:08 -05:00
Thomas Harte
93a80a30d3 With correct divider appears to get reset requests posted. 2021-02-07 23:05:01 -05:00
Thomas Harte
77b1efd176 Sets sensible 'reset' values. 2021-02-07 21:53:57 -05:00
Thomas Harte
acfab1dfb3 Starts to make some effort at timers. 2021-02-06 21:02:44 -05:00
Thomas Harte
819e9039ab Corrects printed target address for ZeroPageRelative. 2021-02-04 20:54:31 -05:00
Thomas Harte
6526c645a5 Merge branch 'master' into AppleIIgs 2021-02-02 21:29:38 -05:00
Thomas Harte
3d2490b774 Merge pull request #869 from TomHarte/OricReads
Flips conditionals to ensure 65802 safety.
2021-02-02 21:03:08 -05:00
Thomas Harte
1e041f1adf Flips conditionals to ensure 65802 safety. 2021-02-02 20:52:34 -05:00
Thomas Harte
4fdf01a1a8 Merge pull request #868 from TomHarte/ElectronSCSI
Adds Electron hard disk support.
2021-02-02 20:43:08 -05:00
Thomas Harte
beb514b231 Adds an additional mapping for copy. 2021-02-02 20:37:15 -05:00
Thomas Harte
f57e897085 Corrects visibility of SCSI output. 2021-02-02 20:24:39 -05:00
Thomas Harte
2a8e8a4982 Slightly increases logging. 2021-02-02 20:24:19 -05:00
Thomas Harte
9f202d4238 Adds SCSI interrupt support. 2021-02-01 17:40:11 -05:00
Thomas Harte
1a40cc048e Niceties: include AP6 ROM for hard-disk users; show SCSI activity indicator. 2021-01-31 21:41:11 -05:00
Thomas Harte
53514c7fdc Ensures non-breakage of Qt interface. 2021-01-31 21:28:55 -05:00
Thomas Harte
274b3c7d24 Handles SCSI changes on-demand. 2021-01-31 21:24:54 -05:00
Thomas Harte
07df7572b3 Switch to preferred Acorn-world extension: DAT. 2021-01-31 21:03:09 -05:00
Thomas Harte
906b6ccdb7 This appears to be sufficient for the Electron to _read_ SCSI.
So that's step one.
2021-01-31 18:36:29 -05:00
Thomas Harte
f1ba040dd8 This is probably how Acorn hard disk images look (?) 2021-01-31 16:00:52 -05:00
Thomas Harte
8db289e229 Adds some notes-to-self on SCSI and a route to using Acorn's ADFS. 2021-01-31 13:12:59 -05:00
Thomas Harte
8142487d57 Merge pull request #867 from TomHarte/ElectronStarCommand
Pause longer for Electron commands that start with a modifier.
2021-01-31 12:34:19 -05:00
Thomas Harte
2860be7068 Permit a longer pause at startup for Electron commands that start with shift, control or func. 2021-01-31 12:25:22 -05:00
Thomas Harte
b5ecd5f7ef Merge branch 'master' into AppleIIgs 2021-01-31 11:47:40 -05:00
Thomas Harte
7e720e754b Merge pull request #866 from TomHarte/ElectronUI
Adds UI for the new Electron configuration options.
2021-01-31 11:44:47 -05:00
Thomas Harte
41a618c957 Adds new Electron configuration options to the Qt UI. 2021-01-31 10:13:32 -05:00
Thomas Harte
3d85e6bb97 Adds Mac UI for new Electron configuration options. 2021-01-31 09:49:51 -05:00
Thomas Harte
d54085c7fd Merge pull request #865 from TomHarte/ADL
Electron: adds support for the ADL file format, and logic for AP6 and sideways RAM selection
2021-01-31 09:37:24 -05:00
Thomas Harte
0bb8bdf938 Switch to O(1) test, which avoids an extra #include. 2021-01-30 23:33:03 -05:00
Thomas Harte
865058b8d6 Adds basic text search to achieve AP6 detection. 2021-01-30 23:32:04 -05:00
Thomas Harte
b6bc0a21fb Adds a TODO on intended logic around the AP6 ROM.
... plus a promise as to intent in the Electron-specific ROM readme.
2021-01-30 23:20:43 -05:00
Thomas Harte
8311ac4a7c Adds parsing of the top-level directory for ADFS images. 2021-01-30 23:10:59 -05:00
Thomas Harte
4636d8dfb7 Adds support for installing the AP6 ROM and/or sideways RAM. 2021-01-30 19:38:19 -05:00
Thomas Harte
ac95e4d758 Adds support for ADL-format disk images. 2021-01-30 18:39:29 -05:00
Thomas Harte
b8c6d4b153 Rips out my high-level ADB microcontroller protocol implementation.
Adds just enough that the main computer validates the ADB controller as present and talking.
2021-01-30 17:53:27 -05:00
Thomas Harte
5eddc92846 Implements direction registers. 2021-01-28 21:06:11 -05:00
Thomas Harte
f50e8b5106 If I'm going to maintain the max_address approach, & is 'correct'.
% +1 would be 'more correct', but I think this approach is probably misguided.
2021-01-27 18:31:11 -05:00
Thomas Harte
dcc2fe0990 Improves M50470 entry-point detection, adds test output. 2021-01-26 21:29:17 -05:00
Thomas Harte
56111c75ae Makes first efforts towards disassembly. 2021-01-26 19:52:30 -05:00
Thomas Harte
cc90935abd Starts to provide just a touch of reflection. 2021-01-26 19:22:00 -05:00
Thomas Harte
413e42e1b6 Attempts to fix BBC.
But thereby stops all ADB output.
2021-01-25 22:34:03 -05:00
Thomas Harte
fc4bda0047 Experimentally flipping interpretation of the output bit gives something closer to coherent. 2021-01-25 22:02:39 -05:00
Thomas Harte
c8beb59172 Attempts properly to track ADB bus activity.
Output is not yet a valid ADB stream. Work to do.
2021-01-25 17:43:22 -05:00
Thomas Harte
8789ffda15 Corrects performer storage, RMW/W confusion, implicit casts, port readback. 2021-01-24 22:30:42 -05:00
Thomas Harte
e8e604dc3c Attempts to wire up M50470 and GLU.
Resulting in an unexpected interest in R15. Bugs to find, I guess.
2021-01-24 18:07:05 -05:00
Thomas Harte
57e0fdfadc Ensures ADB microcontroller is clocked.
And runs at the 'correct' speed (i.e. modulo my instruction-by-instruction implementation).
2021-01-23 22:55:12 -05:00
Thomas Harte
7f62732476 Fixes kiosk target, accepts that I'll probably never add UI tests. 2021-01-23 21:59:21 -05:00
Thomas Harte
36aebe0ff9 Posts cycle lengths. 2021-01-23 21:58:52 -05:00
Thomas Harte
051d2b83f4 Corrects TSX lookup. 2021-01-23 15:45:21 -05:00
Thomas Harte
17b12120eb Corrects bit-selection shifts. 2021-01-21 23:13:00 -05:00
Thomas Harte
6e9ce50569 Corrects duration-based iteration. 2021-01-21 23:05:43 -05:00
Thomas Harte
adef2e9b4e Starts formalising end conditions. 2021-01-21 22:36:44 -05:00
Thomas Harte
0fafbf5092 Completes M50740 instruction set. 2021-01-21 19:08:38 -05:00
Thomas Harte
3c887aff95 Improves consistency. 2021-01-21 18:58:22 -05:00
Thomas Harte
e5076b295b Corrects namespace. 2021-01-21 18:58:11 -05:00
Thomas Harte
c10c161d39 Implements ADC and SBC. 2021-01-21 18:53:24 -05:00
Thomas Harte
04024ca159 Adds BIT. 2021-01-20 21:41:43 -05:00
Thomas Harte
64d556f60f Implements shifts and rotates. 2021-01-20 21:39:13 -05:00
Thomas Harte
8564e7406b Corrects index-mode CMP, LDA. 2021-01-20 21:32:46 -05:00
Thomas Harte
ebdb58d790 Seemingly advances to the first indefinite loop. 2021-01-20 21:18:52 -05:00
Thomas Harte
cf8afc70b2 Takes a swing at BBC, BBS. 2021-01-20 20:52:04 -05:00
Thomas Harte
4f02e8fbaf Knocks off the low-hanging instruction fruit. 2021-01-20 20:41:35 -05:00
Thomas Harte
6e618a6bb7 Adds a list of missing instructions.
Not looking too bad; subject to not yet having a strategy for interrupts, timing, nothing yet implemented for timers, IO ports...
2021-01-20 20:37:35 -05:00
Thomas Harte
df1bc18fb3 Pushes ahead to what will be my first interaction with the T flag. 2021-01-20 20:27:09 -05:00
Thomas Harte
9f12ce2fb8 Corrects RTS, adds the remainder of the direct flag manipulations. 2021-01-20 20:16:55 -05:00
Thomas Harte
b9672c0669 Gets beyond a prima facie convincing JSR/RET. 2021-01-20 18:21:44 -05:00
Thomas Harte
e58608b25a Gets as far as executing a first loop. 2021-01-20 18:15:24 -05:00
Thomas Harte
e502d76371 Corrects immediate instruction length, muddles through to having to parse a second program segment.
Albeit with JSR not yet properly implemented.
2021-01-19 22:12:18 -05:00
Thomas Harte
b0c790f3c6 Adds enough flags seemingly to reach an ASL. 2021-01-19 21:54:15 -05:00
Thomas Harte
aa478cd222 Stops trying to force bit ID into the addressing mode. 2021-01-19 21:51:01 -05:00
Thomas Harte
c78c121159 Succeeds at executing a single instruction. 2021-01-18 20:16:01 -05:00
Thomas Harte
e71e506883 This assert is redundant; not worth an extra #include. 2021-01-18 17:56:40 -05:00
Thomas Harte
a601ac0cab Corrects performer population, lookup, calls. 2021-01-18 17:53:14 -05:00
Thomas Harte
9b92753e0a In theory this should 'execute' up to the first unconditional branch.
Where execution means: do very little.
2021-01-18 17:11:11 -05:00
Thomas Harte
ec0018df79 Routes in the ADB keyboard ROM. This should get as far as parsing. 2021-01-18 16:59:49 -05:00
Thomas Harte
8b19c523cf Starts to bend towards getting some performers in motion. 2021-01-18 16:45:52 -05:00
Thomas Harte
5ace61f9b9 Continues walking very slowly towards cached execution. 2021-01-18 11:20:45 -05:00
Thomas Harte
8a74f5911c Minor reorganisation to finish the day. 2021-01-17 21:56:15 -05:00
Thomas Harte
4982430a29 Takes a run at most of the remaining addressing modes. 2021-01-17 21:52:16 -05:00
Thomas Harte
dea79c6dea Adds missing #include. 2021-01-17 20:56:22 -05:00
Thomas Harte
ad03858c6e Switches performers to member functions. Very slightly starts work on M50740 performers. 2021-01-17 20:53:11 -05:00
Thomas Harte
54b26c7991 Bends to using 8-bit lookups for M50740 instructions. 2021-01-17 20:03:36 -05:00
Thomas Harte
17c3a3eb4b Seeks to switch to maintaining a bank of performers.
My thinking here is that for really simple processors there'll be 256 or less, meaning that they can be stored by simple uint8_t; for every other processor I can currently think of it'll likely be uint16_t.

Either way, that's a much better outcome than using plain pointers, which on architectures I currently build for will always be 8 bytes. For the simple processors I can get eight times as much into the cache; for the others four times.
2021-01-17 19:38:23 -05:00
Thomas Harte
5f413a38df Switches all American-style dates.
I'd failed to configure my new computer appropriately, it seems.
2021-01-16 22:09:19 -05:00
Thomas Harte
8860d0ff51 Starts to establish the CachingExecutor. 2021-01-16 22:06:16 -05:00
Thomas Harte
8bd471fa3c Corrects recursive call. 2021-01-16 21:50:48 -05:00
Thomas Harte
cd6ac51aa6 Muddles along to generating functions.
Albeit right now without a body.
2021-01-16 21:45:44 -05:00
Thomas Harte
10caa1a1fb Steps gingerly towards execution. 2021-01-16 20:51:02 -05:00
Thomas Harte
722e0068ca Adds additional exposition. 2021-01-16 20:10:20 -05:00
Thomas Harte
8f2eea8819 Corrects AccessType::Read. 2021-01-16 20:04:48 -05:00
Thomas Harte
3b2d65fa16 Adds access type declaration. 2021-01-16 20:04:01 -05:00
Thomas Harte
3dc36b704a Starts on the next piece: parsers. 2021-01-16 19:54:40 -05:00
Thomas Harte
37a20e125c Completes the M50740 decoder.
Completely untested.
2021-01-15 22:47:52 -05:00
Thomas Harte
2910faf963 Adds missing #include. 2021-01-15 22:33:14 -05:00
Thomas Harte
321e10fffb Adds 'InstructionSets' to the SDL and Qt projects. 2021-01-15 22:30:02 -05:00
Thomas Harte
1acb8c3c42 Completes the opcode map. 2021-01-15 22:24:37 -05:00
Thomas Harte
f667dd223f Advances to 50% of the opcode map. 2021-01-15 22:05:34 -05:00
Thomas Harte
e0d90f69ec Fills in the first quarter of the opcode map. 2021-01-15 21:58:46 -05:00
Thomas Harte
d82187bee2 Decides to shove bit number into AddressingMode. 2021-01-15 21:50:05 -05:00
Thomas Harte
3c20e1f037 Adds files for the M50740 and corrects namespace errors elsewhere. 2021-01-15 21:30:30 -05:00
Thomas Harte
15bedc74d4 Merge branch 'master' into AppleIIgs 2021-01-15 21:15:10 -05:00
Thomas Harte
4bd6ffa9e4 Merge pull request #863 from TomHarte/DecodersAhoy
Sketches out the concept of a `Decoder`
2021-01-15 21:14:49 -05:00
Thomas Harte
9c2c918760 Better sorts by function, corrects TEST description. 2021-01-15 21:07:02 -05:00
Thomas Harte
47d20699d8 Completes list, ensures POP acts as documented. 2021-01-15 20:48:31 -05:00
Thomas Harte
e8ce70dccb Chips further away at documentation. 2021-01-15 18:52:59 -05:00
Thomas Harte
fa4938f29c Establishes the reason I'm sort-of documenting these. 2021-01-15 18:27:55 -05:00
Thomas Harte
ddb4bb1421 Better plans project layout. 2021-01-15 18:16:01 -05:00
Thomas Harte
ca94e9038e Introduces 'far' test, fixes parsing. 2021-01-14 22:15:38 -05:00
Thomas Harte
2c72a77a25 Adds byte-by-byte decoder test; corrects divergences. 2021-01-13 21:51:18 -05:00
Thomas Harte
8c0e06e645 Adds a test for 0x83 and fixes sign extension.
ODA doesn't seem to accept 0x82, but testing 0x83 adds some confidence.
2021-01-13 20:42:21 -05:00
Thomas Harte
a24ae727a7 Takes a run at 0x82 and 0x83, completing the set. 2021-01-13 20:29:44 -05:00
Thomas Harte
5058a8b96a Completes the first test stream.
... and improves decoding consistency in conjunction.
2021-01-12 21:49:22 -05:00
Thomas Harte
762ecab3aa Adds operand/displacement capture.
This gets unit test as far as a disagreement over how to handle bad 0xc4 suffixes.
2021-01-10 22:55:25 -05:00
Thomas Harte
9ba5b7c1d4 Adds a few more asserts.
It's still just operands and displacements failing, which is nice.
2021-01-08 23:21:01 -05:00
Thomas Harte
5f807b6e47 Ensures that the operand is the only thing failing in decoding of the first instruction. 2021-01-08 23:02:06 -05:00
Thomas Harte
718f950071 Implements 80 and 81. 2021-01-08 22:50:59 -05:00
Thomas Harte
68fe16a092 Marks intent for operand/displacement. 2021-01-08 22:45:27 -05:00
Thomas Harte
97a64db5e0 Edges closer towards full x86 recording. 2021-01-08 22:38:56 -05:00
Thomas Harte
86577b772b Rethinks size; packs all captured information into an x86 Instruction.
Albeit that operand and displacement are't yet captured. Or extractable.
2021-01-08 22:22:07 -05:00
Thomas Harte
306df7554e Starts trying to find a good packing for X86 instructions.
To consider: do I really need `size` on every instruction?
2021-01-08 21:33:01 -05:00
Thomas Harte
30c2c0f050 Attempts to complete operand recognition. 2021-01-07 21:59:00 -05:00
Thomas Harte
205649cac2 Decodes 8e. 2021-01-07 21:36:05 -05:00
Thomas Harte
fd49b72e31 Simplifies macros, implements d0, d1, d2 and d3. 2021-01-07 21:30:01 -05:00
Thomas Harte
995904993d Fills in 8f, c2, c3, ca and cb.
Also switches to RETN and RETF for near/far RET as this seems idiomatic.
2021-01-06 21:18:24 -05:00
Thomas Harte
17cbba85fc Formalises what's missing in terms of opcodes and fills in some blanks. 2021-01-05 21:47:12 -05:00
Thomas Harte
9d7d45338f Ostensibly gets the instruction stream correct for test case 1.
Subject to operand and displacement currently being absent, and likely inconsistencies in field population, most of which are omitted from the Instruction anyway.
2021-01-05 21:34:35 -05:00
Thomas Harte
3b55d3f158 Nudges up to a need to decode operation from the ModRegRM byte. 2021-01-05 21:25:12 -05:00
Thomas Harte
fda2293d6b Improves constness. 2021-01-04 22:36:39 -05:00
Thomas Harte
da814c62bc Merge branch 'master' into AppleIIgs 2021-01-03 20:57:08 -05:00
Thomas Harte
d4095b1b3b Merge branch 'master' into DecodersAhoy 2021-01-03 20:56:47 -05:00
Thomas Harte
ed41154338 Merge pull request #862 from MaddTheSane/madds-patch-1
Madd's improvements
2021-01-03 20:53:39 -05:00
Thomas Harte
38bca5f0f0 Finally runs into the wall of trying to merge operands and offsets. 2021-01-03 20:08:13 -05:00
Thomas Harte
a8738b533a Switch for now to block-level decoding.
It's easier to step debug.
2021-01-03 20:07:46 -05:00
Thomas Harte
29cf96c703 Adds decoding of disp16 RETs. 2021-01-03 19:39:28 -05:00
Thomas Harte
782dc3d046 Distinguishes inter- and intra-segment RET. 2021-01-03 19:37:37 -05:00
Thomas Harte
0ae217f51d Improves exposition, adds decoding of the 0xbx patch of MOVs. 2021-01-03 19:33:16 -05:00
Thomas Harte
adcb2e03e8 Attempts to consolidate source/destination ordering. 2021-01-03 17:28:29 -05:00
Thomas Harte
11b6c1d4b5 Proceeds to three instructions correctly decoded. 'Wow'. 2021-01-03 17:03:50 -05:00
Thomas Harte
367cb1789d Starts building an x86 test. 2021-01-03 16:37:35 -05:00
Thomas Harte
adf1484ecc Introduces third test sequence, uneventfully. 2021-01-03 16:21:23 -05:00
Thomas Harte
5401ff6c78 Proactively fixes li sign extension. 2021-01-03 11:14:43 -05:00
Thomas Harte
eb8d0eefd5 Factors out some boilerplate and introduces second sequence. 2021-01-03 11:14:30 -05:00
Thomas Harte
c934e22cee Introduces a first test of PowerPC decoding.
Corrected as a result: the bcx conditional, that stdu is 64-bit only, extraction of the li field.
2021-01-02 22:47:42 -05:00
Thomas Harte
1a3effc692 Modifies contract again. This is why I'm doing this now. 2021-01-02 21:19:45 -05:00
Thomas Harte
32c942d154 Muddles drunkenly towards decoding ModRM. 2021-01-02 21:11:19 -05:00
Thomas Harte
9c5dc0ed29 Deferring ModRM work, proceeds to 0x9f. 2021-01-02 19:29:43 -05:00
Thomas Harte
290972cedf Adds health warning. 2021-01-02 19:16:21 -05:00
Thomas Harte
dc9d370952 Does the easier part of the easier half of 8086 decoding. 2021-01-02 19:16:07 -05:00
Thomas Harte
a41be61f99 Slightly fleshes out models, for a sensible beginning. 2021-01-01 17:36:47 -05:00
Thomas Harte
3d1783ddae Add exposition as to the purpose of decoders. 2021-01-01 17:32:57 -05:00
Thomas Harte
8151c8e409 Rounds out field list. 2021-01-01 16:38:40 -05:00
Thomas Harte
0ef42f93ff Further rounds out decoder. 2021-01-01 11:46:26 -05:00
Thomas Harte
d318ab4e70 Edges further onwards. 2020-12-31 21:12:36 -05:00
Thomas Harte
ebfa35c2c7 Conquers another page of instructions; adds supervisor flag. 2020-12-31 18:14:38 -05:00
Thomas Harte
db50b0fe23 Gets started on 6+10 decoding, places stake as to other fields. 2020-12-31 16:51:31 -05:00
Thomas Harte
233a69a1d8 Decodes operations for the simplest 45. 2020-12-31 16:02:52 -05:00
C.W. Betts
3749b7b776 My improvements:
Use synthesized properties for CSMissingROM.
Remove openGLView from the xib: that will quiet a warning.
Add nullability metadata to CSStaticAnalyser.
2020-12-31 13:23:46 -07:00
Thomas Harte
ed63e7ea75 Starts building out a PowerPC decoder. 2020-12-30 22:55:59 -05:00
Thomas Harte
31d68622c8 Better ensures frame buffer can be cleared. 2020-12-29 22:26:19 -05:00
Thomas Harte
ee5f45c979 Merge branch 'master' into AppleIIgs 2020-12-29 22:16:23 -05:00
Thomas Harte
3d79b11f92 Merge pull request #861 from TomHarte/DiskIIOtherROM
Ensure proper in-memory ordering of the b72a2c70 ROM.
2020-12-29 22:13:24 -05:00
Thomas Harte
dfe4e49110 Ensure proper in-memory ordering of the b72a2c70 ROM. 2020-12-29 22:08:48 -05:00
Thomas Harte
12784a71e2 A stab in the dark: does the IOLC inhibit also affect vector fetches? 2020-12-29 20:53:56 -05:00
Thomas Harte
e0b36c9c3d Corrects PBR/DBR resetting upon an exception. 2020-12-29 15:27:49 -05:00
Thomas Harte
c5c56f9d05 Mention my manual list sorting. 2020-12-23 11:15:57 -04:00
Thomas Harte
9f0129cab8 Merge pull request #859 from MaddTheSane/gcJoystick
Initial GameController joystick support.
2020-12-16 21:39:28 -04:00
C.W. Betts
5a48e50355 Use isEqual: to compare GCController when connecting/disconnecting.
Only remove observers for GCController notifications.
2020-12-14 15:41:11 -07:00
C.W. Betts
86283b1815 Actually write the setup code. 2020-12-14 01:14:40 -07:00
C.W. Betts
a38d964f62 Initial GameController joystick support. 2020-12-13 11:23:33 -07:00
Thomas Harte
114d48b076 This register appears to be read/write. 2020-12-11 21:43:34 -05:00
Thomas Harte
6e9d517c26 Minor cleanliness improvement. 2020-12-11 21:43:13 -05:00
Thomas Harte
3b2e97e77c Introduces basic auxiliary switch tests.
All of which pass. Grrr.
2020-12-11 21:30:03 -05:00
Thomas Harte
159924dcc0 More clarity tweaks. 2020-12-10 22:47:11 -05:00
Thomas Harte
5d8f284757 Makes minor style improvements. 2020-12-10 22:11:53 -05:00
Thomas Harte
c978a95463 Increases asserts and adds a test.
Thereby discovers and fixes a problem with set_main_paging().
2020-12-10 21:49:23 -05:00
Thomas Harte
fe4caf7a41 Nudges tick frequency up to match the other platforms. 2020-12-10 21:02:13 -05:00
Thomas Harte
4bf85abf30 Ensure defined initial state for the frame buffer. 2020-12-10 18:15:07 -05:00
Thomas Harte
49cee90b4d Ensures no retraces are missed. 2020-12-09 20:32:26 -05:00
Thomas Harte
394f6b58d8 Ensure _finalisedLineTexture really is cleared. 2020-12-09 20:18:53 -05:00
Thomas Harte
dbdea95241 Ensure use_automatic_tape_control_ is always a valid bool. 2020-12-09 20:10:56 -05:00
Thomas Harte
1928c955d9 Ensures safe startup of the Ensoniq. 2020-12-09 19:46:32 -05:00
Thomas Harte
a91a13b46b Merge branch 'master' into AppleIIgs 2020-12-09 19:33:23 -05:00
Thomas Harte
2f86d5ebaf Merge pull request #858 from TomHarte/M1ForLife
Corrects Metal buffer sizing on Retina displays.
2020-12-09 19:18:56 -05:00
Thomas Harte
b589d6e3ef Fixes retina-display buffer size. 2020-12-09 18:51:10 -05:00
Thomas Harte
db8b265e80 Enable M1 release builds. 2020-12-09 18:38:14 -05:00
Thomas Harte
8560b38ffa Reduce to less-daunting URL. 2020-12-09 16:38:59 -05:00
Thomas Harte
049a78c667 Slightly restricts video flushing test. 2020-12-08 18:47:15 -05:00
Thomas Harte
574a37814c Attempts to fix exception selection and timing. 2020-12-08 18:46:30 -05:00
Thomas Harte
94eb17db0c Add sponsorship exposition; improve general wording 2020-12-08 16:35:00 -05:00
Thomas Harte
9577c8e27f Experiment with F 2020-12-08 16:08:25 -05:00
Thomas Harte
c72bdd776e Adds a new assert: I think this is the issue getting into GS/OS. 2020-12-07 22:43:24 -05:00
Thomas Harte
d35def4bbc Ensures a consistent initial state. 2020-12-06 22:01:59 -05:00
Thomas Harte
d5f209366a Extends testing to disabling IO space. 2020-12-06 21:53:53 -05:00
Thomas Harte
9062e80e9d Adds anti-IO protection. 2020-12-06 21:46:04 -05:00
Thomas Harte
fd3760cedc Adds passing test of basic $00 -> $01 -> $e1 shadowing. 2020-12-06 21:19:38 -05:00
Thomas Harte
9b73331ee9 Resolves deprecated use of scanHexInt32. 2020-12-06 20:49:12 -05:00
Thomas Harte
65ca931e83 Throws in a new assert, against the unimplemented bit 0 of new video. 2020-12-06 20:26:24 -05:00
Thomas Harte
6cb71eb11b This needs explicitly to be a bool for the table lookups to work. 2020-12-06 16:43:07 -05:00
Thomas Harte
43251193ee The actual maximum line length is now 656. 2020-12-06 16:42:43 -05:00
Thomas Harte
55de98fb46 Adds a new statement of intent.
Now I need to try to decide whether I like my current all-in-one mapping for shadowing + paging, or whether it's better to split the things. I'm tending towards the latter at least until the functionality works.
2020-12-05 19:09:21 -05:00
Thomas Harte
1422d43c35 Corrects documentation errors and ambiguities. 2020-12-05 19:07:38 -05:00
Thomas Harte
6273ef8ba2 Adds means to force specific ROM 03 self tests. 2020-12-02 20:48:19 -05:00
Thomas Harte
3c6f09a898 Corrects super high-res aspect ratio and placement. 2020-12-02 20:47:26 -05:00
Thomas Harte
24fcb0c24b Corrects video counter values.
The built-in speed test now passes.
2020-12-01 18:35:55 -05:00
Thomas Harte
3162873a9c Improves the meaning and result of time_since_flush(). 2020-12-01 18:35:07 -05:00
Thomas Harte
03e2b6a265 Makes a slightly more rigorous attempt at discerning 1Mhz and 2.8Mhz operation. 2020-12-01 17:46:30 -05:00
Thomas Harte
ee22cf7ca1 Ensures that PAGE2 propagates from the state register to video. 2020-11-30 22:56:19 -05:00
Thomas Harte
187f507532 The soft switch is LCBANK2, not LCBANK1.
[This also jimmys the IIgs into always entering its extended self test, for now]
2020-11-30 22:35:51 -05:00
Thomas Harte
6000bd3a5e Adds a bonus debugging assert. Let's see. 2020-11-30 18:15:02 -05:00
Thomas Harte
87069da3dd Improves exposition, eliminates a couple of redundant map adjustments. 2020-11-30 18:07:03 -05:00
Thomas Harte
5cb4077576 Switches from modulo to and. 2020-11-30 17:47:57 -05:00
Thomas Harte
e9c7e0b9dd Provisionally reverses meaning of language card RAM bank select. 2020-11-29 21:57:17 -05:00
Thomas Harte
35aa7612bb Ensures that auxiliary/language-card soft switches don't trigger my assert. 2020-11-29 21:32:24 -05:00
Thomas Harte
acaa841822 Adds guaranteed trip to ROM for vector pulls. 2020-11-29 21:29:15 -05:00
Thomas Harte
46c1c9b5ee CLRVBLINT calls it 3.75Hz. Which makes the arithmetic nicer. 2020-11-29 21:25:06 -05:00
Thomas Harte
4bdbca64b2 Takes a shot at the Mega II-style video interrupts. 2020-11-29 21:21:46 -05:00
Thomas Harte
3da6b4709c Fixes sign of arithmetic. 2020-11-29 20:23:33 -05:00
Thomas Harte
11fe8ab6db Corrects counter scales, adds a read for $c032.
Albeit that I have no idea what that's supposed to read as.
2020-11-29 20:08:59 -05:00
Thomas Harte
a9ce43d244 Takes a shot at the two video counter registers. 2020-11-29 19:57:35 -05:00
Thomas Harte
091bce9350 Merge branch 'master' into AppleIIgs 2020-11-29 00:09:20 -05:00
Thomas Harte
32ccce3040 Merge pull request #855 from TomHarte/QtNoKeyboardCopy
Qt: don't copy the result of get_keyboard().
2020-11-29 00:05:26 -05:00
Thomas Harte
ab3fcb3ea0 Qt: don't copy the result of get_keyboard(). 2020-11-29 00:01:11 -05:00
Thomas Harte
9610672615 Merge pull request #854 from TomHarte/OpenGLNoColourBurst
Avoids all risk of infinities when there is no colour burst
2020-11-28 23:54:29 -05:00
Thomas Harte
5ee9630624 Use compositeAmplitude in favour of its reciprocal. 2020-11-28 19:53:34 -05:00
Thomas Harte
1b3836eb1c Adds an overt branch for mono/colour composite selection. 2020-11-28 19:47:04 -05:00
Thomas Harte
1302a046e9 Merge branch 'OpenGLNoColourBurst' of github.com:TomHarte/CLK into OpenGLNoColourBurst 2020-11-28 17:19:42 -05:00
Thomas Harte
33dec3c220 Given that lineCompositeAmplitude is not normalised, ups threshold. 2020-11-28 17:19:28 -05:00
Thomas Harte
7c29c3a944 Given that lineCompositeAmplitude is not normalised, ups threshold. 2020-11-28 17:13:18 -05:00
Thomas Harte
c9ca1fc7a0 Merge pull request #853 from TomHarte/AppleIIReset
Improves Apple II keyboard input, especially under SDL.
2020-11-28 12:43:32 -05:00
Thomas Harte
a965c8de9f Resolves intended reset_all_keys. 2020-11-27 21:53:34 -05:00
Thomas Harte
0b4b271e3d Pulls out redundant check. 2020-11-27 21:04:20 -05:00
Thomas Harte
5fc6dd1a4d Regresses macOS deployment target for kiosk mode to avoid OpenGL warning. 2020-11-27 21:02:04 -05:00
Thomas Harte
79ef026b93 Allows machines to declare a preference for logical input.
It's only a preference, and the Apple II does prefer it.
2020-11-27 21:00:48 -05:00
Thomas Harte
a4ab5b0b49 Does a better job of ensuring sensible key mappings. 2020-11-27 20:49:38 -05:00
Thomas Harte
310282b7c9 Ensures extra_border_length always has a defined value. 2020-11-27 10:31:04 -05:00
Thomas Harte
af667c718e Gets a bit more rigorous in remaining missing parts. 2020-11-26 22:36:32 -05:00
Thomas Harte
950f5b1691 Closes the loop on interrupts. 2020-11-26 19:56:42 -05:00
Thomas Harte
f54a3f8619 Limit test target to latest macOS, current architecture. 2020-11-26 19:50:38 -05:00
Thomas Harte
cbc0d848ad Implements most of get_data. 2020-11-26 17:25:27 -05:00
Thomas Harte
f4d13d1f6f Takes a run at the bus side of honouring Ensoniq sequence points. 2020-11-26 17:14:46 -05:00
Thomas Harte
6808ad6f5d Adds a getter for the interrupt line. 2020-11-26 16:44:35 -05:00
Thomas Harte
7a8920ee38 Takes a stab at next_sequence_point. 2020-11-26 16:41:11 -05:00
Thomas Harte
4870506f6e Implements skip_audio. 2020-11-26 16:24:48 -05:00
Thomas Harte
6f47f9d67c Corrects placement of address bits. 2020-11-26 16:15:40 -05:00
Thomas Harte
8093f67173 Ensures video interrupts can't be missed by a suitably-timed access. 2020-11-26 16:11:03 -05:00
Thomas Harte
72884f37c3 It's still interrupt-deficient, but fills in additional Ensoniq audio generation. 2020-11-26 16:03:28 -05:00
Thomas Harte
8edb3fcd5f Attempts to obey accumulator size in determining sample end. 2020-11-26 15:07:29 -05:00
Thomas Harte
b0efc647f1 An OpenGL context is neither still necessary nor desirable. 2020-11-26 13:49:41 -05:00
Thomas Harte
fdd102df52 Resolves border colour resets. 2020-11-26 13:13:48 -05:00
Thomas Harte
73d28838c0 Slightly rebalances template.
More clearly to ensure the lock_guard stays in the correct place.
2020-11-26 13:08:40 -05:00
Thomas Harte
03a893dc74 Quick refactor: this clearly isn't a VideoBase, it's the full implementation. 2020-11-26 12:54:20 -05:00
Thomas Harte
56de2512ae Adds a further safety assert. 2020-11-25 23:34:30 -05:00
Thomas Harte
cdc2311045 Enables fuzzing, adds a definite no-op write. 2020-11-25 23:33:55 -05:00
Thomas Harte
c6c12209e8 Corrects end_data thread safety; permits caller not to have reached new_modals before a machine starts trying to push data. 2020-11-25 23:32:37 -05:00
Thomas Harte
eec27c3406 Reaches for marginally more coherent ADB data. 2020-11-25 17:34:00 -05:00
Thomas Harte
2ac6f96806 Merge branch 'AppleIIgs' of github.com:TomHarte/CLK into AppleIIgs 2020-11-24 18:28:24 -05:00
Thomas Harte
0bd3103949 Wires in the most common IIgs style of 2MG. 2020-11-24 18:19:34 -05:00
Thomas Harte
098a22aa95 Avoid out-of-bounds access of double_bytes. 2020-11-24 09:38:07 -05:00
Thomas Harte
9a819d6ca0 Transcribes interesting 2MG fields, albeit without acting on them. 2020-11-23 22:02:32 -05:00
Thomas Harte
b4bf541eec Adds boilerplate route into a 2MG-handling class. 2020-11-23 21:42:18 -05:00
Thomas Harte
7ede3d2b9e Corrects collection of palettes other than palette 0. 2020-11-23 21:00:26 -05:00
Thomas Harte
e7160fe3c3 Rounds out the IIgs video hardware, bugs aside. 2020-11-23 20:58:32 -05:00
Thomas Harte
9d61665014 Attempts to add colour double [low/high] resolution output. 2020-11-23 19:05:18 -05:00
Thomas Harte
d2938ad7c8 Eliminate magic constants. 2020-11-23 18:36:44 -05:00
Thomas Harte
9e0e063f8a Resolves one further GCC warning.
Technically this leaves one further, on a temporary printf I have in my IIgs. I'll fix that when I strip all this caveman stufff.
2020-11-22 21:57:48 -05:00
Thomas Harte
46f7ff07f7 Adds support for fill mode. 2020-11-22 21:55:21 -05:00
Thomas Harte
8ace258fbc Tackles outstanding GCC warnings. 2020-11-22 21:43:56 -05:00
Thomas Harte
4359fb1746 Enables undefined-behaviour sanitiser. 2020-11-22 21:30:00 -05:00
Thomas Harte
a34f294ba8 Pulls out commonalities re: NTSC colour, ensures mixed modes on a line works. 2020-11-22 21:29:40 -05:00
Thomas Harte
cd7d080b7a Corrects low-resolution mode. 2020-11-22 20:52:42 -05:00
Thomas Harte
b0936b6ef4 Resolves high-resolution output.
Yet to optimise, but working.
2020-11-22 19:10:05 -05:00
Thomas Harte
8fae74f93e Reintroduces delay bit, reverses phase.
There are stray columns of errors, but otherwise this is almost correct.
2020-11-22 11:06:14 -05:00
Thomas Harte
fca48e4b66 Makes hasty attempt to shift 'NTSC' in the most natural direction. 2020-11-21 23:39:58 -05:00
Thomas Harte
dd816c5a0a Restore valid buffering. 2020-11-21 22:55:54 -05:00
Thomas Harte
3b2ea37428 Slightly cleans up. 2020-11-21 22:53:26 -05:00
Thomas Harte
8a805b6ba1 Ensures that get_average_output_peak() returns something sensible even before a set_relative_volumes. 2020-11-21 22:52:57 -05:00
Thomas Harte
3cc89cb4d2 Seeks to avoid false assert failures. 2020-11-21 22:52:19 -05:00
Thomas Harte
9b45c5a1cd Resolves out-of-bounds reads. 2020-11-21 22:36:10 -05:00
Thomas Harte
3cba3a5ac0 Corrects card mask test outside of bank $00. 2020-11-21 22:22:27 -05:00
Thomas Harte
4b024c5787 Starts to make some attempt at classic II modes. 2020-11-21 18:07:51 -05:00
Thomas Harte
4a42de4f18 Attempts to add 5.25" drive support to the IIgs.
I want to try some classic software.
2020-11-20 21:37:17 -05:00
Thomas Harte
d00e5d23ef Takes a second shot at the MemoryWrite constructor complaint. 2020-11-19 22:28:10 -05:00
Thomas Harte
2c9ce116a2 Resolves various GCC-reported issues. 2020-11-19 22:21:20 -05:00
Thomas Harte
3512352c32 Attempt to use the most-significant relevant bits for sample position. 2020-11-19 22:13:09 -05:00
Thomas Harte
4d9372c52f Also takes a stab at swap mode. 2020-11-19 21:56:49 -05:00
Thomas Harte
1d288b08b6 Attempts the two most basic forms of DOC output.
Sans interrupts. Or register reads of any variety.
2020-11-19 21:19:27 -05:00
Thomas Harte
f3c7c11772 Register writes now reach the audio thread. 2020-11-18 21:52:03 -05:00
Thomas Harte
4b9fe805e9 Sets up a queue to push memory writes onto the audio thread. 2020-11-18 21:40:56 -05:00
Thomas Harte
a7051e4e42 Strip this forceinline until I've satisfied myself that it works in declarations. 2020-11-18 21:40:25 -05:00
Thomas Harte
34794223b4 For now, at least, c800–cfff is always built-in ROM.
Otherwise I probably need to extend my c3 logic to cover the other built-in cards (?)
2020-11-18 19:49:45 -05:00
Thomas Harte
96cf617ee6 Advances slightly. I think I need a custom queue for RAM writes. 2020-11-18 19:48:53 -05:00
Thomas Harte
69dddf34b9 Adds bonus sanity check. 2020-11-18 19:47:56 -05:00
Thomas Harte
8f4597f742 Hacks in double text.
Actually, only one error: it should start half a column earlier. All 'double' output should. TODO.
2020-11-18 19:47:22 -05:00
Thomas Harte
98347cb1c3 Starts in the direction of audio support. 2020-11-18 18:39:11 -05:00
Thomas Harte
c7ab3d4075 Reduces cost of bookending video data. 2020-11-18 17:32:11 -05:00
Thomas Harte
cddd72876f Flips meaning of ejected bit, to please the IIgs. 2020-11-18 17:20:48 -05:00
Thomas Harte
62f936128d It seems possibly there is a distinct IIgs character ROM? 2020-11-16 22:22:26 -05:00
Thomas Harte
bb80e53021 Reduces frequency of video flushes. 2020-11-16 21:55:41 -05:00
Thomas Harte
952891d1b6 Improves commentary. 2020-11-16 21:46:35 -05:00
Thomas Harte
6dfad6a44b Slightly reduces logging.
Hopefully soon I can tear the whole lot out.
2020-11-16 21:46:19 -05:00
Thomas Harte
e4c5bfdd5c Takes a repeat shot at proper shadowing.
I think the Apple IIgs Technical Reference explains how these bits interact, and I just had inhibit_all_pages off all on my own.
2020-11-16 19:54:12 -05:00
Thomas Harte
da8563733b Adds an informal guarantee. 2020-11-16 19:53:17 -05:00
Thomas Harte
e41faeb557 Adds a quick protection against sector ID buffer overrun. 2020-11-16 19:52:42 -05:00
Thomas Harte
9a55eb56ea Attempts to provide saner sequence point behaviour. 2020-11-16 19:00:11 -05:00
Thomas Harte
9206ab5dc3 Adds notes to self; implements get_next_sequence_point for video, allowing per-line interrupts. 2020-11-16 14:42:50 -05:00
Thomas Harte
7e39550fc0 Attempts to make JustInTimeActor sequence-point aware.
With the objective of chopping out a lot of future boilerplate.
2020-11-15 21:58:18 -05:00
Thomas Harte
96e79301f3 Clamps 16-bit positioning values. 2020-11-15 19:14:57 -05:00
Thomas Harte
c3f5fbd300 Picks a better framing compromise for classic and new video modes. 2020-11-15 19:14:43 -05:00
Thomas Harte
1db713fec1 Attempts more meaningful super high-res pixel output.
With a timing hack as noted.
2020-11-15 18:36:24 -05:00
Thomas Harte
68ba73bee0 Ensures I get some sort of feedback for non-text modes. 2020-11-15 17:16:52 -05:00
Thomas Harte
cdacf280e1 After much extra logging, corrects destination bank for MVN and MVP. 2020-11-15 16:08:29 -05:00
Thomas Harte
1538a02e18 Better explains concern. 2020-11-14 19:27:20 -05:00
Thomas Harte
f9cec9a102 Attempts also to implement 1Mhz access costs.
Subject to TODO, and same observation as before: this is as to my current understanding only.
2020-11-14 19:23:01 -05:00
Thomas Harte
adda3d8f42 Attempts a 'full' model of 2.8Mhz access timing.
i.e. full to my current understanding.
2020-11-14 19:10:41 -05:00
Thomas Harte
ec3ff0da12 Steps towards proper calculation of time. 2020-11-14 18:39:16 -05:00
Thomas Harte
73c38b3b0d Collapses nested conditionals. 2020-11-14 18:23:31 -05:00
Thomas Harte
edc8050b36 Adds activity indicators. 2020-11-14 18:00:06 -05:00
Thomas Harte
37815a982a Much logging later, corrects 7Mhz IWM windows.
Confirmed by mathematics — the new ones are seven-eighths the length of the established 8Mhz windows — and with reference to suitable Apple documentation.
2020-11-13 22:05:45 -05:00
Thomas Harte
bd8af25294 Merge branch 'master' into AppleIIgs 2020-11-13 21:27:47 -05:00
Thomas Harte
3207183f05 Merge pull request #850 from TomHarte/BigSurAgain
Takes a second stab at resolving Big Sur File -> New...
2020-11-13 20:02:29 -05:00
Thomas Harte
e803f993b7 Increases minimum macOS version to 10.14.
This is lazy, but it means I definitely don't need non-Metal fallback code.
2020-11-13 19:48:45 -05:00
Thomas Harte
5dbc87caf0 Smarter: just ensures any attached panels are closed at close(). 2020-11-13 19:09:30 -05:00
Thomas Harte
4862ccc947 Dismisses ROM requester upon that cancel too. 2020-11-13 19:01:53 -05:00
Thomas Harte
e1ecf66485 Dismisses sheet before closing document. 2020-11-13 19:00:37 -05:00
Thomas Harte
2c71ba0744 Ameliorates against a potential NSRangeException. 2020-11-13 18:29:48 -05:00
Thomas Harte
a7aeb779e9 Disables Apple Silicon binaries until I have some means to test. 2020-11-13 18:07:45 -05:00
Thomas Harte
e72cfbf447 Stop assuming that NSNotification => window.isVisible. 2020-11-13 18:04:31 -05:00
Thomas Harte
0c04a376c4 Stop assuming that NSNotification => window.isVisible. 2020-11-13 18:03:46 -05:00
Thomas Harte
3c6dc4c448 Merge branch 'master' into AppleIIgs 2020-11-13 12:51:53 -05:00
Thomas Harte
5d154e3d0c Merge pull request #849 from TomHarte/BigSur
Slightly tweaks machine picker for macOS Big Sur.
2020-11-13 12:37:36 -05:00
Thomas Harte
86a24cc928 Allows Xcode to bump its versioning on the ROM requester too. 2020-11-13 12:23:48 -05:00
Thomas Harte
e8b52d20e9 Slightly tweaks machine picker for macOS Big Sur. 2020-11-13 12:14:35 -05:00
Thomas Harte
b0fc2f6ecf Amps up logging.
Current suspicion is that the IIgs isn't getting a clean byte stream, never mind whether my assumption of exactly-Mac-style GCR holds (which it probably doesn't).
2020-11-12 21:54:54 -05:00
Thomas Harte
715a1b9cd6 Ensures safe shutdown. 2020-11-12 21:44:51 -05:00
Thomas Harte
81969bbea9 Improves logging, at least for now. 2020-11-12 21:17:14 -05:00
Thomas Harte
86310849eb Corrects IWM clocking. 2020-11-12 18:09:31 -05:00
Thomas Harte
a2a928e262 Takes a guess at the format of IIgs .po files; wires them through to the actual machine.
... which still declines to boot.
2020-11-12 18:01:26 -05:00
Thomas Harte
ffc9e229b6 Adds a route for 'DiskII' images to the IIgs. 2020-11-12 17:35:27 -05:00
Thomas Harte
3813e00ca3 Adds the Apple II toggle speaker. 2020-11-11 21:04:38 -05:00
Thomas Harte
5698aa6499 Corrects colour mapping and improves documentation for self. 2020-11-11 20:41:30 -05:00
Thomas Harte
1f5908dc51 Corrects logging output. 2020-11-11 20:26:04 -05:00
Thomas Harte
72884c3ead Does a better job of shifting output; takes a new guess at the no-receiver case.
ROM03 at least now reaches "check startup device!"
2020-11-11 20:19:35 -05:00
Thomas Harte
80358cf5bd Shift output even if nobody is listening. 2020-11-11 20:04:48 -05:00
Thomas Harte
a15af1df5e Attempts to use the other bit of disk drive control, the 5.25"/3.5" select.
For the record, the ROM thinks it finds some Smartport devices and then attempts to talk to them. Since none is present, it blocks forever.
2020-11-11 17:55:50 -05:00
Thomas Harte
6d511f01a4 Ensures intended no-drive behaviour; no more risks with dangling pointers or nullptr. 2020-11-11 17:54:21 -05:00
Thomas Harte
da9e378ab1 Quietens, for now. 2020-11-11 17:53:21 -05:00
Thomas Harte
6d3d7c6006 It seems like this fix is no longer needed. 2020-11-11 17:30:22 -05:00
Thomas Harte
8024bbd721 Provides minor extra detail. 2020-11-11 17:08:56 -05:00
Thomas Harte
ece9382a4e Also attaches IWM select line. 2020-11-10 18:59:23 -05:00
Thomas Harte
6ba517a4c1 Applies a will-do-for-now crop to video output. 2020-11-10 18:50:23 -05:00
Thomas Harte
20fd5adb24 Makes a first effort at attaching an IWM. 2020-11-10 18:38:23 -05:00
Thomas Harte
abb350ff5b Stubs in audio toggle and disk control.
It appears that ROM 01 now fails because reading the disk interface register doesn't do as expected. ROM 03 starts hitting what should be the IWM and dies in a surplus of logging.
2020-11-09 22:21:52 -05:00
Thomas Harte
dc8d4d49f5 Gives the two sets of switches responsibility for supplying 'state'.
(And fixes language-card state value.)
2020-11-09 22:11:20 -05:00
Thomas Harte
54352cb1cb Stubs in a couple more registers.
PC now hits $0000. Likely a bug.
2020-11-09 21:54:25 -05:00
Thomas Harte
7e106c6add Attempts to stub in read from microcontroller, and extends command 0x06.
A complete guess on the latter, as if you didn't know.
2020-11-09 21:20:53 -05:00
Thomas Harte
0ae49b356a Seems to do enough padding out to get me to my second failing ADB command.
That's better than failing at the first.
2020-11-09 19:05:48 -05:00
Thomas Harte
32374444ba Fixes text output window. 2020-11-08 17:04:04 -05:00
Thomas Harte
287bfeb924 Hacks in 40-column text.
Hot gossip: my IIgs is reporting a system error. A clue!
2020-11-08 17:01:23 -05:00
Thomas Harte
b5fa574686 Merge pull request #847 from TomHarte/MetalTones
Corrects R4G4B4 and R2G2B2 output in Metal.
2020-11-07 23:22:36 -05:00
Thomas Harte
7aea3dc124 Corrects R4G4B4 and R2G2B2 output. 2020-11-07 23:15:48 -05:00
Thomas Harte
81c38c7200 Per the IIgs tech note, this value works the other way around. 2020-11-07 23:15:07 -05:00
Thomas Harte
3bb3d8c5c1 Adds text colour register.
Oddly this isn't currently being set. So probably another latent fault elsewhere.
2020-11-07 23:14:50 -05:00
Thomas Harte
b57a2bfec9 Completes logic for pixel framing. Well, mostly; this doesn't yet allow for auxiliary-using II modes being off to the left.
The perceived effect though is that a frame appears and then freezes. So a clocking issue may still be afoot.
2020-11-07 22:23:48 -05:00
Thomas Harte
93968d267d Corrects R4G4B4 and R2G2B2 output. 2020-11-07 22:19:27 -05:00
Thomas Harte
d27fb5f199 Merge branch 'AppleIIgs' of github.com:TomHarte/CLK into AppleIIgs 2020-11-07 22:03:31 -05:00
Thomas Harte
a51f4122f0 Attempts to respect border colour.
Though for now my display is just a sea of purple.
2020-11-07 22:03:05 -05:00
Thomas Harte
35ba5fc894 Resolves video timing issues. 2020-11-07 21:28:08 -05:00
Thomas Harte
228d901253 Attempts to stabilise image horizontally. 2020-11-07 21:10:05 -05:00
Thomas Harte
d37ba62343 Makes first, faltering steps towards video display. 2020-11-07 20:42:34 -05:00
Thomas Harte
699fb0aa4b Switches to just-in-time video, for easy access to a clock divider. 2020-11-07 19:40:26 -05:00
Thomas Harte
613d4b7c8b Migrates character ROM handling; supplies one for the IIgs. 2020-11-07 17:45:03 -05:00
Thomas Harte
4f9d06d8c7 Merge pull request #846 from MaddTheSane/maddsIIgs
Use url(forResource:... instead of path(forResource:…
2020-11-06 09:39:27 -05:00
Thomas Harte
5149e4364a Merge pull request #845 from MaddTheSane/patch-1
Update 65816kromTests.swift
2020-11-06 09:38:59 -05:00
Thomas Harte
6b29e1f598 Corrects accesses to switch values. 2020-11-05 21:25:06 -05:00
Thomas Harte
6c9edbb7a2 Resolves specious interrupts.dic 2020-11-05 20:51:00 -05:00
Thomas Harte
282d0f1ebb For debugging, adds a dump of anything in the [presumably] text buffer.
Nothing is there.
2020-11-05 18:17:21 -05:00
Thomas Harte
f466cbadec Attempts to do just enough with video to get a functioning vertical blank query. 2020-11-05 17:56:20 -05:00
C.W. Betts
189a468ad4 Use url(forResource:... instead of path(forResource:… as it cuts down on creating a URL struct. 2020-11-05 14:42:39 -07:00
C.W. Betts
a3414c2673 Update 65816kromTests.swift
Only have one runTest method.
2020-11-05 14:36:34 -07:00
Thomas Harte
5126163c5d Attempts to reduce pull request heft.
Given that the licensing of krom's tests is uncertain, and I've given credit and an appropriate link, I needn't include the original code.
2020-11-04 21:49:45 -05:00
Thomas Harte
46ee98639e Stubs in $c010.
Also reduces memory map logging.
2020-11-04 21:35:11 -05:00
Thomas Harte
cc6c0d535c Stubs in some of the sound GLU registers. 2020-11-04 21:29:44 -05:00
Thomas Harte
78b57e73d5 Hacks in a lying vertical blank value. 2020-11-04 21:18:27 -05:00
Thomas Harte
9e2a6526d1 Corrects interpretation of bit 3 of the state register.
And attempts to be a bit more careful with the language card in general.
2020-11-04 21:15:10 -05:00
Thomas Harte
d3c7253981 Shifts size-limiting of X and Y to transitions and mutations, away from reads.
Primarily to remove potential bug-causing complexity — this is easier to debug. But let's see.
2020-11-04 20:35:41 -05:00
Thomas Harte
e3147b6b45 Introduces a pre-STP/WAI limit for the MSC test.
This way I retain testing of NOP, BRK, COP and WDM.
2020-11-03 20:59:07 -05:00
Thomas Harte
d50b059a17 Imports 6502-esque test for decimal SBC overflow.
All applicable krom tests now pass.
2020-11-03 20:37:30 -05:00
Thomas Harte
cc5ec78156 Provides something on WAI/STP; sizes STY by the x flag; disables MSC test. 2020-11-03 20:17:44 -05:00
Thomas Harte
ddc44ce0d1 Reshuffles enum to make macro tests marginally easier. 2020-11-03 20:17:09 -05:00
Thomas Harte
5cbb91f352 Fixes COP vector, ensures WDM skips a byte. 2020-11-03 20:01:02 -05:00
Thomas Harte
91ea2eff4c Corrects MVN/MVP off-by-one and failure to store what was read. 2020-11-03 18:29:35 -05:00
Thomas Harte
bf85d71674 Brings ADC into conformance. Fixes JML. 2020-11-03 18:12:10 -05:00
Thomas Harte
426e90eebf Adds logic to work around Nintendo dependence in the krom tests.
Let the real work begin!
2020-11-03 14:18:40 -05:00
Thomas Harte
3889646d6b Takes a swing at incorporating krom's 65816 test suite. At least as far as ADC. 2020-11-02 21:09:32 -05:00
Thomas Harte
0178aaee2b Attempts retroactively to enforce the rule that 8-bit index modes => no top byte.
(Rather than a preserved but ignored top byte)
2020-11-02 18:55:28 -05:00
Thomas Harte
53f60f7c87 Adds some notes for a pending ADB implementation. 2020-11-01 14:49:04 -05:00
Thomas Harte
2da71acefd Stubs in the ADB GLU. 2020-10-31 21:00:15 -04:00
Thomas Harte
45f5896b76 Stubs video switches into the IIgs. 2020-10-31 20:39:32 -04:00
Thomas Harte
531a3bb7e6 Ensures RAM is zero-initialised, for now, to aid in repeatable bug finding. 2020-10-31 20:03:23 -04:00
Thomas Harte
1b28d929e4 Factors out the Apple II/IIe video switches and mode selection logic. 2020-10-31 20:02:50 -04:00
Thomas Harte
e8943618dc Adds some extra commentary and distinguishes X/Y sizing from M. 2020-10-31 10:21:13 -04:00
Thomas Harte
1ae2f6f449 PHD and PLD should always be 16-bit; PLP 8-bit. 2020-10-31 09:22:35 -04:00
Thomas Harte
88e26b42f5 Fixed: PHP pushes only 8 bits regardless of mode. 2020-10-30 22:36:00 -04:00
Thomas Harte
03d1aff6c0 Fixes 8-bit read/write. 2020-10-30 22:17:55 -04:00
Thomas Harte
e4459b6256 Adds power-on bit to speed register. 2020-10-30 21:50:39 -04:00
Thomas Harte
2be817a6a1 Maps in "the interrupt ROM addresses". 2020-10-30 21:42:43 -04:00
Thomas Harte
a833bb892b Increases logging substantially. 2020-10-30 20:11:55 -04:00
Thomas Harte
7f3f6c339f Corrects stacked program bank during native-mode exceptions. 2020-10-30 20:11:39 -04:00
Thomas Harte
0d562699a2 Ensures unmapped regions are really unmapped. 2020-10-29 22:18:01 -04:00
Thomas Harte
034056d0cd Adds full 8-bit clock addressing; stubs clock into the IIgs. 2020-10-29 21:38:36 -04:00
Thomas Harte
1249fb598b Factors Apple's RTC out of the Macintosh. 2020-10-29 21:03:02 -04:00
Thomas Harte
5a8b8478d2 Corrects unhandled IO assert.
The IIgs proper is actually waiting on communication with the RTC.
2020-10-28 22:14:02 -04:00
Thomas Harte
6c54699c44 Connects up an SCC.
Thereby putting my IIgs into its first perpetual loop. Trying to do something with the SCC I haven't implemented yet perhaps?
2020-10-28 22:07:34 -04:00
Thomas Harte
266022b193 Fixes PEA. 2020-10-28 22:00:28 -04:00
Thomas Harte
94a6da6b7d Exposes much of the auxiliary and language card stuff to the IIgs bus. 2020-10-28 21:58:20 -04:00
Thomas Harte
885fae1534 Stubs in a speed register. 2020-10-28 21:23:45 -04:00
Thomas Harte
1df2ce513a Ensures that reset doesn't push to the stack. 2020-10-28 21:23:35 -04:00
Thomas Harte
1e4679ae14 Corrects JSL and RTL. 2020-10-28 17:25:40 -04:00
Thomas Harte
267dd59a59 Gets as far as seemingly yet another memory-map setting.
Tomorrow, maybe?
2020-10-27 22:31:58 -04:00
Thomas Harte
0a91ac5af5 Adds some extra notes, starts getting into trying to run the IIgs. 2020-10-27 22:09:45 -04:00
Thomas Harte
ad93ad6018 Attempts to finish off shadowing. 2020-10-27 22:05:04 -04:00
Thomas Harte
0c700094ea Goes branchless on shadowing. 2020-10-27 21:56:03 -04:00
Thomas Harte
20631a157b Contorts somewhat in pursuit of branchless shadowing regardless of page and without extra storage. 2020-10-27 21:37:39 -04:00
Thomas Harte
bdda84dfde Adds a very basic shadowing test.
For the record, I'm aware that there's a lot here that I'm not testing. I think the smart move is to get towards a running machine and see which configurations it actually tries to set up, then follow along with appropriate testing; it might cause me to discover a flaw in my comprehension before I've made the same mistake in both the code and a test.
2020-10-27 19:59:41 -04:00
Thomas Harte
e44f95a882 Takes a first, faltering shot at shadowing. 2020-10-27 19:49:47 -04:00
Thomas Harte
31cd45f8b5 Takes a run at set_card_paging and simplifies method of shadowing. 2020-10-27 19:33:47 -04:00
Thomas Harte
74f9f6ad3b Tests and corrects ROM access beyond bank $00. 2020-10-27 19:02:15 -04:00
Thomas Harte
1dfdb51e61 Hits a few other easy cases.
Still to do: card paging, and finding out which banks that applies to, and shadowing. So: everything with flags.
2020-10-26 21:49:47 -04:00
Thomas Harte
18832dc19d Attempts to expand the language card stuff to all affected pages. 2020-10-26 20:30:41 -04:00
Thomas Harte
3dee0666cb Corrects current bank $00 language card behaviour. 2020-10-26 17:46:40 -04:00
Thomas Harte
f830f6a57a Adds failing test of initial ROM mirroring.
It's the end of the evening, so this is it for today.
2020-10-25 22:13:54 -04:00
Thomas Harte
82c733c68c Adds some very basic actual tests. 2020-10-25 21:40:50 -04:00
Thomas Harte
ed510409c4 Starts memory map test class, already finding a typo. 2020-10-25 21:31:21 -04:00
Thomas Harte
7614eba4bf Factors out the IIgs memory map logic.
As testing would be rational.
2020-10-25 21:10:04 -04:00
Thomas Harte
13c8032465 ROM isn't writeable. The clue is in the name. 2020-10-25 18:29:17 -04:00
Thomas Harte
44fc08cd5b Switches to a mapping system that supports non-continuous regions, and is smaller. 2020-10-25 18:28:32 -04:00
Thomas Harte
7631b11c55 Corrects double low-res colour serialisation. 2020-10-24 19:26:32 -04:00
Thomas Harte
726b5f62bb Corrects read/write access to auxiliary soft switches. 2020-10-24 19:00:03 -04:00
Thomas Harte
ddd84db510 Edges towards a functioning IIgs memory map.
Next up: making sure language and auxiliary switches apply. That should get something from the ROM.
2020-10-23 19:41:10 -04:00
Thomas Harte
966241b4cc Adds documentation, ensures the language card signals less noisily. 2020-10-23 18:44:47 -04:00
Thomas Harte
9371a8993f Factors out auxiliary memory switches and related decisions. 2020-10-22 22:33:31 -04:00
Thomas Harte
410c99de54 Factors out the language card memory selection logic. 2020-10-22 21:01:12 -04:00
Thomas Harte
817f93a490 Edges towards a working memory subsystem. At least structurally. 2020-10-22 19:25:04 -04:00
Thomas Harte
43611792ac Adds just enough to get a 65816 ticking over. 2020-10-21 21:19:18 -04:00
Thomas Harte
62231708d7 read_pages_ can be const. 2020-10-21 21:17:15 -04:00
Thomas Harte
a5dcab4092 Ensures machines with no audio output are handled correctly. 2020-10-21 21:16:00 -04:00
Thomas Harte
8bde2e5f4c Slightly neatens Cocoa machine picker. 2020-10-20 22:25:39 -04:00
Thomas Harte
5287c57ee0 Adds the IIgs as a user-selectable machine.
Albeit that there is no underlying machine yet.
2020-10-20 22:18:11 -04:00
Thomas Harte
3aa47f9c68 Merge pull request #843 from TomHarte/MoreDormann
Introduces a build of Dormann's 65C02 tests that is 65816 compatible.
2020-10-19 21:13:46 -04:00
Thomas Harte
ab07814614 Eliminates now-broken 65816 flow test. 2020-10-19 21:02:46 -04:00
Thomas Harte
1653abdf88 Adds the .lst; otherwise I'll probably just lose it. 2020-10-19 20:58:24 -04:00
Thomas Harte
b3ab9fff9b Imports a custom-built copy of Klaus Dormann's 65C02 test, with only 65816-compatible parts.
Thereby fixes another couple of 65816 issues — BRK(, etc) not clearing the decimal flag, and `TRB d` being mismapped.
2020-10-19 19:27:16 -04:00
Thomas Harte
14718b93a4 Improve commentary. 2020-10-19 09:32:50 -04:00
Thomas Harte
69450e27ad Merge pull request #839 from TomHarte/65816
Adds emulation of the 65816.
2020-10-18 21:49:46 -04:00
Thomas Harte
0cd08aa79d Permits the Oric analyser to check CPC-style DSKs.
Oric Mist seems to use that format, so some of these now exist out in the wild.
2020-10-18 21:44:44 -04:00
Thomas Harte
1fa94e1b08 Adds the 65816 as an in-code option for Oric emulation.
This also means it'll be exposed via the SDL build, but that's okay.
2020-10-18 21:43:08 -04:00
Thomas Harte
76d9893866 Declares address-bus sizes formally.
This allows me to fix the final two implicit conversion warnings, albeit that it would have been nice to find a templatey way just to get the type directly from the declaration of `perform_bus_operation`.
2020-10-18 15:08:21 -04:00
Thomas Harte
c3f8982c62 Resolves all internal implicit type-conversion warnings.
Chasing those down, it looks like flags were wrong for PLB and PLD. So it's official: warnings help.
2020-10-18 14:55:17 -04:00
Thomas Harte
99eba2f8ba Ensures intended 65816 exception behaviour.
i.e. the relevant micro-op sequence exists, and its operation isn't lost. Also sets the 65816 by default to jump straight into power-on, not to execute an instruction first. That shouldn't make a functional difference, but it makes debugging easier because it makes startup fully deterministic.
2020-10-18 14:43:47 -04:00
Thomas Harte
69509f6502 Attempts to bring a little more consistency to my use of Swift in test code. 2020-10-17 22:42:54 -04:00
Thomas Harte
c3187fdbe1 Makes minor formatting improvement. 2020-10-17 22:31:51 -04:00
Thomas Harte
42228ea955 Adds 65C02As6502 test, to round out the set. 2020-10-17 22:31:32 -04:00
Thomas Harte
e5f57ea743 Make isReadOperation more overt. 2020-10-17 22:27:04 -04:00
Thomas Harte
3b398f7a9a Attempts to complete all 65816 bus signalling. 2020-10-16 21:56:20 -04:00
Thomas Harte
096add7551 Exposes non-BusOperation bus outputs. 2020-10-16 21:05:42 -04:00
Thomas Harte
334e0666b7 Reports ::Ready upon a WAI. 2020-10-15 21:37:37 -04:00
Thomas Harte
98c81749c8 Adds the conventional flush. 2020-10-15 21:36:04 -04:00
Thomas Harte
5dcf720bb5 Extends list of BusOperations.
Now to retest, widely.
2020-10-15 21:35:01 -04:00
Thomas Harte
9c0c0255f6 Ensures data/program bank can't accidentally be set to 16-bit values. 2020-10-15 21:10:32 -04:00
Thomas Harte
68c15bd605 Updates Qt project; catches another couple of issues via its compiler. 2020-10-15 21:09:22 -04:00
Thomas Harte
9a2f32795f Revokes stack-local storage non-optimisation. 2020-10-15 21:03:10 -04:00
Thomas Harte
7aa6cf4c6b Tidies up layout very slightly. 2020-10-15 20:51:23 -04:00
Thomas Harte
dfda2adf0d Attempts implementations of both ready and abort.
Which I think concludes the inputs?
2020-10-15 20:46:18 -04:00
Thomas Harte
c0a1c34012 Wraps all registers into a struct, so that I can implement abort.
Makes some preparations for ready too.
2020-10-15 18:42:38 -04:00
Thomas Harte
3c6adc1ff4 Completes 65816 addressing mode tests and corresponding fixes. 2020-10-14 22:00:52 -04:00
Thomas Harte
e511d33a7c Adds test for [d], y; fixes implementation. 2020-10-14 21:42:41 -04:00
Thomas Harte
c35969d677 Adds tests for (d, x) and (d), y. Both amply tested in emulation mode, so no problems.
Five to go, all potentially troublesome.
2020-10-14 21:38:00 -04:00
Thomas Harte
27afb8f0a7 Adds direct indirect long test, and thereby fixes addressing mode.
Nine to go!
2020-10-14 21:26:20 -04:00
Thomas Harte
327ab81436 Fills in direct, x and (direct) tests, fixing implementation of the latter.
10 to go.
2020-10-14 21:17:28 -04:00
Thomas Harte
db7178495f Implements direct and final absolute test.
14 to go.
2020-10-14 20:57:47 -04:00
Thomas Harte
979186e71d Transcribes the English-language versions of the outstanding tests.
Passing these will make me willing to call the 65816 functionality provisionally done, other than making sureI signal VPA, VDA, VPB, etc, correctly.
2020-10-14 13:56:37 -04:00
Thomas Harte
f05e0d956b Adds a TODO list in order to keep an end in sight. 2020-10-13 21:43:42 -04:00
Thomas Harte
b22aa5d699 Starts transcribing the addressing examples I have into tests.
Correspondingly extends the exposed register set and test-machine addressing range.
2020-10-13 21:38:30 -04:00
Thomas Harte
3e6a2adaaf Corrects absolute, x and absolute, y addressing modes. 2020-10-13 20:30:39 -04:00
Thomas Harte
8f5537aaaa Attempts to resolve my direct-indirect addressing stumble. 2020-10-13 20:21:53 -04:00
Thomas Harte
a15d4a156b Starts trying to ensure appropriate address wrapping. 2020-10-12 22:33:43 -04:00
Thomas Harte
6a47571d17 Stops truncating tests by two bytes.
Not that it seems to have been problematic.
2020-10-12 21:53:27 -04:00
Thomas Harte
7479dc74ed Removes printf. It's no longer telling me anything. 2020-10-12 21:52:58 -04:00
Thomas Harte
28da1a724a Introduces Jeek816 test case. 2020-10-12 21:43:44 -04:00
Thomas Harte
f529eadbec Corrects 16-bit read-modify-write.
Subject to the TODO proviso on 'correct'; has my 6502 prejudice pushed me into unrealistic bus signalling?
2020-10-12 18:36:09 -04:00
Thomas Harte
5dc3cd3a2f Starts using Jeek816 for a basic native-mode audit. Fixes absolute long addressing. 2020-10-11 22:02:46 -04:00
Thomas Harte
3039a445f0 Ups the 65816 test machine to a full 16mb RAM. 2020-10-11 21:18:01 -04:00
Thomas Harte
82797fd395 Attempts to do the proper thing for interrupts. 2020-10-11 21:10:44 -04:00
Thomas Harte
a0885ab7d0 Implements STP and WAI.
Albeit still without fully-implemented reactions to exceptions in general.
2020-10-11 17:56:55 -04:00
Thomas Harte
8eaf1303a3 Attempts proactively to ensure proper RTI behaviour on the 65816. 2020-10-11 15:25:13 -04:00
Thomas Harte
20cbe72985 Ties to 8- or 16-bit those instructions that aren't M/X-dependent.
This is technically redundant for PEI, PEA and PER since they have dedicated bus programs anyway, but it's good to be explicit.
2020-10-11 14:38:35 -04:00
Thomas Harte
071ad6b767 I don't think RTL is needed; JML looks like it covers it. 2020-10-10 22:16:35 -04:00
Thomas Harte
0619e49eac Takes a short at TSB and TRB.
Three to go.
2020-10-10 22:00:17 -04:00
Thomas Harte
b8848d8580 Implements TCD, TDC, TCS, TSC. 2020-10-10 21:43:05 -04:00
Thomas Harte
aface1f8be Implements XBA and XCE. 2020-10-10 21:34:22 -04:00
Thomas Harte
ae87728770 Ensures M and X are exposed to the public interface. 2020-10-10 21:33:56 -04:00
Thomas Harte
28c8ba70c1 Implements REP and SEP and exposes the MX flags generally. 2020-10-10 21:23:59 -04:00
Thomas Harte
486324ecab This test isn't actually 65816-compatible. 2020-10-10 18:19:48 -04:00
Thomas Harte
6892ac13e8 Corrects BIT. All 65816-applicable Wolfgang Lorenz tests now pass. 2020-10-10 17:47:33 -04:00
Thomas Harte
340ad093a6 Adds 65816 runs of the final tranche of applicable tests. 2020-10-10 17:26:41 -04:00
Thomas Harte
0fe09cd1e4 Knocks SBC into producing likely results; disables Lorenz testing. 2020-10-10 17:13:16 -04:00
Thomas Harte
da4702851f Fixes ADC. 2020-10-10 16:29:48 -04:00
Thomas Harte
09fba72d58 Adds flag manipulation, ADC and SBC 65816 tests.
The latter two fail.
2020-10-10 11:30:15 -04:00
Thomas Harte
d17c90edf7 Corrects ROL d, x. 2020-10-10 11:25:14 -04:00
Thomas Harte
7966592fae Corrects ROL d. 2020-10-10 11:22:23 -04:00
Thomas Harte
6efe4e1753 Fixes AND, EOR, ORA. Takes an unsuccessful shot at ROL. 2020-10-10 10:53:17 -04:00
Thomas Harte
536c4d45c1 Adds additional 65816 tests, some failing; seeks to improve carry behaviour in ASL and ROL. 2020-10-10 10:11:57 -04:00
Thomas Harte
a02f88fe7c Confirms a couple more of the easy sets. 2020-10-10 09:34:29 -04:00
Thomas Harte
d9be6ab806 Confirms that a few other simple tests work immediately on the 65816. 2020-10-09 23:26:35 -04:00
Thomas Harte
290598429a Applies indirect page zero emulation mode addressing constraint to ix addressing.
Lorenz's LDA tests now pass in emulation mode.
2020-10-09 23:22:48 -04:00
Thomas Harte
92e72959c3 Makes corrections to ix addressing mode and shift/roll flags. 2020-10-09 23:12:20 -04:00
Thomas Harte
776f014dbe Attempts LDA tests against the 65816.
Result: ix is faulty. Which we already knew.
2020-10-09 22:23:54 -04:00
Thomas Harte
c01bc784b9 Slightly reduces branching. 2020-10-09 22:21:55 -04:00
Thomas Harte
abcd86a294 Fixes accumulator instructions. 2020-10-09 22:18:22 -04:00
Thomas Harte
451f83ba51 Corrects emulation-mode read-modify-writes not to empty the data buffer. 2020-10-09 22:14:42 -04:00
Thomas Harte
b439f40fe2 Corrects INC and DEC. 2020-10-09 22:04:25 -04:00
Thomas Harte
968166b06d Resolves incorrectly flow after setting up an absolute address. 2020-10-09 21:48:35 -04:00
Thomas Harte
88293909f4 Enables running of a first test on the 65816. 2020-10-09 21:44:47 -04:00
Thomas Harte
9b6c48631d Removes usage of a JAM instruction to spot end-of-tests. 2020-10-09 21:39:34 -04:00
Thomas Harte
0ed98cbfac Attempts to fix direct indirect indexed; not yet successful I think. 2020-10-08 22:15:19 -04:00
Thomas Harte
7dde7cc743 Implements altered direct indexed addressing in emulation mode. 2020-10-08 22:02:14 -04:00
Thomas Harte
755627f12d Corrects direct addressing. 2020-10-08 20:00:01 -04:00
Thomas Harte
f8004d7096 Implements RTI, corrects TAY. 2020-10-08 18:06:11 -04:00
Thomas Harte
0418f51ef2 Takes a shot at emulation-mode 'exceptions'.
It's just RTI and correct decimal SBC left of the official 6502s now, I think.
2020-10-08 17:52:13 -04:00
Thomas Harte
054e0af071 Corrects RTS behaviour: the return address on the stack is off by one.
Dormann's tests now proceed to a BRK.
2020-10-08 16:55:45 -04:00
Thomas Harte
907c3374c3 Attempts to clean up my JMP/JSR mess.
Also takes a step forwards in decimal SBC, but it's not right yet.
2020-10-08 16:48:46 -04:00
Thomas Harte
b578240993 Adds a further error.
Clearly I've severely overloaded 'JMP' and not fully thought through where it gets its addresses from.
2020-10-07 21:47:58 -04:00
Thomas Harte
f83ee97439 PHP pushes with the BRK flag set in emulation mode. 2020-10-07 21:37:50 -04:00
Thomas Harte
19aea85184 Corrects CMP, CPX, CPY carry flags. 2020-10-07 21:23:29 -04:00
Thomas Harte
1ba0a117e7 Corrects PLB, PLD, PLP. 2020-10-07 20:23:53 -04:00
Thomas Harte
b510b9d337 Adds PHD, PHK and 8-bit PHP and PLP. 2020-10-07 20:13:12 -04:00
Thomas Harte
b608e11965 Realises that not all non-incrementing PC fetches should be thrown away. 2020-10-07 20:06:27 -04:00
Thomas Harte
e68b3a2f32 Corrects JMP program. 2020-10-07 19:59:29 -04:00
Thomas Harte
f7b119ffe1 Moves temporary logging, fixes branch instructions. 2020-10-07 19:57:58 -04:00
Thomas Harte
a4cec95db1 Corrects load and transfer flag oversights. 2020-10-07 19:36:23 -04:00
Thomas Harte
84c4fa197b Corrects DEX mapping, notes new Dormann failure case. 2020-10-07 18:48:03 -04:00
Thomas Harte
eac722cf59 Implements enough of ADC and SBC for the Dormann test definitively to fail. 2020-10-07 18:36:17 -04:00
Thomas Harte
7439a326a6 Implements BIT (in regular and immediate forms). 2020-10-07 18:15:18 -04:00
Thomas Harte
5ca1c0747f Generalises CMP to implement CPX and CPY. 2020-10-07 18:09:56 -04:00
Thomas Harte
466ca38dfa Corrects TXY and TYX; kudos to PatrickvL for the spot! 2020-10-07 18:05:42 -04:00
Thomas Harte
93b0839036 Knocks out some transfer operations.
I'm possibly only seven or eight away from being able to test with complete official-opcode-only 6502 code?
2020-10-06 22:29:34 -04:00
Thomas Harte
e068cbc103 Implements CMP and fixes a zero-flag error on 16-bit operations. 2020-10-06 21:47:26 -04:00
Thomas Harte
5c809e5fbf Implements rolls and shifts. 2020-10-06 21:34:39 -04:00
Thomas Harte
3933bf49cf Implements BRL. 2020-10-06 21:28:54 -04:00
Thomas Harte
7065ba4857 Implements the single-byte branches. 2020-10-06 21:24:43 -04:00
Thomas Harte
ebff83018e Implements the bitwise operators. 2020-10-06 20:17:03 -04:00
Thomas Harte
9ce9167e3c Formalises work left to do. 2020-10-06 19:12:19 -04:00
Thomas Harte
993eff1d3d Starts slowly, with flag manipulation. 2020-10-06 16:25:30 -04:00
Thomas Harte
7be983ec00 Slightly improve exposition. 2020-10-05 22:25:20 -04:00
Thomas Harte
18e8d6ce06 Makes an effort to factor out the 6502's [lazy] flags.
This is preparatory to deciding which instructions, if any, are worth factoring out.
2020-10-05 22:23:33 -04:00
Thomas Harte
b7ba0d4327 Attempts to complete all addressing modes.
So, if bugs didn't exist, it'd just be members of the Operation enum to go.
2020-10-05 17:04:57 -04:00
Thomas Harte
825201f4f2 Adds direct indirect. 2020-10-04 22:11:41 -04:00
Thomas Harte
9a05c68ce7 Attempts direct and direct indexed indirect. 2020-10-04 22:06:25 -04:00
Thomas Harte
d8dccf2500 Attempts a full implementation of MVN and MVP. 2020-10-04 19:21:04 -04:00
Thomas Harte
b416aa640f Slightly tidies up, eliminating some store bugs. 2020-10-04 19:12:04 -04:00
Thomas Harte
4ebf594b3b This should bring me up to absolute, y.
i.e. next is datasheet program 7.
2020-10-04 19:02:47 -04:00
Thomas Harte
8a83024962 Starts a dash towards just completing the addressing modes for now.
This brings me up to the end of absolute long (i.e. 4a on the datasheet).
2020-10-04 18:52:46 -04:00
Thomas Harte
bdc1136b96 Edges towards working short absolute addressing mode. 2020-10-03 21:30:24 -04:00
Thomas Harte
da78dea98f Merge branch 'master' into 65816 2020-10-03 21:00:29 -04:00
Thomas Harte
dcf8cb14e2 Merge pull request #842 from TomHarte/QtMouseEscape
Adds F8+F12 as an alternative mouse-release combo for Qt.
2020-10-02 20:37:46 -04:00
Thomas Harte
38912859e1 Adds F8+F12 as an alternative mouse-release combo for Qt. 2020-10-02 20:31:47 -04:00
Thomas Harte
b83d93abc2 Accepts that whether instructions do 8- or 16-bit bus accesses depends on either M or X depending on the operation. 2020-10-02 17:08:30 -04:00
Thomas Harte
36f843bc6e Ensure std::function is visible to 65816Storage.cpp. 2020-09-29 19:23:38 -04:00
Thomas Harte
15c87e02e9 Ditto for printf. 2020-09-29 18:53:02 -04:00
Thomas Harte
00923eac7c Ensure assert is visible to 65816Implementation.hpp. 2020-09-29 18:52:25 -04:00
Thomas Harte
a72ac8294c Adds 65816 alternates to Klaus Dormann's tests.
While also correcting a couple of misspellings of his name. Apologies, Klaus!
2020-09-29 18:49:58 -04:00
Thomas Harte
4f03bf754d Adds the 65816 to SConstruct. 2020-09-29 18:43:39 -04:00
Thomas Harte
78b3ec4b10 The actual work begins: starts implementing 65816 micro-ops. 2020-09-29 18:42:07 -04:00
Thomas Harte
ef1a514785 Introduces 6502Selector, for picking either a 6502 or a 65816 based on a single template parameter. 2020-09-28 21:35:46 -04:00
Thomas Harte
6635876e7e Performs a bare factoring out of the 6502 bus handler. 2020-09-28 18:43:53 -04:00
Thomas Harte
5645f90abe Takes a minor first step towards actually performing 65816 instructions. 2020-09-27 22:20:58 -04:00
Thomas Harte
b96cd4d18b Resolves another unsafe pointer assumption. 2020-09-27 22:20:13 -04:00
Thomas Harte
ad8a2e2cb9 Corrects a long-standing naming obscurity. 2020-09-27 22:19:42 -04:00
Thomas Harte
fa438e5113 Merge branch 'master' into 65816 2020-09-27 15:10:54 -04:00
Thomas Harte
8641494809 Resolve various test-case warnings. 2020-09-27 15:10:29 -04:00
Thomas Harte
f4a23af5d6 Merge pull request #840 from MaddTheSane/patch-1
Update 6502TimingTests.swift
2020-09-27 15:02:10 -04:00
Thomas Harte
5449e90b34 Edges towards offering the 65816 as another type of 6502 for testing. 2020-09-26 22:31:50 -04:00
Thomas Harte
1cd664ad85 Adds a sanity check. 2020-09-26 21:43:26 -04:00
Thomas Harte
e680022b1f Completes the opcode set.
A million bugs yet to find.
2020-09-26 21:35:31 -04:00
Thomas Harte
67c2ce2174 Takes a run at completing the stack section.
I'm not really sure about BRK though — does it gain a signature on the 65816?
2020-09-26 21:20:01 -04:00
Thomas Harte
596e700b60 Drags myself onto the final page of bus programs.
233 opcodes now complete; six bus programs to go.
2020-09-26 20:57:24 -04:00
Thomas Harte
4a53b6e538 Adds push and pull, reaching 229/256 opcodes. 2020-09-26 20:38:29 -04:00
Thomas Harte
687f4bb3bb Adds relative and relative long bus patterns.
Many of the rest cover only one or two opcodes so this puts me at 216/256 opcodes covered; 35/47 bus programs; just more than 5/7 pages.
2020-09-26 20:24:50 -04:00
Thomas Harte
473799cb62 There's not a lot to STP and WAI from a bus program point of view. 2020-09-26 20:18:30 -04:00
C.W. Betts
ce0536cdfa Update 6502TimingTests.swift 2020-09-26 16:13:27 -06:00
Thomas Harte
3dc22a9fd5 Adds implied and immediate modes.
... for 204/256 opcodes covered.
2020-09-26 17:42:42 -04:00
Thomas Harte
f54b655606 Adds d, x and d, y. 2020-09-26 17:26:17 -04:00
Thomas Harte
d2e868ea2b Adds (d), y; [d], y; and [d].
Now covered: 146/256 opcodes, 4/7 pages, 25/47 bus programs.
2020-09-26 16:55:58 -04:00
Thomas Harte
3fc649359a Transcribes the titles of all remaining bus programs.
Thereby frames the distance yet to travel.
2020-09-25 22:29:19 -04:00
Thomas Harte
1512ac11da Adds (d, x) and (d) modes. Albeit by deferring the hard work.
That's: 122/256 opcodes; 22/47 bus programs, ~3.5/7 pages transcribed. Maybe I'll be able to get to the runtime stuff sooner rather than later?
2020-09-25 22:22:30 -04:00
Thomas Harte
5039cc7bb2 Adds direct page.
... to cover 106 opcodes.
2020-09-25 22:01:36 -04:00
Thomas Harte
5360a7b4ce Adds block moves.
These are fairly specialised, dealing in two data addresses simultaneously.
2020-09-25 21:49:03 -04:00
Thomas Harte
2957a31f40 Adds absolute, x; absolute,y; and accumulator addressing modes.
Now covered: 80/256 opcodes, from 2/6 pages of the data sheet; or 16/47 bus programs.
2020-09-25 21:16:36 -04:00
Thomas Harte
8c11df52bf Adds absolute long, x.
Factors out the commonality of a closing read/write while I'm here.
2020-09-25 19:27:17 -04:00
Thomas Harte
2b7ffcd48f Takes a run at JSL al. 2020-09-25 18:35:00 -04:00
Thomas Harte
7980a9033e Adds two-thirds of absolute long.
Working total: 31 opcodes covered; 10/47ths of bus patterns.

Next is JSL, which I think will require additional operations.
2020-09-25 18:16:49 -04:00
Thomas Harte
125ddfa513 Pays a little attention to runtime storage; completes the first page of bus patterns. 2020-09-25 18:00:02 -04:00
Thomas Harte
636e929607 Adds a check for 8/16-bit redundancy. 2020-09-25 17:42:42 -04:00
Thomas Harte
22c792dc46 Adds enough logic to start serialising instructions to somewhere.
Possibly extraneous for now, but it means I can start stepping and testing.
2020-09-25 17:18:25 -04:00
Thomas Harte
95af1815c8 Completes absolute indexed indirect micro-ops.
For the record: this is just six out of forty-seven codes complete. Or about two-thirds of six pages. Plenty to do even before I start trying to interpret these things.
2020-09-24 22:37:31 -04:00
Thomas Harte
d707c5ac95 Switches to generators with stable pointers; adds 2a. 2020-09-24 22:27:20 -04:00
Thomas Harte
5c9192e5e6 Switches to generators for spitting out micro-ops.
Hopefully with a lot of parts to factor out naturally.
2020-09-24 17:36:11 -04:00
Thomas Harte
72b5584042 Immediately runs afoul of a read/write difference in the specs between 8/16-bit mode that suggests maybe this isn't a good structure.
Perhaps generators of some sort?
2020-09-23 22:28:15 -04:00
Thomas Harte
f9045b5352 Rounds out declaration of the absolutes. 2020-09-23 22:23:23 -04:00
Thomas Harte
f87fe92bc8 Begins a meandering road towards the 65816. 2020-09-23 22:14:42 -04:00
Thomas Harte
669d8e64ab Merge pull request #838 from TomHarte/MTKViewRace
Resolves a minor MTKView main-thread race condition.
2020-09-22 22:18:57 -04:00
Thomas Harte
9447aa38be Removes debugging printf. 2020-09-22 22:13:54 -04:00
Thomas Harte
a781c3eb4d Resolves thread-unsafe access of _view.bounds. 2020-09-22 22:13:37 -04:00
Thomas Harte
c0b1308dfd Merge branch 'Vic20Tests' 2020-09-22 22:08:29 -04:00
Thomas Harte
2d9dd6704a Merge branch 'master' of github.com:TomHarte/CLK 2020-09-22 22:07:47 -04:00
Thomas Harte
94dba70bbe Merge pull request #837 from TomHarte/Vic20Tests
Further improves 6522 emulation.
2020-09-22 22:07:34 -04:00
Thomas Harte
022ec20e75 Tries to add semantic meaning to the various auxiliary control fields.
To consider: decoding at set?
2020-09-22 20:50:39 -04:00
Thomas Harte
41f69405d8 Don't decrement timer 1 from the system clock when in PB6 mode.
TODO: rest of PB6 mode.
2020-09-21 22:39:49 -04:00
Thomas Harte
5741e22e29 Switch back to debug-by-default builds. 2020-09-20 18:22:13 -04:00
Thomas Harte
8e242eea54 Ensures timer-linked PB7 output is actually output. 2020-09-20 15:03:26 -04:00
Thomas Harte
703065a0a5 Takes a run at timer-linked PB7 output behaviour.
Seemingly sufficiently to pass the VICE test (which I've transcribed), though with some guesswork.
2020-09-20 14:51:59 -04:00
Thomas Harte
291aa42fe1 Corrects test target. 2020-09-19 21:20:37 -04:00
Thomas Harte
8fc3496cc9 Merge pull request #836 from TomHarte/Vic20Tests
Corrects a couple of minor VIA timer issues
2020-09-17 21:56:43 -04:00
Thomas Harte
e807a462a1 My new reading is that only a write to the counter should affect the interrupt flag. 2020-09-17 21:31:29 -04:00
Thomas Harte
18790a90ae Ensures timer 2 doesn't use timed behaviour when in pulse mode. 2020-09-17 21:09:32 -04:00
Thomas Harte
21afc70261 Adds formal data-sheet names. 2020-09-17 19:00:46 -04:00
Thomas Harte
7bb74af478 Merge pull request #835 from TomHarte/ErrantScan
Allows for permitted 1/32nd timing error in `time_multiplier_`.
2020-09-17 18:17:37 -04:00
Thomas Harte
894269aa06 Allows for permitted 1/32nd timing error in time_multiplier_. 2020-09-17 18:12:21 -04:00
Thomas Harte
8b16da9695 Merge pull request #834 from TomHarte/FloatingSpeaker
Resolves lowpass-speaker position aliasing
2020-09-16 19:05:54 -04:00
Thomas Harte
f783ec6269 Since input and output are floating point, using an integer Stepper is not appropriate. 2020-09-16 18:53:44 -04:00
Thomas Harte
22c9734874 Merge pull request #832 from TomHarte/MetalScanTarget
Adds a Metal ScanTarget, for macOS.
2020-09-16 18:19:58 -04:00
Thomas Harte
a17d0e428f Protects against some further uninitialised values. 2020-09-16 18:15:57 -04:00
Thomas Harte
bb57f0bcc7 Ensures all 6560 properties have a valid default value. 2020-09-16 17:24:18 -04:00
Thomas Harte
b1aefbfe85 Separates asserts. 2020-09-15 23:24:06 -04:00
Thomas Harte
061288f5a7 Add the Macintosh to the Mac kiosk mode informal test set. 2020-09-15 22:49:00 -04:00
Thomas Harte
5a53474536 Ensure MultiKeyboard deconstructs properly. 2020-09-15 22:48:44 -04:00
Thomas Harte
18d0fff8da Graduates the Atari ST. 2020-09-15 22:46:38 -04:00
Thomas Harte
0ac2145740 Add Metal/OpenGL distinction. 2020-09-15 22:43:39 -04:00
Thomas Harte
bc8787ded6 Improves macro safety. 2020-09-15 22:26:33 -04:00
Thomas Harte
69d21daaa3 Improves commentary. 2020-09-15 22:21:05 -04:00
Thomas Harte
5651ef606d Resolves failure to advance video address when output is blocked. 2020-09-15 22:20:06 -04:00
Thomas Harte
b831b31382 Adds a further sanity check. 2020-09-15 17:04:04 -04:00
Thomas Harte
2fd5cc056c Adds std::atomic_thread_fences, but these seem not to be a magic bullet. 2020-09-15 16:34:34 -04:00
Thomas Harte
82dbdf7dfc Switches to using regular linear interpolation for supersampling. 2020-09-14 22:36:00 -04:00
Thomas Harte
eb9903cd10 Defensively disables allocation of anything outside of visible lines. 2020-09-14 22:29:05 -04:00
Thomas Harte
227e98d6d7 Slightly simplifies control flow. 2020-09-14 22:27:25 -04:00
Thomas Harte
35476063b7 Resolves potential data races. 2020-09-14 21:07:50 -04:00
Thomas Harte
8557bb2136 Adds minor exposition. 2020-09-14 20:39:52 -04:00
Thomas Harte
c0c7818d5d Reintroduces screenshots. 2020-09-14 20:33:05 -04:00
Thomas Harte
ceeadd6a33 Edges up towards reimplementing screenshots. 2020-09-13 22:30:17 -04:00
Thomas Harte
1a2545fdea Excises dangling references to OpenGLView, reinstates display link. 2020-09-13 22:11:51 -04:00
Thomas Harte
c5e9a74c88 Uses DisplayMetrics to disable supersampling when too slow. 2020-09-13 21:07:59 -04:00
Thomas Harte
d7972a7b86 Enforces across-the-board supersampling.
I'm damned if I can figure out how to talk an MTKView, or Metal in general, into supersampling so as a first effort this does it in software.
2020-09-13 19:30:26 -04:00
Thomas Harte
7dd4c67304 Corrects access to data_type_size, adds sanity check on output area return. 2020-09-13 18:59:27 -04:00
Thomas Harte
e113780fd1 Minor: ensures no possibility of a dangling(-ish) pointer within the Mac video. 2020-09-10 22:13:19 -04:00
Thomas Harte
e32ae6c191 Adds UGLY HACKs to workaround uncovered issues in the OpenGL scan target. 2020-09-10 22:10:24 -04:00
Thomas Harte
bcaceff378 Simplifies in-Metal transform logic, loading responsibility for setup onto the CPU.
I think I've also finally excised whatever order-of-operations issue I was having with regard to non-4:3 displays.
2020-09-10 20:32:58 -04:00
Thomas Harte
d7b405c6f8 Ensures direct luminance -> 'RGB' video doesn't go down the composition pipeline. 2020-09-10 13:20:40 -04:00
Thomas Harte
edf8cf4dc6 Completes the set of with/without gamma, and ensures correct alpha selection.
Also culls some other repetitive TODOs.
2020-09-09 19:28:38 -04:00
Thomas Harte
dfcc8e9822 Switches some of the interpolated fields to half precision. 2020-09-09 18:17:05 -04:00
Thomas Harte
016e96e6f8 Extends usage of half. Possibly towards its conclusion. 2020-09-09 15:10:19 -04:00
Thomas Harte
e7ce03c418 Attempts to ensure initial finalised line texture state.
This was an attempt to remove the vertical line on the left of a composite display. Obviously the cause is not as suspected.
2020-09-09 13:15:21 -04:00
Thomas Harte
3d392dd81d Completes conversion of composite & S-Video per-pixel processing to 16-bit floats. 2020-09-09 13:02:04 -04:00
Thomas Harte
42d810db7f Switches per-pixel uniforms to halfs. 2020-09-09 10:53:09 -04:00
Thomas Harte
18571e8351 Also calculates a chroma kernel size, though it isn't used for anything yet. 2020-09-08 20:08:56 -04:00
Thomas Harte
dda1649ab7 Introduces smaller luma kernel functions where useable. 2020-09-08 19:55:37 -04:00
Thomas Harte
c82e0df071 Starts a transition towards half-precision arithmetic. 2020-09-08 19:37:36 -04:00
Thomas Harte
06b7ea5a6e Strips the luma kernel back to 1d. 2020-09-08 19:15:19 -04:00
Thomas Harte
c49fcb9ec9 Based on further play: one box filter to separate luma/chroma, another to filter chroma, plus a FIR sharpen on luma. 2020-09-08 16:35:05 -04:00
Thomas Harte
0e44d6d214 Experiments with an all-box filter. 2020-09-08 16:19:08 -04:00
Thomas Harte
6adad7fbf5 Starts experimenting again with box filters. 2020-09-07 22:47:49 -04:00
Thomas Harte
de6ed7b615 Corrects phase-linked luminance support. 2020-09-07 20:53:28 -04:00
Thomas Harte
07dcb4dbb1 Starts reintroducing brightness, gamma and transparency for composite and S-Video pipelines. 2020-09-07 18:19:13 -04:00
Thomas Harte
e99896eadc At least nominates alpha, gamma and brightness to metal. 2020-09-04 16:07:58 -04:00
Thomas Harte
489701afcb Fixes window resizing. 2020-09-03 21:28:39 -04:00
Thomas Harte
55e576cc57 Ensures unpainted areas in composite displays have a non-asymptotic effect on luminance calculations. 2020-09-03 21:10:30 -04:00
Thomas Harte
6bd8ec9545 Alas, 1.0 seems to be the limit for proper artefact colour. 2020-09-03 20:53:45 -04:00
Thomas Harte
5cd8d86eef Switches to dynamic generation of the 'sharpness' filter, correcting issues with the Apple II (amongst others). 2020-09-03 20:48:44 -04:00
Thomas Harte
74d0acdaec Fixes non-RGB colour composite generation.
The hard-coded sharpen filter proves to be a really bad fit for the Apple II though.
2020-09-03 19:04:17 -04:00
Thomas Harte
0288a1974b Tries: separate filters for chroma and luma, plus a post-separation sharpen filter on the latter. 2020-09-03 13:18:21 -04:00
Thomas Harte
6efd8782fe Tweaks coefficients some more; makes sure that data is never larger than the intermediate buffers. 2020-09-02 20:14:41 -04:00
Thomas Harte
8bab9d5d60 Corrects S-Video and composite generation for RGB[1/2/4] sources.
Also toys with a double luminance filter in order to try to sharpen chrominance. But maybe I should be looking at other convolutions entirely?
2020-09-02 19:13:54 -04:00
Thomas Harte
6ef1dfd8be Sets a more realistic colour subcarrier amplitude. 2020-09-02 15:52:05 -04:00
Thomas Harte
7e58648743 Corrects front-running bug, plays further with colour amplitude. 2020-09-02 15:51:48 -04:00
Thomas Harte
0f0c3e616d Tweaks some numbers.
I'm largely treading water here. Probably time to think about the race.
2020-09-02 08:17:01 -04:00
Thomas Harte
c7ce65ea4c Attempts fully to restore composite video.
Subject to some sort of nasty race condition for the time being.
2020-09-02 08:03:10 -04:00
Thomas Harte
c36247b609 Ensures reuse of offset buffers.
There seems to be some sort of epic race condition as the drawing pipeline lags though. Will need to investigate.
2020-09-01 22:11:48 -04:00
Thomas Harte
15296e43a4 Attempts correctly to set up the CPU side of a composite video pipeline, at least.
So: I think this is really close, but I'm out of time for the day.
2020-09-01 21:58:33 -04:00
Thomas Harte
f2929230a2 [Experimentally] introduces blending between computed S-Video fragments. 2020-09-01 21:37:36 -04:00
Thomas Harte
bf252b8061 Fixes sizing of buffers to the current output. 2020-09-01 21:33:54 -04:00
Thomas Harte
9e2bf2af7e Restricts S-Video processing to updated lines. 2020-09-01 21:27:40 -04:00
Thomas Harte
245f2654f0 Shifts S-Video processing into the compute shader. 2020-09-01 20:37:11 -04:00
Thomas Harte
67ca298a72 Forces a no-op compute shader into the S-Video pipeline.
The intention is to restrict the area acted over, and to do the S-Video filtering in there. Then I'll just need two such stages for composite.
2020-09-01 18:39:52 -04:00
Thomas Harte
67d4dbf91a Starts girding for a third pipeline. 2020-08-31 20:01:59 -04:00
Thomas Harte
b344269140 I think I accept the need for intermediate steps now.
This allocates storage.
2020-08-30 20:21:01 -04:00
Thomas Harte
bb547610f2 Adds commentary, shrinks some intermediate texture sizes. 2020-08-30 12:06:29 -04:00
Thomas Harte
1e1f007bb7 Possibly convinces myself that no-separation chroma/luma isn't practical.
... as appealing as it may be, were filters perfect.
2020-08-29 21:25:49 -04:00
Thomas Harte
c40d858f02 Switches back to angular stuff at input resolution; ensures all S-Video modes work.
Now to roll back onto composite. Fingers crossed!
2020-08-29 20:54:46 -04:00
Thomas Harte
3d564d85fd Proves that per-pixel sine/cos evaluation avoids phase issues.
Even in PAL mode. But I'd rather not _require_ this as it kind of negates directly-sampled input.
2020-08-29 18:53:37 -04:00
Thomas Harte
02cea40ffa Attempts to avoid introducing phase error in scanToComposition.
Also brightens S-Video up to RGB levels.
2020-08-25 22:41:37 -04:00
Thomas Harte
e502d336db Having decided that these things probably need to be separate, starts drilling down on S-Video. 2020-08-25 22:05:19 -04:00
Thomas Harte
807cb99f6d Composite angles are signed. 2020-08-23 21:39:04 -04:00
Thomas Harte
8b6879a782 Brief detour: introduces myself to C++11 multiline string literals.
A full cleaning coming soon, I imagine.
2020-08-23 21:18:38 -04:00
Thomas Harte
7ca0362f23 Treads water.
Difficult current question: why does the Atari 2600's display change colours as the display tries to achieve horizontal lock? Phase should be unchanged. Ergo something is amiss.
2020-08-23 21:03:26 -04:00
Thomas Harte
56c7bd242a Marginally tidies. 2020-08-22 16:38:36 -04:00
Thomas Harte
5c6112415a Sets appropriate clear colour for composition render pass. 2020-08-21 22:41:54 -04:00
Thomas Harte
bf6a0c9fc4 Achieves a return of composite colour for RGB-producing machines. 2020-08-21 22:06:36 -04:00
Thomas Harte
d54b937ab6 Starts trying to do actual composite processing. 2020-08-21 21:11:25 -04:00
Thomas Harte
7c23c32e44 Corrects composition colour phase. 2020-08-20 20:45:45 -04:00
Thomas Harte
4e21d24b5f Corrects composition colour amplitude. 2020-08-20 20:34:37 -04:00
Thomas Harte
ad6fb85fda Corrects use of composition buffer.
Something is still very obviously amiss in colour processing somewhere down the line, but the correct forms are once again visibly in evidence.
2020-08-20 20:21:28 -04:00
Thomas Harte
5dc39a5d24 Adds the composition render pass.
Albeit that something here doesn't work at present.
2020-08-19 21:56:53 -04:00
Thomas Harte
3597f687de Continues sidling towards composite & S-Video handling. 2020-08-19 21:20:06 -04:00
Thomas Harte
8811506adf Starts towards building a compound[/composition?] buffer.
I now need to discover whether I can use natural integer texture coordinates.
2020-08-17 22:10:02 -04:00
Thomas Harte
11dec6fc0f Avoids a redundant clear. 2020-08-17 22:09:15 -04:00
Thomas Harte
59c4c8233f Generalises existing scanToDisplay to add lineToDisplay. 2020-08-17 21:15:19 -04:00
Thomas Harte
9da79d2d81 Clarifies scaling logic. 2020-08-17 20:29:46 -04:00
Thomas Harte
246b474a25 Removes ONE_BIG_LOCK, having effectively neutered it anyway.
Starts work on more explicit API usage validation. Maybe the issue isn't a race condition?
2020-08-16 22:09:25 -04:00
Thomas Harte
27e8a3a1b5 Obeys modals' zoom.
Subject to an attempt at factoring aspect ratio differences.
2020-08-16 21:11:43 -04:00
Thomas Harte
745797b596 Introduces a stencil buffer plus the inter-frame clearing it allows. 2020-08-16 16:42:32 -04:00
Thomas Harte
940e9e037e Adds first_scan to LineMetadata.
Also reorders `Line` fields to match `Scan` fields, just for visual consistency.
2020-08-16 08:59:37 -04:00
Thomas Harte
512c0079a9 Makes thread safe. 2020-08-15 21:52:55 -04:00
Thomas Harte
645c29f853 Adds an intermediate buffer to correct inter-frame smoothing.
Also goes someway back to the old scan output scheduling, albeit presently with limited thread safety.
2020-08-15 21:24:10 -04:00
Thomas Harte
e55945674d Reduces main thread blocking. 2020-08-14 22:16:49 -04:00
Thomas Harte
7ac88536dd Respects machine aspect ratio.
To an extent. Doesn't currently deal with cropping of machines when the window aspect ratio is smaller.
2020-08-14 21:24:25 -04:00
Thomas Harte
230b9fc9e6 Permits multiple simultaneous scan reading ranges.
Also updates the OpenGL scan target as per the latest movements of things.
2020-08-12 22:08:41 -04:00
Thomas Harte
27ca782cac Enables blending; attempts to enable frame preservation.
The latter seems to be evidencing a double buffer at play. More investigation required.

On the plus side, the direct route is still well within GPU budget at 4k on my Core M. So a huge improvement there.
2020-08-12 19:34:07 -04:00
Thomas Harte
a136a00a2f Takes a shot at adding RGB -> S-Video and composite conversion, for all RGB types. 2020-08-11 22:11:50 -04:00
Thomas Harte
637ec35d6a Adds getters for standard colour-space conversion matrices.
These are just more details on the meaning of the colour spaces, so I think they belong here.
2020-08-11 19:58:57 -04:00
Thomas Harte
4b55df1cb4 Starts upon a macro-oriented means of RGB input function generation. 2020-08-10 22:03:39 -04:00
Thomas Harte
b9309268ba Possibly finally succeeds at moving Accelerate.framework to where it should be. 2020-08-10 21:46:11 -04:00
Thomas Harte
8fa89baf54 Slightly cleans up Xcode project; reenables kiosk-for-Mac builds. 2020-08-10 21:43:32 -04:00
Thomas Harte
8374a5e579 Adds superficially correct compositeSampleLuminance8Phase8 function.
Thereby uncovering a minor error in my decoding of colour phase.
2020-08-10 21:33:59 -04:00
Thomas Harte
525233e10b Ensures all input data types are parseable in Metal.
Though now I need to think a bit more about the best way to compose signal-type conversions, and whether output-type calculations (i.e. gamma, brightness) are applied.
2020-08-10 19:47:47 -04:00
Thomas Harte
eadda6a967 Further strips OpenGL from the macOS target. 2020-08-09 22:17:27 -04:00
Thomas Harte
3d6590af89 Throws out a little more OpenGL. 2020-08-09 22:11:31 -04:00
Thomas Harte
28d933d5d6 Does just enough to get 8-bit RGB and 1-bit luminance machines to display.
Assuming an 'RGB' output.
2020-08-09 21:19:07 -04:00
Thomas Harte
c1dc42a094 Add comment on latent design aim.
In the hope that I don't forget why I did this.
2020-08-09 21:18:23 -04:00
Thomas Harte
6384ff3ee7 Add fix for data_type_size_ for owners that don't change texture pointer upon new modals. 2020-08-09 21:17:51 -04:00
Thomas Harte
a118594c8b Hacks to make RGB1 visible (in a fashion). 2020-08-09 20:45:51 -04:00
Thomas Harte
93c6105442 Corrects calculation of dirty texture area. 2020-08-09 20:45:14 -04:00
Thomas Harte
ced4a75a1a Adds note on the buffering scan target's minor adaptation of data_offset. 2020-08-09 20:44:46 -04:00
Thomas Harte
57fecdc09e Ties everything together in an attempt to display RGB scans.
I'm actually just getting a mess of pixels, but it's something!
2020-08-09 18:41:15 -04:00
Thomas Harte
cd491bb6e0 Cleans up project file; macOS 10.13 is definitely the deployment target. 2020-08-09 18:27:57 -04:00
Thomas Harte
f16ad8f71d Takes a shot at submitting texture changes. 2020-08-09 17:59:52 -04:00
Thomas Harte
e340685a99 Seemingly proves that proper geometry is reaching Metal by drawing scans.
No in-buffer accumulation yet, but this is progress. If I can add accumulation and stencil clearing, I'm not doing badly.
2020-08-08 23:11:44 -04:00
Thomas Harte
df89a8771c Makes an attempt to have the emulator fill the actual GPU buffers.
Not that they're drawn from correctly yet. I might first take a run at a new quick-path output route for emulated RGB displays, that just seeks to use the scans directly. No intermediate buffers. Besides probably being a good feature, it'll be a good way to ramp further up with Metal.
2020-08-08 22:49:02 -04:00
Thomas Harte
bdcf266e45 Having learnt a bit more: eliminates Metal attribute tags, switches to more natural expression of structs.
Also thereby eliminates the need for a forced alignas(4) on various structs.
2020-08-08 17:27:32 -04:00
Thomas Harte
edf41b06fd Eliminates the quad buffer.
Vertices can be adduced from vertex ID.
2020-08-08 17:12:49 -04:00
Thomas Harte
38960a08d6 Adds adjustment for display aspect ratio.
While also realising that I appear to be getting away without an MTLVertexDescriptor for Scans. Maybe OpenGL has prejudiced me, and they're actually optional for interleaved data?
2020-08-07 22:29:24 -04:00
Thomas Harte
fbda7aab23 Does just enough to get the correct (aspect ratio aside) output of scan outlines.
So, up next, can I start streaming these things?
2020-08-07 22:20:01 -04:00
Thomas Harte
c575aa0640 Adds a buffer for scans, and posts two test instances. 2020-08-07 22:03:54 -04:00
Thomas Harte
583f6b1ba2 Modifies BufferingScanTarget to allow has-a relationship.
I might switch fully to has-a. Further consideration required.
2020-08-07 22:03:27 -04:00
Thomas Harte
bb55ecc101 Disables --volume for kiosk mode testing. 2020-08-07 21:19:53 -04:00
Thomas Harte
4421acef34 Gets some uniforms in on the action.
With some effort towards scans, but incompletely so.
2020-08-07 21:19:17 -04:00
Thomas Harte
4c9418f59a Guarantees alignof(4) on all GPU-bound structures.
Taken as given: Metal's requirement here is reasonable enough that it'll either be the same as other frameworks, or at least possibly help them down a fast path.
2020-08-07 21:18:08 -04:00
Thomas Harte
219923bd63 Reduces vertex size, draws a quad. 2020-08-05 21:33:25 -04:00
Thomas Harte
7551782a25 Switches to interleaved vertex data.
This more closely relates to what I actually want to do.
2020-08-05 17:27:43 -04:00
Thomas Harte
5c836604c0 Reenable MaserSystem code.
Accidental/poor branch management is evidenced here.
2020-08-04 21:50:54 -04:00
Thomas Harte
eff24a8726 My first baby steps in Metal continue; here's a triangle. 2020-08-04 21:49:01 -04:00
Thomas Harte
72df6e52cd This is possibly at least dispatching an empty command buffer correctly. 2020-08-04 19:44:56 -04:00
Thomas Harte
e235a45abb Breaks all output.
... by switching out NSOpenGLView for MKLView with no drawing infrastructure yet in place.
2020-08-04 18:22:14 -04:00
Thomas Harte
d20c11e401 Merge pull request #831 from TomHarte/MultiKeyboard
Ensures that the MultiKeyboard functions.
2020-07-31 22:08:08 -04:00
Thomas Harte
693b889fdd Ensures that the MultiKeyboard functions. 2020-07-31 21:48:20 -04:00
Thomas Harte
671f48dc10 Merge pull request #830 from TomHarte/MSXCrash
Restores audio to multimachines
2020-07-31 18:31:23 -04:00
Thomas Harte
7b1708f0bc Gets explicit that the delegate_ doesn't need a memory barrier. 2020-07-31 18:21:47 -04:00
Thomas Harte
f34a9b4346 Corrects audio output from the multi-speaker.
Specifically: local duplication of the delegate is unnecessary, and leads to confusion.
2020-07-31 18:18:19 -04:00
Thomas Harte
1e6d03246b Merge pull request #829 from TomHarte/MSXCrash
Ensures proper handover of speaker state when picking in a multimachine.
2020-07-30 23:04:55 -04:00
Thomas Harte
cdde57fcf2 Remove unused code. 2020-07-30 23:02:01 -04:00
Thomas Harte
c0a61ac1ee Ensures proper handover of speaker state when picking in a multimachine. 2020-07-30 22:50:32 -04:00
Thomas Harte
9c97c0a906 Merge pull request #828 from TomHarte/LockFreeQueue
Completes LockFreeQueue branch.
2020-07-30 21:46:56 -04:00
Thomas Harte
8cacab196d Merge branch 'master' into LockFreeQueue 2020-07-30 21:43:25 -04:00
Thomas Harte
b14bedbe29 Merge pull request #817 from TomHarte/LockFreeQueue
Fully splits buffering from drawing for the existing OpenGL scan target.
2020-07-30 21:42:20 -04:00
Thomas Harte
6bc66d8b96 Tidies, ensures ::will_change_owner acquires the producer mutex. 2020-07-29 23:18:03 -04:00
Thomas Harte
23f381f381 Fixes frame_is_complete_, gets rid of active_line_, explains ONE_BIG_LOCK in set_write_area. 2020-07-29 23:03:38 -04:00
Thomas Harte
51ad423eca Resolves off-by-one error in line writing, adds diagnostic one-big-lock option. 2020-07-29 22:45:13 -04:00
Thomas Harte
72a8fef989 Switches to much more straightforward Line/LineMetadata storage.
Spoiler: covering this whole segment behind producer_mutex_ seems to resolve all output issues, so clearly the existing logic isn't functioning correctly. Making it simpler seems like a pretty obvious way to get to the bottom of that.
2020-07-29 21:49:17 -04:00
Thomas Harte
02f41ee513 This has become the general producer mutex, might as well name it as such. 2020-07-29 21:34:07 -04:00
Thomas Harte
9410594486 Merge branch 'master' into LockFreeQueue 2020-07-29 21:22:19 -04:00
Thomas Harte
1c6223cc11 Merge pull request #825 from TomHarte/Microdisc
Gives Qt disk controllers independent ROM/RAM selection logic.
2020-07-29 21:21:29 -04:00
Thomas Harte
82d6a5387f Gives Qt disk controllers independent ROM/RAM selection logic.
In particular, this fixes the Microdisc.
2020-07-29 21:06:41 -04:00
Thomas Harte
5165e65021 Reduces scan_buffer_ to a saner size.
Albeit still probably overspecified.
2020-07-28 22:36:57 -04:00
Thomas Harte
1942742d73 Resolves thread data race on Macintosh audio output. 2020-07-28 22:21:52 -04:00
Thomas Harte
b7760bb052 Reorders code, gets explicit about memory ordering. 2020-07-28 22:02:22 -04:00
Thomas Harte
2470055d90 Hides the modals. 2020-07-27 23:33:39 -04:00
Thomas Harte
62be2a2eec Merge branch 'master' into LockFreeQueue 2020-07-27 23:18:45 -04:00
Thomas Harte
77c5b86acc Moves ownership of the scan and line buffers out of the BufferingScanTarget. 2020-07-26 22:46:03 -04:00
Thomas Harte
bc6426313e Localises three of the four macros. 2020-07-26 17:54:33 -04:00
Thomas Harte
8bef7ff4c5 Makes all three PointerSets and is_updating_ private. 2020-07-26 17:27:19 -04:00
Thomas Harte
f9f500c194 Merge branch 'master' into LockFreeQueue 2020-07-24 22:29:45 -04:00
Thomas Harte
89acb70091 Slightly reorganise. 2020-07-24 16:20:20 -04:00
Thomas Harte
66165a6dea Add missing include files. 2020-07-23 23:24:24 -04:00
Thomas Harte
84dcf9925b Updates Scons and Qt projects to include new files. 2020-07-23 23:14:10 -04:00
Thomas Harte
ee1d7eb61f Makes more buffer-specific stuff private. 2020-07-23 23:06:14 -04:00
Thomas Harte
e260f92988 Privatises write_pointers_mutex_ and write_pointers_.
Also gives subclasses control over write-area texture space allocation.
2020-07-23 22:54:40 -04:00
Thomas Harte
74788ccf8e Pulls the BufferingScanTarget into a separate file. 2020-07-22 22:16:47 -04:00
Thomas Harte
0da5c07942 Starts splitting ring-buffer stuff from OpenGL stuff.
Initially via two very codependent classes.
2020-07-21 22:49:46 -04:00
Thomas Harte
fbe479c43f Switch to saving screenshots to the desktop.
Or, at least, try. User permission would be required. More reading necessary.
2020-01-26 17:36:16 -05:00
337 changed files with 481646 additions and 4178 deletions

13
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,13 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: ['https://www.amazon.com/hz/wishlist/ls/8WPVFLQQDPTA']
# Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View File

@@ -10,12 +10,12 @@
using namespace Analyser::Dynamic;
MultiKeyboardMachine::MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) :
keyboard_(machines_) {
MultiKeyboardMachine::MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
for(const auto &machine: machines) {
auto keyboard_machine = machine->keyboard_machine();
if(keyboard_machine) machines_.push_back(keyboard_machine);
}
keyboard_ = std::make_unique<MultiKeyboard>(machines_);
}
void MultiKeyboardMachine::clear_all_keys() {
@@ -45,7 +45,7 @@ bool MultiKeyboardMachine::can_type(char c) const {
}
Inputs::Keyboard &MultiKeyboardMachine::get_keyboard() {
return keyboard_;
return *keyboard_;
}
MultiKeyboardMachine::MultiKeyboard::MultiKeyboard(const std::vector<::MachineTypes::KeyboardMachine *> &machines)

View File

@@ -42,7 +42,7 @@ class MultiKeyboardMachine: public MachineTypes::KeyboardMachine {
std::set<Key> observed_keys_;
bool is_exclusive_ = false;
};
MultiKeyboard keyboard_;
std::unique_ptr<MultiKeyboard> keyboard_;
public:
MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);

View File

@@ -60,12 +60,9 @@ void MultiSpeaker::set_output_volume(float volume) {
}
}
void MultiSpeaker::set_delegate(Outputs::Speaker::Speaker::Delegate *delegate) {
delegate_ = delegate;
}
void MultiSpeaker::speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) {
if(!delegate_) return;
auto delegate = delegate_.load(std::memory_order::memory_order_relaxed);
if(!delegate) return;
{
std::lock_guard lock_guard(front_speaker_mutex_);
if(speaker != front_speaker_) return;
@@ -74,12 +71,13 @@ void MultiSpeaker::speaker_did_complete_samples(Speaker *speaker, const std::vec
}
void MultiSpeaker::speaker_did_change_input_clock(Speaker *speaker) {
if(!delegate_) return;
auto delegate = delegate_.load(std::memory_order::memory_order_relaxed);
if(!delegate) return;
{
std::lock_guard lock_guard(front_speaker_mutex_);
if(speaker != front_speaker_) return;
}
delegate_->speaker_did_change_input_clock(this);
delegate->speaker_did_change_input_clock(this);
}
void MultiSpeaker::set_new_front_machine(::Machine::DynamicMachine *machine) {
@@ -87,7 +85,8 @@ void MultiSpeaker::set_new_front_machine(::Machine::DynamicMachine *machine) {
std::lock_guard lock_guard(front_speaker_mutex_);
front_speaker_ = machine->audio_producer()->get_speaker();
}
if(delegate_) {
delegate_->speaker_did_change_input_clock(this);
auto delegate = delegate_.load(std::memory_order::memory_order_relaxed);
if(delegate) {
delegate->speaker_did_change_input_clock(this);
}
}

View File

@@ -40,7 +40,6 @@ class MultiSpeaker: public Outputs::Speaker::Speaker, Outputs::Speaker::Speaker:
// Below is the standard Outputs::Speaker::Speaker interface; see there for documentation.
float get_ideal_clock_rate_in_range(float minimum, float maximum) override;
void set_computed_output_rate(float cycles_per_second, int buffer_size, bool stereo) override;
void set_delegate(Outputs::Speaker::Speaker::Delegate *delegate) override;
bool get_is_stereo() override;
void set_output_volume(float) override;
@@ -51,7 +50,6 @@ class MultiSpeaker: public Outputs::Speaker::Speaker, Outputs::Speaker::Speaker:
std::vector<Outputs::Speaker::Speaker *> speakers_;
Outputs::Speaker::Speaker *front_speaker_ = nullptr;
Outputs::Speaker::Speaker::Delegate *delegate_ = nullptr;
std::mutex front_speaker_mutex_;
bool stereo_output_ = false;

View File

@@ -89,9 +89,28 @@ void MultiMachine::did_run_machines(MultiTimedMachine *) {
void MultiMachine::pick_first() {
has_picked_ = true;
// Ensure output rate specifics are properly copied; these may be set only once by the owner,
// but rather than being propagated directly by the MultiSpeaker only the derived computed
// output rate is propagated. So this ensures that if a new derivation is made, it's made correctly.
if(machines_[0]->audio_producer()) {
auto multi_speaker = audio_producer_.get_speaker();
auto specific_speaker = machines_[0]->audio_producer()->get_speaker();
if(specific_speaker && multi_speaker) {
specific_speaker->copy_output_rate(*multi_speaker);
}
}
// TODO: because it is not invalid for a caller to keep a reference to anything previously returned,
// this erase can be added only once the Multi machines that take static copies of the machines list
// are updated.
//
// Example failing use case otherwise: a caller still has reference to the MultiJoystickMachine, and
// it has dangling references to the various JoystickMachines.
//
// This gets into particularly long grass with the MultiConfigurable and its MultiStruct.
// machines_.erase(machines_.begin() + 1, machines_.end());
// TODO: this isn't quite correct, because it may leak OpenGL/etc resources through failure to
// request a close_output while the context is active.
}
void *MultiMachine::raw_pointer() {

View File

@@ -14,6 +14,7 @@ namespace Analyser {
enum class Machine {
AmstradCPC,
AppleII,
AppleIIgs,
Atari2600,
AtariST,
ColecoVision,
@@ -23,7 +24,8 @@ enum class Machine {
MSX,
Oric,
Vic20,
ZX8081
ZX8081,
ZXSpectrum,
};
}

View File

@@ -50,7 +50,10 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::s
new_file.name = name;
new_file.load_address = uint32_t(details->samples[0][file_offset] | (details->samples[0][file_offset+1] << 8) | ((details->samples[0][file_offset+6]&0x0c) << 14));
new_file.execution_address = uint32_t(details->samples[0][file_offset+2] | (details->samples[0][file_offset+3] << 8) | ((details->samples[0][file_offset+6]&0xc0) << 10));
new_file.is_protected = names->samples[0][file_offset + 7] & 0x80;
if(names->samples[0][file_offset + 7] & 0x80) {
// File is locked; it may not be altered or deleted.
new_file.flags |= File::Flags::Locked;
}
long data_length = long(details->samples[0][file_offset+4] | (details->samples[0][file_offset+5] << 8) | ((details->samples[0][file_offset+6]&0x30) << 12));
int start_sector = details->samples[0][file_offset+7] | ((details->samples[0][file_offset+6]&0x03) << 8);
@@ -69,11 +72,16 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::s
new_file.data.insert(new_file.data.end(), next_sector->samples[0].begin(), next_sector->samples[0].begin() + length_from_sector);
data_length -= length_from_sector;
}
if(!data_length) catalogue->files.push_back(new_file);
if(!data_length) catalogue->files.push_back(std::move(new_file));
}
return catalogue;
}
/*
Primary resource used: "Acorn 8-Bit ADFS Filesystem Structure";
http://mdfs.net/Docs/Comp/Disk/Format/ADFS
*/
std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) {
auto catalogue = std::make_unique<Catalogue>();
Storage::Encodings::MFM::Parser parser(true, disk);
@@ -101,5 +109,73 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::
case 3: catalogue->bootOption = Catalogue::BootOption::ExecBOOT; break;
}
// Parse the root directory, at least.
for(std::size_t file_offset = 0x005; file_offset < 0x4cb; file_offset += 0x1a) {
// Obtain the name, which will be at most ten characters long, and will
// be terminated by either a NULL character or a \r.
char name[11];
std::size_t c = 0;
for(; c < 10; c++) {
const char next = root_directory[file_offset + c] & 0x7f;
name[c] = next;
if(next == '\0' || next == '\r') break;
}
name[c] = '\0';
// Skip if the name is empty.
if(name[0] == '\0') continue;
// Populate a file then.
File new_file;
new_file.name = name;
new_file.flags =
(root_directory[file_offset + 0] & 0x80 ? File::Flags::Readable : 0) |
(root_directory[file_offset + 1] & 0x80 ? File::Flags::Writable : 0) |
(root_directory[file_offset + 2] & 0x80 ? File::Flags::Locked : 0) |
(root_directory[file_offset + 3] & 0x80 ? File::Flags::IsDirectory : 0) |
(root_directory[file_offset + 4] & 0x80 ? File::Flags::ExecuteOnly : 0) |
(root_directory[file_offset + 5] & 0x80 ? File::Flags::PubliclyReadable : 0) |
(root_directory[file_offset + 6] & 0x80 ? File::Flags::PubliclyWritable : 0) |
(root_directory[file_offset + 7] & 0x80 ? File::Flags::PubliclyExecuteOnly : 0) |
(root_directory[file_offset + 8] & 0x80 ? File::Flags::IsPrivate : 0);
new_file.load_address =
(uint32_t(root_directory[file_offset + 0x0a]) << 0) |
(uint32_t(root_directory[file_offset + 0x0b]) << 8) |
(uint32_t(root_directory[file_offset + 0x0c]) << 16) |
(uint32_t(root_directory[file_offset + 0x0d]) << 24);
new_file.execution_address =
(uint32_t(root_directory[file_offset + 0x0e]) << 0) |
(uint32_t(root_directory[file_offset + 0x0f]) << 8) |
(uint32_t(root_directory[file_offset + 0x10]) << 16) |
(uint32_t(root_directory[file_offset + 0x11]) << 24);
new_file.sequence_number = root_directory[file_offset + 0x19];
const uint32_t size =
(uint32_t(root_directory[file_offset + 0x12]) << 0) |
(uint32_t(root_directory[file_offset + 0x13]) << 8) |
(uint32_t(root_directory[file_offset + 0x14]) << 16) |
(uint32_t(root_directory[file_offset + 0x15]) << 24);
uint32_t start_sector =
(uint32_t(root_directory[file_offset + 0x16]) << 0) |
(uint32_t(root_directory[file_offset + 0x17]) << 8) |
(uint32_t(root_directory[file_offset + 0x18]) << 16);
new_file.data.reserve(size);
while(new_file.data.size() < size) {
const Storage::Encodings::MFM::Sector *const sector = parser.get_sector(start_sector / (80 * 16), (start_sector / 16) % 80, start_sector % 16);
if(!sector) break;
const auto length_from_sector = std::min(size - new_file.data.size(), sector->samples[0].size());
new_file.data.insert(new_file.data.end(), sector->samples[0].begin(), sector->samples[0].begin() + ssize_t(length_from_sector));
++start_sector;
}
catalogue->files.push_back(std::move(new_file));
}
return catalogue;
}

View File

@@ -19,19 +19,38 @@ namespace Acorn {
struct File {
std::string name;
uint32_t load_address;
uint32_t execution_address;
bool is_protected;
uint32_t load_address = 0;
uint32_t execution_address = 0;
enum Flags: uint16_t {
Readable = 1 << 0,
Writable = 1 << 1,
Locked = 1 << 2,
IsDirectory = 1 << 3,
ExecuteOnly = 1 << 4,
PubliclyReadable = 1 << 5,
PubliclyWritable = 1 << 6,
PubliclyExecuteOnly = 1 << 7,
IsPrivate = 1 << 8,
};
uint16_t flags = Flags::Readable | Flags::Readable | Flags::PubliclyReadable | Flags::PubliclyWritable;
uint8_t sequence_number = 0;
std::vector<uint8_t> data;
/// Describes a single chunk of file data; these relate to the tape and ROM filing system.
/// The File-level fields contain a 'definitive' version of the load and execution addresses,
/// but both of those filing systems also store them per chunk.
///
/// Similarly, the file-level data will contain the aggregate data of all chunks.
struct Chunk {
std::string name;
uint32_t load_address;
uint32_t execution_address;
uint16_t block_number;
uint16_t block_length;
uint8_t block_flag;
uint32_t next_address;
uint32_t load_address = 0;
uint32_t execution_address = 0;
uint16_t block_number = 0;
uint16_t block_length = 0;
uint32_t next_address = 0;
uint8_t block_flag = 0;
bool header_crc_matched;
bool data_crc_matched;

View File

@@ -12,6 +12,8 @@
#include "Tape.hpp"
#include "Target.hpp"
#include <algorithm>
using namespace Analyser::Static::Acorn;
static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
@@ -59,10 +61,6 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
auto target = std::make_unique<Target>();
target->confidence = 0.5; // TODO: a proper estimation
target->has_dfs = false;
target->has_adfs = false;
target->should_shift_restart = false;
// strip out inappropriate cartridges
target->media.cartridges = AcornCartridgesFrom(media.cartridges);
@@ -77,8 +75,8 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
if(!files.empty()) {
bool is_basic = true;
// protected files are always for *RUNning only
if(files.front().is_protected) is_basic = false;
// If a file is execute-only, that means *RUN.
if(files.front().flags & File::Flags::ExecuteOnly) is_basic = false;
// check also for a continuous threading of BASIC lines; if none then this probably isn't BASIC code,
// so that's also justification to *RUN
@@ -108,15 +106,60 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
dfs_catalogue = GetDFSCatalogue(disk);
if(dfs_catalogue == nullptr) adfs_catalogue = GetADFSCatalogue(disk);
if(dfs_catalogue || adfs_catalogue) {
// Accept the disk and determine whether DFS or ADFS ROMs are implied.
// Use the Pres ADFS if using an ADFS, as it leaves Page at &EOO.
target->media.disks = media.disks;
target->has_dfs = !!dfs_catalogue;
target->has_adfs = !!adfs_catalogue;
target->has_dfs = bool(dfs_catalogue);
target->has_pres_adfs = bool(adfs_catalogue);
// Check whether a simple shift+break will do for loading this disk.
Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption;
if(bootOption != Catalogue::BootOption::None)
if(bootOption != Catalogue::BootOption::None) {
target->should_shift_restart = true;
else
} else {
target->loading_command = "*CAT\n";
}
// Check whether adding the AP6 ROM is justified.
// For now this is an incredibly dense text search;
// if any of the commands that aren't usually present
// on a stock Electron are here, add the AP6 ROM and
// some sideways RAM such that the SR commands are useful.
for(const auto &file: dfs_catalogue ? dfs_catalogue->files : adfs_catalogue->files) {
for(const auto &command: {
"AQRPAGE", "BUILD", "DUMP", "FORMAT", "INSERT", "LANG", "LIST", "LOADROM",
"LOCK", "LROMS", "RLOAD", "ROMS", "RSAVE", "SAVEROM", "SRLOAD", "SRPAGE",
"SRUNLOCK", "SRWIPE", "TUBE", "TYPE", "UNLOCK", "UNPLUG", "UROMS",
"VERIFY", "ZERO"
}) {
if(std::search(file.data.begin(), file.data.end(), command, command+strlen(command)) != file.data.end()) {
target->has_ap6_rom = true;
target->has_sideways_ram = true;
}
}
}
}
}
// Enable the Acorn ADFS if a mass-storage device is attached;
// unlike the Pres ADFS it retains SCSI logic.
if(!media.mass_storage_devices.empty()) {
target->has_pres_adfs = false; // To override a floppy selection, if one was made.
target->has_acorn_adfs = true;
// Assume some sort of later-era Acorn work is likely to happen;
// so ensure *TYPE, etc are present.
target->has_ap6_rom = true;
target->has_sideways_ram = true;
target->media.mass_storage_devices = media.mass_storage_devices;
// Check for a boot option.
const auto sector = target->media.mass_storage_devices.front()->get_block(1);
if(sector[0xfd]) {
target->should_shift_restart = true;
} else {
target->loading_command = "*CAT\n";
}
}

View File

@@ -109,7 +109,12 @@ static std::unique_ptr<File> GetNextFile(std::deque<File::Chunk> &chunks) {
file->name = file->chunks.front().name;
file->load_address = file->chunks.front().load_address;
file->execution_address = file->chunks.front().execution_address;
file->is_protected = !!(file->chunks.back().block_flag & 0x01); // I think the last flags are the ones that count; TODO: check.
// I think the final chunk's flags are the ones that count; TODO: check.
if(file->chunks.back().block_flag & 0x01) {
// File is locked, which in more generalised terms means it is
// for execution only.
file->flags |= File::Flags::ExecuteOnly;
}
// copy all data into a single big block
for(File::Chunk chunk : file->chunks) {

View File

@@ -18,15 +18,21 @@ namespace Static {
namespace Acorn {
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
bool has_adfs = false;
bool has_acorn_adfs = false;
bool has_pres_adfs = false;
bool has_dfs = false;
bool has_ap6_rom = false;
bool has_sideways_ram = false;
bool should_shift_restart = false;
std::string loading_command;
Target() : Analyser::Static::Target(Machine::Electron) {
if(needs_declare()) {
DeclareField(has_adfs);
DeclareField(has_pres_adfs);
DeclareField(has_acorn_adfs);
DeclareField(has_dfs);
DeclareField(has_ap6_rom);
DeclareField(has_sideways_ram);
}
}
};

View File

@@ -11,12 +11,15 @@
#include <algorithm>
#include <cstring>
#include "Target.hpp"
#include "../../../Storage/Disk/Parsers/CPM.hpp"
#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
#include "../../../Storage/Tape/Parsers/Spectrum.hpp"
static bool strcmp_insensitive(const char *a, const char *b) {
#include "Target.hpp"
namespace {
bool strcmp_insensitive(const char *a, const char *b) {
if(std::strlen(a) != std::strlen(b)) return false;
while(*a) {
if(std::tolower(*a) != std::tolower(*b)) return false;
@@ -26,20 +29,20 @@ static bool strcmp_insensitive(const char *a, const char *b) {
return true;
}
static bool is_implied_extension(const std::string &extension) {
bool is_implied_extension(const std::string &extension) {
return
extension == " " ||
strcmp_insensitive(extension.c_str(), "BAS") ||
strcmp_insensitive(extension.c_str(), "BIN");
}
static void right_trim(std::string &string) {
void right_trim(std::string &string) {
string.erase(std::find_if(string.rbegin(), string.rend(), [](int ch) {
return !std::isspace(ch);
}).base(), string.end());
}
static std::string RunCommandFor(const Storage::Disk::CPM::File &file) {
std::string RunCommandFor(const Storage::Disk::CPM::File &file) {
// Trim spaces from the name.
std::string name = file.name;
right_trim(name);
@@ -58,7 +61,7 @@ static std::string RunCommandFor(const Storage::Disk::CPM::File &file) {
return command + "\n";
}
static void InspectCatalogue(
void InspectCatalogue(
const Storage::Disk::CPM::Catalogue &catalogue,
const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target) {
@@ -155,7 +158,7 @@ static void InspectCatalogue(
target->loading_command = "cat\n";
}
static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target) {
bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target) {
Storage::Encodings::MFM::Parser parser(true, disk);
Storage::Encodings::MFM::Sector *boot_sector = parser.get_sector(0, 0, 0x41);
if(boot_sector != nullptr && !boot_sector->samples.empty() && boot_sector->samples[0].size() == 512) {
@@ -179,6 +182,28 @@ static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, co
return false;
}
bool IsAmstradTape(const std::shared_ptr<Storage::Tape::Tape> &tape) {
// Limited sophistication here; look for a CPC-style file header, that is
// any Spectrum-esque block with a synchronisation character of 0x2c.
//
// More could be done here: parse the header, look for 0x16 data records.
using Parser = Storage::Tape::ZXSpectrum::Parser;
Parser parser(Parser::MachineType::AmstradCPC);
while(true) {
const auto block = parser.find_block(tape);
if(!block) break;
if(block->type == 0x2c) {
return true;
}
}
return false;
}
} // namespace
Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
TargetList destination;
auto target = std::make_unique<Target>();
@@ -187,13 +212,19 @@ Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Medi
target->model = Target::Model::CPC6128;
if(!media.tapes.empty()) {
// TODO: which of these are actually potentially CPC tapes?
target->media.tapes = media.tapes;
bool has_cpc_tape = false;
for(auto &tape: media.tapes) {
has_cpc_tape |= IsAmstradTape(tape);
}
// Ugliness flows here: assume the CPC isn't smart enough to pause between pressing
// enter and responding to the follow-on prompt to press a key, so just type for
// a while. Yuck!
target->loading_command = "|tape\nrun\"\n1234567890";
if(has_cpc_tape) {
target->media.tapes = media.tapes;
// Ugliness flows here: assume the CPC isn't smart enough to pause between pressing
// enter and responding to the follow-on prompt to press a key, so just type for
// a while. Yuck!
target->loading_command = "|tape\nrun\"\n123";
}
}
if(!media.disks.empty()) {

View File

@@ -6,8 +6,8 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#ifndef Target_h
#define Target_h
#ifndef Analyser_Static_AppleII_Target_h
#define Analyser_Static_AppleII_Target_h
#include "../../../Reflection/Enum.hpp"
#include "../../../Reflection/Struct.hpp"
@@ -47,4 +47,4 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
}
}
#endif /* Target_h */
#endif /* Analyser_Static_AppleII_Target_h */

View File

@@ -0,0 +1,19 @@
//
// StaticAnalyser.cpp
// Clock Signal
//
// Created by Thomas Harte on 20/10/2020.
// Copyright 2018 Thomas Harte. All rights reserved.
//
#include "StaticAnalyser.hpp"
#include "Target.hpp"
Analyser::Static::TargetList Analyser::Static::AppleIIgs::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
auto target = std::make_unique<Target>();
target->media = media;
TargetList targets;
targets.push_back(std::move(target));
return targets;
}

View File

@@ -0,0 +1,26 @@
//
// StaticAnalyser.hpp
// Clock Signal
//
// Created by Thomas Harte on 20/10/2020.
// Copyright 2018 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_AppleIIgs_StaticAnalyser_hpp
#define Analyser_Static_AppleIIgs_StaticAnalyser_hpp
#include "../StaticAnalyser.hpp"
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser {
namespace Static {
namespace AppleIIgs {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}
}
}
#endif /* Analyser_Static_AppleIIgs_StaticAnalyser_hpp */

View File

@@ -0,0 +1,49 @@
//
// Target.hpp
// Clock Signal
//
// Created by Thomas Harte on 20/10/2020.
// Copyright 2018 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_AppleIIgs_Target_h
#define Analyser_Static_AppleIIgs_Target_h
#include "../../../Reflection/Enum.hpp"
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
namespace Analyser {
namespace Static {
namespace AppleIIgs {
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(Model,
ROM00,
ROM01,
ROM03
);
ReflectableEnum(MemoryModel,
TwoHundredAndFiftySixKB,
OneMB,
EightMB
);
Model model = Model::ROM03;
MemoryModel memory_model = MemoryModel::EightMB;
Target() : Analyser::Static::Target(Machine::AppleIIgs) {
if(needs_declare()) {
DeclareField(model);
DeclareField(memory_model);
AnnounceEnum(Model);
AnnounceEnum(MemoryModel);
}
}
};
}
}
}
#endif /* Analyser_Static_AppleIIgs_Target_h */

View File

@@ -9,6 +9,7 @@
#include "StaticAnalyser.hpp"
#include "../AppleII/Target.hpp"
#include "../AppleIIgs/Target.hpp"
#include "../Oric/Target.hpp"
#include "../Disassembler/6502.hpp"
#include "../Disassembler/AddressMapper.hpp"
@@ -18,7 +19,7 @@
namespace {
Analyser::Static::Target *AppleTarget(const Storage::Encodings::AppleGCR::Sector *sector_zero) {
Analyser::Static::Target *AppleIITarget(const Storage::Encodings::AppleGCR::Sector *sector_zero) {
using Target = Analyser::Static::AppleII::Target;
auto *const target = new Target;
@@ -31,6 +32,10 @@ Analyser::Static::Target *AppleTarget(const Storage::Encodings::AppleGCR::Sector
return target;
}
Analyser::Static::Target *AppleIIgsTarget() {
return new Analyser::Static::AppleIIgs::Target();
}
Analyser::Static::Target *OricTarget(const Storage::Encodings::AppleGCR::Sector *) {
using Target = Analyser::Static::Oric::Target;
auto *const target = new Target;
@@ -46,8 +51,18 @@ Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(const Media &m
// This analyser can comprehend disks only.
if(media.disks.empty()) return {};
auto &disk = media.disks.front();
TargetList targets;
// If the disk image is too large for a 5.25" disk, map this to the IIgs.
if(disk->get_maximum_head_position() > Storage::Disk::HeadPosition(40)) {
targets.push_back(std::unique_ptr<Analyser::Static::Target>(AppleIIgsTarget()));
targets.back()->media = media;
return targets;
}
// Grab track 0, sector 0: the boot sector.
const auto track_zero = media.disks.front()->get_track_at_position(Storage::Disk::Track::Address(0, Storage::Disk::HeadPosition(0)));
const auto track_zero = disk->get_track_at_position(Storage::Disk::Track::Address(0, Storage::Disk::HeadPosition(0)));
const auto sector_map = Storage::Encodings::AppleGCR::sectors_from_segment(
Storage::Disk::track_serialisation(*track_zero, Storage::Time(1, 50000)));
@@ -61,12 +76,11 @@ Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(const Media &m
// If there's no boot sector then if there are also no sectors at all,
// decline to nominate a machine. Otherwise go with an Apple as the default.
TargetList targets;
if(!sector_zero) {
if(sector_map.empty()) {
return targets;
} else {
targets.push_back(std::unique_ptr<Analyser::Static::Target>(AppleTarget(nullptr)));
targets.push_back(std::unique_ptr<Analyser::Static::Target>(AppleIITarget(nullptr)));
targets.back()->media = media;
return targets;
}
@@ -116,7 +130,7 @@ Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(const Media &m
if(is_oric) {
targets.push_back(std::unique_ptr<Analyser::Static::Target>(OricTarget(sector_zero)));
} else {
targets.push_back(std::unique_ptr<Analyser::Static::Target>(AppleTarget(sector_zero)));
targets.push_back(std::unique_ptr<Analyser::Static::Target>(AppleIITarget(sector_zero)));
}
targets.back()->media = media;
return targets;

View File

@@ -19,6 +19,19 @@ Analyser::Static::TargetList Analyser::Static::Macintosh::GetTargets(const Media
using Target = Analyser::Static::Macintosh::Target;
auto *const target = new Target;
target->media = media;
// If this is a single-sided floppy disk, guess the Macintosh 512kb.
if(media.mass_storage_devices.empty()) {
bool has_800kb_disks = false;
for(const auto &disk: media.disks) {
has_800kb_disks |= disk->get_head_count() > 1;
}
if(!has_800kb_disks) {
target->model = Target::Model::Mac512k;
}
}
targets.push_back(std::unique_ptr<Analyser::Static::Target>(target));
return targets;

View File

@@ -33,8 +33,14 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
BD500
);
ReflectableEnum(Processor,
MOS6502,
WDC65816
);
ROM rom = ROM::BASIC11;
DiskInterface disk_interface = DiskInterface::None;
Processor processor = Processor::MOS6502;
std::string loading_command;
bool should_start_jasmin = false;
@@ -42,8 +48,10 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
if(needs_declare()) {
DeclareField(rom);
DeclareField(disk_interface);
DeclareField(processor);
AnnounceEnum(ROM);
AnnounceEnum(DiskInterface);
AnnounceEnum(Processor);
}
}
};

View File

@@ -17,6 +17,7 @@
#include "Acorn/StaticAnalyser.hpp"
#include "AmstradCPC/StaticAnalyser.hpp"
#include "AppleII/StaticAnalyser.hpp"
#include "AppleIIgs/StaticAnalyser.hpp"
#include "Atari2600/StaticAnalyser.hpp"
#include "AtariST/StaticAnalyser.hpp"
#include "Coleco/StaticAnalyser.hpp"
@@ -27,12 +28,14 @@
#include "Oric/StaticAnalyser.hpp"
#include "Sega/StaticAnalyser.hpp"
#include "ZX8081/StaticAnalyser.hpp"
#include "ZXSpectrum/StaticAnalyser.hpp"
// Cartridges
#include "../../Storage/Cartridge/Formats/BinaryDump.hpp"
#include "../../Storage/Cartridge/Formats/PRG.hpp"
// Disks
#include "../../Storage/Disk/DiskImage/Formats/2MG.hpp"
#include "../../Storage/Disk/DiskImage/Formats/AcornADF.hpp"
#include "../../Storage/Disk/DiskImage/Formats/AppleDSK.hpp"
#include "../../Storage/Disk/DiskImage/Formats/CPCDSK.hpp"
@@ -51,6 +54,7 @@
#include "../../Storage/Disk/DiskImage/Formats/WOZ.hpp"
// Mass Storage Devices (i.e. usually, hard disks)
#include "../../Storage/MassStorage/Formats/DAT.hpp"
#include "../../Storage/MassStorage/Formats/HFV.hpp"
// Tapes
@@ -62,6 +66,7 @@
#include "../../Storage/Tape/Formats/TapeUEF.hpp"
#include "../../Storage/Tape/Formats/TZX.hpp"
#include "../../Storage/Tape/Formats/ZX80O81P.hpp"
#include "../../Storage/Tape/Formats/ZXSpectrumTAP.hpp"
// Target Platform Types
#include "../../Storage/TargetPlatforms.hpp"
@@ -78,36 +83,52 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
std::string extension = file_name.substr(final_dot + 1);
std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
#define Insert(list, class, platforms) \
list.emplace_back(new Storage::class(file_name));\
#define InsertInstance(list, instance, platforms) \
list.emplace_back(instance);\
potential_platforms |= platforms;\
TargetPlatform::TypeDistinguisher *distinguisher = dynamic_cast<TargetPlatform::TypeDistinguisher *>(list.back().get());\
if(distinguisher) potential_platforms &= distinguisher->target_platform_type();
if(distinguisher) potential_platforms &= distinguisher->target_platform_type(); \
#define TryInsert(list, class, platforms) \
#define Insert(list, class, platforms, ...) \
InsertInstance(list, new Storage::class(__VA_ARGS__), platforms);
#define TryInsert(list, class, platforms, ...) \
try {\
Insert(list, class, platforms) \
Insert(list, class, platforms, __VA_ARGS__) \
} catch(...) {}
#define Format(ext, list, class, platforms) \
if(extension == ext) { \
TryInsert(list, class, platforms) \
TryInsert(list, class, platforms, file_name) \
}
// 2MG
if(extension == "2mg") {
// 2MG uses a factory method; defer to it.
try {
InsertInstance(result.disks, Storage::Disk::Disk2MG::open(file_name), TargetPlatform::DiskII)
} catch(...) {}
}
Format("80", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 80
Format("81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 81
Format("a26", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // A26
Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn) // ADF
Format("adl", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn) // ADL
Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::AllCartridge) // BIN (cartridge dump)
Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX) // CAS
Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC) // CDT
Format("col", result.cartridges, Cartridge::BinaryDump, TargetPlatform::ColecoVision) // COL
Format("col", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Coleco) // COL
Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape) // CSW
Format("d64", result.disks, Disk::DiskImageHolder<Storage::Disk::D64>, TargetPlatform::Commodore) // D64
Format("dat", result.mass_storage_devices, MassStorage::DAT, TargetPlatform::Acorn) // DAT
Format("dmk", result.disks, Disk::DiskImageHolder<Storage::Disk::DMK>, TargetPlatform::MSX) // DMK
Format("do", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // DO
Format("dsd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // DSD
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::CPCDSK>, TargetPlatform::AmstradCPC) // DSK (Amstrad CPC)
Format( "dsk",
result.disks,
Disk::DiskImageHolder<Storage::Disk::CPCDSK>,
TargetPlatform::AmstradCPC | TargetPlatform::Oric | TargetPlatform::ZXSpectrum) // DSK (Amstrad CPC, etc)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // DSK (Apple II)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // DSK (Macintosh, floppy disk)
Format("dsk", result.mass_storage_devices, MassStorage::HFV, TargetPlatform::Macintosh) // DSK (Macintosh, hard disk)
@@ -117,7 +138,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
Format( "hfe",
result.disks,
Disk::DiskImageHolder<Storage::Disk::HFE>,
TargetPlatform::Acorn | TargetPlatform::AmstradCPC | TargetPlatform::Commodore | TargetPlatform::Oric)
TargetPlatform::Acorn | TargetPlatform::AmstradCPC | TargetPlatform::Commodore | TargetPlatform::Oric | TargetPlatform::ZXSpectrum)
// HFE (TODO: switch to AllDisk once the MSX stops being so greedy)
Format("img", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // IMG (DiskCopy 4.2)
Format("image", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // IMG (DiskCopy 4.2)
@@ -125,17 +146,23 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
Format("nib", result.disks, Disk::DiskImageHolder<Storage::Disk::NIB>, TargetPlatform::DiskII) // NIB
Format("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // O
Format("p", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P
Format("po", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // PO
Format("po", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // PO (original Apple II kind)
// PO (Apple IIgs kind)
if(extension == "po") {
TryInsert(result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::AppleIIgs, file_name, Storage::Disk::MacintoshIMG::FixedType::GCR)
}
Format("p81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P81
// PRG
if(extension == "prg") {
// try instantiating as a ROM; failing that accept as a tape
try {
Insert(result.cartridges, Cartridge::PRG, TargetPlatform::Commodore)
Insert(result.cartridges, Cartridge::PRG, TargetPlatform::Commodore, file_name)
} catch(...) {
try {
Insert(result.tapes, Tape::PRG, TargetPlatform::Commodore)
Insert(result.tapes, Tape::PRG, TargetPlatform::Commodore, file_name)
} catch(...) {}
}
}
@@ -143,7 +170,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
Format( "rom",
result.cartridges,
Cartridge::BinaryDump,
TargetPlatform::AcornElectron | TargetPlatform::ColecoVision | TargetPlatform::MSX) // ROM
TargetPlatform::AcornElectron | TargetPlatform::Coleco | TargetPlatform::MSX) // ROM
Format("sg", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega) // SG
Format("sms", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega) // SMS
Format("ssd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // SSD
@@ -151,14 +178,16 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
Format("stx", result.disks, Disk::DiskImageHolder<Storage::Disk::STX>, TargetPlatform::AtariST) // STX
Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP (Commodore)
Format("tap", result.tapes, Tape::OricTAP, TargetPlatform::Oric) // TAP (Oric)
Format("tap", result.tapes, Tape::ZXSpectrumTAP, TargetPlatform::ZXSpectrum) // TAP (ZX Spectrum)
Format("tsx", result.tapes, Tape::TZX, TargetPlatform::MSX) // TSX
Format("tzx", result.tapes, Tape::TZX, TargetPlatform::ZX8081) // TZX
Format("tzx", result.tapes, Tape::TZX, TargetPlatform::ZX8081 | TargetPlatform::ZXSpectrum) // TZX
Format("uef", result.tapes, Tape::UEF, TargetPlatform::Acorn) // UEF (tape)
Format("woz", result.disks, Disk::DiskImageHolder<Storage::Disk::WOZ>, TargetPlatform::DiskII) // WOZ
#undef Format
#undef Insert
#undef TryInsert
#undef InsertInstance
return result;
}
@@ -178,26 +207,28 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) {
// Hand off to platform-specific determination of whether these things are actually compatible and,
// if so, how to load them.
#define Append(x) {\
auto new_targets = x::GetTargets(media, file_name, potential_platforms);\
std::move(new_targets.begin(), new_targets.end(), std::back_inserter(targets));\
}
if(potential_platforms & TargetPlatform::Acorn) Append(Acorn);
if(potential_platforms & TargetPlatform::AmstradCPC) Append(AmstradCPC);
if(potential_platforms & TargetPlatform::AppleII) Append(AppleII);
if(potential_platforms & TargetPlatform::Atari2600) Append(Atari2600);
if(potential_platforms & TargetPlatform::AtariST) Append(AtariST);
if(potential_platforms & TargetPlatform::ColecoVision) Append(Coleco);
if(potential_platforms & TargetPlatform::Commodore) Append(Commodore);
if(potential_platforms & TargetPlatform::DiskII) Append(DiskII);
if(potential_platforms & TargetPlatform::Macintosh) Append(Macintosh);
if(potential_platforms & TargetPlatform::MSX) Append(MSX);
if(potential_platforms & TargetPlatform::Oric) Append(Oric);
if(potential_platforms & TargetPlatform::Sega) Append(Sega);
if(potential_platforms & TargetPlatform::ZX8081) Append(ZX8081);
#undef Append
#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));\
}
Append(Acorn);
Append(AmstradCPC);
Append(AppleII);
Append(AppleIIgs);
Append(Atari2600);
Append(AtariST);
Append(Coleco);
Append(Commodore);
Append(DiskII);
Append(Macintosh);
Append(MSX);
Append(Oric);
Append(Sega);
Append(ZX8081);
Append(ZXSpectrum);
#undef Append
// Reset any tapes to their initial position
// Reset any tapes to their initial position.
for(const auto &target : targets) {
for(auto &tape : target->media.tapes) {
tape->reset();

View File

@@ -0,0 +1,95 @@
//
// StaticAnalyser.cpp
// Clock Signal
//
// Created by Thomas Harte on 17/03/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "StaticAnalyser.hpp"
#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
#include "../../../Storage/Tape/Parsers/Spectrum.hpp"
#include "Target.hpp"
namespace {
bool IsSpectrumTape(const std::shared_ptr<Storage::Tape::Tape> &tape) {
using Parser = Storage::Tape::ZXSpectrum::Parser;
Parser parser(Parser::MachineType::ZXSpectrum);
while(true) {
const auto block = parser.find_block(tape);
if(!block) break;
// Check for a Spectrum header block.
if(block->type == 0x00) {
return true;
}
}
return false;
}
bool IsSpectrumDisk(const std::shared_ptr<Storage::Disk::Disk> &disk) {
Storage::Encodings::MFM::Parser parser(true, disk);
// Get logical sector 1; the Spectrum appears to support various physical
// sectors as sector 1.
Storage::Encodings::MFM::Sector *boot_sector = nullptr;
uint8_t sector_mask = 0;
while(!boot_sector) {
boot_sector = parser.get_sector(0, 0, sector_mask + 1);
sector_mask += 0x40;
if(!sector_mask) break;
}
if(!boot_sector) return false;
// Test that the contents of the boot sector sum to 3, modulo 256.
uint8_t byte_sum = 0;
for(auto byte: boot_sector->samples[0]) {
byte_sum += byte;
}
return byte_sum == 3;
}
}
Analyser::Static::TargetList Analyser::Static::ZXSpectrum::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
TargetList destination;
auto target = std::make_unique<Target>();
target->confidence = 0.5;
if(!media.tapes.empty()) {
bool has_spectrum_tape = false;
for(auto &tape: media.tapes) {
has_spectrum_tape |= IsSpectrumTape(tape);
}
if(has_spectrum_tape) {
target->media.tapes = media.tapes;
}
}
if(!media.disks.empty()) {
bool has_spectrum_disk = false;
for(auto &disk: media.disks) {
has_spectrum_disk |= IsSpectrumDisk(disk);
}
if(has_spectrum_disk) {
target->media.disks = media.disks;
target->model = Target::Model::Plus3;
}
}
// If any media survived, add the target.
if(!target->media.empty()) {
target->should_hold_enter = true; // To force entry into the 'loader' and thereby load the media.
destination.push_back(std::move(target));
}
return destination;
}

View File

@@ -0,0 +1,26 @@
//
// StaticAnalyser.hpp
// Clock Signal
//
// Created by Thomas Harte on 17/03/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_ZXSpectrum_StaticAnalyser_hpp
#define Analyser_Static_ZXSpectrum_StaticAnalyser_hpp
#include "../StaticAnalyser.hpp"
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser {
namespace Static {
namespace ZXSpectrum {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}
}
}
#endif /* StaticAnalyser_hpp */

View File

@@ -0,0 +1,45 @@
//
// Target.hpp
// Clock Signal
//
// Created by Thomas Harte on 18/03/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_ZXSpectrum_Target_h
#define Analyser_Static_ZXSpectrum_Target_h
#include "../../../Reflection/Enum.hpp"
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
namespace Analyser {
namespace Static {
namespace ZXSpectrum {
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(Model,
SixteenK,
FortyEightK,
OneTwoEightK,
Plus2,
Plus2a,
Plus3,
);
Model model = Model::Plus2;
bool should_hold_enter = false;
Target(): Analyser::Static::Target(Machine::ZXSpectrum) {
if(needs_declare()) {
DeclareField(model);
AnnounceEnum(Model);
}
}
};
}
}
}
#endif /* Target_h */

View File

@@ -10,7 +10,9 @@
#define ClockReceiver_hpp
#include "ForceInline.hpp"
#include <cstdint>
#include <limits>
/*
Informal pattern for all classes that run from a clock cycle:
@@ -176,6 +178,9 @@ class Cycles: public WrappedInt<Cycles> {
public:
forceinline constexpr Cycles(IntType l) noexcept : WrappedInt<Cycles>(l) {}
forceinline constexpr Cycles() noexcept : WrappedInt<Cycles>() {}
forceinline static constexpr Cycles max() {
return Cycles(std::numeric_limits<IntType>::max());
}
private:
friend WrappedInt;
@@ -195,6 +200,9 @@ class HalfCycles: public WrappedInt<HalfCycles> {
public:
forceinline constexpr HalfCycles(IntType l) noexcept : WrappedInt<HalfCycles>(l) {}
forceinline constexpr HalfCycles() noexcept : WrappedInt<HalfCycles>() {}
forceinline static constexpr HalfCycles max() {
return HalfCycles(std::numeric_limits<IntType>::max());
}
forceinline constexpr HalfCycles(const Cycles &cycles) noexcept : WrappedInt<HalfCycles>(cycles.as_integral() * 2) {}

View File

@@ -10,6 +10,7 @@
#define JustInTime_h
#include "../Concurrency/AsyncTaskQueue.hpp"
#include "ClockingHintSource.hpp"
#include "ForceInline.hpp"
/*!
@@ -21,44 +22,142 @@
Machines that accumulate HalfCycle time but supply to a Cycle-counted device may supply a
separate @c TargetTimeScale at template declaration.
If the held object implements get_next_sequence_point() then it'll be used to flush implicitly
as and when sequence points are hit. Callers can use will_flush() to predict these.
If the held object is a subclass of ClockingHint::Source, this template will register as an
observer and potentially stop clocking or stop delaying clocking until just-in-time references
as directed.
TODO: incorporate and codify AsyncJustInTimeActor.
*/
template <class T, int multiplier = 1, int divider = 1, class LocalTimeScale = HalfCycles, class TargetTimeScale = LocalTimeScale> class JustInTimeActor {
template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int divider = 1> class JustInTimeActor:
public ClockingHint::Observer {
private:
/*!
A std::unique_ptr deleter which causes an update_sequence_point to occur on the actor supplied
to it at construction if it implements get_next_sequence_point(). Otherwise destruction is a no-op.
**Does not delete the object.**
This is used by the -> operators below, which provide a unique pointer to the enclosed object and
update their sequence points upon its destruction — i.e. after the caller has made whatever call
or calls as were relevant to the enclosed object.
*/
class SequencePointAwareDeleter {
public:
explicit SequencePointAwareDeleter(JustInTimeActor<T, LocalTimeScale, multiplier, divider> *actor) noexcept
: actor_(actor) {}
forceinline void operator ()(const T *const) const {
if constexpr (has_sequence_points<T>::value) {
actor_->update_sequence_point();
}
}
private:
JustInTimeActor<T, LocalTimeScale, multiplier, divider> *const actor_;
};
// This block of SFINAE determines whether objects of type T accepts Cycles or HalfCycles.
using HalfRunFor = void (T::*const)(HalfCycles);
static uint8_t half_sig(...);
static uint16_t half_sig(HalfRunFor);
using TargetTimeScale =
std::conditional_t<
sizeof(half_sig(&T::run_for)) == sizeof(uint16_t),
HalfCycles,
Cycles>;
public:
/// Constructs a new JustInTimeActor using the same construction arguments as the included object.
template<typename... Args> JustInTimeActor(Args&&... args) : object_(std::forward<Args>(args)...) {}
template<typename... Args> JustInTimeActor(Args&&... args) : object_(std::forward<Args>(args)...) {
if constexpr (std::is_base_of<ClockingHint::Source, T>::value) {
object_.set_clocking_hint_observer(this);
}
}
/// Adds time to the actor.
forceinline void operator += (const LocalTimeScale &rhs) {
///
/// @returns @c true if adding time caused a flush; @c false otherwise.
forceinline bool operator += (LocalTimeScale rhs) {
if constexpr (std::is_base_of<ClockingHint::Source, T>::value) {
if(clocking_preference_ == ClockingHint::Preference::None) {
return false;
}
}
if constexpr (multiplier != 1) {
time_since_update_ += rhs * multiplier;
} else {
time_since_update_ += rhs;
}
is_flushed_ = false;
if constexpr (std::is_base_of<ClockingHint::Source, T>::value) {
if (clocking_preference_ == ClockingHint::Preference::RealTime) {
flush();
return true;
}
}
if constexpr (has_sequence_points<T>::value) {
time_until_event_ -= rhs;
if(time_until_event_ <= LocalTimeScale(0)) {
time_overrun_ = time_until_event_;
flush();
update_sequence_point();
return true;
}
}
return false;
}
/// Flushes all accumulated time and returns a pointer to the included object.
forceinline T *operator->() {
///
/// If this object provides sequence points, checks for changes to the next
/// sequence point upon deletion of the pointer.
[[nodiscard]] forceinline auto operator->() {
flush();
return &object_;
return std::unique_ptr<T, SequencePointAwareDeleter>(&object_, SequencePointAwareDeleter(this));
}
/// Acts exactly as per the standard ->, but preserves constness.
forceinline const T *operator->() const {
auto non_const_this = const_cast<JustInTimeActor<T, multiplier, divider, LocalTimeScale, TargetTimeScale> *>(this);
///
/// Despite being const, this will flush the object and, if relevant, update the next sequence point.
[[nodiscard]] forceinline auto operator -> () const {
auto non_const_this = const_cast<JustInTimeActor<T, LocalTimeScale, multiplier, divider> *>(this);
non_const_this->flush();
return std::unique_ptr<const T, SequencePointAwareDeleter>(&object_, SequencePointAwareDeleter(non_const_this));
}
/// @returns a pointer to the included object, without flushing time.
[[nodiscard]] forceinline T *last_valid() {
return &object_;
}
/// Returns a pointer to the included object without flushing time.
forceinline T *last_valid() {
/// @returns a const pointer to the included object, without flushing time.
[[nodiscard]] forceinline const T *last_valid() const {
return &object_;
}
/// @returns the amount of time since the object was last flushed, in the target time scale.
[[nodiscard]] forceinline TargetTimeScale time_since_flush() const {
// TODO: does this handle conversions properly where TargetTimeScale != LocalTimeScale?
if constexpr (divider == 1) {
return time_since_update_;
}
return TargetTimeScale(time_since_update_.as_integral() / divider);
}
/// Flushes all accumulated time.
///
/// This does not affect this actor's record of when the next sequence point will occur.
forceinline void flush() {
if(!is_flushed_) {
is_flushed_ = true;
did_flush_ = is_flushed_ = true;
if constexpr (divider == 1) {
const auto duration = time_since_update_.template flush<TargetTimeScale>();
object_.run_for(duration);
@@ -70,56 +169,63 @@ template <class T, int multiplier = 1, int divider = 1, class LocalTimeScale = H
}
}
private:
T object_;
LocalTimeScale time_since_update_;
bool is_flushed_ = true;
};
/// Indicates whether a flush has occurred since the last call to did_flush().
[[nodiscard]] forceinline bool did_flush() {
const bool did_flush = did_flush_;
did_flush_ = false;
return did_flush;
}
/*!
A RealTimeActor presents the same interface as a JustInTimeActor but doesn't defer work.
Time added will be performed immediately.
/// @returns a number in the range [-max, 0] indicating the offset of the most recent sequence
/// point from the final time at the end of the += that triggered the sequence point.
[[nodiscard]] forceinline LocalTimeScale last_sequence_point_overrun() {
return time_overrun_;
}
Its primary purpose is to allow consumers to remain flexible in their scheduling.
*/
template <class T, int multiplier = 1, int divider = 1, class LocalTimeScale = HalfCycles, class TargetTimeScale = LocalTimeScale> class RealTimeActor {
public:
template<typename... Args> RealTimeActor(Args&&... args) : object_(std::forward<Args>(args)...) {}
/// @returns the number of cycles until the next sequence-point-based flush, if the embedded object
/// supports sequence points; @c LocalTimeScale() otherwise.
[[nodiscard]] LocalTimeScale cycles_until_implicit_flush() const {
return time_until_event_;
}
forceinline void operator += (const LocalTimeScale &rhs) {
if constexpr (multiplier == 1 && divider == 1) {
object_.run_for(TargetTimeScale(rhs));
return;
/// Indicates whether a sequence-point-caused flush will occur if the specified period is added.
[[nodiscard]] forceinline bool will_flush(LocalTimeScale rhs) const {
if constexpr (!has_sequence_points<T>::value) {
return false;
}
return rhs >= time_until_event_;
}
if constexpr (multiplier == 1) {
accumulated_time_ += rhs;
} else {
accumulated_time_ += rhs * multiplier;
}
if constexpr (divider == 1) {
const auto duration = accumulated_time_.template flush<TargetTimeScale>();
object_.run_for(duration);
} else {
const auto duration = accumulated_time_.template divide<TargetTimeScale>(LocalTimeScale(divider));
if(duration > TargetTimeScale(0))
object_.run_for(duration);
/// Updates this template's record of the next sequence point.
void update_sequence_point() {
if constexpr (has_sequence_points<T>::value) {
time_until_event_ = object_.get_next_sequence_point();
assert(time_until_event_ > LocalTimeScale(0));
}
}
forceinline T *operator->() { return &object_; }
forceinline const T *operator->() const { return &object_; }
forceinline T *last_valid() { return &object_; }
forceinline void flush() {}
/// @returns A cached copy of the object's clocking preference.
ClockingHint::Preference clocking_preference() const {
return clocking_preference_;
}
private:
T object_;
LocalTimeScale accumulated_time_;
LocalTimeScale time_since_update_, time_until_event_, time_overrun_;
bool is_flushed_ = true;
bool did_flush_ = false;
template <typename S, typename = void> struct has_sequence_points : std::false_type {};
template <typename S> struct has_sequence_points<S, decltype(void(std::declval<S &>().get_next_sequence_point()))> : std::true_type {};
ClockingHint::Preference clocking_preference_ = ClockingHint::Preference::JustInTime;
void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference clocking) {
clocking_preference_ = clocking;
}
};
/*!
A AsyncJustInTimeActor acts like a JustInTimeActor but additionally contains an AsyncTaskQueue.
An AsyncJustInTimeActor acts like a JustInTimeActor but additionally contains an AsyncTaskQueue.
Any time the amount of accumulated time crosses a threshold provided at construction time,
the object will be updated on the AsyncTaskQueue.
*/

View File

@@ -128,13 +128,14 @@ template <class T> class MOS6522: public MOS6522Storage {
void access(int address);
uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output);
uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output, uint8_t timer_mask);
inline void reevaluate_interrupts();
/// Sets the current intended output value for the port and line;
/// if this affects the visible output, it will be passed to the handler.
void set_control_line_output(Port port, Line line, LineState value);
void evaluate_cb2_output();
void evaluate_port_b_output();
};
}

View File

@@ -8,6 +8,10 @@
#include "../../../Outputs/Log.hpp"
// As-yet unimplemented (incomplete list):
//
// PB6 count-down mode for timer 2.
namespace MOS {
namespace MOS6522 {
@@ -34,18 +38,18 @@ template <typename T> void MOS6522<T>::write(int address, uint8_t value) {
address &= 0xf;
access(address);
switch(address) {
case 0x0: // Write Port B.
case 0x0: // Write Port B. ('ORB')
// Store locally and communicate outwards.
registers_.output[1] = value;
bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
bus_handler_.set_port_output(Port::B, value, registers_.data_direction[1]);
evaluate_port_b_output();
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | ((registers_.peripheral_control&0x20) ? 0 : InterruptFlag::CB2ActiveEdge));
reevaluate_interrupts();
break;
case 0xf:
case 0x1: // Write Port A.
case 0x1: // Write Port A. ('ORA')
registers_.output[0] = value;
bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
@@ -59,28 +63,44 @@ template <typename T> void MOS6522<T>::write(int address, uint8_t value) {
reevaluate_interrupts();
break;
case 0x2: // Port B direction.
case 0x2: // Port B direction ('DDRB').
registers_.data_direction[1] = value;
break;
case 0x3: // Port A direction.
case 0x3: // Port A direction ('DDRA').
registers_.data_direction[0] = value;
break;
// Timer 1
case 0x6: case 0x4: registers_.timer_latch[0] = (registers_.timer_latch[0]&0xff00) | value; break;
case 0x5: case 0x7:
case 0x6: case 0x4: // ('T1L-L' and 'T1C-L')
registers_.timer_latch[0] = (registers_.timer_latch[0]&0xff00) | value;
break;
case 0x7: // Timer 1 latch, high ('T1L-H').
registers_.timer_latch[0] = (registers_.timer_latch[0]&0x00ff) | uint16_t(value << 8);
registers_.interrupt_flags &= ~InterruptFlag::Timer1;
if(address == 0x05) {
registers_.next_timer[0] = registers_.timer_latch[0];
timer_is_running_[0] = true;
break;
case 0x5: // Timer 1 counter, high ('T1C-H').
// Fill latch.
registers_.timer_latch[0] = (registers_.timer_latch[0]&0x00ff) | uint16_t(value << 8);
// Restart timer.
registers_.next_timer[0] = registers_.timer_latch[0];
timer_is_running_[0] = true;
// If PB7 output mode is active, set it low.
if(timer1_is_controlling_pb7()) {
registers_.timer_port_b_output &= 0x7f;
evaluate_port_b_output();
}
// Clear existing interrupt flag.
registers_.interrupt_flags &= ~InterruptFlag::Timer1;
reevaluate_interrupts();
break;
// Timer 2
case 0x8: registers_.timer_latch[1] = value; break;
case 0x9:
case 0x8: // ('T2C-L')
registers_.timer_latch[1] = value;
break;
case 0x9: // ('T2C-H')
registers_.interrupt_flags &= ~InterruptFlag::Timer2;
registers_.next_timer[1] = registers_.timer_latch[1] | uint16_t(value << 8);
timer_is_running_[1] = true;
@@ -88,7 +108,7 @@ template <typename T> void MOS6522<T>::write(int address, uint8_t value) {
break;
// Shift
case 0xa:
case 0xa: // ('SR')
registers_.shift = value;
shift_bits_remaining_ = 8;
registers_.interrupt_flags &= ~InterruptFlag::ShiftRegister;
@@ -96,11 +116,18 @@ template <typename T> void MOS6522<T>::write(int address, uint8_t value) {
break;
// Control
case 0xb:
case 0xb: // Auxiliary control ('ACR').
registers_.auxiliary_control = value;
evaluate_cb2_output();
// This is a bit of a guess: reset the timer-based PB7 output to its default high level
// any timer that timer-linked PB7 output is disabled.
if(!timer1_is_controlling_pb7()) {
registers_.timer_port_b_output |= 0x80;
}
evaluate_port_b_output();
break;
case 0xc: {
case 0xc: { // Peripheral control ('PCR').
// const auto old_peripheral_control = registers_.peripheral_control;
registers_.peripheral_control = value;
@@ -141,11 +168,11 @@ template <typename T> void MOS6522<T>::write(int address, uint8_t value) {
} break;
// Interrupt control
case 0xd:
case 0xd: // Interrupt flag regiser ('IFR').
registers_.interrupt_flags &= ~value;
reevaluate_interrupts();
break;
case 0xe:
case 0xe: // Interrupt enable register ('IER').
if(value&0x80)
registers_.interrupt_enable |= value;
else
@@ -159,54 +186,55 @@ template <typename T> uint8_t MOS6522<T>::read(int address) {
address &= 0xf;
access(address);
switch(address) {
case 0x0:
case 0x0: // Read Port B ('IRB').
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge);
reevaluate_interrupts();
return get_port_input(Port::B, registers_.data_direction[1], registers_.output[1]);
return get_port_input(Port::B, registers_.data_direction[1], registers_.output[1], registers_.auxiliary_control & 0x80);
case 0xf:
case 0x1:
case 0x1: // Read Port A ('IRA').
registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | InterruptFlag::CA2ActiveEdge);
reevaluate_interrupts();
return get_port_input(Port::A, registers_.data_direction[0], registers_.output[0]);
return get_port_input(Port::A, registers_.data_direction[0], registers_.output[0], 0);
case 0x2: return registers_.data_direction[1];
case 0x3: return registers_.data_direction[0];
case 0x2: return registers_.data_direction[1]; // Port B direction ('DDRB').
case 0x3: return registers_.data_direction[0]; // Port A direction ('DDRA').
// Timer 1
case 0x4:
case 0x4: // Timer 1 low-order latches ('T1L-L').
registers_.interrupt_flags &= ~InterruptFlag::Timer1;
reevaluate_interrupts();
return registers_.timer[0] & 0x00ff;
case 0x5: return registers_.timer[0] >> 8;
case 0x6: return registers_.timer_latch[0] & 0x00ff;
case 0x7: return registers_.timer_latch[0] >> 8;
case 0x5: return registers_.timer[0] >> 8; // Timer 1 high-order counter ('T1C-H')
case 0x6: return registers_.timer_latch[0] & 0x00ff; // Timer 1 low-order latches ('T1L-L').
case 0x7: return registers_.timer_latch[0] >> 8; // Timer 1 high-order latches ('T1L-H').
// Timer 2
case 0x8:
case 0x8: // Timer 2 low-order counter ('T2C-L').
registers_.interrupt_flags &= ~InterruptFlag::Timer2;
reevaluate_interrupts();
return registers_.timer[1] & 0x00ff;
case 0x9: return registers_.timer[1] >> 8;
case 0x9: return registers_.timer[1] >> 8; // Timer 2 high-order counter ('T2C-H').
case 0xa:
case 0xa: // Shift register ('SR').
shift_bits_remaining_ = 8;
registers_.interrupt_flags &= ~InterruptFlag::ShiftRegister;
reevaluate_interrupts();
return registers_.shift;
case 0xb: return registers_.auxiliary_control;
case 0xc: return registers_.peripheral_control;
case 0xb: return registers_.auxiliary_control; // Auxiliary control ('ACR').
case 0xc: return registers_.peripheral_control; // Peripheral control ('PCR').
case 0xd: return registers_.interrupt_flags | (get_interrupt_line() ? 0x80 : 0x00);
case 0xe: return registers_.interrupt_enable | 0x80;
case 0xd: return registers_.interrupt_flags | (get_interrupt_line() ? 0x80 : 0x00); // Interrupt flag register ('IFR').
case 0xe: return registers_.interrupt_enable | 0x80; // Interrupt enable register ('IER').
}
return 0xff;
}
template <typename T> uint8_t MOS6522<T>::get_port_input(Port port, uint8_t output_mask, uint8_t output) {
template <typename T> uint8_t MOS6522<T>::get_port_input(Port port, uint8_t output_mask, uint8_t output, uint8_t timer_mask) {
bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
const uint8_t input = bus_handler_.get_port_input(port);
output = (output & ~timer_mask) | (registers_.timer_port_b_output & timer_mask);
return (input & ~output_mask) | (output & output_mask);
}
@@ -276,10 +304,13 @@ template <typename T> void MOS6522<T>::do_phase2() {
registers_.timer_needs_reload = false;
registers_.timer[0] = registers_.timer_latch[0];
} else {
registers_.timer[0] --;
-- registers_.timer[0];
}
registers_.timer[1] --;
// Count down timer 2 if it is in timed interrupt mode (i.e. auxiliary control bit 5 is clear).
registers_.timer[1] -= timer2_clock_decrement();
// TODO: can eliminate conditional branches here.
if(registers_.next_timer[0] >= 0) {
registers_.timer[0] = uint16_t(registers_.next_timer[0]);
registers_.next_timer[0] = -1;
@@ -330,20 +361,29 @@ template <typename T> void MOS6522<T>::do_phase1() {
reevaluate_interrupts();
// Determine whether to reload.
if(registers_.auxiliary_control&0x40)
if(timer1_is_continuous())
registers_.timer_needs_reload = true;
else
timer_is_running_[0] = false;
// Determine whether to toggle PB7.
if(registers_.auxiliary_control&0x80) {
registers_.output[1] ^= 0x80;
if(timer1_is_controlling_pb7()) {
registers_.timer_port_b_output ^= 0x80;
bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
bus_handler_.set_port_output(Port::B, registers_.output[1], registers_.data_direction[1]);
evaluate_port_b_output();
}
}
}
template <typename T> void MOS6522<T>::evaluate_port_b_output() {
// Apply current timer-linked PB7 output if any atop the stated output.
const uint8_t timer_control_bit = registers_.auxiliary_control & 0x80;
bus_handler_.set_port_output(
Port::B,
(registers_.output[1] & (0xff ^ timer_control_bit)) | timer_control_bit,
registers_.data_direction[1] | timer_control_bit);
}
/*! Runs for a specified number of half cycles. */
template <typename T> void MOS6522<T>::run_for(const HalfCycles half_cycles) {
auto number_of_half_cycles = half_cycles.as_integral();
@@ -438,10 +478,11 @@ template <typename T> void MOS6522<T>::shift_in() {
}
template <typename T> void MOS6522<T>::shift_out() {
// When shifting out, the shift register rotates rather than strictly shifts.
// TODO: is that true for all modes?
if(shift_mode() == ShiftMode::OutUnderT2FreeRunning || shift_bits_remaining_) {
registers_.shift = uint8_t((registers_.shift << 1) | (registers_.shift >> 7));
const bool is_free_running = shift_mode() == ShiftMode::OutUnderT2FreeRunning;
if(is_free_running || shift_bits_remaining_) {
// Recirculate bits only if in free-running mode (?)
const uint8_t incoming_bit = (registers_.shift >> 7) * is_free_running;
registers_.shift = uint8_t(registers_.shift << 1) | incoming_bit;
evaluate_cb2_output();
--shift_bits_remaining_;

View File

@@ -34,7 +34,9 @@ class MOS6522Storage {
uint8_t peripheral_control = 0;
uint8_t interrupt_flags = 0;
uint8_t interrupt_enable = 0;
bool timer_needs_reload = false;
uint8_t timer_port_b_output = 0xff;
} registers_;
// Control state.
@@ -79,12 +81,30 @@ class MOS6522Storage {
OutUnderPhase2 = 6,
OutUnderCB1 = 7
};
ShiftMode shift_mode() const {
return ShiftMode((registers_.auxiliary_control >> 2) & 7);
bool timer1_is_controlling_pb7() const {
return registers_.auxiliary_control & 0x80;
}
bool timer1_is_continuous() const {
return registers_.auxiliary_control & 0x40;
}
bool is_shifting_out() const {
return registers_.auxiliary_control & 0x10;
}
int timer2_clock_decrement() const {
return 1 ^ ((registers_.auxiliary_control >> 5)&1);
}
int timer2_pb6_decrement() const {
return (registers_.auxiliary_control >> 5)&1;
}
ShiftMode shift_mode() const {
return ShiftMode((registers_.auxiliary_control >> 2) & 7);
}
bool portb_is_latched() const {
return registers_.auxiliary_control & 0x02;
}
bool port1_is_latched() const {
return registers_.auxiliary_control & 0x01;
}
};
}

View File

@@ -445,20 +445,20 @@ template <class BusHandler> class MOS6560 {
// register state
struct {
bool interlaced = false, tall_characters = false;
uint8_t first_column_location, first_row_location;
uint8_t number_of_columns, number_of_rows;
uint16_t character_cell_start_address, video_matrix_start_address;
uint16_t backgroundColour, borderColour, auxiliary_colour;
uint8_t first_column_location = 0, first_row_location = 0;
uint8_t number_of_columns = 0, number_of_rows = 0;
uint16_t character_cell_start_address = 0, video_matrix_start_address = 0;
uint16_t backgroundColour = 0, borderColour = 0, auxiliary_colour = 0;
bool invertedCells = false;
uint8_t direct_values[16];
uint8_t direct_values[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
} registers_;
// output state
enum State {
Sync, ColourBurst, Border, Pixels
} this_state_, output_state_;
int cycles_in_state_;
} this_state_ = State::Sync, output_state_ = State::Sync;
int cycles_in_state_ = 0;
// counters that cover an entire field
int horizontal_counter_ = 0, vertical_counter_ = 0;
@@ -487,23 +487,23 @@ template <class BusHandler> class MOS6560 {
// latches dictating start and length of drawing
bool vertical_drawing_latch_ = false, horizontal_drawing_latch_ = false;
int rows_this_field_, columns_this_line_;
int rows_this_field_ = 0, columns_this_line_ = 0;
// current drawing position counter
int pixel_line_cycle_, column_counter_;
int current_row_;
uint16_t current_character_row_;
uint16_t video_matrix_address_counter_, base_video_matrix_address_counter_;
int pixel_line_cycle_ = 0, column_counter_ = 0;
int current_row_ = 0;
uint16_t current_character_row_ = 0;
uint16_t video_matrix_address_counter_ = 0, base_video_matrix_address_counter_ = 0;
// data latched from the bus
uint8_t character_code_, character_colour_, character_value_;
uint8_t character_code_ = 0, character_colour_ = 0, character_value_ = 0;
bool is_odd_frame_ = false, is_odd_line_ = false;
// lookup table from 6560 colour index to appropriate PAL/NTSC value
uint16_t colours_[16];
uint16_t colours_[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
uint16_t *pixel_pointer;
uint16_t *pixel_pointer = nullptr;
void output_border(int number_of_cycles) {
uint16_t *colour_pointer = reinterpret_cast<uint16_t *>(crt_.begin_data(1));
if(colour_pointer) *colour_pointer = registers_.borderColour;
@@ -511,13 +511,13 @@ template <class BusHandler> class MOS6560 {
}
struct {
int cycles_per_line;
int line_counter_increment_offset;
int final_line_increment_position;
int lines_per_progressive_field;
bool supports_interlacing;
int cycles_per_line = 0;
int line_counter_increment_offset = 0;
int final_line_increment_position = 0;
int lines_per_progressive_field = 0;
bool supports_interlacing = 0;
} timing_;
OutputMode output_mode_;
OutputMode output_mode_ = OutputMode::NTSC;
};
}

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

@@ -8,6 +8,10 @@
#include "z8530.hpp"
#ifndef NDEBUG
#define NDEBUG
#endif
#define LOG_PREFIX "[SCC] "
#include "../../Outputs/Log.hpp"

View File

@@ -190,7 +190,9 @@ void TMS9918::run_for(const HalfCycles cycles) {
int read_cycles_pool = int_cycles;
while(write_cycles_pool || read_cycles_pool) {
#ifndef NDEBUG
LineBufferPointer backup = read_pointer_;
#endif
if(write_cycles_pool) {
// Determine how much writing to do.
@@ -329,8 +331,10 @@ void TMS9918::run_for(const HalfCycles cycles) {
}
#ifndef NDEBUG
assert(backup.row == read_pointer_.row && backup.column == read_pointer_.column);
backup = write_pointer_;
#endif
if(read_cycles_pool) {
@@ -704,9 +708,9 @@ HalfCycles Base::half_cycles_before_internal_cycles(int internal_cycles) {
return HalfCycles(((internal_cycles << 2) + (2 - cycles_error_)) / 3);
}
HalfCycles TMS9918::get_time_until_interrupt() {
if(!generate_interrupts_ && !enable_line_interrupts_) return HalfCycles(-1);
if(get_interrupt_line()) return HalfCycles(0);
HalfCycles TMS9918::get_next_sequence_point() {
if(!generate_interrupts_ && !enable_line_interrupts_) return HalfCycles::max();
if(get_interrupt_line()) return HalfCycles::max();
// Calculate the amount of time until the next end-of-frame interrupt.
const int frame_length = 342 * mode_timing_.total_lines;

View File

@@ -75,13 +75,13 @@ class TMS9918: public Base {
void latch_horizontal_counter();
/*!
Returns the amount of time until @c get_interrupt_line would next return true if
Returns the amount of time until @c get_interrupt_line would next change if
there are no interceding calls to @c write or to @c read.
If get_interrupt_line is true now, returns zero. If get_interrupt_line would
never return true, returns -1.
If get_interrupt_line is true now of if get_interrupt_line would
never return true, returns HalfCycles::max().
*/
HalfCycles get_time_until_interrupt();
HalfCycles get_next_sequence_point();
/*!
Returns the amount of time until the nominated line interrupt position is

View File

@@ -350,11 +350,14 @@ class Base {
case MemoryAccess::Write:
if(master_system_.cram_is_selected) {
// Adjust the palette.
// Adjust the palette. In a Master System blue has a slightly different
// scale; cf. https://www.retrorgb.com/sega-master-system-non-linear-blue-channel-findings.html
constexpr uint8_t rg_scale[] = {0, 85, 170, 255};
constexpr uint8_t b_scale[] = {0, 104, 170, 255};
master_system_.colour_ram[ram_pointer_ & 0x1f] = palette_pack(
uint8_t(((read_ahead_buffer_ >> 0) & 3) * 255 / 3),
uint8_t(((read_ahead_buffer_ >> 2) & 3) * 255 / 3),
uint8_t(((read_ahead_buffer_ >> 4) & 3) * 255 / 3)
rg_scale[(read_ahead_buffer_ >> 0) & 3],
rg_scale[(read_ahead_buffer_ >> 2) & 3],
b_scale[(read_ahead_buffer_ >> 4) & 3]
);
// Schedule a CRAM dot; this is scheduled for wherever it should appear

View File

@@ -164,6 +164,33 @@ template <bool is_stereo> class AY38910: public ::Outputs::Speaker::SampleSource
uint8_t c_left_ = 255, c_right_ = 255;
};
/*!
Provides helper code, to provide something closer to the interface exposed by many
AY-deploying machines of the era.
*/
struct Utility {
template <typename AY> static void write(AY &ay, bool is_data_write, uint8_t data) {
ay.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BDIR | GI::AY38910::BC2 | (is_data_write ? 0 : GI::AY38910::BC1)));
ay.set_data_input(data);
ay.set_control_lines(GI::AY38910::ControlLines(0));
}
template <typename AY> static void select_register(AY &ay, uint8_t reg) {
write(ay, false, reg);
}
template <typename AY> static void write_data(AY &ay, uint8_t reg) {
write(ay, true, reg);
}
template <typename AY> static uint8_t read(AY &ay) {
ay.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BC1));
const uint8_t result = ay.get_data_output();
ay.set_control_lines(GI::AY38910::ControlLines(0));
return result;
}
};
}
}

View File

@@ -0,0 +1,290 @@
//
// RealTimeClock.hpp
// Clock Signal
//
// Created by Thomas Harte on 07/05/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef Apple_RealTimeClock_hpp
#define Apple_RealTimeClock_hpp
namespace Apple {
namespace Clock {
/*!
Models Apple's real-time clocks, as contained in the Macintosh and IIgs.
Since tracking of time is pushed to this class, it is assumed
that whomever is translating real time into emulated time
will also signal interrupts — this is just the storage and time counting.
*/
class ClockStorage {
public:
ClockStorage() {
// TODO: this should persist, if possible, rather than
// being default initialised.
constexpr uint8_t default_data[] = {
0xa8, 0x00, 0x00, 0x00,
0xcc, 0x0a, 0xcc, 0x0a,
0x00, 0x00, 0x00, 0x00,
0x00, 0x02, 0x63, 0x00,
0x03, 0x88, 0x00, 0x4c
};
memcpy(data_, default_data, sizeof(default_data));
memset(&data_[sizeof(default_data)], 0xff, sizeof(data_) - sizeof(default_data));
}
/*!
Advances the clock by 1 second.
The caller should also signal an interrupt.
*/
void update() {
for(int c = 0; c < 4; ++c) {
++seconds_[c];
if(seconds_[c]) break;
}
}
protected:
static constexpr uint16_t NoResult = 0x100;
static constexpr uint16_t DidComplete = 0x101;
uint16_t perform(uint8_t command) {
/*
Documented commands:
z0000001 Seconds register 0 (lowest order byte)
z0000101 Seconds register 1
z0001001 Seconds register 2
z0001101 Seconds register 3
00110001 Test register (write only)
00110101 Write-protect register (write only)
z010aa01 RAM addresses 0x10 - 0x13
z1aaaa01 RAM addresses 0x00 0x0f
z0111abc, followed by 0defgh00
RAM address abcdefgh
z = 1 => a read; z = 0 => a write.
The top bit of the write-protect register enables (0) or disables (1)
writes to other locations.
All the documentation says about the test register is to set the top
two bits to 0 for normal operation. Abnormal operation is undefined.
*/
switch(phase_) {
case Phase::Command:
// Decode an address.
switch(command & 0x70) {
default:
if(command & 0x40) {
// RAM addresses 0x00 0x0f.
address_ = (command >> 2) & 0xf;
} else return DidComplete; // Unrecognised.
break;
case 0x00:
// A time access.
address_ = SecondsBuffer + ((command >> 2)&3);
break;
case 0x30:
// Either a register access or an extended instruction.
if(command & 0x08) {
address_ = (command & 0x7) << 5;
phase_ = (command & 0x80) ? Phase::SecondAddressByteRead : Phase::SecondAddressByteWrite;
return NoResult;
} else {
address_ = (command & 4) ? RegisterWriteProtect : RegisterTest;
}
break;
case 0x20:
// RAM addresses 0x10 0x13.
address_ = 0x10 + ((command >> 2) & 0x3);
break;
}
// If this is a read, return a result; otherwise prepare to write.
if(command & 0x80) {
// The two registers are write-only.
if(address_ == RegisterTest || address_ == RegisterWriteProtect) {
return DidComplete;
}
return (address_ >= SecondsBuffer) ? seconds_[address_ & 0xff] : data_[address_];
}
phase_ = Phase::WriteData;
return NoResult;
case Phase::SecondAddressByteRead:
case Phase::SecondAddressByteWrite:
if(command & 0x83) {
return DidComplete;
}
address_ |= command >> 2;
if(phase_ == Phase::SecondAddressByteRead) {
phase_ = Phase::Command;
return data_[address_]; // Only RAM accesses can get this far.
} else {
phase_ = Phase::WriteData;
}
return NoResult;
case Phase::WriteData:
// First test: is this to the write-protect register?
if(address_ == RegisterWriteProtect) {
write_protect_ = command;
return DidComplete;
}
if(address_ == RegisterTest) {
// No documentation here.
return DidComplete;
}
// No other writing is permitted if the write protect
// register won't allow it.
if(!(write_protect_ & 0x80)) {
if(address_ >= SecondsBuffer) {
seconds_[address_ & 0xff] = command;
} else {
data_[address_] = command;
}
}
phase_ = Phase::Command;
return DidComplete;
}
return NoResult;
}
private:
uint8_t data_[256];
uint8_t seconds_[4];
uint8_t write_protect_;
int address_;
static constexpr int SecondsBuffer = 0x100;
static constexpr int RegisterTest = 0x200;
static constexpr int RegisterWriteProtect = 0x201;
enum class Phase {
Command,
SecondAddressByteRead,
SecondAddressByteWrite,
WriteData
};
Phase phase_ = Phase::Command;
};
/*!
Provides the serial interface implemented by the Macintosh.
*/
class SerialClock: public ClockStorage {
public:
/*!
Sets the current clock and data inputs to the clock.
*/
void set_input(bool clock, bool data) {
// The data line is valid when the clock transitions to level 0.
if(clock && !previous_clock_) {
// Shift into the command_ register, no matter what.
command_ = uint16_t((command_ << 1) | (data ? 1 : 0));
result_ <<= 1;
// Increment phase.
++phase_;
// If a whole byte has been collected, push it onwards.
if(!(phase_&7)) {
// Begin pessimistically.
const auto effect = perform(uint8_t(command_));
switch(effect) {
case ClockStorage::NoResult:
break;
default:
result_ = uint8_t(effect);
break;
case ClockStorage::DidComplete:
abort();
break;
}
}
}
previous_clock_ = clock;
}
/*!
Reads the current data output level from the clock.
*/
bool get_data() {
return !!(result_ & 0x80);
}
/*!
Announces that a serial command has been aborted.
*/
void abort() {
result_ = 0;
phase_ = 0;
command_ = 0;
}
private:
int phase_ = 0;
uint16_t command_;
uint8_t result_ = 0;
bool previous_clock_ = false;
};
/*!
Provides the parallel interface implemented by the IIgs.
*/
class ParallelClock: public ClockStorage {
public:
void set_control(uint8_t control) {
if(!(control&0x80)) return;
if(control & 0x40) {
// Read from the RTC.
// A no-op for now.
} else {
// Write to the RTC. Which in this implementation also sets up a future read.
data_ = uint8_t(perform(data_));
}
// MAGIC! The transaction took 0 seconds.
// TODO: no magic.
control_ = control & 0x7f;
// Bit 5 is also meant to be 1 or 0 to indicate the final byte.
}
uint8_t get_control() {
return control_;
}
void set_data(uint8_t data) {
data_ = data;
}
uint8_t get_data() {
return data_;
}
private:
uint8_t data_;
uint8_t control_;
};
}
}
#endif /* Apple_RealTimeClock_hpp */

View File

@@ -180,29 +180,40 @@ void DiskII::set_state_machine(const std::vector<uint8_t> &state_machine) {
if(state_machine[0] != 0x18) {
for(size_t source_address = 0; source_address < 256; ++source_address) {
// Remap into Beneath Apple Pro-DOS address form.
size_t destination_address =
((source_address&0x80) ? 0x10 : 0x00) |
((source_address&0x01) ? 0x20 : 0x00) |
((source_address&0x40) ? 0x40 : 0x00) |
const size_t destination_address =
((source_address&0x20) ? 0x80 : 0x00) |
((source_address&0x10) ? 0x01 : 0x00) |
((source_address&0x40) ? 0x40 : 0x00) |
((source_address&0x01) ? 0x20 : 0x00) |
((source_address&0x80) ? 0x10 : 0x00) |
((source_address&0x08) ? 0x08 : 0x00) |
((source_address&0x04) ? 0x04 : 0x00) |
((source_address&0x02) ? 0x02 : 0x00);
uint8_t source_value = state_machine[source_address];
((source_address&0x02) ? 0x02 : 0x00) |
((source_address&0x10) ? 0x01 : 0x00);
source_value =
// Store.
const uint8_t source_value = state_machine[source_address];
state_machine_[destination_address] =
((source_value & 0x80) ? 0x10 : 0x0) |
((source_value & 0x40) ? 0x20 : 0x0) |
((source_value & 0x20) ? 0x40 : 0x0) |
((source_value & 0x10) ? 0x80 : 0x0) |
(source_value & 0x0f);
// Store.
state_machine_[destination_address] = source_value;
}
} else {
memcpy(&state_machine_[0], &state_machine[0], 128);
for(size_t source_address = 0; source_address < 256; ++source_address) {
// Reshuffle ordering of bytes only, to retain indexing by the high nibble.
const size_t destination_address =
((source_address&0x80) ? 0x80 : 0x00) |
((source_address&0x40) ? 0x40 : 0x00) |
((source_address&0x01) ? 0x20 : 0x00) |
((source_address&0x20) ? 0x10 : 0x00) |
((source_address&0x08) ? 0x08 : 0x00) |
((source_address&0x04) ? 0x04 : 0x00) |
((source_address&0x02) ? 0x02 : 0x00) |
((source_address&0x10) ? 0x01 : 0x00);
state_machine_[destination_address] = state_machine[source_address];
}
}
}

View File

@@ -0,0 +1,45 @@
//
// DiskIIDrive.cpp
// Clock Signal
//
// Created by Thomas Harte on 20/11/2020.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#include "DiskIIDrive.hpp"
using namespace Apple::Disk;
DiskIIDrive::DiskIIDrive(int input_clock_rate) :
IWMDrive(input_clock_rate, 1) {
Drive::set_rotation_speed(300.0f);
}
void DiskIIDrive::set_enabled(bool enabled) {
set_motor_on(enabled);
}
void DiskIIDrive::set_control_lines(int lines) {
// If the stepper magnet selections have changed, and any is on, see how
// that moves the head.
if(lines ^ stepper_mask_ && lines) {
// Convert from a representation of bits set to the centre of pull.
int direction = 0;
if(lines&1) direction += (((stepper_position_ - 0) + 4)&7) - 4;
if(lines&2) direction += (((stepper_position_ - 2) + 4)&7) - 4;
if(lines&4) direction += (((stepper_position_ - 4) + 4)&7) - 4;
if(lines&8) direction += (((stepper_position_ - 6) + 4)&7) - 4;
const int bits_set = (lines&1) + ((lines >> 1)&1) + ((lines >> 2)&1) + ((lines >> 3)&1);
direction /= bits_set;
// Compare to the stepper position to decide whether that pulls in the
// current cog notch, or grabs a later one.
step(Storage::Disk::HeadPosition(-direction, 4));
stepper_position_ = (stepper_position_ - direction + 8) & 7;
}
stepper_mask_ = lines;
}
bool DiskIIDrive::read() {
return !!(stepper_mask_ & 2) || get_is_read_only();
}

View File

@@ -0,0 +1,33 @@
//
// DiskIIDrive.hpp
// Clock Signal
//
// Created by Thomas Harte on 20/11/2020.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#ifndef DiskIIDrive_hpp
#define DiskIIDrive_hpp
#include "IWM.hpp"
namespace Apple {
namespace Disk {
class DiskIIDrive: public IWMDrive {
public:
DiskIIDrive(int input_clock_rate);
private:
void set_enabled(bool) final;
void set_control_lines(int) final;
bool read() final;
int stepper_mask_ = 0;
int stepper_position_ = 0;
};
}
}
#endif /* DiskIIDrive_hpp */

View File

@@ -8,6 +8,11 @@
#include "IWM.hpp"
#ifndef NDEBUG
#define NDEBUG
#endif
#define LOG_PREFIX "[IWM] "
#include "../../Outputs/Log.hpp"
using namespace Apple;
@@ -50,7 +55,7 @@ uint8_t IWM::read(int address) {
switch(state_ & (Q6 | Q7 | ENABLE)) {
default:
LOG("[IWM] Invalid read\n");
LOG("Invalid read\n");
return 0xff;
// "Read all 1s".
@@ -62,9 +67,8 @@ uint8_t IWM::read(int address) {
const auto result = data_register_;
if(data_register_ & 0x80) {
// printf("\n\nIWM:%02x\n\n", data_register_);
// printf(".");
data_register_ = 0;
// LOG("Reading data: " << PADHEX(2) << int(result));
}
// LOG("Reading data register: " << PADHEX(2) << int(result));
@@ -99,7 +103,7 @@ uint8_t IWM::read(int address) {
bit 6: 1 = write state (0 = underrun has occurred; 1 = no underrun so far).
bit 7: 1 = write data buffer ready for data (1 = ready; 0 = busy).
*/
// LOG("Reading write handshake: " << PADHEX(2) << (0x3f | write_handshake_));
// LOG("Reading write handshake: " << PADHEX(2) << int(0x3f | write_handshake_));
return 0x3f | write_handshake_;
}
@@ -128,13 +132,21 @@ void IWM::write(int address, uint8_t input) {
mode_ = input;
// TEMPORARY. To test for the unimplemented mode.
if(input&0x2) {
LOG("Switched to asynchronous mode");
} else {
LOG("Switched to synchronous mode");
}
switch(mode_ & 0x18) {
case 0x00: bit_length_ = Cycles(24); break; // slow mode, 7Mhz
case 0x08: bit_length_ = Cycles(12); break; // fast mode, 7Mhz
case 0x00: bit_length_ = Cycles(28); break; // slow mode, 7Mhz
case 0x08: bit_length_ = Cycles(14); break; // fast mode, 7Mhz
case 0x10: bit_length_ = Cycles(32); break; // slow mode, 8Mhz
case 0x18: bit_length_ = Cycles(16); break; // fast mode, 8Mhz
}
LOG("IWM mode is now " << PADHEX(2) << int(mode_));
LOG("Mode is now " << PADHEX(2) << int(mode_));
LOG("New bit length is " << std::dec << bit_length_.as_integral());
break;
case Q7|Q6|ENABLE: // Write data register.
@@ -248,6 +260,7 @@ void IWM::run_for(const Cycles cycles) {
drives_[active_drive_]->run_for(Cycles(1));
++cycles_since_shift_;
if(cycles_since_shift_ == bit_length_ + error_margin) {
// LOG("Shifting 0 at " << std::dec << cycles_since_shift_.as_integral());
propose_shift(0);
}
}
@@ -263,41 +276,45 @@ void IWM::run_for(const Cycles cycles) {
} break;
case ShiftMode::Writing:
if(drives_[active_drive_]->is_writing()) {
while(cycles_since_shift_ + integer_cycles >= bit_length_) {
const auto cycles_until_write = bit_length_ - cycles_since_shift_;
while(cycles_since_shift_ + integer_cycles >= bit_length_) {
const auto cycles_until_write = bit_length_ - cycles_since_shift_;
if(drives_[active_drive_]) {
drives_[active_drive_]->run_for(cycles_until_write);
// Output a flux transition if the top bit is set.
drives_[active_drive_]->write_bit(shift_register_ & 0x80);
shift_register_ <<= 1;
}
shift_register_ <<= 1;
integer_cycles -= cycles_until_write.as_integral();
cycles_since_shift_ = Cycles(0);
integer_cycles -= cycles_until_write.as_integral();
cycles_since_shift_ = Cycles(0);
--output_bits_remaining_;
if(!output_bits_remaining_) {
if(!(write_handshake_ & 0x80)) {
write_handshake_ |= 0x80;
shift_register_ = next_output_;
output_bits_remaining_ = 8;
// LOG("Next byte: " << PADHEX(2) << int(shift_register_));
} else {
write_handshake_ &= ~0x40;
drives_[active_drive_]->end_writing();
// printf("\n");
LOG("Overrun; done.");
select_shift_mode();
}
--output_bits_remaining_;
if(!output_bits_remaining_) {
if(!(write_handshake_ & 0x80)) {
shift_register_ = next_output_;
output_bits_remaining_ = 8;
// LOG("Next byte: " << PADHEX(2) << int(shift_register_));
} else {
write_handshake_ &= ~0x40;
if(drives_[active_drive_]) drives_[active_drive_]->end_writing();
LOG("Overrun; done.");
output_bits_remaining_ = 1;
}
}
cycles_since_shift_ = integer_cycles;
if(integer_cycles) {
drives_[active_drive_]->run_for(cycles_since_shift_);
// Either way, the IWM is ready for more data.
write_handshake_ |= 0x80;
}
} else {
drives_[active_drive_]->run_for(cycles);
}
// Either some bits were output, in which case cycles_since_shift_ is no 0 and
// integer_cycles is some number less than bit_length_, or none were and
// cycles_since_shift_ + integer_cycles is less than bit_length, and the new
// part should be accumulated.
cycles_since_shift_ += integer_cycles;
if(drives_[active_drive_] && integer_cycles) {
drives_[active_drive_]->run_for(cycles_since_shift_);
}
break;
@@ -332,12 +349,12 @@ void IWM::select_shift_mode() {
}
// If writing mode just began, set the drive into write mode and cue up the first output byte.
if(drives_[active_drive_] && old_shift_mode != ShiftMode::Writing && shift_mode_ == ShiftMode::Writing) {
drives_[active_drive_]->begin_writing(Storage::Time(1, clock_rate_ / bit_length_.as_integral()), false);
if(old_shift_mode != ShiftMode::Writing && shift_mode_ == ShiftMode::Writing) {
if(drives_[active_drive_]) drives_[active_drive_]->begin_writing(Storage::Time(1, clock_rate_ / bit_length_.as_integral()), false);
shift_register_ = next_output_;
write_handshake_ |= 0x80 | 0x40;
output_bits_remaining_ = 8;
LOG("Seeding output with " << PADHEX(2) << shift_register_);
LOG("Seeding output with " << PADHEX(2) << int(shift_register_));
}
}
@@ -351,6 +368,7 @@ void IWM::process_event(const Storage::Disk::Drive::Event &event) {
switch(event.type) {
case Storage::Disk::Track::Event::IndexHole: return;
case Storage::Disk::Track::Event::FluxTransition:
// LOG("Shifting 1 at " << std::dec << cycles_since_shift_.as_integral());
propose_shift(1);
break;
}
@@ -359,12 +377,13 @@ void IWM::process_event(const Storage::Disk::Drive::Event &event) {
void IWM::propose_shift(uint8_t bit) {
// TODO: synchronous mode.
// LOG("Shifting at " << std::dec << cycles_since_shift_.as_integral());
// LOG("Shifting input");
// See above for text from the IWM patent, column 7, around line 35 onwards.
// The error_margin here implements the 'before' part of that contract.
//
// Basic effective logic: if at least 1 is fozund in the bit_length_ cycles centred
// Basic effective logic: if at least 1 is found in the bit_length_ cycles centred
// on the current expected bit delivery time as implied by cycles_since_shift_,
// shift in a 1 and start a new window wherever the first found 1 was.
//
@@ -374,6 +393,7 @@ void IWM::propose_shift(uint8_t bit) {
shift_register_ = uint8_t((shift_register_ << 1) | bit);
if(shift_register_ & 0x80) {
// if(data_register_ & 0x80) LOG("Byte missed");
data_register_ = shift_register_;
shift_register_ = 0;
}
@@ -386,16 +406,20 @@ void IWM::propose_shift(uint8_t bit) {
void IWM::set_drive(int slot, IWMDrive *drive) {
drives_[slot] = drive;
drive->set_event_delegate(this);
drive->set_clocking_hint_observer(this);
if(drive) {
drive->set_event_delegate(this);
drive->set_clocking_hint_observer(this);
} else {
drive_is_rotating_[slot] = false;
}
}
void IWM::set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) {
const bool is_rotating = clocking != ClockingHint::Preference::None;
if(component == static_cast<ClockingHint::Source *>(drives_[0])) {
if(drives_[0] && component == static_cast<ClockingHint::Source *>(drives_[0])) {
drive_is_rotating_[0] = is_rotating;
} else {
} else if(drives_[1] && component == static_cast<ClockingHint::Source *>(drives_[1])) {
drive_is_rotating_[1] = is_rotating;
}
}

View File

@@ -142,8 +142,11 @@ bool DoubleDensityDrive::read() {
return !get_is_track_zero();
case CA1|CA0: // Disk has been ejected.
// (0 = user has ejected disk)
return !has_new_disk_;
// (1 = user has ejected disk)
//
// TODO: does this really mean _user_ has ejected disk? If so then I should avoid
// changing the flag upon a programmatic eject.
return has_new_disk_;
case CA1|CA0|SEL: // Tachometer.
// (arbitrary)
@@ -170,12 +173,10 @@ bool DoubleDensityDrive::read() {
case CA2|CA1|CA0|SEL: // Drive installed.
// (0 = present, 1 = missing)
//
// TODO: why do I need to return this the wrong way around for the Mac Plus?
return true;
return false;
}
}
void DoubleDensityDrive::did_set_disk() {
has_new_disk_ = true;
void DoubleDensityDrive::did_set_disk(bool did_replace) {
has_new_disk_ = did_replace;
}

View File

@@ -21,7 +21,7 @@ class DoubleDensityDrive: public IWMDrive {
/*!
@returns @c true if this is an 800kb drive; @c false otherwise.
*/
bool is_800k() {
bool is_800k() const {
return is_800k_;
}
@@ -39,7 +39,7 @@ class DoubleDensityDrive: public IWMDrive {
// To receive the proper notifications from Storage::Disk::Drive.
void did_step(Storage::Disk::HeadPosition to_position) final;
void did_set_disk() final;
void did_set_disk(bool) final;
const bool is_800k_;
bool has_new_disk_ = false;

View File

@@ -46,6 +46,8 @@ class Keyboard {
/// Constructs a Keyboard that declares itself to observe only members of @c observed_keys.
Keyboard(const std::set<Key> &observed_keys, const std::set<Key> &essential_modifiers);
virtual ~Keyboard() {}
// Host interface.
/// @returns @c true if the key press affects the machine; @c false otherwise.

View File

@@ -0,0 +1,24 @@
//
// AccessType.h
// Clock Signal
//
// Created by Thomas Harte on 16/01/21.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef AccessType_h
#define AccessType_h
namespace InstructionSet {
enum class AccessType {
None,
Read,
Write,
ReadModifyWrite
};
}
#endif /* AccessType_h */

View File

@@ -0,0 +1,200 @@
//
// CachingExecutor.hpp
// Clock Signal
//
// Created by Thomas Harte on 16/01/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef CachingExecutor_hpp
#define CachingExecutor_hpp
#include "Sizes.hpp"
#include <array>
#include <cstdint>
#include <limits>
#include <list>
#include <map>
#include <queue>
#include <unordered_map>
namespace InstructionSet {
/*!
A caching executor makes use of an instruction set-specific executor to cache 'performers' (i.e. function pointers)
that result from decoding.
In other words, it's almost a JIT compiler, but producing threaded code (in the Forth sense) and then incurring whatever
costs sit behind using the C ABI for calling. Since there'll always be exactly one parameter, being the specific executor,
hopefully the calling costs are acceptable.
Intended usage is for specific executors to subclass from this and declare it a friend.
TODO: determine promises re: interruption, amongst other things.
*/
template <
/// Indicates the Executor for this platform.
typename Executor,
/// Indicates the greatest value the program counter might take.
uint64_t max_address,
/// Indicates the maximum number of potential performers that will be provided.
uint64_t max_performer_count,
/// Provides the type of Instruction to expect.
typename InstructionType,
/// Indicates whether instructions should be treated as ephemeral or included in the cache.
bool retain_instructions
> class CachingExecutor {
public:
using Performer = void (Executor::*)();
using PerformerIndex = typename MinIntTypeValue<max_performer_count>::type;
using ProgramCounterType = typename MinIntTypeValue<max_address>::type;
// MARK: - Parser call-ins.
void announce_overflow(ProgramCounterType) {
/*
Should be impossible for now; this is intended to provide information
when page caching.
*/
}
void announce_instruction(ProgramCounterType, InstructionType instruction) {
// Dutifully map the instruction to a performer and keep it.
program_.push_back(static_cast<Executor *>(this)->action_for(instruction));
if constexpr (retain_instructions) {
// TODO.
}
}
protected:
// Storage for the statically-allocated list of performers. It's a bit more
// work for executors to fill this array, but subsequently performers can be
// indexed by array position, which is a lot more compact than a generic pointer.
std::array<Performer, max_performer_count+1> performers_;
ProgramCounterType program_counter_;
/*!
Moves the current point of execution to @c address, updating necessary performer caches
and doing any translation as is necessary.
*/
void set_program_counter(ProgramCounterType address) {
// Set flag to terminate any inner loop currently running through
// previously-parsed content.
has_branched_ = true;
program_counter_ = address;
// Temporary implementation: just interpret.
program_.clear();
program_index_ = 0;
static_cast<Executor *>(this)->parse(address, ProgramCounterType(max_address));
// const auto page = find_page(address);
// const auto entry = page->entry_points.find(address);
// if(entry == page->entry_points.end()) {
// // Requested segment wasn't found; check whether it was
// // within the recently translated list and otherwise
// // translate it.
// }
}
/*!
Indicates whether the processor is currently 'stopped', i.e. whether all attempts to run
should produce no activity. Some processors have such a state when waiting for
interrupts or for a reset.
*/
void set_is_stopped(bool) {}
/*!
Executes up to the next branch.
*/
void run_to_branch() {
has_branched_ = false;
for(auto index: program_) {
const auto performer = performers_[index];
(static_cast<Executor *>(this)->*performer)();
if(has_branched_) break;
}
}
/*!
Runs for @c duration; the intention is that subclasses provide a method
that is clear about units, and call this to count down in whatever units they
count down in.
*/
void run_for(int duration) {
remaining_duration_ += duration;
while(remaining_duration_ > 0) {
has_branched_ = false;
Executor *const executor = static_cast<Executor *>(this);
while(remaining_duration_ > 0 && !has_branched_) {
const auto performer = performers_[program_[program_index_]];
++program_index_;
(executor->*performer)();
}
}
}
/*!
Should be called by a specific executor to subtract from the remaining
running duration.
*/
inline void subtract_duration(int duration) {
remaining_duration_ -= duration;
}
private:
bool has_branched_ = false;
int remaining_duration_ = 0;
std::vector<PerformerIndex> program_;
size_t program_index_ = 0;
/* TODO: almost below here can be shoved off into an LRUCache object, or similar. */
// static constexpr size_t max_cached_pages = 64;
// struct Page {
// std::map<ProgramCounterType, PerformerIndex> entry_points;
// TODO: can I statically these two? Should I?
// std::vector<PerformerIndex> actions_;
// std::vector<typename std::enable_if<!std::is_same<InstructionType, void>::value, InstructionType>::type> instructions_;
// };
// std::array<Page, max_cached_pages> pages_;
// Maps from page numbers to pages.
// std::unordered_map<ProgramCounterType, Page *> cached_pages_;
// Maintains an LRU of recently-used pages in case of a need for reuse.
// std::list<ProgramCounterType> touched_pages_;
/*!
Finds or creates the page that contains @c address.
*/
/* Page *find_page(ProgramCounterType address) {
// TODO: are 1kb pages always appropriate? Is 64 the correct amount to keep?
const auto page_address = ProgramCounterType(address >> 10);
auto page = cached_pages_.find(page_address);
if(page == cached_pages_.end()) {
// Page wasn't found; either allocate a new one or
// reuse one that already exists.
if(cached_pages_.size() == max_cached_pages) {
} else {
}
} else {
// Page was found; LRU shuffle it.
}
return nullptr;
}*/
};
}
#endif /* CachingExecutor_hpp */

View File

@@ -0,0 +1,89 @@
//
// Disassembler.hpp
// Clock Signal
//
// Created by Thomas Harte on 26/01/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Disassembler_hpp
#define Disassembler_hpp
#include "Sizes.hpp"
#include <list>
#include <map>
#include <set>
namespace InstructionSet {
template <
/// Indicates the Parser for this platform.
template<typename, bool> class ParserType,
/// Indicates the greatest value the program counter might take.
uint64_t max_address,
/// Provides the type of Instruction to expect.
typename InstructionType,
/// Provides the storage size used for memory.
typename MemoryWord,
/// Provides the addressing range of memory.
typename AddressType
> class Disassembler {
public:
using ProgramCounterType = typename MinIntTypeValue<max_address>::type;
/*!
Adds the result of disassembling @c memory which is @c length @c MemoryWords long from @c start_address
to the current net total of instructions and recorded memory accesses.
*/
void disassemble(const MemoryWord *memory, ProgramCounterType location, ProgramCounterType length, ProgramCounterType start_address) {
// TODO: possibly, move some of this stuff to instruction-set specific disassemblers, analogous to
// the Executor's ownership of the Parser. That would allow handling of stateful parsing.
ParserType<decltype(*this), true> parser;
pending_entry_points_.push_back(start_address);
entry_points_.insert(start_address);
while(!pending_entry_points_.empty()) {
const auto next_entry_point = pending_entry_points_.front();
pending_entry_points_.pop_front();
if(next_entry_point >= location) {
parser.parse(*this, memory - location, next_entry_point & max_address, length + location);
}
}
}
const std::map<ProgramCounterType, InstructionType> &instructions() const {
return instructions_;
}
const std::set<ProgramCounterType> &entry_points() const {
return entry_points_;
}
void announce_overflow(ProgramCounterType) {}
void announce_instruction(ProgramCounterType address, InstructionType instruction) {
instructions_[address] = instruction;
}
void add_entry(ProgramCounterType address) {
if(entry_points_.find(address) == entry_points_.end()) {
pending_entry_points_.push_back(address);
entry_points_.insert(address);
}
}
void add_access(AddressType address, AccessType access_type) {
// TODO.
(void)address;
(void)access_type;
}
private:
std::map<ProgramCounterType, InstructionType> instructions_;
std::set<ProgramCounterType> entry_points_;
std::list<ProgramCounterType> pending_entry_points_;
};
}
#endif /* Disassembler_h */

View File

@@ -0,0 +1,282 @@
//
// Decoder.cpp
// Clock Signal
//
// Created by Thomas Harte on 15/01/21.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "Decoder.hpp"
#include <algorithm>
namespace InstructionSet {
namespace M50740 {
Instruction Decoder::instrucion_for_opcode(uint8_t opcode) {
switch(opcode) {
default: return Instruction(opcode);
#define Map(opcode, operation, addressing_mode) case opcode: return Instruction(Operation::operation, AddressingMode::addressing_mode, opcode);
/* 0x00 0x0f */
Map(0x00, BRK, Implied); Map(0x01, ORA, XIndirect);
Map(0x02, JSR, ZeroPageIndirect); Map(0x03, BBS0, AccumulatorRelative);
Map(0x05, ORA, ZeroPage);
Map(0x06, ASL, ZeroPage); Map(0x07, BBS0, ZeroPageRelative);
Map(0x08, PHP, Implied); Map(0x09, ORA, Immediate);
Map(0x0a, ASL, Accumulator); Map(0x0b, SEB0, Accumulator);
Map(0x0d, ORA, Absolute);
Map(0x0e, ASL, Absolute); Map(0x0f, SEB0, ZeroPage);
/* 0x10 0x1f */
Map(0x10, BPL, Relative); Map(0x11, ORA, IndirectY);
Map(0x12, CLT, Implied); Map(0x13, BBC0, AccumulatorRelative);
Map(0x15, ORA, ZeroPageX);
Map(0x16, ASL, ZeroPageX); Map(0x17, BBC0, ZeroPageRelative);
Map(0x18, CLC, Implied); Map(0x19, ORA, AbsoluteY);
Map(0x1a, DEC, Accumulator); Map(0x1b, CLB0, Accumulator);
Map(0x1d, ORA, AbsoluteX);
Map(0x1e, ASL, AbsoluteX); Map(0x1f, CLB0, ZeroPage);
/* 0x20 0x2f */
Map(0x20, JSR, Absolute); Map(0x21, AND, XIndirect);
Map(0x22, JSR, SpecialPage); Map(0x23, BBS1, AccumulatorRelative);
Map(0x24, BIT, ZeroPage); Map(0x25, AND, ZeroPage);
Map(0x26, ROL, ZeroPage); Map(0x27, BBS1, ZeroPageRelative);
Map(0x28, PLP, Implied); Map(0x29, AND, Immediate);
Map(0x2a, ROL, Accumulator); Map(0x2b, SEB1, Accumulator);
Map(0x2c, BIT, Absolute); Map(0x2d, AND, Absolute);
Map(0x2e, ROL, Absolute); Map(0x2f, SEB1, ZeroPage);
/* 0x30 0x3f */
Map(0x30, BMI, Relative); Map(0x31, AND, IndirectY);
Map(0x32, SET, Implied); Map(0x33, BBC1, AccumulatorRelative);
Map(0x35, AND, ZeroPageX);
Map(0x36, ROL, ZeroPageX); Map(0x37, BBC1, ZeroPageRelative);
Map(0x38, SEC, Implied); Map(0x39, AND, AbsoluteY);
Map(0x3a, INC, Accumulator); Map(0x3b, CLB1, Accumulator);
Map(0x3c, LDM, ImmediateZeroPage); Map(0x3d, AND, AbsoluteX);
Map(0x3e, ROL, AbsoluteX); Map(0x3f, CLB1, ZeroPage);
/* 0x40 0x4f */
Map(0x40, RTI, Implied); Map(0x41, EOR, XIndirect);
Map(0x42, STP, Implied); Map(0x43, BBS2, AccumulatorRelative);
Map(0x44, COM, ZeroPage); Map(0x45, EOR, ZeroPage);
Map(0x46, LSR, ZeroPage); Map(0x47, BBS2, ZeroPageRelative);
Map(0x48, PHA, Implied); Map(0x49, EOR, Immediate);
Map(0x4a, LSR, Accumulator); Map(0x4b, SEB2, Accumulator);
Map(0x4c, JMP, Absolute); Map(0x4d, EOR, Absolute);
Map(0x4e, LSR, Absolute); Map(0x4f, SEB2, ZeroPage);
/* 0x50 0x5f */
Map(0x50, BVC, Relative); Map(0x51, EOR, IndirectY);
Map(0x53, BBC2, AccumulatorRelative);
Map(0x55, EOR, ZeroPageX);
Map(0x56, LSR, ZeroPageX); Map(0x57, BBC2, ZeroPageRelative);
Map(0x58, CLI, Implied); Map(0x59, EOR, AbsoluteY);
Map(0x5b, CLB2, Accumulator);
Map(0x5d, EOR, AbsoluteX);
Map(0x5e, LSR, AbsoluteX); Map(0x5f, CLB2, ZeroPage);
/* 0x60 0x6f */
Map(0x60, RTS, Implied); Map(0x61, ADC, XIndirect);
Map(0x63, BBS3, AccumulatorRelative);
Map(0x64, TST, ZeroPage); Map(0x65, ADC, ZeroPage);
Map(0x66, ROR, ZeroPage); Map(0x67, BBS3, ZeroPageRelative);
Map(0x68, PLA, Implied); Map(0x69, ADC, Immediate);
Map(0x6a, ROR, Accumulator); Map(0x6b, SEB3, Accumulator);
Map(0x6c, JMP, AbsoluteIndirect); Map(0x6d, ADC, Absolute);
Map(0x6e, ROR, Absolute); Map(0x6f, SEB3, ZeroPage);
/* 0x70 0x7f */
Map(0x70, BVS, Relative); Map(0x71, ADC, IndirectY);
Map(0x73, BBC3, AccumulatorRelative);
Map(0x75, ADC, ZeroPageX);
Map(0x76, ROR, ZeroPageX); Map(0x77, BBC3, ZeroPageRelative);
Map(0x78, SEI, Implied); Map(0x79, ADC, AbsoluteY);
Map(0x7b, CLB3, Accumulator);
Map(0x7d, ADC, AbsoluteX);
Map(0x7e, ROR, AbsoluteX); Map(0x7f, CLB3, ZeroPage);
/* 0x80 0x8f */
Map(0x80, BRA, Relative); Map(0x81, STA, XIndirect);
Map(0x82, RRF, ZeroPage); Map(0x83, BBS4, AccumulatorRelative);
Map(0x84, STY, ZeroPage); Map(0x85, STA, ZeroPage);
Map(0x86, STX, ZeroPage); Map(0x87, BBS4, ZeroPageRelative);
Map(0x88, DEY, Implied);
Map(0x8a, TXA, Implied); Map(0x8b, SEB4, Accumulator);
Map(0x8c, STY, Absolute); Map(0x8d, STA, Absolute);
Map(0x8e, STX, Absolute); Map(0x8f, SEB4, ZeroPage);
/* 0x90 0x9f */
Map(0x90, BCC, Relative); Map(0x91, STA, IndirectY);
Map(0x93, BBC4, AccumulatorRelative);
Map(0x94, STY, ZeroPageX); Map(0x95, STA, ZeroPageX);
Map(0x96, STX, ZeroPageX); Map(0x97, BBC4, ZeroPageRelative);
Map(0x98, TYA, Implied); Map(0x99, STA, AbsoluteY);
Map(0x9a, TXS, Implied); Map(0x9b, CLB4, Accumulator);
Map(0x9d, ADC, AbsoluteX);
Map(0x9f, CLB4, ZeroPage);
/* 0xa0 0xaf */
Map(0xa0, LDY, Immediate); Map(0xa1, LDA, XIndirect);
Map(0xa2, LDX, Immediate); Map(0xa3, BBS5, AccumulatorRelative);
Map(0xa4, LDY, ZeroPage); Map(0xa5, LDA, ZeroPage);
Map(0xa6, LDX, ZeroPage); Map(0xa7, BBS5, ZeroPageRelative);
Map(0xa8, TAY, Implied); Map(0xa9, LDA, Immediate);
Map(0xaa, TAX, Implied); Map(0xab, SEB5, Accumulator);
Map(0xac, LDY, Absolute); Map(0xad, LDA, Absolute);
Map(0xae, LDX, Absolute); Map(0xaf, SEB5, ZeroPage);
/* 0xb0 0xbf */
Map(0xb0, BCS, Relative); Map(0xb1, STA, IndirectY);
Map(0xb2, JMP, ZeroPageIndirect); Map(0xb3, BBC5, AccumulatorRelative);
Map(0xb4, LDY, ZeroPageX); Map(0xb5, LDA, ZeroPageX);
Map(0xb6, LDX, ZeroPageY); Map(0xb7, BBC5, ZeroPageRelative);
Map(0xb8, CLV, Implied); Map(0xb9, LDA, AbsoluteY);
Map(0xba, TSX, Implied); Map(0xbb, CLB5, Accumulator);
Map(0xbc, LDY, AbsoluteX); Map(0xbd, LDA, AbsoluteX);
Map(0xbe, LDX, AbsoluteY); Map(0xbf, CLB5, ZeroPage);
/* 0xc0 0xcf */
Map(0xc0, CPY, Immediate); Map(0xc1, CMP, XIndirect);
Map(0xc2, SLW, Implied); Map(0xc3, BBS6, AccumulatorRelative);
Map(0xc4, CPY, ZeroPage); Map(0xc5, CMP, ZeroPage);
Map(0xc6, DEC, ZeroPage); Map(0xc7, BBS6, ZeroPageRelative);
Map(0xc8, INY, Implied); Map(0xc9, CMP, Immediate);
Map(0xca, DEX, Implied); Map(0xcb, SEB6, Accumulator);
Map(0xcc, CPY, Absolute); Map(0xcd, CMP, Absolute);
Map(0xce, DEC, Absolute); Map(0xcf, SEB6, ZeroPage);
/* 0xd0 0xdf */
Map(0xd0, BNE, Relative); Map(0xd1, CMP, IndirectY);
Map(0xd3, BBC6, AccumulatorRelative);
Map(0xd5, CMP, ZeroPageX);
Map(0xd6, DEC, ZeroPageX); Map(0xd7, BBC6, ZeroPageRelative);
Map(0xd8, CLD, Implied); Map(0xd9, CMP, AbsoluteY);
Map(0xdb, CLB6, Accumulator);
Map(0xdd, CMP, AbsoluteX);
Map(0xde, DEC, AbsoluteX); Map(0xdf, CLB6, ZeroPage);
/* 0xe0 0xef */
Map(0xe0, CPX, Immediate); Map(0xe1, SBC, XIndirect);
Map(0xe2, FST, Implied); Map(0xe3, BBS7, AccumulatorRelative);
Map(0xe4, CPX, ZeroPage); Map(0xe5, SBC, ZeroPage);
Map(0xe6, INC, ZeroPage); Map(0xe7, BBS7, ZeroPageRelative);
Map(0xe8, INX, Implied); Map(0xe9, SBC, Immediate);
Map(0xea, NOP, Implied); Map(0xeb, SEB7, Accumulator);
Map(0xec, CPX, Absolute); Map(0xed, SBC, Absolute);
Map(0xee, INC, Absolute); Map(0xef, SEB7, ZeroPage);
/* 0xf0 0xff */
Map(0xf0, BEQ, Relative); Map(0xf1, SBC, IndirectY);
Map(0xf3, BBC7, AccumulatorRelative);
Map(0xf5, SBC, ZeroPageX);
Map(0xf6, INC, ZeroPageX); Map(0xf7, BBC7, ZeroPageRelative);
Map(0xf8, SED, Implied); Map(0xf9, SBC, AbsoluteY);
Map(0xfb, CLB7, Accumulator);
Map(0xfd, SBC, AbsoluteX);
Map(0xfe, INC, AbsoluteX); Map(0xff, CLB7, ZeroPage);
#undef Map
}
}
std::pair<int, InstructionSet::M50740::Instruction> Decoder::decode(const uint8_t *source, size_t length) {
const uint8_t *const end = source + length;
if(phase_ == Phase::Instruction && source != end) {
const uint8_t instruction = *source;
++source;
++consumed_;
// Determine the instruction in hand, and finish now if its undefined.
instr_ = instrucion_for_opcode(instruction);
if(instr_.operation == Operation::Invalid) {
consumed_ = 0;
return std::make_pair(1, instr_);
}
// Obtain an operand size and roll onto the correct phase.
operand_size_ = size(instr_.addressing_mode);
phase_ = operand_size_ ? Phase::AwaitingOperand : Phase::ReadyToPost;
operand_bytes_ = 0;
}
if(phase_ == Phase::AwaitingOperand && source != end) {
const int outstanding_bytes = operand_size_ - operand_bytes_;
const int bytes_to_consume = std::min(int(end - source), outstanding_bytes);
consumed_ += bytes_to_consume;
source += bytes_to_consume;
operand_bytes_ += bytes_to_consume;
if(operand_size_ == operand_bytes_) {
phase_ = Phase::ReadyToPost;
} else {
return std::make_pair(-(operand_size_ - operand_bytes_), Instruction());
}
}
if(phase_ == Phase::ReadyToPost) {
const auto result = std::make_pair(consumed_, instr_);
consumed_ = 0;
phase_ = Phase::Instruction;
return result;
}
// Decoding didn't complete, without it being clear how many more bytes are required.
return std::make_pair(0, Instruction());
}
}
}

View File

@@ -0,0 +1,39 @@
//
// Decoder.hpp
// Clock Signal
//
// Created by Thomas Harte on 15/01/21.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef InstructionSets_M50740_Decoder_hpp
#define InstructionSets_M50740_Decoder_hpp
#include "Instruction.hpp"
#include <cstddef>
#include <utility>
namespace InstructionSet {
namespace M50740 {
class Decoder {
public:
std::pair<int, Instruction> decode(const uint8_t *source, size_t length);
Instruction instrucion_for_opcode(uint8_t opcode);
private:
enum class Phase {
Instruction,
AwaitingOperand,
ReadyToPost
} phase_ = Phase::Instruction;
int operand_size_ = 0, operand_bytes_ = 0;
int consumed_ = 0;
Instruction instr_;
};
}
}
#endif /* InstructionSets_M50740_Decoder_hpp */

View File

@@ -0,0 +1,838 @@
//
// Executor.hpp
// Clock Signal
//
// Created by Thomas Harte on 16/1/21.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "Executor.hpp"
#include <algorithm>
#include <cassert>
#include <cstring>
#include "../../Machines/Utility/MemoryFuzzer.hpp"
#define LOG_PREFIX "[M50740] "
#include "../../Outputs/Log.hpp"
using namespace InstructionSet::M50740;
namespace {
constexpr int port_remap[] = {0, 1, 2, 0, 3};
}
Executor::Executor(PortHandler &port_handler) : port_handler_(port_handler) {
// Cut down the list of all generated performers to those the processor actually uses, and install that
// for future referencing by action_for.
Decoder decoder;
for(size_t c = 0; c < 256; c++) {
const auto instruction = decoder.instrucion_for_opcode(uint8_t(c));
// Treat invalid as NOP, because I've got to do _something_.
if(instruction.operation == Operation::Invalid) {
performers_[c] = performer_lookup_.performer(Operation::NOP, instruction.addressing_mode);
} else {
performers_[c] = performer_lookup_.performer(instruction.operation, instruction.addressing_mode);
}
}
// Fuzz RAM; then set anything that may be replaced by ROM to FF.
Memory::Fuzz(memory_);
memset(&memory_[0x100], 0xff, memory_.size() - 0x100);
}
void Executor::set_rom(const std::vector<uint8_t> &rom) {
// Copy into place, and reset.
const auto length = std::min(size_t(0x1000), rom.size());
memcpy(&memory_[0x2000 - length], rom.data(), length);
reset();
}
void Executor::run_for(Cycles cycles) {
// The incoming clock is divided by four; the local cycles_ count
// ensures that fractional parts are kept track of.
cycles_ += cycles;
CachingExecutor::run_for(cycles_.divide(Cycles(4)).as<int>());
}
void Executor::reset() {
// Just jump to the reset vector.
set_program_counter(uint16_t(memory_[0x1ffe] | (memory_[0x1fff] << 8)));
}
void Executor::set_interrupt_line(bool line) {
// Super hack: interrupt now, if permitted. Otherwise do nothing.
// So this will fail to catch enabling of interrupts while the line
// is active, amongst other things.
if(interrupt_line_ != line) {
interrupt_line_ = line;
// TODO: verify interrupt connection. Externally, but stubbed off here.
// if(!interrupt_disable_ && line) {
// perform_interrupt<false>(0x1ff4);
// }
}
}
uint8_t Executor::read(uint16_t address) {
address &= 0x1fff;
// Deal with RAM and ROM accesses quickly.
if(address < 0x60 || address >= 0x100) return memory_[address];
port_handler_.run_ports_for(cycles_since_port_handler_.flush<Cycles>());
switch(address) {
default:
LOG("Unrecognised read from " << PADHEX(4) << address);
return 0xff;
// "Port R"; sixteen four-bit ports
case 0xd0: case 0xd1: case 0xd2: case 0xd3: case 0xd4: case 0xd5: case 0xd6: case 0xd7:
case 0xd8: case 0xd9: case 0xda: case 0xdb: case 0xdc: case 0xdd: case 0xde: case 0xdf:
LOG("Unimplemented Port R read from " << PADHEX(4) << address);
return 0x00;
// Ports P0P3.
case 0xe0: case 0xe2:
case 0xe4: case 0xe8: {
const int port = port_remap[(address - 0xe0) >> 1];
const uint8_t input = port_handler_.get_port_input(port);
// In the direction registers, a 0 indicates input, a 1 indicates output.
return (input &~ port_directions_[port]) | (port_outputs_[port] & port_directions_[port]);
}
case 0xe1: case 0xe3:
case 0xe5: case 0xe9:
return port_directions_[port_remap[(address - 0xe0) >> 1]];
// Timers.
case 0xf9: return prescalers_[0].value;
case 0xfa: return timers_[0].value;
case 0xfb: return timers_[1].value;
case 0xfc: return prescalers_[1].value;
case 0xfd: return timers_[2].value;
case 0xfe: return interrupt_control_;
case 0xff: return timer_control_;
}
}
void Executor::set_port_output(int port) {
// Force 'output' to a 1 anywhere that a bit is set as input.
port_handler_.set_port_output(port, port_outputs_[port] | ~port_directions_[port]);
}
void Executor::write(uint16_t address, uint8_t value) {
address &= 0x1fff;
// RAM writes are easy.
if(address < 0x60) {
memory_[address] = value;
return;
}
// ROM 'writes' are almost as easy (albeit unexpected).
if(address >= 0x100) {
LOG("Attempted ROM write of " << PADHEX(2) << value << " to " << PADHEX(4) << address);
return;
}
// Push time to the port handler.
port_handler_.run_ports_for(cycles_since_port_handler_.flush<Cycles>());
switch(address) {
default:
LOG("Unrecognised write of " << PADHEX(2) << value << " to " << PADHEX(4) << address);
break;
// "Port R"; sixteen four-bit ports
case 0xd0: case 0xd1: case 0xd2: case 0xd3: case 0xd4: case 0xd5: case 0xd6: case 0xd7:
case 0xd8: case 0xd9: case 0xda: case 0xdb: case 0xdc: case 0xdd: case 0xde: case 0xdf:
LOG("Unimplemented Port R write of " << PADHEX(2) << value << " from " << PADHEX(4) << address);
break;
// Ports P0P3.
case 0xe0: case 0xe2:
case 0xe4: case 0xe8: {
const int port = port_remap[(address - 0xe0) >> 1];
port_outputs_[port] = value;
set_port_output(port);
}
break;
case 0xe1: case 0xe3:
case 0xe5: case 0xe9: {
const int port = port_remap[(address - 0xe0) >> 1];
port_directions_[port] = value;
set_port_output(port);
} break;
// Timers.
//
// Reloading of value with the reload value is a guess, based upon what I take
// to be the intended usage of timer 2 in handling key repeat on the Apple IIgs.
case 0xf9: prescalers_[0].value = prescalers_[0].reload_value = value; break;
case 0xfa: timers_[0].value = timers_[0].reload_value = value; break;
case 0xfb: timers_[1].value = timers_[1].reload_value = value; break;
case 0xfc: prescalers_[1].value = prescalers_[1].reload_value = value; break;
case 0xfd: timers_[2].value = timers_[2].reload_value = value; break;
case 0xfe: interrupt_control_ = value; break;
case 0xff: timer_control_ = value; break;
}
}
void Executor::push(uint8_t value) {
write(s_, value);
--s_;
}
uint8_t Executor::pull() {
++s_;
return read(s_);
}
void Executor::set_flags(uint8_t flags) {
negative_result_ = flags;
overflow_result_ = uint8_t(flags << 1);
index_mode_ = flags & 0x20;
decimal_mode_ = flags & 0x08;
interrupt_disable_ = flags & 0x04;
zero_result_ = !(flags & 0x02);
carry_flag_ = flags & 0x01;
}
uint8_t Executor::flags() {
return
(negative_result_ & 0x80) |
((overflow_result_ & 0x80) >> 1) |
(index_mode_ ? 0x20 : 0x00) |
(decimal_mode_ ? 0x08 : 0x00) |
interrupt_disable_ |
(zero_result_ ? 0x00 : 0x02) |
carry_flag_;
}
template<bool is_brk> inline void Executor::perform_interrupt(uint16_t vector) {
// BRK has an unused operand.
++program_counter_;
push(uint8_t(program_counter_ >> 8));
push(uint8_t(program_counter_ & 0xff));
push(flags() | (is_brk ? 0x10 : 0x00));
set_program_counter(uint16_t(memory_[vector] | (memory_[vector+1] << 8)));
}
template <Operation operation, AddressingMode addressing_mode> void Executor::perform() {
// Post cycle cost; this emulation _does not provide accurate timing_.
#define TLength(mode, base) case AddressingMode::mode: subtract_duration(base + t_lengths[index_mode_]); break;
#define Length(mode, base) case AddressingMode::mode: subtract_duration(base); break;
switch(operation) {
case Operation::ADC: case Operation::AND: case Operation::CMP: case Operation::EOR:
case Operation::LDA: case Operation::ORA: case Operation::SBC:
{
constexpr int t_lengths[] = {
0,
operation == Operation::LDA ? 2 : (operation == Operation::CMP ? 1 : 3)
};
switch(addressing_mode) {
TLength(XIndirect, 6);
TLength(ZeroPage, 3);
TLength(Immediate, 2);
TLength(Absolute, 4);
TLength(IndirectY, 6);
TLength(ZeroPageX, 4);
TLength(AbsoluteY, 5);
TLength(AbsoluteX, 5);
default: assert(false);
}
} break;
case Operation::ASL: case Operation::DEC: case Operation::INC: case Operation::LSR:
case Operation::ROL: case Operation::ROR:
switch(addressing_mode) {
Length(ZeroPage, 5);
Length(Accumulator, 2);
Length(Absolute, 6);
Length(ZeroPageX, 6);
Length(AbsoluteX, 7);
default: assert(false);
}
break;
case Operation::BBC0: case Operation::BBC1: case Operation::BBC2: case Operation::BBC3:
case Operation::BBC4: case Operation::BBC5: case Operation::BBC6: case Operation::BBC7:
case Operation::BBS0: case Operation::BBS1: case Operation::BBS2: case Operation::BBS3:
case Operation::BBS4: case Operation::BBS5: case Operation::BBS6: case Operation::BBS7:
switch(addressing_mode) {
Length(AccumulatorRelative, 4);
Length(ZeroPageRelative, 5);
default: assert(false);
}
break;
case Operation::BPL: case Operation::BMI: case Operation::BEQ: case Operation::BNE:
case Operation::BCS: case Operation::BCC: case Operation::BVS: case Operation::BVC:
case Operation::INX: case Operation::INY:
subtract_duration(2);
break;
case Operation::CPX: case Operation::CPY: case Operation::BIT: case Operation::LDX:
case Operation::LDY:
switch(addressing_mode) {
Length(Immediate, 2);
Length(ZeroPage, 3);
Length(Absolute, 4);
Length(ZeroPageX, 4);
Length(ZeroPageY, 4);
Length(AbsoluteX, 5);
Length(AbsoluteY, 5);
default: assert(false);
}
break;
case Operation::BRA: subtract_duration(4); break;
case Operation::BRK: subtract_duration(7); break;
case Operation::CLB0: case Operation::CLB1: case Operation::CLB2: case Operation::CLB3:
case Operation::CLB4: case Operation::CLB5: case Operation::CLB6: case Operation::CLB7:
case Operation::SEB0: case Operation::SEB1: case Operation::SEB2: case Operation::SEB3:
case Operation::SEB4: case Operation::SEB5: case Operation::SEB6: case Operation::SEB7:
switch(addressing_mode) {
Length(Accumulator, 2);
Length(ZeroPage, 5);
default: assert(false);
}
break;
case Operation::CLC: case Operation::CLD: case Operation::CLT: case Operation::CLV:
case Operation::CLI:
case Operation::DEX: case Operation::DEY: case Operation::FST: case Operation::NOP:
case Operation::SEC: case Operation::SED: case Operation::SEI: case Operation::SET:
case Operation::SLW: case Operation::STP: case Operation::TAX: case Operation::TAY:
case Operation::TSX: case Operation::TXA: case Operation::TXS: case Operation::TYA:
subtract_duration(2);
break;
case Operation::COM: subtract_duration(5); break;
case Operation::JMP:
switch(addressing_mode) {
Length(Absolute, 3);
Length(AbsoluteIndirect, 5);
Length(ZeroPageIndirect, 4);
default: assert(false);
}
break;
case Operation::JSR:
switch(addressing_mode) {
Length(ZeroPageIndirect, 7);
Length(Absolute, 6);
Length(SpecialPage, 5);
default: assert(false);
}
break;
case Operation::LDM: subtract_duration(4); break;
case Operation::PHA: case Operation::PHP: case Operation::TST:
subtract_duration(3);
break;
case Operation::PLA: case Operation::PLP:
subtract_duration(4);
break;
case Operation::RRF: subtract_duration(8); break;
case Operation::RTI: subtract_duration(6); break;
case Operation::RTS: subtract_duration(6); break;
case Operation::STA: case Operation::STX: case Operation::STY:
switch(addressing_mode) {
Length(XIndirect, 7);
Length(ZeroPage, 4);
Length(Absolute, 5);
Length(IndirectY, 7);
Length(ZeroPageX, 5);
Length(ZeroPageY, 5);
Length(AbsoluteY, 6);
Length(AbsoluteX, 6);
default: assert(false);
}
break;
default: assert(false);
}
// Deal with all modes that don't access memory up here;
// those that access memory will go through a slightly longer
// sequence below that wraps the address and checks whether
// a write is valid [if required].
unsigned int address;
#define next8() memory_[(program_counter_ + 1) & 0x1fff]
#define next16() uint16_t(memory_[(program_counter_ + 1) & 0x1fff] | (memory_[(program_counter_ + 2) & 0x1fff] << 8))
// Underlying assumption below: the instruction stream will never
// overlap with IO ports.
switch(addressing_mode) {
// Addressing modes with no further memory access.
case AddressingMode::Implied:
perform<operation>(nullptr);
++program_counter_;
return;
case AddressingMode::Accumulator:
perform<operation>(&a_);
++program_counter_;
return;
case AddressingMode::Immediate:
perform<operation>(&next8());
program_counter_ += 2;
return;
// Special-purpose addressing modes.
case AddressingMode::Relative:
address = unsigned(program_counter_ + 1 + size(addressing_mode) + int8_t(next8()));
break;
case AddressingMode::SpecialPage: address = 0x1f00 | next8(); break;
case AddressingMode::ImmediateZeroPage:
// LDM only...
write(memory_[(program_counter_+2)&0x1fff], memory_[(program_counter_+1)&0x1fff]);
program_counter_ += 1 + size(addressing_mode);
return;
case AddressingMode::AccumulatorRelative:
case AddressingMode::ZeroPageRelative: {
// Order of bytes is: (i) zero page address; (ii) relative jump.
uint8_t value;
if constexpr (addressing_mode == AddressingMode::AccumulatorRelative) {
value = a_;
address = unsigned(program_counter_ + 1 + size(addressing_mode) + int8_t(next8()));
} else {
value = read(next8());
address = unsigned(program_counter_ + 1 + size(addressing_mode) + int8_t(memory_[(program_counter_+2)&0x1fff]));
}
program_counter_ += 1 + size(addressing_mode);
switch(operation) {
case Operation::BBS0: case Operation::BBS1: case Operation::BBS2: case Operation::BBS3:
case Operation::BBS4: case Operation::BBS5: case Operation::BBS6: case Operation::BBS7: {
if constexpr (operation >= Operation::BBS0 && operation <= Operation::BBS7) {
constexpr uint8_t mask = 1 << (int(operation) - int(Operation::BBS0));
if(value & mask) {
set_program_counter(uint16_t(address));
subtract_duration(2);
}
}
} return;
case Operation::BBC0: case Operation::BBC1: case Operation::BBC2: case Operation::BBC3:
case Operation::BBC4: case Operation::BBC5: case Operation::BBC6: case Operation::BBC7: {
if constexpr (operation >= Operation::BBC0 && operation <= Operation::BBC7) {
constexpr uint8_t mask = 1 << (int(operation) - int(Operation::BBC0));
if(!(value & mask)) {
set_program_counter(uint16_t(address));
subtract_duration(2);
}
}
} return;
default: assert(false);
}
} break;
// Addressing modes with a memory access.
case AddressingMode::Absolute: address = next16(); break;
case AddressingMode::AbsoluteX: address = next16() + x_; break;
case AddressingMode::AbsoluteY: address = next16() + y_; break;
case AddressingMode::ZeroPage: address = next8(); break;
case AddressingMode::ZeroPageX: address = (next8() + x_) & 0xff; break;
case AddressingMode::ZeroPageY: address = (next8() + y_) & 0xff; break;
case AddressingMode::ZeroPageIndirect:
address = next8();
address = unsigned(memory_[address] | (memory_[(address + 1) & 0xff] << 8));
break;
case AddressingMode::XIndirect:
address = (next8() + x_) & 0xff;
address = unsigned(memory_[address] | (memory_[(address + 1)&0xff] << 8));
break;
case AddressingMode::IndirectY:
address = unsigned((memory_[next8()] | (memory_[(next8()+1)&0xff] << 8)) + y_);
break;
case AddressingMode::AbsoluteIndirect:
address = next16();
address = unsigned(memory_[address & 0x1fff] | (memory_[(address + 1) & 0x1fff] << 8));
break;
default:
assert(false);
}
#undef next16
#undef next8
program_counter_ += 1 + size(addressing_mode);
// Check for a branch; those don't go through the memory accesses below.
switch(operation) {
case Operation::BRA: case Operation::JMP:
set_program_counter(uint16_t(address));
return;
case Operation::JSR: {
// Push one less than the actual return address.
const auto return_address = program_counter_ - 1;
push(uint8_t(return_address >> 8));
push(uint8_t(return_address & 0xff));
set_program_counter(uint16_t(address));
} return;
#define Bcc(c) if(c) { set_program_counter(uint16_t(address)); subtract_duration(2); } return
case Operation::BPL: Bcc(!(negative_result_&0x80));
case Operation::BMI: Bcc(negative_result_&0x80);
case Operation::BEQ: Bcc(!zero_result_);
case Operation::BNE: Bcc(zero_result_);
case Operation::BCS: Bcc(carry_flag_);
case Operation::BCC: Bcc(!carry_flag_);
case Operation::BVS: Bcc(overflow_result_ & 0x80);
case Operation::BVC: Bcc(!(overflow_result_ & 0x80));
#undef Bcc
default: break;
}
assert(access_type(operation) != AccessType::None);
if constexpr(access_type(operation) == AccessType::Read) {
uint8_t source = read(uint16_t(address));
perform<operation>(&source);
return;
}
uint8_t value;
if constexpr(access_type(operation) == AccessType::ReadModifyWrite) {
value = read(uint16_t(address));
} else {
value = 0xff;
}
perform<operation>(&value);
write(uint16_t(address), value);
}
template <Operation operation> void Executor::perform(uint8_t *operand [[maybe_unused]]) {
#define set_nz(a) negative_result_ = zero_result_ = (a)
switch(operation) {
case Operation::LDA:
if(index_mode_) {
write(x_, *operand);
set_nz(*operand);
} else {
set_nz(a_ = *operand);
}
break;
case Operation::LDX: set_nz(x_ = *operand); break;
case Operation::LDY: set_nz(y_ = *operand); break;
case Operation::STA: *operand = a_; break;
case Operation::STX: *operand = x_; break;
case Operation::STY: *operand = y_; break;
case Operation::TXA: set_nz(a_ = x_); break;
case Operation::TYA: set_nz(a_ = y_); break;
case Operation::TXS: s_ = x_; break;
case Operation::TAX: set_nz(x_ = a_); break;
case Operation::TAY: set_nz(y_ = a_); break;
case Operation::TSX: set_nz(x_ = s_); break;
case Operation::SEB0: case Operation::SEB1: case Operation::SEB2: case Operation::SEB3:
case Operation::SEB4: case Operation::SEB5: case Operation::SEB6: case Operation::SEB7:
if constexpr(operation >= Operation::SEB0 && operation <= Operation::SEB7) {
*operand |= 1 << (int(operation) - int(Operation::SEB0));
}
break;
case Operation::CLB0: case Operation::CLB1: case Operation::CLB2: case Operation::CLB3:
case Operation::CLB4: case Operation::CLB5: case Operation::CLB6: case Operation::CLB7:
if constexpr(operation >= Operation::CLB0 && operation <= Operation::CLB7) {
*operand &= ~(1 << (int(operation) - int(Operation::CLB0)));
}
break;
case Operation::CLI: interrupt_disable_ = 0x00; break;
case Operation::SEI: interrupt_disable_ = 0x04; break;
case Operation::CLT: index_mode_ = false; break;
case Operation::SET: index_mode_ = true; break;
case Operation::CLD: decimal_mode_ = false; break;
case Operation::SED: decimal_mode_ = true; break;
case Operation::CLC: carry_flag_ = 0; break;
case Operation::SEC: carry_flag_ = 1; break;
case Operation::CLV: overflow_result_ = 0; break;
case Operation::DEX: --x_; set_nz(x_); break;
case Operation::INX: ++x_; set_nz(x_); break;
case Operation::DEY: --y_; set_nz(y_); break;
case Operation::INY: ++y_; set_nz(y_); break;
case Operation::DEC: --*operand; set_nz(*operand); break;
case Operation::INC: ++*operand; set_nz(*operand); break;
case Operation::RTS: {
uint16_t target = pull();
target |= pull() << 8;
set_program_counter(target+1);
--program_counter_; // To undo the unavoidable increment
// after exiting from here.
} break;
case Operation::RTI: {
set_flags(pull());
uint16_t target = pull();
target |= pull() << 8;
set_program_counter(target);
--program_counter_; // To undo the unavoidable increment
// after exiting from here.
} break;
case Operation::BRK:
perform_interrupt<true>(0x1ff4);
--program_counter_; // To undo the unavoidable increment
// after exiting from here.
break;
case Operation::STP: set_is_stopped(true); break;
case Operation::COM: set_nz(*operand ^= 0xff); break;
case Operation::FST: case Operation::SLW: case Operation::NOP:
// TODO: communicate FST and SLW onwards, I imagine. Find out what they interface with.
break;
case Operation::PHA: push(a_); break;
case Operation::PHP: push(flags()); break;
case Operation::PLA: set_nz(a_ = pull()); break;
case Operation::PLP: set_flags(pull()); break;
case Operation::ASL:
carry_flag_ = *operand >> 7;
*operand <<= 1;
set_nz(*operand);
break;
case Operation::LSR:
carry_flag_ = *operand & 1;
*operand >>= 1;
set_nz(*operand);
break;
case Operation::ROL: {
const uint8_t temp8 = uint8_t((*operand << 1) | carry_flag_);
carry_flag_ = *operand >> 7;
set_nz(*operand = temp8);
} break;
case Operation::ROR: {
const uint8_t temp8 = uint8_t((*operand >> 1) | (carry_flag_ << 7));
carry_flag_ = *operand & 1;
set_nz(*operand = temp8);
} break;
case Operation::RRF:
*operand = uint8_t((*operand >> 4) | (*operand << 4));
break;
case Operation::BIT:
zero_result_ = *operand & a_;
negative_result_ = *operand;
overflow_result_ = uint8_t(*operand << 1);
break;
case Operation::TST:
set_nz(*operand);
break;
/*
Operations affected by the index mode flag: ADC, AND, CMP, EOR, LDA, ORA, and SBC.
*/
#define index(op) \
if(index_mode_) { \
uint8_t t = read(x_); \
op(t); \
write(x_, t); \
} else { \
op(a_); \
}
#define op_ora(x) set_nz(x |= *operand)
#define op_and(x) set_nz(x &= *operand)
#define op_eor(x) set_nz(x ^= *operand)
case Operation::ORA: index(op_ora); break;
case Operation::AND: index(op_and); break;
case Operation::EOR: index(op_eor); break;
#undef op_eor
#undef op_and
#undef op_ora
#undef index
#define op_cmp(x) { \
const uint16_t temp16 = x - *operand; \
set_nz(uint8_t(temp16)); \
carry_flag_ = (~temp16 >> 8)&1; \
}
case Operation::CMP:
if(index_mode_) {
op_cmp(read(x_));
} else {
op_cmp(a_);
}
break;
case Operation::CPX: op_cmp(x_); break;
case Operation::CPY: op_cmp(y_); break;
#undef op_cmp
case Operation::SBC:
case Operation::ADC: {
const uint8_t a = index_mode_ ? read(x_) : a_;
if(decimal_mode_) {
if(operation == Operation::ADC) {
uint16_t partials = 0;
int result = carry_flag_;
#define nibble(mask, limit, adjustment, carry) \
result += (a & mask) + (*operand & mask); \
partials += result & mask; \
if(result >= limit) result = ((result + (adjustment)) & (carry - 1)) + carry;
nibble(0x000f, 0x000a, 0x0006, 0x00010);
nibble(0x00f0, 0x00a0, 0x0060, 0x00100);
#undef nibble
overflow_result_ = uint8_t((partials ^ a) & (partials ^ *operand));
set_nz(uint8_t(result));
carry_flag_ = (result >> 8) & 1;
} else {
unsigned int result = 0;
unsigned int borrow = carry_flag_ ^ 1;
const uint16_t decimal_result = uint16_t(a - *operand - borrow);
#define nibble(mask, adjustment, carry) \
result += (a & mask) - (*operand & mask) - borrow; \
if(result > mask) result -= adjustment; \
borrow = (result > mask) ? carry : 0; \
result &= (carry - 1);
nibble(0x000f, 0x0006, 0x00010);
nibble(0x00f0, 0x0060, 0x00100);
#undef nibble
overflow_result_ = uint8_t((decimal_result ^ a) & (~decimal_result ^ *operand));
set_nz(uint8_t(result));
carry_flag_ = ((borrow >> 8)&1)^1;
}
} else {
int result;
if(operation == Operation::ADC) {
result = int(a + *operand + carry_flag_);
overflow_result_ = uint8_t((result ^ a) & (result ^ *operand));
} else {
result = int(a + ~*operand + carry_flag_);
overflow_result_ = uint8_t((result ^ a) & (result ^ ~*operand));
}
set_nz(uint8_t(result));
carry_flag_ = (result >> 8) & 1;
}
if(index_mode_) {
write(x_, a);
} else {
a_ = a;
}
} break;
/*
Already removed from the instruction stream:
* all branches and jumps;
* LDM.
*/
default:
LOG("Unimplemented operation: " << operation);
assert(false);
}
#undef set_nz
}
inline void Executor::subtract_duration(int duration) {
// Pass along.
CachingExecutor::subtract_duration(duration);
// Update count for potential port accesses.
cycles_since_port_handler_ += Cycles(duration);
// Update timer 1 and 2 prescaler.
constexpr int t12_divider = 4; // A divide by 4 has already been applied before counting instruction lengths; therefore
// this additional divide by 4 produces the correct net divide by 16.
timer_divider_ += duration;
const int t12_ticks = update_timer(prescalers_[0], timer_divider_ / t12_divider);
timer_divider_ &= (t12_divider-1);
// Update timers 1 and 2. TODO: interrupts (elsewhere?).
if(update_timer(timers_[0], t12_ticks)) interrupt_control_ |= 0x20;
if(update_timer(timers_[1], t12_ticks)) interrupt_control_ |= 0x08;
// If timer X is disabled, stop.
if(timer_control_&0x20) {
return;
}
// Update timer X prescaler.
switch(timer_control_ & 0x0c) {
default: {
const int tx_ticks = update_timer(prescalers_[1], t12_ticks); // TODO: don't hard code this. And is this even right?
if(update_timer(timers_[2], tx_ticks))
timer_control_ |= 0x80; // TODO: interrupt result of this.
} break;
case 0x04:
LOG("TODO: Timer X; Pulse output mode");
break;
case 0x08:
LOG("TODO: Timer X; Event counter mode");
break;
case 0x0c:
LOG("TODO: Timer X; Pulse width measurement mode");
break;
}
}
inline int Executor::update_timer(Timer &timer, int count) {
const int next_value = timer.value - count;
if(next_value < 0) {
// Determine how many reloads were required to get above zero.
const int reload_value = timer.reload_value ? timer.reload_value : 256;
const int underflow_count = 1 - next_value / reload_value;
timer.value = uint8_t((next_value % reload_value) + timer.reload_value);
return underflow_count;
}
timer.value = uint8_t(next_value);
return 0;
}
uint8_t Executor::get_output_mask(int port) {
return port_directions_[port];
}

View File

@@ -0,0 +1,180 @@
//
// Executor.h
// Clock Signal
//
// Created by Thomas Harte on 16/01/21.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Executor_h
#define Executor_h
#include "Instruction.hpp"
#include "Parser.hpp"
#include "../CachingExecutor.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
#include <array>
#include <cstdint>
#include <vector>
namespace InstructionSet {
namespace M50740 {
class Executor;
using CachingExecutor = CachingExecutor<Executor, 0x1fff, 255, Instruction, false>;
struct PortHandler {
virtual void run_ports_for(Cycles) = 0;
virtual void set_port_output(int port, uint8_t value) = 0;
virtual uint8_t get_port_input(int port) = 0;
};
/*!
Executes M50740 code subject to heavy limitations:
* the instruction stream cannot run across any of the specialised IO addresses; and
* timing is correct to whole-opcode boundaries only.
*/
class Executor: public CachingExecutor {
public:
Executor(PortHandler &);
void set_rom(const std::vector<uint8_t> &rom);
void reset();
void set_interrupt_line(bool);
uint8_t get_output_mask(int port);
/*!
Runs, in discrete steps, the minimum number of instructions as it takes to complete at least @c cycles.
*/
void run_for(Cycles cycles);
private:
// MARK: - CachingExecutor-facing interface.
friend CachingExecutor;
/*!
Maps instructions to performers; called by the CachingExecutor and for this instruction set, extremely trivial.
*/
inline PerformerIndex action_for(Instruction instruction) {
// This is a super-simple processor, so the opcode can be used directly to index the performers.
return instruction.opcode;
}
/*!
Parses from @c start and no later than @c max_address, using the CachingExecutor as a target.
*/
inline void parse(uint16_t start, uint16_t closing_bound) {
Parser<Executor, false> parser;
parser.parse(*this, &memory_[0], start & 0x1fff, closing_bound);
}
private:
// MARK: - Internal framework for generator performers.
/*!
Provides dynamic lookup of @c perform(Executor*).
*/
class PerformerLookup {
public:
PerformerLookup() {
fill<int(MinOperation)>(performers_);
}
Performer performer(Operation operation, AddressingMode addressing_mode) {
const auto index =
(int(operation) - MinOperation) * (1 + MaxAddressingMode - MinAddressingMode) +
(int(addressing_mode) - MinAddressingMode);
return performers_[index];
}
private:
Performer performers_[(1 + MaxAddressingMode - MinAddressingMode) * (1 + MaxOperation - MinOperation)];
template<int operation, int addressing_mode> void fill_operation(Performer *target) {
*target = &Executor::perform<Operation(operation), AddressingMode(addressing_mode)>;
if constexpr (addressing_mode+1 <= MaxAddressingMode) {
fill_operation<operation, addressing_mode+1>(target + 1);
}
}
template<int operation> void fill(Performer *target) {
fill_operation<operation, int(MinAddressingMode)>(target);
target += 1 + MaxAddressingMode - MinAddressingMode;
if constexpr (operation+1 <= MaxOperation) {
fill<operation+1>(target);
}
}
};
inline static PerformerLookup performer_lookup_;
/*!
Performs @c operation using @c operand as the value fetched from memory, if any.
*/
template <Operation operation> void perform(uint8_t *operand);
/*!
Performs @c operation in @c addressing_mode.
*/
template <Operation operation, AddressingMode addressing_mode> void perform();
private:
// MARK: - Instruction set state.
// Memory.
std::array<uint8_t, 0x2000> memory_;
// Registers.
uint8_t a_ = 0, x_ = 0, y_ = 0, s_ = 0;
uint8_t negative_result_ = 0;
uint8_t zero_result_ = 0;
uint8_t interrupt_disable_ = 0x04;
uint8_t carry_flag_ = 0;
uint8_t overflow_result_ = 0;
bool index_mode_ = false;
bool decimal_mode_ = false;
// IO ports.
uint8_t port_directions_[4] = {0x00, 0x00, 0x00, 0x00};
uint8_t port_outputs_[4] = {0xff, 0xff, 0xff, 0xff};
// Timers.
struct Timer {
uint8_t value = 0xff, reload_value = 0xff;
};
int timer_divider_ = 0;
Timer timers_[3], prescalers_[2];
inline int update_timer(Timer &timer, int count);
// Interrupt and timer control.
uint8_t interrupt_control_ = 0, timer_control_ = 0;
bool interrupt_line_ = false;
// Access helpers.
inline uint8_t read(uint16_t address);
inline void write(uint16_t address, uint8_t value);
inline void push(uint8_t value);
inline uint8_t pull();
inline void set_flags(uint8_t);
inline uint8_t flags();
template<bool is_brk> inline void perform_interrupt(uint16_t vector);
inline void set_port_output(int port);
// MARK: - Execution time
Cycles cycles_;
Cycles cycles_since_port_handler_;
PortHandler &port_handler_;
inline void subtract_duration(int duration);
};
}
}
#endif /* Executor_h */

View File

@@ -0,0 +1,242 @@
//
// Instruction.hpp
// Clock Signal
//
// Created by Thomas Harte on 15/01/21.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef InstructionSets_M50740_Instruction_h
#define InstructionSets_M50740_Instruction_h
#include <cstdint>
#include <iomanip>
#include <string>
#include <sstream>
#include "../AccessType.hpp"
namespace InstructionSet {
namespace M50740 {
enum class AddressingMode {
Implied, Accumulator, Immediate,
Absolute, AbsoluteX, AbsoluteY,
ZeroPage, ZeroPageX, ZeroPageY,
XIndirect, IndirectY,
Relative,
AbsoluteIndirect, ZeroPageIndirect,
SpecialPage,
ImmediateZeroPage,
AccumulatorRelative, ZeroPageRelative
};
static constexpr auto MaxAddressingMode = int(AddressingMode::ZeroPageRelative);
static constexpr auto MinAddressingMode = int(AddressingMode::Implied);
constexpr int size(AddressingMode mode) {
// This is coupled to the AddressingMode list above; be careful!
constexpr int sizes[] = {
0, 0, 1,
2, 2, 2,
1, 1, 1,
1, 1,
1,
2, 1,
1,
2,
1, 2
};
static_assert(sizeof(sizes)/sizeof(*sizes) == int(MaxAddressingMode) + 1);
return sizes[int(mode)];
}
enum class Operation: uint8_t {
Invalid,
// Operations that don't access memory.
BBC0, BBC1, BBC2, BBC3, BBC4, BBC5, BBC6, BBC7,
BBS0, BBS1, BBS2, BBS3, BBS4, BBS5, BBS6, BBS7,
BCC, BCS,
BEQ, BMI, BNE, BPL,
BVC, BVS, BRA, BRK,
JMP, JSR,
RTI, RTS,
CLC, CLD, CLI, CLT, CLV,
SEC, SED, SEI, SET,
INX, INY, DEX, DEY,
FST, SLW,
NOP,
PHA, PHP, PLA, PLP,
STP,
TAX, TAY, TSX, TXA,
TXS, TYA,
// Read operations.
ADC, SBC,
AND, ORA, EOR, BIT,
CMP, CPX, CPY,
LDA, LDX, LDY,
TST,
// Read-modify-write operations.
ASL, LSR,
CLB0, CLB1, CLB2, CLB3, CLB4, CLB5, CLB6, CLB7,
SEB0, SEB1, SEB2, SEB3, SEB4, SEB5, SEB6, SEB7,
COM,
DEC, INC,
ROL, ROR, RRF,
// Write operations.
LDM,
STA, STX, STY,
};
static constexpr auto MaxOperation = int(Operation::STY);
static constexpr auto MinOperation = int(Operation::BBC0);
constexpr AccessType access_type(Operation operation) {
if(operation < Operation::ADC) return AccessType::None;
if(operation < Operation::ASL) return AccessType::Read;
if(operation < Operation::LDM) return AccessType::ReadModifyWrite;
return AccessType::Write;
}
constexpr bool uses_index_mode(Operation operation) {
return
operation == Operation::ADC || operation == Operation::AND ||
operation == Operation::CMP || operation == Operation::EOR ||
operation == Operation::LDA || operation == Operation::ORA ||
operation == Operation::SBC;
}
/*!
@returns The name of @c operation.
*/
inline constexpr const char *operation_name(Operation operation) {
#define MAP(x) case Operation::x: return #x;
switch(operation) {
default: break;
MAP(BBC0); MAP(BBC1); MAP(BBC2); MAP(BBC3); MAP(BBC4); MAP(BBC5); MAP(BBC6); MAP(BBC7);
MAP(BBS0); MAP(BBS1); MAP(BBS2); MAP(BBS3); MAP(BBS4); MAP(BBS5); MAP(BBS6); MAP(BBS7);
MAP(BCC); MAP(BCS); MAP(BEQ); MAP(BMI); MAP(BNE); MAP(BPL); MAP(BVC); MAP(BVS);
MAP(BRA); MAP(BRK); MAP(JMP); MAP(JSR); MAP(RTI); MAP(RTS); MAP(CLC); MAP(CLD);
MAP(CLI); MAP(CLT); MAP(CLV); MAP(SEC); MAP(SED); MAP(SEI); MAP(SET); MAP(INX);
MAP(INY); MAP(DEX); MAP(DEY); MAP(FST); MAP(SLW); MAP(NOP); MAP(PHA); MAP(PHP);
MAP(PLA); MAP(PLP); MAP(STP); MAP(TAX); MAP(TAY); MAP(TSX); MAP(TXA); MAP(TXS);
MAP(TYA); MAP(ADC); MAP(SBC); MAP(AND); MAP(ORA); MAP(EOR); MAP(BIT); MAP(CMP);
MAP(CPX); MAP(CPY); MAP(LDA); MAP(LDX); MAP(LDY); MAP(TST); MAP(ASL); MAP(LSR);
MAP(CLB0); MAP(CLB1); MAP(CLB2); MAP(CLB3); MAP(CLB4); MAP(CLB5); MAP(CLB6); MAP(CLB7);
MAP(SEB0); MAP(SEB1); MAP(SEB2); MAP(SEB3); MAP(SEB4); MAP(SEB5); MAP(SEB6); MAP(SEB7);
MAP(COM); MAP(DEC); MAP(INC); MAP(ROL); MAP(ROR); MAP(RRF); MAP(LDM); MAP(STA);
MAP(STX); MAP(STY);
}
#undef MAP
return "???";
}
inline std::ostream &operator <<(std::ostream &stream, Operation operation) {
stream << operation_name(operation);
return stream;
}
/*!
@returns The name of @c addressing_mode.
*/
inline constexpr const char *addressing_mode_name(AddressingMode addressing_mode) {
switch(addressing_mode) {
default: break;
case AddressingMode::Implied: return "";
case AddressingMode::Accumulator: return "A";
case AddressingMode::Immediate: return "#";
case AddressingMode::Absolute: return "abs";
case AddressingMode::AbsoluteX: return "abs, x";
case AddressingMode::AbsoluteY: return "abs, y";
case AddressingMode::ZeroPage: return "zp";
case AddressingMode::ZeroPageX: return "zp, x";
case AddressingMode::ZeroPageY: return "zp, y";
case AddressingMode::XIndirect: return "((zp, x))";
case AddressingMode::IndirectY: return "((zp), y)";
case AddressingMode::Relative: return "rel";
case AddressingMode::AbsoluteIndirect: return "(abs)";
case AddressingMode::ZeroPageIndirect: return "(zp)";
case AddressingMode::SpecialPage: return "\\sp";
case AddressingMode::ImmediateZeroPage: return "#, zp";
case AddressingMode::AccumulatorRelative: return "A, rel";
case AddressingMode::ZeroPageRelative: return "zp, rel";
}
return "???";
}
inline std::ostream &operator <<(std::ostream &stream, AddressingMode mode) {
stream << addressing_mode_name(mode);
return stream;
}
/*!
@returns The way that the address for an operation with @c addressing_mode and encoded starting from @c operation
would appear in an assembler. E.g. '$5a' for that zero page address, or '$5a, x' for zero-page indexed from $5a. This function
may access up to three bytes from @c operation onwards.
*/
inline std::string address(AddressingMode addressing_mode, const uint8_t *operation, uint16_t program_counter) {
std::stringstream output;
output << std::hex;
#define NUM(x) std::setfill('0') << std::setw(2) << int(x)
#define NUM4(x) std::setfill('0') << std::setw(4) << int(x)
switch(addressing_mode) {
default: return "???";
case AddressingMode::Implied: return "";
case AddressingMode::Accumulator: return "A ";
case AddressingMode::Immediate: output << "#$" << NUM(operation[1]); break;
case AddressingMode::Absolute: output << "$" << NUM(operation[2]) << NUM(operation[1]); break;
case AddressingMode::AbsoluteX: output << "$" << NUM(operation[2]) << NUM(operation[1]) << ", x"; break;
case AddressingMode::AbsoluteY: output << "$" << NUM(operation[2]) << NUM(operation[1]) << ", y"; break;
case AddressingMode::ZeroPage: output << "$" << NUM(operation[1]); break;
case AddressingMode::ZeroPageX: output << "$" << NUM(operation[1]) << ", x"; break;
case AddressingMode::ZeroPageY: output << "$" << NUM(operation[1]) << ", y"; break;
case AddressingMode::XIndirect: output << "(($" << NUM(operation[1]) << ", x))"; break;
case AddressingMode::IndirectY: output << "(($" << NUM(operation[1]) << "), y)"; break;
case AddressingMode::Relative: output << "#$" << NUM4(2 + program_counter + int8_t(operation[1])); break;
case AddressingMode::AbsoluteIndirect: output << "($" << NUM(operation[2]) << NUM(operation[1]) << ") "; break;
case AddressingMode::ZeroPageIndirect: output << "($" << NUM(operation[1]) << ")"; break;
case AddressingMode::SpecialPage: output << "$1f" << NUM(operation[1]); break;
case AddressingMode::ImmediateZeroPage: output << "#$" << NUM(operation[1]) << ", $" << NUM(operation[2]); break;
case AddressingMode::AccumulatorRelative: output << "A, $" << NUM4(2 + program_counter + int8_t(operation[1])); break;
case AddressingMode::ZeroPageRelative:
output << "$" << NUM(operation[1]) << ", $" << NUM4(3 + program_counter + int8_t(operation[2]));
break;
}
#undef NUM4
#undef NUM
return output.str();
}
/*!
Models a complete M50740-style instruction, including its operation, addressing mode and opcode.
*/
struct Instruction {
Operation operation = Operation::Invalid;
AddressingMode addressing_mode = AddressingMode::Implied;
uint8_t opcode = 0;
Instruction(Operation operation, AddressingMode addressing_mode, uint8_t opcode) : operation(operation), addressing_mode(addressing_mode), opcode(opcode) {}
Instruction(uint8_t opcode) : opcode(opcode) {}
Instruction() {}
};
/*!
Outputs a description of @c instruction to @c stream.
*/
inline std::ostream &operator <<(std::ostream &stream, const Instruction &instruction) {
stream << operation_name(instruction.operation) << " " << addressing_mode_name(instruction.addressing_mode);
return stream;
}
}
}
#endif /* InstructionSets_M50740_Instruction_h */

View File

@@ -0,0 +1,125 @@
//
// Parser.hpp
// Clock Signal
//
// Created by Thomas Harte on 16/01/21.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef InstructionSets_M50740_Parser_hpp
#define InstructionSets_M50740_Parser_hpp
#include <cstdint>
#include "Decoder.hpp"
#include "../AccessType.hpp"
namespace InstructionSet {
namespace M50740 {
template<typename Target, bool include_entries_and_accesses> struct Parser {
void parse(Target &target, const uint8_t *storage, uint16_t start, uint16_t closing_bound) {
Decoder decoder;
while(start <= closing_bound) {
const auto next = decoder.decode(&storage[start], 1 + closing_bound - start);
if(next.first <= 0) {
// If there weren't enough bytes left before the closing bound to complete
// an instruction, but implicitly there were some bytes left, announce overflow
// and terminate.
target.announce_overflow(start);
return;
} else {
// Pass on the instruction.
target.announce_instruction(start, next.second);
if constexpr(!include_entries_and_accesses) {
// Do a simplified test: is this a terminating operation?
switch(next.second.operation) {
case Operation::RTS: case Operation::RTI: case Operation::BRK:
case Operation::JMP: case Operation::BRA:
return;
default: break;
}
} else {
// Check for end of stream and potential new entry points.
switch(next.second.operation) {
// Terminating instructions.
case Operation::RTS: case Operation::RTI: case Operation::BRK:
return;
// Terminating operations, possibly with implied additional entry point.
case Operation::JMP:
if(next.second.addressing_mode == AddressingMode::Absolute) {
target.add_entry(uint16_t(storage[start + 1] | (storage[start + 2] << 8)));
}
return;
case Operation::BRA:
target.add_entry(uint16_t(start + 2 + int8_t(storage[start + 1])));
return;
// Instructions that suggest another entry point but don't terminate parsing.
case Operation::BCC: case Operation::BCS:
case Operation::BVC: case Operation::BVS:
case Operation::BMI: case Operation::BPL:
case Operation::BNE: case Operation::BEQ:
target.add_entry(uint16_t(start + 2 + int8_t(storage[start + 1])));
break;
case Operation::JSR:
switch(next.second.addressing_mode) {
default: break;
case AddressingMode::Absolute:
target.add_entry(uint16_t(storage[start + 1] | (storage[start + 2] << 8)));
break;
case AddressingMode::SpecialPage:
target.add_entry(uint16_t(storage[start + 1] | 0x1f00));
break;
}
break;
case Operation::BBS0: case Operation::BBS1: case Operation::BBS2: case Operation::BBS3:
case Operation::BBS4: case Operation::BBS5: case Operation::BBS6: case Operation::BBS7:
case Operation::BBC0: case Operation::BBC1: case Operation::BBC2: case Operation::BBC3:
case Operation::BBC4: case Operation::BBC5: case Operation::BBC6: case Operation::BBC7:
switch(next.second.addressing_mode) {
default: break;
case AddressingMode::AccumulatorRelative:
target.add_entry(uint16_t(start + 2 + int8_t(storage[start + 1])));
break;
case AddressingMode::ZeroPageRelative:
target.add_entry(uint16_t(start + 3 + int8_t(storage[start + 2])));
break;
}
break;
default: break;
}
// Provide any fixed address accesses.
switch(next.second.addressing_mode) {
case AddressingMode::Absolute:
target.add_access(uint16_t(storage[start + 1] | (storage[start + 2] << 8)), access_type(next.second.operation));
break;
case AddressingMode::ZeroPage: case AddressingMode::ZeroPageRelative:
target.add_access(storage[start + 1], access_type(next.second.operation));
break;
case AddressingMode::ImmediateZeroPage:
target.add_access(storage[start + 2], access_type(next.second.operation));
break;
default: break;
}
}
// Advance.
start += next.first;
}
}
}
};
}
}
#endif /* InstructionSets_M50740_Parser_hpp */

View File

@@ -0,0 +1,347 @@
//
// Decoder.cpp
// Clock Signal
//
// Created by Thomas Harte on 30/12/20.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#include "Decoder.hpp"
using namespace InstructionSet::PowerPC;
Decoder::Decoder(Model model) : model_(model) {}
Instruction Decoder::decode(uint32_t opcode) {
// Quick bluffer's guide to PowerPC instruction encoding:
//
// There is a six-bit field at the very top of the instruction.
// Sometimes that fully identifies an instruction, but usually
// it doesn't.
//
// There is an addition 9- or 10-bit field starting one bit above
// least significant that disambiguates the rest. Strictly speaking
// it's a 10-bit field, but the mnemonics for many instructions treat
// it as a 9-bit field with a flag at the top.
//
// I've decided to hew directly to the mnemonics.
//
// Various opcodes in the 1995 documentation define reserved bits,
// which are given the nominal value of 0. It does not give a formal
// definition of a reserved bit. As a result this code does not
// currently check the value of reserved bits. That may need to change
// if/when I add support for extended instruction sets.
#define Bind(mask, operation) case mask: return Instruction(Operation::operation, opcode);
#define BindSupervisor(mask, operation) case mask: return Instruction(Operation::operation, opcode, true);
#define BindConditional(condition, mask, operation) \
case mask: \
if(condition()) return Instruction(Operation::operation, opcode); \
return Instruction(opcode);
#define BindSupervisorConditional(condition, mask, operation) \
case mask: \
if(condition()) return Instruction(Operation::operation, opcode, true); \
return Instruction(opcode);
#define Six(x) (unsigned(x) << 26)
#define SixTen(x, y) (Six(x) | ((y) << 1))
// First pass: weed out all those instructions identified entirely by the
// top six bits.
switch(opcode & Six(0b111111)) {
default: break;
BindConditional(is64bit, Six(0b000010), tdi);
Bind(Six(0b000011), twi);
Bind(Six(0b000111), mulli);
Bind(Six(0b001000), subfic);
Bind(Six(0b001100), addic); Bind(Six(0b001101), addic_);
Bind(Six(0b001110), addi); Bind(Six(0b001111), addis);
case Six(0b010000): {
// This might be a bcx, but check for a valid bo field.
switch((opcode >> 21) & 0x1f) {
case 0: case 1: case 2: case 3: case 4: case 5:
case 8: case 9: case 10: case 11: case 12: case 13:
case 16: case 17: case 18: case 19: case 20:
return Instruction(Operation::bcx, opcode);
default: return Instruction(opcode);
}
} break;
Bind(Six(0b010010), bx);
Bind(Six(0b010100), rlwimix);
Bind(Six(0b010101), rlwinmx);
Bind(Six(0b010111), rlwnmx);
Bind(Six(0b011000), ori); Bind(Six(0b011001), oris);
Bind(Six(0b011010), xori); Bind(Six(0b011011), xoris);
Bind(Six(0b011100), andi_); Bind(Six(0b011101), andis_);
Bind(Six(0b100000), lwz); Bind(Six(0b100001), lwzu);
Bind(Six(0b100010), lbz); Bind(Six(0b100011), lbzu);
Bind(Six(0b100100), stw); Bind(Six(0b100101), stwu);
Bind(Six(0b100110), stb); Bind(Six(0b100111), stbu);
Bind(Six(0b101000), lhz); Bind(Six(0b101001), lhzu);
Bind(Six(0b101010), lha); Bind(Six(0b101011), lhau);
Bind(Six(0b101100), sth); Bind(Six(0b101101), sthu);
Bind(Six(0b101110), lmw); Bind(Six(0b101111), stmw);
Bind(Six(0b110000), lfs); Bind(Six(0b110001), lfsu);
Bind(Six(0b110010), lfd); Bind(Six(0b110011), lfdu);
Bind(Six(0b110100), stfs); Bind(Six(0b110101), stfsu);
Bind(Six(0b110110), stfd); Bind(Six(0b110111), stfdu);
BindConditional(is601, Six(9), dozi);
BindConditional(is601, Six(22), rlmix);
Bind(Six(0b001010), cmpli); Bind(Six(0b001011), cmpi);
}
// Second pass: all those with a top six bits and a bottom nine or ten.
switch(opcode & SixTen(0b111111, 0b1111111111)) {
default: break;
// 64-bit instructions.
BindConditional(is64bit, SixTen(0b011111, 0b0000001001), mulhdux); BindConditional(is64bit, SixTen(0b011111, 0b1000001001), mulhdux);
BindConditional(is64bit, SixTen(0b011111, 0b0000010101), ldx);
BindConditional(is64bit, SixTen(0b011111, 0b0000011011), sldx);
BindConditional(is64bit, SixTen(0b011111, 0b0000110101), ldux);
BindConditional(is64bit, SixTen(0b011111, 0b0000111010), cntlzdx);
BindConditional(is64bit, SixTen(0b011111, 0b0001000100), td);
BindConditional(is64bit, SixTen(0b011111, 0b0001001001), mulhdx); BindConditional(is64bit, SixTen(0b011111, 0b1001001001), mulhdx);
BindConditional(is64bit, SixTen(0b011111, 0b0001010100), ldarx);
BindConditional(is64bit, SixTen(0b011111, 0b0010010101), stdx);
BindConditional(is64bit, SixTen(0b011111, 0b0010110101), stdux);
BindConditional(is64bit, SixTen(0b011111, 0b0011101001), mulld); BindConditional(is64bit, SixTen(0b011111, 0b1011101001), mulld);
BindConditional(is64bit, SixTen(0b011111, 0b0101010101), lwax);
BindConditional(is64bit, SixTen(0b011111, 0b0101110101), lwaux);
BindConditional(is64bit, SixTen(0b011111, 0b1100111011), sradix); BindConditional(is64bit, SixTen(0b011111, 0b1100111010), sradix);
BindConditional(is64bit, SixTen(0b011111, 0b0110110010), slbie);
BindConditional(is64bit, SixTen(0b011111, 0b0111001001), divdux); BindConditional(is64bit, SixTen(0b011111, 0b1111001001), divdux);
BindConditional(is64bit, SixTen(0b011111, 0b0111101001), divdx); BindConditional(is64bit, SixTen(0b011111, 0b1111101001), divdx);
BindConditional(is64bit, SixTen(0b011111, 0b1000011011), srdx);
BindConditional(is64bit, SixTen(0b011111, 0b1100011010), sradx);
BindConditional(is64bit, SixTen(0b111111, 0b1111011010), extsw);
// Power instructions; these are all taken from the MPC601 manual rather than
// the PowerPC Programmer's Reference Guide, hence the decimal encoding of the
// ten-bit field.
BindConditional(is601, SixTen(0b011111, 360), absx); BindConditional(is601, SixTen(0b011111, 512 + 360), absx);
BindConditional(is601, SixTen(0b011111, 531), clcs);
BindConditional(is601, SixTen(0b011111, 331), divx); BindConditional(is601, SixTen(0b011111, 512 + 331), divx);
BindConditional(is601, SixTen(0b011111, 363), divsx); BindConditional(is601, SixTen(0b011111, 512 + 363), divsx);
BindConditional(is601, SixTen(0b011111, 264), dozx); BindConditional(is601, SixTen(0b011111, 512 + 264), dozx);
BindConditional(is601, SixTen(0b011111, 277), lscbxx);
BindConditional(is601, SixTen(0b011111, 29), maskgx);
BindConditional(is601, SixTen(0b011111, 541), maskirx);
BindConditional(is601, SixTen(0b011111, 107), mulx); BindConditional(is601, SixTen(0b011111, 512 + 107), mulx);
BindConditional(is601, SixTen(0b011111, 488), nabsx); BindConditional(is601, SixTen(0b011111, 512 + 488), nabsx);
BindConditional(is601, SixTen(0b011111, 537), rribx);
BindConditional(is601, SixTen(0b011111, 153), slex);
BindConditional(is601, SixTen(0b011111, 217), sleqx);
BindConditional(is601, SixTen(0b011111, 184), sliqx);
BindConditional(is601, SixTen(0b011111, 248), slliqx);
BindConditional(is601, SixTen(0b011111, 216), sllqx);
BindConditional(is601, SixTen(0b011111, 152), slqx);
BindConditional(is601, SixTen(0b011111, 952), sraiqx);
BindConditional(is601, SixTen(0b011111, 920), sraqx);
BindConditional(is601, SixTen(0b011111, 665), srex);
BindConditional(is601, SixTen(0b011111, 921), sreax);
BindConditional(is601, SixTen(0b011111, 729), sreqx);
BindConditional(is601, SixTen(0b011111, 696), sriqx);
BindConditional(is601, SixTen(0b011111, 760), srliqx);
BindConditional(is601, SixTen(0b011111, 728), srlqx);
BindConditional(is601, SixTen(0b011111, 664), srqx);
// 32-bit instructions.
Bind(SixTen(0b010011, 0b0000000000), mcrf);
Bind(SixTen(0b010011, 0b0000010000), bclrx);
Bind(SixTen(0b010011, 0b0000100001), crnor);
Bind(SixTen(0b010011, 0b0000110010), rfi);
Bind(SixTen(0b010011, 0b0010000001), crandc);
Bind(SixTen(0b010011, 0b0010010110), isync);
Bind(SixTen(0b010011, 0b0011000001), crxor);
Bind(SixTen(0b010011, 0b0011100001), crnand);
Bind(SixTen(0b010011, 0b0100000001), crand);
Bind(SixTen(0b010011, 0b0100100001), creqv);
Bind(SixTen(0b010011, 0b0110100001), crorc);
Bind(SixTen(0b010011, 0b0111000001), cror);
Bind(SixTen(0b010011, 0b1000010000), bcctrx);
Bind(SixTen(0b011111, 0b0000000000), cmp);
Bind(SixTen(0b011111, 0b0000000100), tw);
Bind(SixTen(0b011111, 0b0000001000), subfcx); Bind(SixTen(0b011111, 0b1000001000), subfcx);
Bind(SixTen(0b011111, 0b0000001010), addcx); Bind(SixTen(0b011111, 0b1000001010), addcx);
Bind(SixTen(0b011111, 0b0000001011), mulhwux); Bind(SixTen(0b011111, 0b1000001011), mulhwux);
Bind(SixTen(0b011111, 0b0000010011), mfcr);
Bind(SixTen(0b011111, 0b0000010100), lwarx);
Bind(SixTen(0b011111, 0b0000010111), lwzx);
Bind(SixTen(0b011111, 0b0000011000), slwx);
Bind(SixTen(0b011111, 0b0000011010), cntlzwx);
Bind(SixTen(0b011111, 0b0000011100), andx);
Bind(SixTen(0b011111, 0b0000100000), cmpl);
Bind(SixTen(0b011111, 0b0000101000), subfx); Bind(SixTen(0b011111, 0b1000101000), subfx);
Bind(SixTen(0b011111, 0b0000110110), dcbst);
Bind(SixTen(0b011111, 0b0000110111), lwzux);
Bind(SixTen(0b011111, 0b0000111100), andcx);
Bind(SixTen(0b011111, 0b0001001011), mulhwx); Bind(SixTen(0b011111, 0b1001001011), mulhwx);
Bind(SixTen(0b011111, 0b0001010011), mfmsr);
Bind(SixTen(0b011111, 0b0001010110), dcbf);
Bind(SixTen(0b011111, 0b0001010111), lbzx);
Bind(SixTen(0b011111, 0b0001101000), negx); Bind(SixTen(0b011111, 0b1001101000), negx);
Bind(SixTen(0b011111, 0b0001110111), lbzux);
Bind(SixTen(0b011111, 0b0001111100), norx);
Bind(SixTen(0b011111, 0b0010001000), subfex); Bind(SixTen(0b011111, 0b1010001000), subfex);
Bind(SixTen(0b011111, 0b0010001010), addex); Bind(SixTen(0b011111, 0b1010001010), addex);
Bind(SixTen(0b011111, 0b0010010000), mtcrf);
Bind(SixTen(0b011111, 0b0010010010), mtmsr);
Bind(SixTen(0b011111, 0b0010010111), stwx);
Bind(SixTen(0b011111, 0b0010110111), stwux);
Bind(SixTen(0b011111, 0b0011001000), subfzex); Bind(SixTen(0b011111, 0b1011001000), subfzex);
Bind(SixTen(0b011111, 0b0011001010), addzex); Bind(SixTen(0b011111, 0b1011001010), addzex);
Bind(SixTen(0b011111, 0b0011010111), stbx);
Bind(SixTen(0b011111, 0b0011101000), subfmex); Bind(SixTen(0b011111, 0b1011101000), subfmex);
Bind(SixTen(0b011111, 0b0011101010), addmex); Bind(SixTen(0b011111, 0b1011101010), addmex);
Bind(SixTen(0b011111, 0b0011101011), mullwx); Bind(SixTen(0b011111, 0b1011101011), mullwx);
Bind(SixTen(0b011111, 0b0011110110), dcbtst);
Bind(SixTen(0b011111, 0b0011110111), stbux);
Bind(SixTen(0b011111, 0b0100001010), addx); Bind(SixTen(0b011111, 0b1100001010), addx);
Bind(SixTen(0b011111, 0b0100010110), dcbt);
Bind(SixTen(0b011111, 0b0100010111), lhzx);
Bind(SixTen(0b011111, 0b0100011100), eqvx);
Bind(SixTen(0b011111, 0b0100110110), eciwx);
Bind(SixTen(0b011111, 0b0100110111), lhzux);
Bind(SixTen(0b011111, 0b0100111100), xorx);
Bind(SixTen(0b011111, 0b0101010111), lhax);
Bind(SixTen(0b011111, 0b0101110011), mftb);
Bind(SixTen(0b011111, 0b0101110111), lhaux);
Bind(SixTen(0b011111, 0b0110010111), sthx);
Bind(SixTen(0b011111, 0b0110011100), orcx);
Bind(SixTen(0b011111, 0b0110110110), ecowx);
Bind(SixTen(0b011111, 0b0110110111), sthux);
Bind(SixTen(0b011111, 0b0110111100), orx);
Bind(SixTen(0b011111, 0b0111001011), divwux); Bind(SixTen(0b011111, 0b1111001011), divwux);
Bind(SixTen(0b011111, 0b0111010110), dcbi);
Bind(SixTen(0b011111, 0b0111011100), nandx);
Bind(SixTen(0b011111, 0b0111101011), divwx); Bind(SixTen(0b011111, 0b1111101011), divwx);
Bind(SixTen(0b011111, 0b1000000000), mcrxr);
Bind(SixTen(0b011111, 0b1000010101), lswx);
Bind(SixTen(0b011111, 0b1000010110), lwbrx);
Bind(SixTen(0b011111, 0b1000010111), lfsx);
Bind(SixTen(0b011111, 0b1000011000), srwx);
Bind(SixTen(0b011111, 0b1000110111), lfsux);
Bind(SixTen(0b011111, 0b1001010101), lswi);
Bind(SixTen(0b011111, 0b1001010110), sync);
Bind(SixTen(0b011111, 0b1001010111), lfdx);
Bind(SixTen(0b011111, 0b1001110111), lfdux);
Bind(SixTen(0b011111, 0b1010010101), stswx);
Bind(SixTen(0b011111, 0b1010010110), stwbrx);
Bind(SixTen(0b011111, 0b1010010111), stfsx);
Bind(SixTen(0b011111, 0b1010110111), stfsux);
Bind(SixTen(0b011111, 0b1011010101), stswi);
Bind(SixTen(0b011111, 0b1011010111), stfdx);
Bind(SixTen(0b011111, 0b1011110111), stfdux);
Bind(SixTen(0b011111, 0b1100010110), lhbrx);
Bind(SixTen(0b011111, 0b1100011000), srawx);
Bind(SixTen(0b011111, 0b1100111000), srawix);
Bind(SixTen(0b011111, 0b1101010110), eieio);
Bind(SixTen(0b011111, 0b1110010110), sthbrx);
Bind(SixTen(0b011111, 0b1110011010), extshx);
Bind(SixTen(0b011111, 0b1110111010), extsbx);
Bind(SixTen(0b011111, 0b1111010110), icbi);
Bind(SixTen(0b011111, 0b1111010111), stfiwx);
Bind(SixTen(0b011111, 0b1111110110), dcbz);
Bind(SixTen(0b111111, 0b0000000000), fcmpu);
Bind(SixTen(0b111111, 0b0000001100), frspx);
Bind(SixTen(0b111111, 0b0000001110), fctiwx);
Bind(SixTen(0b111111, 0b0000001111), fctiwzx);
Bind(SixTen(0b111111, 0b0000100000), fcmpo);
Bind(SixTen(0b111111, 0b0000100110), mtfsb1x);
Bind(SixTen(0b111111, 0b0000101000), fnegx);
Bind(SixTen(0b111111, 0b0001000000), mcrfs);
Bind(SixTen(0b111111, 0b0001000110), mtfsb0x);
Bind(SixTen(0b111111, 0b0001001000), fmrx);
Bind(SixTen(0b111111, 0b0010000110), mtfsfix);
Bind(SixTen(0b111111, 0b0010001000), fnabsx);
Bind(SixTen(0b111111, 0b0100001000), fabsx);
Bind(SixTen(0b111111, 0b1001000111), mffsx);
Bind(SixTen(0b111111, 0b1011000111), mtfsfx);
Bind(SixTen(0b111111, 0b1100101110), fctidx);
Bind(SixTen(0b111111, 0b1100101111), fctidzx);
Bind(SixTen(0b111111, 0b1101001110), fcfidx);
Bind(SixTen(0b011111, 0b0101010011), mfspr); // Flagged as "supervisor and user"?
Bind(SixTen(0b011111, 0b0111010011), mtspr); // Flagged as "supervisor and user"?
BindSupervisorConditional(is32bit, SixTen(0b011111, 0b0011010010), mtsr);
BindSupervisorConditional(is32bit, SixTen(0b011111, 0b0011110010), mtsrin);
BindSupervisorConditional(is32bit, SixTen(0b011111, 0b1001010011), mfsr);
BindSupervisorConditional(is32bit, SixTen(0b011111, 0b1010010011), mfsrin);
BindSupervisorConditional(is64bit, SixTen(0b011111, 0b0111110010), slbia); // optional
// The following are all optional; should I record that?
BindSupervisor(SixTen(0b011111, 0b0100110010), tlbie);
BindSupervisor(SixTen(0b011111, 0b0101110010), tlbia);
BindSupervisor(SixTen(0b011111, 0b1000110110), tlbsync);
}
// Third pass: like six-ten except that the top five of the final ten
// are reserved (i.e. ignored here).
switch(opcode & SixTen(0b111111, 0b11111)) {
default: break;
Bind(SixTen(0b111011, 0b10010), fdivsx);
Bind(SixTen(0b111011, 0b10100), fsubsx);
Bind(SixTen(0b111011, 0b10101), faddsx);
Bind(SixTen(0b111011, 0b11001), fmulsx);
Bind(SixTen(0b111011, 0b11100), fmsubsx);
Bind(SixTen(0b111011, 0b11101), fmaddsx);
Bind(SixTen(0b111011, 0b11110), fnmsubsx);
Bind(SixTen(0b111011, 0b11111), fnmaddsx);
Bind(SixTen(0b111111, 0b10010), fdivx);
Bind(SixTen(0b111111, 0b10100), fsubx);
Bind(SixTen(0b111111, 0b10101), faddx);
Bind(SixTen(0b111111, 0b11001), fmulx);
Bind(SixTen(0b111111, 0b11100), fmsubx);
Bind(SixTen(0b111111, 0b11101), fmaddx);
Bind(SixTen(0b111111, 0b11110), fnmsubx);
Bind(SixTen(0b111111, 0b11111), fnmaddx);
BindConditional(is64bit, SixTen(0b111011, 0b10110), fsqrtsx);
BindConditional(is64bit, SixTen(0b111011, 0b11000), fresx);
// Optional...
Bind(SixTen(0b111111, 0b10110), fsqrtx);
Bind(SixTen(0b111111, 0b10111), fselx);
Bind(SixTen(0b111111, 0b11010), frsqrtex);
}
// stwcx. and stdcx.
switch(opcode & 0b111111'00'00000000'000'111111111'1){
case 0b011111'00'00000000'00000'0010010110'1: return Instruction(Operation::stwcx_, opcode);
case 0b011111'00'00000000'00000'0011010110'1:
if(is64bit()) return Instruction(Operation::stdcx_, opcode);
return Instruction(opcode);
}
// std and stdu
switch(opcode & 0b111111'00'00000000'00000000'000000'11){
case 0b111110'00'00000000'00000000'000000'00: return Instruction(Operation::std, opcode);
case 0b111110'00'00000000'00000000'000000'01:
if(is64bit()) return Instruction(Operation::stdu, opcode);
return Instruction(opcode);
}
// sc
if((opcode & 0b111111'00'00000000'00000000'000000'1'0) == 0b010001'00'00000000'00000000'000000'1'0) {
return Instruction(Operation::sc, opcode);
}
#undef Six
#undef SixTen
#undef Bind
#undef BindConditional
return Instruction(opcode);
}

View File

@@ -0,0 +1,56 @@
//
// Decoder.hpp
// Clock Signal
//
// Created by Thomas Harte on 30/12/20.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#ifndef InstructionSets_PowerPC_Decoder_hpp
#define InstructionSets_PowerPC_Decoder_hpp
#include "Instruction.hpp"
namespace InstructionSet {
namespace PowerPC {
enum class Model {
/// i.e. 32-bit, with POWER carry-over instructions.
MPC601,
/// i.e. 32-bit, no POWER instructions.
MPC603,
/// i.e. 64-bit.
MPC620,
};
/*!
Implements PowerPC instruction decoding.
This is an experimental implementation; it has not yet undergone significant testing.
*/
struct Decoder {
public:
Decoder(Model model);
Instruction decode(uint32_t opcode);
private:
Model model_;
bool is64bit() const {
return model_ == Model::MPC620;
}
bool is32bit() const {
return !is64bit();
}
bool is601() const {
return model_ == Model::MPC601;
}
};
}
}
#endif /* InstructionSets_PowerPC_Decoder_hpp */

View File

@@ -0,0 +1,190 @@
//
// Instruction.hpp
// Clock Signal
//
// Created by Thomas Harte on 15/01/21.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef InstructionSets_PowerPC_Instruction_h
#define InstructionSets_PowerPC_Instruction_h
#include <cstdint>
namespace InstructionSet {
namespace PowerPC {
enum class Operation: uint8_t {
Undefined,
// These 601-exclusive instructions; a lot of them are carry-overs
// from POWER.
absx, clcs, divx, divsx, dozx, dozi, lscbxx, maskgx, maskirx, mulx,
nabsx, rlmix, rribx, slex, sleqx, sliqx, slliqx, sllqx, slqx,
sraiqx, sraqx, srex, sreax, sreqx, sriqx, srliqx, srlqx, srqx,
// 32- and 64-bit PowerPC instructions.
addx, addcx, addex, addi, addic, addic_, addis, addmex, addzex, andx,
andcx, andi_, andis_, bx, bcx, bcctrx, bclrx, cmp, cmpi, cmpl, cmpli,
cntlzwx, crand, crandc, creqv, crnand, crnor, cror, crorc, crxor, dcbf,
dcbst, dcbt, dcbtst, dcbz, divwx, divwux, eciwx, ecowx, eieio, eqvx,
extsbx, extshx, fabsx, faddx, faddsx, fcmpo, fcmpu, fctiwx, fctiwzx,
fdivx, fdivsx, fmaddx, fmaddsx, fmrx, fmsubx, fmsubsx, fmulx, fmulsx,
fnabsx, fnegx, fnmaddx, fnmaddsx, fnmsubx, fnmsubsx, frspx, fsubx, fsubsx,
icbi, isync, lbz, lbzu, lbzux, lbzx, lfd, lfdu, lfdux, lfdx, lfs, lfsu,
lfsux, lfsx, lha, lhau, lhaux, lhax, lhbrx, lhz, lhzu, lhzux, lhzx, lmw,
lswi, lswx, lwarx, lwbrx, lwz, lwzu, lwzux, lwzx, mcrf, mcrfs, mcrxr,
mfcr, mffsx, mfmsr, mfspr, mfsr, mfsrin, mtcrf, mtfsb0x, mtfsb1x, mtfsfx,
mtfsfix, mtmsr, mtspr, mtsr, mtsrin, mulhwx, mulhwux, mulli, mullwx,
nandx, negx, norx, orx, orcx, ori, oris, rfi, rlwimix, rlwinmx, rlwnmx,
sc, slwx, srawx, srawix, srwx, stb, stbu, stbux, stbx, stfd, stfdu,
stfdux, stfdx, stfs, stfsu, stfsux, stfsx, sth, sthbrx, sthu, sthux, sthx,
stmw, stswi, stswx, stw, stwbrx, stwcx_, stwu, stwux, stwx, subfx, subfcx,
subfex, subfic, subfmex, subfzex, sync, tw, twi, xorx, xori, xoris, mftb,
// 32-bit, supervisor level.
dcbi,
// Supervisor, optional.
tlbia, tlbie, tlbsync,
// Optional.
fresx, frsqrtex, fselx, fsqrtx, slbia, slbie, stfiwx,
// 64-bit only PowerPC instructions.
cntlzdx, divdx, divdux, extswx, fcfidx, fctidx, fctidzx, tdi, mulhdux,
ldx, sldx, ldux, td, mulhdx, ldarx, stdx, stdux, mulld, lwax, lwaux,
sradix, srdx, sradx, extsw, fsqrtsx, std, stdu, stdcx_,
};
/*!
Holds a decoded PowerPC instruction.
Implementation note: because the PowerPC encoding is particularly straightforward,
only the operation has been decoded ahead of time; all other fields are decoded on-demand.
It would be possible to partition the ordering of Operations into user followed by supervisor,
eliminating the storage necessary for a flag, but it wouldn't save anything due to alignment.
*/
struct Instruction {
Operation operation = Operation::Undefined;
bool is_supervisor = false;
uint32_t opcode = 0;
Instruction() noexcept {}
Instruction(uint32_t opcode) noexcept : opcode(opcode) {}
Instruction(Operation operation, uint32_t opcode, bool is_supervisor = false) noexcept : operation(operation), is_supervisor(is_supervisor), opcode(opcode) {}
// Instruction fields are decoded below; naming is a compromise between
// Motorola's documentation and IBM's.
//
// I've dutifully implemented various synonyms with unique entry points,
// in order to capture that information here rather than thrusting it upon
// the reader of whatever implementation may follow.
// Currently omitted: OPCD and XO, which I think are unnecessary given that
// full decoding has already occurred.
/// Immediate field used to specify an unsigned 16-bit integer.
uint16_t uimm() const { return uint16_t(opcode & 0xffff); }
/// Immediate field used to specify a signed 16-bit integer.
int16_t simm() const { return int16_t(opcode & 0xffff); }
/// Immediate field used to specify a signed 16-bit integer.
int16_t d() const { return int16_t(opcode & 0xffff); }
/// Immediate field used to specify a signed 14-bit integer [64-bit only].
int16_t ds() const { return int16_t(opcode & 0xfffc); }
/// Immediate field used as data to be placed into a field in the floating point status and condition register.
int32_t imm() const { return (opcode >> 12) & 0xf; }
/// Specifies the conditions on which to trap.
int32_t to() const { return (opcode >> 21) & 0x1f; }
/// Register source A or destination.
uint32_t rA() const { return (opcode >> 16) & 0x1f; }
/// Register source B.
uint32_t rB() const { return (opcode >> 11) & 0x1f; }
/// Register destination.
uint32_t rD() const { return (opcode >> 21) & 0x1f; }
/// Register source.
uint32_t rS() const { return (opcode >> 21) & 0x1f; }
/// Floating point register source A.
uint32_t frA() const { return (opcode >> 16) & 0x1f; }
/// Floating point register source B.
uint32_t frB() const { return (opcode >> 11) & 0x1f; }
/// Floating point register source C.
uint32_t frC() const { return (opcode >> 6) & 0x1f; }
/// Floating point register source.
uint32_t frS() const { return (opcode >> 21) & 0x1f; }
/// Floating point register destination.
uint32_t frD() const { return (opcode >> 21) & 0x1f; }
/// Branch conditional options.
uint32_t bo() const { return (opcode >> 21) & 0x1f; }
/// Source condition register bit for branch conditionals.
uint32_t bi() const { return (opcode >> 16) & 0x1f; }
/// Branch displacement; provided as already sign extended.
int16_t bd() const { return int16_t(opcode & 0xfffc); }
/// Specifies the first 1 bit of a 32/64-bit mask for rotate operations.
uint32_t mb() const { return (opcode >> 6) & 0x1f; }
/// Specifies the first 1 bit of a 32/64-bit mask for rotate operations.
uint32_t me() const { return (opcode >> 1) & 0x1f; }
/// Condition register source bit A.
uint32_t crbA() const { return (opcode >> 16) & 0x1f; }
/// Condition register source bit B.
uint32_t crbB() const { return (opcode >> 11) & 0x1f; }
/// Condition register (or floating point status & condition register) destination bit.
uint32_t crbD() const { return (opcode >> 21) & 0x1f; }
/// Condition register (or floating point status & condition register) destination field.
uint32_t crfD() const { return (opcode >> 23) & 0x07; }
/// Condition register (or floating point status & condition register) source field.
uint32_t crfS() const { return (opcode >> 18) & 0x07; }
/// Mask identifying fields to be updated by mtcrf.
uint32_t crm() const { return (opcode >> 12) & 0xff; }
/// Mask identifying fields to be updated by mtfsf.
uint32_t fm() const { return (opcode >> 17) & 0xff; }
/// Specifies the number of bytes to move in an immediate string load or store.
uint32_t nb() const { return (opcode >> 11) & 0x1f; }
/// Specifies a shift amount.
/// TODO: possibly bit 30 is also used in 64-bit mode, find out.
uint32_t sh() const { return (opcode >> 11) & 0x1f; }
/// Specifies one of the 16 segment registers [32-bit only].
uint32_t sr() const { return (opcode >> 16) & 0xf; }
/// A 24-bit signed number; provided as already sign extended.
int32_t li() const {
constexpr uint32_t extensions[2] = {
0x0000'0000,
0xfc00'0000
};
const uint32_t value = (opcode & 0x03ff'fffc) | extensions[(opcode >> 25) & 1];
return int32_t(value);
}
/// Absolute address bit; @c 0 or @c non-0.
uint32_t aa() const { return opcode & 0x02; }
/// Link bit; @c 0 or @c non-0.
uint32_t lk() const { return opcode & 0x01; }
/// Record bit; @c 0 or @c non-0.
uint32_t rc() const { return opcode & 0x01; }
/// Whether to compare 32-bit or 64-bit numbers [for 64-bit implementations only]; @c 0 or @c non-0.
uint32_t l() const { return opcode & 0x200000; }
/// Enables setting of OV and SO in the XER; @c 0 or @c non-0.
uint32_t oe() const { return opcode & 0x800; }
};
// Sanity check on Instruction size.
static_assert(sizeof(Instruction) <= 8);
}
}
#endif /* InstructionSets_PowerPC_Instruction_h */

86
InstructionSets/README.md Normal file
View File

@@ -0,0 +1,86 @@
# Instruction Sets
Code in here provides the means to disassemble, and to execute code for certain instruction sets.
It **does not seek to emulate specific processors** other than in terms of implementing their instruction sets. So:
* it doesn't involve itself in the actual bus signalling of real processors; and
* instruction-level timing (e.g. total cycle counts) may be unimplemented, and is likely to be incomplete.
This part of CLK is intended primarily to provide disassembly services for static analysis, and processing for machines where timing is not part of the specification — i.e. anything that's an instruction set and a HAL.
## Decoders
A decoder extracts fully-decoded instructions from a data stream for its associated architecture.
The meaning of 'fully-decoded' is flexible but it means that a caller can easily discern at least:
* the operation in use;
* its addressing mode; and
* relevant registers.
It may be assumed that callers will have access to the original data stream for immediate values, if it is sensible to do so.
In deciding what to expose, what to store ahead of time and what to obtain just-in-time a decoder should have an eye on two principal consumers:
1. disassemblers; and
2. instruction executors.
It may also be reasonable to make allowances for bus-centric CPU emulators, but those will be tightly coupled to specific decoders so no general rules need apply.
Disassemblers are likely to decode an instruction, output it, and then immediately forget about it.
Instruction executors may opt to cache decoded instructions to reduce recurrent costs, but will always be dealing with an actual instruction stream. The chance of caching means that decoded instructions should seek to be small. If helpful then a decoder might prefer to return a `std::pair` or similar of ephemeral information and stuff that it is meaningful to store.
### Likely Interfaces
These examples assume that the processor itself doesn't hold any state that affects instruction parsing. Whether processors with such state offer more than one decoder or take state as an argument will be a question of measure and effect.
#### Fixed-size instruction words
If the instructions are a fixed size, the decoder can provide what is functionally a simple lookup, whether implemented as such or not:
Instruction decode(word_type instruction) { ... }
For now I have preferred not to make this a simple constructor on `Instruction` because I'm reserving the option of switching to an ephemeral/permanent split in what's returned. More consideration needs to be applied here.
#### Variable-size instruction words
If instructions are a variable size, the decoder should maintain internal state such that it can be provided with fragments of instructions until a full decoding has occurred — this avoids an assumption that all source bytes will always be laid out linearly in memory.
A sample interface:
std::pair<int, Instruction> decode(word_type *stream, size_t length) { ... }
In this sample the returned pair provides an `int` size that is one of:
* a positive number, indicating a completed decoding that consumed that many `word_type`s; or
* a negative number, indicating the [negatived] minimum number of `word_type`s that the caller should try to get hold of before calling `decode` again.
A caller is permitted to react in any way it prefers to negative numbers; they're a hint potentially to reduce calling overhead only. A size of `0` would be taken to have the same meaning as a size of `-1`.
## Parsers
A parser sits one level above a decoder; it is handed:
* a start address;
* a closing bound; and
* a target.
It is responsible for parsing the instruction stream from the start address up to and not beyond the closing bound, and no further than any unconditional branches.
It should post to the target:
* any instructions fully decoded;
* any conditional branch destinations encountered;
* any immediately-knowable accessed addresses; and
* if a final instruction exists but runs beyond the closing bound, notification of that fact.
So a parser has the same two primary potential recipients as a decoder: diassemblers, and executors.
## Executors
An executor is responsible for only one thing:
* mapping from decoded instructions to objects that can perform those instructions.
An executor is assumed to bundle all the things that go into instruction set execution: processor state and memory, alongside a parser.
## Caching Executor
The caching executor is a generic class templated on a specific executor. It will use an executor to cache the results of parsing.
Idiomatically, the objects that perform instructions will expect to receive an appropriate executor as an argument. If they require other information, such as a copy of the decoded instruction, it should be built into the classes.

36
InstructionSets/Sizes.hpp Normal file
View File

@@ -0,0 +1,36 @@
//
// Sizes.hpp
// Clock Signal
//
// Created by Thomas Harte on 26/01/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Sizes_h
#define Sizes_h
#include <limits>
/*!
Maps to the smallest integral type that can contain max_value, from the following options:
* uint8_t;
* uint16_t;
* uint32_t; or
* uint64_t.
*/
template <uint64_t max_value> struct MinIntTypeValue {
using type =
std::conditional_t<
max_value <= std::numeric_limits<uint8_t>::max(), uint8_t,
std::conditional_t<
max_value <= std::numeric_limits<uint16_t>::max(), uint16_t,
std::conditional_t<
max_value <= std::numeric_limits<uint32_t>::max(), uint32_t,
uint64_t
>
>
>;
};
#endif /* Sizes_h */

View File

@@ -0,0 +1,625 @@
//
// x86.cpp
// Clock Signal
//
// Created by Thomas Harte on 01/01/21.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "Decoder.hpp"
#include <algorithm>
#include <cassert>
#include <utility>
using namespace InstructionSet::x86;
// Only 8086 is suppoted for now.
Decoder::Decoder(Model) {}
std::pair<int, InstructionSet::x86::Instruction> Decoder::decode(const uint8_t *source, size_t length) {
const uint8_t *const end = source + length;
// MARK: - Prefixes (if present) and the opcode.
/// Helper macro for those that follow.
#define SetOpSrcDestSize(op, src, dest, size) \
operation_ = Operation::op; \
source_ = Source::src; \
destination_ = Source::dest; \
operation_size_ = size
/// Covers anything which is complete as soon as the opcode is encountered.
#define Complete(op, src, dest, size) \
SetOpSrcDestSize(op, src, dest, size); \
phase_ = Phase::ReadyToPost
/// Handles instructions of the form rr, kk and rr, jjkk, i.e. a destination register plus an operand.
#define RegData(op, dest, size) \
SetOpSrcDestSize(op, DirectAddress, dest, size); \
source_ = Source::Immediate; \
operand_size_ = size; \
phase_ = Phase::AwaitingDisplacementOrOperand
/// Handles instructions of the form Ax, jjkk where the latter is implicitly an address.
#define RegAddr(op, dest, op_size, addr_size) \
SetOpSrcDestSize(op, DirectAddress, dest, op_size); \
operand_size_ = addr_size; \
phase_ = Phase::AwaitingDisplacementOrOperand
/// Handles instructions of the form jjkk, Ax where the former is implicitly an address.
#define AddrReg(op, source, op_size, addr_size) \
SetOpSrcDestSize(op, source, DirectAddress, op_size); \
operand_size_ = addr_size; \
destination_ = Source::DirectAddress; \
phase_ = Phase::AwaitingDisplacementOrOperand
/// Covers both `mem/reg, reg` and `reg, mem/reg`.
#define MemRegReg(op, format, size) \
operation_ = Operation::op; \
phase_ = Phase::ModRegRM; \
modregrm_format_ = ModRegRMFormat::format; \
operand_size_ = 0; \
operation_size_ = size
/// Handles JO, JNO, JB, etc — jumps with a single byte displacement.
#define Jump(op) \
operation_ = Operation::op; \
phase_ = Phase::AwaitingDisplacementOrOperand; \
displacement_size_ = 1
/// Handles far CALL and far JMP — fixed four byte operand operations.
#define Far(op) \
operation_ = Operation::op; \
phase_ = Phase::AwaitingDisplacementOrOperand; \
operand_size_ = 4; \
while(phase_ == Phase::Instruction && source != end) {
// Retain the instruction byte, in case additional decoding is deferred
// to the ModRegRM byte.
instr_ = *source;
++source;
++consumed_;
switch(instr_) {
default: {
const auto result = std::make_pair(consumed_, Instruction());
reset_parsing();
return result;
}
#define PartialBlock(start, operation) \
case start + 0x00: MemRegReg(operation, MemReg_Reg, 1); break; \
case start + 0x01: MemRegReg(operation, MemReg_Reg, 2); break; \
case start + 0x02: MemRegReg(operation, Reg_MemReg, 1); break; \
case start + 0x03: MemRegReg(operation, Reg_MemReg, 2); break; \
case start + 0x04: RegData(operation, AL, 1); break; \
case start + 0x05: RegData(operation, AX, 2)
PartialBlock(0x00, ADD); break;
case 0x06: Complete(PUSH, ES, None, 2); break;
case 0x07: Complete(POP, None, ES, 2); break;
PartialBlock(0x08, OR); break;
case 0x0e: Complete(PUSH, CS, None, 2); break;
PartialBlock(0x10, ADC); break;
case 0x16: Complete(PUSH, SS, None, 2); break;
case 0x17: Complete(POP, None, SS, 2); break;
PartialBlock(0x18, SBB); break;
case 0x1e: Complete(PUSH, DS, None, 2); break;
case 0x1f: Complete(POP, None, DS, 2); break;
PartialBlock(0x20, AND); break;
case 0x26: segment_override_ = Source::ES; break;
case 0x27: Complete(DAA, AL, AL, 1); break;
PartialBlock(0x28, SUB); break;
case 0x2e: segment_override_ = Source::CS; break;
case 0x2f: Complete(DAS, AL, AL, 1); break;
PartialBlock(0x30, XOR); break;
case 0x36: segment_override_ = Source::SS; break;
case 0x37: Complete(AAA, AL, AX, 1); break;
PartialBlock(0x38, CMP); break;
case 0x3e: segment_override_ = Source::DS; break;
case 0x3f: Complete(AAS, AL, AX, 1); break;
#undef PartialBlock
#define RegisterBlock(start, operation) \
case start + 0x00: Complete(operation, AX, AX, 2); break; \
case start + 0x01: Complete(operation, CX, CX, 2); break; \
case start + 0x02: Complete(operation, DX, DX, 2); break; \
case start + 0x03: Complete(operation, BX, BX, 2); break; \
case start + 0x04: Complete(operation, SP, SP, 2); break; \
case start + 0x05: Complete(operation, BP, BP, 2); break; \
case start + 0x06: Complete(operation, SI, SI, 2); break; \
case start + 0x07: Complete(operation, DI, DI, 2)
RegisterBlock(0x40, INC); break;
RegisterBlock(0x48, DEC); break;
RegisterBlock(0x50, PUSH); break;
RegisterBlock(0x58, POP); break;
#undef RegisterBlock
// 0x600x6f: not used.
case 0x70: Jump(JO); break;
case 0x71: Jump(JNO); break;
case 0x72: Jump(JB); break;
case 0x73: Jump(JNB); break;
case 0x74: Jump(JE); break;
case 0x75: Jump(JNE); break;
case 0x76: Jump(JBE); break;
case 0x77: Jump(JNBE); break;
case 0x78: Jump(JS); break;
case 0x79: Jump(JNS); break;
case 0x7a: Jump(JP); break;
case 0x7b: Jump(JNP); break;
case 0x7c: Jump(JL); break;
case 0x7d: Jump(JNL); break;
case 0x7e: Jump(JLE); break;
case 0x7f: Jump(JNLE); break;
case 0x80: MemRegReg(Invalid, MemRegADD_to_CMP, 1); break;
case 0x81: MemRegReg(Invalid, MemRegADD_to_CMP, 2); break;
case 0x82: MemRegReg(Invalid, MemRegADC_to_CMP, 1); break;
case 0x83: MemRegReg(Invalid, MemRegADC_to_CMP, 2); break;
case 0x84: MemRegReg(TEST, MemReg_Reg, 1); break;
case 0x85: MemRegReg(TEST, MemReg_Reg, 2); break;
case 0x86: MemRegReg(XCHG, Reg_MemReg, 1); break;
case 0x87: MemRegReg(XCHG, Reg_MemReg, 2); break;
case 0x88: MemRegReg(MOV, MemReg_Reg, 1); break;
case 0x89: MemRegReg(MOV, MemReg_Reg, 2); break;
case 0x8a: MemRegReg(MOV, Reg_MemReg, 1); break;
case 0x8b: MemRegReg(MOV, Reg_MemReg, 2); break;
// 0x8c: not used.
case 0x8d: MemRegReg(LEA, Reg_MemReg, 2); break;
case 0x8e: MemRegReg(MOV, SegReg, 2); break;
case 0x8f: MemRegReg(POP, MemRegPOP, 2); break;
case 0x90: Complete(NOP, None, None, 0); break; // Or XCHG AX, AX?
case 0x91: Complete(XCHG, AX, CX, 2); break;
case 0x92: Complete(XCHG, AX, DX, 2); break;
case 0x93: Complete(XCHG, AX, BX, 2); break;
case 0x94: Complete(XCHG, AX, SP, 2); break;
case 0x95: Complete(XCHG, AX, BP, 2); break;
case 0x96: Complete(XCHG, AX, SI, 2); break;
case 0x97: Complete(XCHG, AX, DI, 2); break;
case 0x98: Complete(CBW, AL, AH, 1); break;
case 0x99: Complete(CWD, AX, DX, 2); break;
case 0x9a: Far(CALLF); break;
case 0x9b: Complete(WAIT, None, None, 0); break;
case 0x9c: Complete(PUSHF, None, None, 2); break;
case 0x9d: Complete(POPF, None, None, 2); break;
case 0x9e: Complete(SAHF, None, None, 1); break;
case 0x9f: Complete(LAHF, None, None, 1); break;
case 0xa0: RegAddr(MOV, AL, 1, 1); break;
case 0xa1: RegAddr(MOV, AX, 2, 2); break;
case 0xa2: AddrReg(MOV, AL, 1, 1); break;
case 0xa3: AddrReg(MOV, AX, 2, 2); break;
case 0xa4: Complete(MOVS, None, None, 1); break;
case 0xa5: Complete(MOVS, None, None, 2); break;
case 0xa6: Complete(CMPS, None, None, 1); break;
case 0xa7: Complete(CMPS, None, None, 2); break;
case 0xa8: RegData(TEST, AL, 1); break;
case 0xa9: RegData(TEST, AX, 2); break;
case 0xaa: Complete(STOS, None, None, 1); break;
case 0xab: Complete(STOS, None, None, 2); break;
case 0xac: Complete(LODS, None, None, 1); break;
case 0xad: Complete(LODS, None, None, 2); break;
case 0xae: Complete(SCAS, None, None, 1); break;
case 0xaf: Complete(SCAS, None, None, 2); break;
case 0xb0: RegData(MOV, AL, 1); break;
case 0xb1: RegData(MOV, CL, 1); break;
case 0xb2: RegData(MOV, DL, 1); break;
case 0xb3: RegData(MOV, BL, 1); break;
case 0xb4: RegData(MOV, AH, 1); break;
case 0xb5: RegData(MOV, CH, 1); break;
case 0xb6: RegData(MOV, DH, 1); break;
case 0xb7: RegData(MOV, BH, 1); break;
case 0xb8: RegData(MOV, AX, 2); break;
case 0xb9: RegData(MOV, CX, 2); break;
case 0xba: RegData(MOV, DX, 2); break;
case 0xbb: RegData(MOV, BX, 2); break;
case 0xbc: RegData(MOV, SP, 2); break;
case 0xbd: RegData(MOV, BP, 2); break;
case 0xbe: RegData(MOV, SI, 2); break;
case 0xbf: RegData(MOV, DI, 2); break;
case 0xc2: RegData(RETN, None, 2); break;
case 0xc3: Complete(RETN, None, None, 2); break;
case 0xc4: MemRegReg(LES, Reg_MemReg, 2); break;
case 0xc5: MemRegReg(LDS, Reg_MemReg, 2); break;
case 0xc6: MemRegReg(MOV, MemRegMOV, 1); break;
case 0xc7: MemRegReg(MOV, MemRegMOV, 2); break;
case 0xca: RegData(RETF, None, 2); break;
case 0xcb: Complete(RETF, None, None, 4); break;
case 0xcc: Complete(INT3, None, None, 0); break;
case 0xcd: RegData(INT, None, 1); break;
case 0xce: Complete(INTO, None, None, 0); break;
case 0xcf: Complete(IRET, None, None, 0); break;
case 0xd0: case 0xd1:
phase_ = Phase::ModRegRM;
modregrm_format_ = ModRegRMFormat::MemRegROL_to_SAR;
operation_size_ = 1 + (instr_ & 1);
source_ = Source::Immediate;
operand_ = 1;
break;
case 0xd2: case 0xd3:
phase_ = Phase::ModRegRM;
modregrm_format_ = ModRegRMFormat::MemRegROL_to_SAR;
operation_size_ = 1 + (instr_ & 1);
source_ = Source::CL;
break;
case 0xd4: RegData(AAM, AX, 1); break;
case 0xd5: RegData(AAD, AX, 1); break;
case 0xd7: Complete(XLAT, None, None, 1); break;
case 0xd8: MemRegReg(ESC, MemReg_Reg, 0); break;
case 0xd9: MemRegReg(ESC, MemReg_Reg, 0); break;
case 0xda: MemRegReg(ESC, MemReg_Reg, 0); break;
case 0xdb: MemRegReg(ESC, MemReg_Reg, 0); break;
case 0xdc: MemRegReg(ESC, MemReg_Reg, 0); break;
case 0xdd: MemRegReg(ESC, MemReg_Reg, 0); break;
case 0xde: MemRegReg(ESC, MemReg_Reg, 0); break;
case 0xdf: MemRegReg(ESC, MemReg_Reg, 0); break;
case 0xe0: Jump(LOOPNE); break;
case 0xe1: Jump(LOOPE); break;
case 0xe2: Jump(LOOP); break;
case 0xe3: Jump(JPCX); break;
case 0xe4: RegAddr(IN, AL, 1, 1); break;
case 0xe5: RegAddr(IN, AX, 2, 1); break;
case 0xe6: AddrReg(OUT, AL, 1, 1); break;
case 0xe7: AddrReg(OUT, AX, 2, 1); break;
case 0xe8: RegData(CALLD, None, 2); break;
case 0xe9: RegData(JMPN, None, 2); break;
case 0xea: Far(JMPF); break;
case 0xeb: Jump(JMPN); break;
case 0xec: Complete(IN, DX, AL, 1); break;
case 0xed: Complete(IN, DX, AX, 1); break;
case 0xee: Complete(OUT, AL, DX, 1); break;
case 0xef: Complete(OUT, AX, DX, 2); break;
case 0xf4: Complete(HLT, None, None, 1); break;
case 0xf5: Complete(CMC, None, None, 1); break;
case 0xf6: MemRegReg(Invalid, MemRegTEST_to_IDIV, 1); break;
case 0xf7: MemRegReg(Invalid, MemRegTEST_to_IDIV, 2); break;
case 0xf8: Complete(CLC, None, None, 1); break;
case 0xf9: Complete(STC, None, None, 1); break;
case 0xfa: Complete(CLI, None, None, 1); break;
case 0xfb: Complete(STI, None, None, 1); break;
case 0xfc: Complete(CLD, None, None, 1); break;
case 0xfd: Complete(STD, None, None, 1); break;
case 0xfe: MemRegReg(Invalid, MemRegINC_DEC, 1); break;
case 0xff: MemRegReg(Invalid, MemRegINC_to_PUSH, 1); break;
// Other prefix bytes.
case 0xf0: lock_ = true; break;
case 0xf2: repetition_ = Repetition::RepNE; break;
case 0xf3: repetition_ = Repetition::RepE; break;
}
}
#undef Far
#undef Jump
#undef MemRegReg
#undef AddrReg
#undef RegAddr
#undef RegData
#undef Complete
#undef SetOpSrcDestSize
// MARK: - ModRegRM byte, if any.
if(phase_ == Phase::ModRegRM && source != end) {
const uint8_t mod = *source >> 6; // i.e. mode.
const uint8_t reg = (*source >> 3) & 7; // i.e. register.
const uint8_t rm = *source & 7; // i.e. register/memory.
++source;
++consumed_;
Source memreg;
constexpr Source reg_table[3][8] = {
{},
{
Source::AL, Source::CL, Source::DL, Source::BL,
Source::AH, Source::CH, Source::DH, Source::BH,
}, {
Source::AX, Source::CX, Source::DX, Source::BX,
Source::SP, Source::BP, Source::SI, Source::DI,
}
};
switch(mod) {
case 0: {
constexpr Source rm_table[8] = {
Source::IndBXPlusSI, Source::IndBXPlusDI,
Source::IndBPPlusSI, Source::IndBPPlusDI,
Source::IndSI, Source::IndDI,
Source::DirectAddress, Source::IndBX,
};
memreg = rm_table[rm];
} break;
default: {
constexpr Source rm_table[8] = {
Source::IndBXPlusSI, Source::IndBXPlusDI,
Source::IndBPPlusSI, Source::IndBPPlusDI,
Source::IndSI, Source::IndDI,
Source::IndBP, Source::IndBX,
};
memreg = rm_table[rm];
displacement_size_ = 1 + (mod == 2);
} break;
// Other operand is just a register.
case 3:
memreg = reg_table[operation_size_][rm];
// LES and LDS accept a memory argument only, not a register.
if(operation_ == Operation::LES || operation_ == Operation::LDS) {
const auto result = std::make_pair(consumed_, Instruction());
reset_parsing();
return result;
}
break;
}
switch(modregrm_format_) {
case ModRegRMFormat::Reg_MemReg:
case ModRegRMFormat::MemReg_Reg: {
if(modregrm_format_ == ModRegRMFormat::Reg_MemReg) {
source_ = memreg;
destination_ = reg_table[operation_size_][reg];
} else {
source_ = reg_table[operation_size_][reg];
destination_ = memreg;
}
} break;
case ModRegRMFormat::MemRegTEST_to_IDIV:
source_ = destination_ = memreg;
switch(reg) {
default: {
const auto result = std::make_pair(consumed_, Instruction());
reset_parsing();
return result;
}
case 0: operation_ = Operation::TEST; break;
case 2: operation_ = Operation::NOT; break;
case 3: operation_ = Operation::NEG; break;
case 4: operation_ = Operation::MUL; break;
case 5: operation_ = Operation::IMUL; break;
case 6: operation_ = Operation::DIV; break;
case 7: operation_ = Operation::IDIV; break;
}
break;
case ModRegRMFormat::SegReg: {
source_ = memreg;
constexpr Source seg_table[4] = {
Source::ES, Source::CS,
Source::SS, Source::DS,
};
if(reg & 4) {
const auto result = std::make_pair(consumed_, Instruction());
reset_parsing();
return result;
}
destination_ = seg_table[reg];
} break;
case ModRegRMFormat::MemRegROL_to_SAR:
destination_ = memreg;
switch(reg) {
default: {
const auto result = std::make_pair(consumed_, Instruction());
reset_parsing();
return result;
}
case 0: operation_ = Operation::ROL; break;
case 2: operation_ = Operation::ROR; break;
case 3: operation_ = Operation::RCL; break;
case 4: operation_ = Operation::RCR; break;
case 5: operation_ = Operation::SAL; break;
case 6: operation_ = Operation::SHR; break;
case 7: operation_ = Operation::SAR; break;
}
break;
case ModRegRMFormat::MemRegINC_DEC:
source_ = destination_ = memreg;
switch(reg) {
default: {
const auto result = std::make_pair(consumed_, Instruction());
reset_parsing();
return result;
}
case 0: operation_ = Operation::INC; break;
case 1: operation_ = Operation::DEC; break;
}
break;
case ModRegRMFormat::MemRegINC_to_PUSH:
source_ = destination_ = memreg;
switch(reg) {
default: {
const auto result = std::make_pair(consumed_, Instruction());
reset_parsing();
return result;
}
case 0: operation_ = Operation::INC; break;
case 1: operation_ = Operation::DEC; break;
case 2: operation_ = Operation::CALLN; break;
case 3:
operation_ = Operation::CALLF;
operand_size_ = 4;
source_ = Source::Immediate;
break;
case 4: operation_ = Operation::JMPN; break;
case 5:
operation_ = Operation::JMPF;
operand_size_ = 4;
source_ = Source::Immediate;
break;
case 6: operation_ = Operation::PUSH; break;
}
break;
case ModRegRMFormat::MemRegPOP:
source_ = destination_ = memreg;
if(reg != 0) {
reset_parsing();
return std::make_pair(consumed_, Instruction());
}
break;
case ModRegRMFormat::MemRegMOV:
source_ = Source::Immediate;
destination_ = memreg;
operand_size_ = operation_size_;
break;
case ModRegRMFormat::MemRegADD_to_CMP:
destination_ = memreg;
operand_size_ = operation_size_;
switch(reg) {
default: operation_ = Operation::ADD; break;
case 1: operation_ = Operation::OR; break;
case 2: operation_ = Operation::ADC; break;
case 3: operation_ = Operation::SBB; break;
case 4: operation_ = Operation::AND; break;
case 5: operation_ = Operation::SUB; break;
case 6: operation_ = Operation::XOR; break;
case 7: operation_ = Operation::CMP; break;
}
break;
case ModRegRMFormat::MemRegADC_to_CMP:
destination_ = memreg;
source_ = Source::Immediate;
operand_size_ = 1; // ... and always 1; it'll be sign extended if
// the operation requires it.
switch(reg) {
default: {
const auto result = std::make_pair(consumed_, Instruction());
reset_parsing();
return result;
}
case 0: operation_ = Operation::ADD; break;
case 2: operation_ = Operation::ADC; break;
case 3: operation_ = Operation::SBB; break;
case 5: operation_ = Operation::SUB; break;
case 7: operation_ = Operation::CMP; break;
}
break;
default: assert(false);
}
phase_ = (displacement_size_ + operand_size_) ? Phase::AwaitingDisplacementOrOperand : Phase::ReadyToPost;
}
// MARK: - Displacement and operand.
if(phase_ == Phase::AwaitingDisplacementOrOperand && source != end) {
const int required_bytes = displacement_size_ + operand_size_;
const int outstanding_bytes = required_bytes - operand_bytes_;
const int bytes_to_consume = std::min(int(end - source), outstanding_bytes);
// TODO: I can surely do better than this?
for(int c = 0; c < bytes_to_consume; c++) {
inward_data_ = (inward_data_ >> 8) | (uint64_t(source[0]) << 56);
++source;
}
consumed_ += bytes_to_consume;
operand_bytes_ += bytes_to_consume;
if(bytes_to_consume == outstanding_bytes) {
phase_ = Phase::ReadyToPost;
switch(operand_size_) {
default: operand_ = 0; break;
case 1:
operand_ = inward_data_ >> 56; inward_data_ <<= 8;
// Sign extend if a single byte operand is feeding a two-byte instruction.
if(operation_size_ == 2 && operation_ != Operation::IN && operation_ != Operation::OUT) {
operand_ |= (operand_ & 0x80) ? 0xff00 : 0x0000;
}
break;
case 4: displacement_size_ = 2; [[fallthrough]];
case 2: operand_ = inward_data_ >> 48; inward_data_ <<= 16; break;
break;
}
switch(displacement_size_) {
default: displacement_ = 0; break;
case 1: displacement_ = int8_t(inward_data_ >> 56); break;
case 2: displacement_ = int16_t(inward_data_ >> 48); break;
}
} else {
// Provide a genuine measure of further bytes required.
return std::make_pair(-(outstanding_bytes - bytes_to_consume), Instruction());
}
}
// MARK: - Check for completion.
if(phase_ == Phase::ReadyToPost) {
const auto result = std::make_pair(
consumed_,
Instruction(
operation_,
source_,
destination_,
lock_,
segment_override_,
repetition_,
Size(operation_size_),
displacement_,
operand_)
);
reset_parsing();
return result;
}
// i.e. not done yet.
return std::make_pair(0, Instruction());
}

View File

@@ -0,0 +1,155 @@
//
// Decoder.hpp
// Clock Signal
//
// Created by Thomas Harte on 01/01/21.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef InstructionSets_x86_Decoder_hpp
#define InstructionSets_x86_Decoder_hpp
#include "Instruction.hpp"
#include <cstddef>
#include <utility>
namespace InstructionSet {
namespace x86 {
enum class Model {
i8086,
};
/*!
Implements Intel x86 instruction decoding.
This is an experimental implementation; it has not yet undergone significant testing.
*/
class Decoder {
public:
Decoder(Model model);
/*!
@returns an @c Instruction plus a size; a positive size to indicate successful decoding; a
negative size specifies the [negatived] number of further bytes the caller should ideally
collect before calling again. The caller is free to call with fewer, but may not get a decoded
instruction in response, and the decoder may still not be able to complete decoding
even if given that number of bytes.
*/
std::pair<int, Instruction> decode(const uint8_t *source, size_t length);
private:
enum class Phase {
/// Captures all prefixes and continues until an instruction byte is encountered.
Instruction,
/// Receives a ModRegRM byte and either populates the source_ and dest_ fields appropriately
/// or completes decoding of the instruction, as per the instruction format.
ModRegRM,
/// Waits for sufficiently many bytes to pass for the required displacement and operand to be captured.
/// Cf. displacement_size_ and operand_size_.
AwaitingDisplacementOrOperand,
/// Forms and returns an Instruction, and resets parsing state.
ReadyToPost
} phase_ = Phase::Instruction;
/// During the ModRegRM phase, format dictates interpretation of the ModRegRM byte.
///
/// During the ReadyToPost phase, format determines how transiently-recorded fields
/// are packaged into an Instruction.
enum class ModRegRMFormat: uint8_t {
// Parse the ModRegRM for mode, register and register/memory fields
// and populate the source_ and destination_ fields appropriate.
MemReg_Reg,
Reg_MemReg,
// Parse for mode and register/memory fields, populating both
// source_ and destination_ fields with the result. Use the 'register'
// field to pick an operation from the TEST/NOT/NEG/MUL/IMUL/DIV/IDIV group.
MemRegTEST_to_IDIV,
// Parse for mode and register/memory fields, populating both
// source_ and destination_ fields with the result. Use the 'register'
// field to check for the POP operation.
MemRegPOP,
// Parse for mode and register/memory fields, populating both
// the destination_ field with the result and setting source_ to Immediate.
// Use the 'register' field to check for the MOV operation.
MemRegMOV,
// Parse for mode and register/memory fields, populating the
// destination_ field with the result. Use the 'register' field
// to pick an operation from the ROL/ROR/RCL/RCR/SAL/SHR/SAR group.
MemRegROL_to_SAR,
// Parse for mode and register/memory fields, populating the
// destination_ field with the result. Use the 'register' field
// to pick an operation from the ADD/OR/ADC/SBB/AND/SUB/XOR/CMP group and
// waits for an operand equal to the operation size.
MemRegADD_to_CMP,
// Parse for mode and register/memory fields, populating the
// source_ field with the result. Fills destination_ with a segment
// register based on the reg field.
SegReg,
// Parse for mode and register/memory fields, populating the
// source_ and destination_ fields with the result. Uses the
// 'register' field to pick INC or DEC.
MemRegINC_DEC,
// Parse for mode and register/memory fields, populating the
// source_ and destination_ fields with the result. Uses the
// 'register' field to pick from INC/DEC/CALL/JMP/PUSH, altering
// the source to ::Immediate and setting an operand size if necessary.
MemRegINC_to_PUSH,
// Parse for mode and register/memory fields, populating the
// source_ and destination_ fields with the result. Uses the
// 'register' field to pick from ADD/ADC/SBB/SUB/CMP, altering
// the source to ::Immediate and setting an appropriate operand size.
MemRegADC_to_CMP,
} modregrm_format_ = ModRegRMFormat::MemReg_Reg;
// Ephemeral decoding state.
Operation operation_ = Operation::Invalid;
uint8_t instr_ = 0x00; // TODO: is this desired, versus loading more context into ModRegRMFormat?
int consumed_ = 0, operand_bytes_ = 0;
// Source and destination locations.
Source source_ = Source::None;
Source destination_ = Source::None;
// Immediate fields.
int16_t displacement_ = 0;
uint16_t operand_ = 0;
uint64_t inward_data_ = 0;
// Facts about the instruction.
int displacement_size_ = 0; // i.e. size of in-stream displacement, if any.
int operand_size_ = 0; // i.e. size of in-stream operand, if any.
int operation_size_ = 0; // i.e. size of data manipulated by the operation.
// Prefix capture fields.
Repetition repetition_ = Repetition::None;
bool lock_ = false;
Source segment_override_ = Source::None;
/// Resets size capture and all fields with default values.
void reset_parsing() {
consumed_ = operand_bytes_ = 0;
displacement_size_ = operand_size_ = 0;
displacement_ = operand_ = 0;
lock_ = false;
segment_override_ = Source::None;
repetition_ = Repetition::None;
phase_ = Phase::Instruction;
source_ = destination_ = Source::None;
}
};
}
}
#endif /* InstructionSets_x86_Decoder_hpp */

View File

@@ -0,0 +1,303 @@
//
// Instruction.hpp
// Clock Signal
//
// Created by Thomas Harte on 15/01/21.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef InstructionSets_x86_Instruction_h
#define InstructionSets_x86_Instruction_h
#include <cstdint>
namespace InstructionSet {
namespace x86 {
/*
Operations are documented below to establish expectations as to which
instruction fields will be meaningful for each; this is a work-in-progress
and may currently contain errors in the opcode descriptions — especially
where implicit register dependencies are afoot.
*/
enum class Operation: uint8_t {
Invalid,
/// ASCII adjust after addition; source will be AL and destination will be AX.
AAA,
/// ASCII adjust before division; destination will be AX and source will be a multiplier.
AAD,
/// ASCII adjust after multiplication; destination will be AX and source will be a divider.
AAM,
/// ASCII adjust after subtraction; source will be AL and destination will be AX.
AAS,
/// Decimal adjust after addition; source and destination will be AL.
DAA,
/// Decimal adjust after subtraction; source and destination will be AL.
DAS,
/// Convert byte into word; source will be AL, destination will be AH.
CBW,
/// Convert word to double word; source will be AX and destination will be DX.
CWD,
/// Escape, for a coprocessor; perform the bus cycles necessary to read the source and destination and perform a NOP.
ESC,
/// Stops the processor until the next interrupt is fired.
HLT,
/// Waits until the WAIT input is asserted; if an interrupt occurs then it is serviced but returns to the WAIT.
WAIT,
/// Add with carry; source, destination, operand and displacement will be populated appropriately.
ADC,
/// Add; source, destination, operand and displacement will be populated appropriately.
ADD,
/// Subtract with borrow; source, destination, operand and displacement will be populated appropriately.
SBB,
/// Subtract; source, destination, operand and displacement will be populated appropriately.
SUB,
/// Unsigned multiply; multiplies the source value by AX or AL, storing the result in DX:AX or AX.
MUL,
/// Signed multiply; multiplies the source value by AX or AL, storing the result in DX:AX or AX.
IMUL,
/// Unsigned divide; divide the source value by AX or AL, storing the quotient in AL and the remainder in AH.
DIV,
/// Signed divide; divide the source value by AX or AL, storing the quotient in AL and the remainder in AH.
IDIV,
/// Increment; source, destination, operand and displacement will be populated appropriately.
INC,
/// Decrement; source, destination, operand and displacement will be populated appropriately.
DEC,
/// Reads from the port specified by source to the destination.
IN,
/// Writes from the port specified by destination from the source.
OUT,
// Various jumps; see the displacement to calculate targets.
JO, JNO, JB, JNB, JE, JNE, JBE, JNBE,
JS, JNS, JP, JNP, JL, JNL, JLE, JNLE,
/// Far call; see the segment() and offset() fields.
CALLF,
/// Displacement call; followed by a 16-bit operand providing a call offset.
CALLD,
/// Near call.
CALLN,
/// Return from interrupt.
IRET,
/// Near return; if source is not ::None then it will be an ::Immediate indicating how many additional bytes to remove from the stack.
RETF,
/// Far return; if source is not ::None then it will be an ::Immediate indicating how many additional bytes to remove from the stack.
RETN,
/// Near jump; if an operand is not ::None then it gives an absolute destination; otherwise see the displacement.
JMPN,
/// Far jump to the indicated segment and offset.
JMPF,
/// Relative jump performed only if CX = 0; see the displacement.
JPCX,
/// Generates a software interrupt of the level stated in the operand.
INT,
/// Generates a software interrupt of level 3.
INT3,
/// Generates a software interrupt of level 4 if overflow is set.
INTO,
/// Load status flags to AH.
LAHF,
/// Load status flags from AH.
SAHF,
/// Load a segment and offset from the source into DS and the destination.
LDS,
/// Load a segment and offset from the source into ES and the destination.
LES,
/// Computes the effective address of the source and loads it into the destination.
LEA,
/// Compare [bytes or words, per operation size]; source and destination implied to be DS:[SI] and ES:[DI].
CMPS,
/// Load string; reads from DS:SI into AL or AX, subject to segment override.
LODS,
/// Move string; moves a byte or word from DS:SI to ES:DI. If a segment override is provided, it overrides the the source.
MOVS,
/// Scan string; reads a byte or word from DS:SI and compares it to AL or AX.
SCAS,
/// Store string; store AL or AX to ES:DI.
STOS,
// Perform a possibly-conditional loop, decrementing CX. See the displacement.
LOOP, LOOPE, LOOPNE,
/// Loads the destination with the source.
MOV,
/// Negatives; source and destination point to the same thing, to negative.
NEG,
/// Logical NOT; source and destination point to the same thing, to negative.
NOT,
/// Logical AND; source, destination, operand and displacement will be populated appropriately.
AND,
/// Logical OR of source onto destination.
OR,
/// Logical XOR of source onto destination.
XOR,
/// NOP; no further fields.
NOP,
/// POP from the stack to destination.
POP,
/// POP from the stack to the flags register.
POPF,
/// PUSH the source to the stack.
PUSH,
/// PUSH the flags register to the stack.
PUSHF,
/// Rotate the destination left through carry the number of bits indicated by source.
RCL,
/// Rotate the destination right through carry the number of bits indicated by source.
RCR,
/// Rotate the destination left the number of bits indicated by source.
ROL,
/// Rotate the destination right the number of bits indicated by source.
ROR,
/// Arithmetic shift left the destination by the number of bits indicated by source.
SAL,
/// Arithmetic shift right the destination by the number of bits indicated by source.
SAR,
/// Logical shift right the destination by the number of bits indicated by source.
SHR,
/// Clear carry flag; no source or destination provided.
CLC,
/// Clear direction flag; no source or destination provided.
CLD,
/// Clear interrupt flag; no source or destination provided.
CLI,
/// Set carry flag.
STC,
/// Set decimal flag.
STD,
/// Set interrupt flag.
STI,
/// Complement carry flag; no source or destination provided.
CMC,
/// Compare; source, destination, operand and displacement will be populated appropriately.
CMP,
/// Sets flags based on the result of a logical AND of source and destination.
TEST,
/// Exchanges the contents of the source and destination.
XCHG,
/// Load AL with DS:[AL+BX].
XLAT,
};
enum class Size: uint8_t {
Implied = 0,
Byte = 1,
Word = 2,
DWord = 4,
};
enum class Source: uint8_t {
None,
CS, DS, ES, SS,
AL, AH, AX,
BL, BH, BX,
CL, CH, CX,
DL, DH, DX,
SI, DI,
BP, SP,
IndBXPlusSI,
IndBXPlusDI,
IndBPPlusSI,
IndBPPlusDI,
IndSI,
IndDI,
DirectAddress,
IndBP,
IndBX,
Immediate
};
enum class Repetition: uint8_t {
None, RepE, RepNE
};
class Instruction {
public:
Operation operation = Operation::Invalid;
bool operator ==(const Instruction &rhs) const {
return
repetition_size_ == rhs.repetition_size_ &&
sources_ == rhs.sources_ &&
displacement_ == rhs.displacement_ &&
operand_ == rhs.operand_;
}
private:
// b0, b1: a Repetition;
// b2+: operation size.
uint8_t repetition_size_ = 0;
// b0b5: source;
// b6b11: destination;
// b12b14: segment override;
// b15: lock.
uint16_t sources_ = 0;
// Unpackable fields.
int16_t displacement_ = 0;
uint16_t operand_ = 0; // ... or used to store a segment for far operations.
public:
Source source() const { return Source(sources_ & 0x3f); }
Source destination() const { return Source((sources_ >> 6) & 0x3f); }
bool lock() const { return sources_ & 0x8000; }
Source segment_override() const { return Source((sources_ >> 12) & 7); }
Repetition repetition() const { return Repetition(repetition_size_ & 3); }
Size operation_size() const { return Size(repetition_size_ >> 2); }
uint16_t segment() const { return uint16_t(operand_); }
uint16_t offset() const { return uint16_t(displacement_); }
int16_t displacement() const { return displacement_; }
uint16_t operand() const { return operand_; }
Instruction() noexcept {}
Instruction(
Operation operation,
Source source,
Source destination,
bool lock,
Source segment_override,
Repetition repetition,
Size operation_size,
int16_t displacement,
uint16_t operand) noexcept :
operation(operation),
repetition_size_(uint8_t((int(operation_size) << 2) | int(repetition))),
sources_(uint16_t(
int(source) |
(int(destination) << 6) |
(int(segment_override) << 12) |
(int(lock) << 15)
)),
displacement_(displacement),
operand_(operand) {}
};
static_assert(sizeof(Instruction) <= 8);
}
}
#endif /* InstructionSets_x86_Instruction_h */

View File

@@ -9,12 +9,12 @@
#include "AmstradCPC.hpp"
#include "Keyboard.hpp"
#include "FDC.hpp"
#include "../../Processors/Z80/Z80.hpp"
#include "../../Components/6845/CRTC6845.hpp"
#include "../../Components/8255/i8255.hpp"
#include "../../Components/8272/i8272.hpp"
#include "../../Components/AY38910/AY38910.hpp"
#include "../Utility/MemoryFuzzer.hpp"
@@ -24,6 +24,7 @@
#include "../MachineTypes.hpp"
#include "../../Storage/Tape/Tape.hpp"
#include "../../Storage/Tape/Parsers/Spectrum.hpp"
#include "../../ClockReceiver/ForceInline.hpp"
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
@@ -31,6 +32,8 @@
#include "../../Analyser/Static/AmstradCPC/Target.hpp"
#include "../../Numeric/CRC.hpp"
#include <array>
#include <cstdint>
#include <vector>
@@ -673,37 +676,6 @@ class KeyboardState: public GI::AY38910::PortHandler {
};
};
/*!
Wraps the 8272 so as to provide proper clocking and RPM counts, and just directly
exposes motor control, applying the same value to all drives.
*/
class FDC: public Intel::i8272::i8272 {
private:
Intel::i8272::BusHandler bus_handler_;
public:
FDC() : i8272(bus_handler_, Cycles(8000000)) {
emplace_drive(8000000, 300, 1);
set_drive(1);
}
void set_motor_on(bool on) {
get_drive().set_motor_on(on);
}
void select_drive(int) {
// TODO: support more than one drive. (and in set_disk)
}
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int) {
get_drive().set_disk(disk);
}
void set_activity_observer(Activity::Observer *observer) {
get_drive().set_activity_observer(observer, "Drive 1", true);
}
};
/*!
Provides the mechanism of receipt for input and output of the 8255's various ports.
*/
@@ -915,6 +887,59 @@ template <bool has_fdc> class ConcreteMachine:
uint16_t address = cycle.address ? *cycle.address : 0x0000;
switch(cycle.operation) {
case CPU::Z80::PartialMachineCycle::ReadOpcode:
// TODO: just capturing byte reads as below doesn't seem to do that much in terms of acceleration;
// I'm not immediately clear whether that's just because the machine still has to sit through
// pilot tone in real time, or just that almost no software uses the ROM loader.
if(use_fast_tape_hack_ && address == tape_read_byte_address && read_pointers_[0] == roms_[ROMType::OS].data()) {
using Parser = Storage::Tape::ZXSpectrum::Parser;
Parser parser(Parser::MachineType::AmstradCPC);
const auto speed = read_pointers_[tape_speed_value_address >> 14][tape_speed_value_address & 16383];
parser.set_cpc_read_speed(speed);
// Seed with the current pulse; the CPC will have finished the
// preceding symbol and be a short way into the pulse that should determine the
// first bit of this byte.
parser.process_pulse(tape_player_.get_current_pulse());
const auto byte = parser.get_byte(tape_player_.get_tape());
auto flags = z80_.get_value_of_register(CPU::Z80::Register::Flags);
if(byte) {
// In A ROM-esque fashion, begin the first pulse after the final one
// that was just consumed.
tape_player_.complete_pulse();
// Update in-memory CRC.
auto crc_value =
uint16_t(
read_pointers_[tape_crc_address >> 14][tape_crc_address & 16383] |
(read_pointers_[(tape_crc_address+1) >> 14][(tape_crc_address+1) & 16383] << 8)
);
tape_crc_.set_value(crc_value);
tape_crc_.add(*byte);
crc_value = tape_crc_.get_value();
write_pointers_[tape_crc_address >> 14][tape_crc_address & 16383] = uint8_t(crc_value);
write_pointers_[(tape_crc_address+1) >> 14][(tape_crc_address+1) & 16383] = uint8_t(crc_value >> 8);
// Indicate successful byte read.
z80_.set_value_of_register(CPU::Z80::Register::A, *byte);
flags |= CPU::Z80::Flag::Carry;
} else {
// TODO: return tape player to previous state and decline to serve.
z80_.set_value_of_register(CPU::Z80::Register::A, 0);
flags &= ~CPU::Z80::Flag::Carry;
}
z80_.set_value_of_register(CPU::Z80::Register::Flags, flags);
// RET.
*cycle.value = 0xc9;
break;
}
[[fallthrough]];
case CPU::Z80::PartialMachineCycle::Read:
*cycle.value = read_pointers_[address >> 14][address & 16383];
break;
@@ -965,6 +990,7 @@ template <bool has_fdc> class ConcreteMachine:
}
}
break;
case CPU::Z80::PartialMachineCycle::Input:
// Default to nothing answering
*cycle.value = 0xff;
@@ -1062,6 +1088,7 @@ template <bool has_fdc> class ConcreteMachine:
// If there are any tapes supplied, use the first of them.
if(!media.tapes.empty()) {
tape_player_.set_tape(media.tapes.front());
set_use_fast_tape_hack();
}
// Insert up to four disks.
@@ -1089,7 +1116,7 @@ template <bool has_fdc> class ConcreteMachine:
return Utility::TypeRecipient<CharacterMapper>::can_type(c);
}
HalfCycles get_typer_delay() const final {
HalfCycles get_typer_delay(const std::string &) const final {
return z80_.get_is_resetting() ? Cycles(3'400'000) : Cycles(0);
}
@@ -1114,18 +1141,22 @@ template <bool has_fdc> class ConcreteMachine:
// MARK: - Activity Source
void set_activity_observer([[maybe_unused]] Activity::Observer *observer) final {
if constexpr (has_fdc) fdc_.set_activity_observer(observer);
tape_player_.set_activity_observer(observer);
}
// MARK: - Configuration options.
std::unique_ptr<Reflection::Struct> get_options() final {
auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly);
options->output = get_video_signal_configurable();
options->quickload = allow_fast_tape_hack_;
return options;
}
void set_options(const std::unique_ptr<Reflection::Struct> &str) {
const auto options = dynamic_cast<Options *>(str.get());
set_video_signal_configurable(options->output);
allow_fast_tape_hack_ = options->quickload;
set_use_fast_tape_hack();
}
// MARK: - Joysticks
@@ -1188,7 +1219,7 @@ template <bool has_fdc> class ConcreteMachine:
i8255PortHandler i8255_port_handler_;
Intel::i8255::i8255<i8255PortHandler> i8255_;
FDC fdc_;
Amstrad::FDC fdc_;
HalfCycles time_since_fdc_update_;
void flush_fdc() {
if constexpr (has_fdc) {
@@ -1203,6 +1234,18 @@ template <bool has_fdc> class ConcreteMachine:
InterruptTimer interrupt_timer_;
Storage::Tape::BinaryTapePlayer tape_player_;
// By luck these values are the same between the 664 and the 6128;
// therefore the has_fdc template flag is sufficient to locate them.
static constexpr uint16_t tape_read_byte_address = has_fdc ? 0x2b20 : 0x29b0;
static constexpr uint16_t tape_speed_value_address = has_fdc ? 0xb1e7 : 0xbc8f;
static constexpr uint16_t tape_crc_address = has_fdc ? 0xb1eb : 0xb8d3;
CRC::CCITT tape_crc_;
bool use_fast_tape_hack_ = false;
bool allow_fast_tape_hack_ = false;
void set_use_fast_tape_hack() {
use_fast_tape_hack_ = allow_fast_tape_hack_ && tape_player_.has_tape();
}
HalfCycles clock_offset_;
HalfCycles crtc_counter_;
HalfCycles half_cycles_since_ay_update_;
@@ -1219,7 +1262,7 @@ template <bool has_fdc> class ConcreteMachine:
ROMType upper_rom_;
uint8_t *ram_pages_[4];
uint8_t *read_pointers_[4];
const uint8_t *read_pointers_[4];
uint8_t *write_pointers_[4];
KeyboardState key_state_;

View File

@@ -29,12 +29,21 @@ class Machine {
static Machine *AmstradCPC(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher);
/// Defines the runtime options available for an Amstrad CPC.
class Options: public Reflection::StructImpl<Options>, public Configurable::DisplayOption<Options> {
class Options:
public Reflection::StructImpl<Options>,
public Configurable::DisplayOption<Options>,
public Configurable::QuickloadOption<Options>
{
friend Configurable::DisplayOption<Options>;
friend Configurable::QuickloadOption<Options>;
public:
Options(Configurable::OptionsType) : Configurable::DisplayOption<Options>(Configurable::Display::RGB) {
Options(Configurable::OptionsType type) :
Configurable::DisplayOption<Options>(Configurable::Display::RGB),
Configurable::QuickloadOption<Options>(type == Configurable::OptionsType::UserFriendly)
{
if(needs_declare()) {
declare_display_option();
declare_quickload_option();
limit_enum(&output, Configurable::Display::RGB, Configurable::Display::CompositeColour, -1);
}
}

View File

@@ -0,0 +1,51 @@
//
// FDC.hpp
// Clock Signal
//
// Created by Thomas Harte on 22/03/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef FDC_h
#define FDC_h
#include "../../Components/8272/i8272.hpp"
namespace Amstrad {
/*!
Wraps the 8272 so as to provide proper clocking and RPM counts, and just directly
exposes motor control, applying the same value to all drives.
*/
class FDC: public Intel::i8272::i8272 {
private:
Intel::i8272::BusHandler bus_handler_;
public:
FDC(Cycles clock_rate = Cycles(8000000)) :
i8272(bus_handler_, clock_rate)
{
emplace_drive(clock_rate.as<int>(), 300, 1);
set_drive(1);
}
void set_motor_on(bool on) {
get_drive().set_motor_on(on);
}
void select_drive(int) {
// TODO: support more than one drive. (and in set_disk)
}
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int) {
get_drive().set_disk(disk);
}
void set_activity_observer(Activity::Observer *observer) {
get_drive().set_activity_observer(observer, "Drive 1", true);
}
};
}
#endif /* FDC_h */

113
Machines/Apple/ADB/Bus.cpp Normal file
View File

@@ -0,0 +1,113 @@
//
// Bus.cpp
// Clock Signal
//
// Created by Thomas Harte on 10/02/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "Bus.hpp"
using namespace Apple::ADB;
Bus::Bus(HalfCycles clock_speed) : half_cycles_to_microseconds_(1'000'000.0 / clock_speed.as<double>()) {}
void Bus::run_for(HalfCycles duration) {
time_in_state_ += duration;
time_since_get_state_ += duration;
}
void Bus::set_device_output(size_t device_id, bool output) {
// Modify the all-devices bus state.
bus_state_[device_id] = output;
// React to signal edges only; don't use get_state here to avoid
// endless recursion should any reactive devices set new output
// during the various calls made below.
const bool data_level = bus_state_.all();
if(data_level_ != data_level) {
data_level_ = data_level;
if(data_level) {
// This was a transition to high; classify what just happened according to
// the duration of the low period.
const double low_microseconds = time_in_state_.as<double>() * half_cycles_to_microseconds_;
// Low periods:
// (partly as adapted from the AN591 data sheet; otherwise from the IIgs reference manual)
//
// > 1040 µs reset
// 5601040 µs attention
// < 50 µs 1
// 5072 µs 0
// 300 µs service request
if(low_microseconds > 1040.0) {
for(auto device: devices_) {
device->adb_bus_did_observe_event(Event::Reset);
}
} else if(low_microseconds >= 560.0) {
for(auto device: devices_) {
device->adb_bus_did_observe_event(Event::Attention);
}
shift_register_ = 1;
start_target_ = 9; // Consume the stop bit before posting the next byte.
phase_ = Phase::AttentionCapture;
} else if(low_microseconds < 50.0) {
shift(1);
} else if(low_microseconds < 72.0) {
shift(0);
} else if(low_microseconds >= 291.0 && low_microseconds <= 309.0) {
for(auto device: devices_) {
device->adb_bus_did_observe_event(Event::ServiceRequest);
}
} else {
for(auto device: devices_) {
device->adb_bus_did_observe_event(Event::Unrecognised);
}
}
}
time_in_state_ = HalfCycles(0);
}
}
void Bus::shift(unsigned int value) {
shift_register_ = (shift_register_ << 1) | value;
// Trigger a byte whenever either:
// * a 'start bit' hits bit 8; or
// * if this was a command byte, wait for the stop bit (i.e. the start bit hits 9).
if(shift_register_ & (1 << start_target_)) {
for(auto device: devices_) {
device->adb_bus_did_observe_event(Event::Byte, uint8_t(shift_register_ >> (start_target_ - 8)));
}
// Expect a real start bit only if moving from attention capture to packet
// capture. Otherwise adopt an implied start bit.
shift_register_ = phase_ == Phase::PacketCapture;
start_target_ = 8;
phase_ = Phase::PacketCapture;
}
}
bool Bus::get_state() const {
const auto microseconds = time_since_get_state_.as<double>() * half_cycles_to_microseconds_;
time_since_get_state_ = HalfCycles(0);
const bool current_level = bus_state_.all();
for(auto device: devices_) {
device->advance_state(microseconds, current_level);
}
return bus_state_.all();
}
size_t Bus::add_device() {
const size_t id = next_device_id_;
++next_device_id_;
return id;
}
size_t Bus::add_device(Device *device) {
devices_.push_back(device);
return add_device();
}

172
Machines/Apple/ADB/Bus.hpp Normal file
View File

@@ -0,0 +1,172 @@
//
// Bus.hpp
// Clock Signal
//
// Created by Thomas Harte on 10/02/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Bus_hpp
#define Bus_hpp
#include "../../../ClockReceiver/ClockReceiver.hpp"
#include <bitset>
#include <cstddef>
#include <ostream>
#include <vector>
namespace Apple {
namespace ADB {
struct Command {
enum class Type {
Reset,
Flush,
Reserved,
/// The host wishes the device to store register contents.
Listen,
/// The host wishes the device to broadcast register contents.
Talk
};
static constexpr uint8_t AllDevices = 0xff;
static constexpr uint8_t NoRegister = 0xff;
Type type = Type::Reserved;
uint8_t device = AllDevices;
uint8_t reg = NoRegister;
Command() {}
Command(Type type) : type(type) {}
Command(Type type, uint8_t device) : type(type), device(device) {}
Command(Type type, uint8_t device, uint8_t reg) : type(type), device(device), reg(reg) {}
};
inline std::ostream &operator <<(std::ostream &stream, Command::Type type) {
switch(type) {
case Command::Type::Reset: stream << "reset"; break;
case Command::Type::Flush: stream << "flush"; break;
case Command::Type::Listen: stream << "listen"; break;
case Command::Type::Talk: stream << "talk"; break;
default: stream << "reserved"; break;
}
return stream;
}
inline std::ostream &operator <<(std::ostream &stream, Command command) {
stream << "Command {";
if(command.device != 0xff) stream << "device " << int(command.device) << ", ";
if(command.reg != 0xff) stream << "register " << int(command.reg) << ", ";
stream << command.type;
stream << "}";
return stream;
}
/*!
@returns The @c Command encoded in @c code.
*/
inline Command decode_command(uint8_t code) {
switch(code & 0x0f) {
default: return Command();
case 0: return Command(Command::Type::Reset);
case 1: return Command(Command::Type::Flush, code >> 4);
case 8: case 9: case 10: case 11:
return Command(Command::Type::Listen, code >> 4, code & 3);
case 12: case 13: case 14: case 15:
return Command(Command::Type::Talk, code >> 4, code & 3);
}
}
/*!
The ADB bus models the data line of the ADB bus; it allows multiple devices to
post their current data level, or read the current level, and also offers a tokenised
version of all activity on the bus.
In implementation terms, two types of device are envisaged:
* proactive devices, which use @c add_device() and then merely @c set_device_output
and @c get_state() as required, according to their own tracking of time; and
* reactive devices, which use @c add_device(Device*) and then merely react to
@c adb_bus_did_observe_event and @c advance_state in order to
update @c set_device_output.
*/
class Bus {
public:
Bus(HalfCycles clock_speed);
/*!
Advances time; ADB is a clocked serial signal.
*/
void run_for(HalfCycles);
/*!
Adds a device to the bus, returning the index it should use
to refer to itself in subsequent calls to set_device_output.
*/
size_t add_device();
/*!
Sets the current data line output for @c device.
*/
void set_device_output(size_t device_id, bool output);
/*!
@returns The current state of the ADB data line.
*/
bool get_state() const;
enum class Event {
Reset,
Attention,
Byte,
ServiceRequest,
Unrecognised
};
struct Device {
/// Reports to an observer that @c event was observed in the activity
/// observed on this bus. If this was a byte event, that byte's value is given as @c value.
virtual void adb_bus_did_observe_event(Event event, uint8_t value = 0xff) = 0;
/// Requests that the device update itself @c microseconds and, if necessary, post a
/// new value ot @c set_device_output. This will be called only when the bus needs
/// to reevaluate its current level. It cannot reliably be used to track the timing between
/// observed events.
virtual void advance_state(double microseconds, bool current_level) = 0;
};
/*!
Adds a device.
*/
size_t add_device(Device *);
private:
HalfCycles time_in_state_;
mutable HalfCycles time_since_get_state_;
double half_cycles_to_microseconds_ = 1.0;
std::vector<Device *> devices_;
unsigned int shift_register_ = 0;
unsigned int start_target_ = 8;
bool data_level_ = true;
// ADB addressing supports at most 16 devices but that doesn't include
// the controller. So assume a maximum of 17 connected devices.
std::bitset<17> bus_state_{0xffffffff};
size_t next_device_id_ = 0;
inline void shift(unsigned int);
enum class Phase {
PacketCapture,
AttentionCapture
} phase_ = Phase::AttentionCapture;
};
}
}
#endif /* Bus_hpp */

View File

@@ -0,0 +1,242 @@
//
// Keyboard.cpp
// Clock Signal
//
// Created by Thomas Harte on 13/02/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "Keyboard.hpp"
using namespace Apple::ADB;
Keyboard::Keyboard(Bus &bus) : ReactiveDevice(bus, 2) {}
void Keyboard::perform_command(const Command &command) {
switch(command.type) {
case Command::Type::Reset:
modifiers_ = 0xffff;
case Command::Type::Flush: {
std::lock_guard lock_guard(keys_mutex_);
pending_events_.clear();
} break;
case Command::Type::Talk:
switch(command.reg) {
case 0: {
// Post up to two key events, or nothing if there are
// no events pending.
std::lock_guard lock_guard(keys_mutex_);
if(!pending_events_.empty()) {
if(pending_events_.size() > 1) {
post_response({pending_events_[0], pending_events_[1]});
pending_events_.erase(pending_events_.begin(), pending_events_.begin()+2);
} else {
// Two bytes are required; provide a key up of the fictional
// key zero as the second.
// That's arbitrary; verify with real machines.
post_response({pending_events_[0], 0x80});
pending_events_.clear();
}
}
} break;
case 2: {
std::lock_guard lock_guard(keys_mutex_);
post_response({uint8_t(modifiers_ >> 8), uint8_t(modifiers_)});
} break;
default: break;
}
break;
case Command::Type::Listen:
// If a listen is incoming for register 2, prepare to capture LED statuses.
if(command.reg == 2) {
receive_bytes(2);
}
break;
default: break;
}
}
void Keyboard::did_receive_data(const Command &, const std::vector<uint8_t> &data) {
// This must be a register 2 listen; update the LED statuses.
// TODO: and possibly display these.
modifiers_ = (modifiers_ & 0xfff8) | (data[1] & 7);
}
bool Keyboard::set_key_pressed(Key key, bool is_pressed) {
// ADB keyboard events: low 7 bits are a key code; bit 7 is either 0 for pressed or 1 for released.
std::lock_guard lock_guard(keys_mutex_);
pending_events_.push_back(uint8_t(key) | (is_pressed ? 0x00 : 0x80));
pressed_keys_[size_t(key)] = is_pressed;
// Track modifier state also.
/*
In all cases below: 0 = pressed/on; 1 = released/off.
b15: None (reserved)
b14: Delete
b13: Caps lock
b12: Reset
b11: Control
b10: Shift
b9: Option
b8: Command
-- From here onwards, available only on the extended keyboard.
b7: Num lock/clear
b6: Scroll lock
b53: None (reserved)
b2: Scroll Lock LED
b1: Caps Lock LED
b0: Num Lock LED
*/
#define SetModifierBit(x) modifiers_ = (modifiers_ & ~x) | (is_pressed ? 0 : x);
#define ToggleModifierBit(x) if(is_pressed) modifiers_ ^= x;
switch(key) {
default: break;
case Key::Delete: SetModifierBit(0x4000); break;
case Key::CapsLock: ToggleModifierBit(0x2000); break;
case Key::Power: SetModifierBit(0x1000); break;
case Key::LeftControl:
case Key::RightControl:
SetModifierBit(0x0800);
break;
case Key::LeftShift:
case Key::RightShift:
SetModifierBit(0x0400);
break;
case Key::LeftOption:
case Key::RightOption:
SetModifierBit(0x0200);
break;
case Key::Command:
SetModifierBit(0x0100);
break;
case Key::KeypadClear: ToggleModifierBit(0x0080); break;
case Key::Help: ToggleModifierBit(0x0040); break;
}
#undef SetModifierBit
// Ensure service occurs.
post_service_request();
return true;
}
void Keyboard::clear_all_keys() {
// For all keys currently marked as down, enqueue key-up actions.
std::lock_guard lock_guard(keys_mutex_);
for(size_t key = 0; key < pressed_keys_.size(); key++) {
if(pressed_keys_[key]) {
pending_events_.push_back(0x80 | uint8_t(key));
pressed_keys_[key] = false;
}
}
if(!pending_events_.empty()) post_service_request();
// Mark all modifiers as released.
modifiers_ |= 0xfff8;
}
// MARK: - KeyboardMapper
uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) const {
using Key = Inputs::Keyboard::Key;
using ADBKey = Apple::ADB::Key;
switch(key) {
default: return MachineTypes::MappedKeyboardMachine::KeyNotMapped;
#define Bind(x, y) case Key::x: return uint16_t(ADBKey::y)
#define BindDirect(x) Bind(x, x)
BindDirect(BackTick);
BindDirect(k1); BindDirect(k2); BindDirect(k3); BindDirect(k4); BindDirect(k5);
BindDirect(k6); BindDirect(k7); BindDirect(k8); BindDirect(k9); BindDirect(k0);
BindDirect(Help);
BindDirect(Home);
BindDirect(PageUp);
BindDirect(Delete);
BindDirect(End);
BindDirect(PageDown);
BindDirect(Escape);
BindDirect(Hyphen);
BindDirect(Equals);
BindDirect(Backspace);
BindDirect(Tab);
BindDirect(F1); BindDirect(F2); BindDirect(F3); BindDirect(F4);
BindDirect(F5); BindDirect(F6); BindDirect(F7); BindDirect(F8);
BindDirect(F9); BindDirect(F10); BindDirect(F11); BindDirect(F12);
BindDirect(Q); BindDirect(W); BindDirect(E); BindDirect(R);
BindDirect(T); BindDirect(Y); BindDirect(U); BindDirect(I);
BindDirect(O); BindDirect(P); BindDirect(A); BindDirect(S);
BindDirect(D); BindDirect(F); BindDirect(G); BindDirect(H);
BindDirect(J); BindDirect(K); BindDirect(L); BindDirect(Z);
BindDirect(X); BindDirect(C); BindDirect(V); BindDirect(B);
BindDirect(N); BindDirect(M);
BindDirect(OpenSquareBracket);
BindDirect(CloseSquareBracket);
BindDirect(Semicolon);
BindDirect(Quote);
BindDirect(Comma);
BindDirect(FullStop);
BindDirect(ForwardSlash);
BindDirect(CapsLock);
BindDirect(LeftShift); BindDirect(RightShift);
BindDirect(LeftControl); BindDirect(RightControl);
BindDirect(LeftOption); BindDirect(RightOption);
Bind(LeftMeta, Command); Bind(RightMeta, Command);
BindDirect(Space);
BindDirect(Backslash);
Bind(Enter, Return);
BindDirect(Left); BindDirect(Right);
BindDirect(Up); BindDirect(Down);
Bind(KeypadDelete, KeypadClear);
BindDirect(KeypadEquals);
BindDirect(KeypadSlash);
BindDirect(KeypadAsterisk);
BindDirect(KeypadMinus);
BindDirect(KeypadPlus);
BindDirect(KeypadEnter);
BindDirect(KeypadDecimalPoint);
BindDirect(Keypad9);
BindDirect(Keypad8);
BindDirect(Keypad7);
BindDirect(Keypad6);
BindDirect(Keypad5);
BindDirect(Keypad4);
BindDirect(Keypad3);
BindDirect(Keypad2);
BindDirect(Keypad1);
BindDirect(Keypad0);
// Leaving unmapped:
// Power, F13, F14, F15
#undef BindDirect
#undef Bind
}
}

View File

@@ -0,0 +1,125 @@
//
// Keyboard.hpp
// Clock Signal
//
// Created by Thomas Harte on 13/02/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Keyboard_hpp
#define Keyboard_hpp
#include "ReactiveDevice.hpp"
#include "../../../Inputs/Keyboard.hpp"
#include "../../KeyboardMachine.hpp"
#include <array>
#include <cstdint>
#include <mutex>
#include <vector>
namespace Apple {
namespace ADB {
/*!
Defines the keycodes that could be passed directly via set_key_pressed; these
are based on the Apple Extended Keyboard.
*/
enum class Key: uint16_t {
/*
These are transcribed from Page 19-11 of
the Macintosh Family Hardware Reference.
*/
BackTick = 0x32,
k1 = 0x12, k2 = 0x13, k3 = 0x14, k4 = 0x15, k5 = 0x17,
k6 = 0x16, k7 = 0x1a, k8 = 0x1c, k9 = 0x19, k0 = 0x1d,
Help = 0x72,
Home = 0x73,
PageUp = 0x74,
Delete = 0x75,
End = 0x77,
PageDown = 0x79,
Escape = 0x35,
Hyphen = 0x1b,
Equals = 0x18,
Backspace = 0x33,
Tab = 0x30,
Power = 0x7f,
F1 = 0x7a, F2 = 0x78, F3 = 0x63, F4 = 0x76,
F5 = 0x60, F6 = 0x61, F7 = 0x62, F8 = 0x64,
F9 = 0x65, F10 = 0x6d, F11 = 0x67, F12 = 0x6f,
F13 = 0x69, F14 = 0x6b, F15 = 0x71,
Q = 0x0c, W = 0x0d, E = 0x0e, R = 0x0f, T = 0x11, Y = 0x10, U = 0x20, I = 0x22, O = 0x1f, P = 0x23,
A = 0x00, S = 0x01, D = 0x02, F = 0x03, G = 0x05, H = 0x04, J = 0x26, K = 0x28, L = 0x25,
Z = 0x06, X = 0x07, C = 0x08, V = 0x09, B = 0x0b, N = 0x2d, M = 0x2e,
OpenSquareBracket = 0x21,
CloseSquareBracket = 0x1e,
Semicolon = 0x29,
Quote = 0x27,
Comma = 0x2b,
FullStop = 0x2f,
ForwardSlash = 0x2c,
CapsLock = 0x39,
LeftShift = 0x38, RightShift = 0x7b,
LeftControl = 0x36, RightControl = 0x7d,
LeftOption = 0x3a, RightOption = 0x7c,
Command = 0x37,
Space = 0x31,
Backslash = 0x2a,
Return = 0x24,
Left = 0x3b,
Right = 0x3c,
Up = 0x3e,
Down = 0x3d,
KeypadClear = 0x47,
KeypadEquals = 0x51,
KeypadSlash = 0x4b,
KeypadAsterisk = 0x43,
KeypadMinus = 0x4e,
KeypadPlus = 0x45,
KeypadEnter = 0x4c,
KeypadDecimalPoint = 0x41,
Keypad9 = 0x5c, Keypad8 = 0x5b, Keypad7 = 0x59,
Keypad6 = 0x58, Keypad5 = 0x57, Keypad4 = 0x56,
Keypad3 = 0x55, Keypad2 = 0x54, Keypad1 = 0x53,
Keypad0 = 0x52,
};
class Keyboard: public ReactiveDevice {
public:
Keyboard(Bus &);
bool set_key_pressed(Key key, bool is_pressed);
void clear_all_keys();
private:
void perform_command(const Command &command) override;
void did_receive_data(const Command &, const std::vector<uint8_t> &) override;
std::mutex keys_mutex_;
std::array<bool, 128> pressed_keys_;
std::vector<uint8_t> pending_events_;
uint16_t modifiers_ = 0xffff;
};
/*!
Provides a mapping from idiomatic PC keys to ADB keys.
*/
class KeyboardMapper: public MachineTypes::MappedKeyboardMachine::KeyboardMapper {
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) const final;
};
}
}
#endif /* Keyboard_hpp */

View File

@@ -0,0 +1,62 @@
//
// Mouse.cpp
// Clock Signal
//
// Created by Thomas Harte on 12/02/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "Mouse.hpp"
using namespace Apple::ADB;
Mouse::Mouse(Bus &bus) : ReactiveDevice(bus, 3) {}
void Mouse::perform_command(const Command &command) {
if(command.type == Command::Type::Talk && command.reg == 0) {
// Read current deltas and buttons, thread safely.
auto delta_x = delta_x_.exchange(0);
auto delta_y = delta_y_.exchange(0);
const int buttons = button_flags_;
// Clamp deltas.
delta_x = std::max(std::min(delta_x, int16_t(127)), int16_t(-128));
delta_y = std::max(std::min(delta_y, int16_t(127)), int16_t(-128));
// Figure out what that would look like, and don't respond if there's
// no change to report.
const uint16_t reg0 =
((buttons & 1) ? 0x0000 : 0x8000) |
((buttons & 2) ? 0x0000 : 0x0080) |
uint16_t(delta_x & 0x7f) |
uint16_t((delta_y & 0x7f) << 8);
if(reg0 == last_posted_reg0_) return;
// Post change.
last_posted_reg0_ = reg0;
post_response({uint8_t(reg0 >> 8), uint8_t(reg0)});
}
}
void Mouse::move(int x, int y) {
delta_x_ += int16_t(x);
delta_y_ += int16_t(y);
post_service_request();
}
int Mouse::get_number_of_buttons() {
return 2;
}
void Mouse::set_button_pressed(int index, bool is_pressed) {
if(is_pressed)
button_flags_ |= (1 << index);
else
button_flags_ &= ~(1 << index);
post_service_request();
}
void Mouse::reset_all_buttons() {
button_flags_ = 0;
post_service_request();
}

View File

@@ -0,0 +1,38 @@
//
// Mouse.hpp
// Clock Signal
//
// Created by Thomas Harte on 12/02/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Mouse_hpp
#define Mouse_hpp
#include "ReactiveDevice.hpp"
#include "../../../Inputs/Mouse.hpp"
namespace Apple {
namespace ADB {
class Mouse: public ReactiveDevice, public Inputs::Mouse {
public:
Mouse(Bus &);
private:
void perform_command(const Command &command) override;
void move(int x, int y) override;
int get_number_of_buttons() override;
void set_button_pressed(int index, bool is_pressed) override;
void reset_all_buttons() override;
std::atomic<int16_t> delta_x_, delta_y_;
std::atomic<int> button_flags_ = 0;
uint16_t last_posted_reg0_ = 0;
};
}
}
#endif /* Mouse_hpp */

View File

@@ -0,0 +1,180 @@
//
// ReactiveDevice.cpp
// Clock Signal
//
// Created by Thomas Harte on 12/02/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "ReactiveDevice.hpp"
#define LOG_PREFIX "[ADB device] "
#include "../../../Outputs/Log.hpp"
using namespace Apple::ADB;
ReactiveDevice::ReactiveDevice(Apple::ADB::Bus &bus, uint8_t adb_device_id) :
bus_(bus),
device_id_(bus.add_device(this)),
default_adb_device_id_(adb_device_id) {
reset();
}
void ReactiveDevice::post_response(const std::vector<uint8_t> &&response) {
response_ = std::move(response);
microseconds_at_bit_ = 0.0;
bit_offset_ = -2;
}
void ReactiveDevice::advance_state(double microseconds, bool current_level) {
// First test: is a service request desired?
if(phase_ == Phase::ServiceRequestPending) {
microseconds_at_bit_ += microseconds;
if(microseconds_at_bit_ < 240.0) {
bus_.set_device_output(device_id_, false);
} else {
bus_.set_device_output(device_id_, true);
phase_ = Phase::AwaitingAttention;
}
return;
}
// Do nothing if not in the process of posting a response.
if(response_.empty()) return;
/*
Total process below:
(1) assume that the data was enqueued before the stop bit had
concluded; wait for the end of that;
(2) wait for the stop-to-start time period;
(3) output a start bit of '1';
(4) output all enqueued bytes, MSB to LSB;
(5) output a stop bit of '0'; and
(6) return this device's output level to high and top.
*/
// Wait for the bus to be clear if transmission has not yet begun.
if(!current_level && bit_offset_ == -2) return;
// Advance time.
microseconds_at_bit_ += microseconds;
// If this is the start of the packet, wait an appropriate stop-to-start time.
if(bit_offset_ == -2) {
if(microseconds_at_bit_ < 150.0) {
return;
}
microseconds_at_bit_ -= 150.0;
++bit_offset_;
}
// Advance the implied number of bits.
const int step = int(microseconds_at_bit_ / 100.0);
bit_offset_ += step;
microseconds_at_bit_ -= double(step * 100.0);
// Check for end-of-transmission.
const int response_bit_length = int(response_.size() * 8);
if(bit_offset_ >= 1 + response_bit_length) {
bus_.set_device_output(device_id_, true);
response_.clear();
return;
}
// Otherwise pick the bit to output: it'll either be the start bit of 1,
// from the provided data, or a stop bit of 0.
int bit = 0;
if(bit_offset_ < 0) {
bit = 1;
} else if(bit_offset_ < response_bit_length) {
const int byte = bit_offset_ >> 3;
const int packet = int(response_[size_t(byte)]);
bit = (packet >> (7 - (bit_offset_ & 7))) & 1;
}
// Convert that into a level.
constexpr double low_periods[] = {66, 33};
bus_.set_device_output(device_id_, microseconds_at_bit_ > low_periods[bit]);
}
void ReactiveDevice::adb_bus_did_observe_event(Bus::Event event, uint8_t value) {
if(phase_ == Phase::AwaitingAttention) {
if(event != Bus::Event::Attention) return;
phase_ = Phase::AwaitingCommand;
return;
}
if(event != Bus::Event::Byte) return;
if(phase_ == Phase::AwaitingContent) {
content_.push_back(value);
if(content_.size() == expected_content_size_) {
phase_ = Phase::AwaitingAttention;
if(command_.reg == 3) {
register3_ = uint16_t((content_[0] << 8) | content_[1]);
} else {
did_receive_data(command_, content_);
}
content_.clear();
}
}
if(phase_ == Phase::AwaitingCommand) {
phase_ = Phase::AwaitingAttention;
command_ = decode_command(value);
// LOG(command_);
// If this command doesn't apply here, but a service request is requested,
// post a service request.
if(command_.device != Command::AllDevices && command_.device != ((register3_ >> 8) & 0xf)) {
if(service_desired_) {
service_desired_ = false;
stop_has_begin_ = false;
phase_ = Phase::ServiceRequestPending;
microseconds_at_bit_ = 0.0;
}
return;
}
// Handle reset and register 3 here automatically; pass everything else along.
switch(command_.type) {
case Command::Type::Reset:
reset();
[[fallthrough]];
default:
perform_command(command_);
break;
case Command::Type::Listen:
case Command::Type::Talk:
if(command_.reg == 3) {
if(command_.type == Command::Type::Talk) {
post_response({uint8_t(register3_ >> 8), uint8_t(register3_ & 0xff)});
} else {
receive_bytes(2);
}
} else {
service_desired_ = false;
perform_command(command_);
}
break;
}
}
}
void ReactiveDevice::receive_bytes(size_t count) {
content_.clear();
expected_content_size_ = count;
phase_ = Phase::AwaitingContent;
}
void ReactiveDevice::reset() {
register3_ = uint16_t(0x6001 | (default_adb_device_id_ << 8));
}
void ReactiveDevice::post_service_request() {
service_desired_ = true;
}

View File

@@ -0,0 +1,66 @@
//
// ReactiveDevice.hpp
// Clock Signal
//
// Created by Thomas Harte on 12/02/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef ReactiveDevice_hpp
#define ReactiveDevice_hpp
#include "Bus.hpp"
#include <atomic>
#include <cstddef>
#include <vector>
namespace Apple {
namespace ADB {
class ReactiveDevice: public Bus::Device {
protected:
ReactiveDevice(Bus &bus, uint8_t adb_device_id);
void post_response(const std::vector<uint8_t> &&response);
void post_service_request();
void receive_bytes(size_t count);
virtual void perform_command(const Command &command) = 0;
virtual void did_receive_data(const Command &, const std::vector<uint8_t> &) {}
private:
void advance_state(double microseconds, bool current_level) override;
void adb_bus_did_observe_event(Bus::Event event, uint8_t value) override;
private:
Bus &bus_;
const size_t device_id_;
std::vector<uint8_t> response_;
int bit_offset_ = 0;
double microseconds_at_bit_ = 0;
enum class Phase {
AwaitingAttention,
AwaitingCommand,
AwaitingContent,
ServiceRequestPending,
} phase_ = Phase::AwaitingAttention;
std::vector<uint8_t> content_;
size_t expected_content_size_ = 0;
Command command_;
bool stop_has_begin_ = false;
uint16_t register3_;
const uint8_t default_adb_device_id_;
std::atomic<bool> service_desired_ = false;
void reset();
};
}
}
#endif /* ReactiveDevice_hpp */

View File

@@ -19,8 +19,11 @@
#include "../../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#include "../../../Outputs/Log.hpp"
#include "AuxiliaryMemorySwitches.hpp"
#include "Card.hpp"
#include "DiskIICard.hpp"
#include "Joystick.hpp"
#include "LanguageCardSwitches.hpp"
#include "Video.hpp"
#include "../../../Analyser/Static/AppleII/Target.hpp"
@@ -37,6 +40,7 @@ namespace II {
#define is_iie() ((model == Analyser::Static::AppleII::Target::Model::IIe) || (model == Analyser::Static::AppleII::Target::Model::EnhancedIIe))
template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
public Apple::II::Machine,
public MachineTypes::TimedMachine,
public MachineTypes::ScanProducer,
public MachineTypes::AudioProducer,
@@ -44,9 +48,7 @@ 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 Apple::II::Machine,
public Activity::Source,
public Apple::II::Card::Delegate {
private:
@@ -63,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;
@@ -88,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_;
@@ -175,7 +171,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
to paging every 6502 page of memory independently. It makes the paging events more expensive,
but hopefully more clear.
*/
uint8_t *read_pages_[256]; // each is a pointer to the 256-block of memory the CPU should read when accessing that page of memory
const uint8_t *read_pages_[256]; // each is a pointer to the 256-block of memory the CPU should read when accessing that page of memory
uint8_t *write_pages_[256]; // as per read_pages_, but this is where the CPU should write. If a pointer is nullptr, don't write.
void page(int start, int end, uint8_t *read, uint8_t *write) {
for(int position = start; position < end; ++position) {
@@ -187,132 +183,166 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
}
}
// MARK: - The language card.
struct {
bool bank1 = false;
bool read = false;
bool pre_write = false;
bool write = false;
} language_card_;
bool has_language_card_ = true;
// MARK: The language card.
LanguageCardSwitches<ConcreteMachine> language_card_;
AuxiliaryMemorySwitches<ConcreteMachine> auxiliary_switches_;
friend LanguageCardSwitches<ConcreteMachine>;
friend AuxiliaryMemorySwitches<ConcreteMachine>;
void set_language_card_paging() {
uint8_t *const ram = alternative_zero_page_ ? aux_ram_ : ram_;
const auto language_state = language_card_.state();
const auto zero_state = auxiliary_switches_.zero_state();
uint8_t *const ram = zero_state ? aux_ram_ : ram_;
uint8_t *const rom = is_iie() ? &rom_[3840] : rom_.data();
// Which way the region here is mapped to be banks 1 and 2 is
// arbitrary.
page(0xd0, 0xe0,
language_card_.read ? &ram[language_card_.bank1 ? 0xd000 : 0xc000] : rom,
language_card_.write ? nullptr : &ram[language_card_.bank1 ? 0xd000 : 0xc000]);
language_state.read ? &ram[language_state.bank2 ? 0xd000 : 0xc000] : rom,
language_state.write ? nullptr : &ram[language_state.bank2 ? 0xd000 : 0xc000]);
page(0xe0, 0x100,
language_card_.read ? &ram[0xe000] : &rom[0x1000],
language_card_.write ? nullptr : &ram[0xe000]);
language_state.read ? &ram[0xe000] : &rom[0x1000],
language_state.write ? nullptr : &ram[0xe000]);
}
// MARK - The IIe's ROM controls.
bool internal_CX_rom_ = false;
bool slot_C3_rom_ = false;
bool internal_c8_rom_ = false;
// MARK: Auxiliary memory and the other IIe improvements.
void set_card_paging() {
page(0xc1, 0xc8, internal_CX_rom_ ? rom_.data() : nullptr, nullptr);
const auto state = auxiliary_switches_.card_state();
if(!internal_CX_rom_) {
if(!slot_C3_rom_) read_pages_[0xc3] = &rom_[0xc300 - 0xc100];
}
page(0xc8, 0xd0, (internal_CX_rom_ || internal_c8_rom_) ? &rom_[0xc800 - 0xc100] : nullptr, nullptr);
page(0xc1, 0xc4, state.region_C1_C3 ? &rom_[0xc100 - 0xc100] : nullptr, nullptr);
read_pages_[0xc3] = state.region_C3 ? &rom_[0xc300 - 0xc100] : nullptr;
page(0xc4, 0xc8, state.region_C4_C8 ? &rom_[0xc400 - 0xc100] : nullptr, nullptr);
page(0xc8, 0xd0, state.region_C8_D0 ? &rom_[0xc800 - 0xc100] : nullptr, nullptr);
}
// MARK - The IIe's auxiliary RAM controls.
bool alternative_zero_page_ = false;
void set_zero_page_paging() {
if(alternative_zero_page_) {
read_pages_[0] = aux_ram_;
if(auxiliary_switches_.zero_state()) {
write_pages_[0] = aux_ram_;
} else {
read_pages_[0] = ram_;
write_pages_[0] = ram_;
}
read_pages_[1] = read_pages_[0] + 256;
write_pages_[0] = read_pages_[0];
write_pages_[1] = read_pages_[1];
write_pages_[1] = write_pages_[0] + 256;
read_pages_[0] = write_pages_[0];
read_pages_[1] = write_pages_[1];
// Zero page banking also affects interpretation of the language card's switches.
set_language_card_paging();
}
void set_main_paging() {
const auto state = auxiliary_switches_.main_state();
page(0x02, 0x04,
state.base.read ? &aux_ram_[0x0200] : &ram_[0x0200],
state.base.write ? &aux_ram_[0x0200] : &ram_[0x0200]);
page(0x08, 0x20,
state.base.read ? &aux_ram_[0x0800] : &ram_[0x0800],
state.base.write ? &aux_ram_[0x0800] : &ram_[0x0800]);
page(0x40, 0xc0,
state.base.read ? &aux_ram_[0x4000] : &ram_[0x4000],
state.base.write ? &aux_ram_[0x4000] : &ram_[0x4000]);
page(0x04, 0x08,
state.region_04_08.read ? &aux_ram_[0x0400] : &ram_[0x0400],
state.region_04_08.write ? &aux_ram_[0x0400] : &ram_[0x0400]);
page(0x20, 0x40,
state.region_20_40.read ? &aux_ram_[0x2000] : &ram_[0x2000],
state.region_20_40.write ? &aux_ram_[0x2000] : &ram_[0x2000]);
}
bool read_auxiliary_memory_ = false;
bool write_auxiliary_memory_ = false;
void set_main_paging() {
page(0x02, 0xc0,
read_auxiliary_memory_ ? &aux_ram_[0x0200] : &ram_[0x0200],
write_auxiliary_memory_ ? &aux_ram_[0x0200] : &ram_[0x0200]);
// MARK: - Keyboard and typing.
if(video_.get_80_store()) {
bool use_aux_ram = video_.get_page2();
page(0x04, 0x08,
use_aux_ram ? &aux_ram_[0x0400] : &ram_[0x0400],
use_aux_ram ? &aux_ram_[0x0400] : &ram_[0x0400]);
struct Keyboard: public Inputs::Keyboard {
Keyboard(Processor *m6502) : m6502_(m6502) {}
if(video_.get_high_resolution()) {
page(0x20, 0x40,
use_aux_ram ? &aux_ram_[0x2000] : &ram_[0x2000],
use_aux_ram ? &aux_ram_[0x2000] : &ram_[0x2000]);
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;
}
}
}
// MARK - typing
std::unique_ptr<Utility::StringSerialiser> string_serialiser_;
// 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;
// MARK - joysticks
class Joystick: public Inputs::ConcreteJoystick {
public:
Joystick() :
ConcreteJoystick({
Input(Input::Horizontal),
Input(Input::Vertical),
// The Apple II offers three buttons between two joysticks;
// this emulator puts three buttons on each joystick and
// combines them.
Input(Input::Fire, 0),
Input(Input::Fire, 1),
Input(Input::Fire, 2),
}) {}
void did_set_input(const Input &input, float value) final {
if(!input.info.control.index && (input.type == Input::Type::Horizontal || input.type == Input::Type::Vertical))
axes[(input.type == Input::Type::Horizontal) ? 0 : 1] = 1.0f - value;
}
void did_set_input(const Input &input, bool value) final {
if(input.type == Input::Type::Fire && input.info.control.index < 3) {
buttons[input.info.control.index] = value;
}
}
bool buttons[3] = {false, false, false};
float axes[2] = {0.5f, 0.5f};
private:
Processor *const m6502_;
};
Keyboard keyboard_;
// On an Apple II, the programmer strobes 0xc070 and that causes each analogue input
// to begin a charge and discharge cycle **if they are not already charging**.
// The greater the analogue input, the faster they will charge and therefore the sooner
// they will discharge.
//
// This emulator models that with analogue_charge_ being essentially the amount of time,
// in charge threshold units, since 0xc070 was last strobed. But if any of the analogue
// inputs were already partially charged then they gain a bias in analogue_biases_.
//
// It's a little indirect, but it means only having to increment the one value in the
// main loop.
float analogue_charge_ = 0.0f;
float analogue_biases_[4] = {0.0f, 0.0f, 0.0f, 0.0f};
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
bool analogue_channel_is_discharged(size_t channel) {
return (1.0f - static_cast<Joystick *>(joysticks_[channel >> 1].get())->axes[channel & 1]) < analogue_charge_ + analogue_biases_[channel];
}
// 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;
// MARK: - Joysticks.
JoystickPair joysticks_;
public:
ConcreteMachine(const Analyser::Static::AppleII::Target &target, const ROMMachine::ROMFetcher &rom_fetcher):
@@ -320,7 +350,10 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
video_bus_handler_(ram_, aux_ram_),
video_(video_bus_handler_),
audio_toggle_(audio_queue_),
speaker_(audio_toggle_) {
speaker_(audio_toggle_),
language_card_(*this),
auxiliary_switches_(*this),
keyboard_(&m6502_) {
// The system's master clock rate.
constexpr float master_clock = 14318180.0;
@@ -342,10 +375,6 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
Memory::Fuzz(ram_, sizeof(ram_));
Memory::Fuzz(aux_ram_, sizeof(aux_ram_));
// Add a couple of joysticks.
joysticks_.emplace_back(new Joystick);
joysticks_.emplace_back(new Joystick);
// Pick the required ROMs.
using Target = Analyser::Static::AppleII::Target;
const std::string machine_name = "AppleII";
@@ -353,21 +382,21 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
size_t rom_size = 12*1024;
switch(target.model) {
default:
rom_descriptions.emplace_back(machine_name, "the basic Apple II character ROM", "apple2-character.rom", 2*1024, 0x64f415c6);
rom_descriptions.push_back(video_.rom_description(Video::VideoBase::CharacterROM::II));
rom_descriptions.emplace_back(machine_name, "the original Apple II ROM", "apple2o.rom", 12*1024, 0xba210588);
break;
case Target::Model::IIplus:
rom_descriptions.emplace_back(machine_name, "the basic Apple II character ROM", "apple2-character.rom", 2*1024, 0x64f415c6);
rom_descriptions.push_back(video_.rom_description(Video::VideoBase::CharacterROM::II));
rom_descriptions.emplace_back(machine_name, "the Apple II+ ROM", "apple2.rom", 12*1024, 0xf66f9c26);
break;
case Target::Model::IIe:
rom_size += 3840;
rom_descriptions.emplace_back(machine_name, "the Apple IIe character ROM", "apple2eu-character.rom", 4*1024, 0x816a86f1);
rom_descriptions.push_back(video_.rom_description(Video::VideoBase::CharacterROM::IIe));
rom_descriptions.emplace_back(machine_name, "the Apple IIe ROM", "apple2eu.rom", 32*1024, 0xe12be18d);
break;
case Target::Model::EnhancedIIe:
rom_size += 3840;
rom_descriptions.emplace_back(machine_name, "the Enhanced Apple IIe character ROM", "apple2e-character.rom", 4*1024, 0x2651014d);
rom_descriptions.push_back(video_.rom_description(Video::VideoBase::CharacterROM::EnhancedIIe));
rom_descriptions.emplace_back(machine_name, "the Enhanced Apple IIe ROM", "apple2e.rom", 32*1024, 0x65989942);
break;
}
@@ -400,9 +429,6 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
// Set the whole card area to initially backed by nothing.
page(0xc0, 0xd0, nullptr, nullptr);
// Set proper values for the language card/ROM area.
set_language_card_paging();
insert_media(target.media);
}
@@ -457,14 +483,8 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
if(write_pages_[address >> 8]) write_pages_[address >> 8][address & 0xff] = *value;
}
if(is_iie() && address >= 0xc300 && address < 0xd000) {
bool internal_c8_rom = internal_c8_rom_;
internal_c8_rom |= ((address >> 8) == 0xc3) && !slot_C3_rom_;
internal_c8_rom &= (address != 0xcfff);
if(internal_c8_rom != internal_c8_rom_) {
internal_c8_rom_ = internal_c8_rom;
set_card_paging();
}
if(is_iie()) {
auxiliary_switches_.access(address, isReadOperation(operation));
}
} else {
// Assume a vapour read unless it turns out otherwise; this is a little
@@ -492,32 +512,32 @@ 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(
static_cast<Joystick *>(joysticks_[0].get())->buttons[0] || static_cast<Joystick *>(joysticks_[1].get())->buttons[2] ||
(is_iie() && open_apple_is_pressed_)
joysticks_.button(0) ||
(is_iie() && keyboard_.open_apple_is_pressed)
)
*value |= 0x80;
break;
case 0xc062: // Switch input 1.
*value &= 0x7f;
if(
static_cast<Joystick *>(joysticks_[0].get())->buttons[1] || static_cast<Joystick *>(joysticks_[1].get())->buttons[1] ||
(is_iie() && closed_apple_is_pressed_)
joysticks_.button(1) ||
(is_iie() && keyboard_.closed_apple_is_pressed)
)
*value |= 0x80;
break;
case 0xc063: // Switch input 2.
*value &= 0x7f;
if(static_cast<Joystick *>(joysticks_[0].get())->buttons[2] || static_cast<Joystick *>(joysticks_[1].get())->buttons[0])
if(joysticks_.button(2))
*value |= 0x80;
break;
@@ -527,20 +547,20 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
case 0xc067: { // Analogue input 3.
const size_t input = address - 0xc064;
*value &= 0x7f;
if(!analogue_channel_is_discharged(input)) {
if(!joysticks_.analogue_channel_is_discharged(input)) {
*value |= 0x80;
}
} break;
// The IIe-only state reads follow...
#define IIeSwitchRead(s) *value = get_keyboard_input(); if(is_iie()) *value = (*value & 0x7f) | (s ? 0x80 : 0x00);
case 0xc011: IIeSwitchRead(language_card_.bank1); break;
case 0xc012: IIeSwitchRead(language_card_.read); break;
case 0xc013: IIeSwitchRead(read_auxiliary_memory_); break;
case 0xc014: IIeSwitchRead(write_auxiliary_memory_); break;
case 0xc015: IIeSwitchRead(internal_CX_rom_); break;
case 0xc016: IIeSwitchRead(alternative_zero_page_); break;
case 0xc017: IIeSwitchRead(slot_C3_rom_); break;
#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;
case 0xc014: IIeSwitchRead(auxiliary_switches_.switches().write_auxiliary_memory); break;
case 0xc015: IIeSwitchRead(auxiliary_switches_.switches().internal_CX_rom); break;
case 0xc016: IIeSwitchRead(auxiliary_switches_.switches().alternative_zero_page); break;
case 0xc017: IIeSwitchRead(auxiliary_switches_.switches().slot_C3_rom); break;
case 0xc018: IIeSwitchRead(video_.get_80_store()); break;
case 0xc019: IIeSwitchRead(video_.get_is_vertical_blank(cycles_since_video_update_)); break;
case 0xc01a: IIeSwitchRead(video_.get_text()); break;
@@ -558,6 +578,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
} else {
// Write-only switches. All IIe as currently implemented.
if(is_iie()) {
auxiliary_switches_.access(address, false);
switch(address) {
default: break;
@@ -565,40 +586,6 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
case 0xc001:
update_video();
video_.set_80_store(!!(address&1));
set_main_paging();
break;
case 0xc002:
case 0xc003:
read_auxiliary_memory_ = !!(address&1);
set_main_paging();
break;
case 0xc004:
case 0xc005:
write_auxiliary_memory_ = !!(address&1);
set_main_paging();
break;
case 0xc006:
case 0xc007:
internal_CX_rom_ = !!(address&1);
set_card_paging();
break;
case 0xc008:
case 0xc009:
// The alternative zero page setting affects both bank 0 and any RAM
// that's paged as though it were on a language card.
alternative_zero_page_ = !!(address&1);
set_zero_page_paging();
set_language_card_paging();
break;
case 0xc00a:
case 0xc00b:
slot_C3_rom_ = !!(address&1);
set_card_paging();
break;
case 0xc00c:
@@ -617,17 +604,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
}
break;
case 0xc070: { // Permit analogue inputs that are currently discharged to begin a charge cycle.
// Ensure those that were still charging retain that state.
for(size_t c = 0; c < 4; ++c) {
if(analogue_channel_is_discharged(c)) {
analogue_biases_[c] = 0.0f;
} else {
analogue_biases_[c] += analogue_charge_;
}
}
analogue_charge_ = 0.0f;
} break;
case 0xc070: joysticks_.access_c070(); break;
/* Switches triggered by reading or writing. */
case 0xc050:
@@ -640,14 +617,14 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
case 0xc054:
case 0xc055:
update_video();
video_.set_page2(!!(address&1));
set_main_paging();
video_.set_page2(address&1);
auxiliary_switches_.access(address, isReadOperation(operation));
break;
case 0xc056:
case 0xc057:
update_video();
video_.set_high_resolution(!!(address&1));
set_main_paging();
video_.set_high_resolution(address&1);
auxiliary_switches_.access(address, isReadOperation(operation));
break;
case 0xc05e:
@@ -659,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;
@@ -681,28 +658,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
case 0xc081: case 0xc085: case 0xc089: case 0xc08d:
case 0xc082: case 0xc086: case 0xc08a: case 0xc08e:
case 0xc083: case 0xc087: case 0xc08b: case 0xc08f:
// Quotes below taken from Understanding the Apple II, p. 5-28 and 5-29.
// "A3 controls the 4K bank selection"
language_card_.bank1 = (address&8);
// "Access to $C080, $C083, $C084, $0087, $C088, $C08B, $C08C, or $C08F sets the READ ENABLE flip-flop"
// (other accesses reset it)
language_card_.read = !(((address&2) >> 1) ^ (address&1));
// "The WRITE ENABLE' flip-flop is reset by an odd read access to the $C08X range when the PRE-WRITE flip-flop is set."
if(language_card_.pre_write && isReadOperation(operation) && (address&1)) language_card_.write = false;
// "[The WRITE ENABLE' flip-flop] is set by an even access in the $C08X range."
if(!(address&1)) language_card_.write = true;
// ("Any other type of access causes the WRITE ENABLE' flip-flop to hold its current state.")
// "The PRE-WRITE flip-flop is set by an odd read access in the $C08X range. It is reset by an even access or a write access."
language_card_.pre_write = isReadOperation(operation) ? (address&1) : false;
// Apply whatever the net effect of all that is to the memory map.
set_language_card_paging();
language_card_.access(address, isReadOperation(operation));
break;
}
@@ -788,7 +744,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
}
// Update analogue charge level.
analogue_charge_ = std::min(analogue_charge_ + 1.0f / 2820.0f, 1.1f);
joysticks_.update_charge();
return Cycles(1);
}
@@ -804,57 +760,16 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
m6502_.run_for(cycles);
}
void reset_all_keys(Inputs::Keyboard *) 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 {
switch(key) {
default: break;
case Key::F12:
m6502_.set_reset_line(is_pressed);
return true;
case Key::LeftOption:
open_apple_is_pressed_ = is_pressed;
return true;
case Key::RightOption:
closed_apple_is_pressed_ = is_pressed;
return true;
}
// If no ASCII value is supplied, look for a few special cases.
if(!value) {
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;
default: return false;
}
}
// Prior to the IIe, the keyboard could produce uppercase only.
if(!is_iie()) value = char(toupper(value));
if(is_pressed) {
keyboard_input_ = uint8_t(value | 0x80);
key_is_down_ = true;
} else {
if((keyboard_input_ & 0x7f) == value) {
key_is_down_ = false;
}
}
bool prefers_logical_input() final {
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 {
@@ -892,7 +807,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
// MARK: JoystickMachine
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final {
return joysticks_;
return joysticks_.get_joysticks();
}
};

View File

@@ -0,0 +1,275 @@
//
// AuxiliaryMemorySwitches.hpp
// Clock Signal
//
// Created by Thomas Harte on 22/10/2020.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#ifndef AuxiliaryMemorySwitches_h
#define AuxiliaryMemorySwitches_h
namespace Apple {
namespace II {
/*!
Models the auxiliary memory soft switches, added as of the Apple IIe, which allow access to the auxiliary 64kb of RAM and to
the additional almost-4kb of ROM.
Relevant memory accesses should be fed to this class; it'll call:
* machine.set_main_paging() if anything in the 'main' state changes, i.e. the lower 48kb excluding the zero and stack pages;
* machine.set_card_state() if anything changes with where ROM should appear rather than cards in the $Cxxx range; and
* machine.set_zero_page_paging() if the selection of the lowest two pages of RAM changes.
Implementation observation: as implemented on the IIe, the zero page setting also affects what happens in the language card area.
*/
template <typename Machine> class AuxiliaryMemorySwitches {
public:
static constexpr bool Auxiliary = true;
static constexpr bool Main = false;
static constexpr bool ROM = true;
static constexpr bool Card = false;
/// Describes banking state between $0200 and $BFFF.
struct MainState {
struct Region {
/// @c true indicates auxiliary memory should be read from; @c false indicates main.
bool read = false;
/// @c true indicates auxiliary memory should be written to; @c false indicates main.
bool write = false;
};
/// Describes banking state in the ranges $0200$03FF, $0800$1FFF and $4000$BFFF.
Region base;
/// Describes banking state in the range $0400$07FF.
Region region_04_08;
/// Describes banking state in the range $2000$3FFF.
Region region_20_40;
bool operator != (const MainState &rhs) const {
return
base.read != rhs.base.read || base.write != rhs.base.write ||
region_04_08.read != rhs.region_04_08.read || region_04_08.write != rhs.region_04_08.write ||
region_20_40.read != rhs.region_20_40.read || region_20_40.write != rhs.region_20_40.write;
}
};
/// Describes banking state between $C100 and $Cfff.
struct CardState {
/// @c true indicates that the built-in ROM should appear from $C100 to $C2FF @c false indicates that cards should service those accesses.
bool region_C1_C3 = false;
/// @c true indicates that the built-in ROM should appear from $C300 to $C3FF; @c false indicates that cards should service those accesses.
bool region_C3 = false;
/// @c true indicates that the built-in ROM should appear from $C400 to $C7FF; @c false indicates that cards should service those accesses.
bool region_C4_C8 = false;
/// @c true indicates that the built-in ROM should appear from $C800 to $CFFF; @c false indicates that cards should service those accesses.
bool region_C8_D0 = false;
bool operator != (const CardState &rhs) const {
return
region_C1_C3 != rhs.region_C1_C3 ||
region_C3 != rhs.region_C3 ||
region_C4_C8 != rhs.region_C4_C8 ||
region_C8_D0 != rhs.region_C8_D0;
}
};
/// Descibes banking state between $0000 and $01ff; @c true indicates that auxiliary memory should be used; @c false indicates main memory.
using ZeroState = bool;
/// Returns raw switch state for all switches that affect banking, even if they're logically video switches.
struct SwitchState {
bool read_auxiliary_memory = false;
bool write_auxiliary_memory = false;
bool internal_CX_rom = false;
bool slot_C3_rom = false;
bool internal_C8_rom = false;
bool store_80 = false;
bool alternative_zero_page = false;
bool video_page_2 = false;
bool high_resolution = false;
};
AuxiliaryMemorySwitches(Machine &machine) : machine_(machine) {}
/// Used by an owner to forward, at least, any access in the range $C000 to $C00B,
/// in $C054 to $C058, or in the range $C300 to $CFFF. Safe to call for any [16-bit] address.
void access(uint16_t address, bool is_read) {
if(address >= 0xc300 && address < 0xd000) {
switches_.internal_C8_rom |= ((address >> 8) == 0xc3) && !switches_.slot_C3_rom;
switches_.internal_C8_rom &= (address != 0xcfff);
set_card_paging();
return;
}
if(address < 0xc000 || address >= 0xc058) return;
switch(address) {
default: break;
case 0xc000: case 0xc001:
if(!is_read) {
switches_.store_80 = address & 1;
set_main_paging();
}
break;
case 0xc002: case 0xc003:
if(!is_read) {
switches_.read_auxiliary_memory = address & 1;
set_main_paging();
}
break;
case 0xc004: case 0xc005:
if(!is_read) {
switches_.write_auxiliary_memory = address & 1;
set_main_paging();
}
break;
case 0xc006: case 0xc007:
if(!is_read) {
switches_.internal_CX_rom = address & 1;
set_card_paging();
}
break;
case 0xc008: case 0xc009: {
const bool alternative_zero_page = address & 1;
if(!is_read && switches_.alternative_zero_page != alternative_zero_page) {
switches_.alternative_zero_page = alternative_zero_page;
set_zero_page_paging();
}
} break;
case 0xc00a: case 0xc00b:
if(!is_read) {
switches_.slot_C3_rom = address & 1;
set_card_paging();
}
break;
case 0xc054: case 0xc055:
switches_.video_page_2 = address & 1;
set_main_paging();
break;
case 0xc056: case 0xc057:
switches_.high_resolution = address & 1;
set_main_paging();
break;
}
}
/// Provides part of the IIgs interface.
void set_state(uint8_t value) {
// b7: 1 => use auxiliary memory for zero page; 0 => use main. [I think the Hardware Reference gets this the wrong way around]
// b6: 1 => text page 2 is selected; 0 => text page 1.
// b5: 1 => auxiliary RAM bank is selected for reads; 0 => main.
// b4: 1 => auxiliary RAM bank is selected for writes; 0 => main.
// b0: 1 => the internal ROM is selected for C800+; 0 => card ROM.
switches_.alternative_zero_page = value & 0x80;
switches_.video_page_2 = value & 0x40;
switches_.read_auxiliary_memory = value & 0x20;
switches_.write_auxiliary_memory = value & 0x10;
switches_.internal_CX_rom = value & 0x01;
set_main_paging();
set_zero_page_paging();
set_card_paging();
}
uint8_t get_state() const {
return
(switches_.alternative_zero_page ? 0x80 : 0x00) |
(switches_.video_page_2 ? 0x40 : 0x00) |
(switches_.read_auxiliary_memory ? 0x20 : 0x00) |
(switches_.write_auxiliary_memory ? 0x10 : 0x00) |
(switches_.internal_CX_rom ? 0x01 : 0x00);
}
const MainState &main_state() const {
return main_state_;
}
const CardState &card_state() const {
return card_state_;
}
/// @returns @c true if the alternative zero page should be used; @c false otherwise.
ZeroState zero_state() const {
return switches_.alternative_zero_page;
}
const SwitchState switches() const {
return switches_;
}
private:
Machine &machine_;
SwitchState switches_;
MainState main_state_;
void set_main_paging() {
const auto previous_state = main_state_;
// The two appropriately named switches provide the base case.
main_state_.base.read = switches_.read_auxiliary_memory;
main_state_.base.write = switches_.write_auxiliary_memory;
if(switches_.store_80) {
// If store 80 is set, use the page 2 flag for the lower carve out;
// if both store 80 and high resolution are set, use the page 2 flag for both carve outs.
main_state_.region_04_08.read = main_state_.region_04_08.write = switches_.video_page_2;
if(switches_.high_resolution) {
main_state_.region_20_40.read = main_state_.region_20_40.write = switches_.video_page_2;
} else {
main_state_.region_20_40 = main_state_.base;
}
} else {
main_state_.region_04_08 = main_state_.region_20_40 = main_state_.base;
}
if(previous_state != main_state_) {
machine_.set_main_paging();
}
}
CardState card_state_;
void set_card_paging() {
const auto previous_state = card_state_;
// By default apply the CX switch through to $C7FF.
card_state_.region_C1_C3 = card_state_.region_C4_C8 = switches_.internal_CX_rom;
// Allow the C3 region to be switched to internal ROM in isolation even if the rest of the
// first half of the CX region is diabled, if its specific switch is also disabled.
if(!switches_.internal_CX_rom && !switches_.slot_C3_rom) {
card_state_.region_C3 = true;
} else {
card_state_.region_C3 = card_state_.region_C1_C3;
}
// Apply the CX switch to $C800+, but also allow the C8 switch to select that region in isolation.
card_state_.region_C8_D0 = switches_.internal_CX_rom || switches_.internal_C8_rom;
if(previous_state != card_state_) {
machine_.set_card_paging();
}
}
void set_zero_page_paging() {
// Believe it or not, the zero page is just set or cleared by a single flag.
// As though life were rational.
machine_.set_zero_page_paging();
}
};
}
}
#endif /* AuxiliaryMemorySwitches_h */

View File

@@ -0,0 +1,9 @@
//
// Joystick.cpp
// Clock Signal
//
// Created by Thomas Harte on 16/02/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "Joystick.hpp"

View File

@@ -0,0 +1,112 @@
//
// Joystick.hpp
// Clock Signal
//
// Created by Thomas Harte on 16/02/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef AppleII_Joystick_hpp
#define AppleII_Joystick_hpp
#include "../../../Inputs/Joystick.hpp"
#include <memory>
#include <vector>
namespace Apple {
namespace II {
class JoystickPair {
public:
JoystickPair() {
// Add a couple of joysticks.
joysticks_.emplace_back(new Joystick);
joysticks_.emplace_back(new Joystick);
}
class Joystick: public Inputs::ConcreteJoystick {
public:
Joystick() :
ConcreteJoystick({
Input(Input::Horizontal),
Input(Input::Vertical),
// The Apple II offers three buttons between two joysticks;
// this emulator puts three buttons on each joystick and
// combines them.
Input(Input::Fire, 0),
Input(Input::Fire, 1),
Input(Input::Fire, 2),
}) {}
void did_set_input(const Input &input, float value) final {
if(!input.info.control.index && (input.type == Input::Type::Horizontal || input.type == Input::Type::Vertical))
axes[(input.type == Input::Type::Horizontal) ? 0 : 1] = 1.0f - value;
}
void did_set_input(const Input &input, bool value) final {
if(input.type == Input::Type::Fire && input.info.control.index < 3) {
buttons[input.info.control.index] = value;
}
}
bool buttons[3] = {false, false, false};
float axes[2] = {0.5f, 0.5f};
};
inline bool button(size_t index) {
return joystick(0)->buttons[index] || joystick(1)->buttons[2-index];
}
inline bool analogue_channel_is_discharged(size_t channel) {
return (1.0f - static_cast<Joystick *>(joysticks_[channel >> 1].get())->axes[channel & 1]) < analogue_charge_ + analogue_biases_[channel];
}
inline void update_charge(float one_mhz_cycles = 1.0f) {
analogue_charge_ = std::min(analogue_charge_ + one_mhz_cycles * (1.0f / 2820.0f), 1.1f);
}
inline void access_c070() {
// Permit analogue inputs that are currently discharged to begin a charge cycle.
// Ensure those that were still charging retain that state.
for(size_t c = 0; c < 4; ++c) {
if(analogue_channel_is_discharged(c)) {
analogue_biases_[c] = 0.0f;
} else {
analogue_biases_[c] += analogue_charge_;
}
}
analogue_charge_ = 0.0f;
}
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() {
return joysticks_;
}
private:
// On an Apple II, the programmer strobes 0xc070 and that causes each analogue input
// to begin a charge and discharge cycle **if they are not already charging**.
// The greater the analogue input, the faster they will charge and therefore the sooner
// they will discharge.
//
// This emulator models that with analogue_charge_ being essentially the amount of time,
// in charge threshold units, since 0xc070 was last strobed. But if any of the analogue
// inputs were already partially charged then they gain a bias in analogue_biases_.
//
// It's a little indirect, but it means only having to increment the one value in the
// main loop.
float analogue_charge_ = 0.0f;
float analogue_biases_[4] = {0.0f, 0.0f, 0.0f, 0.0f};
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
inline Joystick *joystick(size_t index) {
return static_cast<Joystick *>(joysticks_[index].get());
}
};
}
}
#endif /* AppleII_Joystick_hpp */

View File

@@ -0,0 +1,116 @@
//
// LanguageCardSwitches.hpp
// Clock Signal
//
// Created by Thomas Harte on 22/10/2020.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#ifndef LanguageCardSwitches_h
#define LanguageCardSwitches_h
namespace Apple {
namespace II {
/*!
Models the language card soft switches, present on any Apple II with a language card and provided built-in from the IIe onwards.
Relevant memory accesses should be fed to this class; it'll call:
* machine.set_language_card_paging() if the proper mapped state changes.
*/
template <typename Machine> class LanguageCardSwitches {
public:
struct State {
/// When RAM is visible in the range $D000$FFFF:
/// @c true indicates that bank 2 should be used between $D000 and $DFFF;
/// @c false indicates bank 1.
bool bank2 = true;
/// @c true indicates that RAM should be readable in the range $D000$FFFF;
/// @c false indicates ROM should be readable.
bool read = false;
/// @c true indicates that ROM is selected for 'writing' in the range $D000$FFFF (i.e. writes are a no-op);
/// @c false indicates that RAM is selected for writing.
bool write = false;
bool operator != (const State &rhs) const {
return
bank2 != rhs.bank2 ||
read != rhs.read ||
write != rhs.write;
}
};
LanguageCardSwitches(Machine &machine) : machine_(machine) {}
/// Used by an owner to forward any access to $c08x.
void access(uint16_t address, bool is_read) {
const auto previous_state = state_;
// Quotes below taken from Understanding the Apple II, p. 5-28 and 5-29.
// "A3 controls the 4K bank selection"; 0 = bank 2, 1 = bank 1.
state_.bank2 = !(address & 8);
// "Access to $C080, $C083, $C084, $0087, $C088, $C08B, $C08C, or $C08F sets the READ ENABLE flip-flop"
// (other accesses reset it)
state_.read = !(((address&2) >> 1) ^ (address&1));
// "The WRITE ENABLE' flip-flop is reset by an odd read access to the $C08X range when the PRE-WRITE flip-flop is set."
if(pre_write_ && is_read && (address&1)) state_.write = false;
// "[The WRITE ENABLE' flip-flop] is set by an even access in the $C08X range."
if(!(address&1)) state_.write = true;
// ("Any other type of access causes the WRITE ENABLE' flip-flop to hold its current state.")
// "The PRE-WRITE flip-flop is set by an odd read access in the $C08X range. It is reset by an even access or a write access."
pre_write_ = is_read ? (address&1) : false;
// Apply whatever the net effect of all that is to the memory map.
if(previous_state != state_) {
machine_.set_language_card_paging();
}
}
/// Provides read-only access to the current language card switch state.
const State &state() const {
return state_;
}
/// Provides relevant parts of the IIgs interface.
void set_state(uint8_t value) {
const auto previous_state = state_;
// Bit 3: 1 => enable ROM, 0 => enable RAM.
state_.read = !(value & 0x08);
// Bit 2: 1 => select bank 2, 0 => select bank 1. [per errata to the Hardware Reference
// correcting the original, which lists them the other way around]
state_.bank2 = value & 0x04;
if(previous_state != state_) {
machine_.set_language_card_paging();
}
}
uint8_t get_state() const {
return
(state_.read ? 0x00 : 0x08) |
(state_.bank2 ? 0x04 : 0x00);
}
private:
Machine &machine_;
State state_;
// This is an additional flip flop contained on the language card, but
// it is one step removed from current banking state, so I've excluded it
// from the State struct.
bool pre_write_ = false;
};
}
}
#endif /* LanguageCard_h */

View File

@@ -11,9 +11,9 @@
using namespace Apple::II::Video;
VideoBase::VideoBase(bool is_iie, std::function<void(Cycles)> &&target) :
VideoSwitches<Cycles>(is_iie, Cycles(2), std::move(target)),
crt_(910, 1, Outputs::Display::Type::NTSC60, Outputs::Display::InputDataType::Luminance1),
is_iie_(is_iie),
deferrer_(std::move(target)) {
is_iie_(is_iie) {
// Show only the centre 75% of the TV frame.
crt_.set_display_type(Outputs::Display::DisplayType::CompositeColour);
@@ -24,23 +24,6 @@ VideoBase::VideoBase(bool is_iie, std::function<void(Cycles)> &&target) :
// use default_colour_bursts elsewhere, though it otherwise should be. If/when
// it is, start doing so and return to setting the immediate phase up here.
// crt_.set_immediate_default_phase(0.5f);
character_zones[0].xor_mask = 0;
character_zones[0].address_mask = 0x3f;
character_zones[1].xor_mask = 0;
character_zones[1].address_mask = 0x3f;
character_zones[2].xor_mask = 0;
character_zones[2].address_mask = 0x3f;
character_zones[3].xor_mask = 0;
character_zones[3].address_mask = 0x3f;
if(is_iie) {
character_zones[0].xor_mask =
character_zones[2].xor_mask =
character_zones[3].xor_mask = 0xff;
character_zones[2].address_mask =
character_zones[3].address_mask = 0xff;
}
}
void VideoBase::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
@@ -59,121 +42,10 @@ Outputs::Display::DisplayType VideoBase::get_display_type() const {
return crt_.get_display_type();
}
/*
Rote setters and getters.
*/
void VideoBase::set_alternative_character_set(bool alternative_character_set) {
set_alternative_character_set_ = alternative_character_set;
deferrer_.defer(Cycles(2), [this, alternative_character_set] {
alternative_character_set_ = alternative_character_set;
if(alternative_character_set) {
character_zones[1].address_mask = 0xff;
character_zones[1].xor_mask = 0;
} else {
character_zones[1].address_mask = 0x3f;
character_zones[1].xor_mask = flash_mask();
}
});
}
bool VideoBase::get_alternative_character_set() {
return set_alternative_character_set_;
}
void VideoBase::set_80_columns(bool columns_80) {
set_columns_80_ = columns_80;
deferrer_.defer(Cycles(2), [this, columns_80] {
columns_80_ = columns_80;
});
}
bool VideoBase::get_80_columns() {
return set_columns_80_;
}
void VideoBase::set_80_store(bool store_80) {
set_store_80_ = store_80_ = store_80;
}
bool VideoBase::get_80_store() {
return set_store_80_;
}
void VideoBase::set_page2(bool page2) {
set_page2_ = page2_ = page2;
}
bool VideoBase::get_page2() {
return set_page2_;
}
void VideoBase::set_text(bool text) {
set_text_ = text;
deferrer_.defer(Cycles(2), [this, text] {
text_ = text;
});
}
bool VideoBase::get_text() {
return set_text_;
}
void VideoBase::set_mixed(bool mixed) {
set_mixed_ = mixed;
deferrer_.defer(Cycles(2), [this, mixed] {
mixed_ = mixed;
});
}
bool VideoBase::get_mixed() {
return set_mixed_;
}
void VideoBase::set_high_resolution(bool high_resolution) {
set_high_resolution_ = high_resolution;
deferrer_.defer(Cycles(2), [this, high_resolution] {
high_resolution_ = high_resolution;
});
}
bool VideoBase::get_high_resolution() {
return set_high_resolution_;
}
void VideoBase::set_annunciator_3(bool annunciator_3) {
set_annunciator_3_ = annunciator_3;
deferrer_.defer(Cycles(2), [this, annunciator_3] {
annunciator_3_ = annunciator_3;
high_resolution_mask_ = annunciator_3_ ? 0x7f : 0xff;
});
}
bool VideoBase::get_annunciator_3() {
return set_annunciator_3_;
}
void VideoBase::set_character_rom(const std::vector<uint8_t> &character_rom) {
character_rom_ = character_rom;
// Flip all character contents based on the second line of the $ graphic.
if(character_rom_[0x121] == 0x3c || character_rom_[0x122] == 0x3c) {
for(auto &graphic : character_rom_) {
graphic =
((graphic & 0x01) ? 0x40 : 0x00) |
((graphic & 0x02) ? 0x20 : 0x00) |
((graphic & 0x04) ? 0x10 : 0x00) |
((graphic & 0x08) ? 0x08 : 0x00) |
((graphic & 0x10) ? 0x04 : 0x00) |
((graphic & 0x20) ? 0x02 : 0x00) |
((graphic & 0x40) ? 0x01 : 0x00);
}
}
}
void VideoBase::output_text(uint8_t *target, const uint8_t *const source, size_t length, size_t pixel_row) const {
for(size_t c = 0; c < length; ++c) {
const int character = source[c] & character_zones[source[c] >> 6].address_mask;
const uint8_t xor_mask = character_zones[source[c] >> 6].xor_mask;
const int character = source[c] & character_zones_[source[c] >> 6].address_mask;
const uint8_t xor_mask = character_zones_[source[c] >> 6].xor_mask;
const std::size_t character_address = size_t(character << 3) + pixel_row;
const uint8_t character_pattern = character_rom_[character_address] ^ xor_mask;
@@ -194,19 +66,19 @@ void VideoBase::output_double_text(uint8_t *target, const uint8_t *const source,
for(size_t c = 0; c < length; ++c) {
const std::size_t character_addresses[2] = {
size_t(
(auxiliary_source[c] & character_zones[auxiliary_source[c] >> 6].address_mask) << 3
(auxiliary_source[c] & character_zones_[auxiliary_source[c] >> 6].address_mask) << 3
) + pixel_row,
size_t(
(source[c] & character_zones[source[c] >> 6].address_mask) << 3
(source[c] & character_zones_[source[c] >> 6].address_mask) << 3
) + pixel_row
};
const uint8_t character_patterns[2] = {
uint8_t(
character_rom_[character_addresses[0]] ^ character_zones[auxiliary_source[c] >> 6].xor_mask
character_rom_[character_addresses[0]] ^ character_zones_[auxiliary_source[c] >> 6].xor_mask
),
uint8_t(
character_rom_[character_addresses[1]] ^ character_zones[source[c] >> 6].xor_mask
character_rom_[character_addresses[1]] ^ character_zones_[source[c] >> 6].xor_mask
)
};
@@ -270,26 +142,26 @@ void VideoBase::output_double_low_resolution(uint8_t *target, const uint8_t *con
const int row_shift = row&4;
for(size_t c = 0; c < length; ++c) {
if((column + int(c))&1) {
target[0] = target[4] = (auxiliary_source[c] >> row_shift) & 2;
target[1] = target[5] = (auxiliary_source[c] >> row_shift) & 4;
target[2] = target[6] = (auxiliary_source[c] >> row_shift) & 8;
target[3] = (auxiliary_source[c] >> row_shift) & 1;
target[0] = target[4] = (auxiliary_source[c] >> row_shift) & 4;
target[1] = target[5] = (auxiliary_source[c] >> row_shift) & 8;
target[2] = target[6] = (auxiliary_source[c] >> row_shift) & 1;
target[3] = (auxiliary_source[c] >> row_shift) & 2;
target[8] = target[12] = (source[c] >> row_shift) & 4;
target[9] = target[13] = (source[c] >> row_shift) & 8;
target[10] = (source[c] >> row_shift) & 1;
target[7] = target[11] = (source[c] >> row_shift) & 2;
target[8] = target[12] = (source[c] >> row_shift) & 8;
target[9] = target[13] = (source[c] >> row_shift) & 1;
target[10] = (source[c] >> row_shift) & 2;
target[7] = target[11] = (source[c] >> row_shift) & 4;
graphics_carry_ = (source[c] >> row_shift) & 8;
} else {
target[0] = target[4] = (auxiliary_source[c] >> row_shift) & 8;
target[1] = target[5] = (auxiliary_source[c] >> row_shift) & 1;
target[2] = target[6] = (auxiliary_source[c] >> row_shift) & 2;
target[3] = (auxiliary_source[c] >> row_shift) & 4;
target[0] = target[4] = (auxiliary_source[c] >> row_shift) & 1;
target[1] = target[5] = (auxiliary_source[c] >> row_shift) & 2;
target[2] = target[6] = (auxiliary_source[c] >> row_shift) & 4;
target[3] = (auxiliary_source[c] >> row_shift) & 8;
target[8] = target[12] = (source[c] >> row_shift) & 1;
target[9] = target[13] = (source[c] >> row_shift) & 2;
target[10] = (source[c] >> row_shift) & 4;
target[7] = target[11] = (source[c] >> row_shift) & 8;
target[8] = target[12] = (source[c] >> row_shift) & 2;
target[9] = target[13] = (source[c] >> row_shift) & 4;
target[10] = (source[c] >> row_shift) & 8;
target[7] = target[11] = (source[c] >> row_shift) & 1;
graphics_carry_ = (source[c] >> row_shift) & 2;
}
target += 14;

View File

@@ -6,13 +6,15 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#ifndef Video_hpp
#define Video_hpp
#ifndef Apple_II_Video_hpp
#define Apple_II_Video_hpp
#include "../../../Outputs/CRT/CRT.hpp"
#include "../../../ClockReceiver/ClockReceiver.hpp"
#include "../../../ClockReceiver/DeferredQueue.hpp"
#include "VideoSwitches.hpp"
#include <array>
#include <vector>
@@ -33,7 +35,7 @@ class BusHandler {
}
};
class VideoBase {
class VideoBase: public VideoSwitches<Cycles> {
public:
VideoBase(bool is_iie, std::function<void(Cycles)> &&target);
@@ -49,112 +51,6 @@ class VideoBase {
/// Gets the type of output.
Outputs::Display::DisplayType get_display_type() const;
/*
Descriptions for the setters below are taken verbatim from
the Apple IIe Technical Reference. Addresses are the conventional
locations within the Apple II memory map. Only those which affect
video output are implemented here.
Those registers which don't exist on a II/II+ are marked.
*/
/*!
Setter for ALTCHAR ($C00E/$C00F; triggers on write only):
* Off: display text using primary character set.
* On: display text using alternate character set.
Doesn't exist on a II/II+.
*/
void set_alternative_character_set(bool);
bool get_alternative_character_set();
/*!
Setter for 80COL ($C00C/$C00D; triggers on write only).
* Off: display 40 columns.
* On: display 80 columns.
Doesn't exist on a II/II+.
*/
void set_80_columns(bool);
bool get_80_columns();
/*!
Setter for 80STORE ($C000/$C001; triggers on write only).
* Off: cause PAGE2 to select auxiliary RAM.
* On: cause PAGE2 to switch main RAM areas.
Doesn't exist on a II/II+.
*/
void set_80_store(bool);
bool get_80_store();
/*!
Setter for PAGE2 ($C054/$C055; triggers on read or write).
* Off: select Page 1.
* On: select Page 2 or, if 80STORE on, Page 1 in auxiliary memory.
80STORE doesn't exist on a II/II+; therefore this always selects
either Page 1 or Page 2 on those machines.
*/
void set_page2(bool);
bool get_page2();
/*!
Setter for TEXT ($C050/$C051; triggers on read or write).
* Off: display graphics or, if MIXED on, mixed.
* On: display text.
*/
void set_text(bool);
bool get_text();
/*!
Setter for MIXED ($C052/$C053; triggers on read or write).
* Off: display only text or only graphics.
* On: if TEXT off, display text and graphics.
*/
void set_mixed(bool);
bool get_mixed();
/*!
Setter for HIRES ($C056/$C057; triggers on read or write).
* Off: if TEXT off, display low-resolution graphics.
* On: if TEXT off, display high-resolution or, if DHIRES on, double high-resolution graphics.
DHIRES doesn't exist on a II/II+; therefore this always selects
either high- or low-resolution graphics on those machines.
Despite Apple's documentation, the IIe also supports double low-resolution
graphics, which are the 80-column analogue to ordinary low-resolution 40-column
low-resolution graphics.
*/
void set_high_resolution(bool);
bool get_high_resolution();
/*!
Setter for annunciator 3.
* On: turn on annunciator 3.
* Off: turn off annunciator 3.
This exists on both the II/II+ and the IIe, but has no effect on
video on the older machines. It's intended to be used on the IIe
to confirm double-high resolution mode but has side effects in
selecting mixed mode output and discarding high-resolution
delay bits.
*/
void set_annunciator_3(bool);
bool get_annunciator_3();
// Setup for text mode.
void set_character_rom(const std::vector<uint8_t> &);
protected:
Outputs::CRT::CRT crt_;
@@ -162,45 +58,13 @@ class VideoBase {
uint8_t *pixel_pointer_ = nullptr;
// State affecting logical state.
int row_ = 0, column_ = 0, flash_ = 0;
uint8_t flash_mask() {
return uint8_t((flash_ / flash_length) * 0xff);
}
// Enumerates all Apple II and IIe display modes.
enum class GraphicsMode {
Text = 0,
DoubleText,
HighRes,
DoubleHighRes,
LowRes,
DoubleLowRes,
FatLowRes
};
bool is_text_mode(GraphicsMode m) { return m <= GraphicsMode::DoubleText; }
bool is_double_mode(GraphicsMode m) { return !!(int(m)&1); }
// Various soft-switch values.
bool alternative_character_set_ = false, set_alternative_character_set_ = false;
bool columns_80_ = false, set_columns_80_ = false;
bool store_80_ = false, set_store_80_ = false;
bool page2_ = false, set_page2_ = false;
bool text_ = true, set_text_ = true;
bool mixed_ = false, set_mixed_ = false;
bool high_resolution_ = false, set_high_resolution_ = false;
bool annunciator_3_ = false, set_annunciator_3_ = false;
int row_ = 0, column_ = 0;
// Graphics carry is the final level output in a fetch window;
// it carries on into the next if it's high resolution with
// the delay bit set.
mutable uint8_t graphics_carry_ = 0;
bool was_double_ = false;
uint8_t high_resolution_mask_ = 0xff;
// This holds a copy of the character ROM. The regular character
// set is assumed to be in the first 64*8 bytes; the alternative
// is in the 128*8 bytes after that.
std::vector<uint8_t> character_rom_;
// Memory is fetched ahead of time into this array;
// this permits the correct delay between fetching
@@ -208,16 +72,7 @@ class VideoBase {
std::array<uint8_t, 40> base_stream_;
std::array<uint8_t, 40> auxiliary_stream_;
bool is_iie_ = false;
static constexpr int flash_length = 8406;
// Describes the current text mode mapping from in-memory character index
// to output character.
struct CharacterMapping {
uint8_t address_mask;
uint8_t xor_mask;
};
CharacterMapping character_zones[4];
const bool is_iie_ = false;
/*!
Outputs 40-column text to @c target, using @c length bytes from @c source.
@@ -256,9 +111,6 @@ class VideoBase {
clock rather than the 14M.
*/
void output_fat_low_resolution(uint8_t *target, const uint8_t *source, size_t length, int column, int row) const;
// Maintain a DeferredQueue for delayed mode switches.
DeferredQueuePerformer<Cycles> deferrer_;
};
template <class BusHandler, bool is_iie> class Video: public VideoBase {
@@ -268,13 +120,6 @@ template <class BusHandler, bool is_iie> class Video: public VideoBase {
VideoBase(is_iie, [this] (Cycles cycles) { advance(cycles); }),
bus_handler_(bus_handler) {}
/*!
Runs video for @c cycles.
*/
void run_for(Cycles cycles) {
deferrer_.run_for(cycles);
}
/*!
Obtains the last value the video read prior to time now+offset.
*/
@@ -329,7 +174,10 @@ template <class BusHandler, bool is_iie> class Video: public VideoBase {
// Apply carry into the row counter and test it for location.
int mapped_row = row_ + (mapped_column / 65);
return (mapped_row % 262) >= 192;
// Per http://www.1000bit.it/support/manuali/apple/technotes/iigs/tn.iigs.040.html
// "on the IIe, the screen is blanked when the bit is low".
return (mapped_row % 262) < 192;
}
private:
@@ -345,7 +193,7 @@ template <class BusHandler, bool is_iie> class Video: public VideoBase {
A frame is oriented around 65 cycles across, 262 lines down.
*/
constexpr int first_sync_line = 220; // A complete guess. Information needed.
constexpr int first_sync_line = 220; // A complete guess. Information needed.
constexpr int first_sync_column = 49; // Also a guess.
constexpr int sync_length = 4; // One of the two likely candidates.
@@ -422,7 +270,7 @@ template <class BusHandler, bool is_iie> class Video: public VideoBase {
const int pixel_end = std::min(40, ending_column);
const int pixel_row = row_ & 7;
const bool is_double = Video::is_double_mode(line_mode);
const bool is_double = is_double_mode(line_mode);
if(!is_double && was_double_ && pixel_pointer_) {
pixel_pointer_[pixel_start*14 + 0] =
pixel_pointer_[pixel_start*14 + 1] =
@@ -541,7 +389,17 @@ template <class BusHandler, bool is_iie> class Video: public VideoBase {
const int colour_burst_start = std::max(first_sync_column + sync_length + 1, column_);
const int colour_burst_end = std::min(first_sync_column + sync_length + 4, ending_column);
if(colour_burst_end > colour_burst_start) {
crt_.output_colour_burst((colour_burst_end - colour_burst_start) * 14, 0);
// UGLY HACK AHOY!
// The OpenGL scan target introduces a phase error of 1/8th of a wave. The Metal one does not.
// Supply the real phase value if this is an Apple build.
// TODO: eliminate UGLY HACK.
#ifdef __APPLE__
constexpr int phase = 224;
#else
constexpr int phase = 0;
#endif
crt_.output_colour_burst((colour_burst_end - colour_burst_start) * 14, phase);
}
second_blank_start = std::max(first_sync_column + sync_length + 3, column_);
@@ -558,10 +416,7 @@ template <class BusHandler, bool is_iie> class Video: public VideoBase {
column_ = (column_ + cycles_this_line) % 65;
if(!column_) {
row_ = (row_ + 1) % 262;
flash_ = (flash_ + 1) % (2 * flash_length);
if(!alternative_character_set_) {
character_zones[1].xor_mask = flash_mask();
}
did_end_line();
// Add an extra half a colour cycle of blank; this isn't counted in the run_for
// count explicitly but is promised. If this is a vertical sync line, output sync
@@ -575,35 +430,6 @@ template <class BusHandler, bool is_iie> class Video: public VideoBase {
}
}
GraphicsMode graphics_mode(int row) {
if(
text_ ||
(mixed_ && row >= 160 && row < 192)
) return columns_80_ ? GraphicsMode::DoubleText : GraphicsMode::Text;
if(high_resolution_) {
return (annunciator_3_ && columns_80_) ? GraphicsMode::DoubleHighRes : GraphicsMode::HighRes;
} else {
if(columns_80_) return GraphicsMode::DoubleLowRes;
if(annunciator_3_) return GraphicsMode::FatLowRes;
return GraphicsMode::LowRes;
}
}
int video_page() {
return (store_80_ || !page2_) ? 0 : 1;
}
uint16_t get_row_address(int row) {
const int character_row = row >> 3;
const int pixel_row = row & 7;
const uint16_t row_address = uint16_t((character_row >> 3) * 40 + ((character_row&7) << 7));
const GraphicsMode pixel_mode = graphics_mode(row);
return ((pixel_mode == GraphicsMode::HighRes) || (pixel_mode == GraphicsMode::DoubleHighRes)) ?
uint16_t(((video_page()+1) * 0x2000) + row_address + ((pixel_row&7) << 10)) :
uint16_t(((video_page()+1) * 0x400) + row_address);
}
BusHandler &bus_handler_;
};
@@ -611,4 +437,4 @@ template <class BusHandler, bool is_iie> class Video: public VideoBase {
}
}
#endif /* Video_hpp */
#endif /* Apple_II_Video_hpp */

View File

@@ -0,0 +1,377 @@
//
// VideoSwitches.hpp
// Clock Signal
//
// Created by Thomas Harte on 31/10/2020.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#ifndef VideoSwitches_h
#define VideoSwitches_h
#include "../../../ClockReceiver/ClockReceiver.hpp"
#include "../../../ClockReceiver/DeferredQueue.hpp"
#include "../../ROMMachine.hpp"
namespace Apple {
namespace II {
// Enumerates all Apple II and IIe display modes.
enum class GraphicsMode {
Text = 0,
DoubleText,
HighRes,
DoubleHighRes,
LowRes,
DoubleLowRes,
/// Fat low res mode is regular low res mode, but clocked out at 7Mhz rather than 14, leading to improper colours.
FatLowRes
};
constexpr bool is_text_mode(GraphicsMode m) { return m <= GraphicsMode::DoubleText; }
constexpr bool is_double_mode(GraphicsMode m) { return int(m) & 1; }
template <typename TimeUnit> class VideoSwitches {
public:
/*!
Constructs a new instance of VideoSwitches in which changes to relevant switches
affect the video mode only after @c delay cycles.
If @c is_iie is true, these switches will set up the character zones for an IIe-esque
set of potential flashing characters and alternate video modes.
*/
VideoSwitches(bool is_iie, TimeUnit delay, std::function<void(TimeUnit)> &&target) : delay_(delay), deferrer_(std::move(target)) {
character_zones_[0].xor_mask = 0;
character_zones_[0].address_mask = 0x3f;
character_zones_[1].xor_mask = 0;
character_zones_[1].address_mask = 0x3f;
character_zones_[2].xor_mask = 0;
character_zones_[2].address_mask = 0x3f;
character_zones_[3].xor_mask = 0;
character_zones_[3].address_mask = 0x3f;
if(is_iie) {
character_zones_[0].xor_mask =
character_zones_[2].xor_mask =
character_zones_[3].xor_mask = 0xff;
character_zones_[2].address_mask =
character_zones_[3].address_mask = 0xff;
}
}
/*!
Advances @c cycles.
*/
void run_for(TimeUnit cycles) {
deferrer_.run_for(cycles);
}
/*
Descriptions for the setters below are taken verbatim from
the Apple IIe Technical Reference. Addresses are the conventional
locations within the Apple II memory map. Only those which affect
video output are implemented here.
Those registers which don't exist on a II/II+ are marked.
*/
/*!
Setter for ALTCHAR ($C00E/$C00F; triggers on write only):
* Off: display text using primary character set.
* On: display text using alternate character set.
Doesn't exist on a II/II+.
*/
void set_alternative_character_set(bool alternative_character_set) {
external_.alternative_character_set = alternative_character_set;
deferrer_.defer(delay_, [this, alternative_character_set] {
internal_.alternative_character_set = alternative_character_set;
if(alternative_character_set) {
character_zones_[1].address_mask = 0xff;
character_zones_[1].xor_mask = 0;
} else {
character_zones_[1].address_mask = 0x3f;
character_zones_[1].xor_mask = flash_mask();
// The XOR mask is seeded here; it's dynamic, so updated elsewhere.
}
});
}
bool get_alternative_character_set() const {
return external_.alternative_character_set;
}
/*!
Setter for 80COL ($C00C/$C00D; triggers on write only).
* Off: display 40 columns.
* On: display 80 columns.
Doesn't exist on a II/II+.
*/
void set_80_columns(bool columns_80) {
external_.columns_80 = columns_80;
deferrer_.defer(delay_, [this, columns_80] {
internal_.columns_80 = columns_80;
});
}
bool get_80_columns() const {
return external_.columns_80;
}
/*!
Setter for 80STORE ($C000/$C001; triggers on write only).
* Off: cause PAGE2 to select auxiliary RAM.
* On: cause PAGE2 to switch main RAM areas.
Doesn't exist on a II/II+.
*/
void set_80_store(bool store_80) {
external_.store_80 = internal_.store_80 = store_80;
}
bool get_80_store() const {
return external_.store_80;
}
/*!
Setter for PAGE2 ($C054/$C055; triggers on read or write).
* Off: select Page 1.
* On: select Page 2 or, if 80STORE on, Page 1 in auxiliary memory.
80STORE doesn't exist on a II/II+; therefore this always selects
either Page 1 or Page 2 on those machines.
*/
void set_page2(bool page2) {
external_.page2 = internal_.page2 = page2;
}
bool get_page2() const {
return external_.page2;
}
/*!
Setter for TEXT ($C050/$C051; triggers on read or write).
* Off: display graphics or, if MIXED on, mixed.
* On: display text.
*/
void set_text(bool text) {
external_.text = text;
deferrer_.defer(delay_, [this, text] {
internal_.text = text;
});
}
bool get_text() const {
return external_.text;
}
/*!
Setter for MIXED ($C052/$C053; triggers on read or write).
* Off: display only text or only graphics.
* On: if TEXT off, display text and graphics.
*/
void set_mixed(bool mixed) {
external_.mixed = mixed;
deferrer_.defer(delay_, [this, mixed] {
internal_.mixed = mixed;
});
}
bool get_mixed() const {
return external_.mixed;
}
/*!
Setter for HIRES ($C056/$C057; triggers on read or write).
* Off: if TEXT off, display low-resolution graphics.
* On: if TEXT off, display high-resolution or, if DHIRES on, double high-resolution graphics.
DHIRES doesn't exist on a II/II+; therefore this always selects
either high- or low-resolution graphics on those machines.
Despite Apple's documentation, the IIe also supports double low-resolution
graphics, which are the 80-column analogue to ordinary low-resolution 40-column
low-resolution graphics.
*/
void set_high_resolution(bool high_resolution) {
external_.high_resolution = high_resolution;
deferrer_.defer(delay_, [this, high_resolution] {
internal_.high_resolution = high_resolution;
});
}
bool get_high_resolution() const {
return external_.high_resolution;
}
/*!
Setter for annunciator 3.
* On: turn on annunciator 3.
* Off: turn off annunciator 3.
This exists on both the II/II+ and the IIe, but has no effect on
video on the older machines. It's intended to be used on the IIe
to confirm double-high resolution mode but has side effects in
selecting mixed mode output and discarding high-resolution
delay bits.
*/
void set_annunciator_3(bool annunciator_3) {
external_.annunciator_3 = annunciator_3;
deferrer_.defer(delay_, [this, annunciator_3] {
internal_.annunciator_3 = annunciator_3;
high_resolution_mask_ = annunciator_3 ? 0x7f : 0xff;
});
}
bool get_annunciator_3() const {
return external_.annunciator_3;
}
enum class CharacterROM {
/// The ROM that shipped with both the Apple II and the II+.
II,
/// The ROM that shipped with the original IIe.
IIe,
/// The ROM that shipped with the Enhanced IIe.
EnhancedIIe,
/// The ROM that shipped with the IIgs.
IIgs
};
/// @returns A file-level description of @c rom.
static ROMMachine::ROM rom_description(CharacterROM rom) {
const std::string machine_name = "AppleII";
switch(rom) {
case CharacterROM::II:
return ROMMachine::ROM(machine_name, "the basic Apple II character ROM", "apple2-character.rom", 2*1024, 0x64f415c6);
case CharacterROM::IIe:
return ROMMachine::ROM(machine_name, "the Apple IIe character ROM", "apple2eu-character.rom", 4*1024, 0x816a86f1);
default: // To appease GCC.
case CharacterROM::EnhancedIIe:
return ROMMachine::ROM(machine_name, "the Enhanced Apple IIe character ROM", "apple2e-character.rom", 4*1024, 0x2651014d);
case CharacterROM::IIgs:
return ROMMachine::ROM(machine_name, "the Apple IIgs character ROM", "apple2gs.chr", 4*1024, 0x91e53cd8);
}
}
/// Set the character ROM for this video output.
void set_character_rom(const std::vector<uint8_t> &rom) {
character_rom_ = rom;
// There's some inconsistency in bit ordering amongst the common ROM dumps;
// detect that based arbitrarily on the second line of the $ graphic and
// ensure consistency.
if(character_rom_[0x121] == 0x3c || character_rom_[0x122] == 0x3c) {
for(auto &graphic : character_rom_) {
graphic =
((graphic & 0x01) ? 0x40 : 0x00) |
((graphic & 0x02) ? 0x20 : 0x00) |
((graphic & 0x04) ? 0x10 : 0x00) |
((graphic & 0x08) ? 0x08 : 0x00) |
((graphic & 0x10) ? 0x04 : 0x00) |
((graphic & 0x20) ? 0x02 : 0x00) |
((graphic & 0x40) ? 0x01 : 0x00);
}
}
}
protected:
GraphicsMode graphics_mode(int row) const {
if(
internal_.text ||
(internal_.mixed && row >= 160 && row < 192)
) return internal_.columns_80 ? GraphicsMode::DoubleText : GraphicsMode::Text;
if(internal_.high_resolution) {
return (internal_.annunciator_3 && internal_.columns_80) ? GraphicsMode::DoubleHighRes : GraphicsMode::HighRes;
} else {
if(internal_.columns_80) return GraphicsMode::DoubleLowRes;
if(internal_.annunciator_3) return GraphicsMode::FatLowRes;
return GraphicsMode::LowRes;
}
}
int video_page() const {
return (internal_.store_80 || !internal_.page2) ? 0 : 1;
}
uint16_t get_row_address(int row) const {
const int character_row = row >> 3;
const int pixel_row = row & 7;
const uint16_t row_address = uint16_t((character_row >> 3) * 40 + ((character_row&7) << 7));
const GraphicsMode pixel_mode = graphics_mode(row);
return ((pixel_mode == GraphicsMode::HighRes) || (pixel_mode == GraphicsMode::DoubleHighRes)) ?
uint16_t(((video_page()+1) * 0x2000) + row_address + ((pixel_row&7) << 10)) :
uint16_t(((video_page()+1) * 0x400) + row_address);
}
/*!
Should be called by subclasses at the end of each line of the display;
this gives the base class a peg on which to hang flashing-character updates.
*/
void did_end_line() {
// Update character set flashing; flashing is applied only when the alternative
// character set is not selected.
flash_ = (flash_ + 1) % (2 * flash_length);
character_zones_[1].xor_mask = flash_mask() * !internal_.alternative_character_set;
}
private:
// Maintain a DeferredQueue for delayed mode switches.
const TimeUnit delay_;
DeferredQueuePerformer<TimeUnit> deferrer_;
struct Switches {
bool alternative_character_set = false;
bool columns_80 = false;
bool store_80 = false;
bool page2 = false;
bool text = true;
bool mixed = false;
bool high_resolution = false;
bool annunciator_3 = false;
} external_, internal_;
int flash_length = 8406;
int flash_ = 0;
uint8_t flash_mask() const {
return uint8_t((flash_ / flash_length) * 0xff);
}
protected:
// Describes the current text mode mapping from in-memory character index
// to output character; subclasses should:
//
// (i) use the top two-bits of the character code to index character_zones_;
// (ii) apply the address_mask to the character code in order to get a character
// offset into the character ROM; and
// (iii) apply the XOR mask to the output of the character ROM.
//
// By this means they will properly handle the limited character sets of Apple IIs
// prior to the IIe as well as the IIe and onward's alternative character set toggle.
struct CharacterMapping {
uint8_t address_mask;
uint8_t xor_mask;
};
CharacterMapping character_zones_[4];
// A mask that should be applied to high-resolution graphics bytes before output;
// it acts to retain or remove the top bit, affecting whether the half-pixel delay
// bit is effective. On a IIe it's toggleable, on early Apple IIs it doesn't exist.
uint8_t high_resolution_mask_ = 0xff;
// This holds a copy of the character ROM. The regular character
// set is assumed to be in the first 64*8 bytes; the alternative
// is in the 128*8 bytes after that.
std::vector<uint8_t> character_rom_;
};
}
}
#endif /* VideoSwitches_h */

View File

@@ -0,0 +1,290 @@
//
// ADB.cpp
// Clock Signal
//
// Created by Thomas Harte on 31/10/2020.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#include "ADB.hpp"
#include <cassert>
#include <cstdio>
#include <iostream>
// TEST.
#include "../../../InstructionSets/M50740/Parser.hpp"
#include "../../../InstructionSets/Disassembler.hpp"
#define LOG_PREFIX "[ADB GLU] "
#include "../../../Outputs/Log.hpp"
using namespace Apple::IIgs::ADB;
namespace {
// Flags affecting the CPU-visible status register.
enum class CPUFlags: uint8_t {
MouseDataFull = 0x80,
MouseInterruptEnabled = 0x40,
CommandDataIsValid = 0x20,
CommandDataInterruptEnabled = 0x10,
KeyboardDataFull = 0x08,
KeyboardDataInterruptEnabled = 0x04,
MouseXIsAvailable = 0x02,
CommandRegisterFull = 0x01,
};
// Flags affecting the microcontroller-visible register.
enum class MicrocontrollerFlags: uint8_t {
CommandRegisterFull = 0x40,
};
}
GLU::GLU() :
executor_(*this),
bus_(HalfCycles(1'789'772)),
controller_id_(bus_.add_device()),
mouse_(bus_),
keyboard_(bus_) {}
// MARK: - External interface.
uint8_t GLU::get_keyboard_data() {
// The classic Apple II serial keyboard register:
// b7: key strobe.
// b6b0: ASCII code.
return (registers_[0] & 0x7f) | ((status_ & uint8_t(CPUFlags::KeyboardDataFull)) ? 0x80 : 0x00);
}
void GLU::clear_key_strobe() {
// Clears the key strobe of the classic Apple II serial keyboard register.
status_ &= ~uint8_t(CPUFlags::KeyboardDataFull);
}
uint8_t GLU::get_any_key_down() {
// The Apple IIe check-for-any-key-down bit.
return registers_[5];
}
uint8_t GLU::get_mouse_data() {
// Alternates between returning x and y values.
//
// b7: 1 = button is up; 0 = button is down.
// b6: delta sign bit; 1 = negative.
// b5b0: mouse delta.
const uint8_t result = registers_[visible_mouse_register_];
if(visible_mouse_register_ == 2) {
++visible_mouse_register_;
} else {
status_ &= ~uint8_t(CPUFlags::MouseDataFull);
}
return result;
}
uint8_t GLU::get_modifier_status() {
// b7: 1 = command key pressed; 0 = not.
// b6: option key.
// b5: 1 = modifier key latch has been updated, no key has been pressed; 0 = not.
// b4: any numeric keypad key.
// b3: a key is down.
// b2: caps lock is pressed.
// b1: control key.
// b0: shift key.
return registers_[6];
}
uint8_t GLU::get_data() {
// b02: number of data bytes to be returned.
// b3: 1 = a valid service request is pending; 0 = no request pending.
// b4: 1 = control, command and delete keys have been pressed simultaneously; 0 = they haven't.
// b5: 1 = control, command and reset have all been pressed together; 0 = they haven't.
// b6: 1 = ADB controller encountered an error and reset itself; 0 = no error.
// b7: 1 = ADB has received a response from the addressed ADB device; 0 = no respone.
status_ &= ~uint8_t(CPUFlags::CommandDataIsValid);
return registers_[7];
}
uint8_t GLU::get_status() {
// b7: 1 = mouse data register is full; 0 = empty.
// b6: 1 = mouse interrupt is enabled.
// b5: 1 = command/data has valid data.
// b4: 1 = command/data interrupt is enabled.
// b3: 1 = keyboard data is full.
// b2: 1 = keyboard data interrupt is enabled.
// b1: 1 = mouse x-data is available; 0 = y.
// b0: 1 = command register is full (set when command is written); 0 = empty (cleared when data is read).
return status_ | ((visible_mouse_register_ == 2) ? uint8_t(CPUFlags::MouseXIsAvailable) : 0);
}
void GLU::set_status(uint8_t status) {
// This permits only the interrupt flags to be set.
constexpr uint8_t interrupt_flags =
uint8_t(CPUFlags::MouseInterruptEnabled) |
uint8_t(CPUFlags::CommandDataInterruptEnabled) |
uint8_t(CPUFlags::KeyboardDataInterruptEnabled);
status_ = (status_ & ~interrupt_flags) | (status & interrupt_flags);
}
void GLU::set_command(uint8_t command) {
registers_[1] = command;
registers_[4] |= uint8_t(MicrocontrollerFlags::CommandRegisterFull);
status_ |= uint8_t(CPUFlags::CommandRegisterFull);
}
// MARK: - Setup and run.
void GLU::set_microcontroller_rom(const std::vector<uint8_t> &rom) {
executor_.set_rom(rom);
// TEST invocation.
/* InstructionSet::Disassembler<InstructionSet::M50740::Parser, 0x1fff, InstructionSet::M50740::Instruction, uint8_t, uint16_t> disassembler;
disassembler.disassemble(rom.data(), 0x1000, uint16_t(rom.size()), 0x1000);
const auto instructions = disassembler.instructions();
const auto entry_points = disassembler.entry_points();
for(const auto &pair : instructions) {
std::cout << std::hex << pair.first << "\t\t";
if(entry_points.find(pair.first) != entry_points.end()) {
std::cout << "L" << pair.first << "\t";
} else {
std::cout << "\t\t";
}
std::cout << operation_name(pair.second.operation) << " ";
std::cout << address(pair.second.addressing_mode, &rom[pair.first - 0x1000], pair.first);
std::cout << std::endl;
}*/
}
void GLU::run_for(Cycles cycles) {
executor_.run_for(cycles);
}
// MARK: - M50470 port handler
void GLU::set_port_output(int port, uint8_t value) {
switch(port) {
case 0:
register_latch_ = value;
break;
case 1:
// printf("Keyboard write: %02x???\n", value);
break;
case 2: {
// printf("ADB data line input: %d???\n", value >> 7);
// printf("IIe keyboard reset line: %d\n", (value >> 6)&1);
// printf("IIgs reset line: %d\n", (value >> 5)&1);
// printf("GLU strobe: %d\n", (value >> 4)&1);
// printf("Select GLU register: %d [%02x]\n", value & 0xf, value);
register_address_ = value & 0xf;
// This is an ugly hack, I think. Per Neil Parker's Inside the Apple IIGS ADB Controller
// http://nparker.llx.com/a2/adb.html#external:
//
// The protocol for reading an ADB GLU register is as follows:
//
// 1. Put the register number of the ADB GLU register in port P2 bits 0-3.
// 2. Clear bit 4 of port P2, read the data from P0, and set bit 4 of P0.
//
// The protocol for writing a GLU register is similar:
//
// 1. Write the register number to port P2 bits 0-3.
// 2. Write the data to port P0.
// 3. Configure port P0 for output by writing $FF to $E1.
// 4. Clear bit 4 of P2, and immediately set it again.
// 5. Configure port P0 for input by writing 0 to $E1.
//
// ---
//
// I tried: linking a read or write to rising or falling edges of the strobe.
// Including with hysteresis as per the "immediately" (which, in practice, seems
// to mean "in the very next instruction", i.e. 5 cycles later). That didn't seem
// properly to differentiate.
//
// So I'm focussing on the "configure port P0 for output" bit. Which I don't see
// would be visible here unless it is actually an exposed signal, which is unlikely.
//
// Ergo: ugly. HACK.
const bool strobe = value & 0x10;
if(strobe != register_strobe_) {
register_strobe_ = strobe;
if(!register_strobe_) {
if(executor_.get_output_mask(0)) {
registers_[register_address_] = register_latch_;
switch(register_address_) {
default: break;
case 0: status_ |= uint8_t(CPUFlags::KeyboardDataFull); break;
case 2:
case 3:
status_ |= uint8_t(CPUFlags::MouseDataFull);
visible_mouse_register_ = 2;
printf("Mouse: %d <- %02x\n", register_address_, register_latch_);
break;
case 7: status_ |= uint8_t(CPUFlags::CommandDataIsValid); break;
}
} else {
register_latch_ = registers_[register_address_];
switch(register_address_) {
default: break;
case 1:
registers_[4] &= ~uint8_t(MicrocontrollerFlags::CommandRegisterFull);
status_ &= ~uint8_t(CPUFlags::CommandRegisterFull);
break;
}
}
}
}
} break;
case 3:
if(modifier_state_ != (value & 0x30)) {
modifier_state_ = value & 0x30;
LOG("Modifier state: " << int(value & 0x30));
}
// Output is inverted respective to input; the microcontroller
// sets a value of '1' in order to pull the ADB bus low.
bus_.set_device_output(controller_id_, !(value & 0x08));
break;
default: assert(false);
}
}
bool GLU::get_command_button() const {
return modifier_state_ & 0x20;
}
bool GLU::get_option_button() const {
return modifier_state_ & 0x10;
}
uint8_t GLU::get_port_input(int port) {
switch(port) {
case 0: return register_latch_;
case 1:
// printf("IIe keyboard read\n");
return 0x06;
case 2:
// printf("ADB data line input, etc\n");
return bus_.get_state() ? 0x80 : 0x00;
case 3:
// printf("ADB data line output, etc\n");
return 0x00;
default: assert(false);
}
return 0xff;
}
void GLU::run_ports_for(Cycles cycles) {
bus_.run_for(cycles);
}
void GLU::set_vertical_blank(bool is_blank) {
executor_.set_interrupt_line(is_blank);
}

View File

@@ -0,0 +1,92 @@
//
// ADB.hpp
// Clock Signal
//
// Created by Thomas Harte on 31/10/2020.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#ifndef Apple_IIgs_ADB_hpp
#define Apple_IIgs_ADB_hpp
#include <cstdint>
#include <vector>
#include "../../../InstructionSets/M50740/Executor.hpp"
#include "../ADB/Bus.hpp"
#include "../ADB/Mouse.hpp"
#include "../ADB/Keyboard.hpp"
namespace Apple {
namespace IIgs {
namespace ADB {
class GLU: public InstructionSet::M50740::PortHandler {
public:
GLU();
uint8_t get_keyboard_data();
uint8_t get_mouse_data();
uint8_t get_modifier_status();
uint8_t get_any_key_down();
uint8_t get_data();
uint8_t get_status();
void set_command(uint8_t);
void set_status(uint8_t);
void clear_key_strobe();
void set_microcontroller_rom(const std::vector<uint8_t> &rom);
void run_for(Cycles cycles);
bool get_command_button() const;
bool get_option_button() const;
void set_vertical_blank(bool);
bool get_vertical_blank() {
return vertical_blank_;
}
Apple::ADB::Keyboard &keyboard() {
return keyboard_;
}
Inputs::Mouse &get_mouse() {
return mouse_;
}
private:
InstructionSet::M50740::Executor executor_;
void run_ports_for(Cycles) override;
void set_port_output(int port, uint8_t value) override;
uint8_t get_port_input(int port) override;
uint8_t registers_[16]{};
uint8_t register_address_;
uint8_t register_latch_ = 0xff;
bool register_strobe_ = false;
uint8_t status_ = 0x00;
Apple::ADB::Bus bus_;
size_t controller_id_;
uint8_t modifier_state_ = 0;
bool vertical_blank_ = false;
int visible_mouse_register_ = 2;
// For now, attach only a keyboard and mouse.
Apple::ADB::Mouse mouse_;
Apple::ADB::Keyboard keyboard_;
};
}
}
}
#endif /* Apple_IIgs_ADB_hpp */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,33 @@
//
// AppleIIgs.hpp
// Clock Signal
//
// Created by Thomas Harte on 20/10/2020.
// Copyright 2020 Thomas Harte. All rights reserved.
//
#ifndef Machines_Apple_AppleIIgs_hpp
#define Machines_Apple_AppleIIgs_hpp
#include "../../../Configurable/Configurable.hpp"
#include "../../../Configurable/StandardOptions.hpp"
#include "../../../Analyser/Static/StaticAnalyser.hpp"
#include "../../ROMMachine.hpp"
#include <memory>
namespace Apple {
namespace IIgs {
class Machine {
public:
virtual ~Machine();
/// Creates and returns an AppleIIgs.
static Machine *AppleIIgs(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher);
};
}
}
#endif /* Machines_Apple_AppleIIgs_hpp */

View File

@@ -0,0 +1,597 @@
//
// MemoryMap.hpp
// Clock Signal
//
// Created by Thomas Harte on 25/10/2020.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#ifndef Machines_Apple_AppleIIgs_MemoryMap_hpp
#define Machines_Apple_AppleIIgs_MemoryMap_hpp
#include <array>
#include <bitset>
#include <vector>
#include "../AppleII/LanguageCardSwitches.hpp"
#include "../AppleII/AuxiliaryMemorySwitches.hpp"
namespace Apple {
namespace IIgs {
class MemoryMap {
public:
// MARK: - Initial construction and configuration.
MemoryMap() : auxiliary_switches_(*this), language_card_(*this) {}
void set_storage(std::vector<uint8_t> &ram, std::vector<uint8_t> &rom) {
// Keep a pointer for later; also note the proper RAM offset.
ram_base = ram.data();
shadow_base[0] = ram_base; // i.e. all unshadowed writes go to where they've already gone (to make a no-op).
shadow_base[1] = &ram[ram.size() - 0x02'0000]; // i.e. all shadowed writes go somewhere in the last
// 128bk of RAM.
// Establish bank mapping.
uint8_t next_region = 0;
auto region = [&next_region, this]() -> uint8_t {
assert(next_region != this->regions.size());
return next_region++;
};
auto set_region = [this](uint8_t bank, uint16_t start, uint16_t end, uint8_t region) {
assert((end == 0xffff) || !(end&0xff));
assert(!(start&0xff));
// Fill in memory map.
size_t target = size_t((bank << 8) | (start >> 8));
for(int c = start; c < end; c += 0x100) {
region_map[target] = region;
++target;
}
};
auto set_regions = [set_region, region](uint8_t bank, std::initializer_list<uint16_t> addresses, std::vector<uint8_t> allocated_regions = {}) {
uint16_t previous = 0x0000;
auto next_region = allocated_regions.begin();
for(uint16_t address: addresses) {
set_region(bank, previous, address, next_region != allocated_regions.end() ? *next_region : region());
previous = address;
assert(next_region != allocated_regions.end() || allocated_regions.empty());
if(next_region != allocated_regions.end()) ++next_region;
}
assert(next_region == allocated_regions.end());
};
// Current beliefs about the IIgs memory map:
//
// * language card banking applies to banks $00, $01, $e0 and $e1;
// * auxiliary memory switches apply to bank $00 only;
// * shadowing may be enabled only on banks $00 and $01, or on all RAM pages; and
// * whether bit 16 of the address is passed to the Mega II is selectable — this affects both the destination
// of odd-bank shadows, and whether bank $e1 is actually distinct from $e0.
//
// So:
//
// * bank $00 needs to be divided by auxiliary and language card zones;
// * banks $01, $e0 and $e1 need to be divided by language card zones only; and
// * ROM banks and all other fast RAM banks don't need subdivision.
// Language card zones:
//
// $D000$E000 4kb window, into either bank 1 or bank 2
// $E000end 12kb window, always the same RAM.
// Auxiliary zones:
//
// $0000$0200 Zero page (and stack)
// $0200$0400 [space in between]
// $0400$0800 Text Page 1
// $0800$2000 [space in between]
// $2000$4000 High-res Page 1
// $4000$C000 [space in between]
// Card zones:
//
// $C100$C2FF either cards or IIe-style ROM
// $C300$C3FF IIe-supplied 80-column card replacement ROM
// $C400$C7FF either cards or IIe-style ROM
// $C800$CFFF Standard extended card area
// Reserve region 0 as that for unmapped memory.
region();
// Bank $00: all locations potentially affected by the auxiliary switches or the
// language switches.
set_regions(0x00, {
0x0200, 0x0400, 0x0800,
0x2000, 0x4000,
0xc000, 0xc100, 0xc300, 0xc400, 0xc800,
0xd000, 0xe000,
0xffff
});
// Bank $01: all locations potentially affected by the language switches and card switches.
set_regions(0x01, {
0xc000, 0xc100, 0xc300, 0xc400, 0xc800,
0xd000, 0xe000,
0xffff
});
// Banks $02[end of RAM]: a single region.
const auto fast_region = region();
const uint8_t fast_ram_bank_limit = uint8_t(ram.size() / 0x01'0000);
for(uint8_t bank = 0x02; bank < fast_ram_bank_limit; bank++) {
set_region(bank, 0x0000, 0xffff, fast_region);
}
// [Banks $80$e0: empty].
// Banks $e0, $e1: all locations potentially affected by the language switches or marked for IO.
// Alas, separate regions are needed due to the same ROM appearing on both pages.
for(uint8_t c = 0; c < 2; c++) {
set_regions(0xe0 + c, {0xc000, 0xc100, 0xc300, 0xc400, 0xc800, 0xd000, 0xe000, 0xffff});
}
// [Banks $e2[ROM start]: empty].
// ROM banks: directly mapped to ROM.
const uint8_t rom_bank_count = uint8_t(rom.size() >> 16);
const uint8_t first_rom_bank = uint8_t(0x100 - rom_bank_count);
const uint8_t rom_region = region();
for(uint8_t c = 0; c < rom_bank_count; ++c) {
set_region(first_rom_bank + c, 0x0000, 0xffff, rom_region);
}
// Apply proper storage to those banks.
auto set_storage = [this](uint32_t address, const uint8_t *read, uint8_t *write) {
// Don't allow the reserved null region to be modified.
assert(region_map[address >> 8]);
// Either set or apply a quick bit of testing as to the logic at play.
auto &region = regions[region_map[address >> 8]];
if(read) read -= address;
if(write) write -= address;
if(!region.read) {
region.read = read;
region.write = write;
} else {
assert(region.read == read);
assert(region.write == write);
}
};
// This is highly redundant, but decouples this step from the above.
for(size_t c = 0; c < 0x80'0000; c += 0x100) {
if(c < ram.size() - 0x02'0000) {
set_storage(uint32_t(c), &ram[c], &ram[c]);
}
}
uint8_t *const slow_ram = &ram[ram.size() - 0x02'0000] - 0xe0'0000;
for(size_t c = 0xe0'0000; c < 0xe2'0000; c += 0x100) {
set_storage(uint32_t(c), &slow_ram[c], &slow_ram[c]);
}
for(uint32_t c = 0; c < uint32_t(rom_bank_count); c++) {
set_storage((first_rom_bank + c) << 16, &rom[c << 16], nullptr);
}
// Set shadowing as working from banks 0 and 1 (forever).
shadow_banks[0] = true;
// TODO: set 1Mhz flags.
// Apply initial language/auxiliary state.
set_all_paging();
}
// MARK: - Live bus access notifications and register access.
void set_shadow_register(uint8_t value) {
const uint8_t diff = value ^ shadow_register_;
shadow_register_ = value;
if(diff & 0x40) { // IO/language-card inhibit.
set_language_card_paging();
set_card_paging();
}
if(diff & 0x3f) {
set_shadowing();
}
}
uint8_t get_shadow_register() const {
return shadow_register_;
}
void set_speed_register(uint8_t value) {
speed_register_ = value;
// Enable or disable shadowing from banks 0x020x80.
for(size_t c = 0x01; c < 0x40; c++) {
shadow_banks[c] = speed_register_ & 0x10;
}
}
void set_state_register(uint8_t value) {
auxiliary_switches_.set_state(value);
language_card_.set_state(value);
}
uint8_t get_state_register() const {
return language_card_.get_state() | auxiliary_switches_.get_state();
}
void access(uint16_t address, bool is_read) {
auxiliary_switches_.access(address, is_read);
if((address & 0xfff0) == 0xc080) language_card_.access(address, is_read);
}
using AuxiliaryMemorySwitches = Apple::II::AuxiliaryMemorySwitches<MemoryMap>;
const AuxiliaryMemorySwitches &auxiliary_switches() const {
return auxiliary_switches_;
}
using LanguageCardSwitches = Apple::II::LanguageCardSwitches<MemoryMap>;
const LanguageCardSwitches &language_card_switches() const {
return language_card_;
}
private:
AuxiliaryMemorySwitches auxiliary_switches_;
LanguageCardSwitches language_card_;
friend AuxiliaryMemorySwitches;
friend LanguageCardSwitches;
uint8_t shadow_register_ = 0x08;
uint8_t speed_register_ = 0x00;
// MARK: - Memory banking.
#define assert_is_region(start, end) \
assert(region_map[start] == region_map[start-1]+1); \
assert(region_map[end-1] == region_map[start]); \
assert(region_map[end] == region_map[end-1]+1);
// Cf. LanguageCardSwitches; this function should update the region from
// $D000 onwards as per the state of the language card flags — there may
// end up being ROM or RAM (or auxiliary RAM), and the first 4kb of it
// may be drawn from either of two pools.
void set_language_card_paging() {
const auto language_state = language_card_.state();
const auto zero_state = auxiliary_switches_.zero_state();
const bool inhibit_banks0001 = shadow_register_ & 0x40;
auto apply = [&language_state, this](uint32_t bank_base, uint8_t *ram) {
// This assumes bank 1 is the one before bank 2 when RAM is linear.
uint8_t *const d0_ram_bank = ram - (language_state.bank2 ? 0x0000 : 0x1000);
// Crib the ROM pointer from a page it's always visible on.
const uint8_t *const rom = &regions[region_map[0xffd0]].read[0xff'd000] - ((bank_base << 8) + 0xd000);
auto &d0_region = regions[region_map[bank_base | 0xd0]];
d0_region.read = language_state.read ? d0_ram_bank : rom;
d0_region.write = language_state.write ? nullptr : d0_ram_bank;
auto &e0_region = regions[region_map[bank_base | 0xe0]];
e0_region.read = language_state.read ? ram : rom;
e0_region.write = language_state.write ? nullptr : ram;
// Assert assumptions made above re: memory layout.
assert(region_map[bank_base | 0xd0] + 1 == region_map[bank_base | 0xe0]);
assert(region_map[bank_base | 0xe0] == region_map[bank_base | 0xff]);
};
auto set_no_card = [this](uint32_t bank_base) {
auto &d0_region = regions[region_map[bank_base | 0xd0]];
d0_region.read = ram_base;
d0_region.write = ram_base;
auto &e0_region = regions[region_map[bank_base | 0xe0]];
e0_region.read = ram_base;
e0_region.write = ram_base;
// Assert assumptions made above re: memory layout.
assert(region_map[bank_base | 0xd0] + 1 == region_map[bank_base | 0xe0]);
assert(region_map[bank_base | 0xe0] == region_map[bank_base | 0xff]);
};
if(inhibit_banks0001) {
set_no_card(0x0000);
set_no_card(0x0100);
} else {
apply(0x0000, zero_state ? &ram_base[0x01'0000] : ram_base);
apply(0x0100, ram_base);
}
// The pointer stored in region_map[0xe000] has already been adjusted for
// the 0xe0'0000 addressing offset.
uint8_t *const e0_ram = regions[region_map[0xe000]].write;
apply(0xe000, e0_ram);
apply(0xe100, e0_ram);
}
// Cf. AuxiliarySwitches; this should establish whether ROM or card switches
// are exposed in the distinct regions C100C2FF, C300C3FF, C400C7FF and
// C800CFFF.
//
// On the IIgs it intersects with the current shadow register.
//
// TODO: so... shouldn't the card mask be incorporated here? I've got it implemented
// distinctly at present, but does that create any invalid state interactions?
void set_card_paging() {
const bool inhibit_banks0001 = shadow_register_ & 0x40;
const auto state = auxiliary_switches_.card_state();
auto apply = [&state, this](uint32_t bank_base) {
auto &c0_region = regions[region_map[bank_base | 0xc0]];
auto &c1_region = regions[region_map[bank_base | 0xc1]];
auto &c3_region = regions[region_map[bank_base | 0xc3]];
auto &c4_region = regions[region_map[bank_base | 0xc4]];
auto &c8_region = regions[region_map[bank_base | 0xc8]];
const uint8_t *const rom = &regions[region_map[0xffd0]].read[0xffc100] - ((bank_base << 8) + 0xc100);
// This is applied dynamically as it may be added or lost in banks $00 and $01.
c0_region.flags |= Region::IsIO;
#define apply_region(flag, region) \
if(flag) { \
region.read = rom; \
region.flags &= ~Region::IsIO; \
} else { \
region.flags |= Region::IsIO; \
}
apply_region(state.region_C1_C3, c1_region);
apply_region(state.region_C3, c3_region);
apply_region(state.region_C4_C8, c4_region);
apply_region(state.region_C8_D0, c8_region);
#undef apply_region
// Sanity checks.
assert(region_map[bank_base | 0xc1] == region_map[bank_base | 0xc0]+1);
assert(region_map[bank_base | 0xc2] == region_map[bank_base | 0xc1]);
assert(region_map[bank_base | 0xc3] == region_map[bank_base | 0xc2]+1);
assert(region_map[bank_base | 0xc4] == region_map[bank_base | 0xc3]+1);
assert(region_map[bank_base | 0xc7] == region_map[bank_base | 0xc4]);
assert(region_map[bank_base | 0xc8] == region_map[bank_base | 0xc7]+1);
assert(region_map[bank_base | 0xcf] == region_map[bank_base | 0xc8]);
assert(region_map[bank_base | 0xd0] == region_map[bank_base | 0xcf]+1);
};
if(inhibit_banks0001) {
// Set no IO in the Cx00 range for banks $00 and $01, just
// regular RAM (or possibly auxiliary).
const auto auxiliary_state = auxiliary_switches_.main_state();
for(uint8_t region = region_map[0x00c0]; region < region_map[0x00d0]; region++) {
regions[region].read = auxiliary_state.base.read ? &ram_base[0x01'0000] : ram_base;
regions[region].write = auxiliary_state.base.write ? &ram_base[0x01'0000] : ram_base;
regions[region].flags &= ~Region::IsIO;
}
for(uint8_t region = region_map[0x01c0]; region < region_map[0x01d0]; region++) {
regions[region].read = regions[region].write = ram_base;
regions[region].flags &= ~Region::IsIO;
}
} else {
// Obey the card state for banks $00 and $01.
apply(0x0000);
apply(0x0100);
}
// Obey the card state for banks $e0 and $e1.
apply(0xe000);
apply(0xe100);
}
// Cf. LanguageCardSwitches; this should update whether base or auxiliary RAM is
// visible in: (i) the zero and stack pages; and (ii) anywhere that the language
// card is exposing RAM instead of ROM.
void set_zero_page_paging() {
// Affects bank $00 only, and should be a single region.
auto &region = regions[region_map[0]];
region.read = region.write = auxiliary_switches_.zero_state() ? &ram_base[0x01'0000] : ram_base;
assert(region_map[0x0000] == region_map[0x0001]);
assert(region_map[0x0001]+1 == region_map[0x0002]);
// Switching to or from auxiliary RAM potentially affects the
// language card area.
set_language_card_paging();
}
// IIgs specific: sets or resets the ::IsShadowed flag across affected banks as
// per the current state of the shadow register.
//
// Completely distinct from the auxiliary and language card switches.
void set_shadowing() {
// Relevant bits:
//
// b5: inhibit shadowing, text page 2 [if ROM 03; as if always set otherwise]
// b4: inhibit shadowing, auxiliary high-res graphics
// b3: inhibit shadowing, super high-res graphics
// b2: inhibit shadowing, high-res graphics page 2
// b1: inhibit shadowing, high-res graphics page 1
// b0: inhibit shadowing, text page 1
//
// The interpretations of how the overlapping high-res and super high-res inhibit
// bits apply used below is taken from The Apple IIgs Technical Reference, P. 178.
// Of course, zones are:
//
// $0400$0800 Text Page 1
// $0800$0C00 Text Page 2 [ROM 03 machines]
// $2000$4000 High-res Page 1, and Super High-res in odd banks
// $4000$6000 High-res Page 2, and Huper High-res in odd banks
// $6000$a000 Odd banks only, rest of Super High-res
// [plus IO and language card space, subject to your definition of shadowing]
constexpr int shadow_shift = 10;
constexpr int auxiliary_offset = 0x10000 >> shadow_shift;
// Text Page 1, main and auxiliary — $0400$0800.
for(size_t c = 0x0400 >> shadow_shift; c < 0x0800 >> shadow_shift; c++) {
shadow_pages[c] = shadow_pages[c+auxiliary_offset] = !(shadow_register_ & 0x01);
}
// Text Page 2, main and auxiliary — 0x08000x0c00.
// TODO: on a ROM03 machine only.
for(size_t c = 0x0800 >> shadow_shift; c < 0x0c00 >> shadow_shift; c++) {
shadow_pages[c] = shadow_pages[c+auxiliary_offset] = !(shadow_register_ & 0x20);
}
// Hi-res graphics Page 1, main and auxiliary — $2000$4000;
// also part of the super high-res graphics page on odd pages.
//
// Even test applied:
// high-res graphics page 1 inhibit bit alone is definitive.
//
// Odd test:
// (high-res graphics inhibit or auxiliary high res graphics inhibit) _and_ (super high-res inhibit).
//
for(size_t c = 0x2000 >> shadow_shift; c < 0x4000 >> shadow_shift; c++) {
shadow_pages[c] = !(shadow_register_ & 0x02);
shadow_pages[c+auxiliary_offset] = !(shadow_register_ & 0x12);
}
// Hi-res graphics Page 2, main and auxiliary — $4000$6000;
// also part of the super high-res graphics page.
//
// Test applied: much like that for page 1.
for(size_t c = 0x4000 >> shadow_shift; c < 0x6000 >> shadow_shift; c++) {
shadow_pages[c] = !(shadow_register_ & 0x04);
shadow_pages[c+auxiliary_offset] = !(shadow_register_ & 0x14);
}
// Residue of Super Hi-Res — $6000$a000 (odd pages only).
for(size_t c = 0x6000 >> shadow_shift; c < 0xa000 >> shadow_shift; c++) {
shadow_pages[c+auxiliary_offset] = !(shadow_register_ & 0x08);
}
}
// Cf. the AuxiliarySwitches; establishes whether main or auxiliary RAM
// is exposed in bank $00 for a bunch of regions.
void set_main_paging() {
const auto state = auxiliary_switches_.main_state();
#define set(page, flags) {\
auto &region = regions[region_map[page]]; \
region.read = flags.read ? &ram_base[0x01'0000] : ram_base; \
region.write = flags.write ? &ram_base[0x01'0000] : ram_base; \
}
// Base: $0200$03FF.
set(0x02, state.base);
assert_is_region(0x02, 0x04);
// Region $0400$07ff.
set(0x04, state.region_04_08);
assert_is_region(0x04, 0x08);
// Base: $0800$1FFF.
set(0x08, state.base);
assert_is_region(0x08, 0x20);
// Region $2000$3FFF.
set(0x20, state.region_20_40);
assert_is_region(0x20, 0x40);
// Base: $4000$BFFF.
set(0x40, state.base);
assert_is_region(0x40, 0xc0);
#undef set
// This also affects shadowing flags, if shadowing is enabled at all,
// and might affect RAM in the IO area of bank $00 because the language
// card can be inhibited on a IIgs.
set_card_paging();
}
void set_all_paging() {
set_card_paging();
set_zero_page_paging(); // ... which calls set_language_card_paging().
set_main_paging();
set_shadowing();
}
void print_state() {
uint8_t region = region_map[0];
uint32_t start = 0;
for(uint32_t top = 0; top < 65536; top++) {
if(region_map[top] == region) continue;
printf("%06x -> %06x\t", start, top << 8);
printf("%c%c\n",
(regions[region_map[top] - 1].flags & Region::Is1Mhz) ? '1' : '-',
(regions[region_map[top] - 1].flags & Region::IsIO) ? 'x' : '-'
);
start = top << 8;
region = region_map[top];
}
}
#undef assert_is_region
public:
// Memory layout here is done via double indirection; the main loop should:
// (i) use the top two bytes of the address to get an index from memory_map_; and
// (ii) use that to index the memory_regions table.
//
// Pointers are eight bytes at the time of writing, so the extra level of indirection
// reduces what would otherwise be a 1.25mb table down to not a great deal more than 64kb.
std::array<uint8_t, 65536> region_map{};
uint8_t *ram_base = nullptr;
uint8_t *shadow_base[2] = {nullptr, nullptr};
static constexpr int shadow_mask[2] = {0xff'ffff, 0x01'ffff};
struct Region {
uint8_t *write = nullptr;
const uint8_t *read = nullptr;
uint8_t flags = 0;
enum Flag: uint8_t {
Is1Mhz = 1 << 0, // Both reads and writes should be synchronised with the 1Mhz clock.
IsIO = 1 << 1, // Indicates that this region should be checked for soft switches, registers, etc.
};
};
// Shadow_pages: divides the final 128kb of memory into 1kb chunks and includes a flag to indicate whether
// each is a potential destination for shadowing.
//
// Shadow_banks: divides the whole 16mb of memory into 128kb chunks and includes a flag to indicate whether
// each is a potential source of shadowing.
std::bitset<128> shadow_pages, shadow_banks;
std::array<Region, 40> regions; // An assert above ensures that this is large enough; there's no
// doctrinal reason for it to be whatever size it is now, just
// adjust as required.
};
// TODO: branching below on region.read/write is predicated on the idea that extra scratch space
// would be less efficient. Verify that?
#define MemoryMapRegion(map, address) map.regions[map.region_map[address >> 8]]
#define IsShadowed(map, region, address) (map.shadow_pages[((&region.write[address] - map.ram_base) >> 10) & 127] & map.shadow_banks[address >> 17])
#define MemoryMapRead(region, address, value) *value = region.read ? region.read[address] : 0xff
#define MemoryMapWrite(map, region, address, value) \
if(region.write) { \
region.write[address] = *value; \
const bool _mm_is_shadowed = IsShadowed(map, region, address); \
map.shadow_base[_mm_is_shadowed][(&region.write[address] - map.ram_base) & map.shadow_mask[_mm_is_shadowed]] = *value; \
}
// Quick notes on ::IsShadowed contortions:
//
// The objective is to support shadowing:
// 1. without storing a whole extra pointer, and such that the shadowing flags are orthogonal to the current auxiliary memory settings;
// 2. in such a way as to support shadowing both in banks $00/$01 and elsewhere; and
// 3. to do so without introducing too much in the way of branching.
//
// Hence the implemented solution: if shadowing is enabled then use the distance from the start of physical RAM
// modulo 128k indexed into the bank $e0/$e1 RAM.
//
// With a further twist: the modulo and pointer are indexed on ::IsShadowed to eliminate a branch even on that.
}
}
#endif /* MemoryMap_h */

View File

@@ -0,0 +1,375 @@
//
// Sound.cpp
// Clock Signal
//
// Created by Thomas Harte on 04/11/2020.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#include "Sound.hpp"
#include <cassert>
#include <cstdio>
#include <numeric>
// TODO: is it safe not to check for back-pressure in pending_stores_?
using namespace Apple::IIgs::Sound;
GLU::GLU(Concurrency::DeferringAsyncTaskQueue &audio_queue) : audio_queue_(audio_queue) {
// Reset all pending stores.
MemoryWrite disabled_write;
disabled_write.enabled = false;
for(int c = 0; c < StoreBufferSize; c++) {
pending_stores_[c].store(disabled_write);
}
}
void GLU::set_data(uint8_t data) {
if(local_.control & 0x40) {
// RAM access.
local_.ram_[address_] = data;
MemoryWrite write;
write.enabled = true;
write.address = address_;
write.value = data;
write.time = pending_store_write_time_;
pending_stores_[pending_store_write_].store(write, std::memory_order::memory_order_release);
pending_store_write_ = (pending_store_write_ + 1) % (StoreBufferSize - 1);
} else {
// Register access.
const auto address = address_; // To make sure I don't inadvertently 'capture' address_.
local_.set_register(address, data);
audio_queue_.defer([this, address, data] () {
remote_.set_register(address, data);
});
}
if(local_.control & 0x20) {
++address_;
}
}
void GLU::EnsoniqState::set_register(uint16_t address, uint8_t value) {
switch(address & 0xe0) {
case 0x00:
oscillators[address & 0x1f].velocity = uint16_t((oscillators[address & 0x1f].velocity & 0xff00) | (value << 0));
break;
case 0x20:
oscillators[address & 0x1f].velocity = uint16_t((oscillators[address & 0x1f].velocity & 0x00ff) | (value << 8));
break;
case 0x40:
oscillators[address & 0x1f].volume = value;
break;
case 0x60:
/* Does setting the last sample make any sense? */
break;
case 0x80:
oscillators[address & 0x1f].address = value;
break;
case 0xa0: {
oscillators[address & 0x1f].control = value;
// Halt + M0 => reset position.
if((oscillators[address & 0x1f].control & 0x3) == 3) {
oscillators[address & 0x1f].control |= 1;
}
} break;
case 0xc0:
oscillators[address & 0x1f].table_size = value;
// The most-significant bit that should be used is 16 + (value & 7).
oscillators[address & 0x1f].overflow_mask = ~(0xffffff >> (7 - (value & 7)));
break;
default:
switch(address & 0xff) {
case 0xe0:
/* Does setting the interrupt register really make any sense? */
break;
case 0xe1:
oscillator_count = 1 + ((value >> 1) & 31);
break;
case 0xe2:
/* Writing to the analogue to digital input definitely makes no sense. */
break;
}
break;
}
}
uint8_t GLU::get_data() {
const auto address = address_;
if(local_.control & 0x20) {
++address_;
}
switch(address & 0xe0) {
case 0x00: return local_.oscillators[address & 0x1f].velocity & 0xff;
case 0x20: return local_.oscillators[address & 0x1f].velocity >> 8;;
case 0x40: return local_.oscillators[address & 0x1f].volume;
case 0x60: return local_.oscillators[address & 0x1f].sample(local_.ram_); // i.e. look up what the sample was on demand.
case 0x80: return local_.oscillators[address & 0x1f].address;
case 0xa0: return local_.oscillators[address & 0x1f].control;
case 0xc0: return local_.oscillators[address & 0x1f].table_size;
default:
switch(address & 0xff) {
case 0xe0: {
// Find the first enabled oscillator that is signalling an interrupt and has interrupts enabled.
for(int c = 0; c < local_.oscillator_count; c++) {
if(local_.oscillators[c].interrupt_request && (local_.oscillators[c].control & 0x08)) {
local_.oscillators[c].interrupt_request = false;
return uint8_t(0x41 | (c << 1));
}
}
// No interrupt found.
return 0xc1;
} break;
case 0xe1: return uint8_t((local_.oscillator_count - 1) << 1); // TODO: should other bits be 0 or 1?
case 0xe2: return 128; // Input audio. Unimplemented!
}
break;
}
return 0;
}
bool GLU::get_interrupt_line() {
// Return @c true if any oscillator currently has its interrupt request
// set, and has interrupts enabled.
for(int c = 0; c < local_.oscillator_count; c++) {
if(local_.oscillators[c].interrupt_request && (local_.oscillators[c].control & 0x08)) {
return true;
}
}
return false;
}
// MARK: - Time entry points.
void GLU::run_for(Cycles cycles) {
// Update local state, without generating audio.
skip_audio(local_, cycles.as<size_t>());
// Update the timestamp for memory writes;
pending_store_write_time_ += cycles.as<uint32_t>();
}
void GLU::get_samples(std::size_t number_of_samples, std::int16_t *target) {
// Update remote state, generating audio.
generate_audio(number_of_samples, target);
}
void GLU::skip_samples(const std::size_t number_of_samples) {
// Update remote state, without generating audio.
skip_audio(remote_, number_of_samples);
// Apply any pending stores.
std::atomic_thread_fence(std::memory_order::memory_order_acquire);
const uint32_t final_time = pending_store_read_time_ + uint32_t(number_of_samples);
while(true) {
auto next_store = pending_stores_[pending_store_read_].load(std::memory_order::memory_order_acquire);
if(!next_store.enabled) break;
if(next_store.time >= final_time) break;
remote_.ram_[next_store.address] = next_store.value;
next_store.enabled = false;
pending_stores_[pending_store_read_].store(next_store, std::memory_order::memory_order_relaxed);
pending_store_read_ = (pending_store_read_ + 1) & (StoreBufferSize - 1);
}
}
void GLU::set_sample_volume_range(std::int16_t range) {
output_range_ = range;
}
// MARK: - Interface boilerplate.
void GLU::set_control(uint8_t control) {
local_.control = control;
audio_queue_.defer([this, control] () {
remote_.control = control;
});
}
uint8_t GLU::get_control() {
return local_.control;
}
void GLU::set_address_low(uint8_t low) {
address_ = uint16_t((address_ & 0xff00) | low);
}
uint8_t GLU::get_address_low() {
return address_ & 0xff;
}
void GLU::set_address_high(uint8_t high) {
address_ = uint16_t((high << 8) | (address_ & 0x00ff));
}
uint8_t GLU::get_address_high() {
return address_ >> 8;
}
// MARK: - Update logic.
Cycles GLU::get_next_sequence_point() const {
uint32_t result = std::numeric_limits<decltype(result)>::max();
for(int c = 0; c < local_.oscillator_count; c++) {
// Don't do anything for halted oscillators, or for oscillators that can't hit stops.
if((local_.oscillators[c].control&3) != 2) {
continue;
}
// Determine how many cycles until a stop is hit and update the pending result
// if this is the new soonest-to-expire oscillator.
const auto first_overflow_value = (local_.oscillators[c].overflow_mask - 1) << 1;
const auto time_until_stop = (first_overflow_value - local_.oscillators[c].position + local_.oscillators[c].velocity - 1) / local_.oscillators[c].velocity;
result = std::min(result, time_until_stop);
}
return Cycles(result);
}
void GLU::skip_audio(EnsoniqState &state, size_t number_of_samples) {
// Just advance all oscillator pointers and check for interrupts.
// If a read occurs to the current-output level, generate it then.
for(int c = 0; c < state.oscillator_count; c++) {
// Don't do anything for halted oscillators.
if(state.oscillators[c].control&1) continue;
// Update phase.
state.oscillators[c].position += state.oscillators[c].velocity * number_of_samples;
// Check for stops, and any interrupts that therefore flow.
if((state.oscillators[c].control & 2) && (state.oscillators[c].position & state.oscillators[c].overflow_mask)) {
// Apply halt, set interrupt request flag.
state.oscillators[c].position = 0;
state.oscillators[c].control |= 1;
state.oscillators[c].interrupt_request = true;
}
}
}
void GLU::generate_audio(size_t number_of_samples, std::int16_t *target) {
auto next_store = pending_stores_[pending_store_read_].load(std::memory_order::memory_order_acquire);
uint8_t next_amplitude = 255;
for(size_t sample = 0; sample < number_of_samples; sample++) {
// TODO: there's a bit of a hack here where it is assumed that the input clock has been
// divided in advance. Real hardware divides by 8, I think?
// Seed output as 0.
int output = 0;
// Apply phase updates to all enabled oscillators.
for(int c = 0; c < remote_.oscillator_count; c++) {
// Don't do anything for halted oscillators.
if(remote_.oscillators[c].control&1) continue;
remote_.oscillators[c].position += remote_.oscillators[c].velocity;
// Test for a new halting event.
switch(remote_.oscillators[c].control & 6) {
case 0: // Free-run mode; don't truncate the position at all, in case the
// accumulator bits in use changes.
output += remote_.oscillators[c].output(remote_.ram_);
break;
case 2: // One-shot mode; check for end of run. Otherwise update sample.
if(remote_.oscillators[c].position & remote_.oscillators[c].overflow_mask) {
remote_.oscillators[c].position = 0;
remote_.oscillators[c].control |= 1;
}
break;
case 4: // Sync/AM mode.
if(c&1) {
// Oscillator is odd-numbered; it will amplitude-modulate the next voice.
next_amplitude = remote_.oscillators[c].sample(remote_.ram_);
continue;
} else {
// Oscillator is even-numbered; it will 'sync' to the even voice, i.e. any
// time it wraps around, it will reset the next oscillator.
if(remote_.oscillators[c].position & remote_.oscillators[c].overflow_mask) {
remote_.oscillators[c].position &= remote_.oscillators[c].overflow_mask;
remote_.oscillators[c+1].position = 0;
}
}
break;
case 6: // Swap mode; possibly trigger partner, and update sample.
// Per tech note #11: "Whenever a swap occurs from a higher-numbered
// oscillator to a lower-numbered one, the output signal from the corresponding
// generator temporarily falls to the zero-crossing level (silence)"
if(remote_.oscillators[c].position & remote_.oscillators[c].overflow_mask) {
remote_.oscillators[c].control |= 1;
remote_.oscillators[c].position = 0;
remote_.oscillators[c^1].control &= ~1;
}
break;
}
// Don't add output for newly-halted oscillators.
if(remote_.oscillators[c].control&1) continue;
// Append new output.
output += (remote_.oscillators[c].output(remote_.ram_) * next_amplitude) / 255;
next_amplitude = 255;
}
// Maximum total output was 32 channels times a 16-bit range. Map that down.
// TODO: dynamic total volume?
target[sample] = (output * output_range_) >> 20;
// Apply any RAM writes that interleave here.
++pending_store_read_time_;
if(!next_store.enabled) continue;
if(next_store.time != pending_store_read_time_) continue;
remote_.ram_[next_store.address] = next_store.value;
next_store.enabled = false;
pending_stores_[pending_store_read_].store(next_store, std::memory_order::memory_order_relaxed);
pending_store_read_ = (pending_store_read_ + 1) & (StoreBufferSize - 1);
}
}
uint8_t GLU::EnsoniqState::Oscillator::sample(uint8_t *ram) {
// Determines how many you'd have to shift a 16-bit pointer to the right for,
// in order to hit only the position-supplied bits.
const int pointer_shift = 8 - ((table_size >> 3) & 7);
// Table size mask should be 0x8000 for the largest table size, and 0xff00 for
// the smallest.
const uint16_t table_size_mask = 0xffff >> pointer_shift;
// The pointer should use (at most) 15 bits; starting with bit 1 for resolution 0
// and starting at bit 8 for resolution 7.
const uint16_t table_pointer = uint16_t(position >> ((table_size&7) + pointer_shift));
// The full pointer is composed of the bits of the programmed address not touched by
// the table pointer, plus the table pointer.
const uint16_t sample_address = ((address << 8) & ~table_size_mask) | (table_pointer & table_size_mask);
// Ignored here: bit 6 should select between RAM banks. But for now this is IIgs-centric,
// and that has only one bank of RAM.
return ram[sample_address];
}
int16_t GLU::EnsoniqState::Oscillator::output(uint8_t *ram) {
const auto level = sample(ram);
// "An oscillator will halt when a zero is encountered in its waveform table."
// TODO: only if in free-run mode, I think? Or?
if(!level) {
control |= 1;
return 0;
}
// Samples are unsigned 8-bit; do the proper work to make volume work correctly.
return int8_t(level ^ 128) * volume;
}

View File

@@ -0,0 +1,111 @@
//
// Sound.hpp
// Clock Signal
//
// Created by Thomas Harte on 04/11/2020.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#ifndef Apple_IIgs_Sound_hpp
#define Apple_IIgs_Sound_hpp
#include <atomic>
#include "../../../ClockReceiver/ClockReceiver.hpp"
#include "../../../Concurrency/AsyncTaskQueue.hpp"
#include "../../../Outputs/Speaker/Implementation/SampleSource.hpp"
namespace Apple {
namespace IIgs {
namespace Sound {
class GLU: public Outputs::Speaker::SampleSource {
public:
GLU(Concurrency::DeferringAsyncTaskQueue &audio_queue);
void set_control(uint8_t);
uint8_t get_control();
void set_data(uint8_t);
uint8_t get_data();
void set_address_low(uint8_t);
uint8_t get_address_low();
void set_address_high(uint8_t);
uint8_t get_address_high();
void run_for(Cycles);
Cycles get_next_sequence_point() const;
bool get_interrupt_line();
// SampleSource.
void get_samples(std::size_t number_of_samples, std::int16_t *target);
void set_sample_volume_range(std::int16_t range);
void skip_samples(const std::size_t number_of_samples);
private:
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
uint16_t address_ = 0;
// Use a circular buffer for piping memory alterations onto the audio
// thread; it would be prohibitive to defer every write individually.
//
// Assumed: on most modern architectures, an atomic 64-bit read or
// write can be achieved locklessly.
struct MemoryWrite {
uint32_t time;
uint16_t address;
uint8_t value;
bool enabled;
};
static_assert(sizeof(MemoryWrite) == 8);
constexpr static int StoreBufferSize = 16384;
std::atomic<MemoryWrite> pending_stores_[StoreBufferSize];
uint32_t pending_store_read_ = 0, pending_store_read_time_ = 0;
uint32_t pending_store_write_ = 0, pending_store_write_time_ = 0;
// Maintain state both 'locally' (i.e. on the emulation thread) and
// 'remotely' (i.e. on the audio thread).
struct EnsoniqState {
uint8_t ram_[65536];
struct Oscillator {
uint32_t position;
// Programmer-set values.
uint16_t velocity;
uint8_t volume;
uint8_t address;
uint8_t control;
uint8_t table_size;
// Derived state.
uint32_t overflow_mask; // If a non-zero bit gets anywhere into the overflow mask, this channel
// has wrapped around. It's a function of table_size.
bool interrupt_request = false; // Will be non-zero if this channel would request an interrupt, were
// it currently enabled to do so.
uint8_t sample(uint8_t *ram);
int16_t output(uint8_t *ram);
} oscillators[32];
// Some of these aren't actually needed on both threads.
uint8_t control = 0;
int oscillator_count = 1;
void set_register(uint16_t address, uint8_t value);
} local_, remote_;
// Functions to update an EnsoniqState; these don't belong to the state itself
// because they also access the pending stores (inter alia).
void generate_audio(size_t number_of_samples, std::int16_t *target);
void skip_audio(EnsoniqState &state, size_t number_of_samples);
// Audio-thread state.
int16_t output_range_ = 0;
};
}
}
}
#endif /* SoundGLU_hpp */

View File

@@ -0,0 +1,814 @@
//
// Video.cpp
// Clock Signal
//
// Created by Thomas Harte on 31/10/2020.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#include "Video.hpp"
using namespace Apple::IIgs::Video;
namespace {
constexpr int CyclesPerTick = 7; // One 'tick' being the non-stretched length of a cycle on the old Apple II 1Mhz clock.
constexpr int CyclesPerLine = 456; // Each of the Mega II's cycles lasts 7 cycles, making 455/line except for the
// final on on a line which lasts an additional 1 (i.e. is 1/7th longer).
constexpr int Lines = 262;
constexpr int FinalPixelLine = 192;
constexpr auto FinalColumn = CyclesPerLine / CyclesPerTick;
// Converts from Apple's RGB ordering to this emulator's.
#if TARGET_RT_BIG_ENDIAN
#define PaletteConvulve(x) uint16_t(x)
#else
#define PaletteConvulve(x) uint16_t(((x&0xf00) >> 8) | ((x&0x0ff) << 8))
#endif
// The 12-bit values used by the Apple IIgs to approximate Apple II colours,
// as implied by tech note #63's use of them as border colours.
// http://www.1000bit.it/support/manuali/apple/technotes/iigs/tn.iigs.063.html
constexpr uint16_t appleii_palette[16] = {
PaletteConvulve(0x0000), // Black.
PaletteConvulve(0x0d03), // Deep Red.
PaletteConvulve(0x0009), // Dark Blue.
PaletteConvulve(0x0d2d), // Purple.
PaletteConvulve(0x0072), // Dark Green.
PaletteConvulve(0x0555), // Dark Gray.
PaletteConvulve(0x022f), // Medium Blue.
PaletteConvulve(0x06af), // Light Blue.
PaletteConvulve(0x0850), // Brown.
PaletteConvulve(0x0f60), // Orange.
PaletteConvulve(0x0aaa), // Light Grey.
PaletteConvulve(0x0f98), // Pink.
PaletteConvulve(0x01d0), // Light Green.
PaletteConvulve(0x0ff0), // Yellow.
PaletteConvulve(0x04f9), // Aquamarine.
PaletteConvulve(0x0fff), // White.
};
// Reasoned guesswork ahoy!
//
// The IIgs VGC can fetch four bytes per column — I'm unclear physically how, but that's definitely true
// since the IIgs modes packs 160 bytes work of graphics into the Apple II's usual 40-cycle fetch area;
// it's possible that if I understood the meaning of the linear video bit in the new video flag I'd know more.
//
// Super Hi-Res also fetches 16*2 = 32 bytes of palette and a control byte sometime before each row.
// So it needs five windows for that.
//
// Guessing four cycles of sync, I've chosen to arrange one output row for this emulator as:
//
// 5 cycles of back porch; [TODO: include a colour burst]
// 8 windows left border, the final five of which fetch palette and control if in IIgs mode;
// 40 windows of pixel output;
// 8 cycles of right border;
// 4 cycles of sync (including the extra 1/7th window, as it has to go _somewhere_).
//
// Otherwise, the first 200 rows may be pixels and the 192 in the middle of those are the II set.
constexpr int first_sync_line = 220; // A complete guess. Information needed.
constexpr int blank_ticks = 5;
constexpr int left_border_ticks = 8;
constexpr int pixel_ticks = 40;
constexpr int right_border_ticks = 8;
constexpr int start_of_left_border = blank_ticks;
constexpr int start_of_pixels = start_of_left_border + left_border_ticks;
constexpr int start_of_right_border = start_of_pixels + pixel_ticks;
constexpr int start_of_sync = start_of_right_border + right_border_ticks;
constexpr int sync_period = CyclesPerLine - start_of_sync*CyclesPerTick;
// I have made the guess that this occurs when the Mega II horizontal counter rolls over.
// This is just a guess.
constexpr int megaii_interrupt_point = 192*CyclesPerLine + (start_of_pixels - 28)*CyclesPerTick - 2;
// A table to map from 7-bit integers to 14-bit versions with all bits doubled.
constexpr uint16_t double_bytes[128] = {
0x0000, 0x0003, 0x000c, 0x000f, 0x0030, 0x0033, 0x003c, 0x003f,
0x00c0, 0x00c3, 0x00cc, 0x00cf, 0x00f0, 0x00f3, 0x00fc, 0x00ff,
0x0300, 0x0303, 0x030c, 0x030f, 0x0330, 0x0333, 0x033c, 0x033f,
0x03c0, 0x03c3, 0x03cc, 0x03cf, 0x03f0, 0x03f3, 0x03fc, 0x03ff,
0x0c00, 0x0c03, 0x0c0c, 0x0c0f, 0x0c30, 0x0c33, 0x0c3c, 0x0c3f,
0x0cc0, 0x0cc3, 0x0ccc, 0x0ccf, 0x0cf0, 0x0cf3, 0x0cfc, 0x0cff,
0x0f00, 0x0f03, 0x0f0c, 0x0f0f, 0x0f30, 0x0f33, 0x0f3c, 0x0f3f,
0x0fc0, 0x0fc3, 0x0fcc, 0x0fcf, 0x0ff0, 0x0ff3, 0x0ffc, 0x0fff,
0x3000, 0x3003, 0x300c, 0x300f, 0x3030, 0x3033, 0x303c, 0x303f,
0x30c0, 0x30c3, 0x30cc, 0x30cf, 0x30f0, 0x30f3, 0x30fc, 0x30ff,
0x3300, 0x3303, 0x330c, 0x330f, 0x3330, 0x3333, 0x333c, 0x333f,
0x33c0, 0x33c3, 0x33cc, 0x33cf, 0x33f0, 0x33f3, 0x33fc, 0x33ff,
0x3c00, 0x3c03, 0x3c0c, 0x3c0f, 0x3c30, 0x3c33, 0x3c3c, 0x3c3f,
0x3cc0, 0x3cc3, 0x3ccc, 0x3ccf, 0x3cf0, 0x3cf3, 0x3cfc, 0x3cff,
0x3f00, 0x3f03, 0x3f0c, 0x3f0f, 0x3f30, 0x3f33, 0x3f3c, 0x3f3f,
0x3fc0, 0x3fc3, 0x3fcc, 0x3fcf, 0x3ff0, 0x3ff3, 0x3ffc, 0x3fff,
};
}
Video::Video() :
VideoSwitches<Cycles>(true, Cycles(2), [this] (Cycles cycles) { advance(cycles); }),
crt_(CyclesPerLine - 1, 1, Outputs::Display::Type::NTSC60, Outputs::Display::InputDataType::Red4Green4Blue4) {
crt_.set_display_type(Outputs::Display::DisplayType::RGB);
crt_.set_visible_area(Outputs::Display::Rect(0.097f, 0.1f, 0.85f, 0.85f));
// Reduce the initial bounce by cueing up the part of the frame that initial drawing actually
// starts with. More or less.
crt_.output_blank(228*63*2);
// Establish the shift lookup table for NTSC -> RGB output.
for(size_t c = 0; c < sizeof(ntsc_delay_lookup_) / sizeof(*ntsc_delay_lookup_); c++) {
const auto old_delay = c >> 2;
// If delay is 3, 2, 1 or 0 the output is just that minus 1.
// Otherwise the output is either still 4, or 3 if the two lowest bits don't match.
if(old_delay < 4) {
ntsc_delay_lookup_[c] = (old_delay > 0) ? uint8_t(old_delay - 1) : 4;
} else {
ntsc_delay_lookup_[c] = (c&1) == ((c >> 1)&1) ? 4 : 3;
}
ntsc_delay_lookup_[c] = 4;
}
}
void Video::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
crt_.set_scan_target(scan_target);
}
Outputs::Display::ScanStatus Video::get_scaled_scan_status() const {
return crt_.get_scaled_scan_status();
}
void Video::set_display_type(Outputs::Display::DisplayType display_type) {
crt_.set_display_type(display_type);
}
Outputs::Display::DisplayType Video::get_display_type() const {
return crt_.get_display_type();
}
void Video::set_internal_ram(const uint8_t *ram) {
ram_ = ram;
}
void Video::advance(Cycles cycles) {
const int next_cycles_into_frame = cycles_into_frame_ + cycles.as<int>();
// Check for Mega II-style interrupt sources, prior to updating cycles_into_frame_.
if(cycles_into_frame_ < megaii_interrupt_point && next_cycles_into_frame >= megaii_interrupt_point) {
++megaii_frame_counter_;
megaii_interrupt_state_ |= 0x08 | (megaii_frame_counter_ & 0x10);
megaii_frame_counter_ &= 15;
// The "quarter second interrupt" is also called the "3.75Hz interrupt" elsewhere.
// So trigger it every 16 frames.
}
// Update video output.
const int column_start = (cycles_into_frame_ % CyclesPerLine) / CyclesPerTick;
const int row_start = cycles_into_frame_ / CyclesPerLine;
cycles_into_frame_ = next_cycles_into_frame % (CyclesPerLine * Lines);
const int column_end = (cycles_into_frame_ % CyclesPerLine) / CyclesPerTick;
const int row_end = cycles_into_frame_ / CyclesPerLine;
if(row_end == row_start) {
if(column_end != column_start) {
output_row(row_start, column_start, column_end);
}
} else {
if(column_start != FinalColumn) {
output_row(row_start, column_start, FinalColumn);
}
for(int row = row_start+1; row < row_end; row++) {
output_row(row, 0, FinalColumn);
}
if(column_end) {
output_row(row_end, 0, column_end);
}
}
}
Cycles Video::get_next_sequence_point() const {
const int cycles_into_row = cycles_into_frame_ % CyclesPerLine;
const int row = cycles_into_frame_ / CyclesPerLine;
constexpr int sequence_point_offset = (blank_ticks + left_border_ticks) * CyclesPerTick;
// Seed as the distance to the next row 0.
int result = CyclesPerLine + sequence_point_offset - cycles_into_row + (Lines - row - 1)*CyclesPerLine;
// Replace with the start of the next line, if closer.
if(row <= 200) {
if(cycles_into_row < sequence_point_offset) return Cycles(sequence_point_offset - cycles_into_row);
if(row < 200) result = CyclesPerLine + sequence_point_offset - cycles_into_row;
}
// Replace with the next Mega II interrupt point if those are enabled and it is sooner.
if(megaii_interrupt_mask_) {
const int time_until_megaii = megaii_interrupt_point - cycles_into_frame_;
if(time_until_megaii > 0 && time_until_megaii < result) {
result = time_until_megaii;
}
}
return Cycles(result);
}
void Video::output_row(int row, int start, int end) {
// Deal with vertical sync.
if(row >= first_sync_line && row < first_sync_line + 3) {
// Simplification: just output the whole line at line's end.
if(end == FinalColumn) {
crt_.output_sync(CyclesPerLine - sync_period);
crt_.output_blank(sync_period);
}
return;
}
// Pixel or pure border => blank as usual.
// Output blank only at the end of its window.
if(start < blank_ticks && end >= blank_ticks) {
crt_.output_blank(blank_ticks * CyclesPerTick);
start = blank_ticks;
if(start == end) return;
}
// The pixel buffer will actually be allocated a column early, to allow double high/low res to start
// half a column before everything else.
constexpr int pixel_buffer_allocation = start_of_pixels - 1;
// Possibly output border, pixels, border, if this is a pixel line.
if(row < 192 + ((new_video_&0x80) >> 4)) { // i.e. 192 lines for classic Apple II video, 200 for IIgs video.
// Output left border as far as currently known.
if(start >= start_of_left_border && start < pixel_buffer_allocation) {
const int end_of_period = std::min(pixel_buffer_allocation, end);
if(border_colour_) {
uint16_t *const pixel = reinterpret_cast<uint16_t *>(crt_.begin_data(2, 2));
if(pixel) *pixel = border_colour_;
crt_.output_data((end_of_period - start) * CyclesPerTick, 1);
} else {
crt_.output_blank((end_of_period - start) * CyclesPerTick);
}
start = end_of_period;
if(start == end) return;
}
assert(end > start);
// Fetch and output such pixels as it is time for.
if(start >= pixel_buffer_allocation && start < start_of_right_border) {
const int end_of_period = std::min(start_of_right_border, end);
const auto mode = graphics_mode(row);
if(start == pixel_buffer_allocation) {
// YUCKY HACK. I do not know when the IIgs fetches its super high-res palette
// and control byte. Since I do not know, any guess is equally likely negatively
// to affect software. Therefore this hack is as good as any other guess:
// assume RAM has magical burst bandwidth, and fetch the whole set instantly.
// I could spread this stuff out to allow for real bandwidth, but it'd likely be
// no more accurate, while having less of an obvious I-HACKED-THIS red flag attached.
line_control_ = ram_[0x19d00 + row];
const int palette_base = (line_control_ & 15) * 32 + 0x19e00;
for(int c = 0; c < 16; c++) {
const int entry = ram_[palette_base + (c << 1)] | (ram_[palette_base + (c << 1) + 1] << 8);
palette_[c] = PaletteConvulve(entry);
}
// Post an interrupt if requested.
if(line_control_ & 0x40) {
set_interrupts(0x20);
}
// Set up appropriately for fill mode (or not).
for(int c = 0; c < 4; c++) {
palette_zero_[c] = (line_control_ & 0x20) ? &palette_[c * 4] : &palette_throwaway_;
}
// Reset NTSC decoding and total line buffering.
ntsc_delay_ = 4;
pixels_start_column_ = start;
}
if(!next_pixel_ || pixels_format_ != format_for_mode(mode)) {
// Flush anything already in a buffer.
if(pixels_start_column_ < start) {
crt_.output_data((start - pixels_start_column_) * CyclesPerTick, next_pixel_ ? size_t(next_pixel_ - pixels_) : 1);
next_pixel_ = pixels_ = nullptr;
}
// Allocate a new buffer; 640 plus one column is as bad as it gets.
// TODO: make proper size estimate?
next_pixel_ = pixels_ = reinterpret_cast<uint16_t *>(crt_.begin_data(656, 2));
pixels_start_column_ = start;
pixels_format_ = format_for_mode(mode);
}
if(next_pixel_) {
int window_start = start - start_of_pixels;
const int window_end = end_of_period - start_of_pixels;
// Fill in border colour if this is the first column.
if(window_start == -1) {
if(next_pixel_) {
int extra_border_length;
switch(mode) {
case GraphicsMode::DoubleText:
case GraphicsMode::Text:
case GraphicsMode::DoubleHighRes:
case GraphicsMode::DoubleLowRes:
case GraphicsMode::DoubleHighResMono:
extra_border_length = 7;
break;
case GraphicsMode::HighRes:
case GraphicsMode::LowRes:
case GraphicsMode::FatLowRes:
extra_border_length = 14;
break;
case GraphicsMode::SuperHighRes:
extra_border_length = (line_control_ & 0x80) ? 16 : 8;
break;
default: // Unreachable.
extra_border_length = 0;
break;
}
for(int c = 0; c < extra_border_length; c++) {
next_pixel_[c] = border_colour_;
}
next_pixel_ += extra_border_length;
}
++window_start;
if(window_start == window_end) return;
}
switch(mode) {
case GraphicsMode::SuperHighRes:
next_pixel_ = output_super_high_res(next_pixel_, window_start, window_end, row);
break;
case GraphicsMode::Text:
next_pixel_ = output_text(next_pixel_, window_start, window_end, row);
break;
case GraphicsMode::DoubleText:
next_pixel_ = output_double_text(next_pixel_, window_start, window_end, row);
break;
case GraphicsMode::FatLowRes:
next_pixel_ = output_fat_low_resolution(next_pixel_, window_start, window_end, row);
break;
case GraphicsMode::LowRes:
next_pixel_ = output_low_resolution(next_pixel_, window_start, window_end, row);
break;
case GraphicsMode::DoubleLowRes:
next_pixel_ = output_double_low_resolution(next_pixel_, window_start, window_end, row);
break;
case GraphicsMode::HighRes:
next_pixel_ = output_high_resolution(next_pixel_, window_start, window_end, row);
break;
case GraphicsMode::DoubleHighRes:
next_pixel_ = output_double_high_resolution(next_pixel_, window_start, window_end, row);
break;
case GraphicsMode::DoubleHighResMono:
next_pixel_ = output_double_high_resolution_mono(next_pixel_, window_start, window_end, row);
break;
default:
assert(false); // i.e. other modes yet to do.
}
}
if(end_of_period == start_of_right_border) {
// Flush what remains in the NTSC queue, if applicable.
// TODO: with real NTSC test, why not?
if(next_pixel_ && is_colour_ntsc(mode)) {
ntsc_shift_ >>= 14;
next_pixel_ = output_shift(next_pixel_, 81);
}
crt_.output_data((start_of_right_border - pixels_start_column_) * CyclesPerTick, next_pixel_ ? size_t(next_pixel_ - pixels_) : 1);
next_pixel_ = pixels_ = nullptr;
}
start = end_of_period;
if(start == end) return;
}
assert(end > start);
// Output right border as far as currently known.
if(start >= start_of_right_border && start < start_of_sync) {
const int end_of_period = std::min(start_of_sync, end);
if(border_colour_) {
uint16_t *const pixel = reinterpret_cast<uint16_t *>(crt_.begin_data(2, 2));
if(pixel) *pixel = border_colour_;
crt_.output_data((end_of_period - start) * CyclesPerTick, 1);
} else {
crt_.output_blank((end_of_period - start) * CyclesPerTick);
}
// There's no point updating start here; just fall
// through to the end == FinalColumn test.
}
} else {
// This line is all border, all the time.
if(start >= start_of_left_border && start < start_of_sync) {
const int end_of_period = std::min(start_of_sync, end);
if(border_colour_) {
uint16_t *const pixel = reinterpret_cast<uint16_t *>(crt_.begin_data(2, 2));
if(pixel) *pixel = border_colour_;
crt_.output_data((end_of_period - start) * CyclesPerTick, 1);
} else {
crt_.output_blank((end_of_period - start) * CyclesPerTick);
}
start = end_of_period;
if(start == end) return;
}
}
// Output sync if the moment has arrived.
if(end == FinalColumn) {
crt_.output_sync(sync_period);
}
}
bool Video::get_is_vertical_blank(Cycles offset) {
// Cf. http://www.1000bit.it/support/manuali/apple/technotes/iigs/tn.iigs.040.html ;
// this bit covers the entire vertical border area, not just the NTSC-sense vertical blank,
// and considers the border to begin at 192 even though Super High-res mode is 200 lines.
return (cycles_into_frame_ + offset.as<int>())%(Lines * CyclesPerLine) >= FinalPixelLine * CyclesPerLine;
}
Video::Counters Video::get_counters(Cycles offset) {
// Tech note #39:
//
// "The seven-bit horizontal counter starts at $00 and counts from $40 to $7F (the sequence
// is $00, $40, $41,...,$7E, $7F, $00, $40,...). The active video time consists of 40 one
// microsecond clock cycles starting with $58 and ending with $7F."
//
// "The nine-bit vertical counter ranges from $FA through $1FF (250 through 511) in NTSC mode
// (vertical line count of 262) and from $C8 through $1FF (200 through 511) in PAL video timing
// mode (vertical line count of 312). Vertical counter value $100 corresponds to scan line
// zero in NTSC mode."
// Work out the internal offset into frame; a modulo willoccur momentarily...
auto cycles_into_frame = cycles_into_frame_ + offset.as<int>();
// Nudge slightly so that the regular start of line matches mine.
// TODO: reorient my drawing around the native offsets?
cycles_into_frame = (cycles_into_frame + 25 - start_of_pixels)%(Lines * CyclesPerLine);
// Break it down.
const auto cycles_into_line = (cycles_into_frame % CyclesPerLine) / CyclesPerTick;
auto lines_into_frame = cycles_into_frame / CyclesPerLine;
lines_into_frame += 0x100;
return Counters(
lines_into_frame - ((lines_into_frame / 0x200) * 0x106), // TODO: this assumes NTSC.
cycles_into_line + bool(cycles_into_line) * 0x40);
}
uint8_t Video::get_horizontal_counter(Cycles offset) {
const auto counters = get_counters(offset);
return uint8_t(counters.horizontal | (counters.vertical << 7));
}
uint8_t Video::get_vertical_counter(Cycles offset) {
const auto counters = get_counters(offset);
return uint8_t(counters.vertical >> 1);
}
void Video::set_new_video(uint8_t new_video) {
new_video_ = new_video;
}
uint8_t Video::get_new_video() {
return new_video_;
}
void Video::clear_interrupts(uint8_t mask) {
set_interrupts(interrupts_ & ~(mask & 0x60));
}
void Video::set_interrupt_register(uint8_t mask) {
set_interrupts(interrupts_ | (mask & 0x6));
}
uint8_t Video::get_interrupt_register() {
return interrupts_;
}
bool Video::get_interrupt_line() {
return (interrupts_&0x80) || (megaii_interrupt_mask_&megaii_interrupt_state_);
}
void Video::set_megaii_interrupts_enabled(uint8_t mask) {
megaii_interrupt_mask_ = mask;
}
uint8_t Video::get_megaii_interrupt_status() {
return megaii_interrupt_state_ | (get_annunciator_3() ? 0x20 : 0x00);
}
void Video::clear_megaii_interrupts() {
megaii_interrupt_state_ = 0;
}
void Video::notify_clock_tick() {
set_interrupts(interrupts_ | 0x40);
}
void Video::set_interrupts(uint8_t new_value) {
interrupts_ = new_value & 0x7f;
if((interrupts_ >> 4) & interrupts_ & 0x6)
interrupts_ |= 0x80;
}
void Video::set_border_colour(uint8_t colour) {
border_colour_entry_ = colour & 0x0f;
border_colour_ = appleii_palette[border_colour_entry_];
}
uint8_t Video::get_border_colour() {
return border_colour_entry_;
}
void Video::set_text_colour(uint8_t colour) {
text_colour_entry_ = colour;
text_colour_ = appleii_palette[colour >> 4];
background_colour_ = appleii_palette[colour & 0xf];
}
uint8_t Video::get_text_colour() {
return text_colour_entry_;
}
void Video::set_composite_is_colour(bool) {
// TODO.
}
bool Video::get_composite_is_colour() {
return true;
}
// MARK: - Outputters.
uint16_t *Video::output_char(uint16_t *target, uint8_t source, int row) const {
const int character = source & character_zones_[source >> 6].address_mask;
const uint8_t xor_mask = character_zones_[source >> 6].xor_mask;
const std::size_t character_address = size_t(character << 3) + (row & 7);
const uint8_t character_pattern = character_rom_[character_address] ^ xor_mask;
const uint16_t colours[2] = {background_colour_, text_colour_};
target[0] = colours[(character_pattern & 0x40) >> 6];
target[1] = colours[(character_pattern & 0x20) >> 5];
target[2] = colours[(character_pattern & 0x10) >> 4];
target[3] = colours[(character_pattern & 0x08) >> 3];
target[4] = colours[(character_pattern & 0x04) >> 2];
target[5] = colours[(character_pattern & 0x02) >> 1];
target[6] = colours[(character_pattern & 0x01) >> 0];
return target + 7;
}
uint16_t *Video::output_text(uint16_t *target, int start, int end, int row) const {
const uint16_t row_address = get_row_address(row);
for(int c = start; c < end; c++) {
target = output_char(target, ram_[row_address + c], row);
}
return target;
}
uint16_t *Video::output_double_text(uint16_t *target, int start, int end, int row) const {
const uint16_t row_address = get_row_address(row);
for(int c = start; c < end; c++) {
target = output_char(target, ram_[0x10000 + row_address + c], row);
target = output_char(target, ram_[row_address + c], row);
}
return target;
}
uint16_t *Video::output_super_high_res(uint16_t *target, int start, int end, int row) const {
const int row_address = row * 160 + 0x12000;
// The palette_zero_ writes ensure that palette colour 0 is replaced by whatever was last output,
// if fill mode is enabled. Otherwise they go to throwaway storage.
if(line_control_ & 0x80) {
for(int c = start * 4; c < end * 4; c++) {
const uint8_t source = ram_[row_address + c];
*palette_zero_[3] = target[0] = palette_[0x8 + ((source >> 6) & 0x3)];
*palette_zero_[0] = target[1] = palette_[0xc + ((source >> 4) & 0x3)];
*palette_zero_[1] = target[2] = palette_[0x0 + ((source >> 2) & 0x3)];
*palette_zero_[2] = target[3] = palette_[0x4 + ((source >> 0) & 0x3)];
target += 4;
}
} else {
for(int c = start * 4; c < end * 4; c++) {
const uint8_t source = ram_[row_address + c];
*palette_zero_[0] = target[0] = palette_[(source >> 4) & 0xf];
*palette_zero_[0] = target[1] = palette_[source & 0xf];
target += 2;
}
}
return target;
}
uint16_t *Video::output_double_high_resolution_mono(uint16_t *target, int start, int end, int row) {
const uint16_t row_address = get_row_address(row);
constexpr uint16_t colours[] = {0, 0xffff};
for(int c = start; c < end; c++) {
const uint8_t source[2] = {
ram_[0x10000 + row_address + c],
ram_[row_address + c],
};
target[0] = colours[(source[1] >> 0) & 0x1];
target[1] = colours[(source[1] >> 1) & 0x1];
target[2] = colours[(source[1] >> 2) & 0x1];
target[3] = colours[(source[1] >> 3) & 0x1];
target[4] = colours[(source[1] >> 4) & 0x1];
target[5] = colours[(source[1] >> 5) & 0x1];
target[6] = colours[(source[1] >> 6) & 0x1];
target[7] = colours[(source[0] >> 0) & 0x1];
target[8] = colours[(source[0] >> 1) & 0x1];
target[9] = colours[(source[0] >> 2) & 0x1];
target[10] = colours[(source[0] >> 3) & 0x1];
target[11] = colours[(source[0] >> 4) & 0x1];
target[12] = colours[(source[0] >> 5) & 0x1];
target[13] = colours[(source[0] >> 6) & 0x1];
target += 14;
}
return target;
}
uint16_t *Video::output_low_resolution(uint16_t *target, int start, int end, int row) {
const int row_shift = row&4;
const uint16_t row_address = get_row_address(row);
for(int c = start; c < end; c++) {
const uint8_t source = (ram_[row_address + c] >> row_shift) & 0xf;
// Convulve input as a function of odd/even row.
uint32_t long_source;
if(c&1) {
long_source = uint32_t((source >> 2) | (source << 2) | (source << 6) | (source << 10));
} else {
long_source = uint32_t((source | (source << 4) | (source << 8) | (source << 12)) & 0x3fff);
}
ntsc_shift_ = (long_source << 18) | (ntsc_shift_ >> 14);
target = output_shift(target, 1 + c*2);
}
return target;
}
uint16_t *Video::output_fat_low_resolution(uint16_t *target, int start, int end, int row) {
const int row_shift = row&4;
const uint16_t row_address = get_row_address(row);
for(int c = start; c < end; c++) {
const uint32_t doubled_source = uint32_t(double_bytes[(ram_[row_address + c] >> row_shift) & 0xf]);
const uint32_t long_source = doubled_source | (doubled_source << 8);
// TODO: verify the above.
ntsc_shift_ = (long_source << 18) | (ntsc_shift_ >> 14);
target = output_shift(target, 1 + c*2);
}
return target;
}
uint16_t *Video::output_double_low_resolution(uint16_t *target, int start, int end, int row) {
const int row_shift = row&4;
const uint16_t row_address = get_row_address(row);
for(int c = start; c < end; c++) {
const uint8_t source[2] = {
uint8_t((ram_[row_address + c] >> row_shift) & 0xf),
uint8_t((ram_[0x10000 + row_address + c] >> row_shift) & 0xf)
};
// Convulve input as a function of odd/even row; this is very much like low
// resolution mode except that the first 7 bits to be output will come from
// source[1] and the next 7 from source[0]. Also shifting is offset by
// half a window compared to regular low resolution, so the conditional
// works the other way around.
uint32_t long_source;
if(c&1) {
long_source = uint32_t((source[1] | ((source[1] << 4) & 0x70) | ((source[0] << 4) & 0x80) | (source[0] << 8) | (source[0] << 12)) & 0x3fff);
} else {
long_source = uint32_t((source[1] >> 2) | (source[1] << 2) | ((source[1] << 6) & 0x40) | ((source[0] << 6) & 0x380) | (source[0] << 10));
}
ntsc_shift_ = (long_source << 18) | (ntsc_shift_ >> 14);
target = output_shift(target, c*2);
}
return target;
}
uint16_t *Video::output_high_resolution(uint16_t *target, int start, int end, int row) {
const uint16_t row_address = get_row_address(row);
for(int c = start; c < end; c++) {
uint8_t source = ram_[row_address + c];
const uint32_t doubled_source = uint32_t(double_bytes[source & 0x7f]);
// Just append new bits, doubled up (and possibly delayed).
// TODO: I can kill the conditional here. Probably?
if(source & high_resolution_mask_ & 0x80) {
ntsc_shift_ = ((doubled_source & 0x1fff) << 19) | ((ntsc_shift_ >> 13) & 0x40000) | (ntsc_shift_ >> 14);
} else {
ntsc_shift_ = (doubled_source << 18) | (ntsc_shift_ >> 14);
}
target = output_shift(target, 1 + c*2);
}
return target;
}
uint16_t *Video::output_double_high_resolution(uint16_t *target, int start, int end, int row) {
const uint16_t row_address = get_row_address(row);
for(int c = start; c < end; c++) {
const uint8_t source[2] = {
ram_[0x10000 + row_address + c],
ram_[row_address + c],
};
ntsc_shift_ = unsigned(source[1] << 25) | unsigned(source[0] << 18) | (ntsc_shift_ >> 14);
target = output_shift(target, c*2);
}
return target;
}
uint16_t *Video::output_shift(uint16_t *target, int column) {
// Make sure that at least two columns are enqueued before output begins;
// the top bits can't be understood without reference to bits that come afterwards.
if(!column) {
ntsc_shift_ |= ntsc_shift_ >> 14;
return target;
}
// Phase here is kind of arbitrary; it pairs off with the order
// I've picked for my rolls table and with my decision to count
// columns as aligned with double-mode.
const int phase = column * 7 + 3;
constexpr uint8_t rolls[4][16] = {
{
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf
},
{
0x0, 0x2, 0x4, 0x6, 0x8, 0xa, 0xc, 0xe,
0x1, 0x3, 0x5, 0x7, 0x9, 0xb, 0xd, 0xf
},
{
0x0, 0x4, 0x8, 0xc, 0x1, 0x5, 0x9, 0xd,
0x2, 0x6, 0xa, 0xe, 0x3, 0x7, 0xb, 0xf
},
{
0x0, 0x8, 0x1, 0x9, 0x2, 0xa, 0x3, 0xb,
0x4, 0xc, 0x5, 0xd, 0x6, 0xe, 0x7, 0xf
},
};
#define OutputPixel(offset) {\
ntsc_delay_ = ntsc_delay_lookup_[unsigned(ntsc_delay_ << 2) | ((ntsc_shift_ >> offset)&1) | ((ntsc_shift_ >> (offset + 3))&2)]; \
const auto raw_bits = (ntsc_shift_ >> (offset + ntsc_delay_)) & 0x0f; \
target[offset] = appleii_palette[rolls[(phase + offset + ntsc_delay_)&3][raw_bits]]; \
}
OutputPixel(0);
OutputPixel(1);
OutputPixel(2);
OutputPixel(3);
OutputPixel(4);
OutputPixel(5);
OutputPixel(6);
OutputPixel(7);
OutputPixel(8);
OutputPixel(9);
OutputPixel(10);
OutputPixel(11);
OutputPixel(12);
OutputPixel(13);
#undef OutputPixel
return target + 14;
}

View File

@@ -0,0 +1,213 @@
//
// Video.hpp
// Clock Signal
//
// Created by Thomas Harte on 31/10/2020.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#ifndef Apple_IIgs_Video_hpp
#define Apple_IIgs_Video_hpp
#include "../AppleII/VideoSwitches.hpp"
#include "../../../Outputs/CRT/CRT.hpp"
#include "../../../ClockReceiver/ClockReceiver.hpp"
namespace Apple {
namespace IIgs {
namespace Video {
/*!
Provides IIgs video output; assumed clocking here is seven times the usual Apple II clock.
So it'll produce a single line of video every 456 cycles — 65*7 + 1, allowing for the
stretched cycle.
*/
class Video: public Apple::II::VideoSwitches<Cycles> {
public:
Video();
void set_internal_ram(const uint8_t *);
bool get_is_vertical_blank(Cycles offset);
uint8_t get_horizontal_counter(Cycles offset);
uint8_t get_vertical_counter(Cycles offset);
void set_new_video(uint8_t);
uint8_t get_new_video();
void clear_interrupts(uint8_t);
uint8_t get_interrupt_register();
void set_interrupt_register(uint8_t);
bool get_interrupt_line();
void notify_clock_tick();
void set_border_colour(uint8_t);
void set_text_colour(uint8_t);
uint8_t get_text_colour();
uint8_t get_border_colour();
void set_composite_is_colour(bool);
bool get_composite_is_colour();
/// Sets the scan target.
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
/// Gets the current scan status.
Outputs::Display::ScanStatus get_scaled_scan_status() const;
/// Sets the type of output.
void set_display_type(Outputs::Display::DisplayType);
/// Gets the type of output.
Outputs::Display::DisplayType get_display_type() const;
/// Determines the period until video might autonomously update its interrupt lines.
Cycles get_next_sequence_point() const;
/// Sets the Mega II interrupt enable state — 1/4-second and VBL interrupts are
/// generated here.
void set_megaii_interrupts_enabled(uint8_t);
uint8_t get_megaii_interrupt_status();
void clear_megaii_interrupts();
private:
Outputs::CRT::CRT crt_;
// This is coupled to Apple::II::GraphicsMode, but adds detail for the IIgs.
enum class GraphicsMode {
Text = 0,
DoubleText,
HighRes,
DoubleHighRes,
LowRes,
DoubleLowRes,
FatLowRes,
// Additions:
DoubleHighResMono,
SuperHighRes
};
constexpr bool is_colour_ntsc(GraphicsMode m) { return m >= GraphicsMode::HighRes && m <= GraphicsMode::FatLowRes; }
GraphicsMode graphics_mode(int row) const {
if(new_video_ & 0x80) {
return GraphicsMode::SuperHighRes;
}
const auto ii_mode = Apple::II::VideoSwitches<Cycles>::graphics_mode(row);
switch(ii_mode) {
// Coupling very much assumed here.
case Apple::II::GraphicsMode::DoubleHighRes:
if(new_video_ & 0x20) {
return GraphicsMode::DoubleHighResMono;
}
[[fallthrough]];
default: return GraphicsMode(int(ii_mode)); break;
}
}
enum class PixelBufferFormat {
Text, DoubleText, NTSC, NTSCMono, SuperHighRes
};
constexpr PixelBufferFormat format_for_mode(GraphicsMode m) {
switch(m) {
case GraphicsMode::Text: return PixelBufferFormat::Text;
case GraphicsMode::DoubleText: return PixelBufferFormat::DoubleText;
default: return PixelBufferFormat::NTSC;
case GraphicsMode::DoubleHighResMono: return PixelBufferFormat::NTSCMono;
case GraphicsMode::SuperHighRes: return PixelBufferFormat::SuperHighRes;
}
}
void advance(Cycles);
uint8_t new_video_ = 0x01;
uint8_t interrupts_ = 0x00;
void set_interrupts(uint8_t);
int cycles_into_frame_ = 0;
const uint8_t *ram_ = nullptr;
// The modal colours.
uint16_t border_colour_ = 0;
uint8_t border_colour_entry_ = 0;
uint8_t text_colour_entry_ = 0xf0;
uint16_t text_colour_ = 0xffff;
uint16_t background_colour_ = 0;
// Current pixel output buffer and conceptual format.
PixelBufferFormat pixels_format_;
uint16_t *pixels_ = nullptr, *next_pixel_ = nullptr;
int pixels_start_column_;
void output_row(int row, int start, int end);
uint16_t *output_super_high_res(uint16_t *target, int start, int end, int row) const;
uint16_t *output_text(uint16_t *target, int start, int end, int row) const;
uint16_t *output_double_text(uint16_t *target, int start, int end, int row) const;
uint16_t *output_char(uint16_t *target, uint8_t source, int row) const;
uint16_t *output_low_resolution(uint16_t *target, int start, int end, int row);
uint16_t *output_fat_low_resolution(uint16_t *target, int start, int end, int row);
uint16_t *output_double_low_resolution(uint16_t *target, int start, int end, int row);
uint16_t *output_high_resolution(uint16_t *target, int start, int end, int row);
uint16_t *output_double_high_resolution(uint16_t *target, int start, int end, int row);
uint16_t *output_double_high_resolution_mono(uint16_t *target, int start, int end, int row);
// Super high-res per-line state.
uint8_t line_control_;
uint16_t palette_[16];
// Storage used for fill mode.
uint16_t *palette_zero_[4] = {nullptr, nullptr, nullptr, nullptr}, palette_throwaway_;
// Lookup tables and state to assist in the IIgs' mapping from NTSC to RGB.
//
// My understanding of the real-life algorithm is: maintain a four-bit buffer.
// Fill it in a circular fashion. Ordinarily, output the result of looking
// up the RGB mapping of those four bits of Apple II output (which outputs four
// bits per NTSC colour cycle), commuted as per current phase. But if the bit
// being inserted differs from that currently in its position in the shift
// register, hold the existing output for three shifts.
//
// From there I am using the following:
// Maps from:
//
// b0 = b0 of the shift register
// b1 = b4 of the shift register
// b2 = current delay count
//
// to a new delay count.
uint8_t ntsc_delay_lookup_[20];
uint32_t ntsc_shift_ = 0; // Assumption here: logical shifts will ensue, rather than arithmetic.
int ntsc_delay_ = 0;
/// Outputs the lowest 14 bits from @c ntsc_shift_, mapping to RGB.
/// Phase is derived from @c column.
uint16_t *output_shift(uint16_t *target, int column);
// Common getter for the two counters.
struct Counters {
Counters(int v, int h) : vertical(v), horizontal(h) {}
const int vertical, horizontal;
};
Counters get_counters(Cycles offset);
// Marshalls the Mega II-style interrupt state.
uint8_t megaii_interrupt_mask_ = 0;
uint8_t megaii_interrupt_state_ = 0;
int megaii_frame_counter_ = 0; // To count up to quarter-second interrupts.
};
}
}
}
#endif /* Video_hpp */

View File

@@ -25,7 +25,7 @@ Audio::Audio(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(tas
void Audio::post_sample(uint8_t sample) {
// Store sample directly indexed by current write pointer; this ensures that collected samples
// directly map to volume and enabled/disabled states.
sample_queue_.buffer[sample_queue_.write_pointer] = sample;
sample_queue_.buffer[sample_queue_.write_pointer].store(sample, std::memory_order::memory_order_relaxed);
sample_queue_.write_pointer = (sample_queue_.write_pointer + 1) % sample_queue_.buffer.size();
}
@@ -80,7 +80,7 @@ void Audio::get_samples(std::size_t number_of_samples, int16_t *target) {
// Determine the output level, and output that many samples.
// (Hoping that the copiler substitutes an effective memset16-type operation here).
const int16_t output_level = volume_multiplier_ * (int16_t(sample_queue_.buffer[sample_queue_.read_pointer]) - 128);
const int16_t output_level = volume_multiplier_ * (int16_t(sample_queue_.buffer[sample_queue_.read_pointer].load(std::memory_order::memory_order_relaxed)) - 128);
for(size_t c = 0; c < cycles_left_in_sample; ++c) {
target[c] = output_level;
}

View File

@@ -63,7 +63,7 @@ class Audio: public ::Outputs::Speaker::SampleSource {
// A queue of fetched samples; read from by one thread,
// written to by another.
struct {
std::array<uint8_t, 740> buffer;
std::array<std::atomic<uint8_t>, 740> buffer;
size_t read_pointer = 0, write_pointer = 0;
} sample_queue_;

View File

@@ -8,6 +8,36 @@
#include "DriveSpeedAccumulator.hpp"
namespace {
/*
For knowledge encapsulate below, all credit goes to the MAME team. No original research here.
Per their investigation, the bytes collected for PWM output feed a 6-bit LFSR, which then keeps
output high until it eventually reaches a state of 0x20. The LFSR shifts rightward and taps bits
0 and 1 as the new input into bit 5.
I've therefore implemented the LFSR as below, feeding into a lookup table to calculate actual
pulse widths from the values stored into the PWM buffer.
*/
template<uint8_t value> constexpr uint8_t lfsr() {
if constexpr (value == 0x20 || !value) return 0;
return 1+lfsr<(((value ^ (value >> 1))&1) << 5) | (value >> 1)>();
}
constexpr uint8_t pwm_lookup[] = {
lfsr<0>(), lfsr<1>(), lfsr<2>(), lfsr<3>(), lfsr<4>(), lfsr<5>(), lfsr<6>(), lfsr<7>(),
lfsr<8>(), lfsr<9>(), lfsr<10>(), lfsr<11>(), lfsr<12>(), lfsr<13>(), lfsr<14>(), lfsr<15>(),
lfsr<16>(), lfsr<17>(), lfsr<18>(), lfsr<19>(), lfsr<20>(), lfsr<21>(), lfsr<22>(), lfsr<23>(),
lfsr<24>(), lfsr<25>(), lfsr<26>(), lfsr<27>(), lfsr<28>(), lfsr<29>(), lfsr<30>(), lfsr<31>(),
lfsr<32>(), lfsr<33>(), lfsr<34>(), lfsr<35>(), lfsr<36>(), lfsr<37>(), lfsr<38>(), lfsr<39>(),
lfsr<40>(), lfsr<41>(), lfsr<42>(), lfsr<43>(), lfsr<44>(), lfsr<45>(), lfsr<46>(), lfsr<47>(),
lfsr<48>(), lfsr<49>(), lfsr<50>(), lfsr<51>(), lfsr<52>(), lfsr<53>(), lfsr<54>(), lfsr<55>(),
lfsr<56>(), lfsr<57>(), lfsr<58>(), lfsr<59>(), lfsr<60>(), lfsr<61>(), lfsr<62>(), lfsr<63>(),
};
}
using namespace Apple::Macintosh;
void DriveSpeedAccumulator::post_sample(uint8_t sample) {
@@ -17,40 +47,33 @@ void DriveSpeedAccumulator::post_sample(uint8_t sample) {
// the samples until there is a certain small quantity of them,
// then produce a new estimate of rotation speed and start the
// buffer afresh.
samples_[sample_pointer_] = sample;
++sample_pointer_;
if(sample_pointer_ == samples_.size()) {
sample_pointer_ = 0;
//
// Note the table lookup here; see text above.
sample_total_ += pwm_lookup[sample & 0x3f];
++sample_count_;
if(sample_count_ == samples_per_bucket) {
// The below fits for a function like `a + bc`; it encapsultes the following
// beliefs:
//
// (i) motor speed is proportional to voltage supplied;
// (ii) with pulse-width modulation it's therefore proportional to the duty cycle;
// (iii) the Mac pulse-width modulates whatever it reads from the disk speed buffer;
// (iii) the Mac pulse-width modulates whatever it reads from the disk speed buffer, as per the LFSR rules above;
// (iv) ... subject to software pulse-width modulation of that pulse-width modulation.
//
// So, I believe current motor speed is proportional to a low-pass filtering of
// the speed buffer. Which I've implemented very coarsely via 'large' bucketed-averages,
// noting also that exact disk motor speed is always a little approximate.
// Sum all samples.
// TODO: if the above is the correct test, do it on sample receipt rather than
// bothering with an intermediate buffer.
int sum = 0;
for(auto s: samples_) {
sum += s;
}
// The formula below was derived from observing values the Mac wrote into its
// disk-speed buffer. Given that it runs a calibration loop before doing so,
// I cannot guarantee the accuracy of these numbers beyond being within the
// range that the computer would accept.
const float normalised_sum = float(sum) / float(samples_.size());
const float rotation_speed = (normalised_sum * 27.08f) - 259.0f;
const float normalised_sum = float(sample_total_) / float(samples_per_bucket);
const float rotation_speed = (normalised_sum - 3.7f) * 17.6f;
delegate_->drive_speed_accumulator_set_drive_speed(this, rotation_speed);
sample_count_ = 0;
sample_total_ = 0;
}
}

View File

@@ -34,8 +34,9 @@ class DriveSpeedAccumulator {
}
private:
std::array<uint8_t, 20> samples_;
std::size_t sample_pointer_ = 0;
static constexpr int samples_per_bucket = 20;
int sample_count_ = 0;
int sample_total_ = 0;
Delegate *delegate_ = nullptr;
};

View File

@@ -13,7 +13,6 @@
#include "DeferredAudio.hpp"
#include "DriveSpeedAccumulator.hpp"
#include "Keyboard.hpp"
#include "RealTimeClock.hpp"
#include "Video.hpp"
#include "../../MachineTypes.hpp"
@@ -32,6 +31,7 @@
#include "../../../Components/5380/ncr5380.hpp"
#include "../../../Components/6522/6522.hpp"
#include "../../../Components/8530/z8530.hpp"
#include "../../../Components/AppleClock/AppleClock.hpp"
#include "../../../Components/DiskII/IWM.hpp"
#include "../../../Components/DiskII/MacintoshDoubleDensityDrive.hpp"
#include "../../../Processors/68000/68000.hpp"
@@ -528,7 +528,10 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
const auto options = dynamic_cast<Options *>(str.get());
quickboot_ = options->quickboot;
if(quickboot_) {
using Model = Analyser::Static::Macintosh::Target::Model;
const bool is_plus_rom = model == Model::Mac512ke || model == Model::MacPlus;
if(quickboot_ && is_plus_rom) {
// Cf. Big Mess o' Wires' disassembly of the Mac Plus ROM, and the
// test at $E00. TODO: adapt as(/if?) necessary for other Macs.
ram_[0x02ae] = 0x40;
@@ -644,11 +647,11 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
return mouse_;
}
using IWMActor = JustInTimeActor<IWM, 1, 1, HalfCycles, Cycles>;
using IWMActor = JustInTimeActor<IWM>;
class VIAPortHandler: public MOS::MOS6522::PortHandler {
public:
VIAPortHandler(ConcreteMachine &machine, RealTimeClock &clock, Keyboard &keyboard, DeferredAudio &audio, IWMActor &iwm, Inputs::QuadratureMouse &mouse) :
VIAPortHandler(ConcreteMachine &machine, Apple::Clock::SerialClock &clock, Keyboard &keyboard, DeferredAudio &audio, IWMActor &iwm, Inputs::QuadratureMouse &mouse) :
machine_(machine), clock_(clock), keyboard_(keyboard), audio_(audio), iwm_(iwm), mouse_(mouse) {}
using Port = MOS::MOS6522::Port;
@@ -751,7 +754,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
private:
ConcreteMachine &machine_;
RealTimeClock &clock_;
Apple::Clock::SerialClock &clock_;
Keyboard &keyboard_;
DeferredAudio &audio_;
IWMActor &iwm_;
@@ -766,7 +769,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
DeferredAudio audio_;
Video video_;
RealTimeClock clock_;
Apple::Clock::SerialClock clock_;
Keyboard keyboard_;
MOS::MOS6522::MOS6522<VIAPortHandler> via_;

View File

@@ -1,173 +0,0 @@
//
// RealTimeClock.hpp
// Clock Signal
//
// Created by Thomas Harte on 07/05/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef RealTimeClock_hpp
#define RealTimeClock_hpp
#include "../../Utility/MemoryFuzzer.hpp"
namespace Apple {
namespace Macintosh {
/*!
Models the storage component of Apple's real-time clock.
Since tracking of time is pushed to this class, it is assumed
that whomever is translating real time into emulated time
will notify the VIA of a potential interrupt.
*/
class RealTimeClock {
public:
RealTimeClock() {
// TODO: this should persist, if possible, rather than
// being default initialised.
const uint8_t default_data[] = {
0xa8, 0x00, 0x00, 0x00,
0xcc, 0x0a, 0xcc, 0x0a,
0x00, 0x00, 0x00, 0x00,
0x00, 0x02, 0x63, 0x00,
0x03, 0x88, 0x00, 0x4c
};
memcpy(data_, default_data, sizeof(data_));
}
/*!
Advances the clock by 1 second.
The caller should also notify the VIA.
*/
void update() {
for(int c = 0; c < 4; ++c) {
++seconds_[c];
if(seconds_[c]) break;
}
}
/*!
Sets the current clock and data inputs to the clock.
*/
void set_input(bool clock, bool data) {
/*
Documented commands:
z0000001 Seconds register 0 (lowest order byte)
z0000101 Seconds register 1
z0001001 Seconds register 2
z0001101 Seconds register 3
00110001 Test register (write only)
00110101 Write-protect register (write only)
z010aa01 RAM addresses 0x10 - 0x13
z1aaaa01 RAM addresses 0x00 0x0f
z = 1 => a read; z = 0 => a write.
The top bit of the write-protect register enables (0) or disables (1)
writes to other locations.
All the documentation says about the test register is to set the top
two bits to 0 for normal operation. Abnormal operation is undefined.
The data line is valid when the clock transitions to level 0.
*/
if(clock && !previous_clock_) {
// Shift into the command_ register, no matter what.
command_ = uint16_t((command_ << 1) | (data ? 1 : 0));
result_ <<= 1;
// Increment phase.
++phase_;
// When phase hits 8, inspect the command.
// If it's a read, prepare a result.
if(phase_ == 8) {
if(command_ & 0x80) {
// A read.
const auto address = (command_ >> 2) & 0x1f;
// Begin pessimistically.
result_ = 0xff;
if(address < 4) {
result_ = seconds_[address];
} else if(address >= 0x10) {
result_ = data_[address & 0xf];
} else if(address >= 0x8 && address <= 0xb) {
result_ = data_[0x10 + (address & 0x3)];
}
}
}
// If phase hits 16 and this was a read command,
// just stop. If it was a write command, do the
// actual write.
if(phase_ == 16) {
if(!(command_ & 0x8000)) {
// A write.
const auto address = (command_ >> 10) & 0x1f;
const uint8_t value = uint8_t(command_ & 0xff);
// First test: is this to the write-protect register?
if(address == 0xd) {
write_protect_ = value;
}
// No other writing is permitted if the write protect
// register won't allow it.
if(!(write_protect_ & 0x80)) {
if(address < 4) {
seconds_[address] = value;
} else if(address >= 0x10) {
data_[address & 0xf] = value;
} else if(address >= 0x8 && address <= 0xb) {
data_[0x10 + (address & 0x3)] = value;
}
}
}
// A phase of 16 always ends the command, so reset here.
abort();
}
}
previous_clock_ = clock;
}
/*!
Reads the current data output level from the clock.
*/
bool get_data() {
return !!(result_ & 0x80);
}
/*!
Announces that a serial command has been aborted.
*/
void abort() {
result_ = 0;
phase_ = 0;
command_ = 0;
}
private:
uint8_t data_[0x14];
uint8_t seconds_[4];
uint8_t write_protect_;
int phase_ = 0;
uint16_t command_;
uint8_t result_ = 0;
bool previous_clock_ = false;
};
}
}
#endif /* RealTimeClock_hpp */

View File

@@ -29,7 +29,17 @@ Video::Video(DeferredAudio &audio, DriveSpeedAccumulator &drive_speed_accumulato
crt_(704, 1, 370, 6, Outputs::Display::InputDataType::Luminance1) {
crt_.set_display_type(Outputs::Display::DisplayType::RGB);
// UGLY HACK. UGLY, UGLY HACK. UGLY!
// The OpenGL scan target fails properly to place visible areas which are not 4:3.
// The [newer] Metal scan target has no such issue. So assume that Apple => Metal,
// and set a visible area to work around the OpenGL issue if required.
// TODO: eliminate UGLY HACK.
#ifdef __APPLE__
crt_.set_visible_area(Outputs::Display::Rect(0.08f, 10.0f / 368.0f, 0.82f, 344.0f / 368.0f));
#else
crt_.set_visible_area(Outputs::Display::Rect(0.08f, -0.025f, 0.82f, 0.82f));
#endif
crt_.set_aspect_ratio(1.73f); // The Mac uses a non-standard scanning area.
}
@@ -105,10 +115,13 @@ void Video::run_for(HalfCycles duration) {
pixel_buffer_ += 16;
}
} else {
video_address_ += size_t(final_pixel_word - first_word);
}
if(final_pixel_word == 32) {
crt_.output_data(512);
pixel_buffer_ = nullptr;
}
}

View File

@@ -75,7 +75,7 @@ template<class T> class Cartridge:
cycles_since_6532_update_ += Cycles(cycles_run_for / 3);
bus_extender_.advance_cycles(cycles_run_for / 3);
if(operation != CPU::MOS6502::BusOperation::Ready) {
if(isAccessOperation(operation)) {
// give the cartridge a chance to respond to the bus access
bus_extender_.perform_bus_operation(operation, address, value);

View File

@@ -347,18 +347,11 @@ class ConcreteMachine:
update_audio();
if(cycle.operation & Microcycle::Read) {
ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BC1));
cycle.set_value8_high(ay_.get_data_output());
ay_.set_control_lines(GI::AY38910::ControlLines(0));
cycle.set_value8_high(GI::AY38910::Utility::read(ay_));
} else {
// Net effect here: addresses with bit 1 set write to a register,
// addresses with bit 1 clear select a register.
ay_.set_control_lines(GI::AY38910::ControlLines(
GI::AY38910::BC2 | GI::AY38910::BDIR
| ((address&2) ? 0 : GI::AY38910::BC1)
));
ay_.set_data_input(cycle.value8_high());
ay_.set_control_lines(GI::AY38910::ControlLines(0));
GI::AY38910::Utility::write(ay_, address&2, cycle.value8_high());
}
return delay + HalfCycles(2);
@@ -465,16 +458,14 @@ class ConcreteMachine:
}
// Update the video output, checking whether a sequence point has been hit.
while(length >= cycles_until_video_event_) {
length -= cycles_until_video_event_;
video_ += cycles_until_video_event_;
cycles_until_video_event_ = video_->get_next_sequence_point();
assert(cycles_until_video_event_ > HalfCycles(0));
if(video_.will_flush(length)) {
length -= video_.cycles_until_implicit_flush();
video_ += video_.cycles_until_implicit_flush();
mfp_->set_timer_event_input(1, video_->display_enabled());
update_interrupt_input();
}
cycles_until_video_event_ -= length;
video_ += length;
}
@@ -486,12 +477,11 @@ class ConcreteMachine:
HalfCycles bus_phase_;
JustInTimeActor<Video> video_;
HalfCycles cycles_until_video_event_;
// The MFP runs at 819200/2673749ths of the CPU clock rate.
JustInTimeActor<Motorola::MFP68901::MFP68901, 819200, 2673749> mfp_;
JustInTimeActor<Motorola::ACIA::ACIA, 16> keyboard_acia_;
JustInTimeActor<Motorola::ACIA::ACIA, 16> midi_acia_;
JustInTimeActor<Motorola::MFP68901::MFP68901, HalfCycles, 819200, 2673749> mfp_;
JustInTimeActor<Motorola::ACIA::ACIA, HalfCycles, 16> keyboard_acia_;
JustInTimeActor<Motorola::ACIA::ACIA, HalfCycles, 16> midi_acia_;
Concurrency::DeferringAsyncTaskQueue audio_queue_;
GI::AY38910::AY38910<false> ay_;

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) {

Some files were not shown because too many files have changed in this diff Show More