1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-10-03 01:16:26 +00:00

Compare commits

...

774 Commits

Author SHA1 Message Date
Thomas Harte
5f661adb7f Merge pull request #750 from TomHarte/CatchupCap
Avoids trying to paper over huge gaps in running time.
2020-02-12 23:57:01 -05:00
Thomas Harte
109d072cb6 Avoids trying to paper over huge gaps in running time. Also attempts to improve SDL shutdown reliability. 2020-02-12 23:47:04 -05:00
Thomas Harte
0c1c5a0ab8 Merge pull request #749 from TomHarte/DiskIndicator
Moves ownership of drives inside Disk::Controller.
2020-02-12 23:37:32 -05:00
Thomas Harte
e01c66fd65 Implements multidrive support. 2020-02-12 23:32:01 -05:00
Thomas Harte
9f32fa7f5b Resolves potential random RAM writes at startup. 2020-02-12 23:31:48 -05:00
Thomas Harte
91a3d42919 Ensures no DMA clocking whatsoever when asleep. 2020-02-12 23:23:42 -05:00
Thomas Harte
3cb6bbf771 Uses the union of all drive statuses to determine Drive::Controller's preferred clocking. 2020-02-12 22:28:42 -05:00
Thomas Harte
452e281009 Ensures what is currently the only drive is selected. 2020-02-11 22:13:13 -05:00
Thomas Harte
3da948db52 Eliminates local drive. They're not local any more. 2020-02-11 22:12:54 -05:00
Thomas Harte
0c2f77305f Eliminates dangling printf. 2020-02-11 22:12:30 -05:00
Thomas Harte
05bcd73f82 Attempts to pull drive ownership into DiskController.
For the sake of being more intelligent as to drive clocking, hopefully. And, eventually, to support multiple drive selection.
2020-02-11 21:59:13 -05:00
Thomas Harte
654f5b0478 Merge pull request #748 from TomHarte/SDLLatency
Introduces sync matching to the SDL version.
2020-02-10 23:38:24 -05:00
Thomas Harte
886d923e30 Attempts to permit fixed speed multiplication. 2020-02-10 23:30:32 -05:00
Thomas Harte
6624cb7a78 Corrects set_ram macro to act more like a function. 2020-02-10 23:19:47 -05:00
Thomas Harte
6147134423 Introduces frame locking for SDL. 2020-02-10 23:07:09 -05:00
Thomas Harte
bf6bc7c684 Adds speed control into the SDL build.
If I can just figure out how to manipulate OpenGL from the timer thread to SDL's satisfaction, this'll be as good as it probably gets via SDL.
2020-02-09 22:27:02 -05:00
Thomas Harte
0b0a7e241b Factors out the stuff of time warping. 2020-02-09 22:11:06 -05:00
Thomas Harte
705d14259c Experimentally switches to a 'high-resolution' clock for SDL. 2020-02-09 21:44:55 -05:00
Thomas Harte
f1cd35fa16 Merge pull request #746 from TomHarte/LatencyChop
Reduces latency in macOS, improves concurrency
2020-02-09 21:07:40 -05:00
Thomas Harte
6bda4034c6 Ensures no input data is dropped when changing output rates.
I think this 'completely' deals with the problem. At least until someone wants dynamic output buffer sizes or something like that. We'll see.
2020-02-09 19:14:25 -05:00
Thomas Harte
b04daca98e Picks a safer default construction. 2020-02-09 19:13:21 -05:00
Thomas Harte
85dcdbfe9e Adopts a log prefix for the Master System. 2020-02-09 19:12:44 -05:00
Thomas Harte
24340d1d4f Resolves fetch errors. 2020-02-09 17:04:49 -05:00
Thomas Harte
6ae42d07a7 Retains existing output when switching filter coefficients.
This eliminates an issue with dynamic rate matching and throwing away the beginnings of buffers.
2020-02-09 16:42:07 -05:00
Thomas Harte
2ea1e059a8 Softens swings in emulated machine speed. 2020-02-09 16:34:13 -05:00
Thomas Harte
b5d6126a2d Avoids unnecessary filter recalculation. 2020-02-09 16:32:32 -05:00
Thomas Harte
dac217c98c Defers starting the macOS audio queue, and attempts to restart it upon packet loss.
Hopefully forever to vanish permanent audio loss?
2020-02-08 22:08:27 -05:00
Thomas Harte
c26c8992ae Reintroduces joystick support; eliminates CSBestEffortUpdater. 2020-02-08 21:27:04 -05:00
Thomas Harte
b76a5870b3 Moves drawing into the next timer tick after retrace if sync locked.
... which should mean it occurs within 1/600th of a second of announced retrace, which I assume always will be less than the retrace period. So: does the frame buffer update during retrace.

This should completely eliminate tearing for machines that can be synced to the native output rate.
2020-02-08 18:07:13 -05:00
Thomas Harte
7c0f3bb237 Gets to slightly adjusting execution speed and matching up respective vertical syncs.
I probably still need to move the ->draw inline.
2020-02-08 18:01:48 -05:00
Thomas Harte
f615d096ca Switch to obtaining refresh periods ephemerally.
Which simplifies the necessary delegate protocol.
2020-02-08 15:03:18 -05:00
Thomas Harte
09132306e4 Removes two temporary debugging steps. 2020-02-06 23:35:23 -05:00
Thomas Harte
f95b07efea Continues edging towards raster racing and/or time warping. 2020-02-06 23:35:03 -05:00
Thomas Harte
14d976eecb Starts towards an implementation of time warping. 2020-02-04 23:08:54 -05:00
Thomas Harte
e1cbad0b6d Ensures new displayLinkDelegates get a nudge with the initial display link. 2020-02-04 23:08:25 -05:00
Thomas Harte
e7410b8ed8 Uses objective clock for updates. 2020-02-04 22:24:54 -05:00
Thomas Harte
5caf74b930 Corrects typo. 2020-02-04 22:24:37 -05:00
Thomas Harte
b41920990f Moves submit step to end of line, rather than end of scan. 2020-02-04 22:15:20 -05:00
Thomas Harte
709c229cd7 Gets a bit more explicit with ScanTarget documentation. 2020-02-04 20:19:46 -05:00
Thomas Harte
01fd1b1a2e Pulls out ticks as a macro constant.
For playing.
2020-02-03 22:44:39 -05:00
Thomas Harte
96769c52f6 Prevents an endless queue of backlogged updates. 2020-02-03 22:08:07 -05:00
Thomas Harte
cf9729c74f Takes a first shot at running OpenGL work throughout a frame.
Rather than en masse at the end. But it seems I've been lazy with my threading. Work to do!
2020-02-03 21:58:29 -05:00
Thomas Harte
0f2783075f Moves responsibility for timed updates to CSMachine, which gives the CSHighPrecisionTimer a shot. 2020-02-02 21:39:20 -05:00
Thomas Harte
256f4a6679 Fixes -invalidate: cancel the dispatch source, don't just suspend it, and wait until that is done. 2020-02-02 21:29:22 -05:00
Thomas Harte
0310f94f0c Merge pull request #745 from TomHarte/STMonochrome
Adds some amount of 1bpp/72Hz output support for the Atari ST.
2020-02-02 17:46:36 -05:00
Thomas Harte
085529ed72 Makes the shifter behaviour conform to its documentation. 2020-02-02 17:26:39 -05:00
Thomas Harte
8aabf1b374 Allows receivers of nullptr from begin_data to output any quantity of data. 2020-02-01 21:43:48 -05:00
Thomas Harte
ff39f71ca0 Eliminates meaningless constants from the Macintosh video's CRT setup. 2020-01-30 23:29:04 -05:00
Thomas Harte
019474300d Centralises responsibility for picking irrelevant numbers for a computer-style monitor. 2020-01-30 23:26:02 -05:00
Thomas Harte
af976b8b3d Eliminates modulus operation per ROM access. 2020-01-30 23:09:24 -05:00
Thomas Harte
f3db1a0c60 Eliminates ad hoc scheduling for delayed DE -> LOAD. 2020-01-29 22:50:22 -05:00
Thomas Harte
ce28213a5e [Mostly] unifies deferral process. 2020-01-29 22:46:08 -05:00
Thomas Harte
f9ce50d2bb Adds some debugging `asserts. 2020-01-29 22:45:44 -05:00
Thomas Harte
ee16095863 Withdraws advance_to_next; once it has to cope with simultaneous events it stops being faster than advance.
I could possibly try to deal with those at insertion time, but it'd get messy.
2020-01-29 22:45:10 -05:00
Thomas Harte
f0a6e0f3d5 Splits out the queue management stuff from queue+action.
Temporarily breaks ST video in the endeavour.
2020-01-29 22:18:41 -05:00
Thomas Harte
8c4fb0f688 Extends the DeferredQueue to allow out-of-order enqueing. 2020-01-29 21:49:52 -05:00
Thomas Harte
baa51853c4 Introduces RealTimeActor, providing the same interface as JustInTimeActor. 2020-01-29 21:26:15 -05:00
Thomas Harte
0e29c6b0ab On further reflection, I think events should occur after the running period.
I'm testing this now for sanity in 2/4bpp mode.
2020-01-28 23:26:37 -05:00
Thomas Harte
1b27eedf6b Ensure this can definitely never divide by 0. 2020-01-28 23:25:21 -05:00
Thomas Harte
8b1f183198 Reduce test duration much closer to two frames. 2020-01-28 23:25:01 -05:00
Thomas Harte
4766ec55fe Documents units. 2020-01-28 23:23:51 -05:00
Thomas Harte
c5edc879b6 Switches back to testing the monochrome monitor. 2020-01-28 22:12:57 -05:00
Thomas Harte
65309e60c4 Corrects sequence point generation by allowing for hsync_end != end of line. 2020-01-28 20:38:20 -05:00
Thomas Harte
5c4623e9f7 Adds a sequence-point test for 72Hz mode.
Which immediately appears to trigger the hsync issue I'm also seeing in manual testing.
2020-01-28 20:27:24 -05:00
Thomas Harte
2c0cab9e4d Adds line length latching as a line event. 2020-01-28 20:22:37 -05:00
Thomas Harte
d0117556d1 Reintroduces CSHighPrecisionTimer. 2020-01-28 20:09:46 -05:00
Thomas Harte
b1ff031b54 Fixes runtime test. 2020-01-27 23:41:08 -05:00
Thomas Harte
7e8405e68a Makes 72Hz horizontal sync independently relocatable.
... and moves and shortens it, based on my guesswork as to requirements.
2020-01-27 23:40:01 -05:00
Thomas Harte
c8fd00217d Resolves loss of horizontal resolution in 1bpp mode. 2020-01-27 23:08:28 -05:00
Thomas Harte
9d340599a6 Towards ST 1bpp support: puts vsync in an appropriate location, starts experimenting with proper CRT timings. 2020-01-27 23:00:30 -05:00
Thomas Harte
8e094598ca Merge pull request #744 from TomHarte/CRCTemplate
Better templates the CRC generator.
2020-01-27 21:54:32 -05:00
Thomas Harte
189122ab84 Fixes test units. 2020-01-27 20:35:58 -05:00
Thomas Harte
4b53f6a9f0 Renames T to the more-communicative IntType, adds some explicit constexpra. 2020-01-27 08:28:20 -05:00
Thomas Harte
561e149058 Better templates the CRC generator. 2020-01-27 00:03:01 -05:00
Thomas Harte
5975fc8e63 Merge pull request #738 from TomHarte/FinalOverride
Switches to [just] `final` where relevant to mark overrides.
2020-01-26 23:50:05 -05:00
Thomas Harte
7316a3aa88 Merge branch 'master' into FinalOverride 2020-01-26 23:42:25 -05:00
Thomas Harte
50be991415 Merge pull request #743 from TomHarte/macOSScreens
Ensures macOS window can properly be dragged between screens, Retina or not.
2020-01-26 23:33:50 -05:00
Thomas Harte
52e49439a6 Recreates display link upon a screen change.
Different screens may have different refresh rates, and I can find no guarantees about how Apple handles that.
2020-01-26 23:23:33 -05:00
Thomas Harte
6bcdd3177d Ensures that a change of screen issues a reshape. Just in case.
Thereby resolves display mis-sizing when dragging from a Retina display to a regular one, or vice versa.
2020-01-26 18:04:25 -05:00
Thomas Harte
83dbd257e1 Merge pull request #742 from TomHarte/SpeedMultiplier
Adds the option to run machines at a multiple of their real speeds.
2020-01-26 13:30:43 -05:00
Thomas Harte
b514756272 Adds the option to run machines at a multiple of their real speeds.
Exposed to SDL users only, for now.
2020-01-26 13:25:23 -05:00
Thomas Harte
7e4c13c43e Merge pull request #741 from TomHarte/ZX80RateReporting
Corrects ZX80, ZX81 and Amstrad CPC scan status scales.
2020-01-26 11:42:54 -05:00
Thomas Harte
79bb0f8222 Updates comment. 2020-01-26 11:36:06 -05:00
Thomas Harte
43bf6aca67 Corrects reported scan status for the Amstrad CPC. 2020-01-25 23:46:18 -05:00
Thomas Harte
03d23aad41 Corrects reported ZX80/81 scan status. 2020-01-25 23:27:09 -05:00
Thomas Harte
c398aa60c1 Merge pull request #739 from TomHarte/SDLThreadSafety
Resolves thread safety issues in SDK kiosk mode.
2020-01-25 14:56:29 -05:00
Thomas Harte
9666193c67 Pulls the call to .update out of the critical section. 2020-01-25 14:50:28 -05:00
Thomas Harte
3f57020b00 Resolves thread safety oversights in SDK kiosk mode. 2020-01-25 14:48:00 -05:00
Thomas Harte
294e09f275 All these 'override's can be 'final's.
At least for the purpose of being communicative. I doubt there's much to gain in terms of compiler output — the DiskImageHolder can avoid some virtual lookups but nothing else leaps out.
2020-01-23 22:57:51 -05:00
Thomas Harte
ba516387ba In all these instances, final => override. So no need to repeat myself. 2020-01-23 22:35:39 -05:00
Thomas Harte
2103e1b470 Merge pull request #737 from TomHarte/Multisync
Adds multisync monitor support to the Oric.
2020-01-23 22:20:34 -05:00
Thomas Harte
7bac439e95 Adds, and comments out, a useful temporary piece of debugging logging. 2020-01-23 22:14:22 -05:00
Thomas Harte
9136917f00 Enables the Oric for 50/60Hz mode switching, inventing PAL60 for the purpose. 2020-01-23 22:14:02 -05:00
Thomas Harte
6802318784 Removes audio_queue_.flush() calls; I don't think I really need to block. At least, not usually. 2020-01-23 20:13:16 -05:00
Thomas Harte
428d141bc9 Factors out the logic behind the Atari 2600's frequency switching. 2020-01-23 20:12:44 -05:00
Thomas Harte
a86fb33789 Ensures that the ColecoVision, MSX and Master System fully flush. 2020-01-22 22:57:16 -05:00
Thomas Harte
beefb70f75 Adds vertical sync as something that can be run_until. 2020-01-22 22:20:56 -05:00
Thomas Harte
3c6a00dc3c Breaks a potential deadlock. 2020-01-22 22:10:20 -05:00
Thomas Harte
8404409c0d Causes the Atari 2600 to obey normal flush semantics.
This stuff is going to become more important with run_until.
2020-01-22 22:05:51 -05:00
Thomas Harte
a5f285b4ce Enhances reported data. 2020-01-22 22:01:17 -05:00
Thomas Harte
9d97a294a7 Corrects the TMS' get_scaled_scan_status.
I think all platforms are now returning credible numbers.
2020-01-22 19:34:10 -05:00
Thomas Harte
56448373ae Splits one line into two, for the benefit of step debugging. 2020-01-22 19:32:23 -05:00
Thomas Harte
a71c5946f0 Ensures proper manipulation of scan_statuses, leading to the correct result out of a CRTMachine.
Possibly with the exception of the TMS, as I appear to have uncovered an unrelated issue there.
2020-01-21 22:28:25 -05:00
Thomas Harte
e7fff6e123 Minor step towards correct answers: divide by time_multiplier_. 2020-01-20 22:33:51 -05:00
Thomas Harte
82e5def7c4 Implements get_scan_status, but for scale being incorrect. 2020-01-20 21:58:34 -05:00
Thomas Harte
d97a073d1b Adds the necessary routine for all machines to be able to respond to get_scan_status.
They all just as the CRT, as all are currently based on the CRT. Which doesn't currently know the total clock rate it would need to in order properly to scale the answer to the question. Further thought coming.
2020-01-20 21:45:10 -05:00
Thomas Harte
e74f37d6ed Merge pull request #736 from TomHarte/RunUntil
Implements a nascent `run_until`
2020-01-20 17:48:13 -05:00
Thomas Harte
3aa2c297a2 Adds feedback to the best-effort updater; enables the Cocoa port for audio event requests. 2020-01-20 17:38:25 -05:00
Thomas Harte
290db67f09 Adds a forward route for event flags. Doesn't yet account for extra time expended. 2020-01-20 17:09:01 -05:00
Thomas Harte
4de121142b Adds a flags parameter to the BestEffortUpdater delegate.
On the Cocoa side, cuts Swift out of the update loop, as that seems merely to add code.
2020-01-20 16:21:53 -05:00
Thomas Harte
3c760e585a Switches to accepting a bit mask of events to run_until. 2020-01-20 16:08:21 -05:00
Thomas Harte
8adb2283b5 Rewrites the best-effort updater to try to get better thread affinity. 2020-01-20 13:38:46 -05:00
Thomas Harte
cb61e84868 Starts building out higher-level run_until functionality.
Specifically: you can now run until the next set of speaker samples has been delivered.
2020-01-20 12:12:23 -05:00
Thomas Harte
8349005c4b Adds CRTMachine::run_until, which will run until a condition is true.
I want to get to being able to say "run until the beam is 60% of the way down", "run until a new packet of audio has been delivered", etc.
2020-01-19 23:52:47 -05:00
Thomas Harte
a2847f4f8e Merge pull request #735 from TomHarte/Numeric
Eliminates homegrown factoring code
2020-01-19 23:32:50 -05:00
Thomas Harte
add3ebcb44 Updates Xcode project. 2020-01-19 23:23:44 -05:00
Thomas Harte
98daad45c7 Removers Factors.hpp; now this is a C++17 project. 2020-01-19 23:18:59 -05:00
Thomas Harte
1b4b6b0aee Renames: NumberTheory -> Numeric. 2020-01-19 23:14:35 -05:00
Thomas Harte
8f94da9daf Merge pull request #734 from TomHarte/FuzzyBits
Adds PCMSegementEventSource support for 'fuzzy' bits
2020-01-19 21:48:07 -05:00
Thomas Harte
357137918d Adds fuzzy but marking through the GetTrackWithSectors interface. 2020-01-19 21:41:10 -05:00
Thomas Harte
b0f7b762af Adds a possible const. 2020-01-19 21:40:30 -05:00
Thomas Harte
da3ee381f4 Attempts a full wiring up of fuzzy bits. 2020-01-19 21:20:21 -05:00
Thomas Harte
d27d14d2b0 Supplies fuzzy masks where specified. 2020-01-19 21:08:49 -05:00
Thomas Harte
b0326530d6 Allows fuzzy masks to be fed into the FM and MFM encoders. 2020-01-19 21:08:15 -05:00
Thomas Harte
c2bd5be51a This seems to be the proper interpretation of speeds? 2020-01-19 20:42:51 -05:00
Thomas Harte
84f5feab70 Properly flags up overloads. 2020-01-19 20:37:54 -05:00
Thomas Harte
4b2c68c3d3 Documents next. 2020-01-19 20:32:58 -05:00
Thomas Harte
5391a699a4 Adds the ability for a PCMSegment to maintain 'fuzzy' (i.e. random) bits. Implements an LFSR for bit generation.
I'm not necessarily happy with the idea of just shoving in a [pseudo-]random number generator in rather than emulating the proper process underneath, but for now I throw my arms up.
2020-01-19 20:09:11 -05:00
Thomas Harte
f3f8345e5e Corrects spelling mistake. 2020-01-19 20:05:52 -05:00
Thomas Harte
c755411636 Slightly improves comments. 2020-01-19 20:05:22 -05:00
Thomas Harte
f02759b76b Merge pull request #733 from TomHarte/STXTiming
Adds support for STX speed zones.
2020-01-19 12:41:31 -05:00
Thomas Harte
f34ddce28f Adds support for STX speed zones. 2020-01-19 12:38:33 -05:00
Thomas Harte
50348c9fe7 Adds the ability to substitute a target during encoding. 2020-01-19 12:11:56 -05:00
Thomas Harte
3bfeebf2a1 Merge pull request #732 from TomHarte/TraceFlag
Improves 68000 trace support
2020-01-18 23:17:21 -05:00
Thomas Harte
dca79ea10e Requires trace flag currently set. 2020-01-18 22:52:53 -05:00
Thomas Harte
b7fd4de32f Ensures a one-instruction latency on the trace flag. 2020-01-18 22:06:00 -05:00
Thomas Harte
78d08278ed Merge pull request #731 from TomHarte/ShifterSync
Improves STX track locating, plus minor WD emulation improvements
2020-01-18 14:59:07 -05:00
Thomas Harte
d4be052e76 Switch to matching fragments. 2020-01-18 14:18:59 -05:00
Thomas Harte
d674fd0e67 The WD uses only the low two bits for sector size. 2020-01-18 13:40:50 -05:00
Thomas Harte
229b7b36ed Merge branch 'master' into ShifterSync 2020-01-18 13:38:56 -05:00
Thomas Harte
8a8b8db5d1 Merge pull request #729 from TomHarte/JasminLED
Corrects Jasmin activity light.
2020-01-16 23:16:55 -05:00
Thomas Harte
d30f83871d Corrects Jasmin activity light. 2020-01-16 22:59:43 -05:00
Thomas Harte
1422f8a93a Merge pull request #728 from TomHarte/HardenedRuntime
Opts in for the hardened macOS runtime.
2020-01-16 22:27:58 -05:00
Thomas Harte
f0da75f8e9 Opts in for the hardened macOS runtime.
Seemingly with no ill effects.
2020-01-16 22:18:18 -05:00
Thomas Harte
cb8a7a4137 Merge pull request #727 from TomHarte/RDYs
Adds emulation of non-default types of floppy drive RDY output
2020-01-16 22:07:41 -05:00
Thomas Harte
efd684dc56 Opts the BD-500 in for modified Shugart RDY.
Hopefully this is correct. I'm presently mystified as to other options.
2020-01-16 21:34:57 -05:00
Thomas Harte
aeac6b5888 Allows the type of RDY signal to be specified. 2020-01-16 21:34:48 -05:00
Thomas Harte
9bb294a023 Merge pull request #726 from TomHarte/BD-DOS
Implements highly-provisional Byte Drive 500 support for the Oric.
2020-01-16 00:09:34 -05:00
Thomas Harte
1972ca00a4 Fixes quick-NTSC-avoidance fix.
I suspect this is very temporary, but here it is.
2020-01-16 00:01:16 -05:00
Thomas Harte
6a185a574a Adds the BD-500 to the Mac GUI. 2020-01-15 23:56:56 -05:00
Thomas Harte
c606931c93 Ensures a safe default-selected drive. 2020-01-15 23:56:44 -05:00
Thomas Harte
93cecf0882 Ensures no possible initial NTSC, removes printfs. 2020-01-15 23:47:45 -05:00
Thomas Harte
aac3d27c10 Adds activity indicators for the BD-500 and Jasmin.
Also slightly cleans up DiskController a little further.
2020-01-15 23:39:15 -05:00
Thomas Harte
99122efbbc Adds a slight cool-down period on end-of-rotation.
Along with the corresponding inactive transition of the ready signal.
2020-01-15 23:29:52 -05:00
Thomas Harte
30e856b9e4 Renames motor_is_on_ to motor_input_is_on_ to start to disambiguate the two things. 2020-01-15 23:16:25 -05:00
Thomas Harte
91fae86e73 Factors out paging, implements a bit more of the BD500.
That is, enough seemingly fully to work, if I force the drive to report ready.
2020-01-15 23:15:39 -05:00
Thomas Harte
f5c194386c Ties head load to ready.
BD-DOS no longer perpetually retries.
2020-01-14 23:45:36 -05:00
Thomas Harte
98f7662185 Force BASIC 1.0 for the BD-500. 2020-01-14 23:33:52 -05:00
Thomas Harte
62c3720c97 Adds status register and shout-outs on other address access. 2020-01-14 23:24:11 -05:00
Thomas Harte
6b08239199 Adapts slightly; it would seem that BD-DOS disks really fill up space. 2020-01-14 23:16:06 -05:00
Thomas Harte
f258fc2971 Adds enough of a BD500 for the boot sector seemingly to load. 2020-01-14 23:15:27 -05:00
Thomas Harte
6b84ae3095 Makes the Microdisc also a DiskController, and simplifies delegate interface. 2020-01-14 22:53:27 -05:00
Thomas Harte
5dd8c677f1 Factors out from the Jasmin the stuff that I'm going to need to repeat for the BD-500. 2020-01-14 22:23:00 -05:00
Thomas Harte
1cbcd5355f Adds a detector and enumerated Byte Drive 500 disk interface type. 2020-01-14 21:55:04 -05:00
Thomas Harte
9799250f2c Updates to mention the Jasmin's ROM and list the BD-DOS. 2020-01-14 21:54:37 -05:00
Thomas Harte
ecb5807ec0 Enssures STX interprets sector sizes correctly. 2020-01-14 21:35:37 -05:00
Thomas Harte
942986aadc Insures against badly-placed locations. 2020-01-13 22:49:12 -05:00
Thomas Harte
1f539822ee Adds better support for WD-esque false sync, improves STX track patching. 2020-01-13 22:19:48 -05:00
Thomas Harte
fab35b360a Ensure an encoder is created even if no sectors are placed. 2020-01-12 22:37:00 -05:00
Thomas Harte
80fcf5b5c0 Merge pull request #724 from TomHarte/STX2
Adds some support for the STX file format.
2020-01-12 22:28:50 -05:00
Thomas Harte
b3b2e18c4b Ensures head and track counts are reported accurately. 2020-01-12 22:23:34 -05:00
Thomas Harte
2d233b6358 Makes a more concrete attempt at track/sector combination. 2020-01-12 22:18:31 -05:00
Thomas Harte
83ed36eb08 Add missing #include. 2020-01-12 17:56:04 -05:00
Thomas Harte
89f4032ffc Merge branch 'master' into STX2 2020-01-12 17:55:19 -05:00
Thomas Harte
8c90ec4636 Merge pull request #725 from TomHarte/FasterDPLL
Improve DPLL implementation.
2020-01-12 17:54:55 -05:00
Thomas Harte
514141f8c5 Eliminates the optionality of a DPLL receiver. 2020-01-12 17:45:02 -05:00
Thomas Harte
8e3a618619 Corrects Mac build, shrinks default history [back] to 3 slots. 2020-01-12 17:33:34 -05:00
Thomas Harte
6df6af09de Remove dead .cpp. 2020-01-12 17:25:59 -05:00
Thomas Harte
f42655a0fc Promote DigitalPhaseLockedLoop to a template, simplify to O(1) add_pulse. 2020-01-12 17:25:21 -05:00
Thomas Harte
f81a7f0faf Ensures prefixes are MFM encoded and decoded. 2020-01-11 22:10:41 -05:00
Thomas Harte
2b4c924399 Makes an effort to locate address and data bodies within track.
"Not completely successful" would be the polite term.
2020-01-09 23:28:07 -05:00
Thomas Harte
64517a02b7 Adds code to deal with sector-free tracks. 2020-01-09 21:50:32 -05:00
Thomas Harte
b4befd57a9 Advances to being able to cope with STXs with no special features whatsoever.
Well, other than perhaps a broken data CRC. Fuzzy bits, timing differences and the stuff between sectors are all currently absent.
2020-01-09 21:03:01 -05:00
Thomas Harte
2c742a051e Merge pull request #723 from TomHarte/LSLTiming
Introduces a timing test for LSL. Which already passes.
2020-01-08 22:43:50 -05:00
Thomas Harte
6595f8f527 Introduces a timing test for LSL. Which already passes. 2020-01-08 22:35:28 -05:00
Thomas Harte
985b36da73 Starts towards STX support. 2020-01-07 23:21:32 -05:00
Thomas Harte
cdb31b1c2b Merge pull request #721 from TomHarte/AYZero
Ensures programmatic AY volume level 0 is completely off.
2020-01-05 22:50:42 -05:00
Thomas Harte
6a44936a7c Ensures programmatic volume level 0 is completely off. 2020-01-05 22:44:52 -05:00
Thomas Harte
45afb13a54 Merge pull request #720 from TomHarte/Jasmin
Adds emulation of the Oric's Jasmin disk interface.
2020-01-05 22:13:11 -05:00
Thomas Harte
3ced31043a Makes Jasmin autoboot optional, adds a Jasmin reset key, adds the Jasmin to File -> New... .
Also finally implements KeyNMI.
2020-01-05 21:57:33 -05:00
Thomas Harte
7361e7ec34 Fixed: the issue was failing to propagate motor control.
Also it seems to be incorrect to have the Jasmin paged at initial boot.
2020-01-05 21:35:20 -05:00
Thomas Harte
533729638c It seems like Jasmin paged in at boot, and button = page back in and reset works?
At least, that gets me a 'boot failed' error. Which is something.
2020-01-05 20:34:15 -05:00
Thomas Harte
9f30be1c13 Attempts to implement most of a Jasmin disk interface.
With one obvious omission: there's no way to start it? The real interface had a dedicated button, but I don't yet know what that button did. Research needed.
2020-01-05 20:05:55 -05:00
Thomas Harte
09289f383d Makes an effort to detect Jasmin disks, and flags the target if found.
It'll try the Jasmin only if the Microdisc check fails; this is because the latter is preferable — it automatically boots, and is much better tested in Clock Signal terms.
2020-01-05 18:44:58 -05:00
Thomas Harte
20b25ce866 Merge pull request #719 from TomHarte/CleanUps
Standardises on `read` and `write` for bus accesses.
2020-01-05 13:59:02 -05:00
Thomas Harte
c1bae49a92 Standardises on read and write for bus accesses.
Logic being: name these things for the bus action they model, not the effect they have.
2020-01-05 13:40:02 -05:00
Thomas Harte
b3f806201b Merge pull request #718 from TomHarte/BusErrorStack
Adds a test and fixes for the bus error stack frame.
2020-01-05 00:08:53 -05:00
Thomas Harte
9f2f547932 Adds and satisfies test on the function code word.
Thanks to ijor's "68000 Address and Bus Error Stack Frame" re: contents.
2020-01-04 23:58:07 -05:00
Thomas Harte
f0d5bbecf2 Introduces a test of stack contents after an address error.
Fixes: stacked PC, address of fault.
2020-01-04 23:22:07 -05:00
Thomas Harte
3d7ef43293 Merge pull request #717 from TomHarte/JSRA7
Fixes A7-relative JSRs.
2020-01-04 22:29:04 -05:00
Thomas Harte
4578b65487 Merge pull request #716 from TomHarte/PartialDecoding
Clarifies IO decoding, adds YM mirrors.
2020-01-04 22:23:22 -05:00
Thomas Harte
a28c52c250 Fixes A7-relative JSRs.
I completely withdraw my earlier statement re: the test cases.
2020-01-04 22:22:33 -05:00
Thomas Harte
e4349f5e05 Slightly clarifies logic. 2020-01-04 21:32:34 -05:00
Thomas Harte
7b2777ac08 Sorts cases into order; adds copious audio mirrors. 2020-01-04 21:06:21 -05:00
Thomas Harte
0fbcbfc61b Switches to more idiomatic address listing. 2020-01-04 20:35:47 -05:00
Thomas Harte
3ab4fb8c79 Enables an assumption of partial address decoding at the ACIA and PSG. 2020-01-04 17:27:55 -05:00
Thomas Harte
42a9585321 Merge pull request #715 from TomHarte/TestsRedux
After rerunning all tests, adds some notes on questionable results.
2020-01-04 16:41:01 -05:00
Thomas Harte
937cba8978 After rerunning all tests, adds some notes on questionable results.
Also renames a file. But no code changes are currently suggested, at least until I can learn more about DIVU/DIVS.
2020-01-04 16:31:45 -05:00
Thomas Harte
627d3c28ea Merge pull request #714 from TomHarte/STJoystick
Adds Joystick key code mode, ensures events aren't posted in interrogation mode.
2020-01-04 10:01:57 -05:00
Thomas Harte
19ddfae6d6 Adds Joystick key code mode, ensures events aren't posted in interrogation mode.
This should fix Turrican due to the latter change; I'm not aware of software that uses the former.
2020-01-04 09:45:59 -05:00
Thomas Harte
56ebd08af0 Merge pull request #713 from TomHarte/MULUS
Adds DIV and MUL tests, correcting some DIV flags.
2020-01-04 09:22:03 -05:00
Thomas Harte
7de1181213 Make a new guess at post-overflow DIV flags, based on tests.
Specifically: for DIVU, stick with the current guess of a fixed set. For DIVS, leave N and Z alone.
2020-01-03 23:44:49 -05:00
Thomas Harte
c7a5b054db There's no TODO here; overflow is always 0 for a 16x16 multiply.
... and the original 68000 doesn't support 32x32 multiplies.
2020-01-03 22:44:19 -05:00
Thomas Harte
ca12ba297b Renames all files that test multiple opcodes; introduces DIV and MUL tests. 2020-01-03 22:43:24 -05:00
Thomas Harte
7abf527084 Merge pull request #712 from TomHarte/MercsTweaks
Corrects vsync placement and BPP-change pipeline flushing.
2020-01-02 23:50:30 -05:00
Thomas Harte
c0b5bfe726 Ensure no possible return without value. 2020-01-02 23:43:53 -05:00
Thomas Harte
414b0cc234 Reintroduces sync write delay. 2020-01-02 23:36:11 -05:00
Thomas Harte
134e828336 Updates note to self. 2020-01-02 23:33:35 -05:00
Thomas Harte
455e831b87 Corrects bug whereby changing pixel mode mid-line will produce an improper amount of data. 2020-01-02 23:18:21 -05:00
Thomas Harte
617e0bada9 Adds some minor extra testing. Highly duplicative, to be honest. 2020-01-02 23:14:05 -05:00
Thomas Harte
7dea99b1cc Update comment, for sense. 2020-01-02 23:13:12 -05:00
Thomas Harte
42ccf48966 Judging by Pompey Pirates Menu 88, vsync should occur a line earlier, ending during line 0. 2020-01-02 20:16:28 -05:00
Thomas Harte
2f8078db22 Switches to should_log as a global when I'm hacking about. 2020-01-02 20:15:48 -05:00
Thomas Harte
ea45ae78d1 Merge pull request #711 from TomHarte/MoreTests
Introduces further comparative tests, prompting a new CHK fix.
2020-01-01 20:12:07 -05:00
Thomas Harte
cb7d6c185c Further expands test coverage. 2020-01-01 20:00:37 -05:00
Thomas Harte
5be30b1f7b Introduces further comparative tests, prompting a new CHK fix.
Specifically: how to set N when both is_under and is_over are true, and to eliminate a failure fully to prefetch in the longer addressing modes.
2020-01-01 19:11:36 -05:00
Thomas Harte
0bf1a87f4c Merge pull request #710 from TomHarte/STOP
Ensure that an interrupt from a STOP doesn't return to the STOP.
2020-01-01 15:00:11 -05:00
Thomas Harte
b184426f2b Ensure that an interrupt from a STOP doesn't return to the STOP. 2020-01-01 14:51:47 -05:00
Thomas Harte
2456fb120d Merge pull request #709 from TomHarte/MoreMouse
Adds Atari ST mouse support for absolute positioning and inverted scales.
2020-01-01 13:56:25 -05:00
Thomas Harte
23ed9ad2de Corrects application of negative relative scale. 2020-01-01 13:22:21 -05:00
Thomas Harte
017681a97c Now honours permitted mouse range. 2020-01-01 12:48:38 -05:00
Thomas Harte
153f60735d Banishes redefined macro warning. 2020-01-01 12:38:30 -05:00
Thomas Harte
90b899c00e Attempts to implement absolute mouse positioning mode.
Along with mouse direction.
2020-01-01 12:29:33 -05:00
Thomas Harte
5ce8d7c0e5 Merge pull request #708 from TomHarte/KeyboardLogs
Adds necessary logging for further IKYB work.
2019-12-30 23:41:38 -05:00
Thomas Harte
c11fe25537 Merge branch 'master' into EnchantedWoods 2019-12-30 23:32:45 -05:00
Thomas Harte
c4edd635c5 Merge pull request #707 from TomHarte/STGraphicsAgain
Further improves the ST graphics subsystem
2019-12-30 23:31:21 -05:00
Thomas Harte
0a12893d63 Shunts vsync back down to top of frame.
It's guess after guess, basically.
2019-12-30 23:01:31 -05:00
Thomas Harte
8e777c299f Switches to latching video interrupts until acknowledged.
Seems to fix Cisco Heat, at least. I have no idea whether I'm latching the correct thing, whether IACK should clear both or only one, etc.
2019-12-30 23:00:55 -05:00
Thomas Harte
09513ec14c Gets explicit about constexpr expectations here. 2019-12-30 22:58:19 -05:00
Thomas Harte
e23d1a2958 Restores vsync active. 2019-12-29 22:03:36 -05:00
Thomas Harte
6449403f6a Corrects pending_events_ test for sequence points.
Simplifies around as possible.
2019-12-29 21:53:45 -05:00
Thomas Harte
c8fe66092b Attempts to correct insertion logic (and mostly bypasses it). 2019-12-29 21:42:41 -05:00
Thomas Harte
b33218c61e Fixes reload test, which really needs to sense the CRT-headed vsync output.
i.e. not the one heading back to the CPU.
2019-12-29 20:55:34 -05:00
Thomas Harte
8ce26e7182 Adds a delay on vsync visibility (i.e. as to generating an interrupt). 2019-12-29 19:03:08 -05:00
Thomas Harte
47068ee081 Ensures visible hsync end generates a sequence point. 2019-12-29 17:51:50 -05:00
Thomas Harte
5361ee2526 Adds specific Union Demo test. 2019-12-29 17:48:43 -05:00
Thomas Harte
214b6a254a Adds a delay on visibility of the hsync signal, and a test on address reload. 2019-12-29 17:37:09 -05:00
Thomas Harte
93f6964d8a Introduces some preliminary line length unit tests.
Thereby fixes one potential issue with load_ toggling.
2019-12-28 22:50:34 -05:00
Thomas Harte
13f11e071a Simplifies border colour change propagation.
I'm not sure it was even technically correct as was.
2019-12-28 10:45:10 -05:00
Thomas Harte
f7825dd2a2 Pulls out address reload position as a separate constant. 2019-12-28 10:36:50 -05:00
Thomas Harte
a9d1f5d925 Pulls out address reload as something I can position independently.
Sadly receding it by 3 did not have the effect I was hoping for, of receding Enchanted Land's first register tweaking.
2019-12-27 23:47:19 -05:00
Thomas Harte
2757e5d600 Removes untrue comment. 2019-12-27 22:51:11 -05:00
Thomas Harte
5026de9653 Rejigs the video stream to ensure shifter really is continuous.
... and definitively to avoid potential buffer overruns. Or, at least, to have a mechanism in place definitively to avoid them. Which will be tested and debugged as necessary.

Also simplifies the colour burst and border/pixels selection logic.
2019-12-27 22:47:34 -05:00
Thomas Harte
5fa8e046d8 It's inaccurrate to call this _the_ shifter. So don't. 2019-12-27 19:03:10 -05:00
Thomas Harte
ec9357e080 Merge pull request #706 from TomHarte/Vic20Flags
Permits Vic-20 memory to be specified in banks;
2019-12-26 23:04:56 -05:00
Thomas Harte
f8dd33b645 Adds necessary header for strcmp. 2019-12-26 22:53:09 -05:00
Thomas Harte
de43e86310 Permits Vic-20 memory to be specified in banks; adds recognition of TheC64-style file tags to specify them. 2019-12-26 22:49:48 -05:00
Thomas Harte
314973a5ef Merge pull request #704 from TomHarte/RTR
Corrects implementation of RTR
2019-12-25 20:47:21 -05:00
Thomas Harte
d26ce65236 Introduces an RTR test. 2019-12-25 19:50:12 -05:00
Thomas Harte
1de4f179c0 Adds more thorough comment on the bus program used. 2019-12-25 19:49:49 -05:00
Thomas Harte
3cb5684d95 Fixes RTR: the whole top half of the SR should be preserved.
Specifically, the 68000 Reference Manual says: "The supervisor portion of the status register is unaffected." Clearly when I first read that I misread it as the supervisor _flag_ (rather than _portion_) should be preserved.
2019-12-25 19:49:20 -05:00
Thomas Harte
a9a92de954 Adds a bunch of shout-outs for unimplemented behaviour. 2019-12-25 15:32:33 -05:00
Thomas Harte
daacd6805e Merge pull request #703 from TomHarte/Sup133
Fixed: the final track field in an MSA is inclusive, not exclusive.
2019-12-24 23:30:04 -05:00
Thomas Harte
54fe01b532 Fixed: the final track is inclusive, not exclusive. 2019-12-24 23:08:16 -05:00
Thomas Harte
42dd70dbff Merge pull request #702 from TomHarte/NZStory
Corrects WD track-zero and write-protect flags.
2019-12-24 22:22:24 -05:00
Thomas Harte
e59de71d79 Disables status logging, at least until next needed. 2019-12-24 21:44:50 -05:00
Thomas Harte
a8ba3607b7 Adds (and disables) a minor additional piece of logging. 2019-12-24 21:43:39 -05:00
Thomas Harte
4205e95883 Switches to capture of the track 0 flag during a type 1 operation. 2019-12-24 21:43:20 -05:00
Thomas Harte
f633cf4c3f Adds a basic implementation of the non-instantaneous index pulse. 2019-12-24 21:05:17 -05:00
Thomas Harte
dfa6b11737 Adds responsibility for an ongoing index pulse to the drive. 2019-12-24 20:53:37 -05:00
Thomas Harte
42926e72cc Adjusted: Flag::WriteProtect works in real time for a type-1 status. 2019-12-24 19:57:12 -05:00
Thomas Harte
80cb06eb33 It provisionally seems as though spin_up should be reset by a force interrupt? 2019-12-24 19:37:37 -05:00
Thomas Harte
5068328a15 Fixes debugging output. 2019-12-24 19:15:58 -05:00
Thomas Harte
adc2b77833 Enhances with constexpr. 2019-12-24 18:53:50 -05:00
Thomas Harte
99415217dc Merge pull request #701 from TomHarte/TestsSyntax
Corrects syntax errors in test suite.
2019-12-23 22:15:16 -05:00
Thomas Harte
48d519d475 Merge branch 'master' into TestsSyntax 2019-12-23 22:13:55 -05:00
Thomas Harte
ed831e5912 Fixes test syntax errors. 2019-12-23 22:13:25 -05:00
Thomas Harte
1db7c7989b Merge pull request #700 from TomHarte/NoNew
Embraces std::make_[unique/shared] in place of .reset(new .
2019-12-23 22:05:57 -05:00
Thomas Harte
b2bed82da6 Switches to standard logging. 2019-12-23 22:00:40 -05:00
Thomas Harte
afae1443b4 Merge branch 'master' into NoNew 2019-12-23 21:32:17 -05:00
Thomas Harte
0dae608da5 Embraces std::make_[unique/shared] in place of .reset(new . 2019-12-23 21:31:46 -05:00
Thomas Harte
8a1fe99fa4 Merge pull request #699 from TomHarte/SpuriousCRCErrors
Ensure the WD won't confuse sector contents for header content.
2019-12-23 21:15:15 -05:00
Thomas Harte
ac604b30f3 Eliminates dangling static_casts in favour of construction. 2019-12-22 20:59:20 -05:00
Thomas Harte
b035b92f33 Corrects accidental use of sector contents as addresses in multi-sector reads and writes.
As a secondary defect, this was also causing erroneous CRC error reports.
2019-12-22 19:58:02 -05:00
Thomas Harte
d25b48878c Cleans up READ_ID macro, inter alia. 2019-12-22 17:58:33 -05:00
Thomas Harte
34a3790e11 Minor static_cast clean-ups. 2019-12-22 17:56:59 -05:00
Thomas Harte
f3378f3e3e Merge pull request #698 from TomHarte/MoreScreenshots
Adds many additional screenshots.
2019-12-22 14:43:08 -05:00
Thomas Harte
78accc1db1 Seeks to fix macOS desktop picture. 2019-12-22 14:41:13 -05:00
Thomas Harte
a756985e18 Makes a further attempt at this table. 2019-12-22 14:40:02 -05:00
Thomas Harte
30e0d4aa30 Attempts a table fix. 2019-12-22 14:37:59 -05:00
Thomas Harte
de72c66c64 Adds a full image gallery, trying to hit every supported system.
... that isn't already pictured, that is.
2019-12-22 14:36:33 -05:00
Thomas Harte
6edd3c9698 Merge pull request #697 from TomHarte/MoreConstexpr
Further propagates `constexpr`.
2019-12-22 13:53:44 -05:00
Thomas Harte
5456a4a39d Eliminates static where constexpra aren't class members; adds some if constexprs for clarity. 2019-12-22 13:42:24 -05:00
Thomas Harte
66d9b60b98 Merge pull request #696 from TomHarte/make_shared
Makes a variety of minor style improvements
2019-12-22 00:27:57 -05:00
Thomas Harte
274867579b Deploys constexpr as a stricter const. 2019-12-22 00:22:17 -05:00
Thomas Harte
a847654ef2 Corrects various old-fashioned bits of indentation, plus the odd const. 2019-12-22 00:00:23 -05:00
Thomas Harte
05d77d3297 Also deploys make_unique/shared to avoid type repetition. 2019-12-21 23:52:04 -05:00
Thomas Harte
e9318efeb6 Switches to std::make_shared/make_unique in a bunch of applicable places.
No doubt many more similar improvements are available, these are just the ones that were easy to find.
2019-12-21 23:34:25 -05:00
Thomas Harte
25da5ebdae Merge pull request #695 from TomHarte/68000ByteAccess
Corrects 16-bit view of the 68000 bus during 8-bit operations.
2019-12-21 21:08:39 -05:00
Thomas Harte
cf16f41939 Makes value8_high/low and value16 branchless. 2019-12-21 20:58:37 -05:00
Thomas Harte
08f2877382 I think the 68000 actually loads a byte value onto both the upper and lower data lines. 2019-12-21 20:37:03 -05:00
Thomas Harte
6f4444d834 Merge pull request #694 from TomHarte/C++17
Standardises on -O2, C++17.
2019-12-21 20:32:04 -05:00
Thomas Harte
993dfeae1b Standardises on -O2, C++17. 2019-12-21 20:25:43 -05:00
Thomas Harte
b4fd506361 Merge pull request #693 from TomHarte/STComposite
Adds colour composite output to the ST
2019-12-21 00:04:54 -05:00
Thomas Harte
e5440a4146 Hacks in a colour burst.
With a major flaw: it's implicit. I think I need a minor rethink of various components here.
2019-12-20 23:49:38 -05:00
Thomas Harte
57ce10418f Switches prescale logic, the better to deal with changes in prescaler.
According to my assumptions about the behaviour, anyway.
2019-12-20 23:33:14 -05:00
Thomas Harte
47508d50a7 Wires through a composite video option for the ST.
Which is great and all, except that I've not yet inserted a colour burst. So it's monochrome.
2019-12-20 20:49:14 -05:00
Thomas Harte
56cc191a8b Merge pull request #692 from TomHarte/11Sectors
Compacts gaps when necessary to fit more sectors.
2019-12-19 23:33:25 -05:00
Thomas Harte
2a1520c04e Removes mostly-uninformative piece of logging. 2019-12-19 22:58:28 -05:00
Thomas Harte
3d83f5ab49 Ensures a proper size handoff and implements a ripple feature I happened to find a forum post about. 2019-12-19 22:58:07 -05:00
Thomas Harte
0007dc23b3 Eliminates bit 0 of the DMA address. 2019-12-19 22:44:21 -05:00
Thomas Harte
416d68ab3a Installs some additional safety guards. 2019-12-19 22:27:50 -05:00
Thomas Harte
ed7f171736 Moves address reload to end of vertical sync.
I have no information as to when it should be, so this is as valid a guess as any other.
2019-12-19 22:20:43 -05:00
Thomas Harte
0e066f0f70 Removes 'done' TODO.
For certain values of done.
2019-12-19 22:19:59 -05:00
Thomas Harte
3e6f51f5cf Merge branch '11Sectors' of github.com:TomHarte/CLK into 11Sectors 2019-12-19 19:36:33 -05:00
Thomas Harte
797abae4b3 Compacts gaps when necessary to fit more sectors. 2019-12-19 19:36:19 -05:00
Thomas Harte
4605b1b264 Compacts gaps when necessary to fit more sectors. 2019-12-19 19:22:48 -05:00
Thomas Harte
d802e8aee3 Merge pull request #690 from TomHarte/YMNotAY
Adds explicit emulation of the YM2149F.
2019-12-18 22:10:16 -05:00
Thomas Harte
206ab380c7 Introduces double-resolution envelopes for the Atari ST. 2019-12-18 22:03:02 -05:00
Thomas Harte
d85ae21b2f Adds an explicit declaration of chip type to all AY users. 2019-12-18 19:28:41 -05:00
Thomas Harte
470cc572fd Merge pull request #689 from TomHarte/STScreenshot
Adds a token Atari ST screenshot.
2019-12-17 23:29:43 -05:00
Thomas Harte
f0d9d8542b Adds a token Atari ST screenshot. 2019-12-17 23:28:38 -05:00
Thomas Harte
d2390fcb11 Merge pull request #688 from TomHarte/NewAtari
Adds the Atari ST to File -> New in Cocoa world.
2019-12-17 23:18:13 -05:00
Thomas Harte
5ce612cb38 Adds the Atari ST to File -> New in Cocoa world. 2019-12-17 23:04:12 -05:00
Thomas Harte
ec7aa2d355 Merge pull request #687 from TomHarte/68000Tests
Significantly increases 68000 testing
2019-12-17 22:31:54 -05:00
Thomas Harte
9464658d1e Adds a count summary. 2019-12-17 22:19:23 -05:00
Thomas Harte
a3e64cae41 Corrects SBCD carry. 2019-12-17 22:16:02 -05:00
Thomas Harte
e969b386f1 Eliminates DIVU/S and MULU/S from this file. 2019-12-17 20:15:11 -05:00
Thomas Harte
af9c0aca97 Added mention of the Atari ST. 2019-12-17 14:36:08 -05:00
Thomas Harte
8a2ac87209 Reverted SBCD/NBCD V behaviour. 2019-12-16 23:08:59 -05:00
Thomas Harte
096b447b4b Corrects MOVE -(An), SR/CCR, which was not previously decrementing.
Also adds a safety check against other instances of the same error. There seem to be none.
2019-12-16 22:38:54 -05:00
Thomas Harte
0d23f141d6 Regenerates without accidentally hitting MODE to SR. 2019-12-16 22:37:57 -05:00
Thomas Harte
84167af54f Corrects CHK N flag. 2019-12-16 20:01:33 -05:00
Thomas Harte
8be26502c4 Fixes NBCD -(An)+, adds some additional comments. 2019-12-16 20:01:19 -05:00
Thomas Harte
ba2436206f Withdraws test of CHK (exception taken). 2019-12-16 20:00:42 -05:00
Thomas Harte
60a9b260b1 Corrects collection of instruction codes. 2019-12-16 00:01:18 -05:00
Thomas Harte
e603fc6aaa Simplifies failure output for me. 2019-12-15 21:26:47 -05:00
Thomas Harte
81cc278b98 Introduces a barrage of further tests. 2019-12-15 21:26:35 -05:00
Thomas Harte
4c068e9bb8 Corrects flags on CMPA.w. 2019-12-15 20:39:47 -05:00
Thomas Harte
f23c5ada31 Ensures tests can be built as a release target. 2019-12-14 23:53:12 -05:00
Thomas Harte
dc1abd874e Corrects indentation typo. 2019-12-14 23:52:53 -05:00
Thomas Harte
1bf4686c59 Adds plentiful additional tests. Though still only a fraction of the anticipated total. 2019-12-14 22:58:51 -05:00
Thomas Harte
a500fbcd73 Expands tests to most of ORI, EORI, ANDI, ADDI and SUBI. 2019-12-14 22:23:40 -05:00
Thomas Harte
d0ef41f11e Adds a temporary manual escape clause for testing specific features. 2019-12-14 21:40:21 -05:00
Thomas Harte
adf6723bf6 Ensures state is evaluated directly at opcode end. 2019-12-14 15:09:06 -05:00
Thomas Harte
37e26c0c37 Eliminates a class of incorrect sign comparison errors. 2019-12-14 14:50:39 -05:00
Thomas Harte
ac1575be27 Resolves false negatives from checking wrong state. 2019-12-14 14:46:00 -05:00
Thomas Harte
923287bf01 Attempts to introduce a basic means for comparative 68000 testing.
i.e. mine versus another source.
2019-12-14 14:26:33 -05:00
Thomas Harte
77fe14cdb3 Merge pull request #685 from TomHarte/LoadDelay
Adds a delay on load following DE
2019-12-13 21:38:10 -05:00
Thomas Harte
c00ae7ce6a Adds a one-cycle delay on frequency changes. 2019-12-13 19:57:54 -05:00
Thomas Harte
d5b2e6514a Merge branch 'master' into LoadDelay 2019-12-13 19:41:35 -05:00
Thomas Harte
fc7f46006e Merge pull request #686 from TomHarte/AptGet
Adds an apt-get update.
2019-12-13 13:32:18 -05:00
Thomas Harte
41503d7253 Allow releaseinfo changes. 2019-12-13 13:16:24 -05:00
Thomas Harte
f88c942fef Adds an apt-get update. 2019-12-12 23:26:12 -05:00
Thomas Harte
4bcf217324 Ensures delayed loading isn't interrupted by blank, hsync. 2019-12-12 23:20:28 -05:00
Thomas Harte
f6f2b4b90f Removes double DE edge test. 2019-12-12 22:50:35 -05:00
Thomas Harte
95b5db4d87 Tweaks timings yet further, adds a FIFO reset.
The accuracy of this may require further research.
2019-12-11 23:22:20 -05:00
Thomas Harte
de4403e021 Corrects blank timing. 2019-12-10 22:17:57 -05:00
Thomas Harte
0a405d1c06 Introduces a latency between DE and load. 2019-12-10 21:24:15 -05:00
Thomas Harte
768b3709b8 Corrects audio clock rate. 2019-12-10 20:25:27 -05:00
Thomas Harte
7cc5d0b209 Merge pull request #683 from TomHarte/MFPPerformance
Switch to faster timer implementation; it seems to work.
2019-12-09 19:52:16 -05:00
Thomas Harte
c2646a415f Switch to faster timer implementation; it seems to work. 2019-12-09 19:23:08 -05:00
Thomas Harte
e1c7a140d0 Merge pull request #682 from TomHarte/AddressError
ST: Adds some initial bus error logic, plus some optimisations.
2019-12-08 22:53:40 -05:00
Thomas Harte
7cd11ecb7f Adds necessary #include for assert. 2019-12-08 22:43:39 -05:00
Thomas Harte
4dd235f677 Adds supervisor/user to logged flags in trace mode. 2019-12-08 22:39:10 -05:00
Thomas Harte
a7cfb840ef Adds but presently disables a diagnostic for border elimination. 2019-12-08 22:34:42 -05:00
Thomas Harte
acfe2c63b8 Adds an assert to verify the interrupt line is clear after a full reset. 2019-12-08 22:34:19 -05:00
Thomas Harte
b192381928 Implements a fuller reset, takes a run at the overran flag. 2019-12-08 21:20:06 -05:00
Thomas Harte
c785797da6 Adds a warning for unhandled reset. 2019-12-08 21:01:30 -05:00
Thomas Harte
0408592ada Switches to byte buffers and seeks to reduce unnecessary video flushing. 2019-12-08 20:20:13 -05:00
Thomas Harte
407cc78c78 Extends to offer simpler 8-bit access handling. 2019-12-08 20:19:44 -05:00
Thomas Harte
4536c6a224 Resolves printf type errors. 2019-12-08 11:56:05 -05:00
Thomas Harte
0ed87c61bd Introduces an explicit area of floating bus, starts adding bus errors. 2019-12-08 11:52:43 -05:00
Thomas Harte
332f0d6167 Ensures MSAs are explicitly read-only. 2019-12-08 11:52:15 -05:00
Thomas Harte
08a27bdec7 NTSC frame length is correct; removes TODO. 2019-12-08 11:51:12 -05:00
Thomas Harte
288cabbad1 Merge pull request #680 from TomHarte/EventCountReload
Implements MFP timer reload when in event counting mode.
2019-11-19 22:54:34 -05:00
Thomas Harte
7ff57f8cdf Starts to flesh out documentation. 2019-11-19 22:32:07 -05:00
Thomas Harte
06edeea866 Adds reload during event count mode.
Plus a helpful bit of TODO.
2019-11-19 22:24:32 -05:00
Thomas Harte
3c77d3bda0 Merge pull request #679 from TomHarte/OffByOne
Corrects off-by-one error in the ST's vertical state machine
2019-11-19 22:02:41 -05:00
Thomas Harte
72cb3a1cf6 Integrates basic unit test for Atari ST video event prediction. 2019-11-19 21:54:13 -05:00
Thomas Harte
e0ceab6642 Pivots towards looking at Timer B as a cause of in-frame inaccuracy. 2019-11-19 21:52:50 -05:00
Thomas Harte
894066984c Moves beginning and end of vertical sync to what I now believe is its proper place.
At least one demo now successfully opens the top border.
2019-11-19 20:13:47 -05:00
Thomas Harte
c91495d068 Merge pull request #678 from TomHarte/DEDelay
Introduces a 28-cycle delay on DE propagation
2019-11-18 23:53:46 -05:00
Thomas Harte
e787c03530 Slightly shortens NTSC frame.
Either: (i) 263 is incorrect; or (ii) my logic as to frame height is incorrect. Given that the horizontal side of things is really well documented, I'm currently guessing (i). Research to do.
2019-11-18 23:47:27 -05:00
Thomas Harte
b12136691a Corrects comment. 2019-11-18 23:46:33 -05:00
Thomas Harte
c04d2f6c6e Restricts DTack delay to RAM and Shifter accesses. 2019-11-18 22:57:13 -05:00
Thomas Harte
6990abc0d3 Tweaks selected output mode when both BPP bits are set. 2019-11-18 22:56:40 -05:00
Thomas Harte
0ce5057fd9 Attempts to factor in event counting direction. 2019-11-18 22:37:20 -05:00
Thomas Harte
ade8df7217 Permits a delay on DE propagation back to the CPU. Plus tests.
Currently set at 28 cycles, but I don't know.
2019-11-18 22:12:24 -05:00
Thomas Harte
b98703bd5b Corrects lack of const. 2019-11-18 22:11:52 -05:00
Thomas Harte
82c984afa4 Switches the joysticks around.
Thereby finally allowing me to control mode games.
2019-11-18 20:02:27 -05:00
Thomas Harte
1202b0a65f Establishes a pipeline for delayed public state visibility. 2019-11-17 23:28:00 -05:00
Thomas Harte
facc0a1976 Amps up the documentation. 2019-11-17 21:28:51 -05:00
Thomas Harte
25da8b7787 Merge pull request #677 from TomHarte/SyncDisturbance
Corrects accidental dropping of pixel residue.
2019-11-17 18:47:27 -05:00
Thomas Harte
253dd84109 Corrects accidental dropping of pixel residue.
Specific issue: the repeated (start_column != end_column) test, which can no longer be correct if start_column has been incremented in the (x_&7) test.

The visible effect was to omit pixels from the output wave, which also affected observed sync timing.
2019-11-17 18:34:13 -05:00
Thomas Harte
9d07765823 Ensures proper precedence of * over %. 2019-11-14 23:19:31 -05:00
Thomas Harte
11de0e198f Removes dead travis.yml.
Travis never worked; GitHub actions do; that's CI.
2019-11-14 19:58:24 -05:00
Thomas Harte
f16f0897d5 Merge pull request #676 from TomHarte/BuildWorkflow
Adds continuous integration via GitHub actions.
2019-11-14 13:59:52 -05:00
Thomas Harte
3d4d45ef62 Remove redundant 'build' 2019-11-14 13:54:24 -05:00
Thomas Harte
04c4f5f321 Removes syntax violating colon. 2019-11-14 13:53:24 -05:00
Thomas Harte
fa900d22e8 Adds a more manageable title. 2019-11-14 13:52:24 -05:00
Thomas Harte
aee2890b25 Switch to title case.
This seems to fit better with the fixed named steps.
2019-11-14 13:50:03 -05:00
Thomas Harte
40e1ec28fb Attempt sudo.
Sorry for the noise; there's no obvious better way to test this stuff.
2019-11-14 13:42:56 -05:00
Thomas Harte
c6e2b1237c Add build workflow 2019-11-14 13:40:06 -05:00
Thomas Harte
efdd27a435 Merge pull request #675 from TomHarte/STFileFormat
Adds support for .ST files.
2019-11-12 23:32:47 -05:00
Thomas Harte
2c4f372872 Adds support for the .ST file format. 2019-11-12 23:23:14 -05:00
Thomas Harte
74be876d72 Corrects track count calculation for DSD disks. 2019-11-12 23:22:56 -05:00
Thomas Harte
e8e166eec5 Ensures no out-of-disk-bounds mirroring. 2019-11-12 23:22:25 -05:00
Thomas Harte
4ec8fa0d20 Merge branch 'LessACIAState' 2019-11-12 22:34:02 -05:00
Thomas Harte
b019c6f8dd Merge pull request #674 from TomHarte/LessACIAState
Reduces redundant ACIA state.
2019-11-12 22:32:47 -05:00
Thomas Harte
6ec3c47cc0 Ensures same-level interrupts don't double trigger. 2019-11-12 22:18:13 -05:00
Thomas Harte
ccce127f13 Merge branch 'master' into LessACIAState 2019-11-12 19:41:18 -05:00
Thomas Harte
f4cfca0451 Merge branch 'master' of github.com:TomHarte/CLK 2019-11-12 19:38:44 -05:00
Thomas Harte
eb287605f7 Switches to a default of TOS 1.04. 2019-11-12 19:38:30 -05:00
Thomas Harte
2026761a07 Merge pull request #673 from TomHarte/FewerSpans
Reduces Atari ST output heft
2019-11-12 19:37:53 -05:00
Thomas Harte
6a82c87320 Withdraws border optimisation temporarily; I think I may be onto an output bug here. 2019-11-12 19:33:13 -05:00
Thomas Harte
f0478225f0 Adjusts logic to reduce number of output spans. 2019-11-12 19:30:28 -05:00
Thomas Harte
d6edfa5c6d Removes the redundant state encased within interrupt_causes_. 2019-11-11 21:49:02 -05:00
Thomas Harte
e7253a8713 Merge pull request #672 from TomHarte/BetterShifter
Introduces a cleaner, separated shifter.
2019-11-10 21:56:45 -05:00
Thomas Harte
c6f6bc68e1 Undoes non-insertion of media. 2019-11-10 21:52:06 -05:00
Thomas Harte
ab34fad8ca Introduces a cleaner, separated shifter. 2019-11-10 21:39:40 -05:00
Thomas Harte
e4c77614c1 Merge pull request #671 from TomHarte/STJoystick
Makes joysticks a little more const correct
2019-11-09 22:11:11 -05:00
Thomas Harte
072b0266af It seems status reads are not required to clear the interrupt line. 2019-11-09 20:12:09 -05:00
Thomas Harte
7ae0902103 Adds additional joystick commands to the dispatcher. 2019-11-09 20:10:54 -05:00
Thomas Harte
8e9428623e Adds joystick events to the Atari ST. 2019-11-09 18:39:22 -05:00
Thomas Harte
2c25135d8a Fixes const correctness for joystick machines; the ST is now one such. 2019-11-09 18:19:05 -05:00
Thomas Harte
3741cba88c Merge pull request #670 from TomHarte/STKeyboard
Corrects: KeyPad -> Keypad. Also fleshes out Atari ST keyboard mapping.
2019-11-09 18:05:52 -05:00
Thomas Harte
860837d894 Corrects: KeyPad -> Keypad. Also fleshes out Atari ST keyboard mapping. 2019-11-09 18:02:14 -05:00
Thomas Harte
cef07038c1 Merge pull request #662 from TomHarte/AtariST
Adds basic and very buggy Atari ST emulation.
2019-11-09 16:18:50 -05:00
Thomas Harte
0bf61c9c99 Updated: there is some ST emulation here now. 2019-11-09 16:18:29 -05:00
Thomas Harte
837dfd1ab8 Corrects StaticAnalyser references. 2019-11-09 16:14:00 -05:00
Thomas Harte
0204003680 Resolves GCC warnings. 2019-11-09 16:12:37 -05:00
Thomas Harte
5fc4e57db7 Eliminates non-portable use of fls. 2019-11-09 16:03:00 -05:00
Thomas Harte
c4fefe1eb3 Updates SConstruct. 2019-11-09 15:42:19 -05:00
Thomas Harte
77ef7dc8fc Shuffles ST and 2600 into a common parent. 2019-11-09 15:31:41 -05:00
Thomas Harte
e3abbc9966 Renames what didn't end up being a whole SerialPort. 2019-11-09 15:21:51 -05:00
Thomas Harte
70c6010fe0 Expands visible area and adds a few more safety barriers. 2019-11-09 15:00:42 -05:00
Thomas Harte
8c736a639a Eliminates unexpected bottleneck created by ACIA. 2019-11-09 15:00:12 -05:00
Thomas Harte
cc7ff1ec9e Corrects typo. 2019-11-09 14:59:35 -05:00
Thomas Harte
9d12ca691f Makes meaning of 2048 here explicit. 2019-11-09 14:59:16 -05:00
Thomas Harte
db03b03276 Corrects [AND/OR/EOR].bw Dn, -(An) to decrement destination.
It was previously doing a predecrement on the internal source address, which is unused. This fixes at least Dan Dare III and Silkworm.
2019-11-09 11:25:23 -05:00
Thomas Harte
45375fb5d0 Makes endian aware. 2019-11-09 09:48:59 -05:00
Thomas Harte
d2324e413d Clarifies ownership of bpp-has-changed test. 2019-11-09 00:10:59 -05:00
Thomas Harte
e0c15f43bb Avoids massive over-flushing of pixel buffers. 2019-11-09 00:05:02 -05:00
Thomas Harte
8b0d550b81 Attempts to move vertical sync out to cycle 30. 2019-11-08 22:18:47 -05:00
Thomas Harte
d1259f829e Moves vertical state decisions back to cycle 502. 2019-11-08 21:42:05 -05:00
Thomas Harte
7caef46c05 Switches back to hsync for interrupts; corrects current address reads. 2019-11-08 21:25:28 -05:00
Thomas Harte
6902251d8b Fixed: returns video address in bytes, not words. 2019-11-08 20:47:08 -05:00
Thomas Harte
0b683b0360 Adds some sanity checks. 2019-11-08 20:46:24 -05:00
Thomas Harte
5d5dc79f2c Corrects raster race condition with CPU. 2019-11-07 23:27:05 -05:00
Thomas Harte
68f9084d9e Fixes timing of wrap. 2019-11-07 23:24:51 -05:00
Thomas Harte
b7c407be10 The palette is meant to be read/write. 2019-11-07 23:11:06 -05:00
Thomas Harte
f45798faf0 Corrects initial safe value of line_length_. 2019-11-07 22:53:26 -05:00
Thomas Harte
7c66d7a13c Corrects sync and line-length latch timings. 2019-11-07 22:53:04 -05:00
Thomas Harte
f9a35c6636 Corrects potential wrap error for non-single-byte targets. 2019-11-07 22:23:26 -05:00
Thomas Harte
5e1570258d I think the horizontal interrupt is blank, not sync. 2019-11-07 22:00:50 -05:00
Thomas Harte
fc8021c0b0 Adds a centre crop. 2019-11-07 20:02:45 -05:00
Thomas Harte
c9cd56915e Corrects typo that was adding an extra line of PAL video. 2019-11-07 19:55:49 -05:00
Thomas Harte
1fd19c5786 Attempts to add proper bus timing. 2019-11-07 19:55:00 -05:00
Thomas Harte
ce66b5fd9c Corrected member variable names. 2019-11-07 19:44:22 -05:00
Thomas Harte
8aa425c9d8 Fixes medium resolution mode. 2019-11-06 23:25:36 -05:00
Thomas Harte
ec68bc5047 Corrects output glitches: channel de sync and improper border beginnings. 2019-11-06 22:37:05 -05:00
Thomas Harte
0971bbeebe Merge branch 'master' into AtariST 2019-11-05 23:24:26 -05:00
Thomas Harte
9292f66c2d Merge pull request #669 from TomHarte/WOZDoc
Update explanation of NIB processing.
2019-11-05 23:21:25 -05:00
Thomas Harte
f3e2e88986 Update explanation of NIB processing. 2019-11-05 23:20:20 -05:00
Thomas Harte
6afefa107e Resolves unused variable warning. 2019-11-05 23:18:25 -05:00
Thomas Harte
0ce807805d Eliminates most masks, at least for now. 2019-11-05 23:17:59 -05:00
Thomas Harte
41f3c29e30 Attempts to switch to correct video state machine.
Some glitches remain to be ironed out.
2019-11-05 23:02:25 -05:00
Thomas Harte
6c75c60149 Merge branch 'master' into AtariST 2019-11-05 22:05:51 -05:00
Thomas Harte
015f2101f8 Merge pull request #668 from TomHarte/OverAllocation
Resolves a potential out-of-bounds array access.
2019-11-05 21:52:44 -05:00
Thomas Harte
35f1a7ab10 Resolves a potential out-of-bounds array access.
Risk was: allocation exactly joins end of buffer. In which case the next get_data won't wrap the texture y coordinate since it won't spot an x overage.
2019-11-05 21:47:40 -05:00
Thomas Harte
8df1eea955 Goes big on flushing. 2019-11-03 23:03:50 -05:00
Thomas Harte
eeafdf2c03 Slightly expands list of recognised Intelligent Keyboard commands. 2019-11-03 21:58:15 -05:00
Thomas Harte
befe2c2929 Adds floppy drive activity indicators. 2019-11-03 21:57:54 -05:00
Thomas Harte
46ec3510be Fixed: ST sectors are numbered from 1, not 0. 2019-11-03 21:18:29 -05:00
Thomas Harte
e9965c2738 Obeys stated memory size. 2019-11-03 21:18:17 -05:00
Thomas Harte
48b0d8c329 Adds bus req/ack to the DMA controller; hacks support into the ST. 2019-11-03 21:11:25 -05:00
Thomas Harte
07582cee4a BusGrant is a further signal I will need. 2019-11-03 21:10:42 -05:00
Thomas Harte
4dbd2a805a Nudges closer to DMA support. 2019-11-03 17:24:36 -05:00
Thomas Harte
20bf425f98 Drive select lines are active low. 2019-11-02 23:37:56 -04:00
Thomas Harte
0567410bcf Attempts to start getting the WDC working. 2019-11-02 23:26:42 -04:00
Thomas Harte
6d1e09ba55 Connects up the AY to floppy drive/side selection. 2019-11-02 23:04:08 -04:00
Thomas Harte
f40dbefa67 Implements most of keyboard input. 2019-11-02 22:30:02 -04:00
Thomas Harte
f93cdd21de Reverses bit order.
So, for the first time: a green desktop.
2019-11-02 21:53:04 -04:00
Thomas Harte
e1dc3b1915 Reverses mouse buttons.
So I can now navigate the disk-less GEM desktop and click on things.
2019-11-02 21:38:57 -04:00
Thomas Harte
cbf25a16dc Adds relative mouse motion input. 2019-11-02 21:25:45 -04:00
Thomas Harte
14e790746b Fixes return value when reading received data. 2019-11-02 21:25:00 -04:00
Thomas Harte
bf7e9cfd62 Pulls the intelligent keyboard into its own file. 2019-11-02 19:47:44 -04:00
Thomas Harte
a67e0014a4 Fixes video base address and mono/colour monitor value.
Now I see a GEM desktop. In blue.
2019-11-02 19:36:15 -04:00
Thomas Harte
c070f2100c Attempts to regularise data bus access. 2019-11-01 23:01:06 -04:00
Thomas Harte
75e34b4215 Reacts to no acknowledgement. 2019-10-31 21:00:05 -04:00
Thomas Harte
a5bbf54a27 Adds the ability for the 68901 to decline an interrupt acknowledgement. 2019-10-31 19:57:36 -04:00
Thomas Harte
5309ac7c30 Annotates JustInTimeActor as force inline. 2019-10-30 23:18:42 -04:00
Thomas Harte
731dc350b4 Adds sometime real-time clocking for DMA. 2019-10-30 22:59:32 -04:00
Thomas Harte
635e18a50d Ensures the MFP requests and receives real-time clocking when needed. 2019-10-30 22:42:06 -04:00
Thomas Harte
4857ceb3eb Attempts to get a bit more systematic.
Spotted that interrupt_enable_ isn't being used properly while doing so, hopefully that's now correct.
2019-10-29 23:16:08 -04:00
Thomas Harte
1c154131f9 Expands size of storage in Cycles/HalfCycles; adjusts widely to compensate. 2019-10-29 22:36:29 -04:00
Thomas Harte
fd02b6fc18 Corrects in-service test; adds pending clearing upon enabled clearing. 2019-10-28 22:51:00 -04:00
Thomas Harte
553f3b6d8b Properly conforms to GPIP input/output blending. 2019-10-28 22:37:11 -04:00
Thomas Harte
1135576a3a Comments in slightly more detail. 2019-10-28 22:12:56 -04:00
Thomas Harte
a5057e6540 Ensures that stop means stop. 2019-10-28 22:12:45 -04:00
Thomas Harte
1d790ec2a9 Adds the option for a clock conversion to JustInTimeActor and slows the MFP's clock rate. 2019-10-28 21:35:10 -04:00
Thomas Harte
0f2d72c436 Ensures receipt of output line changes. 2019-10-28 21:21:53 -04:00
Thomas Harte
aa52652027 Adds a const. 2019-10-28 21:21:35 -04:00
Thomas Harte
4a1fa8fc13 Adds some const qualifiers. 2019-10-28 21:13:42 -04:00
Thomas Harte
95d3b6e79f Adds a through route for the FDC interrupt line. 2019-10-28 21:13:21 -04:00
Thomas Harte
5f6711b72c Ensures interrupt changes are notified to the delegate. 2019-10-28 21:13:06 -04:00
Thomas Harte
d44734d105 Attempts a fuller setting of GPIP inputs. 2019-10-27 22:39:21 -04:00
Thomas Harte
1aaa6331a0 Stores and returns video mode. 2019-10-27 22:39:00 -04:00
Thomas Harte
de1bfb4e24 Stores and returns timer configuration. 2019-10-27 22:38:49 -04:00
Thomas Harte
0cb19421e8 Adds prefix to mouse position response. 2019-10-27 21:46:03 -04:00
Thomas Harte
92847037b3 Merge branch 'master' into AtariST 2019-10-27 21:40:51 -04:00
Thomas Harte
f4556ef6b0 Merge pull request #666 from TomHarte/CPCSafety
Fixes unit test target
2019-10-27 21:40:06 -04:00
Thomas Harte
4266264449 Switches to the more idiomatic .empty(). 2019-10-27 21:31:31 -04:00
Thomas Harte
1aba1db62c Corrects test. 2019-10-27 21:30:58 -04:00
Thomas Harte
0fc191c87d Switched a few static_cast to constructor syntax. 2019-10-27 14:21:22 -04:00
Thomas Harte
dc4a0e4e3b Video only ever reads from RAM, so it can be const *.
(it can also be *const, since I set it only once)
2019-10-27 14:09:38 -04:00
Thomas Harte
3794d94b68 Adds a sanity check on alignment. 2019-10-27 14:09:02 -04:00
Thomas Harte
0082dc4411 Improves logging. 2019-10-27 00:02:55 -04:00
Thomas Harte
22754683f8 Ensures timer divisor values don't go out of range, adds timer interrupts.
I suspect further timer issues remain.
2019-10-26 23:20:13 -04:00
Thomas Harte
909685d87d A drive with no disk is now happy to spin its motor.
It'll continue to produce index-hole events, which might not be accurate for 5.25" drives. Research to do.
2019-10-26 22:57:05 -04:00
Thomas Harte
55710ea00e Switches the presumption to requiring NDEBUG to avoid forced inlines. 2019-10-26 22:43:25 -04:00
Thomas Harte
36a9a5288b Adds drives to the FDC. 2019-10-26 22:39:11 -04:00
Thomas Harte
e89be6249d Adds a logging prefix. 2019-10-26 22:38:56 -04:00
Thomas Harte
ac39fd0235 Starts work on the DMA controller. 2019-10-26 21:33:57 -04:00
Thomas Harte
ecc0cea5a1 Added a potential branch for the newer TOS memory map. 2019-10-26 16:52:06 -04:00
Thomas Harte
eae11cbf17 Adds a dummy response for mouse interrogation. 2019-10-26 16:14:24 -04:00
Thomas Harte
e96386f572 Takes another stab at MFP interrupt management. 2019-10-26 15:55:19 -04:00
Thomas Harte
a8d481a764 Writes to the pending register appear to be able to clear interrupts too. 2019-10-25 22:46:30 -04:00
Thomas Harte
2207638287 Adds hsync and vsync interrupts. 2019-10-25 22:42:13 -04:00
Thomas Harte
872897029e Attempts a complete wiring of 68901 interrupts. 2019-10-25 22:36:01 -04:00
Thomas Harte
51b4b5551d Actually, I think the 6850 is active low for interrupts. 2019-10-24 22:37:53 -04:00
Thomas Harte
7a2de47f58 Corrects interrupt mask generation. 2019-10-24 22:37:32 -04:00
Thomas Harte
f2f98ed60c Attempts some part of interrupt decision making. 2019-10-24 22:33:42 -04:00
Thomas Harte
77f14fa638 Starts trying to make sense of interrupts. 2019-10-23 23:09:49 -04:00
Thomas Harte
f09a240e6c Gives myself more trace details. 2019-10-21 23:20:03 -04:00
Thomas Harte
092a61f93e Does a better job of having just 512kb. 2019-10-21 23:10:30 -04:00
Thomas Harte
e30ba58e0d Attempts to wire ACIA interrupt signals into the MFP. 2019-10-21 23:02:30 -04:00
Thomas Harte
7cb82fccc0 Attempts properly to maintain interrupt flag; adds delegate. 2019-10-21 22:40:38 -04:00
Thomas Harte
ed9a5b0430 Adds receipt interrupt. 2019-10-21 21:27:57 -04:00
Thomas Harte
8f59a73425 Corrects incoming data capture. 2019-10-21 20:18:52 -04:00
Thomas Harte
91223b9ec8 Sets default level to high. 2019-10-21 20:18:33 -04:00
Thomas Harte
83f5f0e2ad Begins trying to receive ACIA data. 2019-10-21 20:10:19 -04:00
Thomas Harte
cf37e9f5de Remove source control markers. 2019-10-20 23:40:51 -04:00
Thomas Harte
e4f7ead894 Merge branch 'AtariST' of github.com:TomHarte/CLK into AtariST 2019-10-20 23:40:01 -04:00
Thomas Harte
4134463094 The ACIA now receives bits. 2019-10-20 23:34:30 -04:00
Thomas Harte
83d73fb088 The keyboard now responds to a reset on its serial line. 2019-10-20 23:13:44 -04:00
Thomas Harte
75c3e2dacd Adds basic, incomplete dispatcher for the intelligent keyboard. 2019-10-20 23:07:19 -04:00
Thomas Harte
cf07982a9b Ensures good serial line and ACIA behaviour.
Next stop: having the intelligent keyboard react.
2019-10-20 22:10:05 -04:00
Thomas Harte
313aaa8f95 Silences temporarily. 2019-10-20 20:38:56 -04:00
Thomas Harte
2e86dada1d Ensures updates even when the event queue is empty. 2019-10-20 20:38:56 -04:00
Thomas Harte
696af5c3a6 Starts to transfer serial line decoding logic into the line itself. 2019-10-20 20:38:56 -04:00
Thomas Harte
f08b38d0ae Silences, temporarily. 2019-10-20 20:38:55 -04:00
Thomas Harte
9a8352282d Mostly but not quite fixes serial work. 2019-10-20 20:38:55 -04:00
Thomas Harte
3d03cce6b1 Starts working on the GPIP functionality block. 2019-10-20 20:38:55 -04:00
Thomas Harte
34075a7674 Attempts to tie an intelligent keyboard to the other end of its serial line. 2019-10-20 20:38:55 -04:00
Thomas Harte
f79c87659f Corrects documentation error. 2019-10-20 20:38:55 -04:00
Thomas Harte
c10b64e1c0 Adds a received_data_ register, that presently can never fill. 2019-10-20 20:38:55 -04:00
Thomas Harte
5d5fe52144 Corrects transmission logic — exactly hitting write_data_time_remaining now works properly. 2019-10-20 20:38:55 -04:00
Thomas Harte
d461331fd2 Ensures remaining_delays_ is set properly after [reset/flush]_writing. 2019-10-20 20:38:55 -04:00
Thomas Harte
ff62eb6dce The ACIA actually has two clocks, though on an ST they're both 500,000 Hz. 2019-10-20 20:38:55 -04:00
Thomas Harte
374439693e Ensures serial lines know their writer's clock rate. 2019-10-20 20:38:55 -04:00
Thomas Harte
c4ef33b23f JustInTimeActors can now specify a clock divider. 2019-10-20 20:38:55 -04:00
Thomas Harte
a7ed357569 Attempts to implement transmission interrupts and ClockingHint::Source. 2019-10-20 20:38:55 -04:00
Thomas Harte
4e5b440145 Attempts mostly to implement 6850 output. 2019-10-20 20:38:55 -04:00
Thomas Harte
2bd7be13b5 Decodes the 6850 control register, and starts working on standardised serial ports. 2019-10-20 20:38:55 -04:00
Thomas Harte
4b09d7c41d Nudges 6850 towards coherence. 2019-10-20 20:38:55 -04:00
Thomas Harte
97d44129cb Ensures all 16 data lines reach the video. 2019-10-20 20:38:55 -04:00
Thomas Harte
b0f5f7bd37 Attempts to start producing actual video. 2019-10-20 20:38:55 -04:00
Thomas Harte
d1dd6876b5 Adds the option to affix a standard prefix to log messages. 2019-10-20 20:38:55 -04:00
Thomas Harte
a59ec9e818 Provides a token something where DMA should be. 2019-10-20 20:38:55 -04:00
Thomas Harte
4ead905c3c Adds an empty shell for the ACIA. 2019-10-20 20:38:55 -04:00
Thomas Harte
127bb043e7 Adds enough logic to advance to an ACIA access error. 2019-10-20 20:38:55 -04:00
Thomas Harte
42ebe06474 Makes an attempt at tracking video sequence points. 2019-10-20 20:38:55 -04:00
Thomas Harte
74fe32da23 Takes a shot at other display outputs. 2019-10-20 20:38:55 -04:00
Thomas Harte
780916551f Corrects sync generation. 2019-10-20 20:38:54 -04:00
Thomas Harte
305b1211ba Makes a first attempt to box out the ST display area. 2019-10-20 20:38:54 -04:00
Thomas Harte
2cf52fb89c Makes an unsuccessful first attempt at some timer functionality. 2019-10-20 20:38:54 -04:00
Thomas Harte
6e1b606adf Adds a target for MFP read/write operations.
Completely without any implementation, so far.
2019-10-20 20:38:54 -04:00
Thomas Harte
3bb0bf9e14 Adds some semblance of an AY. 2019-10-20 20:38:54 -04:00
Thomas Harte
87a6d22894 Starts to formalise the ST memory map a little. 2019-10-20 20:38:54 -04:00
Thomas Harte
484a0ceeb8 Starts forming an Atari ST memory map. 2019-10-20 20:38:54 -04:00
Thomas Harte
da1436abd2 Gifts the Atari ST a 68000 and non-functional video. 2019-10-20 20:38:54 -04:00
Thomas Harte
125f781ced Starts to create an actual shell of a machine. 2019-10-20 20:38:54 -04:00
Thomas Harte
c66c484c54 Removes unused includes. 2019-10-20 20:38:54 -04:00
Thomas Harte
345b32d6e3 Implements read-only MSA support. 2019-10-20 20:38:54 -04:00
Thomas Harte
8b397626bf Adds a route through the static analyser to the Atari ST. 2019-10-20 20:38:54 -04:00
Thomas Harte
0da1881a07 Adds an Atari ST enumeration and factory method. 2019-10-20 20:38:54 -04:00
Thomas Harte
d4077afd30 Merge pull request #665 from TomHarte/CPCCrash
Slightly improves CPC performance
2019-10-20 20:19:29 -04:00
Thomas Harte
95c45b5515 This can be const. 2019-10-20 17:22:56 -04:00
Thomas Harte
684644420a Increases scan buffer availability. 2019-10-20 17:22:41 -04:00
Thomas Harte
735586f5f8 Corrects tabs; adds potential output_border optimisation. 2019-10-19 21:20:34 -04:00
Thomas Harte
ddae086661 Merge pull request #664 from TomHarte/DataAllocationGuards
Adds safety checks around video data allocation
2019-10-19 18:36:05 -04:00
Thomas Harte
9c7aa5f3fc Attempts also to spot data writes without allocations. 2019-10-19 18:26:56 -04:00
Thomas Harte
418cd07e17 Adds a check against overrunning data. 2019-10-19 18:17:44 -04:00
Thomas Harte
2ae5739b8b Silences temporarily. 2019-10-17 23:59:51 -04:00
Thomas Harte
e095a622d3 Ensures updates even when the event queue is empty. 2019-10-17 23:59:43 -04:00
Thomas Harte
9ab49065cd Starts to transfer serial line decoding logic into the line itself. 2019-10-17 23:34:39 -04:00
Thomas Harte
ab50f17d87 Silences, temporarily. 2019-10-16 23:34:49 -04:00
Thomas Harte
f5a2e180f9 Mostly but not quite fixes serial work. 2019-10-16 23:34:37 -04:00
Thomas Harte
f2e1584275 Starts working on the GPIP functionality block. 2019-10-16 23:21:25 -04:00
Thomas Harte
0fd8813ddb Attempts to tie an intelligent keyboard to the other end of its serial line. 2019-10-16 23:21:14 -04:00
Thomas Harte
b69180ba01 Corrects documentation error. 2019-10-16 23:19:42 -04:00
Thomas Harte
c352d8ae8c Adds a received_data_ register, that presently can never fill. 2019-10-13 23:04:57 -04:00
Thomas Harte
530e831064 Corrects transmission logic — exactly hitting write_data_time_remaining now works properly. 2019-10-13 21:40:46 -04:00
Thomas Harte
3b165a78f2 Ensures remaining_delays_ is set properly after [reset/flush]_writing. 2019-10-13 21:39:25 -04:00
Thomas Harte
8d87e9eb1c The ACIA actually has two clocks, though on an ST they're both 500,000 Hz. 2019-10-13 21:32:34 -04:00
Thomas Harte
f86dc082bb Ensures serial lines know their writer's clock rate. 2019-10-13 20:41:08 -04:00
Thomas Harte
d7982aa84e JustInTimeActors can now specify a clock divider. 2019-10-13 18:19:39 -04:00
Thomas Harte
516d78f5a8 Attempts to implement transmission interrupts and ClockingHint::Source. 2019-10-12 23:46:57 -04:00
Thomas Harte
8b50a7d6e3 Attempts mostly to implement 6850 output. 2019-10-12 23:14:29 -04:00
Thomas Harte
4bf81d3b90 Decodes the 6850 control register, and starts working on standardised serial ports. 2019-10-12 18:19:55 -04:00
Thomas Harte
cd75978e4e Nudges 6850 towards coherence. 2019-10-12 00:04:02 -04:00
Thomas Harte
fda99d9c5f Ensures all 16 data lines reach the video. 2019-10-10 23:29:46 -04:00
Thomas Harte
c5ebf75351 Attempts to start producing actual video. 2019-10-10 22:46:58 -04:00
Thomas Harte
2581b520af Adds the option to affix a standard prefix to log messages. 2019-10-10 22:45:03 -04:00
Thomas Harte
52e5296544 Provides a token something where DMA should be. 2019-10-10 21:04:41 -04:00
Thomas Harte
d7ce2c26e8 Adds an empty shell for the ACIA. 2019-10-10 20:54:29 -04:00
Thomas Harte
f88e1b1373 Adds enough logic to advance to an ACIA access error. 2019-10-09 23:01:11 -04:00
Thomas Harte
021d4dbaf1 Makes an attempt at tracking video sequence points. 2019-10-08 23:06:50 -04:00
Thomas Harte
dbde8f2ee7 Takes a shot at other display outputs. 2019-10-08 22:29:58 -04:00
Thomas Harte
5d06930df4 Corrects sync generation. 2019-10-08 21:29:17 -04:00
Thomas Harte
7722596a3b Makes a first attempt to box out the ST display area. 2019-10-08 21:18:08 -04:00
Thomas Harte
1de1818ebb Makes an unsuccessful first attempt at some timer functionality. 2019-10-07 22:44:35 -04:00
Thomas Harte
885f890df1 Adds a target for MFP read/write operations.
Completely without any implementation, so far.
2019-10-06 23:14:05 -04:00
Thomas Harte
e195497ab7 Adds some semblance of an AY. 2019-10-06 22:30:48 -04:00
Thomas Harte
fcd2143697 Starts to formalise the ST memory map a little. 2019-10-06 17:15:29 -04:00
Thomas Harte
3f45cd2380 Starts forming an Atari ST memory map. 2019-10-04 22:38:46 -04:00
Thomas Harte
a8a34497ff Gifts the Atari ST a 68000 and non-functional video. 2019-10-04 21:34:15 -04:00
Thomas Harte
953423cc02 Starts to create an actual shell of a machine. 2019-10-03 22:47:57 -04:00
Thomas Harte
a2ca887b99 Removes unused includes. 2019-10-03 22:47:41 -04:00
Thomas Harte
3c5ae9cf8e Implements read-only MSA support. 2019-10-03 22:41:20 -04:00
Thomas Harte
fe621d7e52 Adds a route through the static analyser to the Atari ST. 2019-10-03 22:10:10 -04:00
Thomas Harte
814bb4ec63 Adds an Atari ST enumeration and factory method. 2019-10-03 20:17:26 -04:00
Thomas Harte
e8bc254f3f Merge pull request #661 from TomHarte/ListMasterSystemOptions
Adds missing Master System option enumerations.
2019-09-30 21:17:20 -04:00
Thomas Harte
3c146a3fb2 Adds missing Master System enumerations. 2019-09-30 21:10:30 -04:00
Thomas Harte
b609ce6fcb Merge pull request #658 from TomHarte/Afterburner
Minor TMS timing correction.
2019-09-28 23:50:28 -04:00
Thomas Harte
929475d31e Minor correction: round down, not up. 2019-09-28 23:49:32 -04:00
Thomas Harte
f14d98452e Merge pull request #657 from TomHarte/DiskIIAgain
Corrects time propagation for Apple II cards
2019-09-28 23:40:07 -04:00
Thomas Harte
9d17d48bca Ensures cycles_per_revolution_ is populated for fixed-rate drives. 2019-09-28 23:23:15 -04:00
Thomas Harte
4ac3839185 Seeks to ensure that card transitions between real-time and just-in-time don't break timing. 2019-09-28 18:34:04 -04:00
Thomas Harte
c089d1cd09 Improves text; nobody normal knows that this is "a view". 2019-09-24 22:52:08 -04:00
Thomas Harte
cb85ec25cc Merge pull request #656 from TomHarte/MacMenu
Adds Macintosh choice to File -> New...
2019-09-24 22:50:43 -04:00
Thomas Harte
fbf95ec2b8 Removes the now empty local namespace. 2019-09-24 22:48:47 -04:00
Thomas Harte
6adca98f34 Adds Macintosh choice to File -> New... 2019-09-24 22:48:34 -04:00
Thomas Harte
48f4d8b875 Merge pull request #655 from TomHarte/EventProtocol
Rationalises protocol for application-level event theft.
2019-09-24 22:32:22 -04:00
Thomas Harte
7758f9d0a9 Improves nomenclature. 2019-09-24 22:31:36 -04:00
Thomas Harte
7112f0336c Rationalises protocol for application-level event theft. 2019-09-24 22:31:20 -04:00
Thomas Harte
298694a881 Merge pull request #654 from TomHarte/GoodbyeFastTapeProtocol
Eliminates fast loading Objective-C/Swift protocol.
2019-09-24 20:22:08 -04:00
Thomas Harte
7ff4594f09 Eliminates fast loading Objective-C/Swift protocol.
A very long time ago, when each machine had its own Objective-C class, this protocol was used to indicate which machines support that feature. It no longer communicates anything.
2019-09-24 20:13:09 -04:00
Thomas Harte
e8bd538182 Merge pull request #653 from TomHarte/CommandCapture
Allows machines to declare modifiers as 'essential'.
2019-09-22 14:04:06 -04:00
Thomas Harte
8489e8650f Attempts another draft of not inundating the user with open file dialogues. 2019-09-22 13:59:31 -04:00
Thomas Harte
114f81941e Completes the wiring necessary for capture of the command key.
At least when coupled with mouse capture.
2019-09-22 13:53:38 -04:00
Thomas Harte
077c7d767f Shifts essential modifiers up to the Keyboard class.
I had forgotten that mappers are not exposed.
2019-09-22 13:48:50 -04:00
Thomas Harte
8f88addf9f Establishes an interface for requesting shortcut theft. Not yet implemented. 2019-09-22 13:15:35 -04:00
Thomas Harte
f28c124039 Adds a route to divert key events before they reach Cocoa.
If you were to use this, it would have the effect of disabling in-app keyboard short-cuts in favour of routing keys to the emulated machine.
2019-09-22 13:15:01 -04:00
Thomas Harte
a416bc0058 Adds an interface allowing keyboard mappers to declare modifiers that are 'essential'.
i.e. ones that, if not delivered reliably, will cause the related machine to behave unexpectedly.
2019-09-22 13:14:09 -04:00
Thomas Harte
e78b1dcf3c Merge pull request #652 from TomHarte/Xcode11
Updates to Xcode11 recommended project settings.
2019-09-22 12:19:39 -04:00
Thomas Harte
8a14f5d814 Updates to Xcode11 recommended project settings.
The updated compiler also flagged a potential issue with CPU::Z80::Register not being a namespace re: 'Refresh' versus CPU::Z80::PartialMachineCycle. I don't entirely see it, but this fixes the problem.

I also finally figured out what the compiler was trying to tell me about ROMRequester.xib.
2019-09-22 12:13:56 -04:00
Thomas Harte
e5f983fbac Merge pull request #651 from TomHarte/MacOpenWindow
Shows the auto-open dialogue only at most once.
2019-09-21 18:01:54 -04:00
Thomas Harte
3e639e96e7 Shows the auto-open dialogue only at most once. 2019-09-21 18:01:16 -04:00
Thomas Harte
61993f0687 Merge pull request #650 from TomHarte/DeadReference
Removes dead reference to video from VIAPortHandler.
2019-09-21 17:40:22 -04:00
Thomas Harte
5f16fa8c08 Removes dead reference to video from VIAPortHandler. 2019-09-21 17:39:45 -04:00
Thomas Harte
dcea9c9ab2 Merge pull request #649 from TomHarte/BetterTiming
Implements every-other-cycle-during-pixels RAM timing.
2019-09-21 17:35:28 -04:00
Thomas Harte
e7bf0799b6 Implements every-other-cycle-during-pixels RAM timing. 2019-09-21 17:25:20 -04:00
Thomas Harte
e760421f6f Merge pull request #648 from TomHarte/QuickBoot
Adds the option to skip the Mac Plus memory test
2019-09-19 22:47:07 -04:00
Thomas Harte
8ea4c17315 Completes Mac GUI wiring. 2019-09-19 22:37:23 -04:00
Thomas Harte
2e24da4614 Implements quick booting, and edges towards exposing it on the Mac.
It should already work in kiosk mode.
2019-09-19 22:32:12 -04:00
Thomas Harte
e46601872b Establishes that the Macintosh offers the quick-boot option. 2019-09-19 21:50:39 -04:00
Thomas Harte
6d0e41b760 Adds "quick boot" as a standard option.
I assume I'm going to reuse this.
2019-09-19 19:41:43 -04:00
Thomas Harte
5a82df837d Merge pull request #647 from TomHarte/SCSIActivity
Adds the SCSI bus as an Activity::Source.
2019-09-19 19:33:06 -04:00
Thomas Harte
776b819a5a Adds the SCSI bus as an Activity::Source. 2019-09-19 19:31:22 -04:00
Thomas Harte
1783f6c84b Update README.md
Corrects alphabetical order.
2019-09-18 22:11:37 -04:00
Thomas Harte
2ef2c73efe The early Macintoshes are less experimental now. 2019-09-18 22:11:04 -04:00
Thomas Harte
55e003ccc1 Merge pull request #646 from TomHarte/LinuxFixes
Corrects hasty merge for more standards-compliant targets
2019-09-18 22:07:57 -04:00
Thomas Harte
3d54d55dbb Adds missing #include for assert. 2019-09-18 22:06:13 -04:00
Thomas Harte
72c0a631f7 Moves includes to correct file. 2019-09-18 22:04:54 -04:00
Thomas Harte
1608a90d5d Takes another stab at finding ssize_t. 2019-09-18 22:03:51 -04:00
Thomas Harte
4f8a45a6ce Adds #include for ssize_t. 2019-09-18 22:02:59 -04:00
Thomas Harte
4f0f1dcf18 Corrects accidental use of #import. 2019-09-18 21:53:22 -04:00
Thomas Harte
839e51d92d Adds newest files to SConstruct. 2019-09-18 21:49:57 -04:00
Thomas Harte
e470cf23d8 Merge pull request #645 from TomHarte/SCSI
Adds basic SCSI support, along with a Mac Plus to use it.
2019-09-18 21:45:48 -04:00
Thomas Harte
8d4a96683a Reduces output noise. 2019-09-18 21:41:29 -04:00
Thomas Harte
f53411a319 Removes local NDEBUG. 2019-09-18 21:35:26 -04:00
Thomas Harte
128a1da626 Enables write support. 2019-09-18 20:18:02 -04:00
Thomas Harte
962275c22a Removes clock for NCR 5380.
It doesn't have one in real life, and now can live off the time counting that occurs on the SCSI bus.
2019-09-18 20:17:47 -04:00
Thomas Harte
3002ac8a4a Adds mapping of READ(8) size 0 to size 256. 2019-09-17 21:59:32 -04:00
Thomas Harte
ff43674638 Corrects partition map: string fields are 32 bytes long. 2019-09-17 21:46:14 -04:00
Thomas Harte
2f6c366668 Makes a concerted effort at properly wrapping a hard disk image. 2019-09-17 21:30:04 -04:00
Thomas Harte
2ce1f0a3b1 Implements multi-sector read/write.
This once again unblocks Apple HD SC Setup. Progress!
2019-09-16 22:20:42 -04:00
Thomas Harte
210129c3a1 Updated as per Inside Macintosh IV.
Of which I now own a copy.
2019-09-16 21:31:43 -04:00
Thomas Harte
934901447a Adds a temporary version of block access writing.
Whatever I'm doing, it's still not correct. The Macintosh ostensibly appears to 0-fill the direct-access device, then reads a sector back and hangs.
2019-09-15 22:06:45 -04:00
Thomas Harte
960b289e70 Edges closer towards proper DMA operation.
Specifically: differentiates the three kinds of DMA operation. Still doesn't act correctly with regard to DACK though, and leaves the bus instantaneously improperly formed. Which I'm tempted to try to fix on the target side by properly obeying delays.
2019-09-15 15:03:06 -04:00
Thomas Harte
243e40cd79 Adds signalling of DACK. 2019-09-14 13:48:33 -04:00
Thomas Harte
c849188016 Adds format and write to the SCSI target.
Now I think I need to switch back to the 5380 to ensure proper DMA mode interactions when writing.
2019-09-12 21:58:09 -04:00
Thomas Harte
87e8dade2f Implements READ BUFFER to do, you know, *something*. Plus READ CAPACITY.
The HD SC utility now offers up drive 6 for formatting. That's progress.
2019-09-11 21:52:02 -04:00
Thomas Harte
6fc5b4e825 Simplifies INQUIRY for future targets; implements enough of SENSE MODE to advance.
The HD SC setup utility is now looking to read buffer.
2019-09-08 21:59:56 -04:00
Thomas Harte
00ce7f8ae0 Takes a first shot at INQUIRY. 2019-09-07 22:04:44 -04:00
Thomas Harte
6e0e9afe2f Fixed: to post a message, I want message in, not message out. 2019-09-07 13:35:38 -04:00
Thomas Harte
cb0d994827 Provides empty data for the unimplemented sectors. 2019-09-07 13:17:53 -04:00
Thomas Harte
bee782234a Ensures no state transitions while acknowledge is still asserted. 2019-09-07 13:17:34 -04:00
Thomas Harte
64dad35026 Decreases logging, at least temporarily. 2019-09-03 22:40:32 -04:00
Thomas Harte
cbd1a8cf78 Factors out command termination, adds a default implementation of test unit ready. 2019-09-03 22:40:18 -04:00
Thomas Harte
a4ab0afce3 Takes a shot at completing a full SCSI interaction. 2019-09-03 21:15:30 -04:00
Thomas Harte
1c7e0f3c9d Fixes control line modification by the 5380 and SCSI target command chaining.
So now I'm back to trying to guess how a SCSI command terminates re: the relative meanings of a message phase and a status phase.
2019-09-02 23:14:37 -04:00
Thomas Harte
318cdb41ea Adds SCSI bus clocking to the Macintosh, and fixes its internal counting. 2019-09-02 16:03:33 -04:00
Thomas Harte
2f8e31bc8b Takes a first bash at implementing the new SCSI::Bus timing infrastructure. 2019-09-02 13:00:01 -04:00
Thomas Harte
310c722cc0 Starts a transition to bus-level knowledge of SCSI-specific bus timing thresholds.
The idea being that bus attachees need not all count time for themselves. They can be very plain finite state machines.

New semantics are not yet implemented within the Bus. The plan is to do that, remove the internal counting of time within the NCR, then adjust the Target to be more explicitly stateful.
2019-08-31 21:44:22 -04:00
Thomas Harte
25956bd90f Mildly improves temporary logging.
A deckchair shuffle, at best.
2019-08-26 22:35:11 -04:00
Thomas Harte
1a60ced61b Starts trying to deal with creating a whole volume from merely a partition. 2019-08-25 23:03:54 -04:00
Thomas Harte
081316c071 Adds some additional commentary as this takes shape. 2019-08-25 17:46:05 -04:00
Thomas Harte
eafbc12cc1 Ensures a clean entry into the command phase. 2019-08-25 17:43:14 -04:00
Thomas Harte
ca08716c52 Introduces real hard disk images to the nascent world of SCSI. 2019-08-25 17:03:41 -04:00
Thomas Harte
30cef1ee22 Adds write support. 2019-08-25 15:16:35 -04:00
Thomas Harte
5598802439 Makes the static analyser aware of mass-storage devices. 2019-08-25 15:10:09 -04:00
Thomas Harte
1c6720b0db Adds new class to Xcode project. 2019-08-25 15:09:43 -04:00
Thomas Harte
404b088199 Adds a trivial mass-storage device, for Macintosh HFV volumes. 2019-08-25 15:09:27 -04:00
Thomas Harte
7d61df238a Localises #include. 2019-08-25 15:09:04 -04:00
Thomas Harte
c86db12f1c Starts implementing DMA support on the 5380.
The Macintosh doesn't actually use the DMA signals, but uses pseudo-DMA mode so they nevertheless need to be appropriate.
2019-08-24 22:47:11 -04:00
Thomas Harte
ce2e85af8b Adds missing bus state callouts. 2019-08-22 23:27:00 -04:00
Thomas Harte
2d82855f26 Attempts to provide a data out phase. 2019-08-22 23:16:58 -04:00
Thomas Harte
faec516a2c Starts pushing towards figuring out a proper infrastructure for mass storage. 2019-08-21 23:22:58 -04:00
Thomas Harte
8e274ec5d0 Merge branch 'master' into SCSI 2019-08-21 22:38:18 -04:00
Thomas Harte
bb1a0a0b76 Sketches out further SCSI infrastructure. 2019-08-21 22:37:39 -04:00
Thomas Harte
252650808d Starts seeking to unbind SCSI bus logic and command performance. 2019-08-19 22:47:01 -04:00
Thomas Harte
e3d9254555 Implements phase-match bit.
Seemingly causing the command phase to proceed.
2019-08-18 23:15:54 -04:00
Thomas Harte
90cf99b626 Takes a wild swings at speeding up startup.
With no success.
2019-08-18 22:40:16 -04:00
Thomas Harte
955e909e61 Attempts to nudge the command phase further towards functioning. 2019-08-18 22:39:27 -04:00
Thomas Harte
8339e2044c Switches to proper SCSI terminology and better attempts a command phase. 2019-08-18 15:10:07 -04:00
Thomas Harte
0e0c789b02 Starts attempting to introduce a direct access device.
Without having access to the SCSI-1 standard, a lot of this is guesswork.
2019-08-17 23:43:42 -04:00
Thomas Harte
7e001c1d03 Corrects data line loading.
Also adds some extra temporary logging. Outstanding question: why is ATN not being signalled? Is SEL enough?
2019-08-17 21:30:59 -04:00
Thomas Harte
9047932b81 Corrected basic error. Arbitration now seems to succeed.
This is seemingly followed by a pattern of signalling BUSY+SEL followed by just SEL with the various other potential device IDs in turn. To which nothing ever responds as currently implemented.
2019-08-15 23:28:30 -04:00
Thomas Harte
f668e4a54c Makes an attempt at getting the 5380 past arbitration.
Not entirely successful. Also gets a bit smarter with `final` on ClockingHint::Sources.
2019-08-15 23:14:40 -04:00
Thomas Harte
ce1c96d68c Starts thinking out the mechanics of emulating a SCSI-1 bus. 2019-08-13 23:09:11 -04:00
Thomas Harte
0f67e490e8 Adjusts NCR address decoding to produce a more plausible initial interaction. 2019-08-11 22:43:25 -04:00
Thomas Harte
895c315fa5 Increases the Mac Plus too 4mb. 2019-08-11 21:41:12 -04:00
Thomas Harte
a90a74a512 Stubs in just enough of the 5380 to get a Mac Plus too boot. 2019-08-11 20:55:20 -04:00
Thomas Harte
3e1286cbef Merge pull request #644 from MaddTheSane/patch-1
Update CSMachine.mm
2019-08-11 19:25:16 -04:00
Thomas Harte
949c1e1668 Adds an empty shell for what will be my 5380 implementation. 2019-08-10 23:53:52 -04:00
Thomas Harte
bbd4e4d3dc Enhances memory map fidelity to allow for ROM holes on the Mac Plus.
This is how the ROM detects the difference between the Plus and the 512ke, it seems.
2019-08-10 23:53:34 -04:00
C.W. Betts
4c5f596533 Update CSMachine.mm
No need to create a temporary NSNumber object to be passed to a variadic method.
2019-08-10 00:43:30 -06:00
Thomas Harte
4859d3781b Merge pull request #643 from TomHarte/Mac512
Simplifies just-in-time usage of the IWM, and the disk-speed accumulator
2019-08-07 21:51:07 -04:00
Thomas Harte
bac0461f7f Switches the drive-speed accumulator to the delegate pattern.
This allows the Macintosh to ensure that the IWM is kept up just-in-time with drive speed changes.
2019-08-07 21:39:23 -04:00
Thomas Harte
f26a200d78 Switches to a JustInTimeActor to wrap the IWM.
Also simplifies potential future usage of the IWM template.
2019-08-07 21:28:02 -04:00
Thomas Harte
28ccb7b54e Merge branch 'master' of github.com:TomHarte/CLK 2019-08-04 21:34:49 -04:00
Thomas Harte
b6e4c8209b Switches to showing 'File -> Open...' at launch.
As per the prevailing wind.
2019-08-04 21:34:30 -04:00
Thomas Harte
16548f0765 Merge pull request #642 from TomHarte/InterruptSampling
Moves timing of interrupt sampling into prefetch queue advancement.
2019-08-04 21:20:22 -04:00
Thomas Harte
6a80832140 Moves timing of interrupt sampling into prefetch queue advancement.
As per comment, that is definitely the only place it can occur; I don't know whether it always occurs there.
2019-08-04 21:06:34 -04:00
Thomas Harte
c6cf0e914b Merge pull request #641 from TomHarte/DIVSTiming
Substantially improves DIVS timing.
2019-08-04 20:46:50 -04:00
Thomas Harte
35b1a55c12 Corrects DIVS negative flag. 2019-08-04 20:36:33 -04:00
Thomas Harte
e3794c0c0e Takes a second pass at DIVS timing, seeming to correct that side of things. 2019-08-04 20:33:43 -04:00
Thomas Harte
f88dc23c71 Corrects comment. 2019-08-04 20:30:41 -04:00
Thomas Harte
0e293e4983 Relocates RAM delay test in order to scrape out a minor performance win. 2019-08-03 21:46:45 -04:00
Thomas Harte
e334abfe20 Partitions the 68000 arithmetic tests, to allow easier per-instruction execution. 2019-08-03 17:44:47 -04:00
Thomas Harte
fd2fbe0e59 Merge pull request #640 from TomHarte/InterruptSignalling
Corrects 68000 address lines during interrupt acknowledgement.
2019-08-03 15:42:15 -04:00
Thomas Harte
330b27d085 Merge branch 'master' into InterruptSignalling 2019-08-03 15:39:22 -04:00
Thomas Harte
478f2533b5 Corrects 68000 address bus during interrupt acknowledge.
All unused bits should be 1, not 0.
2019-08-03 15:38:36 -04:00
Thomas Harte
b96972a4b9 Merge pull request #639 from InvisibleUp/master
Allow for scons to run on Python 3
2019-08-03 08:51:45 -04:00
InvisibleUp
f2b083f4de Allow for scons to run on Python 3 2019-08-03 00:33:53 -04:00
Thomas Harte
80f6d665d9 Merge pull request #638 from TomHarte/SimplifiedBus
Simplifies code around Mac bus decoding.
2019-08-02 22:33:54 -04:00
Thomas Harte
a07488cf1b Introduces the Mac Plus memory map.
Albeit with no SCSI support yet.
2019-08-02 22:26:40 -04:00
Thomas Harte
d67c5145c0 Introduces RAM access delays. 2019-08-02 22:12:34 -04:00
Thomas Harte
5e76d593af Switches to table-based address decoding. 2019-08-02 21:30:04 -04:00
Thomas Harte
83393e8e91 Merge branch 'master' into SimplifiedBus 2019-08-02 21:05:45 -04:00
Thomas Harte
e08a64d455 Fixes erroneous instruction. 2019-08-02 21:04:53 -04:00
Thomas Harte
b93f9b3973 Distinguishes time advancement from bus response. 2019-08-02 19:48:41 -04:00
Thomas Harte
9c517d07d4 Merge pull request #637 from TomHarte/UserInfo
Improves Macintosh user communications
2019-08-02 17:29:44 -04:00
Thomas Harte
f45de5b87a Adds how-to-release-the-mouse instructions for Cocoa. 2019-08-02 17:07:51 -04:00
Thomas Harte
011d76175c Adds mouse release instructions for SDL. 2019-08-02 16:38:05 -04:00
Thomas Harte
96005261c7 Adds activity lights for Macintosh disk activity.
Prompting a quick fix to drives not spinning down.
2019-08-02 16:26:23 -04:00
Thomas Harte
c8177af45a Introduces missing implementation file. 2019-08-02 16:26:08 -04:00
Thomas Harte
97eff5b16d Formally distinguishes Macintosh keys from virtual keys.
Also: adds mappings for keypad keys, and corrects a couple of
long-standing capitalisation errors in my virtual key set.
2019-08-02 16:15:34 -04:00
Thomas Harte
917520fb1e Merge pull request #636 from TomHarte/PWM
Attempts more accurately to match Apple's IWM windowing logic.
2019-08-02 15:15:11 -04:00
Thomas Harte
335dda3d55 Attempts more accurately to match Apple's windowing logic. 2019-08-02 12:49:45 -04:00
394 changed files with 68211 additions and 3810 deletions

22
.github/workflows/ccpp.yml vendored Normal file
View File

@@ -0,0 +1,22 @@
name: SDL/Ubuntu
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Install dependencies
run: sudo apt-get --allow-releaseinfo-change update; sudo apt-get install libsdl2-dev scons
- name: Make
run: cd OSBindings/SDL; scons

View File

@@ -1,13 +0,0 @@
# language: objective-c
# osx_image: xcode8.2
# xcode_project: OSBindings/Mac/Clock Signal.xcodeproj
# xcode_scheme: Clock Signal
# xcode_sdk: macosx10.12
language: cpp
before_install:
- sudo apt-get install libsdl2-dev
script: cd OSBindings/SDL && scons
compiler:
- clang
- gcc

View File

@@ -22,7 +22,7 @@ namespace Dynamic {
class ConfidenceCounter: public ConfidenceSource {
public:
/*! @returns The computed probability, based on the history of events. */
float get_confidence() override;
float get_confidence() final;
/*! Records an event that implies this is the appropriate class: pushes probability up towards 1.0. */
void add_hit();

View File

@@ -32,7 +32,7 @@ class ConfidenceSummary: public ConfidenceSource {
const std::vector<float> &weights);
/*! @returns The weighted sum of all sources. */
float get_confidence() override;
float get_confidence() final;
private:
std::vector<ConfidenceSource *> sources_;

View File

@@ -60,6 +60,13 @@ void MultiCRTMachine::set_scan_target(Outputs::Display::ScanTarget *scan_target)
if(crt_machine) crt_machine->set_scan_target(scan_target);
}
Outputs::Display::ScanStatus MultiCRTMachine::get_scan_status() const {
CRTMachine::Machine *const crt_machine = machines_.front()->crt_machine();
if(crt_machine) crt_machine->get_scan_status();
return Outputs::Display::ScanStatus();
}
Outputs::Speaker::Speaker *MultiCRTMachine::get_speaker() {
return speaker_;
}

View File

@@ -53,12 +53,13 @@ class MultiCRTMachine: public CRTMachine::Machine {
}
// Below is the standard CRTMachine::Machine interface; see there for documentation.
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override;
Outputs::Speaker::Speaker *get_speaker() override;
void run_for(Time::Seconds duration) override;
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final;
Outputs::Display::ScanStatus get_scan_status() const final;
Outputs::Speaker::Speaker *get_speaker() final;
void run_for(Time::Seconds duration) final;
private:
void run_for(const Cycles cycles) override {}
void run_for(const Cycles cycles) final {}
const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines_;
std::recursive_mutex &machines_mutex_;
std::vector<Concurrency::AsyncTaskQueue> queues_;

View File

@@ -28,10 +28,10 @@ class MultiConfigurable: public Configurable::Device {
MultiConfigurable(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
// Below is the standard Configurable::Device interface; see there for documentation.
std::vector<std::unique_ptr<Configurable::Option>> get_options() override;
void set_selections(const Configurable::SelectionSet &selection_by_option) override;
Configurable::SelectionSet get_accurate_selections() override;
Configurable::SelectionSet get_user_friendly_selections() override;
std::vector<std::unique_ptr<Configurable::Option>> get_options() final;
void set_selections(const Configurable::SelectionSet &selection_by_option) final;
Configurable::SelectionSet get_accurate_selections() final;
Configurable::SelectionSet get_user_friendly_selections() final;
private:
std::vector<Configurable::Device *> devices_;

View File

@@ -25,7 +25,7 @@ class MultiJoystick: public Inputs::Joystick {
}
}
std::vector<Input> &get_inputs() override {
std::vector<Input> &get_inputs() final {
if(inputs.empty()) {
for(const auto &joystick: joysticks_) {
std::vector<Input> joystick_inputs = joystick->get_inputs();
@@ -40,19 +40,19 @@ class MultiJoystick: public Inputs::Joystick {
return inputs;
}
void set_input(const Input &digital_input, bool is_active) override {
void set_input(const Input &digital_input, bool is_active) final {
for(const auto &joystick: joysticks_) {
joystick->set_input(digital_input, is_active);
}
}
void set_input(const Input &digital_input, float value) override {
void set_input(const Input &digital_input, float value) final {
for(const auto &joystick: joysticks_) {
joystick->set_input(digital_input, value);
}
}
void reset_all_inputs() override {
void reset_all_inputs() final {
for(const auto &joystick: joysticks_) {
joystick->reset_all_inputs();
}
@@ -81,6 +81,6 @@ MultiJoystickMachine::MultiJoystickMachine(const std::vector<std::unique_ptr<::M
}
}
std::vector<std::unique_ptr<Inputs::Joystick>> &MultiJoystickMachine::get_joysticks() {
const std::vector<std::unique_ptr<Inputs::Joystick>> &MultiJoystickMachine::get_joysticks() {
return joysticks_;
}

View File

@@ -28,7 +28,7 @@ class MultiJoystickMachine: public JoystickMachine::Machine {
MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
// Below is the standard JoystickMachine::Machine interface; see there for documentation.
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override;
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final;
private:
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;

View File

@@ -32,10 +32,10 @@ class MultiKeyboardMachine: public KeyboardMachine::Machine {
public:
MultiKeyboard(const std::vector<::KeyboardMachine::Machine *> &machines);
void set_key_pressed(Key key, char value, bool is_pressed) override;
void reset_all_keys() override;
const std::set<Key> &observed_keys() override;
bool is_exclusive() override;
void set_key_pressed(Key key, char value, bool is_pressed) final;
void reset_all_keys() final;
const std::set<Key> &observed_keys() final;
bool is_exclusive() final;
private:
const std::vector<::KeyboardMachine::Machine *> &machines_;
@@ -48,10 +48,10 @@ class MultiKeyboardMachine: public KeyboardMachine::Machine {
MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
// Below is the standard KeyboardMachine::Machine interface; see there for documentation.
void clear_all_keys() override;
void set_key_state(uint16_t key, bool is_pressed) override;
void type_string(const std::string &) override;
Inputs::Keyboard &get_keyboard() override;
void clear_all_keys() final;
void set_key_state(uint16_t key, bool is_pressed) final;
void type_string(const std::string &) final;
Inputs::Keyboard &get_keyboard() final;
};
}

View File

@@ -29,7 +29,7 @@ struct MultiMediaTarget: public MediaTarget::Machine {
MultiMediaTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
// Below is the standard MediaTarget::Machine interface; see there for documentation.
bool insert_media(const Analyser::Static::Media &media) override;
bool insert_media(const Analyser::Static::Media &media) final;
private:
std::vector<MediaTarget::Machine *> targets_;

View File

@@ -37,9 +37,9 @@ float MultiSpeaker::get_ideal_clock_rate_in_range(float minimum, float maximum)
return ideal / static_cast<float>(speakers_.size());
}
void MultiSpeaker::set_output_rate(float cycles_per_second, int buffer_size) {
void MultiSpeaker::set_computed_output_rate(float cycles_per_second, int buffer_size) {
for(const auto &speaker: speakers_) {
speaker->set_output_rate(cycles_per_second, buffer_size);
speaker->set_computed_output_rate(cycles_per_second, buffer_size);
}
}
@@ -53,7 +53,7 @@ void MultiSpeaker::speaker_did_complete_samples(Speaker *speaker, const std::vec
std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_);
if(speaker != front_speaker_) return;
}
delegate_->speaker_did_complete_samples(this, buffer);
did_complete_samples(this, buffer);
}
void MultiSpeaker::speaker_did_change_input_clock(Speaker *speaker) {

View File

@@ -39,12 +39,12 @@ class MultiSpeaker: public Outputs::Speaker::Speaker, Outputs::Speaker::Speaker:
// Below is the standard Outputs::Speaker::Speaker interface; see there for documentation.
float get_ideal_clock_rate_in_range(float minimum, float maximum) override;
void set_output_rate(float cycles_per_second, int buffer_size) override;
void set_computed_output_rate(float cycles_per_second, int buffer_size) override;
void set_delegate(Outputs::Speaker::Speaker::Delegate *delegate) override;
private:
void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) override;
void speaker_did_change_input_clock(Speaker *speaker) override;
void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) final;
void speaker_did_change_input_clock(Speaker *speaker) final;
MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speakers);
std::vector<Outputs::Speaker::Speaker *> speakers_;

View File

@@ -50,17 +50,17 @@ class MultiMachine: public ::Machine::DynamicMachine, public MultiCRTMachine::De
static bool would_collapse(const std::vector<std::unique_ptr<DynamicMachine>> &machines);
MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines);
Activity::Source *activity_source() override;
Configurable::Device *configurable_device() override;
CRTMachine::Machine *crt_machine() override;
JoystickMachine::Machine *joystick_machine() override;
MouseMachine::Machine *mouse_machine() override;
KeyboardMachine::Machine *keyboard_machine() override;
MediaTarget::Machine *media_target() override;
void *raw_pointer() override;
Activity::Source *activity_source() final;
Configurable::Device *configurable_device() final;
CRTMachine::Machine *crt_machine() final;
JoystickMachine::Machine *joystick_machine() final;
MouseMachine::Machine *mouse_machine() final;
KeyboardMachine::Machine *keyboard_machine() final;
MediaTarget::Machine *media_target() final;
void *raw_pointer() final;
private:
void multi_crt_did_run_machines() override;
void multi_crt_did_run_machines() final;
std::vector<std::unique_ptr<DynamicMachine>> machines_;
std::recursive_mutex machines_mutex_;

View File

@@ -15,6 +15,7 @@ enum class Machine {
AmstradCPC,
AppleII,
Atari2600,
AtariST,
ColecoVision,
Electron,
Macintosh,

View File

@@ -10,7 +10,7 @@
#include "../../../Storage/Disk/Controller/DiskController.hpp"
#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
#include "../../../NumberTheory/CRC.hpp"
#include "../../../Numeric/CRC.hpp"
#include <algorithm>
@@ -18,7 +18,7 @@ using namespace Analyser::Static::Acorn;
std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) {
// c.f. http://beebwiki.mdfs.net/Acorn_DFS_disc_format
std::unique_ptr<Catalogue> catalogue(new Catalogue);
auto catalogue = std::make_unique<Catalogue>();
Storage::Encodings::MFM::Parser parser(false, disk);
Storage::Encodings::MFM::Sector *names = parser.get_sector(0, 0, 0);
@@ -75,7 +75,7 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::s
return catalogue;
}
std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) {
std::unique_ptr<Catalogue> catalogue(new Catalogue);
auto catalogue = std::make_unique<Catalogue>();
Storage::Encodings::MFM::Parser parser(true, disk);
Storage::Encodings::MFM::Sector *free_space_map_second_half = parser.get_sector(0, 0, 1);

View File

@@ -58,7 +58,7 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
}
Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
std::unique_ptr<Target> target(new Target);
auto target = std::make_unique<Target>();
target->machine = Machine::Electron;
target->confidence = 0.5; // TODO: a proper estimation
target->has_dfs = false;

View File

@@ -10,13 +10,13 @@
#include <deque>
#include "../../../NumberTheory/CRC.hpp"
#include "../../../Numeric/CRC.hpp"
#include "../../../Storage/Tape/Parsers/Acorn.hpp"
using namespace Analyser::Static::Acorn;
static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::Tape::Tape> &tape, Storage::Tape::Acorn::Parser &parser) {
std::unique_ptr<File::Chunk> new_chunk(new File::Chunk);
auto new_chunk = std::make_unique<File::Chunk>();
int shift_register = 0;
// TODO: move this into the parser
@@ -90,7 +90,7 @@ static std::unique_ptr<File> GetNextFile(std::deque<File::Chunk> &chunks) {
if(!chunks.size()) return nullptr;
// accumulate chunks for as long as block number is sequential and the end-of-file bit isn't set
std::unique_ptr<File> file(new File);
auto file = std::make_unique<File>();
uint16_t block_number = 0;

View File

@@ -181,7 +181,7 @@ static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, co
Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
TargetList destination;
std::unique_ptr<Target> target(new Target);
auto target = std::make_unique<Target>();
target->machine = Machine::AmstradCPC;
target->confidence = 0.5;

View File

@@ -10,7 +10,7 @@
#include "Target.hpp"
Analyser::Static::TargetList Analyser::Static::AppleII::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
auto target = std::unique_ptr<Target>(new Target);
auto target = std::make_unique<Target>();
target->machine = Machine::AppleII;
target->media = media;

View File

@@ -12,9 +12,10 @@
#include "../Disassembler/6502.hpp"
using namespace Analyser::Static::Atari;
using namespace Analyser::Static::Atari2600;
using Target = Analyser::Static::Atari2600::Target;
static void DeterminePagingFor2kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) {
static void DeterminePagingFor2kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment) {
// if this is a 2kb cartridge then it's definitely either unpaged or a CommaVid
uint16_t entry_address, break_address;
@@ -48,10 +49,10 @@ static void DeterminePagingFor2kCartridge(Analyser::Static::Atari::Target &targe
// caveat: false positives aren't likely to be problematic; a false positive is a 2KB ROM that always addresses
// itself so as to land in ROM even if mapped as a CommaVid and this code is on the fence as to whether it
// attempts to modify itself but it probably doesn't
if(has_wide_area_store) target.paging_model = Analyser::Static::Atari::Target::PagingModel::CommaVid;
if(has_wide_area_store) target.paging_model = Target::PagingModel::CommaVid;
}
static void DeterminePagingFor8kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
static void DeterminePagingFor8kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
// Activision stack titles have their vectors at the top of the low 4k, not the top, and
// always list 0xf000 as both vectors; they do not repeat them, and, inexplicably, they all
// issue an SEI as their first instruction (maybe some sort of relic of the development environment?)
@@ -60,12 +61,12 @@ static void DeterminePagingFor8kCartridge(Analyser::Static::Atari::Target &targe
(segment.data[8191] != 0xf0 || segment.data[8189] != 0xf0 || segment.data[8190] != 0x00 || segment.data[8188] != 0x00) &&
segment.data[0] == 0x78
) {
target.paging_model = Analyser::Static::Atari::Target::PagingModel::ActivisionStack;
target.paging_model = Target::PagingModel::ActivisionStack;
return;
}
// make an assumption that this is the Atari paging model
target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari8k;
target.paging_model = Target::PagingModel::Atari8k;
std::set<uint16_t> internal_accesses;
internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end());
@@ -85,13 +86,13 @@ static void DeterminePagingFor8kCartridge(Analyser::Static::Atari::Target &targe
tigervision_access_count += masked_address == 0x3f;
}
if(parker_access_count > atari_access_count) target.paging_model = Analyser::Static::Atari::Target::PagingModel::ParkerBros;
else if(tigervision_access_count > atari_access_count) target.paging_model = Analyser::Static::Atari::Target::PagingModel::Tigervision;
if(parker_access_count > atari_access_count) target.paging_model = Target::PagingModel::ParkerBros;
else if(tigervision_access_count > atari_access_count) target.paging_model = Target::PagingModel::Tigervision;
}
static void DeterminePagingFor16kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
static void DeterminePagingFor16kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
// make an assumption that this is the Atari paging model
target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari16k;
target.paging_model = Target::PagingModel::Atari16k;
std::set<uint16_t> internal_accesses;
internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end());
@@ -106,17 +107,17 @@ static void DeterminePagingFor16kCartridge(Analyser::Static::Atari::Target &targ
mnetwork_access_count += masked_address >= 0x1fe0 && masked_address < 0x1ffb;
}
if(mnetwork_access_count > atari_access_count) target.paging_model = Analyser::Static::Atari::Target::PagingModel::MNetwork;
if(mnetwork_access_count > atari_access_count) target.paging_model = Target::PagingModel::MNetwork;
}
static void DeterminePagingFor64kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
static void DeterminePagingFor64kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
// make an assumption that this is a Tigervision if there is a write to 3F
target.paging_model =
(disassembly.external_stores.find(0x3f) != disassembly.external_stores.end()) ?
Analyser::Static::Atari::Target::PagingModel::Tigervision : Analyser::Static::Atari::Target::PagingModel::MegaBoy;
Target::PagingModel::Tigervision : Target::PagingModel::MegaBoy;
}
static void DeterminePagingForCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) {
static void DeterminePagingForCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment) {
if(segment.data.size() == 2048) {
DeterminePagingFor2kCartridge(target, segment);
return;
@@ -140,16 +141,16 @@ static void DeterminePagingForCartridge(Analyser::Static::Atari::Target &target,
DeterminePagingFor8kCartridge(target, segment, disassembly);
break;
case 10495:
target.paging_model = Analyser::Static::Atari::Target::PagingModel::Pitfall2;
target.paging_model = Target::PagingModel::Pitfall2;
break;
case 12288:
target.paging_model = Analyser::Static::Atari::Target::PagingModel::CBSRamPlus;
target.paging_model = Target::PagingModel::CBSRamPlus;
break;
case 16384:
DeterminePagingFor16kCartridge(target, segment, disassembly);
break;
case 32768:
target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari32k;
target.paging_model = Target::PagingModel::Atari32k;
break;
case 65536:
DeterminePagingFor64kCartridge(target, segment, disassembly);
@@ -161,8 +162,8 @@ static void DeterminePagingForCartridge(Analyser::Static::Atari::Target &target,
// check for a Super Chip. Atari ROM images [almost] always have the same value stored over RAM
// regions; when they don't they at least seem to have the first 128 bytes be the same as the
// next 128 bytes. So check for that.
if( target.paging_model != Analyser::Static::Atari::Target::PagingModel::CBSRamPlus &&
target.paging_model != Analyser::Static::Atari::Target::PagingModel::MNetwork) {
if( target.paging_model != Target::PagingModel::CBSRamPlus &&
target.paging_model != Target::PagingModel::MNetwork) {
bool has_superchip = true;
for(std::size_t address = 0; address < 128; address++) {
if(segment.data[address] != segment.data[address+128]) {
@@ -174,19 +175,19 @@ static void DeterminePagingForCartridge(Analyser::Static::Atari::Target &target,
}
// check for a Tigervision or Tigervision-esque scheme
if(target.paging_model == Analyser::Static::Atari::Target::PagingModel::None && segment.data.size() > 4096) {
if(target.paging_model == Target::PagingModel::None && segment.data.size() > 4096) {
bool looks_like_tigervision = disassembly.external_stores.find(0x3f) != disassembly.external_stores.end();
if(looks_like_tigervision) target.paging_model = Analyser::Static::Atari::Target::PagingModel::Tigervision;
if(looks_like_tigervision) target.paging_model = Target::PagingModel::Tigervision;
}
}
Analyser::Static::TargetList Analyser::Static::Atari::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
Analyser::Static::TargetList Analyser::Static::Atari2600::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
// TODO: sanity checking; is this image really for an Atari 2600?
std::unique_ptr<Analyser::Static::Atari::Target> target(new Analyser::Static::Atari::Target);
auto target = std::make_unique<Target>();
target->machine = Machine::Atari2600;
target->confidence = 0.5;
target->media.cartridges = media.cartridges;
target->paging_model = Analyser::Static::Atari::Target::PagingModel::None;
target->paging_model = Target::PagingModel::None;
target->uses_superchip = false;
// try to figure out the paging scheme

View File

@@ -15,7 +15,7 @@
namespace Analyser {
namespace Static {
namespace Atari {
namespace Atari2600 {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);

View File

@@ -6,14 +6,14 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_Atari_Target_h
#define Analyser_Static_Atari_Target_h
#ifndef Analyser_Static_Atari2600_Target_h
#define Analyser_Static_Atari2600_Target_h
#include "../StaticAnalyser.hpp"
namespace Analyser {
namespace Static {
namespace Atari {
namespace Atari2600 {
struct Target: public ::Analyser::Static::Target {
enum class PagingModel {

View File

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

View File

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

View File

@@ -0,0 +1,23 @@
//
// Target.hpp
// Clock Signal
//
// Created by Thomas Harte on 03/06/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_AtariST_Target_h
#define Analyser_Static_AtariST_Target_h
namespace Analyser {
namespace Static {
namespace AtariST {
struct Target: public ::Analyser::Static::Target {
};
}
}
}
#endif /* Analyser_Static_AtariST_Target_h */

View File

@@ -54,7 +54,7 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
Analyser::Static::TargetList Analyser::Static::Coleco::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
TargetList targets;
std::unique_ptr<Target> target(new Target);
auto target = std::make_unique<Target>();
target->machine = Machine::ColecoVision;
target->confidence = 1.0f - 1.0f / 32768.0f;
target->media.cartridges = ColecoCartridgesFrom(media.cartridges);

View File

@@ -19,12 +19,10 @@ using namespace Analyser::Static::Commodore;
class CommodoreGCRParser: public Storage::Disk::Controller {
public:
std::shared_ptr<Storage::Disk::Drive> drive;
CommodoreGCRParser() : Storage::Disk::Controller(4000000), shift_register_(0), track_(1) {
drive.reset(new Storage::Disk::Drive(4000000, 300, 2));
set_drive(drive);
drive->set_motor_on(true);
emplace_drive(4000000, 300, 2);
set_drive(1);
get_drive().set_motor_on(true);
}
struct Sector {
@@ -61,6 +59,10 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
return get_sector(sector);
}
void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk) {
get_drive().set_disk(disk);
}
private:
unsigned int shift_register_;
int index_count_;
@@ -125,7 +127,7 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
}
std::shared_ptr<Sector> get_next_sector() {
std::shared_ptr<Sector> sector(new Sector);
auto sector = std::make_shared<Sector>();
const int max_index_count = index_count_ + 2;
while(index_count_ < max_index_count) {
@@ -170,7 +172,7 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk) {
std::vector<File> files;
CommodoreGCRParser parser;
parser.drive->set_disk(disk);
parser.set_disk(disk);
// find any sector whatsoever to establish the current track
std::shared_ptr<CommodoreGCRParser::Sector> sector;

View File

@@ -16,6 +16,7 @@
#include "../../../Outputs/Log.hpp"
#include <algorithm>
#include <cstring>
#include <sstream>
using namespace Analyser::Static::Commodore;
@@ -44,7 +45,7 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
TargetList destination;
std::unique_ptr<Target> target(new Target);
auto target = std::make_unique<Target>();
target->machine = Machine::Vic20; // TODO: machine estimation
target->confidence = 0.5; // TODO: a proper estimation
@@ -78,7 +79,7 @@ Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media
}
if(!files.empty()) {
target->memory_model = Target::MemoryModel::Unexpanded;
auto memory_model = Target::MemoryModel::Unexpanded;
std::ostringstream string_stream;
string_stream << "LOAD\"" << (is_disk ? "*" : "") << "\"," << device << ",";
if(files.front().is_basic()) {
@@ -94,16 +95,18 @@ Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media
default:
LOG("Unrecognised loading address for Commodore program: " << PADHEX(4) << files.front().starting_address);
case 0x1001:
target->memory_model = Target::MemoryModel::Unexpanded;
memory_model = Target::MemoryModel::Unexpanded;
break;
case 0x1201:
target->memory_model = Target::MemoryModel::ThirtyTwoKB;
memory_model = Target::MemoryModel::ThirtyTwoKB;
break;
case 0x0401:
target->memory_model = Target::MemoryModel::EightKB;
memory_model = Target::MemoryModel::EightKB;
break;
}
target->set_memory_model(memory_model);
// General approach: increase memory size conservatively such that the largest file found will fit.
// for(File &file : files) {
// std::size_t file_size = file.data.size();
@@ -145,13 +148,52 @@ Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media
}
if(!target->media.empty()) {
// Inspect filename for a region hint.
// Inspect filename for configuration hints.
std::string lowercase_name = file_name;
std::transform(lowercase_name.begin(), lowercase_name.end(), lowercase_name.begin(), ::tolower);
// Hint 1: 'ntsc' anywhere in the name implies America.
if(lowercase_name.find("ntsc") != std::string::npos) {
target->region = Analyser::Static::Commodore::Target::Region::American;
}
// Potential additional hints: check for TheC64 tags.
auto final_underscore = lowercase_name.find_last_of('_');
if(final_underscore != std::string::npos) {
auto iterator = lowercase_name.begin() + ssize_t(final_underscore) + 1;
while(iterator != lowercase_name.end()) {
// Grab the next tag.
char next_tag[3] = {0, 0, 0};
next_tag[0] = *iterator++;
if(iterator == lowercase_name.end()) break;
next_tag[1] = *iterator++;
// Exit early if attempting to read another tag has run over the file extension.
if(next_tag[0] == '.' || next_tag[1] == '.') break;
// Check whether it's anything.
target->enabled_ram.bank0 |= !strcmp(next_tag, "b0");
target->enabled_ram.bank1 |= !strcmp(next_tag, "b1");
target->enabled_ram.bank2 |= !strcmp(next_tag, "b2");
target->enabled_ram.bank3 |= !strcmp(next_tag, "b3");
target->enabled_ram.bank5 |= !strcmp(next_tag, "b5");
if(!strcmp(next_tag, "tn")) { // i.e. NTSC.
target->region = Analyser::Static::Commodore::Target::Region::American;
}
if(!strcmp(next_tag, "tp")) { // i.e. PAL.
target->region = Analyser::Static::Commodore::Target::Region::European;
}
// Unhandled:
//
// M6: this is a C64 file.
// MV: this is a Vic-20 file.
// J1/J2: this C64 file should have the primary joystick in slot 1/2.
// RO: this disk image should be treated as read-only.
}
}
// Attach a 1540 if there are any disks here.
target->has_c1540 = !target->media.disks.empty();

View File

@@ -31,7 +31,26 @@ struct Target: public ::Analyser::Static::Target {
Swedish
};
MemoryModel memory_model = MemoryModel::Unexpanded;
/// Maps from a named memory model to a bank enabled/disabled set.
void set_memory_model(MemoryModel memory_model) {
// This is correct for unexpanded and 32kb memory models.
enabled_ram.bank0 = enabled_ram.bank1 =
enabled_ram.bank2 = enabled_ram.bank3 =
enabled_ram.bank5 = memory_model == MemoryModel::ThirtyTwoKB;
// Bank 0 will need to be enabled if this is an 8kb machine.
enabled_ram.bank0 |= memory_model == MemoryModel::EightKB;
}
struct {
bool bank0 = false;
bool bank1 = false;
bool bank2 = false;
bool bank3 = false;
bool bank5 = false;
// Sic. There is no bank 4; this is because the area that logically would be
// bank 4 is occupied by the character ROM, colour RAM, hardware registers, etc.
} enabled_ram;
Region region = Region::European;
bool has_c1540 = false;
std::string loading_command;

View File

@@ -34,7 +34,7 @@ static std::unique_ptr<Analyser::Static::Target> CartridgeTarget(
output_segments.emplace_back(start_address, segment.data);
}
std::unique_ptr<Analyser::Static::MSX::Target> target(new Analyser::Static::MSX::Target);
auto target = std::make_unique<Analyser::Static::MSX::Target>();
target->machine = Analyser::Machine::MSX;
target->confidence = confidence;
@@ -269,7 +269,7 @@ Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(const Media &medi
std::move(cartridge_targets.begin(), cartridge_targets.end(), std::back_inserter(destination));
// Consider building a target for disks and/or tapes.
std::unique_ptr<Target> target(new Target);
auto target = std::make_unique<Target>();
// Check tapes for loadable files.
for(auto &tape : media.tapes) {

View File

@@ -10,10 +10,10 @@
#include "Target.hpp"
Analyser::Static::TargetList Analyser::Static::Macintosh::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
// This analyser can comprehend disks only.
if(media.disks.empty()) return {};
// This analyser can comprehend disks and mass-storage devices only.
if(media.disks.empty() && media.mass_storage_devices.empty()) return {};
// If there is at least one disk, wave it through.
// As there is at least one usable media image, wave it through.
Analyser::Static::TargetList targets;
using Target = Analyser::Static::Macintosh::Target;

View File

@@ -21,7 +21,7 @@ struct Target: public ::Analyser::Static::Target {
MacPlus
};
Model model = Model::Mac512ke;
Model model = Model::MacPlus;
};
}

View File

@@ -20,7 +20,9 @@
using namespace Analyser::Static::Oric;
static int Score(const Analyser::Static::MOS6502::Disassembly &disassembly, const std::set<uint16_t> &rom_functions, const std::set<uint16_t> &variable_locations) {
namespace {
int score(const Analyser::Static::MOS6502::Disassembly &disassembly, const std::set<uint16_t> &rom_functions, const std::set<uint16_t> &variable_locations) {
int score = 0;
for(const auto address : disassembly.outward_calls) score += (rom_functions.find(address) != rom_functions.end()) ? 1 : -1;
@@ -30,7 +32,7 @@ static int Score(const Analyser::Static::MOS6502::Disassembly &disassembly, cons
return score;
}
static int Basic10Score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
int basic10_score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
const std::set<uint16_t> rom_functions = {
0x0228, 0x022b,
0xc3ca, 0xc3f8, 0xc448, 0xc47c, 0xc4b5, 0xc4e3, 0xc4e0, 0xc524, 0xc56f, 0xc5a2, 0xc5f8, 0xc60a, 0xc6a5, 0xc6de, 0xc719, 0xc738,
@@ -51,10 +53,10 @@ static int Basic10Score(const Analyser::Static::MOS6502::Disassembly &disassembl
0x0228, 0x0229, 0x022a, 0x022b, 0x022c, 0x022d, 0x0230
};
return Score(disassembly, rom_functions, variable_locations);
return score(disassembly, rom_functions, variable_locations);
}
static int Basic11Score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
int basic11_score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
const std::set<uint16_t> rom_functions = {
0x0238, 0x023b, 0x023e, 0x0241, 0x0244, 0x0247,
0xc3c6, 0xc3f4, 0xc444, 0xc47c, 0xc4a8, 0xc4d3, 0xc4e0, 0xc524, 0xc55f, 0xc592, 0xc5e8, 0xc5fa, 0xc692, 0xc6b3, 0xc6ee, 0xc70d,
@@ -76,10 +78,10 @@ static int Basic11Score(const Analyser::Static::MOS6502::Disassembly &disassembl
0x0244, 0x0245, 0x0246, 0x0247, 0x0248, 0x0249, 0x024a, 0x024b, 0x024c
};
return Score(disassembly, rom_functions, variable_locations);
return score(disassembly, rom_functions, variable_locations);
}
static bool IsMicrodisc(Storage::Encodings::MFM::Parser &parser) {
bool is_microdisc(Storage::Encodings::MFM::Parser &parser) {
/*
The Microdisc boot sector is sector 2 of track 0 and contains a 23-byte signature.
*/
@@ -100,8 +102,51 @@ static bool IsMicrodisc(Storage::Encodings::MFM::Parser &parser) {
return !std::memcmp(signature, first_sample.data(), sizeof(signature));
}
bool is_400_loader(Storage::Encodings::MFM::Parser &parser, uint16_t range_start, uint16_t range_end) {
/*
Both the Jasmin and BD-DOS boot sectors are sector 1 of track 0 and are loaded at $400;
use disassembly to test for likely matches.
*/
Storage::Encodings::MFM::Sector *sector = parser.get_sector(0, 0, 1);
if(!sector) return false;
if(sector->samples.empty()) return false;
// Take a copy of the first sampling, and keep only the final 256 bytes (assuming at least that many were found).
std::vector<uint8_t> first_sample = sector->samples[0];
if(first_sample.size() < 256) return false;
if(first_sample.size() > 256) {
first_sample.erase(first_sample.end() - 256, first_sample.end());
}
// Grab a disassembly.
const auto disassembly =
Analyser::Static::MOS6502::Disassemble(first_sample, Analyser::Static::Disassembler::OffsetMapper(0x400), {0x400});
// Check for references to the Jasmin registers.
int register_hits = 0;
for(auto list : {disassembly.external_stores, disassembly.external_loads, disassembly.external_modifies}) {
for(auto address : list) {
register_hits += (address >= range_start && address <= range_end);
}
}
// Arbitrary, sure, but as long as at least two accesses to the requested register range are found, accept this.
return register_hits >= 2;
}
bool is_jasmin(Storage::Encodings::MFM::Parser &parser) {
return is_400_loader(parser, 0x3f4, 0x3ff);
}
bool is_bd500(Storage::Encodings::MFM::Parser &parser) {
return is_400_loader(parser, 0x310, 0x323);
}
}
Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
std::unique_ptr<Target> target(new Target);
auto target = std::make_unique<Target>();
target->machine = Machine::Oric;
target->confidence = 0.5;
@@ -115,12 +160,10 @@ Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &med
for(const auto &file : tape_files) {
if(file.data_type == File::MachineCode) {
std::vector<uint16_t> entry_points = {file.starting_address};
Analyser::Static::MOS6502::Disassembly disassembly =
const Analyser::Static::MOS6502::Disassembly disassembly =
Analyser::Static::MOS6502::Disassemble(file.data, Analyser::Static::Disassembler::OffsetMapper(file.starting_address), entry_points);
int basic10_score = Basic10Score(disassembly);
int basic11_score = Basic11Score(disassembly);
if(basic10_score > basic11_score) basic10_votes++; else basic11_votes++;
if(basic10_score(disassembly) > basic11_score(disassembly)) ++basic10_votes; else ++basic11_votes;
}
}
@@ -130,12 +173,22 @@ Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &med
}
if(!media.disks.empty()) {
// Only the Microdisc is emulated right now, so accept only disks that it can boot from.
// 8-DOS is recognised by a dedicated Disk II analyser, so check only for Microdisc,
// Jasmin and BD-DOS formats here.
for(auto &disk: media.disks) {
Storage::Encodings::MFM::Parser parser(true, disk);
if(IsMicrodisc(parser)) {
if(is_microdisc(parser)) {
target->disk_interface = Target::DiskInterface::Microdisc;
target->media.disks.push_back(disk);
} else if(is_jasmin(parser)) {
target->disk_interface = Target::DiskInterface::Jasmin;
target->should_start_jasmin = true;
target->media.disks.push_back(disk);
} else if(is_bd500(parser)) {
target->disk_interface = Target::DiskInterface::BD500;
target->media.disks.push_back(disk);
target->rom = Target::ROM::BASIC10;
}
}
}

View File

@@ -26,12 +26,15 @@ struct Target: public ::Analyser::Static::Target {
enum class DiskInterface {
Microdisc,
Pravetz,
Jasmin,
BD500,
None
};
ROM rom = ROM::BASIC11;
DiskInterface disk_interface = DiskInterface::None;
std::string loading_command;
bool should_start_jasmin = false;
};
}

View File

@@ -18,7 +18,7 @@ Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(const Media &med
return {};
TargetList targets;
std::unique_ptr<Target> target(new Target);
auto target = std::make_unique<Target>();
target->machine = Machine::MasterSystem;

View File

@@ -17,7 +17,8 @@
#include "Acorn/StaticAnalyser.hpp"
#include "AmstradCPC/StaticAnalyser.hpp"
#include "AppleII/StaticAnalyser.hpp"
#include "Atari/StaticAnalyser.hpp"
#include "Atari2600/StaticAnalyser.hpp"
#include "AtariST/StaticAnalyser.hpp"
#include "Coleco/StaticAnalyser.hpp"
#include "Commodore/StaticAnalyser.hpp"
#include "DiskII/StaticAnalyser.hpp"
@@ -40,12 +41,18 @@
#include "../../Storage/Disk/DiskImage/Formats/G64.hpp"
#include "../../Storage/Disk/DiskImage/Formats/DMK.hpp"
#include "../../Storage/Disk/DiskImage/Formats/HFE.hpp"
#include "../../Storage/Disk/DiskImage/Formats/MSA.hpp"
#include "../../Storage/Disk/DiskImage/Formats/MSXDSK.hpp"
#include "../../Storage/Disk/DiskImage/Formats/NIB.hpp"
#include "../../Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp"
#include "../../Storage/Disk/DiskImage/Formats/SSD.hpp"
#include "../../Storage/Disk/DiskImage/Formats/ST.hpp"
#include "../../Storage/Disk/DiskImage/Formats/STX.hpp"
#include "../../Storage/Disk/DiskImage/Formats/WOZ.hpp"
// Mass Storage Devices (i.e. usually, hard disks)
#include "../../Storage/MassStorage/Formats/HFV.hpp"
// Tapes
#include "../../Storage/Tape/Formats/CAS.hpp"
#include "../../Storage/Tape/Formats/CommodoreTAP.hpp"
@@ -102,7 +109,8 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
Format("dsd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // DSD
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::CPCDSK>, TargetPlatform::AmstradCPC) // DSK (Amstrad CPC)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // DSK (Apple II)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // DSK (Macintosh)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // DSK (Macintosh, floppy disk)
Format("dsk", result.mass_storage_devices, MassStorage::HFV, TargetPlatform::Macintosh) // DSK (Macintosh, hard disk)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MSXDSK>, TargetPlatform::MSX) // DSK (MSX)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::OricMFMDSK>, TargetPlatform::Oric) // DSK (Oric)
Format("g64", result.disks, Disk::DiskImageHolder<Storage::Disk::G64>, TargetPlatform::Commodore) // G64
@@ -113,6 +121,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
// HFE (TODO: switch to AllDisk once the MSX stops being so greedy)
Format("img", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // IMG (DiskCopy 4.2)
Format("image", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // IMG (DiskCopy 4.2)
Format("msa", result.disks, Disk::DiskImageHolder<Storage::Disk::MSA>, TargetPlatform::AtariST) // MSA
Format("nib", result.disks, Disk::DiskImageHolder<Storage::Disk::NIB>, TargetPlatform::DiskII) // NIB
Format("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // O
Format("p", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P
@@ -138,6 +147,8 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
Format("sg", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega) // SG
Format("sms", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega) // SMS
Format("ssd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // SSD
Format("st", result.disks, Disk::DiskImageHolder<Storage::Disk::ST>, TargetPlatform::AtariST) // ST
Format("stx", result.disks, Disk::DiskImageHolder<Storage::Disk::STX>, TargetPlatform::AtariST) // STX
Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP (Commodore)
Format("tap", result.tapes, Tape::OricTAP, TargetPlatform::Oric) // TAP (Oric)
Format("tsx", result.tapes, Tape::TZX, TargetPlatform::MSX) // TSX
@@ -160,7 +171,7 @@ Media Analyser::Static::GetMedia(const std::string &file_name) {
TargetList Analyser::Static::GetTargets(const std::string &file_name) {
TargetList targets;
// Collect all disks, tapes and ROMs as can be extrapolated from this file, forming the
// Collect all disks, tapes ROMs, etc as can be extrapolated from this file, forming the
// union of all platforms this file might be a target for.
TargetPlatform::IntType potential_platforms = 0;
Media media = GetMediaAndPlatforms(file_name, potential_platforms);
@@ -174,7 +185,8 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) {
if(potential_platforms & TargetPlatform::Acorn) Append(Acorn);
if(potential_platforms & TargetPlatform::AmstradCPC) Append(AmstradCPC);
if(potential_platforms & TargetPlatform::AppleII) Append(AppleII);
if(potential_platforms & TargetPlatform::Atari2600) Append(Atari);
if(potential_platforms & TargetPlatform::Atari2600) Append(Atari2600);
if(potential_platforms & TargetPlatform::AtariST) Append(AtariST);
if(potential_platforms & TargetPlatform::ColecoVision) Append(Coleco);
if(potential_platforms & TargetPlatform::Commodore) Append(Commodore);
if(potential_platforms & TargetPlatform::DiskII) Append(DiskII);

View File

@@ -11,9 +11,10 @@
#include "../Machines.hpp"
#include "../../Storage/Tape/Tape.hpp"
#include "../../Storage/Disk/Disk.hpp"
#include "../../Storage/Cartridge/Cartridge.hpp"
#include "../../Storage/Disk/Disk.hpp"
#include "../../Storage/MassStorage/MassStorageDevice.hpp"
#include "../../Storage/Tape/Tape.hpp"
#include <memory>
#include <string>
@@ -29,9 +30,10 @@ struct Media {
std::vector<std::shared_ptr<Storage::Disk::Disk>> disks;
std::vector<std::shared_ptr<Storage::Tape::Tape>> tapes;
std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> cartridges;
std::vector<std::shared_ptr<Storage::MassStorage::MassStorageDevice>> mass_storage_devices;
bool empty() const {
return disks.empty() && tapes.empty() && cartridges.empty();
return disks.empty() && tapes.empty() && cartridges.empty() && mass_storage_devices.empty();
}
};

View File

@@ -10,6 +10,7 @@
#define ClockReceiver_hpp
#include "ForceInline.hpp"
#include <cstdint>
/*
Informal pattern for all classes that run from a clock cycle:
@@ -54,7 +55,9 @@
*/
template <class T> class WrappedInt {
public:
forceinline constexpr WrappedInt(int l) noexcept : length_(l) {}
using IntType = int64_t;
forceinline constexpr WrappedInt(IntType l) noexcept : length_(l) {}
forceinline constexpr WrappedInt() noexcept : length_(0) {}
forceinline T &operator =(const T &rhs) {
@@ -133,16 +136,20 @@ template <class T> class WrappedInt {
forceinline constexpr bool operator !() const { return !length_; }
// bool operator () is not supported because it offers an implicit cast to int, which is prone silently to permit misuse
forceinline constexpr int as_int() const { return length_; }
/// @returns The underlying int, cast to an integral type of your choosing.
template<typename Type = IntType> forceinline constexpr Type as() const { return Type(length_); }
/// @returns The underlying int, in its native form.
forceinline constexpr IntType as_integral() const { return length_; }
/*!
Severs from @c this the effect of dividing by @c divisor; @c this will end up with
the value of @c this modulo @c divisor and @c divided by @c divisor is returned.
*/
forceinline T divide(const T &divisor) {
T result(length_ / divisor.length_);
length_ %= divisor.length_;
return result;
template <typename Result = T> forceinline Result divide(const T &divisor) {
Result r;
static_cast<T *>(this)->fill(r, divisor);
return r;
}
/*!
@@ -161,13 +168,13 @@ template <class T> class WrappedInt {
// classes that use this template.
protected:
int length_;
IntType length_;
};
/// Describes an integer number of whole cycles: pairs of clock signal transitions.
class Cycles: public WrappedInt<Cycles> {
public:
forceinline constexpr Cycles(int l) noexcept : WrappedInt<Cycles>(l) {}
forceinline constexpr Cycles(IntType l) noexcept : WrappedInt<Cycles>(l) {}
forceinline constexpr Cycles() noexcept : WrappedInt<Cycles>() {}
forceinline constexpr Cycles(const Cycles &cycles) noexcept : WrappedInt<Cycles>(cycles.length_) {}
@@ -177,15 +184,20 @@ class Cycles: public WrappedInt<Cycles> {
result.length_ = length_;
length_ = 0;
}
void fill(Cycles &result, const Cycles &divisor) {
result.length_ = length_ / divisor.length_;
length_ %= divisor.length_;
}
};
/// Describes an integer number of half cycles: single clock signal transitions.
class HalfCycles: public WrappedInt<HalfCycles> {
public:
forceinline constexpr HalfCycles(int l) noexcept : WrappedInt<HalfCycles>(l) {}
forceinline constexpr HalfCycles(IntType l) noexcept : WrappedInt<HalfCycles>(l) {}
forceinline constexpr HalfCycles() noexcept : WrappedInt<HalfCycles>() {}
forceinline constexpr HalfCycles(const Cycles &cycles) noexcept : WrappedInt<HalfCycles>(cycles.as_int() * 2) {}
forceinline constexpr HalfCycles(const Cycles &cycles) noexcept : WrappedInt<HalfCycles>(cycles.as_integral() * 2) {}
forceinline constexpr HalfCycles(const HalfCycles &half_cycles) noexcept : WrappedInt<HalfCycles>(half_cycles.length_) {}
/// @returns The number of whole cycles completely covered by this span of half cycles.
@@ -215,6 +227,16 @@ class HalfCycles: public WrappedInt<HalfCycles> {
result.length_ = length_;
length_ = 0;
}
void fill(Cycles &result, const HalfCycles &divisor) {
result = Cycles(length_ / (divisor.length_ << 1));
length_ %= (divisor.length_ << 1);
}
void fill(HalfCycles &result, const HalfCycles &divisor) {
result.length_ = length_ / divisor.length_;
length_ %= divisor.length_;
}
};
// Create a specialisation of WrappedInt::flush for converting HalfCycles to Cycles

View File

@@ -13,62 +13,68 @@
#include <vector>
/*!
A DeferredQueue maintains a list of ordered actions and the times at which
they should happen, and divides a total execution period up into the portions
that occur between those actions, triggering each action when it is reached.
Provides the logic to insert into and traverse a list of future scheduled items.
*/
template <typename TimeUnit> class DeferredQueue {
public:
/// Constructs a DeferredQueue that will call target(period) in between deferred actions.
DeferredQueue(std::function<void(TimeUnit)> &&target) : target_(std::move(target)) {}
/*!
Schedules @c action to occur in @c delay units of time.
Actions must be scheduled in the order they will occur. It is undefined behaviour
to schedule them out of order.
*/
void defer(TimeUnit delay, const std::function<void(void)> &action) {
pending_actions_.emplace_back(delay, action);
}
/*!
Runs for @c length units of time.
The constructor-supplied target will be called with one or more periods that add up to @c length;
any scheduled actions will be called between periods.
*/
void run_for(TimeUnit length) {
// If there are no pending actions, just run for the entire length.
// This should be the normal branch.
if(pending_actions_.empty()) {
target_(length);
// Apply immediately if there's no delay (or a negative delay).
if(delay <= TimeUnit(0)) {
action();
return;
}
// Divide the time to run according to the pending actions.
while(length > TimeUnit(0)) {
TimeUnit next_period = pending_actions_.empty() ? length : std::min(length, pending_actions_[0].delay);
target_(next_period);
length -= next_period;
if(!pending_actions_.empty()) {
// Otherwise enqueue, having subtracted the delay for any preceding events,
// and subtracting from the subsequent, if any.
auto insertion_point = pending_actions_.begin();
while(insertion_point != pending_actions_.end() && insertion_point->delay < delay) {
delay -= insertion_point->delay;
++insertion_point;
}
if(insertion_point != pending_actions_.end()) {
insertion_point->delay -= delay;
}
off_t performances = 0;
for(auto &action: pending_actions_) {
action.delay -= next_period;
if(!action.delay) {
action.action();
++performances;
}
}
if(performances) {
pending_actions_.erase(pending_actions_.begin(), pending_actions_.begin() + performances);
pending_actions_.emplace(insertion_point, delay, action);
} else {
pending_actions_.emplace_back(delay, action);
}
}
/*!
@returns The amount of time until the next enqueued action will occur,
or TimeUnit(-1) if the queue is empty.
*/
TimeUnit time_until_next_action() {
if(pending_actions_.empty()) return TimeUnit(-1);
return pending_actions_.front().delay;
}
/*!
Advances the queue the specified amount of time, performing any actions it reaches.
*/
void advance(TimeUnit time) {
auto erase_iterator = pending_actions_.begin();
while(erase_iterator != pending_actions_.end()) {
erase_iterator->delay -= time;
if(erase_iterator->delay <= TimeUnit(0)) {
time = -erase_iterator->delay;
erase_iterator->action();
++erase_iterator;
} else {
break;
}
}
if(erase_iterator != pending_actions_.begin()) {
pending_actions_.erase(pending_actions_.begin(), erase_iterator);
}
}
private:
std::function<void(TimeUnit)> target_;
// The list of deferred actions.
struct DeferredAction {
TimeUnit delay;
@@ -79,4 +85,40 @@ template <typename TimeUnit> class DeferredQueue {
std::vector<DeferredAction> pending_actions_;
};
/*!
A DeferredQueue maintains a list of ordered actions and the times at which
they should happen, and divides a total execution period up into the portions
that occur between those actions, triggering each action when it is reached.
This list is efficient only for short queues.
*/
template <typename TimeUnit> class DeferredQueuePerformer: public DeferredQueue<TimeUnit> {
public:
/// Constructs a DeferredQueue that will call target(period) in between deferred actions.
DeferredQueuePerformer(std::function<void(TimeUnit)> &&target) : target_(std::move(target)) {}
/*!
Runs for @c length units of time.
The constructor-supplied target will be called with one or more periods that add up to @c length;
any scheduled actions will be called between periods.
*/
void run_for(TimeUnit length) {
auto time_to_next = DeferredQueue<TimeUnit>::time_until_next_action();
while(time_to_next != TimeUnit(-1) && time_to_next <= length) {
target_(time_to_next);
length -= time_to_next;
DeferredQueue<TimeUnit>::advance(time_to_next);
}
DeferredQueue<TimeUnit>::advance(length);
target_(length);
// TODO: optimise this to avoid the multiple std::vector deletes. Find a neat way to expose that solution, maybe?
}
private:
std::function<void(TimeUnit)> target_;
};
#endif /* DeferredQueue_h */

View File

@@ -9,9 +9,9 @@
#ifndef ForceInline_hpp
#define ForceInline_hpp
#ifdef DEBUG
#ifndef NDEBUG
#define forceinline
#define forceinline inline
#else

View File

@@ -10,6 +10,7 @@
#define JustInTime_h
#include "../Concurrency/AsyncTaskQueue.hpp"
#include "ForceInline.hpp"
/*!
A JustInTimeActor holds (i) an embedded object with a run_for method; and (ii) an amount
@@ -21,32 +22,52 @@
Machines that accumulate HalfCycle time but supply to a Cycle-counted device may supply a
separate @c TargetTimeScale at template declaration.
*/
template <class T, class LocalTimeScale, class TargetTimeScale = LocalTimeScale> class JustInTimeActor {
template <class T, int multiplier = 1, int divider = 1, class LocalTimeScale = HalfCycles, class TargetTimeScale = LocalTimeScale> class JustInTimeActor {
public:
/// Constructs a new JustInTimeActor using the same construction arguments as the included object.
template<typename... Args> JustInTimeActor(Args&&... args) : object_(std::forward<Args>(args)...) {}
/// Adds time to the actor.
inline void operator += (const LocalTimeScale &rhs) {
time_since_update_ += rhs;
forceinline void operator += (const LocalTimeScale &rhs) {
if constexpr (multiplier != 1) {
time_since_update_ += rhs * multiplier;
} else {
time_since_update_ += rhs;
}
is_flushed_ = false;
}
/// Flushes all accumulated time and returns a pointer to the included object.
inline T *operator->() {
forceinline T *operator->() {
flush();
return &object_;
}
/// Acts exactly as per the standard ->, but preserves constness.
forceinline const T *operator->() const {
auto non_const_this = const_cast<JustInTimeActor<T, multiplier, divider, LocalTimeScale, TargetTimeScale> *>(this);
non_const_this->flush();
return &object_;
}
/// Returns a pointer to the included object without flushing time.
inline T *last_valid() {
forceinline T *last_valid() {
return &object_;
}
/// Flushes all accumulated time.
inline void flush() {
if(!is_flushed_) object_.run_for(time_since_update_.template flush<TargetTimeScale>());
is_flushed_ = true;
forceinline void flush() {
if(!is_flushed_) {
is_flushed_ = true;
if constexpr (divider == 1) {
const auto duration = time_since_update_.template flush<TargetTimeScale>();
object_.run_for(duration);
} else {
const auto duration = time_since_update_.template divide<TargetTimeScale>(LocalTimeScale(divider));
if(duration > TargetTimeScale(0))
object_.run_for(duration);
}
}
}
private:
@@ -55,12 +76,54 @@ template <class T, class LocalTimeScale, class TargetTimeScale = LocalTimeScale>
bool is_flushed_ = true;
};
/*!
A RealTimeActor presents the same interface as a JustInTimeActor but doesn't defer work.
Time added will be performed immediately.
Its primary purpose is to allow consumers to remain flexible in their scheduling.
*/
template <class T, int multiplier = 1, int divider = 1, class LocalTimeScale = HalfCycles, class TargetTimeScale = LocalTimeScale> class RealTimeActor {
public:
template<typename... Args> RealTimeActor(Args&&... args) : object_(std::forward<Args>(args)...) {}
forceinline void operator += (const LocalTimeScale &rhs) {
if constexpr (multiplier == 1 && divider == 1) {
object_.run_for(TargetTimeScale(rhs));
return;
}
if constexpr (multiplier == 1) {
accumulated_time_ += rhs;
} else {
accumulated_time_ += rhs * multiplier;
}
if constexpr (divider == 1) {
const auto duration = accumulated_time_.template flush<TargetTimeScale>();
object_.run_for(duration);
} else {
const auto duration = accumulated_time_.template divide<TargetTimeScale>(LocalTimeScale(divider));
if(duration > TargetTimeScale(0))
object_.run_for(duration);
}
}
forceinline T *operator->() { return &object_; }
forceinline const T *operator->() const { return &object_; }
forceinline T *last_valid() { return &object_; }
forceinline void flush() {}
private:
T object_;
LocalTimeScale accumulated_time_;
};
/*!
A AsyncJustInTimeActor acts like a JustInTimeActor but additionally contains an AsyncTaskQueue.
Any time the amount of accumulated time crosses a threshold provided at construction time,
the object will be updated on the AsyncTaskQueue.
*/
template <class T, class LocalTimeScale, class TargetTimeScale = LocalTimeScale> class AsyncJustInTimeActor {
template <class T, class LocalTimeScale = HalfCycles, class TargetTimeScale = LocalTimeScale> class AsyncJustInTimeActor {
public:
/// Constructs a new AsyncJustInTimeActor using the same construction arguments as the included object.
template<typename... Args> AsyncJustInTimeActor(TargetTimeScale threshold, Args&&... args) :

View File

@@ -0,0 +1,88 @@
//
// ScanSynchroniser.hpp
// Clock Signal
//
// Created by Thomas Harte on 09/02/2020.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#ifndef ScanSynchroniser_h
#define ScanSynchroniser_h
#include "../Outputs/ScanTarget.hpp"
#include <cmath>
namespace Time {
/*!
Where an emulated machine is sufficiently close to a host machine's frame rate that a small nudge in
its speed multiplier will bring it into frame synchronisation, the ScanSynchroniser provides a sequence of
speed multipliers designed both to adjust the machine to the proper speed and, in a reasonable amount
of time, to bring it into phase.
*/
class ScanSynchroniser {
public:
/*!
@returns @c true if the emulated machine can be synchronised with the host frame output based on its
current @c [scan]status and the host machine's @c frame_duration; @c false otherwise.
*/
bool can_synchronise(const Outputs::Display::ScanStatus &scan_status, double frame_duration) {
ratio_ = 1.0;
if(scan_status.field_duration_gradient < 0.00001) {
// Check out the machine's current frame time.
// If it's within 3% of a non-zero integer multiple of the
// display rate, mark this time window to be split over the sync.
ratio_ = (frame_duration * base_multiplier_) / scan_status.field_duration;
const double integer_ratio = round(ratio_);
if(integer_ratio > 0.0) {
ratio_ /= integer_ratio;
return ratio_ <= maximum_rate_adjustment && ratio_ >= 1.0 / maximum_rate_adjustment;
}
}
return false;
}
/*!
@returns The appropriate speed multiplier for the next frame based on the inputs previously supplied to @c can_synchronise.
Results are undefined if @c can_synchroise returned @c false.
*/
double next_speed_multiplier(const Outputs::Display::ScanStatus &scan_status) {
// The host versus emulated ratio is calculated based on the current perceived frame duration of the machine.
// Either that number is exactly correct or it's already the result of some sort of low-pass filter. So there's
// no benefit to second guessing it here — just take it to be correct.
//
// ... with one slight caveat, which is that it is desireable to adjust phase here, to align vertical sync points.
// So the set speed multiplier may be adjusted slightly to aim for that.
double speed_multiplier = 1.0 / (ratio_ / base_multiplier_);
if(scan_status.current_position > 0.0) {
if(scan_status.current_position < 0.5) speed_multiplier /= phase_adjustment_ratio;
else speed_multiplier *= phase_adjustment_ratio;
}
speed_multiplier_ = (speed_multiplier_ * 0.95) + (speed_multiplier * 0.05);
return speed_multiplier_ * base_multiplier_;
}
void set_base_speed_multiplier(double multiplier) {
base_multiplier_ = multiplier;
}
double get_base_speed_multiplier() {
return base_multiplier_;
}
private:
static constexpr double maximum_rate_adjustment = 1.03;
static constexpr double phase_adjustment_ratio = 1.005;
// Managed local state.
double speed_multiplier_ = 1.0;
double base_multiplier_ = 1.0;
// Temporary storage to bridge the can_synchronise -> next_speed_multiplier gap.
double ratio_ = 1.0;
};
}
#endif /* ScanSynchroniser_h */

View File

@@ -9,9 +9,16 @@
#ifndef TimeTypes_h
#define TimeTypes_h
#include <chrono>
namespace Time {
typedef double Seconds;
typedef int64_t Nanos;
inline Nanos nanos_now() {
return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count();
}
}

View File

@@ -9,6 +9,8 @@
#include "1770.hpp"
#include "../../Storage/Disk/Encodings/MFM/Constants.hpp"
#define LOG_PREFIX "[WD FDC] "
#include "../../Outputs/Log.hpp"
using namespace WD;
@@ -16,19 +18,19 @@ using namespace WD;
WD1770::WD1770(Personality p) :
Storage::Disk::MFMController(8000000),
personality_(p),
interesting_event_mask_(static_cast<int>(Event1770::Command)) {
interesting_event_mask_(int(Event1770::Command)) {
set_is_double_density(false);
posit_event(static_cast<int>(Event1770::Command));
posit_event(int(Event1770::Command));
}
void WD1770::set_register(int address, uint8_t value) {
void WD1770::write(int address, uint8_t value) {
switch(address&3) {
case 0: {
if((value&0xf0) == 0xd0) {
if(value == 0xd0) {
// Force interrupt **immediately**.
LOG("Force interrupt immediately");
posit_event(static_cast<int>(Event1770::ForceInterrupt));
posit_event(int(Event1770::ForceInterrupt));
} else {
ERROR("!!!TODO: force interrupt!!!");
update_status([] (Status &status) {
@@ -37,7 +39,7 @@ void WD1770::set_register(int address, uint8_t value) {
}
} else {
command_ = value;
posit_event(static_cast<int>(Event1770::Command));
posit_event(int(Event1770::Command));
}
}
break;
@@ -52,27 +54,35 @@ void WD1770::set_register(int address, uint8_t value) {
}
}
uint8_t WD1770::get_register(int address) {
uint8_t WD1770::read(int address) {
switch(address&3) {
default: {
update_status([] (Status &status) {
status.interrupt_request = false;
});
uint8_t status =
(status_.write_protect ? Flag::WriteProtect : 0) |
(status_.crc_error ? Flag::CRCError : 0) |
(status_.busy ? Flag::Busy : 0);
(status_.crc_error ? Flag::CRCError : 0) |
(status_.busy ? Flag::Busy : 0);
// Per Jean Louis-Guérin's documentation:
//
// * the write-protect bit is locked into place by a type 2 or type 3 command, but is
// read live after a type 1.
// * the track 0 bit is captured during a type 1 instruction and lost upon any other type,
// it is not live sampled.
switch(status_.type) {
case Status::One:
status |=
(get_drive().get_is_track_zero() ? Flag::TrackZero : 0) |
(status_.seek_error ? Flag::SeekError : 0);
// TODO: index hole
(status_.track_zero ? Flag::TrackZero : 0) |
(status_.seek_error ? Flag::SeekError : 0) |
(get_drive().get_is_read_only() ? Flag::WriteProtect : 0) |
(get_drive().get_index_pulse() ? Flag::Index : 0);
break;
case Status::Two:
case Status::Three:
status |=
(status_.write_protect ? Flag::WriteProtect : 0) |
(status_.record_type ? Flag::RecordType : 0) |
(status_.lost_data ? Flag::LostData : 0) |
(status_.data_request ? Flag::DataRequest : 0) |
@@ -89,10 +99,15 @@ uint8_t WD1770::get_register(int address) {
if(status_.type == Status::One)
status |= (status_.spin_up ? Flag::SpinUp : 0);
}
// LOG("Returned status " << PADHEX(2) << int(status) << " of type " << 1+int(status_.type));
return status;
}
case 1: return track_;
case 2: return sector_;
case 1:
LOG("Returned track " << int(track_));
return track_;
case 2:
LOG("Returned sector " << int(sector_));
return sector_;
case 3:
update_status([] (Status &status) {
status.data_request = false;
@@ -105,28 +120,30 @@ void WD1770::run_for(const Cycles cycles) {
Storage::Disk::Controller::run_for(cycles);
if(delay_time_) {
unsigned int number_of_cycles = static_cast<unsigned int>(cycles.as_int());
const auto number_of_cycles = cycles.as_integral();
if(delay_time_ <= number_of_cycles) {
delay_time_ = 0;
posit_event(static_cast<int>(Event1770::Timer));
posit_event(int(Event1770::Timer));
} else {
delay_time_ -= number_of_cycles;
}
}
}
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(mask); return; case __LINE__:
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = int(mask); return; case __LINE__:
#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; delay_time_ = ms * 8000; WAIT_FOR_EVENT(Event1770::Timer);
#define WAIT_FOR_BYTES(count) resume_point_ = __LINE__; distance_into_section_ = 0; WAIT_FOR_EVENT(Event::Token); if(get_latest_token().type == Token::Byte) distance_into_section_++; if(distance_into_section_ < count) { interesting_event_mask_ = static_cast<int>(Event::Token); return; }
#define WAIT_FOR_BYTES(count) resume_point_ = __LINE__; distance_into_section_ = 0; WAIT_FOR_EVENT(Event::Token); if(get_latest_token().type == Token::Byte) distance_into_section_++; if(distance_into_section_ < count) { interesting_event_mask_ = int(Event::Token); return; }
#define BEGIN_SECTION() switch(resume_point_) { default:
#define END_SECTION() (void)0; }
#define READ_ID() \
if(new_event_type == static_cast<int>(Event::Token)) { \
if(!distance_into_section_ && get_latest_token().type == Token::ID) {set_data_mode(DataMode::Reading); distance_into_section_++; } \
else if(distance_into_section_ && distance_into_section_ < 7 && get_latest_token().type == Token::Byte) { \
if(new_event_type == int(Event::Token)) { \
if(!distance_into_section_ && get_latest_token().type == Token::ID) {\
set_data_mode(DataMode::Reading); \
++distance_into_section_; \
} else if(distance_into_section_ && distance_into_section_ < 7 && get_latest_token().type == Token::Byte) { \
header_[distance_into_section_ - 1] = get_latest_token().byte_value; \
distance_into_section_++; \
++distance_into_section_; \
} \
}
@@ -159,10 +176,10 @@ void WD1770::run_for(const Cycles cycles) {
// +--------+----------+-------------------------+
void WD1770::posit_event(int new_event_type) {
if(new_event_type == static_cast<int>(Event::IndexHole)) {
if(new_event_type == int(Event::IndexHole)) {
index_hole_count_++;
if(index_hole_count_target_ == index_hole_count_) {
posit_event(static_cast<int>(Event1770::IndexHoleTarget));
posit_event(int(Event1770::IndexHoleTarget));
index_hole_count_target_ = -1;
}
@@ -177,15 +194,16 @@ void WD1770::posit_event(int new_event_type) {
}
}
if(new_event_type == static_cast<int>(Event1770::ForceInterrupt)) {
if(new_event_type == int(Event1770::ForceInterrupt)) {
interesting_event_mask_ = 0;
resume_point_ = 0;
update_status([] (Status &status) {
status.type = Status::One;
status.data_request = false;
status.spin_up = false;
});
} else {
if(!(interesting_event_mask_ & static_cast<int>(new_event_type))) return;
if(!(interesting_event_mask_ & int(new_event_type))) return;
interesting_event_mask_ &= ~new_event_type;
}
@@ -208,6 +226,7 @@ void WD1770::posit_event(int new_event_type) {
update_status([] (Status &status) {
status.busy = true;
status.interrupt_request = false;
status.track_zero = false; // Always reset by a non-type 1; so reset regardless and set properly later.
});
LOG("Starting " << PADHEX(2) << int(command_));
@@ -240,6 +259,7 @@ void WD1770::posit_event(int new_event_type) {
status.data_request = false;
});
LOG("Step/Seek/Restore with track " << int(track_) << " data " << int(data_));
if(!has_motor_on_line() && !has_head_load_line()) goto test_type1_type;
if(has_motor_on_line()) goto begin_type1_spin_up;
@@ -272,19 +292,19 @@ void WD1770::posit_event(int new_event_type) {
}
perform_seek_or_restore_command:
if(track_ == data_) goto verify;
if(track_ == data_) goto verify_seek;
step_direction_ = (data_ > track_);
adjust_track:
if(step_direction_) track_++; else track_--;
if(step_direction_) ++track_; else --track_;
perform_step:
if(!step_direction_ && get_drive().get_is_track_zero()) {
track_ = 0;
goto verify;
goto verify_seek;
}
get_drive().step(Storage::Disk::HeadPosition(step_direction_ ? 1 : -1));
unsigned int time_to_wait;
Cycles::IntType time_to_wait;
switch(command_ & 3) {
default:
case 0: time_to_wait = 6; break;
@@ -293,14 +313,17 @@ void WD1770::posit_event(int new_event_type) {
case 3: time_to_wait = (personality_ == P1772) ? 3 : 30; break;
}
WAIT_FOR_TIME(time_to_wait);
if(command_ >> 5) goto verify;
if(command_ >> 5) goto verify_seek;
goto perform_seek_or_restore_command;
perform_step_command:
if(command_ & 0x10) goto adjust_track;
goto perform_step;
verify:
verify_seek:
update_status([this] (Status &status) {
status.track_zero = get_drive().get_is_track_zero();
});
if(!(command_ & 0x04)) {
goto wait_for_command;
}
@@ -309,17 +332,20 @@ void WD1770::posit_event(int new_event_type) {
distance_into_section_ = 0;
verify_read_data:
WAIT_FOR_EVENT(static_cast<int>(Event::IndexHole) | static_cast<int>(Event::Token));
WAIT_FOR_EVENT(int(Event::IndexHole) | int(Event::Token));
READ_ID();
if(index_hole_count_ == 6) {
LOG("Nothing found to verify");
update_status([] (Status &status) {
status.seek_error = true;
});
goto wait_for_command;
}
if(distance_into_section_ == 7) {
distance_into_section_ = 0;
set_data_mode(DataMode::Scanning);
if(get_crc_generator().get_value()) {
update_status([] (Status &status) {
status.crc_error = true;
@@ -334,8 +360,6 @@ void WD1770::posit_event(int new_event_type) {
});
goto wait_for_command;
}
distance_into_section_ = 0;
}
goto verify_read_data;
@@ -392,8 +416,11 @@ void WD1770::posit_event(int new_event_type) {
goto wait_for_command;
}
distance_into_section_ = 0;
set_data_mode(DataMode::Scanning);
type2_get_header:
WAIT_FOR_EVENT(static_cast<int>(Event::IndexHole) | static_cast<int>(Event::Token));
WAIT_FOR_EVENT(int(Event::IndexHole) | int(Event::Token));
READ_ID();
if(index_hole_count_ == 5) {
@@ -404,8 +431,10 @@ void WD1770::posit_event(int new_event_type) {
goto wait_for_command;
}
if(distance_into_section_ == 7) {
LOG("Considering " << std::dec << int(header_[0]) << "/" << int(header_[2]));
distance_into_section_ = 0;
set_data_mode(DataMode::Scanning);
LOG("Considering " << std::dec << int(header_[0]) << "/" << int(header_[2]));
if( header_[0] == track_ && header_[2] == sector_ &&
(has_motor_on_line() || !(command_&0x02) || ((command_&0x08) >> 3) == header_[1])) {
LOG("Found " << std::dec << int(header_[0]) << "/" << int(header_[2]));
@@ -422,7 +451,6 @@ void WD1770::posit_event(int new_event_type) {
});
goto type2_read_or_write_data;
}
distance_into_section_ = 0;
}
goto type2_get_header;
@@ -453,7 +481,7 @@ void WD1770::posit_event(int new_event_type) {
status.data_request = true;
});
distance_into_section_++;
if(distance_into_section_ == 128 << header_[3]) {
if(distance_into_section_ == 128 << (header_[3]&3)) {
distance_into_section_ = 0;
goto type2_check_crc;
}
@@ -465,6 +493,9 @@ void WD1770::posit_event(int new_event_type) {
header_[distance_into_section_] = get_latest_token().byte_value;
distance_into_section_++;
if(distance_into_section_ == 2) {
distance_into_section_ = 0;
set_data_mode(DataMode::Scanning);
if(get_crc_generator().get_value()) {
LOG("CRC error; terminating");
update_status([this] (Status &status) {
@@ -473,11 +504,13 @@ void WD1770::posit_event(int new_event_type) {
goto wait_for_command;
}
LOG("Finished reading sector " << std::dec << int(sector_));
if(command_ & 0x10) {
sector_++;
LOG("Advancing to search for sector " << std::dec << int(sector_));
goto test_type2_write_protection;
}
LOG("Finished reading sector " << std::dec << int(sector_));
goto wait_for_command;
}
goto type2_check_crc;
@@ -531,7 +564,7 @@ void WD1770::posit_event(int new_event_type) {
*/
write_byte(data_);
distance_into_section_++;
if(distance_into_section_ == 128 << header_[3]) {
if(distance_into_section_ == 128 << (header_[3]&3)) {
goto type2_write_crc;
}
@@ -610,8 +643,8 @@ void WD1770::posit_event(int new_event_type) {
distance_into_section_ = 0;
read_address_get_header:
WAIT_FOR_EVENT(static_cast<int>(Event::IndexHole) | static_cast<int>(Event::Token));
if(new_event_type == static_cast<int>(Event::Token)) {
WAIT_FOR_EVENT(int(Event::IndexHole) | int(Event::Token));
if(new_event_type == int(Event::Token)) {
if(!distance_into_section_ && get_latest_token().type == Token::ID) {set_data_mode(DataMode::Reading); distance_into_section_++; }
else if(distance_into_section_ && distance_into_section_ < 7 && get_latest_token().type == Token::Byte) {
if(status_.data_request) {
@@ -625,9 +658,11 @@ void WD1770::posit_event(int new_event_type) {
update_status([] (Status &status) {
status.data_request = true;
});
distance_into_section_++;
++distance_into_section_;
if(distance_into_section_ == 7) {
distance_into_section_ = 0;
if(get_crc_generator().get_value()) {
update_status([] (Status &status) {
status.crc_error = true;
@@ -651,7 +686,7 @@ void WD1770::posit_event(int new_event_type) {
index_hole_count_ = 0;
read_track_read_byte:
WAIT_FOR_EVENT(static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole));
WAIT_FOR_EVENT(int(Event::Token) | int(Event::IndexHole));
if(index_hole_count_) {
goto wait_for_command;
}
@@ -718,7 +753,7 @@ void WD1770::posit_event(int new_event_type) {
case 0xfd: case 0xfe:
// clock is 0xc7 = 1010 0000 0010 1010 = 0xa022
write_raw_short(
static_cast<uint16_t>(
uint16_t(
0xa022 |
((data_ & 0x80) << 7) |
((data_ & 0x40) << 6) |
@@ -767,15 +802,18 @@ void WD1770::posit_event(int new_event_type) {
}
void WD1770::update_status(std::function<void(Status &)> updater) {
const Status old_status = status_;
if(delegate_) {
Status old_status = status_;
updater(status_);
bool did_change =
const bool did_change =
(status_.busy != old_status.busy) ||
(status_.data_request != old_status.data_request);
(status_.data_request != old_status.data_request) ||
(status_.interrupt_request != old_status.interrupt_request);
if(did_change) delegate_->wd1770_did_change_output(this);
}
else updater(status_);
} else updater(status_);
if(status_.busy != old_status.busy) update_clocking_observer();
}
void WD1770::set_head_load_request(bool head_load) {}
@@ -783,5 +821,14 @@ void WD1770::set_motor_on(bool motor_on) {}
void WD1770::set_head_loaded(bool head_loaded) {
head_is_loaded_ = head_loaded;
if(head_loaded) posit_event(static_cast<int>(Event1770::HeadLoad));
if(head_loaded) posit_event(int(Event1770::HeadLoad));
}
bool WD1770::get_head_loaded() {
return head_is_loaded_;
}
ClockingHint::Preference WD1770::preferred_clocking() {
if(status_.busy) return ClockingHint::Preference::RealTime;
return Storage::Disk::MFMController::preferred_clocking();
}

View File

@@ -36,29 +36,29 @@ class WD1770: public Storage::Disk::MFMController {
using Storage::Disk::MFMController::set_is_double_density;
/// Writes @c value to the register at @c address. Only the low two bits of the address are decoded.
void set_register(int address, uint8_t value);
void write(int address, uint8_t value);
/// Fetches the value of the register @c address. Only the low two bits of the address are decoded.
uint8_t get_register(int address);
uint8_t read(int address);
/// Runs the controller for @c number_of_cycles cycles.
void run_for(const Cycles cycles);
enum Flag: uint8_t {
NotReady = 0x80,
NotReady = 0x80, // 0x80
MotorOn = 0x80,
WriteProtect = 0x40,
RecordType = 0x20,
WriteProtect = 0x40, // 0x40
RecordType = 0x20, // 0x20
SpinUp = 0x20,
HeadLoaded = 0x20,
RecordNotFound = 0x10,
RecordNotFound = 0x10, // 0x10
SeekError = 0x10,
CRCError = 0x08,
LostData = 0x04,
CRCError = 0x08, // 0x08
LostData = 0x04, // 0x04
TrackZero = 0x04,
DataRequest = 0x02,
DataRequest = 0x02, // 0x02
Index = 0x02,
Busy = 0x01
Busy = 0x01 // 0x01
};
/// @returns The current value of the IRQ line output.
@@ -73,11 +73,16 @@ class WD1770: public Storage::Disk::MFMController {
};
inline void set_delegate(Delegate *delegate) { delegate_ = delegate; }
ClockingHint::Preference preferred_clocking() final;
protected:
virtual void set_head_load_request(bool head_load);
virtual void set_motor_on(bool motor_on);
void set_head_loaded(bool head_loaded);
/// @returns The last value posted to @c set_head_loaded.
bool get_head_loaded();
private:
Personality personality_;
inline bool has_motor_on_line() { return (personality_ != P1793 ) && (personality_ != P1773); }
@@ -94,6 +99,7 @@ class WD1770: public Storage::Disk::MFMController {
bool data_request = false;
bool interrupt_request = false;
bool busy = false;
bool track_zero = false;
enum {
One, Two, Three
} type = One;
@@ -121,7 +127,7 @@ class WD1770: public Storage::Disk::MFMController {
void posit_event(int type);
int interesting_event_mask_;
int resume_point_ = 0;
unsigned int delay_time_ = 0;
Cycles::IntType delay_time_ = 0;
// ID buffer
uint8_t header_[6];

312
Components/5380/ncr5380.cpp Normal file
View File

@@ -0,0 +1,312 @@
//
// ncr5380.cpp
// Clock Signal
//
// Created by Thomas Harte on 10/08/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#include "ncr5380.hpp"
#include "../../Outputs/Log.hpp"
using namespace NCR::NCR5380;
using SCSI::Line;
NCR5380::NCR5380(SCSI::Bus &bus, int clock_rate) :
bus_(bus),
clock_rate_(clock_rate) {
device_id_ = bus_.add_device();
bus_.add_observer(this);
}
void NCR5380::write(int address, uint8_t value, bool dma_acknowledge) {
switch(address & 7) {
case 0:
// LOG("[SCSI 0] Set current SCSI bus state to " << PADHEX(2) << int(value));
data_bus_ = value;
if(dma_request_ && dma_operation_ == DMAOperation::Send) {
// printf("w %02x\n", value);
dma_acknowledge_ = true;
dma_request_ = false;
update_control_output();
bus_.set_device_output(device_id_, bus_output_);
}
break;
case 1: {
// LOG("[SCSI 1] Initiator command register set: " << PADHEX(2) << int(value));
initiator_command_ = value;
bus_output_ &= ~(Line::Reset | Line::Acknowledge | Line::Busy | Line::SelectTarget | Line::Attention);
if(value & 0x80) bus_output_ |= Line::Reset;
if(value & 0x08) bus_output_ |= Line::Busy;
if(value & 0x04) bus_output_ |= Line::SelectTarget;
/* bit 5 = differential enable if this were a 5381 */
test_mode_ = value & 0x40;
assert_data_bus_ = value & 0x01;
update_control_output();
} break;
case 2:
// LOG("[SCSI 2] Set mode: " << PADHEX(2) << int(value));
mode_ = value;
// bit 7: 1 = use block mode DMA mode (if DMA mode is also enabled)
// bit 6: 1 = be a SCSI target; 0 = be an initiator
// bit 5: 1 = check parity
// bit 4: 1 = generate an interrupt if parity checking is enabled and an error is found
// bit 3: 1 = generate an interrupt when an EOP is received from the DMA controller
// bit 2: 1 = generate an interrupt and reset low 6 bits of register 1 if an unexpected loss of Line::Busy occurs
// bit 1: 1 = use DMA mode
// bit 0: 1 = begin arbitration mode (device ID should be in register 0)
arbitration_in_progress_ = false;
switch(mode_ & 0x3) {
case 0x0:
bus_output_ &= ~SCSI::Line::Busy;
dma_request_ = false;
set_execution_state(ExecutionState::None);
break;
case 0x1:
arbitration_in_progress_ = true;
set_execution_state(ExecutionState::WaitingForBusy);
lost_arbitration_ = false;
break;
default:
assert_data_bus_ = false;
set_execution_state(ExecutionState::PerformingDMA);
bus_.update_observers();
break;
}
update_control_output();
break;
case 3: {
// LOG("[SCSI 3] Set target command: " << PADHEX(2) << int(value));
target_command_ = value;
update_control_output();
} break;
case 4:
// LOG("[SCSI 4] Set select enabled: " << PADHEX(2) << int(value));
break;
case 5:
// LOG("[SCSI 5] Start DMA send: " << PADHEX(2) << int(value));
dma_operation_ = DMAOperation::Send;
break;
case 6:
// LOG("[SCSI 6] Start DMA target receive: " << PADHEX(2) << int(value));
dma_operation_ = DMAOperation::TargetReceive;
break;
case 7:
// LOG("[SCSI 7] Start DMA initiator receive: " << PADHEX(2) << int(value));
dma_operation_ = DMAOperation::InitiatorReceive;
break;
}
// Data is output only if the data bus is asserted.
if(assert_data_bus_) {
bus_output_ = (bus_output_ & ~SCSI::Line::Data) | data_bus_;
} else {
bus_output_ &= ~SCSI::Line::Data;
}
// In test mode, still nothing is output. Otherwise throw out
// the current value of bus_output_.
if(test_mode_) {
bus_.set_device_output(device_id_, SCSI::DefaultBusState);
} else {
bus_.set_device_output(device_id_, bus_output_);
}
}
uint8_t NCR5380::read(int address, bool dma_acknowledge) {
switch(address & 7) {
case 0:
// LOG("[SCSI 0] Get current SCSI bus state: " << PADHEX(2) << (bus_.get_state() & 0xff));
if(dma_request_ && dma_operation_ == DMAOperation::InitiatorReceive) {
dma_acknowledge_ = true;
dma_request_ = false;
update_control_output();
bus_.set_device_output(device_id_, bus_output_);
}
return uint8_t(bus_.get_state());
case 1:
// LOG("[SCSI 1] Initiator command register get: " << (arbitration_in_progress_ ? 'p' : '-') << (lost_arbitration_ ? 'l' : '-'));
return
// Bits repeated as they were set.
(initiator_command_ & ~0x60) |
// Arbitration in progress.
(arbitration_in_progress_ ? 0x40 : 0x00) |
// Lost arbitration.
(lost_arbitration_ ? 0x20 : 0x00);
case 2:
// LOG("[SCSI 2] Get mode");
return mode_;
case 3:
// LOG("[SCSI 3] Get target command");
return target_command_;
case 4: {
const auto bus_state = bus_.get_state();
const uint8_t result =
((bus_state & Line::Reset) ? 0x80 : 0x00) |
((bus_state & Line::Busy) ? 0x40 : 0x00) |
((bus_state & Line::Request) ? 0x20 : 0x00) |
((bus_state & Line::Message) ? 0x10 : 0x00) |
((bus_state & Line::Control) ? 0x08 : 0x00) |
((bus_state & Line::Input) ? 0x04 : 0x00) |
((bus_state & Line::SelectTarget) ? 0x02 : 0x00) |
((bus_state & Line::Parity) ? 0x01 : 0x00);
// LOG("[SCSI 4] Get current bus state: " << PADHEX(2) << int(result));
return result;
}
case 5: {
const auto bus_state = bus_.get_state();
const bool phase_matches =
(target_output() & (Line::Message | Line::Control | Line::Input)) ==
(bus_state & (Line::Message | Line::Control | Line::Input));
const uint8_t result =
/* b7 = end of DMA */
((dma_request_ && state_ == ExecutionState::PerformingDMA) ? 0x40 : 0x00) |
/* b5 = parity error */
/* b4 = IRQ active */
(phase_matches ? 0x08 : 0x00) |
/* b2 = busy error */
((bus_state & Line::Attention) ? 0x02 : 0x00) |
((bus_state & Line::Acknowledge) ? 0x01 : 0x00);
// LOG("[SCSI 5] Get bus and status: " << PADHEX(2) << int(result));
return result;
}
case 6:
// LOG("[SCSI 6] Get input data");
return 0xff;
case 7:
// LOG("[SCSI 7] Reset parity/interrupt");
return 0xff;
}
return 0;
}
SCSI::BusState NCR5380::target_output() {
SCSI::BusState output = SCSI::DefaultBusState;
if(target_command_ & 0x08) output |= Line::Request;
if(target_command_ & 0x04) output |= Line::Message;
if(target_command_ & 0x02) output |= Line::Control;
if(target_command_ & 0x01) output |= Line::Input;
return output;
}
void NCR5380::update_control_output() {
bus_output_ &= ~(Line::Request | Line::Message | Line::Control | Line::Input | Line::Acknowledge | Line::Attention);
if(mode_ & 0x40) {
// This is a target; C/D, I/O, /MSG and /REQ are signalled on the bus.
bus_output_ |= target_output();
} else {
// This is an initiator; /ATN and /ACK are signalled on the bus.
if(
(initiator_command_ & 0x10) ||
(state_ == ExecutionState::PerformingDMA && dma_acknowledge_)
) bus_output_ |= Line::Acknowledge;
if(initiator_command_ & 0x02) bus_output_ |= Line::Attention;
}
}
void NCR5380::scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double time_since_change) {
switch(state_) {
default: break;
/*
Official documentation:
Arbitration is accomplished using a bus-free filter to continuously monitor BSY.
If BSY remains inactive for at least 400 nsec then the SCSI bus is considered free
and arbitration may begin. Arbitration will begin if the bus is free, SEL is inactive
and the ARBITRATION bit (port 2, bit 0) is active. Once arbitration has begun
(BSY asserted), an arbitration delay of 2.2 /Lsec must elapse before the data bus
can be examined to deter- mine if arbitration has been won. This delay must be
implemented in the controlling software driver.
Personal notes:
I'm discounting that "arbitratation is accomplished" opening, and assuming that what needs
to happen is:
(i) wait for BSY to be inactive;
(ii) count 400 nsec;
(iii) check that BSY and SEL are inactive.
*/
case ExecutionState::WaitingForBusy:
if(!(new_state & SCSI::Line::Busy) || time_since_change < SCSI::DeskewDelay) return;
state_ = ExecutionState::WatchingBusy;
case ExecutionState::WatchingBusy:
if(!(new_state & SCSI::Line::Busy)) {
lost_arbitration_ = true;
set_execution_state(ExecutionState::None);
}
// Check for having hit 400ns (more or less) since BSY was inactive.
if(time_since_change >= SCSI::BusSettleDelay) {
// arbitration_in_progress_ = false;
if(new_state & SCSI::Line::SelectTarget) {
lost_arbitration_ = true;
set_execution_state(ExecutionState::None);
} else {
bus_output_ &= ~SCSI::Line::Busy;
set_execution_state(ExecutionState::None);
}
}
/* TODO: there's a bug here, given that the dropping of Busy isn't communicated onward. */
break;
case ExecutionState::PerformingDMA:
if(time_since_change < SCSI::DeskewDelay) return;
// Signal a DMA request if the request line is active, i.e. meaningful data is
// on the bus, and this device hasn't yet acknowledged it.
switch(new_state & (SCSI::Line::Request | SCSI::Line::Acknowledge)) {
case 0:
dma_request_ = false;
break;
case SCSI::Line::Request:
dma_request_ = true;
break;
case SCSI::Line::Request | SCSI::Line::Acknowledge:
dma_request_ = false;
break;
case SCSI::Line::Acknowledge:
dma_acknowledge_ = false;
dma_request_ = false;
update_control_output();
bus_.set_device_output(device_id_, bus_output_);
break;
}
break;
}
}
void NCR5380::set_execution_state(ExecutionState state) {
state_ = state;
if(state != ExecutionState::PerformingDMA) dma_operation_ = DMAOperation::Ready;
}

View File

@@ -0,0 +1,75 @@
//
// ncr5380.hpp
// Clock Signal
//
// Created by Thomas Harte on 10/08/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef ncr5380_hpp
#define ncr5380_hpp
#include <cstdint>
#include "../../Storage/MassStorage/SCSI/SCSI.hpp"
namespace NCR {
namespace NCR5380 {
/*!
Models the NCR 5380, a SCSI interface chip.
*/
class NCR5380 final: public SCSI::Bus::Observer {
public:
NCR5380(SCSI::Bus &bus, int clock_rate);
/*! Writes @c value to @c address. */
void write(int address, uint8_t value, bool dma_acknowledge = false);
/*! Reads from @c address. */
uint8_t read(int address, bool dma_acknowledge = false);
private:
SCSI::Bus &bus_;
const int clock_rate_;
size_t device_id_;
SCSI::BusState bus_output_ = SCSI::DefaultBusState;
SCSI::BusState expected_phase_ = SCSI::DefaultBusState;
uint8_t mode_ = 0xff;
uint8_t initiator_command_ = 0xff;
uint8_t data_bus_ = 0xff;
uint8_t target_command_ = 0xff;
bool test_mode_ = false;
bool assert_data_bus_ = false;
bool dma_request_ = false;
bool dma_acknowledge_ = false;
enum class ExecutionState {
None,
WaitingForBusy,
WatchingBusy,
PerformingDMA,
} state_ = ExecutionState::None;
enum class DMAOperation {
Ready,
Send,
TargetReceive,
InitiatorReceive
} dma_operation_ = DMAOperation::Ready;
bool lost_arbitration_ = false, arbitration_in_progress_ = false;
void set_execution_state(ExecutionState state);
SCSI::BusState target_output();
void update_control_output();
void scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double time_since_change) final;
};
}
}
#endif /* ncr5380_hpp */

View File

@@ -94,10 +94,10 @@ template <class T> class MOS6522: public MOS6522Storage {
MOS6522(const MOS6522 &) = delete;
/*! Sets a register value. */
void set_register(int address, uint8_t value);
void write(int address, uint8_t value);
/*! Gets a register value. */
uint8_t get_register(int address);
uint8_t read(int address);
/*! @returns the bus handler. */
T &bus_handler();

View File

@@ -30,7 +30,7 @@ template <typename T> void MOS6522<T>::access(int address) {
}
}
template <typename T> void MOS6522<T>::set_register(int address, uint8_t value) {
template <typename T> void MOS6522<T>::write(int address, uint8_t value) {
address &= 0xf;
access(address);
switch(address) {
@@ -155,7 +155,7 @@ template <typename T> void MOS6522<T>::set_register(int address, uint8_t value)
}
}
template <typename T> uint8_t MOS6522<T>::get_register(int address) {
template <typename T> uint8_t MOS6522<T>::read(int address) {
address &= 0xf;
access(address);
switch(address) {
@@ -346,7 +346,7 @@ template <typename T> void MOS6522<T>::do_phase1() {
/*! Runs for a specified number of half cycles. */
template <typename T> void MOS6522<T>::run_for(const HalfCycles half_cycles) {
int number_of_half_cycles = half_cycles.as_int();
auto number_of_half_cycles = half_cycles.as_integral();
if(!number_of_half_cycles) return;
if(is_phase2_) {
@@ -375,7 +375,7 @@ template <typename T> void MOS6522<T>::flush() {
/*! Runs for a specified number of cycles. */
template <typename T> void MOS6522<T>::run_for(const Cycles cycles) {
int number_of_cycles = cycles.as_int();
auto number_of_cycles = cycles.as_integral();
while(number_of_cycles--) {
do_phase1();
do_phase2();

View File

@@ -32,7 +32,7 @@ template <class T> class MOS6532 {
inline void set_ram(uint16_t address, uint8_t value) { ram_[address&0x7f] = value; }
inline uint8_t get_ram(uint16_t address) { return ram_[address & 0x7f]; }
inline void set_register(int address, uint8_t value) {
inline void write(int address, uint8_t value) {
const uint8_t decodedAddress = address & 0x07;
switch(decodedAddress) {
// Port output
@@ -63,7 +63,7 @@ template <class T> class MOS6532 {
}
}
inline uint8_t get_register(int address) {
inline uint8_t read(int address) {
const uint8_t decodedAddress = address & 0x7;
switch(decodedAddress) {
// Port input
@@ -107,7 +107,7 @@ template <class T> class MOS6532 {
}
inline void run_for(const Cycles cycles) {
unsigned int number_of_cycles = static_cast<unsigned int>(cycles.as_int());
unsigned int number_of_cycles = static_cast<unsigned int>(cycles.as_integral());
// permit counting _to_ zero; counting _through_ zero initiates the other behaviour
if(timer_.value >= number_of_cycles) {

View File

@@ -58,7 +58,7 @@ enum class OutputMode {
To run the VIC for a cycle, the caller should call @c get_address, make the requested bus access
and call @c set_graphics_value with the result.
@c set_register and @c get_register provide register access.
@c write and @c read provide register access.
*/
template <class BusHandler> class MOS6560 {
public:
@@ -83,8 +83,9 @@ template <class BusHandler> class MOS6560 {
speaker_.set_input_rate(static_cast<float>(clock_rate / 4.0));
}
void set_scan_target(Outputs::Display::ScanTarget *scan_target) { crt_.set_scan_target(scan_target); }
void set_display_type(Outputs::Display::DisplayType display_type) { crt_.set_display_type(display_type); }
void set_scan_target(Outputs::Display::ScanTarget *scan_target) { crt_.set_scan_target(scan_target); }
Outputs::Display::ScanStatus get_scaled_scan_status() const { return crt_.get_scaled_scan_status() / 4.0f; }
void set_display_type(Outputs::Display::DisplayType display_type) { crt_.set_display_type(display_type); }
Outputs::Speaker::Speaker *get_speaker() { return &speaker_; }
void set_high_frequency_cutoff(float cutoff) {
@@ -170,7 +171,7 @@ template <class BusHandler> class MOS6560 {
// keep track of the amount of time since the speaker was updated; lazy updates are applied
cycles_since_speaker_update_ += cycles;
int number_of_cycles = cycles.as_int();
auto number_of_cycles = cycles.as_integral();
while(number_of_cycles--) {
// keep an old copy of the vertical count because that test is a cycle later than the actual changes
int previous_vertical_counter = vertical_counter_;
@@ -353,7 +354,7 @@ template <class BusHandler> class MOS6560 {
/*!
Writes to a 6560 register.
*/
void set_register(int address, uint8_t value) {
void write(int address, uint8_t value) {
address &= 0xf;
registers_.direct_values[address] = value;
switch(address) {
@@ -417,7 +418,7 @@ template <class BusHandler> class MOS6560 {
/*
Reads from a 6560 register.
*/
uint8_t get_register(int address) {
uint8_t read(int address) {
address &= 0xf;
switch(address) {
default: return registers_.direct_values[address];

View File

@@ -111,7 +111,7 @@ template <class T> class CRTC6845 {
}
void run_for(Cycles cycles) {
int cyles_remaining = cycles.as_int();
auto cyles_remaining = cycles.as_integral();
while(cyles_remaining--) {
// check for end of visible characters
if(character_counter_ == registers_[1]) {

228
Components/6850/6850.cpp Normal file
View File

@@ -0,0 +1,228 @@
//
// 6850.cpp
// Clock Signal
//
// Created by Thomas Harte on 10/10/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#include "6850.hpp"
#include <cassert>
using namespace Motorola::ACIA;
const HalfCycles ACIA::SameAsTransmit;
ACIA::ACIA(HalfCycles transmit_clock_rate, HalfCycles receive_clock_rate) :
transmit_clock_rate_(transmit_clock_rate),
receive_clock_rate_((receive_clock_rate != SameAsTransmit) ? receive_clock_rate : transmit_clock_rate) {
transmit.set_writer_clock_rate(transmit_clock_rate);
request_to_send.set_writer_clock_rate(transmit_clock_rate);
}
uint8_t ACIA::read(int address) {
if(address&1) {
overran_ = false;
received_data_ |= NoValueMask;
update_interrupt_line();
return uint8_t(received_data_);
} else {
return get_status();
}
}
void ACIA::reset() {
transmit.reset_writing();
transmit.write(true);
request_to_send.reset_writing();
bits_received_ = bits_incoming_ = 0;
receive_interrupt_enabled_ = transmit_interrupt_enabled_ = false;
overran_ = false;
next_transmission_ = received_data_ = NoValueMask;
update_interrupt_line();
assert(!interrupt_line_);
}
void ACIA::write(int address, uint8_t value) {
if(address&1) {
next_transmission_ = value;
consider_transmission();
update_interrupt_line();
} else {
if((value&3) == 3) {
reset();
} else {
switch(value & 3) {
default:
case 0: divider_ = 1; break;
case 1: divider_ = 16; break;
case 2: divider_ = 64; break;
}
switch((value >> 2) & 7) {
default:
case 0: data_bits_ = 7; stop_bits_ = 2; parity_ = Parity::Even; break;
case 1: data_bits_ = 7; stop_bits_ = 2; parity_ = Parity::Odd; break;
case 2: data_bits_ = 7; stop_bits_ = 1; parity_ = Parity::Even; break;
case 3: data_bits_ = 7; stop_bits_ = 1; parity_ = Parity::Odd; break;
case 4: data_bits_ = 8; stop_bits_ = 2; parity_ = Parity::None; break;
case 5: data_bits_ = 8; stop_bits_ = 1; parity_ = Parity::None; break;
case 6: data_bits_ = 8; stop_bits_ = 1; parity_ = Parity::Even; break;
case 7: data_bits_ = 8; stop_bits_ = 1; parity_ = Parity::Odd; break;
}
switch((value >> 5) & 3) {
case 0: request_to_send.write(false); transmit_interrupt_enabled_ = false; break;
case 1: request_to_send.write(false); transmit_interrupt_enabled_ = true; break;
case 2: request_to_send.write(true); transmit_interrupt_enabled_ = false; break;
case 3:
request_to_send.write(false);
transmit_interrupt_enabled_ = false;
transmit.reset_writing();
transmit.write(false);
break;
}
receive.set_read_delegate(this, Storage::Time(divider_ * 2, int(receive_clock_rate_.as_integral())));
receive_interrupt_enabled_ = value & 0x80;
update_interrupt_line();
}
}
update_clocking_observer();
}
void ACIA::consider_transmission() {
if(next_transmission_ != NoValueMask && !transmit.write_data_time_remaining()) {
// Establish start bit and [7 or 8] data bits.
if(data_bits_ == 7) next_transmission_ &= 0x7f;
int transmission = next_transmission_ << 1;
// Add a parity bit, if any.
int mask = 0x2 << data_bits_;
if(parity_ != Parity::None) {
transmission |= parity(uint8_t(next_transmission_)) ? mask : 0;
mask <<= 1;
}
// Add stop bits.
for(int c = 0; c < stop_bits_; ++c) {
transmission |= mask;
mask <<= 1;
}
// Output all that.
const int total_bits = expected_bits();
transmit.write(divider_ * 2, total_bits, transmission);
// Mark the transmit register as empty again.
next_transmission_ = NoValueMask;
}
}
ClockingHint::Preference ACIA::preferred_clocking() {
// Real-time clocking is required if a transmission is ongoing; this is a courtesy for whomever
// is on the receiving end.
if(transmit.transmission_data_time_remaining() > 0) return ClockingHint::Preference::RealTime;
// If a bit reception is ongoing that might lead to an interrupt, ask for real-time clocking
// because it's unclear when the interrupt might come.
if(bits_incoming_ && receive_interrupt_enabled_) return ClockingHint::Preference::RealTime;
// No clocking required then.
return ClockingHint::Preference::None;
}
bool ACIA::get_interrupt_line() const {
return interrupt_line_;
}
int ACIA::expected_bits() {
return 1 + data_bits_ + stop_bits_ + (parity_ != Parity::None);
}
uint8_t ACIA::parity(uint8_t value) {
value ^= value >> 4;
value ^= value >> 2;
value ^= value >> 1;
return value ^ (parity_ == Parity::Even);
}
bool ACIA::serial_line_did_produce_bit(Serial::Line *line, int bit) {
// Shift this bit into the 11-bit input register; this is big enough to hold
// the largest transmission symbol.
++bits_received_;
bits_incoming_ = (bits_incoming_ >> 1) | (bit << 10);
// If that's the now-expected number of bits, update.
const int bit_target = expected_bits();
if(bits_received_ >= bit_target) {
bits_received_ = 0;
overran_ |= get_status() & 1;
received_data_ = uint8_t(bits_incoming_ >> (12 - bit_target));
update_interrupt_line();
update_clocking_observer();
return false;
}
// TODO: overrun, and parity.
// Keep receiving, and consider a potential clocking change.
if(bits_received_ == 1) update_clocking_observer();
return true;
}
void ACIA::set_interrupt_delegate(InterruptDelegate *delegate) {
interrupt_delegate_ = delegate;
}
void ACIA::update_interrupt_line() {
const bool old_line = interrupt_line_;
/*
"Bit 7 of the control register is the rie bit. When the rie bit is high, the rdrf, ndcd,
and ovr bits will assert the nirq output. When the rie bit is low, nirq generation is disabled."
rie = read interrupt enable
rdrf = receive data register full (status word bit 0)
ndcd = data carrier detect (status word bit 2)
over = receiver overrun (status word bit 5)
"Bit 1 of the status register is the tdre bit. When high, the tdre bit indicates that data has been
transferred from the transmitter data register to the output shift register. At this point, the a6850
is ready to accept a new transmit data byte. However, if the ncts signal is high, the tdre bit remains
low regardless of the status of the transmitter data register. Also, if transmit interrupt is enabled,
the nirq output is asserted."
tdre = transmitter data register empty
ncts = clear to send
*/
const auto status = get_status();
interrupt_line_ =
(receive_interrupt_enabled_ && (status & 0x25)) ||
(transmit_interrupt_enabled_ && (status & 0x02));
if(interrupt_delegate_ && old_line != interrupt_line_) {
interrupt_delegate_->acia6850_did_change_interrupt_status(this);
}
}
uint8_t ACIA::get_status() {
return
((received_data_ & NoValueMask) ? 0x00 : 0x01) |
((next_transmission_ == NoValueMask) ? 0x02 : 0x00) |
// (data_carrier_detect.read() ? 0x04 : 0x00) |
// (clear_to_send.read() ? 0x08 : 0x00) |
(overran_ ? 0x20 : 0x00) |
(interrupt_line_ ? 0x80 : 0x00)
;
// b0: receive data full.
// b1: transmit data empty.
// b2: DCD.
// b3: CTS.
// b4: framing error (i.e. no first stop bit where expected).
// b5: receiver overran.
// b6: parity error.
// b7: IRQ state.
}

132
Components/6850/6850.hpp Normal file
View File

@@ -0,0 +1,132 @@
//
// 6850.hpp
// Clock Signal
//
// Created by Thomas Harte on 10/10/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef Motorola_ACIA_6850_hpp
#define Motorola_ACIA_6850_hpp
#include <cstdint>
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../ClockReceiver/ForceInline.hpp"
#include "../../ClockReceiver/ClockingHintSource.hpp"
#include "../Serial/Line.hpp"
namespace Motorola {
namespace ACIA {
class ACIA: public ClockingHint::Source, private Serial::Line::ReadDelegate {
public:
static constexpr const HalfCycles SameAsTransmit = HalfCycles(0);
/*!
Constructs a new instance of ACIA which will receive a transmission clock at a rate of
@c transmit_clock_rate, and a receive clock at a rate of @c receive_clock_rate.
*/
ACIA(HalfCycles transmit_clock_rate, HalfCycles receive_clock_rate = SameAsTransmit);
/*!
Reads from the ACIA.
Bit 0 of the address is used as the ACIA's register select line —
so even addresses select control/status registers, odd addresses
select transmit/receive data registers.
*/
uint8_t read(int address);
/*!
Writes to the ACIA.
Bit 0 of the address is used as the ACIA's register select line —
so even addresses select control/status registers, odd addresses
select transmit/receive data registers.
*/
void write(int address, uint8_t value);
/*!
Advances @c transmission_cycles in time, which should be
counted relative to the @c transmit_clock_rate.
*/
forceinline void run_for(HalfCycles transmission_cycles) {
if(transmit.transmission_data_time_remaining() > HalfCycles(0)) {
const auto write_data_time_remaining = transmit.write_data_time_remaining();
// There's at most one further byte available to enqueue, so a single 'if'
// rather than a 'while' is correct here. It's the responsibilit of the caller
// to ensure run_for lengths are appropriate for longer sequences.
if(transmission_cycles >= write_data_time_remaining) {
if(next_transmission_ != NoValueMask) {
transmit.advance_writer(write_data_time_remaining);
consider_transmission();
transmit.advance_writer(transmission_cycles - write_data_time_remaining);
} else {
transmit.advance_writer(transmission_cycles);
update_clocking_observer();
update_interrupt_line();
}
} else {
transmit.advance_writer(transmission_cycles);
}
}
}
bool get_interrupt_line() const;
void reset();
// Input lines.
Serial::Line receive;
Serial::Line clear_to_send;
Serial::Line data_carrier_detect;
// Output lines.
Serial::Line transmit;
Serial::Line request_to_send;
// ClockingHint::Source.
ClockingHint::Preference preferred_clocking() final;
struct InterruptDelegate {
virtual void acia6850_did_change_interrupt_status(ACIA *acia) = 0;
};
void set_interrupt_delegate(InterruptDelegate *delegate);
private:
int divider_ = 1;
enum class Parity {
Even, Odd, None
} parity_ = Parity::None;
int data_bits_ = 7, stop_bits_ = 2;
static constexpr int NoValueMask = 0x100;
int next_transmission_ = NoValueMask;
int received_data_ = NoValueMask;
int bits_received_ = 0;
int bits_incoming_ = 0;
bool overran_ = false;
void consider_transmission();
int expected_bits();
uint8_t parity(uint8_t value);
bool receive_interrupt_enabled_ = false;
bool transmit_interrupt_enabled_ = false;
HalfCycles transmit_clock_rate_;
HalfCycles receive_clock_rate_;
bool serial_line_did_produce_bit(Serial::Line *line, int bit) final;
bool interrupt_line_ = false;
void update_interrupt_line();
InterruptDelegate *interrupt_delegate_ = nullptr;
uint8_t get_status();
};
}
}
#endif /* Motorola_ACIA_6850_hpp */

View File

@@ -0,0 +1,374 @@
//
// MFP68901.cpp
// Clock Signal
//
// Created by Thomas Harte on 06/10/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#include "MFP68901.hpp"
#include <algorithm>
#include <cstring>
#ifndef NDEBUG
#define NDEBUG
#endif
#define LOG_PREFIX "[MFP] "
#include "../../Outputs/Log.hpp"
using namespace Motorola::MFP68901;
ClockingHint::Preference MFP68901::preferred_clocking() {
// Rule applied: if any timer is actively running and permitted to produce an
// interrupt, request real-time running.
return
(timers_[0].mode >= TimerMode::Delay && interrupt_enable_&Interrupt::TimerA) ||
(timers_[1].mode >= TimerMode::Delay && interrupt_enable_&Interrupt::TimerB) ||
(timers_[2].mode >= TimerMode::Delay && interrupt_enable_&Interrupt::TimerC) ||
(timers_[3].mode >= TimerMode::Delay && interrupt_enable_&Interrupt::TimerD)
? ClockingHint::Preference::RealTime : ClockingHint::Preference::JustInTime;
}
uint8_t MFP68901::read(int address) {
address &= 0x1f;
// Interrupt block: various bits of state can be read, all passively.
if(address >= 0x03 && address <= 0x0b) {
const int shift = (address&1) << 3;
switch(address) {
case 0x03: case 0x04: return uint8_t(interrupt_enable_ >> shift);
case 0x05: case 0x06: return uint8_t(interrupt_pending_ >> shift);
case 0x07: case 0x08: return uint8_t(interrupt_in_service_ >> shift);
case 0x09: case 0x0a: return uint8_t(interrupt_mask_ >> shift);
case 0x0b: return interrupt_vector_;
default: break;
}
}
switch(address) {
// GPIP block: input, and configured active edge and direction values.
case 0x00: return (gpip_input_ & ~gpip_direction_) | (gpip_output_ & gpip_direction_);
case 0x01: return gpip_active_edge_;
case 0x02: return gpip_direction_;
/* Interrupt block dealt with above. */
default: break;
// Timer block: read back A, B and C/D control, and read current timer values.
case 0x0c: case 0x0d: return timer_ab_control_[address - 0xc];
case 0x0e: return timer_cd_control_;
case 0x0f: case 0x10:
case 0x11: case 0x12: return get_timer_data(address - 0xf);
// USART block: TODO.
case 0x13: LOG("Read: sync character generator"); break;
case 0x14: LOG("Read: USART control"); break;
case 0x15: LOG("Read: receiver status"); break;
case 0x16: LOG("Read: transmitter status"); break;
case 0x17: LOG("Read: USART data"); break;
}
return 0x00;
}
void MFP68901::write(int address, uint8_t value) {
address &= 0x1f;
// Interrupt block: enabled and masked interrupts can be set; pending and in-service interrupts can be masked.
if(address >= 0x03 && address <= 0x0b) {
const int shift = (address&1) << 3;
const int preserve = 0xff00 >> shift;
const int word_value = value << shift;
switch(address) {
default: break;
case 0x03: case 0x04: // Adjust enabled interrupts; disabled ones also cease to be pending.
interrupt_enable_ = (interrupt_enable_ & preserve) | word_value;
interrupt_pending_ &= interrupt_enable_;
break;
case 0x05: case 0x06: // Resolve pending interrupts.
interrupt_pending_ &= (preserve | word_value);
break;
case 0x07: case 0x08: // Resolve in-service interrupts.
interrupt_in_service_ &= (preserve | word_value);
break;
case 0x09: case 0x0a: // Adjust interrupt mask.
interrupt_mask_ = (interrupt_mask_ & preserve) | word_value;
break;
case 0x0b: // Set the interrupt vector, possibly changing end-of-interrupt mode.
interrupt_vector_ = value;
// If automatic end-of-interrupt mode has now been enabled, clear
// the in-process mask and re-evaluate.
if(interrupt_vector_ & 0x08) return;
interrupt_in_service_ = 0;
break;
}
// Whatever just happened may have affected the state of the interrupt line.
update_interrupts();
update_clocking_observer();
return;
}
constexpr int timer_prescales[] = {
1, 4, 10, 16, 50, 64, 100, 200
};
switch(address) {
// GPIP block: output and configuration of active edge and direction values.
case 0x00:
gpip_output_ = value;
break;
case 0x01:
gpip_active_edge_ = value;
reevaluate_gpip_interrupts();
break;
case 0x02:
gpip_direction_ = value;
reevaluate_gpip_interrupts();
break;
/* Interrupt block dealt with above. */
default: break;
// Timer block.
case 0x0c:
case 0x0d: {
const auto timer = address - 0xc;
const bool reset = value & 0x10;
timer_ab_control_[timer] = value;
switch(value & 0xf) {
case 0x0: set_timer_mode(timer, TimerMode::Stopped, 1, reset); break;
case 0x1: set_timer_mode(timer, TimerMode::Delay, 4, reset); break;
case 0x2: set_timer_mode(timer, TimerMode::Delay, 10, reset); break;
case 0x3: set_timer_mode(timer, TimerMode::Delay, 16, reset); break;
case 0x4: set_timer_mode(timer, TimerMode::Delay, 50, reset); break;
case 0x5: set_timer_mode(timer, TimerMode::Delay, 64, reset); break;
case 0x6: set_timer_mode(timer, TimerMode::Delay, 100, reset); break;
case 0x7: set_timer_mode(timer, TimerMode::Delay, 200, reset); break;
case 0x8: set_timer_mode(timer, TimerMode::EventCount, 1, reset); break;
case 0x9: set_timer_mode(timer, TimerMode::PulseWidth, 4, reset); break;
case 0xa: set_timer_mode(timer, TimerMode::PulseWidth, 10, reset); break;
case 0xb: set_timer_mode(timer, TimerMode::PulseWidth, 16, reset); break;
case 0xc: set_timer_mode(timer, TimerMode::PulseWidth, 50, reset); break;
case 0xd: set_timer_mode(timer, TimerMode::PulseWidth, 64, reset); break;
case 0xe: set_timer_mode(timer, TimerMode::PulseWidth, 100, reset); break;
case 0xf: set_timer_mode(timer, TimerMode::PulseWidth, 200, reset); break;
}
} break;
case 0x0e:
timer_cd_control_ = value;
set_timer_mode(3, (value & 7) ? TimerMode::Delay : TimerMode::Stopped, timer_prescales[value & 7], false);
set_timer_mode(2, ((value >> 4) & 7) ? TimerMode::Delay : TimerMode::Stopped, timer_prescales[(value >> 4) & 7], false);
break;
case 0x0f: case 0x10: case 0x11: case 0x12:
set_timer_data(address - 0xf, value);
break;
// USART block: TODO.
case 0x13: LOG("Write: sync character generator"); break;
case 0x14: LOG("Write: USART control"); break;
case 0x15: LOG("Write: receiver status"); break;
case 0x16: LOG("Write: transmitter status"); break;
case 0x17: LOG("Write: USART data"); break;
}
update_clocking_observer();
}
void MFP68901::run_for(HalfCycles time) {
cycles_left_ += time;
const int cycles = int(cycles_left_.flush<Cycles>().as_integral());
if(!cycles) return;
for(int c = 0; c < 4; ++c) {
if(timers_[c].mode >= TimerMode::Delay) {
// This code applies the timer prescaling only. prescale_count is used to count
// upwards rather than downwards for simplicity, but on the real hardware it's
// pretty safe to assume it actually counted downwards. So the clamp to 0 is
// because gymnastics may need to occur when the prescale value is altered, e.g.
// if a prescale of 256 is set and the prescale_count is currently 2 then the
// counter should roll over in 254 cycles. If the user at that point changes the
// prescale_count to 1 then the counter will need to be altered to -253 and
// allowed to keep counting up until it crosses both 0 and 1.
const int dividend = timers_[c].prescale_count + cycles;
const int decrements = std::max(dividend / timers_[c].prescale, 0);
if(decrements) {
decrement_timer(c, decrements);
timers_[c].prescale_count = dividend % timers_[c].prescale;
} else {
timers_[c].prescale_count += cycles;
}
}
}
}
HalfCycles MFP68901::get_next_sequence_point() {
return HalfCycles(-1);
}
// MARK: - Timers
void MFP68901::set_timer_mode(int timer, TimerMode mode, int prescale, bool reset_timer) {
LOG("Timer " << timer << " mode set: " << int(mode) << "; prescale: " << prescale);
timers_[timer].mode = mode;
if(reset_timer) {
timers_[timer].prescale_count = 0;
timers_[timer].value = timers_[timer].reload_value;
} else {
// This hoop is because the prescale_count here goes upward but I'm assuming it goes downward in
// real hardware. Therefore this deals with the "switched to a lower prescaling" case whereby the
// old cycle should be allowed naturally to expire.
timers_[timer].prescale_count = prescale - (timers_[timer].prescale - timers_[timer].prescale_count);
}
timers_[timer].prescale = prescale;
}
void MFP68901::set_timer_data(int timer, uint8_t value) {
if(timers_[timer].mode == TimerMode::Stopped) {
timers_[timer].value = value;
}
timers_[timer].reload_value = value;
}
uint8_t MFP68901::get_timer_data(int timer) {
return timers_[timer].value;
}
void MFP68901::set_timer_event_input(int channel, bool value) {
if(timers_[channel].event_input == value) return;
timers_[channel].event_input = value;
if(timers_[channel].mode == TimerMode::EventCount && (value == !!(gpip_active_edge_ & (0x10 >> channel)))) {
// "The active state of the signal on TAI or TBI is dependent upon the associated
// Interrupt Channels edge bit (GPIP 4 for TAI and GPIP 3 for TBI [...] ).
// If the edge bit associated with the TAI or TBI input is a one, it will be active high.
decrement_timer(channel, 1);
}
// TODO:
//
// Altering the edge bit while the timer is in the event count mode can produce a count pulse.
// The interrupt channel associated with the input (I3 for I4 for TAI) is allowed to function normally.
// To count transitions reliably, the input must remain in each state (1/O) for a length of time equal
// to four periods of the timer clock.
//
// (the final bit probably explains 13 cycles of the DE to interrupt latency; not sure about the other ~15)
}
void MFP68901::decrement_timer(int timer, int amount) {
while(amount--) {
--timers_[timer].value;
if(timers_[timer].value < 1) {
switch(timer) {
case 0: begin_interrupts(Interrupt::TimerA); break;
case 1: begin_interrupts(Interrupt::TimerB); break;
case 2: begin_interrupts(Interrupt::TimerC); break;
case 3: begin_interrupts(Interrupt::TimerD); break;
}
// Re: reloading when in event counting mode; I found the data sheet thoroughly unclear on
// this, but it appears empirically to be correct. See e.g. Pompey Pirates menu 27.
if(timers_[timer].mode == TimerMode::Delay || timers_[timer].mode == TimerMode::EventCount) {
timers_[timer].value += timers_[timer].reload_value; // TODO: properly.
}
}
}
}
// MARK: - GPIP
void MFP68901::set_port_input(uint8_t input) {
gpip_input_ = input;
reevaluate_gpip_interrupts();
}
uint8_t MFP68901::get_port_output() {
return 0xff; // TODO.
}
void MFP68901::reevaluate_gpip_interrupts() {
const uint8_t gpip_state = (gpip_input_ & ~gpip_direction_) ^ gpip_active_edge_;
// An interrupt is detected on any falling edge.
const uint8_t new_interrupt_mask = (gpip_state ^ gpip_interrupt_state_) & gpip_interrupt_state_;
if(new_interrupt_mask) {
begin_interrupts(
(new_interrupt_mask & 0x0f) |
((new_interrupt_mask & 0x30) << 2) |
((new_interrupt_mask & 0xc0) << 8)
);
}
gpip_interrupt_state_ = gpip_state;
}
// MARK: - Interrupts
void MFP68901::begin_interrupts(int interrupt) {
interrupt_pending_ |= interrupt & interrupt_enable_;
update_interrupts();
}
void MFP68901::end_interrupts(int interrupt) {
interrupt_pending_ &= ~interrupt;
update_interrupts();
}
void MFP68901::update_interrupts() {
const auto old_interrupt_line = interrupt_line_;
const auto firing_interrupts = interrupt_pending_ & interrupt_mask_;
if(!firing_interrupts) {
interrupt_line_ = false;
} else {
if(interrupt_vector_ & 0x8) {
// Software interrupt mode: permit only if neither this interrupt
// nor a higher interrupt is currently in service.
const int highest_bit = msb16(firing_interrupts);
interrupt_line_ = !(interrupt_in_service_ & ~(highest_bit + highest_bit - 1));
} else {
// Auto-interrupt mode; just signal.
interrupt_line_ = true;
}
}
// Update the delegate if necessary.
if(interrupt_delegate_ && interrupt_line_ != old_interrupt_line) {
interrupt_delegate_->mfp68901_did_change_interrupt_status(this);
}
}
bool MFP68901::get_interrupt_line() {
return interrupt_line_;
}
int MFP68901::acknowledge_interrupt() {
if(!(interrupt_pending_ & interrupt_mask_)) {
return NoAcknowledgement;
}
const int mask = msb16(interrupt_pending_ & interrupt_mask_);
// Clear the pending bit regardless.
interrupt_pending_ &= ~mask;
// If this is software interrupt mode, set the in-service bit.
if(interrupt_vector_ & 0x8) {
interrupt_in_service_ |= mask;
}
update_interrupts();
int selected = 0;
while((1 << selected) != mask) ++selected;
// LOG("Interrupt acknowledged: " << selected);
return (interrupt_vector_ & 0xf0) | uint8_t(selected);
}
void MFP68901::set_interrupt_delegate(InterruptDelegate *delegate) {
interrupt_delegate_ = delegate;
}

View File

@@ -0,0 +1,187 @@
//
// MFP68901.hpp
// Clock Signal
//
// Created by Thomas Harte on 06/10/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef MFP68901_hpp
#define MFP68901_hpp
#include <cstdint>
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../ClockReceiver/ClockingHintSource.hpp"
namespace Motorola {
namespace MFP68901 {
class PortHandler {
public:
// TODO: announce changes in output.
};
/*!
Models the Motorola 68901 Multi-Function Peripheral ('MFP').
*/
class MFP68901: public ClockingHint::Source {
public:
/// @returns the result of a read from @c address.
uint8_t read(int address);
/// Performs a write of @c value to @c address.
void write(int address, uint8_t value);
/// Advances the MFP by the supplied number of HalfCycles.
void run_for(HalfCycles);
/// @returns the number of cycles until the next possible sequence point — the next time
/// at which the interrupt line _might_ change. This object conforms to ClockingHint::Source
/// so that mechanism can also be used to reduce the quantity of calls into this class.
///
/// @discussion TODO, alas.
HalfCycles get_next_sequence_point();
/// Sets the current level of either of the timer event inputs — TAI and TBI in datasheet terms.
void set_timer_event_input(int channel, bool value);
/// Sets a port handler, a receiver that will be notified upon any change in GPIP output.
///
/// @discussion TODO.
void set_port_handler(PortHandler *);
/// Sets the current input GPIP values.
void set_port_input(uint8_t);
/// @returns the current GPIP output values.
///
/// @discussion TODO.
uint8_t get_port_output();
/// @returns @c true if the interrupt output is currently active; @c false otherwise.s
bool get_interrupt_line();
static constexpr int NoAcknowledgement = 0x100;
/// Communicates an interrupt acknowledge cycle.
///
/// @returns the vector placed on the bus if any; @c NoAcknowledgement if nothing is loaded.
int acknowledge_interrupt();
struct InterruptDelegate {
/// Informs the delegate of a change in the interrupt line of the nominated MFP.
virtual void mfp68901_did_change_interrupt_status(MFP68901 *) = 0;
};
/// Sets a delegate that will be notified upon any change in the interrupt line.
void set_interrupt_delegate(InterruptDelegate *delegate);
// ClockingHint::Source.
ClockingHint::Preference preferred_clocking() final;
private:
// MARK: - Timers
enum class TimerMode {
Stopped, EventCount, Delay, PulseWidth
};
void set_timer_mode(int timer, TimerMode, int prescale, bool reset_timer);
void set_timer_data(int timer, uint8_t);
uint8_t get_timer_data(int timer);
void decrement_timer(int timer, int amount);
struct Timer {
TimerMode mode = TimerMode::Stopped;
uint8_t value = 0;
uint8_t reload_value = 0;
int prescale = 1;
int prescale_count = 1;
bool event_input = false;
} timers_[4];
uint8_t timer_ab_control_[2] = { 0, 0 };
uint8_t timer_cd_control_ = 0;
HalfCycles cycles_left_;
// MARK: - GPIP
uint8_t gpip_input_ = 0;
uint8_t gpip_output_ = 0;
uint8_t gpip_active_edge_ = 0;
uint8_t gpip_direction_ = 0;
uint8_t gpip_interrupt_state_ = 0;
void reevaluate_gpip_interrupts();
// MARK: - Interrupts
InterruptDelegate *interrupt_delegate_ = nullptr;
// Ad hoc documentation:
//
// An interrupt becomes pending if it is enabled at the time it occurs.
//
// If a pending interrupt is enabled in the interrupt mask, a processor
// interrupt is generated. Otherwise no processor interrupt is generated.
//
// (Disabling a bit in the enabled mask also instantaneously clears anything
// in the pending mask.)
//
// The user can write to the pending interrupt register; a write
// masks whatever is there — so you can disable bits but you cannot set them.
//
// If the vector register's 'S' bit is set then software end-of-interrupt mode applies:
// Acknowledgement of an interrupt clears that interrupt's pending bit, but also sets
// its in-service bit. That bit will remain set until the user writes a zero to its position.
// If any bits are set in the in-service register, then they will prevent lower-priority
// interrupts from being signalled to the CPU. Further interrupts of the same or a higher
// priority may occur.
//
// If the vector register's 'S' bit is clear then automatic end-of-interrupt mode applies:
// Acknowledgement of an interrupt will automatically clear the corresponding
// pending bit.
//
int interrupt_enable_ = 0;
int interrupt_pending_ = 0;
int interrupt_mask_ = 0;
int interrupt_in_service_ = 0;
bool interrupt_line_ = false;
uint8_t interrupt_vector_ = 0;
enum Interrupt {
GPIP0 = (1 << 0),
GPIP1 = (1 << 1),
GPIP2 = (1 << 2),
GPIP3 = (1 << 3),
TimerD = (1 << 4),
TimerC = (1 << 5),
GPIP4 = (1 << 6),
GPIP5 = (1 << 7),
TimerB = (1 << 8),
TransmitError = (1 << 9),
TransmitBufferEmpty = (1 << 10),
ReceiveError = (1 << 11),
ReceiveBufferFull = (1 << 12),
TimerA = (1 << 13),
GPIP6 = (1 << 14),
GPIP7 = (1 << 15),
};
void begin_interrupts(int interrupt);
void end_interrupts(int interrupt);
void update_interrupts();
/// @returns the most significant bit set in v, assuming it is one of the least significant 16.
inline static int msb16(int v) {
// Saturate all bits below the MSB.
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
// Throw away lesser bits.
return (v+1) >> 1;
}
};
}
}
#endif /* MFP68901_hpp */

View File

@@ -27,7 +27,7 @@ template <class T> class i8255 {
Stores the value @c value to the register at @c address. If this causes a change in 8255 output
then the PortHandler will be informed.
*/
void set_register(int address, uint8_t value) {
void write(int address, uint8_t value) {
switch(address & 3) {
case 0:
if(!(control_ & 0x10)) {
@@ -60,7 +60,7 @@ template <class T> class i8255 {
Obtains the current value for the register at @c address. If this provides a reading
of input then the PortHandler will be queried.
*/
uint8_t get_register(int address) {
uint8_t read(int address) {
switch(address & 3) {
case 0: return (control_ & 0x10) ? port_handler_.get_value(0) : outputs_[0];
case 1: return (control_ & 0x02) ? port_handler_.get_value(1) : outputs_[1];

View File

@@ -95,11 +95,11 @@ void i8272::run_for(Cycles cycles) {
// check for an expired timer
if(delay_time_ > 0) {
if(cycles.as_int() >= delay_time_) {
if(cycles.as_integral() >= delay_time_) {
delay_time_ = 0;
posit_event(static_cast<int>(Event8272::Timer));
} else {
delay_time_ -= cycles.as_int();
delay_time_ -= cycles.as_integral();
}
}
@@ -108,8 +108,8 @@ void i8272::run_for(Cycles cycles) {
int drives_left = drives_seeking_;
for(int c = 0; c < 4; c++) {
if(drives_[c].phase == Drive::Seeking) {
drives_[c].step_rate_counter += cycles.as_int();
int steps = drives_[c].step_rate_counter / (8000 * step_rate_time_);
drives_[c].step_rate_counter += cycles.as_integral();
auto steps = drives_[c].step_rate_counter / (8000 * step_rate_time_);
drives_[c].step_rate_counter %= (8000 * step_rate_time_);
while(steps--) {
// Perform a step.
@@ -141,12 +141,12 @@ void i8272::run_for(Cycles cycles) {
int head = c&1;
if(drives_[drive].head_unload_delay[head] > 0) {
if(cycles.as_int() >= drives_[drive].head_unload_delay[head]) {
if(cycles.as_integral() >= drives_[drive].head_unload_delay[head]) {
drives_[drive].head_unload_delay[head] = 0;
drives_[drive].head_is_loaded[head] = false;
head_timers_running_--;
} else {
drives_[drive].head_unload_delay[head] -= cycles.as_int();
drives_[drive].head_unload_delay[head] -= cycles.as_integral();
}
timers_left--;
if(!timers_left) break;
@@ -163,7 +163,7 @@ void i8272::run_for(Cycles cycles) {
if(is_sleeping_) update_clocking_observer();
}
void i8272::set_register(int address, uint8_t value) {
void i8272::write(int address, uint8_t value) {
// don't consider attempted sets to the status register
if(!address) return;
@@ -181,7 +181,7 @@ void i8272::set_register(int address, uint8_t value) {
}
}
uint8_t i8272::get_register(int address) {
uint8_t i8272::read(int address) {
if(address) {
if(result_stack_.empty()) return 0xff;
uint8_t result = result_stack_.back();
@@ -292,7 +292,7 @@ void i8272::posit_event(int event_type) {
WAIT_FOR_EVENT(Event8272::CommandByte)
SetBusy();
static const std::size_t required_lengths[32] = {
static constexpr std::size_t required_lengths[32] = {
0, 0, 9, 3, 2, 9, 9, 2,
1, 9, 2, 0, 9, 6, 0, 3,
0, 9, 0, 0, 0, 0, 0, 0,
@@ -865,7 +865,7 @@ void i8272::posit_event(int event_type) {
SetDataRequest();
SetDataDirectionToProcessor();
// The actual stuff of unwinding result_stack_ is handled by ::get_register; wait
// The actual stuff of unwinding result_stack_ is handled by ::read; wait
// until the processor has read all result bytes.
WAIT_FOR_EVENT(Event8272::ResultEmpty);

View File

@@ -24,7 +24,7 @@ class BusHandler {
virtual void set_interrupt(bool irq) {}
};
class i8272: public Storage::Disk::MFMController {
class i8272 : public Storage::Disk::MFMController {
public:
i8272(BusHandler &bus_handler, Cycles clock_rate);
@@ -33,13 +33,13 @@ class i8272: public Storage::Disk::MFMController {
void set_data_input(uint8_t value);
uint8_t get_data_output();
void set_register(int address, uint8_t value);
uint8_t get_register(int address);
void write(int address, uint8_t value);
uint8_t read(int address);
void set_dma_acknowledge(bool dack);
void set_terminal_count(bool tc);
ClockingHint::Preference preferred_clocking() override;
ClockingHint::Preference preferred_clocking() final;
protected:
virtual void select_drive(int number) = 0;
@@ -67,13 +67,13 @@ class i8272: public Storage::Disk::MFMController {
ResultEmpty = (1 << 5),
NoLongerReady = (1 << 6)
};
void posit_event(int type) override;
void posit_event(int type) final;
int interesting_event_mask_ = static_cast<int>(Event8272::CommandByte);
int resume_point_ = 0;
bool is_access_command_ = false;
// The counter used for ::Timer events.
int delay_time_ = 0;
Cycles::IntType delay_time_ = 0;
// The connected drives.
struct Drive {
@@ -89,12 +89,12 @@ class i8272: public Storage::Disk::MFMController {
bool seek_failed = false;
// Seeking: transient state.
int step_rate_counter = 0;
Cycles::IntType step_rate_counter = 0;
int steps_taken = 0;
int target_head_position = 0; // either an actual number, or -1 to indicate to step until track zero
// Head state.
int head_unload_delay[2] = {0, 0};
Cycles::IntType head_unload_delay[2] = {0, 0};
bool head_is_loaded[2] = {false, false};
} drives_[4];

View File

@@ -17,16 +17,16 @@ using namespace TI::TMS;
namespace {
const uint8_t StatusInterrupt = 0x80;
const uint8_t StatusSpriteOverflow = 0x40;
constexpr uint8_t StatusInterrupt = 0x80;
constexpr uint8_t StatusSpriteOverflow = 0x40;
const int StatusSpriteCollisionShift = 5;
const uint8_t StatusSpriteCollision = 0x20;
constexpr int StatusSpriteCollisionShift = 5;
constexpr uint8_t StatusSpriteCollision = 0x20;
// 342 internal cycles are 228/227.5ths of a line, so 341.25 cycles should be a whole
// line. Therefore multiply everything by four, but set line length to 1365 rather than 342*4 = 1368.
const unsigned int CRTCyclesPerLine = 1365;
const unsigned int CRTCyclesDivider = 4;
constexpr unsigned int CRTCyclesPerLine = 1365;
constexpr unsigned int CRTCyclesDivider = 4;
struct ReverseTable {
std::uint8_t map[256];
@@ -117,6 +117,14 @@ void TMS9918::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
crt_.set_scan_target(scan_target);
}
Outputs::Display::ScanStatus TMS9918::get_scaled_scan_status() const {
// The input was scaled by 3/4 to convert half cycles to internal ticks,
// so undo that and also allow for: (i) the multiply by 4 that it takes
// to reach the CRT; and (ii) the fact that the half-cycles value was scaled,
// and this should really reply in whole cycles.
return crt_.get_scaled_scan_status() * (4.0f / (3.0f * 8.0f));
}
void TMS9918::set_display_type(Outputs::Display::DisplayType display_type) {
crt_.set_display_type(display_type);
}
@@ -166,7 +174,7 @@ void TMS9918::run_for(const HalfCycles cycles) {
// Convert 456 clocked half cycles per line to 342 internal cycles per line;
// the internal clock is 1.5 times the nominal 3.579545 Mhz that I've advertised
// for this part. So multiply by three quarters.
int int_cycles = (cycles.as_int() * 3) + cycles_error_;
int int_cycles = int(cycles.as_integral() * 3) + cycles_error_;
cycles_error_ = int_cycles & 3;
int_cycles >>= 2;
if(!int_cycles) return;
@@ -352,8 +360,7 @@ void TMS9918::run_for(const HalfCycles cycles) {
// Output video stream.
// --------------------
#define intersect(left, right, code) \
{ \
#define intersect(left, right, code) { \
const int start = std::max(read_pointer_.column, left); \
const int end = std::min(end_column, right); \
if(end > start) {\
@@ -493,7 +500,7 @@ void Base::output_border(int cycles, uint32_t cram_dot) {
}
}
void TMS9918::set_register(int address, uint8_t value) {
void TMS9918::write(int address, uint8_t value) {
// Writes to address 0 are writes to the video RAM. Store
// the value and return.
if(!(address & 1)) {
@@ -625,7 +632,7 @@ void TMS9918::set_register(int address, uint8_t value) {
uint8_t TMS9918::get_current_line() {
// Determine the row to return.
static const int row_change_position = 63; // This is the proper Master System value; substitute if any other VDPs turn out to have this functionality.
constexpr int row_change_position = 63; // This is the proper Master System value; substitute if any other VDPs turn out to have this functionality.
int source_row =
(write_pointer_.column < row_change_position)
? (write_pointer_.row + mode_timing_.total_lines - 1)%mode_timing_.total_lines
@@ -671,7 +678,7 @@ void TMS9918::latch_horizontal_counter() {
latched_column_ = write_pointer_.column;
}
uint8_t TMS9918::get_register(int address) {
uint8_t TMS9918::read(int address) {
write_phase_ = false;
// Reads from address 0 read video RAM, via the read-ahead buffer.
@@ -830,8 +837,8 @@ void Base::draw_tms_character(int start, int end) {
int sprite_collision = 0;
memset(&sprite_buffer[start], 0, size_t(end - start)*sizeof(sprite_buffer[0]));
static const uint32_t sprite_colour_selection_masks[2] = {0x00000000, 0xffffffff};
static const int colour_masks[16] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
constexpr uint32_t sprite_colour_selection_masks[2] = {0x00000000, 0xffffffff};
constexpr int colour_masks[16] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
// Draw all sprites into the sprite buffer.
const int shifter_target = sprites_16x16_ ? 32 : 16;

View File

@@ -44,6 +44,9 @@ class TMS9918: public Base {
/*! Sets the scan target this TMS will post content to. */
void set_scan_target(Outputs::Display::ScanTarget *);
/// Gets the current scan status.
Outputs::Display::ScanStatus get_scaled_scan_status() const;
/*! Sets the type of display the CRT will request. */
void set_display_type(Outputs::Display::DisplayType);
@@ -54,10 +57,10 @@ class TMS9918: public Base {
void run_for(const HalfCycles cycles);
/*! Sets a register value. */
void set_register(int address, uint8_t value);
void write(int address, uint8_t value);
/*! Gets a register value. */
uint8_t get_register(int address);
uint8_t read(int address);
/*! Gets the current scan line; provided by the Master System only. */
uint8_t get_current_line();
@@ -69,8 +72,8 @@ class TMS9918: public Base {
void latch_horizontal_counter();
/*!
Returns the amount of time until get_interrupt_line would next return true if
there are no interceding calls to set_register or get_register.
Returns the amount of time until @c get_interrupt_line would next return true if
there are no interceding calls to @c write or to @c read.
If get_interrupt_line is true now, returns zero. If get_interrupt_line would
never return true, returns -1.

View File

@@ -100,7 +100,7 @@ class Base {
// (though, in practice, it won't happen until the next
// external slot after this number of cycles after the
// device has requested the read or write).
return 7;
return 6;
}
// Holds the main status register.

View File

@@ -12,45 +12,56 @@
using namespace GI::AY38910;
AY38910::AY38910(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {
// set up envelope lookup tables
AY38910::AY38910(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {
// Don't use the low bit of the envelope position if this is an AY.
envelope_position_mask_ |= personality == Personality::AY38910;
// Set up envelope lookup tables.
for(int c = 0; c < 16; c++) {
for(int p = 0; p < 32; p++) {
for(int p = 0; p < 64; p++) {
switch(c) {
case 0: case 1: case 2: case 3: case 9:
envelope_shapes_[c][p] = (p < 16) ? (p^0xf) : 0;
envelope_overflow_masks_[c] = 0x1f;
/* Envelope: \____ */
envelope_shapes_[c][p] = (p < 32) ? (p^0x1f) : 0;
envelope_overflow_masks_[c] = 0x3f;
break;
case 4: case 5: case 6: case 7: case 15:
envelope_shapes_[c][p] = (p < 16) ? p : 0;
envelope_overflow_masks_[c] = 0x1f;
/* Envelope: /____ */
envelope_shapes_[c][p] = (p < 32) ? p : 0;
envelope_overflow_masks_[c] = 0x3f;
break;
case 8:
envelope_shapes_[c][p] = (p & 0xf) ^ 0xf;
/* Envelope: \\\\\\\\ */
envelope_shapes_[c][p] = (p & 0x1f) ^ 0x1f;
envelope_overflow_masks_[c] = 0x00;
break;
case 12:
envelope_shapes_[c][p] = (p & 0xf);
/* Envelope: //////// */
envelope_shapes_[c][p] = (p & 0x1f);
envelope_overflow_masks_[c] = 0x00;
break;
case 10:
envelope_shapes_[c][p] = (p & 0xf) ^ ((p < 16) ? 0xf : 0x0);
/* Envelope: \/\/\/\/ */
envelope_shapes_[c][p] = (p & 0x1f) ^ ((p < 32) ? 0x1f : 0x0);
envelope_overflow_masks_[c] = 0x00;
break;
case 14:
envelope_shapes_[c][p] = (p & 0xf) ^ ((p < 16) ? 0x0 : 0xf);
/* Envelope: /\/\/\/\ */
envelope_shapes_[c][p] = (p & 0x1f) ^ ((p < 32) ? 0x0 : 0x1f);
envelope_overflow_masks_[c] = 0x00;
break;
case 11:
envelope_shapes_[c][p] = (p < 16) ? (p^0xf) : 0xf;
envelope_overflow_masks_[c] = 0x1f;
/* Envelope: \------ (if - is high) */
envelope_shapes_[c][p] = (p < 32) ? (p^0x1f) : 0x1f;
envelope_overflow_masks_[c] = 0x3f;
break;
case 13:
envelope_shapes_[c][p] = (p < 16) ? p : 0xf;
envelope_overflow_masks_[c] = 0x1f;
/* Envelope: /------- */
envelope_shapes_[c][p] = (p < 32) ? p : 0x1f;
envelope_overflow_masks_[c] = 0x3f;
break;
}
}
@@ -61,18 +72,27 @@ AY38910::AY38910(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_
void AY38910::set_sample_volume_range(std::int16_t range) {
// set up volume lookup table
const float max_volume = static_cast<float>(range) / 3.0f; // As there are three channels.
const float root_two = sqrtf(2.0f);
for(int v = 0; v < 16; v++) {
volumes_[v] = static_cast<int>(max_volume / powf(root_two, static_cast<float>(v ^ 0xf)));
const float max_volume = float(range) / 3.0f; // As there are three channels.
constexpr float root_two = 1.414213562373095f;
for(int v = 0; v < 32; v++) {
volumes_[v] = int(max_volume / powf(root_two, float(v ^ 0x1f) / 2.0f));
}
volumes_[0] = 0;
volumes_[0] = 0; // Tie level 0 to silence.
evaluate_output_volume();
}
void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) {
// Note on structure below: the real AY has a built-in divider of 8
// prior to applying its tone and noise dividers. But the YM fills the
// same total periods for noise and tone with double-precision envelopes.
// Therefore this class implements a divider of 4 and doubles the tone
// and noise periods. The envelope ticks along at the divide-by-four rate,
// but if this is an AY rather than a YM then its lowest bit is forced to 1,
// matching the YM datasheet's depiction of envelope level 31 as equal to
// programmatic volume 15, envelope level 29 as equal to programmatic 14, etc.
std::size_t c = 0;
while((master_divider_&7) && c < number_of_samples) {
while((master_divider_&3) && c < number_of_samples) {
target[c] = output_volume_;
master_divider_++;
c++;
@@ -83,49 +103,49 @@ void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) {
if(tone_counters_[c]) tone_counters_[c]--;\
else {\
tone_outputs_[c] ^= 1;\
tone_counters_[c] = tone_periods_[c];\
tone_counters_[c] = tone_periods_[c] << 1;\
}
// update the tone channels
// Update the tone channels.
step_channel(0);
step_channel(1);
step_channel(2);
#undef step_channel
// ... the noise generator. This recomputes the new bit repeatedly but harmlessly, only shifting
// Update the noise generator. This recomputes the new bit repeatedly but harmlessly, only shifting
// it into the official 17 upon divider underflow.
if(noise_counter_) noise_counter_--;
else {
noise_counter_ = noise_period_;
noise_counter_ = noise_period_ << 1; // To cover the double resolution of envelopes.
noise_output_ ^= noise_shift_register_&1;
noise_shift_register_ |= ((noise_shift_register_ ^ (noise_shift_register_ >> 3))&1) << 17;
noise_shift_register_ >>= 1;
}
// ... and the envelope generator. Table based for pattern lookup, with a 'refill' step: a way of
// implementing non-repeating patterns by locking them to table position 0x1f.
// Update the envelope generator. Table based for pattern lookup, with a 'refill' step: a way of
// implementing non-repeating patterns by locking them to the final table position.
if(envelope_divider_) envelope_divider_--;
else {
envelope_divider_ = envelope_period_;
envelope_position_ ++;
if(envelope_position_ == 32) envelope_position_ = envelope_overflow_masks_[output_registers_[13]];
if(envelope_position_ == 64) envelope_position_ = envelope_overflow_masks_[output_registers_[13]];
}
evaluate_output_volume();
for(int ic = 0; ic < 8 && c < number_of_samples; ic++) {
for(int ic = 0; ic < 4 && c < number_of_samples; ic++) {
target[c] = output_volume_;
c++;
master_divider_++;
}
}
master_divider_ &= 7;
master_divider_ &= 3;
}
void AY38910::evaluate_output_volume() {
int envelope_volume = envelope_shapes_[output_registers_[13]][envelope_position_];
int envelope_volume = envelope_shapes_[output_registers_[13]][envelope_position_ | envelope_position_mask_];
// The output level for a channel is:
// 1 if neither tone nor noise is enabled;
@@ -142,9 +162,20 @@ void AY38910::evaluate_output_volume() {
};
#undef level
// Channel volume is a simple selection: if the bit at 0x10 is set, use the envelope volume; otherwise use the lower four bits
// This remapping table seeks to map 'channel volumes', i.e. the levels produced from the
// 16-step progammatic volumes set per channel to 'envelope volumes', i.e. the 32-step
// volumes that are produced by the envelope generators (on a YM at least). My reading of
// the data sheet is that '0' is still off, but 15 should be as loud as peak envelope. So
// I've thrown in the discontinuity at the low end, where it'll be very quiet.
const int channel_volumes[] = {
0, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31
};
static_assert(sizeof(channel_volumes) == 16*sizeof(int));
// Channel volume is a simple selection: if the bit at 0x10 is set, use the envelope volume; otherwise use the lower four bits,
// mapped to the range 131 in case this is a YM.
#define channel_volume(c) \
((output_registers_[c] >> 4)&1) * envelope_volume + (((output_registers_[c] >> 4)&1)^1) * (output_registers_[c]&0xf)
((output_registers_[c] >> 4)&1) * envelope_volume + (((output_registers_[c] >> 4)&1)^1) * channel_volumes[output_registers_[c]&0xf]
const int volumes[3] = {
channel_volume(8),

View File

@@ -35,9 +35,10 @@ class PortHandler {
}
/*!
Requests the current input on an AY port.
Sets the current output on an AY port.
@param port_b @c true if the input being queried is Port B. @c false if it is Port A.
@param port_b @c true if the output being posted is Port B. @c false if it is Port A.
@param value the value now being output.
*/
virtual void set_port_output(bool port_b, uint8_t value) {}
};
@@ -51,6 +52,13 @@ enum ControlLines {
BDIR = (1 << 2)
};
enum class Personality {
/// Provides 16 volume levels to envelopes.
AY38910,
/// Provides 32 volume levels to envelopes.
YM2149F
};
/*!
Provides emulation of an AY-3-8910 / YM2149, which is a three-channel sound chip with a
noise generator and a volume envelope generator, which also provides two bidirectional
@@ -59,7 +67,7 @@ enum ControlLines {
class AY38910: public ::Outputs::Speaker::SampleSource {
public:
/// Creates a new AY38910.
AY38910(Concurrency::DeferringAsyncTaskQueue &task_queue);
AY38910(Personality, Concurrency::DeferringAsyncTaskQueue &);
/// Sets the value the AY would read from its data lines if it were not outputting.
void set_data_input(uint8_t r);
@@ -108,11 +116,11 @@ class AY38910: public ::Outputs::Speaker::SampleSource {
int envelope_period_ = 0;
int envelope_divider_ = 0;
int envelope_position_ = 0;
int envelope_shapes_[16][32];
int envelope_position_ = 0, envelope_position_mask_ = 0;
int envelope_shapes_[16][64];
int envelope_overflow_masks_[16];
int volumes_[16];
int volumes_[32];
enum ControlState {
Inactive,

View File

@@ -78,7 +78,7 @@ void DiskII::select_drive(int drive) {
void DiskII::run_for(const Cycles cycles) {
if(preferred_clocking() == ClockingHint::Preference::None) return;
int integer_cycles = cycles.as_int();
auto integer_cycles = cycles.as_integral();
while(integer_cycles--) {
const int address = (state_ & 0xf0) | inputs_ | ((shift_register_&0x80) >> 6);
if(flux_duration_) {
@@ -124,7 +124,7 @@ void DiskII::run_for(const Cycles cycles) {
// motor switch being flipped and the drive motor actually switching off.
// This models that, accepting overrun as a risk.
if(motor_off_time_ >= 0) {
motor_off_time_ -= cycles.as_int();
motor_off_time_ -= cycles.as_integral();
if(motor_off_time_ < 0) {
set_control(Control::Motor, false);
}
@@ -266,7 +266,7 @@ int DiskII::read_address(int address) {
break;
case 0xf:
if(!(inputs_ & input_mode))
drives_[active_drive_].begin_writing(Storage::Time(1, clock_rate_), false);
drives_[active_drive_].begin_writing(Storage::Time(1, int(clock_rate_)), false);
inputs_ |= input_mode;
break;
}

View File

@@ -26,7 +26,7 @@ namespace Apple {
/*!
Provides an emulation of the Apple Disk II.
*/
class DiskII:
class DiskII :
public Storage::Disk::Drive::EventDelegate,
public ClockingHint::Source,
public ClockingHint::Observer {
@@ -48,7 +48,7 @@ class DiskII:
The value returned by @c read_address if accessing that address
didn't cause the disk II to place anything onto the bus.
*/
const int DidNotLoad = -1;
static constexpr int DidNotLoad = -1;
/// Advances the controller by @c cycles.
void run_for(const Cycles cycles);
@@ -76,7 +76,7 @@ class DiskII:
void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive);
// As per Sleeper.
ClockingHint::Preference preferred_clocking() override;
ClockingHint::Preference preferred_clocking() final;
// The Disk II functions as a potential target for @c Activity::Sources.
void set_activity_observer(Activity::Observer *observer);
@@ -98,10 +98,10 @@ class DiskII:
void select_drive(int drive);
uint8_t trigger_address(int address, uint8_t value);
void process_event(const Storage::Disk::Drive::Event &event) override;
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) override;
void process_event(const Storage::Disk::Drive::Event &event) final;
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) final;
const int clock_rate_ = 0;
const Cycles::IntType clock_rate_ = 0;
uint8_t state_ = 0;
uint8_t inputs_ = 0;
@@ -109,7 +109,7 @@ class DiskII:
int stepper_mask_ = 0;
int stepper_position_ = 0;
int motor_off_time_ = -1;
Cycles::IntType motor_off_time_ = -1;
bool is_write_protected();
std::array<uint8_t, 256> state_machine_;

View File

@@ -13,15 +13,15 @@
using namespace Apple;
namespace {
const int CA0 = 1 << 0;
const int CA1 = 1 << 1;
const int CA2 = 1 << 2;
const int LSTRB = 1 << 3;
const int ENABLE = 1 << 4;
const int DRIVESEL = 1 << 5; /* This means drive select, like on the original Disk II. */
const int Q6 = 1 << 6;
const int Q7 = 1 << 7;
const int SEL = 1 << 8; /* This is an additional input, not available on a Disk II, with a confusingly-similar name to SELECT but a distinct purpose. */
constexpr int CA0 = 1 << 0;
constexpr int CA1 = 1 << 1;
constexpr int CA2 = 1 << 2;
constexpr int LSTRB = 1 << 3;
constexpr int ENABLE = 1 << 4;
constexpr int DRIVESEL = 1 << 5; /* This means drive select, like on the original Disk II. */
constexpr int Q6 = 1 << 6;
constexpr int Q7 = 1 << 7;
constexpr int SEL = 1 << 8; /* This is an additional input, not available on a Disk II, with a confusingly-similar name to SELECT but a distinct purpose. */
}
IWM::IWM(int clock_rate) :
@@ -233,25 +233,34 @@ void IWM::run_for(const Cycles cycles) {
}
// Activity otherwise depends on mode and motor state.
int integer_cycles = cycles.as_int();
auto integer_cycles = cycles.as_integral();
switch(shift_mode_) {
case ShiftMode::Reading:
case ShiftMode::Reading: {
// Per the IWM patent, column 7, around line 35 onwards: "The expected time
// is widened by approximately one-half an interval before and after the
// expected time since the data is not precisely spaced when read due to
// variations in drive speed and other external factors". The error_margin
// here implements the 'after' part of that contract.
const auto error_margin = Cycles(bit_length_.as_integral() >> 1);
if(drive_is_rotating_[active_drive_]) {
while(integer_cycles--) {
drives_[active_drive_]->run_for(Cycles(1));
++cycles_since_shift_;
if(cycles_since_shift_ == bit_length_ + Cycles(2)) {
if(cycles_since_shift_ == bit_length_ + error_margin) {
propose_shift(0);
}
}
} else {
while(cycles_since_shift_ + integer_cycles >= bit_length_ + Cycles(2)) {
while(cycles_since_shift_ + integer_cycles >= bit_length_ + error_margin) {
const auto run_length = bit_length_ + error_margin - cycles_since_shift_;
integer_cycles -= run_length.as_integral();
cycles_since_shift_ += run_length;
propose_shift(0);
integer_cycles -= bit_length_.as_int() + 2 - cycles_since_shift_.as_int();
}
cycles_since_shift_ += Cycles(integer_cycles);
}
break;
} break;
case ShiftMode::Writing:
if(drives_[active_drive_]->is_writing()) {
@@ -263,7 +272,7 @@ void IWM::run_for(const Cycles cycles) {
drives_[active_drive_]->write_bit(shift_register_ & 0x80);
shift_register_ <<= 1;
integer_cycles -= cycles_until_write.as_int();
integer_cycles -= cycles_until_write.as_integral();
cycles_since_shift_ = Cycles(0);
--output_bits_remaining_;
@@ -324,7 +333,7 @@ void IWM::select_shift_mode() {
// If writing mode just began, set the drive into write mode and cue up the first output byte.
if(drives_[active_drive_] && old_shift_mode != ShiftMode::Writing && shift_mode_ == ShiftMode::Writing) {
drives_[active_drive_]->begin_writing(Storage::Time(1, clock_rate_ / bit_length_.as_int()), false);
drives_[active_drive_]->begin_writing(Storage::Time(1, clock_rate_ / bit_length_.as_integral()), false);
shift_register_ = next_output_;
write_handshake_ |= 0x80 | 0x40;
output_bits_remaining_ = 8;
@@ -351,12 +360,28 @@ void IWM::propose_shift(uint8_t bit) {
// TODO: synchronous mode.
// LOG("Shifting input");
// See above for text from the IWM patent, column 7, around line 35 onwards.
// The error_margin here implements the 'before' part of that contract.
//
// Basic effective logic: if at least 1 is fozund in the bit_length_ cycles centred
// on the current expected bit delivery time as implied by cycles_since_shift_,
// shift in a 1 and start a new window wherever the first found 1 was.
//
// If no 1s are found, shift in a 0 and don't alter expectations as to window placement.
const auto error_margin = Cycles(bit_length_.as_integral() >> 1);
if(bit && cycles_since_shift_ < error_margin) return;
shift_register_ = uint8_t((shift_register_ << 1) | bit);
if(shift_register_ & 0x80) {
data_register_ = shift_register_;
shift_register_ = 0;
}
cycles_since_shift_ = Cycles(0);
if(bit)
cycles_since_shift_ = Cycles(0);
else
cycles_since_shift_ -= bit_length_;
}
void IWM::set_drive(int slot, IWMDrive *drive) {
@@ -374,3 +399,8 @@ void IWM::set_component_prefers_clocking(ClockingHint::Source *component, Clocki
drive_is_rotating_[1] = is_rotating;
}
}
void IWM::set_activity_observer(Activity::Observer *observer) {
if(drives_[0]) drives_[0]->set_activity_observer(observer, "Internal Floppy", true);
if(drives_[1]) drives_[1]->set_activity_observer(observer, "External Floppy", true);
}

View File

@@ -9,8 +9,11 @@
#ifndef IWM_hpp
#define IWM_hpp
#include "../../Activity/Observer.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../ClockReceiver/ClockingHintSource.hpp"
#include "../../Storage/Disk/Drive.hpp"
#include <cstdint>
@@ -67,9 +70,13 @@ class IWM:
/// Connects a drive to the IWM.
void set_drive(int slot, IWMDrive *drive);
/// Registers the currently-connected drives as @c Activity::Sources ;
/// the first will be declared 'Internal', the second 'External'.
void set_activity_observer(Activity::Observer *observer);
private:
// Storage::Disk::Drive::EventDelegate.
void process_event(const Storage::Disk::Drive::Event &event) override;
void process_event(const Storage::Disk::Drive::Event &event) final;
const int clock_rate_;
@@ -84,7 +91,7 @@ class IWM:
IWMDrive *drives_[2] = {nullptr, nullptr};
bool drive_is_rotating_[2] = {false, false};
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override;
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) final;
Cycles cycles_until_disable_;
uint8_t write_handshake_ = 0x80;

View File

@@ -71,7 +71,9 @@ void DoubleDensityDrive::set_rotation_speed(float revolutions_per_minute) {
// MARK: - Control input/output.
void DoubleDensityDrive::set_enabled(bool) {
void DoubleDensityDrive::set_enabled(bool enabled) {
// Disabling a drive also stops its motor.
if(!enabled) set_motor_on(false);
}
void DoubleDensityDrive::set_control_lines(int lines) {

View File

@@ -32,14 +32,14 @@ class DoubleDensityDrive: public IWMDrive {
*/
void set_rotation_speed(float revolutions_per_minute);
void set_enabled(bool) override;
void set_control_lines(int) override;
bool read() override;
private:
void set_enabled(bool) final;
void set_control_lines(int) final;
bool read() final;
// To receive the proper notifications from Storage::Disk::Drive.
void did_step(Storage::Disk::HeadPosition to_position) override;
void did_set_disk() override;
void did_step(Storage::Disk::HeadPosition to_position) final;
void did_set_disk() final;
const bool is_800k_;
bool has_new_disk_ = false;

View File

@@ -48,7 +48,7 @@ void SN76489::set_sample_volume_range(std::int16_t range) {
evaluate_output_volume();
}
void SN76489::set_register(uint8_t value) {
void SN76489::write(uint8_t value) {
task_queue_.defer([value, this] () {
if(value & 0x80) {
active_register_ = value;

View File

@@ -26,7 +26,7 @@ class SN76489: public Outputs::Speaker::SampleSource {
SN76489(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue, int additional_divider = 1);
/// Writes a new value to the SN76489.
void set_register(uint8_t value);
void write(uint8_t value);
// As per SampleSource.
void get_samples(std::size_t number_of_samples, std::int16_t *target);

140
Components/Serial/Line.cpp Normal file
View File

@@ -0,0 +1,140 @@
//
// SerialPort.cpp
// Clock Signal
//
// Created by Thomas Harte on 12/10/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#include "Line.hpp"
using namespace Serial;
void Line::set_writer_clock_rate(HalfCycles clock_rate) {
clock_rate_ = clock_rate;
}
void Line::advance_writer(HalfCycles cycles) {
if(cycles == HalfCycles(0)) return;
const auto integral_cycles = cycles.as_integral();
remaining_delays_ = std::max(remaining_delays_ - integral_cycles, Cycles::IntType(0));
if(events_.empty()) {
write_cycles_since_delegate_call_ += integral_cycles;
if(transmission_extra_) {
transmission_extra_ -= integral_cycles;
if(transmission_extra_ <= 0) {
transmission_extra_ = 0;
update_delegate(level_);
}
}
} else {
while(!events_.empty()) {
if(events_.front().delay <= integral_cycles) {
cycles -= events_.front().delay;
write_cycles_since_delegate_call_ += events_.front().delay;
const auto old_level = level_;
auto iterator = events_.begin() + 1;
while(iterator != events_.end() && iterator->type != Event::Delay) {
level_ = iterator->type == Event::SetHigh;
++iterator;
}
events_.erase(events_.begin(), iterator);
if(old_level != level_) {
update_delegate(old_level);
}
// Book enough extra time for the read delegate to be posted
// the final bit if one is attached.
if(events_.empty()) {
transmission_extra_ = minimum_write_cycles_for_read_delegate_bit();
}
} else {
events_.front().delay -= integral_cycles;
write_cycles_since_delegate_call_ += integral_cycles;
break;
}
}
}
}
void Line::write(bool level) {
if(!events_.empty()) {
events_.emplace_back();
events_.back().type = level ? Event::SetHigh : Event::SetLow;
} else {
level_ = level;
transmission_extra_ = minimum_write_cycles_for_read_delegate_bit();
}
}
void Line::write(HalfCycles cycles, int count, int levels) {
remaining_delays_ += count * cycles.as_integral();
auto event = events_.size();
events_.resize(events_.size() + size_t(count)*2);
while(count--) {
events_[event].type = Event::Delay;
events_[event].delay = int(cycles.as_integral());
events_[event+1].type = (levels&1) ? Event::SetHigh : Event::SetLow;
levels >>= 1;
event += 2;
}
}
void Line::reset_writing() {
remaining_delays_ = 0;
events_.clear();
}
bool Line::read() {
return level_;
}
void Line::set_read_delegate(ReadDelegate *delegate, Storage::Time bit_length) {
read_delegate_ = delegate;
read_delegate_bit_length_ = bit_length;
read_delegate_bit_length_.simplify();
write_cycles_since_delegate_call_ = 0;
}
void Line::update_delegate(bool level) {
// Exit early if there's no delegate, or if the delegate is waiting for
// zero and this isn't zero.
if(!read_delegate_) return;
const int cycles_to_forward = write_cycles_since_delegate_call_;
write_cycles_since_delegate_call_ = 0;
if(level && read_delegate_phase_ == ReadDelegatePhase::WaitingForZero) return;
// Deal with a transition out of waiting-for-zero mode by seeding time left
// in bit at half a bit.
if(read_delegate_phase_ == ReadDelegatePhase::WaitingForZero) {
time_left_in_bit_ = read_delegate_bit_length_;
time_left_in_bit_.clock_rate <<= 1;
read_delegate_phase_ = ReadDelegatePhase::Serialising;
}
// Forward as many bits as occur.
Storage::Time time_left(cycles_to_forward, int(clock_rate_.as_integral()));
const int bit = level ? 1 : 0;
int bits = 0;
while(time_left >= time_left_in_bit_) {
++bits;
if(!read_delegate_->serial_line_did_produce_bit(this, bit)) {
read_delegate_phase_ = ReadDelegatePhase::WaitingForZero;
if(bit) return;
}
time_left -= time_left_in_bit_;
time_left_in_bit_ = read_delegate_bit_length_;
}
time_left_in_bit_ -= time_left;
}
Cycles::IntType Line::minimum_write_cycles_for_read_delegate_bit() {
if(!read_delegate_) return 0;
return 1 + (read_delegate_bit_length_ * static_cast<unsigned int>(clock_rate_.as_integral())).get<int>();
}

112
Components/Serial/Line.hpp Normal file
View File

@@ -0,0 +1,112 @@
//
// SerialPort.hpp
// Clock Signal
//
// Created by Thomas Harte on 12/10/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef SerialPort_hpp
#define SerialPort_hpp
#include <vector>
#include "../../Storage/Storage.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../ClockReceiver/ForceInline.hpp"
namespace Serial {
/*!
@c Line connects a single reader and a single writer, allowing timestamped events to be
published and consumed, potentially with a clock conversion in between. It allows line
levels to be written and read in larger collections.
It is assumed that the owner of the reader and writer will ensure that the reader will never
get ahead of the writer. If the writer posts events behind the reader they will simply be
given instanteous effect.
*/
class Line {
public:
void set_writer_clock_rate(HalfCycles clock_rate);
/// Advances the read position by @c cycles relative to the writer's
/// clock rate.
void advance_writer(HalfCycles cycles);
/// Sets the line to @c level.
void write(bool level);
/// Enqueues @c count level changes, the first occurring immediately
/// after the final event currently posted and each subsequent event
/// occurring @c cycles after the previous. An additional gap of @c cycles
/// is scheduled after the final output. The levels to output are
/// taken from @c levels which is read from lsb to msb. @c cycles is
/// relative to the writer's clock rate.
void write(HalfCycles cycles, int count, int levels);
/// @returns the number of cycles until currently enqueued write data is exhausted.
forceinline HalfCycles write_data_time_remaining() const {
return HalfCycles(remaining_delays_);
}
/// @returns the number of cycles left until it is guaranteed that a passive reader
/// has received all currently-enqueued bits.
forceinline HalfCycles transmission_data_time_remaining() const {
return HalfCycles(remaining_delays_ + transmission_extra_);
}
/// Eliminates all future write states, leaving the output at whatever it is now.
void reset_writing();
/// @returns The instantaneous level of this line.
bool read();
struct ReadDelegate {
virtual bool serial_line_did_produce_bit(Line *line, int bit) = 0;
};
/*!
Sets a read delegate, which will receive samples of the output level every
@c bit_lengths of a second apart subject to a state machine:
* initially no bits will be delivered;
* when a zero level is first detected, the line will wait half a bit's length, then start
sampling at single-bit intervals, passing each bit to the delegate while it returns @c true;
* as soon as the delegate returns @c false, the line will return to the initial state.
*/
void set_read_delegate(ReadDelegate *delegate, Storage::Time bit_length);
private:
struct Event {
enum Type {
Delay, SetHigh, SetLow
} type;
int delay;
};
std::vector<Event> events_;
HalfCycles::IntType remaining_delays_ = 0;
HalfCycles::IntType transmission_extra_ = 0;
bool level_ = true;
HalfCycles clock_rate_ = 0;
ReadDelegate *read_delegate_ = nullptr;
Storage::Time read_delegate_bit_length_, time_left_in_bit_;
int write_cycles_since_delegate_call_ = 0;
enum class ReadDelegatePhase {
WaitingForZero,
Serialising
} read_delegate_phase_ = ReadDelegatePhase::WaitingForZero;
void update_delegate(bool level);
HalfCycles::IntType minimum_write_cycles_for_read_delegate_bit();
};
/*!
Defines an RS-232-esque srial port.
*/
class Port {
public:
};
}
#endif /* SerialPort_hpp */

View File

@@ -18,7 +18,7 @@ AsyncTaskQueue::AsyncTaskQueue()
#ifdef __APPLE__
serial_dispatch_queue_ = dispatch_queue_create("com.thomasharte.clocksignal.asyntaskqueue", DISPATCH_QUEUE_SERIAL);
#else
thread_.reset(new std::thread([this]() {
thread_ = std::make_unique<std::thread>([this]() {
while(!should_destruct_) {
std::function<void(void)> next_function;
@@ -39,7 +39,7 @@ AsyncTaskQueue::AsyncTaskQueue()
processing_condition_.wait(lock);
}
}
}));
});
#endif
}
@@ -70,8 +70,8 @@ void AsyncTaskQueue::flush() {
#ifdef __APPLE__
dispatch_sync(serial_dispatch_queue_, ^{});
#else
std::shared_ptr<std::mutex> flush_mutex(new std::mutex);
std::shared_ptr<std::condition_variable> flush_condition(new std::condition_variable);
auto flush_mutex = std::make_shared<std::mutex>();
auto flush_condition = std::make_shared<std::condition_variable>();
std::unique_lock<std::mutex> lock(*flush_mutex);
enqueue([=] () {
std::unique_lock<std::mutex> inner_lock(*flush_mutex);
@@ -88,7 +88,7 @@ DeferringAsyncTaskQueue::~DeferringAsyncTaskQueue() {
void DeferringAsyncTaskQueue::defer(std::function<void(void)> function) {
if(!deferred_tasks_) {
deferred_tasks_.reset(new std::list<std::function<void(void)>>);
deferred_tasks_ = std::make_shared<std::list<std::function<void(void)>>>();
}
deferred_tasks_->push_back(function);
}

View File

@@ -12,61 +12,89 @@
using namespace Concurrency;
BestEffortUpdater::BestEffortUpdater() {
// ATOMIC_FLAG_INIT isn't necessarily safe to use, so establish default state by other means.
update_is_ongoing_.clear();
}
BestEffortUpdater::BestEffortUpdater() :
update_thread_([this]() {
this->update_loop();
}) {}
BestEffortUpdater::~BestEffortUpdater() {
// Don't allow further deconstruction until the task queue is stopped.
// Sever the delegate now, as soon as possible, then wait for any
// pending tasks to finish.
set_delegate(nullptr);
flush();
// Wind up the update thread.
should_quit_ = true;
update();
update_thread_.join();
}
void BestEffortUpdater::update() {
// Perform an update only if one is not currently ongoing.
if(!update_is_ongoing_.test_and_set()) {
async_task_queue_.enqueue([this]() {
// Get time now using the highest-resolution clock provided by the implementation, and determine
// the duration since the last time this section was entered.
const std::chrono::time_point<std::chrono::high_resolution_clock> now = std::chrono::high_resolution_clock::now();
const auto elapsed = now - previous_time_point_;
previous_time_point_ = now;
void BestEffortUpdater::update(int flags) {
// Bump the requested target time and set the update requested flag.
{
std::lock_guard<decltype(update_mutex_)> lock(update_mutex_);
has_skipped_ = update_requested_;
update_requested_ = true;
flags_ |= flags;
target_time_ = std::chrono::high_resolution_clock::now().time_since_epoch().count();
}
update_condition_.notify_one();
}
if(has_previous_time_point_) {
// If the duration is valid, convert it to integer cycles, maintaining a rolling error and call the delegate
// if there is one. Proceed only if the number of cycles is positive, and cap it to the per-second maximum as
// it's possible this is an adjustable clock so be ready to swallow unexpected adjustments.
const int64_t integer_duration = std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed).count();
if(integer_duration > 0) {
if(delegate_) {
// Cap running at 1/5th of a second, to avoid doing a huge amount of work after any
// brief system interruption.
const double duration = std::min(static_cast<double>(integer_duration) / 1e9, 0.2);
delegate_->update(this, duration, has_skipped_);
}
has_skipped_ = false;
}
} else {
has_previous_time_point_ = true;
}
void BestEffortUpdater::update_loop() {
while(true) {
std::unique_lock<decltype(update_mutex_)> lock(update_mutex_);
is_updating_ = false;
// Allow furthers updates to occur.
update_is_ongoing_.clear();
});
} else {
async_task_queue_.enqueue([this]() {
has_skipped_ = true;
// Wait to be signalled.
update_condition_.wait(lock, [this]() -> bool {
return update_requested_;
});
// Possibly this signalling really means 'quit'.
if(should_quit_) return;
// Note update started, crib the target time.
auto target_time = target_time_;
update_requested_ = false;
// If this was actually the first update request, silently swallow it.
if(!has_previous_time_point_) {
has_previous_time_point_ = true;
previous_time_point_ = target_time;
continue;
}
// Release the lock on requesting new updates.
is_updating_ = true;
const int flags = flags_;
flags_ = 0;
lock.unlock();
// Invoke the delegate, if supplied, in order to run.
const int64_t integer_duration = std::max(target_time - previous_time_point_, int64_t(0));
const auto delegate = delegate_.load();
if(delegate) {
// Cap running at 1/5th of a second, to avoid doing a huge amount of work after any
// brief system interruption.
const double duration = std::min(double(integer_duration) / 1e9, 0.2);
const double elapsed_duration = delegate->update(this, duration, has_skipped_, flags);
previous_time_point_ += int64_t(elapsed_duration * 1e9);
has_skipped_ = false;
}
}
}
void BestEffortUpdater::flush() {
async_task_queue_.flush();
// Spin lock; this is allowed to be slow.
while(true) {
std::lock_guard<decltype(update_mutex_)> lock(update_mutex_);
if(!is_updating_) return;
}
}
void BestEffortUpdater::set_delegate(Delegate *const delegate) {
async_task_queue_.enqueue([this, delegate]() {
delegate_ = delegate;
});
delegate_.store(delegate);
}

View File

@@ -11,8 +11,10 @@
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <mutex>
#include <thread>
#include "AsyncTaskQueue.hpp"
#include "../ClockReceiver/TimeTypes.hpp"
namespace Concurrency {
@@ -31,7 +33,13 @@ class BestEffortUpdater {
/// A delegate receives timing cues.
struct Delegate {
virtual void update(BestEffortUpdater *updater, Time::Seconds duration, bool did_skip_previous_update) = 0;
/*!
Instructs the delegate to run for at least @c duration, providing hints as to whether multiple updates were requested before the previous had completed
(as @c did_skip_previous_update) and providing the union of any flags supplied to @c update.
@returns The amount of time actually run for.
*/
virtual Time::Seconds update(BestEffortUpdater *updater, Time::Seconds duration, bool did_skip_previous_update, int flags) = 0;
};
/// Sets the current delegate.
@@ -41,20 +49,32 @@ class BestEffortUpdater {
If the delegate is not currently in the process of an `update` call, calls it now to catch up to the current time.
The call is asynchronous; this method will return immediately.
*/
void update();
void update(int flags = 0);
/// Blocks until any ongoing update is complete.
/// Blocks until any ongoing update is complete; may spin.
void flush();
private:
std::atomic_flag update_is_ongoing_;
AsyncTaskQueue async_task_queue_;
std::atomic<bool> should_quit_;
std::atomic<bool> is_updating_;
std::chrono::time_point<std::chrono::high_resolution_clock> previous_time_point_;
int64_t target_time_;
int flags_ = 0;
bool update_requested_;
std::mutex update_mutex_;
std::condition_variable update_condition_;
decltype(target_time_) previous_time_point_;
bool has_previous_time_point_ = false;
bool has_skipped_ = false;
std::atomic<bool> has_skipped_ = false;
Delegate *delegate_ = nullptr;
std::atomic<Delegate *>delegate_ = nullptr;
void update_loop();
// This is deliberately at the bottom, to ensure it constructs after the various
// mutexs, conditions, etc, that it'll depend upon.
std::thread update_thread_;
};
}

View File

@@ -14,16 +14,16 @@ namespace {
Appends a Boolean selection of @c selection for option @c name to @c selection_set.
*/
void append_bool(Configurable::SelectionSet &selection_set, const std::string &name, bool selection) {
selection_set[name] = std::unique_ptr<Configurable::Selection>(new Configurable::BooleanSelection(selection));
selection_set[name] = std::make_unique<Configurable::BooleanSelection>(selection);
}
/*!
Enquires for a Boolean selection for option @c name from @c selections_by_option, storing it to @c result if found.
*/
bool get_bool(const Configurable::SelectionSet &selections_by_option, const std::string &name, bool &result) {
auto quickload = Configurable::selection<Configurable::BooleanSelection>(selections_by_option, "quickload");
if(!quickload) return false;
result = quickload->value;
auto selection = Configurable::selection<Configurable::BooleanSelection>(selections_by_option, name);
if(!selection) return false;
result = selection->value;
return true;
}
@@ -42,6 +42,7 @@ std::vector<std::unique_ptr<Configurable::Option>> Configurable::standard_option
options.emplace_back(new Configurable::ListOption("Display", "display", display_options));
}
if(mask & AutomaticTapeMotorControl) options.emplace_back(new Configurable::BooleanOption("Automatic Tape Motor Control", "autotapemotor"));
if(mask & QuickBoot) options.emplace_back(new Configurable::BooleanOption("Boot Quickly", "quickboot"));
return options;
}
@@ -63,7 +64,11 @@ void Configurable::append_display_selection(Configurable::SelectionSet &selectio
case Display::CompositeMonochrome: string_selection = "composite-mono"; break;
case Display::CompositeColour: string_selection = "composite"; break;
}
selection_set["display"] = std::unique_ptr<Configurable::Selection>(new Configurable::ListSelection(string_selection));
selection_set["display"] = std::make_unique<Configurable::ListSelection>(string_selection);
}
void Configurable::append_quick_boot_selection(Configurable::SelectionSet &selection_set, bool selection) {
append_bool(selection_set, "quickboot", selection);
}
// MARK: - Selection parsers
@@ -97,3 +102,7 @@ bool Configurable::get_display(const Configurable::SelectionSet &selections_by_o
}
return false;
}
bool Configurable::get_quick_boot(const Configurable::SelectionSet &selections_by_option, bool &result) {
return get_bool(selections_by_option, "quickboot", result);
}

View File

@@ -19,7 +19,8 @@ enum StandardOptions {
DisplayCompositeColour = (1 << 2),
DisplayCompositeMonochrome = (1 << 3),
QuickLoadTape = (1 << 4),
AutomaticTapeMotorControl = (1 << 5)
AutomaticTapeMotorControl = (1 << 5),
QuickBoot = (1 << 6),
};
enum class Display {
@@ -49,6 +50,11 @@ void append_automatic_tape_motor_control_selection(SelectionSet &selection_set,
*/
void append_display_selection(SelectionSet &selection_set, Display selection);
/*!
Appends to @c selection_set a selection of @c selection for QuickBoot.
*/
void append_quick_boot_selection(SelectionSet &selection_set, bool selection);
/*!
Attempts to discern a QuickLoadTape selection from @c selections_by_option.
@@ -76,6 +82,15 @@ bool get_automatic_tape_motor_control_selection(const SelectionSet &selections_b
*/
bool get_display(const SelectionSet &selections_by_option, Display &result);
/*!
Attempts to QuickBoot a QuickLoadTape selection from @c selections_by_option.
@param selections_by_option The user selections.
@param result The location to which the selection will be stored if found.
@returns @c true if a selection is found; @c false otherwise.
*/
bool get_quick_boot(const SelectionSet &selections_by_option, bool &result);
}
#endif /* StandardOptions_hpp */

View File

@@ -170,11 +170,11 @@ class ConcreteJoystick: public Joystick {
}
}
std::vector<Input> &get_inputs() override final {
std::vector<Input> &get_inputs() final {
return inputs_;
}
void set_input(const Input &input, bool is_active) override final {
void set_input(const Input &input, bool is_active) final {
// If this is a digital setting to a digital property, just pass it along.
if(input.is_button() || stick_types_[input.info.control.index] == StickType::Digital) {
did_set_input(input, is_active);
@@ -193,7 +193,7 @@ class ConcreteJoystick: public Joystick {
}
}
void set_input(const Input &input, float value) override final {
void set_input(const Input &input, float value) final {
// If this is an analogue setting to an analogue property, just pass it along.
if(!input.is_button() && stick_types_[input.info.control.index] == StickType::Analogue) {
did_set_input(input, value);

View File

@@ -10,13 +10,14 @@
using namespace Inputs;
Keyboard::Keyboard() {
Keyboard::Keyboard(const std::set<Key> &essential_modifiers) : essential_modifiers_(essential_modifiers) {
for(int k = 0; k < int(Key::Help); ++k) {
observed_keys_.insert(Key(k));
}
}
Keyboard::Keyboard(const std::set<Key> &observed_keys) : observed_keys_(observed_keys), is_exclusive_(false) {}
Keyboard::Keyboard(const std::set<Key> &observed_keys, const std::set<Key> &essential_modifiers) :
observed_keys_(observed_keys), essential_modifiers_(essential_modifiers), is_exclusive_(false) {}
void Keyboard::set_key_pressed(Key key, char value, bool is_pressed) {
std::size_t key_offset = static_cast<std::size_t>(key);
@@ -28,6 +29,10 @@ void Keyboard::set_key_pressed(Key key, char value, bool is_pressed) {
if(delegate_) delegate_->keyboard_did_change_key(this, key, is_pressed);
}
const std::set<Inputs::Keyboard::Key> &Keyboard::get_essential_modifiers() {
return essential_modifiers_;
}
void Keyboard::reset_all_keys() {
std::fill(key_states_.begin(), key_states_.end(), false);
if(delegate_) delegate_->reset_all_keys(this);

View File

@@ -23,26 +23,26 @@ class Keyboard {
public:
enum class Key {
Escape, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, PrintScreen, ScrollLock, Pause,
BackTick, k1, k2, k3, k4, k5, k6, k7, k8, k9, k0, Hyphen, Equals, BackSpace,
Tab, Q, W, E, R, T, Y, U, I, O, P, OpenSquareBracket, CloseSquareBracket, BackSlash,
BackTick, k1, k2, k3, k4, k5, k6, k7, k8, k9, k0, Hyphen, Equals, Backspace,
Tab, Q, W, E, R, T, Y, U, I, O, P, OpenSquareBracket, CloseSquareBracket, Backslash,
CapsLock, A, S, D, F, G, H, J, K, L, Semicolon, Quote, Hash, Enter,
LeftShift, Z, X, C, V, B, N, M, Comma, FullStop, ForwardSlash, RightShift,
LeftControl, LeftOption, LeftMeta, Space, RightMeta, RightOption, RightControl,
Left, Right, Up, Down,
Insert, Home, PageUp, Delete, End, PageDown,
NumLock, KeyPadSlash, KeyPadAsterisk, KeyPadDelete,
KeyPad7, KeyPad8, KeyPad9, KeyPadPlus,
KeyPad4, KeyPad5, KeyPad6, KeyPadMinus,
KeyPad1, KeyPad2, KeyPad3, KeyPadEnter,
KeyPad0, KeyPadDecimalPoint, KeyPadEquals,
NumLock, KeypadSlash, KeypadAsterisk, KeypadDelete,
Keypad7, Keypad8, Keypad9, KeypadPlus,
Keypad4, Keypad5, Keypad6, KeypadMinus,
Keypad1, Keypad2, Keypad3, KeypadEnter,
Keypad0, KeypadDecimalPoint, KeypadEquals,
Help
};
/// Constructs a Keyboard that declares itself to observe all keys.
Keyboard();
Keyboard(const std::set<Key> &essential_modifiers = {});
/// Constructs a Keyboard that declares itself to observe only members of @c observed_keys.
Keyboard(const std::set<Key> &observed_keys);
Keyboard(const std::set<Key> &observed_keys, const std::set<Key> &essential_modifiers);
// Host interface.
virtual void set_key_pressed(Key key, char value, bool is_pressed);
@@ -51,10 +51,18 @@ class Keyboard {
/// @returns a set of all Keys that this keyboard responds to.
virtual const std::set<Key> &observed_keys();
/*
/// @returns the list of modifiers that this keyboard considers 'essential' (i.e. both mapped and highly used).
virtual const std::set<Inputs::Keyboard::Key> &get_essential_modifiers();
/*!
@returns @c true if this keyboard, on its original machine, looked
like a complete keyboard — i.e. if a user would expect this keyboard
to be the only thing a real keyboard maps to.
So this would be true of something like the Amstrad CPC, which has a full
keyboard, but it would be false of something like the Sega Master System
which has some buttons that you'd expect an emulator to map to its host
keyboard but which does not offer a full keyboard.
*/
virtual bool is_exclusive();
@@ -68,6 +76,7 @@ class Keyboard {
private:
std::set<Key> observed_keys_;
std::set<Key> essential_modifiers_;
std::vector<bool> key_states_;
Delegate *delegate_ = nullptr;
bool is_exclusive_ = true;

View File

@@ -34,24 +34,24 @@ class QuadratureMouse: public Mouse {
/*
Inputs, to satisfy the Mouse interface.
*/
void move(int x, int y) override {
void move(int x, int y) final {
// Accumulate all provided motion.
axes_[0] += x;
axes_[1] += y;
}
int get_number_of_buttons() override {
int get_number_of_buttons() final {
return number_of_buttons_;
}
void set_button_pressed(int index, bool is_pressed) override {
void set_button_pressed(int index, bool is_pressed) final {
if(is_pressed)
button_flags_ |= (1 << index);
else
button_flags_ &= ~(1 << index);
}
void reset_all_buttons() override {
void reset_all_buttons() final {
button_flags_ = 0;
}

View File

@@ -41,7 +41,7 @@ namespace AmstradCPC {
std::vector<std::unique_ptr<Configurable::Option>> get_options() {
return Configurable::standard_options(
static_cast<Configurable::StandardOptions>(Configurable::DisplayRGB | Configurable::DisplayCompositeColour)
Configurable::StandardOptions(Configurable::DisplayRGB | Configurable::DisplayCompositeColour)
);
}
@@ -124,7 +124,7 @@ class InterruptTimer {
class AYDeferrer {
public:
/// Constructs a new AY instance and sets its clock rate.
AYDeferrer() : ay_(audio_queue_), speaker_(ay_) {
AYDeferrer() : ay_(GI::AY38910::Personality::AY38910, audio_queue_), speaker_(ay_) {
speaker_.set_input_rate(1000000);
}
@@ -171,7 +171,7 @@ class AYDeferrer {
*/
class CRTCBusHandler {
public:
CRTCBusHandler(uint8_t *ram, InterruptTimer &interrupt_timer) :
CRTCBusHandler(const uint8_t *ram, InterruptTimer &interrupt_timer) :
crt_(1024, 1, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red2Green2Blue2),
ram_(ram),
interrupt_timer_(interrupt_timer) {
@@ -222,9 +222,9 @@ class CRTCBusHandler {
if(cycles_) {
switch(previous_output_mode_) {
default:
case OutputMode::Blank: crt_.output_blank(cycles_ * 16); break;
case OutputMode::Blank: crt_.output_blank(cycles_ * 16); break;
case OutputMode::Sync: crt_.output_sync(cycles_ * 16); break;
case OutputMode::Border: output_border(cycles_); break;
case OutputMode::Border: output_border(cycles_); break;
case OutputMode::ColourBurst: crt_.output_default_colour_burst(cycles_ * 16); break;
case OutputMode::Pixels:
crt_.output_data(cycles_ * 16, size_t(cycles_ * 16 / pixel_divider_));
@@ -249,44 +249,46 @@ class CRTCBusHandler {
// the CPC shuffles output lines as:
// MA13 MA12 RA2 RA1 RA0 MA9 MA8 MA7 MA6 MA5 MA4 MA3 MA2 MA1 MA0 CCLK
// ... so form the real access address.
uint16_t address =
static_cast<uint16_t>(
const uint16_t address =
uint16_t(
((state.refresh_address & 0x3ff) << 1) |
((state.row_address & 0x7) << 11) |
((state.refresh_address & 0x3000) << 2)
);
// fetch two bytes and translate into pixels
// Fetch two bytes and translate into pixels. Guaranteed: the mode can change only at
// hsync, so there's no risk of pixel_pointer_ overrunning 320 output pixels without
// exactly reaching 320 output pixels.
switch(mode_) {
case 0:
reinterpret_cast<uint16_t *>(pixel_pointer_)[0] = mode0_output_[ram_[address]];
reinterpret_cast<uint16_t *>(pixel_pointer_)[1] = mode0_output_[ram_[address+1]];
pixel_pointer_ += 4;
pixel_pointer_ += 2 * sizeof(uint16_t);
break;
case 1:
reinterpret_cast<uint32_t *>(pixel_pointer_)[0] = mode1_output_[ram_[address]];
reinterpret_cast<uint32_t *>(pixel_pointer_)[1] = mode1_output_[ram_[address+1]];
pixel_pointer_ += 8;
pixel_pointer_ += 2 * sizeof(uint32_t);
break;
case 2:
reinterpret_cast<uint64_t *>(pixel_pointer_)[0] = mode2_output_[ram_[address]];
reinterpret_cast<uint64_t *>(pixel_pointer_)[1] = mode2_output_[ram_[address+1]];
pixel_pointer_ += 16;
pixel_pointer_ += 2 * sizeof(uint64_t);
break;
case 3:
reinterpret_cast<uint16_t *>(pixel_pointer_)[0] = mode3_output_[ram_[address]];
reinterpret_cast<uint16_t *>(pixel_pointer_)[1] = mode3_output_[ram_[address+1]];
pixel_pointer_ += 4;
pixel_pointer_ += 2 * sizeof(uint16_t);
break;
}
// flush the current buffer pixel if full; the CRTC allows many different display
// Flush the current buffer pixel if full; the CRTC allows many different display
// widths so it's not necessarily possible to predict the correct number in advance
// and using the upper bound could lead to inefficient behaviour
// and using the upper bound could lead to inefficient behaviour.
if(pixel_pointer_ == pixel_data_ + 320) {
crt_.output_data(cycles_ * 16, size_t(cycles_ * 16 / pixel_divider_));
pixel_pointer_ = pixel_data_ = nullptr;
@@ -333,6 +335,11 @@ class CRTCBusHandler {
crt_.set_scan_target(scan_target);
}
/// @returns The current scan status.
Outputs::Display::ScanStatus get_scaled_scan_status() const {
return crt_.get_scaled_scan_status() / 4.0f;
}
/// Sets the type of display.
void set_display_type(Outputs::Display::DisplayType display_type) {
crt_.set_display_type(display_type);
@@ -369,9 +376,17 @@ class CRTCBusHandler {
private:
void output_border(int length) {
uint8_t *colour_pointer = static_cast<uint8_t *>(crt_.begin_data(1));
if(colour_pointer) *colour_pointer = border_;
crt_.output_level(length * 16);
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_) {
uint8_t *const colour_pointer = static_cast<uint8_t *>(crt_.begin_data(1));
if(colour_pointer) *colour_pointer = border_;
crt_.output_level(length * 16);
} else {
crt_.output_blank(length * 16);
}
}
#define Mode0Colour0(c) ((c & 0x80) >> 7) | ((c & 0x20) >> 3) | ((c & 0x08) >> 2) | ((c & 0x02) << 2)
@@ -387,16 +402,16 @@ class CRTCBusHandler {
void establish_palette_hits() {
for(int c = 0; c < 256; c++) {
mode0_palette_hits_[Mode0Colour0(c)].push_back(static_cast<uint8_t>(c));
mode0_palette_hits_[Mode0Colour1(c)].push_back(static_cast<uint8_t>(c));
mode0_palette_hits_[Mode0Colour0(c)].push_back(uint8_t(c));
mode0_palette_hits_[Mode0Colour1(c)].push_back(uint8_t(c));
mode1_palette_hits_[Mode1Colour0(c)].push_back(static_cast<uint8_t>(c));
mode1_palette_hits_[Mode1Colour1(c)].push_back(static_cast<uint8_t>(c));
mode1_palette_hits_[Mode1Colour2(c)].push_back(static_cast<uint8_t>(c));
mode1_palette_hits_[Mode1Colour3(c)].push_back(static_cast<uint8_t>(c));
mode1_palette_hits_[Mode1Colour0(c)].push_back(uint8_t(c));
mode1_palette_hits_[Mode1Colour1(c)].push_back(uint8_t(c));
mode1_palette_hits_[Mode1Colour2(c)].push_back(uint8_t(c));
mode1_palette_hits_[Mode1Colour3(c)].push_back(uint8_t(c));
mode3_palette_hits_[Mode3Colour0(c)].push_back(static_cast<uint8_t>(c));
mode3_palette_hits_[Mode3Colour1(c)].push_back(static_cast<uint8_t>(c));
mode3_palette_hits_[Mode3Colour0(c)].push_back(uint8_t(c));
mode3_palette_hits_[Mode3Colour1(c)].push_back(uint8_t(c));
}
}
@@ -406,7 +421,7 @@ class CRTCBusHandler {
// Mode 0: abcdefgh -> [gcea] [hdfb]
for(int c = 0; c < 256; c++) {
// prepare mode 0
uint8_t *mode0_pixels = reinterpret_cast<uint8_t *>(&mode0_output_[c]);
uint8_t *const mode0_pixels = reinterpret_cast<uint8_t *>(&mode0_output_[c]);
mode0_pixels[0] = palette_[Mode0Colour0(c)];
mode0_pixels[1] = palette_[Mode0Colour1(c)];
}
@@ -415,7 +430,7 @@ class CRTCBusHandler {
case 1:
for(int c = 0; c < 256; c++) {
// prepare mode 1
uint8_t *mode1_pixels = reinterpret_cast<uint8_t *>(&mode1_output_[c]);
uint8_t *const mode1_pixels = reinterpret_cast<uint8_t *>(&mode1_output_[c]);
mode1_pixels[0] = palette_[Mode1Colour0(c)];
mode1_pixels[1] = palette_[Mode1Colour1(c)];
mode1_pixels[2] = palette_[Mode1Colour2(c)];
@@ -426,7 +441,7 @@ class CRTCBusHandler {
case 2:
for(int c = 0; c < 256; c++) {
// prepare mode 2
uint8_t *mode2_pixels = reinterpret_cast<uint8_t *>(&mode2_output_[c]);
uint8_t *const mode2_pixels = reinterpret_cast<uint8_t *>(&mode2_output_[c]);
mode2_pixels[0] = palette_[((c & 0x80) >> 7)];
mode2_pixels[1] = palette_[((c & 0x40) >> 6)];
mode2_pixels[2] = palette_[((c & 0x20) >> 5)];
@@ -441,7 +456,7 @@ class CRTCBusHandler {
case 3:
for(int c = 0; c < 256; c++) {
// prepare mode 3
uint8_t *mode3_pixels = reinterpret_cast<uint8_t *>(&mode3_output_[c]);
uint8_t *const mode3_pixels = reinterpret_cast<uint8_t *>(&mode3_output_[c]);
mode3_pixels[0] = palette_[Mode3Colour0(c)];
mode3_pixels[1] = palette_[Mode3Colour1(c)];
}
@@ -453,7 +468,7 @@ class CRTCBusHandler {
switch(mode_) {
case 0: {
for(uint8_t c : mode0_palette_hits_[pen]) {
uint8_t *mode0_pixels = reinterpret_cast<uint8_t *>(&mode0_output_[c]);
uint8_t *const mode0_pixels = reinterpret_cast<uint8_t *>(&mode0_output_[c]);
mode0_pixels[0] = palette_[Mode0Colour0(c)];
mode0_pixels[1] = palette_[Mode0Colour1(c)];
}
@@ -461,7 +476,7 @@ class CRTCBusHandler {
case 1:
if(pen > 3) return;
for(uint8_t c : mode1_palette_hits_[pen]) {
uint8_t *mode1_pixels = reinterpret_cast<uint8_t *>(&mode1_output_[c]);
uint8_t *const mode1_pixels = reinterpret_cast<uint8_t *>(&mode1_output_[c]);
mode1_pixels[0] = palette_[Mode1Colour0(c)];
mode1_pixels[1] = palette_[Mode1Colour1(c)];
mode1_pixels[2] = palette_[Mode1Colour2(c)];
@@ -478,7 +493,7 @@ class CRTCBusHandler {
if(pen > 3) return;
// Same argument applies here as to case 1, as the unused bits aren't masked out.
for(uint8_t c : mode3_palette_hits_[pen]) {
uint8_t *mode3_pixels = reinterpret_cast<uint8_t *>(&mode3_output_[c]);
uint8_t *const mode3_pixels = reinterpret_cast<uint8_t *>(&mode3_output_[c]);
mode3_pixels[0] = palette_[Mode3Colour0(c)];
mode3_pixels[1] = palette_[Mode3Colour1(c)];
}
@@ -499,7 +514,7 @@ class CRTCBusHandler {
uint8_t mapped_palette_value(uint8_t colour) {
#define COL(r, g, b) (r << 4) | (g << 2) | b
static const uint8_t mapping[32] = {
constexpr uint8_t mapping[32] = {
COL(1, 1, 1), COL(1, 1, 1), COL(0, 2, 1), COL(2, 2, 1),
COL(0, 0, 1), COL(2, 0, 1), COL(0, 1, 1), COL(2, 1, 1),
COL(2, 0, 1), COL(2, 2, 1), COL(2, 2, 0), COL(2, 2, 2),
@@ -528,7 +543,7 @@ class CRTCBusHandler {
Outputs::CRT::CRT crt_;
uint8_t *pixel_data_ = nullptr, *pixel_pointer_ = nullptr;
uint8_t *ram_ = nullptr;
const uint8_t *const ram_ = nullptr;
int next_mode_ = 2, mode_ = 2;
@@ -564,7 +579,7 @@ class KeyboardState: public GI::AY38910::PortHandler {
Sets the row currently being reported to the AY.
*/
void set_row(int row) {
row_ = static_cast<size_t>(row);
row_ = size_t(row);
}
/*!
@@ -583,7 +598,7 @@ class KeyboardState: public GI::AY38910::PortHandler {
*/
void set_is_pressed(bool is_pressed, int line, int key) {
int mask = 1 << key;
assert(static_cast<size_t>(line) < sizeof(rows_));
assert(size_t(line) < sizeof(rows_));
if(is_pressed) rows_[line] &= ~mask; else rows_[line] |= mask;
}
@@ -594,7 +609,7 @@ class KeyboardState: public GI::AY38910::PortHandler {
memset(rows_, 0xff, sizeof(rows_));
}
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() {
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() {
return joysticks_;
}
@@ -617,7 +632,7 @@ class KeyboardState: public GI::AY38910::PortHandler {
}),
state_(state) {}
void did_set_input(const Input &input, bool is_active) override {
void did_set_input(const Input &input, bool is_active) final {
uint8_t mask = 0;
switch(input.type) {
default: return;
@@ -646,17 +661,15 @@ class KeyboardState: public GI::AY38910::PortHandler {
class FDC: public Intel::i8272::i8272 {
private:
Intel::i8272::BusHandler bus_handler_;
std::shared_ptr<Storage::Disk::Drive> drive_;
public:
FDC() :
i8272(bus_handler_, Cycles(8000000)),
drive_(new Storage::Disk::Drive(8000000, 300, 1)) {
set_drive(drive_);
FDC() : i8272(bus_handler_, Cycles(8000000)) {
emplace_drive(8000000, 300, 1);
set_drive(1);
}
void set_motor_on(bool on) {
drive_->set_motor_on(on);
get_drive().set_motor_on(on);
}
void select_drive(int c) {
@@ -664,11 +677,11 @@ class FDC: public Intel::i8272::i8272 {
}
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
drive_->set_disk(disk);
get_drive().set_disk(disk);
}
void set_activity_observer(Activity::Observer *observer) {
drive_->set_activity_observer(observer, "Drive 1", true);
get_drive().set_activity_observer(observer, "Drive 1", true);
}
};
@@ -816,8 +829,8 @@ template <bool has_fdc> class ConcreteMachine:
for(std::size_t index = 0; index < roms.size(); ++index) {
auto &data = roms[index];
if(!data) throw ROMMachine::Error::MissingROMs;
roms_[static_cast<int>(index)] = std::move(*data);
roms_[static_cast<int>(index)].resize(16384);
roms_[int(index)] = std::move(*data);
roms_[int(index)].resize(16384);
}
// Establish default memory map
@@ -862,13 +875,15 @@ template <bool has_fdc> class ConcreteMachine:
// TODO (in the player, not here): adapt it to accept an input clock rate and
// run_for as HalfCycles
if(!tape_player_is_sleeping_) tape_player_.run_for(cycle.length.as_int());
if(!tape_player_is_sleeping_) tape_player_.run_for(cycle.length.as_integral());
// Pump the AY
ay_.run_for(cycle.length);
// Clock the FDC, if connected, using a lazy scale by two
time_since_fdc_update_ += cycle.length;
if constexpr (has_fdc) {
// Clock the FDC, if connected, using a lazy scale by two
time_since_fdc_update_ += cycle.length;
}
// Update typing activity
if(typer_) typer_->run_for(cycle.length);
@@ -894,9 +909,11 @@ template <bool has_fdc> class ConcreteMachine:
}
// Check for an upper ROM selection
if(has_fdc && !(address&0x2000)) {
upper_rom_ = (*cycle.value == 7) ? ROMType::AMSDOS : ROMType::BASIC;
if(upper_rom_is_paged_) read_pointers_[3] = roms_[upper_rom_].data();
if constexpr (has_fdc) {
if(!(address&0x2000)) {
upper_rom_ = (*cycle.value == 7) ? ROMType::AMSDOS : ROMType::BASIC;
if(upper_rom_is_paged_) read_pointers_[3] = roms_[upper_rom_].data();
}
}
// Check for a CRTC access
@@ -910,19 +927,21 @@ template <bool has_fdc> class ConcreteMachine:
// Check for an 8255 PIO access
if(!(address & 0x800)) {
i8255_.set_register((address >> 8) & 3, *cycle.value);
i8255_.write((address >> 8) & 3, *cycle.value);
}
// Check for an FDC access
if(has_fdc && (address & 0x580) == 0x100) {
flush_fdc();
fdc_.set_register(address & 1, *cycle.value);
}
if constexpr (has_fdc) {
// Check for an FDC access
if((address & 0x580) == 0x100) {
flush_fdc();
fdc_.write(address & 1, *cycle.value);
}
// Check for a disk motor access
if(has_fdc && !(address & 0x580)) {
flush_fdc();
fdc_.set_motor_on(!!(*cycle.value));
// Check for a disk motor access
if(!(address & 0x580)) {
flush_fdc();
fdc_.set_motor_on(!!(*cycle.value));
}
}
break;
case CPU::Z80::PartialMachineCycle::Input:
@@ -931,13 +950,15 @@ template <bool has_fdc> class ConcreteMachine:
// Check for a PIO access
if(!(address & 0x800)) {
*cycle.value &= i8255_.get_register((address >> 8) & 3);
*cycle.value &= i8255_.read((address >> 8) & 3);
}
// Check for an FDC access
if(has_fdc && (address & 0x580) == 0x100) {
flush_fdc();
*cycle.value &= fdc_.get_register(address & 1);
if constexpr (has_fdc) {
if((address & 0x580) == 0x100) {
flush_fdc();
*cycle.value &= fdc_.read(address & 1);
}
}
// Check for a CRTC access; the below is not a typo, the CRTC can be selected
@@ -984,26 +1005,31 @@ template <bool has_fdc> class ConcreteMachine:
}
/// A CRTMachine function; sets the destination for video.
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override final {
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
crtc_bus_handler_.set_scan_target(scan_target);
}
/// A CRTMachine function; returns the current scan status.
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
return crtc_bus_handler_.get_scaled_scan_status();
}
/// A CRTMachine function; sets the output display type.
void set_display_type(Outputs::Display::DisplayType display_type) override final {
void set_display_type(Outputs::Display::DisplayType display_type) final {
crtc_bus_handler_.set_display_type(display_type);
}
/// @returns the speaker in use.
Outputs::Speaker::Speaker *get_speaker() override final {
Outputs::Speaker::Speaker *get_speaker() final {
return ay_.get_speaker();
}
/// Wires virtual-dispatched CRTMachine run_for requests to the static Z80 method.
void run_for(const Cycles cycles) override final {
void run_for(const Cycles cycles) final {
z80_.run_for(cycles);
}
bool insert_media(const Analyser::Static::Media &media) override final {
bool insert_media(const Analyser::Static::Media &media) final {
// If there are any tapes supplied, use the first of them.
if(!media.tapes.empty()) {
tape_player_.set_tape(media.tapes.front());
@@ -1020,70 +1046,70 @@ template <bool has_fdc> class ConcreteMachine:
return !media.tapes.empty() || (!media.disks.empty() && has_fdc);
}
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override final {
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) final {
fdc_is_sleeping_ = fdc_.preferred_clocking() == ClockingHint::Preference::None;
tape_player_is_sleeping_ = tape_player_.preferred_clocking() == ClockingHint::Preference::None;
}
// MARK: - Keyboard
void type_string(const std::string &string) override final {
void type_string(const std::string &string) final {
std::unique_ptr<CharacterMapper> mapper(new CharacterMapper());
Utility::TypeRecipient::add_typer(string, std::move(mapper));
}
HalfCycles get_typer_delay() override final {
HalfCycles get_typer_delay() final {
return Cycles(4000000); // Wait 1 second before typing.
}
HalfCycles get_typer_frequency() override final {
HalfCycles get_typer_frequency() final {
return Cycles(160000); // Type one character per frame.
}
// See header; sets a key as either pressed or released.
void set_key_state(uint16_t key, bool isPressed) override final {
void set_key_state(uint16_t key, bool isPressed) final {
key_state_.set_is_pressed(isPressed, key >> 4, key & 7);
}
// See header; sets all keys to released.
void clear_all_keys() override final {
void clear_all_keys() final {
key_state_.clear_all_keys();
}
KeyboardMapper *get_keyboard_mapper() override {
KeyboardMapper *get_keyboard_mapper() final {
return &keyboard_mapper_;
}
// MARK: - Activity Source
void set_activity_observer(Activity::Observer *observer) override {
if(has_fdc) fdc_.set_activity_observer(observer);
void set_activity_observer(Activity::Observer *observer) final {
if constexpr (has_fdc) fdc_.set_activity_observer(observer);
}
// MARK: - Configuration options.
std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
std::vector<std::unique_ptr<Configurable::Option>> get_options() final {
return AmstradCPC::get_options();
}
void set_selections(const Configurable::SelectionSet &selections_by_option) override {
void set_selections(const Configurable::SelectionSet &selections_by_option) final {
Configurable::Display display;
if(Configurable::get_display(selections_by_option, display)) {
set_video_signal_configurable(display);
}
}
Configurable::SelectionSet get_accurate_selections() override {
Configurable::SelectionSet get_accurate_selections() final {
Configurable::SelectionSet selection_set;
Configurable::append_display_selection(selection_set, Configurable::Display::RGB);
return selection_set;
}
Configurable::SelectionSet get_user_friendly_selections() override {
Configurable::SelectionSet get_user_friendly_selections() final {
Configurable::SelectionSet selection_set;
Configurable::append_display_selection(selection_set, Configurable::Display::RGB);
return selection_set;
}
// MARK: - Joysticks
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final {
return key_state_.get_joysticks();
}
@@ -1145,11 +1171,13 @@ template <bool has_fdc> class ConcreteMachine:
FDC fdc_;
HalfCycles time_since_fdc_update_;
void flush_fdc() {
// Clock the FDC, if connected, using a lazy scale by two
if(has_fdc && !fdc_is_sleeping_) {
fdc_.run_for(Cycles(time_since_fdc_update_.as_int()));
if constexpr (has_fdc) {
// Clock the FDC, if connected, using a lazy scale by two
if(!fdc_is_sleeping_) {
fdc_.run_for(Cycles(time_since_fdc_update_.as_integral()));
}
time_since_fdc_update_ = HalfCycles(0);
}
time_since_fdc_update_ = HalfCycles(0);
}
InterruptTimer interrupt_timer_;

View File

@@ -31,12 +31,12 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
BIND(F11, KeyRightSquareBracket);
BIND(F12, KeyClear);
BIND(Hyphen, KeyMinus); BIND(Equals, KeyCaret); BIND(BackSpace, KeyDelete);
BIND(Hyphen, KeyMinus); BIND(Equals, KeyCaret); BIND(Backspace, KeyDelete);
BIND(Tab, KeyTab);
BIND(OpenSquareBracket, KeyAt);
BIND(CloseSquareBracket, KeyLeftSquareBracket);
BIND(BackSlash, KeyBackSlash);
BIND(Backslash, KeyBackSlash);
BIND(CapsLock, KeyCapsLock);
BIND(Semicolon, KeyColon);
@@ -57,19 +57,19 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
BIND(Left, KeyLeft); BIND(Right, KeyRight);
BIND(Up, KeyUp); BIND(Down, KeyDown);
BIND(KeyPad0, KeyF0);
BIND(KeyPad1, KeyF1); BIND(KeyPad2, KeyF2); BIND(KeyPad3, KeyF3);
BIND(KeyPad4, KeyF4); BIND(KeyPad5, KeyF5); BIND(KeyPad6, KeyF6);
BIND(KeyPad7, KeyF7); BIND(KeyPad8, KeyF8); BIND(KeyPad9, KeyF9);
BIND(KeyPadPlus, KeySemicolon);
BIND(KeyPadMinus, KeyMinus);
BIND(Keypad0, KeyF0);
BIND(Keypad1, KeyF1); BIND(Keypad2, KeyF2); BIND(Keypad3, KeyF3);
BIND(Keypad4, KeyF4); BIND(Keypad5, KeyF5); BIND(Keypad6, KeyF6);
BIND(Keypad7, KeyF7); BIND(Keypad8, KeyF8); BIND(Keypad9, KeyF9);
BIND(KeypadPlus, KeySemicolon);
BIND(KeypadMinus, KeyMinus);
BIND(KeyPadEnter, KeyEnter);
BIND(KeyPadDecimalPoint, KeyFullStop);
BIND(KeyPadEquals, KeyMinus);
BIND(KeyPadSlash, KeyForwardSlash);
BIND(KeyPadAsterisk, KeyColon);
BIND(KeyPadDelete, KeyDelete);
BIND(KeypadEnter, KeyEnter);
BIND(KeypadDecimalPoint, KeyFullStop);
BIND(KeypadEquals, KeyMinus);
BIND(KeypadSlash, KeyForwardSlash);
BIND(KeypadAsterisk, KeyColon);
BIND(KeypadDelete, KeyDelete);
}
#undef BIND
}

View File

@@ -79,13 +79,15 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
void update_video() {
video_.run_for(cycles_since_video_update_.flush<Cycles>());
}
static const int audio_divider = 8;
static constexpr int audio_divider = 8;
void update_audio() {
speaker_.run_for(audio_queue_, cycles_since_audio_update_.divide(Cycles(audio_divider)));
}
void update_just_in_time_cards() {
for(const auto &card : just_in_time_cards_) {
card->run_for(cycles_since_card_update_, stretched_cycles_since_card_update_);
if(cycles_since_card_update_ > Cycles(0)) {
for(const auto &card : just_in_time_cards_) {
card->run_for(cycles_since_card_update_, stretched_cycles_since_card_update_);
}
}
cycles_since_card_update_ = 0;
stretched_cycles_since_card_update_ = 0;
@@ -124,22 +126,28 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
pick_card_messaging_group(card);
}
bool is_every_cycle_card(Apple::II::Card *card) {
bool is_every_cycle_card(const Apple::II::Card *card) {
return !card->get_select_constraints();
}
bool card_lists_are_dirty_ = true;
bool card_became_just_in_time_ = false;
void pick_card_messaging_group(Apple::II::Card *card) {
// Simplify to a card being either just-in-time or realtime.
// Don't worry about exactly what it's watching,
const bool is_every_cycle = is_every_cycle_card(card);
std::vector<Apple::II::Card *> &intended = is_every_cycle ? every_cycle_cards_ : just_in_time_cards_;
std::vector<Apple::II::Card *> &undesired = is_every_cycle ? just_in_time_cards_ : every_cycle_cards_;
// If the card is already in the proper group, stop.
if(std::find(intended.begin(), intended.end(), card) != intended.end()) return;
auto old_membership = std::find(undesired.begin(), undesired.end(), card);
if(old_membership != undesired.end()) undesired.erase(old_membership);
intended.push_back(card);
// Otherwise, mark the sets as dirty. It isn't safe to transition the card here,
// as the main loop may be part way through iterating the two lists.
card_lists_are_dirty_ = true;
card_became_just_in_time_ |= !is_every_cycle;
}
void card_did_change_select_constraints(Apple::II::Card *card) override {
void card_did_change_select_constraints(Apple::II::Card *card) final {
pick_card_messaging_group(card);
}
@@ -275,12 +283,12 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
Input(Input::Fire, 2),
}) {}
void did_set_input(const Input &input, float value) override {
void did_set_input(const Input &input, float value) final {
if(!input.info.control.index && (input.type == Input::Type::Horizontal || input.type == Input::Type::Vertical))
axes[(input.type == Input::Type::Horizontal) ? 0 : 1] = 1.0f - value;
}
void did_set_input(const Input &input, bool value) override {
void did_set_input(const Input &input, bool value) final {
if(input.type == Input::Type::Fire && input.info.control.index < 3) {
buttons[input.info.control.index] = value;
}
@@ -321,7 +329,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
audio_toggle_(audio_queue_),
speaker_(audio_toggle_) {
// The system's master clock rate.
const float master_clock = 14318180.0;
constexpr float master_clock = 14318180.0;
// This is where things get slightly convoluted: establish the machine as having a clock rate
// equal to the number of cycles of work the 6502 will actually achieve. Which is less than
@@ -409,16 +417,20 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
audio_queue_.flush();
}
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override {
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
video_.set_scan_target(scan_target);
}
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
return video_.get_scaled_scan_status();
}
/// Sets the type of display.
void set_display_type(Outputs::Display::DisplayType display_type) override {
void set_display_type(Outputs::Display::DisplayType display_type) final {
video_.set_display_type(display_type);
}
Outputs::Speaker::Speaker *get_speaker() override {
Outputs::Speaker::Speaker *get_speaker() final {
return &speaker_;
}
@@ -753,6 +765,31 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
}
}
// Update the card lists if any mutations are due.
if(card_lists_are_dirty_) {
card_lists_are_dirty_ = false;
// There's only one counter of time since update
// for just-in-time cards. If something new is
// transitioning, that needs to be zeroed.
if(card_became_just_in_time_) {
card_became_just_in_time_ = false;
update_just_in_time_cards();
}
// Clear the two lists and repopulate.
every_cycle_cards_.clear();
just_in_time_cards_.clear();
for(const auto &card: cards_) {
if(!card) continue;
if(is_every_cycle_card(card.get())) {
every_cycle_cards_.push_back(card.get());
} else {
just_in_time_cards_.push_back(card.get());
}
}
}
// Update analogue charge level.
analogue_charge_ = std::min(analogue_charge_ + 1.0f / 2820.0f, 1.1f);
@@ -766,15 +803,15 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
audio_queue_.perform();
}
void run_for(const Cycles cycles) override {
void run_for(const Cycles cycles) final {
m6502_.run_for(cycles);
}
void reset_all_keys() override {
void reset_all_keys() final {
open_apple_is_pressed_ = closed_apple_is_pressed_ = key_is_down_ = false;
}
void set_key_pressed(Key key, char value, bool is_pressed) override {
void set_key_pressed(Key key, char value, bool is_pressed) final {
switch(key) {
default: break;
case Key::F12:
@@ -795,7 +832,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
case Key::Right: value = 0x15; break;
case Key::Down: value = 0x0a; break;
case Key::Up: value = 0x0b; break;
case Key::BackSpace: value = 0x7f; break;
case Key::Backspace: value = 0x7f; break;
default: return;
}
}
@@ -813,38 +850,38 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
}
}
Inputs::Keyboard &get_keyboard() override {
Inputs::Keyboard &get_keyboard() final {
return *this;
}
void type_string(const std::string &string) override {
string_serialiser_.reset(new Utility::StringSerialiser(string, true));
void type_string(const std::string &string) final {
string_serialiser_ = std::make_unique<Utility::StringSerialiser>(string, true);
}
// MARK:: Configuration options.
std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
std::vector<std::unique_ptr<Configurable::Option>> get_options() final {
return Apple::II::get_options();
}
void set_selections(const Configurable::SelectionSet &selections_by_option) override {
void set_selections(const Configurable::SelectionSet &selections_by_option) final {
Configurable::Display display;
if(Configurable::get_display(selections_by_option, display)) {
set_video_signal_configurable(display);
}
}
Configurable::SelectionSet get_accurate_selections() override {
Configurable::SelectionSet get_accurate_selections() final {
Configurable::SelectionSet selection_set;
Configurable::append_display_selection(selection_set, Configurable::Display::CompositeColour);
return selection_set;
}
Configurable::SelectionSet get_user_friendly_selections() override {
Configurable::SelectionSet get_user_friendly_selections() final {
return get_accurate_selections();
}
// MARK: MediaTarget
bool insert_media(const Analyser::Static::Media &media) override {
bool insert_media(const Analyser::Static::Media &media) final {
if(!media.disks.empty()) {
auto diskii = diskii_card();
if(diskii) diskii->set_disk(media.disks[0], 0);
@@ -853,14 +890,14 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
}
// MARK: Activity::Source
void set_activity_observer(Activity::Observer *observer) override {
void set_activity_observer(Activity::Observer *observer) final {
for(const auto &card: cards_) {
if(card) card->set_activity_observer(observer);
}
}
// MARK: JoystickMachine
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final {
return joysticks_;
}
};

View File

@@ -83,10 +83,8 @@ class Card {
will receive a perform_bus_operation every cycle. To reduce the number of
virtual method calls, they **will not** receive run_for. run_for will propagate
only to cards that register for IO and/or Device accesses only.
*/
int get_select_constraints() {
int get_select_constraints() const {
return select_constraints_;
}

View File

@@ -52,7 +52,7 @@ void DiskIICard::perform_bus_operation(Select select, bool is_read, uint16_t add
void DiskIICard::run_for(Cycles cycles, int stretches) {
if(diskii_clocking_preference_ == ClockingHint::Preference::None) return;
diskii_.run_for(Cycles(cycles.as_int() * 2));
diskii_.run_for(Cycles(cycles.as_integral() * 2));
}
void DiskIICard::set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive) {
@@ -65,7 +65,7 @@ void DiskIICard::set_activity_observer(Activity::Observer *observer) {
void DiskIICard::set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) {
diskii_clocking_preference_ = preference;
set_select_constraints((preference != ClockingHint::Preference::RealTime) ? (IO | Device) : 0);
set_select_constraints((preference != ClockingHint::Preference::RealTime) ? (IO | Device) : None);
}
Storage::Disk::Drive &DiskIICard::get_drive(int drive) {

View File

@@ -27,16 +27,16 @@ class DiskIICard: public Card, public ClockingHint::Observer {
public:
DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector);
void perform_bus_operation(Select select, bool is_read, uint16_t address, uint8_t *value) override;
void run_for(Cycles cycles, int stretches) override;
void perform_bus_operation(Select select, bool is_read, uint16_t address, uint8_t *value) final;
void run_for(Cycles cycles, int stretches) final;
void set_activity_observer(Activity::Observer *observer) override;
void set_activity_observer(Activity::Observer *observer) final;
void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive);
Storage::Disk::Drive &get_drive(int drive);
private:
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override;
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) final;
std::vector<uint8_t> boot_;
Apple::DiskII diskii_;
ClockingHint::Preference diskii_clocking_preference_ = ClockingHint::Preference::RealTime;

View File

@@ -47,6 +47,10 @@ void VideoBase::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
crt_.set_scan_target(scan_target);
}
Outputs::Display::ScanStatus VideoBase::get_scaled_scan_status() const {
return crt_.get_scaled_scan_status() / 14.0f;
}
void VideoBase::set_display_type(Outputs::Display::DisplayType display_type) {
crt_.set_display_type(display_type);
}

View File

@@ -40,6 +40,9 @@ class VideoBase {
/// Sets the scan target.
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
/// Gets the current scan status.
Outputs::Display::ScanStatus get_scaled_scan_status() const;
/// Sets the type of output.
void set_display_type(Outputs::Display::DisplayType);
@@ -203,7 +206,7 @@ class VideoBase {
std::array<uint8_t, 40> auxiliary_stream_;
bool is_iie_ = false;
static const int flash_length = 8406;
static constexpr int flash_length = 8406;
// Describes the current text mode mapping from in-memory character index
// to output character.
@@ -252,7 +255,7 @@ class VideoBase {
void output_fat_low_resolution(uint8_t *target, const uint8_t *source, size_t length, int column, int row) const;
// Maintain a DeferredQueue for delayed mode switches.
DeferredQueue<Cycles> deferrer_;
DeferredQueuePerformer<Cycles> deferrer_;
};
template <class BusHandler, bool is_iie> class Video: public VideoBase {
@@ -284,7 +287,7 @@ template <class BusHandler, bool is_iie> class Video: public VideoBase {
// Source: Have an Apple Split by Bob Bishop; http://rich12345.tripod.com/aiivideo/softalk.html
// Determine column at offset.
int mapped_column = column_ + offset.as_int();
int mapped_column = column_ + int(offset.as_integral());
// Map that backwards from the internal pixels-at-start generation to pixels-at-end
// (so what was column 0 is now column 25).
@@ -315,7 +318,7 @@ template <class BusHandler, bool is_iie> class Video: public VideoBase {
bool get_is_vertical_blank(Cycles offset) {
// Map that backwards from the internal pixels-at-start generation to pixels-at-end
// (so what was column 0 is now column 25).
int mapped_column = column_ + offset.as_int();
int mapped_column = column_ + int(offset.as_integral());
// Map that backwards from the internal pixels-at-start generation to pixels-at-end
// (so what was column 0 is now column 25).
@@ -339,11 +342,11 @@ template <class BusHandler, bool is_iie> class Video: public VideoBase {
A frame is oriented around 65 cycles across, 262 lines down.
*/
static const int first_sync_line = 220; // A complete guess. Information needed.
static const int first_sync_column = 49; // Also a guess.
static const int sync_length = 4; // One of the two likely candidates.
constexpr int first_sync_line = 220; // A complete guess. Information needed.
constexpr int first_sync_column = 49; // Also a guess.
constexpr int sync_length = 4; // One of the two likely candidates.
int int_cycles = cycles.as_int();
int int_cycles = int(cycles.as_integral());
while(int_cycles) {
const int cycles_this_line = std::min(65 - column_, int_cycles);
const int ending_column = column_ + cycles_this_line;

View File

@@ -11,7 +11,7 @@
using namespace Apple::Macintosh;
void DriveSpeedAccumulator::post_sample(uint8_t sample) {
if(!number_of_drives_) return;
if(!delegate_) return;
// An Euler-esque approximation is used here: just collect all
// the samples until there is a certain small quantity of them,
@@ -50,14 +50,7 @@ void DriveSpeedAccumulator::post_sample(uint8_t sample) {
const float normalised_sum = float(sum) / float(samples_.size());
const float rotation_speed = (normalised_sum * 27.08f) - 259.0f;
for(int c = 0; c < number_of_drives_; ++c) {
drives_[c]->set_rotation_speed(rotation_speed);
}
// printf("RPM: %0.2f (%d sum)\n", rotation_speed, sum);
delegate_->drive_speed_accumulator_set_drive_speed(this, rotation_speed);
}
}
void DriveSpeedAccumulator::add_drive(Apple::Macintosh::DoubleDensityDrive *drive) {
drives_[number_of_drives_] = drive;
++number_of_drives_;
}

View File

@@ -13,8 +13,6 @@
#include <cstddef>
#include <cstdint>
#include "../../../Components/DiskII/MacintoshDoubleDensityDrive.hpp"
namespace Apple {
namespace Macintosh {
@@ -25,18 +23,20 @@ class DriveSpeedAccumulator {
*/
void post_sample(uint8_t sample);
struct Delegate {
virtual void drive_speed_accumulator_set_drive_speed(DriveSpeedAccumulator *, float speed) = 0;
};
/*!
Adds a connected drive. Up to two of these
can be supplied. Only Macintosh DoubleDensityDrives
are supported.
Sets the delegate to receive drive speed changes.
*/
void add_drive(Apple::Macintosh::DoubleDensityDrive *drive);
void set_delegate(Delegate *delegate) {
delegate_ = delegate;;
}
private:
std::array<uint8_t, 20> samples_;
std::size_t sample_pointer_ = 0;
Apple::Macintosh::DoubleDensityDrive *drives_[2] = {nullptr, nullptr};
int number_of_drives_ = 0;
Delegate *delegate_ = nullptr;
};
}

View File

@@ -0,0 +1,88 @@
//
// Keyboard.cpp
// Clock Signal
//
// Created by Thomas Harte on 02/08/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#include "Keyboard.hpp"
using namespace Apple::Macintosh;
uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
using Key = Inputs::Keyboard::Key;
using MacKey = Apple::Macintosh::Key;
switch(key) {
default: return KeyboardMachine::MappedMachine::KeyNotMapped;
#define Bind(x, y) case Key::x: return uint16_t(y)
Bind(BackTick, MacKey::BackTick);
Bind(k1, MacKey::k1); Bind(k2, MacKey::k2); Bind(k3, MacKey::k3);
Bind(k4, MacKey::k4); Bind(k5, MacKey::k5); Bind(k6, MacKey::k6);
Bind(k7, MacKey::k7); Bind(k8, MacKey::k8); Bind(k9, MacKey::k9);
Bind(k0, MacKey::k0);
Bind(Hyphen, MacKey::Hyphen);
Bind(Equals, MacKey::Equals);
Bind(Backspace, MacKey::Backspace);
Bind(Tab, MacKey::Tab);
Bind(Q, MacKey::Q); Bind(W, MacKey::W); Bind(E, MacKey::E); Bind(R, MacKey::R);
Bind(T, MacKey::T); Bind(Y, MacKey::Y); Bind(U, MacKey::U); Bind(I, MacKey::I);
Bind(O, MacKey::O); Bind(P, MacKey::P);
Bind(OpenSquareBracket, MacKey::OpenSquareBracket);
Bind(CloseSquareBracket, MacKey::CloseSquareBracket);
Bind(CapsLock, MacKey::CapsLock);
Bind(A, MacKey::A); Bind(S, MacKey::S); Bind(D, MacKey::D); Bind(F, MacKey::F);
Bind(G, MacKey::G); Bind(H, MacKey::H); Bind(J, MacKey::J); Bind(K, MacKey::K);
Bind(L, MacKey::L);
Bind(Semicolon, MacKey::Semicolon);
Bind(Quote, MacKey::Quote);
Bind(Enter, MacKey::Return);
Bind(LeftShift, MacKey::Shift);
Bind(Z, MacKey::Z); Bind(X, MacKey::X); Bind(C, MacKey::C); Bind(V, MacKey::V);
Bind(B, MacKey::B); Bind(N, MacKey::N); Bind(M, MacKey::M);
Bind(Comma, MacKey::Comma);
Bind(FullStop, MacKey::FullStop);
Bind(ForwardSlash, MacKey::ForwardSlash);
Bind(RightShift, MacKey::Shift);
Bind(Left, MacKey::Left);
Bind(Right, MacKey::Right);
Bind(Up, MacKey::Up);
Bind(Down, MacKey::Down);
Bind(LeftOption, MacKey::Option);
Bind(RightOption, MacKey::Option);
Bind(LeftMeta, MacKey::Command);
Bind(RightMeta, MacKey::Command);
Bind(Space, MacKey::Space);
Bind(Backslash, MacKey::Backslash);
Bind(KeypadDelete, MacKey::KeypadDelete);
Bind(KeypadEquals, MacKey::KeypadEquals);
Bind(KeypadSlash, MacKey::KeypadSlash);
Bind(KeypadAsterisk, MacKey::KeypadAsterisk);
Bind(KeypadMinus, MacKey::KeypadMinus);
Bind(KeypadPlus, MacKey::KeypadPlus);
Bind(KeypadEnter, MacKey::KeypadEnter);
Bind(KeypadDecimalPoint, MacKey::KeypadDecimalPoint);
Bind(Keypad9, MacKey::Keypad9);
Bind(Keypad8, MacKey::Keypad8);
Bind(Keypad7, MacKey::Keypad7);
Bind(Keypad6, MacKey::Keypad6);
Bind(Keypad5, MacKey::Keypad5);
Bind(Keypad4, MacKey::Keypad4);
Bind(Keypad3, MacKey::Keypad3);
Bind(Keypad2, MacKey::Keypad2);
Bind(Keypad1, MacKey::Keypad1);
Bind(Keypad0, MacKey::Keypad0);
#undef Bind
}
}

View File

@@ -10,6 +10,7 @@
#define Apple_Macintosh_Keyboard_hpp
#include "../../KeyboardMachine.hpp"
#include "../../../ClockReceiver/ClockReceiver.hpp"
#include <mutex>
#include <vector>
@@ -17,6 +18,72 @@
namespace Apple {
namespace Macintosh {
constexpr uint16_t KeypadMask = 0x100;
/*!
Defines the keycodes that could be passed directly to a Macintosh via set_key_pressed.
*/
enum class Key: uint16_t {
/*
See p284 of the Apple Guide to the Macintosh Family Hardware
for documentation of the mapping below.
*/
BackTick = 0x65,
k1 = 0x25, k2 = 0x27, k3 = 0x29, k4 = 0x2b, k5 = 0x2f,
k6 = 0x2d, k7 = 0x35, k8 = 0x39, k9 = 0x33, k0 = 0x3b,
Hyphen = 0x37,
Equals = 0x31,
Backspace = 0x67,
Tab = 0x61,
Q = 0x19, W = 0x1b, E = 0x1d, R = 0x1f, T = 0x23, Y = 0x21, U = 0x41, I = 0x45, O = 0x3f, P = 0x47,
A = 0x01, S = 0x03, D = 0x05, F = 0x07, G = 0x0b, H = 0x09, J = 0x4d, K = 0x51, L = 0x4b,
Z = 0x0d, X = 0x0f, C = 0x11, V = 0x13, B = 0x17, N = 0x5b, M = 0x5d,
OpenSquareBracket = 0x43,
CloseSquareBracket = 0x3d,
Semicolon = 0x53,
Quote = 0x4f,
Comma = 0x57,
FullStop = 0x5f,
ForwardSlash = 0x59,
CapsLock = 0x73,
Shift = 0x71,
Option = 0x75,
Command = 0x6f,
Space = 0x63,
Backslash = 0x55,
Return = 0x49,
Left = KeypadMask | 0x0d,
Right = KeypadMask | 0x05,
Up = KeypadMask | 0x1b,
Down = KeypadMask | 0x11,
KeypadDelete = KeypadMask | 0x0f,
KeypadEquals = KeypadMask | 0x11,
KeypadSlash = KeypadMask | 0x1b,
KeypadAsterisk = KeypadMask | 0x05,
KeypadMinus = KeypadMask | 0x1d,
KeypadPlus = KeypadMask | 0x0d,
KeypadEnter = KeypadMask | 0x19,
KeypadDecimalPoint = KeypadMask | 0x03,
Keypad9 = KeypadMask | 0x39,
Keypad8 = KeypadMask | 0x37,
Keypad7 = KeypadMask | 0x33,
Keypad6 = KeypadMask | 0x31,
Keypad5 = KeypadMask | 0x2f,
Keypad4 = KeypadMask | 0x2d,
Keypad3 = KeypadMask | 0x2b,
Keypad2 = KeypadMask | 0x29,
Keypad1 = KeypadMask | 0x27,
Keypad0 = KeypadMask | 0x25
};
class Keyboard {
public:
void set_input(bool data) {
@@ -147,14 +214,16 @@ class Keyboard {
// Keys on the keypad are preceded by a $79 keycode; in the internal naming scheme
// they are indicated by having bit 8 set. So add the $79 prefix if required.
if(key & 0x100) {
if(key & KeypadMask) {
key_queue_.insert(key_queue_.begin(), 0x79);
}
key_queue_.insert(key_queue_.begin(), (is_pressed ? 0x00 : 0x80) | uint8_t(key));
}
private:
/// Performs the pre-ADB Apple keyboard protocol command @c command, returning
/// the proper result if the command were to terminate now. So, it treats inquiry
/// and instant as the same command.
int perform_command(int command) {
switch(command) {
case 0x10: // Inquiry.
@@ -180,22 +249,41 @@ class Keyboard {
return 0x7b; // No key transition.
}
/// Maintains the current operating mode — a record of what the
/// keyboard is doing now.
enum class Mode {
/// The keyboard is waiting to begin a transaction.
Waiting,
/// The keyboard is currently clocking in a new command.
AcceptingCommand,
/// The keyboard is waiting for the computer to indicate that it is ready for a response.
AwaitingEndOfCommand,
/// The keyboard is in the process of performing the command it most-recently received.
/// If the command was an 'inquiry', this state may persist for a non-neglibible period of time.
PerformingCommand,
/// The keyboard is currently shifting a response back to the computer.
SendingResponse,
PerformingCommand
} mode_ = Mode::Waiting;
/// Holds a count of progress through the current @c Mode. Exact meaning depends on mode.
int phase_ = 0;
/// Holds the most-recently-received command; the command is shifted into here as it is received
/// so this may not be valid prior to Mode::PerformingCommand.
int command_ = 0;
/// Populated during PerformingCommand as the response to the most-recently-received command, this
/// is then shifted out to teh host computer. So it is guaranteed valid at the beginning of Mode::SendingResponse,
/// but not afterwards.
int response_ = 0;
/// The current state of the serial connection's data input.
bool data_input_ = false;
/// The current clock output from this keyboard.
bool clock_output_ = false;
// TODO: improve this very, very simple implementation.
/// Guards multithread access to key_queue_.
std::mutex key_queue_mutex_;
/// A FIFO queue for key events, in the form they'd be communicated to the Macintosh,
/// with the newest events towards the front.
std::vector<uint8_t> key_queue_;
};
@@ -203,89 +291,7 @@ class Keyboard {
Provides a mapping from idiomatic PC keys to Macintosh keys.
*/
class KeyboardMapper: public KeyboardMachine::MappedMachine::KeyboardMapper {
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) override {
using Key = Inputs::Keyboard::Key;
switch(key) {
default: return KeyboardMachine::MappedMachine::KeyNotMapped;
/*
See p284 of the Apple Guide to the Macintosh Family Hardware
for documentation of the mapping below.
*/
case Key::BackTick: return 0x65;
case Key::k1: return 0x25;
case Key::k2: return 0x27;
case Key::k3: return 0x29;
case Key::k4: return 0x2b;
case Key::k5: return 0x2f;
case Key::k6: return 0x2d;
case Key::k7: return 0x35;
case Key::k8: return 0x39;
case Key::k9: return 0x33;
case Key::k0: return 0x3b;
case Key::Hyphen: return 0x37;
case Key::Equals: return 0x31;
case Key::BackSpace: return 0x67;
case Key::Tab: return 0x61;
case Key::Q: return 0x19;
case Key::W: return 0x1b;
case Key::E: return 0x1d;
case Key::R: return 0x1f;
case Key::T: return 0x23;
case Key::Y: return 0x21;
case Key::U: return 0x41;
case Key::I: return 0x45;
case Key::O: return 0x3f;
case Key::P: return 0x47;
case Key::OpenSquareBracket: return 0x43;
case Key::CloseSquareBracket: return 0x3d;
case Key::CapsLock: return 0x73;
case Key::A: return 0x01;
case Key::S: return 0x03;
case Key::D: return 0x05;
case Key::F: return 0x07;
case Key::G: return 0x0b;
case Key::H: return 0x09;
case Key::J: return 0x4d;
case Key::K: return 0x51;
case Key::L: return 0x4b;
case Key::Semicolon: return 0x53;
case Key::Quote: return 0x4f;
case Key::Enter: return 0x49;
case Key::LeftShift: return 0x71;
case Key::Z: return 0x0d;
case Key::X: return 0x0f;
case Key::C: return 0x11;
case Key::V: return 0x13;
case Key::B: return 0x17;
case Key::N: return 0x5b;
case Key::M: return 0x5d;
case Key::Comma: return 0x57;
case Key::FullStop: return 0x5f;
case Key::ForwardSlash: return 0x59;
case Key::RightShift: return 0x71;
case Key::Left: return 0x100 | 0x0d;
case Key::Right: return 0x100 | 0x05;
case Key::Up: return 0x100 | 0x1b;
case Key::Down: return 0x100 | 0x11;
case Key::LeftOption:
case Key::RightOption: return 0x75;
case Key::LeftMeta:
case Key::RightMeta: return 0x6f;
case Key::Space: return 0x63;
case Key::BackSlash: return 0x55;
/* TODO: the numeric keypad. */
}
}
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) final;
};
}

View File

@@ -16,6 +16,7 @@
#include "RealTimeClock.hpp"
#include "Video.hpp"
#include "../../../Activity/Source.hpp"
#include "../../CRTMachine.hpp"
#include "../../KeyboardMachine.hpp"
#include "../../MediaTarget.hpp"
@@ -25,15 +26,22 @@
#include "../../../Outputs/Log.hpp"
#include "../../../ClockReceiver/JustInTime.hpp"
#include "../../../ClockReceiver/ClockingHintSource.hpp"
#include "../../../Configurable/StandardOptions.hpp"
//#define LOG_TRACE
#include "../../../Components/5380/ncr5380.hpp"
#include "../../../Components/6522/6522.hpp"
#include "../../../Components/8530/z8530.hpp"
#include "../../../Components/DiskII/IWM.hpp"
#include "../../../Components/DiskII/MacintoshDoubleDensityDrive.hpp"
#include "../../../Processors/68000/68000.hpp"
#include "../../../Storage/MassStorage/SCSI/SCSI.hpp"
#include "../../../Storage/MassStorage/SCSI/DirectAccessDevice.hpp"
#include "../../../Storage/MassStorage/Encodings/MacintoshVolume.hpp"
#include "../../../Analyser/Static/Macintosh/Target.hpp"
#include "../../Utility/MemoryPacker.hpp"
@@ -41,13 +49,19 @@
namespace {
const int CLOCK_RATE = 7833600;
constexpr int CLOCK_RATE = 7833600;
}
namespace Apple {
namespace Macintosh {
std::vector<std::unique_ptr<Configurable::Option>> get_options() {
return Configurable::standard_options(
static_cast<Configurable::StandardOptions>(Configurable::QuickBoot)
);
}
template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachine:
public Machine,
public CRTMachine::Machine,
@@ -55,16 +69,28 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
public MouseMachine::Machine,
public CPU::MC68000::BusHandler,
public KeyboardMachine::MappedMachine,
public Zilog::SCC::z8530::Delegate {
public Zilog::SCC::z8530::Delegate,
public Activity::Source,
public Configurable::Device,
public DriveSpeedAccumulator::Delegate,
public ClockingHint::Observer {
public:
using Target = Analyser::Static::Macintosh::Target;
ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
KeyboardMachine::MappedMachine({
Inputs::Keyboard::Key::LeftShift, Inputs::Keyboard::Key::RightShift,
Inputs::Keyboard::Key::LeftOption, Inputs::Keyboard::Key::RightOption,
Inputs::Keyboard::Key::LeftMeta, Inputs::Keyboard::Key::RightMeta,
}),
mc68000_(*this),
iwm_(CLOCK_RATE),
video_(ram_, audio_, drive_speed_accumulator_),
video_(audio_, drive_speed_accumulator_),
via_(via_port_handler_),
via_port_handler_(*this, clock_, keyboard_, video_, audio_, iwm_, mouse_),
via_port_handler_(*this, clock_, keyboard_, audio_, iwm_, mouse_),
scsi_bus_(CLOCK_RATE * 2),
scsi_(scsi_bus_, CLOCK_RATE * 2),
hard_drive_(scsi_bus_, 6 /* SCSI ID */),
drives_{
{CLOCK_RATE, model >= Analyser::Static::Macintosh::Target::Model::Mac512ke},
{CLOCK_RATE, model >= Analyser::Static::Macintosh::Target::Model::Mac512ke}
@@ -91,7 +117,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
break;
case Model::Mac512ke:
case Model::MacPlus: {
ram_size = 512*1024;
ram_size = ((model == Model::MacPlus) ? 4096 : 512)*1024;
rom_size = 128*1024;
const std::initializer_list<uint32_t> crc32s = { 0x4fa5b399, 0x7cacd18f, 0xb2102e8e };
rom_descriptions.emplace_back(machine_name, "the Macintosh Plus ROM", "macplus.rom", 128*1024, crc32s);
@@ -99,7 +125,8 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
}
ram_mask_ = (ram_size >> 1) - 1;
rom_mask_ = (rom_size >> 1) - 1;
video_.set_ram_mask(ram_mask_);
ram_.resize(ram_size >> 1);
video_.set_ram(ram_.data(), ram_mask_);
// Grab a copy of the ROM and convert it into big-endian data.
const auto roms = rom_fetcher(rom_descriptions);
@@ -110,62 +137,462 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
Memory::PackBigEndian16(*roms[0], rom_);
// Randomise memory contents.
Memory::Fuzz(ram_, sizeof(ram_) / sizeof(*ram_));
Memory::Fuzz(ram_);
// Attach the drives to the IWM.
iwm_.iwm.set_drive(0, &drives_[0]);
iwm_.iwm.set_drive(1, &drives_[1]);
iwm_->set_drive(0, &drives_[0]);
iwm_->set_drive(1, &drives_[1]);
// If they are 400kb drives, also attach them to the drive-speed accumulator.
if(!drives_[0].is_800k()) drive_speed_accumulator_.add_drive(&drives_[0]);
if(!drives_[1].is_800k()) drive_speed_accumulator_.add_drive(&drives_[1]);
if(!drives_[0].is_800k() || !drives_[1].is_800k()) {
drive_speed_accumulator_.set_delegate(this);
}
// Make sure interrupt changes from the SCC are observed.
scc_.set_delegate(this);
// Also watch for changes in clocking requirement from the SCSI chip.
if constexpr (model == Analyser::Static::Macintosh::Target::Model::MacPlus) {
scsi_bus_.set_clocking_hint_observer(this);
}
// The Mac runs at 7.8336mHz.
set_clock_rate(double(CLOCK_RATE));
audio_.speaker.set_input_rate(float(CLOCK_RATE) / 2.0f);
// Insert any supplied media.
insert_media(target.media);
// Set the immutables of the memory map.
setup_memory_map();
}
~ConcreteMachine() {
audio_.queue.flush();
}
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override {
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
video_.set_scan_target(scan_target);
}
Outputs::Speaker::Speaker *get_speaker() override {
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
return video_.get_scaled_scan_status();
}
Outputs::Speaker::Speaker *get_speaker() final {
return &audio_.speaker;
}
void run_for(const Cycles cycles) override {
void run_for(const Cycles cycles) final {
mc68000_.run_for(cycles);
}
using Microcycle = CPU::MC68000::Microcycle;
HalfCycles perform_bus_operation(const Microcycle &cycle, int is_supervisor) {
// TODO: pick a delay if this is a video-clashing memory fetch.
HalfCycles delay(0);
forceinline HalfCycles perform_bus_operation(const Microcycle &cycle, int is_supervisor) {
// Advance time.
advance_time(cycle.length);
time_since_video_update_ += cycle.length;
iwm_.time_since_update += cycle.length;
// A null cycle leaves nothing else to do.
if(!(cycle.operation & (Microcycle::NewAddress | Microcycle::SameAddress))) return HalfCycles(0);
// Grab the value on the address bus, at word precision.
uint32_t word_address = cycle.active_operation_word_address();
// Everything above E0 0000 is signalled as being on the peripheral bus.
mc68000_.set_is_peripheral_address(word_address >= 0x700000);
// All code below deals only with reads and writes — cycles in which a
// data select is active. So quit now if this is not the active part of
// a read or write.
//
// The 68000 uses 6800-style autovectored interrupts, so the mere act of
// having set VPA above deals with those given that the generated address
// for interrupt acknowledge cycles always has all bits set except the
// lowest explicit address lines.
if(!cycle.data_select_active() || (cycle.operation & Microcycle::InterruptAcknowledge)) return HalfCycles(0);
// Grab the word-precision address being accessed.
uint16_t *memory_base = nullptr;
HalfCycles delay;
switch(memory_map_[word_address >> 16]) {
default: assert(false);
case BusDevice::Unassigned:
fill_unmapped(cycle);
return delay;
case BusDevice::VIA: {
if(*cycle.address & 1) {
fill_unmapped(cycle);
} else {
const int register_address = word_address >> 8;
// VIA accesses are via address 0xefe1fe + register*512,
// which at word precision is 0x77f0ff + register*256.
if(cycle.operation & Microcycle::Read) {
cycle.value->halves.low = via_.read(register_address);
} else {
via_.write(register_address, cycle.value->halves.low);
}
if(cycle.operation & Microcycle::SelectWord) cycle.value->halves.high = 0xff;
}
} return delay;
case BusDevice::PhaseRead: {
if(cycle.operation & Microcycle::Read) {
cycle.value->halves.low = phase_ & 7;
}
if(cycle.operation & Microcycle::SelectWord) cycle.value->halves.high = 0xff;
} return delay;
case BusDevice::IWM: {
if(*cycle.address & 1) {
const int register_address = word_address >> 8;
// The IWM; this is a purely polled device, so can be run on demand.
if(cycle.operation & Microcycle::Read) {
cycle.value->halves.low = iwm_->read(register_address);
} else {
iwm_->write(register_address, cycle.value->halves.low);
}
if(cycle.operation & Microcycle::SelectWord) cycle.value->halves.high = 0xff;
} else {
fill_unmapped(cycle);
}
} return delay;
case BusDevice::SCSI: {
const int register_address = word_address >> 3;
const bool dma_acknowledge = word_address & 0x100;
// Even accesses = read; odd = write.
if(*cycle.address & 1) {
// Odd access => this is a write. Data will be in the upper byte.
if(cycle.operation & Microcycle::Read) {
scsi_.write(register_address, 0xff, dma_acknowledge);
} else {
if(cycle.operation & Microcycle::SelectWord) {
scsi_.write(register_address, cycle.value->halves.high, dma_acknowledge);
} else {
scsi_.write(register_address, cycle.value->halves.low, dma_acknowledge);
}
}
} else {
// Even access => this is a read.
if(cycle.operation & Microcycle::Read) {
const auto result = scsi_.read(register_address, dma_acknowledge);
if(cycle.operation & Microcycle::SelectWord) {
// Data is loaded on the top part of the bus only.
cycle.value->full = uint16_t((result << 8) | 0xff);
} else {
cycle.value->halves.low = result;
}
}
}
} return delay;
case BusDevice::SCCReadResetPhase: {
// Any word access here adjusts phase.
if(cycle.operation & Microcycle::SelectWord) {
adjust_phase();
} else {
// A0 = 1 => reset; A0 = 0 => read.
if(*cycle.address & 1) {
scc_.reset();
if(cycle.operation & Microcycle::Read) {
cycle.value->halves.low = 0xff;
}
} else {
const auto read = scc_.read(int(word_address));
if(cycle.operation & Microcycle::Read) {
cycle.value->halves.low = read;
}
}
}
} return delay;
case BusDevice::SCCWrite: {
// Any word access here adjusts phase.
if(cycle.operation & Microcycle::SelectWord) {
adjust_phase();
} else {
if(*cycle.address & 1) {
if(cycle.operation & Microcycle::Read) {
scc_.write(int(word_address), 0xff);
cycle.value->halves.low = 0xff;
} else {
scc_.write(int(word_address), cycle.value->halves.low);
}
} else {
fill_unmapped(cycle);
}
}
} return delay;
case BusDevice::RAM: {
// This is coupled with the Macintosh implementation of video; the magic
// constant should probably be factored into the Video class.
// It embodies knowledge of the fact that video (and audio) will always
// be fetched from the final $d900 bytes (i.e. $6c80 words) of memory.
// (And that ram_mask_ = ram size - 1).
if(word_address > ram_mask_ - 0x6c80)
update_video();
memory_base = ram_.data();
word_address &= ram_mask_;
// Apply a delay due to video contention if applicable; scheme applied:
// only every other access slot is available during the period of video
// output. I believe this to be correct for the 128k, 512k and Plus.
// More research to do on other models.
if(video_is_outputting() && ram_subcycle_ < 8) {
delay = HalfCycles(8 - ram_subcycle_);
advance_time(delay);
}
} break;
case BusDevice::ROM: {
if(!(cycle.operation & Microcycle::Read)) return delay;
memory_base = rom_;
word_address &= rom_mask_;
} break;
}
// If control has fallen through to here, the access is either a read from ROM, or a read or write to RAM.
switch(cycle.operation & (Microcycle::SelectWord | Microcycle::SelectByte | Microcycle::Read)) {
default:
break;
case Microcycle::SelectWord | Microcycle::Read:
cycle.value->full = memory_base[word_address];
break;
case Microcycle::SelectByte | Microcycle::Read:
cycle.value->halves.low = uint8_t(memory_base[word_address] >> cycle.byte_shift());
break;
case Microcycle::SelectWord:
memory_base[word_address] = cycle.value->full;
break;
case Microcycle::SelectByte:
memory_base[word_address] = uint16_t(
(cycle.value->halves.low << cycle.byte_shift()) |
(memory_base[word_address] & cycle.untouched_byte_mask())
);
break;
}
return delay;
}
void flush() {
// Flush the video before the audio queue; in a Mac the
// video is responsible for providing part of the
// audio signal, so the two aren't as distinct as in
// most machines.
update_video();
// As above: flush audio after video.
via_.flush();
audio_.queue.perform();
// This avoids deferring IWM costs indefinitely, until
// they become artbitrarily large.
iwm_.flush();
}
void set_rom_is_overlay(bool rom_is_overlay) {
ROM_is_overlay_ = rom_is_overlay;
using Model = Analyser::Static::Macintosh::Target::Model;
switch(model) {
case Model::Mac128k:
case Model::Mac512k:
case Model::Mac512ke:
populate_memory_map(0, [rom_is_overlay] (std::function<void(int target, BusDevice device)> map_to) {
// Addresses up to $80 0000 aren't affected by this bit.
if(rom_is_overlay) {
// Up to $60 0000 mirrors of the ROM alternate with unassigned areas every $10 0000 byes.
for(int c = 0; c < 0x600000; c += 0x100000) {
map_to(c + 0x100000, (c & 0x100000) ? BusDevice::Unassigned : BusDevice::ROM);
}
map_to(0x800000, BusDevice::RAM);
} else {
map_to(0x400000, BusDevice::RAM);
map_to(0x500000, BusDevice::ROM);
map_to(0x800000, BusDevice::Unassigned);
}
});
break;
case Model::MacPlus:
populate_memory_map(0, [rom_is_overlay] (std::function<void(int target, BusDevice device)> map_to) {
// Addresses up to $80 0000 aren't affected by this bit.
if(rom_is_overlay) {
for(int c = 0; c < 0x580000; c += 0x20000) {
map_to(c + 0x20000, ((c & 0x100000) || (c & 0x20000)) ? BusDevice::Unassigned : BusDevice::ROM);
}
map_to(0x600000, BusDevice::SCSI);
map_to(0x800000, BusDevice::RAM);
} else {
map_to(0x400000, BusDevice::RAM);
for(int c = 0x400000; c < 0x580000; c += 0x20000) {
map_to(c + 0x20000, ((c & 0x100000) || (c & 0x20000)) ? BusDevice::Unassigned : BusDevice::ROM);
}
map_to(0x600000, BusDevice::SCSI);
map_to(0x800000, BusDevice::Unassigned);
}
});
break;
}
}
bool video_is_outputting() {
return video_.is_outputting(time_since_video_update_);
}
void set_use_alternate_buffers(bool use_alternate_screen_buffer, bool use_alternate_audio_buffer) {
update_video();
video_.set_use_alternate_buffers(use_alternate_screen_buffer, use_alternate_audio_buffer);
}
bool insert_media(const Analyser::Static::Media &media) final {
if(media.disks.empty() && media.mass_storage_devices.empty())
return false;
// TODO: shouldn't allow disks to be replaced like this, as the Mac
// uses software eject. Will need to expand messaging ability of
// insert_media.
if(!media.disks.empty()) {
if(drives_[0].has_disk())
drives_[1].set_disk(media.disks[0]);
else
drives_[0].set_disk(media.disks[0]);
}
// TODO: allow this only at machine startup.
if(!media.mass_storage_devices.empty()) {
const auto volume = dynamic_cast<Storage::MassStorage::Encodings::Macintosh::Volume *>(media.mass_storage_devices.front().get());
if(volume) {
volume->set_drive_type(Storage::MassStorage::Encodings::Macintosh::DriveType::SCSI);
}
hard_drive_->set_storage(media.mass_storage_devices.front());
}
return true;
}
// MARK: Keyboard input.
KeyboardMapper *get_keyboard_mapper() final {
return &keyboard_mapper_;
}
void set_key_state(uint16_t key, bool is_pressed) final {
keyboard_.enqueue_key_state(key, is_pressed);
}
// TODO: clear all keys.
// MARK: Interrupt updates.
void did_change_interrupt_status(Zilog::SCC::z8530 *sender, bool new_status) final {
update_interrupt_input();
}
void update_interrupt_input() {
// Update interrupt input.
// TODO: does this really cascade like this?
if(scc_.get_interrupt_line()) {
mc68000_.set_interrupt_level(2);
} else if(via_.get_interrupt_line()) {
mc68000_.set_interrupt_level(1);
} else {
mc68000_.set_interrupt_level(0);
}
}
// MARK: - Activity Source
void set_activity_observer(Activity::Observer *observer) final {
iwm_->set_activity_observer(observer);
if constexpr (model == Analyser::Static::Macintosh::Target::Model::MacPlus) {
scsi_bus_.set_activity_observer(observer);
}
}
// MARK: - Configuration options.
std::vector<std::unique_ptr<Configurable::Option>> get_options() final {
return Apple::Macintosh::get_options();
}
void set_selections(const Configurable::SelectionSet &selections_by_option) final {
bool quick_boot;
if(Configurable::get_quick_boot(selections_by_option, quick_boot)) {
if(quick_boot) {
// Cf. Big Mess o' Wires' disassembly of the Mac Plus ROM, and the
// test at $E00. TODO: adapt as(/if?) necessary for other Macs.
ram_[0x02ae >> 1] = 0x40;
ram_[0x02b0 >> 1] = 0x00;
}
}
}
Configurable::SelectionSet get_accurate_selections() final {
Configurable::SelectionSet selection_set;
Configurable::append_quick_boot_selection(selection_set, false);
return selection_set;
}
Configurable::SelectionSet get_user_friendly_selections() final {
Configurable::SelectionSet selection_set;
Configurable::append_quick_boot_selection(selection_set, true);
return selection_set;
}
private:
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) final {
scsi_bus_is_clocked_ = scsi_bus_.preferred_clocking() != ClockingHint::Preference::None;
}
void drive_speed_accumulator_set_drive_speed(DriveSpeedAccumulator *, float speed) final {
iwm_.flush();
drives_[0].set_rotation_speed(speed);
drives_[1].set_rotation_speed(speed);
}
forceinline void adjust_phase() {
++phase_;
}
forceinline void fill_unmapped(const Microcycle &cycle) {
if(!(cycle.operation & Microcycle::Read)) return;
if(cycle.operation & Microcycle::SelectWord) {
cycle.value->full = 0xffff;
} else {
cycle.value->halves.low = 0xff;
}
}
/// Advances all non-CPU components by @c duration half cycles.
forceinline void advance_time(HalfCycles duration) {
time_since_video_update_ += duration;
iwm_ += duration;
ram_subcycle_ = (ram_subcycle_ + duration.as_integral()) & 15;
// The VIA runs at one-tenth of the 68000's clock speed, in sync with the E clock.
// See: Guide to the Macintosh Hardware Family p149 (PDF p188). Some extra division
// may occur here in order to provide VSYNC at a proper moment.
// Possibly route vsync.
if(time_since_video_update_ < time_until_video_event_) {
via_clock_ += cycle.length;
via_clock_ += duration;
via_.run_for(via_clock_.divide(HalfCycles(10)));
} else {
auto via_time_base = time_since_video_update_ - cycle.length;
auto via_cycles_outstanding = cycle.length;
auto via_time_base = time_since_video_update_ - duration;
auto via_cycles_outstanding = duration;
while(time_until_video_event_ < time_since_video_update_) {
const auto via_cycles = time_until_video_event_ - via_time_base;
via_time_base = HalfCycles(0);
@@ -187,7 +614,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
// The keyboard also has a clock, albeit a very slow one — 100,000 cycles/second.
// Its clock and data lines are connected to the VIA.
keyboard_clock_ += cycle.length;
keyboard_clock_ += duration;
const auto keyboard_ticks = keyboard_clock_.divide(HalfCycles(CLOCK_RATE / 100000));
if(keyboard_ticks > HalfCycles(0)) {
keyboard_.run_for(keyboard_ticks);
@@ -197,7 +624,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
// Feed mouse inputs within at most 1250 cycles of each other.
if(mouse_.has_steps()) {
time_since_mouse_update_ += cycle.length;
time_since_mouse_update_ += duration;
const auto mouse_ticks = time_since_mouse_update_.divide(HalfCycles(2500));
if(mouse_ticks > HalfCycles(0)) {
mouse_.prepare_step();
@@ -210,8 +637,8 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
// anything connected.
// Consider updating the real-time clock.
real_time_clock_ += cycle.length;
auto ticks = real_time_clock_.divide_cycles(Cycles(CLOCK_RATE)).as_int();
real_time_clock_ += duration;
auto ticks = real_time_clock_.divide_cycles(Cycles(CLOCK_RATE)).as_integral();
while(ticks--) {
clock_.update();
// TODO: leave a delay between toggling the input rather than using this coupled hack.
@@ -219,272 +646,27 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::Two, false);
}
// A null cycle leaves nothing else to do.
if(!(cycle.operation & (Microcycle::NewAddress | Microcycle::SameAddress))) return delay;
auto word_address = cycle.active_operation_word_address();
// Everything above E0 0000 is signalled as being on the peripheral bus.
mc68000_.set_is_peripheral_address(word_address >= 0x700000);
// All code below deals only with reads and writes — cycles in which a
// data select is active. So quit now if this is not the active part of
// a read or write.
if(!cycle.data_select_active()) return delay;
// Check whether this access maps into the IO area; if so then
// apply more complicated decoding logic.
if(word_address >= 0x400000) {
const int register_address = word_address >> 8;
switch(word_address & 0x78f000) {
case 0x70f000:
// VIA accesses are via address 0xefe1fe + register*512,
// which at word precision is 0x77f0ff + register*256.
if(cycle.operation & Microcycle::Read) {
cycle.value->halves.low = via_.get_register(register_address);
} else {
via_.set_register(register_address, cycle.value->halves.low);
}
break;
case 0x68f000:
// The IWM; this is a purely polled device, so can be run on demand.
iwm_.flush();
if(cycle.operation & Microcycle::Read) {
cycle.value->halves.low = iwm_.iwm.read(register_address);
} else {
iwm_.iwm.write(register_address, cycle.value->halves.low);
}
break;
case 0x780000:
// Phase read.
if(cycle.operation & Microcycle::Read) {
cycle.value->halves.low = phase_ & 7;
}
break;
case 0x480000: case 0x48f000:
case 0x580000: case 0x58f000:
// Any word access here adjusts phase.
if(cycle.operation & Microcycle::SelectWord) {
++phase_;
} else {
if(word_address < 0x500000) {
// A0 = 1 => reset; A0 = 0 => read.
if(*cycle.address & 1) {
scc_.reset();
} else {
const auto read = scc_.read(int(word_address));
if(cycle.operation & Microcycle::Read) {
cycle.value->halves.low = read;
}
}
} else {
if(*cycle.address & 1) {
if(cycle.operation & Microcycle::Read) {
scc_.write(int(word_address), 0xff);
} else {
scc_.write(int(word_address), cycle.value->halves.low);
}
}
}
}
break;
default:
if(cycle.operation & Microcycle::Read) {
LOG("Unrecognised read " << PADHEX(6) << (*cycle.address & 0xffffff));
cycle.value->halves.low = 0x00;
} else {
LOG("Unrecognised write %06x" << PADHEX(6) << (*cycle.address & 0xffffff));
}
break;
}
if(cycle.operation & Microcycle::SelectWord) cycle.value->halves.high = 0xff;
return delay;
}
// Having reached here, this is a RAM or ROM access.
// When ROM overlay is enabled, the ROM begins at both $000000 and $400000,
// and RAM is available at $600000.
//
// Otherwise RAM is mapped at $000000 and ROM from $400000.
uint16_t *memory_base;
if(
(!ROM_is_overlay_ && word_address < 0x200000) ||
(ROM_is_overlay_ && word_address >= 0x300000)
) {
memory_base = ram_;
word_address &= ram_mask_;
// This is coupled with the Macintosh implementation of video; the magic
// constant should probably be factored into the Video class.
// It embodies knowledge of the fact that video (and audio) will always
// be fetched from the final $d900 bytes (i.e. $6c80 words) of memory.
// (And that ram_mask_ = ram size - 1).
// if(word_address > ram_mask_ - 0x6c80)
update_video();
} else {
memory_base = rom_;
word_address &= rom_mask_;
// Writes to ROM have no effect, and it doesn't mirror above 0x60000.
if(!(cycle.operation & Microcycle::Read)) return delay;
if(word_address >= 0x300000) {
if(cycle.operation & Microcycle::SelectWord) {
cycle.value->full = 0xffff;
} else {
cycle.value->halves.low = 0xff;
}
return delay;
}
}
switch(cycle.operation & (Microcycle::SelectWord | Microcycle::SelectByte | Microcycle::Read | Microcycle::InterruptAcknowledge)) {
default:
break;
// Catches the deliberation set of operation to 0 above.
case 0: break;
case Microcycle::InterruptAcknowledge | Microcycle::SelectByte:
// The Macintosh uses autovectored interrupts.
mc68000_.set_is_peripheral_address(true);
break;
case Microcycle::SelectWord | Microcycle::Read:
cycle.value->full = memory_base[word_address];
break;
case Microcycle::SelectByte | Microcycle::Read:
cycle.value->halves.low = uint8_t(memory_base[word_address] >> cycle.byte_shift());
break;
case Microcycle::SelectWord:
memory_base[word_address] = cycle.value->full;
break;
case Microcycle::SelectByte:
memory_base[word_address] = uint16_t(
(cycle.value->halves.low << cycle.byte_shift()) |
(memory_base[word_address] & cycle.untouched_byte_mask())
);
break;
}
/*
Normal memory map:
000000: RAM
400000: ROM
9FFFF8+: SCC read operations
BFFFF8+: SCC write operations
DFE1FF+: IWM
EFE1FE+: VIA
*/
return delay;
}
void flush() {
// Flush the video before the audio queue; in a Mac the
// video is responsible for providing part of the
// audio signal, so the two aren't as distinct as in
// most machines.
update_video();
// As above: flush audio after video.
via_.flush();
audio_.queue.perform();
// Experimental?
iwm_.flush();
}
void set_rom_is_overlay(bool rom_is_overlay) {
ROM_is_overlay_ = rom_is_overlay;
}
bool video_is_outputting() {
return video_.is_outputting(time_since_video_update_);
}
void set_use_alternate_buffers(bool use_alternate_screen_buffer, bool use_alternate_audio_buffer) {
update_video();
video_.set_use_alternate_buffers(use_alternate_screen_buffer, use_alternate_audio_buffer);
}
bool insert_media(const Analyser::Static::Media &media) override {
if(media.disks.empty())
return false;
// TODO: shouldn't allow disks to be replaced like this, as the Mac
// uses software eject. Will need to expand messaging ability of
// insert_media.
if(drives_[0].has_disk())
drives_[1].set_disk(media.disks[0]);
else
drives_[0].set_disk(media.disks[0]);
return true;
}
// MARK: Keyboard input.
KeyboardMapper *get_keyboard_mapper() override {
return &keyboard_mapper_;
}
void set_key_state(uint16_t key, bool is_pressed) override {
keyboard_.enqueue_key_state(key, is_pressed);
}
// TODO: clear all keys.
// MARK: Interrupt updates.
void did_change_interrupt_status(Zilog::SCC::z8530 *sender, bool new_status) override {
update_interrupt_input();
}
void update_interrupt_input() {
// Update interrupt input.
// TODO: does this really cascade like this?
if(scc_.get_interrupt_line()) {
mc68000_.set_interrupt_level(2);
} else if(via_.get_interrupt_line()) {
mc68000_.set_interrupt_level(1);
} else {
mc68000_.set_interrupt_level(0);
// Update the SCSI if currently active.
if constexpr (model == Analyser::Static::Macintosh::Target::Model::MacPlus) {
if(scsi_bus_is_clocked_) scsi_bus_.run_for(duration);
}
}
private:
void update_video() {
forceinline void update_video() {
video_.run_for(time_since_video_update_.flush<HalfCycles>());
time_until_video_event_ = video_.get_next_sequence_point();
}
Inputs::Mouse &get_mouse() override {
Inputs::Mouse &get_mouse() final {
return mouse_;
}
struct IWM {
IWM(int clock_rate) : iwm(clock_rate) {}
HalfCycles time_since_update;
Apple::IWM iwm;
void flush() {
iwm.run_for(time_since_update.flush<Cycles>());
}
};
using IWMActor = JustInTimeActor<IWM, 1, 1, HalfCycles, Cycles>;
class VIAPortHandler: public MOS::MOS6522::PortHandler {
public:
VIAPortHandler(ConcreteMachine &machine, RealTimeClock &clock, Keyboard &keyboard, Video &video, DeferredAudio &audio, IWM &iwm, Inputs::QuadratureMouse &mouse) :
machine_(machine), clock_(clock), keyboard_(keyboard), video_(video), audio_(audio), iwm_(iwm), mouse_(mouse) {}
VIAPortHandler(ConcreteMachine &machine, RealTimeClock &clock, Keyboard &keyboard, DeferredAudio &audio, IWMActor &iwm, Inputs::QuadratureMouse &mouse) :
machine_(machine), clock_(clock), keyboard_(keyboard), audio_(audio), iwm_(iwm), mouse_(mouse) {}
using Port = MOS::MOS6522::Port;
using Line = MOS::MOS6522::Line;
@@ -505,8 +687,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
b3: 0 = use alternate sound buffer, 1 = use ordinary sound buffer
b2b0: audio output volume
*/
iwm_.flush();
iwm_.iwm.set_select(!!(value & 0x20));
iwm_->set_select(!!(value & 0x20));
machine_.set_use_alternate_buffers(!(value & 0x40), !(value&0x08));
machine_.set_rom_is_overlay(!!(value & 0x10));
@@ -574,7 +755,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
void run_for(HalfCycles duration) {
// The 6522 enjoys a divide-by-ten, so multiply back up here to make the
// divided-by-two clock the audio works on.
audio_.time_since_update += HalfCycles(duration.as_int() * 5);
audio_.time_since_update += HalfCycles(duration.as_integral() * 5);
}
void flush() {
@@ -589,16 +770,15 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
ConcreteMachine &machine_;
RealTimeClock &clock_;
Keyboard &keyboard_;
Video &video_;
DeferredAudio &audio_;
IWM &iwm_;
IWMActor &iwm_;
Inputs::QuadratureMouse &mouse_;
};
CPU::MC68000::Processor<ConcreteMachine, true> mc68000_;
DriveSpeedAccumulator drive_speed_accumulator_;
IWM iwm_;
IWMActor iwm_;
DeferredAudio audio_;
Video video_;
@@ -610,6 +790,10 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
VIAPortHandler via_port_handler_;
Zilog::SCC::z8530 scc_;
SCSI::Bus scsi_bus_;
NCR::NCR5380::NCR5380 scsi_;
SCSI::Target::Target<SCSI::DirectAccessDevice> hard_drive_;
bool scsi_bus_is_clocked_ = false;
HalfCycles via_clock_;
HalfCycles real_time_clock_;
@@ -620,16 +804,61 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
bool ROM_is_overlay_ = true;
int phase_ = 1;
int ram_subcycle_ = 0;
DoubleDensityDrive drives_[2];
Inputs::QuadratureMouse mouse_;
Apple::Macintosh::KeyboardMapper keyboard_mapper_;
enum class BusDevice {
RAM, ROM, VIA, IWM, SCCWrite, SCCReadResetPhase, SCSI, PhaseRead, Unassigned
};
/// Divides the 24-bit address space up into $20000 (i.e. 128kb) segments, recording
/// which device is current mapped in each area. Keeping it in a table is a bit faster
/// than the multi-level address inspection that is otherwise required, as well as
/// simplifying slightly the handling of different models.
///
/// So: index with the top 7 bits of the 24-bit address.
BusDevice memory_map_[128];
void setup_memory_map() {
// Apply the power-up memory map, i.e. assume that ROM_is_overlay_ = true;
// start by calling into set_rom_is_overlay to seed everything up to $800000.
set_rom_is_overlay(true);
populate_memory_map(0x800000, [] (std::function<void(int target, BusDevice device)> map_to) {
map_to(0x900000, BusDevice::Unassigned);
map_to(0xa00000, BusDevice::SCCReadResetPhase);
map_to(0xb00000, BusDevice::Unassigned);
map_to(0xc00000, BusDevice::SCCWrite);
map_to(0xd00000, BusDevice::Unassigned);
map_to(0xe00000, BusDevice::IWM);
map_to(0xe80000, BusDevice::Unassigned);
map_to(0xf00000, BusDevice::VIA);
map_to(0xf80000, BusDevice::PhaseRead);
map_to(0x1000000, BusDevice::Unassigned);
});
}
void populate_memory_map(int start_address, std::function<void(std::function<void(int, BusDevice)>)> populator) {
// Define semantics for below; map_to will write from the current cursor position
// to the supplied 24-bit address, setting a particular mapped device.
int segment = start_address >> 17;
auto map_to = [&segment, this](int address, BusDevice device) {
for(; segment < address >> 17; ++segment) {
this->memory_map_[segment] = device;
}
};
populator(map_to);
}
uint32_t ram_mask_ = 0;
uint32_t rom_mask_ = 0;
uint16_t rom_[64*1024];
uint16_t ram_[256*1024];
uint16_t rom_[64*1024]; // i.e. up to 128kb in size.
std::vector<uint16_t> ram_;
};
}

View File

@@ -9,12 +9,15 @@
#ifndef Macintosh_hpp
#define Macintosh_hpp
#include "../../../Configurable/Configurable.hpp"
#include "../../../Analyser/Static/StaticAnalyser.hpp"
#include "../../ROMMachine.hpp"
namespace Apple {
namespace Macintosh {
std::vector<std::unique_ptr<Configurable::Option>> get_options();
class Machine {
public:
virtual ~Machine();

View File

@@ -25,8 +25,15 @@ class RealTimeClock {
public:
RealTimeClock() {
// TODO: this should persist, if possible, rather than
// being randomly initialised.
Memory::Fuzz(data_, sizeof(data_));
// being default initialised.
const uint8_t default_data[] = {
0xa8, 0x00, 0x00, 0x00,
0xcc, 0x0a, 0xcc, 0x0a,
0x00, 0x00, 0x00, 0x00,
0x00, 0x02, 0x63, 0x00,
0x03, 0x88, 0x00, 0x4c
};
memcpy(data_, default_data, sizeof(data_));
}
/*!

View File

@@ -12,16 +12,6 @@
using namespace Apple::Macintosh;
namespace {
const HalfCycles line_length(704);
const int number_of_lines = 370;
const HalfCycles frame_length(line_length * HalfCycles(number_of_lines));
const int sync_start = 36;
const int sync_end = 38;
}
// Re: CRT timings, see the Apple Guide to the Macintosh Hardware Family,
// bottom of page 400:
//
@@ -33,11 +23,10 @@ const int sync_end = 38;
// "The visible portion of a full-screen display consists of 342 horizontal scan lines...
// During the vertical blanking interval, the turned-off beam ... traces out an additional 28 scan lines,"
//
Video::Video(uint16_t *ram, DeferredAudio &audio, DriveSpeedAccumulator &drive_speed_accumulator) :
Video::Video(DeferredAudio &audio, DriveSpeedAccumulator &drive_speed_accumulator) :
audio_(audio),
drive_speed_accumulator_(drive_speed_accumulator),
crt_(704, 1, 370, Outputs::Display::ColourSpace::YIQ, 1, 1, 6, false, Outputs::Display::InputDataType::Luminance1),
ram_(ram) {
crt_(704, 1, 370, 6, Outputs::Display::InputDataType::Luminance1) {
crt_.set_display_type(Outputs::Display::DisplayType::RGB);
crt_.set_visible_area(Outputs::Display::Rect(0.08f, -0.025f, 0.82f, 0.82f));
@@ -48,6 +37,10 @@ void Video::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
crt_.set_scan_target(scan_target);
}
Outputs::Display::ScanStatus Video::get_scaled_scan_status() const {
return crt_.get_scaled_scan_status() / 2.0f;
}
void Video::run_for(HalfCycles duration) {
// Determine the current video and audio bases. These values don't appear to be latched, they apply immediately.
const size_t video_base = (use_alternate_screen_buffer_ ? (0xffff2700 >> 1) : (0xffffa700 >> 1)) & ram_mask_;
@@ -58,7 +51,7 @@ void Video::run_for(HalfCycles duration) {
// the number of fetches.
while(duration > HalfCycles(0)) {
const auto pixel_start = frame_position_ % line_length;
const int line = (frame_position_ / line_length).as_int();
const int line = int((frame_position_ / line_length).as_integral());
const auto cycles_left_in_line = std::min(line_length - pixel_start, duration);
@@ -73,8 +66,8 @@ void Video::run_for(HalfCycles duration) {
//
// Then 12 lines of border, 3 of sync, 11 more of border.
const int first_word = pixel_start.as_int() >> 4;
const int final_word = (pixel_start + cycles_left_in_line).as_int() >> 4;
const int first_word = int(pixel_start.as_integral()) >> 4;
const int final_word = int((pixel_start + cycles_left_in_line).as_integral()) >> 4;
if(first_word != final_word) {
if(line < 342) {
@@ -164,12 +157,12 @@ void Video::run_for(HalfCycles duration) {
}
bool Video::vsync() {
const int line = (frame_position_ / line_length).as_int();
const auto line = (frame_position_ / line_length).as_integral();
return line >= 353 && line < 356;
}
HalfCycles Video::get_next_sequence_point() {
const int line = (frame_position_ / line_length).as_int();
const auto line = (frame_position_ / line_length).as_integral();
if(line >= 353 && line < 356) {
// Currently in vsync, so get time until start of line 357,
// when vsync will end.
@@ -184,18 +177,12 @@ HalfCycles Video::get_next_sequence_point() {
}
}
bool Video::is_outputting(HalfCycles offset) {
const auto offset_position = frame_position_ + offset % frame_length;
const int column = (offset_position % line_length).as_int() >> 4;
const int line = (offset_position / line_length).as_int();
return line < 342 && column < 32;
}
void Video::set_use_alternate_buffers(bool use_alternate_screen_buffer, bool use_alternate_audio_buffer) {
use_alternate_screen_buffer_ = use_alternate_screen_buffer;
use_alternate_audio_buffer_ = use_alternate_audio_buffer;
}
void Video::set_ram_mask(uint32_t mask) {
void Video::set_ram(uint16_t *ram, uint32_t mask) {
ram_ = ram;
ram_mask_ = mask;
}

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