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

Compare commits

...

1629 Commits

Author SHA1 Message Date
Thomas Harte
bdf0a1941c Merge pull request #1002 from TomHarte/FastBlitterFills
Switch to a table-based implementation of fill mode.
2021-12-19 17:35:27 -05:00
Thomas Harte
d0e3024bec Switch to nibble-oriented lookup tables for fill mode. 2021-12-19 17:16:46 -05:00
Thomas Harte
d2ad149e56 Fill mode always runs right to left. 2021-12-19 16:43:18 -05:00
Thomas Harte
ad602a4722 Merge pull request #1001 from TomHarte/AmigaReadWrite
Ensures Chipset reads can map to writes and vice versa.
2021-12-19 16:35:43 -05:00
Thomas Harte
348840a2aa It's probably a net detriment to use a template in this scenario. 2021-12-19 16:31:44 -05:00
Thomas Harte
3a719633eb Consolidate interface; correct LOGs. 2021-12-18 19:39:41 -05:00
Thomas Harte
bd69948d37 The Copper can now skip Chipset::perform. 2021-12-18 17:53:11 -05:00
Thomas Harte
54aa211f56 Avoid infinite loops for completely undefined addresses. 2021-12-18 17:48:45 -05:00
Thomas Harte
f118891970 Breaks Chipset::perform into read and write.
This allows each to call the other when a read occurs of a write-only address, and vice versa.
2021-12-18 17:43:53 -05:00
Thomas Harte
c4055fde97 Merge pull request #1000 from TomHarte/CopperTests
Amiga: regularises timing; improves Copper sleep/wait costs
2021-12-18 16:46:53 -05:00
Thomas Harte
dbae3fc9a5 Propagate to bitplanes immediately; fix odd/even confusion. 2021-12-18 16:37:40 -05:00
Thomas Harte
7c73ed7ed5 Bump Xcode version number. 2021-12-18 14:55:27 -05:00
Thomas Harte
c834960bfb Withdraw separate x-and-y guess, make MOVE lose a cycle if a sleep/wake occurs. 2021-12-12 19:18:18 -05:00
Thomas Harte
600abc55b5 Compare x and y separately, wake immediately from a sleep, log more. 2021-12-12 17:26:33 -05:00
Thomas Harte
f3ec7d54bb Clarifies wait-for-CPU-slot semantics.
Big bonus: this guarantees `advance_dma`s will be called at most once per output cycle, even if they return `false`.
2021-12-09 19:17:44 -05:00
Thomas Harte
090760e526 Merge pull request #998 from TomHarte/QtAmiga
Add the Amiga to the Qt UI.
2021-12-08 13:45:34 -05:00
Thomas Harte
cccde7dc89 Correct given memory size. 2021-12-08 11:41:50 -05:00
Thomas Harte
849e48f519 Add the Amiga to Qt's UI. 2021-12-08 11:41:38 -05:00
Thomas Harte
1c3935eb40 Add README.md
As a warning.
2021-12-07 18:19:51 -05:00
Thomas Harte
466bed3163 Merge pull request #994 from TomHarte/AmigaREADME
Fess up to the Amiga.
2021-12-07 04:32:43 -05:00
Thomas Harte
641a9c72e9 Fess up to the Amiga. 2021-12-07 04:30:54 -05:00
Thomas Harte
5138216ba1 Merge pull request #978 from TomHarte/Amiga
Introduces nascent Amiga emulation
2021-12-07 04:18:53 -05:00
Thomas Harte
de1f5686a8 Reenable hardened runtime. 2021-12-07 04:05:10 -05:00
Thomas Harte
c983678fcd Reenable app sandbox. 2021-12-07 03:57:58 -05:00
Thomas Harte
2b0415d552 Attempt to avoid off-by-one buffer reads, add modulation. 2021-12-06 19:28:40 -05:00
Thomas Harte
066e4421e8 Attempt volcntrld. 2021-12-06 06:35:08 -05:00
Thomas Harte
f02a241249 Inserts an additional reload. 2021-12-05 17:47:12 -05:00
Thomas Harte
a5fe1e4259 Largely debugs audio state machine.
I think I'm still missing an address reload somewhere though, and attachment doesn't actually push.
2021-12-05 15:27:35 -05:00
Thomas Harte
9b80563443 Exposes targets for modulation. 2021-12-05 06:38:55 -05:00
Thomas Harte
91b5da06e3 Perform reload on Disabled -> WaitingForDummyDMA. 2021-12-04 19:17:40 -05:00
Thomas Harte
7320f96ae7 Capture attachment flags. 2021-12-04 18:02:43 -05:00
Thomas Harte
fdf2b9cd7b Add local data pointers. 2021-12-04 17:58:41 -05:00
Thomas Harte
bfc70a1b60 Ensure interrupt request bits always propagate. 2021-12-04 16:50:42 -05:00
Thomas Harte
aff7a93106 Move DMAFlags to Flags.hpp. 2021-12-04 08:26:28 -05:00
Thomas Harte
3b027c4593 Switch and -> or for testing transitions from ::PlayingLow. 2021-12-04 08:24:41 -05:00
Thomas Harte
42d3bdd373 Adds a begin_state template. 2021-12-04 07:20:17 -05:00
Thomas Harte
57789092c1 Keep audio fetches in bounds. 2021-12-03 07:16:21 -05:00
Thomas Harte
6bc5268cbd Reload period counter on low -> high transition. 2021-12-02 18:43:02 -05:00
Thomas Harte
887ab705d1 Add missing <cassert>. 2021-12-02 13:00:25 -05:00
Thomas Harte
ff6ddaed2e Full scale is 65536. 2021-12-02 12:55:11 -05:00
Thomas Harte
e6fe36f45c Add buffer-length assert; add <tuple> where std::tuple_size is used. 2021-12-02 12:53:20 -05:00
Thomas Harte
3a26f6b8bf Ensure full buffer provision. 2021-12-02 12:52:43 -05:00
Thomas Harte
06b6f85d55 Correct stereo. 2021-12-02 11:15:29 -05:00
Thomas Harte
d6f1ea50a6 Switch to slightly more straightforward presumption of no data wanted. 2021-12-02 09:41:16 -05:00
Thomas Harte
9554869886 Simplify DMA logic. 2021-12-02 09:33:02 -05:00
Thomas Harte
364059551c Add extra notes per errata, plus bonus state code repetitions. 2021-12-02 09:30:52 -05:00
Thomas Harte
06340b1ad7 Advance DMA pointer, treat audio as signed, request data on low -> high transition.
There's now some audio, sometimes when there should be. But it's not correct.
2021-12-01 18:34:54 -05:00
Thomas Harte
d23511860d Attempts audio output. 2021-12-01 06:01:58 -05:00
Thomas Harte
a8dd4660b2 Adds a pipeline for audio output. 2021-12-01 05:37:58 -05:00
Thomas Harte
eb3a0eb3c7 Attempt full implementation of collisions. 2021-11-29 18:39:33 -05:00
Thomas Harte
cd0148e0bc Switch to a default 1mb of Chip RAM. 2021-11-29 16:55:45 -05:00
Thomas Harte
8584ee609f Support a fetch window start on line 0. 2021-11-28 05:37:49 -05:00
Thomas Harte
373847e2b7 Avoid posting redundant key events. 2021-11-28 05:31:00 -05:00
Thomas Harte
84f7d8dfc2 Factors out pixel generation, adds HAM. 2021-11-28 05:06:30 -05:00
Thomas Harte
e057a7d0dd Attempts to implement sprite/playfield priorities. 2021-11-27 15:03:46 -05:00
Thomas Harte
7bab15bf99 Minor copy improvements. 2021-11-27 11:38:41 -05:00
Thomas Harte
dac40630fd Adds support for the Blitter-busy flag to WAIT and SKIP. 2021-11-27 11:36:15 -05:00
Thomas Harte
33bfa1b81c Move BitplaneShifter adjacent to expand_bitplane_byte. 2021-11-26 18:29:09 -05:00
Thomas Harte
8fc27dc292 Moves bitplane collection and shifter out of Chipset.[h/c]pp. 2021-11-26 18:16:24 -05:00
Thomas Harte
f8e8f18be5 Switch to std::clamp. 2021-11-26 18:10:29 -05:00
Thomas Harte
8b38c567d2 Add missing #include for std::clamp. 2021-11-26 18:08:39 -05:00
Thomas Harte
cd53e42d79 Resolve operator precedence. 2021-11-26 18:08:10 -05:00
Thomas Harte
bea6cf2038 Move mouse and joystick into a separate file, give a common parent. 2021-11-26 17:50:47 -05:00
Thomas Harte
eca80f1425 Sprites: avoid magic constants, ensure proper DMA resumption. 2021-11-26 16:02:18 -05:00
Thomas Harte
1c0962e53c Move sprites into their own source file. 2021-11-26 15:30:31 -05:00
Thomas Harte
4b21549ff4 Add a couple of static asserts. 2021-11-26 15:23:54 -05:00
Thomas Harte
30d7b0129b Correct sprite ordering within pairs. 2021-11-26 11:58:50 -05:00
Thomas Harte
ce6877d6e4 Sprites: infer part of DMA state from slot, no access during blank.
Also sets the proper vertical blank length.
2021-11-26 09:37:52 -05:00
Thomas Harte
0ab5177637 Allow DMAState::FetchStopAndControl on y == v_stop_. 2021-11-25 14:29:12 -05:00
Thomas Harte
276cbfa505 Simplify sprite state machine.
This now better matches the explanation given on Page 133 of the Amiga System Programmer's Guide.
2021-11-25 14:08:55 -05:00
Thomas Harte
610c85a354 Correct test logic.
All tests now pass.
2021-11-25 04:11:20 -05:00
Thomas Harte
012084b37b Fix exclusive fill, sizing, eliminate ECS call-ins.
The clock test now proceeds further, but still doesn't seem to pass.
2021-11-24 17:25:32 -05:00
Thomas Harte
55af6681af Avoid unnecessary get_port_input calls. 2021-11-24 17:15:48 -05:00
Thomas Harte
2a7a42ff8f Add header for assert. 2021-11-24 16:28:18 -05:00
Thomas Harte
7af5737ec5 Switch to LOG. 2021-11-24 16:15:40 -05:00
Thomas Harte
0ad1529f3f Retain delegate bit length for non-self-clocked data. 2021-11-24 16:15:27 -05:00
Thomas Harte
0df8173536 Merge branch 'master' into Amiga 2021-11-24 08:58:03 -05:00
Thomas Harte
b517811e2f Merge pull request #988 from TomHarte/HeaderOnly6502
Moves the 6502 towards being a header-only dependency.
2021-11-24 08:57:45 -05:00
Thomas Harte
83d3a9c6dd Merge branch 'master' into HeaderOnly6502 2021-11-24 08:48:36 -05:00
Thomas Harte
d0402261e6 Merge pull request #993 from TomHarte/PushAudio
Adds a push route for lowpass-filtered audio.
2021-11-24 08:47:10 -05:00
Thomas Harte
6f6e09d200 Correct: load -> store. 2021-11-22 15:18:12 -05:00
Thomas Harte
24e2fd4184 Avoid implicit conversion. 2021-11-22 11:28:02 -05:00
Thomas Harte
1aada996dc Correct consting. 2021-11-22 11:18:17 -05:00
Thomas Harte
f5d3d6bcea Splits the lowpass filter into push and pull variants. 2021-11-21 15:37:29 -05:00
Thomas Harte
a8a99f647f Further improves framing. 2021-11-21 08:13:55 -05:00
Thomas Harte
ff68b26c44 Push HSYNC 11 slots over, to its proper position, and add a frame crop. 2021-11-20 12:39:50 -05:00
Thomas Harte
a94b4f62fd Takes a stab at attached sprites. 2021-11-19 14:19:47 -05:00
Thomas Harte
bcc959d938 Sprites: deconflate vertical and modification flags; disarm on CTL not POS. 2021-11-19 08:03:10 -05:00
Thomas Harte
cf25d8a378 Increase logging (but leave it disabled). 2021-11-19 08:01:23 -05:00
Thomas Harte
c750bdafd5 Switch to a saturating conversion. 2021-11-18 18:01:30 -05:00
Thomas Harte
693d46f8ea Mask by index, not colour. 2021-11-18 05:36:38 -05:00
Thomas Harte
3496ebd1d7 Constrain sprite fetches to Chip RAM. 2021-11-17 17:49:42 -05:00
Thomas Harte
be763cf7fe Expose joystick to the world. 2021-11-17 15:33:46 -05:00
Thomas Harte
c3b4bee210 Adds a joystick class. 2021-11-17 14:26:51 -05:00
Thomas Harte
6df0227ab1 Hacks in a basic effort at dual playfields. 2021-11-16 18:26:27 -05:00
Thomas Harte
2a3a7fa8a0 Reset will_request_interrupt. 2021-11-15 16:00:35 -05:00
Thomas Harte
50a6496399 Avoids over-greedy DMA. 2021-11-15 12:31:15 -05:00
Thomas Harte
c99dee86dd Adds missing low -> high actions, implements more transitions. 2021-11-15 12:29:32 -05:00
Thomas Harte
0c5bb9626b Separates state transitions and tests. 2021-11-15 05:29:28 -05:00
Thomas Harte
a9971917f5 Attempts a translation of Commodore's documentation. 2021-11-14 14:54:33 -05:00
Thomas Harte
4c62611da3 Adds enough state machine to get into the near-incomprehensible stuff on the right. 2021-11-14 10:48:50 -05:00
Thomas Harte
47f36f08fb Switches to a synchronous audio state machine; renames advance -> advance_dma.
I can worry about how to just-in-time things once I better understand the hardware in general.
2021-11-13 15:53:41 -05:00
Thomas Harte
f906bab1a5 Provides feedback on interrupt flags, starts on state machine. 2021-11-13 11:05:39 -05:00
Thomas Harte
fffc03c4e4 Propagates time to the audio subsystem. 2021-11-12 15:30:52 -05:00
Thomas Harte
0f6934a131 This uses Cycles and HalfCycles, so should include ClockReceiver. 2021-11-11 09:24:32 -05:00
Thomas Harte
0a94184d6b Provides a greater wealth of audio data. 2021-11-11 09:24:15 -05:00
Thomas Harte
7be3578497 Adds a target for audio writes. 2021-11-09 07:11:23 -05:00
Thomas Harte
eeaccb8ac0 Implements clear_all_keys. 2021-11-08 17:49:09 -05:00
Thomas Harte
8ef9a932aa Adds inclusive fill test; fixes inclusive fills. 2021-11-07 14:26:13 -08:00
Thomas Harte
31e22e4cfb Provides full serial input. 2021-11-07 05:19:16 -08:00
Thomas Harte
4fc25fb798 Adds basic shift input. 2021-11-07 05:18:54 -08:00
Thomas Harte
941d9a46a2 Makes a better effort at exposition; better implements clocked line. 2021-11-07 05:18:40 -08:00
Thomas Harte
ecfe68d70f Introduce the principle that a Serial::Line can be two-wire — clock + data. 2021-11-06 16:54:20 -07:00
Thomas Harte
c0c2b5e3a9 Post key actions to the nominated serial line.
Albeit that I'm still thinking through whether I want the option of including a clock on Serial::Line. It'd be natural in one sense — there's already one built in — but might weaken Serial::Line's claim to be a one-stop shop for both enqueued and real-time connections without a reasonable bit of extra work.
2021-11-06 12:03:09 -07:00
Thomas Harte
f102d8a4b4 Extend to allow full-[byte/word/dword] writes, in LSB or MSB order. 2021-11-06 12:01:32 -07:00
Thomas Harte
471e13efbc Transcribes keycodes. 2021-11-04 18:54:42 -07:00
Thomas Harte
6d34432988 Starts to build in a serial line for input. 2021-11-04 18:54:28 -07:00
Thomas Harte
d3f0d15732 Merge branch 'master' into Amiga 2021-11-03 19:27:06 -07:00
Thomas Harte
b827b9e33e Add necessary shift storage. 2021-11-03 19:26:45 -07:00
Thomas Harte
29e5ecc282 Add TODOs rather than complete stop on shift register acccesses. 2021-11-02 18:19:31 -07:00
Thomas Harte
c9bf2dda16 Attempt implementation of disk sync. 2021-11-02 18:18:59 -07:00
Thomas Harte
3ceb378b9b Relocate disk logic into a separate compilation unit. 2021-11-02 17:35:23 -07:00
Thomas Harte
1cf1c90511 Adds support for interlaced output. 2021-11-02 14:34:03 -07:00
Thomas Harte
491b9f83f2 Merge pull request #990 from mariuszkurek/master
Make SDL and Qt binary names consistent
2021-11-02 13:15:27 -07:00
Thomas Harte
d989825216 Add bonus notes on VPOSR. 2021-11-02 03:47:39 -07:00
Thomas Harte
3976420b88 Retains a little more of output controls. 2021-11-01 17:15:36 -07:00
Thomas Harte
2f1ce5fe43 Switch to using the swizzled palette for playfield output. 2021-11-01 14:44:30 -07:00
Thomas Harte
42145a5b8a Delay bitplane installation until end of slot. 2021-11-01 14:18:58 -07:00
mariuszkurek
04f4536cb2 Make SDL and Qt binary names consistent 2021-11-01 09:13:06 +01:00
Thomas Harte
4e66017205 Enable sprite reuse and toggle to inactive when visible region is over. 2021-10-31 16:52:48 -07:00
Thomas Harte
2c1f2edcf2 Introduce failing 'clock' test case.
i.e. a few seconds of the Workbench 1.0 clock application.
2021-10-31 16:12:51 -07:00
Thomas Harte
299d517449 Performs a first implementation of fill mode. 2021-10-31 14:36:31 -07:00
Thomas Harte
561e73dbd7 Merge branch 'Amiga' of github.com:TomHarte/CLK into Amiga 2021-10-31 14:12:40 -07:00
Thomas Harte
9e6ffaad7d Introduce test case for fill mode. 2021-10-31 14:12:26 -07:00
Thomas Harte
9cded1e92c Introduce test case for fill mode. 2021-10-31 14:08:37 -07:00
Thomas Harte
4c1ab6ff25 Rethinks bitplane stops. 2021-10-31 09:01:38 -07:00
Thomas Harte
16f31cab6a Avoid duplication of CIA select test. 2021-10-30 12:05:18 -07:00
Thomas Harte
02c88e6826 VHPOSR's fields are the other way around. 2021-10-30 12:04:46 -07:00
Thomas Harte
9ecd43238f Correct 8520 TOD setting and getting. 2021-10-30 12:02:43 -07:00
Thomas Harte
5ffe71346c Eliminate interrupt magic constants. 2021-10-29 19:04:06 -07:00
Thomas Harte
d25804f4a2 Throws in official register names. 2021-10-29 14:05:11 -07:00
Thomas Harte
edb75e69cb Implement bitplane modulos. 2021-10-29 11:29:22 -07:00
Thomas Harte
f3e895f17c Tag intended unused parameters. 2021-10-29 06:21:02 -07:00
Thomas Harte
b952d73e83 Disallow programmatic setting of blitter status. 2021-10-29 06:19:57 -07:00
Thomas Harte
07facc0636 Takes a stab at BZERO. 2021-10-28 18:12:46 -07:00
Thomas Harte
da1a69be27 Caps mouse speed.
Also takes another guess at CIA interrupt bits. To no avail.
2021-10-27 18:38:02 -07:00
Thomas Harte
7e31658932 Remove accidental commit. 2021-10-26 21:49:32 -07:00
Thomas Harte
5ebc59dd1f Introduce additional test cases. 2021-10-26 20:58:38 -07:00
Thomas Harte
b10f5ab110 Apply A mask when loading into barrel shifter. 2021-10-26 20:02:28 -07:00
Thomas Harte
b4286bb42b Modulos are subtracted in descending mode. 2021-10-26 07:21:51 -07:00
Thomas Harte
4d7ce3792f Use additional test cases. 2021-10-25 21:48:43 -07:00
Thomas Harte
76767da300 Undo accidental change. 2021-10-25 21:48:19 -07:00
Thomas Harte
dc8701a929 Introduce some additional Blitter test cases. 2021-10-25 21:40:20 -07:00
Thomas Harte
139d35c6f9 Switches to basic use of sprite shifters. 2021-10-25 20:58:48 -07:00
Thomas Harte
cb24457b4a Starts on a two-at-a-time sprite shifter. 2021-10-25 16:30:30 -07:00
Thomas Harte
9f3efb7f05 Limits graphical output to [all but one bit] of the display window. 2021-10-25 14:12:23 -07:00
Thomas Harte
e6001e0f22 Shifts bitplanes irrespective of output window. 2021-10-25 13:59:39 -07:00
Thomas Harte
c6535bf035 Switches bitplane shifter to returning four high-res pixels at a time. 2021-10-25 13:34:36 -07:00
Thomas Harte
7118a515e0 Reduce logging in trustworthy areas. 2021-10-23 20:36:41 -07:00
Thomas Harte
952451c9b8 Add mouse input. 2021-10-23 20:17:13 -07:00
Thomas Harte
610327a04e Fix sprite H start bit order. 2021-10-22 23:20:20 -07:00
Thomas Harte
2121e32409 Fix sprite bit ordering. 2021-10-22 21:10:01 -07:00
Thomas Harte
7ec21edc2f Attempts to hack in some form of sprite display. 2021-10-22 19:51:10 -07:00
Thomas Harte
003162f710 Limit to specific purpose. 2021-10-22 16:16:19 -07:00
Thomas Harte
040ac93042 Takes a shot at the vertical stuff of sprite DMA. 2021-10-22 14:32:59 -07:00
Thomas Harte
b489ba3d0d Adds sprite DMA windows. 2021-10-22 13:07:20 -07:00
Thomas Harte
c5e8b547af Captures the attach flag and observes activation rule. 2021-10-22 11:21:58 -07:00
Thomas Harte
e67de90ad0 Starts to bring sprites inside DMADevice orthodoxy. 2021-10-21 21:57:46 -07:00
Thomas Harte
c3c84c88a1 Switch to ahead-of-time planar to chunky conversion. 2021-10-21 20:48:57 -07:00
Thomas Harte
0dc9c4cee1 Undo hard-coding of fetch window. 2021-10-19 15:18:39 -07:00
Thomas Harte
544c137cb0 Add updated intel. 2021-10-16 13:30:56 -07:00
Thomas Harte
b312a61a81 Add two dummy reads. 2021-10-16 13:30:45 -07:00
Thomas Harte
4917556a99 The shift goes the other way in descending mode. 2021-10-16 11:09:40 -07:00
Thomas Harte
15ed4a0d09 Introduce failing test case for sector decoding. 2021-10-16 10:48:32 -07:00
Thomas Harte
aa6b0f07b7 Correct filename. 2021-10-16 05:37:46 -07:00
Thomas Harte
d9d20d9d30 Walk back slightly. 2021-10-14 18:02:58 -07:00
Thomas Harte
689bfbbdb3 Be overt in initialiser list. 2021-10-14 16:57:26 -07:00
Thomas Harte
e27a10bde4 Simplify control flow. 2021-10-14 16:47:18 -07:00
Thomas Harte
253a199f27 Fire sync-match interrupt upon any match. 2021-10-14 16:36:17 -07:00
Thomas Harte
61e5702520 Remove dead TODO. 2021-10-14 16:09:11 -07:00
Thomas Harte
b12c640807 Makes drives non-copyable.
To avoid error in the future.
2021-10-14 12:37:55 -07:00
Thomas Harte
9be23ecc34 Add end-of-Blit interrupt.
Along with a slightly easier path for posting interrupts, in C++ compilation unit terms.
2021-10-13 15:09:19 -07:00
Thomas Harte
8960f471a0 Use unspread_bits for FM and MFM decoding. 2021-10-12 15:18:50 -07:00
Thomas Harte
955cb6411c Factor out bit spreading.
(And do a better job of it)
2021-10-12 14:49:01 -07:00
Thomas Harte
fc4ca4f8e3 I don't think there are sync words at the start of the track. 2021-10-12 10:38:15 -07:00
Thomas Harte
eec068914e Slightly improve logging. 2021-10-11 18:05:57 -07:00
Thomas Harte
a1f02d0cd8 Add track padding. 2021-10-11 18:05:37 -07:00
Thomas Harte
39b8285ba5 Trust the HRM on step bit, but catch rising edge. 2021-10-11 07:42:42 -07:00
Thomas Harte
7733fef3bd DSKLEN has to be written twice. 2021-10-11 06:16:01 -07:00
Thomas Harte
6acddfdb98 Add the sync match interrupt.
Albeit that it doesn't yet unblock disk DMA.
2021-10-11 03:37:56 -07:00
Thomas Harte
ec3d5c0b32 Increase maximum number of activity LEDs to eight. 2021-10-10 18:37:33 -07:00
Thomas Harte
99492c2ec2 Further tweak logging. 2021-10-10 18:19:50 -07:00
Thomas Harte
addf9f9af4 Moves block byte writes into Storage::Encodings::MFM::Encoder. 2021-10-10 16:06:51 -07:00
Thomas Harte
846b505d27 Reduce logging; disk data probably isn't the immediate obstacle. 2021-10-10 13:04:10 -07:00
Thomas Harte
c4cfcfab8e Checksums appear to be calculated as 32-bit quantities. 2021-10-10 12:58:10 -07:00
Thomas Harte
5e083426c5 Takes another run at checksums.
It turns out I'd read entirely the wrong section of the ADF FAQ. Am now trying to piece things together from various EAB threads.
2021-10-10 11:47:48 -07:00
Thomas Harte
8d43b4a98d Expands Disk DMA access window. 2021-10-10 11:47:02 -07:00
Thomas Harte
aeaea073c6 Switch both: (i) which bits are odd/even; and (ii) nibble ordering. 2021-10-09 13:45:19 -07:00
Thomas Harte
6b0dd19442 Name file appropriately: the logo comes from Kickstart. 2021-10-09 08:02:15 -07:00
Thomas Harte
9336ffe216 Take a stab at index-hole sync. 2021-10-09 08:01:02 -07:00
Thomas Harte
eb157f15f3 Adds index hole interrupt. 2021-10-09 04:08:59 -07:00
Thomas Harte
d6e2a3f425 Make a first attempt to spool into RAM. 2021-10-08 18:11:47 -07:00
Thomas Harte
b47ca13ed3 Push disk data onwards. 2021-10-08 17:18:11 -07:00
Thomas Harte
67546c4d6e Per the HRM, the index hole is connected to CIA B, potentially to raise an interrupt. 2021-10-08 17:12:37 -07:00
Thomas Harte
f72deb0a5c Correct RDY position. 2021-10-08 04:32:13 -07:00
Thomas Harte
616ccbb878 Correct ID bit placement, multiplex with motor state.
The latter per my reading of http://www.primrosebank.net/computers/amiga/upgrades/amiga_upgrades_storage_fdis.htm
2021-10-08 04:05:57 -07:00
Thomas Harte
5899af0038 Starts accumulating disk data. 2021-10-07 05:11:32 -07:00
Thomas Harte
ed303310bb Spell out slightly more; this makes debugging a touch easier. 2021-10-06 13:40:48 -07:00
Thomas Harte
33ff4f3b5c Eliminate drive copies. 2021-10-06 13:40:28 -07:00
Thomas Harte
20bad38d42 Add drive activity lights. 2021-10-06 04:54:40 -07:00
Thomas Harte
92a07398cd I think CHNG works the other way around. 2021-10-06 04:47:52 -07:00
Thomas Harte
ce8f782577 Corrects meaning of IBM-style RDY. 2021-10-06 04:42:44 -07:00
Thomas Harte
e961d0b4a3 Switch RDY type. 2021-10-06 04:41:09 -07:00
Thomas Harte
2253ff656a Adds route for inserting disks. 2021-10-05 16:12:30 -07:00
Thomas Harte
18631399ad Attempts to clock the disk controller. 2021-10-05 15:38:56 -07:00
Thomas Harte
ad4afcdcd5 Switch stepping direction.
Empirically, based on the actions of Kickstart, and assuming my confusion is because the relevant signal is active low.
2021-10-05 15:23:48 -07:00
Thomas Harte
2cf5bcc5db Clarify logic somewhat. 2021-10-05 15:20:05 -07:00
Thomas Harte
1180ad7662 Disables a couple of now-trustworthy LOGs. 2021-10-05 06:51:47 -07:00
Thomas Harte
5463cd1ae3 Attempts to support stepping and head selection. 2021-10-05 06:36:17 -07:00
Thomas Harte
647ec770ce Implements motor latching, drive ID shift registers. 2021-10-05 05:12:01 -07:00
Thomas Harte
e47bec2e65 Switch CIA B ports over. 2021-10-05 03:38:11 -07:00
Thomas Harte
6566936be9 Be overt about the intended interface. 2021-10-04 16:45:33 -07:00
Thomas Harte
674941abdf Starts to add a disk controller. 2021-10-04 16:45:05 -07:00
Thomas Harte
b3f0ca39ed Adds some unused drives. 2021-10-04 08:12:13 -07:00
Thomas Harte
5ccb512883 Moves the CIAs into the Chipset class.
This reflects the routing of interrupt signals for now, but also prepares for the addition of disk drives.
2021-10-04 06:44:54 -07:00
Thomas Harte
da286d5ae8 Switch spaces to tabs. 2021-10-04 05:27:25 -07:00
Thomas Harte
73e45511dc Add missing #include. 2021-10-04 05:26:38 -07:00
Thomas Harte
a282a51673 Remove last of the direct printf'ing. 2021-09-30 02:42:59 -04:00
Thomas Harte
b7b13e20d1 Single column blits should use both masks. 2021-09-29 22:49:35 -04:00
Thomas Harte
ad90c6b6ce Now that this is getting close, don't stop at the first error. 2021-09-29 22:19:34 -04:00
Thomas Harte
402fa41bc0 Corrects initial error value. 2021-09-29 22:19:17 -04:00
Thomas Harte
0b9ebafc0f Flip bit deserialisation order. 2021-09-28 22:12:13 -04:00
Thomas Harte
140e24ef15 Grab further copy flags. 2021-09-28 22:11:58 -04:00
Thomas Harte
0c998d60cb Correct test logic for line draws that repeatedly write to the same address. 2021-09-28 21:45:55 -04:00
Thomas Harte
ffcd2ea10c Attempts more properly to implement line mode. 2021-09-28 21:39:09 -04:00
Thomas Harte
cb460de94d Makes bad first attempt at a Bresenham inner loop. 2021-09-27 22:06:00 -04:00
Thomas Harte
f6624bf776 Edges mildly closer to line output. 2021-09-26 19:18:12 -04:00
Thomas Harte
b4b6c4d86f Attempts to support left and right masks. 2021-09-26 18:42:08 -04:00
Thomas Harte
759689ff31 Fix line mode flag, add busy status. 2021-09-26 18:16:00 -04:00
Thomas Harte
1dfc36f311 Flip loop, add modulo mappings. 2021-09-26 18:15:32 -04:00
Thomas Harte
1c03ff1d37 Fix bltdptl to bltbptl misstatement; remove pre-DMA writes. 2021-09-26 18:14:50 -04:00
Thomas Harte
19dd2f92bd Implements test case. Failing at present, naturally. 2021-09-25 21:52:41 -04:00
Thomas Harte
acfaa016a0 Adds a capture of traffic leading up to the Workbench boot logo.
Around which to construct a test case.
2021-09-25 18:10:07 -04:00
Thomas Harte
732761433a Merge branch 'master' into HeaderOnly6502 2021-09-23 23:00:11 -04:00
Thomas Harte
9012a7f5e1 Merge branch 'master' into Amiga 2021-09-23 23:00:03 -04:00
Thomas Harte
e957b471b2 Merge pull request #989 from TomHarte/Xcode13
Resolves Clang 13 implicit conversion warnings.
2021-09-23 22:59:42 -04:00
Thomas Harte
e5a5faa417 Resolves Clang 13 implicit conversion warnings. 2021-09-23 22:53:41 -04:00
Thomas Harte
313dbe05e0 Switch to more consistent inlining. 2021-09-23 22:36:15 -04:00
Thomas Harte
adf7124e2c Eliminate 6502Base.cpp. 2021-09-23 22:33:33 -04:00
Thomas Harte
c4ab2bbeed Hard-code fetch window width. For now. 2021-09-23 22:06:13 -04:00
Thomas Harte
42ef459e20 Resolve resting values. 2021-09-23 22:05:59 -04:00
Thomas Harte
cad1a9e0f1 Correct bit test. 2021-09-23 20:42:31 -04:00
Thomas Harte
f1d514470d Add note to future self. 2021-09-23 20:29:39 -04:00
Thomas Harte
9a7a54f22f Take alternative guess as to meaning of 'use' bits. 2021-09-23 18:42:12 -04:00
Thomas Harte
137d1c61bd Allow for channel enables and blitting direction. 2021-09-23 18:38:37 -04:00
Thomas Harte
adc071ed7a Fix: modulos are 15-bit signed, the minterms are also in regular BLTCON0. 2021-09-23 18:30:35 -04:00
Thomas Harte
e06f470044 Ensure no implicit conversion from int to IntT. 2021-09-23 18:30:04 -04:00
Thomas Harte
ab69fe56c9 Take a first shot at magical instant blitting. 2021-09-23 18:13:51 -04:00
Thomas Harte
60bad22a91 Correct fetch window. 2021-09-23 18:13:24 -04:00
Thomas Harte
7092429f7c Added some notes to self on line mode. 2021-09-20 23:08:26 -04:00
Thomas Harte
fa800bb809 Introduces code for minterm application. 2021-09-20 19:13:23 -04:00
Thomas Harte
e15f1103a0 Takes a shot at low resolution shifting. 2021-09-20 19:00:52 -04:00
Thomas Harte
a4263b5a8c Ties bitplane collection to line position.
Outgoing bug: incrementing the video relative offset too often, due to cycles that are discovered to be CPU-targetted.
2021-09-19 21:55:45 -04:00
Thomas Harte
3d85f820f4 Add missing file to kiosk project. 2021-09-16 21:29:11 -04:00
Thomas Harte
245b7baa61 Moves the Copper into its own file. 2021-09-16 21:17:23 -04:00
Thomas Harte
0eeaaa150a Correct Copper start address. 2021-09-16 21:01:37 -04:00
Thomas Harte
692d87f446 Attempts to restrict blitter slot allocation. 2021-09-16 19:56:28 -04:00
Thomas Harte
6572efe2a7 Clarifies word addressing. 2021-09-16 08:24:52 -04:00
Thomas Harte
8aac2bd029 Stubs in serial port status. 2021-09-14 21:53:07 -04:00
Thomas Harte
add11db369 Factors out DMADevice, which is now a parent of Blitter. 2021-09-14 20:51:32 -04:00
Thomas Harte
e47eab1d40 Merge branch 'master' into Amiga 2021-09-14 20:27:59 -04:00
Thomas Harte
2f86dfdf2b Merge pull request #987 from TomHarte/IIgsImprovements
Further iterates the IIgs towards full functionality.
2021-09-14 20:27:25 -04:00
Thomas Harte
fa71ae3174 Add apology. 2021-09-14 20:23:36 -04:00
Thomas Harte
dfcd1508c9 Establishes valid initial BRAM. 2021-09-10 19:56:20 -04:00
Thomas Harte
0ca4631279 Switch to zero-initialised state; be more careful about resetting data. 2021-09-09 23:08:13 -04:00
Thomas Harte
7e5fc4444a Default to ROM01. 2021-09-09 22:09:09 -04:00
Thomas Harte
a6221ca322 Reload data only if an output is found. 2021-09-09 22:07:03 -04:00
Thomas Harte
d8e42c4379 Tweak guess at initial state. 2021-09-09 22:06:36 -04:00
Thomas Harte
3bf109ae0b Merge pull request #986 from TomHarte/IIgsSync
Stabilises Apple IIgs display.
2021-09-09 20:14:40 -04:00
Thomas Harte
dd37fa49a0 Stabilises Apple IIgs display. 2021-09-09 20:08:15 -04:00
Thomas Harte
3227ec72a2 Merge branch 'master' into Amiga 2021-09-08 21:08:47 -04:00
Thomas Harte
ee324c3d89 Merge pull request #985 from TomHarte/68000Improvements
68000: fix E alignment, expand Microcycle::apply.
2021-09-08 21:08:33 -04:00
Thomas Harte
863971f944 68000: fix E alignment, expand Microcycle::apply. 2021-09-08 21:03:37 -04:00
Thomas Harte
fd70f7ad43 Attempts to make pixel content observeable. 2021-09-08 20:57:26 -04:00
Thomas Harte
6e034c9b7f At least manages to place a pixel region on screen.
Albeit that I've suddenly realised that I've failed properly to think about high-res versus low-res.
2021-08-11 20:31:37 -04:00
Thomas Harte
52e375a985 Move towards playfield decoding. 2021-08-11 18:47:35 -04:00
Thomas Harte
635c1eacd5 Merge branch 'master' into Amiga 2021-08-11 17:31:17 -04:00
Thomas Harte
f49ba18627 Merge pull request #983 from TomHarte/MachinePickerLayout
macOS: cleans up layout of machine picker.
2021-08-11 17:30:58 -04:00
Thomas Harte
6dbce96781 Switch to non-breaking space, to avoid orphan word. 2021-08-11 17:28:37 -04:00
Thomas Harte
9ec42f0f8f Cleans up bottom constraints. 2021-08-11 17:12:01 -04:00
Thomas Harte
10a5e7313f Makes a buggy first attempt at bitplane data collection. 2021-08-10 21:28:48 -04:00
Thomas Harte
ec9cb21fae Starts towards bitplane collection. 2021-08-10 19:01:41 -04:00
Thomas Harte
fdd02ad6a6 Neaten, slightly. 2021-08-10 09:20:34 -04:00
Thomas Harte
76e9fcc94a Obey blitter DMA-enable mask. 2021-08-10 09:19:15 -04:00
Thomas Harte
e412927415 Logs a bit more from the Blitter, gives it access to slots. 2021-08-10 07:17:01 -04:00
Thomas Harte
dda154c7c6 Adds nonsense disk reads, which seems to lead to bitplane and blitter requests.
Progress, at last!
2021-08-09 20:31:14 -04:00
Thomas Harte
9215535bee Adds a container for the disk controller.
Thereby appears to prove that my Amiga is getting as far as attempting to load from floppy.
2021-08-09 17:35:09 -04:00
Thomas Harte
27726fd2d1 Merge branch 'master' into Amiga 2021-08-09 17:24:06 -04:00
Thomas Harte
77befb7f8e Correct Atari ST text placement; add missing Enteprise constraint. 2021-08-09 17:14:37 -04:00
Thomas Harte
86c6248b48 Merge branch 'master' into Amiga 2021-08-09 17:09:04 -04:00
Thomas Harte
f2af8ff25d Merge pull request #981 from TomHarte/ColourPrecision
Increase precision of phase interpolation.
2021-08-09 17:08:17 -04:00
Thomas Harte
7d8894415c Increase precision of phase interpolation. 2021-08-09 15:48:27 -04:00
Thomas Harte
f8380d2d4c Add 8250 feature of 'count, regardless'. 2021-08-08 22:32:41 -04:00
Thomas Harte
5cc25d0846 Adds a further sanity assert. 2021-08-08 21:52:52 -04:00
Thomas Harte
1502c4530e Takes a further step towards real timing. 2021-08-08 21:52:28 -04:00
Thomas Harte
c1df4d1c0b Mirroring is correct. 2021-08-08 20:20:12 -04:00
Thomas Harte
1f9e41e9cb Ensure TOD isn't firing from power-on. 2021-08-08 18:51:58 -04:00
Thomas Harte
e402e690b0 Assume and test that divide-by-zero posts the PC of the offending instruction. 2021-08-07 17:51:00 -04:00
Thomas Harte
6a15bb15ca Adds a simpler way of deferring single values. 2021-08-07 17:29:21 -04:00
Thomas Harte
3255fc91fa Merge branch 'Amiga' of github.com:TomHarte/CLK into Amiga 2021-08-07 17:00:54 -04:00
Thomas Harte
7f2610c4fc Disambiguates serial control logs. 2021-08-07 16:57:30 -04:00
Thomas Harte
79bd3eb6ae Merge branch 'Amiga' of github.com:TomHarte/CLK into Amiga 2021-08-07 16:56:40 -04:00
Thomas Harte
b11dd6950c Adds an entry for DiagROM. 2021-08-07 16:56:18 -04:00
Thomas Harte
98bd6fc240 Adds a further logging hint. 2021-08-06 23:16:06 -04:00
Thomas Harte
8be053fd35 Fixes top constraint for Atari ST. 2021-08-06 22:57:45 -04:00
Thomas Harte
99fee22a9f Adjusts defaults. 2021-08-06 22:13:21 -04:00
Thomas Harte
084d002353 Adds the Amiga to macOS File -> New... 2021-08-06 21:58:31 -04:00
Thomas Harte
dcbc9847a3 Attempts to get E synchronisation correct. 2021-08-05 20:08:34 -04:00
Thomas Harte
db3c158215 Further increases logging. 2021-08-05 20:07:14 -04:00
Thomas Harte
25e2bd307a Sets VPA for CIA accesses; logs a little more. 2021-08-05 20:06:48 -04:00
Thomas Harte
b9f78f5d33 Fix final timer B test. 2021-08-03 22:27:23 -04:00
Thomas Harte
b4ec9d70da Adds the CNT input. 2021-08-03 22:19:41 -04:00
Thomas Harte
738999a8b7 Further expands list of applied tests. 2021-08-03 22:08:50 -04:00
Thomas Harte
dd91d793d9 Correct typo. 2021-08-03 21:45:44 -04:00
Thomas Harte
1f0bf1b32d Merge pull request #980 from adamsmasher/improve-apple-ii-kb
Improve raw keyboard handling for original Apple ][
2021-08-03 21:14:06 -04:00
Thomas Harte
8e51e8eb77 Does just a touch of 6526 TOD work. 2021-08-03 21:13:08 -04:00
Thomas Harte
6210605bc7 Transfers full TOD responsibility onto the chip-specific templates. 2021-08-03 19:10:09 -04:00
Thomas Harte
0245b040b0 Splits TOD storage by model.
TOD storage will probably end up being a full-on class.
2021-08-03 18:50:58 -04:00
Thomas Harte
34c1cc5693 Adds entry points for all remaining tests.
Failing now: the TB123s, which are TOD related, both CIA2 tests, and CIA1TAB (which I think needs me to implement Port B output toggling).
2021-08-03 17:19:35 -04:00
Thomas Harte
8795719c18 This counts reloads, most accurately. 2021-08-03 17:12:08 -04:00
Thomas Harte
6bbbf43341 At least attempts to chain correctly. 2021-08-03 17:03:58 -04:00
Thomas Harte
f0ef45f0ca Introduces two further tests. 2021-08-03 16:58:51 -04:00
Thomas Harte
ee6039bfa5 Writes to a timer _during reload_ now have effect.
Net: one CIA test passed.
2021-08-03 16:57:05 -04:00
Thomas Harte
ef58ce6277 Gets a bit more rigorous about the clocking stage.
Albeit without advancing relative to the test.
2021-08-02 21:04:00 -04:00
Thomas Harte
15de5e98c4 Adds [partial] test for whether counters are linked. 2021-08-02 20:17:37 -04:00
Thomas Harte
38848ca2db Rationalises reload logic and cuts storage.
Failure point is now chaining, I think.
2021-08-02 20:14:01 -04:00
Thomas Harte
77c627e822 Ensure that reading the interrupt flags really clears the master bit.
Also makes some guesses on one-shot and reload timing. Alas the test isn't in itself specific enough to be more systematic here.
2021-08-02 07:47:08 -04:00
Thomas Harte
c640132699 Reinstates clocking. 2021-08-01 21:35:08 -04:00
Thomas Harte
60b09d9bb0 Increases compile-time logging options. 2021-08-01 21:22:33 -04:00
Thomas Harte
57dd38aef2 Reintroduces reload-on-off, adds interrupt delay. 2021-08-01 21:09:02 -04:00
Thomas Harte
460a6cb6fe Attempts a more literal implementation. 2021-08-01 18:14:10 -04:00
Adam Smith
fdb676da4e . 2021-08-01 00:26:14 -07:00
Thomas Harte
26aaddaa33 Adds further documentation. 2021-07-30 21:34:22 -04:00
Thomas Harte
e51151e558 Adds readme related to C64 ROMs.
Necessary for the Lorenz 6526 tests. I've no current plans to work on the C64.
2021-07-30 21:23:12 -04:00
Thomas Harte
f576baf214 I'm not yet sure this is the best approach, but starts trying to make use of Lorenz's 6526 tests. 2021-07-30 21:21:16 -04:00
Thomas Harte
5c1ac05170 Add documentation. 2021-07-30 21:20:45 -04:00
Thomas Harte
1bae4973bc Post the serial control write onwards. 2021-07-30 18:24:27 -04:00
Thomas Harte
3d9f86c584 Begins keyboard sketches and notes. 2021-07-30 18:23:15 -04:00
Thomas Harte
3514e537ca Minor logging tweaks. 2021-07-30 18:22:59 -04:00
Thomas Harte
3d160ce85f Add another potential warning. 2021-07-30 18:21:38 -04:00
Thomas Harte
b78090ec76 Fixes IOPortsAndTimers classification. 2021-07-28 19:39:42 -04:00
Thomas Harte
759007ffc1 Attempts to route CIA interrupts. 2021-07-28 19:36:30 -04:00
Thomas Harte
37a55c3a77 Corrects 6526 interrupt control write.
This seems to imply that the 6526 should be interrupting too.
2021-07-28 19:26:02 -04:00
Thomas Harte
69ae9d72c8 Remove dead non-access. 2021-07-27 22:27:20 -04:00
Thomas Harte
604232acd9 Establish appropriate word-size mask. 2021-07-27 22:23:38 -04:00
Thomas Harte
82205d71cc Breaks up loop for arithmetic simplicity. 2021-07-27 21:59:27 -04:00
Thomas Harte
402eab10f8 Breaks video output while attempting to pull it into the main loop. 2021-07-27 21:33:07 -04:00
Thomas Harte
b6bf4d73ad Blitter-finished bit aside, attempts to complete the Copper. 2021-07-27 21:10:14 -04:00
Thomas Harte
5425b5c423 Adds some form of WAITing to the Copper. 2021-07-27 19:32:55 -04:00
Thomas Harte
29cd8504ca Implements enough Copper to get a first store. 2021-07-27 19:06:16 -04:00
Thomas Harte
3544746934 Modifies interface, starts on scheduler.
Probably corrects the pixel clock, which I think was scaled up by a factor of 4.
2021-07-27 16:41:18 -04:00
Thomas Harte
d8f814f1c4 If I'm going to push only a single colour, might as well make it fast. 2021-07-26 21:19:43 -04:00
Thomas Harte
a43175125a Assuming I'm going to keep this synchronous, extends function signature. 2021-07-26 20:13:06 -04:00
Thomas Harte
1d03bc560a Stores the colour palette, uses entry 0 as my new always output. 2021-07-26 18:59:11 -04:00
Thomas Harte
3832acf6e3 Produces a static white box, at least. 2021-07-26 18:51:01 -04:00
Thomas Harte
7894b50321 Starts towards an actual pixel output loop. 2021-07-26 18:44:20 -04:00
Thomas Harte
ffded619e6 Returns track 0 found, as a guess. 2021-07-26 18:44:01 -04:00
Thomas Harte
bcb7bb5cce Improves logging further.
To investigate the new perpetual loop.
2021-07-26 17:02:30 -04:00
Thomas Harte
87dcd82f69 Makes a first attempt at some sort of interrupt functionality. 2021-07-26 16:40:42 -04:00
Thomas Harte
e671cc6056 Add stubs for joystick/mouse querying. 2021-07-26 16:21:51 -04:00
Thomas Harte
5da89b88a6 Add missing space. 2021-07-25 22:17:55 -04:00
Thomas Harte
5d60c1f20b Stubs in Paula. 2021-07-25 22:16:31 -04:00
Thomas Harte
7fd00165c9 Switch to [hard-coded] PAL, for now.
In the hope that I get to see some graphics soon, this should better conform to my expectations.
2021-07-25 20:41:51 -04:00
Thomas Harte
34d4420e8c Correct reading of top byte of counter 2. 2021-07-25 20:41:15 -04:00
Thomas Harte
20da194fab Log slightly more accurately. 2021-07-25 19:59:24 -04:00
Thomas Harte
8d2d4c850f Revoke temporary debugging. 2021-07-25 19:59:10 -04:00
Thomas Harte
b7bed027d7 Ensures the value initially loaded to A7 is aligned.
This is a bit of a guess; it's likely to be true though per the rule that A7 is always kept aligned.
2021-07-25 19:55:23 -04:00
Thomas Harte
fcd6b7b0ea Takes further aim at the conters.
I think test cases are needed, probably.
2021-07-24 16:06:49 -04:00
Thomas Harte
ceca32ceb3 Takes a guess at one-shot mode. 2021-07-24 15:53:18 -04:00
Thomas Harte
e3bb9fc1d7 Increase logging. 2021-07-23 23:10:00 -04:00
Thomas Harte
77a8ddb95c Edges towards working counters. 2021-07-23 22:43:47 -04:00
Thomas Harte
c733a4dbf8 Beefs up interrupt awareness. 2021-07-23 21:58:52 -04:00
Thomas Harte
d898a43dff Implements time-of-day counters, provisionally.
Interrupts to do.
2021-07-23 21:24:07 -04:00
Thomas Harte
6216d53b1a Adds a faster flushing HalfCycles -> Cycles conversion. 2021-07-23 20:07:57 -04:00
Thomas Harte
86c30769d9 Add a divide-by-ten for the CIAs. 2021-07-23 19:25:53 -04:00
Thomas Harte
956a6dbd64 Improve commentary. 2021-07-23 19:23:54 -04:00
Thomas Harte
68fe19818e Expose more information about the E clock state. 2021-07-23 19:22:00 -04:00
Thomas Harte
de208ead4e Stubs in enough to get back into a persistent loop. 2021-07-22 22:00:53 -04:00
Thomas Harte
69d62560b4 Adds comment to avoid potential future error. 2021-07-22 22:00:33 -04:00
Thomas Harte
87d2fc1491 Adds enough raster position to return something. 2021-07-22 21:45:51 -04:00
Thomas Harte
2bc9af09e1 Factors out the chipset. 2021-07-22 21:16:23 -04:00
Thomas Harte
26f4758523 Makes a further accommodation for PermitRead/Write. 2021-07-22 21:11:25 -04:00
Thomas Harte
6123349b79 Stubs in control registers and disables exit-on-miss.
I think I may be running up against the limits of stubbing now. Probably time to implement some stuff.
2021-07-22 19:28:01 -04:00
Thomas Harte
d1ac54fe92 Stubs in sprite containers. 2021-07-22 19:00:26 -04:00
Thomas Harte
9468adf737 Stubs in Copper addresses. 2021-07-22 18:51:23 -04:00
Thomas Harte
e85db40b0f Sketches out a blitter class. 2021-07-22 18:43:07 -04:00
Thomas Harte
b3d55cc16d Adds non-committal reads for some write-only registers.
The hardware now proceeds to trying to talk to the Blitter. So that's next.
2021-07-22 16:10:30 -04:00
Thomas Harte
56b62a5e49 Adds a dummy interrupt control register. 2021-07-22 16:09:32 -04:00
Thomas Harte
3ee1fc544f Fix: (1) memory base adjustment; (2) out-of-bounds writes. 2021-07-21 21:49:20 -04:00
Thomas Harte
5401744dc0 Add additional asserts. 2021-07-21 21:47:44 -04:00
Thomas Harte
fe10a10ac2 Correct address on stack upon priviliege exception. 2021-07-21 21:46:55 -04:00
Thomas Harte
ba2e5a97a9 Provisionally adds a status LED. 2021-07-19 22:31:36 -04:00
Thomas Harte
4515d1220c Switches CIA A/B byte connections; applies reset to memory map. 2021-07-19 22:17:40 -04:00
Thomas Harte
486959bce8 With minor additional logging, it appears the Amiga just keeps resetting itself. 2021-07-19 21:50:35 -04:00
Thomas Harte
e1a410bf3d Further mildly increases logging. 2021-07-19 20:54:32 -04:00
Thomas Harte
3767cc7c0b Increase logging; fix set/clear of interrupt enable mask. 2021-07-19 19:03:37 -04:00
Thomas Harte
96b0ce9ef2 Merge branch 'master' into Amiga 2021-07-18 22:16:05 -04:00
Thomas Harte
038ed0551e Merge pull request #979 from TomHarte/Warnings
Resolve all dangling GCC warnings.
2021-07-18 22:15:45 -04:00
Thomas Harte
cfaf4a8a65 Add advised brackets; clarify type punning. 2021-07-18 22:11:11 -04:00
Thomas Harte
22dd8a8847 Stubs onward to a second endless loop. 2021-07-18 20:55:33 -04:00
Thomas Harte
b2ae8e7a4a Adds a type for the operation bitfield. 2021-07-18 20:54:54 -04:00
Thomas Harte
3e2bac8129 Stubs in enough to get to a permanent loop. 2021-07-18 20:25:43 -04:00
Thomas Harte
50b9d0e86d Logically, I think this should be unsigned. 2021-07-18 20:25:22 -04:00
Thomas Harte
a030d9935e Adds port input. 2021-07-18 20:25:04 -04:00
Thomas Harte
c425dec4d5 Makes some attempt to get as far as the overlay being disabled. 2021-07-18 17:17:41 -04:00
Thomas Harte
67d53601d5 Latch and return data direction.
Albeit with no port-handling effect yet.
2021-07-18 12:23:47 -04:00
Thomas Harte
622cca0acf Adds sufficient address decoding to print a more helpful exit message. 2021-07-18 12:13:56 -04:00
Thomas Harte
48999c03a5 Adds concept of time, captured port handler. 2021-07-18 11:49:10 -04:00
Thomas Harte
377cc7bdcd Start to introduce a 6526/8250. 2021-07-18 11:36:13 -04:00
Thomas Harte
a5d0976c2d Eliminate unused #includes. 2021-07-18 11:35:57 -04:00
Thomas Harte
ae05010255 Improve indentation. 2021-07-18 11:29:26 -04:00
Thomas Harte
66cacbd0e0 Be overt about the type being supplied. 2021-07-18 11:28:18 -04:00
Thomas Harte
b1616be4b8 Gets to what is probably a CIA access? 2021-07-17 21:36:20 -04:00
Thomas Harte
a0a9a72d8f Begins sketching out a memory mapper. 2021-07-17 21:10:06 -04:00
Thomas Harte
0cfc7f732c Extends to support read/write permissions in apply. 2021-07-17 21:09:52 -04:00
Thomas Harte
f7de6f790c Meanders vaguely towards a memory map. 2021-07-16 21:42:17 -04:00
Thomas Harte
d1f3b5ed80 Obtains a Kickstart ROM, adds a 68000. 2021-07-16 21:07:12 -04:00
Thomas Harte
7925dcc5a2 Advances far enough for the Amiga to be autonomous. 2021-07-16 20:49:12 -04:00
Thomas Harte
6ade36bf09 Adds an empty shell of a machine. 2021-07-16 20:30:48 -04:00
Thomas Harte
c52945aab5 Adds passthrough for Amiga media. 2021-07-16 20:15:36 -04:00
Thomas Harte
2b0a4055f7 Makes an attempt at Amiga ADF encoding. 2021-07-16 20:07:17 -04:00
Thomas Harte
7cb16a3fc5 Introduces a shell for Amiga ADF decoding. 2021-07-16 18:11:07 -04:00
Thomas Harte
0b80c1988b Add Amiga enums. 2021-07-16 17:59:08 -04:00
Thomas Harte
eab9bc1503 Make implicit conversion explicit. 2021-07-16 17:45:14 -04:00
Thomas Harte
5bfedff8d1 Mutate dangling printf to a LOG. 2021-07-16 17:32:05 -04:00
Thomas Harte
c8638c0ffb Merge pull request #977 from TomHarte/MouseFade
Slightly adjusts macOS mouse hiding semantics.
2021-07-16 17:25:59 -04:00
Thomas Harte
8a95b91e2a Merge pull request #976 from TomHarte/DiskIIClocking
Correct Disk II sleeping test to allow for spin-down.
2021-07-16 17:22:04 -04:00
Thomas Harte
c226be612f Slightly adjusts mouse hiding semantics.
This allows the Macintosh and ST to fade out volume and settings even without having captured the mouse.
2021-07-16 17:21:25 -04:00
Thomas Harte
c8699d9770 Correct Disk II sleeping test to allow for spin-down. 2021-07-16 17:12:57 -04:00
Thomas Harte
a0799e14cc Merge pull request #975 from TomHarte/LEDStyles
Classify some LEDs as 'persistent'
2021-07-15 22:05:14 -04:00
Thomas Harte
dea6048849 Add documentation. 2021-07-15 22:00:10 -04:00
Thomas Harte
813e252539 Ignore hidden files. 2021-07-15 21:57:25 -04:00
Thomas Harte
b41e29a83b Slows CPC typer to avoid dropped characters. 2021-07-15 21:54:02 -04:00
Thomas Harte
d35c7ad127 Take advantage of persistence flag for more intelligent LED presentation. 2021-07-15 21:49:11 -04:00
Thomas Harte
ea63415d0e Exposes persistent LED flag to Swift. 2021-07-15 21:34:14 -04:00
Thomas Harte
52ea3b741c Introduces a presentation flag for LEDs.
All existing receivers ignore it.
2021-07-15 21:26:02 -04:00
Thomas Harte
2731ca8c92 Merge pull request #974 from TomHarte/KickstartROMs
Introduces Amiga ROMs to the catalogue.
2021-07-15 21:14:50 -04:00
Thomas Harte
af1ade9433 Introduces Amiga ROMs to the catalogue. 2021-07-15 21:09:20 -04:00
Thomas Harte
fc248951cc Merge pull request #973 from TomHarte/TransientActivity
Converts activity indicators to transient in-window presentation.
2021-07-15 20:15:33 -04:00
Thomas Harte
84547ee1c1 Reduce spurious in-window appearances. 2021-07-15 19:53:40 -04:00
Thomas Harte
a42848c62f Add windowed LED reappearance upon blink.
Also fix crash-at-startup for fullscreen.
2021-07-15 19:51:23 -04:00
Thomas Harte
c7b5d69431 Add extra usage hint. 2021-07-15 19:50:43 -04:00
Thomas Harte
81374b70b5 Switch to transient LED presentation in windowed mode. 2021-07-15 19:22:23 -04:00
Thomas Harte
47a530fd5c Fixes LED ordering.
Still work to do on capturing the proper window title.
2021-07-14 22:01:42 -04:00
Thomas Harte
58451d7c0c Attempts to incorporate LEDs into the window title when in windowed mode. 2021-07-14 21:43:58 -04:00
Thomas Harte
5c8f8c76fe Thus ends the View menu. 2021-07-14 21:02:58 -04:00
Thomas Harte
ae1d1bdb5b Wires up controller for QuickLoadOptions. 2021-07-14 21:02:04 -04:00
Thomas Harte
33cc1154a2 Simplify ViewFader and avoid second-guessing when to hard-set opacity. 2021-07-14 20:50:41 -04:00
Thomas Harte
4bc0b75c30 Ensure Macintosh controller is effective. 2021-07-14 20:50:12 -04:00
Thomas Harte
eb8ec1efb1 Makes ViewFader the full master of fading. 2021-07-14 19:03:44 -04:00
Thomas Harte
616f8efc47 Improves optional hysteresis. 2021-07-13 23:40:15 -04:00
Thomas Harte
29e4369420 Attempts to switch activity indicators to smart in-window presentation. 2021-07-13 23:32:00 -04:00
Thomas Harte
bd7f7bc8d7 Remove dead 'show options'. 2021-07-13 22:28:03 -04:00
Thomas Harte
e689ca92c4 Minor rearrangements, for cleanliness. 2021-07-13 22:26:50 -04:00
Thomas Harte
4ef3005072 Merge pull request #972 from TomHarte/InWindowOptions
macOS: moves machine options into the emulation window
2021-07-13 22:06:08 -04:00
Thomas Harte
174c837767 Switches to a logarithmic volume dial. 2021-07-13 21:45:07 -04:00
Thomas Harte
486bb911a9 Adapts ZX80/81 options. 2021-07-13 21:26:20 -04:00
Thomas Harte
754221d697 Adapts QuickLoadOptions.
Not that it currently seems to be used.
2021-07-13 21:21:02 -04:00
Thomas Harte
3c36c90729 Adapts QuickLoadCompositeOptions. 2021-07-13 21:17:52 -04:00
Thomas Harte
3d1d15a25b Updates the Oric options. 2021-07-13 19:32:23 -04:00
Thomas Harte
000d99f26c Adapts the Macintosh options. 2021-07-13 19:26:29 -04:00
Thomas Harte
524e2abc8c Adapts composite options. 2021-07-13 19:19:47 -04:00
Thomas Harte
00bab98e09 Converts the Apple II options into an in-window view. 2021-07-13 19:14:54 -04:00
Thomas Harte
6d98349be1 Fully invests in options controllers, distinct from the views.
Per MVC, I should have been doing something closer to this from day one.
2021-07-13 19:04:24 -04:00
Thomas Harte
d24d153c08 Use modern constraint specification, add layers to XIBs. 2021-07-12 22:55:53 -04:00
Thomas Harte
b01561712c Tightens spacing slightly. 2021-07-12 22:49:42 -04:00
Thomas Harte
324edcb391 Starts towards using an in-window options panel.
With the same fade in/out behaviour as the volume control.
2021-07-12 22:38:08 -04:00
Thomas Harte
6e62e4e296 Merge branch 'master' of github.com:TomHarte/CLK 2021-07-12 22:01:25 -04:00
Thomas Harte
f81ecbf4a0 Force icons back to white. 2021-07-12 22:01:19 -04:00
Thomas Harte
4370456323 Switch to an NSVisualEffectView for volume controls.
It provides a background that better contrasts with arbitrary content.
2021-07-12 21:28:04 -04:00
Thomas Harte
a424ed7c00 Makes for slightly more straightforward constraints. 2021-07-12 19:25:06 -04:00
Thomas Harte
a2065f59a1 Adds a 0.1 second pause before exit-related menu fadeout.
This is because the system may post a quick succession of exits and enters if the view hierarchy changes.
2021-07-12 19:12:04 -04:00
Thomas Harte
c1bd7f5c67 Pull release links up closer to the lede. 2021-07-12 10:03:03 -04:00
Thomas Harte
5810a1a98e Merge pull request #971 from TomHarte/ChaseHQ
Flip meaning of INT1 input read.
2021-07-09 22:48:01 -04:00
Thomas Harte
a4c011e3c0 Flip meaning of INT1 input read. 2021-07-09 22:39:51 -04:00
Thomas Harte
337fd15dc0 Merge pull request #970 from TomHarte/SwiftUniformity
Swift: be consisted on `.selectedTag()`.
2021-07-08 22:43:57 -04:00
Thomas Harte
9bc94f4536 Be consisted on .selectedTag(). 2021-07-08 22:38:54 -04:00
Thomas Harte
3f4cf35384 Merge pull request #969 from TomHarte/SixMHz
Adds the option of running an Enterprise at 6MHz.
2021-07-08 22:36:22 -04:00
Thomas Harte
4dd7f2cc09 Add 6Mhz option to Qt UI. 2021-07-08 22:30:35 -04:00
Thomas Harte
1b29cc34c4 Correct input list. 2021-07-08 22:22:48 -04:00
Thomas Harte
53c3c1f5ab Allows macOS users to select the 6MHz Enterprise. 2021-07-08 18:50:37 -04:00
Thomas Harte
6225abd751 Adds 6MHz Enterprise option. 2021-07-07 20:57:04 -04:00
Thomas Harte
c6fcd9a1eb Merge pull request #968 from TomHarte/DaveAudio
Dave: apply ring modulation during sync, too.
2021-07-06 23:41:37 -04:00
Thomas Harte
30fbb6ea53 Ensure run command is issued. 2021-07-06 23:16:16 -04:00
Thomas Harte
0e49258546 Remove caveman debugging. 2021-07-06 23:15:53 -04:00
Thomas Harte
264b8dfb28 Dave: apply ring modulation even in sync mode. 2021-07-06 23:11:30 -04:00
Thomas Harte
6a15b8f695 Merge pull request #967 from TomHarte/EnterpriseTiming
Correct Enterprise timing error.
2021-07-06 22:48:58 -04:00
Thomas Harte
5167d256cc Remove detritus. 2021-07-06 22:43:17 -04:00
Thomas Harte
16bd826491 Reduce nesting. 2021-07-06 22:32:59 -04:00
Thomas Harte
55af8fa5d9 Avoid erroneous Nick delays. 2021-07-06 22:28:44 -04:00
Thomas Harte
1ec8ff20af Ensure data bus is 0xff during interrupts. 2021-07-06 21:58:17 -04:00
Thomas Harte
99a65d3297 Merge pull request #966 from TomHarte/DaveUnifiedTimer
Switches to a unified counter for 1/50/1000Hz Dave interrupts.
2021-07-06 21:50:32 -04:00
Thomas Harte
94907b51aa Remove redundant parameter. 2021-07-06 20:47:49 -04:00
Thomas Harte
0085265d13 Test for a longer period; fix expected tone 1 count. 2021-07-06 20:46:22 -04:00
Thomas Harte
8e0893bd42 Clarifies control flow. 2021-07-06 20:28:32 -04:00
Thomas Harte
704dc9bdcb Improves test, to assert that state toggles happen at interrupts. 2021-07-06 20:25:32 -04:00
Thomas Harte
7a673a2448 Avoid confusing temporary storage. 2021-07-06 20:23:09 -04:00
Thomas Harte
33e2a4b21c Minor cleanups. 2021-07-06 20:20:13 -04:00
Thomas Harte
3e6b804896 Switches to linked 1/50/1000 Hz timers, and per-interrupt state toggling. 2021-07-06 20:12:44 -04:00
Thomas Harte
e98165a657 Merge pull request #965 from TomHarte/DaveDivider
Ensure two-cycle pauses in 12MHz mode.
2021-07-04 21:13:05 -04:00
Thomas Harte
2a7727d12b Merge branch 'master' into DaveDivider 2021-07-04 21:02:09 -04:00
Thomas Harte
c20e8f4062 Honours 8/12Mhz selection in non-video delays. 2021-07-03 23:05:09 -04:00
Thomas Harte
4ca9db7d49 Merge pull request #963 from TomHarte/DaveDivider
Obey Dave's 8/12MHz programmable divider.
2021-07-03 23:00:11 -04:00
Thomas Harte
4add48cffb Obey Dave's 8/12MHz programmable divider. 2021-07-03 22:43:20 -04:00
Thomas Harte
adbfb009f8 Merge pull request #960 from TomHarte/QtLEDs
Ensure LEDs are cleared between machines in Qt.
2021-07-03 19:17:51 -04:00
Thomas Harte
43ceca8711 Use type alias. 2021-07-03 19:10:39 -04:00
Thomas Harte
3ef28a4f03 Remove unused instance variable. 2021-07-03 19:10:29 -04:00
Thomas Harte
adcd580d5b Ensure LEDs are cleared upon a new machine. 2021-07-03 19:06:15 -04:00
Thomas Harte
5715c9183f The target is now definitely used. 2021-07-03 15:20:37 -04:00
Thomas Harte
ceb62ac7f9 Reenable the hardened runtime for macOS. 2021-07-03 13:41:32 -04:00
Thomas Harte
bda0756620 Merge pull request #959 from TomHarte/WriteCrash
Corrects buffer placement of decoded sectors.
2021-07-03 13:41:00 -04:00
Thomas Harte
6b47fb38c6 Corrects buffer placement of decoded sectors. 2021-07-03 13:36:01 -04:00
Thomas Harte
38bf8a06a7 Merge pull request #958 from TomHarte/EnterpriseFloatingBus
Makes a guess re: the Enterprise floating bus
2021-07-03 13:26:19 -04:00
Thomas Harte
196651d9aa Consolidates TODO. 2021-07-03 13:08:53 -04:00
Thomas Harte
6b46212a4e Deal with dangling TODO. 2021-07-03 13:07:41 -04:00
Thomas Harte
2a6fff2008 Takes a stab at what might happen if you read from Nick. 2021-07-03 13:06:07 -04:00
Thomas Harte
c5944efe50 Adds various method definitions. 2021-07-03 12:56:56 -04:00
Thomas Harte
f384370b18 Switch what's left of Enterprise logging to actual LOGs. 2021-07-03 12:50:46 -04:00
Thomas Harte
0c09275a9f Merge pull request #957 from TomHarte/EnterpriseTimingWindow
Correct various Enterprise timing discrepancies.
2021-07-03 12:47:29 -04:00
Thomas Harte
278671cdb9 Correct Nick interrupt prediction. 2021-07-03 00:05:13 -04:00
Thomas Harte
964d2d4fa4 Be consistent in expression of logic. 2021-07-03 00:00:00 -04:00
Thomas Harte
f371221dba Add a quick test of tone generator 1. 2021-07-02 23:57:11 -04:00
Thomas Harte
27b0579ec6 Avoid stack-error test case.
Also test that the interrupt is generated on the downward stroke.
2021-07-02 23:55:43 -04:00
Thomas Harte
283092cfbc With a unit test in aid, corrects some lingering TimedInterruptSource issues. 2021-07-02 23:41:19 -04:00
Thomas Harte
614953a222 Allows the low-pass filter to react to high-pass effects. 2021-07-02 22:36:35 -04:00
Thomas Harte
4fffb3cf19 Allow that final Z80 cycle to start anywhere in the first three of Nick's window of six. 2021-07-02 22:29:35 -04:00
Thomas Harte
850aa2b23a Merge pull request #956 from TomHarte/EnterpriseComposite
Adds Enterprise composite video option.
2021-07-02 22:22:09 -04:00
Thomas Harte
d715e5fd1d Expose composite/RGB option in Qt. 2021-07-02 21:51:48 -04:00
Thomas Harte
7826a26c7b Adds Enterprise composite video option.
While enabling more pixels on the left for RGB mode.
2021-07-02 21:42:09 -04:00
Thomas Harte
dc0a82cf9a Merge pull request #955 from TomHarte/FAT12
Adds a FAT12 parser.
2021-07-02 21:33:54 -04:00
Thomas Harte
2e60c81bd6 Enter :dir as a complete command. 2021-07-02 21:15:48 -04:00
Thomas Harte
763b9ba0ec Ensure the splash screen is skipped for self-booting disks. 2021-07-02 21:11:54 -04:00
Thomas Harte
bae8bb0c00 Gives the FAT parser responsibility for right trims. 2021-07-02 19:50:27 -04:00
Thomas Harte
bcf483fb7e Adds some basic loading command assistance. 2021-07-02 19:42:43 -04:00
Thomas Harte
a5b7d819a7 Correct FAT parser. 2021-07-02 19:28:13 -04:00
Thomas Harte
fe07a0b1d8 Starts to add a FAT[12] parser. 2021-07-02 18:56:43 -04:00
Thomas Harte
d9231e5d4a Merge pull request #954 from TomHarte/stddefRedux
The FIRFilter interface depends upon size_t.
2021-07-02 17:26:37 -04:00
Thomas Harte
b7aa1a1c84 The FIRFilter interface depends upon size_t. 2021-07-02 17:21:53 -04:00
Thomas Harte
32e144115d Add missing article, plus other minor corrections. 2021-07-02 11:03:14 -04:00
Thomas Harte
177cc96f49 Merge pull request #953 from TomHarte/stddef
Add missing stddef header where size_t is used.
2021-07-01 23:29:56 -04:00
Thomas Harte
51d98ef9ab Add missing stddef header where size_t is used. 2021-07-01 23:15:32 -04:00
Thomas Harte
2327c48cc4 Merge pull request #952 from TomHarte/EnterpriseTyper
Add typer support for the Enterprise.
2021-07-01 22:59:04 -04:00
Thomas Harte
742d44a532 Switch to an activity-based typing trigger; add a target loading command. 2021-07-01 22:53:23 -04:00
Thomas Harte
52b96db2b9 Correct syntax, mapping and inter-key timing. 2021-07-01 21:18:15 -04:00
Thomas Harte
0b9de78c38 Add typer support for the Enterprise. 2021-07-01 21:05:03 -04:00
Thomas Harte
2c28cb8c57 Merge pull request #951 from TomHarte/EnterpriseMention
Add Enterprise screenshots
2021-06-30 22:24:43 -04:00
Thomas Harte
483fe82e9d Add a second image, to even things out. 2021-06-30 22:23:26 -04:00
Thomas Harte
29492d6138 Add an Enterprise screenshot. 2021-06-30 22:18:29 -04:00
Thomas Harte
19310e32c4 Adds the Enterprise 64/128 as a bullet-pointed item.
No relevant screenshots yet.
2021-06-30 08:06:20 -04:00
Thomas Harte
c04a395499 Merge pull request #950 from TomHarte/Enterprise
Adds emulation of the Enterprise
2021-06-29 21:37:38 -04:00
Thomas Harte
1c424833a9 Correct EXDOS ROM name. 2021-06-29 21:04:53 -04:00
Thomas Harte
a46ff5590d Adds Enterprise new machine dialogue for Qt. 2021-06-29 21:04:17 -04:00
Thomas Harte
ab059b63fd Add Enterprise to Qt project file. 2021-06-29 20:36:28 -04:00
Thomas Harte
3d8fc9952d Remove dead TODO, correct for overflow position. 2021-06-29 15:44:02 -04:00
Thomas Harte
8ce8fbd977 Provide correct input when one of the tone generators is the interrupt source. 2021-06-29 15:41:08 -04:00
Thomas Harte
7f08218b28 The Nick interrupt input also seems to be a live poll, not a retrieval of the mask.
This corrects the two pieces of software I knew not to be working.
2021-06-28 22:10:11 -04:00
Thomas Harte
2c139ad931 Adds some notes to self.
I think I'm starting to find enough information to handle tapes.
2021-06-28 22:03:06 -04:00
Thomas Harte
1119779c8b Ensure EXDOS card is completely disabled if no FDC is present. 2021-06-28 21:47:53 -04:00
Thomas Harte
5351ac560f Ensure the motor goes off for unselected drives. 2021-06-28 21:40:12 -04:00
Thomas Harte
49f0ab0f15 Add note to self.
Although I still think there may be some issue lurking.
2021-06-28 21:31:55 -04:00
Thomas Harte
a5c57e777e VRES appears to work negatively in attribute mode too. 2021-06-28 21:24:13 -04:00
Thomas Harte
3c59042388 Fixes initial state for 1kHz. 2021-06-28 21:08:41 -04:00
Thomas Harte
919e211bc4 Reduces number of interrupt-related sequence points. 2021-06-28 19:30:12 -04:00
Thomas Harte
daa0737ce4 Ensure addresses tick upwards even during sync/burst; correct 2/4/8bpp character sizing. 2021-06-28 19:00:51 -04:00
Thomas Harte
36805cb120 Correct tone channel interrupts, remove dead warning. 2021-06-27 23:21:00 -04:00
Thomas Harte
7de69e9874 Makes an attempt to round out the timed interrupts. 2021-06-27 23:09:11 -04:00
Thomas Harte
b93575bbcc Spots that b0 and b2 of 0xb4 are 'dividers', not enables. 2021-06-27 22:33:20 -04:00
Thomas Harte
116e0f0105 Interupts 1kHz and 50Hz interrupts, while edging towards tone generator interrupts. 2021-06-27 22:08:38 -04:00
Thomas Harte
e4a650aaff Implements the 1Hz interrupt. 2021-06-27 21:47:21 -04:00
Thomas Harte
b5312b9ba0 get_interrupt_line can be const. 2021-06-27 21:37:11 -04:00
Thomas Harte
6afee7bb9b Captures appropriate fields.
No action yet.
2021-06-27 21:36:55 -04:00
Thomas Harte
5729e6e13a Corrects potential JustInTimeActor overflow. 2021-06-27 21:36:41 -04:00
Thomas Harte
2f53b105bb The Enterprise is now an Activity::Source; also sketches out the owner of Dave's timed interrupt logic. 2021-06-27 21:02:04 -04:00
Thomas Harte
b698056f78 Correct divisor. 2021-06-27 17:39:13 -04:00
Thomas Harte
95c906f03d Takes a serious shot at back_map. 2021-06-27 17:36:25 -04:00
Thomas Harte
be19fa9dde This mapping needs to know where it will occur. 2021-06-27 17:30:09 -04:00
Thomas Harte
81e9ba5608 This is correct from the Enterprise's side of things, I think.
I just need to complete the missing part of JustInTimeActor. After I do some empirical testing of this.
2021-06-27 17:24:21 -04:00
Thomas Harte
f2d7b9f6a9 Apply a crop, allow time until Z80 slot to be a future-based query. 2021-06-27 17:13:07 -04:00
Thomas Harte
1ea034310a Edge up very close to video waits.
I just need to implement back conversions that include marginal phase over in the JustInTimeActor.
2021-06-27 16:28:01 -04:00
Thomas Harte
bdcab447f9 Add a further accessor. 2021-06-27 16:27:26 -04:00
Thomas Harte
10bf6744aa Correct typo. 2021-06-27 16:26:55 -04:00
Thomas Harte
895d98e266 Implements out-of-video-area pauses. 2021-06-27 16:11:22 -04:00
Thomas Harte
903e343895 Attempts to complete Dave's audio duties. 2021-06-27 14:06:49 -04:00
Thomas Harte
f8b7c59616 Corrects tone frequency. 2021-06-26 23:51:43 -04:00
Thomas Harte
fcd267a3f9 Starts implementing noise. 2021-06-26 23:48:53 -04:00
Thomas Harte
f8bb66d2a0 Attempts an essentially-complete implementation of tone channels. 2021-06-26 23:39:59 -04:00
Thomas Harte
90782d3c27 Corrects for IntType != int. 2021-06-26 23:39:37 -04:00
Thomas Harte
f2336d2efc I think reloads occur after overflow, not before. 2021-06-26 23:16:00 -04:00
Thomas Harte
c2d093fa3c Respect user volume input.
Basic tones are now present. Neato!
2021-06-24 22:27:02 -04:00
Thomas Harte
1a97cc8a91 Start making some effort towards audio generation. 2021-06-24 22:21:01 -04:00
Thomas Harte
c34a548fa0 Ensure character pixel reads can't go out of bounds. 2021-06-24 22:19:50 -04:00
Thomas Harte
d1b89392a2 Improve exposiiton. 2021-06-24 22:18:31 -04:00
Thomas Harte
ed734754e5 Adds a through route for IMG files. 2021-06-24 21:04:21 -04:00
Thomas Harte
520c3c9218 Corrects colour deserialisation.
Long story short: the documentation I'm reading inexplicably lists the bits in reverse order. Luckily, a lot of the other documentation doesn't.
2021-06-24 20:59:04 -04:00
Thomas Harte
9230cf1726 Corrects bug when left_ or right_margin_ = 0. 2021-06-24 20:28:50 -04:00
Thomas Harte
6e616972a5 Better binds margin tests to window movements; simplifies line parameter addressing. 2021-06-24 18:55:15 -04:00
Thomas Harte
f98888824d Switches to an overt active/inactive state machine. 2021-06-24 18:34:21 -04:00
Thomas Harte
6c8b23e708 Alters 4bpp mapping; adds character mode 4bpp and 8bpp. 2021-06-23 19:35:47 -04:00
Thomas Harte
2c2bb3765f Withdraws the EPDOS option.
At least for now; it's something to worry about later.
2021-06-23 19:32:34 -04:00
Thomas Harte
0d165740ea Honours memory size request. 2021-06-22 21:48:55 -04:00
Thomas Harte
88f0f2b623 Adds to the macOS UI and wires through all Enterprise options. 2021-06-22 21:39:07 -04:00
Thomas Harte
0afa143375 Add missing include. 2021-06-22 21:31:46 -04:00
Thomas Harte
8319aca351 Correct syntax errors. 2021-06-22 20:50:03 -04:00
Thomas Harte
a66734883a Starts sketching out Dave. 2021-06-22 19:33:41 -04:00
Thomas Harte
d2ab0dd839 Adds a quick way to get the compiler to pick an integral type. 2021-06-22 19:33:29 -04:00
Thomas Harte
2574407afb Relocates MinIntTypeValue to Numeric. 2021-06-22 19:33:02 -04:00
Thomas Harte
83a54fd6d2 Use the FAT12 boot sector to determine geometry. 2021-06-22 06:54:17 -04:00
Thomas Harte
e062780968 Extends back to 128kb and stops halting on unrecognised ports. 2021-06-22 06:15:42 -04:00
Thomas Harte
3acd0be1f7 Copy and paste 2bpp character support. 2021-06-21 23:27:13 -04:00
Thomas Harte
69c0734975 WD1770: switch motor on even if spin-up is disabled. 2021-06-21 23:26:55 -04:00
Thomas Harte
c1678d7be7 Corrects exposition and transmission of drive selection.
What a klutz I've been.
2021-06-21 22:56:25 -04:00
Thomas Harte
117f9a9794 Adds notes on intended meaning of status register. 2021-06-21 07:31:58 -04:00
Thomas Harte
b49cc407c6 Adds some guesses as to the EXDos expansion.
... with plenty left to do.
2021-06-20 22:30:27 -04:00
Thomas Harte
954386f1cc Creates a shell for the disk-drive add-on card. 2021-06-20 20:50:23 -04:00
Thomas Harte
d7ff6bd04d Adds necessary declarations to install a DOS ROM. 2021-06-20 20:30:54 -04:00
Thomas Harte
6025516f9f Ensure addresses increment even when there's no target for pixels. 2021-06-20 14:31:02 -04:00
Thomas Harte
d8b9cdf7a2 Correct multiplier. 2021-06-20 14:25:37 -04:00
Thomas Harte
09dbff39f2 Also map keypad to F keys.
This is a pragmatic and arguably Apple-centric decision. But also it's fairly arbitrary, as the Enterprise doesn't have a number pad.
2021-06-20 14:25:28 -04:00
Thomas Harte
2fe15a6168 Switch to idealised Nick clock rate. 2021-06-20 14:21:56 -04:00
Thomas Harte
07dc26f8fa Adds TODO to resolve screen jumping. 2021-06-19 23:41:29 -04:00
Thomas Harte
a08d65b1ff Adds IMG -> Enterprise connection.
Albeit still without an Enterprise static analyser.
2021-06-19 23:16:33 -04:00
Thomas Harte
199621db08 Observes that the actual guess here is MS-DOS-style. 2021-06-19 23:11:51 -04:00
Thomas Harte
0e1e8c7faa Attempts to support the panoply of EXOS and BASIC versions. 2021-06-19 22:59:09 -04:00
Thomas Harte
42a98e1676 Fix composition with empty nodes. 2021-06-19 22:13:17 -04:00
Thomas Harte
23e26e0333 Attempts to complete handling of VRES. 2021-06-19 22:00:19 -04:00
Thomas Harte
fadb04f3f3 Attempts to implement LSBALT and MSBALT. 2021-06-19 21:57:26 -04:00
Thomas Harte
4968ccf46d Corrects attributed mode. 2021-06-19 13:08:14 -04:00
Thomas Harte
1dcac304d3 Implements the ALTIND bits and attempts ATTR mode. 2021-06-19 13:04:18 -04:00
Thomas Harte
1651efe4fc Ensures all keys are initially unpressed. 2021-06-19 13:03:31 -04:00
Thomas Harte
8f24aed43e Slightly reduces logging. 2021-06-18 23:17:44 -04:00
Thomas Harte
a381374e31 Drops back down to 64kb. 2021-06-18 23:14:44 -04:00
Thomas Harte
9411c37d23 Fleshes out the keyboard map. 2021-06-18 23:14:35 -04:00
Thomas Harte
6af6f21868 Attempts to implement interrupt latches and clears. 2021-06-18 22:59:41 -04:00
Thomas Harte
9a0022cfcb Removes temporary work. 2021-06-18 18:44:07 -04:00
Thomas Harte
266310d9c2 Fixes automatic flushing for non-1/1-clocked actors. 2021-06-18 18:43:08 -04:00
Thomas Harte
fbf1adef05 Introduces unit test and thereby seemingly fixes get_next_sequence_point.
There's still improper output in the actual machine though, so maybe something else is afoot?
2021-06-18 17:44:17 -04:00
Thomas Harte
311ddfb05a Add note to self for tomorrow. 2021-06-17 22:34:52 -04:00
Thomas Harte
2fd8a8aa66 Begins addition of interrupt feedback from Nick.
Also fixes clock rate. Though clearly get_next_sequence_point isn't quite right yet.
2021-06-17 22:30:24 -04:00
Thomas Harte
0c3e9dca28 Adds some basic keyboard inputs.
I think the next thing required is interrupts though.
2021-06-17 20:47:56 -04:00
Thomas Harte
c331d15429 Makes space to allow for 64kb EXOS ROMs.
I think some of the later ROMs have a more thorough memory test, which might provide better detail on whatever's going on here.
2021-06-16 22:25:00 -04:00
Thomas Harte
4414e96710 Adds enough text mode for now.
Discovered: a memory fault is being reported at startup.
2021-06-16 21:42:20 -04:00
Thomas Harte
7161783a4f Adds lpixel output. 2021-06-16 21:16:26 -04:00
Thomas Harte
cbac48da86 Attempts full run at pixel mode. 2021-06-16 20:43:22 -04:00
Thomas Harte
d9142d5427 Adjusts declared scale, accurately to communicate pixel clock. 2021-06-15 22:39:44 -04:00
Thomas Harte
e5e988b28f Adds an incorrect assumed-pixel-mode serialiser.
This actually shows something a bit like the Enterprise boot logo.
2021-06-15 22:31:50 -04:00
Thomas Harte
e94e051c87 Adds an allocator for pixels. 2021-06-15 22:03:41 -04:00
Thomas Harte
5fc91effb5 Corrects top border. 2021-06-15 21:48:11 -04:00
Thomas Harte
6c9dacbe89 Stabilises display, albeit that top border mode now appears to be off. 2021-06-15 21:31:07 -04:00
Thomas Harte
6a7eb832cc Introduces almost-stable block-level frame generation. 2021-06-15 20:55:58 -04:00
Thomas Harte
60cf8486bb Makes a genuine attempt at real line counting.
No output yet though.
2021-06-15 18:57:14 -04:00
Thomas Harte
90b8163d54 Add exposition. 2021-06-15 17:44:39 -04:00
Thomas Harte
a1e4389f63 Give Nick some RAM to inspect and just enough sense to know when it should reload its line parameter table. 2021-06-15 17:43:13 -04:00
Thomas Harte
440b11708b Adds an unused CRT. 2021-06-14 23:11:48 -04:00
Thomas Harte
f90dce5c54 Take a guess at Nick timing. 2021-06-14 22:56:26 -04:00
Thomas Harte
606c7709cf Shims in enough to get the Z80 to run perpetually.
Notably I don't actually currently know how the interrupt registers work, but getting some sort of display running might be the first order of business.
2021-06-14 22:28:31 -04:00
Thomas Harte
1d1e6d1820 Adds a shell of a Nick. 2021-06-14 22:19:25 -04:00
Thomas Harte
3eb4dd74a2 Exposes memory control.
Machine now runs as far as trying to interact with Nick.
2021-06-14 21:45:12 -04:00
Thomas Harte
853914480c Revised guess; there's a jump to C02E almost immediately. 2021-06-14 21:40:19 -04:00
Thomas Harte
fe04410681 Merge branch 'master' into Enterprise 2021-06-14 21:30:49 -04:00
Thomas Harte
1f686c4e6b Add missing AppleIIOptionsPanel class. 2021-06-14 21:30:30 -04:00
Thomas Harte
2a2ac1227b Makes first attempt at giving the Z80 something to do. 2021-06-14 21:29:56 -04:00
Thomas Harte
b5340c8f74 Correct syntax. 2021-06-14 21:17:35 -04:00
Thomas Harte
196c4dcdd9 Adds an appropriate ROM request. 2021-06-14 21:17:09 -04:00
Thomas Harte
c5a86f0ef7 Add Enterprise parts of the static analyser. 2021-06-14 21:11:06 -04:00
Thomas Harte
88f2a2940b Add Enterprise source paths. 2021-06-14 21:07:35 -04:00
Thomas Harte
26b019a4d4 Removes assumption that all machines produce audio. 2021-06-14 21:02:55 -04:00
Thomas Harte
5f7b3ae313 Adds bare minimum to get a non-functional machine. 2021-06-14 21:02:40 -04:00
Thomas Harte
61c127ed2e Adds Enterprise as a File -> New... machine.
And, similarly, exposes it for the route used by SDL.
2021-06-14 20:55:39 -04:00
Thomas Harte
333981e2a7 Merge branch 'master' into Enterprise 2021-06-13 22:22:44 -04:00
Thomas Harte
423fbc9ac7 Merge pull request #949 from TomHarte/QtSnapshotDragAndDrop
Adds drag-and-drop snapshot support for Qt.
2021-06-13 21:48:42 -04:00
Thomas Harte
1c1719e561 Adds drag-and-drop snapshot support for Qt. 2021-06-13 21:41:20 -04:00
Thomas Harte
56c30e1651 Merge pull request #948 from TomHarte/QtAspectRatio
Ensures Apple II square pixels work correctly under OpenGL
2021-06-13 21:23:28 -04:00
Thomas Harte
1ea4130035 Avoid OpenGL restretching bug. 2021-06-13 19:46:47 -04:00
Thomas Harte
57713d63fa Avoids regression of selected tab upon app restart. 2021-06-13 19:38:56 -04:00
Thomas Harte
d18a537509 Fiddles with the preprocessor to make kiosk mode match other OSes even on macOS. 2021-06-13 19:28:05 -04:00
Thomas Harte
8e0a6df03b Merge branch 'master' into Enterprise 2021-06-10 21:41:57 -04:00
Thomas Harte
95a52a9f62 Merge pull request #947 from TomHarte/AppleIISquarePixels
Adds optional square pixel output for the Apple II
2021-06-08 18:04:08 -04:00
Thomas Harte
ae2993625c Add missing header. 2021-06-08 17:54:30 -04:00
Thomas Harte
0982141442 Corrects many minor errors. 2021-06-08 17:52:39 -04:00
Thomas Harte
85fab2abc4 Takes a swing at adding a square pixels toggle for Qt. 2021-06-08 17:37:46 -04:00
Thomas Harte
de3b37799c Switches to a static_cast. No need for reflection here. 2021-06-08 17:37:28 -04:00
Thomas Harte
70851f3b2d Resolve misplacement. 2021-06-07 21:43:26 -04:00
Thomas Harte
462bbf2e40 Exposes square pixels option on macOS. 2021-06-07 21:21:45 -04:00
Thomas Harte
778b9ef683 Ensures set_square_pixels is exposed, works around OpenGL aspect ratio bug. 2021-06-07 20:41:02 -04:00
Thomas Harte
96e7eb1bed Adds a use-square-pixels option for the Apple II. 2021-06-07 20:16:01 -04:00
Thomas Harte
05671f3553 Merge pull request #946 from TomHarte/OptionalOricColourROM
Introduces a new grammar for ROM requests.
2021-06-06 22:47:46 -04:00
Thomas Harte
6e4832f999 Ensures Oric honours absence of the colour ROM. 2021-06-06 22:43:53 -04:00
Thomas Harte
54e3332673 Ensure optionals appear at the end of any ROM request list. 2021-06-06 22:36:26 -04:00
Thomas Harte
6c559d7556 Fix lead-in text. 2021-06-06 22:02:11 -04:00
Thomas Harte
9165a85484 Correct wstring conversion. 2021-06-06 21:58:38 -04:00
Thomas Harte
98ada2588a Resolve name confusion. 2021-06-06 21:51:51 -04:00
Thomas Harte
43f686c22d Correct return type and map insertion. 2021-06-06 21:44:37 -04:00
Thomas Harte
4a2673d757 Make a prima facie attempt to adapt the Qt build. 2021-06-06 20:47:25 -04:00
Thomas Harte
f27e331462 Updates autotests to new RomFetcher world. 2021-06-06 20:34:55 -04:00
Thomas Harte
dd64aef910 Improves request construction and improves descriptions. 2021-06-06 20:25:26 -04:00
Thomas Harte
95971f39f1 Reintroduces full messaging to macOS. 2021-06-06 20:02:13 -04:00
Thomas Harte
83beb3c0e6 Introduces slightly-less manual ROM::Request::visit. 2021-06-06 18:28:02 -04:00
Thomas Harte
76335e5cf2 Factors out and slightly generalises textual descriptions of ROM::Descriptions. 2021-06-06 18:15:00 -04:00
Thomas Harte
4494320238 Corrects the macOS Swift side of things. 2021-06-06 14:56:43 -04:00
Thomas Harte
5acd97c860 Puts enough in place for a GUI-led installation process.
... and provides a lot of the Objective-C wiring necessary to expose that to Swift.
2021-06-06 14:24:38 -04:00
Thomas Harte
b0f551c307 Ensures only _missing_ ROMs are reported. 2021-06-05 21:09:35 -04:00
Thomas Harte
b6b3d845a3 Correct Apple IIe and Enhanced IIe startup. 2021-06-04 22:48:08 -04:00
Thomas Harte
505d84f336 Corrects Amstrad 664 and 6128 ROM collection. 2021-06-04 22:43:26 -04:00
Thomas Harte
1d5144b912 Correct no-interrupt signal. 2021-06-04 22:38:07 -04:00
Thomas Harte
deff91e460 Correct Electron name mapping. 2021-06-04 22:25:11 -04:00
Thomas Harte
afd8dc0915 Nudge just far enough to be able to launch again under macOS. 2021-06-04 22:24:31 -04:00
Thomas Harte
fbee74e1fe Avoids storing or printing a CRC if none is known. 2021-06-04 22:03:08 -04:00
Thomas Harte
ccd82591aa Reinstates SDL error message; adds expansion of ~. 2021-06-04 21:53:56 -04:00
Thomas Harte
64931e476d Completes transcription of ROM details with the Oric and MSX. 2021-06-04 19:50:49 -04:00
Thomas Harte
604a715a49 Transcribes the Spectrum, Electron, Master System and Vic-20 ROMs. 2021-06-04 19:45:47 -04:00
Thomas Harte
24757ef20c Transcribes the Macintosh, Atari ST, ColecoVision and ZX80/81 ROMs. 2021-06-04 19:24:57 -04:00
Thomas Harte
e36cc9e777 Transcribes the Apple II ROM descriptions. 2021-06-04 19:19:55 -04:00
Thomas Harte
2e999889bd Attempts to implement tree construction. 2021-06-04 19:03:07 -04:00
Thomas Harte
f4db4c3a73 Implements ROM::Request::validate.
It now also validates ROM sizes, so can no longer take a const Map.
2021-06-04 18:54:50 -04:00
Thomas Harte
d923fe72c0 Resolves various ROM selection warnings. 2021-06-03 22:46:47 -04:00
Thomas Harte
f05cdd5e34 With large swathes of implementation missing, compiles. 2021-06-03 22:39:18 -04:00
Thomas Harte
f9954619d4 Add missing header file. 2021-06-03 22:28:30 -04:00
Thomas Harte
0aa8c3c40d For SDL at least, advances to failed linking.
... and with error reporting currently AWOL.
2021-06-03 22:22:56 -04:00
Thomas Harte
a30eeaab6a Starts to introduce a new grammar for ROM requests.
They can be optional, and chained together in AND or OR combinations. A central catalogue knows the definitions of all ROMs.
2021-06-03 21:55:59 -04:00
Thomas Harte
3858e79579 Merge pull request #944 from TomHarte/SDLErrorReporting
Improve SDL failed-ROM reporting.
2021-05-30 19:50:53 -04:00
Thomas Harte
b4a5fa33b0 Improve SDL failed-ROM reporting.
Specifically to include all paths tried, and not use the plural for 'crc32' when only one is present.
2021-05-30 19:40:29 -04:00
Thomas Harte
2a6e9c5e8a Add readme for Enterprise ROM names. 2021-05-30 19:28:26 -04:00
Thomas Harte
488c2aed51 Merge pull request #939 from TomHarte/DragAndDropState
Accept insertion of state snapshots into existing windows
2021-05-16 20:47:36 -04:00
Thomas Harte
5483f979dc Merge branch 'master' into DragAndDropState 2021-05-16 20:42:44 -04:00
Thomas Harte
ea11f3826a Merge pull request #941 from TomHarte/LargeDSK
Adds support for Macintosh SCSI drive images.
2021-05-13 19:17:18 -04:00
Thomas Harte
ceae81a332 Add missing header. 2021-05-13 19:11:19 -04:00
Thomas Harte
50ea56e908 Adds support for Macintosh SCSI device images.
This is now in addition to the single-partition images previously supported.
2021-05-13 19:06:00 -04:00
Thomas Harte
bfb2f79cff That's two learning curves. 2021-05-10 21:33:40 -04:00
Thomas Harte
8268e8ee4c Ensures music survives a machine switch. 2021-05-08 20:46:17 -04:00
Thomas Harte
cb31e22f59 Merge branch 'master' into DragAndDropState 2021-05-08 20:41:44 -04:00
Thomas Harte
6752f4fd73 Merge pull request #940 from TomHarte/TighterTapeStop
Tightens automatic tape control timing.
2021-05-08 18:21:14 -04:00
Thomas Harte
22c31e4f55 Tightens automatic tape control timing. 2021-05-08 17:34:59 -04:00
Thomas Harte
c2ff64c1e0 Removes dangling OpenGL reference, attempts to ensure audio handover upon a machine change. 2021-05-08 14:42:43 -04:00
Thomas Harte
4db792591a macOS: ensure activity and options panels change upon a drag-and-drop state. 2021-05-08 14:35:57 -04:00
Thomas Harte
1290a8e32b SDL: Ensures joysticks, mouse, LEDs, etc, all update to a dragged state snapshot. 2021-05-08 13:30:07 -04:00
Thomas Harte
8ae38991b0 Factor out machine wiring. 2021-05-08 13:15:18 -04:00
Thomas Harte
6d40549c0c Merge branch 'master' into DragAndDropState 2021-05-07 21:56:36 -04:00
Thomas Harte
93d5c9a3c7 Tighten wording further. 2021-05-07 18:55:15 -04:00
Thomas Harte
9af6c0b37a Improves comment. 2021-05-06 12:57:32 -04:00
Thomas Harte
7e3528c692 Shunt the tech/URL stuff below the headline feature list. 2021-05-06 09:44:40 -04:00
Thomas Harte
41f2fc51be Clarify second sentence.
As per discussion at https://www.retrogameboards.com/t/clock-signal-a-multi-platform-emulator-that-focuses-on-a-better-user-experience/2375 — the previous could be read as "no emulator | or per-emulated-machine learning curve". But there is an emulator.
2021-05-06 09:43:19 -04:00
Thomas Harte
11228dc265 Merge pull request #937 from TomHarte/XKeySyms
Eliminate magic constants in Qt/X11 keyboard code.
2021-05-05 22:21:31 -04:00
Thomas Harte
ef50967793 Limit X11 linkage to Linux. 2021-05-05 22:17:24 -04:00
Thomas Harte
5f6c08b7e0 Avoid partial struct instantiation. 2021-05-05 22:00:50 -04:00
Thomas Harte
6cb23ec5be Tidy up and comment. 2021-05-05 21:58:54 -04:00
Thomas Harte
1bae70bcf8 Correct capitalisation. 2021-05-05 21:49:01 -04:00
Thomas Harte
9820591ba4 Corrects enum references. 2021-05-05 21:46:34 -04:00
Thomas Harte
77071b3c69 Adds KeySym -> key lookup. 2021-05-05 21:41:59 -04:00
Thomas Harte
335e839b31 Wrangles a single working call to XKeysymToKeycode. 2021-05-05 21:35:08 -04:00
Thomas Harte
6fe947b8b9 Fix class name, add constructor. 2021-05-05 19:17:23 -04:00
Thomas Harte
22b29e77a7 Add keyboard.cpp/h to the Qt project. 2021-05-05 19:06:25 -04:00
Thomas Harte
4858cfce6b Starts to factor out the keyboard mapper.
The more easily to clarify as to #includes, etc, and to allow for a relevant constructor.
2021-05-05 18:56:10 -04:00
Thomas Harte
8da3e91f5e Merge branch 'master' into XKeySyms 2021-05-03 22:23:55 -04:00
Thomas Harte
012235bfeb Merge pull request #936 from TomHarte/Style
Correct minor style errors.
2021-05-03 22:23:27 -04:00
Thomas Harte
052e284c33 Add overt fallthrough. 2021-05-03 22:17:43 -04:00
Thomas Harte
32e3dd71b1 Be overt in empty std::string construction. 2021-05-03 22:17:32 -04:00
Thomas Harte
95f4272919 Make sure size_t is visible. 2021-05-03 22:17:25 -04:00
Thomas Harte
00679b6135 t may be unused, per the if constexpr. 2021-05-03 22:17:19 -04:00
Thomas Harte
2c18bb4508 Make it overt that this can't return without a value. 2021-05-03 22:17:12 -04:00
Thomas Harte
0cf1c9040a Add missing fallthrough declaration. 2021-05-03 22:17:06 -04:00
Thomas Harte
9196341482 Retrenches: it seems nativeVirtualKey does what I want.
Hooray!
2021-05-03 21:45:53 -04:00
Thomas Harte
685140a4c2 Correct Qt -> QT. 2021-05-03 21:18:14 -04:00
Thomas Harte
1465b0ee4d Shunt X11 code to bottom of file, to avoid #include interference. 2021-05-03 21:15:20 -04:00
Thomas Harte
0bf6b765d3 Further namespace/name corrections. 2021-05-03 21:11:47 -04:00
Thomas Harte
4774676e2a Correct keypad symbols, push X11 into a namespace. 2021-05-03 21:09:01 -04:00
Thomas Harte
9c29655da2 Add x11extras as per use of <QX11Info>. 2021-05-03 20:43:22 -04:00
Thomas Harte
c8ab18f2b6 Add overt fallthrough. 2021-05-03 20:38:50 -04:00
Thomas Harte
8ebce466db Be overt in empty std::string construction. 2021-05-03 20:35:23 -04:00
Thomas Harte
1b39b17125 Make sure size_t is visible. 2021-05-03 20:33:25 -04:00
Thomas Harte
5a46853075 t may be unused, per the if constexpr. 2021-05-03 20:32:16 -04:00
Thomas Harte
48ad4d4c4c Make it overt that this can't return without a value. 2021-05-03 20:31:39 -04:00
Thomas Harte
056a036712 Add missing fallthrough declaration. 2021-05-03 20:31:13 -04:00
Thomas Harte
70eaa79108 Makes an attempt to use X11 KeySyms.
Rather than hard-coding a mapping.
2021-05-03 18:51:58 -04:00
Thomas Harte
20c814a4dd Factors out boilerplate around full-device sector images. 2021-05-01 21:10:46 -04:00
Thomas Harte
6a052e1900 Starts working on SDL drag-and-drop support for snapshots. 2021-04-30 22:56:13 -04:00
Thomas Harte
cecdf8584a Ensures proper propagation of will_change_owner. 2021-04-30 22:51:26 -04:00
Thomas Harte
4758bc8615 Attempts to support insertion of states into existing windows. 2021-04-30 21:37:41 -04:00
Thomas Harte
c906dc3c0a Merge pull request #935 from TomHarte/OricJoystick
Adds Altai-style joystick support for the Oric.
2021-04-29 20:15:42 -04:00
Thomas Harte
d1dcb41b6f Adds Altai-style joystick support. 2021-04-29 18:29:29 -04:00
Thomas Harte
96ac86a757 Merge pull request #934 from TomHarte/OricTapes
Relaxes Oric .tap signature check.
2021-04-29 18:14:36 -04:00
Thomas Harte
4919786825 Relaxes Oric .tap signature check. 2021-04-29 18:00:02 -04:00
Thomas Harte
24b4185714 Merge pull request #933 from TomHarte/SpectrumJoystick
Adds ZX Spectrum joystick support.
2021-04-28 21:08:36 -04:00
Thomas Harte
ad10d0037a Inverts the Game Controller Framework value of the y axis. 2021-04-28 20:31:35 -04:00
Thomas Harte
b6554c8255 Adds joystick support. 2021-04-28 20:19:01 -04:00
Thomas Harte
01dc83d0d6 Merge pull request #932 from MaddTheSane/xcodemaintenance
Xcode maintenance.
2021-04-27 19:53:51 -04:00
C.W. Betts
2fd08789ab Xcode maintenance. 2021-04-27 12:50:26 -06:00
Thomas Harte
bc9e529995 Merge pull request #931 from TomHarte/FieldName
This field is counted in half-cycles.
2021-04-26 21:33:38 -04:00
Thomas Harte
708c24cc57 This field is counted in half-cycles. 2021-04-26 21:20:32 -04:00
Thomas Harte
7fb3048257 Update AllDisk and AllTape. 2021-04-26 21:04:25 -04:00
Thomas Harte
9319f0525a Merge pull request #930 from TomHarte/SZX
Adds SZX support.
2021-04-26 20:57:06 -04:00
Thomas Harte
b7a62e0121 Adds SZX support.
Tweaking exposed Spectrum state object as relevant.
2021-04-26 20:47:28 -04:00
Thomas Harte
bd5dd9b9a3 Merge pull request #929 from TomHarte/SpectrumSnapshots
Adds loading of state snapshots for the ZX Spectrum
2021-04-26 17:44:02 -04:00
Thomas Harte
3348167c46 Ensures AY registers are conveyed. 2021-04-26 17:39:11 -04:00
Thomas Harte
700c505974 Ensures the ZX Spectrum properly reports its display type. 2021-04-25 21:16:22 -04:00
Thomas Harte
d403036d86 Reduce bounce at Spectrum startup. 2021-04-25 20:56:57 -04:00
Thomas Harte
5e08d7db39 Carries through paging state; avoids file rereads. 2021-04-25 20:46:49 -04:00
Thomas Harte
c34cb310a8 Switches to more straightforward handler for .z80-style compression. 2021-04-25 18:07:36 -04:00
Thomas Harte
8d86aa69bc Adds an assert to check handling of compressed data. 2021-04-25 18:02:31 -04:00
Thomas Harte
cc41ccc5f1 Adds RAM deserialisation. 2021-04-25 17:55:52 -04:00
Thomas Harte
e6252fe0ed Sneaks up towards loading RAM. 2021-04-25 17:34:43 -04:00
Thomas Harte
03577de675 Adds an empty vessel for .z80 support. 2021-04-25 16:54:34 -04:00
Thomas Harte
205518ba75 Switch to more efficient copy. 2021-04-25 16:51:07 -04:00
Thomas Harte
2510064218 Completes state object.
Subject to not yet dealing with last_fetches_ and last_contended_access_ correctly. Thought required.
2021-04-25 14:20:40 -04:00
Thomas Harte
0ef2806970 Adds just enough to ensure that border state gets through. 2021-04-25 14:16:35 -04:00
Thomas Harte
d80f03e369 Corrects longstanding deviation from naming convention. 2021-04-25 14:11:36 -04:00
Thomas Harte
fd271d920b Adds capture and forwarding of border colour. 2021-04-25 14:00:12 -04:00
Thomas Harte
2bbf8bc9fa Ensures 16/48kb snapshots are properly copied into place. 2021-04-25 13:27:11 -04:00
Thomas Harte
9b65d56ed0 Resolves potential flaw in POPping here. 2021-04-25 13:26:53 -04:00
Thomas Harte
a5098a60ec Attempts to get in-SNA software to start. 2021-04-25 13:18:26 -04:00
Thomas Harte
0ebd900e40 Baby steps: apply Z80 state.
As far as it currently is. Since SNA is leaving the PC at the default of 0x0000, this currently has no visible effect.
2021-04-25 13:03:24 -04:00
Thomas Harte
7aeb17ac92 Corrects HeaderDoc/etc directive. 2021-04-25 13:01:23 -04:00
Thomas Harte
cc78bfb229 Forwards most of the Z80 state. 2021-04-25 13:00:43 -04:00
Thomas Harte
485c2a866c Without yet a struct for Spectrum states, at least checks general wiring. 2021-04-24 23:38:00 -04:00
Thomas Harte
5b419ca5bf Add State folder to Scons and Qt projects. 2021-04-24 23:25:08 -04:00
Thomas Harte
14ae579fca Add further note to future self. 2021-04-24 23:19:41 -04:00
Thomas Harte
1c2ea0d7fe unique_ptr makes more sense here. 2021-04-24 23:19:30 -04:00
Thomas Harte
e7a9ae18a1 Introduce further default state. 2021-04-24 23:18:00 -04:00
Thomas Harte
d61f478a39 Basic sketch for state snapshots: an extra field on Target.
I think it doesn't make sense for states to own a target as that complicates the concept of Media. Plus they're distinct because it makes sense to have only one per Target. Let's see how this pans out.
2021-04-24 23:17:47 -04:00
Thomas Harte
9cc747b3e2 Resolves potential source of errors: specifying incorrect table size.
(Having made exactly this mistake with the ZX Spectrum)
2021-04-24 12:10:28 -04:00
Thomas Harte
2f223f7db2 Spectrum emulation is no longer +2a/+3 specific. 2021-04-23 22:55:54 -04:00
Thomas Harte
17f11a3be3 Merge pull request #928 from TomHarte/ContentionTests
Add timing tests, fix +3 discrepancy.
2021-04-23 22:54:34 -04:00
Thomas Harte
37dcf61130 Add timing tests, fix +3 discrepancy. 2021-04-23 22:29:57 -04:00
Thomas Harte
856ebfacca Merge pull request #927 from TomHarte/SimplifiedTiming
Moves horizontal sync on the 48kb.
2021-04-21 19:50:40 -04:00
Thomas Harte
9731fdd33b Moves horizontal sync on the 48kb. 2021-04-21 19:46:44 -04:00
Thomas Harte
5ea605ccf7 Merge pull request #926 from TomHarte/SimplifiedTiming
Attempts more cleanly to express ZX Spectrum timing.
2021-04-21 19:46:23 -04:00
Thomas Harte
d0c789ff9a Locks declarative form of contention closer to regular expressions. 2021-04-21 19:37:36 -04:00
Thomas Harte
9baa861742 Simplifies timing calculation expression. 2021-04-21 19:18:07 -04:00
Thomas Harte
30a1a53c97 Merge pull request #925 from TomHarte/ZXROMSpeed
Corrects timing error in Spectrum 48kb and 128kb ROM accesses.
2021-04-21 18:54:16 -04:00
Thomas Harte
bdb1b7e77c Reinstate the +2 as the default Spectrum. 2021-04-21 18:49:39 -04:00
Thomas Harte
9293bcbc88 Exclude the ROM from contention on 48kb and 128kb models. 2021-04-21 18:49:18 -04:00
Thomas Harte
c481f475e7 Merge pull request #923 from TomHarte/STStartup
Resolves failure of ST to startup
2021-04-20 22:43:55 -04:00
Thomas Harte
ef01471e17 Ensures the DMA controller remains clocked. 2021-04-20 22:34:13 -04:00
Thomas Harte
73c8157197 Retain 6850 time tracking at all times. 2021-04-20 22:26:43 -04:00
Thomas Harte
af1dc2d3b2 Switches to correct non-value sentinel. 2021-04-20 21:56:58 -04:00
Thomas Harte
8f6b3feee1 Merge pull request #921 from TomHarte/Plus2aDefault
Switches default machine back to +2a.
2021-04-19 22:15:48 -04:00
Thomas Harte
a20f5528b7 Switches default machine back to +2a. 2021-04-19 22:04:49 -04:00
Thomas Harte
f48876d80e Merge pull request #920 from TomHarte/AppleIIVirtual
Disambiguates `reset_all_keys`.
2021-04-19 22:03:01 -04:00
Thomas Harte
db52f13c32 Disambiguates reset_all_keys. 2021-04-19 21:49:06 -04:00
Thomas Harte
2590769d3f Merge pull request #919 from TomHarte/XcodeProjectTweaks
Increases warnings, cleans up a touch.
2021-04-19 21:33:04 -04:00
Thomas Harte
5667dcac36 Increases warnings, cleans up a touch. 2021-04-19 21:28:12 -04:00
Thomas Harte
bec71ead39 Merge pull request #918 from TomHarte/macOS13
Reintroduces macOS 10.13 support.
2021-04-19 21:12:57 -04:00
Thomas Harte
e4d9022d37 Returns deployment target to 10.13. 2021-04-19 20:57:56 -04:00
Thomas Harte
572be48f38 Attempts to add an early exit for non-Metal Macs.
This will be necessary only prior to 10.14.
2021-04-19 20:55:25 -04:00
Thomas Harte
6f4ccebfa1 Merge pull request #917 from TomHarte/InterruptAddress
Put the program counter on the bus during interrupt acknowledge.
2021-04-19 20:08:22 -04:00
Thomas Harte
77fcf52d27 Purely style: remove some redundant nullptrs. 2021-04-19 18:53:00 -04:00
Thomas Harte
79c2bc1fd7 Put the program counter on the bus during interrupt acknowledge. 2021-04-19 18:43:50 -04:00
Thomas Harte
76370d9418 Merge pull request #916 from TomHarte/OffByOne
Corrects off-by-one timing errors in the ZX Spectrum.
2021-04-18 20:25:13 -04:00
Thomas Harte
7bac18bd65 Address bus load time is not + 1/2. 2021-04-18 18:41:24 -04:00
Thomas Harte
704737144a Corrects all interrupt timing for sign and off-by-one errors. 2021-04-18 18:40:44 -04:00
Thomas Harte
2a9c73a1d3 Merge pull request #915 from TomHarte/SpectrumSDLOptions
Adds display of Spectrum command-line options.
2021-04-18 12:08:02 -04:00
Thomas Harte
e87e851401 Add a redundant but idiomatic initial value. 2021-04-18 11:56:22 -04:00
Thomas Harte
80d4846a27 Respond with 0xff during an interrupt acknowledge. 2021-04-18 11:56:00 -04:00
Thomas Harte
9fd53c9c91 Adds the ZX Spectrum to ::AllMachines. 2021-04-17 23:06:37 -04:00
Thomas Harte
53eae873d8 Merge pull request #913 from TomHarte/LowerModelTiming
Brings timings into line with WoS specs.
2021-04-16 22:45:54 -04:00
Thomas Harte
93422f4b1c Brings timings into line with WoS specs. 2021-04-16 22:40:51 -04:00
Thomas Harte
06cedb2e50 Merge pull request #912 from TomHarte/128kDecoding
Corrects Spectrum 128kb partial decoding.
2021-04-16 22:02:25 -04:00
Thomas Harte
7fdb1d848b Corrects Spectrum 128kb partial decoding. 2021-04-16 21:54:52 -04:00
Thomas Harte
246fd9442f Merge pull request #911 from TomHarte/48kbSpectrum
Adds the 48kb and 128kb Spectrums.
2021-04-15 22:25:07 -04:00
Thomas Harte
eb99a64b29 Adds new Spectrum models to Qt UI. 2021-04-15 22:20:34 -04:00
Thomas Harte
d7954a4cb1 Tweaks timing a little. 2021-04-15 21:51:49 -04:00
Thomas Harte
ef636da866 Attempts 48/128kb floating bus behaviour. 2021-04-15 21:19:21 -04:00
Thomas Harte
fa18b06dbf Correct get_floating_value to be consistent in out-of-bounds behaviour. 2021-04-15 21:13:36 -04:00
Thomas Harte
349b9ce502 Don't post contended accesses other than on the +2a/+3.
Those machines have an actual latch for this stuff, the others don't.
2021-04-15 21:13:06 -04:00
Thomas Harte
b2cf121410 Regresses default to the more-compatible +2. 2021-04-15 19:31:45 -04:00
Thomas Harte
71cf63bd35 Corrects internal cycle contention. 2021-04-15 19:17:11 -04:00
Thomas Harte
d1bb3aada4 Attempts to complete the in-machine application of contention. 2021-04-15 18:57:34 -04:00
Thomas Harte
b4214c6e08 Blocks off the AY from inputs in 48kb mode. 2021-04-15 18:04:16 -04:00
Thomas Harte
f5c7746493 Extends fast loading support to the just-introduced models. 2021-04-15 17:31:42 -04:00
Thomas Harte
f10ec80153 Gets started on different video timings. 2021-04-14 22:23:27 -04:00
Thomas Harte
0af405aa46 Starts working in the 48kb and 128kb Spectrums. 2021-04-14 21:37:10 -04:00
Thomas Harte
cf481effa6 Merge pull request #910 from TomHarte/FastContention
Establishes that the 48/128kb contention patterns can be derived from my partial machine cycles alone.
2021-04-14 20:21:52 -04:00
Thomas Harte
a1511f9600 Establishes that the 48/128kb contention patterns can be derived from my partial machine cycles alone. 2021-04-14 20:15:40 -04:00
Thomas Harte
325e2b3941 Merge pull request #902 from TomHarte/Z80Lines
Spell out, test and correct Z80 bus activity.
2021-04-13 22:22:26 -04:00
Thomas Harte
7017324d60 r_step is obsolete now that I know that [DD/FD]CB don't have a refresh cycle. 2021-04-13 22:17:30 -04:00
Thomas Harte
deb5d69ac7 Consolidates macros. 2021-04-13 22:11:28 -04:00
Thomas Harte
68a04f4e6a Adds IN/OUT I/D [R] to complete tests. 2021-04-13 22:00:24 -04:00
Thomas Harte
0d61902b10 Adds CP[I/D/IR/DR] tests. 2021-04-13 20:03:11 -04:00
Thomas Harte
3eec210b30 Adds LDI/LDD/LDIR/LDDR tests. 2021-04-13 20:00:29 -04:00
Thomas Harte
5998f3b35b Corrects LD[I/D/IR/DR] timing.
Macro cleanup to come.
2021-04-13 20:00:18 -04:00
Thomas Harte
869567fdd9 Corrects EX (SP), HL breakdown. 2021-04-13 19:45:48 -04:00
Thomas Harte
2e70b5eb9f Advances to EX (SP), HL, leaving only [LD/CP/IN/OT][I/D]{R}. 2021-04-13 19:45:29 -04:00
Thomas Harte
8a3bfb8672 Adds an IN/OUT test. 2021-04-13 17:55:51 -04:00
Thomas Harte
06f1e64177 Advances to IO. 2021-04-12 21:41:20 -04:00
Thomas Harte
b42780173a Establishes that there really is no Read4 and Read4Pre distinction.
Will finish these unit tests, then clean up.
2021-04-12 20:54:10 -04:00
Thomas Harte
36c8821c4c Reaches the halfway point in tests. 2021-04-12 17:29:03 -04:00
Thomas Harte
947de2d54a Switches five-cycle read to a post hoc pause. 2021-04-12 17:17:08 -04:00
Thomas Harte
9347fe5f44 Advances to next failing test: LD (ii+n), n. 2021-04-12 17:11:58 -04:00
Thomas Harte
e82367def3 Switches to test-conformant behaviour for (IX/IY+n) opcode fetches. 2021-04-11 23:01:00 -04:00
Thomas Harte
9cde7c12ba Shifts responsibility for refresh into the fetch-decode-execute sequence. 2021-04-11 22:50:24 -04:00
Thomas Harte
015556cc91 Switch (ii+n) to Read4Pre. 2021-04-11 10:26:14 -04:00
Thomas Harte
47c5a243aa Restructures, the better to explore errors. 2021-04-10 21:32:42 -04:00
Thomas Harte
070e359d82 Introduces failing test for BIT b, (ii+n). 2021-04-10 18:00:23 -04:00
Thomas Harte
b397059d5e Moves read time in Read4Pre. 2021-04-10 17:54:20 -04:00
Thomas Harte
400f54e508 Introduces failing test for bit b, (hl). 2021-04-10 12:04:48 -04:00
Thomas Harte
e0736435f8 Makes assumption that the address bus just holds its value during an internal operation. 2021-04-10 12:00:53 -04:00
Thomas Harte
b09c5538c6 Adds failing test for simple (ii+n) tests. 2021-04-09 21:28:35 -04:00
Thomas Harte
ce3d2913bf Advances to 9 source table rows tested out of 37. 2021-04-09 20:38:17 -04:00
Thomas Harte
87202a2a27 Add two further tests, add checking of collected data size for all tests. 2021-04-09 18:32:03 -04:00
Thomas Harte
818a4dff25 Corrects ADD HL, dd test.
Or, at least, likely corrects. The bus cycle breakdown in the Z80 data sheet implies these accesses should come after completion of the refresh cycle, not during its long tail, so I think +1 is correct.
2021-04-08 22:23:15 -04:00
Thomas Harte
eacffa49f5 Exposes IR during 'internal' operations. 2021-04-08 22:22:26 -04:00
Thomas Harte
9e506c3206 Adds failing ADD hl, dd test. 2021-04-08 22:19:22 -04:00
Thomas Harte
29cf80339a Corrects too-short buffer. 2021-04-08 22:15:03 -04:00
Thomas Harte
50f53f7d97 Adds INC/DEC rr and LD SP, HL tests. 2021-04-08 22:14:53 -04:00
Thomas Harte
73fbd89c85 Correct opcodes, ability to terminate on a single-cycle contention. 2021-04-08 22:09:33 -04:00
Thomas Harte
f74fa06f2d Introduces failing test for LD [A/I/R], [A/I/R]. 2021-04-08 20:28:55 -04:00
Thomas Harte
ee989ab762 Fills in the rest of the simple two-byte instructions. 2021-04-08 20:13:52 -04:00
Thomas Harte
818655a9b6 Starts on two-bus-cycle instructions, correcting validators. 2021-04-08 20:01:46 -04:00
Thomas Harte
57a7e0834f Corrects sampling of MREQ. 2021-04-08 19:21:35 -04:00
Thomas Harte
cd787486d2 Tests all of the single-byte, no-access opcodes. 2021-04-07 22:07:52 -04:00
Thomas Harte
67fd6787a6 Builds what I think I need to validate Z80 address, MREQ, IOREQ and RFSH. 2021-04-07 21:57:40 -04:00
Thomas Harte
627b96f73c Merge branch 'master' into Z80Lines 2021-04-07 21:02:15 -04:00
Thomas Harte
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
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
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
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
436 changed files with 3306984 additions and 4917 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

@@ -23,10 +23,19 @@ namespace Activity {
*/
class Observer {
public:
/// Provides hints as to the sort of information presented on an LED.
enum LEDPresentation: uint8_t {
/// This LED informs the user of some sort of persistent state, e.g. scroll lock.
/// If this flag is absent then the LED describes an ephemeral state, such as media access.
Persistent = (1 << 0),
};
/// Announces to the receiver that there is an LED of name @c name.
virtual void register_led([[maybe_unused]] const std::string &name) {}
virtual void register_led([[maybe_unused]] const std::string &name, [[maybe_unused]] uint8_t presentation = 0) {}
/// Announces to the receiver that there is a drive of name @c name.
///
/// If a drive has the same name as an LED, that LED goes with this drive.
virtual void register_drive([[maybe_unused]] const std::string &name) {}
/// Informs the receiver of the new state of the LED with name @c name.

View File

@@ -14,16 +14,20 @@ namespace Analyser {
enum class Machine {
AmstradCPC,
AppleII,
AppleIIgs,
Atari2600,
AtariST,
Amiga,
ColecoVision,
Electron,
Enterprise,
Macintosh,
MasterSystem,
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

@@ -0,0 +1,25 @@
//
// StaticAnalyser.cpp
// Clock Signal
//
// Created by Thomas Harte on 16/07/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "StaticAnalyser.hpp"
#include "Target.hpp"
Analyser::Static::TargetList Analyser::Static::Amiga::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
// This analyser can comprehend disks and mass-storage devices only.
if(media.disks.empty()) return {};
// As there is at least one usable media image, wave it through.
Analyser::Static::TargetList targets;
using Target = Analyser::Static::Amiga::Target;
auto *const target = new Target();
target->media = media;
targets.push_back(std::unique_ptr<Analyser::Static::Target>(target));
return targets;
}

View File

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

View File

@@ -0,0 +1,27 @@
//
// Target.hpp
// Clock Signal
//
// Created by Thomas Harte on 16/07/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_Amiga_Target_h
#define Analyser_Static_Amiga_Target_h
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
namespace Analyser {
namespace Static {
namespace Amiga {
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
Target() : Analyser::Static::Target(Machine::Amiga) {}
};
}
}
}
#endif /* Analyser_Static_Amiga_Target_h */

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::ROM01;
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

@@ -0,0 +1,84 @@
//
// StaticAnalyser.cpp
// Clock Signal
//
// Created by Thomas Harte on 24/06/2021.
// Copyright 2021 Thomas Harte. All rights reserved.
//
#include "StaticAnalyser.hpp"
#include "Target.hpp"
#include "../../../Storage/Disk/Parsers/FAT.hpp"
#include <algorithm>
namespace {
bool insensitive_equal(const std::string &lhs, const std::string &rhs) {
return std::equal(
lhs.begin(), lhs.end(),
rhs.begin(), rhs.end(),
[] (char l, char r) {
return tolower(l) == tolower(r);
});
}
}
Analyser::Static::TargetList Analyser::Static::Enterprise::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
// This analyser can comprehend disks only.
if(media.disks.empty()) return {};
// Otherwise, assume a return will happen.
Analyser::Static::TargetList targets;
using Target = Analyser::Static::Enterprise::Target;
auto *const target = new Target;
target->media = media;
// Always require a BASIC.
target->basic_version = Target::BASICVersion::Any;
// Inspect any supplied disks.
if(!media.disks.empty()) {
// DOS will be needed.
target->dos = Target::DOS::EXDOS;
// Grab the volume information, which includes the root directory.
auto volume = Storage::Disk::FAT::GetVolume(media.disks.front());
if(volume) {
// If there's an EXDOS.INI then this disk should be able to boot itself.
// If not but if there's only one visible .COM or .BAS, automatically load
// that. Otherwise, issue a :DIR.
using File = Storage::Disk::FAT::File;
const File *selected_file = nullptr;
bool has_exdos_ini = false;
bool did_pick_file = false;
for(const auto &file: (*volume).root_directory) {
if(insensitive_equal(file.name, "exdos") && insensitive_equal(file.extension, "ini")) {
has_exdos_ini = true;
break;
}
if(!(file.attributes & File::Attribute::Hidden) &&
(insensitive_equal(file.extension, "com") || insensitive_equal(file.extension, "bas"))
) {
did_pick_file = !selected_file;
selected_file = &file;
}
}
if(!has_exdos_ini) {
if(did_pick_file) {
target->loading_command = std::string("run \"") + selected_file->name + "." + selected_file->extension + "\"\n";
} else {
target->loading_command = ":dir\n";
}
}
}
}
targets.push_back(std::unique_ptr<Analyser::Static::Target>(target));
return targets;
}

View File

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

View File

@@ -0,0 +1,57 @@
//
// Target.hpp
// Clock Signal
//
// Created by Thomas Harte on 14/06/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_Enterprise_Target_h
#define Analyser_Static_Enterprise_Target_h
#include "../../../Reflection/Enum.hpp"
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
#include <string>
namespace Analyser {
namespace Static {
namespace Enterprise {
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(Model, Enterprise64, Enterprise128, Enterprise256);
ReflectableEnum(EXOSVersion, v10, v20, v21, v23, Any);
ReflectableEnum(BASICVersion, v10, v11, v21, Any, None);
ReflectableEnum(DOS, EXDOS, None);
ReflectableEnum(Speed, FourMHz, SixMHz);
Model model = Model::Enterprise128;
EXOSVersion exos_version = EXOSVersion::Any;
BASICVersion basic_version = BASICVersion::None;
DOS dos = DOS::None;
Speed speed = Speed::FourMHz;
std::string loading_command;
Target() : Analyser::Static::Target(Machine::Enterprise) {
if(needs_declare()) {
AnnounceEnum(Model);
AnnounceEnum(EXOSVersion);
AnnounceEnum(BASICVersion);
AnnounceEnum(DOS);
AnnounceEnum(Speed);
DeclareField(model);
DeclareField(exos_version);
DeclareField(basic_version);
DeclareField(dos);
DeclareField(speed);
}
}
};
}
}
}
#endif /* Analyser_Static_Enterprise_Target_h */

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

@@ -15,34 +15,40 @@
// Analysers
#include "Acorn/StaticAnalyser.hpp"
#include "Amiga/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"
#include "Commodore/StaticAnalyser.hpp"
#include "DiskII/StaticAnalyser.hpp"
#include "Enterprise/StaticAnalyser.hpp"
#include "Macintosh/StaticAnalyser.hpp"
#include "MSX/StaticAnalyser.hpp"
#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/AmigaADF.hpp"
#include "../../Storage/Disk/DiskImage/Formats/AppleDSK.hpp"
#include "../../Storage/Disk/DiskImage/Formats/CPCDSK.hpp"
#include "../../Storage/Disk/DiskImage/Formats/D64.hpp"
#include "../../Storage/Disk/DiskImage/Formats/MacintoshIMG.hpp"
#include "../../Storage/Disk/DiskImage/Formats/G64.hpp"
#include "../../Storage/Disk/DiskImage/Formats/DMK.hpp"
#include "../../Storage/Disk/DiskImage/Formats/FAT12.hpp"
#include "../../Storage/Disk/DiskImage/Formats/HFE.hpp"
#include "../../Storage/Disk/DiskImage/Formats/MSA.hpp"
#include "../../Storage/Disk/DiskImage/Formats/MSXDSK.hpp"
#include "../../Storage/Disk/DiskImage/Formats/NIB.hpp"
#include "../../Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp"
#include "../../Storage/Disk/DiskImage/Formats/SSD.hpp"
@@ -51,8 +57,15 @@
#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/DSK.hpp"
#include "../../Storage/MassStorage/Formats/HFV.hpp"
// State Snapshots
#include "../../Storage/State/SNA.hpp"
#include "../../Storage/State/SZX.hpp"
#include "../../Storage/State/Z80.hpp"
// Tapes
#include "../../Storage/Tape/Formats/CAS.hpp"
#include "../../Storage/Tape/Formats/CommodoreTAP.hpp"
@@ -62,83 +75,114 @@
#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"
using namespace Analyser::Static;
static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::IntType &potential_platforms) {
Media result;
namespace {
std::string get_extension(const std::string &name) {
// Get the extension, if any; it will be assumed that extensions are reliable, so an extension is a broad-phase
// test as to file format.
std::string::size_type final_dot = file_name.find_last_of(".");
if(final_dot == std::string::npos) return result;
std::string extension = file_name.substr(final_dot + 1);
std::string::size_type final_dot = name.find_last_of(".");
if(final_dot == std::string::npos) return name;
std::string extension = name.substr(final_dot + 1);
std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
return extension;
}
#define Insert(list, class, platforms) \
list.emplace_back(new Storage::class(file_name));\
}
static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::IntType &potential_platforms) {
Media result;
const std::string extension = get_extension(file_name);
#define InsertInstance(list, instance, platforms) \
list.emplace_back(instance);\
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("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn) // ADF (Acorn)
Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AmigaADF>, TargetPlatform::Amiga) // ADF (Amiga)
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 | TargetPlatform::Oric) // DSK (Amstrad CPC)
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)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MSXDSK>, TargetPlatform::MSX) // DSK (MSX)
Format("dsk", result.mass_storage_devices, MassStorage::HFV, TargetPlatform::Macintosh) // DSK (Macintosh, hard disk, single volume image)
Format("dsk", result.mass_storage_devices, MassStorage::DSK, TargetPlatform::Macintosh) // DSK (Macintosh, hard disk, full device image)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::FAT12>, TargetPlatform::MSX) // DSK (MSX)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::OricMFMDSK>, TargetPlatform::Oric) // DSK (Oric)
Format("g64", result.disks, Disk::DiskImageHolder<Storage::Disk::G64>, TargetPlatform::Commodore) // G64
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)
Format("img", result.disks, Disk::DiskImageHolder<Storage::Disk::FAT12>, TargetPlatform::Enterprise) // IMG (Enterprise/MS-DOS style)
Format("msa", result.disks, Disk::DiskImageHolder<Storage::Disk::MSA>, TargetPlatform::AtariST) // MSA
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(...) {}
}
}
@@ -146,7 +190,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
@@ -154,14 +198,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;
}
@@ -173,34 +219,59 @@ Media Analyser::Static::GetMedia(const std::string &file_name) {
TargetList Analyser::Static::GetTargets(const std::string &file_name) {
TargetList targets;
const std::string extension = get_extension(file_name);
// Check whether the file directly identifies a target; if so then just return that.
#define Format(ext, class) \
if(extension == ext) { \
try { \
auto target = Storage::State::class::load(file_name); \
if(target) { \
targets.push_back(std::move(target)); \
return targets; \
} \
} catch(...) {} \
}
Format("sna", SNA);
Format("szx", SZX);
Format("z80", Z80);
#undef TryInsert
// Otherwise:
//
// Collect all disks, tapes ROMs, etc as can be extrapolated from this file, forming the
// union of all platforms this file might be a target for.
TargetPlatform::IntType potential_platforms = 0;
Media media = GetMediaAndPlatforms(file_name, potential_platforms);
// Hand off to platform-specific determination of whether these things are actually compatible and,
// if so, how to load them.
#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
// Hand off to platform-specific determination of whether these
// things are actually compatible and, if so, how to load them.
#define Append(x) if(potential_platforms & TargetPlatform::x) {\
auto new_targets = x::GetTargets(media, file_name, potential_platforms);\
std::move(new_targets.begin(), new_targets.end(), std::back_inserter(targets));\
}
Append(Acorn);
Append(AmstradCPC);
Append(AppleII);
Append(AppleIIgs);
Append(Amiga);
Append(Atari2600);
Append(AtariST);
Append(Coleco);
Append(Commodore);
Append(DiskII);
Append(Enterprise);
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

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

View File

@@ -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,10 @@
#define ClockReceiver_hpp
#include "ForceInline.hpp"
#include <algorithm>
#include <cstdint>
#include <limits>
/*
Informal pattern for all classes that run from a clock cycle:
@@ -136,8 +139,11 @@ template <class T> class WrappedInt {
forceinline constexpr bool operator !() const { return !length_; }
// bool operator () is not supported because it offers an implicit cast to int, which is prone silently to permit misuse
/// @returns The underlying int, cast to an integral type of your choosing.
template<typename Type = IntType> forceinline constexpr Type as() const { return Type(length_); }
/// @returns The underlying int, converted to an integral type of your choosing, clamped to that int's range.
template<typename Type = IntType> forceinline constexpr Type as() const {
const auto clamped = std::clamp(length_, IntType(std::numeric_limits<Type>::min()), IntType(std::numeric_limits<Type>::max()));
return Type(clamped);
}
/// @returns The underlying int, in its native form.
forceinline constexpr IntType as_integral() const { return length_; }
@@ -176,6 +182,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 +204,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) {}
@@ -205,7 +217,7 @@ class HalfCycles: public WrappedInt<HalfCycles> {
/*!
Severs from @c this the effect of dividing by @c divisor; @c this will end up with
the value of @c this modulo @c divisor and @c divided by @c divisor is returned.
the value of @c this modulo @c divisor . @c this divided by @c divisor is returned.
*/
forceinline Cycles divide_cycles(const Cycles &divisor) {
const HalfCycles half_divisor = HalfCycles(divisor);
@@ -214,6 +226,15 @@ class HalfCycles: public WrappedInt<HalfCycles> {
return result;
}
/*!
Equivalent to @c divide_cycles(Cycles(1)) but faster.
*/
forceinline Cycles divide_cycles() {
const Cycles result(length_ >> 1);
length_ &= 1;
return result;
}
private:
friend WrappedInt;
void fill(Cycles &result) {

View File

@@ -0,0 +1,48 @@
//
// DeferredValue.hpp
// Clock Signal
//
// Created by Thomas Harte on 07/08/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef DeferredValue_h
#define DeferredValue_h
/*!
Provides storage for a single deferred value: one with a current value and a certain number
of future values.
*/
template <int DeferredDepth, typename ValueT> class DeferredValue {
private:
static_assert(sizeof(ValueT) <= 4);
constexpr int elements_per_uint32 = sizeof(uint32_t) / sizeof(ValueT);
constexpr int unit_shift = sizeof(ValueT) * 8;
constexpr int insert_shift = (DeferredDepth & (elements_per_uint32 - 1)) * unit_shift;
constexpr uint32_t insert_mask = ~(0xffff'ffff << insert_shift);
std::array<uint32_t, (DeferredDepth + elements_per_uint32 - 1) / elements_per_uint32> backlog;
public:
/// @returns the current value.
ValueT value() const {
return uint8_t(backlog[0]);
}
/// Advances to the next enqueued value.
void advance() {
for(size_t c = 0; c < backlog.size() - 1; c--) {
backlog[c] = (backlog[c] >> unit_shift) | (backlog[c+1] << (32 - unit_shift));
}
backlog[backlog.size() - 1] >>= unit_shift;
}
/// Inserts a new value, replacing whatever is currently at the end of the queue.
void insert(ValueT value) {
backlog[DeferredDepth / elements_per_uint32] =
(backlog[DeferredDepth / elements_per_uint32] & insert_mask) | (value << insert_shift);
}
};
#endif /* DeferredValue_h */

View File

@@ -9,7 +9,9 @@
#ifndef JustInTime_h
#define JustInTime_h
#include "ClockReceiver.hpp"
#include "../Concurrency/AsyncTaskQueue.hpp"
#include "ClockingHintSource.hpp"
#include "ForceInline.hpp"
/*!
@@ -21,44 +23,150 @@
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 * multiplier;
if(time_until_event_ <= LocalTimeScale(0)) {
time_overrun_ = time_until_event_ / divider;
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 {
if constexpr (divider == 1) {
return time_since_update_;
}
return TargetTimeScale(time_since_update_.as_integral() / divider);
}
/// @returns the amount of time since the object was last flushed, plus the local time scale @c offset,
/// converted to the target time scale.
[[nodiscard]] forceinline TargetTimeScale time_since_flush(LocalTimeScale offset) const {
if constexpr (divider == 1) {
return time_since_update_ + offset;
}
return TargetTimeScale((time_since_update_ + offset).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 +178,96 @@ 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_ / divider;
}
forceinline void operator += (const LocalTimeScale &rhs) {
/// 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_;
}
/// Indicates the amount of time, in the local time scale, until the first local slot that falls wholly
/// after @c duration, if that delay were to occur in @c offset units of time from now.
[[nodiscard]] forceinline LocalTimeScale back_map(TargetTimeScale duration, TargetTimeScale offset) const {
// A 1:1 mapping is easy.
if constexpr (multiplier == 1 && divider == 1) {
object_.run_for(TargetTimeScale(rhs));
return;
return duration;
}
if constexpr (multiplier == 1) {
accumulated_time_ += rhs;
} else {
accumulated_time_ += rhs * multiplier;
}
// Work out when this query is placed, and the time to which it relates
const auto base = time_since_update_ + offset * divider;
const auto target = base + duration * divider;
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);
// Figure out the number of whole input steps that is required to get
// past target, and subtract the number of whole input steps necessary
// to get to base.
const auto steps_to_base = base.as_integral() / multiplier;
const auto steps_to_target = (target.as_integral() + divider - 1) / multiplier;
return LocalTimeScale(steps_to_target - steps_to_base);
}
/// Updates this template's record of the next sequence point.
void update_sequence_point() {
if constexpr (has_sequence_points<T>::value) {
// Keep a fast path where no conversions will be applied; if conversions are
// going to be applied then do a direct max -> max translation rather than
// allowing the arithmetic to overflow.
if constexpr (divider == 1 && std::is_same_v<LocalTimeScale, TargetTimeScale>) {
time_until_event_ = object_.get_next_sequence_point();
} else {
const auto time = object_.get_next_sequence_point();
if(time == TargetTimeScale::max()) {
time_until_event_ = LocalTimeScale::max();
} else {
time_until_event_ = time * divider;
}
}
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

@@ -276,7 +276,10 @@ void WD1770::posit_event(int new_event_type) {
goto test_type1_type;
begin_type1_spin_up:
if((command_&0x08) || get_drive().get_motor_on()) goto test_type1_type;
if((command_&0x08) || get_drive().get_motor_on()) {
set_motor_on(true);
goto test_type1_type;
}
SPIN_UP();
test_type1_type:
@@ -387,7 +390,10 @@ void WD1770::posit_event(int new_event_type) {
distance_into_section_ = 0;
if((command_&0x08) && has_motor_on_line()) goto test_type2_delay;
if(!has_motor_on_line() && !has_head_load_line()) goto test_type2_delay;
if(!has_motor_on_line() && !has_head_load_line()) {
if(has_motor_on_line()) set_motor_on(true);
goto test_type2_delay;
}
if(has_motor_on_line()) goto begin_type2_spin_up;
goto begin_type2_load_head;

View File

@@ -10,8 +10,6 @@
#define _522_hpp
#include <cstdint>
#include <typeinfo>
#include <cstdio>
#include "Implementation/6522Storage.hpp"
@@ -37,22 +35,24 @@ enum Line {
class PortHandler {
public:
/// Requests the current input value of @c port from the port handler.
uint8_t get_port_input([[maybe_unused]] Port port) { return 0xff; }
uint8_t get_port_input([[maybe_unused]] Port port) {
return 0xff;
}
/// Sets the current output value of @c port and provides @c direction_mask, indicating which pins are marked as output.
void set_port_output([[maybe_unused]] Port port, [[maybe_unused]] uint8_t value, [[maybe_unused]] uint8_t direction_mask) {}
void set_port_output([[maybe_unused]] Port port, [[maybe_unused]] uint8_t value, [[maybe_unused]] uint8_t direction_mask) {}
/// Sets the current logical output level for line @c line on port @c port.
void set_control_line_output([[maybe_unused]] Port port, [[maybe_unused]] Line line, [[maybe_unused]] bool value) {}
void set_control_line_output([[maybe_unused]] Port port, [[maybe_unused]] Line line, [[maybe_unused]] bool value) {}
/// Sets the current logical value of the interrupt line.
void set_interrupt_status([[maybe_unused]] bool status) {}
void set_interrupt_status([[maybe_unused]] bool status) {}
/// Provides a measure of time elapsed between other calls.
void run_for([[maybe_unused]] HalfCycles duration) {}
void run_for([[maybe_unused]] HalfCycles duration) {}
/// Receives passed-on flush() calls from the 6522.
void flush() {}
void flush() {}
};
/*!
@@ -88,9 +88,9 @@ class IRQDelegatePortHandler: public PortHandler {
Consumers should derive their own curiously-recurring-template-pattern subclass,
implementing bus communications as required.
*/
template <class T> class MOS6522: public MOS6522Storage {
template <class BusHandlerT> class MOS6522: public MOS6522Storage {
public:
MOS6522(T &bus_handler) noexcept : bus_handler_(bus_handler) {}
MOS6522(BusHandlerT &bus_handler) noexcept : bus_handler_(bus_handler) {}
MOS6522(const MOS6522 &) = delete;
/*! Sets a register value. */
@@ -100,7 +100,7 @@ template <class T> class MOS6522: public MOS6522Storage {
uint8_t read(int address);
/*! @returns the bus handler. */
T &bus_handler();
BusHandlerT &bus_handler();
/// Sets the input value of line @c line on port @c port.
void set_control_line_input(Port port, Line line, bool value);
@@ -123,7 +123,7 @@ template <class T> class MOS6522: public MOS6522Storage {
void shift_in();
void shift_out();
T &bus_handler_;
BusHandlerT &bus_handler_;
HalfCycles time_since_bus_handler_call_;
void access(int address);

94
Components/6526/6526.hpp Normal file
View File

@@ -0,0 +1,94 @@
//
// 6526.h
// Clock Signal
//
// Created by Thomas Harte on 18/07/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef _526_h
#define _526_h
#include <cstdint>
#include "Implementation/6526Storage.hpp"
#include "../Serial/Line.hpp"
namespace MOS {
namespace MOS6526 {
enum Port {
A = 0,
B = 1
};
struct PortHandler {
/// Requests the current input value of @c port from the port handler.
uint8_t get_port_input([[maybe_unused]] Port port) {
return 0xff;
}
/// Sets the current output value of @c port; any bits marked as input will be supplied as 1s.
void set_port_output([[maybe_unused]] Port port, [[maybe_unused]] uint8_t value) {}
};
enum class Personality {
// The 6526, used in machines such as the C64, has a BCD time-of-day clock.
P6526,
// The 8250, used in the Amiga, provides a binary time-of-day clock.
P8250,
};
template <typename PortHandlerT, Personality personality> class MOS6526:
private MOS6526Storage,
private Serial::Line<true>::ReadDelegate
{
public:
MOS6526(PortHandlerT &port_handler) noexcept : port_handler_(port_handler) {
serial_input.set_read_delegate(this);
}
MOS6526(const MOS6526 &) = delete;
/// Writes @c value to the register at @c address. Only the low two bits of the address are decoded.
void write(int address, uint8_t value);
/// Fetches the value of the register @c address. Only the low two bits of the address are decoded.
uint8_t read(int address);
/// Pulses Phi2 to advance by the specified number of half cycles.
void run_for(const HalfCycles half_cycles);
/// Pulses the TOD input the specified number of times.
void advance_tod(int count);
/// @returns @c true if the interrupt output is active, @c false otherwise.
bool get_interrupt_line();
/// Sets the current state of the CNT input.
void set_cnt_input(bool active);
/// Provides both the serial input bit and an additional source of CNT.
Serial::Line<true> serial_input;
/// Sets the current state of the FLG input.
void set_flag_input(bool low);
private:
PortHandlerT &port_handler_;
TODStorage<personality == Personality::P8250> tod_;
template <int port> void set_port_output();
template <int port> uint8_t get_port_input();
void update_interrupts();
void posit_interrupt(uint8_t mask);
void advance_counters(int);
bool serial_line_did_produce_bit(Serial::Line<true> *line, int bit) final;
};
}
}
#include "Implementation/6526Implementation.hpp"
#endif /* _526_h */

View File

@@ -0,0 +1,244 @@
//
// 6526Implementation.hpp
// Clock Signal
//
// Created by Thomas Harte on 18/07/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef _526Implementation_h
#define _526Implementation_h
#include <cassert>
#include <cstdio>
namespace MOS {
namespace MOS6526 {
enum Interrupts: uint8_t {
TimerA = 1 << 0,
TimerB = 1 << 1,
Alarm = 1 << 2,
SerialPort = 1 << 3,
Flag = 1 << 4,
};
template <typename BusHandlerT, Personality personality>
template <int port> void MOS6526<BusHandlerT, personality>::set_port_output() {
const uint8_t output = output_[port] | (~data_direction_[port]);
port_handler_.set_port_output(Port(port), output);
}
template <typename BusHandlerT, Personality personality>
template <int port> uint8_t MOS6526<BusHandlerT, personality>::get_port_input() {
// Avoid bothering the port handler if there's no input active.
const uint8_t input_mask = ~data_direction_[port];
const uint8_t input = input_mask ? port_handler_.get_port_input(Port(port)) : 0x00;
return (input & input_mask) | (output_[port] & data_direction_[port]);
}
template <typename BusHandlerT, Personality personality>
void MOS6526<BusHandlerT, personality>::posit_interrupt(uint8_t mask) {
if(!mask) {
return;
}
interrupt_state_ |= mask;
update_interrupts();
}
template <typename BusHandlerT, Personality personality>
void MOS6526<BusHandlerT, personality>::update_interrupts() {
if(interrupt_state_ & interrupt_control_) {
pending_ |= InterruptInOne;
}
}
template <typename BusHandlerT, Personality personality>
bool MOS6526<BusHandlerT, personality>::get_interrupt_line() {
return interrupt_state_ & 0x80;
}
template <typename BusHandlerT, Personality personality>
void MOS6526<BusHandlerT, personality>::set_cnt_input(bool active) {
cnt_edge_ = active && !cnt_state_;
cnt_state_ = active;
}
template <typename BusHandlerT, Personality personality>
void MOS6526<BusHandlerT, personality>::set_flag_input(bool low) {
if(low && !flag_state_) {
posit_interrupt(Interrupts::Flag);
}
flag_state_ = low;
}
template <typename BusHandlerT, Personality personality>
void MOS6526<BusHandlerT, personality>::write(int address, uint8_t value) {
address &= 0xf;
switch(address) {
// Port output.
case 0:
output_[0] = value;
set_port_output<0>();
break;
case 1:
output_[1] = value;
set_port_output<1>();
break;
// Port direction.
case 2:
data_direction_[0] = value;
set_port_output<0>();
break;
case 3:
data_direction_[1] = value;
set_port_output<1>();
break;
// Counters; writes set the reload values.
case 4: counter_[0].template set_reload<0, personality == Personality::P8250>(value); break;
case 5: counter_[0].template set_reload<8, personality == Personality::P8250>(value); break;
case 6: counter_[1].template set_reload<0, personality == Personality::P8250>(value); break;
case 7: counter_[1].template set_reload<8, personality == Personality::P8250>(value); break;
// Time-of-day clock.
case 8: tod_.template write<0>(value); break;
case 9: tod_.template write<1>(value); break;
case 10: tod_.template write<2>(value); break;
case 11: tod_.template write<3>(value); break;
// Interrupt control.
case 13: {
if(value & 0x80) {
interrupt_control_ |= value & 0x7f;
} else {
interrupt_control_ &= ~(value & 0x7f);
}
update_interrupts();
} break;
// Control. Posted to both the counters and the clock as it affects both.
case 14:
counter_[0].template set_control<false>(value);
tod_.template set_control<false>(value);
if(shifter_is_output_ != bool(value & 0x40)) {
shifter_is_output_ = value & 0x40;
shift_bits_ = 0;
}
break;
case 15:
counter_[1].template set_control<true>(value);
tod_.template set_control<true>(value);
break;
// Shift control.
case 12:
printf("TODO: write to shift register\n");
break;
default:
printf("Unhandled 6526 write: %02x to %d\n", value, address);
assert(false);
break;
}
}
template <typename BusHandlerT, Personality personality>
uint8_t MOS6526<BusHandlerT, personality>::read(int address) {
address &= 0xf;
switch(address) {
case 0: return get_port_input<0>();
case 1: return get_port_input<1>();
case 2: case 3:
return data_direction_[address - 2];
// Counters; reads obtain the current values.
case 4: return uint8_t(counter_[0].value >> 0);
case 5: return uint8_t(counter_[0].value >> 8);
case 6: return uint8_t(counter_[1].value >> 0);
case 7: return uint8_t(counter_[1].value >> 8);
// Interrupt state.
case 13: {
const uint8_t result = interrupt_state_;
interrupt_state_ = 0;
pending_ &= ~(InterruptNow | InterruptInOne);
update_interrupts();
return result;
} break;
case 14: case 15:
return counter_[address - 14].control;
// Time-of-day clock.
case 8: return tod_.template read<0>();
case 9: return tod_.template read<1>();
case 10: return tod_.template read<2>();
case 11: return tod_.template read<3>();
// Shift register.
case 12: return shift_data_;
default:
printf("Unhandled 6526 read from %d\n", address);
assert(false);
break;
}
return 0xff;
}
template <typename BusHandlerT, Personality personality>
void MOS6526<BusHandlerT, personality>::run_for(const HalfCycles half_cycles) {
half_divider_ += half_cycles;
int sub = half_divider_.divide_cycles().template as<int>();
while(sub--) {
pending_ <<= 1;
if(pending_ & InterruptNow) {
interrupt_state_ |= 0x80;
}
pending_ &= PendingClearMask;
// TODO: use CNT potentially to clock timer A, elimiante conditional above.
const bool timer1_did_reload = counter_[0].template advance<false>(false, cnt_state_, cnt_edge_);
const bool timer1_carry = timer1_did_reload && (counter_[1].control & 0x60) == 0x40;
const bool timer2_did_reload = counter_[1].template advance<true>(timer1_carry, cnt_state_, cnt_edge_);
posit_interrupt((timer1_did_reload ? Interrupts::TimerA : 0x00) | (timer2_did_reload ? Interrupts::TimerB : 0x00));
cnt_edge_ = false;
}
}
template <typename BusHandlerT, Personality personality>
void MOS6526<BusHandlerT, personality>::advance_tod(int count) {
if(!count) return;
if(tod_.advance(count)) {
posit_interrupt(Interrupts::Alarm);
}
}
template <typename BusHandlerT, Personality personality>
bool MOS6526<BusHandlerT, personality>::serial_line_did_produce_bit(Serial::Line<true> *, int bit) {
// TODO: post CNT change; might affect timer.
if(!shifter_is_output_) {
shift_register_ = uint8_t((shift_register_ << 1) | bit);
++shift_bits_;
if(shift_bits_ == 8) {
shift_bits_ = 0;
shift_data_ = shift_register_;
posit_interrupt(Interrupts::SerialPort);
}
}
return true;
}
}
}
#endif /* _526Implementation_h */

View File

@@ -0,0 +1,339 @@
//
// 6526Storage.hpp
// Clock Signal
//
// Created by Thomas Harte on 18/07/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef _526Storage_h
#define _526Storage_h
#include <array>
#include "../../../ClockReceiver/ClockReceiver.hpp"
namespace MOS {
namespace MOS6526 {
class TODBase {
public:
template <bool is_timer2> void set_control(uint8_t value) {
if constexpr (is_timer2) {
write_alarm = value & 0x80;
} else {
is_50Hz = value & 0x80;
}
}
protected:
bool write_alarm = false, is_50Hz = false;
};
template <bool is_8250> class TODStorage {};
template <> class TODStorage<false>: public TODBase {
private:
bool increment_ = true, latched_ = false;
int divider_ = 0;
std::array<uint8_t, 4> value_;
std::array<uint8_t, 4> latch_;
std::array<uint8_t, 4> alarm_;
static constexpr uint8_t masks[4] = {0xf, 0x3f, 0x3f, 0x1f};
void bcd_increment(uint8_t &value) {
++value;
if((value&0x0f) > 0x09) value += 0x06;
}
public:
template <int byte> void write(uint8_t v) {
if(write_alarm) {
alarm_[byte] = v & masks[byte];
} else {
value_[byte] = v & masks[byte];
if constexpr (byte == 0) {
increment_ = true;
}
if constexpr (byte == 3) {
increment_ = false;
}
}
}
template <int byte> uint8_t read() {
if(latched_) {
const uint8_t result = latch_[byte];
if constexpr (byte == 0) {
latched_ = false;
}
return result;
}
if constexpr (byte == 3) {
latched_ = true;
latch_ = value_;
}
return value_[byte];
}
bool advance(int count) {
if(!increment_) {
return false;
}
while(count--) {
// Increment the pre-10ths divider.
++divider_;
if(divider_ < 5) continue;
if(divider_ < 6 && !is_50Hz) continue;
divider_ = 0;
// Increments 10ths of a second. One BCD digit.
++value_[0];
if(value_[0] < 10) {
continue;
}
// Increment seconds. Actual BCD needed from here onwards.
bcd_increment(value_[1]);
if(value_[1] != 60) {
continue;
}
value_[1] = 0;
// Increment minutes.
bcd_increment(value_[2]);
if(value_[2] != 60) {
continue;
}
value_[2] = 0;
// TODO: increment hours, keeping AM/PM separate?
}
return false; // TODO: test against alarm.
}
};
template <> class TODStorage<true>: public TODBase {
private:
uint32_t increment_mask_ = uint32_t(~0);
uint32_t latch_ = 0;
uint32_t value_ = 0;
uint32_t alarm_ = 0xff'ffff;
public:
template <int byte> void write(uint8_t v) {
if constexpr (byte == 3) {
return;
}
constexpr int shift = byte << 3;
// Write to either the alarm or the current value as directed;
// writing to any part of the current value other than the LSB
// pauses incrementing until the LSB is written.
const uint32_t mask = uint32_t(~(0xff << shift));
if(write_alarm) {
alarm_ = (alarm_ & mask) | uint32_t(v << shift);
} else {
value_ = (value_ & mask) | uint32_t(v << shift);
increment_mask_ = (byte == 0) ? uint32_t(~0) : 0;
}
}
template <int byte> uint8_t read() {
if constexpr (byte == 3) {
return 0xff; // Assumed. Just a guess.
}
constexpr int shift = byte << 3;
if constexpr (byte == 2) {
latch_ = value_ | 0xff00'0000;
}
const uint32_t source = latch_ ? latch_ : value_;
const uint8_t result = uint8_t((source >> shift) & 0xff);
if constexpr (byte == 0) {
latch_ = 0;
}
return result;
}
bool advance(int count) {
// The 8250 uses a simple binary counter to replace the
// 6526's time-of-day clock. So this is easy.
const uint32_t distance_to_alarm = (alarm_ - value_) & 0xff'ffff;
const auto increment = uint32_t(count) & increment_mask_;
value_ = (value_ + increment) & 0xff'ffff;
return distance_to_alarm <= increment;
}
};
struct MOS6526Storage {
bool cnt_state_ = false; // Inactive by default.
bool cnt_edge_ = false;
bool flag_state_ = false;
HalfCycles half_divider_;
uint8_t output_[2] = {0, 0};
uint8_t data_direction_[2] = {0, 0};
uint8_t interrupt_control_ = 0;
uint8_t interrupt_state_ = 0;
uint8_t shift_data_ = 0;
uint8_t shift_register_ = 0;
int shift_bits_ = 0;
bool shifter_is_output_ = false;
struct Counter {
uint16_t reload = 0;
uint16_t value = 0;
uint8_t control = 0;
template <int shift, bool is_8250> void set_reload(uint8_t v) {
reload = (reload & (0xff00 >> shift)) | uint16_t(v << shift);
if constexpr (shift == 8) {
// This seems to be a special 8250 feature per the Amiga
// Hardware Reference Manual; cf. Appendix F.
if(is_8250) {
control |= 1;
pending |= ReloadInOne;
} else {
if(!(control&1)) {
pending |= ReloadInOne;
}
}
}
// If this write has hit during a reload cycle, reload.
if(pending & ReloadNow) {
value = reload;
}
}
template <bool is_counter_2> void set_control(uint8_t v) {
control = v;
if(v&2) {
printf("UNIMPLEMENTED: PB strobe\n");
}
}
template <bool is_counter_2> bool advance(bool chained_input, bool cnt_state, bool cnt_edge) {
// TODO: remove most of the conditionals here in favour of bit shuffling.
pending = (pending & PendingClearMask) << 1;
//
// Apply feeder states inputs: anything that
// will take effect in the future.
//
// Schedule a force reload if requested.
if(control & 0x10) {
pending |= ReloadInOne;
control &= ~0x10;
}
// Keep a history of the one-shot bit.
if(control & 0x08) {
pending |= OneShotInOne;
}
// Determine whether an input clock is applicable.
if constexpr(is_counter_2) {
switch(control&0x60) {
case 0x00: // Count Phi2 pulses.
pending |= TestInputNow;
break;
case 0x20: // Count negative CNTs, with an extra cycle of delay.
pending |= cnt_edge ? TestInputInOne : 0;
break;
case 0x40: // Count timer A reloads.
pending |= chained_input ? TestInputNow : 0;
break;
case 0x60: // Count timer A transitions when CNT is low.
pending |= chained_input && cnt_state ? TestInputNow : 0;
break;
}
} else {
if(!(control&0x20)) {
pending |= TestInputNow;
} else if (cnt_edge) {
pending |= TestInputInOne;
}
}
if(pending&TestInputNow && control&1) {
pending |= ApplyClockInTwo;
}
//
// Perform a timer tick and decide whether a reload is prompted.
//
if(pending & ApplyClockNow) {
--value;
}
const bool should_reload = !value && (pending & ApplyClockInOne);
// Schedule a reload if so ordered.
if(should_reload) {
pending |= ReloadNow; // Combine this decision with a deferred
// input from the force-reoad test above.
// If this was one-shot, stop.
if(pending&(OneShotInOne | OneShotNow)) {
control &= ~1;
pending &= ~(ApplyClockInOne|ApplyClockInTwo); // Cancel scheduled ticks.
}
}
// Reload if scheduled.
if(pending & ReloadNow) {
value = reload;
pending &= ~ApplyClockInOne; // Skip next decrement.
}
return should_reload;
}
private:
int pending = 0;
static constexpr int ReloadInOne = 1 << 0;
static constexpr int ReloadNow = 1 << 1;
static constexpr int OneShotInOne = 1 << 2;
static constexpr int OneShotNow = 1 << 3;
static constexpr int ApplyClockInTwo = 1 << 4;
static constexpr int ApplyClockInOne = 1 << 5;
static constexpr int ApplyClockNow = 1 << 6;
static constexpr int TestInputInOne = 1 << 7;
static constexpr int TestInputNow = 1 << 8;
static constexpr int PendingClearMask = ~(ReloadNow | OneShotNow | ApplyClockNow);
bool active_ = false;
} counter_[2];
static constexpr int InterruptInOne = 1 << 0;
static constexpr int InterruptNow = 1 << 1;
static constexpr int PendingClearMask = ~(InterruptNow);
int pending_ = 0;
};
}
}
#endif /* _526Storage_h */

View File

@@ -435,7 +435,7 @@ template <class BusHandler> class MOS6560 {
Concurrency::DeferringAsyncTaskQueue audio_queue_;
AudioGenerator audio_generator_;
Outputs::Speaker::LowpassSpeaker<AudioGenerator> speaker_;
Outputs::Speaker::PullLowpass<AudioGenerator> speaker_;
Cycles cycles_since_speaker_update_;
void update_audio() {

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 {
@@ -148,7 +148,7 @@ uint8_t ACIA::parity(uint8_t value) {
return value ^ (parity_ == Parity::Even);
}
bool ACIA::serial_line_did_produce_bit(Serial::Line *, int bit) {
bool ACIA::serial_line_did_produce_bit(Serial::Line<false> *, int bit) {
// Shift this bit into the 11-bit input register; this is big enough to hold
// the largest transmission symbol.
++bits_received_;

View File

@@ -18,7 +18,7 @@
namespace Motorola {
namespace ACIA {
class ACIA: public ClockingHint::Source, private Serial::Line::ReadDelegate {
class ACIA: public ClockingHint::Source, private Serial::Line<false>::ReadDelegate {
public:
static constexpr const HalfCycles SameAsTransmit = HalfCycles(0);
@@ -77,13 +77,13 @@ class ACIA: public ClockingHint::Source, private Serial::Line::ReadDelegate {
void reset();
// Input lines.
Serial::Line receive;
Serial::Line clear_to_send;
Serial::Line data_carrier_detect;
Serial::Line<false> receive;
Serial::Line<false> clear_to_send;
Serial::Line<false> data_carrier_detect;
// Output lines.
Serial::Line transmit;
Serial::Line request_to_send;
Serial::Line<false> transmit;
Serial::Line<false> request_to_send;
// ClockingHint::Source.
ClockingHint::Preference preferred_clocking() const final;
@@ -118,7 +118,7 @@ class ACIA: public ClockingHint::Source, private Serial::Line::ReadDelegate {
HalfCycles transmit_clock_rate_;
HalfCycles receive_clock_rate_;
bool serial_line_did_produce_bit(Serial::Line *line, int bit) final;
bool serial_line_did_produce_bit(Serial::Line<false> *line, int bit) final;
bool interrupt_line_ = false;
void update_interrupt_line();

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

@@ -708,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;
@@ -750,7 +750,7 @@ HalfCycles TMS9918::get_time_until_interrupt() {
if(next_line_interrupt_row == -1) {
return generate_interrupts_ ?
half_cycles_before_internal_cycles(time_until_frame_interrupt) :
HalfCycles(-1);
HalfCycles::max();
}
// Figure out the number of internal cycles until the next line interrupt, which is the amount

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

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

View File

@@ -0,0 +1,299 @@
//
// 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
#include <array>
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() {}
/*!
Advances the clock by 1 second.
The caller should also signal an interrupt if applicable.
*/
void update() {
for(size_t c = 0; c < 4; ++c) {
++seconds_[c];
if(seconds_[c]) break;
}
}
/*!
Sets the current [P/B]RAM contents.
*/
template <typename CollectionT> void set_data(const CollectionT &collection) {
set_data(collection.begin(), collection.end());
}
template <typename IteratorT> void set_data(IteratorT begin, const IteratorT end) {
size_t c = 0;
while(begin != end && c < 256) {
data_[c] = *begin;
++begin;
++c;
}
}
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_ = unsigned((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:
std::array<uint8_t, 256> data_{0xff};
std::array<uint8_t, 4> seconds_{};
uint8_t write_protect_ = 0;
unsigned int address_ = 0;
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.
const auto result = perform(data_);
if(result < 0x100) {
data_ = uint8_t(result);
}
}
// 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

@@ -22,7 +22,10 @@ namespace {
DiskII::DiskII(int clock_rate) :
clock_rate_(clock_rate),
inputs_(input_command),
drives_{{clock_rate, 300, 1}, {clock_rate, 300, 1}}
drives_{
Storage::Disk::Drive{clock_rate, 300, 1},
Storage::Disk::Drive{clock_rate, 300, 1}
}
{
drives_[0].set_clocking_hint_observer(this);
drives_[1].set_clocking_hint_observer(this);
@@ -137,10 +140,15 @@ void DiskII::decide_clocking_preference() {
// If in read mode, clocking is either:
//
// just-in-time, if drives are running or the shift register has any 1s in it or a flux event hasn't yet passed; or
// none, given that drives are not running, the shift register has already emptied and there's no flux about to be received.
// just-in-time, if drives are running or the shift register has any 1s in it and shifting may occur, or a flux event hasn't yet passed; or
// none, given that drives are not running, the shift register has already emptied or stopped and there's no flux about to be received.
if(!(inputs_ & ~input_flux)) {
clocking_preference_ = (!motor_is_enabled_ && !shift_register_ && (inputs_&input_flux)) ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime;
const bool is_stuck_at_nop =
!flux_duration_ && state_machine_[(state_ & 0xf0) | inputs_ | ((shift_register_&0x80) >> 6)] == state_ && (state_ &0xf) == 0x8;
clocking_preference_ =
(drive_is_sleeping_[0] && drive_is_sleeping_[1] && (!shift_register_ || is_stuck_at_nop) && (inputs_&input_flux))
? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime;
}
// If in writing mode, clocking is real time.
@@ -180,29 +188,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

@@ -8,13 +8,18 @@
#include "Line.hpp"
#include <cassert>
#include <limits>
using namespace Serial;
void Line::set_writer_clock_rate(HalfCycles clock_rate) {
template <bool include_clock>
void Line<include_clock>::set_writer_clock_rate(HalfCycles clock_rate) {
clock_rate_ = clock_rate;
}
void Line::advance_writer(HalfCycles cycles) {
template <bool include_clock>
void Line<include_clock>::advance_writer(HalfCycles cycles) {
if(cycles == HalfCycles(0)) return;
const auto integral_cycles = cycles.as_integral();
@@ -25,7 +30,9 @@ void Line::advance_writer(HalfCycles cycles) {
transmission_extra_ -= integral_cycles;
if(transmission_extra_ <= 0) {
transmission_extra_ = 0;
update_delegate(level_);
if constexpr (!include_clock) {
update_delegate(level_);
}
}
}
} else {
@@ -38,12 +45,17 @@ void Line::advance_writer(HalfCycles cycles) {
auto iterator = events_.begin() + 1;
while(iterator != events_.end() && iterator->type != Event::Delay) {
level_ = iterator->type == Event::SetHigh;
if constexpr(include_clock) {
update_delegate(level_);
}
++iterator;
}
events_.erase(events_.begin(), iterator);
if(old_level != level_) {
update_delegate(old_level);
if constexpr (!include_clock) {
if(old_level != level_) {
update_delegate(old_level);
}
}
// Book enough extra time for the read delegate to be posted
@@ -60,7 +72,8 @@ void Line::advance_writer(HalfCycles cycles) {
}
}
void Line::write(bool level) {
template <bool include_clock>
void Line<include_clock>::write(bool level) {
if(!events_.empty()) {
events_.emplace_back();
events_.back().type = level ? Event::SetHigh : Event::SetLow;
@@ -70,7 +83,8 @@ void Line::write(bool level) {
}
}
void Line::write(HalfCycles cycles, int count, int levels) {
template <bool include_clock>
template <bool lsb_first, typename IntT> void Line<include_clock>::write_internal(HalfCycles cycles, int count, IntT levels) {
remaining_delays_ += count * cycles.as_integral();
auto event = events_.size();
@@ -78,63 +92,122 @@ void Line::write(HalfCycles cycles, int count, int levels) {
while(count--) {
events_[event].type = Event::Delay;
events_[event].delay = int(cycles.as_integral());
events_[event+1].type = (levels&1) ? Event::SetHigh : Event::SetLow;
levels >>= 1;
IntT bit;
if constexpr (lsb_first) {
bit = levels & 1;
levels >>= 1;
} else {
constexpr auto top_bit = IntT(0x80) << ((sizeof(IntT) - 1) * 8);
bit = levels & top_bit;
levels <<= 1;
}
events_[event+1].type = bit ? Event::SetHigh : Event::SetLow;
event += 2;
}
}
void Line::reset_writing() {
template <bool include_clock>
void Line<include_clock>::write(HalfCycles cycles, int count, int levels) {
write_internal<true, int>(cycles, count, levels);
}
template <bool include_clock>
template <bool lsb_first, typename IntT> void Line<include_clock>::write(HalfCycles cycles, IntT value) {
write_internal<lsb_first, IntT>(cycles, 8 * sizeof(IntT), value);
}
template <bool include_clock>
void Line<include_clock>::reset_writing() {
remaining_delays_ = 0;
events_.clear();
}
bool Line::read() const {
template <bool include_clock>
bool Line<include_clock>::read() const {
return level_;
}
void Line::set_read_delegate(ReadDelegate *delegate, Storage::Time bit_length) {
template <bool include_clock>
void Line<include_clock>::set_read_delegate(ReadDelegate *delegate, Storage::Time bit_length) {
read_delegate_ = delegate;
read_delegate_bit_length_ = bit_length;
read_delegate_bit_length_.simplify();
write_cycles_since_delegate_call_ = 0;
if constexpr (!include_clock) {
assert(bit_length > Storage::Time(0));
read_delegate_bit_length_ = bit_length;
read_delegate_bit_length_.simplify();
write_cycles_since_delegate_call_ = 0;
}
}
void Line::update_delegate(bool level) {
template <bool include_clock>
void Line<include_clock>::update_delegate(bool level) {
// Exit early if there's no delegate, or if the delegate is waiting for
// zero and this isn't zero.
if(!read_delegate_) return;
const int cycles_to_forward = write_cycles_since_delegate_call_;
write_cycles_since_delegate_call_ = 0;
if(level && read_delegate_phase_ == ReadDelegatePhase::WaitingForZero) return;
if constexpr (!include_clock) {
const int cycles_to_forward = write_cycles_since_delegate_call_;
write_cycles_since_delegate_call_ = 0;
if(level && read_delegate_phase_ == ReadDelegatePhase::WaitingForZero) return;
// Deal with a transition out of waiting-for-zero mode by seeding time left
// in bit at half a bit.
if(read_delegate_phase_ == ReadDelegatePhase::WaitingForZero) {
time_left_in_bit_ = read_delegate_bit_length_;
time_left_in_bit_.clock_rate <<= 1;
read_delegate_phase_ = ReadDelegatePhase::Serialising;
}
// Forward as many bits as occur.
Storage::Time time_left(cycles_to_forward, int(clock_rate_.as_integral()));
const int bit = level ? 1 : 0;
int bits = 0;
while(time_left >= time_left_in_bit_) {
++bits;
if(!read_delegate_->serial_line_did_produce_bit(this, bit)) {
read_delegate_phase_ = ReadDelegatePhase::WaitingForZero;
if(bit) return;
// Deal with a transition out of waiting-for-zero mode by seeding time left
// in bit at half a bit.
if(read_delegate_phase_ == ReadDelegatePhase::WaitingForZero) {
time_left_in_bit_ = read_delegate_bit_length_;
time_left_in_bit_.clock_rate <<= 1;
read_delegate_phase_ = ReadDelegatePhase::Serialising;
}
time_left -= time_left_in_bit_;
time_left_in_bit_ = read_delegate_bit_length_;
// Forward as many bits as occur.
Storage::Time time_left(cycles_to_forward, int(clock_rate_.as_integral()));
const int bit = level ? 1 : 0;
int bits = 0;
while(time_left >= time_left_in_bit_) {
++bits;
if(!read_delegate_->serial_line_did_produce_bit(this, bit)) {
read_delegate_phase_ = ReadDelegatePhase::WaitingForZero;
if(bit) return;
}
time_left -= time_left_in_bit_;
time_left_in_bit_ = read_delegate_bit_length_;
}
time_left_in_bit_ -= time_left;
} else {
read_delegate_->serial_line_did_produce_bit(this, level);
}
time_left_in_bit_ -= time_left;
}
Cycles::IntType Line::minimum_write_cycles_for_read_delegate_bit() {
template <bool include_clock>
Cycles::IntType Line<include_clock>::minimum_write_cycles_for_read_delegate_bit() {
if(!read_delegate_) return 0;
return 1 + (read_delegate_bit_length_ * unsigned(clock_rate_.as_integral())).get<int>();
return 1 + (read_delegate_bit_length_ * unsigned(clock_rate_.as_integral())).template get<int>();
}
//
// Explicitly instantiate the meaningful instances of templates above;
// this class uses templates primarily to keep the interface compact and
// to take advantage of constexpr functionality selection, not so as
// to be generic.
//
template class Serial::Line<true>;
template class Serial::Line<false>;
template void Line<true>::write<true, uint8_t>(HalfCycles, uint8_t);
template void Line<true>::write<false, uint8_t>(HalfCycles, uint8_t);
template void Line<true>::write<true, uint16_t>(HalfCycles, uint16_t);
template void Line<true>::write<false, uint16_t>(HalfCycles, uint16_t);
template void Line<true>::write<true, uint32_t>(HalfCycles, uint32_t);
template void Line<true>::write<false, uint32_t>(HalfCycles, uint32_t);
template void Line<true>::write<true, uint64_t>(HalfCycles, uint64_t);
template void Line<true>::write<false, uint64_t>(HalfCycles, uint64_t);
template void Line<false>::write<true, uint8_t>(HalfCycles, uint8_t);
template void Line<false>::write<false, uint8_t>(HalfCycles, uint8_t);
template void Line<false>::write<true, uint16_t>(HalfCycles, uint16_t);
template void Line<false>::write<false, uint16_t>(HalfCycles, uint16_t);
template void Line<false>::write<true, uint32_t>(HalfCycles, uint32_t);
template void Line<false>::write<false, uint32_t>(HalfCycles, uint32_t);
template void Line<false>::write<true, uint64_t>(HalfCycles, uint64_t);
template void Line<false>::write<false, uint64_t>(HalfCycles, uint64_t);

View File

@@ -17,25 +17,42 @@
namespace Serial {
/*!
@c Line connects a single reader and a single writer, allowing timestamped events to be
published and consumed, potentially with a clock conversion in between. It allows line
levels to be written and read in larger collections.
Models one of two connections, either:
It is assumed that the owner of the reader and writer will ensure that the reader will never
get ahead of the writer. If the writer posts events behind the reader they will simply be
given instanteous effect.
(i) a plain single-line serial; or
(ii) a two-line data + clock.
In both cases connects a single reader to a single writer.
When operating as a single-line serial connection:
Provides a mechanism for the writer to enqueue levels arbitrarily far
ahead of the current time, which are played back only as the
write queue advances. Permits the reader and writer to work at
different clock rates, and provides a virtual delegate protocol with
start bit detection.
Can alternatively be used by reader and/or writer only in immediate
mode, getting or setting the current level now, without the actor on
the other end having to have made the same decision.
When operating as a two-line connection:
Implies a clock over enqueued data and provides the reader with
all enqueued bits at appropriate times.
*/
class Line {
template <bool include_clock> class Line {
public:
void set_writer_clock_rate(HalfCycles clock_rate);
/// Advances the read position by @c cycles relative to the writer's
/// clock rate.
void advance_writer(HalfCycles cycles);
/// Sets the line to @c level.
/// Sets the line to @c level instantaneously.
void write(bool level);
/// @returns The instantaneous level of this line.
bool read() const;
/// Sets the denominator for the between levels for any data enqueued
/// via an @c write.
void set_writer_clock_rate(HalfCycles clock_rate);
/// Enqueues @c count level changes, the first occurring immediately
/// after the final event currently posted and each subsequent event
/// occurring @c cycles after the previous. An additional gap of @c cycles
@@ -44,6 +61,10 @@ class Line {
/// relative to the writer's clock rate.
void write(HalfCycles cycles, int count, int levels);
/// Enqueus every bit from @c value as per the rules of write(HalfCycles, int, int),
/// either in LSB or MSB order as per the @c lsb_first template flag.
template <bool lsb_first, typename IntT> void write(HalfCycles cycles, IntT value);
/// @returns the number of cycles until currently enqueued write data is exhausted.
forceinline HalfCycles write_data_time_remaining() const {
return HalfCycles(remaining_delays_);
@@ -55,25 +76,36 @@ class Line {
return HalfCycles(remaining_delays_ + transmission_extra_);
}
/// Advances the read position by @c cycles relative to the writer's
/// clock rate.
void advance_writer(HalfCycles cycles);
/// Eliminates all future write states, leaving the output at whatever it is now.
void reset_writing();
/// @returns The instantaneous level of this line.
bool read() const;
struct ReadDelegate {
virtual bool serial_line_did_produce_bit(Line *line, int bit) = 0;
};
/*!
Sets a read delegate, which will receive samples of the output level every
@c bit_lengths of a second apart subject to a state machine:
Sets a read delegate.
* initially no bits will be delivered;
* when a zero level is first detected, the line will wait half a bit's length, then start
sampling at single-bit intervals, passing each bit to the delegate while it returns @c true;
* as soon as the delegate returns @c false, the line will return to the initial state.
Single line serial connection:
The delegate will receive samples of the output level every
@c bit_lengths of a second apart subject to a state machine:
* initially no bits will be delivered;
* when a zero level is first detected, the line will wait half a bit's length, then start
sampling at single-bit intervals, passing each bit to the delegate while it returns @c true;
* as soon as the delegate returns @c false, the line will return to the initial state.
Two-line clock + data connection:
The delegate will receive every bit that has been enqueued, spaced as nominated
by the writer. @c bit_length is ignored, as is the return result of
@c ReadDelegate::serial_line_did_produce_bit.
*/
void set_read_delegate(ReadDelegate *delegate, Storage::Time bit_length);
void set_read_delegate(ReadDelegate *delegate, Storage::Time bit_length = Storage::Time());
private:
struct Event {
@@ -98,6 +130,9 @@ class Line {
void update_delegate(bool level);
HalfCycles::IntType minimum_write_cycles_for_read_delegate_bit();
template <bool lsb_first, typename IntT> void
write_internal(HalfCycles, int, IntT);
};
/*!

View File

@@ -11,11 +11,11 @@
using namespace Concurrency;
AsyncTaskQueue::AsyncTaskQueue()
#ifndef __APPLE__
#ifndef USE_GCD
: should_destruct_(false)
#endif
{
#ifdef __APPLE__
#ifdef USE_GCD
serial_dispatch_queue_ = dispatch_queue_create("com.thomasharte.clocksignal.asyntaskqueue", DISPATCH_QUEUE_SERIAL);
#else
thread_ = std::make_unique<std::thread>([this]() {
@@ -44,7 +44,7 @@ AsyncTaskQueue::AsyncTaskQueue()
}
AsyncTaskQueue::~AsyncTaskQueue() {
#ifdef __APPLE__
#ifdef USE_GCD
flush();
dispatch_release(serial_dispatch_queue_);
serial_dispatch_queue_ = nullptr;
@@ -57,7 +57,7 @@ AsyncTaskQueue::~AsyncTaskQueue() {
}
void AsyncTaskQueue::enqueue(std::function<void(void)> function) {
#ifdef __APPLE__
#ifdef USE_GCD
dispatch_async(serial_dispatch_queue_, ^{function();});
#else
std::lock_guard lock(queue_mutex_);
@@ -67,7 +67,7 @@ void AsyncTaskQueue::enqueue(std::function<void(void)> function) {
}
void AsyncTaskQueue::flush() {
#ifdef __APPLE__
#ifdef USE_GCD
dispatch_sync(serial_dispatch_queue_, ^{});
#else
auto flush_mutex = std::make_shared<std::mutex>();

View File

@@ -16,8 +16,9 @@
#include <memory>
#include <thread>
#ifdef __APPLE__
#if defined(__APPLE__) && !defined(IGNORE_APPLE)
#include <dispatch/dispatch.h>
#define USE_GCD
#endif
namespace Concurrency {
@@ -47,7 +48,7 @@ class AsyncTaskQueue {
void flush();
private:
#ifdef __APPLE__
#ifdef USE_GCD
dispatch_queue_t serial_dispatch_queue_;
#else
std::unique_ptr<std::thread> thread_;

View File

@@ -9,6 +9,7 @@
#ifndef Joystick_hpp
#define Joystick_hpp
#include <cstddef>
#include <vector>
namespace Inputs {
@@ -35,7 +36,10 @@ class Joystick {
// Fire buttons.
Fire,
// Other labelled keys.
Key
Key,
// The maximum value this enum can contain.
Max = Key
};
const Type type;

View File

@@ -8,6 +8,8 @@
#include "Keyboard.hpp"
#include <cstddef>
using namespace Inputs;
Keyboard::Keyboard(const std::set<Key> &essential_modifiers) : essential_modifiers_(essential_modifiers) {

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 "../Numeric/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 "../Numeric/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.

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 */

254
Machines/Amiga/Amiga.cpp Normal file
View File

@@ -0,0 +1,254 @@
//
// Amiga.cpp
// Clock Signal
//
// Created by Thomas Harte on 16/07/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "Amiga.hpp"
#include "../../Activity/Source.hpp"
#include "../MachineTypes.hpp"
#include "../../Processors/68000/68000.hpp"
#include "../../Analyser/Static/Amiga/Target.hpp"
#include "../Utility/MemoryPacker.hpp"
#include "../Utility/MemoryFuzzer.hpp"
//#define NDEBUG
#define LOG_PREFIX "[Amiga] "
#include "../../Outputs/Log.hpp"
#include "Chipset.hpp"
#include "Keyboard.hpp"
#include "MemoryMap.hpp"
#include <cassert>
namespace {
// NTSC clock rate: 2*3.579545 = 7.15909Mhz.
// PAL clock rate: 7.09379Mhz; 227 cycles/line.
constexpr int PALClockRate = 7'093'790;
//constexpr int NTSCClockRate = 7'159'090;
}
namespace Amiga {
class ConcreteMachine:
public Activity::Source,
public CPU::MC68000::BusHandler,
public MachineTypes::AudioProducer,
public MachineTypes::JoystickMachine,
public MachineTypes::MappedKeyboardMachine,
public MachineTypes::MediaTarget,
public MachineTypes::MouseMachine,
public MachineTypes::ScanProducer,
public MachineTypes::TimedMachine,
public Machine {
public:
ConcreteMachine(const Analyser::Static::Amiga::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
mc68000_(*this),
chipset_(memory_, PALClockRate)
{
// Temporary: use a hard-coded Kickstart selection.
constexpr ROM::Name rom_name = ROM::Name::AmigaA500Kickstart13;
ROM::Request request(rom_name);
auto roms = rom_fetcher(request);
if(!request.validate(roms)) {
throw ROMMachine::Error::MissingROMs;
}
Memory::PackBigEndian16(roms.find(rom_name)->second, memory_.kickstart.data());
// For now, also hard-code assumption of PAL.
// (Assumption is both here and in the video timing of the Chipset).
set_clock_rate(PALClockRate);
// Insert supplied media.
insert_media(target.media);
}
// MARK: - MediaTarget.
bool insert_media(const Analyser::Static::Media &media) final {
return chipset_.insert(media.disks);
}
// MARK: - MC68000::BusHandler.
using Microcycle = CPU::MC68000::Microcycle;
HalfCycles perform_bus_operation(const CPU::MC68000::Microcycle &cycle, int) {
// Do a quick advance check for Chip RAM access; add a suitable delay if required.
HalfCycles total_length;
if(cycle.operation & Microcycle::NewAddress && *cycle.address < 0x20'0000) {
total_length = chipset_.run_until_after_cpu_slot().duration;
assert(total_length >= cycle.length);
} else {
total_length = cycle.length;
chipset_.run_for(total_length);
}
mc68000_.set_interrupt_level(chipset_.get_interrupt_level());
// Check for assertion of reset.
if(cycle.operation & Microcycle::Reset) {
memory_.reset();
LOG("Reset; PC is around " << PADHEX(8) << mc68000_.get_state().program_counter);
}
// Autovector interrupts.
if(cycle.operation & Microcycle::InterruptAcknowledge) {
mc68000_.set_is_peripheral_address(true);
return total_length - cycle.length;
}
// Do nothing if no address is exposed.
if(!(cycle.operation & (Microcycle::NewAddress | Microcycle::SameAddress))) return total_length - cycle.length;
// Grab the target address to pick a memory source.
const uint32_t address = cycle.host_endian_byte_address();
// Set VPA if this is [going to be] a CIA access.
mc68000_.set_is_peripheral_address((address & 0xe0'0000) == 0xa0'0000);
if(!memory_.regions[address >> 18].read_write_mask) {
if((cycle.operation & (Microcycle::SelectByte | Microcycle::SelectWord))) {
// Check for various potential chip accesses.
// Per the manual:
//
// CIA A is: 101x xxxx xx01 rrrr xxxx xxx0 (i.e. loaded into high byte)
// CIA B is: 101x xxxx xx10 rrrr xxxx xxx1 (i.e. loaded into low byte)
//
// but in order to map 0xbfexxx to CIA A and 0xbfdxxx to CIA B, I think
// these might be listed the wrong way around.
//
// Additional assumption: the relevant CIA select lines are connected
// directly to the chip enables.
if((address & 0xe0'0000) == 0xa0'0000) {
const int reg = address >> 8;
const bool select_a = !(address & 0x1000);
const bool select_b = !(address & 0x2000);
if(cycle.operation & Microcycle::Read) {
uint16_t result = 0xffff;
if(select_a) result &= 0xff00 | (chipset_.cia_a.read(reg) << 0);
if(select_b) result &= 0x00ff | (chipset_.cia_b.read(reg) << 8);
cycle.set_value16(result);
} else {
if(select_a) chipset_.cia_a.write(reg, cycle.value8_low());
if(select_b) chipset_.cia_b.write(reg, cycle.value8_high());
}
// LOG("CIA " << (((address >> 12) & 3)^3) << " " << (cycle.operation & Microcycle::Read ? "read " : "write ") << std::dec << (reg & 0xf) << " of " << PADHEX(4) << +cycle.value16());
} else if(address >= 0xdf'f000 && address <= 0xdf'f1be) {
chipset_.perform(cycle);
} else {
// This'll do for open bus, for now.
if(cycle.operation & Microcycle::Read) {
cycle.set_value16(0xffff);
}
// Don't log for the region that is definitely just ROM this machine doesn't have.
if(address < 0xf0'0000) {
LOG("Unmapped " << (cycle.operation & Microcycle::Read ? "read from " : "write to ") << PADHEX(6) << ((*cycle.address)&0xffffff) << " of " << cycle.value16());
}
}
}
} else {
// A regular memory access.
cycle.apply(
&memory_.regions[address >> 18].contents[address],
memory_.regions[address >> 18].read_write_mask
);
}
return total_length - cycle.length;
}
void flush() {
chipset_.flush();
}
private:
CPU::MC68000::Processor<ConcreteMachine, true> mc68000_;
// MARK: - Memory map.
MemoryMap memory_;
// MARK: - Chipset.
Chipset chipset_;
// MARK: - Activity Source
void set_activity_observer(Activity::Observer *observer) final {
chipset_.set_activity_observer(observer);
}
// MARK: - MachineTypes::AudioProducer.
Outputs::Speaker::Speaker *get_speaker() final {
return chipset_.get_speaker();
}
// MARK: - MachineTypes::ScanProducer.
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
chipset_.set_scan_target(scan_target);
}
Outputs::Display::ScanStatus get_scaled_scan_status() const {
return chipset_.get_scaled_scan_status();
}
// MARK: - MachineTypes::TimedMachine.
void run_for(const Cycles cycles) {
mc68000_.run_for(cycles);
}
// MARK: - MachineTypes::MouseMachine.
Inputs::Mouse &get_mouse() final {
return chipset_.get_mouse();;
}
// MARK: - MachineTypes::JoystickMachine.
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() {
return chipset_.get_joysticks();
}
// MARK: - Keyboard.
Amiga::KeyboardMapper keyboard_mapper_;
KeyboardMapper *get_keyboard_mapper() {
return &keyboard_mapper_;
}
void set_key_state(uint16_t key, bool is_pressed) {
chipset_.get_keyboard().set_key_state(key, is_pressed);
}
void clear_all_keys() {
chipset_.get_keyboard().clear_all_keys();
}
};
}
using namespace Amiga;
Machine *Machine::Amiga(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
using Target = Analyser::Static::Amiga::Target;
const Target *const amiga_target = dynamic_cast<const Target *>(target);
return new Amiga::ConcreteMachine(*amiga_target, rom_fetcher);
}
Machine::~Machine() {}

27
Machines/Amiga/Amiga.hpp Normal file
View File

@@ -0,0 +1,27 @@
//
// Amiga.hpp
// Clock Signal
//
// Created by Thomas Harte on 16/07/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Amiga_hpp
#define Amiga_hpp
#include "../../Analyser/Static/StaticAnalyser.hpp"
#include "../ROMMachine.hpp"
namespace Amiga {
class Machine {
public:
virtual ~Machine();
/// Creates and returns an Amiga.
static Machine *Amiga(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher);
};
}
#endif /* Amiga_hpp */

678
Machines/Amiga/Audio.cpp Normal file
View File

@@ -0,0 +1,678 @@
//
// Audio.cpp
// Clock Signal
//
// Created by Thomas Harte on 09/11/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "Audio.hpp"
#include "Flags.hpp"
#define LOG_PREFIX "[Audio] "
#include "../../Outputs/Log.hpp"
#include <cassert>
#include <tuple>
using namespace Amiga;
Audio::Audio(Chipset &chipset, uint16_t *ram, size_t word_size, float output_rate) :
DMADevice<4>(chipset, ram, word_size) {
// Mark all buffers as available.
for(auto &flag: buffer_available_) {
flag.store(true, std::memory_order::memory_order_relaxed);
}
speaker_.set_input_rate(output_rate);
speaker_.set_high_frequency_cutoff(7000.0f);
}
// MARK: - Exposed setters.
void Audio::set_length(int channel, uint16_t length) {
assert(channel >= 0 && channel < 4);
channels_[channel].length = length;
}
void Audio::set_period(int channel, uint16_t period) {
assert(channel >= 0 && channel < 4);
channels_[channel].period = period;
}
void Audio::set_volume(int channel, uint16_t volume) {
assert(channel >= 0 && channel < 4);
channels_[channel].volume = (volume & 0x40) ? 64 : (volume & 0x3f);
}
template <bool is_external> void Audio::set_data(int channel, uint16_t data) {
assert(channel >= 0 && channel < 4);
channels_[channel].wants_data = false;
channels_[channel].data = data;
// TODO: "the [PWM] counter is reset when ... AUDxDAT is written", but
// does that just mean written by the CPU, or does it include DMA?
// My guess is the former. But TODO.
if constexpr (is_external) {
channels_[channel].reset_output_phase();
}
}
template void Audio::set_data<false>(int, uint16_t);
template void Audio::set_data<true>(int, uint16_t);
void Audio::set_channel_enables(uint16_t enables) {
channels_[0].dma_enabled = enables & 1;
channels_[1].dma_enabled = enables & 2;
channels_[2].dma_enabled = enables & 4;
channels_[3].dma_enabled = enables & 8;
}
void Audio::set_modulation_flags(uint16_t flags) {
channels_[3].attach_period = flags & 0x80;
channels_[2].attach_period = flags & 0x40;
channels_[1].attach_period = flags & 0x20;
channels_[0].attach_period = flags & 0x10;
channels_[3].attach_volume = flags & 0x08;
channels_[2].attach_volume = flags & 0x04;
channels_[1].attach_volume = flags & 0x02;
channels_[0].attach_volume = flags & 0x01;
}
void Audio::set_interrupt_requests(uint16_t requests) {
channels_[0].interrupt_pending = requests & uint16_t(InterruptFlag::AudioChannel0);
channels_[1].interrupt_pending = requests & uint16_t(InterruptFlag::AudioChannel1);
channels_[2].interrupt_pending = requests & uint16_t(InterruptFlag::AudioChannel2);
channels_[3].interrupt_pending = requests & uint16_t(InterruptFlag::AudioChannel3);
}
// MARK: - DMA and mixing.
bool Audio::advance_dma(int channel) {
if(!channels_[channel].wants_data) {
return false;
}
if(channels_[channel].should_reload_address) {
channels_[channel].data_address = pointer_[size_t(channel)];
channels_[channel].should_reload_address = false;
}
set_data<false>(channel, ram_[channels_[channel].data_address & ram_mask_]);
if(channels_[channel].state != Channel::State::WaitingForDummyDMA) {
++channels_[channel].data_address;
}
return true;
}
void Audio::output() {
constexpr InterruptFlag interrupts[] = {
InterruptFlag::AudioChannel0,
InterruptFlag::AudioChannel1,
InterruptFlag::AudioChannel2,
InterruptFlag::AudioChannel3,
};
Channel *const modulands[] = {
&channels_[1],
&channels_[2],
&channels_[3],
nullptr,
};
for(int c = 0; c < 4; c++) {
if(channels_[c].output(modulands[c])) {
posit_interrupt(interrupts[c]);
}
}
// Spin until the next buffer is available if just entering it for the first time.
// Contention here should be essentially non-existent.
if(!sample_pointer_) {
while(!buffer_available_[buffer_pointer_].load(std::memory_order::memory_order_relaxed));
}
// Left.
static_assert(std::tuple_size<AudioBuffer>::value % 2 == 0);
buffer_[buffer_pointer_][sample_pointer_] = int16_t(
(
channels_[1].output_level * channels_[1].output_enabled +
channels_[2].output_level * channels_[2].output_enabled
) << 7
);
// Right.
buffer_[buffer_pointer_][sample_pointer_ + 1] = int16_t(
(
channels_[0].output_level * channels_[0].output_enabled +
channels_[3].output_level * channels_[3].output_enabled
) << 7
);
sample_pointer_ += 2;
if(sample_pointer_ == buffer_[buffer_pointer_].size()) {
const auto &buffer = buffer_[buffer_pointer_];
auto &flag = buffer_available_[buffer_pointer_];
flag.store(false, std::memory_order::memory_order_release);
queue_.enqueue([this, &buffer, &flag] {
speaker_.push(buffer.data(), buffer.size() >> 1);
flag.store(true, std::memory_order::memory_order_relaxed);
});
buffer_pointer_ = (buffer_pointer_ + 1) % BufferCount;
sample_pointer_ = 0;
}
}
// MARK: - Per-channel logic.
/*
Big spiel on the state machine:
Commodore's Hardware Rerefence Manual provides the audio subsystem's state
machine, so I've just tried to reimplement it verbatim. It's depicted
diagrammatically in the original source as a finite state automata, the
below is my attempt to translate that into text.
000 State::Disabled:
-> State::Disabled (000)
if: N/A
action: percntrld
-> State::PlayingHigh (010)
if: AUDDAT, and not AUDxON, and not AUDxIP
action: percntrld, AUDxIR, volcntrld, pbudld1
-> State::WaitingForDummyDMA (001)
if: AUDxON
action: percntrld, AUDxDR, lencntrld, dmasen*
* NOTE: except for this case, dmasen is true only when
LENFIN = 1. Also, AUDxDSR = (AUDxDR and dmasen).
001 State::WaitingForDummyDMA:
-> State::WaitingForDummyDMA (001)
if: N/A
action: None
-> State::Disabled (000)
if: not AUDxON
action: None
-> State::WaitingForDMA (101)
if: AUDxON, and AUDxDAT
action:
1. AUDxIR
2. if not lenfin, then lencount
101 State::WaitingForDMA:
-> State::WaitingForDMA (101)
if: N/A
action: None
-> State:Disabled (000)
if: not AUDxON
action: None
-> State::PlayingHigh (010)
if: AUDxON, and AUDxDAT
action:
1. volcntrld, percntrld, pbufld1
2. if napnav, then AUDxDR
010 State::PlayingHigh
-> State::PlayingHigh (010)
if: N/A
action: percount, and penhi
-> State::PlayingLow (011)
if: perfin
action:
1. if AUDxAP, then pbufld2
2. if AUDxAP and AUDxON, then AUDxDR
3. percntrld
4. if intreq2 and AUDxON and AUDxAP, then AUDxIR
5. if AUDxAP and AUDxON, then AUDxIR
6. if lenfin and AUDxON and AUDxDAT, then lencntrld
7. if (not lenfin) and AUDxON and AUDxDAT, then lencount
8. if lenfin and AUDxON and AUDxDAT, then intreq2
[note that 68 are shared with the Low -> High transition]
011 State::PlayingLow
-> State::PlayingLow (011)
if: N/A
action: percount, and not penhi
-> State::Disabled (000)
if: perfin and not (AUDxON or not AUDxIP)
action: None
-> State::PlayingHigh (010)
if: perfin and (AUDxON or not AUDxIP)
action:
1. pbufld1
2. percntrld
3. if napnav and AUDxON, then AUDxDR
4. if napnav and AUDxON and intreq2, AUDxIR
5. if napnav and not AUDxON, AUDxIR
6. if lenfin and AUDxON and AUDxDAT, then lencntrld
7. if (not lenfin) and AUDxON and AUDxDAT, then lencount
8. if lenfin and AUDxON and AUDxDAT, then intreq2
[note that 6-8 are shared with the High -> Low transition]
Definitions:
AUDxON DMA on "x" indicates channel number (signal from DMACON).
AUDxIP Audio interrupt pending (input to channel from interrupt circuitry).
AUDxIR Audio interrupt request (output from channel to interrupt circuitry).
intreq1 Interrupt request that combines with intreq2 to form AUDxIR.
intreq2 Prepare for interrupt request. Request comes out after the
next 011->010 transition in normal operation.
AUDxDAT Audio data load signal. Loads 16 bits of data to audio channel.
AUDxDR Audio DMA request to Agnus for one word of data.
AUDxDSR Audio DMA request to Agnus to reset pointer to start of block.
dmasen Restart request enable.
percntrld Reload period counter from back-up latch typically written
by processor with AUDxPER (can also be written by attach mode).
percount Count period counter down one latch.
perfin Period counter finished (value = 1).
lencntrld Reload length counter from back-up latch.
lencount Count length counter down one notch.
lenfin Length counter finished (value = 1).
volcntrld Reload volume counter from back-up latch.
pbufld1 Load output buffer from holding latch written to by AUDxDAT.
pbufld2 Like pbufld1, but only during 010->011 with attach period.
AUDxAV Attach volume. Send data to volume latch of next channel
instead of to D->A converter.
AUDxAP Attach period. Send data to period latch of next channel
instead of to the D->A converter.
penhi Enable the high 8 bits of data to go to the D->A converter.
napnav /AUDxAV * /AUDxAP + AUDxAV -- no attach stuff or else attach
volume. Condition for normal DMA and interrupt requests.
*/
//
// Non-action fallback transition and setter, plus specialised begin_state declarations.
//
template <Audio::Channel::State end> void Audio::Channel::begin_state(Channel *) {
state = end;
}
template <> void Audio::Channel::begin_state<Audio::Channel::State::PlayingHigh>(Channel *);
template <> void Audio::Channel::begin_state<Audio::Channel::State::PlayingLow>(Channel *);
template <
Audio::Channel::State begin,
Audio::Channel::State end> bool Audio::Channel::transit(Channel *moduland) {
begin_state<end>(moduland);
return false;
}
//
// Audio::Channel::State::Disabled
//
template <> bool Audio::Channel::transit<
Audio::Channel::State::Disabled,
Audio::Channel::State::PlayingHigh>(Channel *moduland) {
begin_state<State::PlayingHigh>(moduland);
// percntrld
period_counter = period;
// [AUDxIR]: see return result.
// volcntrld
volume_latch = volume;
reset_output_phase();
// pbufld1
data_latch = data;
wants_data = true;
if(moduland && attach_volume) moduland->volume = uint8_t(data_latch);
// AUDxIR.
return true;
}
template <> bool Audio::Channel::transit<
Audio::Channel::State::Disabled,
Audio::Channel::State::WaitingForDummyDMA>(Channel *moduland) {
begin_state<State::WaitingForDummyDMA>(moduland);
// percntrld
period_counter = period;
// AUDxDR
wants_data = true;
// lencntrld
length_counter = length;
// dmasen / AUDxDSR
should_reload_address = true;
return false;
}
template <> bool Audio::Channel::output<Audio::Channel::State::Disabled>(Channel *moduland) {
// if AUDDAT, and not AUDxON, and not AUDxIP.
if(!wants_data && !dma_enabled && !interrupt_pending) {
return transit<State::Disabled, State::PlayingHigh>(moduland);
}
// if AUDxON.
if(dma_enabled) {
return transit<State::Disabled, State::WaitingForDummyDMA>(moduland);
}
return false;
}
//
// Audio::Channel::State::WaitingForDummyDMA
//
template <> bool Audio::Channel::transit<
Audio::Channel::State::WaitingForDummyDMA,
Audio::Channel::State::WaitingForDMA>(Channel *moduland) {
begin_state<State::WaitingForDMA>(moduland);
// AUDxDR
wants_data = true;
// if not lenfin, then lencount
if(length != 1) {
-- length_counter;
}
// AUDxIR
return true;
}
template <> bool Audio::Channel::output<Audio::Channel::State::WaitingForDummyDMA>(Channel *moduland) {
// if not AUDxON
if(!dma_enabled) {
return transit<State::WaitingForDummyDMA, State::Disabled>(moduland);
}
// if AUDxON and AUDxDAT
if(dma_enabled && !wants_data) {
return transit<State::WaitingForDummyDMA, State::WaitingForDMA>(moduland);
}
return false;
}
//
// Audio::Channel::State::WaitingForDMA
//
template <> bool Audio::Channel::transit<
Audio::Channel::State::WaitingForDMA,
Audio::Channel::State::PlayingHigh>(Channel *moduland) {
begin_state<State::PlayingHigh>(moduland);
// volcntrld
volume_latch = volume;
reset_output_phase();
// percntrld
period_counter = period;
// pbufld1
data_latch = data;
if(moduland && attach_volume) moduland->volume = uint8_t(data_latch);
// if napnav
if(attach_volume || !(attach_volume || attach_period)) {
// AUDxDR
wants_data = true;
}
return false;
}
template <> bool Audio::Channel::output<Audio::Channel::State::WaitingForDMA>(Channel *moduland) {
// if: not AUDxON
if(!dma_enabled) {
return transit<State::WaitingForDummyDMA, State::Disabled>(moduland);
}
// if: AUDxON, and AUDxDAT
if(dma_enabled && !wants_data) {
return transit<State::WaitingForDummyDMA, State::PlayingHigh>(moduland);
}
return false;
}
//
// Audio::Channel::State::PlayingHigh
//
void Audio::Channel::decrement_length() {
// if lenfin and AUDxON and AUDxDAT, then lencntrld
// if (not lenfin) and AUDxON and AUDxDAT, then lencount
// if lenfin and AUDxON and AUDxDAT, then intreq2
if(dma_enabled && !wants_data) {
-- length_counter;
if(!length_counter) {
length_counter = length;
will_request_interrupt = true;
should_reload_address = true; // This feels logical to me; it's a bit
// of a stab in the dark though.
}
}
}
template <> bool Audio::Channel::transit<
Audio::Channel::State::PlayingHigh,
Audio::Channel::State::PlayingLow>(Channel *moduland) {
begin_state<State::PlayingLow>(moduland);
bool wants_interrupt = false;
// if AUDxAP
if(attach_period) {
// pbufld2
data_latch = data;
if(moduland) moduland->period = data_latch;
// [if AUDxAP] and AUDxON
if(dma_enabled) {
// AUDxDR
wants_data = true;
// [if AUDxAP and AUDxON] and intreq2
if(will_request_interrupt) {
will_request_interrupt = false;
// AUDxIR
wants_interrupt = true;
}
} else {
// i.e. if AUDxAP and AUDxON, then AUDxIR
wants_interrupt = true;
}
}
// percntrld
period_counter = period;
decrement_length();
return wants_interrupt;
}
template <> void Audio::Channel::begin_state<Audio::Channel::State::PlayingHigh>(Channel *) {
state = Audio::Channel::State::PlayingHigh;
// penhi.
output_level = int8_t(data_latch >> 8);
}
template <> bool Audio::Channel::output<Audio::Channel::State::PlayingHigh>(Channel *moduland) {
// This is a reasonable guess as to the exit condition for this node;
// Commodore doesn't document.
if(period_counter == 1) {
return transit<State::PlayingHigh, State::PlayingLow>(moduland);
}
// percount.
-- period_counter;
return false;
}
//
// Audio::Channel::State::PlayingLow
//
template <> bool Audio::Channel::transit<
Audio::Channel::State::PlayingLow,
Audio::Channel::State::Disabled>(Channel *moduland) {
begin_state<State::Disabled>(moduland);
// Clear the slightly nebulous 'if intreq2 occurred' state.
will_request_interrupt = false;
return false;
}
template <> bool Audio::Channel::transit<
Audio::Channel::State::PlayingLow,
Audio::Channel::State::PlayingHigh>(Channel *moduland) {
begin_state<State::PlayingHigh>(moduland);
bool wants_interrupt = false;
// volcntrld
volume_latch = volume;
reset_output_phase(); // Is this correct?
// percntrld
period_counter = period;
// pbufld1
data_latch = data;
if(moduland && attach_volume) moduland->volume = uint8_t(data_latch);
// if napnav
if(attach_volume || !(attach_volume || attach_period)) {
// [if napnav] and AUDxON
if(dma_enabled) {
// AUDxDR
wants_data = true;
// [if napnav and AUDxON] and intreq2
if(will_request_interrupt) {
will_request_interrupt = false;
wants_interrupt = true;
}
} else {
// AUDxIR
wants_interrupt = true;
}
}
decrement_length();
return wants_interrupt;
}
template <> void Audio::Channel::begin_state<Audio::Channel::State::PlayingLow>(Channel *) {
state = Audio::Channel::State::PlayingLow;
// Output low byte.
output_level = int8_t(data_latch & 0xff);
}
template <> bool Audio::Channel::output<Audio::Channel::State::PlayingLow>(Channel *moduland) {
-- period_counter;
if(!period_counter) {
const bool dma_or_no_interrupt = dma_enabled || !interrupt_pending;
if(dma_or_no_interrupt) {
return transit<State::PlayingLow, State::PlayingHigh>(moduland);
} else {
return transit<State::PlayingLow, State::Disabled>(moduland);
}
}
return false;
}
//
// Dispatcher
//
bool Audio::Channel::output(Channel *moduland) {
// Update pulse-width modulation.
output_phase = output_phase + 1;
if(output_phase == 64) {
reset_output_phase();
} else {
output_enabled &= output_phase != volume_latch;
}
switch(state) {
case State::Disabled: return output<State::Disabled>(moduland);
case State::WaitingForDummyDMA: return output<State::WaitingForDummyDMA>(moduland);
case State::WaitingForDMA: return output<State::WaitingForDMA>(moduland);
case State::PlayingHigh: return output<State::PlayingHigh>(moduland);
case State::PlayingLow: return output<State::PlayingLow>(moduland);
default:
assert(false);
break;
}
return false;
}

163
Machines/Amiga/Audio.hpp Normal file
View File

@@ -0,0 +1,163 @@
//
// Audio.hpp
// Clock Signal
//
// Created by Thomas Harte on 09/11/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Audio_hpp
#define Audio_hpp
#include <atomic>
#include <cstdint>
#include "DMADevice.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../Concurrency/AsyncTaskQueue.hpp"
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
namespace Amiga {
class Audio: public DMADevice<4> {
public:
Audio(Chipset &chipset, uint16_t *ram, size_t word_size, float output_rate);
/// Idiomatic call-in for DMA scheduling; indicates that this class may
/// perform a DMA access for the stated channel now.
bool advance_dma(int channel);
/// Advances output by one DMA window, which is implicitly two cycles
/// at the output rate that was specified to the constructor.
void output();
/// Sets the total number of words to fetch for the given channel.
void set_length(int channel, uint16_t);
/// Sets the number of DMA windows between each 8-bit output,
/// in the same time base as @c ticks_per_line.
void set_period(int channel, uint16_t);
/// Sets the output volume for the given channel; if bit 6 is set
/// then output is maximal; otherwise bits 05 select
/// a volume of [063]/64, on a logarithmic scale.
void set_volume(int channel, uint16_t);
/// Sets the next two samples of audio to output.
template <bool is_external = true> void set_data(int channel, uint16_t);
/// Provides a copy of the DMA enable flags, for the purpose of
/// determining which channels are enabled for DMA.
void set_channel_enables(uint16_t);
/// Sets which channels, if any, modulate period or volume of
/// their neighbours.
void set_modulation_flags(uint16_t);
/// Sets which interrupt requests are currently active.
void set_interrupt_requests(uint16_t);
/// Obtains the output source.
Outputs::Speaker::Speaker *get_speaker() {
return &speaker_;
}
private:
struct Channel {
// The data latch plus a count of unused samples
// in the latch, which will always be 0, 1 or 2.
uint16_t data = 0x0000;
bool wants_data = false;
uint16_t data_latch = 0x0000;
// The DMA address; unlike most of the Amiga Chipset,
// the user posts a value to feed a pointer, rather
// than having access to the pointer itself.
bool should_reload_address = false;
uint32_t data_address = 0x0000'0000;
// Number of words remaining in DMA data.
uint16_t length = 0;
uint16_t length_counter = 0;
// Number of ticks between each sample, plus the
// current counter, which counts downward.
uint16_t period = 0;
uint16_t period_counter = 0;
// Modulation / attach flags.
bool attach_period = false;
bool attach_volume = false;
// Output volume, [0, 64].
uint8_t volume = 0;
uint8_t volume_latch = 0;
// Indicates whether DMA is enabled for this channel.
bool dma_enabled = false;
// Records whether this audio interrupt is pending.
bool interrupt_pending = false;
bool will_request_interrupt = false;
// Replicates the Hardware Reference Manual state machine;
// comments indicate which of the documented states each
// label refers to.
enum class State {
Disabled, // 000
WaitingForDummyDMA, // 001
WaitingForDMA, // 101
PlayingHigh, // 010
PlayingLow, // 011
} state = State::Disabled;
/// Dispatches to the appropriate templatised output for the current state.
/// @param moduland The channel to modulate, if modulation is enabled.
/// @returns @c true if an interrupt should be posted; @c false otherwise.
bool output(Channel *moduland);
/// Applies dynamic logic for @c state, mostly testing for potential state transitions.
/// @param moduland The channel to modulate, if modulation is enabled.
/// @returns @c true if an interrupt should be posted; @c false otherwise.
template <State state> bool output(Channel *moduland);
/// Transitions from @c begin to @c end, calling the appropriate @c begin_state
/// and taking any steps specific to that particular transition.
/// @param moduland The channel to modulate, if modulation is enabled.
/// @returns @c true if an interrupt should be posted; @c false otherwise.
template <State begin, State end> bool transit(Channel *moduland);
/// Begins @c state, performing all fixed logic that would otherwise have to be
/// repeated endlessly in the relevant @c output.
/// @param moduland The channel to modulate, if modulation is enabled.
template <State state> void begin_state(Channel *moduland);
/// Provides the common length-decrementing logic used when transitioning
/// between PlayingHigh and PlayingLow in either direction.
void decrement_length();
// Output state.
int8_t output_level = 0;
uint8_t output_phase = 0;
bool output_enabled = false;
void reset_output_phase() {
output_phase = 0;
output_enabled = (volume_latch > 0) && !attach_period && !attach_volume;
}
} channels_[4];
// Transient output state, and its destination.
Outputs::Speaker::PushLowpass<true> speaker_;
Concurrency::AsyncTaskQueue queue_;
using AudioBuffer = std::array<int16_t, 4096>;
static constexpr int BufferCount = 3;
AudioBuffer buffer_[BufferCount];
std::atomic<bool> buffer_available_[BufferCount];
size_t buffer_pointer_ = 0, sample_pointer_ = 0;
};
}
#endif /* Audio_hpp */

View File

@@ -0,0 +1,132 @@
//
// Bitplanes.cpp
// Clock Signal
//
// Created by Thomas Harte on 26/11/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "Bitplanes.hpp"
#include "Chipset.hpp"
using namespace Amiga;
namespace {
/// Expands @c source so that b7 is the least-significant bit of the most-significant byte of the result,
/// b6 is the least-significant bit of the next most significant byte, etc. b0 stays in place.
constexpr uint64_t expand_bitplane_byte(uint8_t source) {
uint64_t result = source; // 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 abcd efgh
result = (result | (result << 28)) & 0x0000'000f'0000'000f; // 0000 0000 0000 0000 0000 0000 0000 abcd 0000 0000 0000 0000 0000 0000 0000 efgh
result = (result | (result << 14)) & 0x0003'0003'0003'0003; // 0000 0000 0000 00ab 0000 0000 0000 00cd 0000 0000 0000 00ef 0000 0000 0000 00gh
result = (result | (result << 7)) & 0x0101'0101'0101'0101; // 0000 000a 0000 000b 0000 000c 0000 000d 0000 000e 0000 000f 0000 000g 0000 000h
return result;
}
// A very small selection of test cases.
static_assert(expand_bitplane_byte(0xff) == 0x01'01'01'01'01'01'01'01);
static_assert(expand_bitplane_byte(0x55) == 0x00'01'00'01'00'01'00'01);
static_assert(expand_bitplane_byte(0xaa) == 0x01'00'01'00'01'00'01'00);
static_assert(expand_bitplane_byte(0x00) == 0x00'00'00'00'00'00'00'00);
}
// MARK: - BitplaneShifter.
void BitplaneShifter::set(const BitplaneData &previous, const BitplaneData &next, int odd_delay, int even_delay) {
const uint16_t planes[6] = {
uint16_t(((previous[0] << 16) | next[0]) >> even_delay),
uint16_t(((previous[1] << 16) | next[1]) >> odd_delay),
uint16_t(((previous[2] << 16) | next[2]) >> even_delay),
uint16_t(((previous[3] << 16) | next[3]) >> odd_delay),
uint16_t(((previous[4] << 16) | next[4]) >> even_delay),
uint16_t(((previous[5] << 16) | next[5]) >> odd_delay),
};
// Swizzle bits into the form:
//
// [b5 b3 b1 b4 b2 b0]
//
// ... and assume a suitably adjusted palette is in use elsewhere.
// This makes dual playfields very easy to separate.
data_[0] =
(expand_bitplane_byte(uint8_t(planes[0])) << 0) |
(expand_bitplane_byte(uint8_t(planes[2])) << 1) |
(expand_bitplane_byte(uint8_t(planes[4])) << 2) |
(expand_bitplane_byte(uint8_t(planes[1])) << 3) |
(expand_bitplane_byte(uint8_t(planes[3])) << 4) |
(expand_bitplane_byte(uint8_t(planes[5])) << 5);
data_[1] =
(expand_bitplane_byte(uint8_t(planes[0] >> 8)) << 0) |
(expand_bitplane_byte(uint8_t(planes[2] >> 8)) << 1) |
(expand_bitplane_byte(uint8_t(planes[4] >> 8)) << 2) |
(expand_bitplane_byte(uint8_t(planes[1] >> 8)) << 3) |
(expand_bitplane_byte(uint8_t(planes[3] >> 8)) << 4) |
(expand_bitplane_byte(uint8_t(planes[5] >> 8)) << 5);
}
// MARK: - Bitplanes.
bool Bitplanes::advance_dma(int cycle) {
#define BIND_CYCLE(offset, plane) \
case offset: \
if(plane_count_ > plane) { \
next[plane] = ram_[pointer_[plane] & ram_mask_]; \
++pointer_[plane]; \
if constexpr (!plane) { \
chipset_.post_bitplanes(next); \
} \
return true; \
} \
return false;
if(is_high_res_) {
switch(cycle&3) {
default: return false;
BIND_CYCLE(0, 3);
BIND_CYCLE(1, 1);
BIND_CYCLE(2, 2);
BIND_CYCLE(3, 0);
}
} else {
switch(cycle&7) {
default: return false;
/* Omitted: 0. */
BIND_CYCLE(1, 3);
BIND_CYCLE(2, 5);
BIND_CYCLE(3, 1);
/* Omitted: 4. */
BIND_CYCLE(5, 2);
BIND_CYCLE(6, 4);
BIND_CYCLE(7, 0);
}
}
return false;
#undef BIND_CYCLE
}
void Bitplanes::do_end_of_line() {
// Apply modulos here. Posssibly correct?
pointer_[0] += modulos_[1];
pointer_[2] += modulos_[1];
pointer_[4] += modulos_[1];
pointer_[1] += modulos_[0];
pointer_[3] += modulos_[0];
pointer_[5] += modulos_[0];
}
void Bitplanes::set_control(uint16_t control) {
is_high_res_ = control & 0x8000;
plane_count_ = (control >> 12) & 7;
// TODO: who really has responsibility for clearing the other
// bit plane fields?
std::fill(next.begin() + plane_count_, next.end(), 0);
if(plane_count_ == 7) {
plane_count_ = 4;
}
}

View File

@@ -0,0 +1,96 @@
//
// Bitplanes.hpp
// Clock Signal
//
// Created by Thomas Harte on 26/11/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Bitplanes_hpp
#define Bitplanes_hpp
#include <cstdint>
#include "DMADevice.hpp"
namespace Amiga {
struct BitplaneData: public std::array<uint16_t, 6> {
BitplaneData &operator <<= (int c) {
(*this)[0] <<= c;
(*this)[1] <<= c;
(*this)[2] <<= c;
(*this)[3] <<= c;
(*this)[4] <<= c;
(*this)[5] <<= c;
return *this;
}
void clear() {
std::fill(begin(), end(), 0);
}
};
class Bitplanes: public DMADevice<6, 2> {
public:
using DMADevice::DMADevice;
bool advance_dma(int cycle);
void do_end_of_line();
void set_control(uint16_t);
private:
bool is_high_res_ = false;
int plane_count_ = 0;
BitplaneData next;
};
template <typename SourceT> constexpr SourceT bitplane_swizzle(SourceT value) {
return
(value&0x21) |
((value&0x02) << 2) |
((value&0x04) >> 1) |
((value&0x08) << 1) |
((value&0x10) >> 2);
}
class BitplaneShifter {
public:
/// Installs a new set of output pixels.
void set(
const BitplaneData &previous,
const BitplaneData &next,
int odd_delay,
int even_delay);
/// Shifts either two pixels (in low-res mode) and four pixels (in high-res).
void shift(bool high_res) {
constexpr int shifts[] = {16, 32};
data_[1] = (data_[1] << shifts[high_res]) | (data_[0] >> (64 - shifts[high_res]));
data_[0] <<= shifts[high_res];
}
/// @returns The next four pixels to output; in low-resolution mode only two
/// of them will be unique. The value is arranges so that MSB = first pixel to output,
/// LSB = last. Each byte is formed as 00[bitplane 5][bitplane 4]...[bitplane 0].
uint32_t get(bool high_res) {
if(high_res) {
return uint32_t(data_[1] >> 32);
} else {
uint32_t result = uint16_t(data_[1] >> 48);
result = ((result & 0xff00) << 8) | (result & 0x00ff);
result |= result << 8;
return result;
}
}
private:
std::array<uint64_t, 2> data_{};
};
}
#endif /* Bitplanes_hpp */

400
Machines/Amiga/Blitter.cpp Normal file
View File

@@ -0,0 +1,400 @@
//
// Blitter.cpp
// Clock Signal
//
// Created by Thomas Harte on 22/07/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "Blitter.hpp"
#include "Minterms.hpp"
#include <cassert>
#ifndef NDEBUG
#define NDEBUG
#endif
#define LOG_PREFIX "[Blitter] "
#include "../../Outputs/Log.hpp"
using namespace Amiga;
namespace {
/// @returns Either the final carry flag or the output nibble when using fill mode given that it either @c is_exclusive fill mode, or isn't;
/// and the specified initial @c carry and input @c nibble.
template <bool wants_carry> constexpr uint32_t fill_nibble(bool is_exclusive, uint8_t carry, uint8_t nibble) {
uint8_t fill_output = 0;
uint8_t bit = 0x01;
while(bit < 0x10) {
auto pre_toggle = nibble & bit, post_toggle = pre_toggle;
if(!is_exclusive) {
pre_toggle &= ~carry; // Accept bits that would transition to set immediately.
post_toggle &= carry; // Accept bits that would transition to clear after the fact.
} else {
post_toggle = 0; // Just do the pre-toggle.
}
carry ^= pre_toggle;
fill_output |= carry;
carry ^= post_toggle;
bit <<= 1;
carry <<= 1;
}
if constexpr (wants_carry) {
return carry >> 4;
} else {
return fill_output;
}
}
// Lookup key for these tables is:
//
// b0b3: input nibble
// b4: carry
// b5: is_exclusive
//
// i.e. it's in the range [0, 63].
//
// Tables below are indexed such that the higher-order bits select a table entry, lower-order bits select
// a bit or nibble from within the indexed item.
constexpr uint32_t fill_carries[] = {
(fill_nibble<true>(false, 0, 0x0) << 0x0) | (fill_nibble<true>(false, 0, 0x1) << 0x1) | (fill_nibble<true>(false, 0, 0x2) << 0x2) | (fill_nibble<true>(false, 0, 0x3) << 0x3) |
(fill_nibble<true>(false, 0, 0x4) << 0x4) | (fill_nibble<true>(false, 0, 0x5) << 0x5) | (fill_nibble<true>(false, 0, 0x6) << 0x6) | (fill_nibble<true>(false, 0, 0x7) << 0x7) |
(fill_nibble<true>(false, 0, 0x8) << 0x8) | (fill_nibble<true>(false, 0, 0x9) << 0x9) | (fill_nibble<true>(false, 0, 0xa) << 0xa) | (fill_nibble<true>(false, 0, 0xb) << 0xb) |
(fill_nibble<true>(false, 0, 0xc) << 0xc) | (fill_nibble<true>(false, 0, 0xd) << 0xd) | (fill_nibble<true>(false, 0, 0xe) << 0xe) | (fill_nibble<true>(false, 0, 0xf) << 0xf) |
(fill_nibble<true>(false, 1, 0x0) << 0x10) | (fill_nibble<true>(false, 1, 0x1) << 0x11) | (fill_nibble<true>(false, 1, 0x2) << 0x12) | (fill_nibble<true>(false, 1, 0x3) << 0x13) |
(fill_nibble<true>(false, 1, 0x4) << 0x14) | (fill_nibble<true>(false, 1, 0x5) << 0x15) | (fill_nibble<true>(false, 1, 0x6) << 0x16) | (fill_nibble<true>(false, 1, 0x7) << 0x17) |
(fill_nibble<true>(false, 1, 0x8) << 0x18) | (fill_nibble<true>(false, 1, 0x9) << 0x19) | (fill_nibble<true>(false, 1, 0xa) << 0x1a) | (fill_nibble<true>(false, 1, 0xb) << 0x1b) |
(fill_nibble<true>(false, 1, 0xc) << 0x1c) | (fill_nibble<true>(false, 1, 0xd) << 0x1d) | (fill_nibble<true>(false, 1, 0xe) << 0x1e) | (fill_nibble<true>(false, 1, 0xf) << 0x1f),
(fill_nibble<true>(true, 0, 0x0) << 0x0) | (fill_nibble<true>(true, 0, 0x1) << 0x1) | (fill_nibble<true>(true, 0, 0x2) << 0x2) | (fill_nibble<true>(true, 0, 0x3) << 0x3) |
(fill_nibble<true>(true, 0, 0x4) << 0x4) | (fill_nibble<true>(true, 0, 0x5) << 0x5) | (fill_nibble<true>(true, 0, 0x6) << 0x6) | (fill_nibble<true>(true, 0, 0x7) << 0x7) |
(fill_nibble<true>(true, 0, 0x8) << 0x8) | (fill_nibble<true>(true, 0, 0x9) << 0x9) | (fill_nibble<true>(true, 0, 0xa) << 0xa) | (fill_nibble<true>(true, 0, 0xb) << 0xb) |
(fill_nibble<true>(true, 0, 0xc) << 0xc) | (fill_nibble<true>(true, 0, 0xd) << 0xd) | (fill_nibble<true>(true, 0, 0xe) << 0xe) | (fill_nibble<true>(true, 0, 0xf) << 0xf) |
(fill_nibble<true>(true, 1, 0x0) << 0x10) | (fill_nibble<true>(true, 1, 0x1) << 0x11) | (fill_nibble<true>(true, 1, 0x2) << 0x12) | (fill_nibble<true>(true, 1, 0x3) << 0x13) |
(fill_nibble<true>(true, 1, 0x4) << 0x14) | (fill_nibble<true>(true, 1, 0x5) << 0x15) | (fill_nibble<true>(true, 1, 0x6) << 0x16) | (fill_nibble<true>(true, 1, 0x7) << 0x17) |
(fill_nibble<true>(true, 1, 0x8) << 0x18) | (fill_nibble<true>(true, 1, 0x9) << 0x19) | (fill_nibble<true>(true, 1, 0xa) << 0x1a) | (fill_nibble<true>(true, 1, 0xb) << 0x1b) |
(fill_nibble<true>(true, 1, 0xc) << 0x1c) | (fill_nibble<true>(true, 1, 0xd) << 0x1d) | (fill_nibble<true>(true, 1, 0xe) << 0x1e) | (fill_nibble<true>(true, 1, 0xf) << 0x1f),
};
constexpr uint32_t fill_values[] = {
(fill_nibble<false>(false, 0, 0x0) << 0) | (fill_nibble<false>(false, 0, 0x1) << 4) | (fill_nibble<false>(false, 0, 0x2) << 8) | (fill_nibble<false>(false, 0, 0x3) << 12) |
(fill_nibble<false>(false, 0, 0x4) << 16) | (fill_nibble<false>(false, 0, 0x5) << 20) | (fill_nibble<false>(false, 0, 0x6) << 24) | (fill_nibble<false>(false, 0, 0x7) << 28),
(fill_nibble<false>(false, 0, 0x8) << 0) | (fill_nibble<false>(false, 0, 0x9) << 4) | (fill_nibble<false>(false, 0, 0xa) << 8) | (fill_nibble<false>(false, 0, 0xb) << 12) |
(fill_nibble<false>(false, 0, 0xc) << 16) | (fill_nibble<false>(false, 0, 0xd) << 20) | (fill_nibble<false>(false, 0, 0xe) << 24) | (fill_nibble<false>(false, 0, 0xf) << 28),
(fill_nibble<false>(false, 1, 0x0) << 0) | (fill_nibble<false>(false, 1, 0x1) << 4) | (fill_nibble<false>(false, 1, 0x2) << 8) | (fill_nibble<false>(false, 1, 0x3) << 12) |
(fill_nibble<false>(false, 1, 0x4) << 16) | (fill_nibble<false>(false, 1, 0x5) << 20) | (fill_nibble<false>(false, 1, 0x6) << 24) | (fill_nibble<false>(false, 1, 0x7) << 28),
(fill_nibble<false>(false, 1, 0x8) << 0) | (fill_nibble<false>(false, 1, 0x9) << 4) | (fill_nibble<false>(false, 1, 0xa) << 8) | (fill_nibble<false>(false, 1, 0xb) << 12) |
(fill_nibble<false>(false, 1, 0xc) << 16) | (fill_nibble<false>(false, 1, 0xd) << 20) | (fill_nibble<false>(false, 1, 0xe) << 24) | (fill_nibble<false>(false, 1, 0xf) << 28),
(fill_nibble<false>(true, 0, 0x0) << 0) | (fill_nibble<false>(true, 0, 0x1) << 4) | (fill_nibble<false>(true, 0, 0x2) << 8) | (fill_nibble<false>(true, 0, 0x3) << 12) |
(fill_nibble<false>(true, 0, 0x4) << 16) | (fill_nibble<false>(true, 0, 0x5) << 20) | (fill_nibble<false>(true, 0, 0x6) << 24) | (fill_nibble<false>(true, 0, 0x7) << 28),
(fill_nibble<false>(true, 0, 0x8) << 0) | (fill_nibble<false>(true, 0, 0x9) << 4) | (fill_nibble<false>(true, 0, 0xa) << 8) | (fill_nibble<false>(true, 0, 0xb) << 12) |
(fill_nibble<false>(true, 0, 0xc) << 16) | (fill_nibble<false>(true, 0, 0xd) << 20) | (fill_nibble<false>(true, 0, 0xe) << 24) | (fill_nibble<false>(true, 0, 0xf) << 28),
(fill_nibble<false>(true, 1, 0x0) << 0) | (fill_nibble<false>(true, 1, 0x1) << 4) | (fill_nibble<false>(true, 1, 0x2) << 8) | (fill_nibble<false>(true, 1, 0x3) << 12) |
(fill_nibble<false>(true, 1, 0x4) << 16) | (fill_nibble<false>(true, 1, 0x5) << 20) | (fill_nibble<false>(true, 1, 0x6) << 24) | (fill_nibble<false>(true, 1, 0x7) << 28),
(fill_nibble<false>(true, 1, 0x8) << 0) | (fill_nibble<false>(true, 1, 0x9) << 4) | (fill_nibble<false>(true, 1, 0xa) << 8) | (fill_nibble<false>(true, 1, 0xb) << 12) |
(fill_nibble<false>(true, 1, 0xc) << 16) | (fill_nibble<false>(true, 1, 0xd) << 20) | (fill_nibble<false>(true, 1, 0xe) << 24) | (fill_nibble<false>(true, 1, 0xf) << 28),
};
}
void Blitter::set_control(int index, uint16_t value) {
if(index) {
line_mode_ = (value & 0x0001);
one_dot_ = value & 0x0002;
line_direction_ = (value >> 2) & 7;
line_sign_ = (value & 0x0040) ? -1 : 1;
direction_ = one_dot_ ? uint32_t(-1) : uint32_t(1);
exclusive_fill_ = (value & 0x0010);
inclusive_fill_ = !exclusive_fill_ && (value & 0x0008); // Exclusive fill takes precedence. Probably? TODO: verify.
fill_carry_ = (value & 0x0004);
} else {
minterms_ = value & 0xff;
channel_enables_[3] = value & 0x100;
channel_enables_[2] = value & 0x200;
channel_enables_[1] = value & 0x400;
channel_enables_[0] = value & 0x800;
}
shifts_[index] = value >> 12;
LOG("Set control " << index << " to " << PADHEX(4) << value);
}
void Blitter::set_first_word_mask(uint16_t value) {
LOG("Set first word mask: " << PADHEX(4) << value);
a_mask_[0] = value;
}
void Blitter::set_last_word_mask(uint16_t value) {
LOG("Set last word mask: " << PADHEX(4) << value);
a_mask_[1] = value;
}
void Blitter::set_size(uint16_t value) {
// width_ = (width_ & ~0x3f) | (value & 0x3f);
// height_ = (height_ & ~0x3ff) | (value >> 6);
width_ = value & 0x3f;
if(!width_) width_ = 0x40;
height_ = value >> 6;
if(!height_) height_ = 1024;
LOG("Set size to " << std::dec << width_ << ", " << height_);
// Current assumption: writing this register informs the
// blitter that it should treat itself as about to start a new line.
}
void Blitter::set_minterms(uint16_t value) {
LOG("Set minterms " << PADHEX(4) << value);
minterms_ = value & 0xff;
}
//void Blitter::set_vertical_size([[maybe_unused]] uint16_t value) {
// LOG("Set vertical size " << PADHEX(4) << value);
// // TODO. This is ECS only, I think. Ditto set_horizontal_size.
//}
//
//void Blitter::set_horizontal_size([[maybe_unused]] uint16_t value) {
// LOG("Set horizontal size " << PADHEX(4) << value);
//}
void Blitter::set_data(int channel, uint16_t value) {
LOG("Set data " << channel << " to " << PADHEX(4) << value);
// Ugh, backed myself into a corner. TODO: clean.
switch(channel) {
case 0: a_data_ = value; break;
case 1: b_data_ = value; break;
case 2: c_data_ = value; break;
default: break;
}
}
uint16_t Blitter::get_status() {
const uint16_t result =
(not_zero_flag_ ? 0x0000 : 0x2000) | (height_ ? 0x4000 : 0x0000);
LOG("Returned status of " << result);
return result;
}
bool Blitter::advance_dma() {
if(!height_) return false;
not_zero_flag_ = false;
if(line_mode_) {
// As-yet unimplemented:
assert(b_data_ == 0xffff);
//
// Line mode.
//
// Bluffer's guide to line mode:
//
// In Bresenham terms, the following registers have been set up:
//
// [A modulo] = 4 * (dy - dx)
// [B modulo] = 4 * dy
// [A pointer] = 4 * dy - 2 * dx, with the sign flag in BLTCON1 indicating sign.
//
// [A data] = 0x8000
// [Both masks] = 0xffff
// [A shift] = x1 & 15
//
// [B data] = texture
// [B shift] = bit at which to start the line texture (0 = LSB)
//
// [C and D pointers] = word containing the first pixel of the line
// [C and D modulo] = width of the bitplane in bytes
//
// height = number of pixels
//
// If ONEDOT of BLTCON1 is set, plot only a single bit per horizontal row.
//
// BLTCON1 quadrants are (bits 24):
//
// 110 -> step in x, x positive, y negative
// 111 -> step in x, x negative, y negative
// 101 -> step in x, x negative, y positive
// 100 -> step in x, x positive, y positive
//
// 001 -> step in y, x positive, y negative
// 011 -> step in y, x negative, y negative
// 010 -> step in y, x negative, y positive
// 000 -> step in y, x positive, y positive
//
// So that's:
//
// * bit 4 = x [=1] or y [=0] major;
// * bit 3 = 1 => major variable negative; otherwise positive;
// * bit 2 = 1 => minor variable negative; otherwise positive.
//
// Implementation below is heavily based on the documentation found
// at https://github.com/niklasekstrom/blitter-subpixel-line/blob/master/Drawing%20lines%20using%20the%20Amiga%20blitter.pdf
//
int error = int16_t(pointer_[0] << 1) >> 1; // TODO: what happens if line_sign_ doesn't agree with this?
bool draw_ = true;
while(height_--) {
if(draw_) {
// TODO: patterned lines. Unclear what to do with the bit that comes out of b.
// Probably extend it to a full word?
c_data_ = ram_[pointer_[3] & ram_mask_];
const uint16_t output =
apply_minterm<uint16_t>(a_data_ >> shifts_[0], b_data_, c_data_, minterms_);
ram_[pointer_[3] & ram_mask_] = output;
not_zero_flag_ |= output;
draw_ &= !one_dot_;
}
constexpr int LEFT = 1 << 0;
constexpr int RIGHT = 1 << 1;
constexpr int UP = 1 << 2;
constexpr int DOWN = 1 << 3;
int step = (line_direction_ & 4) ?
((line_direction_ & 1) ? LEFT : RIGHT) :
((line_direction_ & 1) ? UP : DOWN);
if(error < 0) {
error += modulos_[1];
} else {
step |=
(line_direction_ & 4) ?
((line_direction_ & 2) ? UP : DOWN) :
((line_direction_ & 2) ? LEFT : RIGHT);
error += modulos_[0];
}
if(step & LEFT) {
--shifts_[0];
if(shifts_[0] == -1) {
--pointer_[3];
}
} else if(step & RIGHT) {
++shifts_[0];
if(shifts_[0] == 16) {
++pointer_[3];
}
}
shifts_[0] &= 15;
if(step & UP) {
pointer_[3] -= modulos_[2];
draw_ = true;
} else if(step & DOWN) {
pointer_[3] += modulos_[2];
draw_ = true;
}
}
} else {
// Copy mode.
// Quick hack: do the entire action atomically.
a32_ = 0;
b32_ = 0;
for(int y = 0; y < height_; y++) {
bool fill_carry = fill_carry_;
for(int x = 0; x < width_; x++) {
uint16_t a_mask = 0xffff;
if(x == 0) a_mask &= a_mask_[0];
if(x == width_ - 1) a_mask &= a_mask_[1];
if(channel_enables_[0]) {
a_data_ = ram_[pointer_[0] & ram_mask_];
pointer_[0] += direction_;
}
a32_ = (a32_ << 16) | (a_data_ & a_mask);
if(channel_enables_[1]) {
b_data_ = ram_[pointer_[1] & ram_mask_];
pointer_[1] += direction_;
}
b32_ = (b32_ << 16) | b_data_;
if(channel_enables_[2]) {
c_data_ = ram_[pointer_[2] & ram_mask_];
pointer_[2] += direction_;
}
uint16_t a, b;
// The barrel shifter shifts to the right in ascending address mode,
// but to the left otherwise
if(!one_dot_) {
a = uint16_t(a32_ >> shifts_[0]);
b = uint16_t(b32_ >> shifts_[1]);
} else {
// TODO: there must be a neater solution than this.
a = uint16_t(
(a32_ << shifts_[0]) |
(a32_ >> (32 - shifts_[0]))
);
b = uint16_t(
(b32_ << shifts_[1]) |
(b32_ >> (32 - shifts_[1]))
);
}
uint16_t output =
apply_minterm<uint16_t>(
a,
b,
c_data_,
minterms_);
if(exclusive_fill_ || inclusive_fill_) {
// Use the fill tables nibble-by-nibble to figure out the filled word.
uint16_t fill_output = 0;
int ongoing_carry = fill_carry;
const int type_mask = exclusive_fill_ ? (1 << 5) : 0;
for(int c = 0; c < 16; c += 4) {
const int total_index = (output & 0xf) | (ongoing_carry << 4) | type_mask;
fill_output |= ((fill_values[total_index >> 3] >> ((total_index & 7) * 4)) & 0xf) << c;
ongoing_carry = (fill_carries[total_index >> 5] >> (total_index & 31)) & 1;
output >>= 4;
}
output = fill_output;
fill_carry = ongoing_carry;
}
not_zero_flag_ |= output;
if(channel_enables_[3]) {
ram_[pointer_[3] & ram_mask_] = output;
pointer_[3] += direction_;
}
}
pointer_[0] += modulos_[0] * channel_enables_[0] * direction_;
pointer_[1] += modulos_[1] * channel_enables_[1] * direction_;
pointer_[2] += modulos_[2] * channel_enables_[2] * direction_;
pointer_[3] += modulos_[3] * channel_enables_[3] * direction_;
}
}
posit_interrupt(InterruptFlag::Blitter);
height_ = 0;
return true;
}

View File

@@ -0,0 +1,69 @@
//
// Blitter.hpp
// Clock Signal
//
// Created by Thomas Harte on 22/07/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Blitter_hpp
#define Blitter_hpp
#include <cstddef>
#include <cstdint>
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "DMADevice.hpp"
namespace Amiga {
class Blitter: public DMADevice<4, 4> {
public:
using DMADevice::DMADevice;
// Various setters; it's assumed that address decoding is handled externally.
//
// In all cases where a channel is identified numerically, it's taken that
// 0 = A, 1 = B, 2 = C, 3 = D.
void set_control(int index, uint16_t value);
void set_first_word_mask(uint16_t value);
void set_last_word_mask(uint16_t value);
void set_size(uint16_t value);
void set_minterms(uint16_t value);
// void set_vertical_size(uint16_t value);
// void set_horizontal_size(uint16_t value);
void set_data(int channel, uint16_t value);
uint16_t get_status();
bool advance_dma();
private:
int width_ = 0, height_ = 0;
int shifts_[2]{};
uint16_t a_mask_[2] = {0xffff, 0xffff};
bool line_mode_ = false;
bool one_dot_ = false;
int line_direction_ = 0;
int line_sign_ = 1;
uint32_t direction_ = 1;
bool inclusive_fill_ = false;
bool exclusive_fill_ = false;
bool fill_carry_ = false;
bool channel_enables_[4]{};
uint8_t minterms_ = 0;
uint32_t a32_ = 0, b32_ = 0;
uint16_t a_data_ = 0, b_data_ = 0, c_data_ = 0;
bool not_zero_flag_ = false;
};
}
#endif /* Blitter_hpp */

1255
Machines/Amiga/Chipset.cpp Normal file

File diff suppressed because it is too large Load Diff

366
Machines/Amiga/Chipset.hpp Normal file
View File

@@ -0,0 +1,366 @@
//
// Chipset.hpp
// Clock Signal
//
// Created by Thomas Harte on 22/07/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Chipset_hpp
#define Chipset_hpp
#include <algorithm>
#include <array>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include "../../Activity/Source.hpp"
#include "../../ClockReceiver/ClockingHintSource.hpp"
#include "../../ClockReceiver/JustInTime.hpp"
#include "../../Components/6526/6526.hpp"
#include "../../Outputs/CRT/CRT.hpp"
#include "../../Processors/68000/68000.hpp"
#include "../../Storage/Disk/Controller/DiskController.hpp"
#include "../../Storage/Disk/Drive.hpp"
#include "Audio.hpp"
#include "Bitplanes.hpp"
#include "Blitter.hpp"
#include "Copper.hpp"
#include "DMADevice.hpp"
#include "Flags.hpp"
#include "Keyboard.hpp"
#include "MouseJoystick.hpp"
#include "MemoryMap.hpp"
#include "Sprites.hpp"
namespace Amiga {
class Chipset: private ClockingHint::Observer {
public:
Chipset(MemoryMap &memory_map, int input_clock_rate);
struct Changes {
int interrupt_level = 0;
HalfCycles duration;
Changes &operator += (const Changes &rhs) {
duration += rhs.duration;
return *this;
}
};
/// Advances the stated amount of time.
Changes run_for(HalfCycles);
/// Advances to the end of the next available CPU slot.
Changes run_until_after_cpu_slot();
/// Performs the provided microcycle, which the caller guarantees to be a memory access.
void perform(const CPU::MC68000::Microcycle &);
/// Sets the current state of the CIA interrupt lines.
void set_cia_interrupts(bool cia_a, bool cia_b);
/// Provides the chipset's current interrupt level.
int get_interrupt_level() {
return interrupt_level_;
}
/// Inserts the disks provided.
/// @returns @c true if anything was inserted; @c false otherwise.
bool insert(const std::vector<std::shared_ptr<Storage::Disk::Disk>> &disks);
// The standard CRT set.
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
Outputs::Display::ScanStatus get_scaled_scan_status() const;
void set_display_type(Outputs::Display::DisplayType);
Outputs::Display::DisplayType get_display_type() const;
// Activity observation.
void set_activity_observer(Activity::Observer *observer) {
cia_a_handler_.set_activity_observer(observer);
disk_controller_.set_activity_observer(observer);
}
// Keyboard and mouse exposure.
Keyboard &get_keyboard() {
return keyboard_;
}
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() {
return joysticks_;
}
// Synchronisation.
void flush();
// Input for receiving collected bitplanes.
void post_bitplanes(const BitplaneData &data);
// Obtains the source of audio output.
Outputs::Speaker::Speaker *get_speaker() {
return audio_.get_speaker();
}
private:
friend class DMADeviceBase;
// MARK: - Register read/write functions.
uint16_t read(uint32_t address, bool allow_conversion = true);
void write(uint32_t address, uint16_t value, bool allow_conversion = true);
static constexpr uint32_t ChipsetAddressMask = 0x1fe;
friend class Copper;
// MARK: - E Clock and keyboard dividers.
HalfCycles cia_divider_;
HalfCycles keyboard_divider_;
// MARK: - Interrupts.
uint16_t interrupt_enable_ = 0;
uint16_t interrupt_requests_ = 0;
int interrupt_level_ = 0;
void update_interrupts();
void posit_interrupt(InterruptFlag);
// MARK: - Scheduler.
template <bool stop_on_cpu> Changes run(HalfCycles duration = HalfCycles::max());
template <bool stop_on_cpu> int advance_slots(int, int);
template <int cycle, bool stop_if_cpu> bool perform_cycle();
template <int cycle> void output();
void output_pixels(int cycles_until_sync);
void apply_ham(uint8_t);
// MARK: - DMA Control, Scheduler and Blitter.
uint16_t dma_control_ = 0;
Blitter blitter_;
// MARK: - Sprites and collision flags.
std::array<Sprite, 8> sprites_;
std::array<TwoSpriteShifter, 4> sprite_shifters_;
uint16_t collisions_ = 0, collisions_flags_= 0;
uint32_t playfield_collision_mask_ = 0, playfield_collision_complement_ = 0;
// MARK: - Raster position and state.
// Definitions related to PAL/NTSC.
// (Default values are PAL).
int line_length_ = 227;
int short_field_height_ = 312;
int vertical_blank_height_ = 25; // PAL = 25, NTSC = 20
// Current raster position.
int line_cycle_ = 0, y_ = 0;
// Parameters affecting bitplane collection and output.
uint16_t display_window_start_[2] = {0, 0};
uint16_t display_window_stop_[2] = {0, 0};
uint16_t fetch_window_[2] = {0, 0};
// Ephemeral bitplane collection state.
bool fetch_vertical_ = false, fetch_horizontal_ = false;
bool display_horizontal_ = false;
bool did_fetch_ = false;
uint16_t fetch_stop_ = 0xffff;
// Output state.
uint16_t border_colour_ = 0;
bool is_border_ = true;
int zone_duration_ = 0;
uint16_t *pixels_ = nullptr;
uint16_t last_colour_ = 0; // Retained for HAM mode.
void flush_output();
Bitplanes bitplanes_;
BitplaneData next_bitplanes_, previous_bitplanes_;
bool has_next_bitplanes_ = false;
int odd_priority_ = 0, even_priority_ = 0;
bool even_over_odd_ = false;
bool hold_and_modify_ = false;
bool dual_playfields_ = false;
bool interlace_ = false;
bool is_long_field_ = false;
BitplaneShifter bitplane_pixels_;
int odd_delay_ = 0, even_delay_ = 0;
bool is_high_res_ = false;
// MARK: - Copper.
Copper copper_;
// MARK: - Audio.
Audio audio_;
// MARK: - Serial port.
class SerialPort {
public:
void set_control(uint16_t) {}
private:
uint16_t value = 0, reload = 0;
uint16_t shift = 0, receive_shift = 0;
uint16_t status;
} serial_;
// MARK: - Pixel output.
Outputs::CRT::CRT crt_;
uint16_t palette_[32]{};
uint16_t swizzled_palette_[64]{};
// MARK: - Mouse.
private:
Mouse mouse_;
public:
Inputs::Mouse &get_mouse() {
return mouse_;
}
// MARK: - Joystick.
private:
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
Joystick &joystick(size_t index) const {
return *static_cast<Joystick *>(joysticks_[index].get());
}
// MARK: - CIAs.
private:
class DiskController;
class CIAAHandler: public MOS::MOS6526::PortHandler {
public:
CIAAHandler(MemoryMap &map, DiskController &controller, Mouse &mouse);
void set_port_output(MOS::MOS6526::Port port, uint8_t value);
uint8_t get_port_input(MOS::MOS6526::Port port);
void set_activity_observer(Activity::Observer *observer);
// TEMPORARY.
// TODO: generalise mice and joysticks.
// This is a hack. A TEMPORARY HACK.
void set_joystick(Joystick *joystick) {
joystick_ = joystick;
}
private:
MemoryMap &map_;
DiskController &controller_;
Mouse &mouse_;
Joystick *joystick_ = nullptr;
Activity::Observer *observer_ = nullptr;
inline static const std::string led_name = "Power";
} cia_a_handler_;
class CIABHandler: public MOS::MOS6526::PortHandler {
public:
CIABHandler(DiskController &controller);
void set_port_output(MOS::MOS6526::Port port, uint8_t value);
uint8_t get_port_input(MOS::MOS6526::Port);
private:
DiskController &controller_;
} cia_b_handler_;
public:
using CIAA = MOS::MOS6526::MOS6526<CIAAHandler, MOS::MOS6526::Personality::P8250>;
using CIAB = MOS::MOS6526::MOS6526<CIABHandler, MOS::MOS6526::Personality::P8250>;
// CIAs are provided for direct access; it's up to the caller properly
// to distinguish relevant accesses.
CIAA cia_a;
CIAB cia_b;
private:
// MARK: - Disk drives.
class DiskDMA: public DMADevice<1> {
public:
using DMADevice::DMADevice;
void set_length(uint16_t);
void set_control(uint16_t);
bool advance_dma();
void enqueue(uint16_t value, bool matches_sync);
private:
uint16_t length_;
bool dma_enable_ = false;
bool write_ = false;
uint16_t last_set_length_ = 0;
bool sync_with_word_ = false;
std::array<uint16_t, 4> buffer_;
size_t buffer_read_ = 0, buffer_write_ = 0;
enum class State {
Inactive,
WaitingForSync,
Reading,
} state_ = State::Inactive;
} disk_;
class DiskController: public Storage::Disk::Controller {
public:
DiskController(Cycles clock_rate, Chipset &chipset, DiskDMA &disk_dma, CIAB &cia);
void set_mtr_sel_side_dir_step(uint8_t);
uint8_t get_rdy_trk0_wpro_chng();
void run_for(Cycles duration) {
Storage::Disk::Controller::run_for(duration);
}
bool insert(const std::shared_ptr<Storage::Disk::Disk> &disk, size_t drive);
void set_activity_observer(Activity::Observer *);
void set_sync_word(uint16_t);
void set_control(uint16_t);
private:
void process_input_bit(int value) final;
void process_index_hole() final;
// Implement the Amiga's drive ID shift registers
// directly in the controller for now.
uint32_t drive_ids_[4]{};
uint32_t previous_select_ = 0;
uint16_t data_ = 0;
int bit_count_ = 0;
uint16_t sync_word_ = 0x4489; // TODO: confirm or deny guess.
bool sync_with_word_ = false;
Chipset &chipset_;
DiskDMA &disk_dma_;
CIAB &cia_;
} disk_controller_;
friend DiskController;
void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference) final;
bool disk_controller_is_sleeping_ = false;
uint16_t paula_disk_control_ = 0;
// MARK: - Keyboard.
Keyboard keyboard_;
};
}
#endif /* Chipset_hpp */

145
Machines/Amiga/Copper.cpp Normal file
View File

@@ -0,0 +1,145 @@
//
// Copper.cpp
// Clock Signal
//
// Created by Thomas Harte on 16/09/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef NDEBUG
#define NDEBUG
#endif
#define LOG_PREFIX "[Copper] "
#include "../../Outputs/Log.hpp"
#include "Chipset.hpp"
#include "Copper.hpp"
using namespace Amiga;
namespace {
bool satisfies_raster(uint16_t position, uint16_t blitter_status, uint16_t *instruction) {
const uint16_t mask = 0x8000 | (instruction[1] & 0x7ffe);
return
(position & mask) >= (instruction[0] & mask) &&
(!(blitter_status & 0x4000) || (instruction[1] & 0x8000));
}
}
//
// Quick notes on the Copper:
//
// There are three instructions: move, wait and skip. All are two words in length.
//
// Move writes a value to one of the Chipset registers; it is encoded as:
//
// First word:
// b0: 0
// b1b8: register address
// b9+: unused ("should be set to 0")
//
// Second word:
// b0b15: value to move.
//
//
// Wait waits until the raster gets to at least a certain position, and
// optionally until the Blitter has finished. It is encoded as:
//
// First word:
// b0: 1
// b1b7: horizontal beam position
// b8+: vertical beam position
//
// Second word:
// b0: 0
// b1b7: horizontal beam comparison mask
// b8b14: vertical beam comparison mask
// b15: 1 => don't also wait for the Blitter to be finished; 0 => wait.
//
//
// Skip skips the next instruction if the raster has already reached a certain
// position, and optionally only if the Blitter has finished, and only if the
// next instruction is a move.
//
// First word:
// b0: 1
// b1b7: horizontal beam position
// b8+: vertical beam position
//
// Second word:
// b0: 1
// b1b7: horizontal beam comparison mask
// b8b14: vertical beam comparison mask
// b15: 1 => don't also test whether the Blitter is finished; 0 => test.
//
bool Copper::advance_dma(uint16_t position, uint16_t blitter_status) {
switch(state_) {
default: return false;
case State::Waiting:
if(satisfies_raster(position, blitter_status, instruction_)) {
LOG("Unblocked waiting for " << PADHEX(4) << instruction_[0] << " at " << PADHEX(4) << position << " with mask " << PADHEX(4) << (instruction_[1] & 0x7ffe));
state_ = State::FetchFirstWord;
}
return false;
case State::FetchFirstWord:
instruction_[0] = ram_[address_ & ram_mask_];
++address_;
state_ = State::FetchSecondWord;
LOG("First word fetch at " << PADHEX(4) << position);
break;
case State::FetchSecondWord: {
// Get and reset the should-skip-next flag.
const bool should_skip_move = skip_next_;
skip_next_ = false;
// Read in the second instruction word.
instruction_[1] = ram_[address_ & ram_mask_];
++address_;
LOG("Second word fetch at " << PADHEX(4) << position);
// Check for a MOVE.
if(!(instruction_[0] & 1)) {
if(!should_skip_move) {
// Stop if this move would be a privilege violation.
instruction_[0] &= 0x1fe;
if((instruction_[0] < 0x10) || (instruction_[0] < 0x20 && !(control_&1))) {
LOG("Invalid MOVE to " << PADHEX(4) << instruction_[0] << "; stopping");
state_ = State::Stopped;
break;
}
chipset_.write(instruction_[0], instruction_[1]);
}
// Roll onto the next command.
state_ = State::FetchFirstWord;
break;
}
// Got to here => this is a WAIT or a SKIP.
const bool raster_is_satisfied = satisfies_raster(position, blitter_status, instruction_);
if(!(instruction_[1] & 1)) {
// A WAIT. Empirically, I don't think this waits at all if the test is
// already satisfied.
state_ = raster_is_satisfied ? State::FetchFirstWord : State::Waiting;
if(raster_is_satisfied) LOG("Will wait from " << PADHEX(4) << position);
break;
}
// Neither a WAIT nor a MOVE => a SKIP.
skip_next_ = satisfies_raster(position, blitter_status, instruction_);
state_ = State::FetchFirstWord;
} break;
}
return true;
}

54
Machines/Amiga/Copper.hpp Normal file
View File

@@ -0,0 +1,54 @@
//
// Copper.hpp
// Clock Signal
//
// Created by Thomas Harte on 16/09/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Copper_h
#define Copper_h
#include "DMADevice.hpp"
namespace Amiga {
class Copper: public DMADevice<2> {
public:
using DMADevice<2>::DMADevice;
/// Offers a DMA slot to the Copper, specifying the current beam position and Blitter status.
///
/// @returns @c true if the slot was used; @c false otherwise.
bool advance_dma(uint16_t position, uint16_t blitter_status);
/// Forces a reload of address @c id (i.e. 0 or 1) and restarts the Copper.
template <int id> void reload() {
address_ = pointer_[id];
state_ = State::FetchFirstWord;
}
/// Sets the Copper control word.
void set_control(uint16_t c) {
control_ = c;
}
/// Forces the Copper into the stopped state.
void stop() {
state_ = State::Stopped;
}
private:
uint32_t address_ = 0;
uint16_t control_ = 0;
enum class State {
FetchFirstWord, FetchSecondWord, Waiting, Stopped,
} state_ = State::Stopped;
bool skip_next_ = false;
uint16_t instruction_[2]{};
};
}
#endif /* Copper_h */

View File

@@ -0,0 +1,74 @@
//
// DMADevice.hpp
// Clock Signal
//
// Created by Thomas Harte on 14/09/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef DMADevice_hpp
#define DMADevice_hpp
#include <array>
#include <cstddef>
#include <cstdint>
#include "Flags.hpp"
namespace Amiga {
class Chipset;
class DMADeviceBase {
public:
DMADeviceBase(Chipset &chipset, uint16_t *ram, size_t word_size) :
chipset_(chipset), ram_(ram), ram_mask_(uint32_t(word_size - 1)) {}
void posit_interrupt(Amiga::InterruptFlag);
protected:
Chipset &chipset_;
uint16_t *const ram_ = nullptr;
const uint32_t ram_mask_ = 0;
};
template <size_t num_addresses, size_t num_modulos = 0> class DMADevice: public DMADeviceBase {
public:
using DMADeviceBase::DMADeviceBase;
/// Writes the word @c value to the address register @c id, shifting it by @c shift (0 or 16) first.
template <int id, int shift> void set_pointer(uint16_t value) {
static_assert(id < num_addresses);
static_assert(shift == 0 || shift == 16);
byte_pointer_[id] = (byte_pointer_[id] & (0xffff'0000 >> shift)) | uint32_t(value << shift);
pointer_[id] = byte_pointer_[id] >> 1;
}
/// Writes the word @c value to the modulo register @c id, shifting it by @c shift (0 or 16) first.
template <int id> void set_modulo(uint16_t value) {
static_assert(id < num_modulos);
// Convert by sign extension.
modulos_[id] = uint32_t(int16_t(value) >> 1);
}
template <int id, int shift> uint16_t get_pointer() {
// Restore the original least-significant bit.
const uint32_t source = (pointer_[id] << 1) | (byte_pointer_[id] & 1);
return uint16_t(source >> shift);
}
protected:
// These are shifted right one to provide word-indexing pointers;
// subclasses should use e.g. ram_[pointer_[0] & ram_mask_] directly.
std::array<uint32_t, num_addresses> pointer_{};
std::array<uint32_t, num_modulos> modulos_{};
private:
std::array<uint32_t, num_addresses> byte_pointer_{};
};
}
#endif /* DMADevice_hpp */

259
Machines/Amiga/Disk.cpp Normal file
View File

@@ -0,0 +1,259 @@
//
// Disk.cpp
// Clock Signal
//
// Created by Thomas Harte on 02/11/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "Chipset.hpp"
#define LOG_PREFIX "[Disk] "
#include "../../Outputs/Log.hpp"
using namespace Amiga;
// MARK: - Disk DMA.
void Chipset::DiskDMA::enqueue(uint16_t value, bool matches_sync) {
if(matches_sync && state_ == State::WaitingForSync) {
state_ = State::Reading;
return;
}
if(state_ == State::Reading) {
buffer_[buffer_write_ & 3] = value;
if(buffer_write_ == buffer_read_ + 4) {
++buffer_read_;
}
++buffer_write_;
}
}
void Chipset::DiskDMA::set_control(uint16_t control) {
sync_with_word_ = control & 0x400;
}
void Chipset::DiskDMA::set_length(uint16_t value) {
if(value == last_set_length_) {
dma_enable_ = value & 0x8000;
write_ = value & 0x4000;
length_ = value & 0x3fff;
buffer_read_ = buffer_write_ = 0;
if(dma_enable_) {
LOG("Disk DMA " << (write_ ? "write" : "read") << " of " << length_ << " to " << PADHEX(8) << pointer_[0]);
}
state_ = sync_with_word_ ? State::WaitingForSync : State::Reading;
}
last_set_length_ = value;
}
bool Chipset::DiskDMA::advance_dma() {
if(!dma_enable_) return false;
if(!write_) {
if(length_ && buffer_read_ != buffer_write_) {
ram_[pointer_[0] & ram_mask_] = buffer_[buffer_read_ & 3];
++pointer_[0];
++buffer_read_;
--length_;
if(!length_) {
chipset_.posit_interrupt(InterruptFlag::DiskBlock);
state_ = State::Inactive;
}
return true;
}
}
return false;
}
// MARK: - Disk Controller.
Chipset::DiskController::DiskController(Cycles clock_rate, Chipset &chipset, DiskDMA &disk_dma, CIAB &cia) :
Storage::Disk::Controller(clock_rate),
chipset_(chipset),
disk_dma_(disk_dma),
cia_(cia) {
// Add four drives.
for(int c = 0; c < 4; c++) {
emplace_drive(clock_rate.as<int>(), 300, 2, Storage::Disk::Drive::ReadyType::IBMRDY);
}
}
void Chipset::DiskController::process_input_bit(int value) {
data_ = uint16_t((data_ << 1) | value);
++bit_count_;
const bool sync_matches = data_ == sync_word_;
if(sync_matches) {
chipset_.posit_interrupt(InterruptFlag::DiskSyncMatch);
if(sync_with_word_) {
bit_count_ = 0;
}
}
if(!(bit_count_ & 15)) {
disk_dma_.enqueue(data_, sync_matches);
}
}
void Chipset::DiskController::set_sync_word(uint16_t value) {
LOG("Set disk sync word to " << PADHEX(4) << value);
sync_word_ = value;
}
void Chipset::DiskController::set_control(uint16_t control) {
// b13 and b14: precompensation length specifier
// b12: 0 => GCR precompensation; 1 => MFM.
// b10: 1 => enable use of word sync; 0 => disable.
// b9: 1 => sync on MSB (Disk II style, presumably?); 0 => don't.
// b8: 1 => 2µs per bit; 0 => 4µs.
sync_with_word_ = control & 0x400;
Storage::Time bit_length;
bit_length.length = 1;
bit_length.clock_rate = (control & 0x100) ? 500000 : 250000;
set_expected_bit_length(bit_length);
LOG((sync_with_word_ ? "Will" : "Won't") << " sync with word; bit length is " << ((control & 0x100) ? "short" : "long"));
}
void Chipset::DiskController::process_index_hole() {
// Pulse the CIA flag input.
//
// TODO: rectify once drives do an actual index pulse, with length.
cia_.set_flag_input(true);
cia_.set_flag_input(false);
// Resync word output. Experimental!!
bit_count_ = 0;
}
void Chipset::DiskController::set_mtr_sel_side_dir_step(uint8_t value) {
// b7: /MTR
// b6: /SEL3
// b5: /SEL2
// b4: /SEL1
// b3: /SEL0
// b2: /SIDE
// b1: DIR
// b0: /STEP
// Select active drive.
set_drive(((value >> 3) & 0x0f) ^ 0x0f);
// "[The MTR] signal is nonstandard on the Amiga system.
// Each drive will latch the motor signal at the time its
// select signal turns on." — The Hardware Reference Manual.
const auto difference = int(previous_select_ ^ value);
previous_select_ = value;
// Check for changes in the SEL line per drive.
const bool motor_on = !(value & 0x80);
const int side = (value & 0x04) ? 0 : 1;
const bool did_step = difference & value & 0x01;
const auto direction = Storage::Disk::HeadPosition(
(value & 0x02) ? -1 : 1
);
for(int c = 0; c < 4; c++) {
auto &drive = get_drive(size_t(c));
const int select_mask = 0x08 << c;
const bool is_selected = !(value & select_mask);
// Both the motor state and the ID shifter are affected upon
// changes in drive selection only.
if(difference & select_mask) {
// If transitioning to inactive, shift the drive ID value;
// if transitioning to active, possibly reset the drive
// ID and definitely latch the new motor state.
if(!is_selected) {
drive_ids_[c] <<= 1;
LOG("Shifted drive ID shift register for drive " << +c << " to " << PADHEX(4) << std::bitset<16>{drive_ids_[c]});
} else {
// Motor transition on -> off => reload register.
if(!motor_on && drive.get_motor_on()) {
// NB:
// 0xffff'ffff = 3.5" drive;
// 0x5555'5555 = 5.25" drive;
// 0x0000'0000 = no drive.
drive_ids_[c] = 0xffff'ffff;
LOG("Reloaded drive ID shift register for drive " << +c);
}
// Also latch the new motor state.
drive.set_motor_on(motor_on);
}
}
// Set the new side.
drive.set_head(side);
// Possibly step.
if(did_step && is_selected) {
LOG("Stepped drive " << +c << " by " << std::dec << +direction.as_int());
drive.step(direction);
}
}
}
uint8_t Chipset::DiskController::get_rdy_trk0_wpro_chng() {
// b5: /RDY
// b4: /TRK0
// b3: /WPRO
// b2: /CHNG
// My interpretation:
//
// RDY isn't RDY, it's a shift value as described above, combined with the motor state.
// CHNG is what is normally RDY.
const uint32_t combined_id =
((previous_select_ & 0x40) ? 0 : drive_ids_[3]) |
((previous_select_ & 0x20) ? 0 : drive_ids_[2]) |
((previous_select_ & 0x10) ? 0 : drive_ids_[1]) |
((previous_select_ & 0x08) ? 0 : drive_ids_[0]);
auto &drive = get_drive();
const uint8_t active_high =
((combined_id & 0x8000) >> 10) |
(drive.get_motor_on() ? 0x20 : 0x00) |
(drive.get_is_ready() ? 0x00 : 0x04) |
(drive.get_is_track_zero() ? 0x10 : 0x00) |
(drive.get_is_read_only() ? 0x08 : 0x00);
return ~active_high;
}
void Chipset::DiskController::set_activity_observer(Activity::Observer *observer) {
for_all_drives([observer] (Storage::Disk::Drive &drive, size_t index) {
drive.set_activity_observer(observer, "Drive " + std::to_string(index+1), true);
});
}
bool Chipset::DiskController::insert(const std::shared_ptr<Storage::Disk::Disk> &disk, size_t drive) {
if(drive >= 4) return false;
get_drive(drive).set_disk(disk);
return true;
}
bool Chipset::insert(const std::vector<std::shared_ptr<Storage::Disk::Disk>> &disks) {
bool inserted = false;
size_t target = 0;
for(const auto &disk: disks) {
inserted |= disk_controller_.insert(disk, target);
++target;
}
return inserted;
}

49
Machines/Amiga/Flags.hpp Normal file
View File

@@ -0,0 +1,49 @@
//
// Flags.hpp
// Clock Signal
//
// Created by Thomas Harte on 13/10/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Flags_hpp
#define Flags_hpp
namespace Amiga {
enum class InterruptFlag: uint16_t {
SerialPortTransmit = 1 << 0,
DiskBlock = 1 << 1,
Software = 1 << 2,
IOPortsAndTimers = 1 << 3, // i.e. CIA A.
Copper = 1 << 4,
VerticalBlank = 1 << 5,
Blitter = 1 << 6,
AudioChannel0 = 1 << 7,
AudioChannel1 = 1 << 8,
AudioChannel2 = 1 << 9,
AudioChannel3 = 1 << 10,
SerialPortReceive = 1 << 11,
DiskSyncMatch = 1 << 12,
External = 1 << 13, // i.e. CIA B.
};
enum class DMAFlag: uint16_t {
AudioChannel0 = 1 << 0,
AudioChannel1 = 1 << 1,
AudioChannel2 = 1 << 2,
AudioChannel3 = 1 << 3,
Disk = 1 << 4,
Sprites = 1 << 5,
Blitter = 1 << 6,
Copper = 1 << 7,
Bitplane = 1 << 8,
AllBelow = 1 << 9,
BlitterPriority = 1 << 10,
BlitterZero = 1 << 13,
BlitterBusy = 1 << 14,
};
};
#endif /* Flags_hpp */

188
Machines/Amiga/Keyboard.cpp Normal file
View File

@@ -0,0 +1,188 @@
//
// Keyboard.cpp
// Clock Signal
//
// Created by Thomas Harte on 29/07/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "Keyboard.hpp"
// Notes to self:
//
//
// Before
// the transmission starts, both KCLK and KDAT are high. The keyboard starts
// the transmission by putting out the first data bit (on KDAT), followed by
// a pulse on KCLK (low then high); then it puts out the second data bit and
// pulses KCLK until all eight data bits have been sent.
//
// When the computer has received the eighth bit, it must pulse KDAT low for
// at least 1 (one) microsecond, as a handshake signal to the keyboard. The
// keyboard must be able to detect pulses greater than or equal
// to 1 microsecond. Software MUST pulse the line low for 85 microseconds to
// ensure compatibility with all keyboard models.
//
//
// If the handshake pulse does not arrive within
// 143 ms of the last clock of the transmission, the keyboard will assume
// that the computer is still waiting for the rest of the transmission and is
// therefore out of sync. The keyboard will then attempt to restore sync by
// going into "resync mode." In this mode, the keyboard clocks out a 1 and
// waits for a handshake pulse. If none arrives within 143 ms, it clocks out
// another 1 and waits again.
//
// The keyboard Hard Resets the Amiga by pulling KCLK low and starting a 500
// millisecond timer. When one or more of the keys is released and 500
// milliseconds have passed, the keyboard will release KCLK.
//
// The usual sequence of events will therefore be: power-up; synchronize;
// transmit "initiate power-up key stream" ($FD); transmit "terminate key
// stream" ($FE).
using namespace Amiga;
Keyboard::Keyboard(Serial::Line<true> &output) : output_(output) {
output_.set_writer_clock_rate(HalfCycles(1'000'000)); // Use µs.
}
/*uint8_t Keyboard::update(uint8_t input) {
// If a bit transmission is ongoing, continue that, up to and including
// the handshake. If no handshake comes, set a macro state of synchronising.
switch(shift_state_) {
case ShiftState::Shifting:
// The keyboard processor sets the KDAT line about 20 microseconds before it
// pulls KCLK low. KCLK stays low for about 20 microseconds, then goes high
// again. The processor waits another 20 microseconds before changing KDAT.
switch(bit_phase_) {
default: break;
case 0: lines_ = Lines::Clock | (shift_sequence_ & 1); break;
case 20: lines_ = (shift_sequence_ & 1); break;
case 40: lines_ = Lines::Clock | (shift_sequence_ & 1); break;
}
bit_phase_ = (bit_phase_ + 1) % 60;
if(!bit_phase_) {
--bits_remaining_;
shift_sequence_ >>= 1;
if(!bits_remaining_) {
shift_state_ = ShiftState::AwaitingHandshake;
}
}
return lines_;
case ShiftState::AwaitingHandshake:
if(!(input & Lines::Data)) {
shift_state_ = ShiftState::Idle;
}
++bit_phase_;
if(bit_phase_ == 143) {
// shift_state_ = ShiftState::Synchronising;
}
return lines_;
default: break;
}
switch(state_) {
case State::Startup:
bit_phase_ = 0;
shift_sequence_ = 0xff;
shift_state_ = ShiftState::Shifting;
break;
}
return lines_;
}*/
void Keyboard::set_key_state(uint16_t key, bool is_pressed) {
if(pressed_[key] == is_pressed) {
return;
}
pressed_[key] = is_pressed;
output_.write<false>(
HalfCycles(60),
uint8_t(((key << 1) | (is_pressed ? 0 : 1)) ^ 0xff)
);
}
void Keyboard::clear_all_keys() {
for(uint16_t c = 0; c < uint16_t(pressed_.size()); c++) {
if(pressed_[c]) set_key_state(c, false);
}
}
// MARK: - KeyboardMapper.
uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) const {
#define BIND(source, dest) case Inputs::Keyboard::Key::source: return uint16_t(Key::dest)
#define DIRECTBIND(source) BIND(source, source)
switch(key) {
default: break;
DIRECTBIND(Escape);
DIRECTBIND(Delete);
DIRECTBIND(F1); DIRECTBIND(F2); DIRECTBIND(F3); DIRECTBIND(F4); DIRECTBIND(F5);
DIRECTBIND(F6); DIRECTBIND(F7); DIRECTBIND(F8); DIRECTBIND(F9); DIRECTBIND(F10);
BIND(BackTick, Tilde);
DIRECTBIND(k1); DIRECTBIND(k2); DIRECTBIND(k3); DIRECTBIND(k4); DIRECTBIND(k5);
DIRECTBIND(k6); DIRECTBIND(k7); DIRECTBIND(k8); DIRECTBIND(k9); DIRECTBIND(k0);
DIRECTBIND(Hyphen);
DIRECTBIND(Equals);
DIRECTBIND(Backslash);
DIRECTBIND(Backspace);
DIRECTBIND(Tab);
DIRECTBIND(CapsLock);
BIND(LeftControl, Control);
BIND(RightControl, Control);
DIRECTBIND(LeftShift);
DIRECTBIND(RightShift);
BIND(LeftOption, Alt);
BIND(RightOption, Alt);
BIND(LeftMeta, LeftAmiga);
BIND(RightMeta, RightAmiga);
DIRECTBIND(Q); DIRECTBIND(W); DIRECTBIND(E); DIRECTBIND(R); DIRECTBIND(T);
DIRECTBIND(Y); DIRECTBIND(U); DIRECTBIND(I); DIRECTBIND(O); DIRECTBIND(P);
DIRECTBIND(A); DIRECTBIND(S); DIRECTBIND(D); DIRECTBIND(F); DIRECTBIND(G);
DIRECTBIND(H); DIRECTBIND(J); DIRECTBIND(K); DIRECTBIND(L); DIRECTBIND(Z);
DIRECTBIND(X); DIRECTBIND(C); DIRECTBIND(V); DIRECTBIND(B); DIRECTBIND(N);
DIRECTBIND(M);
DIRECTBIND(OpenSquareBracket);
DIRECTBIND(CloseSquareBracket);
DIRECTBIND(Help);
BIND(Insert, Help);
BIND(Home, Help);
BIND(End, Help);
BIND(Enter, Return);
DIRECTBIND(Semicolon);
DIRECTBIND(Quote);
DIRECTBIND(Comma);
DIRECTBIND(FullStop);
DIRECTBIND(ForwardSlash);
DIRECTBIND(Space);
DIRECTBIND(Up);
DIRECTBIND(Down);
DIRECTBIND(Left);
DIRECTBIND(Right);
DIRECTBIND(Keypad0); DIRECTBIND(Keypad1); DIRECTBIND(Keypad2);
DIRECTBIND(Keypad3); DIRECTBIND(Keypad4); DIRECTBIND(Keypad5);
DIRECTBIND(Keypad6); DIRECTBIND(Keypad7); DIRECTBIND(Keypad8);
DIRECTBIND(Keypad9);
DIRECTBIND(KeypadDecimalPoint);
DIRECTBIND(KeypadMinus);
DIRECTBIND(KeypadEnter);
}
#undef DIRECTBIND
#undef BIND
return MachineTypes::MappedKeyboardMachine::KeyNotMapped;
}

121
Machines/Amiga/Keyboard.hpp Normal file
View File

@@ -0,0 +1,121 @@
//
// Keyboard.hpp
// Clock Signal
//
// Created by Thomas Harte on 29/07/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Machines_Amiga_Keyboard_hpp
#define Machines_Amiga_Keyboard_hpp
#include <array>
#include <cstdint>
#include "../KeyboardMachine.hpp"
#include "../../Components/Serial/Line.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
namespace Amiga {
enum class Key: uint16_t {
Escape = 0x45,
Delete = 0x46,
F1 = 0x50, F2 = 0x51, F3 = 0x52, F4 = 0x53, F5 = 0x54,
F6 = 0x55, F7 = 0x56, F8 = 0x57, F9 = 0x58, F10 = 0x59,
Tilde = 0x00,
k1 = 0x01, k2 = 0x02, k3 = 0x03, k4 = 0x04, k5 = 0x05,
k6 = 0x06, k7 = 0x07, k8 = 0x08, k9 = 0x09, k0 = 0x0a,
Hyphen = 0x0b,
Equals = 0x0c,
Backslash = 0x0d,
Backspace = 0x41,
Tab = 0x42,
Control = 0x63,
CapsLock = 0x62,
LeftShift = 0x60,
RightShift = 0x61,
Q = 0x10, W = 0x11, E = 0x12, R = 0x13, T = 0x14,
Y = 0x15, U = 0x16, I = 0x17, O = 0x18, P = 0x19,
A = 0x20, S = 0x21, D = 0x22, F = 0x23, G = 0x24,
H = 0x25, J = 0x26, K = 0x27, L = 0x28, Z = 0x31,
X = 0x32, C = 0x33, V = 0x34, B = 0x35, N = 0x36,
M = 0x37,
OpenSquareBracket = 0x1a,
CloseSquareBracket = 0x1b,
Help = 0x5f,
Return = 0x44,
Semicolon = 0x29,
Quote = 0x2a,
Comma = 0x38,
FullStop = 0x39,
ForwardSlash = 0x3a,
Alt = 0x64,
LeftAmiga = 0x66,
RightAmiga = 0x67,
Space = 0x40,
Up = 0x4c, Left = 0x4f, Right = 0x4e, Down = 0x4d,
Keypad7 = 0x3d, Keypad8 = 0x3e, Keypad9 = 0x3f,
Keypad4 = 0x2d, Keypad5 = 0x2e, Keypad6 = 0x2f,
Keypad1 = 0x1d, Keypad2 = 0x1e, Keypad3 = 0x1f,
Keypad0 = 0x0f, KeypadDecimalPoint = 0x3c,
KeypadMinus = 0x4a, KeypadEnter = 0x43,
KeypadOpenBracket = 0x5a,
KeypadCloseBracket = 0x5b,
KeypadDivide = 0x5c,
KeypadMultiply = 0x5d,
KeypadPlus = 0x5e,
};
struct KeyboardMapper: public MachineTypes::MappedKeyboardMachine::KeyboardMapper {
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) const final;
};
class Keyboard {
public:
Keyboard(Serial::Line<true> &output);
// enum Lines: uint8_t {
// Data = (1 << 0),
// Clock = (1 << 1),
// };
//
// uint8_t update(uint8_t);
void set_key_state(uint16_t, bool);
void clear_all_keys();
void run_for(HalfCycles duration) {
output_.advance_writer(duration);
}
private:
enum class ShiftState {
Shifting,
AwaitingHandshake,
Idle,
} shift_state_ = ShiftState::Idle;
enum class State {
Startup,
} state_ = State::Startup;
int bit_phase_ = 0;
uint32_t shift_sequence_ = 0;
int bits_remaining_ = 0;
uint8_t lines_ = 0;
Serial::Line<true> &output_;
std::array<bool, 128> pressed_{};
};
}
#endif /* Machines_Amiga_Keyboard_hpp */

View File

@@ -0,0 +1,85 @@
//
// MemoryMap.hpp
// Clock Signal
//
// Created by Thomas Harte on 04/10/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef MemoryMap_hpp
#define MemoryMap_hpp
namespace Amiga {
class MemoryMap {
private:
static constexpr auto PermitRead = CPU::MC68000::Microcycle::PermitRead;
static constexpr auto PermitWrite = CPU::MC68000::Microcycle::PermitWrite;
static constexpr auto PermitReadWrite = PermitRead | PermitWrite;
public:
// TODO: decide what of the below I want to be dynamic.
std::array<uint8_t, 1024*1024> chip_ram{};
std::array<uint8_t, 512*1024> kickstart{0xff};
struct MemoryRegion {
uint8_t *contents = nullptr;
unsigned int read_write_mask = 0;
} regions[64]; // i.e. top six bits are used as an index.
MemoryMap() {
// Address spaces that matter:
//
// 00'0000 08'0000: chip RAM. [or overlayed KickStart]
// 10'0000: extended chip ram for ECS.
// 20'0000: slow RAM and further chip RAM.
// a0'0000: auto-config space (/fast RAM).
// ...
// bf'd000 c0'0000: 8250s.
// c0'0000 d8'0000: pseudo-fast RAM.
// ...
// dc'0000 dd'0000: optional real-time clock.
// df'f000 - e0'0000: custom chip registers.
// ...
// f0'0000 — : 512kb Kickstart (or possibly just an extra 512kb reserved for hypothetical 1mb Kickstart?).
// f8'0000 — : 256kb Kickstart if 2.04 or higher.
// fc'0000 : 256kb Kickstart otherwise.
set_region(0xfc'0000, 0x1'00'0000, kickstart.data(), PermitRead);
reset();
}
void reset() {
set_overlay(true);
}
void set_overlay(bool enabled) {
if(overlay_ == enabled) {
return;
}
overlay_ = enabled;
set_region(0x00'0000, uint32_t(chip_ram.size()), chip_ram.data(), PermitReadWrite);
if(enabled) {
set_region(0x00'0000, 0x08'0000, kickstart.data(), PermitRead);
}
}
private:
bool overlay_ = false;
void set_region(uint32_t start, uint32_t end, uint8_t *base, unsigned int read_write_mask) {
[[maybe_unused]] constexpr uint32_t precision_loss_mask = uint32_t(~0xfc'0000);
assert(!(start & precision_loss_mask));
assert(!((end - (1 << 18)) & precision_loss_mask));
assert(end > start);
if(base) base -= start;
for(decltype(start) c = start >> 18; c < end >> 18; c++) {
regions[c].contents = base;
regions[c].read_write_mask = read_write_mask;
}
}
};
}
#endif /* MemoryMap_hpp */

387
Machines/Amiga/Minterms.hpp Normal file
View File

@@ -0,0 +1,387 @@
//
// Minterms.hpp
// Clock Signal
//
// Created by Thomas Harte on 20/09/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Minterms_hpp
#define Minterms_hpp
namespace Amiga {
/// @returns the result of applying the Amiga-format @c minterm to inputs @c a, @c b and @c c.
template <typename IntT> IntT apply_minterm(IntT a, IntT b, IntT c, int minterm) {
// Quick implementation notes:
//
// This was created very lazily. I tried to enter as many logical combinations of
// a, b and c as I could think of and had a test program match them up to the
// Amiga minterm IDs, prioritising by simplicity.
//
// That got me most of the way; starting from the point indicated below I had run
// out of good ideas and automatically generated the rest.
//
switch(minterm) {
default:
case 0x00: return IntT(0);
case 0xff: return IntT(~0);
case 0xf0: return a;
case 0xcc: return b;
case 0xaa: return c;
case 0x0f: return ~a;
case 0x33: return ~b;
case 0x55: return ~c;
case 0xfc: return a | b;
case 0xfa: return a | c;
case 0xee: return b | c;
case 0xfe: return a | b | c;
case 0xf3: return a | ~b;
case 0xf5: return a | ~c;
case 0xdd: return b | ~c;
case 0xfd: return a | b | ~c;
case 0xfb: return a | ~b | c;
case 0xf7: return a | ~b | ~c;
case 0xcf: return ~a | b;
case 0xaf: return ~a | c;
case 0xbb: return ~b | c;
case 0xef: return ~a | b | c;
case 0xdf: return ~a | b | ~c;
case 0x7f: return ~a | ~b | ~c;
case 0x3c: return a ^ b;
case 0x5a: return a ^ c;
case 0x66: return b ^ c;
case 0x96: return a ^ b ^ c;
case 0xc3: return ~a ^ b;
case 0xa5: return ~a ^ c;
case 0x99: return ~b ^ c;
case 0x69: return ~a ^ b ^ c;
case 0xc0: return a & b;
case 0xa0: return a & c;
case 0x88: return b & c;
case 0x80: return a & b & c;
case 0x30: return a & ~b;
case 0x50: return a & ~c;
case 0x44: return b & ~c;
case 0x0c: return ~a & b;
case 0x0a: return ~a & c;
case 0x22: return ~b & c;
case 0x40: return a & b & ~c;
case 0x20: return a & ~b & c;
case 0x08: return ~a & b & c;
case 0x10: return a & ~b & ~c;
case 0x04: return ~a & b & ~c;
case 0x02: return ~a & ~b & c;
case 0x03: return ~a & ~b;
case 0x05: return ~a & ~c;
case 0x11: return ~b & ~c;
case 0x01: return ~a & ~b & ~c;
case 0x70: return a & ~(b & c);
case 0x4c: return b & ~(a & c);
case 0x2a: return c & ~(a & b);
case 0x07: return ~a & ~(b & c);
case 0x13: return ~b & ~(a & c);
case 0x15: return ~c & ~(a & b);
case 0xe0: return a & (b | c);
case 0xc8: return b & (a | c);
case 0xa8: return c & (a | b);
case 0x0e: return ~a & (b | c);
case 0x32: return ~b & (a | c);
case 0x54: return ~c & (a | b);
case 0x60: return a & (b ^ c);
case 0x48: return b & (a ^ c);
case 0x28: return c & (a ^ b);
case 0x06: return ~a & (b ^ c);
case 0x12: return ~b & (a ^ c);
case 0x14: return ~c & (a ^ b);
case 0x90: return a & ~(b ^ c);
case 0x84: return b & ~(a ^ c);
case 0x82: return c & ~(a ^ b);
case 0x09: return ~a & ~(b ^ c);
case 0x21: return ~b & ~(a ^ c);
case 0x41: return ~c & ~(a ^ b);
case 0xb0: return a & (~b | c);
case 0xd0: return a & (b | ~c);
case 0x0b: return ~a & (~b | c);
case 0x0d: return ~a & (b | ~c);
case 0xf6: return a | (b ^ c);
case 0xde: return b | (a ^ c);
case 0xbe: return c | (a ^ b);
case 0x6f: return ~a | (b ^ c);
case 0x7b: return ~b | (a ^ c);
case 0x7d: return ~c | (a ^ b);
case 0x9f: return ~a | ~(b ^ c);
case 0xb7: return ~b | ~(a ^ c);
case 0xd7: return ~c | ~(a ^ b);
case 0xf8: return a | (b & c);
case 0xec: return b | (a & c);
case 0xea: return c | (a & b);
case 0x8f: return ~a | (b & c);
case 0xb3: return ~b | (a & c);
case 0xd5: return ~c | (a & b);
case 0xf1: return a | ~(b | c);
case 0xcd: return b | ~(a | c);
case 0xab: return c | ~(a | b);
case 0x1f: return ~a | ~(b | c);
case 0x37: return ~b | ~(a | c);
case 0x57: return ~c | ~(a | b);
case 0x8c: return b & (~a | c);
case 0x8a: return c & (~a | b);
case 0xc4: return b & (a | ~c);
case 0xa2: return c & (a | ~b);
case 0x78: return a ^ (b & c);
case 0x6c: return b ^ (a & c);
case 0x6a: return c ^ (a & b);
case 0x87: return ~a ^ (b & c);
case 0x93: return ~b ^ (a & c);
case 0x95: return ~c ^ (a & b);
case 0x1e: return a ^ (b | c);
case 0x36: return b ^ (a | c);
case 0x56: return c ^ (a | b);
case 0x2d: return a ^ (b | ~c);
case 0x4b: return a ^ (~b | c);
case 0xe1: return a ^ ~(b | c);
case 0x39: return b ^ (a | ~c);
case 0x63: return b ^ (~a | c);
case 0xc9: return b ^ ~(a | c);
case 0x59: return c ^ (a | ~b);
case 0x65: return c ^ (~a | b);
case 0xa9: return c ^ ~(a | b);
case 0x24: return (a ^ b) & (b ^ c);
case 0x18: return (a ^ b) & (a ^ c);
case 0x42: return (a ^ c) & (b ^ c);
case 0xa6: return (a & b) ^ (b ^ c);
case 0xc6: return (a & c) ^ (b ^ c);
case 0x5c: return (a | b) ^ (a & c);
case 0x74: return (a | b) ^ (b & c);
case 0x72: return (a | c) ^ (b & c);
case 0x4e: return (b | c) ^ (a & c);
case 0x58: return (a | b) & (a ^ c);
case 0x62: return (a | c) & (b ^ c);
case 0x7e: return (a ^ b) | (a ^ c);
case 0xca: return (a & b) | (~a & c);
case 0xac: return (~a & b) | (a & c);
case 0xa3: return (~a & ~b) | (a & c);
case 0xf4: return a | ((a ^ b) & (b ^ c));
case 0xf2: return a | ((a ^ c) & (b ^ c));
case 0xdc: return b | ((a ^ b) & (a ^ c));
case 0xce: return b | ((a ^ c) & (b ^ c));
case 0xae: return c | ((a ^ b) & (b ^ c));
case 0xba: return c | ((a ^ b) & (a ^ c));
case 0x2f: return ~a | ((a ^ b) & (b ^ c));
case 0x4f: return ~a | ((a ^ c) & (b ^ c));
case 0x3b: return ~b | ((a ^ b) & (a ^ c));
case 0x73: return ~b | ((a ^ c) & (b ^ c));
case 0x75: return ~c | ((a ^ b) & (b ^ c));
case 0x5d: return ~c | ((a ^ b) & (a ^ c));
case 0x3f: return ~a | ~b | ((a ^ b) & (b ^ c));
case 0x77: return ~b | ~c | ((a ^ b) & (b ^ c));
case 0x27: return ~(a | b) | ((a ^ b) & (b ^ c));
case 0x47: return ~(a | c) | ((a ^ c) & (b ^ c));
case 0x53: return ~(b | c) | ((a ^ c) & (b ^ c));
case 0x43: return ~(a | b | c) | ((a ^ c) & (b ^ c));
case 0x7a: return (a & ~b) | (a ^ c);
case 0x76: return (a & ~b) | (b ^ c);
case 0x7c: return (a & ~c) | (a ^ b);
case 0x5e: return (~a & b) | (a ^ c);
case 0x6e: return (~a & b) | (b ^ c);
case 0x3e: return (~a & c) | (a ^ b);
case 0xad: return (~a & b) | ~(a ^ c);
case 0xb5: return (a & ~b) | ~(a ^ c);
case 0xcb: return (~a & c) | ~(a ^ b);
case 0xd3: return (a & ~c) | ~(a ^ b);
case 0x9b: return (~a & c) | ~(b ^ c);
case 0xd9: return (a & ~c) | ~(b ^ c);
case 0x9d: return (~a & b) | ~(b ^ c);
case 0xb9: return (a & ~b) | ~(b ^ c);
case 0x9e: return (~a & b) | (a ^ b ^ c);
case 0xb6: return (a & ~b) | (a ^ b ^ c);
case 0xd6: return (a & ~c) | (a ^ b ^ c);
case 0xbf: return ~(a & b) | (a ^ b ^ c);
case 0x6d: return (~a & b) | ~(a ^ b ^ c);
case 0x79: return (a & ~b) | ~(a ^ b ^ c);
case 0x6b: return (~a & c) | ~(a ^ b ^ c);
case 0xe9: return (b & c) | ~(a ^ b ^ c);
case 0xb8: return (a & ~b) | (c & b);
case 0xd8: return (a & ~c) | (b & c);
case 0xe4: return (b & ~c) | (a & c);
case 0xe2: return (c & ~b) | (a & b);
case 0x2c: return (~a & b) | ((a ^ b) & (b ^ c));
case 0x34: return (a & ~b) | ((a ^ b) & (b ^ c));
case 0x4a: return (~a & c) | ((a ^ c) & (b ^ c));
case 0x52: return (a & ~c) | ((a ^ c) & (b ^ c));
case 0x5f: return ~(a & c) | ((a ^ c) & (b ^ c));
case 0x16: return (a & ~(c | b)) | (c & ~(b | a)) | (b & ~(a | c));
case 0x81: return (a ^ ~(c | b)) & (c ^ ~(b | a)) & (b ^ ~(a | c));
case 0x2e: return (~a & (b | c)) | (~b & c);
case 0x3a: return (~b & (a | c)) | (~a & c);
case 0x8b: return (~a & ~b) | (c & b);
case 0x8d: return (~a & ~c) | (b & c);
case 0xb1: return (~b & ~c) | (a & c);
case 0xd1: return (~c & ~b) | (a & b);
case 0x98: return (a & ~(c | b)) | (b & c);
case 0x8e: return (~a & (c | b)) | (b & c);
case 0x46: return (~a | b) & (b ^ c);
case 0xe6: return ((~a | b) & (b ^ c)) ^ (a & c);
case 0xc2: return ((a | ~b) & (b ^ c)) ^ (a & c);
case 0x85: return (~a | b) & ~(a ^ c);
case 0x83: return (~a | c) & ~(a ^ b);
case 0x89: return (~a | c) & ~(b ^ c);
case 0xa1: return (a | ~b) & ~(a ^ c);
case 0x91: return (a | ~b) & ~(b ^ c);
case 0xc1: return (a | ~c) & ~(a ^ b);
case 0x94: return (a | b) & (a ^ b ^ c);
case 0x86: return (b | c) & (a ^ b ^ c);
case 0x92: return (a | c) & (a ^ b ^ c);
case 0x68: return (a | b) & ~(a ^ b ^ c);
case 0x61: return (a | ~b) & ~(a ^ b ^ c);
case 0x49: return (~a | b) & ~(a ^ b ^ c);
case 0x29: return (~a | c) & ~(a ^ b ^ c);
case 0x64: return (a & ~b & c) | (b & ~c);
//
// From here downwards functions were found automatically.
// Neater versions likely exist of many of the functions below.
//
case 0xe8: return (a & b) | ((b | a) & c);
case 0xd4: return (a & b) | ((b | a) & ~c);
case 0xb2: return (a & ~b) | ((~b | a) & c);
case 0x17: return (~a & ~b) | ((~b | ~a) & ~c);
case 0x1b: return (~a & ~b) | (~b & ~c) | (~a & c);
case 0x1d: return (~a & b) | ((~b | ~a) & ~c);
case 0x2b: return (~a & ~b) | ((~b | ~a) & c);
case 0x35: return (a & ~b) | ((~b | ~a) & ~c);
case 0x4d: return (~a & b) | ((b | ~a) & ~c);
case 0x71: return (a & ~b) | ((~b | a) & ~c);
case 0xbd: return (~a & b) | (~b & ~c) | (a & c);
case 0xc5: return (a & b) | ((b | ~a) & ~c);
case 0xdb: return (a & b) | (~b & ~c) | (~a & c);
case 0xe7: return (~a & ~b) | (b & ~c) | (a & c);
case 0x1c: return (~a & b) | (a & ~b & ~c);
case 0x23: return (~a & ~b) | (a & ~b & c);
case 0x31: return (a & ~b) | (~a & ~b & ~c);
case 0x38: return (a & ~b) | (~a & b & c);
case 0x1a: return (~a & c) | (a & ~b & ~c);
case 0x25: return (~a & ~c) | (a & ~b & c);
case 0x45: return (~a & ~c) | (a & b & ~c);
case 0x51: return (a & ~c) | (~a & ~b & ~c);
case 0xa4: return (a & c) | (~a & b & ~c);
case 0x19: return (~b & ~c) | (~a & b & c);
case 0x26: return (~b & c) | (~a & b & ~c);
case 0xc7: return (a & b) | (~a & (~b | ~c));
case 0x3d: return (a & ~b) | (~a & (b | ~c));
case 0xbc: return (~a & b) | (a & (~b | c));
case 0xe3: return (~a & ~b) | (a & (b | c));
case 0xa7: return (a & c) | (~a & (~b | ~c));
case 0x5b: return (a & ~c) | (~a & (~b | c));
case 0xda: return (~a & c) | (a & (b | ~c));
case 0xe5: return (~a & ~c) | (a & (b | c));
case 0x67: return (~a & ~b) | ((~a | b) & ~c) | (~b & c);
case 0x97: return (~a & ~b) | ((~a | ~b) & ~c) | (a & b & c);
case 0xb4: return (a & ~b) | (a & c) | (~a & b & ~c);
case 0x9c: return (~a & b) | (b & c) | (a & ~b & ~c);
case 0xd2: return ((~c | b) & a) | (~a & ~b & c);
case 0x9a: return ((~a | b) & c) | (a & ~b & ~c);
case 0xf9: return a | (~b & ~c) | (b & c);
case 0xed: return b | (~a & ~c) | (a & c);
case 0xeb: return c | (~a & ~b) | (a & b);
}
// Should be unreachable.
return 0;
}
}
#endif /* Minterms_hpp */

View File

@@ -0,0 +1,124 @@
//
// MouseJoystick.cpp
// Clock Signal
//
// Created by Thomas Harte on 26/11/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "MouseJoystick.hpp"
#include <algorithm>
using namespace Amiga;
// MARK: - Mouse.
int Mouse::get_number_of_buttons() {
return 2;
}
void Mouse::set_button_pressed(int button, bool is_set) {
switch(button) {
case 0:
cia_state_ = (cia_state_ &~ 0x40) | (is_set ? 0 : 0x40);
break;
default:
break;
}
}
uint8_t Mouse::get_cia_button() const {
return cia_state_;
}
void Mouse::reset_all_buttons() {
cia_state_ = 0xff;
}
void Mouse::move(int x, int y) {
position_[0] += x;
position_[1] += y;
}
uint16_t Mouse::get_position() {
// The Amiga hardware retains only eight bits of position
// for the mouse; its software polls frequently and maps
// changes into a larger space.
//
// On modern computers with 5k+ displays and trackpads, it
// proved empirically possible to overflow the hardware
// counters more quickly than software would poll.
//
// Therefore the approach taken for mapping mouse motion
// into the Amiga is to do it in steps of no greater than
// [-128, +127], as per the below.
const int pending[] = {
position_[0], position_[1]
};
const int8_t change[] = {
int8_t(std::clamp(pending[0], -128, 127)),
int8_t(std::clamp(pending[1], -128, 127))
};
position_[0] -= change[0];
position_[1] -= change[1];
declared_position_[0] += change[0];
declared_position_[1] += change[1];
return uint16_t(
(declared_position_[1] << 8) |
declared_position_[0]
);
}
// MARK: - Joystick.
// TODO: add second fire button.
Joystick::Joystick() :
ConcreteJoystick({
Input(Input::Up),
Input(Input::Down),
Input(Input::Left),
Input(Input::Right),
Input(Input::Fire, 0),
}) {}
void Joystick::did_set_input(const Input &input, bool is_active) {
// Accumulate state.
inputs_[input.type] = is_active;
// Determine what that does to the two position bits.
const auto low =
(inputs_[Input::Type::Down] ^ inputs_[Input::Type::Right]) |
(inputs_[Input::Type::Right] << 1);
const auto high =
(inputs_[Input::Type::Up] ^ inputs_[Input::Type::Left]) |
(inputs_[Input::Type::Left] << 1);
// Ripple upwards if that affects the mouse position counters.
const uint8_t previous_low = position_ & 3;
uint8_t low_upper = (position_ >> 2) & 0x3f;
const uint8_t previous_high = (position_ >> 8) & 3;
uint8_t high_upper = (position_ >> 10) & 0x3f;
if(!low && previous_low == 3) ++low_upper;
if(!previous_low && low == 3) --low_upper;
if(!high && previous_high == 3) ++high_upper;
if(!previous_high && high == 3) --high_upper;
position_ = uint16_t(
low | ((low_upper & 0x3f) << 2) |
(high << 8) | ((high_upper & 0x3f) << 10)
);
}
uint16_t Joystick::get_position() {
return position_;
}
uint8_t Joystick::get_cia_button() const {
return inputs_[Input::Type::Fire] ? 0xbf : 0xff;
}

View File

@@ -0,0 +1,57 @@
//
// MouseJoystick.hpp
// Clock Signal
//
// Created by Thomas Harte on 26/11/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef MouseJoystick_hpp
#define MouseJoystick_hpp
#include <array>
#include <atomic>
#include "../../Inputs/Joystick.hpp"
#include "../../Inputs/Mouse.hpp"
namespace Amiga {
struct MouseJoystickInput {
virtual uint16_t get_position() = 0;
virtual uint8_t get_cia_button() const = 0;
};
class Mouse: public Inputs::Mouse, public MouseJoystickInput {
public:
uint16_t get_position() final;
uint8_t get_cia_button() const final;
private:
int get_number_of_buttons() final;
void set_button_pressed(int, bool) final;
void reset_all_buttons() final;
void move(int, int) final;
uint8_t declared_position_[2]{};
uint8_t cia_state_ = 0xff;
std::array<std::atomic<int>, 2> position_{};
};
class Joystick: public Inputs::ConcreteJoystick, public MouseJoystickInput {
public:
Joystick();
uint16_t get_position() final;
uint8_t get_cia_button() const final;
private:
void did_set_input(const Input &input, bool is_active) final;
bool inputs_[Joystick::Input::Type::Max]{};
uint16_t position_ = 0;
};
}
#endif /* MouseJoystick_hpp */

115
Machines/Amiga/Sprites.cpp Normal file
View File

@@ -0,0 +1,115 @@
//
// Sprites.cpp
// Clock Signal
//
// Created by Thomas Harte on 26/11/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "Sprites.hpp"
using namespace Amiga;
namespace {
/// Expands @c source from b15 ... b0 to 000b15 ... 000b0.
constexpr uint64_t expand_sprite_word(uint16_t source) {
uint64_t result = source;
result = (result | (result << 24)) & 0x0000'00ff'0000'00ff;
result = (result | (result << 12)) & 0x000f'000f'000f'000f;
result = (result | (result << 6)) & 0x0303'0303'0303'0303;
result = (result | (result << 3)) & 0x1111'1111'1111'1111;
return result;
}
// A very small selection of test cases.
static_assert(expand_sprite_word(0xffff) == 0x11'11'11'11'11'11'11'11);
static_assert(expand_sprite_word(0x5555) == 0x01'01'01'01'01'01'01'01);
static_assert(expand_sprite_word(0xaaaa) == 0x10'10'10'10'10'10'10'10);
static_assert(expand_sprite_word(0x0000) == 0x00'00'00'00'00'00'00'00);
}
// MARK: - Sprites.
void Sprite::set_start_position(uint16_t value) {
v_start_ = (v_start_ & 0xff00) | (value >> 8);
h_start = uint16_t((h_start & 0x0001) | ((value & 0xff) << 1));
}
void Sprite::set_stop_and_control(uint16_t value) {
h_start = uint16_t((h_start & 0x01fe) | (value & 0x01));
v_stop_ = uint16_t((value >> 8) | ((value & 0x02) << 7));
v_start_ = uint16_t((v_start_ & 0x00ff) | ((value & 0x04) << 6));
attached = value & 0x80;
// Disarm the sprite, but expect graphics next from DMA.
visible = false;
dma_state_ = DMAState::FetchImage;
}
void Sprite::set_image_data(int slot, uint16_t value) {
data[slot] = value;
visible |= slot == 0;
}
void Sprite::advance_line(int y, bool is_end_of_blank) {
if(dma_state_ == DMAState::FetchImage && y == v_start_) {
visible = true;
}
if(is_end_of_blank || y == v_stop_) {
dma_state_ = DMAState::FetchControl;
visible = true;
}
}
bool Sprite::advance_dma(int offset) {
if(!visible) return false;
// Fetch another word.
const uint16_t next_word = ram_[pointer_[0] & ram_mask_];
++pointer_[0];
// Put the fetched word somewhere appropriate and update the DMA state.
switch(dma_state_) {
// i.e. stopped.
default: return false;
case DMAState::FetchControl:
if(offset) {
set_stop_and_control(next_word);
} else {
set_start_position(next_word);
}
return true;
case DMAState::FetchImage:
set_image_data(1 - bool(offset), next_word);
return true;
}
return false;
}
template <int sprite> void TwoSpriteShifter::load(
uint16_t lsb,
uint16_t msb,
int delay) {
constexpr int sprite_shift = sprite << 1;
const int delay_shift = delay << 2;
// Clear out any current sprite pixels; this is a reload.
data_ &= 0xcccc'cccc'cccc'ccccull >> (sprite_shift + delay_shift);
// Map LSB and MSB up to 64-bits and load into the shifter.
const uint64_t new_data =
(
expand_sprite_word(lsb) |
(expand_sprite_word(msb) << 1)
) << sprite_shift;
data_ |= new_data >> delay_shift;
overflow_ |= uint8_t((new_data << 8) >> delay_shift);
}
template void TwoSpriteShifter::load<0>(uint16_t, uint16_t, int);
template void TwoSpriteShifter::load<1>(uint16_t, uint16_t, int);

View File

@@ -0,0 +1,76 @@
//
// Sprites.hpp
// Clock Signal
//
// Created by Thomas Harte on 26/11/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef Sprites_hpp
#define Sprites_hpp
#include <cstdint>
#include "DMADevice.hpp"
namespace Amiga {
class Sprite: public DMADevice<1> {
public:
using DMADevice::DMADevice;
void set_start_position(uint16_t value);
void set_stop_and_control(uint16_t value);
void set_image_data(int slot, uint16_t value);
void advance_line(int y, bool is_end_of_blank);
bool advance_dma(int offset);
uint16_t data[2]{};
bool attached = false;
bool visible = false;
uint16_t h_start = 0;
private:
uint16_t v_start_ = 0, v_stop_ = 0;
enum class DMAState {
FetchControl,
FetchImage
} dma_state_ = DMAState::FetchControl;
};
class TwoSpriteShifter {
public:
/// Installs new pixel data for @c sprite (either 0 or 1),
/// with @c delay being either 0 or 1 to indicate whether
/// output should begin now or in one pixel's time.
template <int sprite> void load(
uint16_t lsb,
uint16_t msb,
int delay);
/// Shifts two pixels.
void shift() {
data_ <<= 8;
data_ |= overflow_;
overflow_ = 0;
}
/// @returns The next two pixels to output, formulated as
/// abcd efgh where ab and ef are two pixels of the first sprite
/// and cd and gh are two pixels of the second. In each case the
/// more significant two are output first.
uint8_t get() {
return uint8_t(data_ >> 56);
}
private:
uint64_t data_;
uint8_t overflow_;
};
}
#endif /* Sprites_hpp */

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>
@@ -155,7 +158,7 @@ class AYDeferrer {
private:
Concurrency::DeferringAsyncTaskQueue audio_queue_;
GI::AY38910::AY38910<true> ay_;
Outputs::Speaker::LowpassSpeaker<GI::AY38910::AY38910<true>> speaker_;
Outputs::Speaker::PullLowpass<GI::AY38910::AY38910<true>> speaker_;
HalfCycles cycles_since_update_;
};
@@ -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.
*/
@@ -815,45 +787,43 @@ template <bool has_fdc> class ConcreteMachine:
ay_.ay().set_port_handler(&key_state_);
// construct the list of necessary ROMs
const std::string machine_name = "AmstradCPC";
std::vector<ROMMachine::ROM> required_roms = {
ROMMachine::ROM(machine_name, "the Amstrad Disk Operating System", "amsdos.rom", 16*1024, 0x1fe22ecd)
};
std::string model_number;
uint32_t crcs[2];
bool has_amsdos = false;
ROM::Name firmware, basic;
switch(target.model) {
default:
model_number = "6128";
has_128k_ = true;
crcs[0] = 0x0219bb74;
crcs[1] = 0xca6af63d;
break;
case Analyser::Static::AmstradCPC::Target::Model::CPC464:
model_number = "464";
has_128k_ = false;
crcs[0] = 0x815752df;
crcs[1] = 0x7d9a3bac;
firmware = ROM::Name::CPC464Firmware;
basic = ROM::Name::CPC464BASIC;
break;
case Analyser::Static::AmstradCPC::Target::Model::CPC664:
model_number = "664";
has_128k_ = false;
crcs[0] = 0x3f5a6dc4;
crcs[1] = 0x32fee492;
firmware = ROM::Name::CPC664Firmware;
basic = ROM::Name::CPC664BASIC;
has_amsdos = true;
break;
default:
firmware = ROM::Name::CPC6128Firmware;
basic = ROM::Name::CPC6128BASIC;
has_amsdos = true;
break;
}
required_roms.emplace_back(machine_name, "the CPC " + model_number + " firmware", "os" + model_number + ".rom", 16*1024, crcs[0]);
required_roms.emplace_back(machine_name, "the CPC " + model_number + " BASIC ROM", "basic" + model_number + ".rom", 16*1024, crcs[1]);
// fetch and verify the ROMs
const auto roms = rom_fetcher(required_roms);
for(std::size_t index = 0; index < roms.size(); ++index) {
auto &data = roms[index];
if(!data) throw ROMMachine::Error::MissingROMs;
roms_[index] = std::move(*data);
roms_[index].resize(16384);
ROM::Request request = ROM::Request(firmware) && ROM::Request(basic);
if(has_amsdos) {
request = request && ROM::Request(ROM::Name::AMSDOS);
}
// Fetch and verify the ROMs.
auto roms = rom_fetcher(request);
if(!request.validate(roms)) {
throw ROMMachine::Error::MissingROMs;
}
if(has_amsdos) {
roms_[ROMType::AMSDOS] = roms.find(ROM::Name::AMSDOS)->second;
}
roms_[ROMType::OS] = roms.find(firmware)->second;
roms_[ROMType::BASIC] = roms.find(basic)->second;
// Establish default memory map
upper_rom_is_paged_ = true;
upper_rom_ = ROMType::BASIC;
@@ -915,6 +885,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 +988,7 @@ template <bool has_fdc> class ConcreteMachine:
}
}
break;
case CPU::Z80::PartialMachineCycle::Input:
// Default to nothing answering
*cycle.value = 0xff;
@@ -1062,6 +1086,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,12 +1114,12 @@ 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);
}
HalfCycles get_typer_frequency() const final {
return Cycles(80'000); // Perform one key transition per frame.
return Cycles(160'000); // Perform one key transition per frame and a half.
}
// See header; sets a key as either pressed or released.
@@ -1114,18 +1139,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 +1217,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 +1232,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 +1260,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 */

View File

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

View File

@@ -40,7 +40,7 @@ struct KeyboardMapper: public MachineTypes::MappedKeyboardMachine::KeyboardMappe
struct CharacterMapper: public ::Utility::CharacterMapper {
const uint16_t *sequence_for_character(char character) const override;
bool needs_pause_after_reset_all_keys() const override { return false; }
bool needs_pause_after_reset_all_keys() const override { return true; }
bool needs_pause_after_key(uint16_t key) const override;
};

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