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

Compare commits

...

385 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
175 changed files with 7631 additions and 1968 deletions
+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]);
+25 -2
View File
@@ -85,6 +85,12 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(
TargetPlatform::IntType,
bool
) {
const auto early_exit = [](auto &ptr) {
TargetList list;
list.push_back(std::move(ptr));
return list;
};
auto targetElectron = std::make_unique<ElectronTarget>();
auto targetBBC = std::make_unique<BBCMicroTarget>();
auto targetArchimedes = std::make_unique<ArchimedesTarget>();
@@ -126,7 +132,7 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(
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.
// Electron: use the Pres ADFS if using an ADFS, as it leaves Page at &EOO.
// 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);
@@ -136,6 +142,14 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(
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) {
@@ -160,10 +174,19 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(
sole_basic_file ? "CHAIN \"" + sole_basic_file->name + "\"\n" : "*CAT\n";
}
// 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: dfs_catalogue ? dfs_catalogue->files : adfs_catalogue->files) {
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
+8
View File
@@ -9,6 +9,7 @@
#pragma once
#include "Analyser/Static/StaticAnalyser.hpp"
#include "Reflection/Enum.hpp"
#include "Reflection/Struct.hpp"
#include <string>
@@ -43,6 +44,10 @@ struct BBCMicroTarget: public ::Analyser::Static::Target, public Reflection::Str
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) {}
@@ -52,6 +57,9 @@ private:
DeclareField(has_1770dfs);
DeclareField(has_adfs);
DeclareField(has_sideways_ram);
DeclareField(has_beebsid);
AnnounceEnum(TubeProcessor);
DeclareField(tube_processor);
}
};
+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
@@ -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;
}
+30 -17
View File
@@ -8,8 +8,8 @@
#include "1770.hpp"
#include "Storage/Disk/Encodings/MFM/Constants.hpp"
#include "Outputs/Log.hpp"
#include "Storage/Disk/Encodings/MFM/Constants.hpp"
namespace {
using Logger = Log::Logger<Log::Source::WDFDC>;
@@ -133,17 +133,34 @@ void WD1770::run_for(const Cycles cycles) {
}
}
#include <iostream>
void WD1770::posit_event(const int new_event_type) {
#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) distance_into_section_ = 0; \
WAIT_FOR_EVENT(Event::Token); \
if(get_latest_token().type == Token::Byte) ++distance_into_section_; \
if(distance_into_section_ < count) { \
return; \
}
#define BEGIN_SECTION() switch(resume_point_) { default:
#define END_SECTION() (void)0; }
#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)) {
@@ -184,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;
@@ -215,7 +232,7 @@ void WD1770::posit_event(const int new_event_type) {
BEGIN_SECTION()
// Wait for a new command, branch to the appropriate handler.
case 0:
case IdleResumePoint:
wait_for_command:
Logger::info().append("Idle...");
set_data_mode(DataMode::Scanning);
@@ -487,12 +504,8 @@ void WD1770::posit_event(const int new_event_type) {
WAIT_FOR_EVENT(Event::Token);
if(get_latest_token().type != Token::Byte) goto type2_read_byte;
data_ = get_latest_token().byte_value;
// Logger::info().append("Posting %02x", data_);
update_status([] (Status &status) {
status.lost_data |= status.data_request;
// if(status.lost_data) {
// Logger::info().append("Lost data");
// }
status.data_request = true;
});
distance_into_section_++;
+4 -1
View File
@@ -124,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];
+2 -3
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_;
@@ -148,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 {
+22 -23
View File
@@ -76,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;
}
@@ -94,12 +94,18 @@ 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) {
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;
@@ -121,13 +127,13 @@ public:
}
// 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_line = value; break;
case 10:
@@ -156,15 +162,15 @@ public:
0x7f, // Start horizontal retrace.
0x1f, 0x7f, 0x7f,
0xff, 0x1f, 0x7f, 0x1f,
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;
}
}
@@ -486,9 +492,6 @@ private:
/// Provide interlaced sync and scan even/odd lines depending on field.
SyncAndVideo,
};
enum class BlinkMode {
// TODO.
};
// Comments on the right provide the corresponding signal name in hoglet's VHDL implementation.
struct {
@@ -521,7 +524,7 @@ private:
uint8_t registers_[18]{};
uint8_t dummy_register_ = 0;
int selected_register_ = 0;
Numeric::SizedInt<5> selected_register_ = 0;
CharacterAddress character_counter_; // h_counter
Numeric::SizedInt<3> character_reset_history_; // sol
@@ -530,7 +533,6 @@ private:
LineAddress line_; // line_counter
LineAddress next_line_; // line_counter_next
RefreshAddress refresh_; // ma_i
uint8_t adjustment_counter_ = 0;
bool character_is_visible_ = false; // h_display
bool row_is_visible_ = false; // v_display
@@ -546,8 +548,7 @@ private:
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; // eof_latched
bool eom_latched_ = false; // eom_latched
@@ -558,8 +559,6 @@ private:
bool vsync_even_ = false; // vs_even
bool vsync_odd_ = false; // vs_odd
bool reset_ = false;
Numeric::SizedInt<3> cursor_history_; // cursor0, cursor1, cursor2 [TODO]
bool line_is_interlaced_ = false;
+29 -19
View File
@@ -58,15 +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;
@@ -141,22 +144,30 @@ uint8_t i8272::read(const int address) {
}
void i8272::posit_event(const int event_type) {
#define BEGIN_SECTION() switch(resume_point_) { default:
#define BEGIN_SECTION() switch(resume_point_) { default: case IdleResumePoint:
#define END_SECTION() }
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; \
interesting_event_mask_ = int(mask); \
return; \
case __LINE__:
#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) interesting_event_mask_ = int(Event8272::Timer); \
delay_time_ = ms_to_cycles(ms); \
is_sleeping_ = false; \
update_clocking_observer(); \
resume_point_ = __LINE__; \
[[fallthrough]]; \
case __LINE__: \
if(delay_time_) return;
#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 LABEL(x, y) PASTE(x, y)
@@ -716,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;
@@ -731,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;
+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;
+3 -9
View File
@@ -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
@@ -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.
+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]{};
};
}
+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");
}
};
}
}
+9 -8
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.
@@ -192,8 +192,8 @@ public:
const auto analogue_value = [&](const int mask) {
switch(mask) {
default: return 0.5f;
case 0b01: return digital_maximum();
case 0b10: return digital_minimum();
case 0b01: return digital_minimum();
case 0b10: return digital_maximum();
}
};
@@ -264,10 +264,11 @@ private:
const int mask = [&] {
switch(input.type) {
default: return 0;
case Input::Type::Up: return 1 << 1;
case Input::Type::Down: return 1 << 2;
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;
case Input::Type::Left: return 1 << 4;
}
} ();
if(is_active) {
@@ -279,8 +280,8 @@ private:
int digital_mask(const Input::Type axis) const {
switch(axis) {
default: return 0;
case Input::Type::Horizontal: return (digital_inputs_ >> 3) & 3;
case Input::Type::Vertical: return (digital_inputs_ >> 1) & 3;
case Input::Type::Horizontal: return (digital_inputs_ >> 2) & 3;
case Input::Type::Vertical: return (digital_inputs_ >> 0) & 3;
}
}
int digital_inputs_ = 0;
+2 -2
View File
@@ -909,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;
@@ -924,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;
+1 -1
View File
@@ -204,7 +204,7 @@ struct SegmentDescriptor {
/// Accesses must be `>= bounds().begin` and `<= bounds().end`.
DescriptorBounds bounds() const { return bounds_; }
bool present() const { return type_ & 0x80; }
bool present() const { return type_ & 0x80; }
int privilege_level() const { return (type_ >> 5) & 3; }
uint8_t access_rights() const { return uint8_t(type_); }
@@ -290,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
@@ -450,8 +450,12 @@ template <
}
return;
case Operation::OUT: Primitive::out<IntT>(port(instruction.destination().source()), pair_low(), context); return;
case Operation::IN: Primitive::in<IntT>(port(instruction.source().source()), pair_low(), context); return;
case Operation::OUT:
Primitive::out<IntT>(port(instruction.destination().source()), pair_low(), context);
return;
case Operation::IN:
Primitive::in<IntT>(port(instruction.source().source()), pair_low(), context);
return;
case Operation::XLAT: Primitive::xlat<AddressT>(instruction, context); return;
@@ -746,7 +750,7 @@ requires is_context<ContextT>
void perform(
const Instruction<type> &instruction,
ContextT &context,
uint32_t source_ip
const ip_size_t<ContextT> source_ip
) {
if constexpr (uses_8086_exceptions(ContextT::model)) {
InstructionSet::x86::perform(
@@ -843,7 +847,7 @@ requires is_context<ContextT>
void fault(
const Exception exception,
ContextT &context,
const uint32_t source_ip
const ip_size_t<ContextT> source_ip
) {
if constexpr (uses_8086_exceptions(ContextT::model)) {
InstructionSet::x86::interrupt(
@@ -299,10 +299,10 @@ void sal(
} else {
const auto mask = (Numeric::top_bit<IntT>() >> (count - 1));
context.flags.template set_from<Flag::Carry>(
destination & mask
destination & mask
);
context.flags.template set_from<Flag::Overflow>(IntT(
(destination ^ (destination << 1)) & mask
(destination ^ (destination << 1)) & mask
));
destination <<= count;
}
+6 -2
View File
@@ -250,6 +250,10 @@ concept is_context =
is_context_real<ContextT> &&
(!has_protected_mode<ContextT::model> || is_context_protected<ContextT>);
/// Gives the instruction pointer size for a given context.
template <typename ContextT>
using ip_size_t = std::conditional_t<has_32bit_instructions<ContextT::model>, uint32_t, uint16_t>;
/// Performs @c instruction querying @c registers and/or @c memory as required, using @c io for port input/output,
/// and providing any flow control effects to @c flow_controller.
///
@@ -259,7 +263,7 @@ template <
typename ContextT
>
requires is_context<ContextT>
void perform(const Instruction<type> &, ContextT &, uint32_t source_ip);
void perform(const Instruction<type> &, ContextT &, ip_size_t<ContextT> source_ip);
/// Performs an Exception, which includes those generated by external sources.
/// @c source_ip is unused if the exception is an instance of `Exception::interrupt` but is required for other internal faults.
@@ -267,7 +271,7 @@ template <
typename ContextT
>
requires is_context<ContextT>
void fault(Exception, ContextT &, const uint32_t source_ip = 0);
void fault(Exception, ContextT &, const ip_size_t<ContextT> source_ip = 0);
}
+2 -2
View File
@@ -127,8 +127,8 @@ public:
template <DescriptorTable table>
void set(const DescriptorTablePointer location) {
switch(table) {
case DescriptorTable::Local: local_ = location; break;
case DescriptorTable::Global: global_ = location; break;
case DescriptorTable::Local: local_ = location; break;
case DescriptorTable::Global: global_ = location; break;
case DescriptorTable::Interrupt: interrupt_ = location; break;
}
}
+16 -10
View File
@@ -396,13 +396,19 @@ private:
//
// Mouse scripting; tick at a minimum of frame length.
//
static constexpr int TickFrequency = 24'000'000 / 50;
static constexpr int TickFrequency = ClockRate / 50; // i.e. 480,000
Cycles subtractor = cursor_action_subcycle_;
cursor_action_subcycle_ += cycles;
auto segments = cursor_action_subcycle_.divide(Cycles(TickFrequency)).as<int>();
while(segments--) {
Cycles next = Cycles(TickFrequency);
//
// Run up until end of next window.
//
Cycles next = Cycles(TickFrequency) - subtractor;
subtractor = Cycles(0);
if(next > cycles) next = cycles;
cycles -= next;
run(next);
if(!cursor_actions_.empty()) {
const auto move_to_next = [&]() {
@@ -413,7 +419,7 @@ private:
const auto &action = cursor_actions_.front();
switch(action.type) {
case CursorAction::Type::MoveTo: {
// A measure of where within the tip lies within
// A measure of where the tip lies within
// the default RISC OS cursor.
static constexpr int ActionPointOffset = 20;
static constexpr int MaxStep = 24;
@@ -457,12 +463,12 @@ private:
break;
}
}
//
// Execution proper.
//
run(next);
}
//
// Discharge residue.
//
run(cycles);
}
template <bool original_speed>
@@ -509,13 +515,13 @@ private:
// MARK: - Configuration options.
std::unique_ptr<Reflection::Struct> get_options() const final {
auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly);
options->quickload = accelerate_loading_;
options->quick_load = accelerate_loading_;
return options;
}
void set_options(const std::unique_ptr<Reflection::Struct> &str) final {
const auto options = dynamic_cast<Options *>(str.get());
accelerate_loading_ = options->quickload;
accelerate_loading_ = options->quick_load;
}
// MARK: - AudioProducer
+5 -5
View File
@@ -24,13 +24,13 @@ struct Machine {
const ROMMachine::ROMFetcher &rom_fetcher
);
class Options: public Reflection::StructImpl<Options>, public Configurable::QuickloadOption<Options> {
friend Configurable::QuickloadOption<Options>;
class Options: public Reflection::StructImpl<Options>, public Configurable::Options::QuickLoad<Options> {
friend Configurable::Options::QuickLoad<Options>;
public:
Options(Configurable::OptionsType type) :
Configurable::QuickloadOption<Options>(type == Configurable::OptionsType::UserFriendly) {}
Options(const Configurable::OptionsType type) :
Configurable::Options::QuickLoad<Options>(type == Configurable::OptionsType::UserFriendly) {}
private:
Options() : Options(Configurable::OptionsType::UserFriendly) {}
Options() : Options( Configurable::OptionsType::UserFriendly) {}
friend Reflection::StructImpl<Options>;
void declare_fields() {
declare_quickload_option();
+11 -11
View File
@@ -27,8 +27,17 @@ struct Video {
sound_(sound),
ram_(ram),
crt_(Outputs::Display::InputDataType::Red4Green4Blue4) {
const auto cycles_per_line = static_cast<int>(24'000'000 / (312 * 50));
crt_.set_new_timing(
cycles_per_line,
312, /* Height of display. */
Outputs::CRT::PAL::ColourSpace,
Outputs::CRT::PAL::ColourCycleNumerator,
Outputs::CRT::PAL::ColourCycleDenominator,
Outputs::CRT::PAL::VerticalSyncLength,
Outputs::CRT::PAL::AlternatesPhase);
set_clock_divider(3);
crt_.set_visible_area(Outputs::Display::Rect(0.041f, 0.04f, 0.95f, 0.95f));
crt_.set_fixed_framing(Outputs::Display::Rect(0.041f, 0.04f, 0.95f, 0.95f));
crt_.set_display_type(Outputs::Display::DisplayType::RGB);
}
@@ -210,7 +219,7 @@ struct Video {
case Phase::StartInterlacedSync: tick_horizontal<Phase::StartInterlacedSync>(); break;
case Phase::EndInterlacedSync: tick_horizontal<Phase::EndInterlacedSync>(); break;
}
++time_in_phase_;
time_in_phase_ += clock_divider_;
}
/// @returns @c true if a vertical retrace interrupt has been signalled since the last call to @c interrupt(); @c false otherwise.
@@ -476,15 +485,6 @@ private:
}
clock_divider_ = divider;
const auto cycles_per_line = static_cast<int>(24'000'000 / (divider * 312 * 50));
crt_.set_new_timing(
cycles_per_line,
312, /* Height of display. */
Outputs::CRT::PAL::ColourSpace,
Outputs::CRT::PAL::ColourCycleNumerator,
Outputs::CRT::PAL::ColourCycleDenominator,
Outputs::CRT::PAL::VerticalSyncLength,
Outputs::CRT::PAL::AlternatesPhase);
clock_rate_observer_.update_clock_rates();
}
+399 -125
View File
@@ -1,4 +1,5 @@
//
//
// BBCMicro.cpp
// Clock Signal
//
@@ -14,11 +15,16 @@
#include "Machines/Utility/MemoryFuzzer.hpp"
#include "Machines/Utility/Typer.hpp"
#include "Processors/6502/6502.hpp"
#include "Processors/6502Mk2/6502Mk2.hpp"
#include "Machines/Acorn/Tube/ULA.hpp"
#include "Machines/Acorn/Tube/Tube6502.hpp"
#include "Machines/Acorn/Tube/TubeZ80.hpp"
#include "Components/6522/6522.hpp"
#include "Components/6845/CRTC6845.hpp"
#include "Components/6850/6850.hpp"
#include "Components/SID/SID.hpp"
#include "Components/SAA5050/SAA5050.hpp"
#include "Components/SN76489/SN76489.hpp"
#include "Components/uPD7002/uPD7002.hpp"
@@ -30,6 +36,7 @@
#include "Outputs/Log.hpp"
#include "Outputs/CRT/CRT.hpp"
#include "Outputs/Speaker/Implementation/CompoundSource.hpp"
#include "Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#include "Concurrency/AsyncTaskQueue.hpp"
@@ -45,6 +52,9 @@ namespace BBCMicro {
namespace {
using Logger = Log::Logger<Log::Source::BBCMicro>;
using TubeProcessor = Analyser::Static::Acorn::BBCMicroTarget::TubeProcessor;
// MARK: - Joysticks.
/*!
Provides an analogue joystick with a single fire button.
@@ -93,25 +103,54 @@ private:
const int first_channel_;
bool fire_ = false;
};
// MARK: - Lazy audio holder.
/*!
Combines an SN76489 with an appropriate asynchronous queue and filtering speaker.
*/
// TODO: generalise the below and clean up across the project.
template <bool has_beebsid>
struct Audio {
private:
using CompoundSource = Outputs::Speaker::CompoundSource<TI::SN76489, MOS::SID::SID>;
using Source = std::conditional_t<has_beebsid, CompoundSource, TI::SN76489>;
using Speaker = Outputs::Speaker::PullLowpass<Source>;
Source &speaker_source() {
if constexpr (has_beebsid) {
return compound_;
} else {
return sn76489_;
}
}
public:
Audio() :
sn76489_(TI::SN76489::Personality::SN76489, audio_queue_, 2),
speaker_(sn76489_)
sn76489_(TI::SN76489::Personality::SN76489, audio_queue_, 4),
sid_(audio_queue_),
compound_(sn76489_, sid_),
speaker_(speaker_source())
{
// Combined with the additional divider specified above, implies this chip is clocked at 4Mhz.
speaker_.set_input_rate(2'000'000.0f);
// Combined with the additional divider specified above, implies the SN76489 is clocked at 4Mhz.
speaker_.set_input_rate(1'000'000.0f);
}
~Audio() {
audio_queue_.flush();
audio_queue_.lock_flush();
}
TI::SN76489 *operator ->() {
speaker_.run_for(audio_queue_, time_since_update_.flush<Cycles>());
return &sn76489_;
template <typename TargetT>
TargetT &get() {
post_time();
if constexpr (std::is_same_v<TargetT, TI::SN76489>) {
return sn76489_;
}
if constexpr (std::is_same_v<TargetT, MOS::SID::SID>) {
return sid_;
}
}
void operator +=(const Cycles duration) {
@@ -119,7 +158,7 @@ struct Audio {
}
void flush() {
speaker_.run_for(audio_queue_, time_since_update_.flush<Cycles>());
post_time();
audio_queue_.perform();
}
@@ -127,10 +166,20 @@ struct Audio {
return &speaker_;
}
size_t queue_size() {
return audio_queue_.size();
}
private:
void post_time() {
speaker_.run_for(audio_queue_, time_since_update_.divide(Cycles(2)));
}
Concurrency::AsyncTaskQueue<false> audio_queue_;
TI::SN76489 sn76489_;
Outputs::Speaker::PullLowpass<TI::SN76489> speaker_;
MOS::SID::SID sid_;
CompoundSource compound_;
Outputs::Speaker::PullLowpass<Source> speaker_;
Cycles time_since_update_;
};
@@ -158,23 +207,31 @@ protected:
uint16_t video_base_ = 0;
};
// MARK: - VIAs.
/*!
Models the system VIA, which connects to the SN76489 and the keyboard.
*/
struct SystemVIAPortHandler;
using SystemVIA = MOS::MOS6522::MOS6522<SystemVIAPortHandler>;
struct VSyncReceiver {
virtual void set_vsync(bool) = 0;
};
struct SystemVIADelegate {
virtual void strobe_lightpen() = 0;
};
struct SystemVIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
template <typename AudioT>
struct SystemVIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler, public VSyncReceiver {
SystemVIAPortHandler(
Audio &audio,
AudioT &audio,
VideoBaseAddress &video_base,
SystemVIA &via,
MOS::MOS6522::MOS6522<SystemVIAPortHandler<AudioT>> &via,
SystemVIADelegate &delegate,
const std::vector<std::unique_ptr<Inputs::Joystick>> &joysticks,
const bool run_disk
) :
audio_(audio), video_base_(video_base), via_(via), joysticks_(joysticks)
audio_(audio), video_base_(video_base), via_(via), joysticks_(joysticks), delegate_(delegate)
{
set_key_flag(6, run_disk);
set_key_flag(uint8_t(Key::Bit3), run_disk);
}
// CA2: key pressed;
@@ -203,7 +260,7 @@ struct SystemVIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
// Check for a strobe on the audio output.
if((old_latch^latch_) & old_latch & LatchFlags::WriteToSN76489) {
audio_->write(port_a_output_);
audio_.template get<TI::SN76489>().write(port_a_output_);
}
// Pass on the video wraparound/base.
@@ -221,12 +278,16 @@ struct SystemVIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
if(new_caps != caps_led_state_) {
caps_led_state_ = new_caps;
activity_observer_->set_led_status(caps_led, caps_led_state_);
if(activity_observer_) {
activity_observer_->set_led_status(caps_led, caps_led_state_);
}
}
if(new_shift != shift_led_state_) {
shift_led_state_ = new_shift;
activity_observer_->set_led_status(shift_led, shift_led_state_);
if(activity_observer_) {
activity_observer_->set_led_status(shift_led, shift_led_state_);
}
}
}
}
@@ -253,6 +314,16 @@ struct SystemVIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
return key_state;
}
template<MOS::MOS6522::Port port, MOS::MOS6522::Line line>
void set_control_line_output(const bool value) {
if constexpr (port == MOS::MOS6522::Port::B && line == MOS::MOS6522::Line::Two) {
if(previous_cb2_ != value && !value) {
delegate_.strobe_lightpen();
}
previous_cb2_ = value;
}
}
void set_key(const uint8_t key, const bool pressed) {
set_key_flag(key, pressed);
update_ca2();
@@ -289,6 +360,10 @@ struct SystemVIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
}
}
bool caps_lock() const {
return caps_led_state_;
}
private:
uint8_t latch_ = 0;
enum LatchFlags: uint8_t {
@@ -297,11 +372,12 @@ private:
};
uint8_t port_a_output_ = 0;
bool previous_cb2_ = false;
Audio &audio_;
AudioT &audio_;
VideoBaseAddress &video_base_;
SystemVIA &via_;
MOS::MOS6522::MOS6522<SystemVIAPortHandler<AudioT>> &via_;
// MARK: - Keyboard state and helpers.
@@ -332,7 +408,7 @@ private:
}
} ()).to_ulong() & 0xfe; // Discard the first row.
via_.set_control_line_input<MOS::MOS6522::Port::A, MOS::MOS6522::Line::Two>(state);
via_.template set_control_line_input<MOS::MOS6522::Port::A, MOS::MOS6522::Line::Two>(state);
}
static inline const std::string caps_led = "CAPS";
@@ -342,19 +418,39 @@ private:
Activity::Observer *activity_observer_ = nullptr;
const std::vector<std::unique_ptr<Inputs::Joystick>> &joysticks_;
SystemVIADelegate &delegate_;
void set_vsync(const bool vsync) override {
via_.template set_control_line_input<MOS::MOS6522::Port::A, MOS::MOS6522::Line::One>(vsync);
}
};
// MARK: - CRTC output.
/*!
Handles CRTC bus activity.
*/
class CRTCBusHandler: public VideoBaseAddress {
public:
CRTCBusHandler(const uint8_t *const ram, SystemVIA &system_via) :
CRTCBusHandler(const uint8_t *const ram, VSyncReceiver &vsync_receiver) :
crt_(1024, 1, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red1Green1Blue1),
ram_(ram),
system_via_(system_via)
{
crt_.set_visible_area(crt_.get_rect_for_area(30, 256, 160, 800, 4.0f / 3.0f));
vsync_receiver_(vsync_receiver)
{}
void set_dynamic_framing(const bool enable) {
dynamic_framing_ = enable;
if(enable) {
crt_.set_dynamic_framing(
Outputs::Display::Rect(0.13333f, 0.06507f, 0.71579f, 0.86069f),
0.0f, 0.05f);
} else {
crt_.set_fixed_framing(crt_.get_rect_for_area(30, 256, 160, 800));
}
}
bool dynamic_framing() const {
return dynamic_framing_;
}
void set_palette(const uint8_t value) {
@@ -370,8 +466,7 @@ public:
}
void set_control(const uint8_t value) {
crtc_clock_multiplier_ = (value & 0x10) ? 1 : 2;
active_collation_.crtc_clock_multiplier = (value & 0x10) ? 1 : 2;
active_collation_.pixels_per_clock = 1 << ((value >> 2) & 0x03);
active_collation_.is_teletext = value & 0x02;
flash_mask_ = value & 0x01 ? 7 : 0;
@@ -388,7 +483,10 @@ public:
static_assert(!(PixelAllocationUnit % 16));
static_assert(!(PixelAllocationUnit % 12));
system_via_.set_control_line_input<MOS::MOS6522::Port::A, MOS::MOS6522::Line::One>(state.vsync);
if(state.vsync != vsync_) {
vsync_receiver_.set_vsync(state.vsync);
vsync_ = state.vsync;
}
// Count cycles since horizontal sync to insert a colour burst.
// TODO: this is copy/pasted from the CPC. How does the BBC do it?
@@ -419,7 +517,7 @@ public:
}
previous_vsync_ = state.vsync;
if(state.display_enable && !previous_display_enabled_) {
if(state.display_enable && !previous_display_enabled_ && active_collation_.is_teletext) {
saa5050_serialiser_.begin_line();
}
previous_display_enabled_ = state.display_enable;
@@ -500,7 +598,7 @@ public:
previous_collation_ = active_collation_;
if(!pixel_data_) {
pixel_pointer_ = pixel_data_ = crt_.begin_data(PixelAllocationUnit, 8);
pixel_pointer_ = pixel_data_ = crt_.begin_data(PixelAllocationUnit);
}
if(pixel_data_) {
@@ -518,7 +616,7 @@ public:
pixel_pointer_ += 12;
}
} else {
switch(crtc_clock_multiplier_ * active_collation_.pixels_per_clock) {
switch(active_collation_.crtc_clock_multiplier * active_collation_.pixels_per_clock) {
case 1: shift_pixels<1>(cursor_shifter_ & 7); break;
case 2: shift_pixels<2>(cursor_shifter_ & 7); break;
case 4: shift_pixels<4>(cursor_shifter_ & 7); break;
@@ -531,7 +629,7 @@ public:
}
// Increment cycles since state changed.
cycles_ += crtc_clock_multiplier_ << 3;
cycles_ += active_collation_.crtc_clock_multiplier << 3;
}
/// Sets the destination for output.
@@ -562,20 +660,30 @@ private:
Pixels
};
struct PixelCollation {
int pixels_per_clock;
bool is_teletext;
int crtc_clock_multiplier = 1;
int pixels_per_clock = 4;
bool is_teletext = false;
bool operator !=(const PixelCollation &rhs) {
if(is_teletext && rhs.is_teletext) return false;
return pixels_per_clock != rhs.pixels_per_clock;
// If both are teletext, just inspect the clock multiplier.
if(is_teletext && rhs.is_teletext) {
return crtc_clock_multiplier != rhs.crtc_clock_multiplier;
}
// If one is teletext but the other isn't, that's a sufficient difference.
if(is_teletext != rhs.is_teletext) return true;
// Compare pixel clock rate.
return pixels_per_clock != rhs.pixels_per_clock || crtc_clock_multiplier != rhs.crtc_clock_multiplier;
}
};
OutputMode previous_output_mode_ = OutputMode::Sync;
int cycles_ = 0;
int cycles_into_hsync_ = 0;
// int cycles_into_hsync_ = 0;
Outputs::CRT::CRT crt_;
bool dynamic_framing_ = true;
uint8_t *pixel_data_ = nullptr, *pixel_pointer_ = nullptr;
size_t pixels_collected() const {
@@ -590,7 +698,6 @@ private:
std::bitset<16> flash_flags_;
uint8_t flash_mask_ = 0;
int crtc_clock_multiplier_ = 1;
PixelCollation active_collation_;
uint8_t pixel_shifter_ = 0;
@@ -614,7 +721,8 @@ private:
}
const uint8_t *const ram_ = nullptr;
SystemVIA &system_via_;
VSyncReceiver &vsync_receiver_;
bool vsync_ = false;
Mullard::SAA5050Serialiser saa5050_serialiser_;
};
@@ -624,9 +732,30 @@ using CRTC = Motorola::CRTC::CRTC6845<
Motorola::CRTC::CursorType::Native>;
}
template <bool has_1770>
// MARK: - Tube.
template <typename HostT, TubeProcessor tube_processor>
struct Tube {
using TubeULA = Acorn::Tube::ULA<HostT>;
TubeULA ula;
Acorn::Tube::Processor<TubeULA, tube_processor> processor;
Tube(HostT &owner) :
ula(owner),
processor(ula) {}
};
template <typename HostT>
struct Tube<HostT, TubeProcessor::None> {
Tube(HostT &) {}
};
// MARK: - ConcreteMachine.
template <TubeProcessor tube_processor, bool has_1770, bool has_beebsid>
class ConcreteMachine:
public Activity::Source,
public Configurable::Device,
public Machine,
public MachineTypes::AudioProducer,
public MachineTypes::JoystickMachine,
@@ -636,6 +765,7 @@ class ConcreteMachine:
public MachineTypes::TimedMachine,
public MOS::MOS6522::IRQDelegatePortHandler::Delegate,
public NEC::uPD7002::Delegate,
public SystemVIADelegate,
public Utility::TypeRecipient<CharacterMapper>,
public WD::WD1770::Delegate
{
@@ -645,13 +775,14 @@ public:
const ROMMachine::ROMFetcher &rom_fetcher
) :
m6502_(*this),
system_via_port_handler_(audio_, crtc_bus_handler_, system_via_, joysticks_, target.should_shift_restart),
system_via_port_handler_(audio_, crtc_bus_handler_, system_via_, *this, joysticks_, target.should_shift_restart),
user_via_(user_via_port_handler_),
system_via_(system_via_port_handler_),
crtc_bus_handler_(ram_.data(), system_via_),
crtc_bus_handler_(ram_.data(), system_via_port_handler_),
crtc_(crtc_bus_handler_),
acia_(HalfCycles(2'000'000)), // TODO: look up real ACIA clock rate.
adc_(HalfCycles(2'000'000))
adc_(HalfCycles(2'000'000)),
tube_(*this)
{
set_clock_rate(2'000'000);
@@ -668,13 +799,17 @@ public:
using Name = ::ROM::Name;
auto request = Request(Name::AcornBASICII) && Request(Name::BBCMicroMOS12);
if(target.has_1770dfs) {
request = request && Request(Name::BBCMicroDFS226);
if(target.has_1770dfs || tube_processor != TubeProcessor::None) {
request = request && Request(Name::BBCMicro1770DFS226);
}
if(target.has_adfs) {
request = request && Request(Name::BBCMicroADFS130);
}
if constexpr (tube_processor != TubeProcessor::None) {
request = request && Request(tube_.processor.ROM);
}
auto roms = rom_fetcher(request);
if(!request.validate(roms)) {
throw ROMMachine::Error::MissingROMs;
@@ -686,13 +821,33 @@ public:
// Put BASIC in pole position.
install_sideways(15, roms.find(Name::AcornBASICII)->second, false);
// Install filing systems: put the DFS before the ADFS because it's more common on the BBC.
// Install filing systems: put the DFS before the ADFS because it's more common on the BBC if the user
// explicitly requested DFS. Include it at the end otherwise if it's just implied by the Tube.
size_t fs_slot = 14;
const auto add_sideways = [&](const Name name) {
install_sideways(fs_slot--, roms.find(name)->second, false);
};
if(target.has_1770dfs) {
install_sideways(fs_slot--, roms.find(Name::BBCMicroDFS226)->second, false);
add_sideways(Name::BBCMicro1770DFS226);
}
if(target.has_adfs) {
install_sideways(fs_slot--, roms.find(Name::BBCMicroADFS130)->second, false);
add_sideways(Name::BBCMicroADFS130);
}
if(!target.has_1770dfs && tube_processor != TubeProcessor::None) {
add_sideways(Name::BBCMicro1770DFS226);
}
// Throw the tube ROM to its target.
if constexpr (tube_processor != TubeProcessor::None) {
tube_.processor.set_rom(roms.find(tube_.processor.ROM)->second);
}
// Install the ADT ROM if available, but don't error if it's missing. It's very optional.
if(target.has_1770dfs || target.has_adfs) {
const auto adt_rom = rom_fetcher(Request(Name::BBCMicroAdvancedDiscToolkit140));
if(const auto rom = adt_rom.find(Name::BBCMicroAdvancedDiscToolkit140); rom != adt_rom.end()) {
install_sideways(fs_slot--, rom->second, false);
}
}
// Throw sideways RAM into all unused slots.
@@ -722,13 +877,10 @@ public:
}
// MARK: - 6502 bus.
Cycles perform_bus_operation(
const CPU::MOS6502::BusOperation operation,
const uint16_t address,
uint8_t *const value
) {
template <CPU::MOS6502Mk2::BusOperation operation, typename AddressT>
Cycles perform(const AddressT address, CPU::MOS6502Mk2::data_t<operation> value) {
// Returns @c true if @c address is a device on the 1Mhz bus; @c false otherwise.
static constexpr auto is_1mhz = [](const uint16_t address) {
const auto is_1mhz = [&] {
// Fast exit if outside the IO space.
if(address < 0xfc00) return false;
if(address >= 0xff00) return false;
@@ -747,10 +899,10 @@ public:
// Otherwise: in IO space, but not a 1Mhz device.
return false;
};
}();
// Determine whether this access hits the 1Mhz bus; if so then apply appropriate penalty, and update phase.
const auto duration = Cycles(is_1mhz(address) ? 2 + (phase_&1) : 1);
const auto duration = Cycles(is_1mhz ? 2 + (phase_&1) : 1);
if(typer_) typer_->run_for(duration);
phase_ += duration.as<int>();
@@ -777,11 +929,14 @@ public:
}
adc_.run_for(duration);
if constexpr (has_1770) {
// The WD1770 is nominally clocked at 8Mhz.
wd1770_.run_for(duration * 4);
}
if constexpr (requires {tube_.processor;}) {
tube_.processor.run_for(duration);
}
//
// Questionably-clocked devices.
@@ -794,109 +949,126 @@ public:
//
if(address >= 0xfc00 && address < 0xff00) {
if(address >= 0xfe40 && address < 0xfe60) {
if(is_read(operation)) {
*value = system_via_.read(address);
if constexpr (is_read(operation)) {
value = system_via_.read(address);
} else {
system_via_.write(address, *value);
system_via_.write(address, value);
}
} else if(address >= 0xfe60 && address < 0xfe80) {
if(is_read(operation)) {
*value = user_via_.read(address);
if constexpr (is_read(operation)) {
value = user_via_.read(address);
} else {
user_via_.write(address, *value);
user_via_.write(address, value);
}
} else if(address == 0xfe30) {
if(is_read(operation)) {
*value = 0xfe;
if constexpr (is_read(operation)) {
value = 0xfe;
} else {
page_sideways(*value & 0xf);
page_sideways(value & 0xf);
}
} else if(address >= 0xfe00 && address < 0xfe08) {
if(is_read(operation)) {
if constexpr (is_read(operation)) {
if(address & 1) {
*value = crtc_.get_register();
value = crtc_.get_register();
} else {
*value = crtc_.get_status();
value = crtc_.get_status();
}
} else {
if(address & 1) {
crtc_.set_register(*value);
crtc_.set_register(value);
} else {
crtc_.select_register(*value);
crtc_.select_register(value);
}
}
} else if(address >= 0xfe20 && address < 0xfe30) {
if(is_read(operation)) {
*value = 0xfe;
if constexpr (is_read(operation)) {
value = 0xfe;
} else {
switch(address) {
case 0xfe20:
crtc_bus_handler_.set_control(*value);
crtc_2mhz_ = *value & 0x10;
crtc_bus_handler_.set_control(value);
crtc_2mhz_ = value & 0x10;
break;
case 0xfe21:
crtc_bus_handler_.set_palette(*value);
crtc_bus_handler_.set_palette(value);
break;
}
}
} else if(address == 0xfee0) {
if(is_read(operation)) {
Logger::info().append("Read tube status: 0");
*value = 0;
} else if(address >= 0xfee0 && address < 0xfee8) {
if constexpr (requires {tube_.ula;}) {
if constexpr (is_read(operation)) {
value = tube_.ula.host_read(address);
} else {
tube_.ula.host_write(address, value);
}
} else {
Logger::info().append("Wrote tube: %02x", *value);
if constexpr (is_read(operation)) {
value = address == 0xfee0 ? 0xfe : 0xff;
}
}
} else if(address >= 0xfe08 && address < 0xfe10) {
if(is_read(operation)) {
if constexpr (is_read(operation)) {
// Logger::info().append("ACIA read");
*value = acia_.read(address);
value = acia_.read(address);
} else {
// Logger::info().append("ACIA write: %02x", *value);
acia_.write(address, *value);
acia_.write(address, value);
}
} else if(address >= 0xfec0 && address < 0xfee0) {
if(is_read(operation)) {
*value = adc_.read(address);
if constexpr (is_read(operation)) {
value = adc_.read(address);
} else {
adc_.write(address, *value);
adc_.write(address, value);
}
} else if(has_1770 && address >= 0xfe80 && address < 0xfe88) {
switch(address) {
case 0xfe80:
if(!is_read(operation)) {
wd1770_.set_control_register(*value);
if constexpr (!is_read(operation)) {
wd1770_.set_control_register(value);
} else {
value = 0xff;
}
break;
default:
if(is_read(operation)) {
*value = wd1770_.read(address);
if constexpr (is_read(operation)) {
value = wd1770_.read(address);
} else {
wd1770_.write(address, *value);
wd1770_.write(address, value);
}
break;
}
}
else {
} else if(has_beebsid && address >= 0xfc20 && address < 0xfc40) {
if constexpr (is_read(operation)) {
value = audio_.template get<MOS::SID::SID>().read(+address);
} else {
audio_.template get<MOS::SID::SID>().write(+address, value);
}
} else {
Logger::error()
.append("Unhandled IO %s at %04x", is_read(operation) ? "read" : "write", address)
.append_if(!is_read(operation), ": %02x", *value);
.append_if(!is_read(operation), ": %02x", value);
if constexpr (is_read(operation)) {
value = 0xff;
}
}
return duration;
}
//
// ROM or RAM access.
//
if(is_read(operation)) {
if constexpr (is_read(operation)) {
// TODO: probably don't do this with this condition? See how it compiles. If it's a CMOV somehow, no problem.
if((address >> 14) == 2 && !sideways_read_mask_) {
*value = 0xff;
value = 0xff;
} else {
*value = memory_[address >> 14][address];
value = memory_[address >> 14][address];
}
} else {
if(memory_write_masks_[address >> 14]) {
memory_[address >> 14][address] = *value;
memory_[address >> 14][address] = value;
}
}
@@ -926,6 +1098,11 @@ private:
return crtc_bus_handler_.get_scaled_scan_status();
}
// MARK: - SystemVIADelegate.
void strobe_lightpen() override {
crtc_.trigger_light_pen();
}
// MARK: - KeyboardMachine.
BBCMicro::KeyboardMapper mapper_;
KeyboardMapper *get_keyboard_mapper() override {
@@ -933,20 +1110,48 @@ private:
}
void set_key_state(const uint16_t key, const bool is_pressed) override {
if(key == uint16_t(BBCMicro::Key::Break)) {
m6502_.set_reset_line(is_pressed);
} else {
system_via_port_handler_.set_key(uint8_t(key), is_pressed);
switch(Key(key)) {
case Key::SwitchOffCaps:
// Store current caps lock state for a potential restore; press caps lock
// now if there's a need to exit caps lock mode.
was_caps_ = system_via_port_handler_.caps_lock();
if(was_caps_) {
system_via_port_handler_.set_key(uint8_t(Key::CapsLock), true);
}
break;
case Key::RestoreCaps:
// Press caps lock again if the machine was originally in the caps lock state.
// If so then SwitchOffCaps switched it off.
if(was_caps_) {
system_via_port_handler_.set_key(uint8_t(Key::CapsLock), true);
}
break;
case Key::Break:
set_reset(is_pressed);
break;
default:
system_via_port_handler_.set_key(uint8_t(key), is_pressed);
break;
}
}
bool was_caps_ = false;
void clear_all_keys() final {
m6502_.set_reset_line(false);
set_reset(false);
system_via_port_handler_.clear_all_keys();
}
void set_reset(const bool reset) {
m6502_.template set<CPU::MOS6502Mk2::Line::Reset>(reset);
if constexpr (requires {tube_.ula;}) {
tube_.ula.set_reset(reset);
}
}
HalfCycles get_typer_delay(const std::string &text) const final {
if(!m6502_.get_is_resetting()) {
if(!m6502_.is_resetting()) {
return Cycles(0);
}
@@ -966,7 +1171,7 @@ private:
Utility::TypeRecipient<CharacterMapper>::add_typer(string);
}
bool can_type(char c) const final {
bool can_type(const char c) const final {
return Utility::TypeRecipient<CharacterMapper>::can_type(c);
}
@@ -976,7 +1181,10 @@ private:
}
void flush_output(const int outputs) final {
if(outputs & Output::Audio) {
// TODO: I think there's an infrastructural bug here on macOS; if the audio output has stalled out,
// the outer wrapper won't ask for an audio flush, which means the queue will never try to start,
// and the audio queue will just fill indefinitely. Could this be the mythical 'leak'?
if(outputs & Output::Audio || audio_.queue_size() > 200) {
audio_.flush();
}
}
@@ -988,7 +1196,7 @@ private:
// MARK: - uPD7002::Delegate.
void did_change_interrupt_status(NEC::uPD7002 &) override {
system_via_.set_control_line_input<MOS::MOS6522::Port::B, MOS::MOS6522::Line::One>(adc_.interrupt());
system_via_.template set_control_line_input<MOS::MOS6522::Port::B, MOS::MOS6522::Line::One>(adc_.interrupt());
}
// MARK: - MediaTarget.
@@ -1029,26 +1237,46 @@ private:
rom_write_masks_[slot] = is_writeable;
rom_inserted_[slot] = true;
assert(source.size() == roms_[slot].size());
std::copy(source.begin(), source.end(), roms_[slot].begin());
assert(roms_[slot].size() % source.size() == 0);
auto begin = roms_[slot].begin();
while(begin != roms_[slot].end()) {
std::copy(source.begin(), source.end(), begin);
std::advance(begin, source.size());
}
}
// MARK: - Components.
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_;
struct M6502Traits {
static constexpr auto uses_ready_line = false;
static constexpr auto pause_precision = CPU::MOS6502Mk2::PausePrecision::BetweenInstructions;
using BusHandlerT = ConcreteMachine;
};
CPU::MOS6502Mk2::Processor<CPU::MOS6502Mk2::Model::M6502, M6502Traits> m6502_;
UserVIAPortHandler user_via_port_handler_;
SystemVIAPortHandler system_via_port_handler_;
SystemVIAPortHandler<Audio<has_beebsid>> system_via_port_handler_;
UserVIA user_via_;
SystemVIA system_via_;
MOS::MOS6522::MOS6522<SystemVIAPortHandler<Audio<has_beebsid>>> system_via_;
void update_irq_line() {
m6502_.set_irq_line(
const bool tube_irq =
[&] {
if constexpr (requires {tube_.ula;}) {
return tube_.ula.has_host_irq();
} else {
return false;
}
} ();
m6502_.template set<CPU::MOS6502Mk2::Line::IRQ>(
user_via_.get_interrupt_line() ||
system_via_.get_interrupt_line()
system_via_.get_interrupt_line() ||
tube_irq
);
}
Audio audio_;
Audio<has_beebsid> audio_;
CRTCBusHandler crtc_bus_handler_;
CRTC crtc_;
@@ -1061,7 +1289,9 @@ private:
// MARK: - WD1770.
Electron::Plus3 wd1770_;
void wd1770_did_change_output(WD::WD1770 &) override {
m6502_.set_nmi_line(wd1770_.get_interrupt_request_line() || wd1770_.get_data_request_line());
m6502_.template set<CPU::MOS6502Mk2::Line::NMI>(
wd1770_.get_interrupt_request_line() || wd1770_.get_data_request_line()
);
}
// MARK: - Joysticks
@@ -1069,21 +1299,65 @@ private:
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
return joysticks_;
}
// MARK: - Configuration options.
std::unique_ptr<Reflection::Struct> get_options() const final {
auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly);
options->dynamic_crop = crtc_bus_handler_.dynamic_framing();
return options;
}
void set_options(const std::unique_ptr<Reflection::Struct> &str) final {
const auto options = dynamic_cast<Options *>(str.get());
crtc_bus_handler_.set_dynamic_framing(options->dynamic_crop);
}
// MARK: - Tube.
Tube<ConcreteMachine, tube_processor> tube_;
public:
void set_host_tube_irq(bool) { update_irq_line(); }
void set_parasite_tube_irq(const bool active) { tube_.processor.set_irq(active); }
void set_parasite_tube_nmi(const bool active) { tube_.processor.set_nmi(active); }
void set_parasite_reset(const bool active) { tube_.processor.set_reset(active); }
};
}
using namespace BBCMicro;
namespace {
using Target = Analyser::Static::Acorn::BBCMicroTarget;
template <Target::TubeProcessor processor, bool has_1770>
std::unique_ptr<Machine> machine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) {
if(target.has_beebsid) {
return std::make_unique<BBCMicro::ConcreteMachine<processor, has_1770, true>>(target, rom_fetcher);
} else {
return std::make_unique<BBCMicro::ConcreteMachine<processor, has_1770, false>>(target, rom_fetcher);
}
}
template <Target::TubeProcessor processor>
std::unique_ptr<Machine> machine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) {
if(target.has_1770dfs || target.has_adfs) {
return machine<processor, true>(target, rom_fetcher);
} else {
return machine<processor, false>(target, rom_fetcher);
}
}
}
std::unique_ptr<Machine> Machine::BBCMicro(
const Analyser::Static::Target *target,
const ROMMachine::ROMFetcher &rom_fetcher
) {
using Target = Analyser::Static::Acorn::BBCMicroTarget;
const Target *const acorn_target = dynamic_cast<const Target *>(target);
if(acorn_target->has_1770dfs || acorn_target->has_adfs) {
return std::make_unique<BBCMicro::ConcreteMachine<true>>(*acorn_target, rom_fetcher);
} else {
return std::make_unique<BBCMicro::ConcreteMachine<false>>(*acorn_target, rom_fetcher);
switch(acorn_target->tube_processor) {
case TubeProcessor::None: return machine<TubeProcessor::None>(*acorn_target, rom_fetcher);
case TubeProcessor::WDC65C02: return machine<TubeProcessor::WDC65C02>(*acorn_target, rom_fetcher);
case TubeProcessor::Z80: return machine<TubeProcessor::Z80>(*acorn_target, rom_fetcher);
default: return nullptr;
}
}
+21
View File
@@ -9,6 +9,8 @@
#pragma once
#include "Analyser/Static/StaticAnalyser.hpp"
#include "Configurable/Configurable.hpp"
#include "Configurable/StandardOptions.hpp"
#include "Machines/ROMMachine.hpp"
#include <memory>
@@ -22,6 +24,25 @@ struct Machine {
const Analyser::Static::Target *target,
const ROMMachine::ROMFetcher &rom_fetcher
);
class Options:
public Reflection::StructImpl<Options>,
public Configurable::Options::DynamicCrop<Options>
{
public:
Options(const Configurable::OptionsType type) :
Configurable::Options::DynamicCrop<Options>(type == Configurable::OptionsType::UserFriendly) {}
private:
friend Configurable::Options::DynamicCrop<Options>;
Options() : Options( Configurable::OptionsType::UserFriendly) {}
friend Reflection::StructImpl<Options>;
void declare_fields() {
declare_dynamic_crop_option();
}
};
};
}
+44 -35
View File
@@ -43,23 +43,29 @@ enum class Key: uint16_t {
ForwardSlash = 0x68,
Bit1 = 0x08,
Right = 0x79, Left = 0x19, Down = 0x29, Up = 0x39,
Return = 0x49, Delete = 0x59, Copy = 0x69, Bit0 = 0x09,
Right = 0x79, Left = 0x19, Down = 0x29, Up = 0x39,
Return = 0x49, Delete = 0x59, Copy = 0x69, Bit0 = 0x09,
//
// Break; a key, but not on the keyboard matrix.
//
Break = 0xfffc,
Break = 0xfe00,
//
// Fictional keys to aid key entry.
//
SwitchOffCaps = 0xfe01,
RestoreCaps = 0xfe02,
//
// Master only keys.
//
Keypad4 = 0x7a, Keypad6 = 0x1a, Keypad8 = 0x2a, KeypadPlus = 0x3a,
KeypadDivide = 0x4a, KeypadHash = 0x5a, Keypad0 = 0x6a,
Keypad5 = 0x7b, Keypad7 = 0x1b, Keypad9 = 0x2b, KeypadMinus = 0x3b,
KeypadDeleted = 0x4b, KeypadMultiply = 0x5b, Keypad1 = 0x6b,
Keypad2 = 0x7c, F11 = 0x1c, PauseBreak = 0x2c, KeypadReturn = 0x3c,
KeypadDot = 0x4c, KeypadComma = 0x5c, Keypad3 = 0x6c,
Keypad4 = 0x7a, Keypad6 = 0x1a, Keypad8 = 0x2a, KeypadPlus = 0x3a,
KeypadDivide = 0x4a, KeypadHash = 0x5a, Keypad0 = 0x6a,
Keypad5 = 0x7b, Keypad7 = 0x1b, Keypad9 = 0x2b, KeypadMinus = 0x3b,
KeypadDeleted = 0x4b, KeypadMultiply = 0x5b, Keypad1 = 0x6b,
Keypad2 = 0x7c, F11 = 0x1c, PauseBreak = 0x2c, KeypadReturn = 0x3c,
KeypadDot = 0x4c, KeypadComma = 0x5c, Keypad3 = 0x6c,
Alt = 0x02,
LeftShift = 0x03,
@@ -162,33 +168,36 @@ private:
}
static inline const std::unordered_map<char, Sequence> sequences = {
{'Q', keys({Key::Q}) }, {'W', keys({Key::W}) },
{'E', keys({Key::E}) }, {'R', keys({Key::R}) },
{'T', keys({Key::T}) }, {'Y', keys({Key::Y}) },
{'U', keys({Key::U}) }, {'I', keys({Key::I}) },
{'O', keys({Key::O}) }, {'P', keys({Key::P}) },
{'A', keys({Key::A}) }, {'S', keys({Key::S}) },
{'D', keys({Key::D}) }, {'F', keys({Key::F}) },
{'G', keys({Key::G}) }, {'H', keys({Key::H}) },
{'J', keys({Key::J}) }, {'K', keys({Key::K}) },
{'L', keys({Key::L}) }, {'Z', keys({Key::Z}) },
{'X', keys({Key::X}) }, {'C', keys({Key::C}) },
{'V', keys({Key::V}) }, {'B', keys({Key::B}) },
{'N', keys({Key::N}) }, {'M', keys({Key::M}) },
{Utility::Typer::BeginString, keys({Key::SwitchOffCaps})},
{Utility::Typer::EndString, keys({Key::RestoreCaps})},
{'q', keys({Key::Shift, Key::Q}) }, {'w', keys({Key::Shift, Key::W}) },
{'e', keys({Key::Shift, Key::E}) }, {'r', keys({Key::Shift, Key::R}) },
{'t', keys({Key::Shift, Key::T}) }, {'y', keys({Key::Shift, Key::Y}) },
{'u', keys({Key::Shift, Key::U}) }, {'i', keys({Key::Shift, Key::I}) },
{'o', keys({Key::Shift, Key::O}) }, {'p', keys({Key::Shift, Key::P}) },
{'a', keys({Key::Shift, Key::A}) }, {'s', keys({Key::Shift, Key::S}) },
{'d', keys({Key::Shift, Key::D}) }, {'f', keys({Key::Shift, Key::F}) },
{'g', keys({Key::Shift, Key::G}) }, {'h', keys({Key::Shift, Key::H}) },
{'j', keys({Key::Shift, Key::J}) }, {'k', keys({Key::Shift, Key::K}) },
{'l', keys({Key::Shift, Key::L}) }, {'z', keys({Key::Shift, Key::Z}) },
{'x', keys({Key::Shift, Key::X}) }, {'c', keys({Key::Shift, Key::C}) },
{'v', keys({Key::Shift, Key::V}) }, {'b', keys({Key::Shift, Key::B}) },
{'n', keys({Key::Shift, Key::N}) }, {'m', keys({Key::Shift, Key::M}) },
{'q', keys({Key::Q}) }, {'w', keys({Key::W}) },
{'e', keys({Key::E}) }, {'r', keys({Key::R}) },
{'t', keys({Key::T}) }, {'y', keys({Key::Y}) },
{'u', keys({Key::U}) }, {'i', keys({Key::I}) },
{'o', keys({Key::O}) }, {'p', keys({Key::P}) },
{'a', keys({Key::A}) }, {'s', keys({Key::S}) },
{'d', keys({Key::D}) }, {'f', keys({Key::F}) },
{'g', keys({Key::G}) }, {'h', keys({Key::H}) },
{'j', keys({Key::J}) }, {'k', keys({Key::K}) },
{'l', keys({Key::L}) }, {'z', keys({Key::Z}) },
{'x', keys({Key::X}) }, {'c', keys({Key::C}) },
{'v', keys({Key::V}) }, {'b', keys({Key::B}) },
{'n', keys({Key::N}) }, {'m', keys({Key::M}) },
{'Q', keys({Key::Shift, Key::Q}) }, {'W', keys({Key::Shift, Key::W}) },
{'E', keys({Key::Shift, Key::E}) }, {'R', keys({Key::Shift, Key::R}) },
{'T', keys({Key::Shift, Key::T}) }, {'Y', keys({Key::Shift, Key::Y}) },
{'U', keys({Key::Shift, Key::U}) }, {'I', keys({Key::Shift, Key::I}) },
{'O', keys({Key::Shift, Key::O}) }, {'P', keys({Key::Shift, Key::P}) },
{'A', keys({Key::Shift, Key::A}) }, {'S', keys({Key::Shift, Key::S}) },
{'D', keys({Key::Shift, Key::D}) }, {'F', keys({Key::Shift, Key::F}) },
{'G', keys({Key::Shift, Key::G}) }, {'H', keys({Key::Shift, Key::H}) },
{'J', keys({Key::Shift, Key::J}) }, {'K', keys({Key::Shift, Key::K}) },
{'L', keys({Key::Shift, Key::L}) }, {'Z', keys({Key::Shift, Key::Z}) },
{'X', keys({Key::Shift, Key::X}) }, {'C', keys({Key::Shift, Key::C}) },
{'V', keys({Key::Shift, Key::V}) }, {'B', keys({Key::Shift, Key::B}) },
{'N', keys({Key::Shift, Key::N}) }, {'M', keys({Key::Shift, Key::M}) },
{'0', keys({Key::k0}) }, {'1', keys({Key::k1}) },
{'2', keys({Key::k2}) }, {'3', keys({Key::k3}) },
+16 -29
View File
@@ -15,7 +15,6 @@
#include "ClockReceiver/ClockReceiver.hpp"
#include "ClockReceiver/ForceInline.hpp"
#include "Configurable/StandardOptions.hpp"
#include "Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#include "Processors/6502/6502.hpp"
#include "Storage/MassStorage/SCSI/SCSI.hpp"
@@ -27,6 +26,8 @@
#include "ClockReceiver/JustInTime.hpp"
#include "Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#include "Interrupts.hpp"
#include "Keyboard.hpp"
#include "Plus3.hpp"
@@ -58,8 +59,11 @@ public:
hard_drive_(scsi_bus_, 0),
scsi_device_(scsi_bus_.add_device()),
video_(ram_),
sound_generator_(audio_queue_),
speaker_(sound_generator_) {
audio_(
2000000.0 / SoundGenerator::clock_rate_divider,
SoundGenerator::clock_rate_divider,
6000.0f
) {
memset(key_states_, 0, sizeof(key_states_));
for(int c = 0; c < 16; c++)
memset(roms_[c], 0xff, 16384);
@@ -67,9 +71,6 @@ public:
tape_.set_delegate(this);
set_clock_rate(2000000);
speaker_.set_input_rate(2000000 / SoundGenerator::clock_rate_divider);
speaker_.set_high_frequency_cutoff(6000);
::ROM::Request request = ::ROM::Request(::ROM::Name::AcornBASICII) && ::ROM::Request(::ROM::Name::AcornElectronMOS100);
if(target.has_pres_adfs) {
request = request && ::ROM::Request(::ROM::Name::PRESADFSSlot1) && ::ROM::Request(::ROM::Name::PRESADFSSlot2);
@@ -143,7 +144,7 @@ public:
}
~ConcreteMachine() {
audio_queue_.flush();
audio_.stop();
}
void set_key_state(uint16_t key, bool isPressed) final {
@@ -234,8 +235,7 @@ public:
const auto [cycles, video_interrupts] = run_for_access(address);
signal_interrupt(video_interrupts);
cycles_since_audio_update_ += cycles;
if(cycles_since_audio_update_ > Cycles(16384)) update_audio();
audio_ += cycles;
tape_.run_for(cycles);
if(typer_) typer_->run_for(cycles);
@@ -278,8 +278,7 @@ public:
// update speaker mode
bool new_speaker_is_enabled = (*value & 6) == 2;
if(new_speaker_is_enabled != speaker_is_enabled_) {
update_audio();
sound_generator_.set_is_enabled(new_speaker_is_enabled);
audio_->set_is_enabled(new_speaker_is_enabled);
speaker_is_enabled_ = new_speaker_is_enabled;
}
@@ -340,8 +339,7 @@ public:
break;
case 0xfe06:
if(!is_read(operation)) {
update_audio();
sound_generator_.set_divider(*value);
audio_->set_divider(*value);
tape_.set_counter(*value);
}
break;
@@ -510,8 +508,7 @@ public:
void flush_output(int outputs) final {
if(outputs & Output::Audio) {
update_audio();
audio_queue_.perform();
audio_.perform();
}
}
@@ -532,7 +529,7 @@ public:
}
Outputs::Speaker::Speaker *get_speaker() final {
return &speaker_;
return &audio_.speaker();
}
void run_for(const Cycles cycles) final {
@@ -599,7 +596,7 @@ public:
std::unique_ptr<Reflection::Struct> get_options() const final {
auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly);
options->output = get_video_signal_configurable();
options->quickload = allow_fast_tape_hack_;
options->quick_load = allow_fast_tape_hack_;
return options;
}
@@ -607,7 +604,7 @@ public:
const auto options = dynamic_cast<Options *>(str.get());
set_video_signal_configurable(options->output);
allow_fast_tape_hack_ = options->quickload;
allow_fast_tape_hack_ = options->quick_load;
set_use_fast_tape_hack();
}
@@ -682,10 +679,6 @@ private:
}
// MARK: - Work deferral updates.
inline void update_audio() {
speaker_.run_for(audio_queue_, cycles_since_audio_update_.divide(Cycles(SoundGenerator::clock_rate_divider)));
}
inline void signal_interrupt(uint8_t interrupt) {
if(!interrupt) {
return;
@@ -732,9 +725,6 @@ private:
uint8_t key_states_[14];
Electron::KeyboardMapper keyboard_mapper_;
// Counters related to simultaneous subsystems
Cycles cycles_since_audio_update_ = 0;
// Tape
Tape tape_;
bool use_fast_tape_hack_ = false;
@@ -770,10 +760,7 @@ private:
// Outputs
VideoOutput video_;
Concurrency::AsyncTaskQueue<false> audio_queue_;
SoundGenerator sound_generator_;
Outputs::Speaker::PullLowpass<SoundGenerator> speaker_;
Outputs::Speaker::PullLowpassSpeakerQueue<Cycles, SoundGenerator> audio_;
bool speaker_is_enabled_ = false;
+7 -7
View File
@@ -35,22 +35,22 @@ struct Machine {
/// Defines the runtime options available for an Electron.
class Options:
public Reflection::StructImpl<Options>,
public Configurable::DisplayOption<Options>,
public Configurable::QuickloadOption<Options>
public Configurable::Options::Display<Options>,
public Configurable::Options::QuickLoad<Options>
{
friend Configurable::DisplayOption<Options>;
friend Configurable::QuickloadOption<Options>;
friend Configurable::Options::Display<Options>;
friend Configurable::Options::QuickLoad<Options>;
public:
Options(const Configurable::OptionsType type) :
Configurable::DisplayOption<Options>(
Configurable::Options::Display<Options>(
type == Configurable::OptionsType::UserFriendly ?
Configurable::Display::RGB : Configurable::Display::CompositeColour
),
Configurable::QuickloadOption<Options>(
Configurable::Options::QuickLoad<Options>(
type == Configurable::OptionsType::UserFriendly) {}
private:
Options() : Options(Configurable::OptionsType::UserFriendly) {}
Options() : Options( Configurable::OptionsType::UserFriendly) {}
friend Reflection::StructImpl<Options>;
void declare_fields() {
+2 -3
View File
@@ -30,9 +30,8 @@ enum Key: uint16_t {
KeyShift = 0x00d0 | 0x08, KeyControl = 0x00d0 | 0x04, KeyFunc = 0x00d0 | 0x02, KeyEscape = 0x00d0 | 0x01,
// Virtual keys.
KeyF1 = 0xfff0, KeyF2, KeyF3, KeyF4, KeyF5, KeyF6, KeyF7, KeyF8, KeyF9, KeyF0,
KeyBreak = 0xfffc,
KeyF1 = 0xfe00, KeyF2, KeyF3, KeyF4, KeyF5, KeyF6, KeyF7, KeyF8, KeyF9, KeyF0,
KeyBreak,
};
constexpr bool is_modifier(const Key key) {
+3 -3
View File
@@ -12,7 +12,7 @@
using namespace Electron;
SoundGenerator::SoundGenerator(Concurrency::AsyncTaskQueue<false> &audio_queue) :
SoundGenerator::SoundGenerator(Outputs::Speaker::TaskQueue &audio_queue) :
audio_queue_(audio_queue) {}
void SoundGenerator::set_sample_volume_range(std::int16_t range) {
@@ -40,13 +40,13 @@ template void SoundGenerator::apply_samples<Outputs::Speaker::Action::Mix>(std::
template void SoundGenerator::apply_samples<Outputs::Speaker::Action::Store>(std::size_t, Outputs::Speaker::MonoSample *);
template void SoundGenerator::apply_samples<Outputs::Speaker::Action::Ignore>(std::size_t, Outputs::Speaker::MonoSample *);
void SoundGenerator::set_divider(uint8_t divider) {
void SoundGenerator::set_divider(const uint8_t divider) {
audio_queue_.enqueue([this, divider]() {
divider_ = divider * 32 / clock_rate_divider;
});
}
void SoundGenerator::set_is_enabled(bool is_enabled) {
void SoundGenerator::set_is_enabled(const bool is_enabled) {
audio_queue_.enqueue([this, is_enabled]() {
is_enabled_ = is_enabled;
counter_ = 0;
+3 -3
View File
@@ -9,13 +9,13 @@
#pragma once
#include "Outputs/Speaker/Implementation/BufferSource.hpp"
#include "Concurrency/AsyncTaskQueue.hpp"
#include "Outputs/Speaker/SpeakerQueue.hpp"
namespace Electron {
class SoundGenerator: public ::Outputs::Speaker::BufferSource<SoundGenerator, false> {
public:
SoundGenerator(Concurrency::AsyncTaskQueue<false> &);
SoundGenerator(Outputs::Speaker::TaskQueue &);
void set_divider(uint8_t);
void set_is_enabled(bool);
@@ -28,7 +28,7 @@ public:
void set_sample_volume_range(std::int16_t range);
private:
Concurrency::AsyncTaskQueue<false> &audio_queue_;
Outputs::Speaker::TaskQueue &audio_queue_;
unsigned int counter_ = 0;
unsigned int divider_ = 0;
bool is_enabled_ = false;
+5 -7
View File
@@ -20,13 +20,11 @@ VideoOutput::VideoOutput(const uint8_t *memory) :
1,
Outputs::Display::Type::PAL50,
Outputs::Display::InputDataType::Red1Green1Blue1) {
crt_.set_visible_area(crt_.get_rect_for_area(
312 - vsync_end,
256,
h_total - hsync_start,
80 * 8,
4.0f / 3.0f
));
// Default construction values leave this out of text mode, and text
// mode uses a subregion of pixel modes.
crt_.set_fixed_framing([&] {
run_for(Cycles(10'000));
});
}
void VideoOutput::set_scan_target(Outputs::Display::ScanTarget *const scan_target) {
+65
View File
@@ -0,0 +1,65 @@
//
// Header.hpp
// Clock Signal
//
// Created by Thomas Harte on 30/10/2025.
// Copyright © 2025 Thomas Harte. All rights reserved.
//
#pragma once
#include <array>
#include <cstdint>
namespace Acorn::Tube {
template <size_t length, typename ULAT>
struct FIFO {
FIFO(ULAT &ula, const uint8_t mask = 0x00) : ula_(ula), mask_(mask) {}
/// @returns b7 set exactly if this FIFO is not empty.
uint8_t data_available() const {
return read_ != write_ ? 0x80 : 0x00;
}
/// @returns b6 set exactly if this FIFO is not full.
uint8_t not_full() const {
return size_t(write_ - read_) < length ? 0x40 : 0x00;
}
/// Adds a value to the FIFO, notifying the ULA if this would cause the FIFO to
/// transition from empty to not-empty. The ULA will be supplied with this FIFO's
/// mask as specified at construction.
void write(const uint8_t value) {
if(write_ - read_ == length) return;
if(write_ == read_) {
ula_.fifo_has_data(mask_);
}
buffer_[(write_++) % length] = value;
}
/// Removes a value from the FIFO.
uint8_t read() {
const uint8_t result = buffer_[read_ % length];
if(write_ != read_) ++read_;
if(write_ == read_) {
ula_.fifo_is_empty(mask_);
}
return result;
}
/// Empties the FIFO.
void reset() {
read_ = write_ = 0;
ula_.fifo_is_empty(mask_);
}
private:
ULAT &ula_;
uint8_t mask_;
std::array<uint8_t, length> buffer_;
uint32_t read_ = 0;
uint32_t write_ = 0;
};
}
+81
View File
@@ -0,0 +1,81 @@
//
// Tube6502.hpp
// Clock Signal
//
// Created by Thomas Harte on 03/11/2025.
// Copyright © 2025 Thomas Harte. All rights reserved.
//
#pragma once
#include "TubeProcessor.hpp"
#include "Processors/6502Mk2/6502Mk2.hpp"
#include "Machines/Utility/ROMCatalogue.hpp"
#include <algorithm>
namespace Acorn::Tube {
template <typename ULAT>
class Processor<ULAT, TubeProcessor::WDC65C02> {
public:
static constexpr auto ROM = ROM::Name::BBCMicro6502Tube110;
void set_rom(std::vector<uint8_t> source) {
source.resize(sizeof(rom_));
std::copy(source.begin(), source.end(), rom_);
}
Processor(ULAT &ula) : m6502_(*this), ula_(ula) {}
// By convention, these are cycles relative to the host's 2Mhz bus.
// Multiply by 3/2 to turn that into the tube 6502's usual 3Mhz bus.
void run_for(const Cycles cycles) {
cycles_modulo_ += cycles * 3;
m6502_.run_for(cycles_modulo_.divide(Cycles(2)));
}
template <CPU::MOS6502Mk2::BusOperation operation, typename AddressT>
Cycles perform(const AddressT address, CPU::MOS6502Mk2::data_t<operation> value) {
if(address >= 0xfef8 && address < 0xff00) {
rom_visible_ = false;
if constexpr (is_read(operation)) {
value = ula_.parasite_read(address);
} else {
ula_.parasite_write(address, value);
}
} else {
if constexpr (is_read(operation)) {
constexpr uint16_t RomStart = sizeof(ram_) - sizeof(rom_);
value = rom_visible_ && address >= RomStart ? rom_[address - RomStart] : ram_[address];
} else {
ram_[address] = value;
}
}
return Cycles(1);
}
void set_irq(const bool active) { m6502_.template set<CPU::MOS6502Mk2::Line::IRQ>(active); }
void set_nmi(const bool active) { m6502_.template set<CPU::MOS6502Mk2::Line::NMI>(active); }
void set_reset(const bool reset) {
m6502_.template set<CPU::MOS6502Mk2::Line::Reset>(reset);
rom_visible_ |= reset;
}
private:
uint8_t rom_[2048];
uint8_t ram_[65536];
Cycles cycles_modulo_;
struct M6502Traits {
static constexpr auto uses_ready_line = false;
static constexpr auto pause_precision = CPU::MOS6502Mk2::PausePrecision::AnyCycle;
using BusHandlerT = Processor;
};
CPU::MOS6502Mk2::Processor<CPU::MOS6502Mk2::Model::WDC65C02, M6502Traits> m6502_;
bool rom_visible_ = true;
ULAT &ula_;
};
};
+18
View File
@@ -0,0 +1,18 @@
//
// TubeProcessor.hpp
// Clock Signal
//
// Created by Thomas Harte on 07/11/2025.
// Copyright © 2025 Thomas Harte. All rights reserved.
//
#pragma once
#include "Analyser/Static/Acorn/Target.hpp"
namespace Acorn::Tube {
using TubeProcessor = Analyser::Static::Acorn::BBCMicroTarget::TubeProcessor;
template <typename ULAT, TubeProcessor> class Processor;
}
+97
View File
@@ -0,0 +1,97 @@
//
// TubeZ80.hpp
// Clock Signal
//
// Created by Thomas Harte on 04/11/2025.
// Copyright © 2025 Thomas Harte. All rights reserved.
//
#pragma once
#include "TubeProcessor.hpp"
#include "Processors/Z80/Z80.hpp"
#include "Machines/Utility/ROMCatalogue.hpp"
#include <algorithm>
namespace Acorn::Tube {
template <typename ULAT>
class Processor<ULAT, TubeProcessor::Z80>: public CPU::Z80::BusHandler {
public:
static constexpr auto ROM = ROM::Name::BBCMicroZ80Tube122;
void set_rom(std::vector<uint8_t> rom) {
rom.resize(sizeof(rom_));
std::copy(rom.begin(), rom.end(), std::begin(rom_));
}
Processor(ULAT &ula) : z80_(*this), ula_(ula) {}
void run_for(const Cycles cycles) {
// Map from 2Mhz to 6Mhz.
z80_.run_for(cycles * 3);
}
void set_irq(const bool active) { z80_.set_interrupt_line(active); }
void set_nmi(const bool active) { z80_.set_non_maskable_interrupt_line(active); }
void set_reset(const bool reset) {
z80_.set_reset_line(reset);
rom_visible_ |= reset;
}
HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
if(!cycle.is_terminal()) {
return HalfCycles(0);
}
const uint16_t address = *cycle.address;
switch(cycle.operation) {
case CPU::Z80::PartialMachineCycle::ReadOpcode:
if(address == 0x66) {
rom_visible_ = true;
}
rom_visible_ &= address < 0x8000;
[[fallthrough]];
case CPU::Z80::PartialMachineCycle::Read:
if(rom_visible_ && address <= sizeof(rom_)) {
*cycle.value = rom_[address];
return HalfCycles(2);
} else {
*cycle.value = ram_[address];
}
break;
case CPU::Z80::PartialMachineCycle::Write:
ram_[address] = *cycle.value;
break;
case CPU::Z80::PartialMachineCycle::Interrupt:
*cycle.value = 0xfe;
break;
case CPU::Z80::PartialMachineCycle::Input:
*cycle.value = ula_.parasite_read(address);
break;
case CPU::Z80::PartialMachineCycle::Output:
ula_.parasite_write(address, *cycle.value);
break;
default: break;
}
return HalfCycles(0);
}
private:
CPU::Z80::Processor<Processor, false, false> z80_;
bool rom_visible_ = true;
uint8_t rom_[4096];
uint8_t ram_[65536];
ULAT &ula_;
};
}
+194
View File
@@ -0,0 +1,194 @@
//
// ULA.hpp
// Clock Signal
//
// Created by Thomas Harte on 03/11/2025.
// Copyright © 2025 Thomas Harte. All rights reserved.
//
#pragma once
#include "FIFO.hpp"
namespace Acorn::Tube {
/*!
The non-FIFO section of the tube ULA.
*/
template <typename HostT>
struct ULA {
ULA(HostT &host) :
host_(host),
to_parasite1_(*this, 0x02),
to_parasite2_(*this),
to_parasite3_(*this, 0x08),
to_parasite4_(*this, 0x04),
to_host1_(*this),
to_host2_(*this),
to_host3_(*this),
to_host4_(*this, 0x01)
{}
/// Call-in for the FIFOs; indicates that a FIFO just went from empty to not-empty,
/// which might cause an interrupt elsewhere depending on the mask and on whether
/// that interrupt is enabled.
void fifo_has_data(const uint8_t mask) {
apply_fifo_mask(mask, 0xff);
}
void fifo_is_empty(const uint8_t mask) {
apply_fifo_mask(0x00, ~mask);
}
bool has_host_irq() const {
return (flags_ & 0x01) && to_host4_.data_available();
}
bool has_parasite_irq() const {
return
((flags_ & 0x02) && to_parasite1_.data_available()) ||
((flags_ & 0x04) && to_parasite4_.data_available());
}
bool has_parasite_nmi() const {
return (flags_ & 0x08) && to_parasite3_.data_available();
}
void parasite_write(const uint16_t address, const uint8_t value) {
switch(address & 7) {
case 1: to_host1_.write(value); break;
case 3: to_host2_.write(value); break;
case 5: to_host3_.write(value); break;
case 7: to_host4_.write(value); break;
default: break;
}
}
uint8_t parasite_read(const uint16_t address) {
switch(address & 7) {
case 0: return to_parasite1_.data_available() | to_host1_.not_full() | status();
case 1: return to_parasite1_.read();
case 2: return to_parasite2_.data_available() | to_host2_.not_full();
case 3: return to_parasite2_.read();
case 4: return to_parasite3_.data_available() | to_host3_.not_full();
case 5: return to_parasite3_.read();
case 6: return to_parasite4_.data_available() | to_host4_.not_full();
case 7: return to_parasite4_.read();
default: __builtin_unreachable();
}
}
void host_write(const uint16_t address, const uint8_t value) {
switch(address & 7) {
case 0: set_status(value); break;
case 1: to_parasite1_.write(value); break;
case 3: to_parasite2_.write(value); break;
case 5: to_parasite3_.write(value); break;
case 7: to_parasite4_.write(value); break;
default: break;
}
}
uint8_t host_read(const uint16_t address) {
switch(address & 7) {
case 0: return to_host1_.data_available() | to_parasite1_.not_full() | status();
case 1: return to_host1_.read();
case 2: return to_host2_.data_available() | to_parasite2_.not_full();
case 3: return to_host2_.read();
case 4: return to_host3_.data_available() | to_parasite3_.not_full();
case 5: return to_host3_.read();
case 6: return to_host4_.data_available() | to_parasite4_.not_full();
case 7: return to_host4_.read();
default: __builtin_unreachable();
}
}
void set_reset(const bool reset) {
if(reset_ == reset) {
return;
}
// This is a software approximtion of holding the reset state for as long
// as it is signalled.
if(!reset) {
flags_ = 0x01;
to_parasite1_.reset();
to_parasite2_.reset();
to_parasite3_.reset();
to_parasite4_.reset();
to_host1_.reset();
to_host2_.reset();
to_host3_.reset();
to_host4_.reset();
}
reset_ = reset;
update_parasite_reset();
}
private:
void signal_changes(const uint8_t changes) {
if(changes & 0x01) {
host_.set_host_tube_irq(interrupt_sources_ & 0x01);
}
if(changes & 0x06) {
host_.set_parasite_tube_irq(interrupt_sources_ & 0x06);
}
if(changes & 0x08) {
host_.set_parasite_tube_nmi(interrupt_sources_ & 0x08);
}
}
uint8_t signalling_fifos() const {
return interrupt_sources_ & flags_;
}
void apply_fifo_mask(const uint8_t or_, const uint8_t and_) {
const auto signalling = signalling_fifos();
interrupt_sources_ = (interrupt_sources_ | or_) & and_;
signal_changes(signalling_fifos() ^ signalling);
}
void update_parasite_reset() {
host_.set_parasite_reset((flags_ & 0x20) || reset_);
}
uint8_t status() const {
return flags_;
}
void set_status(const uint8_t value) {
const auto signalling = signalling_fifos();
const uint8_t bits = value & 0x3f;
if(value & 0x80) {
flags_ |= bits;
} else {
flags_ &= ~bits;
}
signal_changes(signalling_fifos() ^ signalling);
if(value & 0x20) {
update_parasite_reset();
}
// TODO: understand meaning of bits 4 and 6.
}
HostT &host_;
uint8_t flags_ = 0x01;
bool reset_ = false;
uint8_t interrupt_sources_ = 0x00;
FIFO<1, ULA> to_parasite1_;
FIFO<1, ULA> to_parasite2_;
FIFO<2, ULA> to_parasite3_;
FIFO<1, ULA> to_parasite4_;
FIFO<24, ULA> to_host1_;
FIFO<1, ULA> to_host2_;
FIFO<2, ULA> to_host3_;
FIFO<1, ULA> to_host4_;
};
}
+1 -1
View File
@@ -46,7 +46,7 @@ Chipset::Chipset(MemoryMap &map, int input_clock_rate) :
// Very conservatively crop, to roughly the centre 88% of a frame.
// This rectange was specifically calibrated around the default Workbench display.
crt_.set_visible_area(Outputs::Display::Rect(0.05f, 0.055f, 0.88f, 0.88f));
crt_.set_fixed_framing(Outputs::Display::Rect(0.05f, 0.055f, 0.88f, 0.88f));
}
#undef DMA_CONSTRUCT
+23 -13
View File
@@ -127,7 +127,7 @@ public:
}
~AYDeferrer() {
audio_queue_.flush();
audio_queue_.lock_flush();
}
/// Adds @c half_cycles half cycles to the amount of time that has passed.
@@ -175,11 +175,25 @@ public:
interrupt_timer_(interrupt_timer) {
establish_palette_hits();
build_mode_table();
crt_.set_visible_area(Outputs::Display::Rect(0.1072f, 0.1f, 0.842105263157895f, 0.842105263157895f));
crt_.set_brightness(3.0f / 2.0f); // As only the values 0, 1 and 2 will be used in each channel,
// whereas Red2Green2Blue2 defines a range of 0-3.
}
void set_dynamic_framing(const bool enable) {
dynamic_framing_ = enable;
if(enable) {
crt_.set_dynamic_framing(
Outputs::Display::Rect(0.16842f, 0.19909f, 0.71579f, 0.67197f),
0.0f, 0.1f);
} else {
crt_.set_fixed_framing(Outputs::Display::Rect(0.1072f, 0.1f, 0.842105263157895f, 0.842105263157895f));
}
}
bool dynamic_framing() const {
return dynamic_framing_;
}
/*!
The CRTC entry function for the main part of each clock cycle; takes the current
bus state and determines what output to produce based on the current palette and mode.
@@ -377,14 +391,7 @@ private:
void output_border(const int length) {
assert(length >= 0);
// A black border can be output via crt_.output_blank for a minor performance
// win; otherwise paint whatever the border colour really is.
if(border_) {
crt_.output_level<uint8_t>(length * 16, border_);
} else {
crt_.output_blank(length * 16);
}
crt_.output_level<uint8_t>(length * 16, border_);
}
template <typename IntT>
@@ -548,6 +555,7 @@ private:
int cycles_into_hsync_ = 0;
Outputs::CRT::CRT crt_;
bool dynamic_framing_ = false;
uint8_t *pixel_data_ = nullptr, *pixel_pointer_ = nullptr;
const uint8_t *const ram_ = nullptr;
@@ -1196,15 +1204,17 @@ public:
std::unique_ptr<Reflection::Struct> get_options() const final {
auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly);
options->output = get_video_signal_configurable();
options->quickload = allow_fast_tape_hack_;
options->quick_load = allow_fast_tape_hack_;
options->dynamic_crop = crtc_bus_handler_.dynamic_framing();
return options;
}
void set_options(const std::unique_ptr<Reflection::Struct> &str) {
void set_options(const std::unique_ptr<Reflection::Struct> &str) final {
const auto options = dynamic_cast<Options *>(str.get());
set_video_signal_configurable(options->output);
allow_fast_tape_hack_ = options->quickload;
allow_fast_tape_hack_ = options->quick_load;
set_use_fast_tape_hack();
crtc_bus_handler_.set_dynamic_framing(options->dynamic_crop);
}
// MARK: - Joysticks
+13 -8
View File
@@ -8,9 +8,9 @@
#pragma once
#include "Analyser/Static/StaticAnalyser.hpp"
#include "Configurable/Configurable.hpp"
#include "Configurable/StandardOptions.hpp"
#include "Analyser/Static/StaticAnalyser.hpp"
#include "Machines/ROMMachine.hpp"
#include <memory>
@@ -32,28 +32,33 @@ struct Machine {
/// Defines the runtime options available for an Amstrad CPC.
class Options:
public Reflection::StructImpl<Options>,
public Configurable::DisplayOption<Options>,
public Configurable::QuickloadOption<Options>
public Configurable::Options::Display<Options>,
public Configurable::Options::QuickLoad<Options>,
public Configurable::Options::DynamicCrop<Options>
{
public:
Options(const Configurable::OptionsType type) :
Configurable::DisplayOption<Options>(Configurable::Display::RGB),
Configurable::QuickloadOption<Options>(type == Configurable::OptionsType::UserFriendly) {}
Configurable::Options::Display<Options>(Configurable::Display::RGB),
Configurable::Options::QuickLoad<Options>(type == Configurable::OptionsType::UserFriendly),
Configurable::Options::DynamicCrop<Options>(type == Configurable::OptionsType::UserFriendly) {}
private:
friend Configurable::DisplayOption<Options>;
friend Configurable::QuickloadOption<Options>;
friend Configurable::Options::Display<Options>;
friend Configurable::Options::QuickLoad<Options>;
friend Configurable::Options::DynamicCrop<Options>;
Options() : Options(Configurable::OptionsType::UserFriendly) {}
Options() : Options( Configurable::OptionsType::UserFriendly) {}
friend Reflection::StructImpl<Options>;
void declare_fields() {
declare_display_option();
declare_quickload_option();
declare_dynamic_crop_option();
limit_enum(&output, Configurable::Display::RGB, Configurable::Display::CompositeColour, -1);
}
};
// Provided for running the SHAKER test suite.
struct SSMDelegate {
virtual void perform(uint16_t) = 0;
};
+1 -1
View File
@@ -678,7 +678,7 @@ public:
}
~ConcreteMachine() {
audio_queue_.flush();
audio_queue_.lock_flush();
}
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
+5 -5
View File
@@ -24,15 +24,15 @@ struct Machine {
static std::unique_ptr<Machine> AppleII(const Analyser::Static::Target *, const ROMMachine::ROMFetcher &);
/// Defines the runtime options available for an Apple II.
class Options: public Reflection::StructImpl<Options>, public Configurable::DisplayOption<Options> {
friend Configurable::DisplayOption<Options>;
class Options: public Reflection::StructImpl<Options>, public Configurable::Options::Display<Options> {
friend Configurable::Options::Display<Options>;
public:
bool use_square_pixels = false;
Options(Configurable::OptionsType) :
Configurable::DisplayOption<Options>(Configurable::Display::CompositeColour) {}
Options(const Configurable::OptionsType) :
Configurable::Options::Display<Options>(Configurable::Display::CompositeColour) {}
private:
Options() : Options(Configurable::OptionsType::UserFriendly) {}
Options() : Options( Configurable::OptionsType::UserFriendly) {}
friend Reflection::StructImpl<Options>;
void declare_fields() {
+6 -2
View File
@@ -38,9 +38,13 @@ public:
Input(Input::Fire, 2),
}) {}
void did_set_input(const Input &input, float value) final {
if(!input.info.control.index && (input.type == Input::Type::Horizontal || input.type == Input::Type::Vertical))
void did_set_input(const Input &input, const float value) final {
if(
!input.info.control.index &&
(input.type == Input::Type::Horizontal || input.type == Input::Type::Vertical)
) {
axes[(input.type == Input::Type::Horizontal) ? 0 : 1] = 1.0f - value;
}
}
void did_set_input(const Input &input, bool value) final {
+8 -13
View File
@@ -25,20 +25,8 @@ VideoBase::VideoBase(bool is_iie, std::function<void(Cycles)> &&target) :
// crt_.set_immediate_default_phase(0.5f);
}
void VideoBase::set_use_square_pixels(bool use_square_pixels) {
void VideoBase::set_use_square_pixels(const bool use_square_pixels) {
use_square_pixels_ = use_square_pixels;
// HYPER-UGLY HACK. See correlated hack in the Macintosh.
#if defined(__APPLE__) && !defined(IGNORE_APPLE)
crt_.set_visible_area(Outputs::Display::Rect(0.128f, 0.122f, 0.75f, 0.77f));
#else
if(use_square_pixels) {
crt_.set_visible_area(Outputs::Display::Rect(0.128f, 0.09f, 0.75f, 0.77f));
} else {
crt_.set_visible_area(Outputs::Display::Rect(0.128f, 0.12f, 0.75f, 0.77f));
}
#endif
if(use_square_pixels) {
// From what I can make out, many contemporary Apple II monitors were
// calibrated slightly to stretch the Apple II's display slightly wider
@@ -55,6 +43,13 @@ void VideoBase::set_use_square_pixels(bool use_square_pixels) {
crt_.set_aspect_ratio(4.0f / 3.0f);
}
}
void VideoBase::establish_framing() {
crt_.set_fixed_framing([&] {
run_for(Cycles(10'000));
});
}
bool VideoBase::get_use_square_pixels() const {
return use_square_pixels_;
}
+8 -3
View File
@@ -35,8 +35,10 @@ class VideoBase: public VideoSwitches<Cycles> {
public:
VideoBase(bool is_iie, std::function<void(Cycles)> &&target);
void establish_framing();
/// Sets the scan target.
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
void set_scan_target(Outputs::Display::ScanTarget *);
/// Gets the current scan status.
Outputs::Display::ScanStatus get_scaled_scan_status() const;
@@ -119,8 +121,11 @@ template <class BusHandler, bool is_iie> class Video: public VideoBase {
public:
/// Constructs an instance of the video feed; a CRT is also created.
Video(BusHandler &bus_handler) :
VideoBase(is_iie, [this] (Cycles cycles) { advance(cycles); }),
bus_handler_(bus_handler) {}
VideoBase(is_iie, [this] (const Cycles cycles) { advance(cycles); }),
bus_handler_(bus_handler)
{
establish_framing();
}
/*!
Obtains the last value the video read prior to time now+offset, according to the *current*
+1 -1
View File
@@ -210,7 +210,7 @@ public:
}
~ConcreteMachine() {
audio_queue_.flush();
audio_queue_.lock_flush();
}
void run_for(const Cycles cycles) override {
+6 -24
View File
@@ -111,11 +111,6 @@ Video::Video() :
VideoSwitches<Cycles>(true, Cycles(2), [this] (Cycles cycles) { advance(cycles); }),
crt_(CyclesPerLine - 1, 1, Outputs::Display::Type::NTSC60, Outputs::Display::InputDataType::Red4Green4Blue4) {
crt_.set_display_type(Outputs::Display::DisplayType::RGB);
crt_.set_visible_area(Outputs::Display::Rect(0.097f, 0.1f, 0.85f, 0.85f));
// Reduce the initial bounce by cueing up the part of the frame that initial drawing actually
// starts with. More or less.
crt_.output_blank(228*63*2);
// Establish the shift lookup table for NTSC -> RGB output.
for(size_t c = 0; c < sizeof(ntsc_delay_lookup_) / sizeof(*ntsc_delay_lookup_); c++) {
@@ -149,8 +144,11 @@ Outputs::Display::DisplayType Video::get_display_type() const {
return crt_.get_display_type();
}
void Video::set_internal_ram(const uint8_t *ram) {
void Video::set_internal_ram(const uint8_t *const ram) {
ram_ = ram;
// crt_.set_automatic_fixed_framing([&] {
// run_for(Cycles(10'000));
// });
}
void Video::advance(Cycles cycles) {
@@ -410,15 +408,7 @@ void Video::output_row(int row, int start, int end) {
// Output right border as far as currently known.
if(start >= start_of_right_border && start < start_of_sync) {
const int end_of_period = std::min(start_of_sync, end);
if(border_colour_) {
uint16_t *const pixel = reinterpret_cast<uint16_t *>(crt_.begin_data(2, 2));
if(pixel) *pixel = border_colour_;
crt_.output_data((end_of_period - start) * CyclesPerTick, 1);
} else {
crt_.output_blank((end_of_period - start) * CyclesPerTick);
}
crt_.output_level<uint16_t>((end_of_period - start) * CyclesPerTick, border_colour_);
// There's no point updating start here; just fall
// through to the end == FinalColumn test.
}
@@ -426,15 +416,7 @@ void Video::output_row(int row, int start, int end) {
// This line is all border, all the time.
if(start >= start_of_left_border && start < start_of_sync) {
const int end_of_period = std::min(start_of_sync, end);
if(border_colour_) {
uint16_t *const pixel = reinterpret_cast<uint16_t *>(crt_.begin_data(2, 2));
if(pixel) *pixel = border_colour_;
crt_.output_data((end_of_period - start) * CyclesPerTick, 1);
} else {
crt_.output_blank((end_of_period - start) * CyclesPerTick);
}
crt_.output_level<uint16_t>((end_of_period - start) * CyclesPerTick, border_colour_);
start = end_of_period;
if(start == end) return;
}
+3 -3
View File
@@ -172,7 +172,7 @@ public:
}
~ConcreteMachine() {
audio_.queue.flush();
audio_.queue.lock_flush();
}
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
@@ -506,7 +506,7 @@ public:
// MARK: - Configuration options.
std::unique_ptr<Reflection::Struct> get_options() const final {
auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly);
options->quickboot = quickboot_;
options->quick_boot = quickboot_;
return options;
}
@@ -515,7 +515,7 @@ public:
// It should probably be a construction option.
const auto options = dynamic_cast<Options *>(str.get());
quickboot_ = options->quickboot;
quickboot_ = options->quick_boot;
using Model = Analyser::Static::Macintosh::Target::Model;
const bool is_plus_rom = model == Model::Mac512ke || model == Model::MacPlus;
+4 -4
View File
@@ -21,11 +21,11 @@ struct Machine {
/// Creates and returns a Macintosh.
static std::unique_ptr<Machine> Macintosh(const Analyser::Static::Target *, const ROMMachine::ROMFetcher &);
class Options: public Reflection::StructImpl<Options>, public Configurable::QuickbootOption<Options> {
friend Configurable::QuickbootOption<Options>;
class Options: public Reflection::StructImpl<Options>, public Configurable::Options::QuickBoot<Options> {
friend Configurable::Options::QuickBoot<Options>;
public:
Options(Configurable::OptionsType type) :
Configurable::QuickbootOption<Options>(type == Configurable::OptionsType::UserFriendly) {}
Options(const Configurable::OptionsType type) :
Configurable::Options::QuickBoot<Options>(type == Configurable::OptionsType::UserFriendly) {}
private:
Options() : Options(Configurable::OptionsType::UserFriendly) {}
+6 -12
View File
@@ -39,17 +39,6 @@ Video::Video(DeferredAudio &audio, DriveSpeedAccumulator &drive_speed_accumulato
crt_(704, 1, 370, 6, Outputs::Display::InputDataType::Luminance1) {
crt_.set_display_type(Outputs::Display::DisplayType::RGB);
// UGLY HACK. UGLY, UGLY HACK. UGLY!
// The OpenGL scan target fails properly to place visible areas which are not 4:3.
// The [newer] Metal scan target has no such issue. So assume that Apple => Metal,
// and set a visible area to work around the OpenGL issue if required.
// TODO: eliminate UGLY HACK.
#if defined(__APPLE__) && !defined(IGNORE_APPLE)
crt_.set_visible_area(Outputs::Display::Rect(0.08f, 10.0f / 368.0f, 0.82f, 344.0f / 368.0f));
#else
crt_.set_visible_area(Outputs::Display::Rect(0.08f, -0.025f, 0.82f, 0.82f));
#endif
crt_.set_aspect_ratio(1.73f); // The Mac uses a non-standard scanning area.
}
@@ -191,7 +180,12 @@ void Video::set_use_alternate_buffers(bool use_alternate_screen_buffer, bool use
use_alternate_audio_buffer_ = use_alternate_audio_buffer;
}
void Video::set_ram(uint16_t *ram, uint32_t mask) {
void Video::set_ram(const uint16_t *const ram, const uint32_t mask) {
ram_ = ram;
ram_mask_ = mask;
// Now that RAM is assigned, the CRT cna be warmed.
crt_.set_fixed_framing([&] {
run_for(Cycles(10'000));
});
}
+2 -2
View File
@@ -57,7 +57,7 @@ public:
Provides a base address and a mask indicating which parts of the generated video and audio/drive addresses are
actually decoded, accessing *word-sized memory*; e.g. for a 128kb Macintosh this should be (1 << 16) - 1 = 0xffff.
*/
void set_ram(uint16_t *ram, uint32_t mask);
void set_ram(const uint16_t *ram, uint32_t mask);
/*!
@returns @c true if the video is currently outputting a vertical sync, @c false otherwise.
@@ -86,7 +86,7 @@ private:
DriveSpeedAccumulator &drive_speed_accumulator_;
Outputs::CRT::CRT crt_;
uint16_t *ram_ = nullptr;
const uint16_t *ram_ = nullptr;
uint32_t ram_mask_ = 0;
HalfCycles frame_position_;
+7 -5
View File
@@ -13,6 +13,8 @@
#include "Machines/MachineTypes.hpp"
#include "Outputs/CRT/MismatchWarner.hpp"
#include "Analyser/Static/Atari2600/Target.hpp"
#include "Cartridges/Atari8k.hpp"
@@ -157,7 +159,7 @@ public:
// to satisfy CRTMachine::Machine
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
bus_->speaker_.set_input_rate(float(get_clock_rate() / double(CPUTicksPerAudioTick)));
bus_->audio_.speaker().set_input_rate(float(get_clock_rate() / double(CPUTicksPerAudioTick)));
bus_->tia_.set_crt_delegate(&frequency_mismatch_warner_);
bus_->tia_.set_scan_target(scan_target);
}
@@ -167,7 +169,7 @@ public:
}
Outputs::Speaker::Speaker *get_speaker() final {
return &bus_->speaker_;
return &bus_->audio_.speaker();
}
void run_for(const Cycles cycles) final {
@@ -204,11 +206,11 @@ private:
// a confidence counter
Analyser::Dynamic::ConfidenceCounter confidence_counter_;
void set_is_ntsc(bool is_ntsc) {
void set_is_ntsc(const bool is_ntsc) {
bus_->tia_.set_output_mode(is_ntsc ? TIA::OutputMode::NTSC : TIA::OutputMode::PAL);
const double clock_rate = is_ntsc ? NTSC_clock_rate : PAL_clock_rate;
bus_->speaker_.set_input_rate(float(clock_rate) / float(CPUTicksPerAudioTick));
bus_->speaker_.set_high_frequency_cutoff(float(clock_rate) / float(CPUTicksPerAudioTick * 2));
bus_->audio_.speaker().set_input_rate(float(clock_rate) / float(CPUTicksPerAudioTick));
bus_->audio_.speaker().set_high_frequency_cutoff(float(clock_rate) / float(CPUTicksPerAudioTick * 2));
set_clock_rate(clock_rate);
}
};
+7 -18
View File
@@ -21,12 +21,10 @@ namespace Atari2600 {
class Bus {
public:
Bus() :
tia_sound_(audio_queue_),
speaker_(tia_sound_) {}
Bus() : audio_(Cycles(CPUTicksPerAudioTick * 3)) {}
virtual ~Bus() {
audio_queue_.flush();
audio_.stop();
}
virtual void run_for(const Cycles cycles) = 0;
@@ -34,31 +32,22 @@ public:
virtual void set_reset_line(bool state) = 0;
virtual void flush() = 0;
// the RIOT, TIA and speaker
// The RIOT, TIA and speaker.
PIA mos6532_;
TIA tia_;
Outputs::Speaker::PullLowpassSpeakerQueue<Cycles, TIASound> audio_;
Concurrency::AsyncTaskQueue<false> audio_queue_;
TIASound tia_sound_;
Outputs::Speaker::PullLowpass<TIASound> speaker_;
// joystick state
// Joystick state.
uint8_t tia_input_value_[2] = {0xff, 0xff};
protected:
// speaker backlog accumlation counter
Cycles cycles_since_speaker_update_;
inline void update_audio() {
speaker_.run_for(audio_queue_, cycles_since_speaker_update_.divide(Cycles(CPUTicksPerAudioTick * 3)));
}
// video backlog accumulation counter
// Video backlog accumulation counter.
Cycles cycles_since_video_update_;
inline void update_video() {
tia_.run_for(cycles_since_video_update_.flush<Cycles>());
}
// RIOT backlog accumulation counter
// RIOT backlog accumulation counter.
Cycles cycles_since_6532_update_;
inline void update_6532() {
mos6532_.run_for(cycles_since_6532_update_.flush<Cycles>());
+7 -7
View File
@@ -70,10 +70,11 @@ public:
// leap to the end of ready only once ready is signalled because on a 6502 ready doesn't take
// effect until the next read; therefore it isn't safe to assume that signalling ready immediately
// skips to the end of the line.
if(operation == CPU::MOS6502::BusOperation::Ready)
if(operation == CPU::MOS6502::BusOperation::Ready) {
cycles_run_for = tia_.get_cycles_until_horizontal_blank(cycles_since_video_update_);
}
cycles_since_speaker_update_ += Cycles(cycles_run_for);
audio_ += Cycles(cycles_run_for);
cycles_since_video_update_ += Cycles(cycles_run_for);
cycles_since_6532_update_ += Cycles(cycles_run_for / 3);
bus_extender_.advance_cycles(cycles_run_for / 3);
@@ -171,11 +172,11 @@ public:
case 0x2c: update_video(); tia_.clear_collision_flags(); break;
case 0x15:
case 0x16: update_audio(); tia_sound_.set_control(decodedAddress - 0x15, *value); break;
case 0x16: audio_->set_control(decodedAddress - 0x15, *value); break;
case 0x17:
case 0x18: update_audio(); tia_sound_.set_divider(decodedAddress - 0x17, *value); break;
case 0x18: audio_->set_divider(decodedAddress - 0x17, *value); break;
case 0x19:
case 0x1a: update_audio(); tia_sound_.set_volume(decodedAddress - 0x19, *value); break;
case 0x1a: audio_->set_volume(decodedAddress - 0x19, *value); break;
}
}
}
@@ -201,9 +202,8 @@ public:
}
void flush() override {
update_audio();
update_video();
audio_queue_.perform();
audio_.perform();
}
protected:
+1 -1
View File
@@ -10,7 +10,7 @@
using namespace Atari2600;
Atari2600::TIASound::TIASound(Concurrency::AsyncTaskQueue<false> &audio_queue) :
Atari2600::TIASound::TIASound(Outputs::Speaker::TaskQueue &audio_queue) :
audio_queue_(audio_queue)
{}
+3 -3
View File
@@ -9,7 +9,7 @@
#pragma once
#include "Outputs/Speaker/Implementation/BufferSource.hpp"
#include "Concurrency/AsyncTaskQueue.hpp"
#include "Outputs/Speaker/SpeakerQueue.hpp"
namespace Atari2600 {
@@ -19,7 +19,7 @@ constexpr int CPUTicksPerAudioTick = 2;
class TIASound: public Outputs::Speaker::BufferSource<TIASound, false> {
public:
TIASound(Concurrency::AsyncTaskQueue<false> &);
TIASound(Outputs::Speaker::TaskQueue &);
void set_volume(int channel, uint8_t volume);
void set_divider(int channel, uint8_t divider);
@@ -30,7 +30,7 @@ public:
void set_sample_volume_range(std::int16_t);
private:
Concurrency::AsyncTaskQueue<false> &audio_queue_;
Outputs::Speaker::TaskQueue &audio_queue_;
uint8_t volume_[2];
uint8_t divider_[2];
+1 -1
View File
@@ -144,7 +144,7 @@ public:
}
~ConcreteMachine() {
audio_queue_.flush();
audio_queue_.lock_flush();
}
// MARK: CRTMachine::Machine
+4 -4
View File
@@ -22,15 +22,15 @@ struct Machine {
static std::unique_ptr<Machine> AtariST(const Analyser::Static::Target *, const ROMMachine::ROMFetcher &);
class Options: public Reflection::StructImpl<Options>, public Configurable::DisplayOption<Options> {
friend Configurable::DisplayOption<Options>;
class Options: public Reflection::StructImpl<Options>, public Configurable::Options::Display<Options> {
friend Configurable::Options::Display<Options>;
public:
Options(Configurable::OptionsType type) : Configurable::DisplayOption<Options>(
Options(const Configurable::OptionsType type) : Configurable::Options::Display<Options>(
type == Configurable::OptionsType::UserFriendly ?
Configurable::Display::RGB : Configurable::Display::CompositeColour) {}
private:
Options() : Options(Configurable::OptionsType::UserFriendly) {}
Options() : Options( Configurable::OptionsType::UserFriendly) {}
friend Reflection::StructImpl<Options>;
void declare_fields() {
+2 -3
View File
@@ -127,10 +127,9 @@ Video::Video() :
// Show a total of 260 lines; a little short for PAL but a compromise between that and the ST's
// usual output height of 200 lines.
crt_.set_visible_area(crt_.get_rect_for_area(
crt_.set_fixed_framing(crt_.get_rect_for_area(
33, 260,
480, 1280,
4.0f / 3.0f));
480, 1280));
}
void Video::set_ram(uint16_t *ram, size_t size) {
+1 -1
View File
@@ -168,7 +168,7 @@ public:
}
~ConcreteMachine() {
audio_queue_.flush();
audio_queue_.lock_flush();
}
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final {
+5 -5
View File
@@ -21,15 +21,15 @@ struct Machine {
virtual ~Machine() = default;
static std::unique_ptr<Machine> ColecoVision(const Analyser::Static::Target *, const ROMMachine::ROMFetcher &);
class Options: public Reflection::StructImpl<Options>, public Configurable::DisplayOption<Options> {
friend Configurable::DisplayOption<Options>;
class Options: public Reflection::StructImpl<Options>, public Configurable::Options::Display<Options> {
friend Configurable::Options::Display<Options>;
public:
Options(Configurable::OptionsType type) :
Configurable::DisplayOption<Options>(type == Configurable::OptionsType::UserFriendly ?
Options(const Configurable::OptionsType type) :
Configurable::Options::Display<Options>(type == Configurable::OptionsType::UserFriendly ?
Configurable::Display::SVideo : Configurable::Display::CompositeColour) {}
private:
Options() : Options(Configurable::OptionsType::UserFriendly) {}
Options() : Options( Configurable::OptionsType::UserFriendly) {}
friend Reflection::StructImpl<Options>;
void declare_fields() {
+102 -68
View File
@@ -17,7 +17,10 @@
using namespace Commodore::C1540;
namespace {
ROM::Name rom_name(Personality personality) {
// MARK: - Construction, including ROM requests.
ROM::Name rom_name(const Personality personality) {
switch(personality) {
default:
case Personality::C1540: return ROM::Name::Commodore1540;
@@ -26,11 +29,11 @@ ROM::Name rom_name(Personality personality) {
}
}
ROM::Request Machine::rom_request(Personality personality) {
ROM::Request Machine::rom_request(const Personality personality) {
return ROM::Request(rom_name(personality));
}
MachineBase::MachineBase(Personality personality, const ROM::Map &roms) :
MachineBase::MachineBase(const Personality personality, const ROM::Map &roms) :
Storage::Disk::Controller(1000000),
m6502_(*this),
drive_VIA_(drive_VIA_port_handler_),
@@ -58,14 +61,13 @@ MachineBase::MachineBase(Personality personality, const ROM::Map &roms) :
std::memcpy(rom_, rom->second.data(), std::min(sizeof(rom_), rom->second.size()));
}
Machine::Machine(Personality personality, const ROM::Map &roms) :
Machine::Machine(const Personality personality, const ROM::Map &roms) :
MachineBase(personality, roms) {}
void Machine::set_serial_bus(Commodore::Serial::Bus &serial_bus) {
Commodore::Serial::attach(serial_port_, serial_bus);
}
// MARK: - 6502 bus.
Cycles MachineBase::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
template <CPU::MOS6502Mk2::BusOperation operation, typename AddressT>
Cycles MachineBase::perform(const AddressT address, CPU::MOS6502Mk2::data_t<operation> value) {
/*
Memory map (given that I'm unsure yet on any potential mirroring):
@@ -75,24 +77,28 @@ Cycles MachineBase::perform_bus_operation(CPU::MOS6502::BusOperation operation,
0xc000-0xffff ROM
*/
if(address < 0x800) {
if(is_read(operation))
*value = ram_[address];
if constexpr (is_read(operation))
value = ram_[address];
else
ram_[address] = *value;
ram_[address] = value;
} else if(address >= 0xc000) {
if(is_read(operation)) {
*value = rom_[address & 0x3fff];
if constexpr (is_read(operation)) {
value = rom_[address & 0x3fff];
}
} else if(address >= 0x1800 && address <= 0x180f) {
if(is_read(operation))
*value = serial_port_VIA_.read(address);
if constexpr (is_read(operation))
value = serial_port_VIA_.read(address);
else
serial_port_VIA_.write(address, *value);
serial_port_VIA_.write(address, value);
} else if(address >= 0x1c00 && address <= 0x1c0f) {
if(is_read(operation))
*value = drive_VIA_.read(address);
if constexpr (is_read(operation))
value = drive_VIA_.read(address);
else
drive_VIA_.write(address, *value);
drive_VIA_.write(address, value);
} else {
if constexpr (is_read(operation)) {
value = 0xff;
}
}
serial_port_VIA_.run_for(Cycles(1));
@@ -101,34 +107,42 @@ Cycles MachineBase::perform_bus_operation(CPU::MOS6502::BusOperation operation,
return Cycles(1);
}
void Machine::set_disk(std::shared_ptr<Storage::Disk::Disk> disk) {
get_drive().set_disk(disk);
}
void Machine::run_for(const Cycles cycles) {
m6502_.run_for(cycles);
const bool drive_motor = drive_VIA_port_handler_.get_motor_enabled();
const bool drive_motor = drive_VIA_port_handler_.motor_enabled();
get_drive().set_motor_on(drive_motor);
if(drive_motor)
if(drive_motor) {
Storage::Disk::Controller::run_for(cycles);
}
}
void MachineBase::set_activity_observer(Activity::Observer *observer) {
// MARK: - External attachments.
void Machine::set_serial_bus(Commodore::Serial::Bus &serial_bus) {
Commodore::Serial::attach(serial_port_, serial_bus);
}
void Machine::set_disk(std::shared_ptr<Storage::Disk::Disk> disk) {
get_drive().set_disk(disk);
drive_VIA_port_handler_.set_is_read_only(disk->is_read_only());
}
void MachineBase::set_activity_observer(Activity::Observer *const observer) {
drive_VIA_.bus_handler().set_activity_observer(observer);
get_drive().set_activity_observer(observer, "Drive", false);
}
// MARK: - 6522 delegate
// MARK: - 6522 delegate.
void MachineBase::mos6522_did_change_interrupt_status(void *) {
// both VIAs are connected to the IRQ line
m6502_.set_irq_line(serial_port_VIA_.get_interrupt_line() || drive_VIA_.get_interrupt_line());
m6502_.set<CPU::MOS6502Mk2::Line::IRQ>(serial_port_VIA_.get_interrupt_line() || drive_VIA_.get_interrupt_line());
}
// MARK: - Disk drive
// MARK: - Disk drive.
void MachineBase::process_input_bit(int value) {
void MachineBase::process_input_bit(const int value) {
shift_register_ = (shift_register_ << 1) | value;
if((shift_register_ & 0x3ff) == 0x3ff) {
drive_VIA_port_handler_.set_sync_detected(true);
@@ -140,11 +154,11 @@ void MachineBase::process_input_bit(int value) {
if(bit_window_offset_ == 8) {
drive_VIA_port_handler_.set_data_input(uint8_t(shift_register_));
bit_window_offset_ = 0;
if(drive_VIA_port_handler_.get_should_set_overflow()) {
m6502_.set_overflow_line(true);
if(drive_VIA_port_handler_.should_set_overflow()) {
m6502_.set<CPU::MOS6502Mk2::Line::Overflow>(true);
}
}
else m6502_.set_overflow_line(false);
else m6502_.set<CPU::MOS6502Mk2::Line::Overflow>(false);
}
// the 1540 does not recognise index holes
@@ -164,7 +178,9 @@ void MachineBase::drive_via_did_set_data_density(DriveVIA &, const int density)
template <MOS::MOS6522::Port port>
uint8_t SerialPortVIA::get_port_input() const {
if(port) return port_b_;
if(port) {
return port_b_;
}
return 0xff;
}
@@ -203,13 +219,15 @@ void SerialPortVIA::set_serial_port(Commodore::Serial::Port &port) {
void SerialPortVIA::update_data_line() {
// "ATN (Attention) is an input on pin 3 of P2 and P3 that is sensed at PB7 and CA1 of UC3 after being inverted by UA1"
serial_port_->set_output(::Commodore::Serial::Line::Data,
Serial::LineLevel(!data_level_output_ && (attention_level_input_ != attention_acknowledge_level_)));
serial_port_->set_output(
::Commodore::Serial::Line::Data,
Serial::LineLevel(!data_level_output_ && (attention_level_input_ != attention_acknowledge_level_))
);
}
// MARK: - DriveVIA
void DriveVIA::set_delegate(Delegate *delegate) {
void DriveVIA::set_delegate(Delegate *const delegate) {
delegate_ = delegate;
}
@@ -220,18 +238,22 @@ uint8_t DriveVIA::get_port_input() const {
}
void DriveVIA::set_sync_detected(const bool sync_detected) {
port_b_ = (port_b_ & 0x7f) | (sync_detected ? 0x00 : 0x80);
port_b_ = (port_b_ & ~0x80) | (sync_detected ? 0x00 : 0x80);
}
void DriveVIA::set_data_input(uint8_t value) {
void DriveVIA::set_is_read_only(const bool is_read_only) {
port_b_ = (port_b_ & ~0x10) | (is_read_only ? 0x00 : 0x10);
}
void DriveVIA::set_data_input(const uint8_t value) {
port_a_ = value;
}
bool DriveVIA::get_should_set_overflow() {
bool DriveVIA::should_set_overflow() {
return should_set_overflow_;
}
bool DriveVIA::get_motor_enabled() {
bool DriveVIA::motor_enabled() {
return drive_motor_;
}
@@ -240,36 +262,48 @@ void DriveVIA::set_control_line_output(const bool value) {
if(port == MOS::MOS6522::Port::A && line == MOS::MOS6522::Line::Two) {
should_set_overflow_ = value;
}
}
template <MOS::MOS6522::Port port>
void DriveVIA::set_port_output(const uint8_t value, uint8_t) {
if(port) {
if(previous_port_b_output_ != value) {
// Record drive motor state.
drive_motor_ = value&4;
// Check for a head step.
const int step_difference = ((value&3) - (previous_port_b_output_&3))&3;
if(step_difference) {
if(delegate_) delegate_->drive_via_did_step_head(*this, (step_difference == 1) ? 1 : -1);
}
// Check for a change in density.
const int density_difference = (previous_port_b_output_^value) & (3 << 5);
if(density_difference && delegate_) {
delegate_->drive_via_did_set_data_density(*this, (value >> 5)&3);
}
// Post the LED status.
if(observer_) observer_->set_led_status("Drive", value&8);
previous_port_b_output_ = value;
if(port == MOS::MOS6522::Port::B && line == MOS::MOS6522::Line::Two) {
// TODO: 0 = write, 1 = read.
if(!value) {
printf("NOT IMPLEMENTED: write mode\n");
}
}
}
void DriveVIA::set_activity_observer(Activity::Observer *observer) {
template <>
void DriveVIA::set_port_output<MOS::MOS6522::Port::B>(const uint8_t value, uint8_t) {
if(previous_port_b_output_ != value) {
// Record drive motor state.
drive_motor_ = value&4;
// Check for a head step.
const int step_difference = ((value&3) - (previous_port_b_output_&3))&3;
if(step_difference && delegate_) {
delegate_->drive_via_did_step_head(*this, (step_difference == 1) ? 1 : -1);
}
// Check for a change in density.
const int density_difference = (previous_port_b_output_^value) & (3 << 5);
if(density_difference && delegate_) {
delegate_->drive_via_did_set_data_density(*this, (value >> 5)&3);
}
// Post the LED status.
if(observer_) {
observer_->set_led_status("Drive", value&8);
}
previous_port_b_output_ = value;
}
}
template <>
void DriveVIA::set_port_output<MOS::MOS6522::Port::A>(const uint8_t value, uint8_t) {
printf("TODO: output is %02x\n", value);
}
void DriveVIA::set_activity_observer(Activity::Observer *const observer) {
observer_ = observer;
if(observer) {
observer->register_led("Drive");
@@ -277,9 +311,9 @@ void DriveVIA::set_activity_observer(Activity::Observer *observer) {
}
}
// MARK: - SerialPort
// MARK: - SerialPort.
void SerialPort::set_input(Serial::Line line, Serial::LineLevel level) {
void SerialPort::set_input(const Serial::Line line, const Serial::LineLevel level) {
serial_port_via_->set_serial_line_state(line, bool(level), *via_);
}
@@ -8,7 +8,7 @@
#pragma once
#include "Processors/6502/6502.hpp"
#include "Processors/6502Mk2/6502Mk2.hpp"
#include "Components/6522/6522.hpp"
#include "Machines/Commodore/SerialBus.hpp"
@@ -65,15 +65,17 @@ private:
It is wired up such that Port B contains:
Bits 0/1: head step direction
Bit 2: motor control
Bit 3: LED control (TODO)
Bit 3: LED control
Bit 4: write protect photocell status (TODO)
Bits 5/6: read/write density
Bit 7: 0 if sync marks are currently being detected, 1 otherwise.
... and Port A contains the byte most recently read from the disk or the byte next to write to the disk, depending on data direction.
It is implied that CA2 might be used to set processor overflow, CA1 a strobe for data input, and one of the CBs being definitive on
whether the disk head is being told to read or write, but it's unclear and I've yet to investigate. So, TODO.
Elsewhere:
* CA2 might is used to set processor overflow;
* CA1 a strobe for data input; and
* CB2 indicates read/write mode; 1 = read, 0 = write.
*/
class DriveVIA: public MOS::MOS6522::IRQDelegatePortHandler {
public:
@@ -88,8 +90,9 @@ public:
void set_sync_detected(bool);
void set_data_input(uint8_t);
bool get_should_set_overflow();
bool get_motor_enabled();
void set_is_read_only(bool);
bool should_set_overflow();
bool motor_enabled();
template <MOS::MOS6522::Port, MOS::MOS6522::Line>
void set_control_line_output(bool value);
@@ -122,7 +125,6 @@ private:
};
class MachineBase:
public CPU::MOS6502::BusHandler,
public MOS::MOS6522::IRQDelegatePortHandler::Delegate,
public DriveVIA::Delegate,
public Storage::Disk::Controller {
@@ -134,7 +136,8 @@ public:
void set_activity_observer(Activity::Observer *);
// to satisfy CPU::MOS6502::Processor
Cycles perform_bus_operation(CPU::MOS6502::BusOperation, uint16_t address, uint8_t *value);
template <CPU::MOS6502Mk2::BusOperation operation, typename AddressT>
Cycles perform(const AddressT, CPU::MOS6502Mk2::data_t<operation>);
protected:
// to satisfy MOS::MOS6522::Delegate
@@ -144,7 +147,12 @@ protected:
void drive_via_did_step_head(DriveVIA &, int direction) override;
void drive_via_did_set_data_density(DriveVIA &, int density) override;
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, MachineBase, false> m6502_;
struct M6502Traits {
static constexpr auto uses_ready_line = false;
static constexpr auto pause_precision = CPU::MOS6502Mk2::PausePrecision::AnyCycle;
using BusHandlerT = MachineBase;
};
CPU::MOS6502Mk2::Processor<CPU::MOS6502Mk2::Model::M6502, M6502Traits> m6502_;
uint8_t ram_[0x800];
uint8_t rom_[0x4000];
+3 -3
View File
@@ -9,13 +9,13 @@
#pragma once
#include "Outputs/Speaker/Implementation/BufferSource.hpp"
#include "Concurrency/AsyncTaskQueue.hpp"
#include "Outputs/Speaker/SpeakerQueue.hpp"
namespace Commodore::Plus4 {
class Audio: public Outputs::Speaker::BufferSource<Audio, false> {
public:
Audio(Concurrency::AsyncTaskQueue<false> &audio_queue) :
Audio(Outputs::Speaker::TaskQueue &audio_queue) :
audio_queue_(audio_queue) {}
template <Outputs::Speaker::Action action>
@@ -122,7 +122,7 @@ public:
private:
// Calling-thread state.
Concurrency::AsyncTaskQueue<false> &audio_queue_;
Outputs::Speaker::TaskQueue &audio_queue_;
// Audio-thread state.
int16_t external_volume_ = 0;
+190 -214
View File
@@ -16,7 +16,7 @@
#include "Machines/MachineTypes.hpp"
#include "Machines/Utility/MemoryFuzzer.hpp"
#include "Processors/6502/6502.hpp"
#include "Processors/6502Mk2/6502Mk2.hpp"
#include "Analyser/Static/Commodore/Target.hpp"
#include "Outputs/Log.hpp"
#include "Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
@@ -186,13 +186,11 @@ public:
interrupts_(*this),
timers_(interrupts_),
video_(video_map_, interrupts_),
audio_(audio_queue_),
speaker_(audio_)
audio_(clock_rate(false), Cycles(1))
{
const auto clock = clock_rate(false);
media_divider_ = Cycles(clock);
set_clock_rate(clock);
speaker_.set_input_rate(float(clock));
const auto kernel = ROM::Name::Plus4KernelPALv5;
const auto basic = ROM::Name::Plus4BASIC;
@@ -236,20 +234,17 @@ public:
}
~ConcreteMachine() {
audio_queue_.flush();
audio_.stop();
}
// HACK. NOCOMMIT.
// int pulse_num_ = 0;
Cycles perform_bus_operation(
const CPU::MOS6502::BusOperation operation,
const uint16_t address,
uint8_t *const value
) {
template <CPU::MOS6502Mk2::BusOperation operation, typename AddressT>
Cycles perform(const AddressT address, CPU::MOS6502Mk2::data_t<operation> value) {
// Determine from the TED video subsystem the length of this clock cycle as perceived by the 6502,
// relative to the master clock.
const auto length = video_.cycle_length(operation == CPU::MOS6502::BusOperation::Ready);
const auto length = video_.cycle_length(operation == CPU::MOS6502Mk2::BusOperation::Ready);
// Update other subsystems.
advance_timers_and_tape(length);
@@ -261,11 +256,10 @@ public:
c1541_->run_for(c1541_cycles_.divide(media_divider_));
}
time_since_audio_update_ += length;
audio_ += length;
}
if(operation == CPU::MOS6502::BusOperation::Ready) {
if(operation == CPU::MOS6502Mk2::BusOperation::Ready) {
return length;
}
@@ -282,17 +276,17 @@ public:
// b1 = serial clock out and cassette write;
// b0 = serial data out.
if(is_read(operation)) {
if constexpr (is_read(operation)) {
if(!address) {
*value = io_direction_;
value = io_direction_;
} else {
*value = io_input();
value = io_input();
}
} else {
if(!address) {
io_direction_ = *value;
io_direction_ = value;
} else {
io_output_ = *value;
io_output_ = value;
}
const auto output = io_output_ | ~io_direction_;
@@ -323,36 +317,36 @@ public:
// );
// }
if(
use_fast_tape_hack_ &&
operation == CPU::MOS6502Esque::BusOperation::ReadOpcode &&
(
(use_hle && address == 0xe5fd) ||
address == 0xe68b ||
address == 0xe68d
)
) {
// ++pulse_num_;
if(use_hle) {
read_dipole();
}
if constexpr (is_read(operation)) {
if(
use_fast_tape_hack_ &&
operation == CPU::MOS6502Mk2::BusOperation::ReadOpcode &&
(
(use_hle && address == 0xe5fd) ||
address == 0xe68b ||
address == 0xe68d
)
) {
// ++pulse_num_;
if(use_hle) {
read_dipole();
}
// using Flag = CPU::MOS6502::Flag;
// using Register = CPU::MOS6502::Register;
// const auto flags = m6502_.value_of(Register::Flags);
// printf("to %lld: %c%c%c\n",
// tape_player_->serialiser()->offset(),
// flags & Flag::Sign ? 'n' : '-',
// flags & Flag::Overflow ? 'v' : '-',
// flags & Flag::Carry ? 'c' : '-'
// );
*value = 0x60;
} else {
if(is_read(operation)) {
*value = map_.read(address);
// using Flag = CPU::MOS6502::Flag;
// using Register = CPU::MOS6502::Register;
// const auto flags = m6502_.value_of(Register::Flags);
// printf("to %lld: %c%c%c\n",
// tape_player_->serialiser()->offset(),
// flags & Flag::Sign ? 'n' : '-',
// flags & Flag::Overflow ? 'v' : '-',
// flags & Flag::Carry ? 'c' : '-'
// );
value = 0x60;
} else {
map_.write(address) = *value;
value = map_.read(address);
}
} else {
map_.write(address) = value;
}
@@ -393,12 +387,12 @@ public:
// ram_[0x90] = 0;
// ram_[0x93] = 0;
//
// *value = 0x0c; // NOP abs.
// value = 0x0c; // NOP abs.
// }
// }
} else if(address < 0xff00) {
// Miscellaneous hardware. All TODO.
if(is_read(operation)) {
if constexpr (is_read(operation)) {
switch(address & 0xfff0) {
case 0xfd10:
// 6529 parallel port, about which I know only what I've found in kernel ROM disassemblies.
@@ -406,7 +400,7 @@ public:
// If play button is not currently pressed and this read is immediately followed by
// an AND 4, press it. The kernel will deal with motor control subsequently.
if(!play_button_) {
const uint16_t pc = m6502_.value_of(CPU::MOS6502::Register::ProgramCounter);
const uint16_t pc = m6502_.registers().pc.full;
const uint8_t next[] = {
map_.read(pc+0),
map_.read(pc+1),
@@ -422,22 +416,23 @@ public:
}
}
*value = 0xff ^ (play_button_ ? 0x4 :0x0);
value = 0xff ^ (play_button_ ? 0x4 :0x0);
break;
case 0xfdd0:
case 0xfdf0:
*value = uint8_t(address >> 8);
value = uint8_t(address >> 8);
break;
default:
value = 0xff;
Logger::info().append("TODO: read @ %04x", address);
break;
}
} else {
switch(address & 0xfff0) {
case 0xfd30:
keyboard_mask_ = *value;
keyboard_mask_ = value;
break;
case 0xfdd0: {
@@ -447,28 +442,28 @@ public:
} break;
default:
Logger::info().append("TODO: write of %02x @ %04x", *value, address);
Logger::info().append("TODO: write of %02x @ %04x", value, address);
break;
}
}
} else {
const auto pc = m6502_.value_of(CPU::MOS6502::Register::ProgramCounter);
const auto pc = m6502_.registers().pc.full;
const bool is_from_rom =
(rom_is_paged_ && pc >= 0x8000) ||
(pc >= 0x400 && pc < 0x500) ||
(pc >= 0x700 && pc < 0x800);
bool is_hit = true;
if(is_read(operation)) {
if constexpr (is_read(operation)) {
switch(address) {
case 0xff00: *value = timers_.read<0>(); break;
case 0xff01: *value = timers_.read<1>(); break;
case 0xff02: *value = timers_.read<2>(); break;
case 0xff03: *value = timers_.read<3>(); break;
case 0xff04: *value = timers_.read<4>(); break;
case 0xff05: *value = timers_.read<5>(); break;
case 0xff06: *value = video_.read<0xff06>(); break;
case 0xff07: *value = video_.read<0xff07>(); break;
case 0xff00: value = timers_.read<0>(); break;
case 0xff01: value = timers_.read<1>(); break;
case 0xff02: value = timers_.read<2>(); break;
case 0xff03: value = timers_.read<3>(); break;
case 0xff04: value = timers_.read<4>(); break;
case 0xff05: value = timers_.read<5>(); break;
case 0xff06: value = video_.read<0xff06>(); break;
case 0xff07: value = video_.read<0xff07>(); break;
case 0xff08: {
const uint8_t keyboard_input =
~(
@@ -487,127 +482,122 @@ public:
((joystick_mask_ & 0x02) ? 0xff : (joystick(0).mask() | 0x40)) &
((joystick_mask_ & 0x04) ? 0xff : (joystick(1).mask() | 0x80));
*value = keyboard_input & joystick_mask;
value = keyboard_input & joystick_mask;
} break;
case 0xff09: *value = interrupts_.status(); break;
case 0xff09: value = interrupts_.status(); break;
case 0xff0a:
*value = interrupts_.mask() | video_.read<0xff0a>() | 0xa0;
value = interrupts_.mask() | video_.read<0xff0a>() | 0xa0;
break;
case 0xff0b: *value = video_.read<0xff0b>(); break;
case 0xff0c: *value = video_.read<0xff0c>(); break;
case 0xff0d: *value = video_.read<0xff0d>(); break;
case 0xff0e: *value = ff0e_; break;
case 0xff0f: *value = ff0f_; break;
case 0xff10: *value = ff10_ | 0xfc; break;
case 0xff11: *value = ff11_; break;
case 0xff12: *value = ff12_ | 0xc0; break;
case 0xff13: *value = ff13_ | (rom_is_paged_ ? 1 : 0); break;
case 0xff14: *value = video_.read<0xff14>(); break;
case 0xff15: *value = video_.read<0xff15>(); break;
case 0xff16: *value = video_.read<0xff16>(); break;
case 0xff17: *value = video_.read<0xff17>(); break;
case 0xff18: *value = video_.read<0xff18>(); break;
case 0xff19: *value = video_.read<0xff19>(); break;
case 0xff1a: *value = video_.read<0xff1a>(); break;
case 0xff1b: *value = video_.read<0xff1b>(); break;
case 0xff1c: *value = video_.read<0xff1c>(); break;
case 0xff1d: *value = video_.read<0xff1d>(); break;
case 0xff1e: *value = video_.read<0xff1e>(); break;
case 0xff1f: *value = video_.read<0xff1f>(); break;
case 0xff0b: value = video_.read<0xff0b>(); break;
case 0xff0c: value = video_.read<0xff0c>(); break;
case 0xff0d: value = video_.read<0xff0d>(); break;
case 0xff0e: value = ff0e_; break;
case 0xff0f: value = ff0f_; break;
case 0xff10: value = ff10_ | 0xfc; break;
case 0xff11: value = ff11_; break;
case 0xff12: value = ff12_ | 0xc0; break;
case 0xff13: value = ff13_ | (rom_is_paged_ ? 1 : 0); break;
case 0xff14: value = video_.read<0xff14>(); break;
case 0xff15: value = video_.read<0xff15>(); break;
case 0xff16: value = video_.read<0xff16>(); break;
case 0xff17: value = video_.read<0xff17>(); break;
case 0xff18: value = video_.read<0xff18>(); break;
case 0xff19: value = video_.read<0xff19>(); break;
case 0xff1a: value = video_.read<0xff1a>(); break;
case 0xff1b: value = video_.read<0xff1b>(); break;
case 0xff1c: value = video_.read<0xff1c>(); break;
case 0xff1d: value = video_.read<0xff1d>(); break;
case 0xff1e: value = video_.read<0xff1e>(); break;
case 0xff1f: value = video_.read<0xff1f>(); break;
case 0xff3e: *value = 0; break;
case 0xff3f: *value = 0; break;
case 0xff3e: value = 0; break;
case 0xff3f: value = 0; break;
default:
Logger::info().append("TODO: TED read at %04x", address);
value = 0xff;
is_hit = false;
}
} else {
switch(address) {
case 0xff00: timers_.write<0>(*value); break;
case 0xff01: timers_.write<1>(*value); break;
case 0xff02: timers_.write<2>(*value); break;
case 0xff03: timers_.write<3>(*value); break;
case 0xff04: timers_.write<4>(*value); break;
case 0xff05: timers_.write<5>(*value); break;
case 0xff06: video_.write<0xff06>(*value); break;
case 0xff00: timers_.write<0>(value); break;
case 0xff01: timers_.write<1>(value); break;
case 0xff02: timers_.write<2>(value); break;
case 0xff03: timers_.write<3>(value); break;
case 0xff04: timers_.write<4>(value); break;
case 0xff05: timers_.write<5>(value); break;
case 0xff06: video_.write<0xff06>(value); break;
case 0xff07:
video_.write<0xff07>(*value);
update_audio();
audio_.set_divider(*value);
video_.write<0xff07>(value);
audio_->set_divider(value);
break;
case 0xff08:
// Observation here: the kernel posts a 0 to this
// address upon completing each keyboard scan cycle,
// once per frame.
if(typer_ && !*value) {
if(typer_ && !value) {
if(!typer_->type_next_character()) {
clear_all_keys();
typer_.reset();
}
}
joystick_mask_ = *value;
joystick_mask_ = value;
break;
case 0xff09:
interrupts_.set_status(*value);
interrupts_.set_status(value);
break;
case 0xff0a:
interrupts_.set_mask(*value);
video_.write<0xff0a>(*value);
interrupts_.set_mask(value);
video_.write<0xff0a>(value);
break;
case 0xff0b: video_.write<0xff0b>(*value); break;
case 0xff0c: video_.write<0xff0c>(*value); break;
case 0xff0d: video_.write<0xff0d>(*value); break;
case 0xff0b: video_.write<0xff0b>(value); break;
case 0xff0c: video_.write<0xff0c>(value); break;
case 0xff0d: video_.write<0xff0d>(value); break;
case 0xff0e:
ff0e_ = *value;
update_audio();
audio_.set_frequency_low<0>(*value);
ff0e_ = value;
audio_->set_frequency_low<0>(value);
break;
case 0xff0f:
ff0f_ = *value;
update_audio();
audio_.set_frequency_low<1>(*value);
ff0f_ = value;
audio_->set_frequency_low<1>(value);
break;
case 0xff10:
ff10_ = *value;
update_audio();
audio_.set_frequency_high<1>(*value);
ff10_ = value;
audio_->set_frequency_high<1>(value);
break;
case 0xff11:
ff11_ = *value;
update_audio();
audio_.set_control(*value);
ff11_ = value;
audio_->set_control(value);
break;
case 0xff12:
ff12_ = *value & 0x3f;
video_.write<0xff12>(*value);
ff12_ = value & 0x3f;
video_.write<0xff12>(value);
if((*value & 4)) {
if((value & 4)) {
page_video_rom();
} else {
page_video_ram();
}
update_audio();
audio_.set_frequency_high<0>(*value);
audio_->set_frequency_high<0>(value);
break;
case 0xff13:
ff13_ = *value & 0xfe;
video_.write<0xff13>(*value);
ff13_ = value & 0xfe;
video_.write<0xff13>(value);
break;
case 0xff14: video_.write<0xff14>(*value); break;
case 0xff15: video_.write<0xff15>(*value); break;
case 0xff16: video_.write<0xff16>(*value); break;
case 0xff17: video_.write<0xff17>(*value); break;
case 0xff18: video_.write<0xff18>(*value); break;
case 0xff19: video_.write<0xff19>(*value); break;
case 0xff1a: video_.write<0xff1a>(*value); break;
case 0xff1b: video_.write<0xff1b>(*value); break;
case 0xff1c: video_.write<0xff1c>(*value); break;
case 0xff1d: video_.write<0xff1d>(*value); break;
case 0xff1e: video_.write<0xff1e>(*value); break;
case 0xff1f: video_.write<0xff1f>(*value); break;
case 0xff14: video_.write<0xff14>(value); break;
case 0xff15: video_.write<0xff15>(value); break;
case 0xff16: video_.write<0xff16>(value); break;
case 0xff17: video_.write<0xff17>(value); break;
case 0xff18: video_.write<0xff18>(value); break;
case 0xff19: video_.write<0xff19>(value); break;
case 0xff1a: video_.write<0xff1a>(value); break;
case 0xff1b: video_.write<0xff1b>(value); break;
case 0xff1c: video_.write<0xff1c>(value); break;
case 0xff1d: video_.write<0xff1d>(value); break;
case 0xff1e: video_.write<0xff1e>(value); break;
case 0xff1f: video_.write<0xff1f>(value); break;
case 0xff3e: page_cpu_rom(); break;
case 0xff3f: page_cpu_ram(); break;
@@ -626,22 +616,26 @@ public:
}
private:
using Processor = CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, ConcreteMachine, true>;
Processor m6502_;
struct M6502Traits {
static constexpr auto uses_ready_line = true;
static constexpr auto pause_precision = CPU::MOS6502Mk2::PausePrecision::BetweenInstructions;
using BusHandlerT = ConcreteMachine;
};
CPU::MOS6502Mk2::Processor<CPU::MOS6502Mk2::Model::M6502, M6502Traits> m6502_;
Outputs::Speaker::Speaker *get_speaker() override {
return &speaker_;
return &audio_.speaker();
}
void set_activity_observer(Activity::Observer *const observer) final {
if(c1541_) c1541_->set_activity_observer(observer);
}
void set_irq_line(bool active) override {
m6502_.set_irq_line(active);
void set_irq_line(const bool active) override {
m6502_.template set<CPU::MOS6502Mk2::Line::IRQ>(active);
}
void set_ready_line(bool active) override {
m6502_.set_ready_line(active);
void set_ready_line(const bool active) override {
m6502_.template set<CPU::MOS6502Mk2::Line::Ready>(active);
}
void page_video_rom() {
@@ -684,16 +678,12 @@ private:
void run_for(const Cycles cycles) final {
m6502_.run_for(cycles);
// I don't know why.
update_audio();
audio_queue_.perform();
audio_.perform();
}
void flush_output(int outputs) override {
if(outputs & Output::Audio) {
update_audio();
audio_queue_.perform();
audio_.perform();
}
}
@@ -720,14 +710,7 @@ private:
Cycles timers_subcycles_;
Timers timers_;
Video video_;
Concurrency::AsyncTaskQueue<false> audio_queue_;
Audio audio_;
Cycles time_since_audio_update_;
Outputs::Speaker::PullLowpass<Audio> speaker_;
void update_audio() {
speaker_.run_for(audio_queue_, time_since_audio_update_.flush<Cycles>());
}
Outputs::Speaker::PullLowpassSpeakerQueue<Cycles, Audio> audio_;
// MARK: - MappedKeyboardMachine.
MappedKeyboardMachine::KeyboardMapper *get_keyboard_mapper() override {
@@ -788,26 +771,19 @@ private:
// TODO: substantially simplify the below; at the minute it's a
// literal transcription of the original as a simple first step.
void read_dipole() {
using Register = CPU::MOS6502::Register;
using Flag = CPU::MOS6502::Flag;
using Flag = CPU::MOS6502Mk2::Flag;
//
// Get registers now and ensure they'll be written back at function exit.
//
CPU::MOS6502Esque::LazyFlags flags(uint8_t(m6502_.value_of(Register::Flags)));
uint8_t x, y, a;
uint8_t s = uint8_t(m6502_.value_of(Register::StackPointer));
auto registers = m6502_.registers();
struct ScopeGuard {
ScopeGuard(std::function<void(void)> at_exit) : at_exit_(at_exit) {}
~ScopeGuard() { at_exit_(); }
private:
std::function<void(void)> at_exit_;
} registers([&] {
m6502_.set_value_of(Register::Flags, flags.get());
m6502_.set_value_of(Register::A, a);
m6502_.set_value_of(Register::X, x);
m6502_.set_value_of(Register::Y, y);
m6502_.set_value_of(Register::StackPointer, s);
} store_registers([&] {
m6502_.set_registers(registers);
});
//
@@ -822,38 +798,38 @@ private:
// 6502 pseudo-ops.
//
const auto ldabs = [&] (uint8_t &target, const uint16_t address) {
flags.set_nz(target = map_.read(address));
registers.flags.set_per<Flag::NegativeZero>(target = map_.read(address));
};
const auto ldimm = [&] (uint8_t &target, const uint8_t value) {
flags.set_nz(target = value);
registers.flags.set_per<Flag::NegativeZero>(target = value);
};
const auto pha = [&] () {
map_.write(0x100 + s) = a;
--s;
map_.write(0x100 + registers.s) = registers.a;
--registers.s;
};
const auto pla = [&] () {
++s;
a = map_.read(0x100 + s);
++registers.s;
registers.a = map_.read(0x100 + registers.s);
};
const auto bit = [&] (const uint8_t value) {
flags.zero_result = a & value;
flags.negative_result = value;
flags.overflow = value & CPU::MOS6502Esque::Flag::Overflow;
registers.flags.set_per<Flag::Zero>(registers.a & value);
registers.flags.set_per<Flag::Negative>(value);
registers.flags.set_per<Flag::Overflow>(value);
};
const auto cmp = [&] (const uint8_t value) {
const uint16_t temp16 = a - value;
flags.set_nz(uint8_t(temp16));
flags.carry = ((~temp16) >> 8)&1;
const uint16_t temp16 = registers.a - value;
registers.flags.set_per<Flag::NegativeZero>(uint8_t(temp16));
registers.flags.set_per<Flag::Carry>(((~temp16) >> 8)&1);
};
const auto andimm = [&] (const uint8_t value) {
a &= value;
flags.set_nz(a);
registers.a &= value;
registers.flags.set_per<Flag::NegativeZero>(registers.a);
};
const auto ne = [&]() -> bool {
return flags.zero_result;
return !registers.flags.get<Flag::Zero>();
};
const auto eq = [&]() -> bool {
return !flags.zero_result;
return registers.flags.get<Flag::Zero>();
};
//
@@ -862,7 +838,7 @@ private:
const auto dipok = [&] {
// clc ; everything's fine
// rts
flags.carry = 0;
registers.flags.set_per<Flag::Carry>(0);
};
const auto rshort = [&] {
// bit tshrtd ; got a short
@@ -878,7 +854,7 @@ private:
const auto rderr1 = [&] {
// sec ; i'm confused
// rts
flags.carry = Flag::Carry;
registers.flags.set_per<Flag::Carry>(Flag::Carry);
};
//
@@ -891,8 +867,8 @@ private:
//rddipl
// ldx dsamp1 ; setup x,y with 1st sample point
// ldy dsamp1+1
ldabs(x, dsamp1);
ldabs(y, dsamp1 + 1);
ldabs(registers.x, dsamp1);
ldabs(registers.y, dsamp1 + 1);
advance_cycles(8);
//badeg1
@@ -901,9 +877,9 @@ private:
// pha
// lda dsamp2
// pha
ldabs(a, dsamp2 + 1);
ldabs(registers.a, dsamp2 + 1);
pha();
ldabs(a, dsamp2);
ldabs(registers.a, dsamp2);
pha();
advance_cycles(14);
@@ -911,7 +887,7 @@ private:
//rwtl ; wait till rd line is high
// bit port [= $0001]
// beq rwtl ; !ls!
ldimm(a, 0x10);
ldimm(registers.a, 0x10);
advance_cycles(2);
do {
bit(io_input());
@@ -933,8 +909,8 @@ private:
// stx timr2l
// sty timr2h
timers_.write<2>(x);
timers_.write<3>(y);
timers_.write<2>(registers.x);
timers_.write<3>(registers.y);
advance_cycles(8);
@@ -945,9 +921,9 @@ private:
// pla
// sta timr3h ;go! ...tb
pla();
timers_.write<4>(a);
timers_.write<4>(registers.a);
pla();
timers_.write<5>(a);
timers_.write<5>(registers.a);
advance_cycles(14);
@@ -955,8 +931,8 @@ private:
//
// lda #$50 ; clr ta,tb
// sta tedirq
ldimm(a, 0x50);
interrupts_.set_status(a);
ldimm(registers.a, 0x50);
interrupts_.set_status(registers.a);
advance_cycles(6);
@@ -969,7 +945,7 @@ private:
// and #$10 ; a look at that edge again
// bne badeg1 ; woa! got a bad edge trigger !ls!
do {
ldimm(a, io_input());
ldimm(registers.a, io_input());
cmp(io_input());
if(advance_cycles(9)) {
return;
@@ -992,7 +968,7 @@ private:
// lda #$10
//wata ; wait for ta to timeout
ldimm(a, 0x10);
ldimm(registers.a, 0x10);
advance_cycles(3);
do {
// bit port ; kuldge, kludge, kludge !!! <<><>>
@@ -1020,7 +996,7 @@ private:
do {
// lda port
// cmp port
ldimm(a, io_input());
ldimm(registers.a, io_input());
cmp(io_input());
if(advance_cycles(9)) {
@@ -1048,7 +1024,7 @@ private:
//
//; wait for tb to timeout
//; now do the dipole sample #2
ldimm(a, 0x40);
ldimm(registers.a, 0x40);
advance_cycles(3);
do {
bit(interrupts_.status());
@@ -1063,7 +1039,7 @@ private:
// cmp port
// bne casdb3
do {
ldimm(a, io_input());
ldimm(registers.a, io_input());
cmp(io_input());
if(advance_cycles(9)) {
return;
@@ -1084,10 +1060,10 @@ private:
// sta timr2l
// lda zcell+1
// sta timr2h
ldabs(a, zcell);
timers_.write<2>(a);
ldabs(a, zcell + 1);
timers_.write<3>(y);
ldabs(registers.a, zcell);
timers_.write<2>(registers.a);
ldabs(registers.a, zcell + 1);
timers_.write<3>(registers.y);
advance_cycles(16);
@@ -1096,9 +1072,9 @@ private:
// lda #$10
// sta tedirq ; verify +180 half of word dipole
// lda #$10
ldimm(a, 0x10);
interrupts_.set_status(a);
ldimm(a, 0x10);
ldimm(registers.a, 0x10);
interrupts_.set_status(registers.a);
ldimm(registers.a, 0x10);
advance_cycles(8);
//wata2
@@ -1116,7 +1092,7 @@ private:
// cmp port
// bne casdb4
do {
ldimm(a, io_input());
ldimm(registers.a, io_input());
cmp(io_input());
if(advance_cycles(9)) {
return;
@@ -1173,7 +1149,7 @@ private:
std::unique_ptr<Reflection::Struct> get_options() const final {
auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly);
options->output = get_video_signal_configurable();
options->quickload = allow_fast_tape_hack_;
options->quick_load = allow_fast_tape_hack_;
return options;
}
@@ -1181,7 +1157,7 @@ private:
const auto options = dynamic_cast<Options *>(str.get());
set_video_signal_configurable(options->output);
allow_fast_tape_hack_ = options->quickload;
allow_fast_tape_hack_ = options->quick_load;
set_use_fast_tape();
}
};
+8 -8
View File
@@ -26,19 +26,19 @@ struct Machine {
class Options:
public Reflection::StructImpl<Options>,
public Configurable::DisplayOption<Options>,
public Configurable::QuickloadOption<Options>
public Configurable::Options::Display<Options>,
public Configurable::Options::QuickLoad<Options>
{
friend Configurable::DisplayOption<Options>;
friend Configurable::QuickloadOption<Options>;
friend Configurable::Options::Display<Options>;
friend Configurable::Options::QuickLoad<Options>;
public:
Options(Configurable::OptionsType type) :
Configurable::DisplayOption<Options>(type == Configurable::OptionsType::UserFriendly ?
Options(const Configurable::OptionsType type) :
Configurable::Options::Display<Options>(type == Configurable::OptionsType::UserFriendly ?
Configurable::Display::SVideo : Configurable::Display::CompositeColour),
Configurable::QuickloadOption<Options>(type == Configurable::OptionsType::UserFriendly) {
Configurable::Options::QuickLoad<Options>(type == Configurable::OptionsType::UserFriendly) {
}
private:
Options() : Options(Configurable::OptionsType::UserFriendly) {}
Options() : Options( Configurable::OptionsType::UserFriendly) {}
friend Reflection::StructImpl<Options>;
void declare_fields() {
+2 -3
View File
@@ -36,12 +36,11 @@ public:
const auto visible_lines = 33 * 8;
const auto centre = eos() - vs_stop() + 104; // i.e. centre on vertical_counter_ = 104.
crt_.set_visible_area(crt_.get_rect_for_area(
crt_.set_fixed_framing(crt_.get_rect_for_area(
centre - (visible_lines / 2),
visible_lines,
int(HorizontalEvent::Begin40Columns) - int(HorizontalEvent::BeginSync) + int(HorizontalEvent::ScheduleCounterReset) + 1 - 8,
int(HorizontalEvent::End40Columns) - int(HorizontalEvent::Begin40Columns) + 16,
4.0f / 3.0f
int(HorizontalEvent::End40Columns) - int(HorizontalEvent::Begin40Columns) + 16
));
}
+7 -7
View File
@@ -36,15 +36,15 @@ enum Key: uint16_t {
Key9 = key(0, 0x10), KeyPlus = key(0, 0x20), KeyGBP = key(0, 0x40), KeyDelete = key(0, 0x80),
// Virtual keys.
KeyUp = 0xfff0,
KeyLeft = 0xfff1,
KeyF2 = 0xfff2,
KeyF4 = 0xfff3,
KeyF6 = 0xfff4,
KeyF8 = 0xfff5,
KeyUp = 0xfe00,
KeyLeft,
KeyF2,
KeyF4,
KeyF6,
KeyF8,
// Physical keys not within the usual matrix.
KeyRestore = 0xfffc,
KeyRestore,
};
struct KeyboardMapper: public MachineTypes::MappedKeyboardMachine::KeyboardMapper {
+64 -48
View File
@@ -13,7 +13,7 @@
#include "Activity/Source.hpp"
#include "Machines/MachineTypes.hpp"
#include "Processors/6502/6502.hpp"
#include "Processors/6502Mk2/6502Mk2.hpp"
#include "Components/6560/6560.hpp"
#include "Components/6522/6522.hpp"
@@ -72,26 +72,30 @@ public:
// Port A provides information about the presence or absence of a tape, and parts of
// the joystick and serial port state, both of which have been statefully collected
// into port_a_.
if(!port) {
if constexpr (port == MOS::MOS6522::Port::A) {
return port_a_ | (tape_->has_tape() ? 0x00 : 0x40);
}
return 0xff;
}
/// Receives announcements of control line output change from the 6522.
template <MOS::MOS6522::Port port, MOS::MOS6522::Line line> void set_control_line_output(const bool value) {
// The CA2 output is used to control the tape motor.
if(port == MOS::MOS6522::Port::A && line == MOS::MOS6522::Line::Two) {
template <MOS::MOS6522::Port port, MOS::MOS6522::Line line>
void set_control_line_output(const bool value) {
// CA2: control the tape motor.
if constexpr (port == MOS::MOS6522::Port::A && line == MOS::MOS6522::Line::Two) {
tape_->set_motor_control(!value);
}
}
/// Receives announcements of changes in the serial bus connected to the serial port and propagates them into Port A.
void set_serial_line_state(Commodore::Serial::Line line, const bool value) {
void set_serial_line_state(const Commodore::Serial::Line line, const bool value) {
const auto set = [&](const uint8_t bit) {
port_a_ = (port_a_ & ~bit) | (value ? bit : 0x00);
};
switch(line) {
default: break;
case ::Commodore::Serial::Line::Data: port_a_ = (port_a_ & ~0x02) | (value ? 0x02 : 0x00); break;
case ::Commodore::Serial::Line::Clock: port_a_ = (port_a_ & ~0x01) | (value ? 0x01 : 0x00); break;
case ::Commodore::Serial::Line::Data: set(0x02); break;
case ::Commodore::Serial::Line::Clock: set(0x01); break;
}
}
@@ -146,7 +150,7 @@ public:
/// Sets all keys as unpressed.
void clear_all_keys() {
memset(columns_, 0xff, sizeof(columns_));
std::fill(std::begin(columns_), std::end(columns_), 0xff);
}
/// Called by the 6522 to get input. Reads the keyboard on Port A, returns a small amount of joystick state on Port B.
@@ -230,8 +234,6 @@ struct Vic6560BusHandler {
// It is assumed that these pointers have been filled in by the machine.
const uint8_t *video_memory_map[16]{}; // Segments video memory into 1kb portions.
const uint8_t *colour_memory{}; // Colour memory must be contiguous.
// TODO: make the above const.
};
/*!
@@ -253,7 +255,7 @@ public:
void did_set_input(const Input &digital_input, const bool is_active) final {
if(const auto mapped_input = [&]() -> std::optional<JoystickInput> {
switch(digital_input.type) {
default: return std::nullopt;
default: return std::nullopt;
case Input::Up: return Up;
case Input::Down: return Down;
case Input::Left: return Left;
@@ -280,7 +282,6 @@ class ConcreteMachine:
public MachineTypes::MappedKeyboardMachine,
public MachineTypes::JoystickMachine,
public Configurable::Device,
public CPU::MOS6502::BusHandler,
public MOS::MOS6522::IRQDelegatePortHandler::Delegate,
public Utility::TypeRecipient<CharacterMapper>,
public Storage::Tape::BinaryTapePlayer::Delegate,
@@ -455,7 +456,7 @@ public:
}
void set_key_state(const uint16_t key, const bool is_pressed) final {
if(key < 0xfff0) {
if(key < KeyUp) {
keyboard_via_port_handler_.set_key_state(key, is_pressed);
} else {
switch(key) {
@@ -481,28 +482,27 @@ public:
void clear_all_keys() final {
keyboard_via_port_handler_.clear_all_keys();
set_key_state(KeyRestore, false);
}
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final {
return joysticks_;
}
// to satisfy CPU::MOS6502::Processor
forceinline Cycles perform_bus_operation(
const CPU::MOS6502::BusOperation operation,
const uint16_t address,
uint8_t *const value
) {
template <CPU::MOS6502Mk2::BusOperation operation, typename AddressT>
Cycles perform(const AddressT address, CPU::MOS6502Mk2::data_t<operation> value) {
// Tun the phase-1 part of this cycle, in which the VIC accesses memory.
cycles_since_mos6560_update_++;
// Run the phase-2 part of the cycle, which is whatever the 6502 said it should be.
const bool is_from_rom = m6502_.value_of(CPU::MOS6502::Register::ProgramCounter) > 0x8000;
if(is_read(operation)) {
const auto is_from_rom = [&]() {
return m6502_.registers().pc.full > 0x8000;
};
if constexpr (is_read(operation)) {
const auto page = processor_read_memory_map_[address >> 10];
uint8_t result;
if(!page) {
if(!is_from_rom) confidence_.add_miss();
if(!is_from_rom()) confidence_.add_miss();
result = 0xff;
} else {
result = processor_read_memory_map_[address >> 10][address & 0x3ff];
@@ -515,7 +515,7 @@ public:
if(address & 0x10) result &= user_port_via_.read(address);
if(address & 0x20) result &= keyboard_via_.read(address);
if(!is_from_rom) {
if(!is_from_rom()) {
if((address & 0x100) && !(address & 0x30)) {
confidence_.add_miss();
} else {
@@ -523,10 +523,10 @@ public:
}
}
}
*value = result;
value = result;
// Consider applying the fast tape hack.
if(use_fast_tape_hack_ && operation == CPU::MOS6502::BusOperation::ReadOpcode) {
if(use_fast_tape_hack_ && operation == CPU::MOS6502Mk2::BusOperation::ReadOpcode) {
if(address == 0xf7b2) {
// Address 0xf7b2 contains a JSR to 0xf8c0 ('RDTPBLKS') that will fill the tape buffer with the
// next header. Skip that via a three-byte NOP and fill in the next header programmatically.
@@ -551,10 +551,10 @@ public:
ram_[0x90] = 0;
ram_[0x93] = 0;
*value = 0x0c; // i.e. NOP abs, to swallow the entire JSR
value = 0x0c; // i.e. NOP abs, to swallow the entire JSR
} else if(address == 0xf90b) {
const auto x = uint8_t(m6502_.value_of(CPU::MOS6502::Register::X));
if(x == 0xe) {
auto registers = m6502_.registers();
if(registers.x == 0xe) {
Storage::Tape::Commodore::Parser parser(TargetPlatform::Vic20);
const auto tape_position = tape_->serialiser()->offset();
const std::unique_ptr<Storage::Tape::Commodore::Data> data = parser.get_next_data(*tape_->serialiser());
@@ -576,14 +576,13 @@ public:
// set tape status, carry and flag
ram_[0x90] |= 0x40;
uint8_t flags = uint8_t(m6502_.value_of(CPU::MOS6502::Register::Flags));
flags &= ~uint8_t((CPU::MOS6502::Flag::Carry | CPU::MOS6502::Flag::Interrupt));
m6502_.set_value_of(CPU::MOS6502::Register::Flags, flags);
registers.flags.set_per<CPU::MOS6502Mk2::Flag::Carry>(0);
registers.flags.set_per<CPU::MOS6502Mk2::Flag::Interrupt>(0);
// to ensure that execution proceeds to 0xfccf, pretend a NOP was here and
// ensure that the PC leaps to 0xfccf
m6502_.set_value_of(CPU::MOS6502::Register::ProgramCounter, 0xfccf);
*value = 0xea; // i.e. NOP implied
registers.pc.full = 0xfccf;
value = 0xea; // i.e. NOP implied
hold_tape_ = true;
Logger::info().append("Found data");
} else {
@@ -592,27 +591,28 @@ public:
Logger::info().append("Didn't find data");
}
}
m6502_.set_registers(registers);
}
}
} else {
uint8_t *const ram = processor_write_memory_map_[address >> 10];
if(ram) {
update_video();
ram[address & 0x3ff] = *value;
ram[address & 0x3ff] = value;
}
// Anything between 0x9000 and 0x9400 is the IO area.
if((address&0xfc00) == 0x9000) {
// The VIC is selected by bit 8 = 0
if(!(address&0x100)) {
update_video();
mos6560_.write(address, *value);
mos6560_.write(address, value);
}
// The first VIA is selected by bit 4 = 1.
if(address & 0x10) user_port_via_.write(address, *value);
if(address & 0x10) user_port_via_.write(address, value);
// The second VIA is selected by bit 5 = 1.
if(address & 0x20) keyboard_via_.write(address, *value);
if(address & 0x20) keyboard_via_.write(address, value);
if(!is_from_rom) {
if(!is_from_rom()) {
if((address & 0x100) && !(address & 0x30)) {
confidence_.add_miss();
} else {
@@ -620,13 +620,13 @@ public:
}
}
} else if(!ram) {
if(!is_from_rom) confidence_.add_miss();
if(!is_from_rom()) confidence_.add_miss();
}
}
user_port_via_.run_for(Cycles(1));
keyboard_via_.run_for(Cycles(1));
if(typer_ && address == 0xeb1e && operation == CPU::MOS6502::BusOperation::ReadOpcode) {
if(typer_ && address == 0xeb1e && operation == CPU::MOS6502Mk2::BusOperation::ReadOpcode) {
if(!typer_->type_next_character()) {
clear_all_keys();
typer_.reset();
@@ -672,8 +672,8 @@ public:
}
void mos6522_did_change_interrupt_status(void *) final {
m6502_.set_nmi_line(user_port_via_.get_interrupt_line());
m6502_.set_irq_line(keyboard_via_.get_interrupt_line());
m6502_.template set<CPU::MOS6502Mk2::Line::NMI>(user_port_via_.get_interrupt_line());
m6502_.template set<CPU::MOS6502Mk2::Line::IRQ>(keyboard_via_.get_interrupt_line());
}
void type_string(const std::string &string) final {
@@ -696,14 +696,14 @@ public:
std::unique_ptr<Reflection::Struct> get_options() const final {
auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly);
options->output = get_video_signal_configurable();
options->quickload = allow_fast_tape_hack_;
options->quick_load = allow_fast_tape_hack_;
return options;
}
void set_options(const std::unique_ptr<Reflection::Struct> &str) final {
const auto options = dynamic_cast<Options *>(str.get());
set_video_signal_configurable(options->output);
allow_fast_tape_hack_ = options->quickload;
allow_fast_tape_hack_ = options->quick_load;
set_use_fast_tape();
}
@@ -718,10 +718,16 @@ public:
}
private:
struct M6502Traits {
static constexpr auto uses_ready_line = false;
static constexpr auto pause_precision = CPU::MOS6502Mk2::PausePrecision::BetweenInstructions;
using BusHandlerT = ConcreteMachine;
};
CPU::MOS6502Mk2::Processor<CPU::MOS6502Mk2::Model::M6502, M6502Traits> m6502_;
void update_video() {
mos6560_.run_for(cycles_since_mos6560_update_.flush<Cycles>());
}
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_;
std::vector<uint8_t> character_rom_;
std::vector<uint8_t> basic_rom_;
@@ -745,12 +751,22 @@ private:
++address;
}
}
void write_to_map(const uint8_t **const map, const uint8_t *area, uint16_t address, size_t length) {
void write_to_map(
const uint8_t **const map,
const uint8_t *const area,
const uint16_t address,
const size_t length
) {
write_to_map([&](const uint16_t address, const size_t offset) {
map[address] = &area[offset];
}, address, length);
}
void write_to_map(uint8_t **const map, uint8_t *area, uint16_t address, size_t length) {
void write_to_map(
uint8_t **const map,
uint8_t *const area,
const uint16_t address,
const size_t length
) {
write_to_map([&](const uint16_t address, const size_t offset) {
map[address] = &area[offset];
}, address, length);
+7 -7
View File
@@ -28,16 +28,16 @@ struct Machine {
class Options:
public Reflection::StructImpl<Options>,
public Configurable::DisplayOption<Options>,
public Configurable::QuickloadOption<Options>
public Configurable::Options::Display<Options>,
public Configurable::Options::QuickLoad<Options>
{
friend Configurable::DisplayOption<Options>;
friend Configurable::QuickloadOption<Options>;
friend Configurable::Options::Display<Options>;
friend Configurable::Options::QuickLoad<Options>;
public:
Options(Configurable::OptionsType type) :
Configurable::DisplayOption<Options>(type == Configurable::OptionsType::UserFriendly ?
Options(const Configurable::OptionsType type) :
Configurable::Options::Display<Options>(type == Configurable::OptionsType::UserFriendly ?
Configurable::Display::SVideo : Configurable::Display::CompositeColour),
Configurable::QuickloadOption<Options>(type == Configurable::OptionsType::UserFriendly) {
Configurable::Options::QuickLoad<Options>(type == Configurable::OptionsType::UserFriendly) {
}
private:
Options() : Options(Configurable::OptionsType::UserFriendly) {}
+1 -1
View File
@@ -12,7 +12,7 @@ using namespace Enterprise::Dave;
// MARK: - Audio generator
Audio::Audio(Concurrency::AsyncTaskQueue<false> &audio_queue) :
Audio::Audio(Outputs::Speaker::TaskQueue &audio_queue) :
audio_queue_(audio_queue) {}
void Audio::write(uint16_t address, const uint8_t value) {
+3 -3
View File
@@ -11,7 +11,7 @@
#include <cstdint>
#include "ClockReceiver/ClockReceiver.hpp"
#include "Concurrency/AsyncTaskQueue.hpp"
#include "Outputs/Speaker/SpeakerQueue.hpp"
#include "Numeric/LFSR.hpp"
#include "Outputs/Speaker/Implementation/BufferSource.hpp"
@@ -28,7 +28,7 @@ enum class Interrupt: uint8_t {
*/
class Audio: public Outputs::Speaker::BufferSource<Audio, true> {
public:
Audio(Concurrency::AsyncTaskQueue<false> &audio_queue);
Audio(Outputs::Speaker::TaskQueue &);
/// Modifies an register in the audio range; only the low 4 bits are
/// used for register decoding so it's assumed that the caller has
@@ -41,7 +41,7 @@ public:
void apply_samples(std::size_t number_of_samples, Outputs::Speaker::StereoSample *target);
private:
Concurrency::AsyncTaskQueue<false> &audio_queue_;
Outputs::Speaker::TaskQueue &audio_queue_;
// Global divider (i.e. 8MHz/12Mhz switch).
uint8_t global_divider_;
+10 -21
View File
@@ -103,12 +103,10 @@ public:
min_ram_slot_(min_ram_slot(target)),
z80_(*this),
nick_(ram_.end() - 65536),
dave_audio_(audio_queue_),
speaker_(dave_audio_) {
audio_(float(clock_rate) / float(DaveDivider), DaveDivider) {
// Request a clock of 4Mhz; this'll be mapped upwards for Nick and downwards for Dave elsewhere.
set_clock_rate(clock_rate);
speaker_.set_input_rate(float(clock_rate) / float(dave_divider));
ROM::Request request;
using Target = Analyser::Static::Enterprise::Target;
@@ -257,7 +255,7 @@ public:
}
~ConcreteMachine() {
audio_queue_.flush();
audio_.stop();
}
// MARK: - Z80::BusHandler.
@@ -344,7 +342,7 @@ public:
}
const HalfCycles full_length = cycle.length + penalty;
time_since_audio_update_ += full_length;
audio_ += full_length;
advance_nick(full_length);
if(dave_timer_ += full_length) {
set_interrupts(dave_timer_.last_valid()->get_new_interrupts(), dave_timer_.last_sequence_point_overrun());
@@ -475,8 +473,7 @@ public:
case 0xa4: case 0xa5: case 0xa6: case 0xa7:
case 0xa8: case 0xa9: case 0xaa: case 0xab:
case 0xac: case 0xad: case 0xae: case 0xaf:
update_audio();
dave_audio_.write(address, *cycle.value);
audio_->write(address, *cycle.value);
dave_timer_->write(address, *cycle.value);
break;
@@ -563,8 +560,7 @@ public:
nick_.flush();
}
if(outputs & Output::Audio) {
update_audio();
audio_queue_.perform();
audio_.perform();
}
}
@@ -650,7 +646,7 @@ private:
// MARK: - AudioProducer
Outputs::Speaker::Speaker *get_speaker() final {
return &speaker_;
return &audio_.speaker();
}
// MARK: - TimedMachine
@@ -726,20 +722,13 @@ private:
bool previous_nick_interrupt_line_ = false;
// Cf. timing guesses above.
Concurrency::AsyncTaskQueue<false> audio_queue_;
Dave::Audio dave_audio_;
Outputs::Speaker::PullLowpass<Dave::Audio> speaker_;
HalfCycles time_since_audio_update_;
Outputs::Speaker::PullLowpassSpeakerQueue<HalfCycles, Dave::Audio> audio_;
HalfCycles dave_delay_ = HalfCycles(2);
// The divider supplied to the JustInTimeActor and the manual divider used in
// update_audio() should match.
static constexpr int dave_divider = 8;
JustInTimeActor<Dave::TimedInterruptSource, HalfCycles, 1, dave_divider> dave_timer_;
inline void update_audio() {
speaker_.run_for(audio_queue_, time_since_audio_update_.divide_cycles(Cycles(dave_divider)));
}
// the spekaer queue should match.
static constexpr int DaveDivider = 8;
JustInTimeActor<Dave::TimedInterruptSource, HalfCycles, 1, DaveDivider> dave_timer_;
// MARK: - EXDos card.
EXDos exdos_;
+5 -5
View File
@@ -28,16 +28,16 @@ struct Machine {
static std::unique_ptr<Machine> Enterprise(const Analyser::Static::Target *, const ROMMachine::ROMFetcher &);
/// Defines the runtime options available for an Enterprise.
class Options: public Reflection::StructImpl<Options>, public Configurable::DisplayOption<Options> {
friend Configurable::DisplayOption<Options>;
class Options: public Reflection::StructImpl<Options>, public Configurable::Options::Display<Options> {
friend Configurable::Options::Display<Options>;
public:
Options(Configurable::OptionsType type) :
Configurable::DisplayOption<Options>(
Options(const Configurable::OptionsType type) :
Configurable::Options::Display<Options>(
type == Configurable::OptionsType::UserFriendly ?
Configurable::Display::RGB : Configurable::Display::CompositeColour) {}
private:
Options() : Options(Configurable::OptionsType::UserFriendly) {}
Options() : Options( Configurable::OptionsType::UserFriendly) {}
friend Reflection::StructImpl<Options>;
void declare_fields() {
+1 -1
View File
@@ -54,7 +54,7 @@ Nick::Nick(const uint8_t *const ram) :
set_display_type(Outputs::Display::DisplayType::RGB);
// Crop to the centre 90% of the display.
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));
}
void Nick::write(const uint16_t address, const uint8_t value) {
+3 -3
View File
@@ -360,7 +360,7 @@ public:
}
~ConcreteMachine() {
speaker_.audio_queue.flush();
speaker_.audio_queue.lock_flush();
}
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
@@ -815,14 +815,14 @@ public:
std::unique_ptr<Reflection::Struct> get_options() const final {
auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly);
options->output = get_video_signal_configurable();
options->quickload = allow_fast_tape_;
options->quick_load = allow_fast_tape_;
return options;
}
void set_options(const std::unique_ptr<Reflection::Struct> &str) final {
const auto options = dynamic_cast<Options *>(str.get());
set_video_signal_configurable(options->output);
allow_fast_tape_ = options->quickload;
allow_fast_tape_ = options->quick_load;
set_use_fast_tape();
}
+8 -8
View File
@@ -23,18 +23,18 @@ struct Machine {
class Options:
public Reflection::StructImpl<Options>,
public Configurable::DisplayOption<Options>,
public Configurable::QuickloadOption<Options>
public Configurable::Options::Display<Options>,
public Configurable::Options::QuickLoad<Options>
{
friend Configurable::DisplayOption<Options>;
friend Configurable::QuickloadOption<Options>;
friend Configurable::Options::Display<Options>;
friend Configurable::Options::QuickLoad<Options>;
public:
Options(Configurable::OptionsType type) :
Configurable::DisplayOption<Options>(type == Configurable::OptionsType::UserFriendly ? Configurable::Display::RGB : Configurable::Display::CompositeColour),
Configurable::QuickloadOption<Options>(type == Configurable::OptionsType::UserFriendly) {}
Options(const Configurable::OptionsType type) :
Configurable::Options::Display<Options>(type == Configurable::OptionsType::UserFriendly ? Configurable::Display::RGB : Configurable::Display::CompositeColour),
Configurable::Options::QuickLoad<Options>(type == Configurable::OptionsType::UserFriendly) {}
private:
Options() : Options(Configurable::OptionsType::UserFriendly) {}
Options() : Options( Configurable::OptionsType::UserFriendly) {}
friend Reflection::StructImpl<Options>;
void declare_fields() {
+1 -1
View File
@@ -178,7 +178,7 @@ public:
}
~ConcreteMachine() {
audio_queue_.flush();
audio_queue_.lock_flush();
}
ChangeEffect effect_for_file_did_change(const std::string &) const final {
+5 -5
View File
@@ -21,15 +21,15 @@ struct Machine {
virtual ~Machine() = default;
static std::unique_ptr<Machine> MasterSystem(const Analyser::Static::Target *, const ROMMachine::ROMFetcher &);
class Options: public Reflection::StructImpl<Options>, public Configurable::DisplayOption<Options> {
friend Configurable::DisplayOption<Options>;
class Options: public Reflection::StructImpl<Options>, public Configurable::Options::Display<Options> {
friend Configurable::Options::Display<Options>;
public:
Options(Configurable::OptionsType type) :
Configurable::DisplayOption<Options>(type == Configurable::OptionsType::UserFriendly ?
Options(const Configurable::OptionsType type) :
Configurable::Options::Display<Options>(type == Configurable::OptionsType::UserFriendly ?
Configurable::Display::RGB : Configurable::Display::CompositeColour) {}
private:
Options() : Options(Configurable::OptionsType::UserFriendly) {}
Options() : Options( Configurable::OptionsType::UserFriendly) {}
friend Reflection::StructImpl<Options>;
void declare_fields() {
+2 -2
View File
@@ -31,8 +31,8 @@ enum Key: uint16_t {
KeyEquals = 0x0700 | 0x80, KeyReturn = 0x0700 | 0x20, KeyRightShift = 0x0700 | 0x10,
KeyForwardSlash = 0x0700 | 0x08, Key0 = 0x0700 | 0x04, KeyL = 0x0700 | 0x02, Key8 = 0x0700 | 0x01,
KeyNMI = 0xfffc,
KeyJasminReset = 0xfffb,
KeyNMI = 0xfe00,
KeyJasminReset,
};
struct KeyboardMapper: public MachineTypes::MappedKeyboardMachine::KeyboardMapper {
+3 -3
View File
@@ -406,7 +406,7 @@ public:
}
~ConcreteMachine() {
audio_queue_.flush();
audio_queue_.lock_flush();
}
void set_key_state(uint16_t key, bool is_pressed) final {
@@ -679,14 +679,14 @@ public:
std::unique_ptr<Reflection::Struct> get_options() const final {
auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly);
options->output = get_video_signal_configurable();
options->quickload = use_fast_tape_hack_;
options->quick_load = use_fast_tape_hack_;
return options;
}
void set_options(const std::unique_ptr<Reflection::Struct> &str) final {
const auto options = dynamic_cast<Options *>(str.get());
set_video_signal_configurable(options->output);
set_use_fast_tape_hack(options->quickload);
set_use_fast_tape_hack(options->quick_load);
}
void set_activity_observer(Activity::Observer *observer) final {
+8 -8
View File
@@ -26,19 +26,19 @@ struct Machine {
class Options:
public Reflection::StructImpl<Options>,
public Configurable::DisplayOption<Options>,
public Configurable::QuickloadOption<Options>
public Configurable::Options::Display<Options>,
public Configurable::Options::QuickLoad<Options>
{
friend Configurable::DisplayOption<Options>;
friend Configurable::QuickloadOption<Options>;
friend Configurable::Options::Display<Options>;
friend Configurable::Options::QuickLoad<Options>;
public:
Options(Configurable::OptionsType type) :
Configurable::DisplayOption<Options>(type == Configurable::OptionsType::UserFriendly ?
Options(const Configurable::OptionsType type) :
Configurable::Options::Display<Options>(type == Configurable::OptionsType::UserFriendly ?
Configurable::Display::RGB : Configurable::Display::CompositeColour),
Configurable::QuickloadOption<Options>(type == Configurable::OptionsType::UserFriendly) {}
Configurable::Options::QuickLoad<Options>(type == Configurable::OptionsType::UserFriendly) {}
private:
Options() : Options(Configurable::OptionsType::UserFriendly) {}
Options() : Options( Configurable::OptionsType::UserFriendly) {}
friend Reflection::StructImpl<Options>;
void declare_fields() {
+5 -4
View File
@@ -34,6 +34,11 @@ VideoOutput::VideoOutput(uint8_t *memory) :
crt_.set_input_data_type(data_type_);
crt_.set_delegate(&frequency_mismatch_warner_);
update_crt_frequency();
// Prewarm CRT.
crt_.set_fixed_framing([&] {
run_for(Cycles(10'000));
});
}
void VideoOutput::register_crt_frequency_mismatch() {
@@ -42,11 +47,7 @@ void VideoOutput::register_crt_frequency_mismatch() {
}
void VideoOutput::update_crt_frequency() {
// Set the proper frequency...
crt_.set_new_display_type(64*6, crt_is_60Hz_ ? Outputs::Display::Type::PAL60 : Outputs::Display::Type::PAL50);
// ... but also pick an appropriate crop rectangle.
crt_.set_visible_area(crt_.get_rect_for_area(crt_is_60Hz_ ? 26 : 54, 224, 16 * 6, 40 * 6, 4.0f / 3.0f));
}
void VideoOutput::set_display_type(Outputs::Display::DisplayType display_type) {
+1
View File
@@ -9,6 +9,7 @@
#pragma once
#include "Outputs/CRT/CRT.hpp"
#include "Outputs/CRT/MismatchWarner.hpp"
#include "ClockReceiver/ClockReceiver.hpp"
#include <cstdint>
+1 -6
View File
@@ -103,7 +103,6 @@ private:
CRTCOutputter() :
crt(910, 8, Outputs::Display::Type::NTSC60, Outputs::Display::InputDataType::Red2Green2Blue2)
{
crt.set_visible_area(Outputs::Display::Rect(0.095f, 0.095f, 0.82f, 0.82f));
crt.set_display_type(Outputs::Display::DisplayType::RGB);
}
@@ -192,11 +191,7 @@ private:
switch(output_state) {
case OutputState::Sync: crt.output_sync(count * active_clock_divider); break;
case OutputState::Border:
if(active_border_colour) {
crt.output_blank(count * active_clock_divider);
} else {
crt.output_level<uint8_t>(count * active_clock_divider, active_border_colour);
}
crt.output_level<uint8_t>(count * active_clock_divider, active_border_colour);
break;
case OutputState::ColourBurst: crt.output_colour_burst(count * active_clock_divider, colour_phase); break;
case OutputState::Pixels: flush_pixels(); break;
+3 -3
View File
@@ -104,9 +104,9 @@ public:
return channels_[channel].address.halves.low;
}
}
}
case 0x8: return status();
case 0xd: return temporary_register();
}
case 0x8: return status();
case 0xd: return temporary_register();
}
}
+7 -7
View File
@@ -266,7 +266,7 @@ public:
// Status:
// b7 = 1 => parity error on transmission;
// b6 = 1 => receive timeout;
// b5 = 1 => transmit timeout;
// b5 = 1 => transmit timeout;
// b4 = 1 => keyboard enabled via physical key;
// b3 = 1 = data at 0060 is command, 0 = data;
// b2 = 1 = selftest OK; 0 = just powered up or reset;
@@ -328,7 +328,7 @@ private:
switch(command) {
case Command::SelfTest: return 15;
default: return 0;
default: return 0;
}
}
@@ -448,11 +448,11 @@ private:
bool has_input_ = false;
bool has_command_ = false;
// bit 7 = 0 keyboard inhibited
// bit 6 = 0 CGA, else MDA
// bit 5 = 0 manufacturing jumper installed
// bit 4 = 0 system RAM 512K, else 640K
// bit 3-0 reserved
// bit 7 = 0 keyboard inhibited
// bit 6 = 0 CGA, else MDA
// bit 5 = 0 manufacturing jumper installed
// bit 4 = 0 system RAM 512K, else 640K
// bit 3-0 reserved
uint8_t switches_ = 0b1011'0000;
int perform_delay_ = 0;
-1
View File
@@ -96,7 +96,6 @@ private:
// TODO: really this should be a Luminance8 and set an appropriate modal tint colour;
// consider whether that's worth building into the scan target.
{
crt.set_visible_area(Outputs::Display::Rect(0.028f, 0.025f, 0.98f, 0.98f));
crt.set_display_type(Outputs::Display::DisplayType::RGB);
}
+9 -9
View File
@@ -540,14 +540,14 @@ private:
case 0x0086: return dma_.pages.template page<6>();
case 0x0087: return dma_.pages.template page<7>();
case 0x0088: if(require_at(port)) return dma_.pages.template page<0x8>(); break;
case 0x0089: if(require_at(port)) return dma_.pages.template page<0x9>(); break;
case 0x008a: if(require_at(port)) return dma_.pages.template page<0xa>(); break;
case 0x008b: if(require_at(port)) return dma_.pages.template page<0xb>(); break;
case 0x008c: if(require_at(port)) return dma_.pages.template page<0xc>(); break;
case 0x008d: if(require_at(port)) return dma_.pages.template page<0xd>(); break;
case 0x008e: if(require_at(port)) return dma_.pages.template page<0xe>(); break;
case 0x008f: if(require_at(port)) return dma_.pages.template page<0xf>(); break;
case 0x0088: if(require_at(port)) return dma_.pages.template page<0x8>(); break;
case 0x0089: if(require_at(port)) return dma_.pages.template page<0x9>(); break;
case 0x008a: if(require_at(port)) return dma_.pages.template page<0xa>(); break;
case 0x008b: if(require_at(port)) return dma_.pages.template page<0xb>(); break;
case 0x008c: if(require_at(port)) return dma_.pages.template page<0xc>(); break;
case 0x008d: if(require_at(port)) return dma_.pages.template page<0xd>(); break;
case 0x008e: if(require_at(port)) return dma_.pages.template page<0xe>(); break;
case 0x008f: if(require_at(port)) return dma_.pages.template page<0xf>(); break;
case 0x03f4: return fdc_.status();
case 0x03f5: return fdc_.read();
@@ -817,7 +817,7 @@ public:
}
~ConcreteMachine() {
speaker_.queue.flush();
speaker_.queue.lock_flush();
}
// MARK: - TimedMachine.
+5 -5
View File
@@ -30,15 +30,15 @@ struct Machine {
/// Defines the runtime options [sometimes] available for a PC.
class Options:
public Reflection::StructImpl<Options>,
public Configurable::DisplayOption<Options>
public Configurable::Options::Display<Options>
{
friend Configurable::DisplayOption<Options>;
friend Configurable::Options::Display<Options>;
public:
Options(Configurable::OptionsType) :
Configurable::DisplayOption<Options>(Configurable::Display::RGB) {}
Options(const Configurable::OptionsType) :
Configurable::Options::Display<Options>(Configurable::Display::RGB) {}
private:
Options() : Options(Configurable::OptionsType::UserFriendly) {}
Options() : Options( Configurable::OptionsType::UserFriendly) {}
friend Reflection::StructImpl<Options>;
void declare_fields() {
+6 -4
View File
@@ -170,7 +170,7 @@ public:
void preauthorise_stack_read(const uint32_t size, const uint32_t granularity) {
const auto &descriptor = segments_.descriptors[InstructionSet::x86::Source::SS];
const auto trailing_distance = 65536 - registers_.sp();
const uint32_t trailing_distance = 65536 - registers_.sp();
if(trailing_distance >= size) {
descriptor.template authorise<InstructionSet::x86::AccessType::Read, uint16_t>(
uint16_t(registers_.sp()),
@@ -189,7 +189,7 @@ public:
0 // i.e. 65536
);
const auto remainder = size - trailing_distance;
const uint32_t remainder = size - trailing_distance;
descriptor.template authorise<InstructionSet::x86::AccessType::Read, uint16_t>(
0,
uint16_t(remainder)
@@ -198,10 +198,12 @@ public:
}
void preauthorise_read(const InstructionSet::x86::Source descriptor, const uint16_t offset, const uint32_t size) {
segments_.descriptors[descriptor].template authorise<InstructionSet::x86::AccessType::Read, uint16_t>(offset, offset + size);
segments_.descriptors[descriptor]
.template authorise<InstructionSet::x86::AccessType::Read, uint16_t>(offset, uint16_t(offset + size));
}
void preauthorise_write(const InstructionSet::x86::Source descriptor, const uint16_t offset, const uint32_t size) {
segments_.descriptors[descriptor].template authorise<InstructionSet::x86::AccessType::Write, uint16_t>(offset, offset + size);
segments_.descriptors[descriptor]
.template authorise<InstructionSet::x86::AccessType::Write, uint16_t>(offset, uint16_t(offset + size));
}
// TODO: perform authorisation checks.
+3 -1
View File
@@ -186,7 +186,9 @@ public:
InstructionSet::x86::SegmentRegisterSet<Descriptor> descriptors;
auto operator <=>(const Segments &rhs) const = default;
auto operator ==(const Segments &rhs) const {
return descriptors == rhs.descriptors;
}
private:
void load_real(const Source segment) {
+1 -1
View File
@@ -27,7 +27,7 @@ Video::Video() :
// Show only the centre 80% of the TV frame.
crt_.set_display_type(Outputs::Display::DisplayType::CompositeMonochrome);
crt_.set_visible_area(Outputs::Display::Rect(0.1f, 0.1f, 0.8f, 0.8f));
crt_.set_fixed_framing(Outputs::Display::Rect(0.1f, 0.1f, 0.8f, 0.8f));
}
void Video::run_for(const HalfCycles half_cycles) {
+3 -3
View File
@@ -114,7 +114,7 @@ public:
}
~ConcreteMachine() {
audio_queue_.flush();
audio_queue_.lock_flush();
}
forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
@@ -386,14 +386,14 @@ public:
std::unique_ptr<Reflection::Struct> get_options() const final {
auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly); // OptionsType is arbitrary, but not optional.
options->automatic_tape_motor_control = use_automatic_tape_motor_control_;
options->quickload = allow_fast_tape_hack_;
options->quick_load = allow_fast_tape_hack_;
return options;
}
void set_options(const std::unique_ptr<Reflection::Struct> &str) {
const auto options = dynamic_cast<Options *>(str.get());
set_use_automatic_tape_motor_control(options->automatic_tape_motor_control);
allow_fast_tape_hack_ = options->quickload;
allow_fast_tape_hack_ = options->quick_load;
set_use_fast_tape();
}
+5 -5
View File
@@ -26,17 +26,17 @@ struct Machine {
virtual bool get_tape_is_playing() = 0;
/// Defines the runtime options available for a ZX80/81.
class Options: public Reflection::StructImpl<Options>, public Configurable::QuickloadOption<Options> {
friend Configurable::QuickloadOption<Options>;
class Options: public Reflection::StructImpl<Options>, public Configurable::Options::QuickLoad<Options> {
friend Configurable::Options::QuickLoad<Options>;
public:
bool automatic_tape_motor_control = true;
Options(Configurable::OptionsType type):
Configurable::QuickloadOption<Options>(type == Configurable::OptionsType::UserFriendly),
Options(const Configurable::OptionsType type):
Configurable::Options::QuickLoad<Options>(type == Configurable::OptionsType::UserFriendly),
automatic_tape_motor_control(type == Configurable::OptionsType::UserFriendly) {}
private:
Options() : Options(Configurable::OptionsType::UserFriendly) {}
Options() : Options( Configurable::OptionsType::UserFriendly) {}
friend Reflection::StructImpl<Options>;
void declare_fields() {

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