1
0
mirror of https://github.com/TomHarte/CLK.git synced 2026-04-26 19:17:52 +00:00

Compare commits

..

1041 Commits

Author SHA1 Message Date
Thomas Harte 4be5ee5b35 Fix value interactions. 2025-11-17 23:25:47 -05:00
Thomas Harte 92e6dc64d4 Merge branch 'master' into QueueDelegate 2025-11-17 23:21:24 -05:00
Thomas Harte a9d945d6d2 Merge pull request #1639 from TomHarte/CommodoreNew6502s
Adapt all Commodore machines to 6502Mk2.
2025-11-17 23:16:17 -05:00
Thomas Harte 5e465f1ff4 Avoid function specialisation. 2025-11-17 23:04:33 -05:00
Thomas Harte 5359964fef Make minor style improvements, fix cursor keys. 2025-11-17 22:55:18 -05:00
Thomas Harte fa8be26f9f Fix read-only bit. 2025-11-17 22:20:20 -05:00
Thomas Harte aabfe7c284 Observe that there is an attempt to output data. 2025-11-17 21:15:42 -05:00
Thomas Harte d011b10b5d Update comments, add note-to-self on write mode. 2025-11-17 17:54:09 -05:00
Thomas Harte 332b37063f Adjust for style. 2025-11-17 17:15:26 -05:00
Thomas Harte b3a9e39be3 Transfer C1540, ensure Plus 4 bus always holds _something_. 2025-11-17 14:39:35 -05:00
Thomas Harte 67590cf06b Adapt Vic-20 to the newer 6502. 2025-11-17 14:23:05 -05:00
Thomas Harte 236fdacb36 Adapt Plus 4 to the newer 6502. 2025-11-17 14:14:25 -05:00
Thomas Harte f422cda553 Adapt the Enterprise, accepting possible need for HalfCycles. 2025-11-16 08:04:47 -05:00
Thomas Harte 2c44d3a7d3 Adapt the Plus 4. 2025-11-15 22:31:17 -05:00
Thomas Harte 051ce98ecb Adapt Vic-20. 2025-11-15 22:18:46 -05:00
Thomas Harte 33ae24c961 Attempt to shrink repetition even further. 2025-11-15 21:41:34 -05:00
Thomas Harte 4247d0ef40 Adapt Atari 2600. 2025-11-14 22:58:41 -05:00
Thomas Harte ffababdb45 With the Electron as a test bed, start to simplify audio class groups. 2025-11-14 22:39:53 -05:00
Thomas Harte 176bda9eb8 Merge pull request #1637 from TomHarte/Voice3Off
SID: support the voice 3 disable bit.
2025-11-14 19:35:05 -05:00
Thomas Harte 9f0a0443a8 SID: support the voice 3 disable bit. 2025-11-14 15:38:54 -05:00
Thomas Harte fd1a7e78c5 Merge pull request #1636 from TomHarte/BiquadAttribution
Record references for the SID and biquad filter.
2025-11-14 13:29:55 -05:00
Thomas Harte 909fa57b27 Record references for the SID and biquad filter. 2025-11-14 13:29:15 -05:00
Thomas Harte 5630b1c351 Merge pull request #1634 from TomHarte/BeebSID
Investigate the SID via BeebSID.
2025-11-13 20:38:34 -05:00
Thomas Harte c4fe38a61f Allow potentometer inputs to be set; disable SID by default. 2025-11-13 18:05:13 -05:00
Thomas Harte 5b4f303e35 Mention memory barrier. 2025-11-13 18:02:41 -05:00
Thomas Harte c9c1bde6e2 Switch to spinning on SID thread synchronisation. 2025-11-13 17:59:24 -05:00
Thomas Harte d01e1f3bb1 Block and synchronise threads for voice 3 readback. 2025-11-13 17:34:57 -05:00
Thomas Harte fd32e63459 Clean up: pull out noise generation, remove code from header. 2025-11-13 13:44:53 -05:00
Thomas Harte dbbb1d60fc Add commentary, use filter reconfiguration to retain sample history. 2025-11-13 13:27:55 -05:00
Thomas Harte 1ce013bcf7 Simplify, update noise tap. 2025-11-13 11:59:56 -05:00
Thomas Harte 86bf019aac Attempt further to improve filter precision. 2025-11-13 11:54:37 -05:00
Thomas Harte d00546dd77 Add post hoc filter, attempt to juice precision. 2025-11-13 07:31:59 -05:00
Thomas Harte cf33e17688 Attempt to use biquad filter; fix signs. 2025-11-12 23:08:35 -05:00
Thomas Harte c5c6c5ff72 Add textbook filter construction. 2025-11-12 22:06:48 -05:00
Thomas Harte fa0835abd8 Capture all filter parameters. 2025-11-12 17:47:49 -05:00
Thomas Harte f232b179ed Partition channels into filtered and unfiltered, and apply no-op biquad. 2025-11-12 17:40:14 -05:00
Thomas Harte a4a0026cab Reintroduce decay stage; flip pulse meaning. 2025-11-11 21:38:27 -05:00
Thomas Harte eac7493180 Support master volume. 2025-11-11 21:04:07 -05:00
Thomas Harte 989fb32fba Fix clocking, do a linear attack phase. 2025-11-11 20:53:54 -05:00
Thomas Harte 735afcfabb Adopt painful pulse test, temporarily (?). 2025-11-11 18:26:00 -05:00
Thomas Harte 37152a1fad Start testing; I'm now unsure about pulses. 2025-11-11 17:54:31 -05:00
Thomas Harte 4e86184955 Add local hack to ensure good flushing. 2025-11-11 14:40:13 -05:00
Thomas Harte d23dbb96c2 Support system volume, avoid clipping. 2025-11-11 14:40:04 -05:00
Thomas Harte 4586e4b4c1 Apply envelope. 2025-11-11 14:26:53 -05:00
Thomas Harte de5cdbf18c Make a complete attempt at ADSR. 2025-11-11 14:25:36 -05:00
Thomas Harte 8c2294fc0d Treat sustain as a volume; start second prescaler table. 2025-11-11 12:43:48 -05:00
Thomas Harte b0b82782ad Build in initial prescaler. 2025-11-11 12:21:49 -05:00
Thomas Harte b9f5802c89 Return whatever was written last if read. 2025-11-11 09:19:01 -05:00
Thomas Harte 29235f1276 Adjust noise clocking, make it reactive to the test bit. 2025-11-11 09:16:43 -05:00
Thomas Harte 8c74e2a323 Implement LFSR. 2025-11-10 22:44:00 -05:00
Thomas Harte ae2936b9c3 Correct clock rate, triangle wave. 2025-11-10 22:35:13 -05:00
Thomas Harte 0d295a6338 Don't capture a reference to parameters. 2025-11-10 22:10:28 -05:00
Thomas Harte 3ebd6c6871 Rejig oscillators, output some vague noise. 2025-11-10 21:52:10 -05:00
Thomas Harte 6e2cd0ace6 Divide state, start adding waveforms. 2025-11-10 17:27:32 -05:00
Thomas Harte af82a0bcda Add ADSR TODO. 2025-11-10 14:18:24 -05:00
Thomas Harte 6fe208ae77 Honour test and sync bits. 2025-11-10 14:17:54 -05:00
Thomas Harte f569b86c90 Merge branch 'master' into BeebSID 2025-11-10 14:10:33 -05:00
Thomas Harte b622cc9536 Merge pull request #1635 from TomHarte/CleanerQueue
Enforce perform_automatically, start_immediately; relax Boolean access order.
2025-11-10 14:09:31 -05:00
Thomas Harte 7dfd5ea0d0 Add phase accumulation, rename to pitch. 2025-11-10 13:27:43 -05:00
Thomas Harte a81309433c Switch to lambda form. 2025-11-09 21:09:57 -05:00
Thomas Harte 902f388cb1 Enforce perform_automatically, start_immediately; relax Boolean access order. 2025-11-09 00:17:39 -05:00
Thomas Harte 0cc5a9d74f Move thread. 2025-11-08 23:02:54 -05:00
Thomas Harte 5e98e6502d Attempt some basic voice details. 2025-11-08 21:54:41 -05:00
Thomas Harte fe7a206fc5 Add an empty vessel of a SID. 2025-11-07 22:51:28 -05:00
Thomas Harte c5704aaaff Merge pull request #1633 from TomHarte/TubeBrevity
Move point of templature to tube processors.
2025-11-07 18:05:19 -05:00
Thomas Harte e115f09f51 Merge pull request #1632 from TomHarte/6845DeadState
Remove dead state.
2025-11-07 17:54:13 -05:00
Thomas Harte 32cd142629 Move point of templature to tube processors. 2025-11-07 17:27:52 -05:00
Thomas Harte b00be303aa Remove dead state. 2025-11-07 13:08:12 -05:00
Thomas Harte 273e23bd98 Merge pull request #1631 from TomHarte/SecondProcessorScreenshot
Substitute a screenshot of Second Processor Elite.
2025-11-07 12:54:16 -05:00
Thomas Harte 5063e6943d Substitute a screenshot of Second Processor Elite. 2025-11-07 12:53:09 -05:00
Thomas Harte ce32747973 Record new version number. 2025-11-07 12:39:46 -05:00
Thomas Harte a8ef8dfb21 Merge pull request #1630 from TomHarte/DNFS
Add DFS 0.9 to ROM catalogue; restrict CRTC pointer size.
2025-11-07 11:19:21 -05:00
Thomas Harte 7658edca62 Remove unused enum. 2025-11-07 10:49:26 -05:00
Thomas Harte 056028e07b Put bit restriction on register pointer. 2025-11-07 10:43:14 -05:00
Thomas Harte 34992126a8 Allow installation of smaller ROMs. 2025-11-07 09:12:47 -05:00
Thomas Harte d30d4e8f89 Add DFS 0.9 to the ROM catalogue. 2025-11-07 09:08:16 -05:00
Thomas Harte f562deca48 Record new version number. 2025-11-05 20:56:13 -05:00
Thomas Harte b2b7aa221b Merge pull request #1629 from TomHarte/6502SecondAnalyser
Automatically add a 65c02 second processor if seemingly helpful.
2025-11-05 20:51:44 -05:00
Thomas Harte b98a9a8487 Add automatic test for 6502 second processor. 2025-11-05 20:40:29 -05:00
Thomas Harte 7f36a8a746 Merge pull request #1628 from TomHarte/TubeResetLater
Give Tube ULA full ownership of parasite IRQ/NMI/reset.
2025-11-05 15:41:50 -05:00
Thomas Harte 2fe6e9c7fc Reformulate to give ULA full IRQ/NMI/reset signalling duties. 2025-11-05 15:27:11 -05:00
Thomas Harte 62919e77d4 Reset at end. 2025-11-05 15:11:49 -05:00
Thomas Harte 871b724290 Merge pull request #1627 from TomHarte/TubeReset
Propagate system reset to the tube.
2025-11-04 23:24:20 -05:00
Thomas Harte ca23c04ba1 Propagate system reset to the tube. 2025-11-04 23:18:18 -05:00
Thomas Harte 44b8f75611 Merge pull request #1626 from TomHarte/TubeSelection
Detect CP/M discs and route to the Z80 second processor.
2025-11-04 23:11:31 -05:00
Thomas Harte 301df785fe Fix weird Os for 0s substitution. 2025-11-04 23:11:06 -05:00
Thomas Harte 9657109471 Correct comment. 2025-11-04 23:10:14 -05:00
Thomas Harte 139569e291 Detect CP/M discs and route to the Z80 second processor. 2025-11-04 22:46:15 -05:00
Thomas Harte 1383c1dad4 Merge pull request #1625 from TomHarte/TubeZ80
Add Z80 second processor.
2025-11-04 22:35:26 -05:00
Thomas Harte 1d2cdd85a3 Explain size limit. 2025-11-04 22:27:05 -05:00
Thomas Harte 76082b1271 Allow DSD discs to be twice as large. 2025-11-04 22:20:06 -05:00
Thomas Harte 25dcbf918d Ensure Z80 interrupts end. 2025-11-04 22:04:41 -05:00
Thomas Harte 6c72c1842b Change interrupt vector. 2025-11-04 21:57:20 -05:00
Thomas Harte 4e4388dc35 Adjust reset logic, 6502 tube paging. 2025-11-04 21:34:45 -05:00
Thomas Harte 54bff80ecc Fix: it's the PC going above 0x8000 that ends ROM visibility. 2025-11-04 18:21:00 -05:00
Thomas Harte 78073aaa11 Add Z80 tube ROM. 2025-11-04 17:45:27 -05:00
Thomas Harte 4df01a7e0d Ensure Z80 processes. 2025-11-04 17:38:46 -05:00
Thomas Harte f4d15d0640 Mostly wire in a Z80 second processor. 2025-11-04 17:36:30 -05:00
Thomas Harte 64842d4de2 Merge pull request #1621 from TomHarte/Tube
Introduce experimental 6502 tube processor support.
2025-11-04 13:53:35 -05:00
Thomas Harte f52315ac92 Further annotate. 2025-11-04 12:48:49 -05:00
Thomas Harte cc88877109 Move tube holder out of line. 2025-11-04 12:46:15 -05:00
Thomas Harte d7568e57c3 Make other layout edits. 2025-11-04 09:27:04 -05:00
Thomas Harte d49b301fca Improve commentary. 2025-11-04 09:22:17 -05:00
Thomas Harte 0113bcbea7 Include tube storage only when needed. 2025-11-04 09:20:28 -05:00
Thomas Harte 0332bb4f12 Merge pull request #1624 from TomHarte/ArchimedesVideoTiming
Pull clock divider inside the loop.
2025-11-03 21:32:11 -05:00
Thomas Harte 82a5d5116a Expose processor option to macOS users, too. 2025-11-03 21:31:44 -05:00
Thomas Harte 62a6797ef3 The second processor appears to be a 65c02. 2025-11-03 21:06:34 -05:00
Thomas Harte bb66033682 Fix interrupting inward FIFO. 2025-11-03 21:02:14 -05:00
Thomas Harte d4aa0799a9 Take a swing at reset. 2025-11-03 20:50:47 -05:00
Thomas Harte fba2d37714 Correct flags, out-of-bounds writes. 2025-11-03 20:32:08 -05:00
Thomas Harte e891697f88 Attempt full wiring. 2025-11-03 20:19:31 -05:00
Thomas Harte ee6ac3b4a9 Restore build. 2025-11-03 17:47:22 -05:00
Thomas Harte c8130f9d6f Begin FIFO wiring. 2025-11-03 17:27:48 -05:00
Thomas Harte 8abd837c8b Ths host is now possibly awaiting tube activity. 2025-11-03 13:33:26 -05:00
Thomas Harte 02ad080bb8 Apply clock multiplier. 2025-11-03 13:20:51 -05:00
Thomas Harte 5887e3e580 Provide ROM to second processor. 2025-11-03 13:17:35 -05:00
Thomas Harte 1994b2dc9f Pay for a second processor, even if disconnected. 2025-11-03 13:12:19 -05:00
Thomas Harte d7a82d00b1 Pull clock divider inside the loop. 2025-11-03 12:49:53 -05:00
Thomas Harte 0017bd6d0f Pull clock divider inside the loop. 2025-11-03 12:49:30 -05:00
Thomas Harte 15f30995b1 Promote tube processor to template parameter. 2025-11-03 09:26:31 -05:00
Thomas Harte 37ca0e4f81 Introduce one-directional FIFO. 2025-11-02 23:18:56 -05:00
Thomas Harte e400aa200c Slightly clean-up spacing. 2025-11-02 23:18:56 -05:00
Thomas Harte e168298aa0 Add Tube boot ROM. 2025-11-02 23:18:56 -05:00
Thomas Harte c4afbf8f2e Recast middle button as tertiary. 2025-11-02 23:12:38 -05:00
Thomas Harte 112aff9887 SDL: change mouse button indices. 2025-11-02 23:12:38 -05:00
Thomas Harte a8207ded4f Regularise mouse-event clock. 2025-11-02 23:12:38 -05:00
Thomas Harte bafef023a5 Remove danglng misuses of previous_posted_rect_. 2025-10-31 19:36:18 -04:00
Thomas Harte 9b39eebc2d Attempt to keep dynamic framing going permanently, for smooth transitions. 2025-10-31 15:58:09 -04:00
Thomas Harte 02fdcd6ece Merge pull request #1619 from TomHarte/OptionalFullFrame
Make dynamic cropping optional for the Amstrad CPC and BBC Micro.
2025-10-30 20:46:03 -04:00
Thomas Harte 49601e0a78 Introduce dynamic crop option to the BBC. 2025-10-30 09:09:53 -04:00
Thomas Harte cf10abff5b Attempt to smooth framing transitions. 2025-10-29 21:47:05 -04:00
Thomas Harte e75c27cb66 Add macOS UI to dynamic cropping option, apply at runtime to CPC. 2025-10-29 21:21:21 -04:00
Thomas Harte d27f0e3633 Declare that dynamic crop is an option. 2025-10-29 17:43:19 -04:00
Thomas Harte e19bd0d517 Alphabetise; mark override. 2025-10-29 17:43:05 -04:00
Thomas Harte 3427120b3f Expose dynamic crop option from the CPC. 2025-10-29 17:37:58 -04:00
Thomas Harte ecc623cd6c Improve option naming, add one for dynamic crop. 2025-10-29 17:19:15 -04:00
Thomas Harte 3ef615f508 Merge pull request #1618 from TomHarte/FurtherWarnings
Localise shorthand `Storage`; note that labels may be unused.
2025-10-29 12:39:50 -04:00
Thomas Harte 1c4c3a6cae Avoid VLA extension. 2025-10-29 12:29:15 -04:00
Thomas Harte fd7142f6a1 Comment out unused storage. 2025-10-29 12:28:09 -04:00
Thomas Harte b30fda3c36 Localise shorthand Storage; note that labels may be unused. 2025-10-29 12:19:25 -04:00
Thomas Harte 7e43b40415 Merge pull request #1617 from TomHarte/NoWarnings
Resolve x86-related build warnings plus various whitespace deficiencies.
2025-10-29 12:16:53 -04:00
Thomas Harte 53501a9443 Merge pull request #1616 from TomHarte/BBCNew6502
Adapt the BBC Micro to use the new 6502.
2025-10-29 12:16:16 -04:00
Thomas Harte c5dc65fc61 Resolve various whitespace errors. 2025-10-29 11:50:56 -04:00
Thomas Harte b389889e1f Limit sizes to pointer size. 2025-10-29 09:19:04 -04:00
Thomas Harte 02a29056b7 Resolve IP size imbalance. 2025-10-29 09:16:30 -04:00
Thomas Harte 5e789b4b0c Grab last PC operation address; ensure power-on reset is predictable. 2025-10-29 09:03:35 -04:00
Thomas Harte 9ff09a45be Allow is-1mhz decision to observe shortened addresses. 2025-10-28 21:32:09 -04:00
Thomas Harte fc02a3d34b Merge branch 'master' into BBCNew6502 2025-10-28 21:22:30 -04:00
Thomas Harte 48f983c040 Merge pull request #1613 from TomHarte/Turbo6502
Introduce alternative 6502 implementation.
2025-10-28 21:21:47 -04:00
Thomas Harte dd25d387fe Allow for __COUNTER__ potentially not starting at 0. 2025-10-28 21:07:03 -04:00
Thomas Harte 80a503f317 Adjust formatting. 2025-10-28 20:54:05 -04:00
Thomas Harte 5aa9168dd6 Make overflow private. 2025-10-28 20:49:59 -04:00
Thomas Harte b3f01fe314 Move carry into private storage. 2025-10-28 20:43:57 -04:00
Thomas Harte e688d87c22 Move negative and zero into private storage. 2025-10-28 18:23:16 -04:00
Thomas Harte 332fb2f384 Make decimal flag private. 2025-10-28 17:34:31 -04:00
Thomas Harte 5332bcd6b4 Clarify set/get difference; make interrupt flag storage private. 2025-10-28 17:32:10 -04:00
Thomas Harte 55c59e6164 Start hiding Flags implementation. 2025-10-28 17:24:53 -04:00
Thomas Harte 80bfa1859f Merge branch 'Turbo6502' into BBCNew6502 2025-10-27 22:22:11 -04:00
Thomas Harte 58e1880773 Eliminate 'addr' side effects. 2025-10-27 22:14:48 -04:00
Thomas Harte c8c4c99f09 Incorporate IRQ timing test; accept that it must be tested within the access macro. 2025-10-27 21:32:52 -04:00
Thomas Harte 350f424055 Merge branch 'Turbo6502' into BBCNew6502 2025-10-27 17:27:04 -04:00
Thomas Harte bc5b7a6725 Unpublish WriteableReader. 2025-10-27 17:01:29 -04:00
Thomas Harte 7b4e71e6dd Just use the preprocessor then. Yuck. 2025-10-27 13:10:24 -04:00
Thomas Harte a32202ab72 Be more overt in trying to avoid "use of undeclared identifier". 2025-10-27 12:52:57 -04:00
Thomas Harte 193c027c8b To simplify debugging, add non-constructing path.
This won't have any effect on generated code.
2025-10-27 12:42:00 -04:00
Thomas Harte 3dd07b6ac1 To simplify debugging, add non-constructing path.
This won't have any effect on generated code.
2025-10-27 12:41:17 -04:00
Thomas Harte 12d912b627 Add insurance against bus handler not writing. 2025-10-27 12:37:38 -04:00
Thomas Harte f847a72696 Add insurance against bus handler not writing. 2025-10-27 12:37:14 -04:00
Thomas Harte 967c3f6dba Ensure NMI isn't perpetual. 2025-10-26 21:56:35 -04:00
Thomas Harte 6cf87e3aa9 Ensure reads always achieve something. 2025-10-26 21:56:13 -04:00
Thomas Harte cbc6477431 Ensure NMI isn't perpetual. 2025-10-26 21:55:09 -04:00
Thomas Harte 45d0f101a7 Switch the BBC Micro to 6502Mk2. 2025-10-26 21:04:59 -04:00
Thomas Harte cba96aee37 Honour interrupt flag. 2025-10-25 17:02:48 -04:00
Thomas Harte 17325834b5 Implement is_resetting. 2025-10-25 17:02:27 -04:00
Thomas Harte 3673144a44 Enable all tests. 2025-10-25 09:00:55 -04:00
Thomas Harte 4df49d9f18 Round out interrupt signalling. 2025-10-25 08:54:25 -04:00
Thomas Harte 8b04608d68 Implement STP and WAI. 2025-10-24 23:47:30 -04:00
Thomas Harte 2bac276870 Populate type_of. 2025-10-24 23:42:40 -04:00
Thomas Harte 213f9850e7 Add WDC65C02 decoder. 2025-10-24 23:41:55 -04:00
Thomas Harte 378bffbf84 Implement BBR/BBS. 2025-10-24 23:37:18 -04:00
Thomas Harte c291d5313d Fix PLX. 2025-10-24 22:22:05 -04:00
Thomas Harte 1f6f665639 Exclude all 65c02 NOPs from timing checks. 2025-10-24 22:19:15 -04:00
Thomas Harte e81233c586 Implement JMP (abs,x). 2025-10-24 22:16:36 -04:00
Thomas Harte b946029394 Correct 65c02 JMPAbsoluteIndirect. 2025-10-24 21:57:53 -04:00
Thomas Harte 6095936354 Exclude tests I believe faulty. 2025-10-24 21:54:23 -04:00
Thomas Harte fe79a1231d Add extra cycle to immediate decimal arithmetic. 2025-10-24 21:39:14 -04:00
Thomas Harte 76a5872d17 Install extra cycle for 65c02 decimal arithmetic. 2025-10-24 21:24:05 -04:00
Thomas Harte 0d72c75e15 Give modify stalls to fast NOPs. 2025-10-24 16:51:07 -04:00
Thomas Harte 2e0e89c494 Implement fast modify path; fix more NOPs. 2025-10-24 16:43:57 -04:00
Thomas Harte 7d6b7a5874 Adjust 0x?b NOPs. 2025-10-24 15:57:54 -04:00
Thomas Harte 48f8ddf53a 65c02: make AbsoluteIndexed modify cycle harmless. 2025-10-24 15:55:45 -04:00
Thomas Harte 9aae07b737 Implement zero indirect addressing mode. 2025-10-24 15:53:55 -04:00
Thomas Harte d7abdc8017 Transfer ownership of final PC increment, to accomodate 65c02 misreads. 2025-10-24 15:49:17 -04:00
Thomas Harte cb81156835 65c02: distinguish 'fast' NOPs from regular. 2025-10-24 13:52:32 -04:00
Thomas Harte 1fd8d94e2e Import further NOPs. 2025-10-24 13:33:10 -04:00
Thomas Harte e4fe127444 Fix 65c02 modify cycles: read/read/write, not read/write/write. 2025-10-24 13:30:10 -04:00
Thomas Harte aeabd5f113 Patch in TSB and TRB. 2025-10-24 12:33:13 -04:00
Thomas Harte 58f7d4065c 65c02: support single-cycle NOP. 2025-10-24 12:29:53 -04:00
Thomas Harte 60f25a3ba4 Add some of the easier overrides. 2025-10-24 12:21:53 -04:00
Thomas Harte d267571dc6 Add spot to fill in Synertek mappings. 2025-10-24 12:13:10 -04:00
Thomas Harte 3c34aa6696 Setup to test 65c02s. 2025-10-24 12:07:40 -04:00
Thomas Harte 5dc00a2092 Update #undef list. 2025-10-24 12:00:22 -04:00
Thomas Harte b20d489bf0 Remove SHA/SHX/etc. 2025-10-24 11:58:55 -04:00
Thomas Harte df39870587 Factor out index decision. 2025-10-24 11:53:55 -04:00
Thomas Harte f742eab4be Reduce to a generic case. 2025-10-23 21:54:50 -04:00
Thomas Harte e9c8c61dcf Reformulate to be slightly more conditional, but substantially deduplicate code. 2025-10-23 21:52:31 -04:00
Thomas Harte e5f09002e9 Extract bit operators. 2025-10-23 20:47:55 -04:00
Thomas Harte d42f005e17 Improve consistency. 2025-10-23 20:43:15 -04:00
Thomas Harte 24e060abee Elide ADC logic. 2025-10-23 19:54:07 -04:00
Thomas Harte 8b6d763442 Reduce duplication within ARR. 2025-10-23 19:42:36 -04:00
Thomas Harte e239745f63 Fix typo. 2025-10-23 19:35:40 -04:00
Thomas Harte cfef2b4e19 Eliminate 16-bit arithmetic from SBX. 2025-10-23 19:32:50 -04:00
Thomas Harte cf93c39881 Pull out overflow logic, remove 16-bit arithmetic from ADC. 2025-10-23 18:23:09 -04:00
Thomas Harte 5d223bce4c Pull out and simplify compare. 2025-10-23 17:47:15 -04:00
Thomas Harte b454ebc1c9 Extricate further operations. 2025-10-23 17:41:13 -04:00
Thomas Harte 7cf9910cae Pull ADC, SBC and some others out.
This resolves the wacky control flow somewhat.
2025-10-23 17:15:21 -04:00
Thomas Harte 79ab1d8cb1 Implement final SHA. 2025-10-23 13:42:23 -04:00
Thomas Harte 7cd20f5d12 Add all absolute-indexed oddities. 2025-10-23 13:39:03 -04:00
Thomas Harte 5396d751e1 Support SHX and a SHA. 2025-10-23 13:27:55 -04:00
Thomas Harte d23e715650 Decision: these five have weird addressing, so that counts as weird addressing modes. 2025-10-23 13:13:01 -04:00
Thomas Harte 0791bce338 Fix everything other than the oddball SHA/SHX/SHY/SHS. 2025-10-22 22:12:32 -04:00
Thomas Harte 2bcb74072a Add trqnsfers, correct a STA. 2025-10-22 21:20:11 -04:00
Thomas Harte c5f2f17f33 Further populate perform.
First failing test is now 0x8a.
2025-10-22 21:13:57 -04:00
Thomas Harte 62a8bf4261 Add missing RTS cycle. 2025-10-22 17:56:11 -04:00
Thomas Harte ebda18b44e Implement the two JMPs. 2025-10-22 17:52:55 -04:00
Thomas Harte a8f41b9017 Implement RTI and RTS. 2025-10-22 17:48:19 -04:00
Thomas Harte 410c19a7da Fix pull. 2025-10-22 17:43:08 -04:00
Thomas Harte a346e2e04b Transcribe bit logic. 2025-10-22 17:40:03 -04:00
Thomas Harte 2d114b6677 Implement JSR. 2025-10-22 17:37:01 -04:00
Thomas Harte 02e74ca1f4 Add absolute-indexed addressing. 2025-10-22 17:18:54 -04:00
Thomas Harte 69122cdec4 Swing at zero-indexed addressing. 2025-10-22 17:12:23 -04:00
Thomas Harte d730168631 Remove dead label. 2025-10-22 13:30:24 -04:00
Thomas Harte 2f210ebe3b Fix IndexedIndirect/IndirectIndexed confusion, proceed to test 0x14. 2025-10-22 13:29:45 -04:00
Thomas Harte 693b53baa2 Proceed through absolute addressing to test 0x10. 2025-10-22 13:05:46 -04:00
Thomas Harte 77554879a5 Add missing 0x?e group. 2025-10-22 13:00:36 -04:00
Thomas Harte 45363922b5 Adds rolls and shifts, and zero-page addressing. 2025-10-22 12:56:07 -04:00
Thomas Harte 0463c1ceda Reduce repetition. 2025-10-21 23:21:11 -04:00
Thomas Harte b35a55a658 Implement jamming. 2025-10-21 23:16:59 -04:00
Thomas Harte 4da68c9fa8 Implement IndirectIndexedRead. 2025-10-21 23:01:41 -04:00
Thomas Harte 72f133f31b Do enough work to verify BRK. 2025-10-21 22:07:35 -04:00
Thomas Harte af4a8f6d9c Add enough to attempt to run processor tests. 2025-10-21 21:30:42 -04:00
Thomas Harte b5899a2e42 Implement simplest operations. 2025-10-21 17:33:36 -04:00
Thomas Harte 4ee8f8564e Catch unimplemented. 2025-10-21 13:40:23 -04:00
Thomas Harte ff08c03bc5 Coral into building. 2025-10-21 13:31:48 -04:00
Thomas Harte 95dd430b0d Shoehorn in an invocation. 2025-10-21 13:12:58 -04:00
Thomas Harte 20eb8b1442 Move RDY inline. 2025-10-21 12:59:16 -04:00
Thomas Harte 2d6a0b3ed0 Add a branch to nowhere. 2025-10-20 23:08:04 -04:00
Thomas Harte 80f0ce78e0 Eliminate unused enum. 2025-10-20 22:51:12 -04:00
Thomas Harte fde0e2434e Attempt to transcribe base 6502 instruction set. 2025-10-20 22:50:14 -04:00
Thomas Harte fe2da7fd95 Merge branch 'master' into Turbo6502 2025-10-20 13:56:58 -04:00
Thomas Harte 25e783ff2f Merge pull request #1614 from TomHarte/6845Reading
Return 0 for write-only and nonexistent registers.
2025-10-20 13:56:27 -04:00
Thomas Harte 2eb94f1b66 Return 0 for write-only and nonexistent registers. 2025-10-20 13:26:22 -04:00
Thomas Harte 2cdf6ac8f9 Add interrupt, RDY and instruction fetch logic. 2025-10-20 13:16:03 -04:00
Thomas Harte 309c58a93d Include in CI builds; start implementation. 2025-10-19 23:29:27 -04:00
Thomas Harte 700bd0ddd4 Merge branch 'master' into Turbo6502 2025-10-19 22:21:19 -04:00
Thomas Harte bd5a2f240d Record version number. 2025-10-19 19:51:06 -04:00
Thomas Harte 73054d971c Merge pull request #1612 from TomHarte/NoCounter
Eliminate CompileTimeCounter.
2025-10-19 19:45:52 -04:00
Thomas Harte 8c7f2491d7 Eliminate CompileTimeCounter. 2025-10-19 19:36:36 -04:00
Thomas Harte 24fcbea6f2 Add TODO. 2025-10-19 19:28:38 -04:00
Thomas Harte fddc9c8c48 Add base classes, reshuffle. 2025-10-18 22:45:09 -04:00
Thomas Harte 294893b7da Start transferring 6502 precepts. 2025-10-18 22:31:00 -04:00
Thomas Harte 564542420b Merge pull request #1609 from TomHarte/BBCAdvancedDiscToolkit
BBC Micro: add ADT ROM if available.
2025-10-18 21:03:07 -04:00
Thomas Harte 3f7e3e6d75 Use the ADT ROM if available. 2025-10-18 09:33:55 -04:00
Thomas Harte 6521d7d02b Add ADT 1.40 to ROM catalogue. 2025-10-18 09:15:17 -04:00
Thomas Harte ad162a4e4a Merge pull request #1607 from TomHarte/MacAudio
Correct Mac audio buffering
2025-10-17 21:58:57 -04:00
Thomas Harte 676b1f6fdc Adopt brackets, as is now a macro. 2025-10-17 21:38:12 -04:00
Thomas Harte 406ef4e16c Record new version number. 2025-10-17 21:00:11 -04:00
Thomas Harte 217976350b Merge pull request #1606 from TomHarte/CPCAnalyser
Improve file-selection logic.
2025-10-17 18:38:33 -04:00
Thomas Harte e8f860d6fe Improve file-selection logic. 2025-10-17 18:25:44 -04:00
Thomas Harte 859e6e2396 Merge pull request #1605 from TomHarte/CompiletimeCounter
Add compile-time counter; switch 1770 to using it for sequence points.
2025-10-17 17:44:13 -04:00
Thomas Harte 51186e615f Add warning. 2025-10-17 16:10:23 -04:00
Thomas Harte bd8287fda3 Resolve obstructive warning. 2025-10-17 15:21:47 -04:00
Thomas Harte 287ff99bbc Use Numeric::Counter. 2025-10-17 15:06:05 -04:00
Thomas Harte 0bbfcedabb Alphabetise includes. 2025-10-17 15:05:52 -04:00
Thomas Harte 812e1e637d Eliminate magic constant. 2025-10-17 11:22:02 -04:00
Thomas Harte f20fd38940 Introduce a compile-time counter; use it for 1770 sequencing. 2025-10-17 11:19:21 -04:00
Thomas Harte b4cfabc005 Merge pull request #1604 from TomHarte/CPCBrightness
Apply outputMultiplier in direct RGB output.
2025-10-16 23:42:43 -04:00
Thomas Harte c49e160501 Apply outputMultiplier in direct RGB output. 2025-10-16 22:44:40 -04:00
Thomas Harte a0a24902d5 Merge pull request #1603 from TomHarte/JoystickDirection
Provide joystick up/down as down = positive again.
2025-10-16 21:58:22 -04:00
Thomas Harte 1047bc8a80 Provide up/down in down = positive again. 2025-10-16 21:30:49 -04:00
Thomas Harte 0eed49c4cb Merge pull request #1599 from TomHarte/AutoClip
Automatically select and zoom to 'interesting' content.
2025-10-16 21:04:23 -04:00
Thomas Harte e7f09e2ece Fix first reading. 2025-10-16 20:51:39 -04:00
Thomas Harte 89678f1ea7 Tweak decision process, add maximum scale parameter. 2025-10-16 16:26:16 -04:00
Thomas Harte e43ec7d549 Correct bias to the left. 2025-10-16 11:50:32 -04:00
Thomas Harte 95395132f0 Make stability threshold modal. 2025-10-16 11:29:41 -04:00
Thomas Harte 89293d8481 Add stability as a prefilter. 2025-10-16 11:26:07 -04:00
Thomas Harte e6de24557f Set appropriate BBC bounds. 2025-10-15 23:39:55 -04:00
Thomas Harte 66d76dc36a Adjust dynamic semantics again. 2025-10-15 23:30:25 -04:00
Thomas Harte 06629def62 Restore some fixed areas, work on API. 2025-10-14 22:51:36 -04:00
Thomas Harte 97aeb5e930 Merge branch 'AutoClip' of github.com:TomHarte/CLK into AutoClip 2025-10-14 22:23:12 -04:00
Thomas Harte bf45b6e20b Merge branch 'master' into AutoClip 2025-10-14 22:23:06 -04:00
Thomas Harte 6ad41326b0 Remove errant space. 2025-10-13 23:13:45 -04:00
Thomas Harte 2bbca3c169 Slightly beef up 8272 logging. 2025-10-13 23:09:10 -04:00
Thomas Harte ae903b0712 Increase consts. 2025-10-13 22:53:52 -04:00
Thomas Harte a2a7f82716 Merge branch 'master' into AutoClip 2025-10-13 22:51:21 -04:00
Thomas Harte 00456c891a Merge pull request #1601 from TomHarte/EvenShorterText
Even shorter text
2025-10-13 22:50:53 -04:00
Thomas Harte afd5faaab1 Tweak constraints again. 2025-10-13 13:29:31 -04:00
Thomas Harte bb33cf0f8d Shorten text even further. 2025-10-13 13:26:49 -04:00
Thomas Harte edc510572a Reorder constraints. 2025-10-13 10:55:23 -04:00
Thomas Harte bc6cffa95c Enable full dynamic selection again for the CPC. 2025-10-13 08:54:33 -04:00
Thomas Harte 48ed2912b0 Reenable dynamic framing. 2025-10-12 22:30:37 -04:00
Thomas Harte a8af262c41 Avoid shadowing, use normal instance suffix. 2025-10-12 21:41:22 -04:00
Thomas Harte dcf49933bc Merge branch 'master' into AutoClip 2025-10-12 21:32:22 -04:00
Thomas Harte 9c014001da Merge pull request #1600 from TomHarte/ShorterOpenDialogue
macOS: shorten prompt to File -> Open...
2025-10-12 21:27:07 -04:00
Thomas Harte 4f410088dd Improve constraints. 2025-10-12 21:17:07 -04:00
Thomas Harte e1c1b66dc5 Shorten footer text. 2025-10-12 20:55:29 -04:00
Thomas Harte 23c3a1fa99 Lean further overtly towards a state machine. 2025-10-12 08:59:07 -04:00
Thomas Harte ef6e1b2f74 Unpublish enum, simplify function names. 2025-10-11 15:07:09 -04:00
Thomas Harte e130ae0a8a Merge branch 'AutoClip' of github.com:TomHarte/CLK into AutoClip 2025-10-10 22:27:47 -04:00
Thomas Harte 1a1e3281e4 Avoid overlong line; add consts. 2025-10-10 22:27:29 -04:00
Thomas Harte a4e55c9362 Avoid overlong line. 2025-10-10 22:25:16 -04:00
Thomas Harte 0b4c51eebd Scale interesting rects once only. 2025-10-10 22:23:22 -04:00
Thomas Harte 1107f0d9a3 For relevant machines: pick different amounts of border to show. 2025-10-10 21:58:03 -04:00
Thomas Harte 775819432b Apply warm-up for the Apple II and ZX Spectrum. 2025-10-10 21:37:31 -04:00
Thomas Harte a71a60937f Prewarm Macintosh; mark RAM as const. 2025-10-10 18:02:46 -04:00
Thomas Harte 5e661fe96b Add prewarming to the Oric. 2025-10-10 18:00:54 -04:00
Thomas Harte a9f5b17fcb Eliminate frame_is_complete_, add prewalming loop. 2025-10-10 17:59:10 -04:00
Thomas Harte b0c2b55fc9 Fix initial bounds, slightly update breathing space. 2025-10-10 15:44:54 -04:00
Thomas Harte 925832aac5 Include tolerance for interlacing. 2025-10-10 14:29:40 -04:00
Thomas Harte 994131e2ea Use stability as test for initial frame. 2025-10-10 14:18:25 -04:00
Thomas Harte f8d27d0ae0 Remove explicit visible area declarations. 2025-10-09 22:17:02 -04:00
Thomas Harte fc50af0e17 Adjust vertical sync test. 2025-10-09 22:16:43 -04:00
Thomas Harte 087d3535f6 Start focussing on getting a good crop for 'static' machines. 2025-10-09 18:01:46 -04:00
Thomas Harte e9d310962f Support an asymmetric 90%. 2025-10-09 14:01:52 -04:00
Thomas Harte 0f9c89d259 Limit to 90%. 2025-10-09 13:59:03 -04:00
Thomas Harte 258c37685b Fix axis. 2025-10-09 13:53:35 -04:00
Thomas Harte 56f092a0c3 Try a rolling average of 250 frames, subject to thresholding. 2025-10-09 13:51:19 -04:00
Thomas Harte 6c3048ffbf Relax flywheel response rate again. 2025-10-08 22:12:58 -04:00
Thomas Harte c58eba61de Extend required stability window. 2025-10-08 22:00:32 -04:00
Thomas Harte 8a54773f1b Reduce Metal buffer thrashing. 2025-10-08 21:19:31 -04:00
Thomas Harte 2c483e7b97 Avoid nullptr dereference if there is no activity observer. 2025-10-08 17:42:57 -04:00
Thomas Harte 1027e9ffdc Add but abandon first attempt at sane limits. 2025-10-08 17:34:54 -04:00
Thomas Harte 85d6957e03 Attempt to do better at startup. 2025-10-08 14:33:49 -04:00
Thomas Harte c3609b66a9 Attempt a quick snap at startup. 2025-10-08 14:13:34 -04:00
Thomas Harte 605f4a92d7 Use animation curve, try to be fooled less at startup. 2025-10-08 12:58:12 -04:00
Thomas Harte d395e2bc75 Introduce animated crop. 2025-10-08 12:18:04 -04:00
Thomas Harte e6ccdc5a97 Edge towards animations. 2025-10-07 23:00:36 -04:00
Thomas Harte a68c7aa45f Use filter, attempt to be intelligent about the border. 2025-10-07 22:56:51 -04:00
Thomas Harte 66e959ab65 Temporarily exclude borders. 2025-10-07 22:42:26 -04:00
Thomas Harte d68b172a40 Introduce preliminary output frame filtering. 2025-10-07 22:36:36 -04:00
Thomas Harte d3ee778265 Eliminate common black border -> blank mapping.
Will move this inside the CRT.
2025-10-07 22:10:14 -04:00
Thomas Harte da96df7df7 Ensure OpenGL appropriately letterboxes or pillarboxes. 2025-10-07 21:37:22 -04:00
Thomas Harte 4ea82581ec Factor out zoom logic, start trying to knock OpenGL into shape. 2025-10-07 13:29:21 -04:00
Thomas Harte 4473d3400e Reformat slightly. 2025-10-07 12:52:55 -04:00
Thomas Harte 2f1f843e48 Correct origin.y minification. 2025-10-07 12:44:03 -04:00
Thomas Harte 53a3d9042e Switch to multiline strings, shorter comments. 2025-10-06 22:58:50 -04:00
Thomas Harte 6eb32f98b2 Fix rectangle union. 2025-10-06 22:50:29 -04:00
Thomas Harte 0fad97ed48 Apply different axis scales. 2025-10-06 22:36:19 -04:00
Thomas Harte 27246247a2 OpenGL: fix centring. 2025-10-06 20:58:42 -04:00
Thomas Harte cbc96e2223 Reformat in proximity. 2025-10-06 20:45:20 -04:00
Thomas Harte 8fdf32cde8 Avoid OpenGL churn. 2025-10-06 20:43:12 -04:00
Thomas Harte 03a94e59e2 Merge branch 'master' into AutoClip 2025-10-06 20:29:08 -04:00
Thomas Harte 2c0610fef8 Accumulate union of all pixel-bearing scans. 2025-10-06 20:26:15 -04:00
Thomas Harte 60b3c51085 Merge pull request #1598 from TomHarte/DynamicViewArea
Begin move towards automatic cropping.
2025-10-06 19:07:20 -04:00
Thomas Harte d7b5a45417 Adopt even more aggressive mixing, avoid negative. 2025-10-06 16:20:54 -04:00
Thomas Harte e11060bde8 Further improve asserting. 2025-10-06 16:16:06 -04:00
Thomas Harte 4653de9161 Pull out and comment on mix, improve asserts. 2025-10-06 16:11:59 -04:00
Thomas Harte 1926ad9215 Normalise and slightly reformat flywheel interface. 2025-10-06 14:53:08 -04:00
Thomas Harte 33d047c703 Add a const. 2025-10-06 14:38:40 -04:00
Thomas Harte fadda00246 Eliminate flywheel 'get's, hence normalise CRT line lengths. 2025-10-06 14:36:39 -04:00
Thomas Harte a3fed788d8 Reduce repetition. 2025-10-06 14:27:57 -04:00
Thomas Harte dde31e8687 Reformat inner loop. 2025-10-06 14:26:03 -04:00
Thomas Harte 190fb009bc Clean up CRT.hpp for formatting. Switch pointer to reference. 2025-10-06 13:55:03 -04:00
Thomas Harte 62574d04c6 Avoid some redundant parameter names. 2025-10-06 13:32:28 -04:00
Thomas Harte 2496257bcf Adopt normative public-then-private ordering. 2025-10-06 13:28:04 -04:00
Thomas Harte ab73b4de6b Split off the mismatch warner. 2025-10-06 13:27:10 -04:00
Thomas Harte 6c1c32baca Move flywheels local. 2025-10-04 22:42:56 -04:00
Thomas Harte 239cc15c8f Introduce cubic timing function. 2025-10-04 22:26:09 -04:00
Thomas Harte 6b437c3907 Merge pull request #1597 from TomHarte/NewShaker
Ensure CPCShakerTests is runnable.
2025-10-03 22:33:52 -04:00
Thomas Harte 4756f63169 Ensure CPCShakerTests is runnable. 2025-10-03 22:25:16 -04:00
Thomas Harte 7229acb34f Merge pull request #1596 from TomHarte/FaultyLineLength
Correct collation test, to ensure no accidental buffer mixing.
2025-10-03 18:06:34 -04:00
Thomas Harte 43cb91760a Update SAA5050 row counter only in teletext mode. 2025-10-03 18:05:36 -04:00
Thomas Harte 7bb4d052d1 Correct collation test, to ensure no accidental buffer mixing. 2025-10-03 17:29:45 -04:00
Thomas Harte 5885bdf0f8 Merge pull request #1595 from TomHarte/BBCSaving
Ensure 1770 doesn't get stuck when writing.
2025-10-03 16:51:24 -04:00
Thomas Harte 05042b1859 Remove unnecessary log. 2025-10-03 16:42:04 -04:00
Thomas Harte 3ca2f72184 Merge pull request #1594 from TomHarte/CompoundingTyper
Simplify typer logic.
2025-10-03 16:41:43 -04:00
Thomas Harte b076450b73 Ensure 1770 doesn't get stuck when writing. 2025-10-03 16:39:27 -04:00
Thomas Harte 7a90662c06 Merge branch 'master' into CompoundingTyper 2025-10-03 16:06:47 -04:00
Thomas Harte eb31aaeb7d Merge pull request #1593 from TomHarte/FakeLightpen
Support CB2 output strobe as triggering lightpen capture.
2025-10-03 16:05:11 -04:00
Thomas Harte ebfb215246 Support CB2 output strobe as triggering lightpen capture. 2025-10-03 15:39:16 -04:00
Thomas Harte eb97e4e518 Reserve entire FF page; simplify logic. 2025-10-03 13:10:45 -04:00
Thomas Harte 61d3e65c05 Merge pull request #1591 from TomHarte/BetterTyper
BBC typer: properly support lowercase input.
2025-10-03 12:27:32 -04:00
Thomas Harte 0ac5681d13 Confirmed: 'capslock' has yet to become a single word. 2025-10-03 11:37:39 -04:00
Thomas Harte 1e27c5759b Add missing const. 2025-10-03 09:26:53 -04:00
Thomas Harte 5e71aedc99 Support lowercase typing into the BBC. 2025-10-03 09:25:58 -04:00
Thomas Harte d0d8c2316b Bump version number. 2025-10-02 22:55:50 -04:00
Thomas Harte 8f0a5b2191 Merge pull request #1589 from TomHarte/6522PB7
Expose PB7 timer regardless.
2025-10-02 22:48:31 -04:00
Thomas Harte 242b180862 Expose PB7 timer regardless. 2025-10-02 22:40:36 -04:00
Thomas Harte feb4766d7b Merge pull request #1587 from TomHarte/BetterAnalogue
Improve mapping of digital inputs to analogue joysticks.
2025-10-02 21:22:21 -04:00
Thomas Harte a224eea077 Merge pull request #1586 from TomHarte/SAA5050Split
Make centre row the thick one.
2025-10-02 20:59:55 -04:00
Thomas Harte ecefbc23ae Resolve ability of analogue joysticks to get stuck with digital input. 2025-10-02 20:52:16 -04:00
Thomas Harte e5e0cbfc53 Make centre row the thick one. 2025-10-02 20:35:53 -04:00
Thomas Harte 38781c9395 Merge pull request #1585 from TomHarte/BBCJoystick
Add BBC joysticks.
2025-10-02 20:21:31 -04:00
Thomas Harte e70d72b614 Merge pull request #1584 from TomHarte/CursorMask
Variously tweak and improve BBC graphics infrastructure.
2025-10-02 17:44:10 -04:00
Thomas Harte fcf648bbb2 Flip axes, maximise range. 2025-10-02 17:43:33 -04:00
Thomas Harte a8325b6bce Add BBC joysticks. 2025-10-02 17:10:27 -04:00
Thomas Harte 22554a9ba4 Incorporate a one-column delay into CPC output. 2025-10-02 15:22:01 -04:00
Thomas Harte 02a10ef651 Fall in line with nonsense. 2025-10-02 15:11:01 -04:00
Thomas Harte e3ca44f3ca Reseparate pixels. 2025-10-02 13:24:00 -04:00
Thomas Harte a9abc0dd5f Document a little further. 2025-10-02 09:30:33 -04:00
Thomas Harte cbcc7c718e SAA: smooth output just in time. 2025-10-02 09:20:58 -04:00
Thomas Harte 4377c79068 Switch blink rates. 2025-10-02 09:10:12 -04:00
Thomas Harte 514993bc2e Pull out cursor mask as a separate concern. 2025-10-02 07:56:07 -04:00
Thomas Harte c182176134 Merge pull request #1583 from TomHarte/SizedInt
Rename SizedCounter.
2025-10-01 22:23:51 -04:00
Thomas Harte abd1f10395 Ensure <=> is implemented. 2025-10-01 22:01:35 -04:00
Thomas Harte f279bebc1a Reduce redundant masking. 2025-10-01 21:59:40 -04:00
Thomas Harte 4e3fa5a6ff Use std::popcount. 2025-10-01 21:25:12 -04:00
Thomas Harte 01d355a247 Rename SizedCounter. 2025-10-01 20:58:34 -04:00
Thomas Harte 009f71a186 Update version number. 2025-09-30 21:52:53 -04:00
Thomas Harte de5c311d84 Merge pull request #1582 from TomHarte/SAAFlash
Switch to asymmetric SAA flash interval.
2025-09-30 21:48:16 -04:00
Thomas Harte eefe34f99e Merge branch 'master' into SAAFlash 2025-09-30 21:43:32 -04:00
Thomas Harte 82e3c870b3 Merge pull request #1581 from TomHarte/BetterKeyboardMap
BBC Micro: add character mapper, other improvements.
2025-09-30 21:43:15 -04:00
Thomas Harte d44a1d9761 Give SAA flashing an asymmetric appearance. 2025-09-30 21:33:37 -04:00
Thomas Harte e6fd54c14b Correct forward slash key. 2025-09-30 21:33:15 -04:00
Thomas Harte ccb8e90110 Improve naming. 2025-09-30 21:27:34 -04:00
Thomas Harte a4e66f291a Avoid ambiguity with new DelaySlot 'key'. 2025-09-30 21:23:30 -04:00
Thomas Harte 3ab3f34bef If there's only one BASIC file, CHAIN that. 2025-09-30 21:13:38 -04:00
Thomas Harte 99da8c4424 Avoid assuming 0 is not a valid key. 2025-09-30 21:07:02 -04:00
Thomas Harte 9a1bf1cf74 Reduce delay. 2025-09-30 21:06:56 -04:00
Thomas Harte 2256e99157 Attempt to add a typer. 2025-09-30 20:57:28 -04:00
Thomas Harte 3a5f8b4987 Fill in rest of character mapper. 2025-09-29 23:02:51 -04:00
Thomas Harte 6de5fcc980 Simplify test. 2025-09-29 22:52:56 -04:00
Thomas Harte a454d0d4b7 Begin work on character mapper. 2025-09-29 22:52:48 -04:00
Thomas Harte 67339754e3 Resolve potential crash at startup. 2025-09-29 16:13:56 -04:00
Thomas Harte 7316fe00ee Support native blink speeds. 2025-09-29 16:13:39 -04:00
Thomas Harte feb4a7021c Add enum of BBC key names. 2025-09-29 15:00:56 -04:00
Thomas Harte 5e84b671b6 Merge pull request #1580 from TomHarte/BBCReadme
Add BBC Micro to README.
2025-09-28 22:32:20 -04:00
Thomas Harte bcefef62f9 Add BBC Micro to README. 2025-09-28 22:30:06 -04:00
Thomas Harte cc953dda34 Merge pull request #1579 from TomHarte/EliteInterrupts
6522: avoid handshaking with register F.
2025-09-28 22:25:41 -04:00
Thomas Harte f9e5b0f0c7 6522: avoid handshaking with register F. 2025-09-28 22:12:53 -04:00
Thomas Harte 578654411e Merge pull request #1578 from TomHarte/CapsLock
Add meta as a synonym of control, option as caps lock.
2025-09-27 22:38:43 -04:00
Thomas Harte 247e92cfa2 Correct mapping of F5. 2025-09-27 22:27:57 -04:00
Thomas Harte 66f605de0f Add meta as a synonym of control, option as caps lock. 2025-09-27 22:03:27 -04:00
Thomas Harte 709b0efc9b Merge pull request #1577 from TomHarte/BBCAnalyser
Poke around trying to find a way to discern BBC and Electron software.
2025-09-27 08:17:57 -04:00
Thomas Harte 622679f4c2 Slow flash rate (though it's probably asymmetrical?) 2025-09-27 07:58:11 -04:00
Thomas Harte fdeb421513 Prefer the BBC for DFS media. 2025-09-27 07:49:42 -04:00
Thomas Harte 8fe25cde8d Add search for 'MODE7'. 2025-09-27 07:41:21 -04:00
Thomas Harte fbd71451f1 Enable sideways RAM by default. 2025-09-26 22:00:40 -04:00
Thomas Harte 0d91ce8e6a Add some Electron addresses. 2025-09-26 21:29:49 -04:00
Thomas Harte d71796c88a Support automatic disk starting. 2025-09-26 15:55:04 -04:00
Thomas Harte 277748c8f5 Install a basic search for CRTC/etc addresses. 2025-09-26 15:52:20 -04:00
Thomas Harte 8c1358ace9 Generate a BBC target, even though not yet exposed. 2025-09-26 15:37:32 -04:00
Thomas Harte 1254916058 Merge pull request #1576 from TomHarte/ReadmitBBC
Permit the BBC to appear in release builds.
2025-09-26 13:51:16 -04:00
Thomas Harte f228bee4b8 Permit the BBC to appear in release builds. 2025-09-26 13:49:19 -04:00
Thomas Harte 8094477b09 Merge pull request #1575 from TomHarte/CropFactor
Introduce a crop, centred on the pixel area.
2025-09-26 12:42:43 -04:00
Thomas Harte 32a5bf76cd Introduce a crop, centred on the pixel area. 2025-09-26 12:06:44 -04:00
Thomas Harte 0375d000e6 Merge pull request #1574 from TomHarte/SAA5050
Add SAA5050 and hence BBC Mode 7.
2025-09-25 23:12:28 -04:00
Thomas Harte 141d43d3e5 Further express smoothing in terms of pixel patterns. 2025-09-25 23:02:33 -04:00
Thomas Harte 823f7b1d2e Attempt held graphics. 2025-09-25 22:47:13 -04:00
Thomas Harte 6579f011d0 Support flash and conceal. 2025-09-25 22:37:38 -04:00
Thomas Harte 93f768af9b Bump control codes up in the roster. 2025-09-25 22:18:52 -04:00
Thomas Harte f8c11bf217 Rejig to ensure SAA output ends. 2025-09-25 21:31:21 -04:00
Thomas Harte 26ccd930c3 Begin tidying. 2025-09-25 17:53:54 -04:00
Thomas Harte 82211c7312 Add some 'graphics' support. 2025-09-25 17:50:26 -04:00
Thomas Harte 2015c154fe Correctly clear double-height flags. 2025-09-25 13:28:22 -04:00
Thomas Harte ef17d116a8 Don't permit single-height text on a lower double-height row. 2025-09-25 13:22:25 -04:00
Thomas Harte 46fddc44bf Support double-height text. 2025-09-25 13:21:49 -04:00
Thomas Harte 0214a77cd7 Add TODO. 2025-09-25 13:10:52 -04:00
Thomas Harte 425ed658f1 Support colour control codes, clarify SAA5050 signalling. 2025-09-25 13:03:55 -04:00
Thomas Harte a53adb561e Erase TODO, continue to update state without target. 2025-09-25 09:25:46 -04:00
Thomas Harte 3c3c55090a Port forward ElectrEm's font smoothing. 2025-09-25 09:22:16 -04:00
Thomas Harte ebc04c6520 Eliminate warning. 2025-09-24 22:58:50 -04:00
Thomas Harte 8b0e8f5b13 Move all work [near] definitively into the SAA5050. 2025-09-24 22:55:49 -04:00
Thomas Harte 16132a007e Remove silly call. 2025-09-24 22:26:37 -04:00
Thomas Harte b6e41ceea7 Hack in low-resolution Mode 7. 2025-09-24 22:25:43 -04:00
Thomas Harte 7015e46227 Put together enough of an interface to expect to see some pixels. 2025-09-24 22:08:04 -04:00
Thomas Harte cce2607c80 Add file for SAA5050 logic. 2025-09-24 21:43:25 -04:00
Thomas Harte 9dd2ec8bda Merge pull request #1573 from TomHarte/New6845
Improve 6845.
2025-09-24 21:36:16 -04:00
Thomas Harte 068726e0ab Add TODO. 2025-09-24 21:26:04 -04:00
Thomas Harte 89e86ad9bd Delay publication of the refresh address. 2025-09-24 21:20:20 -04:00
Thomas Harte 2e49bc2044 Add teletext pixel route, albeit without proper selection. 2025-09-24 20:33:07 -04:00
Thomas Harte 174c8dafbf Resolve potential out-of-phase line counter. 2025-09-24 17:26:40 -04:00
Thomas Harte 90a96293de Implement interlace-dependent row addressing. 2025-09-24 17:20:04 -04:00
Thomas Harte 84877c4fec Reenable the cursor; good enough for now. 2025-09-24 14:37:52 -04:00
Thomas Harte a7cceb5fa9 Avoid circular state dependency. 2025-09-24 14:30:37 -04:00
Thomas Harte ca6359a597 Reintroduce pixels, proving myself to be off-by-one. 2025-09-24 14:29:25 -04:00
Thomas Harte b7c3667be1 Work out inadvertent discrepancies. 2025-09-24 14:11:06 -04:00
Thomas Harte b6dea59db3 This tests lines, not rows. 2025-09-24 13:56:16 -04:00
Thomas Harte aa51f13743 Reorder to avoid dependencies upon values that mutate. 2025-09-24 13:54:09 -04:00
Thomas Harte f34ec03ff0 Attempt to fix off-by-one; adopt fixed pixel pattern. 2025-09-24 13:42:17 -04:00
Thomas Harte 1363be59b7 Formalise field size. 2025-09-24 11:17:47 -04:00
Thomas Harte 622c24ef24 This indicates a line, not a row. 2025-09-23 22:36:56 -04:00
Thomas Harte 539b0e49d4 Start in mode 7, reallow interlaced modes. 2025-09-23 14:45:32 -04:00
Thomas Harte 0c42976312 Add notes to self. 2025-09-23 14:42:16 -04:00
Thomas Harte 3f6b3a4fa0 Don't allow a state to be permanently accumulated. 2025-09-23 14:41:59 -04:00
Thomas Harte 67e1773495 This flag covers rows, not lines. 2025-09-23 14:29:00 -04:00
Thomas Harte a199b64aa0 Clarify naming, attempt better to conform to FPGA precedent. 2025-09-23 14:27:21 -04:00
Thomas Harte ebf09aceb2 Further extend. This is becoming more of a SizedInt. 2025-09-23 14:26:58 -04:00
Thomas Harte ca226e4295 Merge branch 'master' into New6845 2025-09-22 13:28:33 -04:00
Thomas Harte 9261939f62 Switch to working PC for testing. 2025-09-22 13:24:35 -04:00
Thomas Harte 0349931953 Shuffle declare order. 2025-09-22 13:21:48 -04:00
Thomas Harte d612a385d2 Dig in further on types. 2025-09-22 13:20:10 -04:00
Thomas Harte ed4f299d55 Start formalising types. 2025-09-22 13:09:30 -04:00
Thomas Harte 7cef789d41 Merge branch 'master' into New6845 2025-09-22 12:47:32 -04:00
Thomas Harte 66bfb86d42 Introduce SizedCounter as start of CRTC reworking. 2025-09-22 12:46:39 -04:00
Thomas Harte c4a5bc12ef Merge pull request #1572 from TomHarte/BBCADFS
Support ADFS, sideways RAM.
2025-09-20 23:27:15 -04:00
Thomas Harte 557631f6ba Support ADFS, sideways RAM. 2025-09-20 22:33:08 -04:00
Thomas Harte 362ffaff7f Merge pull request #1571 from TomHarte/RandomPauses
Correct uPD7002 interrupt wiring and behaviour.
2025-09-20 22:08:13 -04:00
Thomas Harte fb5ef200fb Correct uPD7002 interrupt wiring. 2025-09-20 21:51:19 -04:00
Thomas Harte 5e78ac3af5 Adjust keyboard map slightly. 2025-09-20 21:35:01 -04:00
Thomas Harte 719a090b34 Retain bit 2. 2025-09-20 20:06:28 -04:00
Thomas Harte 3af85da6e0 Adjust conversion bits in status. 2025-09-20 19:52:47 -04:00
Thomas Harte 8fd62aa525 Disable interrupt at start of conversion. 2025-09-20 19:49:16 -04:00
Thomas Harte 40747f51bd Disable ADC interrupt, experimentally. 2025-09-20 17:41:22 -04:00
Thomas Harte f3cef6bd73 Merge pull request #1570 from TomHarte/BBCCursor
Add BBC cursor.
2025-09-20 08:42:53 -04:00
Thomas Harte eef0ee8180 Support cursor to end of row. 2025-09-20 08:27:58 -04:00
Thomas Harte 503e974375 Restrict cursor to visible area, fix width. 2025-09-20 08:15:02 -04:00
Thomas Harte c959f2fee5 Attempt to show the hardware cursor. 2025-09-20 07:54:37 -04:00
Thomas Harte 7d5e434cba Merge pull request #1569 from TomHarte/BBCActivityIndicators
Add activity indicators.
2025-09-19 23:51:18 -04:00
Thomas Harte 2720bcdf18 Retrench to static inline const. 2025-09-19 23:40:30 -04:00
Thomas Harte c513b7262b Hit up two further strings for constexpr. 2025-09-19 23:37:11 -04:00
Thomas Harte 57a795df96 Add keyboard LEDs. 2025-09-19 23:34:51 -04:00
Thomas Harte 6bdd9e4543 Add drive activity indicators. 2025-09-19 23:26:50 -04:00
Thomas Harte ede3def37f Merge pull request #1568 from TomHarte/BBC1770
Add 1770 DFS support.
2025-09-19 23:20:29 -04:00
Thomas Harte 87d9022280 Collapse operations. 2025-09-19 23:03:11 -04:00
Thomas Harte ff0ba7d48b Reduce logging again. 2025-09-19 22:59:58 -04:00
Thomas Harte b49c47425f Set I flag on NMI and reset. 2025-09-19 22:59:37 -04:00
Thomas Harte 3916ba1a42 This intermittently succeeds. Doubling down on investigation. 2025-09-19 20:33:02 -04:00
Thomas Harte 0b3d22b97c Take a swing and a miss at alternative documentation interpretations. 2025-09-19 19:59:12 -04:00
Thomas Harte 9b8b0f2023 Attempt to introduce a DFS ROM and WD1770. 2025-09-19 10:38:22 -04:00
Thomas Harte 06e0d17be0 Merge pull request #1567 from TomHarte/AllPixelModes
Perform proper pixel generation in all bitmap modes.
2025-09-18 21:54:24 -04:00
Thomas Harte 239c485f3c An underclock will do. 2025-09-18 21:35:08 -04:00
Thomas Harte 5e5fdda0ca Correct audio. 2025-09-18 21:33:25 -04:00
Thomas Harte 4b2dddf3c6 Remove stale TODO. 2025-09-18 21:21:51 -04:00
Thomas Harte c99ec745ca Remove dead logging. 2025-09-18 21:20:27 -04:00
Thomas Harte 1ec2e455ec Support flash, mixed modes. 2025-09-18 21:19:33 -04:00
Thomas Harte 69304737c6 Switch red and blue. 2025-09-18 17:53:58 -04:00
Thomas Harte fe91670127 Pull count outside loop, simplify state machine. 2025-09-18 17:50:46 -04:00
Thomas Harte 7a59f94f3d Install more realistic pixel pipeline. 2025-09-18 17:46:09 -04:00
Thomas Harte 4efe3a333d Merge pull request #1566 from TomHarte/BBCADC
Add the BBC's ADC.
2025-09-18 12:39:34 -04:00
Thomas Harte 421bf28582 Add comments, correct address decoding. 2025-09-18 12:27:13 -04:00
Thomas Harte 4c49ffe3d1 Attmept full ADC implementation. 2025-09-18 12:21:25 -04:00
Thomas Harte 26b1ef247b Add calls to ADB. 2025-09-17 23:11:48 -04:00
Thomas Harte 3aafba707a Use more efficient means for blank lines. 2025-09-17 22:33:59 -04:00
Thomas Harte ae774e88fa Add header for ADC. 2025-09-17 21:42:42 -04:00
Thomas Harte ff56dd53cf Remove dead code. 2025-09-17 21:42:33 -04:00
Thomas Harte 12f063c178 Hack in a stable sync. 2025-09-17 21:35:41 -04:00
Thomas Harte 888148d282 Reduce chatter. 2025-09-17 21:35:34 -04:00
Thomas Harte 7bba0b82ef Correct video address generation. 2025-09-17 21:26:13 -04:00
Thomas Harte 41196a862d Merge pull request #1565 from TomHarte/BBCKeyboard 2025-09-17 18:22:47 -04:00
Thomas Harte a99ed0e557 Add break key. 2025-09-17 17:26:28 -04:00
Thomas Harte 654981fb03 Clean up. 2025-09-17 17:24:08 -04:00
Thomas Harte c473c36d46 Add comma, etc. 2025-09-17 11:48:33 -04:00
Thomas Harte e863e61af8 Remove dead code. 2025-09-17 11:46:53 -04:00
Thomas Harte efb486dd1b Fill in much more of key map. 2025-09-17 11:46:29 -04:00
Thomas Harte 25b15fcdd1 Switch to map-based mapping. 2025-09-17 11:34:55 -04:00
Thomas Harte 1106fbb5ef Implement circular scan. 2025-09-17 10:44:53 -04:00
Thomas Harte 9e1a29f0ff Merge branch 'master' into BBCKeyboard 2025-09-16 23:16:03 -04:00
Thomas Harte b3c057f911 Increase logging, play about more. 2025-09-16 23:14:05 -04:00
Thomas Harte 1c33e9ead9 Attempt row scanning. 2025-09-16 23:03:25 -04:00
Thomas Harte d78f35b940 Take a swing at scanning versus not. 2025-09-16 22:29:00 -04:00
Thomas Harte 506236a5ed Merge pull request #1564 from TomHarte/BBCPixels
Hard-code Mode 0 but hence get some pixels.
2025-09-16 21:52:16 -04:00
Thomas Harte 18b32dbba3 Attempt keyboard input. 2025-09-16 21:51:25 -04:00
Thomas Harte 26e40564dc Establish keyboard state. 2025-09-16 21:11:27 -04:00
Thomas Harte b6e8421a0a Hard-code Mode 0 but hence get some pixels. 2025-09-16 20:57:21 -04:00
Thomas Harte 03c5a3b325 Merge pull request #1563 from TomHarte/BBCACIA
BBC: Add ACIA.
2025-09-16 20:38:43 -04:00
Thomas Harte a1f33d3fc6 Redisable test code. 2025-09-16 17:54:42 -04:00
Thomas Harte b8fca7db80 Merge branch 'master' into BBCACIA 2025-09-16 17:54:17 -04:00
Thomas Harte 6ea70cd245 Merge pull request #1562 from TomHarte/BBCCRTC
Add some degree of a CRTC.
2025-09-16 17:53:49 -04:00
Thomas Harte 683fea675e Add ACIA.
Probably with incorrect clock, and connected to nothing.
2025-09-16 17:50:54 -04:00
Thomas Harte 811a010a60 Fix: keys are now unpressed.
Some sort of text is now 'output' (though not yet displayed by the emulator) and then an endless loop on the ACIA begins.

So the next PR will need to add that.
2025-09-16 17:25:13 -04:00
Thomas Harte 019526332d Declare no tube, optimistically watch for characters. 2025-09-16 16:25:41 -04:00
Thomas Harte 1e90387198 Add extra curly brackets. 2025-09-16 16:25:29 -04:00
Thomas Harte 84d6bb47ea Log more. 2025-09-16 15:32:42 -04:00
Thomas Harte 512179d92a Handle clock-rate change correctly in onward signalling. 2025-09-16 13:04:03 -04:00
Thomas Harte 04344a3723 The OS isn't writeable. 2025-09-16 12:47:55 -04:00
Thomas Harte d032207473 Made some attempt at 1Mhz CRTC clocking. 2025-09-16 12:46:44 -04:00
Thomas Harte b33dc2779d Correct RAM visibility. 2025-09-16 12:24:48 -04:00
Thomas Harte 28699a1af5 Correct clock selection bit. 2025-09-16 09:15:08 -04:00
Thomas Harte ff3fe135a3 Convince myself that this isn't a case of present but invisible content. 2025-09-16 07:36:59 -04:00
Thomas Harte ff69709926 Disable interlace support. 2025-09-15 23:53:36 -04:00
Thomas Harte 2162822cec Eliminate C++23 extension. 2025-09-15 23:42:57 -04:00
Thomas Harte 34330baaa0 Add comment on keyboard. 2025-09-15 23:42:47 -04:00
Thomas Harte 28d8aab7e5 Forward Port A correctly. 2025-09-15 23:36:07 -04:00
Thomas Harte 6a91c89126 Introduce a colour burst. 2025-09-15 23:32:20 -04:00
Thomas Harte c350f6fe5e Fix interrupting sync. 2025-09-15 23:28:38 -04:00
Thomas Harte 493bf0a666 Proceed to a solid blank display. 2025-09-15 23:27:04 -04:00
Thomas Harte 0305203e61 Wire up vertical sync interrupt. 2025-09-15 23:21:06 -04:00
Thomas Harte 71d7b1dfad Add a ticking-but-diconnected CRTC. 2025-09-15 23:16:42 -04:00
Thomas Harte 69748a1f1b Merge pull request #1561 from TomHarte/BBC6522s
BBC Micro: add 6522s.
2025-09-15 22:39:08 -04:00
Thomas Harte 39c2db38b7 Improve logged IO detail. 2025-09-15 22:29:22 -04:00
Thomas Harte f499de3622 Add sideways ROM paging. 2025-09-15 22:26:02 -04:00
Thomas Harte e8a16a8fce Attempt to incorporate SN76489. 2025-09-15 22:17:40 -04:00
Thomas Harte 81f2952c97 Log just enough to see that this looks like an SN76489 write. 2025-09-15 22:03:37 -04:00
Thomas Harte dcf9de1a01 Add IO access to the 6522s. 2025-09-15 21:26:36 -04:00
Thomas Harte 95f57f4eeb Add VIA instances, flesh out 1Mhz space. 2025-09-15 21:23:34 -04:00
Thomas Harte 7a794b8b6e Merge pull request #1559 from TomHarte/BBCMicro
Add BBC Micro skeleton.
2025-09-15 20:00:57 -04:00
Thomas Harte cbaf841f13 Add 1Mhz bus transitions. 2025-09-15 20:00:29 -04:00
Thomas Harte 6fdca9e89c Hide BBC Micro until complete. 2025-09-15 19:50:26 -04:00
Thomas Harte 8c3b3d98f6 Add error output. 2025-09-15 17:53:41 -04:00
Thomas Harte 6dd2abfb61 Fix BBC Micro path. 2025-09-15 17:53:32 -04:00
Thomas Harte 32defde397 Exit after the first filename that works. 2025-09-15 17:53:12 -04:00
Thomas Harte b1969c9528 Add BBC Micro. 2025-09-15 17:52:57 -04:00
Thomas Harte b6d5af81b5 Attempt to flesh out the 6502 space. 2025-09-15 17:44:07 -04:00
Thomas Harte 0358853d5a Permit 6502 to spin forever, doing nothing. 2025-09-15 17:14:17 -04:00
Thomas Harte eb60f63223 Remove redundant state. 2025-09-15 17:12:13 -04:00
Thomas Harte fdbb06436d Merge branch 'master' into BBCMicro 2025-09-15 14:54:46 -04:00
Thomas Harte bceaa073cd Merge pull request #1560 from TomHarte/OpenMultiple
macOS: Allow the same software to be open multiple times.
2025-09-15 14:54:25 -04:00
Thomas Harte ccace48d5a Declare clock rate, at least. 2025-09-15 14:54:10 -04:00
Thomas Harte 193203bbf7 Wire up enough Mac GUI to get to an empty husk. 2025-09-15 14:53:19 -04:00
Thomas Harte d0cc4e1557 Allow multiple copies of a program to be open. 2025-09-15 14:39:05 -04:00
Thomas Harte d52ae8c662 Eliminate macro. 2025-09-14 22:13:51 -04:00
Thomas Harte 6afd40cb39 Add to further project files. 2025-09-14 21:59:00 -04:00
Thomas Harte 6713baf86b Add BBC Micro class 2025-09-14 21:57:09 -04:00
Thomas Harte 365145e7c0 Allow updated version string. 2025-09-13 22:31:51 -04:00
Thomas Harte 0413af79e9 Merge pull request #1557 from TomHarte/BBCROMs
Add BBC MOS ROM to catalogue and move BASIC to a machine-neutral folder.
2025-09-13 21:38:20 -04:00
Thomas Harte 868c498e28 Separate BBC BASIC ROM from the Electron. 2025-09-12 23:25:21 -04:00
Thomas Harte c5fbbe8a69 Add BBC MOS; alphabetise. 2025-09-12 22:24:37 -04:00
Thomas Harte b698850ce8 Merge pull request #1556 from TomHarte/StaticConstexpr
Further prefer `static constexpr`.
2025-09-12 22:16:01 -04:00
Thomas Harte 0d1fe03369 Further prefer static constexpr. 2025-09-12 21:40:08 -04:00
Thomas Harte 98b900e886 Merge pull request #1555 from TomHarte/NoLoggingStorage
Definitively eliminate per-logger state.
2025-09-12 12:03:38 -04:00
Thomas Harte 8210da9e34 Modify xcodebuild overtly to download Metal toolchain. 2025-09-12 11:42:25 -04:00
Thomas Harte fac3d99f64 Switch to no-instance logging. 2025-09-12 07:17:23 -04:00
Thomas Harte d3c216374f Add #include for std::fill. 2025-09-12 07:17:17 -04:00
Thomas Harte 105272630e Definitively eliminate per-logger state. 2025-09-11 23:29:47 -04:00
Thomas Harte 53cc8ecaf0 Merge pull request #1554 from TomHarte/Keyboard
Tweak logging to emphasise only floppy & keyboard errors.
2025-09-11 23:06:20 -04:00
Thomas Harte fb8e8b4b3a Restore old logic, to reinstate working XT. 2025-09-11 22:47:26 -04:00
Thomas Harte 035713b4d3 Remove logging. 2025-09-10 23:00:42 -04:00
Thomas Harte 54b7dc56b5 Resolve risk of acknowledged interrupt refiring. 2025-09-10 22:59:57 -04:00
Thomas Harte 7fd39f44d0 Add some logging, take a stab at returning requests. 2025-09-10 21:46:58 -04:00
Thomas Harte 691292501a Promote constexprs to static. 2025-09-10 21:46:44 -04:00
Thomas Harte a58158ae08 Add PIT and PIC. 2025-09-10 21:45:51 -04:00
Thomas Harte ef09b971fa Watch software interrupt flags.
Now tracking: issue seems to be reaching TEST4.ASM:D11 with an interrupt that it believes to be software-originating.
2025-09-10 15:47:19 -04:00
Thomas Harte e07dee380d Experiment with further delays. 2025-09-10 14:18:30 -04:00
Thomas Harte 125bc5baa6 Install communication delay. 2025-09-10 13:48:51 -04:00
Thomas Harte 995444b11b Add TODO on what seems to be the current issue. 2025-09-10 11:33:38 -04:00
Thomas Harte 8f2384dbfc Fix log entry interleaving. 2025-09-10 09:52:55 -04:00
Thomas Harte 0cdd1c23ce Guess at another ID.
Cf. https://stanislavs.org/helppc/keyboard_commands.html
2025-09-09 23:40:55 -04:00
Thomas Harte 4765a39759 New guess: writing to the keyboard implicitly enables communications. 2025-09-09 23:38:21 -04:00
Thomas Harte 7f4047772c Continue naming things. 2025-09-09 15:36:02 -04:00
Thomas Harte 45c4ca6bec Attempt further to elide storage. 2025-09-09 13:58:37 -04:00
Thomas Harte 4a573a5aae Clarify one magic constant. 2025-09-09 13:44:31 -04:00
Thomas Harte 5125ff6a8c Combine enables, silence port 61 for now. 2025-09-09 11:16:42 -04:00
Thomas Harte 482d3301ce Avoid faulty sector access. 2025-09-08 23:14:50 -04:00
Thomas Harte cdeec8ac47 Take various more failed swings at which bits do what. 2025-09-08 22:54:10 -04:00
Thomas Harte 3cef12b53b Reintroduce proper ordering of log comments. 2025-09-08 22:27:40 -04:00
Thomas Harte dd098a16a8 Log more. 2025-09-08 21:54:56 -04:00
Thomas Harte 61a175e84a Merge branch 'master' into Keyboard 2025-09-08 21:43:25 -04:00
Thomas Harte a5bcd38fe8 Slightly reformat. 2025-09-08 21:43:18 -04:00
Thomas Harte cad42beef4 Roll in some random style improvements. 2025-09-08 20:38:50 -04:00
Thomas Harte 5a57958639 Reduce log repetition. 2025-09-08 17:22:53 -04:00
Thomas Harte 260336c1e5 Adopt phase as communicative of whether more bytes are expected. 2025-09-08 17:13:27 -04:00
Thomas Harte 889cb9c78f Attempt a dual-queue solution to enabling/disabling keyboard. 2025-09-08 14:40:08 -04:00
Thomas Harte b90e8f5af3 Further tweak reporting. 2025-09-06 23:16:10 -04:00
Thomas Harte 12361d2854 Adopt proper error/info distinction. 2025-09-06 23:13:33 -04:00
Thomas Harte d307ddfa8e Merge branch 'master' into Keyboard 2025-09-05 23:21:41 -04:00
Thomas Harte 96fd0b7892 Merge pull request #1553 from TomHarte/IndentationSomeMore
Further reduce indentation.
2025-09-05 23:20:19 -04:00
Thomas Harte 6f1db15d7c Further reduce indentation. 2025-09-05 23:07:45 -04:00
Thomas Harte 1854296ee8 Merge pull request #1552 from TomHarte/ElectronIDE
Reduce code duplication within the ROM catalogue.
2025-09-05 22:45:55 -04:00
Thomas Harte 515cc5f326 Correct spelling. 2025-09-05 22:09:38 -04:00
Thomas Harte 091be7eafe Remove unused header. 2025-09-05 22:03:45 -04:00
Thomas Harte 27a19ea417 Eliminate line-length violations. 2025-09-05 22:03:19 -04:00
Thomas Harte 9a5e9af67c Standardise layout. 2025-09-05 22:00:42 -04:00
Thomas Harte 3a493f2428 Merge pull request #1551 from TomHarte/LogLevels
Allow logging of errors but not info.
2025-09-05 21:04:56 -04:00
Thomas Harte ca6e34f4b4 Fix dangling OpenGL accesses. 2025-09-05 19:30:33 -04:00
Thomas Harte e1e68312c4 Transcribe remaining catalogue entries. 2025-09-05 17:23:38 -04:00
Thomas Harte c7ff2cece4 Head in search of a more-compact form. 2025-09-05 16:55:00 -04:00
Thomas Harte 8e6f4fa36f Fix NDEBUG route. 2025-09-05 14:34:08 -04:00
Thomas Harte 4d302da9fa Allow logging of errors but not info. 2025-09-05 14:25:56 -04:00
Thomas Harte e0917dc734 Switch focus back to keyboard. 2025-09-04 22:21:05 -04:00
Thomas Harte 26f82e8143 Merge pull request #1550 from TomHarte/SpaceshipOperator
Adopt spaceship.
2025-09-04 22:08:35 -04:00
Thomas Harte 6518f08bc7 Adopt spaceship. 2025-09-04 21:25:12 -04:00
Thomas Harte 8599123b30 Merge pull request #1549 from TomHarte/MoreFDC
Implement further FDC commands.
2025-09-04 19:25:32 -04:00
Thomas Harte f934a1aa10 Ensure std::hash is known. 2025-09-04 17:53:18 -04:00
Thomas Harte 81be5f809f Fix logged statement. 2025-09-04 17:48:50 -04:00
Thomas Harte 8bb94804d4 Attempt to bluster to something for read ID. 2025-09-04 17:45:41 -04:00
Thomas Harte c3f64e85ce Support unordered maps; use spaceship operator. 2025-09-04 17:39:09 -04:00
Thomas Harte 53057aff5d Reduce type redundancy. 2025-09-04 17:29:34 -04:00
Thomas Harte 8cad5ac7e9 Reduce repetitive array references. 2025-09-04 17:00:10 -04:00
Thomas Harte b4f643f3dd Merge pull request #1541 from TomHarte/Spelling
Adopt my native spelling: 'licence' is a noun.
2025-09-04 16:01:11 -04:00
Thomas Harte 2d659289e8 Merge pull request #1546 from TomHarte/SomeIDE
Add some IDE/ATA structure.
2025-09-04 15:58:26 -04:00
Thomas Harte 9883640b63 Better record scope of incomplete work. 2025-09-04 15:22:04 -04:00
Thomas Harte bc3a0c3c91 Fix stqtic declaration. 2025-09-04 15:17:24 -04:00
Thomas Harte 4e822347a5 Finally crack case of controller failure. 2025-09-04 15:16:48 -04:00
Thomas Harte d5650da8c0 Avoid strict aliasing violation. 2025-09-04 14:23:29 -04:00
Thomas Harte d3c77523c3 Report normal terminations, usually. 2025-09-04 11:41:29 -04:00
Thomas Harte 787a5ce568 Reduce speed multiplier. 2025-09-03 22:22:12 -04:00
Thomas Harte 91dfd405d1 Adjust logging. 2025-09-03 13:44:30 -04:00
Thomas Harte 08c615c493 Improve ready flag. 2025-09-03 13:31:06 -04:00
Thomas Harte 5483102276 Add missing #includes. 2025-09-02 21:29:49 -04:00
Thomas Harte 32686d898b Fix again for concurrent seeks. 2025-09-02 21:18:35 -04:00
Thomas Harte 4b50c8d96c Toy with sense interrupt status being directly seek-linked. 2025-09-02 17:50:59 -04:00
Thomas Harte c1780ee26b Add some form of sense drive status. 2025-09-02 15:21:11 -04:00
Thomas Harte 46b2db00bc Establish that the AT actually wants SenseDriveStatus. 2025-09-02 12:52:04 -04:00
Thomas Harte 7042e457ab Fix OUT sizes. 2025-09-02 12:32:27 -04:00
Thomas Harte faeec1701f Add logging, reduce template specialisation. 2025-09-02 12:31:16 -04:00
Thomas Harte 2a75a1f9f2 Add IDE logging. 2025-09-02 12:21:01 -04:00
Thomas Harte 8c65dccc53 Support 16-bit IO access. 2025-09-01 23:31:31 -04:00
Thomas Harte de7c3ba92f Mostly kick IDE accesses down the road. 2025-09-01 00:02:16 -04:00
Thomas Harte ac204aadd2 Clean up, better constify SCSI innards. 2025-08-31 10:56:01 -04:00
Thomas Harte 46fc0d677f Merge branch 'master' into SomeIDE 2025-08-30 23:52:58 -04:00
Thomas Harte 2d4f4b0036 Merge pull request #1545 from TomHarte/SomeConsts
Improve `const` arguments.
2025-08-30 23:36:49 -04:00
Thomas Harte c1de00bf9d Improve const arguments. 2025-08-30 23:25:19 -04:00
Thomas Harte 4639e1c47c Merge pull request #1544 from TomHarte/Macros
Further reduce historical dependence on macros.
2025-08-30 14:19:17 -04:00
Thomas Harte 5d2c156bc9 Use popcount. 2025-08-30 10:44:00 -04:00
Thomas Harte 357f98f015 Remove macros, shorten line lengths. 2025-08-30 10:41:28 -04:00
Thomas Harte 62f23ac27c Commute macros. 2025-08-30 10:34:49 -04:00
Thomas Harte 0936646ef9 Eliminate macros. 2025-08-30 10:29:53 -04:00
Thomas Harte 856c12f46f Eliminate macro. 2025-08-30 10:24:27 -04:00
Thomas Harte 7489783837 Eliminate macros. 2025-08-30 10:22:21 -04:00
Thomas Harte a4f0a4c310 Merge pull request #1543 from TomHarte/MinorTidies
Perform further tidying steps.
2025-08-30 10:00:41 -04:00
Thomas Harte bb1ef114f1 Fix header declaration. 2025-08-30 09:40:06 -04:00
Thomas Harte f1610b6407 Fix Qt speaker delegation. 2025-08-30 00:13:47 -04:00
Thomas Harte 5e48a4c724 Fix SDL speaker sample receipt. 2025-08-30 00:10:17 -04:00
Thomas Harte d825c03372 Prefer references for delegate protocols. 2025-08-30 00:09:38 -04:00
Thomas Harte d177549dd6 Reduce more indentation. 2025-08-29 23:56:35 -04:00
Thomas Harte 9d1543401f Merge pull request #1542 from TomHarte/VHD
Sketch out VHD support.
2025-08-29 23:47:01 -04:00
Thomas Harte 094eb7e252 Resolve Github build breakage. 2025-08-29 23:09:13 -04:00
Thomas Harte 95e6726468 Reduce indentation. 2025-08-29 23:02:34 -04:00
Thomas Harte 8fded8f210 Add consts, remove get_s. 2025-08-29 22:55:50 -04:00
Thomas Harte 19c4940abd Extend and constify MassStorageDevice. 2025-08-29 22:17:01 -04:00
Thomas Harte 7b1f6b3c53 Add negative asserts. 2025-08-29 22:02:54 -04:00
Thomas Harte 43042c3737 Parse a little further. 2025-08-29 21:55:21 -04:00
Thomas Harte 30b50b8a1b Add missing header, CMake file. 2025-08-28 21:56:03 -04:00
Thomas Harte 095be3072b Distinguish when to include the trailing NULL. 2025-08-28 21:53:52 -04:00
Thomas Harte 91831200d6 Eliminate runtime strlen. 2025-08-28 18:45:04 -04:00
Thomas Harte 8295d4511b Improve whence type safety. 2025-08-28 17:41:58 -04:00
Thomas Harte df589d9588 Test image type. 2025-08-28 17:29:26 -04:00
Thomas Harte b826e1c661 Do some small measure of header parsing. 2025-08-28 17:20:29 -04:00
Thomas Harte 6727e2fe73 Add shell of a class for VHD files. 2025-08-27 17:09:52 -04:00
Thomas Harte ecdcee8d4e Adopt my native spelling: 'licence' is a noun. 2025-08-26 23:48:00 -04:00
Thomas Harte 8b4a4369c1 Add a target for IDE activity. 2025-08-26 23:47:39 -04:00
Thomas Harte eeb06de916 Merge pull request #1540 from TomHarte/KeyboardAgain
Be more rigorous about `static constexpr`
2025-08-26 23:22:19 -04:00
Thomas Harte 5018d7d577 Be more rigorous about static constexpr. 2025-08-26 22:54:39 -04:00
Thomas Harte 1ca279d99d Add keyboard command lookaside; dummy IDE read. 2025-08-25 22:49:32 -04:00
Thomas Harte 8a149a188c Store keyboard command until enabled. 2025-08-25 22:44:22 -04:00
Thomas Harte 076334bc4e Take first stab at separating keyboard & controller. 2025-08-25 22:14:51 -04:00
Thomas Harte e6b45c978c Merge pull request #1539 from TomHarte/KeyboardAgain
Name keyoard commands, implement a couple more.
2025-08-25 22:12:00 -04:00
Thomas Harte a07615445f Uncover likely issue: commands directly to keyboard. 2025-08-23 22:51:09 -04:00
Thomas Harte 41d30c2835 More formally designate the resets. 2025-08-23 22:41:58 -04:00
Thomas Harte 71f1635e23 Name commands, implement a couple more. 2025-08-23 22:33:19 -04:00
Thomas Harte 57df6d9bf7 Merge pull request #1538 from TomHarte/LessMemory
Reduce AT to 640kb RAM.
2025-08-23 22:02:28 -04:00
Thomas Harte fd670d5175 Reduce AT to 640kb RAM.
This substantially speeds up the boot process, clearing the way for other experimentation.
2025-08-23 21:51:48 -04:00
Thomas Harte 39d4c315c8 Merge pull request #1537 from TomHarte/FloppyExists
Increase floppy logging, adjust sense interrupt.
2025-08-23 21:33:22 -04:00
Thomas Harte 6487086354 Increase floppy logging, adjust sense interrupt. 2025-08-23 21:21:16 -04:00
Thomas Harte 7d6e24b8ed Merge pull request #1536 from TomHarte/8042Redo
Rejig 8042; extend logger for common conditional use case.
2025-08-21 23:23:59 -04:00
Thomas Harte 4922073300 Reestablish pre-AT keyboard link. 2025-08-21 16:48:05 -04:00
Thomas Harte 778a02324e Add enabled flag. 2025-08-20 22:56:04 -04:00
Thomas Harte 8e89aa97a0 Switch status bit 3; fix reading of is_tested_; guess at it self-setting. 2025-08-20 22:09:08 -04:00
Thomas Harte dfd521e938 Attempt to reformulate keyboard controller. 2025-08-20 17:17:54 -04:00
Thomas Harte d47332adf5 Reduce need for scopes. 2025-08-20 17:17:27 -04:00
Thomas Harte 14e7ba8fab Merge pull request #1535 from TomHarte/MDAStatus
Add MDA status register.
2025-08-19 16:39:03 -04:00
Thomas Harte e68a356fd0 Adjust AT switches input. 2025-08-18 23:26:58 -04:00
Thomas Harte 6e77b8659c Add various missing enum -> string mappings. 2025-08-18 14:59:58 -04:00
Thomas Harte 00fad7e424 Merge pull request #1534 from TomHarte/PCBASIC
Add PC BASIC ROM to the PC AT.
2025-08-14 22:28:48 -04:00
Thomas Harte 0a65248bf7 Add MDA status register, various notes. 2025-08-14 22:28:22 -04:00
Thomas Harte 9cff26b163 Install BASIC ROM. 2025-08-13 23:36:07 -04:00
Thomas Harte 9309d8c3f2 Add ROM BIOS to the ROM catalogue. 2025-08-13 22:18:16 -04:00
Thomas Harte 07e96c10d2 Merge pull request #1533 from TomHarte/MultifacetedCall
Further implement the 80286
2025-08-13 19:05:52 -04:00
Thomas Harte d95abc99d9 Slightly increase logging. 2025-08-13 16:44:53 -04:00
Thomas Harte b83c2615de Limit LSL types. 2025-08-13 13:53:02 -04:00
Thomas Harte 78a2b27e65 Attempt LSL, LAR. 2025-08-13 13:43:19 -04:00
Thomas Harte bae594e34c Fix ARPL flag. 2025-08-12 21:32:20 -04:00
Thomas Harte 4ded6fceea Fix VERR/VERW. 2025-08-12 21:20:01 -04:00
Thomas Harte 0e498829f7 Attempt VERR, VERW.
Without complete success; IBM's third invocation (which I think is a VERR) doesn't give the result that BIOS is looking for.
2025-08-12 17:22:14 -04:00
Thomas Harte ddd090d581 Implement STR. 2025-08-12 16:21:23 -04:00
Thomas Harte 4cd979e5fb Take a shot at LTR. 2025-08-12 13:49:59 -04:00
Thomas Harte 2f7a6bb242 Establish a specialised validate_call. 2025-08-11 17:10:58 -04:00
Thomas Harte 3b3b2e61b0 Overtly separate call authorisation. 2025-08-11 13:24:51 -04:00
Thomas Harte ab4fde9bd7 Slightly clean up. 2025-08-11 09:41:31 -04:00
Thomas Harte a9996f0b81 Add consts. 2025-08-11 09:34:17 -04:00
Thomas Harte 246d34e072 Merge pull request #1532 from TomHarte/SimplerDescriptors
Boil down descriptor attributes.
2025-08-10 20:49:44 -04:00
Thomas Harte d35efe3f32 Boil down descriptor attributes. 2025-08-09 23:10:26 -04:00
Thomas Harte ebd1a6b47c Merge pull request #1531 from TomHarte/286DecodingTests
Resolve majority of 286 test case failures.
2025-08-08 15:04:58 -04:00
Thomas Harte 83980678a0 Add additional known bad.
38 failures left.
2025-08-08 12:06:48 -04:00
Thomas Harte 201393f87d Start discounting test cases that look broken.
39 failures remaining.
2025-08-08 12:00:58 -04:00
Thomas Harte 055eb3873e Switch far jump to piece-by-piece authorisation.
43 failures.
2025-08-08 11:30:51 -04:00
Thomas Harte dc94d58148 Switch CALL to access-by-access validation.
47 failures remaining.
2025-08-08 11:28:32 -04:00
Thomas Harte 0adaec1665 Allow ENTER to write partially.
51 failures left.
2025-08-08 11:18:14 -04:00
Thomas Harte 4ee30dc36f Correct stack validation, fixing POPA.
71 failures.
2025-08-08 11:05:01 -04:00
Thomas Harte 54ff2fa01f Fix new LES/etc failures.
Remaining: 72.
2025-08-08 09:55:31 -04:00
Thomas Harte 03c70b49ba Throw GPF for overlong instructions; fix BOUND validation.
79 failures outstanding.
2025-08-08 09:43:16 -04:00
Thomas Harte 4b2d8e13d1 Add consts, TODO. 2025-08-08 07:39:34 -04:00
Thomas Harte a0c50f0521 Support 286-style DAS.
321 failures to go.
2025-08-07 19:49:41 -04:00
Thomas Harte b15a865c88 Add extra MOV sanity check.
Failures still standing: 406.
2025-08-07 17:40:13 -04:00
Thomas Harte 8e5bbbbc71 Implement 80286 INS/OUTS oddities.
795 failures outstanding.
2025-08-07 15:31:07 -04:00
Thomas Harte 615ebaf711 Correct RCL overflow when shift count is 0.
1,013 failures remaining.
2025-08-07 15:23:11 -04:00
Thomas Harte 0882d2b7ce Correct LEAVE authorisation.
Failures: 1,207.
2025-08-07 15:16:12 -04:00
Thomas Harte 900195efac Correct HLT IP comparison.
Failures: 1,425.
2025-08-07 15:01:22 -04:00
Thomas Harte b58b962ccf Apply 80286 LODS craziness.
2,425 errors remaining.
2025-08-07 14:53:16 -04:00
Thomas Harte 5255499445 Implement 286 weirdness for SCAS.
2,690 failures.
2025-08-07 14:50:59 -04:00
Thomas Harte d9a2be4250 Avoid upfront testing for POPA.
Failures: 2,966.
2025-08-07 14:41:01 -04:00
Thomas Harte 256e14a8a6 Decline upfront validation for PUSHA.
Total failures remaining: 3,239.
2025-08-07 14:36:11 -04:00
Thomas Harte 1ab26d4a2f Determine 80286 CMPS rules.
Remaining: 3,521 failures.
2025-08-07 12:28:50 -04:00
Thomas Harte 91b2c751af Determine 80286 logic for MOVS.
4,043 failures left.
2025-08-07 12:17:52 -04:00
Thomas Harte edf7617d1e Fix is_address.
Failures: 4,568.
2025-08-07 09:25:23 -04:00
Thomas Harte 32666d304f Filter out some illegal JMP/CALL fars.
Failure count now: 5,966.
2025-08-07 09:18:23 -04:00
Thomas Harte b65f7b4a6a Restrict BOUNDS checks to 80286. 2025-08-06 22:32:50 -04:00
Thomas Harte 7c4df23c1c Fix BOUND.
7085 remaining failures.
2025-08-06 22:19:35 -04:00
Thomas Harte a8e60163e1 Commute Overflow from fault to trap.
9,331 failures remaining.
2025-08-06 21:30:08 -04:00
Thomas Harte 02ec1b5da6 Fix SHR overflow flag.
Failing: 11,802.
2025-08-06 21:14:15 -04:00
Thomas Harte a9a6aba862 Correct RCR overflow.
Now: 14,097 failures.
2025-08-06 17:46:05 -04:00
Thomas Harte 03c6a60f68 Avoid extra judgment on LEAVE.
Failures remaining: 16,295.
2025-08-06 17:19:20 -04:00
Thomas Harte 8ab688687e Decode .6 as SAL.
Amazingly: now 20,814 failures outstanding.
2025-08-06 16:30:55 -04:00
Thomas Harte bdec32722e Include failures/file. 2025-08-06 16:07:54 -04:00
Thomas Harte ad50e5c754 Ensure an invalid instruction is generated upon length limit. 2025-08-06 15:59:24 -04:00
Thomas Harte 9c48e44e9e Fix fast-path selection.
50,814 failures.
2025-08-06 15:33:51 -04:00
Thomas Harte 76284eb462 Fix 8088 assumption about unused flags; 80286 PUSHF now passes amongst others.
51,091 failures still to fix though.
2025-08-06 15:31:03 -04:00
Thomas Harte 0745c5128a Avoid expensive path for 8088; pull out allow list. 2025-08-06 15:21:54 -04:00
Thomas Harte 01fbe2d3de Support 808286 STOS oddities. 2025-08-06 13:37:34 -04:00
Thomas Harte 9e14c22259 Take another run at ENTER. 2025-08-06 12:55:37 -04:00
Thomas Harte dff0111cd5 Overtly capture decoding failures. 2025-08-05 13:03:54 -04:00
Thomas Harte e7452b0ea1 Continue accepting F7.2 as TEST. 2025-08-04 21:45:14 -04:00
Thomas Harte 61a0f892c4 Fix PUSH immediate. 2025-08-04 21:23:27 -04:00
Thomas Harte 4ceab01ed4 Fix result of IMUL_3. 2025-08-04 21:05:14 -04:00
Thomas Harte 9908969eea Diagnose, correct AAA and AAS. 2025-08-04 17:49:07 -04:00
Thomas Harte 19a78ef1ac Correct for 286+ PUSH SP. 2025-08-04 17:23:02 -04:00
Thomas Harte 4785c1ae84 Grab new punchlist. 2025-08-04 17:19:11 -04:00
Thomas Harte ef03841efa Deal with potential reason for wrong top-of-flags. 2025-08-04 17:14:16 -04:00
Thomas Harte 4747a70ce7 Correct for accesses right at segment end. 2025-08-04 17:08:01 -04:00
Thomas Harte cd986cc2dc Ensure tests get the default IDT. 2025-08-04 12:47:52 -04:00
Thomas Harte c29d5ca4a8 Catch address wraparound out-of-bounds access. 2025-08-04 09:32:35 -04:00
Thomas Harte 56b49011d6 Shorten reports. 2025-08-04 09:21:49 -04:00
Thomas Harte 48c55211e6 Fix descriptor bounds test. 2025-08-04 09:16:33 -04:00
Thomas Harte 72f68f3b0b Include hash in error record. 2025-08-03 20:11:35 -04:00
Thomas Harte 7b6dddc994 Include number. 2025-08-03 17:57:26 -04:00
Thomas Harte 51fbe4e8c5 Consume 286 HLT. 2025-08-03 17:41:02 -04:00
Thomas Harte c148d9ee6c Ensure ENTER can execute. 2025-08-03 17:30:02 -04:00
Thomas Harte 9dfe59a104 Take a swing at three-operand IMUL. 2025-08-02 22:23:34 -04:00
Thomas Harte b6aae65afd Clean up, separate. 2025-08-02 21:45:01 -04:00
Thomas Harte 9fed93a771 Use 286 test suite for decoding tests too. 2025-08-02 21:31:04 -04:00
Thomas Harte cdfb68f261 Merge pull request #1530 from TomHarte/FullerTests
Utilise 80286 real-mode tests.
2025-08-02 19:00:47 -04:00
Thomas Harte 46450bd080 Use proper perform in tests. 2025-08-02 18:47:06 -04:00
Thomas Harte 9a25d601f1 Fully transfer faulting logic. 2025-08-02 18:37:56 -04:00
Thomas Harte fe0834ecda Fix type difference. 2025-08-02 18:32:23 -04:00
Thomas Harte 846f745e2c Attempt to transfer ownership of fault. 2025-08-02 18:26:00 -04:00
Thomas Harte 30d40e6f9b Add TODO. 2025-08-01 20:58:12 -04:00
Thomas Harte f7501b10f7 Move ExecutionSupport to heap. 2025-08-01 20:11:26 -04:00
Thomas Harte 379c513f8a Add const getter, mode getter; further template. 2025-08-01 19:56:57 -04:00
Thomas Harte 5a6d77e958 Generalise, towards using 80286 tests. 2025-08-01 15:59:40 -04:00
Thomas Harte 6646039ffe Templatise to allow beyond-8086 execution. 2025-07-31 21:26:29 -04:00
Thomas Harte 5e0994270f Merge pull request #1529 from TomHarte/StatusFlagsMask
Correct metadata observation.
2025-07-31 19:52:16 -04:00
Thomas Harte 44fc801720 Correct metadata observation. 2025-07-31 15:47:07 -04:00
Thomas Harte 405c61f53d Merge pull request #1528 from TomHarte/ZX81Typer
ZX80/81: Reduce typing speed.
2025-07-31 15:16:43 -04:00
Thomas Harte c40acb9406 Reduce typing speed. 2025-07-29 21:55:56 -04:00
Thomas Harte 7778d2a47e Merge pull request #1527 from TomHarte/8088TestFailures
Restore proper register and memory contents.
2025-07-29 21:36:12 -04:00
Thomas Harte 96afb245a5 Fix test suite memory state. 2025-07-29 21:25:15 -04:00
Thomas Harte cf0677c30b Avoid spurious register comparison failures. 2025-07-28 17:44:32 -04:00
Thomas Harte 667614d9de Merge pull request #1525 from TomHarte/x86Tests
Revive x86 JSON tests
2025-07-27 22:23:27 -04:00
Thomas Harte 652ede57b3 Further clone FlowController. 2025-07-27 22:00:54 -04:00
Thomas Harte 09a34f880e Start trying to return preauthorisation testability. 2025-07-27 21:17:54 -04:00
Thomas Harte a9f9be330d Allow SegmentedMemory to take different LinearMemorys. 2025-07-26 08:18:16 -04:00
Thomas Harte 39568d2464 Run headfirst into a LinearMemory substitution brick wall. 2025-07-25 21:43:54 -04:00
Thomas Harte 10e07a9966 Add missing concept requirement. 2025-07-25 21:27:51 -04:00
Thomas Harte fe00a69136 Start transitioning to PCCompatible::Segments. 2025-07-25 16:58:07 -04:00
Thomas Harte 9d0c2cd67f Switch to a parasitic use of PCCompatible::LinearMemory. 2025-07-25 16:47:08 -04:00
Thomas Harte b5aab442f8 Template immediate-read Segments; continue fixing 8088Tests. 2025-07-24 22:24:53 -04:00
Thomas Harte 7c010bd1ef Relocate validation logic, such as it is. 2025-07-22 22:42:01 -04:00
Thomas Harte 1bf898405f Generalise 'Registers'. 2025-07-21 21:17:54 -04:00
Thomas Harte c490166b35 Fully apply line length limit. 2025-07-21 17:09:07 -04:00
Thomas Harte e6862364ed Correct syntax to the point of failing only concepts. 2025-07-21 17:06:17 -04:00
Thomas Harte cf20c84edd Merge pull request #1523 from TomHarte/MacintoshIMGStyle
Use <algorithm>, and otherwise reduce.
2025-07-19 21:56:50 -04:00
Thomas Harte 88e776ad5b Use <algorithm>, and otherwise reduce. 2025-07-19 21:37:03 -04:00
Thomas Harte e79a60f5cd Merge pull request #1522 from TomHarte/MacintoshIMGStyle
Make minor style improvements.
2025-07-16 22:47:46 -04:00
Thomas Harte fd4a91ba72 Make minor style improvements. 2025-07-16 22:31:41 -04:00
Thomas Harte 5705ece2a3 Merge pull request #1521 from atsampson/includes
Add some missing <cstdint> includes
2025-07-10 21:20:28 -04:00
Thomas Harte c723f20f39 Merge pull request #1520 from TomHarte/ElectronTiming
Electron: move CPU slots to first half of each 1Mhz window.
2025-07-10 19:06:06 -04:00
Adam Sampson 0f7447d539 Add some missing <cstdint> includes. 2025-07-10 23:58:12 +01:00
Thomas Harte 1a08944854 std::tuple is defined in utility, not tuple. 2025-07-09 09:26:16 -04:00
Thomas Harte 7b0b06f6df Adhere to line length limit. 2025-07-09 13:27:53 +08:00
Thomas Harte d2ad227a24 Relocate CPU activity to start of each 1Mhz slot. 2025-07-09 13:18:54 +08:00
Thomas Harte 71d7982d14 Tweak ownership of lookahead logic. 2025-07-09 12:55:53 +08:00
Thomas Harte a94dcc12ef Reformat, consider CPU slot repositioning. 2025-07-09 10:41:03 +08:00
Thomas Harte b7bfcfa1e3 Add note to future self. 2025-07-07 17:41:11 +08:00
Thomas Harte 416ae0ca04 Separate location tests from loop. 2025-07-07 17:37:31 +08:00
Thomas Harte 8d66cd4874 Merge pull request #1519 from TomHarte/ElectronTiming
Deduplicate Electron palette work.
2025-06-25 16:47:15 +07:00
Thomas Harte a701ba8030 Switch to requires. 2025-06-25 16:28:26 +07:00
Thomas Harte 0160908522 Further deduplicate palette actions. 2025-06-25 16:23:25 +07:00
Thomas Harte cdd0d6d127 Add consts. 2025-06-23 14:40:48 +07:00
Thomas Harte 65ee745d6e Avoid repetition of palette data. 2025-06-23 14:35:31 +07:00
Thomas Harte 4141dfc353 Merge pull request #1518 from TomHarte/ElectronColoursEtc
Correct Electron 1bpp palette G/B confusion.
2025-06-22 05:18:29 -04:00
Thomas Harte fb6cd105c3 Overtly name type. 2025-06-22 16:03:50 +07:00
Thomas Harte 6ff9168146 Correct G/B 1bpp mismapping. 2025-06-22 15:58:16 +07:00
Thomas Harte 7b5e08aab6 Slightly improve palette semantics. 2025-06-22 15:25:20 +07:00
Thomas Harte c7dd4526c1 Merge pull request #1516 from TomHarte/JFDFiles
Add elementary JFD support.
2025-06-01 22:48:00 -04:00
Thomas Harte 066036ccdd Add to CMake. 2025-05-31 21:28:02 -04:00
Thomas Harte 7c164453a5 Correct overrun test. 2025-05-30 22:33:27 -04:00
Thomas Harte 8b31cfeafb Correct offset into track table. 2025-05-30 21:47:16 -04:00
Thomas Harte 2ddaf0afa3 Attempt preliminary track building. 2025-05-30 21:25:07 -04:00
Thomas Harte a8a97b4606 Get as far as printing sector stats. 2025-05-29 22:37:10 -04:00
Thomas Harte a55b63a210 Add skeleton for JFD support. 2025-05-29 09:27:13 -04:00
Thomas Harte 2e4f7cd667 Merge pull request #1511 from TomHarte/PrivilegeLevels
Implement some of the missing 80286 operations.
2025-05-28 22:39:05 -04:00
Thomas Harte bf257a8d9e Adjust ownership for segment load detection. 2025-05-28 22:08:43 -04:00
Thomas Harte c4e66f7a35 Merge branch 'master' into PrivilegeLevels 2025-05-28 21:05:36 -04:00
Thomas Harte efff433aa0 Merge pull request #1515 from TomHarte/BitwiseAF
x86: clear auxiliary carry on AND, OR, XOR and TEST.
2025-05-27 11:37:03 -04:00
Thomas Harte ee60e36a16 x86: clear auxiliary carry on AND, OR, XOR and TEST. 2025-05-27 11:23:17 -04:00
Thomas Harte 841fc3cfaf Accept version-number increase. 2025-05-26 22:49:35 -04:00
Thomas Harte 2a44caea6c Merge pull request #1514 from TomHarte/ElectronChangeObsevrer
Add Electron to `MediaChangeObserver` gang.
2025-05-26 22:45:45 -04:00
Thomas Harte 0f661928ae Add Electron to MediaChangeObserver gang. 2025-05-26 22:35:08 -04:00
Thomas Harte 0961e5cc2e Add TSS deserialiser. 2025-05-20 21:56:58 -04:00
Thomas Harte df621a8205 Add real and protected callbacks. 2025-05-18 23:35:49 -04:00
Thomas Harte bfa416ca99 Fix #include. 2025-05-18 22:30:20 -04:00
Thomas Harte 8041b87317 Introduce segment preauthorisation. 2025-05-18 22:11:23 -04:00
Thomas Harte b3000f6350 Made mode knowable; factor out main part of far jump. 2025-05-17 23:08:07 -04:00
Thomas Harte 947baab269 Add TODO. 2025-05-14 22:12:59 -04:00
Thomas Harte a41ea90ca7 Implement CLTS. 2025-05-14 21:08:26 -04:00
Thomas Harte 8b3f0d8fd6 Implement ARPL. 2025-05-14 21:01:48 -04:00
Thomas Harte bd9740a9a4 Add additional informative static asserts. 2025-05-13 22:54:05 -04:00
Thomas Harte 3f735e44f1 Merge pull request #1510 from TomHarte/LLDT
Begin LDT support.
2025-05-13 22:41:53 -04:00
Thomas Harte 9e5235fd30 Descriptor tables are always at most 64kb. 2025-05-13 14:07:28 -04:00
Thomas Harte 159f3cb780 Add SLDT. 2025-05-12 23:17:34 -04:00
Thomas Harte 61469f8e09 Reindent to avoid many false warnings. 2025-05-12 21:36:49 -04:00
Thomas Harte 71343b5131 Add additional possible exception causes. 2025-05-12 21:34:59 -04:00
Thomas Harte 82caee6d7d Add potential LLDT exceptions. 2025-05-12 17:31:56 -04:00
Thomas Harte 275e75980c Take initial swing at LLDT. 2025-05-12 17:22:11 -04:00
Thomas Harte 2572da872a Improve consts, use concepts, reduce indentation. 2025-05-12 09:13:27 -04:00
Thomas Harte 6934618589 Add note to self. 2025-05-11 22:53:28 -04:00
Thomas Harte 01fd07c372 Merge pull request #1509 from TomHarte/GPFs
Implement per-access GPF checks.
2025-05-11 22:34:09 -04:00
Thomas Harte 02f9cf0318 Add basic partial GPF testing. 2025-05-11 22:24:21 -04:00
Thomas Harte 6bc586025a Attempt per-access part of GPF test. 2025-05-11 22:05:33 -04:00
Thomas Harte 0d34960d60 Properly place ownership of linear authorisation. 2025-05-11 21:36:36 -04:00
Thomas Harte 99b94a31ea Give descriptors knowledge of their indices. 2025-05-11 21:32:00 -04:00
Thomas Harte b0d4bcd26c Route all authorisation messages to a common receiver. 2025-05-11 21:08:02 -04:00
Thomas Harte 248ea52e06 Merge pull request #1508 from TomHarte/ExceptionBackfill
Improve IDT support.
2025-05-11 20:40:32 -04:00
Thomas Harte 9b51fe3db4 Take a shot at IDT dispatch. 2025-05-10 21:34:43 -04:00
Thomas Harte 5f95696815 Remove done TODO. 2025-05-05 22:56:24 -04:00
Thomas Harte 8c9df5556d Generalise support for multiple speeds. 2025-05-05 22:55:23 -04:00
Thomas Harte 32495f47b3 Bifurcate descriptor types. 2025-05-05 17:26:39 -04:00
Thomas Harte 23bc561524 Add note to self. 2025-05-04 22:21:37 -04:00
Thomas Harte fa2cc0f62e Proceed as far as believing I probably need a gate descriptor type. 2025-05-04 22:11:53 -04:00
Thomas Harte a686a167cc Factor out 'read descriptor'. 2025-05-04 21:03:17 -04:00
Thomas Harte 8a2468a4fb Apply IDT reset condition, factor in to real-mode interrupts. 2025-05-04 16:57:14 -04:00
Thomas Harte 4cc21a2c20 Include descriptor table and MSW requirements. 2025-05-03 23:01:31 -04:00
Thomas Harte 5350e41da1 Switch to mildly-more-modern template form. 2025-05-02 13:50:06 -04:00
Thomas Harte e07e6b6954 Merge pull request #1507 from TomHarte/Morex86Exceptions
Reformulate x86 exceptions.
2025-05-02 10:42:11 -04:00
Thomas Harte 0a60e38d82 Abandon Interrupt naming. 2025-05-02 10:23:20 -04:00
Thomas Harte f53b40e127 Focus on an Exception as the interrupt token. 2025-05-01 22:36:58 -04:00
Thomas Harte 4df51a00ed Try to be a bit more rigorous in exception generation syntax. 2025-05-01 17:17:29 -04:00
Thomas Harte 3981c4d101 Merge pull request #1506 from TomHarte/x86Concepts
Apply concepts to x86 interface.
2025-04-30 22:07:50 -04:00
Thomas Harte fc3e8f7cef Add memory subsystem requirements. 2025-04-30 21:30:36 -04:00
Thomas Harte f4d67ec5e6 Eliminate bad #include. 2025-04-29 22:32:29 -04:00
Thomas Harte 59aafa6c1e Add linear memory concept. 2025-04-29 22:28:23 -04:00
Thomas Harte 75da46dac5 Add CPU control concept. 2025-04-29 22:24:06 -04:00
Thomas Harte 1f1d380e26 Fill in flow-controller requirements. 2025-04-29 22:09:59 -04:00
Thomas Harte 35b3e425be Spell out registers requirements. 2025-04-29 22:06:59 -04:00
Thomas Harte b4535c489d Wrangle test for segments interface. 2025-04-29 21:55:17 -04:00
Thomas Harte f6bb502e87 Start bashing out an attempt at is_context. 2025-04-27 23:40:40 -04:00
Thomas Harte 6cf825d3d8 Lock down Intruction type. 2025-04-27 21:43:46 -04:00
Thomas Harte f766841fad Add usage-hint concepts. 2025-04-27 14:51:34 -04:00
Thomas Harte 10e4e7f6c6 Limit integer types. 2025-04-27 14:47:56 -04:00
Thomas Harte 1277e56435 Limit integers that can be the subject of accessors. 2025-04-27 14:44:07 -04:00
Thomas Harte 4089532f81 Merge pull request #1505 from TomHarte/OtherC++20Improvements
Make other scattered C++20 improvements.
2025-04-25 23:54:31 -04:00
Thomas Harte 9790b4d2e9 Throw in some consts. 2025-04-25 23:17:00 -04:00
Thomas Harte ad37c0d2ac Use std::rotr. 2025-04-25 23:10:09 -04:00
Thomas Harte b7a1fd4f8f Autodetect whether shift count could be a register. 2025-04-25 23:01:04 -04:00
Thomas Harte be5362e393 Eliminate builtin. 2025-04-25 23:00:36 -04:00
Thomas Harte 4977c9bc4c Further use rotl/r. 2025-04-25 22:53:11 -04:00
Thomas Harte e13dbc03da Make elementary use of rotl and rotr. 2025-04-25 22:37:43 -04:00
Thomas Harte 16fec0679b Use std::popcount further. 2025-04-25 22:24:00 -04:00
Thomas Harte 55361b8552 Remove unused popcount. 2025-04-25 22:18:39 -04:00
Thomas Harte cc67993621 Temporarily disable, in lieu of splitting memory. 2025-04-25 22:18:25 -04:00
Thomas Harte 49ba4998d6 Use std::popcount for parity. 2025-04-25 22:18:05 -04:00
Thomas Harte 03eb381b3b Adopt std::ranges::copy where it is trivial to do so. 2025-04-25 22:17:07 -04:00
Thomas Harte e5161faa43 Merge pull request #1504 from TomHarte/M_PI
Eliminate all references to M_PI.
2025-04-25 21:43:06 -04:00
Thomas Harte de78fb7a1c Eliminate all references to M_PI. 2025-04-24 21:57:29 -04:00
Thomas Harte a9ceb5e21a Merge pull request #1503 from TomHarte/C++20
Bump to C++20.
2025-04-24 21:52:01 -04:00
Thomas Harte e62b41f615 Avoid implicit capture of 'this' via '='. 2025-04-24 21:27:23 -04:00
Thomas Harte 592e339b70 Resolve syntax error, fix line lengths. 2025-04-24 21:12:17 -04:00
Thomas Harte 84a9138df7 Bump to C++20. 2025-04-24 20:56:15 -04:00
Thomas Harte 6db7c4a8eb Merge pull request #1502 from TomHarte/LocalFilesystemAccess
Make style corrections to the Enterprise and CPC.
2025-04-23 12:38:36 -04:00
Thomas Harte 213bd09a9c Remove test trap. 2025-04-23 11:36:29 -04:00
Thomas Harte 8eb246cdec Improve consts, line lengths. 2025-04-23 11:01:23 -04:00
Thomas Harte caacf8e373 Eliminate macros. 2025-04-23 10:51:49 -04:00
Thomas Harte c53d42a578 Eliminate macro. 2025-04-23 10:38:42 -04:00
Thomas Harte cfc5ef4a3c Eliminate risk of overrun. 2025-04-22 22:50:38 -04:00
Thomas Harte e78c1bbec9 Improve consts, indentation. 2025-04-22 22:42:13 -04:00
Thomas Harte b1582d33c0 Adjust indentation, remove one macro. 2025-04-22 21:45:36 -04:00
Thomas Harte 5abcf28a0e Merge pull request #1498 from TomHarte/Descriptors
Edge further along on x86 descriptors.
2025-04-22 21:23:17 -04:00
Thomas Harte 4cd57856ce Take ownership of 32-bit assumption. 2025-04-22 21:10:20 -04:00
Thomas Harte a826fd5c0e Add return. 2025-04-21 23:14:28 -04:00
Thomas Harte 7de23ec2aa Be specific about types. 2025-04-21 23:03:57 -04:00
Thomas Harte d7d2957319 Avoid fallthrough warning. 2025-04-21 22:57:07 -04:00
Thomas Harte fbd81b9930 Merge branch 'master' into Descriptors 2025-04-21 22:43:30 -04:00
Thomas Harte dacb52403a Merge pull request #1501 from TomHarte/NoVLAs
Eliminate VLAs, resolve some fallthrough warnings, reduce macros.
2025-04-21 15:24:40 -04:00
Thomas Harte e008a02b99 Shuffle further to avoid optics of a fallthrough. 2025-04-21 15:13:10 -04:00
Thomas Harte 9363453720 Reduce macros. 2025-04-21 15:00:49 -04:00
Thomas Harte 9c70615fd1 Trim maximum line length. 2025-04-21 15:00:02 -04:00
Thomas Harte 1c78c65816 Add missing constraint. 2025-04-21 09:19:36 -04:00
Thomas Harte 2a9a68ca53 Annotate further fallthroughs. 2025-04-21 09:15:55 -04:00
Thomas Harte fb16baab99 Add further fallthrough. 2025-04-20 23:39:26 -04:00
Thomas Harte 54f509c210 Enforce size restriction. 2025-04-20 23:27:44 -04:00
Thomas Harte 5be8e5eff3 Avoid improper fallthroughs. 2025-04-20 22:55:03 -04:00
Thomas Harte 29b9f129f6 Improve constness, line lengths, eliminate macros. 2025-04-20 22:33:44 -04:00
Thomas Harte f41629daae Add compiler-calming fallthroughs. 2025-04-20 22:19:11 -04:00
Thomas Harte feea6023f4 Eliminate macro. 2025-04-20 12:37:14 -07:00
Thomas Harte 262d8cd0d9 Enable further warnings. 2025-04-20 12:31:57 -07:00
Thomas Harte fbbec04f8c Update version check. 2025-04-20 12:29:03 -07:00
Thomas Harte 3e4eaee96b Overtly cast. 2025-04-20 12:27:38 -07:00
Thomas Harte 5f99a2240d Shorten lines; apply minor style fixes. 2025-04-20 12:26:37 -07:00
Thomas Harte 5937387e94 Overtly note fallthrough. 2025-04-20 11:55:07 -07:00
Thomas Harte b3099d8e71 Eliminate use of VLAs. 2025-04-12 14:34:57 -04:00
Thomas Harte 7721f74200 Further flesh out descriptors: decode all bits, add printf warnings. 2025-04-10 17:07:45 -04:00
Thomas Harte fa58cc05f3 Attempt to avoid type punning. 2025-04-06 22:48:22 -04:00
Thomas Harte c61a9e47b2 Slightly tweak constness. 2025-04-06 22:40:29 -04:00
Thomas Harte 148ee266ed Extend operator== path. 2025-04-06 22:37:59 -04:00
Thomas Harte 8ccec81cc6 Disable awaiting_eoi_. 2025-04-06 22:24:25 -04:00
Thomas Harte 668901f71d Fix comparison. 2025-04-06 22:24:09 -04:00
Thomas Harte ad6ad144a5 Don't regress PC for external interrupts. 2025-04-05 21:39:37 -04:00
Thomas Harte d5997a30b2 Reset output on latch write in applicable modes. 2025-04-04 12:30:08 -04:00
Thomas Harte ecc7501377 Avoid explicit instantiation, precedence error. 2025-04-03 22:09:49 -04:00
Thomas Harte 45262a1a46 Copy reload value to latch. 2025-04-03 21:59:26 -04:00
Thomas Harte 3c04e08df2 Ensure 16-bit ins and outs always occur as two 8-bit operations.
Advances the AT to system error 108, something about timer 2.
2025-04-03 19:52:40 -04:00
Thomas Harte 7c7675179e Restrict shift operand size, causing text output at last. 2025-04-03 17:42:15 -04:00
Thomas Harte 88ed49a833 Enable A20 at reset; fully propagate return to real mode. 2025-04-03 16:14:49 -04:00
Thomas Harte 0c88e62815 Add various caveman debugging comments. 2025-04-02 23:28:20 -04:00
Thomas Harte 88d34012c4 Continue trying to flesh out exceptions. 2025-04-02 23:27:43 -04:00
Thomas Harte 3be8de6fb0 Enforce set-only nature of protected mode bit. 2025-04-02 23:26:21 -04:00
Thomas Harte 804fbf5d5f Add [S/L]MSW. 2025-04-02 23:24:28 -04:00
Thomas Harte 1a68dcbc14 PUSH always pushes a word. 2025-04-02 23:24:00 -04:00
Thomas Harte a9a72a767d Improve fault pathways. 2025-04-01 09:13:41 -04:00
Thomas Harte afc3a8d373 Correct header path. 2025-03-31 09:54:11 -04:00
Thomas Harte da00e6588c Consolidate on class. 2025-03-31 09:34:17 -04:00
Thomas Harte d6376d0ddf Remove improper header. 2025-03-31 09:33:30 -04:00
Thomas Harte 1cca711560 Name MSW bits. 2025-03-30 14:04:43 -04:00
Thomas Harte 552f9196af Convert INTO, AAM; map which instructions post their IP. 2025-03-30 13:39:52 -04:00
Thomas Harte c6fa72cd83 Bring bound inside new orthodoxy. 2025-03-30 13:31:39 -04:00
Thomas Harte 42edc46887 Add invalid-opcode exception; transcribe has-code table. 2025-03-30 13:29:20 -04:00
Thomas Harte ec7e343673 Start to establish throw/catch of 80286 exceptions. 2025-03-30 13:23:36 -04:00
Thomas Harte 69d4d8acb0 Switch to construct and copy. 2025-03-29 17:27:29 -04:00
Thomas Harte a7eab8df22 Add getter for local descriptor table. 2025-03-29 17:24:30 -04:00
Thomas Harte 4247da9118 Add notes to self on exceptions. 2025-03-27 18:08:09 -04:00
Thomas Harte cfba8aeb89 Make style improvements. 2025-03-27 18:07:52 -04:00
Thomas Harte db26a26926 Fix decoding of PUSH immediate. 2025-03-27 13:07:13 -04:00
Thomas Harte 1551fbeb1f Make some stab at descriptor fetch. 2025-03-27 12:50:50 -04:00
Thomas Harte d5c53ca624 Set A20 line properly. 2025-03-26 21:51:43 -04:00
Thomas Harte b34702e370 Set an initial A20 state. 2025-03-26 07:35:17 -04:00
Thomas Harte 8b1543d9c9 Fuzz memory, setup FS and GS. 2025-03-25 17:16:36 -04:00
Thomas Harte e264375a97 Attempt to reintroduce 80286 support (as was). 2025-03-25 09:24:55 -04:00
Thomas Harte fd31d07f41 Begin division of memory into linear and segmented mappings. 2025-03-24 22:58:19 -04:00
Thomas Harte fac15f5539 Introduce a linear-memory holder. 2025-03-24 21:23:08 -04:00
Thomas Harte 6ad88101f1 Saunter up to a circular issue: segments needs memory access. 2025-03-24 17:31:17 -04:00
Thomas Harte 2768b66d10 Propagate mode change. 2025-03-22 23:00:51 -04:00
Thomas Harte d10164be26 Merge branch 'Descriptors' of github.com:TomHarte/CLK into Descriptors 2025-03-22 22:09:50 -04:00
Thomas Harte ce7ff13bbe Proceed to a local assert on LMSW. 2025-03-22 21:57:56 -04:00
Thomas Harte c1d2c159f3 Reenable backdoor AT. 2025-03-21 11:22:09 -04:00
Thomas Harte d3dbdb153c Use indexed descriptors. 2025-03-21 11:20:33 -04:00
Thomas Harte e7218c0321 Add means for indexed segment access. 2025-03-21 11:16:25 -04:00
Thomas Harte 48d8fdb875 Adopt descriptors in memory handling. 2025-03-21 10:18:26 -04:00
Thomas Harte b387ca921a Merge branch 'master' into Descriptors 2025-03-20 21:09:09 -04:00
Thomas Harte 0c502fc9cf Adopt more consistent 'Pointer' naming; eliminate size warning. 2025-03-20 15:33:07 -04:00
Thomas Harte 5d1e3b6c93 Create a home for descriptors. 2025-03-19 14:20:50 -04:00
454 changed files with 24102 additions and 11268 deletions
+4 -1
View File
@@ -17,7 +17,9 @@ jobs:
xcode-version: latest-stable
- name: Make
working-directory: OSBindings/Mac
run: xcodebuild CODE_SIGN_IDENTITY=-
run: |
xcodebuild -downloadComponent MetalToolchain
xcodebuild CODE_SIGN_IDENTITY=-
build-sdl-cmake:
name: SDL UI / cmake / ${{ matrix.os }}
@@ -37,6 +39,7 @@ jobs:
sudo apt-get --fix-missing install cmake gcc-10 libsdl2-dev
;;
macOS)
brew uninstall cmake
brew install cmake sdl2
;;
esac
+1 -1
View File
@@ -14,7 +14,7 @@ namespace Activity {
class Source {
public:
virtual void set_activity_observer(Observer *observer) = 0;
virtual void set_activity_observer(Observer *) = 0;
};
}
+1 -1
View File
@@ -10,7 +10,7 @@
using namespace Analyser::Dynamic;
float ConfidenceCounter::get_confidence() {
float ConfidenceCounter::confidence() const {
return float(hits_) / float(hits_ + misses_);
}
+1 -1
View File
@@ -20,7 +20,7 @@ namespace Analyser::Dynamic {
class ConfidenceCounter: public ConfidenceSource {
public:
/*! @returns The computed probability, based on the history of events. */
float get_confidence() final;
float confidence() const final;
/*! Records an event that implies this is the appropriate class: pushes probability up towards 1.0. */
void add_hit();
+1 -1
View File
@@ -17,7 +17,7 @@ namespace Analyser::Dynamic {
program is handed to an Atari 2600 then its confidence should grow towards 1.0.
*/
struct ConfidenceSource {
virtual float get_confidence() = 0;
virtual float confidence() const = 0;
};
}
+2 -2
View File
@@ -22,10 +22,10 @@ ConfidenceSummary::ConfidenceSummary(
weight_sum_ = std::accumulate(weights.begin(), weights.end(), 0.0f);
}
float ConfidenceSummary::get_confidence() {
float ConfidenceSummary::confidence() const {
float result = 0.0f;
for(std::size_t index = 0; index < sources_.size(); ++index) {
result += sources_[index]->get_confidence() * weights_[index];
result += sources_[index]->confidence() * weights_[index];
}
return result / weight_sum_;
}
+1 -1
View File
@@ -30,7 +30,7 @@ public:
const std::vector<float> &weights);
/*! @returns The weighted sum of all sources. */
float get_confidence() final;
float confidence() const final;
private:
const std::vector<ConfidenceSource *> sources_;
@@ -106,5 +106,5 @@ void MultiTimedMachine::run_for(const Time::Seconds duration) {
if(machine->get_confidence() >= 0.01f) machine->run_for(duration);
});
if(delegate_) delegate_->did_run_machines(this);
if(delegate_) delegate_->did_run_machines(*this);
}
@@ -60,7 +60,7 @@ public:
been received.
*/
struct Delegate {
virtual void did_run_machines(MultiTimedMachine *) = 0;
virtual void did_run_machines(MultiTimedMachine &) = 0;
};
/// Sets @c delegate as the receiver of delegate messages.
void set_delegate(Delegate *const delegate) {
@@ -60,24 +60,24 @@ void MultiSpeaker::set_output_volume(const float volume) {
}
}
void MultiSpeaker::speaker_did_complete_samples(Speaker *const speaker, const std::vector<int16_t> &buffer) {
void MultiSpeaker::speaker_did_complete_samples(Speaker &speaker, const std::vector<int16_t> &buffer) {
auto delegate = delegate_.load(std::memory_order_relaxed);
if(!delegate) return;
{
std::lock_guard lock_guard(front_speaker_mutex_);
if(speaker != front_speaker_) return;
if(&speaker != front_speaker_) return;
}
did_complete_samples(this, buffer, stereo_output_);
}
void MultiSpeaker::speaker_did_change_input_clock(Speaker *const speaker) {
void MultiSpeaker::speaker_did_change_input_clock(Speaker &speaker) {
auto delegate = delegate_.load(std::memory_order_relaxed);
if(!delegate) return;
{
std::lock_guard lock_guard(front_speaker_mutex_);
if(speaker != front_speaker_) return;
if(&speaker != front_speaker_) return;
}
delegate->speaker_did_change_input_clock(this);
delegate->speaker_did_change_input_clock(*this);
}
void MultiSpeaker::set_new_front_machine(::Machine::DynamicMachine *const machine) {
@@ -87,6 +87,6 @@ void MultiSpeaker::set_new_front_machine(::Machine::DynamicMachine *const machin
}
auto delegate = delegate_.load(std::memory_order_relaxed);
if(delegate) {
delegate->speaker_did_change_input_clock(this);
delegate->speaker_did_change_input_clock(*this);
}
}
@@ -42,8 +42,8 @@ public:
void set_output_volume(float) override;
private:
void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) final;
void speaker_did_change_input_clock(Speaker *speaker) final;
void speaker_did_complete_samples(Speaker &, const std::vector<int16_t> &buffer) final;
void speaker_did_change_input_clock(Speaker &) final;
MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speakers);
std::vector<Outputs::Speaker::Speaker *> speakers_;
@@ -12,9 +12,7 @@
#include <algorithm>
namespace {
Log::Logger<Log::Source::MultiMachine> logger;
using Logger = Log::Logger<Log::Source::MultiMachine>;
}
using namespace Analyser::Dynamic;
@@ -68,11 +66,11 @@ bool MultiMachine::would_collapse(const std::vector<std::unique_ptr<DynamicMachi
(machines.front()->timed_machine()->get_confidence() >= 2.0f * machines[1]->timed_machine()->get_confidence());
}
void MultiMachine::did_run_machines(MultiTimedMachine *) {
void MultiMachine::did_run_machines(MultiTimedMachine &) {
std::lock_guard machines_lock(machines_mutex_);
if constexpr (logger.enabled) {
auto line = logger.info();
if constexpr (Logger::InfoEnabled) {
auto line = Logger::info();
for(const auto &machine: machines_) {
auto timed_machine = machine->timed_machine();
line.append("%0.4f %s; ", timed_machine->get_confidence(), timed_machine->debug_type().c_str());
@@ -62,7 +62,7 @@ public:
void *raw_pointer() final;
private:
void did_run_machines(MultiTimedMachine *) final;
void did_run_machines(MultiTimedMachine &) final;
std::vector<std::unique_ptr<DynamicMachine>> machines_;
std::recursive_mutex machines_mutex_;
+1
View File
@@ -18,6 +18,7 @@ enum class Machine {
AtariST,
Amiga,
Archimedes,
BBCMicro,
ColecoVision,
Electron,
Enterprise,
+1 -1
View File
@@ -44,7 +44,7 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::s
case 3: catalogue->bootOption = Catalogue::BootOption::ExecBOOT; break;
}
for(std::size_t file_offset = 8; file_offset < final_file_offset; file_offset += 8) {
for(std::size_t file_offset = 8; file_offset <= final_file_offset; file_offset += 8) {
File new_file;
char name[10];
snprintf(name, 10, "%c.%.7s", names->samples[0][file_offset + 7] & 0x7f, &names->samples[0][file_offset]);
+1
View File
@@ -8,6 +8,7 @@
#pragma once
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
+166 -56
View File
@@ -19,6 +19,23 @@
using namespace Analyser::Static::Acorn;
namespace {
bool is_basic(const File &file) {
std::size_t pointer = 0;
const uint8_t *const data = file.data.data();
const std::size_t data_size = file.data.size();
while(true) {
if(pointer >= data_size-1 || data[pointer] != 0x0d) {
return false;
}
if((data[pointer+1]&0x7f) == 0x7f) break;
pointer += data[pointer+3];
}
return true;
}
}
static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
AcornCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> acorn_cartridges;
@@ -68,11 +85,22 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(
TargetPlatform::IntType,
bool
) {
auto target8bit = std::make_unique<ElectronTarget>();
auto targetArchimedes = std::make_unique<ArchimedesTarget>();
const auto early_exit = [](auto &ptr) {
TargetList list;
list.push_back(std::move(ptr));
return list;
};
// Copy appropriate cartridges to the 8-bit target.
target8bit->media.cartridges = AcornCartridgesFrom(media.cartridges);
auto targetElectron = std::make_unique<ElectronTarget>();
auto targetBBC = std::make_unique<BBCMicroTarget>();
auto targetArchimedes = std::make_unique<ArchimedesTarget>();
int bbc_hits = 0;
int electron_hits = 0;
bool format_prefers_bbc = false;
// Copy appropriate cartridges to the 8-bit targets.
targetElectron->media.cartridges = AcornCartridgesFrom(media.cartridges);
targetBBC->media.cartridges = AcornCartridgesFrom(media.cartridges);
// If there are tapes, attempt to get data from the first.
if(!media.tapes.empty()) {
@@ -80,35 +108,15 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(
auto serialiser = tape->serialiser();
std::vector<File> files = GetFiles(*serialiser);
// continue if there are any files
// Continue only if there are any files.
if(!files.empty()) {
bool is_basic = true;
// If a file is execute-only, that means *RUN.
if(files.front().flags & File::Flags::ExecuteOnly) {
is_basic = false;
}
// Check also for a continuous threading of BASIC lines; if none then this probably isn't BASIC code,
// so that's also justification to *RUN.
if(is_basic) {
std::size_t pointer = 0;
uint8_t *const data = &files.front().data[0];
const std::size_t data_size = files.front().data.size();
while(true) {
if(pointer >= data_size-1 || data[pointer] != 0x0d) {
is_basic = false;
break;
}
if((data[pointer+1]&0x7f) == 0x7f) break;
pointer += data[pointer+3];
}
}
// Inspect first file. If it's protected or doesn't look like BASIC
// then the loading command is *RUN. Otherwise it's CHAIN"".
target8bit->loading_command = is_basic ? "CHAIN\"\"\n" : "*RUN\n";
target8bit->media.tapes = media.tapes;
targetElectron->loading_command =
(files.front().flags & File::Flags::ExecuteOnly) || !is_basic(files.front()) ? "*RUN\n" : "CHAIN\"\"\n";
targetElectron->media.tapes = media.tapes;
// TODO: my BBC Micro doesn't yet support tapes; evaluate here in the future.
}
}
@@ -123,25 +131,67 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(
// 8-bit options: DFS and Hugo-style ADFS.
if(dfs_catalogue || (adfs_catalogue && !adfs_catalogue->has_large_sectors && adfs_catalogue->is_hugo)) {
// Accept the disk and determine whether DFS or ADFS ROMs are implied.
// Use the Pres ADFS if using an ADFS, as it leaves Page at &EOO.
target8bit->media.disks = media.disks;
target8bit->has_dfs = bool(dfs_catalogue);
target8bit->has_pres_adfs = bool(adfs_catalogue);
// Electron: use the Pres ADFS if using an ADFS, as it leaves Page at &E00.
targetElectron->media.disks = media.disks;
targetElectron->has_dfs = bool(dfs_catalogue);
targetElectron->has_pres_adfs = bool(adfs_catalogue);
// BBC: only the 1770 DFS is currently supported, so use that.
targetBBC->media.disks = media.disks;
targetBBC->has_1770dfs = bool(dfs_catalogue);
targetBBC->has_adfs = bool(adfs_catalogue);
// Special case: if there's only one file, and it is called CPMDISC,
// select a BBC with the Z80 second processor.
const auto &files = dfs_catalogue ? dfs_catalogue->files : adfs_catalogue->files;
if(files.size() == 1 && files[0].name == "$.CPMDISC") {
targetBBC->tube_processor = BBCMicroTarget::TubeProcessor::Z80;
return early_exit(targetBBC);
}
// Check whether a simple shift+break will do for loading this disk.
const auto bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption;
if(bootOption != Catalogue::BootOption::None) {
target8bit->should_shift_restart = true;
targetBBC->should_shift_restart = targetElectron->should_shift_restart = true;
} else {
target8bit->loading_command = "*CAT\n";
// Otherwise: if there's only one BASIC program then chain it.
// Failing that, do a *CAT to be communicative.
const File *sole_basic_file = nullptr;
for(const auto &file: dfs_catalogue ? dfs_catalogue->files : adfs_catalogue->files) {
if(is_basic(file)) {
if(!sole_basic_file) {
sole_basic_file = &file;
} else {
sole_basic_file = nullptr;
break;
}
}
}
targetBBC->loading_command = targetElectron->loading_command =
sole_basic_file ? "CHAIN \"" + sole_basic_file->name + "\"\n" : "*CAT\n";
}
// Check whether adding the AP6 ROM is justified.
// For now this is an incredibly dense text search;
// if any of the commands that aren't usually present
// on a stock Electron are here, add the AP6 ROM and
// some sideways RAM such that the SR commands are useful.
for(const auto &file: dfs_catalogue ? dfs_catalogue->files : adfs_catalogue->files) {
// Further special case: if any of the files have a top word of 0x0003 then
// they're for a 6502 second processor, so provide a BBC with one of those.
for(const auto &file: files) {
if((file.load_address >> 16) == 3) {
targetBBC->tube_processor = BBCMicroTarget::TubeProcessor::WDC65C02;
return early_exit(targetBBC);
}
}
// Add a slight preference for the BBC over the Electron, all else being equal, if this is a DFS floppy.
format_prefers_bbc = bool(dfs_catalogue);
for(const auto &file: files) {
// Electron: check whether adding the AP6 ROM is justified.
// For now this is an incredibly dense text search;
// if any of the commands that aren't usually present
// on a stock Electron are here, add the AP6 ROM and
// some sideways RAM such that the SR commands are useful.
for(const auto &command: {
"AQRPAGE", "BUILD", "DUMP", "FORMAT", "INSERT", "LANG", "LIST", "LOADROM",
"LOCK", "LROMS", "RLOAD", "ROMS", "RSAVE", "SAVEROM", "SRLOAD", "SRPAGE",
@@ -149,10 +199,60 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(
"VERIFY", "ZERO"
}) {
if(std::search(file.data.begin(), file.data.end(), command, command+strlen(command)) != file.data.end()) {
target8bit->has_ap6_rom = true;
target8bit->has_sideways_ram = true;
targetElectron->has_ap6_rom = true;
targetElectron->has_sideways_ram = true;
}
}
// Look for any 'BBC indicators', i.e. direct access to BBC-specific hardware.
// Also currently a dense search.
const auto hits = [&](const std::initializer_list<uint16_t> collection) {
int hits = 0;
for(const auto address: collection) {
const uint8_t sta_address[3] = {
0x8d, uint8_t(address & 0xff), uint8_t(address >> 8)
};
if(std::search(
file.data.begin(), file.data.end(),
std::begin(sta_address), std::end(sta_address)
) != file.data.end()) {
++hits;
}
// I think I'll want std::ranges::contains_subrange if/when building for C++23.
}
return hits;
};
bbc_hits += hits({
// The video control registers.
0xfe20, 0xfe21,
// The system VIA.
0xfe40, 0xfe41, 0xfe42, 0xfe43, 0xfe44, 0xfe45, 0xfe46, 0xfe47,
0xfe48, 0xfe49, 0xfe4a, 0xfe4b, 0xfe4c, 0xfe4d, 0xfe4e, 0xfe4f,
// The user VIA.
0xfe60, 0xfe61, 0xfe62, 0xfe63, 0xfe64, 0xfe65, 0xfe66, 0xfe67,
0xfe68, 0xfe69, 0xfe6a, 0xfe6b, 0xfe6c, 0xfe6d, 0xfe6e, 0xfe6f,
});
// BASIC for "MODE7".
static constexpr uint8_t mode7[] = {0xeb, 0x37};
bbc_hits += std::search(
file.data.begin(), file.data.end(),
std::begin(mode7), std::end(mode7)
) != file.data.end();
electron_hits += hits({
// ULA addresses that aren't also the BBC's CRTC.
0xfe03, 0xfe04, 0xfe05,
0xfe06, 0xfe07, 0xfe08,
0xfe09, 0xfe0a, 0xfe0b,
0xfe0c, 0xfe0d, 0xfe0e,
0xfe0f,
});
}
} else if(adfs_catalogue) {
// Archimedes options, implicitly: ADFS, non-Hugo.
@@ -167,8 +267,8 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(
// Take whatever else comes with a preference for things that don't
// have 'boot' or 'read' in them (the latter of which will tend to be
// read_me or read_this or similar).
constexpr char read[] = "read";
constexpr char boot[] = "boot";
static constexpr char read[] = "read";
static constexpr char boot[] = "boot";
const auto has = [&](const char *begin, const char *end) {
return std::search(
file.name.begin(), file.name.end(),
@@ -197,28 +297,38 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(
// Enable the Acorn ADFS if a mass-storage device is attached;
// unlike the Pres ADFS it retains SCSI logic.
if(!media.mass_storage_devices.empty()) {
target8bit->has_pres_adfs = false; // To override a floppy selection, if one was made.
target8bit->has_acorn_adfs = true;
targetElectron->has_pres_adfs = false; // To override a floppy selection, if one was made.
targetElectron->has_acorn_adfs = true;
// Assume some sort of later-era Acorn work is likely to happen;
// so ensure *TYPE, etc are present.
target8bit->has_ap6_rom = true;
target8bit->has_sideways_ram = true;
targetElectron->has_ap6_rom = true;
targetElectron->has_sideways_ram = true;
target8bit->media.mass_storage_devices = media.mass_storage_devices;
targetElectron->media.mass_storage_devices = media.mass_storage_devices;
// Check for a boot option.
const auto sector = target8bit->media.mass_storage_devices.front()->get_block(1);
const auto sector = targetElectron->media.mass_storage_devices.front()->get_block(1);
if(sector[0xfd]) {
target8bit->should_shift_restart = true;
targetElectron->should_shift_restart = true;
} else {
target8bit->loading_command = "*CAT\n";
targetElectron->loading_command = "*CAT\n";
}
}
TargetList targets;
if(!target8bit->media.empty()) {
targets.push_back(std::move(target8bit));
if(!targetElectron->media.empty() && !targetBBC->media.empty()) {
if(bbc_hits > electron_hits || (bbc_hits == electron_hits && format_prefers_bbc)) {
targets.push_back(std::move(targetBBC));
} else {
targets.push_back(std::move(targetElectron));
}
} else {
if(!targetElectron->media.empty()) {
targets.push_back(std::move(targetElectron));
} else if(!targetBBC->media.empty()) {
targets.push_back(std::move(targetBBC));
}
}
if(!targetArchimedes->media.empty()) {
targets.push_back(std::move(targetArchimedes));
+27
View File
@@ -9,6 +9,7 @@
#pragma once
#include "Analyser/Static/StaticAnalyser.hpp"
#include "Reflection/Enum.hpp"
#include "Reflection/Struct.hpp"
#include <string>
@@ -36,6 +37,32 @@ private:
}
};
struct BBCMicroTarget: public ::Analyser::Static::Target, public Reflection::StructImpl<BBCMicroTarget> {
std::string loading_command;
bool should_shift_restart = false;
bool has_1770dfs = false;
bool has_adfs = false;
bool has_sideways_ram = true;
bool has_beebsid = false;
ReflectableEnum(TubeProcessor, None, WDC65C02, Z80);
TubeProcessor tube_processor = TubeProcessor::None;
BBCMicroTarget() : Analyser::Static::Target(Machine::BBCMicro) {}
private:
friend Reflection::StructImpl<BBCMicroTarget>;
void declare_fields() {
DeclareField(has_1770dfs);
DeclareField(has_adfs);
DeclareField(has_sideways_ram);
DeclareField(has_beebsid);
AnnounceEnum(TubeProcessor);
DeclareField(tube_processor);
}
};
struct ArchimedesTarget: public ::Analyser::Static::Target, public Reflection::StructImpl<ArchimedesTarget> {
std::string main_program;
+84 -48
View File
@@ -16,9 +16,18 @@
#include <algorithm>
#include <cstring>
#include <unordered_set>
namespace {
std::string rtrimmed(const std::string &input) {
auto trimmed = input;
trimmed.erase(std::find_if(trimmed.rbegin(), trimmed.rend(), [](const char ch) {
return !std::isspace(ch);
}).base(), trimmed.end());
return trimmed;
}
bool strcmp_insensitive(const char *a, const char *b) {
if(std::strlen(a) != std::strlen(b)) return false;
while(*a) {
@@ -104,58 +113,85 @@ void InspectCatalogue(
return;
}
// If only one file is [potentially] BASIC, run that one; otherwise if only one has a suffix
// that AMSDOS allows to be omitted, pick that one.
int basic_files = 0;
int implicit_suffixed_files = 0;
const auto run_name = [&]() -> std::optional<std::string> {
// Collect:
//
// 1. a set of all files that can be run without specifying an extension plus their appearance counts;
// 2. a set of all BASIC file names.
std::unordered_map<std::string, int> candidates;
std::unordered_set<std::string> basic_names;
for(std::size_t c = 0; c < candidate_files.size(); c++) {
// Files with nothing but spaces in their name can't be loaded by the user, so disregard them.
if(
(candidate_files[c]->type == " " && candidate_files[c]->name == " ") ||
!is_implied_extension(candidate_files[c]->type)
) {
continue;
}
std::size_t last_basic_file = 0;
std::size_t last_implicit_suffixed_file = 0;
for(std::size_t c = 0; c < candidate_files.size(); c++) {
// Files with nothing but spaces in their name can't be loaded by the user, so disregard them.
if(candidate_files[c]->type == " " && candidate_files[c]->name == " ")
continue;
// Check for whether this is [potentially] BASIC.
if(candidate_files[c]->data.size() >= 128 && !((candidate_files[c]->data[18] >> 1) & 7)) {
basic_files++;
last_basic_file = c;
}
// Check suffix for emptiness.
if(is_implied_extension(candidate_files[c]->type)) {
implicit_suffixed_files++;
last_implicit_suffixed_file = c;
}
}
if(basic_files == 1 || implicit_suffixed_files == 1) {
std::size_t selected_file = (basic_files == 1) ? last_basic_file : last_implicit_suffixed_file;
target->loading_command = RunCommandFor(*candidate_files[selected_file]);
return;
}
// One more guess: if only one remaining candidate file has a different name than the others,
// assume it is intended to stand out.
std::map<std::string, int> name_counts;
std::map<std::string, std::size_t> indices_by_name;
std::size_t index = 0;
for(const auto &file : candidate_files) {
name_counts[file->name]++;
indices_by_name[file->name] = index;
index++;
}
if(name_counts.size() == 2) {
for(const auto &pair : name_counts) {
if(pair.second == 1) {
target->loading_command = RunCommandFor(*candidate_files[indices_by_name[pair.first]]);
return;
++candidates[candidate_files[c]->name];
if(candidate_files[c]->data.size() >= 128 && !((candidate_files[c]->data[18] >> 1) & 7)) {
basic_names.insert(candidate_files[c]->name);
}
}
}
// Desperation.
target->loading_command = "cat\n";
// Only one candidate total.
if(candidates.size() == 1) {
return candidates.begin()->first;
}
// Only one BASIC candidate.
if(basic_names.size() == 1) {
return *basic_names.begin();
}
// Exactly two candidate names, but only one is a unique name.
if(candidates.size() == 2) {
const auto item1 = candidates.begin();
const auto item2 = std::next(item1);
if(item1->second == 1 && item2->second != 1) {
return item1->first;
}
if(item2->second == 1 && item1->second != 1) {
return item2->first;
}
}
// Remove from candidates anything that is just a suffixed version of
// another name, as long as the other name is three or more characters.
std::vector<std::string> to_remove;
for(const auto &lhs: candidates) {
const auto trimmed = rtrimmed(lhs.first);
if(trimmed.size() < 3) {
continue;
}
for(const auto &rhs: candidates) {
if(lhs.first == rhs.first) {
continue;
}
if(rhs.first.find(trimmed) == 0) {
to_remove.push_back(rhs.first);
}
}
}
for(const auto &candidate: to_remove) {
candidates.erase(candidate);
}
if(candidates.size() == 1) {
return candidates.begin()->first;
}
return {};
} ();
if(run_name.has_value()) {
target->loading_command = "run\"" + rtrimmed(*run_name) + "\n";
} else {
target->loading_command = "cat\n";
}
}
bool CheckBootSector(
+1 -1
View File
@@ -258,7 +258,7 @@ analyse_starting_address(uint16_t starting_address) {
case 0x1c01:
// TODO: assume C128.
default:
Log::Logger<Log::Source::CommodoreStaticAnalyser>().error().append(
Log::Logger<Log::Source::CommodoreStaticAnalyser>::error().append(
"Unrecognised loading address for Commodore program: %04x", starting_address);
[[fallthrough]];
case 0x1001:
+3 -1
View File
@@ -9,6 +9,8 @@
#include "Tape.hpp"
#include "Storage/Tape/Parsers/Commodore.hpp"
#include <algorithm>
using namespace Analyser::Static::Commodore;
std::vector<File> Analyser::Static::Commodore::GetFiles(Storage::Tape::TapeSerialiser &serialiser, TargetPlatform::Type type) {
@@ -37,7 +39,7 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(Storage::Tape::TapeSeria
header = parser.get_next_header(serialiser);
if(!header) continue;
if(header->type != Storage::Tape::Commodore::Header::DataBlock) break;
std::copy(header->data.begin(), header->data.end(), std::back_inserter(new_file.data));
std::ranges::copy(header->data, std::back_inserter(new_file.data));
}
}
break;
+6 -6
View File
@@ -60,11 +60,11 @@ private:
bool overrun_ = false;
};
constexpr uint8_t x(uint8_t v) { return v >> 6; }
constexpr uint8_t y(uint8_t v) { return (v >> 3) & 7; }
constexpr uint8_t q(uint8_t v) { return (v >> 3) & 1; }
constexpr uint8_t p(uint8_t v) { return (v >> 4) & 3; }
constexpr uint8_t z(uint8_t v) { return v & 7; }
constexpr uint8_t x(const uint8_t v) { return v >> 6; }
constexpr uint8_t y(const uint8_t v) { return (v >> 3) & 7; }
constexpr uint8_t q(const uint8_t v) { return (v >> 3) & 1; }
constexpr uint8_t p(const uint8_t v) { return (v >> 4) & 3; }
constexpr uint8_t z(const uint8_t v) { return v & 7; }
Instruction::Condition condition_table[] = {
Instruction::Condition::NZ, Instruction::Condition::Z,
@@ -92,7 +92,7 @@ Instruction::Location RegisterTableEntry(
Instruction &instruction,
const bool needs_indirect_offset
) {
constexpr Instruction::Location register_table[] = {
static constexpr Instruction::Location register_table[] = {
Instruction::Location::B, Instruction::Location::C,
Instruction::Location::D, Instruction::Location::E,
Instruction::Location::H, Instruction::Location::L,
+4
View File
@@ -27,6 +27,10 @@ constexpr bool is_at(const Model model) {
return model >= Model::AT;
}
constexpr bool has_ide(const Model model) {
return model >= Model::AT;
}
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(VideoAdaptor,
MDA,
+7 -7
View File
@@ -9,6 +9,7 @@
#include "StaticAnalyser.hpp"
#include <algorithm>
#include <bit>
#include <cstddef>
#include <cstdlib>
#include <cstring>
@@ -52,6 +53,7 @@
#include "Storage/Disk/DiskImage/Formats/HFE.hpp"
#include "Storage/Disk/DiskImage/Formats/IPF.hpp"
#include "Storage/Disk/DiskImage/Formats/IMD.hpp"
#include "Storage/Disk/DiskImage/Formats/JFD.hpp"
#include "Storage/Disk/DiskImage/Formats/MacintoshIMG.hpp"
#include "Storage/Disk/DiskImage/Formats/MSA.hpp"
#include "Storage/Disk/DiskImage/Formats/NIB.hpp"
@@ -66,6 +68,7 @@
#include "Storage/MassStorage/Formats/DSK.hpp"
#include "Storage/MassStorage/Formats/HDV.hpp"
#include "Storage/MassStorage/Formats/HFV.hpp"
#include "Storage/MassStorage/Formats/VHD.hpp"
// State Snapshots
#include "Storage/State/SNA.hpp"
@@ -205,6 +208,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
accumulator.try_standard<Disk::DiskImageHolder<Disk::AcornADF>>(TargetPlatform::Acorn, "adf");
accumulator.try_standard<Disk::DiskImageHolder<Disk::AmigaADF>>(TargetPlatform::Amiga, "adf");
accumulator.try_standard<Disk::DiskImageHolder<Disk::AcornADF>>(TargetPlatform::Acorn, "adl");
accumulator.try_standard<Disk::DiskImageHolder<Disk::JFD>>(TargetPlatform::Archimedes, "jfd");
accumulator.try_standard<Cartridge::BinaryDump>(TargetPlatform::AllCartridge, "bin");
@@ -299,6 +303,8 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
accumulator.try_standard<Tape::UEF>(TargetPlatform::Acorn, "uef");
accumulator.try_standard<MassStorage::VHD>(TargetPlatform::PCCompatible, "vhd");
accumulator.try_standard<Disk::DiskImageHolder<Disk::WOZ>>(TargetPlatform::DiskII, "woz");
return accumulator.media;
@@ -340,13 +346,7 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) {
TargetPlatform::IntType potential_platforms = 0;
Media media = GetMediaAndPlatforms(file_name, potential_platforms);
// TODO: std::popcount here.
int total_options = 0;
TargetPlatform::IntType mask = 1;
while(mask) {
total_options += bool(potential_platforms & mask);
mask <<= 1;
}
int total_options = std::popcount(potential_platforms);
const bool is_confident = total_options == 1;
// i.e. This analyser `is_confident` if file analysis suggested only one potential target platform.
// The machine-specific static analyser will still run in case it can provide meaningful annotations on
+1 -1
View File
@@ -5,7 +5,7 @@ project(CLK
VERSION 24.01.22
)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
+1 -6
View File
@@ -128,12 +128,7 @@ public:
forceinline constexpr T operator -() const { return T(- length_); }
forceinline constexpr bool operator <(const T &rhs) const { return length_ < rhs.length_; }
forceinline constexpr bool operator >(const T &rhs) const { return length_ > rhs.length_; }
forceinline constexpr bool operator <=(const T &rhs) const { return length_ <= rhs.length_; }
forceinline constexpr bool operator >=(const T &rhs) const { return length_ >= rhs.length_; }
forceinline constexpr bool operator ==(const T &rhs) const { return length_ == rhs.length_; }
forceinline constexpr bool operator !=(const T &rhs) const { return length_ != rhs.length_; }
auto operator <=>(const WrappedInt &) const = default;
forceinline constexpr bool operator !() const { return !length_; }
// bool operator () is not supported because it offers an implicit cast to int,
+4 -4
View File
@@ -16,10 +16,10 @@ 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);
static constexpr int elements_per_uint32 = sizeof(uint32_t) / sizeof(ValueT);
static constexpr int unit_shift = sizeof(ValueT) * 8;
static constexpr int insert_shift = (DeferredDepth & (elements_per_uint32 - 1)) * unit_shift;
static constexpr uint32_t insert_mask = ~(0xffff'ffff << insert_shift);
std::array<uint32_t, (DeferredDepth + elements_per_uint32 - 1) / elements_per_uint32> backlog;
+1 -1
View File
@@ -325,7 +325,7 @@ public:
/// Flushes all accumulated time.
inline void flush() {
if(!is_flushed_) {
task_queue_.flush();
task_queue_.lock_flush();
object_.run_for(time_since_update_.template flush<TargetTimeScale>());
is_flushed_ = true;
}
+79 -59
View File
@@ -8,11 +8,11 @@
#include "1770.hpp"
#include "Storage/Disk/Encodings/MFM/Constants.hpp"
#include "Outputs/Log.hpp"
#include "Storage/Disk/Encodings/MFM/Constants.hpp"
namespace {
Log::Logger<Log::Source::WDFDC> logger;
using Logger = Log::Logger<Log::Source::WDFDC>;
}
using namespace WD;
@@ -31,10 +31,10 @@ void WD1770::write(const int address, const uint8_t value) {
if((value&0xf0) == 0xd0) {
if(value == 0xd0) {
// Force interrupt **immediately**.
logger.info().append("Force interrupt immediately");
Logger::info().append("Force interrupt immediately");
posit_event(int(Event1770::ForceInterrupt));
} else {
logger.error().append("TODO: force interrupt");
Logger::error().append("TODO: force interrupt");
update_status([] (Status &status) {
status.type = Status::One;
});
@@ -101,19 +101,20 @@ uint8_t WD1770::read(const int address) {
if(status_.type == Status::One)
status |= (status_.spin_up ? Flag::SpinUp : 0);
}
// logger.info().append("Returned status %02x of type %d", status, 1+int(status_.type));
// Logger::info().append("Returned status %02x of type %d", status, 1+int(status_.type));
return status;
}
case 1:
logger.info().append("Returned track %d", track_);
Logger::info().append("Returned track %d", track_);
return track_;
case 2:
logger.info().append("Returned sector %d", sector_);
Logger::info().append("Returned sector %d", sector_);
return sector_;
case 3:
update_status([] (Status &status) {
status.data_request = false;
});
// Logger::info().append("Returned data %02x; [drq:%d]", data_, status_.data_request);
return data_;
}
}
@@ -132,26 +133,46 @@ void WD1770::run_for(const Cycles cycles) {
}
}
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = int(mask); return; case __LINE__:
#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; delay_time_ = ms * 8000; WAIT_FOR_EVENT(Event1770::Timer);
#define WAIT_FOR_BYTES(count) resume_point_ = __LINE__; distance_into_section_ = 0; WAIT_FOR_EVENT(Event::Token); if(get_latest_token().type == Token::Byte) distance_into_section_++; if(distance_into_section_ < count) { interesting_event_mask_ = int(Event::Token); return; }
#define BEGIN_SECTION() switch(resume_point_) { default:
#define END_SECTION() (void)0; }
#include <iostream>
#define READ_ID() \
if(new_event_type == int(Event::Token)) { \
if(!distance_into_section_ && get_latest_token().type == Token::ID) {\
set_data_mode(DataMode::Reading); \
++distance_into_section_; \
} else if(distance_into_section_ && distance_into_section_ < 7 && get_latest_token().type == Token::Byte) { \
header_[distance_into_section_ - 1] = get_latest_token().byte_value; \
++distance_into_section_; \
} \
void WD1770::posit_event(const int new_event_type) {
#define WAIT_FOR_EVENT(mask) { \
interesting_event_mask_ = int(mask); \
static constexpr int location = __COUNTER__ + 1; \
resume_point_ = location; \
return; \
case location: \
(void)0; \
}
#define WAIT_FOR_TIME(ms) \
delay_time_ = ms * 8000; \
WAIT_FOR_EVENT(Event1770::Timer);
#define WAIT_FOR_BYTES(count) \
distance_into_section_ = 0; \
WAIT_FOR_EVENT(Event::Token); \
distance_into_section_ += get_latest_token().type == Token::Byte; \
if(distance_into_section_ < count) { \
RESUME_WAIT(Event::Token); \
}
#define RESUME_WAIT(mask) interesting_event_mask_ = int(mask); return;
#define BEGIN_SECTION() switch(resume_point_) { default:
#define END_SECTION() (void)0; }
const auto READ_ID = [&] {
if(new_event_type == int(Event::Token)) {
if(!distance_into_section_ && get_latest_token().type == Token::ID) {
set_data_mode(DataMode::Reading);
++distance_into_section_;
} else if(distance_into_section_ && distance_into_section_ < 7 && get_latest_token().type == Token::Byte) {
header_[distance_into_section_ - 1] = get_latest_token().byte_value;
++distance_into_section_;
}
}
#define CONCATENATE(x, y) x ## y
#define INDIRECT_CONCATENATE(x, y) TOKENPASTE(x, y)
#define LINE_LABEL INDIRECT_CONCATENATE(label, __LINE__)
};
#define SPIN_UP() \
set_motor_on(true); \
@@ -160,24 +181,6 @@ void WD1770::run_for(const Cycles cycles) {
WAIT_FOR_EVENT(Event1770::IndexHoleTarget); \
status_.spin_up = true;
// +--------+----------+-------------------------+
// ! ! ! BITS !
// ! TYPE ! COMMAND ! 7 6 5 4 3 2 1 0 !
// +--------+----------+-------------------------+
// ! 1 ! Restore ! 0 0 0 0 h v r1 r0 !
// ! 1 ! Seek ! 0 0 0 1 h v r1 r0 !
// ! 1 ! Step ! 0 0 1 u h v r1 r0 !
// ! 1 ! Step-in ! 0 1 0 u h v r1 r0 !
// ! 1 ! Step-out ! 0 1 1 u h v r1 r0 !
// ! 2 ! Rd sectr ! 1 0 0 m h E 0 0 !
// ! 2 ! Wt sectr ! 1 0 1 m h E P a0 !
// ! 3 ! Rd addr ! 1 1 0 0 h E 0 0 !
// ! 3 ! Rd track ! 1 1 1 0 h E 0 0 !
// ! 3 ! Wt track ! 1 1 1 1 h E P 0 !
// ! 4 ! Forc int ! 1 1 0 1 i3 i2 i1 i0 !
// +--------+----------+-------------------------+
void WD1770::posit_event(const int new_event_type) {
if(new_event_type == int(Event::IndexHole)) {
index_hole_count_++;
if(index_hole_count_target_ == index_hole_count_) {
@@ -198,7 +201,7 @@ void WD1770::posit_event(const int new_event_type) {
if(new_event_type == int(Event1770::ForceInterrupt)) {
interesting_event_mask_ = 0;
resume_point_ = 0;
resume_point_ = IdleResumePoint;
update_status([] (Status &status) {
status.type = Status::One;
status.data_request = false;
@@ -209,12 +212,29 @@ void WD1770::posit_event(const int new_event_type) {
interesting_event_mask_ &= ~new_event_type;
}
// +--------+----------+-------------------------+
// ! ! ! BITS !
// ! TYPE ! COMMAND ! 7 6 5 4 3 2 1 0 !
// +--------+----------+-------------------------+
// ! 1 ! Restore ! 0 0 0 0 h v r1 r0 !
// ! 1 ! Seek ! 0 0 0 1 h v r1 r0 !
// ! 1 ! Step ! 0 0 1 u h v r1 r0 !
// ! 1 ! Step-in ! 0 1 0 u h v r1 r0 !
// ! 1 ! Step-out ! 0 1 1 u h v r1 r0 !
// ! 2 ! Rd sectr ! 1 0 0 m h E 0 0 !
// ! 2 ! Wt sectr ! 1 0 1 m h E P a0 !
// ! 3 ! Rd addr ! 1 1 0 0 h E 0 0 !
// ! 3 ! Rd track ! 1 1 1 0 h E 0 0 !
// ! 3 ! Wt track ! 1 1 1 1 h E P 0 !
// ! 4 ! Forc int ! 1 1 0 1 i3 i2 i1 i0 !
// +--------+----------+-------------------------+
BEGIN_SECTION()
// Wait for a new command, branch to the appropriate handler.
case 0:
case IdleResumePoint:
wait_for_command:
logger.info().append("Idle...");
Logger::info().append("Idle...");
set_data_mode(DataMode::Scanning);
index_hole_count_ = 0;
@@ -231,7 +251,7 @@ void WD1770::posit_event(const int new_event_type) {
status.track_zero = false; // Always reset by a non-type 1; so reset regardless and set properly later.
});
logger.info().append("Starting %02x", command_);
Logger::info().append("Starting %02x", command_);
if(!(command_ & 0x80)) goto begin_type_1;
if(!(command_ & 0x40)) goto begin_type_2;
@@ -261,7 +281,7 @@ void WD1770::posit_event(const int new_event_type) {
status.data_request = false;
});
logger.info().append("Step/Seek/Restore with track %d data %d", track_, data_);
Logger::info().append("Step/Seek/Restore with track %d data %d", track_, data_);
if(!has_motor_on_line() && !has_head_load_line()) goto test_type1_type;
if(has_motor_on_line()) goto begin_type1_spin_up;
@@ -341,7 +361,7 @@ void WD1770::posit_event(const int new_event_type) {
READ_ID();
if(index_hole_count_ == 6) {
logger.info().append("Nothing found to verify");
Logger::info().append("Nothing found to verify");
update_status([] (Status &status) {
status.seek_error = true;
});
@@ -359,7 +379,7 @@ void WD1770::posit_event(const int new_event_type) {
}
if(header_[0] == track_) {
logger.info().append("Reached track %d", track_);
Logger::info().append("Reached track %d", track_);
update_status([] (Status &status) {
status.crc_error = false;
});
@@ -432,7 +452,7 @@ void WD1770::posit_event(const int new_event_type) {
READ_ID();
if(index_hole_count_ == 5) {
logger.info().append("Failed to find sector %d", sector_);
Logger::info().append("Failed to find sector %d", sector_);
update_status([] (Status &status) {
status.record_not_found = true;
});
@@ -442,12 +462,12 @@ void WD1770::posit_event(const int new_event_type) {
distance_into_section_ = 0;
set_data_mode(DataMode::Scanning);
logger.info().append("Considering %d/%d", header_[0], header_[2]);
Logger::info().append("Considering %d/%d", header_[0], header_[2]);
if( header_[0] == track_ && header_[2] == sector_ &&
(has_motor_on_line() || !(command_&0x02) || ((command_&0x08) >> 3) == header_[1])) {
logger.info().append("Found %d/%d", header_[0], header_[2]);
Logger::info().append("Found %d/%d", header_[0], header_[2]);
if(get_crc_generator().get_value()) {
logger.info().append("CRC error; back to searching");
Logger::info().append("CRC error; back to searching");
update_status([] (Status &status) {
status.crc_error = true;
});
@@ -505,18 +525,18 @@ void WD1770::posit_event(const int new_event_type) {
set_data_mode(DataMode::Scanning);
if(get_crc_generator().get_value()) {
logger.info().append("CRC error; terminating");
Logger::info().append("CRC error; terminating");
update_status([] (Status &status) {
status.crc_error = true;
});
goto wait_for_command;
}
logger.info().append("Finished reading sector %d", sector_);
Logger::info().append("Finished reading sector %d", sector_);
if(command_ & 0x10) {
sector_++;
logger.info().append("Advancing to search for sector %d", sector_);
Logger::info().append("Advancing to search for sector %d", sector_);
goto test_type2_write_protection;
}
goto wait_for_command;
@@ -600,7 +620,7 @@ void WD1770::posit_event(const int new_event_type) {
sector_++;
goto test_type2_write_protection;
}
logger.info().append("Wrote sector %d", sector_);
Logger::info().append("Wrote sector %d", sector_);
goto wait_for_command;
@@ -818,7 +838,7 @@ void WD1770::update_status(const std::function<void(Status &)> updater) {
(status_.busy != old_status.busy) ||
(status_.data_request != old_status.data_request) ||
(status_.interrupt_request != old_status.interrupt_request);
if(did_change) delegate_->wd1770_did_change_output(this);
if(did_change) delegate_->wd1770_did_change_output(*this);
} else updater(status_);
if(status_.busy != old_status.busy) update_clocking_observer();
+6 -4
View File
@@ -66,9 +66,8 @@ public:
/// @returns The current value of the DRQ line output.
inline bool get_data_request_line() const { return status_.data_request; }
class Delegate {
public:
virtual void wd1770_did_change_output(WD1770 *wd1770) = 0;
struct Delegate {
virtual void wd1770_did_change_output(WD1770 &) = 0;
};
inline void set_delegate(Delegate *delegate) { delegate_ = delegate; }
@@ -125,9 +124,12 @@ private:
};
void posit_event(int type);
int interesting_event_mask_;
int resume_point_ = 0;
Cycles::IntType delay_time_ = 0;
// Current state machine stap pointer.
static constexpr int IdleResumePoint = 0;
int resume_point_ = IdleResumePoint;
// ID buffer
uint8_t header_[6];
+24 -24
View File
@@ -11,7 +11,7 @@
#include "Outputs/Log.hpp"
namespace {
Log::Logger<Log::Source::NCR5380> logger;
using Logger = Log::Logger<Log::Source::NCR5380>;
}
// TODO:
//
@@ -25,7 +25,7 @@ NCR5380::NCR5380(SCSI::Bus &bus, const int clock_rate) :
bus_(bus),
clock_rate_(clock_rate) {
device_id_ = bus_.add_device();
bus_.add_observer(this);
bus_.add_observer(*this);
// TODO: use clock rate and expected phase. This implementation currently
// provides only CPU-driven polling behaviour.
@@ -36,7 +36,7 @@ NCR5380::NCR5380(SCSI::Bus &bus, const int clock_rate) :
void NCR5380::write(const int address, const uint8_t value, bool) {
switch(address & 7) {
case 0:
logger.info().append("[0] Set current SCSI bus state to %02x", value);
Logger::info().append("[0] Set current SCSI bus state to %02x", value);
data_bus_ = value;
if(dma_request_ && dma_operation_ == DMAOperation::Send) {
@@ -45,7 +45,7 @@ void NCR5380::write(const int address, const uint8_t value, bool) {
break;
case 1: {
logger.info().append("[1] Initiator command register set: %02x", value);
Logger::info().append("[1] Initiator command register set: %02x", value);
initiator_command_ = value;
bus_output_ &= ~(Line::Reset | Line::Acknowledge | Line::Busy | Line::SelectTarget | Line::Attention);
@@ -61,7 +61,7 @@ void NCR5380::write(const int address, const uint8_t value, bool) {
} break;
case 2:
logger.info().append("[2] Set mode: %02x", value);
Logger::info().append("[2] Set mode: %02x", value);
mode_ = value;
// bit 7: 1 = use block mode DMA mode (if DMA mode is also enabled)
@@ -102,27 +102,27 @@ void NCR5380::write(const int address, const uint8_t value, bool) {
break;
case 3:
logger.info().append("[3] Set target command: %02x", value);
Logger::info().append("[3] Set target command: %02x", value);
target_command_ = value;
update_control_output();
break;
case 4:
logger.info().append("[4] Set select enabled: %02x", value);
Logger::info().append("[4] Set select enabled: %02x", value);
break;
case 5:
logger.info().append("[5] Start DMA send: %02x", value);
Logger::info().append("[5] Start DMA send: %02x", value);
dma_operation_ = DMAOperation::Send;
break;
case 6:
logger.info().append("[6] Start DMA target receive: %02x", value);
Logger::info().append("[6] Start DMA target receive: %02x", value);
dma_operation_ = DMAOperation::TargetReceive;
break;
case 7:
logger.info().append("[7] Start DMA initiator receive: %02x", value);
Logger::info().append("[7] Start DMA initiator receive: %02x", value);
dma_operation_ = DMAOperation::InitiatorReceive;
break;
}
@@ -146,15 +146,15 @@ void NCR5380::write(const int address, const uint8_t value, bool) {
uint8_t NCR5380::read(const int address, bool) {
switch(address & 7) {
case 0:
logger.info().append("[0] Get current SCSI bus state: %02x", (bus_.get_state() & 0xff));
Logger::info().append("[0] Get current SCSI bus state: %02x", (bus_.state() & 0xff));
if(dma_request_ && dma_operation_ == DMAOperation::InitiatorReceive) {
return dma_acknowledge();
}
return uint8_t(bus_.get_state());
return uint8_t(bus_.state());
case 1:
logger.info().append(
Logger::info().append(
"[1] Initiator command register get: %c%c",
arbitration_in_progress_ ? 'p' : '-',
lost_arbitration_ ? 'l' : '-');
@@ -169,15 +169,15 @@ uint8_t NCR5380::read(const int address, bool) {
(lost_arbitration_ ? 0x20 : 0x00);
case 2:
logger.info().append("[2] Get mode");
Logger::info().append("[2] Get mode");
return mode_;
case 3:
logger.info().append("[3] Get target command");
Logger::info().append("[3] Get target command");
return target_command_;
case 4: {
const auto bus_state = bus_.get_state();
const auto bus_state = bus_.state();
const uint8_t result =
((bus_state & Line::Reset) ? 0x80 : 0x00) |
((bus_state & Line::Busy) ? 0x40 : 0x00) |
@@ -187,12 +187,12 @@ uint8_t NCR5380::read(const int address, bool) {
((bus_state & Line::Input) ? 0x04 : 0x00) |
((bus_state & Line::SelectTarget) ? 0x02 : 0x00) |
((bus_state & Line::Parity) ? 0x01 : 0x00);
logger.info().append("[4] Get current bus state: %02x", result);
Logger::info().append("[4] Get current bus state: %02x", result);
return result;
}
case 5: {
const auto bus_state = bus_.get_state();
const auto bus_state = bus_.state();
const uint8_t result =
(end_of_dma_ ? 0x80 : 0x00) |
((dma_request_ && state_ == ExecutionState::PerformingDMA) ? 0x40 : 0x00) |
@@ -202,16 +202,16 @@ uint8_t NCR5380::read(const int address, bool) {
/* b2 = busy error */
((bus_state & Line::Attention) ? 0x02 : 0x00) |
((bus_state & Line::Acknowledge) ? 0x01 : 0x00);
logger.info().append("[5] Get bus and status: %02x", result);
Logger::info().append("[5] Get bus and status: %02x", result);
return result;
}
case 6:
logger.info().append("[6] Get input data");
Logger::info().append("[6] Get input data");
return 0xff;
case 7:
logger.info().append("[7] Reset parity/interrupt");
Logger::info().append("[7] Reset parity/interrupt");
irq_ = false;
return 0xff;
}
@@ -242,7 +242,7 @@ void NCR5380::update_control_output() {
}
}
void NCR5380::scsi_bus_did_change(SCSI::Bus *, const SCSI::BusState new_state, const double time_since_change) {
void NCR5380::scsi_bus_did_change(SCSI::Bus &, const SCSI::BusState new_state, const double time_since_change) {
/*
When connected as an Initiator with DMA Mode True,
if the phase lines I//O, C//D, and /MSG do not match the
@@ -350,7 +350,7 @@ bool NCR5380::dma_request() {
}
uint8_t NCR5380::dma_acknowledge() {
const uint8_t bus_state = uint8_t(bus_.get_state());
const uint8_t bus_state = uint8_t(bus_.state());
dma_acknowledge_ = true;
dma_request_ = false;
@@ -370,7 +370,7 @@ void NCR5380::dma_acknowledge(const uint8_t value) {
}
bool NCR5380::phase_matches() const {
const auto bus_state = bus_.get_state();
const auto bus_state = bus_.state();
return
(target_output() & (Line::Message | Line::Control | Line::Input)) ==
(bus_state & (Line::Message | Line::Control | Line::Input));
+1 -1
View File
@@ -78,7 +78,7 @@ private:
SCSI::BusState target_output() const;
void update_control_output();
void scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double time_since_change) final;
void scsi_bus_did_change(SCSI::Bus &, SCSI::BusState, double) final;
bool phase_matches() const;
};
+3 -4
View File
@@ -60,10 +60,9 @@ public:
*/
class IRQDelegatePortHandler: public PortHandler {
public:
class Delegate {
public:
/// Indicates that the interrupt status has changed for the IRQDelegatePortHandler provided.
virtual void mos6522_did_change_interrupt_status(void *irq_delegate) = 0;
struct Delegate {
/// Indicates that the interrupt status has changed for the IRQDelegatePortHandler provided.
virtual void mos6522_did_change_interrupt_status(void *irq_delegate) = 0;
};
/// Sets the delegate that will receive notification of changes in the interrupt line.
@@ -23,7 +23,7 @@ template <typename T> void MOS6522<T>::access(const int address) {
}
break;
case 0xf:
// case 0xf:
case 0x1:
// In both handshake and pulse modes, CA2 goes low on any read or write of Port A.
if(handshake_modes_[0] != HandshakeMode::None) {
@@ -57,14 +57,17 @@ template <typename T> void MOS6522<T>::write(int address, const uint8_t value) {
bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
bus_handler_.template set_port_output<Port::A>(value, registers_.data_direction[0]);
if(handshake_modes_[1] != HandshakeMode::None) {
set_control_line_output<Port::A, Line::Two>(LineState::Off);
}
// Avoid handshaking if this was via address 0xf.
if(address == 0x1) {
if(handshake_modes_[1] != HandshakeMode::None) {
set_control_line_output<Port::A, Line::Two>(LineState::Off);
}
registers_.interrupt_flags &= ~(
InterruptFlag::CA1ActiveEdge |
((registers_.peripheral_control&0x02) ? 0 : InterruptFlag::CB2ActiveEdge)
);
registers_.interrupt_flags &= ~(
InterruptFlag::CA1ActiveEdge |
((registers_.peripheral_control&0x02) ? 0 : InterruptFlag::CB2ActiveEdge)
);
}
reevaluate_interrupts();
break;
@@ -144,10 +147,11 @@ template <typename T> void MOS6522<T>::write(int address, const uint8_t value) {
reevaluate_interrupts();
break;
case 0xe: // Interrupt enable register ('IER').
if(value&0x80)
if(value&0x80) {
registers_.interrupt_enable |= value;
else
} else {
registers_.interrupt_enable &= ~value;
}
reevaluate_interrupts();
break;
}
@@ -195,9 +199,10 @@ template <typename T> uint8_t MOS6522<T>::read(int address) {
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge);
reevaluate_interrupts();
return get_port_input<Port::B>(registers_.data_direction[1], registers_.output[1], registers_.auxiliary_control & 0x80);
case 0xf:
case 0x1: // Read Port A ('IRA').
case 0x1: // Read Port A ('IRA') [with handshaking].
registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | InterruptFlag::CA2ActiveEdge);
[[fallthrough]];
case 0xf: // Read Port A ('IRA') [without handshaking].
reevaluate_interrupts();
return get_port_input<Port::A>(registers_.data_direction[0], registers_.output[0], 0);
@@ -245,8 +250,10 @@ uint8_t MOS6522<T>::get_port_input(
) {
bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
const uint8_t input = bus_handler_.template get_port_input<port>();
output = (output & ~timer_mask) | (registers_.timer_port_b_output & timer_mask);
return (input & ~output_mask) | (output & output_mask);
// Force any timer-adjusted PB7 to be visible even if the pin is set as input.
output = (input & ~output_mask) | (output & output_mask);
return (output & ~timer_mask) | (registers_.timer_port_b_output & timer_mask);
}
template <typename T> T &MOS6522<T>::bus_handler() {
+1 -1
View File
@@ -83,7 +83,7 @@ private:
bool serial_line_did_produce_bit(Serial::Line<true> *line, int bit) final;
Log::Logger<Log::Source::MOS6526> log;
using Logger = Log::Logger<Log::Source::MOS6526>;
};
}
@@ -129,7 +129,7 @@ void MOS6526<BusHandlerT, personality>::write(int address, const uint8_t value)
// Shift control.
case 12:
log.error().append("TODO: write to shift register");
Logger::error().append("TODO: write to shift register");
break;
// Logically unreachable.
@@ -125,11 +125,11 @@ private:
uint32_t alarm_ = 0xff'ffff;
public:
template <int byte> void write(uint8_t v) {
template <int byte> void write(const uint8_t v) {
if constexpr (byte == 3) {
return;
}
constexpr int shift = byte << 3;
static 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
@@ -147,7 +147,7 @@ public:
if constexpr (byte == 3) {
return 0xff; // Assumed. Just a guess.
}
constexpr int shift = byte << 3;
static constexpr int shift = byte << 3;
if constexpr (byte == 2) {
latch_ = value_ | 0xff00'0000;
@@ -163,7 +163,7 @@ public:
return result;
}
bool advance(int count) {
bool advance(const 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;
@@ -221,8 +221,7 @@ struct MOS6526Storage {
control = v;
if(v&2) {
Log::Logger<Log::Source::MOS6526> log;
log.error().append("UNIMPLEMENTED: PB strobe");
Log::Logger<Log::Source::MOS6526>::error().append("UNIMPLEMENTED: PB strobe");
}
}
+23 -14
View File
@@ -12,10 +12,9 @@
using namespace MOS::MOS6560;
AudioGenerator::AudioGenerator(Concurrency::AsyncTaskQueue<false> &audio_queue) :
AudioGenerator::AudioGenerator(Outputs::Speaker::TaskQueue &audio_queue) :
audio_queue_(audio_queue) {}
void AudioGenerator::set_volume(const uint8_t volume) {
audio_queue_.enqueue([this, volume]() {
volume_ = int16_t(volume) * range_multiplier_;
@@ -97,19 +96,29 @@ constexpr uint8_t noise_pattern[] = {
0xf0, 0xe1, 0xe0, 0x78, 0x70, 0x38, 0x3c, 0x3e, 0x1e, 0x3c, 0x1e, 0x1c, 0x70, 0x3c, 0x38, 0x3f,
};
#define shift(r) shift_registers_[r] = \
(shift_registers_[r] << 1) | (((shift_registers_[r]^0x80)&control_registers_[r]) >> 7)
#define increment(r) shift_registers_[r] = (shift_registers_[r]+1)%8191
#define update(r, m, up) \
counters_[r]++; if((counters_[r] >> m) == 0x80) { up(r); counters_[r] = unsigned(control_registers_[r]&0x7f) << m; }
// Note on slightly askew test: as far as I can make out, if the value in the register is 0x7f then what's supposed to
// happen is that the 0x7f is loaded, on the next clocked cycle the Vic spots a 0x7f, pumps the output, reloads, etc. No
// increment ever occurs. It's conditional. I don't really want two conditionals if I can avoid it so I'm incrementing
// regardless and testing against 0x80. The effect should be the same: loading with 0x7f means an output update every
// cycle, loading with 0x7e means every second cycle, etc.
template <Outputs::Speaker::Action action>
void AudioGenerator::apply_samples(const std::size_t number_of_samples, Outputs::Speaker::MonoSample *const target) {
const auto shift = [&](const int r) {
shift_registers_[r] =
(shift_registers_[r] << 1) | (((shift_registers_[r] ^ 0x80) & control_registers_[r]) >> 7);
};
const auto increment = [&](const int r) {
shift_registers_[r] = (shift_registers_[r] + 1) % 8191;
};
const auto update = [&](const int r, const int m, auto &&up) {
++counters_[r];
if((counters_[r] >> m) == 0x80) {
up(r);
counters_[r] = unsigned(control_registers_[r]&0x7f) << m;
}
// Note on slightly askew test: as far as I can make out, if the value in the register is 0x7f then what's
// supposed to happen is that the 0x7f is loaded, on the next clocked cycle the Vic spots a 0x7f, pumps the
// output, reloads, etc. No increment ever occurs. It's conditional. I don't really want two conditionals if I
// can avoid it so I'm incrementing regardless and testing against 0x80. The effect should be the same: loading
// with 0x7f means an output update every cycle, loading with 0x7e means every second cycle, etc.
};
for(unsigned int c = 0; c < number_of_samples; ++c) {
update(0, 2, shift);
update(1, 1, shift);
@@ -138,7 +147,7 @@ template void AudioGenerator::apply_samples<Outputs::Speaker::Action::Store>(
template void AudioGenerator::apply_samples<Outputs::Speaker::Action::Ignore>(
std::size_t, Outputs::Speaker::MonoSample *);
void AudioGenerator::set_sample_volume_range(std::int16_t range) {
void AudioGenerator::set_sample_volume_range(const std::int16_t range) {
range_multiplier_ = int16_t(range / 64);
}
+14 -25
View File
@@ -19,7 +19,7 @@ namespace MOS::MOS6560 {
// audio state
class AudioGenerator: public Outputs::Speaker::BufferSource<AudioGenerator, false> {
public:
AudioGenerator(Concurrency::AsyncTaskQueue<false> &audio_queue);
AudioGenerator(Outputs::Speaker::TaskQueue &audio_queue);
void set_volume(uint8_t);
void set_control(int channel, uint8_t value);
@@ -30,7 +30,7 @@ public:
void set_sample_volume_range(std::int16_t);
private:
Concurrency::AsyncTaskQueue<false> &audio_queue_;
Outputs::Speaker::TaskQueue &audio_queue_;
unsigned int counters_[4] = {2, 1, 0, 0}; // create a slight phase offset for the three channels
unsigned int shift_registers_[4] = {0, 0, 0, 0};
@@ -64,8 +64,7 @@ public:
MOS6560(BusHandler &bus_handler) :
bus_handler_(bus_handler),
crt_(65*4, 1, Outputs::Display::Type::NTSC60, Outputs::Display::InputDataType::Luminance8Phase8),
audio_generator_(audio_queue_),
speaker_(audio_generator_)
audio_(Cycles(4))
{
// default to s-video output
crt_.set_display_type(Outputs::Display::DisplayType::SVideo);
@@ -75,11 +74,11 @@ public:
}
~MOS6560() {
audio_queue_.flush();
audio_.stop();
}
void set_clock_rate(const double clock_rate) {
speaker_.set_input_rate(float(clock_rate / 4.0));
audio_.speaker().set_input_rate(float(clock_rate / 4.0));
}
void set_scan_target(Outputs::Display::ScanTarget *const scan_target) {
@@ -95,11 +94,11 @@ public:
return crt_.get_display_type();
}
Outputs::Speaker::Speaker *get_speaker() {
return &speaker_;
return &audio_.speaker();
}
void set_high_frequency_cutoff(const float cutoff) {
speaker_.set_high_frequency_cutoff(cutoff);
audio_.speaker().set_high_frequency_cutoff(cutoff);
}
/*!
@@ -161,10 +160,10 @@ public:
switch(output_mode) {
case OutputMode::PAL:
crt_.set_visible_area(Outputs::Display::Rect(0.1f, 0.07f, 0.9f, 0.9f));
crt_.set_fixed_framing(Outputs::Display::Rect(0.1f, 0.07f, 0.9f, 0.9f));
break;
case OutputMode::NTSC:
crt_.set_visible_area(Outputs::Display::Rect(0.05f, 0.05f, 0.9f, 0.9f));
crt_.set_fixed_framing(Outputs::Display::Rect(0.05f, 0.05f, 0.9f, 0.9f));
break;
}
@@ -180,7 +179,7 @@ public:
*/
inline void run_for(const Cycles cycles) {
// keep track of the amount of time since the speaker was updated; lazy updates are applied
cycles_since_speaker_update_ += cycles;
audio_ += cycles;
auto number_of_cycles = cycles.as_integral();
while(number_of_cycles--) {
@@ -377,8 +376,7 @@ public:
Causes the 6560 to flush as much pending CRT and speaker communications as possible.
*/
inline void flush() {
update_audio();
audio_queue_.perform();
audio_.perform();
}
/*!
@@ -420,14 +418,12 @@ public:
case 0xb:
case 0xc:
case 0xd:
update_audio();
audio_generator_.set_control(address - 0xa, value);
audio_->set_control(address - 0xa, value);
break;
case 0xe:
update_audio();
registers_.auxiliary_colour = colours_[value >> 4];
audio_generator_.set_volume(value & 0xf);
audio_->set_volume(value & 0xf);
break;
case 0xf: {
@@ -467,14 +463,7 @@ private:
BusHandler &bus_handler_;
Outputs::CRT::CRT crt_;
Concurrency::AsyncTaskQueue<false> audio_queue_;
AudioGenerator audio_generator_;
Outputs::Speaker::PullLowpass<AudioGenerator> speaker_;
Cycles cycles_since_speaker_update_;
void update_audio() {
speaker_.run_for(audio_queue_, Cycles(cycles_since_speaker_update_.divide(Cycles(4))));
}
Outputs::Speaker::PullLowpassSpeakerQueue<Cycles, AudioGenerator> audio_;
// register state
struct {
+417 -258
View File
@@ -9,28 +9,43 @@
#pragma once
#include "ClockReceiver/ClockReceiver.hpp"
#include "Numeric/SizedInt.hpp"
#include <cstdint>
#include <cstdio>
//
// WARNING: code is in flux. I'm attempting to use hoglet's FPGA implementation at
// https://github.com/hoglet67/BeebFpga/blob/master/src/common/mc6845.vhd as an authoritative guide to proper behaviour,
// having found his Electron ULA to be excellent. This is starting by mapping various bits of internal state here
// to hoglet's equivalents; cf. comments.
//
namespace Motorola::CRTC {
using RefreshAddress = Numeric::SizedInt<14>;
using LineAddress = Numeric::SizedInt<5>;
using SyncCounter = Numeric::SizedInt<4>;
using CharacterAddress = Numeric::SizedInt<8>;
using RowAddress = Numeric::SizedInt<7>;
struct BusState {
bool display_enable = false;
bool hsync = false;
bool vsync = false;
bool hsync = false; // hs
bool vsync = false; // vs
bool cursor = false;
uint16_t refresh_address = 0;
uint16_t row_address = 0;
RefreshAddress refresh;
LineAddress line;
// Not strictly part of the bus state; provided because the partition between 6845 and bus handler
// doesn't quite hold up in some emulated systems where the two are integrated and share more state.
int field_count = 0;
Numeric::SizedInt<5> field_count = 0; // field_counter
};
class BusHandler {
public:
void perform_bus_cycle(const BusState &) {}
public:
void perform_bus_cycle(const BusState &) {}
};
enum class Personality {
@@ -39,14 +54,8 @@ enum class Personality {
UM6845R, // Type 1 in CPC parlance. Status register, fixed-length VSYNC.
MC6845, // Type 2. No status register, fixed-length VSYNC, no zero-length HSYNC.
AMS40226, // Type 3. Status is get register, fixed-length VSYNC, no zero-length HSYNC.
EGA, // Extended EGA-style CRTC; uses 16-bit addressing throughout.
};
constexpr bool is_egavga(const Personality p) {
return p >= Personality::EGA;
}
// https://www.pcjs.org/blog/2018/03/20/ advises that "the behavior of bits 5 and 6 [of register 10, the cursor start
// register is really card specific".
//
@@ -54,10 +63,10 @@ constexpr bool is_egavga(const Personality p) {
enum class CursorType {
/// No cursor signal is generated.
None,
/// Built-in 6845 style: 00 => no blinking; 01 => no cursor; 10 => slow blink; 11 => fast blink
Native,
/// MDA style: 00 => symmetric blinking; 01 or 10 => no blinking; 11 => short on, long off.
MDA,
/// EGA style: ignore the bits completely.
EGA,
};
// TODO UM6845R and R12/R13; see http://www.cpcwiki.eu/index.php/CRTC#CRTC_Differences
@@ -67,7 +76,7 @@ public:
CRTC6845(BusHandlerT &bus_handler) noexcept :
bus_handler_(bus_handler), status_(0) {}
void select_register(uint8_t r) {
void select_register(const uint8_t r) {
selected_register_ = r;
}
@@ -85,65 +94,64 @@ public:
if(selected_register_ == 16 || selected_register_ == 17) status_ &= ~0x40;
if(personality == Personality::UM6845R && selected_register_ == 31) return dummy_register_;
if(selected_register_ < 12 || selected_register_ > 17) return 0xff;
return registers_[selected_register_];
// Registers below 12 are write-only; no registers are defined above position 17
// (other than the UM6845R-specific test register as per above).
//
// Per the BBC Wiki, attempting to read such a register results in 0.
if(selected_register_ < 12 || selected_register_ > 17) return 0x00;
return registers_[selected_register_.get()];
}
void set_register(const uint8_t value) {
static constexpr bool is_ega = is_egavga(personality);
const auto load_low = [value](uint16_t &target) {
target = (target & 0xff00) | value;
};
const auto load_high = [value](uint16_t &target) {
constexpr uint8_t mask = RefreshMask >> 8;
target = uint16_t((target & 0x00ff) | ((value & mask) << 8));
};
switch(selected_register_) {
switch(selected_register_.get()) {
case 0: layout_.horizontal.total = value; break;
case 1: layout_.horizontal.displayed = value; break;
case 2: layout_.horizontal.start_sync = value; break;
case 3:
layout_.horizontal.sync_width = value & 0xf;
layout_.horizontal.sync_width = value;
layout_.vertical.sync_lines = value >> 4;
// TODO: vertical sync lines:
// "(0 means 16 on some CRTC. Not present on all CRTCs, fixed to 16 lines on these)"
break;
case 4: layout_.vertical.total = value & 0x7f; break;
case 5: layout_.vertical.adjust = value & 0x1f; break;
case 6: layout_.vertical.displayed = value & 0x7f; break;
case 7: layout_.vertical.start_sync = value & 0x7f; break;
case 4: layout_.vertical.total = value; break;
case 5: layout_.vertical.adjust = value; break;
case 6: layout_.vertical.displayed = value; break;
case 7: layout_.vertical.start_sync = value; break;
case 8:
switch(value & 3) {
default: layout_.interlace_mode_ = InterlaceMode::Off; break;
case 0b01: layout_.interlace_mode_ = InterlaceMode::InterlaceSync; break;
case 0b11: layout_.interlace_mode_ = InterlaceMode::InterlaceSyncAndVideo; break;
default: layout_.interlace_mode_ = InterlaceMode::Off; break;
case 0b01: layout_.interlace_mode_ = InterlaceMode::Sync; break;
case 0b11: layout_.interlace_mode_ = InterlaceMode::SyncAndVideo; break;
}
// Per CPC documentation, skew doesn't work on a "type 1 or 2", i.e. an MC6845 or a UM6845R.
if(personality != Personality::UM6845R && personality != Personality::MC6845) {
switch((value >> 4)&3) {
default: display_skew_mask_ = 1; break;
case 1: display_skew_mask_ = 2; break;
case 2: display_skew_mask_ = 4; break;
}
}
// if(personality != Personality::UM6845R && personality != Personality::MC6845) {
// switch((value >> 4)&3) {
// default: display_skew_mask_ = 1; break;
// case 1: display_skew_mask_ = 2; break;
// case 2: display_skew_mask_ = 4; break;
// }
// }
break;
case 9: layout_.vertical.end_row = value & 0x1f; break;
case 9: layout_.vertical.end_line = value; break;
case 10:
layout_.vertical.start_cursor = value & 0x1f;
layout_.cursor_flags = (value >> 5) & 3;
layout_.vertical.start_cursor = value;
layout_.cursor_flags = value >> 5;
update_cursor_mask();
break;
case 11:
layout_.vertical.end_cursor = value & 0x1f;
layout_.vertical.end_cursor = value;
break;
case 12: load_high(layout_.start_address); break;
case 13: load_low(layout_.start_address); break;
case 14: load_high(layout_.cursor_address); break;
case 15: load_low(layout_.cursor_address); break;
case 12: layout_.start_address.template load<8>(value); break;
case 13: layout_.start_address.template load<0>(value); break;
case 14: layout_.cursor_address.template load<8>(value); break;
case 15: layout_.cursor_address.template load<0>(value); break;
}
// Take redundant copies of all registers, limited to their actual bit sizes,
// to proffer up if the registers are read.
static constexpr uint8_t masks[] = {
0xff, // Horizontal total.
0xff, // Horizontal display end.
@@ -152,24 +160,24 @@ public:
// EGA: b0b4: end of horizontal blank;
// b5b6: "Number of character clocks to delay start of display after Horizontal Total has been reached."
is_ega ? 0xff : 0x7f, // Start horizontal retrace.
0x7f, // Start horizontal retrace.
0x1f, 0x7f, 0x7f,
0xff, 0x1f, 0x7f, 0x1f,
uint8_t(RefreshMask >> 8), uint8_t(RefreshMask),
uint8_t(RefreshMask >> 8), uint8_t(RefreshMask),
0xfc, 0x1f, 0x7f, 0x1f,
uint8_t(RefreshAddress::Mask >> 8), uint8_t(RefreshAddress::Mask),
uint8_t(RefreshAddress::Mask >> 8), uint8_t(RefreshAddress::Mask),
};
if(selected_register_ < 16) {
registers_[selected_register_] = value & masks[selected_register_];
registers_[selected_register_.get()] = value & masks[selected_register_.get()];
}
if(selected_register_ == 31 && personality == Personality::UM6845R) {
if(selected_register_.get() == 31 && personality == Personality::UM6845R) {
dummy_register_ = value;
}
}
void trigger_light_pen() {
registers_[17] = bus_state_.refresh_address & 0xff;
registers_[16] = bus_state_.refresh_address >> 8;
registers_[17] = bus_state_.refresh.get() & 0xff;
registers_[16] = bus_state_.refresh.get() >> 8;
status_ |= 0x40;
}
@@ -180,179 +188,291 @@ public:
// ordered so that whatever assignments result don't affect any subsequent conditionals
// Do bus work.
bus_state_.cursor = is_cursor_line_ &&
bus_state_.refresh_address == layout_.cursor_address;
bus_state_.display_enable = character_is_visible_ && line_is_visible_;
bus_handler_.perform_bus_cycle(bus_state_);
//
// External bus activity.
//
bus_state_.line = line_is_interlaced_ ? (line_ & LineAddress::IntT(~1)) | (odd_field_ ? 1 : 0) : line_;
bus_state_.display_enable = character_is_visible_ && row_is_visible_;
bus_state_.cursor = (cursor_mask_ && is_cursor_line_ && bus_state_.refresh == layout_.cursor_address)
&& bus_state_.display_enable;
bus_handler_.perform_bus_cycle(bus_state_);
bus_state_.refresh = refresh_; // Deliberate: do this after bus activity.
// TODO: is this a hack?
//
// Shared, stateless signals.
// Shared signals.
//
const bool character_total_hit = character_counter_ == layout_.horizontal.total;
const uint8_t lines_per_row =
layout_.interlace_mode_ == InterlaceMode::InterlaceSyncAndVideo ?
layout_.vertical.end_row & ~1 : layout_.vertical.end_row;
const bool row_end_hit = bus_state_.row_address == lines_per_row && !is_in_adjustment_period_;
const bool was_eof = eof_latched_;
const bool character_total_hit = character_counter_ == layout_.horizontal.total; // r00_h_total_hit
const auto lines_per_row =
layout_.interlace_mode_ == InterlaceMode::SyncAndVideo ?
layout_.vertical.end_line & LineAddress::IntT(~1) : layout_.vertical.end_line; // max_scanline
const bool line_end_hit = line_ == lines_per_row && !is_in_adjustment_period_; // max_scanline_hit
const bool new_frame =
character_total_hit && was_eof &&
character_total_hit && eof_latched_ &&
(
layout_.interlace_mode_ == InterlaceMode::Off ||
!odd_field_
);
!bus_state_.field_count.bit<0>() ||
extra_line_
); // new_frame
//
// Horizontal.
//
// Update horizontal sync.
if(bus_state_.hsync) {
++hsync_counter_;
bus_state_.hsync = hsync_counter_ != layout_.horizontal.sync_width;
}
if(character_counter_ == layout_.horizontal.start_sync) {
hsync_counter_ = 0;
bus_state_.hsync = true;
}
// Check for end-of-line.
character_reset_history_ <<= 1;
if(character_total_hit) {
character_counter_ = 0;
character_is_visible_ = true;
character_reset_history_ |= 1;
} else {
character_counter_++;
}
// Check for end of visible characters.
if(character_counter_ == layout_.horizontal.displayed) {
character_is_visible_ = false;
}
//
// End-of-frame.
//
if(character_total_hit) {
if(was_eof) {
eof_latched_ = eom_latched_ = is_in_adjustment_period_ = false;
adjustment_counter_ = 0;
} else if(is_in_adjustment_period_) {
adjustment_counter_ = (adjustment_counter_ + 1) & 31;
}
}
if(character_reset_history_ & 2) {
eom_latched_ |= row_end_hit && row_counter_ == layout_.vertical.total;
}
if(character_reset_history_ & 4 && eom_latched_) {
// TODO: I don't believe the "add 1 for interlaced" test here is accurate;
// others represent the extra scanline as additional state, presumably because
// adjust total might be reprogrammed at any time.
const auto adjust_length =
layout_.vertical.adjust + (layout_.interlace_mode_ != InterlaceMode::Off && odd_field_ ? 1 : 0);
is_in_adjustment_period_ |= adjustment_counter_ != adjust_length;
eof_latched_ |= adjustment_counter_ == adjust_length;
}
//
// Vertical.
//
// Sync.
const bool vsync_horizontal =
(!odd_field_ && !character_counter_) ||
(odd_field_ && character_counter_ == (layout_.horizontal.total >> 1));
if(vsync_horizontal) {
if((row_counter_ == layout_.vertical.start_sync && !bus_state_.row_address) || bus_state_.vsync) {
bus_state_.vsync = true;
vsync_counter_ = (vsync_counter_ + 1) & 0xf;
} else {
vsync_counter_ = 0;
}
if(vsync_counter_ == layout_.vertical.sync_lines) {
bus_state_.vsync = false;
}
}
// Row address.
if(character_total_hit) {
if(was_eof) {
bus_state_.row_address = 0;
eof_latched_ = eom_latched_ = false;
} else if(row_end_hit) {
bus_state_.row_address = 0;
} else if(layout_.interlace_mode_ == InterlaceMode::InterlaceSyncAndVideo) {
bus_state_.row_address = (bus_state_.row_address + 2) & ~1 & 31;
} else {
bus_state_.row_address = (bus_state_.row_address + 1) & 31;
}
}
// Row counter.
row_counter_ = next_row_counter_;
if(new_frame) {
next_row_counter_ = 0;
is_first_scanline_ = true;
} else {
next_row_counter_ = row_end_hit && character_total_hit ?
(next_row_counter_ + 1) : next_row_counter_;
is_first_scanline_ &= !row_end_hit;
}
// Vertical display enable.
if(is_first_scanline_) {
line_is_visible_ = true;
odd_field_ = bus_state_.field_count & 1;
} else if(line_is_visible_ && row_counter_ == layout_.vertical.displayed) {
line_is_visible_ = false;
++bus_state_.field_count;
}
// Cursor.
if constexpr (cursor_type != CursorType::None) {
// Check for cursor enable.
is_cursor_line_ |= bus_state_.row_address == layout_.vertical.start_cursor;
is_cursor_line_ &= bus_state_.row_address != layout_.vertical.end_cursor;
switch(cursor_type) {
// MDA-style blinking.
// https://retrocomputing.stackexchange.com/questions/27803/what-are-the-blinking-rates-of-the-caret-and-of-blinking-text-on-pc-graphics-car
// gives an 8/8 pattern for regular blinking though mode 11 is then just a guess.
case CursorType::MDA:
switch(layout_.cursor_flags) {
case 0b11: is_cursor_line_ &= (bus_state_.field_count & 8) < 3; break;
case 0b00: is_cursor_line_ &= bool(bus_state_.field_count & 8); break;
case 0b01: is_cursor_line_ = false; break;
case 0b10: is_cursor_line_ = true; break;
default: break;
}
break;
}
}
//
// Addressing.
//
// Start-of-line address: seeded with the programmed display start address upon a new frame;
// otherwise copied from the refresh address at the end of each line of characters.
const auto initial_line_address = line_address_;
if(new_frame) {
bus_state_.refresh_address = layout_.start_address;
line_address_ = layout_.start_address;
} else if(character_counter_ == layout_.horizontal.displayed && line_end_hit) {
line_address_ = refresh_;
}
// Refresh address: seeded with the programmed display start address upon a new frame;
// otherwise copied from the start-of-line address is a new line is about to start;
// otherwise incremented across the line.
if(new_frame) {
refresh_ = layout_.start_address;
} else if(character_total_hit) {
bus_state_.refresh_address = line_address_;
refresh_ = initial_line_address;
} else {
bus_state_.refresh_address = (bus_state_.refresh_address + 1) & RefreshMask;
++refresh_;
}
//
// Per hoglet: b0 does not vary within a line even if you switch in/out of interlaced mode.
// He reproduces the same with extra state, which probably doesn't exist on the real device.
// This implementation follows his lead.
//
if(character_total_hit) {
line_is_interlaced_ = layout_.interlace_mode_ == InterlaceMode::SyncAndVideo;
}
//
// Sync.
//
// Vertical sync.
//
// Counter:
// Sync width of 0 => 16 lines of sync.
// Triggered by the row counter becoming equal to the sync start position, regardless of when.
// Subsequently increments at the start of each line.
const bool hit_vsync = row_counter_ == layout_.vertical.start_sync; // vs_hit
const bool is_vsync_rising_edge = hit_vsync && !hit_vsync_last_;
hit_vsync_last_ = hit_vsync;
// Select odd or even sync depending on the field.
// (Noted: the reverse-odd-test is intentional)
bus_state_.vsync = (layout_.interlace_mode_ != InterlaceMode::Off && !odd_field_) ?
vsync_odd_ : vsync_even_;
// Odd sync copies even sync, but half a line later.
if(character_counter_ == layout_.horizontal.total >> 1) {
vsync_odd_ = vsync_even_;
}
// Even sync begins on the rising edge of vsync, then continues until the counter hits its proper
// target, one cycle after reset of the horizontal counter.
if(is_vsync_rising_edge) {
vsync_even_ = true;
} else if(vsync_counter_ == layout_.vertical.sync_lines && character_reset_history_.bit<0>()) {
vsync_even_ = false;
}
// The vsync counter is zeroed by the rising edge of sync but subsequently increments immediately
// upon reset of the horizontal counter.
if(is_vsync_rising_edge) {
vsync_counter_ = 0;
} else if(character_total_hit) {
++vsync_counter_;
}
// Horizontal sync.
//
// A sync width of 0 should mean that no sync is observed.
// Hitting the start sync condition while sync is already ongoing should have no effect.
if(bus_state_.hsync) {
++hsync_counter_;
} else {
hsync_counter_ = 0;
}
if(hsync_counter_ == layout_.horizontal.sync_width) {
bus_state_.hsync = false;
} else if(character_counter_ == layout_.horizontal.start_sync) {
bus_state_.hsync = true;
}
//
// Horizontal.
//
// Check for visible characters; visibility starts in the first column and continues
if(!character_counter_) {
character_is_visible_ = true;
}
if(character_counter_ == layout_.horizontal.displayed || character_total_hit) {
character_is_visible_ = false;
}
// Check for end-of-line.
//
// character_reset_history_ is used because some events are defined to occur one or two
// cycles after end-of-line regardless of whether an additional end of line is hit in
// the interim.
if(character_total_hit) {
character_counter_ = 0;
} else {
++character_counter_;
}
//
// Vertical.
//
// Update line counter (which also counts the vertical adjust period).
//
// Counts in steps of 2 only if & 3) mode is InterlaceMode::SyncAndVideo and this is
// not the adjustment period. Otherwise counts in steps of 1.
if(new_frame) {
line_ = 0;
} else if(character_total_hit) {
line_ = next_line_;
}
if(line_end_hit) {
next_line_ = 0;
} else if(is_in_adjustment_period_ || layout_.interlace_mode_ != InterlaceMode::SyncAndVideo) {
next_line_ = line_ + 1;
} else {
next_line_ = (line_ + 2) & LineAddress::IntT(~1);
}
// Update row counter.
//
// Very straightforward: tests at end of line whether row end has also been hit. If so, increments.
row_counter_ = next_row_counter_;
if(new_frame) {
next_row_counter_ = 0;
} else if(character_total_hit && line_end_hit) {
next_row_counter_ = row_counter_ + 1;
}
// Vertical display enable.
if(is_first_scanline_) {
row_is_visible_ = true;
odd_field_ = bus_state_.field_count.bit<0>();
} else if(row_is_visible_ && row_counter_ == layout_.vertical.displayed) {
row_is_visible_ = false;
++bus_state_.field_count;
update_cursor_mask();
}
//
// End-of-frame.
//
if(new_frame) {
is_in_adjustment_period_ = false;
} else if(character_total_hit && eom_latched_ && will_adjust_) {
is_in_adjustment_period_ = true;
}
if(new_frame) {
line_address_ = layout_.start_address;
} else if(character_counter_ == layout_.horizontal.displayed && row_end_hit) {
line_address_ = bus_state_.refresh_address;
is_first_scanline_ = true;
} else if(character_total_hit) {
is_first_scanline_ = false;
}
// The extra-line flag holds true for a single line if one is needed to complete
// an odd interlaced field.
if(
character_total_hit &&
eof_latched_ &&
layout_.interlace_mode_ != InterlaceMode::Off &&
bus_state_.field_count.bit<0>() &&
!extra_line_
) {
extra_line_ = true;
} else if(character_total_hit) {
extra_line_ = false;
}
// EOF (end of field) marks the end of the regular set of scans, including the adjustment area.
// It doesn't include the extra line added during odd interlaced fields.
if(new_frame) {
eof_latched_ = false;
} else if(eom_latched_ && !will_adjust_ && character_reset_history_.bit<2>()) {
eof_latched_ = true;
}
// Will-adjust indicates whether an adjustment area is upcoming; if so then it occurs after EOM.
if(new_frame) {
will_adjust_ = false;
} else if(character_reset_history_.bit<1>() && eom_latched_) {
if(next_line_ == layout_.vertical.adjust) {
will_adjust_ = false;
} else {
will_adjust_ = true;
}
}
// EOM (end of main) marks the end of the visible set of rows, prior to any adjustment area.
// It is set one cycle after the most-recent start of line.
if(new_frame) {
eom_latched_ = false;
} else if(character_reset_history_.bit<0>() && line_end_hit && row_counter_ == layout_.vertical.total) {
eom_latched_ = true;
}
//
// Cursor
//
cursor_history_ <<= 1;
if constexpr (cursor_type != CursorType::None) {
if(character_total_hit) {
// This is clearly a nonsense test; there's absolutely no reason a real 6845 would do anything
// other than equality comparisons, to maintain internal state.
//
// ... that said, I have been unable to reconcile:
//
// 1. the PCjs results on real MC6845Ps that show wraparound cursors
// Cf. https://www.pcjs.org/blog/2018/03/20/ ; and
// 2. the expectations of the BBC Micro (which sets an out-of-range stop line for its cursor
// right at initial boot) and various pieces of its software (including but
// not limited to Arcadians, which uses in-range numbers but has start > end and expects
// the cursor correspondingly to be hidden).
//
// I also note that the two BBC FPGA implementations I glanced at, hoglet's and Mister's, use
// fictional range comparisons.
//
// But, on the other hand, Tom Seddon remarks at https://github.com/tom-seddon/6845-tests that
// "Looks like the cursor switches on when cursor is off and raster matches R10, and switches
// off when cursor is on and raster matches R11."
//
// (but also seems to use a range test in his software implementation?)
is_cursor_line_ =
line_ >= layout_.vertical.start_cursor &&
line_ <= layout_.vertical.end_cursor;
}
}
//
// Event history.
//
// Somewhat of a fiction, this keeps a track of recent character resets because
// some events are keyed on 1 cycle after last reset, 2 cycles after last reset, etc.
character_reset_history_ <<= 1;
character_reset_history_ |= character_total_hit;
}
}
@@ -361,78 +481,117 @@ public:
}
private:
static constexpr uint16_t RefreshMask = (personality >= Personality::EGA) ? 0xffff : 0x3fff;
BusHandlerT &bus_handler_;
BusState bus_state_;
enum class InterlaceMode {
/// No interlacing.
Off,
InterlaceSync,
InterlaceSyncAndVideo,
};
enum class BlinkMode {
// TODO.
/// Provide interlaced sync, but just scan out the exact same display for each field.
Sync,
/// Provide interlaced sync and scan even/odd lines depending on field.
SyncAndVideo,
};
// Comments on the right provide the corresponding signal name in hoglet's VHDL implementation.
struct {
struct {
uint8_t total;
uint8_t displayed;
uint8_t start_sync;
uint8_t sync_width;
CharacterAddress total; // r00_h_total
CharacterAddress displayed; // r01_h_displayed
CharacterAddress start_sync; // r02_h_sync_pos
SyncCounter sync_width; // r03_h_sync_width
} horizontal;
struct {
uint8_t total;
uint8_t displayed;
uint8_t start_sync;
uint8_t sync_lines;
uint8_t adjust;
RowAddress total; // r04_v_total
RowAddress displayed; // r06_v_displayed
RowAddress start_sync; // r07_v_sync_pos
SyncCounter sync_lines; // r03_v_sync_width
LineAddress adjust; // r05_v_total_adj
uint8_t end_row;
uint8_t start_cursor;
uint8_t end_cursor;
LineAddress end_line; // r09_max_scanline_addr
LineAddress start_cursor; // r10_cursor_start
LineAddress end_cursor; // r11_cursor_end
} vertical;
InterlaceMode interlace_mode_ = InterlaceMode::Off;
uint8_t end_row() const {
return interlace_mode_ == InterlaceMode::InterlaceSyncAndVideo ? vertical.end_row & ~1 : vertical.end_row;
}
InterlaceMode interlace_mode_ = InterlaceMode::Off; // r08_interlace
uint16_t start_address;
uint16_t cursor_address;
uint16_t light_pen_address;
uint8_t cursor_flags;
RefreshAddress start_address; // r12_start_addr_h + r13_start_addr_l
RefreshAddress cursor_address; // r14_cursor_h + r15_cursor_l
RefreshAddress light_pen_address; // r16_light_pen_h + r17_light_pen_l
Numeric::SizedInt<2> cursor_flags; // r10_cursor_mode
} layout_;
uint8_t registers_[18]{};
uint8_t dummy_register_ = 0;
int selected_register_ = 0;
Numeric::SizedInt<5> selected_register_ = 0;
uint8_t character_counter_ = 0;
uint32_t character_reset_history_ = 0;
uint8_t row_counter_ = 0, next_row_counter_ = 0;
uint8_t adjustment_counter_ = 0;
CharacterAddress character_counter_; // h_counter
Numeric::SizedInt<3> character_reset_history_; // sol
RowAddress row_counter_; // row_counter
RowAddress next_row_counter_; // row_counter_next
LineAddress line_; // line_counter
LineAddress next_line_; // line_counter_next
RefreshAddress refresh_; // ma_i
bool character_is_visible_ = false;
bool line_is_visible_ = false;
bool character_is_visible_ = false; // h_display
bool row_is_visible_ = false; // v_display
bool is_first_scanline_ = false;
bool is_cursor_line_ = false;
bool cursor_mask_ = false;
int hsync_counter_ = 0;
int vsync_counter_ = 0;
bool is_in_adjustment_period_ = false;
SyncCounter hsync_counter_; // h_sync_counter
SyncCounter vsync_counter_; // v_sync_counter
bool will_adjust_ = false; // in_adj
bool is_in_adjustment_period_ = false; // adj_in_progress
uint16_t line_address_ = 0;
RefreshAddress line_address_; // ma_row
uint8_t status_ = 0;
int display_skew_mask_ = 1;
unsigned int character_is_visible_shifter_ = 0;
// int display_skew_mask_ = 1;
bool eof_latched_ = false;
bool eom_latched_ = false;
uint16_t next_row_address_ = 0;
bool odd_field_ = false;
bool eof_latched_ = false; // eof_latched
bool eom_latched_ = false; // eom_latched
bool odd_field_ = false; // odd_field
bool extra_line_ = false; // extra_scanline
bool hit_vsync_last_ = false; // vs_hit_last
bool vsync_even_ = false; // vs_even
bool vsync_odd_ = false; // vs_odd
Numeric::SizedInt<3> cursor_history_; // cursor0, cursor1, cursor2 [TODO]
bool line_is_interlaced_ = false;
void update_cursor_mask() {
switch(cursor_type) {
case CursorType::None:
break;
// MDA-style blinking.
// https://retrocomputing.stackexchange.com/questions/27803/what-are-the-blinking-rates-of-the-caret-and-of-blinking-text-on-pc-graphics-car
// gives an 8/8 pattern for regular blinking though mode 11 is then just a guess.
case CursorType::MDA:
switch(layout_.cursor_flags.get()) {
case 0b11: cursor_mask_ = (bus_state_.field_count & 8) < 3; break;
case 0b00: cursor_mask_ = bus_state_.field_count.bit<3>(); break;
case 0b01: cursor_mask_ = false; break;
case 0b10: cursor_mask_ = true; break;
default: break;
}
break;
// Standard built-in 6845 blinking.
case CursorType::Native:
switch(layout_.cursor_flags.get()) {
case 0b00: cursor_mask_ = true; break;
case 0b01: cursor_mask_ = false; break;
case 0b10: cursor_mask_ = bus_state_.field_count.bit<3>(); break;
case 0b11: cursor_mask_= bus_state_.field_count.bit<4>(); break;
default: break;
}
break;
}
}
};
}
+3 -5
View File
@@ -8,6 +8,7 @@
#include "6850.hpp"
#include <bit>
#include <cassert>
using namespace Motorola::ACIA;
@@ -141,11 +142,8 @@ int ACIA::expected_bits() {
return 1 + data_bits_ + stop_bits_ + (parity_ != Parity::None);
}
uint8_t ACIA::parity(uint8_t value) {
value ^= value >> 4;
value ^= value >> 2;
value ^= value >> 1;
return value ^ (parity_ == Parity::Even);
uint8_t ACIA::parity(const uint8_t value) {
return (std::popcount(value) & 1) ^ (parity_ == Parity::Even);
}
bool ACIA::serial_line_did_produce_bit(Serial::Line<false> *, const int bit) {
+14 -16
View File
@@ -14,9 +14,7 @@
#include "Outputs/Log.hpp"
namespace {
Log::Logger<Log::Source::MFP68901> logger;
using Logger = Log::Logger<Log::Source::MFP68901>;
}
using namespace Motorola::MFP68901;
@@ -65,11 +63,11 @@ uint8_t MFP68901::read(int address) {
case 0x11: case 0x12: return get_timer_data(address - 0xf);
// USART block: TODO.
case 0x13: logger.error().append("Read: sync character generator"); break;
case 0x14: logger.error().append("Read: USART control"); break;
case 0x15: logger.error().append("Read: receiver status"); break;
case 0x16: logger.error().append("Read: transmitter status"); break;
case 0x17: logger.error().append("Read: USART data"); break;
case 0x13: Logger::error().append("Read: sync character generator"); break;
case 0x14: Logger::error().append("Read: USART control"); break;
case 0x15: Logger::error().append("Read: receiver status"); break;
case 0x16: Logger::error().append("Read: transmitter status"); break;
case 0x17: Logger::error().append("Read: USART data"); break;
}
return 0x00;
}
@@ -114,7 +112,7 @@ void MFP68901::write(int address, const uint8_t value) {
return;
}
constexpr int timer_prescales[] = {
static constexpr int timer_prescales[] = {
1, 4, 10, 16, 50, 64, 100, 200
};
@@ -170,11 +168,11 @@ void MFP68901::write(int address, const uint8_t value) {
break;
// USART block: TODO.
case 0x13: logger.error().append("Write: sync character generator"); break;
case 0x14: logger.error().append("Write: USART control"); break;
case 0x15: logger.error().append("Write: receiver status"); break;
case 0x16: logger.error().append("Write: transmitter status"); break;
case 0x17: logger.error().append("Write: USART data"); break;
case 0x13: Logger::error().append("Write: sync character generator"); break;
case 0x14: Logger::error().append("Write: USART control"); break;
case 0x15: Logger::error().append("Write: receiver status"); break;
case 0x16: Logger::error().append("Write: transmitter status"); break;
case 0x17: Logger::error().append("Write: USART data"); break;
}
update_clocking_observer();
@@ -221,7 +219,7 @@ HalfCycles MFP68901::next_sequence_point() {
// MARK: - Timers
void MFP68901::set_timer_mode(const int timer, const TimerMode mode, const int prescale, const bool reset_timer) {
logger.error().append("Timer %d mode set: %d; prescale: %d", timer, mode, prescale);
Logger::error().append("Timer %d mode set: %d; prescale: %d", timer, mode, prescale);
timers_[timer].mode = mode;
if(reset_timer) {
timers_[timer].prescale_count = 0;
@@ -399,7 +397,7 @@ int MFP68901::acknowledge_interrupt() {
int selected = 0;
while((1 << selected) != mask) ++selected;
// logger.error().append("Interrupt acknowledged: %d", selected);
// Logger::error().append("Interrupt acknowledged: %d", selected);
return (interrupt_vector_ & 0xf0) | uint8_t(selected);
}
+2 -2
View File
@@ -16,8 +16,8 @@
namespace Motorola::MFP68901 {
class PortHandler {
public:
// TODO: announce changes in output.
public:
// TODO: announce changes in output.
};
/*!
+5 -5
View File
@@ -91,12 +91,12 @@ public:
void set(const MainStatus flag, const bool value) {
set(uint8_t(flag), value, main_status_);
}
void start_seek(const int drive) { main_status_ |= 1 << drive; }
void set(const Status0 flag) { set(uint8_t(flag), true, status_[0]); }
void set(const Status1 flag) { set(uint8_t(flag), true, status_[1]); }
void set(const Status2 flag) { set(uint8_t(flag), true, status_[2]); }
void start_seek(const int drive) { main_status_ |= 1 << drive; }
void set(const Status0 flag) { set(uint8_t(flag), true, status_[0]); }
void set(const Status1 flag) { set(uint8_t(flag), true, status_[1]); }
void set(const Status2 flag) { set(uint8_t(flag), true, status_[2]); }
void set_status0(uint8_t value) { status_[0] = value; }
void set_status0(const uint8_t value) { status_[0] = value; }
//
// Flag getters.
+100 -70
View File
@@ -11,9 +11,8 @@
#include "Outputs/Log.hpp"
namespace {
Log::Logger<Log::Source::i8272> logger;
using Logger = Log::Logger<Log::Source::i8272>;
constexpr int ms_to_cycles(const int x) { return x * 8000; }
}
using namespace Intel::i8272;
@@ -59,14 +58,18 @@ void i8272::run_for(const Cycles cycles) {
drives_[c].step_rate_counter %= (8000 * step_rate_time_);
while(steps--) {
// Perform a step.
int direction = (drives_[c].target_head_position < drives_[c].head_position) ? -1 : 1;
logger.info().append("Target %d versus believed %d", drives_[c].target_head_position, drives_[c].head_position);
const int direction = (drives_[c].target_head_position < drives_[c].head_position) ? -1 : 1;
select_drive(c);
get_drive().step(Storage::Disk::HeadPosition(direction));
if(drives_[c].target_head_position >= 0) drives_[c].head_position += direction;
Logger::info().append(
"Drive %d: seeking %d but seemingly at %d", c, drives_[c].target_head_position, drives_[c].head_position);
// Check for completion.
if(seek_is_satisfied(c)) {
Logger::info().append(
"Drive %d: seek satisfied", c, drives_[c].target_head_position, drives_[c].head_position);
drives_[c].phase = Drive::CompletedSeeking;
drives_seeking_--;
break;
@@ -140,52 +143,73 @@ uint8_t i8272::read(const int address) {
}
}
#define BEGIN_SECTION() switch(resume_point_) { default:
void i8272::posit_event(const int event_type) {
#define BEGIN_SECTION() switch(resume_point_) { default: case IdleResumePoint:
#define END_SECTION() }
#define MS_TO_CYCLES(x) x * 8000
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = int(mask); return; case __LINE__:
#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; interesting_event_mask_ = int(Event8272::Timer); delay_time_ = MS_TO_CYCLES(ms); is_sleeping_ = false; update_clocking_observer(); case __LINE__: if(delay_time_) return;
#define WAIT_FOR_EVENT(mask) { \
static constexpr int location = __COUNTER__ + 1; \
resume_point_ = location; \
interesting_event_mask_ = int(mask); \
return; \
case location: \
(void)0; \
}
#define WAIT_FOR_TIME(ms) { \
static constexpr int location = __COUNTER__ + 1; \
interesting_event_mask_ = int(Event8272::Timer); \
delay_time_ = ms_to_cycles(ms); \
is_sleeping_ = false; \
update_clocking_observer(); \
resume_point_ = location; \
[[fallthrough]]; \
case location: \
if(delay_time_) return; \
}
#define PASTE(x, y) x##y
#define CONCAT(x, y) PASTE(x, y)
#define LABEL(x, y) PASTE(x, y)
#define FIND_HEADER() \
set_data_mode(DataMode::Scanning); \
CONCAT(find_header, __LINE__): WAIT_FOR_EVENT(int(Event::Token) | int(Event::IndexHole)); \
LABEL(find_header, __LINE__): WAIT_FOR_EVENT(int(Event::Token) | int(Event::IndexHole)); \
if(event_type == int(Event::IndexHole)) { index_hole_limit_--; } \
else if(get_latest_token().type == Token::ID) goto CONCAT(header_found, __LINE__); \
else if(get_latest_token().type == Token::ID) goto LABEL(header_found, __LINE__); \
\
if(index_hole_limit_) goto CONCAT(find_header, __LINE__); \
CONCAT(header_found, __LINE__): (void)0;\
if(index_hole_limit_) goto LABEL(find_header, __LINE__); \
LABEL(header_found, __LINE__): (void)0;\
#define FIND_DATA() \
set_data_mode(DataMode::Scanning); \
CONCAT(find_data, __LINE__): WAIT_FOR_EVENT(int(Event::Token) | int(Event::IndexHole)); \
LABEL(find_data, __LINE__): WAIT_FOR_EVENT(int(Event::Token) | int(Event::IndexHole)); \
if(event_type == int(Event::Token)) { \
if(get_latest_token().type == Token::Byte || get_latest_token().type == Token::Sync) goto CONCAT(find_data, __LINE__); \
if(get_latest_token().type == Token::Byte || get_latest_token().type == Token::Sync) \
goto LABEL(find_data, __LINE__); \
}
#define READ_HEADER() \
distance_into_section_ = 0; \
set_data_mode(DataMode::Reading); \
CONCAT(read_header, __LINE__): WAIT_FOR_EVENT(Event::Token); \
LABEL(read_header, __LINE__): WAIT_FOR_EVENT(Event::Token); \
header_[distance_into_section_] = get_latest_token().byte_value; \
distance_into_section_++; \
if(distance_into_section_ < 6) goto CONCAT(read_header, __LINE__); \
if(distance_into_section_ < 6) goto LABEL(read_header, __LINE__); \
#define SET_DRIVE_HEAD_MFM() \
active_drive_ = command_.target().drive; \
active_head_ = command_.target().head; \
select_drive(active_drive_); \
get_drive().set_head(active_head_); \
set_is_double_density(command_.target().mfm);
const auto SET_DRIVE_HEAD_MFM = [&] {
active_drive_ = command_.target().drive;
active_head_ = command_.target().head;
select_drive(active_drive_);
get_drive().set_head(active_head_);
set_is_double_density(command_.target().mfm);
};
#define WAIT_FOR_BYTES(n) \
distance_into_section_ = 0; \
CONCAT(wait_bytes, __LINE__): WAIT_FOR_EVENT(Event::Token); \
LABEL(wait_bytes, __LINE__): WAIT_FOR_EVENT(Event::Token); \
if(get_latest_token().type == Token::Byte) distance_into_section_++; \
if(distance_into_section_ < (n)) goto CONCAT(wait_bytes, __LINE__);
if(distance_into_section_ < (n)) goto LABEL(wait_bytes, __LINE__);
#define LOAD_HEAD() \
if(!drives_[active_drive_].head_is_loaded[active_head_]) { \
@@ -198,18 +222,20 @@ uint8_t i8272::read(const int address) {
} \
}
#define SCHEDULE_HEAD_UNLOAD() \
if(drives_[active_drive_].head_is_loaded[active_head_]) {\
if(drives_[active_drive_].head_unload_delay[active_head_] == 0) { \
head_timers_running_++; \
is_sleeping_ = false; \
update_clocking_observer(); \
} \
drives_[active_drive_].head_unload_delay[active_head_] = MS_TO_CYCLES(head_unload_time_);\
}
const auto SCHEDULE_HEAD_UNLOAD = [&] {
if(drives_[active_drive_].head_is_loaded[active_head_]) {
if(drives_[active_drive_].head_unload_delay[active_head_] == 0) {
++head_timers_running_;
is_sleeping_ = false;
update_clocking_observer();
}
drives_[active_drive_].head_unload_delay[active_head_] = ms_to_cycles(head_unload_time_);
}
};
void i8272::posit_event(const int event_type) {
if(event_type == int(Event::IndexHole)) index_hole_count_++;
if(event_type == int(Event::IndexHole)) {
++index_hole_count_;
}
if(event_type == int(Event8272::NoLongerReady)) {
status_.set(Status0::NotReady);
goto abort;
@@ -234,7 +260,7 @@ void i8272::posit_event(const int event_type) {
wait_for_complete_command_sequence:
status_.set(MainStatus::DataReady, true);
status_.set(MainStatus::DataIsToProcessor, false);
WAIT_FOR_EVENT(Event8272::CommandByte)
WAIT_FOR_EVENT(Event8272::CommandByte);
if(!command_.has_command()) {
goto wait_for_complete_command_sequence;
@@ -258,8 +284,8 @@ void i8272::posit_event(const int event_type) {
drives_seeking_--;
}
}
// Establishes the drive and head being addressed, and whether in double density mode; populates the internal
// cylinder, head, sector and size registers from the command stream.
// Establishes the drive and head being addressed, and whether in double density mode; populates
// the internal cylinder, head, sector and size registers from the command stream.
is_executing_ = true;
if(!dma_mode_) {
status_.set(MainStatus::InNonDMAExecution, true);
@@ -309,17 +335,17 @@ void i8272::posit_event(const int event_type) {
// the index hole limit is breached or a sector is found with a cylinder, head, sector and size equal to the
// values in the internal registers.
index_hole_limit_ = 2;
// logger.info().append("Seeking " << PADDEC(0) << cylinder_ << " " << head_ " " << sector_ << " " << size_);
// Logger::info().append("Seeking " << PADDEC(0) << cylinder_ << " " << head_ " " << sector_ << " " << size_);
find_next_sector:
FIND_HEADER();
if(!index_hole_limit_) {
// Two index holes have passed wihout finding the header sought.
// logger.info().append("Not found");
// Logger::info().append("Not found");
status_.set(Status1::NoData);
goto abort;
}
index_hole_count_ = 0;
// logger.info().append("Header");
// Logger::info().append("Header");
READ_HEADER();
if(index_hole_count_) {
// This implies an index hole was sighted within the header. Error out.
@@ -330,26 +356,30 @@ void i8272::posit_event(const int event_type) {
// This implies a CRC error in the header; mark as such but continue.
status_.set(Status1::DataError);
}
// logger.info().append("Considering %02x %02x %02x %02x [%04x]", header_[0], header_[1], header_[2], header_[3], get_crc_generator().get_value());
if(header_[0] != cylinder_ || header_[1] != head_ || header_[2] != sector_ || header_[3] != size_) goto find_next_sector;
// Logger::info().append(
// "Considering %02x %02x %02x %02x [%04x]",
// header_[0], header_[1], header_[2], header_[3], get_crc_generator().get_value());
if(header_[0] != cylinder_ || header_[1] != head_ || header_[2] != sector_ || header_[3] != size_) {
goto find_next_sector;
}
// Branch to whatever is supposed to happen next
// logger.info().append("Proceeding");
// Logger::info().append("Proceeding");
switch(command_.command()) {
default:
case Command::ReadData:
case Command::ReadDeletedData:
goto read_data_found_header;
case Command::WriteData: // write data
case Command::WriteDeletedData: // write deleted data
case Command::WriteData:
case Command::WriteDeletedData:
goto write_data_found_header;
}
// Performs the read data or read deleted data command.
read_data:
// logger.info().append("Read [deleted] data [%02x %02x %02x %02x ... %02x %02x]",
// Logger::info().append("Read [deleted] data [%02x %02x %02x %02x ... %02x %02x]",
// command_[2],
// command_[3],
// command_[4],
@@ -390,8 +420,8 @@ void i8272::posit_event(const int event_type) {
distance_into_section_ = 0;
set_data_mode(Reading);
// Waits for the next token, then supplies it to the CPU by: (i) setting data request and direction; and (ii) resetting
// data request once the byte has been taken. Continues until all bytes have been read.
// Waits for the next token, then supplies it to the CPU by: (i) setting data request and direction; and
// (ii) resetting data request once the byte has been taken. Continues until all bytes have been read.
//
// TODO: consider DTL.
read_data_get_byte:
@@ -439,7 +469,7 @@ void i8272::posit_event(const int event_type) {
goto post_st012chrn;
write_data:
// logger.info().append("Write [deleted] data [%02x %02x %02x %02x ... %02x %02x]",
// Logger::info().append("Write [deleted] data [%02x %02x %02x %02x ... %02x %02x]",
// command_[2],
// command_[3],
// command_[4],
@@ -480,7 +510,7 @@ void i8272::posit_event(const int event_type) {
goto write_loop;
}
logger.info().append("Wrote %d bytes", distance_into_section_);
Logger::info().append("Wrote %d bytes", distance_into_section_);
write_crc();
expects_input_ = false;
WAIT_FOR_EVENT(Event::DataWritten);
@@ -496,10 +526,11 @@ void i8272::posit_event(const int event_type) {
// Performs the read ID command.
read_id:
// Establishes the drive and head being addressed, and whether in double density mode.
// logger.info().append("Read ID [%02x %02x]", command_[0], command_[1]);
// Logger::info().append("Read ID [%02x %02x]", command_[0], command_[1]);
// Sets a maximum index hole limit of 2 then waits either until it finds a header mark or sees too many index holes.
// If a header mark is found, reads in the following bytes that produce a header. Otherwise branches to data not found.
// Sets a maximum index hole limit of 2 then waits either until it finds a header mark or sees too many
// index holes. If a header mark is found, reads in the following bytes that produce a header. Otherwise
// branches to data not found.
index_hole_limit_ = 2;
FIND_HEADER();
if(!index_hole_limit_) {
@@ -518,7 +549,7 @@ void i8272::posit_event(const int event_type) {
// Performs read track.
read_track:
// logger.info().append("Read track [%02x %02x %02x %02x]"
// Logger::info().append("Read track [%02x %02x %02x %02x]"
// command_[2],
// command_[3],
// command_[4],
@@ -563,7 +594,7 @@ void i8272::posit_event(const int event_type) {
// Performs format [/write] track.
format_track:
logger.info().append("Format track");
Logger::info().append("Format track");
if(get_drive().is_read_only()) {
status_.set(Status1::NotWriteable);
goto abort;
@@ -607,7 +638,7 @@ void i8272::posit_event(const int event_type) {
break;
}
logger.info().append("W: %02x %02x %02x %02x, %04x",
Logger::info().append("W: %02x %02x %02x %02x, %04x",
header_[0], header_[1], header_[2], header_[3], get_crc_generator().get_value());
write_crc();
@@ -640,15 +671,15 @@ void i8272::posit_event(const int event_type) {
goto post_st012chrn;
scan_low:
logger.error().append("Scan low unimplemented!!");
Logger::error().append("Scan low unimplemented!!");
goto wait_for_command;
scan_low_or_equal:
logger.error().append("Scan low or equal unimplemented!!");
Logger::error().append("Scan low or equal unimplemented!!");
goto wait_for_command;
scan_high_or_equal:
logger.error().append("Scan high or equal unimplemented!!");
Logger::error().append("Scan high or equal unimplemented!!");
goto wait_for_command;
// Performs both recalibrate and seek commands. These commands occur asynchronously, so the actual work
@@ -679,11 +710,11 @@ void i8272::posit_event(const int event_type) {
// up in run_for understands to mean 'keep going until track 0 is active').
if(command_.command() != Command::Recalibrate) {
drives_[drive].target_head_position = command_.seek_target();
logger.info().append("Seek to %d", command_.seek_target());
Logger::info().append("Seek to %d", command_.seek_target());
} else {
drives_[drive].target_head_position = -1;
drives_[drive].head_position = 0;
logger.info().append("Recalibrate");
Logger::info().append("Recalibrate");
}
// Check whether any steps are even needed; if not then mark as completed already.
@@ -696,7 +727,6 @@ void i8272::posit_event(const int event_type) {
// Performs sense interrupt status.
sense_interrupt_status:
logger.info().append("Sense interrupt status");
{
// Find the first drive that is in the CompletedSeeking state.
int found_drive = -1;
@@ -711,12 +741,12 @@ void i8272::posit_event(const int event_type) {
if(found_drive != -1) {
drives_[found_drive].phase = Drive::NotSeeking;
status_.set_status0(uint8_t(found_drive | uint8_t(Status0::SeekEnded)));
// status_.end_sense_interrupt_status(found_drive, 0);
// status_.set(Status0::SeekEnded);
result_stack_ = { drives_[found_drive].head_position, status_[0]};
Logger::info().append("Sense interrupt status: returning %02x %02x", result_stack_[0], result_stack_[1]);
} else {
result_stack_ = { 0x80 };
Logger::info().append("Sense interrupt status: returning %02x", result_stack_[0]);
}
}
goto post_result;
@@ -724,7 +754,7 @@ void i8272::posit_event(const int event_type) {
// Performs specify.
specify:
// Just store the values, and terminate the command.
logger.info().append("Specify");
Logger::info().append("Specify");
step_rate_time_ = command_.specify_specs().step_rate_time;
head_unload_time_ = command_.specify_specs().head_unload_time;
head_load_time_ = command_.specify_specs().head_load_time;
@@ -735,7 +765,7 @@ void i8272::posit_event(const int event_type) {
goto wait_for_command;
sense_drive_status:
logger.info().append("Sense drive status");
Logger::info().append("Sense drive status");
{
int drive = command_.target().drive;
select_drive(drive);
@@ -775,7 +805,7 @@ void i8272::posit_event(const int event_type) {
// last thing in it will be returned first.
post_result:
// {
// auto line = logger.info();
// auto line = Logger::info();
// line.append("Result to %02x, main %02x", command_[0] & 0x1f, main_status_);
// for(std::size_t c = 0; c < result_stack_.size(); c++) {
// line.append(" %02x", result_stack_[result_stack_.size() - 1 - c]);
+3 -1
View File
@@ -72,9 +72,11 @@ private:
};
void posit_event(int type) final;
int interesting_event_mask_ = int(Event8272::CommandByte);
int resume_point_ = 0;
bool is_access_command_ = false;
static constexpr int IdleResumePoint = 0;
int resume_point_ = 0;
// The counter used for ::Timer events.
Cycles::IntType delay_time_ = 0;
+19 -21
View File
@@ -13,9 +13,7 @@
using namespace Zilog::SCC;
namespace {
Log::Logger<Log::Source::SCC> logger;
using Logger = Log::Logger<Log::Source::SCC>;
}
void z8530::reset() {
@@ -54,7 +52,7 @@ std::uint8_t z8530::read(const int address) {
case 2: // Handled non-symmetrically between channels.
if(address & 1) {
logger.error().append("Unimplemented: register 2 status bits");
Logger::error().append("Unimplemented: register 2 status bits");
} else {
result = interrupt_vector_;
@@ -111,11 +109,11 @@ void z8530::write(const int address, const std::uint8_t value) {
case 2: // Interrupt vector register; used only by Channel B.
// So there's only one of these.
interrupt_vector_ = value;
logger.info().append("Interrupt vector set to %d", value);
Logger::info().append("Interrupt vector set to %d", value);
break;
case 9: // Master interrupt and reset register; there is also only one of these.
logger.info().append("Master interrupt and reset register: %02x", value);
Logger::info().append("Master interrupt and reset register: %02x", value);
master_interrupt_control_ = value;
break;
}
@@ -152,7 +150,7 @@ uint8_t z8530::Channel::read(const bool data, const uint8_t pointer) {
if(data) {
return data_;
} else {
logger.info().append("Control read from register %d", pointer);
Logger::info().append("Control read from register %d", pointer);
// Otherwise, this is a control read...
switch(pointer) {
default:
@@ -237,10 +235,10 @@ void z8530::Channel::write(const bool data, const uint8_t pointer, const uint8_t
data_ = value;
return;
} else {
logger.info().append("Control write: %02x to register %d", value, pointer);
Logger::info().append("Control write: %02x to register %d", value, pointer);
switch(pointer) {
default:
logger.info().append("Unrecognised control write: %02x to register %d", value, pointer);
Logger::info().append("Unrecognised control write: %02x to register %d", value, pointer);
break;
case 0x0: // Write register 0 — CRC reset and other functions.
@@ -248,13 +246,13 @@ void z8530::Channel::write(const bool data, const uint8_t pointer, const uint8_t
switch(value >> 6) {
default: /* Do nothing. */ break;
case 1:
logger.error().append("TODO: reset Rx CRC checker.");
Logger::error().append("TODO: reset Rx CRC checker.");
break;
case 2:
logger.error().append("TODO: reset Tx CRC checker.");
Logger::error().append("TODO: reset Tx CRC checker.");
break;
case 3:
logger.error().append("TODO: reset Tx underrun/EOM latch.");
Logger::error().append("TODO: reset Tx underrun/EOM latch.");
break;
}
@@ -262,24 +260,24 @@ void z8530::Channel::write(const bool data, const uint8_t pointer, const uint8_t
switch((value >> 3)&7) {
default: /* Do nothing. */ break;
case 2:
// logger.info().append("reset ext/status interrupts.");
// Logger::info().append("reset ext/status interrupts.");
external_status_interrupt_ = false;
external_interrupt_status_ = 0;
break;
case 3:
logger.error().append("TODO: send abort (SDLC).");
Logger::error().append("TODO: send abort (SDLC).");
break;
case 4:
logger.error().append("TODO: enable interrupt on next Rx character.");
Logger::error().append("TODO: enable interrupt on next Rx character.");
break;
case 5:
logger.error().append("TODO: reset Tx interrupt pending.");
Logger::error().append("TODO: reset Tx interrupt pending.");
break;
case 6:
logger.error().append("TODO: reset error.");
Logger::error().append("TODO: reset error.");
break;
case 7:
logger.error().append("TODO: reset highest IUS.");
Logger::error().append("TODO: reset highest IUS.");
break;
}
break;
@@ -304,7 +302,7 @@ void z8530::Channel::write(const bool data, const uint8_t pointer, const uint8_t
b1 = 1 => transmit buffer empty interrupt is enabled; 0 => it isn't.
b0 = 1 => external interrupt is enabled; 0 => it isn't.
*/
logger.info().append("Interrupt mask: %02x", value);
Logger::info().append("Interrupt mask: %02x", value);
break;
case 0x2: // Write register 2 - interrupt vector.
@@ -319,7 +317,7 @@ void z8530::Channel::write(const bool data, const uint8_t pointer, const uint8_t
case 2: receive_bit_count = 6; break;
case 3: receive_bit_count = 8; break;
}
logger.info().append("Receive bit count: %d", receive_bit_count);
Logger::info().append("Receive bit count: %d", receive_bit_count);
/*
b7,b6:
@@ -425,6 +423,6 @@ void z8530::update_delegate() {
const bool interrupt_line = get_interrupt_line();
if(interrupt_line != previous_interrupt_line_) {
previous_interrupt_line_ = interrupt_line;
if(delegate_) delegate_->did_change_interrupt_status(this, interrupt_line);
if(delegate_) delegate_->did_change_interrupt_status(*this, interrupt_line);
}
}
+2 -2
View File
@@ -43,7 +43,7 @@ public:
/*!
Communicates that @c scc now has the interrupt line status @c new_status.
*/
virtual void did_change_interrupt_status(z8530 *, bool new_status) = 0;
virtual void did_change_interrupt_status(z8530 &, bool new_status) = 0;
};
/*!
@@ -54,7 +54,7 @@ public:
void set_delegate(Delegate *const delegate) {
if(delegate_ == delegate) return;
delegate_ = delegate;
delegate_->did_change_interrupt_status(this, get_interrupt_line());
delegate_->did_change_interrupt_status(*this, get_interrupt_line());
}
/*
+10 -16
View File
@@ -23,7 +23,7 @@ namespace {
constexpr unsigned int CRTCyclesPerLine = 1365;
constexpr unsigned int CRTCyclesDivider = 4;
Log::Logger<Log::Source::TMS9918> logger;
using Logger = Log::Logger<Log::Source::TMS9918>;
}
@@ -74,9 +74,9 @@ TMS9918<personality>::TMS9918() {
this->crt_.set_display_type(Outputs::Display::DisplayType::RGB);
if constexpr (is_yamaha_vdp(personality)) {
this->crt_.set_visible_area(Outputs::Display::Rect(0.07f, 0.065f, 0.875f, 0.875f));
this->crt_.set_fixed_framing(Outputs::Display::Rect(0.07f, 0.065f, 0.875f, 0.875f));
} else {
this->crt_.set_visible_area(Outputs::Display::Rect(0.07f, 0.0375f, 0.875f, 0.875f));
this->crt_.set_fixed_framing(Outputs::Display::Rect(0.07f, 0.0375f, 0.875f, 0.875f));
}
// The TMS remains in-phase with the NTSC colour clock; this is an empirical measurement
@@ -226,7 +226,7 @@ void TMS9918<personality>::run_for(const HalfCycles cycles) {
// TODO: where did this magic constant come from? https://www.smspower.org/forums/17970-RoadRashHow#111000 mentioned in passing
// that "the vertical scroll register is latched at the start of the active display" and this is two clocks before that, so it's
// not uncompelling. I can just no longer find my source.
constexpr auto latch_time = LineLayout<personality>::EndOfLeftBorder - 2;
static constexpr auto latch_time = LineLayout<personality>::EndOfLeftBorder - 2;
static_assert(latch_time > 0);
if(this->fetch_pointer_.column < latch_time && end_column >= latch_time) {
if(!this->fetch_pointer_.row) {
@@ -683,13 +683,7 @@ void Base<personality>::output_border(int cycles, [[maybe_unused]] const uint32_
return;
}
// If the border colour is 0, that can be communicated
// more efficiently as an explicit blank.
if(border_colour) {
crt_.output_level<uint32_t>(cycles, border_colour);
} else {
crt_.output_blank(cycles);
}
crt_.output_level<uint32_t>(cycles, border_colour);
}
// MARK: - External interface.
@@ -843,7 +837,7 @@ void Base<personality>::commit_register(int reg, const uint8_t value) {
Storage<personality>::solid_background_ = value & 0x20;
Storage<personality>::sprites_enabled_ = !(value & 0x02);
if(value & 0x01) {
logger.error().append("TODO: Yamaha greyscale");
Logger::error().append("TODO: Yamaha greyscale");
}
// b7: "1 = input on colour bus, enable mouse; 1 = output on colour bus, disable mouse" [documentation clearly in error]
// b6: 1 = enable light pen
@@ -860,7 +854,7 @@ void Base<personality>::commit_register(int reg, const uint8_t value) {
// TODO: on the Yamaha, at least, tie this interrupt overtly to vertical state.
if(value & 0x08) {
logger.error().append("TODO: Yamaha interlace mode");
Logger::error().append("TODO: Yamaha interlace mode");
}
// b7: 1 = 212 lines of pixels; 0 = 192
@@ -924,7 +918,7 @@ void Base<personality>::commit_register(int reg, const uint8_t value) {
case 20:
case 21:
case 22:
// logger.error().append("TODO: Yamaha colour burst selection; %02x", value);
// Logger::error().append("TODO: Yamaha colour burst selection; %02x", value);
// Documentation is "fill with 0s for no colour burst; magic pattern for colour burst"
break;
@@ -1010,7 +1004,7 @@ void Base<personality>::commit_register(int reg, const uint8_t value) {
// Kill the command immediately if it's done in zero operations
// (e.g. a line of length 0).
if(!Storage<personality>::command_ && (value >> 4)) {
logger.error().append("TODO: Yamaha command %02x", value);
Logger::error().append("TODO: Yamaha command %02x", value);
}
// Seed timing information if a command was found.
@@ -1211,7 +1205,7 @@ uint8_t TMS9918<personality>::read(const int address) {
template <Personality personality>
int Base<personality>::fetch_line() const {
// This is the proper Master System value; TODO: what's correct for Yamaha, etc?
constexpr int row_change_position = 31;
static constexpr int row_change_position = 31;
return
(this->fetch_pointer_.column < row_change_position)
+4 -4
View File
@@ -87,8 +87,8 @@ template <Personality personality> struct Base: public Storage<personality> {
/// applicable @c memory_mask.
template <int shift, int length = 8> void install_field(AddressT &target, const uint8_t source) {
static_assert(length > 0 && length <= 8);
constexpr auto source_mask = (1 << length) - 1;
constexpr auto mask = AddressT(~(source_mask << shift));
static constexpr auto source_mask = (1 << length) - 1;
static constexpr auto mask = AddressT(~(source_mask << shift));
target = (
(target & mask) |
AddressT((source & source_mask) << shift)
@@ -545,8 +545,8 @@ template <Personality personality> struct Base: public Storage<personality> {
if(Storage<personality>::cram_is_selected_) {
// Adjust the palette. In a Master System blue has a slightly different
// scale; cf. https://www.retrorgb.com/sega-master-system-non-linear-blue-channel-findings.html
constexpr uint8_t rg_scale[] = {0, 85, 170, 255};
constexpr uint8_t b_scale[] = {0, 104, 170, 255};
static constexpr uint8_t rg_scale[] = {0, 85, 170, 255};
static constexpr uint8_t b_scale[] = {0, 104, 170, 255};
Storage<personality>::colour_ram_[address & 0x1f] = palette_pack(
rg_scale[(read_ahead_buffer_ >> 0) & 3],
rg_scale[(read_ahead_buffer_ >> 2) & 3],
+3 -3
View File
@@ -93,8 +93,8 @@ void Base<personality>::draw_sprites(
assert(!buffer.is_filling);
}
constexpr uint32_t sprite_colour_selection_masks[2] = {0x00000000, 0xffffffff};
constexpr int colour_masks[16] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
static constexpr uint32_t sprite_colour_selection_masks[2] = {0x00000000, 0xffffffff};
static constexpr int colour_masks[16] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
const int sprite_width = sprites_16x16_ ? 16 : 8;
const int shifter_target = sprite_width << 1;
const int pixel_width = sprites_magnified_ ? sprite_width << 1 : sprite_width;
@@ -526,7 +526,7 @@ void Base<personality>::draw_yamaha(const uint8_t y, int start, int end) {
}
}
constexpr std::array<uint32_t, 16> graphics7_sprite_palette = {
static constexpr std::array<uint32_t, 16> graphics7_sprite_palette = {
palette_pack(0b00000000, 0b00000000, 0b00000000), palette_pack(0b00000000, 0b00000000, 0b01001001),
palette_pack(0b00000000, 0b01101101, 0b00000000), palette_pack(0b00000000, 0b01101101, 0b01001001),
palette_pack(0b01101101, 0b00000000, 0b00000000), palette_pack(0b01101101, 0b00000000, 0b01001001),
+10 -10
View File
@@ -407,8 +407,8 @@ struct TextSequencer {
return;
} else {
// For the 120 slots in between follow a three-step pattern of:
constexpr int offset = cycle - 30;
constexpr auto column = AddressT(offset / 3);
static constexpr int offset = cycle - 30;
static constexpr auto column = AddressT(offset / 3);
switch(offset % 3) {
case 0: // (1) fetch tile name.
fetcher.fetch_name(column);
@@ -457,16 +457,16 @@ struct CharacterSequencer {
// Body of line: tiles themselves, plus some additional potential sprites.
if(cycle >= 27 && cycle < 155) {
constexpr int offset = cycle - 27;
constexpr int block = offset >> 2;
constexpr int sub_block = offset & 3;
static constexpr int offset = cycle - 27;
static constexpr int block = offset >> 2;
static constexpr int sub_block = offset & 3;
switch(sub_block) {
case 0: character_fetcher.fetch_name(block); break;
case 1:
if(!(block & 3)) {
character_fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
} else {
constexpr int sprite = 8 + ((block >> 2) * 3) + ((block & 3) - 1);
static constexpr int sprite = 8 + ((block >> 2) * 3) + ((block & 3) - 1);
sprite_fetcher.fetch_y(sprite);
}
break;
@@ -549,9 +549,9 @@ struct SMSSequencer {
}
if(cycle >= 25 && cycle < 153) {
constexpr int offset = cycle - 25;
constexpr int block = offset >> 2;
constexpr int sub_block = offset & 3;
static constexpr int offset = cycle - 25;
static constexpr int block = offset >> 2;
static constexpr int sub_block = offset & 3;
switch(sub_block) {
default: break;
@@ -561,7 +561,7 @@ struct SMSSequencer {
if(!(block & 3)) {
fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow, Clock::FromStartOfSync>(cycle));
} else {
constexpr int sprite = (8 + ((block >> 2) * 3) + ((block & 3) - 1)) << 1;
static constexpr int sprite = (8 + ((block >> 2) * 3) + ((block & 3) - 1)) << 1;
fetcher.posit_sprite(sprite);
fetcher.posit_sprite(sprite+1);
}
+26 -26
View File
@@ -29,49 +29,49 @@ template <Personality personality, typename Enable = void> struct LineLayout;
// * text mode on all VDPs adjusts border width.
template <Personality personality> struct LineLayout<personality, std::enable_if_t<is_classic_vdp(personality)>> {
constexpr static int StartOfSync = 0;
constexpr static int EndOfSync = 26;
constexpr static int StartOfColourBurst = 29;
constexpr static int EndOfColourBurst = 43;
constexpr static int EndOfLeftErase = 50;
constexpr static int EndOfLeftBorder = 63;
constexpr static int EndOfPixels = 319;
constexpr static int EndOfRightBorder = 334;
static constexpr int StartOfSync = 0;
static constexpr int EndOfSync = 26;
static constexpr int StartOfColourBurst = 29;
static constexpr int EndOfColourBurst = 43;
static constexpr int EndOfLeftErase = 50;
static constexpr int EndOfLeftBorder = 63;
static constexpr int EndOfPixels = 319;
static constexpr int EndOfRightBorder = 334;
constexpr static int CyclesPerLine = 342;
static constexpr int CyclesPerLine = 342;
constexpr static int TextModeEndOfLeftBorder = 69;
constexpr static int TextModeEndOfPixels = 309;
static constexpr int TextModeEndOfLeftBorder = 69;
static constexpr int TextModeEndOfPixels = 309;
constexpr static int ModeLatchCycle = 36; // Just a guess; correlates with the known 144 for the Yamaha VDPs,
static constexpr int ModeLatchCycle = 36; // Just a guess; correlates with the known 144 for the Yamaha VDPs,
// and falls into the collection gap between the final sprite
// graphics and the initial tiles or pixels.
/// The number of internal cycles that must elapse between a request to read or write and
/// it becoming a candidate for action.
constexpr static int VRAMAccessDelay = 6;
static constexpr int VRAMAccessDelay = 6;
};
template <Personality personality> struct LineLayout<personality, std::enable_if_t<is_yamaha_vdp(personality)>> {
constexpr static int StartOfSync = 0;
constexpr static int EndOfSync = 100;
constexpr static int StartOfColourBurst = 113;
constexpr static int EndOfColourBurst = 167;
constexpr static int EndOfLeftErase = 202;
constexpr static int EndOfLeftBorder = 258;
constexpr static int EndOfPixels = 1282;
constexpr static int EndOfRightBorder = 1341;
static constexpr int StartOfSync = 0;
static constexpr int EndOfSync = 100;
static constexpr int StartOfColourBurst = 113;
static constexpr int EndOfColourBurst = 167;
static constexpr int EndOfLeftErase = 202;
static constexpr int EndOfLeftBorder = 258;
static constexpr int EndOfPixels = 1282;
static constexpr int EndOfRightBorder = 1341;
constexpr static int CyclesPerLine = 1368;
static constexpr int CyclesPerLine = 1368;
constexpr static int TextModeEndOfLeftBorder = 294;
constexpr static int TextModeEndOfPixels = 1254;
static constexpr int TextModeEndOfLeftBorder = 294;
static constexpr int TextModeEndOfPixels = 1254;
constexpr static int ModeLatchCycle = 144;
static constexpr int ModeLatchCycle = 144;
/// The number of internal cycles that must elapse between a request to read or write and
/// it becoming a candidate for action.
constexpr static int VRAMAccessDelay = 16;
static constexpr int VRAMAccessDelay = 16;
};
}
+6 -5
View File
@@ -202,6 +202,7 @@ protected:
} else {
return Event::Type::External;
}
break;
case 6:
if((block & 3) != 3) {
return Event::Type::External;
@@ -280,11 +281,11 @@ protected:
const int block = offset / 32;
const int sub_block = offset & 31;
switch(sub_block) {
case 0: if(block > 0) return Event(Event::Type::Name, uint8_t(block - 1));
case 6: if((sub_block & 3) != 3) return Event::Type::External;
case 12: if(block < 32) return Event(Event::Type::SpriteY, uint8_t(block));
case 18: if(block > 0) return Event(Event::Type::Pattern, uint8_t(block - 1));
case 24: if(block > 0) return Event(Event::Type::Colour, uint8_t(block - 1));
case 0: if(block > 0) return Event(Event::Type::Name, uint8_t(block - 1)); break;
case 6: if((sub_block & 3) != 3) return Event::Type::External; break;
case 12: if(block < 32) return Event(Event::Type::SpriteY, uint8_t(block)); break;
case 18: if(block > 0) return Event(Event::Type::Pattern, uint8_t(block - 1)); break;
case 24: if(block > 0) return Event(Event::Type::Colour, uint8_t(block - 1)); break;
}
}
@@ -18,8 +18,8 @@ struct Vector {
int v[2]{};
template <int offset, bool high> void set(const uint8_t value) {
constexpr uint8_t mask = high ? (offset ? 0x3 : 0x1) : 0xff;
constexpr int shift = high ? 8 : 0;
static constexpr uint8_t mask = high ? (offset ? 0x3 : 0x1) : 0xff;
static constexpr int shift = high ? 8 : 0;
v[offset] = (v[offset] & ~(mask << shift)) | ((value & mask) << shift);
}
+1 -1
View File
@@ -91,7 +91,7 @@ void AY38910SampleSource<is_stereo>::set_sample_volume_range(const std::int16_t
// from the YM's datasheet, showing a clear power curve, and fitting that to observed
// values reported elsewhere.
const float max_volume = float(range) / 3.0f; // As there are three channels.
constexpr float root_two = 1.414213562373095f;
static constexpr float root_two = 1.414213562373095f;
for(int v = 0; v < 32; v++) {
volumes_[v] = int(max_volume / powf(root_two, float(v ^ 0x1f) / 3.18f));
}
+2 -5
View File
@@ -8,6 +8,7 @@
#include "DiskII.hpp"
#include <bit>
#include <cstdio>
#include <cstring>
@@ -62,11 +63,7 @@ void DiskII::set_control(const Control control, const bool on) {
if(stepper_mask_&4) direction += (((stepper_position_ - 4) + 4)&7) - 4;
if(stepper_mask_&8) direction += (((stepper_position_ - 6) + 4)&7) - 4;
// TODO: when adopting C++20, replace with std::popcount.
int bits_set = stepper_mask_;
bits_set = (bits_set & 0x5) + ((bits_set >> 1) & 0x5);
bits_set = (bits_set & 0x3) + ((bits_set >> 2) & 0x3);
const int bits_set = std::popcount(uint8_t(stepper_mask_));
direction /= bits_set;
// Compare to the stepper position to decide whether that pulls in the current cog notch,
+3 -1
View File
@@ -8,6 +8,8 @@
#include "DiskIIDrive.hpp"
#include <bit>
using namespace Apple::Disk;
DiskIIDrive::DiskIIDrive(const int input_clock_rate) :
@@ -29,7 +31,7 @@ void DiskIIDrive::set_control_lines(const int lines) {
if(lines&2) direction += (((stepper_position_ - 2) + 4)&7) - 4;
if(lines&4) direction += (((stepper_position_ - 4) + 4)&7) - 4;
if(lines&8) direction += (((stepper_position_ - 6) + 4)&7) - 4;
const int bits_set = (lines&1) + ((lines >> 1)&1) + ((lines >> 2)&1) + ((lines >> 3)&1);
const int bits_set = std::popcount(uint8_t(lines));
direction /= bits_set;
// Compare to the stepper position to decide whether that pulls in the
+19 -17
View File
@@ -24,7 +24,7 @@ namespace {
constexpr int SEL = 1 << 8; /* This is an additional input, not available on a Disk II, with a
confusingly-similar name to SELECT but a distinct purpose. */
Log::Logger<Log::Source::IWM> logger;
using Logger = Log::Logger<Log::Source::IWM>;
}
IWM::IWM(int clock_rate) :
@@ -53,7 +53,7 @@ uint8_t IWM::read(const int address) {
switch(state_ & (Q6 | Q7 | ENABLE)) {
default:
logger.info().append("Invalid read\n");
Logger::info().append("Invalid read\n");
return 0xff;
case 0:
@@ -62,9 +62,11 @@ uint8_t IWM::read(const int address) {
if(data_register_ & 0x80) {
data_register_ = 0;
// logger.info().append("Reading data: %02x", result);
// Logger::info().append("Reading data: %02x", result);
} else {
// Logger::info().append("Spurious read?");
}
// logger.info().append("Reading data register: %02x", result);
// Logger::info().append("Reading data register: %02x", result);
return result;
}
@@ -97,7 +99,7 @@ uint8_t IWM::read(const int address) {
bit 6: 1 = write state (0 = underrun has occurred; 1 = no underrun so far).
bit 7: 1 = write data buffer ready for data (1 = ready; 0 = busy).
*/
// logger.info().append("Reading write handshake: %02x", 0x3f | write_handshake_);
// Logger::info().append("Reading write handshake: %02x", 0x3f | write_handshake_);
return 0x3f | write_handshake_;
}
@@ -128,9 +130,9 @@ void IWM::write(const int address, const uint8_t input) {
// TEMPORARY. To test for the unimplemented mode.
if(input&0x2) {
logger.info().append("Switched to asynchronous mode");
Logger::info().append("Switched to asynchronous mode");
} else {
logger.info().append("Switched to synchronous mode");
Logger::info().append("Switched to synchronous mode");
}
switch(mode_ & 0x18) {
@@ -139,8 +141,8 @@ void IWM::write(const int address, const uint8_t input) {
case 0x10: bit_length_ = Cycles(32); break; // slow mode, 8Mhz
case 0x18: bit_length_ = Cycles(16); break; // fast mode, 8Mhz
}
logger.info().append("Mode is now %02x", mode_);
logger.info().append("New bit length is %d", bit_length_.as<int>());
Logger::info().append("Mode is now %02x", mode_);
Logger::info().append("New bit length is %d", bit_length_.as<int>());
break;
case Q7|Q6|ENABLE: // Write data register.
@@ -254,7 +256,7 @@ void IWM::run_for(const Cycles cycles) {
drives_[active_drive_]->run_for(Cycles(1));
++cycles_since_shift_;
if(cycles_since_shift_ == bit_length_ + error_margin) {
// logger.info().append("Shifting 0 at %d ", cycles_since_shift_.as<int>());
// Logger::info().append("Shifting 0 at %d ", cycles_since_shift_.as<int>());
propose_shift(0);
}
}
@@ -288,11 +290,11 @@ void IWM::run_for(const Cycles cycles) {
if(!(write_handshake_ & 0x80)) {
shift_register_ = next_output_;
output_bits_remaining_ = 8;
// logger.info().append("Next byte: %02x", shift_register_);
// Logger::info().append("Next byte: %02x", shift_register_);
} else {
write_handshake_ &= ~0x40;
if(drives_[active_drive_]) drives_[active_drive_]->end_writing();
logger.info().append("Overrun; done.");
Logger::info().append("Overrun; done.");
output_bits_remaining_ = 1;
}
@@ -348,7 +350,7 @@ void IWM::select_shift_mode() {
shift_register_ = next_output_;
write_handshake_ |= 0x80 | 0x40;
output_bits_remaining_ = 8;
logger.info().append("Seeding output with %02x", shift_register_);
Logger::info().append("Seeding output with %02x", shift_register_);
}
}
@@ -362,7 +364,7 @@ void IWM::process_event(const Storage::Disk::Drive::Event &event) {
switch(event.type) {
case Storage::Disk::Track::Event::IndexHole: return;
case Storage::Disk::Track::Event::FluxTransition:
// logger.info().append("Shifting 1 at %d", cycles_since_shift_.as<int>());
// Logger::info().append("Shifting 1 at %d", cycles_since_shift_.as<int>());
propose_shift(1);
break;
}
@@ -371,8 +373,8 @@ void IWM::process_event(const Storage::Disk::Drive::Event &event) {
void IWM::propose_shift(const uint8_t bit) {
// TODO: synchronous mode.
// logger.info().append("Shifting at %d", cycles_since_shift_.as<int>());
// logger.info().append("Shifting input");
// Logger::info().append("Shifting at %d", cycles_since_shift_.as<int>());
// Logger::info().append("Shifting input");
// See above for text from the IWM patent, column 7, around line 35 onwards.
// The error_margin here implements the 'before' part of that contract.
@@ -387,7 +389,7 @@ void IWM::propose_shift(const uint8_t bit) {
shift_register_ = uint8_t((shift_register_ << 1) | bit);
if(shift_register_ & 0x80) {
// if(data_register_ & 0x80) logger.info().append("Byte missed");
// if(data_register_ & 0x80) Logger::info().append("Byte missed");
data_register_ = shift_register_;
shift_register_ = 0;
}
+6 -8
View File
@@ -13,9 +13,7 @@
using namespace I2C;
namespace {
Log::Logger<Log::Source::I2C> logger;
using Logger = Log::Logger<Log::Source::I2C>;
}
void Bus::set_data(const bool pulled) {
@@ -52,7 +50,7 @@ void Bus::set_clock_data(const bool clock_pulled, const bool data_pulled) {
if(peripheral_bits_) {
// Trailing edge of clock => bit has been consumed.
if(!prior_clock && clock_) {
logger.info().append("<< %d", (peripheral_response_ >> 7) & 1);
Logger::info().append("<< %d", (peripheral_response_ >> 7) & 1);
--peripheral_bits_;
peripheral_response_ <<= 1;
@@ -68,10 +66,10 @@ void Bus::set_clock_data(const bool clock_pulled, const bool data_pulled) {
// A data transition outside of a clock cycle implies a start or stop.
in_bit_ = false;
if(data_) {
logger.info().append("S");
Logger::info().append("S");
signal(Event::Start);
} else {
logger.info().append("W");
Logger::info().append("W");
signal(Event::Stop);
}
} else if(clock_ != prior_clock) {
@@ -85,10 +83,10 @@ void Bus::set_clock_data(const bool clock_pulled, const bool data_pulled) {
in_bit_ = false;
if(data_) {
logger.info().append("0");
Logger::info().append("0");
signal(Event::Zero);
} else {
logger.info().append("1");
Logger::info().append("1");
signal(Event::One);
}
}
@@ -18,8 +18,8 @@ public:
this is used to select a key scaling rate if key-rate scaling is enabled.
*/
void set_period(const int period, const int octave) {
constexpr int key_level_scales[16] = {0, 48, 64, 74, 80, 86, 90, 94, 96, 100, 102, 104, 106, 108, 110, 112};
constexpr int masks[2] = {~0, 0};
static constexpr int key_level_scales[16] = {0, 48, 64, 74, 80, 86, 90, 94, 96, 100, 102, 104, 106, 108, 110, 112};
static constexpr int masks[2] = {~0, 0};
// A two's complement assumption is embedded below; the use of masks relies
// on the sign bit to clamp to zero.
@@ -33,7 +33,7 @@ public:
*/
void set_key_scaling_level(const int level) {
// '7' is just a number large enough to render all possible scaling coefficients as 0.
constexpr int key_level_scale_shifts[4] = {7, 1, 2, 0};
static constexpr int key_level_scale_shifts[4] = {7, 1, 2, 0};
shift_ = key_level_scale_shifts[level];
}
@@ -24,8 +24,8 @@ public:
Advances the phase generator a single step, given the current state of the low-frequency oscillator, @c oscillator.
*/
void update(const LowFrequencyOscillator &oscillator) {
constexpr int vibrato_shifts[4] = {3, 1, 0, 1};
constexpr int vibrato_signs[2] = {1, -1};
static constexpr int vibrato_shifts[4] = {3, 1, 0, 1};
static constexpr int vibrato_signs[2] = {1, -1};
// Get just the top three bits of the period_.
const int top_freq = period_ >> (precision - 3);
@@ -61,7 +61,7 @@ public:
plus the degree of feedback to apply
*/
void apply_feedback(const LogSign first, const LogSign second, const int level) {
constexpr int masks[] = {0, ~0, ~0, ~0, ~0, ~0, ~0, ~0};
static constexpr int masks[] = {0, ~0, ~0, ~0, ~0, ~0, ~0, ~0};
phase_ += ((second.level(precision) + first.level(precision)) >> (8 - level)) & masks[level];
}
@@ -72,7 +72,7 @@ public:
void set_multiple(const int multiple) {
// This encodes the MUL -> multiple table given on page 12,
// multiplied by two.
constexpr int multipliers[] = {
static constexpr int multipliers[] = {
1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 20, 24, 24, 30, 30
};
assert(multiple < 16);
@@ -22,8 +22,8 @@ public:
/*!
@returns The output of waveform @c form at [integral] phase @c phase.
*/
static constexpr LogSign wave(Waveform form, int phase) {
constexpr int waveforms[4][4] = {
static constexpr LogSign wave(const Waveform form, const int phase) {
int waveforms[4][4] = {
{1023, 1023, 1023, 1023}, // Sine: don't mask in any quadrant.
{511, 511, 0, 0}, // Half sine: keep the first half intact, lock to 0 in the second half.
{511, 511, 511, 511}, // AbsSine: endlessly repeat the first half of the sine wave.
+38 -33
View File
@@ -314,7 +314,11 @@ void OPLL::update_all_channels() {
envelope_generators_[c + 9].update(oscillator_);
}
#define VOLUME(x) int16_t(((x) * total_volume_) >> 12)
const auto volume = [&](const int x) {
return int16_t(
(x * total_volume_) >> 12
);
};
if(rhythm_mode_enabled_) {
// Advance the rhythm envelope generators.
@@ -323,32 +327,32 @@ void OPLL::update_all_channels() {
}
// Fill in the melodic channels.
output_levels_[3] = VOLUME(melodic_output(0));
output_levels_[4] = VOLUME(melodic_output(1));
output_levels_[5] = VOLUME(melodic_output(2));
output_levels_[3] = volume(melodic_output(0));
output_levels_[4] = volume(melodic_output(1));
output_levels_[5] = volume(melodic_output(2));
output_levels_[9] = VOLUME(melodic_output(3));
output_levels_[10] = VOLUME(melodic_output(4));
output_levels_[11] = VOLUME(melodic_output(5));
output_levels_[9] = volume(melodic_output(3));
output_levels_[10] = volume(melodic_output(4));
output_levels_[11] = volume(melodic_output(5));
// Bass drum, which is a regular FM effect.
output_levels_[2] = output_levels_[15] = VOLUME(bass_drum());
output_levels_[2] = output_levels_[15] = volume(bass_drum());
oscillator_.update_lfsr();
// Tom tom, which is a single operator.
output_levels_[1] = output_levels_[14] = VOLUME(tom_tom());
output_levels_[1] = output_levels_[14] = volume(tom_tom());
oscillator_.update_lfsr();
// Snare.
output_levels_[6] = output_levels_[16] = VOLUME(snare_drum());
output_levels_[6] = output_levels_[16] = volume(snare_drum());
oscillator_.update_lfsr();
// Cymbal.
output_levels_[7] = output_levels_[17] = VOLUME(cymbal());
output_levels_[7] = output_levels_[17] = volume(cymbal());
oscillator_.update_lfsr();
// High-hat.
output_levels_[0] = output_levels_[13] = VOLUME(high_hat());
output_levels_[0] = output_levels_[13] = volume(high_hat());
oscillator_.update_lfsr();
// Unutilised slots.
@@ -365,32 +369,35 @@ void OPLL::update_all_channels() {
output_levels_[6] = output_levels_[7] = output_levels_[8] =
output_levels_[12] = output_levels_[13] = output_levels_[14] = 0;
output_levels_[3] = VOLUME(melodic_output(0));
output_levels_[4] = VOLUME(melodic_output(1));
output_levels_[5] = VOLUME(melodic_output(2));
output_levels_[3] = volume(melodic_output(0));
output_levels_[4] = volume(melodic_output(1));
output_levels_[5] = volume(melodic_output(2));
output_levels_[9] = VOLUME(melodic_output(3));
output_levels_[10] = VOLUME(melodic_output(4));
output_levels_[11] = VOLUME(melodic_output(5));
output_levels_[9] = volume(melodic_output(3));
output_levels_[10] = volume(melodic_output(4));
output_levels_[11] = volume(melodic_output(5));
output_levels_[15] = VOLUME(melodic_output(6));
output_levels_[16] = VOLUME(melodic_output(7));
output_levels_[17] = VOLUME(melodic_output(8));
output_levels_[15] = volume(melodic_output(6));
output_levels_[16] = volume(melodic_output(7));
output_levels_[17] = volume(melodic_output(8));
}
#undef VOLUME
// TODO: batch updates of the LFSR.
}
// TODO: verify attenuation scales pervasively below.
namespace {
#define ATTENUATION(x) ((x) << 7)
// TODO: verify attenuation scales pervasively below.
constexpr int attenuation(const int x) {
return x << 7;
}
}
int OPLL::melodic_output(const int channel) {
// The modulator always updates after the carrier, oddly enough. So calculate actual output first, based on the modulator's last value.
auto carrier = WaveformGenerator<period_precision>::wave(channels_[channel].carrier_waveform, phase_generators_[channel].scaled_phase(), channels_[channel].modulator_output);
carrier += envelope_generators_[channel].attenuation() + ATTENUATION(channels_[channel].attenuation) + key_level_scalers_[channel].attenuation();
carrier += envelope_generators_[channel].attenuation() + attenuation(channels_[channel].attenuation) + key_level_scalers_[channel].attenuation();
// Get the modulator's new value.
auto modulation = WaveformGenerator<period_precision>::wave(channels_[channel].modulator_waveform, phase_generators_[channel + 9].phase());
@@ -409,7 +416,7 @@ int OPLL::bass_drum() const {
modulation += rhythm_envelope_generators_[RhythmIndices::BassModulator].attenuation();
auto carrier = WaveformGenerator<period_precision>::wave(Waveform::Sine, phase_generators_[6].scaled_phase(), modulation);
carrier += rhythm_envelope_generators_[RhythmIndices::BassCarrier].attenuation() + ATTENUATION(channels_[6].attenuation);
carrier += rhythm_envelope_generators_[RhythmIndices::BassCarrier].attenuation() + attenuation(channels_[6].attenuation);
return carrier.level();
}
@@ -417,7 +424,7 @@ int OPLL::tom_tom() const {
// Use modulator 8 and the 'instrument' selection for channel 8 as an attenuation.
auto tom_tom = WaveformGenerator<period_precision>::wave(Waveform::Sine, phase_generators_[8 + 9].phase());
tom_tom += rhythm_envelope_generators_[RhythmIndices::TomTom].attenuation();
tom_tom += ATTENUATION(channels_[8].instrument);
tom_tom += attenuation(channels_[8].instrument);
return tom_tom.level();
}
@@ -425,7 +432,7 @@ int OPLL::snare_drum() const {
// Use modulator 7 and the carrier attenuation level for channel 7.
LogSign snare = WaveformGenerator<period_precision>::snare(oscillator_, phase_generators_[7 + 9].phase());
snare += rhythm_envelope_generators_[RhythmIndices::Snare].attenuation();
snare += ATTENUATION(channels_[7].attenuation);
snare += attenuation(channels_[7].attenuation);
return snare.level();
}
@@ -433,7 +440,7 @@ int OPLL::cymbal() const {
// Use modulator 7, carrier 8 and the attenuation level for channel 8.
LogSign cymbal = WaveformGenerator<period_precision>::cymbal(phase_generators_[8].phase(), phase_generators_[7 + 9].phase());
cymbal += rhythm_envelope_generators_[RhythmIndices::Cymbal].attenuation();
cymbal += ATTENUATION(channels_[8].attenuation);
cymbal += attenuation(channels_[8].attenuation);
return cymbal.level();
}
@@ -441,8 +448,6 @@ int OPLL::high_hat() const {
// Use modulator 7, carrier 8 a and the 'instrument' selection for channel 7 as an attenuation.
LogSign high_hat = WaveformGenerator<period_precision>::high_hat(oscillator_, phase_generators_[8].phase(), phase_generators_[7 + 9].phase());
high_hat += rhythm_envelope_generators_[RhythmIndices::HighHat].attenuation();
high_hat += ATTENUATION(channels_[7].instrument);
high_hat += attenuation(channels_[7].instrument);
return high_hat.level();
}
#undef ATTENUATION
+1 -1
View File
@@ -47,7 +47,7 @@ void RP5C01::run_for(const HalfCycles cycles) {
// Update time within day.
seconds_ += elapsed_seconds;
constexpr int day_length = 60 * 60 * 24;
static constexpr int day_length = 60 * 60 * 24;
if(seconds_ < day_length) {
return;
}
+351
View File
@@ -0,0 +1,351 @@
//
// SAA5050.cpp
// Clock Signal
//
// Created by Thomas Harte on 24/09/2025.
// Copyright © 2025 Thomas Harte. All rights reserved.
//
#include "SAA5050.hpp"
#include <algorithm>
#include <cstdint>
namespace {
// SAA5050 font, padded out to one byte per row. The least-significant five bits of each byte
// are the meaningful pixels for that row, with the LSB being on the right.
constexpr uint8_t font[][10] = {
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, // Character 32.
{0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x04, 0x00, 0x00, },
{0x00, 0x0a, 0x0a, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, },
{0x00, 0x06, 0x09, 0x08, 0x1c, 0x08, 0x08, 0x1f, 0x00, 0x00, },
{0x00, 0x0e, 0x15, 0x14, 0x0e, 0x05, 0x15, 0x0e, 0x00, 0x00, },
{0x00, 0x18, 0x19, 0x02, 0x04, 0x08, 0x13, 0x03, 0x00, 0x00, },
{0x00, 0x08, 0x14, 0x14, 0x08, 0x15, 0x12, 0x0d, 0x00, 0x00, },
{0x00, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, },
{0x00, 0x02, 0x04, 0x08, 0x08, 0x08, 0x04, 0x02, 0x00, 0x00, },
{0x00, 0x08, 0x04, 0x02, 0x02, 0x02, 0x04, 0x08, 0x00, 0x00, },
{0x00, 0x04, 0x15, 0x0e, 0x04, 0x0e, 0x15, 0x04, 0x00, 0x00, },
{0x00, 0x00, 0x04, 0x04, 0x1f, 0x04, 0x04, 0x00, 0x00, 0x00, },
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x08, 0x00, },
{0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, },
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, },
{0x00, 0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x00, 0x00, 0x00, },
{0x00, 0x04, 0x0a, 0x11, 0x11, 0x11, 0x0a, 0x04, 0x00, 0x00, },
{0x00, 0x04, 0x0c, 0x04, 0x04, 0x04, 0x04, 0x0e, 0x00, 0x00, },
{0x00, 0x0e, 0x11, 0x01, 0x06, 0x08, 0x10, 0x1f, 0x00, 0x00, },
{0x00, 0x1f, 0x01, 0x02, 0x06, 0x01, 0x11, 0x0e, 0x00, 0x00, },
{0x00, 0x02, 0x06, 0x0a, 0x12, 0x1f, 0x02, 0x02, 0x00, 0x00, },
{0x00, 0x1f, 0x10, 0x1e, 0x01, 0x01, 0x11, 0x0e, 0x00, 0x00, },
{0x00, 0x06, 0x08, 0x10, 0x1e, 0x11, 0x11, 0x0e, 0x00, 0x00, },
{0x00, 0x1f, 0x01, 0x02, 0x04, 0x08, 0x08, 0x08, 0x00, 0x00, },
{0x00, 0x0e, 0x11, 0x11, 0x0e, 0x11, 0x11, 0x0e, 0x00, 0x00, },
{0x00, 0x0e, 0x11, 0x11, 0x0f, 0x01, 0x02, 0x0c, 0x00, 0x00, },
{0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, },
{0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x04, 0x08, 0x00, },
{0x00, 0x02, 0x04, 0x08, 0x10, 0x08, 0x04, 0x02, 0x00, 0x00, },
{0x00, 0x00, 0x00, 0x1f, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, },
{0x00, 0x08, 0x04, 0x02, 0x01, 0x02, 0x04, 0x08, 0x00, 0x00, },
{0x00, 0x0e, 0x11, 0x02, 0x04, 0x04, 0x00, 0x04, 0x00, 0x00, },
{0x00, 0x0e, 0x11, 0x17, 0x15, 0x17, 0x10, 0x0e, 0x00, 0x00, },
{0x00, 0x04, 0x0a, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x00, 0x00, },
{0x00, 0x1e, 0x11, 0x11, 0x1e, 0x11, 0x11, 0x1e, 0x00, 0x00, },
{0x00, 0x0e, 0x11, 0x10, 0x10, 0x10, 0x11, 0x0e, 0x00, 0x00, },
{0x00, 0x1e, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, },
{0x00, 0x1f, 0x10, 0x10, 0x1e, 0x10, 0x10, 0x1f, 0x00, 0x00, },
{0x00, 0x1f, 0x10, 0x10, 0x1e, 0x10, 0x10, 0x10, 0x00, 0x00, },
{0x00, 0x0e, 0x11, 0x10, 0x10, 0x13, 0x11, 0x0f, 0x00, 0x00, },
{0x00, 0x11, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x11, 0x00, 0x00, },
{0x00, 0x0e, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0e, 0x00, 0x00, },
{0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x11, 0x0e, 0x00, 0x00, },
{0x00, 0x11, 0x12, 0x14, 0x18, 0x14, 0x12, 0x11, 0x00, 0x00, },
{0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1f, 0x00, 0x00, },
{0x00, 0x11, 0x1b, 0x15, 0x15, 0x11, 0x11, 0x11, 0x00, 0x00, },
{0x00, 0x11, 0x11, 0x19, 0x15, 0x13, 0x11, 0x11, 0x00, 0x00, },
{0x00, 0x0e, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, },
{0x00, 0x1e, 0x11, 0x11, 0x1e, 0x10, 0x10, 0x10, 0x00, 0x00, },
{0x00, 0x0e, 0x11, 0x11, 0x11, 0x15, 0x12, 0x0d, 0x00, 0x00, },
{0x00, 0x1e, 0x11, 0x11, 0x1e, 0x14, 0x12, 0x11, 0x00, 0x00, },
{0x00, 0x0e, 0x11, 0x10, 0x0e, 0x01, 0x11, 0x0e, 0x00, 0x00, },
{0x00, 0x1f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, },
{0x00, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, },
{0x00, 0x11, 0x11, 0x11, 0x0a, 0x0a, 0x04, 0x04, 0x00, 0x00, },
{0x00, 0x11, 0x11, 0x11, 0x15, 0x15, 0x15, 0x0a, 0x00, 0x00, },
{0x00, 0x11, 0x11, 0x0a, 0x04, 0x0a, 0x11, 0x11, 0x00, 0x00, },
{0x00, 0x11, 0x11, 0x0a, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, },
{0x00, 0x1f, 0x01, 0x02, 0x04, 0x08, 0x10, 0x1f, 0x00, 0x00, },
{0x00, 0x00, 0x04, 0x08, 0x1f, 0x08, 0x04, 0x00, 0x00, 0x00, },
{0x00, 0x10, 0x10, 0x10, 0x10, 0x16, 0x01, 0x02, 0x04, 0x07, },
{0x00, 0x00, 0x04, 0x02, 0x1f, 0x02, 0x04, 0x00, 0x00, 0x00, },
{0x00, 0x00, 0x04, 0x0e, 0x15, 0x04, 0x04, 0x00, 0x00, 0x00, },
{0x00, 0x0a, 0x0a, 0x1f, 0x0a, 0x1f, 0x0a, 0x0a, 0x00, 0x00, },
{0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, },
{0x00, 0x00, 0x00, 0x0e, 0x01, 0x0f, 0x11, 0x0f, 0x00, 0x00, },
{0x00, 0x10, 0x10, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, },
{0x00, 0x00, 0x00, 0x0f, 0x10, 0x10, 0x10, 0x0f, 0x00, 0x00, },
{0x00, 0x01, 0x01, 0x0f, 0x11, 0x11, 0x11, 0x0f, 0x00, 0x00, },
{0x00, 0x00, 0x00, 0x0e, 0x11, 0x1f, 0x10, 0x0e, 0x00, 0x00, },
{0x00, 0x02, 0x04, 0x04, 0x0e, 0x04, 0x04, 0x04, 0x00, 0x00, },
{0x00, 0x00, 0x00, 0x0f, 0x11, 0x11, 0x11, 0x0f, 0x01, 0x0e, },
{0x00, 0x10, 0x10, 0x1e, 0x11, 0x11, 0x11, 0x11, 0x00, 0x00, },
{0x00, 0x04, 0x00, 0x0c, 0x04, 0x04, 0x04, 0x0e, 0x00, 0x00, },
{0x00, 0x04, 0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, },
{0x00, 0x08, 0x08, 0x09, 0x0a, 0x0c, 0x0a, 0x09, 0x00, 0x00, },
{0x00, 0x0c, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0e, 0x00, 0x00, },
{0x00, 0x00, 0x00, 0x1a, 0x15, 0x15, 0x15, 0x15, 0x00, 0x00, },
{0x00, 0x00, 0x00, 0x1e, 0x11, 0x11, 0x11, 0x11, 0x00, 0x00, },
{0x00, 0x00, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, },
{0x00, 0x00, 0x00, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x10, 0x10, },
{0x00, 0x00, 0x00, 0x0f, 0x11, 0x11, 0x11, 0x0f, 0x01, 0x01, },
{0x00, 0x00, 0x00, 0x0b, 0x0c, 0x08, 0x08, 0x08, 0x00, 0x00, },
{0x00, 0x00, 0x00, 0x0f, 0x10, 0x0e, 0x01, 0x1e, 0x00, 0x00, },
{0x00, 0x04, 0x04, 0x0e, 0x04, 0x04, 0x04, 0x02, 0x00, 0x00, },
{0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x0f, 0x00, 0x00, },
{0x00, 0x00, 0x00, 0x11, 0x11, 0x0a, 0x0a, 0x04, 0x00, 0x00, },
{0x00, 0x00, 0x00, 0x11, 0x11, 0x15, 0x15, 0x0a, 0x00, 0x00, },
{0x00, 0x00, 0x00, 0x11, 0x0a, 0x04, 0x0a, 0x11, 0x00, 0x00, },
{0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x0f, 0x01, 0x0e, },
{0x00, 0x00, 0x00, 0x1f, 0x02, 0x04, 0x08, 0x1f, 0x00, 0x00, },
{0x00, 0x10, 0x10, 0x10, 0x10, 0x11, 0x03, 0x05, 0x07, 0x01, },
{0x00, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x00, 0x00, },
{0x00, 0x18, 0x04, 0x18, 0x04, 0x19, 0x03, 0x05, 0x07, 0x01, },
{0x00, 0x00, 0x04, 0x00, 0x1f, 0x00, 0x04, 0x00, 0x00, 0x00, },
{0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, },
};
enum ControlCode: uint8_t {
RedAlpha = 0x01,
GreenAlpha = 0x02,
YellowAlpha = 0x03,
BlueAlpha = 0x04,
MagentaAlpha = 0x05,
CyanAlpha = 0x06,
WhiteAlpha = 0x07,
Flash = 0x08,
Steady = 0x09,
RedGraphics = 0x11,
GreenGraphics = 0x12,
YellowGraphics = 0x13,
BlueGraphics = 0x14,
MagentaGraphics = 0x15,
CyanGraphics = 0x16,
WhiteGraphics = 0x17,
Conceal = 0x18,
ContinuousGraphics = 0x19,
SeparatedGraphics = 0x1a,
NormalHeight = 0xc,
DoubleHeight = 0xd,
BlackBackground = 0x1c,
NewBackground = 0x1d,
HoldGraphics = 0x1e,
ReleaseGraphics = 0x1f,
};}
using namespace Mullard;
void SAA5050Serialiser::begin_frame(const bool is_odd) {
line_ = -2;
row_ = 0;
odd_frame_ = is_odd;
row_has_double_height_ = false;
double_height_offset_ = 0;
++frame_counter_;
}
void SAA5050Serialiser::begin_line() {
line_ += 2;
if(line_ == 20) {
line_ = 0;
++row_;
if(row_has_double_height_) {
double_height_offset_ = (double_height_offset_ + 5) % 10;
}
row_has_double_height_ = false;
}
output_.reset();
has_output_ = false;
apply_control(ControlCode::WhiteAlpha);
apply_control(ControlCode::Steady);
apply_control(ControlCode::NormalHeight);
apply_control(ControlCode::ContinuousGraphics);
apply_control(ControlCode::BlackBackground);
apply_control(ControlCode::ReleaseGraphics);
}
bool SAA5050Serialiser::has_output() const {
return has_output_;
}
SAA5050Serialiser::Output SAA5050Serialiser::output() {
has_output_ = false;
return output_;
}
void SAA5050Serialiser::apply_control(const uint8_t value) {
const auto set_alpha = [&](const uint8_t colour) {
alpha_mode_ = true;
conceal_ = false;
output_.alpha = colour;
hold_graphics_ = false;
};
const auto set_graphics = [&](const uint8_t colour) {
alpha_mode_ = false;
conceal_ = false;
output_.alpha = colour;
hold_graphics_ = false;
};
switch(value) {
default: break;
case RedAlpha: set_alpha(0b100); break;
case GreenAlpha: set_alpha(0b010); break;
case YellowAlpha: set_alpha(0b110); break;
case BlueAlpha: set_alpha(0b001); break;
case MagentaAlpha: set_alpha(0b101); break;
case CyanAlpha: set_alpha(0b011); break;
case WhiteAlpha: set_alpha(0b111); break;
case Flash: flash_ = true; break;
case Steady: flash_ = false; break;
case RedGraphics: set_graphics(0b100); break;
case GreenGraphics: set_graphics(0b010); break;
case YellowGraphics: set_graphics(0b110); break;
case BlueGraphics: set_graphics(0b001); break;
case MagentaGraphics: set_graphics(0b101); break;
case CyanGraphics: set_graphics(0b011); break;
case WhiteGraphics: set_graphics(0b111); break;
case Conceal: conceal_ = true; break;
case ContinuousGraphics: separated_graphics_ = false; break;
case SeparatedGraphics: separated_graphics_ = true; break;
case NormalHeight: double_height_ = false; break;
case DoubleHeight: double_height_ = row_has_double_height_ = true; break;
case BlackBackground: output_.background = 0; break;
case NewBackground: output_.background = output_.alpha; break;
case HoldGraphics: hold_graphics_ = true; break;
case ReleaseGraphics: hold_graphics_ = false; last_graphic_ = 32; break;
}
}
void SAA5050Serialiser::set_reveal(const bool reveal) {
reveal_ = reveal;
}
void SAA5050Serialiser::add(const Numeric::SizedInt<7> c) {
has_output_ = true;
if(c.get() < 32) {
if(hold_graphics_) {
load_pixels(last_graphic_);
} else {
output_.reset();
}
apply_control(c.get());
return;
}
load_pixels(c.get());
}
void SAA5050Serialiser::load_pixels(const uint8_t c) {
if(flash_ && ((frame_counter_&31) > 23)) { // Complete guess on the blink period here.
output_.reset();
return;
}
if(conceal_ && !reveal_) {
output_.reset();
return;
}
// Divert into graphics only if both the mode and the character code allows it.
if(!alpha_mode_ && (c & (1 << 5))) {
last_graphic_ = c;
// Graphics layout:
//
// |----|----|
// | | |
// | b0 | b1 |
// | | |
// |----|----|
// | | |
// | b2 | b3 |
// | | |
// |----|----|
// | | |
// | b4 | b6 |
// | | |
// |----|----|
if(separated_graphics_ && (line_ == 6 || line_ == 12 || line_ == 18)) {
output_.reset();
return;
}
uint8_t pixels;
if(line_ < 6) {
pixels =
((c & 1) ? 0b111'000 : 0) |
((c & 2) ? 0b000'111 : 0);
} else if(line_ < 14) {
pixels =
((c & 4) ? 0b111'000 : 0) |
((c & 8) ? 0b000'111 : 0);
} else {
pixels =
((c & 16) ? 0b111'000 : 0) |
((c & 64) ? 0b000'111 : 0);
}
if(separated_graphics_) {
pixels &= 0b011'011;
}
output_.load(pixels);
return;
}
if(double_height_) {
const auto top_address = (line_ >> 2) + double_height_offset_;
const uint8_t top = font[c - 32][top_address];
const uint8_t bottom = font[c - 32][std::min(9, top_address + 1)];
if(line_ & 2) {
output_.load(bottom, top);
} else {
output_.load(top, bottom);
}
} else {
if(double_height_offset_) {
output_.reset();
} else {
const auto top_address = line_ >> 1;
const uint8_t top = font[c - 32][top_address];
const uint8_t bottom = font[c - 32][std::min(9, top_address + 1)];
if(odd_frame_) {
output_.load(bottom, top);
} else {
output_.load(top, bottom);
}
}
}
}
+100
View File
@@ -0,0 +1,100 @@
//
// SAA5050.hpp
// Clock Signal
//
// Created by Thomas Harte on 24/09/2025.
// Copyright © 2025 Thomas Harte. All rights reserved.
//
#pragma once
#include <cstdint>
#include "Numeric/SizedInt.hpp"
namespace Mullard {
struct SAA5050Serialiser {
public:
void begin_frame(bool is_odd);
void begin_line();
void add(Numeric::SizedInt<7>);
struct Output {
void reset() {
top_ = bottom_ = 0;
}
void load(const uint8_t top, const uint8_t bottom) {
top_ = top;
bottom_ = bottom;
}
void load(const uint8_t top) {
top_ = bottom_ = top;
}
// The low twelve bits of this word provide 1bpp pixels.
uint16_t pixels() const {
// Adapted from old ElectrEm source; my original provenance for this being the correct logic is unknown.
uint16_t wide =
((top_ & 0b000001) ? 0b0000'0000'0011 : 0) |
((top_ & 0b000010) ? 0b0000'0000'1100 : 0) |
((top_ & 0b000100) ? 0b0000'0011'0000 : 0) |
((top_ & 0b001000) ? 0b0000'1100'0000 : 0) |
((top_ & 0b010000) ? 0b0011'0000'0000 : 0) |
((top_ & 0b100000) ? 0b1100'0000'0000 : 0);
if(top_ != bottom_) {
if((top_ & 0b10000) && (bottom_ & 0b11000) == 0b01000) wide |= 0b0000'1000'0000;
if((top_ & 0b01000) && (bottom_ & 0b01100) == 0b00100) wide |= 0b0000'0010'0000;
if((top_ & 0b00100) && (bottom_ & 0b00110) == 0b00010) wide |= 0b0000'0000'1000;
if((top_ & 0b00010) && (bottom_ & 0b00011) == 0b00001) wide |= 0b0000'0000'0010;
if((top_ & 0b01000) && (bottom_ & 0b11000) == 0b10000) wide |= 0b0001'0000'0000;
if((top_ & 0b00100) && (bottom_ & 0b01100) == 0b01000) wide |= 0b0000'0100'0000;
if((top_ & 0b00010) && (bottom_ & 0b00110) == 0b00100) wide |= 0b0000'0001'0000;
if((top_ & 0b00001) && (bottom_ & 0b00011) == 0b00010) wide |= 0b0000'0000'0100;
}
return wide;
}
// Colours for foreground and background pixels.
uint8_t alpha;
uint8_t background;
private:
uint8_t top_, bottom_;
};
bool has_output() const;
Output output();
void set_reveal(bool);
private:
Output output_;
bool has_output_ = false;
int row_, line_;
bool odd_frame_;
bool flash_ = false;
int frame_counter_ = 0;
bool reveal_ = false;
bool conceal_ = false;
bool alpha_mode_ = true;
bool separated_graphics_ = false;
bool double_height_ = false;
bool row_has_double_height_ = false;
int double_height_offset_ = 0;
bool hold_graphics_ = false;
uint8_t last_graphic_ = 0;
void load_pixels(const uint8_t);
void apply_control(const uint8_t);
};
}
+433
View File
@@ -0,0 +1,433 @@
//
// SID.cpp
// Clock Signal
//
// Created by Thomas Harte on 07/11/2025.
// Copyright © 2025 Thomas Harte. All rights reserved.
//
#include "SID.hpp"
// Sources used:
//
// (1) SID Article v0.2 at https://github.com/ImreOlajos/SID-Article
// (2) Technical SID Information/Software stuff at http://www.sidmusic.org/sid/sidtech2.html
// (3) SID 6581/8580 (Sound Interface Device) reference at https://oxyron.de/html/registers_sid.html
using namespace MOS::SID;
SID::SID(Concurrency::AsyncTaskQueue<false> &audio_queue) :
audio_queue_(audio_queue),
output_filter_(
SignalProcessing::BiquadFilter::Type::LowPass,
1000000.0f,
15000.0f
) {}
// MARK: - Programmer interface.
void SID::write(const Numeric::SizedInt<5> address, const uint8_t value) {
last_write_ = value;
audio_queue_.enqueue([=, this] {
const auto voice = [&]() -> Voice & {
return voices_[address.get() / 7];
};
const auto oscillator = [&]() -> Voice::Oscillator & {
return voice().oscillator;
};
const auto adsr = [&]() -> Voice::ADSR & {
return voice().adsr;
};
switch(address.get()) {
case 0x00: case 0x07: case 0x0e:
oscillator().pitch = (oscillator().pitch & 0xff'00'00) | uint32_t(value << 8);
break;
case 0x01: case 0x08: case 0x0f:
oscillator().pitch = (oscillator().pitch & 0x00'ff'00) | uint32_t(value << 16);
break;
case 0x02: case 0x09: case 0x10:
oscillator().pulse_width = (oscillator().pitch & 0xf0'00'00'00) | uint32_t(value << 20);
break;
case 0x03: case 0x0a: case 0x11:
// The top bit of the phase counter is inverted; since it'll be compared directly with the
// pulse width, invert that bit too.
oscillator().pulse_width =
(
(oscillator().pitch & 0x0f'f0'00'00) |
uint32_t(value << 28)
);
break;
case 0x04: case 0x0b: case 0x12:
voice().set_control(value);
break;
case 0x05: case 0x0c: case 0x13:
adsr().attack = value >> 4;
adsr().decay = value;
adsr().set_phase(adsr().phase);
break;
case 0x06: case 0x0d: case 0x14:
adsr().sustain = (value >> 4) | (value & 0xf0);
adsr().release = value;
adsr().set_phase(adsr().phase);
break;
case 0x15:
filter_cutoff_.load<0, 3>(value);
update_filter();
break;
case 0x16:
filter_cutoff_.load<3>(value);
update_filter();
break;
case 0x17:
filter_channels_ = value;
filter_resonance_ = value >> 4;
update_filter();
break;
case 0x18:
volume_ = value & 0x0f;
filter_mode_ = value >> 4;
voice3_disable_ = value & 0x80;
update_filter();
break;
}
});
}
void SID::set_potentometer_input(const int index, const uint8_t value) {
potentometers_[index] = value;
}
void SID::update_filter() {
using Type = SignalProcessing::BiquadFilter::Type;
Type type = Type::AllPass;
switch(filter_mode_.get()) {
case 0:
filter_ = SignalProcessing::BiquadFilter();
return;
case 1:
case 3: type = Type::LowPass; break;
case 2: type = Type::BandPass; break;
case 5: type = Type::Notch; break;
case 4:
case 6: type = Type::HighPass; break;
case 7: type = Type::AllPass; break;
}
filter_.configure(
type,
1'000'000.0f,
30.0f + float(filter_cutoff_.get()) * 5.8f,
0.707f + float(filter_resonance_.get()) * 0.2862f,
6.0f,
true
);
// Filter cutoff: the data sheet provides that it is linear, and "approximate Cutoff Frequency
// ranges between 30Hz and 12KHz [with recommended externally-supplied capacitors]."
//
// It's an 11-bit number, so the above is "approximate"ly right.
// Resonance: a complete from-thin-air guess. The data sheet says merely:
//
// "There are 16 Resonance settings ranging from about 0.707 (Critical Damping) for a count of 0
// to a maximum for a count of 15"
//
// i.e. no information is given on the maximum. I've taken it to be 5-ish per commentary on more general sites
// that 5 is a typical ceiling for the resonance factor.
}
uint8_t SID::read(const Numeric::SizedInt<5> address) {
switch(address.get()) {
default: return last_write_;
case 0x19: return potentometers_[0];
case 0x1a: return potentometers_[1];
case 0x1b:
case 0x1c:
// Ensure all channels are entirely up to date.
audio_queue_.spin_flush();
return (address == 0x1c) ? voices_[2].adsr.envelope : uint8_t(voices_[2].output(voices_[1]) >> 4);
}
}
// MARK: - Oscillators.
void Voice::Oscillator::reset_phase() {
phase = PhaseReload;
}
bool Voice::Oscillator::did_raise_b23() const {
return previous_phase > phase;
}
bool Voice::Oscillator::did_raise_b19() const {
static constexpr int NoiseBit = 1 << (19 + 8);
return (previous_phase ^ phase) & phase & NoiseBit;
}
uint16_t Voice::Oscillator::sawtooth_output() const {
return (phase >> 20) ^ 0x800;
}
// MARK: - Noise generator.
uint16_t Voice::NoiseGenerator::output() const {
// Uses bits: 20, 18, 14, 11, 9, 5, 2 and 0, plus four more zero bits.
const uint16_t output =
((noise >> 9) & 0b1000'0000'0000) | // b20 -> b11
((noise >> 8) & 0b0100'0000'0000) | // b18 -> b10
((noise >> 5) & 0b0010'0000'0000) | // b14 -> b9
((noise >> 3) & 0b0001'0000'0000) | // b11 -> b8
((noise >> 2) & 0b0000'1000'0000) | // b9 -> b7
((noise << 1) & 0b0000'0100'0000) | // b5 -> b6
((noise << 3) & 0b0000'0010'0000) | // b2 -> b5
((noise << 4) & 0b0000'0001'0000); // b0 -> b4
assert(output <= Voice::MaxWaveformValue);
return output;
}
void Voice::NoiseGenerator::update(const bool test) {
noise =
(noise << 1) |
(((noise >> 17) ^ ((noise >> 22) | test)) & 1);
}
// MARK: - ADSR.
void Voice::ADSR::set_phase(const Phase new_phase) {
static constexpr uint16_t rate_prescaler[] = {
9, 32, 63, 95, 149, 220, 267, 313, 392, 977, 1954, 3126, 3907, 11720, 19532, 31251
};
static_assert(sizeof(rate_prescaler) / sizeof(*rate_prescaler) == 16);
phase = new_phase;
switch(phase) {
case Phase::Attack: rate_counter_target = rate_prescaler[attack.get()]; break;
case Phase::DecayAndHold: rate_counter_target = rate_prescaler[decay.get()]; break;
case Phase::Release: rate_counter_target = rate_prescaler[release.get()]; break;
}
}
// MARK: - Voices.
void Voice::set_control(const uint8_t new_control) {
const bool old_gate = gate();
control = new_control;
if(gate() && !old_gate) {
adsr.set_phase(ADSR::Phase::Attack);
} else if(!gate() && old_gate) {
adsr.set_phase(ADSR::Phase::Release);
}
}
bool Voice::noise() const { return control.bit<7>(); }
bool Voice::pulse() const { return control.bit<6>(); }
bool Voice::sawtooth() const { return control.bit<5>(); }
bool Voice::triangle() const { return control.bit<4>(); }
bool Voice::test() const { return control.bit<3>(); }
bool Voice::ring_mod() const { return control.bit<2>(); }
bool Voice::sync() const { return control.bit<1>(); }
bool Voice::gate() const { return control.bit<0>(); }
void Voice::update() {
// Oscillator.
oscillator.previous_phase = oscillator.phase;
if(test()) {
oscillator.phase = 0;
} else {
oscillator.phase += oscillator.pitch;
if(oscillator.did_raise_b19()) {
noise_generator.update(test());
}
}
// ADSR.
// First prescalar, which is a function of the programmer-set rate.
++ adsr.rate_counter;
if(adsr.rate_counter == adsr.rate_counter_target) {
adsr.rate_counter = 0;
// Second prescalar, which approximates an exponential.
static constexpr uint8_t exponential_prescaler[] = {
1, // 0
30, 30, 30, 30, 30, 30, // 16
16, 16, 16, 16, 16, 16, 16, 16, // 714
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, // 1526
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 2754
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 5594
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1,
};
static_assert(sizeof(exponential_prescaler) == 256);
static_assert(exponential_prescaler[0] == 1);
static_assert(exponential_prescaler[1] == 30);
static_assert(exponential_prescaler[6] == 30);
static_assert(exponential_prescaler[7] == 16);
static_assert(exponential_prescaler[14] == 16);
static_assert(exponential_prescaler[15] == 8);
static_assert(exponential_prescaler[26] == 8);
static_assert(exponential_prescaler[27] == 4);
static_assert(exponential_prescaler[54] == 4);
static_assert(exponential_prescaler[55] == 2);
static_assert(exponential_prescaler[94] == 2);
static_assert(exponential_prescaler[95] == 1);
static_assert(exponential_prescaler[255] == 1);
if(adsr.phase == ADSR::Phase::Attack) {
++adsr.envelope;
// TODO: what really resets the exponential counter? If anything?
adsr.exponential_counter = 0;
if(adsr.envelope == 0xff) {
adsr.set_phase(ADSR::Phase::DecayAndHold);
}
} else {
++adsr.exponential_counter;
if(adsr.exponential_counter == exponential_prescaler[adsr.envelope]) {
adsr.exponential_counter = 0;
if(adsr.envelope && (adsr.envelope != adsr.sustain || adsr.phase != ADSR::Phase::DecayAndHold)) {
--adsr.envelope;
}
}
}
}
}
void Voice::synchronise(const Voice &prior) {
// Only oscillator work to do here.
if(
sync() &&
prior.oscillator.did_raise_b23()
) {
oscillator.phase = Oscillator::PhaseReload;
}
}
uint16_t Voice::pulse_output() const {
return (
(oscillator.phase ^ 0x8000'0000) < oscillator.pulse_width
) ? 0 : MaxWaveformValue;
}
uint16_t Voice::triangle_output(const Voice &prior) const {
const uint16_t sawtooth = oscillator.sawtooth_output();
const uint16_t xor_mask1 = sawtooth;
const uint16_t xor_mask2 = ring_mod() ? prior.sawtooth() : 0;
const uint16_t xor_mask = ((xor_mask1 ^ xor_mask2) & 0x800) ? 0xfff : 0x000;
return ((sawtooth << 1) ^ xor_mask) & 0xfff;
}
uint16_t Voice::output(const Voice &prior) const {
// TODO: true composite waves.
//
// My current understanding on this: if multiple waveforms are enabled, the pull to zero beats the
// pull to one on any line where the two compete. But the twist is that the lines are not necessarily
// one per bit since they lead to a common ground. Ummm, I think.
//
// Anyway, first pass: logical AND. It's not right. It will temporarily do.
uint16_t output = MaxWaveformValue;
if(pulse()) output &= pulse_output();
if(sawtooth()) output &= oscillator.sawtooth_output();
if(triangle()) output &= triangle_output(prior);
if(noise()) output &= noise_generator.output();
return (output * adsr.envelope) / 255;
}
// MARK: - Wave generation
void SID::set_sample_volume_range(const std::int16_t range) {
range_ = range;
}
bool SID::is_zero_level() const {
return false;
}
template <Outputs::Speaker::Action action>
void SID::apply_samples(const std::size_t number_of_samples, Outputs::Speaker::MonoSample *const target) {
for(std::size_t c = 0; c < number_of_samples; c++) {
// Advance phase.
voices_[0].update();
voices_[1].update();
voices_[2].update();
// Apply hard synchronisations.
voices_[0].synchronise(voices_[2]);
voices_[1].synchronise(voices_[0]);
voices_[2].synchronise(voices_[1]);
// Construct filtered and unfiltered output.
const uint16_t outputs[3] = {
voices_[0].output(voices_[2]),
voices_[1].output(voices_[0]),
voices_[2].output(voices_[1]),
};
const uint16_t direct_sample =
(filter_channels_.bit<0>() ? 0 : outputs[0]) +
(filter_channels_.bit<1>() ? 0 : outputs[1]) +
(filter_channels_.bit<2>() || voice3_disable_ ? 0 : outputs[2]);
const int16_t filtered_sample =
filter_.apply(
(filter_channels_.bit<0>() ? outputs[0] : 0) +
(filter_channels_.bit<1>() ? outputs[1] : 0) +
(filter_channels_.bit<2>() ? outputs[2] : 0)
);
// Sum, apply volume and output.
const auto sample = output_filter_.apply(int16_t(
(
volume_ * (
direct_sample +
filtered_sample
- 227 // DC offset.
)
- 88732
) / 3
));
// Maximum range of above: 15 * (4095 * 3 - 227) = [-3405, 180870]
// So subtracting 88732 will move to the centre of the range, and 3 is the smallest
// integer that avoids clipping.
Outputs::Speaker::apply<action>(
target[c],
Outputs::Speaker::MonoSample((sample * range_) >> 16)
);
}
}
template void SID::apply_samples<Outputs::Speaker::Action::Mix>(
std::size_t, Outputs::Speaker::MonoSample *);
template void SID::apply_samples<Outputs::Speaker::Action::Store>(
std::size_t, Outputs::Speaker::MonoSample *);
template void SID::apply_samples<Outputs::Speaker::Action::Ignore>(
std::size_t, Outputs::Speaker::MonoSample *);
+129
View File
@@ -0,0 +1,129 @@
//
// SID.hpp
// Clock Signal
//
// Created by Thomas Harte on 07/11/2025.
// Copyright © 2025 Thomas Harte. All rights reserved.
//
#pragma once
#include "Numeric/SizedInt.hpp"
#include "Concurrency/AsyncTaskQueue.hpp"
#include "Outputs/Speaker/Implementation/BufferSource.hpp"
#include "SignalProcessing/BiquadFilter.hpp"
namespace MOS::SID {
struct Voice {
static constexpr uint16_t MaxWaveformValue = (1 << 12) - 1;
struct Oscillator {
// Programmer inputs.
uint32_t pitch = 0;
uint32_t pulse_width = 0;
// State.
//
// A real SID has a 24-bit phase counter and does various things when the top bit transitions from 0 to 1.
// This implementation maintains a 32-bit phase counter in which the low byte is unused and the top bit
// is inverted. That saves the cost of any masking and makes the 0 -> 1 transition test actually a 1 -> 0
// transition test, which can be phrased simply as after < before. Sadly overflow of signed integers is
// still undefined behaviour in C++ at the time of writing.
static constexpr uint32_t PhaseReload = 0x8000'0000;
uint32_t phase = PhaseReload;
uint32_t previous_phase = PhaseReload;
void reset_phase();
bool did_raise_b23() const;
bool did_raise_b19() const;
uint16_t sawtooth_output() const;
} oscillator;
struct ADSR {
// Programmer inputs.
Numeric::SizedInt<4> attack;
Numeric::SizedInt<4> decay;
Numeric::SizedInt<4> release;
Numeric::SizedInt<8> sustain;
// State.
enum class Phase {
Attack,
DecayAndHold,
Release,
} phase = Phase::Release;
Numeric::SizedInt<15> rate_counter;
Numeric::SizedInt<15> rate_counter_target;
uint8_t exponential_counter;
uint8_t envelope;
void set_phase(const Phase);
} adsr;
struct NoiseGenerator {
static constexpr uint32_t NoiseReload = 0x7'ffff;
uint32_t noise = NoiseReload;
uint16_t output() const;
void update(const bool test);
} noise_generator;
void set_control(const uint8_t);
void update();
void synchronise(const Voice &prior);
uint16_t output(const Voice &prior) const;
private:
Numeric::SizedInt<8> control;
bool noise() const;
bool pulse() const;
bool sawtooth() const;
bool triangle() const;
bool test() const;
bool ring_mod() const;
bool sync() const;
bool gate() const;
uint16_t pulse_output() const;
uint16_t triangle_output(const Voice &prior) const;
};
class SID: public Outputs::Speaker::BufferSource<SID, false> {
public:
SID(Concurrency::AsyncTaskQueue<false> &audio_queue);
void write(Numeric::SizedInt<5> address, uint8_t value);
uint8_t read(Numeric::SizedInt<5> address);
void set_potentometer_input(int index, uint8_t value);
// Outputs::Speaker::BufferSource.
template <Outputs::Speaker::Action action>
void apply_samples(std::size_t, Outputs::Speaker::MonoSample *);
bool is_zero_level() const;
void set_sample_volume_range(std::int16_t);
private:
Concurrency::AsyncTaskQueue<false> &audio_queue_;
Voice voices_[3];
uint8_t last_write_;
int16_t range_ = 0;
uint8_t volume_ = 0;
SignalProcessing::BiquadFilter filter_;
Numeric::SizedInt<11> filter_cutoff_;
Numeric::SizedInt<4> filter_resonance_;
Numeric::SizedInt<4> filter_channels_;
Numeric::SizedInt<3> filter_mode_;
bool voice3_disable_;
void update_filter();
SignalProcessing::BiquadFilter output_filter_;
uint8_t potentometers_[2]{};
};
}
+1 -1
View File
@@ -101,7 +101,7 @@ template <bool lsb_first, typename IntT> void Line<include_clock>::write_interna
bit = levels & 1;
levels >>= 1;
} else {
constexpr auto top_bit = IntT(0x80) << ((sizeof(IntT) - 1) * 8);
static constexpr auto top_bit = IntT(0x80) << ((sizeof(IntT) - 1) * 8);
bit = levels & top_bit;
levels <<= 1;
}
-1
View File
@@ -137,7 +137,6 @@ private:
Defines an RS-232-esque srial port.
*/
class Port {
public:
};
}
+85
View File
@@ -0,0 +1,85 @@
//
// uPD7002.cpp
// Clock Signal
//
// Created by Thomas Harte on 17/09/2025.
// Copyright © 2025 Thomas Harte. All rights reserved.
//
#include "uPD7002.hpp"
using namespace NEC;
uPD7002::uPD7002(const HalfCycles clock_rate) {
// Per the BBC AUG: "8 bit conversions typically take 4 ms to complete whereas 10 bit
// conversions typically take 10 ms to complete".
fast_period_ = clock_rate / 250;
slow_period_ = clock_rate / 100;
}
void uPD7002::run_for(const HalfCycles count) {
if(!conversion_time_remaining_) {
return;
}
if(count >= conversion_time_remaining_) {
conversion_time_remaining_ = HalfCycles(0);
result_ = uint16_t(inputs_[channel_] * 65535.0f) & (high_precision_ ? 0xfff0 : 0xff00);
set_interrupt(true);
return;
}
conversion_time_remaining_ -= count;
}
bool uPD7002::interrupt() const {
return interrupt_;
}
void uPD7002::write(const uint16_t address, const uint8_t value) {
const auto local_address = address & 3;
if(!local_address) {
channel_ = value & 0b0000'0011;
spare_ = value & 0b0000'0100;
high_precision_ = value & 0b0000'1000;
conversion_time_remaining_ = high_precision_ ? slow_period_ : fast_period_;
set_interrupt(false);
return;
}
}
uint8_t uPD7002::read(const uint16_t address) {
switch(address & 3) {
default:
case 0: return status();
case 1:
set_interrupt(false);
return uint8_t(result_ >> 8);
case 2: return uint8_t(result_);
case 3: return 0xff;
}
}
uint8_t uPD7002::status() const {
return
channel_ |
spare_ |
(high_precision_ ? 0x08 : 0) |
((result_ >> 14) & 0x30) |
(conversion_time_remaining_ > HalfCycles(0) ? 0x00 : 0x40) |
(interrupt_ ? 0x00 : 0x80);
}
void uPD7002::set_delegate(Delegate *const delegate) {
delegate_ = delegate;
}
void uPD7002::set_interrupt(const bool value) {
if(interrupt_ == value) return;
interrupt_ = value;
if(delegate_) delegate_->did_change_interrupt_status(*this);
}
void uPD7002::set_input(const int channel, const float value) {
inputs_[channel] = value;
}
+51
View File
@@ -0,0 +1,51 @@
//
// uPD7002.hpp
// Clock Signal
//
// Created by Thomas Harte on 17/09/2025.
// Copyright © 2025 Thomas Harte. All rights reserved.
//
#include "ClockReceiver/ClockReceiver.hpp"
namespace NEC {
class uPD7002 {
public:
/// Constructs a PD7002 that will receive @c run_for updates at the specified clock rate.
uPD7002(HalfCycles clock_rate);
void run_for(HalfCycles);
/// @returns The current state of the interrupt line.
bool interrupt() const;
/// Defines a mean for an observer to receive notifications upon updates to the interrupt line.
struct Delegate {
virtual void did_change_interrupt_status(uPD7002 &) = 0;
};
void set_delegate(Delegate *);
void write(uint16_t address, uint8_t value);
uint8_t read(uint16_t address);
/// Sets the floating point value, which should be in the range [0.0, 1.0], for the signal currently
/// being supplied to @c channel.
void set_input(int channel, float value);
private:
float inputs_[4]{};
uint16_t result_ = 0;
bool interrupt_ = false;
uint8_t channel_ = 0, spare_ = 0;
bool high_precision_ = false;
HalfCycles conversion_time_remaining_{};
HalfCycles fast_period_, slow_period_;
uint8_t status() const;
void set_interrupt(bool);
Delegate *delegate_ = nullptr;
};
}
+80 -33
View File
@@ -28,7 +28,7 @@ template <typename Performer> struct TaskQueueStorage {
protected:
void update() {
auto time_now = Time::nanos_now();
const auto time_now = Time::nanos_now();
performer.perform(time_now - last_fired_);
last_fired_ = time_now;
}
@@ -40,9 +40,12 @@ private:
/// An implementation detail; provides a no-op implementation of time advances for TaskQueues without a Performer.
template <> struct TaskQueueStorage<void> {
TaskQueueStorage() {}
protected:
void update() {}
};
protected:
void update() {}
struct EnqueueDelegate {
virtual std::function<void(void)> prepare_enqueue() = 0;
};
/*!
@@ -64,12 +67,22 @@ template <> struct TaskQueueStorage<void> {
action occupies the asynchronous thread for long enough. So it is not true that @c perform will be
called once per action.
*/
template <bool perform_automatically, bool start_immediately = true, typename Performer = void> class AsyncTaskQueue: public TaskQueueStorage<Performer> {
template <
bool perform_automatically,
bool start_immediately = true,
bool use_enqueue_delegate = false,
typename Performer = void
>
class AsyncTaskQueue: public TaskQueueStorage<Performer> {
public:
void set_enqueue_delegate(EnqueueDelegate *const delegate) {
enqueue_delegate_ = delegate;
}
template <typename... Args> AsyncTaskQueue(Args&&... args) :
TaskQueueStorage<Performer>(std::forward<Args>(args)...) {
if constexpr (start_immediately) {
start();
start_impl();
}
}
@@ -84,7 +97,10 @@ public:
/// on the same thread as the performer, after the performer has been updated
/// to 'now'.
void enqueue(const std::function<void(void)> &post_action) {
std::lock_guard guard(condition_mutex_);
const std::lock_guard guard(condition_mutex_);
if constexpr (use_enqueue_delegate) {
actions_.push_back(enqueue_delegate_->prepare_enqueue());
}
actions_.push_back(post_action);
if constexpr (perform_automatically) {
@@ -92,8 +108,15 @@ public:
}
}
/// @returns The number of items currently enqueued.
size_t size() {
const std::lock_guard guard(condition_mutex_);
return actions_.size();
}
/// Causes any enqueued actions that are not yet scheduled to be scheduled.
void perform() {
static_assert(!perform_automatically);
if(actions_.empty()) {
return;
}
@@ -106,7 +129,7 @@ public:
/// The queue cannot be restarted; this is a destructive action.
void stop() {
if(thread_.joinable()) {
should_quit_ = true;
should_quit_.store(true, std::memory_order_relaxed);
enqueue([] {});
if constexpr (!perform_automatically) {
perform();
@@ -119,36 +142,13 @@ public:
///
/// This is not guaranteed safely to restart a stopped queue.
void start() {
thread_ = std::thread{
[this] {
ActionVector actions;
// Continue until told to quit.
while(!should_quit_) {
// Wait for new actions to be signalled, and grab them.
std::unique_lock lock(condition_mutex_);
while(actions_.empty() && !should_quit_) {
condition_.wait(lock);
}
std::swap(actions, actions_);
lock.unlock();
// Update to now (which is possibly a no-op).
TaskQueueStorage<Performer>::update();
// Perform the actions and destroy them.
for(const auto &action: actions) {
action();
}
actions.clear();
}
}
};
static_assert(!start_immediately);
start_impl();
}
/// Schedules any remaining unscheduled work, then blocks synchronously
/// until all scheduled work has been performed.
void flush() {
void lock_flush() {
std::mutex flush_mutex;
std::condition_variable flush_condition;
bool has_run = false;
@@ -167,11 +167,58 @@ public:
flush_condition.wait(lock, [&has_run] { return has_run; });
}
/// Schedules any remaining unscheduled work, then spins
/// until all scheduled work has been performed, placing a memory barrier
/// in between.
void spin_flush() {
std::atomic<bool> has_run = false;
enqueue([&has_run] () {
has_run.store(true, std::memory_order::release);
});
if constexpr (!perform_automatically) {
perform();
}
while(!has_run.load(std::memory_order::acquire));
}
~AsyncTaskQueue() {
stop();
}
private:
void start_impl() {
thread_ = std::thread{
[this] {
ActionVector actions;
// Continue until told to quit.
while(!should_quit_.load(std::memory_order_relaxed)) {
// Wait for new actions to be signalled, and grab them.
std::unique_lock lock(condition_mutex_);
condition_.wait(lock, [&] {
return !actions_.empty() || should_quit_.load(std::memory_order_relaxed);
});
std::swap(actions, actions_);
lock.unlock();
// Update to now (which is possibly a no-op).
TaskQueueStorage<Performer>::update();
// Perform the actions and destroy them.
for(const auto &action: actions) {
action();
}
actions.clear();
}
}
};
}
EnqueueDelegate *enqueue_delegate_ = nullptr;
// The list of actions waiting be performed. These will be elided,
// increasing their latency, if the emulation thread falls behind.
using ActionVector = std::vector<std::function<void(void)>>;
+24 -10
View File
@@ -25,10 +25,12 @@ ReflectableEnum(Display,
// ensure unified property naming.
//===
template <typename Owner> class DisplayOption {
namespace Options {
template <typename Owner> class Display {
public:
Configurable::Display output;
DisplayOption(Configurable::Display output) : output(output) {}
Display(const Configurable::Display output) noexcept : output(output) {}
protected:
void declare_display_option() {
@@ -37,26 +39,38 @@ protected:
}
};
template <typename Owner> class QuickloadOption {
template <typename Owner> class QuickLoad {
public:
bool quickload;
QuickloadOption(bool quickload) : quickload(quickload) {}
bool quick_load;
QuickLoad(const bool quick_load) noexcept : quick_load(quick_load) {}
protected:
void declare_quickload_option() {
static_cast<Owner *>(this)->declare(&quickload, "quickload");
static_cast<Owner *>(this)->declare(&quick_load, "quickload");
}
};
template <typename Owner> class QuickbootOption {
template <typename Owner> class QuickBoot {
public:
bool quickboot;
QuickbootOption(bool quickboot) : quickboot(quickboot) {}
bool quick_boot;
QuickBoot(const bool quick_boot) noexcept : quick_boot(quick_boot) {}
protected:
void declare_quickboot_option() {
static_cast<Owner *>(this)->declare(&quickboot, "quickboot");
static_cast<Owner *>(this)->declare(&quick_boot, "quickboot");
}
};
template <typename Owner> class DynamicCrop {
public:
bool dynamic_crop;
DynamicCrop(const bool dynamic_crop) noexcept : dynamic_crop(dynamic_crop) {}
protected:
void declare_dynamic_crop_option() {
static_cast<Owner *>(this)->declare(&dynamic_crop, "dynamiccrop");
}
};
}
}
+76 -19
View File
@@ -29,7 +29,7 @@ public:
/// Defines the broad type of the input.
enum Type {
// Half-axis inputs.
Up, Down, Left, Right,
Down, Up, Left, Right,
// Full-axis inputs.
Horizontal, Vertical,
// Fire buttons.
@@ -165,10 +165,11 @@ public:
const bool is_analogue_axis = input.is_analogue_axis();
if(is_digital_axis || is_analogue_axis) {
const size_t required_size = size_t(input.info.control.index+1);
if(stick_types_.size() < required_size) {
stick_types_.resize(required_size);
if(sticks_.size() < required_size) {
sticks_.resize(required_size);
}
stick_types_[size_t(input.info.control.index)] = is_digital_axis ? StickType::Digital : StickType::Analogue;
sticks_[size_t(input.info.control.index)].type =
is_digital_axis ? Stick::Type::Digital : Stick::Type::Analogue;
}
}
}
@@ -179,26 +180,50 @@ public:
void set_input(const Input &input, const bool is_active) final {
// If this is a digital setting to a digital property, just pass it along.
if(input.is_button() || stick_types_[input.info.control.index] == StickType::Digital) {
if(input.is_button() || sticks_[input.info.control.index].type == Stick::Type::Digital) {
did_set_input(input, is_active);
return;
}
// Otherwise this is logically to an analogue axis; for now just use some
// convenient hard-coded values. TODO: make these a function of time.
using Type = Joystick::Input::Type;
// Otherwise this is logically to an analogue axis; map appropriately.
// TODO: make these a function of time.
auto &stick = sticks_[input.info.control.index];
stick.apply_digital(input, is_active);
const auto analogue_value = [&](const int mask) {
switch(mask) {
default: return 0.5f;
case 0b01: return digital_minimum();
case 0b10: return digital_maximum();
}
};
switch(input.type) {
default: did_set_input(input, is_active ? 1.0f : 0.0f); break;
case Type::Left: did_set_input(Input(Type::Horizontal, input.info.control.index), is_active ? 0.1f : 0.5f); break;
case Type::Right: did_set_input(Input(Type::Horizontal, input.info.control.index), is_active ? 0.9f : 0.5f); break;
case Type::Up: did_set_input(Input(Type::Vertical, input.info.control.index), is_active ? 0.1f : 0.5f); break;
case Type::Down: did_set_input(Input(Type::Vertical, input.info.control.index), is_active ? 0.9f : 0.5f); break;
using enum Joystick::Input::Type;
default:
did_set_input(input, is_active ? 1.0f : 0.0f);
break;
case Left:
case Right:
did_set_input(
Input(Horizontal, input.info.control.index),
analogue_value(stick.digital_mask(Horizontal))
);
break;
case Up:
case Down:
did_set_input(
Input(Vertical, input.info.control.index),
analogue_value(stick.digital_mask(Vertical))
);
break;
}
}
void set_input(const Input &input, const float value) final {
// If this is an analogue setting to an analogue property, just pass it along.
if(!input.is_button() && stick_types_[input.info.control.index] == StickType::Analogue) {
if(!input.is_button() && sticks_[input.info.control.index].type == Stick::Type::Analogue) {
did_set_input(input, value);
return;
}
@@ -206,7 +231,9 @@ public:
// Otherwise apply a threshold test to convert to digital, with remapping from axes to digital inputs.
using Type = Joystick::Input::Type;
switch(input.type) {
default: did_set_input(input, value > 0.5f); break;
default:
did_set_input(input, value > 0.5f);
break;
case Type::Horizontal:
did_set_input(Input(Type::Left, input.info.control.index), value <= 0.25f);
did_set_input(Input(Type::Right, input.info.control.index), value >= 0.75f);
@@ -221,15 +248,45 @@ public:
protected:
virtual void did_set_input([[maybe_unused]] const Input &input, [[maybe_unused]] float value) {}
virtual void did_set_input([[maybe_unused]] const Input &input, [[maybe_unused]] bool value) {}
virtual float digital_minimum() const { return 0.1f; }
virtual float digital_maximum() const { return 0.9f; }
private:
const std::vector<Input> inputs_;
enum class StickType {
Digital,
Analogue
struct Stick {
enum class Type {
Digital,
Analogue
} type;
void apply_digital(const Input &input, const bool is_active) {
const int mask = [&] {
switch(input.type) {
default: return 0;
case Input::Type::Up: return 1 << 0;
case Input::Type::Down: return 1 << 1;
case Input::Type::Left: return 1 << 2;
case Input::Type::Right: return 1 << 3;
}
} ();
if(is_active) {
digital_inputs_ |= mask;
} else {
digital_inputs_ &= ~mask;
}
}
int digital_mask(const Input::Type axis) const {
switch(axis) {
default: return 0;
case Input::Type::Horizontal: return (digital_inputs_ >> 2) & 3;
case Input::Type::Vertical: return (digital_inputs_ >> 0) & 3;
}
}
int digital_inputs_ = 0;
};
std::vector<StickType> stick_types_;
std::vector<Stick> sticks_;
};
}
+3 -2
View File
@@ -8,6 +8,7 @@
#include "Keyboard.hpp"
#include <algorithm>
#include <cstddef>
using namespace Inputs;
@@ -28,7 +29,7 @@ bool Keyboard::set_key_pressed(const Key key, const char, const bool is_pressed,
}
key_states_[key_offset] = is_pressed;
if(delegate_) return delegate_->keyboard_did_change_key(this, key, is_pressed);
if(delegate_) return delegate_->keyboard_did_change_key(*this, key, is_pressed);
return false;
}
@@ -38,7 +39,7 @@ const std::set<Inputs::Keyboard::Key> &Keyboard::get_essential_modifiers() const
void Keyboard::reset_all_keys() {
std::fill(key_states_.begin(), key_states_.end(), false);
if(delegate_) delegate_->reset_all_keys(this);
if(delegate_) delegate_->reset_all_keys(*this);
}
void Keyboard::set_delegate(Delegate *const delegate) {
+2 -2
View File
@@ -73,8 +73,8 @@ public:
// Delegate interface.
struct Delegate {
virtual bool keyboard_did_change_key(Keyboard *keyboard, Key key, bool is_pressed) = 0;
virtual void reset_all_keys(Keyboard *keyboard) = 0;
virtual bool keyboard_did_change_key(Keyboard &, Key, bool is_pressed) = 0;
virtual void reset_all_keys(Keyboard &) = 0;
};
void set_delegate(Delegate *);
bool get_key_state(Key) const;
+21 -21
View File
@@ -15,30 +15,30 @@ namespace Inputs {
some quantity of buttons.
*/
class Mouse {
public:
/*!
Indicates a movement of the mouse.
*/
virtual void move([[maybe_unused]] const int x, [[maybe_unused]] const int y) {}
public:
/*!
Indicates a movement of the mouse.
*/
virtual void move([[maybe_unused]] const int x, [[maybe_unused]] const int y) {}
/*!
@returns the number of buttons on this mouse.
*/
virtual int get_number_of_buttons() const {
return 1;
}
/*!
@returns the number of buttons on this mouse.
*/
virtual int get_number_of_buttons() const {
return 1;
}
/*!
Indicates that button @c index is now either pressed or unpressed.
The intention is that @c index be semantic, not positional:
0 for the primary button, 1 for the secondary, 2 for the tertiary, etc.
*/
virtual void set_button_pressed([[maybe_unused]] const int index, [[maybe_unused]] const bool is_pressed) {}
/*!
Indicates that button @c index is now either pressed or unpressed.
The intention is that @c index be semantic, not positional:
0 for the primary button, 1 for the secondary, 2 for the tertiary, etc.
*/
virtual void set_button_pressed([[maybe_unused]] const int index, [[maybe_unused]] const bool is_pressed) {}
/*!
Releases all depressed buttons.
*/
virtual void reset_all_buttons() {}
/*!
Releases all depressed buttons.
*/
virtual void reset_all_buttons() {}
};
}
+22 -22
View File
@@ -79,23 +79,23 @@ template <int i, typename SchedulerT> void OperationMapper<Page::Page0>::dispatc
using AM = AddressingMode;
using O = Operation;
constexpr auto upper = (i >> 4) & 0xf;
constexpr auto lower = (i >> 0) & 0xf;
static constexpr auto upper = (i >> 4) & 0xf;
static constexpr auto lower = (i >> 0) & 0xf;
constexpr AddressingMode modes[] = {
static constexpr AddressingMode modes[] = {
AM::Immediate, AM::Direct, AM::Indexed, AM::Extended
};
constexpr AddressingMode mode = modes[(i >> 4) & 3];
static constexpr AddressingMode mode = modes[(i >> 4) & 3];
switch(upper) {
default: break;
case 0x1: {
constexpr Operation operations[] = {
static constexpr Operation operations[] = {
O::Page1, O::Page2, O::NOP, O::SYNC, O::None, O::None, O::LBRA, O::LBSR,
O::None, O::DAA, O::ORCC, O::None, O::ANDCC, O::SEX, O::EXG, O::TFR,
};
constexpr AddressingMode modes[] = {
static constexpr AddressingMode modes[] = {
AM::Variant, AM::Variant, AM::Inherent, AM::Inherent,
AM::Illegal, AM::Illegal, AM::Relative, AM::Relative,
AM::Illegal, AM::Inherent, AM::Immediate, AM::Illegal,
@@ -104,18 +104,18 @@ template <int i, typename SchedulerT> void OperationMapper<Page::Page0>::dispatc
s.template schedule<operations[lower], modes[lower]>();
} break;
case 0x2: {
constexpr Operation operations[] = {
static constexpr Operation operations[] = {
O::BRA, O::BRN, O::BHI, O::BLS, O::BCC, O::BCS, O::BNE, O::BEQ,
O::BVC, O::BVS, O::BPL, O::BMI, O::BGE, O::BLT, O::BGT, O::BLE,
};
s.template schedule<operations[lower], AM::Relative>();
} break;
case 0x3: {
constexpr Operation operations[] = {
static constexpr Operation operations[] = {
O::LEAX, O::LEAY, O::LEAS, O::LEAU, O::PSHS, O::PULS, O::PSHU, O::PULU,
O::None, O::RTS, O::ABX, O::RTI, O::CWAI, O::MUL, O::RESET, O::SWI,
};
constexpr auto op = operations[lower];
static constexpr auto op = operations[lower];
switch(lower) {
case 0x0: case 0x1: case 0x2: case 0x3:
s.template schedule<op, AM::Indexed>();
@@ -132,31 +132,31 @@ template <int i, typename SchedulerT> void OperationMapper<Page::Page0>::dispatc
}
} break;
case 0x4: {
constexpr Operation operations[] = {
static constexpr Operation operations[] = {
O::NEGA, O::None, O::None, O::COMA, O::LSRA, O::None, O::RORA, O::ASRA,
O::LSLA, O::ROLA, O::DECA, O::None, O::INCA, O::TSTA, O::None, O::CLRA,
};
constexpr auto op = operations[lower];
static constexpr auto op = operations[lower];
s.template schedule<op, op == O::None ? AM::Illegal : AM::Inherent>();
} break;
case 0x5: {
constexpr Operation operations[] = {
static constexpr Operation operations[] = {
O::NEGB, O::None, O::None, O::COMB, O::LSRB, O::None, O::RORB, O::ASRB,
O::LSLB, O::ROLB, O::DECB, O::None, O::INCB, O::TSTB, O::None, O::CLRB,
};
constexpr auto op = operations[lower];
static constexpr auto op = operations[lower];
s.template schedule<op, op == O::None ? AM::Illegal : AM::Inherent>();
} break;
case 0x0: case 0x6: case 0x7: {
constexpr Operation operations[] = {
static constexpr Operation operations[] = {
O::NEG, O::None, O::None, O::COM, O::LSR, O::None, O::ROR, O::ASR,
O::LSL, O::ROL, O::DEC, O::None, O::INC, O::TST, O::JMP, O::CLR,
};
constexpr auto op = operations[lower];
static constexpr auto op = operations[lower];
s.template schedule<op, op == O::None ? AM::Illegal : upper == 0 ? AM::Direct : mode>();
} break;
case 0x8: case 0x9: case 0xa: case 0xb: {
constexpr Operation operations[] = {
static constexpr Operation operations[] = {
O::SUBA, O::CMPA, O::SBCA, O::SUBD, O::ANDA, O::BITA, O::LDA, O::STA,
O::EORA, O::ADCA, O::ORA, O::ADDA, O::CMPX, O::JSR, O::LDX, O::STX,
};
@@ -164,7 +164,7 @@ template <int i, typename SchedulerT> void OperationMapper<Page::Page0>::dispatc
else s.template schedule<operations[lower], mode>();
} break;
case 0xc: case 0xd: case 0xe: case 0xf: {
constexpr Operation operations[] = {
static constexpr Operation operations[] = {
O::SUBB, O::CMPB, O::SBCB, O::ADDD, O::ANDB, O::BITB, O::LDB, O::STB,
O::EORB, O::ADCB, O::ORB, O::ADDB, O::LDD, O::STD, O::LDU, O::STU,
};
@@ -178,13 +178,13 @@ template <int i, typename SchedulerT> void OperationMapper<Page::Page1>::dispatc
using AM = AddressingMode;
using O = Operation;
constexpr AddressingMode modes[] = {
static constexpr AddressingMode modes[] = {
AM::Immediate, AM::Direct, AM::Indexed, AM::Extended
};
constexpr auto mode = modes[(i >> 4) & 3];
static constexpr auto mode = modes[(i >> 4) & 3];
if constexpr (i >= 0x21 && i < 0x30) {
constexpr Operation operations[] = {
static constexpr Operation operations[] = {
O::LBRN, O::LBHI, O::LBLS, O::LBCC, O::LBCS, O::LBNE, O::LBEQ,
O::LBVC, O::LBVS, O::LBPL, O::LBMI, O::LBGE, O::LBLT, O::LBGT, O::LBLE,
};
@@ -219,10 +219,10 @@ template <int i, typename SchedulerT> void OperationMapper<Page::Page2>::dispatc
using AM = AddressingMode;
using O = Operation;
constexpr AddressingMode modes[] = {
static constexpr AddressingMode modes[] = {
AM::Immediate, AM::Direct, AM::Indexed, AM::Extended
};
constexpr auto mode = modes[(i >> 4) & 3];
static constexpr auto mode = modes[(i >> 4) & 3];
switch(i) {
default: s.template schedule<O::None, AM::Illegal>(); break;
+3 -3
View File
@@ -8,6 +8,8 @@
#pragma once
#include <bit>
namespace InstructionSet::ARM {
enum class ShiftType {
@@ -99,14 +101,12 @@ void shift(uint32_t &source, uint32_t amount, const typename Carry<set_carry>::t
amount &= 31;
if(amount) {
if constexpr (set_carry) carry = source & (1 << (amount - 1));
source = (source >> amount) | (source << (32 - amount));
source = std::rotr(source, int(amount));
} else {
if constexpr (set_carry) carry = source & 0x8000'0000;
}
} break;
// TODO: upon adoption of C++20, use std::rotr.
default: break;
}
}
+5 -5
View File
@@ -172,7 +172,7 @@ struct Disassembler {
}
template <Flags f> void perform(const DataProcessing fields) {
constexpr DataProcessingFlags flags(f);
static constexpr DataProcessingFlags flags(f);
instruction_.operand1.type = Operand::Type::Register;
instruction_.operand1.value = fields.operand1();
@@ -215,7 +215,7 @@ struct Disassembler {
template <Flags> void perform(Multiply) {}
template <Flags f> void perform(const SingleDataTransfer fields) {
constexpr SingleDataTransferFlags flags(f);
static constexpr SingleDataTransferFlags flags(f);
instruction_.operation =
(flags.operation() == SingleDataTransferFlags::Operation::STR) ?
Instruction::Operation::STR : Instruction::Operation::LDR;
@@ -227,7 +227,7 @@ struct Disassembler {
instruction_.operand1.value = fields.base();
}
template <Flags f> void perform(const BlockDataTransfer fields) {
constexpr BlockDataTransferFlags flags(f);
static constexpr BlockDataTransferFlags flags(f);
instruction_.operation =
(flags.operation() == BlockDataTransferFlags::Operation::STM) ?
Instruction::Operation::STM : Instruction::Operation::LDM;
@@ -239,7 +239,7 @@ struct Disassembler {
instruction_.operand1.value = fields.register_list();
}
template <Flags f> void perform(const Branch fields) {
constexpr BranchFlags flags(f);
static constexpr BranchFlags flags(f);
instruction_.operation =
(flags.operation() == BranchFlags::Operation::BL) ?
Instruction::Operation::BL : Instruction::Operation::B;
@@ -247,7 +247,7 @@ struct Disassembler {
instruction_.operand1.value = fields.offset();
}
template <Flags f> void perform(CoprocessorRegisterTransfer) {
constexpr CoprocessorRegisterTransferFlags flags(f);
static constexpr CoprocessorRegisterTransferFlags flags(f);
instruction_.operation =
(flags.operation() == CoprocessorRegisterTransferFlags::Operation::MRC) ?
Instruction::Operation::MRC : Instruction::Operation::MCR;
+11 -12
View File
@@ -63,7 +63,7 @@ struct Executor {
return condition == Condition::AL ? true : registers_.test(condition);
}
template <bool allow_register, bool set_carry, typename FieldsT>
template <bool set_carry, typename FieldsT>
uint32_t decode_shift(const FieldsT fields, uint32_t &rotate_carry, const uint32_t pc_offset) {
// "When R15 appears in the Rm position it will give the value of the PC together
// with the PSR flags to the barrel shifter. ...
@@ -78,8 +78,7 @@ struct Executor {
operand2 = registers_[fields.operand2()];
}
// TODO: in C++20, a quick `if constexpr (requires` can eliminate the `allow_register` parameter.
if constexpr (allow_register) {
if constexpr (requires {fields.shift_count_is_register();}) {
if(fields.shift_count_is_register()) {
uint32_t shift_amount;
@@ -106,7 +105,7 @@ struct Executor {
}
template <Flags f> void perform(const DataProcessing fields) {
constexpr DataProcessingFlags flags(f);
static constexpr DataProcessingFlags flags(f);
const bool shift_by_register = !flags.operand2_is_immediate() && fields.shift_count_is_register();
// Write a raw result into the PC proxy if the target is R15; it'll be stored properly later.
@@ -128,14 +127,14 @@ struct Executor {
uint32_t rotate_carry = registers_.c();
// Populate carry from the shift only if it'll be used.
constexpr bool shift_sets_carry = is_logical(flags.operation()) && flags.set_condition_codes();
static constexpr bool shift_sets_carry = is_logical(flags.operation()) && flags.set_condition_codes();
// Get operand 2.
if constexpr (flags.operand2_is_immediate()) {
operand2 = fields.immediate();
shift<ShiftType::RotateRight, shift_sets_carry, false>(operand2, fields.rotate(), rotate_carry);
} else {
operand2 = decode_shift<true, shift_sets_carry>(fields, rotate_carry, shift_by_register ? 8 : 4);
operand2 = decode_shift<shift_sets_carry>(fields, rotate_carry, shift_by_register ? 8 : 4);
}
uint32_t conditions = 0;
@@ -231,7 +230,7 @@ struct Executor {
}
template <Flags f> void perform(const Multiply fields) {
constexpr MultiplyFlags flags(f);
static constexpr MultiplyFlags flags(f);
// R15 rules:
//
@@ -258,7 +257,7 @@ struct Executor {
}
template <Flags f> void perform(const Branch branch) {
constexpr BranchFlags flags(f);
static constexpr BranchFlags flags(f);
if constexpr (flags.operation() == BranchFlags::Operation::BL) {
registers_[14] = registers_.pc_status(0);
@@ -267,7 +266,7 @@ struct Executor {
}
template <Flags f> void perform(const SingleDataTransfer transfer) {
constexpr SingleDataTransferFlags flags(f);
static constexpr SingleDataTransferFlags flags(f);
// Calculate offset.
uint32_t offset;
@@ -276,7 +275,7 @@ struct Executor {
// the register specified shift amounts are not available
// in this instruction class.
uint32_t carry = registers_.c();
offset = decode_shift<false, false>(transfer, carry, 4);
offset = decode_shift<false>(transfer, carry, 4);
} else {
offset = transfer.immediate();
}
@@ -392,8 +391,8 @@ struct Executor {
}
}
template <Flags f> void perform(const BlockDataTransfer transfer) {
constexpr BlockDataTransferFlags flags(f);
constexpr bool is_ldm = flags.operation() == BlockDataTransferFlags::Operation::LDM;
static constexpr BlockDataTransferFlags flags(f);
static constexpr bool is_ldm = flags.operation() == BlockDataTransferFlags::Operation::LDM;
// Ensure that *base points to the base register if it can be written back;
// also set address to the base.
+1 -12
View File
@@ -293,18 +293,7 @@ struct BlockDataTransfer: public WithShiftControlBits {
uint32_t base() const { return (opcode_ >> 16) & 0xf; }
/// A bitfield indicating which registers to load or store.
uint16_t register_list() const { return static_cast<uint16_t>(opcode_); }
uint32_t popcount() const {
const uint16_t list = register_list();
// TODO: just use std::popcount when adopting C++20.
uint32_t total = ((list & 0xaaaa) >> 1) + (list & 0x5555);
total = ((total & 0xcccc) >> 2) + (total & 0x3333);
total = ((total & 0xf0f0) >> 4) + (total & 0x0f0f);
total = ((total & 0xff00) >> 8) + (total & 0x00ff);
return total;
}
uint16_t register_list() const { return static_cast<uint16_t>(opcode_); }
};
//
+2 -1
View File
@@ -17,7 +17,8 @@ Instruction Decoder::instrucion_for_opcode(const uint8_t opcode) {
switch(opcode) {
default: return Instruction(opcode);
#define Map(opcode, operation, addressing_mode) case opcode: return Instruction(Operation::operation, AddressingMode::addressing_mode, opcode);
#define Map(opcode, operation, addressing_mode) \
case opcode: return Instruction(Operation::operation, AddressingMode::addressing_mode, opcode);
/* 0x00 0x0f */
Map(0x00, BRK, Implied); Map(0x01, ORA, XIndirect);
+17 -16
View File
@@ -18,8 +18,8 @@
using namespace InstructionSet::M50740;
namespace {
constexpr int port_remap[] = {0, 1, 2, 0, 3};
Log::Logger<Log::Source::M50740> logger;
constexpr int port_remap[] = {0, 1, 2, 0, 3};
using Logger = Log::Logger<Log::Source::M50740>;
}
Executor::Executor(PortHandler &port_handler) : port_handler_(port_handler) {
@@ -82,13 +82,13 @@ uint8_t Executor::read(uint16_t address) {
port_handler_.run_ports_for(cycles_since_port_handler_.flush<Cycles>());
switch(address) {
default:
logger.error().append("Unrecognised read from %02x", address);
Logger::error().append("Unrecognised read from %02x", address);
return 0xff;
// "Port R"; sixteen four-bit ports
case 0xd0: case 0xd1: case 0xd2: case 0xd3: case 0xd4: case 0xd5: case 0xd6: case 0xd7:
case 0xd8: case 0xd9: case 0xda: case 0xdb: case 0xdc: case 0xdd: case 0xde: case 0xdf:
logger.error().append("Unimplemented Port R read from %04x", address);
Logger::error().append("Unimplemented Port R read from %04x", address);
return 0x00;
// Ports P0P3.
@@ -133,7 +133,7 @@ void Executor::write(uint16_t address, const uint8_t value) {
// ROM 'writes' are almost as easy (albeit unexpected).
if(address >= 0x100) {
logger.info().append("Attempted ROM write of %02x to %04x", value, address);
Logger::info().append("Attempted ROM write of %02x to %04x", value, address);
return;
}
@@ -142,13 +142,13 @@ void Executor::write(uint16_t address, const uint8_t value) {
switch(address) {
default:
logger.error().append("Unrecognised write of %02x to %04x", value, address);
Logger::error().append("Unrecognised write of %02x to %04x", value, address);
break;
// "Port R"; sixteen four-bit ports
case 0xd0: case 0xd1: case 0xd2: case 0xd3: case 0xd4: case 0xd5: case 0xd6: case 0xd7:
case 0xd8: case 0xd9: case 0xda: case 0xdb: case 0xdc: case 0xdd: case 0xde: case 0xdf:
logger.error().append("Unimplemented Port R write of %02x to %04x", value, address);
Logger::error().append("Unimplemented Port R write of %02x to %04x", value, address);
break;
// Ports P0P3.
@@ -241,7 +241,7 @@ template <Operation operation, AddressingMode addressing_mode> void Executor::pe
case Operation::ADC: case Operation::AND: case Operation::CMP: case Operation::EOR:
case Operation::LDA: case Operation::ORA: case Operation::SBC:
{
constexpr int t_lengths[] = {
static constexpr int t_lengths[] = {
0,
operation == Operation::LDA ? 2 : (operation == Operation::CMP ? 1 : 3)
};
@@ -444,7 +444,7 @@ template <Operation operation, AddressingMode addressing_mode> void Executor::pe
case Operation::BBS0: case Operation::BBS1: case Operation::BBS2: case Operation::BBS3:
case Operation::BBS4: case Operation::BBS5: case Operation::BBS6: case Operation::BBS7: {
if constexpr (operation >= Operation::BBS0 && operation <= Operation::BBS7) {
constexpr uint8_t mask = 1 << (int(operation) - int(Operation::BBS0));
static constexpr uint8_t mask = 1 << (int(operation) - int(Operation::BBS0));
if(value & mask) {
set_program_counter(uint16_t(address));
subtract_duration(2);
@@ -454,7 +454,7 @@ template <Operation operation, AddressingMode addressing_mode> void Executor::pe
case Operation::BBC0: case Operation::BBC1: case Operation::BBC2: case Operation::BBC3:
case Operation::BBC4: case Operation::BBC5: case Operation::BBC6: case Operation::BBC7: {
if constexpr (operation >= Operation::BBC0 && operation <= Operation::BBC7) {
constexpr uint8_t mask = 1 << (int(operation) - int(Operation::BBC0));
static constexpr uint8_t mask = 1 << (int(operation) - int(Operation::BBC0));
if(!(value & mask)) {
set_program_counter(uint16_t(address));
subtract_duration(2);
@@ -792,7 +792,7 @@ template <Operation operation> void Executor::perform(uint8_t *operand [[maybe_u
*/
default:
logger.error().append("Unimplemented operation: %d", operation);
Logger::error().append("Unimplemented operation: %d", operation);
assert(false);
}
}
@@ -805,8 +805,9 @@ inline void Executor::subtract_duration(const int duration) {
cycles_since_port_handler_ += Cycles(duration);
// Update timer 1 and 2 prescaler.
constexpr int t12_divider = 4; // A divide by 4 has already been applied before counting instruction lengths; therefore
// this additional divide by 4 produces the correct net divide by 16.
static constexpr int t12_divider = 4; // A divide by 4 has already been applied before counting instruction
// lengths; therefore this additional divide by 4 produces the correct net
// divide by 16.
timer_divider_ += duration;
const int clock_ticks = timer_divider_ / t12_divider;
@@ -835,13 +836,13 @@ inline void Executor::subtract_duration(const int duration) {
}
} break;
case 0x04:
logger.error().append("TODO: Timer X; Pulse output mode");
Logger::error().append("TODO: Timer X; Pulse output mode");
break;
case 0x08:
logger.error().append("TODO: Timer X; Event counter mode");
Logger::error().append("TODO: Timer X; Event counter mode");
break;
case 0x0c:
logger.error().append("TODO: Timer X; Pulse width measurement mode");
Logger::error().append("TODO: Timer X; Pulse width measurement mode");
break;
}
}
@@ -202,7 +202,7 @@ template <typename IntT> IntT Executor<model, BusHandler>::State::read_pc() {
template <Model model, typename BusHandler>
uint32_t Executor<model, BusHandler>::State::index_8bitdisplacement(uint32_t base) {
// Determine whether full extension addressing modes are supported.
constexpr bool supports_full_extensions = model >= Model::M68020;
static constexpr bool supports_full_extensions = model >= Model::M68020;
// Get the [first] extension word.
const auto extension = read_pc<uint16_t>();
@@ -14,6 +14,7 @@ template <Model model, Operation t_operation> constexpr uint8_t operand_flags(co
switch((t_operation != Operation::Undefined) ? t_operation : r_operation) {
default:
assert(false);
[[fallthrough]];
//
// No operands are fetched or stored.
@@ -12,6 +12,7 @@
#include "InstructionSets/M68k/ExceptionVectors.hpp"
#include <algorithm>
#include <bit>
#include <cassert>
#include <cmath>
@@ -33,7 +34,7 @@ static void add_sub(const IntT source, IntT &destination, Status &status) {
static_assert(!std::numeric_limits<IntT>::is_signed);
static_assert(operation == Numeric::Operation::Add || operation == Numeric::Operation::Subtract);
constexpr bool is_add = operation == Numeric::Operation::Add;
static constexpr bool is_add = operation == Numeric::Operation::Add;
IntT result = is_add ?
destination + source :
destination - source;
@@ -312,7 +313,7 @@ void shift(const uint32_t source, IntT &destination, Status &status, FlowControl
operation == Operation::LSRb || operation == Operation::LSRw || operation == Operation::LSRl
);
constexpr auto size = Numeric::bit_size<IntT>();
static constexpr auto size = Numeric::bit_size<IntT>();
const auto shift = shift_count<IntT>(uint8_t(source), flow_controller);
if(!shift) {
@@ -407,7 +408,7 @@ void rotate(const uint32_t source, IntT &destination, Status &status, FlowContro
operation == Operation::RORb || operation == Operation::RORw || operation == Operation::RORl
);
constexpr auto size = Numeric::bit_size<IntT>();
static constexpr auto size = Numeric::bit_size<IntT>();
auto shift = shift_count<IntT>(uint8_t(source), flow_controller);
if(!shift) {
@@ -417,21 +418,11 @@ void rotate(const uint32_t source, IntT &destination, Status &status, FlowContro
switch(operation) {
case Operation::ROLb: case Operation::ROLw: case Operation::ROLl:
if(shift) {
destination = IntT(
(destination << shift) |
(destination >> (size - shift))
);
}
destination = std::rotl<IntT>(destination, shift);
status.carry_flag = Status::FlagT(destination & 1);
break;
case Operation::RORb: case Operation::RORw: case Operation::RORl:
if(shift) {
destination = IntT(
(destination >> shift) |
(destination << (size - shift))
);
}
destination = std::rotr<IntT>(destination, shift);
status.carry_flag = Status::FlagT(destination & Numeric::top_bit<IntT>());
break;
}
@@ -449,7 +440,7 @@ void rox(const uint32_t source, IntT &destination, Status &status, FlowControlle
operation == Operation::ROXRb || operation == Operation::ROXRw || operation == Operation::ROXRl
);
constexpr auto size = Numeric::bit_size<IntT>();
static constexpr auto size = Numeric::bit_size<IntT>();
auto shift = shift_count<IntT>(uint8_t(source), flow_controller) % (size + 1);
if(!shift) {
+5 -2
View File
@@ -14,8 +14,6 @@ using namespace InstructionSet::M68k;
std::string Preinstruction::operand_description(const int index, const int opcode) const {
switch(mode(index)) {
default: assert(false);
case AddressingMode::None:
return "";
@@ -54,7 +52,12 @@ std::string Preinstruction::operand_description(const int index, const int opcod
return "Q";
}
return std::to_string(int(quick(uint16_t(opcode), operation)));
// TODO: 68020+ modes.
default: break;
}
assert(false);
return "[TODO]";
}
namespace {
+1 -1
View File
@@ -1500,7 +1500,7 @@ struct Instruction {
/// A 24-bit signed number; provided as already sign extended.
int32_t li() const {
constexpr uint32_t extensions[2] = {
static constexpr uint32_t extensions[2] = {
0x0000'0000,
0xfc00'0000
};
+24 -6
View File
@@ -8,6 +8,8 @@
#pragma once
#include <type_traits>
namespace InstructionSet::x86 {
/// Explains the type of access that `perform` intends to perform; is provided as a template parameter to whatever
@@ -43,25 +45,41 @@ constexpr bool is_writeable(const AccessType type) {
return type == AccessType::ReadModifyWrite || type == AccessType::Write;
}
// Allow only 8-, 16- and 32-bit unsigned int accesses.
template <typename IntT>
concept is_x86_data_type
= std::is_same_v<IntT, uint8_t> || std::is_same_v<IntT, uint16_t> || std::is_same_v<IntT, uint32_t>;
template <typename IntT, AccessType type> struct Accessor;
// Reads: return a value directly.
template <typename IntT> struct Accessor<IntT, AccessType::Read> { using type = const IntT; };
template <typename IntT> struct Accessor<IntT, AccessType::PreauthorisedRead> { using type = const IntT; };
template <typename IntT>
requires is_x86_data_type<IntT>
struct Accessor<IntT, AccessType::Read> { using type = IntT; };
template <typename IntT>
requires is_x86_data_type<IntT>
struct Accessor<IntT, AccessType::PreauthorisedRead> { using type = IntT; };
// Writes: return a custom type that can be written but not read.
template <typename IntT>
requires is_x86_data_type<IntT>
class Writeable {
public:
Writeable(IntT &target) : target_(target) {}
IntT operator=(IntT value) { return target_ = value; }
constexpr Writeable(IntT &target) noexcept : target_(target) {}
IntT operator=(const IntT value) { return target_ = value; }
private:
IntT &target_;
};
template <typename IntT> struct Accessor<IntT, AccessType::Write> { using type = Writeable<IntT>; };
template <typename IntT>
requires is_x86_data_type<IntT>
struct Accessor<IntT, AccessType::Write> { using type = Writeable<IntT>; };
// Read-modify-writes: return a reference.
template <typename IntT> struct Accessor<IntT, AccessType::ReadModifyWrite> { using type = IntT &; };
template <typename IntT>
requires is_x86_data_type<IntT>
struct Accessor<IntT, AccessType::ReadModifyWrite> { using type = IntT &; };
// Shorthands; assumed that preauthorised reads have the same return type as reads.
template<typename IntT> using read_t = typename Accessor<IntT, AccessType::Read>::type;
+57 -26
View File
@@ -30,6 +30,7 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
// without any loss of context. This reduces the risk of the decoder tricking a caller into
// an infinite loop.
static constexpr int max_instruction_length = model >= Model::i80386 ? 15 : (model == Model::i80286 ? 10 : 65536);
static constexpr bool is_32bit = instruction_type(model) == InstructionType::Bits32;
const uint8_t *const end = source + std::min(length, size_t(max_instruction_length - consumed_));
// MARK: - Prefixes (if present) and the opcode.
@@ -194,6 +195,8 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
displacement(Operation::JP, DataSize::Byte);
} else {
immediate(Operation::PUSH, DataSize::Byte);
operation_size_ = data_size_;
sign_extend_operand_ = true;
}
break;
case 0x6b:
@@ -688,7 +691,7 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
};
displacement_size_ = sizes[mod];
if(is_32bit(model) && address_size_ == AddressSize::b32) {
if(is_32bit && address_size_ == AddressSize::b32) {
// 32-bit decoding: the range of potential indirections is expanded,
// and may segue into obtaining a SIB.
sib_ = ScaleIndexBase(0, Source::None, reg_table[rm]);
@@ -734,9 +737,8 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
switch(reg) {
default:
// case 1 is treated as another form of TEST on the 8086.
// (and, I guess, the 80186?)
if constexpr (model >= Model::i80286) {
// case 1 is treated as another form of TEST through to at least the 80286.
if constexpr (model >= Model::i80386) {
return undefined();
}
[[fallthrough]];
@@ -763,7 +765,7 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
// The 16-bit chips have four segment registers;
// the 80386 onwards has six.
if constexpr (is_32bit(model)) {
if constexpr (is_32bit) {
if(masked_reg > 5) {
return undefined();
}
@@ -790,15 +792,27 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
case ModRegRMFormat::MemRegROL_to_SAR:
destination_ = memreg;
// TODO: is this true? It appears empirically to be so from the PC AT BIOS, but find a source.
if(operand_size_ != DataSize::None) {
operand_size_ = DataSize::Byte;
}
switch(reg) {
default:
if constexpr (model == Model::i8086) {
if(source_ == Source::eCX) {
set(Operation::SETMOC);
} else {
set(Operation::SETMO);
}
} else {
switch(model) {
case Model::i8086:
if(source_ == Source::eCX) {
set(Operation::SETMOC);
} else {
set(Operation::SETMO);
}
break;
case Model::i80286:
set(Operation::SAL);
break;
default:
return undefined();
}
break;
@@ -858,6 +872,13 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
source_ = Source::Immediate;
destination_ = memreg;
operand_size_ = operation_size_;
// This form requires that the reg field be blank
if constexpr (model >= Model::i80286) {
if(reg != 0) {
return undefined();
}
}
break;
case ModRegRMFormat::MemRegADD_to_CMP:
@@ -888,7 +909,7 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
destination_ = source_ = memreg;
switch(reg) {
default: return undefined();
default: return undefined();
case 0: set(Operation::SLDT); break;
case 1: set(Operation::STR); break;
@@ -903,7 +924,7 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
destination_ = source_ = memreg;
switch(reg) {
default: return undefined();
default: return undefined();
case 0: set(Operation::SGDT); break;
case 1: set(Operation::SIDT); break;
@@ -943,7 +964,7 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
// MARK: - ScaleIndexBase
if constexpr (is_32bit(model)) {
if constexpr (is_32bit) {
if(phase_ == Phase::ScaleIndexBase && source != end) {
sib_ = *source;
++source;
@@ -1010,8 +1031,13 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
}
}
} else {
// Provide a genuine measure of further bytes required.
return std::make_pair(-(outstanding_bytes - bytes_to_consume), InstructionT());
// Provide a genuine measure of further bytes required, or post a bad instruction
// if the length limit has been breached.
if(consumed_ != max_instruction_length) {
return std::make_pair(-(outstanding_bytes - bytes_to_consume), InstructionT());
} else {
return overlong();
}
}
}
@@ -1030,6 +1056,18 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
// operations would unlock an extra bit of storage for a net gain of 239 extra operation types and thereby
// alleviating any concerns over whether there'll be space to handle MMX, floating point extensions, etc.
if constexpr (model >= Model::i80286) {
if(operation_ == Operation::BOUND && !is_address(source_)) {
return undefined();
}
}
if(
(operation_ == Operation::JMPfar || operation_ == Operation::CALLfar) &&
destination_ < Source::DirectAddress
) {
return undefined();
}
const auto result = std::make_pair(
consumed_,
InstructionT(
@@ -1051,14 +1089,7 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
// Check for a too-long instruction.
if(consumed_ == max_instruction_length) {
std::pair<int, InstructionT> result;
if(max_instruction_length == 65536) {
result = std::make_pair(consumed_, InstructionT(Operation::NOP));
} else {
result = std::make_pair(consumed_, InstructionT());
}
reset_parsing();
return result;
return overlong();
}
// i.e. not done yet.
@@ -1066,7 +1097,7 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(
}
template <Model model> void Decoder<model>::set_32bit_protected_mode(bool enabled) {
if constexpr (!is_32bit(model)) {
if constexpr (instruction_type(model) == InstructionType::Bits16) {
assert(!enabled);
return;
}
+12 -1
View File
@@ -23,7 +23,7 @@ namespace InstructionSet::x86 {
*/
template <Model model> class Decoder {
public:
using InstructionT = Instruction<is_32bit(model)>;
using InstructionT = Instruction<instruction_type(model)>;
/*!
@returns an @c Instruction plus a size; a positive size indicates successful decoding of
@@ -349,6 +349,7 @@ private:
phase_ = Phase::DisplacementOrOperand;
displacement_size_ = DataSize::Word;
operand_size_ = DataSize::Byte;
operation_size_ = DataSize::Byte;
}
/// Sets up the operation size, oncoming phase and modregrm format for a member of the shift group (i.e. 'group 2').
@@ -367,6 +368,16 @@ private:
reset_parsing();
return result;
}
std::pair<int, typename Decoder<model>::InstructionT> overlong() {
const auto consumed = consumed_;
reset_parsing();
if(consumed == 65536) {
return std::make_pair(consumed, InstructionT(Operation::NOP));
} else {
return std::make_pair(consumed, InstructionT(ExceptionCode()));
}
}
};
extern template class InstructionSet::x86::Decoder<InstructionSet::x86::Model::i8086>;
+356
View File
@@ -0,0 +1,356 @@
//
// Descriptors.hpp
// Clock Signal
//
// Created by Thomas Harte on 19/03/2025.
// Copyright © 2025 Thomas Harte. All rights reserved.
//
#pragma once
#include "Exceptions.hpp"
#include "Instruction.hpp"
//#include "Perform.hpp"
#include <cassert>
#include <concepts>
namespace InstructionSet::x86 {
enum class DescriptorTable {
Global, Local, Interrupt,
};
struct DescriptorTablePointer {
uint16_t limit;
uint32_t base;
};
struct DescriptorBounds {
uint32_t begin, end;
auto operator <=>(const DescriptorBounds &) const = default;
};
enum class DescriptorType {
Code, Data, Stack,
CallGate, TaskGate, InterruptGate, TrapGate,
AvailableTaskStateSegment, LDT, BusyTaskStateSegment,
Invalid,
};
constexpr bool is_data_or_code(const DescriptorType type) {
return type <= DescriptorType::Stack;
}
enum DescriptorTypeFlag: uint8_t {
Accessed = 1 << 0,
Busy = 1 << 1,
};
struct DescriptorDescription {
DescriptorType type = DescriptorType::Invalid;
bool readable = false;
bool writeable = false;
bool conforming = false;
bool is32bit = false;
};
struct SegmentDescriptor {
SegmentDescriptor() = default;
/// Creates a new descriptor with four 16-bit from a descriptor table.
SegmentDescriptor(
const uint16_t segment,
const bool local,
const uint16_t descriptor[4]
) noexcept : segment_(segment), local_(local) {
base_ = uint32_t(descriptor[1] | ((descriptor[2] & 0xff) << 16));
type_ = descriptor[2] >> 8;
offset_ = descriptor[0];
if(description().type != DescriptorType::Stack) {
bounds_ = DescriptorBounds{ 0, offset_ };
} else {
if(offset_ != std::numeric_limits<uint32_t>::max()) {
bounds_ = DescriptorBounds{ uint32_t(offset_ + 1), std::numeric_limits<uint32_t>::max() };
} else {
// This descriptor is impossible to satisfy for reasons that aren't
// properly expressed if the lower bound is incremented, so make it
// impossible to satisfy in a more prosaic sense.
bounds_ = DescriptorBounds{ 1, 0 };
}
}
}
/// Rewrites this descriptor as a real-mode segment.
void set_segment(const uint16_t segment) {
segment_ = segment;
base_ = uint32_t(segment) << 4;
bounds_ = DescriptorBounds{ 0x0000, 0xffff };
offset_ = 0;
type_ = 0b1'00'1'001'0; // Present, privilege level 0, expand-up writeable data, unaccessed.
}
uint16_t segment() const {
return segment_;
}
/// @returns The linear address for offest @c address within the segment described by this descriptor.
uint32_t to_linear(const uint32_t address) const {
return base_ + address;
}
void throw_gpf() const {
throw Exception::exception<Vector::GeneralProtectionFault>(
ExceptionCode(
segment_,
local_,
false,
false
)
);
}
template <AccessType type, typename AddressT>
requires std::same_as<AddressT, uint16_t> || std::same_as<AddressT, uint32_t>
void authorise(const AddressT begin, const AddressT end) const {
// Test for bounds; end && end < begin captures instances where end is
// both out of bounds and beyond the range of AddressT.
if(begin < bounds_.begin || end > bounds_.end || (end && end < begin)) {
throw_gpf();
}
// Tested at loading (?): present(), privilege_level().
const auto desc = description();
if(type == AccessType::Read && !desc.readable) {
throw_gpf();
}
if(type == AccessType::Write && !desc.writeable) {
throw_gpf();
}
}
void validate_as(const Source segment) const {
const auto desc = description();
switch(segment) {
case Source::DS:
case Source::ES:
if(!desc.readable) {
printf("TODO: throw for unreadable DS or ES source.\n");
assert(false);
}
break;
case Source::SS:
if(!desc.writeable) {
printf("TODO: throw for unwriteable SS target.\n");
assert(false);
}
break;
default: break;
}
// TODO: is this descriptor privilege within reach?
// TODO: is this an empty descriptor*? If so: exception!
}
// TODO: validators for:
// INT
// IRET
// JMP
// RET
//
// Verify also: MOV, POP, both of which can mutate DS/ES, SS, etc.
void validate_call(
const std::function<void(const SegmentDescriptor &)> &call_callback
) const {
const auto desc = description();
switch(desc.type) {
case DescriptorType::Code:
if(desc.conforming) {
// TODO:
// DPL must be :5 CPL else #GP (code segment selector)
} else {
}
call_callback(*this);
break;
case DescriptorType::CallGate:
assert(false);
break;
case DescriptorType::AvailableTaskStateSegment:
assert(false);
break;
default:
throw_gpf();
break;
}
}
/// @returns The base of this segment descriptor.
uint32_t base() const { return base_; }
/// @returns The offset of this segment descriptor.
uint32_t offset() const { return offset_; }
/// @returns The bounds of this segment descriptor; will be either [0, limit] or [limit, INT_MAX] depending on descriptor type.
/// Accesses must be `>= bounds().begin` and `<= bounds().end`.
DescriptorBounds bounds() const { return bounds_; }
bool present() const { return type_ & 0x80; }
int privilege_level() const { return (type_ >> 5) & 3; }
uint8_t access_rights() const { return uint8_t(type_); }
DescriptorDescription description() const {
using Type = DescriptorType;
switch(type_ & 0b11111) {
default:
case 0b00000: return { .type = Type::Invalid };
case 0b00001: return { .type = Type::AvailableTaskStateSegment, .is32bit = false };
case 0b00010: return { .type = Type::LDT };
case 0b00011: return { .type = Type::BusyTaskStateSegment, .is32bit = false };
case 0b00100: return { .type = Type::CallGate, .is32bit = false };
case 0b00101: return { .type = Type::TaskGate };
case 0b00110: return { .type = Type::InterruptGate, .is32bit = false };
case 0b00111: return { .type = Type::TrapGate, .is32bit = false };
case 0b01000: return { .type = Type::Invalid };
case 0b01001: return { .type = Type::AvailableTaskStateSegment, .is32bit = true };
case 0b01010: return { .type = Type::Invalid };
case 0b01011: return { .type = Type::BusyTaskStateSegment, .is32bit = true };
case 0b01100: return { .type = Type::CallGate, .is32bit = true };
case 0b01101: return { .type = Type::Invalid };
case 0b01110: return { .type = Type::InterruptGate, .is32bit = true };
case 0b01111: return { .type = Type::TrapGate, .is32bit = true };
// b0 is the accessed flag for non-system descriptors; it doesn't affect the type.
case 0b10000:
case 0b10001: return { .type = Type::Data, .readable = true, .writeable = false };
case 0b10010:
case 0b10011: return { .type = Type::Data, .readable = true, .writeable = true };
case 0b10100:
case 0b10101: return { .type = Type::Stack, .readable = true, .writeable = false };
case 0b10110:
case 0b10111: return { .type = Type::Stack, .readable = true, .writeable = true };
case 0b11000:
case 0b11001: return { .type = Type::Code, .readable = false, .writeable = false, .conforming = false };
case 0b11010:
case 0b11011: return { .type = Type::Code, .readable = true, .writeable = false, .conforming = false };
case 0b11100:
case 0b11101: return { .type = Type::Code, .readable = false, .writeable = false, .conforming = true };
case 0b11110:
case 0b11111: return { .type = Type::Code, .readable = true, .writeable = false, .conforming = true };
}
}
auto operator <=> (const SegmentDescriptor &) const = default;
private:
uint32_t base_;
uint32_t offset_;
DescriptorBounds bounds_;
uint8_t type_;
uint16_t segment_;
bool local_;
};
struct InterruptDescriptor {
InterruptDescriptor(const uint16_t, bool, const uint16_t descriptor[4]) noexcept :
segment_(descriptor[1]),
offset_(uint32_t(descriptor[0] | (descriptor[3] << 16))),
flags_(descriptor[2] >> 8) {}
uint16_t segment() const { return segment_; }
uint32_t offset() const { return offset_; }
bool present() const { return flags_ & 0x80; }
uint8_t priority() const { return (flags_ >> 5) & 3; }
enum class Type {
Task = 0x5,
Interrupt16 = 0x6, Trap16 = 0x7,
Interrupt32 = 0xe, Trap32 = 0xf,
};
Type type() const {
return Type(flags_ & 0xf);
}
private:
uint16_t segment_;
uint32_t offset_;
uint8_t flags_;
};
template <typename SegmentT>
struct SegmentRegisterSet {
SegmentT &operator[](const Source segment) {
return values_[index_of(segment)];
}
const SegmentT &operator[](const Source segment) const {
return values_[index_of(segment)];
}
auto operator <=>(const SegmentRegisterSet &rhs) const = default;
private:
std::array<SegmentT, 6> values_{};
static constexpr size_t index_of(const Source segment) {
assert(is_segment_register(segment));
return size_t(segment) - size_t(Source::ES);
}
};
template <typename DescriptorT, typename LinearMemoryT>
//requires is_linear_memory<LinearMemoryT>
DescriptorT descriptor_at(
LinearMemoryT &memory,
const DescriptorTablePointer table,
const uint16_t offset,
const bool local
) {
if(offset > table.limit - 8) {
printf("TODO: descriptor table overrun exception.\n");
assert(false);
}
const auto address = table.base + offset;
using AccessType = InstructionSet::x86::AccessType;
const uint32_t table_end = table.base + table.limit;
const uint16_t entry[] = {
memory.template access<uint16_t, AccessType::Read>(address, table_end),
memory.template access<uint16_t, AccessType::Read>(address + 2, table_end),
memory.template access<uint16_t, AccessType::Read>(address + 4, table_end),
memory.template access<uint16_t, AccessType::Read>(address + 6, table_end)
};
return DescriptorT(uint16_t(offset) & ~7, local, entry);
}
template <typename DescriptorT, typename LinearMemoryT>
//requires is_linear_memory<LinearMemoryT>
void set_descriptor_type_flag(
LinearMemoryT &memory,
const DescriptorTablePointer table,
const DescriptorT &descriptor,
const DescriptorTypeFlag flag
) {
const auto address = table.base + (descriptor.segment() & ~7);
const uint32_t table_end = table.base + table.limit;
auto type = memory.template access<uint16_t, AccessType::PreauthorisedRead>(address + 5, table_end);
type |= flag;
memory.template access<uint16_t, AccessType::Write>(address + 5, table_end) = type;
}
}
+168
View File
@@ -0,0 +1,168 @@
//
// Interrupts.hpp
// Clock Signal
//
// Created by Thomas Harte on 06/10/2023.
// Copyright © 2023 Thomas Harte. All rights reserved.
//
#pragma once
#include <cassert>
#include <cstdint>
namespace InstructionSet::x86 {
enum class Vector: uint8_t {
//
// Present on all devices.
//
DivideError = 0,
SingleStep = 1,
NMI = 2,
Breakpoint = 3,
Overflow = 4,
BoundRangeExceeded = 5,
//
// Added by the 80286.
//
InvalidOpcode = 6,
DeviceNotAvailable = 7,
DoubleFault = 8,
CoprocessorSegmentOverrun = 9,
InvalidTSS = 10,
SegmentNotPresent = 11,
StackSegmentFault = 12,
GeneralProtectionFault = 13,
FloatingPointException = 16,
//
// Added by the 80286.
//
PageFault = 14,
AlignmentCheck = 17,
MachineCheck = 18,
};
constexpr bool has_error_code(const Vector vector) {
switch(vector) {
using enum Vector;
case DivideError:
case SingleStep:
case NMI:
case Breakpoint:
case Overflow:
case BoundRangeExceeded:
case InvalidOpcode:
case DeviceNotAvailable:
case CoprocessorSegmentOverrun:
case FloatingPointException:
return false;
case DoubleFault:
case InvalidTSS:
case SegmentNotPresent:
case StackSegmentFault:
case GeneralProtectionFault:
return true;
default: // 386 exceptions; I don't know yet.
break;
}
assert(false);
return false;
}
constexpr bool posts_next_ip(const Vector vector) {
switch(vector) {
using enum Vector;
default:
return false;
case SingleStep:
case Breakpoint:
case Overflow:
return true;
}
}
struct ExceptionCode {
ExceptionCode() = default;
ExceptionCode(
const uint16_t index,
const bool is_local,
const bool is_interrupt,
const bool was_external) noexcept :
value_(
index |
(is_local ? 0x4 : 0x0) |
(is_interrupt ? 0x2 : 0x0) |
(was_external ? 0x1 : 0x0)
) {}
// i.e.:
// b3b15: IDT/GDT/LDT entry
// b2: 1 => in LDT; 0 => in GDT;
// b1: 1 => in IDT, ignore b2; 0 => use b2;
// b0:
// 1 => trigger was external to program code;
// 0 => trigger was caused by the instruction described by the CS:IP that is on the stack.
ExceptionCode(const uint16_t value) :
value_(value) {}
operator uint16_t() const {
return value_;
}
static ExceptionCode zero() {
return ExceptionCode();
}
private:
uint16_t value_ = 0;
};
struct Exception {
ExceptionCode code{}; // Exception code to push to the stack if this is an internal
// exception that provides a code and post_ip_as_code is `false`.
uint8_t vector{}; // Will be equal to value of a `Vector` enum if internal.
enum class CodeType: uint8_t {
Internal,
External,
};
CodeType code_type = CodeType::Internal;
/// Generates an internal exception with no error code.
template <Vector cause>
requires (!has_error_code(cause))
static constexpr Exception exception() {
return Exception(uint8_t(cause));
}
/// Generates an internal exception with a specified error code.
template <Vector cause>
requires (has_error_code(cause))
static constexpr Exception exception(const ExceptionCode code) {
return Exception(uint8_t(cause), code);
}
/// Generates an externally-motivated exception (i.e. an interrupt).
static constexpr Exception interrupt(const uint8_t vector) {
return Exception(vector, CodeType::External);
}
private:
constexpr Exception(const uint8_t vector) noexcept : vector(vector) {}
constexpr Exception(const uint8_t vector, const ExceptionCode code) noexcept : code(code), vector(vector){}
constexpr Exception(const uint8_t vector, const CodeType code_type) noexcept :
vector(vector), code_type(code_type) {}
};
static_assert(sizeof(Exception) <= 4);
}
+35 -13
View File
@@ -9,6 +9,7 @@
#pragma once
#include "Numeric/Carry.hpp"
#include "AccessType.hpp"
namespace InstructionSet::x86 {
@@ -80,6 +81,18 @@ class Flags {
public:
using FlagT = uint32_t;
Flags(const Model model) {
switch(model) {
case Model::i8086:
case Model::i80186:
forced_set_ = 0xf002;
break;
default:
forced_set_ = 0x0002;
break;
}
}
// Flag getters.
template <Flag flag_v> bool flag() const {
switch(flag_v) {
@@ -121,7 +134,9 @@ public:
/// • Flag::Interrupt: sets interrupt if @c value is non-zero;
/// • Flag::Trap: sets interrupt if @c value is non-zero;
/// • Flag::Direction: sets direction if @c value is non-zero.
template <typename IntT, Flag... flags> void set_from(const IntT value) {
template <typename IntT, Flag... flags>
requires is_x86_data_type<IntT>
void set_from(const IntT value) {
for(const auto flag: {flags...}) {
switch(flag) {
default: break;
@@ -141,7 +156,9 @@ public:
set_from<FlagT, flags...>(value);
}
template <typename IntT> IntT carry_bit() const { return carry_ ? 1 : 0; }
template <typename IntT>
requires is_x86_data_type<IntT>
IntT carry_bit() const { return carry_ ? 1 : 0; }
bool not_parity_bit() const {
// x86 parity always considers the lowest 8-bits only.
auto result = static_cast<uint8_t>(parity_);
@@ -151,7 +168,9 @@ public:
return result & 1;
}
template <typename IntT> IntT direction() const { return static_cast<IntT>(direction_); }
template <typename IntT>
requires is_x86_data_type<IntT>
IntT direction() const { return static_cast<IntT>(direction_); }
// Complete value get and set.
void set(uint16_t value) {
@@ -170,7 +189,7 @@ public:
uint16_t get() const {
return
0xf002 |
forced_set_ |
(flag<Flag::Carry>() ? FlagValue::Carry : 0) |
(flag<Flag::AuxiliaryCarry>() ? FlagValue::AuxiliaryCarry : 0) |
@@ -209,22 +228,25 @@ public:
private:
// Non-zero => set; zero => unset.
uint32_t carry_;
uint32_t auxiliary_carry_;
uint32_t sign_;
uint32_t overflow_;
uint32_t trap_;
uint32_t interrupt_;
uint32_t carry_{};
uint32_t auxiliary_carry_{};
uint32_t sign_{};
uint32_t overflow_{};
uint32_t trap_{};
uint32_t interrupt_{};
// +1 = direction flag not set;
// -1 = direction flag set.
int32_t direction_;
int32_t direction_{};
// Zero => set; non-zero => unset.
uint32_t zero_;
uint32_t zero_{};
// Odd number of bits => set; even => unset.
uint32_t parity_;
uint32_t parity_{};
// Model specific stuff: bits that are always set, regardless of other state.
uint16_t forced_set_{};
};
}
@@ -9,7 +9,7 @@
#pragma once
#include "InstructionSets/x86/AccessType.hpp"
#include "InstructionSets/x86/Interrupts.hpp"
#include "InstructionSets/x86/Exceptions.hpp"
#include "InstructionSets/x86/Perform.hpp"
#include "Numeric/Carry.hpp"
@@ -92,11 +92,11 @@ void test(
/*
The OF and CF flags are cleared to 0.
The SF, ZF, and PF flags are set according to the result (see the Operation section above).
The state of the AF flag is undefined.
The state of the AF flag is formally undefined but known to be reset.
*/
const IntT result = destination & source;
context.flags.template set_from<Flag::Carry, Flag::Overflow>(0);
context.flags.template set_from<Flag::Carry, Flag::Overflow, Flag::AuxiliaryCarry>(0);
context.flags.template set_from<IntT, Flag::Zero, Flag::Sign, Flag::ParityOdd>(result);
}
@@ -128,7 +128,7 @@ void mul(
}
template <typename IntT, typename ContextT>
void imul(
void imul_double(
modify_t<IntT> destination_high,
modify_t<IntT> destination_low,
read_t<IntT> source,
@@ -167,6 +167,36 @@ void imul(
context.flags.template set_from<Flag::Overflow, Flag::Carry>(destination_high != sign_extension);
}
template <typename IntT, typename ContextT>
void imul_single(
write_t<IntT> destination,
read_t<IntT> source1,
read_t<IntT> source2,
ContextT &context
) {
using sIntT = typename std::make_signed<IntT>::type;
const auto top_part = IntT((sIntT(source1) * sIntT(source2)) >> (8 * sizeof(IntT)));
const auto result = IntT(sIntT(source1) * sIntT(source2));
destination = result;
const auto sign_extension = (result & Numeric::top_bit<IntT>()) ? IntT(~0) : 0;
context.flags.template set_from<Flag::Overflow, Flag::Carry>(top_part != sign_extension);
}
template <typename ContextT>
void divide_error(ContextT &context) {
// 8086-style: just segue directly to the interrupt.
//
// 80286-style: throw the divide error, allowing the caller to insert
// additional context (primarily: IP of this instruction, not the next).
static constexpr auto exception = Exception::exception<Vector::DivideError>();
if constexpr (uses_8086_exceptions(ContextT::model)) {
interrupt(exception, context);
} else {
throw exception;
}
}
template <typename IntT, typename ContextT>
void div(
modify_t<IntT> destination_high,
@@ -212,16 +242,14 @@ void div(
The CF, OF, SF, ZF, AF, and PF flags are undefined.
*/
if(!source) {
interrupt(Interrupt::DivideError, context);
return;
return divide_error(context);
}
// TEMPORARY HACK. Will not work with DWords.
const uint32_t dividend = uint32_t((destination_high << (8 * sizeof(IntT))) + destination_low);
const auto result = dividend / source;
if(IntT(result) != result) {
interrupt(Interrupt::DivideError, context);
return;
return divide_error(context);
}
destination_low = IntT(result);
@@ -262,7 +290,7 @@ void idiv(
FI;
ELSE (* quadword/doubleword operation *)
temp EDX:EAX / SRC; (* signed division *)
IF (temp > 7FFFFFFFH) OR (temp < 80000000H) (* if a positive result is greater than 7FFFFFFFH
IF (temp > 7FFFFFFFH) OR (temp < 80000000H) (* if a positive result is greater than 7FFFFFFFH
or a negative result is less than 80000000H *)
THEN #DE; (* divide error *) ;
ELSE
@@ -276,8 +304,7 @@ void idiv(
The CF, OF, SF, ZF, AF, and PF flags are undefined.
*/
if(!source) {
interrupt(Interrupt::DivideError, context);
return;
return divide_error(context);
}
// TEMPORARY HACK. Will not work with DWords.
@@ -292,8 +319,7 @@ void idiv(
}
if(sIntT(result) != result) {
interrupt(Interrupt::DivideError, context);
return;
return divide_error(context);
}
destination_low = IntT(result);
+29 -7
View File
@@ -22,10 +22,18 @@ void aaas(
) {
if((ax.halves.low & 0x0f) > 9 || context.flags.template flag<Flag::AuxiliaryCarry>()) {
if constexpr (add) {
ax.halves.low += 6;
if constexpr (ContextT::model <= Model::i80186) {
ax.halves.low += 6;
} else {
ax.full += 6;
}
++ax.halves.high;
} else {
ax.halves.low -= 6;
if constexpr (ContextT::model <= Model::i80186) {
ax.halves.low -= 6;
} else {
ax.full -= 6;
}
--ax.halves.high;
}
context.flags.template set_from<Flag::Carry, Flag::AuxiliaryCarry>(1);
@@ -75,8 +83,13 @@ void aam(
If ... an immediate value of 0 is used, it will cause a #DE (divide error) exception.
*/
if(!imm) {
interrupt(Interrupt::DivideError, context);
return;
static constexpr auto exception = Exception::exception<Vector::DivideError>();
if constexpr (uses_8086_exceptions(ContextT::model)) {
interrupt(exception, context);
return;
} else {
throw exception;
}
}
ax.halves.high = ax.halves.low / imm;
@@ -91,18 +104,27 @@ void daas(
ContextT &context
) {
bool top_exceeded_threshold;
if constexpr (ContextT::model == Model::i8086) {
static constexpr bool is_8086 = ContextT::model == Model::i8086;
if constexpr (is_8086) {
top_exceeded_threshold = al > (context.flags.template flag<Flag::AuxiliaryCarry>() ? 0x9f : 0x99);
} else {
top_exceeded_threshold = al > 0x99;
}
const auto initial_cf = context.flags.template flag<Flag::Carry>();
if((al & 0x0f) > 0x09 || context.flags.template flag<Flag::AuxiliaryCarry>()) {
if constexpr (add) al += 0x06; else al -= 0x06;
const auto prior_al = al;
if constexpr (add) {
al += 0x06;
if(!is_8086 && al < prior_al) context.flags.template set_from<Flag::Carry>(1);
} else {
al -= 0x06;
if(!is_8086 && al > prior_al) context.flags.template set_from<Flag::Carry>(1);
}
context.flags.template set_from<Flag::AuxiliaryCarry>(1);
}
if(top_exceeded_threshold || context.flags.template flag<Flag::Carry>()) {
if(top_exceeded_threshold || initial_cf) {
if constexpr (add) al += 0x60; else al -= 0x60;
context.flags.template set_from<Flag::Carry>(1);
}
@@ -10,6 +10,7 @@
#include "Resolver.hpp"
#include "Stack.hpp"
#include "InstructionSets/x86/TaskStateSegment.hpp"
#include "InstructionSets/x86/AccessType.hpp"
#include <type_traits>
@@ -105,59 +106,73 @@ void jump_absolute(
context.flow_controller.template jump<uint16_t>(target);
}
template <typename AddressT, typename InstructionT, typename ContextT>
template <typename AddressT, typename ContextT>
void call_far(
InstructionT &instruction,
const uint16_t segment,
const AddressT offset,
ContextT &context
) {
// TODO: eliminate 16-bit assumption below.
const Source source_segment = instruction.data_segment();
context.memory.preauthorise_stack_write(sizeof(uint16_t) * 2);
context.segments.preauthorise_call(
Source::CS,
offset,
[&] {
context.memory.preauthorise_stack_write(sizeof(uint16_t) * 2, sizeof(uint16_t));
push<uint16_t, true>(context.registers.cs(), context);
push<uint16_t, true>(context.registers.ip(), context);
context.flow_controller.template jump<AddressT>(segment, offset);
},
[&] (const SegmentDescriptor &descriptor) {
(void)descriptor;
printf("TODO: protected mode far call");
}
);
}
uint16_t source_address;
template <typename AddressT, InstructionType type, typename ContextT>
void call_far(
const Instruction<type> &instruction,
ContextT &context
) {
const Source source_segment = instruction.data_segment();
AddressT source_address;
const auto pointer = instruction.destination();
switch(pointer.source()) {
default:
case Source::Immediate:
push<uint16_t, true>(context.registers.cs(), context);
push<uint16_t, true>(context.registers.ip(), context);
context.flow_controller.template jump<uint16_t>(instruction.segment(), instruction.offset());
call_far(instruction.segment(), instruction.offset(), context);
return;
case Source::Indirect:
source_address = uint16_t(
address<Source::Indirect, uint16_t, AccessType::Read>(instruction, pointer, context)
address<Source::Indirect, AddressT, AccessType::Read>(instruction, pointer, context)
);
break;
case Source::IndirectNoBase:
source_address = uint16_t(
address<Source::IndirectNoBase, uint16_t, AccessType::Read>(instruction, pointer, context)
address<Source::IndirectNoBase, AddressT, AccessType::Read>(instruction, pointer, context)
);
break;
case Source::DirectAddress:
source_address = uint16_t(
address<Source::DirectAddress, uint16_t, AccessType::Read>(instruction, pointer, context)
address<Source::DirectAddress, AddressT, AccessType::Read>(instruction, pointer, context)
);
break;
}
context.memory.preauthorise_read(source_segment, source_address, sizeof(uint16_t) * 2);
// context.memory.preauthorise_read(source_segment, source_address, sizeof(uint16_t) + sizeof(AddressT));
const auto offset =
context.memory.template access<uint16_t, AccessType::PreauthorisedRead>(source_segment, source_address);
context.memory.template access<AddressT, AccessType::Read>(source_segment, source_address);
source_address += 2;
const auto segment =
context.memory.template access<uint16_t, AccessType::PreauthorisedRead>(source_segment, source_address);
context.memory.template access<uint16_t, AccessType::Read>(source_segment, source_address);
// At least on an 8086, the stack writes occur after the target address read.
push<uint16_t, true>(context.registers.cs(), context);
push<uint16_t, true>(context.registers.ip(), context);
context.flow_controller.template jump<AddressT>(segment, offset);
call_far(segment, offset, context);
}
template <typename InstructionT, typename ContextT>
template <InstructionType type, typename ContextT>
void jump_far(
InstructionT &instruction,
const Instruction<type> &instruction,
ContextT &context
) {
// TODO: eliminate 16-bit assumption below.
@@ -187,13 +202,13 @@ void jump_far(
}
const Source source_segment = instruction.data_segment();
context.memory.preauthorise_read(source_segment, source_address, sizeof(uint16_t) * 2);
// context.memory.preauthorise_read(source_segment, source_address, sizeof(uint16_t) * 2);
const auto offset =
context.memory.template access<uint16_t, AccessType::PreauthorisedRead>(source_segment, source_address);
context.memory.template access<uint16_t, AccessType::Read>(source_segment, source_address);
source_address += 2;
const auto segment =
context.memory.template access<uint16_t, AccessType::PreauthorisedRead>(source_segment, source_address);
context.memory.template access<uint16_t, AccessType::Read>(source_segment, source_address);
context.flow_controller.template jump<uint16_t>(segment, offset);
}
@@ -202,7 +217,7 @@ void iret(
ContextT &context
) {
// TODO: all modes other than 16-bit real mode.
context.memory.preauthorise_stack_read(sizeof(uint16_t) * 3);
context.memory.preauthorise_stack_read(sizeof(uint16_t) * 3, sizeof(uint16_t));
const auto ip = pop<uint16_t, true>(context);
const auto cs = pop<uint16_t, true>(context);
context.flags.set(pop<uint16_t, true>(context));
@@ -224,7 +239,7 @@ void ret_far(
const InstructionT instruction,
ContextT &context
) {
context.memory.preauthorise_stack_read(sizeof(uint16_t) * 2);
context.memory.preauthorise_stack_read(sizeof(uint16_t) * 2, sizeof(uint16_t));
const auto ip = pop<uint16_t, true>(context);
const auto cs = pop<uint16_t, true>(context);
context.registers.sp() += instruction.operand();
@@ -236,28 +251,37 @@ void into(
ContextT &context
) {
if(context.flags.template flag<Flag::Overflow>()) {
interrupt(Interrupt::Overflow, context);
static constexpr auto exception = Exception::exception<Vector::Overflow>();
if constexpr (uses_8086_exceptions(ContextT::model)) {
interrupt(exception, context);
} else {
throw exception;
}
}
}
template <typename IntT, typename AddressT, typename InstructionT, typename ContextT>
void bound(
const InstructionT &instruction,
read_t<AddressT> destination,
read_t<IntT> destination,
read_t<AddressT> source,
ContextT &context
) {
using sIntT = typename std::make_signed<IntT>::type;
const auto source_segment = instruction.data_segment();
context.memory.preauthorise_read(source_segment, source, 2*sizeof(IntT));
const auto lower_bound =
sIntT(context.memory.template access<IntT, AccessType::PreauthorisedRead>(source_segment, source));
sIntT(context.memory.template access<IntT, AccessType::Read>(source_segment, source));
const auto upper_bound =
sIntT(context.memory.template access<IntT, AccessType::PreauthorisedRead>(source_segment, IntT(source + 2)));
sIntT(context.memory.template access<IntT, AccessType::Read>(source_segment, IntT(source + 2)));
if(sIntT(destination) < lower_bound || sIntT(destination) > upper_bound) {
interrupt(Interrupt::BoundRangeExceeded, context);
static constexpr auto exception = Exception::exception<Vector::BoundRangeExceeded>();
if constexpr (uses_8086_exceptions(ContextT::model)) {
interrupt(exception, context);
} else {
throw exception;
}
}
}

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