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

Compare commits

...

2160 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
Thomas Harte
0c8e313fd5 Merge pull request #635 from TomHarte/Cleanup
Improves code quality, particularly the NSDocument subclass.
2019-08-01 14:32:49 -04:00
Thomas Harte
f64ec11668 Tidies up and simplifies panel flow. 2019-08-01 14:31:45 -04:00
Thomas Harte
9bbccd89d3 Adds an extended rationale for current implementation.
Also strips some cruft of prior guesses.
2019-07-31 23:19:46 -04:00
Thomas Harte
1ae3799aee Merge pull request #634 from TomHarte/OpenCrash
Resolves a potential crash at launch on the Mac
2019-07-31 14:11:58 -04:00
Thomas Harte
260843e5b1 Starts poking at a pure total-based formula.
On the assumption that any relevant DC offset will fall out in the wash. This causes one 400kb disk to boot in a Macintosh 128kb, another couple to do reasonably well. So it's better than what was here before.
2019-07-31 12:42:23 -04:00
Thomas Harte
e3f22e5787 This should actually be PWM, I think.
I'm just unsure of exactly the proper formula, owing to my ignorance on basic electrical engineering. Research ahoy!
2019-07-30 22:02:41 -04:00
Thomas Harte
2aa308efdd Tweaks magic formulas.
The computer now at least seeks outward, until this attempt at drive speed calculation fails.
2019-07-30 16:18:36 -04:00
Thomas Harte
74c18d7861 Attempts a full wiring up of 400kb drive speed. 2019-07-30 15:08:55 -04:00
Thomas Harte
c41cccd9a6 Adds a workaround to display the ROM import banner even from File -> Open... . 2019-07-30 13:07:33 -04:00
Thomas Harte
bba34b28b8 Merge pull request #633 from TomHarte/Deferrer
Starts to formalise just-in-time handling
2019-07-30 11:35:13 -04:00
Thomas Harte
d8a41575c8 Picks a better function name. 2019-07-30 10:54:56 -04:00
Thomas Harte
0521de668a Omits redundant qualifier.
This code can't be anything but `inline`.
2019-07-29 23:07:02 -04:00
Thomas Harte
12441bddab Tries operator overloading as a workaround. 2019-07-29 23:04:02 -04:00
Thomas Harte
bc25c52683 Although duplicative, resolves function redefinition. 2019-07-29 22:49:02 -04:00
Thomas Harte
eb3fb70ea1 Merge branch 'Deferrer' of github.com:TomHarte/CLK into Deferrer 2019-07-29 21:24:21 -04:00
Thomas Harte
2f90ed1f32 Attempts to reformulate into valid C++. 2019-07-29 21:23:37 -04:00
Thomas Harte
f3dd4b028d Rolls out JustInTime acting to the MSX and ColecoVision. 2019-07-29 21:22:31 -04:00
Thomas Harte
7dcad516bd Undoes incorrect project change. 2019-07-29 17:21:34 -04:00
Thomas Harte
9859f99513 Adds a route to not bumping time. 2019-07-29 17:21:27 -04:00
Thomas Harte
51b7f2777d Adds a route to not bumping time. 2019-07-29 17:17:39 -04:00
Thomas Harte
2f2478d2d3 Implements AsyncJustInTimeActor, experimentally. 2019-07-29 16:38:57 -04:00
Thomas Harte
a43ada82b2 Experiments with a JustInTimeActor in the Master System. 2019-07-29 15:38:41 -04:00
Thomas Harte
5149f290d0 Starts trying to formalise just-in-time execution.
Which, at least, simplifies Cycle/HalfCycle to Cycle run_for usage via template.
2019-07-28 21:49:54 -04:00
Thomas Harte
0dc6f08deb Merge pull request #632 from TomHarte/BINSwitch
Eliminates the crutch of PlusToo BIN files.
2019-07-28 16:08:54 -04:00
Thomas Harte
b1f04ed96d Eliminates the crutch of PlusToo BIN files.
Thereby returning the .bin extension to the various consoles.
2019-07-28 16:07:16 -04:00
Thomas Harte
cd49b3c89b Merge pull request #631 from TomHarte/MacPageFlipping
Removes the video and audio base address latches.
2019-07-27 22:25:12 -04:00
Thomas Harte
f894d43111 Removes the video and audio base address latches.
It now seems to me that these take effect immediately.
2019-07-27 22:23:40 -04:00
Thomas Harte
4033c0c754 Merge pull request #630 from TomHarte/IWMWrites
Makes various fixes to `Drive` and `Track`.
2019-07-26 23:41:20 -04:00
Thomas Harte
786b26d23e Adds an admission of incompleteness.
I really need to figure out the 5-and-3 encoding.
2019-07-26 23:23:40 -04:00
Thomas Harte
d08d8ed22c Adds a further drift safeguard. 2019-07-26 23:23:01 -04:00
Thomas Harte
b7b62aa3f6 Resolves some type conversion warnings. 2019-07-26 23:20:40 -04:00
Thomas Harte
39d7e3c62c Ensures relative_exponents less than or equal to -32 don't overflow. 2019-07-26 22:19:40 -04:00
Thomas Harte
81b57ecf7c Adds noexcept. 2019-07-26 22:18:40 -04:00
Thomas Harte
572e8b52e1 At the cost of extra storage, attempts to simplify away potential rounding issues around the index hole. 2019-07-26 17:20:32 -04:00
Thomas Harte
9b634472c6 Gets a little more rigorous on "high resolution". 2019-07-26 15:26:51 -04:00
Thomas Harte
d8bc20b1ab Ensures quieter Release behaviour. 2019-07-25 22:55:27 -04:00
Thomas Harte
d2bfd59953 Ensures positional continuity across rotation speed changes. 2019-07-25 22:29:54 -04:00
Thomas Harte
3d20ae47ea Ensures cycles_since_index_hole_ is wrapped to track length. 2019-07-25 21:48:01 -04:00
Thomas Harte
85cf8d89bc Ensures an initial non-zero value. 2019-07-25 21:47:44 -04:00
Thomas Harte
50e954223a Merge pull request #629 from TomHarte/MinorSpeedImprovements
Attempts to improve 68000 (and Macintosh) emulation speed
2019-07-25 10:39:41 -04:00
Thomas Harte
109d5d16bd Withdraws optimisation, for further testing in the future. 2019-07-25 10:33:38 -04:00
Thomas Harte
1672dc5946 Reduces frequency of update_video() calls. 2019-07-25 10:14:52 -04:00
Thomas Harte
5769944918 Shrinks MicroOp struct size from 16 bytes to 4. 2019-07-25 10:14:36 -04:00
Thomas Harte
9ef1211d53 Adds missing header file. 2019-07-24 22:13:32 -04:00
Thomas Harte
f2ae04597f Updates test case. 2019-07-24 22:07:17 -04:00
Thomas Harte
1327de1c82 Slims the Program struct down to 8 bytes total. 2019-07-24 22:02:50 -04:00
Thomas Harte
827c4e172a Cuts a third from the Program struct.
Observation: [source/destination]_address are always one of the address registers. So you can fit both within a single byte.

Net effect: around a 12% reduction in execution costs, given that this reduces the size of the instructions table from 3mb to 2mb.
2019-07-24 18:39:36 -04:00
Thomas Harte
c300bd17fa Regularises as many source/destination sets as fit the current setter. 2019-07-24 18:22:44 -04:00
Thomas Harte
0187fd8eae Hides all runtime Program member accesses behind macros.
... and fixes unit tests.
2019-07-24 12:01:30 -04:00
Thomas Harte
0469f0240b Moves interrupt level selection outside the loop. 2019-07-23 23:13:03 -04:00
Thomas Harte
4aca6c5ef8 Adds a note of admission here. 2019-07-23 23:03:15 -04:00
Thomas Harte
d69aee4972 Removes stray \n. 2019-07-23 22:17:46 -04:00
Thomas Harte
3da47318b1 Updates copyright year. 2019-07-23 18:03:37 -04:00
Thomas Harte
ef036df2bc Merge pull request #628 from TomHarte/XCodeUpdate
Completes Xcode 10.3 upgrade checks.
2019-07-23 16:27:46 -04:00
Thomas Harte
579f68cf11 Completes Xcode 10.3 upgrade checks. 2019-07-23 16:27:18 -04:00
Thomas Harte
90f6ca4635 Merge pull request #627 from TomHarte/ExtraROMDetails
Extends system ROM details; provides for manual import on the Mac
2019-07-23 16:24:55 -04:00
Thomas Harte
374cac0107 Adds negative feedback to ROM installation process.
As an ugly kludge, code wise.
2019-07-23 16:24:23 -04:00
Thomas Harte
4d361b1952 Adds MIME type for Apple-recognised disk images. 2019-07-23 11:36:47 -04:00
Thomas Harte
fcee7779b0 Inserts missing spaces. 2019-07-22 23:11:37 -04:00
Thomas Harte
b4191b6225 Corrects DiskII boot ROM CRCs and improves corresponding declarations. 2019-07-22 23:07:23 -04:00
Thomas Harte
dbee37ab34 Provides extended ROM details for the VIC-20 and Oric. 2019-07-22 22:15:44 -04:00
Thomas Harte
a3ad0ab09b Completes the successful import path. 2019-07-22 21:46:28 -04:00
Thomas Harte
ed0c4c117b Ensures that machine name reaches Swift. 2019-07-22 21:18:30 -04:00
Thomas Harte
2432151bf8 Puts machine name into ROMMachine::ROM.
Also switches to idiomatic exit codes.
2019-07-22 21:14:21 -04:00
Thomas Harte
2129bfc570 Gets as far as testing ROMs against the missing list.
Though now it strikes me that I've forgotten to retain the machine name.
2019-07-22 18:02:48 -04:00
Thomas Harte
8de6cd3f44 Ensures that ROM files can be dragged and dropped into Swift.
Also adjusts the main window background colour, better to bridge the time between selecting a machine and it starting.
2019-07-22 17:18:31 -04:00
Thomas Harte
9b9831f28b The Mac port will now at least display a list of missing ROMs.
It doesn't yet offer the drag-and-drop functionality it promises, however.
2019-07-22 13:00:17 -04:00
Thomas Harte
8a2cac0d0c Fixes layout constraints. 2019-07-22 11:30:26 -04:00
Thomas Harte
e17b105574 Adds a quick label in exposition. 2019-07-22 11:18:39 -04:00
Thomas Harte
67c5f6b7cb Ensures the missing ROM list bubbles up to Swift. 2019-07-21 22:05:22 -04:00
Thomas Harte
d452d070a1 Extends the Mac ROM fetcher to return a missing-ROMs list. 2019-07-21 18:41:00 -04:00
Thomas Harte
a846c3245d Checks the application support directory before the application bundle for ROM images. 2019-07-20 23:04:46 -04:00
Thomas Harte
4ffa3c1b49 Provides an output for some of the extended ROM information. 2019-07-20 22:52:57 -04:00
Thomas Harte
b2a6682798 Adds extended ROM information for the ZX80 and '81. 2019-07-20 22:46:49 -04:00
Thomas Harte
f3aac603f8 Adds extended Introduces extended ROM details for the C1540, Electron, Master System and MSX. 2019-07-20 21:30:37 -04:00
Thomas Harte
712cb473f7 Adds extended ROM information for the CPC and ColecoVision. 2019-07-20 17:07:59 -04:00
Thomas Harte
3c68a5ca65 Enhances the amount of ROM information posted by the Apple machines. 2019-07-20 16:08:40 -04:00
Thomas Harte
20670bab2f Expands information included in ROM load requests. 2019-07-19 22:35:22 -04:00
Thomas Harte
86d709ae01 Merge pull request #626 from TomHarte/MacScreenshot
Adds a Macintosh screenshot
2019-07-18 20:43:26 -04:00
Thomas Harte
0aba95cc9d Recondenses image placement. 2019-07-18 20:40:25 -04:00
Thomas Harte
de3c8373fd Adds a Macintosh screenshot to the rogue's gallery. 2019-07-18 20:37:14 -04:00
Thomas Harte
75ecd4e72d Adds mention of the Macintosh. 2019-07-17 16:31:24 -04:00
Thomas Harte
56555a4d99 Merge pull request #621 from TomHarte/Mac128k
Adds preliminary emulation of the 512ke Macintosh.
2019-07-17 16:29:58 -04:00
Thomas Harte
cfad20bb33 Surfaces missing Macintosh types. 2019-07-17 16:02:25 -04:00
Thomas Harte
fa226bb1b9 Seeks to reduce enquiry costs. 2019-07-17 15:09:26 -04:00
Thomas Harte
77333ff9f7 It appears that file checksums are not reliable. 2019-07-17 14:56:50 -04:00
Thomas Harte
b9a34bee51 Substitutes a more efficient inner loop for audio generation. 2019-07-17 14:54:06 -04:00
Thomas Harte
22ee51c12c Corrects clocking issues around audio, and cuts down queue costs. 2019-07-17 14:41:36 -04:00
Thomas Harte
ee8d853fcb Ensures you can't get a phase 2 for free with run_for(0). 2019-07-17 14:20:27 -04:00
Thomas Harte
19198ea665 Improves const usage. 2019-07-16 22:13:47 -04:00
Thomas Harte
bcbda4d855 Adds .image as a synonym of .img. 2019-07-16 21:44:59 -04:00
Thomas Harte
79a624e696 Applies more rigorous logic to deciding when to stop parsing. 2019-07-16 18:06:54 -04:00
Thomas Harte
c123ca1054 Slightly improves syntax. 2019-07-16 18:05:58 -04:00
Thomas Harte
9f0f35033d Introduces sector interleaving. 2019-07-16 18:05:40 -04:00
Thomas Harte
3633285aaa Ensures a trailing zero bit isn't dropped. 2019-07-16 16:36:00 -04:00
Thomas Harte
cb16790330 Improves qualifiers. 2019-07-15 22:40:45 -04:00
Thomas Harte
67055d8b56 Reduces CheckingWriteProtect costs, negligibly. 2019-07-15 22:39:55 -04:00
Thomas Harte
ca37fd8f4c Corrects tag preservation. 2019-07-15 17:15:06 -04:00
Thomas Harte
46b98dab70 Bumps up the amount of reserved storage.
To avoid a reallocation when reading Mac data.
2019-07-15 17:12:31 -04:00
Thomas Harte
0568996264 Fixes a couple of data arrangement issues on output. 2019-07-15 17:11:58 -04:00
Thomas Harte
7baad61746 Attempts a full implementation of asynchronous write mode. 2019-07-15 17:11:12 -04:00
Thomas Harte
1d1e0d74f8 Corrects and introduces new parts. 2019-07-12 21:37:33 -04:00
Thomas Harte
d53d1c616f Continues trying to get to write support. 2019-07-12 21:20:05 -04:00
Thomas Harte
5b05a9bc61 Extends Drive to report is_writing and so that writing works as the first action. 2019-07-12 18:53:41 -04:00
Thomas Harte
2c39229b13 Adds has-new-disk flag, allowing mounting of software from the desktop. 2019-07-12 13:17:24 -04:00
Thomas Harte
59b5dfddec Added logic to allow a second disk to be inserted, at least. 2019-07-11 23:03:02 -04:00
Thomas Harte
b730ac5d5a Reintroduces 1-second disable implementation. 2019-07-11 23:02:47 -04:00
Thomas Harte
4860d8a7df Adds ask as a synonym of img. 2019-07-11 22:56:29 -04:00
Thomas Harte
9f0cde3d69 Improves mouse capture behaviour. 2019-07-11 22:56:08 -04:00
Thomas Harte
c8917e677b Edging towards implementing IWM write support, but mainly tidied up. 2019-07-11 21:42:34 -04:00
Thomas Harte
6c2cc206a6 Takes a first stab at write support for Macintosh disk images.
Albeit that the Macintosh itself can't yet write.
2019-07-11 18:09:37 -04:00
Thomas Harte
5a9f3cfc1e Completes Mac GCR decoding and its associated test. 2019-07-11 17:37:07 -04:00
Thomas Harte
8f28b33342 Starts work on Macintosh GCR decoding. 2019-07-11 16:28:52 -04:00
Thomas Harte
cac97a9663 Devolves drive responsibility. 2019-07-10 22:39:56 -04:00
Thomas Harte
2ccb564a7b Throws some extra logging into place, to test the IWM changeover. 2019-07-10 21:39:45 -04:00
Thomas Harte
d1d0430fce Eliminates the SonyDrive class. 2019-07-10 17:38:05 -04:00
Thomas Harte
be251d6b03 Begins substituting the DoubleDensityDrive for the Sony. 2019-07-10 16:24:48 -04:00
Thomas Harte
6cfaf920ee Added attribution and commentary on rotation speeds. 2019-07-10 16:22:06 -04:00
Thomas Harte
1657f8768c Transfers and slightly extends drive logic into the drive. 2019-07-10 16:17:51 -04:00
Thomas Harte
c4ab0bb867 Starts sketching out an interface for IWM drives, eliminating a dangling use of unsigned as it goes. 2019-07-10 16:05:59 -04:00
Thomas Harte
886946cc8c Rejigs time-until-event tracking. 2019-07-09 23:27:27 -04:00
Thomas Harte
ed4ddcfda8 Reduces call/return overhead on Microcycle methods. 2019-07-09 19:55:30 -04:00
Thomas Harte
7886cd63bd Flattens the Macintosh's perform_bus_operation, for legibility. 2019-07-09 19:49:06 -04:00
Thomas Harte
69b94719a1 Switches to faster bit count logic. 2019-07-09 18:41:20 -04:00
Thomas Harte
b4a3f66773 Restores just-in-time processing of video. 2019-07-09 18:08:07 -04:00
Thomas Harte
ab14433151 Tweaks optimisation level. 2019-07-09 18:07:43 -04:00
Thomas Harte
5078f6fb5c Marginally reduces MOVE heft. 2019-07-09 18:07:11 -04:00
Thomas Harte
fc6d62aefb Removes non-functioning workaround. 2019-07-09 16:41:15 -04:00
Thomas Harte
f73bccfec8 Adds a potential workaround for SDL mouse motion. 2019-07-09 16:38:16 -04:00
Thomas Harte
96be1a3f62 Corrects SDL mouse button up/down capture. 2019-07-09 16:32:38 -04:00
Thomas Harte
52e96e3d2a Documents and extends the Video interface.
With the intention of returning it soon to JIT execution.
2019-07-08 22:28:05 -04:00
Thomas Harte
33e2721eb2 Fully embraces forceinline. 2019-07-08 21:11:31 -04:00
Thomas Harte
4bc44666e5 Adds status notes. 2019-07-08 21:11:12 -04:00
Thomas Harte
3d8e4f96c8 Merge branch 'Mac128k' of github.com:TomHarte/CLK into Mac128k 2019-07-08 18:34:45 -04:00
Thomas Harte
94457d81b6 Eliminates redundant and integer-size-troubling AND on ASL. 2019-07-08 18:33:50 -04:00
Thomas Harte
c212bf27db Eliminates redundant and integer-size-troubling AND on ASL. 2019-07-08 18:28:36 -04:00
Thomas Harte
59b5ee65d4 Adds the Zilog SCC to SConstruct. 2019-07-08 18:18:49 -04:00
Thomas Harte
60cedca97b Adds cmath in support of ceilf. 2019-07-08 18:14:03 -04:00
Thomas Harte
1a9aa60bf7 Ensures no compiler will think this can exit without returning. 2019-07-08 18:13:23 -04:00
Thomas Harte
6438a5ca1f Updated SConstruct as per new Apple grouping. 2019-07-08 18:10:39 -04:00
Thomas Harte
3f303511bd Adds cstring include, in support of memcpy. 2019-07-08 18:06:58 -04:00
Thomas Harte
fb352a8d40 Ensures assert is completely excluded if NDEBUG. 2019-07-08 18:00:37 -04:00
Thomas Harte
ea7899f47d Updates the SConstruct in obvious ways. 2019-07-08 17:38:43 -04:00
Thomas Harte
fb6da1de4a Reduces logging temporarily. 2019-07-08 17:37:15 -04:00
Thomas Harte
2651b15db1 Takes a first stab at mouse input support from SDL.
There seems to be something odd going on with mouse buttons though; I'm going to test elsewhere.
2019-07-08 17:36:55 -04:00
Thomas Harte
6e7a733c3c Adds appropriate files to the Mac kiosk build. 2019-07-08 16:57:13 -04:00
Thomas Harte
245e27c893 Solidifies belief that the shift register bit is cleared on read/write. 2019-07-08 16:45:15 -04:00
Thomas Harte
793c2df7ee Fixes keypad keys. 2019-07-08 16:38:06 -04:00
Thomas Harte
28de629c08 Fixes the 6522 sufficiently to fix keyboard input. 2019-07-08 15:29:34 -04:00
Thomas Harte
210bcaa56d Introduces an initial shift unit test, and makes it pass. 2019-07-07 22:13:36 -04:00
Thomas Harte
d7329c1bdd Experiments with a timeout on keyboard interactions. 2019-07-07 14:13:55 -04:00
Thomas Harte
a5f0761a43 Copies in notes for required test functions. 2019-07-07 14:13:00 -04:00
Thomas Harte
dd963d6161 Eliminates call/return cost on WrappedInts. 2019-07-07 14:12:20 -04:00
Thomas Harte
96c0253ee2 Fixes mouse input when a button is pressed; attempts keyboard input.
I think the VIA is somehow sending spurious commands.
2019-07-02 21:14:33 -04:00
Thomas Harte
191a7a9386 Reintroduces an empty second drive.
This prevents the uninitialised disk error. Which is a clue.
2019-07-02 16:59:00 -04:00
Thomas Harte
387be4a0a6 Ensures mouse button presses propagate correctly.
Beyond the one that initiates mouse capture, that is.
2019-07-02 16:57:51 -04:00
Thomas Harte
b9c2c42bc0 Switches drives to using floats for time counting.
Hopefully to eliminate a lot of unnecessary `Time` work; inaccuracies should still be within tolerable range.
2019-07-02 15:43:03 -04:00
Thomas Harte
fffe6ed2df Chops the Macintosh down to a single drive to aid in development. 2019-07-02 13:59:30 -04:00
Thomas Harte
c4cbe9476c Corrects EA selection logic, fixing MOVEP. 2019-07-02 13:54:21 -04:00
Thomas Harte
0a67cc3dab Goes nuclear on ROXL and ROXR. 2019-07-01 23:05:48 -04:00
Thomas Harte
726e07ed5b Corrects ASL overflow flag. 2019-07-01 19:46:58 -04:00
Thomas Harte
ebb6313eef Corrects missing file. 2019-07-01 18:18:46 -04:00
Thomas Harte
11d8f765b2 Corrects divide-by-zero exception length, enables all other DIVS checks. 2019-07-01 15:46:04 -04:00
Thomas Harte
514e57b3e9 Corrects DIVU timing and flags, improves DIVS. 2019-07-01 14:24:32 -04:00
Thomas Harte
d8fb6fb951 Corrects MULU timing. 2019-06-30 22:40:10 -04:00
Thomas Harte
255f0d4b2a Corrects MULS timing. 2019-06-30 22:33:54 -04:00
Thomas Harte
d30e7504c2 Factors out MOVE tests, and ensures test machine RAM is zero initialised. 2019-06-30 21:43:30 -04:00
Thomas Harte
8d0cd356fd Corrects TRAP, TRAPV and CHK timing. 2019-06-29 21:25:22 -04:00
Thomas Harte
aff40bf00a Imports AND tests. 2019-06-29 20:16:10 -04:00
Thomas Harte
eedf7358b4 Imports first part of AND tests. 2019-06-29 16:29:47 -04:00
Thomas Harte
26aebcc167 Imports ROXL and ROXR tests.
Confirming the significant deficiencies I suspected.
2019-06-29 15:26:09 -04:00
Thomas Harte
9d420c727e Factors out rolls and shifts. 2019-06-29 14:12:52 -04:00
Thomas Harte
60fe84ad16 Imports Bcc tests. 2019-06-29 14:07:21 -04:00
Thomas Harte
6a44c682ad Factors out control flow tests. 2019-06-29 13:47:05 -04:00
Thomas Harte
60df44f0ca Imports CMPI tests. 2019-06-29 13:40:02 -04:00
Thomas Harte
ac926f5070 Factors BCD out of general arithmetic. 2019-06-29 13:31:24 -04:00
Thomas Harte
6e9a4a48f7 Imports TAS tests. 2019-06-28 22:56:35 -04:00
Thomas Harte
a8894b308a Splits out arithmetic tests, as so far implemented.
Further subdivision may be advisable.
2019-06-28 22:08:32 -04:00
Thomas Harte
7cc91e1bc5 Factors the bitwise tests out of the main bundle, as that pushes up towards 6,000 lines. 2019-06-28 21:58:38 -04:00
Thomas Harte
9eb51f164c Imports ANDI, ORI and EORI tests. 2019-06-28 21:42:58 -04:00
Thomas Harte
a1c00e9318 Adds BSR tests. 2019-06-28 21:31:41 -04:00
Thomas Harte
17666bc059 Corrects CHK flags. 2019-06-28 19:48:53 -04:00
Thomas Harte
241d29ff7c Imports SBCD and NBCD tests, and fixes corresponding operation. 2019-06-28 19:39:08 -04:00
Thomas Harte
c5039a4719 Imports ANDI, ORI and EORI to SR tests.
Hence corrects supervisor/user privileges for SR/CCR.
2019-06-28 15:05:46 -04:00
Thomas Harte
fd604048db Imports SUBX tests. 2019-06-28 14:30:26 -04:00
Thomas Harte
6a77ed1e07 Imports SUBI test. 2019-06-28 13:53:53 -04:00
Thomas Harte
9e38815ec4 Imports SUBQ tests. 2019-06-28 13:48:02 -04:00
Thomas Harte
86c325c4ec Imports MOVEA tests. 2019-06-28 13:41:37 -04:00
Thomas Harte
bfcc6cf12c Imports MULU tests.
Timing is wrong for now.
2019-06-28 13:33:41 -04:00
Thomas Harte
8ba8cf7c23 Imports TST tests. 2019-06-28 13:17:21 -04:00
Thomas Harte
6c588a1510 Makes some further random swings at tracking the startup procedure. 2019-06-28 13:03:47 -04:00
Thomas Harte
651fd9c4a5 Imports EOR tests. 2019-06-28 13:03:27 -04:00
Thomas Harte
5d0db2198c Imports BRA, EORI CCR and ORI CCR tests, extends PEA tests. 2019-06-27 23:05:00 -04:00
Thomas Harte
d81053ea38 Invents some additional PEA tests, and further fixes PEA. 2019-06-27 17:59:03 -04:00
Thomas Harte
8d39c3bc98 Takes a shot at fixing PEA for A7-relative addresses.
Unit tests required. Tomorrow.
2019-06-26 23:24:54 -04:00
Thomas Harte
da351a3e32 Imports MOVEQ tests. 2019-06-26 22:36:48 -04:00
Thomas Harte
c0591090f5 Imports DIVU tests. 2019-06-26 22:25:48 -04:00
Thomas Harte
538aecb46e Imports CMP tests, and fixes CMP.l timing. 2019-06-26 22:02:04 -04:00
Thomas Harte
dbdbea85c2 Imports CMPA tests, and fixes CMPA.w. 2019-06-26 21:42:48 -04:00
Thomas Harte
ba2224dd06 Imports NEGX tests and thereby fixes NEGX's zero flag. 2019-06-26 19:39:04 -04:00
Thomas Harte
44e2aa9183 Imports MOVEP tests; code corrections to come. 2019-06-26 19:01:09 -04:00
Thomas Harte
202bff70fe Imports BCLR and BTST tests. 2019-06-26 17:51:07 -04:00
Thomas Harte
26c0cd7f7c Imports ADDI tests. 2019-06-26 16:42:23 -04:00
Thomas Harte
cb76301fbe Imports BCHG tests. 2019-06-26 16:33:23 -04:00
Thomas Harte
8bfa12edf1 Adds lengths to ADD tests, imports ANDI ,CCR and MOVE to CCR. 2019-06-26 16:12:27 -04:00
Thomas Harte
7daa969a5a Imports SUBA tests. 2019-06-26 15:47:59 -04:00
Thomas Harte
4aeb60100d Completes import of MOVEM tests. 2019-06-26 15:31:21 -04:00
Thomas Harte
e2c7aaac5a Imports CLR tests. 2019-06-25 22:47:30 -04:00
Thomas Harte
6ff661c30d Imports OR tests. 2019-06-25 22:34:04 -04:00
Thomas Harte
79066f8628 Imports NOT tests, fixes NOT overflow and carry flags. 2019-06-25 22:18:11 -04:00
Thomas Harte
2c813a2692 Imports CMPM tests and fixes CMPM.bw source/destination order. 2019-06-25 21:46:01 -04:00
Thomas Harte
d2cb595b83 Proactively attempts to fix CMPM PostInc addressing. 2019-06-25 21:24:03 -04:00
Thomas Harte
cc4abcb00a Imports ADDQ tests. 2019-06-25 21:19:04 -04:00
Thomas Harte
c1ca85987f Incorporates MOVE to SR test. 2019-06-25 19:30:51 -04:00
Thomas Harte
ecb5a0b8cc Incorporates ADDX tests and fixes ADDX PreDec. 2019-06-25 19:18:07 -04:00
Thomas Harte
e12e8fc616 Incorporates ASR tests, and fixes ASR (xxx).w.
... which was re-injecting the wrong bit to preserve sign.
2019-06-25 18:44:31 -04:00
Thomas Harte
1fbbf32cd2 Adds ASL tests, and corrects ASL (xxx).w.
Overflow is wrong on other ASLs though, I think.
2019-06-25 18:09:01 -04:00
Thomas Harte
31edb15369 Reduces 68000 startup costs a little further. 2019-06-25 17:41:13 -04:00
Thomas Harte
d7883d18d4 Imports CHK tests.
Proving that I need to do some research on CHK's flags.
2019-06-25 14:55:03 -04:00
Thomas Harte
40100773d3 Imports LSR tests. 2019-06-25 13:57:42 -04:00
Thomas Harte
4048ed3a33 Imports ROR tests. 2019-06-25 13:16:44 -04:00
Thomas Harte
11f2d3cea7 Imports EXT tests. 2019-06-24 22:12:29 -04:00
Thomas Harte
aa656a39b8 Imports SUB tests. 2019-06-24 22:00:37 -04:00
Thomas Harte
e830d23533 Incorporates TRAPV tests. 2019-06-24 21:21:35 -04:00
Thomas Harte
9a666fb8cc Imports NEG tests and fixes NEG.l Dn timing. 2019-06-24 19:43:30 -04:00
Thomas Harte
0e208ed432 Fixes cycle counting in the test machine. 2019-06-24 17:55:09 -04:00
Thomas Harte
c8b769de8a Completes import of LSL tests and fixes various LSL issues.
Including LSL (xxx).w actually being LSR, and the carry flag generally being questionable.
2019-06-24 17:45:38 -04:00
Thomas Harte
c447655047 Resolves assumption that shifts greater than the bit count of the relevant int are well-defined in C. 2019-06-24 16:51:43 -04:00
Thomas Harte
3ec9a1d869 Incorporates JMP tests, fixes JSR (xxx).l timing. 2019-06-24 15:36:33 -04:00
Thomas Harte
d326886852 Completes BSET tests. 2019-06-24 14:04:08 -04:00
Thomas Harte
faef917cbd Improves resizeable microcycle test. 2019-06-24 10:55:22 -04:00
Thomas Harte
d27ba90c07 Attempts to introduce more rigour to variable-length instruction handling. 2019-06-24 10:43:28 -04:00
Thomas Harte
db4ca746e3 Introduces BSET tests, fixes BSET timing. 2019-06-23 22:53:37 -04:00
Thomas Harte
d50fbfb506 Imports EXG and PEA tests, and fixes EXG timing. 2019-06-23 22:21:25 -04:00
Thomas Harte
5d283a9f1f Imports LEA tests. 2019-06-23 21:48:47 -04:00
Thomas Harte
86fdc75feb Incorporates RTR test, adding a ProcessorState helper. 2019-06-23 18:37:32 -04:00
Thomas Harte
b63231523a Completes import of ROL tests. 2019-06-23 17:33:12 -04:00
Thomas Harte
70e296674d Starts import of ROL tests.
Including time tests, this time.
2019-06-22 22:42:57 -04:00
Thomas Harte
5089fcd2f6 Makes a slightly futile attempt to resolve Heisen-failures. 2019-06-22 18:52:06 -04:00
Thomas Harte
df2ce8ca6f Imports MOVE tests. 2019-06-21 22:03:27 -04:00
Thomas Harte
7e209353bb Imports UNLINK and NOP tests. 2019-06-21 21:29:02 -04:00
Thomas Harte
c2806a94e2 Imports further MOVEM tests. 2019-06-21 21:20:13 -04:00
Thomas Harte
d428120776 Completes import of LINK tests. 2019-06-21 18:33:44 -04:00
Thomas Harte
8c8493bc9d Ensures proper loading of the SP at reset. 2019-06-21 18:20:26 -04:00
Thomas Harte
6b996ae57d Improves test machine and incorporates a first test of LINK. 2019-06-21 18:20:13 -04:00
Thomas Harte
ccfe1b13cb Imports DIVS, MULS and MOVE from SR tests.
Not all passing.
2019-06-21 16:03:11 -04:00
Thomas Harte
0c1c10bc66 Introduces a test that proves that DIVS' attempt to set proper timing isn't working. 2019-06-20 19:29:02 -04:00
Thomas Harte
fafd1801fe Introduces first DIVS test, and associated fixes. 2019-06-20 19:02:03 -04:00
Thomas Harte
bcf6f665b8 Simplifies and completes DBcc tests.
Subject to omitting a few that look to me like duplicates.
2019-06-20 17:19:25 -04:00
Thomas Harte
bd069490b5 Incorporates approximately half of the DBcc tests. 2019-06-20 16:29:32 -04:00
Thomas Harte
79d8d27b4c Reintroduces use of locations_by_bus_step_ to decrease 68000 construction time. 2019-06-20 15:10:11 -04:00
Thomas Harte
624b0b6372 Adds Scc tests. No implementation fixes required. 2019-06-19 21:42:54 -04:00
Thomas Harte
7976cf5b3c Adds ADDA tests. All passing without 68000 changes. 2019-06-19 21:31:14 -04:00
Thomas Harte
440f52c943 Incorporates TRAP test. 2019-06-19 21:18:30 -04:00
Thomas Harte
47b1218a68 Adds a couple of the one-shots: SWAP, MOVE USP. 2019-06-19 19:10:36 -04:00
Thomas Harte
91ced056d2 Adds tests for ADD. No failures. 2019-06-19 18:56:21 -04:00
Thomas Harte
8dace34e63 Imports third-party tests for ABCD, and thereby fixes ABCD. 2019-06-19 18:13:06 -04:00
Thomas Harte
8182b0363f Adds enum to help with status decoding. 2019-06-19 17:01:49 -04:00
Thomas Harte
c5b036fedf Ensures aborted decodes don't overwrite prior correct ones. 2019-06-19 17:00:44 -04:00
Thomas Harte
e26ddd0ed5 Corrects address fetches for CMPI.l #, (xxx).w. 2019-06-19 13:52:56 -04:00
Thomas Harte
ca83431e54 Fixed: Scc is a byte operation.
It was, until now, post-incrementing and pre-decrementing registers other than A7 incorrectly.
2019-06-19 13:15:12 -04:00
Thomas Harte
68a3e5a739 Renamed DiskCopy42 to MacintoshIMG, now that it's not just DiskCopy 4.2 files. 2019-06-18 14:32:58 -04:00
Thomas Harte
b98f10cb45 Substitutes working GCR test. 2019-06-18 14:24:55 -04:00
Thomas Harte
9730800b6a Adds support for raw sector dumps. 2019-06-18 14:14:25 -04:00
Thomas Harte
506276a2bd Corrected: use format tag as intended. 2019-06-18 14:04:28 -04:00
Thomas Harte
00c32e4b59 Further miscellaneous changes to debug logging. All temporary. 2019-06-18 10:34:31 -04:00
Thomas Harte
df56e6fe53 Fixed: the sector number also goes into sector bodies.
Also the checksum is written in the other order, and the final byte of data isn't output.
2019-06-18 10:34:10 -04:00
Thomas Harte
756641e837 Fixed: tags go first, then data. 2019-06-16 22:00:12 -04:00
Thomas Harte
05c2854dbc Makes at least some attempt at producing real disk tracks. 2019-06-16 21:17:24 -04:00
Thomas Harte
5c8aacdc17 Fixes the more obvious issues with GCR encoding: byte order, top bit selection. 2019-06-16 17:17:24 -04:00
Thomas Harte
745a5ab749 Introduces failing test of Macintosh GCR data encoding. 2019-06-16 16:53:03 -04:00
Thomas Harte
fe0dc4df88 Starts building out some tests for Apple GCR encoding. 2019-06-15 22:48:24 -04:00
Thomas Harte
33f2664fe9 Makes a first attempt at Macintosh GCR encoding. 2019-06-15 22:29:02 -04:00
Thomas Harte
a17e47fa43 Apple's GCR header varies between the Mac and the Apple II. 2019-06-15 16:32:56 -04:00
Thomas Harte
877b46d2c1 Advances IWM/drive emulation very close to the point of 'Welcome to Macintosh'. 2019-06-15 16:08:54 -04:00
Thomas Harte
cc7226ae9f Starts trying to get a bit more rigorous about collected meanings. 2019-06-13 22:48:10 -04:00
Thomas Harte
bde975a3b9 Possibly mights the tiniest bit of headway with 'the IWM'.
I'm now pretty sure that my 3.5" drive, which for now is implemented in the IWM (yuck) is just responding to queries incorrectly.
2019-06-13 22:38:09 -04:00
Thomas Harte
f6f9024631 Corrects Macintosh aspect ratio (and framing). 2019-06-13 18:41:38 -04:00
Thomas Harte
39aae34323 Avoids multiple calls to -[NSCursor hide] and -unhide.
Those are reference counted.
2019-06-13 13:39:35 -04:00
Thomas Harte
5630141ad7 Ensures randomised memory contents at startup. 2019-06-13 13:35:16 -04:00
Thomas Harte
535747e3f2 Restores single-line logging format. 2019-06-13 13:35:03 -04:00
Thomas Harte
59a94943aa Resolves final set of build warnings. 2019-06-13 10:55:29 -04:00
Thomas Harte
bf4889f238 Reduces warnings to 6. 2019-06-13 10:43:00 -04:00
Thomas Harte
7cc5afd798 Eliminates another couple of implicit type conversion warnings. 2019-06-13 10:30:26 -04:00
Thomas Harte
11ab021672 Further reduces implicit conversion warnings, to 17. 2019-06-13 10:27:49 -04:00
Thomas Harte
feafd4bdae Eliminates further type conversion warnings. 2019-06-13 10:20:17 -04:00
Thomas Harte
d6150645c0 By hook or by crook, mouse input now works. 2019-06-12 22:19:25 -04:00
Thomas Harte
ccd2cb44a2 Fills in enough of the SCC to allow completion of the Macintosh side of that relationship. 2019-06-12 17:51:50 -04:00
Thomas Harte
ec5701459c Makes various temporary logging changes. 2019-06-11 19:54:07 -04:00
Thomas Harte
ad8b68c998 Switches to a proper form of zero-upon-read data.
Not that it's necessarily correct.
2019-06-11 19:53:51 -04:00
Thomas Harte
c8066b01b6 Restores attempt at proper audio behaviour. 2019-06-11 19:53:22 -04:00
Thomas Harte
ebd59f4dd3 Performs the trivial part of wiring up the Macintosh mouse.
SCC still to go.
2019-06-11 19:52:37 -04:00
Thomas Harte
109953ef49 Ensures proper routing of mouse events from Cocoa. 2019-06-11 18:41:41 -04:00
Thomas Harte
124c7bcbb0 Makes the Macintosh a mouse machine, and makes mouse machines detectable. 2019-06-11 18:21:56 -04:00
Thomas Harte
a0321aa6ff Starts sketching out an emulator interface for mice. 2019-06-11 17:47:24 -04:00
Thomas Harte
567feaac10 Adds a proper shout out for releasing the mouse. 2019-06-11 16:35:04 -04:00
Thomas Harte
15c38e2f15 Adds the option for mouse capture. 2019-06-11 16:30:53 -04:00
Thomas Harte
3c075e9542 Switches drives 0 and 1. 2019-06-10 14:58:39 -04:00
Thomas Harte
9230969f43 Corrects enough of the 6522 and Keyboard to get an initial command seemingly working. 2019-06-10 09:28:27 -04:00
Thomas Harte
0e16c67805 Improves shift register connection, towards having the keyboard function properly.
It now seems not to receive a command terminator, but is at least getting a command.
2019-06-08 23:04:55 -04:00
Thomas Harte
697e094a4e Sketches out the absolute basics of an SCC interface. 2019-06-08 18:47:11 -04:00
Thomas Harte
50d37798a2 Eradicates magic constants. 2019-06-06 21:37:43 -04:00
Thomas Harte
e9d0676e75 Fiddles further with the tachometer. 2019-06-06 21:36:19 -04:00
Thomas Harte
7591906777 Numerous IWM fixes: the machine now seems to be trying to measure the tachometer. 2019-06-06 18:32:11 -04:00
Thomas Harte
08671ed69c Fixes setting of a Time to a float. 2019-06-05 14:43:34 -04:00
Thomas Harte
511d292e73 Ensures gain noise is forgotten upon assumption of a new track. 2019-06-05 14:43:17 -04:00
Thomas Harte
a413ae11cb Makes some sort of first attempt at having the IWM read. 2019-06-04 22:13:00 -04:00
Thomas Harte
833258f3d7 Sets things up to allow variable rotation rates, and especially Sony 800kb-style self-selecting rates. 2019-06-04 21:41:54 -04:00
Thomas Harte
b8a1553368 Adds putative support for PlusToo-style BIN files.
Albeit a bit of a guess, since it's not intended to be an emulator file format.
2019-06-04 21:41:09 -04:00
Thomas Harte
058fe3e986 Fixes some other low-hanging warning fruit. 2019-06-04 16:47:10 -04:00
Thomas Harte
51ee83a427 Resolves a further 11 conversion errors. 2019-06-04 16:34:45 -04:00
Thomas Harte
5b21da7874 Reduces number of warnings to 70. 2019-06-04 16:27:09 -04:00
Thomas Harte
bd7f00bd9c Resolves a further handful of implicit type conversion warnings. 2019-06-04 15:43:44 -04:00
Thomas Harte
517cca251f Corrected: the repository shouldn't default to a Release build. 2019-06-04 15:41:36 -04:00
Thomas Harte
1033abd9fe Starts making some space for Macintosh-style GCR encoding. 2019-06-04 15:41:15 -04:00
Thomas Harte
113d022741 Merge branch 'master' into Mac128k 2019-06-03 21:58:22 -04:00
Thomas Harte
299a7b99ae Merge pull request #624 from TomHarte/BookendCrash
Permits end_data only after a begin_data.
2019-06-03 21:57:59 -04:00
Thomas Harte
66540ff86f Permits end_data only after a begin_data. 2019-06-03 21:56:53 -04:00
Thomas Harte
8557558bd8 Mildly improves investigatory reporting. 2019-06-03 21:51:45 -04:00
Thomas Harte
376cf08c71 Merge branch 'master' into Mac128k 2019-06-03 15:59:33 -04:00
Thomas Harte
83e5e650d2 Merge pull request #623 from TomHarte/SharpEdges
Disallows smaller buffer use for 'sharp' displays and tightens sampling window.
2019-06-03 15:59:05 -04:00
Thomas Harte
b860ba2ee3 Disallows smaller buffer use for 'sharp' displays and tightens sampling window. 2019-06-03 15:58:14 -04:00
Thomas Harte
661fe1e649 Disables logging, for now. 2019-06-03 15:57:53 -04:00
Thomas Harte
5b8375f0a0 Disallows smaller buffer use for 'sharp' displays and tightens sampling window. 2019-06-03 15:57:31 -04:00
Thomas Harte
abe55fe950 Adds Timer 1 toggling of PB7. 2019-06-03 15:39:20 -04:00
Thomas Harte
4d4ddded6d Fixes register-relative JMP and JSR. 2019-06-03 15:29:50 -04:00
Thomas Harte
1328708a70 Switches to testing against the Mac Plus ROM.
Immediately uncovering an issue with JMP.
2019-06-03 14:54:18 -04:00
Thomas Harte
85298319fa Expands towards supporting multiple Macintosh models.
To provide another variable to help with bug isolation.
2019-06-03 14:50:36 -04:00
Thomas Harte
881feb1bd3 Adds preliminary parsing of the Disk Copy 4.2 format. 2019-06-02 13:39:25 -04:00
Thomas Harte
3e9fa63799 Adds a receiver for drive-motor control bytes.
My new belief is that I'm either reading the buffer from the wrong place, or the 68000 isn't filling it for some reason.
2019-06-01 19:31:32 -04:00
Thomas Harte
da2b190288 Stores expected bit length. 2019-06-01 19:08:29 -04:00
Thomas Harte
48d837c636 Attempts to respond more sensibly to various queries.
Including adding a 1-second delay on motor off.
2019-06-01 18:43:47 -04:00
Thomas Harte
983407896c Ensures consistent audio pipeline. 2019-06-01 17:29:57 -04:00
Thomas Harte
5c08bb810e In theory provides a full implementation of audio.
Albeit seemingly ineffective.
2019-06-01 15:44:29 -04:00
Thomas Harte
17635da812 Pushes Mac audio further towards being able to function. 2019-06-01 15:18:27 -04:00
Thomas Harte
6d985866ee All proper inputs are now provided to the audio generator.
Hopefully. The next job is to generate audio. If that sounds correct, then the disk motor speed question can be tackled.
2019-06-01 15:03:15 -04:00
Thomas Harte
723137c0d4 With some time additions to the 6522, starts wiring in Macintosh audio.
The audio buffer is also the disk motor buffer, so this is preparatory to further disk work.
2019-06-01 14:39:40 -04:00
Thomas Harte
938928865d Merge branch 'master' into Mac128k 2019-05-30 22:29:56 -04:00
Thomas Harte
d80b0cbf90 Merge pull request #622 from TomHarte/SConstructUTF
Adds recommended fix for 0xc3 in position 12 error.
2019-05-30 22:25:57 -04:00
Thomas Harte
e88ef30ce6 Adds recommended fix for 0xc3 in position 12 error. 2019-05-30 22:20:15 -04:00
Thomas Harte
4197c6f149 Attempts to make some further semantic sense of the various IWM controls. 2019-05-30 22:17:49 -04:00
Thomas Harte
035f07877c Reduces conversions to vector. 2019-05-30 12:08:35 -04:00
Thomas Harte
4632be4fe5 Wires up the final IWM signal, SEL, preparatory to an implementation. 2019-05-30 12:08:00 -04:00
Thomas Harte
b3d2b4cd37 Fixes the interrupt return address. 2019-05-29 20:27:46 -04:00
Thomas Harte
c86fe9ada9 Ensures replace_write_values works in release builds. 2019-05-29 19:00:53 -04:00
Thomas Harte
ecf93b7822 Eliminates some type conversion warnings. 2019-05-29 14:56:50 -04:00
Thomas Harte
541b75ee6e Further fixes PEA, and OR/AND/EOR Dn, (An). 2019-05-29 14:37:15 -04:00
Thomas Harte
77b08febdb Corrects PEA and adds an additional debugging aid. 2019-05-29 12:47:17 -04:00
Thomas Harte
fcda376f33 Removes three further type conversion warnings. 2019-05-28 21:56:49 -04:00
Thomas Harte
0848fc7e03 Ensures the Mac uses auto vectored interrupts. 2019-05-28 16:24:41 -04:00
Thomas Harte
3bb8d6717f Ensures A7 is correct at end of an UNLINK. 2019-05-28 16:02:42 -04:00
Thomas Harte
5e2496d59c Simplifies and corrects MOVE logic. 2019-05-28 15:17:03 -04:00
Thomas Harte
c52da9d802 Adds some logging preparatory to a MOVE change. 2019-05-28 15:05:42 -04:00
Thomas Harte
1d3dde32f2 Ensures final byte of data can be accessed. 2019-05-09 07:24:26 -04:00
Thomas Harte
0b999ce0e4 Attempts to fix register-relative JSRs. 2019-05-09 06:43:07 -04:00
Thomas Harte
b04bd7069d Corrects Scc and DBcc (xxx).l and (xxx).w. 2019-05-09 06:28:55 -04:00
Thomas Harte
249b0fbb32 Corrects PC on stack after an illegal instruction.
Also fixed LOG_TRACE functionality.
2019-05-08 22:36:25 -04:00
Thomas Harte
41740fb45e Implements video position feedback.
At a substantial performance cost for now, but I'll worry about that once things are working.
2019-05-08 16:54:19 -04:00
Thomas Harte
0ad88508f7 Removes ROM mirroring above $600000. 2019-05-08 15:07:03 -04:00
Thomas Harte
8293b18278 Adds a TODO on what I think might be an incorrect implementation? 2019-05-08 15:06:40 -04:00
Thomas Harte
2ba0364850 Adds the shift register interrupt. 2019-05-08 15:02:07 -04:00
Thomas Harte
8b72043f33 Ensures no uninitialised variables. 2019-05-08 14:54:54 -04:00
Thomas Harte
2e7bc0b98a Attempts the shift register. 2019-05-08 14:54:40 -04:00
Thomas Harte
f0f9722ca6 Takes a first crack at the keyboard's serial protocol.
Albeit that without a working shift register in the VIA, this shouldn't really work yet.
2019-05-08 14:20:28 -04:00
Thomas Harte
b5ef88902b Edges further towards a functioning keyboard. 2019-05-08 13:58:52 -04:00
Thomas Harte
8278809383 Attempts to get more rigorous on communicating outward control line changes. 2019-05-08 13:33:22 -04:00
Thomas Harte
4367459cf2 Takes a first go at handshake and pulse modes. 2019-05-08 12:48:29 -04:00
Thomas Harte
254132b83d Eliminates 6522Base in pursuit of working handshake modes.
Specifically: this means that the places from which the BusHandler may be called are more numerous.
2019-05-08 12:35:17 -04:00
Thomas Harte
7b466e6d0a Begins work on a functioning keyboard. 2019-05-08 12:34:26 -04:00
Thomas Harte
7e6d4f5a3e Adds emulation of the real-time clock. 2019-05-08 00:12:19 -04:00
Thomas Harte
ce099a297a Eliminates RAM writes in ROM area.
I no longer think that logic is correct.
2019-05-07 17:16:22 -04:00
Thomas Harte
949c848815 Broadens address decoding.
To no obvious change in output.
2019-05-06 22:57:29 -04:00
Thomas Harte
9bf9b9ea8c Ensures unmapped peripherals return a consistent value. 2019-05-06 21:32:10 -04:00
Thomas Harte
d8ed8b66f3 Improves carry/extend for ROXL and ROXR. 2019-05-06 21:14:16 -04:00
Thomas Harte
a131d39451 I now believe only the 6522 is on the synchronous bus. 2019-05-06 14:10:13 -04:00
Thomas Harte
b540f58457 Sets a more appropriate display type. 2019-05-05 23:22:05 -04:00
Thomas Harte
4f5a38b5c5 Adds support for the alternate video buffer. 2019-05-05 23:05:24 -04:00
Thomas Harte
cefc3af08b Corrects RAM read decoding when the ROM overlay is enabled. 2019-05-05 22:48:40 -04:00
Thomas Harte
e6ed50383c Corrects PEA and MOVE.l (An)[+], (xxx).L; also adds an extra test that caught the latter. 2019-05-05 22:47:54 -04:00
Thomas Harte
96facc103a Adds an IWM shim and corrects graphics output.
... now that there is some.
2019-05-05 21:55:34 -04:00
Thomas Harte
407bbfb379 Pretending the Disk II is an IWM doesn't seem to achieve much. 2019-05-05 18:12:25 -04:00
Thomas Harte
a99ebda513 Takes a first shot at (inverted) Mac video output. 2019-05-04 22:27:58 -04:00
Thomas Harte
537b604fc9 It looks like writes should always go to RAM.
Now I see the screen buffer being filled with `0xffff`s, along with what is probably disk motor control data.
2019-05-04 17:29:30 -04:00
Thomas Harte
98bc570bf7 Adds further boilerplate around VIA and IWM decoding. 2019-05-04 17:12:26 -04:00
Thomas Harte
181b77c490 Adds decoding of IWM accesses and respect for the ROM overlay bit. 2019-05-04 16:38:01 -04:00
Thomas Harte
bc9eb82e6f Adds in VIA access decoding, and a note to self on video.
The Mac now proceeds to try to talk to the IWM.
2019-05-04 14:23:37 -04:00
Thomas Harte
29fc024ecd Starts negotiating the Macintosh memory map. 2019-05-04 12:33:27 -04:00
Thomas Harte
c1695d0910 Adds various notes to self. 2019-05-03 23:55:28 -04:00
Thomas Harte
6d6a4e79c9 Adds the absolute basics to include a 6522 in the Macintosh.
Not yet wired to anything.
2019-05-03 23:40:22 -04:00
Thomas Harte
417a3e1540 Adds missing call to flush. 2019-05-03 23:31:12 -04:00
Thomas Harte
fa8c804d47 Makes explicit a few implicit type conversions.
There's plenty more down this well, alas.
2019-05-03 23:26:03 -04:00
Thomas Harte
68392ce6f5 Adds enough of a concept of Mac video to get a properly initialised display.
Completely empty at present, naturally. Also this is the very first time I've run my 68000 at live speed. From just one data point, it's not terrible. Phew!
2019-05-03 23:25:42 -04:00
Thomas Harte
6873f62ad8 Ensures that the Mac now retains its ROM properly. 2019-05-03 22:39:09 -04:00
Thomas Harte
5f385e15f6 Adds the bare bones necessary to be able to create a Macintosh from File -> New... . 2019-05-03 22:16:07 -04:00
Thomas Harte
8c5d37b6ee Refactors the AppleII into a sub-namespace to make room for other Apple machines. 2019-05-03 18:14:10 -04:00
Thomas Harte
9c3c2192dd Merge pull request #611 from TomHarte/68000
Adds an Initial Emulation of the 68000
2019-05-03 15:08:24 -04:00
Thomas Harte
4f9f73ca81 Corrects tests affected by change in run_for_instructions semantics and new program base address. 2019-05-03 15:05:14 -04:00
Thomas Harte
2c9a1f7b16 Restores vector. 2019-05-03 14:50:07 -04:00
Thomas Harte
0ea4c1ac80 Evicts #includes from my namespace. 2019-05-03 14:48:39 -04:00
Thomas Harte
a873ec97eb Also previously missing: vector.h. 2019-05-03 14:43:31 -04:00
Thomas Harte
cc8a65780e Adds further missing includes. 2019-05-03 14:42:36 -04:00
Thomas Harte
c117deb43b Introduces a couple of missing #includes. 2019-05-03 14:37:05 -04:00
Thomas Harte
ae31d45c88 Introduces the 68000 to SConstruct. 2019-05-03 14:31:09 -04:00
Thomas Harte
a0eb20ff1f Tweaks divide-by-zero timing. 2019-05-03 14:29:36 -04:00
Thomas Harte
34fe9981e4 Added necessary mea culpas. 2019-05-03 14:25:25 -04:00
Thomas Harte
291e91375f Takes a shot at the synchronous bus. 2019-05-03 14:20:59 -04:00
Thomas Harte
857f74b320 Fixed: the accepted interrupt level now appears on the bus. 2019-05-02 15:47:12 -04:00
Thomas Harte
1d9608efc7 Alters the order of interrupt bus activity, to bring it into line with a real 68000. 2019-05-02 15:25:43 -04:00
Thomas Harte
93616a4903 Completes test of a vectored interrupt.
Correcting issues uncovered.
2019-05-02 00:00:09 -04:00
Thomas Harte
bb07206c55 Corrects internet response to work as currently implemented.
Also makes corrections to the bus error and address error exceptions.
2019-05-01 21:59:06 -04:00
Thomas Harte
2e5c0811e7 Makes some effort at getting into interrupt processing. 2019-05-01 15:26:36 -04:00
Thomas Harte
f6ac407e4d Takes further steps towards supporting interrupts.
Specifically:
* introduces the necessary bus signalling; and
* adds corresponding functional steps.

Still to figure out: getting into and out of an interrupt cycle.
2019-05-01 15:19:24 -04:00
Thomas Harte
078c3135df The 5/3 split of microcycles appears not accurately to model when lines are tested.
Therefore I've reverted to a more normative 4:4 form.
2019-04-30 22:09:13 -04:00
Thomas Harte
92568c90c8 Adds support for HALT as an input, and puts some effort into how to calculate E. 2019-04-30 22:07:48 -04:00
Thomas Harte
f1879c5fbc Corrects interrupt level test within STOP. 2019-04-30 19:32:35 -04:00
Thomas Harte
31bb770fdd Implement STOPpages, waits for DTack, and bus and address error exceptions. 2019-04-30 19:24:22 -04:00
Thomas Harte
e430f2658f Adds a test and by that means fixes divide-by-zero exception return addresses. 2019-04-29 23:09:50 -04:00
Thomas Harte
3060175ff5 Eliminates constructions of std::tuple for performance reasons.
Specifically: reduces 68000 construction time from 10+ seconds to more like 2.8.
2019-04-29 22:43:15 -04:00
Thomas Harte
eb4233e2fd Joins some commonalities, shaving about 150 lines of code. 2019-04-29 22:37:23 -04:00
Thomas Harte
6b4c656849 Reverses order of instruction instantiation, reducing total bus step heft by about 11%.
... since that means inserting more complicated instructions before simpler ones in general, making subset finds more likely.
2019-04-29 22:20:18 -04:00
Thomas Harte
1b8fada6aa Restores accidentally-cropped functionality. 2019-04-29 22:10:00 -04:00
Thomas Harte
7332c64964 Improves testing of function as distinct from timing. 2019-04-29 22:08:37 -04:00
Thomas Harte
977f9ee831 Takes a run at divide-by-zero exceptions and starts looking towards ways to improve startup time. 2019-04-29 22:08:16 -04:00
Thomas Harte
16fb3b49a5 It leads to a TODO, but implemented decoding and initial setup of STOPpages. 2019-04-29 19:30:00 -04:00
Thomas Harte
3da1b3bf9b Introduces storage for various bus inputs. 2019-04-29 19:22:05 -04:00
Thomas Harte
bc00856c05 Removed TODO; it appears this is just the standard stack frame. 2019-04-29 19:09:20 -04:00
Thomas Harte
52e3dece81 Improves exposition. 2019-04-29 19:07:14 -04:00
Thomas Harte
2c1d8fa18a Adds a check for instruction privilege violation, albeit that I think I need different bus steps. 2019-04-29 19:06:10 -04:00
Thomas Harte
3e34ae67f6 Implements support for the trace flag. 2019-04-29 19:02:59 -04:00
Thomas Harte
d6e16d0042 Adds a test of TOS 1.00, as far as it goes without meaningful hardware. 2019-04-29 18:04:57 -04:00
Thomas Harte
8e02d29ae6 Trims test to length of trace capture. 2019-04-29 17:56:49 -04:00
Thomas Harte
ceebecec8d Corrects zero and negative flags for EXT.w. 2019-04-29 17:54:33 -04:00
Thomas Harte
05d1eda422 Fixes crossed-over decoding of EORI and ORI. 2019-04-29 17:45:52 -04:00
Thomas Harte
31f318ad43 Fixes MOVE.bw #, (xxx).w. 2019-04-29 17:41:46 -04:00
Thomas Harte
270f46e147 Normalises CMPl. 2019-04-29 17:27:56 -04:00
Thomas Harte
c0e9c37cc7 Improves memory map model, as far as it goes. 2019-04-29 17:27:44 -04:00
Thomas Harte
8564945713 Corrects vector nomination for unrecognised opcodes. 2019-04-29 17:10:33 -04:00
Thomas Harte
7bd7f3fb73 Sign-extends (xxx).w addresses. 2019-04-29 16:55:43 -04:00
Thomas Harte
5b5bfc8445 Applies trace testing to EmuTOS. 2019-04-29 16:55:21 -04:00
Thomas Harte
c466b6f9e7 Factors out the [unit testing] stuff of being a trace-checking 68000 bus handler. 2019-04-29 16:11:01 -04:00
Thomas Harte
407643c575 Tweaks test length slightly to ensure this doesn't run beyond the final line's end. 2019-04-29 15:40:17 -04:00
Thomas Harte
d9071ee9f1 Starts sketching out the asynchronous bus. 2019-04-29 13:45:53 -04:00
Thomas Harte
97e118abfa Corrects accidental exclusion of MOVE.bw (xxx).w, [(xxx).w/(xxx).l]. 2019-04-28 23:25:46 -04:00
Thomas Harte
412f091d76 Implements a missing form of BTST. 2019-04-28 23:20:50 -04:00
Thomas Harte
d9278e9827 Attempts to complete the list of things I can't disassemble.
Mysteries to be solved here, definitely. But: 13 missing opcodes remaining.
2019-04-28 23:11:49 -04:00
Thomas Harte
ca1f669e64 Implements MOVEP.
371 is now the alleged number of missing opcodes. But I'd dare imagine it's more like three or four.
2019-04-28 22:52:54 -04:00
Thomas Harte
0298b1b3b7 Implements LINK and UNLINK.
Also starts excluding opcodes that I can't determine the mapping of from the list of those tested against.

Due to those two things together, the latter incomplete: 627 opcodes outstanding. But only STOP and MOVEP remain on my list of things to implement prior to exceptions.
2019-04-28 17:12:31 -04:00
Thomas Harte
4b1324de77 Takes a run at TRAPV.
... to leave 1466 as the unimplemented count.
2019-04-28 15:52:58 -04:00
Thomas Harte
8e8dce9bec Attempts an implementation of CHK.
1467 is now the official count of things to implement, though I'm starting to get suspicious.
2019-04-28 15:47:21 -04:00
Thomas Harte
f4350522bf Implements NBCD.
Now outstanding: 1891.
2019-04-27 21:29:50 -04:00
Thomas Harte
e2abb66a11 Adds missing addressing modes for ADDA and SUBA.
... reducing missing opcodes to 1941.
2019-04-27 17:22:26 -04:00
Thomas Harte
ab5fcab9bf Attempts an implementation of ADDX and SUBX.
Leaving 2005 non-[A/F]-line instructions.
2019-04-27 16:57:47 -04:00
Thomas Harte
cf547ef569 Improves semantic communications and temporarily omits A- and F-line instructions.
So it looks like 2773 instructions left to go.
2019-04-27 15:15:03 -04:00
Thomas Harte
e75b386f7d Attempts DIVU and DIVS.
Reportedly leaving 10965 operations now unimplemented.
2019-04-26 22:22:35 -04:00
Thomas Harte
796203859f Implements PEA.
This decreases the unimplemented count by 28 from 11841 to 11813.
2019-04-26 13:49:59 -04:00
Thomas Harte
40f68b70c1 Adds quantification of reports.
Depressingly; 11,841 opcodes are still missing. Better get on with it!
2019-04-26 13:25:34 -04:00
Thomas Harte
40b2fe7339 Merge branch 'master' into 68000 2019-04-26 00:02:35 -04:00
Thomas Harte
a3b6d2d16e Corrects test and resolves all instances of opcodes that are valid but shouldn't be.
The converse case will require implementation of the remaining instructions.
2019-04-25 22:54:58 -04:00
Thomas Harte
3983f8303f Introduces failing test of 68000 opcode coverage. 2019-04-25 22:06:05 -04:00
Thomas Harte
7cbd5e0ef6 Imports additional files used as test cases. 2019-04-25 21:43:47 -04:00
Thomas Harte
dab9bb6575 Implements EXT. 2019-04-25 18:22:19 -04:00
Thomas Harte
7df85ea695 Cleans up and formally introduces a comparative source for QL startup. 2019-04-25 15:42:41 -04:00
Thomas Harte
c132bda01c Implements MOVE from SR. 2019-04-25 14:39:32 -04:00
Thomas Harte
4e25bcfcdc Corrects decoding of AND/OR x, Dn. 2019-04-25 14:19:13 -04:00
Thomas Harte
ea463549c7 Corrects overflow flag for LSL and LSR. 2019-04-25 13:59:10 -04:00
Thomas Harte
723acb31b3 Corrects various flag issues with ADD, SUB and NEG. 2019-04-25 13:53:23 -04:00
Thomas Harte
5725db9234 Corrects calculated-address TAS. 2019-04-25 12:42:05 -04:00
Thomas Harte
8557e563bc Takes a run at TAS, clarifying bus cycles. 2019-04-25 12:19:40 -04:00
Thomas Harte
d2491633ce Ensures MOVEM to M .w correctly updates A7. 2019-04-24 23:21:15 -04:00
Thomas Harte
002796e5f5 Takes a run at BSET and BCHG. 2019-04-24 23:01:32 -04:00
Thomas Harte
fa0accf251 Attempts to correct flags for ASL, ASR, LSL, LSR. 2019-04-24 21:04:47 -04:00
Thomas Harte
dcb8176d90 Corrects potential failure properly to set stack pointer state. 2019-04-24 17:58:27 -04:00
Thomas Harte
be32b1a198 Fixes JSR (An) return address [again]. 2019-04-24 17:50:38 -04:00
Thomas Harte
582e4acc11 Implements ANDI/ORI/EOR to SR/CCR. 2019-04-24 17:38:59 -04:00
Thomas Harte
10f75acf71 Causes EXG to function. 2019-04-24 16:32:16 -04:00
Thomas Harte
b9933f512f Fixed: the word/long-word bit works the other way around. 2019-04-24 16:30:15 -04:00
Thomas Harte
75a7f7ab22 Inserts missing program fetch for CMPI.bw #, (d8/16...). 2019-04-24 14:45:24 -04:00
Thomas Harte
757be2906e Merge pull request #620 from rzumer/fix_typos
Correct typos in Z80.hpp
2019-04-24 13:25:06 -04:00
Thomas Harte
e214584c76 SWAP should clear overflow and carry. 2019-04-24 13:19:56 -04:00
Thomas Harte
0bb6b498ce Simplifies and fixes post-inc MOVE behaviour. 2019-04-24 13:14:25 -04:00
Thomas Harte
958d44a20d Causes SWAP actually to perform. 2019-04-24 13:06:12 -04:00
Thomas Harte
bb9424d944 Corrects byte increment/decrement actions for A7. 2019-04-24 13:01:08 -04:00
Thomas Harte
11bf706aa2 Attempts to fix LT and LTE conditions. 2019-04-24 10:07:17 -04:00
Thomas Harte
033b8e6b36 ADD/SUBQ #, An shouldn't set flags.
Also, temporarily at least, adds a new means for observing CPU behaviour.
2019-04-24 09:59:54 -04:00
Thomas Harte
7c3ea7b2ea Resolves additional byte accesses being signalled as word. 2019-04-23 21:23:20 -04:00
Thomas Harte
a08043ae88 Ensures that MOVE.b #, (xxx).l writes only a byte.
Also rearranges some of the temporary logging functionality.
2019-04-23 19:01:58 -04:00
Thomas Harte
7c132a3ed5 Ensures 16-bit values of Xn for (d8, An, Xn) are sign extended. 2019-04-22 22:13:02 -04:00
Thomas Harte
20e774be1e Corrects return address of JSR (An). 2019-04-22 21:11:49 -04:00
Thomas Harte
6d6046757d Fixes predecrementing MOVEM to leave the proper address in the relevant register. 2019-04-22 15:41:09 -04:00
Thomas Harte
55073b0a52 Corrects a bunch of MOVEs to (d8/16, PC/An, [Xn]). 2019-04-21 22:55:23 -04:00
Thomas Harte
44eb4e51ed Ensures DBcc properly signals program fetches. 2019-04-21 22:54:20 -04:00
Thomas Harte
3cb042a49d Corrects the carry and extend flags for various long-word operations. 2019-04-21 22:08:18 -04:00
Thomas Harte
b78ea7d24c Further simplifies CMPA. 2019-04-20 21:23:36 -04:00
Thomas Harte
c66728dce2 Corrects decoding of CMPA. 2019-04-20 21:21:33 -04:00
Thomas Harte
0be9a0cb88 Corrects Scc (and other conditionals) for complex addressing modes. 2019-04-20 18:35:19 -04:00
Thomas Harte
a90f12dab7 Corrects return address for TRAP. 2019-04-20 15:49:32 -04:00
Thomas Harte
ef33b004f9 Corrects word access order of MOVEM.l. 2019-04-20 15:13:12 -04:00
Thomas Harte
2cac4b0d74 Corrects EA usage for ADDA and SUBA. 2019-04-19 23:02:41 -04:00
Thomas Harte
a49f516265 Corrects direction of MOVE [to/from] USP. 2019-04-19 22:41:06 -04:00
Raphaël Zumer
71ac26944d Correct typos in Z80.hpp 2019-04-19 17:44:52 -04:00
Thomas Harte
2d97fc1f59 Beefs up documentation and developer support. 2019-04-19 13:29:35 -04:00
Thomas Harte
9ef7743205 Attempts to unify type decoding a little further. 2019-04-19 13:29:20 -04:00
Thomas Harte
ee7ae11e90 Implements EXG and SWAP. 2019-04-19 11:27:43 -04:00
Thomas Harte
f67d7f1db5 Adds the final (!) set of missing MOVEs. 2019-04-19 11:11:38 -04:00
Thomas Harte
99981751a2 Adds the official NOP.
Which is a freebie.
2019-04-18 23:46:01 -04:00
Thomas Harte
ffdf02c5df Adds MOVE XXX.lw, -(An) 2019-04-18 23:40:54 -04:00
Thomas Harte
27c7d00a05 Commutes final missing MOVEs to TODOs. 2019-04-18 23:35:32 -04:00
Thomas Harte
64c4137e5b Begins a cleanup procedure on MOVE. 2019-04-18 23:25:19 -04:00
Thomas Harte
8c26d0c6e6 Makes an attempt at RTE and RTR. 2019-04-18 20:50:58 -04:00
Thomas Harte
81dcfd9f85 Implements AND, OR and EOR.
As well as introducing a little more nuance to the double-decoding test.
2019-04-18 16:34:48 -04:00
Thomas Harte
9334557fbf Added important TODO. 2019-04-17 23:12:32 -04:00
Thomas Harte
b09de8efce Attempts to fill in the rest of MOVE x, -(An). 2019-04-17 23:05:16 -04:00
Thomas Harte
5a50eb56dd Marginally increases coverage of MOVE x, -(An). 2019-04-17 22:30:07 -04:00
Thomas Harte
e49b257e94 Takes a run at TRAP. 2019-04-17 22:21:56 -04:00
Thomas Harte
b8a0f4e831 Implements MOVE to/from USP. 2019-04-17 16:58:59 -04:00
Thomas Harte
c265ea9847 Corrects byte writes in both test machines. 2019-04-17 16:39:10 -04:00
Thomas Harte
29f8dcfb40 Fixes a bunch of (d16, An)-type MOVEs and implements MOVE (XXX).wl, (d16,An)/etc. 2019-04-17 16:13:35 -04:00
Thomas Harte
0c05983617 Shortens impact of MULU on the instruction stream to correct parsing.
I need to look into this.
2019-04-17 15:15:48 -04:00
Thomas Harte
0bd653708c Corrects MOVE.bw Dn, (An)[+]. 2019-04-17 14:31:20 -04:00
Thomas Harte
41d800cb63 Fixes ADD/SUB Dn,x to use the proper destination value. 2019-04-17 10:23:47 -04:00
Thomas Harte
cadc0bd509 Mental delusion lifted: JSR doesn't look enough like BSR. 2019-04-17 10:02:14 -04:00
Thomas Harte
b64da2710a Corrects a few MOVE #s. 2019-04-17 10:00:14 -04:00
Thomas Harte
82b08d0e3a Corrects addressing behaviour of nRd[+-]. 2019-04-17 08:53:34 -04:00
Thomas Harte
8f77d1831b Implements MULU and MULS. 2019-04-16 22:16:43 -04:00
Thomas Harte
be722143e1 Completes addressing modes for ADDI/etc/etc. 2019-04-16 21:34:16 -04:00
Thomas Harte
d8d974e2d7 Consolidates JSR and BSR preparation. 2019-04-16 21:29:37 -04:00
Thomas Harte
9b7ca6f271 Implements the basics of EORI, ORI, ANDI, SUBI and ADDI.
Also corrects the BSR return address.
2019-04-16 19:50:10 -04:00
Thomas Harte
8ce018dbab Adds the necessary runtime support for AND, EOR and OR. 2019-04-16 15:17:40 -04:00
Thomas Harte
180062c58c Finishes fleshing out [ADD/SUB]Q. 2019-04-16 14:28:31 -04:00
Thomas Harte
6076b8df69 Merge branch 'master' into 68000 2019-04-16 14:07:23 -04:00
Thomas Harte
5e65ee79b1 Merge pull request #617 from TomHarte/MSXDisk
Removes hard-coded assumption about disk ROM list placement.
2019-04-16 11:22:52 -04:00
Thomas Harte
c0861c7362 Removes hard-coded assumption about disk ROM list placement. 2019-04-16 11:22:03 -04:00
Thomas Harte
37656f14d8 Adds basic addressing modes for [ADD/SUB]Q. 2019-04-16 11:19:45 -04:00
Thomas Harte
dec5535e54 Implements (arguably: fixes) BSR. 2019-04-15 23:20:36 -04:00
Thomas Harte
1f0e3b157a Corrects a couple of JSR and JMP addressing modes. 2019-04-15 22:37:11 -04:00
Thomas Harte
d802e83f49 Fills in further MOVEs. 2019-04-15 22:25:22 -04:00
Thomas Harte
ebcae25762 Adjusts JSR behaviour and further extends MOVE. 2019-04-15 22:02:52 -04:00
Thomas Harte
5330267d16 Implements BCLR. 2019-04-15 18:11:02 -04:00
Thomas Harte
892476973b Attempts RO{X}[L/R]. 2019-04-15 17:31:58 -04:00
Thomas Harte
84f4a25bc9 Completes TST. 2019-04-15 16:28:20 -04:00
Thomas Harte
1460a88bb3 Takes a run at JSR and RTS. 2019-04-15 15:14:38 -04:00
Thomas Harte
62e4c23961 Corrects memory map, causing the RAM test no longer to fail. 2019-04-15 13:03:32 -04:00
Thomas Harte
d25ab35d58 Finally gets setw usage correct. 2019-04-15 12:41:56 -04:00
Thomas Harte
a223cd90a1 Adds predecrement TSTs, increases QL running time, reduces logging. 2019-04-15 12:36:08 -04:00
Thomas Harte
aef92ba29c Corrects immediate shift count. 2019-04-15 12:25:45 -04:00
Thomas Harte
328d297490 Implements the first few addressing modes for TST. 2019-04-15 10:03:52 -04:00
Thomas Harte
3d240f3f18 Corrects decoding of DBcc. 2019-04-15 09:49:23 -04:00
Thomas Harte
45f35236a7 Corrects decoding of ADDA and SUBA. 2019-04-15 09:44:06 -04:00
Thomas Harte
fba210f7ce Corrects MOVE.l Dn, (An)[+]. 2019-04-15 09:30:49 -04:00
Thomas Harte
8a09e5fc16 Implements Scc. 2019-04-14 22:39:13 -04:00
Thomas Harte
52e33e861c Starts to introduce the QL as a second source for 68000 testing.
It's advantageous over the ST in that a commented disassembly of the ROM is available.
2019-04-14 22:15:09 -04:00
Thomas Harte
75d8824e6b Eliminates implicit type conversion. 2019-04-14 21:02:28 -04:00
Thomas Harte
325af677d3 Implements MOVEM to M with an implicit type conversion. 2019-04-14 20:53:27 -04:00
Thomas Harte
1003e70b5e Implements MOVEM to R. 2019-04-14 20:02:18 -04:00
Thomas Harte
d70229201d Advances right up to the lack of MOVEM actions being the final piece. 2019-04-14 14:45:29 -04:00
Thomas Harte
823f91605b Still slow pedalling slightly, adds further MOVEM storage. 2019-04-14 14:31:13 -04:00
Thomas Harte
53f75034fc Commits at least to decoding MOVEM. 2019-04-14 14:09:28 -04:00
Thomas Harte
78649a5b54 Fleshes out MOVE, (XXX) a little further. 2019-04-12 17:16:03 -04:00
Thomas Harte
f48db625a0 Corrects write-back and zero flag for ADD/SUB.l. 2019-04-12 16:41:00 -04:00
Thomas Harte
2ba66c4457 Corrects MOVEA, adds extra test safeguards. 2019-04-12 16:10:17 -04:00
Thomas Harte
2c78ea1a4e Completes conversion away from magic constants. 2019-04-12 15:48:29 -04:00
Thomas Harte
73f50ac44e Commits further to elimination of magic constants. 2019-04-12 13:45:28 -04:00
Thomas Harte
9ce48953c1 Improves debugging printout. 2019-04-12 13:45:03 -04:00
Thomas Harte
1098cd0c6b Begins rooting out magic constants. 2019-04-11 22:31:17 -04:00
Thomas Harte
652ebd143c Corrects addressing mode support for LEA. 2019-04-11 11:58:34 -04:00
Thomas Harte
8e9d7c0f40 Corrects register-relative address calculation. 2019-04-10 23:09:03 -04:00
Thomas Harte
a64948a2ba Permits zero-bus-op non-terminals. 2019-04-10 22:42:43 -04:00
Thomas Harte
43f619a081 Implements ASL, ASR, LSL and LSR. 2019-04-10 22:31:04 -04:00
Thomas Harte
a07de97df4 Implements the fixed part of register shifts. 2019-04-09 22:12:37 -04:00
Thomas Harte
85d25068a8 Attempts a full implementation of memory shifts. 2019-04-09 22:04:25 -04:00
Thomas Harte
7a0319cfe5 Kicks the work of dealing with ASL/etc into the runtime. 2019-04-09 21:48:08 -04:00
Thomas Harte
f750671f33 Stepping gingerly onwards, adds a double-decoding test.
As a result of that, collapses BRA into Bcc. Which provisionally looks correct.
2019-04-09 16:54:41 -04:00
Thomas Harte
7886fe677a Cleans up commenting. 2019-04-08 22:51:18 -04:00
Thomas Harte
73c027f8e3 Implements CMPA and CMPM. [Provisionally] completing the CMPs. 2019-04-08 22:40:38 -04:00
Thomas Harte
eda88cc462 Implements MOVE to CCR. 2019-04-07 22:24:17 -04:00
Thomas Harte
652f4ebfed Implements CLR, NEG, NEGX and NOT. 2019-04-07 22:07:39 -04:00
Thomas Harte
06a2f59bd0 Implements DBcc. 2019-04-06 23:21:01 -04:00
Thomas Harte
0af57806da Adds a hard-coded value sufficient to advance in TOS startup. 2019-04-06 20:00:34 -04:00
Thomas Harte
03f365e696 Corrects source/destination order of CMP setup. 2019-04-06 20:00:15 -04:00
Thomas Harte
49a22674ba Corrects MOVE destinations. 2019-04-06 18:33:53 -04:00
Thomas Harte
ec494511ec Implements CMP. 2019-04-06 10:41:19 -04:00
Thomas Harte
af02ce9c6e Attempts to correct various instances of PC-relative addressing. 2019-04-05 23:49:13 -04:00
Thomas Harte
56e42859ab Ensures the supervisor flag is updated properly on MOVE to SR. 2019-04-05 23:21:50 -04:00
Thomas Harte
2d153359f8 Adds BTST. 2019-04-04 21:43:22 -04:00
Thomas Harte
068ce23716 Adds a few more MOVEs. 2019-04-04 19:49:19 -04:00
Thomas Harte
03be2e3652 Adds decoding of ADDA and SUBA. 2019-04-03 22:39:01 -04:00
Thomas Harte
4ef2c0bed8 Completes ADD and SUB. 2019-04-03 21:41:59 -04:00
Thomas Harte
bfd405613c Reuse of addresses is also no longer implicit. 2019-04-03 21:27:11 -04:00
Thomas Harte
73e1c8c780 Corrects now-unimplemented ADD/SUB. 2019-04-03 19:43:54 -04:00
Thomas Harte
689ba1d4a2 Effective address adjustments now have to be explicit. 2019-04-03 19:13:10 -04:00
Thomas Harte
39b9d00550 Moves some way towards mapping out ADD and SUB, fixing a bug with address register modification. 2019-04-02 21:50:58 -04:00
Thomas Harte
64f99d83a4 Takes a stab at offering ADD, ADDA, SUB and SUBA operations.
Not yet decoded.
2019-04-01 21:21:26 -04:00
Thomas Harte
8f1faefa1c Implements further MOVEs and fixes a potential error in program formation. 2019-03-31 22:34:28 -04:00
Thomas Harte
2c5ff9ada0 Switches to running the real TOS, at least temporarily, and enables better testing. 2019-03-31 22:27:57 -04:00
Thomas Harte
a9ceef5c37 Improves communication slightly. 2019-03-31 22:27:33 -04:00
Thomas Harte
c6f977ed4b Corrects CMPI and documentation; implements JMP. 2019-03-31 21:13:26 -04:00
Thomas Harte
cb240cd32a Switches to a more explicit tokeniser, to allow for greater flexibility momentarily. 2019-03-30 23:11:39 -04:00
Thomas Harte
bc6349f823 Adds RESET, fixes branches and attempts to fix CMPI. 2019-03-29 23:40:54 -04:00
Thomas Harte
a93a1ae40f Completes MOVE.blw <ea>, Dn/An/(An)/(An)+, implements MOVEq. 2019-03-29 23:13:41 -04:00
Thomas Harte
25254255fe Implements a few additional MOVEs. 2019-03-27 21:26:04 -04:00
Thomas Harte
b0b2798f39 Updates to track Swift. 2019-03-27 21:25:51 -04:00
Thomas Harte
7f5c637aeb Updates to Swift 5. 2019-03-26 22:15:38 -04:00
Thomas Harte
42634b500c Implements LEA. 2019-03-26 22:07:28 -04:00
Thomas Harte
6f0eb5eccd Merge branch 'master' into 68000 2019-03-26 21:03:57 -04:00
Thomas Harte
3d83891eb0 Merge pull request #613 from TomHarte/Swift5
Performs basic migration to Xcode 10.2.
2019-03-26 21:03:30 -04:00
Thomas Harte
69a2a133d5 Performs basic migration to Xcode 10.2 — project settings, the one new warning, etc. 2019-03-26 19:47:41 -04:00
Thomas Harte
be4b38c76a Adds BRA and Bcc. 2019-03-25 22:54:49 -04:00
Thomas Harte
7163b1132c Takes a run at CMPI.
Also factors out a couple of mode things, clarifies on where things from the
prefetch are assembled to, and switches to ordering implemented instructions
alphabetically.
2019-03-24 23:05:57 -04:00
Thomas Harte
3ccec1c996 Implements MOVE to SR, fleshing out the final bits of storage for the status word. 2019-03-24 18:20:54 -04:00
Thomas Harte
47359dc8f1 Adds tests for MOVE.l (An), Dn, and thereby correct their implementation. 2019-03-23 21:41:47 -04:00
Thomas Harte
43532c8455 Starts to make incursions into MOVE[A].l. 2019-03-23 21:03:52 -04:00
Thomas Harte
d7c3d4ce52 Adds test for MOVEA.w (0x1000), A1 and fixes implementation thereof. 2019-03-22 23:27:48 -04:00
Thomas Harte
ed7060a105 Made an initial stab at completing MOVEA.w.
I think I'm probably peeking into the prefetch queue incorrectly.
2019-03-22 21:43:51 -04:00
Thomas Harte
db0da4b741 Improves get/set state. 2019-03-22 19:34:17 -04:00
Thomas Harte
c9c16968bb Implements MOVEA as distinct from MOVE.
At least as far as MOVE is implemented, that is.
2019-03-22 19:25:53 -04:00
Thomas Harte
87420881c8 Extends to a failing test. 2019-03-21 23:32:03 -04:00
Thomas Harte
fdc598f2e1 Starts MOVE tests; in pursuit of which talks the 68000 into obeying run lengths. 2019-03-21 22:30:41 -04:00
Thomas Harte
f679145bd1 Makes a further push into the MOVEs.
With some quick notation shortening.
2019-03-20 23:21:02 -04:00
Thomas Harte
eeb161ec51 Converts the prefetch queue into a 32-bit quantity. 2019-03-19 21:33:52 -04:00
Thomas Harte
21cb7307d0 Adds MOVE #, Dn and MOVEA An, An.
As well as the scheduling for `(d16,PC), Dd` and `MOVE (d8,As,Xn), Dd` other than the .ls.
2019-03-19 11:53:37 -04:00
Thomas Harte
412a1eb7ee Takes an initial run at (An)+, -(An), (d16,An) and (d8,An,Xn) addressing modes.
With only MOVEs from those to a data register implemented so far.
2019-03-18 22:51:32 -04:00
Thomas Harte
1d801acf72 Switched to a better ABCD fix. 2019-03-17 22:04:32 -04:00
Thomas Harte
0d7bbdad54 Begins a basic get/set state API, allowing some actual unit tests, implying an ABCD fix. 2019-03-17 21:57:00 -04:00
Thomas Harte
53b3d9cf9d Implements a few more MOVE variants, plus MOVEA. 2019-03-17 14:34:16 -04:00
Thomas Harte
c3ebbfb10e Implements all MOVE Dn, Dn. 2019-03-16 23:14:18 -04:00
Thomas Harte
58f035e31a Makes error more communicative. 2019-03-16 23:05:12 -04:00
Thomas Harte
a8f1d98d40 Small further adjustments; seems likely to be correct now. 2019-03-16 23:01:56 -04:00
Thomas Harte
cf6fa98433 Corrects detection of terminal micro-ops. 2019-03-16 22:50:44 -04:00
Thomas Harte
937b3ca81d Attempts properly to honour the bus-op and microcycle contract. 2019-03-16 22:36:09 -04:00
Thomas Harte
d0c5cf0d2d Starts attempting to kill the need to prepare all bus step sequences in advance. 2019-03-16 21:47:46 -04:00
Thomas Harte
4cbf2bef82 By way of a friend, clears a bunch of transient stuff out of 68000Storage.hpp.
As, even if not in the programmer's eye, this does affect recompilation times.
2019-03-16 19:41:07 -04:00
Thomas Harte
388d808536 Switches to providing UDS and LDS implicitly via address.
Also makes sure that the difference between a non-data cycle that starts without the address strobe active and one that starts with it active can be discerned.
2019-03-16 17:54:58 -04:00
Thomas Harte
720aba3f2d Adds an implementation of SBCD and slightly neatens syntax for building programs. 2019-03-14 21:22:02 -04:00
Thomas Harte
f9101de956 This might very well be the 68000's first real gasp: performing an ABCD. 2019-03-14 19:32:15 -04:00
Thomas Harte
bb04981280 I'm still dithering on address management, but this seeks fully to implement ABCD and SUBD bus programs. 2019-03-13 21:08:13 -04:00
Thomas Harte
57898ed6dd This is where my thinking now resides. Two levels of indirection, and consolidated collections. 2019-03-12 22:46:31 -04:00
Thomas Harte
33b53e7605 Settles upon disassembly as the route in, and begins work in that direction. 2019-03-11 22:47:58 -04:00
Thomas Harte
9e8928aad9 Implements as much as I currently care about of the Atari ST memory map. 2019-03-11 22:47:37 -04:00
Thomas Harte
89c71f9119 Introduces EmuTOS, and starts constructing test cases around it. 2019-03-10 18:40:12 -04:00
Thomas Harte
a4f6db6719 Removes ArrayBuilderTests as the ArrayBuilder is long gone. Disables TIA tests for now. 2019-03-10 18:07:23 -04:00
Thomas Harte
2d8e65ea32 Merge branch 'master' into 68000 2019-03-10 17:56:58 -04:00
Thomas Harte
48d1d27067 Merge pull request #612 from TomHarte/HighPrecisionTimer
Sketches a high-precision timer class.
2019-03-10 17:47:20 -04:00
Thomas Harte
98aa597510 A theoretical 68000 could now perform its /RESET. That's all though. 2019-03-10 17:42:13 -04:00
Thomas Harte
de56d48b2f Embraces a more communicative 68000 bus. 2019-03-10 17:27:34 -04:00
Thomas Harte
4aeb9a7c56 Genericises RegisterPair. 2019-03-09 21:16:11 -05:00
Thomas Harte
b9b52b7c8b Begins some very early sketching out of a 68000. 2019-03-09 00:00:23 -05:00
Thomas Harte
dc464a0b7b Introduces a wrapper class for high-precision timers. 2019-03-07 22:04:29 -05:00
Thomas Harte
13b6079826 Merge pull request #609 from TomHarte/ReducedScanTargetContention
Reduces draw/update contention.
2019-03-07 19:31:35 -05:00
Thomas Harte
6f7dd10d95 Reduces draw/update contention.
This won't yet have any effect on either port owing to the way they handle contexts, but here it is.
2019-03-07 19:28:32 -05:00
Thomas Harte
24fb95291a Reverts to support a full RGBA colour buffer. 2019-03-07 19:22:40 -05:00
Thomas Harte
48430bee60 Merge pull request #606 from TomHarte/MouseHiding
Causes the Mac mouse pointer to hide after 3 seconds.
2019-03-06 22:52:40 -05:00
Thomas Harte
42997dcb80 Switches brace style, to bring this into line with other source files. 2019-03-06 21:54:21 -05:00
Thomas Harte
0ace189e38 Takes a basic stab at mouse cursor hiding. 2019-03-06 21:49:50 -05:00
Thomas Harte
d03a7911b5 Merge pull request #605 from TomHarte/DisplayMetrics
UNREADY! Introduces dynamic output quality selection.
2019-03-06 19:20:35 -05:00
Thomas Harte
84422676cb Switches to more coherent logic about buffer sizing. 2019-03-06 19:19:30 -05:00
Thomas Harte
7441e3f4c5 Corrects aspect ratio when changing accumulation texture size. 2019-03-05 22:10:32 -05:00
Thomas Harte
f18132d674 Makes effort to round out draft 1 of Outputs::Display::Metrics. 2019-03-05 22:01:58 -05:00
Thomas Harte
5660007221 Experimentally introduces adaptive quality intermediate buffers. 2019-03-05 21:41:20 -05:00
Thomas Harte
cfebf1dc4a Merge branch 'master' into DisplayMetrics 2019-03-05 20:21:44 -05:00
Thomas Harte
5b0111b4c8 Merge pull request #604 from TomHarte/AYInputOutput
Implements proper AY IO output behaviour.
2019-03-05 20:21:21 -05:00
Thomas Harte
62a1d69cee Implements proper AY IO output behaviour. 2019-03-05 20:20:26 -05:00
Thomas Harte
86a6b04d4a Begins attempts to keep track of display metrics.
i.e. a system that can both make smart decisions about when to use a lower resolution, and hopefully allow some sort of flywheel-esque horizontal retrace synchronisation. And possibly some raster beam chasing?
2019-03-04 21:54:50 -05:00
Thomas Harte
8915950c7d Merge pull request #601 from TomHarte/8ppStencil
Switches to an 8bpp stencil, for Nvidia compatibility.
2019-03-03 20:39:50 -05:00
Thomas Harte
641e349f33 Switches to an 8bpp stencil, for Nvidia compatibility. 2019-03-03 20:38:24 -05:00
Thomas Harte
72b4bf9c98 Merge pull request #600 from TomHarte/MacCrash
Reintroduces proper locking of the Mac OpenGL context.
2019-03-03 15:25:26 -05:00
Thomas Harte
ccdeb3fbc8 Ensures draw is a no-op prior to pipeline setup. 2019-03-03 15:04:14 -05:00
Thomas Harte
34047fa60a Reintroduces proper locking of the OpenGL context in macOS. 2019-03-03 14:49:20 -05:00
Thomas Harte
05d483bc5b Corrects potential machine shutdown race condition. 2019-03-02 23:17:31 -05:00
Thomas Harte
113efd9b16 Merge pull request #598 from TomHarte/SVideoColeco
Introduces S-Video support for the ColecoVision.
2019-03-02 23:03:02 -05:00
Thomas Harte
c11a1f9679 Introduces S-Video support for the ColecoVision. 2019-03-02 23:02:37 -05:00
Thomas Harte
2beeaa513b Ensures a machine exists before messaging it. 2019-03-02 21:27:34 -05:00
Thomas Harte
5b56ad0d78 Merge pull request #597 from TomHarte/MacRaceCondition
Splits OpenGL ScanTarget update and draw functions.
2019-03-02 19:36:19 -05:00
Thomas Harte
bee0d09877 Splits display update and draw functions.
On the Mac, draw is now called without an update for resizing events, and
anything else requested by AppKit. In all other cases — including from
the SDL version — both are called as if they were still a single function.
2019-03-02 19:33:28 -05:00
Thomas Harte
42d8d187b3 Merge pull request #596 from TomHarte/MSXLogging
Eliminates dangling uses of `printf`.
2019-03-02 18:23:32 -05:00
Thomas Harte
d97348dd38 Eliminates dangling uses of printf. 2019-03-02 18:07:05 -05:00
Thomas Harte
e1ebb7ce9c Ensures no attempt to call nullptr. 2019-03-02 17:37:56 -05:00
Thomas Harte
47dd8ad069 Minor grammar fix. 2019-03-02 17:31:11 -05:00
Thomas Harte
6a55d75b3d Merge pull request #595 from TomHarte/MSXTapeMotor
Fixes various MSX tape-handling bugs, and adds a status LED.
2019-03-02 15:00:03 -05:00
Thomas Harte
d5b4ddd9e5 Simplifies use_fast_tape_ logic. 2019-03-02 14:54:26 -05:00
Thomas Harte
9c8a2265b5 Breaks infinite loop where signature[0] == 0x1f but some of the rest doesn't match. 2019-03-02 14:47:52 -05:00
Thomas Harte
84d7157dfb Corrects arithmetic on raw data blocks. 2019-03-02 14:40:48 -05:00
Thomas Harte
ddce4fb46b Ensures that unexpected padding goes somewhere. 2019-03-02 14:35:16 -05:00
Thomas Harte
1ccee036c4 Switches complete logic behind CAS to wave conversion to parsing tape files. 2019-03-02 14:19:54 -05:00
Thomas Harte
ef085e3f93 MSX: introduces a tape motor LED, and limits the fast-tape hack to the BIOS. 2019-03-01 18:49:21 -05:00
Thomas Harte
3862a93ff9 Removes mapping of the equals key to break.
... because I keep pressing it by accident.
2019-02-28 21:47:12 -05:00
Thomas Harte
fc21fbd1f1 Merge pull request #594 from TomHarte/MSXRegions
Introduces region support to the MSX.
2019-02-28 21:03:34 -05:00
Thomas Harte
903f9b5240 Gives the static analyser an opinion, at least. 2019-02-28 20:59:07 -05:00
Thomas Harte
816ad0a94c Introduces region support to the MSX. 2019-02-28 20:54:43 -05:00
Thomas Harte
0536697d8f Corrects scope of delay. 2019-02-28 18:46:28 -05:00
Thomas Harte
0dbd8a667d Corrects delay for SN access. 2019-02-27 22:58:43 -05:00
Thomas Harte
56e691f256 Merge pull request #592 from TomHarte/RecursiveMultiMachine
Resolves a potential deadlock on multi machine handover.
2019-02-27 22:42:40 -05:00
Thomas Harte
b0503efa3d Resolves a potential deadlock on multi machine handover. 2019-02-27 22:39:33 -05:00
Thomas Harte
b81e59fd8f Merge pull request #591 from TomHarte/ColecoVisionM1
Adds a single-cycle M1 delay to the ColecoVision.
2019-02-27 22:02:58 -05:00
Thomas Harte
d2da55aa03 Adds a single-cycle M1 delay to the ColecoVision. 2019-02-27 22:01:30 -05:00
Thomas Harte
28e69152d8 Merge pull request #590 from TomHarte/Screenshots
Unifies the OpenGL screenshot code and corrects it for arbitrary alignment.
2019-02-27 21:14:22 -05:00
Thomas Harte
d122535a65 Unifies the OpenGL screenshot code and corrects it for arbitrary alignment. 2019-02-27 21:05:02 -05:00
Thomas Harte
c8c24f81c8 Merge pull request #581 from TomHarte/ScanTarget
Decouples output of raster scans from their generation
2019-02-27 18:52:19 -05:00
Thomas Harte
db078c7363 Minor tweak: don't start counting phase from zero.
This should ensure no first pixel issues resulting from clamping.
2019-02-27 18:51:51 -05:00
Thomas Harte
6b4f6971de Disables upper limit on frame buffer size.
Filtering is sufficiently imperfect as to make this justifiable only when performance requires it. So I need a test for that. Marked as TODO.
2019-02-26 22:39:07 -05:00
Thomas Harte
79707a3c66 Improves filtering slightly, and ensures coefficients are always set when needed. 2019-02-26 22:35:55 -05:00
Thomas Harte
694783efe9 Brings S-Video inside the group that filters luminance.
Thereby revealing some sort of error in offset selection.
2019-02-26 22:27:40 -05:00
Thomas Harte
68c5474e36 Reintroduces basic filtering for RGB mode (and introduces it for monochrome composite). 2019-02-26 22:21:49 -05:00
Thomas Harte
cd055a0298 Introduces an upper bound on output resolution, and resolves full-screen clear colour. 2019-02-25 22:07:48 -05:00
Thomas Harte
8f2abab0d9 Ensures texture targets are initially clear. 2019-02-25 21:55:14 -05:00
Thomas Harte
4c5dee866b Ensures a proper black fill for Luminance8Phase8 input data. 2019-02-25 21:32:15 -05:00
Thomas Harte
7030abca97 Corrects PAL colours for the Vic-20. 2019-02-25 19:28:52 -05:00
Thomas Harte
c7c21a7e2c Sorry, ColecoVision, it's composite only for you. 2019-02-24 22:37:24 -05:00
Thomas Harte
b23e10e261 Improves error messaging and avoids trying to use a null window. 2019-02-24 22:31:59 -05:00
Thomas Harte
16731661e8 Switches back to being explicit about the colour burst phase.
Some sort of phase inversion otherwise seems to be achievable by software that switch modes often.
2019-02-24 22:28:11 -05:00
Thomas Harte
7bb90c78d9 Resolves out-of-bounds initial condition whenever this loop began with start_line = 2047.
This, I believe, was the remaining cause of screen flashes.
2019-02-24 19:50:19 -05:00
Thomas Harte
a6e61ef83b Reverts the clear colour to black.
The change was related to debugging; it was not intentionally committed.
2019-02-24 14:36:08 -05:00
Thomas Harte
d4134cd0d8 Restores proper colour phase to the Apple II.
Given that its timing errors were fixed, this also switches back to using 'default' colour bursts — i.e. ones with implicit phase. The Apple II continues to be an excellent bellwether for issues in the pipeline, and this helps further to ensure that.
2019-02-24 14:35:13 -05:00
Thomas Harte
c775a6c0f8 Introduces but disables a couple of bits of logging that might be helpful again in the future. 2019-02-24 14:30:39 -05:00
Thomas Harte
2f9e825728 Forces the outward-communicated composite angles to have the same precision as the cycle counts.
While also making a minor improvement to output range. Which is neither here nor there.
2019-02-24 14:29:43 -05:00
Thomas Harte
2f491b5be1 Reintroduces fragment snapping for composite colour sampling.
Thereby uncovers some sort of slightly-off recording of scan lines. On the Apple II, individual scans reach the ScanTarget at a density of exactly 0.25 colour cycles per pixel. So that timing information propagates exactly. But the whole lines that are composed via ::announce end up trying to fit 0.250154 colour cycles per pixel. Which creates a phase error as the display progresses from left to right.

This will need to be resolved in order to be able to fix the Apple II's intended colour phase. But, also, it's probably what was wrong with the Oric. And, quite possibly, why the single-step shader didn't work.
2019-02-24 13:39:14 -05:00
Thomas Harte
de7ebead23 Ensures the line_allocation_has_failed_ condition can be exited. 2019-02-21 22:30:41 -05:00
Thomas Harte
c0c4704419 Ensures that failure to allocate a line blocks all other allocations. 2019-02-21 21:38:48 -05:00
Thomas Harte
ec14750ff1 Minor text improvement. 2019-02-20 22:32:42 -05:00
Thomas Harte
e43de5f1ba Allows for failure to get a GL context as a reportable issue. 2019-02-20 22:06:22 -05:00
Thomas Harte
080f949f89 Ensures OpenGL version is logged prior to any other GL calls. 2019-02-20 20:21:17 -05:00
Thomas Harte
9f6956bd87 Awards default values to Scan, to appease GCC 7.3. 2019-02-19 21:40:42 -05:00
Thomas Harte
ddf5e1632d Ensures log memory is automatically initialised. 2019-02-18 22:08:03 -05:00
Thomas Harte
40bfde41cb Adds an OpenGL version shout out. 2019-02-18 22:01:56 -05:00
Thomas Harte
e0751af56d Handles the 0 return case. 2019-02-18 21:56:49 -05:00
Thomas Harte
3979faf43b Gets more explicit about potential causes of failure. 2019-02-18 21:53:35 -05:00
Thomas Harte
878b480a44 Tidies up marginally. 2019-02-18 21:37:07 -05:00
Thomas Harte
b35b6b2ba8 Resolves a couple of missing #includes for cassert. 2019-02-18 21:29:39 -05:00
Thomas Harte
d0b967ce53 Corrects typo; disables original colour ROM usage for now. 2019-02-18 20:49:54 -05:00
Thomas Harte
e5addb27ec Corrects log output. 2019-02-18 20:49:01 -05:00
Thomas Harte
ac8d43cc4a Improves use of const. 2019-02-18 20:21:41 -05:00
Thomas Harte
40ee215b1b By #define provides a means not to use the real composite samples.
To aid with debugging.
2019-02-18 17:20:52 -05:00
Thomas Harte
6c1d94beaa Adds composite monochrome output for the Oric. At least temporarily. 2019-02-18 16:56:48 -05:00
Thomas Harte
6b2e1fe62b Makes error reporting more communicative. 2019-02-18 11:13:54 -05:00
Thomas Harte
8ecf885629 Attempts to put in better OpenGL safety rails. 2019-02-18 10:29:40 -05:00
Thomas Harte
6d76b7cd94 Attempts to ensure proper colour output during alternating PAL lines. 2019-02-17 21:50:15 -05:00
Thomas Harte
7bd721f334 Resolves improper state if an end-of-frame clear is triggered by the first new line. 2019-02-17 21:49:53 -05:00
Thomas Harte
7939897622 Fixes announced timing difference between pixel and border lines.
The Apple II sync fault is now fixed!
2019-02-12 22:32:02 -05:00
Thomas Harte
77bebd4a65 Accounts for periods near an expected sync after a sync actually occurred. 2019-02-12 22:30:40 -05:00
Thomas Harte
5d68a5bdd0 Merge pull request #588 from TomHarte/SeparateChromaBuffer
Reintroduces a separate chrominance buffer
2019-02-12 19:52:54 -05:00
Thomas Harte
3e0b5433b9 Institutes colour/monochrome screen selection as an Apple II option.
Allowing me to test that straight-through composite still works.
2019-02-12 19:52:32 -05:00
Thomas Harte
ec8f1157c8 Corrects S-Video output. 2019-02-12 19:31:12 -05:00
Thomas Harte
037cbd534e Corrects phase error in chrominance separation. 2019-02-12 19:24:28 -05:00
Thomas Harte
208ef70e31 Corrects documentation. 2019-02-12 18:55:58 -05:00
Thomas Harte
2fa4c59523 Correction: use the QAM texture for colours. 2019-02-12 18:42:28 -05:00
Thomas Harte
cda0a2de79 Establishes QAM colour buffer lookups within the composite colour path.
Subject to errors in channel scaling and absolute position.
2019-02-10 23:02:31 -05:00
Thomas Harte
008f50832c Fixed: the two shaders that use a common input array should use common bindings. 2019-02-10 22:39:24 -05:00
Thomas Harte
c94acb1ca2 With a little more debug logging, discovered an issue with incrementing by four. 2019-02-09 22:45:20 -05:00
Thomas Harte
d341f98b09 Corrects horizontal scale. 2019-02-09 18:52:43 -05:00
Thomas Harte
e35a3ab566 Ensures proper uniforms and varyings for the qam_separation_shader. 2019-02-09 18:35:14 -05:00
Thomas Harte
b3b4b7cf0c Corrects QAM texture generation logic. 2019-02-09 17:20:13 -05:00
Thomas Harte
1cd6d58f17 Restores S-Video through line, as monochrome. 2019-02-09 17:13:43 -05:00
Thomas Harte
eecd4417e7 Bites the bullet and accepts that an additional texture will be useful for QAM separation. 2019-02-09 16:54:31 -05:00
Thomas Harte
21908dfcef Restores Oric audio. 2019-02-05 21:43:07 -05:00
Thomas Harte
75987f64ec Restores Oric audio. 2019-02-05 21:42:39 -05:00
Thomas Harte
798cc58f76 Simplifies the composite colour shader no longer to handle colour. 2019-02-05 19:22:35 -05:00
Thomas Harte
6ba1194d74 Sets a clear colour appropriate for phase-linked luminance clearing. 2019-02-03 22:33:04 -05:00
Thomas Harte
e5f75b5df2 Resolves repetition between svideo_sample and composite_sample. 2019-02-03 22:09:16 -05:00
Thomas Harte
b75ad3def2 Updates the multimachine for the ScanTarget world. 2019-02-03 15:07:22 -05:00
Thomas Harte
10c98f0a15 Switches TapeUEF to using LOG.
Reducing console noise for release builds.
2019-02-02 22:30:10 -05:00
Thomas Harte
caf72afcb4 Switches to a seven-point scheme, to determine whether falsely-shared luminance is at fault.
It doesn't seem to be, alas.
2019-01-31 21:19:30 -05:00
Thomas Harte
687e0b376e Enhances error checking around setting of uniforms. 2019-01-31 21:17:49 -05:00
Thomas Harte
122857e5b5 Improves automatic index generation, to allow for matrices implicitly taking up to four slots. 2019-01-31 18:49:01 -05:00
Thomas Harte
5002290428 Makes consistent use of textureLod rather than texture. 2019-01-26 22:05:15 -05:00
Thomas Harte
d09ac3384f Eliminates some old-school manual memory management.
In favour of additional copying, but I still think this is safer.
2019-01-25 22:54:23 -05:00
Thomas Harte
b6a4a7e0a5 This is no longer TODO. 2019-01-25 22:47:15 -05:00
Thomas Harte
c87994336c Switches the Shader class to using LOG. 2019-01-25 22:45:47 -05:00
Thomas Harte
85ad490089 Offers a less error-prone route to attribute binding. 2019-01-25 21:56:55 -05:00
Thomas Harte
73e32a9c76 Adds a missing directory. 2019-01-25 20:26:20 -05:00
Thomas Harte
a321ff3037 Adds some default values. 2019-01-25 20:21:24 -05:00
Thomas Harte
68d6feaa03 Adds missing includes and gets more explicit about exceptions. 2019-01-25 20:19:50 -05:00
Thomas Harte
74e1a9a621 Declines improper use of offset within loops and adds missing header. 2019-01-25 20:14:53 -05:00
Thomas Harte
097bc7055e Adds a default selection, for invalid models. 2019-01-25 19:31:44 -05:00
Thomas Harte
6a43fc5df0 Resolves a GCC-troubling circular declaration issue vs. atomic.h. 2019-01-25 19:30:39 -05:00
Thomas Harte
312f38906b Corrects two improper include paths. 2019-01-25 19:19:23 -05:00
Thomas Harte
f0ec9fa5d2 Updates the SConstruct file for new Outputs. 2019-01-25 19:11:57 -05:00
Thomas Harte
20b4896940 Eliminates the dead stuff of CRTConstants.hpp. 2019-01-25 19:11:39 -05:00
Thomas Harte
6a93d2d006 Corrects some minor spaces-instead-of-tabs errors. 2019-01-24 22:59:03 -05:00
Thomas Harte
ae0bc7e7aa Calculates sampling offsets up front. 2019-01-23 20:53:10 -05:00
Thomas Harte
a8acadbe13 Gives the shader builders freer rein over what to use as inputs, and turns angles into a varying.
All dropping out during the never-ending diagnosis at play here.
2019-01-22 22:20:12 -05:00
Thomas Harte
727f2e2ba0 Updates to the ScanTarget world. 2019-01-17 22:28:02 -05:00
Thomas Harte
a6683cb9b8 Avoids scaling luminance prior to extracting chrominance. 2019-01-17 20:52:33 -05:00
Thomas Harte
5ceb711bd3 Allows amplitude to be specified even for a default colour burst. 2019-01-17 20:47:42 -05:00
Thomas Harte
4748b09721 Ensures safe OpenGL shutdown. 2019-01-17 20:44:18 -05:00
Thomas Harte
d593796dae Reintroduces less-filtered black and white video where there's no colour burst. 2019-01-16 22:22:29 -05:00
Thomas Harte
ef0dbc2a41 Undoes hard-coding of target framebuffer and display gamma. 2019-01-15 21:33:30 -05:00
Thomas Harte
6c49953115 Returns gamma correction, and corrects Amstrad CPC brightness. 2019-01-14 22:56:08 -05:00
Thomas Harte
55290f4dad Attempts a fix of frame_was_complete_ logic, to try to eliminate black flashes. 2019-01-14 21:42:45 -05:00
Thomas Harte
f373a3fbb1 Merge branch 'TrigonometricDecode' into ScanTarget 2019-01-13 23:08:21 -05:00
Thomas Harte
bb03d2f2ad Removes redundant enumeration. 2019-01-13 23:07:50 -05:00
Thomas Harte
82922aa2c7 Merge pull request #585 from TomHarte/TrigonometricDecode
Collapses video pipeline down to two stages.
2019-01-13 23:07:09 -05:00
Thomas Harte
7aec5be61a Cleans up and simplifies shader creation. 2019-01-13 22:49:01 -05:00
Thomas Harte
2ef6d4327c Resolves further build warnings. 2019-01-13 20:37:50 -05:00
Thomas Harte
cc95e587db Adds virtual destructors for various interface classes. 2019-01-13 19:19:01 -05:00
Thomas Harte
e89e55a9bb Attempts to factor actual composite amplitude into output. 2019-01-13 14:45:17 -05:00
Thomas Harte
7c2c243985 Corrects sample spacing, and removes a lot of detritus. 2019-01-12 18:36:54 -05:00
Thomas Harte
25a1f23fc0 Takes a first shot at re[re,re]-implementing composite colour decoding. 2019-01-12 17:59:24 -05:00
Thomas Harte
27541196cc Corrects Luminance8Phase8 and PhaseLinkedLuminance8 composite encodings. 2019-01-11 22:46:50 -05:00
Thomas Harte
5d9521fcb9 Advances back to a semi-complete monochrome composite output.
i.e. composite phase and amplitude is ostensibly flowing to its new destination.
2019-01-11 22:02:15 -05:00
Thomas Harte
ccb52fb625 Ensures no writes to pixel_pointer_ when allocation has failed. 2019-01-11 22:00:44 -05:00
Thomas Harte
028e530232 Shunts output shader to its proper place. 2019-01-06 22:59:14 -05:00
Thomas Harte
906a2ff6eb Switches to using clock times for buffer merging and output. 2019-01-06 18:47:01 -05:00
Thomas Harte
248a8efd2f Corrects declared pixel clock GCD. 2019-01-06 16:32:13 -05:00
Thomas Harte
c392c819c1 Switches to using the announce is_visible flag to spot line ends. 2019-01-06 13:37:34 -05:00
Thomas Harte
e9d9ff0da0 Enhances ScanTarget to provide additional timing information. 2019-01-05 23:09:17 -05:00
Thomas Harte
46d756d298 Starts towards a flattening of the intermediate video processing.
Immediate issue: using x position to index into a bitmap sampled at the input data rate doesn't allow for the disconnection between input rate and output speed provided by the flywheels.
2019-01-05 18:11:39 -05:00
Thomas Harte
fd0ffc7085 Attempts an initial flattening of the pipeline, seemingly losing all output. 2019-01-01 21:02:21 -05:00
Thomas Harte
601961deeb Wires through set_display_type. 2018-11-29 20:44:21 -08:00
Thomas Harte
557a2a0ddf Moves pipeline setup into draw(), where there'll definitely be an OpenGL context. 2018-11-29 19:41:54 -08:00
Thomas Harte
b723740f64 Improves PAL colours. 2018-11-29 19:12:20 -08:00
Thomas Harte
6be46ae921 Mostly restores Atari 2600 output. PAL colours need work. 2018-11-29 18:26:05 -08:00
Thomas Harte
a25470ee41 Permits tweaking of PhaseLinkedLuminance8 sampling offset. 2018-11-29 16:29:28 -08:00
Thomas Harte
fd579a019b Introduces a new scan source data type, motivated by the reasoning used by the Oric.
Specifically: it'll allow PCM sampling of the potentially arbitrary composite generation logic of various machines.
2018-11-28 20:40:22 -08:00
Thomas Harte
e39ecf59ef Restores RGB mode to the Oric. More thought required for composite. 2018-11-28 18:40:43 -08:00
Thomas Harte
5f90941e4e Starts nudging the Oric back to functionality under the new regime.
i.e. one where it can't invent internal pixel formats.
2018-11-28 18:16:13 -08:00
Thomas Harte
64465f97b6 Starts towards reintroducing the proper mechanisms for selecting a display type at runtime. 2018-11-28 17:53:33 -08:00
Thomas Harte
aa22af6f05 Corrects regression in VDP type selection. 2018-11-26 22:40:01 -05:00
Thomas Harte
a6383247fc Attempts further to ensure proper CRT signalling. 2018-11-26 22:36:22 -05:00
Thomas Harte
d45c2a1f28 Settles, at least for now, on 15-tap notch filtering. 2018-11-26 22:34:31 -05:00
Thomas Harte
61a63a673c Adds a negative operator. 2018-11-26 22:34:04 -05:00
Thomas Harte
5618288459 Reduces visible area, producing a tighter crop. 2018-11-25 22:32:12 -05:00
Thomas Harte
b69ac4ec2f Ensures video stability is no longer affected by transient allocation failures. 2018-11-25 22:04:04 -05:00
Thomas Harte
f3174069fa Attempts a linear comb filter for YC separation, plus post-separation Y filtering. 2018-11-25 21:54:12 -05:00
Thomas Harte
cd1e796093 Attempts to add clearing of the destination framebuffer too. 2018-11-24 23:31:56 -05:00
Thomas Harte
dd4af4f0df Removes dead files. 2018-11-24 22:40:06 -05:00
Thomas Harte
76656fab23 Applies harsher filtering. 2018-11-24 22:39:53 -05:00
Thomas Harte
cf49603a9e Makes first reintroduction of colour composite decoding. 2018-11-24 22:30:39 -05:00
Thomas Harte
6c92853461 Corrects monochrome composite generation. 2018-11-24 21:55:15 -05:00
Thomas Harte
6a62cf9146 Corrects shader generation for S-Video input to S-Video output. 2018-11-24 21:40:34 -05:00
Thomas Harte
4fa6bc0ad1 Corrects S-Video decoding for most machines.
Ironically, that being those other than luminance/chrominance input machines. Further investigation required.
2018-11-24 21:30:09 -05:00
Thomas Harte
95685749ad Attempts fully to implement the S-Video pipeline, without success. 2018-11-24 18:51:07 -05:00
Thomas Harte
d7c0f0c804 Switches to an ordinary sampler for scan processing. 2018-11-24 18:03:44 -05:00
Thomas Harte
6b42b92930 Kills CRTOpenGL.cpp and simplifies shader output very slightly. 2018-11-24 17:37:58 -05:00
Thomas Harte
f4764ea680 Fixes divider. 2018-11-24 16:56:41 -05:00
Thomas Harte
538c57664f Establishes attribute bindings to allow multiple shaders to use the same vertex array. 2018-11-24 16:06:26 -05:00
Thomas Harte
a66a20f7fe Manages to get a brilliant white out of the new pipeline. 2018-11-23 22:54:52 -05:00
Thomas Harte
d4ac79b0af Attempts to introduce a full-on processing pipeline, in theory putting me two shaders away from completion.
Well, subject to finding the last flashing bug and updating the multimachine, anyway.
2018-11-23 22:34:38 -05:00
Thomas Harte
a5a3769a0f Reaches for conceptual const correctness. 2018-11-23 22:33:28 -05:00
Thomas Harte
dc4b5cc37d Effects DefaultAttenuation as an explicit default. 2018-11-23 22:33:01 -05:00
Thomas Harte
ee89be6730 Removes many stray spaces. 2018-11-23 22:32:32 -05:00
Thomas Harte
770d7e90e9 Removes stale sampling functions. 2018-11-22 22:47:29 -05:00
Thomas Harte
b9aca39eb0 Reintroduces Vic-20 output.
Resolving errors in shader generation while I'm here.
2018-11-22 22:43:42 -05:00
Thomas Harte
c0454ff101 Corrects chrominance scale. 2018-11-22 18:18:16 -05:00
Thomas Harte
a697a2e4f6 Attempts to complete all input processing — an RGB, S-Video or composite input buffer is now produced.
... for all input data types.
2018-11-22 17:20:31 -05:00
Thomas Harte
396cf72029 Renames OutputType as DisplayType and promotes it to a scan target modal. 2018-11-22 14:36:46 -05:00
Thomas Harte
bfe9704829 Reintroduces respect of each machine's nominated visible area. 2018-11-22 13:22:04 -05:00
Thomas Harte
43ee540233 Avoids race condition on .is_first_in_frame 2018-11-21 18:27:04 -05:00
Thomas Harte
817aa186c2 Revokes 'synchronous' as a function of onlyIfDirty, as it doesn't allow for double buffering. 2018-11-20 22:00:40 -05:00
Thomas Harte
38ffc4fdb3 Invalidates the stencil buffer upon buffer resizes. 2018-11-20 19:51:11 -05:00
Thomas Harte
f12d734957 Disables multisampling, since there's no way it's being helpful. 2018-11-19 23:36:29 -05:00
Thomas Harte
a70991d50e Eliminates minor gap. 2018-11-19 23:35:12 -05:00
Thomas Harte
4c00456166 Makes first attempt to draw only new lines. 2018-11-19 23:25:26 -05:00
Thomas Harte
26219213d7 Marginally increases scan size. 2018-11-18 23:03:56 -05:00
Thomas Harte
97c5ee6c0a Corrects stencil buffer creation, and edges towards using it for [guaranteed] full-screen decay. 2018-11-18 22:22:43 -05:00
Thomas Harte
75bc0e451d Reintroduces the accumulation texture.
Disables automatic clearing of the texture target, as the profiler indicates the vector instantiation to be a huge time sink.
2018-11-18 21:39:11 -05:00
Thomas Harte
6496b6313c Attempts to fix random stray noise lines. 2018-11-17 23:27:25 -05:00
Thomas Harte
c5d9bf2c12 Optimises slightly for black borders.
Specifically to help to debug proper display of unused lines in the new scan target.
2018-11-17 18:23:42 -05:00
Thomas Harte
8f05560dd7 Corrects right-edge bookending. 2018-11-17 17:46:57 -05:00
Thomas Harte
06c0c64c1a Shifts intermediate buffer sampling into the middle of each pixel row. 2018-11-17 17:31:32 -05:00
Thomas Harte
c173777d12 Extends TextureTarget so that targets can be created with a one-bit stencil. 2018-11-17 15:51:12 -05:00
Thomas Harte
16dfeb3fc8 Discards empty lines, yet makes some attempt at restoring transparency.
The two things conflict more than a little, so work to do.
2018-11-15 21:51:27 -05:00
Thomas Harte
5a31891048 Returns Amstrad CPC output.
Which is probably it until I get some more composite processing back in.
2018-11-15 21:32:22 -05:00
Thomas Harte
8b37496447 Restores video output to the Master System. 2018-11-15 21:21:54 -05:00
Thomas Harte
8f6664f0d7 Starts towards picking an input shader based on data type and pipeline. 2018-11-15 21:02:46 -05:00
Thomas Harte
15b1176841 Ensures no border output if space is not allocated. 2018-11-14 22:32:33 -05:00
Thomas Harte
3eab1f8f7c Removes a little cruft. 2018-11-14 22:26:31 -05:00
Thomas Harte
9dff13cbbf Re-establishes output from the machines with 9918s and derivatives. 2018-11-14 22:25:19 -05:00
Thomas Harte
a47de9a884 Returns the Apple II to submitting video. 2018-11-14 22:04:57 -05:00
Thomas Harte
8a699b6072 Kills setup_output definitively, saving some indirection. set_scan_target takes its place. 2018-11-14 21:52:57 -05:00
Thomas Harte
87df8b9e85 Makes an attempt at pre-emptive line buffer clearing. 2018-11-14 21:19:14 -05:00
Thomas Harte
91b19c5c70 Adds bookending, and finally kills the TextureBuilder. Farewell. 2018-11-14 20:49:06 -05:00
Thomas Harte
0487580a1a Corrects initial state of is_drawing_ and expands lines to full display. 2018-11-14 20:10:38 -05:00
Thomas Harte
3dca836571 Ensures no overflow, and adds a couple of consts. 2018-11-14 20:09:57 -05:00
Thomas Harte
6ba02c44d0 Better binds buffer sizes. 2018-11-13 23:08:51 -05:00
Thomas Harte
bf3ab4e260 Proceeds as drawing to the unprocessed line buffer and drawing from it.
Very, very slowly, and without yet clearing.
2018-11-13 21:15:33 -05:00
Thomas Harte
02f9cada43 Communicates the colour subcarrier frequency, and uses it to pick a buffer width. 2018-11-13 18:33:44 -05:00
Thomas Harte
654a19ea15 Switches back to working on the scan shaders.
Pixels from the emulated machine are now starting to appear.
2018-11-12 22:52:26 -05:00
Thomas Harte
ecb5504bd1 Switches enable_vertex_attribute_with_pointer to silent failure (versus glGetError). 2018-11-12 22:51:44 -05:00
Thomas Harte
2adf3d353e Subtracts retrace periods from output scale. 2018-11-12 20:20:09 -05:00
Thomas Harte
3045e85004 Ensures redraws when resizing; declines to busy wait otherwise. 2018-11-12 20:15:38 -05:00
Thomas Harte
e9d1afd515 Appears to demonstrates that the line buffer is approximately working. 2018-11-12 19:10:48 -05:00
Thomas Harte
833ab7945b Slow steps towards switching to line output. 2018-11-12 18:56:54 -05:00
Thomas Harte
0af1d668a6 Takes a first step towards generality, and thereby starts submitting lines. 2018-11-12 18:47:55 -05:00
Thomas Harte
0ac62e3805 Flips and properly sizes output scans. 2018-11-12 18:28:09 -05:00
Thomas Harte
938d09f34a Corrects scan outline generation. 2018-11-12 18:23:45 -05:00
Thomas Harte
dce52d740d Finally gets some pixels back on screen.
For now, just the raw scans, direct to the framebuffer, with no intermediate processing. But it seems to prove that at least some of the proper data is reaching the GPU.
2018-11-11 23:23:42 -05:00
Thomas Harte
3ae333fa84 Edges further towards reviving the shaders. 2018-11-11 21:41:13 -05:00
Thomas Harte
d5af1f3948 Removes some migrated work. 2018-11-11 16:22:14 -05:00
Thomas Harte
0ba3ae53ab Connects up the necessary recording to use intermediate composite buffers. 2018-11-11 15:20:18 -05:00
Thomas Harte
be12d78c83 Corrects vertical event announcement, and adjusts namespaces for OpenGL primitives. 2018-11-11 15:11:32 -05:00
Thomas Harte
b70227ac1b Ensures proper write area locations end up in the scans. 2018-11-10 21:10:33 -05:00
Thomas Harte
6d277fecd5 Makes ScanTarget a little more communicative and orthogonal. 2018-11-10 19:52:57 -05:00
Thomas Harte
491817d85c Corrects allocation error and begins submitting raw textures. 2018-11-08 23:02:36 -05:00
Thomas Harte
20faf4e477 Adds submission of scans to the GPU. 2018-11-08 22:21:11 -05:00
Thomas Harte
4fe5c7c24e Conspires to handle multithreading side of things in a lockless fashion.
At least on x86-64.
2018-11-08 21:57:28 -05:00
Thomas Harte
36bf640c6f Acts as if it is going to submit scans, at least. 2018-11-07 22:53:46 -05:00
Thomas Harte
7881e40e0b Shuffles the OpenGL primitives into their own collection. 2018-11-07 19:11:01 -05:00
Thomas Harte
55da1e9c0f Simplifies semantics a little and starts accepting a single buffer of pixel data. 2018-11-06 22:23:38 -05:00
Thomas Harte
9799aa0975 Completes documentation and rounds out implementation. 2018-11-04 22:17:33 -05:00
Thomas Harte
1effb97b74 Reintroduces colour phase acquisition from the colour burst. 2018-11-04 21:57:46 -05:00
Thomas Harte
eb28095041 Ensures proper accumulation and reporting of colour phase across lines. 2018-11-04 21:44:22 -05:00
Thomas Harte
014da41471 Ensures scan positions are communicated with a specified range, and switches manner of pixel clock communication. 2018-11-04 21:06:25 -05:00
Thomas Harte
0446e350d3 Resolves sizing of texture coordinates, and improves constness slightly. 2018-11-03 23:51:26 -04:00
Thomas Harte
05fb7db147 Reduces CRT chattiness. 2018-11-03 23:47:41 -04:00
Thomas Harte
f6562de325 Possibly adds enough for the Electron and ZX80 to start outputting dummy lines.
Let's see!
2018-11-03 23:40:39 -04:00
Thomas Harte
b40211d2c0 Starts to bend 'CRTMachine' to a world farther from owning the GPU relationship. 2018-11-03 21:54:25 -04:00
Thomas Harte
da4d883321 Adds first, incomplete attempts to talk to a ScanTarget from the CRT.
Does away with the hassle of `unsigned` while I'm here; that was a schoolboy error.
2018-11-03 19:58:44 -04:00
Thomas Harte
373820f080 Attempts to establish interface to decouple scan output from generation.
Restores some functionality that had dropped out in the interim: diagonal scans, decoupling of scan scaling from timing of the composite subcarrier.
2018-10-30 21:50:35 -04:00
Thomas Harte
6e517983b9 Merge branch 'master' into ScanTarget 2018-10-30 18:15:55 -04:00
Thomas Harte
b9a752fda1 Merge pull request #580 from TomHarte/NameInitialisation
Ensures offset and flags are initialised to 0.
2018-10-29 22:10:28 -04:00
Thomas Harte
f65d80b7d1 Ensures offset and flags are initialised to 0.
This prevents a potential crash at startup.
2018-10-29 22:09:32 -04:00
Thomas Harte
4701aa149a Adds first draft of an interface to separate CRT logic from the GPU-side stuff. 2018-10-29 22:08:17 -04:00
Thomas Harte
0d051502e2 Merge pull request #579 from TomHarte/MasterSystemOfficial
Promotes the Master System to full mention.
2018-10-26 21:25:09 -04:00
Thomas Harte
2e28a8e51c Promotes the Master System to full mention. 2018-10-26 21:24:40 -04:00
Thomas Harte
4af0b74a42 Merge pull request #578 from TomHarte/SMSBIOSFallback
Attempts to carry on even if no BIOS is found.
2018-10-26 21:20:43 -04:00
Thomas Harte
d1fc39d6e5 Attempts to carry on even if no BIOS is found. 2018-10-26 21:19:16 -04:00
Thomas Harte
4f0d324a6b Merge pull request #577 from TomHarte/9918RandomStart
(Mostly) randomises the 9918 start position.
2018-10-26 21:10:54 -04:00
Thomas Harte
8652d8b23d (Mostly) randomises the 9918 start position. 2018-10-26 21:02:56 -04:00
Thomas Harte
e02aa885d8 Testing against the ColecoVision suggests this is probably always 7. 2018-10-26 20:59:12 -04:00
Thomas Harte
1fc9356796 Merge pull request #576 from TomHarte/CRAMDots
Adds display of CRAM dots and enforces VRAM delays.
2018-10-26 20:32:00 -04:00
Thomas Harte
bb09762029 Introduces extra delays to VRAM access. 2018-10-26 20:19:08 -04:00
Thomas Harte
05a5c7120e Shunts CRAM dots into their proper place. 2018-10-26 20:06:51 -04:00
Thomas Harte
521d603902 Adds a first attempt at CRAM dot output. With a TODO. 2018-10-26 19:26:46 -04:00
Thomas Harte
916710353a Makes it explicit that I want the reference. 2018-10-25 23:18:34 -04:00
Thomas Harte
53b00dea3f Adds missing include. 2018-10-25 23:12:41 -04:00
Thomas Harte
0587b9f257 Edges to within millimetres of CRAM dots.
... but all the way up to bedtime.
2018-10-25 23:12:03 -04:00
Thomas Harte
9621ba59ae Merge pull request #574 from TomHarte/MulticolourMode
Fixes broken implementation of 9918 multicolour mode.
2018-10-24 22:41:18 -04:00
Thomas Harte
5accd8cf08 Fixes broken implementation of 9918 multicolour mode. 2018-10-24 22:40:38 -04:00
Thomas Harte
38c130df2b Merge pull request #573 from TomHarte/SmallKeyboard
Extends the concept of a 'keyboard' to sets of keys less than a full keyboard in size
2018-10-24 22:32:55 -04:00
Thomas Harte
8730ffb4e2 Restores multi-machine keyboard propagation. 2018-10-24 22:20:58 -04:00
Thomas Harte
a8645f80bf Introduces 'non-exclusive' emulator-space keyboards.
i.e. sets of keys that don't amount to an entire keyboard in the modern sense. Experimentally used by the Master System for its reset key.
2018-10-24 21:59:30 -04:00
Thomas Harte
278585fd94 Merge pull request #572 from TomHarte/TallModeSprites
Fixes sprite list termination in 224- and 240-line modes.
2018-10-24 19:56:42 -04:00
Thomas Harte
d61c3a9442 Fixes sprite list termination in 224- and 240-line modes. 2018-10-24 19:53:46 -04:00
Thomas Harte
2cdeaa2575 Moves misplaced bracket. 2018-10-23 22:37:19 -04:00
Thomas Harte
286783e880 Accepts GCC's suggestion of extra clarity brackets. 2018-10-23 22:36:23 -04:00
Thomas Harte
f7b0f1af70 Merge pull request #571 from TomHarte/DisplaySelection
Adds composite/RGB selection for the Master System.
2018-10-23 22:32:59 -04:00
Thomas Harte
f69cb28933 Reverts accidental project configuration change. 2018-10-23 22:32:05 -04:00
Thomas Harte
e3fd63b2d7 Adds composite/RGB selection for the Master System. 2018-10-23 22:30:24 -04:00
Thomas Harte
6cb956d1d6 Merge pull request #570 from TomHarte/TecToyEtc
Separates request for an SMS2 VDP from current graphics mode.
2018-10-23 22:20:11 -04:00
Thomas Harte
00e7958a97 Separates request for an SMS2 VDP from current graphics mode.
Thereby fixes various minor segments of Codemasters games.
2018-10-23 22:19:45 -04:00
Thomas Harte
cba8e6814f Merge pull request #569 from TomHarte/224px
Adds 'full' support for 224- and 240-line SMS modes
2018-10-23 21:22:19 -04:00
Thomas Harte
2f995eb622 Adjusts vertical timing for display height. 2018-10-23 21:20:44 -04:00
Thomas Harte
90fbad0f1c Implements SMS2-style addressing if in a 224 or 240-line mode.
This isn't quite accurate, but it'll do for development.
2018-10-23 20:30:08 -04:00
Thomas Harte
2cbd28478d Allows the sprite terminator to be specified. 2018-10-23 20:01:47 -04:00
Thomas Harte
7eeefd2602 Ensures LOGs look like statements even in release builds. 2018-10-22 22:37:11 -04:00
Thomas Harte
1331457314 Merge pull request #567 from TomHarte/VDPDelay
Slightly adjusts pixel output time.
2018-10-22 22:10:47 -04:00
Thomas Harte
7855145ebd Slightly adjusts pixel output time.
i.e. respective to reading; sprite collision times now seem correct.
2018-10-22 19:58:33 -04:00
Thomas Harte
6ab30e9cac Adds a mention of the Master System.
Given that Mac users are only one constituency now; others are directly tracking the repository.
2018-10-22 13:47:43 -04:00
Thomas Harte
027e9c7816 Merge pull request #563 from TomHarte/ResizeCrash
Corrects likely crash shortly after starting a TMS9918 or derivative
2018-10-22 10:19:20 -04:00
Thomas Harte
7c65cfd932 Adds default values for WriteArea. 2018-10-21 21:18:54 -04:00
Thomas Harte
883680731a Uses explicit state to determine whether a pixel target has been requested. 2018-10-21 21:18:41 -04:00
Thomas Harte
fb3171f366 Merge pull request #562 from TomHarte/TimingTweaks
Corrects residual Master System interrupt timing issues.
2018-10-21 18:47:25 -04:00
Thomas Harte
c07f9fed99 Corrects test and implementation to pass the exhaustive VDP interrupt prediction test. 2018-10-21 18:42:49 -04:00
Thomas Harte
616777517d Makes the failing test more communicative, in the hope of more easily debugging errors. 2018-10-21 14:35:44 -04:00
Thomas Harte
b3f1677da5 Introduces new failing test for rational continuous interrupt prediction. 2018-10-21 13:59:14 -04:00
Thomas Harte
16f08eb654 Slightly tweaks Master System timing numbers. 2018-10-21 13:58:34 -04:00
Thomas Harte
a38974ef2e Merge pull request #559 from TomHarte/RowPhase
Corrects row reporting for modes other than 192-line NTSC
2018-10-20 18:28:26 -04:00
Thomas Harte
725b364bbc Improves testing; now tests for time to the first interrupt. 2018-10-20 18:25:55 -04:00
Thomas Harte
30b99f0049 Fixes a couple of interrupt prediction errors. 2018-10-20 18:25:28 -04:00
Thomas Harte
b61de65b43 Restores proper phase with the CPU. 2018-10-19 23:18:16 -04:00
Thomas Harte
0822c96ce0 Implements the proper row counter values for > 192 row modes. 2018-10-19 22:37:56 -04:00
Thomas Harte
3b164e5ffe Adds missing #includes. 2018-10-19 22:20:23 -04:00
Thomas Harte
d5f1e76707 Merge pull request #558 from TomHarte/CodemastersDetection
Implements the Codemasters paging scheme
2018-10-19 22:11:33 -04:00
Thomas Harte
f49718e94b Ensures Codemasters games have the proper initial state. 2018-10-19 22:10:14 -04:00
Thomas Harte
b18db78cce Merge pull request #557 from TomHarte/MasterSystemScreenshot
Sneaks in a Master System screenshot.
2018-10-19 21:57:07 -04:00
Thomas Harte
c39fd17e54 Corrects extension. 2018-10-19 21:55:35 -04:00
Thomas Harte
78fff5bdd9 Sneaks a Sonic picture into the readme.
Without even yet claiming to be a Master System emulator, given that I've still quite a few things to do there.
2018-10-19 21:54:21 -04:00
Thomas Harte
6fff514901 Honours the region by implementing Japanese (no BIOS) and European (PAL) paths. 2018-10-19 21:37:05 -04:00
Thomas Harte
f9a6c00493 Makes first attempt to support PAL timings. 2018-10-19 21:36:13 -04:00
Thomas Harte
fa77d81813 Corrects test for whether to consider a European or American region. 2018-10-19 21:35:52 -04:00
Thomas Harte
f0b6c406ff The Sega static analyser now attempts to differentiate region and paging scheme. 2018-10-19 20:32:09 -04:00
Thomas Harte
c365cca38a Makes order of operations explicit. 2018-10-18 22:37:04 -04:00
Thomas Harte
4cd65eab5c Seeks to avoid bad macro expansion. 2018-10-18 22:36:25 -04:00
Thomas Harte
2ee360e6ba Merge pull request #553 from TomHarte/MasterSystemVDP
Adds Initial Sega SG1000 and Master System emulation
2018-10-18 22:30:57 -04:00
Thomas Harte
9bc09046c0 Attempts to ensure that sprites can go off the top of the screen. 2018-10-18 21:48:57 -04:00
Thomas Harte
10d9cbdeb1 Adds an extra LOG to track the memory map as a potential cause of emulation failure. 2018-10-18 21:48:37 -04:00
Thomas Harte
57f03e660c Ensures console output only in debug builds. 2018-10-18 21:16:56 -04:00
Thomas Harte
512f085891 Ensures proper left clipping of sprites. 2018-10-18 21:14:16 -04:00
Thomas Harte
6a2db52adb Ensures safe Megacart cartridge sizes too. 2018-10-18 21:09:05 -04:00
Thomas Harte
34e13d0d4d Clears top bit when reading the keypad and ensures no undefined behaviour reading the cartridge. 2018-10-18 21:05:58 -04:00
Thomas Harte
da00c832f5 Corrects colour fetching for multicolour text mode. 2018-10-18 20:38:00 -04:00
Thomas Harte
8ff265c3a1 Corrects multicolour text mode. 2018-10-18 20:25:42 -04:00
Thomas Harte
0278d5b61c Restores SG1000 compatibility. 2018-10-18 19:13:15 -04:00
Thomas Harte
1fc88c4eff Corrects off-by-one error in line fetching coroutines. 2018-10-16 21:36:31 -04:00
Thomas Harte
58ca74c68a Resolves right-side TMS sprite droppages. 2018-10-16 21:25:08 -04:00
Thomas Harte
b4f871a2ef Corrects first line sprite row selection. 2018-10-16 21:16:29 -04:00
Thomas Harte
0f7bf6d6c6 Resolves attempt to output graphics on the line one before the display. 2018-10-16 21:02:31 -04:00
Thomas Harte
5dfe7d8596 Corrects most of TMS sprite drawing. 2018-10-16 20:49:04 -04:00
Thomas Harte
231009b901 Makes faulty attempt to reintroduce TMS-mode sprites. 2018-10-16 20:00:06 -04:00
Thomas Harte
1c5f939aea Reintroduces tiles and some element of sprites in regular TMS mode. 2018-10-14 21:52:13 -04:00
Thomas Harte
c1e6406fc9 Corrects sprite accumulation. 2018-10-14 19:56:09 -04:00
Thomas Harte
d66979c68f Switched to a very large number of buffers, and resolved stupid attempt to reassign a reference. 2018-10-14 18:19:11 -04:00
Thomas Harte
6c09abc6cb Makes a flawed attempt to reformulate this exactly as two separate processes on a common clock with an interchange buffer.
Specifically because closer inspection of the TMS modes shows it isn't quite valid to model output of one line as having fully completed prior to fetching of the next. So some sort of extra buffer is required. At which point it is most natural to continue with the logic that each fetch routine is oriented around the fetching process for a single line, and each output routine has the same view, suggesting separate read/write addresses.

Something is wrong though, as video data is being output too rapidly (I think) and with occasional sync issues (again: subject to investigation).
2018-10-14 16:23:45 -04:00
Thomas Harte
9e52ead09a Ensures sprite scanning doesn't improperly set collision flag; that slot 151 is filled. 2018-10-12 19:50:48 -04:00
Thomas Harte
9ab0c54426 Eliminates faulty attempt to satisfy SMSVDP vertical counter test. 2018-10-12 18:57:07 -04:00
Thomas Harte
f6af6778ab Moves scrolling latch to proper position and implements 4-window fetching offset. 2018-10-11 22:36:27 -04:00
Thomas Harte
6a94dda60d Selects potentially-correct interrupt times. 2018-10-11 21:42:09 -04:00
Thomas Harte
82b7944599 Fixes horizontal counter wrapping. 2018-10-11 20:37:29 -04:00
Thomas Harte
52e02db5c8 Introduces horizontal counter latching and reading.
Then makes a new guess at frame IRQ position. But gets it wrong. Hmmm.
2018-10-11 19:56:32 -04:00
Thomas Harte
9a933993f5 Added TODO. 2018-10-10 22:17:17 -04:00
Thomas Harte
062b2ae8d3 Corrects calculation of [NTSC, 192 line] current row. 2018-10-10 22:15:38 -04:00
Thomas Harte
9f69dbf31a Adds half-updating of RAM pointer.
This emulator now passes the first screen of the SMS VDP test.
2018-10-10 21:59:08 -04:00
Thomas Harte
63fb3f03d1 Corrects address loading upon accesses of registers other than 0. 2018-10-10 21:47:48 -04:00
Thomas Harte
2e379b0834 Adds latching of scroll values. 2018-10-10 21:28:18 -04:00
Thomas Harte
f00f6c8c23 Allows the frame interrupt to be placed anywhere in the frame. 2018-10-10 21:07:39 -04:00
Thomas Harte
50e23f4a2e Fixes 16px-high sprites. 2018-10-10 20:34:00 -04:00
Thomas Harte
acdc84e08c Improves test slightly, and fixes line interrupt reload value setting. 2018-10-09 22:14:35 -04:00
Thomas Harte
c128ddb549 Introduces a first unit test for line interrupts and corrects backup behaviour. 2018-10-09 21:49:21 -04:00
Thomas Harte
dccf17e770 Makes a first serious attempt at Master System line interrupts. 2018-10-09 20:51:09 -04:00
Thomas Harte
2d8ab72e22 Fixed proper starting position for (interrupted) tile drawing. 2018-10-08 23:13:37 -04:00
Thomas Harte
748366c70e Corrects buffer overrun when the horizontal scroll lock is on. 2018-10-08 23:06:22 -04:00
Thomas Harte
7a74fe2ff7 Corrects tile plotting window and eliminates a redundant local. 2018-10-08 22:56:31 -04:00
Thomas Harte
e410302237 Switches to real SMS line output composition.
Including setting the sprite collision bit.
2018-10-08 22:43:10 -04:00
Thomas Harte
bca2161a05 Fixes TMS text mode for the new addressing order. 2018-10-07 21:09:01 -04:00
Thomas Harte
5f789092be Flips sprite priority in the temporary renderer.
The better to test other issues in the interim.
2018-10-07 19:16:35 -04:00
Thomas Harte
6975ed22c0 Doubles down on address-storage format, and implements the vertical scrolling lock. 2018-10-07 18:55:35 -04:00
Thomas Harte
24644f1dd1 Adds a low-pass filter, picked entirely by ear, and switches to composite output, at least for now. 2018-10-07 18:39:03 -04:00
Thomas Harte
3bead07043 Introduces proper indirection for sprite patterns.
This seems to work, so the onus is now back on the rendering loop.
2018-10-07 17:15:42 -04:00
Thomas Harte
ee20e42372 Makes initial attempt at collecting sprite contents.
With test plotting, indicating some sort of issue.
2018-10-07 16:53:25 -04:00
Thomas Harte
df411b4ede Corrects storage of visible sprites. 2018-10-07 16:40:32 -04:00
Thomas Harte
bfb9d8ccb6 At least attempts to use proper addressing for sprite info fetches. 2018-10-07 14:32:20 -04:00
Thomas Harte
338aec2930 Groups background fetches and experimentally seeks to daub sprites as white. 2018-10-06 22:07:04 -04:00
Thomas Harte
e6510dc87b Attempts to get at least as far as picking visible sprite indices. 2018-10-06 19:27:19 -04:00
Thomas Harte
76f3b9f6ba Fixed: paging writes don't obstruct RAM. 2018-10-06 14:26:00 -04:00
Thomas Harte
7830cda912 Implements line querying and most of line interrupts. 2018-10-04 22:50:35 -04:00
Thomas Harte
aac97a8983 Re-revokes fine scroll on the top two lines when requested. 2018-10-04 19:18:15 -04:00
Thomas Harte
ca26dfcd61 Correct Master System palette writes. 2018-10-04 19:12:31 -04:00
Thomas Harte
858721a7a5 Added left border hiding. 2018-10-04 18:52:23 -04:00
Thomas Harte
89db1d6a6a Switches to a more accurate means of left-padding. 2018-10-04 18:44:49 -04:00
Thomas Harte
de4e5c40aa Implements horizontal scrolling lock. 2018-10-03 23:28:33 -04:00
Thomas Harte
05248ab990 Starts to reimplement Master System output. 2018-10-03 23:13:21 -04:00
Thomas Harte
252f47a425 Ensures no pixel output on line one before end, and adds a temporary debugging test. 2018-10-02 22:59:20 -04:00
Thomas Harte
be52b31b5c Attempts fully to revive text mode. 2018-10-02 22:05:58 -04:00
Thomas Harte
23c3fa6993 Fixed: it's the SMS that has 8 sprites, not text mode (which has none). 2018-10-02 22:01:43 -04:00
Thomas Harte
499fc62187 Sets things up for implementation of the inner mode-specific logic. 2018-10-02 21:58:09 -04:00
Thomas Harte
1dd5272190 Ensures real-time output of all areas, to ensure proper palette response. 2018-10-02 21:18:28 -04:00
Thomas Harte
5361120353 Restores a stable frame. 2018-10-02 21:05:30 -04:00
Thomas Harte
60bab8fdf1 Starts to reformulate TMS collection as coroutines.
For the time being, thereby breaks all video. A static screen of the border colour is all you'll see.
2018-10-01 23:03:17 -04:00
Thomas Harte
cc99b0f532 Fixes typo. 2018-09-30 20:48:55 -04:00
Thomas Harte
91aa8f9295 Amps up colour content a little. 2018-09-30 20:47:26 -04:00
Thomas Harte
e9328d819e Switches to RGB output, at least for development. 2018-09-30 20:47:03 -04:00
Thomas Harte
48ece623e7 Adds the Sega Master System to SConstruct. 2018-09-30 20:46:38 -04:00
Thomas Harte
23191efc05 Starts writing and referring to colour RAM for colours. 2018-09-29 19:50:13 -04:00
Thomas Harte
0d8af010b6 Takes a stab at tile reversal and vertical scrolling. 2018-09-28 22:37:10 -04:00
Thomas Harte
7b9bb772ca Corrected to give a not-exactly-indexed-correctly approximation of what's on display. 2018-09-28 21:03:51 -04:00
Thomas Harte
f7e211c245 Makes first attempt to put something vaguely like the Master System tile map on screen. 2018-09-28 20:39:14 -04:00
Thomas Harte
43bcb6415b Merge branch 'master' into MasterSystemVDP 2018-09-27 22:38:15 -04:00
Thomas Harte
15ec4aaa43 Merge pull request #554 from TomHarte/65C02s
Corrects Rockwell and WDC references.
2018-09-27 22:37:45 -04:00
Thomas Harte
364859467f Corrects Rockwell and WDC references.
Also shuffles the NES CPU type up into the top position, so this is a strict progression in terms of functionality.
2018-09-27 22:36:45 -04:00
Thomas Harte
35c2e74af8 Attempts to establish a coroutine-ish structure for access patterns.
The Master System mode, inevitably, is the test case.
2018-09-27 22:33:41 -04:00
Thomas Harte
19482a563f Attempts to explicitly make room for the SMS VDP mode. 2018-09-27 21:22:57 -04:00
Thomas Harte
2e4c4c3e91 Makes some attempt to implement paging.
This causes several of the 32kb games to be recognised by the BIOS and permitted to start, so it really really may be time to stop deferring work on the VDP.
2018-09-24 21:34:42 -04:00
Thomas Harte
7515fa8a98 Ensures the SG1000 gets an unadulterated TMS and SN. 2018-09-23 22:24:29 -04:00
Thomas Harte
5b9e7213dd Adds a couple of joystick inputs.
SG1000 titles all seem to work now.
2018-09-23 21:55:07 -04:00
Thomas Harte
2253341904 This now goes far enough for the only SG1000 game I'm testing to start up.
Which hopefully gives me as much as I need to implement joypads, etc, and definitively get to just the VDP being outstanding.
2018-09-23 17:42:42 -04:00
Thomas Harte
e155dc8d6e Adds fairly standard memory map indirection. 2018-09-23 17:36:30 -04:00
Thomas Harte
00b2db4fb9 Ensures the Master System is informed when it should pretend to be an SG1000. 2018-09-23 16:34:47 -04:00
Thomas Harte
f59386f523 Adds just enough input logic that the Sega sound now plays. 2018-09-23 16:05:37 -04:00
Thomas Harte
9683c8f664 Advances towards the Master System actually receiving interrupts. 2018-09-23 15:58:23 -04:00
Thomas Harte
38a1fde3bf Attempts to permit Master System interrupts. 2018-09-23 00:07:46 -04:00
Thomas Harte
40c7a63fb5 Makes a first attempt at Master System IO decoding. 2018-09-22 23:45:29 -04:00
Thomas Harte
d9e65cd758 Ensures neither the ColecoVision nor the MSX processes mid-cycles. 2018-09-21 22:53:35 -04:00
Thomas Harte
e511261b04 Adds a Master System class, so that SMSs can end up somewhere. 2018-09-21 22:13:07 -04:00
Thomas Harte
0d01346ad4 Advertises SMS support and goes as far as realising it needs to spawn a Master System. 2018-09-20 22:04:28 -04:00
Thomas Harte
e7f4babf41 Starts taking steps towards SMS/GG and V9938/9958 support.
Specifically: routine namespace stuff, plus the intention to move to a table-based operation+cost version of timing. Reordering works fine for the TMS, and probably would also for the SMS/GG, but it'd be problematic with the command engine of the V9938/9958 and maintaining a consistent set of code is easier.
2018-09-17 22:59:16 -04:00
Thomas Harte
a29a8e292b Merge pull request #552 from TomHarte/AppleIIBrightness
Turns down the Apple II brightness a little.
2018-09-17 20:09:06 -04:00
Thomas Harte
a8b116e217 Turns down the Apple II brightness a little.
So that light blue is more like a blue.
2018-09-17 20:08:20 -04:00
Thomas Harte
e582b4c8ca Eliminates some dangling references to iCoordinate. 2018-09-13 19:35:15 -04:00
Thomas Harte
3b70dbfebe Merge pull request #550 from TomHarte/ElectronCorruption
Resolves potential Electron output errors
2018-09-12 21:04:20 -04:00
Thomas Harte
868cd5cb09 Improves alignment request. 2018-09-12 20:27:02 -04:00
Thomas Harte
dec18d9acc Restores full pixel output to the Electron. 2018-09-12 20:25:30 -04:00
Thomas Harte
a7508bc2ae Switching explicitly to one pixel per sample eliminates the need for a bookender. 2018-09-12 20:11:17 -04:00
Thomas Harte
a38639d099 Eliminates the concept of an iCoordinate.
Real-life precision appears not to support the idea of sub-sample pixel storage.
2018-09-12 20:05:39 -04:00
Thomas Harte
c6e94bc2a6 Adds missing #include. 2018-09-11 21:55:03 -04:00
Thomas Harte
12e9478a81 Merge pull request #549 from TomHarte/MSXPaste
Simplifies and corrects MSX pasting behaviour.
2018-09-11 21:47:41 -04:00
Thomas Harte
09dafb1a79 Simplifies and corrects MSX pasting behaviour.
Now including mapping \n -> \r.
2018-09-11 21:46:28 -04:00
Thomas Harte
3358c07107 Merge pull request #548 from TomHarte/MoreBooleans
Adds 'false' and 'f' to the list of acceptable command-line option falses.
2018-09-11 20:38:11 -04:00
Thomas Harte
36ff2105fb Updates C-style (bool) casts. 2018-09-11 20:37:15 -04:00
Thomas Harte
1739d18433 Adds 'false' and 'f' to the list of acceptable refusals. 2018-09-11 20:36:49 -04:00
Thomas Harte
61272cfe20 Merge pull request #547 from TomHarte/VicUB
Fixes undefined behaviour resulting from uninitialised VIC state.
2018-09-10 22:35:14 -04:00
Thomas Harte
31b048f966 Ensures all bools start in a valid state. 2018-09-10 22:21:03 -04:00
Thomas Harte
7e29d49451 Merge branch 'master' of github.com:TomHarte/CLK 2018-09-10 21:38:38 -04:00
Thomas Harte
5445081c96 It's eight pixels that aren't written in double output mode, not four. 2018-09-10 21:38:05 -04:00
Thomas Harte
d791facc87 Update README.md
Promoted my estimation of the Apple IIs.
2018-09-10 17:57:40 -04:00
Thomas Harte
21a9bd927a Merge pull request #545 from TomHarte/LeftBorder
Adds a left gutter to complement the right.
2018-09-09 21:53:25 -04:00
Thomas Harte
d73d3b4480 Adds a left border to complement the right. 2018-09-09 21:52:48 -04:00
Thomas Harte
7b9c1bb69c Makes minor layout improvements. 2018-09-09 21:02:31 -04:00
Thomas Harte
224b3163f2 Merge pull request #544 from TomHarte/MSXColours
Corrects composition-time over-saturation.
2018-09-09 20:39:13 -04:00
Thomas Harte
fc84ae611e Resolves various instances of spaces in place of tabs. 2018-09-09 20:33:56 -04:00
Thomas Harte
22a52bdca2 Merge branch 'master' into MSXColours 2018-09-09 20:31:24 -04:00
Thomas Harte
6e9cd5cb21 Resolves over-brightness created by over-composition. 2018-09-09 20:30:43 -04:00
Thomas Harte
c73445199c Eliminates a couple of instances of manual memory management. 2018-09-09 20:29:58 -04:00
Thomas Harte
ab02f82470 Merge pull request #543 from TomHarte/CFBundleTypeOSTypes
Removes `LSItemContentTypes` so as not to reject files.
2018-09-09 17:49:16 -04:00
Thomas Harte
1e3318816c Removes LSItemContentTypes so as not to reject files. 2018-09-09 17:47:03 -04:00
Thomas Harte
4c8781c762 Increases documentation slightly. 2018-09-09 17:17:38 -04:00
Thomas Harte
3a3dec92c7 Merge pull request #540 from MaddTheSane/plistFix
Remove LSItemContentTypes
2018-09-09 10:07:19 -04:00
Thomas Harte
5a5fc1ae1a Merge pull request #541 from TomHarte/Annunciator3
Implements the two undocumented annunciator 3 graphics modes
2018-09-09 10:06:52 -04:00
Thomas Harte
8d79a1e381 Corrected fat low-res implementation.
As per comment of awanderin that "the odd addresses don't get their pixels auto-shifted by the hardware as with normal lo-res".
2018-09-09 10:06:21 -04:00
Thomas Harte
d70f5da94e Attempts an implementation of the undocumented low res + annunciator 3 graphics mode. 2018-09-08 20:51:15 -04:00
C.W. Betts
05d4274019 Remove LSItemContentTypes: they should be unique identifiers, not generic types like public.item or public.data.
This can result in strange icons showing up in the wrong places.

Also added a category type.
2018-09-07 16:39:52 -06:00
Thomas Harte
afeec09902 Gets explicit about DHIRES being annunciator 3; implements four-colour high res mode. 2018-09-06 23:23:19 -04:00
Thomas Harte
0526ac2ee2 Slightly increases const correctness.
The converters from source data to output pixels do not modify the source data. It's a shame there's no `restrict` in C++.
2018-09-05 11:36:40 -04:00
Thomas Harte
6725ee2190 Merge pull request #539 from TomHarte/40ColumnTextCorruption
Corrects 40-column alternative text mode corruption
2018-09-05 10:27:09 -04:00
Thomas Harte
8b661fb90f Introduces an extra level of indirection for text mapping. 2018-09-05 10:26:08 -04:00
Thomas Harte
dab7d3db1b Merge branch 'master' into 40ColumnTextCorruption 2018-08-30 20:24:47 -04:00
Thomas Harte
1cba3d48d9 Merge pull request #538 from TomHarte/AppleDecodingAgain
Correction: 0xc011 et al get the keyboard value in bits 0 to 6...
2018-08-30 20:19:48 -04:00
Thomas Harte
d53b38ec7e Correction: 0xc011 et al get the keyboard value in bits 0 to 6 and the switch value in bit 7. 2018-08-30 20:18:36 -04:00
Thomas Harte
5d0f47eda2 Merge pull request #536 from TomHarte/AppleDecoding
Adds mirrors for keyboard input and the audio toggle.
2018-08-27 21:14:48 -04:00
Thomas Harte
2e04c4442c Adds mirrors for keyboard input and the audio toggle. 2018-08-27 21:14:21 -04:00
Thomas Harte
f639cdc8ad Merge pull request #535 from TomHarte/DSKFixes
Corrects Apple DSK track length, inter-track skew, and Pro-DOS volume number.
2018-08-27 21:07:11 -04:00
Thomas Harte
71ec7624ca Corrects Apple DSK track length, inter-track skew, and Pro-DOS volume number. 2018-08-27 20:56:25 -04:00
Thomas Harte
0599d9602e Ensures no out-of-bounds accesses to inverses on a IIe. 2018-08-26 23:02:31 -04:00
Thomas Harte
234bef2a88 Adds default to make it explicit that fetch_address is initialised. 2018-08-24 22:26:03 -04:00
Thomas Harte
adb574e1cd Merge pull request #529 from TomHarte/AppleDelay
Corrects Apple II video defects
2018-08-24 22:11:41 -04:00
Thomas Harte
1f491e764e Nudges visible area slightly to the right. 2018-08-24 22:08:11 -04:00
Thomas Harte
114a43a662 Corrects improper indexing for byte shift. 2018-08-24 21:58:43 -04:00
Thomas Harte
5547c39c91 Corrects documentation. 2018-08-24 20:06:40 -04:00
Thomas Harte
97a89aaf4d Factors out the stuff of deferred action interleaving, as I suspect it'll come in handy. 2018-08-24 20:04:26 -04:00
Thomas Harte
61e46399dc About face! There should be no delay on serialisation, but a delay on interpretation-affecting soft switches. 2018-08-22 21:56:45 -04:00
Thomas Harte
e802f6ecc2 Rearranges draw loop around a fixed-size 568-sample line buffer. 2018-08-19 22:31:04 -04:00
Thomas Harte
4209f0e044 Moves memory collection into a separate loop. 2018-08-18 21:54:24 -04:00
Thomas Harte
33576aa2c4 Uses const to ensure output_* are properly constrained. 2018-08-18 21:36:48 -04:00
Thomas Harte
17bf1a64bf Moves the stuff of generating pixels out of the main loop. 2018-08-18 18:44:31 -04:00
Thomas Harte
f8d46f8f3d Merge branch 'master' into AppleDelay 2018-08-18 14:11:21 -04:00
Thomas Harte
8787d85e64 Eliminates #undefs as being (i) unnecessary, now this is a source file; and (ii) incomplete in any case. 2018-08-17 22:24:42 -04:00
Thomas Harte
7f0f17f435 Merge pull request #523 from TomHarte/Further65C02
Further corrects 65C02 behaviour
2018-08-17 21:58:38 -04:00
Thomas Harte
0e7f54f375 Implements STP and WAI, and ensures all unimplemented 65C02 instructions are NOP for all 65C02s. 2018-08-17 21:49:06 -04:00
Thomas Harte
b3bdfa9f46 Corrected: it's three-cycle 65C02 branches that ignore interrupts, not two. 2018-08-16 20:47:49 -04:00
Thomas Harte
592ec69d36 Causes the 65C02 not to accept interrupts immediately after untaken branches. 2018-08-15 22:42:04 -04:00
Thomas Harte
60e00ddd02 Correction: the test for not skipping an operand fetch requires a 65C02. 2018-08-15 22:07:17 -04:00
Thomas Harte
6806193dc2 Ensures that "Read/Modify/Write instructions absolute indexed in same page" take only six cycles on a 65C02. 2018-08-15 19:17:37 -04:00
Thomas Harte
c35dca783f Ensures that page-crossing indexing no longer causes an extra read of an invalid address on the 65C02.
It rereads the last byte of the instruction stream instead.
2018-08-15 18:47:53 -04:00
Thomas Harte
901e0d65b9 Documents all 6502 micro-operations.
Also makes sure 1-cycle NOPs really, definitely are one cycle only on a 65C02 and eliminates OperationCopyOperandFromA as a redundant copy of OperationSTA.
2018-08-14 22:17:53 -04:00
Thomas Harte
ddf45a0010 Ensures NMI and RST reset D on 65C02s. 2018-08-14 19:49:14 -04:00
Thomas Harte
1eca4463b3 Ensures NMI can no longer usurp BRK on 65C02s. 2018-08-14 19:33:48 -04:00
Thomas Harte
be01203cc1 Starts to expand the range of supported 6502s.
This fully implements the NES 6502 because, well, it's virtually no extra work, and ensures that RDY takes effect on write cycles on 65C02s.
2018-08-13 22:17:22 -04:00
Thomas Harte
4d1d19a464 Introduces an intermediate buffer for Apple II video data. 2018-08-12 20:36:08 -04:00
Thomas Harte
760817eb3b Merge pull request #521 from TomHarte/AppleVideo
Fixes Apple II double low resolution graphics
2018-08-11 23:20:40 -04:00
Thomas Harte
cb47575860 Eliminates stdout chatter. 2018-08-11 22:57:54 -04:00
Thomas Harte
434d184503 Corrects deserialisation order in double low res mode. 2018-08-11 22:53:06 -04:00
Thomas Harte
7374c665e8 Corrects regression in video flushing. 2018-08-11 19:57:39 -04:00
Thomas Harte
10c930a59d Merge pull request #520 from TomHarte/EnhancedIIe
Adds Enhanced IIe emulation.
2018-08-11 19:42:47 -04:00
Thomas Harte
60ab6f0c2a Entrusts IIe-esque character logic fully to the ROM. 2018-08-11 18:45:39 -04:00
Thomas Harte
a13eb351da Implements the Enhanced IIe, other than some text selection errors. 2018-08-11 10:26:30 -04:00
Thomas Harte
4b91910fab Removes erroneous addition. 2018-08-10 23:27:09 -04:00
Thomas Harte
f46d52364c Merge pull request #519 from TomHarte/65C02
Makes an initial pass at 65C02 emulation
2018-08-10 23:21:45 -04:00
Thomas Harte
878c63dcd2 Ensures ADC and SBC decimal take an extra cycle on the 65C02. 2018-08-10 22:52:55 -04:00
Thomas Harte
261fb3d4f8 Implements proper test for ADC/SBC 65C02 NZ, though not yet the proper timing.
This gets Klaus Dorman's test to pass.
2018-08-10 22:42:35 -04:00
Thomas Harte
b63e0cff72 Improves has-completed test. 2018-08-10 22:27:01 -04:00
Thomas Harte
5d6e479338 Implements RMB and SMB, and fixes SBC (zero). 2018-08-10 22:13:51 -04:00
Thomas Harte
90094529a5 Implements TSB and TRB, and adds the extra BIT instructions. 2018-08-10 22:04:45 -04:00
Thomas Harte
aed4c0539e Implements STZ. 2018-08-10 21:17:02 -04:00
Thomas Harte
8b50ab2593 Corrects (zero) behaviour. 2018-08-10 21:12:55 -04:00
Thomas Harte
95164b79c9 Attempted implementation of (zp) addressing mode. 2018-08-09 21:51:14 -04:00
Thomas Harte
6f838fe190 Implements INA and DEA. 2018-08-08 22:30:19 -04:00
Thomas Harte
bb680b40d8 Implements the 65C02's JMPs. 2018-08-08 22:26:57 -04:00
Thomas Harte
e3f6da6994 Implements the 65C02 NOPs. 2018-08-08 20:00:14 -04:00
Thomas Harte
e46bde35f5 Implements BBS and BBR. 2018-08-07 21:52:17 -04:00
Thomas Harte
32338bea4d Implements BRA. 2018-08-06 22:37:30 -04:00
Thomas Harte
5c881bd19d Implements PLX, PLY, PHX and PHY. 2018-08-06 22:00:23 -04:00
Thomas Harte
1a44ef0469 Introduces Klaus Dorman's 65C02 tests. All failing. 2018-08-06 21:48:43 -04:00
Thomas Harte
ebce9a2e51 Fixes test target. 2018-08-06 21:15:13 -04:00
Thomas Harte
633af4d404 The operations table is now per-instance. 2018-08-06 20:47:14 -04:00
Thomas Harte
76a73c835c Forces 6502 consumers to declare which model — the original, 65C02 or 65SC02.
All present machines use a regular 6502.
2018-08-06 20:06:07 -04:00
Thomas Harte
c1d1c451ef Merge pull request #518 from TomHarte/MacInsertDisplay
Tweaks the Mac UI
2018-08-06 19:12:17 -04:00
Thomas Harte
3be30d8c71 Tries once again to introduce file type icons. 2018-08-06 19:08:27 -04:00
Thomas Harte
d4c1244485 Adds a hint for users. 2018-08-06 18:56:59 -04:00
Thomas Harte
c61b9dca17 Ensures the Mac doesn't show the 'Insert...' option for machines that can't accept an insertion. 2018-08-06 18:52:42 -04:00
Thomas Harte
39bf682016 Adds mentions of the IIe. 2018-08-06 12:03:54 -04:00
Thomas Harte
60ac9b49ea Merge pull request #517 from TomHarte/MacInsertUI
Completes Mac UI 'Insert...' change
2018-08-05 22:58:16 -04:00
Thomas Harte
a8bb18e2cf Merge branch 'master' into MacInsertUI 2018-08-05 22:57:28 -04:00
Thomas Harte
1852786609 Merge pull request #516 from TomHarte/MacInsertUI
Adds an 'Insert...' menu option.
2018-08-05 22:51:00 -04:00
Thomas Harte
31df8c7e91 Corrects improper NSWindowController sheet stack manipulation.
As a result, 'Insert...' now seems to work properly.
2018-08-05 22:47:51 -04:00
Thomas Harte
832939f5b7 Merge branch 'master' into MacInsertUI 2018-08-05 22:39:00 -04:00
Thomas Harte
c2d9e1ec81 Merge pull request #515 from TomHarte/POPImage
Adds an Apple II screenshot to the readme.
2018-08-05 17:58:53 -04:00
Thomas Harte
673b915ee8 Reduces post-table image size a little. 2018-08-05 17:57:37 -04:00
Thomas Harte
032a62dfff Adds An Apple II screenshot to the mix. 2018-08-05 17:56:10 -04:00
Thomas Harte
f2d78182a3 Merge pull request #514 from TomHarte/VideoFixes
Fixes various IIe video deficiencies.
2018-08-05 17:49:13 -04:00
Thomas Harte
de68e70246 Fixes various IIe video deficiencies.
Specifically:
* the double-high resolution switches should be read/write; and
* the other IIe-specific switches should cause a video update for real-time effect.
2018-08-05 17:47:23 -04:00
Thomas Harte
e07447eb9a Merge pull request #513 from TomHarte/JoystickRange
Significant improves Apple II joystick compatibility
2018-08-05 17:37:38 -04:00
Thomas Harte
5cdeb58571 Makes digital to analogue conversion more extreme. 2018-08-05 17:36:20 -04:00
Thomas Harte
ce14cc8677 Flips meaning of analogue input bits, correcting most joystick titles.
Mysteriously, some functioned correctly before this. But they continue to do so.
2018-08-05 17:36:01 -04:00
Thomas Harte
bcd0479074 This in principle completes the insert action.
With the caveat that 'New...' machines seem to have blocked the window's panel queue somehow.
2018-08-05 13:12:02 -04:00
Thomas Harte
d72dd8c4ff Merge branch 'master' into macInsertUI 2018-08-05 11:54:26 -04:00
Thomas Harte
f7ce86fef8 Merge pull request #512 from TomHarte/80Text
Extends correct text handling to 80-column mode.
2018-08-04 22:28:59 -04:00
Thomas Harte
55f2fccf5e Extends correct text handling to 80-column mode. 2018-08-04 22:25:29 -04:00
Thomas Harte
c939a274be Makes first attempt to connect up an in-machine open panel. 2018-08-04 22:21:23 -04:00
Thomas Harte
101fb5d7bf Merge pull request #511 from TomHarte/ColecoSizeCheck
Relaxes ColecoVision cartridge image size check
2018-08-04 21:47:30 -04:00
Thomas Harte
3c51e335c3 Makes extra sure not to try to read from an empty characters list. 2018-08-04 21:40:26 -04:00
Thomas Harte
33ea90678c Relaxes ColecoVision cartridge size test. 2018-08-04 21:40:02 -04:00
Thomas Harte
11ae2c64ba Merge pull request #502 from TomHarte/IIe
Extends Apple II emulation to include the IIe
2018-08-04 21:02:45 -04:00
Thomas Harte
26624d7652 Fixes vertical blank signal; it should be the other way around. 2018-08-04 20:57:02 -04:00
Thomas Harte
85fb4773b0 Tweaks Apple key mapping and implements reset_all_keys. 2018-08-04 20:31:37 -04:00
Thomas Harte
099d66804e Makes colour burst phase explicit. 2018-08-04 19:29:34 -04:00
Thomas Harte
086596c28e Adds reading of vertical blank and implements the full IIe keyboard logic.
i.e. there are now two Apple keys, and shift isn't assumed.
2018-08-04 19:17:04 -04:00
Thomas Harte
3aeb4213fe Implements the C010 read value. 2018-08-04 17:57:02 -04:00
Thomas Harte
558b96bc05 Corrects IIe text display. 2018-08-04 16:52:29 -04:00
Thomas Harte
e97cc40a2c Corrects typo in Cx-page ROM paging. 2018-08-04 12:44:58 -04:00
Thomas Harte
94503ed771 Disables the macOS Apple II options panel, since it now has no options. 2018-08-04 12:37:55 -04:00
Thomas Harte
c4f86cc324 The Disk II now being its proper speed, withdraws the quickload option. 2018-08-03 21:20:21 -04:00
Thomas Harte
70c4d6b9b3 Adds a one second delay between controller and drive motor off. 2018-08-03 21:13:18 -04:00
Thomas Harte
78c7137427 Avoids observer communication if motor status hasn't changed. 2018-08-03 21:11:22 -04:00
Thomas Harte
74a2f717b3 Turns down the composite signal amplitude a little, to help colour distinctness. 2018-08-01 18:52:42 -04:00
Thomas Harte
98bb5bd9f1 Ensures flux bits are observable for two cycles rather than one; it should be 1us. 2018-07-31 23:01:11 -04:00
Thomas Harte
c91eaaf8da Takes a stab at double low-res graphics. 2018-07-31 21:45:09 -04:00
Thomas Harte
a36f37d240 Introduces a 1/14th delay in output of double high res. 2018-07-31 21:29:51 -04:00
Thomas Harte
c773d3501a Implements the INTC8ROM switch.
Finally causing the Zellyn tests to pass! Is this nightmare behind me?
2018-07-31 19:00:46 -04:00
Thomas Harte
5810f9b3f9 Fixes high resolution address range and switching logic. 2018-07-30 23:23:18 -04:00
Thomas Harte
3f56683342 Fixes order of deserialisation between auxiliary and base RAM. 2018-07-30 23:08:45 -04:00
Thomas Harte
16ccbdefd6 Of course, | has higher precedence than ?. Classic! 2018-07-30 23:08:22 -04:00
Thomas Harte
a533d09fe7 Sets the IIe as the default model. 2018-07-30 23:07:34 -04:00
Thomas Harte
e9aaa5bbdf Factors out the page-mapping function.
For one less potential source of failure.
2018-07-30 22:23:48 -04:00
Thomas Harte
ecb26e3281 Corrections: slot_C3_rom_ works the other way around; 80STORE doesn't affect most of RAM but does always affect the text screen.
Also factored out `set_zero_page_paging` for consistency.
2018-07-30 19:54:25 -04:00
Thomas Harte
5aa0b17720 Improves IIe paging further. 2018-07-29 23:02:27 -04:00
Thomas Harte
632b37ecec Attempts an implementation of auxiliary memory. 2018-07-29 10:41:12 -04:00
Thomas Harte
c905de2e40 Restores IIe ROM-over-card paging. 2018-07-28 13:31:25 -04:00
Thomas Harte
bc2afe69e1 Accepting that memory mapping on a IIe is more complicated than I anticiapted, introduces mapping for all pages.
Also picks a name for the Unenhanced Apple IIe ROM.
2018-07-28 13:02:49 -04:00
Thomas Harte
894998b163 Merge branch 'master' into IIe 2018-07-28 10:54:04 -04:00
Thomas Harte
51192d8397 Merge pull request #508 from TomHarte/Whitespace
Eliminates various blank lines.
2018-07-28 10:53:17 -04:00
Thomas Harte
3c33ccd730 Eliminates various blank lines. 2018-07-28 10:52:34 -04:00
Thomas Harte
3e35109d63 Merge pull request #507 from TomHarte/BetterBMPDestination
Use `xdg-user-dir PICTURES` instead of $HOME for screenshots
2018-07-28 10:48:28 -04:00
Thomas Harte
99c770eab4 Ensure that the output of xdg-user-dir is properly filtered. 2018-07-28 10:45:50 -04:00
Thomas Harte
34aa78b7ce Attempts to use xdg-user-dir PICTURES in preference to $HOME for pictures. 2018-07-28 09:14:18 -04:00
Thomas Harte
8cca9c2055 Merge branch 'master' into IIe 2018-07-27 23:52:39 -04:00
Thomas Harte
85ce21c79f Merge pull request #505 from TomHarte/MacScreenshots
Attempts to introduce screenshot capture for macOS.
2018-07-27 23:43:13 -04:00
Thomas Harte
d19d949b9c Removes unnecessary import. 2018-07-27 23:41:55 -04:00
Thomas Harte
1cb3713b84 Attempts to introduce screenshot capture for macOS. 2018-07-27 23:37:24 -04:00
Thomas Harte
689850d698 Merge pull request #504 from TomHarte/SDLBMPByteOrder
Ensures SDL is properly informed of buffer byte order.
2018-07-27 18:53:16 -04:00
Thomas Harte
c572a52049 Ensures SDL is properly informed of buffer byte order. 2018-07-27 18:51:38 -04:00
Thomas Harte
41765e00c4 Merge branch 'master' into IIe 2018-07-26 21:24:46 -04:00
Thomas Harte
080aa0acc5 Merge pull request #503 from TomHarte/SDLScreenshots
Adds screenshot saving upon ctrl+shift+d.
2018-07-26 20:58:35 -04:00
Thomas Harte
5e7c46a72a Adds screenshot saving upon ctrl+shift+d. 2018-07-26 20:53:12 -04:00
Thomas Harte
5f2b9b2d5a Implements the alternative zero page soft switch. 2018-07-25 22:10:21 -04:00
Thomas Harte
5c4506a9db Talks the IIe into proceeding to a beep and an improperly-formed logo. 2018-07-25 21:43:12 -04:00
Thomas Harte
55a6431fb3 Puts in enough logic to be able to launch a non-functional IIe. 2018-07-25 18:58:34 -04:00
Thomas Harte
ede2696a77 Edges further towards implementing the IIe video subsystem.
All video-specific switches are in place, and mostly honoured, and a IIe machine configuration is advertised at least.
2018-07-24 22:15:42 -04:00
Thomas Harte
59b9e39022 Starts the process of supporting the Apple IIe graphics modes.
Albeit that I'm not yet even up on the proper soft switches.
2018-07-23 22:14:41 -04:00
Thomas Harte
6b2970f2f2 Ensures no-hat input doesn't override analogue axes. 2018-07-22 17:29:37 -04:00
Thomas Harte
6a73fe7d65 Merge pull request #500 from TomHarte/MacJoysticks
Implements initial joystick support for the Mac
2018-07-22 16:56:40 -04:00
Thomas Harte
1362906f94 Wires joystick support all the way through to machines.
Ensures there's only one joystick manager, which is shared by all machines, with input going only to the key window.
2018-07-22 16:55:47 -04:00
Thomas Harte
8f4042c4bb Permits joysticks to be queried for number of fire buttons. 2018-07-22 16:52:58 -04:00
Thomas Harte
c05b6397b0 Attempts a full implementation of the joystick manager.
So it currently vends a list of existing joysticks plus their states. More work will be required for a UI — e.g. there is no way to identify one joystick from another — but this'll do for now.
2018-07-22 15:23:26 -04:00
Thomas Harte
8d18808efe Walks a few steps further along device inspection. 2018-07-20 23:33:04 -04:00
Thomas Harte
09950d9414 Gamely starts to create a HID input manager for joysticks/pads/etc. 2018-07-19 22:43:01 -04:00
Thomas Harte
badbbdf155 Merge pull request #498 from TomHarte/DisplayBorder
Resolves border issues in fullscreen mode
2018-07-16 22:01:08 -04:00
Thomas Harte
2832792fed Corrects improper use of doubles. 2018-07-16 21:55:19 -04:00
Thomas Harte
efa45b9504 Adds a right gutter to clip persistence errors.
Also uncovers and corrects a long-standing centring error.
2018-07-16 21:52:31 -04:00
Thomas Harte
523749edf8 Merge branch 'master' into DisplayBorder 2018-07-16 20:00:52 -04:00
Thomas Harte
5a0499e8a7 Merge pull request #499 from TomHarte/EditorConfig
Adds a .editorconfig to aid Github display.
2018-07-16 20:00:27 -04:00
Thomas Harte
258c8b5900 Adds a .editorconfig to aid Github display. 2018-07-16 19:59:03 -04:00
Thomas Harte
24b861f056 Eliminates make_unique as this is presently a C++11 project. 2018-07-15 22:52:36 -04:00
Thomas Harte
29f7f4d432 Adds missing #include. 2018-07-15 22:47:50 -04:00
Thomas Harte
21080a1149 Merge branch 'master' into DisplayBorder 2018-07-15 22:31:33 -04:00
Thomas Harte
1d068fd09b Merge pull request #497 from TomHarte/RobocopSprites
Ensures only the first 8px of sprites is output in 8x8 mode.
2018-07-15 22:30:42 -04:00
Thomas Harte
92065813ef Ensures only the first 8px of sprites is output in 8x8 mode.
Also adds a little extra documentation.
2018-07-15 22:21:29 -04:00
Thomas Harte
3e9ef6b8cb Adds indicator lights for the SDL port.
To complete #426
2018-07-15 20:19:06 -04:00
Thomas Harte
c9451a5382 Introduces an object for drawing OpenGL rectangles. 2018-07-14 17:42:23 -04:00
Thomas Harte
2be3b027db Merge branch 'master' into DisplayBorder 2018-07-14 13:13:29 -04:00
Thomas Harte
e339d169c5 Ensures the joystick doesn't obstruct tape input. 2018-07-12 22:10:05 -04:00
Thomas Harte
87001f86ee Merge pull request #495 from TomHarte/MSXJoysticks
Adds joystick support for the MSX.
2018-07-12 21:44:26 -04:00
Thomas Harte
58484e8f37 Adds joystick support for the MSX. 2018-07-12 21:42:47 -04:00
Thomas Harte
94f68f9d55 Merge pull request #494 from TomHarte/CustomInfoBlock
Corrects TZX custom info block parsing.
2018-07-11 22:22:49 -04:00
Thomas Harte
3f6944de54 Corrects custom info block parsing. 2018-07-11 22:21:35 -04:00
Thomas Harte
00cb4d26b3 Corrects typo. 2018-07-11 19:52:55 -04:00
Thomas Harte
774d8668bf Merge pull request #493 from TomHarte/SpecificROMs
Clarifies startup procedure for machines
2018-07-10 22:15:40 -04:00
Thomas Harte
8503589828 Corrects failure to retain OS. 2018-07-10 22:05:50 -04:00
Thomas Harte
0f95ef2059 Merge branch 'SpecificROMs' of github.com:TomHarte/CLK into SpecificROMs 2018-07-10 21:54:45 -04:00
Thomas Harte
efd812cf22 Ensures no buffer overrun when installing the OS ROM. 2018-07-10 21:54:36 -04:00
Thomas Harte
736e14c83e Ensures no buffer overrun when installing the OS ROM. 2018-07-10 21:49:38 -04:00
Thomas Harte
57f161e64c Corrects documentation of the media target. 2018-07-10 21:42:09 -04:00
Thomas Harte
0897210969 Neither cartridge machine should be a media target; their media can't be changed at runtime. 2018-07-10 21:40:13 -04:00
Thomas Harte
7e58a44771 Renames ConfigurationTarget to MediaTarget as per its newly-reduced interface. 2018-07-10 21:32:28 -04:00
Thomas Harte
e8f847d288 Fixes CRC generator used to verify Acorn programs. 2018-07-10 20:01:31 -04:00
Thomas Harte
a0f817108e Minor style fix. 2018-07-10 20:01:11 -04:00
Thomas Harte
3862fdb44c Simplifies initialisation procedure for all machines.
With the side effect of allowing every machine to try to load only the ROMs that it needs.
2018-07-10 20:00:46 -04:00
Thomas Harte
3e2d271566 Merge pull request #491 from TomHarte/CPCClip
[Re-]recalibrates CRT retrace period and affected view windows.
2018-07-05 22:38:20 -04:00
Thomas Harte
c97c5fa03a [Re-]recalibrates CRT retrace period and affected view windows.
In the hope of moving the CPC closer to the real CTM visible area.
2018-07-05 22:07:18 -04:00
Thomas Harte
fa63f7ffc3 Merge pull request #490 from TomHarte/NIBTails
Ensures NIB tracks aren't truncated
2018-07-03 21:38:31 -04:00
Thomas Harte
bfccadd356 Corrects comment typo. 2018-07-03 21:38:04 -04:00
Thomas Harte
5b3512f1df Attempts to pick an intelligent place to pad out tracks. 2018-07-03 20:10:22 -04:00
Thomas Harte
6e34e60f8a Ensures no data is dropped in transcribing a NIB to real track data. 2018-07-03 20:01:07 -04:00
Thomas Harte
a391d0f4ae Merge pull request #489 from TomHarte/OricDiskII
Simplifies disk track storage and writing implementation
2018-07-02 22:09:58 -04:00
Thomas Harte
abc5c50b2e Added some additional exposition. 2018-07-02 21:51:53 -04:00
Thomas Harte
1fcb461c42 Ensures that segments are written in a properly-circular fashion. 2018-07-02 19:35:49 -04:00
Thomas Harte
abca38a548 Makes an initial removal of PCMPatchedTrack. Farewell, old friend. 2018-07-01 22:49:57 -04:00
Thomas Harte
b4be2cd063 Implements PCMTrack::add_segment. Thereby completes PCMTrack::resampled_clone. 2018-07-01 18:28:25 -04:00
Thomas Harte
2d83eeb9c4 Further minor style improvements. 2018-07-01 17:59:43 -04:00
Thomas Harte
4d9e897cc3 Corrects addressing for deserialisation of bytes. 2018-07-01 15:58:56 -04:00
Thomas Harte
be664b5695 Ensures that start positions are properly related to sectors. 2018-07-01 15:53:48 -04:00
Thomas Harte
c3751066b7 Ensures segments are properly sized. 2018-07-01 15:43:31 -04:00
Thomas Harte
77feee8197 Applies minor style improvements. 2018-07-01 15:38:42 -04:00
Thomas Harte
f75af3b45e Adds some extra exposition. 2018-07-01 14:41:17 -04:00
Thomas Harte
1471a35bb8 Reserves a more appropriate amount of data. 2018-07-01 14:40:48 -04:00
Thomas Harte
555c2a4377 Makes a first sweep at converting the storage underlying PCMSegment to vector<bool>.
This is to remove another pain point, in preparation for the work immediately forthcoming but also work as-yet unknown.
2018-07-01 12:05:41 -04:00
Thomas Harte
16bef0dcd5 Starts the movement towards a world without PCMPatchedTrack. 2018-06-30 20:03:18 -04:00
Thomas Harte
cd464fc7de Corrects status logging. 2018-06-26 20:53:08 -04:00
Thomas Harte
5b88207477 Merge pull request #488 from TomHarte/AYClarification
Removes unused AY state and implements AND output readback.
2018-06-26 19:31:50 -04:00
Thomas Harte
df8c896193 Removes unused state and implements AND output readback. 2018-06-26 19:31:16 -04:00
Thomas Harte
5d3e1f7084 Merge pull request #487 from TomHarte/SpuriousKeyboard
Corrects a misreported value when reading the AY if not in reading mode
2018-06-25 20:49:32 -04:00
Thomas Harte
59f8eeb05a Ensures the AY goes high impedance when not in read mode. 2018-06-25 20:48:24 -04:00
Thomas Harte
0b14850467 Corrects some comments. 2018-06-24 23:02:36 -04:00
Thomas Harte
f72e260915 Merge pull request #486 from TomHarte/AppleIIEqualisation
Ensures the Apple II retains horizontal sync for its entire display
2018-06-24 11:27:57 -04:00
Thomas Harte
640a84d456 Shift the h-within-v pulse to eliminate a curved top line. 2018-06-24 11:27:18 -04:00
Thomas Harte
04f6cb1750 Merge branch 'master' into AppleIIEqualisation 2018-06-23 23:10:29 -04:00
Thomas Harte
87d688b7e3 Merge pull request #485 from TomHarte/FurtherTweaks
Various tweaks: Mac UI and CPC window size
2018-06-23 23:10:07 -04:00
Thomas Harte
26141a59b0 Moves all default Mac window positions up by 50px. 2018-06-23 23:09:34 -04:00
Thomas Harte
a93f8103ad Zooms out the CPC a little more.
To fix the maximum amount of content that I can, at least for now.
2018-06-23 22:15:34 -04:00
Thomas Harte
4a3d7c338a Moves the activity window down to start at approximately the same top as the options window. 2018-06-23 22:14:44 -04:00
Thomas Harte
55ab305dbf Introduces equalisation pulses for the Apple II. 2018-06-23 22:11:39 -04:00
Thomas Harte
e48ba89721 Merge pull request #484 from TomHarte/MachinePickerLocation
Cleans up the Mac UI
2018-06-23 19:45:49 -04:00
Thomas Harte
9bb55b6b61 Ensures that 'Activity' view has minimum acceptable height. 2018-06-23 19:44:35 -04:00
Thomas Harte
c33308bdc5 Attempts to improve relative default window positions. 2018-06-23 18:59:19 -04:00
Thomas Harte
44a33941bf Undoes Xcode's folder renaming. 2018-06-23 18:55:17 -04:00
Thomas Harte
cc34cd2133 Merge pull request #481 from TomHarte/CPCJoysticks
Introduces joystick support for the CPC.
2018-06-23 17:09:32 -04:00
Thomas Harte
52c9f9e89e Merge branch 'master' into CPCJoysticks 2018-06-23 16:43:49 -04:00
Thomas Harte
2363deb19c Merge pull request #483 from TomHarte/BetterClip
Picks more appropriate cropping now that I'm obeying HSYNC-as-blank.
2018-06-23 16:42:44 -04:00
Thomas Harte
1c6af279b2 Picks more appropriate cropping now that I'm obeying HSYNC-as-blank. 2018-06-23 16:40:17 -04:00
Thomas Harte
6e96275e1c Merge pull request #482 from TomHarte/PixelCapture
Ensures the pixel collection test is inline with other decisions.
2018-06-23 16:19:04 -04:00
Thomas Harte
9968342a11 Ensures the pixel collection test is inline with other decisions. 2018-06-23 16:18:33 -04:00
Thomas Harte
c248ecde48 Introduces joystick support for the CPC. 2018-06-21 22:46:10 -04:00
Thomas Harte
370952ab33 Merge pull request #480 from TomHarte/Blank
Corrects left-border handling on the CPC
2018-06-21 20:08:09 -04:00
Thomas Harte
154c89e041 Introduces a missing separator. 2018-06-21 20:01:04 -04:00
Thomas Harte
d45f1a793d Introduces composite/RGB selection for the Amstrad CPC. 2018-06-21 20:00:49 -04:00
Thomas Harte
9800951f18 Merge branch 'master' into Blank 2018-06-21 19:41:04 -04:00
Thomas Harte
17251997c2 Merge pull request #479 from TomHarte/8272Logging
Returns sanity to 8272 logging.
2018-06-21 19:40:42 -04:00
Thomas Harte
5ab4cfee84 Factors out repeated hex-size setting. 2018-06-21 19:27:54 -04:00
Thomas Harte
a9eb0d02c6 Returns sanity to 8272 logging. 2018-06-20 23:02:32 -04:00
Thomas Harte
1f8b69a5b0 Attempts to honour the full CRTC 'sync' period, placing blank and the colour burst. 2018-06-20 22:38:54 -04:00
Thomas Harte
8b83f58d7a Merge pull request #478 from TomHarte/CPCTimingTests
Differentiates reasons for a read to be four cycles.
2018-06-20 21:39:12 -04:00
Thomas Harte
9a91ae38c1 Differentiates reasons for a read to be four cycles.
Specifically, puts the enforced wait either before or after checking the wait line. More research may be required; it feels more likely to me that a forced post wait should complete the read then wait, but would that still count as a single machine cycle?
2018-06-20 21:34:21 -04:00
Thomas Harte
ad57caed5e Merge pull request #476 from TomHarte/LongLines
Increases permissible error in scanline length.
2018-06-19 22:25:37 -04:00
Thomas Harte
283ed8dbae Increases permissible error in scanline length. 2018-06-19 22:24:11 -04:00
Thomas Harte
acb74185d5 Revokes test logging. 2018-06-19 19:39:09 -04:00
Thomas Harte
7a5d16ccf8 Merge pull request #475 from TomHarte/VisibleActivity
Shows activity indicators on the Mac
2018-06-18 22:49:33 -04:00
Thomas Harte
adca862166 Finally makes an initial pass at logging macros. 2018-06-18 22:37:19 -04:00
Thomas Harte
1bdc718527 Ensures the MSX reports the proper number of drives. 2018-06-18 22:15:52 -04:00
Thomas Harte
685a80f95b Ensures the Electron Plus 3 properly announces drives to an activity observer.
Does away with lazy allocation as not all that helpful, and liable to cause complexity.
2018-06-18 21:49:57 -04:00
Thomas Harte
62eef8cb40 Reinstates proper ready behaviour. 2018-06-18 21:35:39 -04:00
Thomas Harte
6ed3a49fe1 Made failed attempt to apply height constraint. 2018-06-18 21:35:22 -04:00
Thomas Harte
17702bfb89 Causes GUI LEDs to reflect their underlying activity. 2018-06-18 21:22:51 -04:00
Thomas Harte
292e02702a Progresses very slightly to being able to show up to four activity indicator names.
Blinking to come.
2018-06-17 22:52:17 -04:00
Thomas Harte
5a56d8a5d0 Exposes a list of machine LEDs to Swift.
Also gets explicit about nullability on the Objective-C side.
2018-06-17 18:53:56 -04:00
Thomas Harte
3da1d5700c Merge pull request #472 from TomHarte/SDLJoystick
Connects SDL joystick input to joystick machines.
2018-06-16 22:26:19 -04:00
Thomas Harte
d437e06e15 Adds support for digital hat input as an alternative to analogue sticks. 2018-06-16 22:25:46 -04:00
Thomas Harte
6a3702a5c7 Reduces space for floating point accuracy errors. 2018-06-16 22:22:40 -04:00
Thomas Harte
83a654540a Fixes threshold for positive movement. 2018-06-16 22:22:14 -04:00
Thomas Harte
678bd93c52 Connects SDL joystick input to joystick machines. 2018-06-14 22:37:44 -04:00
Thomas Harte
1bf0c1891a Merge pull request #471 from TomHarte/FadeAwayNotRadiate
Hides persistent low-part colour channel errors.
2018-06-14 20:45:41 -04:00
Thomas Harte
b899a22c7d Hides persistent low-part colour channel errors. 2018-06-14 20:40:27 -04:00
Thomas Harte
1bd6bbca8d Merge pull request #468 from TomHarte/PALColour
Tweaks blending back to appease interlaced video
2018-06-14 18:28:06 -04:00
Thomas Harte
14a2e470e4 Corrects overbrightness issue with autogeneration of PAL composite from an RGB source. 2018-06-14 18:25:48 -04:00
Thomas Harte
41dcf1de42 Increases blur again just a little more, after consideration of interlaced output. 2018-06-14 18:25:04 -04:00
Thomas Harte
0c65385c82 Undoes older interpretation of alternating phase.
I now understand, hopefully, that it's only the phase of the second colour component that alternates. That has the pointwise effect of reversing the colour signal. Hence the effect of phase errors cancelling themselves out up on successive lines up to a point.
2018-06-14 18:24:32 -04:00
Thomas Harte
4aaf43150a Merge pull request #467 from TomHarte/Sharpness
Decreases inter-frame blur.
2018-06-14 17:43:16 -04:00
Thomas Harte
f05ee525cb Tweaks blurriness downward. 2018-06-14 17:41:17 -04:00
Thomas Harte
1172c4fd97 Merge pull request #466 from TomHarte/CPCKeyboard
Improves key state handling under SDL when switching to/from full screen
2018-06-14 17:26:11 -04:00
Thomas Harte
15deef50c8 Adds a key reset upon screen mode changes in SDL. 2018-06-14 17:24:16 -04:00
Thomas Harte
7728adfc5a Eliminates repetition of the 10 constant. 2018-06-14 17:23:47 -04:00
Thomas Harte
eff67f2250 Merge pull request #465 from TomHarte/SDLSwitch
Changes SDL full screen/window toggle and allows ROM path to be specified
2018-06-13 21:36:36 -04:00
Thomas Harte
64e3cf5de2 Ensured all usage messages reflect latest usage. 2018-06-13 21:31:13 -04:00
Thomas Harte
31a6d620e8 Revokes make_unique; I had forgotten that's a C++14 feature. 2018-06-13 21:24:12 -04:00
Thomas Harte
dfd37e7dec Switches full-screen command and adds user-specifiable ROM paths. 2018-06-13 21:21:52 -04:00
Thomas Harte
8d8f244bf5 Merge pull request #461 from TomHarte/Joystick
Introduces Joystick support for the Apple II
2018-06-13 19:34:26 -04:00
Thomas Harte
037b4802db Eliminates unused usings. 2018-06-13 19:26:59 -04:00
Thomas Harte
51da21b844 Formally introduces keyboard-as-joystick for the Mac, and fixes discovered joystick issues.
Specifically:
* digital to analogue conversion not returning to centre;
* Apple II axes being the wrong way around; and
* Apple II buttons using improper selection logic.
2018-06-13 19:22:34 -04:00
Thomas Harte
0be19d8de7 Ensures analogue channels which are already charging don't abide by c070. 2018-06-13 18:16:02 -04:00
Thomas Harte
f26e4734b3 Adds use of joystick input in the Apple II. 2018-06-12 22:21:43 -04:00
Thomas Harte
f1b430338e Makes the Apple II a joystick machine.
Albeit that the values supplied to its joysticks do not currently make it into the emulated state.
2018-06-11 22:16:32 -04:00
Thomas Harte
2954373115 Introduces an intermediary for digital <-> analogue conversion. 2018-06-11 21:35:03 -04:00
Thomas Harte
42d21ea3a9 Merge branch 'master' into Joystick 2018-06-10 21:08:59 -04:00
Thomas Harte
7d761f145f Corrects typo that mapped Apple II options to the Electron. 2018-06-10 21:05:14 -04:00
Thomas Harte
27657fcde0 Adds necessary header for assert. 2018-06-10 21:02:19 -04:00
Thomas Harte
3ea2a4ccb8 Moves the joystick class towards accepting analogue inputs. 2018-06-10 20:45:52 -04:00
Thomas Harte
a1c60152d4 Merge pull request #460 from TomHarte/RWTSAcceleration
Introduces optional quick loading of Apple DOS 3.3 programs
2018-06-10 18:41:23 -04:00
Thomas Harte
69da00fcfb Modifies test slightly for usual RWTS16 location.
Also eliminates messy print logging.
2018-06-10 18:41:09 -04:00
Thomas Harte
c4108efc5c Adds a more accurate option description for the Apple II. 2018-06-10 18:32:22 -04:00
Thomas Harte
d576ff1172 Exposes DOS 3.3 acceleration as an option.
Albeit with an unhelpful label in the macOS GUI for now.
2018-06-10 18:28:29 -04:00
Thomas Harte
af54666c23 Implements RWTS acceleration. 2018-06-10 17:58:16 -04:00
Thomas Harte
e0b75b6e3d Corrects logic for avoiding overwrite. 2018-06-09 21:47:51 -04:00
Thomas Harte
12c59ede09 Adds writeback of track location. 2018-06-09 20:34:12 -04:00
Thomas Harte
b4d0d4fff6 Starts building out some fast-loading infrastructure for DOS 3.3. 2018-06-09 17:29:14 -04:00
Thomas Harte
a694844190 Moves gap 1 into proper ownership. 2018-06-09 17:28:08 -04:00
Thomas Harte
28f2d331a8 Switches to more realistic gaps. 2018-06-09 13:06:45 -04:00
Thomas Harte
dde9b73a22 Creates the through-path that will be necessary for RWTS acceleration. 2018-06-09 12:51:53 -04:00
Thomas Harte
fb4bb21bf6 Ensures an objective copy of the bus address is kept, and forwarded to cards. 2018-06-08 20:12:15 -04:00
Thomas Harte
744c35b617 Caps the number of sync bytes inserted at five. 2018-06-06 21:52:26 -04:00
Thomas Harte
9ac21a4e71 Switches to ignoring the byte count, trusting the bit count entirely. 2018-06-06 21:51:55 -04:00
562 changed files with 103744 additions and 11890 deletions

6
.editorconfig Normal file
View File

@@ -0,0 +1,6 @@
[*]
charset = utf-8
indent_style = tab
indent_size = 4
trim_trailing_whitespace = true

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

@@ -24,13 +24,13 @@ namespace Activity {
class Observer {
public:
/// Announces to the receiver that there is an LED of name @c name.
virtual void register_led(const std::string &name) = 0;
virtual void register_led(const std::string &name) {}
/// Announces to the receiver that there is a drive of name @c name.
virtual void register_drive(const std::string &name) = 0;
virtual void register_drive(const std::string &name) {}
/// Informs the receiver of the new state of the LED with name @c name.
virtual void set_led_status(const std::string &name, bool lit) = 0;
virtual void set_led_status(const std::string &name, bool lit) {}
enum class DriveEvent {
StepNormal,
@@ -39,11 +39,10 @@ class Observer {
};
/// Informs the receiver that the named event just occurred for the drive with name @c name.
virtual void announce_drive_event(const std::string &name, DriveEvent event) = 0;
virtual void announce_drive_event(const std::string &name, DriveEvent event) {}
/// Informs the receiver of the motor-on status of the drive with name @c name.
virtual void set_drive_motor_status(const std::string &name, bool is_on) = 0;
virtual void set_drive_motor_status(const std::string &name, bool is_on) {}
};
}

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

@@ -13,7 +13,7 @@
using namespace Analyser::Dynamic;
MultiCRTMachine::MultiCRTMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::mutex &machines_mutex) :
MultiCRTMachine::MultiCRTMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex) :
machines_(machines), machines_mutex_(machines_mutex), queues_(machines.size()) {
speaker_ = MultiSpeaker::create(machines);
}
@@ -25,7 +25,7 @@ void MultiCRTMachine::perform_parallel(const std::function<void(::CRTMachine::Ma
std::condition_variable condition;
std::mutex mutex;
{
std::lock_guard<std::mutex> machines_lock(machines_mutex_);
std::lock_guard<decltype(machines_mutex_)> machines_lock(machines_mutex_);
std::lock_guard<std::mutex> lock(mutex);
outstanding_machines = machines_.size();
@@ -46,29 +46,25 @@ void MultiCRTMachine::perform_parallel(const std::function<void(::CRTMachine::Ma
}
void MultiCRTMachine::perform_serial(const std::function<void (::CRTMachine::Machine *)> &function) {
std::lock_guard<std::mutex> machines_lock(machines_mutex_);
std::lock_guard<decltype(machines_mutex_)> machines_lock(machines_mutex_);
for(const auto &machine: machines_) {
CRTMachine::Machine *crt_machine = machine->crt_machine();
CRTMachine::Machine *const crt_machine = machine->crt_machine();
if(crt_machine) function(crt_machine);
}
}
void MultiCRTMachine::setup_output(float aspect_ratio) {
perform_serial([=](::CRTMachine::Machine *machine) {
machine->setup_output(aspect_ratio);
});
void MultiCRTMachine::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
scan_target_ = scan_target;
CRTMachine::Machine *const crt_machine = machines_.front()->crt_machine();
if(crt_machine) crt_machine->set_scan_target(scan_target);
}
void MultiCRTMachine::close_output() {
perform_serial([=](::CRTMachine::Machine *machine) {
machine->close_output();
});
}
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();
Outputs::CRT::CRT *MultiCRTMachine::get_crt() {
std::lock_guard<std::mutex> machines_lock(machines_mutex_);
CRTMachine::Machine *crt_machine = machines_.front()->crt_machine();
return crt_machine ? crt_machine->get_crt() : nullptr;
return Outputs::Display::ScanStatus();
}
Outputs::Speaker::Speaker *MultiCRTMachine::get_speaker() {
@@ -84,6 +80,14 @@ void MultiCRTMachine::run_for(Time::Seconds duration) {
}
void MultiCRTMachine::did_change_machine_order() {
if(scan_target_) scan_target_->will_change_owner();
perform_serial([=](::CRTMachine::Machine *machine) {
machine->set_scan_target(nullptr);
});
CRTMachine::Machine *const crt_machine = machines_.front()->crt_machine();
if(crt_machine) crt_machine->set_scan_target(scan_target_);
if(speaker_) {
speaker_->set_new_front_machine(machines_.front().get());
}

View File

@@ -31,7 +31,7 @@ namespace Dynamic {
*/
class MultiCRTMachine: public CRTMachine::Machine {
public:
MultiCRTMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::mutex &machines_mutex);
MultiCRTMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex);
/*!
Informs the MultiCRTMachine that the order of machines has changed; the MultiCRTMachine
@@ -53,19 +53,19 @@ class MultiCRTMachine: public CRTMachine::Machine {
}
// Below is the standard CRTMachine::Machine interface; see there for documentation.
void setup_output(float aspect_ratio) override;
void close_output() override;
Outputs::CRT::CRT *get_crt() 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::mutex &machines_mutex_;
std::recursive_mutex &machines_mutex_;
std::vector<Concurrency::AsyncTaskQueue> queues_;
MultiSpeaker *speaker_ = nullptr;
Delegate *delegate_ = nullptr;
Outputs::Display::ScanTarget *scan_target_ = nullptr;
/*!
Performs a parallel for operation across all machines, performing the supplied

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

@@ -1,29 +0,0 @@
//
// MultiConfigurationTarget.cpp
// Clock Signal
//
// Created by Thomas Harte on 29/01/2018.
// Copyright 2018 Thomas Harte. All rights reserved.
//
#include "MultiConfigurationTarget.hpp"
using namespace Analyser::Dynamic;
MultiConfigurationTarget::MultiConfigurationTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
for(const auto &machine: machines) {
ConfigurationTarget::Machine *configuration_target = machine->configuration_target();
if(configuration_target) targets_.push_back(configuration_target);
}
}
void MultiConfigurationTarget::configure_as_target(const Analyser::Static::Target *target) {
}
bool MultiConfigurationTarget::insert_media(const Analyser::Static::Media &media) {
bool inserted = false;
for(const auto &target : targets_) {
inserted |= target->insert_media(media);
}
return inserted;
}

View File

@@ -1,42 +0,0 @@
//
// MultiConfigurationTarget.hpp
// Clock Signal
//
// Created by Thomas Harte on 29/01/2018.
// Copyright 2018 Thomas Harte. All rights reserved.
//
#ifndef MultiConfigurationTarget_hpp
#define MultiConfigurationTarget_hpp
#include "../../../../Machines/ConfigurationTarget.hpp"
#include "../../../../Machines/DynamicMachine.hpp"
#include <memory>
#include <vector>
namespace Analyser {
namespace Dynamic {
/*!
Provides a class that multiplexes the configuration target interface to multiple machines.
Makes a static internal copy of the list of machines; makes no guarantees about the
order of delivered messages.
*/
struct MultiConfigurationTarget: public ConfigurationTarget::Machine {
public:
MultiConfigurationTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
// Below is the standard ConfigurationTarget::Machine interface; see there for documentation.
void configure_as_target(const Analyser::Static::Target *target) override;
bool insert_media(const Analyser::Static::Media &media) override;
private:
std::vector<ConfigurationTarget::Machine *> targets_;
};
}
}
#endif /* MultiConfigurationTarget_hpp */

View File

@@ -25,14 +25,14 @@ class MultiJoystick: public Inputs::Joystick {
}
}
std::vector<DigitalInput> get_inputs() override {
std::vector<DigitalInput> inputs;
for(const auto &joystick: joysticks_) {
std::vector<DigitalInput> joystick_inputs = joystick->get_inputs();
for(const auto &input: joystick_inputs) {
if(std::find(inputs.begin(), inputs.end(), input) != inputs.end()) {
inputs.push_back(input);
std::vector<Input> &get_inputs() final {
if(inputs.empty()) {
for(const auto &joystick: joysticks_) {
std::vector<Input> joystick_inputs = joystick->get_inputs();
for(const auto &input: joystick_inputs) {
if(std::find(inputs.begin(), inputs.end(), input) != inputs.end()) {
inputs.push_back(input);
}
}
}
}
@@ -40,18 +40,26 @@ class MultiJoystick: public Inputs::Joystick {
return inputs;
}
void set_digital_input(const DigitalInput &digital_input, bool is_active) override {
void set_input(const Input &digital_input, bool is_active) final {
for(const auto &joystick: joysticks_) {
joystick->set_digital_input(digital_input, is_active);
joystick->set_input(digital_input, is_active);
}
}
void reset_all_inputs() 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() final {
for(const auto &joystick: joysticks_) {
joystick->reset_all_inputs();
}
}
private:
std::vector<Input> inputs;
std::vector<Inputs::Joystick *> joysticks_;
};
@@ -73,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

@@ -10,7 +10,8 @@
using namespace Analyser::Dynamic;
MultiKeyboardMachine::MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
MultiKeyboardMachine::MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) :
keyboard_(machines_) {
for(const auto &machine: machines) {
KeyboardMachine::Machine *keyboard_machine = machine->keyboard_machine();
if(keyboard_machine) machines_.push_back(keyboard_machine);
@@ -35,9 +36,34 @@ void MultiKeyboardMachine::type_string(const std::string &string) {
}
}
void MultiKeyboardMachine::keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) {
Inputs::Keyboard &MultiKeyboardMachine::get_keyboard() {
return keyboard_;
}
MultiKeyboardMachine::MultiKeyboard::MultiKeyboard(const std::vector<::KeyboardMachine::Machine *> &machines)
: machines_(machines) {
for(const auto &machine: machines_) {
uint16_t mapped_key = machine->get_keyboard_mapper()->mapped_key_for_key(key);
if(mapped_key != KeyNotMapped) machine->set_key_state(mapped_key, is_pressed);
observed_keys_.insert(machine->get_keyboard().observed_keys().begin(), machine->get_keyboard().observed_keys().end());
is_exclusive_ |= machine->get_keyboard().is_exclusive();
}
}
void MultiKeyboardMachine::MultiKeyboard::set_key_pressed(Key key, char value, bool is_pressed) {
for(const auto &machine: machines_) {
machine->get_keyboard().set_key_pressed(key, value, is_pressed);
}
}
void MultiKeyboardMachine::MultiKeyboard::reset_all_keys() {
for(const auto &machine: machines_) {
machine->get_keyboard().reset_all_keys();
}
}
const std::set<Inputs::Keyboard::Key> &MultiKeyboardMachine::MultiKeyboard::observed_keys() {
return observed_keys_;
}
bool MultiKeyboardMachine::MultiKeyboard::is_exclusive() {
return is_exclusive_;
}

View File

@@ -25,17 +25,33 @@ namespace Dynamic {
order of delivered messages.
*/
class MultiKeyboardMachine: public KeyboardMachine::Machine {
private:
std::vector<::KeyboardMachine::Machine *> machines_;
class MultiKeyboard: public Inputs::Keyboard {
public:
MultiKeyboard(const std::vector<::KeyboardMachine::Machine *> &machines);
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_;
std::set<Key> observed_keys_;
bool is_exclusive_ = false;
};
MultiKeyboard keyboard_;
public:
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;
void keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) override;
private:
std::vector<::KeyboardMachine::Machine *> machines_;
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

@@ -0,0 +1,26 @@
//
// MultiMediaTarget.cpp
// Clock Signal
//
// Created by Thomas Harte on 29/01/2018.
// Copyright 2018 Thomas Harte. All rights reserved.
//
#include "MultiMediaTarget.hpp"
using namespace Analyser::Dynamic;
MultiMediaTarget::MultiMediaTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
for(const auto &machine: machines) {
MediaTarget::Machine *media_target = machine->media_target();
if(media_target) targets_.push_back(media_target);
}
}
bool MultiMediaTarget::insert_media(const Analyser::Static::Media &media) {
bool inserted = false;
for(const auto &target : targets_) {
inserted |= target->insert_media(media);
}
return inserted;
}

View File

@@ -0,0 +1,41 @@
//
// MultiMediaTarget.hpp
// Clock Signal
//
// Created by Thomas Harte on 29/01/2018.
// Copyright 2018 Thomas Harte. All rights reserved.
//
#ifndef MultiMediaTarget_hpp
#define MultiMediaTarget_hpp
#include "../../../../Machines/MediaTarget.hpp"
#include "../../../../Machines/DynamicMachine.hpp"
#include <memory>
#include <vector>
namespace Analyser {
namespace Dynamic {
/*!
Provides a class that multiplexes the media target interface to multiple machines.
Makes a static internal copy of the list of machines; makes no guarantees about the
order of delivered messages.
*/
struct MultiMediaTarget: public MediaTarget::Machine {
public:
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) final;
private:
std::vector<MediaTarget::Machine *> targets_;
};
}
}
#endif /* MultiMediaTarget_hpp */

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

@@ -7,6 +7,7 @@
//
#include "MultiMachine.hpp"
#include "../../../Outputs/Log.hpp"
#include <algorithm>
@@ -15,10 +16,10 @@ using namespace Analyser::Dynamic;
MultiMachine::MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines) :
machines_(std::move(machines)),
configurable_(machines_),
configuration_target_(machines_),
crt_machine_(machines_, machines_mutex_),
joystick_machine_(machines),
keyboard_machine_(machines_) {
keyboard_machine_(machines_),
media_target_(machines_) {
crt_machine_.set_delegate(this);
}
@@ -26,11 +27,11 @@ Activity::Source *MultiMachine::activity_source() {
return nullptr; // TODO
}
ConfigurationTarget::Machine *MultiMachine::configuration_target() {
MediaTarget::Machine *MultiMachine::media_target() {
if(has_picked_) {
return machines_.front()->configuration_target();
return machines_.front()->media_target();
} else {
return &configuration_target_;
return &media_target_;
}
}
@@ -58,6 +59,11 @@ KeyboardMachine::Machine *MultiMachine::keyboard_machine() {
}
}
MouseMachine::Machine *MultiMachine::mouse_machine() {
// TODO.
return nullptr;
}
Configurable::Device *MultiMachine::configurable_device() {
if(has_picked_) {
return machines_.front()->configurable_device();
@@ -73,15 +79,13 @@ bool MultiMachine::would_collapse(const std::vector<std::unique_ptr<DynamicMachi
}
void MultiMachine::multi_crt_did_run_machines() {
std::lock_guard<std::mutex> machines_lock(machines_mutex_);
#ifdef DEBUG
std::lock_guard<decltype(machines_mutex_)> machines_lock(machines_mutex_);
#ifndef NDEBUG
for(const auto &machine: machines_) {
CRTMachine::Machine *crt = machine->crt_machine();
printf("%0.2f ", crt->get_confidence());
crt->print_type();
printf("; ");
LOGNBR(PADHEX(2) << crt->get_confidence() << " " << crt->debug_type() << "; ");
}
printf("\n");
LOGNBR(std::endl);
#endif
DynamicMachine *front = machines_.front().get();

View File

@@ -12,10 +12,10 @@
#include "../../../Machines/DynamicMachine.hpp"
#include "Implementation/MultiConfigurable.hpp"
#include "Implementation/MultiConfigurationTarget.hpp"
#include "Implementation/MultiCRTMachine.hpp"
#include "Implementation/MultiJoystickMachine.hpp"
#include "Implementation/MultiKeyboardMachine.hpp"
#include "Implementation/MultiMediaTarget.hpp"
#include <memory>
#include <mutex>
@@ -50,25 +50,26 @@ 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;
ConfigurationTarget::Machine *configuration_target() override;
CRTMachine::Machine *crt_machine() override;
JoystickMachine::Machine *joystick_machine() override;
KeyboardMachine::Machine *keyboard_machine() override;
Configurable::Device *configurable_device() 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::mutex machines_mutex_;
std::recursive_mutex machines_mutex_;
MultiConfigurable configurable_;
MultiConfigurationTarget configuration_target_;
MultiCRTMachine crt_machine_;
MultiJoystickMachine joystick_machine_;
MultiKeyboardMachine keyboard_machine_;
MultiMediaTarget media_target_;
void pick_first();
bool has_picked_ = false;

View File

@@ -15,8 +15,11 @@ enum class Machine {
AmstradCPC,
AppleII,
Atari2600,
AtariST,
ColecoVision,
Electron,
Macintosh,
MasterSystem,
MSX,
Oric,
Vic20,

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;
@@ -69,13 +69,13 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
target->media.cartridges = AcornCartridgesFrom(media.cartridges);
// if there are any tapes, attempt to get data from the first
if(media.tapes.size() > 0) {
if(!media.tapes.empty()) {
std::shared_ptr<Storage::Tape::Tape> tape = media.tapes.front();
std::vector<File> files = GetFiles(tape);
tape->reset();
// continue if there are any files
if(files.size()) {
if(!files.empty()) {
bool is_basic = true;
// protected files are always for *RUNning only
@@ -103,7 +103,7 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
}
}
if(media.disks.size() > 0) {
if(!media.disks.empty()) {
std::shared_ptr<Storage::Disk::Disk> disk = media.disks.front();
std::unique_ptr<Catalogue> dfs_catalogue, adfs_catalogue;
dfs_catalogue = GetDFSCatalogue(disk);

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

@@ -18,7 +18,9 @@ namespace AppleII {
struct Target: public ::Analyser::Static::Target {
enum class Model {
II,
IIplus
IIplus,
IIe,
EnhancedIIe
};
enum class DiskController {
None,
@@ -26,7 +28,7 @@ struct Target: public ::Analyser::Static::Target {
ThirteenSector
};
Model model = Model::IIplus;
Model model = Model::IIe;
DiskController disk_controller = DiskController::None;
};

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

@@ -17,13 +17,8 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
// only one mapped item is allowed
if(segments.size() != 1) continue;
// which must be 8, 12, 16, 24 or 32 kb in size
const Storage::Cartridge::Cartridge::Segment &segment = segments.front();
const std::size_t data_size = segment.data.size();
const std::size_t overflow = data_size&8191;
if(overflow > 8 && overflow != 512 && (data_size != 12*1024)) continue;
if(data_size < 8192) continue;
// the two bytes that will be first must be 0xaa and 0x55, either way around
auto *start = &segment.data[0];
@@ -34,19 +29,24 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
if(start[0] == start[1]) continue;
// probability of a random binary blob that isn't a Coleco ROM proceeding to here is 1 - 1/32768.
if(!overflow) {
coleco_cartridges.push_back(cartridge);
// Round up to the next multiple of 8kb if this image is less than 32kb. Otherwise round down if
// this image is within a short distance of 32kb.
std::vector<Storage::Cartridge::Cartridge::Segment> output_segments;
size_t target_size;
if(data_size >= 32*1024 && data_size < 32*1024 + 512) {
target_size = 32 * 1024;
} else {
// Size down to a multiple of 8kb and apply the start address.
std::vector<Storage::Cartridge::Cartridge::Segment> output_segments;
std::vector<uint8_t> truncated_data;
std::vector<uint8_t>::difference_type truncated_size = static_cast<std::vector<uint8_t>::difference_type>(segment.data.size()) & ~8191;
truncated_data.insert(truncated_data.begin(), segment.data.begin(), segment.data.begin() + truncated_size);
output_segments.emplace_back(0x8000, truncated_data);
coleco_cartridges.emplace_back(new Storage::Cartridge::Cartridge(output_segments));
target_size = data_size + ((8192 - (data_size & 8191)) & 8191);
}
std::vector<uint8_t> truncated_data;
truncated_data = segment.data;
truncated_data.resize(target_size);
output_segments.emplace_back(0x8000, truncated_data);
coleco_cartridges.emplace_back(new Storage::Cartridge::Cartridge(output_segments));
}
return coleco_cartridges;
@@ -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

@@ -23,5 +23,4 @@ TargetList GetTargets(const Media &media, const std::string &file_name, TargetPl
}
}
#endif /* StaticAnalyser_hpp */

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

@@ -13,8 +13,10 @@
#include "Tape.hpp"
#include "Target.hpp"
#include "../../../Storage/Cartridge/Encodings/CommodoreROM.hpp"
#include "../../../Outputs/Log.hpp"
#include <algorithm>
#include <cstring>
#include <sstream>
using namespace Analyser::Static::Commodore;
@@ -43,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
@@ -77,10 +79,10 @@ 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()) {
if(files.front().is_basic()) {
string_stream << "0";
} else {
string_stream << "1";
@@ -91,18 +93,20 @@ Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media
// make a first guess based on loading address
switch(files.front().starting_address) {
default:
printf("Starting address %04x?\n", files.front().starting_address);
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();
@@ -144,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

@@ -63,9 +63,9 @@ class Accessor {
#define z(v) (v & 7)
Instruction::Condition condition_table[] = {
Instruction::Condition::NZ, Instruction::Condition::Z,
Instruction::Condition::NC, Instruction::Condition::C,
Instruction::Condition::PO, Instruction::Condition::PE,
Instruction::Condition::NZ, Instruction::Condition::Z,
Instruction::Condition::NC, Instruction::Condition::C,
Instruction::Condition::PO, Instruction::Condition::PE,
Instruction::Condition::P, Instruction::Condition::M
};

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) {
@@ -285,7 +285,12 @@ Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(const Media &medi
}
}
// Region selection: for now, this as simple as:
// "If a tape is involved, be European. Otherwise be American (i.e. English, but 60Hz)".
target->region = target->media.tapes.empty() ? Target::Region::USA : Target::Region::Europe;
// Blindly accept disks for now.
// TODO: how to spot an MSX disk?
target->media.disks = media.disks;
target->has_disk_drive = !media.disks.empty();

View File

@@ -19,6 +19,12 @@ namespace MSX {
struct Target: public ::Analyser::Static::Target {
bool has_disk_drive = false;
std::string loading_command;
enum class Region {
Japan,
USA,
Europe
} region = Region::USA;
};
}

View File

@@ -0,0 +1,26 @@
//
// StaticAnalyser.cpp
// Clock Signal
//
// Created by Thomas Harte on 02/06/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#include "StaticAnalyser.hpp"
#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 and mass-storage devices only.
if(media.disks.empty() && media.mass_storage_devices.empty()) return {};
// As there is at least one usable media image, wave it through.
Analyser::Static::TargetList targets;
using Target = Analyser::Static::Macintosh::Target;
auto *target = new Target;
target->machine = Analyser::Machine::Macintosh;
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 02/06/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_Macintosh_StaticAnalyser_hpp
#define Analyser_Static_Macintosh_StaticAnalyser_hpp
#include "../StaticAnalyser.hpp"
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser {
namespace Static {
namespace Macintosh {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}
}
}
#endif /* Analyser_Static_Macintosh_StaticAnalyser_hpp */

View File

@@ -0,0 +1,31 @@
//
// Target.hpp
// Clock Signal
//
// Created by Thomas Harte on 03/06/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_Macintosh_Target_h
#define Analyser_Static_Macintosh_Target_h
namespace Analyser {
namespace Static {
namespace Macintosh {
struct Target: public ::Analyser::Static::Target {
enum class Model {
Mac128k,
Mac512k,
Mac512ke,
MacPlus
};
Model model = Model::MacPlus;
};
}
}
}
#endif /* Analyser_Static_Macintosh_Target_h */

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;
@@ -111,16 +156,14 @@ Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &med
for(auto &tape : media.tapes) {
std::vector<File> tape_files = GetFiles(tape);
tape->reset();
if(tape_files.size()) {
if(!tape_files.empty()) {
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

@@ -0,0 +1,82 @@
//
// StaticAnalyser.cpp
// Clock Signal
//
// Created by Thomas Harte on 20/09/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#include "StaticAnalyser.hpp"
#include "Target.hpp"
#include <algorithm>
#include <cstring>
Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
if(media.cartridges.empty())
return {};
TargetList targets;
auto target = std::make_unique<Target>();
target->machine = Machine::MasterSystem;
// Files named .sg are treated as for the SG1000; otherwise assume a Master System.
if(file_name.size() >= 2 && *(file_name.end() - 2) == 's' && *(file_name.end() - 1) == 'g') {
target->model = Target::Model::SG1000;
} else {
target->model = Target::Model::MasterSystem;
}
// If this is a Master System title, look for a ROM header.
if(target->model == Target::Model::MasterSystem) {
const auto &data = media.cartridges.front()->get_segments()[0].data;
// First try to locate a header.
size_t header_offset = 0;
size_t potential_offsets[] = {0x1ff0, 0x3ff0, 0x7ff0};
for(auto potential_offset: potential_offsets) {
if(!memcmp(&data[potential_offset], "TMR SEGA", 8)) {
header_offset = potential_offset;
break;
}
}
// If a header was found, use it to crib region.
if(header_offset) {
// Treat export titles as European by default; decline to
// do so only if (US) or (NTSC) is in the file name.
const uint8_t region = data[header_offset + 0x0f] >> 4;
switch(region) {
default: break;
case 4: {
std::string lowercase_name = file_name;
std::transform(lowercase_name.begin(), lowercase_name.end(), lowercase_name.begin(), ::tolower);
if(lowercase_name.find("(jp)") == std::string::npos) {
target->region =
(lowercase_name.find("(us)") == std::string::npos &&
lowercase_name.find("(ntsc)") == std::string::npos) ? Target::Region::Europe : Target::Region::USA;
}
} break;
}
// Also check for a Codemasters header.
// If one is found, set the paging scheme appropriately.
const uint16_t inverse_checksum = uint16_t(0x10000 - (data[0x7fe6] | (data[0x7fe7] << 8)));
if(
data[0x7fe3] >= 0x87 && data[0x7fe3] < 0x96 && // i.e. game is dated between 1987 and 1996
(inverse_checksum&0xff) == data[0x7fe8] &&
(inverse_checksum >> 8) == data[0x7fe9] && // i.e. the standard checksum appears to be present
!data[0x7fea] && !data[0x7feb] && !data[0x7fec] && !data[0x7fed] && !data[0x7fee] && !data[0x7fef]
) {
target->paging_scheme = Target::PagingScheme::Codemasters;
target->model = Target::Model::MasterSystem2;
}
}
}
target->media.cartridges = media.cartridges;
targets.push_back(std::move(target));
return targets;
}

View File

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

View File

@@ -0,0 +1,46 @@
//
// Target.hpp
// Clock Signal
//
// Created by Thomas Harte on 23/09/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_Sega_Target_h
#define Analyser_Static_Sega_Target_h
namespace Analyser {
namespace Static {
namespace Sega {
struct Target: public ::Analyser::Static::Target {
enum class Model {
SG1000,
MasterSystem,
MasterSystem2,
};
enum class Region {
Japan,
USA,
Europe,
Brazil
};
enum class PagingScheme {
Sega,
Codemasters
};
Model model = Model::MasterSystem;
Region region = Region::Japan;
PagingScheme paging_scheme = PagingScheme::Sega;
};
#define is_master_system(v) v >= Analyser::Static::Sega::Target::Model::MasterSystem
}
}
}
#endif /* Analyser_Static_Sega_Target_h */

View File

@@ -17,12 +17,15 @@
#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"
#include "Macintosh/StaticAnalyser.hpp"
#include "MSX/StaticAnalyser.hpp"
#include "Oric/StaticAnalyser.hpp"
#include "Sega/StaticAnalyser.hpp"
#include "ZX8081/StaticAnalyser.hpp"
// Cartridges
@@ -34,15 +37,22 @@
#include "../../Storage/Disk/DiskImage/Formats/AppleDSK.hpp"
#include "../../Storage/Disk/DiskImage/Formats/CPCDSK.hpp"
#include "../../Storage/Disk/DiskImage/Formats/D64.hpp"
#include "../../Storage/Disk/DiskImage/Formats/MacintoshIMG.hpp"
#include "../../Storage/Disk/DiskImage/Formats/G64.hpp"
#include "../../Storage/Disk/DiskImage/Formats/DMK.hpp"
#include "../../Storage/Disk/DiskImage/Formats/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"
@@ -84,34 +94,39 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
TryInsert(list, class, platforms) \
}
Format("80", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 80
Format("81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 81
Format("a26", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // A26
Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn) // ADF
Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::AllCartridge) // BIN
Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX) // CAS
Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC) // CDT
Format("col", result.cartridges, Cartridge::BinaryDump, TargetPlatform::ColecoVision) // COL
Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape) // CSW
Format("d64", result.disks, Disk::DiskImageHolder<Storage::Disk::D64>, TargetPlatform::Commodore) // D64
Format("dmk", result.disks, Disk::DiskImageHolder<Storage::Disk::DMK>, TargetPlatform::MSX) // DMK
Format("do", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // DO
Format("dsd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // DSD
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::CPCDSK>, TargetPlatform::AmstradCPC) // DSK (Amstrad CPC)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // DSK (Apple)
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
Format("80", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 80
Format("81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 81
Format("a26", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // A26
Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn) // ADF
Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::AllCartridge) // BIN (cartridge dump)
Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX) // CAS
Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC) // CDT
Format("col", result.cartridges, Cartridge::BinaryDump, TargetPlatform::ColecoVision) // COL
Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape) // CSW
Format("d64", result.disks, Disk::DiskImageHolder<Storage::Disk::D64>, TargetPlatform::Commodore) // D64
Format("dmk", result.disks, Disk::DiskImageHolder<Storage::Disk::DMK>, TargetPlatform::MSX) // DMK
Format("do", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // DO
Format("dsd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // DSD
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::CPCDSK>, TargetPlatform::AmstradCPC) // 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, 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
Format( "hfe",
result.disks,
Disk::DiskImageHolder<Storage::Disk::HFE>,
TargetPlatform::Acorn | TargetPlatform::AmstradCPC | TargetPlatform::Commodore | TargetPlatform::Oric)
// HFE (TODO: switch to AllDisk once the MSX stops being so greedy)
Format("nib", result.disks, Disk::DiskImageHolder<Storage::Disk::NIB>, TargetPlatform::DiskII) // NIB
Format("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // O
Format("p", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P
Format("po", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // PO
Format("p81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P81
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
Format("po", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // PO
Format("p81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P81
// PRG
if(extension == "prg") {
@@ -128,14 +143,18 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
Format( "rom",
result.cartridges,
Cartridge::BinaryDump,
TargetPlatform::AcornElectron | TargetPlatform::ColecoVision | TargetPlatform::MSX) // ROM
Format("ssd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // SSD
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
Format("tzx", result.tapes, Tape::TZX, TargetPlatform::ZX8081) // TZX
Format("uef", result.tapes, Tape::UEF, TargetPlatform::Acorn) // UEF (tape)
Format("woz", result.disks, Disk::DiskImageHolder<Storage::Disk::WOZ>, TargetPlatform::DiskII) // WOZ
TargetPlatform::AcornElectron | TargetPlatform::ColecoVision | TargetPlatform::MSX) // ROM
Format("sg", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega) // SG
Format("sms", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega) // SMS
Format("ssd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // SSD
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
Format("tzx", result.tapes, Tape::TZX, TargetPlatform::ZX8081) // TZX
Format("uef", result.tapes, Tape::UEF, TargetPlatform::Acorn) // UEF (tape)
Format("woz", result.disks, Disk::DiskImageHolder<Storage::Disk::WOZ>, TargetPlatform::DiskII) // WOZ
#undef Format
#undef Insert
@@ -152,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);
@@ -166,12 +185,15 @@ 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);
if(potential_platforms & TargetPlatform::Macintosh) Append(Macintosh);
if(potential_platforms & TargetPlatform::MSX) Append(MSX);
if(potential_platforms & TargetPlatform::Oric) Append(Oric);
if(potential_platforms & TargetPlatform::Sega) Append(Sega);
if(potential_platforms & TargetPlatform::ZX8081) Append(ZX8081);
#undef Append

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

@@ -9,6 +9,9 @@
#ifndef ClockReceiver_hpp
#define ClockReceiver_hpp
#include "ForceInline.hpp"
#include <cstdint>
/*
Informal pattern for all classes that run from a clock cycle:
@@ -52,149 +55,193 @@
*/
template <class T> class WrappedInt {
public:
constexpr WrappedInt(int l) : length_(l) {}
constexpr WrappedInt() : length_(0) {}
using IntType = int64_t;
T &operator =(const T &rhs) {
forceinline constexpr WrappedInt(IntType l) noexcept : length_(l) {}
forceinline constexpr WrappedInt() noexcept : length_(0) {}
forceinline T &operator =(const T &rhs) {
length_ = rhs.length_;
return *this;
}
T &operator +=(const T &rhs) {
forceinline T &operator +=(const T &rhs) {
length_ += rhs.length_;
return *static_cast<T *>(this);
}
T &operator -=(const T &rhs) {
forceinline T &operator -=(const T &rhs) {
length_ -= rhs.length_;
return *static_cast<T *>(this);
}
T &operator ++() {
forceinline T &operator ++() {
++ length_;
return *static_cast<T *>(this);
}
T &operator ++(int) {
forceinline T &operator ++(int) {
length_ ++;
return *static_cast<T *>(this);
}
T &operator --() {
forceinline T &operator --() {
-- length_;
return *static_cast<T *>(this);
}
T &operator --(int) {
forceinline T &operator --(int) {
length_ --;
return *static_cast<T *>(this);
}
T &operator %=(const T &rhs) {
forceinline T &operator *=(const T &rhs) {
length_ *= rhs.length_;
return *static_cast<T *>(this);
}
forceinline T &operator /=(const T &rhs) {
length_ /= rhs.length_;
return *static_cast<T *>(this);
}
forceinline T &operator %=(const T &rhs) {
length_ %= rhs.length_;
return *static_cast<T *>(this);
}
T &operator &=(const T &rhs) {
forceinline T &operator &=(const T &rhs) {
length_ &= rhs.length_;
return *static_cast<T *>(this);
}
constexpr T operator +(const T &rhs) const { return T(length_ + rhs.length_); }
constexpr T operator -(const T &rhs) const { return T(length_ - rhs.length_); }
forceinline constexpr T operator +(const T &rhs) const { return T(length_ + rhs.length_); }
forceinline constexpr T operator -(const T &rhs) const { return T(length_ - rhs.length_); }
constexpr T operator %(const T &rhs) const { return T(length_ % rhs.length_); }
constexpr T operator &(const T &rhs) const { return T(length_ & rhs.length_); }
forceinline constexpr T operator *(const T &rhs) const { return T(length_ * rhs.length_); }
forceinline constexpr T operator /(const T &rhs) const { return T(length_ / rhs.length_); }
constexpr T operator -() const { return T(- length_); }
forceinline constexpr T operator %(const T &rhs) const { return T(length_ % rhs.length_); }
forceinline constexpr T operator &(const T &rhs) const { return T(length_ & rhs.length_); }
constexpr bool operator <(const T &rhs) const { return length_ < rhs.length_; }
constexpr bool operator >(const T &rhs) const { return length_ > rhs.length_; }
constexpr bool operator <=(const T &rhs) const { return length_ <= rhs.length_; }
constexpr bool operator >=(const T &rhs) const { return length_ >= rhs.length_; }
constexpr bool operator ==(const T &rhs) const { return length_ == rhs.length_; }
constexpr bool operator !=(const T &rhs) const { return length_ != rhs.length_; }
forceinline constexpr T operator -() const { return T(- length_); }
constexpr bool operator !() const { return !length_; }
forceinline constexpr bool operator <(const T &rhs) const { return length_ < rhs.length_; }
forceinline constexpr bool operator >(const T &rhs) const { return length_ > rhs.length_; }
forceinline constexpr bool operator <=(const T &rhs) const { return length_ <= rhs.length_; }
forceinline constexpr bool operator >=(const T &rhs) const { return length_ >= rhs.length_; }
forceinline constexpr bool operator ==(const T &rhs) const { return length_ == rhs.length_; }
forceinline constexpr bool operator !=(const T &rhs) const { return length_ != rhs.length_; }
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
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.
*/
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;
}
/*!
Flushes the value in @c this. The current value is returned, and the internal value
is reset to zero.
*/
T flush() {
T result(length_);
length_ = 0;
return result;
template <typename Result> Result flush() {
// Jiggery pokery here; switching to function overloading avoids
// the namespace-level requirement for template specialisation.
Result r;
static_cast<T *>(this)->fill(r);
return r;
}
// operator int() is deliberately not provided, to avoid accidental subtitution of
// 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:
constexpr Cycles(int l) : WrappedInt<Cycles>(l) {}
constexpr Cycles() : WrappedInt<Cycles>() {}
constexpr Cycles(const Cycles &cycles) : WrappedInt<Cycles>(cycles.length_) {}
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_) {}
private:
friend WrappedInt;
void fill(Cycles &result) {
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:
constexpr HalfCycles(int l) : WrappedInt<HalfCycles>(l) {}
constexpr HalfCycles() : WrappedInt<HalfCycles>() {}
forceinline constexpr HalfCycles(IntType l) noexcept : WrappedInt<HalfCycles>(l) {}
forceinline constexpr HalfCycles() noexcept : WrappedInt<HalfCycles>() {}
constexpr HalfCycles(const Cycles cycles) : WrappedInt<HalfCycles>(cycles.as_int() * 2) {}
constexpr HalfCycles(const HalfCycles &half_cycles) : WrappedInt<HalfCycles>(half_cycles.length_) {}
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.
constexpr Cycles cycles() {
forceinline constexpr Cycles cycles() const {
return Cycles(length_ >> 1);
}
/// Flushes the whole cycles in @c this, subtracting that many from the total stored here.
Cycles flush_cycles() {
Cycles result(length_ >> 1);
length_ &= 1;
return result;
}
/// Flushes the half cycles in @c this, returning the number stored and setting this total to zero.
HalfCycles flush() {
HalfCycles result(length_);
length_ = 0;
return result;
}
/*!
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.
*/
Cycles divide_cycles(const Cycles &divisor) {
HalfCycles half_divisor = HalfCycles(divisor);
Cycles result(length_ / half_divisor.length_);
forceinline Cycles divide_cycles(const Cycles &divisor) {
const HalfCycles half_divisor = HalfCycles(divisor);
const Cycles result(length_ / half_divisor.length_);
length_ %= half_divisor.length_;
return result;
}
private:
friend WrappedInt;
void fill(Cycles &result) {
result = Cycles(length_ >> 1);
length_ &= 1;
}
void fill(HalfCycles &result) {
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
// without losing the fractional part.
/*!
If a component implements only run_for(Cycles), an owner can wrap it in HalfClockReceiver
automatically to gain run_for(HalfCycles).
@@ -203,9 +250,9 @@ template <class T> class HalfClockReceiver: public T {
public:
using T::T;
inline void run_for(const HalfCycles half_cycles) {
forceinline void run_for(const HalfCycles half_cycles) {
half_cycles_ += half_cycles;
T::run_for(half_cycles_.flush_cycles());
T::run_for(half_cycles_.flush<Cycles>());
}
private:

View File

@@ -0,0 +1,124 @@
//
// DeferredQueue.hpp
// Clock Signal
//
// Created by Thomas Harte on 23/08/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef DeferredQueue_h
#define DeferredQueue_h
#include <functional>
#include <vector>
/*!
Provides the logic to insert into and traverse a list of future scheduled items.
*/
template <typename TimeUnit> class DeferredQueue {
public:
/*!
Schedules @c action to occur in @c delay units of time.
*/
void defer(TimeUnit delay, const std::function<void(void)> &action) {
// Apply immediately if there's no delay (or a negative delay).
if(delay <= TimeUnit(0)) {
action();
return;
}
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;
}
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:
// The list of deferred actions.
struct DeferredAction {
TimeUnit delay;
std::function<void(void)> action;
DeferredAction(TimeUnit delay, const std::function<void(void)> &action) : delay(delay), action(std::move(action)) {}
};
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

@@ -0,0 +1,173 @@
//
// JustInTime.hpp
// Clock Signal
//
// Created by Thomas Harte on 28/07/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef JustInTime_h
#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
of time since run_for was last called.
Time can be added using the += operator. The -> operator can be used to access the
embedded object. All time accumulated will be pushed to object before the pointer is returned.
Machines that accumulate HalfCycle time but supply to a Cycle-counted device may supply a
separate @c TargetTimeScale at template declaration.
*/
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.
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.
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.
forceinline T *last_valid() {
return &object_;
}
/// Flushes all accumulated time.
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:
T object_;
LocalTimeScale time_since_update_;
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 = 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) :
object_(std::forward<Args>(args)...),
threshold_(threshold) {}
/// Adds time to the actor.
inline void operator += (const LocalTimeScale &rhs) {
time_since_update_ += rhs;
if(time_since_update_ >= threshold_) {
time_since_update_ -= threshold_;
task_queue_.enqueue([this] () {
object_.run_for(threshold_);
});
}
is_flushed_ = false;
}
/// Flushes all accumulated time and returns a pointer to the included object.
inline T *operator->() {
flush();
return &object_;
}
/// Returns a pointer to the included object without flushing time.
inline T *last_valid() {
return &object_;
}
/// Flushes all accumulated time.
inline void flush() {
if(!is_flushed_) {
task_queue_.flush();
object_.run_for(time_since_update_.template flush<TargetTimeScale>());
is_flushed_ = true;
}
}
private:
T object_;
LocalTimeScale time_since_update_;
TargetTimeScale threshold_;
bool is_flushed_ = true;
Concurrency::AsyncTaskQueue task_queue_;
};
#endif /* JustInTime_h */

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

@@ -7,35 +7,39 @@
//
#include "1770.hpp"
#include "../../Storage/Disk/Encodings/MFM/Constants.hpp"
#define LOG_PREFIX "[WD FDC] "
#include "../../Outputs/Log.hpp"
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**.
printf("Force interrupt immediately\n");
posit_event(static_cast<int>(Event1770::ForceInterrupt));
LOG("Force interrupt immediately");
posit_event(int(Event1770::ForceInterrupt));
} else {
printf("!!!TODO: force interrupt!!!\n");
ERROR("!!!TODO: force interrupt!!!");
update_status([] (Status &status) {
status.type = Status::One;
});
}
} else {
command_ = value;
posit_event(static_cast<int>(Event1770::Command));
posit_event(int(Event1770::Command));
}
}
break;
@@ -50,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) |
@@ -87,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;
@@ -103,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_; \
} \
}
@@ -139,28 +158,28 @@ void WD1770::run_for(const Cycles cycles) {
WAIT_FOR_EVENT(Event1770::IndexHoleTarget); \
status_.spin_up = true;
// +--------+----------+-------------------------+
// ! ! ! BITS !
// ! TYPE ! COMMAND ! 7 6 5 4 3 2 1 0 !
// +--------+----------+-------------------------+
// ! 1 ! Restore ! 0 0 0 0 h v r1 r0 !
// ! 1 ! Seek ! 0 0 0 1 h v r1 r0 !
// ! 1 ! Step ! 0 0 1 u h v r1 r0 !
// ! 1 ! Step-in ! 0 1 0 u h v r1 r0 !
// ! 1 ! Step-out ! 0 1 1 u h v r1 r0 !
// ! 2 ! Rd sectr ! 1 0 0 m h E 0 0 !
// ! 2 ! Wt sectr ! 1 0 1 m h E P a0 !
// ! 3 ! Rd addr ! 1 1 0 0 h E 0 0 !
// ! 3 ! Rd track ! 1 1 1 0 h E 0 0 !
// ! 3 ! Wt track ! 1 1 1 1 h E P 0 !
// ! 4 ! Forc int ! 1 1 0 1 i3 i2 i1 i0 !
// +--------+----------+-------------------------+
// +--------+----------+-------------------------+
// ! ! ! BITS !
// ! TYPE ! COMMAND ! 7 6 5 4 3 2 1 0 !
// +--------+----------+-------------------------+
// ! 1 ! Restore ! 0 0 0 0 h v r1 r0 !
// ! 1 ! Seek ! 0 0 0 1 h v r1 r0 !
// ! 1 ! Step ! 0 0 1 u h v r1 r0 !
// ! 1 ! Step-in ! 0 1 0 u h v r1 r0 !
// ! 1 ! Step-out ! 0 1 1 u h v r1 r0 !
// ! 2 ! Rd sectr ! 1 0 0 m h E 0 0 !
// ! 2 ! Wt sectr ! 1 0 1 m h E P a0 !
// ! 3 ! Rd addr ! 1 1 0 0 h E 0 0 !
// ! 3 ! Rd track ! 1 1 1 0 h E 0 0 !
// ! 3 ! Wt track ! 1 1 1 1 h E P 0 !
// ! 4 ! Forc int ! 1 1 0 1 i3 i2 i1 i0 !
// +--------+----------+-------------------------+
void WD1770::posit_event(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;
}
@@ -175,25 +194,25 @@ 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;
}
Status new_status;
BEGIN_SECTION()
// Wait for a new command, branch to the appropriate handler.
case 0:
wait_for_command:
printf("Idle...\n");
LOG("Idle...");
set_data_mode(DataMode::Scanning);
index_hole_count_ = 0;
@@ -207,9 +226,10 @@ 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.
});
printf("Starting %02x\n", command_);
LOG("Starting " << PADHEX(2) << int(command_));
if(!(command_ & 0x80)) goto begin_type_1;
if(!(command_ & 0x40)) goto begin_type_2;
@@ -219,16 +239,16 @@ void WD1770::posit_event(int new_event_type) {
/*
Type 1 entry point.
*/
// +--------+----------+-------------------------+
// ! ! ! BITS !
// ! TYPE ! COMMAND ! 7 6 5 4 3 2 1 0 !
// +--------+----------+-------------------------+
// ! 1 ! Restore ! 0 0 0 0 h v r1 r0 !
// ! 1 ! Seek ! 0 0 0 1 h v r1 r0 !
// ! 1 ! Step ! 0 0 1 u h v r1 r0 !
// ! 1 ! Step-in ! 0 1 0 u h v r1 r0 !
// ! 1 ! Step-out ! 0 1 1 u h v r1 r0 !
// +--------+----------+-------------------------+
// +--------+----------+-------------------------+
// ! ! ! BITS !
// ! TYPE ! COMMAND ! 7 6 5 4 3 2 1 0 !
// +--------+----------+-------------------------+
// ! 1 ! Restore ! 0 0 0 0 h v r1 r0 !
// ! 1 ! Seek ! 0 0 0 1 h v r1 r0 !
// ! 1 ! Step ! 0 0 1 u h v r1 r0 !
// ! 1 ! Step-in ! 0 1 0 u h v r1 r0 !
// ! 1 ! Step-out ! 0 1 1 u h v r1 r0 !
// +--------+----------+-------------------------+
begin_type_1:
// Set initial flags, skip spin-up if possible.
@@ -239,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;
@@ -271,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;
@@ -292,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;
}
@@ -308,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;
@@ -327,14 +354,12 @@ void WD1770::posit_event(int new_event_type) {
}
if(header_[0] == track_) {
printf("Reached track %d\n", track_);
LOG("Reached track " << std::dec << int(track_));
update_status([] (Status &status) {
status.crc_error = false;
});
goto wait_for_command;
}
distance_into_section_ = 0;
}
goto verify_read_data;
@@ -342,13 +367,13 @@ void WD1770::posit_event(int new_event_type) {
/*
Type 2 entry point.
*/
// +--------+----------+-------------------------+
// ! ! ! BITS !
// ! TYPE ! COMMAND ! 7 6 5 4 3 2 1 0 !
// +--------+----------+-------------------------+
// ! 2 ! Rd sectr ! 1 0 0 m h E 0 0 !
// ! 2 ! Wt sectr ! 1 0 1 m h E P a0 !
// +--------+----------+-------------------------+
// +--------+----------+-------------------------+
// ! ! ! BITS !
// ! TYPE ! COMMAND ! 7 6 5 4 3 2 1 0 !
// +--------+----------+-------------------------+
// ! 2 ! Rd sectr ! 1 0 0 m h E 0 0 !
// ! 2 ! Wt sectr ! 1 0 1 m h E P a0 !
// +--------+----------+-------------------------+
begin_type_2:
update_status([] (Status &status) {
@@ -391,25 +416,30 @@ 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) {
printf("Failed to find sector %d\n", sector_);
LOG("Failed to find sector " << std::dec << int(sector_));
update_status([] (Status &status) {
status.record_not_found = true;
});
goto wait_for_command;
}
if(distance_into_section_ == 7) {
printf("Considering %d/%d\n", header_[0], 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])) {
printf("Found %d/%d\n", header_[0], header_[2]);
LOG("Found " << std::dec << int(header_[0]) << "/" << int(header_[2]));
if(get_crc_generator().get_value()) {
printf("CRC error; back to searching\n");
LOG("CRC error; back to searching");
update_status([] (Status &status) {
status.crc_error = true;
});
@@ -421,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;
@@ -452,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;
}
@@ -464,19 +493,24 @@ 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()) {
printf("CRC error; terminating\n");
LOG("CRC error; terminating");
update_status([this] (Status &status) {
status.crc_error = true;
});
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;
}
printf("Finished reading sector %d\n", sector_);
goto wait_for_command;
}
goto type2_check_crc;
@@ -530,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;
}
@@ -558,21 +592,21 @@ void WD1770::posit_event(int new_event_type) {
sector_++;
goto test_type2_write_protection;
}
printf("Wrote sector %d\n", sector_);
LOG("Wrote sector " << std::dec << int(sector_));
goto wait_for_command;
/*
Type 3 entry point.
*/
// +--------+----------+-------------------------+
// ! ! ! BITS !
// ! TYPE ! COMMAND ! 7 6 5 4 3 2 1 0 !
// +--------+----------+-------------------------+
// ! 3 ! Rd addr ! 1 1 0 0 h E 0 0 !
// ! 3 ! Rd track ! 1 1 1 0 h E 0 0 !
// ! 3 ! Wt track ! 1 1 1 1 h E P 0 !
// +--------+----------+-------------------------+
// +--------+----------+-------------------------+
// ! ! ! BITS !
// ! TYPE ! COMMAND ! 7 6 5 4 3 2 1 0 !
// +--------+----------+-------------------------+
// ! 3 ! Rd addr ! 1 1 0 0 h E 0 0 !
// ! 3 ! Rd track ! 1 1 1 0 h E 0 0 !
// ! 3 ! Wt track ! 1 1 1 1 h E P 0 !
// +--------+----------+-------------------------+
begin_type_3:
update_status([] (Status &status) {
status.type = Status::Three;
@@ -609,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) {
@@ -624,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;
@@ -650,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;
}
@@ -717,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) |
@@ -766,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) {}
@@ -782,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

@@ -47,6 +47,12 @@ class PortHandler {
/// Sets the current logical value of the interrupt line.
void set_interrupt_status(bool status) {}
/// Provides a measure of time elapsed between other calls.
void run_for(HalfCycles duration) {}
/// Receives passed-on flush() calls from the 6522.
void flush() {}
};
/*!
@@ -71,8 +77,31 @@ class IRQDelegatePortHandler: public PortHandler {
Delegate *delegate_ = nullptr;
};
class MOS6522Base: public MOS6522Storage {
/*!
Implements a template for emulation of the MOS 6522 Versatile Interface Adaptor ('VIA').
The VIA provides:
* two timers, each of which may trigger interrupts and one of which may repeat;
* two digial input/output ports; and
* a serial-to-parallel shifter.
Consumers should derive their own curiously-recurring-template-pattern subclass,
implementing bus communications as required.
*/
template <class T> class MOS6522: public MOS6522Storage {
public:
MOS6522(T &bus_handler) noexcept : bus_handler_(bus_handler) {}
MOS6522(const MOS6522 &) = delete;
/*! Sets a register value. */
void write(int address, uint8_t value);
/*! Gets a register value. */
uint8_t read(int address);
/*! @returns the bus handler. */
T &bus_handler();
/// Sets the input value of line @c line on port @c port.
void set_control_line_input(Port port, Line line, bool value);
@@ -85,47 +114,32 @@ class MOS6522Base: public MOS6522Storage {
/// @returns @c true if the IRQ line is currently active; @c false otherwise.
bool get_interrupt_line();
private:
inline void do_phase1();
inline void do_phase2();
virtual void reevaluate_interrupts() = 0;
};
/*!
Implements a template for emulation of the MOS 6522 Versatile Interface Adaptor ('VIA').
The VIA provides:
* two timers, each of which may trigger interrupts and one of which may repeat;
* two digial input/output ports; and
* a serial-to-parallel shifter.
Consumers should derive their own curiously-recurring-template-pattern subclass,
implementing bus communications as required.
*/
template <class T> class MOS6522: public MOS6522Base {
public:
MOS6522(T &bus_handler) noexcept : bus_handler_(bus_handler) {}
MOS6522(const MOS6522 &) = delete;
/*! Sets a register value. */
void set_register(int address, uint8_t value);
/*! Gets a register value. */
uint8_t get_register(int address);
/*! @returns the bus handler. */
T &bus_handler();
/// Updates the port handler to the current time and then requests that it flush.
void flush();
private:
void do_phase1();
void do_phase2();
void shift_in();
void shift_out();
T &bus_handler_;
HalfCycles time_since_bus_handler_call_;
void access(int address);
uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output);
inline void reevaluate_interrupts();
/// Sets the current intended output value for the port and line;
/// if this affects the visible output, it will be passed to the handler.
void set_control_line_output(Port port, Line line, LineState value);
void evaluate_cb2_output();
};
}
}
#include "Implementation/6522Implementation.hpp"
}
}
#endif /* _522_hpp */

View File

@@ -1,116 +0,0 @@
//
// 6522Base.cpp
// Clock Signal
//
// Created by Thomas Harte on 04/09/2017.
// Copyright 2017 Thomas Harte. All rights reserved.
//
#include "../6522.hpp"
using namespace MOS::MOS6522;
void MOS6522Base::set_control_line_input(Port port, Line line, bool value) {
switch(line) {
case Line::One:
if( value != control_inputs_[port].line_one &&
value == !!(registers_.peripheral_control & (port ? 0x10 : 0x01))
) {
registers_.interrupt_flags |= port ? InterruptFlag::CB1ActiveEdge : InterruptFlag::CA1ActiveEdge;
reevaluate_interrupts();
}
control_inputs_[port].line_one = value;
break;
case Line::Two:
// TODO: output modes, but probably elsewhere?
if( value != control_inputs_[port].line_two && // i.e. value has changed ...
!(registers_.peripheral_control & (port ? 0x80 : 0x08)) && // ... and line is input ...
value == !!(registers_.peripheral_control & (port ? 0x40 : 0x04)) // ... and it's either high or low, as required
) {
registers_.interrupt_flags |= port ? InterruptFlag::CB2ActiveEdge : InterruptFlag::CA2ActiveEdge;
reevaluate_interrupts();
}
control_inputs_[port].line_two = value;
break;
}
}
void MOS6522Base::do_phase2() {
registers_.last_timer[0] = registers_.timer[0];
registers_.last_timer[1] = registers_.timer[1];
if(registers_.timer_needs_reload) {
registers_.timer_needs_reload = false;
registers_.timer[0] = registers_.timer_latch[0];
} else {
registers_.timer[0] --;
}
registers_.timer[1] --;
if(registers_.next_timer[0] >= 0) {
registers_.timer[0] = static_cast<uint16_t>(registers_.next_timer[0]);
registers_.next_timer[0] = -1;
}
if(registers_.next_timer[1] >= 0) {
registers_.timer[1] = static_cast<uint16_t>(registers_.next_timer[1]);
registers_.next_timer[1] = -1;
}
}
void MOS6522Base::do_phase1() {
// IRQ is raised on the half cycle after overflow
if((registers_.timer[1] == 0xffff) && !registers_.last_timer[1] && timer_is_running_[1]) {
timer_is_running_[1] = false;
registers_.interrupt_flags |= InterruptFlag::Timer2;
reevaluate_interrupts();
}
if((registers_.timer[0] == 0xffff) && !registers_.last_timer[0] && timer_is_running_[0]) {
registers_.interrupt_flags |= InterruptFlag::Timer1;
reevaluate_interrupts();
if(registers_.auxiliary_control&0x40)
registers_.timer_needs_reload = true;
else
timer_is_running_[0] = false;
}
}
/*! Runs for a specified number of half cycles. */
void MOS6522Base::run_for(const HalfCycles half_cycles) {
int number_of_half_cycles = half_cycles.as_int();
if(is_phase2_) {
do_phase2();
number_of_half_cycles--;
}
while(number_of_half_cycles >= 2) {
do_phase1();
do_phase2();
number_of_half_cycles -= 2;
}
if(number_of_half_cycles) {
do_phase1();
is_phase2_ = true;
} else {
is_phase2_ = false;
}
}
/*! Runs for a specified number of cycles. */
void MOS6522Base::run_for(const Cycles cycles) {
int number_of_cycles = cycles.as_int();
while(number_of_cycles--) {
do_phase1();
do_phase2();
}
}
/*! @returns @c true if the IRQ line is currently active; @c false otherwise. */
bool MOS6522Base::get_interrupt_line() {
uint8_t interrupt_status = registers_.interrupt_flags & registers_.interrupt_enable & 0x7f;
return !!interrupt_status;
}

View File

@@ -6,29 +6,63 @@
// Copyright 2017 Thomas Harte. All rights reserved.
//
template <typename T> void MOS6522<T>::set_register(int address, uint8_t value) {
address &= 0xf;
#include "../../../Outputs/Log.hpp"
namespace MOS {
namespace MOS6522 {
template <typename T> void MOS6522<T>::access(int address) {
switch(address) {
case 0x0:
// In both handshake and pulse modes, CB2 goes low on any read or write of Port B.
if(handshake_modes_[1] != HandshakeMode::None) {
set_control_line_output(Port::B, Line::Two, LineState::Off);
}
break;
case 0xf:
case 0x1:
// In both handshake and pulse modes, CA2 goes low on any read or write of Port A.
if(handshake_modes_[0] != HandshakeMode::None) {
set_control_line_output(Port::A, Line::Two, LineState::Off);
}
break;
}
}
template <typename T> void MOS6522<T>::write(int address, uint8_t value) {
address &= 0xf;
access(address);
switch(address) {
case 0x0: // Write Port B.
// Store locally and communicate outwards.
registers_.output[1] = value;
bus_handler_.set_port_output(Port::B, value, registers_.data_direction[1]); // TODO: handshake
bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
bus_handler_.set_port_output(Port::B, value, registers_.data_direction[1]);
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | ((registers_.peripheral_control&0x20) ? 0 : InterruptFlag::CB2ActiveEdge));
reevaluate_interrupts();
break;
case 0xf:
case 0x1:
case 0x1: // Write Port A.
registers_.output[0] = value;
bus_handler_.set_port_output(Port::A, value, registers_.data_direction[0]); // TODO: handshake
bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
bus_handler_.set_port_output(Port::A, value, registers_.data_direction[0]);
if(handshake_modes_[1] != HandshakeMode::None) {
set_control_line_output(Port::A, Line::Two, LineState::Off);
}
registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | ((registers_.peripheral_control&0x02) ? 0 : InterruptFlag::CB2ActiveEdge));
reevaluate_interrupts();
break;
case 0x2:
case 0x2: // Port B direction.
registers_.data_direction[1] = value;
break;
case 0x3:
case 0x3: // Port A direction.
registers_.data_direction[0] = value;
break;
@@ -54,32 +88,57 @@ template <typename T> void MOS6522<T>::set_register(int address, uint8_t value)
break;
// Shift
case 0xa: registers_.shift = value; break;
case 0xa:
registers_.shift = value;
shift_bits_remaining_ = 8;
registers_.interrupt_flags &= ~InterruptFlag::ShiftRegister;
reevaluate_interrupts();
break;
// Control
case 0xb:
registers_.auxiliary_control = value;
evaluate_cb2_output();
break;
case 0xc:
// printf("Peripheral control %02x\n", value);
case 0xc: {
// const auto old_peripheral_control = registers_.peripheral_control;
registers_.peripheral_control = value;
// TODO: simplify below; trying to avoid improper logging of unimplemented warnings in input mode
if(value & 0x08) {
switch(value & 0x0e) {
default: printf("Unimplemented control line mode %d\n", (value >> 1)&7); break;
case 0x0c: bus_handler_.set_control_line_output(Port::A, Line::Two, false); break;
case 0x0e: bus_handler_.set_control_line_output(Port::A, Line::Two, true); break;
int shift = 0;
for(int port = 0; port < 2; ++port) {
handshake_modes_[port] = HandshakeMode::None;
switch((value >> shift) & 0x0e) {
default: break;
case 0x00: // Negative interrupt input; set Cx2 interrupt on negative Cx2 transition, clear on access to Port x register.
case 0x02: // Independent negative interrupt input; set Cx2 interrupt on negative transition, don't clear automatically.
case 0x04: // Positive interrupt input; set Cx2 interrupt on positive Cx2 transition, clear on access to Port x register.
case 0x06: // Independent positive interrupt input; set Cx2 interrupt on positive transition, don't clear automatically.
set_control_line_output(Port(port), Line::Two, LineState::Input);
break;
case 0x08: // Handshake: set Cx2 to low on any read or write of Port x; set to high on an active transition of Cx1.
handshake_modes_[port] = HandshakeMode::Handshake;
set_control_line_output(Port(port), Line::Two, LineState::Off); // At a guess.
break;
case 0x0a: // Pulse output: Cx2 is low for one cycle following a read or write of Port x.
handshake_modes_[port] = HandshakeMode::Pulse;
set_control_line_output(Port(port), Line::Two, LineState::On);
break;
case 0x0c: // Manual output: Cx2 low.
set_control_line_output(Port(port), Line::Two, LineState::Off);
break;
case 0x0e: // Manual output: Cx2 high.
set_control_line_output(Port(port), Line::Two, LineState::On);
break;
}
shift += 4;
}
if(value & 0x80) {
switch(value & 0xe0) {
default: printf("Unimplemented control line mode %d\n", (value >> 5)&7); break;
case 0xc0: bus_handler_.set_control_line_output(Port::B, Line::Two, false); break;
case 0xe0: bus_handler_.set_control_line_output(Port::B, Line::Two, true); break;
}
}
break;
} break;
// Interrupt control
case 0xd:
@@ -96,14 +155,15 @@ 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) {
case 0x0:
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge);
reevaluate_interrupts();
return get_port_input(Port::B, registers_.data_direction[1], registers_.output[1]);
case 0xf: // TODO: handshake, latching
case 0xf:
case 0x1:
registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | InterruptFlag::CA2ActiveEdge);
reevaluate_interrupts();
@@ -128,7 +188,11 @@ template <typename T> uint8_t MOS6522<T>::get_register(int address) {
return registers_.timer[1] & 0x00ff;
case 0x9: return registers_.timer[1] >> 8;
case 0xa: return registers_.shift;
case 0xa:
shift_bits_remaining_ = 8;
registers_.interrupt_flags &= ~InterruptFlag::ShiftRegister;
reevaluate_interrupts();
return registers_.shift;
case 0xb: return registers_.auxiliary_control;
case 0xc: return registers_.peripheral_control;
@@ -141,7 +205,8 @@ template <typename T> uint8_t MOS6522<T>::get_register(int address) {
}
template <typename T> uint8_t MOS6522<T>::get_port_input(Port port, uint8_t output_mask, uint8_t output) {
uint8_t input = bus_handler_.get_port_input(port);
bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
const uint8_t input = bus_handler_.get_port_input(port);
return (input & ~output_mask) | (output & output_mask);
}
@@ -154,6 +219,238 @@ template <typename T> void MOS6522<T>::reevaluate_interrupts() {
bool new_interrupt_status = get_interrupt_line();
if(new_interrupt_status != last_posted_interrupt_status_) {
last_posted_interrupt_status_ = new_interrupt_status;
bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
bus_handler_.set_interrupt_status(new_interrupt_status);
}
}
template <typename T> void MOS6522<T>::set_control_line_input(Port port, Line line, bool value) {
switch(line) {
case Line::One:
if(value != control_inputs_[port].lines[line]) {
// In handshake mode, any transition on C[A/B]1 sets output high on C[A/B]2.
if(handshake_modes_[port] == HandshakeMode::Handshake) {
set_control_line_output(port, Line::Two, LineState::On);
}
// Set the proper transition interrupt bit if enabled.
if(value == !!(registers_.peripheral_control & (port ? 0x10 : 0x01))) {
registers_.interrupt_flags |= port ? InterruptFlag::CB1ActiveEdge : InterruptFlag::CA1ActiveEdge;
reevaluate_interrupts();
}
// If this is a transition on CB1, consider updating the shift register.
// TODO: and at least one full clock since the shift register was written?
if(port == Port::B) {
switch(shift_mode()) {
default: break;
case ShiftMode::InUnderCB1: if(value) shift_in(); break; // Shifts in are captured on a low-to-high transition.
case ShiftMode::OutUnderCB1: if(!value) shift_out(); break; // Shifts out are updated on a high-to-low transition.
}
}
}
control_inputs_[port].lines[line] = value;
break;
case Line::Two:
if( value != control_inputs_[port].lines[line] && // i.e. value has changed ...
!(registers_.peripheral_control & (port ? 0x80 : 0x08)) && // ... and line is input ...
value == !!(registers_.peripheral_control & (port ? 0x40 : 0x04)) // ... and it's either high or low, as required
) {
registers_.interrupt_flags |= port ? InterruptFlag::CB2ActiveEdge : InterruptFlag::CA2ActiveEdge;
reevaluate_interrupts();
}
control_inputs_[port].lines[line] = value;
break;
}
}
template <typename T> void MOS6522<T>::do_phase2() {
++ time_since_bus_handler_call_;
registers_.last_timer[0] = registers_.timer[0];
registers_.last_timer[1] = registers_.timer[1];
if(registers_.timer_needs_reload) {
registers_.timer_needs_reload = false;
registers_.timer[0] = registers_.timer_latch[0];
} else {
registers_.timer[0] --;
}
registers_.timer[1] --;
if(registers_.next_timer[0] >= 0) {
registers_.timer[0] = static_cast<uint16_t>(registers_.next_timer[0]);
registers_.next_timer[0] = -1;
}
if(registers_.next_timer[1] >= 0) {
registers_.timer[1] = static_cast<uint16_t>(registers_.next_timer[1]);
registers_.next_timer[1] = -1;
}
// In pulse modes, CA2 and CB2 go high again on the next clock edge.
if(handshake_modes_[1] == HandshakeMode::Pulse) {
set_control_line_output(Port::B, Line::Two, LineState::On);
}
if(handshake_modes_[0] == HandshakeMode::Pulse) {
set_control_line_output(Port::A, Line::Two, LineState::On);
}
// If the shift register is shifting according to the input clock, do a shift.
switch(shift_mode()) {
default: break;
case ShiftMode::InUnderPhase2: shift_in(); break;
case ShiftMode::OutUnderPhase2: shift_out(); break;
}
}
template <typename T> void MOS6522<T>::do_phase1() {
++ time_since_bus_handler_call_;
// IRQ is raised on the half cycle after overflow
if((registers_.timer[1] == 0xffff) && !registers_.last_timer[1] && timer_is_running_[1]) {
timer_is_running_[1] = false;
// If the shift register is shifting according to this timer, do a shift.
// TODO: "shift register is driven by only the low order 8 bits of timer 2"?
switch(shift_mode()) {
default: break;
case ShiftMode::InUnderT2: shift_in(); break;
case ShiftMode::OutUnderT2FreeRunning: shift_out(); break;
case ShiftMode::OutUnderT2: shift_out(); break; // TODO: present a clock on CB1.
}
registers_.interrupt_flags |= InterruptFlag::Timer2;
reevaluate_interrupts();
}
if((registers_.timer[0] == 0xffff) && !registers_.last_timer[0] && timer_is_running_[0]) {
registers_.interrupt_flags |= InterruptFlag::Timer1;
reevaluate_interrupts();
// Determine whether to reload.
if(registers_.auxiliary_control&0x40)
registers_.timer_needs_reload = true;
else
timer_is_running_[0] = false;
// Determine whether to toggle PB7.
if(registers_.auxiliary_control&0x80) {
registers_.output[1] ^= 0x80;
bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
bus_handler_.set_port_output(Port::B, registers_.output[1], registers_.data_direction[1]);
}
}
}
/*! Runs for a specified number of half cycles. */
template <typename T> void MOS6522<T>::run_for(const HalfCycles half_cycles) {
auto number_of_half_cycles = half_cycles.as_integral();
if(!number_of_half_cycles) return;
if(is_phase2_) {
do_phase2();
number_of_half_cycles--;
}
while(number_of_half_cycles >= 2) {
do_phase1();
do_phase2();
number_of_half_cycles -= 2;
}
if(number_of_half_cycles) {
do_phase1();
is_phase2_ = true;
} else {
is_phase2_ = false;
}
}
template <typename T> void MOS6522<T>::flush() {
bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
bus_handler_.flush();
}
/*! Runs for a specified number of cycles. */
template <typename T> void MOS6522<T>::run_for(const Cycles cycles) {
auto number_of_cycles = cycles.as_integral();
while(number_of_cycles--) {
do_phase1();
do_phase2();
}
}
/*! @returns @c true if the IRQ line is currently active; @c false otherwise. */
template <typename T> bool MOS6522<T>::get_interrupt_line() {
uint8_t interrupt_status = registers_.interrupt_flags & registers_.interrupt_enable & 0x7f;
return !!interrupt_status;
}
template <typename T> void MOS6522<T>::evaluate_cb2_output() {
// CB2 is a special case, being both the line the shift register can output to,
// and one that can be used as an input or handshaking output according to the
// peripheral control register.
// My guess: other CB2 functions work only if the shift register is disabled (?).
if(shift_mode() != ShiftMode::Disabled) {
// Shift register is enabled, one way or the other; but announce only output.
if(is_shifting_out()) {
// Output mode; set the level according to the current top of the shift register.
bus_handler_.set_control_line_output(Port::B, Line::Two, !!(registers_.shift & 0x80));
} else {
// Input mode.
bus_handler_.set_control_line_output(Port::B, Line::Two, true);
}
} else {
// Shift register is disabled.
bus_handler_.set_control_line_output(Port::B, Line::Two, control_outputs_[1].lines[1] != LineState::Off);
}
}
template <typename T> void MOS6522<T>::set_control_line_output(Port port, Line line, LineState value) {
if(port == Port::B && line == Line::Two) {
control_outputs_[port].lines[line] = value;
evaluate_cb2_output();
} else {
// Do nothing if unchanged.
if(value == control_outputs_[port].lines[line]) {
return;
}
control_outputs_[port].lines[line] = value;
if(value != LineState::Input) {
bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
bus_handler_.set_control_line_output(port, line, value != LineState::Off);
}
}
}
template <typename T> void MOS6522<T>::shift_in() {
registers_.shift = uint8_t((registers_.shift << 1) | (control_inputs_[1].lines[1] ? 1 : 0));
--shift_bits_remaining_;
if(!shift_bits_remaining_) {
registers_.interrupt_flags |= InterruptFlag::ShiftRegister;
reevaluate_interrupts();
}
}
template <typename T> void MOS6522<T>::shift_out() {
// When shifting out, the shift register rotates rather than strictly shifts.
// TODO: is that true for all modes?
if(shift_mode() == ShiftMode::OutUnderT2FreeRunning || shift_bits_remaining_) {
registers_.shift = uint8_t((registers_.shift << 1) | (registers_.shift >> 7));
evaluate_cb2_output();
--shift_bits_remaining_;
if(!shift_bits_remaining_) {
registers_.interrupt_flags |= InterruptFlag::ShiftRegister;
reevaluate_interrupts();
}
}
}
}
}

View File

@@ -21,7 +21,7 @@ class MOS6522Storage {
// The registers
struct Registers {
// "A low reset (RES) input clears all R6522 internal registers to logic 0"
// "A low reset (RES) input clears all R6522 internal registers to logic 0"
uint8_t output[2] = {0, 0};
uint8_t input[2] = {0, 0};
uint8_t data_direction[2] = {0, 0};
@@ -37,14 +37,27 @@ class MOS6522Storage {
bool timer_needs_reload = false;
} registers_;
// control state
// Control state.
struct {
bool line_one = false;
bool line_two = false;
bool lines[2] = {false, false};
} control_inputs_[2];
enum class LineState {
On, Off, Input
};
struct {
LineState lines[2] = {LineState::Input, LineState::Input};
} control_outputs_[2];
enum class HandshakeMode {
None,
Handshake,
Pulse
} handshake_modes_[2] = { HandshakeMode::None, HandshakeMode::None };
bool timer_is_running_[2] = {false, false};
bool last_posted_interrupt_status_ = false;
int shift_bits_remaining_ = 8;
enum InterruptFlag: uint8_t {
CA2ActiveEdge = 1 << 0,
@@ -55,6 +68,23 @@ class MOS6522Storage {
Timer2 = 1 << 5,
Timer1 = 1 << 6,
};
enum class ShiftMode {
Disabled = 0,
InUnderT2 = 1,
InUnderPhase2 = 2,
InUnderCB1 = 3,
OutUnderT2FreeRunning = 4,
OutUnderT2 = 5,
OutUnderPhase2 = 6,
OutUnderCB1 = 7
};
ShiftMode shift_mode() const {
return ShiftMode((registers_.auxiliary_control >> 2) & 7);
}
bool is_shifting_out() const {
return registers_.auxiliary_control & 0x10;
}
};
}

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

@@ -34,7 +34,7 @@ class AudioGenerator: public ::Outputs::Speaker::SampleSource {
private:
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
unsigned int counters_[4] = {2, 1, 0, 0}; // create a slight phase offset for the three channels
unsigned int counters_[4] = {2, 1, 0, 0}; // create a slight phase offset for the three channels
unsigned int shift_registers_[4] = {0, 0, 0, 0};
uint8_t control_registers_[4] = {0, 0, 0, 0};
int16_t volume_ = 0;
@@ -58,29 +58,18 @@ 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:
MOS6560(BusHandler &bus_handler) :
bus_handler_(bus_handler),
crt_(new Outputs::CRT::CRT(65*4, 4, Outputs::CRT::DisplayType::NTSC60, 2)),
crt_(65*4, 1, Outputs::Display::Type::NTSC60, Outputs::Display::InputDataType::Luminance8Phase8),
audio_generator_(audio_queue_),
speaker_(audio_generator_)
{
crt_->set_svideo_sampling_function(
"vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
"{"
"vec2 yc = texture(texID, coordinate).rg / vec2(255.0);"
"float phaseOffset = 6.283185308 * 2.0 * yc.y;"
"float chroma = step(yc.y, 0.75) * cos(phase + phaseOffset);"
"return vec2(yc.x, chroma);"
"}");
// default to s-video output
crt_->set_video_signal(Outputs::CRT::VideoSignal::SVideo);
crt_.set_display_type(Outputs::Display::DisplayType::SVideo);
// default to NTSC
set_output_mode(OutputMode::NTSC);
@@ -94,7 +83,9 @@ template <class BusHandler> class MOS6560 {
speaker_.set_input_rate(static_cast<float>(clock_rate / 4.0));
}
Outputs::CRT::CRT *get_crt() { return crt_.get(); }
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) {
@@ -117,12 +108,12 @@ template <class BusHandler> class MOS6560 {
// Chrominances are encoded such that 0-128 is a complete revolution of phase;
// anything above 191 disables the colour subcarrier. Phase is relative to the
// colour burst, so 0 is green.
// colour burst, so 0 is green (NTSC) or blue/violet (PAL).
const uint8_t pal_chrominances[16] = {
255, 255, 37, 101,
19, 86, 123, 59,
46, 53, 37, 101,
19, 86, 123, 59,
255, 255, 90, 20,
96, 42, 8, 72,
84, 90, 90, 20,
96, 42, 8, 72,
};
const uint8_t ntsc_chrominances[16] = {
255, 255, 121, 57,
@@ -131,12 +122,12 @@ template <class BusHandler> class MOS6560 {
103, 42, 80, 16,
};
const uint8_t *chrominances;
Outputs::CRT::DisplayType display_type;
Outputs::Display::Type display_type;
switch(output_mode) {
default:
chrominances = pal_chrominances;
display_type = Outputs::CRT::DisplayType::PAL50;
display_type = Outputs::Display::Type::PAL50;
timing_.cycles_per_line = 71;
timing_.line_counter_increment_offset = 4;
timing_.final_line_increment_position = timing_.cycles_per_line - timing_.line_counter_increment_offset;
@@ -146,7 +137,7 @@ template <class BusHandler> class MOS6560 {
case OutputMode::NTSC:
chrominances = ntsc_chrominances;
display_type = Outputs::CRT::DisplayType::NTSC60;
display_type = Outputs::Display::Type::NTSC60;
timing_.cycles_per_line = 65;
timing_.line_counter_increment_offset = 40;
timing_.final_line_increment_position = 58;
@@ -155,14 +146,14 @@ template <class BusHandler> class MOS6560 {
break;
}
crt_->set_new_display_type(static_cast<unsigned int>(timing_.cycles_per_line*4), display_type);
crt_.set_new_display_type(timing_.cycles_per_line*4, display_type);
switch(output_mode) {
case OutputMode::PAL:
crt_->set_visible_area(Outputs::CRT::Rect(0.1f, 0.07f, 0.9f, 0.9f));
crt_.set_visible_area(Outputs::Display::Rect(0.1f, 0.07f, 0.9f, 0.9f));
break;
case OutputMode::NTSC:
crt_->set_visible_area(Outputs::CRT::Rect(0.05f, 0.05f, 0.9f, 0.9f));
crt_.set_visible_area(Outputs::Display::Rect(0.05f, 0.05f, 0.9f, 0.9f));
break;
}
@@ -180,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_;
@@ -284,17 +275,17 @@ template <class BusHandler> class MOS6560 {
// update the CRT
if(this_state_ != output_state_) {
switch(output_state_) {
case State::Sync: crt_->output_sync(cycles_in_state_ * 4); break;
case State::ColourBurst: crt_->output_colour_burst(cycles_in_state_ * 4, (is_odd_frame_ || is_odd_line_) ? 128 : 0); break;
case State::Border: output_border(cycles_in_state_ * 4); break;
case State::Pixels: crt_->output_data(cycles_in_state_ * 4); break;
case State::Sync: crt_.output_sync(cycles_in_state_ * 4); break;
case State::ColourBurst: crt_.output_colour_burst(cycles_in_state_ * 4, (is_odd_frame_ || is_odd_line_) ? 128 : 0); break;
case State::Border: output_border(cycles_in_state_ * 4); break;
case State::Pixels: crt_.output_data(cycles_in_state_ * 4); break;
}
output_state_ = this_state_;
cycles_in_state_ = 0;
pixel_pointer = nullptr;
if(output_state_ == State::Pixels) {
pixel_pointer = reinterpret_cast<uint16_t *>(crt_->allocate_write_area(260));
pixel_pointer = reinterpret_cast<uint16_t *>(crt_.begin_data(260));
}
}
cycles_in_state_++;
@@ -363,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) {
@@ -427,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];
@@ -438,7 +429,7 @@ template <class BusHandler> class MOS6560 {
private:
BusHandler &bus_handler_;
std::unique_ptr<Outputs::CRT::CRT> crt_;
Outputs::CRT::CRT crt_;
Concurrency::DeferringAsyncTaskQueue audio_queue_;
AudioGenerator audio_generator_;
@@ -451,12 +442,12 @@ template <class BusHandler> class MOS6560 {
// register state
struct {
bool interlaced, tall_characters;
bool interlaced = false, tall_characters = false;
uint8_t first_column_location, first_row_location;
uint8_t number_of_columns, number_of_rows;
uint16_t character_cell_start_address, video_matrix_start_address;
uint16_t backgroundColour, borderColour, auxiliary_colour;
bool invertedCells;
bool invertedCells = false;
uint8_t direct_values[16];
} registers_;
@@ -465,7 +456,7 @@ template <class BusHandler> class MOS6560 {
enum State {
Sync, ColourBurst, Border, Pixels
} this_state_, output_state_;
unsigned int cycles_in_state_;
int cycles_in_state_;
// counters that cover an entire field
int horizontal_counter_ = 0, vertical_counter_ = 0;
@@ -493,7 +484,7 @@ template <class BusHandler> class MOS6560 {
}
// latches dictating start and length of drawing
bool vertical_drawing_latch_, horizontal_drawing_latch_;
bool vertical_drawing_latch_ = false, horizontal_drawing_latch_ = false;
int rows_this_field_, columns_this_line_;
// current drawing position counter
@@ -511,10 +502,10 @@ template <class BusHandler> class MOS6560 {
uint16_t colours_[16];
uint16_t *pixel_pointer;
void output_border(unsigned int number_of_cycles) {
uint16_t *colour_pointer = reinterpret_cast<uint16_t *>(crt_->allocate_write_area(1));
void output_border(int number_of_cycles) {
uint16_t *colour_pointer = reinterpret_cast<uint16_t *>(crt_.begin_data(1));
if(colour_pointer) *colour_pointer = registers_.borderColour;
crt_->output_level(number_of_cycles);
crt_.output_level(number_of_cycles);
}
struct {

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

@@ -7,9 +7,8 @@
//
#include "i8272.hpp"
//#include "../../Storage/Disk/Encodings/MFM/Encoder.hpp"
#include <cstdio>
#include "../../Outputs/Log.hpp"
using namespace Intel::i8272;
@@ -96,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();
}
}
@@ -109,13 +108,13 @@ 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.
int direction = (drives_[c].target_head_position < drives_[c].head_position) ? -1 : 1;
printf("Target %d versus believed %d\n", drives_[c].target_head_position, drives_[c].head_position);
LOG("Target " << PADDEC(0) << drives_[c].target_head_position << " versus believed " << static_cast<int>(drives_[c].head_position));
select_drive(c);
get_drive().step(Storage::Disk::HeadPosition(direction));
if(drives_[c].target_head_position >= 0) drives_[c].head_position += direction;
@@ -142,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;
@@ -164,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;
@@ -182,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();
@@ -293,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,
@@ -386,17 +385,17 @@ void i8272::posit_event(int event_type) {
// the index hole limit is breached or a sector is found with a cylinder, head, sector and size equal to the
// values in the internal registers.
index_hole_limit_ = 2;
// printf("Seeking %02x %02x %02x %02x\n", cylinder_, head_, sector_, size_);
// LOG("Seeking " << PADDEC(0) << cylinder_ << " " << head_ " " << sector_ << " " << size_);
find_next_sector:
FIND_HEADER();
if(!index_hole_limit_) {
// Two index holes have passed wihout finding the header sought.
// printf("Not found\n");
// LOG("Not found");
SetNoData();
goto abort;
}
index_hole_count_ = 0;
// printf("Header\n");
// LOG("Header");
READ_HEADER();
if(index_hole_count_) {
// This implies an index hole was sighted within the header. Error out.
@@ -407,11 +406,11 @@ void i8272::posit_event(int event_type) {
// This implies a CRC error in the header; mark as such but continue.
SetDataError();
}
// printf("Considering %02x %02x %02x %02x [%04x]\n", header_[0], header_[1], header_[2], header_[3], get_crc_generator().get_value());
// LOG("Considering << PADHEX(2) << header_[0] << " " << header_[1] << " " << header_[2] << " " << header_[3] << " [" << get_crc_generator().get_value() << "]");
if(header_[0] != cylinder_ || header_[1] != head_ || header_[2] != sector_ || header_[3] != size_) goto find_next_sector;
// Branch to whatever is supposed to happen next
// printf("Proceeding\n");
// LOG("Proceeding");
switch(command_[0] & 0x1f) {
case CommandReadData:
case CommandReadDeletedData:
@@ -425,7 +424,13 @@ void i8272::posit_event(int event_type) {
// Performs the read data or read deleted data command.
read_data:
printf("Read [deleted] data [%02x %02x %02x %02x ... %02x %02x]\n", command_[2], command_[3], command_[4], command_[5], command_[6], command_[8]);
LOG(PADHEX(2) << "Read [deleted] data ["
<< static_cast<int>(command_[2]) << " "
<< static_cast<int>(command_[3]) << " "
<< static_cast<int>(command_[4]) << " "
<< static_cast<int>(command_[5]) << " ... "
<< static_cast<int>(command_[6]) << " "
<< static_cast<int>(command_[8]) << "]");
read_next_data:
goto read_write_find_header;
@@ -509,7 +514,13 @@ void i8272::posit_event(int event_type) {
goto post_st012chrn;
write_data:
printf("Write [deleted] data [%02x %02x %02x %02x ... %02x %02x]\n", command_[2], command_[3], command_[4], command_[5], command_[6], command_[8]);
LOG(PADHEX(2) << "Write [deleted] data ["
<< static_cast<int>(command_[2]) << " "
<< static_cast<int>(command_[3]) << " "
<< static_cast<int>(command_[4]) << " "
<< static_cast<int>(command_[5]) << " ... "
<< static_cast<int>(command_[6]) << " "
<< static_cast<int>(command_[8]) << "]");
if(get_drive().get_is_read_only()) {
SetNotWriteable();
@@ -544,7 +555,7 @@ void i8272::posit_event(int event_type) {
goto write_loop;
}
printf("Wrote %d bytes\n", distance_into_section_);
LOG("Wrote " << PADDEC(0) << distance_into_section_ << " bytes");
write_crc();
expects_input_ = false;
WAIT_FOR_EVENT(Event::DataWritten);
@@ -560,7 +571,7 @@ void i8272::posit_event(int event_type) {
// Performs the read ID command.
read_id:
// Establishes the drive and head being addressed, and whether in double density mode.
printf("Read ID [%02x %02x]\n", command_[0], command_[1]);
LOG(PADHEX(2) << "Read ID [" << static_cast<int>(command_[0]) << " " << static_cast<int>(command_[1]) << "]");
// Sets a maximum index hole limit of 2 then waits either until it finds a header mark or sees too many index holes.
// If a header mark is found, reads in the following bytes that produce a header. Otherwise branches to data not found.
@@ -582,7 +593,11 @@ void i8272::posit_event(int event_type) {
// Performs read track.
read_track:
printf("Read track [%02x %02x %02x %02x]\n", command_[2], command_[3], command_[4], command_[5]);
LOG(PADHEX(2) << "Read track ["
<< static_cast<int>(command_[2]) << " "
<< static_cast<int>(command_[3]) << " "
<< static_cast<int>(command_[4]) << " "
<< static_cast<int>(command_[5]) << "]");
// Wait for the index hole.
WAIT_FOR_EVENT(Event::IndexHole);
@@ -623,7 +638,7 @@ void i8272::posit_event(int event_type) {
// Performs format [/write] track.
format_track:
printf("Format track\n");
LOG("Format track");
if(get_drive().get_is_read_only()) {
SetNotWriteable();
goto abort;
@@ -667,7 +682,12 @@ void i8272::posit_event(int event_type) {
break;
}
printf("W: %02x %02x %02x %02x, %04x\n", header_[0], header_[1], header_[2], header_[3], get_crc_generator().get_value());
LOG(PADHEX(2) << "W:"
<< static_cast<int>(header_[0]) << " "
<< static_cast<int>(header_[1]) << " "
<< static_cast<int>(header_[2]) << " "
<< static_cast<int>(header_[3]) << ", "
<< get_crc_generator().get_value());
write_crc();
// Write the sector body.
@@ -699,15 +719,15 @@ void i8272::posit_event(int event_type) {
goto post_st012chrn;
scan_low:
printf("Scan low unimplemented!!\n");
ERROR("Scan low unimplemented!!");
goto wait_for_command;
scan_low_or_equal:
printf("Scan low or equal unimplemented!!\n");
ERROR("Scan low or equal unimplemented!!");
goto wait_for_command;
scan_high_or_equal:
printf("Scan high or equal unimplemented!!\n");
ERROR("Scan high or equal unimplemented!!");
goto wait_for_command;
// Performs both recalibrate and seek commands. These commands occur asynchronously, so the actual work
@@ -738,11 +758,11 @@ void i8272::posit_event(int event_type) {
// up in run_for understands to mean 'keep going until track 0 is active').
if(command_.size() > 2) {
drives_[drive].target_head_position = command_[2];
printf("Seek to %02x\n", command_[2]);
LOG(PADHEX(2) << "Seek to " << static_cast<int>(command_[2]));
} else {
drives_[drive].target_head_position = -1;
drives_[drive].head_position = 0;
printf("Recalibrate\n");
LOG("Recalibrate");
}
// Check whether any steps are even needed; if not then mark as completed already.
@@ -755,7 +775,7 @@ void i8272::posit_event(int event_type) {
// Performs sense interrupt status.
sense_interrupt_status:
printf("Sense interrupt status\n");
LOG("Sense interrupt status");
{
// Find the first drive that is in the CompletedSeeking state.
int found_drive = -1;
@@ -783,7 +803,7 @@ void i8272::posit_event(int event_type) {
// Performs specify.
specify:
// Just store the values, and terminate the command.
printf("Specify\n");
LOG("Specify");
step_rate_time_ = 16 - (command_[1] >> 4); // i.e. 1 to 16ms
head_unload_time_ = (command_[1] & 0x0f) << 4; // i.e. 16 to 240ms
head_load_time_ = command_[2] & ~1; // i.e. 2 to 254 ms in increments of 2ms
@@ -794,7 +814,7 @@ void i8272::posit_event(int event_type) {
goto wait_for_command;
sense_drive_status:
printf("Sense drive status\n");
LOG("Sense drive status");
{
int drive = command_[1] & 3;
select_drive(drive);
@@ -833,11 +853,11 @@ void i8272::posit_event(int event_type) {
// Posts whatever is in result_stack_ as a result phase. Be aware that it is a stack, so the
// last thing in it will be returned first.
post_result:
printf("Result to %02x, main %02x: ", command_[0] & 0x1f, main_status_);
LOGNBR(PADHEX(2) << "Result to " << static_cast<int>(command_[0] & 0x1f) << ", main " << static_cast<int>(main_status_) << "; ");
for(std::size_t c = 0; c < result_stack_.size(); c++) {
printf("%02x ", result_stack_[result_stack_.size() - 1 - c]);
LOGNBR(" " << static_cast<int>(result_stack_[result_stack_.size() - 1 - c]));
}
printf("\n");
LOGNBR(std::endl);
// Set ready to send data to the processor, no longer in non-DMA execution phase.
is_executing_ = false;
@@ -845,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];

268
Components/8530/z8530.cpp Normal file
View File

@@ -0,0 +1,268 @@
//
// 8530.cpp
// Clock Signal
//
// Created by Thomas Harte on 07/06/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#include "z8530.hpp"
#include "../../Outputs/Log.hpp"
using namespace Zilog::SCC;
void z8530::reset() {
// TODO.
}
bool z8530::get_interrupt_line() {
return
(master_interrupt_control_ & 0x8) &&
(
channels_[0].get_interrupt_line() ||
channels_[1].get_interrupt_line()
);
}
std::uint8_t z8530::read(int address) {
if(address & 2) {
// Read data register for channel
return 0x00;
} else {
// Read control register for channel.
uint8_t result = 0;
switch(pointer_) {
default:
result = channels_[address & 1].read(address & 2, pointer_);
break;
case 2: // Handled non-symmetrically between channels.
if(address & 1) {
LOG("[SCC] Unimplemented: register 2 status bits");
} else {
result = interrupt_vector_;
// Modify the vector if permitted.
// if(master_interrupt_control_ & 1) {
for(int port = 0; port < 2; ++port) {
// TODO: the logic below assumes that DCD is the only implemented interrupt. Fix.
if(channels_[port].get_interrupt_line()) {
const uint8_t shift = 1 + 3*((master_interrupt_control_ & 0x10) >> 4);
const uint8_t mask = uint8_t(~(7 << shift));
result = uint8_t(
(result & mask) |
((1 | ((port == 1) ? 4 : 0)) << shift)
);
break;
}
}
// }
}
break;
}
pointer_ = 0;
update_delegate();
return result;
}
return 0x00;
}
void z8530::write(int address, std::uint8_t value) {
if(address & 2) {
// Write data register for channel.
} else {
// Write control register for channel.
// Most registers are per channel, but a couple are shared; sever
// them here.
switch(pointer_) {
default:
channels_[address & 1].write(address & 2, pointer_, value);
break;
case 2: // Interrupt vector register; shared between both channels.
interrupt_vector_ = value;
LOG("[SCC] Interrupt vector set to " << PADHEX(2) << int(value));
break;
case 9: // Master interrupt and reset register; also shared between both channels.
LOG("[SCC] Master interrupt and reset register: " << PADHEX(2) << int(value));
master_interrupt_control_ = value;
break;
}
// The pointer number resets to 0 after every access, but if it is zero
// then crib at least the next set of pointer bits (which, similarly, are shared
// between the two channels).
if(pointer_) {
pointer_ = 0;
} else {
// The lowest three bits are the lowest three bits of the pointer.
pointer_ = value & 7;
// If the command part of the byte is a 'point high', also set the
// top bit of the pointer.
if(((value >> 3)&7) == 1) {
pointer_ |= 8;
}
}
}
update_delegate();
}
void z8530::set_dcd(int port, bool level) {
channels_[port].set_dcd(level);
update_delegate();
}
// MARK: - Channel implementations
uint8_t z8530::Channel::read(bool data, uint8_t pointer) {
// If this is a data read, just return it.
if(data) {
return data_;
} else {
// Otherwise, this is a control read...
switch(pointer) {
default:
LOG("[SCC] Unrecognised control read from register " << int(pointer));
return 0x00;
case 0:
return dcd_ ? 0x8 : 0x0;
case 0xf:
return external_interrupt_status_;
}
}
return 0x00;
}
void z8530::Channel::write(bool data, uint8_t pointer, uint8_t value) {
if(data) {
data_ = value;
return;
} else {
switch(pointer) {
default:
LOG("[SCC] Unrecognised control write: " << PADHEX(2) << int(value) << " to register " << int(pointer));
break;
case 0x0: // Write register 0 — CRC reset and other functions.
// Decode CRC reset instructions.
switch(value >> 6) {
default: /* Do nothing. */ break;
case 1:
LOG("[SCC] TODO: reset Rx CRC checker.");
break;
case 2:
LOG("[SCC] TODO: reset Tx CRC checker.");
break;
case 3:
LOG("[SCC] TODO: reset Tx underrun/EOM latch.");
break;
}
// Decode command code.
switch((value >> 3)&7) {
default: /* Do nothing. */ break;
case 2:
// LOG("[SCC] reset ext/status interrupts.");
external_status_interrupt_ = false;
external_interrupt_status_ = 0;
break;
case 3:
LOG("[SCC] TODO: send abort (SDLC).");
break;
case 4:
LOG("[SCC] TODO: enable interrupt on next Rx character.");
break;
case 5:
LOG("[SCC] TODO: reset Tx interrupt pending.");
break;
case 6:
LOG("[SCC] TODO: reset error.");
break;
case 7:
LOG("[SCC] TODO: reset highest IUS.");
break;
}
break;
case 0x1: // Write register 1 — Transmit/Receive Interrupt and Data Transfer Mode Definition.
interrupt_mask_ = value;
break;
case 0x4: // Write register 4 — Transmit/Receive Miscellaneous Parameters and Modes.
// Bits 0 and 1 select parity mode.
if(!(value&1)) {
parity_ = Parity::Off;
} else {
parity_ = (value&2) ? Parity::Even : Parity::Odd;
}
// Bits 2 and 3 select stop bits.
switch((value >> 2)&3) {
default: stop_bits_ = StopBits::Synchronous; break;
case 1: stop_bits_ = StopBits::OneBit; break;
case 2: stop_bits_ = StopBits::OneAndAHalfBits; break;
case 3: stop_bits_ = StopBits::TwoBits; break;
}
// Bits 4 and 5 pick a sync mode.
switch((value >> 4)&3) {
default: sync_mode_ = Sync::Monosync; break;
case 1: sync_mode_ = Sync::Bisync; break;
case 2: sync_mode_ = Sync::SDLC; break;
case 3: sync_mode_ = Sync::External; break;
}
// Bits 6 and 7 select a clock rate multiplier, unless synchronous
// mode is enabled (and this is ignored if sync mode is external).
if(stop_bits_ == StopBits::Synchronous) {
clock_rate_multiplier_ = 1;
} else {
switch((value >> 6)&3) {
default: clock_rate_multiplier_ = 1; break;
case 1: clock_rate_multiplier_ = 16; break;
case 2: clock_rate_multiplier_ = 32; break;
case 3: clock_rate_multiplier_ = 64; break;
}
}
break;
case 0xf: // Write register 15 — External/Status Interrupt Control.
external_interrupt_mask_ = value;
break;
}
}
}
void z8530::Channel::set_dcd(bool level) {
if(dcd_ == level) return;
dcd_ = level;
if(external_interrupt_mask_ & 0x8) {
external_status_interrupt_ = true;
external_interrupt_status_ |= 0x8;
}
}
bool z8530::Channel::get_interrupt_line() {
return
(interrupt_mask_ & 1) && external_status_interrupt_;
// TODO: other potential causes of an interrupt.
}
void z8530::update_delegate() {
const bool interrupt_line = get_interrupt_line();
if(interrupt_line != previous_interrupt_line_) {
previous_interrupt_line_ = interrupt_line;
if(delegate_) delegate_->did_change_interrupt_status(this, interrupt_line);
}
}

99
Components/8530/z8530.hpp Normal file
View File

@@ -0,0 +1,99 @@
//
// z8530.hpp
// Clock Signal
//
// Created by Thomas Harte on 07/06/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef z8530_hpp
#define z8530_hpp
#include <cstdint>
namespace Zilog {
namespace SCC {
/*!
Models the Zilog 8530 SCC, a serial adaptor.
*/
class z8530 {
public:
/*
**Interface for emulated machine.**
Notes on addressing below:
There's no inherent ordering of the two 'address' lines,
A/B and C/D, but the methods below assume:
A/B = A0
C/D = A1
*/
std::uint8_t read(int address);
void write(int address, std::uint8_t value);
void reset();
bool get_interrupt_line();
struct Delegate {
virtual void did_change_interrupt_status(z8530 *, bool new_status) = 0;
};
void set_delegate(Delegate *delegate) {
delegate_ = delegate;
}
/*
**Interface for serial port input.**
*/
void set_dcd(int port, bool level);
private:
class Channel {
public:
uint8_t read(bool data, uint8_t pointer);
void write(bool data, uint8_t pointer, uint8_t value);
void set_dcd(bool level);
bool get_interrupt_line();
private:
uint8_t data_ = 0xff;
enum class Parity {
Even, Odd, Off
} parity_ = Parity::Off;
enum class StopBits {
Synchronous, OneBit, OneAndAHalfBits, TwoBits
} stop_bits_ = StopBits::Synchronous;
enum class Sync {
Monosync, Bisync, SDLC, External
} sync_mode_ = Sync::Monosync;
int clock_rate_multiplier_ = 1;
uint8_t interrupt_mask_ = 0; // i.e. Write Register 0x1.
uint8_t external_interrupt_mask_ = 0; // i.e. Write Register 0xf.
bool external_status_interrupt_ = false;
uint8_t external_interrupt_status_ = 0;
bool dcd_ = false;
} channels_[2];
uint8_t pointer_ = 0;
uint8_t interrupt_vector_ = 0;
uint8_t master_interrupt_control_ = 0;
bool previous_interrupt_line_ = false;
void update_delegate();
Delegate *delegate_ = nullptr;
};
}
}
#endif /* z8530_hpp */

File diff suppressed because it is too large Load Diff

View File

@@ -17,6 +17,7 @@
#include <cstdint>
namespace TI {
namespace TMS {
/*!
Provides emulation of the TMS9918a, TMS9928 and TMS9929. Likely in the future to be the
@@ -29,30 +30,25 @@ namespace TI {
These chips have only one non-on-demand interaction with the outside world: an interrupt line.
See get_time_until_interrupt and get_interrupt_line for asynchronous operation options.
*/
class TMS9918: public TMS9918Base {
class TMS9918: public Base {
public:
enum Personality {
TMS9918A, // includes the 9928 and 9929; set TV standard and output device as desired.
};
/*!
Constructs an instance of the drive controller that behaves according to personality @c p.
@param p The type of controller to emulate.
*/
TMS9918(Personality p);
enum TVStandard {
/*! i.e. 50Hz output at around 312.5 lines/field */
PAL,
/*! i.e. 60Hz output at around 262.5 lines/field */
NTSC
};
/*! Sets the TV standard for this TMS, if that is hard-coded in hardware. */
void set_tv_standard(TVStandard standard);
/*! Provides the CRT this TMS is connected to. */
Outputs::CRT::CRT *get_crt();
/*! 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);
/*!
Runs the VCP for the number of cycles indicate; it is an implicit assumption of the code
@@ -61,26 +57,46 @@ class TMS9918: public TMS9918Base {
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();
/*! Gets the current latched horizontal counter; provided by the Master System only. */
uint8_t get_latched_horizontal_counter();
/*! Latches the current horizontal counter. */
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.
*/
HalfCycles get_time_until_interrupt();
/*!
Returns the amount of time until the nominated line interrupt position is
reached on line @c line. If no line interrupt position is defined for
this VDP, returns the time until the 'beginning' of that line, whatever
that may mean.
@line is relative to the first pixel line of the display and may be negative.
*/
HalfCycles get_time_until_line(int line);
/*!
@returns @c true if the interrupt line is currently active; @c false otherwise.
*/
bool get_interrupt_line();
};
};
}
}
#endif /* TMS9918_hpp */

View File

@@ -12,90 +12,848 @@
#include "../../../Outputs/CRT/CRT.hpp"
#include "../../../ClockReceiver/ClockReceiver.hpp"
#include <cassert>
#include <cstdint>
#include <memory>
#include <vector>
namespace TI {
namespace TMS {
enum Personality {
TMS9918A, // includes the 9928 and 9929; set TV standard and output device as desired.
V9938,
V9958,
SMSVDP,
SMS2VDP,
GGVDP,
};
enum class TVStandard {
/*! i.e. 50Hz output at around 312.5 lines/field */
PAL,
/*! i.e. 60Hz output at around 262.5 lines/field */
NTSC
};
#define is_sega_vdp(x) ((x) >= SMSVDP)
class Base {
public:
static const uint32_t palette_pack(uint8_t r, uint8_t g, uint8_t b) {
uint32_t result = 0;
uint8_t *const result_ptr = reinterpret_cast<uint8_t *>(&result);
result_ptr[0] = r;
result_ptr[1] = g;
result_ptr[2] = b;
result_ptr[3] = 0;
return result;
}
class TMS9918Base {
protected:
TMS9918Base();
const static int output_lag = 11; // i.e. pixel output will occur 11 cycles after corresponding data read.
std::unique_ptr<Outputs::CRT::CRT> crt_;
// The default TMS palette.
const uint32_t palette[16] = {
palette_pack(0, 0, 0),
palette_pack(0, 0, 0),
palette_pack(33, 200, 66),
palette_pack(94, 220, 120),
uint8_t ram_[16384];
palette_pack(84, 85, 237),
palette_pack(125, 118, 252),
palette_pack(212, 82, 77),
palette_pack(66, 235, 245),
palette_pack(252, 85, 84),
palette_pack(255, 121, 120),
palette_pack(212, 193, 84),
palette_pack(230, 206, 128),
palette_pack(33, 176, 59),
palette_pack(201, 91, 186),
palette_pack(204, 204, 204),
palette_pack(255, 255, 255)
};
Base(Personality p);
const Personality personality_;
Outputs::CRT::CRT crt_;
TVStandard tv_standard_ = TVStandard::NTSC;
// Holds the contents of this VDP's connected DRAM.
std::vector<uint8_t> ram_;
// Holds the state of the DRAM/CRAM-access mechanism.
uint16_t ram_pointer_ = 0;
uint8_t read_ahead_buffer_ = 0;
enum class MemoryAccess {
Read, Write, None
} queued_access_ = MemoryAccess::None;
int cycles_until_access_ = 0;
int minimum_access_column_ = 0;
int vram_access_delay() {
// This seems to be correct for all currently-modelled VDPs;
// it's the delay between an external device scheduling a
// read or write and the very first time that can occur
// (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 6;
}
// Holds the main status register.
uint8_t status_ = 0;
bool write_phase_ = false;
uint8_t low_write_ = 0;
// Current state of programmer input.
bool write_phase_ = false; // Determines whether the VDP is expecting the low or high byte of a write.
uint8_t low_write_ = 0; // Buffers the low byte of a write.
// The various register flags.
int next_screen_mode_ = 0, screen_mode_ = 0;
bool next_blank_screen_ = true, blank_screen_ = true;
// Various programmable flags.
bool mode1_enable_ = false;
bool mode2_enable_ = false;
bool mode3_enable_ = false;
bool blank_display_ = false;
bool sprites_16x16_ = false;
bool sprites_magnified_ = false;
bool generate_interrupts_ = false;
int sprite_height_ = 8;
uint16_t pattern_name_address_ = 0;
uint16_t colour_table_address_ = 0;
uint16_t pattern_generator_table_address_ = 0;
uint16_t sprite_attribute_table_address_ = 0;
uint16_t sprite_generator_table_address_ = 0;
size_t pattern_name_address_ = 0; // i.e. address of the tile map.
size_t colour_table_address_ = 0; // address of the colour map (if applicable).
size_t pattern_generator_table_address_ = 0; // address of the tile contents.
size_t sprite_attribute_table_address_ = 0; // address of the sprite list.
size_t sprite_generator_table_address_ = 0; // address of the sprite contents.
uint8_t text_colour_ = 0;
uint8_t background_colour_ = 0;
HalfCycles half_cycles_into_frame_;
int column_ = 0, row_ = 0, output_column_ = 0;
// This implementation of this chip officially accepts a 3.58Mhz clock, but runs
// internally at 5.37Mhz. The following two help to maintain a lossless conversion
// from the one to the other.
int cycles_error_ = 0;
uint32_t *pixel_target_ = nullptr, *pixel_base_ = nullptr;
HalfCycles half_cycles_before_internal_cycles(int internal_cycles);
void output_border(int cycles);
// Internal mechanisms for position tracking.
int latched_column_ = 0;
// Vertical timing details.
int frame_lines_ = 262;
int first_vsync_line_ = 227;
// A helper function to output the current border colour for
// the number of cycles supplied.
void output_border(int cycles, uint32_t cram_dot);
// A struct to contain timing information for the current mode.
struct {
/*
Vertical layout:
Lines 0 to [pixel_lines]: standard data fetch and drawing will occur.
... to [first_vsync_line]: refresh fetches will occur and border will be output.
.. to [2.5 or 3 lines later]: vertical sync is output.
... to [total lines - 1]: refresh fetches will occur and border will be output.
... for one line: standard data fetch will occur, without drawing.
*/
int total_lines = 262;
int pixel_lines = 192;
int first_vsync_line = 227;
// Maximum number of sprite slots to populate;
// if sprites beyond this number should be visible
// then the appropriate status information will be set.
int maximum_visible_sprites = 4;
// Set the position, in cycles, of the two interrupts,
// within a line.
struct {
int column = 4;
int row = 193;
} end_of_frame_interrupt_position;
int line_interrupt_position = -1;
// Enables or disabled the recognition of the sprite
// list terminator, and sets the terminator value.
bool allow_sprite_terminator = true;
uint8_t sprite_terminator = 0xd0;
} mode_timing_;
uint8_t line_interrupt_target = 0xff;
uint8_t line_interrupt_counter = 0;
bool enable_line_interrupts_ = false;
bool line_interrupt_pending_ = false;
// The screen mode is a necessary predecessor to picking the line mode,
// which is the thing latched per line.
enum class ScreenMode {
Blank,
Text,
MultiColour,
ColouredText,
Graphics,
SMSMode4
} screen_mode_;
// Horizontal selections.
enum class LineMode {
Text = 0,
Character = 1,
Refresh = 2
} line_mode_ = LineMode::Text;
int first_pixel_column_, first_right_border_column_;
Text,
Character,
Refresh,
SMS
};
uint8_t pattern_names_[40];
uint8_t pattern_buffer_[40];
uint8_t colour_buffer_[40];
// Temporary buffers collect a representation of this line prior to pixel serialisation.
struct LineBuffer {
// The line mode describes the proper timing diagram for this line.
LineMode line_mode = LineMode::Text;
struct SpriteSet {
// Holds the horizontal scroll position to apply to this line;
// of those VDPs currently implemented, affects the Master System only.
uint8_t latched_horizontal_scroll = 0;
// The names array holds pattern names, as an offset into memory, and
// potentially flags also.
struct {
size_t offset = 0;
uint8_t flags = 0;
} names[40];
// The patterns array holds tile patterns, corresponding 1:1 with names.
// Four bytes per pattern is the maximum required by any
// currently-implemented VDP.
uint8_t patterns[40][4];
/*
Horizontal layout (on a 342-cycle clock):
15 cycles right border
58 cycles blanking & sync
13 cycles left border
... i.e. to cycle 86, then:
border up to first_pixel_output_column;
pixels up to next_border_column;
border up to the end.
e.g. standard 256-pixel modes will want to set
first_pixel_output_column = 86, next_border_column = 342.
*/
int first_pixel_output_column = 94;
int next_border_column = 334;
// An active sprite is one that has been selected for composition onto
// this line.
struct ActiveSprite {
int index = 0;
int row = 0;
int index = 0; // The original in-table index of this sprite.
int row = 0; // The row of the sprite that should be drawn.
int x = 0; // The sprite's x position on screen.
uint8_t info[4];
uint8_t image[2];
uint8_t image[4]; // Up to four bytes of image information.
int shift_position = 0; // An offset representing how much of the image information has already been drawn.
} active_sprites[8];
int shift_position = 0;
} active_sprites[4];
int active_sprite_slot = 0;
} sprite_sets_[2];
int active_sprite_set_ = 0;
bool sprites_stopped_ = false;
int active_sprite_slot = 0; // A pointer to the slot into which a new active sprite will be deposited, if required.
bool sprites_stopped = false; // A special TMS feature is that a sentinel value can be used to prevent any further sprites
// being evaluated for display. This flag determines whether the sentinel has yet been reached.
int access_pointer_ = 0;
void reset_sprite_collection();
} line_buffers_[313];
void posit_sprite(LineBuffer &buffer, int sprite_number, int sprite_y, int screen_row);
inline void test_sprite(int sprite_number, int screen_row);
inline void get_sprite_contents(int start, int cycles, int screen_row);
// There is a delay between reading into the line buffer and outputting from there to the screen. That delay
// is observeable because reading time affects availability of memory accesses and therefore time in which
// to update sprites and tiles, but writing time affects when the palette is used and when the collision flag
// may end up being set. So the two processes are slightly decoupled. The end of reading one line may overlap
// with the beginning of writing the next, hence the two separate line buffers.
struct LineBufferPointer {
int row, column;
} read_pointer_, write_pointer_;
// The SMS VDP has a programmer-set colour palette, with a dedicated patch of RAM. But the RAM is only exactly
// fast enough for the pixel clock. So when the programmer writes to it, that causes a one-pixel glitch; there
// isn't the bandwidth for the read both write to occur simultaneously. The following buffer therefore keeps
// track of pending collisions, for visual reproduction.
struct CRAMDot {
LineBufferPointer location;
uint32_t value;
};
std::vector<CRAMDot> upcoming_cram_dots_;
// Extra information that affects the Master System output mode.
struct {
// Programmer-set flags.
bool vertical_scroll_lock = false;
bool horizontal_scroll_lock = false;
bool hide_left_column = false;
bool shift_sprites_8px_left = false;
bool mode4_enable = false;
uint8_t horizontal_scroll = 0;
uint8_t vertical_scroll = 0;
// The Master System's additional colour RAM.
uint32_t colour_ram[32];
bool cram_is_selected = false;
// Holds the vertical scroll position for this frame; this is latched
// once and cannot dynamically be changed until the next frame.
uint8_t latched_vertical_scroll = 0;
size_t pattern_name_address;
size_t sprite_attribute_table_address;
size_t sprite_generator_table_address;
} master_system_;
void set_current_screen_mode() {
if(blank_display_) {
screen_mode_ = ScreenMode::Blank;
return;
}
if(is_sega_vdp(personality_) && master_system_.mode4_enable) {
screen_mode_ = ScreenMode::SMSMode4;
mode_timing_.maximum_visible_sprites = 8;
return;
}
mode_timing_.maximum_visible_sprites = 4;
if(!mode1_enable_ && !mode2_enable_ && !mode3_enable_) {
screen_mode_ = ScreenMode::ColouredText;
return;
}
if(mode1_enable_ && !mode2_enable_ && !mode3_enable_) {
screen_mode_ = ScreenMode::Text;
return;
}
if(!mode1_enable_ && mode2_enable_ && !mode3_enable_) {
screen_mode_ = ScreenMode::Graphics;
return;
}
if(!mode1_enable_ && !mode2_enable_ && mode3_enable_) {
screen_mode_ = ScreenMode::MultiColour;
return;
}
// TODO: undocumented TMS modes.
screen_mode_ = ScreenMode::Blank;
}
void do_external_slot(int access_column) {
// Don't do anything if the required time for the access to become executable
// has yet to pass.
if(access_column < minimum_access_column_) {
return;
}
switch(queued_access_) {
default: return;
case MemoryAccess::Write:
if(master_system_.cram_is_selected) {
// Adjust the palette.
master_system_.colour_ram[ram_pointer_ & 0x1f] = palette_pack(
static_cast<uint8_t>(((read_ahead_buffer_ >> 0) & 3) * 255 / 3),
static_cast<uint8_t>(((read_ahead_buffer_ >> 2) & 3) * 255 / 3),
static_cast<uint8_t>(((read_ahead_buffer_ >> 4) & 3) * 255 / 3)
);
// Schedule a CRAM dot; this is scheduled for wherever it should appear
// on screen. So it's wherever the output stream would be now. Which
// is output_lag cycles ago from the point of view of the input stream.
upcoming_cram_dots_.emplace_back();
CRAMDot &dot = upcoming_cram_dots_.back();
dot.location.column = write_pointer_.column - output_lag;
dot.location.row = write_pointer_.row;
// Handle before this row conditionally; then handle after (or, more realistically,
// exactly at the end of) naturally.
if(dot.location.column < 0) {
--dot.location.row;
dot.location.column += 342;
}
dot.location.row += dot.location.column / 342;
dot.location.column %= 342;
dot.value = master_system_.colour_ram[ram_pointer_ & 0x1f];
} else {
ram_[ram_pointer_ & 16383] = read_ahead_buffer_;
}
break;
case MemoryAccess::Read:
read_ahead_buffer_ = ram_[ram_pointer_ & 16383];
break;
}
++ram_pointer_;
queued_access_ = MemoryAccess::None;
}
/*
Fetching routines follow below; they obey the following rules:
1) input is a start position and an end position; they should perform the proper
operations for the period: start <= time < end.
2) times are measured relative to a 172-cycles-per-line clock (so: they directly
count access windows on the TMS and Master System).
3) time 0 is the beginning of the access window immediately after the last pattern/data
block fetch that would contribute to this line, in a normal 32-column mode. So:
* it's cycle 309 on Mattias' TMS diagram;
* it's cycle 1238 on his V9938 diagram;
* it's after the last background render block in Mask of Destiny's Master System timing diagram.
That division point was selected, albeit arbitrarily, because it puts all the tile
fetches for a single line into the same [0, 171] period.
4) all of these functions are templated with a `use_end` parameter. That will be true if
end is < 172, false otherwise. So functions can use it to eliminate should-exit-not checks,
for the more usual path of execution.
Provided for the benefit of the methods below:
* the function external_slot(), which will perform any pending VRAM read/write.
* the macros slot(n) and external_slot(n) which can be used to schedule those things inside a
switch(start)-based implementation.
All functions should just spool data to intermediary storage. This is because for most VDPs there is
a decoupling between fetch pattern and output pattern, and it's neater to keep the same division
for the exceptions.
*/
#define slot(n) \
if(use_end && end == n) return;\
case n
#define external_slot(n) \
slot(n): do_external_slot((n)*2);
#define external_slots_2(n) \
external_slot(n); \
external_slot(n+1);
#define external_slots_4(n) \
external_slots_2(n); \
external_slots_2(n+2);
#define external_slots_8(n) \
external_slots_4(n); \
external_slots_4(n+4);
#define external_slots_16(n) \
external_slots_8(n); \
external_slots_8(n+8);
#define external_slots_32(n) \
external_slots_16(n); \
external_slots_16(n+16);
/***********************************************
TMS9918 Fetching Code
************************************************/
template<bool use_end> void fetch_tms_refresh(int start, int end) {
#define refresh(location) \
slot(location): \
external_slot(location+1);
#define refreshes_2(location) \
refresh(location); \
refresh(location+2);
#define refreshes_4(location) \
refreshes_2(location); \
refreshes_2(location+4);
#define refreshes_8(location) \
refreshes_4(location); \
refreshes_4(location+8);
switch(start) {
default: assert(false);
/* 44 external slots */
external_slots_32(0)
external_slots_8(32)
external_slots_4(40)
/* 64 refresh/external slot pairs (= 128 windows) */
refreshes_8(44);
refreshes_8(60);
refreshes_8(76);
refreshes_8(92);
refreshes_8(108);
refreshes_8(124);
refreshes_8(140);
refreshes_8(156);
return;
}
#undef refreshes_8
#undef refreshes_4
#undef refreshes_2
#undef refresh
}
template<bool use_end> void fetch_tms_text(int start, int end) {
#define fetch_tile_name(location, column) slot(location): line_buffer.names[column].offset = ram_[row_base + column];
#define fetch_tile_pattern(location, column) slot(location): line_buffer.patterns[column][0] = ram_[row_offset + size_t(line_buffer.names[column].offset << 3)];
#define fetch_column(location, column) \
fetch_tile_name(location, column); \
external_slot(location+1); \
fetch_tile_pattern(location+2, column);
#define fetch_columns_2(location, column) \
fetch_column(location, column); \
fetch_column(location+3, column+1);
#define fetch_columns_4(location, column) \
fetch_columns_2(location, column); \
fetch_columns_2(location+6, column+2);
#define fetch_columns_8(location, column) \
fetch_columns_4(location, column); \
fetch_columns_4(location+12, column+4);
LineBuffer &line_buffer = line_buffers_[write_pointer_.row];
const size_t row_base = pattern_name_address_ & (0x3c00 | static_cast<size_t>(write_pointer_.row >> 3) * 40);
const size_t row_offset = pattern_generator_table_address_ & (0x3800 | (write_pointer_.row & 7));
switch(start) {
default: assert(false);
/* 47 external slots (= 47 windows) */
external_slots_32(0)
external_slots_8(32)
external_slots_4(40)
external_slots_2(44)
external_slot(46)
/* 40 column fetches (= 120 windows) */
fetch_columns_8(47, 0);
fetch_columns_8(71, 8);
fetch_columns_8(95, 16);
fetch_columns_8(119, 24);
fetch_columns_8(143, 32);
/* 5 more external slots */
external_slots_4(167);
external_slot(171);
return;
}
#undef fetch_columns_8
#undef fetch_columns_4
#undef fetch_columns_2
#undef fetch_column
#undef fetch_tile_pattern
#undef fetch_tile_name
}
template<bool use_end> void fetch_tms_character(int start, int end) {
#define sprite_fetch_coordinates(location, sprite) \
slot(location): \
slot(location+1): \
line_buffer.active_sprites[sprite].x = \
ram_[\
sprite_attribute_table_address_ & size_t(0x3f81 | (line_buffer.active_sprites[sprite].index << 2))\
];
// This implementation doesn't refetch Y; it's unclear to me
// whether it's refetched.
#define sprite_fetch_graphics(location, sprite) \
slot(location): \
slot(location+1): \
slot(location+2): \
slot(location+3): {\
const uint8_t name = ram_[\
sprite_attribute_table_address_ & size_t(0x3f82 | (line_buffer.active_sprites[sprite].index << 2))\
] & (sprites_16x16_ ? ~3 : ~0);\
line_buffer.active_sprites[sprite].image[2] = ram_[\
sprite_attribute_table_address_ & size_t(0x3f83 | (line_buffer.active_sprites[sprite].index << 2))\
];\
line_buffer.active_sprites[sprite].x -= (line_buffer.active_sprites[sprite].image[2] & 0x80) >> 2;\
const size_t graphic_location = sprite_generator_table_address_ & size_t(0x3800 | (name << 3) | line_buffer.active_sprites[sprite].row); \
line_buffer.active_sprites[sprite].image[0] = ram_[graphic_location];\
line_buffer.active_sprites[sprite].image[1] = ram_[graphic_location+16];\
}
#define sprite_fetch_block(location, sprite) \
sprite_fetch_coordinates(location, sprite) \
sprite_fetch_graphics(location+2, sprite)
#define sprite_y_read(location, sprite) \
slot(location): posit_sprite(sprite_selection_buffer, sprite, ram_[sprite_attribute_table_address_ & (((sprite) << 2) | 0x3f80)], write_pointer_.row);
#define fetch_tile_name(column) line_buffer.names[column].offset = ram_[(row_base + column) & 0x3fff];
#define fetch_tile(column) {\
line_buffer.patterns[column][1] = ram_[(colour_base + size_t((line_buffer.names[column].offset << 3) >> colour_name_shift)) & 0x3fff]; \
line_buffer.patterns[column][0] = ram_[(pattern_base + size_t(line_buffer.names[column].offset << 3)) & 0x3fff]; \
}
#define background_fetch_block(location, column, sprite) \
slot(location): fetch_tile_name(column) \
external_slot(location+1); \
slot(location+2): \
slot(location+3): fetch_tile(column) \
slot(location+4): fetch_tile_name(column+1) \
sprite_y_read(location+5, sprite); \
slot(location+6): \
slot(location+7): fetch_tile(column+1) \
slot(location+8): fetch_tile_name(column+2) \
sprite_y_read(location+9, sprite+1); \
slot(location+10): \
slot(location+11): fetch_tile(column+2) \
slot(location+12): fetch_tile_name(column+3) \
sprite_y_read(location+13, sprite+2); \
slot(location+14): \
slot(location+15): fetch_tile(column+3)
LineBuffer &line_buffer = line_buffers_[write_pointer_.row];
LineBuffer &sprite_selection_buffer = line_buffers_[(write_pointer_.row + 1) % mode_timing_.total_lines];
const size_t row_base = pattern_name_address_ & (size_t((write_pointer_.row << 2)&~31) | 0x3c00);
size_t pattern_base = pattern_generator_table_address_;
size_t colour_base = colour_table_address_;
int colour_name_shift = 6;
if(screen_mode_ == ScreenMode::Graphics) {
// If this is high resolution mode, allow the row number to affect the pattern and colour addresses.
pattern_base &= size_t(0x2000 | ((write_pointer_.row & 0xc0) << 5));
colour_base &= size_t(0x2000 | ((write_pointer_.row & 0xc0) << 5));
colour_base += size_t(write_pointer_.row & 7);
colour_name_shift = 0;
} else {
colour_base &= size_t(0xffc0);
pattern_base &= size_t(0x3800);
}
if(screen_mode_ == ScreenMode::MultiColour) {
pattern_base += size_t((write_pointer_.row >> 2) & 7);
} else {
pattern_base += size_t(write_pointer_.row & 7);
}
switch(start) {
default: assert(false);
external_slots_2(0);
sprite_fetch_block(2, 0);
sprite_fetch_block(8, 1);
sprite_fetch_coordinates(14, 2);
external_slots_4(16);
external_slot(20);
sprite_fetch_graphics(21, 2);
sprite_fetch_block(25, 3);
slot(31):
sprite_selection_buffer.reset_sprite_collection();
do_external_slot(31*2);
external_slots_2(32);
external_slot(34);
sprite_y_read(35, 0);
sprite_y_read(36, 1);
sprite_y_read(37, 2);
sprite_y_read(38, 3);
sprite_y_read(39, 4);
sprite_y_read(40, 5);
sprite_y_read(41, 6);
sprite_y_read(42, 7);
background_fetch_block(43, 0, 8);
background_fetch_block(59, 4, 11);
background_fetch_block(75, 8, 14);
background_fetch_block(91, 12, 17);
background_fetch_block(107, 16, 20);
background_fetch_block(123, 20, 23);
background_fetch_block(139, 24, 26);
background_fetch_block(155, 28, 29);
return;
}
#undef background_fetch_block
#undef fetch_tile
#undef fetch_tile_name
#undef sprite_y_read
#undef sprite_fetch_block
#undef sprite_fetch_graphics
#undef sprite_fetch_coordinates
}
/***********************************************
Master System Fetching Code
************************************************/
template<bool use_end> void fetch_sms(int start, int end) {
#define sprite_fetch(sprite) {\
line_buffer.active_sprites[sprite].x = \
ram_[\
master_system_.sprite_attribute_table_address & size_t(0x3f80 | (line_buffer.active_sprites[sprite].index << 1))\
] - (master_system_.shift_sprites_8px_left ? 8 : 0); \
const uint8_t name = ram_[\
master_system_.sprite_attribute_table_address & size_t(0x3f81 | (line_buffer.active_sprites[sprite].index << 1))\
] & (sprites_16x16_ ? ~1 : ~0);\
const size_t graphic_location = master_system_.sprite_generator_table_address & size_t(0x2000 | (name << 5) | (line_buffer.active_sprites[sprite].row << 2)); \
line_buffer.active_sprites[sprite].image[0] = ram_[graphic_location]; \
line_buffer.active_sprites[sprite].image[1] = ram_[graphic_location+1]; \
line_buffer.active_sprites[sprite].image[2] = ram_[graphic_location+2]; \
line_buffer.active_sprites[sprite].image[3] = ram_[graphic_location+3]; \
}
#define sprite_fetch_block(location, sprite) \
slot(location): \
slot(location+1): \
slot(location+2): \
slot(location+3): \
slot(location+4): \
slot(location+5): \
sprite_fetch(sprite);\
sprite_fetch(sprite+1);
#define sprite_y_read(location, sprite) \
slot(location): \
posit_sprite(sprite_selection_buffer, sprite, ram_[master_system_.sprite_attribute_table_address & ((sprite) | 0x3f00)], write_pointer_.row); \
posit_sprite(sprite_selection_buffer, sprite+1, ram_[master_system_.sprite_attribute_table_address & ((sprite + 1) | 0x3f00)], write_pointer_.row); \
#define fetch_tile_name(column, row_info) {\
const size_t scrolled_column = (column - horizontal_offset) & 0x1f;\
const size_t address = row_info.pattern_address_base + (scrolled_column << 1); \
line_buffer.names[column].flags = ram_[address+1]; \
line_buffer.names[column].offset = static_cast<size_t>( \
(((line_buffer.names[column].flags&1) << 8) | ram_[address]) << 5 \
) + row_info.sub_row[(line_buffer.names[column].flags&4) >> 2]; \
}
#define fetch_tile(column) \
line_buffer.patterns[column][0] = ram_[line_buffer.names[column].offset]; \
line_buffer.patterns[column][1] = ram_[line_buffer.names[column].offset+1]; \
line_buffer.patterns[column][2] = ram_[line_buffer.names[column].offset+2]; \
line_buffer.patterns[column][3] = ram_[line_buffer.names[column].offset+3];
#define background_fetch_block(location, column, sprite, row_info) \
slot(location): fetch_tile_name(column, row_info) \
external_slot(location+1); \
slot(location+2): \
slot(location+3): \
slot(location+4): \
fetch_tile(column) \
fetch_tile_name(column+1, row_info) \
sprite_y_read(location+5, sprite); \
slot(location+6): \
slot(location+7): \
slot(location+8): \
fetch_tile(column+1) \
fetch_tile_name(column+2, row_info) \
sprite_y_read(location+9, sprite+2); \
slot(location+10): \
slot(location+11): \
slot(location+12): \
fetch_tile(column+2) \
fetch_tile_name(column+3, row_info) \
sprite_y_read(location+13, sprite+4); \
slot(location+14): \
slot(location+15): fetch_tile(column+3)
// Determine the coarse horizontal scrolling offset; this isn't applied on the first two lines if the programmer has requested it.
LineBuffer &line_buffer = line_buffers_[write_pointer_.row];
LineBuffer &sprite_selection_buffer = line_buffers_[(write_pointer_.row + 1) % mode_timing_.total_lines];
const int horizontal_offset = (write_pointer_.row >= 16 || !master_system_.horizontal_scroll_lock) ? (line_buffer.latched_horizontal_scroll >> 3) : 0;
// Limit address bits in use if this is a SMS2 mode.
const bool is_tall_mode = mode_timing_.pixel_lines != 192;
const size_t pattern_name_address = master_system_.pattern_name_address | (is_tall_mode ? 0x800 : 0);
const size_t pattern_name_offset = is_tall_mode ? 0x100 : 0;
// Determine row info for the screen both (i) if vertical scrolling is applied; and (ii) if it isn't.
// The programmer can opt out of applying vertical scrolling to the right-hand portion of the display.
const int scrolled_row = (write_pointer_.row + master_system_.latched_vertical_scroll) % (is_tall_mode ? 256 : 224);
struct RowInfo {
size_t pattern_address_base;
size_t sub_row[2];
};
const RowInfo scrolled_row_info = {
(pattern_name_address & size_t(((scrolled_row & ~7) << 3) | 0x3800)) - pattern_name_offset,
{static_cast<size_t>((scrolled_row & 7) << 2), 28 ^ static_cast<size_t>((scrolled_row & 7) << 2)}
};
RowInfo row_info;
if(master_system_.vertical_scroll_lock) {
row_info.pattern_address_base = (pattern_name_address & size_t(((write_pointer_.row & ~7) << 3) | 0x3800)) - pattern_name_offset;
row_info.sub_row[0] = size_t((write_pointer_.row & 7) << 2);
row_info.sub_row[1] = 28 ^ size_t((write_pointer_.row & 7) << 2);
} else row_info = scrolled_row_info;
// ... and do the actual fetching, which follows this routine:
switch(start) {
default: assert(false);
sprite_fetch_block(0, 0);
sprite_fetch_block(6, 2);
external_slots_4(12);
external_slot(16);
sprite_fetch_block(17, 4);
sprite_fetch_block(23, 6);
slot(29):
sprite_selection_buffer.reset_sprite_collection();
do_external_slot(29*2);
external_slot(30);
sprite_y_read(31, 0);
sprite_y_read(32, 2);
sprite_y_read(33, 4);
sprite_y_read(34, 6);
sprite_y_read(35, 8);
sprite_y_read(36, 10);
sprite_y_read(37, 12);
sprite_y_read(38, 14);
background_fetch_block(39, 0, 16, scrolled_row_info);
background_fetch_block(55, 4, 22, scrolled_row_info);
background_fetch_block(71, 8, 28, scrolled_row_info);
background_fetch_block(87, 12, 34, scrolled_row_info);
background_fetch_block(103, 16, 40, scrolled_row_info);
background_fetch_block(119, 20, 46, scrolled_row_info);
background_fetch_block(135, 24, 52, row_info);
background_fetch_block(151, 28, 58, row_info);
external_slots_4(167);
return;
}
#undef background_fetch_block
#undef fetch_tile
#undef fetch_tile_name
#undef sprite_y_read
#undef sprite_fetch_block
#undef sprite_fetch
}
#undef external_slot
#undef slot
uint32_t *pixel_target_ = nullptr, *pixel_origin_ = nullptr;
bool asked_for_write_area_ = false;
void draw_tms_character(int start, int end);
void draw_tms_text(int start, int end);
void draw_sms(int start, int end, uint32_t cram_dot);
};
}
}
#endif /* TMS9918Base_hpp */

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),
@@ -173,11 +204,15 @@ void AY38910::select_register(uint8_t r) {
}
void AY38910::set_register_value(uint8_t value) {
// There are only 16 registers.
if(selected_register_ > 15) return;
registers_[selected_register_] = value;
// If this is a register that affects audio output, enqueue a mutation onto the
// audio generation thread.
if(selected_register_ < 14) {
int selected_register = selected_register_;
const int selected_register = selected_register_;
task_queue_.defer([=] () {
// Perform any register-specific mutation to output generation.
uint8_t masked_value = value;
switch(selected_register) {
case 0: case 2: case 4:
@@ -208,12 +243,34 @@ void AY38910::set_register_value(uint8_t value) {
envelope_position_ = 0;
break;
}
// Store a copy of the current register within the storage used by the audio generation
// thread, and apply any changes to output volume.
output_registers_[selected_register] = masked_value;
evaluate_output_volume();
});
} else {
if(port_handler_) port_handler_->set_port_output(selected_register_ == 15, value);
}
// Decide which outputs are going to need updating (if any).
bool update_port_a = false;
bool update_port_b = true;
if(port_handler_) {
if(selected_register_ == 7) {
const uint8_t io_change = registers_[7] ^ value;
update_port_b = !!(io_change&0x80);
update_port_a = !!(io_change&0x40);
} else {
update_port_b = selected_register_ == 15;
update_port_a = selected_register_ != 15;
}
}
// Keep a copy of the new value that is usable from the emulation thread.
registers_[selected_register_] = value;
// Update ports as required.
if(update_port_b) set_port_output(true);
if(update_port_a) set_port_output(false);
}
uint8_t AY38910::get_register_value() {
@@ -225,14 +282,10 @@ uint8_t AY38910::get_register_value() {
};
if(selected_register_ > 15) return 0xff;
switch(selected_register_) {
default: return registers_[selected_register_] & register_masks[selected_register_];
case 14: return (registers_[0x7] & 0x40) ? registers_[14] : port_inputs_[0];
case 15: return (registers_[0x7] & 0x80) ? registers_[15] : port_inputs_[1];
}
return registers_[selected_register_] & register_masks[selected_register_];
}
// MARK: - Port handling
// MARK: - Port querying
uint8_t AY38910::get_port_output(bool port_b) {
return registers_[port_b ? 15 : 14];
@@ -242,6 +295,8 @@ uint8_t AY38910::get_port_output(bool port_b) {
void AY38910::set_port_handler(PortHandler *handler) {
port_handler_ = handler;
set_port_output(true);
set_port_output(false);
}
void AY38910::set_data_input(uint8_t r) {
@@ -249,12 +304,27 @@ void AY38910::set_data_input(uint8_t r) {
update_bus();
}
void AY38910::set_port_output(bool port_b) {
// Per the data sheet: "each [IO] pin is provided with an on-chip pull-up resistor,
// so that when in the "input" mode, all pins will read normally high". Therefore,
// report programmer selection of input mode as creating an output of 0xff.
if(port_handler_) {
const bool is_output = !!(registers_[7] & (port_b ? 0x80 : 0x40));
port_handler_->set_port_output(port_b, is_output ? registers_[port_b ? 15 : 14] : 0xff);
}
}
uint8_t AY38910::get_data_output() {
if(control_state_ == Read && selected_register_ >= 14) {
if(port_handler_) {
return port_handler_->get_port_input(selected_register_ == 15);
} else {
return 0xff;
if(control_state_ == Read && selected_register_ >= 14 && selected_register_ < 16) {
// Per http://cpctech.cpc-live.com/docs/psgnotes.htm if a port is defined as output then the
// value returned to the CPU when reading it is the and of the output value and any input.
// If it's defined as input then you just get the input.
const uint8_t mask = port_handler_ ? port_handler_->get_port_input(selected_register_ == 15) : 0xff;
switch(selected_register_) {
default: break;
case 14: return mask & ((registers_[0x7] & 0x40) ? registers_[14] : 0xff);
case 15: return mask & ((registers_[0x7] & 0x80) ? registers_[15] : 0xff);
}
}
return data_output_;
@@ -276,8 +346,10 @@ void AY38910::set_control_lines(ControlLines control_lines) {
}
void AY38910::update_bus() {
// Assume no output, unless this turns out to be a read.
data_output_ = 0xff;
switch(control_state_) {
default: break;
default: break;
case LatchAddress: select_register(data_input_); break;
case Write: set_register_value(data_input_); break;
case Read: data_output_ = get_register_value(); break;

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);
@@ -83,7 +91,7 @@ class AY38910: public ::Outputs::Speaker::SampleSource {
*/
void set_port_handler(PortHandler *);
// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter; not for public consumption
// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter.
void get_samples(std::size_t number_of_samples, int16_t *target);
bool is_zero_level();
void set_sample_volume_range(std::int16_t range);
@@ -94,7 +102,6 @@ class AY38910: public ::Outputs::Speaker::SampleSource {
int selected_register_ = 0;
uint8_t registers_[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
uint8_t output_registers_[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
uint8_t port_inputs_[2];
int master_divider_ = 0;
@@ -109,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,
@@ -129,10 +136,11 @@ class AY38910: public ::Outputs::Speaker::SampleSource {
uint8_t data_input_, data_output_;
int16_t output_volume_;
inline void evaluate_output_volume();
void evaluate_output_volume();
inline void update_bus();
void update_bus();
PortHandler *port_handler_ = nullptr;
void set_port_output(bool port_b);
};
}

View File

@@ -22,7 +22,7 @@ namespace {
DiskII::DiskII(int clock_rate) :
clock_rate_(clock_rate),
inputs_(input_command),
drives_{{static_cast<unsigned int>(clock_rate), 300, 1}, {static_cast<unsigned int>(clock_rate), 300, 1}}
drives_{{clock_rate, 300, 1}, {clock_rate, 300, 1}}
{
drives_[0].set_clocking_hint_observer(this);
drives_[1].set_clocking_hint_observer(this);
@@ -73,13 +73,18 @@ void DiskII::select_drive(int drive) {
drives_[active_drive_].set_motor_on(motor_is_enabled_);
}
// The read pulse is controlled by a special IC that outputs a 1us pulse for every field reversal on the disk.
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);
inputs_ |= input_flux;
if(flux_duration_) {
--flux_duration_;
if(!flux_duration_) inputs_ |= input_flux;
}
state_ = state_machine_[static_cast<std::size_t>(address)];
switch(state_ & 0xf) {
default: shift_register_ = 0; break; // clear
@@ -115,6 +120,15 @@ void DiskII::run_for(const Cycles cycles) {
if(!drive_is_sleeping_[1]) drives_[1].run_for(Cycles(1));
}
// Per comp.sys.apple2.programmer there is a delay between the controller
// 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_integral();
if(motor_off_time_ < 0) {
set_control(Control::Motor, false);
}
}
decide_clocking_preference();
}
@@ -197,9 +211,10 @@ void DiskII::set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int driv
drives_[drive].set_disk(disk);
}
void DiskII::process_event(const Storage::Disk::Track::Event &event) {
void DiskII::process_event(const Storage::Disk::Drive::Event &event) {
if(event.type == Storage::Disk::Track::Event::FluxTransition) {
inputs_ &= ~input_flux;
flux_duration_ = 2; // Upon detection of a flux transition, the flux flag should stay set for 1us. Emulate that as two cycles.
decide_clocking_preference();
}
}
@@ -232,9 +247,12 @@ int DiskII::read_address(int address) {
case 0x8:
shift_register_ = 0;
set_control(Control::Motor, false);
motor_off_time_ = clock_rate_;
break;
case 0x9:
set_control(Control::Motor, true);
motor_off_time_ = -1;
break;
case 0x9: set_control(Control::Motor, true); break;
case 0xa: select_drive(0); break;
case 0xb: select_drive(1); break;
@@ -248,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;
}
@@ -260,3 +278,7 @@ void DiskII::set_activity_observer(Activity::Observer *observer) {
drives_[0].set_activity_observer(observer, "Drive 1", true);
drives_[1].set_activity_observer(observer, "Drive 2", true);
}
Storage::Disk::Drive &DiskII::get_drive(int index) {
return drives_[index];
}

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,13 +48,13 @@ 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);
/*!
Supplies the image of the state machine (i.e. P6) ROM,
Supplies the image of the state machine (i.e. P6) ROM,
which dictates how the Disk II will respond to input.
To reduce processing costs, some assumptions are made by
@@ -76,11 +76,15 @@ 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);
// Returns the Storage::Disk::Drive in use for drive @c index.
// *NOT FOR HARDWARE EMULATION USAGE*.
Storage::Disk::Drive &get_drive(int index);
private:
enum class Control {
P0, P1, P2, P3,
@@ -94,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::Track::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;
@@ -105,6 +109,7 @@ class DiskII:
int stepper_mask_ = 0;
int stepper_position_ = 0;
Cycles::IntType motor_off_time_ = -1;
bool is_write_protected();
std::array<uint8_t, 256> state_machine_;
@@ -117,6 +122,7 @@ class DiskII:
ClockingHint::Preference clocking_preference_ = ClockingHint::Preference::RealTime;
uint8_t data_input_ = 0;
int flux_duration_ = 0;
};
}

406
Components/DiskII/IWM.cpp Normal file
View File

@@ -0,0 +1,406 @@
//
// IWM.cpp
// Clock Signal
//
// Created by Thomas Harte on 05/05/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#include "IWM.hpp"
#include "../../Outputs/Log.hpp"
using namespace Apple;
namespace {
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) :
clock_rate_(clock_rate) {}
// MARK: - Bus accessors
uint8_t IWM::read(int address) {
access(address);
// Per Inside Macintosh:
//
// "Before you can read from any of the disk registers you must set up the state of the IWM so that it
// can pass the data through to the MC68000's address space where you'll be able to read it. To do that,
// you must first turn off Q7 by reading or writing dBase+q7L. Then turn on Q6 by accessing dBase+q6H.
// After that, the IWM will be able to pass data from the disk's RD/SENSE line through to you."
//
// My understanding:
//
// Q6 = 1, Q7 = 0 reads the status register. The meaning of the top 'SENSE' bit is then determined by
// the CA0,1,2 and SEL switches as described in Inside Macintosh, summarised above as RD/SENSE.
if(address&1) {
return 0xff;
}
switch(state_ & (Q6 | Q7 | ENABLE)) {
default:
LOG("[IWM] Invalid read\n");
return 0xff;
// "Read all 1s".
// printf("Reading all 1s\n");
// return 0xff;
case 0:
case ENABLE: { /* Read data register. Zeroing afterwards is a guess. */
const auto result = data_register_;
if(data_register_ & 0x80) {
// printf("\n\nIWM:%02x\n\n", data_register_);
// printf(".");
data_register_ = 0;
}
// LOG("Reading data register: " << PADHEX(2) << int(result));
return result;
}
case Q6: case Q6|ENABLE: {
/*
[If A = 0], Read status register:
bits 0-4: same as mode register.
bit 5: 1 = either /ENBL1 or /ENBL2 is currently low.
bit 6: 1 = MZ (reserved for future compatibility; should always be read as 0).
bit 7: 1 = SENSE input high; 0 = SENSE input low.
(/ENBL1 is low when the first drive's motor is on; /ENBL2 is low when the second drive's motor is on.
If the 1-second timer is enabled, motors remain on for one second after being programmatically disabled.)
*/
return uint8_t(
(mode_&0x1f) |
((state_ & ENABLE) ? 0x20 : 0x00) |
(sense() & 0x80)
);
} break;
case Q7: case Q7|ENABLE:
/*
Read write-handshake register:
bits 0-5: reserved for future use (currently read as 1).
bit 6: 1 = write state (0 = underrun has occurred; 1 = no underrun so far).
bit 7: 1 = write data buffer ready for data (1 = ready; 0 = busy).
*/
// LOG("Reading write handshake: " << PADHEX(2) << (0x3f | write_handshake_));
return 0x3f | write_handshake_;
}
return 0xff;
}
void IWM::write(int address, uint8_t input) {
access(address);
switch(state_ & (Q6 | Q7 | ENABLE)) {
default: break;
case Q7|Q6:
/*
Write mode register:
bit 0: 1 = latch mode (should be set in asynchronous mode).
bit 1: 0 = synchronous handshake protocol; 1 = asynchronous.
bit 2: 0 = 1-second on-board timer enable; 1 = timer disable.
bit 3: 0 = slow mode; 1 = fast mode.
bit 4: 0 = 7Mhz; 1 = 8Mhz (7 or 8 mHz clock descriptor).
bit 5: 1 = test mode; 0 = normal operation.
bit 6: 1 = MZ-reset.
bit 7: reserved for future expansion.
*/
mode_ = input;
switch(mode_ & 0x18) {
case 0x00: bit_length_ = Cycles(24); break; // slow mode, 7Mhz
case 0x08: bit_length_ = Cycles(12); break; // fast mode, 7Mhz
case 0x10: bit_length_ = Cycles(32); break; // slow mode, 8Mhz
case 0x18: bit_length_ = Cycles(16); break; // fast mode, 8Mhz
}
LOG("IWM mode is now " << PADHEX(2) << int(mode_));
break;
case Q7|Q6|ENABLE: // Write data register.
next_output_ = input;
write_handshake_ &= ~0x80;
break;
}
}
// MARK: - Switch access
void IWM::access(int address) {
// Keep a record of switch state; bits in state_
// should correlate with the anonymous namespace constants
// defined at the top of this file — CA0, CA1, etc.
address &= 0xf;
const auto mask = 1 << (address >> 1);
const auto old_state = state_;
if(address & 1) {
state_ |= mask;
} else {
state_ &= ~mask;
}
// React appropriately to ENABLE and DRIVESEL changes, and changes into/out of write mode.
if(old_state != state_) {
push_drive_state();
switch(mask) {
default: break;
case ENABLE:
if(address & 1) {
if(drives_[active_drive_]) drives_[active_drive_]->set_enabled(true);
} else {
// If the 1-second delay is enabled, set up a timer for that.
if(!(mode_ & 4)) {
cycles_until_disable_ = Cycles(clock_rate_);
} else {
if(drives_[active_drive_]) drives_[active_drive_]->set_enabled(false);
}
}
break;
case DRIVESEL: {
const int new_drive = address & 1;
if(new_drive != active_drive_) {
if(drives_[active_drive_]) drives_[active_drive_]->set_enabled(false);
active_drive_ = new_drive;
if(drives_[active_drive_]) {
drives_[active_drive_]->set_enabled(state_ & ENABLE || (cycles_until_disable_ > Cycles(0)));
push_drive_state();
}
}
} break;
case Q6:
case Q7:
select_shift_mode();
break;
}
}
}
void IWM::set_select(bool enabled) {
// Store SEL as an extra state bit.
if(enabled) state_ |= SEL;
else state_ &= ~SEL;
push_drive_state();
}
void IWM::push_drive_state() {
if(drives_[active_drive_]) {
const uint8_t drive_control_lines =
((state_ & CA0) ? IWMDrive::CA0 : 0) |
((state_ & CA1) ? IWMDrive::CA1 : 0) |
((state_ & CA2) ? IWMDrive::CA2 : 0) |
((state_ & SEL) ? IWMDrive::SEL : 0) |
((state_ & LSTRB) ? IWMDrive::LSTRB : 0);
drives_[active_drive_]->set_control_lines(drive_control_lines);
}
}
// MARK: - Active logic
void IWM::run_for(const Cycles cycles) {
// Check for a timeout of the motor-off timer.
if(cycles_until_disable_ > Cycles(0)) {
cycles_until_disable_ -= cycles;
if(cycles_until_disable_ <= Cycles(0)) {
cycles_until_disable_ = Cycles(0);
if(drives_[active_drive_])
drives_[active_drive_]->set_enabled(false);
}
}
// Activity otherwise depends on mode and motor state.
auto integer_cycles = cycles.as_integral();
switch(shift_mode_) {
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_ + error_margin) {
propose_shift(0);
}
}
} else {
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);
}
cycles_since_shift_ += Cycles(integer_cycles);
}
} break;
case ShiftMode::Writing:
if(drives_[active_drive_]->is_writing()) {
while(cycles_since_shift_ + integer_cycles >= bit_length_) {
const auto cycles_until_write = bit_length_ - cycles_since_shift_;
drives_[active_drive_]->run_for(cycles_until_write);
// Output a flux transition if the top bit is set.
drives_[active_drive_]->write_bit(shift_register_ & 0x80);
shift_register_ <<= 1;
integer_cycles -= cycles_until_write.as_integral();
cycles_since_shift_ = Cycles(0);
--output_bits_remaining_;
if(!output_bits_remaining_) {
if(!(write_handshake_ & 0x80)) {
write_handshake_ |= 0x80;
shift_register_ = next_output_;
output_bits_remaining_ = 8;
// LOG("Next byte: " << PADHEX(2) << int(shift_register_));
} else {
write_handshake_ &= ~0x40;
drives_[active_drive_]->end_writing();
// printf("\n");
LOG("Overrun; done.");
select_shift_mode();
}
}
}
cycles_since_shift_ = integer_cycles;
if(integer_cycles) {
drives_[active_drive_]->run_for(cycles_since_shift_);
}
} else {
drives_[active_drive_]->run_for(cycles);
}
break;
case ShiftMode::CheckingWriteProtect:
if(integer_cycles < 8) {
shift_register_ = (shift_register_ >> integer_cycles) | (sense() & (0xff << (8 - integer_cycles)));
} else {
shift_register_ = sense();
}
/* Deliberate fallthrough. */
default:
if(drive_is_rotating_[active_drive_]) drives_[active_drive_]->run_for(cycles);
break;
}
}
void IWM::select_shift_mode() {
// Don't allow an ongoing write to be interrupted.
if(shift_mode_ == ShiftMode::Writing && drives_[active_drive_] && drives_[active_drive_]->is_writing()) return;
const auto old_shift_mode = shift_mode_;
switch(state_ & (Q6|Q7)) {
default: shift_mode_ = ShiftMode::CheckingWriteProtect; break;
case 0: shift_mode_ = ShiftMode::Reading; break;
case Q7:
// "The IWM is put into the write state by a transition from the write protect sense state to the
// write load state".
if(shift_mode_ == ShiftMode::CheckingWriteProtect) shift_mode_ = ShiftMode::Writing;
break;
}
// If writing mode just began, set the drive into write mode and cue up the first output byte.
if(drives_[active_drive_] && old_shift_mode != ShiftMode::Writing && shift_mode_ == ShiftMode::Writing) {
drives_[active_drive_]->begin_writing(Storage::Time(1, clock_rate_ / bit_length_.as_integral()), false);
shift_register_ = next_output_;
write_handshake_ |= 0x80 | 0x40;
output_bits_remaining_ = 8;
LOG("Seeding output with " << PADHEX(2) << shift_register_);
}
}
uint8_t IWM::sense() {
return drives_[active_drive_] ? (drives_[active_drive_]->read() ? 0xff : 0x00) : 0xff;
}
void IWM::process_event(const Storage::Disk::Drive::Event &event) {
if(shift_mode_ != ShiftMode::Reading) return;
switch(event.type) {
case Storage::Disk::Track::Event::IndexHole: return;
case Storage::Disk::Track::Event::FluxTransition:
propose_shift(1);
break;
}
}
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;
}
if(bit)
cycles_since_shift_ = Cycles(0);
else
cycles_since_shift_ -= bit_length_;
}
void IWM::set_drive(int slot, IWMDrive *drive) {
drives_[slot] = drive;
drive->set_event_delegate(this);
drive->set_clocking_hint_observer(this);
}
void IWM::set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) {
const bool is_rotating = clocking != ClockingHint::Preference::None;
if(component == static_cast<ClockingHint::Source *>(drives_[0])) {
drive_is_rotating_[0] = is_rotating;
} else {
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);
}

124
Components/DiskII/IWM.hpp Normal file
View File

@@ -0,0 +1,124 @@
//
// IWM.hpp
// Clock Signal
//
// Created by Thomas Harte on 05/05/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#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>
namespace Apple {
/*!
Defines the drive interface used by the IWM, derived from the external pinout as
per e.g. https://old.pinouts.ru/HD/MacExtDrive_pinout.shtml
These are subclassed of Storage::Disk::Drive, so accept any disk the emulator supports,
and provide the usual read/write interface for on-disk data.
*/
struct IWMDrive: public Storage::Disk::Drive {
IWMDrive(int input_clock_rate, int number_of_heads) : Storage::Disk::Drive(input_clock_rate, number_of_heads) {}
enum Line: int {
CA0 = 1 << 0,
CA1 = 1 << 1,
CA2 = 1 << 2,
LSTRB = 1 << 3,
SEL = 1 << 4,
};
virtual void set_enabled(bool) = 0;
virtual void set_control_lines(int) = 0;
virtual bool read() = 0;
};
class IWM:
public Storage::Disk::Drive::EventDelegate,
public ClockingHint::Observer {
public:
IWM(int clock_rate);
/// Sets the current external value of the data bus.
void write(int address, uint8_t value);
/*!
Submits an access to address @c address.
@returns The 8-bit value loaded to the data bus by the IWM.
*/
uint8_t read(int address);
/*!
Sets the current input of the IWM's SEL line.
*/
void set_select(bool enabled);
/// Advances the controller by @c cycles.
void run_for(const Cycles cycles);
/// 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) final;
const int clock_rate_;
uint8_t data_register_ = 0;
uint8_t mode_ = 0;
bool read_write_ready_ = true;
bool write_overran_ = false;
int state_ = 0;
int active_drive_ = 0;
IWMDrive *drives_[2] = {nullptr, nullptr};
bool drive_is_rotating_[2] = {false, false};
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) final;
Cycles cycles_until_disable_;
uint8_t write_handshake_ = 0x80;
void access(int address);
uint8_t shift_register_ = 0;
uint8_t next_output_ = 0;
int output_bits_remaining_ = 0;
void propose_shift(uint8_t bit);
Cycles cycles_since_shift_;
Cycles bit_length_ = Cycles(16);
void push_drive_state();
enum class ShiftMode {
Reading,
Writing,
CheckingWriteProtect
} shift_mode_;
uint8_t sense();
void select_shift_mode();
};
}
#endif /* IWM_hpp */

View File

@@ -0,0 +1,181 @@
//
// MacintoshDoubleDensityDrive.cpp
// Clock Signal
//
// Created by Thomas Harte on 10/07/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#include "MacintoshDoubleDensityDrive.hpp"
/*
Sources used pervasively:
http://members.iinet.net.au/~kalandi/apple/AUG/1991/11%20NOV.DEC/DISK.STUFF.html
Apple Guide to the Macintosh Family Hardware
Inside Macintosh III
*/
using namespace Apple::Macintosh;
DoubleDensityDrive::DoubleDensityDrive(int input_clock_rate, bool is_800k) :
IWMDrive(input_clock_rate, is_800k ? 2 : 1), // Only 800kb drives are double sided.
is_800k_(is_800k) {
// Start with a valid rotation speed.
if(is_800k) {
Drive::set_rotation_speed(393.3807f);
}
}
// MARK: - Speed Selection
void DoubleDensityDrive::did_step(Storage::Disk::HeadPosition to_position) {
// printf("At track %d\n", to_position.as_int());
// The 800kb drive automatically selects rotation speed as a function of
// head position; the 400kb drive doesn't do so.
if(is_800k_) {
/*
Numbers below cribbed from the Kryoflux forums; specifically:
https://forum.kryoflux.com/viewtopic.php?t=1090
They can almost be worked out algorithmically, since the point is to
produce an almost-constant value for speed*(number of sectors), and:
393.3807 * 12 = 4720.5684
429.1723 * 11 = 4720.895421
472.1435 * 10 = 4721.435
524.5672 * 9 = 4721.1048
590.1098 * 8 = 4720.8784
So 4721 / (number of sectors per track in zone) would give essentially
the same results.
*/
const int zone = to_position.as_int() >> 4;
switch(zone) {
case 0: Drive::set_rotation_speed(393.3807f); break;
case 1: Drive::set_rotation_speed(429.1723f); break;
case 2: Drive::set_rotation_speed(472.1435f); break;
case 3: Drive::set_rotation_speed(524.5672f); break;
default: Drive::set_rotation_speed(590.1098f); break;
}
}
}
void DoubleDensityDrive::set_rotation_speed(float revolutions_per_minute) {
if(!is_800k_) {
// Don't allow drive speeds to drop below 10 RPM, as a temporary sop
// to sanity.
Drive::set_rotation_speed(std::max(10.0f, revolutions_per_minute));
}
}
// MARK: - Control input/output.
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) {
const auto old_state = control_state_;
control_state_ = lines;
// Catch low-to-high LSTRB transitions.
if((old_state ^ control_state_) & control_state_ & Line::LSTRB) {
switch(control_state_ & (Line::CA2 | Line::CA1 | Line::CA0 | Line::SEL)) {
default:
break;
case 0: // Set step direction — CA2 set => step outward.
case Line::CA2:
step_direction_ = (control_state_ & Line::CA2) ? -1 : 1;
break;
case Line::CA1: // Set drive motor — CA2 set => motor off.
case Line::CA1|Line::CA2:
set_motor_on(!(control_state_ & Line::CA2));
break;
case Line::CA0: // Initiate a step.
step(Storage::Disk::HeadPosition(step_direction_));
break;
case Line::SEL|Line::CA2: // Reset new disk flag.
has_new_disk_ = false;
break;
case Line::CA2 | Line::CA1 | Line::CA0: // Eject the disk.
set_disk(nullptr);
break;
}
}
}
bool DoubleDensityDrive::read() {
switch(control_state_ & (CA2 | CA1 | CA0 | SEL)) {
default:
return false;
case 0: // Head step direction.
// (0 = inward)
return step_direction_ <= 0;
case SEL: // Disk in place.
// (0 = disk present)
return !has_disk();
case CA0: // Disk head step completed.
// (0 = still stepping)
return true; // TODO: stepping delay. But at the main Drive level.
case CA0|SEL: // Disk locked.
// (0 = write protected)
return !get_is_read_only();
case CA1: // Disk motor running.
// (0 = motor on)
return !get_motor_on();
case CA1|SEL: // Head at track 0.
// (0 = at track 0)
// "This bit becomes valid beginning 12 msec after the step that places the head at track 0."
return !get_is_track_zero();
case CA1|CA0: // Disk has been ejected.
// (0 = user has ejected disk)
return !has_new_disk_;
case CA1|CA0|SEL: // Tachometer.
// (arbitrary)
return get_tachometer();
case CA2: // Read data, lower head.
set_head(0);
return false;
case CA2|SEL: // Read data, upper head.
set_head(1);
return false;
case CA2|CA1: // Single- or double-sided drive.
// (0 = single sided)
return get_head_count() != 1;
case CA2|CA1|CA0: // "Present/HD" (per the Mac Plus ROM)
// (0 = ??HD??)
//
// Alternative explanation: "Disk ready for reading?"
// (0 = ready)
return false;
case CA2|CA1|CA0|SEL: // Drive installed.
// (0 = present, 1 = missing)
//
// TODO: why do I need to return this the wrong way around for the Mac Plus?
return true;
}
}
void DoubleDensityDrive::did_set_disk() {
has_new_disk_ = true;
}

View File

@@ -0,0 +1,53 @@
//
// MacintoshDoubleDensityDrive.hpp
// Clock Signal
//
// Created by Thomas Harte on 10/07/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef MacintoshDoubleDensityDrive_hpp
#define MacintoshDoubleDensityDrive_hpp
#include "IWM.hpp"
namespace Apple {
namespace Macintosh {
class DoubleDensityDrive: public IWMDrive {
public:
DoubleDensityDrive(int input_clock_rate, bool is_800k);
/*!
@returns @c true if this is an 800kb drive; @c false otherwise.
*/
bool is_800k() {
return is_800k_;
}
/*!
Sets the current rotation speed of this drive only if it is a 400kb drive.
800kb drives select their own rotation speed based on head position,
and ignore this input.
*/
void set_rotation_speed(float revolutions_per_minute);
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) final;
void did_set_disk() final;
const bool is_800k_;
bool has_new_disk_ = false;
int control_state_ = 0;
int step_direction_ = 1;
};
}
}
#endif /* MacintoshDoubleDensityDrive_hpp */

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

@@ -19,7 +19,7 @@ ListSelection *ListSelection::list_selection() {
}
BooleanSelection *ListSelection::boolean_selection() {
return new BooleanSelection(value != "no" && value != "n");
return new BooleanSelection(value != "no" && value != "n" && value != "false" && value != "f");
}
BooleanSelection *BooleanSelection::boolean_selection() {

View File

@@ -69,7 +69,7 @@ struct BooleanSelection: public Selection {
struct ListSelection: public Selection {
std::string value;
ListSelection *list_selection();
BooleanSelection *boolean_selection();
ListSelection(const std::string value) : value(value) {}

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;
}
@@ -33,14 +33,16 @@ bool get_bool(const Configurable::SelectionSet &selections_by_option, const std:
std::vector<std::unique_ptr<Configurable::Option>> Configurable::standard_options(Configurable::StandardOptions mask) {
std::vector<std::unique_ptr<Configurable::Option>> options;
if(mask & QuickLoadTape) options.emplace_back(new Configurable::BooleanOption("Load Tapes Quickly", "quickload"));
if(mask & (DisplayRGB | DisplayComposite | DisplaySVideo)) {
if(mask & (DisplayRGB | DisplayCompositeColour | DisplayCompositeMonochrome | DisplaySVideo)) {
std::vector<std::string> display_options;
if(mask & DisplayComposite) display_options.emplace_back("composite");
if(mask & DisplaySVideo) display_options.emplace_back("svideo");
if(mask & DisplayRGB) display_options.emplace_back("rgb");
if(mask & DisplayCompositeColour) display_options.emplace_back("composite");
if(mask & DisplayCompositeMonochrome) display_options.emplace_back("composite-mono");
if(mask & DisplaySVideo) display_options.emplace_back("svideo");
if(mask & DisplayRGB) display_options.emplace_back("rgb");
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;
}
@@ -57,11 +59,16 @@ void Configurable::append_display_selection(Configurable::SelectionSet &selectio
std::string string_selection;
switch(selection) {
default:
case Display::RGB: string_selection = "rgb"; break;
case Display::SVideo: string_selection = "svideo"; break;
case Display::Composite: string_selection = "composite"; break;
case Display::RGB: string_selection = "rgb"; break;
case Display::SVideo: string_selection = "svideo"; break;
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
@@ -85,9 +92,17 @@ bool Configurable::get_display(const Configurable::SelectionSet &selections_by_o
return true;
}
if(display->value == "composite") {
result = Configurable::Display::Composite;
result = Configurable::Display::CompositeColour;
return true;
}
if(display->value == "composite-mono") {
result = Configurable::Display::CompositeMonochrome;
return true;
}
}
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

@@ -16,15 +16,18 @@ namespace Configurable {
enum StandardOptions {
DisplayRGB = (1 << 0),
DisplaySVideo = (1 << 1),
DisplayComposite = (1 << 2),
QuickLoadTape = (1 << 3),
AutomaticTapeMotorControl = (1 << 4)
DisplayCompositeColour = (1 << 2),
DisplayCompositeMonochrome = (1 << 3),
QuickLoadTape = (1 << 4),
AutomaticTapeMotorControl = (1 << 5),
QuickBoot = (1 << 6),
};
enum class Display {
RGB,
SVideo,
Composite
CompositeColour,
CompositeMonochrome
};
/*!
@@ -47,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.
@@ -74,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 */

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