1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-09-11 10:28:07 +00:00

Compare commits

...

1851 Commits

Author SHA1 Message Date
Thomas Harte
1f7f9884f5 Experiment with a fullscreen toggle. 2022-07-06 08:21:51 -04:00
Thomas Harte
2bee9b3d56 This is a struct, not a class. 2022-07-06 08:21:26 -04:00
Thomas Harte
18735ee571 Merge pull request #1060 from TomHarte/QtErrors
Resolve invalid use of `constexpr` in IPF.cpp.
2022-07-05 17:09:05 -04:00
Thomas Harte
1ce07e2ee8 This reads the file, so it can't be constexpr. 2022-07-05 17:01:38 -04:00
Thomas Harte
7cbee172b2 Merge pull request #1041 from TomHarte/InST
Switch the Atari ST to the newer 68000.
2022-06-30 17:15:04 -04:00
Thomas Harte
fca974723f Merge pull request #1045 from TomHarte/InAmiga
Switch the Amiga to the newer 68000.
2022-06-30 17:14:54 -04:00
Thomas Harte
6a2d4ae11d Merge branch 'master' into InAmiga 2022-06-30 10:12:32 -04:00
Thomas Harte
6da634b79f Merge branch 'master' into InST 2022-06-30 10:12:23 -04:00
Thomas Harte
c85ca09236 Merge pull request #1058 from TomHarte/ContinuousLabels
Further compact list of potential switch targets.
2022-06-30 10:12:12 -04:00
Thomas Harte
a5b7ef5498 Further compact list of potential switch targets. 2022-06-30 08:31:51 -04:00
Thomas Harte
970087eefb Merge pull request #1057 from TomHarte/ContinuousLabels
68000: Eliminate large gap in `case` values.
2022-06-29 21:48:44 -04:00
Thomas Harte
11305c2e6b Eliminate large gap in case values. 2022-06-29 21:40:48 -04:00
Thomas Harte
5da16023d8 Merge pull request #1056 from TomHarte/Warnings
Switch to an alternative form of avoiding unused goto warnings.
2022-06-29 21:19:34 -04:00
Thomas Harte
b1d8a45339 Just disable the diagnostic. 2022-06-29 21:13:00 -04:00
Thomas Harte
c133f80c73 Try a compiler-specific attribute. 2022-06-29 19:20:44 -04:00
Thomas Harte
58b04cdfa4 Switch to an alternative form of avoiding unused goto warnings. 2022-06-29 19:08:41 -04:00
Thomas Harte
1e149d0add Merge pull request #1055 from TomHarte/IIgsMemoryMap
Introduce further IIgs memory map tests.
2022-06-29 15:19:31 -04:00
Thomas Harte
f7e75da4bd Disable [temporarily?] outdated shadowing tests. 2022-06-29 15:14:51 -04:00
Thomas Harte
c2938a4f63 Avoid potential classic macro error with address. 2022-06-29 15:09:52 -04:00
Thomas Harte
825136b168 Fix installation of LCW test value; thereby permit all tests. 2022-06-29 15:04:21 -04:00
Thomas Harte
5a9eb58d33 Fix test generator: IO state can be cleared. 2022-06-29 14:57:14 -04:00
Thomas Harte
beb4993548 Remove card pages from the equation. 2022-06-29 14:51:50 -04:00
Thomas Harte
48e8bfbb0e Introduce failing is-IO test. 2022-06-29 14:44:17 -04:00
Thomas Harte
5dfbc58959 Fix test generator's concept of hires2 shadowing. 2022-06-29 14:41:56 -04:00
Thomas Harte
924de35cf3 Go all in on support for physical shadowing. 2022-06-29 14:39:56 -04:00
Thomas Harte
7cf9e08948 Map shadowing by logical address, not physical.
Disclaimer: although this better matches the tests, I've yet to verify.
2022-06-29 06:10:15 -04:00
Thomas Harte
60d3519993 Clarify, attempt to implement as internally documented. 2022-06-28 22:32:31 -04:00
Thomas Harte
c6b4570424 Fix Markdown code marking. 2022-06-28 17:12:38 -04:00
Thomas Harte
f5d56cc473 Add first pass at testing shadowing. 2022-06-28 17:12:25 -04:00
Thomas Harte
4e52572b03 Omit language card write tests. 2022-06-28 16:57:09 -04:00
Thomas Harte
6abc317986 Avoid permitting writes in the Cx00 region after uninhibiting the language card. 2022-06-28 16:35:47 -04:00
Thomas Harte
22c0b588c4 Tidy up slightly, without fixing failure. 2022-06-28 16:32:35 -04:00
Thomas Harte
6c9fc0ac75 Introduce [failing] write area tests. 2022-06-28 16:28:00 -04:00
Thomas Harte
ef322dc705 Reformulate to allow addition of write tests, momentarily. 2022-06-28 16:22:41 -04:00
Thomas Harte
94fcc90886 Use auxiliary switches to control language card area when card is inhibited. 2022-06-28 12:46:31 -04:00
Thomas Harte
d0df156b05 Merge branch 'IIgsMemoryMap' of github.com:TomHarte/CLK into IIgsMemoryMap 2022-06-28 11:26:13 -04:00
Thomas Harte
7aeaa4a485 Tweak paging semantics, to allow simple multiple dependencies. 2022-06-27 21:38:45 -04:00
Thomas Harte
823c7765f8 Avoid manual index counting. 2022-06-27 11:16:05 -04:00
Thomas Harte
5cb0aebdf4 For the sake of poor Xcode, stop after a single failure. 2022-06-27 11:10:51 -04:00
Thomas Harte
ef40a81be2 Remove temporary hack. 2022-06-27 08:00:29 -04:00
Thomas Harte
21842052cf Alternative zero page should affect bank 0's language card area when the card is disabled. 2022-06-27 07:56:45 -04:00
Thomas Harte
686dccb48d Correct comparison. 2022-06-26 21:49:58 -04:00
Thomas Harte
1f7700edac Ensure proper register hits. 2022-06-26 21:20:57 -04:00
Thomas Harte
5adc656066 Make some attempt to use the JSON tests. 2022-06-25 21:41:37 -04:00
Thomas Harte
e0ec3c986d Ensure appropriate data bus size. 2022-06-25 21:07:29 -04:00
Thomas Harte
827b137c86 Merge pull request #1054 from TomHarte/JeekTest
Add an automatic bus size selector.
2022-06-25 16:47:15 -04:00
Thomas Harte
9cf64ea643 Import generated tests. 2022-06-25 16:46:57 -04:00
Thomas Harte
fc1952bf42 Add an automatic bus size selector.
This fixes the Jeek test.
2022-06-25 16:28:06 -04:00
Thomas Harte
9888f079fa Merge pull request #1053 from TomHarte/65816Tests
Add 65816 test generator; correct disagreements with other emulations.
2022-06-24 21:28:24 -04:00
Thomas Harte
f2c2027a8c Disable test generation for commit. 2022-06-24 16:50:23 -04:00
Thomas Harte
4467eb1c41 Ensure relevant throwaway stack reads use the previous stack address.
TODO: can CycleFetchPreviousThrowaway be used more widely?
2022-06-24 14:00:03 -04:00
Thomas Harte
ef5ac1442f Don't invent an address for STP and WAI. 2022-06-24 13:05:32 -04:00
Thomas Harte
1c1ce625a7 Vector reads signal VDA. 2022-06-24 10:37:39 -04:00
Thomas Harte
a442077eac Allow repetition for MVN and MVP only. 2022-06-24 10:34:43 -04:00
Thomas Harte
6c638712f3 Attempt to capture MVP and MVN in their entirety. 2022-06-24 07:39:58 -04:00
Thomas Harte
069a057a94 Resolve assumption of arithmetic shifts. 2022-06-24 07:26:07 -04:00
Thomas Harte
4ed3b21bf3 Decimal SBC tweak: negative partial results don't cause carry. 2022-06-23 21:58:09 -04:00
Thomas Harte
2e7afb13c7 Exit gracefully upon a STP or WAI. 2022-06-23 21:03:40 -04:00
Thomas Harte
a23b0f5122 Map STA (d), y to correct calculator. 2022-06-23 20:57:47 -04:00
Thomas Harte
da552abf75 Fix BIT overflow flag. 2022-06-23 15:24:51 -04:00
Thomas Harte
380b5141fb Be overt about conversion wanted here. 2022-06-23 13:03:26 -04:00
Thomas Harte
66775b2c4e Always consume a second cycle in 16-bit mode. 2022-06-23 12:46:51 -04:00
Thomas Harte
2c12a7d968 Make absolutely sure there's no address bit 24. 2022-06-23 12:12:02 -04:00
Thomas Harte
5a97c09238 Flip internal presumption on the BRK flag. 2022-06-23 11:23:00 -04:00
Thomas Harte
3112376943 Don't include DBR in direct indexed indirect. 2022-06-23 11:03:37 -04:00
Thomas Harte
65140b341d Simplify slightly, per new S reporting rule. 2022-06-22 16:43:00 -04:00
Thomas Harte
ecfd17a259 Report a 1 in the stack pointer high byte when in emulation mode.
It has one internally, it just wasn't previously exposed via this method.
2022-06-22 15:55:34 -04:00
Thomas Harte
a72dd96dc6 Page boundary crossing is free outside of emulation mode. 2022-06-22 15:31:30 -04:00
Thomas Harte
944e5ebbfa Take another run at IO addresses. 2022-06-22 15:28:11 -04:00
Thomas Harte
76767110b7 Fix overflow for 8-bit calculations; essentially a revert for ADC. 2022-06-22 15:18:47 -04:00
Thomas Harte
2f684ee66d Use null for values that were never loaded. 2022-06-21 21:47:18 -04:00
Thomas Harte
7dcfa9eb65 65816: improve decimal calculations, posted IO addresses, read/write during redundant read-modify-write cycle. 2022-06-21 14:33:06 -04:00
Thomas Harte
ec98736bd7 Ensure IO cycles don't produce an address of (PC+1). 2022-06-21 11:41:05 -04:00
Thomas Harte
ab0c290489 Use 'x' instead of 'i'. 2022-06-19 06:58:23 -04:00
Thomas Harte
15ac2c3e5a Output to files, at volume, with extended bus flags. 2022-06-18 22:00:50 -04:00
Thomas Harte
0c24a27ba6 Completely prints tests. 2022-06-18 21:32:50 -04:00
Thomas Harte
eb82e06fab Add randomised initial state, fix PC. 2022-06-18 19:21:56 -04:00
Thomas Harte
f8e6954739 Ensure complete runs of each tested opcode. 2022-06-18 16:26:40 -04:00
Thomas Harte
586ef4810b Add restart_operation_fetch, to aid with testing. 2022-06-18 16:25:57 -04:00
Thomas Harte
b62f484d93 Start scaffolding a 65816 test generator. 2022-06-18 13:28:15 -04:00
Thomas Harte
07b600ccaf Merge branch 'master' into InST 2022-06-17 12:10:47 -04:00
Thomas Harte
907dc75e5b Merge branch 'master' into InAmiga 2022-06-17 12:10:40 -04:00
Thomas Harte
ea0d2971eb Merge pull request #1052 from TomHarte/StraightforwardMicrocycle
Clean up Microcycle helpers.
2022-06-17 12:10:22 -04:00
Thomas Harte
a0bc332fe6 Taking a second parse, prefer non-lookup-table solutions. 2022-06-17 11:55:38 -04:00
Thomas Harte
b0ab5b7b62 Simplify Microcycle helpers. 2022-06-16 21:34:24 -04:00
Thomas Harte
a3fc8dbf42 Merge branch 'master' into InAmiga 2022-06-16 16:37:49 -04:00
Thomas Harte
37516e6f6b Merge pull request #1051 from TomHarte/STOPReturn
Fix return address following a STOP.
2022-06-16 15:15:50 -04:00
Thomas Harte
9fde7c0f89 Merge branch 'STOPReturn' into InST 2022-06-16 15:11:06 -04:00
Thomas Harte
dc8103ea82 Fix return address following a STOP. 2022-06-16 15:10:35 -04:00
Thomas Harte
e248092014 Fix return address following a STOP. 2022-06-16 15:10:19 -04:00
Thomas Harte
f343635cab Merge branch 'master' into InAmiga 2022-06-16 13:37:37 -04:00
Thomas Harte
60daf9678f Merge pull request #1050 from TomHarte/ByteLengthWarning
Resolve release-build byte length warning
2022-06-16 11:16:37 -04:00
Thomas Harte
5d5bd6791b Merge branch 'master' into InMacintosh 2022-06-16 11:01:18 -04:00
Thomas Harte
fe748507f0 Merge branch 'master' into InAmiga 2022-06-15 21:23:30 -04:00
Thomas Harte
cb7230e7b7 Merge branch 'master' into InST 2022-06-15 21:23:18 -04:00
Thomas Harte
cb162b6755 Merge pull request #1049 from TomHarte/68000Mk2Bus
Correct 68000 mark 2 Microcycle helper methods.
2022-06-15 21:22:49 -04:00
Thomas Harte
7d00b50e13 Fix upper/lower_data_select; simplify value8_low. 2022-06-15 21:11:31 -04:00
Thomas Harte
12b058867e Correct very minor typo. 2022-06-15 19:34:54 -04:00
Thomas Harte
8ff09a1923 Fix value8_high. 2022-06-15 19:34:49 -04:00
Thomas Harte
62fa0991ed Disallow copying, add some basic asserts. 2022-06-15 19:34:43 -04:00
Thomas Harte
52a8566e7f Correct very minor typo. 2022-06-15 17:06:56 -04:00
Thomas Harte
3bfcf252a8 Fix value8_high. 2022-06-15 17:06:40 -04:00
Thomas Harte
f4ae58b1e5 Disallow copying, add some basic asserts. 2022-06-15 12:59:03 -04:00
Thomas Harte
12277d9305 Merge branch 'master' into InST 2022-06-15 11:07:43 -04:00
Thomas Harte
c9d3f4492d Merge branch 'master' into InAmiga 2022-06-15 11:07:31 -04:00
Thomas Harte
f23c5cc6df Merge pull request #1048 from TomHarte/STOPMk2
68000mk2: apply STOP status.
2022-06-15 11:07:14 -04:00
Thomas Harte
24823233ff Add spurious interrupt support. 2022-06-15 11:00:27 -04:00
Thomas Harte
bd056973ba Don't allow STOP state to block execution. 2022-06-15 10:56:45 -04:00
Thomas Harte
5420fd5aa3 Fix: new status word is still in prefetch. 2022-06-15 10:54:34 -04:00
Thomas Harte
44cdb4726e Fix: new status word is still in prefetch. 2022-06-15 10:54:14 -04:00
Thomas Harte
698cc182ae Merge branch 'STOPMk2' into InAmiga 2022-06-15 10:50:19 -04:00
Thomas Harte
93615f6647 Apply new status before entering STOP loop. 2022-06-15 10:50:03 -04:00
Thomas Harte
fba709f46d Merge branch 'InST' of github.com:TomHarte/CLK into InST 2022-06-15 08:12:45 -04:00
Thomas Harte
bb34aaa0c7 Merge branch 'master' into InST 2022-06-15 08:12:35 -04:00
Thomas Harte
5661c3317a Merge branch 'master' into InAmiga 2022-06-15 08:12:19 -04:00
Thomas Harte
733ffc0eee Merge pull request #1047 from TomHarte/OldVsNew
Introduce randomised old vs new 68000 tests.
2022-06-15 08:11:54 -04:00
Thomas Harte
6cc41d6dda Restore 1000 test count. 2022-06-14 22:02:53 -04:00
Thomas Harte
d91f8a264e Flip presumption, reenabling most tests. 2022-06-14 21:57:14 -04:00
Thomas Harte
0ace9634ce Fix MOVEA. 2022-06-14 21:56:48 -04:00
Thomas Harte
48d51759cd At huge copy-and-paste cost, fix MOVE.l. 2022-06-14 21:22:28 -04:00
Thomas Harte
bfd0b683bf Extend MOVE.b fix to cover MOVE.w. 2022-06-14 17:04:11 -04:00
Thomas Harte
61e0f60e94 Add specialised MOVE.b to correct bus sequencing.
This is a bit of a trial balloon; .w and .l to come.
2022-06-13 21:49:00 -04:00
Thomas Harte
7fa715e37a Provide more thorough documentation. 2022-06-13 15:27:23 -04:00
Thomas Harte
e066546c13 Resolve PEA timing errors. 2022-06-13 14:08:42 -04:00
Thomas Harte
7dc66128c2 Fix strobe output. 2022-06-13 10:49:47 -04:00
Thomas Harte
e484e4c9d7 Expand test to make sure that correct data strobes are active. 2022-06-13 10:39:06 -04:00
Thomas Harte
4a75691005 Avoid double conditional for CalcEffectiveAddressIdleFor8bitDisplacementAndPreDec. 2022-06-13 10:27:22 -04:00
Thomas Harte
8ada73b283 Use the outer switch for addressing mode dispatch, saving a lot of syntax. 2022-06-13 08:57:49 -04:00
Thomas Harte
f316cbcf94 The old implementation was correct. 2022-06-11 21:15:08 -04:00
Thomas Harte
2a9a05785c Bus and address error don't affect interrupt level. 2022-06-11 21:10:24 -04:00
Thomas Harte
0a6b2b7d32 Verify newer CMPA.l, RTE, TRAP[V] and CHK. 2022-06-11 11:17:18 -04:00
Thomas Harte
c3345dd839 Fix MOVEM timing. 2022-06-10 21:52:07 -04:00
Thomas Harte
917b7fbf80 Notarise won't fix status of CLR, NEGX, NEG, NOT. 2022-06-10 16:50:38 -04:00
Thomas Harte
97715e7ccc Expand test set to include those with timing discrepancies. 2022-06-10 16:34:05 -04:00
Thomas Harte
43c0dea1bd With the difference in RESET times now factored out, test timing too. 2022-06-10 16:12:54 -04:00
Thomas Harte
2e4652209b Remove entire RESET sequence, move to testing PEA. 2022-06-10 15:57:54 -04:00
Thomas Harte
aec4bf9d45 Correct TAS timing. 2022-06-10 15:57:35 -04:00
Thomas Harte
e2d811a7a0 Notarise digressions that appear to be correct, remove now-working RTE/RTR. 2022-06-09 21:48:15 -04:00
Thomas Harte
f8643a62e6 Change RTE and RTR read order. 2022-06-09 21:47:28 -04:00
Thomas Harte
dd5c903fd6 DIVS also appears sometimes to differ. 2022-06-09 20:19:39 -04:00
Thomas Harte
2e1675066d Reinstate address error non-testing. 2022-06-09 16:59:06 -04:00
Thomas Harte
be84ce657b Add an optional testing whitelist. 2022-06-09 16:18:04 -04:00
Thomas Harte
64053d697f Take improved guess at address error stacking order. 2022-06-09 16:17:09 -04:00
Thomas Harte
a59ad06438 Print out summary of failure. 2022-06-09 13:13:33 -04:00
Thomas Harte
5af03d74ec Add note to self about first diagnosis. 2022-06-09 12:21:39 -04:00
Thomas Harte
ba2803c807 Include all bus activity after the split. 2022-06-09 11:30:22 -04:00
Thomas Harte
fdcbf617d8 Avoid STOP. 2022-06-09 08:42:31 -04:00
Thomas Harte
cc7a4f7f91 Fix test build. 2022-06-08 21:15:11 -04:00
Thomas Harte
2e42bda0a3 Permit instructions that end in an address error to differ in transactions. 2022-06-08 16:15:33 -04:00
Thomas Harte
da8e6737c6 Fix standard exception stack write order. 2022-06-08 16:15:11 -04:00
Thomas Harte
670201fcc2 Reset time debt upon 'reset'. 2022-06-08 16:03:16 -04:00
Thomas Harte
168dc12e27 Avoid spurious mismatches. 2022-06-08 16:03:02 -04:00
Thomas Harte
fd1955e15b Attempt to randomise and test register contents. 2022-06-08 15:12:47 -04:00
Thomas Harte
ab35016aae Clear any time debt upon phoney reset. 2022-06-08 15:12:32 -04:00
Thomas Harte
f4f93f4836 Test a single, whole instruction; record read/write. 2022-06-08 14:53:04 -04:00
Thomas Harte
6efb9b24e0 Ensure that a phoney reset gets the proper vector. 2022-06-08 14:44:15 -04:00
Thomas Harte
dd0a7533ab Randomise all parts of memory other than the opcode. 2022-06-08 14:43:51 -04:00
Thomas Harte
079c3fd263 Abort address error-causing exceptions before they begin. 2022-06-08 14:43:31 -04:00
Thomas Harte
8cbf929671 Don't duplicate work that the RESET program already does. 2022-06-08 11:42:56 -04:00
Thomas Harte
50130b7004 Minor layout tweak. 2022-06-08 11:42:42 -04:00
Thomas Harte
ab52c5cef2 Pass first all-zeroes test, establishing that processors aren't being fully reset. 2022-06-08 10:56:54 -04:00
Thomas Harte
c7fa93a5bc Attempt human-legible explanation of differences encountered. 2022-06-08 10:51:05 -04:00
Thomas Harte
400b73b5a2 Allow capture to be limited; retain timestamps. 2022-06-08 09:49:27 -04:00
Thomas Harte
788b026cf5 Log and attempt to compare some activity. Sort of. 2022-06-07 16:56:05 -04:00
Thomas Harte
9009645cea Add 'reset' functions. 2022-06-07 16:55:39 -04:00
Thomas Harte
c4ae5d4c8d Establishes at least that both 68000s can run. 2022-06-06 21:47:10 -04:00
Thomas Harte
ca8dd61045 Start sketching out an old vs new 68000 test. 2022-06-06 21:19:57 -04:00
Thomas Harte
ac037bcffd Merge branch 'master' into InAmiga 2022-06-06 16:17:40 -04:00
Thomas Harte
d429dec2e5 Merge branch 'master' into InST 2022-06-06 16:17:33 -04:00
Thomas Harte
d779bc3784 Merge pull request #1046 from TomHarte/StatusChanges
Ensure RTE triggers a stack pointer change if needed.
2022-06-06 16:16:52 -04:00
Thomas Harte
a4baa33e2f Ensure RTE triggers a stack pointer change if needed. 2022-06-06 16:08:50 -04:00
Thomas Harte
56aa182fb6 Fix debug builds. 2022-06-06 15:26:15 -04:00
Thomas Harte
9818c7e78c Switch the Amiga to the newer 68000. 2022-06-06 11:10:56 -04:00
Thomas Harte
6aa599a17c Future-proof perform_bus_operation. 2022-06-06 08:20:16 -04:00
Thomas Harte
57858b2fa5 Merge branch 'master' into InST 2022-06-05 20:59:48 -04:00
Thomas Harte
d4c1e92b1c Merge pull request #1044 from TomHarte/MacintoshAudio
Add missing `flush`.
2022-06-05 09:20:08 -04:00
Thomas Harte
403eda7024 Add missing flush. 2022-06-05 09:08:36 -04:00
Thomas Harte
1671827d24 Add flush. 2022-06-05 09:07:43 -04:00
Thomas Harte
578c3e21a5 Merge branch 'master' into InST 2022-06-04 21:32:10 -04:00
Thomas Harte
87ef0d9ab3 Merge pull request #1042 from TomHarte/68000Interrupt
Fix interrupt acknowledge cycle: signals and data size.
2022-06-04 21:31:03 -04:00
Thomas Harte
cfafbfd141 Fix interrupt acknowledge cycle: signals and data size. 2022-06-04 21:23:57 -04:00
Thomas Harte
9289a6c1bb Fix interrupt acknowledge cycle: signals and data size. 2022-06-04 15:20:38 -04:00
Thomas Harte
4a740fbd14 Switch Atari ST to using the new 68000. 2022-06-04 08:43:43 -04:00
Thomas Harte
7eb00c131f Merge pull request #1036 from TomHarte/InMacintosh
Switch the Macintosh to the newer 68000.
2022-06-03 20:22:27 -04:00
Thomas Harte
5ae461eb0b Avoid warning during optimised builds. 2022-06-03 15:43:27 -04:00
Thomas Harte
542126194a Capture interrupt input at the end of an access cycle, not the beginning.
All still a guess.
2022-06-03 15:39:53 -04:00
Thomas Harte
a61f7e38b6 Very minor: avoid division and modulus when unnecessary. 2022-06-03 15:39:29 -04:00
Thomas Harte
3d059cb751 Make use of Microcycle helpers where relevant.
None of these existed when the Macintosh was first added to this emulator.
2022-06-03 15:33:31 -04:00
Thomas Harte
74e96b881c Merge branch 'master' into InMacintosh 2022-06-03 11:20:46 -04:00
Thomas Harte
9848fa9a4d Merge pull request #1040 from TomHarte/68000RESET
Fix decoding of 68000 RESET.
2022-06-03 11:20:27 -04:00
Thomas Harte
c24a7a8b58 Merge branch '68000RESET' into InMacintosh 2022-06-03 11:17:06 -04:00
Thomas Harte
71e38a6781 Fix decoding of RESET. 2022-06-03 11:15:50 -04:00
Thomas Harte
7b3cf6e747 Add missing instruction: RESET. 2022-06-03 11:15:39 -04:00
Thomas Harte
676e4a6112 Merge branch 'master' into InMacintosh 2022-06-03 10:31:07 -04:00
Thomas Harte
fd66a9b396 Merge pull request #1033 from TomHarte/68000Mk2
Implement a bus binding for the discrete 68000 decoder and performer.
2022-06-03 10:30:44 -04:00
Thomas Harte
640b04e59e Test only well-defined flags.
Albeit that timing is still off.
2022-06-03 10:18:46 -04:00
Thomas Harte
1625796cfe Test only well-defined flags.
Albeit that timing is still off.
2022-06-03 10:17:55 -04:00
Thomas Harte
93749cd650 Merge branch '68000Mk2' into InMacintosh 2022-06-03 08:38:58 -04:00
Thomas Harte
02b6ea6c46 Factor out would-accept-interrupt test, per uncertainty re: level 7. 2022-06-03 08:31:56 -04:00
Thomas Harte
6fcaf3571e Fix bus/address error exception frame: order and contents. 2022-06-03 08:27:49 -04:00
Thomas Harte
10b9b13673 Disable divide-by-zero PC test in lieu of better documentation. 2022-06-03 08:27:20 -04:00
Thomas Harte
6cb559f65e Merge branch '68000Mk2' of github.com:TomHarte/CLK into 68000Mk2 2022-06-02 21:43:28 -04:00
Thomas Harte
c3b436fe96 Use int64_t as an intermediary to avoid x86 exception on INT_MIN/-1. 2022-06-02 21:39:52 -04:00
Thomas Harte
aaac777651 Merge branch 'master' into 68000Mk2 2022-06-02 17:08:41 -04:00
Thomas Harte
103de74063 Merge pull request #1039 from TomHarte/UniqueAsync
Switch DeferringAsyncTaskQueue to `unique_ptr`.
2022-06-02 17:06:59 -04:00
Thomas Harte
7f33a5ca0c Simplify: (i) repetitive type for TaskList; (ii) unnecessary unique_ptr. 2022-06-02 17:02:36 -04:00
Thomas Harte
e389dcb912 Further simplify syntax. 2022-06-02 16:52:03 -04:00
Thomas Harte
9d278d80f1 Remove redundant reset. 2022-06-02 16:50:59 -04:00
Thomas Harte
e994910ff6 Switch to unique_ptr. 2022-06-02 16:46:41 -04:00
Thomas Harte
e7b3705060 Merge pull request #1007 from TomHarte/IPFFileFormat
Adds partial support for the IPF file format.
2022-06-02 12:58:47 -04:00
Thomas Harte
f17502fe81 Merge branch 'master' into 68000Mk2 2022-06-02 12:57:34 -04:00
Thomas Harte
8e7df5c1b1 Merge branch 'master' into InMacintosh 2022-06-02 12:57:24 -04:00
Thomas Harte
8ba1b4e0cf Merge pull request #1037 from TomHarte/SaferShutdown
Reduce potential surprise in DeferringAsyncTaskQueue::flush.
2022-06-02 12:56:51 -04:00
Thomas Harte
93679f8d48 Reduce potential surprise in DeferringAsyncTaskQueue::flush. 2022-06-02 12:50:45 -04:00
Thomas Harte
a292483344 Merge branch '68000Mk2' into InMacintosh 2022-06-02 12:30:54 -04:00
Thomas Harte
90d720ca28 Don't test undocumented flags. 2022-06-02 12:30:39 -04:00
Thomas Harte
f8e933438e Add missing tail cost. 2022-06-02 12:26:25 -04:00
Thomas Harte
6dd89eb0d7 Adjust my expectation as to length. 2022-06-02 12:11:54 -04:00
Thomas Harte
2bd20446bb Merge branch '68000Mk2' of github.com:TomHarte/CLK into 68000Mk2 2022-06-02 05:39:32 -04:00
Thomas Harte
659e4f6987 Include fixed cost of rolls. Which includes providing slightly more information to did_shift. 2022-06-01 20:30:51 -04:00
Thomas Harte
cd5f3c90c2 Ensure proper resumption after a forced exit in will_perform. 2022-06-01 15:27:09 -04:00
Thomas Harte
91a6911a51 Correct ADDA/SUBA timing. 2022-06-01 15:03:03 -04:00
Thomas Harte
0857dd0ae5 Include fixed base cost in MULU and MULS. 2022-06-01 14:05:23 -04:00
Thomas Harte
8c242fa2dd Merge branch '68000Mk2' into InMacintosh 2022-06-01 10:48:38 -04:00
Thomas Harte
5a4f117a12 Merge branch '68000Mk2' of github.com:TomHarte/CLK into 68000Mk2 2022-06-01 10:48:14 -04:00
Thomas Harte
62ed1ca2fd Fix MOVE CCR permissions. 2022-06-01 09:22:47 -04:00
Thomas Harte
d1298c8863 Correct MOVE timing without breaking PEA, LEA, etc. 2022-06-01 09:06:08 -04:00
Thomas Harte
75e85b80aa Factor out the common stuff of exception state. 2022-06-01 08:20:33 -04:00
Thomas Harte
73815ba1dd No need for this hoop jumping here. 2022-06-01 08:20:06 -04:00
Thomas Harte
e1abf431cb Don't test undefined flags. 2022-05-30 16:23:51 -04:00
Thomas Harte
8e0fa3bb5f DIV # with a divide by zero should be 44 cycles. 2022-05-29 21:22:45 -04:00
Thomas Harte
8ffaf1a8e4 Ensure did_divu/s are performed even upon divide by zero. 2022-05-29 21:18:19 -04:00
Thomas Harte
7788a109b0 Tweak more overtly to avoid divide by zero. 2022-05-29 20:51:50 -04:00
Thomas Harte
9eea471e72 Resolve infinite recursion. 2022-05-29 20:39:22 -04:00
Thomas Harte
3ef53315a2 Don't try to append operands to 'None'. 2022-05-29 15:28:16 -04:00
Thomas Harte
2a40e419fc Fix CHK tests: timing and expected flags. 2022-05-29 15:26:56 -04:00
Thomas Harte
d6f72d9862 Avoid runtime checking of instruction supervisor requirements. 2022-05-29 14:56:44 -04:00
Thomas Harte
3da720c789 Make requires_supervisor explicitly compile-time usable. 2022-05-29 14:55:24 -04:00
Thomas Harte
dbf7909b85 Fix timing of CMPM. 2022-05-29 14:49:42 -04:00
Thomas Harte
57aa8d2f17 Correct timing of ADDQ. 2022-05-29 14:34:06 -04:00
Thomas Harte
a318a49c72 Merge branch '68000Mk2' into InMacintosh 2022-05-28 15:01:58 -04:00
Thomas Harte
35e73b77f4 Fix interrupt stack frame. 2022-05-27 21:55:17 -04:00
Thomas Harte
698d1a7111 Fix interrupt stack frame. 2022-05-27 21:54:23 -04:00
Thomas Harte
1365fca161 Avoid phoney write modifies. 2022-05-27 21:42:55 -04:00
Thomas Harte
d17d77714f Remove outdated TODO. 2022-05-27 15:40:06 -04:00
Thomas Harte
e8dd8215ba Tweak per empirical results. 2022-05-27 15:39:02 -04:00
Thomas Harte
e11990e453 Make an attempt at DIVS timing. 2022-05-27 15:38:54 -04:00
Thomas Harte
165ebe8ae3 Add time calculation for MULU and MULS. 2022-05-27 15:38:14 -04:00
Thomas Harte
e746637bee Fill in dynamic cost of shifts. 2022-05-27 15:38:08 -04:00
Thomas Harte
0e6370d467 Tweak per empirical results. 2022-05-27 15:37:40 -04:00
Thomas Harte
512cd333e5 Make an attempt at DIVS timing. 2022-05-27 14:56:04 -04:00
Thomas Harte
f599a78cad Add time calculation for MULU and MULS. 2022-05-27 14:41:42 -04:00
Thomas Harte
7601dab464 Fill in timing calculation for DIVU. 2022-05-27 14:30:03 -04:00
Thomas Harte
a8623eab4a Fill in dynamic cost of shifts. 2022-05-27 11:12:10 -04:00
Thomas Harte
c367ddff1b Merge branch '68000Mk2' into InMacintosh 2022-05-27 10:34:11 -04:00
Thomas Harte
67b340fa5e Fix interrupt request address. 2022-05-27 10:33:36 -04:00
Thomas Harte
c97245e626 Fix CalcEA timing; make MOVEfromSR a read-modify-write. 2022-05-27 10:32:28 -04:00
Thomas Harte
79e2c17f93 Fix interrupt request address. 2022-05-26 20:20:28 -04:00
Thomas Harte
5937737bb7 Merge branch '68000Mk2' into InMacintosh 2022-05-26 19:37:44 -04:00
Thomas Harte
5f030edea4 Simplify transaction. 2022-05-26 19:37:30 -04:00
Thomas Harte
88e33353a1 Fix instruction and time counting, and initial state. 2022-05-26 09:17:37 -04:00
Thomas Harte
f3c0c62c79 Switch register-setting interface. 2022-05-26 07:52:14 -04:00
Thomas Harte
866787c5d3 Make an effort to withdraw from the high-circuitous stuff of working around the reset sequence. 2022-05-25 20:22:38 -04:00
Thomas Harte
367ad8079a Add a call to set register state with population of the prefetch. 2022-05-25 20:22:05 -04:00
Thomas Harte
64491525b4 Work further to guess at caller's intention for set_state.
Probably I should just eliminate the initial reset, somehow.
2022-05-25 17:01:18 -04:00
Thomas Harte
68b184885f Reapply only the status. 2022-05-25 16:54:25 -04:00
Thomas Harte
06f3c716f5 Make better effort to establish initial state. 2022-05-25 16:47:41 -04:00
Thomas Harte
22714b8c7f Capture state at instruction end, for potential inspection. 2022-05-25 16:32:26 -04:00
Thomas Harte
80c1bedffb Eliminate false prefetch for BSR. 2022-05-25 16:32:02 -04:00
Thomas Harte
56ad6d24ee Fix ANDI/ORI/EORI to CCR/SR timing. 2022-05-25 16:20:26 -04:00
Thomas Harte
4ad0e04c23 Fix macro for n being an expression. 2022-05-25 16:05:45 -04:00
Thomas Harte
f9d1c554b7 Fix for the actual number of cycles in a standard reset. 2022-05-25 16:05:28 -04:00
Thomas Harte
ee58301a46 Add RaiseException macro. 2022-05-25 15:45:09 -04:00
Thomas Harte
f2a7660390 Merge branch 'master' into 68000Mk2 2022-05-25 15:40:10 -04:00
Thomas Harte
d4c7ce2d6f Merge pull request #1035 from TomHarte/68000TestIssues
Add details on gaps in coverage.
2022-05-25 15:39:42 -04:00
Thomas Harte
4961e39fb6 Mention DIVU/DIVS flags. 2022-05-25 15:39:00 -04:00
Thomas Harte
0bedf608c0 Add details on gaps in coverage. 2022-05-25 15:36:27 -04:00
Thomas Harte
1ab831f571 Add the option to log a list of all untested instructions. 2022-05-25 13:17:01 -04:00
Thomas Harte
b90f1a48ce Merge branch '68000Mk2' into InMacintosh 2022-05-25 13:02:44 -04:00
Thomas Harte
72425fc2e1 Fix bus data size of MOVE.b xx, -(An). 2022-05-25 13:00:36 -04:00
Thomas Harte
a5f2dfbc0c Initialise registers to 0 for better testability.
TODO: is this the real initial state?
2022-05-25 11:47:42 -04:00
Thomas Harte
5db6a937cb Have TRAP and TRAPV push the next instruction address to the stack. 2022-05-25 11:47:21 -04:00
Thomas Harte
9709b9b1b1 Standard exceptions don't raise the interrupt level. 2022-05-25 11:37:39 -04:00
Thomas Harte
2c6b9b4c9d Switch comparative trace tests to 68000 Mk2. 2022-05-25 11:32:00 -04:00
Thomas Harte
463fbb07f9 Adapt remaining 68000 tests to use Mk2. 2022-05-25 10:55:17 -04:00
Thomas Harte
b6e473a515 Adapt remaining 68000 tests to use Mk2. 2022-05-25 10:55:03 -04:00
Thomas Harte
24f7b5806c Merge branch '68000Mk2' into InMacintosh 2022-05-25 08:15:41 -04:00
Thomas Harte
5872e0ea4a Resolve MOVE.l xx, -(An) write target. 2022-05-25 08:15:18 -04:00
Thomas Harte
04d2d6012a Merge branch '68000Mk2' into InMacintosh 2022-05-24 16:08:56 -04:00
Thomas Harte
f43d27541b Avoid attempt to establish operand flags for undefined opcodes. 2022-05-24 15:53:12 -04:00
Thomas Harte
c8d3d980ba Avoid attempt to establish operand flags for undefined opcodes. 2022-05-24 15:52:53 -04:00
Thomas Harte
f93bf06b99 Merge branch '68000Mk2' into InMacintosh 2022-05-24 15:51:22 -04:00
Thomas Harte
0f7cb2fa5a Attempt to honour the trace flag. 2022-05-24 15:47:47 -04:00
Thomas Harte
01e93ba916 Make an attempt at bus/address error. 2022-05-24 15:42:50 -04:00
Thomas Harte
780954f27b Add TRAP, TRAPV. 2022-05-24 15:14:46 -04:00
Thomas Harte
19d69bdbb5 Add TRAP, TRAPV. 2022-05-24 15:14:20 -04:00
Thomas Harte
27fac7af86 Merge branch '68000Mk2' into InMacintosh 2022-05-24 12:48:54 -04:00
Thomas Harte
6f048de973 Pull unrecognised instruction handling into the usual switch table. 2022-05-24 12:42:34 -04:00
Thomas Harte
a611a745e7 Switch the Macintosh to 68000 mk2. 2022-05-24 12:35:36 -04:00
Thomas Harte
0dfaa7d9cf Interrupt fixes: supply proper address, raise level, fetch from vector. 2022-05-24 12:16:06 -04:00
Thomas Harte
eab720f6ea Ensure proper transition from unrecognised instructions. 2022-05-24 12:16:00 -04:00
Thomas Harte
8ad1d6b813 Interrupt fixes: supply proper address, raise level, fetch from vector. 2022-05-24 12:15:35 -04:00
Thomas Harte
be684d66fd Ensure proper transition from unrecognised instructions. 2022-05-24 11:36:11 -04:00
Thomas Harte
a7e8aef9d3 Add MOVEA, be slightly more careful about next_operand_. 2022-05-24 11:30:09 -04:00
Thomas Harte
4b07c41df9 Ensure alignment of storage. 2022-05-24 11:29:28 -04:00
Thomas Harte
df54f1f1b7 Update TODO. 2022-05-24 11:06:05 -04:00
Thomas Harte
9e3c2b68d7 Eliminate potential future implicit conversion warnings. 2022-05-24 11:05:24 -04:00
Thomas Harte
3349bcaaed Attempt interrupt support. 2022-05-24 10:53:59 -04:00
Thomas Harte
3a4fb81242 Add a dummy STOP state. 2022-05-24 10:25:40 -04:00
Thomas Harte
1df3ad0671 Ensure TAS responds to VPA, BERR. 2022-05-24 09:17:58 -04:00
Thomas Harte
523cdd859b Add bus and address error, and VPA checks. 2022-05-24 09:08:31 -04:00
Thomas Harte
b037c76da6 Add public interface for everything except HALT and BUS REQ/etc.
... neither of which are used by machines I currently implement.
2022-05-23 20:55:01 -04:00
Thomas Harte
9cac4ca317 Add MOVE to/from USP. 2022-05-23 20:42:41 -04:00
Thomas Harte
34e5f39571 Ensure that running exactly up to a boundary gives the bus handler the next microcycle to contemplate. 2022-05-23 15:11:33 -04:00
Thomas Harte
e0a279344c Codify the existence of special cases, implement NOP and RESET. 2022-05-23 15:09:46 -04:00
Thomas Harte
e2f4db3e45 Shuffle more of the flow controller methods into their proper place. 2022-05-23 12:06:14 -04:00
Thomas Harte
cdb9eae1ee Merge branch 'master' into 68000Mk2 2022-05-23 11:02:57 -04:00
Thomas Harte
c1837af84a Add notes to self on work remaining. 2022-05-23 11:02:31 -04:00
Thomas Harte
a87f6a28c9 Fix LINK A7. 2022-05-23 10:43:17 -04:00
Thomas Harte
98325325b1 Fix UNLINK A7. 2022-05-23 10:27:44 -04:00
Thomas Harte
26bf66e3f8 Fix shifts and rolls. 2022-05-23 10:09:46 -04:00
Thomas Harte
363cd97154 Resolve double definition of did_shift. 2022-05-23 10:07:24 -04:00
Thomas Harte
5eb19da91f Merge pull request #1034 from fedex81/patch-1
Update nbcd_pea.json
2022-05-23 10:06:01 -04:00
Thomas Harte
c6b3281274 Attempt the shifts and rolls. 2022-05-23 09:29:19 -04:00
Thomas Harte
1e8adc2bd9 Fix MOVEP to R. 2022-05-23 09:00:37 -04:00
Thomas Harte
c73021cf3c Implement MOVE. 2022-05-23 08:46:06 -04:00
Thomas Harte
1b3acf9cd8 Eliminate assumption. 2022-05-23 08:18:37 -04:00
Federico Berti
1a26d4e409 Update nbcd_pea.json
Add missing bracket
2022-05-23 12:14:00 +01:00
Thomas Harte
c8ede400eb Fix RTE. 2022-05-22 21:17:28 -04:00
Thomas Harte
269263eecf Implement RTE, RTS, RTR. 2022-05-22 21:16:38 -04:00
Thomas Harte
4e21cdfc63 Enable NEGX/CLR tests. 2022-05-22 20:55:21 -04:00
Thomas Harte
faef5633f8 Ensure MOVE from SR has an effective address to write to. 2022-05-22 20:52:00 -04:00
Thomas Harte
7d1f1a3175 Implement MOVE [to/from] [CCR/SR]. 2022-05-22 19:45:22 -04:00
Thomas Harte
4e34727195 Fully implement TAS. 2022-05-22 16:14:03 -04:00
Thomas Harte
1dd6ed6ae3 Implement TAS Dn, with detour for other TASes. 2022-05-22 16:08:30 -04:00
Thomas Harte
cb4d6710df Switch to a more direct indication of progress. 2022-05-22 11:27:58 -04:00
Thomas Harte
3b68b9a83b Implement PEA. 2022-05-22 11:27:38 -04:00
Thomas Harte
4279ce87ea Implement LEA. 2022-05-22 08:29:12 -04:00
Thomas Harte
3c1c4f89e9 Add MULU/S functionality, though not timing. 2022-05-22 08:02:32 -04:00
Thomas Harte
4a6512f5d5 Reduce dispatch boilerplate. 2022-05-22 07:39:16 -04:00
Thomas Harte
284f23c6ea Implement JMP. 2022-05-22 07:16:38 -04:00
Thomas Harte
11a9a5c126 Use common macros for the two forms of Perform. 2022-05-22 07:08:14 -04:00
Thomas Harte
4993801741 Add missing prefetch to BSET, BCHG, BCLR. 2022-05-21 21:05:05 -04:00
Thomas Harte
4b35899a12 Bcc: properly establish offset. 2022-05-21 20:59:34 -04:00
Thomas Harte
1304e930eb DBcc is two-operand. 2022-05-21 20:06:03 -04:00
Thomas Harte
94288d5a94 Excludes DBcc from standard operand fetch. 2022-05-21 19:53:28 -04:00
Thomas Harte
3811ab1b82 Fix the two 8bit-with-displacement effective address Calc steps. 2022-05-21 16:20:01 -04:00
Thomas Harte
c869eb1eec Correct omission: wasn't testing the final PC.
Plenty of new errors incoming.
2022-05-21 15:56:27 -04:00
Thomas Harte
f97d2a0eb9 Add DIVU/DIVS, at least as far as getting the correct numeric result. 2022-05-21 15:56:09 -04:00
Thomas Harte
176c8355cb The tests in chk.json now pass. 2022-05-21 14:32:58 -04:00
Thomas Harte
2258434326 Ensure proper return addresses are calculated for JSR. 2022-05-21 14:28:44 -04:00
Thomas Harte
e46a3c4046 Implement JSR. 2022-05-21 10:29:36 -04:00
Thomas Harte
0e4cfde657 Fix MOVEM predec. 2022-05-21 08:17:39 -04:00
Thomas Harte
4bd9c36922 Fix postincrement mode. 2022-05-20 21:01:23 -04:00
Thomas Harte
256da43fe5 Fix MOVEM other than postinc and predec. 2022-05-20 20:47:54 -04:00
Thomas Harte
6a442e0136 MOVEM has an immediate first operand. 2022-05-20 20:34:51 -04:00
Thomas Harte
a818650027 Add a faulty attempt at MOVEM. 2022-05-20 18:48:19 -04:00
Thomas Harte
9d79e64f5c Add a mere calculate effective address pathway.
Plus a lot of waffle to try to justify the further code duplication.
2022-05-20 16:23:52 -04:00
Thomas Harte
c7c12f9638 After a quick check, eori_andi_ori also now passes. 2022-05-20 14:47:11 -04:00
Thomas Harte
ee942c5c17 Fix PC-relative fetches. 2022-05-20 14:42:51 -04:00
Thomas Harte
d157819c49 Implement the various to-[SR/CCR] actions, which do a 'repeat' prefetch.
(which isn't exactly a repeat, at least in the SR cases, because the function code might have changed)
2022-05-20 14:29:14 -04:00
Thomas Harte
2d91fb5441 Implement MOVEP. 2022-05-20 14:22:32 -04:00
Thomas Harte
81431a5453 Attempt BTST, BCHG, BCLR and BSET. 2022-05-20 12:58:45 -04:00
Thomas Harte
6d7ec07216 Uncover another three already-working test files. 2022-05-20 12:44:57 -04:00
Thomas Harte
b4978d1452 Implement BSR, adding one more test file to the working set. 2022-05-20 12:40:35 -04:00
Thomas Harte
cb77519af8 Make BSR operate like the other offsets: the flow controller gets whatever was in the opcode. 2022-05-20 12:40:09 -04:00
Thomas Harte
45e9648b8c Implement Bcc. 2022-05-20 12:04:43 -04:00
Thomas Harte
ce32957d9d Shuffle two more into the working column. 2022-05-20 11:53:12 -04:00
Thomas Harte
ba8592ceae At least on the 68000, Scc is read-modify-write. 2022-05-20 11:43:26 -04:00
Thomas Harte
4327af3760 DBcc: add write-back. 2022-05-20 11:37:18 -04:00
Thomas Harte
860cc63e21 Attempt DBcc. 2022-05-20 11:32:06 -04:00
Thomas Harte
452dd3ccfd Add a performer call-out for Scc; use it to implement proper timing in the mk2 68000. 2022-05-20 11:20:23 -04:00
Thomas Harte
e5c1621382 Add missing fallthrough, patterns for all ADDs and SUBs. 2022-05-20 07:02:02 -04:00
Thomas Harte
af3518dc1f Implement various ADD, SUB patterns. 2022-05-19 20:50:37 -04:00
Thomas Harte
6cfc0e80d9 Don't test the unrecognised instruction exception. 2022-05-19 19:45:38 -04:00
Thomas Harte
1ee9c585ca Fix segue into second operand. 2022-05-19 19:38:42 -04:00
Thomas Harte
efe5a5ac26 Signal will_perform even for invalid instructions. 2022-05-19 18:50:43 -04:00
Thomas Harte
334e3ec529 Add privilege and instruction error exceptions; permit two operands to be stored. 2022-05-19 16:55:16 -04:00
Thomas Harte
84c165459f ext.json now passes. 2022-05-19 16:32:40 -04:00
Thomas Harte
282c4121d6 CLR also follows the NEGX/NEG/NOT pattern. 2022-05-19 16:30:08 -04:00
Thomas Harte
6c2eee0e44 Implement CHK, and therefore the standard exception pattern. 2022-05-19 16:27:39 -04:00
Thomas Harte
eeb6a088b8 Add a tag to avoid duplication. 2022-05-19 15:49:42 -04:00
Thomas Harte
22b63fe1f8 Add EXT, and notes to self. 2022-05-19 15:41:02 -04:00
Thomas Harte
7ef526e2d3 Fix destination decrement. 2022-05-19 15:22:59 -04:00
Thomas Harte
ce7f94559b Add EXG, ABCD, SBCD. 2022-05-19 15:19:00 -04:00
Thomas Harte
0471decfc8 Implement the complete set of fetch addressing modes.
Subject to observations: (1) MOVE uses slightly custom versions of many of these for its stores; and (2) PEA and LEA need to do the calculation but not the read, so some of this will be duplicated further. It's either that or include greater conditionality on the path.
2022-05-19 15:03:22 -04:00
Thomas Harte
e4c0a89889 Just use the four-bit register number directly. 2022-05-19 15:01:09 -04:00
Thomas Harte
084d6ca11d Simplify address handling; add perform patterns for CMP, AND, OR, EOR. 2022-05-19 12:18:47 -04:00
Thomas Harte
274902c3c1 Add to-memory write-back. Am going to reconsider usage of temporary_address_ as noted. 2022-05-19 11:23:26 -04:00
Thomas Harte
f46e7c65c5 Add AddressRegisterIndirect fetches. 2022-05-19 10:47:57 -04:00
Thomas Harte
c6c6213460 Bifurcate the fetch-operand flow.
Address calculation will be the same, but the fetch will differ. I don't think there's a neat costless way to factor out the address calculations, alas, but I'll see whether macros can save the day.
2022-05-19 10:27:51 -04:00
Thomas Harte
29f6b02c04 Factor out register setup/testing, generalising the DIVU/DIVS flag check. 2022-05-18 21:13:34 -04:00
Thomas Harte
1bf7c0ae5f Attempt better to avoid entering a second instruction. 2022-05-18 21:00:34 -04:00
Thomas Harte
1b87626b82 Move some way towards MOVE. 2022-05-18 21:00:10 -04:00
Thomas Harte
44ae084794 Avoid the repeated .fill; reduces debug-build executor test time to 1.5s.
i.e. eliminates about 95% of costs.
2022-05-18 17:10:23 -04:00
Thomas Harte
13a1809101 Avoid memset. 2022-05-18 17:00:35 -04:00
Thomas Harte
c35200fbd0 Shuffle mildly, primarily to avoid repeated 16mb allocations. 2022-05-18 16:59:37 -04:00
Thomas Harte
da9fb216b1 Remove setup_operation in favour of doing the equivalent inline.
... as it'll probably allow me a route to `goto` straight out of there, too. At least, if I can find a sufficiently neat macro formulation.
2022-05-18 16:45:40 -04:00
Thomas Harte
bef12f3d65 Move ExecutionState into Implementation.hpp; use goto to avoid some double switches.
Re: the latter, yuck. Yuck yuck yuck. But it does mean I can stop going back and forth on how to structure conditionality on effective address generation segueing into fetches without doubling up on tests.
2022-05-18 15:35:38 -04:00
Thomas Harte
aa9e7eb7a2 Codify MOVE's status somewhat, avoid reading write-only operands. 2022-05-17 16:57:33 -04:00
Thomas Harte
f3d3e588fd Add enough of state to [sort-of] pass the first test.
i.e. until the processor overruns, as it is permitted to do, and can't handle the second instruction.
2022-05-17 16:51:26 -04:00
Thomas Harte
4a40581deb Completes performance of NBCD D0. 2022-05-17 16:10:20 -04:00
Thomas Harte
eed2672db5 Add documentation, honour signal_will_perform. 2022-05-17 15:05:11 -04:00
Thomas Harte
84071ac6d0 Implement reset logic, advance as far as actually performing an NBCD on D0 (but not writing it back). 2022-05-17 14:51:49 -04:00
Thomas Harte
1a27eea46c Establish general pattern for selecting a performance phase and obtaining operands. 2022-05-17 14:08:50 -04:00
Thomas Harte
d0b6451f02 Step gingerly on to fetching operands. 2022-05-17 08:26:35 -04:00
Thomas Harte
2147c5a5f2 Fill in missing #undefs. 2022-05-16 21:02:25 -04:00
Thomas Harte
c7aa4d8b6d Fix state transitions.
Confirmed that the 68000 mk 2 now appears correctly to perform a reset.
2022-05-16 21:00:25 -04:00
Thomas Harte
e94efe887c Switch to use of __COUNTER__. 2022-05-16 20:38:17 -04:00
Thomas Harte
3db2de7478 Works 68000 mk2 into the comparative tests.
... revealing that I've leant a little too hard on __LINE__.
2022-05-16 20:04:13 -04:00
Thomas Harte
345f7c3c62 Fill in just enough to attempt the reset exception, assuming DTACK rather than VPA or BERR. 2022-05-16 16:57:40 -04:00
Thomas Harte
13848ddbbc Add half-and-half access for SlicedInt32. 2022-05-16 16:56:54 -04:00
Thomas Harte
6f6e466c08 Make a first sketch of the coroutine-esque structure I'm going to experiment with here. 2022-05-16 11:59:03 -04:00
Thomas Harte
b0518040b5 Plants the seek of a 68000 mark 2. 2022-05-16 11:44:16 -04:00
Thomas Harte
29c872d867 Merge pull request #1032 from TomHarte/68000DIVUDIVS
Generalises the 68000's DIVU and DIVS.
2022-05-15 20:33:22 -04:00
Thomas Harte
acb63a1307 Pull generalised DIVU/DIVS into a macro. 2022-05-15 20:01:51 -04:00
Thomas Harte
341bf2e480 Repattern DIVS after DIVU. 2022-05-15 16:54:58 -04:00
Thomas Harte
20a191f144 Switch to same tests, run through a more modern emulator. 2022-05-15 16:33:08 -04:00
Thomas Harte
81f4581f41 Merge pull request #1031 from TomHarte/BCDTests
Correct 68000 BCD test results.
2022-05-15 07:24:30 -04:00
Thomas Harte
dfaf8ce64e Merge pull request #1028 from TomHarte/68000Perform
Add free function implementation of 68000 operations, and an instruction-set interpreter.
2022-05-15 07:21:03 -04:00
Thomas Harte
f60f1932f2 Restrict DIVU and DIVS tests to those which are well-defined. 2022-05-14 20:28:54 -04:00
Thomas Harte
ff8e4754d7 Ensure STOP exits the run loop. 2022-05-14 19:17:32 -04:00
Thomas Harte
27c4d19455 Support STOP. 2022-05-14 11:35:35 -04:00
Thomas Harte
7f704fdae1 Improve README. 2022-05-13 16:28:56 -04:00
Thomas Harte
dd63a6b61e Correct all [A/S/N]BCD tests. 2022-05-13 16:18:58 -04:00
Thomas Harte
1935d968c5 Add ability to suggest solutions. 2022-05-13 15:27:11 -04:00
Thomas Harte
f83954f5b7 Switch to common bit-selection logic. 2022-05-13 15:08:15 -04:00
Thomas Harte
77b56c50e6 Ensure you can't trace into divide-by-zero, etc. 2022-05-13 14:02:56 -04:00
Thomas Harte
002a8c061f Trim the public interface of Executor. 2022-05-13 13:55:37 -04:00
Thomas Harte
4299334e24 Clean up some TODOs, eliminate one further conditional. 2022-05-13 11:17:57 -04:00
Thomas Harte
4d03c73222 Ensure that the first instruction of privilege/line1010/etc exceptions isn't traced. 2022-05-13 11:08:22 -04:00
Thomas Harte
84cfbaa0a4 Remove manual test count, now that all are being performed. 2022-05-13 11:00:26 -04:00
Thomas Harte
7a2fd93d08 Document BusHandler interface. 2022-05-13 10:59:36 -04:00
Thomas Harte
0d81992f6a Move object creation. 2022-05-13 10:50:16 -04:00
Thomas Harte
5b67c9bf4a MOVE to SR requires supervisor privileges. 2022-05-13 09:01:03 -04:00
Thomas Harte
6594b38567 Tidy up, and reduce for now to a summary report. 2022-05-13 08:02:20 -04:00
Thomas Harte
6c854e8ecc Simplify is_supervisor semantics. 2022-05-13 07:53:40 -04:00
Thomas Harte
2e796f31d4 Support interrupts; documentation to come. 2022-05-12 20:52:24 -04:00
Thomas Harte
3d8f5d4302 Improve failure logging.
This confirms that it's only the *BCDs and DIVU/DIVS in which I do not match the tests.
2022-05-12 20:23:32 -04:00
Thomas Harte
2fa6b2301b Move string logic into Preinstruction. 2022-05-12 19:46:08 -04:00
Thomas Harte
4ba20132b9 Avoid repeated allocations on the new path, reducing total runtime by almost two thirds. 2022-05-12 16:35:41 -04:00
Thomas Harte
a6e4d23c29 Tidy up primarily as per PatickvL's comments.
... though pulling the flag values out of an enum and into a namespace is entirely my own contribution, to keep them in their own namespace but having them overtly be ints.
2022-05-12 16:23:07 -04:00
Thomas Harte
6d43576db7 Remove errant semicolon. 2022-05-12 16:21:36 -04:00
Thomas Harte
b7d1bff0c7 Eliminate branches from ABCD. 2022-05-12 15:25:01 -04:00
Thomas Harte
79c5af755f Eliminate branches from SBCD. 2022-05-12 15:18:03 -04:00
Thomas Harte
c6d84e7e60 Use Status::FlagT pervasively. 2022-05-12 11:42:33 -04:00
Thomas Harte
192513656a After much guesswork, fix SBCD and thereby pass flamewing tests. 2022-05-12 11:39:01 -04:00
Thomas Harte
41dc728c9b Merge branch '68000Perform' of github.com:TomHarte/CLK into 68000Perform 2022-05-12 11:27:59 -04:00
Thomas Harte
f3c1b1f052 Name flags, remove closing underscores on exposed data fields. 2022-05-12 08:19:41 -04:00
Thomas Harte
bd61c72007 Mutate SBCD to correct values, though not yet statuses. 2022-05-12 07:22:26 -04:00
Thomas Harte
0efeea1294 Slightly improve SBCD. Not there yet though. 2022-05-12 07:07:21 -04:00
Thomas Harte
56ce1ec6e8 No need to subclass. 2022-05-11 21:25:38 -04:00
Thomas Harte
de168956e4 Fix tested operand order. 2022-05-11 16:44:39 -04:00
Thomas Harte
5b80844d81 Add a sanity test count, temporarily. 2022-05-11 16:34:28 -04:00
Thomas Harte
a9902fc817 Fix ABCD when the result has an invalid lower digit. 2022-05-11 16:31:27 -04:00
Thomas Harte
ed75688251 Fix capture of the initial zero flag. 2022-05-11 15:40:17 -04:00
Thomas Harte
17add4b585 Introduce and overwhelmingly fail the flamewing BCD tests. 2022-05-11 15:19:39 -04:00
Thomas Harte
d492156453 Add noreturn attribute as a warning. 2022-05-11 10:51:48 -04:00
Thomas Harte
96af3d5ec5 Fix infinite inner/outer loop. 2022-05-11 10:26:12 -04:00
Thomas Harte
69ba14e34e Support the trace flag. 2022-05-11 09:39:15 -04:00
Thomas Harte
943c924382 Add missing: MOVE to/from USP, RESET. 2022-05-11 07:52:23 -04:00
Thomas Harte
4b97427937 Remove further magic constants. 2022-05-11 07:00:35 -04:00
Thomas Harte
ab8e1fdcbf Take a swing at access faults and address errors. 2022-05-10 16:20:30 -04:00
Thomas Harte
477979c275 Fully formulate and document the flow controller. 2022-05-10 10:34:07 -04:00
Thomas Harte
c635720a09 Tidy up; provide a notification for bit-change operations. 2022-05-10 08:23:25 -04:00
Thomas Harte
f2a6a12f79 Remove further vestiges of timing. 2022-05-09 20:58:51 -04:00
Thomas Harte
7445c617bc Start removing 68000-specific timing calculations. 2022-05-09 20:32:02 -04:00
Thomas Harte
8e7340860e Minor thematic rearrangement. 2022-05-09 16:35:17 -04:00
Thomas Harte
2ca1eb4cf8 Move set_pc into the operation-specific group. 2022-05-09 16:20:15 -04:00
Thomas Harte
0af8660181 Remove add_pc and decline_branch in favour of operation-specific signals. 2022-05-09 16:19:25 -04:00
Thomas Harte
330ec1b848 TODO is done. 2022-05-09 11:52:33 -04:00
Thomas Harte
2f7cff84d9 Enable missing rotates and shifts. 2022-05-09 11:26:01 -04:00
Thomas Harte
8e5650fde9 Clean up Instruction.hpp. 2022-05-09 10:13:42 -04:00
Thomas Harte
539932dc56 Provide function codes. TODO: optionally. 2022-05-09 09:18:02 -04:00
Thomas Harte
5ab5e1270e Fix test for new MOVEM semantics. 2022-05-09 09:17:48 -04:00
Thomas Harte
e35de357fa Route reads and writes through a common path. 2022-05-08 17:17:46 -04:00
Thomas Harte
0818fd7828 Ensure no status updates fall through the cracks. 2022-05-07 21:29:12 -04:00
Thomas Harte
98cb9cc1eb Fix CHK operand size. 2022-05-07 21:16:44 -04:00
Thomas Harte
bf8c97abbb Permit TRAP, TRAPV and CHK to push the next PC rather than the current. 2022-05-07 20:32:39 -04:00
Thomas Harte
ad6cf5e401 Pull out magic constant, simplify sp and TAS. 2022-05-07 20:20:24 -04:00
Thomas Harte
2b3900fd14 Fix LINK A7. 2022-05-07 08:15:26 -04:00
Thomas Harte
1defeca1ad Implement RTS, RTR, RTE. 2022-05-06 12:30:49 -04:00
Thomas Harte
ac6a9ab631 Fix TAS Dn. 2022-05-06 12:23:04 -04:00
Thomas Harte
8176bb6f79 Expose issues with TST and TAS. 2022-05-06 12:18:56 -04:00
Thomas Harte
9c266d4316 Proceed to unimplemented TST. 2022-05-06 11:33:57 -04:00
Thomas Harte
d478a1b448 Proceed to next failure: PEA. 2022-05-06 10:04:20 -04:00
Thomas Harte
190a351a29 Fix address writeback. 2022-05-06 09:56:01 -04:00
Thomas Harte
607ddd2f78 Preserve MOVEM order in Operation. 2022-05-06 09:45:06 -04:00
Thomas Harte
fed79a116f Be overt about the size being described here. 2022-05-06 09:22:38 -04:00
Thomas Harte
5db0ea0236 Add note for my tomorrow self. 2022-05-05 21:11:02 -04:00
Thomas Harte
06fe320cc0 Correct source counting, but this leaves the operands still being the wrong way around. 2022-05-05 21:06:53 -04:00
Thomas Harte
f7991e18de Makes a failed attempt to implement MOVEM to registers. 2022-05-05 20:32:21 -04:00
Thomas Harte
d7d0a5c15e Implement MOVEM to memory. 2022-05-05 18:51:29 -04:00
Thomas Harte
47f4bbeec6 Switch to a contiguous block of 16 registers. 2022-05-05 15:31:59 -04:00
Thomas Harte
9ab70b340c Route MOVEM appropriately. 2022-05-05 12:42:57 -04:00
Thomas Harte
70cdc2ca9f Fix MOVEP to register.
Advance to lack of MOVEM.
2022-05-05 12:37:47 -04:00
Thomas Harte
f63a872387 BTST does not write back. 2022-05-05 12:32:15 -04:00
Thomas Harte
67462c2f92 Rewire MOVEP. 2022-05-05 12:27:36 -04:00
Thomas Harte
4a4e786060 Hit a realisation: write-back isn't going to work with MOVEP as formulated. 2022-05-05 09:26:26 -04:00
Thomas Harte
665f2d4c00 Attempts MOVEP. 2022-05-05 09:00:33 -04:00
Thomas Harte
64586ca7ba Implement BTST/etc. 2022-05-04 20:57:22 -04:00
Thomas Harte
46686b4b9c Start testing move. 2022-05-04 20:38:56 -04:00
Thomas Harte
15c90e546f Fix rotates and shifts to memory. 2022-05-04 19:44:59 -04:00
Thomas Harte
5aabe01b6d Mostly fix LINK and UNLK. 2022-05-04 08:41:55 -04:00
Thomas Harte
5d1d94848c Take a bash at LINK and UNLK. 2022-05-04 08:26:11 -04:00
Thomas Harte
7d10976e08 Add LINK and UNLINK to operand_flags. 2022-05-03 20:51:02 -04:00
Thomas Harte
d3b55a74a5 Fix LEA, proceed to non-functional LINK and UNLK. 2022-05-03 20:45:36 -04:00
Thomas Harte
de58ec71fd Fix EXT, SWAP. 2022-05-03 20:17:36 -04:00
Thomas Harte
052ba80fd7 Add enough wiring to complete but fail EXT and JMP/JSR. 2022-05-03 15:49:55 -04:00
Thomas Harte
39f0ec7536 Get far enough through CHK to realise that MOVEM probably needs to be divided by direction. 2022-05-03 15:40:04 -04:00
Thomas Harte
af973138df Correct decoding of Bcc.b, satisfying Bcc and BSR tests. 2022-05-03 15:32:54 -04:00
Thomas Harte
5a87506f3d Fix Bcc, making decision that add_pc is relative to start of instruction. 2022-05-03 15:21:42 -04:00
Thomas Harte
90f0005cf2 Proceed to failing Bcc and flagging up my lack of an implementation for BSR. 2022-05-03 14:45:49 -04:00
Thomas Harte
d8b3748d24 Fix Scc size, DBcc behaviour. 2022-05-03 14:40:51 -04:00
Thomas Harte
1b224c961e Fix Scc, add operand flags for DBcc. 2022-05-03 14:23:57 -04:00
Thomas Harte
b6ffff5bbd Distinguish [ADD/SUB]QA from [ADD/SUB]Q. 2022-05-03 14:17:26 -04:00
Thomas Harte
5ebae85a16 Start recording successes. 2022-05-03 11:28:50 -04:00
Thomas Harte
b3cf13775b Consume operand_flags into Instruction.hpp. 2022-05-03 11:09:57 -04:00
Thomas Harte
c61809f0c4 Add CMPAl. 2022-05-03 09:20:02 -04:00
Thomas Harte
2f2d6bc08b Correct CMPw. 2022-05-03 09:05:34 -04:00
Thomas Harte
1bb809098c Switch — messily — to a more compact way of indicating sequence. 2022-05-03 09:04:54 -04:00
Thomas Harte
17a2ce0464 Fix missung #undefs. 2022-05-02 21:29:46 -04:00
Thomas Harte
011506f00d Add basic exceptions. 2022-05-02 21:27:58 -04:00
Thomas Harte
25ab478461 Fix immediate byte and word fetches. 2022-05-02 20:17:44 -04:00
Thomas Harte
fc9a35dd04 Test add/sub, add an exception for invalid Sequences. 2022-05-02 20:09:38 -04:00
Thomas Harte
7efe30f34c Fix (d8, _, Xn) calculation. 2022-05-02 15:09:59 -04:00
Thomas Harte
ef28d5512b Annotate further. 2022-05-02 12:58:04 -04:00
Thomas Harte
3827ecd6d3 Proceed to complete test running. 2022-05-02 12:57:45 -04:00
Thomas Harte
fa49737538 Correct processor name. 2022-05-02 08:40:47 -04:00
Thomas Harte
14532867a4 Sneaks towards testing EXT. 2022-05-02 08:00:56 -04:00
Thomas Harte
73f340586d Proceed to building, but failing tests. 2022-05-02 07:45:07 -04:00
Thomas Harte
56fe00c5fb Correct errors preparatory to Executor's lack of flow controller actions. 2022-05-01 20:40:57 -04:00
Thomas Harte
3c26177239 Provide both compile- and run-time operation selection options. 2022-05-01 17:39:56 -04:00
Thomas Harte
fe8f0d960d Equivocate.
(Specifically: addresses cannot generally be obtained in advance, as they are often the product of registers, but things like displacements, immediate values and absolute addresses can)
2022-05-01 15:30:03 -04:00
Thomas Harte
c72caef4fd Correct further size specifiers. 2022-05-01 15:21:58 -04:00
Thomas Harte
0720a391e8 Correct address register mutations. 2022-05-01 15:18:06 -04:00
Thomas Harte
d16ac70a50 Correct include path. 2022-05-01 15:14:12 -04:00
Thomas Harte
fc8e020436 Improve field name. 2022-05-01 15:12:13 -04:00
Thomas Harte
6b073c6067 Attempt to round out addressing modes, shift to a header, as per templating on BusHandler. 2022-05-01 15:10:54 -04:00
Thomas Harte
0b19bbff8d Marginally refactor, to avoid repetition of read/write branch. 2022-05-01 13:09:28 -04:00
Thomas Harte
42927c1e32 Establish more of the 680x0 executor loop. 2022-05-01 13:00:20 -04:00
Thomas Harte
df999978f1 Figure out what the call to perform should look like.
Albeit that this class doesn't currently offer any of the proper flow control actions.
2022-04-30 20:34:44 -04:00
Thomas Harte
43cd740a7b Shuffle Step to give meaning to the LSB. 2022-04-30 20:33:35 -04:00
Thomas Harte
52f355db24 Decision: operation is not a template parameter. Hence can use condition as fully typed. 2022-04-30 14:08:51 -04:00
Thomas Harte
a86c5ccdc9 Merge branch '68000Perform' of github.com:TomHarte/CLK into 68000Perform 2022-04-30 14:02:23 -04:00
Thomas Harte
e532562108 Merge branch 'master' into 68000Perform 2022-04-30 14:02:17 -04:00
Thomas Harte
4293ab2acb Merge pull request #1030 from TomHarte/68000cc
Include decoded condition in Preinstruction.
2022-04-30 13:56:49 -04:00
Thomas Harte
8d24c00df2 Include decoded condition in Preinstruction. 2022-04-30 09:00:47 -04:00
Thomas Harte
f4074e0bba Add basic status. 2022-04-30 08:38:28 -04:00
Thomas Harte
e4426dc952 Introduce calculate EA steps. 2022-04-29 20:30:48 -04:00
Thomas Harte
9359f6477b Start drafting an Executor. 2022-04-29 17:12:06 -04:00
Thomas Harte
a103f30d51 Attempt to game out LEA, PEA. Add various special MOVEs. 2022-04-29 14:43:58 -04:00
Thomas Harte
78b60dbd1a Evict MOVEM and MOVEP, enable TRAP and TRAPV, complete CHK. 2022-04-29 14:43:30 -04:00
Thomas Harte
cde75a1c00 Make steps more visible. 2022-04-29 11:26:39 -04:00
Thomas Harte
b9d243552c MOVEs don't read from operand 2. 2022-04-29 11:22:06 -04:00
Thomas Harte
85242ba896 Add to Xcode project, template on Model as per CLR being odd. Fill in some obvious answers. 2022-04-29 11:10:14 -04:00
Thomas Harte
d16dab6f62 Starts introducing a sequencer, to resolve responsibility of perform. 2022-04-29 10:40:19 -04:00
Thomas Harte
8066b19f93 Correct typos. 2022-04-29 07:57:02 -04:00
Thomas Harte
abd2a831a3 Added a further ambiguity. 2022-04-29 05:08:44 -04:00
Thomas Harte
824d3ae3f7 Conclusion: a union does produce better code.
(But needn't be so verbose)
2022-04-29 04:51:02 -04:00
Thomas Harte
727a14c6f9 Add notes for myself on decisions yet to make. 2022-04-29 03:53:17 -04:00
Thomas Harte
13d20137d3 Tackle two lingering references to exception_handler. 2022-04-29 03:38:23 -04:00
Thomas Harte
9680566595 Include in automated build, temporarily. 2022-04-28 20:42:44 -04:00
Thomas Harte
33c9ea2cf7 A flow controller feels more natural than an exception handler. 2022-04-28 20:42:04 -04:00
Thomas Harte
1d8d2b373b Port all simple instruction bodies. 2022-04-28 16:55:47 -04:00
Thomas Harte
611b472b12 Add evaluate_condition, to check standard 68000 condition codes. 2022-04-28 16:54:57 -04:00
Thomas Harte
bb73eb0db3 Start working on an isolation of 68000 instruction execution. 2022-04-28 15:35:40 -04:00
Thomas Harte
8a18685902 Relocated RegisterSizes to Numeric. 2022-04-28 15:10:08 -04:00
Thomas Harte
872b941b20 Merge pull request #1027 from TomHarte/GCCWarnings
Resolve GCC compilation warnings.
2022-04-27 20:00:38 -04:00
Thomas Harte
39261436c8 Remove unused type alias. 2022-04-27 19:53:32 -04:00
Thomas Harte
5e355383df Correct SIB test. 2022-04-27 19:53:15 -04:00
Thomas Harte
90bfec8c04 Merge pull request #1026 from TomHarte/FarewellOfft
Eliminate `off_t`.
2022-04-27 19:29:10 -04:00
Thomas Harte
866b6c6129 Eliminate off_t. 2022-04-27 19:16:37 -04:00
Thomas Harte
649fe7a1ec Merge pull request #1021 from TomHarte/68kDecoder
Establishes a formal 68k [pre-]decoder.
2022-04-27 08:14:24 -04:00
Thomas Harte
9cbbb6e508 Adjust path to match namespace; add to Qt project. 2022-04-27 08:05:36 -04:00
Thomas Harte
9908769bb3 Normalise test name. 2022-04-26 20:32:39 -04:00
Thomas Harte
8902bb1af0 Include size and supervisor flag in Preinstruction. 2022-04-26 19:44:02 -04:00
Thomas Harte
baf1bd354d Avoid packing/unpacking of operands. 2022-04-26 19:37:07 -04:00
Thomas Harte
539c2985aa Fill in size table, define quick to return a uint32_t. 2022-04-26 12:30:14 -04:00
Thomas Harte
5c356e15b5 Completes requires_supervisor. 2022-04-25 20:05:45 -04:00
Thomas Harte
8ff0b71b29 Subsume MOVEQ into MOVE.l; add missing invalid_operands. 2022-04-25 19:58:19 -04:00
Thomas Harte
4e5a6c89b9 Merge pull request #1025 from TomHarte/AndValidate
Switch to validation via a simple AND mask.
2022-04-25 16:29:23 -04:00
Thomas Harte
8f8f201186 Complete transition to simple AND-based verification. 2022-04-25 16:23:16 -04:00
Thomas Harte
0c688757b0 Adapt the last of the MOVEs, TAS, NOT, SUB and TST. 2022-04-25 16:05:44 -04:00
Thomas Harte
5778e92e70 Adapt MOVE, DIV, MUL, OR. 2022-04-25 15:43:25 -04:00
Thomas Harte
3268ea42ff Translate SUB, PEA. 2022-04-25 12:41:41 -04:00
Thomas Harte
1538500903 Add enough to make AND masks the default case. 2022-04-25 12:30:44 -04:00
Thomas Harte
6ca30a16ca Update JMP, JSR. 2022-04-25 12:05:07 -04:00
Thomas Harte
e6dc2e0d31 Add EXG, EXT. 2022-04-25 11:49:14 -04:00
Thomas Harte
9bbd1390c1 Add new-style validation of EORI to CCR, move EXG decoding into page navigation. 2022-04-25 11:43:30 -04:00
Thomas Harte
27f8db6e8b Update DBcc, DIVU/DIVS, EOR. 2022-04-25 09:49:18 -04:00
Thomas Harte
dda0c0e097 Update CMPM, CMPI. 2022-04-25 09:39:22 -04:00
Thomas Harte
f5ea5c26a3 Translate CHK, CLR, CMP, CMPA. 2022-04-24 21:05:00 -04:00
Thomas Harte
d01fa96177 Port BSR, BTST. 2022-04-24 20:49:41 -04:00
Thomas Harte
03caa53863 Translate BSET. 2022-04-24 19:58:10 -04:00
Thomas Harte
4f4a2e6d92 Translate ASL, ASR, Bcc, BCHG, BCLR. 2022-04-24 19:53:54 -04:00
Thomas Harte
87178ed725 Port AND. 2022-04-24 15:12:18 -04:00
Thomas Harte
94e5436f6e Attempt a more compact retelling. 2022-04-24 14:47:14 -04:00
Thomas Harte
b965f2053a Start experimenting with a simple AND for operand validation. 2022-04-24 10:43:06 -04:00
Thomas Harte
959db77b88 Eliminate concept of skips. 2022-04-22 20:59:25 -04:00
Thomas Harte
edee078f0a Eliminate last set of failures. 2022-04-22 20:57:45 -04:00
Thomas Harte
d4b766bf3f Introduce directional ADD/SUB/AND/OR.
Just 512 failures to go.
2022-04-22 20:37:09 -04:00
Thomas Harte
72772c9a83 Remove branch from combined_mode.
On x86 it was probably only a conditional move, but this is fine.
2022-04-22 15:11:41 -04:00
Thomas Harte
4c806d7c51 Tidy up slightly, ahead of a final push to getting complete test success.
After which I can start undoing style errors.
2022-04-22 14:51:25 -04:00
Thomas Harte
c16a60c5ea Import correct STOP, LINK, EXT. 2022-04-22 14:36:29 -04:00
Thomas Harte
96afcb7a43 Introduce remainder of tests. 2022-04-22 14:33:43 -04:00
Thomas Harte
e5a8d8b9ad Import corrected TRAPs and RTE/RTR. 2022-04-22 14:26:44 -04:00
Thomas Harte
efeee5160e Add tests for RTE, RTR, TRAP, TRAPV, CHK. 2022-04-22 10:06:39 -04:00
Thomas Harte
06fb502047 Add MUL/DIV tests and exclusions. 2022-04-22 09:47:16 -04:00
Thomas Harte
977192f480 Resolve D-page decoding errors.
In particular: that I'd overlooked CMPM, and was treating NOT as two-operand.
2022-04-22 09:24:16 -04:00
Thomas Harte
cf66d9d38d Add failing tests for EOR, NOT, OR; disambiguate EOR vs CMP. 2022-04-21 20:36:04 -04:00
Thomas Harte
25eeff8fc5 Correct CMP decoding, correct AND as far as asymmetry of Dn, Dn. 2022-04-21 20:14:52 -04:00
Thomas Harte
d342cdad2b Import corrected MOVEPs. 2022-04-21 19:04:14 -04:00
Thomas Harte
c899ee0d55 Enable MOVEP tests. 2022-04-21 18:57:47 -04:00
Thomas Harte
220408fcaa Introduce MOVEM tests.
12662 opcodes to go.
2022-04-21 16:39:17 -04:00
Thomas Harte
f4e99be7e1 Import BSRs, corrected MOVEMs. 2022-04-21 16:35:24 -04:00
Thomas Harte
bf9fc0ae96 Correct decoding of BSR. 2022-04-21 16:24:34 -04:00
Thomas Harte
9697e666b7 With a shift to MOVE.q, all tests now pass again.
12802 opcodes now untested.
2022-04-21 16:16:34 -04:00
Thomas Harte
a8a1a74b79 Correct BSRb quick value. 2022-04-21 16:13:06 -04:00
Thomas Harte
216ca7cbc9 Import BCC/BSR/BRA quick values. 2022-04-21 16:11:29 -04:00
Thomas Harte
549e440f7c Add 'quick' decoding and testing. 2022-04-21 16:05:00 -04:00
Thomas Harte
45c02c31f8 Add MOVEM exclusions. 2022-04-21 15:47:34 -04:00
Thomas Harte
b6b092d124 Add tests, exclusions for rest of shift/roll group. 2022-04-21 11:26:56 -04:00
Thomas Harte
d346d4a9b6 Import updated quick values. 2022-04-21 09:59:04 -04:00
Thomas Harte
c84e98774a Import corrected register ASL/etcs. 2022-04-21 09:51:21 -04:00
Thomas Harte
e1f4187430 Introduce failing ASL test. 2022-04-20 20:22:56 -04:00
Thomas Harte
3af93ada6f Test and correct Bcc, BSR, CLR, NEGX, NEG. 2022-04-20 20:19:56 -04:00
Thomas Harte
fa4dee8cfd Import two-operand DBccs. 2022-04-20 20:07:20 -04:00
Thomas Harte
3888492f0d Import corrected DBccs and JSRs. 2022-04-20 19:57:54 -04:00
Thomas Harte
dc16928f74 Add appropriate exclusions for JSR, JMP, Scc. 2022-04-20 16:56:26 -04:00
Thomas Harte
a4e440527b Import corrected CMPA references. 2022-04-20 16:46:05 -04:00
Thomas Harte
80ff146620 Add CMP, CMPA and TST tests and exclusions. 2022-04-20 16:29:45 -04:00
Thomas Harte
d4fe9d8166 Complete BTST/etc exclusions. 2022-04-20 16:16:24 -04:00
Thomas Harte
85a0af03c1 Import more standard JSON; start validating. 2022-04-20 09:17:00 -04:00
Thomas Harte
dc43f5605b Give MOVEPs precedence. 2022-04-20 08:40:56 -04:00
Thomas Harte
e0d2baae58 Test ANDI/ORI/EORI SR/CCR, and fail BTST/BCLR/BCHG/BSET. 2022-04-20 08:39:43 -04:00
Thomas Harte
437de19ecb Correct MOVE USP entries. 2022-04-20 08:34:10 -04:00
Thomas Harte
fab064641f Add Move[to/from][SR/CCR/USP] tests, correct decodings. 2022-04-20 07:59:13 -04:00
Thomas Harte
cc69d01bdc Strip dead code. 2022-04-19 20:41:39 -04:00
Thomas Harte
461a95d7ff Introduce missing register numbers for PEA, and elsewhere. 2022-04-19 20:39:01 -04:00
Thomas Harte
316e9681cc Weed out false PEAs. 2022-04-19 20:34:08 -04:00
Thomas Harte
4181313cc6 Correct decoding of SWAP. 2022-04-19 20:28:00 -04:00
Thomas Harte
aa1665acce Fix LEA transcription problems. 2022-04-19 20:24:03 -04:00
Thomas Harte
6aabc5e7b0 Test LEA, PEA, add name for MOVEq. 2022-04-19 19:45:51 -04:00
Thomas Harte
343a8e0192 Resolve wrong-headed mapping of LEA to MOVEAl. 2022-04-19 19:36:21 -04:00
Thomas Harte
2707887a65 Indicate MOVEAs. 2022-04-19 17:17:19 -04:00
Thomas Harte
ef87d09cfa Clear up MOVEs, fail on MOVEAs. 2022-04-19 17:13:23 -04:00
Thomas Harte
d21c67f237 Don't permit byte move from address register. 2022-04-19 16:49:26 -04:00
Thomas Harte
de0432b317 Include register numbers in MOVEs. 2022-04-19 16:34:22 -04:00
Thomas Harte
de40fed248 Test MOVEs and add operand validation. 2022-04-19 16:31:03 -04:00
Thomas Harte
76d7e0e1f8 Test and correct SUBs. 2022-04-19 16:27:20 -04:00
Thomas Harte
bfa551ec08 Correct ADDX and SUBX listings. 2022-04-19 16:21:40 -04:00
Thomas Harte
740e564bc7 Improve validation, add all ADDs.
It now looks like probably the ADDXs in the JSON are incorrect.
2022-04-19 14:45:15 -04:00
Thomas Harte
1f585d67b6 ADDA: correct decoding, add validation. 2022-04-19 14:43:01 -04:00
Thomas Harte
5b22e94a4b Map invalid reg+mode combinations to AddressingMode::None; add validation of ADDs and decoding of ADDX. 2022-04-19 14:36:36 -04:00
Thomas Harte
7749aef6b6 Improve const correctness. 2022-04-19 14:35:40 -04:00
Thomas Harte
5de8fb0d08 Disallow four illegal NBCD addressing modes. 2022-04-19 09:59:02 -04:00
Thomas Harte
19f7335926 Add post validation step. 2022-04-19 09:44:02 -04:00
Thomas Harte
9b61830a55 Add ADD.b as a note to self that .q decoding is also required. 2022-04-19 08:44:44 -04:00
Thomas Harte
99f4cd867d Decode the two EXTs. 2022-04-19 08:42:17 -04:00
Thomas Harte
f29fec33a2 Eliminate mismatches due to unsupported addressing modes. 2022-04-19 08:37:53 -04:00
Thomas Harte
93fe3459fd The quick value won't always fit in reg; turf the problem elsewhere. 2022-04-19 08:37:35 -04:00
Thomas Harte
1abd3bd7f3 Decode SWAP. 2022-04-19 08:37:13 -04:00
Thomas Harte
5509f20025 Fix MOVEfrom/toSR and NBCD listings. 2022-04-19 08:07:34 -04:00
Thomas Harte
fc4fd41be4 Reorder from most specific to least. 2022-04-19 08:00:52 -04:00
Thomas Harte
3ffca20001 Uncover various discrepancies with NBCD. 2022-04-19 07:15:54 -04:00
Thomas Harte
7c29305788 Test all ABCDs. 2022-04-18 20:00:39 -04:00
Thomas Harte
41fb18e573 Add 68k decoder to SDL build.
... and therefore to automated compilation testing.
2022-04-18 14:43:41 -04:00
Thomas Harte
e4c6251ef5 Express the BSR/Bcc.l test properly. 2022-04-18 14:42:31 -04:00
Thomas Harte
7aa250eaf7 Advances to hitting the same absent/present mapping as the old decoder. 2022-04-18 14:41:26 -04:00
Thomas Harte
ff380b686a Decode MOVEq. 2022-04-18 09:12:45 -04:00
Thomas Harte
d2452f4b68 Fix SUBQ ExtendedOperation mappings. 2022-04-18 09:08:49 -04:00
Thomas Harte
deb9c32a38 Add missing Sccs. 2022-04-18 09:04:17 -04:00
Thomas Harte
440f45b996 Attempt decoding and disambiguation of Scc, DBcc, Bcc and BSR. 2022-04-18 08:55:46 -04:00
Thomas Harte
7d64c4ec66 Add STOP. 2022-04-18 08:29:10 -04:00
Thomas Harte
7fe0d530c1 Add a decoder for TRAP. 2022-04-18 08:05:33 -04:00
Thomas Harte
c944767554 Better document decoding patterns, add LEA and CHK. 2022-04-18 08:00:43 -04:00
Thomas Harte
fde5a1c507 Ensure ADDI, SUBI, etc, provide an operation. 2022-04-18 07:42:30 -04:00
Thomas Harte
0fbfb41fa8 Expand on none-matching text. 2022-04-18 07:42:14 -04:00
Thomas Harte
1991ed0804 Introduce failing [partial-]test of new 68000 decoder. 2022-04-18 07:23:25 -04:00
Thomas Harte
e782b92a80 Add exposition. 2022-04-17 19:56:39 -04:00
Thomas Harte
07635ea2be Add register names, Q values. 2022-04-17 19:46:21 -04:00
Thomas Harte
fb3de9ce9d Merge pull request #1023 from TomHarte/AppleIIAutostart
Undo bad guess at initial switch state.
2022-04-17 17:07:56 -04:00
Thomas Harte
efff91ea3d Undo bad guess at initial switch state. 2022-04-17 17:03:05 -04:00
Thomas Harte
1916bd3bd0 Import a first effort at listing all 68000 instruction specs. 2022-04-17 07:57:59 -04:00
Thomas Harte
4eb752b000 Even out tabs. 2022-04-15 20:41:39 -04:00
Thomas Harte
bfb29a58f3 Take another crack at neatness; make LEA overt. 2022-04-15 20:33:59 -04:00
Thomas Harte
f86e455a87 Advance permissively through the 4xxx page to LEA. 2022-04-15 16:01:33 -04:00
Thomas Harte
faa35fe9fc Decode MOVE and the fixed 0x4xxx set. 2022-04-15 15:40:31 -04:00
Thomas Harte
89b8b59658 Ostensibly completes the 0 line. 2022-04-15 15:33:54 -04:00
Thomas Harte
de55a1adc4 Require a model for decoding; shift a bunch of immediates into ExtendedOperation. 2022-04-15 09:40:37 -04:00
Thomas Harte
d1613025ee For now, assume the .q actions can be handled inside Preinstruction. 2022-04-13 09:29:12 -04:00
Thomas Harte
cc4431c409 Expand decode to accept a wider array of operations, and then funnel them down. 2022-04-12 16:17:30 -04:00
Thomas Harte
3d5986c55d Some minor style changes, plus I think I've talked myself into an expanded Operation-tracking enum. Probably. 2022-04-12 14:54:11 -04:00
Thomas Harte
9aeb6ee532 Formally prepare for one- and two-operand instructions. 2022-04-12 09:14:46 -04:00
Thomas Harte
e7f6cc598d Make first attempt to complete broad phase of decoding. 2022-04-12 09:08:46 -04:00
Thomas Harte
cd465dd121 Decode page E. 2022-04-12 09:04:40 -04:00
Thomas Harte
174b48a14a Populate lines 9 and D. 2022-04-12 08:57:40 -04:00
Thomas Harte
bca18e7aba Fill in line decoders for 5, 6 and 7.
This leaves 9, D and E to go.
2022-04-12 08:44:32 -04:00
Thomas Harte
17e761d6c6 Add enough code to pages 0–3 to shift problem to decode(). 2022-04-12 08:36:44 -04:00
Thomas Harte
c50556dde4 Create empty line decoders. 2022-04-12 08:16:29 -04:00
Thomas Harte
dd5bdd67d7 Add B page and a large chunk of 4. 2022-04-12 07:49:08 -04:00
Thomas Harte
21ac9363e9 Add page 8. 2022-04-11 16:32:57 -04:00
Thomas Harte
8e3cccf4d6 Begins a formalised 68k decoder. 2022-04-11 15:00:55 -04:00
Thomas Harte
945e935312 Merge pull request #1020 from TomHarte/RotateMask
Improve PowerPC rotate mask generation.
2022-04-10 15:24:17 -04:00
Thomas Harte
bb5cf570e5 Remove conditional, make generic enough for both 32- and 64-bit operation. 2022-04-10 15:18:23 -04:00
Thomas Harte
a5ed288db2 Merge pull request #1018 from TomHarte/PowerPCTests
Import Dingusdev PowerPC tests
2022-04-10 09:45:39 -04:00
Thomas Harte
7002d6d306 Improve accuracy of comment. 2022-04-10 09:37:18 -04:00
Thomas Harte
1b8d8f3a04 Default to 32-bit versions. 2022-04-10 09:35:58 -04:00
Thomas Harte
284440336d Correct rotate_mask(). 2022-04-10 09:31:39 -04:00
Thomas Harte
140ae7a513 Clarify template parameters. 2022-04-10 08:57:09 -04:00
Thomas Harte
21328d9e37 Normalise macros, remove unused AssertEqualOperationNameO. 2022-04-09 21:25:00 -04:00
Thomas Harte
5177fe1db7 Update tests. 2022-04-09 21:11:58 -04:00
Thomas Harte
7de50b5e2e Provide 64-bit me, mb and sh. Add direct getter for rotate masks. 2022-04-09 21:08:01 -04:00
Thomas Harte
4652a84b43 Add exposition. 2022-04-09 19:20:13 -04:00
Thomas Harte
9e0755bc86 Introduce overlooked: ld, ldu, rldclx, rldcrx, rldicx, rldiclx, rldicrx, rldimix. 2022-04-09 18:28:51 -04:00
Thomas Harte
da0f7d7907 Rearrange into alphabetical order. 2022-04-09 10:20:03 -04:00
Thomas Harte
88d72bf31d Fill in more mnemonics. 2022-04-08 10:01:52 -04:00
Thomas Harte
aac2f7dd73 Add missing validations. 2022-04-08 09:47:04 -04:00
Thomas Harte
1f44ad1723 Completes test cases. 2022-04-06 21:09:58 -04:00
Thomas Harte
4ab1857a11 Complete MPC601 commentary. 2022-04-06 20:53:44 -04:00
Thomas Harte
d23c714ec7 Build in an optional post hoc validation.
TODO: validate.
2022-04-05 11:23:54 -04:00
Thomas Harte
ac524532e7 Handle the synonym test cases. 2022-04-04 08:09:59 -04:00
Thomas Harte
59a1fde2a1 Fix is_zero_mask. 2022-04-03 20:37:09 -04:00
Thomas Harte
31276de5c3 Complete 'misc instructions' tests. 2022-04-03 20:33:32 -04:00
Thomas Harte
c581aef11d Test as far as mffs. 2022-04-03 18:29:40 -04:00
Thomas Harte
7f6a955a71 Complete the cmp set. 2022-04-03 15:50:03 -04:00
Thomas Harte
125d97cc41 Complete floating point tests. 2022-04-03 08:55:28 -04:00
Thomas Harte
de7d9ba471 Add further floating point tests. 2022-04-03 08:06:59 -04:00
Thomas Harte
ad54b44235 Begin documentation and testing of the floating point instructions. 2022-04-02 19:58:21 -04:00
Thomas Harte
42532ec0f5 Test floating point loads and stores. 2022-04-02 15:40:17 -04:00
Thomas Harte
b84fa619da Test integer loads and stores. 2022-04-02 15:27:12 -04:00
Thomas Harte
8a1409184f Add decoding of lwa. 2022-04-02 10:31:55 -04:00
Thomas Harte
8a3c16a5bc Add lwa. 2022-04-02 10:26:47 -04:00
Thomas Harte
6343c65ce2 Document further; mftb is optional. 2022-04-02 10:09:58 -04:00
Thomas Harte
20b4736a1f Test tw, twi. 2022-04-02 10:09:35 -04:00
Thomas Harte
d5967f7834 Correct decoding of stwcx. and stdcx. 2022-04-01 20:37:36 -04:00
Thomas Harte
d5f7650ac1 Test synchronising loads and stores, further expand documentation. 2022-04-01 18:30:48 -04:00
Thomas Harte
6330caffde Test logical immediates. 2022-04-01 17:52:38 -04:00
Thomas Harte
8f580c256c Remove explanations; saying nothing is better than giving incomplete advice. 2022-04-01 17:49:34 -04:00
Thomas Harte
4671b8db5c Add tests for non-immediate logicals. 2022-04-01 17:35:47 -04:00
Thomas Harte
7c8f044380 Complete shift tests. 2022-04-01 17:22:32 -04:00
Thomas Harte
8efd506471 Transcribe up to the end of 'e', use extswx and remove extsw. 2022-04-01 17:11:57 -04:00
Thomas Harte
e83267751e Start shuffling parameters into conventional order; expand on cmp–cmpli, dcbf–dcbz. 2022-03-30 20:36:46 -04:00
Thomas Harte
a3b110aee5 Clean up. Shifts next. 2022-03-30 17:04:41 -04:00
Thomas Harte
84f0b0a84c Test rotates. 2022-03-30 16:43:09 -04:00
Thomas Harte
c9c5adc650 Test crand ... crxor. 2022-03-30 12:40:57 -04:00
Thomas Harte
52e7226655 Merge branch 'PowerPCTests' of github.com:TomHarte/CLK into PowerPCTests 2022-03-29 20:50:40 -04:00
Thomas Harte
b89c8decd4 Test addx–divwx and mtcrf; document fields for crand, etc. 2022-03-29 20:48:43 -04:00
Thomas Harte
d783975597 Start offering a list of relevant fields per Operation. 2022-03-29 19:59:21 -04:00
Thomas Harte
5ec291df5c Merge branch 'PowerPCTests' of github.com:TomHarte/CLK into PowerPCTests 2022-03-29 14:38:28 -04:00
Thomas Harte
0a45355055 Add a few more field comments. 2022-03-29 14:37:21 -04:00
Thomas Harte
e696624da0 Now passes negx, subfex, subfzex, subfmex, dozx, absx, nabsx. 2022-03-28 20:47:32 -04:00
Thomas Harte
99ad40f3e0 Test subfcx, subfx; correct decoding of oe(). 2022-03-28 20:39:52 -04:00
Thomas Harte
b9c8016aca Merge branch 'PowerPCTests' of github.com:TomHarte/CLK into PowerPCTests 2022-03-28 20:20:59 -04:00
Thomas Harte
8ad1f2d4f5 Add bad attempt to catch subfc. 2022-03-28 20:18:41 -04:00
Thomas Harte
dc30581be0 Fix typo; . -> , 2022-03-28 16:39:55 -04:00
Thomas Harte
2e56b606fa Improve file division, document some further operations. 2022-03-27 18:44:56 -04:00
Thomas Harte
d84c72afe5 Test loads and stores, and immediate arithmetic. 2022-03-27 08:47:01 -04:00
Thomas Harte
2d69896f64 Merge branch 'master' into PowerPCTests 2022-03-26 10:12:15 -04:00
Thomas Harte
b3dd2db815 Merge pull request #1017 from TomHarte/CPC128k
CPC: ensure 64/128k RAM is properly selected.
2022-03-26 09:08:40 -04:00
Thomas Harte
290dd3993b CPC: ensure 64/128k RAM is properly selected. 2022-03-26 08:54:07 -04:00
Thomas Harte
4f6a9917c6 Test lbzx, lbzux. 2022-03-26 08:45:07 -04:00
Thomas Harte
3d48183753 Test lwzux. 2022-03-25 20:31:47 -04:00
Thomas Harte
33c31eb798 Test lwzx. 2022-03-25 20:23:21 -04:00
Thomas Harte
73ae7ad82f Resolve final branch test: aa() applies. 2022-03-25 20:10:08 -04:00
Thomas Harte
ee6470708b Merge pull request #1016 from TomHarte/unistd
Eliminate usages of unistd.h.
2022-03-25 17:04:29 -04:00
Thomas Harte
61f25926b5 Eliminate usages of unistd.h. 2022-03-25 16:58:06 -04:00
Thomas Harte
1a5d3bb69c Match majority of branch tests. 2022-03-25 08:41:57 -04:00
Thomas Harte
7d4fe55d63 Handle bclrx set and clear. 2022-03-25 06:25:06 -04:00
Thomas Harte
089e03afe8 Navigates bcctrx tests, adding simplified bo() helpers and bi() helpers. 2022-03-24 20:44:03 -04:00
Thomas Harte
8e019f01ab Document dozx and dozi. 2022-03-21 10:49:01 -04:00
Thomas Harte
77bdaf3c78 These are likely to be useful outside of the decoder. 2022-03-21 10:41:17 -04:00
Thomas Harte
0b6828c895 Decision: these enums will be at namespace scope. 2022-03-21 10:19:30 -04:00
Thomas Harte
d4704c656f Merge branch 'PowerPCTests' of github.com:TomHarte/CLK into PowerPCTests 2022-03-21 10:18:36 -04:00
Thomas Harte
c01192c784 Add exposition for absx to divsx. 2022-03-21 10:17:55 -04:00
Thomas Harte
8adb611edf Attempt to clarify with an enum. 2022-03-19 12:27:28 -04:00
Thomas Harte
e5af5b57ad Add documentation for bx, bcx, bcctrx.
Catch bcx tests.
2022-03-18 19:55:26 -04:00
Thomas Harte
f05d3e6af3 Introduce dingusdev tests, do just enough to check bx. 2022-03-18 17:24:12 -04:00
Thomas Harte
5963d038ef Merge pull request #1014 from TomHarte/DDFSTRTSTOP
Improve application of DDFSTRT and DDFSTOP.
2022-03-18 15:48:06 -04:00
Thomas Harte
bfd28a04ba Remove noise. 2022-03-18 10:41:20 -04:00
Thomas Harte
359ec257c0 Add a further state, seemingly to fix high-res mode. 2022-03-18 08:27:46 -04:00
Thomas Harte
88767e402c Switch DDFSTART/STOP state machine. 2022-03-17 20:03:36 -04:00
Thomas Harte
88c7a6d053 Merge pull request #1013 from TomHarte/CroppedBottom
Switches all Copper WAITs to 12 cycles
2022-03-13 13:38:32 -04:00
Thomas Harte
e698cbf092 Silence debugging information. 2022-03-13 12:48:05 -04:00
Thomas Harte
f2ce646d8d Undo 8-cycle-if-met WAIT. 2022-03-13 12:47:48 -04:00
Thomas Harte
cbf9b345ff Merge pull request #1010 from TomHarte/80386
Expands x86 decoder to recognise 80386 opcodes.
2022-03-12 12:46:15 -05:00
Thomas Harte
1725894fe9 Eliminate redundant CMPSD, CDQ, CWDE.
Also removes IBTS for now, as I'm unclear where it should sit in the opcode map.
2022-03-12 12:24:44 -05:00
Thomas Harte
fd4f85eb19 Add SMSW. 2022-03-12 12:23:48 -05:00
Thomas Harte
f1c4864016 Eliminate INSD. 2022-03-12 11:37:21 -05:00
Thomas Harte
e6bd265729 Explain which BOUNDs operand is which. 2022-03-11 20:34:28 -05:00
Thomas Harte
c22e8112e7 Expand exposition. 2022-03-11 20:30:56 -05:00
Thomas Harte
44252984c2 Eliminate INT3 special case. 2022-03-11 14:03:46 -05:00
Thomas Harte
4b4f92780e Shuffle extension word order.
The primary objective here is simplifying index calculation, but as per the note it does also potentially open up options with regard to packing in the future.
2022-03-11 13:24:45 -05:00
Thomas Harte
f694620087 Resolve TODO. 2022-03-11 13:10:44 -05:00
Thomas Harte
dc1d1f132e Add one more address size modifier test. 2022-03-11 13:01:02 -05:00
Thomas Harte
9b4048ec6e The address size modifier doesn't seem to affect far address sizes.
It's meant to affect only instructions with operands that reside in memory, I think. So probably only ::DirectAddress in my nomenclature. More research to do.
2022-03-11 12:46:07 -05:00
Thomas Harte
727342134c Add 8086 length limit test. 2022-03-11 11:55:41 -05:00
Thomas Harte
c744a97e3c Ensure no extensions for default constructed Instruction. 2022-03-11 11:55:26 -05:00
Thomas Harte
40cafb95ed Add 286 and 386 instruction length tests. 2022-03-11 09:48:51 -05:00
Thomas Harte
91d75d7704 Switch strategy on 8086 instruction lengths. 2022-03-11 09:48:26 -05:00
Thomas Harte
dc8cff364f Switch to common test. 2022-03-11 09:48:02 -05:00
Thomas Harte
572dc40e6b Allow assignments. 2022-03-11 09:47:23 -05:00
Thomas Harte
f92ffddb82 Add instruction length limits. 2022-03-10 20:47:56 -05:00
Thomas Harte
641e0c1afc Resolve default segment question. 2022-03-10 20:27:35 -05:00
Thomas Harte
bf7faa80c1 Add TODO. 2022-03-10 16:47:54 -05:00
Thomas Harte
a2ae3771eb Add test for switch to Source::IndirectNoBase. 2022-03-10 15:45:56 -05:00
Thomas Harte
673ffc50da Switch to intended compact version of Instruction. 2022-03-10 15:14:50 -05:00
Thomas Harte
6dc9973754 Incorporate length into Instruction. 2022-03-10 07:12:12 -05:00
Thomas Harte
cf6a910630 Handle no-base case directly in existing switch. 2022-03-09 20:20:32 -05:00
Thomas Harte
520baa6ec8 Formalise IndirectNoBase and permit a knowledgable caller to avoid conditionals. 2022-03-09 20:19:40 -05:00
Thomas Harte
c1cc4f96df Switch to const auto. 2022-03-09 16:56:32 -05:00
Thomas Harte
bbf925a27e Clarify, unify and correct decoding and encoding of [CALL/RET/JMP][near/far/relative/absolute]. 2022-03-09 16:48:06 -05:00
Thomas Harte
381fd5dbe4 E8 is a relative call. 2022-03-09 16:37:07 -05:00
Thomas Harte
ead8b7437e Remove done TODO. 2022-03-09 15:26:20 -05:00
Thomas Harte
9f2d18b7ba Improve comment formatting. 2022-03-09 15:25:46 -05:00
Thomas Harte
acd9df6745 Fix segment/offset sizes for far calls. 2022-03-09 15:23:43 -05:00
Thomas Harte
f96c051932 Record PUSH immediate operation size. 2022-03-09 14:24:57 -05:00
Thomas Harte
67b2e40fae Fixed: INs and OUTs remain single byte. 2022-03-09 10:51:16 -05:00
Thomas Harte
081a2acd61 Fix shift group operand size. 2022-03-09 09:33:25 -05:00
Thomas Harte
de79acc790 Fix RegAddr/AddrRegs and group 2 decoding. 2022-03-09 08:38:34 -05:00
Thomas Harte
a125bc7242 Fill in more of test32bitSequence. 2022-03-08 20:16:19 -05:00
Thomas Harte
ebed4cd728 Introduce failing 32-bit parsing test. 2022-03-08 19:57:10 -05:00
Thomas Harte
21d4838322 Fix current implementation of data_segment.
As far as it goes.
2022-03-08 17:08:21 -05:00
Thomas Harte
926a373591 Extend SIB test, correct decoder. 2022-03-08 15:03:37 -05:00
Thomas Harte
0cbb481fa4 Add a formal SIB test. 2022-03-08 14:56:27 -05:00
Thomas Harte
a954f23642 Attempt 32-bit modregrm + SIB parsing. 2022-03-08 14:39:49 -05:00
Thomas Harte
41a104cc10 Adds special test/control/debug MOVs.
This'll do; it's not ideal but avoids bloating up the `Source` enum.
2022-03-07 17:04:05 -05:00
Thomas Harte
f0b4971c7b Correct SHLD format. 2022-03-07 16:39:02 -05:00
Thomas Harte
8e669a32a3 Take a stab at group 8. 2022-03-07 16:34:56 -05:00
Thomas Harte
0e16e7935e Correct double reference to Group 6. 2022-03-07 16:26:17 -05:00
Thomas Harte
7ea84d9a4e Add MOVZX, MOVSX. 2022-03-07 16:25:44 -05:00
Thomas Harte
7313c89dec Add BT, BTS, BTR, BTC, BSF, BSR. 2022-03-07 16:23:25 -05:00
Thomas Harte
35a66c03c2 Add the SETs. 2022-03-07 10:32:34 -05:00
Thomas Harte
bbb3168bae Adds the missing shift group segues at c0 and c1. 2022-03-07 09:18:59 -05:00
Thomas Harte
1ea9d3faf8 Introduce additional forms of IMUL. 2022-03-07 09:05:22 -05:00
Thomas Harte
4479be4fd0 Add the two immediate PUSHes. 2022-03-06 14:28:41 -05:00
Thomas Harte
e7aaf4dd2e Add LDS, LES, LSS test. 2022-03-06 12:10:25 -05:00
Thomas Harte
91a6bf671d Also 'easy': LSS, LFS, LGS.
Though perhaps I'm off on LES and LDS?
2022-03-06 09:28:43 -05:00
Thomas Harte
49b5889d9e 0x8c is available on the 8086. 2022-03-06 09:24:59 -05:00
Thomas Harte
ede61ae130 Flag up TODOs, for easier in-editor navigation. 2022-03-05 17:48:01 -05:00
Thomas Harte
7a79111767 Add the easiest 80386 extensions: PUSH/POP FS/GS and longer conditional jumps. 2022-03-05 17:32:21 -05:00
Thomas Harte
6432521b9d Correct two references to JP that should be JL. 2022-03-05 17:16:32 -05:00
Thomas Harte
65f578fe61 Add notes on all missing opcodes. 2022-03-05 17:16:13 -05:00
Thomas Harte
3a8eb4a4f0 Add 80386 segment overrides. 2022-03-05 17:03:46 -05:00
Thomas Harte
eb180656bb Fix $8e data size, add $8c. 2022-03-05 17:00:48 -05:00
Thomas Harte
1afcbba218 Clarify sign extension availability. 2022-03-05 16:44:26 -05:00
Thomas Harte
8a0902a83b Adapts existing opcodes for 32-bit parsing. 2022-03-05 13:52:07 -05:00
Thomas Harte
dfb312fee6 Make column and row meanings overt. 2022-03-05 11:56:08 -05:00
Thomas Harte
11bb594fa2 Sets up [ignored] memory and data size prefixes. 2022-03-02 20:23:35 -05:00
Thomas Harte
8e3ae2c78f Add opcode map as documentation. 2022-03-02 20:00:21 -05:00
Thomas Harte
8080d1d961 Extend test case slightly. 2022-03-01 20:22:43 -05:00
Thomas Harte
4b4135e35a Correct #undef. 2022-03-01 18:23:24 -05:00
Thomas Harte
d1148c4cab Switch to constexpr function, for guaranteed semantics. 2022-03-01 17:30:41 -05:00
Thomas Harte
8ee62b4789 Simplify address size semantics.
Since it'll no longer be a mode-dependant toggle, but a fully-retained value.
2022-03-01 17:29:26 -05:00
Thomas Harte
5e7a142ff1 Fix is_write errors, update comment, add additional source for asserts. 2022-03-01 16:51:54 -05:00
Thomas Harte
2c816db45e Refactor: (i) to expose effective address calculation; and (ii) to include address size in Instruction. 2022-03-01 09:36:37 -05:00
Thomas Harte
b920507f34 Double down on AddressT, add an assert on memory_mask. 2022-02-28 10:03:58 -05:00
Thomas Harte
d8601ef01f Add missing hex specifier. Test now passes. 2022-02-28 09:54:29 -05:00
Thomas Harte
afbc57cc0c Incorporate displacement, switch macro flag. 2022-02-28 09:53:23 -05:00
Thomas Harte
9f12c009d6 Correct data size when accessing address registers. 2022-02-27 19:45:03 -05:00
Thomas Harte
84ac68a58b Fix indirect memory read/write 2022-02-27 18:43:00 -05:00
Thomas Harte
27d1df4699 Introduce enough of a DataPointerResolver test to build but fail. 2022-02-27 18:27:58 -05:00
Thomas Harte
0d7a7dc7c9 Introduce DataPointerResolver, to codify the meaning of DataPointer and validate that enough information is present. 2022-02-27 11:25:02 -05:00
Thomas Harte
b8bff0e7f5 Double up eSP, eBP, eSI, eDI and AH, CH, DH, BH enums, as per Intel's encoding. 2022-02-24 05:16:15 -05:00
Thomas Harte
60bf1ef7ea Rename SourceSIB to DataPointer, extend to allow for an absent base. 2022-02-23 08:28:20 -05:00
Thomas Harte
dc37b692cf Switch to templated test function. 2022-02-23 04:33:28 -05:00
Thomas Harte
95976d8b58 Add missing #include. 2022-02-21 16:33:58 -05:00
Thomas Harte
ecb20cc29b Improve tabbing. 2022-02-21 16:09:03 -05:00
Thomas Harte
b6183e86eb Clarifies model tests by macro; adds the address size toggle. 2022-02-21 16:06:02 -05:00
Thomas Harte
229af0380c This is normatively called the address size. 2022-02-21 15:52:16 -05:00
Thomas Harte
b968a662d3 Dump notes on intended Instruction layout, add memory size flag. 2022-02-21 15:48:58 -05:00
Thomas Harte
159e869fe6 Justifies the templatisation. 2022-02-21 15:33:08 -05:00
Thomas Harte
76814588b8 Template Instruction on its content size. 2022-02-21 12:36:03 -05:00
Thomas Harte
1934c7faa2 Switch Decoder into a template. 2022-02-21 12:21:57 -05:00
Thomas Harte
9e9e160c43 Eliminate Ind[BXPlusSI/etc] in favour of specifying everything via a ScaleIndexBase. 2022-02-21 11:45:46 -05:00
Thomas Harte
546b4edbf1 Ensure ScaleIndexBase can be used constexpr; add note-to-self on indexing table. 2022-02-20 19:22:28 -05:00
Thomas Harte
63d8a88e2f Switch to holding the SIB as a typed ScaleIndexBase.
(and permit copy assignment)
2022-02-20 17:54:53 -05:00
Thomas Harte
75d2d64e7c Albeit that it requires nuanced shift/roll semantics, eliminates CL constant.
Shifts and rolls are already slightly semantically special for being undefined for values greater than 8/16/32 — i.e. in some implementations they don't even use the entirety of CL, just the low five bits. Which makes me feel a little better.

The upside of no ambiguity between eCX size 1 and CL justifies the trade.
2022-02-20 17:52:19 -05:00
Thomas Harte
a5113998e2 Accept that IN and OUT are going to have special semantics, thereby kill ::AX and ::DX. 2022-02-20 17:15:01 -05:00
Thomas Harte
4d2e8cd71d Adds a presently-unreachable step for SIB consumption. 2022-02-19 18:00:27 -05:00
Thomas Harte
30b355fd6f Chips away further at the legacy register names. 2022-02-18 18:37:47 -05:00
Thomas Harte
c257b91552 Update tests to preference away from [A/B/C/D]L. 2022-02-18 16:32:28 -05:00
Thomas Harte
12df7112da Starts adjusting the concept of a Source. 2022-02-17 11:32:09 -05:00
Thomas Harte
cd5ca3f65b Attempts a full decoding of the 80286 instruction set. 2022-02-10 17:13:50 -05:00
Thomas Harte
0bd63cf00f Introduces the easy F page instructions. 2022-02-10 09:35:05 -05:00
Thomas Harte
7ceb3369eb Attempts decoding of the 80186 set. 2022-02-09 17:51:48 -05:00
Thomas Harte
ae21726287 Splits 80186 additions from 80286; fills in a touch more. 2022-02-01 20:38:10 -05:00
Thomas Harte
a4da1b6eb0 Begins enumerating the 80286 and 80386 instructions. 2022-01-31 09:11:06 -05:00
Thomas Harte
85bfd2eba3 Remove further errant 'Awaiting's. 2022-01-31 08:22:07 -05:00
Thomas Harte
2d543590dc Make a noun, for better consistency. 2022-01-31 08:14:33 -05:00
Thomas Harte
18b6f17e86 With some refactoring makes some minor steps towards supporting gaps. 2022-01-06 17:24:31 -05:00
Thomas Harte
f37179d9f2 Gaps appear to contain pre-MFM data (?) 2022-01-02 15:39:26 -05:00
Thomas Harte
3e0b7d71d4 Properly handle partial bytes. 2022-01-01 19:09:19 -05:00
Thomas Harte
58d10943ed Add asserts to validate my reserve sizes. 2022-01-01 19:08:44 -05:00
Thomas Harte
dc920a04f6 Add missing #include. 2022-01-01 19:03:07 -05:00
Thomas Harte
d031381e70 Gaps provide content, and data chunk lengths seem to be in terms of unencoded bytes. 2022-01-01 18:47:07 -05:00
Thomas Harte
ed1b0b90f7 Makes a first attempt at encoding data. 2022-01-01 18:36:44 -05:00
Thomas Harte
38dd3c5c60 On second thoughts, no need to use a vector here. 2022-01-01 17:15:12 -05:00
Thomas Harte
d3189acaa6 Add a constexpr route that explicitly calculates the simplest possible form. 2022-01-01 17:14:52 -05:00
Thomas Harte
350c98ab4d Add those densities I've yet discovered the rules for. 2021-12-29 18:15:37 -05:00
Thomas Harte
4f3c754771 Adds exposition. 2021-12-27 19:15:46 -05:00
Thomas Harte
dc994f001d Mention units. 2021-12-27 18:55:11 -05:00
Thomas Harte
9b6ccbcc95 Parses data and gap stream elements. 2021-12-27 18:12:44 -05:00
Thomas Harte
9d3cf9c73c Collate descriptions of all tracks. 2021-12-26 14:49:51 -05:00
Thomas Harte
28572d4392 Enforce string-length requirement. 2021-12-26 09:12:44 -05:00
Thomas Harte
0433db0370 Eliminate macro. 2021-12-25 19:36:54 -05:00
Thomas Harte
a6b326da48 Parse the INFO record. 2021-12-25 18:17:13 -05:00
Thomas Harte
e457ce66ea Adds sanity checks around CAPS block. 2021-12-25 17:32:29 -05:00
Thomas Harte
c118dd8afe Adds just enough to list all the blocks in an IPF. 2021-12-25 17:27:50 -05:00
Thomas Harte
dba3a3d942 Add through route to an IPF container. 2021-12-25 17:06:47 -05:00
Thomas Harte
6c606b5506 Fix through route to TargetPlatform::TypeDistinguisher. 2021-12-25 17:06:12 -05:00
Thomas Harte
55dbeefeb2 Merge pull request #1005 from TomHarte/SerialPort
Adds empty callouts for all serial port registers.
2021-12-25 16:39:27 -05:00
Thomas Harte
4d9589af7c Merge pull request #1006 from TomHarte/Shared68000Tables
Minor 68000 style improvements.
2021-12-25 14:11:25 -05:00
Thomas Harte
ee625cb8a8 Minor style improvements; especially: don't assume value of NoBusProgram. 2021-12-25 14:05:38 -05:00
Thomas Harte
f20940a37b Give Program full ownership of the sentinel value.
In case I want to reduce the size of this field later.
2021-12-23 16:32:21 -05:00
Thomas Harte
32e0a66610 Trust the compiler with this bit field. 2021-12-23 16:28:55 -05:00
Thomas Harte
d9598b35c2 Add some additional metrics. 2021-12-23 16:27:54 -05:00
Thomas Harte
acba357df6 Adds empty callouts for all serial port registers. 2021-12-23 15:22:20 -05:00
Thomas Harte
7ce335d9da Merge pull request #1004 from TomHarte/FastRAM
Adds fast RAM to the Amiga, along with size selection for both fast & chip.
2021-12-23 11:43:42 -05:00
Thomas Harte
3caf9ca914 Remove a bunch of unused names. 2021-12-23 11:39:00 -05:00
Thomas Harte
fd569201ef Add Qt GUI for Amiga memory selection. 2021-12-23 11:28:44 -05:00
Thomas Harte
f094aa946a Add Mac GUI for Amiga memory selection. 2021-12-22 18:20:55 -05:00
Thomas Harte
a17c192a9e Allow chip RAM size selection, while I'm here. 2021-12-22 15:30:19 -05:00
Thomas Harte
1916a9b99c Remove stdout noise. 2021-12-22 15:22:28 -05:00
Thomas Harte
9796b308dc Add basic implementation of fast RAM. 2021-12-22 15:17:11 -05:00
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
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
353 changed files with 3023011 additions and 8198 deletions

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

@@ -17,8 +17,10 @@ enum class Machine {
AppleIIgs,
Atari2600,
AtariST,
Amiga,
ColecoVision,
Electron,
Enterprise,
Macintosh,
MasterSystem,
MSX,

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,48 @@
//
// 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> {
ReflectableEnum(ChipRAM,
FiveHundredAndTwelveKilobytes,
OneMegabyte,
TwoMegabytes);
ReflectableEnum(FastRAM,
None,
OneMegabyte,
TwoMegabytes,
FourMegabytes,
EightMegabytes);
ChipRAM chip_ram = ChipRAM::FiveHundredAndTwelveKilobytes;
FastRAM fast_ram = FastRAM::EightMegabytes;
Target() : Analyser::Static::Target(Machine::Amiga) {
if(needs_declare()) {
DeclareField(fast_ram);
DeclareField(chip_ram);
AnnounceEnum(FastRAM);
AnnounceEnum(ChipRAM);
}
}
};
}
}
}
#endif /* Analyser_Static_Amiga_Target_h */

View File

@@ -29,7 +29,7 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
EightMB
);
Model model = Model::ROM03;
Model model = Model::ROM01;
MemoryModel memory_model = MemoryModel::EightMB;
Target() : Analyser::Static::Target(Machine::AppleIIgs) {

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

@@ -15,6 +15,7 @@
// Analysers
#include "Acorn/StaticAnalyser.hpp"
#include "Amiga/StaticAnalyser.hpp"
#include "AmstradCPC/StaticAnalyser.hpp"
#include "AppleII/StaticAnalyser.hpp"
#include "AppleIIgs/StaticAnalyser.hpp"
@@ -23,6 +24,7 @@
#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"
@@ -37,15 +39,17 @@
// 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/IPF.hpp"
#include "../../Storage/Disk/DiskImage/Formats/MacintoshIMG.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"
@@ -55,8 +59,14 @@
// 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"
@@ -73,21 +83,29 @@
using namespace Analyser::Static;
static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::IntType &potential_platforms) {
Media result;
namespace {
std::string get_extension(const std::string &name) {
// Get the extension, if any; it will be assumed that extensions are reliable, so an extension is a broad-phase
// test as to file format.
std::string::size_type final_dot = file_name.find_last_of(".");
if(final_dot == std::string::npos) return result;
std::string extension = file_name.substr(final_dot + 1);
std::string::size_type final_dot = name.find_last_of(".");
if(final_dot == std::string::npos) return name;
std::string extension = name.substr(final_dot + 1);
std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
return extension;
}
}
static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::IntType &potential_platforms) {
Media result;
const std::string extension = get_extension(file_name);
#define InsertInstance(list, instance, platforms) \
list.emplace_back(instance);\
potential_platforms |= platforms;\
TargetPlatform::TypeDistinguisher *distinguisher = dynamic_cast<TargetPlatform::TypeDistinguisher *>(list.back().get());\
if(distinguisher) potential_platforms &= distinguisher->target_platform_type(); \
TargetPlatform::TypeDistinguisher *const distinguisher = dynamic_cast<TargetPlatform::TypeDistinguisher *>(list.back().get());\
if(distinguisher) potential_platforms &= distinguisher->target_platform_type();
#define Insert(list, class, platforms, ...) \
InsertInstance(list, new Storage::class(__VA_ARGS__), platforms);
@@ -113,7 +131,8 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
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
@@ -131,8 +150,9 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
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",
@@ -142,6 +162,11 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
// 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( "ipf",
result.disks,
Disk::DiskImageHolder<Storage::Disk::IPF>,
TargetPlatform::Amiga | TargetPlatform::AtariST | TargetPlatform::AmstradCPC | TargetPlatform::ZXSpectrum) // IPF
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
@@ -199,14 +224,35 @@ Media Analyser::Static::GetMedia(const std::string &file_name) {
TargetList Analyser::Static::GetTargets(const std::string &file_name) {
TargetList targets;
const std::string extension = get_extension(file_name);
// Check whether the file directly identifies a target; if so then just return that.
#define Format(ext, class) \
if(extension == ext) { \
try { \
auto target = Storage::State::class::load(file_name); \
if(target) { \
targets.push_back(std::move(target)); \
return targets; \
} \
} catch(...) {} \
}
Format("sna", SNA);
Format("szx", SZX);
Format("z80", Z80);
#undef TryInsert
// Otherwise:
//
// Collect all disks, tapes ROMs, etc as can be extrapolated from this file, forming the
// union of all platforms this file might be a target for.
TargetPlatform::IntType potential_platforms = 0;
Media media = GetMediaAndPlatforms(file_name, potential_platforms);
// Hand off to platform-specific determination of whether these things are actually compatible and,
// if so, how to load them.
// Hand off to platform-specific determination of whether these
// things are actually compatible and, if so, how to load them.
#define Append(x) if(potential_platforms & TargetPlatform::x) {\
auto new_targets = x::GetTargets(media, file_name, potential_platforms);\
std::move(new_targets.begin(), new_targets.end(), std::back_inserter(targets));\
@@ -215,11 +261,13 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) {
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);

View File

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

View File

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

View File

@@ -11,6 +11,7 @@
#include "ForceInline.hpp"
#include <algorithm>
#include <cstdint>
#include <limits>
@@ -138,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_; }
@@ -213,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);
@@ -222,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,6 +9,7 @@
#ifndef JustInTime_h
#define JustInTime_h
#include "ClockReceiver.hpp"
#include "../Concurrency/AsyncTaskQueue.hpp"
#include "ClockingHintSource.hpp"
#include "ForceInline.hpp"
@@ -103,9 +104,9 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
}
if constexpr (has_sequence_points<T>::value) {
time_until_event_ -= rhs;
time_until_event_ -= rhs * multiplier;
if(time_until_event_ <= LocalTimeScale(0)) {
time_overrun_ = time_until_event_;
time_overrun_ = time_until_event_ / divider;
flush();
update_sequence_point();
return true;
@@ -145,13 +146,21 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
/// @returns the amount of time since the object was last flushed, in the target time scale.
[[nodiscard]] forceinline TargetTimeScale time_since_flush() const {
// TODO: does this handle conversions properly where TargetTimeScale != LocalTimeScale?
if constexpr (divider == 1) {
return time_since_update_;
}
return TargetTimeScale(time_since_update_.as_integral() / divider);
}
/// @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.
@@ -185,7 +194,7 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
/// @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_;
return time_until_event_ / divider;
}
/// Indicates whether a sequence-point-caused flush will occur if the specified period is added.
@@ -196,10 +205,43 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
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) {
return duration;
}
// 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;
// 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) {
time_until_event_ = object_.get_next_sequence_point();
// 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));
}
}

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

@@ -750,7 +750,7 @@ HalfCycles TMS9918::get_next_sequence_point() {
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

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

View File

@@ -9,6 +9,8 @@
#ifndef Apple_RealTimeClock_hpp
#define Apple_RealTimeClock_hpp
#include <array>
namespace Apple {
namespace Clock {
@@ -21,32 +23,36 @@ namespace Clock {
*/
class ClockStorage {
public:
ClockStorage() {
// TODO: this should persist, if possible, rather than
// being default initialised.
constexpr uint8_t default_data[] = {
0xa8, 0x00, 0x00, 0x00,
0xcc, 0x0a, 0xcc, 0x0a,
0x00, 0x00, 0x00, 0x00,
0x00, 0x02, 0x63, 0x00,
0x03, 0x88, 0x00, 0x4c
};
memcpy(data_, default_data, sizeof(default_data));
memset(&data_[sizeof(default_data)], 0xff, sizeof(data_) - sizeof(default_data));
}
ClockStorage() {}
/*!
Advances the clock by 1 second.
The caller should also signal an interrupt.
The caller should also signal an interrupt if applicable.
*/
void update() {
for(int c = 0; c < 4; ++c) {
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;
@@ -92,7 +98,7 @@ class ClockStorage {
case 0x30:
// Either a register access or an extended instruction.
if(command & 0x08) {
address_ = (command & 0x7) << 5;
address_ = unsigned((command & 0x7) << 5);
phase_ = (command & 0x80) ? Phase::SecondAddressByteRead : Phase::SecondAddressByteWrite;
return NoResult;
} else {
@@ -162,10 +168,10 @@ class ClockStorage {
private:
uint8_t data_[256];
uint8_t seconds_[4];
uint8_t write_protect_;
int address_;
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;
@@ -257,7 +263,10 @@ class ParallelClock: public ClockStorage {
// A no-op for now.
} else {
// Write to the RTC. Which in this implementation also sets up a future read.
data_ = uint8_t(perform(data_));
const auto result = perform(data_);
if(result < 0x100) {
data_ = uint8_t(result);
}
}
// MAGIC! The transaction took 0 seconds.

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.

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,53 +11,53 @@
using namespace Concurrency;
AsyncTaskQueue::AsyncTaskQueue()
#ifndef __APPLE__
: should_destruct_(false)
#endif
{
#ifdef __APPLE__
serial_dispatch_queue_ = dispatch_queue_create("com.thomasharte.clocksignal.asyntaskqueue", DISPATCH_QUEUE_SERIAL);
#ifndef USE_GCD
:
should_destruct_(false),
thread_([this] () {
while(!should_destruct_) {
std::function<void(void)> next_function;
// Take lock, check for a new task.
std::unique_lock lock(queue_mutex_);
if(!pending_tasks_.empty()) {
next_function = pending_tasks_.front();
pending_tasks_.pop_front();
}
if(next_function) {
// If there is a task, release lock and perform it.
lock.unlock();
next_function();
} else {
// If there isn't a task, atomically block on the processing condition and release the lock
// until there's something pending (and then release it again via scope).
processing_condition_.wait(lock);
}
}
})
#else
thread_ = std::make_unique<std::thread>([this]() {
while(!should_destruct_) {
std::function<void(void)> next_function;
// Take lock, check for a new task
std::unique_lock lock(queue_mutex_);
if(!pending_tasks_.empty()) {
next_function = pending_tasks_.front();
pending_tasks_.pop_front();
}
if(next_function) {
// If there is a task, release lock and perform it
lock.unlock();
next_function();
} else {
// If there isn't a task, atomically block on the processing condition and release the lock
// until there's something pending (and then release it again via scope)
processing_condition_.wait(lock);
}
}
});
: serial_dispatch_queue_(dispatch_queue_create("com.thomasharte.clocksignal.asyntaskqueue", DISPATCH_QUEUE_SERIAL))
#endif
}
{}
AsyncTaskQueue::~AsyncTaskQueue() {
#ifdef __APPLE__
#ifdef USE_GCD
flush();
dispatch_release(serial_dispatch_queue_);
serial_dispatch_queue_ = nullptr;
#else
// Set should destruct, and then give the thread a bit of a nudge
// via an empty enqueue.
should_destruct_ = true;
enqueue([](){});
thread_->join();
thread_.reset();
// Wait for the thread safely to terminate.
thread_.join();
#endif
}
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>();
@@ -88,18 +88,22 @@ DeferringAsyncTaskQueue::~DeferringAsyncTaskQueue() {
void DeferringAsyncTaskQueue::defer(std::function<void(void)> function) {
if(!deferred_tasks_) {
deferred_tasks_ = std::make_shared<std::list<std::function<void(void)>>>();
deferred_tasks_ = std::make_unique<TaskList>();
}
deferred_tasks_->push_back(function);
}
void DeferringAsyncTaskQueue::perform() {
if(!deferred_tasks_) return;
std::shared_ptr<std::list<std::function<void(void)>>> deferred_tasks = deferred_tasks_;
deferred_tasks_.reset();
enqueue([deferred_tasks] {
enqueue([deferred_tasks_raw = deferred_tasks_.release()] {
std::unique_ptr<TaskList> deferred_tasks(deferred_tasks_raw);
for(const auto &function : *deferred_tasks) {
function();
}
});
}
void DeferringAsyncTaskQueue::flush() {
perform();
AsyncTaskQueue::flush();
}

View File

@@ -16,12 +16,15 @@
#include <memory>
#include <thread>
#ifdef __APPLE__
#if defined(__APPLE__) && !defined(IGNORE_APPLE)
#include <dispatch/dispatch.h>
#define USE_GCD
#endif
namespace Concurrency {
using TaskList = std::list<std::function<void(void)>>;
/*!
An async task queue allows a caller to enqueue void(void) functions. Those functions are guaranteed
to be performed serially and asynchronously from the caller. A caller may also request to flush,
@@ -47,15 +50,15 @@ class AsyncTaskQueue {
void flush();
private:
#ifdef __APPLE__
#ifdef USE_GCD
dispatch_queue_t serial_dispatch_queue_;
#else
std::unique_ptr<std::thread> thread_;
std::mutex queue_mutex_;
std::list<std::function<void(void)>> pending_tasks_;
std::condition_variable processing_condition_;
std::atomic_bool should_destruct_;
std::condition_variable processing_condition_;
std::mutex queue_mutex_;
TaskList pending_tasks_;
std::thread thread_;
#endif
};
@@ -86,10 +89,13 @@ class DeferringAsyncTaskQueue: public AsyncTaskQueue {
*/
void perform();
/*!
Blocks the caller until all previously-enqueud functions have completed.
*/
void flush();
private:
// TODO: this is a shared_ptr because of the issues capturing moveables in C++11;
// switch to a unique_ptr if/when adapting to C++14
std::shared_ptr<std::list<std::function<void(void)>>> deferred_tasks_;
std::unique_ptr<TaskList> deferred_tasks_;
};
}

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

@@ -9,7 +9,7 @@
#ifndef CachingExecutor_hpp
#define CachingExecutor_hpp
#include "Sizes.hpp"
#include "../Numeric/Sizes.hpp"
#include <array>
#include <cstdint>

View File

@@ -9,7 +9,7 @@
#ifndef Disassembler_hpp
#define Disassembler_hpp
#include "Sizes.hpp"
#include "../Numeric/Sizes.hpp"
#include <list>
#include <map>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,113 @@
//
// Decoder.hpp
// Clock Signal
//
// Created by Thomas Harte on 10/04/2022.
// Copyright © 2022 Thomas Harte. All rights reserved.
//
#ifndef InstructionSets_M68k_Decoder_hpp
#define InstructionSets_M68k_Decoder_hpp
#include "Instruction.hpp"
#include "Model.hpp"
namespace InstructionSet {
namespace M68k {
/*!
A stateless decoder that can map from instruction words to preinstructions
(i.e. enough to know the operation and size, and either know the addressing mode
and registers or else know how many further extension words are needed).
WARNING: at present this handles the original 68000 instruction set only. It
requires a model only for the sake of not baking in assumptions about MOVE SR, etc,
and supporting extended addressing modes in some cases.
But it does not yet decode any operations which were not present on the 68000.
*/
template <Model model> class Predecoder {
public:
Preinstruction decode(uint16_t instruction);
private:
// Page by page decoders; each gets a bit ad hoc so
// it is neater to separate them.
Preinstruction decode0(uint16_t instruction);
Preinstruction decode1(uint16_t instruction);
Preinstruction decode2(uint16_t instruction);
Preinstruction decode3(uint16_t instruction);
Preinstruction decode4(uint16_t instruction);
Preinstruction decode5(uint16_t instruction);
Preinstruction decode6(uint16_t instruction);
Preinstruction decode7(uint16_t instruction);
Preinstruction decode8(uint16_t instruction);
Preinstruction decode9(uint16_t instruction);
Preinstruction decodeA(uint16_t instruction);
Preinstruction decodeB(uint16_t instruction);
Preinstruction decodeC(uint16_t instruction);
Preinstruction decodeD(uint16_t instruction);
Preinstruction decodeE(uint16_t instruction);
Preinstruction decodeF(uint16_t instruction);
using OpT = uint8_t;
// Specific instruction decoders.
template <OpT operation, bool validate = true> Preinstruction decode(uint16_t instruction);
template <OpT operation, bool validate> Preinstruction validated(
AddressingMode op1_mode = AddressingMode::None, int op1_reg = 0,
AddressingMode op2_mode = AddressingMode::None, int op2_reg = 0,
Condition condition = Condition::True
);
template <uint8_t op> uint32_t invalid_operands();
// Extended operation list; collapses into a single byte enough information to
// know both the type of operation and how to decode the operands. Most of the
// time that's knowable from the Operation alone, hence the rather awkward
// extension of @c Operation.
enum ExtendedOperation: OpT {
MOVEPtoRl = uint8_t(Operation::Max) + 1, MOVEPtoRw,
MOVEPtoMl, MOVEPtoMw,
MOVEQ,
ADDQb, ADDQw, ADDQl,
ADDQAw, ADDQAl,
SUBQb, SUBQw, SUBQl,
SUBQAw, SUBQAl,
ADDIb, ADDIw, ADDIl,
ORIb, ORIw, ORIl,
SUBIb, SUBIw, SUBIl,
ANDIb, ANDIw, ANDIl,
EORIb, EORIw, EORIl,
CMPIb, CMPIw, CMPIl,
BTSTI, BCHGI, BCLRI, BSETI,
CMPMb, CMPMw, CMPMl,
MOVEq,
ADDtoMb, ADDtoMw, ADDtoMl,
ADDtoRb, ADDtoRw, ADDtoRl,
SUBtoMb, SUBtoMw, SUBtoMl,
SUBtoRb, SUBtoRw, SUBtoRl,
ANDtoMb, ANDtoMw, ANDtoMl,
ANDtoRb, ANDtoRw, ANDtoRl,
ORtoMb, ORtoMw, ORtoMl,
ORtoRb, ORtoRw, ORtoRl,
EXGRtoR, EXGAtoA, EXGRtoA,
};
static constexpr Operation operation(OpT op);
};
}
}
#endif /* InstructionSets_M68k_Decoder_hpp */

View File

@@ -0,0 +1,50 @@
//
// ExceptionVectors.hpp
// Clock Signal
//
// Created by Thomas Harte on 10/05/2022.
// Copyright © 2022 Thomas Harte. All rights reserved.
//
#ifndef InstructionSets_M68k_ExceptionVectors_hpp
#define InstructionSets_M68k_ExceptionVectors_hpp
namespace InstructionSet {
namespace M68k {
enum Exception {
InitialStackPointer = 0,
InitialProgramCounter = 1,
AccessFault = 2,
AddressError = 3,
IllegalInstruction = 4,
IntegerDivideByZero = 5,
CHK = 6,
TRAPV = 7,
PrivilegeViolation = 8,
Trace = 9,
Line1010 = 10,
Line1111 = 11,
CoprocessorProtocolViolation = 13,
FormatError = 14,
UninitialisedInterrupt = 15,
SpuriousInterrupt = 24,
InterruptAutovectorBase = 25, // This is the vector for interrupt level _1_.
TrapBase = 32,
FPBranchOrSetOnUnorderedCondition = 48,
FPInexactResult = 49,
FPDivideByZero = 50,
FPUnderflow = 51,
FPOperandError = 52,
FPOverflow = 53,
FPSignallingNAN = 54,
FPUnimplementedDataType = 55,
MMUConfigurationError = 56,
MMUIllegalOperationError = 57,
MMUAccessLevelViolationError = 58,
};
}
}
#endif /* InstructionSets_M68k_ExceptionVectors_hpp */

View File

@@ -0,0 +1,171 @@
//
// Executor.hpp
// Clock Signal
//
// Created by Thomas Harte on 29/04/2022.
// Copyright © 2022 Thomas Harte. All rights reserved.
//
#ifndef InstructionSets_M68k_Executor_hpp
#define InstructionSets_M68k_Executor_hpp
#include "Decoder.hpp"
#include "Instruction.hpp"
#include "Model.hpp"
#include "Perform.hpp"
#include "RegisterSet.hpp"
#include "Status.hpp"
namespace InstructionSet {
namespace M68k {
/// Maps the 68k function codes such that bits 0, 1 and 2 represent
/// FC0, FC1 and FC2 respectively.
enum class FunctionCode {
UserData = 0b001,
UserProgram = 0b010,
SupervisorData = 0b101,
SupervisorProgram = 0b110,
InterruptAcknowledge = 0b111,
};
/// The Executor is templated on a class that implements bus handling as defined below;
/// the bus handler is responsible for all reads and writes, and will also receive resets and
/// interrupt acknowledgements.
///
/// The executor will provide 32-bit addresses and act as if it had a 32-bit data bus, even
/// if interpretting the original 68000 instruction set.
struct BusHandler {
/// Write @c value of type/size @c IntT to @c address with the processor signalling
/// a FunctionCode of @c function. @c IntT will be one of @c uint8_t, @c uint16_t
/// or @c uint32_t.
template <typename IntT> void write(uint32_t address, IntT value, FunctionCode function);
/// Read and return a value of type/size @c IntT from @c address with the processor signalling
/// a FunctionCode of @c function. @c IntT will be one of @c uint8_t, @c uint16_t
/// or @c uint32_t.
template <typename IntT> IntT read(uint32_t address, FunctionCode function);
/// React to the processor programmatically strobing its RESET output.
void reset();
/// Respond to an interrupt acknowledgement at @c interrupt_level from the processor.
/// Should return @c -1 in order to trigger autovectoring, or the appropriate exception vector
/// number otherwise.
///
/// It is undefined behaviour to return a number greater than 255.
int acknowlege_interrupt(int interrupt_level);
};
/// Ties together the decoder, sequencer and performer to provide an executor for 680x0 instruction streams.
/// As is standard for these executors, no bus- or cache-level fidelity to any real 680x0 is attempted. This is
/// simply an executor of 680x0 code.
template <Model model, typename BusHandler> class Executor {
public:
Executor(BusHandler &);
/// Reset the processor, back to a state as if just externally reset.
void reset();
/// Executes the number of instructions specified;
/// other events — such as initial reset or branching
/// to exceptions — may be zero costed, and interrupts
/// will not necessarily take effect immediately when signalled.
void run_for_instructions(int);
/// Call this at any time to interrupt processing with a bus error;
/// the function code and address must be provided. Internally
/// this will raise a C++ exception, and therefore doesn't return.
[[noreturn]] void signal_bus_error(FunctionCode, uint32_t address);
/// Sets the current input interrupt level.
void set_interrupt_level(int);
// State for the executor is just the register set.
RegisterSet get_state();
void set_state(const RegisterSet &);
private:
class State: public NullFlowController {
public:
State(BusHandler &handler) : bus_handler_(handler) {}
void run(int &);
bool stopped = false;
void read(DataSize size, uint32_t address, CPU::SlicedInt32 &value);
void write(DataSize size, uint32_t address, CPU::SlicedInt32 value);
template <typename IntT> IntT read(uint32_t address, bool is_from_pc = false);
template <typename IntT> void write(uint32_t address, IntT value);
template <typename IntT> IntT read_pc();
// Processor state.
Status status;
CPU::SlicedInt32 program_counter;
CPU::SlicedInt32 registers[16]; // D0D7 followed by A0A7.
CPU::SlicedInt32 stack_pointers[2];
uint32_t instruction_address;
uint16_t instruction_opcode;
// Things that are ephemerally duplicative of Status.
int active_stack_pointer = 0;
Status::FlagT should_trace = 0;
// Bus state.
int interrupt_input = 0;
// A lookup table to ensure that A7 is adjusted by 2 rather than 1 in
// postincrement and predecrement mode.
static constexpr uint32_t byte_increments[] = {
1, 1, 1, 1, 1, 1, 1, 2
};
// Flow control; Cf. Perform.hpp.
template <bool use_current_instruction_pc = true> void raise_exception(int);
void did_update_status();
template <typename IntT> void complete_bcc(bool matched_condition, IntT offset);
void complete_dbcc(bool matched_condition, bool overflowed, int16_t offset);
void bsr(uint32_t offset);
void jmp(uint32_t);
void jsr(uint32_t offset);
void rtr();
void rts();
void rte();
void stop();
void reset();
void link(Preinstruction instruction, uint32_t offset);
void unlink(uint32_t &address);
void pea(uint32_t address);
void move_to_usp(uint32_t address);
void move_from_usp(uint32_t &address);
template <typename IntT> void movep(Preinstruction instruction, uint32_t source, uint32_t dest);
template <typename IntT> void movem_toM(Preinstruction instruction, uint32_t source, uint32_t dest);
template <typename IntT> void movem_toR(Preinstruction instruction, uint32_t source, uint32_t dest);
void tas(Preinstruction instruction, uint32_t address);
private:
BusHandler &bus_handler_;
Predecoder<model> decoder_;
struct EffectiveAddress {
CPU::SlicedInt32 value;
bool requires_fetch;
};
EffectiveAddress calculate_effective_address(Preinstruction instruction, uint16_t opcode, int index);
uint32_t index_8bitdisplacement();
} state_;
};
}
}
#include "Implementation/ExecutorImplementation.hpp"
#endif /* InstructionSets_M68k_Executor_hpp */

View File

@@ -0,0 +1,695 @@
//
// ExecutorImplementation.hpp
// Clock Signal
//
// Created by Thomas Harte on 01/05/2022.
// Copyright © 2022 Thomas Harte. All rights reserved.
//
#ifndef InstructionSets_M68k_ExecutorImplementation_hpp
#define InstructionSets_M68k_ExecutorImplementation_hpp
#include "../Perform.hpp"
#include "../ExceptionVectors.hpp"
#include <cassert>
namespace InstructionSet {
namespace M68k {
#define An(x) state_.registers[8 + x]
#define Dn(x) state_.registers[x]
#define sp An(7)
#define AccessException(code, address, vector) \
uint64_t(((vector) << 8) | uint64_t(code) | ((address) << 16))
// MARK: - Executor itself.
template <Model model, typename BusHandler>
Executor<model, BusHandler>::Executor(BusHandler &handler) : state_(handler) {
reset();
}
template <Model model, typename BusHandler>
void Executor<model, BusHandler>::reset() {
// Establish: supervisor state, all interrupts blocked.
state_.status.set_status(0b0010'0011'1000'0000);
state_.did_update_status();
// Clear the STOPped state, if currently active.
state_.stopped = false;
// Seed stack pointer and program counter.
sp.l = state_.template read<uint32_t>(0) & 0xffff'fffe;
state_.program_counter.l = state_.template read<uint32_t>(4);
}
template <Model model, typename BusHandler>
void Executor<model, BusHandler>::signal_bus_error(FunctionCode code, uint32_t address) {
throw AccessException(code, address, Exception::AccessFault);
}
template <Model model, typename BusHandler>
void Executor<model, BusHandler>::set_interrupt_level(int level) {
state_.interrupt_input_ = level;
state_.stopped &= !state_.status.would_accept_interrupt(level);
}
template <Model model, typename BusHandler>
void Executor<model, BusHandler>::run_for_instructions(int count) {
if(state_.stopped) return;
while(count > 0) {
try {
state_.run(count);
} catch (uint64_t exception) {
// Potiental source of an exception #1: STOP. Check for that first.
if(state_.stopped) return;
// Unpack the exception; this is the converse of the AccessException macro.
const int vector_address = (exception >> 6) & 0xfc;
const uint16_t code = uint16_t(exception & 0xff);
const uint32_t faulting_address = uint32_t(exception >> 16);
// Grab the status to store, then switch into supervisor mode.
const uint16_t status = state_.status.status();
state_.status.is_supervisor = true;
state_.status.trace_flag = 0;
state_.did_update_status();
// Ensure no tracing occurs into the exception.
state_.should_trace = 0;
// Push status and the program counter at instruction start.
state_.template write<uint16_t>(sp.l - 14, code);
state_.template write<uint32_t>(sp.l - 12, faulting_address);
state_.template write<uint16_t>(sp.l - 8, state_.instruction_opcode);
state_.template write<uint16_t>(sp.l - 6, status);
state_.template write<uint16_t>(sp.l - 4, state_.instruction_address);
sp.l -= 14;
// Fetch the new program counter; reset on a double fault.
try {
state_.program_counter.l = state_.template read<uint32_t>(vector_address);
} catch (uint64_t) {
// TODO: I think this is incorrect, but need to verify consistency
// across different 680x0s.
reset();
}
}
}
}
template <Model model, typename BusHandler>
RegisterSet Executor<model, BusHandler>::get_state() {
RegisterSet result;
for(int c = 0; c < 8; c++) {
result.data[c] = Dn(c).l;
}
for(int c = 0; c < 7; c++) {
result.address[c] = An(c).l;
}
result.status = state_.status.status();
result.program_counter = state_.program_counter.l;
state_.stack_pointers[state_.active_stack_pointer] = sp;
result.user_stack_pointer = state_.stack_pointers[0].l;
result.supervisor_stack_pointer = state_.stack_pointers[1].l;
return result;
}
template <Model model, typename BusHandler>
void Executor<model, BusHandler>::set_state(const RegisterSet &state) {
for(int c = 0; c < 8; c++) {
Dn(c).l = state.data[c];
}
for(int c = 0; c < 7; c++) {
An(c).l = state.address[c];
}
state_.status.set_status(state.status);
state_.did_update_status();
state_.program_counter.l = state.program_counter;
state_.stack_pointers[0].l = state.user_stack_pointer;
state_.stack_pointers[1].l = state.supervisor_stack_pointer;
sp = state_.stack_pointers[state_.active_stack_pointer];
}
#undef Dn
#undef An
// MARK: - State.
#define An(x) registers[8 + x]
#define Dn(x) registers[x]
template <Model model, typename BusHandler>
template <typename IntT>
IntT Executor<model, BusHandler>::State::read(uint32_t address, bool is_from_pc) {
const auto code = FunctionCode((active_stack_pointer << 2) | 1 << int(is_from_pc));
if(model == Model::M68000 && sizeof(IntT) > 1 && address & 1) {
throw AccessException(code, address, Exception::AddressError | (int(is_from_pc) << 3) | (1 << 4));
}
return bus_handler_.template read<IntT>(address, code);
}
template <Model model, typename BusHandler>
template <typename IntT>
void Executor<model, BusHandler>::State::write(uint32_t address, IntT value) {
const auto code = FunctionCode((active_stack_pointer << 2) | 1);
if(model == Model::M68000 && sizeof(IntT) > 1 && address & 1) {
throw AccessException(code, address, Exception::AddressError);
}
bus_handler_.template write<IntT>(address, value, code);
}
template <Model model, typename BusHandler>
void Executor<model, BusHandler>::State::read(DataSize size, uint32_t address, CPU::SlicedInt32 &value) {
switch(size) {
case DataSize::Byte: value.b = read<uint8_t>(address); break;
case DataSize::Word: value.w = read<uint16_t>(address); break;
case DataSize::LongWord: value.l = read<uint32_t>(address); break;
}
}
template <Model model, typename BusHandler>
void Executor<model, BusHandler>::State::write(DataSize size, uint32_t address, CPU::SlicedInt32 value) {
switch(size) {
case DataSize::Byte: write<uint8_t>(address, value.b); break;
case DataSize::Word: write<uint16_t>(address, value.w); break;
case DataSize::LongWord: write<uint32_t>(address, value.l); break;
}
}
template <Model model, typename BusHandler>
template <typename IntT> IntT Executor<model, BusHandler>::State::read_pc() {
const IntT result = read<IntT>(program_counter.l, true);
if constexpr (sizeof(IntT) == 4) {
program_counter.l += 4;
} else {
program_counter.l += 2;
}
return result;
}
template <Model model, typename BusHandler>
uint32_t Executor<model, BusHandler>::State::index_8bitdisplacement() {
// TODO: if not a 68000, check bit 8 for whether this should be a full extension word;
// also include the scale field even if not.
const auto extension = read_pc<uint16_t>();
const auto offset = int8_t(extension);
const int register_index = (extension >> 12) & 15;
const uint32_t displacement = registers[register_index].l;
const uint32_t sized_displacement = (extension & 0x800) ? displacement : int16_t(displacement);
return offset + sized_displacement;
}
template <Model model, typename BusHandler>
typename Executor<model, BusHandler>::State::EffectiveAddress
Executor<model, BusHandler>::State::calculate_effective_address(Preinstruction instruction, uint16_t opcode, int index) {
EffectiveAddress ea;
switch(instruction.mode(index)) {
case AddressingMode::None:
// Permit an uninitialised effective address to be returned;
// this value shouldn't be used.
break;
//
// Operands that don't have effective addresses, which are returned as values.
//
case AddressingMode::DataRegisterDirect:
case AddressingMode::AddressRegisterDirect:
ea.value = registers[instruction.lreg(index)];
ea.requires_fetch = false;
break;
case AddressingMode::Quick:
ea.value.l = quick(opcode, instruction.operation);
ea.requires_fetch = false;
break;
case AddressingMode::ImmediateData:
switch(instruction.operand_size()) {
case DataSize::Byte:
ea.value.l = read_pc<uint16_t>() & 0xff;
break;
case DataSize::Word:
ea.value.l = read_pc<uint16_t>();
break;
case DataSize::LongWord:
ea.value.l = read_pc<uint32_t>();
break;
}
ea.requires_fetch = false;
break;
//
// Absolute addresses.
//
case AddressingMode::AbsoluteShort:
ea.value.l = int16_t(read_pc<uint16_t>());
ea.requires_fetch = true;
break;
case AddressingMode::AbsoluteLong:
ea.value.l = read_pc<uint32_t>();
ea.requires_fetch = true;
break;
//
// Address register indirects.
//
case AddressingMode::AddressRegisterIndirect:
ea.value = An(instruction.reg(index));
ea.requires_fetch = true;
break;
case AddressingMode::AddressRegisterIndirectWithPostincrement: {
const auto reg = instruction.reg(index);
ea.value = An(reg);
ea.requires_fetch = true;
switch(instruction.operand_size()) {
case DataSize::Byte: An(reg).l += byte_increments[reg]; break;
case DataSize::Word: An(reg).l += 2; break;
case DataSize::LongWord: An(reg).l += 4; break;
}
} break;
case AddressingMode::AddressRegisterIndirectWithPredecrement: {
const auto reg = instruction.reg(index);
switch(instruction.operand_size()) {
case DataSize::Byte: An(reg).l -= byte_increments[reg]; break;
case DataSize::Word: An(reg).l -= 2; break;
case DataSize::LongWord: An(reg).l -= 4; break;
}
ea.value = An(reg);
ea.requires_fetch = true;
} break;
case AddressingMode::AddressRegisterIndirectWithDisplacement:
ea.value.l = An(instruction.reg(index)).l + int16_t(read_pc<uint16_t>());
ea.requires_fetch = true;
break;
case AddressingMode::AddressRegisterIndirectWithIndex8bitDisplacement:
ea.value.l = An(instruction.reg(index)).l + index_8bitdisplacement();
ea.requires_fetch = true;
break;
//
// PC-relative addresses.
//
case AddressingMode::ProgramCounterIndirectWithDisplacement:
ea.value.l = program_counter.l + int16_t(read_pc<uint16_t>());
ea.requires_fetch = true;
break;
case AddressingMode::ProgramCounterIndirectWithIndex8bitDisplacement:
ea.value.l = program_counter.l + index_8bitdisplacement();
ea.requires_fetch = true;
break;
default:
assert(false);
}
return ea;
}
template <Model model, typename BusHandler>
void Executor<model, BusHandler>::State::run(int &count) {
while(count--) {
// Check for a new interrupt.
if(status.would_accept_interrupt(interrupt_input)) {
const int vector = bus_handler_.acknowlege_interrupt(interrupt_input);
if(vector >= 0) {
raise_exception<false>(vector);
} else {
raise_exception<false>(Exception::InterruptAutovectorBase - 1 + interrupt_input);
}
status.interrupt_level = interrupt_input;
}
// Capture the trace bit, indicating whether to trace
// after this instruction.
//
// If an exception occurs, this value will be cleared, but
// it'll persist across mere status register changes for
// one instruction's duration.
should_trace = status.trace_flag;
// Read the next instruction.
instruction_address = program_counter.l;
instruction_opcode = read_pc<uint16_t>();
const Preinstruction instruction = decoder_.decode(instruction_opcode);
if(instruction.requires_supervisor() && !status.is_supervisor) {
raise_exception(Exception::PrivilegeViolation);
continue;
}
if(instruction.operation == Operation::Undefined) {
switch(instruction_opcode & 0xf000) {
default:
raise_exception(Exception::IllegalInstruction);
continue;
case 0xa000:
raise_exception(Exception::Line1010);
continue;
case 0xf000:
raise_exception(Exception::Line1111);
continue;
}
}
// Temporary storage.
CPU::SlicedInt32 operand_[2];
EffectiveAddress effective_address_[2];
// Calculate effective addresses; copy 'addresses' into the
// operands by default both: (i) because they might be values,
// rather than addresses; and (ii) then they'll be there for use
// by LEA and PEA.
effective_address_[0] = calculate_effective_address(instruction, instruction_opcode, 0);
effective_address_[1] = calculate_effective_address(instruction, instruction_opcode, 1);
operand_[0] = effective_address_[0].value;
operand_[1] = effective_address_[1].value;
// Obtain the appropriate sequence.
const auto flags = operand_flags<model>(instruction.operation);
#define fetch_operand(n) \
if(effective_address_[n].requires_fetch) { \
read(instruction.operand_size(), effective_address_[n].value.l, operand_[n]); \
}
if(flags & FetchOp1) { fetch_operand(0); }
if(flags & FetchOp2) { fetch_operand(1); }
#undef fetch_operand
perform<model>(instruction, operand_[0], operand_[1], status, *this);
#define store_operand(n) \
if(!effective_address_[n].requires_fetch) { \
registers[instruction.lreg(n)] = operand_[n]; \
} else { \
write(instruction.operand_size(), effective_address_[n].value.l, operand_[n]); \
}
if(flags & StoreOp1) { store_operand(0); }
if(flags & StoreOp2) { store_operand(1); }
#undef store_operand
// If the trace bit was set, trigger the trace exception.
if(should_trace) {
raise_exception<false>(Exception::Trace);
}
}
}
// MARK: - Flow Control.
template <Model model, typename BusHandler>
template <bool use_current_instruction_pc>
void Executor<model, BusHandler>::State::raise_exception(int index) {
const uint32_t address = index << 2;
// Grab the status to store, then switch into supervisor mode
// and disable tracing.
const uint16_t previous_status = status.status();
status.is_supervisor = true;
status.trace_flag = 0;
did_update_status();
// Push status and the program counter at instruction start.
write<uint32_t>(sp.l - 4, use_current_instruction_pc ? instruction_address : program_counter.l);
write<uint16_t>(sp.l - 6, previous_status);
sp.l -= 6;
// Ensure no tracing occurs into the exception.
should_trace = 0;
// Fetch the new program counter.
program_counter.l = read<uint32_t>(address);
}
template <Model model, typename BusHandler>
void Executor<model, BusHandler>::State::did_update_status() {
// Shuffle the stack pointers.
stack_pointers[active_stack_pointer] = sp;
sp = stack_pointers[int(status.is_supervisor)];
active_stack_pointer = int(status.is_supervisor);
}
template <Model model, typename BusHandler>
void Executor<model, BusHandler>::State::stop() {
stopped = true;
// Raise an exception to exit the run loop; it doesn't matter
// what value is used as long as it is a uint64_t, so 0 will do.
throw uint64_t();
}
template <Model model, typename BusHandler>
void Executor<model, BusHandler>::State::reset() {
bus_handler_.reset();
}
template <Model model, typename BusHandler>
void Executor<model, BusHandler>::State::jmp(uint32_t address) {
program_counter.l = address;
}
template <Model model, typename BusHandler>
template <typename IntT> void Executor<model, BusHandler>::State::complete_bcc(bool branch, IntT offset) {
if(branch) {
program_counter.l = instruction_address + offset + 2;
}
}
template <Model model, typename BusHandler>
void Executor<model, BusHandler>::State::complete_dbcc(bool matched_condition, bool overflowed, int16_t offset) {
if(!matched_condition && !overflowed) {
program_counter.l = instruction_address + offset + 2;
}
}
template <Model model, typename BusHandler>
void Executor<model, BusHandler>::State::bsr(uint32_t offset) {
sp.l -= 4;
write<uint32_t>(sp.l, program_counter.l);
program_counter.l = instruction_address + offset + 2;
}
template <Model model, typename BusHandler>
void Executor<model, BusHandler>::State::jsr(uint32_t address) {
sp.l -= 4;
write<uint32_t>(sp.l, program_counter.l);
program_counter.l = address;
}
template <Model model, typename BusHandler>
void Executor<model, BusHandler>::State::link(Preinstruction instruction, uint32_t offset) {
const auto reg = 8 + instruction.reg<0>();
sp.l -= 4;
write<uint32_t>(sp.l, Dn(reg).l);
Dn(reg) = sp;
sp.l += offset;
}
template <Model model, typename BusHandler>
void Executor<model, BusHandler>::State::unlink(uint32_t &address) {
sp.l = address;
address = read<uint32_t>(sp.l);
sp.l += 4;
}
template <Model model, typename BusHandler>
void Executor<model, BusHandler>::State::pea(uint32_t address) {
sp.l -= 4;
write<uint32_t>(sp.l, address);
}
template <Model model, typename BusHandler>
void Executor<model, BusHandler>::State::rtr() {
status.set_ccr(read<uint16_t>(sp.l));
sp.l += 2;
rts();
}
template <Model model, typename BusHandler>
void Executor<model, BusHandler>::State::rte() {
status.set_status(read<uint16_t>(sp.l));
sp.l += 2;
rts();
}
template <Model model, typename BusHandler>
void Executor<model, BusHandler>::State::rts() {
program_counter.l = read<uint32_t>(sp.l);
sp.l += 4;
}
template <Model model, typename BusHandler>
void Executor<model, BusHandler>::State::tas(Preinstruction instruction, uint32_t address) {
uint8_t value;
if(instruction.mode<0>() != AddressingMode::DataRegisterDirect) {
value = read<uint8_t>(address);
write<uint8_t>(address, value | 0x80);
} else {
value = uint8_t(address);
Dn(instruction.reg<0>()).b = uint8_t(address | 0x80);
}
status.overflow_flag = status.carry_flag = 0;
status.zero_result = value;
status.negative_flag = value & 0x80;
}
template <Model model, typename BusHandler>
void Executor<model, BusHandler>::State::move_to_usp(uint32_t address) {
stack_pointers[0].l = address;
}
template <Model model, typename BusHandler>
void Executor<model, BusHandler>::State::move_from_usp(uint32_t &address) {
address = stack_pointers[0].l;
}
template <Model model, typename BusHandler>
template <typename IntT>
void Executor<model, BusHandler>::State::movep(Preinstruction instruction, uint32_t source, uint32_t dest) {
if(instruction.mode<0>() == AddressingMode::DataRegisterDirect) {
// Move register to memory.
const uint32_t reg = source;
uint32_t address = dest;
if constexpr (sizeof(IntT) == 4) {
write<uint8_t>(address, uint8_t(reg >> 24));
address += 2;
write<uint8_t>(address, uint8_t(reg >> 16));
address += 2;
}
write<uint8_t>(address, uint8_t(reg >> 8));
address += 2;
write<uint8_t>(address, uint8_t(reg));
} else {
// Move memory to register.
uint32_t &reg = Dn(instruction.reg<1>()).l;
uint32_t address = source;
if constexpr (sizeof(IntT) == 4) {
reg = read<uint8_t>(address) << 24;
address += 2;
reg |= read<uint8_t>(address) << 16;
address += 2;
} else {
reg &= 0xffff0000;
}
reg |= read<uint8_t>(address) << 8;
address += 2;
reg |= read<uint8_t>(address);
}
}
template <Model model, typename BusHandler>
template <typename IntT>
void Executor<model, BusHandler>::State::movem_toM(Preinstruction instruction, uint32_t source, uint32_t dest) {
// Move registers to memory. This is the only permitted use of the predecrement mode,
// which reverses output order.
if(instruction.mode<1>() == AddressingMode::AddressRegisterIndirectWithPredecrement) {
// The structure of the code in the mainline part of the executor is such
// that the address register will already have been predecremented before
// reaching here, and it'll have been by two bytes per the operand size
// rather than according to the instruction size. That's not wanted, so undo it.
//
// TODO: with the caveat that the 68020+ have different behaviour:
//
// "For the MC68020, MC68030, MC68040, and CPU32, if the addressing register is also
// moved to memory, the value written is the initial register value decremented by the
// size of the operation. The MC68000 and MC68010 write the initial register value
// (not decremented)."
An(instruction.reg<1>()).l += 2;
uint32_t address = An(instruction.reg<1>()).l;
int index = 15;
while(source) {
if(source & 1) {
address -= sizeof(IntT);
write<IntT>(address, IntT(registers[index].l));
}
--index;
source >>= 1;
}
An(instruction.reg<1>()).l = address;
return;
}
int index = 0;
while(source) {
if(source & 1) {
write<IntT>(dest, IntT(registers[index].l));
dest += sizeof(IntT);
}
++index;
source >>= 1;
}
}
template <Model model, typename BusHandler>
template <typename IntT>
void Executor<model, BusHandler>::State::movem_toR(Preinstruction instruction, uint32_t source, uint32_t dest) {
// Move memory to registers.
//
// A 68000 convention has been broken here; the instruction form is:
// MOVEM <ea>, #
// ... but the instruction is encoded as [MOVEM] [#] [ea].
//
// This project's decoder decodes as #, <ea>.
int index = 0;
while(source) {
if(source & 1) {
if constexpr (sizeof(IntT) == 2) {
registers[index].l = int16_t(read<uint16_t>(dest));
} else {
registers[index].l = read<uint32_t>(dest);
}
dest += sizeof(IntT);
}
++index;
source >>= 1;
}
if(instruction.mode<1>() == AddressingMode::AddressRegisterIndirectWithPostincrement) {
// "If the effective address is specified by the postincrement mode ...
// [i]f the addressing register is also loaded from memory, the memory value is
// ignored and the register is written with the postincremented effective address."
An(instruction.reg<1>()).l = dest;
}
}
#undef sp
#undef Dn
#undef An
#undef AccessException
}
}
#endif /* InstructionSets_M68k_ExecutorImplementation_hpp */

View File

@@ -0,0 +1,146 @@
//
// InstructionOperandFlags.hpp
// Clock Signal
//
// Created by Thomas Harte on 09/05/2022.
// Copyright © 2022 Thomas Harte. All rights reserved.
//
#ifndef InstructionSets_68k_InstructionOperandFlags_hpp
#define InstructionSets_68k_InstructionOperandFlags_hpp
namespace InstructionSet {
namespace M68k {
template <Model model, Operation t_operation> constexpr uint8_t operand_flags(Operation r_operation) {
switch((t_operation != Operation::Undefined) ? t_operation : r_operation) {
default:
assert(false);
//
// No operands are fetched or stored.
// (which means that source and destination will appear as their effective addresses)
//
case Operation::PEA:
case Operation::JMP: case Operation::JSR:
case Operation::MOVEPw: case Operation::MOVEPl:
case Operation::TAS:
case Operation::RTR: case Operation::RTS: case Operation::RTE:
return 0;
//
// Single-operand read.
//
case Operation::MOVEtoSR: case Operation::MOVEtoCCR: case Operation::MOVEtoUSP:
case Operation::ORItoSR: case Operation::ORItoCCR:
case Operation::ANDItoSR: case Operation::ANDItoCCR:
case Operation::EORItoSR: case Operation::EORItoCCR:
case Operation::Bccb: case Operation::Bccw: case Operation::Bccl:
case Operation::BSRb: case Operation::BSRw: case Operation::BSRl:
case Operation::TSTb: case Operation::TSTw: case Operation::TSTl:
case Operation::MOVEMtoMw: case Operation::MOVEMtoMl:
case Operation::MOVEMtoRw: case Operation::MOVEMtoRl:
return FetchOp1;
//
// Single-operand write.
//
case Operation::MOVEfromUSP:
return StoreOp1;
//
// Single-operand read-modify-write.
//
case Operation::NBCD:
case Operation::NOTb: case Operation::NOTw: case Operation::NOTl:
case Operation::NEGb: case Operation::NEGw: case Operation::NEGl:
case Operation::NEGXb: case Operation::NEGXw: case Operation::NEGXl:
case Operation::EXTbtow: case Operation::EXTwtol:
case Operation::SWAP:
case Operation::UNLINK:
case Operation::ASLm: case Operation::ASRm:
case Operation::LSLm: case Operation::LSRm:
case Operation::ROLm: case Operation::RORm:
case Operation::ROXLm: case Operation::ROXRm:
case Operation::Scc:
return FetchOp1 | StoreOp1;
//
// CLR and MOVE SR, which are model-dependent.
//
case Operation::MOVEfromSR:
case Operation::CLRb: case Operation::CLRw: case Operation::CLRl:
if constexpr (model == Model::M68000) {
return FetchOp1 | StoreOp1;
} else {
return StoreOp1;
}
//
// Two-operand; read both.
//
case Operation::CMPb: case Operation::CMPw: case Operation::CMPl:
case Operation::CMPAw: case Operation::CMPAl:
case Operation::CHK:
case Operation::BTST:
case Operation::LINKw:
return FetchOp1 | FetchOp2;
//
// Two-operand; read source, write dest.
//
case Operation::MOVEb: case Operation::MOVEw: case Operation::MOVEl:
case Operation::MOVEAw: case Operation::MOVEAl:
return FetchOp1 | StoreOp2;
//
// Two-operand; read both, write dest.
//
case Operation::ABCD: case Operation::SBCD:
case Operation::ADDb: case Operation::ADDw: case Operation::ADDl:
case Operation::ADDAw: case Operation::ADDAl:
case Operation::ADDXb: case Operation::ADDXw: case Operation::ADDXl:
case Operation::SUBb: case Operation::SUBw: case Operation::SUBl:
case Operation::SUBAw: case Operation::SUBAl:
case Operation::SUBXb: case Operation::SUBXw: case Operation::SUBXl:
case Operation::ORb: case Operation::ORw: case Operation::ORl:
case Operation::ANDb: case Operation::ANDw: case Operation::ANDl:
case Operation::EORb: case Operation::EORw: case Operation::EORl:
case Operation::DIVU: case Operation::DIVS:
case Operation::MULU: case Operation::MULS:
case Operation::ASLb: case Operation::ASLw: case Operation::ASLl:
case Operation::ASRb: case Operation::ASRw: case Operation::ASRl:
case Operation::LSLb: case Operation::LSLw: case Operation::LSLl:
case Operation::LSRb: case Operation::LSRw: case Operation::LSRl:
case Operation::ROLb: case Operation::ROLw: case Operation::ROLl:
case Operation::RORb: case Operation::RORw: case Operation::RORl:
case Operation::ROXLb: case Operation::ROXLw: case Operation::ROXLl:
case Operation::ROXRb: case Operation::ROXRw: case Operation::ROXRl:
case Operation::BCHG:
case Operation::BCLR: case Operation::BSET:
return FetchOp1 | FetchOp2 | StoreOp2;
//
// Two-operand; read both, write source.
//
case Operation::DBcc:
return FetchOp1 | FetchOp2 | StoreOp1;
//
// Two-operand; read both, write both.
//
case Operation::EXG:
return FetchOp1 | FetchOp2 | StoreOp1 | StoreOp2;
//
// Two-operand; just write destination.
//
case Operation::LEA:
return StoreOp2;
}
}
}
}
#endif /* InstructionSets_68k_InstructionOperandFlags_hpp */

View File

@@ -0,0 +1,121 @@
//
// InstructionOperandSize.hpp
// Clock Signal
//
// Created by Thomas Harte on 09/05/2022.
// Copyright © 2022 Thomas Harte. All rights reserved.
//
#ifndef InstructionSets_68k_InstructionOperandSize_hpp
#define InstructionSets_68k_InstructionOperandSize_hpp
namespace InstructionSet {
namespace M68k {
template <Operation t_operation>
constexpr DataSize operand_size(Operation r_operation) {
switch((t_operation == Operation::Undefined) ? r_operation : t_operation) {
// These are given a value arbitrarily, to
// complete the switch statement.
case Operation::Undefined:
case Operation::NOP:
case Operation::STOP:
case Operation::RESET:
case Operation::RTE: case Operation::RTR:
case Operation::TRAP:
case Operation::TRAPV:
case Operation::ABCD: case Operation::SBCD:
case Operation::NBCD:
case Operation::ADDb: case Operation::ADDXb:
case Operation::SUBb: case Operation::SUBXb:
case Operation::MOVEb:
case Operation::ORItoCCR:
case Operation::ANDItoCCR:
case Operation::EORItoCCR:
case Operation::BTST: case Operation::BCLR:
case Operation::BCHG: case Operation::BSET:
case Operation::CMPb: case Operation::TSTb:
case Operation::Bccb: case Operation::BSRb:
case Operation::CLRb:
case Operation::Scc:
case Operation::NEGXb: case Operation::NEGb:
case Operation::ASLb: case Operation::ASRb:
case Operation::LSLb: case Operation::LSRb:
case Operation::ROLb: case Operation::RORb:
case Operation::ROXLb: case Operation::ROXRb:
case Operation::ANDb: case Operation::EORb:
case Operation::NOTb: case Operation::ORb:
case Operation::TAS:
return DataSize::Byte;
case Operation::ADDw: case Operation::ADDAw:
case Operation::ADDXw: case Operation::SUBw:
case Operation::SUBAw: case Operation::SUBXw:
case Operation::MOVEw: case Operation::MOVEAw:
case Operation::ORItoSR:
case Operation::ANDItoSR:
case Operation::EORItoSR:
case Operation::MOVEtoSR:
case Operation::MOVEfromSR:
case Operation::MOVEtoCCR:
case Operation::CMPw: case Operation::CMPAw:
case Operation::TSTw:
case Operation::DBcc:
case Operation::Bccw: case Operation::BSRw:
case Operation::CLRw:
case Operation::NEGXw: case Operation::NEGw:
case Operation::ASLw: case Operation::ASLm:
case Operation::ASRw: case Operation::ASRm:
case Operation::LSLw: case Operation::LSLm:
case Operation::LSRw: case Operation::LSRm:
case Operation::ROLw: case Operation::ROLm:
case Operation::RORw: case Operation::RORm:
case Operation::ROXLw: case Operation::ROXLm:
case Operation::ROXRw: case Operation::ROXRm:
case Operation::MOVEMtoRw:
case Operation::MOVEMtoRl:
case Operation::MOVEMtoMw:
case Operation::MOVEMtoMl:
case Operation::MOVEPw:
case Operation::ANDw: case Operation::EORw:
case Operation::NOTw: case Operation::ORw:
case Operation::DIVU: case Operation::DIVS:
case Operation::MULU: case Operation::MULS:
case Operation::EXTbtow:
case Operation::LINKw:
case Operation::CHK:
return DataSize::Word;
case Operation::ADDl: case Operation::ADDAl:
case Operation::ADDXl: case Operation::SUBl:
case Operation::SUBAl: case Operation::SUBXl:
case Operation::MOVEl: case Operation::MOVEAl:
case Operation::LEA: case Operation::PEA:
case Operation::EXG: case Operation::SWAP:
case Operation::MOVEtoUSP:
case Operation::MOVEfromUSP:
case Operation::CMPl: case Operation::CMPAl:
case Operation::TSTl:
case Operation::JMP: case Operation::JSR:
case Operation::RTS:
case Operation::Bccl: case Operation::BSRl:
case Operation::CLRl:
case Operation::NEGXl: case Operation::NEGl:
case Operation::ASLl: case Operation::ASRl:
case Operation::LSLl: case Operation::LSRl:
case Operation::ROLl: case Operation::RORl:
case Operation::ROXLl: case Operation::ROXRl:
case Operation::MOVEPl:
case Operation::ANDl: case Operation::EORl:
case Operation::NOTl: case Operation::ORl:
case Operation::EXTwtol:
case Operation::UNLINK:
return DataSize::LongWord;
}
}
}
}
#endif /* InstructionSets_68k_InstructionOperandSize_hpp */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,295 @@
//
// Instruction.cpp
// Clock Signal
//
// Created by Thomas Harte on 12/05/2022.
// Copyright © 2022 Thomas Harte. All rights reserved.
//
#include "Instruction.hpp"
#include <cassert>
using namespace InstructionSet::M68k;
std::string Preinstruction::operand_description(int index, int opcode) const {
switch(mode(index)) {
default: assert(false);
case AddressingMode::None:
return "";
case AddressingMode::DataRegisterDirect:
return std::string("D") + std::to_string(reg(index));
case AddressingMode::AddressRegisterDirect:
return std::string("A") + std::to_string(reg(index));
case AddressingMode::AddressRegisterIndirect:
return std::string("(A") + std::to_string(reg(index)) + ")";
case AddressingMode::AddressRegisterIndirectWithPostincrement:
return std::string("(A") + std::to_string(reg(index)) + ")+";
case AddressingMode::AddressRegisterIndirectWithPredecrement:
return std::string("-(A") + std::to_string(reg(index)) + ")";
case AddressingMode::AddressRegisterIndirectWithDisplacement:
return std::string("(d16, A") + std::to_string(reg(index)) + ")";
case AddressingMode::AddressRegisterIndirectWithIndex8bitDisplacement:
return std::string("(d8, A") + std::to_string(reg(index)) + ", Xn)";
case AddressingMode::ProgramCounterIndirectWithDisplacement:
return "(d16, PC)";
case AddressingMode::ProgramCounterIndirectWithIndex8bitDisplacement:
return "(d8, PC, Xn)";
case AddressingMode::AbsoluteShort:
return "(xxx).w";
case AddressingMode::AbsoluteLong:
return "(xxx).l";
case AddressingMode::ImmediateData:
return "#";
case AddressingMode::Quick:
if(opcode == -1) {
return "Q";
}
return std::to_string(int(quick(uint16_t(opcode), operation)));
}
}
std::string Preinstruction::to_string(int opcode) const {
bool flip_operands = false;
const char *instruction;
switch(operation) {
case Operation::Undefined: return "None";
case Operation::NOP: instruction = "NOP"; break;
case Operation::ABCD: instruction = "ABCD"; break;
case Operation::SBCD: instruction = "SBCD"; break;
case Operation::NBCD: instruction = "NBCD"; break;
case Operation::ADDb: instruction = "ADD.b"; break;
case Operation::ADDw: instruction = "ADD.w"; break;
case Operation::ADDl: instruction = "ADD.l"; break;
case Operation::ADDAw:
if(mode<0>() == AddressingMode::Quick) {
instruction = "ADD.w";
} else {
instruction = "ADDA.w";
}
break;
case Operation::ADDAl:
if(mode<0>() == AddressingMode::Quick) {
instruction = "ADD.l";
} else {
instruction = "ADDA.l";
}
break;
case Operation::ADDXb: instruction = "ADDX.b"; break;
case Operation::ADDXw: instruction = "ADDX.w"; break;
case Operation::ADDXl: instruction = "ADDX.l"; break;
case Operation::SUBb: instruction = "SUB.b"; break;
case Operation::SUBw: instruction = "SUB.w"; break;
case Operation::SUBl: instruction = "SUB.l"; break;
case Operation::SUBAw:
if(mode<0>() == AddressingMode::Quick) {
instruction = "SUB.w";
} else {
instruction = "SUBA.w";
}
break;
case Operation::SUBAl:
if(mode<0>() == AddressingMode::Quick) {
instruction = "SUB.l";
} else {
instruction = "SUBA.l";
}
break;
case Operation::SUBXb: instruction = "SUBX.b"; break;
case Operation::SUBXw: instruction = "SUBX.w"; break;
case Operation::SUBXl: instruction = "SUBX.l"; break;
case Operation::MOVEb: instruction = "MOVE.b"; break;
case Operation::MOVEw: instruction = "MOVE.w"; break;
case Operation::MOVEl:
if(mode<0>() == AddressingMode::Quick) {
instruction = "MOVE.q";
} else {
instruction = "MOVE.l";
}
break;
case Operation::MOVEAw: instruction = "MOVEA.w"; break;
case Operation::MOVEAl: instruction = "MOVEA.l"; break;
case Operation::LEA: instruction = "LEA"; break;
case Operation::PEA: instruction = "PEA"; break;
case Operation::MOVEtoSR: instruction = "MOVEtoSR"; break;
case Operation::MOVEfromSR: instruction = "MOVEfromSR"; break;
case Operation::MOVEtoCCR: instruction = "MOVEtoCCR"; break;
case Operation::MOVEtoUSP: instruction = "MOVEtoUSP"; break;
case Operation::MOVEfromUSP: instruction = "MOVEfromUSP"; break;
case Operation::ORItoSR: instruction = "ORItoSR"; break;
case Operation::ORItoCCR: instruction = "ORItoCCR"; break;
case Operation::ANDItoSR: instruction = "ANDItoSR"; break;
case Operation::ANDItoCCR: instruction = "ANDItoCCR"; break;
case Operation::EORItoSR: instruction = "EORItoSR"; break;
case Operation::EORItoCCR: instruction = "EORItoCCR"; break;
case Operation::BTST: instruction = "BTST"; break;
case Operation::BCLR: instruction = "BCLR"; break;
case Operation::BCHG: instruction = "BCHG"; break;
case Operation::BSET: instruction = "BSET"; break;
case Operation::CMPb: instruction = "CMP.b"; break;
case Operation::CMPw: instruction = "CMP.w"; break;
case Operation::CMPl: instruction = "CMP.l"; break;
case Operation::CMPAw: instruction = "CMPA.w"; break;
case Operation::CMPAl: instruction = "CMPA.l"; break;
case Operation::TSTb: instruction = "TST.b"; break;
case Operation::TSTw: instruction = "TST.w"; break;
case Operation::TSTl: instruction = "TST.l"; break;
case Operation::JMP: instruction = "JMP"; break;
case Operation::JSR: instruction = "JSR"; break;
case Operation::RTS: instruction = "RTS"; break;
case Operation::DBcc: instruction = "DBcc"; break;
case Operation::Scc: instruction = "Scc"; break;
case Operation::Bccb:
case Operation::Bccl:
case Operation::Bccw: instruction = "Bcc"; break;
case Operation::BSRb:
case Operation::BSRl:
case Operation::BSRw: instruction = "BSR"; break;
case Operation::CLRb: instruction = "CLR.b"; break;
case Operation::CLRw: instruction = "CLR.w"; break;
case Operation::CLRl: instruction = "CLR.l"; break;
case Operation::NEGXb: instruction = "NEGX.b"; break;
case Operation::NEGXw: instruction = "NEGX.w"; break;
case Operation::NEGXl: instruction = "NEGX.l"; break;
case Operation::NEGb: instruction = "NEG.b"; break;
case Operation::NEGw: instruction = "NEG.w"; break;
case Operation::NEGl: instruction = "NEG.l"; break;
case Operation::ASLb: instruction = "ASL.b"; break;
case Operation::ASLw: instruction = "ASL.w"; break;
case Operation::ASLl: instruction = "ASL.l"; break;
case Operation::ASLm: instruction = "ASL.w"; break;
case Operation::ASRb: instruction = "ASR.b"; break;
case Operation::ASRw: instruction = "ASR.w"; break;
case Operation::ASRl: instruction = "ASR.l"; break;
case Operation::ASRm: instruction = "ASR.w"; break;
case Operation::LSLb: instruction = "LSL.b"; break;
case Operation::LSLw: instruction = "LSL.w"; break;
case Operation::LSLl: instruction = "LSL.l"; break;
case Operation::LSLm: instruction = "LSL.w"; break;
case Operation::LSRb: instruction = "LSR.b"; break;
case Operation::LSRw: instruction = "LSR.w"; break;
case Operation::LSRl: instruction = "LSR.l"; break;
case Operation::LSRm: instruction = "LSR.w"; break;
case Operation::ROLb: instruction = "ROL.b"; break;
case Operation::ROLw: instruction = "ROL.w"; break;
case Operation::ROLl: instruction = "ROL.l"; break;
case Operation::ROLm: instruction = "ROL.w"; break;
case Operation::RORb: instruction = "ROR.b"; break;
case Operation::RORw: instruction = "ROR.w"; break;
case Operation::RORl: instruction = "ROR.l"; break;
case Operation::RORm: instruction = "ROR.w"; break;
case Operation::ROXLb: instruction = "ROXL.b"; break;
case Operation::ROXLw: instruction = "ROXL.w"; break;
case Operation::ROXLl: instruction = "ROXL.l"; break;
case Operation::ROXLm: instruction = "ROXL.w"; break;
case Operation::ROXRb: instruction = "ROXR.b"; break;
case Operation::ROXRw: instruction = "ROXR.w"; break;
case Operation::ROXRl: instruction = "ROXR.l"; break;
case Operation::ROXRm: instruction = "ROXR.w"; break;
case Operation::MOVEMtoMl: instruction = "MOVEM.l"; break;
case Operation::MOVEMtoMw: instruction = "MOVEM.w"; break;
case Operation::MOVEMtoRl:
instruction = "MOVEM.l";
flip_operands = true;
break;
case Operation::MOVEMtoRw:
instruction = "MOVEM.w";
flip_operands = true;
break;
case Operation::MOVEPl: instruction = "MOVEP.l"; break;
case Operation::MOVEPw: instruction = "MOVEP.w"; break;
case Operation::ANDb: instruction = "AND.b"; break;
case Operation::ANDw: instruction = "AND.w"; break;
case Operation::ANDl: instruction = "AND.l"; break;
case Operation::EORb: instruction = "EOR.b"; break;
case Operation::EORw: instruction = "EOR.w"; break;
case Operation::EORl: instruction = "EOR.l"; break;
case Operation::NOTb: instruction = "NOT.b"; break;
case Operation::NOTw: instruction = "NOT.w"; break;
case Operation::NOTl: instruction = "NOT.l"; break;
case Operation::ORb: instruction = "OR.b"; break;
case Operation::ORw: instruction = "OR.w"; break;
case Operation::ORl: instruction = "OR.l"; break;
case Operation::MULU: instruction = "MULU"; break;
case Operation::MULS: instruction = "MULS"; break;
case Operation::DIVU: instruction = "DIVU"; break;
case Operation::DIVS: instruction = "DIVS"; break;
case Operation::RTE: instruction = "RTE"; break;
case Operation::RTR: instruction = "RTR"; break;
case Operation::TRAP: instruction = "TRAP"; break;
case Operation::TRAPV: instruction = "TRAPV"; break;
case Operation::CHK: instruction = "CHK"; break;
case Operation::EXG: instruction = "EXG"; break;
case Operation::SWAP: instruction = "SWAP"; break;
case Operation::TAS: instruction = "TAS"; break;
case Operation::EXTbtow: instruction = "EXT.w"; break;
case Operation::EXTwtol: instruction = "EXT.l"; break;
case Operation::LINKw: instruction = "LINK"; break;
case Operation::UNLINK: instruction = "UNLINK"; break;
case Operation::STOP: instruction = "STOP"; break;
case Operation::RESET: instruction = "RESET"; break;
default:
assert(false);
}
const std::string operand1 = operand_description(0 ^ int(flip_operands), opcode);
const std::string operand2 = operand_description(1 ^ int(flip_operands), opcode);
std::string result = instruction;
if(!operand1.empty()) result += std::string(" ") + operand1;
if(!operand2.empty()) result += std::string(", ") + operand2;
return result;
}

View File

@@ -0,0 +1,357 @@
//
// Instruction.hpp
// Clock Signal
//
// Created by Thomas Harte on 10/04/2022.
// Copyright © 2022 Thomas Harte. All rights reserved.
//
#ifndef InstructionSets_68k_Instruction_hpp
#define InstructionSets_68k_Instruction_hpp
#include "Model.hpp"
#include <cassert>
#include <cstdint>
#include <string>
namespace InstructionSet {
namespace M68k {
enum class Operation: uint8_t {
Undefined,
NOP,
ABCD, SBCD, NBCD,
ADDb, ADDw, ADDl,
ADDAw, ADDAl,
ADDXb, ADDXw, ADDXl,
SUBb, SUBw, SUBl,
SUBAw, SUBAl,
SUBXb, SUBXw, SUBXl,
MOVEb, MOVEw, MOVEl,
MOVEAw, MOVEAl,
LEA, PEA,
MOVEtoSR, MOVEfromSR,
MOVEtoCCR,
MOVEtoUSP, MOVEfromUSP,
ORItoSR, ORItoCCR,
ANDItoSR, ANDItoCCR,
EORItoSR, EORItoCCR,
BTST, BCLR,
BCHG, BSET,
CMPb, CMPw, CMPl,
CMPAw, CMPAl,
TSTb, TSTw, TSTl,
JMP,
JSR, RTS,
DBcc,
Scc,
Bccb, Bccw, Bccl,
BSRb, BSRw, BSRl,
CLRb, CLRw, CLRl,
NEGXb, NEGXw, NEGXl,
NEGb, NEGw, NEGl,
ASLb, ASLw, ASLl, ASLm,
ASRb, ASRw, ASRl, ASRm,
LSLb, LSLw, LSLl, LSLm,
LSRb, LSRw, LSRl, LSRm,
ROLb, ROLw, ROLl, ROLm,
RORb, RORw, RORl, RORm,
ROXLb, ROXLw, ROXLl, ROXLm,
ROXRb, ROXRw, ROXRl, ROXRm,
MOVEMtoRl, MOVEMtoRw,
MOVEMtoMl, MOVEMtoMw,
MOVEPl, MOVEPw,
ANDb, ANDw, ANDl,
EORb, EORw, EORl,
NOTb, NOTw, NOTl,
ORb, ORw, ORl,
MULU, MULS,
DIVU, DIVS,
RTE, RTR,
TRAP, TRAPV,
CHK,
EXG, SWAP,
TAS,
EXTbtow, EXTwtol,
LINKw, UNLINK,
STOP, RESET,
Max = RESET
};
template <Model model>
constexpr bool requires_supervisor(Operation op) {
switch(op) {
case Operation::MOVEfromSR:
if constexpr (model == Model::M68000) {
return false;
}
[[fallthrough]];
case Operation::ORItoSR: case Operation::ANDItoSR:
case Operation::EORItoSR: case Operation::RTE:
case Operation::RESET: case Operation::STOP:
case Operation::MOVEtoUSP: case Operation::MOVEfromUSP:
case Operation::MOVEtoSR:
return true;
default:
return false;
}
}
enum class DataSize {
Byte = 0,
Word = 1,
LongWord = 2,
};
/// Classifies operations by the size of their memory accesses, if any.
///
/// For any operations that don't fit the neat model of reading one or two operands,
/// then writing zero or one, the size determines the data size of the operands only,
/// not any other accesses.
template <Operation t_operation = Operation::Undefined>
constexpr DataSize operand_size(Operation operation = Operation::Undefined);
template <Operation t_op = Operation::Undefined>
constexpr uint32_t quick(uint16_t instruction, Operation r_op = Operation::Undefined) {
switch((t_op != Operation::Undefined) ? t_op : r_op) {
case Operation::Bccb:
case Operation::BSRb:
case Operation::MOVEl: return uint32_t(int8_t(instruction));
case Operation::TRAP: return uint32_t(instruction & 15);
default: {
uint32_t value = (instruction >> 9) & 7;
value |= (value - 1)&8;
return value;
}
}
}
static constexpr uint8_t FetchOp1 = (1 << 0);
static constexpr uint8_t FetchOp2 = (1 << 1);
static constexpr uint8_t StoreOp1 = (1 << 2);
static constexpr uint8_t StoreOp2 = (1 << 3);
/*!
Provides a bitfield with a value in the range 015 indicating which of the provided operation's
operands are accessed via standard fetch and store cycles; the bitfield is composted of
[Fetch/Store]Op[1/2] as defined above.
Unusual bus sequences, such as TAS or MOVEM, are not described here.
*/
template <Model model, Operation t_operation = Operation::Undefined>
constexpr uint8_t operand_flags(Operation r_operation = Operation::Undefined);
/// Lists the various condition codes used by the 680x0.
enum class Condition {
True = 0x00, False = 0x01,
High = 0x02, LowOrSame = 0x03,
CarryClear = 0x04, CarrySet = 0x05,
NotEqual = 0x06, Equal = 0x07,
OverflowClear = 0x08, OverflowSet = 0x09,
Positive = 0x0a, Negative = 0x0b,
GreaterThanOrEqual = 0x0c, LessThan = 0x0d,
GreaterThan = 0x0e, LessThanOrEqual = 0x0f,
};
/// Indicates the addressing mode applicable to an operand.
///
/// Implementation notes:
///
/// Those entries starting 0b00 or 0b01 are mapped as per the 68000's native encoding;
/// those starting 0b00 are those which are indicated directly by a mode field and those starting
/// 0b01 are those which are indicated by a register field given a mode of 0b111. The only minor
/// exception is AddressRegisterDirect, which exists on a 68000 but isn't specifiable by a
/// mode and register, it's contextual based on the instruction.
///
/// Those modes starting in 0b10 are the various extended addressing modes introduced as
/// of the 68020, which can be detected only after interpreting an extension word. At the
/// Preinstruction stage:
///
/// * AddressRegisterIndirectWithIndexBaseDisplacement, MemoryIndirectPostindexed
/// and MemoryIndirectPreindexed will have been partially decoded as
/// AddressRegisterIndirectWithIndex8bitDisplacement; and
/// * ProgramCounterIndirectWithIndexBaseDisplacement,
/// ProgramCounterMemoryIndirectPostindexed and
/// ProgramCounterMemoryIndirectPreindexed will have been partially decoded
/// as ProgramCounterIndirectWithIndex8bitDisplacement.
enum class AddressingMode: uint8_t {
/// No adddressing mode; this operand doesn't exist.
None = 0b01'101,
/// Dn
DataRegisterDirect = 0b00'000,
/// An
AddressRegisterDirect = 0b00'001,
/// (An)
AddressRegisterIndirect = 0b00'010,
/// (An)+
AddressRegisterIndirectWithPostincrement = 0b00'011,
/// -(An)
AddressRegisterIndirectWithPredecrement = 0b00'100,
/// (d16, An)
AddressRegisterIndirectWithDisplacement = 0b00'101,
/// (d8, An, Xn)
AddressRegisterIndirectWithIndex8bitDisplacement = 0b00'110,
/// (bd, An, Xn) [68020+]
AddressRegisterIndirectWithIndexBaseDisplacement = 0b10'000,
/// ([bd, An, Xn], od) [68020+]
MemoryIndirectPostindexed = 0b10'001,
/// ([bd, An], Xn, od) [68020+]
MemoryIndirectPreindexed = 0b10'010,
/// (d16, PC)
ProgramCounterIndirectWithDisplacement = 0b01'010,
/// (d8, PC, Xn)
ProgramCounterIndirectWithIndex8bitDisplacement = 0b01'011,
/// (bd, PC, Xn) [68020+]
ProgramCounterIndirectWithIndexBaseDisplacement = 0b10'011,
/// ([bd, PC, Xn], od) [68020+]
ProgramCounterMemoryIndirectPostindexed = 0b10'100,
/// ([bc, PC], Xn, od) [68020+]
ProgramCounterMemoryIndirectPreindexed = 0b10'101,
/// (xxx).W
AbsoluteShort = 0b01'000,
/// (xxx).L
AbsoluteLong = 0b01'001,
/// #
ImmediateData = 0b01'100,
/// .q; value is embedded in the opcode.
Quick = 0b01'110,
};
/// Guaranteed to be 1+[largest value used by AddressingMode].
static constexpr int AddressingModeCount = 0b10'110;
/*!
A preinstruction is as much of an instruction as can be decoded with
only the first instruction word — i.e. an operation, and:
* on the 68000 and 68010, the complete addressing modes;
* on subsequent, a decent proportion of the addressing mode. See
the notes on @c AddressingMode for potential aliasing.
*/
class Preinstruction {
public:
Operation operation = Operation::Undefined;
// Instructions come with 0, 1 or 2 operands;
// the getters below act to provide a list of operands
// that is terminated by an AddressingMode::None.
//
// For two-operand instructions, argument 0 is a source
// and argument 1 is a destination.
//
// For one-operand instructions, only argument 0 will
// be provided, and will be a source and/or destination as
// per the semantics of the operation.
//
// The versions templated on index do a range check;
// if using the runtime versions then results for indices
// other than 0 and 1 are undefined.
AddressingMode mode(int index) const {
return AddressingMode(operands_[index] >> 3);
}
template <int index> AddressingMode mode() const {
if constexpr (index > 1) {
return AddressingMode::None;
}
return mode(index);
}
int reg(int index) const {
return operands_[index] & 7;
}
template <int index> int reg() const {
if constexpr (index > 1) {
return 0;
}
return reg(index);
}
/// @returns 07 to indicate data registers 0 to 7, or 815 to indicate address registers 0 to 7 respectively.
/// Provides undefined results if the addressing mode is not either @c DataRegisterDirect or
/// @c AddressRegisterDirect.
int lreg(int index) const {
return operands_[index] & 0xf;
}
bool requires_supervisor() const {
return flags_ & 0x80;
}
DataSize operand_size() const {
return DataSize(flags_ & 0x03);
}
Condition condition() const {
return Condition((flags_ >> 2) & 0x0f);
}
private:
uint8_t operands_[2] = { uint8_t(AddressingMode::None), uint8_t(AddressingMode::None)};
uint8_t flags_ = 0;
std::string operand_description(int index, int opcode) const;
public:
Preinstruction(
Operation operation,
AddressingMode op1_mode, int op1_reg,
AddressingMode op2_mode, int op2_reg,
bool is_supervisor,
DataSize size,
Condition condition) : operation(operation)
{
operands_[0] = uint8_t((uint8_t(op1_mode) << 3) | op1_reg);
operands_[1] = uint8_t((uint8_t(op2_mode) << 3) | op2_reg);
flags_ = uint8_t(
(is_supervisor ? 0x80 : 0x00) |
(int(condition) << 2) |
int(size)
);
}
Preinstruction() {}
/// Produces a string description of this instruction; if @c opcode
/// is supplied then any quick fields in this instruction will be decoded;
/// otherwise they'll be printed as just 'Q'.
std::string to_string(int opcode = -1) const;
};
}
}
#include "Implementation/InstructionOperandSize.hpp"
#include "Implementation/InstructionOperandFlags.hpp"
#endif /* InstructionSets_68k_Instruction_hpp */

View File

@@ -0,0 +1,26 @@
//
// Model.hpp
// Clock Signal
//
// Created by Thomas Harte on 15/04/2022.
// Copyright © 2022 Thomas Harte. All rights reserved.
//
#ifndef InstructionSets_M68k_Model_hpp
#define InstructionSets_M68k_Model_hpp
namespace InstructionSet {
namespace M68k {
enum class Model {
M68000,
M68010,
M68020,
M68030,
M68040,
};
}
}
#endif /* InstructionSets_M68k_Model_hpp */

View File

@@ -0,0 +1,175 @@
//
// Perform.hpp
// Clock Signal
//
// Created by Thomas Harte on 28/04/2022.
// Copyright © 2022 Thomas Harte. All rights reserved.
//
#ifndef InstructionSets_M68k_Perform_h
#define InstructionSets_M68k_Perform_h
#include "Model.hpp"
#include "Instruction.hpp"
#include "Status.hpp"
#include "../../Numeric/RegisterSizes.hpp"
namespace InstructionSet {
namespace M68k {
struct NullFlowController {
//
// Various operation-specific did-perform notfications; these all relate to operations
// with variable timing on a 68000, providing the fields that contribute to that timing.
//
/// Indicates that a @c MULU was performed, providing the @c source operand.
template <typename IntT> void did_mulu(IntT) {}
/// Indicates that a @c MULS was performed, providing the @c source operand.
template <typename IntT> void did_muls(IntT) {}
/// Indicates that a @c CHK was performed, along with whether the result @c was_under zero or @c was_over the source operand.
void did_chk([[maybe_unused]] bool was_under, [[maybe_unused]] bool was_over) {}
/// Indicates an in-register shift or roll occurred, providing the number of bits shifted by
/// and the type shifted.
///
/// @c IntT may be uint8_t, uint16_t or uint32_t.
template <typename IntT> void did_shift([[maybe_unused]] int bit_count) {}
/// Indicates that a @c DIVU was performed, providing the @c dividend and @c divisor.
/// If @c did_overflow is @c true then the divide ended in overflow.
template <bool did_overflow> void did_divu([[maybe_unused]] uint32_t dividend, [[maybe_unused]] uint32_t divisor) {}
/// Indicates that a @c DIVS was performed, providing the @c dividend and @c divisor.
/// If @c did_overflow is @c true then the divide ended in overflow.
template <bool did_overflow> void did_divs([[maybe_unused]] int32_t dividend, [[maybe_unused]] int32_t divisor) {}
/// Indicates that a bit-manipulation operation (i.e. BTST, BCHG or BSET) was performed, affecting the bit at posiition @c bit_position.
void did_bit_op([[maybe_unused]] int bit_position) {}
/// Indicates that an @c Scc was performed; if @c did_set_ff is true then the condition was true and FF
/// written to the operand; otherwise 00 was written.
void did_scc([[maybe_unused]] bool did_set_ff) {}
/// Provides a notification that the upper byte of the status register has been affected by the current instruction;
/// this gives an opportunity to track the supervisor flag.
void did_update_status() {}
//
// Operations that don't fit the reductive load-modify-store pattern; these are requests from perform
// that the flow controller do something (and, correspondingly, do not have empty implementations).
//
// All offsets are the native values as encoded in the corresponding operations.
//
/// If @c matched_condition is @c true, apply the @c offset to the PC.
template <typename IntT> void complete_bcc(bool matched_condition, IntT offset);
/// If both @c matched_condition and @c overflowed are @c false, apply @c offset to the PC.
void complete_dbcc(bool matched_condition, bool overflowed, int16_t offset);
/// Push the program counter of the next instruction to the stack, and add @c offset to the PC.
void bsr(uint32_t offset);
/// Push the program counter of the next instruction to the stack, and load @c offset to the PC.
void jsr(uint32_t address);
/// Set the program counter to @c address.
void jmp(uint32_t address);
/// Pop a word from the stack and use that to set the status condition codes. Then pop a new value for the PC.
void rtr();
/// Pop a word from the stack and use that to set the entire status register. Then pop a new value for the PC.
void rte();
/// Pop a new value for the PC from the stack.
void rts();
/// Put the processor into the stopped state, waiting for interrupts.
void stop();
/// Assert the reset output.
void reset();
/// Perform LINK using the address register identified by @c instruction and the specified @c offset.
void link(Preinstruction instruction, uint32_t offset);
/// Perform unlink, with @c address being the target address register.
void unlink(uint32_t &address);
/// Push @c address to the stack.
void pea(uint32_t address);
/// Replace the current user stack pointer with @c address.
/// The processor is guranteed to be in supervisor mode.
void move_to_usp(uint32_t address);
/// Put the value of the user stack pointer into @c address.
/// The processor is guranteed to be in supervisor mode.
void move_from_usp(uint32_t &address);
/// Perform an atomic TAS cycle; if @c instruction indicates that this is a TAS Dn then
/// perform the TAS directly upon that register; otherwise perform it on the memory at
/// @c address. If this is a TAS Dn then @c address will contain the initial value of
/// the register.
void tas(Preinstruction instruction, uint32_t address);
/// Use @c instruction to determine the direction of this MOVEP and perform it;
/// @c source is the first operand provided to the MOVEP — either an address or register
/// contents — and @c dest is the second.
///
/// @c IntT may be either uint16_t or uint32_t.
template <typename IntT> void movep(Preinstruction instruction, uint32_t source, uint32_t dest);
/// Perform a MOVEM to memory, from registers. @c instruction will indicate the mask as the first operand,
/// and the target address and addressing mode as the second; the mask and address are also supplied
/// as @c mask and @c address. If the addressing mode is -(An) then the address register will have
/// been decremented already.
///
/// The receiver is responsible for updating the address register if applicable.
///
/// @c IntT may be either uint16_t or uint32_t.
template <typename IntT> void movem_toM(Preinstruction instruction, uint32_t mask, uint32_t address);
/// Perform a MOVEM to registers, from memory. @c instruction will indicate the mask as the first operand,
/// and the target address and addressing mode as the second; the mask and address are also supplied
/// as @c mask and @c address. If the addressing mode is (An)+ then the address register will have been
/// incremented, but @c address will be its value before that occurred.
///
/// The receiver is responsible for updating the address register if applicable.
///
/// @c IntT may be either uint16_t or uint32_t.
template <typename IntT> void movem_toR(Preinstruction instruction, uint32_t mask, uint32_t address);
/// Raises a short-form exception using @c vector. If @c use_current_instruction_pc is @c true,
/// the program counter for the current instruction is included in the resulting stack frame. Otherwise the program
/// counter for the next instruction is used.
template <bool use_current_instruction_pc = true>
void raise_exception([[maybe_unused]] int vector);
};
/// Performs @c instruction using @c source and @c dest (one or both of which may be ignored as per
/// the semantics of the operation).
///
/// Any change in processor status will be applied to @c status. If this operation does not fit the reductive model
/// of being a read and possibly a modify and possibly a write of up to two operands then the @c flow_controller
/// will be asked to fill in the gaps.
///
/// If the template parameter @c operation is not @c Operation::Undefined then that operation will be performed, ignoring
/// whatever is specifed in @c instruction. This allows selection either at compile time or at run time; per Godbolt all modern
/// compilers seem to be smart enough fully to optimise the compile-time case.
template <
Model model,
typename FlowController,
Operation operation = Operation::Undefined
> void perform(Preinstruction instruction, CPU::RegisterPair32 &source, CPU::RegisterPair32 &dest, Status &status, FlowController &flow_controller);
}
}
#include "Implementation/PerformImplementation.hpp"
#endif /* InstructionSets_M68k_Perform_h */

View File

@@ -0,0 +1,31 @@
//
// RegisterSet.hpp
// Clock Signal
//
// Created by Thomas Harte on 16/05/2022.
// Copyright © 2022 Thomas Harte. All rights reserved.
//
#ifndef InstructionSets_M68k_RegisterSet_h
#define InstructionSets_M68k_RegisterSet_h
namespace InstructionSet {
namespace M68k {
struct RegisterSet {
uint32_t data[8], address[7];
uint32_t user_stack_pointer;
uint32_t supervisor_stack_pointer;
uint16_t status;
uint32_t program_counter;
/// @returns The active stack pointer, whichever it may be.
uint32_t stack_pointer() const {
return (status & 0x2000) ? supervisor_stack_pointer : user_stack_pointer;
}
};
}
}
#endif /* InstructionSets_M68k_RegisterSet_h */

View File

@@ -0,0 +1,153 @@
//
// Status.hpp
// Clock Signal
//
// Created by Thomas Harte on 28/04/2022.
// Copyright © 2022 Thomas Harte. All rights reserved.
//
#ifndef InstructionSets_M68k_Status_h
#define InstructionSets_M68k_Status_h
#include "Instruction.hpp"
namespace InstructionSet {
namespace M68k {
namespace ConditionCode {
static constexpr uint16_t Carry = 1 << 0;
static constexpr uint16_t Overflow = 1 << 1;
static constexpr uint16_t Zero = 1 << 2;
static constexpr uint16_t Negative = 1 << 3;
static constexpr uint16_t Extend = 1 << 4;
static constexpr uint16_t AllConditions = Carry | Overflow | Zero | Negative | Extend;
static constexpr uint16_t Supervisor = 1 << 13;
static constexpr uint16_t Trace = 1 << 15;
static constexpr uint16_t InterruptPriorityMask = 0b111 << 8;
}
struct Status {
/// Generally holds an unevaluated flag for potential later lazy evaluation; it'll be zero for one outcome,
/// non-zero for the other, but no guarantees are made about the potential range of non-zero values.
using FlagT = uint_fast32_t;
/* b15 */
FlagT trace_flag = 0; // The trace flag is set if and only if this value is non-zero.
/* b13 */
bool is_supervisor = false; // true => processor is in supervisor mode; false => it isn't.
/* b7b9 */
int interrupt_level = 0; // The direct integer value of the current interrupt level.
// Values of 8 or greater have undefined meaning.
/* b0b4 */
FlagT zero_result = 0; // The zero flag is set if and only if this value is zero.
FlagT carry_flag = 0; // The carry flag is set if and only if this value is non-zero.
FlagT extend_flag = 0; // The extend flag is set if and only if this value is non-zero.
FlagT overflow_flag = 0; // The overflow flag is set if and only if this value is non-zero.
FlagT negative_flag = 0; // The negative flag is set if and only this value is non-zero.
/// Gets the current condition codes.
constexpr uint16_t ccr() const {
return
(carry_flag ? ConditionCode::Carry : 0) |
(overflow_flag ? ConditionCode::Overflow : 0) |
(!zero_result ? ConditionCode::Zero : 0) |
(negative_flag ? ConditionCode::Negative : 0) |
(extend_flag ? ConditionCode::Extend : 0);
}
/// Sets the current condition codes.
constexpr void set_ccr(uint16_t ccr) {
carry_flag = ccr & ConditionCode::Carry;
overflow_flag = ccr & ConditionCode::Overflow;
zero_result = ~ccr & ConditionCode::Zero;
negative_flag = ccr & ConditionCode::Negative;
extend_flag = ccr & ConditionCode::Extend;
}
/// Gets the current value of the status register.
constexpr uint16_t status() const {
return uint16_t(
ccr() |
(interrupt_level << 8) |
(trace_flag ? ConditionCode::Trace : 0) |
(is_supervisor ? ConditionCode::Supervisor : 0)
);
}
/// Sets the current value of the status register;
/// @returns @c true if the processor finishes in supervisor mode; @c false otherwise.
constexpr bool set_status(uint16_t status) {
set_ccr(status);
interrupt_level = (status >> 8) & 7;
trace_flag = status & ConditionCode::Trace;
is_supervisor = status & ConditionCode::Supervisor;
return is_supervisor;
}
/// Adjusts the status for exception processing — sets supervisor mode, disables trace,
/// and if @c new_interrupt_level is greater than or equal to 0 sets that as the new
/// interrupt level.
///
/// @returns The status prior to those changes.
uint16_t begin_exception(int new_interrupt_level = -1) {
const uint16_t initial_status = status();
if(new_interrupt_level >= 0) {
interrupt_level = new_interrupt_level;
}
is_supervisor = true;
trace_flag = 0;
return initial_status;
}
/// Evaluates @c condition.
constexpr bool evaluate_condition(Condition condition) const {
switch(condition) {
default:
case Condition::True: return true;
case Condition::False: return false;
case Condition::High: return zero_result && !carry_flag;
case Condition::LowOrSame: return !zero_result || carry_flag;
case Condition::CarryClear: return !carry_flag;
case Condition::CarrySet: return carry_flag;
case Condition::NotEqual: return zero_result;
case Condition::Equal: return !zero_result;
case Condition::OverflowClear: return !overflow_flag;
case Condition::OverflowSet: return overflow_flag;
case Condition::Positive: return !negative_flag;
case Condition::Negative: return negative_flag;
case Condition::GreaterThanOrEqual:
return (negative_flag && overflow_flag) || (!negative_flag && !overflow_flag);
case Condition::LessThan:
return (negative_flag && !overflow_flag) || (!negative_flag && overflow_flag);
case Condition::GreaterThan:
return zero_result && ((negative_flag && overflow_flag) || (!negative_flag && !overflow_flag));
case Condition::LessThanOrEqual:
return !zero_result || (negative_flag && !overflow_flag) || (!negative_flag && overflow_flag);
}
}
/// @returns @c true if an interrupt at level @c level should be accepted; @c false otherwise.
constexpr bool would_accept_interrupt(int level) const {
// TODO: is level seven really non-maskable? If so then what mechanism prevents
// rapid stack overflow upon a level-seven interrupt?
return level > interrupt_level;
}
};
}
}
#endif /* InstructionSets_M68k_Status_h */

View File

@@ -10,9 +10,240 @@
using namespace InstructionSet::PowerPC;
Decoder::Decoder(Model model) : model_(model) {}
namespace {
Instruction Decoder::decode(uint32_t opcode) {
template <Model model, bool validate_reserved_bits, Operation operation> Instruction instruction(uint32_t opcode, bool is_supervisor = false) {
// If validation isn't required, there's nothing to do here.
if constexpr (!validate_reserved_bits) {
return Instruction(operation, opcode, is_supervisor);
}
// Otherwise, validation depends on operation
// (and, in principle, processor model).
switch(operation) {
case Operation::absx: case Operation::clcs:
case Operation::nabsx:
case Operation::addmex: case Operation::addzex:
case Operation::bcctrx: case Operation::bclrx:
case Operation::cntlzdx: case Operation::cntlzwx:
case Operation::extsbx: case Operation::extshx: case Operation::extswx:
case Operation::fmulx: case Operation::fmulsx:
case Operation::negx:
case Operation::subfmex: case Operation::subfzex:
if(opcode & 0b000000'00000'00000'11111'0000000000'0) return Instruction(opcode);
break;
case Operation::cmp: case Operation::cmpl:
if(opcode & 0b000000'00010'00000'00000'0000000000'1) return Instruction(opcode);
break;
case Operation::cmpi: case Operation::cmpli:
if(opcode & 0b000000'00010'00000'00000'0000000000'0) return Instruction(opcode);
break;
case Operation::dcbf: case Operation::dcbi: case Operation::dcbst:
case Operation::dcbt: case Operation::dcbtst: case Operation::dcbz:
if(opcode & 0b000000'11111'00000'00000'0000000000'0) return Instruction(opcode);
break;
case Operation::crand: case Operation::crandc: case Operation::creqv:
case Operation::crnand: case Operation::crnor: case Operation::cror:
case Operation::crorc: case Operation::crxor:
case Operation::eciwx: case Operation::ecowx:
case Operation::lbzux: case Operation::lbzx:
case Operation::ldarx:
case Operation::ldux: case Operation::ldx:
case Operation::lfdux: case Operation::lfdx:
case Operation::lfsux: case Operation::lfsx:
case Operation::lhaux: case Operation::lhax: case Operation::lhbrx:
case Operation::lhzux: case Operation::lhzx:
case Operation::lswi: case Operation::lswx:
case Operation::lwarx: case Operation::lwaux: case Operation::lwax: case Operation::lwbrx:
case Operation::lwzux: case Operation::lwzx:
case Operation::mfspr: case Operation::mftb:
case Operation::mtspr:
case Operation::stbux: case Operation::stbx:
case Operation::stdux: case Operation::stdx:
case Operation::stfdux: case Operation::stfdx:
case Operation::stfiwx:
case Operation::stfsux: case Operation::stfsx:
case Operation::sthbrx:
case Operation::sthux: case Operation::sthx:
case Operation::stswi: case Operation::stswx:
case Operation::stwbrx:
case Operation::stwux: case Operation::stwx:
case Operation::td: case Operation::tw:
if(opcode & 0b000000'00000'00000'00000'0000000000'1) return Instruction(opcode);
break;
case Operation::fabsx: case Operation::fcfidx:
case Operation::fctidx: case Operation::fctidzx:
case Operation::fctiwx: case Operation::fctiwzx:
case Operation::fmrx: case Operation::fnabsx:
case Operation::fnegx: case Operation::frspx:
if(opcode & 0b000000'00000'11111'00000'0000000000'0) return Instruction(opcode);
break;
case Operation::faddx: case Operation::faddsx:
case Operation::fdivx: case Operation::fdivsx:
case Operation::fsubx: case Operation::fsubsx:
if(opcode & 0b000000'00000'00000'00000'1111100000'0) return Instruction(opcode);
break;
case Operation::fcmpo: case Operation::fcmpu:
if(opcode & 0b000000'00011'00000'00000'0000000000'1) return Instruction(opcode);
break;
case Operation::fresx: case Operation::frsqrtex:
case Operation::fsqrtx: case Operation::fsqrtsx:
if(opcode & 0b000000'00000'11111'00000'1111100000'1) return Instruction(opcode);
break;
case Operation::icbi:
if(opcode & 0b000000'11111'00000'00000'0000000000'1) return Instruction(opcode);
break;
case Operation::eieio:
case Operation::isync:
case Operation::rfi:
case Operation::slbia:
case Operation::sync:
case Operation::tlbia:
case Operation::tlbsync:
if(opcode & 0b000000'11111'11111'11111'0000000000'1) return Instruction(opcode);
break;
case Operation::mcrf: case Operation::mcrfs:
if(opcode & 0b000000'00011'00011'11111'0000000000'1) return Instruction(opcode);
break;
case Operation::mcrxr:
if(opcode & 0b000000'00011'11111'11111'0000000000'1) return Instruction(opcode);
break;
case Operation::mfcr:
case Operation::mfmsr:
case Operation::mtmsr:
if(opcode & 0b000000'00000'11111'11111'0000000000'1) return Instruction(opcode);
break;
case Operation::mffsx:
case Operation::mtfsb0x:
case Operation::mtfsb1x:
if(opcode & 0b000000'00000'11111'11111'0000000000'0) return Instruction(opcode);
break;
case Operation::mtfsfx:
if(opcode & 0b000000'10000'00001'00000'0000000000'0) return Instruction(opcode);
break;
case Operation::mtfsfix:
if(opcode & 0b000000'00011'11111'00001'0000000000'0) return Instruction(opcode);
break;
case Operation::mtsr:
if(opcode & 0b000000'00000'10000'11111'0000000000'1) return Instruction(opcode);
break;
case Operation::mtsrin: case Operation::mfsrin:
if(opcode & 0b000000'00000'11111'00000'0000000000'1) return Instruction(opcode);
break;
case Operation::mfsr:
if(opcode & 0b000000'00000'10000'11111'0000000000'1) return Instruction(opcode);
break;
case Operation::mtcrf:
if(opcode & 0b000000'00000'10000'00001'0000000000'1) return Instruction(opcode);
break;
case Operation::mulhdx: case Operation::mulhdux:
case Operation::mulhwx: case Operation::mulhwux:
if(opcode & 0b000000'00000'00000'00000'1000000000'0) return Instruction(opcode);
break;
case Operation::sc:
if(opcode & 0b000000'11111'11111'11111'1111111110'1) return Instruction(opcode);
break;
case Operation::slbie:
case Operation::tlbie:
if(opcode & 0b000000'11111'11111'00000'0000000000'1) return Instruction(opcode);
break;
case Operation::stwcx_:
if(!(opcode & 0b000000'00000'00000'00000'0000000000'1)) return Instruction(opcode);
break;
case Operation::divx: case Operation::divsx:
case Operation::dozx: case Operation::dozi:
case Operation::lscbxx:
case Operation::maskgx: case Operation::maskirx:
case Operation::mulx:
case Operation::rlmix: case Operation::rribx:
case Operation::slex: case Operation::sleqx: case Operation::sliqx:
case Operation::slliqx: case Operation::sllqx: case Operation::slqx:
case Operation::sraiqx: case Operation::sraqx:
case Operation::srex: case Operation::sreqx:
case Operation::sriqx: case Operation::srliqx:
case Operation::srlqx: case Operation::srqx:
case Operation::sreax:
case Operation::addx: case Operation::addcx: case Operation::addex:
case Operation::addi: case Operation::addic: case Operation::addic_:
case Operation::addis:
case Operation::andx: case Operation::andcx:
case Operation::andi_: case Operation::andis_:
case Operation::bx: case Operation::bcx:
case Operation::divdx: case Operation::divdux:
case Operation::divwx: case Operation::divwux:
case Operation::eqvx:
case Operation::fmaddx: case Operation::fmaddsx:
case Operation::fmsubx: case Operation::fmsubsx:
case Operation::fnmaddx: case Operation::fnmaddsx:
case Operation::fnmsubx: case Operation::fnmsubsx:
case Operation::fselx:
case Operation::lbz: case Operation::lbzu:
case Operation::lfd: case Operation::lfdu:
case Operation::lfs: case Operation::lfsu:
case Operation::lha: case Operation::lhau:
case Operation::lhz: case Operation::lhzu:
case Operation::lmw: case Operation::lwa:
case Operation::lwz: case Operation::lwzu:
case Operation::mulldx: case Operation::mulli: case Operation::mullwx:
case Operation::nandx: case Operation::norx:
case Operation::orx: case Operation::orcx:
case Operation::ori: case Operation::oris:
case Operation::rlwimix: case Operation::rlwinmx: case Operation::rlwnmx:
case Operation::sldx: case Operation::slwx:
case Operation::sradx: case Operation::sradix:
case Operation::srawx: case Operation::srawix:
case Operation::srdx: case Operation::srwx:
case Operation::stb: case Operation::stbu:
case Operation::std: case Operation::stdcx_: case Operation::stdu:
case Operation::stfd: case Operation::stfdu:
case Operation::stfs: case Operation::stfsu:
case Operation::sth: case Operation::sthu:
case Operation::stmw:
case Operation::stw: case Operation::stwu:
case Operation::subfx: case Operation::subfcx: case Operation::subfex:
case Operation::subfic:
case Operation::tdi: case Operation::twi:
case Operation::xorx: case Operation::xori: case Operation::xoris:
case Operation::ld: case Operation::ldu:
case Operation::rldclx: case Operation::rldcrx:
case Operation::rldicx: case Operation::rldiclx:
case Operation::rldicrx: case Operation::rldimix:
break;
}
return Instruction(operation, opcode, is_supervisor);
}
}
template <Model model, bool validate_reserved_bits>
Instruction Decoder<model, validate_reserved_bits>::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.
@@ -32,16 +263,16 @@ Instruction Decoder::decode(uint32_t opcode) {
// 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 Bind(mask, operation) case mask: return instruction<model, validate_reserved_bits, Operation::operation>(opcode);
#define BindSupervisor(mask, operation) case mask: return instruction<model, validate_reserved_bits, Operation::operation>(opcode, true);
#define BindConditional(condition, mask, operation) \
case mask: \
if(condition()) return Instruction(Operation::operation, opcode); \
return Instruction(opcode);
if(condition(model)) return instruction<model, validate_reserved_bits, Operation::operation>(opcode); \
return instruction<model, validate_reserved_bits, Operation::operation>(opcode);
#define BindSupervisorConditional(condition, mask, operation) \
case mask: \
if(condition()) return Instruction(Operation::operation, opcode, true); \
return Instruction(opcode);
if(condition(model)) return instruction<model, validate_reserved_bits, Operation::operation>(opcode, true); \
return instruction<model, validate_reserved_bits, Operation::operation>(opcode);
#define Six(x) (unsigned(x) << 26)
#define SixTen(x, y) (Six(x) | ((y) << 1))
@@ -64,7 +295,7 @@ Instruction Decoder::decode(uint32_t opcode) {
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);
return instruction<model, validate_reserved_bits, Operation::bcx>(opcode);
default: return Instruction(opcode);
}
@@ -111,7 +342,7 @@ Instruction Decoder::decode(uint32_t opcode) {
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, 0b0011101001), mulldx); BindConditional(is64bit, SixTen(0b011111, 0b1011101001), mulldx);
BindConditional(is64bit, SixTen(0b011111, 0b0101010101), lwax);
BindConditional(is64bit, SixTen(0b011111, 0b0101110101), lwaux);
BindConditional(is64bit, SixTen(0b011111, 0b1100111011), sradix); BindConditional(is64bit, SixTen(0b011111, 0b1100111010), sradix);
@@ -120,7 +351,7 @@ Instruction Decoder::decode(uint32_t opcode) {
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);
BindConditional(is64bit, SixTen(0b111111, 0b1111011010), extswx);
// Power instructions; these are all taken from the MPC601 manual rather than
// the PowerPC Programmer's Reference Guide, hence the decimal encoding of the
@@ -309,6 +540,8 @@ Instruction Decoder::decode(uint32_t opcode) {
BindConditional(is64bit, SixTen(0b111011, 0b10110), fsqrtsx);
BindConditional(is64bit, SixTen(0b111011, 0b11000), fresx);
BindConditional(is64bit, SixTen(0b011110, 0b01000), rldclx);
BindConditional(is64bit, SixTen(0b011110, 0b01001), rldcrx);
// Optional...
Bind(SixTen(0b111111, 0b10110), fsqrtx);
@@ -316,25 +549,41 @@ Instruction Decoder::decode(uint32_t opcode) {
Bind(SixTen(0b111111, 0b11010), frsqrtex);
}
// rldicx, rldiclx, rldicrx, rldimix
if(is64bit(model)) {
switch(opcode & 0b111111'00000'00000'00000'000000'111'00) {
default: break;
case 0b011110'00000'00000'00000'000000'000'00: return instruction<model, validate_reserved_bits, Operation::rldiclx>(opcode);
case 0b011110'00000'00000'00000'000000'001'00: return instruction<model, validate_reserved_bits, Operation::rldicrx>(opcode);
case 0b011110'00000'00000'00000'000000'010'00: return instruction<model, validate_reserved_bits, Operation::rldicx>(opcode);
case 0b011110'00000'00000'00000'000000'011'00: return instruction<model, validate_reserved_bits, Operation::rldimix>(opcode);
}
}
// 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);
switch(opcode & 0b111111'0000'0000'0000'0000'111111111'1) {
default: break;
case 0b011111'0000'0000'0000'0000'010010110'1: return instruction<model, validate_reserved_bits, Operation::stwcx_>(opcode);
case 0b011111'0000'0000'0000'0000'011010110'1:
if(is64bit(model)) return instruction<model, validate_reserved_bits, 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);
// std, stdu, ld, ldu, lwa
if(is64bit(model)) {
switch(opcode & 0b111111'00'00000000'00000000'000000'11) {
default: break;
case 0b111010'00'00000000'00000000'000000'00: return instruction<model, validate_reserved_bits, Operation::ld>(opcode);
case 0b111010'00'00000000'00000000'000000'01: return instruction<model, validate_reserved_bits, Operation::ldu>(opcode);
case 0b111010'00'00000000'00000000'000000'10: return instruction<model, validate_reserved_bits, Operation::lwa>(opcode);
case 0b111110'00'00000000'00000000'000000'00: return instruction<model, validate_reserved_bits, Operation::std>(opcode);
case 0b111110'00'00000000'00000000'000000'01: return instruction<model, validate_reserved_bits, Operation::stdu>(opcode);
}
}
// sc
if((opcode & 0b111111'00'00000000'00000000'000000'1'0) == 0b010001'00'00000000'00000000'000000'1'0) {
return Instruction(Operation::sc, opcode);
return instruction<model, validate_reserved_bits, Operation::sc>(opcode);
}
#undef Six
@@ -345,3 +594,11 @@ Instruction Decoder::decode(uint32_t opcode) {
return Instruction(opcode);
}
template struct InstructionSet::PowerPC::Decoder<InstructionSet::PowerPC::Model::MPC601, true>;
template struct InstructionSet::PowerPC::Decoder<InstructionSet::PowerPC::Model::MPC603, true>;
template struct InstructionSet::PowerPC::Decoder<InstructionSet::PowerPC::Model::MPC620, true>;
template struct InstructionSet::PowerPC::Decoder<InstructionSet::PowerPC::Model::MPC601, false>;
template struct InstructionSet::PowerPC::Decoder<InstructionSet::PowerPC::Model::MPC603, false>;
template struct InstructionSet::PowerPC::Decoder<InstructionSet::PowerPC::Model::MPC620, false>;

View File

@@ -23,31 +23,31 @@ enum class Model {
MPC620,
};
constexpr bool is64bit(Model model) {
return model == Model::MPC620;
}
constexpr bool is32bit(Model model) {
return !is64bit(model);
}
constexpr bool is601(Model model) {
return model == Model::MPC601;
}
/*!
Implements PowerPC instruction decoding.
This is an experimental implementation; it has not yet undergone significant testing.
@c model Indicates the instruction set to decode.
@c validate_reserved_bits If set to @c true, check that all
reserved bits are 0 or 1 as required and produce an invalid opcode if not.
Otherwise does no inspection of reserved bits.
TODO: determine what specific models of PowerPC do re: reserved bits.
*/
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;
}
template <Model model, bool validate_reserved_bits = false> struct Decoder {
Instruction decode(uint32_t opcode);
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,320 @@
//
// DataPointerResolver.hpp
// Clock Signal
//
// Created by Thomas Harte on 24/02/2022.
// Copyright © 2022 Thomas Harte. All rights reserved.
//
#ifndef DataPointerResolver_hpp
#define DataPointerResolver_hpp
#include "Instruction.hpp"
#include "Model.hpp"
#include <cassert>
namespace InstructionSet {
namespace x86 {
/// Unlike source, describes only registers, and breaks
/// them down by conventional name — so AL, AH, AX and EAX are all
/// listed separately and uniquely, rather than being eAX+size or
/// eSPorAH with a size of 1.
enum class Register: uint8_t {
// 8-bit registers.
AL, AH,
CL, CH,
DL, DH,
BL, BH,
// 16-bit registers.
AX, CX, DX, BX,
SP, BP, SI, DI,
ES, CS, SS, DS,
FS, GS,
// 32-bit registers.
EAX, ECX, EDX, EBX,
ESP, EBP, ESI, EDI,
//
None
};
/// @returns @c true if @c r is the same size as @c DataT; @c false otherwise.
/// @discussion Provided primarily to aid in asserts; if the decoder and resolver are both
/// working then it shouldn't be necessary to test this in register files.
template <typename DataT> constexpr bool is_sized(Register r) {
static_assert(sizeof(DataT) == 4 || sizeof(DataT) == 2 || sizeof(DataT) == 1);
if constexpr (sizeof(DataT) == 4) {
return r >= Register::EAX && r < Register::None;
}
if constexpr (sizeof(DataT) == 2) {
return r >= Register::AX && r < Register::EAX;
}
if constexpr (sizeof(DataT) == 1) {
return r >= Register::AL && r < Register::AX;
}
return false;
}
/// @returns the proper @c Register given @c source and data of size @c sizeof(DataT),
/// or Register::None if no such register exists (e.g. asking for a 32-bit version of CS).
template <typename DataT> constexpr Register register_for_source(Source source) {
static_assert(sizeof(DataT) == 4 || sizeof(DataT) == 2 || sizeof(DataT) == 1);
if constexpr (sizeof(DataT) == 4) {
switch(source) {
case Source::eAX: return Register::EAX;
case Source::eCX: return Register::ECX;
case Source::eDX: return Register::EDX;
case Source::eBX: return Register::EBX;
case Source::eSPorAH: return Register::ESP;
case Source::eBPorCH: return Register::EBP;
case Source::eSIorDH: return Register::ESI;
case Source::eDIorBH: return Register::EDI;
default: break;
}
}
if constexpr (sizeof(DataT) == 2) {
switch(source) {
case Source::eAX: return Register::AX;
case Source::eCX: return Register::CX;
case Source::eDX: return Register::DX;
case Source::eBX: return Register::BX;
case Source::eSPorAH: return Register::SP;
case Source::eBPorCH: return Register::BP;
case Source::eSIorDH: return Register::SI;
case Source::eDIorBH: return Register::DI;
case Source::ES: return Register::ES;
case Source::CS: return Register::CS;
case Source::SS: return Register::SS;
case Source::DS: return Register::DS;
case Source::FS: return Register::FS;
case Source::GS: return Register::GS;
default: break;
}
}
if constexpr (sizeof(DataT) == 1) {
switch(source) {
case Source::eAX: return Register::AL;
case Source::eCX: return Register::CL;
case Source::eDX: return Register::DL;
case Source::eBX: return Register::BL;
case Source::eSPorAH: return Register::AH;
case Source::eBPorCH: return Register::CH;
case Source::eSIorDH: return Register::DH;
case Source::eDIorBH: return Register::BH;
default: break;
}
}
return Register::None;
}
/// Reads from or writes to the source or target identified by a DataPointer, relying upon two user-supplied classes:
///
/// * a register bank; and
/// * a memory pool.
///
/// The register bank should implement `template<typename DataT, Register> DataT read()` and `template<typename DataT, Register> void write(DataT)`.
/// Those functions will be called only with registers and data types that are appropriate to the @c model.
///
/// The memory pool should implement `template<typename DataT> DataT read(Source segment, uint32_t address)` and
/// `template<typename DataT> void write(Source segment, uint32_t address, DataT value)`.
template <Model model, typename RegistersT, typename MemoryT> class DataPointerResolver {
public:
public:
/// Reads the data pointed to by @c pointer, referencing @c instruction, @c memory and @c registers as necessary.
template <typename DataT> static DataT read(
RegistersT &registers,
MemoryT &memory,
const Instruction<is_32bit(model)> &instruction,
DataPointer pointer);
/// Writes @c value to the data pointed to by @c pointer, referencing @c instruction, @c memory and @c registers as necessary.
template <typename DataT> static void write(
RegistersT &registers,
MemoryT &memory,
const Instruction<is_32bit(model)> &instruction,
DataPointer pointer,
DataT value);
/// Computes the effective address of @c pointer including any displacement applied by @c instruction.
/// @c pointer must be of type Source::Indirect.
template <bool obscured_indirectNoBase = true, bool has_base = true>
static uint32_t effective_address(
RegistersT &registers,
const Instruction<is_32bit(model)> &instruction,
DataPointer pointer);
private:
template <bool is_write, typename DataT> static void access(
RegistersT &registers,
MemoryT &memory,
const Instruction<is_32bit(model)> &instruction,
DataPointer pointer,
DataT &value);
};
//
// Implementation begins here.
//
template <Model model, typename RegistersT, typename MemoryT>
template <typename DataT> DataT DataPointerResolver<model, RegistersT, MemoryT>::read(
RegistersT &registers,
MemoryT &memory,
const Instruction<is_32bit(model)> &instruction,
DataPointer pointer) {
DataT result;
access<false>(registers, memory, instruction, pointer, result);
return result;
}
template <Model model, typename RegistersT, typename MemoryT>
template <typename DataT> void DataPointerResolver<model, RegistersT, MemoryT>::write(
RegistersT &registers,
MemoryT &memory,
const Instruction<is_32bit(model)> &instruction,
DataPointer pointer,
DataT value) {
access<true>(registers, memory, instruction, pointer, value);
}
#define rw(v, r, is_write) \
case Source::r: \
using VType = typename std::remove_reference<decltype(v)>::type; \
if constexpr (is_write) { \
registers.template write<VType, register_for_source<VType>(Source::r)>(v); \
} else { \
v = registers.template read<VType, register_for_source<VType>(Source::r)>(); \
} \
break;
#define ALLREGS(v, i) rw(v, eAX, i); rw(v, eCX, i); \
rw(v, eDX, i); rw(v, eBX, i); \
rw(v, eSPorAH, i); rw(v, eBPorCH, i); \
rw(v, eSIorDH, i); rw(v, eDIorBH, i); \
rw(v, ES, i); rw(v, CS, i); \
rw(v, SS, i); rw(v, DS, i); \
rw(v, FS, i); rw(v, GS, i);
template <Model model, typename RegistersT, typename MemoryT>
template <bool obscured_indirectNoBase, bool has_base>
uint32_t DataPointerResolver<model, RegistersT, MemoryT>::effective_address(
RegistersT &registers,
const Instruction<is_32bit(model)> &instruction,
DataPointer pointer) {
using AddressT = typename Instruction<is_32bit(model)>::AddressT;
AddressT base = 0, index = 0;
if constexpr (has_base) {
switch(pointer.base<obscured_indirectNoBase>()) {
default: break;
ALLREGS(base, false);
}
}
switch(pointer.index()) {
default: break;
ALLREGS(index, false);
}
uint32_t address = index;
if constexpr (model >= Model::i80386) {
address <<= pointer.scale();
} else {
assert(!pointer.scale());
}
// Always compute address as 32-bit.
// TODO: verify use of memory_mask around here.
// Also I think possibly an exception is supposed to be generated
// if the programmer is in 32-bit mode and has asked for 16-bit
// address computation but generated e.g. a 17-bit result. Look into
// that when working on execution. For now the goal is merely decoding
// and this code exists both to verify the presence of all necessary
// fields and to help to explore the best breakdown of storage
// within Instruction.
constexpr uint32_t memory_masks[] = {0x0000'ffff, 0xffff'ffff};
const uint32_t memory_mask = memory_masks[int(instruction.address_size())];
address = (address & memory_mask) + (base & memory_mask) + instruction.displacement();
return address;
}
template <Model model, typename RegistersT, typename MemoryT>
template <bool is_write, typename DataT> void DataPointerResolver<model, RegistersT, MemoryT>::access(
RegistersT &registers,
MemoryT &memory,
const Instruction<is_32bit(model)> &instruction,
DataPointer pointer,
DataT &value) {
const Source source = pointer.source<false>();
switch(source) {
default:
if constexpr (!is_write) {
value = 0;
}
return;
ALLREGS(value, is_write);
case Source::DirectAddress:
if constexpr(is_write) {
memory.template write(instruction.data_segment(), instruction.displacement(), value);
} else {
value = memory.template read<DataT>(instruction.data_segment(), instruction.displacement());
}
break;
case Source::Immediate:
value = DataT(instruction.operand());
break;
#define indirect(has_base) { \
const auto address = effective_address<false, has_base> \
(registers, instruction, pointer); \
\
if constexpr (is_write) { \
memory.template write( \
instruction.data_segment(), \
address, \
value \
); \
} else { \
value = memory.template read<DataT>( \
instruction.data_segment(), \
address \
); \
} \
}
case Source::IndirectNoBase:
indirect(false);
break;
case Source::Indirect:
indirect(true);
break;
#undef indirect
}
}
#undef ALLREGS
#undef rw
}
}
#endif /* DataPointerResolver_hpp */

File diff suppressed because it is too large Load Diff

View File

@@ -10,6 +10,7 @@
#define InstructionSets_x86_Decoder_hpp
#include "Instruction.hpp"
#include "Model.hpp"
#include <cstddef>
#include <utility>
@@ -17,38 +18,54 @@
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 {
template <Model model> class Decoder {
public:
Decoder(Model model);
using InstructionT = Instruction<is_32bit(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.
@returns an @c Instruction plus a size; a positive size indicates successful decoding of
an instruction that was that many bytes long in total; a negative size specifies the [negatived]
minimum 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.
Successful decoding is defined to mean that all decoding steps are complete. The output
may still be an illegal instruction (indicated by Operation::Invalid), if the byte sequence
supplied cannot form a valid instruction.
@discussion although instructions also contain an indicator of their length, on chips prior
to the 80286 there is no limit to instruction length and that could in theory overflow the available
storage, which can describe instructions only up to 1kb in size.
The 80286 and 80386 have instruction length limits of 10 and 15 bytes respectively, so
cannot overflow the field.
*/
std::pair<int, Instruction> decode(const uint8_t *source, size_t length);
std::pair<int, InstructionT> decode(const uint8_t *source, size_t length);
/*!
Enables or disables 32-bit protected mode. Meaningful only if the @c Model supports it.
*/
void set_32bit_protected_mode(bool);
private:
enum class Phase {
/// Captures all prefixes and continues until an instruction byte is encountered.
Instruction,
/// Having encountered a 0x0f first instruction byte, waits for the next byte fully to determine the instruction.
InstructionPageF,
/// 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,
/// Awaits n 80386+-style scale-index-base byte ('SIB'), indicating the form of indirect addressing.
ScaleIndexBase,
/// Waits for sufficiently many bytes to pass for the required displacement and operand to be captured.
/// Cf. displacement_size_ and operand_size_.
AwaitingDisplacementOrOperand,
DisplacementOrOperand,
/// Forms and returns an Instruction, and resets parsing state.
ReadyToPost
} phase_ = Phase::Instruction;
@@ -59,29 +76,27 @@ class Decoder {
/// 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.
// and populate the source_ and destination_ fields appropriately.
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,
// source_ and destination_ fields with the single register/memory result.
MemRegSingleOperand,
// 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,
// source_ field with the result. Fills destination_ with a segment
// register based on the reg field.
Seg_MemReg,
MemReg_Seg,
//
// 'Group 1'
//
// Parse for mode and register/memory fields, populating the
// destination_ field with the result. Use the 'register' field
@@ -89,32 +104,76 @@ class Decoder {
// waits for an operand equal to the operation size.
MemRegADD_to_CMP,
// Acts exactly as MemRegADD_to_CMP but the operand is fixed in size
// at a single byte, which is sign extended to the operation size.
MemRegADD_to_CMP_SignExtend,
//
// 'Group 2'
//
// 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,
// 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,
//
// 'Group 3'
//
// 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,
//
// 'Group 4'
//
// 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,
//
// 'Group 5'
//
// 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,
//
// 'Group 6'
//
// Parse for mode and register/memory field, populating both source_
// and destination_ fields with the result. Uses the 'register' field
// to pick from SLDT/STR/LLDT/LTR/VERR/VERW.
MemRegSLDT_to_VERW,
//
// 'Group 7'
//
// Parse for mode and register/memory field, populating both source_
// and destination_ fields with the result. Uses the 'register' field
// to pick from SGDT/LGDT/SMSW/LMSW.
MemRegSGDT_to_LMSW,
//
// 'Group 8'
//
// Parse for mode and register/memory field, populating destination,
// and prepare to read a single byte as source.
MemRegBT_to_BTC,
} 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.
@@ -122,30 +181,49 @@ class Decoder {
Source destination_ = Source::None;
// Immediate fields.
int16_t displacement_ = 0;
uint16_t operand_ = 0;
int32_t displacement_ = 0;
uint32_t operand_ = 0;
uint64_t inward_data_ = 0;
int next_inward_data_shift_ = 0;
// Indirection style.
ScaleIndexBase sib_;
// 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.
DataSize displacement_size_ = DataSize::None; // i.e. size of in-stream displacement, if any.
DataSize operand_size_ = DataSize::None; // i.e. size of in-stream operand, if any.
DataSize operation_size_ = DataSize::None; // i.e. size of data manipulated by the operation.
bool sign_extend_ = false; // If set then sign extend the operand up to the operation size;
// otherwise it'll be zero-padded.
// Prefix capture fields.
Repetition repetition_ = Repetition::None;
bool lock_ = false;
Source segment_override_ = Source::None;
// 32-bit/16-bit selection.
AddressSize default_address_size_ = AddressSize::b16;
DataSize default_data_size_ = DataSize::Word;
AddressSize address_size_ = AddressSize::b16;
DataSize data_size_ = DataSize::Word;
/// Resets size capture and all fields with default values.
void reset_parsing() {
consumed_ = operand_bytes_ = 0;
displacement_size_ = operand_size_ = 0;
displacement_size_ = operand_size_ = operation_size_ = DataSize::None;
displacement_ = operand_ = 0;
lock_ = false;
address_size_ = default_address_size_;
data_size_ = default_data_size_;
segment_override_ = Source::None;
repetition_ = Repetition::None;
phase_ = Phase::Instruction;
source_ = destination_ = Source::None;
sib_ = ScaleIndexBase();
next_inward_data_shift_ = 0;
inward_data_ = 0;
sign_extend_ = false;
}
};

View File

@@ -0,0 +1,917 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>80386 Opcode Map</title>
<style>
table, table th, table td {
border: 1px solid;
border-collapse: collapse;
text-align: center;
}
.codetable, .codetable th, .codetable td {
border: 0px;
border-collapse: collapse;
padding-right: 1em;
text-align: left;
vertical-align: top;
}
.optable th, .optable td {
width: 5em;
}
.optable tr:nth-child(even) {
border-top: 3px solid;
}
.grouptable, .grouptable th, .grouptable td {
border-bottom: 3px solid;
}
.grouptable th, .grouptable td {
width: 4em;
}
.skiprow {
background-color: darkgray;
}
</style>
</head>
<body>
<h1>Codes for Addressing Method</h1>
<table class="codetable">
<tr>
<td>A</td>
<td>Direct address; the instruction has no MODRM field; the address of the operand is encoded in the instruction; no base register, index register, or scaling factor can be applied; e.g., far JMP (EA).</td>
</tr>
<tr>
<td>C</td>
<td>The reg field of the MODRM field selects a control register; e.g., MOV (0F20, 0F22).</td>
</tr>
<tr>
<td>D</td>
<td>The reg field of the MODRM field selects a debug register; e.g., MOV (0F21, 0F23).</td>
</tr>
<tr>
<td>E</td>
<td>A MODRM field follows the opcode and specifies the operand. The operand is either a general register or a memory address. If it is a memory address, the address is computed from a segment register and any of the following values: a base register, an index register, a scaling factor, a displacement.</td>
</tr>
<tr>
<td>F</td>
<td>Flags register</td>
</tr>
<tr>
<td>G</td>
<td>The reg field of the MODRM field selects a general register; e.g,. ADD (00).</td>
</tr>
<tr>
<td>I</td>
<td>Immediate data. The value of the operand is encoded in subsequent bytes of the instruction.</td>
</tr>
<tr>
<td>J</td>
<td>The instruction contains a relative offset to be added to the instruction-pointer register; e.g., JMP short, LOOP.</td>
</tr>
<tr>
<td>M</td>
<td>The MODRM field may refer only to memory; e.g., BOUND, LES, LDS, LSS, LFS, LGS.</td>
</tr>
<tr>
<td>O</td>
<td>The instruction has no MODRM field; the offset of the operand is coded as a word or dword (depending on address sie attribute) in the instruction. No base register, index register, or scaling factor can be applied; e.g., MOV (A0A3).</td>
</tr>
<tr>
<td>R</td>
<td>The mod field of the MODRM field may refer only to a general register; e.g., MOV(0F200F24, 0F26).</td>
</tr>
<tr>
<td>S</td>
<td>The reg field of the MODRM field selects a segment register; e.g., MOV (8C, 8E).</td>
</tr>
<tr>
<td>T</td>
<td>The reg field of the MODRM field selects a test register; e.g., MOV (0F24, 0F26).</td>
</tr>
<tr>
<td>X</td>
<td>Memory addressed by DS:SI; e.g., MOVS, COMPS, OUTS, LODS, SCAS.</td>
</tr>
<tr>
<td>Y</td>
<td>Memory addressed by ES:DI; e.g., MOVS, CMPS, INS, STOS.</td>
</tr>
</table>
<h1>Codes for Operand Type</h1>
<table class="codetable">
<tr>
<td>a</td>
<td>Two one-word operands in memory or two dword operands in memory, depending on operand size attribute (used only by BOUND).</td>
</tr>
<tr>
<td>b</td>
<td>Byte (regardless of operand size attribute).</td>
</tr>
<tr>
<td>c</td>
<td>Byte or word, depending on operand size attribute.</td>
</tr>
<tr>
<td>d</td>
<td>Dword (regardless of operand size attribute).</td>
</tr>
<tr>
<td>p</td>
<td>32-bit or 48-bit pointer, depending on operand size attribute.</td>
</tr>
<tr>
<td>s</td>
<td>Six-byte pesudo-descriptor.</td>
</tr>
<tr>
<td>v</td>
<td>Word or dword, depending on operand size attribute.</td>
</tr>
<tr>
<td>w</td>
<td>Word (regardless of operand size attribute).</td>
</tr>
</table>
<h1>Register Codes</h1>
When an operand is a specific register encoded in the opcode, the register is identifed by its name; e.g., AX, CL, or ESI. The name of the register indicates whether the register is 32, 16, or 8 bits wide. A register identifier of the form eXX is used when the width of the register depends on the operand size attribute. For example, eAX indicates that the AX register is used when the operand size attribute is 16, and the EAX register is used when the operand size attribute is 32.
<h1>One-byte 80386 Opcode Map</h1>
<table class="optable">
<tr>
<th></th>
<th>x0</th>
<th>x1</th>
<th>x2</th>
<th>x3</th>
<th>x4</th>
<th>x5</th>
<th>x6</th>
<th>x7</th>
<th>x8</th>
<th>x9</th>
<th>xA</th>
<th>xB</th>
<th>xC</th>
<th>xD</th>
<th>xE</th>
<th>xF</th>
</tr>
<tr>
<th rowspan=2>0x</th>
<td colspan=6>ADD</td>
<td rowspan=2>PUSH ES</td>
<td rowspan=2>POP ES</td>
<td colspan=6>OR</td>
<td rowspan=2>PUSH CS</td>
<td rowspan=2>2-byte escape codes</td>
</tr>
<tr>
<!-- ADD -->
<td>Eb, Gb</td>
<td>Ev, Gv</td>
<td>Gb, Eb</td>
<td>Gv, Ev</td>
<td>AL, Ib</td>
<td>eAX, Iv</td>
<!-- OR -->
<td>Eb, Gb</td>
<td>Ev, Gv</td>
<td>Gb, Eb</td>
<td>Gv, Ev</td>
<td>AL, Ib</td>
<td>eAX, Iv</td>
</tr>
<tr>
<th rowspan=2>1x</th>
<td colspan=6>ADC</td>
<td rowspan=2>PUSH SS</td>
<td rowspan=2>POP SS</td>
<td colspan=6>SBB</td>
<td rowspan=2>PUSH DS</td>
<td rowspan=2>POP DS</td>
</tr>
<tr>
<!-- ADC -->
<td>Eb, Gb</td>
<td>Ev, Gv</td>
<td>Gb, Eb</td>
<td>Gv, Ev</td>
<td>AL, Ib</td>
<td>eAX, Iv</td>
<!-- SBB -->
<td>Eb, Gb</td>
<td>Ev, Gv</td>
<td>Gb, Eb</td>
<td>Gv, Ev</td>
<td>AL, Ib</td>
<td>eAX, Iv</td>
</tr>
<tr>
<th rowspan=2>2x</th>
<td colspan=6>AND</td>
<td rowspan=2>SEG =ES</td>
<td rowspan=2>POP ES</td>
<td colspan=6>SUB</td>
<td rowspan=2>SEG =CS</td>
<td rowspan=2>DAS</td>
</tr>
<tr>
<!-- AND -->
<td>Eb, Gb</td>
<td>Ev, Gv</td>
<td>Gb, Eb</td>
<td>Gv, Ev</td>
<td>AL, Ib</td>
<td>eAX, Iv</td>
<!-- SUB -->
<td>Eb, Gb</td>
<td>Ev, Gv</td>
<td>Gb, Eb</td>
<td>Gv, Ev</td>
<td>AL, Ib</td>
<td>eAX, Iv</td>
</tr>
<tr>
<th rowspan=2>3x</th>
<td colspan=6>XOR</td>
<td rowspan=2>SEG =SS</td>
<td rowspan=2>AAA</td>
<td colspan=6>CMP</td>
<td rowspan=2>SEG =DS</td>
<td rowspan=2>AAS</td>
</tr>
<tr>
<!-- XOR -->
<td>Eb, Gb</td>
<td>Ev, Gv</td>
<td>Gb, Eb</td>
<td>Gv, Ev</td>
<td>AL, Ib</td>
<td>eAX, Iv</td>
<!-- CMP -->
<td>Eb, Gb</td>
<td>Ev, Gv</td>
<td>Gb, Eb</td>
<td>Gv, Ev</td>
<td>AL, Ib</td>
<td>eAX, Iv</td>
</tr>
<tr>
<th rowspan=2>4x</th>
<td colspan=8>INC general register</td>
<td colspan=8>DEC general register</td>
</tr>
<tr>
<!-- INC general register -->
<td>eAX</td>
<td>eCX</td>
<td>eDX</td>
<td>eBX</td>
<td>eSP</td>
<td>eBP</td>
<td>eSI</td>
<td>eDI</td>
<!-- DEC general register -->
<td>eAX</td>
<td>eCX</td>
<td>eDX</td>
<td>eBX</td>
<td>eSP</td>
<td>eBP</td>
<td>eSI</td>
<td>eDI</td>
</tr>
<tr>
<th rowspan=2>5x</th>
<td colspan=8>PUSH general register</td>
<td colspan=8>POP general register</td>
</tr>
<tr>
<!-- PUSH general register -->
<td>eAX</td>
<td>eCX</td>
<td>eDX</td>
<td>eBX</td>
<td>eSP</td>
<td>eBP</td>
<td>eSI</td>
<td>eDI</td>
<!-- POP general register -->
<td>eAX</td>
<td>eCX</td>
<td>eDX</td>
<td>eBX</td>
<td>eSP</td>
<td>eBP</td>
<td>eSI</td>
<td>eDI</td>
</tr>
<tr>
<th rowspan=2>6x</th>
<td rowspan=2>PUSHA</td>
<td rowspan=2>POPA</td>
<td rowspan=2>BOUND Gv, Ma</td>
<td rowspan=2>ARPL Gv, Ma</td>
<td rowspan=2>SEG =FS</td>
<td rowspan=2>SEG =GS</td>
<td rowspan=2>Operand Size</td>
<td rowspan=2>Address Size</td>
<td rowspan=2>PUSH Iv</td>
<td rowspan=2>IMUL GvEvIv</td>
<td rowspan=2>PUSH Ib</td>
<td rowspan=2>IMUL GvEvIb</td>
<td rowspan=2>INSB Yb, Dx</td>
<td rowspan=2>INSW/D Yv, Dx</td>
<td rowspan=2>OUTSB Dx, Xb</td>
<td rowspan=2>OUTSW/D Dx, Xb</td>
</tr>
<tr></tr>
<tr>
<th rowspan=2>7x</th>
<td colspan=16>Short-displacement jump on condition (Jb)</td>
</tr>
<tr>
<!-- Short-displacement jump on condition (Jb) -->
<td>JO</td>
<td>JNO</td>
<td>JB</td>
<td>JNB</td>
<td>JZ</td>
<td>JNZ</td>
<td>JBE</td>
<td>JNBE</td>
<td>JS</td>
<td>JNS</td>
<td>JP</td>
<td>JNP</td>
<td>JL</td>
<td>JNL</td>
<td>JLE</td>
<td>JNLE</td>
</tr>
<tr>
<th rowspan=2>8x</th>
<td colspan=2>Immediate Grp1</td>
<td rowspan=2></td>
<td rowspan=2>Grp1 Ev, Ib</td>
<td colspan=2>TEST</td>
<td colspan=2>XCHG</td>
<td colspan=4>MOV</td>
<td rowspan=2>MOV Ew, Sw</td>
<td rowspan=2>LEA Gv, M</td>
<td rowspan=2>MOV Sw, Ew</td>
<td rowspan=2>POP Ev</td>
</tr>
<tr>
<!-- Immediate Grp1 -->
<td>Eb, Ib</td>
<td>Ev, Iv</td>
<!-- TEST -->
<td>Eb, Gb</td>
<td>Ev, Gv</td>
<!-- XCHG -->
<td>Eb, Gb</td>
<td>Ev, Gv</td>
<!-- MOV -->
<td>Eb, Gb</td>
<td>Ev, Gv</td>
<td>Gb, Eb</td>
<td>Gv, Ev</td>
</tr>
<tr>
<th rowspan=2>9x</th>
<td rowspan=2>NOP</td>
<td colspan=7>XCHG word or double-word register with eAX</td>
<td rowspan=2>CBW</td>
<td rowspan=2>CWD</td>
<td rowspan=2>CALL Ap</td>
<td rowspan=2>WAIT</td>
<td rowspan=2>PUSHF Fv</td>
<td rowspan=2>POPF Fv</td>
<td rowspan=2>SAHF</td>
<td rowspan=2>LAHF</td>
</tr>
<tr>
<!-- XCHG -->
<td>eCX</td>
<td>eDX</td>
<td>eBX</td>
<td>eSP</td>
<td>eBP</td>
<td>eSI</td>
<td>eDI</td>
</tr>
<tr>
<th rowspan=2>Ax</th>
<td colspan=4>MOV</td>
<td rowspan=2>MOVSB Xb, Yv</td>
<td rowspan=2>MOVSW/D Xv, Yv</td>
<td rowspan=2>CMPSB Xb, Yb</td>
<td rowspan=2>CMPSW/D Xv, Yv</td>
<td colspan=2>TEST</td>
<td rowspan=2>STOSB Yb, AL</td>
<td rowspan=2>STOSW/D Yv, eAX</td>
<td rowspan=2>LDSB AL, Xb</td>
<td rowspan=2>LDSW/D eAX, Yv</td>
<td rowspan=2>SCASB AL, Xb</td>
<td rowspan=2>SCASW/D eAX, Xv</td>
</tr>
<tr>
<!-- MOV -->
<td>AL, Ob</td>
<td>eAX, Ov</td>
<td>Ob, AL</td>
<td>Ov, eAX</td>
<!-- TEST -->
<td>AL, Ib</td>
<td>eAX, Iv</td>
</tr>
<tr>
<th rowspan=2>Bx</th>
<td colspan=8>MOV immediate byte into byte register</td>
<td colspan=8>MOV immediate word or double into word or double register</td>
</tr>
<tr>
<td>AL</td>
<td>CL</td>
<td>DL</td>
<td>BL</td>
<td>AH</td>
<td>CH</td>
<td>DH</td>
<td>BH</td>
<td>eAX</td>
<td>eCX</td>
<td>eDX</td>
<td>eBX</td>
<td>eSP</td>
<td>eBP</td>
<td>eSI</td>
<td>eDI</td>
</tr>
<tr>
<th rowspan=2>Cx</th>
<td colspan=2>Shift Grp2</td>
<td colspan=2>RET near</td>
<td rowspan=2>LES Gv, Mp</td>
<td rowspan=2>LDS Gv, Mp</td>
<td colspan=2>MOV</td>
<td rowspan=2>ENTER</td>
<td rowspan=2>LEAVE</td>
<td colspan=2>RET far</td>
<td rowspan=2>INT 3</td>
<td rowspan=2>INT Ib</td>
<td rowspan=2>INTO</td>
<td rowspan=2>IRET</td>
</tr>
<tr>
<td>Eb, Ib</td>
<td>Ev, Iv</td>
<td>Iw</td>
<td></td>
<td>Eb, Ib</td>
<td>Ev, Iv</td>
<td>Iw</td>
<td></td>
</tr>
<tr>
<th rowspan=2>Dx</th>
<td colspan=4>Shift Grp2</td>
<td rowspan=2>AAM</td>
<td rowspan=2>AAD</td>
<td rowspan=2></td>
<td rowspan=2>XLAT</td>
<td colspan=8 rowspan=2>ESC (Escape to coprocessor instruction set)</td>
</tr>
<tr>
<td>Eb, 1</td>
<td>Ev, 1</td>
<td>Eb, CL</td>
<td>Ev, CL</td>
</tr>
<tr>
<th rowspan=2>Ex</th>
<td rowspan=2>LOOPNE Jb</td>
<td rowspan=2>LOOPE Jb</td>
<td rowspan=2>LOOP Jb</td>
<td rowspan=2>JCXZ Jb</td>
<td colspan=2>IN</td>
<td colspan=2>OUT</td>
<td rowspan=2>CALL Jv</td>
<td colspan=3>JMP</td>
<td colspan=2>IN</td>
<td colspan=2>OUT</td>
</tr>
<tr>
<!-- IN -->
<td>AL, Ib</td>
<td>eAX, Ib</td>
<!-- OUT -->
<td>Ib, AL</td>
<td>Ib, eAX</td>
<!-- JMP -->
<td>Jv</td>
<td>Ap</td>
<td>Jb</td>
<!-- IN -->
<td>AL, DX</td>
<td>eAX, DX</td>
<!-- OUT -->
<td>DX, AL</td>
<td>DX, eAX</td>
</tr>
<tr>
<th rowspan=2>Fx</th>
<td rowspan=2>LOCK</td>
<td rowspan=2></td>
<td rowspan=2>REPNE</td>
<td rowspan=2>REP / REPE</td>
<td rowspan=2>HLT</td>
<td rowspan=2>CMC</td>
<td colspan=2>Unary Grp3</td>
<td rowspan=2>CLC</td>
<td rowspan=2>STC</td>
<td rowspan=2>CLI</td>
<td rowspan=2>STI</td>
<td rowspan=2>CLD</td>
<td rowspan=2>STD</td>
<td rowspan=2>INC/DEC Grp4</td>
<td rowspan=2>Indirect Grp5</td>
</tr>
<tr>
<!-- Unary Grp3 -->
<td>Eb</td>
<td>Ev</td>
</tr>
</table>
<h1>Two-Byte 80386 Opcode Map (First byte is 0FH)</h1>
<table class="optable">
<tr>
<th></th>
<th>x0</th>
<th>x1</th>
<th>x2</th>
<th>x3</th>
<th>x4</th>
<th>x5</th>
<th>x6</th>
<th>x7</th>
<th>x8</th>
<th>x9</th>
<th>xA</th>
<th>xB</th>
<th>xC</th>
<th>xD</th>
<th>xE</th>
<th>xF</th>
</tr>
<tr>
<th rowspan=2>0x</th>
<td rowspan=2>Grp6</td>
<td rowspan=2>Grp7</td>
<td rowspan=2>LAR Gv, Ew</td>
<td rowspan=2>LSL Gv, Ew</td>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2>CLTS</td>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2></td>
</tr>
<tr></tr>
<tr>
<th rowspan=2>1x</th>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2></td>
</tr>
<tr></tr>
<tr>
<th rowspan=2>2x</th>
<td rowspan=2>MOV Cd, Rd</td>
<td rowspan=2>MOV Dd, Rd</td>
<td rowspan=2>MOV Rd, Cd</td>
<td rowspan=2>MOV Rd, Dd</td>
<td rowspan=2>MOV Td, Rd</td>
<td rowspan=2></td>
<td rowspan=2>MOV Rd, Td</td>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2></td>
</tr>
<tr></tr>
<tr class="skiprow">
<th rowspan=2 colspan=17></th>
</tr>
<tr></tr>
<tr>
<th rowspan=2>8x</th>
<td colspan=16>Long-displacement jump on condition (Jv)</td>
</tr>
<tr>
<!-- Long-displacement jump on condition (Jv) -->
<td>JO</td>
<td>JNO</td>
<td>JB</td>
<td>JNB</td>
<td>JZ</td>
<td>JNZ</td>
<td>JBE</td>
<td>JNBE</td>
<td>JS</td>
<td>JNS</td>
<td>JP</td>
<td>JNP</td>
<td>JL</td>
<td>JNL</td>
<td>JLE</td>
<td>JNLE</td>
</tr>
<tr>
<th rowspan=2>9x</th>
<td colspan=16>Byte set on condition (Eb)</td>
</tr>
<tr>
<!-- Byte set on condition (Eb) -->
<td>SETO</td>
<td>SETNO</td>
<td>SETB</td>
<td>SETNB</td>
<td>SETZ</td>
<td>SETNZ</td>
<td>SETBE</td>
<td>SETNBE</td>
<td>SETS</td>
<td>SETNS</td>
<td>SETP</td>
<td>SETNP</td>
<td>SETL</td>
<td>SETNL</td>
<td>SETLE</td>
<td>SETNLE</td>
</tr>
<tr>
<th rowspan=2>Ax</th>
<td rowspan=2>PUSH FS</td>
<td rowspan=2>POP FS</td>
<td rowspan=2></td>
<td rowspan=2>BT Ev, Gv</td>
<td rowspan=2>SHLD EvGvIb</td>
<td rowspan=2>SHLD EvGvCL</td>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2>PUSH GS</td>
<td rowspan=2>POP GS</td>
<td rowspan=2></td>
<td rowspan=2>BTS Ev, Gv</td>
<td rowspan=2>SHRD EvGvIb</td>
<td rowspan=2>SHRD EvGvCL</td>
<td rowspan=2></td>
<td rowspan=2>IMUL Gv, Ev</td>
</tr>
<tr></tr>
<tr>
<th rowspan=2>Bx</th>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2>LSS Mp</td>
<td rowspan=2>BTR Ev, Gv</td>
<td rowspan=2>LFS Mp</td>
<td rowspan=2>LGS Mp</td>
<td colspan=2>MOVZX</td>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2>Grp8 Ev, Ib</td>
<td rowspan=2>BTC Ev, Gv</td>
<td rowspan=2>BSF Gv, Ev</td>
<td rowspan=2>BSR Gv, Ev</td>
<td colspan=2>MOVSX</td>
</tr>
<tr>
<!-- MOVZX -->
<td>Gv, Eb</td>
<td>Gv, Ew</td>
<!-- MOVSX -->
<td>Gv, Eb</td>
<td>Gv, Ew</td>
</tr>
<tr class="skiprow">
<th rowspan=2 colspan=17></th>
</tr>
<tr></tr>
<tr>
<th rowspan=2>Fx</th>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2></td>
<td rowspan=2></td>
</tr>
<tr></tr>
</table>
<h1>Opcodes Determined by Bits 5, 4, 3 of MODRM Field</h1>
<table>
<tr>
<td>mod</td>
<td>nnn</td>
<td>R/M</td>
</tr>
</table>
<br />
<table class="grouptable">
<tr>
<th></th>
<th>000</th>
<th>001</th>
<th>010</th>
<th>011</th>
<th>100</th>
<th>101</th>
<th>110</th>
<th>111</th>
</tr>
<tr>
<th>Group 1</th>
<td>ADD</td>
<td>OR</td>
<td>ADC</td>
<td>SBB</td>
<td>AND</td>
<td>SUB</td>
<td>XOR</td>
<td>CMP</td>
</tr>
<tr>
<th>Group 2</th>
<td>ROL</td>
<td>ROR</td>
<td>RCL</td>
<td>RCR</td>
<td>SHL</td>
<td>SHR</td>
<td></td>
<td>SAR</td>
</tr>
<tr>
<th>Group 3</th>
<td>TEST Ib/Iv</td>
<td></td>
<td>NOT</td>
<td>NEG</td>
<td>MUL AL/eAX</td>
<td>IMUL AL/EAX</td>
<td>DIV AL/eAX</td>
<td>IDIV AL/eAX</td>
</tr>
<tr>
<th>Group 4</th>
<td>INC Eb</td>
<td>DEC Eb</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<th>Group 5</th>
<td>INC Ev</td>
<td>DEC Ev</td>
<td>CALL Ev</td>
<td>CALL Ep</td>
<td>JMP Ev</td>
<td>JMP Ep</td>
<td>PUSH Ev</td>
<td></td>
</tr>
<tr>
<th>Group 6</th>
<td>SLDT Ew</td>
<td>STR Ew</td>
<td>LLDT Ew</td>
<td>LTR Ew</td>
<td>VERR Ew</td>
<td>VERW Ew</td>
<td></td>
<td></td>
</tr>
<tr>
<th>Group 7</th>
<td>SGDT Ms</td>
<td>SIDT Ms</td>
<td>LGDT Ms</td>
<td>LIDT Ms</td>
<td>SMSW Ew</td>
<td></td>
<td>LMSW Ew</td>
<td></td>
</tr>
<tr>
<th>Group 8</th>
<td></td>
<td></td>
<td></td>
<td></td>
<td>BT</td>
<td>BTS</td>
<td>BTR</td>
<td>BTC</td>
</tr>
</table>
</body>
</html>

View File

@@ -9,7 +9,9 @@
#ifndef InstructionSets_x86_Instruction_h
#define InstructionSets_x86_Instruction_h
#include <cstddef>
#include <cstdint>
#include <type_traits>
namespace InstructionSet {
namespace x86 {
@@ -23,6 +25,10 @@ namespace x86 {
enum class Operation: uint8_t {
Invalid,
//
// 8086 instructions.
//
/// 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.
@@ -36,9 +42,13 @@ enum class Operation: uint8_t {
/// Decimal adjust after subtraction; source and destination will be AL.
DAS,
/// Convert byte into word; source will be AL, destination will be AH.
/// If data size is word, convert byte into word; source will be AL, destination will be AH.
/// If data size is DWord, convert word to dword; AX will be expanded to fill EAX.
/// In both cases, conversion will be by sign extension.
CBW,
/// Convert word to double word; source will be AX and destination will be DX.
/// If data size is Word, converts word to double word; source will be AX and destination will be DX.
/// If data size is DWord, converts double word to quad word (i.e. CDW); source will be EAX and destination will be EDX:EAX.
/// In both cases, conversion will be by sign extension.
CWD,
/// Escape, for a coprocessor; perform the bus cycles necessary to read the source and destination and perform a NOP.
@@ -59,8 +69,8 @@ enum class Operation: uint8_t {
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,
/// Single operand signed multiply; multiplies the source value by AX or AL, storing the result in DX:AX or AX.
IMUL_1,
/// 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.
@@ -81,27 +91,27 @@ enum class Operation: uint8_t {
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,
CALLfar,
/// Relative call; see displacement().
CALLrel,
/// Near call.
CALLN,
CALLabs,
/// 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,
RETfar,
/// 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,
RETnear,
/// Near jump with an absolute destination.
JMPabs,
/// Near jump with a relative destination.
JMPrel,
/// Far jump to the indicated segment and offset.
JMPF,
JMPfar,
/// 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,
@@ -152,19 +162,19 @@ enum class Operation: uint8_t {
PUSH,
/// PUSH the flags register to the stack.
PUSHF,
/// Rotate the destination left through carry the number of bits indicated by source.
/// Rotate the destination left through carry the number of bits indicated by source; if the source is a register then implicitly its size is 1.
RCL,
/// Rotate the destination right through carry the number of bits indicated by source.
/// Rotate the destination right through carry the number of bits indicated by source; if the source is a register then implicitly its size is 1.
RCR,
/// Rotate the destination left the number of bits indicated by source.
/// Rotate the destination left the number of bits indicated by source; if the source is a register then implicitly its size is 1.
ROL,
/// Rotate the destination right the number of bits indicated by source.
/// Rotate the destination right the number of bits indicated by source; if the source is a register then implicitly its size is 1.
ROR,
/// Arithmetic shift left the destination by the number of bits indicated by source.
/// Arithmetic shift left the destination by the number of bits indicated by source; if the source is a register then implicitly its size is 1.
SAL,
/// Arithmetic shift right the destination by the number of bits indicated by source.
/// Arithmetic shift right the destination by the number of bits indicated by source; if the source is a register then implicitly its size is 1.
SAR,
/// Logical shift right the destination by the number of bits indicated by source.
/// Logical shift right the destination by the number of bits indicated by source; if the source is a register then implicitly its size is 1.
SHR,
/// Clear carry flag; no source or destination provided.
@@ -192,110 +202,599 @@ enum class Operation: uint8_t {
/// Load AL with DS:[AL+BX].
XLAT,
//
// 80186 additions.
//
/// Checks whether the signed value in the destination register is within the bounds
/// stored at the location indicated by the source register, which will point to two
/// 16- or 32-bit words, the first being a signed lower bound and the signed upper.
/// Raises a bounds exception if not.
BOUND,
/// Create stack frame. See operand() for the nesting level and offset()
/// for the dynamic storage size.
ENTER,
/// Procedure exit; copies BP to SP, then pops a new BP from the stack.
LEAVE,
/// Inputs a byte, word or double word from the port specified by DX, writing it to
/// ES:[e]DI and incrementing or decrementing [e]DI as per the
/// current EFLAGS DF flag.
INS,
/// Outputs a byte, word or double word from ES:[e]DI to the port specified by DX,
/// incrementing or decrementing [e]DI as per the current EFLAGS DF flag.]
OUTS,
/// Pushes all general purpose registers to the stack, in the order:
/// AX, CX, DX, BX, [original] SP, BP, SI, DI.
PUSHA,
/// Pops all general purpose registers from the stack, in the reverse of
/// the PUSHA order, i.e. DI, SI, BP, [final] SP, BX, DX, CX, AX.
POPA,
//
// 80286 additions.
//
// TODO: expand detail on all operations below.
/// Adjusts requested privilege level.
ARPL,
/// Clears the task-switched flag.
CLTS,
/// Loads access rights.
LAR,
/// Loads the global descriptor table.
LGDT,
/// Loads the interrupt descriptor table.
LIDT,
/// Loads the local descriptor table.
LLDT,
/// Stores the global descriptor table.
SGDT,
/// Stores the interrupt descriptor table.
SIDT,
/// Stores the local descriptor table.
SLDT,
/// Verifies a segment for reading.
VERR,
/// Verifies a segment for writing.
VERW,
/// Loads the machine status word.
LMSW,
/// Stores the machine status word.
SMSW,
/// Loads a segment limit
LSL,
/// Loads the task register.
LTR,
/// Stores the task register.
STR,
/// Three-operand form of IMUL; multiply the immediate by the source and write to the destination.
IMUL_3,
/// Undocumented (but used); loads all registers, including internal ones.
LOADALL,
//
// 80386 additions.
//
/// Loads a pointer to FS.
LFS,
/// Loads a pointer to GS.
LGS,
/// Loads a pointer to SS.
LSS,
/// Shift left double.
SHLDimm,
SHLDCL,
/// Shift right double.
SHRDimm,
SHRDCL,
/// Bit scan forwards.
BSF,
/// Bit scan reverse.
BSR,
/// Bit test.
BT,
/// Bit test and complement.
BTC,
/// Bit test and reset.
BTR,
/// Bit test and set.
BTS,
/// Move from the source to the destination, extending the source with zeros.
/// The instruction data size dictates the size of the source; the destination will
/// be either 16- or 32-bit depending on the current processor operating mode.
MOVZX,
/// Move from the source to the destination, applying a sign extension.
/// The instruction data size dictates the size of the source; the destination will
/// be either 16- or 32-bit depending on the current processor operating mode.
MOVSX,
/// Two-operand form of IMUL; multiply the source by the destination and write to the destination.
IMUL_2,
// Various conditional sets; each sets the byte at the location given by the operand
// to $ff if the condition is met; $00 otherwise.
SETO, SETNO, SETB, SETNB, SETZ, SETNZ, SETBE, SETNBE,
SETS, SETNS, SETP, SETNP, SETL, SETNL, SETLE, SETNLE,
// Various special-case moves (i.e. those where it is impractical to extend the
// Source enum, so the requirement for special handling is loaded into the operation).
// In all cases the Cx, Dx and Tx Source aliases can be used to reinterpret the relevant
// source or destination.
MOVtoCr, MOVfromCr,
MOVtoDr, MOVfromDr,
MOVtoTr, MOVfromTr,
};
enum class Size: uint8_t {
Implied = 0,
Byte = 1,
Word = 2,
DWord = 4,
enum class DataSize: uint8_t {
Byte = 0,
Word = 1,
DWord = 2,
None = 3,
};
constexpr int byte_size(DataSize size) {
return (1 << int(size)) & 7;
}
constexpr int bit_size(DataSize size) {
return (8 << int(size)) & 0x3f;
}
enum class AddressSize: uint8_t {
b16 = 0,
b32 = 1,
};
constexpr DataSize data_size(AddressSize size) {
return DataSize(int(size) + 1);
}
constexpr int byte_size(AddressSize size) {
return 2 << int(size);
}
constexpr int bit_size(AddressSize size) {
return 16 << int(size);
}
enum class Source: uint8_t {
// These are in SIB order; this matters for packing later on.
/// AL, AX or EAX depending on size.
eAX,
/// CL, CX or ECX depending on size.
eCX,
/// DL, DX or EDX depending on size.
eDX,
/// BL, BX or BDX depending on size.
eBX,
/// AH if size is 1; SP or ESP otherwise.
eSPorAH,
/// CH if size is 1; BP or EBP otherwise.
eBPorCH,
/// DH if size is 1; SI or ESI otherwise.
eSIorDH,
/// BH if size is 1; DI or EDI otherwise.
eDIorBH,
// Aliases for the dual-purpose enums.
eSP = eSPorAH, AH = eSPorAH,
eBP = eBPorCH, CH = eBPorCH,
eSI = eSIorDH, DH = eSIorDH,
eDI = eDIorBH, BH = eDIorBH,
// Aliases for control, test and debug registers.
C0 = 0, C1 = 1, C2 = 2, C3 = 3, C4 = 4, C5 = 5, C6 = 6, C7 = 7,
T0 = 0, T1 = 1, T2 = 2, T3 = 3, T4 = 4, T5 = 5, T6 = 6, T7 = 7,
D0 = 0, D1 = 1, D2 = 2, D3 = 3, D4 = 4, D5 = 5, D6 = 6, D7 = 7,
// Selectors.
ES, CS, SS, DS, FS, GS,
/// @c None can be treated as a source that produces 0 when encountered;
/// it is semantically valid to receive it with that meaning in some contexts —
/// e.g. to indicate no index in indirect addressing.
/// It's listed here in order to allow an [optional] segment override to fit into three bits.
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,
/// The address included within this instruction should be used as the source.
DirectAddress,
IndBP,
IndBX,
Immediate
/// The immediate value included within this instruction should be used as the source.
Immediate,
/// The ScaleIndexBase associated with this source should be used.
Indirect = 0b11000,
// Elsewhere, as an implementation detail, the low three bits of an indirect source
// are reused; (Indirect-1) is also used as a sentinel value but is not a valid member
// of the enum and isn't exposed externally.
/// The ScaleIndexBase associated with this source should be used, but
/// its base should be ignored (and is guaranteed to be zero if the default
/// getter is used).
IndirectNoBase = Indirect - 1,
};
enum class Repetition: uint8_t {
None, RepE, RepNE
};
class Instruction {
/// Provides a 32-bit-style scale, index and base; to produce the address this represents,
/// calcluate base() + (index() << scale()).
///
/// This form of indirect addressing is used to describe both 16- and 32-bit indirect addresses,
/// even though it is a superset of that supported prior to the 80386.
///
/// This class can represent only exactly what a SIB byte can — a scale of 0 to 3, a base
/// that is any one of the eight general purpose registers, and an index that is one of the seven
/// general purpose registers excluding eSP or is ::None.
///
/// It cannot natively describe a base of ::None.
class ScaleIndexBase {
public:
Operation operation = Operation::Invalid;
constexpr ScaleIndexBase() noexcept {}
constexpr ScaleIndexBase(uint8_t sib) noexcept : sib_(sib) {}
constexpr ScaleIndexBase(int scale, Source index, Source base) noexcept :
sib_(uint8_t(
scale << 6 |
(int(index != Source::None ? index : Source::eSI) << 3) |
int(base)
)) {}
constexpr ScaleIndexBase(Source index, Source base) noexcept : ScaleIndexBase(0, index, base) {}
constexpr explicit ScaleIndexBase(Source base) noexcept : ScaleIndexBase(0, Source::None, base) {}
bool operator ==(const Instruction &rhs) const {
/// @returns the power of two by which to multiply @c index() before adding it to @c base().
constexpr int scale() const {
return sib_ >> 6;
}
/// @returns the @c index for this address; this is guaranteed to be one of eAX, eBX, eCX, eDX, None, eBP, eSI or eDI.
constexpr Source index() const {
constexpr Source sources[] = {
Source::eAX, Source::eCX, Source::eDX, Source::eBX, Source::None, Source::eBP, Source::eSI, Source::eDI,
};
static_assert(sizeof(sources) == 8);
return sources[(sib_ >> 3) & 0x7];
}
/// @returns the @c base for this address; this is guaranteed to be one of eAX, eBX, eCX, eDX, eSP, eBP, eSI or eDI.
constexpr Source base() const {
return Source(sib_ & 0x7);
}
constexpr uint8_t without_base() const {
return sib_ & ~0x3;
}
bool operator ==(const ScaleIndexBase &rhs) const {
// Permit either exact equality or index and base being equal
// but transposed with a scale of 1.
return
repetition_size_ == rhs.repetition_size_ &&
sources_ == rhs.sources_ &&
displacement_ == rhs.displacement_ &&
operand_ == rhs.operand_;
(sib_ == rhs.sib_) ||
(
!scale() && !rhs.scale() &&
rhs.index() == base() &&
rhs.base() == index()
);
}
operator uint8_t() const {
return sib_;
}
private:
// b0, b1: a Repetition;
// b2+: operation size.
uint8_t repetition_size_ = 0;
// Data is stored directly as an 80386 SIB byte.
uint8_t sib_ = 0;
};
static_assert(sizeof(ScaleIndexBase) == 1);
static_assert(alignof(ScaleIndexBase) == 1);
// b0b5: source;
// b6b11: destination;
// b12b14: segment override;
// b15: lock.
uint16_t sources_ = 0;
/// Provides the location of an operand's source or destination.
///
/// Callers should use .source() as a first point of entry. If it directly nominates a register
/// then use the register contents directly. If it indicates ::DirectAddress or ::Immediate
/// then ask the instruction for the address or immediate value that was provided in
/// the instruction.
///
/// If .source() indicates ::Indirect then use base(), index() and scale() to construct an address.
///
/// In all cases, the applicable segment is indicated by the instruction.
class DataPointer {
public:
/// Constricts a DataPointer referring to the given source; it shouldn't be ::Indirect.
constexpr DataPointer(Source source) noexcept : source_(source) {}
// Unpackable fields.
int16_t displacement_ = 0;
uint16_t operand_ = 0; // ... or used to store a segment for far operations.
/// Constricts a DataPointer with a source of ::Indirect and the specified sib.
constexpr DataPointer(ScaleIndexBase sib) noexcept : sib_(sib) {}
/// Constructs a DataPointer with a source and SIB; use the source to indicate
/// whether the base field of the SIB is effective.
constexpr DataPointer(Source source, ScaleIndexBase sib) noexcept : source_(source), sib_(sib) {}
/// Constructs an indirect DataPointer referencing the given base, index and scale.
/// Automatically maps Source::Indirect to Source::IndirectNoBase if base is Source::None.
constexpr DataPointer(Source base, Source index, int scale) noexcept :
source_(base != Source::None ? Source::Indirect : Source::IndirectNoBase),
sib_(scale, index, base) {}
constexpr bool operator ==(const DataPointer &rhs) const {
// Require a SIB match only if source_ is ::Indirect or ::IndirectNoBase.
return
source_ == rhs.source_ && (
source_ < Source::IndirectNoBase ||
(source_ == Source::Indirect && sib_ == rhs.sib_) ||
(source_ == Source::IndirectNoBase && sib_.without_base() == rhs.sib_.without_base())
);
}
template <bool obscure_indirectNoBase = false> constexpr Source source() const {
if constexpr (obscure_indirectNoBase) {
return (source_ >= Source::IndirectNoBase) ? Source::Indirect : source_;
}
return source_;
}
constexpr int scale() const {
return sib_.scale();
}
constexpr Source index() const {
return sib_.index();
}
template <bool obscure_indirectNoBase = false> constexpr Source base() const {
if constexpr (obscure_indirectNoBase) {
return (source_ <= Source::IndirectNoBase) ? Source::None : sib_.base();
}
return sib_.base();
}
private:
Source source_ = Source::Indirect;
ScaleIndexBase sib_;
};
template<bool is_32bit> class Instruction {
public:
Operation operation = Operation::Invalid;
bool operator ==(const Instruction<is_32bit> &rhs) const {
if( operation != rhs.operation ||
mem_exts_source_ != rhs.mem_exts_source_ ||
source_data_dest_sib_ != rhs.source_data_dest_sib_) {
return false;
}
// Have already established above that this and RHS have the
// same extensions, if any.
const int extension_count = has_length_extension() + has_displacement() + has_operand();
for(int c = 0; c < extension_count; c++) {
if(extensions_[c] != rhs.extensions_[c]) return false;
}
return true;
}
using DisplacementT = typename std::conditional<is_32bit, int32_t, int16_t>::type;
using ImmediateT = typename std::conditional<is_32bit, uint32_t, uint16_t>::type;
using AddressT = ImmediateT;
private:
// Packing and encoding of fields is admittedly somewhat convoluted; what this
// achieves is that instructions will be sized:
//
// four bytes + up to three extension words
// (two bytes for 16-bit instructions, four for 32)
//
// Two of the extension words are used to retain an operand and displacement
// if the instruction has those. The other can store sizes greater than 15
// bytes (for earlier processors), plus any repetition, segment override or
// repetition prefixes.
// b7: address size;
// b6: has displacement;
// b5: has operand;
// [b4, b0]: source.
uint8_t mem_exts_source_ = 0;
bool has_displacement() const {
return mem_exts_source_ & (1 << 6);
}
bool has_operand() const {
return mem_exts_source_ & (1 << 5);
}
// [b15, b14]: data size;
// [b13, b10]: source length (0 => has length extension);
// [b9, b5]: top five of SIB;
// [b4, b0]: dest.
uint16_t source_data_dest_sib_ = 1 << 10; // So that ::Invalid doesn't seem to have a length extension.
bool has_length_extension() const {
return !((source_data_dest_sib_ >> 10) & 15);
}
// {operand}, {displacement}, {length extension}.
//
// If length extension is present then:
//
// [b15, b6]: source length;
// [b5, b4]: repetition;
// [b3, b1]: segment override;
// b0: lock.
ImmediateT extensions_[3]{};
ImmediateT operand_extension() const {
return extensions_[0];
}
ImmediateT displacement_extension() const {
return extensions_[(mem_exts_source_ >> 5) & 1];
}
ImmediateT length_extension() const {
return extensions_[((mem_exts_source_ >> 5) & 1) + ((mem_exts_source_ >> 6) & 1)];
}
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); }
/// @returns The number of bytes used for meaningful content within this class. A receiver must use at least @c sizeof(Instruction) bytes
/// to store an @c Instruction but is permitted to reuse the trailing sizeof(Instruction) - packing_size() for any purpose it likes. Teleologically,
/// this allows a denser packing of instructions into containers.
size_t packing_size() const {
return
offsetof(Instruction<is_32bit>, extensions) +
(has_displacement() + has_operand() + has_length_extension()) * sizeof(ImmediateT);
Repetition repetition() const { return Repetition(repetition_size_ & 3); }
Size operation_size() const { return Size(repetition_size_ >> 2); }
// To consider in the future: the length extension is always the last one,
// and uses only 8 bits of content within 32-bit instructions, so it'd be
// possible further to trim the packing size on little endian machines.
//
// ... but is that a speed improvement? How much space does it save, and
// is it enough to undo the costs of unaligned data?
}
uint16_t segment() const { return uint16_t(operand_); }
uint16_t offset() const { return uint16_t(displacement_); }
private:
// A lookup table to help with stripping parts of the SIB that have been
// hidden within the source/destination fields.
static constexpr uint8_t sib_masks[] = {
0x1f, 0x1f, 0x1f, 0x18
};
int16_t displacement() const { return displacement_; }
uint16_t operand() const { return operand_; }
public:
DataPointer source() const {
return DataPointer(
Source(mem_exts_source_ & sib_masks[(mem_exts_source_ >> 3) & 3]),
((source_data_dest_sib_ >> 2) & 0xf8) | (mem_exts_source_ & 0x07)
);
}
DataPointer destination() const {
return DataPointer(
Source(source_data_dest_sib_ & sib_masks[(source_data_dest_sib_ >> 3) & 3]),
((source_data_dest_sib_ >> 2) & 0xf8) | (source_data_dest_sib_ & 0x07)
);
}
bool lock() const {
return has_length_extension() && length_extension()&1;
}
Instruction() noexcept {}
Instruction(
AddressSize address_size() const {
return AddressSize(mem_exts_source_ >> 7);
}
/// @returns @c Source::DS if no segment override was found; the overridden segment otherwise.
/// On x86 a segment override cannot modify the segment used as a destination in string instructions,
/// or that used by stack instructions, but this function does not spend the time necessary to provide
/// the correct default for those.
Source data_segment() const {
if(!has_length_extension()) return Source::DS;
return Source(
int(Source::ES) +
((length_extension() >> 1) & 7)
);
}
Repetition repetition() const {
if(!has_length_extension()) return Repetition::None;
return Repetition((length_extension() >> 4) & 3);
}
DataSize operation_size() const {
return DataSize(source_data_dest_sib_ >> 14);
}
int length() const {
const int short_length = (source_data_dest_sib_ >> 10) & 15;
if(short_length) return short_length;
return length_extension() >> 6;
}
ImmediateT operand() const {
const ImmediateT ops[] = {0, operand_extension()};
return ops[has_operand()];
}
DisplacementT displacement() const {
return DisplacementT(offset());
}
uint16_t segment() const {
return uint16_t(operand());
}
ImmediateT offset() const {
const ImmediateT offsets[] = {0, displacement_extension()};
return offsets[has_displacement()];
}
constexpr Instruction() noexcept {}
constexpr Instruction(Operation operation, int length) noexcept :
Instruction(operation, Source::None, Source::None, ScaleIndexBase(), false, AddressSize::b16, Source::None, Repetition::None, DataSize::None, 0, 0, length) {}
constexpr Instruction(
Operation operation,
Source source,
Source destination,
ScaleIndexBase sib,
bool lock,
AddressSize address_size,
Source segment_override,
Repetition repetition,
Size operation_size,
int16_t displacement,
uint16_t operand) noexcept :
DataSize data_size,
DisplacementT displacement,
ImmediateT operand,
int length) noexcept :
operation(operation),
repetition_size_(uint8_t((int(operation_size) << 2) | int(repetition))),
sources_(uint16_t(
mem_exts_source_(uint8_t(
(int(address_size) << 7) |
(displacement ? 0x40 : 0x00) |
(operand ? 0x20 : 0x00) |
int(source) |
(int(destination) << 6) |
(int(segment_override) << 12) |
(int(lock) << 15)
(source == Source::Indirect ? (uint8_t(sib) & 7) : 0)
)),
displacement_(displacement),
operand_(operand) {}
source_data_dest_sib_(uint16_t(
(int(data_size) << 14) |
((
(lock || (segment_override != Source::None) || (length > 15) || (repetition != Repetition::None))
) ? 0 : (length << 10)) |
((uint8_t(sib) & 0xf8) << 2) |
int(destination) |
(destination == Source::Indirect ? (uint8_t(sib) & 7) : 0)
)) {
// Decisions on whether to include operand, displacement and/or size extension words
// have implicitly been made in the int packing above; honour them here.
int extension = 0;
if(has_operand()) {
extensions_[extension] = operand;
++extension;
}
if(has_displacement()) {
extensions_[extension] = ImmediateT(displacement);
++extension;
}
if(has_length_extension()) {
// As per the rule stated for segment(), this class provides ::DS for any instruction
// that doesn't have a segment override.
if(segment_override == Source::None) segment_override = Source::DS;
extensions_[extension] = ImmediateT(
(length << 6) | (int(repetition) << 4) | ((int(segment_override) & 7) << 1) | int(lock)
);
++extension;
}
}
};
static_assert(sizeof(Instruction) <= 8);
static_assert(sizeof(Instruction<true>) <= 16);
static_assert(sizeof(Instruction<false>) <= 10);
}
}

View File

@@ -0,0 +1,27 @@
//
// Model.hpp
// Clock Signal
//
// Created by Thomas Harte on 27/02/2022.
// Copyright © 2022 Thomas Harte. All rights reserved.
//
#ifndef Model_h
#define Model_h
namespace InstructionSet {
namespace x86 {
enum class Model {
i8086,
i80186,
i80286,
i80386,
};
static constexpr bool is_32bit(Model model) { return model >= Model::i80386; }
}
}
#endif /* Model_h */

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

@@ -0,0 +1,262 @@
//
// 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/68000Mk2/68000Mk2.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::MC68000Mk2::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),
memory_(target.chip_ram, target.fast_ram),
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.
template <typename Microcycle> HalfCycles perform_bus_operation(const 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().registers.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 if(address >= 0xe8'0000 && address < 0xe9'0000) {
// This is the Autoconf space; right now the only
// Autoconf device this emulator implements is fast RAM,
// which if present is provided as part of the memory map.
//
// Relevant quote: "The Zorro II configuration space is the 64K memory block $00E8xxxx"
memory_.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::MC68000Mk2::Processor<ConcreteMachine, true, 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);
flush();
}
// 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 */

1276
Machines/Amiga/Chipset.cpp Normal file

File diff suppressed because it is too large Load Diff

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

@@ -0,0 +1,372 @@
//
// 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/68000Mk2/68000Mk2.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::MC68000Mk2::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;
bool display_horizontal_ = false;
bool did_fetch_ = false;
int horizontal_offset_ = 0;
enum HorizontalFetch {
Started, WillRequestStop, StopRequested, Stopped
} horizontal_fetch_ = HorizontalFetch::Stopped;
// 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);
void set_data(uint16_t);
uint16_t get_status();
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 */

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

@@ -0,0 +1,143 @@
//
// 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.
if(!(instruction_[1] & 1)) {
// A WAIT. The wait-for-start-of-next PAL wait of
// $FFDF,$FFFE seems to suggest evaluation will happen
// in the next cycle rather than this one.
state_ = State::Waiting;
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 */

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

@@ -0,0 +1,263 @@
//
// Disk.cpp
// Clock Signal
//
// Created by Thomas Harte on 02/11/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "Chipset.hpp"
#ifndef NDEBUG
#define NDEBUG
#endif
#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,196 @@
//
// 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
#include "../../Analyser/Static/Amiga/Target.hpp"
#include <array>
#include <cassert>
#include <vector>
namespace Amiga {
class MemoryMap {
private:
static constexpr auto PermitRead = CPU::MC68000Mk2::Microcycle::PermitRead;
static constexpr auto PermitWrite = CPU::MC68000Mk2::Microcycle::PermitWrite;
static constexpr auto PermitReadWrite = PermitRead | PermitWrite;
public:
std::array<uint8_t, 512*1024> kickstart{0xff};
std::vector<uint8_t> chip_ram{};
struct MemoryRegion {
uint8_t *contents = nullptr;
unsigned int read_write_mask = 0;
} regions[64]; // i.e. top six bits are used as an index.
using FastRAM = Analyser::Static::Amiga::Target::FastRAM;
using ChipRAM = Analyser::Static::Amiga::Target::ChipRAM;
MemoryMap(ChipRAM chip_ram_size, FastRAM fast_ram_size) {
// 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);
switch(chip_ram_size) {
default:
case ChipRAM::FiveHundredAndTwelveKilobytes:
chip_ram.resize(512 * 1024);
break;
case ChipRAM::OneMegabyte:
chip_ram.resize(1 * 1024 * 1024);
break;
case ChipRAM::TwoMegabytes:
chip_ram.resize(2 * 1024 * 1024);
break;
}
switch(fast_ram_size) {
default:
fast_autoconf_visible_ = false;
break;
case FastRAM::OneMegabyte:
fast_ram_.resize(1 * 1024 * 1024);
fast_ram_size_ = 5;
break;
case FastRAM::TwoMegabytes:
fast_ram_.resize(2 * 1024 * 1024);
fast_ram_size_ = 6;
break;
case FastRAM::FourMegabytes:
fast_ram_.resize(4 * 1024 * 1024);
fast_ram_size_ = 7;
break;
case FastRAM::EightMegabytes:
fast_ram_.resize(8 * 1024 * 1024);
fast_ram_size_ = 0;
break;
}
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);
}
}
/// Performs the provided microcycle, which the caller guarantees to be a memory access,
/// and in the Zorro register range.
bool perform(const CPU::MC68000Mk2::Microcycle &cycle) {
if(!fast_autoconf_visible_) return false;
const uint32_t register_address = *cycle.address & 0xfe;
using Microcycle = CPU::MC68000Mk2::Microcycle;
if(cycle.operation & Microcycle::Read) {
// Re: Autoconf:
//
// "All read registers physically return only the top 4 bits of data, on D31-D28";
// (this is from Zorro III documentation; I'm assuming it to be D15D11 for the
// 68000's 16-bit bus);
//
// "Every AUTOCONFIG register is logically considered to be 8 bits wide; the
// 8 bits actually being nybbles from two paired addresses."
uint8_t value = 0xf;
switch(register_address) {
default: break;
case 0x00: // er_Type (high)
value =
0xc | // Zoro II-style PIC.
0x2; // Memory will be linked into the free pool
break;
case 0x02: // er_Type (low)
value = fast_ram_size_;
break;
// er_Manufacturer
//
// On the manufacturer number: this is supposed to be assigned
// by Commodore. TODO: find and crib a real fast RAM number, if it matters.
//
// (0xffff seems to be invalid, so _something_ needs to be supplied)
case 0x10: case 0x12:
value = 0xa; // Manufacturer's number, high byte.
break;
case 0x14: case 0x16:
value = 0xb; // Manufacturer's number, low byte.
break;
}
// Shove the value into the top of the data bus.
cycle.set_value16(uint16_t(0x0fff | (value << 12)));
} else {
fast_autoconf_visible_ &= !(register_address >= 0x4c && register_address < 0x50);
switch(register_address) {
default: break;
case 0x48: { // ec_BaseAddress (A23A16)
const auto address = uint32_t(cycle.value8_high()) << 16;
set_region(address, uint32_t(address + fast_ram_.size()), fast_ram_.data(), PermitRead | PermitWrite);
fast_autoconf_visible_ = false;
} break;
}
}
return true;
}
private:
std::vector<uint8_t> fast_ram_{};
uint8_t fast_ram_size_ = 0;
bool fast_autoconf_visible_ = true;
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

@@ -158,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_;
};
@@ -787,45 +787,44 @@ 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;
using Model = Analyser::Static::AmstradCPC::Target::Model;
switch(target.model) {
case Model::CPC464:
firmware = ROM::Name::CPC464Firmware;
basic = ROM::Name::CPC464BASIC;
break;
case Model::CPC664:
firmware = ROM::Name::CPC664Firmware;
basic = ROM::Name::CPC664BASIC;
has_amsdos = true;
break;
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;
break;
case Analyser::Static::AmstradCPC::Target::Model::CPC664:
model_number = "664";
has_128k_ = false;
crcs[0] = 0x3f5a6dc4;
crcs[1] = 0x32fee492;
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;
@@ -840,6 +839,9 @@ template <bool has_fdc> class ConcreteMachine:
read_pointers_[2] = write_pointers_[2];
read_pointers_[3] = roms_[upper_rom_].data();
// Set total RAM available.
has_128k_ = target.model == Model::CPC6128;
// Type whatever is required.
if(!target.loading_command.empty()) {
type_string(target.loading_command);
@@ -1121,7 +1123,7 @@ template <bool has_fdc> class ConcreteMachine:
}
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.
@@ -1250,20 +1252,20 @@ template <bool has_fdc> class ConcreteMachine:
HalfCycles crtc_counter_;
HalfCycles half_cycles_since_ay_update_;
bool fdc_is_sleeping_;
bool tape_player_is_sleeping_;
bool has_128k_;
bool fdc_is_sleeping_ = false;
bool tape_player_is_sleeping_ = false;
bool has_128k_ = false;
enum ROMType: int {
AMSDOS = 0, OS = 1, BASIC = 2
};
std::vector<uint8_t> roms_[3];
bool upper_rom_is_paged_;
bool upper_rom_is_paged_ = false;
ROMType upper_rom_;
uint8_t *ram_pages_[4];
const uint8_t *read_pointers_[4];
uint8_t *write_pointers_[4];
uint8_t *ram_pages_[4]{};
const uint8_t *read_pointers_[4]{};
uint8_t *write_pointers_[4]{};
KeyboardState key_state_;
AmstradCPC::KeyboardMapper keyboard_mapper_;

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;
};

View File

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

View File

@@ -8,6 +8,8 @@
#include "Mouse.hpp"
#include <algorithm>
using namespace Apple::ADB;
Mouse::Mouse(Bus &bus) : ReactiveDevice(bus, 3) {}
@@ -20,8 +22,8 @@ void Mouse::perform_command(const Command &command) {
const int buttons = button_flags_;
// Clamp deltas.
delta_x = std::max(std::min(delta_x, int16_t(127)), int16_t(-128));
delta_y = std::max(std::min(delta_y, int16_t(127)), int16_t(-128));
delta_x = std::clamp(delta_x, int16_t(-128), int16_t(127));
delta_y = std::clamp(delta_y, int16_t(-128), int16_t(127));
// Figure out what that would look like, and don't respond if there's
// no change to report.

View File

@@ -48,7 +48,6 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
public MachineTypes::MappedKeyboardMachine,
public MachineTypes::JoystickMachine,
public CPU::MOS6502::BusHandler,
public Inputs::Keyboard,
public Configurable::Device,
public Activity::Source,
public Apple::II::Card::Delegate {
@@ -66,7 +65,11 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
uint8_t *ram_, *aux_ram_;
};
CPU::MOS6502::Processor<(model == Analyser::Static::AppleII::Target::Model::EnhancedIIe) ? CPU::MOS6502::Personality::PSynertek65C02 : CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_;
using Processor = CPU::MOS6502::Processor<
(model == Analyser::Static::AppleII::Target::Model::EnhancedIIe) ? CPU::MOS6502::Personality::PSynertek65C02 : CPU::MOS6502::Personality::P6502,
ConcreteMachine,
false>;
Processor m6502_;
VideoBusHandler video_bus_handler_;
Apple::II::Video::Video<VideoBusHandler, is_iie()> video_;
int cycles_into_current_line_ = 0;
@@ -91,20 +94,10 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
uint8_t ram_[65536], aux_ram_[65536];
std::vector<uint8_t> rom_;
uint8_t keyboard_input_ = 0x00;
bool key_is_down_ = false;
uint8_t get_keyboard_input() {
if(string_serialiser_) {
return string_serialiser_->head() | 0x80;
} else {
return keyboard_input_;
}
}
Concurrency::DeferringAsyncTaskQueue audio_queue_;
Audio::Toggle audio_toggle_;
Outputs::Speaker::LowpassSpeaker<Audio::Toggle> speaker_;
Outputs::Speaker::PullLowpass<Audio::Toggle> speaker_;
Cycles cycles_since_audio_update_;
// MARK: - Cards
@@ -190,84 +183,223 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
}
}
// MARK: The language card.
// MARK: - The language card, auxiliary memory, and IIe-specific improvements.
LanguageCardSwitches<ConcreteMachine> language_card_;
AuxiliaryMemorySwitches<ConcreteMachine> auxiliary_switches_;
friend LanguageCardSwitches<ConcreteMachine>;
friend AuxiliaryMemorySwitches<ConcreteMachine>;
void set_language_card_paging() {
const auto language_state = language_card_.state();
const auto zero_state = auxiliary_switches_.zero_state();
uint8_t *const ram = zero_state ? aux_ram_ : ram_;
uint8_t *const rom = is_iie() ? &rom_[3840] : rom_.data();
// Which way the region here is mapped to be banks 1 and 2 is
// arbitrary.
page(0xd0, 0xe0,
language_state.read ? &ram[language_state.bank2 ? 0xd000 : 0xc000] : rom,
language_state.write ? nullptr : &ram[language_state.bank2 ? 0xd000 : 0xc000]);
page(0xe0, 0x100,
language_state.read ? &ram[0xe000] : &rom[0x1000],
language_state.write ? nullptr : &ram[0xe000]);
}
// MARK: Auxiliary memory and the other IIe improvements.
void set_card_paging() {
const auto state = auxiliary_switches_.card_state();
page(0xc1, 0xc4, state.region_C1_C3 ? &rom_[0xc100 - 0xc100] : nullptr, nullptr);
read_pages_[0xc3] = state.region_C3 ? &rom_[0xc300 - 0xc100] : nullptr;
page(0xc4, 0xc8, state.region_C4_C8 ? &rom_[0xc400 - 0xc100] : nullptr, nullptr);
page(0xc8, 0xd0, state.region_C8_D0 ? &rom_[0xc800 - 0xc100] : nullptr, nullptr);
}
void set_zero_page_paging() {
if(auxiliary_switches_.zero_state()) {
write_pages_[0] = aux_ram_;
} else {
write_pages_[0] = ram_;
template <int type> void set_paging() {
if constexpr (bool(type & PagingType::ZeroPage)) {
if(auxiliary_switches_.zero_state()) {
write_pages_[0] = aux_ram_;
} else {
write_pages_[0] = ram_;
}
write_pages_[1] = write_pages_[0] + 256;
read_pages_[0] = write_pages_[0];
read_pages_[1] = write_pages_[1];
}
write_pages_[1] = write_pages_[0] + 256;
read_pages_[0] = write_pages_[0];
read_pages_[1] = write_pages_[1];
// Zero page banking also affects interpretation of the language card's switches.
set_language_card_paging();
}
void set_main_paging() {
const auto state = auxiliary_switches_.main_state();
if constexpr (bool(type & (PagingType::LanguageCard | PagingType::ZeroPage))) {
const auto language_state = language_card_.state();
const auto zero_state = auxiliary_switches_.zero_state();
page(0x02, 0x04,
state.base.read ? &aux_ram_[0x0200] : &ram_[0x0200],
state.base.write ? &aux_ram_[0x0200] : &ram_[0x0200]);
page(0x08, 0x20,
state.base.read ? &aux_ram_[0x0800] : &ram_[0x0800],
state.base.write ? &aux_ram_[0x0800] : &ram_[0x0800]);
page(0x40, 0xc0,
state.base.read ? &aux_ram_[0x4000] : &ram_[0x4000],
state.base.write ? &aux_ram_[0x4000] : &ram_[0x4000]);
uint8_t *const ram = zero_state ? aux_ram_ : ram_;
uint8_t *const rom = is_iie() ? &rom_[3840] : rom_.data();
page(0x04, 0x08,
state.region_04_08.read ? &aux_ram_[0x0400] : &ram_[0x0400],
state.region_04_08.write ? &aux_ram_[0x0400] : &ram_[0x0400]);
// Which way the region here is mapped to be banks 1 and 2 is
// arbitrary.
page(0xd0, 0xe0,
language_state.read ? &ram[language_state.bank2 ? 0xd000 : 0xc000] : rom,
language_state.write ? nullptr : &ram[language_state.bank2 ? 0xd000 : 0xc000]);
page(0x20, 0x40,
state.region_20_40.read ? &aux_ram_[0x2000] : &ram_[0x2000],
state.region_20_40.write ? &aux_ram_[0x2000] : &ram_[0x2000]);
page(0xe0, 0x100,
language_state.read ? &ram[0xe000] : &rom[0x1000],
language_state.write ? nullptr : &ram[0xe000]);
}
if constexpr (bool(type & PagingType::CardArea)) {
const auto state = auxiliary_switches_.card_state();
page(0xc1, 0xc4, state.region_C1_C3 ? &rom_[0xc100 - 0xc100] : nullptr, nullptr);
read_pages_[0xc3] = state.region_C3 ? &rom_[0xc300 - 0xc100] : nullptr;
page(0xc4, 0xc8, state.region_C4_C8 ? &rom_[0xc400 - 0xc100] : nullptr, nullptr);
page(0xc8, 0xd0, state.region_C8_D0 ? &rom_[0xc800 - 0xc100] : nullptr, nullptr);
}
if constexpr (bool(type & PagingType::Main)) {
const auto state = auxiliary_switches_.main_state();
page(0x02, 0x04,
state.base.read ? &aux_ram_[0x0200] : &ram_[0x0200],
state.base.write ? &aux_ram_[0x0200] : &ram_[0x0200]);
page(0x08, 0x20,
state.base.read ? &aux_ram_[0x0800] : &ram_[0x0800],
state.base.write ? &aux_ram_[0x0800] : &ram_[0x0800]);
page(0x40, 0xc0,
state.base.read ? &aux_ram_[0x4000] : &ram_[0x4000],
state.base.write ? &aux_ram_[0x4000] : &ram_[0x4000]);
page(0x04, 0x08,
state.region_04_08.read ? &aux_ram_[0x0400] : &ram_[0x0400],
state.region_04_08.write ? &aux_ram_[0x0400] : &ram_[0x0400]);
page(0x20, 0x40,
state.region_20_40.read ? &aux_ram_[0x2000] : &ram_[0x2000],
state.region_20_40.write ? &aux_ram_[0x2000] : &ram_[0x2000]);
}
}
// MARK - typing
std::unique_ptr<Utility::StringSerialiser> string_serialiser_;
// MARK: - Keyboard and typing.
// MARK - Joysticks.
struct Keyboard: public Inputs::Keyboard {
Keyboard(Processor *m6502) : m6502_(m6502) {}
void reset_all_keys() final {
open_apple_is_pressed = closed_apple_is_pressed = control_is_pressed = shift_is_pressed = key_is_down = false;
}
bool set_key_pressed(Key key, char value, bool is_pressed) final {
// If no ASCII value is supplied, look for a few special cases.
switch(key) {
case Key::Left: value = 0x08; break;
case Key::Right: value = 0x15; break;
case Key::Down: value = 0x0a; break;
case Key::Up: value = 0x0b; break;
case Key::Backspace:
if(is_iie()) {
value = 0x7f;
break;
} else {
return false;
}
case Key::Enter: value = 0x0d; break;
case Key::Tab:
if (is_iie()) {
value = '\t';
break;
} else {
return false;
}
case Key::Escape: value = 0x1b; break;
case Key::Space: value = 0x20; break;
case Key::LeftOption:
case Key::RightMeta:
if (is_iie()) {
open_apple_is_pressed = is_pressed;
return true;
} else {
return false;
}
case Key::RightOption:
case Key::LeftMeta:
if (is_iie()) {
closed_apple_is_pressed = is_pressed;
return true;
} else {
return false;
}
case Key::LeftControl:
control_is_pressed = is_pressed;
return true;
case Key::LeftShift:
case Key::RightShift:
shift_is_pressed = is_pressed;
return true;
case Key::F1: case Key::F2: case Key::F3: case Key::F4:
case Key::F5: case Key::F6: case Key::F7: case Key::F8:
case Key::F9: case Key::F10: case Key::F11: case Key::F12:
case Key::PrintScreen:
case Key::ScrollLock:
case Key::Pause:
case Key::Insert:
case Key::Home:
case Key::PageUp:
case Key::PageDown:
case Key::End:
// Accept a bunch non-symbolic other keys, as
// reset, in the hope that the user can find
// at least one usable key.
m6502_->set_reset_line(is_pressed);
return true;
default:
if(!value) {
return false;
}
// Prior to the IIe, the keyboard could produce uppercase only.
if(!is_iie()) value = char(toupper(value));
if(control_is_pressed && isalpha(value)) value &= 0xbf;
// TODO: properly map IIe keys
if(!is_iie() && shift_is_pressed) {
switch(value) {
case 0x27: value = 0x22; break; // ' -> "
case 0x2c: value = 0x3c; break; // , -> <
case 0x2e: value = 0x3e; break; // . -> >
case 0x2f: value = 0x3f; break; // / -> ?
case 0x30: value = 0x29; break; // 0 -> )
case 0x31: value = 0x21; break; // 1 -> !
case 0x32: value = 0x40; break; // 2 -> @
case 0x33: value = 0x23; break; // 3 -> #
case 0x34: value = 0x24; break; // 4 -> $
case 0x35: value = 0x25; break; // 5 -> %
case 0x36: value = 0x5e; break; // 6 -> ^
case 0x37: value = 0x26; break; // 7 -> &
case 0x38: value = 0x2a; break; // 8 -> *
case 0x39: value = 0x28; break; // 9 -> (
case 0x3b: value = 0x3a; break; // ; -> :
case 0x3d: value = 0x2b; break; // = -> +
}
}
break;
}
if(is_pressed) {
keyboard_input = uint8_t(value | 0x80);
key_is_down = true;
} else {
if((keyboard_input & 0x3f) == value) {
key_is_down = false;
}
}
return true;
}
uint8_t get_keyboard_input() {
if(string_serialiser) {
return string_serialiser->head() | 0x80;
} else {
return keyboard_input;
}
}
bool shift_is_pressed = false;
bool control_is_pressed = false;
// The IIe has three keys that are wired directly to the same input as the joystick buttons.
bool open_apple_is_pressed = false;
bool closed_apple_is_pressed = false;
uint8_t keyboard_input = 0x00;
bool key_is_down = false;
std::unique_ptr<Utility::StringSerialiser> string_serialiser;
private:
Processor *const m6502_;
};
Keyboard keyboard_;
// MARK: - Joysticks.
JoystickPair joysticks_;
// The IIe has three keys that are wired directly to the same input as the joystick buttons.
bool open_apple_is_pressed_ = false;
bool closed_apple_is_pressed_ = false;
public:
ConcreteMachine(const Analyser::Static::AppleII::Target &target, const ROMMachine::ROMFetcher &rom_fetcher):
m6502_(*this),
@@ -276,7 +408,8 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
audio_toggle_(audio_queue_),
speaker_(audio_toggle_),
language_card_(*this),
auxiliary_switches_(*this) {
auxiliary_switches_(*this),
keyboard_(&m6502_) {
// The system's master clock rate.
constexpr float master_clock = 14318180.0;
@@ -300,54 +433,59 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
// Pick the required ROMs.
using Target = Analyser::Static::AppleII::Target;
const std::string machine_name = "AppleII";
std::vector<ROMMachine::ROM> rom_descriptions;
size_t rom_size = 12*1024;
ROM::Name character, system;
switch(target.model) {
default:
rom_descriptions.push_back(video_.rom_description(Video::VideoBase::CharacterROM::II));
rom_descriptions.emplace_back(machine_name, "the original Apple II ROM", "apple2o.rom", 12*1024, 0xba210588);
character = ROM::Name::AppleIICharacter;
system = ROM::Name::AppleIIOriginal;
break;
case Target::Model::IIplus:
rom_descriptions.push_back(video_.rom_description(Video::VideoBase::CharacterROM::II));
rom_descriptions.emplace_back(machine_name, "the Apple II+ ROM", "apple2.rom", 12*1024, 0xf66f9c26);
character = ROM::Name::AppleIICharacter;
system = ROM::Name::AppleIIPlus;
break;
case Target::Model::IIe:
rom_size += 3840;
rom_descriptions.push_back(video_.rom_description(Video::VideoBase::CharacterROM::IIe));
rom_descriptions.emplace_back(machine_name, "the Apple IIe ROM", "apple2eu.rom", 32*1024, 0xe12be18d);
character = ROM::Name::AppleIIeCharacter;
system = ROM::Name::AppleIIe;
break;
case Target::Model::EnhancedIIe:
rom_size += 3840;
rom_descriptions.push_back(video_.rom_description(Video::VideoBase::CharacterROM::EnhancedIIe));
rom_descriptions.emplace_back(machine_name, "the Enhanced Apple IIe ROM", "apple2e.rom", 32*1024, 0x65989942);
character = ROM::Name::AppleIIEnhancedECharacter;
system = ROM::Name::AppleIIEnhancedE;
break;
}
const auto roms = rom_fetcher(rom_descriptions);
// Try to install a Disk II card now, before checking the ROM list,
// to make sure that Disk II dependencies have been communicated.
if(target.disk_controller != Target::DiskController::None) {
ROM::Request request = ROM::Request(character) && ROM::Request(system);
// Add the necessary Disk II requests if appropriate.
const bool has_disk_controller = target.disk_controller != Target::DiskController::None;
const bool is_sixteen_sector = target.disk_controller == Target::DiskController::SixteenSector;
if(has_disk_controller) {
// Apple recommended slot 6 for the (first) Disk II.
install_card(6, new Apple::II::DiskIICard(rom_fetcher, target.disk_controller == Target::DiskController::SixteenSector));
request = request && DiskIICard::rom_request(is_sixteen_sector);
}
// Now, check and move the ROMs.
if(!roms[0] || !roms[1]) {
// Request, validate and install ROMs.
auto roms = rom_fetcher(request);
if(!request.validate(roms)) {
throw ROMMachine::Error::MissingROMs;
}
rom_ = std::move(*roms[1]);
if(rom_.size() > rom_size) {
rom_.erase(rom_.begin(), rom_.end() - off_t(rom_size));
if(has_disk_controller) {
install_card(6, new Apple::II::DiskIICard(roms, is_sixteen_sector));
}
video_.set_character_rom(*roms[0]);
rom_ = std::move(roms.find(system)->second);
// The IIe and Enhanced IIe ROMs often distributed are oversized; trim if necessary.
if(system == ROM::Name::AppleIIe || system == ROM::Name::AppleIIEnhancedE) {
if(rom_.size() > 16128) {
rom_.erase(rom_.begin(), rom_.end() - 16128);
}
}
video_.set_character_rom(roms.find(character)->second);
// Set up the default memory blocks. On a II or II+ these values will never change.
// On a IIe they'll be affected by selection of auxiliary RAM.
set_main_paging();
set_zero_page_paging();
set_paging<PagingType::Main | PagingType::ZeroPage>();
// Set the whole card area to initially backed by nothing.
page(0xc0, 0xd0, nullptr, nullptr);
@@ -435,18 +573,18 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
default: break;
case 0xc000:
*value = get_keyboard_input();
*value = keyboard_.get_keyboard_input();
break;
case 0xc001: case 0xc002: case 0xc003: case 0xc004: case 0xc005: case 0xc006: case 0xc007:
case 0xc008: case 0xc009: case 0xc00a: case 0xc00b: case 0xc00c: case 0xc00d: case 0xc00e: case 0xc00f:
*value = (*value & 0x80) | (get_keyboard_input() & 0x7f);
*value = (*value & 0x80) | (keyboard_.get_keyboard_input() & 0x7f);
break;
case 0xc061: // Switch input 0.
*value &= 0x7f;
if(
joysticks_.button(0) ||
(is_iie() && open_apple_is_pressed_)
(is_iie() && keyboard_.open_apple_is_pressed)
)
*value |= 0x80;
break;
@@ -454,7 +592,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
*value &= 0x7f;
if(
joysticks_.button(1) ||
(is_iie() && closed_apple_is_pressed_)
(is_iie() && keyboard_.closed_apple_is_pressed)
)
*value |= 0x80;
break;
@@ -476,7 +614,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
} break;
// The IIe-only state reads follow...
#define IIeSwitchRead(s) *value = get_keyboard_input(); if(is_iie()) *value = (*value & 0x7f) | (s ? 0x80 : 0x00);
#define IIeSwitchRead(s) *value = keyboard_.get_keyboard_input(); if(is_iie()) *value = (*value & 0x7f) | (s ? 0x80 : 0x00);
case 0xc011: IIeSwitchRead(language_card_.state().bank2); break;
case 0xc012: IIeSwitchRead(language_card_.state().read); break;
case 0xc013: IIeSwitchRead(auxiliary_switches_.switches().read_auxiliary_memory); break;
@@ -559,15 +697,15 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
break;
case 0xc010:
keyboard_input_ &= 0x7f;
if(string_serialiser_) {
if(!string_serialiser_->advance())
string_serialiser_.reset();
keyboard_.keyboard_input &= 0x7f;
if(keyboard_.string_serialiser) {
if(!keyboard_.string_serialiser->advance())
keyboard_.string_serialiser.reset();
}
// On the IIe, reading C010 returns additional key info.
if(is_iie() && isReadOperation(operation)) {
*value = (key_is_down_ ? 0x80 : 0x00) | (keyboard_input_ & 0x7f);
*value = (keyboard_.key_is_down ? 0x80 : 0x00) | (keyboard_.keyboard_input & 0x7f);
}
break;
@@ -683,81 +821,16 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
m6502_.run_for(cycles);
}
void reset_all_keys() final {
open_apple_is_pressed_ = closed_apple_is_pressed_ = key_is_down_ = false;
}
bool prefers_logical_input() final {
return true;
}
bool set_key_pressed(Key key, char value, bool is_pressed) final {
// If no ASCII value is supplied, look for a few special cases.
switch(key) {
case Key::Left: value = 0x08; break;
case Key::Right: value = 0x15; break;
case Key::Down: value = 0x0a; break;
case Key::Up: value = 0x0b; break;
case Key::Backspace: value = 0x7f; break;
case Key::Enter: value = 0x0d; break;
case Key::Tab: value = '\t'; break;
case Key::Escape: value = 0x1b; break;
case Key::LeftOption:
case Key::RightMeta:
open_apple_is_pressed_ = is_pressed;
return true;
case Key::RightOption:
case Key::LeftMeta:
closed_apple_is_pressed_ = is_pressed;
return true;
case Key::F1: case Key::F2: case Key::F3: case Key::F4:
case Key::F5: case Key::F6: case Key::F7: case Key::F8:
case Key::F9: case Key::F10: case Key::F11: case Key::F12:
case Key::PrintScreen:
case Key::ScrollLock:
case Key::Pause:
case Key::Insert:
case Key::Home:
case Key::PageUp:
case Key::PageDown:
case Key::End:
// Accept a bunch non-symbolic other keys, as
// reset, in the hope that the user can find
// at least one usable key.
m6502_.set_reset_line(is_pressed);
return true;
default:
if(!value) {
return false;
}
// Prior to the IIe, the keyboard could produce uppercase only.
if(!is_iie()) value = char(toupper(value));
break;
}
if(is_pressed) {
keyboard_input_ = uint8_t(value | 0x80);
key_is_down_ = true;
} else {
if((keyboard_input_ & 0x7f) == value) {
key_is_down_ = false;
}
}
return true;
return is_iie();
}
Inputs::Keyboard &get_keyboard() final {
return *this;
return keyboard_;
}
void type_string(const std::string &string) final {
string_serialiser_ = std::make_unique<Utility::StringSerialiser>(string, true);
keyboard_.string_serialiser = std::make_unique<Utility::StringSerialiser>(string, true);
}
bool can_type(char c) const final {
@@ -769,12 +842,14 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
std::unique_ptr<Reflection::Struct> get_options() final {
auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly);
options->output = get_video_signal_configurable();
options->use_square_pixels = video_.get_use_square_pixels();
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);
video_.set_use_square_pixels(options->use_square_pixels);
}
// MARK: MediaTarget

View File

@@ -30,8 +30,11 @@ class Machine {
class Options: public Reflection::StructImpl<Options>, public Configurable::DisplayOption<Options> {
friend Configurable::DisplayOption<Options>;
public:
Options(Configurable::OptionsType) : Configurable::DisplayOption<Options>(Configurable::Display::CompositeColour) {
bool use_square_pixels = false;
Options(Configurable::OptionsType) : Configurable::DisplayOption<Options>(Configurable::Display::CompositeColour) {
if(needs_declare()) {
DeclareField(use_square_pixels);
declare_display_option();
limit_enum(&output, Configurable::Display::CompositeMonochrome, Configurable::Display::CompositeColour, -1);
}

View File

@@ -9,6 +9,8 @@
#ifndef AuxiliaryMemorySwitches_h
#define AuxiliaryMemorySwitches_h
#include "MemorySwitches.hpp"
namespace Apple {
namespace II {
@@ -235,7 +237,7 @@ template <typename Machine> class AuxiliaryMemorySwitches {
}
if(previous_state != main_state_) {
machine_.set_main_paging();
machine_.template set_paging<PagingType::Main>();
}
}
@@ -258,14 +260,14 @@ template <typename Machine> class AuxiliaryMemorySwitches {
card_state_.region_C8_D0 = switches_.internal_CX_rom || switches_.internal_C8_rom;
if(previous_state != card_state_) {
machine_.set_card_paging();
machine_.template set_paging<PagingType::CardArea>();
}
}
void set_zero_page_paging() {
// Believe it or not, the zero page is just set or cleared by a single flag.
// As though life were rational.
machine_.set_zero_page_paging();
machine_.template set_paging<PagingType::ZeroPage>();
}
};

View File

@@ -10,27 +10,34 @@
using namespace Apple::II;
DiskIICard::DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector) : diskii_(2045454) {
std::vector<std::unique_ptr<std::vector<uint8_t>>> roms;
ROM::Request DiskIICard::rom_request(bool is_16_sector) {
if(is_16_sector) {
roms = rom_fetcher({
{"DiskII", "the Disk II 16-sector boot ROM", "boot-16.rom", 256, 0xce7144f6},
{"DiskII", "the Disk II 16-sector state machine ROM", "state-machine-16.rom", 256, { 0x9796a238, 0xb72a2c70 } }
});
return ROM::Request(ROM::Name::DiskIIBoot16Sector) && ROM::Request(ROM::Name::DiskIIStateMachine16Sector);
} else {
roms = rom_fetcher({
{"DiskII", "the Disk II 13-sector boot ROM", "boot-13.rom", 256, 0xd34eb2ff},
{"DiskII", "the Disk II 16-sector state machine ROM", "state-machine-16.rom", 256, { 0x9796a238, 0xb72a2c70 } }
// {"DiskII", "the Disk II 13-sector state machine ROM", "state-machine-13.rom", 256, 0x62e22620 }
/* TODO: once the DiskII knows how to decode common images of the 13-sector state machine, use that instead of the 16-sector. */
});
/* TODO: once the DiskII knows how to decode common images of the 13-sector state machine, use that instead of the 16-sector. */
return ROM::Request(ROM::Name::DiskIIBoot13Sector) && ROM::Request(ROM::Name::DiskIIStateMachine16Sector);
}
if(!roms[0] || !roms[1]) {
}
DiskIICard::DiskIICard(ROM::Map &map, bool is_16_sector) : diskii_(2045454) {
std::vector<std::unique_ptr<std::vector<uint8_t>>> roms;
ROM::Map::iterator state_machine, boot;
if(is_16_sector) {
state_machine = map.find(ROM::Name::DiskIIStateMachine16Sector);
boot = map.find(ROM::Name::DiskIIBoot16Sector);
} else {
// TODO: see above re: 13-sector state machine.
state_machine = map.find(ROM::Name::DiskIIStateMachine16Sector);
boot = map.find(ROM::Name::DiskIIBoot13Sector);
}
if(state_machine == map.end() || boot == map.end()) {
throw ROMMachine::Error::MissingROMs;
}
boot_ = std::move(*roms[0]);
diskii_.set_state_machine(*roms[1]);
boot_ = std::move(boot->second);
diskii_.set_state_machine(state_machine->second);
set_select_constraints(None);
diskii_.set_clocking_hint_observer(this);
}

View File

@@ -25,7 +25,8 @@ namespace II {
class DiskIICard: public Card, public ClockingHint::Observer {
public:
DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector);
static ROM::Request rom_request(bool is_16_sector);
DiskIICard(ROM::Map &, bool is_16_sector);
void perform_bus_operation(Select select, bool is_read, uint16_t address, uint8_t *value) final;
void run_for(Cycles cycles, int stretches) final;

View File

@@ -9,6 +9,8 @@
#ifndef LanguageCardSwitches_h
#define LanguageCardSwitches_h
#include "MemorySwitches.hpp"
namespace Apple {
namespace II {
@@ -70,7 +72,7 @@ template <typename Machine> class LanguageCardSwitches {
// Apply whatever the net effect of all that is to the memory map.
if(previous_state != state_) {
machine_.set_language_card_paging();
machine_.template set_paging<PagingType::LanguageCard>();
}
}
@@ -90,7 +92,7 @@ template <typename Machine> class LanguageCardSwitches {
state_.bank2 = value & 0x04;
if(previous_state != state_) {
machine_.set_language_card_paging();
machine_.template set_paging<PagingType::LanguageCard>();
}
}

View File

@@ -0,0 +1,25 @@
//
// MemorySwitches.hpp
// Clock Signal
//
// Created by Thomas Harte on 27/06/2022.
// Copyright © 2022 Thomas Harte. All rights reserved.
//
#ifndef MemorySwitches_h
#define MemorySwitches_h
namespace Apple {
namespace II {
enum PagingType: int {
Main = 1 << 0,
ZeroPage = 1 << 1,
CardArea = 1 << 2,
LanguageCard = 1 << 3,
};
}
}
#endif /* MemorySwitches_h */

View File

@@ -15,9 +15,8 @@ VideoBase::VideoBase(bool is_iie, std::function<void(Cycles)> &&target) :
crt_(910, 1, Outputs::Display::Type::NTSC60, Outputs::Display::InputDataType::Luminance1),
is_iie_(is_iie) {
// Show only the centre 75% of the TV frame.
crt_.set_display_type(Outputs::Display::DisplayType::CompositeColour);
crt_.set_visible_area(Outputs::Display::Rect(0.118f, 0.122f, 0.77f, 0.77f));
set_use_square_pixels(use_square_pixels_);
// TODO: there seems to be some sort of bug whereby switching modes can cause
// a signal discontinuity that knocks phase out of whack. So it isn't safe to
@@ -26,6 +25,41 @@ VideoBase::VideoBase(bool is_iie, std::function<void(Cycles)> &&target) :
// crt_.set_immediate_default_phase(0.5f);
}
void VideoBase::set_use_square_pixels(bool use_square_pixels) {
use_square_pixels_ = use_square_pixels;
// HYPER-UGLY HACK. See correlated hack in the Macintosh.
#if defined(__APPLE__) && !defined(IGNORE_APPLE)
crt_.set_visible_area(Outputs::Display::Rect(0.128f, 0.122f, 0.75f, 0.77f));
#else
if(use_square_pixels) {
crt_.set_visible_area(Outputs::Display::Rect(0.128f, 0.09f, 0.75f, 0.77f));
} else {
crt_.set_visible_area(Outputs::Display::Rect(0.128f, 0.12f, 0.75f, 0.77f));
}
#endif
if(use_square_pixels) {
// From what I can make out, many contemporary Apple II monitors were
// calibrated slightly to stretch the Apple II's display slightly wider
// than it should be per the NTSC standards, for approximately square
// pixels. This reproduces that.
// 243 lines and 52µs are visible.
// i.e. to be square, 1 pixel should be: (1/243 * 52) * (3/4) = 156/972 = 39/243 µs
// On an Apple II each pixel is actually 1/7µs.
// Therefore the adjusted aspect ratio should be (4/3) * (39/243)/(1/7) = (4/3) * 273/243 = 1092/729 = 343/243 ~= 1.412
crt_.set_aspect_ratio(343.0f / 243.0f);
} else {
// Standard NTSC aspect ratio.
crt_.set_aspect_ratio(4.0f / 3.0f);
}
}
bool VideoBase::get_use_square_pixels() {
return use_square_pixels_;
}
void VideoBase::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
crt_.set_scan_target(scan_target);
}

View File

@@ -51,8 +51,14 @@ class VideoBase: public VideoSwitches<Cycles> {
/// Gets the type of output.
Outputs::Display::DisplayType get_display_type() const;
/// Sets whether the current CRT should be recalibrated away from normative NTSC
/// to produce square pixels in 40-column text mode.
void set_use_square_pixels(bool);
bool get_use_square_pixels();
protected:
Outputs::CRT::CRT crt_;
bool use_square_pixels_ = false;
// State affecting output video stream generation.
uint8_t *pixel_pointer_ = nullptr;
@@ -393,7 +399,7 @@ template <class BusHandler, bool is_iie> class Video: public VideoBase {
// The OpenGL scan target introduces a phase error of 1/8th of a wave. The Metal one does not.
// Supply the real phase value if this is an Apple build.
// TODO: eliminate UGLY HACK.
#ifdef __APPLE__
#if defined(__APPLE__) && !defined(IGNORE_APPLE)
constexpr int phase = 224;
#else
constexpr int phase = 0;

View File

@@ -228,36 +228,6 @@ template <typename TimeUnit> class VideoSwitches {
return external_.annunciator_3;
}
enum class CharacterROM {
/// The ROM that shipped with both the Apple II and the II+.
II,
/// The ROM that shipped with the original IIe.
IIe,
/// The ROM that shipped with the Enhanced IIe.
EnhancedIIe,
/// The ROM that shipped with the IIgs.
IIgs
};
/// @returns A file-level description of @c rom.
static ROMMachine::ROM rom_description(CharacterROM rom) {
const std::string machine_name = "AppleII";
switch(rom) {
case CharacterROM::II:
return ROMMachine::ROM(machine_name, "the basic Apple II character ROM", "apple2-character.rom", 2*1024, 0x64f415c6);
case CharacterROM::IIe:
return ROMMachine::ROM(machine_name, "the Apple IIe character ROM", "apple2eu-character.rom", 4*1024, 0x816a86f1);
default: // To appease GCC.
case CharacterROM::EnhancedIIe:
return ROMMachine::ROM(machine_name, "the Enhanced Apple IIe character ROM", "apple2e-character.rom", 4*1024, 0x2651014d);
case CharacterROM::IIgs:
return ROMMachine::ROM(machine_name, "the Apple IIgs character ROM", "apple2gs.chr", 4*1024, 0x91e53cd8);
}
}
/// Set the character ROM for this video output.
void set_character_rom(const std::vector<uint8_t> &rom) {
character_rom_ = rom;

View File

@@ -38,11 +38,40 @@
#include <cassert>
#include <array>
//
// HEAVY WARNING: THIS IS INCOMPLETE AND VERY PROVISIONAL.
//
// You'll notice lots of random bits of debugging code sitting around but commented out.
// Most of this will go when this machine is complete. Please look past the gross ugliness
// of this code's intermediate state if you are able.
//
namespace {
constexpr int CLOCK_RATE = 14'318'180;
class MemManagerChecker {
// This is the first result that came up when searching for valid Apple IIgs BRAM states;
// I'm unclear on its provenance.
constexpr uint8_t default_bram[] = {
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0d, 0x06, 0x02, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x07, 0x06, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x06, 0x06, 0x00, 0x05, 0x06,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x03, 0x02, 0x02, 0x02,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
0x07, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
0x0e, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0x96, 0x57, 0x3c,
};
/*class MemManagerChecker {
int handle_total_ = 0;
bool dump_bank(const Apple::IIgs::MemoryMap &memory, const char *name, uint32_t address, bool print, uint32_t must_contain = 0xffffffff) {
const auto handles = memory.regions[memory.region_map[0xe117]].read;
@@ -149,7 +178,7 @@ class MemManagerChecker {
return result;
}
};
};*/
}
@@ -187,33 +216,26 @@ class ConcreteMachine:
set_clock_rate(double(CLOCK_RATE));
speaker_.set_input_rate(float(CLOCK_RATE) / float(audio_divider));
clock_.ClockStorage::set_data(std::begin(default_bram), std::end(default_bram));
using Target = Analyser::Static::AppleIIgs::Target;
std::vector<ROMMachine::ROM> rom_descriptions;
const std::string machine_name = "AppleIIgs";
ROM::Name system;
switch(target.model) {
case Target::Model::ROM00:
/* TODO */
case Target::Model::ROM01:
rom_descriptions.emplace_back(machine_name, "the Apple IIgs ROM01", "apple2gs.rom", 128*1024, 0x42f124b0);
break;
case Target::Model::ROM03:
rom_descriptions.emplace_back(machine_name, "the Apple IIgs ROM03", "apple2gs.rom2", 256*1024, 0xde7ddf29);
break;
case Target::Model::ROM00: system = ROM::Name::AppleIIgsROM00; break;
case Target::Model::ROM01: system = ROM::Name::AppleIIgsROM01; break;
default: system = ROM::Name::AppleIIgsROM03; break;
}
rom_descriptions.push_back(video_->rom_description(Video::Video::CharacterROM::EnhancedIIe));
constexpr ROM::Name characters = ROM::Name::AppleIIEnhancedECharacter;
constexpr ROM::Name microcontroller = ROM::Name::AppleIIgsMicrocontrollerROM03;
// TODO: pick a different ADB ROM for earlier machine revisions?
rom_descriptions.emplace_back(machine_name, "the Apple IIgs ADB microcontroller ROM", "341s0632-2", 4*1024, 0xe1c11fb0);
const auto roms = rom_fetcher(rom_descriptions);
if(!roms[0] || !roms[1] || !roms[2]) {
ROM::Request request = ROM::Request(system) && ROM::Request(characters) && ROM::Request(microcontroller);
auto roms = rom_fetcher(request);
if(!request.validate(roms)) {
throw ROMMachine::Error::MissingROMs;
}
rom_ = *roms[0];
video_->set_character_rom(*roms[1]);
adb_glu_->set_microcontroller_rom(*roms[2]);
rom_ = roms.find(system)->second;
video_->set_character_rom(roms.find(characters)->second);
adb_glu_->set_microcontroller_rom(roms.find(microcontroller)->second);
// Run only the currently-interesting self test.
// rom_[0x36402] = 2;
@@ -256,7 +278,7 @@ class ConcreteMachine:
// rom_[0x36403] = 0xab; // ECT_SEQ
// rom_[0x36404] = 0x64;
rom_[0xfc146f] = rom_[0xfc1470] = 0xea;
// rom_[0xfc146f] = rom_[0xfc1470] = 0xea;
size_t ram_size = 0;
switch(target.memory_model) {
@@ -285,6 +307,11 @@ class ConcreteMachine:
// std::srand(23);
Memory::Fuzz(ram_);
// Prior to ROM03 there's no power-on bit.
if(target.model != Target::Model::ROM03) {
speed_register_ &= ~0x40;
}
// Sync up initial values.
memory_.set_speed_register(speed_register_ ^ 0x80);
@@ -356,6 +383,23 @@ class ConcreteMachine:
static bool log = false;
bool is_1Mhz = false;
// if(operation == CPU::WDC65816::BusOperation::ReadOpcode) {
// if(address == 0xfe00d5) {
// printf("");
// }
//
// printf("%06x a:%04x x:%04x y:%04x s:%04x d:%04x b:%04x\n",
// address,
// m65816_.get_value_of_register(CPU::WDC65816::Register::A),
// m65816_.get_value_of_register(CPU::WDC65816::Register::X),
// m65816_.get_value_of_register(CPU::WDC65816::Register::Y),
//// m65816_.get_value_of_register(CPU::WDC65816::Register::Flags),
// m65816_.get_value_of_register(CPU::WDC65816::Register::StackPointer),
// m65816_.get_value_of_register(CPU::WDC65816::Register::Direct),
// m65816_.get_value_of_register(CPU::WDC65816::Register::DataBank)
// );
// }
if(operation == CPU::WDC65816::BusOperation::ReadVector && !(memory_.get_shadow_register()&0x40)) {
// I think vector pulls always go to ROM?
// That's slightly implied in the documentation, and doing so makes GS/OS boot, so...
@@ -944,9 +988,9 @@ class ConcreteMachine:
// }
if(operation == CPU::WDC65816::BusOperation::ReadOpcode) {
if(total > 482342960 && total < 482352960 && address == 0xe10000) {
printf("entry: %llu\n", static_cast<unsigned long long>(total));
}
// if(total > 482342960 && total < 482352960 && address == 0xe10000) {
// printf("entry: %llu\n", static_cast<unsigned long long>(total));
// }
// log |= address == 0xfc144f;
// log &= !((address < 0xfc144f) || (address >= 0xfc1490));
@@ -1107,7 +1151,7 @@ class ConcreteMachine:
Audio::Toggle audio_toggle_;
using AudioSource = Outputs::Speaker::CompoundSource<Apple::IIgs::Sound::GLU, Audio::Toggle>;
AudioSource mixer_;
Outputs::Speaker::LowpassSpeaker<AudioSource> speaker_;
Outputs::Speaker::PullLowpass<AudioSource> speaker_;
Cycles cycles_since_audio_update_;
Cycles cycles_until_audio_event_;
static constexpr int audio_divider = 16;
@@ -1155,3 +1199,4 @@ Machine *Machine::AppleIIgs(const Analyser::Static::Target *target, const ROMMac
}
Machine::~Machine() {}

View File

@@ -20,6 +20,9 @@ namespace Apple {
namespace IIgs {
class MemoryMap {
private:
using PagingType = Apple::II::PagingType;
public:
// MARK: - Initial construction and configuration.
@@ -179,7 +182,7 @@ class MemoryMap {
// TODO: set 1Mhz flags.
// Apply initial language/auxiliary state.
set_all_paging();
set_paging<~0>();
}
// MARK: - Live bus access notifications and register access.
@@ -189,8 +192,7 @@ class MemoryMap {
shadow_register_ = value;
if(diff & 0x40) { // IO/language-card inhibit.
set_language_card_paging();
set_card_paging();
set_paging<PagingType::LanguageCard | PagingType::CardArea>();
}
if(diff & 0x3f) {
@@ -241,7 +243,7 @@ class MemoryMap {
friend AuxiliaryMemorySwitches;
friend LanguageCardSwitches;
uint8_t shadow_register_ = 0x08;
uint8_t shadow_register_ = 0x00;
uint8_t speed_register_ = 0x00;
// MARK: - Memory banking.
@@ -251,150 +253,181 @@ class MemoryMap {
assert(region_map[end-1] == region_map[start]); \
assert(region_map[end] == region_map[end-1]+1);
// Cf. LanguageCardSwitches; this function should update the region from
// $D000 onwards as per the state of the language card flags — there may
// end up being ROM or RAM (or auxiliary RAM), and the first 4kb of it
// may be drawn from either of two pools.
void set_language_card_paging() {
const auto language_state = language_card_.state();
const auto zero_state = auxiliary_switches_.zero_state();
const bool inhibit_banks0001 = shadow_register_ & 0x40;
template <int type> void set_paging() {
// Update the region from
// $D000 onwards as per the state of the language card flags — there may
// end up being ROM or RAM (or auxiliary RAM), and the first 4kb of it
// may be drawn from either of two pools.
if constexpr (bool(type & (PagingType::LanguageCard | PagingType::ZeroPage | PagingType::Main))) {
const auto language_state = language_card_.state();
const auto zero_state = auxiliary_switches_.zero_state();
const auto main = auxiliary_switches_.main_state();
const bool inhibit_banks0001 = shadow_register_ & 0x40;
auto apply = [&language_state, this](uint32_t bank_base, uint8_t *ram) {
// This assumes bank 1 is the one before bank 2 when RAM is linear.
uint8_t *const d0_ram_bank = ram - (language_state.bank2 ? 0x0000 : 0x1000);
auto apply = [&language_state, this](uint32_t bank_base, uint8_t *ram) {
// This assumes bank 1 is the one before bank 2 when RAM is linear.
uint8_t *const d0_ram_bank = ram - (language_state.bank2 ? 0x0000 : 0x1000);
// Crib the ROM pointer from a page it's always visible on.
const uint8_t *const rom = &regions[region_map[0xffd0]].read[0xff'd000] - ((bank_base << 8) + 0xd000);
// Crib the ROM pointer from a page it's always visible on.
const uint8_t *const rom = &regions[region_map[0xffd0]].read[0xff'd000] - ((bank_base << 8) + 0xd000);
auto &d0_region = regions[region_map[bank_base | 0xd0]];
d0_region.read = language_state.read ? d0_ram_bank : rom;
d0_region.write = language_state.write ? nullptr : d0_ram_bank;
auto &d0_region = regions[region_map[bank_base | 0xd0]];
d0_region.read = language_state.read ? d0_ram_bank : rom;
d0_region.write = language_state.write ? nullptr : d0_ram_bank;
auto &e0_region = regions[region_map[bank_base | 0xe0]];
e0_region.read = language_state.read ? ram : rom;
e0_region.write = language_state.write ? nullptr : ram;
auto &e0_region = regions[region_map[bank_base | 0xe0]];
e0_region.read = language_state.read ? ram : rom;
e0_region.write = language_state.write ? nullptr : ram;
// Assert assumptions made above re: memory layout.
assert(region_map[bank_base | 0xd0] + 1 == region_map[bank_base | 0xe0]);
assert(region_map[bank_base | 0xe0] == region_map[bank_base | 0xff]);
};
auto set_no_card = [this](uint32_t bank_base) {
auto &d0_region = regions[region_map[bank_base | 0xd0]];
d0_region.read = ram_base;
d0_region.write = ram_base;
// Assert assumptions made above re: memory layout.
assert(region_map[bank_base | 0xd0] + 1 == region_map[bank_base | 0xe0]);
assert(region_map[bank_base | 0xe0] == region_map[bank_base | 0xff]);
};
auto set_no_card = [this](uint32_t bank_base, uint8_t *read, uint8_t *write) {
auto &d0_region = regions[region_map[bank_base | 0xd0]];
d0_region.read = read;
d0_region.write = write;
auto &e0_region = regions[region_map[bank_base | 0xe0]];
e0_region.read = ram_base;
e0_region.write = ram_base;
auto &e0_region = regions[region_map[bank_base | 0xe0]];
e0_region.read = read;
e0_region.write = write;
// Assert assumptions made above re: memory layout.
assert(region_map[bank_base | 0xd0] + 1 == region_map[bank_base | 0xe0]);
assert(region_map[bank_base | 0xe0] == region_map[bank_base | 0xff]);
};
// Assert assumptions made above re: memory layout.
assert(region_map[bank_base | 0xd0] + 1 == region_map[bank_base | 0xe0]);
assert(region_map[bank_base | 0xe0] == region_map[bank_base | 0xff]);
};
if(inhibit_banks0001) {
set_no_card(0x0000);
set_no_card(0x0100);
} else {
apply(0x0000, zero_state ? &ram_base[0x01'0000] : ram_base);
apply(0x0100, ram_base);
}
// The pointer stored in region_map[0xe000] has already been adjusted for
// the 0xe0'0000 addressing offset.
uint8_t *const e0_ram = regions[region_map[0xe000]].write;
apply(0xe000, e0_ram);
apply(0xe100, e0_ram);
}
// Cf. AuxiliarySwitches; this should establish whether ROM or card switches
// are exposed in the distinct regions C100C2FF, C300C3FF, C400C7FF and
// C800CFFF.
//
// On the IIgs it intersects with the current shadow register.
//
// TODO: so... shouldn't the card mask be incorporated here? I've got it implemented
// distinctly at present, but does that create any invalid state interactions?
void set_card_paging() {
const bool inhibit_banks0001 = shadow_register_ & 0x40;
const auto state = auxiliary_switches_.card_state();
auto apply = [&state, this](uint32_t bank_base) {
auto &c0_region = regions[region_map[bank_base | 0xc0]];
auto &c1_region = regions[region_map[bank_base | 0xc1]];
auto &c3_region = regions[region_map[bank_base | 0xc3]];
auto &c4_region = regions[region_map[bank_base | 0xc4]];
auto &c8_region = regions[region_map[bank_base | 0xc8]];
const uint8_t *const rom = &regions[region_map[0xffd0]].read[0xffc100] - ((bank_base << 8) + 0xc100);
// This is applied dynamically as it may be added or lost in banks $00 and $01.
c0_region.flags |= Region::IsIO;
#define apply_region(flag, region) \
if(flag) { \
region.read = rom; \
region.flags &= ~Region::IsIO; \
} else { \
region.flags |= Region::IsIO; \
if(inhibit_banks0001) {
set_no_card(0x0000,
main.base.read ? &ram_base[0x01'0000] : ram_base,
main.base.write ? &ram_base[0x01'0000] : ram_base);
set_no_card(0x0100, ram_base, ram_base);
} else {
apply(0x0000, zero_state ? &ram_base[0x01'0000] : ram_base);
apply(0x0100, ram_base);
}
apply_region(state.region_C1_C3, c1_region);
apply_region(state.region_C3, c3_region);
apply_region(state.region_C4_C8, c4_region);
apply_region(state.region_C8_D0, c8_region);
// The pointer stored in region_map[0xe000] has already been adjusted for
// the 0xe0'0000 addressing offset.
uint8_t *const e0_ram = regions[region_map[0xe000]].write;
apply(0xe000, e0_ram);
apply(0xe100, e0_ram);
}
// Establish whether main or auxiliary RAM
// is exposed in bank $00 for a bunch of regions.
if constexpr (type & PagingType::Main) {
const auto state = auxiliary_switches_.main_state();
#define set(page, flags) {\
auto &region = regions[region_map[page]]; \
region.read = flags.read ? &ram_base[0x01'0000] : ram_base; \
region.write = flags.write ? &ram_base[0x01'0000] : ram_base; \
}
// Base: $0200$03FF.
set(0x02, state.base);
assert_is_region(0x02, 0x04);
// Region $0400$07ff.
set(0x04, state.region_04_08);
assert_is_region(0x04, 0x08);
// Base: $0800$1FFF.
set(0x08, state.base);
assert_is_region(0x08, 0x20);
// Region $2000$3FFF.
set(0x20, state.region_20_40);
assert_is_region(0x20, 0x40);
// Base: $4000$BFFF.
set(0x40, state.base);
assert_is_region(0x40, 0xc0);
#undef set
}
// Update whether base or auxiliary RAM is visible in: (i) the zero
// and stack pages; and (ii) anywhere that the language card is exposing RAM instead of ROM.
if constexpr (bool(type & PagingType::ZeroPage)) {
// Affects bank $00 only, and should be a single region.
auto &region = regions[region_map[0]];
region.read = region.write = auxiliary_switches_.zero_state() ? &ram_base[0x01'0000] : ram_base;
assert(region_map[0x0000] == region_map[0x0001]);
assert(region_map[0x0001]+1 == region_map[0x0002]);
}
// Establish whether ROM or card switches are exposed in the distinct
// regions C100C2FF, C300C3FF, C400C7FF and C800CFFF.
//
// On the IIgs it intersects with the current shadow register.
if constexpr (bool(type & (PagingType::CardArea | PagingType::Main))) {
const bool inhibit_banks0001 = shadow_register_ & 0x40;
const auto state = auxiliary_switches_.card_state();
auto apply = [&state, this](uint32_t bank_base) {
auto &c0_region = regions[region_map[bank_base | 0xc0]];
auto &c1_region = regions[region_map[bank_base | 0xc1]];
auto &c3_region = regions[region_map[bank_base | 0xc3]];
auto &c4_region = regions[region_map[bank_base | 0xc4]];
auto &c8_region = regions[region_map[bank_base | 0xc8]];
const uint8_t *const rom = &regions[region_map[0xffd0]].read[0xffc100] - ((bank_base << 8) + 0xc100);
// This is applied dynamically as it may be added or lost in banks $00 and $01.
c0_region.flags |= Region::IsIO;
#define apply_region(flag, region) \
region.write = nullptr; \
if(flag) { \
region.read = rom; \
region.flags &= ~Region::IsIO; \
} else { \
region.flags |= Region::IsIO; \
}
apply_region(state.region_C1_C3, c1_region);
apply_region(state.region_C3, c3_region);
apply_region(state.region_C4_C8, c4_region);
apply_region(state.region_C8_D0, c8_region);
#undef apply_region
// Sanity checks.
assert(region_map[bank_base | 0xc1] == region_map[bank_base | 0xc0]+1);
assert(region_map[bank_base | 0xc2] == region_map[bank_base | 0xc1]);
assert(region_map[bank_base | 0xc3] == region_map[bank_base | 0xc2]+1);
assert(region_map[bank_base | 0xc4] == region_map[bank_base | 0xc3]+1);
assert(region_map[bank_base | 0xc7] == region_map[bank_base | 0xc4]);
assert(region_map[bank_base | 0xc8] == region_map[bank_base | 0xc7]+1);
assert(region_map[bank_base | 0xcf] == region_map[bank_base | 0xc8]);
assert(region_map[bank_base | 0xd0] == region_map[bank_base | 0xcf]+1);
};
// Sanity checks.
assert(region_map[bank_base | 0xc1] == region_map[bank_base | 0xc0]+1);
assert(region_map[bank_base | 0xc2] == region_map[bank_base | 0xc1]);
assert(region_map[bank_base | 0xc3] == region_map[bank_base | 0xc2]+1);
assert(region_map[bank_base | 0xc4] == region_map[bank_base | 0xc3]+1);
assert(region_map[bank_base | 0xc7] == region_map[bank_base | 0xc4]);
assert(region_map[bank_base | 0xc8] == region_map[bank_base | 0xc7]+1);
assert(region_map[bank_base | 0xcf] == region_map[bank_base | 0xc8]);
assert(region_map[bank_base | 0xd0] == region_map[bank_base | 0xcf]+1);
};
if(inhibit_banks0001) {
// Set no IO in the Cx00 range for banks $00 and $01, just
// regular RAM (or possibly auxiliary).
const auto auxiliary_state = auxiliary_switches_.main_state();
for(uint8_t region = region_map[0x00c0]; region < region_map[0x00d0]; region++) {
regions[region].read = auxiliary_state.base.read ? &ram_base[0x01'0000] : ram_base;
regions[region].write = auxiliary_state.base.write ? &ram_base[0x01'0000] : ram_base;
regions[region].flags &= ~Region::IsIO;
if(inhibit_banks0001) {
// Set no IO in the Cx00 range for banks $00 and $01, just
// regular RAM (or possibly auxiliary).
const auto auxiliary_state = auxiliary_switches_.main_state();
for(uint8_t region = region_map[0x00c0]; region < region_map[0x00d0]; region++) {
regions[region].read = auxiliary_state.base.read ? &ram_base[0x01'0000] : ram_base;
regions[region].write = auxiliary_state.base.write ? &ram_base[0x01'0000] : ram_base;
regions[region].flags &= ~Region::IsIO;
}
for(uint8_t region = region_map[0x01c0]; region < region_map[0x01d0]; region++) {
regions[region].read = regions[region].write = ram_base;
regions[region].flags &= ~Region::IsIO;
}
} else {
// Obey the card state for banks $00 and $01.
apply(0x0000);
apply(0x0100);
}
for(uint8_t region = region_map[0x01c0]; region < region_map[0x01d0]; region++) {
regions[region].read = regions[region].write = ram_base;
regions[region].flags &= ~Region::IsIO;
}
} else {
// Obey the card state for banks $00 and $01.
apply(0x0000);
apply(0x0100);
// Obey the card state for banks $e0 and $e1.
apply(0xe000);
apply(0xe100);
}
// Obey the card state for banks $e0 and $e1.
apply(0xe000);
apply(0xe100);
}
// Cf. LanguageCardSwitches; this should update whether base or auxiliary RAM is
// visible in: (i) the zero and stack pages; and (ii) anywhere that the language
// card is exposing RAM instead of ROM.
void set_zero_page_paging() {
// Affects bank $00 only, and should be a single region.
auto &region = regions[region_map[0]];
region.read = region.write = auxiliary_switches_.zero_state() ? &ram_base[0x01'0000] : ram_base;
assert(region_map[0x0000] == region_map[0x0001]);
assert(region_map[0x0001]+1 == region_map[0x0002]);
// Switching to or from auxiliary RAM potentially affects the
// language card area.
set_language_card_paging();
}
// IIgs specific: sets or resets the ::IsShadowed flag across affected banks as
@@ -423,18 +456,27 @@ class MemoryMap {
// $6000$a000 Odd banks only, rest of Super High-res
// [plus IO and language card space, subject to your definition of shadowing]
constexpr int shadow_shift = 10;
constexpr int auxiliary_offset = 0x10000 >> shadow_shift;
static constexpr int shadow_shift = 10;
static constexpr int auxiliary_offset = 0x1'0000 >> shadow_shift;
enum Inhibit {
TextPage1 = 0x01,
HighRes1 = 0x02,
HighRes2 = 0x04,
SuperHighRes = 0x08,
AuxiliaryHighRes = 0x10,
TextPage2 = 0x20,
};
// Text Page 1, main and auxiliary — $0400$0800.
for(size_t c = 0x0400 >> shadow_shift; c < 0x0800 >> shadow_shift; c++) {
shadow_pages[c] = shadow_pages[c+auxiliary_offset] = !(shadow_register_ & 0x01);
shadow_pages[c] = shadow_pages[c+auxiliary_offset] = !(shadow_register_ & Inhibit::TextPage1);
}
// Text Page 2, main and auxiliary — 0x08000x0c00.
// TODO: on a ROM03 machine only.
for(size_t c = 0x0800 >> shadow_shift; c < 0x0c00 >> shadow_shift; c++) {
shadow_pages[c] = shadow_pages[c+auxiliary_offset] = !(shadow_register_ & 0x20);
shadow_pages[c] = shadow_pages[c+auxiliary_offset] = !(shadow_register_ & Inhibit::TextPage2);
}
// Hi-res graphics Page 1, main and auxiliary — $2000$4000;
@@ -447,8 +489,11 @@ class MemoryMap {
// (high-res graphics inhibit or auxiliary high res graphics inhibit) _and_ (super high-res inhibit).
//
for(size_t c = 0x2000 >> shadow_shift; c < 0x4000 >> shadow_shift; c++) {
shadow_pages[c] = !(shadow_register_ & 0x02);
shadow_pages[c+auxiliary_offset] = !(shadow_register_ & 0x12);
shadow_pages[c] = !(shadow_register_ & Inhibit::HighRes1);
shadow_pages[c+auxiliary_offset] = !(
shadow_register_ & (Inhibit::HighRes1 | Inhibit::AuxiliaryHighRes) &&
shadow_register_ & Inhibit::SuperHighRes
);
}
// Hi-res graphics Page 2, main and auxiliary — $4000$6000;
@@ -456,62 +501,23 @@ class MemoryMap {
//
// Test applied: much like that for page 1.
for(size_t c = 0x4000 >> shadow_shift; c < 0x6000 >> shadow_shift; c++) {
shadow_pages[c] = !(shadow_register_ & 0x04);
shadow_pages[c+auxiliary_offset] = !(shadow_register_ & 0x14);
shadow_pages[c] = !(shadow_register_ & Inhibit::HighRes2);
shadow_pages[c+auxiliary_offset] = !(
shadow_register_ & (Inhibit::HighRes2 | Inhibit::AuxiliaryHighRes) &&
shadow_register_ & Inhibit::SuperHighRes
);
}
// Residue of Super Hi-Res — $6000$a000 (odd pages only).
//
// Test applied:
// auxiliary high res graphics inhibit and super high-res inhibit
for(size_t c = 0x6000 >> shadow_shift; c < 0xa000 >> shadow_shift; c++) {
shadow_pages[c+auxiliary_offset] = !(shadow_register_ & 0x08);
shadow_pages[c+auxiliary_offset] =
!(shadow_register_ & Inhibit::SuperHighRes && shadow_register_ & Inhibit::AuxiliaryHighRes);
}
}
// Cf. the AuxiliarySwitches; establishes whether main or auxiliary RAM
// is exposed in bank $00 for a bunch of regions.
void set_main_paging() {
const auto state = auxiliary_switches_.main_state();
#define set(page, flags) {\
auto &region = regions[region_map[page]]; \
region.read = flags.read ? &ram_base[0x01'0000] : ram_base; \
region.write = flags.write ? &ram_base[0x01'0000] : ram_base; \
}
// Base: $0200$03FF.
set(0x02, state.base);
assert_is_region(0x02, 0x04);
// Region $0400$07ff.
set(0x04, state.region_04_08);
assert_is_region(0x04, 0x08);
// Base: $0800$1FFF.
set(0x08, state.base);
assert_is_region(0x08, 0x20);
// Region $2000$3FFF.
set(0x20, state.region_20_40);
assert_is_region(0x20, 0x40);
// Base: $4000$BFFF.
set(0x40, state.base);
assert_is_region(0x40, 0xc0);
#undef set
// This also affects shadowing flags, if shadowing is enabled at all,
// and might affect RAM in the IO area of bank $00 because the language
// card can be inhibited on a IIgs.
set_card_paging();
}
void set_all_paging() {
set_card_paging();
set_zero_page_paging(); // ... which calls set_language_card_paging().
set_main_paging();
set_shadowing();
}
void print_state() {
uint8_t region = region_map[0];
uint32_t start = 0;
@@ -559,7 +565,7 @@ class MemoryMap {
//
// Shadow_banks: divides the whole 16mb of memory into 128kb chunks and includes a flag to indicate whether
// each is a potential source of shadowing.
std::bitset<128> shadow_pages, shadow_banks;
std::bitset<128> shadow_pages{}, shadow_banks{};
std::array<Region, 40> regions; // An assert above ensures that this is large enough; there's no
// doctrinal reason for it to be whatever size it is now, just
@@ -570,8 +576,28 @@ class MemoryMap {
// would be less efficient. Verify that?
#define MemoryMapRegion(map, address) map.regions[map.region_map[address >> 8]]
#define IsShadowed(map, region, address) (map.shadow_pages[((&region.write[address] - map.ram_base) >> 10) & 127] & map.shadow_banks[address >> 17])
#define MemoryMapRead(region, address, value) *value = region.read ? region.read[address] : 0xff
// The below encapsulates the fact that I've yet to determine whether Apple intends to
// indicate that logical addresses (i.e. those prior to being mapped per the current paging)
// or physical addresses (i.e. after mapping) are subject to shadowing.
#ifdef SHADOW_LOGICAL
#define IsShadowed(map, region, address) \
(map.shadow_pages[((&region.write[address] - map.ram_base) >> 10) & 127] & map.shadow_banks[address >> 17])
#define MemoryMapWrite(map, region, address, value) \
if(region.write) { \
region.write[address] = *value; \
const bool _mm_is_shadowed = IsShadowed(map, region, address); \
map.shadow_base[_mm_is_shadowed][address & map.shadow_mask[_mm_is_shadowed]] = *value; \
}
#else
#define IsShadowed(map, region, address) \
(map.shadow_pages[(address >> 10) & 127] & map.shadow_banks[address >> 17])
#define MemoryMapWrite(map, region, address, value) \
if(region.write) { \
region.write[address] = *value; \
@@ -579,6 +605,8 @@ class MemoryMap {
map.shadow_base[_mm_is_shadowed][(&region.write[address] - map.ram_base) & map.shadow_mask[_mm_is_shadowed]] = *value; \
}
#endif
// Quick notes on ::IsShadowed contortions:
//
// The objective is to support shadowing:

View File

@@ -182,7 +182,7 @@ void Video::advance(Cycles cycles) {
if(column_start != FinalColumn) {
output_row(row_start, column_start, FinalColumn);
}
for(int row = row_start+1; row < row_end; row++) {
for(int row = row_start+1; row != row_end; row = (row + 1)%Lines) {
output_row(row, 0, FinalColumn);
}
if(column_end) {

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