1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-10-25 09:27:01 +00:00

Compare commits

...

1943 Commits

Author SHA1 Message Date
Thomas Harte
506b4da6c3 Merge pull request #408 from TomHarte/MixerBalance
Enhances the CompoundSource so that constituents can have different volumes.
2018-04-07 14:32:47 -04:00
Thomas Harte
10f637d2cf Enhances the CompoundSource so that constituents can have different volumes. 2018-04-07 14:30:02 -04:00
Thomas Harte
0bab7c88f0 Merge pull request #407 from TomHarte/NameImplications
Allows the Vic-20 analyser to act on 'NTSC' in a filename.
2018-04-06 20:10:56 -04:00
Thomas Harte
78c612ca17 Adds a missing import, removes a redundant conversion. 2018-04-06 20:07:10 -04:00
Thomas Harte
e1c4035812 Switches away from C strings and allows Vic-20 region inference from filenames. 2018-04-06 17:42:24 -04:00
Thomas Harte
eb6d6c8033 Merge pull request #406 from TomHarte/NewFixes
Tweaks the 'new machine' dialogue for ZX memory size
2018-04-05 22:02:10 -04:00
Thomas Harte
7bf88565ce Resizes to fit all options. 2018-04-05 21:59:19 -04:00
Thomas Harte
ee10155296 Adds advice and withdraws the ZX 64kb option. 2018-04-05 21:57:26 -04:00
Thomas Harte
cc49140f6f Merge pull request #405 from TomHarte/VicFraming
Introduces different clipping zones for NTSC and PAL output.
2018-04-05 21:26:07 -04:00
Thomas Harte
3e846f89a1 Introduces different clipping zones for NTSC and PAL output. 2018-04-05 21:25:19 -04:00
Thomas Harte
5782cab2a0 Minor whitespace fix. 2018-04-05 21:15:25 -04:00
Thomas Harte
8c511e2b76 Merge pull request #404 from TomHarte/ProperShaderSetup
Ensures the SVideo shader gets all proper `enable_vertex_attribute_with_pointer`s.
2018-04-05 21:13:26 -04:00
Thomas Harte
ec72fb3baf Ensures the SVideo shader gets all proper enable_vertex_attribute_with_pointers. 2018-04-05 21:12:28 -04:00
Thomas Harte
bab1440f5c Merge pull request #403 from TomHarte/VicRange
Causes the 6560 to obey `set_sample_volume_range`.
2018-04-05 21:06:09 -04:00
Thomas Harte
60c1da6a66 Causes the 6560 to obey set_sample_volume_range.
Thereby resolves a clipping issue.
2018-04-05 21:04:46 -04:00
Thomas Harte
a849b3f2e4 Merge pull request #402 from TomHarte/AudioCutoff
Ensures artificial audio frequency limits are honoured.
2018-04-05 19:05:48 -04:00
Thomas Harte
dbe3c5c3f8 Ensures artificial frequency limits are honoured. 2018-04-05 18:40:07 -04:00
Thomas Harte
60cf6b3cfd Merge pull request #401 from TomHarte/VideoQuirks
Corrects composite output of the ZX80/81 and the Oric
2018-04-04 19:23:45 -04:00
Thomas Harte
5044aac337 Sizes up default window size better to fit machine selector. 2018-04-04 19:18:22 -04:00
Thomas Harte
36e0cb29c0 Ensures proper propagation of video choice through the Oric. 2018-04-04 19:14:42 -04:00
Thomas Harte
c0b4dd65da Mades the expected video signal usage explicit. 2018-04-04 19:01:18 -04:00
Thomas Harte
d061ea232b Ensures no attempt to compile an SVideo shader without appropriate source. 2018-04-04 19:01:01 -04:00
Thomas Harte
49feca4ddf Merge pull request #400 from TomHarte/NewCrash
Introduces a rudimentary 'new' dialogue for the Mac
2018-04-03 23:24:00 -04:00
Thomas Harte
46b1c57bf4 Enables the titlebar, inexplicably allowing the sheet to obtain focus. 2018-04-03 23:22:26 -04:00
Thomas Harte
eaf1482182 Reverts the once-again-unused document controller. 2018-04-03 23:11:19 -04:00
Thomas Harte
d3418550eb Attempts explicitly to disable promise of saving. 2018-04-03 23:06:48 -04:00
Thomas Harte
3ffa9e2751 Ensures complete machine picker state is preserved. 2018-04-03 23:01:12 -04:00
Thomas Harte
c697dd78f0 Ensures a new machine starts as first responder. 2018-04-03 22:22:39 -04:00
Thomas Harte
7dac791290 Causes the machine picker to show as a sheet.
Albeit with some user experience issues lingering.
2018-04-03 18:47:07 -04:00
Thomas Harte
cde2faeda6 Makes an unsuccessful attempt to show the new machine dialogue as a sheet.
Also corrects the 'open' case versus recent changes.
2018-04-02 23:31:36 -04:00
Thomas Harte
69f520428d Makes a first, ugly attempt at a 'new machine' dialogue for the Mac.
Which has implied getting much more specific about MSX disk drive attachment, and has prompted an excuse to offer the ZX80 with the ZX81 ROM.
2018-04-02 22:42:41 -04:00
Thomas Harte
80c84ddd75 Merge pull request #398 from TomHarte/SVideoOption
Exposes S-Video as a user-selectable option
2018-04-01 13:30:41 -04:00
Thomas Harte
fca8a58b36 Exposes S-Video option in the Mac UI. 2018-04-01 13:29:04 -04:00
Thomas Harte
33084899d0 Provides s-video as a command-line option. 2018-03-31 22:14:34 -04:00
Thomas Harte
7b381a8b6b Merge pull request #397 from TomHarte/Vic20FastTape
Improves Vic-20 fast tape ownership and simplifies memory logic.
2018-03-31 21:05:22 -04:00
Thomas Harte
9c75689a8d Increased verbosity. 2018-03-31 20:58:16 -04:00
Thomas Harte
0ee40e8556 Reintroduces 90% crop for VIC output. 2018-03-31 20:57:45 -04:00
Thomas Harte
8b45377b89 Simplifies storage underlying Vic memory.
In the hope of avoiding non-obvious bugs.
2018-03-31 18:54:40 -04:00
Thomas Harte
f6fb368d88 Allows the fast-tape mechanism to take ownership of tape handling.
Any successful fast tape interaction will now permanently pause the tape until a failed interaction occurs. This may or may not be a good idea.
2018-03-30 21:22:52 -04:00
Thomas Harte
183a5379de Merge pull request #396 from TomHarte/SVideo
Adds support for s-video.
2018-03-30 18:25:28 -04:00
Thomas Harte
912791d3d4 Causes the s-video path correctly to function. 2018-03-30 18:24:18 -04:00
Thomas Harte
163a61dd63 Corrects SVideo-as-composite output; the Atari and Vic-20 now both supply svideo. 2018-03-30 13:16:18 -04:00
Thomas Harte
207d462dbf Attempts to provide an implementation of SVideo support. 2018-03-30 12:41:20 -04:00
Thomas Harte
33281b9d89 Introduces S-Video as a video signal type at the interface level. 2018-03-30 10:25:41 -04:00
Thomas Harte
389979923e Performs update to and satisfaction of Xcode 9.3's preferred warnings. 2018-03-30 10:25:01 -04:00
Thomas Harte
067174965e Merge pull request #395 from TomHarte/TEDEsqueColours
Introduces Vic luminances sourced from the TED manual.
2018-03-30 09:39:02 -04:00
Thomas Harte
286259c83b Adds missing 6560 update hooks. 2018-03-29 20:49:36 -04:00
Thomas Harte
e1aa3e5a7f Imports chrominances from the TED documentation. They seem to apply to the VIC-I also. 2018-03-29 20:04:37 -04:00
Thomas Harte
78e1c2851a Merge pull request #393 from TomHarte/Vic20Faster
Introduces some minor Vic-20 optimisations.
2018-03-27 22:04:40 -04:00
Thomas Harte
0869213c55 Cuts detritus. 2018-03-27 22:00:13 -04:00
Thomas Harte
f3fe16215a Reintroduces options for the Vic-20, now tape loading speed only. 2018-03-27 21:55:43 -04:00
Thomas Harte
ec353ce663 Makes minor Vic-20 optimisations.
Specifically: the 6560 is updated only upon writes (more nuance can arrive), and tape sleeps are observed.
2018-03-27 21:52:52 -04:00
Thomas Harte
b7ff5ef9dd Merge pull request #392 from TomHarte/VicPalette
Tweaks VIC palette, especially PAL.
2018-03-26 21:25:12 -04:00
Thomas Harte
3b26e0a7c5 Tweaks NTSC colour generation. 2018-03-26 21:22:06 -04:00
Thomas Harte
6d464557a0 Reintroduces a warm-up run for the C1540.
That simulates the normal real-life scenario of switching the drive on slightly before the computer, and causes it to function correctly from immediate fast typing on an American Vic.

Also switches a couple of casts within the C1540 to functional style.
2018-03-26 21:06:07 -04:00
Thomas Harte
a776bec46a Tweaks PAL colours for the 6560 to be closer to screenshots found online. 2018-03-26 19:02:16 -04:00
Thomas Harte
a2da51c30b Commutes Vic-20 machine configuration options to its Target. 2018-03-26 19:01:57 -04:00
Thomas Harte
8067bf548a Merge pull request #390 from TomHarte/VicOptions
Ensures the Vic-20 doesn't show the ZX80/81 options panel on macOS.
2018-03-25 16:07:13 -04:00
Thomas Harte
62b0645ed0 Ensures the Vic-20 doesn't show the ZX80/81 options panel on macOS. 2018-03-25 16:04:44 -04:00
Thomas Harte
39a94874ae Merge pull request #389 from TomHarte/VicAnalysis
Strips back Vic-20 static analysis to the bare minimum.
2018-03-25 13:42:59 -04:00
Thomas Harte
e15d6717a1 Strips back Vic-20 static analysis to the bare minimum.
Also corrects an unsafe assumption in fast loading.
2018-03-25 13:37:33 -04:00
Thomas Harte
37ef46e7bb Merge branch 'SDLTravis' of github.com:TomHarte/CLK into SDLTravis 2018-03-23 21:52:27 -04:00
Thomas Harte
70c09b3031 Attempted to draft a travis.yml for SDL. 2018-03-23 21:51:15 -04:00
Thomas Harte
9378fbb0df Attempted to draft a travis.yml for SDL. 2018-03-23 21:40:46 -04:00
Thomas Harte
2118b9c0cd Merge pull request #385 from TomHarte/OricHFE
Corrects nullptr references in the CPC static analyser.
2018-03-23 18:40:13 -04:00
Thomas Harte
d0c53de250 Corrects nullptr references in the CPC static analyser. 2018-03-23 18:39:37 -04:00
Thomas Harte
d98507eab0 Merge pull request #384 from TomHarte/PlentifulIcons
Fills out the application icon set.
2018-03-23 18:33:02 -04:00
Thomas Harte
760c75103e Fills out the application icon set. 2018-03-23 18:29:18 -04:00
Thomas Harte
4407fd2f1f Merge pull request #383 from TomHarte/D64Crash
Ensures the rom fetcher is properly provided to the C1540.
2018-03-23 18:22:37 -04:00
Thomas Harte
7fcd243be0 Ensures the rom fetcher is properly recorded for potential provision to the C1540. 2018-03-23 18:20:17 -04:00
Thomas Harte
3165e9d82e Merge pull request #382 from TomHarte/Headers
Introduces missing #includes.
2018-03-23 18:08:55 -04:00
Thomas Harte
6656a08c60 Introduces missing #includes. 2018-03-23 18:05:51 -04:00
Thomas Harte
76661c0b51 Merge pull request #375 from TomHarte/UndefinedBehaviour
Resolves various pieces of undefined behaviour.
2018-03-22 22:01:19 -04:00
Thomas Harte
3bb496f9ae Enforces a maximum sector size to avoid impossible sizes.
Such as 128 * 2^255.
2018-03-22 22:00:26 -04:00
Thomas Harte
45be1c19df Resolves undefined behaviour of a signed shift left. 2018-03-22 21:59:39 -04:00
Thomas Harte
a301964bd0 Ensures all audio queues are fully merged before machine destruction.
Thereby avoids a race condition.
2018-03-22 21:59:19 -04:00
Thomas Harte
eea6858121 Resolves undefined behaviour from uninitialised limited-range values. 2018-03-22 21:58:42 -04:00
Thomas Harte
2a320fdf56 Merge pull request #374 from TomHarte/HFEFixup
Corrects two errors in all-machine HFE offering.
2018-03-22 20:24:24 -04:00
Thomas Harte
4695296ef2 Corrects bit mask for offering HFE around. 2018-03-22 20:23:39 -04:00
Thomas Harte
0fdbbeca1d Ensures the Commodore parser properly rejects non-GCR disks. 2018-03-22 20:23:21 -04:00
Thomas Harte
34cc39ad65 Merge pull request #373 from TomHarte/SpeakerCritical
Moves all LowpassSpeaker delegate calls outside of critical sections.
2018-03-22 20:07:20 -04:00
Thomas Harte
3d0c832a21 Moves all LowpassSpeaker delegate calls outside of critical sections. 2018-03-22 19:01:20 -04:00
Thomas Harte
1acdab9448 Expanded potential HFE targets to everything other than the MSX.
The MSX does not yet perform any sanity checks on disks. That's TODO.
2018-03-22 18:55:52 -04:00
Thomas Harte
93e85c5c4a The CPC now accepts disks only if it can make sense of them. 2018-03-22 18:52:43 -04:00
Thomas Harte
ab98189d25 Merge pull request #372 from TomHarte/MultiJoystick
Implements multimachine joystick support.
2018-03-22 11:09:38 -04:00
Thomas Harte
cd0fb7624b Pulls delegate messages out of the critical sections. 2018-03-22 11:08:07 -04:00
Thomas Harte
bae38497bb Implements multitarget joysticks. 2018-03-22 11:07:52 -04:00
Thomas Harte
29921bfa8d Merge pull request #371 from TomHarte/NanosecondMachines
Devolves time -> clock rate mapping to machines.
2018-03-22 10:08:58 -04:00
Thomas Harte
2712702461 Makes get_clock_rate protected. It's now an implementation detail. 2018-03-22 10:01:18 -04:00
Thomas Harte
a3fa9440d1 Renames method better to communicate purpose. 2018-03-22 09:49:36 -04:00
Thomas Harte
6419b0e619 Reintroduces CSMachineDelegate, allowing the Mac port to switch output audio rate dynamically. 2018-03-22 09:48:19 -04:00
Thomas Harte
58e5b6e3f1 Updates SDL kiosk mode to the death of CRTMachineDelegate. 2018-03-22 09:23:27 -04:00
Thomas Harte
682c3d8079 Adds new hook for watching audio output rate changes. 2018-03-22 09:23:01 -04:00
Thomas Harte
da3d65c18f Devolves time to cycle conversion to machines.
Thereby avoids a whole bunch of complicated machinations that would otherwise have been required of the multimachine.
2018-03-21 22:18:13 -04:00
Thomas Harte
ece3a05504 Merge pull request #370 from TomHarte/OricDiskDetection
Causes the Oric properly to evaluate disks offered to it.
2018-03-21 20:51:12 -04:00
Thomas Harte
927697b0f0 Causes the Oric properly to evaluate disks offered to it. 2018-03-21 20:48:21 -04:00
Thomas Harte
74dfc80b0f Merge pull request #369 from TomHarte/AnalyserUnion
Encapsulates per-platform analyser result fields.
2018-03-09 16:13:17 -05:00
Thomas Harte
a7f229bc4b Adds missing files. 2018-03-09 16:10:17 -05:00
Thomas Harte
89bec2919f Encapsulates machine configuration properties for all remaining platforms. 2018-03-09 16:07:29 -05:00
Thomas Harte
78eaecb29e Provides the proper framework for encapsulation of analyser target specifics.
... while making them a safe container for objects too. Uses the ZX80/81 as the pilot platform.
2018-03-09 15:36:11 -05:00
Thomas Harte
d410aea856 Merge pull request #368 from TomHarte/DiamondInheritance
Eliminates diamond inheritance of KeyboardMachine::Machine by typers.
2018-03-09 15:19:54 -05:00
Thomas Harte
6b1eef572b Eliminates diamond inheritance of KeyboardMachine::Machine by typers.
Specifically by pulling the key action stuff into a purely abstract class [/interface]. Takes the opportunity to unpublish a bunch of machine details.
2018-03-09 15:19:02 -05:00
Thomas Harte
719f5d79c2 Merge pull request #367 from TomHarte/DynamicVolume
Introduces formal setting of the output volume to `SampleSource`.
2018-03-09 14:10:55 -05:00
Thomas Harte
48737a32a7 Introduces formal setting of the output volume to SampleSource.
Previously every output device was making its own decision. Which is increasingly less sustainable due to the CompoundSource.
2018-03-09 13:23:18 -05:00
Thomas Harte
53f05efb2d Merge pull request #366 from TomHarte/MoreMemptr
Improves Z80 memptr behaviour.
2018-03-09 10:05:57 -05:00
Thomas Harte
0e73ba4b3e Introduces proper 5/3 SCF/CCF behaviour for the Z80.
While also `const`ing a bunch of things.
2018-03-09 09:47:00 -05:00
Thomas Harte
f0f9d5a6af Corrects memptr leakage via BIT, and ld (de/bc/nn), A behaviour. 2018-03-08 20:30:22 -05:00
Thomas Harte
03501df9e5 Merge pull request #365 from TomHarte/CartridgeDetermination
Works towards eliminating the special cases for Atari 2600 ROM handling.
2018-03-08 18:40:58 -05:00
Thomas Harte
dd6f85d4db Merge pull request #364 from TomHarte/TimingUpfront
Ensures the Coleco & MSX account for instruction lengths prior to outward accesses.
2018-03-07 17:29:32 -05:00
Thomas Harte
1804ea6849 Ensures the ColecoVision and MSX account for instruction lengths in advance when timing secondary components. 2018-03-07 17:00:18 -05:00
Thomas Harte
c8657e08f4 Merge remote-tracking branch 'origin/master' into CartridgeDetermination 2018-03-07 16:42:16 -05:00
Thomas Harte
a942e1319b Merge pull request #363 from TomHarte/ZonX
Introduces ZonX emulation and corrects a minor ColecoVision AY timing issue.
2018-03-07 16:23:51 -05:00
Thomas Harte
9e0a56b4f0 Withdraws the 2600 from .rom consideration.
Will return when it is performing more sanity checks; for the time being I don't want it constantly forcing multimachines.
2018-03-07 16:21:17 -05:00
Thomas Harte
9abc020818 Corrects potential ColecoVision SGM AY timing issues. 2018-03-07 16:16:58 -05:00
Thomas Harte
2dade8d353 Introduces ZonX emulation for the ZX81. 2018-03-07 16:16:29 -05:00
Thomas Harte
1100dc6993 Opens up .bin and .rom to all cartridge platforms, and adds a confidence estimate to the Atari 2600. 2018-03-07 14:26:07 -05:00
Thomas Harte
f212b18511 Declares a confidence for the ColecoVision equal to the probability that the special bytes are wrong. 2018-03-07 14:25:25 -05:00
Thomas Harte
a6ca69550f Standardises machines that aren't making a real guess on reporting a confidence of 0.5. 2018-03-07 14:24:52 -05:00
Thomas Harte
2452641844 Introduces a fast workaround to avert a MultiMachine where it would instantly end. 2018-03-06 19:08:02 -05:00
Thomas Harte
c82af4b814 Introduces get_confidence for the ColecoVision.
Based almost entirely on joypad accesses for now.
2018-03-06 19:06:35 -05:00
Thomas Harte
fdef914137 Corrects test target regression. 2018-03-06 18:32:21 -05:00
Thomas Harte
dfcc502a88 Merge pull request #360 from TomHarte/SDLJoystick
Introduces keyboard-as-joystick fallback for the SDL target.
2018-03-04 17:28:05 -05:00
Thomas Harte
1c6faaae88 Introduces keyboard-as-joystick fallback for the SDL target. 2018-03-04 17:26:32 -05:00
Thomas Harte
35c8a0dd8c Merge pull request #359 from TomHarte/MentionColecoVision
Adds the ColecoVision to the declared list of machines.
2018-03-03 19:05:05 -05:00
Thomas Harte
38feedaf6a Adds the ColecoVision. 2018-03-03 19:03:54 -05:00
Thomas Harte
0a2f908af4 Merge pull request #358 from TomHarte/TMSPhase
Picks a phase for the TMS empirically.
2018-03-03 13:56:10 -05:00
Thomas Harte
705d53cc21 Picks a phase for the TMS empirically. 2018-03-03 13:53:00 -05:00
Thomas Harte
35b18d58af Merge pull request #357 from TomHarte/SuperGameModule
Adds Super Game Module support for the ColecoVision.
2018-03-03 13:14:48 -05:00
Thomas Harte
3c5a8d9ff3 Adds Super Game Module support for the ColecoVision. 2018-03-03 13:08:33 -05:00
Thomas Harte
7ca02be578 Merge pull request #356 from TomHarte/Multicolour
Implements multicolour mode on the TMS.
2018-03-02 23:10:31 -05:00
Thomas Harte
ea13c7dd32 Implements multicolour mode on the TMS. 2018-03-02 23:08:01 -05:00
Thomas Harte
fdfd72a42c Merge pull request #355 from TomHarte/MegaCart
Adds MegaCart support for the ColecoVision.
2018-03-02 19:21:26 -05:00
Thomas Harte
da97bf95c0 Loosens ColecoVision cartridge size test to allow for slightly broken images. 2018-03-02 19:20:37 -05:00
Thomas Harte
bdfc36427c Implements MegaCart support. 2018-03-02 18:40:01 -05:00
Thomas Harte
74dfe56d2b Expands documentation of NMI setting.
Given that it was previously incorrect, explains logic behind request_status_ and last_request_status_ setting. Also takes the opportunity to ensure that NMI is 'sampled' at the same time as IRQ; whether the next thing should be the NMI routine now occurs one cycle before the end of any instruction. That's an assumption for now. Testing to come.
2018-03-02 11:10:02 -05:00
Thomas Harte
6cce9aa54e Merge pull request #353 from TomHarte/ColecoVision
Adds provisional emulation of the ColecoVision
2018-03-01 22:33:34 -05:00
Thomas Harte
ba68b7247b Adds latest files to SConstruct. 2018-03-01 22:19:50 -05:00
Thomas Harte
b02e4fbbf6 Corrects NMI receipt to be genuinely edge triggered.
Previously a caller that signalled NMI set multiple times would trigger multiple NMIs.
2018-03-01 22:04:56 -05:00
Thomas Harte
59b4c7314d Merge branch 'master' into ColecoVision 2018-03-01 22:01:26 -05:00
Thomas Harte
d328589bd0 Merge pull request #354 from TomHarte/MSXTiming
Corrects a counting error in the MSX.
2018-03-01 22:00:53 -05:00
Thomas Harte
b05d2b26bf Corrects a counting error in the MSX. 2018-03-01 21:59:51 -05:00
Thomas Harte
86239469e7 Allows SN76489 consumers to apply an additional divider that reduces computation. 2018-03-01 18:51:05 -05:00
Thomas Harte
7890506b16 Gives the SN76489 its proper dividers and personalities. 2018-02-28 22:36:03 -05:00
Thomas Harte
83f73c3f02 Installs additional safeguards against unsafe deconstruction. 2018-02-28 22:15:22 -05:00
Thomas Harte
87760297fc Fixes underpumping of SN76489.
Audio works now. Though I still need properly to confirm who owns dividers in practice. I think probably all division should be within the SN.
2018-02-27 22:59:29 -05:00
Thomas Harte
5b854d51e7 Corrects out-of-bounds access. 2018-02-27 22:45:45 -05:00
Thomas Harte
d4df101ab6 Makes a first attempt at implementing the SN76489. 2018-02-27 22:25:12 -05:00
Thomas Harte
0ad2676640 Adds a class for the SN76489 and wires it into the ColecoVision. 2018-02-26 22:04:34 -05:00
Thomas Harte
a074ee2071 Possibly fixes ColecoVision input mapping.
Also provides symbolic input from the Mac.
2018-02-25 22:47:47 -05:00
Thomas Harte
204d5cc964 Extends JoystickMachine protocol to cover ColecoVision use case.
Also thereby implements input on the ColecoVision, in theory at least. No input is being fed though, so...
2018-02-25 19:08:50 -05:00
Thomas Harte
23d15a4d6c The ColecoVision now accepts and loads cartridges. 2018-02-24 18:26:44 -05:00
Thomas Harte
23c47e21de Proceeds the ColecoVision to booting. 2018-02-24 18:14:38 -05:00
Thomas Harte
5530b96446 Wired up a class and analyser for a ColecoVision. 2018-02-23 22:47:15 -05:00
Thomas Harte
99d28a172b Merge pull request #352 from TomHarte/TZXCompletion
Makes an attempt at implementing all missing TZX 1.20 blocks.
2018-02-22 21:37:46 -05:00
Thomas Harte
d83178f29d Makes an attempt at implementing all missing TZX 1.20 blocks.
Towards that aim, simplifies CSW handling so that even regular RLE compression uses a static grab of file contents.
2018-02-22 21:28:12 -05:00
Thomas Harte
d9d5ffdaa2 Merge pull request #351 from TomHarte/TMSFlip
Optimises the inner TMS loops slightly.
2018-02-21 21:33:04 -05:00
Thomas Harte
cabad6fc05 Optimises the inner TMS loops slightly. 2018-02-21 21:29:17 -05:00
Thomas Harte
a4dc9c0403 Merge pull request #350 from TomHarte/MinorMSXOptimisations
Introduces modest MSX optimisations
2018-02-19 20:53:20 -05:00
Thomas Harte
270723ae72 Forces the MSX's perform_machine_cycle into the Z80. 2018-02-19 19:54:42 -05:00
Thomas Harte
b215cf83d5 Eliminates implicit update queue flush, as unnecessary. 2018-02-19 19:54:18 -05:00
Thomas Harte
f237dcf904 Avoids deadlock when one bestEffortUpdate action implies another. 2018-02-19 18:44:12 -05:00
Thomas Harte
fc81bfa59b Eliminates tape player call when tape is not playing. 2018-02-19 18:36:31 -05:00
Thomas Harte
832ac173ae Merge pull request #349 from TomHarte/CheaperTapeChecks
Reduces cost of checking for fast-tape usage
2018-02-19 16:58:03 -05:00
Thomas Harte
3673cfe9be Pulls method call for tape fast loading checks out of inner loop for the Vic, Electron and ZX80/81. 2018-02-19 16:57:24 -05:00
Thomas Harte
6aaef97158 Breaks Mac machine shutdown deadlock. 2018-02-19 16:48:03 -05:00
Thomas Harte
b0ab617393 Simplifies inner loop test for MSX fast loading. 2018-02-19 16:24:47 -05:00
Thomas Harte
6780b0bf11 Corrects error preventing fast loading preference from making it to machines on the Mac. 2018-02-19 16:24:28 -05:00
Thomas Harte
9c0a440c38 Merge pull request #347 from TomHarte/DynamicAnalysis
Introduces dynamic selection of MSX MegaROM type
2018-02-19 16:10:46 -05:00
Thomas Harte
2439f5aee5 Corrects some whitespace errors. 2018-02-19 16:06:46 -05:00
Thomas Harte
8265f289bd Improves documentation within the new parts. 2018-02-19 16:03:17 -05:00
Thomas Harte
9728bea0a7 Updates scons file and corrects missing headers; backports to C++11. 2018-02-19 05:13:41 -08:00
Thomas Harte
fc9e84c72e Eliminates unsafe optimisation.
Also likely to be unhelpful as and when multiple machines are in play.
2018-02-18 22:09:27 -05:00
Thomas Harte
7d75e864b1 Ensures thread safety of usages of bestEffortLock. 2018-02-18 22:09:03 -05:00
Thomas Harte
a005dabbe3 Corrects some minor outstanding data races. 2018-02-18 16:37:07 -05:00
Thomas Harte
c8a4432c63 Makes an attempt to transfer audio outputs during dynamic analysis. 2018-02-18 15:23:15 -05:00
Thomas Harte
7b420d56e3 Removed state mirroring in the machine-specific Mac UI classes. 2018-02-14 21:46:50 -05:00
Thomas Harte
ddf1bf3cbf Reintroduces options selection for the Mac.
For everything except the Vic-20, anyway. That has a somewhat outdated notion of what an options panel should be, corresponding to the work yet to do on its analyser.
2018-02-12 21:46:21 -05:00
Thomas Harte
7ea4ca00dc Ensures perform_parallel doesn't lock up if all machines complete prior to reaching condition.wait. 2018-02-11 21:06:51 -05:00
Thomas Harte
6b8c223804 Adds an extra termination condition for the multimachine. 2018-02-11 21:05:59 -05:00
Thomas Harte
23105956d6 Fixes spurious unrecognised miss detection for the ASCII mappers. 2018-02-11 20:51:39 -05:00
Thomas Harte
d751b7e2cb Marginally reformats for current style. 2018-02-11 20:32:59 -05:00
Thomas Harte
f02989649c Corrects effect of pc_is_outside_bios. 2018-02-11 20:32:45 -05:00
Thomas Harte
dcf313a833 Changes equivocal semantics. 2018-02-11 20:32:21 -05:00
Thomas Harte
9960121b08 Introduces an exit condition for the multi machine. 2018-02-11 20:24:08 -05:00
Thomas Harte
8eea55b51c Simplifies perform_parallel slightly. 2018-02-10 23:39:30 -05:00
Thomas Harte
e1cab52c84 Ensures thread safety of access to machines array. 2018-02-10 19:38:26 -05:00
Thomas Harte
eb39617ad0 Allows cartridges to filter based on the actor talking to them; corrects outstanding_machines access error. 2018-02-10 17:11:16 -05:00
Thomas Harte
43b682a5af Adds multiple target versions of all the DynamicMachine-vended types. 2018-02-09 16:31:05 -05:00
Thomas Harte
043fd5d404 Merge branch 'DynamicAnalysis' of github.com:TomHarte/CLK into DynamicAnalysis 2018-02-09 09:12:05 -05:00
Thomas Harte
d63a95983d Adds a couple of hard-stop conditions to the MSX, and respect for hard stops. 2018-02-09 09:10:56 -05:00
Thomas Harte
4cf258f952 Parallelises MultiMachine running, and ensures errors propagate. 2018-02-08 20:33:57 -05:00
Thomas Harte
4e720d57b2 With debugging hooks still on display, makes first attempt at dynamic analysis. 2018-02-01 07:53:52 -05:00
Thomas Harte
c12aaea747 Attempts to get as far as running the MultiMachine.
In doing so, fixes the long-standing bug that machines that output audio but don't have a listener produce a divide by zero.
2018-01-30 22:23:06 -05:00
Thomas Harte
ca48497e87 Pulls DynamicMachine out of MachineForTarget and adds MultiConfigurationTarget as a first multiplexer. 2018-01-29 21:49:49 -05:00
Thomas Harte
d493ea4bca Introduces a multimachine to handle multi-target static analyser outputs.
Non-functional as of yet.
2018-01-28 22:22:21 -05:00
Thomas Harte
e025674eb2 The MSX analyser is now smart enough not to be definitive when it's uncertain.
The cartridge type has also migrated to being a property of the cartridge, prefiguring my intention to discard the static analyser union.
2018-01-25 22:16:46 -05:00
Thomas Harte
f2519f4fd7 Decided to focus on 'confidence' over 'probability'.
Besides anything else, it individualises the measure. E.g. two targets can each have a confidence of 0.8 without each giving the wrong answer about probability.
2018-01-25 19:02:16 -05:00
Thomas Harte
db914d8c56 Removes redundant second configuration. 2018-01-25 18:50:23 -05:00
Thomas Harte
66faed4008 Gives MachineForTargets complete responsibility for initial machine state. 2018-01-25 18:28:19 -05:00
Thomas Harte
11abc99ef8 Introduces the extra level of indirection necessary to make Analyser::Static::Target polymorphic. 2018-01-24 22:35:54 -05:00
Thomas Harte
21efb32b6f Integrates the static and nascent dynamic analyser namespaces. 2018-01-24 21:48:44 -05:00
Thomas Harte
622a04aec8 Starts stripping the Mac port of its special machine knowledge.
Partly to force myself into moving that stuff into the cross-platform area, but mainly so that dynamic analysis can work equally from day one.
2018-01-24 20:14:15 -05:00
Thomas Harte
d360b2c62d Standardises the static analyser on std::vector and slightly widens passageway to a machine.
The SDL target would now be fooled by a hypothetical multi-target, the Mac not yet.
2018-01-23 22:18:16 -05:00
Thomas Harte
6a112edc18 Corrects 16kb ASCII mapper.
Also increases hit position acceptance for the 8kb ASCII.
2018-01-22 22:13:16 -05:00
Thomas Harte
8fb4409ebb Adds hasty attempt at dynamic analysis to the MSX ROM handlers.
Logging for now, for further experimentation.
2018-01-22 21:50:56 -05:00
Thomas Harte
d213341d9c Introduces the counters upon which I expect dynamic analysis to rest. 2018-01-22 21:39:23 -05:00
Thomas Harte
c2f1306d85 Updates copyright year. 2018-01-18 21:11:30 -05:00
Thomas Harte
2143ea6f12 Merge pull request #344 from TomHarte/MacICON
Introduces an icon for the Mac.
2018-01-18 18:08:44 -08:00
Thomas Harte
edb30b3c6c Introduces an icon for the Mac.
About which I have yet to decide my full feelings.
2018-01-18 21:01:30 -05:00
Thomas Harte
234e4f6f66 Merge pull request #343 from TomHarte/MSXROMs
Allows 8kb and not-quite-multiple-of-8kb MSX ROMs
2018-01-18 16:57:05 -08:00
Thomas Harte
ce2d3c6e82 Resolves implicit conversion warning. 2018-01-17 22:02:16 -05:00
Thomas Harte
46c76b9c07 Switches to using the boilerplate public.item for all macOS UTIs. 2018-01-17 22:01:38 -05:00
Thomas Harte
583c3cfe7d Allows the MSX to load ROMs that aren't quite multiples of 8kb. 2018-01-16 22:27:41 -05:00
Thomas Harte
e13312dcc5 Removed stray new line. 2018-01-16 21:46:31 -05:00
Thomas Harte
d9e49c0d5f Merge pull request #340 from TomHarte/MSXDocs
Adds the MSX to README.md.
2018-01-16 16:47:57 -08:00
Thomas Harte
8a370cc1ac Adds the MSX to README.md. 2018-01-16 19:46:29 -05:00
Thomas Harte
cdae0fa593 Merge pull request #339 from TomHarte/AcornROMs
Allows the Electron to load 8kb ROMs.
2018-01-15 18:28:19 -08:00
Thomas Harte
765c0d4ff8 Allows the Electron to load 8kb ROMs. 2018-01-15 21:27:45 -05:00
Thomas Harte
4cf2e16b5c Merge pull request #338 from TomHarte/MSXComposite
onsolidates Mac presentation of composite video selection.
2018-01-15 15:38:45 -08:00
Thomas Harte
9cbd61e709 Replaces CRT quantity assert with test.
Primarily to handle television/composite target switches that can unsync the buffers.
2018-01-15 18:37:09 -05:00
Thomas Harte
0202c7afb2 Consolidates Mac presentation of composite video selection.
Moves handling of an RGB/composite into `MachinePanel`, eliminating the need for `ElectronOptionsPanel` and `OricOptionsPanel`; similarly merges the MSX and Electron options panels so as to provide television/monitor selection for the MSX.
2018-01-15 18:36:22 -05:00
Thomas Harte
c187c5a637 Merge pull request #337 from TomHarte/DoublePhase
Corrects calculation of intermediate buffer width multiplier.
2018-01-15 13:57:26 -08:00
Thomas Harte
23c34a8c14 Corrects calculation of intermediate buffer width multiplier.
Specifically: I had failed to factor in that the multiplied-up input frequency might be less than than the full width of the bitmap.

The Atari and MSX in particular now look much better.
2018-01-15 16:52:40 -05:00
Thomas Harte
93ece2aec7 "Doubles" the bandwidth given to composite signals.
Because I suspect it may inadvertently have been halved previously. I'm investigating.
2018-01-14 20:44:53 -05:00
Thomas Harte
e12ab8fe2e Merge pull request #336 from TomHarte/TMSGamma
Sets TMS input gamma.
2018-01-13 19:20:32 -08:00
Thomas Harte
2fe0ceb52a Sets TMS input gamma. 2018-01-13 22:19:41 -05:00
Thomas Harte
f354c12c81 Merge pull request #335 from TomHarte/BetterTape
Makes MSX tape parsing more tolerant to phase.
2018-01-10 18:56:44 -08:00
Thomas Harte
def82cba49 Makes MSX tape parsing more tolerant to phase.
Also reintroduces proper file type association for TSX on the Mac.
2018-01-10 21:54:15 -05:00
Thomas Harte
e7bc7b94c9 Merge pull request #334 from TomHarte/DMK
Adds support for the DMK file format
2018-01-09 19:22:00 -08:00
Thomas Harte
aafdff49be Implements the ugly stuff of converting a DMK back to flux. 2018-01-09 22:13:04 -05:00
Thomas Harte
4ef583813a Minor tidying of PCMSegment and Oric MFM DSK. 2018-01-09 22:12:34 -05:00
Thomas Harte
9f97fb738e Merge branch 'master' into DMK 2018-01-09 19:42:27 -05:00
Thomas Harte
4e124047c6 Introduces enough DMK support to progress to failure to parse a track. 2018-01-08 21:57:11 -05:00
Thomas Harte
6eb56a1564 Corrects various comment typos. 2018-01-08 20:55:40 -05:00
Thomas Harte
35fc0a5c16 Corrects assumption of double sidedness. 2018-01-08 09:35:29 -05:00
Thomas Harte
b36c917810 Merge pull request #331 from TomHarte/MSXFloppy
Adds floppy emulation for the MSX
2018-01-07 19:25:11 -08:00
Thomas Harte
a5ac8c824e Removes logging and unnecessary get_drive_is_ready. 2018-01-07 21:59:59 -05:00
Thomas Harte
0ccc104027 Corrects start sector and track interleaving for MSX DSK.
MSX DSKs start with sector 1; Acorn disks still begin with sector 0. Also it turns out that MSX DSKs are indeed interleaved.
2018-01-07 21:59:18 -05:00
Thomas Harte
8be6cb827b Implements MSX interrupt/data request reading register.
The disk ROM now appears to accept on-disk bytes, but still announces an IO failure.
2018-01-07 20:28:34 -05:00
Thomas Harte
2f59226300 Fixes: DiskROM drive motor control, track_for_sectors' sides. 2018-01-07 20:02:40 -05:00
Thomas Harte
793ef68206 Implements unconditional force interrupt for the WD. 2018-01-07 19:42:38 -05:00
Thomas Harte
513c067f94 Makes an attempt to rope in the WD1770 for MSX disk ROM emulation. 2018-01-07 19:12:52 -05:00
Thomas Harte
999a0c22d4 Adds superficial support for MSX .DSK.
In the sense that the file format itself is properly parsed, but the MSX doesn't actually yet have disk hardware.
2018-01-07 16:35:57 -05:00
Thomas Harte
5d0832613f Merge pull request #330 from TomHarte/SCC
Adds emulation of the Konami SCC
2018-01-07 07:14:05 -08:00
Thomas Harte
2ffde4c3c2 Corrects SCC volume errors.
Which were leading to substantial overflow.
2018-01-07 09:59:00 -05:00
Thomas Harte
57ddfcd645 Corrects AY counter type. 2018-01-06 23:16:01 -05:00
Thomas Harte
fc16e8eb8c Makes first attempt at actually implementing the SCC. 2018-01-06 23:15:42 -05:00
Thomas Harte
655b971976 Establishes that there is such as a thing as a Konami SCC.
Creates one, ensures it appears in memory when intended to, lets it handle reads and writes. It currently does nothing.
2018-01-06 20:15:55 -05:00
Thomas Harte
3e1d8ea082 Adds is_silent to SampleSource plus shortcut processing to CompoundSource. 2018-01-06 18:50:26 -05:00
Thomas Harte
772c320d5a Merge pull request #329 from TomHarte/TMSTopLine
Corrects bad TMS sprite selections on the top row of the screen.
2018-01-06 13:26:33 -08:00
Thomas Harte
bcc7ad0c30 Corrects bad TMS sprite selections on the top row of the screen. 2018-01-06 16:26:11 -05:00
Thomas Harte
73b4e1722b Merge pull request #328 from TomHarte/MSXROMs
Introduces a basic attempt at MSX MegaROM support
2018-01-06 12:55:00 -08:00
Thomas Harte
185cd3c123 Expands and documents MSX::MemoryMap and MSX::ROMSlotHandler.
Hopefully to cover all intended use cases.
2018-01-06 15:51:29 -05:00
Thomas Harte
ed564cb810 Implements the main four cartridge banking schemes.
Slightly proof of concept for now.
2018-01-04 22:18:18 -05:00
Thomas Harte
b78ece1f1e Adds an attempt to catch LD (xx), A / [CALL/JP] pairs.
Also corrects use of std::stable_sort. Results are still largely incorrect though.
2018-01-02 22:18:10 -05:00
Thomas Harte
c8367a017f Cleans up test and makes attempt to factor in cartridge type popularity. 2018-01-01 21:21:05 -05:00
Thomas Harte
344a12566b Tweaks a couple of expected cartridge types. 2018-01-01 20:14:56 -05:00
Thomas Harte
c07113ea95 Ensures no illegal accesses while testing MSX ROM type detection.
Specifically: the static analyser doesn't even correctly identify everything that is an MSX ROM yet, let alone then properly determine type.
2018-01-01 17:38:26 -05:00
Thomas Harte
bc2879c412 Corrects the MSX ROM unit test.
I.e. the test is correct now, for those SHAs I could find. The static analyser is still wrong just slightly less than half the time.
2018-01-01 17:35:13 -05:00
Thomas Harte
1d47b55729 Ensures the selected cartridge start address is recorded in the cartridge. 2018-01-01 16:38:49 -05:00
Thomas Harte
db25b4554b Introduces failing tests of the MSX static analyser. 2018-01-01 16:38:26 -05:00
Thomas Harte
05b95ea2e0 Corrects Xcode tests. 2018-01-01 16:04:13 -05:00
Thomas Harte
250f7bf6b0 Makes attempt to support 48kb ROMs. 2018-01-01 11:25:27 -05:00
Thomas Harte
34db35b500 Merge pull request #327 from TomHarte/Z80Disassembler
Introduces a Z80 disassembler.
2017-12-31 18:39:01 -08:00
Thomas Harte
f75590253d Introduces necessary header for std::sort. 2017-12-31 21:36:24 -05:00
Thomas Harte
4f6abc9059 Introduces missing header. 2017-12-31 21:34:35 -05:00
Thomas Harte
c70dbc6a49 Introduces the most basic attempt to guess MSX cartridge type. 2017-12-31 21:23:30 -05:00
Thomas Harte
1c255b9e7d Generalises some of the disassembler, and provides Z80 logic to create a [first attempt at a] Z80 disassembler. 2017-12-31 18:49:35 -05:00
Thomas Harte
188bfa9c18 Merge pull request #326 from TomHarte/TyperTermination
Ensures typers terminate.
2017-12-30 10:49:53 -08:00
Thomas Harte
c7f8f37822 Ensures typers terminate. 2017-12-30 13:46:30 -05:00
Thomas Harte
4a19dbb8cb Merge pull request #325 from TomHarte/ContentTypes
Adds document type UTIs.
2017-12-30 10:41:14 -08:00
Thomas Harte
bf0601123b Adds some document type UTIs.
Will need to survey all the other Mac emulators to get a complete list, I guess.
2017-12-30 13:36:29 -05:00
Thomas Harte
9339f3413f Liberalises the end-of-file test for MSX ASCII.
From: must be back padded with 0x1a to merely must contain 0x1a.
2017-12-29 20:54:10 -05:00
Thomas Harte
c18517be4b Ensures that the fast loading option successfully flows from the Mac interface. 2017-12-29 19:07:22 -05:00
Thomas Harte
eef34adcbd Merge pull request #324 from TomHarte/MSXAnalysis
Introduces basic tape analysis for the MSX
2017-12-29 15:45:21 -08:00
Thomas Harte
769d9dfbb9 Adds missing header. 2017-12-29 18:41:26 -05:00
Thomas Harte
6a0bb83716 Corrects typos in the SDL main. 2017-12-29 18:40:32 -05:00
Thomas Harte
6da8a3e24b Causes the MSX to respond to the appropriate standard configuration options. 2017-12-29 18:36:42 -05:00
Thomas Harte
e349161a53 Rejigs the typing relationship so that use of a typer is not strongly implied by the interface.
Simultaneously implements typing on the MSX by direct insertion into the key buffer.
2017-12-29 18:30:46 -05:00
Thomas Harte
d5b1a9d918 Moves the typer functionality behind a functionality-based naming scheme, eliminates its C-style memory management. 2017-12-29 15:26:03 -05:00
Thomas Harte
76af0228dd Corrects longstanding survival of camel case in the analyser's loadingCommand. 2017-12-29 15:15:29 -05:00
Thomas Harte
2cc1a2684a Introduces [over-]analysis of cassette contents prior to starting the MSX, and simplifies ROM checking.
So a proper loading command is now known.
2017-12-29 15:11:10 -05:00
Thomas Harte
98a9d57c0b Imputes the alignment requirement for CAS headers.
Also stops adding a spurious 0xff as the final byte on the tape.
2017-12-29 10:42:18 -05:00
Thomas Harte
c481293aca Liberalises CAS interpretation.
It seems to be an even weirder file format than I thought; it can contain only ROM-formatted data but seemingly often contains blobs that the ROM cannot write.
2017-12-29 09:56:58 -05:00
Thomas Harte
5fd0a2b9ea Attempts to pull reimplementations of TAPION and TAPIN better into line with originals.
Also improves whole flow of the fast tape hack that uses them.
2017-12-28 22:48:04 -05:00
Thomas Harte
11b73a9c0b Adds preliminary, non-error-checking wiring in of MSX parser alternatives to TAPION and TAPIN.
As both a prototype of the pending fast tape loading, and to provide for exact behaviour comparison.
2017-12-26 22:31:34 -05:00
Thomas Harte
c4950574ea Introduces an attempted reimplementation of the MSX BIOS's two main tape reading entry points. 2017-12-26 22:19:37 -05:00
Thomas Harte
0b297f2972 Adds some appropriate costs to the tape players. 2017-12-26 22:13:28 -05:00
Thomas Harte
f9f870ad2d Merge pull request #323 from TomHarte/MSXCAS
Adds support for the MSX .CAS file format.
2017-12-23 17:00:02 -08:00
Thomas Harte
cbba6a5595 Ensures final few bytes of a CAS file aren't dropped. 2017-12-23 19:54:42 -05:00
Thomas Harte
0a079b0f94 Attempts to fix failure to distinguish end-of-file. 2017-12-23 19:32:24 -05:00
Thomas Harte
9a7e974579 Corrects skipping of every other file, and transition from bytes back into header. 2017-12-23 19:20:04 -05:00
Thomas Harte
f4d414d6e4 Removes stray line break. 2017-12-23 18:42:04 -05:00
Thomas Harte
b4bfcd4279 Switches to an attempt to break the .CAS into files ahead of time.
Hopefully the better to insert appropriate lengths of header and gap.
2017-12-23 18:41:50 -05:00
Thomas Harte
e8ddff0ee0 Makes a first, messy, attempt at serialising CAS files into audio. 2017-12-21 22:34:03 -05:00
Thomas Harte
b61fab9df7 Merge pull request #322 from TomHarte/MSXTapes
Introduces TSX support for the MSX.
2017-12-20 18:43:54 -08:00
Thomas Harte
28fb1ce2ae Removes unnecessary logging. 2017-12-20 21:39:17 -05:00
Thomas Harte
b9b107ee85 Switches KeyGrave and KeyQuote, correcting a disarrangement. 2017-12-20 21:16:54 -05:00
Thomas Harte
f17758e7f9 Attempts better to deal with large numbers. 2017-12-20 21:03:24 -05:00
Thomas Harte
0bb24075b6 Immediate fixes: TSX is seemingly TZX 1.21; the tape motor control works the other way around.
Input is not yet being recognised.
2017-12-19 22:17:42 -05:00
Thomas Harte
db6d9b59d0 Attempts to implement TSX support for the MSX. 2017-12-19 21:53:04 -05:00
Thomas Harte
51e82c10c5 Merge pull request #321 from TomHarte/MSXKeyTaps
Introduces the MSX keyboard toggle sample source.
2017-12-19 18:19:42 -08:00
Thomas Harte
2d892da225 Introduces the MSX keyboard toggle sample source.
In support of which, it also introduces a means of sample source composition.
2017-12-19 21:08:10 -05:00
Thomas Harte
b99ba2bc02 Merge pull request #320 from TomHarte/AudioRejig
Separates the audio pipeline into its component parts
2017-12-18 18:50:36 -08:00
Thomas Harte
d36e9d0b0d Reintroduces cstring.h to a few files that previously got it implicitly. 2017-12-18 21:47:30 -05:00
Thomas Harte
2dc1d4443e Separates LowpassFilter and SampleSource. 2017-12-18 21:39:23 -05:00
Thomas Harte
f8a2459c91 Corrects two lingering adaptation errors in the Vic-20. 2017-12-17 21:43:08 -05:00
Thomas Harte
ac80d10cd8 Separates the component parts of running an audio stream: task deferral, filtering and generation.
Walking towards improving opportunities for composition.
2017-12-17 21:26:06 -05:00
Thomas Harte
eb6b612052 Adds DeferringAsyncTaskQueue as a base concurrency primitive. 2017-12-15 22:14:09 -05:00
Thomas Harte
d66a33f249 Merge pull request #319 from TomHarte/TMSTests
Corrects a couple of lingering TMS issues and tidies it up
2017-12-14 18:20:13 -08:00
Thomas Harte
ec4c259695 Removes unused file. 2017-12-14 21:19:09 -05:00
Thomas Harte
ad50b6b1fb Corrects TMS' get_time_until_interrupt when the next interrupt is exactly a frame away. 2017-12-14 21:12:51 -05:00
Thomas Harte
3da323c657 Corrects lingering free TMS read. 2017-12-14 20:30:56 -05:00
Thomas Harte
aca7842ca4 Better documents and tidies the TMS9918. 2017-12-14 20:27:26 -05:00
Thomas Harte
38c912b968 Merge pull request #318 from TomHarte/TMSVRAMTiming
Attempts real VRAM access timings for the TMS9918a
2017-12-13 19:56:56 -08:00
Thomas Harte
7a52e7d6d2 Provides an empty value for the interrupt cycle. 2017-12-13 22:44:03 -05:00
Thomas Harte
c36de4f640 Attempts real VRAM access timings, correcting a frame timing error as I go. 2017-12-13 22:37:27 -05:00
Thomas Harte
504772bcda Merge pull request #317 from TomHarte/SpriteGlitching
Corrects occasional TMS sprite glitching.
2017-12-12 19:20:27 -08:00
Thomas Harte
5d0c33d545 Corrects occasional TMS sprite glitching. 2017-12-12 22:19:33 -05:00
Thomas Harte
7bc1bcd493 Merge pull request #316 from TomHarte/SpriteTopLine
Adds one-before-the-graphics as a line for TMS video collection.
2017-12-12 18:36:03 -08:00
Thomas Harte
b0616ee10c Adds one-before-the-graphics as a line for video collection.
Thereby corrects sprites on line 0.
2017-12-12 21:35:33 -05:00
Thomas Harte
da57df55e8 Merge pull request #315 from TomHarte/MSX
Introduces very provisional MSX 1 emulation
2017-12-12 18:30:09 -08:00
Thomas Harte
4daea1121b Gives up on C-BIOS for a while, to get to an acceptable merge point. 2017-12-12 21:19:33 -05:00
Thomas Harte
afcdd64d5e Switches to a less easy-to-confuse storage arrangement for MSX memory slots. 2017-12-11 21:09:53 -05:00
Thomas Harte
798cdba979 8255: update_outputs now affects only those ports designated as outputs. 2017-12-10 17:55:37 -05:00
Thomas Harte
f957344ac4 Corrects TMS failure to show background through tile layer. 2017-12-09 23:15:04 -05:00
Thomas Harte
b3fbd0f352 Tidies up some of the TMS' magic constants. 2017-12-09 23:08:07 -05:00
Thomas Harte
042edc72f7 Adjusts TMS declared timing so as to be in-phase with an NTSC clock, and adopts an alternative palette. 2017-12-09 22:28:34 -05:00
Thomas Harte
943418c434 Reformulates TMS sprite plotting to set the collision flag and to support magnified sprites. 2017-12-09 20:30:12 -05:00
Thomas Harte
7d7e2538bd Introduces a computationally simplified inner loop for TMS graphics modes, modelled on that for text. 2017-12-09 16:02:33 -05:00
Thomas Harte
7a544731e2 Makes minor tidiness improvements to the TMS. 2017-12-08 22:20:21 -05:00
Thomas Harte
e1914b4f16 Attempts to add a proper intermediate buffer for sprites to allow the split of collection and output. 2017-12-08 22:12:39 -05:00
Thomas Harte
202958303e Merge branch 'MSX' of github.com:TomHarte/CLK into MSX 2017-12-06 21:58:29 -05:00
Thomas Harte
57b060ac3c Updates SConstruct for the incoming MSX changes. 2017-12-06 18:56:26 -08:00
Thomas Harte
8653eb8b55 Corrects various latent errors in optimised TMS video collection. 2017-12-06 20:24:29 -05:00
Thomas Harte
a4f0a260fd Reformulates the TMS graphics mode fetch loop to try to eliminate heavy conditionality. Temporarily introduces some sprite selection issues. 2017-12-05 22:39:03 -05:00
Thomas Harte
d4a53e82bb Replaces manual retread of memcpy with standard memcpy. 2017-12-05 18:21:34 -05:00
Thomas Harte
6eedc99286 Makes substantial optimisations to text mode.
Character optimisations to come.
2017-12-04 22:18:51 -05:00
Thomas Harte
ec266d6c8e Ensures the AY stops listening to the bus after each read or write. 2017-12-04 19:18:54 -05:00
Thomas Harte
e3a5218e78 Fixes AY and random port input for the MSX. 2017-12-03 22:25:18 -05:00
Thomas Harte
a473338abe Makes minor type conversion fixes. 2017-12-03 22:24:48 -05:00
Thomas Harte
ae21782adc Corrects two Cartridge type mismatches. 2017-12-03 15:43:59 -05:00
Thomas Harte
ee44d671e7 Steps towards exposing the MSX in Cocoa builds. 2017-12-03 15:42:54 -05:00
Thomas Harte
3766bef962 Eliminates some redundant white space. 2017-12-03 14:52:42 -05:00
Thomas Harte
ad3df36c20 Corrects sprite information collection to cover all four. 2017-12-03 14:51:55 -05:00
Thomas Harte
38b11893e8 Takes first steps towards sprite display on the TMS. 2017-12-02 22:13:43 -05:00
Thomas Harte
e4534775b0 Cleans up and zooms in on the TMS slightly. 2017-12-02 17:48:31 -05:00
Thomas Harte
fe7fc6b22e Enables AY output from the MSX. 2017-12-02 16:30:43 -05:00
Thomas Harte
fe0cdc8d69 Corrects colour fetching in TMS Graphics II to be a function of row. 2017-12-02 16:10:29 -05:00
Thomas Harte
7f8a13a409 Adds bare minimum to get accepted 16- and 32kb cartridges to start on the MSX. 2017-12-02 16:06:04 -05:00
Thomas Harte
ca26ce8400 Slightly corrects style errors in the Cartridge hierarchy, and introduces mapping of .ROM to the MSX when appropriate. 2017-12-02 16:01:30 -05:00
Thomas Harte
d3dd8f3f2a Implements screen 2 addressing. 2017-12-02 14:05:52 -05:00
Thomas Harte
3c8d2d579d Resolves remaining sources of text mode instability. 2017-11-30 22:48:07 -05:00
Thomas Harte
edcbb3dfed Tidies code a little and thereby uncovers and corrects one cause of output instability. 2017-11-30 22:19:53 -05:00
Thomas Harte
9c8158753e Makes a first attempt at displaying text mode. 2017-11-30 21:35:26 -05:00
Thomas Harte
5da9cb2957 Introduces most of a keyboard mapping for the MSX. 2017-11-30 19:27:53 -05:00
Thomas Harte
54c845b6e2 Adds just enough logic to make every host key look like '0' to the MSX. 2017-11-29 22:07:30 -05:00
Thomas Harte
ee84f33ab5 Ensures that the 9918 admits that it is the source of interrupts. 2017-11-29 21:33:43 -05:00
Thomas Harte
f0f149c018 Simplified paging logic. 2017-11-29 20:49:02 -05:00
Thomas Harte
7dfbe4bb93 Ensures proper Boolean startup values for IFF1 and IFF2. 2017-11-29 20:32:55 -05:00
Thomas Harte
aa4eef41d8 Seeks to introduce MSX interrupts. 2017-11-29 20:31:55 -05:00
Thomas Harte
69ec8a362e Makes an attempt to perform MSX memory paging. 2017-11-28 21:56:15 -05:00
Thomas Harte
ecd7d4731b Advances emulation to showing what looks like appropriate text on screen. 2017-11-28 21:27:15 -05:00
Thomas Harte
563aa051e4 Simplifies code a little and gives something on screen. 2017-11-28 21:19:28 -05:00
Thomas Harte
642bb8333f Introduces something of a first attempt at graphics collection and display. An unsuccessful attempt. 2017-11-28 21:10:30 -05:00
Thomas Harte
c558e86e03 Adds border colour output. 2017-11-27 22:05:40 -05:00
Thomas Harte
dbb14ea2e2 Corrects counting deficiencies that could produce an unstable display. 2017-11-27 21:36:12 -05:00
Thomas Harte
173e16b107 Corrects the 9918 so that it terminates. 2017-11-27 19:48:04 -05:00
Thomas Harte
7d2adad67e Adds the absolute most basic version of in-frame time keeping, to display a white square. 2017-11-27 19:43:33 -05:00
Thomas Harte
d33612def5 Ensures the MSX provides a clock to the VDP. 2017-11-26 20:07:30 -05:00
Thomas Harte
9cb6ca3440 Adds elementary decoding of VDP accesses. 2017-11-26 20:01:11 -05:00
Thomas Harte
e957e40b14 Shifts 8255 logging up into its own port handler. That's probably fine for now. 2017-11-26 18:59:29 -05:00
Thomas Harte
7a8a43a96a Adds just enough of the MSX memory map for the Z80 to appear to try to do useful things. 2017-11-26 18:34:40 -05:00
Thomas Harte
0eb5dd9688 Introduces the fundamentals of bus routing for the MSX. 2017-11-26 16:47:59 -05:00
Thomas Harte
a14b53a9ab Adds a TMS9918 skeleton plus enough in the MSX to get to a blank screen in SDL/kiosk mode. 2017-11-26 13:28:26 -05:00
Thomas Harte
576d554a2c Expands upon the MSX skeleton. 2017-11-25 13:33:51 -05:00
Thomas Harte
68a2895753 Adds enough static analyser to get to the MSX itself as the point of failure in SDL/kiosk mode. 2017-11-25 13:18:24 -05:00
Thomas Harte
f90b3f06aa Merge branch 'master' into MSX 2017-11-25 08:19:24 -05:00
Thomas Harte
f067fa9923 Merge pull request #310 from TomHarte/ROMSafety
Simplifies CPC ROM input mechanism.
2017-11-25 05:19:00 -08:00
Thomas Harte
ee9f89ccb5 Simplifies CPC ROM input mechanism. 2017-11-25 08:18:01 -05:00
Thomas Harte
573a9c6fb2 Merge pull request #309 from TomHarte/ROMSafety
Ensures all vectors loaded from disk are the expected size.
2017-11-25 05:17:23 -08:00
Thomas Harte
a46a37fba9 Ensures all vectors loaded from disk are the expected size. 2017-11-24 22:22:32 -05:00
Thomas Harte
324b57c054 Adds inclusion of the 3/4 of the MSX's support chips that are currently implemented. 2017-11-24 22:05:50 -05:00
Thomas Harte
ae50ca9ab2 Moves the MSX class to the appropriate place and gives it a Z80. 2017-11-24 21:59:54 -05:00
Thomas Harte
6e4bde00d3 Merge branch 'master' into MSX 2017-11-24 21:50:38 -05:00
Thomas Harte
d4d0dd87c9 Merge pull request #307 from TomHarte/MacDynamic
Adapts the Mac port to use a Machine::DynamicMachine.
2017-11-24 18:43:27 -08:00
Thomas Harte
221c05ca76 Adapts the Mac port to use a Machine::DynamicMachine, thereby eliminating plenty of duplication. 2017-11-24 21:36:22 -05:00
Thomas Harte
ff21ff90eb Introduces MSX ROMs and an MSX class. 2017-11-24 20:43:26 -05:00
Thomas Harte
fcf295fd68 Merge pull request #306 from TomHarte/ShaderUniforms
Formalises naming of shader inputs and related guarantees.
2017-11-24 16:28:30 -08:00
Thomas Harte
2008dec1ed Adds exceptions for bad enumeration values. 2017-11-24 19:27:49 -05:00
Thomas Harte
b4f3c41aae Formalises naming of shader inputs and related guarantees. 2017-11-24 18:45:24 -05:00
Thomas Harte
90c4e3726f Merge pull request #305 from TomHarte/MacCleanup
Withdraws genericised selection and ROM provision interfaces.
2017-11-24 14:58:49 -08:00
Thomas Harte
c83b3cefbc Eliminates the generalised special case selectors and ROM suppliers from the CPC, Vic-20, Electron and ZX80/81. 2017-11-24 17:55:28 -05:00
Thomas Harte
a8ac51da73 Eliminates the Oric's non-reflective inputs for selections, and the Oric-specific ROM setter. 2017-11-24 16:59:00 -05:00
Thomas Harte
bc65ba3f9b Merge pull request #303 from mattgodbolt/fixes-for-uninitialized-errors
Initialize all `const` members.
2017-11-24 12:19:55 -08:00
Thomas Harte
79674fdbd3 Merge pull request #304 from mattgodbolt/gitignore
Add a .gitignore file to ignore the built `clksignal` binary
2017-11-24 12:19:25 -08:00
Matt Godbolt
adea4711f1 Add a .gitignore file to ignore the built clksignal binary 2017-11-24 12:12:48 -06:00
Matt Godbolt
bded406caa Initialize all const members.
Without this change, GCC versions >4.8 will error with things like:

```
./CLK/Outputs/CRT/Internals/CRTOpenGL.cpp:154:2:error: uninitialized const member
'Outputs::CRT::OpenGLOutputBuilder::draw_frame(unsigned int, unsigned int, bool)::RenderStage::target'
```
2017-11-24 12:09:10 -06:00
Thomas Harte
85085a6375 Merge pull request #302 from TomHarte/OricStartup
Ensures Oric video output starts up and changes validly.
2017-11-23 13:23:31 -08:00
Thomas Harte
d122d598d3 Merge branch 'OricStartup' of github.com:TomHarte/CLK into OricStartup 2017-11-23 16:20:19 -05:00
Thomas Harte
d6192b8c58 Ensures Oric video output starts up and changes validly. 2017-11-23 16:19:41 -05:00
Thomas Harte
f02d4dbb59 Ensures Oric video output starts up and changes validly. 2017-11-23 16:17:52 -05:00
Thomas Harte
f3818991f6 Merge pull request #301 from TomHarte/ElectronMode3
Corrects Electron Mode 3 timing.
2017-11-23 13:07:15 -08:00
Thomas Harte
c7dd6247f0 Corrects Electron Mode 3 timing. 2017-11-23 16:06:05 -05:00
Thomas Harte
99e17600d7 Updated as per slow appropriate of the full 'Clock Signal'. 2017-11-22 20:44:06 -05:00
Thomas Harte
1d821ad459 Corrected name of build tool. 2017-11-22 20:11:02 -05:00
Thomas Harte
c60a9ee3c3 Merge pull request #298 from TomHarte/ReadMeUpdates
Provided exposition of new platform support.
2017-11-22 17:08:58 -08:00
Thomas Harte
ffcbd1e94d Provided exposition of new platform support. 2017-11-22 20:08:07 -05:00
Thomas Harte
6c8b503402 Merge pull request #297 from TomHarte/Instructions
Adds build instructions and references the special SDL key combinations.
2017-11-22 17:04:01 -08:00
Thomas Harte
55e1d25966 Adds build instructions and references the special SDL key combinations. 2017-11-22 20:03:28 -05:00
Thomas Harte
0bdd776114 Merge pull request #296 from TomHarte/SDLAudioRejig
Switches to using the supply-on-demand audio route through SDL.
2017-11-22 16:45:01 -08:00
Thomas Harte
c1b7bceec8 Switches to using the supply-on-demand audio route through SDL.
This gives an additional hook from which machine updates can be hooked, so separates that buffer size from any implicit frame rate assumptions.
2017-11-22 19:36:39 -05:00
Thomas Harte
dc4f58e40c Hides the mouse cursor when in SDL fullscreen mode. 2017-11-21 21:52:32 -05:00
Thomas Harte
3b8cdd620c Merge pull request #295 from TomHarte/SDLPaste
Adds acceptance of paste and fullscreen toggle to SDL target.
2017-11-21 18:50:17 -08:00
Thomas Harte
3365ff0200 Adds type recipient as a dynamic type, and accepts paste and fullscreen toggle in SDL. 2017-11-21 21:44:29 -05:00
Thomas Harte
89c3e2ba5a Merge pull request #294 from TomHarte/Vic20Startup
Corrects application Vic-20 startup issues.
2017-11-21 18:26:16 -08:00
Thomas Harte
c6306db47c Ensures the 6560 is fully initialised by setup_output. 2017-11-21 21:24:06 -05:00
Thomas Harte
8ddc64c82a Ensures well-defined default speaker clock rate values. 2017-11-21 21:18:58 -05:00
Thomas Harte
b887cb7255 Merge pull request #293 from TomHarte/ROMExposition
Adds user-facing information about which ROMs a machine attempted to load if it fails.
2017-11-21 16:24:23 -08:00
Thomas Harte
d54ee2af82 Adds user-facing information about which ROMs a machine attempted to load if it fails. 2017-11-21 19:22:33 -05:00
Thomas Harte
723c113186 Merge pull request #292 from TomHarte/Help
Introduces command-line help and reduces code duplicity in those options.
2017-11-20 19:01:06 -08:00
Thomas Harte
c368c4443e Improves both internal and external exposition for the SDL version. 2017-11-20 21:59:53 -05:00
Thomas Harte
7b25b03cd5 Formally standardises machine options and introduces a --help option for the SDL target. 2017-11-20 21:55:32 -05:00
Thomas Harte
9961d13e2d Merge pull request #290 from TomHarte/DragAndDrop
Adds drag and drop receivership to the SDL target.
2017-11-19 15:21:25 -08:00
Thomas Harte
29b5ccc767 Removes redundant logging on the Mac. 2017-11-19 18:05:39 -05:00
Thomas Harte
90af395df2 Adds support for receiving dragged and dropped files under SDL. 2017-11-19 18:05:31 -05:00
Thomas Harte
6f8d4d6c5c Merge pull request #282 from TomHarte/BooleanSelections
Boolean selections
2017-11-18 18:16:33 -08:00
Thomas Harte
63381ff505 Fixes accidental typographic quote in SConstruct. 2017-11-18 21:13:55 -05:00
Thomas Harte
2ea050556b Adds transcoding of ostensible list selections to Boolean selections, and vice versa. 2017-11-18 21:09:26 -05:00
Thomas Harte
8dcac6561e Merge pull request #281 from TomHarte/MachineOptions
Introduces reflective machine options and a command-line parser for them.
2017-11-18 17:03:53 -08:00
Thomas Harte
90d33949f9 Adds a mapping of backspace for the Electron. 2017-11-18 20:02:04 -05:00
Thomas Harte
d3e68914dd Removes uninteresting logging. 2017-11-18 20:00:40 -05:00
Thomas Harte
82ad0354c4 Adds configuration options to the Vic-20, Oric and ZX80/81. 2017-11-18 19:48:10 -05:00
Thomas Harte
073e439518 Adds a basic argument parser, allowing machine options to be set. 2017-11-18 19:34:38 -05:00
Thomas Harte
27b123549b Adds missing #include. 2017-11-17 23:15:37 -05:00
Thomas Harte
de9db724a7 Introduces Configurable::Device and implements it for the Electron.
Configurable::Device covers devices that have user-facing configuration options, listing them and accepting them.
2017-11-17 23:02:00 -05:00
Thomas Harte
532ea35ee9 Merge pull request #280 from TomHarte/AttributeBindings
Corrects intermediate shader attribute bindings.
2017-11-16 17:20:23 -08:00
Thomas Harte
e9ddec35d6 Corrects intermediate shader attribute bindings. 2017-11-16 20:19:54 -05:00
Thomas Harte
7647f8089b Merge pull request #279 from TomHarte/StringStream
Substitutes std::osringstream for C-esque `asprintf`.
2017-11-15 18:49:16 -08:00
Thomas Harte
f00f0353a6 Removes unnecessary temporaries. 2017-11-15 21:48:10 -05:00
Thomas Harte
e19ae5d43d Merge branch 'StringStream' of github.com:TomHarte/CLK into StringStream 2017-11-15 21:30:16 -05:00
Thomas Harte
0a9622435c Merge branch 'StringStream' of github.com:TomHarte/CLK into StringStream 2017-11-15 21:30:04 -05:00
Thomas Harte
f704932475 Merge branch 'StringStream' of github.com:TomHarte/CLK into StringStream 2017-11-15 21:29:22 -05:00
Thomas Harte
d0f096a20b Substitutes std::osringstream for C-esque asprintf. 2017-11-15 21:28:48 -05:00
Thomas Harte
949d0f3928 Substitutes std::osringstream for C-esque asprintf. 2017-11-15 21:25:01 -05:00
Thomas Harte
a2d48223c3 Merge pull request #278 from TomHarte/OpenGL32
Adds an explicit request for OpenGL 3.2 under SDL.
2017-11-14 16:02:33 -08:00
Thomas Harte
fc080c773f Adds an explicit request for OpenGL 3.2. 2017-11-14 18:59:18 -05:00
Thomas Harte
adb3811847 Ensures deterministic initial state for the atomic flag. 2017-11-13 22:51:42 -05:00
Thomas Harte
dbbea78b76 Merge branch 'master' of github.com:TomHarte/CLK 2017-11-13 22:40:05 -05:00
Thomas Harte
fd96e3e657 Eliminates all unused #ifdef GL_NV_texture_barrier code. 2017-11-13 22:39:18 -05:00
Thomas Harte
06d81b3a97 Eliminates all unused #ifdef GL_NV_texture_barrier code. 2017-11-13 22:38:33 -05:00
Thomas Harte
88551607a6 Ensures the GL error flag is cleared after a potential error-raising call. 2017-11-13 22:31:41 -05:00
Thomas Harte
2a9dccff26 Fixes typo. 2017-11-13 22:28:11 -05:00
Thomas Harte
1027f85683 Merge pull request #277 from TomHarte/MappingFallback
Adds a fallback route for the array builder if it can't map a buffer.
2017-11-13 19:27:48 -08:00
Thomas Harte
9bb9cb4a65 Adds a fallback route for the array builder if it can't map a buffer. 2017-11-13 22:27:04 -05:00
Thomas Harte
2de80646ec Merge pull request #276 from TomHarte/SafeTextureTarget
Updates style of OpenGL::TextureTarget for instance variable names and RAII.
2017-11-13 19:13:32 -08:00
Thomas Harte
bf4ed57f68 Updates style of OpenGL::TextureTarget for instance variable names and preference for RAII. 2017-11-13 22:04:13 -05:00
Thomas Harte
9578f3dc44 Merge pull request #275 from TomHarte/SDLLogging
Adds some very basic logging to the SDL target.
2017-11-12 21:24:39 -05:00
Thomas Harte
a97c478a34 Adds some very basic logging to the SDL target. 2017-11-12 21:23:48 -05:00
Thomas Harte
e0113d5dce Merge pull request #274 from TomHarte/TargetFramebuffer
Attempts more cleanly to deal with window resizing in SDL.
2017-11-12 19:48:23 -05:00
Thomas Harte
980cf541d2 Attempts more cleanly to deal with window resizing in SDL. 2017-11-12 19:47:18 -05:00
Thomas Harte
69c983f9ee Merge pull request #273 from TomHarte/TargetFramebuffer
Allows a CRT machine owner to set the target frame buffer for OpenGL output.
2017-11-12 19:30:06 -05:00
Thomas Harte
70039d22f1 Allows a CRT machine owner to set the target frame buffer for OpenGL output, breaking the assumption that it'll be zero. 2017-11-12 19:29:22 -05:00
Thomas Harte
ebdb80c908 Merge pull request #272 from TomHarte/UnusedResults
Resolves all GCC warnings
2017-11-12 17:55:23 -05:00
Thomas Harte
0eaac99d74 Avoids implicit signed/unsigned comparison in the G64 reader. 2017-11-12 17:48:11 -05:00
Thomas Harte
792061a82b Corrects warnings in the CSW, CPC DSK, ZX8081 data encoding, and PRG and binary cartridges. 2017-11-12 17:46:06 -05:00
Thomas Harte
d2ba7d7430 Corrects GCC warnings in Commodore::File and the FileHolder. 2017-11-12 17:38:21 -05:00
Thomas Harte
8713cfa613 Ensured all asprintf return values are checked. 2017-11-12 17:29:20 -05:00
Thomas Harte
aa77be1c10 Introduces missing include. 2017-11-12 17:20:37 -05:00
Thomas Harte
e6aa2321cd Merge branch 'UnusedResults' of github.com:TomHarte/CLK into UnusedResults 2017-11-12 17:17:49 -05:00
Thomas Harte
c827d14d97 Corrects various GCC warnings across the 6560, CPC, TIA, Oric video and elsewhere. 2017-11-12 17:17:27 -05:00
Thomas Harte
2979d19621 Enables all warnings for the SDL build. 2017-11-12 16:46:10 -05:00
Thomas Harte
282e5c9d3e For GCC's benefit, added impossible default options. 2017-11-12 16:45:31 -05:00
Thomas Harte
ede47d4ba7 Improves type safety within CSW file support. 2017-11-12 16:42:53 -05:00
Thomas Harte
5408efe9b5 Flags obvious default options within the 6560, Vic-20 and DynamicMachine. 2017-11-12 16:41:09 -05:00
Thomas Harte
d6141cb020 Increases number of warnings in Xcode. 2017-11-12 16:37:39 -05:00
Thomas Harte
198d0fd1de Makes it obvious to GCC that a return result is always supplied. 2017-11-12 16:37:18 -05:00
Thomas Harte
6d80856f02 Attempts to eliminate warnings around a meaningless value and an unused label in the 8272. 2017-11-12 16:34:51 -05:00
Thomas Harte
4778616fd7 Eliminates unused result and unused label. 2017-11-12 16:30:23 -05:00
Thomas Harte
2e025d85eb Added check in SDL main that the expected number of bytes is read. 2017-11-12 16:26:42 -05:00
Thomas Harte
61f2191c86 Merge branch 'PragmaMark' 2017-11-12 16:11:36 -05:00
Thomas Harte
c1eab8d5f3 Corrects a pragma mark that escaped detection through typo. 2017-11-12 16:11:24 -05:00
Thomas Harte
91d2d59ae5 Merge pull request #271 from TomHarte/PragmaMark
Commutes cross-platform `#pragma mark`s to `//MARK:`s.
2017-11-12 16:02:18 -05:00
Thomas Harte
5aef81cf24 Commutes cross-platform #pragma marks to //MARK:s. 2017-11-12 15:59:11 -05:00
Thomas Harte
3550196bed Merge pull request #270 from TomHarte/TrackCloning
Corrects `insert` explicitly to supply a `shared_ptr` rather than a raw one.
2017-11-11 18:23:49 -05:00
Thomas Harte
bce58683fa Corrects insert explicitly to supply a shared_ptr rather than a raw one. 2017-11-11 18:22:41 -05:00
Thomas Harte
c91a5875b2 Merge pull request #269 from TomHarte/StdNamespace
Starts doubling down on <cX> over <X.h> for C includes, plus appropriate namespace usage.
2017-11-11 15:32:50 -05:00
Thomas Harte
2e15fab651 Doubles down on <cX> over <X.h> for C includes, and usage of the namespace for those types and functions. 2017-11-11 15:28:40 -05:00
Thomas Harte
6a176082a0 Switches a couple of overlooked C-style casts to functional style. 2017-11-11 12:41:49 -05:00
Thomas Harte
fd346bac3e Merge pull request #267 from TomHarte/AudioCleanup
Resolves dangling C-isms in my FIR filter, and introduces composition.
2017-11-11 12:38:45 -05:00
Thomas Harte
25e9dcc800 Merge pull request #268 from TomHarte/SerialPortVIAInitialisation
Resolvws out-of-order initialisation within the C1540.
2017-11-11 12:37:48 -05:00
Thomas Harte
792cbb1536 Resolvws out-of-order initialisation within the C1540. 2017-11-11 12:35:51 -05:00
Thomas Harte
2e12370251 Resolves some of the dangling C-isms remaining in my FIR filter, and introduces filter composition. 2017-11-11 12:30:45 -05:00
Thomas Harte
7adc25694a Merge pull request #266 from TomHarte/SDLScons
Introduces an SCons build file and corrects remaining Ubuntu build errors
2017-11-10 23:43:44 -05:00
Thomas Harte
ca80da7fbe Merge branch 'SDLScons' of github.com:TomHarte/CLK into SDLScons 2017-11-10 23:17:05 -05:00
Thomas Harte
f853d87884 Switches SConstruct build file to producing an optimised result. 2017-11-10 23:16:05 -05:00
Thomas Harte
524087805f Switches SConstruct build file to producing an optimised result. 2017-11-10 23:11:40 -05:00
Thomas Harte
916eb96b47 Makes buffer size restriction explicit in the Vic-20. 2017-11-10 22:59:11 -05:00
Thomas Harte
4add2c1051 Corrects order-of-initialisation errors in the TIA. 2017-11-10 22:57:43 -05:00
Thomas Harte
cb0f58ab7a Corrects order-of-initialisation errors in the CPC (again), TextureBuilder, TextureTarget, Z80, MFM parser and binary tape player. 2017-11-10 22:57:03 -05:00
Thomas Harte
d9e56711ce Corrects order-of-initialisation errors in the Amstrad CPC, Vic-20, Oric, Commodore File, MFM disk controller, UEF and Commodore tape parser. 2017-11-10 22:47:10 -05:00
Thomas Harte
d60692b6fd Corrects order of initialisation for the Typer and Oric video. 2017-11-10 22:35:05 -05:00
Thomas Harte
5b6ea35d96 Corrects initialisation ordering for the ZX80/81, C1540 and AY-3-8910. 2017-11-10 22:31:27 -05:00
Thomas Harte
4cbc87a17d Corrects out-of-order initialisations for the 1770, Atari 2600 joystick, Pitfall II bus extender, Microdisc and 6502. 2017-11-10 22:20:44 -05:00
Thomas Harte
46e7c199b2 Corrects improper initialisation order of the Commodore .tap and CRTMachine::Machine. 2017-11-10 22:08:40 -05:00
Thomas Harte
ff7ba526fb Corrects improper initialisation order on the 6560. 2017-11-10 22:05:35 -05:00
Thomas Harte
a825da3715 Reinstates missing include file. 2017-11-10 22:02:02 -05:00
Thomas Harte
fabaf4e607 Adds missing include files, corrects bad include paths and eliminates the Clang-specific __undefined. 2017-11-10 21:56:53 -05:00
Thomas Harte
153067c018 Adds missing files to SConstruct. 2017-11-10 21:56:15 -05:00
Thomas Harte
f7f2736d4d Corrects missing includes in the SerialBus, Electron Video and Typer. 2017-11-10 20:37:18 -05:00
Thomas Harte
a16ca65825 Adds object files and SConstruct intermediaries to .gitignore. 2017-11-10 20:36:47 -05:00
Thomas Harte
cb015c83e1 Eliminated C99-style struct initialisations. 2017-11-10 19:14:19 -05:00
Thomas Harte
2203499215 Enables -Wreorder and corrects a few of the more trivial fixes thereby suggested. 2017-11-09 22:14:22 -05:00
Thomas Harte
c0055a5a5f Further builds up SConstruct, correcting many missed imports and a couple of improper uses of C99 in C++ code. 2017-11-09 22:04:49 -05:00
Thomas Harte
62218e81bf Fixes the FIR filter again from the Apple side. 2017-11-08 22:48:44 -05:00
Thomas Harte
c45d4831ec Introduces an SConstruct file and corrects those errors and warnings that arise in Ubuntu. 2017-11-08 22:36:41 -05:00
Thomas Harte
9fd33bdfde Merge pull request #265 from TomHarte/Whitespace
Eliminates a large number of instance of end-of-line white space.
2017-11-07 22:54:46 -05:00
Thomas Harte
6e1d69581c Eliminates a variety of end-of-line spaces. 2017-11-07 22:54:22 -05:00
Thomas Harte
f95515ae81 Eliminates a large number of instance of end-of-line tabs. 2017-11-07 22:51:06 -05:00
Thomas Harte
09c855a659 Merge pull request #264 from TomHarte/SDLKiosk
SDL kiosk
2017-11-07 22:44:48 -05:00
Thomas Harte
16c96b605a Xcode 9.1 auto-change. 2017-11-07 22:43:25 -05:00
Thomas Harte
e10d369e53 Ensures that execution doesn't proceed if ROMs are missing. 2017-11-07 22:32:59 -05:00
Thomas Harte
0d1b63a8c5 Switches the Objective-C machine bindings to use the set_rom_fetcher path for supplying ROMs, simplifying and unifying. 2017-11-07 22:29:57 -05:00
Thomas Harte
ddcdd07dd0 Modifies the Vic-20 and C1540 to bring them into the realm of self-ROM fetching.
Hence enables Vic-20 support within kiosk mode as currently drafted.
2017-11-07 21:19:51 -05:00
Thomas Harte
35da3edf60 Implements install_roms on the Electron, Oric and ZX80/81. 2017-11-06 22:14:15 -05:00
Thomas Harte
d605022ea3 Moves output setup to after the machine has been configured as its target. 2017-11-06 22:13:38 -05:00
Thomas Harte
0da78065ce Eliminates some dangling cases of undefined initial state in the TIA. 2017-11-06 22:12:39 -05:00
Thomas Harte
4b68c372c6 Adds a first attempt at audio via SDL. 2017-11-05 22:29:25 -05:00
Thomas Harte
13406fedd8 Explains commenting. 2017-11-05 21:29:20 -05:00
Thomas Harte
a209ae76ca Adds keyboard input from SDL. 2017-11-05 21:16:14 -05:00
Thomas Harte
0116d7f071 Added a platform-neutral route for feeding ROMs to machines, in a platform-dependant fashion; implemented for the CPC. 2017-11-05 20:12:01 -05:00
Thomas Harte
512e877d06 Ensures proper initialisation of the delegate pointer. 2017-11-05 20:11:18 -05:00
Thomas Harte
1e1efcdcb8 Pushes far enough along the path of having the SDL version do work that it becomes obvious I've never figured out the correct course of action if there is no sound output. 2017-11-05 12:49:28 -05:00
Thomas Harte
bc2f58e9de Starts the process of adding an SDL-based 'kiosk' (i.e. TV UI, or even UI-less for now) mode.
Specifically, introduces to the Mac side of things an SDL target with, so far, enough logic to create a window and pump SDL's events, after having decided which machine and configuration it should use.
2017-11-04 19:36:46 -04:00
Thomas Harte
fd10c42433 Merge pull request #263 from TomHarte/WriteableDSK
Makes CPC-style .DSK files writeable
2017-11-03 21:59:06 -04:00
Thomas Harte
794437f20f Corrects fixed buffer size error in FileHolder::check_signature. 2017-11-03 21:43:31 -04:00
Thomas Harte
23d5849cda Attempts to map recognised [M]FM errors back to FDC status codes. 2017-11-03 21:29:42 -04:00
Thomas Harte
5070a8414f Improves FileHolder documentation 2017-11-03 21:29:15 -04:00
Thomas Harte
5a3ca0e447 Adds output for modified CPC DSKs. 2017-11-03 21:10:22 -04:00
Thomas Harte
e384c50580 Switches FileHolder to have a usage much closer to FILE *.
Thereby opens a route for file format implementations such as that appearing for CPC DSK that create an in-memory copy and perform a full rewrite.
2017-11-02 22:32:00 -04:00
Thomas Harte
b9734278f6 Provides an up-front evaluation of performance versus objectices via README.MD. 2017-11-02 12:22:27 -04:00
Thomas Harte
f807a6b608 Generalises the concept of multiple samplings of an FM/MFM sector, simplifying CPC DSK support and paving the way for generic weak/fuzzy bit support. 2017-10-31 21:32:28 -04:00
Thomas Harte
833f8c02a4 Switches the CPC DSK implementation to building an in-memory version of the structure up front.
Preparatory to making these things writeable.
2017-10-31 19:41:16 -04:00
Thomas Harte
0248c6a282 Merge pull request #262 from TomHarte/BookEnds
Adds a facility for 'bookending' data runs, eliminating an occasional Electron rendering error
2017-10-23 18:36:54 -04:00
Thomas Harte
218b976dbc Adds through route for setting a texture bookender, and exploits it from the Electron. 2017-10-23 18:35:37 -04:00
Thomas Harte
513903890e Corrects definition of Bookender and provides the default implementation. 2017-10-22 17:24:41 -04:00
Thomas Harte
1157bde453 Sketches interface for a GPU data bookender, to avoid stray errors with packed pixel formats. 2017-10-22 10:48:10 -04:00
Thomas Harte
46345c6a3e Merge pull request #261 from TomHarte/UIntCasts
Continues the process of conversion to functional casts.
2017-10-21 22:53:14 -04:00
Thomas Harte
c13f8e5390 Corrects a couple of cast conversion errors. 2017-10-21 22:42:19 -04:00
Thomas Harte
ad9df4bb90 Commutes uint8_t *, uint16_t *, uint32_t *, size_t, off_t and long to functional-style casts. 2017-10-21 22:30:15 -04:00
Thomas Harte
e983854e71 Converts all uint8_t and uint16_t casts to the functional style. 2017-10-21 21:50:53 -04:00
Thomas Harte
ec999446e8 Commutes int and unsigned casts to the functional style. 2017-10-21 21:00:40 -04:00
Thomas Harte
5e3e91373a Switches all unsigned int and double casts to functional style. 2017-10-21 19:49:04 -04:00
Thomas Harte
c52348d8d7 Merge pull request #260 from TomHarte/KeyboardCleanup
Cleans up after keyboard formalisation.
2017-10-21 10:53:46 -04:00
Thomas Harte
9e0907ee76 Completes clean-up of post-formalisation per-machine keyboard code.
At least for now. Standardising on how column + row is encoded might be helpful.
2017-10-21 10:52:35 -04:00
Thomas Harte
9ad4025138 Relocates things that were in Machines/ for machine usage.
Leaving only those things intended to be visible interface.
2017-10-21 10:30:02 -04:00
Thomas Harte
405f58d6a3 Corrects write guard names. 2017-10-21 10:21:40 -04:00
Thomas Harte
afbd1c425c Merge pull request #259 from TomHarte/Vic20Keyboard
Consolidates Vic-20 keyboard code.
2017-10-19 22:28:11 -04:00
Thomas Harte
b2c1b83fcd Consolidates Vic-20 keyboard code. 2017-10-19 22:27:30 -04:00
Thomas Harte
8d2b9a581a Merge pull request #256 from TomHarte/UniversalInput
Standardises the host-side interface for joystick and keyboard input
2017-10-19 22:15:47 -04:00
Thomas Harte
1825af0dd3 Eliminates dead code in the Vic-20 and Inputs::Joystick. 2017-10-19 22:15:21 -04:00
Thomas Harte
c2f6799f0c Implements Vic-20 restore key. 2017-10-19 22:02:34 -04:00
Thomas Harte
b5b6219cb7 Slightly simplifies TextureBuilder arithmetic. 2017-10-19 22:02:00 -04:00
Thomas Harte
185a699279 Fixes off-by-one keyboard state accumulation error. 2017-10-19 22:01:24 -04:00
Thomas Harte
96b8f9ae9f Merge branch 'master' into UniversalInput 2017-10-17 22:54:17 -04:00
Thomas Harte
88e2350b8f Prevents undefined behaviour from the CPC's timer. 2017-10-17 22:53:52 -04:00
Thomas Harte
5c141af734 Prevents undefined behaviour from the CPC's timer. 2017-10-17 22:40:32 -04:00
Thomas Harte
da580e4186 Merge branch 'master' into UniversalInput 2017-10-17 22:36:22 -04:00
Thomas Harte
57ee09dffb Merge pull request #258 from TomHarte/UndefinedBehaviour
Corrects large swathes of undefined behaviour
2017-10-17 22:35:59 -04:00
Thomas Harte
7c8e830b90 Adjusted the Acorn tape parser to avoid signed left shifts. 2017-10-17 22:34:49 -04:00
Thomas Harte
ba5f668338 Ensured full CRT instance initialisation. 2017-10-17 22:34:10 -04:00
Thomas Harte
2c1e99858b Fixed HalfCycles to allow conversion from Cycles without relying on undefined behaviour.
Specifically: left shifting a negative number.
2017-10-17 22:22:51 -04:00
Thomas Harte
7f2febeec9 Ensures complete DPLL initial state assignment. 2017-10-17 22:13:37 -04:00
Thomas Harte
2d7a4fe5f0 Switches the MFM shifter to unsigned accumulation.
Since left shifting signed numbers is undefined behaviour.
2017-10-17 22:12:04 -04:00
Thomas Harte
91b867a7b3 Ensures full 8272 instance state initialisation. 2017-10-17 22:11:01 -04:00
Thomas Harte
3944e734d3 Ensures full 6845 instance state initialisation and uses an unsigned shifter. 2017-10-17 22:10:28 -04:00
Thomas Harte
ce78d9d12c Introduces buffer alignment when writing to textures.
To avoid cross-boundary writes and hopefully to eke out a little better performance.
2017-10-17 22:09:48 -04:00
Thomas Harte
edbc60a3fb Various undefined behaviour fixes.
Primarily around uninitialised variables, but also with an attempted use of a negative pointer.
2017-10-17 21:29:19 -04:00
Thomas Harte
6ea3ff62df Merge branch 'master' into UniversalInput 2017-10-17 21:28:40 -04:00
Thomas Harte
88959571f1 Merge pull request #257 from TomHarte/CPMReading
Corrects CPM reader buffer overwrites
2017-10-17 20:54:02 -04:00
Thomas Harte
b4583e976e Corrects buffer overwrites resulting from failure to treat a number of records of 0x80 as a special case. 2017-10-17 20:52:16 -04:00
Thomas Harte
92d9805f09 Removes dead Objective-C protocol references. 2017-10-17 20:51:40 -04:00
Thomas Harte
0c2dd62328 Various undefined behaviour fixes.
Primarily around uninitialised variables, but also with an attempted use of a negative pointer.
2017-10-17 20:50:46 -04:00
Thomas Harte
3f4d90d775 Corrects buffer overwrites resulting from failure to treat a number of records of 0x80 as a special case. 2017-10-17 20:49:12 -04:00
Thomas Harte
542ec4312f Switched the Objective-C code to using dynamic_cast alone to decide whether to post keyboard or joystick events. 2017-10-15 21:25:56 -04:00
Thomas Harte
18798c9886 Corrects joystick memory leaks. 2017-10-15 20:49:47 -04:00
Thomas Harte
7aaf27389c Commutes the Atari 2600 to the JoystickMachine interface. 2017-10-15 20:44:59 -04:00
Thomas Harte
ee179aa7bd Introduces a joystick analogue to the shared keyboard interface, and implements it for the Vic-20. 2017-10-14 22:36:31 -04:00
Thomas Harte
3a05ce36de Adds a reference to the calling keyboard in reset_all_keys. 2017-10-14 22:07:11 -04:00
Thomas Harte
4f289ab10b Corrects some deficiencies in Vic-20 keyboard mapping.
... albeit without yet being clear on the wiring behind restore.
2017-10-12 22:33:00 -04:00
Thomas Harte
78ee46270b Transfers possession of keyboard mappings from the Mac side over to individual machines.
Specifically by establishing an intermediate representation of a useful mix between the American and British IBM and Mac keyboard layouts, and routing through that.
2017-10-12 22:25:02 -04:00
Thomas Harte
edb632af52 Sketches first design for generalising keyboard input. 2017-10-09 22:26:39 -04:00
Thomas Harte
19c03a08a6 Merge pull request #255 from TomHarte/BatchDriveUpdates
Rewires so as to give disk images visibility of large change sets rather than per-sector track rewrites.
2017-10-07 19:42:14 -04:00
Thomas Harte
44cdc124af Switches to providing a full record of changes to disk images, rather than feeding them a track at a time.
Gets explicit about `override`s while doing so, to ensure full adaptation.
2017-10-07 19:37:36 -04:00
Thomas Harte
b37787a414 Ensures lifetime-linked track flushing without relying on virtual calls within a destructor. 2017-10-07 19:14:18 -04:00
Thomas Harte
53b99ea248 Uses Disk::flush_tracks to elide replacement of dirty tracks. 2017-10-06 22:07:42 -04:00
Thomas Harte
97a2be71e3 Introduces flush_tracks to Drive, while switching its interface to using Track::Address and adjusting associated integer types. 2017-10-06 21:45:12 -04:00
Thomas Harte
f623bff5c3 Removes unnecessary call. 2017-10-06 18:48:51 -04:00
Thomas Harte
2511fc8401 Merge pull request #254 from TomHarte/C++BestEffortUpdater
Commutes the best-effort updater into C++11.
2017-10-05 18:28:46 -04:00
Thomas Harte
d37ec9e5b0 Attempts to ensure good behaviour if dealt an adjustable clock, and consts where possible. 2017-10-05 18:23:56 -04:00
Thomas Harte
95c82f5b36 Merge branch 'C++BestEffortUpdater' of github.com:TomHarte/CLK into C++BestEffortUpdater 2017-10-05 18:17:52 -04:00
Thomas Harte
ec202ed8be Merge branch 'master' into C++BestEffortUpdater 2017-10-05 18:17:35 -04:00
Thomas Harte
7190225603 Merge branch 'master' into C++BestEffortUpdater 2017-10-05 18:12:33 -04:00
Thomas Harte
52e7cabd4e Merge pull request #253 from TomHarte/Swift4UnitTests
Removes usages of deprecated Swift initialiser within unit tests.
2017-10-05 18:12:12 -04:00
Thomas Harte
064f1dfdbc Removes usages of deprecated initialiser. 2017-10-05 18:10:47 -04:00
Thomas Harte
f40e1fd840 Commutes the best-effort updater into C++11. 2017-10-05 18:09:58 -04:00
Thomas Harte
e194a2a015 Removes usages of deprecated initialiser. 2017-10-05 16:45:13 -04:00
Thomas Harte
c39759333a Merge pull request #252 from TomHarte/Casts
Begins this project's conversion to functional-style casts.
2017-10-03 22:05:22 -04:00
Thomas Harte
edb9fd301c Begins this project's conversion to functional-style casts. 2017-10-03 22:04:15 -04:00
Thomas Harte
ea5023ac26 Merge pull request #251 from TomHarte/HFEWriteable
Makes HFE files writeable
2017-10-03 21:32:05 -04:00
Thomas Harte
0fb363ea0e Adds writing support for HFEs. 2017-10-03 21:24:20 -04:00
Thomas Harte
1cc85615d5 Factors HFE track seeking out from the track fetching method. 2017-10-03 20:33:55 -04:00
Thomas Harte
7b01c1bee6 Revokes direct visibility of is_read_only_ to subclasses of FileHolder. 2017-10-03 19:36:06 -04:00
Thomas Harte
35705c5345 Factors out bit reversing from the HFE class. 2017-10-03 19:12:45 -04:00
Thomas Harte
f41da83d97 Seeks to eliminate race conditions on the best-effort updater. 2017-09-30 21:34:43 -04:00
Thomas Harte
cd1e5dea4d Merge pull request #250 from TomHarte/TrackToBits
Refactors MFM support, breaking it into components
2017-09-30 20:31:43 -04:00
Thomas Harte
ef605eda51 Factors out commonalities in SSD/DSD and ADF implementations. 2017-09-30 20:30:15 -04:00
Thomas Harte
2f48ee59fa Merge branch 'TrackToBits' of github.com:TomHarte/CLK into TrackToBits 2017-09-30 20:12:56 -04:00
Thomas Harte
f86729c4ac Ensures safe machine release upon window closure. 2017-09-30 20:12:46 -04:00
Thomas Harte
5f99f4442c Ensures safe machine release upon window closure. 2017-09-30 20:07:04 -04:00
Thomas Harte
326857a84d Corrects FM/MFM selection when looking for sectors. 2017-09-29 22:48:00 -04:00
Thomas Harte
5dd3945695 Factors out the more egregious similarities between ADF and SSD. 2017-09-29 22:07:23 -04:00
Thomas Harte
19eb975c73 Adds an intermediate step in CP/M directory parsing.
To reduce amount of time spent allocating and reallocating buffers.
2017-09-29 21:38:16 -04:00
Thomas Harte
698ffca51b Recasts the [M]FM parser in terms of the new factoring.
Temporarily breaks SSD writing support.
2017-09-29 20:08:36 -04:00
Thomas Harte
fe3cc5c57c Removes dead pragma. 2017-09-28 20:47:25 -04:00
Thomas Harte
f488854720 Switches Oric MFM DSK serialisation to feeding a track serialisation to a shifter.
Thereby eliminates the parser's need to offer get_track.
2017-09-27 22:14:50 -04:00
Thomas Harte
51c0c45e04 Turns MFM bit length into a globally-available constant. 2017-09-27 21:30:09 -04:00
Thomas Harte
c3e1489a8e Introduces Track::Address, a parallel to Sector::Address to enable more uniform storage. 2017-09-27 21:29:06 -04:00
Thomas Harte
e3420f62c6 Switches the Acorn ADF implementation to using the new track_serialisation/sectors_from_segment route for decomposition of a track into sectors. 2017-09-26 22:05:33 -04:00
Thomas Harte
970c80f2e3 Adds TrackSerialiser.cpp to the project and reorders section. 2017-09-26 22:03:42 -04:00
Thomas Harte
9f4a407f94 Switches the track serialiser to a more standard header + implementation separation.
Also introduces a full priming of the PLL before deserialisation begins.
2017-09-26 22:01:32 -04:00
Thomas Harte
5dda897334 Changes function name to sector_size — into line with idioms. 2017-09-26 22:00:19 -04:00
Thomas Harte
3982e375e3 Introduces a route from a PCMSegment to a list of [M]FM sectors. 2017-09-25 19:57:11 -04:00
Thomas Harte
a8524daecb Marks the move constructor as noexcept, to improve usage with vector. 2017-09-25 19:53:22 -04:00
Thomas Harte
d1ce764201 Provides SectorsFromSegment, a bitstream to sector converter. 2017-09-24 22:41:16 -04:00
Thomas Harte
8875982e1f Ensures Sectors are move constructible (and still default constructible), and adds proper const qualifiers to Sector::Address. 2017-09-24 22:40:38 -04:00
Thomas Harte
3319a4f589 Isolates those Sector fields that describe its address and makes them usable as a set key. 2017-09-24 21:57:21 -04:00
Thomas Harte
c7f27b2db4 Renames MFM.[c/h]pp as per its new remit: encoding only. 2017-09-24 21:40:43 -04:00
Thomas Harte
631f630549 Severs the MFM parser from the overweight single MFM.hpp. 2017-09-24 20:31:19 -04:00
Thomas Harte
2a08bd9ecc Factors shifting plus stateful [M]FM token recognition out of the MFMDiskController.
Given the proliferation of MFM-related classes, establishes a subdirectory for them.
2017-09-24 20:07:56 -04:00
Thomas Harte
f789ee4ff0 Introduces a track to segment decoder.
This will be needed to make formats like G64 and HFE writeable, but probably also will be usable to speed up static analysis.
2017-09-23 22:39:19 -04:00
Thomas Harte
a295b42497 Merge pull request #248 from TomHarte/BetterCPCShot
Adds a better example of correct-aspect-ratio CPC output
2017-09-22 23:05:01 -04:00
Thomas Harte
d8337492cc Bowdlerised images. 2017-09-22 23:02:17 -04:00
Thomas Harte
15c8debc16 Added larger CPC screenshots. 2017-09-22 22:58:18 -04:00
Thomas Harte
67af153c16 Merge pull request #247 from TomHarte/WriteableHFE
Cleans up the `Disk` hierarchy
2017-09-22 22:55:22 -04:00
Thomas Harte
d72dad2d1a Severs the DiskImage implementation from its public header file. 2017-09-22 22:46:31 -04:00
Thomas Harte
698e4fe550 Tidies the Disk file hierarchy. 2017-09-22 22:39:23 -04:00
Thomas Harte
b5406b90cd Introduces a new class hierarchy for disk images.
Increasing independence of format-specific stuff and generic caching without mangling them into a common namespace, and allowing in some cases for a decrease in read/write blocking.
2017-09-22 20:28:11 -04:00
Thomas Harte
05a93ba237 Merge pull request #246 from TomHarte/MainThreadBackingSize
Ensures self.bounds and -convertSizeToBacking: are called only on the main queue.
2017-09-20 20:00:45 -04:00
Thomas Harte
77548d14db Ensures self.bounds and -convertSizeToBacking: are called only on the main queue. 2017-09-20 19:59:34 -04:00
Thomas Harte
b85dd608e7 Merge pull request #245 from TomHarte/Xcode9
Updates to Swift 4 and Xcode 9's recommended project settings.
2017-09-20 19:54:31 -04:00
Thomas Harte
231f13d810 Updates to Swift 4 and Xcode 9's recommended project settings. 2017-09-19 23:06:37 -04:00
Thomas Harte
704bfa114c Merge pull request #244 from TomHarte/FasterStartup
Improves CPC analysis times
2017-09-16 22:07:43 -04:00
Thomas Harte
44a56724cb Speeds up byte decoding within sectors for the ahead-of-time MFM parser. 2017-09-16 20:28:24 -04:00
Thomas Harte
5fbea625ae Switches the CPC static analyser to maintaining a vector of pointers rather than a complete copy of files.
Hence saves a lot of copying and moving — around a second's worth when dealing with the selected test disk.
2017-09-16 20:15:06 -04:00
Thomas Harte
ac57b37e96 Eliminates repetition of the 'untypable character' test. 2017-09-16 19:46:41 -04:00
Thomas Harte
e3e9baeaa4 Merge pull request #243 from TomHarte/Detection
Adds a test that file extension also be typeable.
2017-09-16 19:11:53 -04:00
Thomas Harte
e071123f90 Adds a test that file extension also be typeable. 2017-09-16 19:10:17 -04:00
Thomas Harte
98adb01721 Merge pull request #242 from TomHarte/8272ReadyInterruption
Improves CPC disk emulation
2017-09-16 18:28:00 -04:00
Thomas Harte
d6a5f9a29e Revokes unnecessary change. 2017-09-16 18:24:13 -04:00
Thomas Harte
0d84b4b9dd Removes some redundant end_writing calls. 2017-09-16 17:09:17 -04:00
Thomas Harte
a85909198f Adds defences against double calls to end writing. 2017-09-16 17:07:36 -04:00
Thomas Harte
98751e6ac8 Ensures that all result phases are exactly the intended length by replacing accumulation with assignment.
Also attempts a different version of control mark behaviour. Experiments.
2017-09-15 22:59:26 -04:00
Thomas Harte
da082673d7 Drives now have a finite number of heads.
The Amstrad volunteers itself to be single sided. Everything else stays as it was.
2017-09-15 21:18:36 -04:00
Thomas Harte
35fe4d50d4 Adds command termination upon drive becoming unready, and copies head and drive selection into ST0. 2017-09-15 20:26:41 -04:00
Thomas Harte
b835cb73e2 Merge pull request #241 from TomHarte/DriveEvents
Devolves `TimedEventLoop` ownership from disk controllers to drives
2017-09-15 19:15:44 -04:00
Thomas Harte
662d031e3c Adds exposition on the meaning of a disk controller being in write mode. 2017-09-15 19:14:36 -04:00
Thomas Harte
bf20c717fb The Drive now no longer produces input when in writing mode — other than announcing the index hole. 2017-09-14 22:32:13 -04:00
Thomas Harte
4d4a0cf1d2 Puts the disk controller back into the loop with knowledge about reading mode, and uses that knowledge to cut off the PLL. 2017-09-14 22:30:40 -04:00
Thomas Harte
b62f3e726a Adds a start-of-execution-phase get-out for drives that aren't ready. 2017-09-12 20:43:53 -04:00
Thomas Harte
82b13e98f2 Implements the real hardware ready test for Drives — motor on plus two index holes. 2017-09-11 22:27:50 -04:00
Thomas Harte
9ac831b09c Added an additional protection against overflow. 2017-09-11 22:24:24 -04:00
Thomas Harte
42616da7ff Adjusts the Oric Microdisc to propagate motor control more widely. 2017-09-11 22:15:54 -04:00
Thomas Harte
2f13517f38 Adjusts the 1770 not to talk directly to the drive about motor status. 2017-09-11 22:10:56 -04:00
Thomas Harte
fb9fd26af7 Updates the 1540 for the slightly-more modern world of decoupled drives and disks (!). 2017-09-11 22:08:10 -04:00
Thomas Harte
d3c385b471 Separates the 8272's drive selection signalling from actual drive ownership.
Thereby returns working motor control to the CPC.
2017-09-11 21:25:26 -04:00
Thomas Harte
96bf133924 Withdraws requirement for DiskController users to specify a PLL multiplier or to provide rotation speed.
In the latter case because it's no longer of any interest to the controller, and in the former because I'd rather it be picked automatically.
2017-09-10 22:56:05 -04:00
Thomas Harte
6d6cac429d Fixes extra time accumulation during track running.
Introduces a bunch of further asserts, which aided me in determining the fix, i.e. that Drives being responsible for their own setup_track could double-pump the event loop.
2017-09-10 22:44:14 -04:00
Thomas Harte
dc0b65f9c9 Corrects initial event loop timing state. 2017-09-10 20:51:21 -04:00
Thomas Harte
8882aa496f Corrected wiring to get advance signals through to Drive event delegates. 2017-09-10 20:51:05 -04:00
Thomas Harte
0622187ddf Strips Controller of all capabilities now housed on the Drive. 2017-09-10 19:23:23 -04:00
Thomas Harte
523e1288fa Updates the MFM parser to use SingleTrackDisk rather than the equivalent withdrawn Drive functionality. 2017-09-10 17:34:52 -04:00
Thomas Harte
1a96cce26f Implements SingleTrackDisk, a Disk that contains only a single, specified, track. 2017-09-10 17:34:14 -04:00
Thomas Harte
a4e275e1fc Provides an implementation of Drive's new interface.
Mostly lifted from DiskController. `set_disk_with_track` has been withdrawn in favour of providing a suitable wrapper `Disk` subclass, as being an unnecessary complexity and intermingling of concerns.
2017-09-10 17:33:01 -04:00
Thomas Harte
6075064400 Adds the ability to query a TimedEventLoop for its input clock rate. 2017-09-10 17:31:43 -04:00
Thomas Harte
ff6e65cca9 Introduces necessary storage and interface for writing. 2017-09-10 16:23:31 -04:00
Thomas Harte
90d2347c90 Extended to permit subclasses that are interested to get sub-run_for information about event times. 2017-09-10 14:44:38 -04:00
Thomas Harte
90c7056d12 Started devolving timed event loop logic down to the drives, moving them closer to modelling real life. 2017-09-10 14:43:20 -04:00
Thomas Harte
fed2bc9fc9 Merge pull request #240 from TomHarte/C1540
Simplifies the published C1540 interface and corrects a transcription bug.
2017-09-05 21:22:25 -04:00
Thomas Harte
ff510f3b84 Explicitly disallows copying of VIAs, and marks the constructor as noexcept. 2017-09-05 21:21:23 -04:00
Thomas Harte
3b12fca417 Corrects non-recurring-pattern adaptation bug: the 'SerialPortVIA' should keep a reference to its VIA, not a copy of it. 2017-09-05 21:19:56 -04:00
Thomas Harte
8eeb7e73cd Adds a commented-out printf that I might like to use again later. 2017-09-05 21:15:56 -04:00
Thomas Harte
7fd6699e0b Corrects comment indentation. 2017-09-05 21:15:15 -04:00
Thomas Harte
ed70b15fc9 Merge pull request #239 from TomHarte/6522Tests
Corrects 6522 bridge per has-a-not-is-a template switch.
2017-09-04 21:58:07 -04:00
Thomas Harte
ff24e1de31 Corrects 6522 bridge per has-a-not-is-a template switch. 2017-09-04 21:56:21 -04:00
Thomas Harte
6547102511 Attempts better to hide C1540 implementation details from the reader.
In this case not from the compiler, as it's desireable to keep `run_for` as a non-virtual call, and therefore everything else comes alone for the ride.
2017-09-04 20:58:00 -04:00
Thomas Harte
d538ff5039 Merge pull request #238 from TomHarte/C1540
Minor tweaks to re-enable proper file selection in the Vic-20.
2017-09-04 20:56:54 -04:00
Thomas Harte
a49594c6a3 Tweaks Vic20 Machine parent class order so that when turned into a CRTMachine, still successfully dynamically casts as a ConfigurationTarget.
More thorough thought is required.
2017-09-04 20:56:00 -04:00
Thomas Harte
3544c0f014 Switches from testing size() != 0 to empty() != true.
Partly as size() is O(n) but empty is O(1), but primarily for style.
2017-09-04 20:54:38 -04:00
Thomas Harte
f26fe3756c Merge pull request #237 from TomHarte/6522CleanUp
Significantly cleans up the 6522.
2017-09-04 18:23:33 -04:00
Thomas Harte
a42ca290cb Reformulates the Oric more cleanly into the modern world.
Specifically: now that the implementation is contained within the CPP file, there's no need to embed the keyboard, tape player and VIA port handler as private classes. Also the pain of additional syntax is reduced, so the keyboard has been bumped up to a fully data-hiding class. I've also transferred overall ownership of the tape player, AY and keyboard up to the Oric itself, with the VIA merely being wired to them, and added a whole bunch of extra documentation.
2017-09-04 18:22:14 -04:00
Thomas Harte
da09098e49 Updates clipped area per latest CRT response to vertical sync. 2017-09-04 17:51:02 -04:00
Thomas Harte
450712f39c Improves and corrects 6522 header documentation. 2017-09-04 14:32:34 -04:00
Thomas Harte
24b3faa427 Deconstitutes the 6522 into component parts, templated and non-templated.
Adjusts the Oric, Vic-20 and C-1540 accordingly, albeit with the quickest possible solutions.
2017-09-04 14:26:04 -04:00
Thomas Harte
40d11ea0e3 Merge pull request #236 from TomHarte/CPUSeparation
Further cements CPU file separation.
2017-09-04 11:20:00 -04:00
Thomas Harte
ab2bcb939f Separates 6502Base into its constituent parts. 2017-09-04 11:08:33 -04:00
Thomas Harte
45499050b6 Separates Z80Base.cpp into its component classes. 2017-09-04 11:04:01 -04:00
Thomas Harte
0c9197df30 Merge pull request #235 from TomHarte/Reencapsulation
Further strips back the amount exposed in Z80-related headers.
2017-09-01 22:38:33 -04:00
Thomas Harte
a1e200cc65 Further strips back the amount exposed in Z80-related headers.
Almost all opcode table generation macros and code now resides neatly in the world of .CPP.
2017-09-01 22:19:16 -04:00
Thomas Harte
8a612bb6ab Merge pull request #234 from TomHarte/TidyZ80
Separates interface and implementation of the Z80
2017-09-01 20:54:01 -04:00
Thomas Harte
e6ac939ae0 Reintroduces missing noexcept specifier. 2017-09-01 20:51:31 -04:00
Thomas Harte
b034d4e6f8 Refactors the Z80 to separate out interface and implementation.
Following the pattern just established by the 6502, puts all implementation specifics beyond the visibility of a human reading Z80.hpp and in subfolders so as to promote the idea that they shouldn't go out of their way.
2017-09-01 20:50:24 -04:00
Thomas Harte
de218611e4 Corrects possible confusion as documentation recommends Cycles(0) as default, but then gives Cycles(1). 2017-09-01 20:49:24 -04:00
Thomas Harte
615f7ce176 Merge pull request #233 from TomHarte/BetterYet6502
Removes from 6502.hpp all remaining implementation details.
2017-09-01 19:47:49 -04:00
Thomas Harte
b306776ba9 Removes from 6502.hpp all remaining implementation details, making it purely an interface document.
Though those details remain visible to files including 6502.hpp through necessity.
2017-09-01 19:46:29 -04:00
Thomas Harte
0f85cffc78 Merge pull request #232 from TomHarte/ElectronShift
Ensures all parts of the Electron have a fully-defined initial state.
2017-08-31 22:29:50 -04:00
Thomas Harte
96648df5fe Ensures all parts of the Electron have a fully-defined initial state.
Specifically to resolve an error with shift being pressed at startup due to a failure to establish a default value for that flag, but applying the same principle across the board.
2017-08-31 22:29:24 -04:00
Thomas Harte
2c99a2d6ec Merge pull request #231 from TomHarte/NeaterTemplates
Tidies the 6502 template and folder hierarchy.
2017-08-31 22:17:08 -04:00
Thomas Harte
4af333d5ec Tidies the 6502 template and folder hierarchy.
Specifically: there's now just the one .h file at the top level, giving a clear indication of what a user should read. That separates implementation from interface. It also devolves a lot more to the base class because doing so makes debug builds less of a hassle. The all-RAM 6502 has been shuffled off into a subfolder, to indicate that it's not something you necessarily need know about. Also general documentation improvements have been applied: incorrect citing of the recurring-template pattern has been removed and the meaning of the two BusHandler methods has now accrued at the bus handler.
2017-08-31 22:10:27 -04:00
Thomas Harte
a5f9869769 Merge pull request #230 from TomHarte/CyclicShutdown
Eliminates potential cyclic entry into CSMachine during its `-dealloc`.
2017-08-31 21:23:15 -04:00
Thomas Harte
f10be2a18a Eliminates potential cyclic entry into CSMachine during its -dealloc.
Explicit cause: dealloc calls close_output(). That may decide to flush work, indiscriminately. Some of the flushed work might be audio generation. Audio generation might cause the audio queue to react with an out-of-data announcement. Which would cause a fresh attempt to update the CSMachine.
2017-08-31 21:22:23 -04:00
Thomas Harte
c88d627b4e Merge pull request #229 from TomHarte/Skew
Adds an initial implementation of display skew to the 6845
2017-08-29 22:32:26 -04:00
Thomas Harte
b30bb2a234 Adds an initial implementation of display skew, as a completely live property. 2017-08-29 22:16:40 -04:00
Thomas Harte
d498080eb4 Merge pull request #228 from TomHarte/CRTCStatus
Takes initial steps towards supporting CRTC manufacturer diversity.
2017-08-27 22:26:40 -04:00
Thomas Harte
334afbc710 Removes const from get_status and get_register, as both may now logically mutate the object. 2017-08-27 18:13:55 -04:00
Thomas Harte
17c13624e5 Improved comments. 2017-08-27 18:11:40 -04:00
Thomas Harte
113349d272 Started making some formal admissions that different CRTC models exist. Plenty yet to do. 2017-08-27 18:10:07 -04:00
Thomas Harte
0ced7866fc Merge pull request #227 from TomHarte/NoCPCOptions
Removes the CPC options panel.
2017-08-27 17:12:24 -04:00
Thomas Harte
d06031dfcb Removes the options panel for CPC display. 2017-08-27 17:11:35 -04:00
Thomas Harte
3f22a71276 Merge pull request #226 from TomHarte/TargetAwareness
Substantially rewires Mac-side target selection and as proof-of-concept adapts the generic-side ZX80 to instantiate without wait line support
2017-08-27 16:55:01 -04:00
Thomas Harte
53a88a7e12 Causes the ZX80/81 to omit support for the wait line if being configured as a ZX80. 2017-08-27 16:45:36 -04:00
Thomas Harte
4a66dd9e82 Arranges for the ZX80/81 to get a peek at target configuration prior to construction. I'm as yet undecided on whether to make this the norm. 2017-08-27 16:42:16 -04:00
Thomas Harte
522839143f Revokes -[CSMachine init] and the slightly troubling create-on-demand semantics it places upon subclasses via .machine. Therefore each machine must announce its own implementation of -init. 2017-08-27 16:36:21 -04:00
Thomas Harte
b4c532c0d5 Merge pull request #225 from TomHarte/TargetHints
Factors the concept of a target platform out from the static analyser, allowing file formats to opine
2017-08-27 15:46:55 -04:00
Thomas Harte
a3e2d142e3 Extends UEF support to include chunk 0005, the target platform description, which is exposed via TargetPlatform::TypeDistinguisher. 2017-08-27 15:43:09 -04:00
Thomas Harte
63ee8c9d58 Uses file containers' type distinguishers where available, and supplies potential insight to the ZX80/81 analyser as now required. 2017-08-27 15:20:58 -04:00
Thomas Harte
437023bff6 Expands to take an already-accrued list of potential platforms, as that may indicate that one or the other of the ZX80 and ZX81 is already out of contention and therefore save the need to attempt analysis. 2017-08-27 15:20:22 -04:00
Thomas Harte
4465098157 Since it has descendants, gives Storage::Cartridge a virtual destructor. 2017-08-27 15:19:30 -04:00
Thomas Harte
56dd677e9c Creates a virtual interface that can be adopted by classes that are able to provide some insight as to target machine. 2017-08-27 15:19:03 -04:00
Thomas Harte
9aa150c338 Abstracts the target platform type out from the static analyser's ownership. 2017-08-27 15:02:13 -04:00
Thomas Harte
fab6908129 Corrects the all-RAM Z80 to declare that it needs the wait line to be implemented. 2017-08-26 23:18:11 -04:00
Thomas Harte
e34d4ce903 Merge pull request #224 from TomHarte/OptionalWait
Makes the Z80's support for WAIT input optional
2017-08-26 23:16:22 -04:00
Thomas Harte
d411827733 Merge branch 'master' into OptionalWait 2017-08-26 23:11:23 -04:00
Thomas Harte
f1ba7755dd Merge pull request #223 from TomHarte/cpctest
Moves test for 6845 horizontal sync timing into the time after phase 1 and before phase 2
2017-08-26 23:11:03 -04:00
Thomas Harte
57bfec285f Makes it optional whether the Z80 supports the wait line. If the wait line isn't in use, runtime costs are decreased because the optional wait cycles need not be iterated over. 2017-08-26 23:08:57 -04:00
Thomas Harte
bdda701207 Reverts previous unevidenced change. 2017-08-26 22:58:16 -04:00
Thomas Harte
487fe83dca Ensures that vertical sync and end-of-visible-lines conditions potentially trigger whenever line_counter_ changes, not only when it increments. 2017-08-26 17:54:54 -04:00
Thomas Harte
6c5a03187b Relocates the HSYNC start test, in order to pass Arnold's cpctest HSYNC start position conformance test. 2017-08-26 17:22:48 -04:00
Thomas Harte
97f57a3948 Merge pull request #222 from TomHarte/6845GetState
Refines observable 6845 behaviour
2017-08-26 14:46:29 -04:00
Thomas Harte
7d7aa2f5d5 Eliminates repetition of the unpacking of register 3 into a horizontal sync count. 2017-08-26 14:37:03 -04:00
Thomas Harte
e7ad79c79a Breaks apart the CPC's 6845 bus handler to obey phase 1 and phase 2, and now back-dates interrupts when appropriate. 2017-08-26 14:07:51 -04:00
Thomas Harte
28550c0227 Breaks the 6845 bus cycle into a phase 1 and a phase 2 per the belief that sync line changes, which are observable, happen at the end of the first phase rather than at the beginning of the next. This may have interrupt timing effects, as machines often derive an interrupt from sync. 2017-08-26 13:56:23 -04:00
Thomas Harte
6e99169348 Permits the 6845's bus state to be examined by an owner, eliminating the need to buffer it in the bus handler. But more than that it allows the CRTC to decide when it adjusts the various outputs respective to the main phase. So a net effect of the change is that the CPC now sees vsync a cycle earlier, because my current reading of the 6845 datasheet is that it is set at the end of phase 1, not the beginning of the next phase 1. 2017-08-26 12:59:59 -04:00
Thomas Harte
1017bb9f6b Merge pull request #221 from TomHarte/6845UpCount
Regularises the 6845 sync counters
2017-08-26 12:51:39 -04:00
Thomas Harte
3caa4705ca Limits sync counter size. 2017-08-26 12:31:19 -04:00
Thomas Harte
039aed1bd1 Switches the two sync counters to upward-going rather than downward, as a more likely match to the way the rest of the 6845 implementation. 2017-08-25 21:26:01 -04:00
Thomas Harte
d77d7fdd78 Merge pull request #220 from TomHarte/Analysis
Resolves all current analyser warnings.
2017-08-24 22:19:51 -04:00
Thomas Harte
c6e6c3fcfb Resolves all current analyser warnings. 2017-08-24 22:18:44 -04:00
Thomas Harte
ecd3350a6f Merge pull request #219 from TomHarte/ConstSafety
Makes all of PartialMachineCycle const
2017-08-24 22:04:06 -04:00
Thomas Harte
fa19e2d9c2 Removes some detritus. 2017-08-24 22:00:21 -04:00
Thomas Harte
95d360251d Makes all of PartialMachineCycle const, with the exception of the target of *value, since that's intended to be writeable by recipients. 2017-08-24 21:32:33 -04:00
Thomas Harte
7af3de010e Suspected my mode 1 interrupt timing might be off. Reminded myself of the sources. Persuaded myself that it wasn't. Added appropriate comments. 2017-08-23 22:25:31 -04:00
Thomas Harte
cefd421992 Merge pull request #218 from TomHarte/6845Factored
Refactors the 6845 to make end-of-line and end-of-frame conditions more explicit and to reduce repetition
2017-08-22 22:21:17 -04:00
Thomas Harte
a914eadc85 Ensured that register 6 is checked on every loop. 2017-08-22 22:17:45 -04:00
Thomas Harte
131b340d75 Dodges a lambda copy. 2017-08-22 21:55:10 -04:00
Thomas Harte
e956740c56 Refactors the 6845 more clearly to break out the acts of ending a line and ending a frame, changing the way the memory address is altered — the end-of-line value is provisionally stored and then used if necessary — in order to do so. 2017-08-22 21:54:48 -04:00
Thomas Harte
8afd83b91f Merge pull request #217 from TomHarte/CompiletimeOptions
Introduces compile-time selection of minor CPU core features and applies forceinline when appropriate
2017-08-21 22:29:24 -04:00
Thomas Harte
40d7a603db Ensured that forceinline does nothing in debug builds. 2017-08-21 22:04:15 -04:00
Thomas Harte
ee71be0e7e Added the option not to include ready line support in the 6502 core, and took advantage of it in the Electron, Oric and Vic-20 implementations. Also tagged those as forceinline and/or override final where applicable. 2017-08-21 21:56:42 -04:00
Thomas Harte
cde29c4bf4 Added forceinlines and properly declared finals and overrides. 2017-08-21 21:07:10 -04:00
Thomas Harte
e1aded0d95 Allows Z80 users to opt out of support for the bus request line. Which both now do. 2017-08-21 20:43:12 -04:00
Thomas Harte
1237f174fe Merge pull request #216 from TomHarte/NoiseReduciton
Cleans up issues affecting the sleeper mechanism and the CPC
2017-08-20 13:26:56 -04:00
Thomas Harte
0cbc1753b9 Quick fixes: the binary tape player now considers talk to the sleep observer only if motor control changes. The Amstrad CPC no longer attempts to use the component argument to identify the caller, since this will often be that of the superclass and not that of the derived class known to the CPC. 2017-08-20 13:18:46 -04:00
Thomas Harte
5cf0395936 Merge pull request #215 from TomHarte/Z80BusReq
Removes repeated checking of bus_request_line_ by the Z80.
2017-08-20 12:40:37 -04:00
Thomas Harte
6315c22b80 Removed repeated checking of bus_request_line_. It's now checked only after each outward perform_machine_cycle. 2017-08-20 12:39:45 -04:00
Thomas Harte
4614a56843 Merge pull request #214 from TomHarte/Sleeper
Experimentally introduces the concept of a 'sleeper' — a component that will volunteer to be unclocked for a period
2017-08-20 12:29:32 -04:00
Thomas Harte
8f5ae4a326 The CPC now responds to tape-originating sleeper observations. 2017-08-20 12:21:02 -04:00
Thomas Harte
8fdc5012e4 Updated TapePlayer and BinaryTapePlayer to be sleepers. 2017-08-20 12:18:36 -04:00
Thomas Harte
e88a51e75e Worked logic all the way down to the CPC. If the 8272 announces that it is asleep, it is now no longer clocked. Also very slightly cut down on IRQ line chatter to the Z80. 2017-08-20 12:05:00 -04:00
Thomas Harte
49285e9caa Attempted to implement Sleeper in Drive and therefore in DiskController. Also corrected a couple of nonconformant file names. 2017-08-20 11:54:54 -04:00
Thomas Harte
e3f2118757 Merge branch 'master' into Sleeper 2017-08-20 10:58:03 -04:00
Thomas Harte
daeaa4752f Merge pull request #213 from TomHarte/MinorPalette
Makes a variety of minor performance improvements to the CPC
2017-08-20 10:57:41 -04:00
Thomas Harte
5344e3098b Minor: made has_disk something that is decided on insertion/deletion. 2017-08-20 10:55:08 -04:00
Thomas Harte
cedb809c21 Sketched out a protocol designed to save processing time on anything that may sleep — probably just disk controllers for now but one can easily imagine it being applicable to printers, and possibly sound chips with suitable changes in guarantee for sound packet receivers. 2017-08-20 10:53:25 -04:00
Thomas Harte
2d9efccc98 Introduced a master 'is sleeping' flag. I'm starting to think there's a pattern forming here. 2017-08-20 10:43:53 -04:00
Thomas Harte
8ce46b6e49 Having spotted that I was using my single-character loop counter names incorrectly (quelle surprise!), got a bit more explicit. Also flattened into a single loop so that I can break rather than returning. 2017-08-20 10:32:09 -04:00
Thomas Harte
f2699a3f2b Okay, even if releasing it is unsafe, I can at least move the typer so that it is no longer called. 2017-08-20 10:24:01 -04:00
Thomas Harte
85253a5876 Sought further to reduce the processing footprint of palette changes by updating only those table entries that are affected by a change. 2017-08-20 10:13:23 -04:00
Thomas Harte
911ee5a0d3 At least added a fast return. 2017-08-19 22:22:51 -04:00
Thomas Harte
57c5b38a6d Step one towards cutting much of this cost: build only the table that's appropriate for the current mode, and at least declare when a more minimal change would be sufficient. 2017-08-19 22:19:46 -04:00
Thomas Harte
669e0caff5 Ensured the head_unload_delay values are properly seeded, and generalised the quick escape. 2017-08-19 22:06:56 -04:00
Thomas Harte
b24d04fc09 Merge pull request #212 from TomHarte/MFMParserDensity
Improves the variability of the MFM parser used for static analysis
2017-08-18 15:57:37 -04:00
Thomas Harte
ef07c33741 Merge branch 'Plus10' into MFMParserDensity 2017-08-18 15:48:20 -04:00
Thomas Harte
e559a65ede Ideally I would be able to kill this multiplier, as it could easily be derived at runtime. But, for now, just turned it up so that the analysis-oriented parser is better at parsing different bit rates. 2017-08-18 15:47:46 -04:00
Thomas Harte
5bdd24d93f Merge pull request #211 from TomHarte/HFE
Introduces support for the HFE file format.
2017-08-17 22:43:23 -04:00
Thomas Harte
af61a7fa28 Two quick fixes: correctly set segment size, and flip bytes to match HFE's bit ordering to PCMTrack's. 2017-08-17 22:28:00 -04:00
Thomas Harte
c8c1792c3f Made a first attempt at HFE support. 2017-08-17 22:20:02 -04:00
Thomas Harte
e6683e7f2d Added the base skeletal stuff of HFE support. 2017-08-17 21:48:48 -04:00
Thomas Harte
0c1714b695 Relaxed a little to allow +10% in track length. 2017-08-17 21:36:14 -04:00
Thomas Harte
dc0ca83003 Merge pull request #210 from TomHarte/TrackSize
Various MFM and DSK fixes
2017-08-17 15:50:27 -04:00
Thomas Harte
2c2dd8073c Modified to return nullptr if asked for an extended disk image track that doesn't exist. 2017-08-17 15:32:24 -04:00
Thomas Harte
4f8b89772e Improved logic for detecting when all sense has been derived from a track to spot any repeated track, not necessarily the first one. That avoids sectors that run over the index hold and obscure the first throwing things. 2017-08-17 15:31:53 -04:00
Thomas Harte
733ee5a5c3 Ensured no attempt to put a null track into the cache 2017-08-17 15:30:02 -04:00
Thomas Harte
fedf5a44a6 Imposes a maximum track length. 2017-08-17 15:20:49 -04:00
Thomas Harte
6a09022896 Merge pull request #209 from TomHarte/BootOnly
Generalises: an acceptable boot sector is always acceptable
2017-08-17 14:29:37 -04:00
Thomas Harte
5b3c707959 Generalised: an acceptable boot sector is acceptable even if no valid CP/M catalogue is found anywhere. 2017-08-17 14:28:16 -04:00
Thomas Harte
9b21ef1507 Merge pull request #208 from TomHarte/SpecialCharacters
Improves the CPC static analyser's correct file name predictions
2017-08-17 13:25:30 -04:00
Thomas Harte
da3e8655e9 Withdrew some caveman debugging nonsense. 2017-08-17 13:25:19 -04:00
Thomas Harte
41e4386164 Added another "one thing is different" test: one thing has a different file name. Also decided to right-time the type (/extension) as well as the file name. 2017-08-17 13:21:48 -04:00
Thomas Harte
b0a98bd239 Added nuance: file names with unprintables are filtered, and then system files are considered if there are no remaining non-system files. 2017-08-17 12:48:15 -04:00
Thomas Harte
42ad670ec8 Fixed: catalogue bitmap is in blocks, not sectors. 2017-08-17 12:47:47 -04:00
Thomas Harte
58063b69a6 Merge pull request #207 from TomHarte/DragAndDrop
Establishes drag and drop as a mechanism to change the media inserted into a machine while it is running
2017-08-17 11:19:56 -04:00
Thomas Harte
378f231499 Fully wired in drag-and-drop for media insertion. 2017-08-17 11:00:08 -04:00
Thomas Harte
f68565a33f Split the static analyser functionality so that it's possible just to ask for the set of media implied by a particular file. Extended ConfigurationTarget so that media alone can be pushed to a machine. 2017-08-17 10:48:29 -04:00
Thomas Harte
175faebdc9 Merge pull request #206 from TomHarte/AnalysisFailure
Corrects a couple of cases in which the CPC analyser could pick an incorrect loading command
2017-08-16 22:26:26 -04:00
Thomas Harte
76c6b715a2 Adjusted rules so as not to type unnecessary spaces in the name, and to include the extension if AMSDOS won't imply it. 2017-08-16 22:24:37 -04:00
Thomas Harte
b476f06524 Slowed the typer, having discovered that otherwise it has problems transitioning from a shifted to an unshifted character. 2017-08-16 22:12:16 -04:00
Thomas Harte
48290a8bbe Added a prefilter to catalogues to remove system files. They're not listed when you CAT, so almost certainly aren't what a user would be expected to load. 2017-08-16 22:11:49 -04:00
Thomas Harte
9d9a1c341d Added an extra test to try to avoid spurious |cpm launches. 2017-08-16 21:55:31 -04:00
Thomas Harte
952da1e581 Merge pull request #205 from TomHarte/CPCShots
Adds a CPC screenshot, to show that this isn't just about composite video
2017-08-16 21:05:55 -04:00
Thomas Harte
a988255558 Adjusted brightness better to match its comparison. And to try partly to obscure its source. This isn't meant to be about software X versus software Y. 2017-08-16 21:04:09 -04:00
Thomas Harte
cca66ab450 Added a CPC grab, to show that it's not all about being composite. 2017-08-16 21:01:22 -04:00
Thomas Harte
bcd7a312a4 Merge pull request #204 from TomHarte/VicInline
Brings the Vic-20 into line with the new idiom on machine declaration
2017-08-16 16:24:38 -04:00
Thomas Harte
925e774015 Added a decent portion of documentation. But started feeling like I should address my various ownership decisions. Which would justify a separate pull request. 2017-08-16 16:23:33 -04:00
Thomas Harte
4c15e46fd1 Performed the normative removal from public view of Vic-20 implementation details. Which were hefty. 2017-08-16 16:05:30 -04:00
Thomas Harte
3c50903a2b Merge pull request #203 from TomHarte/ElectronInline
Adds the Electron to the pantheon of machines that reveal very little in their public interface
2017-08-16 15:35:11 -04:00
Thomas Harte
75208b0762 Moves the Electron implementation behind a more opaque interface, in line with changes elsewhere. 2017-08-16 15:33:40 -04:00
Thomas Harte
f1e64169cd Merge pull request #202 from TomHarte/AtariInline
Catches the Atari 2600 up with the no-internals-published trend.
2017-08-16 14:54:33 -04:00
Thomas Harte
903a17ae11 Corrected typo and removed replication of what's already declared formally. 2017-08-16 14:53:03 -04:00
Thomas Harte
de1c526789 Cut the amount disclosed by the Atari 2600 for public inspection down to the minimum, relocating implementation into the .cpp. 2017-08-16 14:52:40 -04:00
Thomas Harte
b7e0f64892 Merge pull request #201 from TomHarte/OricInline
Hides the Oric implementation innards
2017-08-16 14:37:42 -04:00
Thomas Harte
148591b7f2 Hid most of the Oric innards, and corrected a potential multi-thread access error emanating from the Mac side of the world. 2017-08-16 14:35:53 -04:00
Thomas Harte
2105597910 Merge pull request #200 from TomHarte/6502BusHandler
Converts the 6502 into a bus-handler-type template, and makes appropriate adjustments to all 6502 machines
2017-08-16 14:10:08 -04:00
Thomas Harte
3c148f5721 Fixed clanger of an error. 2017-08-16 14:02:46 -04:00
Thomas Harte
360c8a99a3 Adjusted Atari2600 actually to use the nominated type of bus extender. 2017-08-16 12:57:32 -04:00
Thomas Harte
06e31f5102 Consequential to the 6502 change, severs the Atari 2600's cartridge container from its former attempt at runtime polymorphism, in favour of each cartridge's specific hardware being defined as a 'bus extender'. 2017-08-16 12:39:15 -04:00
Thomas Harte
42b5b66305 Remove the 6502's use of runtime polymorphism in favour of ordinary templating. 2017-08-16 11:56:52 -04:00
Thomas Harte
27018ba64e Merge pull request #199 from TomHarte/VicColoursUSA
Corrects Vic-I NTSC colour output
2017-08-16 10:00:40 -04:00
Thomas Harte
e208f03636 Corrects the US colour palette, effectively undoing what was a mistaken adjustment for the time when Oric-centric phase alignment was built into the CRT based on a false calculation that it wouldn't affect the machines that generate chrominance functionally. 2017-08-16 09:58:34 -04:00
Thomas Harte
cc9d23f23b Inverted meaning of register_masks, as it's a bit weird that the mask is inverted immediately upon usage. It's a left-over from thinking the unused bits should be 1s; unit tests reveal they should be 0s. Comment updated appropriately. 2017-08-16 09:29:48 -04:00
Thomas Harte
1a831bcf9b Quick fix: supply the port being written to correctly. 2017-08-16 09:15:57 -04:00
Thomas Harte
82367a2246 Added documentation. 2017-08-16 09:14:56 -04:00
Thomas Harte
f18206767f Merge pull request #198 from TomHarte/LiveKeyboard
Introduces active input handling for the AY and uses it for CPC keyboard input
2017-08-15 22:48:09 -04:00
Thomas Harte
3947347d88 Introduces active input handling for the AY and uses it in the CPC to give proper, active keyboard input, rather than push-on-select, which was only ever a temporary hack. Also maps a few more keys for the Amstrad. 2017-08-15 22:47:17 -04:00
Thomas Harte
8a37a0ff2e Merge pull request #197 from TomHarte/MoreFDC
Improves the 8272 and the whole disk infrastructure behind it
2017-08-15 22:09:32 -04:00
Thomas Harte
468770b382 Removed debugging nonsense. 2017-08-15 22:06:58 -04:00
Thomas Harte
6cfc3daacb Introduced a test within the disk controller so as not to request illegal tracks from disks, instead automatically substituting an 'unformatted' track. Which is just empty. 2017-08-15 21:52:12 -04:00
Thomas Harte
aefbafa18d Centralised the 8272's actions of setting the non-DMA execution flag, picking the drive and head and loading the head for accessing commands, and switched error flag if read ID doesn't find anything. 2017-08-15 21:49:10 -04:00
Thomas Harte
d12c834f9c Increased amount reported. 2017-08-15 20:35:33 -04:00
Thomas Harte
56de5cb2b3 Removed one further logging event. 2017-08-15 20:18:32 -04:00
Thomas Harte
709257a0c5 Quick fix: also treat reception of sync as a reason not to stop looking for a data address mark. 2017-08-15 20:16:56 -04:00
Thomas Harte
75a9d2bb33 Started withdrawing logging, reduced index hole count back to the correct number and tried to increase read_data rigour. 2017-08-15 20:12:01 -04:00
Thomas Harte
7b92b235e1 Further upped asserts, thereby discovering the mistake I'd recently introduced: seeking properly within the event source as per its potential left-clipping, but then not allowing for that in the calculated current time. 2017-08-15 16:25:46 -04:00
Thomas Harte
c196f0018f Upped the assert quotient. 2017-08-15 16:15:09 -04:00
Thomas Harte
73080d6c36 Added an easy way for disk controllers to clamp termination of written data exactly to the index hole.
This commit also temporarily provides a whole load of extra logging and minor logic improvements from the 8272. I'm mid-flow on finding a particularly vicious error in its handling of writing; wait for the pull request. But, at least: now waits for the first part of a post-ID gap before writing data, and attempts partially to handle appearance of the index hole during writing a track. More work to do on that though.
2017-08-15 16:05:10 -04:00
Thomas Harte
9541a2a5f0 Corrections: seek_to now takes the segment_start_time into account, correcting a windowing error where segments overlay other segments. Also added some asserts while bug hunting, and corrected the steps taken when inserting a longer-than-a-track segment so that each is correctly windowed. 2017-08-15 15:54:09 -04:00
Thomas Harte
944222eba4 Added: write_id_data_joiner can now be instructed not to write the first portion of gap. Which makes more sense as an option, to avoiding splicing errors. 2017-08-15 15:29:23 -04:00
Thomas Harte
9d77f33611 Dealt with another source of repeating magic constants: the command numbers. 2017-08-15 11:06:10 -04:00
Thomas Harte
7d132f81f7 Increased logging by quite a distance and made an attempt once again to allow the processor some time to supply the first byte when writing before declaring overrun. 2017-08-15 10:50:28 -04:00
Thomas Harte
0972f19fc5 Merge pull request #196 from TomHarte/Minor6845
Corrects 8272 multi-sector reads and deleted data reads, and ensures that a zero-height 6845 display shows nothing
2017-08-14 22:31:24 -04:00
Thomas Harte
6553bf05b4 Corrected multi-sector reads: ensured the incremented sector number isn't replaced by the original, and that the controller returns to scanning mode. 2017-08-14 22:27:31 -04:00
Thomas Harte
0816d3f5a9 Corrected 'read deleted data' command. It's 0xc, not 0xb. 2017-08-14 21:41:20 -04:00
Thomas Harte
55055c7847 Minor: ensured immediate line comparison works. But I think my problem might be trying to do this as straight line logic? 2017-08-14 19:08:20 -04:00
Thomas Harte
113da93796 Merge pull request #195 from TomHarte/8272Tidying
Brings the 1770 into usage of those parts factored out of it for the 8272
2017-08-14 16:34:15 -04:00
Thomas Harte
cddcd0fb79 Put my money where my mouth is and switched the superclass of WD1770 to MFMController, eliminating duplicated (/factored out) code. 2017-08-14 16:32:53 -04:00
Thomas Harte
a366298022 Factored out the standard [M]FM gap and mark groups, to increase 8272 readability and because it's pretty-much certain I'll need them again if ever I try to tackle e.g. the 8271. 2017-08-14 16:03:35 -04:00
Thomas Harte
4df9307d25 Factored out the dull and repetitious stuff of writing n bytes of the same value. 2017-08-14 15:50:36 -04:00
Thomas Harte
d7bed958b3 Merge pull request #194 from TomHarte/8272Write
Introduces initial implementations of the 8272's write data, write deleted data, read track and format track
2017-08-14 14:35:45 -04:00
Thomas Harte
9038ba622e Added a quick version of read track. 2017-08-14 14:34:56 -04:00
Thomas Harte
7b8bb0297a Implemented single density version of format track. 2017-08-14 13:03:17 -04:00
Thomas Harte
0da02d3902 Added read/write escape clauses if faced with a read-only disk. 2017-08-14 12:53:18 -04:00
Thomas Harte
334872d374 Clarified, slightly. 2017-08-14 12:47:11 -04:00
Thomas Harte
2e5ad19fe1 Minor tidying. 2017-08-14 12:42:48 -04:00
Thomas Harte
a10389a22c Factored out the stuff of stuffing the bus. 2017-08-14 12:42:22 -04:00
Thomas Harte
cefec7a19f Sought more robustly (i.e. less repetitively) to handle dispatch, including cancelling seeks where appropriate. 2017-08-14 10:37:39 -04:00
Thomas Harte
7264fbb3d2 read_id now clears status. I probably need to find a way to generalise this. 2017-08-14 09:58:55 -04:00
Thomas Harte
0e083e9dc6 Factored composition of a run command out, as I think I need to worry about extensions, and can trim spaces. 2017-08-14 09:48:56 -04:00
Thomas Harte
8a7b23dc9e Ensured data-accessing commands cancel seeks on their drives. Also introduced a count of drives currently seeking in order to make for a slightly better broad-phase test in run_for. 2017-08-14 09:45:39 -04:00
Thomas Harte
b7065575f3 Added (empty) call-ins for DMA usage; switched to having the 'is seeking' bit in the status register stay high until sense interrupt status, but now it goes high even for seeks that don't actually go anywhere, and corrected interpretation of the specify command, with a positive result: the received step rate time, now that it's being interpreted correctly, is much shorter. 2017-08-14 09:04:22 -04:00
Thomas Harte
7ea703f150 Started making provisions for a DMA-compatible implementation. Re: the CPC, it sounds like DMA acknowledge might be permanently wired, causing DMA mode seemingly to work from the 8272's point of view. 2017-08-14 08:38:00 -04:00
Thomas Harte
ea64125124 Added an explicit nilling, to help with debugging. 2017-08-13 22:15:25 -04:00
Thomas Harte
1011143dbe Sought to correct my interpretation of 'gap 3'. 2017-08-13 21:52:48 -04:00
Thomas Harte
9ace6e1f71 Applied minimum constraints for specified parameters. 2017-08-13 19:25:57 -04:00
Thomas Harte
750f2cb883 Flagged as not read-only, at least for now, to allow 8272 writing tests definitively to function. 2017-08-13 18:54:39 -04:00
Thomas Harte
5221837be8 Fixed Non-DMA flag for the format track execution phase. The emulated machine now provides sector details. 2017-08-13 18:51:06 -04:00
Thomas Harte
1576b4500b Added documentation. 2017-08-13 18:27:00 -04:00
Thomas Harte
e1e9a06712 Made an attempt at format a track. 2017-08-13 18:05:19 -04:00
Thomas Harte
6e36f8ffa4 Removed index-hole announcement. 2017-08-13 12:50:24 -04:00
Thomas Harte
b0a7208cc7 Strung together a very basic version of 8272 write [/deleted] data. Lots of cases as-yet unhandled. 2017-08-13 12:50:07 -04:00
Thomas Harte
eec42aa7ae Entrusted further status to drives; also adjusted them to report read only if diskless, which I now believe to be correct. 2017-08-13 11:50:49 -04:00
Thomas Harte
6d2e969e7d Merge pull request #193 from TomHarte/8272Style
Improves 8272 implementation style
2017-08-12 18:05:27 -04:00
Thomas Harte
5f42022c1d Added a tester for the control mark. 2017-08-12 17:35:14 -04:00
Thomas Harte
11d0c37506 Attempted to find a more expressive way for maintaining state — macros for all conditions, to bind both values and destinations. 2017-08-12 17:33:52 -04:00
Thomas Harte
58bad1e2a3 Merge branch 'PerDriveStatus' 2017-08-12 16:49:38 -04:00
Thomas Harte
27d1dc5c37 Removed some old printfs. 2017-08-12 16:49:20 -04:00
Thomas Harte
e7345c7a20 Merge pull request #192 from TomHarte/PerDriveStatus
Expands 8272 emulation further
2017-08-12 16:49:06 -04:00
Thomas Harte
186048a88e Made an attempt to fix the condition for setting a broken header CRC. 2017-08-12 16:39:32 -04:00
Thomas Harte
7135259cc1 Sought to flesh out error conditions. 2017-08-12 16:36:37 -04:00
Thomas Harte
4909325e79 Implemented read deleted data. 2017-08-12 13:01:17 -04:00
Thomas Harte
a4ee697ed1 Quickie: head unload is scheduled only if the head is presently loaded. 2017-08-12 12:53:45 -04:00
Thomas Harte
0f15a2f97f Relented: it actually looks like status bytes aren't per-drive. But each drive may fail at seeking individually. So that piece of state accumulates at the 8272 drive. 2017-08-12 12:52:36 -04:00
Thomas Harte
89ace671a4 Corrected unload time. Was 8000 times too short. 2017-08-12 09:44:01 -04:00
Thomas Harte
e7db2a2f6d Sought to introduce head loading and unloading delays. 2017-08-12 09:36:21 -04:00
Thomas Harte
8c33ac71ee Merge pull request #191 from TomHarte/Precache
Introduces more aggressive caching of sectors in the MFM decoder, improves CPC static analysis further
2017-08-12 09:10:29 -04:00
Thomas Harte
69914faf02 Fixed comments. 2017-08-11 20:22:14 -04:00
Thomas Harte
daafebe7ac Moved curly bracket. 2017-08-11 19:19:04 -04:00
Thomas Harte
2d81acb82e Upped C++ standard to C++14 and added an #if that's intended to use the built-in std::gcd when compiled on C++17 or better. Fixed for new signedness warnings resulting for taking the step to C++14. 2017-08-11 19:18:45 -04:00
Thomas Harte
82ca49c840 Adjusted to avoid calls to ::greatest_common_divisor(numerator % denominator, denominator) unless necessary. 2017-08-11 19:05:46 -04:00
Thomas Harte
bfe297052d Picked up another subtlety: disk names may be outside of the ones a user could type, in which case they definitely don't affect the decision. 2017-08-11 18:59:38 -04:00
Thomas Harte
ffb1a14ace Minor: clear status registers before a read data. 2017-08-11 18:56:33 -04:00
Thomas Harte
7e35e44934 Added an extra sanity check on treating system disks as system disks. 2017-08-11 18:46:39 -04:00
Thomas Harte
0c8769e335 Just to be safe. 2017-08-11 18:41:08 -04:00
Thomas Harte
83c7d34df2 Switched to populating the sector cache with everything in a track the first time anything on that track is requested. That avoids the problem whereby each request of a non-existent sector costs two spins. 2017-08-11 18:40:16 -04:00
Thomas Harte
ad3c9842d7 Merge pull request #190 from TomHarte/SingleImplicit
Corrects a couple of CPC static analysis pitfalls
2017-08-11 16:41:03 -04:00
Thomas Harte
44dace2eef Made an attempt not to interrogate files that definitely don't have the normal header. 2017-08-11 16:34:29 -04:00
Thomas Harte
a12671010a Sector size is now reported, and CRC failures are merely indicated, not cause for a sector to be thrown away. 2017-08-11 16:23:33 -04:00
Thomas Harte
23c149368b Broadened CPC data disk analysis to spot when there is only one implicitly-runnable file, rather than only one without suffix. 2017-08-11 16:23:00 -04:00
Thomas Harte
09716d4716 Merge pull request #189 from TomHarte/CPCSystemDisks
Corrects analysis of CPC system disks
2017-08-11 15:56:35 -04:00
Thomas Harte
4b7c504d22 Corrects analysis of system disks — they have a catalogue that is correct read, but can be launched without reference to it. 2017-08-11 15:55:33 -04:00
Thomas Harte
1e4f9d4eda Merge pull request #188 from TomHarte/AYFidelity
Switches to guessing that the AY doesn't reset its dividers upon frequency changes
2017-08-11 14:50:11 -04:00
Thomas Harte
e4f04d0977 Merge branch 'master' into AYFidelity 2017-08-11 14:41:08 -04:00
Thomas Harte
0f75525640 Merge pull request #187 from TomHarte/CRCErrors
Extends the built-in [M]FM encoder to be able to produce incorrect CRC values, and adjusts the CPC .DSK handler to request them when appropriate
2017-08-11 14:34:31 -04:00
Thomas Harte
edb088526f Simplified slightly, and updated TODO as to still-missing functionality. 2017-08-11 14:33:34 -04:00
Thomas Harte
80ebc63101 Updated the SSD file format container to specify sector sizes, now that it's no longer implicit. 2017-08-11 14:30:35 -04:00
Thomas Harte
cf1403bc79 Increased documentation. 2017-08-11 14:27:07 -04:00
Thomas Harte
fcf63a7547 Expands the [M]FM encoder to respect some new Sector flags: it will now wilfully make CRC errors, omit data, include data that is different than the ID's declared length, write deleted data, and can be commanded as to header/data gaps and what should be within them. All based around expanding towards the needs for reproduction of the CPC's .DSK file format. 2017-08-11 14:24:50 -04:00
Thomas Harte
9c0b75faba Merge pull request #186 from TomHarte/CPCTyper
Introduces typer support for the Amstrad CPC.
2017-08-11 12:35:43 -04:00
Thomas Harte
1f2bfc9581 Ensured tape loading really begins. 2017-08-11 12:30:36 -04:00
Thomas Harte
14ab03d1e0 Added a further fallback: if all files have an extension but one doesn't, take that one. 2017-08-11 12:27:50 -04:00
Thomas Harte
3831fbaca2 Ensured the ZX80 and '81 also provide the necessary hook for destruction. 2017-08-11 12:11:01 -04:00
Thomas Harte
1d8edf58dd Ensured that a virtual destructor is declared, so that the various automatically-generated real constructors get in on the action. 2017-08-11 12:07:48 -04:00
Thomas Harte
4785e316ff Now with exposition. 2017-08-11 11:36:03 -04:00
Thomas Harte
44da9de5b0 Tweaked typing timing expectations. 2017-08-11 11:35:28 -04:00
Thomas Harte
4ecd093891 Fixed test for termination of a key sequence; the previous error will have seen this reduce all multi-key sequences to just the one, and expand single-key sequences to "probably" two, posting an out-of-bounds code to the machine at completion. 2017-08-11 11:35:14 -04:00
Thomas Harte
dd4bc87d52 Fixed: should be a full-path #ifdef guard, given that this is one of the classes named relative to its namespace. 2017-08-11 11:21:33 -04:00
Thomas Harte
570d25214e Made an initial attempt at typer support for the CPC. 2017-08-11 11:21:07 -04:00
Thomas Harte
f0b7e58968 Merge pull request #185 from TomHarte/CPMCatalogue
Introduces a CP/M catalogue parser, as a basic for the CPC's static analyser
2017-08-11 11:00:49 -04:00
Thomas Harte
0411b51582 Added an attempt to deal with 16-bit allocation units, and to ensure middle-of-file holes are respected. 2017-08-11 10:59:37 -04:00
Thomas Harte
dea782cff9 Added a "yeah, I don't know" fallback. 2017-08-11 10:47:45 -04:00
Thomas Harte
388dd99762 Advanced this just enough to suggest a loading command for most things. 2017-08-11 10:47:12 -04:00
Thomas Harte
026101a268 Killed logic_extents_per_physical, since I don't know how to handle it, and instituted tracks, to allow a decision about short versus long allocation units. 2017-08-11 10:46:50 -04:00
Thomas Harte
734099a956 Threw a sector cache into my MFM parser, in an attempt to cut down analysis costs. Also made it aware of multiple heads. 2017-08-11 10:29:13 -04:00
Thomas Harte
6be5851484 Cleaned up. 2017-08-10 22:34:29 -04:00
Thomas Harte
994179f188 Taking a whole bunch of guesses, this might be correct. 2017-08-10 22:33:08 -04:00
Thomas Harte
6a65c7a52a Started working on a CPC-oriented analyser; for now I just want to be able to make a good guess at the appropriate file to load from a disk. As it turns out, the CPC simply adopts the CP/M format, so a generic parser is appropriate. This is its beginning. 2017-08-10 17:10:21 -04:00
Thomas Harte
0d2d3ea17c Merge branch 'master' into DeferredVideo 2017-08-10 16:01:02 -04:00
Thomas Harte
5d374ebb18 Merge pull request #184 from TomHarte/DeferredVideo
Reduces CRTC counter sizes to match real hardware; introduces a CRT gamma transform
2017-08-10 16:00:39 -04:00
Thomas Harte
62eadbb51a Adjusted gamma ratio to be the correct way around. The PAL midrange should be slightly darker now. 2017-08-10 15:36:27 -04:00
Thomas Harte
ad8c8166bc Built in gamma conversion for all machines, assuming an output of 2.8 for PAL, 2.2 for NTSC. 2017-08-10 15:17:08 -04:00
Thomas Harte
a5593bec79 Threw in support for the light-pen trigger. 2017-08-10 15:00:14 -04:00
Thomas Harte
a1e2646301 Imposed counter size limits. 2017-08-10 14:58:24 -04:00
Thomas Harte
cf810d8357 Minor: ensure the CRT is set to output as a monitor. 2017-08-10 14:42:47 -04:00
Thomas Harte
f258d6fbb2 Merge pull request #183 from TomHarte/6845Address
Corrects 6845 address loading
2017-08-10 12:51:40 -04:00
Thomas Harte
4961fda2a9 Ensured counter-intuitive CRTC writes get through, taking the opportunity to correct my handling of port IO in general: selecting multiple devices for input results in a logical AND (i.e. open collector mode), and both the CRTC and gate array will receive data from 'input's if applicable. 2017-08-10 12:39:19 -04:00
Thomas Harte
6a6e5ae79c Forced users of the 6845 to be explicit about which type. So far with no effect. 2017-08-10 12:28:57 -04:00
Thomas Harte
02d792c003 Simplified logic slightly, avoiding repetition. 2017-08-10 11:48:37 -04:00
Thomas Harte
be8e7a4144 Eliminated false register aliasing, restricted register sizes and locked out reading and writing where appropriate. 2017-08-10 11:22:30 -04:00
Thomas Harte
b1dbd7833a Merge branch 'master' into 6845Address 2017-08-10 11:15:08 -04:00
Thomas Harte
84ab05c5ef Merge branch 'BetterFDC' 2017-08-10 11:14:53 -04:00
Thomas Harte
78138261c2 Merge pull request #182 from TomHarte/BetterFDC
Continues building up the 8272
2017-08-10 11:14:08 -04:00
Thomas Harte
a4c910f1de This appears to be a more accurate take on 6845 address advancement — it is necessary that character output has finished for the line address to be updated. 2017-08-10 11:12:53 -04:00
Thomas Harte
2eed24e859 Made an initial attempt at [a subset of] multi-sector reads. 2017-08-10 11:11:26 -04:00
Thomas Harte
7d1023ea98 Added a 'ready' getter to Drive, formally to let the drive take ownership of that test. 2017-08-08 21:15:56 -04:00
Thomas Harte
b11d142cff Switched to descriptive names. 2017-08-08 20:35:41 -04:00
Thomas Harte
021ff8674e Added something for sense drive status. 2017-08-08 20:30:54 -04:00
Thomas Harte
1b86bc21ab Might as well get official on my ongoing efforts at CPC emulation. 2017-08-08 17:58:54 -04:00
Thomas Harte
e3d1f4fe1e Subjectively, this might be more correct. It definitely prevents intermediate frequencies. More research required. 2017-08-08 17:58:35 -04:00
Thomas Harte
a7452aebff Merge pull request #181 from TomHarte/6128
Introduces support for 128kb CPCs.
2017-08-08 16:06:31 -04:00
Thomas Harte
484524d781 Implements RAM paging. The 6128 is now emulated. 2017-08-08 16:01:56 -04:00
Thomas Harte
dbf9927caf Merge branch '8255Modes' 2017-08-08 07:44:56 -04:00
Thomas Harte
3bdedfd749 Improved comments. 2017-08-08 07:44:46 -04:00
Thomas Harte
005084af3d Merge pull request #180 from TomHarte/6845FrameLength
Corrects an off-by-one error in [character] line counting within the 6845
2017-08-08 07:37:48 -04:00
Thomas Harte
46278ff297 Experimental: is this meant to be a compare-before-increment? 2017-08-07 23:02:29 -04:00
Thomas Harte
d73dcbb268 Merge pull request #179 from TomHarte/AYDirection
Makes various AY input/output fixes, and a few 8255 fixes along the way
2017-08-07 19:58:39 -04:00
Thomas Harte
390ecec3d9 Added: now declines to pass on output if in input mode for ports A and B. 2017-08-07 19:56:22 -04:00
Thomas Harte
41a30c147d Adjusted: invalid register selection simply deselects all registers. 2017-08-07 19:51:36 -04:00
Thomas Harte
4709ae80cb Added port direction tests. 2017-08-07 19:36:55 -04:00
Thomas Harte
7fbb455836 Per the CPC test I'm checking, 0s should be returned for non-retained bits, not 1s. 2017-08-07 19:07:12 -04:00
Thomas Harte
745afd217f The port input/output flags are now honoured; reading a port that is set as an output returns the current output value. 2017-08-07 19:01:18 -04:00
Thomas Harte
4427e9a254 Merge pull request #178 from TomHarte/CPC664
Introduces the beginning seeds 8272 emulation, and therefore floppy emulation for the CPC
2017-08-07 18:39:33 -04:00
Thomas Harte
2b0dcf8573 Transcribed the status bits that I think actually need to be obeyed. 2017-08-07 12:37:45 -04:00
Thomas Harte
47732ffb98 Prevented the 8272 from overreading ID fields (and, by doing so, overrunning its internal buffer). Exposed the MFMController's CRC generator for inspection. 2017-08-07 12:37:22 -04:00
Thomas Harte
d07f3216ab Added a broad phase on whether seeking is ongoing. 2017-08-07 12:12:59 -04:00
Thomas Harte
56d65ba6f3 Adapted slightly, to retain the ability to advertise an incorrect size, to adjust the confusion I've created by having two different types of thing both called new_sector, and to print a warning when ignoring error flags. 2017-08-07 12:12:04 -04:00
Thomas Harte
895a3cbf24 Corrected reading of the track size table for extended disks. My first extended disk has now loaded. 2017-08-07 11:38:19 -04:00
Thomas Harte
d951c8c1c2 Fixed search for track start position with extended disks: it's no longer an infinite loop. So that's a pretty good performance improvement. 2017-08-07 11:36:29 -04:00
Thomas Harte
a294963e98 Made an absolutely basic attempt to accommodate some extended disk images. 2017-08-07 11:26:15 -04:00
Thomas Harte
68c73184b1 Had failed to spot that by taking control of stepping at this level, the appropriate invalidate_tracks were not being sent. 2017-08-07 10:36:53 -04:00
Thomas Harte
7f824d6494 Ensured seeks and recalibrates end immediately if no seeking is required. 2017-08-07 10:31:32 -04:00
Thomas Harte
3219212f03 A closer inspection of the data sheet seems to suggest that invalid command sequences will post ST0. 2017-08-07 07:35:41 -04:00
Thomas Harte
d90e35e5bd Added a bunch of comments, and ensured that the data request bit remains set for the entire period that command bytes are accepted. 2017-08-07 07:27:00 -04:00
Thomas Harte
73f8488150 Reaching the end of the usable part of my day, decided to tidy up a little before bed with indentation that reflects a distinction between top-level entry points and mere loops. 2017-08-06 22:14:18 -04:00
Thomas Harte
3853966a1e Removed formal storage of ST3, as it just seems to be composed live. This may turn out also to be the best way to deal with ST0–2, time will tell. Also took a stab at the error in responding properly to the ROM's intended use of seek might be accepting new commands as replacements for old ones rather than rejecting them. That didn't seem to do the trick. 2017-08-06 22:10:12 -04:00
Thomas Harte
d63893a437 Collapsed implementations of recalibrate and seek, and decided to intend to go for an upward count on steps taken rather than a downward one. But seek continues presently to fail. 2017-08-06 21:52:52 -04:00
Thomas Harte
90c74043f5 Remembered to toggle off RQM between bytes. CAT now works. 2017-08-06 21:21:59 -04:00
Thomas Harte
600445d90a Made a first attempt to return sector contents. 2017-08-06 20:40:29 -04:00
Thomas Harte
e4b405fd3d With the ROM now using a read ID to set its expectations, implemented that and fixed FIND/READ_HEADER macros for multiple use. Execution now reaches the unimplemented section of read data. 2017-08-06 20:32:46 -04:00
Thomas Harte
3b7ecbdf0d Renamed result_ to result_stack_ to emphasis the fact that it goes backwards. Switched meaning of CB so that it is set for the entire command, execution and result phases. 2017-08-06 20:17:12 -04:00
Thomas Harte
01efb645cb Took a reasonable gamble that the CHRN reported is from internal registers, not from the last-found header. 2017-08-06 19:57:34 -04:00
Thomas Harte
b5ec1f42d5 Started resetting 'busy' when entering the result phase. AMSDOS now complains of a missing disk after failing to find sector 01. My belief is that it should end up asking for C1. So this is not even getting through a failure to find a sector correctly yet. 2017-08-06 19:48:17 -04:00
Thomas Harte
c839556a27 Fixed: rewind the file to check for 'EXTENDED' if 'MV - CPC' failed. 2017-08-06 19:47:10 -04:00
Thomas Harte
e9972aa0dd Added respect for the index-hole limit on reading, and an error phase. 2017-08-06 19:25:44 -04:00
Thomas Harte
1c9a744b01 Made an effort to start inspecting ID fields, at least. Discovered that my emulation has somehow stopped proceeding beyond sense interrupt status though. Fix one in that area: adjust ST0 just in time for the sense interrupt status response, as that'll need to specify the drive number properly. 2017-08-06 18:06:20 -04:00
Thomas Harte
e6d4bb29d8 Discovered correct sense interrupt status result if nobody is in the completed seeking state, and switched to it. It's a single 0x80 rather than two bytes. 2017-08-06 15:34:33 -04:00
Thomas Harte
6c5b562d97 Made an attempt at some of the correct seek/recalibrate behaviour: it's now asynchronous from command processing and able to work on up to four drives at once. I just probably am not yet hitting all the status flags I need to hit. 2017-08-06 15:22:07 -04:00
Thomas Harte
a7103f9333 Disks are now communicated to the 8272. Which is able to handle four of them. 2017-08-06 13:24:14 -04:00
Thomas Harte
c12425e141 Added storage for the extended four status registers, and made an attempt at implementing the two most trivial result-phase commands. Am slightly paused momentarily trying to figure out whether seek activity is orthogonal to read/write activity. 2017-08-06 12:55:57 -04:00
Thomas Harte
89f6de1383 Started on a real ugly-implementation coroutine approach, and implemented specify as a fairly trivial first command: it has no result phase, and is the only thing called by AMSDOS as part of the initialisation sequence. 2017-08-06 12:36:18 -04:00
Thomas Harte
77da582e88 Switched the container in which events are passed to int, with the intention of subclasses extending the receivable range. 2017-08-06 12:35:20 -04:00
Thomas Harte
34eaf75352 Fixed WAIT_FOR_TIME macro. 2017-08-06 12:08:54 -04:00
Thomas Harte
ffadb04761 Documented, and removed a couple of Event types that are WD-specific but had accidentally flown into here. Will need to figure out how best to expose the CRC result too, but I'm willing to let that one drop out naturally as I implement the 8272. 2017-08-06 11:36:36 -04:00
Thomas Harte
29288b690e Switched disk controllers to be instantiated explicitly in terms of cycles, created an Amstrad-specific subclass of the 8272 to record the direct programmatic availability of all disk motors bundled together, and otherwise adjusted to ensure the thing is clocked and that the motor is enabled and disabled appropriately. The 8272 is also now formally a subclass of the incoming MDM controller. 2017-08-06 09:45:16 -04:00
Thomas Harte
25fd3f7e50 Mildly increased work in here, still primarily oriented towards logging what I actually need to get done. 2017-08-05 22:26:59 -04:00
Thomas Harte
4d60b8801c Started trying to factor out just the PLL stream -> FM/MFM events part that is presently in the WD1770. 2017-08-05 22:26:15 -04:00
Thomas Harte
3e984e75b6 Strung up an empty shell that eventually should contain the 8272, and added appropriate IO decoding to the Amstrad. 2017-08-05 19:45:52 -04:00
Thomas Harte
9e8645ca7a Fixed ROM paging port decoding. It should have been fd00 if completely decoded, not df00, but also shouldn't be completely decoded. 2017-08-05 19:24:03 -04:00
Thomas Harte
caf3ac0645 Sought: (i) to instruct the CPC that it should be a 664, not a 464, if given a disk image (at least until I have RAM paging implemented for a 6128); (ii) to support ROM selection within the CPC and allow paging in of AMSDOS. 2017-08-05 19:20:38 -04:00
Thomas Harte
0f4343cd84 Merge pull request #177 from TomHarte/DSKFileFormat
Introduces parsing of the file format support for the simplest of CPC disks
2017-08-05 11:55:13 -04:00
Thomas Harte
192f232d3f Silenced warnings. 2017-08-05 11:53:29 -04:00
Thomas Harte
6e4d3b8a77 Added enough logic to produce some sort of version of a completely unprotected DSK. So enough to start bootstrapping an FDC emulation, at least. 2017-08-05 11:44:53 -04:00
Thomas Harte
8eda24261c Removed unnecessary header — it's implied by being a child of FileHolder. 2017-08-05 11:44:06 -04:00
Thomas Harte
75c59fefab Added an empty husk to begin support for Amstrad CPC disk image formats. 2017-08-05 10:02:10 -04:00
Thomas Harte
4b19cf60df Added omitted semicolon. 2017-08-05 09:18:55 -04:00
Thomas Harte
2b476554e7 Merge pull request #176 from TomHarte/AYDeferrer
Corrects the CPC's handling of AY time accumulation
2017-08-05 09:13:47 -04:00
Thomas Harte
b3788fed41 Fixed AY queuing behaviour as handled by the Amstrad. I think I need to come up with clearer semantics here. 2017-08-05 09:12:17 -04:00
Thomas Harte
b999c1d8aa Merge branch 'FilteredSync' 2017-08-04 16:52:14 -04:00
Thomas Harte
a63aa80dc9 Merge branch 'master' of github.com:TomHarte/CLK 2017-08-04 16:51:52 -04:00
Thomas Harte
63f57c8c4f Adjusted visible portion of frame; completely empirical, as I'm chasing a machine that shipped with a monitor. 2017-08-04 16:51:46 -04:00
Thomas Harte
7e6a6365c9 Adjusted visible portion of frame; completely empirical, as I'm chasing a machine that shipped with a monitor. 2017-08-04 16:50:58 -04:00
Thomas Harte
3dbf26ac49 Merge pull request #175 from TomHarte/FilteredSync
Introduces filtering of the CRTC's hsync signal into the gate array.
2017-08-04 16:38:52 -04:00
Thomas Harte
f075fea78c Introduces filtering of the CRTC's vsync signal into the gate array. 2017-08-04 16:36:55 -04:00
Thomas Harte
cc8380c286 Merge pull request #174 from TomHarte/ProperBorder
Removes quick-hack solution for border drawing
2017-08-04 12:15:22 -04:00
Thomas Harte
c0f0c68f4f Corrects quick-hack version of border drawing: the assumption that the colour must be the same over a plotted period. Also corrects my entry for colour 15. 2017-08-04 12:13:05 -04:00
Thomas Harte
c2bb019381 Merge pull request #173 from TomHarte/TimingFixes
Corrects two misimplementations of the CPC's interrupt counter
2017-08-04 09:06:42 -04:00
Thomas Harte
26ce6cdab2 Permitted register 3 to dictate vertical sync length. 2017-08-04 08:56:36 -04:00
Thomas Harte
d9097facf1 Found documentation that makes more sense, and in practice seems to be more correct: the test after vertical sync is for greater than 32, not less. Also I decided to chance my arm on counter reset also resetting interrupt request. The raster effects of Ghouls 'n' Ghosts is now pretty much correct but one line off. I think probably either something is off in my wait-two logic on the post-vsync timer event, or possibly the vsync bit exposed via the PPI doesn't mean exactly what I think it means. 2017-08-04 08:56:09 -04:00
Thomas Harte
b927500487 Clarified code a little, but this is mostly fiddling in the margins. 2017-08-03 22:00:30 -04:00
Thomas Harte
e71eabedf9 Fixed timer clearing tet. 2017-08-03 21:30:04 -04:00
Thomas Harte
33ed27c3ad Minor tidiness: included missing headers, and spaced out the ROM type and key lists for readability. 2017-08-03 12:45:42 -04:00
Thomas Harte
45cbab6751 Merge pull request #172 from TomHarte/KeyboardMachines
Publicly declares the ZX80/81 and Amstrad CPC as keyboard machines
2017-08-03 12:39:22 -04:00
Thomas Harte
a7b74d6164 Merge pull request #171 from TomHarte/EverMoreTZX
Further expands the implemented subset of the TZX file format
2017-08-03 12:38:44 -04:00
Thomas Harte
575b1dba75 Formally declared the ZX80/81 and Amstrad CPC as keyboard machines in their public interface. Which means not having to repeat the meaning of set_key_state and clear_all_keys. So: a minor DRY improvement. 2017-08-03 12:38:22 -04:00
Thomas Harte
a3b16b6dfa Further beefs up the list of chunks that this TZX parser can either comprehend or skip. 2017-08-03 12:13:41 -04:00
Thomas Harte
c8f4de6f11 Merge pull request #170 from TomHarte/KeyboardCraziness
Separates knowing the mapping from ASCII to machine keys from the act of typing them
2017-08-03 11:53:42 -04:00
Thomas Harte
bbb17acf3a Expanded interface so that an external machine caller can request a string be typed without any knowledge of whatever it intends to do re: CharacterMappers. Which is immediately useful in paste functionality. 2017-08-03 11:50:50 -04:00
Thomas Harte
ad3a98387f Within the Typer framework: hatched out CharacterMapper as a distinct thing from the target for keypresses, better to formalise responsibility but also to make it easy cleanly to sever that stuff into its own little part. 2017-08-03 11:42:31 -04:00
Thomas Harte
985fbf59c2 Merge pull request #169 from TomHarte/Z80HasA
Converts the Z80 into a BusAdaptor-type component
2017-08-02 22:42:49 -04:00
Thomas Harte
2f2071be8a These should actually both be in the public header, as the types are used in an exposed method. 2017-08-02 22:18:30 -04:00
Thomas Harte
6d510e4e70 Made it no longer public knowledge that any sort of Typer is involved in being a ZX80/81. 2017-08-02 22:17:22 -04:00
Thomas Harte
8e0736fbe6 Reinstated typing ability, albeit with an ugly inline insertion. But I think I can defer dealing with typers to another pull request. The whole issue of keyboard mapping probably needs reappraisal. 2017-08-02 22:16:09 -04:00
Thomas Harte
681d1e2f8d Breaking its typer for now, adapted the ZX80/81 to having a Z80, not being one. 2017-08-02 22:12:59 -04:00
Thomas Harte
42e70ef993 Adjusted slightly as per Z80 change, and to pull everything internally declared into the Amstrad CPC namespace. 2017-08-02 22:11:03 -04:00
Thomas Harte
039811ce6a Switched the Z80 to being something a machine has, not something a machine is. 2017-08-02 22:09:59 -04:00
Thomas Harte
a54ccd1969 Merge pull request #168 from TomHarte/CPC
Adds an initial Amstrad CPC 464 emuation
2017-08-02 20:50:02 -04:00
Thomas Harte
707821ca55 Added the normal readme to explain what's omitted here. 2017-08-02 20:45:14 -04:00
Thomas Harte
d3bf8fa53b Upped the documentation. 2017-08-02 20:37:26 -04:00
Thomas Harte
f5e2dd410e Constrained output to the centre 90%. 2017-08-02 19:55:44 -04:00
Thomas Harte
3ca9c38777 Attempted to move to more accurate bus reading — if control lines are set then all subsequent data inputs should act according to the current control lines; changes to port input should be reflected live upon readings, etc. 2017-08-02 19:45:58 -04:00
Thomas Harte
2d2cefb0b0 Adjusted factoring to introduce support for block 10. 2017-08-02 14:36:47 -04:00
Thomas Harte
2fd071e45d Made an honest attempt at outputting turbo speed data block data. The CPC now at least starts to load. 2017-08-02 14:24:34 -04:00
Thomas Harte
d7a5c3f49a Added support for the ID 20 block and fixed a minor error in my skip-the-contents version of block 11: length is three bytes long, not two. This gives me enough structure properly to get to the end of my current test CDT, albeit without making any of the noises. 2017-08-02 14:12:34 -04:00
Thomas Harte
819761f9fb Fixed another uninitialised pointer. 2017-08-02 13:56:35 -04:00
Thomas Harte
e50adf1cc8 Were my TZX support up to it, this would likely be sufficient for tape emulation. 2017-08-02 13:50:14 -04:00
Thomas Harte
dcab10f53e Ensured the AY's async queue doesn't just fill and fill. 2017-08-02 07:38:35 -04:00
Thomas Harte
633d8965e2 Removed accidental logging commit. 2017-08-02 07:38:14 -04:00
Thomas Harte
f602f9b6ec Adds an attempt to clock the AY. 2017-08-02 07:21:33 -04:00
Thomas Harte
f7e66dea61 Added a compound divide and convert. 2017-08-02 07:21:21 -04:00
Thomas Harte
bda9441620 Made an attempt to clock the AY. 2017-08-02 07:20:59 -04:00
Thomas Harte
4d5d5041df Attempted to ensure a clean startup. 2017-08-01 22:18:42 -04:00
Thomas Harte
587eb3a67c Factored interrupt counting out of the CRTCBusHandler. 2017-08-01 22:15:39 -04:00
Thomas Harte
6ca07f1e28 I guess it might end up living somewhere else, but introduced a header with the compiler-specific stuff to allow me to force things inline. 2017-08-01 22:04:58 -04:00
Thomas Harte
8d39a20088 Added proper output of mode 3, were anything ever to try to use it. 2017-08-01 21:51:41 -04:00
Thomas Harte
4b6370eb86 Realised my colour error: mapping the ROM numbers as though they were the hardware numbers. Having fixed that, spotted that I was deserialising R and B the wrong way around and dividing by too much. Colours now appear to be correct. 2017-08-01 21:47:52 -04:00
Thomas Harte
c6e340a8a2 Wired up the vsync signal. Pen 15 no longer flashes like crazy. Still can't figure out why the palette is so askew; was looking for perhaps some sort of detection of a green screen rather than a colour one, but there's no obvious input for that. 2017-08-01 21:21:59 -04:00
Thomas Harte
31c7153301 Corrected bit to colour mapping for modes 0 and 1. The total palette is still way off but there's consistency between modes now. 2017-08-01 20:52:42 -04:00
Thomas Harte
7e04d00cc1 Fixed key values, causing the new set of keys to work, decreased quantity of output and ensured that pixels appear in modes 0 and 2. 2017-08-01 20:39:10 -04:00
Thomas Harte
9d43784c65 Significantly increased quantity of keys forwarded. 2017-08-01 20:37:55 -04:00
Thomas Harte
eca9586a0f Fixed: input value is no longer overwritten by 0xff. The '0' key now works. 2017-08-01 20:19:02 -04:00
Thomas Harte
0267bc237f Added the ability to set a port input, and relaxed bus state testing. I think my on-demand bus reactions here are inappropriate, so more work to do here probably. 2017-08-01 18:04:51 -04:00
Thomas Harte
2e4577f741 Made a game attempt at implementing a (sticky) keyboard. No effect yet. 2017-08-01 17:52:05 -04:00
Thomas Harte
f5b278d683 Added enough stuff to put the emulated Amstrad CPC in a state of knowing whether its '0' key is pressed. 2017-08-01 17:31:56 -04:00
Thomas Harte
e6854ff8db Corrected typo: the input to an AY is BDIR, not BCDIR. 2017-08-01 17:06:57 -04:00
Thomas Harte
3b292273c7 Fixed: BC2 is always implicitly set. The machine is now periodically checking the AY's register 14 (i.e. the first input port), so probably there's enough here now to implement keyboard input. 2017-08-01 17:05:11 -04:00
Thomas Harte
cb732e5d5f Made an attempt to wire in an [unclocked] AY, in an endeavour to get to keyboard reading. 2017-08-01 17:01:58 -04:00
Thomas Harte
2d4e202be3 Completed dangling comment. 2017-08-01 17:01:36 -04:00
Thomas Harte
64da8e17d1 Fixed: of course this should take a reference to an existing port handler rather than hatching its own; otherwise additional communication with a port handler by an i8255 owner doesn't work as intended. 2017-08-01 17:01:20 -04:00
Thomas Harte
08ad35efd9 It's barely an implementation of the 8255, but ensured that data is bounced into the PortHandler, conveniently assuming the interaction mode used by the CPC. 2017-08-01 16:34:13 -04:00
Thomas Harte
58b98267fc Formally transferred ownership of PIO accesses to an incoming template, and decided to start being explicit about how to specify the interfaces and provide fallbacks for optional behaviour for the new, clean generation of interfaces. A full-project sweep will inevitably occur but I'll try to tie off this branch first. 2017-08-01 16:15:19 -04:00
Thomas Harte
ace71280a0 Removed implementation file; this is only ever going to be a template. 2017-08-01 16:00:17 -04:00
Thomas Harte
a27946102a Took a shot at the interrupt counter. Attempts at keyboard reading now recur so it'll probably do for now. I think that next puts me into the realm of needing to implement the 8255. 2017-08-01 15:49:16 -04:00
Thomas Harte
1d99c116e7 Actually, this is probably more correct: increment and then compare, but increment the refresh address once more after the final character, to avoid repeating it. 2017-08-01 15:29:37 -04:00
Thomas Harte
ee27e16fb1 Switched to post-tests increment. Seems to give proper screen width, but also eliminates that 'compare to +1' step that felt unlikely. 2017-08-01 15:19:25 -04:00
Thomas Harte
6ac7132799 Had a quick go at properly outputting Mode 1, adding wiring to communicate palette and mode changes to the CRTC bus handler. Colours are off but it's sufficient for now. 2017-08-01 15:16:13 -04:00
Thomas Harte
ca42abab70 Doubled up to ensure that every byte that should be inspected is represented. This makes it clearer that I'm on the right road. A garbled version of 'Amstrad 64k Microcomputer' can be discerned, in a weird grayscale and with the right-hand column missing and skewed output as a result. 2017-08-01 07:56:44 -04:00
Thomas Harte
933d69a256 Fixed slightly: the CPC wiki has a typo. It's 12 and 13 that move up to 14 and 15. 2017-08-01 07:51:13 -04:00
Thomas Harte
3b1db14817 Made a quick attempt at properly updating the refresh address. 2017-08-01 07:36:03 -04:00
Thomas Harte
10a5581aea Made first attempt at offering some sort of pictographic of actual RAM contents. 2017-08-01 07:34:12 -04:00
Thomas Harte
3ae699964f Ensured an actual pixel stream is supplied for pixel regions. Though it's just a long stream of white pixels for now. So visual output is unchanged. 2017-08-01 07:24:29 -04:00
Thomas Harte
9d953421d8 After a quick check, added a couple of other _delegate initialisations. I should probably find a way to template this. 2017-08-01 07:07:43 -04:00
Thomas Harte
763e3b65d1 Ensured a proper initial value for delegate_. 2017-07-31 22:46:06 -04:00
Thomas Harte
42dd27c9b1 Shunted method bodies inline, given that there's no need for a declaration/definition distinction. 2017-07-31 22:39:25 -04:00
Thomas Harte
2b168f7383 Disabled the address sanitiser as an every-time run again, as it just pushes my computer a bit too far. 2017-07-31 22:32:56 -04:00
Thomas Harte
0536f089e1 Eliminated old-[personal-]fashioned line break. 2017-07-31 22:32:26 -04:00
Thomas Harte
3df13cddd4 As per my keenness for cleanliness improvements corresponding to my ever-increasing C++ ability: turned the Amstrad into something that a factory produces, allowing me completely to hide a bunch of implementation details. 2017-07-31 22:32:04 -04:00
Thomas Harte
e3f677fa37 I was under-counting row lines. Adjusted comparison. The emulator now produces a solid white square of approximately correct proportions. I'm sure that filling in pixels will reveal the next set of bugs. 2017-07-31 22:21:46 -04:00
Thomas Harte
c2253c1e0f Fixed multiplier: the dot clock I've used to instantiate the CRT is the pixel clock, not the character clock. 2017-07-31 22:17:46 -04:00
Thomas Harte
5c68b6cc21 Fixed display enable reset when there's no adjustment area. A practical lesson in failure to factor. 2017-07-31 22:16:08 -04:00
Thomas Harte
ffaa627820 Fixed frame restart when there is no adjustment period. 2017-07-31 22:13:45 -04:00
Thomas Harte
f742fd5d4a Made basic attempt to get something on screen: white where the display is enabled, black for the border. 2017-07-31 22:13:20 -04:00
Thomas Harte
69b99fe127 Transferred ownership of the CRT to the CRTC bus handler, to give it easy access. 2017-07-31 22:04:52 -04:00
Thomas Harte
5a396f6787 Added an explicit cast. 2017-07-31 22:04:31 -04:00
Thomas Harte
cb0dc7b434 I'm sure it's not going to be this easy, but this is a genuine attempt at full horizontal and vertical timing. 2017-07-31 22:01:54 -04:00
Thomas Harte
e28829bd1b Corrected CRTC timing, gave it someone to talk to and a means with which to talk. 2017-07-31 20:14:46 -04:00
Thomas Harte
68ceeab610 Created a 6845 class and started pushing data at it and clocking it. It doesn't currently have the concept of a bus but will do, hence the in-header implementation. 2017-07-31 19:56:59 -04:00
Thomas Harte
68dca9d047 Made a first attempt at ROM paging, with pretty much the same scheme that'll be needed for 128kb support. 2017-07-31 19:37:28 -04:00
Thomas Harte
d88ca151f4 Added a first attempt at output port decoding. Just logging for now. 2017-07-31 19:25:10 -04:00
Thomas Harte
3c90218c3d With a very basic stab at something a bit like the memory map (sans paging), execution begins. 2017-07-31 19:15:43 -04:00
Thomas Harte
afd409c883 Ensured that ROM images are loaded and passed to the Amstrad CPC. 2017-07-31 18:44:49 -04:00
Thomas Harte
26b6c03a2a Re-enabled the address sanitiser as a development tool. 2017-07-31 07:30:07 -04:00
Thomas Harte
9c04d851e4 Added the basics necessary to get the CPU ticking over, at a nominal 4Mhz but with the wait states that I currently believe to be accurate. 2017-07-31 07:29:50 -04:00
Thomas Harte
1d6fe11906 Added an instance of Outputs::CRT::CRT. So progress is now: select CDT, up comes a blank window. 2017-07-31 07:16:51 -04:00
Thomas Harte
c0f1313830 Performed sufficient wiring to get to the point where attempting to load a CDT creates an instance of the Amstrad CPC and then fails only because the thing vends a nullptr CRT. 2017-07-30 22:05:29 -04:00
Thomas Harte
fb51fadf00 Merge branch 'master' into CPC 2017-07-30 21:29:31 -04:00
Thomas Harte
55fd9122d0 Slightly relaxed vertical sync testing. 2017-07-30 21:19:42 -04:00
Thomas Harte
5b5720fac0 Added to the static analyser the most basic through-path for Amstrad CPC content. 2017-07-30 21:15:20 -04:00
Thomas Harte
d25d7d7d40 Added the Amstrad CPC as a named target and declared support for its CDT file format. 2017-07-29 21:56:33 -04:00
Thomas Harte
ba4f2d8917 Merge branch 'master' into VerticalSync 2017-07-29 21:45:22 -04:00
Thomas Harte
a2aec39633 Merge pull request #167 from TomHarte/VerticalSync
Adjusts vertical sync detection
2017-07-29 21:44:47 -04:00
Thomas Harte
0bf4fdc9af Simplified slightly. 2017-07-29 21:37:59 -04:00
Thomas Harte
ed8c73eb14 Ensured lengthy constant sync can't appear to be two sync pulses, regardless of other interruption. 2017-07-29 18:25:04 -04:00
Thomas Harte
3528a7f78b Made an attempt at triggering vertical sync the expected number of time after it begins, regardless of total length. 2017-07-29 17:33:52 -04:00
Thomas Harte
54bcc40192 With an eye towards being more accurate as to vertical sync recognition: acknowledged that the detection period varies between PAL and NTSC. 2017-07-29 14:53:53 -04:00
Thomas Harte
4b5e9ffb83 Ensured is_at_end_ is initially cleared by default. 2017-07-27 22:22:43 -04:00
Thomas Harte
a7f5f035a6 Merge pull request #166 from TomHarte/NoRefs
Standardises on `const [Half]Cycles`
2017-07-27 22:07:05 -04:00
Thomas Harte
4abd62e62b Standardises on const [Half]Cycles as the thing called and returned, rather than const [Half]Cycles & as it's explicitly defined to be only one int in size, so using a reference is overly weighty. 2017-07-27 22:05:29 -04:00
Thomas Harte
1fb158b297 Merge pull request #165 from TomHarte/HalfCycleTyper
Brings `Typer` into the new `run_for` orthodoxy
2017-07-27 21:56:02 -04:00
Thomas Harte
968d2bb8ba Brought Typer into the new run_for orthodoxy, making it easier to clock consistently regardless of unit. Which necessitated adding a negative operator for WrappedInts. 2017-07-27 21:53:45 -04:00
Thomas Harte
92a3dfe44a Merge pull request #164 from TomHarte/NoInt
Revokes the operator bool() on WrappedInt and simplifies/generalises HalfClockReceiver
2017-07-27 21:41:04 -04:00
Thomas Harte
9ef232157b Revoked the operator bool() on WrappedInt as providing an indirect means for implicit but incorrect assignment to unwrapped ints. Got explicit about run_for intention and simplified HalfClockReceiver slightly by building a lossy and a flushing conversion to Cycles into HalfCycles. Adapted the all-RAM Z80 properly to return HalfCycles. 2017-07-27 21:38:50 -04:00
Thomas Harte
b9f4f7a530 Merge pull request #163 from TomHarte/WaitSampling
Adjusts the timing of the Z80's wait line sampling to be on a half clock and better regularises 'action' partial bus cycles
2017-07-27 21:19:29 -04:00
Thomas Harte
761afad118 Corrected timestamp return, and its testing by the 6502 timing tests. 2017-07-27 21:19:16 -04:00
Thomas Harte
8848ebbd4f Formalised set_interrupt_line's optional parameter as being a count of HalfCycles; corrected PartialMachineCycle.is_wait and effected the proper timing for counter reset on a ZX81. 2017-07-27 21:10:14 -04:00
Thomas Harte
37950143fc Attempted to nudge wait timing onto half-cycle boundaries, which expands the number of partial machine cycles the Z80 can post but pleasingly also regularises them. Switched the AllRAMProcessor to reporting half cycles by default and corrected all Z80 tests. 2017-07-27 20:17:13 -04:00
Thomas Harte
25fd95044c Merge pull request #154 from TomHarte/Memptr
Introduces a subset of necessary test cases for the Z80 memptr register, and corrects implementation to match
2017-07-27 08:10:22 -04:00
Thomas Harte
1da24d10fd Corrected a couple of build errors. 2017-07-27 08:05:14 -04:00
Thomas Harte
60e374dca3 Merge branch 'master' into Memptr 2017-07-27 07:54:25 -04:00
Thomas Harte
7a65f91575 Merge pull request #162 from TomHarte/ClockReceiverHolder
Completes revocation of ClockReceiver
2017-07-27 07:43:12 -04:00
Thomas Harte
6f8b558724 Revoked dead #include. 2017-07-27 07:41:59 -04:00
Thomas Harte
1a88b62bf7 Merge branch 'master' into ClockReceiverHolder 2017-07-27 07:41:20 -04:00
Thomas Harte
8361756dc4 Switched definitively to the works-for-now approach of requiring an explicit opt-in where somebody wants to clock a whole-cycle receiver from a half-cycle clock. 2017-07-27 07:40:02 -04:00
Thomas Harte
273299028e Merge pull request #161 from TomHarte/Z80WaitSampling
Doubles the timing precision used by the Z80 up to HalfCycles
2017-07-27 07:39:36 -04:00
Thomas Harte
847e49ccdf Corrected timestamp reporting by the all-RAM Z80. 2017-07-26 19:47:39 -04:00
Thomas Harte
81a3899381 Adjusted the Z80 formally to communicate in terms of half cycles rather than whole. 2017-07-26 19:42:00 -04:00
Thomas Harte
9257a3f6d7 Added test for 16-bit arithmetic, and fixed implementation. 2017-07-26 19:04:52 -04:00
Thomas Harte
728143247d Added a test for RLD and RRD. Which already passes. 2017-07-26 18:56:35 -04:00
Thomas Harte
6ec4e4e3d7 Merge branch 'master' into Memptr 2017-07-25 23:01:34 -04:00
Thomas Harte
7922a12f02 Merge pull request #159 from TomHarte/JamReplacement
Corrects those tests broken by withdrawal of the 6502 jam handler functionality
2017-07-25 23:01:13 -04:00
Thomas Harte
37ccb9d3b6 Fixed 6502 timing tests. 2017-07-25 23:00:39 -04:00
Thomas Harte
3c254360ba Completed fixture of the 6502 BCD test. 2017-07-25 22:55:45 -04:00
Thomas Harte
d2a0beaa67 Merge branch 'master' into JamReplacement 2017-07-25 22:49:23 -04:00
Thomas Harte
cda223ffc0 Added explicit signedness cast. 2017-07-25 22:49:03 -04:00
Thomas Harte
3ca51bedc6 Discovered legitimate uses of the jam opcode so reinstated it. Corrected illegitimate uses. 2017-07-25 22:48:44 -04:00
Thomas Harte
36076b7ea5 Eliminated final vestige of professed jam handling. This should make it clear which tests still think they can capture jams. 2017-07-25 22:38:26 -04:00
Thomas Harte
e90d128a26 Merge pull request #158 from TomHarte/HalfCycles
Formalises run_for_cycles, offering half- and full-length cycle options
2017-07-25 22:34:35 -04:00
Thomas Harte
966b5e6372 Adapted the Z80's perform_machine_cycle to return Cycles. 2017-07-25 22:25:44 -04:00
Thomas Harte
279c369a1f Switched to Cycles as the result from the 6502 perform_bus_operation, helping slightly to clarify what you're intended to return and reducing type jumping within the 6502 implementation. 2017-07-25 22:21:09 -04:00
Thomas Harte
d9c6b3bcf7 Corrected TIA's WSYNC lookahead to accept Cycles. 2017-07-25 22:13:41 -04:00
Thomas Harte
1c2f68f129 Removed, as it's been relocated. 2017-07-25 20:43:05 -04:00
Thomas Harte
296c7cec05 Adopted flush widely. 2017-07-25 20:42:51 -04:00
Thomas Harte
75d67ee770 Relocated ClockReceiver.hpp as it's a dependency for parts of the static analyser, and therefore needs to be distinct from the actual emulation parts. 2017-07-25 20:20:55 -04:00
Thomas Harte
a1e9a54765 Eliminated redundant uses of ClockReceiver and sought to ensure that proper run_fors are inherited all the way down. 2017-07-25 20:09:13 -04:00
Thomas Harte
8d1dacd951 Clean ups along the Electron::Tape line: ensured that the ClockReceiver is opted into only once, and that its run_for propagates all the way along the chain. 2017-07-25 20:01:30 -04:00
Thomas Harte
545683df6f Added some documentation, got explicit again about cycle/half-cycle intermingling, and added flush as what amounts to divide(1), for cleaner usage without a clock divider. 2017-07-25 19:50:40 -04:00
Thomas Harte
cfbd62a5dc Attempted to fix implementation of divide, and marked everything as-yet unmarked as inline. 2017-07-25 07:43:39 -04:00
Thomas Harte
40339a12e1 Formalised the use of a cycles count with a divider, bringing a few additional plain-int users into the fold. 2017-07-25 07:15:31 -04:00
Thomas Harte
90bf6565d0 Reduced int/Cycle conversions in the Electron and on the Atari 2600, where the current framework makes it possible to do so. 2017-07-24 22:53:13 -04:00
Thomas Harte
9be9bd9106 Neatened layout. 2017-07-24 22:52:35 -04:00
Thomas Harte
c1527cc9e2 Reduced back-and-forth between Cycles and ints within the Oric. 2017-07-24 22:46:31 -04:00
Thomas Harte
a1a3aab115 Fixed implicit sign conversion. 2017-07-24 22:40:15 -04:00
Thomas Harte
c77a83d86f The 6560 is now a ClockReceiver. This reduces to zero the number of remaining instances of the text run_for_cycles in this codebase. 2017-07-24 22:38:35 -04:00
Thomas Harte
a6e377aa57 The Electron's video is now a ClockReceiver. 2017-07-24 22:36:42 -04:00
Thomas Harte
df4732be2e Corrected test. 2017-07-24 22:33:49 -04:00
Thomas Harte
9435c1e12a The 1540 is now a ClockReceiver. 2017-07-24 22:32:41 -04:00
Thomas Harte
efdac2ce8c The 6522 is now a ClockReceiver. 2017-07-24 22:29:09 -04:00
Thomas Harte
2912d7055b The 6532 is now a ClockReceiver. 2017-07-24 21:57:24 -04:00
Thomas Harte
d056f2e246 Updated comment. 2017-07-24 21:51:22 -04:00
Thomas Harte
55ecb0c022 Converted the Microdisc into a ClockReceiver. 2017-07-24 21:51:13 -04:00
Thomas Harte
13f7aa4063 The TIA is now a ClockReceiver. 2017-07-24 21:48:34 -04:00
Thomas Harte
915f587ef1 Updated the Electron's tape class to be a ClockReceiver. 2017-07-24 21:36:30 -04:00
Thomas Harte
b7f88e8f61 Filter is now a ClockReciever, affecting all sound output devices. 2017-07-24 21:29:13 -04:00
Thomas Harte
677ed463f0 Updated comment per new method name. 2017-07-24 21:19:49 -04:00
Thomas Harte
8a2bdb8d22 Converted the TimedEventLoop and the things that sit atop it into ClockReceivers. 2017-07-24 21:19:05 -04:00
Thomas Harte
9bff787ee1 Corrected for the new, non-integral type. 2017-07-24 21:05:07 -04:00
Thomas Harte
b3ae920746 Converted the DPLL and disk controller classes to be ClockReceivers. 2017-07-24 21:04:47 -04:00
Thomas Harte
b82bef95f3 Decided to follow through on Cycles and HalfCycles as complete integer-alikes. Which means giving them the interesting range of operators. Also killed the implicit conversion to int as likely to lead to type confusion. 2017-07-24 20:10:05 -04:00
Thomas Harte
e6578defcd It turns out that quite a few tests still rely on CSTestMachine6502JamOpcode. Though since it no longer works, that'll need to be fixed. In the meantime, fixed the test build process at least, as it's not really what this branch is meant to be invested in. 2017-07-23 22:22:50 -04:00
Thomas Harte
ace8e30818 Bubbled the Z80's move into clock receiver territory up into the Z80 test machine. 2017-07-23 22:21:39 -04:00
Thomas Harte
ec3aa06caf Removed dangling reference. 2017-07-23 22:16:00 -04:00
Thomas Harte
ba088e5545 Adapted the Z80 into a clock receiver, which also vends Cycles rather than a raw int within its PartialMachineCycle struct. The objective is to update it to vend HalfCycles within its struct, but I think I need to do some work on cycle/half-cycle arithmetic first. 2017-07-23 22:15:04 -04:00
Thomas Harte
8a0b0cb3d7 Extended both classes to allow copy assignment, copy construction and implicit zero-length construction. 2017-07-23 22:13:41 -04:00
Thomas Harte
6369138bd1 Converted the Oric's video output into a ClockReceiver. 2017-07-22 23:11:30 -04:00
Thomas Harte
c2a7dffa7d Converted the ZX80/81 video component into a ClockReceiver. As it happens, it's most convenient to take the half-cycle bus here. 2017-07-22 23:02:28 -04:00
Thomas Harte
b0c2325adc Corrected run call, and accepted that jam handling is gone forever. 2017-07-22 22:21:26 -04:00
Thomas Harte
2ff157cf7a Switched CRTMachine over to use Cycles as an explicit statement of units, and followed through on the effects of that. 2017-07-22 22:17:29 -04:00
Thomas Harte
83628b285b Experimentally turned the 6502 into a clock receiver. No problem encountered. 2017-07-22 21:52:21 -04:00
Thomas Harte
1ba3f262a2 Sketched out a template for clock-receiving components to allow them to be implemented in terms of either half or whole cycles. 2017-07-22 21:46:50 -04:00
Thomas Harte
97334e10af Merge pull request #157 from TomHarte/Documentation
Brings some minor documentation improvements
2017-07-22 17:40:44 -04:00
Thomas Harte
31b4f8fa31 Added an expository TODO. 2017-07-22 17:40:06 -04:00
Thomas Harte
1bbb4cb478 Increased documentation. 2017-07-22 17:39:51 -04:00
Thomas Harte
d46da6ac9d Added documentation. 2017-07-22 17:31:12 -04:00
Thomas Harte
825b38850e Removes non-idiomatic line break. 2017-07-22 17:30:58 -04:00
Thomas Harte
8755824c64 Added some documentation. 2017-07-22 17:25:53 -04:00
Thomas Harte
4ea835e50b Added test for EX (SP), rp, which passes. 2017-07-22 17:17:32 -04:00
Thomas Harte
5fddbec132 Merge branch 'master' into Memptr 2017-07-22 17:06:22 -04:00
Thomas Harte
b3d3fd70aa Merge pull request #156 from TomHarte/P81
Adds support for the P81 file format
2017-07-22 16:03:38 -04:00
Thomas Harte
6633537fb8 Discovering that there is such a thing as P81 — a ZX81 file without the name omitted — added support for it. Extended FileHolder while I was here to retain the file name and be able to supply its extension, as my quick-fix test-the-last-character approach to o/p/80/81 discrimination stops working with p81 thrown into the mix and this feels like the correct factoring. 2017-07-22 16:02:25 -04:00
Thomas Harte
4dec9716c4 Merge pull request #155 from TomHarte/MissingByte
Corrects infrastructure that led to the final bit of a ZX80 or ZX81 tape never exiting the parser
2017-07-22 15:43:50 -04:00
Thomas Harte
313b36944f Ensured that the final bit of a tape isn't dropped even if the tape ends exactly after it, by not posting a false pulse, being less restrictive about what can cap a bit, and in any case using a long gap as the end-of-file bookend. 2017-07-22 15:41:33 -04:00
Thomas Harte
456fdda6c2 Ensured that the mark_end step can't overwrite another recognised symbol. 2017-07-22 15:40:22 -04:00
Thomas Harte
6437c43147 Added CPI and CPD tests: at last two that pass without requiring implementation changes! 2017-07-22 12:38:18 -04:00
Thomas Harte
5928a24803 Transcribed missing tests as TODOs. 2017-07-22 11:44:17 -04:00
Thomas Harte
20a6bcc676 Added tests for the various LD (nn), rr instructions and corrected implementation to pass. 2017-07-22 11:39:13 -04:00
Thomas Harte
eaf313b0f6 Added a test on LD A, (DE) and LD A, (BC), and adjusted implementation to pass. 2017-07-22 11:20:21 -04:00
Thomas Harte
d51b66c204 Expanded test to hit all 65536 possibilities (and not to allocate a fresh Z80 test machine each time, as that's unnecessary and slow), and fixed implementation to pass test. 2017-07-21 23:01:35 -04:00
Thomas Harte
660f0e4c40 Added Objective-C through wiring and a Swift test class for Memptr modifications. So far with a single test, that fails. 2017-07-21 22:52:25 -04:00
Thomas Harte
540a03f75c Exposed the memptr register. 2017-07-21 22:31:42 -04:00
Thomas Harte
a6b239698c Merge pull request #153 from TomHarte/StricterWarnings
Enabled stricter compiler warnings
2017-07-21 22:28:55 -04:00
Thomas Harte
92d1dd9a4a Attempts to eliminate all remaining type variations. 2017-07-21 21:54:05 -04:00
Thomas Harte
45ec5f8eab Eliminated implicit sign conversion. 2017-07-21 21:53:27 -04:00
Thomas Harte
1d01acce06 Fixed differing types of loop variables and targets. 2017-07-21 21:53:05 -04:00
Thomas Harte
be750ee427 Eliminated all dangling implicit signedness conversions. 2017-07-21 21:52:37 -04:00
Thomas Harte
dddb30477b Used a different inner-loop variable, for clarity. 2017-07-21 21:52:08 -04:00
Thomas Harte
37459a3ebc Fixed parameter shadowing. 2017-07-21 21:51:18 -04:00
Thomas Harte
449c33ee8b Signedness fixes. 2017-07-21 21:28:04 -04:00
Thomas Harte
2b5d0877a8 Adjusted parameter name to match documentation. I think it's a carry-over from before I was passing the whole event along. 2017-07-21 21:27:50 -04:00
Thomas Harte
6d5807ec4b Fixed sign and ensured that the DataFromString implementation is of the thing declared in the header. 2017-07-21 21:24:28 -04:00
Thomas Harte
64865b3f41 Signedness fixes. 2017-07-21 21:23:34 -04:00
Thomas Harte
53f0e1896b Made delay_time_ unsigned for safe comparison. 2017-07-21 21:21:23 -04:00
Thomas Harte
aaa60dab12 Fixed signedness of index. 2017-07-21 21:21:01 -04:00
Thomas Harte
9b72c445a7 Fixed indexing type. 2017-07-21 21:19:46 -04:00
Thomas Harte
3f609e17b3 Factored out the table-lookup approach to being a typer, and adjusted so as definitely to limit myself to positive offset table lookups. 2017-07-21 21:18:51 -04:00
Thomas Harte
ef03c84b21 More definitively removed the old sample-offset blending approach to filtering. 2017-07-21 20:58:55 -04:00
Thomas Harte
163c0f1b44 Ensured offset means exactly one thing. 2017-07-21 20:58:17 -04:00
Thomas Harte
807e1d36d5 Resolved signedness mismatch. 2017-07-21 20:57:48 -04:00
Thomas Harte
5545cc8bab Merge branch 'master' into StricterWarnings 2017-07-21 20:47:28 -04:00
Thomas Harte
d6e60c8e3a Merge pull request #151 from TomHarte/TZX
Adds partial TZX file format support
2017-07-21 20:47:08 -04:00
Thomas Harte
2d8e7e9f8b Removed reference to a parameter long-since dead. 2017-07-21 20:46:25 -04:00
Thomas Harte
5b4c5b0cbf Avoided having two different variables named next_output_run. 2017-07-21 20:46:08 -04:00
Thomas Harte
2471ef805b Fixed signed/unsigned comparison and potential negative table reference. 2017-07-21 20:45:49 -04:00
Thomas Harte
2a7fc86b15 Enabled stricter warnings. 2017-07-21 20:44:35 -04:00
Thomas Harte
3c8bf52fb8 Believing my 64kb memory map not currently to work, temporarily disabled reference to it in the static analyser. 2017-07-21 20:43:56 -04:00
Thomas Harte
a026998682 Marginally optimised set_offset to avoid resets when possible. 2017-07-21 20:43:20 -04:00
Thomas Harte
06ea81fdb2 Made sure that unrecognised waves don't block the symbol queue, and allowed any type of nonsense to be skipped before finding a byte. 2017-07-21 20:23:26 -04:00
Thomas Harte
d69b1e2d11 Switched parsing logic to looking only for upward zero crossings, that being my new understanding of the ROM. 2017-07-21 19:39:38 -04:00
Thomas Harte
a3e0024980 Chopped time accumulation out of the default Tape process because it's proving to be sufficiently expensive for a TZX as not to be worthwhile. Introduced a cheaper position capturing/restoring method. 2017-07-21 18:55:03 -04:00
Thomas Harte
d9a2c32aca Made an attempt to obey the proper TZX rules on gaps, and to hit the common-clock-rate Time optimisation. 2017-07-21 18:21:12 -04:00
Thomas Harte
e152ed2e61 Made an attempt to avoid GCD costs when accumulating Times with a common clock rate (/divisor). 2017-07-21 18:20:27 -04:00
Thomas Harte
9e975a46a2 Mildly simplified syntax. 2017-07-21 18:18:45 -04:00
Thomas Harte
70af075012 Determined what appears to be an appropriate workaround for the ZX81 TZX that I've managed to obtain. 2017-07-19 21:28:33 -04:00
Thomas Harte
44e5a03cf2 Removed just-don't-power-the-tape approach to pausing and playing, in favour of being fully communicative. 2017-07-19 19:21:27 -04:00
Thomas Harte
c8cee88e33 Ensured that a stopped tape outputs a false level and took an extra safety check in instantiation. 2017-07-18 22:49:11 -04:00
Thomas Harte
35296017b5 Clarified meaning of is_high_ flag and ensured it is honoured properly. 2017-07-17 21:36:05 -04:00
Thomas Harte
130d598ec9 Corrected some minor out-of-style breaks, and ensured that the name of ZX81 files is returned. 2017-07-17 19:52:54 -04:00
Thomas Harte
0350925c1e Started sketching out greater support. Mostly TODOs right now, but pulse sequence and pure tone are implemented. I probably also need at least the CSW block to hit everything you might see in a ZX80 or ZX81 tape. Then I can worry about the rest when I have a way to test them. 2017-07-17 19:38:15 -04:00
Thomas Harte
94e3dd0d4f Merge branch 'master' into TZX 2017-07-17 19:07:05 -04:00
Thomas Harte
5d44422230 Merge pull request #152 from TomHarte/CSWLength
Corrects a false assumption about decompressed CSW size
2017-07-17 19:06:43 -04:00
Thomas Harte
eafdd7dbd7 Corrected decompressed size expectations: it may be up to five times the size of the number of waves, as waves are up to five bytes in length. 2017-07-17 19:04:25 -04:00
Thomas Harte
127b584e79 Ensured that resetting a TZX resets the is-at-end flag. 2017-07-17 07:52:28 -04:00
Thomas Harte
2179edc7fe Adjusted to allow the very first thing found to be data, and ensured that unrecognised symbols break files just as gaps do. 2017-07-17 07:43:47 -04:00
Thomas Harte
9108495586 Added a safety seek. 2017-07-17 07:35:53 -04:00
Thomas Harte
fa617eac6b Spotted that pilot and data segments have different encodings — of course! — and attempted to adapt. 2017-07-17 07:34:10 -04:00
Thomas Harte
b63971caf7 Took a first, incorrect, shot at TZX chunk 0x19, the generalised data block. 2017-07-16 22:40:38 -04:00
Thomas Harte
7327da6350 Switched the nascent TZX to use the new PulseQueuedTape. 2017-07-16 22:06:56 -04:00
Thomas Harte
8f72fc4a44 Factored out from the UEF implementation the concept of being a tape that has a queue of pending pulses and manages that queue. 2017-07-16 22:04:40 -04:00
Thomas Harte
238348c885 Performed the initial wiring to announce that this application supports TZX files and to route them to the ZX80/81 static analyser. The TZX class itself does not yet do much beyond basic validation. I think it'll be easiest if it follows in UEF's footsteps in queuing up pulses ahead of time, so some factoring out is now required. 2017-07-16 21:33:11 -04:00
Thomas Harte
7b5f93510b Fixed the DigitalPhaseLockedLoopBridge bridge, once again fixing tests. 2017-07-16 20:55:57 -04:00
Thomas Harte
b3861ff755 Reduced copying of Pulses. 2017-07-16 19:49:31 -04:00
Thomas Harte
5a6b7219b9 Merge pull request #150 from TomHarte/PLLParsing
Introduces PLL-driven tape parsing of Acorn tapes
2017-07-16 19:28:51 -04:00
Thomas Harte
c2bc34fd87 Eliminated the PLLParser class. I think the proper abstraction if and when another machine requires PLL-esque parsing is probably to template out the Acorn wiring of a PLL to a Parser, and/or generalise the Acorn shifter. It'll be easier to decide when the time comes. 2017-07-16 19:25:26 -04:00
Thomas Harte
1d3ae31755 Abstracted the concept of an Acorn shifter away from being a PLLParser. The Acorn tape parser now skips using that class and uses the shifter. The actual Electron also uses the shifter. So the two are completely aligned. Net result: the Electron should successfully load exactly when static analysis was successful. 2017-07-16 19:24:01 -04:00
Thomas Harte
8ddd686049 Removed redundant variable. 2017-07-16 19:04:03 -04:00
Thomas Harte
e5188a60dc Settled on the new average-of-length approach to a PLL window sizing, eliminating the old errors-of-phase approach. Since it anchors automatically to the original target clocks per bit, killed the explicit mention of a tolerance. 2017-07-16 19:03:50 -04:00
Thomas Harte
e71d13c090 With the new PLL implementation, switching to a deeper window size returns the Acorn tape parser to: working. 2017-07-16 17:12:12 -04:00
Thomas Harte
ba83dfd454 Merge branch 'master' into PLLParsing 2017-07-16 17:01:00 -04:00
Thomas Harte
2fb0aea990 Updated the C1540 test vessel to the new world. 2017-07-16 17:00:39 -04:00
Thomas Harte
51177e4e1f Attempted a different implementation of the PLL, that responds to changes only once. 2017-07-16 16:49:04 -04:00
Thomas Harte
004d6da9fb Merge branch 'master' into PLLParsing 2017-07-16 15:03:13 -04:00
Thomas Harte
561373e793 Merge pull request #149 from TomHarte/BetterVectors
Corrects some long-standing unnecessarily convoluted ways to handle semi-dynamic arrays
2017-07-16 15:02:52 -04:00
Thomas Harte
279f4760d7 Eliminated buffer_size_ as something explicitly stored, and reduced size of delegate call out. 2017-07-16 15:01:39 -04:00
Thomas Harte
f931cd582d Switched to use of std::vector in those few remaining places where I was still using a unique_ptr to a native type and newing for myself. So, some of my earliest bits of code. 2017-07-16 13:54:07 -04:00
Thomas Harte
ab51bc443b Eliminated foolish double indirection on phase history. 2017-07-16 13:39:41 -04:00
Thomas Harte
c8575fe6e0 Mild clean ups, and a tweak to permitted top and bottom phase. 2017-07-16 13:39:08 -04:00
Thomas Harte
4489f120f9 Eliminated foolish double indirection on phase history. 2017-07-15 22:40:38 -04:00
Thomas Harte
253f9603ed Split the normal tape parser class into two in order to add a new option: a PLL-driven tape parser. Decided to see what happens if I attempt to use that to parse CSW Acorn data. 2017-07-15 19:07:35 -04:00
Thomas Harte
6ca712b498 Merge pull request #148 from TomHarte/CSW
Adds support for the CSW file format
2017-07-15 15:33:05 -04:00
Thomas Harte
b743566339 Corrected under-request of data: was erroneously supplying the size of input as the expected size of output. 2017-07-15 15:19:03 -04:00
Thomas Harte
481487f084 Oh yuck, it looks like I've repeated this same test in two different places. Must figure out where to factor it out to. But in the meantime, the emulated Electron has just loaded its first CSW. 2017-07-13 22:39:30 -04:00
Thomas Harte
fc8313430a Added an early exit if what was read as a header turns out pretty much certainly not to be a header. 2017-07-13 21:26:45 -04:00
Thomas Harte
648618d280 Tweaked bit timing decision. 2017-07-13 21:26:05 -04:00
Thomas Harte
ae1a130843 Fixed: length of 0 is a special case. 2017-07-13 20:57:27 -04:00
Thomas Harte
33d16ae0cd Fixes: individual static analysers reset tapes, for potential successors. The ZX81 file analyser no longer overruns its buffer upon receiving a file that is shorter than 11 bytes. 2017-07-12 21:34:08 -04:00
Thomas Harte
f09fe30af5 Attempted a full implementation of CSW. All in memory for now. 2017-07-12 21:23:59 -04:00
Thomas Harte
33eadb5549 Started taking further steps towards CSW support; reading the ZLib documentation is next. 2017-07-11 22:41:10 -04:00
Thomas Harte
368bff1a82 Added a shell class that will one day be able to parse CSW files, plus the logic and metadata to instantiate it when a CSW presents itself. 2017-07-10 21:43:58 -04:00
Thomas Harte
d853841dd5 Further lightened up my file-is-ZX81 check. 2017-07-10 20:44:13 -04:00
Thomas Harte
eacaafeb48 Merge pull request #147 from TomHarte/ZX8081Typer
Introduces preliminary `Typer` support for the ZX80 and ZX81
2017-07-09 22:30:30 -04:00
Thomas Harte
ac59dd8b1d Added enough typing to issue a load command. No thoughts as to running yet though. 2017-07-09 22:07:12 -04:00
Thomas Harte
353c854734 Removed a TODO that is no longer appropriate. 2017-07-09 22:06:50 -04:00
Thomas Harte
3e5c209039 Added basic Typer support for the ZX80 and '81. 2017-07-09 22:00:34 -04:00
Thomas Harte
f56d96267e Merge pull request #146 from TomHarte/MSCrash
Corrects accounting errors in ZX80/81 video
2017-07-09 20:09:44 -04:00
Thomas Harte
ed28260aaf Hardens the ZX80/81 video routines to ensure they never try to push data into the future and don't double-count time when pixels would ostensibly run into sync. You could previously see the CRT being handed negative run lengths if sync interrupted pixels or if a run of more than 320 pixels (my arbitrary buffer size) occurred, with corresponding poor behaviour given my use of unsigned numbers. 2017-07-09 19:33:05 -04:00
Thomas Harte
8ccec37a4b Eliminated a further potential cause of texture/geometry mismatch: the texture retain succeeded but then there wasn't room for geometry. 2017-07-09 19:11:38 -04:00
Thomas Harte
646622b99e Merge pull request #145 from TomHarte/RandomNoise
Eliminates occasional on-screen noise
2017-07-09 17:59:16 -04:00
Thomas Harte
a25c2fd6b5 Got more explicit about what the thinking is here re: multiple sources of action. 2017-07-09 17:54:26 -04:00
Thomas Harte
ee1a9a4781 Eliminates attempts cleverly to shuffle unsubmitted runs, because no mechanism exists to stop them overwriting previously-submitted-but-not-yet-flushed runs. Which implies that the buffer must be fully circular. The cost of which is sometimes having to make two calls to glTexSubImage2D. Also added some TODOs, and a means for reporting when a retain_latest is ineffective, in which situation it would be inappropriate to attempt to generate correlated geometry 2017-07-09 17:50:22 -04:00
Thomas Harte
a0367d6669 Merge pull request #144 from TomHarte/HiRes
Corrects various ZX80/81 video deficiencies
2017-07-09 17:17:16 -04:00
Thomas Harte
87658e83c1 Moved line counter reset logic; I think this is actually correct. 2017-07-09 00:05:30 -04:00
Thomas Harte
4509c3ce34 By observation, it appears that disabling vsync occurs on any port output whatsoever, as long as NMI isn't blocking it. 2017-07-08 21:01:52 -04:00
Thomas Harte
30e93979d2 Removed data work if sync is enabled; in that case no data is output. 2017-07-08 21:01:07 -04:00
Thomas Harte
d6b87053bf Introduced an explicit record of whether a video byte is latched. It's definitely incorrect to treat the latching of 0 as equivalent to no latching, as the byte that will eventually become video is not strongly implied. 2017-07-08 20:40:19 -04:00
Thomas Harte
22389a5d2d Merge branch 'master' into HiRes 2017-07-08 20:38:25 -04:00
Thomas Harte
37696532c2 Merge pull request #143 from TomHarte/MotorControl
Introduces both manual and automatic tape control for the ZX80 and 81
2017-07-08 19:34:40 -04:00
Thomas Harte
54efcb7e2f Made a game attempt at automatic motor control and ensured setting is initialised correctly from the user defaults. 2017-07-08 19:31:20 -04:00
Thomas Harte
bcb7c27cc4 Given that I'm not racing this any more, turned the intended 1 second back into 1 second. 2017-07-08 19:21:33 -04:00
Thomas Harte
e2575d6de4 Routed tape motor selections through to the C++ side of the world, and ensured that manual tape playback works properly. 2017-07-08 19:21:12 -04:00
Thomas Harte
23e989e170 This will likely do for the Swift/XIB side of things: the play/pause button is enabled or disabled as per the user's choice of automatic tape control, and toggles function when pressed. It communicates activity down to the Objective-C[++] layer, giving it a route through to the actual machine. 2017-07-08 19:12:06 -04:00
Thomas Harte
28412150e6 Added controls for controlling the tape motor of the ZX80/81, assuming I can find an automatic option. 2017-07-08 17:59:33 -04:00
Thomas Harte
6d941b0c1f Merge pull request #142 from TomHarte/BufferOverflow
Corrects a source of potential desynchronisation between the texture and geometry builders
2017-07-08 16:54:41 -04:00
Thomas Harte
2f90f35478 Ensured the same write area can be submitted multiple times — this is actively used if a run of data overlaps a flywheel-suggested sync. Which nullifies the idea of not having a write area in the barrel, at least as soon as any one has been allocated. 2017-07-07 23:37:44 -04:00
Thomas Harte
12f7e1b804 Enshrined a default colour burst amplitude. Which now everybody relies on. The 102 figure is derived from the burst apparently being 40 IRE. 2017-07-07 23:35:14 -04:00
Thomas Harte
c7fa2ed11a It makes more sense not to retain the previous texture builder run until vertex storage is confirmed. 2017-07-07 23:21:25 -04:00
Thomas Harte
bfbe12b94b Made an attempt further to tie geometry and texture generation fully together, removing the assumption that the caller will achieve one-to-one calling. 2017-07-07 22:25:05 -04:00
Thomas Harte
7476c64a66 Merge branch 'master' into BufferOverflow 2017-07-07 21:11:07 -04:00
Thomas Harte
46fff8e8a2 Ensured bit 8 is uniquely from the latched video byte, not an OR of that with the refresh address. 2017-07-06 22:48:48 -04:00
Thomas Harte
cd646aab9e Merge pull request #141 from TomHarte/ZX81FastLoading
Corrects ZX81 fast loading
2017-07-06 22:39:19 -04:00
Thomas Harte
a3684545b5 Added a block on the tape motor for a short period after each time the ROM routine is intercepted for a substituted byte read. To reduce the collision between fast tape and real tape loading. 2017-07-06 22:33:54 -04:00
Thomas Harte
2f42874fd3 Another fix to deal with real-time fighting: allow 8 and 18 pulses to be recognised as 1s and 0s. That's because the hand-off from ROM routines to parsing may occur very shortly before the first pulse of a valid sequence, making it look like there's a ghost. A cleaner solution needs to be found, probably revolving around allowing parsers to be attached to tapes and therefore to run constantly. 2017-07-06 22:33:03 -04:00
Thomas Harte
84d0e9b4cd Accept a pulse that begins exactly on seek_time as being found while seeking. 2017-07-06 22:31:45 -04:00
Thomas Harte
a53011f778 Extended intro and outro length because right now I'm racing this myself. Can return to normal once tape motor control is implemented. 2017-07-06 22:31:12 -04:00
Thomas Harte
b842c5b8bb Merge branch 'master' into ZX81FastLoading 2017-07-06 22:03:24 -04:00
Thomas Harte
ab1374f801 Added an assert on an assumed buffer size alignment. 2017-07-06 21:46:24 -04:00
Thomas Harte
a5359027f0 Merge pull request #140 from TomHarte/ColourSuppression
Causes the CRT to react properly to absence of a colour burst
2017-07-06 21:42:02 -04:00
Thomas Harte
43951a36eb Merge branch 'master' into ColourSuppression 2017-07-06 21:40:42 -04:00
Thomas Harte
55df96491c Merge pull request #139 from TomHarte/TyperFixes
Corrects a potential invalid memory dereference in the default typer implementation
2017-07-06 21:40:15 -04:00
Thomas Harte
0c037627fc Typer fixes: the recipient no longer releases the caller, and a duplicate call to strlen and piece of arithmetic is corrected. 2017-07-06 21:38:56 -04:00
Thomas Harte
344d267fd2 Introduced sharper chrominance for genuinely black-and-white signals. 2017-07-06 21:38:33 -04:00
Thomas Harte
4211389ac7 Connected machine-supplied colour burst amplitude to shader, discarding hard-coded value. Net effect: the colour component is now discarded for the ZX80 and 81. 2017-07-06 21:29:08 -04:00
Thomas Harte
c6d00ec7d1 Switched phase and amplitude varying to a 3d vector; the third component is 1/amplitude if amplitude is non-zero, and zero otherwise. So you can multiply by that to get chrominance, rather than dividing by amplitude. With the direct effect that detected chrominance should automatically be zero if the colour burst didn't exist (i.e. had zero amplitude). 2017-07-06 21:25:38 -04:00
Thomas Harte
212ae60622 Typer fixes: the recipient no longer releases the caller, and a duplicate call to strlen and piece of arithmetic is corrected. 2017-07-06 21:17:04 -04:00
Thomas Harte
a72a2e0a1a Ensured tape doesn't proceed of its own volition when in fast-loading mode. 2017-06-23 20:21:37 -04:00
Thomas Harte
50375fb373 Ensured tape position is unaffected if the attempt at loading quickly fails. 2017-06-23 20:18:19 -04:00
Thomas Harte
2d02c23574 Merge pull request #138 from TomHarte/ZX81Ports
Increases port decoding on the ZX81
2017-06-23 08:32:21 -04:00
Thomas Harte
cb105fdeb4 Took a first stab at high-res support. 2017-06-22 22:48:17 -04:00
Thomas Harte
acfd4dde36 Reduced port writes which can adjust programmatic sync, and prevented anything while NMI generation is active. Moved line counter increment from triggered by interrupt acknowledge to triggered by horizontal sync. In both cases, cribbing from my own earlier work. Initial results suggest that sync issues are resolved in third-party software. 2017-06-22 22:44:06 -04:00
Thomas Harte
919fc48cc5 Fixed dumb out-of-bounds access error. 2017-06-22 22:28:50 -04:00
Thomas Harte
aec4fd066b I think I've definitively decided against this model of timing. 2017-06-22 21:32:14 -04:00
Thomas Harte
73c7b18900 Added a public declaration of ZX80/81 support to the readme. 2017-06-22 21:14:43 -04:00
Thomas Harte
3dfe45d225 Merge pull request #137 from TomHarte/NMIWaitTest
Introduces an NMI/wait interrupt timing test
2017-06-22 21:11:54 -04:00
Thomas Harte
95a6b0f85c Introduced an NMI/wait interrupt timing test, and adjusted the Z80 to conform to information posted by Wilf Rigter. 2017-06-22 21:09:26 -04:00
Thomas Harte
87ee8450fe Minor rejig: it's much more likely that something that can't be distinguished is a ZX81 program. TODO: some sort of BASIC token parsing, to be more confident. 2017-06-22 20:23:14 -04:00
Thomas Harte
f2a6bcf2a8 Merge pull request #136 from TomHarte/ZX8081Options
Finally adjusts ZX80/81 tape loading so that the fast loading hack is optional as per the GUI
2017-06-22 20:21:28 -04:00
Thomas Harte
644ef13acd Connected up the fast-tape GUI option for the ZX80 and '81. 2017-06-22 20:20:31 -04:00
Thomas Harte
342574761f Merge pull request #135 from TomHarte/InterruptWaitStates
Ensures wait states are observed during interrupt response
2017-06-22 20:15:54 -04:00
Thomas Harte
b7c978e078 Added getters for most of the input lines, and attempted to round out the ZX81's wait logic. 2017-06-22 20:11:19 -04:00
Thomas Harte
f0398a6db8 Added wait state hooks to the interrupt programs, and added an is_wait query on PartialMachineCycle. 2017-06-22 20:07:47 -04:00
Thomas Harte
f3b1ef99cc Merge pull request #134 from TomHarte/BinaryTape
Switches the binary tape player to low = false, high = true
2017-06-21 22:25:42 -04:00
Thomas Harte
52d9ddf9e5 Gave the binary tape player a more logical assignment of wave level to output level. Which miraculously appears to have been the issue with the ZX80/81 tape loading — the inconsistency of silences seems to have been the issue. 2017-06-21 22:13:24 -04:00
Thomas Harte
93f251dbcd Merge pull request #133 from TomHarte/ZX81Wait
Utilises the newly-working wait line in ZX81 emulation
2017-06-21 21:46:08 -04:00
Thomas Harte
a6810fc3ef Removed some minor duplicity and ensured that hsync/NMI ends on the nominated cycle, not one afterwards. 2017-06-21 21:44:42 -04:00
Thomas Harte
15f6c51062 Added the most trivial implementation of the ZX81 wait line. 2017-06-21 21:28:14 -04:00
Thomas Harte
5e21c706f3 Merge pull request #132 from TomHarte/MachineCycles
Subdivides the Z80's machine cycles
2017-06-21 21:19:48 -04:00
Thomas Harte
e1355d4b62 Restored proper video output. 2017-06-21 21:18:09 -04:00
Thomas Harte
7eeac3b586 Switched R back to incrementing after the refresh cycle. It had snuck to before by virtue of subdivision of the M1 cycle. Which shortened the ZX80 line time, breaking synchronisation. 2017-06-21 21:11:00 -04:00
Thomas Harte
4bf13610ce Reinstated interrupts by moving the refresh test back into the refresh cycle. 2017-06-21 21:03:39 -04:00
Thomas Harte
0e0ce379b4 Renamed MachineCycle to PartialMachineCycle given that it mostly no longer intends to describe an entire machine cycle. 2017-06-21 20:38:08 -04:00
Thomas Harte
36e8a11505 Sought to simplify the way partial machine cycles are communicated, for ease of machine implementation. Also implemented the wait line. 2017-06-21 20:32:08 -04:00
Thomas Harte
45f442ea63 Corrected interrupt mode 2: was both failing properly to load the vector address, and failing to read from it. 2017-06-21 19:08:48 -04:00
Thomas Harte
db743c90d8 Had neglected to count refresh time in my interrupt programs. Corrected. Mode 0 timing test succeeds again. Only Mode 2 is now at fault. 2017-06-21 18:58:44 -04:00
Thomas Harte
10cc94f581 Attempted to fix interrupt response timing; ensured initial interrupt mode is one that won't jump beyond the interrupt response program table's length, and that the conditionals other than CALL definitely have no alternative program attached. 2017-06-21 18:47:00 -04:00
Thomas Harte
108da64562 Fixed LD H, (HL) and LD L, (HL) by ensuring that whatever the subclass does goes to a temporary place before updating the address. Corrected the LD (IX+d), n machine cycle test for my new best-guess timing. This should leave only interrupt timing as currently amiss. 2017-06-20 22:25:00 -04:00
Thomas Harte
f85b46286e Resolved the timing disparity between LD (HL),n and LD (IX+d), n, hopefully having come up with a convincing theory of timing for the latter. 2017-06-20 22:20:58 -04:00
Thomas Harte
184b371649 Attempted to get to 'proper' timing for LD (IX+d),n, albeit that proper is a guess. 2017-06-20 21:48:50 -04:00
Thomas Harte
b0375bb037 Fixed the three LD rr, (nn) operations. Back down to four FUSE failures. 2017-06-20 21:32:23 -04:00
Thomas Harte
48942848e7 Fixed (Ix+d) read timing. I've put an extra wait cycle into the read, so no need to extend the refresh. 2017-06-20 21:15:56 -04:00
Thomas Harte
27ac342928 Corrected conditional call timing, and its test. 2017-06-20 20:57:23 -04:00
Thomas Harte
25aba16ef8 Quickly checking the FUSE tests, corrected a handful of instances where PC should be modified but isn't, correcting around 800 new failures. 2017-06-19 22:20:23 -04:00
Thomas Harte
a0d0f383c8 Corrected unconditional CALL timing. Conditional's going to require more work because once the wait state is put into the right place, it breaks the assumption under which the Z80 handles conditions — that they're either do something or else do nothing. So that can wait a day. 2017-06-19 22:07:36 -04:00
Thomas Harte
6752f165db Added failing tests for both kinds of CALL. 2017-06-19 22:03:29 -04:00
Thomas Harte
e05076b258 Added tests for everything except CALL. All passing. 2017-06-19 22:00:04 -04:00
Thomas Harte
fadbfdf801 Added DJNZ test. 2017-06-19 21:31:56 -04:00
Thomas Harte
cb277b8d1e Added JP and JR tests. 2017-06-19 21:27:23 -04:00
Thomas Harte
234f14dbbe Tests were at fault; all passing now. 2017-06-19 21:14:40 -04:00
Thomas Harte
99ede3a9ef BIT/SET (IX+d) were incorrectly encoded. Hence fixed BIT (IX+d). 2017-06-19 21:04:14 -04:00
Thomas Harte
378233f53d Extended to BITs and SETs, accruing three new failures. 2017-06-19 21:01:30 -04:00
Thomas Harte
f903408980 Caught up on comments. 2017-06-19 20:53:22 -04:00
Thomas Harte
cc8f316941 Resolved read-modify-write (IX+d) timing, and therefore RLC (IX+d). 2017-06-19 20:51:28 -04:00
Thomas Harte
b684254908 Introduced further tests down to a failing attempt at RLC (IX+d). Made an initial attempt to fix, failed. 2017-06-19 20:33:34 -04:00
Thomas Harte
351d90ca55 Added tests down to INC IX. No additional failures yet, though I've yet to reach conditional CALL. 2017-06-19 20:04:55 -04:00
Thomas Harte
23177df26a Added various tests of the basic ALU ops. 2017-06-19 19:53:26 -04:00
Thomas Harte
ba15371948 Introduced timing tests for LDI[R] and CPI[R], fixing a latent issue in the rejig of LD BC, nn while I'm here. 2017-06-19 19:47:00 -04:00
Thomas Harte
73dbaebbc1 Fixed timing of EX (SP), HL/IX. 2017-06-19 19:25:53 -04:00
Thomas Harte
8d60734737 Added tests for EXX, EX (SP), HL and EX (SP), IX. The latter two currently being incorrect. 2017-06-19 19:17:54 -04:00
Thomas Harte
002098d496 The final two tests were at fault — expecting POPs to write rather than read. Fixed, so the subset of timing tests as-yet implemented now passes. Which means it's time to slog through further tests. 2017-06-19 07:45:41 -04:00
Thomas Harte
e3244eb68e Rephrased internal operation machine cycles as having only an end. So they're now easy to count. Hence the test machine spots them, and a couple more of the current timing subset passes. 2017-06-19 07:39:46 -04:00
Thomas Harte
85c6fb1430 Explained refresh cycles to the all-RAM Z80. 2017-06-19 07:36:11 -04:00
Thomas Harte
54e4643396 Corrected non-default refresh cycle lengths. Reduces failures of the currently-tested timing subset from 10 to 4. 2017-06-19 07:34:23 -04:00
Thomas Harte
85c5c4405a Ensured that wait states don't appear unless requested (TODO: requesting), and made the output of my timing tests a little easier to parse. 2017-06-19 07:30:01 -04:00
Thomas Harte
d668879ba6 Started trying to wade back to passing tests. Working on the new timing tests first, and focussing on getting the Objective-C test machine to compile bus operations into machine cycles, which means indicating phase to all-RAM delegates. 2017-06-18 22:03:13 -04:00
Thomas Harte
cb140aa06e Managed to navigate back to building. 2017-06-18 21:00:44 -04:00
Thomas Harte
6a769d3953 Finally dipped below the 20 error threshold that the compiler tops out at. 2017-06-18 20:34:46 -04:00
Thomas Harte
3be8ffd826 Some correct timings have gone out the window for now, but only the final quarter of the base page now contains compiler errors. 2017-06-18 20:31:12 -04:00
Thomas Harte
bb910e14a4 Dealt with the CB page. 2017-06-18 18:01:33 -04:00
Thomas Harte
69ebbe019a Completed ED page conversion. Rolling onwards... 2017-06-18 17:56:48 -04:00
Thomas Harte
0d39672d32 Fixing typos here and there, persuaded the first half of the ED table to compile. 2017-06-18 17:48:54 -04:00
Thomas Harte
0d1231980a Advanced to getting specific warnings in the ed-page table. So that's progress. 2017-06-18 17:25:15 -04:00
Thomas Harte
82a015892b Started adapting to the newly-segmented world. 2017-06-18 17:18:01 -04:00
Thomas Harte
194b7f60c5 Rephrased to allow non-conditional waits; expanded macros to cover all permitted lengths of read and write. 2017-06-18 17:08:50 -04:00
Thomas Harte
ebc7356db5 Reformulated the machine cycle slightly to support posting operation plus phase, thereby exposing the segue points at which waits might be inserted. So: to stick to the rule that CPUs expose the minimum amount of information sufficient completely to reconstruct bus activity. This breaks the Z80 for now. 2017-06-18 12:21:27 -04:00
Thomas Harte
e1a2580b2a Renamed BusOperation to MachineCycle::Operation. 2017-06-17 21:53:45 -04:00
Thomas Harte
b6f51474ff Ensured that -description can handle the newly-captured bus actions. 2017-06-17 18:20:30 -04:00
Thomas Harte
0f18768091 Disabled attempts at bus activity matching within the FUSE tests, at least until I settle on exactly what I intend to do. 2017-06-17 18:19:25 -04:00
Thomas Harte
efc7f9df37 Combined I and R into a register pair. 2017-06-17 18:18:28 -04:00
Thomas Harte
50cd617bd9 Ensured test raises only the intentional failure exceptions. 2017-06-15 22:33:46 -04:00
Thomas Harte
838b818cd3 Finished transcribing first page of machine cycle documentation; several failures contained. 2017-06-15 22:19:49 -04:00
Thomas Harte
cf795562bf Continued filling in tests, fleshing out what the test machine captures as a result. 2017-06-15 20:59:59 -04:00
Thomas Harte
ac37424878 Set up a test class to allow me to discover which of the machine cycle sequences I'm in error on. 2017-06-15 19:06:59 -04:00
Thomas Harte
a336048c98 Merge branch 'ZX80FileFormats' 2017-06-15 18:33:42 -04:00
Thomas Harte
87496f9978 Merge pull request #131 from TomHarte/ZX80FileFormats
Adds very preliminary emulation of the ZX80.
2017-06-15 18:32:38 -04:00
Thomas Harte
08a542a324 Reenabled the fast-loading hack. 2017-06-15 18:30:12 -04:00
Thomas Harte
9b3d05e05f Simplified decoding logic. 2017-06-14 22:24:44 -04:00
Thomas Harte
d8e3103a2b Fixes: switched ZX80 and ZX81 timing to the correct way around, ensured that my wait takes effect if HALT **isn't** set, and made sure to recover from it. 2017-06-13 21:48:17 -04:00
Thomas Harte
76a64d13a0 Made a first attempt at ZX81 emulation. 2017-06-13 21:25:55 -04:00
Thomas Harte
1e975859c2 Started splitting ZX80 and ZX81 paths. Also the '80 fires its horizontal sync a little earlier than the '81, so pulled that back a little. 2017-06-13 20:09:09 -04:00
Thomas Harte
4c5261bfa0 Made first attempt to use the horizontal counter for something; here for sync timing only, even though I've gone exclusively with '81-style timing for now. 2017-06-12 22:28:30 -04:00
Thomas Harte
aed2827e7b Implemented a rudimentary way to test that instructions take as long as the FUSE tests think they should. Hence discovered that the (HL)-accessing BIT, RES and SET weren't. Corrected. 2017-06-12 22:22:00 -04:00
Thomas Harte
e6e6e4e62b Adds an extra character for padding the ZX81 table. 2017-06-12 22:08:11 -04:00
Thomas Harte
8b09b4180b This now at least remembers whether it is meant to be a ZX81 and has storage for a horizontal counter. 2017-06-12 21:33:16 -04:00
Thomas Harte
626737b9fa Started mucking about with some string conversion routines. Not finished yet. 2017-06-12 21:32:36 -04:00
Thomas Harte
22de481557 Made an attempt to get .p/.80 checked and as far as the emulated machine. 2017-06-12 19:41:59 -04:00
Thomas Harte
b9dbb6bcf8 Discovered my timing error: the I/R <-> A loads should take an extra cycle. This means the ZX80 now finally takes the correct 207 cycles per line. Fixed the video output wave to be clocked at the appropriate rate. 2017-06-12 18:55:04 -04:00
Thomas Harte
a48616a138 Fixed reference to Swift-world MachineDocument for the ZX81 file type. 2017-06-12 18:51:11 -04:00
Thomas Harte
8222aac9e3 Added an official declaration of support for ZX81 files. 2017-06-11 21:40:41 -04:00
Thomas Harte
77aa3c187e Rebranded ZX80O as ZX80O81P, with an eye to making it accept ZX81 .p files. Adjusted the initial selection part of the static analyser appropriately. 2017-06-11 21:38:32 -04:00
Thomas Harte
ee0283c985 Modified to use an in-memory buffer for file contents. 2017-06-11 21:35:09 -04:00
Thomas Harte
302c2e94de Corrected lingering hard-coded mask. So titles for memory configurations above 1kb now load. 2017-06-11 21:27:46 -04:00
Thomas Harte
06fe07932a While tidying up, killed an unused instance variable. 2017-06-11 21:21:26 -04:00
Thomas Harte
6913c7a018 This also can just use rom_mask_. 2017-06-11 19:29:20 -04:00
Thomas Harte
6b602c74b7 Made an attempt to support memory maps other than the unexpanded default of 1kb. 2017-06-11 19:29:02 -04:00
Thomas Harte
8116f85479 Allowed the static analyser to specify a ZX80 or 81, and a memory model. Neither is respected yet in the machine. 2017-06-11 19:12:20 -04:00
Thomas Harte
e40d553045 Bumped the tape parser up into the machine to ensure a maintained state. Temporarily disabled normally-timed tape playback. 2017-06-11 18:31:43 -04:00
Thomas Harte
2c6414ce11 Adjusted to allow inspect_waves to swallow a gap before a bit if necessary, increasing the opportunities for its call. 2017-06-11 18:31:09 -04:00
Thomas Harte
e5aea632ee Updated curly bracket placement. 2017-06-11 17:29:22 -04:00
Thomas Harte
e5b30cdfbb Attempted to ensure appropriate resumption of processing after quick-reading a tape byte. 2017-06-11 17:28:47 -04:00
Thomas Harte
ba5f34f827 Narrowed view to the centre 80% of a frame. 2017-06-11 17:24:32 -04:00
Thomas Harte
84d2feb2e6 Cleaned up and implemented fast-tape hack. I've decided it'd be better to test some other software, potentially to give multiple issues to think about, rather than sitting around with just the one. 2017-06-11 16:42:49 -04:00
Thomas Harte
d12e50eb02 Corrected "should I adjust history?" tests. 2017-06-11 16:41:34 -04:00
Thomas Harte
c2bc9a8c62 Ensured no namespace collision in double-include guards. 2017-06-11 16:41:15 -04:00
Thomas Harte
d910a4fd38 Adjusted to signal an interrupt during the refresh cycle rather than weirdly just afterwards. Which cuts video timing down by 4 cycles a line. There still might be a problem here somewhere though, as I'm getting 206 cycles/line and the internet states it should be 207.
Also: lots of printfs have grown temporarily as I try to figure out what I'm doing so wrong as to break loading.
2017-06-11 13:32:20 -04:00
Thomas Harte
db30f53ab0 Added the capacity to back-date interrupt line changes within a machine cycle, so that machines which time themselves entirely within perform_machine_cycle can still be cycle accurate on those changes. 2017-06-11 13:31:02 -04:00
Thomas Harte
50be3a24fe Sought to ensure that Mode 1 interrupts aren't happening early. Which they seem not to be. 2017-06-11 13:30:08 -04:00
Thomas Harte
256ba4028b Rejigged to eliminate semi-duplication of the is-a-file test. 2017-06-08 21:52:13 -04:00
Thomas Harte
b07af2660d Adjusted to make sure that the very end of a tape is properly measured. 2017-06-08 21:33:35 -04:00
Thomas Harte
bc0d70b2f7 Added: a shout-out when the tape has been exhausted. 2017-06-08 21:32:27 -04:00
Thomas Harte
c6e48dfd56 Given that a final gap is semantically part of describing tape contents, ensured one formally appears before declaring that the tape has ended. 2017-06-08 21:31:54 -04:00
Thomas Harte
c775db50ef Ensured no out-of-bounds accesses. 2017-06-08 21:31:03 -04:00
Thomas Harte
ee4c8b5ad2 Ensured final byte plays out. 2017-06-08 19:51:49 -04:00
Thomas Harte
d8b76e31c3 Added and improved the is-this-ZX80-stuff test. It seems some bytes are going missing in the to->from tape conversion. 2017-06-08 19:49:18 -04:00
Thomas Harte
7e10c7f9d8 Relocated the ZX80/81 concept of a 'file' out from Tape into Data, given that it's an exact duplicate of memory. 2017-06-08 19:09:51 -04:00
Thomas Harte
c47128f433 Widened tolerances and ensured zero bits aren't prematurely discarded. 2017-06-07 17:50:03 -04:00
Thomas Harte
8aab9acc10 Eliminated use of the zero level; now definitively returns a low/high input. 2017-06-07 17:39:29 -04:00
Thomas Harte
350b36df25 Wired in an attempted usage of the new tape parser. 2017-06-07 17:30:53 -04:00
Thomas Harte
dbd2944c13 Took an initial run at the ZX80/81 parser. 2017-06-07 17:27:05 -04:00
Thomas Harte
4603fa6f24 Extended explicitly to support a token of lookahead, which is pretty much what was on offer anyway. Also corrected instance variable names, as per better adoption of C++ norms. 2017-06-07 17:21:57 -04:00
Thomas Harte
60300851ea Started sketching out a tape parser for ZX80 and '81 files. I think this'll help me to verify whether the .O input is working. 2017-06-07 10:12:13 -04:00
Thomas Harte
58312ea2b7 Updated to new standardisation on curly bracket placement. 2017-06-07 10:05:43 -04:00
Thomas Harte
cb534d8b85 Corrected comment. 2017-06-07 10:05:16 -04:00
Thomas Harte
5626d35bc4 Tried flipping the bit meaning; decided at least to leave it in full-byte form. 2017-06-06 18:38:05 -04:00
Thomas Harte
4677cebf40 Rejigged to correct: spaces go after bits, not after bytes. 2017-06-06 18:29:15 -04:00
Thomas Harte
7399f3d798 Caveman debugging in place, it looks like this file is returning nonsense. 2017-06-06 18:18:55 -04:00
Thomas Harte
faeecf7665 Made sure that there's nothing but silence at the end of the tape, even if the .O file is too long. 2017-06-06 18:16:47 -04:00
Thomas Harte
63e0802f4e Ensured tape input appears on the returned value. 2017-06-06 18:16:27 -04:00
Thomas Harte
e3ee9604a5 Added comments. 2017-06-06 18:01:33 -04:00
Thomas Harte
8c66e1d99d Factored out ZX80/81 video and rejigged to ensure it will keep ticking over irrespective of whether the machine is supplying data. 2017-06-06 17:53:23 -04:00
Thomas Harte
b55579c348 Fixed usage of flush: the subclass version is definitively used. 2017-06-06 17:52:44 -04:00
Thomas Harte
ca9e8aecd6 Made a seemingly unsuccessful attempt to add tape input. 2017-06-06 10:13:32 -04:00
Thomas Harte
cc4cb45e9d Implemented keyboard input and ensured that the signal generated is marked as composite, putting the colour-suppression ball into the CRT's court. 2017-06-06 09:25:18 -04:00
Thomas Harte
ebbf6e6133 Surprisingly, I think this may actually be the correct output: stopped throwing away the I part of the refresh register and flipped black and white. 2017-06-06 09:03:09 -04:00
Thomas Harte
cba07dec7e Doubled up to display all eight pixels. To confirm that they are the wrong pixels. 2017-06-06 08:59:00 -04:00
Thomas Harte
6f7037b2b1 Made an initial stab at outputting half the correct pixels. 2017-06-06 08:55:07 -04:00
Thomas Harte
ef4b2f963d Probably more-or-less corrected. But this is all a bit too interdependent. 2017-06-05 23:52:56 -04:00
Thomas Harte
97f3ff03b6 Restored white background and attempted to correct output timing deficiencies. Incomplete success. 2017-06-05 23:50:04 -04:00
Thomas Harte
2fbc7a2869 Made a very basic attempt at getting something that at least demarcates proper graphics output. 2017-06-05 23:32:49 -04:00
Thomas Harte
4983718df7 Got to outputting something to the CRT. Should be just proper syncs and a paper background. It's not synchronising properly, so something is amiss in my timing. 2017-06-05 10:47:42 -04:00
Thomas Harte
23ca00fd9a Added memory fuzzing as a way to verify state being written by the Z80. Eventually discovered the HALT problem as fixed in the last commit, so have stripped away the caveman stuff again. 2017-06-05 10:36:07 -04:00
Thomas Harte
3df6eba237 Fixed: my HALT line wasn't actually halting. NOPs followed, but the PC just kept counting. 2017-06-05 10:35:03 -04:00
Thomas Harte
893f61b490 Attempted specifically to reproduce the 1kb ZX80 memory map in the hope of getting compact lines and in case mirroring is why I'm getting completely empty video reads. Still no action. 2017-06-05 09:38:49 -04:00
Thomas Harte
e940e02126 Added a short circuit to set_interrupt_line, mostly to make breakpoints slightly more convenient to place. 2017-06-05 09:37:19 -04:00
Thomas Harte
7e3a46c33e [Re]discovered that sync may also be a product of the interrupt cycle. So started looking into that. 2017-06-04 21:54:55 -04:00
Thomas Harte
7f743c6fb0 Got explicit about permitted type conversions. 2017-06-04 18:40:59 -04:00
Thomas Harte
73654d51dd Wired up actually to run. 2017-06-04 18:37:13 -04:00
Thomas Harte
096551ab3e Made a first attempt to hash out the ZX80's bus. Video output isn't yet going though. Can't seem to find clarity on whether horizontal sync is really programmatic. Let's see. 2017-06-04 18:32:23 -04:00
Thomas Harte
c485c460f7 Imported the ZX80 and 81 system ROMs (though not publicly), added enough code to post their contents into C++ world. 2017-06-04 18:08:35 -04:00
Thomas Harte
b0a7c58287 Fixed project to point to the XIB I actually want to keep; fixed that XIB to have the correct contents. 2017-06-04 17:57:37 -04:00
Thomas Harte
d2637123c4 Added necessary support to get as far as an empty window when attempting to load a piece of ZX80 software. 2017-06-04 17:55:19 -04:00
Thomas Harte
02b7c3d1b0 Added the necessary wiring to get into a ZX80/81-oriented part of the static analyser, which could in principle post a ZX80 target. 2017-06-04 17:04:06 -04:00
Thomas Harte
8c1769f157 Made a quick attempt at serialising from ZX80 .O to waves. 2017-06-04 16:59:26 -04:00
Thomas Harte
655809517c Ensured that there is a subclass of file that is entrusted to load .O/.80 files, and that the code routes such files to it, noting that it should consider whether a ZX80 is required. 2017-06-04 16:37:03 -04:00
Thomas Harte
2190f60a89 Reinstated manual-by-stealth secondary usage of the Zexall test as a benchmarking tool. 2017-06-04 15:46:35 -04:00
Thomas Harte
18faebc93c Merge pull request #130 from TomHarte/Bits35
Corrects bit 3 & 5 emulation for everything except BIT n, (HL)
2017-06-04 15:42:22 -04:00
Thomas Harte
0eebfdb4cc Expanded emulation of memptr, though still incomplete. Reverted zexall tests to zexdoc. Will probably leave memptr until I've an emulated machine as test suites seem to exist, but they're machine-dependant, so figuring out how to isolate them from an architecture will be a lot easier if and when I have functioning machines. 2017-06-04 15:39:37 -04:00
Thomas Harte
7811374b0f Started sneaking in memptr emulation, hopefully to get to a working BIT (hl). 2017-06-04 15:07:07 -04:00
Thomas Harte
a2f01b4a46 Corrected CPx bit 3 and 5 flags. I think only BIT n, (HL) with the famous MEMPTR reliance is preventing a complete pass by Zexall now. 2017-06-04 14:59:18 -04:00
Thomas Harte
f5c910beb7 Fixed LDIR/LDDR bit 3/5 flags. This seems once again to satisfy FUSE. 2017-06-04 14:18:04 -04:00
Thomas Harte
4e014ca748 Ensured BIT takes bits 5 and 3 from the computed address if used on indexed pages. That seems to cover 97 failures out of 100? 2017-06-04 14:13:38 -04:00
Thomas Harte
87095b0578 Undid consciously discard for bits 3 and 5 in the FUSE tests. Back to 100 failures. 2017-06-04 14:04:26 -04:00
Thomas Harte
fba6ac2b4c Merge pull request #129 from TomHarte/TestMachineCommonality
Generalises the Z80 test machine's trap handler also to cover the 6502
2017-06-03 22:27:55 -04:00
Thomas Harte
1a811b1ab1 Eliminated the function call inherent to every decode, and also moved the fixed table of operations into a non-templated base class. 2017-06-03 22:19:35 -04:00
Thomas Harte
c26349624c This, of course, should be inline to gain any benefit from the slightly-tortured private implementation. 2017-06-03 22:00:57 -04:00
Thomas Harte
b642d9f712 Eliminates the 6502's specialised jam handler in favour of the generic trap handler, and simplifies the lookup costs of that as it's otherwise doubling execution costs. 2017-06-03 21:54:42 -04:00
Thomas Harte
fd6623b5a5 Attempted to bring a common hierarchy to the Z80 and 6502 test machines, particularly with a view to eliminating the special-case Jam stuff on the 6502. 2017-06-03 21:22:16 -04:00
Thomas Harte
0b2a3f18bc Merge pull request #128 from TomHarte/Scheduling
Eliminates the micro-op scheduler
2017-06-03 20:32:39 -04:00
Thomas Harte
b304c3a4b9 Eliminated the 6502's reliance on the micro-op scheduler. 2017-06-03 20:30:07 -04:00
Thomas Harte
3ceef2005b Pulled the Z80 from the MicroOpScheduler inheritance tree as it barely uses the thing, and that allows me to make the MicroOp structure private. 2017-06-03 19:17:34 -04:00
Thomas Harte
0f438f524b Merge pull request #124 from TomHarte/Z80
Introduces a decent but as-yet-imperfect implementation of the Z80 processor.
2017-06-03 19:11:21 -04:00
Thomas Harte
24c84ca6f5 Commented out as-yet-unimplemented features. 2017-06-03 19:10:23 -04:00
Thomas Harte
7898f643ac Added bus request/acknowledge logic. 2017-06-03 19:09:47 -04:00
Thomas Harte
7bd45d308a Error was simply failure of the interrupt-mode setter. Fixed. 2017-06-03 18:58:13 -04:00
Thomas Harte
b3da16911f Tweaked timing of mode 0, per contradictory information. Wrote a failing test of mode 2. 2017-06-03 18:42:54 -04:00
Thomas Harte
e52892f75b Added a test of interrupt mode 1. 2017-06-03 18:16:13 -04:00
Thomas Harte
8c41a0f0ed Added a test to confirm interrupts are disabled, and a response to the interrupt cycle within the all-RAM machine. 2017-06-03 17:53:44 -04:00
Thomas Harte
3e9212aaff Plumbed through to allow interrupt tests, wrote an NMI test, corrected the error revealed. 2017-06-03 17:41:45 -04:00
Thomas Harte
a2ec902773 Made an attempt at implementing all three modes of IRQ. 2017-06-03 17:07:05 -04:00
Thomas Harte
1c0130fd02 Cleaned up with a macro, and decided to make absolutely sure that DecodeOperation is functioning as intended by removing the MoveToNextProgram from fetch-decode-execute. 2017-06-03 12:19:25 -04:00
Thomas Harte
3e3d6f97f4 Edged towards being able to implement interrupt mode 0: created a special-case micro-op for incrementing the PC, and formalised that DecodeOperation is a terminal operation. 2017-06-03 12:16:21 -04:00
Thomas Harte
9c3bda0111 Attempted to round out NMI handling. 2017-06-03 11:30:12 -04:00
Thomas Harte
d14902700a Minor syntax and wiring fixes. 2017-06-01 22:33:05 -04:00
Thomas Harte
c95c32a9fe Implemented the reset line program and disabled fictitious automatic power-on reset for the Z80 test machine. 2017-06-01 22:31:04 -04:00
Thomas Harte
35e045d7a7 Made a first attempt at the correct segue into the three main kinds of interrupt, though the programs aren't written yet. So undefined behaviour would abound were an interrupt to occur. But it lets me figure out what effect the check has on performance. I hope little. 2017-06-01 22:16:22 -04:00
Thomas Harte
084e1f3d51 Added a latching of interrupt status before each bus operation, and reset and power-on inputs. 2017-06-01 21:40:08 -04:00
Thomas Harte
5b43cefb85 Started filling an appropriate mask variable with the interrupt request status right now. Which is step one towards implementing interrupts. 2017-06-01 20:34:52 -04:00
Thomas Harte
aab637c9e7 Made check_address_for_trap inlineable. 2017-06-01 18:28:34 -04:00
Thomas Harte
7d9b197383 Pulled the .get() call for fetch-decode-execute out of the main loop. 2017-06-01 18:28:04 -04:00
Thomas Harte
c9dd267ec1 Sketched an interface for signalling interrupts and pulled out some of the repetition in flag setting from ADD/ADC/SUB/SBC/CP. 2017-05-31 22:51:32 -04:00
Thomas Harte
a5254989f8 Rewired the Z80 not to use the program queue, as it's not proven a useful abstraction in practice and doing so yields an immediate 22% speed increase. 2017-05-31 20:15:56 -04:00
Thomas Harte
494ce073b5 Tests having been fixed by instating proper Z80 cycle counting, removed caveman logging. 2017-05-31 19:58:57 -04:00
Thomas Harte
b99e4210ba Eliminated pointless abstraction; I ended up going indirect on instruction pages rather than scheduling methods. 2017-05-31 19:57:03 -04:00
Thomas Harte
d3b74cbc91 Set proper initial value for number_of_cycles_. 2017-05-31 19:55:51 -04:00
Thomas Harte
5ff73faf48 Ensured Zexall can pass. 2017-05-31 19:55:06 -04:00
Thomas Harte
2f7f11e2e5 Added diagnosis props. 2017-05-31 06:54:25 -04:00
Thomas Harte
5119997122 Made an attempt, flawed so far, to find a neat way for processor subclasses to offer bus management as an inline function. 2017-05-30 22:41:23 -04:00
Thomas Harte
b5c1773d59 Eliminated another conditional. Albeit a very predictable one. 2017-05-30 22:15:43 -04:00
Thomas Harte
dfb5057342 Moved repetition group conditions explicitly into the switch statement. 2017-05-30 22:12:10 -04:00
Thomas Harte
7bddd294c9 Resolved an unpredictable conditional and temporarily disabled the Zexalltest as part of the default suite, since it takes so long to run. 2017-05-30 21:03:02 -04:00
Thomas Harte
01f7394f7f Corrected 6502 scheduling when flushing the pipeline. 2017-05-30 20:58:07 -04:00
Thomas Harte
5aa8b03349 Attempted to regularise the 6502 with the Z80 as to scheduling. I think that at least one bug remains. 2017-05-30 20:36:53 -04:00
Thomas Harte
b5ad910b81 Merge branch 'Z80' into StraightPointer 2017-05-30 19:25:38 -04:00
Thomas Harte
da65bae86e Switched to supplying the bus operation by reference, go guarantee that it isn't null. 2017-05-30 19:24:58 -04:00
Thomas Harte
a0189a6fe1 Switched to following the current program via address. 2017-05-30 18:49:40 -04:00
Thomas Harte
244b5ba3c2 Added a proper termination condition for Zexall and, for now, a Mhz counter. 2017-05-30 18:32:38 -04:00
Thomas Harte
960de7bd7b Marginally reduced test machine costs based on usage. 2017-05-30 11:59:07 -04:00
Thomas Harte
c6185baa99 Fixed R incrementation and attempted to make the status flags cheaper to write to. 2017-05-29 22:23:19 -04:00
Thomas Harte
4d4695032c Discovered that Zexall is just really slow. Disabled the address sanitiser, and started working towards a verifiable end. 2017-05-29 21:46:00 -04:00
Thomas Harte
9d29cefe75 Evicted manual memory management. 2017-05-29 21:44:33 -04:00
Thomas Harte
35f535b9a3 Noodled around with initial state. 2017-05-29 19:25:08 -04:00
Thomas Harte
6d22f6fcd5 Having decided the bus operation error on 10 is probably in the test cases, decided to allow myself to skip that one comparison. Back to zero failing cases, and with no more useful information to derive from the FUSE test set for the time being. 2017-05-29 17:17:17 -04:00
Thomas Harte
8bfaa487ce Improved logging of bus operations and corrected placement of the OUT step in that repetition group; was otherwise outputting the wrong side of the B adjustment and therefore to the wrong port (if interpreted as 16 bit). 2017-05-29 17:13:24 -04:00
Thomas Harte
0d067d2f01 Adjusted OTI/etc timing; 23 failures outstanding. 2017-05-29 16:54:45 -04:00
Thomas Harte
d66755fd1e Corrected INI/D[r] timing. Down to 45 failures. 2017-05-29 16:50:52 -04:00
Thomas Harte
267b2add9a Adjusted for where FUSE nominally places timestamps. Down to 92 failures. 2017-05-29 16:44:07 -04:00
Thomas Harte
d290e3d99e Corrected simple logging error. Which mysteriously moves me all the way up to 117 failures (!) 2017-05-29 16:35:00 -04:00
Thomas Harte
a6a4c5a936 Made an attempt to introduce checking of bus activity against the FUSE tests. Appears to suggest 54 new failures. 2017-05-29 15:57:27 -04:00
Thomas Harte
8a8f0cef20 With all intentional opcode entry points now covered, commuted XX into NOP to give proper meaning to otherwise undefined codes. 2017-05-29 12:25:10 -04:00
Thomas Harte
91dc0d5f4a Adjusted HALT to issue never-ending M1 fetches on the next instruction. 2017-05-29 12:20:33 -04:00
Thomas Harte
ed7b07c8b1 Made an attempt to implement HALT as an operation that merely leaves the PC in place, adding the Z80's output line. Included that flag in FUSE tests. Discovered that it does not think that HALT acts that way. Which is probably correct. 2017-05-29 11:54:27 -04:00
Thomas Harte
3f880fa769 Fixed [FD/DD][74/75], which always store H or L, never IXh, IXl, IYh or IYl. 2017-05-29 11:44:26 -04:00
Thomas Harte
d83dd17738 [DD/FD]36 turns out to be a timing error: offset calculation overlaps with value fetch. So the FUSE test was cutting off my implementation early. Fixed. 2017-05-29 11:40:56 -04:00
Thomas Harte
9ade0dcae3 One failure was just PUSH AF due to throwing away the 5 & 3 flags at the start. Switched to throwing them away at comparison. 2017-05-29 11:06:23 -04:00
Thomas Harte
a329d85697 Instituted memory value checks, flushing out seven new failures. 2017-05-29 11:01:45 -04:00
Thomas Harte
c322410783 Corrected CP[I/D]R termination logic; all tests now passing to the extent of interrogation. 2017-05-29 10:52:54 -04:00
Thomas Harte
b67331e018 Fixing the OUT repetition group reduces the code to one failing test. 2017-05-29 10:48:53 -04:00
Thomas Harte
a47b339668 Made an attempt at OUT[I/D]R. 10 failures remaining. None of which, I guess, are due to unimplemented operations. 2017-05-29 10:28:04 -04:00
Thomas Harte
ad56a9215c Implemented IN[I/D]x. 18 failures remaining. 2017-05-29 10:12:33 -04:00
Thomas Harte
c56a5344b9 Implemented CP[I/D]x. 2017-05-29 08:54:00 -04:00
Thomas Harte
1f62cbe21a Reduced LD[I/D}{R} repetition. 2017-05-29 08:24:10 -04:00
Thomas Harte
47845f8c19 Tried to complete the LD[I/D]{R} group. 32 issues remain. 2017-05-28 23:55:54 -04:00
Thomas Harte
409c82ce73 Implemented RLD and RRD. 34 failures remaining. 2017-05-28 16:46:27 -04:00
Thomas Harte
dc3f5b6211 Fixed flag setting for LD A, I and LD A, R, and corrected typo affecting LD DE, (nn). 2017-05-28 16:32:10 -04:00
Thomas Harte
fb02b77e63 Implemented RETI/RETN. 40 warnings remaining. 2017-05-28 16:07:25 -04:00
Thomas Harte
f974d54c7a Implemented IM. 48 failures remain. 2017-05-28 15:55:21 -04:00
Thomas Harte
68978c6e25 Implemented NEG and filled in the load/store and copy parts of the ED page that roll directly off the tongue. 53 issues outstanding. 2017-05-28 15:47:48 -04:00
Thomas Harte
6e83b7d6df Attempted to add a proper exit condition for Zexall. 2017-05-28 15:13:47 -04:00
Thomas Harte
5a4d448cc1 Corrected logical flags; now down to 68 failures, all of them on the ED page. 2017-05-28 15:09:58 -04:00
Thomas Harte
743eac8c55 Implemented EXX to complete the base page. 83 failures. 2017-05-28 14:55:14 -04:00
Thomas Harte
6b66c8f304 Implemented inputs and outputs, determined how to answer port requests to please FUSE and hence reduced failures to 84. 2017-05-28 14:50:51 -04:00
Thomas Harte
c976fbfcd5 Implemented the base-page IN and OUT instructions, bringing FUSE test failures down to 91. 2017-05-28 14:20:05 -04:00
Thomas Harte
ed3e38ac31 Performed some quick tidying. 2017-05-28 00:12:42 -04:00
Thomas Harte
76f03900d2 Implemented EX HL, (SP) so as, allowing for indexed pages, to bring issues below the psychological 100 barrier. To 99. 2017-05-28 00:02:14 -04:00
Thomas Harte
035df316aa FUSE seems to have inconsistent ideas about where b3 and b5 come from in more-complicated BIT instructions. So I'm not testing them for now. Within that reality, reduced to 102 failures. 2017-05-27 23:54:53 -04:00
Thomas Harte
9759a04c7d Timing fixes: the fetch-decode-execute pattern is now per-page, since that on [DD/FD]CB not only doesn't increment R but doesn't take four cycles, so is probably a normal read cycle. Adjusted timing all around. 2017-05-27 23:54:06 -04:00
Thomas Harte
c7cb47a1d8 Readded and then disabled my temporary one-test-only patch. Failures are currently at 237. 2017-05-27 21:10:25 -04:00
Thomas Harte
0d2d04e17b Seeking proper [F/D]DCB emulation: the offset comes before the final byte of opcode, and adding seems to overlap with the opcode fetch, which does not increment R. Also needs to duplicate the result to visible registers. 2017-05-27 21:06:56 -04:00
Thomas Harte
98423c6e41 Accepted FUSE's view of bits 3 & 5 from BIT and RES, reducing to 623 issues. 2017-05-27 16:19:15 -04:00
Thomas Harte
33c3fa21e3 Fixed (HL)/(In + d) CB page modify instructions. Reducing failures to 672. 2017-05-27 15:54:24 -04:00
Thomas Harte
2141d52794 Corrected typo. Now at 696 failures. 2017-05-27 15:41:26 -04:00
Thomas Harte
16b8021401 Made a stab at the CB pages. 2017-05-27 15:39:22 -04:00
Thomas Harte
151b09b5ca Fixed various other obvious cases for indexing. 2017-05-26 23:37:17 -04:00
Thomas Harte
9bc2b48d9b Found a form I like for indexed addressing, applying it only where obvious for now. Which eliminates more than a couple of hundred of remaining failures. 2017-05-26 23:23:33 -04:00
Thomas Harte
ab8a98f1df Implemented RST. 2017-05-26 07:29:19 -04:00
Thomas Harte
efe354a7b1 Fixed half carry after logical operation.s 2017-05-25 22:55:04 -04:00
Thomas Harte
d50d3fc837 Implemented CPL, SCF and CCF. 2017-05-25 22:51:08 -04:00
Thomas Harte
83ee92af1a Made DAA work sufficiently well for the FUSE test. 2017-05-25 22:41:05 -04:00
Thomas Harte
ea0ad9fd87 Took a shot at DAA, seemingly not to Fuse's liking though. 2017-05-25 22:17:48 -04:00
Thomas Harte
ff3c60c0e1 Implemented the conditional JRs. 2017-05-25 21:51:30 -04:00
Thomas Harte
399703a471 Implemented JR. 2017-05-25 21:48:28 -04:00
Thomas Harte
82017c4aea Implemented DJNZ. 2017-05-25 21:44:24 -04:00
Thomas Harte
bdf07c3dc9 Implemented EX AF, AF'. 2017-05-25 21:26:32 -04:00
Thomas Harte
598be24644 Fixed overflow for 8-bit decrementing. 2017-05-25 21:23:38 -04:00
Thomas Harte
e4e71a1e5f Switched back to descriptive failures, but put a cap on them. 2017-05-25 21:08:24 -04:00
Thomas Harte
fba5af280e Shortened failure message, at least for now. 2017-05-25 21:05:47 -04:00
Thomas Harte
c668ff9472 Added incrementing of the refresh register. 2017-05-25 21:01:52 -04:00
Thomas Harte
2cadc706e2 Now runs FUSE tests, albeit testing only a subset of the results. But enough to get started. 2017-05-25 21:00:33 -04:00
Thomas Harte
3c6f63abcc Started towards running the FUSE tests. Just need to deal with the memory segments. 2017-05-25 19:12:59 -04:00
Thomas Harte
00cd7e7e9c After hitting my head against the wall of trying to use [NS]Scanner as a parser some more, have given up and transcoded the two tests files to JSON. 2017-05-25 18:20:13 -04:00
Thomas Harte
055c860b43 Sealed off RegisterState as immutable, and started trying to parse the .expected file. 2017-05-23 22:32:36 -04:00
Thomas Harte
454c8628c3 Implemented an additional constructor for RegisterStates, pulling it out into file-level scope and implementing Equatable. 2017-05-23 22:05:33 -04:00
Thomas Harte
a23a6db4d6 Tidied up, creating a holder for RegisterState and giving it deserialisation logic. This makes sense because a register state will also need to be taken from the outputScanner, and from the machine. 2017-05-23 08:13:24 -04:00
Thomas Harte
6575091a78 Fixed Z80's ownership of its fetch-decode-execute program, its habit of scheduling invalidly when hitting an unrecognised operation and the test machine's habit of dereferencing invalidly. 2017-05-22 21:50:34 -04:00
Thomas Harte
9e25d014d2 Made an attempt to log bus activity for comparison with FUSE results. 2017-05-22 19:49:38 -04:00
Thomas Harte
41d5dd8679 Added a memory access delegate to the Z80 all-ram processor, to allow access patterns to be captured. 2017-05-22 19:24:11 -04:00
Thomas Harte
c3ea6dc1f5 Added respect for limiting to the requested number of cycles in the Z80. 2017-05-22 19:15:55 -04:00
Thomas Harte
22afa509ca Got to a parsing and towards an attempt to run FUSE tests. 2017-05-22 19:14:46 -04:00
Thomas Harte
3fb3cc8269 Got explicit about encodings. 2017-05-21 22:53:06 -04:00
Thomas Harte
e3e461d7cb Added a test class for running the FUSE tests. With nothing much in it. 2017-05-21 22:49:24 -04:00
Thomas Harte
c16fccb317 Fixed file names. 2017-05-21 22:43:07 -04:00
Thomas Harte
b9cffdf2bd Imported the FUSE tests. 2017-05-21 22:42:20 -04:00
Thomas Harte
f2aae72cc2 Fixed the 16-bit ADCs and SBCs, added INC (HL) and DEC (HL). Zexall now enters a seemingly-infinite loop. Which is progress, at least. 2017-05-21 20:43:36 -04:00
Thomas Harte
fe8db1873c Added 16-bit ADC and SBC table entries; once again extended logging. 2017-05-21 20:32:06 -04:00
Thomas Harte
c66c715ac9 Starts to try to figure out how to implemented the index register pages, but doesn't yet read offsets. 2017-05-21 19:26:40 -04:00
Thomas Harte
5dcfd85642 Added a compact and copy stage for instruction pages, both [mostly] eliminating the mistake of letting static data structures contain pointers to instance storage and opening the door for addition of the DD and FD pages. 2017-05-21 19:15:52 -04:00
Thomas Harte
c70dfe1b09 Implemented the two variations of loading between (nn) and SP. 2017-05-21 13:20:28 -04:00
Thomas Harte
232c591655 Threw in a little macro documentation and a missing macro. 2017-05-21 13:13:21 -04:00
Thomas Harte
790614b544 Added EI and DI. 2017-05-21 12:53:17 -04:00
Thomas Harte
32c032cd97 Implemented a couple of easy-to-add missing base page instructions. 2017-05-21 10:18:43 -04:00
Thomas Harte
e48ee16366 Continued cleaning efforts, added conditional RET. 2017-05-21 10:13:59 -04:00
Thomas Harte
e92d936ce8 Added conditional calls. 2017-05-21 10:03:46 -04:00
Thomas Harte
4e210c5396 Added LD A, (nn). 2017-05-21 10:00:10 -04:00
Thomas Harte
3d3e60b1fc Implemented LD (HL), r. 2017-05-21 09:56:41 -04:00
Thomas Harte
f3f0e2f1a9 Implemented RRA and RRCA. 2017-05-21 09:52:19 -04:00
Thomas Harte
08206eea56 This logging has outlived its usefulness for now. 2017-05-21 09:47:53 -04:00
Thomas Harte
78296246e8 Added ALU n. 2017-05-21 09:46:18 -04:00
Thomas Harte
85b5dd35b1 Took a shot at 8-bit arithmetic. 2017-05-21 09:43:17 -04:00
Thomas Harte
11cfaa3e3d Performed light syntactic cleaning on the first part of the base page table, eliminated redundant temporary variables, implemented 8-bit increment and decrement. 2017-05-21 09:17:30 -04:00
Thomas Harte
103c863534 Through temporarily dramatically increased logging, fixed conditional JP. 2017-05-20 23:03:52 -04:00
Thomas Harte
6688f83226 Took a shot at LDIR. 2017-05-20 21:58:24 -04:00
Thomas Harte
01a064dd63 Added an empty ED page. 2017-05-20 17:29:30 -04:00
Thomas Harte
7b234078ae Implemented EX DE, HL and shuffled to allow instruction pages. 2017-05-20 17:04:25 -04:00
Thomas Harte
add02a7897 Added LD (nn), A, and reduced double logging to single for now. 2017-05-19 23:13:28 -04:00
Thomas Harte
19167df692 Consolidated and filled in AND and XOR. 2017-05-19 23:03:34 -04:00
Thomas Harte
6766845e21 Filled in most of the loads. 2017-05-19 22:57:43 -04:00
Thomas Harte
bc3b5f3e35 Added 16-bit INCs and DECs. Which don't set flags, so are easy. 2017-05-19 22:13:36 -04:00
Thomas Harte
5fe23113ec Moved RET to the correct place, implemented POP AF. 2017-05-19 22:03:12 -04:00
Thomas Harte
c55e1c1d17 Implemented POP and therefore RET; corrected timing of PUSH. 2017-05-19 21:59:45 -04:00
Thomas Harte
d910405648 Added enough infrastructure to be able to react to the two CP/M calls this cares about. 2017-05-19 21:53:39 -04:00
Thomas Harte
62b432c046 Added the concept of a trap handler to the all-RAM processor and exposed it via the test Z80 classes. 2017-05-19 21:20:28 -04:00
Thomas Harte
eae1f78221 Implemented the main page pushes. 2017-05-19 19:28:38 -04:00
Thomas Harte
11d05fb3b8 Expanded a little on operations, added an implementation or two. 2017-05-19 19:18:35 -04:00
Thomas Harte
58efca835f Sought to add a further opcode. 2017-05-18 22:53:43 -04:00
Thomas Harte
da6e520b91 Merge branch 'master' into Z80 2017-05-18 22:30:51 -04:00
Thomas Harte
a5099f69d8 Merge pull request #127 from TomHarte/OricShift
Maps either Mac shift key to both Oric shifts
2017-05-18 22:27:47 -04:00
Thomas Harte
9398b6c2c8 Unable to differentiate, decided to map a Mac shift key to both Oric shifts. 2017-05-18 22:25:59 -04:00
Thomas Harte
99f2060fc1 Further improved macros. 2017-05-18 22:11:54 -04:00
Thomas Harte
5d3ebcb35a Made a first attempt at LD HL, (nn). 2017-05-17 22:42:30 -04:00
Thomas Harte
509d011fbe Implemented JP, my first Z80 operation. 2017-05-17 22:31:41 -04:00
Thomas Harte
17ffd604bf Made an attempt to get the Z80 at least as far as rejecting an opcode. 2017-05-17 21:45:23 -04:00
Thomas Harte
a3dafa9056 Abbreviated uses of enumerations. 2017-05-17 21:44:08 -04:00
Thomas Harte
21d0602305 Restored the all RAM 6502's lack of power-on reset. 2017-05-17 21:43:40 -04:00
Thomas Harte
64d6ee1be5 Adjusted slightly to adapt to latest Swift warnings. 2017-05-17 07:49:48 -04:00
Thomas Harte
1378ab7278 Ensured initial program counter and stack pointer are correct for Zexall, fixed the Z80 to use a compile-time polymorphic call for bus access. 2017-05-17 07:36:06 -04:00
Thomas Harte
87a021ec2d Made further attempt to get as fas as having the Z80 attempt to do something. 2017-05-16 22:19:40 -04:00
Thomas Harte
189317b80c Added enough of a Z80 test machine to bridge up into Swift. 2017-05-16 22:05:42 -04:00
Thomas Harte
4f0775cc7c Imported the Zexall.com tester, as a first thing to throw at the Z80 to be. 2017-05-16 21:37:09 -04:00
Thomas Harte
7190f927b7 Factored out the stuff that both all-RAM processors would share, rather than duplicating it. 2017-05-16 21:28:17 -04:00
Thomas Harte
d559d8b901 Continued edging towards getting the absolute basics of a testable Z80, for test-driven development. Corrected old-fashioned instance naming issues with the corresponding 6502 class and removed an unnecessary source file while at it. 2017-05-16 21:19:17 -04:00
Thomas Harte
2562306802 Merge branch 'master' into Z80 2017-05-16 21:05:00 -04:00
Thomas Harte
15394358df Merge pull request #126 from TomHarte/GCRAnalysis
Corrects infinite loop when performing GCR analysis
2017-05-16 20:54:24 -04:00
Thomas Harte
df4d4467b3 Ensured GCR parser spins the disk. 2017-05-16 20:53:06 -04:00
Thomas Harte
67ec0b9e6c Merge pull request #125 from TomHarte/SampledComposite
Formalises reasoning for the colour phase clamp and offset...
2017-05-16 20:47:20 -04:00
Thomas Harte
2ee8a7056e Corrected TIA no longer to assume phase is an automatic quarter askew. 2017-05-16 20:43:28 -04:00
Thomas Harte
a5075d9eb5 Formalised the reasoning behind the colour phase fix-up and made it an opt-in per-caller value. Only the Oric currently needs to opt in. 2017-05-16 20:31:39 -04:00
Thomas Harte
50bb4f0142 There's finally a loop in here, at least. 2017-05-15 22:25:52 -04:00
Thomas Harte
df80c37adb Renamed TestMachine to TestMachine6502 since there's going to be multiple of them. 2017-05-15 08:18:57 -04:00
Thomas Harte
7da51602d5 Moved flush, added run_for_cycles, which does nothing right now. 2017-05-15 07:59:21 -04:00
Thomas Harte
5152517887 Added the boilerplate stuff necessary to query registers. 2017-05-15 07:55:53 -04:00
Thomas Harte
eb8a2de5d6 Settled definitively on flush as more communicative than synchronise (and slightly more locale neutral); culled some more duplication from the Z80. 2017-05-15 07:38:59 -04:00
Thomas Harte
f2a1a906ff Adapted what negligible amount there is of the z80 as per the new CPU namespace. 2017-05-14 22:15:16 -04:00
Thomas Harte
0808e9b6fb Pulled the 6502 into a CPU namespace, making it an instance of something that has micro-opcodes and schedules them, and factoring out the formulation of a register pair. 2017-05-14 22:08:15 -04:00
Thomas Harte
b81a2cc273 First tentative steps towards adding a Z80 implementation. 2017-05-14 17:46:41 -04:00
Thomas Harte
abeaedf16f Merge pull request #123 from TomHarte/RemovedDeadOption
Formally withdraws the 'load automatically' option for the Vic
2017-05-14 17:03:33 -04:00
Thomas Harte
8e35e913bb Formally withdrew the 'load automatically' option for the Vic, having removed that option elsewhere. 2017-05-14 16:59:24 -04:00
Thomas Harte
81c5f4ab19 Merge pull request #122 from TomHarte/16bit6560
Increases VIC-I intermediate format to 16 bit and corrects output colours
2017-05-13 22:03:08 -04:00
Thomas Harte
e270b726b3 Tweaked blue, increased saturation. 2017-05-13 22:01:02 -04:00
Thomas Harte
c2b5a9bb1f Minor fix: given that phase is now a function of position, stop nudging position. 2017-05-13 21:50:48 -04:00
Thomas Harte
44ce7fa54c Corrected luminances across the board, and PAL colours. 2017-05-13 21:50:09 -04:00
Thomas Harte
b0142cf050 Made an updated stab at NTSC colours. 2017-05-13 14:29:36 -04:00
Thomas Harte
a340331229 Introduced 1-bit of saturation, returning black and white as black and white. 2017-05-11 21:31:58 -04:00
Thomas Harte
b14c892740 Switched to a safer RAII approach to this lock. 2017-05-10 21:29:39 -04:00
Thomas Harte
15d17c12d5 Switched the 6560 to two bytes per pixel, since one isn't sufficient for precision and because mixing up the implementation might help me to figure out what's amiss. 2017-05-09 21:22:01 -04:00
Thomas Harte
99800d9840 Merge pull request #121 from TomHarte/VicColours
Permits ROM-area located PRGs that are not a power-of-two in size
2017-05-08 22:18:47 -04:00
Thomas Harte
5d91a2600d Permitted ROM-style PRGs that are not a power-of-two in size, and added extra safety checks on loading data from a tape. 2017-05-08 22:15:35 -04:00
Thomas Harte
cb66c7e2dc Performed some minor tidying. 2017-05-08 21:05:35 -04:00
Thomas Harte
58488c93be Merge pull request #120 from TomHarte/Vic20Colours
Corrects — and improves — Vic-20 tape loading
2017-05-08 20:59:29 -04:00
Thomas Harte
61f8f2f18c Switched to a more straightforward way of exiting from tape data loading. 2017-05-08 20:58:55 -04:00
Thomas Harte
7b43ae0a92 Implemented a catch for loading the data portion of files. 2017-05-07 22:22:59 -04:00
Thomas Harte
2807e3134f Implemented speedy header finding. So that's half of it. 2017-05-07 20:32:48 -04:00
Thomas Harte
0771363f3b Removed one piece of unnecessary logging. 2017-05-06 22:22:03 -04:00
Thomas Harte
1f56e85f6d Centralised resetting of tape files within the static analyser, having implemented it patchily. 2017-05-06 22:19:08 -04:00
Thomas Harte
2edf73908c Temporarily disabled the existing fast loading implementation in pursuit of another, and started trying to correct the lack of connection between the userport VIA and the tape drive. 2017-05-06 22:00:12 -04:00
Thomas Harte
6a37a02eee Switched to std::ostringstream to avoid the need to make a string length count (and I was one off when loading a disk). 2017-05-06 19:55:42 -04:00
Thomas Harte
5998123868 Added some consts, for a minor safety improvement. 2017-05-06 19:53:24 -04:00
Thomas Harte
26cb903b08 Merge pull request #119 from TomHarte/C11
Ejects the deprecated OSAtomicTestAnd[Re]Set test-and-[re]set in favour of C11's atomic_flag.
2017-04-15 21:45:48 -04:00
Thomas Harte
92a8b68859 Dumped Mach-specific test-and-set in favour of ordinary C11. 2017-04-15 21:41:59 -04:00
Thomas Harte
4127350abe Merge pull request #118 from TomHarte/HighResolutionAtariAudio
Introduces a higher source sampling rate for Atari audio
2017-04-15 21:21:58 -04:00
Thomas Harte
ed6b135015 Made final switch to permit high-sampling rate Atari audio. 2017-04-15 21:18:00 -04:00
Thomas Harte
f95015c7f6 Pulled out the divisor for audio. 2017-04-03 21:16:39 -04:00
Thomas Harte
defec2c9b0 Fixed: operation reads now fulfil the promise of seeding the value to be read with 0xff. 2017-03-26 20:56:27 -04:00
Thomas Harte
04921e64de Merge pull request #117 from TomHarte/AudioRace
Works around what appears to be a bug in Apple's AudioQueue.
2017-03-26 20:29:41 -04:00
Thomas Harte
bdd432fe1d Added an ugly workaround for the empirical sound shutdown issues. 2017-03-26 20:28:04 -04:00
Thomas Harte
6e9ab9f330 Merge pull request #116 from TomHarte/YarsBug
Fixes a bug in which TIA access during the border would flush all motion steps.
2017-03-26 18:38:51 -04:00
Thomas Harte
814c0ada13 Fixed action counts for border motion. 2017-03-26 18:33:05 -04:00
Thomas Harte
dfc468f220 Locked down all initial state. 2017-03-26 15:47:04 -04:00
Thomas Harte
6817b38322 Merge pull request #115 from TomHarte/Style
Settles my C++ curly bracket style indecision
2017-03-26 14:37:23 -04:00
Thomas Harte
e01f3f06c8 Completed curly bracket movement. 2017-03-26 14:34:47 -04:00
Thomas Harte
3229502fa1 Standardised curly bracket placement across the Atari. 2017-03-23 21:59:16 -04:00
Thomas Harte
a4c5eebd1e The latest Atari Age-discovered numbers suggest this starts up in 1024T mode. 2017-03-21 18:22:50 -04:00
Thomas Harte
a3c22d5abb Merge pull request #114 from TomHarte/Pitfall2
Adds support for the Pitfall 2 DPC. And therefore for Pitfall 2.
2017-03-20 20:44:43 -04:00
Thomas Harte
a26b87f348 Fixed: mistake was failure to count ready cycles. 2017-03-20 20:44:03 -04:00
Thomas Harte
4c3cc42c91 This gives a very noisy version of the real audio. 2017-03-20 20:38:29 -04:00
Thomas Harte
f3f4e1a541 Made a first, hacky, attempt at audio. 2017-03-20 19:35:51 -04:00
Thomas Harte
4722f6b5c4 Fixed sprite disappearance: test should be applied predecrement, not post — it relates to the address being used this access, not the next one. 2017-03-19 18:58:35 -04:00
Thomas Harte
7d8d1c7828 Fixed 'random' number generator. 2017-03-19 18:54:35 -04:00
Thomas Harte
4bb70e7d31 Resetting the mask upon low byte write appears to resolve some issues. 2017-03-19 18:49:37 -04:00
Thomas Harte
321030bb44 Added a slightly faulty but seemingly 'close' version of masking. 2017-03-19 18:28:06 -04:00
Thomas Harte
6c161b1150 This gives something that might be the correct background. 2017-03-19 17:49:48 -04:00
Thomas Harte
d5c37c8619 Shushed a little, so as to be able to see a reasonable amount of output during my lifetime. 2017-03-19 17:38:54 -04:00
Thomas Harte
c445eaec3e Switched startup values, following a comment on AtariAge. May or may not be correct, the thread was speculative. 2017-03-19 17:38:26 -04:00
Thomas Harte
7c66c36d3f Attempted at least to manage appropriate data storage. 2017-03-19 17:31:08 -04:00
Thomas Harte
031a68000a Added a class to contain the Pitfall 2 pager and a skeleton of initial work. 2017-03-18 22:08:47 -04:00
Thomas Harte
7d7b665be8 Merge pull request #113 from TomHarte/PagingTemplates
Pulls the Atari 2600 paging schemes out into individual classes
2017-03-18 21:05:01 -04:00
Thomas Harte
c3d82f88a5 Tidied up and commented on the Activision stack implementation. 2017-03-18 21:01:58 -04:00
Thomas Harte
c033bad0b9 Here's MNetwork! 2017-03-18 20:51:49 -04:00
Thomas Harte
c31d85f820 Re-emplaced the MegaBoy. Also cut detritus from the main Atari header. 2017-03-18 19:02:34 -04:00
Thomas Harte
217fbf257e CBS RAM Plus returns. 2017-03-18 18:56:20 -04:00
Thomas Harte
0b611a14b9 Tigervision paging returns. 2017-03-18 18:50:13 -04:00
Thomas Harte
df6861c9dc Parker Bros paging is back. 2017-03-18 18:21:01 -04:00
Thomas Harte
a4cd12394e Reinstated the Activision stack pager. 2017-03-18 18:03:48 -04:00
Thomas Harte
e0bca1e37b Reinstated the 16 and 32 kb Atari pagers, and ensured the 6532 always starts in a valid state. 2017-03-18 17:34:34 -04:00
Thomas Harte
55ce851bb2 Fixed types of the 8k cartridges, ensured the 6502 starts without an IRQ request history. 2017-03-18 17:04:01 -04:00
Thomas Harte
e8d34f2eb4 Having farmed out the bus, the Atari itself no longer is/owns a 6502. 2017-03-18 16:34:41 -04:00
Thomas Harte
bb3daaa99b Sought to reintroduce the Atari 8k paging scheme, at the same time deciding to do away with the copy and paste of holding on to ROM data. 2017-03-18 15:04:01 -04:00
Thomas Harte
36b58d03b7 Formalised read bus value guarantee from the 6502, fixed missing clock signal wiring on the Atari cartridge class, reintroduced CommaVid support. 2017-03-18 14:46:46 -04:00
Thomas Harte
7958459db9 In theory unpaged cartridges should now work. Time to debug. 2017-03-18 14:01:04 -04:00
Thomas Harte
14a76af0d3 Started trying to float out bus control to cartridges. 2017-03-17 20:28:07 -04:00
Thomas Harte
a04a58e01f Merge pull request #112 from TomHarte/LineTiming
Fixes declared Atari line length.
2017-03-14 21:34:19 -04:00
Thomas Harte
afbd9fd41b Fixed declared line length. 2017-03-14 21:33:38 -04:00
Thomas Harte
3d53d4e55e Merge pull request #111 from TomHarte/ActivisionStack
Implements the Activision stack paging scheme.
2017-03-14 20:41:04 -04:00
Thomas Harte
7302703039 Implemented the Activision stack paging scheme. 2017-03-14 20:24:05 -04:00
Thomas Harte
97a8a96593 Rejigged how the memory map is handled and implemented MNetwork support. 2017-03-14 20:07:54 -04:00
Thomas Harte
be2e99077e Merge pull request #110 from TomHarte/MegaBoy
Adds emulation of the MegaBoy paging scheme.
2017-03-14 17:40:41 -04:00
Thomas Harte
3b29276228 Implemented the MegaBoy paging scheme. 2017-03-14 17:40:01 -04:00
Thomas Harte
4a528b9ecb Merge pull request #109 from TomHarte/CBSRamPlus
Adds emulation of the CBS RAM Plus paging scheme.
2017-03-14 17:26:09 -04:00
Thomas Harte
b3632a4e86 Institutes CBS RAM Plus emulation. 2017-03-14 17:25:10 -04:00
Thomas Harte
8a659e3117 Merge pull request #108 from TomHarte/PitfallIIDetection
Adds tested detection of the Pitfall II and MegaBoy paging schemes.
2017-03-13 20:44:28 -04:00
Thomas Harte
a6897ebde0 Added an attempt to distinguish the MegaBoy (now with proper capitalisation) and a test for it. 2017-03-13 20:43:12 -04:00
Thomas Harte
582da14a14 Added an enumerated type and detection of Pitfall 2. 2017-03-13 08:15:36 -04:00
Thomas Harte
b81bf6b547 Merge pull request #107 from TomHarte/PagingTests
Introduces unit tests for the Atari static analyser
2017-03-12 22:17:17 -04:00
Thomas Harte
8e147444d5 Added a readme, as is traditional for folders I'm excluding from Git. 2017-03-12 22:16:12 -04:00
Thomas Harte
a9964ee0c8 Simplified CommaVid test. 2017-03-12 22:14:39 -04:00
Thomas Harte
b671df9906 'Perfected' the Activision stack paging detector. 2017-03-12 22:11:58 -04:00
Thomas Harte
0bcf9c30de Added an attempt, at least, at spotting the Activision titles. Which reduces back to only the one breakage, but this is getting a bit too much like spotting the hard-coded bytes. 2017-03-12 21:14:12 -04:00
Thomas Harte
2c07cce282 Had the wrong paging scheme listed for Robot Tank and Thwocker. Better to get this right before trying to come up with a test for the Activision stack scheme. 2017-03-12 21:03:10 -04:00
Thomas Harte
8c5e39c0c5 Adjusted Super Chip test to look for two portions of 128 bytes. It now spots Dig Dug and doesn't acquire any further false positives amongst the tested set. So failure to spot the Activision stack-based paging mechanism is now the only remaining failure. 2017-03-12 20:39:45 -04:00
Thomas Harte
cae48aaa95 Added an MNetwork test. Reduces failures to two of the set that I have: Dig Dug, which uses a Super Chip but has inconsistent bytes in its image, and Decathlon, which uses the Activision stack paging mechanism for which I don't yet have detection code. 2017-03-12 18:54:49 -04:00
Thomas Harte
37f4f6ba14 Cut down to one disassembly, now I know a bit more about how the non-Atari schemes work. 2017-03-12 17:50:24 -04:00
Thomas Harte
597bd97b01 Corrected two more table errors. 2017-03-12 15:46:25 -04:00
Thomas Harte
38de5300e5 Elevator Action seemingly uses a Super Chip. 2017-03-12 15:43:42 -04:00
Thomas Harte
62b3c9dda8 Corrected 8k Tigervision test, putting detection failures below 10 (i.e. at 9) for the first time. 2017-03-12 15:41:48 -04:00
Thomas Harte
146f3ea0f5 Fixed: Crystal Castles is 16kb. 2017-03-12 15:39:07 -04:00
Thomas Harte
af9b7fbc30 Switched to a voting method for classifying 8kb ROMs. 2017-03-12 15:36:01 -04:00
Thomas Harte
78213f1e95 Fixed a couple more table entries, introduced per-size tests (plus a catch-all), to speed up the development/testing cycle. 2017-03-12 15:35:36 -04:00
Thomas Harte
de347ad7c8 Improved CBS RAM Plus and Super Chip detection exclusion, reducing error count to 15. 2017-03-12 14:03:17 -04:00
Thomas Harte
a4bba8a92e Made a couple of lookup table fixes and corrected RAM region detection windows; failures now down to 19. 2017-03-11 23:18:30 -05:00
Thomas Harte
fcacfc2726 Tidied up spacing, slightly. 2017-03-11 23:01:42 -05:00
Thomas Harte
bab464e765 I'm far from confident, but this should reduce the deviations close to those that result from mistakes by the static analyser, rather than table errors. 2017-03-11 22:58:11 -05:00
Thomas Harte
2879763c34 Reduced to 84 failures through more accurate tabulation. 2017-03-11 21:52:52 -05:00
Thomas Harte
ea2ea30193 Fleshed entire table out with most common values. Exceptions now to fix. 2017-03-11 21:11:25 -05:00
Thomas Harte
608569cc48 Typed out all the 'A's that I am aware of. So about 5% done. 2017-03-11 20:58:38 -05:00
Thomas Harte
c7e973aab4 Extended test set a little, corrected current failures. 2017-03-11 20:51:25 -05:00
Thomas Harte
443d57bc32 Slimmed output and added first six tests. Acid Drop fails since I'm not yet declaring Atari 16k and Atari 32k. 2017-03-11 20:43:19 -05:00
Thomas Harte
57ec756f5b Started speccing out a unit test for Atari ROM analysis. 2017-03-11 20:33:58 -05:00
Thomas Harte
9286a5ba73 Added a new folder to the exclusion list, to contain commercial Atari images, to unit test my paging hardware selection logic. 2017-03-11 19:42:23 -05:00
Thomas Harte
1c9dffe41f Added an earlier exit. 2017-03-11 19:38:05 -05:00
Thomas Harte
8c7f724ce4 Merge pull request #106 from TomHarte/Tigervision
Adds detection and emulation of the Tigervision paging scheme
2017-03-11 18:18:29 -05:00
Thomas Harte
b193248056 Ensured that queue is not touched at all outside of the critical section. 2017-03-11 18:17:09 -05:00
Thomas Harte
f0d944847b Fixed setting of the second 1kb. 2017-03-11 18:16:29 -05:00
Thomas Harte
a72d70e707 Enabled code coverage calculation for unit tests. 2017-03-11 17:44:56 -05:00
Thomas Harte
add14fb43a Made an attempt to implement Tigervision paging. 2017-03-11 17:44:35 -05:00
Thomas Harte
38ce4dc56c Fixed potential deadlock, if a delegate decided to dealloc the queue as a result of its prompting. 2017-03-11 17:44:02 -05:00
Thomas Harte
bce5abd33b Made an attempt to spot Tigervision paging requests. 2017-03-11 13:12:23 -05:00
Thomas Harte
3f36eeb071 Merge pull request #105 from TomHarte/ParkerBros
Introduces detection and emulation of the Parker Bros paging scheme
2017-03-11 13:05:17 -05:00
Thomas Harte
33bda2d40c Switched to image inspection for RAM guesses rather than disassembly. Which fixes the other Parker Bros titles. 2017-03-11 13:04:23 -05:00
Thomas Harte
2b5e3a600e Made a first attempt at implementing the Parker Bros pager within the emulation. 2017-03-11 12:43:12 -05:00
Thomas Harte
8dbf9fd302 Started adding an attempt to distinguish between Atari and Parker Bros paging schemes. 2017-03-11 11:41:18 -05:00
Thomas Harte
9c72ce5bd2 Ensured a settling delay is permitted before an NTSC/PAL decision is made. To avoid false switches just due to startup. 2017-03-06 19:37:35 -05:00
Thomas Harte
ec2762509b Merge pull request #104 from TomHarte/SyncDetection
Improves vertical sync recognition
2017-03-06 19:18:52 -05:00
Thomas Harte
e63229a5e5 Pulled vertical sync detection entirely outside the loop, and gave it greater perspective. 2017-03-06 19:15:33 -05:00
Thomas Harte
ad73379d1c Took vertical sync detection logic entirely out of the loop. 2017-03-05 20:17:55 -05:00
Thomas Harte
abd4d2c42a Extended has-RAM test to check all banks. 2017-03-05 11:58:52 -05:00
Thomas Harte
79784a8e57 Followed tip: missiles are locked to the position after four output pixels, not four input pixels. 2017-03-04 22:23:50 -05:00
Thomas Harte
61b8fc1e2f Merge pull request #103 from TomHarte/CRTCounting
Fixes NTSC colour cycle count
2017-03-04 17:32:45 -05:00
Thomas Harte
4751615623 Fixed NTSC colour cycle count, and hence the 2600's reported line lengths and phase offset. 2017-03-04 17:31:39 -05:00
Thomas Harte
cccdc558e7 Merge pull request #102 from TomHarte/6532Accuracy
Improves 6532 counting accuracy
2017-03-04 17:04:43 -05:00
Thomas Harte
d3257c345a Tested against public ROMs and corrected. Also moved the deferred adjustment into a more canonical place. 2017-03-04 17:00:28 -05:00
Thomas Harte
e09b76bf32 Fixed 'same value, then immediate increment, then proper counting increments' behaviour and ensured it takes one cycle to commit a value. Adjusted tests to match. 2017-03-04 15:57:54 -05:00
Thomas Harte
837cccdf83 Switched to deferred updates for the 6532. 2017-03-04 14:58:28 -05:00
Thomas Harte
a3fcd15980 Loosened sync charge level requirement. 2017-03-01 22:16:56 -05:00
Thomas Harte
93d1573481 Added a fix for certain homebrews. 2017-03-01 07:59:25 -05:00
Thomas Harte
893a5dd007 Added an artificial low pass filter, attempting to capture post-digital effects. 2017-02-28 21:29:55 -05:00
Thomas Harte
026b418b4a Ensured filtered 1:1 audio resampling is applied. 2017-02-28 21:27:38 -05:00
Thomas Harte
06dd98b23c Pulled the reset time for horizontal blank extend up to position 224. 2017-02-28 20:28:54 -05:00
Thomas Harte
2a81ae1dec Now required: at least four stores for RAM to be detected. 2017-02-28 20:28:14 -05:00
Thomas Harte
1625b9c7f9 Merge pull request #101 from TomHarte/CommaVid
Adds detection and emulation of the CommaVid paging scheme
2017-02-27 20:53:19 -05:00
Thomas Harte
184c8ae707 Extended to emulate the CommaVid. 2017-02-27 20:50:59 -05:00
Thomas Harte
8f8b103224 Slightly tidier. Also in the interim: confirmed no remaining false positives or negatives from the existing published set. 2017-02-27 08:42:43 -05:00
Thomas Harte
1af415a88e Okay, this is a bit desperate, but worth investigation. 2017-02-27 08:39:53 -05:00
Thomas Harte
fe07cd0248 Made another attempt to distinguish. 2017-02-27 08:06:57 -05:00
Thomas Harte
a3d339092e Corrected callers of the 6502 disassembler. 2017-02-27 07:56:59 -05:00
Thomas Harte
837216ee9a Adjusted semantics to allow for more complicated mappings, e.g. whereby supplied data repeats itself within a range. 2017-02-27 07:49:33 -05:00
Thomas Harte
dcd0c90283 Switched time of best-effort updater delegate setting, to avoid a callback before setupClockRate has happened, and therefore before it's clear what should be going on with audio. 2017-02-26 21:58:59 -05:00
Thomas Harte
b24cd00a39 Switched time of best-effort updater delegate setting, to avoid a callback before setupClockRate has happened, and therefore before it's clear what should be going on with audio. 2017-02-26 21:58:43 -05:00
Thomas Harte
0273860018 Sought to weed out further false positives on CommaVid, partly by introducing a record of internal calls, to pair with the now confusingly named outward_calls. 2017-02-26 21:58:09 -05:00
Thomas Harte
82c089cde4 Factored out paging type detection and, from that, took out 2k cartridge detection. Added an attempt at a CommaVid detector, which does not currently work. 2017-02-26 21:24:54 -05:00
Thomas Harte
997707a45b Merge pull request #100 from TomHarte/SuperChipDetection
Adds detection and emulation of the Super Chip
2017-02-26 18:04:50 -05:00
Thomas Harte
9d7985c1e1 Added Super Chip emulation. 2017-02-26 17:47:29 -05:00
Thomas Harte
8b1ec827e0 Made an initial, very naive attempt to recognise two types of expanded cartridge: those with a superchip and those with a CBS RAM+, in both cases by looking for instructions that appear to write into cartridge space. 2017-02-26 17:11:57 -05:00
Thomas Harte
153525f23d Extended quality of known address read/write/modify mapping, and started recording internal accesses in addition to external. 2017-02-26 17:10:33 -05:00
Thomas Harte
3101dc94a7 Merge pull request #98 from TomHarte/TIAImprovements
Reimplements the Atari TIA, as a discrete component
2017-02-26 15:14:59 -05:00
Thomas Harte
e6a84fd26b Attempted a hardware-correct implementation of missile-to-player latching. This completes the last of the **knowing** inaccuracies. The rest are as-of-yet unwitting. 2017-02-26 15:12:31 -05:00
Thomas Harte
440467ea3e Started communicating which copy is being requested. 2017-02-26 13:39:25 -05:00
Thomas Harte
98376de9ad Started returning 'no effect' for pot ports, rather than doing nothing. Still very much TODO though. 2017-02-25 22:58:58 -05:00
Thomas Harte
e61e355251 Moved to the maximum possibly required queue length of 4. Though the emulated 2600 should never need more than 2 slots as per the current calling pattern, it's not a contractual guarantee. 2017-02-25 17:25:10 -05:00
Thomas Harte
c898c8a99e Ensured the missiles and ball don't attempt to enqueue. Because I don't think they're supposed to. 2017-02-25 17:13:22 -05:00
Thomas Harte
8c9062857c Added a single-slot queue for player objects to defer drawing, thereby deferring pixel lookup. Which I think is correct. Though more slots might be needed. 2017-02-25 17:10:24 -05:00
Thomas Harte
77ed4ddc05 Slightly simplified ready line release logic. 2017-02-23 21:08:32 -05:00
Thomas Harte
82f392fada This should be the other way around. I want whichever is later. 2017-02-22 21:54:49 -05:00
Thomas Harte
2f0c923c29 Switched away from @synchronized as it appears possibly to be the lock used during -dealloc, creating deadlock with the CSAudioQueueDeallocLock. 2017-02-22 21:42:10 -05:00
Thomas Harte
8291a63d5f Fixed loss of audio when switching to PAL. 2017-02-22 21:15:37 -05:00
Thomas Harte
4c947ad553 Attempted to resolve risk of an audio callback being in progress when -dealloc is received. 2017-02-22 21:12:59 -05:00
Thomas Harte
6120dae61a While I'm using the hacky approach to player/missile synchronisation, I need to seed adder. 2017-02-22 07:39:11 -05:00
Thomas Harte
1d03793f22 Fixed potential race condition: ensure the queue is disposed of synchronously because otherwise there'll be a potential dangling reference to self. 2017-02-22 07:35:09 -05:00
Thomas Harte
4f5f191cd6 Fixed: will no longer attempt to output pixels from before the pixel part of a line on which sync was disabled abruptly. 2017-02-22 07:33:36 -05:00
Thomas Harte
21abf4e9fc Enshrined a terminology switch, albeit without any flow change behind it. 2017-02-22 07:29:48 -05:00
Thomas Harte
144d6b70d9 Minor cleaning. 2017-02-22 07:14:30 -05:00
Thomas Harte
b769f22ca0 Switched back to the collision_buffer_ being part of the TIA object, added one more assert. 2017-02-21 22:26:20 -05:00
Thomas Harte
7019d396d0 Threw in some asserts, discovering a bug in missile positioning. 2017-02-21 22:04:27 -05:00
Thomas Harte
f4447fd9cd Attempted to fix failure of sprites properly to wrap when performing motion. 2017-02-21 21:53:09 -05:00
Thomas Harte
36396b3d62 Made a slightly better, albeit still inaccurate, version of missile-player lock.Enough for Combat to do reasonable things. 2017-02-21 20:45:20 -05:00
Thomas Harte
d1dbf8c21f Missile to player lock is supposed to be a toggle; also factored out the commonalities of missile and ball drawing. 2017-02-21 07:58:37 -05:00
Thomas Harte
1bde0fed6f Simplified relationship between Objects and the usage-specific components through inheritance. 2017-02-21 07:37:20 -05:00
Thomas Harte
7ab2358bba Made an attempt to reintroduce missiles. 2017-02-20 22:22:39 -05:00
Thomas Harte
99547181f1 Attempted to template this thing. Without yet a plan in place for pixel lookup timing. 2017-02-20 21:42:59 -05:00
Thomas Harte
2bf784535c Simplified calllng. 2017-02-20 18:04:40 -05:00
Thomas Harte
57f434c199 Reorganised state, with an eye towards unifying object motion and triggers. 2017-02-20 17:58:28 -05:00
Thomas Harte
87afa9140e Took some provision steps towards paging type autodetection and communication. But I think this is a distraction. 2017-02-20 17:44:36 -05:00
Thomas Harte
d19f26887d Performed a very naive shuffling of output builder sets onto the OpenGL queue. Which makes the frequency switcher work properly from it's possibly-contextless thread. 2017-02-20 10:39:31 -05:00
Thomas Harte
6cb95b4fc5 Switched to passing around std::strings rather than char *s, because they should be easier to capture. 2017-02-20 10:35:33 -05:00
Thomas Harte
d979a822ac Introduced a deferred task list for the OpenGL thread. 2017-02-19 21:46:07 -05:00
Thomas Harte
fccdce65b9 Switched to lock guards. 2017-02-19 21:45:28 -05:00
Thomas Harte
99a35266e1 Attempted to bring frequency-switching logic into the cross-platform realm. Which for now creates an issue with the OpenGL context. 2017-02-19 21:20:37 -05:00
Thomas Harte
51bcaea60c Disabled incorrect 'optimisations'. 2017-02-19 12:00:04 -05:00
Thomas Harte
e00339ef0a Attempted to reintroduce the ball. 2017-02-19 08:02:54 -05:00
Thomas Harte
53cd125712 Added stub calls to draw the missiles and ball. 2017-02-19 07:28:24 -05:00
Thomas Harte
04693b067c Fixed failure of the optimised route to pump the pixel clock; removed optimisation entirely for now. 2017-02-18 21:36:48 -05:00
Thomas Harte
cd7876a746 Reintroduced the extra clocking delay. 2017-02-18 20:18:50 -05:00
Thomas Harte
ed5ff49ef5 Fixed vertical delay, retreated from my previous thought about adding the one extra cycle of sprite delay, at least temporarily. 2017-02-16 20:52:01 -05:00
Thomas Harte
8d502a0b03 Decided not to run before I can walk and switched to storing the motion time and next step explicitly per object. 2017-02-16 20:28:37 -05:00
Thomas Harte
5ea232310f Added a check against negative runs. 2017-02-16 18:55:58 -05:00
Thomas Harte
09309aa74f Attempted to prevent extraneous moves. 2017-02-16 18:52:39 -05:00
Thomas Harte
b5357860b9 Made an attempt to split things apart so as to be able to introduce the proper sprite latency. 2017-02-14 20:56:16 -05:00
Thomas Harte
dd17459687 Added my first failing test: delay is incorrect when resetting outside of the play area. 2017-02-12 20:42:49 -05:00
Thomas Harte
cd90118a0f Added two, extraordinarily simple tests. 2017-02-12 20:32:53 -05:00
Thomas Harte
25776de59d I think unit testing this thing is the only way forwards. Started adding appropriate hooks. 2017-02-12 19:55:02 -05:00
Thomas Harte
600bdc9af7 In C++, I think the implicit cast to bool negates the need for any manual collapsing? 2017-02-12 18:18:35 -05:00
Thomas Harte
0c9be2b09e Shunted the collisions buffer onto a separate area of the heap for the time being, as a debugging aid. Also added a few more initial values. 2017-02-12 18:16:50 -05:00
Thomas Harte
df8a5cbe6d Made attempts (i) to respect the delay flag; and (ii) to account for border-region sprite clocking. 2017-02-12 17:35:09 -05:00
Thomas Harte
9ce68c38ae Made an effort to implement proper pixel output for sprites. 2017-02-12 14:01:50 -05:00
Thomas Harte
40954d6a2a Attempted to factor out parts I expect to reuse for missiles and the ball. 2017-02-12 11:31:17 -05:00
Thomas Harte
ac444a3f34 Corrected both position increments and target time calculation. 2017-02-11 21:24:14 -05:00
Thomas Harte
b8abeced6d Made an attempt to introduce the proper eventful loop for player output. With debugging yet to occur. 2017-02-11 21:01:58 -05:00
Thomas Harte
aeff59addc Implemented motion 'correctly', for programs written to do all work outside of the pixel area. 2017-02-11 20:25:49 -05:00
Thomas Harte
7ab6023a0c Ensured no attempt to call strcmp on null if a file name without an extension got into here. 2017-02-11 13:36:36 -05:00
Thomas Harte
97cdfea9e9 Resolved spurious static analyser complaint: input_size and output_size aren't supposed to have defined values if input or output is null. But whatever. 2017-02-11 13:36:09 -05:00
Thomas Harte
aff69dbc34 Resolved spurious static analyser issue; screen mode will always be 0–6 but it doesn't know that. Setting a non-zero divider doesn't feel worth worrying about for a cleaner compile. 2017-02-11 13:35:22 -05:00
Thomas Harte
6381e4e1b0 All that's happened to position is that numbers have been added to it. So it can't be negative, given that it wasn't before. So a regular modulo will do. 2017-02-11 13:34:36 -05:00
Thomas Harte
c8e595d9aa Merge branch 'master' into TIAImprovements 2017-02-11 13:20:52 -05:00
Thomas Harte
8c88fd4261 Merge pull request #99 from TomHarte/StartupRace
Resolves a race condition on machine startup
2017-02-11 13:18:19 -05:00
Thomas Harte
a86a6367b5 Slightly shuffled to avoid a race condition on the best-effort updater. 2017-02-11 13:17:11 -05:00
Thomas Harte
905ed1f87b Switched to the more natural type, which is also signed, making my logic less prone to error. 2017-02-11 13:16:53 -05:00
Thomas Harte
8de6caf6ff Started trying to get into a proper structure here. Chickened out. 2017-02-11 12:59:13 -05:00
Thomas Harte
327c19a222 Slightly shuffled to avoid a race condition on the best-effort updater. 2017-02-11 12:58:47 -05:00
Thomas Harte
40d3f5f7f6 Attempted properly to respect start. 2017-02-11 08:26:09 -05:00
Thomas Harte
64d5712d1d Added an incorrectly-coded version of horizontal move, at least so that I can verify that information is going into the correct slots. 2017-02-10 07:23:43 -05:00
Thomas Harte
3b20d862f0 Made an initial attempt to mark sprite positions. But without hmove implemented, they're all over the place. 2017-02-09 20:53:42 -05:00
Thomas Harte
2e9ef2b0ef Took a shot at reinstating the horizontal blank extend flag. 2017-02-09 18:37:19 -05:00
Thomas Harte
70745286a5 Ensured this array is properly aligned for the uin32_t accesses I intend to make for background drawing. 2017-02-08 20:25:23 -05:00
Thomas Harte
dcb7584060 Added the four-cycle playfield output latency and ensured you can't get smaller-than-usual pixels by rapid register value changing. 2017-02-08 07:30:32 -05:00
Thomas Harte
a477499724 Got a bit more explicit with range returned by get_cycles_until_horizontal_blank and hence attempted a more thorough (/correct) version of WSYNC. 2017-02-07 22:14:45 -05:00
Thomas Harte
944d835eea Switched explicitly to an accumulation model for filling the collision buffer. 2017-02-06 21:59:28 -05:00
Thomas Harte
8f5039130c Changed index naming order to ensure no out-of-bounds accesses. 2017-02-06 21:48:41 -05:00
Thomas Harte
ba165bb70a Made an attempt properly to populate collision registers from the collision buffer. 2017-02-06 21:15:55 -05:00
Thomas Harte
474e2e8d2c Fixed once again to respect mid-line palette changes. 2017-02-06 20:09:12 -05:00
Thomas Harte
8b8eb787df Fixed complete invisibility. 2017-02-06 18:42:58 -05:00
Thomas Harte
66bcdd36f3 Made an attempt to introduce an intermediate buffer that ends up with a bit mask of all graphical components present on it, and to use that to infer collision flags and colours, based on playfield priority and colour palette. Immediately yielding: a blank screen. Good work! 2017-02-06 18:29:00 -05:00
Thomas Harte
fcf8cafb5d Sought to ensure that communicating a colour burst in multiple parts doesn't ruin the phase. 2017-02-06 18:27:44 -05:00
Thomas Harte
6bcf95042c Started trying to be a bit more explicit about usage, and to divide up drawing responsibility. 2017-02-05 17:51:56 -05:00
Thomas Harte
23f3ccd77a Made a further attempt to prevent overwrites. 2017-02-05 17:47:34 -05:00
Thomas Harte
f2437cb257 Added some additional documentation, started making steps towards returning sprites, fixed a counter bug that would exhibit as incorrect sync. 2017-01-31 20:30:32 -05:00
Thomas Harte
abe04334c2 Attempted to retain more player information, and removed the output cursor from class storage as I think it's acceptable as a temporary. 2017-01-30 22:42:27 -05:00
Thomas Harte
8545707b54 Reinstituted the playfield. Probably needs more buffering though. Time to look into delays. 2017-01-30 21:38:58 -05:00
Thomas Harte
2b08758b2b Started capturing playfield/ball and background colours. 2017-01-30 08:08:03 -05:00
Thomas Harte
764b528891 Made a first attempt at switching to a model that respects blank and sync. 2017-01-30 07:19:19 -05:00
Thomas Harte
92754ace7a Some mild fixes get me up to having a rolling screen of vertical lines. Which is what I was hoping for right now! 2017-01-29 22:16:23 -05:00
Thomas Harte
1cc13b2799 Merge branch 'master' into TIAImprovements 2017-01-29 16:13:46 -05:00
Thomas Harte
38f944bc34 This needs to be a memmove as the areas may overlap. 2017-01-29 16:13:33 -05:00
Thomas Harte
427175b9c0 Added an extra flag to avoid potential race condition on is_full_, being reset from the background despite a write area not having been allocated. 2017-01-29 16:13:28 -05:00
Thomas Harte
ebde955356 This needs to be a memmove as the areas may overlap. 2017-01-29 16:12:48 -05:00
Thomas Harte
7fd02e7f4c Added an extra flag to avoid potential race condition on is_full_, being reset from the background despite a write area not having been allocated. 2017-01-29 16:11:29 -05:00
Thomas Harte
d51f185dc7 Made an attempt to reintroduce the basic horizontal loop. 2017-01-29 15:43:57 -05:00
Thomas Harte
2390358c24 Prevented unbounded CPU usage, albeit without yet deciding who has authority for the clock rate. 2017-01-29 14:19:26 -05:00
Thomas Harte
2432a3b4d7 Fixed condition — >= is smarter. 2017-01-29 14:00:01 -05:00
Thomas Harte
9c3597c7e3 Attempted to reintroduce enough logic to handle [most of] line timing, such that WSYNC works. Initial objective is to get back to having a working background. 2017-01-29 13:47:36 -05:00
Thomas Harte
fba6baaa9c Stubbed and disabled to get back to building. 2017-01-28 21:56:01 -05:00
Thomas Harte
a246530953 Supposing the TIA were implemented, this is (more or less) what the Atari 2600 would now look like. 2017-01-28 21:46:40 -05:00
Thomas Harte
0ffded72a6 Created a placeholder class for a factored-out TIA. There's a bit more it'll need to do, like vending (or receiving) a CRT but this is the full hardware stuff, I think. 2017-01-28 16:19:08 -05:00
502 changed files with 157779 additions and 14221 deletions

8
.gitignore vendored
View File

@@ -18,8 +18,14 @@ DerivedData
*.xcuserstate
.DS_Store
# Exclude system ROMs
# Exclude system ROMs and unit test ROMs
ROMImages/*
OSBindings/Mac/Clock SignalTests/Atari ROMs
OSBindings/Mac/Clock SignalTests/MSX ROMs
# Exclude intermediate build products
*.o
.sconsign.dblite
# CocoaPods
#

View File

@@ -1,5 +1,13 @@
language: objective-c
osx_image: xcode8.2
xcode_project: OSBindings/Mac/Clock Signal.xcodeproj
xcode_scheme: Clock Signal
xcode_sdk: macosx10.12
# 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

@@ -0,0 +1,30 @@
//
// ConfidenceCounter.cpp
// Clock Signal
//
// Created by Thomas Harte on 21/01/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#include "ConfidenceCounter.hpp"
using namespace Analyser::Dynamic;
float ConfidenceCounter::get_confidence() {
return static_cast<float>(hits_) / static_cast<float>(hits_ + misses_);
}
void ConfidenceCounter::add_hit() {
hits_++;
}
void ConfidenceCounter::add_miss() {
misses_++;
}
void ConfidenceCounter::add_equivocal() {
if(hits_ > misses_) {
hits_++;
misses_++;
}
}

View File

@@ -0,0 +1,47 @@
//
// ConfidenceCounter.hpp
// Clock Signal
//
// Created by Thomas Harte on 21/01/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef ConfidenceCounter_hpp
#define ConfidenceCounter_hpp
#include "ConfidenceSource.hpp"
namespace Analyser {
namespace Dynamic {
/*!
Provides a confidence source that calculates its probability by virtual of a history of events.
The initial value of the confidence counter is 0.5.
*/
class ConfidenceCounter: public ConfidenceSource {
public:
/*! @returns The computed probability, based on the history of events. */
float get_confidence() override;
/*! Records an event that implies this is the appropriate class — pushes probability up towards 1.0. */
void add_hit();
/*! Records an event that implies this is not the appropriate class — pushes probability down towards 0.0. */
void add_miss();
/*!
Records an event that could be correct but isn't necessarily so; which can push probability
down towards 0.5, but will never push it upwards.
*/
void add_equivocal();
private:
int hits_ = 1;
int misses_ = 1;
};
}
}
#endif /* ConfidenceCounter_hpp */

View File

@@ -0,0 +1,28 @@
//
// ConfidenceSource.hpp
// Clock Signal
//
// Created by Thomas Harte on 21/01/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef ConfidenceSource_hpp
#define ConfidenceSource_hpp
namespace Analyser {
namespace Dynamic {
/*!
Provides an abstract interface through which objects can declare the probability
that they are the proper target for their input; e.g. if an Acorn Electron is asked
to run an Atari 2600 program then its confidence should shrink towards 0.0; if the
program is handed to an Atari 2600 then its confidence should grow towards 1.0.
*/
struct ConfidenceSource {
virtual float get_confidence() = 0;
};
}
}
#endif /* ConfidenceSource_hpp */

View File

@@ -0,0 +1,28 @@
//
// ConfidenceSummary.cpp
// Clock Signal
//
// Created by Thomas Harte on 21/01/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#include "ConfidenceSummary.hpp"
#include <cassert>
#include <numeric>
using namespace Analyser::Dynamic;
ConfidenceSummary::ConfidenceSummary(const std::vector<ConfidenceSource *> &sources, const std::vector<float> &weights) :
sources_(sources), weights_(weights) {
assert(weights.size() == sources.size());
weight_sum_ = std::accumulate(weights.begin(), weights.end(), 0.0f);
}
float ConfidenceSummary::get_confidence() {
float result = 0.0f;
for(std::size_t index = 0; index < sources_.size(); ++index) {
result += sources_[index]->get_confidence() * weights_[index];
}
return result / weight_sum_;
}

View File

@@ -0,0 +1,46 @@
//
// ConfidenceSummary.hpp
// Clock Signal
//
// Created by Thomas Harte on 21/01/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef ConfidenceSummary_hpp
#define ConfidenceSummary_hpp
#include "ConfidenceSource.hpp"
#include <vector>
namespace Analyser {
namespace Dynamic {
/*!
Summaries a collection of confidence sources by calculating their weighted sum.
*/
class ConfidenceSummary: public ConfidenceSource {
public:
/*!
Instantiates a summary that will produce the weighted sum of
@c sources, each using the corresponding entry of @c weights.
Requires that @c sources and @c weights are of the same length.
*/
ConfidenceSummary(
const std::vector<ConfidenceSource *> &sources,
const std::vector<float> &weights);
/*! @returns The weighted sum of all sources. */
float get_confidence() override;
private:
std::vector<ConfidenceSource *> sources_;
std::vector<float> weights_;
float weight_sum_;
};
}
}
#endif /* ConfidenceSummary_hpp */

View File

@@ -0,0 +1,90 @@
//
// MultiCRTMachine.cpp
// Clock Signal
//
// Created by Thomas Harte on 29/01/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#include "MultiCRTMachine.hpp"
#include <condition_variable>
#include <mutex>
using namespace Analyser::Dynamic;
MultiCRTMachine::MultiCRTMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::mutex &machines_mutex) :
machines_(machines), machines_mutex_(machines_mutex), queues_(machines.size()) {
speaker_ = MultiSpeaker::create(machines);
}
void MultiCRTMachine::perform_parallel(const std::function<void(::CRTMachine::Machine *)> &function) {
// Apply a blunt force parallelisation of the machines; each run_for is dispatched
// to a separate queue and this queue will block until all are done.
volatile std::size_t outstanding_machines;
std::condition_variable condition;
std::mutex mutex;
{
std::lock_guard<std::mutex> machines_lock(machines_mutex_);
std::lock_guard<std::mutex> lock(mutex);
outstanding_machines = machines_.size();
for(std::size_t index = 0; index < machines_.size(); ++index) {
CRTMachine::Machine *crt_machine = machines_[index]->crt_machine();
queues_[index].enqueue([&mutex, &condition, crt_machine, function, &outstanding_machines]() {
if(crt_machine) function(crt_machine);
std::lock_guard<std::mutex> lock(mutex);
outstanding_machines--;
condition.notify_all();
});
}
}
std::unique_lock<std::mutex> lock(mutex);
condition.wait(lock, [&outstanding_machines] { return !outstanding_machines; });
}
void MultiCRTMachine::perform_serial(const std::function<void (::CRTMachine::Machine *)> &function) {
std::lock_guard<std::mutex> machines_lock(machines_mutex_);
for(const auto &machine: machines_) {
CRTMachine::Machine *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::close_output() {
perform_serial([=](::CRTMachine::Machine *machine) {
machine->close_output();
});
}
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;
}
Outputs::Speaker::Speaker *MultiCRTMachine::get_speaker() {
return speaker_;
}
void MultiCRTMachine::run_for(Time::Seconds duration) {
perform_parallel([=](::CRTMachine::Machine *machine) {
if(machine->get_confidence() >= 0.01f) machine->run_for(duration);
});
if(delegate_) delegate_->multi_crt_did_run_machines();
}
void MultiCRTMachine::did_change_machine_order() {
if(speaker_) {
speaker_->set_new_front_machine(machines_.front().get());
}
}

View File

@@ -0,0 +1,89 @@
//
// MultiCRTMachine.hpp
// Clock Signal
//
// Created by Thomas Harte on 29/01/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef MultiCRTMachine_hpp
#define MultiCRTMachine_hpp
#include "../../../../Concurrency/AsyncTaskQueue.hpp"
#include "../../../../Machines/CRTMachine.hpp"
#include "../../../../Machines/DynamicMachine.hpp"
#include "MultiSpeaker.hpp"
#include <memory>
#include <mutex>
#include <vector>
namespace Analyser {
namespace Dynamic {
/*!
Provides a class that multiplexes the CRT machine interface to multiple machines.
Keeps a reference to the original vector of machines; will access it only after
acquiring a supplied mutex. The owner should also call did_change_machine_order()
if the order of machines changes.
*/
class MultiCRTMachine: public CRTMachine::Machine {
public:
MultiCRTMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::mutex &machines_mutex);
/*!
Informs the MultiCRTMachine that the order of machines has changed; the MultiCRTMachine
uses this as an opportunity to synthesis any CRTMachine::Machine::Delegate messages that
are necessary to bridge the gap between one machine and the next.
*/
void did_change_machine_order();
/*!
Provides a mechanism by which a delegate can be informed each time a call to run_for has
been received.
*/
struct Delegate {
virtual void multi_crt_did_run_machines() = 0;
};
/// Sets @c delegate as the receiver of delegate messages.
void set_delegate(Delegate *delegate) {
delegate_ = delegate;
}
// 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;
private:
void run_for(const Cycles cycles) override {}
const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines_;
std::mutex &machines_mutex_;
std::vector<Concurrency::AsyncTaskQueue> queues_;
MultiSpeaker *speaker_ = nullptr;
Delegate *delegate_ = nullptr;
/*!
Performs a parallel for operation across all machines, performing the supplied
function on each and returning only once all applications have completed.
No guarantees are extended as to which thread operations will occur on.
*/
void perform_parallel(const std::function<void(::CRTMachine::Machine *)> &);
/*!
Performs a serial for operation across all machines, performing the supplied
function on each on the calling thread.
*/
void perform_serial(const std::function<void(::CRTMachine::Machine *)> &);
};
}
}
#endif /* MultiCRTMachine_hpp */

View File

@@ -0,0 +1,64 @@
//
// MultiConfigurable.cpp
// Clock Signal
//
// Created by Thomas Harte on 09/02/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#include "MultiConfigurable.hpp"
#include <algorithm>
using namespace Analyser::Dynamic;
MultiConfigurable::MultiConfigurable(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
for(const auto &machine: machines) {
Configurable::Device *device = machine->configurable_device();
if(device) devices_.push_back(device);
}
}
std::vector<std::unique_ptr<Configurable::Option>> MultiConfigurable::get_options() {
std::vector<std::unique_ptr<Configurable::Option>> options;
// Produce the list of unique options.
for(const auto &device : devices_) {
std::vector<std::unique_ptr<Configurable::Option>> device_options = device->get_options();
for(auto &option : device_options) {
if(std::find(options.begin(), options.end(), option) == options.end()) {
options.push_back(std::move(option));
}
}
}
return options;
}
void MultiConfigurable::set_selections(const Configurable::SelectionSet &selection_by_option) {
for(const auto &device : devices_) {
device->set_selections(selection_by_option);
}
}
Configurable::SelectionSet MultiConfigurable::get_accurate_selections() {
Configurable::SelectionSet set;
for(const auto &device : devices_) {
Configurable::SelectionSet device_set = device->get_accurate_selections();
for(auto &selection : device_set) {
set.insert(std::move(selection));
}
}
return set;
}
Configurable::SelectionSet MultiConfigurable::get_user_friendly_selections() {
Configurable::SelectionSet set;
for(const auto &device : devices_) {
Configurable::SelectionSet device_set = device->get_user_friendly_selections();
for(auto &selection : device_set) {
set.insert(std::move(selection));
}
}
return set;
}

View File

@@ -0,0 +1,43 @@
//
// MultiConfigurable.hpp
// Clock Signal
//
// Created by Thomas Harte on 09/02/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef MultiConfigurable_hpp
#define MultiConfigurable_hpp
#include "../../../../Machines/DynamicMachine.hpp"
#include <memory>
#include <vector>
namespace Analyser {
namespace Dynamic {
/*!
Provides a class that multiplexes the configurable interface to multiple machines.
Makes a static internal copy of the list of machines; makes no guarantees about the
order of delivered messages.
*/
class MultiConfigurable: public Configurable::Device {
public:
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;
private:
std::vector<Configurable::Device *> devices_;
};
}
}
#endif /* MultiConfigurable_hpp */

View File

@@ -0,0 +1,29 @@
//
// 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

@@ -0,0 +1,42 @@
//
// 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

@@ -0,0 +1,78 @@
//
// MultiJoystickMachine.cpp
// Clock Signal
//
// Created by Thomas Harte on 09/02/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#include "MultiJoystickMachine.hpp"
#include <algorithm>
using namespace Analyser::Dynamic;
namespace {
class MultiJoystick: public Inputs::Joystick {
public:
MultiJoystick(std::vector<JoystickMachine::Machine *> &machines, std::size_t index) {
for(const auto &machine: machines) {
const auto &joysticks = machine->get_joysticks();
if(joysticks.size() >= index) {
joysticks_.push_back(joysticks[index].get());
}
}
}
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);
}
}
}
return inputs;
}
void set_digital_input(const DigitalInput &digital_input, bool is_active) override {
for(const auto &joystick: joysticks_) {
joystick->set_digital_input(digital_input, is_active);
}
}
void reset_all_inputs() override {
for(const auto &joystick: joysticks_) {
joystick->reset_all_inputs();
}
}
private:
std::vector<Inputs::Joystick *> joysticks_;
};
}
MultiJoystickMachine::MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
std::size_t total_joysticks = 0;
std::vector<JoystickMachine::Machine *> joystick_machines;
for(const auto &machine: machines) {
JoystickMachine::Machine *joystick_machine = machine->joystick_machine();
if(joystick_machine) {
joystick_machines.push_back(joystick_machine);
total_joysticks = std::max(total_joysticks, joystick_machine->get_joysticks().size());
}
}
for(std::size_t index = 0; index < total_joysticks; ++index) {
joysticks_.emplace_back(new MultiJoystick(joystick_machines, index));
}
}
std::vector<std::unique_ptr<Inputs::Joystick>> &MultiJoystickMachine::get_joysticks() {
return joysticks_;
}

View File

@@ -0,0 +1,40 @@
//
// MultiJoystickMachine.hpp
// Clock Signal
//
// Created by Thomas Harte on 09/02/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef MultiJoystickMachine_hpp
#define MultiJoystickMachine_hpp
#include "../../../../Machines/DynamicMachine.hpp"
#include <memory>
#include <vector>
namespace Analyser {
namespace Dynamic {
/*!
Provides a class that multiplexes the joystick machine interface to multiple machines.
Makes a static internal copy of the list of machines; makes no guarantees about the
order of delivered messages.
*/
class MultiJoystickMachine: public JoystickMachine::Machine {
public:
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;
private:
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
};
}
}
#endif /* MultiJoystickMachine_hpp */

View File

@@ -0,0 +1,43 @@
//
// MultiKeyboardMachine.cpp
// Clock Signal
//
// Created by Thomas Harte on 09/02/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#include "MultiKeyboardMachine.hpp"
using namespace Analyser::Dynamic;
MultiKeyboardMachine::MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
for(const auto &machine: machines) {
KeyboardMachine::Machine *keyboard_machine = machine->keyboard_machine();
if(keyboard_machine) machines_.push_back(keyboard_machine);
}
}
void MultiKeyboardMachine::clear_all_keys() {
for(const auto &machine: machines_) {
machine->clear_all_keys();
}
}
void MultiKeyboardMachine::set_key_state(uint16_t key, bool is_pressed) {
for(const auto &machine: machines_) {
machine->set_key_state(key, is_pressed);
}
}
void MultiKeyboardMachine::type_string(const std::string &string) {
for(const auto &machine: machines_) {
machine->type_string(string);
}
}
void MultiKeyboardMachine::keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) {
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);
}
}

View File

@@ -0,0 +1,44 @@
//
// MultiKeyboardMachine.hpp
// Clock Signal
//
// Created by Thomas Harte on 09/02/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef MultiKeyboardMachine_hpp
#define MultiKeyboardMachine_hpp
#include "../../../../Machines/DynamicMachine.hpp"
#include "../../../../Machines/KeyboardMachine.hpp"
#include <memory>
#include <vector>
namespace Analyser {
namespace Dynamic {
/*!
Provides a class that multiplexes the keyboard machine interface to multiple machines.
Makes a static internal copy of the list of machines; makes no guarantees about the
order of delivered messages.
*/
class MultiKeyboardMachine: public KeyboardMachine::Machine {
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_;
};
}
}
#endif /* MultiKeyboardMachine_hpp */

View File

@@ -0,0 +1,76 @@
//
// MultiSpeaker.cpp
// Clock Signal
//
// Created by Thomas Harte on 18/02/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#include "MultiSpeaker.hpp"
using namespace Analyser::Dynamic;
MultiSpeaker *MultiSpeaker::create(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
std::vector<Outputs::Speaker::Speaker *> speakers;
for(const auto &machine: machines) {
Outputs::Speaker::Speaker *speaker = machine->crt_machine()->get_speaker();
if(speaker) speakers.push_back(speaker);
}
if(speakers.empty()) return nullptr;
return new MultiSpeaker(speakers);
}
MultiSpeaker::MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speakers) :
speakers_(speakers), front_speaker_(speakers.front()) {
for(const auto &speaker: speakers_) {
speaker->set_delegate(this);
}
}
float MultiSpeaker::get_ideal_clock_rate_in_range(float minimum, float maximum) {
float ideal = 0.0f;
for(const auto &speaker: speakers_) {
ideal += speaker->get_ideal_clock_rate_in_range(minimum, maximum);
}
return ideal / static_cast<float>(speakers_.size());
}
void MultiSpeaker::set_output_rate(float cycles_per_second, int buffer_size) {
for(const auto &speaker: speakers_) {
speaker->set_output_rate(cycles_per_second, buffer_size);
}
}
void MultiSpeaker::set_delegate(Outputs::Speaker::Speaker::Delegate *delegate) {
delegate_ = delegate;
}
void MultiSpeaker::speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) {
if(!delegate_) return;
{
std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_);
if(speaker != front_speaker_) return;
}
delegate_->speaker_did_complete_samples(this, buffer);
}
void MultiSpeaker::speaker_did_change_input_clock(Speaker *speaker) {
if(!delegate_) return;
{
std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_);
if(speaker != front_speaker_) return;
}
delegate_->speaker_did_change_input_clock(this);
}
void MultiSpeaker::set_new_front_machine(::Machine::DynamicMachine *machine) {
{
std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_);
front_speaker_ = machine->crt_machine()->get_speaker();
}
if(delegate_) {
delegate_->speaker_did_change_input_clock(this);
}
}

View File

@@ -0,0 +1,59 @@
//
// MultiSpeaker.hpp
// Clock Signal
//
// Created by Thomas Harte on 18/02/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef MultiSpeaker_hpp
#define MultiSpeaker_hpp
#include "../../../../Machines/DynamicMachine.hpp"
#include "../../../../Outputs/Speaker/Speaker.hpp"
#include <memory>
#include <mutex>
#include <vector>
namespace Analyser {
namespace Dynamic {
/*!
Provides a class that multiplexes calls to and from Outputs::Speaker::Speaker in order
transparently to connect a single caller to multiple destinations.
Makes a static internal copy of the list of machines; expects the owner to keep it
abreast of the current frontmost machine.
*/
class MultiSpeaker: public Outputs::Speaker::Speaker, Outputs::Speaker::Speaker::Delegate {
public:
/*!
Provides a construction mechanism that may return nullptr, in the case that all included
machines return nullptr as their speaker.
*/
static MultiSpeaker *create(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
/// This class requires the caller to nominate changes in the frontmost machine.
void set_new_front_machine(::Machine::DynamicMachine *machine);
// 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_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;
MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speakers);
std::vector<Outputs::Speaker::Speaker *> speakers_;
Outputs::Speaker::Speaker *front_speaker_ = nullptr;
Outputs::Speaker::Speaker::Delegate *delegate_ = nullptr;
std::mutex front_speaker_mutex_;
};
}
}
#endif /* MultiSpeaker_hpp */

View File

@@ -0,0 +1,109 @@
//
// MultiMachine.cpp
// Clock Signal
//
// Created by Thomas Harte on 28/01/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#include "MultiMachine.hpp"
#include <algorithm>
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_) {
crt_machine_.set_delegate(this);
}
ConfigurationTarget::Machine *MultiMachine::configuration_target() {
if(has_picked_) {
return machines_.front()->configuration_target();
} else {
return &configuration_target_;
}
}
CRTMachine::Machine *MultiMachine::crt_machine() {
if(has_picked_) {
return machines_.front()->crt_machine();
} else {
return &crt_machine_;
}
}
JoystickMachine::Machine *MultiMachine::joystick_machine() {
if(has_picked_) {
return machines_.front()->joystick_machine();
} else {
return &joystick_machine_;
}
}
KeyboardMachine::Machine *MultiMachine::keyboard_machine() {
if(has_picked_) {
return machines_.front()->keyboard_machine();
} else {
return &keyboard_machine_;
}
}
Configurable::Device *MultiMachine::configurable_device() {
if(has_picked_) {
return machines_.front()->configurable_device();
} else {
return &configurable_;
}
}
bool MultiMachine::would_collapse(const std::vector<std::unique_ptr<DynamicMachine>> &machines) {
return
(machines.front()->crt_machine()->get_confidence() > 0.9f) ||
(machines.front()->crt_machine()->get_confidence() >= 2.0f * machines[1]->crt_machine()->get_confidence());
}
void MultiMachine::multi_crt_did_run_machines() {
std::lock_guard<std::mutex> machines_lock(machines_mutex_);
#ifdef DEBUG
for(const auto &machine: machines_) {
CRTMachine::Machine *crt = machine->crt_machine();
printf("%0.2f ", crt->get_confidence());
crt->print_type();
printf("; ");
}
printf("\n");
#endif
DynamicMachine *front = machines_.front().get();
std::stable_sort(machines_.begin(), machines_.end(),
[] (const std::unique_ptr<DynamicMachine> &lhs, const std::unique_ptr<DynamicMachine> &rhs){
CRTMachine::Machine *lhs_crt = lhs->crt_machine();
CRTMachine::Machine *rhs_crt = rhs->crt_machine();
return lhs_crt->get_confidence() > rhs_crt->get_confidence();
});
if(machines_.front().get() != front) {
crt_machine_.did_change_machine_order();
}
if(would_collapse(machines_)) {
pick_first();
}
}
void MultiMachine::pick_first() {
has_picked_ = true;
// machines_.erase(machines_.begin() + 1, machines_.end());
// TODO: this isn't quite correct, because it may leak OpenGL/etc resources through failure to
// request a close_output while the context is active.
}
void *MultiMachine::raw_pointer() {
return nullptr;
}

View File

@@ -0,0 +1,79 @@
//
// MultiMachine.hpp
// Clock Signal
//
// Created by Thomas Harte on 28/01/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef MultiMachine_hpp
#define MultiMachine_hpp
#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 <memory>
#include <mutex>
#include <vector>
namespace Analyser {
namespace Dynamic {
/*!
Provides the same interface as to a single machine, while multiplexing all
underlying calls to an array of real dynamic machines.
Calls to crt_machine->get_crt will return that for the frontmost machine;
anything installed as the speaker's delegate will similarly receive
feedback only from that machine.
Following each crt_machine->run_for, reorders the supplied machines by
confidence.
If confidence for any machine becomes disproportionately low compared to
the others in the set, that machine stops running.
*/
class MultiMachine: public ::Machine::DynamicMachine, public MultiCRTMachine::Delegate {
public:
/*!
Allows a potential MultiMachine creator to enquire as to whether there's any benefit in
requesting this class as a proxy.
@returns @c true if the multimachine would discard all but the first machine in this list;
@c false otherwise.
*/
static bool would_collapse(const std::vector<std::unique_ptr<DynamicMachine>> &machines);
MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines);
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;
private:
void multi_crt_did_run_machines() override;
std::vector<std::unique_ptr<DynamicMachine>> machines_;
std::mutex machines_mutex_;
MultiConfigurable configurable_;
MultiConfigurationTarget configuration_target_;
MultiCRTMachine crt_machine_;
MultiJoystickMachine joystick_machine_;
MultiKeyboardMachine keyboard_machine_;
void pick_first();
bool has_picked_ = false;
};
}
}
#endif /* MultiMachine_hpp */

27
Analyser/Machines.hpp Normal file
View File

@@ -0,0 +1,27 @@
//
// Machines.h
// Clock Signal
//
// Created by Thomas Harte on 24/01/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef Machines_h
#define Machines_h
namespace Analyser {
enum class Machine {
AmstradCPC,
Atari2600,
ColecoVision,
Electron,
MSX,
Oric,
Vic20,
ZX8081
};
}
#endif /* Machines_h */

View File

@@ -0,0 +1,105 @@
//
// Disk.cpp
// Clock Signal
//
// Created by Thomas Harte on 18/09/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "Disk.hpp"
#include "../../../Storage/Disk/Controller/DiskController.hpp"
#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
#include "../../../NumberTheory/CRC.hpp"
#include <algorithm>
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);
Storage::Encodings::MFM::Parser parser(false, disk);
Storage::Encodings::MFM::Sector *names = parser.get_sector(0, 0, 0);
Storage::Encodings::MFM::Sector *details = parser.get_sector(0, 0, 1);
if(!names || !details) return nullptr;
if(names->samples.empty() || details->samples.empty()) return nullptr;
if(names->samples[0].size() != 256 || details->samples[0].size() != 256) return nullptr;
uint8_t final_file_offset = details->samples[0][5];
if(final_file_offset&7) return nullptr;
if(final_file_offset < 8) return nullptr;
char disk_name[13];
snprintf(disk_name, 13, "%.8s%.4s", &names->samples[0][0], &details->samples[0][0]);
catalogue->name = disk_name;
switch((details->samples[0][6] >> 4)&3) {
case 0: catalogue->bootOption = Catalogue::BootOption::None; break;
case 1: catalogue->bootOption = Catalogue::BootOption::LoadBOOT; break;
case 2: catalogue->bootOption = Catalogue::BootOption::RunBOOT; break;
case 3: catalogue->bootOption = Catalogue::BootOption::ExecBOOT; break;
}
for(std::size_t file_offset = 8; file_offset < final_file_offset; file_offset += 8) {
File new_file;
char name[10];
snprintf(name, 10, "%c.%.7s", names->samples[0][file_offset + 7] & 0x7f, &names->samples[0][file_offset]);
new_file.name = name;
new_file.load_address = (uint32_t)(details->samples[0][file_offset] | (details->samples[0][file_offset+1] << 8) | ((details->samples[0][file_offset+6]&0x0c) << 14));
new_file.execution_address = (uint32_t)(details->samples[0][file_offset+2] | (details->samples[0][file_offset+3] << 8) | ((details->samples[0][file_offset+6]&0xc0) << 10));
new_file.is_protected = !!(names->samples[0][file_offset + 7] & 0x80);
long data_length = static_cast<long>(details->samples[0][file_offset+4] | (details->samples[0][file_offset+5] << 8) | ((details->samples[0][file_offset+6]&0x30) << 12));
int start_sector = details->samples[0][file_offset+7] | ((details->samples[0][file_offset+6]&0x03) << 8);
new_file.data.reserve(static_cast<std::size_t>(data_length));
if(start_sector < 2) continue;
while(data_length > 0) {
uint8_t sector = static_cast<uint8_t>(start_sector % 10);
uint8_t track = static_cast<uint8_t>(start_sector / 10);
start_sector++;
Storage::Encodings::MFM::Sector *next_sector = parser.get_sector(0, track, sector);
if(!next_sector) break;
long length_from_sector = std::min(data_length, 256l);
new_file.data.insert(new_file.data.end(), next_sector->samples[0].begin(), next_sector->samples[0].begin() + length_from_sector);
data_length -= length_from_sector;
}
if(!data_length) catalogue->files.push_back(new_file);
}
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);
Storage::Encodings::MFM::Parser parser(true, disk);
Storage::Encodings::MFM::Sector *free_space_map_second_half = parser.get_sector(0, 0, 1);
if(!free_space_map_second_half) return nullptr;
std::vector<uint8_t> root_directory;
root_directory.reserve(5 * 256);
for(uint8_t c = 2; c < 7; c++) {
Storage::Encodings::MFM::Sector *sector = parser.get_sector(0, 0, c);
if(!sector) return nullptr;
root_directory.insert(root_directory.end(), sector->samples[0].begin(), sector->samples[0].end());
}
// Quick sanity checks.
if(root_directory[0x4cb]) return nullptr;
if(root_directory[1] != 'H' || root_directory[2] != 'u' || root_directory[3] != 'g' || root_directory[4] != 'o') return nullptr;
if(root_directory[0x4FB] != 'H' || root_directory[0x4FC] != 'u' || root_directory[0x4FD] != 'g' || root_directory[0x4FE] != 'o') return nullptr;
switch(free_space_map_second_half->samples[0][0xfd]) {
default: catalogue->bootOption = Catalogue::BootOption::None; break;
case 1: catalogue->bootOption = Catalogue::BootOption::LoadBOOT; break;
case 2: catalogue->bootOption = Catalogue::BootOption::RunBOOT; break;
case 3: catalogue->bootOption = Catalogue::BootOption::ExecBOOT; break;
}
return catalogue;
}

View File

@@ -10,14 +10,16 @@
#define StaticAnalyser_Acorn_Disk_hpp
#include "File.hpp"
#include "../../Storage/Disk/Disk.hpp"
#include "../../../Storage/Disk/Disk.hpp"
namespace StaticAnalyser {
namespace Analyser {
namespace Static {
namespace Acorn {
/// Describes a DFS- or ADFS-format catalogue(/directory) — the list of files available and the catalogue's boot option.
struct Catalogue {
std::string name;
std::list<File> files;
std::vector<File> files;
enum class BootOption {
None,
LoadBOOT,
@@ -29,6 +31,7 @@ struct Catalogue {
std::unique_ptr<Catalogue> GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk);
std::unique_ptr<Catalogue> GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk);
}
}
}

View File

@@ -6,15 +6,15 @@
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef File_hpp
#define File_hpp
#ifndef StaticAnalyser_Acorn_File_hpp
#define StaticAnalyser_Acorn_File_hpp
#include <list>
#include <memory>
#include <string>
#include <vector>
namespace StaticAnalyser {
namespace Analyser {
namespace Static {
namespace Acorn {
struct File {
@@ -38,9 +38,10 @@ struct File {
std::vector<uint8_t> data;
};
std::list<Chunk> chunks;
std::vector<Chunk> chunks;
};
}
}
}

View File

@@ -10,24 +10,23 @@
#include "Disk.hpp"
#include "Tape.hpp"
#include "Target.hpp"
using namespace StaticAnalyser::Acorn;
using namespace Analyser::Static::Acorn;
static std::list<std::shared_ptr<Storage::Cartridge::Cartridge>>
AcornCartridgesFrom(const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges)
{
std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> acorn_cartridges;
static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
AcornCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> acorn_cartridges;
for(std::shared_ptr<Storage::Cartridge::Cartridge> cartridge : cartridges)
{
const std::list<Storage::Cartridge::Cartridge::Segment> &segments = cartridge->get_segments();
for(const auto &cartridge : cartridges) {
const auto &segments = cartridge->get_segments();
// only one mapped item is allowed
if(segments.size() != 1) continue;
// which must be 16 kb in size
Storage::Cartridge::Cartridge::Segment segment = segments.front();
if(segment.data.size() != 0x4000) continue;
// which must be 8 or 16 kb in size
const Storage::Cartridge::Cartridge::Segment &segment = segments.front();
if(segment.data.size() != 0x4000 && segment.data.size() != 0x2000) continue;
// is a copyright string present?
uint8_t copyright_offset = segment.data[7];
@@ -58,33 +57,25 @@ static std::list<std::shared_ptr<Storage::Cartridge::Cartridge>>
return acorn_cartridges;
}
void StaticAnalyser::Acorn::AddTargets(
const std::list<std::shared_ptr<Storage::Disk::Disk>> &disks,
const std::list<std::shared_ptr<Storage::Tape::Tape>> &tapes,
const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges,
std::list<StaticAnalyser::Target> &destination)
{
Target target;
target.machine = Target::Electron;
target.probability = 1.0; // TODO: a proper estimation
target.acorn.has_dfs = false;
target.acorn.has_adfs = false;
target.acorn.should_shift_restart = false;
void Analyser::Static::Acorn::AddTargets(const Media &media, std::vector<std::unique_ptr<::Analyser::Static::Target>> &destination) {
std::unique_ptr<Target> target(new Target);
target->machine = Machine::Electron;
target->confidence = 0.5; // TODO: a proper estimation
target->has_dfs = false;
target->has_adfs = false;
target->should_shift_restart = false;
// strip out inappropriate cartridges
target.cartridges = AcornCartridgesFrom(cartridges);
target->media.cartridges = AcornCartridgesFrom(media.cartridges);
// if there are any tapes, attempt to get data from the first
if(tapes.size() > 0)
{
std::shared_ptr<Storage::Tape::Tape> tape = tapes.front();
tape->reset();
std::list<File> files = GetFiles(tape);
if(media.tapes.size() > 0) {
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.size()) {
bool is_basic = true;
// protected files are always for *RUNning only
@@ -92,13 +83,11 @@ void StaticAnalyser::Acorn::AddTargets(
// check also for a continuous threading of BASIC lines; if none then this probably isn't BASIC code,
// so that's also justification to *RUN
size_t pointer = 0;
std::size_t pointer = 0;
uint8_t *data = &files.front().data[0];
size_t data_size = files.front().data.size();
while(1)
{
if(pointer >= data_size-1 || data[pointer] != 13)
{
std::size_t data_size = files.front().data.size();
while(1) {
if(pointer >= data_size-1 || data[pointer] != 13) {
is_basic = false;
break;
}
@@ -108,32 +97,31 @@ void StaticAnalyser::Acorn::AddTargets(
// Inspect first file. If it's protected or doesn't look like BASIC
// then the loading command is *RUN. Otherwise it's CHAIN"".
target.loadingCommand = is_basic ? "CHAIN\"\"\n" : "*RUN\n";
target->loading_command = is_basic ? "CHAIN\"\"\n" : "*RUN\n";
target.tapes = tapes;
target->media.tapes = media.tapes;
}
}
if(disks.size() > 0)
{
std::shared_ptr<Storage::Disk::Disk> disk = disks.front();
if(media.disks.size() > 0) {
std::shared_ptr<Storage::Disk::Disk> disk = media.disks.front();
std::unique_ptr<Catalogue> dfs_catalogue, adfs_catalogue;
dfs_catalogue = GetDFSCatalogue(disk);
if(dfs_catalogue == nullptr) adfs_catalogue = GetADFSCatalogue(disk);
if(dfs_catalogue || adfs_catalogue)
{
target.disks = disks;
target.acorn.has_dfs = !!dfs_catalogue;
target.acorn.has_adfs = !!adfs_catalogue;
if(dfs_catalogue || adfs_catalogue) {
target->media.disks = media.disks;
target->has_dfs = !!dfs_catalogue;
target->has_adfs = !!adfs_catalogue;
Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption;
if(bootOption != Catalogue::BootOption::None)
target.acorn.should_shift_restart = true;
target->should_shift_restart = true;
else
target.loadingCommand = "*CAT\n";
target->loading_command = "*CAT\n";
}
}
if(target.tapes.size() || target.disks.size() || target.cartridges.size())
destination.push_back(target);
if(target->media.tapes.size() || target->media.disks.size() || target->media.cartridges.size()) {
destination.push_back(std::move(target));
}
}

View File

@@ -11,16 +11,13 @@
#include "../StaticAnalyser.hpp"
namespace StaticAnalyser {
namespace Analyser {
namespace Static {
namespace Acorn {
void AddTargets(
const std::list<std::shared_ptr<Storage::Disk::Disk>> &disks,
const std::list<std::shared_ptr<Storage::Tape::Tape>> &tapes,
const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges,
std::list<Target> &destination
);
void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination);
}
}
}

View File

@@ -9,13 +9,13 @@
#include "Tape.hpp"
#include <deque>
#include "../../NumberTheory/CRC.hpp"
#include "../../Storage/Tape/Parsers/Acorn.hpp"
using namespace StaticAnalyser::Acorn;
#include "../../../NumberTheory/CRC.hpp"
#include "../../../Storage/Tape/Parsers/Acorn.hpp"
static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::Tape::Tape> &tape, Storage::Tape::Acorn::Parser &parser)
{
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);
int shift_register = 0;
@@ -23,14 +23,12 @@ static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::
#define shift() shift_register = (shift_register >> 1) | (parser.get_next_bit(tape) << 9)
// find next area of high tone
while(!tape->is_at_end() && (shift_register != 0x3ff))
{
while(!tape->is_at_end() && (shift_register != 0x3ff)) {
shift();
}
// find next 0x2a (swallowing stop bit)
while(!tape->is_at_end() && (shift_register != 0x254))
{
while(!tape->is_at_end() && (shift_register != 0x254)) {
shift();
}
@@ -41,9 +39,8 @@ static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::
// read out name
char name[11];
int name_ptr = 0;
while(!tape->is_at_end() && name_ptr < sizeof(name))
{
std::size_t name_ptr = 0;
while(!tape->is_at_end() && name_ptr < sizeof(name)) {
name[name_ptr] = (char)parser.get_next_byte(tape);
if(!name[name_ptr]) break;
name_ptr++;
@@ -54,43 +51,39 @@ static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::
// addresses
new_chunk->load_address = (uint32_t)parser.get_next_word(tape);
new_chunk->execution_address = (uint32_t)parser.get_next_word(tape);
new_chunk->block_number = (uint16_t)parser.get_next_short(tape);
new_chunk->block_length = (uint16_t)parser.get_next_short(tape);
new_chunk->block_flag = (uint8_t)parser.get_next_byte(tape);
new_chunk->block_number = static_cast<uint16_t>(parser.get_next_short(tape));
new_chunk->block_length = static_cast<uint16_t>(parser.get_next_short(tape));
new_chunk->block_flag = static_cast<uint8_t>(parser.get_next_byte(tape));
new_chunk->next_address = (uint32_t)parser.get_next_word(tape);
uint16_t calculated_header_crc = parser.get_crc();
uint16_t stored_header_crc = (uint16_t)parser.get_next_short(tape);
stored_header_crc = (uint16_t)((stored_header_crc >> 8) | (stored_header_crc << 8));
uint16_t stored_header_crc = static_cast<uint16_t>(parser.get_next_short(tape));
stored_header_crc = static_cast<uint16_t>((stored_header_crc >> 8) | (stored_header_crc << 8));
new_chunk->header_crc_matched = stored_header_crc == calculated_header_crc;
if(!new_chunk->header_crc_matched) return nullptr;
parser.reset_crc();
new_chunk->data.reserve(new_chunk->block_length);
for(int c = 0; c < new_chunk->block_length; c++)
{
new_chunk->data.push_back((uint8_t)parser.get_next_byte(tape));
for(int c = 0; c < new_chunk->block_length; c++) {
new_chunk->data.push_back(static_cast<uint8_t>(parser.get_next_byte(tape)));
}
if(new_chunk->block_length && !(new_chunk->block_flag&0x40))
{
if(new_chunk->block_length && !(new_chunk->block_flag&0x40)) {
uint16_t calculated_data_crc = parser.get_crc();
uint16_t stored_data_crc = (uint16_t)parser.get_next_short(tape);
stored_data_crc = (uint16_t)((stored_data_crc >> 8) | (stored_data_crc << 8));
uint16_t stored_data_crc = static_cast<uint16_t>(parser.get_next_short(tape));
stored_data_crc = static_cast<uint16_t>((stored_data_crc >> 8) | (stored_data_crc << 8));
new_chunk->data_crc_matched = stored_data_crc == calculated_data_crc;
}
else
{
} else {
new_chunk->data_crc_matched = true;
}
return parser.get_error_flag() ? nullptr : std::move(new_chunk);
}
std::unique_ptr<File> GetNextFile(std::deque<File::Chunk> &chunks)
{
static std::unique_ptr<File> GetNextFile(std::deque<File::Chunk> &chunks) {
// find next chunk with a block number of 0
while(chunks.size() && chunks.front().block_number)
{
while(chunks.size() && chunks.front().block_number) {
chunks.pop_front();
}
@@ -101,8 +94,7 @@ std::unique_ptr<File> GetNextFile(std::deque<File::Chunk> &chunks)
uint16_t block_number = 0;
while(chunks.size())
{
while(chunks.size()) {
if(chunks.front().block_number != block_number) return nullptr;
bool was_last = chunks.front().block_flag & 0x80;
@@ -120,37 +112,31 @@ std::unique_ptr<File> GetNextFile(std::deque<File::Chunk> &chunks)
file->is_protected = !!(file->chunks.back().block_flag & 0x01); // I think the last flags are the ones that count; TODO: check.
// copy all data into a single big block
for(File::Chunk chunk : file->chunks)
{
for(File::Chunk chunk : file->chunks) {
file->data.insert(file->data.end(), chunk.data.begin(), chunk.data.end());
}
return file;
}
std::list<File> StaticAnalyser::Acorn::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape)
{
std::vector<File> Analyser::Static::Acorn::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
Storage::Tape::Acorn::Parser parser;
// populate chunk list
std::deque<File::Chunk> chunk_list;
while(!tape->is_at_end())
{
while(!tape->is_at_end()) {
std::unique_ptr<File::Chunk> chunk = GetNextChunk(tape, parser);
if(chunk)
{
if(chunk) {
chunk_list.push_back(*chunk);
}
}
// decompose into file list
std::list<File> file_list;
std::vector<File> file_list;
while(chunk_list.size())
{
while(chunk_list.size()) {
std::unique_ptr<File> next_file = GetNextFile(chunk_list);
if(next_file)
{
if(next_file) {
file_list.push_back(*next_file);
}
}

View File

@@ -12,13 +12,15 @@
#include <memory>
#include "File.hpp"
#include "../../Storage/Tape/Tape.hpp"
#include "../../../Storage/Tape/Tape.hpp"
namespace StaticAnalyser {
namespace Analyser {
namespace Static {
namespace Acorn {
std::list<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
}
}
}

View File

@@ -0,0 +1,28 @@
//
// Target.hpp
// Clock Signal
//
// Created by Thomas Harte on 09/03/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_Acorn_Target_h
#define Analyser_Static_Acorn_Target_h
#include "../StaticAnalyser.hpp"
namespace Analyser {
namespace Static {
namespace Acorn {
struct Target: public ::Analyser::Static::Target {
bool has_adfs = false;
bool has_dfs = false;
bool should_shift_restart = false;
};
}
}
}
#endif /* Analyser_Static_Acorn_Target_h */

View File

@@ -0,0 +1,244 @@
//
// AmstradCPC.cpp
// Clock Signal
//
// Created by Thomas Harte on 30/07/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "StaticAnalyser.hpp"
#include <algorithm>
#include <cstring>
#include "Target.hpp"
#include "../../../Storage/Disk/Parsers/CPM.hpp"
#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
static bool strcmp_insensitive(const char *a, const char *b) {
if(std::strlen(a) != std::strlen(b)) return false;
while(*a) {
if(std::tolower(*a) != std::tolower(*b)) return false;
a++;
b++;
}
return true;
}
static bool is_implied_extension(const std::string &extension) {
return
extension == " " ||
strcmp_insensitive(extension.c_str(), "BAS") ||
strcmp_insensitive(extension.c_str(), "BIN");
}
static void right_trim(std::string &string) {
string.erase(std::find_if(string.rbegin(), string.rend(), [](int ch) {
return !std::isspace(ch);
}).base(), string.end());
}
static std::string RunCommandFor(const Storage::Disk::CPM::File &file) {
// Trim spaces from the name.
std::string name = file.name;
right_trim(name);
// Form the basic command.
std::string command = "run\"" + name;
// Consider whether the extension is required.
if(!is_implied_extension(file.type)) {
std::string type = file.type;
right_trim(type);
command += "." + type;
}
// Add a newline and return.
return command + "\n";
}
static void InspectCatalogue(
const Storage::Disk::CPM::Catalogue &catalogue,
const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target) {
std::vector<const Storage::Disk::CPM::File *> candidate_files;
candidate_files.reserve(catalogue.files.size());
for(auto &file : catalogue.files) {
candidate_files.push_back(&file);
}
// Remove all files with untypable characters.
candidate_files.erase(
std::remove_if(candidate_files.begin(), candidate_files.end(), [](const Storage::Disk::CPM::File *file) {
for(auto c : file->name + file->type) {
if(c < 32) return true;
}
return false;
}),
candidate_files.end());
// If that leaves a mix of 'system' (i.e. hidden) and non-system files, remove the system files.
bool are_all_system = true;
for(auto file : candidate_files) {
if(!file->system) {
are_all_system = false;
break;
}
}
if(!are_all_system) {
candidate_files.erase(
std::remove_if(candidate_files.begin(), candidate_files.end(), [](const Storage::Disk::CPM::File *file) {
return file->system;
}),
candidate_files.end());
}
// If there's just one file, run that.
if(candidate_files.size() == 1) {
target->loading_command = RunCommandFor(*candidate_files[0]);
return;
}
// If only one file is [potentially] BASIC, run that one; otherwise if only one has a suffix
// that AMSDOS allows to be omitted, pick that one.
int basic_files = 0;
int implicit_suffixed_files = 0;
std::size_t last_basic_file = 0;
std::size_t last_implicit_suffixed_file = 0;
for(std::size_t c = 0; c < candidate_files.size(); c++) {
// Files with nothing but spaces in their name can't be loaded by the user, so disregard them.
if(candidate_files[c]->type == " " && candidate_files[c]->name == " ")
continue;
// Check for whether this is [potentially] BASIC.
if(candidate_files[c]->data.size() >= 128 && !((candidate_files[c]->data[18] >> 1) & 7)) {
basic_files++;
last_basic_file = c;
}
// Check suffix for emptiness.
if(is_implied_extension(candidate_files[c]->type)) {
implicit_suffixed_files++;
last_implicit_suffixed_file = c;
}
}
if(basic_files == 1 || implicit_suffixed_files == 1) {
std::size_t selected_file = (basic_files == 1) ? last_basic_file : last_implicit_suffixed_file;
target->loading_command = RunCommandFor(*candidate_files[selected_file]);
return;
}
// One more guess: if only one remaining candidate file has a different name than the others,
// assume it is intended to stand out.
std::map<std::string, int> name_counts;
std::map<std::string, std::size_t> indices_by_name;
std::size_t index = 0;
for(auto file : candidate_files) {
name_counts[file->name]++;
indices_by_name[file->name] = index;
index++;
}
if(name_counts.size() == 2) {
for(auto &pair : name_counts) {
if(pair.second == 1) {
target->loading_command = RunCommandFor(*candidate_files[indices_by_name[pair.first]]);
return;
}
}
}
// Desperation.
target->loading_command = "cat\n";
}
static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, const std::unique_ptr<Analyser::Static::AmstradCPC::Target> &target) {
Storage::Encodings::MFM::Parser parser(true, disk);
Storage::Encodings::MFM::Sector *boot_sector = parser.get_sector(0, 0, 0x41);
if(boot_sector != nullptr && !boot_sector->samples.empty() && boot_sector->samples[0].size() == 512) {
// Check that the first 64 bytes of the sector aren't identical; if they are then probably
// this disk was formatted and the filler byte never replaced.
bool matched = true;
for(std::size_t c = 1; c < 64; c++) {
if(boot_sector->samples[0][c] != boot_sector->samples[0][0]) {
matched = false;
break;
}
}
// This is a system disk, then launch it as though it were CP/M.
if(!matched) {
target->loading_command = "|cpm\n";
return true;
}
}
return false;
}
void Analyser::Static::AmstradCPC::AddTargets(const Media &media, std::vector<std::unique_ptr<Analyser::Static::Target>> &destination) {
std::unique_ptr<Target> target(new Target);
target->machine = Machine::AmstradCPC;
target->confidence = 0.5;
target->model = Target::Model::CPC6128;
if(!media.tapes.empty()) {
// TODO: which of these are actually potentially CPC tapes?
target->media.tapes = media.tapes;
// Ugliness flows here: assume the CPC isn't smart enough to pause between pressing
// enter and responding to the follow-on prompt to press a key, so just type for
// a while. Yuck!
target->loading_command = "|tape\nrun\"\n1234567890";
}
if(!media.disks.empty()) {
Storage::Disk::CPM::ParameterBlock data_format;
data_format.sectors_per_track = 9;
data_format.tracks = 40;
data_format.block_size = 1024;
data_format.first_sector = 0xc1;
data_format.catalogue_allocation_bitmap = 0xc000;
data_format.reserved_tracks = 0;
Storage::Disk::CPM::ParameterBlock system_format;
system_format.sectors_per_track = 9;
system_format.tracks = 40;
system_format.block_size = 1024;
system_format.first_sector = 0x41;
system_format.catalogue_allocation_bitmap = 0xc000;
system_format.reserved_tracks = 2;
for(const auto &disk: media.disks) {
// Check for an ordinary catalogue.
std::unique_ptr<Storage::Disk::CPM::Catalogue> data_catalogue = Storage::Disk::CPM::GetCatalogue(disk, data_format);
if(data_catalogue) {
InspectCatalogue(*data_catalogue, target);
target->media.disks.push_back(disk);
continue;
}
// Failing that check for a boot sector.
if(CheckBootSector(disk, target)) {
target->media.disks.push_back(disk);
continue;
}
// Failing that check for a system catalogue.
std::unique_ptr<Storage::Disk::CPM::Catalogue> system_catalogue = Storage::Disk::CPM::GetCatalogue(disk, system_format);
if(system_catalogue) {
InspectCatalogue(*system_catalogue, target);
target->media.disks.push_back(disk);
continue;
}
}
}
// If any media survived, add the target.
if(!target->media.empty())
destination.push_back(std::move(target));
}

View File

@@ -0,0 +1,24 @@
//
// StaticAnalyser.hpp
// Clock Signal
//
// Created by Thomas Harte on 30/07/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef StaticAnalyser_AmstradCPC_StaticAnalyser_hpp
#define StaticAnalyser_AmstradCPC_StaticAnalyser_hpp
#include "../StaticAnalyser.hpp"
namespace Analyser {
namespace Static {
namespace AmstradCPC {
void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination);
}
}
}
#endif /* StaticAnalyser_AmstradCPC_StaticAnalyser_hpp */

View File

@@ -0,0 +1,33 @@
//
// Target.hpp
// Clock Signal
//
// Created by Thomas Harte on 09/03/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_AmstradCPC_Target_h
#define Analyser_Static_AmstradCPC_Target_h
#include "../StaticAnalyser.hpp"
namespace Analyser {
namespace Static {
namespace AmstradCPC {
struct Target: public ::Analyser::Static::Target {
enum class Model {
CPC464,
CPC664,
CPC6128
};
Model model = Model::CPC464;
};
}
}
}
#endif /* Analyser_Static_AmstradCPC_Target_h */

View File

@@ -0,0 +1,203 @@
//
// StaticAnalyser.cpp
// Clock Signal
//
// Created by Thomas Harte on 15/09/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "StaticAnalyser.hpp"
#include "Target.hpp"
#include "../Disassembler/6502.hpp"
using namespace Analyser::Static::Atari;
static void DeterminePagingFor2kCartridge(Analyser::Static::Atari::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;
entry_address = (static_cast<uint16_t>(segment.data[0x7fc] | (segment.data[0x7fd] << 8))) & 0x1fff;
break_address = (static_cast<uint16_t>(segment.data[0x7fe] | (segment.data[0x7ff] << 8))) & 0x1fff;
// a CommaVid start address needs to be outside of its RAM
if(entry_address < 0x1800 || break_address < 0x1800) return;
std::function<std::size_t(uint16_t address)> high_location_mapper = [](uint16_t address) {
address &= 0x1fff;
return static_cast<std::size_t>(address - 0x1800);
};
Analyser::Static::MOS6502::Disassembly high_location_disassembly =
Analyser::Static::MOS6502::Disassemble(segment.data, high_location_mapper, {entry_address, break_address});
// assume that any kind of store that looks likely to be intended for large amounts of memory implies
// large amounts of memory
bool has_wide_area_store = false;
for(std::map<uint16_t, Analyser::Static::MOS6502::Instruction>::value_type &entry : high_location_disassembly.instructions_by_address) {
if(entry.second.operation == Analyser::Static::MOS6502::Instruction::STA) {
has_wide_area_store |= entry.second.addressing_mode == Analyser::Static::MOS6502::Instruction::Indirect;
has_wide_area_store |= entry.second.addressing_mode == Analyser::Static::MOS6502::Instruction::IndexedIndirectX;
has_wide_area_store |= entry.second.addressing_mode == Analyser::Static::MOS6502::Instruction::IndirectIndexedY;
if(has_wide_area_store) break;
}
}
// conclude that this is a CommaVid if it attempted to write something to the CommaVid RAM locations;
// 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;
}
static void DeterminePagingFor8kCartridge(Analyser::Static::Atari::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?)
if(
segment.data[4095] == 0xf0 && segment.data[4093] == 0xf0 && segment.data[4094] == 0x00 && segment.data[4092] == 0x00 &&
(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;
return;
}
// make an assumption that this is the Atari paging model
target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari8k;
std::set<uint16_t> internal_accesses;
internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end());
internal_accesses.insert(disassembly.internal_modifies.begin(), disassembly.internal_modifies.end());
internal_accesses.insert(disassembly.internal_loads.begin(), disassembly.internal_loads.end());
int atari_access_count = 0;
int parker_access_count = 0;
int tigervision_access_count = 0;
for(uint16_t address : internal_accesses) {
uint16_t masked_address = address & 0x1fff;
atari_access_count += masked_address >= 0x1ff8 && masked_address < 0x1ffa;
parker_access_count += masked_address >= 0x1fe0 && masked_address < 0x1ff8;
}
for(uint16_t address: disassembly.external_stores) {
uint16_t masked_address = address & 0x1fff;
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;
}
static void DeterminePagingFor16kCartridge(Analyser::Static::Atari::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;
std::set<uint16_t> internal_accesses;
internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end());
internal_accesses.insert(disassembly.internal_modifies.begin(), disassembly.internal_modifies.end());
internal_accesses.insert(disassembly.internal_loads.begin(), disassembly.internal_loads.end());
int atari_access_count = 0;
int mnetwork_access_count = 0;
for(uint16_t address : internal_accesses) {
uint16_t masked_address = address & 0x1fff;
atari_access_count += masked_address >= 0x1ff6 && masked_address < 0x1ffa;
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;
}
static void DeterminePagingFor64kCartridge(Analyser::Static::Atari::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;
}
static void DeterminePagingForCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) {
if(segment.data.size() == 2048) {
DeterminePagingFor2kCartridge(target, segment);
return;
}
uint16_t entry_address, break_address;
entry_address = static_cast<uint16_t>(segment.data[segment.data.size() - 4] | (segment.data[segment.data.size() - 3] << 8));
break_address = static_cast<uint16_t>(segment.data[segment.data.size() - 2] | (segment.data[segment.data.size() - 1] << 8));
std::function<std::size_t(uint16_t address)> address_mapper = [](uint16_t address) {
if(!(address & 0x1000)) return static_cast<std::size_t>(-1);
return static_cast<std::size_t>(address & 0xfff);
};
std::vector<uint8_t> final_4k(segment.data.end() - 4096, segment.data.end());
Analyser::Static::MOS6502::Disassembly disassembly = Analyser::Static::MOS6502::Disassemble(final_4k, address_mapper, {entry_address, break_address});
switch(segment.data.size()) {
case 8192:
DeterminePagingFor8kCartridge(target, segment, disassembly);
break;
case 10495:
target.paging_model = Analyser::Static::Atari::Target::PagingModel::Pitfall2;
break;
case 12288:
target.paging_model = Analyser::Static::Atari::Target::PagingModel::CBSRamPlus;
break;
case 16384:
DeterminePagingFor16kCartridge(target, segment, disassembly);
break;
case 32768:
target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari32k;
break;
case 65536:
DeterminePagingFor64kCartridge(target, segment, disassembly);
break;
default:
break;
}
// 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) {
bool has_superchip = true;
for(std::size_t address = 0; address < 128; address++) {
if(segment.data[address] != segment.data[address+128]) {
has_superchip = false;
break;
}
}
target.uses_superchip = has_superchip;
}
// check for a Tigervision or Tigervision-esque scheme
if(target.paging_model == Analyser::Static::Atari::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;
}
}
void Analyser::Static::Atari::AddTargets(const Media &media, std::vector<std::unique_ptr<Analyser::Static::Target>> &destination) {
// TODO: sanity checking; is this image really for an Atari 2600?
std::unique_ptr<Analyser::Static::Atari::Target> target(new Analyser::Static::Atari::Target);
target->machine = Machine::Atari2600;
target->confidence = 0.5;
target->media.cartridges = media.cartridges;
target->paging_model = Analyser::Static::Atari::Target::PagingModel::None;
target->uses_superchip = false;
// try to figure out the paging scheme
if(!media.cartridges.empty()) {
const auto &segments = media.cartridges.front()->get_segments();
if(segments.size() == 1) {
const Storage::Cartridge::Cartridge::Segment &segment = segments.front();
DeterminePagingForCartridge(*target, segment);
}
}
destination.push_back(std::move(target));
}

View File

@@ -11,16 +11,13 @@
#include "../StaticAnalyser.hpp"
namespace StaticAnalyser {
namespace Analyser {
namespace Static {
namespace Atari {
void AddTargets(
const std::list<std::shared_ptr<Storage::Disk::Disk>> &disks,
const std::list<std::shared_ptr<Storage::Tape::Tape>> &tapes,
const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges,
std::list<Target> &destination
);
void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination);
}
}
}

View File

@@ -0,0 +1,43 @@
//
// Target.hpp
// Clock Signal
//
// Created by Thomas Harte on 09/03/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_Atari_Target_h
#define Analyser_Static_Atari_Target_h
#include "../StaticAnalyser.hpp"
namespace Analyser {
namespace Static {
namespace Atari {
struct Target: public ::Analyser::Static::Target {
enum class PagingModel {
None,
CommaVid,
Atari8k,
Atari16k,
Atari32k,
ActivisionStack,
ParkerBros,
Tigervision,
CBSRamPlus,
MNetwork,
MegaBoy,
Pitfall2
};
// TODO: shouldn't these be properties of the cartridge?
PagingModel paging_model = PagingModel::None;
bool uses_superchip = false;
};
}
}
}
#endif /* Analyser_Static_Atari_Target_h */

View File

@@ -0,0 +1,62 @@
//
// StaticAnalyser.cpp
// Clock Signal
//
// Created by Thomas Harte on 23/02/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#include "StaticAnalyser.hpp"
static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
ColecoCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> coleco_cartridges;
for(const auto &cartridge : cartridges) {
const auto &segments = cartridge->get_segments();
// 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];
if((data_size & static_cast<std::size_t>(~8191)) > 32768) {
start = &segment.data[segment.data.size() - 16384];
}
if(start[0] != 0xaa && start[0] != 0x55 && start[1] != 0xaa && start[1] != 0x55) continue;
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);
} 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));
}
}
return coleco_cartridges;
}
void Analyser::Static::Coleco::AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination) {
std::unique_ptr<Target> target(new Target);
target->machine = Machine::ColecoVision;
target->confidence = 1.0f - 1.0f / 32768.0f;
target->media.cartridges = ColecoCartridgesFrom(media.cartridges);
if(!target->media.empty())
destination.push_back(std::move(target));
}

View File

@@ -0,0 +1,25 @@
//
// StaticAnalyser.hpp
// Clock Signal
//
// Created by Thomas Harte on 23/02/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef StaticAnalyser_Coleco_StaticAnalyser_hpp
#define StaticAnalyser_Coleco_StaticAnalyser_hpp
#include "../StaticAnalyser.hpp"
namespace Analyser {
namespace Static {
namespace Coleco {
void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination);
}
}
}
#endif /* StaticAnalyser_hpp */

View File

@@ -7,28 +7,27 @@
//
#include "Disk.hpp"
#include "../../Storage/Disk/DiskController.hpp"
#include "../../Storage/Disk/Encodings/CommodoreGCR.hpp"
#include "../../Storage/Data/Commodore.hpp"
#include "../../../Storage/Disk/Controller/DiskController.hpp"
#include "../../../Storage/Disk/Encodings/CommodoreGCR.hpp"
#include "../../../Storage/Data/Commodore.hpp"
#include <limits>
#include <vector>
#include <array>
using namespace StaticAnalyser::Commodore;
using namespace Analyser::Static::Commodore;
class CommodoreGCRParser: public Storage::Disk::Controller {
public:
std::shared_ptr<Storage::Disk::Drive> drive;
CommodoreGCRParser() : Storage::Disk::Controller(4000000, 1, 300), shift_register_(0), track_(1)
{
drive.reset(new Storage::Disk::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);
}
struct Sector
{
struct Sector {
uint8_t sector, track;
std::array<uint8_t, 256> data;
bool header_checksum_matched;
@@ -40,17 +39,15 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
@returns a sector if one was found; @c nullptr otherwise.
*/
std::shared_ptr<Sector> get_sector(uint8_t track, uint8_t sector)
{
int difference = (int)track - (int)track_;
std::shared_ptr<Sector> get_sector(uint8_t track, uint8_t sector) {
int difference = static_cast<int>(track) - static_cast<int>(track_);
track_ = track;
if(difference)
{
if(difference) {
int direction = difference < 0 ? -1 : 1;
difference *= 2 * direction;
for(int c = 0; c < difference; c++) step(direction);
for(int c = 0; c < difference; c++) get_drive().step(direction);
unsigned int zone = 3;
if(track >= 18) zone = 2;
@@ -69,112 +66,96 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
uint8_t track_;
std::shared_ptr<Sector> sector_cache_[65536];
void process_input_bit(int value, unsigned int cycles_since_index_hole)
{
shift_register_ = ((shift_register_ << 1) | (unsigned int)value) & 0x3ff;
void process_input_bit(int value) {
shift_register_ = ((shift_register_ << 1) | static_cast<unsigned int>(value)) & 0x3ff;
bit_count_++;
}
unsigned int proceed_to_next_block()
{
unsigned int proceed_to_next_block(int max_index_count) {
// find GCR lead-in
proceed_to_shift_value(0x3ff);
if(shift_register_ != 0x3ff) return 0xff;
// find end of lead-in
while(shift_register_ == 0x3ff && index_count_ < 2)
{
run_for_cycles(1);
while(shift_register_ == 0x3ff && index_count_ < max_index_count) {
run_for(Cycles(1));
}
// continue for a further nine bits
bit_count_ = 0;
while(bit_count_ < 9 && index_count_ < 2)
{
run_for_cycles(1);
while(bit_count_ < 9 && index_count_ < max_index_count) {
run_for(Cycles(1));
}
return Storage::Encodings::CommodoreGCR::decoding_from_dectet(shift_register_);
}
unsigned int get_next_byte()
{
unsigned int get_next_byte() {
bit_count_ = 0;
while(bit_count_ < 10) run_for_cycles(1);
while(bit_count_ < 10) run_for(Cycles(1));
return Storage::Encodings::CommodoreGCR::decoding_from_dectet(shift_register_);
}
void proceed_to_shift_value(unsigned int shift_value)
{
index_count_ = 0;
while(shift_register_ != shift_value && index_count_ < 2)
{
run_for_cycles(1);
void proceed_to_shift_value(unsigned int shift_value) {
const int max_index_count = index_count_ + 2;
while(shift_register_ != shift_value && index_count_ < max_index_count) {
run_for(Cycles(1));
}
}
void process_index_hole()
{
void process_index_hole() {
index_count_++;
}
std::shared_ptr<Sector> get_sector(uint8_t sector)
{
uint16_t sector_address = (uint16_t)((track_ << 8) | sector);
std::shared_ptr<Sector> get_sector(uint8_t sector) {
uint16_t sector_address = static_cast<uint16_t>((track_ << 8) | sector);
if(sector_cache_[sector_address]) return sector_cache_[sector_address];
std::shared_ptr<Sector> first_sector = get_next_sector();
if(!first_sector) return first_sector;
if(first_sector->sector == sector) return first_sector;
while(1)
{
while(1) {
std::shared_ptr<Sector> next_sector = get_next_sector();
if(next_sector->sector == first_sector->sector) return nullptr;
if(next_sector->sector == sector) return next_sector;
}
}
std::shared_ptr<Sector> get_next_sector()
{
std::shared_ptr<Sector> get_next_sector() {
std::shared_ptr<Sector> sector(new Sector);
index_count_ = 0;
const int max_index_count = index_count_ + 2;
while(index_count_ < 2)
{
while(index_count_ < max_index_count) {
// look for a sector header
while(1)
{
if(proceed_to_next_block() == 0x08) break;
if(index_count_ >= 2) return nullptr;
while(1) {
if(proceed_to_next_block(max_index_count) == 0x08) break;
if(index_count_ >= max_index_count) return nullptr;
}
// get sector details, skip if this looks malformed
uint8_t checksum = (uint8_t)get_next_byte();
sector->sector = (uint8_t)get_next_byte();
sector->track = (uint8_t)get_next_byte();
uint8_t checksum = static_cast<uint8_t>(get_next_byte());
sector->sector = static_cast<uint8_t>(get_next_byte());
sector->track = static_cast<uint8_t>(get_next_byte());
uint8_t disk_id[2];
disk_id[0] = (uint8_t)get_next_byte();
disk_id[1] = (uint8_t)get_next_byte();
disk_id[0] = static_cast<uint8_t>(get_next_byte());
disk_id[1] = static_cast<uint8_t>(get_next_byte());
if(checksum != (sector->sector ^ sector->track ^ disk_id[0] ^ disk_id[1])) continue;
// look for the following data
while(1)
{
if(proceed_to_next_block() == 0x07) break;
if(index_count_ >= 2) return nullptr;
while(1) {
if(proceed_to_next_block(max_index_count) == 0x07) break;
if(index_count_ >= max_index_count) return nullptr;
}
checksum = 0;
for(size_t c = 0; c < 256; c++)
{
sector->data[c] = (uint8_t)get_next_byte();
for(std::size_t c = 0; c < 256; c++) {
sector->data[c] = static_cast<uint8_t>(get_next_byte());
checksum ^= sector->data[c];
}
if(checksum == get_next_byte())
{
uint16_t sector_address = (uint16_t)((sector->track << 8) | sector->sector);
if(checksum == get_next_byte()) {
uint16_t sector_address = static_cast<uint16_t>((sector->track << 8) | sector->sector);
sector_cache_[sector_address] = sector;
return sector;
}
@@ -184,9 +165,8 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
}
};
std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk)
{
std::list<File> files;
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);
@@ -197,8 +177,7 @@ std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storag
std::vector<uint8_t> directory;
uint8_t next_track = 18;
uint8_t next_sector = 1;
while(1)
{
while(1) {
sector = parser.get_sector(next_track, next_sector);
if(!sector) break;
directory.insert(directory.end(), sector->data.begin(), sector->data.end());
@@ -209,14 +188,12 @@ std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storag
}
// parse directory
size_t header_pointer = (size_t)-32;
while(header_pointer+32+31 < directory.size())
{
std::size_t header_pointer = static_cast<std::size_t>(-32);
while(header_pointer+32+31 < directory.size()) {
header_pointer += 32;
File new_file;
switch(directory[header_pointer + 2] & 7)
{
switch(directory[header_pointer + 2] & 7) {
case 0: // DEL files
default: continue; // Unknown file types
@@ -230,25 +207,23 @@ std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storag
next_sector = directory[header_pointer + 4];
new_file.raw_name.reserve(16);
for(size_t c = 0; c < 16; c++)
{
for(std::size_t c = 0; c < 16; c++) {
new_file.raw_name.push_back(directory[header_pointer + 5 + c]);
}
new_file.name = Storage::Data::Commodore::petscii_from_bytes(&new_file.raw_name[0], 16, false);
size_t number_of_sectors = (size_t)directory[header_pointer + 0x1e] + ((size_t)directory[header_pointer + 0x1f] << 8);
std::size_t number_of_sectors = static_cast<std::size_t>(directory[header_pointer + 0x1e]) + (static_cast<std::size_t>(directory[header_pointer + 0x1f]) << 8);
new_file.data.reserve((number_of_sectors - 1) * 254 + 252);
bool is_first_sector = true;
while(next_track)
{
while(next_track) {
sector = parser.get_sector(next_track, next_sector);
if(!sector) break;
next_track = sector->data[0];
next_sector = sector->data[1];
if(is_first_sector) new_file.starting_address = (uint16_t)sector->data[2] | (uint16_t)(sector->data[3] << 8);
if(is_first_sector) new_file.starting_address = static_cast<uint16_t>(sector->data[2]) | static_cast<uint16_t>(sector->data[3] << 8);
if(next_track)
new_file.data.insert(new_file.data.end(), sector->data.begin() + (is_first_sector ? 4 : 2), sector->data.end());
else

View File

@@ -9,17 +9,19 @@
#ifndef StaticAnalyser_Commodore_Disk_hpp
#define StaticAnalyser_Commodore_Disk_hpp
#include "../../Storage/Disk/Disk.hpp"
#include "../../../Storage/Disk/Disk.hpp"
#include "File.hpp"
#include <list>
namespace StaticAnalyser {
#include <vector>
namespace Analyser {
namespace Static {
namespace Commodore {
std::list<File> GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk);
std::vector<File> GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk);
}
}
}
#endif /* Disk_hpp */

View File

@@ -8,8 +8,7 @@
#include "File.hpp"
bool StaticAnalyser::Commodore::File::is_basic()
{
bool Analyser::Static::Commodore::File::is_basic() {
// BASIC files are always relocatable (?)
if(type != File::RelocatableProgram) return false;
@@ -23,26 +22,24 @@ bool StaticAnalyser::Commodore::File::is_basic()
// [4 bytes: this line number]
// ... null-terminated code ...
// (with a next line address of 0000 indicating end of program)ß
while(1)
{
if(line_address - starting_address >= data.size() + 2) break;
while(1) {
if(static_cast<size_t>(line_address - starting_address) >= data.size() + 2) break;
uint16_t next_line_address = data[line_address - starting_address];
next_line_address |= data[line_address - starting_address + 1] << 8;
if(!next_line_address)
{
if(!next_line_address) {
return true;
}
if(next_line_address < line_address + 5) break;
if(line_address - starting_address >= data.size() + 5) break;
if(static_cast<size_t>(line_address - starting_address) >= data.size() + 5) break;
uint16_t next_line_number = data[line_address - starting_address + 2];
next_line_number |= data[line_address - starting_address + 3] << 8;
if(next_line_number <= line_number) break;
line_number = (uint16_t)next_line_number;
line_number = static_cast<uint16_t>(next_line_number);
line_address = next_line_address;
}

View File

@@ -12,18 +12,17 @@
#include <string>
#include <vector>
namespace StaticAnalyser {
namespace Analyser {
namespace Static {
namespace Commodore {
struct File {
File() : is_closed(false), is_locked(false) {}
std::wstring name;
std::vector<uint8_t> raw_name;
uint16_t starting_address;
uint16_t ending_address;
bool is_locked;
bool is_closed;
bool is_locked = false;
bool is_closed = false;
enum {
RelocatableProgram,
NonRelocatableProgram,
@@ -36,6 +35,7 @@ struct File {
bool is_basic();
};
}
}
}

View File

@@ -0,0 +1,154 @@
//
// CommodoreAnalyser.cpp
// Clock Signal
//
// Created by Thomas Harte on 06/09/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "StaticAnalyser.hpp"
#include "Disk.hpp"
#include "File.hpp"
#include "Tape.hpp"
#include "Target.hpp"
#include "../../../Storage/Cartridge/Encodings/CommodoreROM.hpp"
#include <algorithm>
#include <sstream>
using namespace Analyser::Static::Commodore;
static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
Vic20CartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> vic20_cartridges;
for(const auto &cartridge : cartridges) {
const auto &segments = cartridge->get_segments();
// only one mapped item is allowed
if(segments.size() != 1) continue;
// which must be 16 kb in size
Storage::Cartridge::Cartridge::Segment segment = segments.front();
if(segment.start_address != 0xa000) continue;
if(!Storage::Cartridge::Encodings::CommodoreROM::isROM(segment.data)) continue;
vic20_cartridges.push_back(cartridge);
}
return vic20_cartridges;
}
void Analyser::Static::Commodore::AddTargets(const Media &media, std::vector<std::unique_ptr<Analyser::Static::Target>> &destination, const std::string &file_name) {
std::unique_ptr<Target> target(new Target);
target->machine = Machine::Vic20; // TODO: machine estimation
target->confidence = 0.5; // TODO: a proper estimation
int device = 0;
std::vector<File> files;
bool is_disk = false;
// strip out inappropriate cartridges
target->media.cartridges = Vic20CartridgesFrom(media.cartridges);
// check disks
for(auto &disk : media.disks) {
std::vector<File> disk_files = GetFiles(disk);
if(!disk_files.empty()) {
is_disk = true;
files.insert(files.end(), disk_files.begin(), disk_files.end());
target->media.disks.push_back(disk);
if(!device) device = 8;
}
}
// check tapes
for(auto &tape : media.tapes) {
std::vector<File> tape_files = GetFiles(tape);
tape->reset();
if(!tape_files.empty()) {
files.insert(files.end(), tape_files.begin(), tape_files.end());
target->media.tapes.push_back(tape);
if(!device) device = 1;
}
}
if(!files.empty()) {
target->memory_model = Target::MemoryModel::Unexpanded;
std::ostringstream string_stream;
string_stream << "LOAD\"" << (is_disk ? "*" : "") << "\"," << device << ",";
if(files.front().is_basic()) {
string_stream << "0";
} else {
string_stream << "1";
}
string_stream << "\nRUN\n";
target->loading_command = string_stream.str();
// make a first guess based on loading address
switch(files.front().starting_address) {
default:
printf("Starting address %04x?\n", files.front().starting_address);
case 0x1001:
target->memory_model = Target::MemoryModel::Unexpanded;
break;
case 0x1201:
target->memory_model = Target::MemoryModel::ThirtyTwoKB;
break;
case 0x0401:
target->memory_model = Target::MemoryModel::EightKB;
break;
}
// 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();
// bool is_basic = file.is_basic();
/*if(is_basic)
{
// BASIC files may be relocated, so the only limit is size.
//
// An unexpanded machine has 3583 bytes free for BASIC;
// a 3kb expanded machine has 6655 bytes free.
if(file_size > 6655)
target->vic20.memory_model = Vic20MemoryModel::ThirtyTwoKB;
else if(target->vic20.memory_model == Vic20MemoryModel::Unexpanded && file_size > 3583)
target->vic20.memory_model = Vic20MemoryModel::EightKB;
}
else
{*/
// if(!file.type == File::NonRelocatableProgram)
// {
// Non-BASIC files may be relocatable but, if so, by what logic?
// Given that this is unknown, take starting address as literal
// and check against memory windows.
//
// (ignoring colour memory...)
// An unexpanded Vic has memory between 0x0000 and 0x0400; and between 0x1000 and 0x2000.
// A 3kb expanded Vic fills in the gap and has memory between 0x0000 and 0x2000.
// A 32kb expanded Vic has memory in the entire low 32kb.
// uint16_t starting_address = file.starting_address;
// If anything above the 8kb mark is touched, mark as a 32kb machine; otherwise if the
// region 0x0400 to 0x1000 is touched and this is an unexpanded machine, mark as 3kb.
// if(starting_address + file_size > 0x2000)
// target->memory_model = Target::MemoryModel::ThirtyTwoKB;
// else if(target->memory_model == Target::MemoryModel::Unexpanded && !(starting_address >= 0x1000 || starting_address+file_size < 0x0400))
// target->memory_model = Target::MemoryModel::ThirtyTwoKB;
// }
// }
}
if(!target->media.empty()) {
// Inspect filename for a region hint.
std::string lowercase_name = file_name;
std::transform(lowercase_name.begin(), lowercase_name.end(), lowercase_name.begin(), ::tolower);
if(lowercase_name.find("ntsc") != std::string::npos) {
target->region = Analyser::Static::Commodore::Target::Region::American;
}
destination.push_back(std::move(target));
}
}

View File

@@ -10,17 +10,15 @@
#define StaticAnalyser_Commodore_StaticAnalyser_hpp
#include "../StaticAnalyser.hpp"
#include <string>
namespace StaticAnalyser {
namespace Analyser {
namespace Static {
namespace Commodore {
void AddTargets(
const std::list<std::shared_ptr<Storage::Disk::Disk>> &disks,
const std::list<std::shared_ptr<Storage::Tape::Tape>> &tapes,
const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges,
std::list<Target> &destination
);
void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination, const std::string &file_name);
}
}
}

View File

@@ -8,29 +8,24 @@
#include "Tape.hpp"
#include "../../Storage/Tape/Parsers/Commodore.hpp"
#include "../../../Storage/Tape/Parsers/Commodore.hpp"
using namespace StaticAnalyser::Commodore;
using namespace Analyser::Static::Commodore;
std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape)
{
std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
Storage::Tape::Commodore::Parser parser;
std::list<File> file_list;
std::vector<File> file_list;
std::unique_ptr<Storage::Tape::Commodore::Header> header = parser.get_next_header(tape);
while(!tape->is_at_end())
{
if(!header)
{
while(!tape->is_at_end()) {
if(!header) {
header = parser.get_next_header(tape);
continue;
}
switch(header->type)
{
case Storage::Tape::Commodore::Header::DataSequenceHeader:
{
switch(header->type) {
case Storage::Tape::Commodore::Header::DataSequenceHeader: {
File new_file;
new_file.name = header->name;
new_file.raw_name = header->raw_name;
@@ -39,8 +34,7 @@ std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storag
new_file.type = File::DataSequence;
new_file.data.swap(header->data);
while(!tape->is_at_end())
{
while(!tape->is_at_end()) {
header = parser.get_next_header(tape);
if(!header) continue;
if(header->type != Storage::Tape::Commodore::Header::DataBlock) break;
@@ -52,11 +46,9 @@ std::list<File> StaticAnalyser::Commodore::GetFiles(const std::shared_ptr<Storag
break;
case Storage::Tape::Commodore::Header::RelocatableProgram:
case Storage::Tape::Commodore::Header::NonRelocatableProgram:
{
case Storage::Tape::Commodore::Header::NonRelocatableProgram: {
std::unique_ptr<Storage::Tape::Commodore::Data> data = parser.get_next_data(tape);
if(data)
{
if(data) {
File new_file;
new_file.name = header->name;
new_file.raw_name = header->raw_name;

View File

@@ -9,15 +9,16 @@
#ifndef StaticAnalyser_Commodore_Tape_hpp
#define StaticAnalyser_Commodore_Tape_hpp
#include "../../Storage/Tape/Tape.hpp"
#include "../../../Storage/Tape/Tape.hpp"
#include "File.hpp"
#include <list>
namespace StaticAnalyser {
namespace Analyser {
namespace Static {
namespace Commodore {
std::list<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
}
}
}

View File

@@ -0,0 +1,42 @@
//
// Target.hpp
// Clock Signal
//
// Created by Thomas Harte on 09/03/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_Commodore_Target_h
#define Analyser_Static_Commodore_Target_h
#include "../StaticAnalyser.hpp"
namespace Analyser {
namespace Static {
namespace Commodore {
struct Target: public ::Analyser::Static::Target {
enum class MemoryModel {
Unexpanded,
EightKB,
ThirtyTwoKB
};
enum class Region {
American,
Danish,
Japanese,
European,
Swedish
};
MemoryModel memory_model = MemoryModel::Unexpanded;
Region region = Region::European;
bool has_c1540 = false;
};
}
}
}
#endif /* Analyser_Static_Commodore_Target_h */

View File

@@ -6,25 +6,25 @@
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "Disassembler6502.hpp"
#include <map>
#include "6502.hpp"
using namespace StaticAnalyser::MOS6502;
#include "Kernel.hpp"
struct PartialDisassembly {
Disassembly disassembly;
std::vector<uint16_t> remaining_entry_points;
};
using namespace Analyser::Static::MOS6502;
namespace {
static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<uint8_t> &memory, uint16_t start_address, uint16_t entry_point)
{
using PartialDisassembly = Analyser::Static::Disassembly::PartialDisassembly<Disassembly, uint16_t>;
struct MOS6502Disassembler {
static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<uint8_t> &memory, const std::function<std::size_t(uint16_t)> &address_mapper, uint16_t entry_point) {
disassembly.disassembly.internal_calls.insert(entry_point);
uint16_t address = entry_point;
while(1)
{
uint16_t local_address = address - start_address;
while(true) {
std::size_t local_address = address_mapper(address);
if(local_address >= memory.size()) return;
struct Instruction instruction;
Instruction instruction;
instruction.address = address;
address++;
@@ -32,8 +32,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
uint8_t operation = memory[local_address];
// decode addressing mode
switch(operation&0x1f)
{
switch(operation&0x1f) {
case 0x00:
if(operation >= 0x80) instruction.addressing_mode = Instruction::Immediate;
else if(operation == 0x20) instruction.addressing_mode = Instruction::Absolute;
@@ -93,8 +92,11 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
#define IM_INSTRUCTION(base, op) \
case base: instruction.operation = op; break;
switch(operation)
{
switch(operation) {
default:
instruction.operation = Instruction::KIL;
break;
IM_INSTRUCTION(0x00, Instruction::BRK)
IM_INSTRUCTION(0x20, Instruction::JSR)
IM_INSTRUCTION(0x40, Instruction::RTI)
@@ -189,7 +191,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
break;
case 0x87: case 0x97: case 0x83: case 0x8f:
instruction.operation = Instruction::SAX;
instruction.operation = Instruction::AXS;
break;
case 0xa7: case 0xb7: case 0xa3: case 0xb3: case 0xaf: case 0xbf:
instruction.operation = Instruction::LAX;
@@ -205,7 +207,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
IM_INSTRUCTION(0x6b, Instruction::ARR)
IM_INSTRUCTION(0x8b, Instruction::XAA)
IM_INSTRUCTION(0xab, Instruction::LAX)
IM_INSTRUCTION(0xcb, Instruction::AXS)
IM_INSTRUCTION(0xcb, Instruction::SAX)
IM_INSTRUCTION(0xeb, Instruction::SBC)
case 0x93: case 0x9f:
instruction.operation = Instruction::AHX;
@@ -221,8 +223,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
#undef IM_INSTRUCTION
// get operand
switch(instruction.addressing_mode)
{
switch(instruction.addressing_mode) {
// zero-byte operands
case Instruction::Implied:
instruction.operand = 0;
@@ -232,9 +233,8 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
case Instruction::Immediate:
case Instruction::ZeroPage: case Instruction::ZeroPageX: case Instruction::ZeroPageY:
case Instruction::IndexedIndirectX: case Instruction::IndirectIndexedY:
case Instruction::Relative:
{
uint16_t operand_address = address - start_address;
case Instruction::Relative: {
std::size_t operand_address = address_mapper(address);
if(operand_address >= memory.size()) return;
address++;
@@ -244,13 +244,13 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
// two-byte operands
case Instruction::Absolute: case Instruction::AbsoluteX: case Instruction::AbsoluteY:
case Instruction::Indirect:
{
uint16_t operand_address = address - start_address;
if(operand_address >= memory.size()-1) return;
case Instruction::Indirect: {
std::size_t low_operand_address = address_mapper(address);
std::size_t high_operand_address = address_mapper(address + 1);
if(low_operand_address >= memory.size() || high_operand_address >= memory.size()) return;
address += 2;
instruction.operand = memory[operand_address] | (uint16_t)(memory[operand_address+1] << 8);
instruction.operand = memory[low_operand_address] | static_cast<uint16_t>(memory[high_operand_address] << 8);
}
break;
}
@@ -259,59 +259,62 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
disassembly.disassembly.instructions_by_address[instruction.address] = instruction;
// TODO: something wider-ranging than this
if(instruction.addressing_mode == Instruction::Absolute && (instruction.operand < start_address || instruction.operand >= start_address + memory.size()))
{
if( instruction.operation == Instruction::STY ||
instruction.operation == Instruction::STX ||
instruction.operation == Instruction::STA)
disassembly.disassembly.external_stores.insert(instruction.operand);
if( instruction.operation == Instruction::LDY ||
instruction.operation == Instruction::LDX ||
instruction.operation == Instruction::LDA)
disassembly.disassembly.external_loads.insert(instruction.operand);
if(instruction.addressing_mode == Instruction::Absolute || instruction.addressing_mode == Instruction::ZeroPage) {
std::size_t mapped_address = address_mapper(instruction.operand);
bool is_external = mapped_address >= memory.size();
switch(instruction.operation) {
default: break;
case Instruction::LDY: case Instruction::LDX: case Instruction::LDA:
case Instruction::LAX:
case Instruction::AND: case Instruction::EOR: case Instruction::ORA: case Instruction::BIT:
case Instruction::ADC: case Instruction::SBC:
case Instruction::LAS:
case Instruction::CMP: case Instruction::CPX: case Instruction::CPY:
(is_external ? disassembly.disassembly.external_loads : disassembly.disassembly.internal_loads).insert(instruction.operand);
break;
case Instruction::STY: case Instruction::STX: case Instruction::STA:
case Instruction::AXS: case Instruction::AHX: case Instruction::SHX: case Instruction::SHY:
case Instruction::TAS:
(is_external ? disassembly.disassembly.external_stores : disassembly.disassembly.internal_stores).insert(instruction.operand);
break;
case Instruction::SLO: case Instruction::RLA: case Instruction::SRE: case Instruction::RRA:
case Instruction::DCP: case Instruction::ISC:
case Instruction::INC: case Instruction::DEC:
case Instruction::ASL: case Instruction::ROL: case Instruction::LSR: case Instruction::ROR:
(is_external ? disassembly.disassembly.external_modifies : disassembly.disassembly.internal_modifies).insert(instruction.operand);
break;
}
}
// decide on overall flow control
if(instruction.operation == Instruction::RTS || instruction.operation == Instruction::RTI) return;
if(instruction.operation == Instruction::BRK) return; // TODO: check whether IRQ vector is within memory range
if(instruction.operation == Instruction::JSR)
{
if(instruction.operation == Instruction::JSR) {
disassembly.remaining_entry_points.push_back(instruction.operand);
}
if(instruction.operation == Instruction::JMP)
{
if(instruction.operation == Instruction::JMP) {
if(instruction.addressing_mode == Instruction::Absolute)
disassembly.remaining_entry_points.push_back(instruction.operand);
return;
}
if(instruction.addressing_mode == Instruction::Relative)
{
uint16_t destination = (uint16_t)(address + (int8_t)instruction.operand);
if(instruction.addressing_mode == Instruction::Relative) {
uint16_t destination = static_cast<uint16_t>(address + (int8_t)instruction.operand);
disassembly.remaining_entry_points.push_back(destination);
}
}
}
Disassembly StaticAnalyser::MOS6502::Disassemble(const std::vector<uint8_t> &memory, uint16_t start_address, std::vector<uint16_t> entry_points)
{
PartialDisassembly partialDisassembly;
partialDisassembly.remaining_entry_points = entry_points;
};
while(!partialDisassembly.remaining_entry_points.empty())
{
// pull the next entry point from the back of the vector
uint16_t next_entry_point = partialDisassembly.remaining_entry_points.back();
partialDisassembly.remaining_entry_points.pop_back();
} // end of anonymous namespace
// if that address has already bene visited, forget about it
if(partialDisassembly.disassembly.instructions_by_address.find(next_entry_point) != partialDisassembly.disassembly.instructions_by_address.end()) continue;
// if it's outgoing, log it as such and forget about it; otherwise disassemble
if(next_entry_point < start_address || next_entry_point >= start_address + memory.size())
partialDisassembly.disassembly.outward_calls.insert(next_entry_point);
else
AddToDisassembly(partialDisassembly, memory, start_address, next_entry_point);
}
return std::move(partialDisassembly.disassembly);
Disassembly Analyser::Static::MOS6502::Disassemble(
const std::vector<uint8_t> &memory,
const std::function<std::size_t(uint16_t)> &address_mapper,
std::vector<uint16_t> entry_points) {
return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, MOS6502Disassembler>(memory, address_mapper, entry_points);
}

View File

@@ -0,0 +1,101 @@
//
// 6502.hpp
// Clock Signal
//
// Created by Thomas Harte on 10/11/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef StaticAnalyser_Disassembler_6502_hpp
#define StaticAnalyser_Disassembler_6502_hpp
#include <cstdint>
#include <functional>
#include <map>
#include <memory>
#include <set>
#include <vector>
namespace Analyser {
namespace Static {
namespace MOS6502 {
/*!
Describes a 6502 instruciton — its address, the operation it performs, its addressing mode
and its operand, if any.
*/
struct Instruction {
/*! The address this instruction starts at. This is a mapped address. */
uint16_t address = 0;
/*! The operation this instruction performs. */
enum {
BRK, JSR, RTI, RTS, JMP,
CLC, SEC, CLD, SED, CLI, SEI, CLV,
NOP,
SLO, RLA, SRE, RRA, ALR, ARR,
SAX, LAX, DCP, ISC,
ANC, XAA, AXS,
AND, EOR, ORA, BIT,
ADC, SBC,
AHX, SHY, SHX, TAS, LAS,
LDA, STA, LDX, STX, LDY, STY,
BPL, BMI, BVC, BVS, BCC, BCS, BNE, BEQ,
CMP, CPX, CPY,
INC, DEC, DEX, DEY, INX, INY,
ASL, ROL, LSR, ROR,
TAX, TXA, TAY, TYA, TSX, TXS,
PLA, PHA, PLP, PHP,
KIL
} operation = NOP;
/*! The addressing mode used by the instruction. */
enum {
Absolute,
AbsoluteX,
AbsoluteY,
Immediate,
Implied,
ZeroPage,
ZeroPageX,
ZeroPageY,
Indirect,
IndexedIndirectX,
IndirectIndexedY,
Relative,
} addressing_mode = Implied;
/*! The instruction's operand, if any. */
uint16_t operand = 0;
};
/*! Represents the disassembled form of a program. */
struct Disassembly {
/*! All instructions found, mapped by address. */
std::map<uint16_t, Instruction> instructions_by_address;
/*! The set of all calls or jumps that land outside of the area covered by the data provided for disassembly. */
std::set<uint16_t> outward_calls;
/*! The set of all calls or jumps that land inside of the area covered by the data provided for disassembly. */
std::set<uint16_t> internal_calls;
/*! The sets of all stores, loads and modifies that occur to data outside of the area covered by the data provided for disassembly. */
std::set<uint16_t> external_stores, external_loads, external_modifies;
/*! The sets of all stores, loads and modifies that occur to data inside of the area covered by the data provided for disassembly. */
std::set<uint16_t> internal_stores, internal_loads, internal_modifies;
};
/*!
Disassembles the data provided as @c memory, mapping it into the 6502's full address range via the @c address_mapper,
starting disassembly from each of the @c entry_points.
*/
Disassembly Disassemble(
const std::vector<uint8_t> &memory,
const std::function<std::size_t(uint16_t)> &address_mapper,
std::vector<uint16_t> entry_points);
}
}
}
#endif /* Disassembler6502_hpp */

View File

@@ -0,0 +1,9 @@
//
// AddressMapper.cpp
// Clock Signal
//
// Created by Thomas Harte on 30/12/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "AddressMapper.hpp"

View File

@@ -0,0 +1,32 @@
//
// AddressMapper.hpp
// Clock Signal
//
// Created by Thomas Harte on 30/12/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef AddressMapper_hpp
#define AddressMapper_hpp
#include <functional>
namespace Analyser {
namespace Static {
namespace Disassembler {
/*!
Provides an address mapper that relocates a chunk of memory so that it starts at
address @c start_address.
*/
template <typename T> std::function<std::size_t(T)> OffsetMapper(T start_address) {
return [start_address](T argument) {
return static_cast<std::size_t>(argument - start_address);
};
}
}
}
}
#endif /* AddressMapper_hpp */

View File

@@ -0,0 +1,52 @@
//
// Kernel.hpp
// Clock Signal
//
// Created by Thomas Harte on 31/12/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef Kernel_hpp
#define Kernel_hpp
namespace Analyser {
namespace Static {
namespace Disassembly {
template <typename D, typename S> struct PartialDisassembly {
D disassembly;
std::vector<S> remaining_entry_points;
};
template <typename D, typename S, typename Disassembler> D Disassemble(
const std::vector<uint8_t> &memory,
const std::function<std::size_t(S)> &address_mapper,
std::vector<S> entry_points) {
PartialDisassembly<D, S> partial_disassembly;
partial_disassembly.remaining_entry_points = entry_points;
while(!partial_disassembly.remaining_entry_points.empty()) {
// pull the next entry point from the back of the vector
S next_entry_point = partial_disassembly.remaining_entry_points.back();
partial_disassembly.remaining_entry_points.pop_back();
// if that address has already been visited, forget about it
if( partial_disassembly.disassembly.instructions_by_address.find(next_entry_point)
!= partial_disassembly.disassembly.instructions_by_address.end()) continue;
// if it's outgoing, log it as such and forget about it; otherwise disassemble
std::size_t mapped_entry_point = address_mapper(next_entry_point);
if(mapped_entry_point >= memory.size())
partial_disassembly.disassembly.outward_calls.insert(next_entry_point);
else
Disassembler::AddToDisassembly(partial_disassembly, memory, address_mapper, next_entry_point);
}
return partial_disassembly.disassembly;
}
}
}
}
#endif /* Kernel_hpp */

View File

@@ -0,0 +1,619 @@
//
// Z80.cpp
// Clock Signal
//
// Created by Thomas Harte on 30/12/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "Z80.hpp"
#include "Kernel.hpp"
using namespace Analyser::Static::Z80;
namespace {
using PartialDisassembly = Analyser::Static::Disassembly::PartialDisassembly<Disassembly, uint16_t>;
class Accessor {
public:
Accessor(const std::vector<uint8_t> &memory, const std::function<std::size_t(uint16_t)> &address_mapper, uint16_t address) :
memory_(memory), address_mapper_(address_mapper), address_(address) {}
uint8_t byte() {
std::size_t mapped_address = address_mapper_(address_);
address_++;
if(mapped_address >= memory_.size()) {
overrun_ = true;
return 0xff;
}
return memory_[mapped_address];
}
uint16_t word() {
uint8_t low = byte();
uint8_t high = byte();
return static_cast<uint16_t>(low | (high << 8));
}
bool overrun() {
return overrun_;
}
bool at_end() {
std::size_t mapped_address = address_mapper_(address_);
return mapped_address >= memory_.size();
}
uint16_t address() {
return address_;
}
private:
const std::vector<uint8_t> &memory_;
const std::function<std::size_t(uint16_t)> &address_mapper_;
uint16_t address_;
bool overrun_ = false;
};
#define x(v) (v >> 6)
#define y(v) ((v >> 3) & 7)
#define q(v) ((v >> 3) & 1)
#define p(v) ((v >> 4) & 3)
#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::P, Instruction::Condition::M
};
Instruction::Location register_pair_table[] = {
Instruction::Location::BC,
Instruction::Location::DE,
Instruction::Location::HL,
Instruction::Location::SP
};
Instruction::Location register_pair_table2[] = {
Instruction::Location::BC,
Instruction::Location::DE,
Instruction::Location::HL,
Instruction::Location::AF
};
Instruction::Location RegisterTableEntry(int offset, Accessor &accessor, Instruction &instruction, bool needs_indirect_offset) {
Instruction::Location register_table[] = {
Instruction::Location::B, Instruction::Location::C,
Instruction::Location::D, Instruction::Location::E,
Instruction::Location::H, Instruction::Location::L,
Instruction::Location::HL_Indirect,
Instruction::Location::A
};
Instruction::Location location = register_table[offset];
if(location == Instruction::Location::HL_Indirect && needs_indirect_offset) {
instruction.offset = accessor.byte() - 128;
}
return location;
}
Instruction::Operation alu_table[] = {
Instruction::Operation::ADD,
Instruction::Operation::ADC,
Instruction::Operation::SUB,
Instruction::Operation::SBC,
Instruction::Operation::AND,
Instruction::Operation::XOR,
Instruction::Operation::OR,
Instruction::Operation::CP
};
Instruction::Operation rotation_table[] = {
Instruction::Operation::RLC,
Instruction::Operation::RRC,
Instruction::Operation::RL,
Instruction::Operation::RR,
Instruction::Operation::SLA,
Instruction::Operation::SRA,
Instruction::Operation::SLL,
Instruction::Operation::SRL
};
Instruction::Operation block_table[][4] = {
{Instruction::Operation::LDI, Instruction::Operation::CPI, Instruction::Operation::INI, Instruction::Operation::OUTI},
{Instruction::Operation::LDD, Instruction::Operation::CPD, Instruction::Operation::IND, Instruction::Operation::OUTD},
{Instruction::Operation::LDIR, Instruction::Operation::CPIR, Instruction::Operation::INIR, Instruction::Operation::OTIR},
{Instruction::Operation::LDDR, Instruction::Operation::CPDR, Instruction::Operation::INDR, Instruction::Operation::OTDR},
};
void DisassembleCBPage(Accessor &accessor, Instruction &instruction, bool needs_indirect_offset) {
const uint8_t operation = accessor.byte();
if(!x(operation)) {
instruction.operation = rotation_table[y(operation)];
instruction.source = instruction.destination = RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset);
} else {
instruction.destination = RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset);
instruction.source = Instruction::Location::Operand;
instruction.operand = y(operation);
switch(x(operation)) {
case 1: instruction.operation = Instruction::Operation::BIT; break;
case 2: instruction.operation = Instruction::Operation::RES; break;
case 3: instruction.operation = Instruction::Operation::SET; break;
}
}
}
void DisassembleEDPage(Accessor &accessor, Instruction &instruction, bool needs_indirect_offset) {
const uint8_t operation = accessor.byte();
switch(x(operation)) {
default:
instruction.operation = Instruction::Operation::Invalid;
break;
case 2:
if(z(operation) < 4 && y(operation) >= 4) {
instruction.operation = block_table[y(operation)-4][z(operation)];
} else {
instruction.operation = Instruction::Operation::Invalid;
}
break;
case 3:
switch(z(operation)) {
case 0:
instruction.operation = Instruction::Operation::IN;
instruction.source = Instruction::Location::BC_Indirect;
if(y(operation) == 6) {
instruction.destination = Instruction::Location::None;
} else {
instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
}
break;
case 1:
instruction.operation = Instruction::Operation::OUT;
instruction.destination = Instruction::Location::BC_Indirect;
if(y(operation) == 6) {
instruction.source = Instruction::Location::None;
} else {
instruction.source = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
}
break;
case 2:
instruction.operation = (y(operation)&1) ? Instruction::Operation::ADC : Instruction::Operation::SBC;
instruction.destination = Instruction::Location::HL;
instruction.source = register_pair_table[y(operation) >> 1];
break;
case 3:
instruction.operation = Instruction::Operation::LD;
if(q(operation)) {
instruction.destination = RegisterTableEntry(p(operation), accessor, instruction, needs_indirect_offset);
instruction.source = Instruction::Location::Operand_Indirect;
} else {
instruction.destination = Instruction::Location::Operand_Indirect;
instruction.source = RegisterTableEntry(p(operation), accessor, instruction, needs_indirect_offset);
}
instruction.operand = accessor.word();
break;
case 4:
instruction.operation = Instruction::Operation::NEG;
break;
case 5:
instruction.operation = (y(operation) == 1) ? Instruction::Operation::RETI : Instruction::Operation::RETN;
break;
case 6:
instruction.operation = Instruction::Operation::IM;
instruction.source = Instruction::Location::Operand;
switch(y(operation)&3) {
case 0: instruction.operand = 0; break;
case 1: instruction.operand = 0; break;
case 2: instruction.operand = 1; break;
case 3: instruction.operand = 2; break;
}
break;
case 7:
switch(y(operation)) {
case 0:
instruction.operation = Instruction::Operation::LD;
instruction.destination = Instruction::Location::I;
instruction.source = Instruction::Location::A;
break;
case 1:
instruction.operation = Instruction::Operation::LD;
instruction.destination = Instruction::Location::R;
instruction.source = Instruction::Location::A;
break;
case 2:
instruction.operation = Instruction::Operation::LD;
instruction.destination = Instruction::Location::A;
instruction.source = Instruction::Location::I;
break;
case 3:
instruction.operation = Instruction::Operation::LD;
instruction.destination = Instruction::Location::A;
instruction.source = Instruction::Location::R;
break;
case 4: instruction.operation = Instruction::Operation::RRD; break;
case 5: instruction.operation = Instruction::Operation::RLD; break;
default: instruction.operation = Instruction::Operation::NOP; break;
}
break;
}
break;
}
}
void DisassembleMainPage(Accessor &accessor, Instruction &instruction) {
bool needs_indirect_offset = false;
enum HLSubstitution {
None, IX, IY
} hl_substitution = None;
while(true) {
uint8_t operation = accessor.byte();
switch(x(operation)) {
case 0:
switch(z(operation)) {
case 0:
switch(y(operation)) {
case 0: instruction.operation = Instruction::Operation::NOP; break;
case 1: instruction.operation = Instruction::Operation::EXAFAFd; break;
case 2:
instruction.operation = Instruction::Operation::DJNZ;
instruction.operand = accessor.byte() - 128;
break;
default:
instruction.operation = Instruction::Operation::JR;
instruction.operand = accessor.byte() - 128;
if(y(operation) >= 4) instruction.condition = condition_table[y(operation) - 4];
break;
}
break;
case 1:
if(y(operation)&1) {
instruction.operation = Instruction::Operation::ADD;
instruction.destination = Instruction::Location::HL;
instruction.source = register_pair_table[y(operation) >> 1];
} else {
instruction.operation = Instruction::Operation::LD;
instruction.destination = register_pair_table[y(operation) >> 1];
instruction.source = Instruction::Location::Operand;
instruction.operand = accessor.word();
}
break;
case 2:
switch(y(operation)) {
case 0:
instruction.operation = Instruction::Operation::LD;
instruction.destination = Instruction::Location::BC_Indirect;
instruction.source = Instruction::Location::A;
break;
case 1:
instruction.operation = Instruction::Operation::LD;
instruction.destination = Instruction::Location::A;
instruction.source = Instruction::Location::BC_Indirect;
break;
case 2:
instruction.operation = Instruction::Operation::LD;
instruction.destination = Instruction::Location::DE_Indirect;
instruction.source = Instruction::Location::A;
break;
case 3:
instruction.operation = Instruction::Operation::LD;
instruction.destination = Instruction::Location::A;
instruction.source = Instruction::Location::DE_Indirect;
break;
case 4:
instruction.operation = Instruction::Operation::LD;
instruction.destination = Instruction::Location::Operand_Indirect;
instruction.source = Instruction::Location::HL;
break;
case 5:
instruction.operation = Instruction::Operation::LD;
instruction.destination = Instruction::Location::HL;
instruction.source = Instruction::Location::Operand_Indirect;
break;
case 6:
instruction.operation = Instruction::Operation::LD;
instruction.destination = Instruction::Location::Operand_Indirect;
instruction.source = Instruction::Location::A;
break;
case 7:
instruction.operation = Instruction::Operation::LD;
instruction.destination = Instruction::Location::A;
instruction.source = Instruction::Location::Operand_Indirect;
break;
}
if(y(operation) > 3) {
instruction.operand = accessor.word();
}
break;
case 3:
if(y(operation)&1) {
instruction.operation = Instruction::Operation::DEC;
} else {
instruction.operation = Instruction::Operation::INC;
}
instruction.source = instruction.destination = register_pair_table[y(operation) >> 1];
break;
case 4:
instruction.operation = Instruction::Operation::INC;
instruction.source = instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
break;
case 5:
instruction.operation = Instruction::Operation::DEC;
instruction.source = instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
break;
case 6:
instruction.operation = Instruction::Operation::LD;
instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
instruction.source = Instruction::Location::Operand;
instruction.operand = accessor.byte();
break;
case 7:
switch(y(operation)) {
case 0: instruction.operation = Instruction::Operation::RLCA; break;
case 1: instruction.operation = Instruction::Operation::RRCA; break;
case 2: instruction.operation = Instruction::Operation::RLA; break;
case 3: instruction.operation = Instruction::Operation::RRA; break;
case 4: instruction.operation = Instruction::Operation::DAA; break;
case 5: instruction.operation = Instruction::Operation::CPL; break;
case 6: instruction.operation = Instruction::Operation::SCF; break;
case 7: instruction.operation = Instruction::Operation::CCF; break;
}
break;
}
break;
case 1:
if(y(operation) == 6 && z(operation) == 6) {
instruction.operation = Instruction::Operation::HALT;
} else {
instruction.operation = Instruction::Operation::LD;
instruction.source = RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset);
instruction.destination = RegisterTableEntry(y(operation), accessor, instruction, needs_indirect_offset);
}
break;
case 2:
instruction.operation = alu_table[y(operation)];
instruction.source = RegisterTableEntry(z(operation), accessor, instruction, needs_indirect_offset);
instruction.destination = Instruction::Location::A;
break;
case 3:
switch(z(operation)) {
case 0:
instruction.operation = Instruction::Operation::RET;
instruction.condition = condition_table[y(operation)];
break;
case 1:
switch(y(operation)) {
default:
instruction.operation = Instruction::Operation::POP;
instruction.source = register_pair_table2[y(operation) >> 1];
break;
case 1:
instruction.operation = Instruction::Operation::RET;
break;
case 3:
instruction.operation = Instruction::Operation::EXX;
break;
case 5:
instruction.operation = Instruction::Operation::JP;
instruction.source = Instruction::Location::HL;
break;
case 7:
instruction.operation = Instruction::Operation::LD;
instruction.destination = Instruction::Location::SP;
instruction.source = Instruction::Location::HL;
break;
}
break;
case 2:
instruction.operation = Instruction::Operation::JP;
instruction.condition = condition_table[y(operation)];
instruction.operand = accessor.word();
break;
case 3:
switch(y(operation)) {
case 0:
instruction.operation = Instruction::Operation::JP;
instruction.source = Instruction::Location::Operand;
instruction.operand = accessor.word();
break;
case 1:
DisassembleCBPage(accessor, instruction, needs_indirect_offset);
break;
case 2:
instruction.operation = Instruction::Operation::OUT;
instruction.source = Instruction::Location::A;
instruction.destination = Instruction::Location::Operand_Indirect;
instruction.operand = accessor.byte();
break;
case 3:
instruction.operation = Instruction::Operation::IN;
instruction.destination = Instruction::Location::A;
instruction.source = Instruction::Location::Operand_Indirect;
instruction.operand = accessor.byte();
break;
case 4:
instruction.operation = Instruction::Operation::EX;
instruction.destination = Instruction::Location::SP_Indirect;
instruction.source = Instruction::Location::HL;
break;
case 5:
instruction.operation = Instruction::Operation::EX;
instruction.destination = Instruction::Location::DE;
instruction.source = Instruction::Location::HL;
break;
case 6:
instruction.operation = Instruction::Operation::DI;
break;
case 7:
instruction.operation = Instruction::Operation::EI;
break;
}
break;
case 4:
instruction.operation = Instruction::Operation::CALL;
instruction.source = Instruction::Location::Operand_Indirect;
instruction.operand = accessor.word();
instruction.condition = condition_table[y(operation)];
break;
case 5:
switch(y(operation)) {
default:
instruction.operation = Instruction::Operation::PUSH;
instruction.source = register_pair_table2[y(operation) >> 1];
break;
case 1:
instruction.operation = Instruction::Operation::CALL;
instruction.source = Instruction::Location::Operand;
instruction.operand = accessor.word();
break;
case 3:
needs_indirect_offset = true;
hl_substitution = IX;
continue; // i.e. repeat loop.
case 5:
DisassembleEDPage(accessor, instruction, needs_indirect_offset);
break;
case 7:
needs_indirect_offset = true;
hl_substitution = IY;
continue; // i.e. repeat loop.
}
break;
case 6:
instruction.operation = alu_table[y(operation)];
instruction.source = Instruction::Location::Operand;
instruction.destination = Instruction::Location::A;
instruction.operand = accessor.byte();
break;
case 7:
instruction.operation = Instruction::Operation::RST;
instruction.source = Instruction::Location::Operand;
instruction.operand = y(operation) << 3;
break;
}
break;
}
// This while(true) isn't an infinite loop for everything except those paths that opt in
// via continue.
break;
}
// Perform IX/IY substitution for HL, if applicable.
if(hl_substitution != None) {
// EX DE, HL is not affected.
if(instruction.operation == Instruction::Operation::EX) return;
// If an (HL) is involved, switch it for IX+d or IY+d.
if( instruction.source == Instruction::Location::HL_Indirect ||
instruction.destination == Instruction::Location::HL_Indirect) {
if(instruction.source == Instruction::Location::HL_Indirect) {
instruction.source = (hl_substitution == IX) ? Instruction::Location::IX_Indirect_Offset : Instruction::Location::IY_Indirect_Offset;
}
if(instruction.destination == Instruction::Location::HL_Indirect) {
instruction.destination = (hl_substitution == IX) ? Instruction::Location::IX_Indirect_Offset : Instruction::Location::IY_Indirect_Offset;
}
return;
}
// Otherwise, switch either of H or L for I[X/Y]h and I[X/Y]l.
if(instruction.source == Instruction::Location::H) {
instruction.source = (hl_substitution == IX) ? Instruction::Location::IXh : Instruction::Location::IYh;
}
if(instruction.source == Instruction::Location::L) {
instruction.source = (hl_substitution == IX) ? Instruction::Location::IXl : Instruction::Location::IYl;
}
if(instruction.destination == Instruction::Location::H) {
instruction.destination = (hl_substitution == IX) ? Instruction::Location::IXh : Instruction::Location::IYh;
}
if(instruction.destination == Instruction::Location::L) {
instruction.destination = (hl_substitution == IX) ? Instruction::Location::IXl : Instruction::Location::IYl;
}
}
}
struct Z80Disassembler {
static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<uint8_t> &memory, const std::function<std::size_t(uint16_t)> &address_mapper, uint16_t entry_point) {
disassembly.disassembly.internal_calls.insert(entry_point);
Accessor accessor(memory, address_mapper, entry_point);
while(!accessor.at_end()) {
Instruction instruction;
instruction.address = accessor.address();
DisassembleMainPage(accessor, instruction);
// If any memory access was invalid, end disassembly.
if(accessor.overrun()) return;
// Store the instruction away.
disassembly.disassembly.instructions_by_address[instruction.address] = instruction;
// Update access tables.
int access_type =
((instruction.source == Instruction::Location::Operand_Indirect) ? 1 : 0) |
((instruction.destination == Instruction::Location::Operand_Indirect) ? 2 : 0);
uint16_t address = static_cast<uint16_t>(instruction.operand);
bool is_internal = address_mapper(address) < memory.size();
switch(access_type) {
default: break;
case 1:
if(is_internal) {
disassembly.disassembly.internal_loads.insert(address);
} else {
disassembly.disassembly.external_loads.insert(address);
}
break;
case 2:
if(is_internal) {
disassembly.disassembly.internal_stores.insert(address);
} else {
disassembly.disassembly.external_stores.insert(address);
}
break;
case 3:
if(is_internal) {
disassembly.disassembly.internal_modifies.insert(address);
} else {
disassembly.disassembly.internal_modifies.insert(address);
}
break;
}
// Add any (potentially) newly discovered entry point.
if( instruction.operation == Instruction::Operation::JP ||
instruction.operation == Instruction::Operation::JR ||
instruction.operation == Instruction::Operation::CALL ||
instruction.operation == Instruction::Operation::RST) {
disassembly.remaining_entry_points.push_back(static_cast<uint16_t>(instruction.operand));
}
// This is it if: an unconditional RET, RETI, RETN, JP or JR is found.
if(instruction.condition != Instruction::Condition::None) continue;
if(instruction.operation == Instruction::Operation::RET) return;
if(instruction.operation == Instruction::Operation::RETI) return;
if(instruction.operation == Instruction::Operation::RETN) return;
if(instruction.operation == Instruction::Operation::JP) return;
if(instruction.operation == Instruction::Operation::JR) return;
}
}
};
} // end of anonymous namespace
Disassembly Analyser::Static::Z80::Disassemble(
const std::vector<uint8_t> &memory,
const std::function<std::size_t(uint16_t)> &address_mapper,
std::vector<uint16_t> entry_points) {
return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, Z80Disassembler>(memory, address_mapper, entry_points);
}

View File

@@ -0,0 +1,90 @@
//
// Z80.hpp
// Clock Signal
//
// Created by Thomas Harte on 30/12/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef StaticAnalyser_Disassembler_Z80_hpp
#define StaticAnalyser_Disassembler_Z80_hpp
#include <cstdint>
#include <functional>
#include <map>
#include <set>
#include <vector>
namespace Analyser {
namespace Static {
namespace Z80 {
struct Instruction {
/*! The address this instruction starts at. This is a mapped address. */
uint16_t address = 0;
/*! The operation this instruction performs. */
enum class Operation {
NOP,
EXAFAFd, EXX, EX,
LD, HALT,
ADD, ADC, SUB, SBC, AND, XOR, OR, CP,
INC, DEC,
RLCA, RRCA, RLA, RRA, DAA, CPL, SCF, CCF,
RLD, RRD,
DJNZ, JR, JP, CALL, RST, RET, RETI, RETN,
PUSH, POP,
IN, OUT,
EI, DI,
RLC, RRC, RL, RR, SLA, SRA, SLL, SRL,
BIT, RES, SET,
LDI, CPI, INI, OUTI,
LDD, CPD, IND, OUTD,
LDIR, CPIR, INIR, OTIR,
LDDR, CPDR, INDR, OTDR,
NEG,
IM,
Invalid
} operation = Operation::NOP;
/*! The condition required for this instruction to take effect. */
enum class Condition {
None, NZ, Z, NC, C, PO, PE, P, M
} condition = Condition::None;
enum class Location {
B, C, D, E, H, L, HL_Indirect, A, I, R,
BC, DE, HL, SP, AF, Operand,
IX_Indirect_Offset, IY_Indirect_Offset, IXh, IXl, IYh, IYl,
Operand_Indirect,
BC_Indirect, DE_Indirect, SP_Indirect,
None
};
/*! The locations of source data for this instruction. */
Location source = Location::None;
/*! The locations of destination data from this instruction. */
Location destination = Location::None;
/*! The operand, if any; if this is used then it'll be referenced by either the source or destination location. */
int operand = 0;
/*! The offset to apply, if any; applies to IX_Indirect_Offset and IY_Indirect_Offset locations. */
int offset = 0;
};
struct Disassembly {
std::map<uint16_t, Instruction> instructions_by_address;
std::set<uint16_t> outward_calls;
std::set<uint16_t> internal_calls;
std::set<uint16_t> external_stores, external_loads, external_modifies;
std::set<uint16_t> internal_stores, internal_loads, internal_modifies;
};
Disassembly Disassemble(
const std::vector<uint8_t> &memory,
const std::function<std::size_t(uint16_t)> &address_mapper,
std::vector<uint16_t> entry_points);
}
}
}
#endif /* StaticAnalyser_Disassembler_Z80_hpp */

View File

@@ -0,0 +1,40 @@
//
// Cartridge.hpp
// Clock Signal
//
// Created by Thomas Harte on 25/01/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef Cartridge_hpp
#define Cartridge_hpp
#include "../../../Storage/Cartridge/Cartridge.hpp"
namespace Analyser {
namespace Static {
namespace MSX {
/*!
Extends the base cartridge class by adding a (guess at) the banking scheme.
*/
struct Cartridge: public ::Storage::Cartridge::Cartridge {
enum Type {
None,
Konami,
KonamiWithSCC,
ASCII8kb,
ASCII16kb,
FMPac
};
const Type type;
Cartridge(const std::vector<Segment> &segments, Type type) :
Storage::Cartridge::Cartridge(segments), type(type) {}
};
}
}
}
#endif /* Cartridge_hpp */

View File

@@ -0,0 +1,295 @@
//
// StaticAnalyser.cpp
// Clock Signal
//
// Created by Thomas Harte on 25/11/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "StaticAnalyser.hpp"
#include "Cartridge.hpp"
#include "Tape.hpp"
#include "Target.hpp"
#include "../Disassembler/Z80.hpp"
#include "../Disassembler/AddressMapper.hpp"
#include <algorithm>
static std::unique_ptr<Analyser::Static::Target> CartridgeTarget(
const Storage::Cartridge::Cartridge::Segment &segment,
uint16_t start_address,
Analyser::Static::MSX::Cartridge::Type type,
float confidence) {
// Size down to a multiple of 8kb in size and apply the start address.
std::vector<Storage::Cartridge::Cartridge::Segment> output_segments;
if(segment.data.size() & 0x1fff) {
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()) & ~0x1fff;
truncated_data.insert(truncated_data.begin(), segment.data.begin(), segment.data.begin() + truncated_size);
output_segments.emplace_back(start_address, truncated_data);
} else {
output_segments.emplace_back(start_address, segment.data);
}
std::unique_ptr<Analyser::Static::MSX::Target> target(new Analyser::Static::MSX::Target);
target->machine = Analyser::Machine::MSX;
target->confidence = confidence;
if(type == Analyser::Static::MSX::Cartridge::Type::None) {
target->media.cartridges.emplace_back(new Storage::Cartridge::Cartridge(output_segments));
} else {
target->media.cartridges.emplace_back(new Analyser::Static::MSX::Cartridge(output_segments, type));
}
return target;
}
/*
Expected standard cartridge format:
DEFB "AB" ; expansion ROM header
DEFW initcode ; start of the init code, 0 if no initcode
DEFW callstat; pointer to CALL statement handler, 0 if no such handler
DEFW device; pointer to expansion device handler, 0 if no such handler
DEFW basic ; pointer to the start of a tokenized basicprogram, 0 if no basicprogram
DEFS 6,0 ; room reserved for future extensions
MSX cartridges often include banking hardware; those games were marketed as MegaROMs. The file
format that the MSX community has decided upon doesn't retain the type of hardware included, so
this analyser has to guess.
(additional audio hardware is also sometimes included, but it's implied by the banking hardware)
*/
static std::vector<std::unique_ptr<Analyser::Static::Target>> CartridgeTargetsFrom(
const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
// No cartridges implies no targets.
if(cartridges.empty()) {
return {};
}
std::vector<std::unique_ptr<Analyser::Static::Target>> targets;
for(const auto &cartridge : cartridges) {
const auto &segments = cartridge->get_segments();
// Only one mapped item is allowed.
if(segments.size() != 1) continue;
// Which must be no more than 63 bytes larger than a multiple of 8 kb in size.
Storage::Cartridge::Cartridge::Segment segment = segments.front();
const size_t data_size = segment.data.size();
if(data_size < 0x2000 || (data_size & 0x1fff) > 64) continue;
// Check for a ROM header at address 0; if it's not found then try 0x4000
// and adjust the start address;
uint16_t start_address = 0;
bool found_start = false;
if(segment.data[0] == 0x41 && segment.data[1] == 0x42) {
start_address = 0x4000;
found_start = true;
} else if(segment.data.size() >= 0x8000 && segment.data[0x4000] == 0x41 && segment.data[0x4001] == 0x42) {
start_address = 0;
found_start = true;
}
// Reject cartridge if the ROM header wasn't found.
if(!found_start) continue;
uint16_t init_address = static_cast<uint16_t>(segment.data[2] | (segment.data[3] << 8));
// TODO: check for a rational init address?
// If this ROM is less than 48kb in size then it's an ordinary ROM. Just emplace it and move on.
if(data_size <= 0xc000) {
targets.emplace_back(CartridgeTarget(segment, start_address, Analyser::Static::MSX::Cartridge::Type::None, 1.0));
continue;
}
// If this ROM is greater than 48kb in size then some sort of MegaROM scheme must
// be at play; disassemble to try to figure it out.
std::vector<uint8_t> first_8k;
first_8k.insert(first_8k.begin(), segment.data.begin(), segment.data.begin() + 8192);
Analyser::Static::Z80::Disassembly disassembly =
Analyser::Static::Z80::Disassemble(
first_8k,
Analyser::Static::Disassembler::OffsetMapper(start_address),
{ init_address }
);
// // Look for a indirect store followed by an unconditional JP or CALL into another
// // segment, that's a fairly explicit sign where found.
using Instruction = Analyser::Static::Z80::Instruction;
std::map<uint16_t, Instruction> &instructions = disassembly.instructions_by_address;
bool is_ascii = false;
// auto iterator = instructions.begin();
// while(iterator != instructions.end()) {
// auto next_iterator = iterator;
// next_iterator++;
// if(next_iterator == instructions.end()) break;
//
// if( iterator->second.operation == Instruction::Operation::LD &&
// iterator->second.destination == Instruction::Location::Operand_Indirect &&
// (
// iterator->second.operand == 0x5000 ||
// iterator->second.operand == 0x6000 ||
// iterator->second.operand == 0x6800 ||
// iterator->second.operand == 0x7000 ||
// iterator->second.operand == 0x77ff ||
// iterator->second.operand == 0x7800 ||
// iterator->second.operand == 0x8000 ||
// iterator->second.operand == 0x9000 ||
// iterator->second.operand == 0xa000
// ) &&
// (
// next_iterator->second.operation == Instruction::Operation::CALL ||
// next_iterator->second.operation == Instruction::Operation::JP
// ) &&
// ((next_iterator->second.operand >> 13) != (0x4000 >> 13))
// ) {
// const uint16_t address = static_cast<uint16_t>(next_iterator->second.operand);
// switch(iterator->second.operand) {
// case 0x6000:
// if(address >= 0x6000 && address < 0x8000) {
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
// }
// break;
// case 0x6800:
// if(address >= 0x6000 && address < 0x6800) {
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::ASCII8kb;
// }
// break;
// case 0x7000:
// if(address >= 0x6000 && address < 0x8000) {
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
// }
// if(address >= 0x7000 && address < 0x7800) {
// is_ascii = true;
// }
// break;
// case 0x77ff:
// if(address >= 0x7000 && address < 0x7800) {
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::ASCII16kb;
// }
// break;
// case 0x7800:
// if(address >= 0xa000 && address < 0xc000) {
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::ASCII8kb;
// }
// break;
// case 0x8000:
// if(address >= 0x8000 && address < 0xa000) {
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
// }
// break;
// case 0x9000:
// if(address >= 0x8000 && address < 0xa000) {
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
// }
// break;
// case 0xa000:
// if(address >= 0xa000 && address < 0xc000) {
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::Konami;
// }
// break;
// case 0xb000:
// if(address >= 0xa000 && address < 0xc000) {
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
// }
// break;
// }
// }
//
// iterator = next_iterator;
// Look for LD (nnnn), A instructions, and collate those addresses.
std::map<uint16_t, int> address_counts;
for(const auto &instruction_pair : instructions) {
if( instruction_pair.second.operation == Instruction::Operation::LD &&
instruction_pair.second.destination == Instruction::Location::Operand_Indirect &&
instruction_pair.second.source == Instruction::Location::A) {
address_counts[static_cast<uint16_t>(instruction_pair.second.operand)]++;
}
}
// Weight confidences by number of observed hits.
float total_hits =
static_cast<float>(
address_counts[0x6000] + address_counts[0x6800] +
address_counts[0x7000] + address_counts[0x7800] +
address_counts[0x77ff] + address_counts[0x8000] +
address_counts[0xa000] + address_counts[0x5000] +
address_counts[0x9000] + address_counts[0xb000]
);
targets.push_back(CartridgeTarget(
segment,
start_address,
Analyser::Static::MSX::Cartridge::ASCII8kb,
static_cast<float>( address_counts[0x6000] +
address_counts[0x6800] +
address_counts[0x7000] +
address_counts[0x7800]) / total_hits));
targets.push_back(CartridgeTarget(
segment,
start_address,
Analyser::Static::MSX::Cartridge::ASCII16kb,
static_cast<float>( address_counts[0x6000] +
address_counts[0x7000] +
address_counts[0x77ff]) / total_hits));
if(!is_ascii) {
targets.push_back(CartridgeTarget(
segment,
start_address,
Analyser::Static::MSX::Cartridge::Konami,
static_cast<float>( address_counts[0x6000] +
address_counts[0x8000] +
address_counts[0xa000]) / total_hits));
}
if(!is_ascii) {
targets.push_back(CartridgeTarget(
segment,
start_address,
Analyser::Static::MSX::Cartridge::KonamiWithSCC,
static_cast<float>( address_counts[0x5000] +
address_counts[0x7000] +
address_counts[0x9000] +
address_counts[0xb000]) / total_hits));
}
}
return targets;
}
void Analyser::Static::MSX::AddTargets(const Media &media, std::vector<std::unique_ptr<::Analyser::Static::Target>> &destination) {
// Append targets for any cartridges that look correct.
auto cartridge_targets = CartridgeTargetsFrom(media.cartridges);
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);
// Check tapes for loadable files.
for(const auto &tape : media.tapes) {
std::vector<File> files_on_tape = GetFiles(tape);
if(!files_on_tape.empty()) {
switch(files_on_tape.front().type) {
case File::Type::ASCII: target->loading_command = "RUN\"CAS:\r"; break;
case File::Type::TokenisedBASIC: target->loading_command = "CLOAD\rRUN\r"; break;
case File::Type::Binary: target->loading_command = "BLOAD\"CAS:\",R\r"; break;
default: break;
}
target->media.tapes.push_back(tape);
}
}
// Blindly accept disks for now.
target->media.disks = media.disks;
target->has_disk_drive = !media.disks.empty();
if(!target->media.empty()) {
target->machine = Machine::MSX;
target->confidence = 0.5;
destination.push_back(std::move(target));
}
}

View File

@@ -0,0 +1,24 @@
//
// StaticAnalyser.hpp
// Clock Signal
//
// Created by Thomas Harte on 25/11/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef StaticAnalyser_MSX_StaticAnalyser_hpp
#define StaticAnalyser_MSX_StaticAnalyser_hpp
#include "../StaticAnalyser.hpp"
namespace Analyser {
namespace Static {
namespace MSX {
void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination);
}
}
}
#endif /* StaticAnalyser_MSX_StaticAnalyser_hpp */

View File

@@ -0,0 +1,165 @@
//
// Tape.cpp
// Clock Signal
//
// Created by Thomas Harte on 25/12/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "Tape.hpp"
#include "../../../Storage/Tape/Parsers/MSX.hpp"
using namespace Analyser::Static::MSX;
File::File(File &&rhs) :
name(std::move(rhs.name)),
type(rhs.type),
data(std::move(rhs.data)),
starting_address(rhs.starting_address),
entry_address(rhs.entry_address) {}
File::File() :
type(Type::Binary),
starting_address(0),
entry_address(0) {} // For the sake of initialising in a defined state.
std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
std::vector<File> files;
Storage::Tape::BinaryTapePlayer tape_player(1000000);
tape_player.set_motor_control(true);
tape_player.set_tape(tape);
using Parser = Storage::Tape::MSX::Parser;
// Get all recognisable files from the tape.
while(!tape->is_at_end()) {
// Try to locate and measure a header.
std::unique_ptr<Parser::FileSpeed> file_speed = Parser::find_header(tape_player);
if(!file_speed) continue;
// Check whether what follows is a recognisable file type.
uint8_t header[10] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
for(std::size_t c = 0; c < sizeof(header); ++c) {
int next_byte = Parser::get_byte(*file_speed, tape_player);
if(next_byte == -1) break;
header[c] = static_cast<uint8_t>(next_byte);
}
bool bytes_are_same = true;
for(std::size_t c = 1; c < sizeof(header); ++c)
bytes_are_same &= (header[c] == header[0]);
if(!bytes_are_same) continue;
if(header[0] != 0xd0 && header[0] != 0xd3 && header[0] != 0xea) continue;
File file;
// Determine file type from information already collected.
switch(header[0]) {
case 0xd0: file.type = File::Type::Binary; break;
case 0xd3: file.type = File::Type::TokenisedBASIC; break;
case 0xea: file.type = File::Type::ASCII; break;
default: break; // Unreachable.
}
// Read file name.
char name[7];
for(std::size_t c = 1; c < 6; ++c)
name[c] = static_cast<char>(Parser::get_byte(*file_speed, tape_player));
name[6] = '\0';
file.name = name;
// ASCII: Read 256-byte segments until one ends with an end-of-file character.
if(file.type == File::Type::ASCII) {
while(true) {
file_speed = Parser::find_header(tape_player);
if(!file_speed) break;
int c = 256;
bool contains_end_of_file = false;
while(c--) {
int byte = Parser::get_byte(*file_speed, tape_player);
if(byte == -1) break;
contains_end_of_file |= (byte == 0x1a);
file.data.push_back(static_cast<uint8_t>(byte));
}
if(c != -1) break;
if(contains_end_of_file) {
files.push_back(std::move(file));
break;
}
}
continue;
}
// Read a single additional segment, using the information at the begging to determine length.
file_speed = Parser::find_header(tape_player);
if(!file_speed) continue;
// Binary: read start address, end address, entry address, then that many bytes.
if(file.type == File::Type::Binary) {
uint8_t locations[6];
uint16_t end_address;
std::size_t c;
for(c = 0; c < sizeof(locations); ++c) {
int byte = Parser::get_byte(*file_speed, tape_player);
if(byte == -1) break;
locations[c] = static_cast<uint8_t>(byte);
}
if(c != sizeof(locations)) continue;
file.starting_address = static_cast<uint16_t>(locations[0] | (locations[1] << 8));
end_address = static_cast<uint16_t>(locations[2] | (locations[3] << 8));
file.entry_address = static_cast<uint16_t>(locations[4] | (locations[5] << 8));
if(end_address < file.starting_address) continue;
std::size_t length = end_address - file.starting_address;
while(length--) {
int byte = Parser::get_byte(*file_speed, tape_player);
if(byte == -1) continue;
file.data.push_back(static_cast<uint8_t>(byte));
}
files.push_back(std::move(file));
continue;
}
// Tokenised BASIC, then: keep following 'next line' links from a hypothetical start of
// 0x8001, until finding the final line.
uint16_t current_address = 0x8001;
while(current_address) {
int next_address_buffer[2];
next_address_buffer[0] = Parser::get_byte(*file_speed, tape_player);
next_address_buffer[1] = Parser::get_byte(*file_speed, tape_player);
if(next_address_buffer[0] == -1 || next_address_buffer[1] == -1) break;
file.data.push_back(static_cast<uint8_t>(next_address_buffer[0]));
file.data.push_back(static_cast<uint8_t>(next_address_buffer[1]));
uint16_t next_address = static_cast<uint16_t>(next_address_buffer[0] | (next_address_buffer[1] << 8));
if(!next_address) {
files.push_back(std::move(file));
break;
}
if(next_address < current_address+2) break;
// This line makes sense, so push it all in.
std::size_t length = next_address - current_address - 2;
current_address = next_address;
bool found_error = false;
while(length--) {
int byte = Parser::get_byte(*file_speed, tape_player);
if(byte == -1) {
found_error = true;
break;
}
file.data.push_back(static_cast<uint8_t>(byte));
}
if(found_error) break;
}
}
return files;
}

View File

@@ -0,0 +1,44 @@
//
// Tape.hpp
// Clock Signal
//
// Created by Thomas Harte on 25/12/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef StaticAnalyser_MSX_Tape_hpp
#define StaticAnalyser_MSX_Tape_hpp
#include "../../../Storage/Tape/Tape.hpp"
#include <string>
#include <vector>
namespace Analyser {
namespace Static {
namespace MSX {
struct File {
std::string name;
enum Type {
Binary,
TokenisedBASIC,
ASCII
} type;
std::vector<uint8_t> data;
uint16_t starting_address; // Provided only for Type::Binary files.
uint16_t entry_address; // Provided only for Type::Binary files.
File(File &&rhs);
File();
};
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
}
}
}
#endif /* StaticAnalyser_MSX_Tape_hpp */

View File

@@ -0,0 +1,26 @@
//
// Target.hpp
// Clock Signal
//
// Created by Thomas Harte on 02/04/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_MSX_Target_h
#define Analyser_Static_MSX_Target_h
#include "../StaticAnalyser.hpp"
namespace Analyser {
namespace Static {
namespace MSX {
struct Target: public ::Analyser::Static::Target {
bool has_disk_drive = false;
};
}
}
}
#endif /* Analyser_Static_MSX_Target_h */

View File

@@ -9,12 +9,18 @@
#include "StaticAnalyser.hpp"
#include "Tape.hpp"
#include "../Disassembler/Disassembler6502.hpp"
#include "Target.hpp"
using namespace StaticAnalyser::Oric;
#include "../Disassembler/6502.hpp"
#include "../Disassembler/AddressMapper.hpp"
static int Score(const StaticAnalyser::MOS6502::Disassembly &disassembly, const std::set<uint16_t> &rom_functions, const std::set<uint16_t> &variable_locations)
{
#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
#include <cstring>
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) {
int score = 0;
for(auto address : disassembly.outward_calls) score += (rom_functions.find(address) != rom_functions.end()) ? 1 : -1;
@@ -24,8 +30,7 @@ static int Score(const StaticAnalyser::MOS6502::Disassembly &disassembly, const
return score;
}
static int Basic10Score(const StaticAnalyser::MOS6502::Disassembly &disassembly)
{
static int Basic10Score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
std::set<uint16_t> rom_functions = {
0x0228, 0x022b,
0xc3ca, 0xc3f8, 0xc448, 0xc47c, 0xc4b5, 0xc4e3, 0xc4e0, 0xc524, 0xc56f, 0xc5a2, 0xc5f8, 0xc60a, 0xc6a5, 0xc6de, 0xc719, 0xc738,
@@ -49,8 +54,7 @@ static int Basic10Score(const StaticAnalyser::MOS6502::Disassembly &disassembly)
return Score(disassembly, rom_functions, variable_locations);
}
static int Basic11Score(const StaticAnalyser::MOS6502::Disassembly &disassembly)
{
static int Basic11Score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
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,
@@ -75,31 +79,44 @@ static int Basic11Score(const StaticAnalyser::MOS6502::Disassembly &disassembly)
return Score(disassembly, rom_functions, variable_locations);
}
void StaticAnalyser::Oric::AddTargets(
const std::list<std::shared_ptr<Storage::Disk::Disk>> &disks,
const std::list<std::shared_ptr<Storage::Tape::Tape>> &tapes,
const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges,
std::list<StaticAnalyser::Target> &destination)
{
Target target;
target.machine = Target::Oric;
target.probability = 1.0;
static bool IsMicrodisc(Storage::Encodings::MFM::Parser &parser) {
/*
The Microdisc boot sector is sector 2 of track 0 and contains a 23-byte signature.
*/
Storage::Encodings::MFM::Sector *sector = parser.get_sector(0, 0, 2);
if(!sector) return false;
if(sector->samples.empty()) return false;
const std::vector<uint8_t> &first_sample = sector->samples[0];
if(first_sample.size() != 256) return false;
const uint8_t signature[] = {
0x00, 0x00, 0xFF, 0x00, 0xD0, 0x9F, 0xD0,
0x9F, 0x02, 0xB9, 0x01, 0x00, 0xFF, 0x00,
0x00, 0xB9, 0xE4, 0xB9, 0x00, 0x00, 0xE6,
0x12, 0x00
};
return !std::memcmp(signature, first_sample.data(), sizeof(signature));
}
void Analyser::Static::Oric::AddTargets(const Media &media, std::vector<std::unique_ptr<Analyser::Static::Target>> &destination) {
std::unique_ptr<Target> target(new Target);
target->machine = Machine::Oric;
target->confidence = 0.5;
int basic10_votes = 0;
int basic11_votes = 0;
for(auto tape : tapes)
{
std::list<File> tape_files = GetFiles(tape);
if(tape_files.size())
{
for(auto file : tape_files)
{
if(file.data_type == File::MachineCode)
{
for(auto &tape : media.tapes) {
std::vector<File> tape_files = GetFiles(tape);
tape->reset();
if(tape_files.size()) {
for(auto file : tape_files) {
if(file.data_type == File::MachineCode) {
std::vector<uint16_t> entry_points = {file.starting_address};
StaticAnalyser::MOS6502::Disassembly disassembly =
StaticAnalyser::MOS6502::Disassemble(file.data, file.starting_address, entry_points);
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);
@@ -107,26 +124,28 @@ void StaticAnalyser::Oric::AddTargets(
}
}
target.tapes.push_back(tape);
target.loadingCommand = "CLOAD\"\"\n";
target->media.tapes.push_back(tape);
target->loading_command = "CLOAD\"\"\n";
}
}
// trust that any disk supplied can be handled by the Microdisc. TODO: check.
if(!disks.empty())
{
target.oric.has_microdisc = true;
target.disks = disks;
}
else
{
target.oric.has_microdisc = false;
if(!media.disks.empty()) {
// Only the Microdisc is emulated right now, so accept only disks that it can boot from.
for(const auto &disk: media.disks) {
Storage::Encodings::MFM::Parser parser(true, disk);
if(IsMicrodisc(parser)) {
target->has_microdrive = true;
target->media.disks.push_back(disk);
}
}
} else {
target->has_microdrive = false;
}
// TODO: really this should add two targets if not all votes agree
target.oric.use_atmos_rom = basic11_votes >= basic10_votes;
if(target.oric.has_microdisc) target.oric.use_atmos_rom = true;
target->use_atmos_rom = basic11_votes >= basic10_votes;
if(target->has_microdrive) target->use_atmos_rom = true;
if(target.tapes.size() || target.disks.size() || target.cartridges.size())
destination.push_back(target);
if(target->media.tapes.size() || target->media.disks.size() || target->media.cartridges.size())
destination.push_back(std::move(target));
}

View File

@@ -11,18 +11,14 @@
#include "../StaticAnalyser.hpp"
namespace StaticAnalyser {
namespace Analyser {
namespace Static {
namespace Oric {
void AddTargets(
const std::list<std::shared_ptr<Storage::Disk::Disk>> &disks,
const std::list<std::shared_ptr<Storage::Tape::Tape>> &tapes,
const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges,
std::list<Target> &destination
);
void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination);
}
}
}
#endif /* StaticAnalyser_hpp */

View File

@@ -7,18 +7,15 @@
//
#include "Tape.hpp"
#include "../../Storage/Tape/Parsers/Oric.hpp"
#include "../../../Storage/Tape/Parsers/Oric.hpp"
using namespace StaticAnalyser::Oric;
using namespace Analyser::Static::Oric;
std::list<File> StaticAnalyser::Oric::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape)
{
std::list<File> files;
std::vector<File> Analyser::Static::Oric::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
std::vector<File> files;
Storage::Tape::Oric::Parser parser;
tape->reset();
while(!tape->is_at_end())
{
while(!tape->is_at_end()) {
// sync to next lead-in, check that it's one of three 0x16s
bool is_fast = parser.sync_and_get_encoding_speed(tape);
int next_bytes[2];
@@ -29,8 +26,7 @@ std::list<File> StaticAnalyser::Oric::GetFiles(const std::shared_ptr<Storage::Ta
// get the first byte that isn't a 0x16, check it was a 0x24
int byte = 0x16;
while(!tape->is_at_end() && byte == 0x16)
{
while(!tape->is_at_end() && byte == 0x16) {
byte = parser.get_next_byte(tape, is_fast);
}
if(byte != 0x24) continue;
@@ -41,24 +37,22 @@ std::list<File> StaticAnalyser::Oric::GetFiles(const std::shared_ptr<Storage::Ta
// get data and launch types
File new_file;
switch(parser.get_next_byte(tape, is_fast))
{
switch(parser.get_next_byte(tape, is_fast)) {
case 0x00: new_file.data_type = File::ProgramType::BASIC; break;
case 0x80: new_file.data_type = File::ProgramType::MachineCode; break;
default: new_file.data_type = File::ProgramType::None; break;
}
switch(parser.get_next_byte(tape, is_fast))
{
switch(parser.get_next_byte(tape, is_fast)) {
case 0x80: new_file.launch_type = File::ProgramType::BASIC; break;
case 0xc7: new_file.launch_type = File::ProgramType::MachineCode; break;
default: new_file.launch_type = File::ProgramType::None; break;
}
// read end and start addresses
new_file.ending_address = (uint16_t)(parser.get_next_byte(tape, is_fast) << 8);
new_file.ending_address |= (uint16_t)parser.get_next_byte(tape, is_fast);
new_file.starting_address = (uint16_t)(parser.get_next_byte(tape, is_fast) << 8);
new_file.starting_address |= (uint16_t)parser.get_next_byte(tape, is_fast);
new_file.ending_address = static_cast<uint16_t>(parser.get_next_byte(tape, is_fast) << 8);
new_file.ending_address |= static_cast<uint16_t>(parser.get_next_byte(tape, is_fast));
new_file.starting_address = static_cast<uint16_t>(parser.get_next_byte(tape, is_fast) << 8);
new_file.starting_address |= static_cast<uint16_t>(parser.get_next_byte(tape, is_fast));
// skip an empty byte
parser.get_next_byte(tape, is_fast);
@@ -66,8 +60,7 @@ std::list<File> StaticAnalyser::Oric::GetFiles(const std::shared_ptr<Storage::Ta
// read file name, up to 16 characters and null terminated
char file_name[17];
int name_pos = 0;
while(name_pos < 16)
{
while(name_pos < 16) {
file_name[name_pos] = (char)parser.get_next_byte(tape, is_fast);
if(!file_name[name_pos]) break;
name_pos++;
@@ -76,20 +69,17 @@ std::list<File> StaticAnalyser::Oric::GetFiles(const std::shared_ptr<Storage::Ta
new_file.name = file_name;
// read body
size_t body_length = new_file.ending_address - new_file.starting_address + 1;
std::size_t body_length = new_file.ending_address - new_file.starting_address + 1;
new_file.data.reserve(body_length);
for(size_t c = 0; c < body_length; c++)
{
new_file.data.push_back((uint8_t)parser.get_next_byte(tape, is_fast));
for(std::size_t c = 0; c < body_length; c++) {
new_file.data.push_back(static_cast<uint8_t>(parser.get_next_byte(tape, is_fast)));
}
// only one validation check: was there enough tape?
if(!tape->is_at_end())
{
if(!tape->is_at_end()) {
files.push_back(new_file);
}
}
tape->reset();
return files;
}

View File

@@ -9,12 +9,13 @@
#ifndef StaticAnalyser_Oric_Tape_hpp
#define StaticAnalyser_Oric_Tape_hpp
#include "../../Storage/Tape/Tape.hpp"
#include <list>
#include "../../../Storage/Tape/Tape.hpp"
#include <string>
#include <vector>
namespace StaticAnalyser {
namespace Analyser {
namespace Static {
namespace Oric {
struct File {
@@ -30,8 +31,9 @@ struct File {
std::vector<uint8_t> data;
};
std::list<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
}
}
}

View File

@@ -0,0 +1,25 @@
//
// Target.hpp
// Clock Signal
//
// Created by Thomas Harte on 09/03/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_Oric_Target_h
#define Analyser_Static_Oric_Target_h
namespace Analyser {
namespace Static {
namespace Oric {
struct Target: public ::Analyser::Static::Target {
bool use_atmos_rom = false;
bool has_microdrive = false;
};
}
}
}
#endif /* Analyser_Static_Oric_Target_h */

View File

@@ -0,0 +1,175 @@
//
// StaticAnalyser.cpp
// Clock Signal
//
// Created by Thomas Harte on 23/08/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "StaticAnalyser.hpp"
#include <algorithm>
#include <cstdlib>
#include <cstring>
// Analysers
#include "Acorn/StaticAnalyser.hpp"
#include "AmstradCPC/StaticAnalyser.hpp"
#include "Atari/StaticAnalyser.hpp"
#include "Coleco/StaticAnalyser.hpp"
#include "Commodore/StaticAnalyser.hpp"
#include "MSX/StaticAnalyser.hpp"
#include "Oric/StaticAnalyser.hpp"
#include "ZX8081/StaticAnalyser.hpp"
// Cartridges
#include "../../Storage/Cartridge/Formats/BinaryDump.hpp"
#include "../../Storage/Cartridge/Formats/PRG.hpp"
// Disks
#include "../../Storage/Disk/DiskImage/Formats/AcornADF.hpp"
#include "../../Storage/Disk/DiskImage/Formats/CPCDSK.hpp"
#include "../../Storage/Disk/DiskImage/Formats/D64.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/MSXDSK.hpp"
#include "../../Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp"
#include "../../Storage/Disk/DiskImage/Formats/SSD.hpp"
// Tapes
#include "../../Storage/Tape/Formats/CAS.hpp"
#include "../../Storage/Tape/Formats/CommodoreTAP.hpp"
#include "../../Storage/Tape/Formats/CSW.hpp"
#include "../../Storage/Tape/Formats/OricTAP.hpp"
#include "../../Storage/Tape/Formats/TapePRG.hpp"
#include "../../Storage/Tape/Formats/TapeUEF.hpp"
#include "../../Storage/Tape/Formats/TZX.hpp"
#include "../../Storage/Tape/Formats/ZX80O81P.hpp"
// Target Platform Types
#include "../../Storage/TargetPlatforms.hpp"
using namespace Analyser::Static;
static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::IntType &potential_platforms) {
Media result;
// Get the extension, if any; it will be assumed that extensions are reliable, so an extension is a broad-phase
// test as to file format.
std::string::size_type final_dot = file_name.find_last_of(".");
if(final_dot == std::string::npos) return result;
std::string extension = file_name.substr(final_dot + 1);
std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
#define Insert(list, class, platforms) \
list.emplace_back(new Storage::class(file_name));\
potential_platforms |= platforms;\
TargetPlatform::TypeDistinguisher *distinguisher = dynamic_cast<TargetPlatform::TypeDistinguisher *>(list.back().get());\
if(distinguisher) potential_platforms &= distinguisher->target_platform_type();
#define TryInsert(list, class, platforms) \
try {\
Insert(list, class, platforms) \
} catch(...) {}
#define Format(ext, list, class, platforms) \
if(extension == ext) { \
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("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::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("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // O
Format("p", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P
Format("p81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P81
// PRG
if(extension == "prg") {
// try instantiating as a ROM; failing that accept as a tape
try {
Insert(result.cartridges, Cartridge::PRG, TargetPlatform::Commodore)
} catch(...) {
try {
Insert(result.tapes, Tape::PRG, TargetPlatform::Commodore)
} catch(...) {}
}
}
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)
#undef Format
#undef Insert
#undef TryInsert
return result;
}
Media Analyser::Static::GetMedia(const std::string &file_name) {
TargetPlatform::IntType throwaway;
return GetMediaAndPlatforms(file_name, throwaway);
}
std::vector<std::unique_ptr<Target>> Analyser::Static::GetTargets(const std::string &file_name) {
std::vector<std::unique_ptr<Target>> targets;
// Collect all disks, tapes and ROMs as can be extrapolated from this file, forming the
// union of all platforms this file might be a target for.
TargetPlatform::IntType potential_platforms = 0;
Media media = GetMediaAndPlatforms(file_name, potential_platforms);
// Hand off to platform-specific determination of whether these things are actually compatible and,
// if so, how to load them.
if(potential_platforms & TargetPlatform::Acorn) Acorn::AddTargets(media, targets);
if(potential_platforms & TargetPlatform::AmstradCPC) AmstradCPC::AddTargets(media, targets);
if(potential_platforms & TargetPlatform::Atari2600) Atari::AddTargets(media, targets);
if(potential_platforms & TargetPlatform::ColecoVision) Coleco::AddTargets(media, targets);
if(potential_platforms & TargetPlatform::Commodore) Commodore::AddTargets(media, targets, file_name);
if(potential_platforms & TargetPlatform::MSX) MSX::AddTargets(media, targets);
if(potential_platforms & TargetPlatform::Oric) Oric::AddTargets(media, targets);
if(potential_platforms & TargetPlatform::ZX8081) ZX8081::AddTargets(media, targets, potential_platforms);
// Reset any tapes to their initial position
for(auto &target : targets) {
for(auto &tape : target->media.tapes) {
tape->reset();
}
}
// Sort by initial confidence. Use a stable sort in case any of the machine-specific analysers
// picked their insertion order carefully.
std::stable_sort(targets.begin(), targets.end(),
[] (const std::unique_ptr<Target> &a, const std::unique_ptr<Target> &b) {
return a->confidence > b->confidence;
});
return targets;
}

View File

@@ -0,0 +1,66 @@
//
// StaticAnalyser.hpp
// Clock Signal
//
// Created by Thomas Harte on 23/08/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef StaticAnalyser_hpp
#define StaticAnalyser_hpp
#include "../Machines.hpp"
#include "../../Storage/Tape/Tape.hpp"
#include "../../Storage/Disk/Disk.hpp"
#include "../../Storage/Cartridge/Cartridge.hpp"
#include <string>
#include <vector>
namespace Analyser {
namespace Static {
/*!
A list of disks, tapes and cartridges.
*/
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;
bool empty() const {
return disks.empty() && tapes.empty() && cartridges.empty();
}
};
/*!
A list of disks, tapes and cartridges plus information about the machine to which to attach them and its configuration,
and instructions on how to launch the software attached, plus a measure of confidence in this target's correctness.
*/
struct Target {
virtual ~Target() {}
Machine machine;
Media media;
float confidence;
std::string loading_command;
};
/*!
Attempts, through any available means, to return a list of potential targets for the file with the given name.
@returns The list of potential targets, sorted from most to least probable.
*/
std::vector<std::unique_ptr<Target>> GetTargets(const std::string &file_name);
/*!
Inspects the supplied file and determines the media included.
*/
Media GetMedia(const std::string &file_name);
}
}
#endif /* StaticAnalyser_hpp */

View File

@@ -0,0 +1,68 @@
//
// StaticAnalyser.cpp
// Clock Signal
//
// Created by Thomas Harte on 04/06/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "StaticAnalyser.hpp"
#include <string>
#include <vector>
#include "Target.hpp"
#include "../../../Storage/Tape/Parsers/ZX8081.hpp"
static std::vector<Storage::Data::ZX8081::File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
std::vector<Storage::Data::ZX8081::File> files;
Storage::Tape::ZX8081::Parser parser;
while(!tape->is_at_end()) {
std::shared_ptr<Storage::Data::ZX8081::File> next_file = parser.get_next_file(tape);
if(next_file != nullptr) {
files.push_back(*next_file);
}
}
return files;
}
void Analyser::Static::ZX8081::AddTargets(const Media &media, std::vector<std::unique_ptr<::Analyser::Static::Target>> &destination, TargetPlatform::IntType potential_platforms) {
if(!media.tapes.empty()) {
std::vector<Storage::Data::ZX8081::File> files = GetFiles(media.tapes.front());
media.tapes.front()->reset();
if(!files.empty()) {
Target *target = new Target;
destination.push_back(std::unique_ptr<::Analyser::Static::Target>(target));
target->machine = Machine::ZX8081;
// Guess the machine type from the file only if it isn't already known.
switch(potential_platforms & (TargetPlatform::ZX80 | TargetPlatform::ZX81)) {
default:
target->is_ZX81 = files.front().isZX81;
break;
case TargetPlatform::ZX80: target->is_ZX81 = false; break;
case TargetPlatform::ZX81: target->is_ZX81 = true; break;
}
/*if(files.front().data.size() > 16384) {
target->zx8081.memory_model = ZX8081MemoryModel::SixtyFourKB;
} else*/ if(files.front().data.size() > 1024) {
target->memory_model = Target::MemoryModel::SixteenKB;
} else {
target->memory_model = Target::MemoryModel::Unexpanded;
}
target->media.tapes = media.tapes;
// TODO: how to run software once loaded? Might require a BASIC detokeniser.
if(target->is_ZX81) {
target->loading_command = "J\"\"\n";
} else {
target->loading_command = "W\n";
}
}
}
}

View File

@@ -0,0 +1,25 @@
//
// StaticAnalyser.hpp
// Clock Signal
//
// Created by Thomas Harte on 04/06/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef StaticAnalyser_ZX8081_StaticAnalyser_hpp
#define StaticAnalyser_ZX8081_StaticAnalyser_hpp
#include "../StaticAnalyser.hpp"
#include "../../../Storage/TargetPlatforms.hpp"
namespace Analyser {
namespace Static {
namespace ZX8081 {
void AddTargets(const Media &media, std::vector<std::unique_ptr<Target>> &destination, TargetPlatform::IntType potential_platforms);
}
}
}
#endif /* StaticAnalyser_hpp */

View File

@@ -0,0 +1,34 @@
//
// Target.hpp
// Clock Signal
//
// Created by Thomas Harte on 09/03/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_ZX8081_Target_h
#define Analyser_Static_ZX8081_Target_h
#include "../StaticAnalyser.hpp"
namespace Analyser {
namespace Static {
namespace ZX8081 {
struct Target: public ::Analyser::Static::Target {
enum class MemoryModel {
Unexpanded,
SixteenKB,
SixtyFourKB
};
MemoryModel memory_model = MemoryModel::Unexpanded;
bool is_ZX81 = false;
bool ZX80_uses_ZX81_ROM = false;
};
}
}
}
#endif /* Analyser_Static_ZX8081_Target_h */

30
BUILD.txt Normal file
View File

@@ -0,0 +1,30 @@
Linux, BSD
==========
Prerequisites are SDL 2, ZLib and OpenGL (or Mesa), and SCons for the provided build script. OpenGL 3.2 or better is required at runtime.
Build:
cd OSBindings/SDL
scons
Optionally:
cp clksignal /usr/bin
To launch:
clksignal file
Setting up clksignal as the associated program for supported file types in your favoured filesystem browser is recommended; it has no file navigation abilities of its own.
Some emulated systems require the provision of original machine ROMs. These are not included and may be located in either /usr/local/share/CLK/ or /usr/share/CLK/. You will be prompted for them if they are found to be missing. The structure should mirror that under OSBindings in the source archive; see the readme.txt in each folder to determine the proper files and names ahead of time.
macOS
=====
There are no prerequisites beyond the normal system libraries; the macOS build is a native Cocoa application.
Build: open the Xcode project in OSBindings/Mac and press command+b.
Machine ROMs are intended to be built into the application bundle; populate the dummy folders below ROMImages before building.

View File

@@ -0,0 +1,216 @@
//
// ClockReceiver.hpp
// Clock Signal
//
// Created by Thomas Harte on 22/07/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef ClockReceiver_hpp
#define ClockReceiver_hpp
/*
Informal pattern for all classes that run from a clock cycle:
Each will implement either or both of run_for(Cycles) and run_for(HalfCycles), as
is appropriate.
Callers that are accumulating HalfCycles but want to talk to receivers that implement
only run_for(Cycles) can use HalfCycle.flush_cycles if they have appropriate storage, or
can wrap the receiver in HalfClockReceiver in order automatically to bind half-cycle
storage to it.
Alignment rule:
run_for(Cycles) may be called only after an even number of half cycles. E.g. the following
sequence will have undefined results:
run_for(HalfCycles(1))
run_for(Cycles(1))
An easy way to ensure this as a caller is to pick only one of run_for(Cycles) and
run_for(HalfCycles) to use.
Reasoning:
Users of this template may with to implement run_for(Cycles) and run_for(HalfCycles)
where there is a need to implement at half-cycle precision but a faster execution
path can be offered for full-cycle precision. Those users are permitted to assume
phase in run_for(Cycles) and should do so to be compatible with callers that use
only run_for(Cycles).
Corollary:
Starting from nothing, the first run_for(HalfCycles(1)) will do the **first** half
of a full cycle. The second will do the second half. Etc.
*/
/*!
Provides a class that wraps a plain int, providing most of the basic arithmetic and
Boolean operators, but forcing callers and receivers to be explicit as to usage.
*/
template <class T> class WrappedInt {
public:
inline WrappedInt(int l) : length_(l) {}
inline WrappedInt() : length_(0) {}
inline T &operator =(const T &rhs) {
length_ = rhs.length_;
return *this;
}
inline T &operator +=(const T &rhs) {
length_ += rhs.length_;
return *static_cast<T *>(this);
}
inline T &operator -=(const T &rhs) {
length_ -= rhs.length_;
return *static_cast<T *>(this);
}
inline T &operator ++() {
++ length_;
return *static_cast<T *>(this);
}
inline T &operator ++(int) {
length_ ++;
return *static_cast<T *>(this);
}
inline T &operator --() {
-- length_;
return *static_cast<T *>(this);
}
inline T &operator --(int) {
length_ --;
return *static_cast<T *>(this);
}
inline T &operator %=(const T &rhs) {
length_ %= rhs.length_;
return *static_cast<T *>(this);
}
inline T &operator &=(const T &rhs) {
length_ &= rhs.length_;
return *static_cast<T *>(this);
}
inline T operator +(const T &rhs) const { return T(length_ + rhs.length_); }
inline T operator -(const T &rhs) const { return T(length_ - rhs.length_); }
inline T operator %(const T &rhs) const { return T(length_ % rhs.length_); }
inline T operator &(const T &rhs) const { return T(length_ & rhs.length_); }
inline T operator -() const { return T(- length_); }
inline bool operator <(const T &rhs) const { return length_ < rhs.length_; }
inline bool operator >(const T &rhs) const { return length_ > rhs.length_; }
inline bool operator <=(const T &rhs) const { return length_ <= rhs.length_; }
inline bool operator >=(const T &rhs) const { return length_ >= rhs.length_; }
inline bool operator ==(const T &rhs) const { return length_ == rhs.length_; }
inline bool operator !=(const T &rhs) const { return length_ != rhs.length_; }
inline 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
inline int as_int() 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.
*/
inline T divide(const T &divisor) {
T result(length_ / divisor.length_);
length_ %= divisor.length_;
return result;
}
/*!
Flushes the value in @c this. The current value is returned, and the internal value
is reset to zero.
*/
inline T flush() {
T result(length_);
length_ = 0;
return result;
}
// operator int() is deliberately not provided, to avoid accidental subtitution of
// classes that use this template.
protected:
int length_;
};
/// Describes an integer number of whole cycles — pairs of clock signal transitions.
class Cycles: public WrappedInt<Cycles> {
public:
inline Cycles(int l) : WrappedInt<Cycles>(l) {}
inline Cycles() : WrappedInt<Cycles>() {}
inline Cycles(const Cycles &cycles) : WrappedInt<Cycles>(cycles.length_) {}
};
/// Describes an integer number of half cycles — single clock signal transitions.
class HalfCycles: public WrappedInt<HalfCycles> {
public:
inline HalfCycles(int l) : WrappedInt<HalfCycles>(l) {}
inline HalfCycles() : WrappedInt<HalfCycles>() {}
inline HalfCycles(const Cycles cycles) : WrappedInt<HalfCycles>(cycles.as_int() * 2) {}
inline HalfCycles(const HalfCycles &half_cycles) : WrappedInt<HalfCycles>(half_cycles.length_) {}
/// @returns The number of whole cycles completely covered by this span of half cycles.
inline Cycles cycles() {
return Cycles(length_ >> 1);
}
/// Flushes the whole cycles in @c this, subtracting that many from the total stored here.
inline 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.
inline 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.
*/
inline Cycles divide_cycles(const Cycles &divisor) {
HalfCycles half_divisor = HalfCycles(divisor);
Cycles result(length_ / half_divisor.length_);
length_ %= half_divisor.length_;
return result;
}
};
/*!
If a component implements only run_for(Cycles), an owner can wrap it in HalfClockReceiver
automatically to gain run_for(HalfCycles).
*/
template <class T> class HalfClockReceiver: public T {
public:
using T::T;
using T::run_for;
inline void run_for(const HalfCycles half_cycles) {
half_cycles_ += half_cycles;
T::run_for(half_cycles_.flush_cycles());
}
private:
HalfCycles half_cycles_;
};
#endif /* ClockReceiver_hpp */

View File

@@ -0,0 +1,26 @@
//
// ForceInline.h
// Clock Signal
//
// Created by Thomas Harte on 01/08/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef ForceInline_hpp
#define ForceInline_hpp
#ifdef DEBUG
#define forceinline
#else
#ifdef __GNUC__
#define forceinline __attribute__((always_inline)) inline
#elif _MSC_VER
#define forceinline __forceinline
#endif
#endif
#endif /* ForceInline_h */

60
ClockReceiver/Sleeper.hpp Normal file
View File

@@ -0,0 +1,60 @@
//
// Sleeper.h
// Clock Signal
//
// Created by Thomas Harte on 20/08/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef Sleeper_hpp
#define Sleeper_hpp
/*!
A sleeper is any component that sometimes requires a clock but at other times is 'asleep' — i.e. is not doing
any clock-derived work, so needn't receive a clock. A disk controller is an archetypal example.
A sleeper will signal sleeps and wakes to an observer.
This is intended to allow for performance improvements to machines with components that can sleep. The observer
callout is virtual so the intended use case is that a machine holds a component that might sleep. Its transitions
into and out of sleep are sufficiently infrequent that a virtual call to announce them costs sufficiently little that
the saved ::run_fors add up to a substantial amount.
By convention, sleeper components must be willing to accept ::run_for even after announcing sleep. It's a hint,
not a command.
*/
class Sleeper {
public:
Sleeper() : sleep_observer_(nullptr) {}
class SleepObserver {
public:
/// Called to inform an observer that the component @c component has either gone to sleep or become awake.
virtual void set_component_is_sleeping(void *component, bool is_sleeping) = 0;
};
/// Registers @c observer as the new sleep observer;
void set_sleep_observer(SleepObserver *observer) {
sleep_observer_ = observer;
}
/// @returns @c true if the component is currently sleeping; @c false otherwise.
virtual bool is_sleeping() = 0;
protected:
/// Provided for subclasses; send sleep announcements to the sleep_observer_.
SleepObserver *sleep_observer_;
/*!
Provided for subclasses; call this whenever is_sleeping might have changed, and the observer will be notified,
if one exists.
@c is_sleeping will be called only if there is an observer.
*/
void update_sleep_observer() {
if(!sleep_observer_) return;
sleep_observer_->set_component_is_sleeping(this, is_sleeping());
}
};
#endif /* Sleeper_h */

View File

@@ -0,0 +1,18 @@
//
// TimeTypes.hpp
// Clock Signal
//
// Created by Thomas Harte on 21/03/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef TimeTypes_h
#define TimeTypes_h
namespace Time {
typedef double Seconds;
}
#endif /* TimeTypes_h */

View File

@@ -7,69 +7,35 @@
//
#include "1770.hpp"
#include "../../Storage/Disk/Encodings/MFM.hpp"
#include "../../Storage/Disk/Encodings/MFM/Constants.hpp"
using namespace WD;
WD1770::Status::Status() :
type(Status::One),
write_protect(false),
record_type(false),
spin_up(false),
record_not_found(false),
crc_error(false),
seek_error(false),
lost_data(false),
data_request(false),
interrupt_request(false),
busy(false)
{}
WD1770::WD1770(Personality p) :
Storage::Disk::Controller(8000000, 16, 300),
crc_generator_(0x1021, 0xffff),
interesting_event_mask_(Event::Command),
resume_point_(0),
delay_time_(0),
index_hole_count_target_(-1),
is_awaiting_marker_value_(false),
data_mode_(DataMode::Scanning),
delegate_(nullptr),
personality_(p),
head_is_loaded_(false)
{
Storage::Disk::MFMController(8000000),
personality_(p),
interesting_event_mask_(static_cast<int>(Event1770::Command)) {
set_is_double_density(false);
posit_event(Event::Command);
posit_event(static_cast<int>(Event1770::Command));
}
void WD1770::set_is_double_density(bool is_double_density)
{
is_double_density_ = is_double_density;
Storage::Time bit_length;
bit_length.length = 1;
bit_length.clock_rate = is_double_density ? 500000 : 250000;
set_expected_bit_length(bit_length);
if(!is_double_density) is_awaiting_marker_value_ = false;
}
void WD1770::set_register(int address, uint8_t value)
{
switch(address&3)
{
case 0:
{
if((value&0xf0) == 0xd0)
{
printf("!!!TODO: force interrupt!!!\n");
update_status([] (Status &status) {
status.type = Status::One;
});
}
else
{
void WD1770::set_register(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));
} else {
printf("!!!TODO: force interrupt!!!\n");
update_status([] (Status &status) {
status.type = Status::One;
});
}
} else {
command_ = value;
posit_event(Event::Command);
posit_event(static_cast<int>(Event1770::Command));
}
}
break;
@@ -84,12 +50,9 @@ void WD1770::set_register(int address, uint8_t value)
}
}
uint8_t WD1770::get_register(int address)
{
switch(address&3)
{
default:
{
uint8_t WD1770::get_register(int address) {
switch(address&3) {
default: {
update_status([] (Status &status) {
status.interrupt_request = false;
});
@@ -97,11 +60,10 @@ uint8_t WD1770::get_register(int address)
(status_.write_protect ? Flag::WriteProtect : 0) |
(status_.crc_error ? Flag::CRCError : 0) |
(status_.busy ? Flag::Busy : 0);
switch(status_.type)
{
switch(status_.type) {
case Status::One:
status |=
(get_is_track_zero() ? Flag::TrackZero : 0) |
(get_drive().get_is_track_zero() ? Flag::TrackZero : 0) |
(status_.seek_error ? Flag::SeekError : 0);
// TODO: index hole
break;
@@ -116,15 +78,12 @@ uint8_t WD1770::get_register(int address)
break;
}
if(!has_motor_on_line())
{
status |= get_drive_is_ready() ? 0 : Flag::NotReady;
if(!has_motor_on_line()) {
status |= get_drive().get_is_ready() ? 0 : Flag::NotReady;
if(status_.type == Status::One)
status |= (head_is_loaded_ ? Flag::HeadLoaded : 0);
}
else
{
status |= (get_motor_on() ? Flag::MotorOn : 0);
} else {
status |= (get_drive().get_motor_on() ? Flag::MotorOn : 0);
if(status_.type == Status::One)
status |= (status_.spin_up ? Flag::SpinUp : 0);
}
@@ -140,177 +99,31 @@ uint8_t WD1770::get_register(int address)
}
}
void WD1770::run_for_cycles(unsigned int number_of_cycles)
{
Storage::Disk::Controller::run_for_cycles((int)number_of_cycles);
void WD1770::run_for(const Cycles cycles) {
Storage::Disk::Controller::run_for(cycles);
if(delay_time_)
{
if(delay_time_ <= number_of_cycles)
{
if(delay_time_) {
unsigned int number_of_cycles = static_cast<unsigned int>(cycles.as_int());
if(delay_time_ <= number_of_cycles) {
delay_time_ = 0;
posit_event(Event::Timer);
}
else
{
posit_event(static_cast<int>(Event1770::Timer));
} else {
delay_time_ -= number_of_cycles;
}
}
}
void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole)
{
if(data_mode_ == DataMode::Writing) return;
shift_register_ = (shift_register_ << 1) | value;
bits_since_token_++;
if(data_mode_ == DataMode::Scanning)
{
Token::Type token_type = Token::Byte;
if(!is_double_density_)
{
switch(shift_register_ & 0xffff)
{
case Storage::Encodings::MFM::FMIndexAddressMark:
token_type = Token::Index;
crc_generator_.reset();
crc_generator_.add(latest_token_.byte_value = Storage::Encodings::MFM::IndexAddressByte);
break;
case Storage::Encodings::MFM::FMIDAddressMark:
token_type = Token::ID;
crc_generator_.reset();
crc_generator_.add(latest_token_.byte_value = Storage::Encodings::MFM::IDAddressByte);
break;
case Storage::Encodings::MFM::FMDataAddressMark:
token_type = Token::Data;
crc_generator_.reset();
crc_generator_.add(latest_token_.byte_value = Storage::Encodings::MFM::DataAddressByte);
break;
case Storage::Encodings::MFM::FMDeletedDataAddressMark:
token_type = Token::DeletedData;
crc_generator_.reset();
crc_generator_.add(latest_token_.byte_value = Storage::Encodings::MFM::DeletedDataAddressByte);
break;
default:
break;
}
}
else
{
switch(shift_register_ & 0xffff)
{
case Storage::Encodings::MFM::MFMIndexSync:
bits_since_token_ = 0;
is_awaiting_marker_value_ = true;
token_type = Token::Sync;
latest_token_.byte_value = Storage::Encodings::MFM::MFMIndexSyncByteValue;
break;
case Storage::Encodings::MFM::MFMSync:
bits_since_token_ = 0;
is_awaiting_marker_value_ = true;
crc_generator_.set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue);
token_type = Token::Sync;
latest_token_.byte_value = Storage::Encodings::MFM::MFMSyncByteValue;
break;
default:
break;
}
}
if(token_type != Token::Byte)
{
latest_token_.type = token_type;
bits_since_token_ = 0;
posit_event(Event::Token);
return;
}
}
if(bits_since_token_ == 16)
{
latest_token_.type = Token::Byte;
latest_token_.byte_value = (uint8_t)(
((shift_register_ & 0x0001) >> 0) |
((shift_register_ & 0x0004) >> 1) |
((shift_register_ & 0x0010) >> 2) |
((shift_register_ & 0x0040) >> 3) |
((shift_register_ & 0x0100) >> 4) |
((shift_register_ & 0x0400) >> 5) |
((shift_register_ & 0x1000) >> 6) |
((shift_register_ & 0x4000) >> 7));
bits_since_token_ = 0;
if(is_awaiting_marker_value_ && is_double_density_)
{
is_awaiting_marker_value_ = false;
switch(latest_token_.byte_value)
{
case Storage::Encodings::MFM::IndexAddressByte:
latest_token_.type = Token::Index;
break;
case Storage::Encodings::MFM::IDAddressByte:
latest_token_.type = Token::ID;
break;
case Storage::Encodings::MFM::DataAddressByte:
latest_token_.type = Token::Data;
break;
case Storage::Encodings::MFM::DeletedDataAddressByte:
latest_token_.type = Token::DeletedData;
break;
default: break;
}
}
crc_generator_.add(latest_token_.byte_value);
posit_event(Event::Token);
return;
}
}
void WD1770::process_index_hole()
{
index_hole_count_++;
posit_event(Event::IndexHole);
if(index_hole_count_target_ == index_hole_count_)
{
posit_event(Event::IndexHoleTarget);
index_hole_count_target_ = -1;
}
// motor power-down
if(index_hole_count_ == 9 && !status_.busy && has_motor_on_line())
{
set_motor_on(false);
}
// head unload
if(index_hole_count_ == 15 && !status_.busy && has_head_load_line())
{
set_head_load_request(false);
}
}
void WD1770::process_write_completed()
{
posit_event(Event::DataWritten);
}
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = mask; return; case __LINE__:
#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; interesting_event_mask_ = Event::Timer; delay_time_ = ms * 8000; if(delay_time_) return; case __LINE__:
#define WAIT_FOR_BYTES(count) resume_point_ = __LINE__; interesting_event_mask_ = Event::Token; distance_into_section_ = 0; return; case __LINE__: if(latest_token_.type == Token::Byte) distance_into_section_++; if(distance_into_section_ < count) { interesting_event_mask_ = Event::Token; return; }
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = static_cast<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 BEGIN_SECTION() switch(resume_point_) { default:
#define END_SECTION() 0; }
#define END_SECTION() (void)0; }
#define READ_ID() \
if(new_event_type == Event::Token) \
{ \
if(!distance_into_section_ && latest_token_.type == Token::ID) {data_mode_ = DataMode::Reading; distance_into_section_++; } \
else if(distance_into_section_ && distance_into_section_ < 7 && latest_token_.type == Token::Byte) \
{ \
header_[distance_into_section_ - 1] = latest_token_.byte_value; \
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) { \
header_[distance_into_section_ - 1] = get_latest_token().byte_value; \
distance_into_section_++; \
} \
}
@@ -323,7 +136,7 @@ void WD1770::process_write_completed()
set_motor_on(true); \
index_hole_count_ = 0; \
index_hole_count_target_ = 6; \
WAIT_FOR_EVENT(Event::IndexHoleTarget); \
WAIT_FOR_EVENT(Event1770::IndexHoleTarget); \
status_.spin_up = true;
// +--------+----------+-------------------------+
@@ -343,18 +156,45 @@ void WD1770::process_write_completed()
// ! 4 ! Forc int ! 1 1 0 1 i3 i2 i1 i0 !
// +--------+----------+-------------------------+
void WD1770::posit_event(Event new_event_type)
{
if(!(interesting_event_mask_ & (int)new_event_type)) return;
interesting_event_mask_ &= ~new_event_type;
void WD1770::posit_event(int new_event_type) {
if(new_event_type == static_cast<int>(Event::IndexHole)) {
index_hole_count_++;
if(index_hole_count_target_ == index_hole_count_) {
posit_event(static_cast<int>(Event1770::IndexHoleTarget));
index_hole_count_target_ = -1;
}
// motor power-down
if(index_hole_count_ == 9 && !status_.busy && has_motor_on_line()) {
set_motor_on(false);
}
// head unload
if(index_hole_count_ == 15 && !status_.busy && has_head_load_line()) {
set_head_load_request(false);
}
}
if(new_event_type == static_cast<int>(Event1770::ForceInterrupt)) {
interesting_event_mask_ = 0;
resume_point_ = 0;
update_status([] (Status &status) {
status.type = Status::One;
status.data_request = false;
});
} else {
if(!(interesting_event_mask_ & static_cast<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");
data_mode_ = DataMode::Scanning;
set_data_mode(DataMode::Scanning);
index_hole_count_ = 0;
update_status([] (Status &status) {
@@ -362,7 +202,7 @@ void WD1770::posit_event(Event new_event_type)
status.interrupt_request = true;
});
WAIT_FOR_EVENT(Event::Command);
WAIT_FOR_EVENT(Event1770::Command);
update_status([] (Status &status) {
status.busy = true;
@@ -405,18 +245,17 @@ void WD1770::posit_event(Event new_event_type)
goto begin_type1_load_head;
begin_type1_load_head:
if(!(command_&0x08))
{
if(!(command_&0x08)) {
set_head_load_request(false);
goto test_type1_type;
}
set_head_load_request(true);
if(head_is_loaded_) goto test_type1_type;
WAIT_FOR_EVENT(Event::HeadLoad);
WAIT_FOR_EVENT(Event1770::HeadLoad);
goto test_type1_type;
begin_type1_spin_up:
if((command_&0x08) || get_motor_on()) goto test_type1_type;
if((command_&0x08) || get_drive().get_motor_on()) goto test_type1_type;
SPIN_UP();
test_type1_type:
@@ -426,8 +265,7 @@ void WD1770::posit_event(Event new_event_type)
if((command_ >> 5) != 0) goto perform_step_command;
// This is now definitely either a seek or a restore; if it's a restore then set track to 0xff and data to 0x00.
if(!(command_ & 0x10))
{
if(!(command_ & 0x10)) {
track_ = 0xff;
data_ = 0;
}
@@ -440,15 +278,13 @@ void WD1770::posit_event(Event new_event_type)
if(step_direction_) track_++; else track_--;
perform_step:
if(!step_direction_ && get_is_track_zero())
{
if(!step_direction_ && get_drive().get_is_track_zero()) {
track_ = 0;
goto verify;
}
step(step_direction_ ? 1 : -1);
int time_to_wait;
switch(command_ & 3)
{
get_drive().step(step_direction_ ? 1 : -1);
unsigned int time_to_wait;
switch(command_ & 3) {
default:
case 0: time_to_wait = 6; break;
case 1: time_to_wait = 12; break;
@@ -464,8 +300,7 @@ void WD1770::posit_event(Event new_event_type)
goto perform_step;
verify:
if(!(command_ & 0x04))
{
if(!(command_ & 0x04)) {
goto wait_for_command;
}
@@ -473,29 +308,25 @@ void WD1770::posit_event(Event new_event_type)
distance_into_section_ = 0;
verify_read_data:
WAIT_FOR_EVENT(Event::IndexHole | Event::Token);
WAIT_FOR_EVENT(static_cast<int>(Event::IndexHole) | static_cast<int>(Event::Token));
READ_ID();
if(index_hole_count_ == 6)
{
if(index_hole_count_ == 6) {
update_status([] (Status &status) {
status.seek_error = true;
});
goto wait_for_command;
}
if(distance_into_section_ == 7)
{
data_mode_ = DataMode::Scanning;
if(crc_generator_.get_value())
{
if(distance_into_section_ == 7) {
set_data_mode(DataMode::Scanning);
if(get_crc_generator().get_value()) {
update_status([] (Status &status) {
status.crc_error = true;
});
goto verify_read_data;
}
if(header_[0] == track_)
{
if(header_[0] == track_) {
printf("Reached track %d\n", track_);
update_status([] (Status &status) {
status.crc_error = false;
@@ -539,11 +370,11 @@ void WD1770::posit_event(Event new_event_type)
begin_type2_load_head:
set_head_load_request(true);
if(head_is_loaded_) goto test_type2_delay;
WAIT_FOR_EVENT(Event::HeadLoad);
WAIT_FOR_EVENT(Event1770::HeadLoad);
goto test_type2_delay;
begin_type2_spin_up:
if(get_motor_on()) goto test_type2_delay;
if(get_drive().get_motor_on()) goto test_type2_delay;
// Perform spin up.
SPIN_UP();
@@ -553,8 +384,7 @@ void WD1770::posit_event(Event new_event_type)
WAIT_FOR_TIME(30);
test_type2_write_protection:
if(command_&0x20 && get_drive_is_read_only())
{
if(command_&0x20 && get_drive().get_is_read_only()) {
update_status([] (Status &status) {
status.write_protect = true;
});
@@ -562,27 +392,23 @@ void WD1770::posit_event(Event new_event_type)
}
type2_get_header:
WAIT_FOR_EVENT(Event::IndexHole | Event::Token);
WAIT_FOR_EVENT(static_cast<int>(Event::IndexHole) | static_cast<int>(Event::Token));
READ_ID();
if(index_hole_count_ == 5)
{
if(index_hole_count_ == 5) {
printf("Failed to find sector %d\n", sector_);
update_status([] (Status &status) {
status.record_not_found = true;
});
goto wait_for_command;
}
if(distance_into_section_ == 7)
{
if(distance_into_section_ == 7) {
printf("Considering %d/%d\n", header_[0], header_[2]);
data_mode_ = DataMode::Scanning;
if(header_[0] == track_ && header_[2] == sector_ &&
(has_motor_on_line() || !(command_&0x02) || ((command_&0x08) >> 3) == header_[1]))
{
set_data_mode(DataMode::Scanning);
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]);
if(crc_generator_.get_value())
{
if(get_crc_generator().get_value()) {
printf("CRC error; back to searching\n");
update_status([] (Status &status) {
status.crc_error = true;
@@ -607,28 +433,26 @@ void WD1770::posit_event(Event new_event_type)
type2_read_data:
WAIT_FOR_EVENT(Event::Token);
// TODO: timeout
if(latest_token_.type == Token::Data || latest_token_.type == Token::DeletedData)
{
if(get_latest_token().type == Token::Data || get_latest_token().type == Token::DeletedData) {
update_status([this] (Status &status) {
status.record_type = (latest_token_.type == Token::DeletedData);
status.record_type = (get_latest_token().type == Token::DeletedData);
});
distance_into_section_ = 0;
data_mode_ = DataMode::Reading;
set_data_mode(DataMode::Reading);
goto type2_read_byte;
}
goto type2_read_data;
type2_read_byte:
WAIT_FOR_EVENT(Event::Token);
if(latest_token_.type != Token::Byte) goto type2_read_byte;
data_ = latest_token_.byte_value;
if(get_latest_token().type != Token::Byte) goto type2_read_byte;
data_ = get_latest_token().byte_value;
update_status([] (Status &status) {
status.lost_data |= status.data_request;
status.data_request = true;
});
distance_into_section_++;
if(distance_into_section_ == 128 << header_[3])
{
if(distance_into_section_ == 128 << header_[3]) {
distance_into_section_ = 0;
goto type2_check_crc;
}
@@ -636,13 +460,11 @@ void WD1770::posit_event(Event new_event_type)
type2_check_crc:
WAIT_FOR_EVENT(Event::Token);
if(latest_token_.type != Token::Byte) goto type2_read_byte;
header_[distance_into_section_] = latest_token_.byte_value;
if(get_latest_token().type != Token::Byte) goto type2_read_byte;
header_[distance_into_section_] = get_latest_token().byte_value;
distance_into_section_++;
if(distance_into_section_ == 2)
{
if(crc_generator_.get_value())
{
if(distance_into_section_ == 2) {
if(get_crc_generator().get_value()) {
printf("CRC error; terminating\n");
update_status([this] (Status &status) {
status.crc_error = true;
@@ -650,12 +472,11 @@ void WD1770::posit_event(Event new_event_type)
goto wait_for_command;
}
if(command_ & 0x10)
{
if(command_ & 0x10) {
sector_++;
goto test_type2_write_protection;
}
printf("Read sector %d\n", sector_);
printf("Finished reading sector %d\n", sector_);
goto wait_for_command;
}
goto type2_check_crc;
@@ -667,37 +488,31 @@ void WD1770::posit_event(Event new_event_type)
status.data_request = true;
});
WAIT_FOR_BYTES(9);
if(status_.data_request)
{
if(status_.data_request) {
update_status([] (Status &status) {
status.lost_data = true;
});
goto wait_for_command;
}
WAIT_FOR_BYTES(1);
if(is_double_density_)
{
if(get_is_double_density()) {
WAIT_FOR_BYTES(11);
}
data_mode_ = DataMode::Writing;
begin_writing();
for(int c = 0; c < (is_double_density_ ? 12 : 6); c++)
{
set_data_mode(DataMode::Writing);
begin_writing(false);
for(int c = 0; c < (get_is_double_density() ? 12 : 6); c++) {
write_byte(0);
}
WAIT_FOR_EVENT(Event::DataWritten);
if(is_double_density_)
{
crc_generator_.set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue);
if(get_is_double_density()) {
get_crc_generator().set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue);
for(int c = 0; c < 3; c++) write_raw_short(Storage::Encodings::MFM::MFMSync);
write_byte((command_&0x01) ? Storage::Encodings::MFM::DeletedDataAddressByte : Storage::Encodings::MFM::DataAddressByte);
}
else
{
crc_generator_.reset();
crc_generator_.add((command_&0x01) ? Storage::Encodings::MFM::DeletedDataAddressByte : Storage::Encodings::MFM::DataAddressByte);
} else {
get_crc_generator().reset();
get_crc_generator().add((command_&0x01) ? Storage::Encodings::MFM::DeletedDataAddressByte : Storage::Encodings::MFM::DataAddressByte);
write_raw_short((command_&0x01) ? Storage::Encodings::MFM::FMDeletedDataAddressMark : Storage::Encodings::MFM::FMDataAddressMark);
}
@@ -715,8 +530,7 @@ void WD1770::posit_event(Event new_event_type)
*/
write_byte(data_);
distance_into_section_++;
if(distance_into_section_ == 128 << header_[3])
{
if(distance_into_section_ == 128 << header_[3]) {
goto type2_write_crc;
}
@@ -724,8 +538,7 @@ void WD1770::posit_event(Event new_event_type)
status.data_request = true;
});
WAIT_FOR_EVENT(Event::DataWritten);
if(status_.data_request)
{
if(status_.data_request) {
end_writing();
update_status([] (Status &status) {
status.lost_data = true;
@@ -736,17 +549,12 @@ void WD1770::posit_event(Event new_event_type)
goto type2_write_loop;
type2_write_crc:
{
uint16_t crc = crc_generator_.get_value();
write_byte(crc >> 8);
write_byte(crc & 0xff);
}
write_crc();
write_byte(0xff);
WAIT_FOR_EVENT(Event::DataWritten);
end_writing();
if(command_ & 0x10)
{
if(command_ & 0x10) {
sector_++;
goto test_type2_write_protection;
}
@@ -780,11 +588,11 @@ void WD1770::posit_event(Event new_event_type)
begin_type3_load_head:
set_head_load_request(true);
if(head_is_loaded_) goto type3_test_delay;
WAIT_FOR_EVENT(Event::HeadLoad);
WAIT_FOR_EVENT(Event1770::HeadLoad);
goto type3_test_delay;
begin_type3_spin_up:
if((command_&0x08) || get_motor_on()) goto type3_test_delay;
if((command_&0x08) || get_drive().get_motor_on()) goto type3_test_delay;
SPIN_UP();
type3_test_delay:
@@ -801,30 +609,25 @@ void WD1770::posit_event(Event new_event_type)
distance_into_section_ = 0;
read_address_get_header:
WAIT_FOR_EVENT(Event::IndexHole | Event::Token);
if(new_event_type == Event::Token)
{
if(!distance_into_section_ && latest_token_.type == Token::ID) {data_mode_ = DataMode::Reading; distance_into_section_++; }
else if(distance_into_section_ && distance_into_section_ < 7 && latest_token_.type == Token::Byte)
{
if(status_.data_request)
{
WAIT_FOR_EVENT(static_cast<int>(Event::IndexHole) | static_cast<int>(Event::Token));
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(status_.data_request) {
update_status([] (Status &status) {
status.lost_data = true;
});
goto wait_for_command;
}
header_[distance_into_section_ - 1] = data_ = latest_token_.byte_value;
header_[distance_into_section_ - 1] = data_ = get_latest_token().byte_value;
track_ = header_[0];
update_status([] (Status &status) {
status.data_request = true;
});
distance_into_section_++;
if(distance_into_section_ == 7)
{
if(crc_generator_.get_value())
{
if(distance_into_section_ == 7) {
if(get_crc_generator().get_value()) {
update_status([] (Status &status) {
status.crc_error = true;
});
@@ -834,8 +637,7 @@ void WD1770::posit_event(Event new_event_type)
}
}
if(index_hole_count_ == 6)
{
if(index_hole_count_ == 6) {
update_status([] (Status &status) {
status.record_not_found = true;
});
@@ -848,19 +650,17 @@ void WD1770::posit_event(Event new_event_type)
index_hole_count_ = 0;
read_track_read_byte:
WAIT_FOR_EVENT(Event::Token | Event::IndexHole);
if(index_hole_count_)
{
WAIT_FOR_EVENT(static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole));
if(index_hole_count_) {
goto wait_for_command;
}
if(status_.data_request)
{
if(status_.data_request) {
update_status([] (Status &status) {
status.lost_data = true;
});
goto wait_for_command;
}
data_ = latest_token_.byte_value;
data_ = get_latest_token().byte_value;
update_status([] (Status &status) {
status.data_request = true;
});
@@ -872,9 +672,7 @@ void WD1770::posit_event(Event new_event_type)
status.lost_data = false;
});
write_track_test_write_protect:
if(get_drive_is_read_only())
{
if(get_drive().get_is_read_only()) {
update_status([] (Status &status) {
status.write_protect = true;
});
@@ -885,49 +683,41 @@ void WD1770::posit_event(Event new_event_type)
status.data_request = true;
});
WAIT_FOR_BYTES(3);
if(status_.data_request)
{
if(status_.data_request) {
update_status([] (Status &status) {
status.lost_data = true;
});
goto wait_for_command;
}
WAIT_FOR_EVENT(Event::IndexHoleTarget);
begin_writing();
WAIT_FOR_EVENT(Event1770::IndexHoleTarget);
begin_writing(true);
index_hole_count_ = 0;
write_track_write_loop:
if(is_double_density_)
{
switch(data_)
{
if(get_is_double_density()) {
switch(data_) {
case 0xf5:
write_raw_short(Storage::Encodings::MFM::MFMSync);
crc_generator_.set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue);
get_crc_generator().set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue);
break;
case 0xf6:
write_raw_short(Storage::Encodings::MFM::MFMIndexSync);
break;
case 0xff: {
uint16_t crc = crc_generator_.get_value();
write_byte(crc >> 8);
write_byte(crc & 0xff);
} break;
case 0xff:
write_crc();
break;
default:
write_byte(data_);
break;
}
}
else
{
switch(data_)
{
} else {
switch(data_) {
case 0xf8: case 0xf9: case 0xfa: case 0xfb:
case 0xfd: case 0xfe:
// clock is 0xc7 = 1010 0000 0010 1010 = 0xa022
write_raw_short(
(uint16_t)(
static_cast<uint16_t>(
0xa022 |
((data_ & 0x80) << 7) |
((data_ & 0x40) << 6) |
@@ -939,17 +729,15 @@ void WD1770::posit_event(Event new_event_type)
(data_ & 0x01)
)
);
crc_generator_.reset();
crc_generator_.add(data_);
get_crc_generator().reset();
get_crc_generator().add(data_);
break;
case 0xfc:
write_raw_short(Storage::Encodings::MFM::FMIndexAddressMark);
break;
case 0xf7: {
uint16_t crc = crc_generator_.get_value();
write_byte(crc >> 8);
write_byte(crc & 0xff);
} break;
case 0xf7:
write_crc();
break;
default:
write_byte(data_);
break;
@@ -960,16 +748,14 @@ void WD1770::posit_event(Event new_event_type)
status.data_request = true;
});
WAIT_FOR_EVENT(Event::DataWritten);
if(status_.data_request)
{
if(status_.data_request) {
update_status([] (Status &status) {
status.lost_data = true;
});
end_writing();
goto wait_for_command;
}
if(index_hole_count_)
{
if(index_hole_count_) {
end_writing();
goto wait_for_command;
}
@@ -979,10 +765,8 @@ void WD1770::posit_event(Event new_event_type)
END_SECTION()
}
void WD1770::update_status(std::function<void(Status &)> updater)
{
if(delegate_)
{
void WD1770::update_status(std::function<void(Status &)> updater) {
if(delegate_) {
Status old_status = status_;
updater(status_);
bool did_change =
@@ -994,38 +778,9 @@ void WD1770::update_status(std::function<void(Status &)> updater)
}
void WD1770::set_head_load_request(bool head_load) {}
void WD1770::set_motor_on(bool motor_on) {}
void WD1770::set_head_loaded(bool head_loaded)
{
void WD1770::set_head_loaded(bool head_loaded) {
head_is_loaded_ = head_loaded;
if(head_loaded) posit_event(Event::HeadLoad);
}
void WD1770::write_bit(int bit)
{
if(is_double_density_)
{
Controller::write_bit(!bit && !last_bit_);
Controller::write_bit(!!bit);
last_bit_ = bit;
}
else
{
Controller::write_bit(true);
Controller::write_bit(!!bit);
}
}
void WD1770::write_byte(uint8_t byte)
{
for(int c = 0; c < 8; c++) write_bit((byte << c)&0x80);
crc_generator_.add(byte);
}
void WD1770::write_raw_short(uint16_t value)
{
for(int c = 0; c < 16; c++)
{
Controller::write_bit(!!((value << c)&0x8000));
}
if(head_loaded) posit_event(static_cast<int>(Event1770::HeadLoad));
}

View File

@@ -9,26 +9,41 @@
#ifndef _770_hpp
#define _770_hpp
#include "../../Storage/Disk/DiskController.hpp"
#include "../../NumberTheory/CRC.hpp"
#include "../../Storage/Disk/Controller/MFMDiskController.hpp"
namespace WD {
class WD1770: public Storage::Disk::Controller {
/*!
Provides an emulation of various Western Digital drive controllers, including the
WD1770, WD1772, FDC1773 and FDC1793.
*/
class WD1770: public Storage::Disk::MFMController {
public:
enum Personality {
P1770, // implies automatic motor-on management with Type 2 commands offering a spin-up disable
P1770, // implies automatic motor-on management, with Type 2 commands offering a spin-up disable
P1772, // as per the 1770, with different stepping rates
P1773, // implements the side number-testing logic of the 1793; omits spin-up/loading logic
P1793 // implies Type 2 commands use side number testing logic; spin-up/loading is by HLD and HLT
};
/*!
Constructs an instance of the drive controller that behaves according to personality @c p.
@param p The type of controller to emulate.
*/
WD1770(Personality p);
void set_is_double_density(bool is_double_density);
/// Sets the value of the double-density input; when @c is_double_density is @c true, reads and writes double-density format data.
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);
/// Fetches the value of the register @c address. Only the low two bits of the address are decoded.
uint8_t get_register(int address);
void run_for_cycles(unsigned int number_of_cycles);
/// Runs the controller for @c number_of_cycles cycles.
void run_for(const Cycles cycles);
using Storage::Disk::Controller::run_for;
enum Flag: uint8_t {
NotReady = 0x80,
@@ -47,8 +62,12 @@ class WD1770: public Storage::Disk::Controller {
Busy = 0x01
};
/// @returns The current value of the IRQ line output.
inline bool get_interrupt_request_line() { return status_.interrupt_request; }
/// @returns The current value of the DRQ line output.
inline bool get_data_request_line() { return status_.data_request; }
class Delegate {
public:
virtual void wd1770_did_change_output(WD1770 *wd1770) = 0;
@@ -57,6 +76,7 @@ class WD1770: public Storage::Disk::Controller {
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);
private:
@@ -65,20 +85,19 @@ class WD1770: public Storage::Disk::Controller {
inline bool has_head_load_line() { return (personality_ == P1793 ); }
struct Status {
Status();
bool write_protect;
bool record_type;
bool spin_up;
bool record_not_found;
bool crc_error;
bool seek_error;
bool lost_data;
bool data_request;
bool interrupt_request;
bool busy;
bool write_protect = false;
bool record_type = false;
bool spin_up = false;
bool record_not_found = false;
bool crc_error = false;
bool seek_error = false;
bool lost_data = false;
bool data_request = false;
bool interrupt_request = false;
bool busy = false;
enum {
One, Two, Three
} type;
} type = One;
} status_;
uint8_t track_;
uint8_t sector_;
@@ -86,67 +105,33 @@ class WD1770: public Storage::Disk::Controller {
uint8_t command_;
int index_hole_count_;
int index_hole_count_target_;
int bits_since_token_;
int index_hole_count_target_ = -1;
int distance_into_section_;
bool is_awaiting_marker_value_;
int step_direction_;
void update_status(std::function<void(Status &)> updater);
// Tokeniser
enum DataMode {
Scanning,
Reading,
Writing
} data_mode_;
bool is_double_density_;
int shift_register_;
struct Token {
enum Type {
Index, ID, Data, DeletedData, Sync, Byte
} type;
uint8_t byte_value;
} latest_token_;
// Events
enum Event: int {
Command = (1 << 0), // Indicates receipt of a new command.
Token = (1 << 1), // Indicates recognition of a new token in the flux stream. Interrogate latest_token_ for details.
IndexHole = (1 << 2), // Indicates the passing of a physical index hole.
HeadLoad = (1 << 3), // Indicates the head has been loaded (1973 only).
DataWritten = (1 << 4), // Indicates that all queued bits have been written
enum Event1770: int {
Command = (1 << 3), // Indicates receipt of a new command.
HeadLoad = (1 << 4), // Indicates the head has been loaded (1973 only).
Timer = (1 << 5), // Indicates that the delay_time_-powered timer has timed out.
IndexHoleTarget = (1 << 6) // Indicates that index_hole_count_ has reached index_hole_count_target_.
IndexHoleTarget = (1 << 6), // Indicates that index_hole_count_ has reached index_hole_count_target_.
ForceInterrupt = (1 << 7) // Indicates a forced interrupt.
};
void posit_event(Event type);
void posit_event(int type);
int interesting_event_mask_;
int resume_point_;
int delay_time_;
// Output
int last_bit_;
void write_bit(int bit);
void write_byte(uint8_t byte);
void write_raw_short(uint16_t value);
int resume_point_ = 0;
unsigned int delay_time_ = 0;
// ID buffer
uint8_t header_[6];
// CRC generator
NumberTheory::CRC16 crc_generator_;
// 1793 head-loading logic
bool head_is_loaded_;
bool head_is_loaded_ = false;
// delegate
Delegate *delegate_;
// Storage::Disk::Controller
virtual void process_input_bit(int value, unsigned int cycles_since_index_hole);
virtual void process_index_hole();
virtual void process_write_completed();
Delegate *delegate_ = nullptr;
};
}

View File

@@ -13,7 +13,83 @@
#include <typeinfo>
#include <cstdio>
#include "Implementation/6522Storage.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
namespace MOS {
namespace MOS6522 {
enum Port {
A = 0,
B = 1
};
enum Line {
One = 0,
Two = 1
};
/*!
Provides the mechanism for just-int-time communication from a 6522; the normal use case is to compose a
6522 and a subclass of PortHandler in order to reproduce a 6522 and its original bus wiring.
*/
class PortHandler {
public:
/// Requests the current input value of @c port from the port handler.
uint8_t get_port_input(Port port) { return 0xff; }
/// Sets the current output value of @c port and provides @c direction_mask, indicating which pins are marked as output.
void set_port_output(Port port, uint8_t value, uint8_t direction_mask) {}
/// Sets the current logical output level for line @c line on port @c port.
void set_control_line_output(Port port, Line line, bool value) {}
/// Sets the current logical value of the interrupt line.
void set_interrupt_status(bool status) {}
};
/*!
Provided as an optional alternative base to @c PortHandler for port handlers; via the delegate pattern adds
a virtual level of indirection for receiving changes to the interrupt line.
*/
class IRQDelegatePortHandler: public PortHandler {
public:
class Delegate {
public:
/// Indicates that the interrupt status has changed for the IRQDelegatePortHandler provided.
virtual void mos6522_did_change_interrupt_status(void *irq_delegate) = 0;
};
/// Sets the delegate that will receive notification of changes in the interrupt line.
void set_interrupt_delegate(Delegate *delegate);
/// Overrides PortHandler::set_interrupt_status, notifying the delegate if one is set.
void set_interrupt_status(bool new_status);
private:
Delegate *delegate_ = nullptr;
};
class MOS6522Base: public MOS6522Storage {
public:
/// Sets the input value of line @c line on port @c port.
void set_control_line_input(Port port, Line line, bool value);
/// Runs for a specified number of half cycles.
void run_for(const HalfCycles half_cycles);
/// Runs for a specified number of cycles.
void run_for(const Cycles cycles);
/// @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').
@@ -26,398 +102,27 @@ namespace MOS {
Consumers should derive their own curiously-recurring-template-pattern subclass,
implementing bus communications as required.
*/
template <class T> class MOS6522 {
private:
enum InterruptFlag: uint8_t {
CA2ActiveEdge = 1 << 0,
CA1ActiveEdge = 1 << 1,
ShiftRegister = 1 << 2,
CB2ActiveEdge = 1 << 3,
CB1ActiveEdge = 1 << 4,
Timer2 = 1 << 5,
Timer1 = 1 << 6,
};
template <class T> class MOS6522: public MOS6522Base {
public:
enum Port {
A = 0,
B = 1
};
enum Line {
One = 0,
Two = 1
};
MOS6522(T &bus_handler) noexcept : bus_handler_(bus_handler) {}
MOS6522(const MOS6522 &) = delete;
/*! Sets a register value. */
inline void set_register(int address, uint8_t value)
{
address &= 0xf;
// printf("6522 [%s]: %0x <- %02x\n", typeid(*this).name(), address, value);
switch(address)
{
case 0x0:
registers_.output[1] = value;
static_cast<T *>(this)->set_port_output(Port::B, value, registers_.data_direction[1]); // TODO: handshake
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | ((registers_.peripheral_control&0x20) ? 0 : InterruptFlag::CB2ActiveEdge));
reevaluate_interrupts();
break;
case 0xf:
case 0x1:
registers_.output[0] = value;
static_cast<T *>(this)->set_port_output(Port::A, value, registers_.data_direction[0]); // TODO: handshake
registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | ((registers_.peripheral_control&0x02) ? 0 : InterruptFlag::CB2ActiveEdge));
reevaluate_interrupts();
break;
// // No handshake, so write directly
// registers_.output[0] = value;
// static_cast<T *>(this)->set_port_output(0, value);
// break;
case 0x2:
registers_.data_direction[1] = value;
break;
case 0x3:
registers_.data_direction[0] = value;
break;
// Timer 1
case 0x6: case 0x4: registers_.timer_latch[0] = (registers_.timer_latch[0]&0xff00) | value; break;
case 0x5: case 0x7:
registers_.timer_latch[0] = (registers_.timer_latch[0]&0x00ff) | (uint16_t)(value << 8);
registers_.interrupt_flags &= ~InterruptFlag::Timer1;
if(address == 0x05)
{
registers_.next_timer[0] = registers_.timer_latch[0];
timer_is_running_[0] = true;
}
reevaluate_interrupts();
break;
// Timer 2
case 0x8: registers_.timer_latch[1] = value; break;
case 0x9:
registers_.interrupt_flags &= ~InterruptFlag::Timer2;
registers_.next_timer[1] = registers_.timer_latch[1] | (uint16_t)(value << 8);
timer_is_running_[1] = true;
reevaluate_interrupts();
break;
// Shift
case 0xa: registers_.shift = value; break;
// Control
case 0xb:
registers_.auxiliary_control = value;
break;
case 0xc:
// printf("Peripheral control %02x\n", value);
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: static_cast<T *>(this)->set_control_line_output(Port::A, Line::Two, false); break;
case 0x0e: static_cast<T *>(this)->set_control_line_output(Port::A, Line::Two, true); break;
}
}
if(value & 0x80)
{
switch(value & 0xe0)
{
default: printf("Unimplemented control line mode %d\n", (value >> 5)&7); break;
case 0xc0: static_cast<T *>(this)->set_control_line_output(Port::B, Line::Two, false); break;
case 0xe0: static_cast<T *>(this)->set_control_line_output(Port::B, Line::Two, true); break;
}
}
break;
// Interrupt control
case 0xd:
registers_.interrupt_flags &= ~value;
reevaluate_interrupts();
break;
case 0xe:
if(value&0x80)
registers_.interrupt_enable |= value;
else
registers_.interrupt_enable &= ~value;
reevaluate_interrupts();
break;
}
}
void set_register(int address, uint8_t value);
/*! Gets a register value. */
inline uint8_t get_register(int address)
{
address &= 0xf;
// printf("6522 %p: %d\n", this, 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 0x1:
registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | InterruptFlag::CA2ActiveEdge);
reevaluate_interrupts();
return get_port_input(Port::A, registers_.data_direction[0], registers_.output[0]);
case 0x2: return registers_.data_direction[1];
case 0x3: return registers_.data_direction[0];
// Timer 1
case 0x4:
registers_.interrupt_flags &= ~InterruptFlag::Timer1;
reevaluate_interrupts();
return registers_.timer[0] & 0x00ff;
case 0x5: return registers_.timer[0] >> 8;
case 0x6: return registers_.timer_latch[0] & 0x00ff;
case 0x7: return registers_.timer_latch[0] >> 8;
// Timer 2
case 0x8:
registers_.interrupt_flags &= ~InterruptFlag::Timer2;
reevaluate_interrupts();
return registers_.timer[1] & 0x00ff;
case 0x9: return registers_.timer[1] >> 8;
case 0xa: return registers_.shift;
case 0xb: return registers_.auxiliary_control;
case 0xc: return registers_.peripheral_control;
case 0xd: return registers_.interrupt_flags | (get_interrupt_line() ? 0x80 : 0x00);
case 0xe: return registers_.interrupt_enable | 0x80;
}
return 0xff;
}
inline void 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;
}
}
#define 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] = (uint16_t)registers_.next_timer[0]; registers_.next_timer[0] = -1; }\
if(registers_.next_timer[1] >= 0) { registers_.timer[1] = (uint16_t)registers_.next_timer[1]; registers_.next_timer[1] = -1; }\
// IRQ is raised on the half cycle after overflow
#define phase1() \
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.
Although the original chip accepts only a phase-2 input, timer reloads are specified as occuring
1.5 cycles after the timer hits zero. It therefore may be necessary to emulate at half-cycle precision.
The first emulated half-cycle will be the period between the trailing edge of a phase-2 input and the
next rising edge. So it should align with a full system's phase-1. The next emulated half-cycle will be
that which occurs during phase-2.
Callers should decide whether they are going to use @c run_for_half_cycles or @c run_for_cycles, and not
intermingle usage.
*/
inline void run_for_half_cycles(unsigned int number_of_cycles)
{
if(is_phase2_)
{
phase2();
number_of_cycles--;
}
while(number_of_cycles >= 2)
{
phase1();
phase2();
number_of_cycles -= 2;
}
if(number_of_cycles)
{
phase1();
is_phase2_ = true;
}
else
{
is_phase2_ = false;
}
}
/*!
Runs for a specified number of cycles.
Callers should decide whether they are going to use @c run_for_half_cycles or @c run_for_cycles, and not
intermingle usage.
*/
inline void run_for_cycles(unsigned int number_of_cycles)
{
while(number_of_cycles--)
{
phase1();
phase2();
}
}
#undef phase1
#undef phase2
/*! @returns @c true if the IRQ line is currently active; @c false otherwise. */
inline bool get_interrupt_line()
{
uint8_t interrupt_status = registers_.interrupt_flags & registers_.interrupt_enable & 0x7f;
return !!interrupt_status;
}
MOS6522() :
timer_is_running_{false, false},
last_posted_interrupt_status_(false),
is_phase2_(false)
{}
uint8_t get_register(int address);
private:
// Expected to be overridden
uint8_t get_port_input(Port port) { return 0xff; }
void set_port_output(Port port, uint8_t value, uint8_t direction_mask) {}
void set_control_line_output(Port port, Line line, bool value) {}
void set_interrupt_status(bool status) {}
T &bus_handler_;
// Input/output multiplexer
uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output)
{
uint8_t input = static_cast<T *>(this)->get_port_input(port);
return (input & ~output_mask) | (output & output_mask);
}
// Phase toggle
bool is_phase2_;
// Delegate and communications
bool last_posted_interrupt_status_;
inline void reevaluate_interrupts()
{
bool new_interrupt_status = get_interrupt_line();
if(new_interrupt_status != last_posted_interrupt_status_)
{
last_posted_interrupt_status_ = new_interrupt_status;
static_cast<T *>(this)->set_interrupt_status(new_interrupt_status);
}
}
// The registers
struct Registers {
uint8_t output[2], input[2], data_direction[2];
uint16_t timer[2], timer_latch[2], last_timer[2];
int next_timer[2];
uint8_t shift;
uint8_t auxiliary_control, peripheral_control;
uint8_t interrupt_flags, interrupt_enable;
bool timer_needs_reload;
// "A low reset (RES) input clears all R6522 internal registers to logic 0"
Registers() :
output{0, 0}, input{0, 0}, data_direction{0, 0},
auxiliary_control(0), peripheral_control(0),
interrupt_flags(0), interrupt_enable(0),
last_timer{0, 0}, timer_needs_reload(false),
next_timer{-1, -1} {}
} registers_;
// control state
struct {
bool line_one, line_two;
} control_inputs_[2];
// Internal state other than the registers
bool timer_is_running_[2];
uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output);
inline void reevaluate_interrupts();
};
/*!
Provided for optional composition with @c MOS6522, @c MOS6522IRQDelegate provides for a delegate
that will receive IRQ line change notifications.
*/
class MOS6522IRQDelegate {
public:
class Delegate {
public:
virtual void mos6522_did_change_interrupt_status(void *mos6522) = 0;
};
inline void set_interrupt_delegate(Delegate *delegate)
{
delegate_ = delegate;
}
inline void set_interrupt_status(bool new_status)
{
if(delegate_) delegate_->mos6522_did_change_interrupt_status(this);
}
private:
Delegate *delegate_;
};
#include "Implementation/6522Implementation.hpp"
}
}
#endif /* _522_hpp */

View File

@@ -0,0 +1,116 @@
//
// 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

@@ -0,0 +1,155 @@
//
// Implementation.hpp
// Clock Signal
//
// Created by Thomas Harte on 04/09/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
template <typename T> void MOS6522<T>::set_register(int address, uint8_t value) {
address &= 0xf;
switch(address) {
case 0x0:
registers_.output[1] = value;
bus_handler_.set_port_output(Port::B, value, registers_.data_direction[1]); // TODO: handshake
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | ((registers_.peripheral_control&0x20) ? 0 : InterruptFlag::CB2ActiveEdge));
reevaluate_interrupts();
break;
case 0xf:
case 0x1:
registers_.output[0] = value;
bus_handler_.set_port_output(Port::A, value, registers_.data_direction[0]); // TODO: handshake
registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | ((registers_.peripheral_control&0x02) ? 0 : InterruptFlag::CB2ActiveEdge));
reevaluate_interrupts();
break;
case 0x2:
registers_.data_direction[1] = value;
break;
case 0x3:
registers_.data_direction[0] = value;
break;
// Timer 1
case 0x6: case 0x4: registers_.timer_latch[0] = (registers_.timer_latch[0]&0xff00) | value; break;
case 0x5: case 0x7:
registers_.timer_latch[0] = (registers_.timer_latch[0]&0x00ff) | static_cast<uint16_t>(value << 8);
registers_.interrupt_flags &= ~InterruptFlag::Timer1;
if(address == 0x05) {
registers_.next_timer[0] = registers_.timer_latch[0];
timer_is_running_[0] = true;
}
reevaluate_interrupts();
break;
// Timer 2
case 0x8: registers_.timer_latch[1] = value; break;
case 0x9:
registers_.interrupt_flags &= ~InterruptFlag::Timer2;
registers_.next_timer[1] = registers_.timer_latch[1] | static_cast<uint16_t>(value << 8);
timer_is_running_[1] = true;
reevaluate_interrupts();
break;
// Shift
case 0xa: registers_.shift = value; break;
// Control
case 0xb:
registers_.auxiliary_control = value;
break;
case 0xc:
// printf("Peripheral control %02x\n", value);
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;
}
}
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;
// Interrupt control
case 0xd:
registers_.interrupt_flags &= ~value;
reevaluate_interrupts();
break;
case 0xe:
if(value&0x80)
registers_.interrupt_enable |= value;
else
registers_.interrupt_enable &= ~value;
reevaluate_interrupts();
break;
}
}
template <typename T> uint8_t MOS6522<T>::get_register(int address) {
address &= 0xf;
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 0x1:
registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | InterruptFlag::CA2ActiveEdge);
reevaluate_interrupts();
return get_port_input(Port::A, registers_.data_direction[0], registers_.output[0]);
case 0x2: return registers_.data_direction[1];
case 0x3: return registers_.data_direction[0];
// Timer 1
case 0x4:
registers_.interrupt_flags &= ~InterruptFlag::Timer1;
reevaluate_interrupts();
return registers_.timer[0] & 0x00ff;
case 0x5: return registers_.timer[0] >> 8;
case 0x6: return registers_.timer_latch[0] & 0x00ff;
case 0x7: return registers_.timer_latch[0] >> 8;
// Timer 2
case 0x8:
registers_.interrupt_flags &= ~InterruptFlag::Timer2;
reevaluate_interrupts();
return registers_.timer[1] & 0x00ff;
case 0x9: return registers_.timer[1] >> 8;
case 0xa: return registers_.shift;
case 0xb: return registers_.auxiliary_control;
case 0xc: return registers_.peripheral_control;
case 0xd: return registers_.interrupt_flags | (get_interrupt_line() ? 0x80 : 0x00);
case 0xe: return registers_.interrupt_enable | 0x80;
}
return 0xff;
}
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);
return (input & ~output_mask) | (output & output_mask);
}
// Delegate and communications
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_.set_interrupt_status(new_interrupt_status);
}
}

View File

@@ -0,0 +1,63 @@
//
// 6522Storage.hpp
// Clock Signal
//
// Created by Thomas Harte on 04/09/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef _522Storage_hpp
#define _522Storage_hpp
#include <cstdint>
namespace MOS {
namespace MOS6522 {
class MOS6522Storage {
protected:
// Phase toggle
bool is_phase2_ = false;
// The registers
struct Registers {
// "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};
uint16_t timer[2] = {0, 0};
uint16_t timer_latch[2] = {0, 0};
uint16_t last_timer[2] = {0, 0};
int next_timer[2] = {-1, -1};
uint8_t shift = 0;
uint8_t auxiliary_control = 0;
uint8_t peripheral_control = 0;
uint8_t interrupt_flags = 0;
uint8_t interrupt_enable = 0;
bool timer_needs_reload = false;
} registers_;
// control state
struct {
bool line_one = false;
bool line_two = false;
} control_inputs_[2];
bool timer_is_running_[2] = {false, false};
bool last_posted_interrupt_status_ = false;
enum InterruptFlag: uint8_t {
CA2ActiveEdge = 1 << 0,
CA1ActiveEdge = 1 << 1,
ShiftRegister = 1 << 2,
CB2ActiveEdge = 1 << 3,
CB1ActiveEdge = 1 << 4,
Timer2 = 1 << 5,
Timer1 = 1 << 6,
};
};
}
}
#endif /* _522Storage_hpp */

View File

@@ -0,0 +1,19 @@
//
// IRQDelegatePortHandler.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 IRQDelegatePortHandler::set_interrupt_delegate(Delegate *delegate) {
delegate_ = delegate;
}
void IRQDelegatePortHandler::set_interrupt_status(bool new_status) {
if(delegate_) delegate_->mos6522_did_change_interrupt_status(this);
}

View File

@@ -12,6 +12,8 @@
#include <cstdint>
#include <cstdio>
#include "../../ClockReceiver/ClockReceiver.hpp"
namespace MOS {
/*!
@@ -30,8 +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 set_register(int address, uint8_t value) {
const uint8_t decodedAddress = address & 0x07;
switch(decodedAddress) {
// Port output
@@ -48,16 +49,13 @@ template <class T> class MOS6532 {
// The timer and edge detect control
case 0x04: case 0x05: case 0x06: case 0x07:
if(address & 0x10)
{
if(address & 0x10) {
timer_.writtenShift = timer_.activeShift = (decodedAddress - 0x04) * 3 + (decodedAddress / 0x07); // i.e. 0, 3, 6, 10
timer_.value = ((unsigned int)(value) << timer_.activeShift) | ((1 << timer_.activeShift)-1);
timer_.value = (static_cast<unsigned int>(value) << timer_.activeShift) ;
timer_.interrupt_enabled = !!(address&0x08);
interrupt_status_ &= ~InterruptFlag::Timer;
evaluate_interrupts();
}
else
{
} else {
a7_interrupt_.enabled = !!(address&0x2);
a7_interrupt_.active_on_positive = !!(address & 0x01);
}
@@ -65,13 +63,11 @@ template <class T> class MOS6532 {
}
}
inline uint8_t get_register(int address)
{
inline uint8_t get_register(int address) {
const uint8_t decodedAddress = address & 0x7;
switch(decodedAddress) {
// Port input
case 0x00: case 0x02:
{
case 0x00: case 0x02: {
const int port = decodedAddress / 2;
uint8_t input = static_cast<T *>(this)->get_port_input(port);
return (input & ~port_[port].output_mask) | (port_[port].output & port_[port].output_mask);
@@ -82,9 +78,8 @@ template <class T> class MOS6532 {
break;
// Timer and interrupt control
case 0x04: case 0x06:
{
uint8_t value = (uint8_t)(timer_.value >> timer_.activeShift);
case 0x04: case 0x06: {
uint8_t value = static_cast<uint8_t>(timer_.value >> timer_.activeShift);
timer_.interrupt_enabled = !!(address&0x08);
interrupt_status_ &= ~InterruptFlag::Timer;
evaluate_interrupts();
@@ -99,8 +94,7 @@ template <class T> class MOS6532 {
}
break;
case 0x05: case 0x07:
{
case 0x05: case 0x07: {
uint8_t value = interrupt_status_;
interrupt_status_ &= ~InterruptFlag::PA7;
evaluate_interrupts();
@@ -112,41 +106,35 @@ template <class T> class MOS6532 {
return 0xff;
}
inline void run_for_cycles(unsigned int number_of_cycles)
{
inline void run_for(const Cycles cycles) {
unsigned int number_of_cycles = static_cast<unsigned int>(cycles.as_int());
// permit counting _to_ zero; counting _through_ zero initiates the other behaviour
if(timer_.value >= number_of_cycles) {
timer_.value -= number_of_cycles;
} else {
number_of_cycles -= timer_.value;
timer_.value = 0x100 - number_of_cycles;
timer_.value = (0x100 - number_of_cycles) & 0xff;
timer_.activeShift = 0;
interrupt_status_ |= InterruptFlag::Timer;
evaluate_interrupts();
}
}
MOS6532() :
interrupt_status_(0),
port_{{.output_mask = 0, .output = 0}, {.output_mask = 0, .output = 0}},
a7_interrupt_({.last_port_value = 0, .enabled = false}),
interrupt_line_(false)
{}
MOS6532() {
timer_.value = static_cast<unsigned int>((rand() & 0xff) << 10);
}
inline void set_port_did_change(int port)
{
if(!port)
{
inline void set_port_did_change(int port) {
if(!port) {
uint8_t new_port_a_value = (get_port_input(0) & ~port_[0].output_mask) | (port_[0].output & port_[0].output_mask);
uint8_t difference = new_port_a_value ^ a7_interrupt_.last_port_value;
a7_interrupt_.last_port_value = new_port_a_value;
if(difference&0x80)
{
if(difference&0x80) {
if(
((new_port_a_value&0x80) && a7_interrupt_.active_on_positive) ||
(!(new_port_a_value&0x80) && !a7_interrupt_.active_on_positive)
)
{
) {
interrupt_status_ |= InterruptFlag::PA7;
evaluate_interrupts();
}
@@ -154,8 +142,7 @@ template <class T> class MOS6532 {
}
}
inline bool get_inerrupt_line()
{
inline bool get_inerrupt_line() {
return interrupt_line_;
}
@@ -164,34 +151,33 @@ template <class T> class MOS6532 {
struct {
unsigned int value;
unsigned int activeShift, writtenShift;
bool interrupt_enabled;
unsigned int activeShift = 10, writtenShift = 10;
bool interrupt_enabled = false;
} timer_;
struct {
bool enabled;
bool active_on_positive;
uint8_t last_port_value;
bool enabled = false;
bool active_on_positive = false;
uint8_t last_port_value = 0;
} a7_interrupt_;
struct {
uint8_t output_mask, output;
uint8_t output_mask = 0, output = 0;
} port_[2];
uint8_t interrupt_status_;
uint8_t interrupt_status_ = 0;
enum InterruptFlag: uint8_t {
Timer = 0x80,
PA7 = 0x40
};
bool interrupt_line_;
bool interrupt_line_ = false;
// expected to be overridden
uint8_t get_port_input(int port) { return 0xff; }
void set_port_output(int port, uint8_t value, uint8_t output_mask) {}
void set_irq_line(bool new_value) {}
inline void evaluate_interrupts()
{
inline void evaluate_interrupts() {
interrupt_line_ =
((interrupt_status_&InterruptFlag::Timer) && timer_.interrupt_enabled) ||
((interrupt_status_&InterruptFlag::PA7) && a7_interrupt_.enabled);

View File

@@ -8,25 +8,22 @@
#include "6560.hpp"
#include <cstring>
using namespace MOS;
Speaker::Speaker() :
volume_(0),
control_registers_{0, 0, 0, 0},
shift_registers_{0, 0, 0, 0},
counters_{2, 1, 0, 0} // create a slight phase offset for the three channels
{}
AudioGenerator::AudioGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue) :
audio_queue_(audio_queue) {}
void Speaker::set_volume(uint8_t volume)
{
enqueue([=]() {
volume_ = volume;
void AudioGenerator::set_volume(uint8_t volume) {
audio_queue_.defer([=]() {
volume_ = static_cast<int16_t>(volume) * range_multiplier_;
});
}
void Speaker::set_control(int channel, uint8_t value)
{
enqueue([=]() {
void AudioGenerator::set_control(int channel, uint8_t value) {
audio_queue_.defer([=]() {
control_registers_[channel] = value;
});
}
@@ -101,17 +98,15 @@ static uint8_t noise_pattern[] = {
#define shift(r) shift_registers_[r] = (shift_registers_[r] << 1) | (((shift_registers_[r]^0x80)&control_registers_[r]) >> 7)
#define increment(r) shift_registers_[r] = (shift_registers_[r]+1)%8191
#define update(r, m, up) counters_[r]++; if((counters_[r] >> m) == 0x80) { up(r); counters_[r] = (unsigned int)(control_registers_[r]&0x7f) << m; }
#define update(r, m, up) counters_[r]++; if((counters_[r] >> m) == 0x80) { up(r); counters_[r] = static_cast<unsigned int>(control_registers_[r]&0x7f) << m; }
// Note on slightly askew test: as far as I can make out, if the value in the register is 0x7f then what's supposed to happen
// is that the 0x7f is loaded, on the next clocked cycle the Vic spots a 0x7f, pumps the output, reloads, etc. No increment
// ever occurs. It's conditional. I don't really want two conditionals if I can avoid it so I'm incrementing regardless and
// testing against 0x80. The effect should be the same: loading with 0x7f means an output update every cycle, loading with 0x7e
// means every second cycle, etc.
void Speaker::get_samples(unsigned int number_of_samples, int16_t *target)
{
for(unsigned int c = 0; c < number_of_samples; c++)
{
void AudioGenerator::get_samples(std::size_t number_of_samples, int16_t *target) {
for(unsigned int c = 0; c < number_of_samples; c++) {
update(0, 2, shift);
update(1, 1, shift);
update(2, 0, shift);
@@ -119,19 +114,17 @@ void Speaker::get_samples(unsigned int number_of_samples, int16_t *target)
// this sums the output of all three sounds channels plus a DC offset for volume;
// TODO: what's the real ratio of this stuff?
target[c] = (
target[c] = static_cast<int16_t>(
(shift_registers_[0]&1) +
(shift_registers_[1]&1) +
(shift_registers_[2]&1) +
((noise_pattern[shift_registers_[3] >> 3] >> (shift_registers_[3]&7))&(control_registers_[3] >> 7)&1)
) * volume_ * 700 + volume_ * 44;
) * volume_ + (volume_ >> 4);
}
}
void Speaker::skip_samples(unsigned int number_of_samples)
{
for(unsigned int c = 0; c < number_of_samples; c++)
{
void AudioGenerator::skip_samples(std::size_t number_of_samples) {
for(unsigned int c = 0; c < number_of_samples; c++) {
update(0, 2, shift);
update(1, 1, shift);
update(2, 0, shift);
@@ -139,6 +132,10 @@ void Speaker::skip_samples(unsigned int number_of_samples)
}
}
void AudioGenerator::set_sample_volume_range(std::int16_t range) {
range_multiplier_ = static_cast<int16_t>(range / 64);
}
#undef shift
#undef increment
#undef update

View File

@@ -9,27 +9,35 @@
#ifndef _560_hpp
#define _560_hpp
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../Concurrency/AsyncTaskQueue.hpp"
#include "../../Outputs/CRT/CRT.hpp"
#include "../../Outputs/Speaker.hpp"
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
namespace MOS {
// audio state
class Speaker: public ::Outputs::Filter<Speaker> {
class AudioGenerator: public ::Outputs::Speaker::SampleSource {
public:
Speaker();
AudioGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue);
void set_volume(uint8_t volume);
void set_control(int channel, uint8_t value);
void get_samples(unsigned int number_of_samples, int16_t *target);
void skip_samples(unsigned int number_of_samples);
// For ::SampleSource.
void get_samples(std::size_t number_of_samples, int16_t *target);
void skip_samples(std::size_t number_of_samples);
void set_sample_volume_range(std::int16_t range);
private:
unsigned int counters_[4];
unsigned int shift_registers_[4];
uint8_t control_registers_[4];
uint8_t volume_;
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
unsigned int counters_[4] = {2, 1, 0, 0}; // create a slight phase offset for the three channels
unsigned int shift_registers_[4] = {0, 0, 0, 0};
uint8_t control_registers_[4] = {0, 0, 0, 0};
int16_t volume_ = 0;
int16_t range_multiplier_ = 1;
};
/*!
@@ -43,37 +51,42 @@ class Speaker: public ::Outputs::Filter<Speaker> {
template <class T> class MOS6560 {
public:
MOS6560() :
crt_(new Outputs::CRT::CRT(65*4, 4, Outputs::CRT::NTSC60, 1)),
speaker_(new Speaker),
horizontal_counter_(0),
vertical_counter_(0),
cycles_since_speaker_update_(0),
is_odd_frame_(false),
is_odd_line_(false)
crt_(new Outputs::CRT::CRT(65*4, 4, Outputs::CRT::DisplayType::NTSC60, 2)),
audio_generator_(audio_queue_),
speaker_(audio_generator_)
{
crt_->set_composite_sampling_function(
"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
crt_->set_svideo_sampling_function(
"vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase)"
"{"
"uint c = texture(texID, coordinate).r;"
"float y = float(c >> 4) / 4.0;"
"uint yC = c & 15u;"
"float phaseOffset = 6.283185308 * float(yC) / 16.0;"
"vec2 yc = texture(texID, coordinate).rg / vec2(255.0);"
"float chroma = cos(phase + phaseOffset);"
"return mix(y, step(yC, 14) * chroma, amplitude);"
"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);
// default to NTSC
set_output_mode(OutputMode::NTSC);
}
void set_clock_rate(double clock_rate)
{
speaker_->set_input_rate((float)(clock_rate / 4.0));
~MOS6560() {
audio_queue_.flush();
}
std::shared_ptr<Outputs::CRT::CRT> get_crt() { return crt_; }
std::shared_ptr<Outputs::Speaker> get_speaker() { return speaker_; }
void set_clock_rate(double clock_rate) {
speaker_.set_input_rate(static_cast<float>(clock_rate / 4.0));
}
Outputs::CRT::CRT *get_crt() { return crt_.get(); }
Outputs::Speaker::Speaker *get_speaker() { return &speaker_; }
void set_high_frequency_cutoff(float cutoff) {
speaker_.set_high_frequency_cutoff(cutoff);
}
enum OutputMode {
PAL, NTSC
@@ -82,29 +95,39 @@ template <class T> class MOS6560 {
/*!
Sets the output mode to either PAL or NTSC.
*/
void set_output_mode(OutputMode output_mode)
{
void set_output_mode(OutputMode output_mode) {
output_mode_ = output_mode;
uint8_t luminances[16] = { // range is 04
0, 4, 1, 3, 2, 2, 1, 3,
2, 1, 2, 1, 2, 3, 2, 3
// Luminances are encoded trivially: on a 0255 scale.
const uint8_t luminances[16] = {
0, 255, 60, 189,
80, 144, 40, 227,
90, 161, 207, 227,
200, 196, 160, 196
};
uint8_t pal_chrominances[16] = { // range is 015; 15 is a special case meaning "no chrominance"
15, 15, 5, 13, 2, 10, 0, 8,
6, 7, 5, 13, 2, 10, 0, 8,
// Chrominances are encoded such that 0128 is a complete revolution of phase;
// anything above 191 disables the colour subcarrier. Phase is relative to the
// colour burst, so 0 is green.
const uint8_t pal_chrominances[16] = {
255, 255, 37, 101,
19, 86, 123, 59,
46, 53, 37, 101,
19, 86, 123, 59,
};
uint8_t ntsc_chrominances[16] = {
15, 15, 2, 10, 4, 12, 6, 14,
0, 8, 2, 10, 4, 12, 6, 14,
const uint8_t ntsc_chrominances[16] = {
255, 255, 7, 71,
25, 86, 48, 112,
0, 119, 7, 71,
25, 86, 48, 112,
};
uint8_t *chrominances;
const uint8_t *chrominances;
Outputs::CRT::DisplayType display_type;
switch(output_mode)
{
case OutputMode::PAL:
switch(output_mode) {
default:
chrominances = pal_chrominances;
display_type = Outputs::CRT::PAL50;
display_type = Outputs::CRT::DisplayType::PAL50;
timing_.cycles_per_line = 71;
timing_.line_counter_increment_offset = 0;
timing_.lines_per_progressive_field = 312;
@@ -113,7 +136,7 @@ template <class T> class MOS6560 {
case OutputMode::NTSC:
chrominances = ntsc_chrominances;
display_type = Outputs::CRT::NTSC60;
display_type = Outputs::CRT::DisplayType::NTSC60;
timing_.cycles_per_line = 65;
timing_.line_counter_increment_offset = 65 - 33; // TODO: this is a bit of a hack; separate vertical and horizontal counting
timing_.lines_per_progressive_field = 261;
@@ -121,45 +144,41 @@ template <class T> class MOS6560 {
break;
}
crt_->set_new_display_type((unsigned int)(timing_.cycles_per_line*4), display_type);
// crt_->set_visible_area(Outputs::CRT::Rect(0.1f, 0.1f, 0.8f, 0.8f));
crt_->set_new_display_type(static_cast<unsigned int>(timing_.cycles_per_line*4), display_type);
// switch(output_mode)
// {
// case OutputMode::PAL:
// crt_->set_visible_area(crt_->get_rect_for_area(16, 237, 15*4, 55*4, 4.0f / 3.0f));
// break;
// case OutputMode::NTSC:
// crt_->set_visible_area(crt_->get_rect_for_area(16, 237, 11*4, 55*4, 4.0f / 3.0f));
// break;
// }
switch(output_mode) {
case OutputMode::PAL:
crt_->set_visible_area(Outputs::CRT::Rect(0.1f, 0.05f, 0.9f, 0.9f));
break;
case OutputMode::NTSC:
crt_->set_visible_area(Outputs::CRT::Rect(0.05f, 0.05f, 0.9f, 0.9f));
break;
}
for(int c = 0; c < 16; c++)
{
colours_[c] = (uint8_t)((luminances[c] << 4) | chrominances[c]);
for(int c = 0; c < 16; c++) {
uint8_t *colour = reinterpret_cast<uint8_t *>(&colours_[c]);
colour[0] = luminances[c];
colour[1] = chrominances[c];
}
}
/*!
Runs for cycles. Derr.
*/
inline void run_for_cycles(unsigned int number_of_cycles)
{
inline void run_for(const Cycles cycles) {
// keep track of the amount of time since the speaker was updated; lazy updates are applied
cycles_since_speaker_update_ += number_of_cycles;
cycles_since_speaker_update_ += cycles;
while(number_of_cycles--)
{
int number_of_cycles = cycles.as_int();
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_;
// keep track of internal time relative to this scanline
horizontal_counter_++;
full_frame_counter_++;
if(horizontal_counter_ == timing_.cycles_per_line)
{
if(horizontal_drawing_latch_)
{
if(horizontal_counter_ == timing_.cycles_per_line) {
if(horizontal_drawing_latch_) {
current_character_row_++;
if(
(current_character_row_ == 16) ||
@@ -179,8 +198,7 @@ template <class T> class MOS6560 {
horizontal_drawing_latch_ = false;
vertical_counter_ ++;
if(vertical_counter_ == (registers_.interlaced ? (is_odd_frame_ ? 262 : 263) : timing_.lines_per_progressive_field))
{
if(vertical_counter_ == (registers_.interlaced ? (is_odd_frame_ ? 262 : 263) : timing_.lines_per_progressive_field)) {
vertical_counter_ = 0;
full_frame_counter_ = 0;
@@ -198,11 +216,9 @@ template <class T> class MOS6560 {
horizontal_drawing_latch_ |= vertical_drawing_latch_ && (horizontal_counter_ == registers_.first_column_location);
if(pixel_line_cycle_ >= 0) pixel_line_cycle_++;
switch(pixel_line_cycle_)
{
switch(pixel_line_cycle_) {
case -1:
if(horizontal_drawing_latch_)
{
if(horizontal_drawing_latch_) {
pixel_line_cycle_ = 0;
video_matrix_address_counter_ = base_video_matrix_address_counter_;
}
@@ -213,15 +229,11 @@ template <class T> class MOS6560 {
}
uint16_t fetch_address = 0x1c;
if(column_counter_ >= 0 && column_counter_ < columns_this_line_*2)
{
if(column_counter_&1)
{
if(column_counter_ >= 0 && column_counter_ < columns_this_line_*2) {
if(column_counter_&1) {
fetch_address = registers_.character_cell_start_address + (character_code_*(registers_.tall_characters ? 16 : 8)) + current_character_row_;
}
else
{
fetch_address = (uint16_t)(registers_.video_matrix_start_address + video_matrix_address_counter_);
} else {
fetch_address = static_cast<uint16_t>(registers_.video_matrix_start_address + video_matrix_address_counter_);
video_matrix_address_counter_++;
if(
(current_character_row_ == 15) ||
@@ -244,8 +256,7 @@ template <class T> class MOS6560 {
// determine output state; colour burst and sync timing are currently a guess
if(horizontal_counter_ > timing_.cycles_per_line-4) this_state_ = State::ColourBurst;
else if(horizontal_counter_ > timing_.cycles_per_line-7) this_state_ = State::Sync;
else
{
else {
this_state_ = (column_counter_ >= 0 && column_counter_ < columns_this_line_*2) ? State::Pixels : State::Border;
}
@@ -262,12 +273,10 @@ template <class T> class MOS6560 {
this_state_ = State::Sync;
// update the CRT
if(this_state_ != output_state_)
{
switch(output_state_)
{
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, 0); 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, 1); break;
}
@@ -275,32 +284,24 @@ template <class T> class MOS6560 {
cycles_in_state_ = 0;
pixel_pointer = nullptr;
if(output_state_ == State::Pixels)
{
pixel_pointer = crt_->allocate_write_area(260);
if(output_state_ == State::Pixels) {
pixel_pointer = reinterpret_cast<uint16_t *>(crt_->allocate_write_area(260));
}
}
cycles_in_state_++;
if(this_state_ == State::Pixels)
{
if(column_counter_&1)
{
if(this_state_ == State::Pixels) {
if(column_counter_&1) {
character_value_ = pixel_data;
if(pixel_pointer)
{
uint8_t cell_colour = colours_[character_colour_ & 0x7];
if(!(character_colour_&0x8))
{
uint8_t colours[2];
if(registers_.invertedCells)
{
if(pixel_pointer) {
uint16_t cell_colour = colours_[character_colour_ & 0x7];
if(!(character_colour_&0x8)) {
uint16_t colours[2];
if(registers_.invertedCells) {
colours[0] = cell_colour;
colours[1] = registers_.backgroundColour;
}
else
{
} else {
colours[0] = registers_.backgroundColour;
colours[1] = cell_colour;
}
@@ -312,10 +313,8 @@ template <class T> class MOS6560 {
pixel_pointer[5] = colours[(character_value_ >> 2)&1];
pixel_pointer[6] = colours[(character_value_ >> 1)&1];
pixel_pointer[7] = colours[(character_value_ >> 0)&1];
}
else
{
uint8_t colours[4] = {registers_.backgroundColour, registers_.borderColour, cell_colour, registers_.auxiliary_colour};
} else {
uint16_t colours[4] = {registers_.backgroundColour, registers_.borderColour, cell_colour, registers_.auxiliary_colour};
pixel_pointer[0] =
pixel_pointer[1] = colours[(character_value_ >> 6)&3];
pixel_pointer[2] =
@@ -325,11 +324,10 @@ template <class T> class MOS6560 {
pixel_pointer[6] =
pixel_pointer[7] = colours[(character_value_ >> 0)&3];
}
pixel_pointer += 8;
}
}
else
{
} else {
character_code_ = pixel_data;
character_colour_ = colour_data;
}
@@ -342,17 +340,18 @@ template <class T> class MOS6560 {
/*!
Causes the 6560 to flush as much pending CRT and speaker communications as possible.
*/
inline void synchronise() { update_audio(); speaker_->flush(); }
inline void flush() {
update_audio();
audio_queue_.perform();
}
/*!
Writes to a 6560 register.
*/
void set_register(int address, uint8_t value)
{
void set_register(int address, uint8_t value) {
address &= 0xf;
registers_.direct_values[address] = value;
switch(address)
{
switch(address) {
case 0x0:
registers_.interlaced = !!(value&0x80) && timing_.supports_interlacing;
registers_.first_column_location = value & 0x7f;
@@ -364,7 +363,7 @@ template <class T> class MOS6560 {
case 0x2:
registers_.number_of_columns = value & 0x7f;
registers_.video_matrix_start_address = (uint16_t)((registers_.video_matrix_start_address & 0x3c00) | ((value & 0x80) << 2));
registers_.video_matrix_start_address = static_cast<uint16_t>((registers_.video_matrix_start_address & 0x3c00) | ((value & 0x80) << 2));
break;
case 0x3:
@@ -373,8 +372,8 @@ template <class T> class MOS6560 {
break;
case 0x5:
registers_.character_cell_start_address = (uint16_t)((value & 0x0f) << 10);
registers_.video_matrix_start_address = (uint16_t)((registers_.video_matrix_start_address & 0x0200) | ((value & 0xf0) << 6));
registers_.character_cell_start_address = static_cast<uint16_t>((value & 0x0f) << 10);
registers_.video_matrix_start_address = static_cast<uint16_t>((registers_.video_matrix_start_address & 0x0200) | ((value & 0xf0) << 6));
break;
case 0xa:
@@ -382,20 +381,18 @@ template <class T> class MOS6560 {
case 0xc:
case 0xd:
update_audio();
speaker_->set_control(address - 0xa, value);
audio_generator_.set_control(address - 0xa, value);
break;
case 0xe:
update_audio();
registers_.auxiliary_colour = colours_[value >> 4];
speaker_->set_volume(value & 0xf);
audio_generator_.set_volume(value & 0xf);
break;
case 0xf:
{
uint8_t new_border_colour = colours_[value & 0x07];
if(this_state_ == State::Border && new_border_colour != registers_.borderColour)
{
case 0xf: {
uint16_t new_border_colour = colours_[value & 0x07];
if(this_state_ == State::Border && new_border_colour != registers_.borderColour) {
output_border(cycles_in_state_ * 4);
cycles_in_state_ = 0;
}
@@ -415,27 +412,26 @@ template <class T> class MOS6560 {
/*
Reads from a 6560 register.
*/
uint8_t get_register(int address)
{
uint8_t get_register(int address) {
address &= 0xf;
int current_line = (full_frame_counter_ + timing_.line_counter_increment_offset) / timing_.cycles_per_line;
switch(address)
{
switch(address) {
default: return registers_.direct_values[address];
case 0x03: return (uint8_t)(current_line << 7) | (registers_.direct_values[3] & 0x7f);
case 0x03: return static_cast<uint8_t>(current_line << 7) | (registers_.direct_values[3] & 0x7f);
case 0x04: return (current_line >> 1) & 0xff;
}
}
private:
std::shared_ptr<Outputs::CRT::CRT> crt_;
std::unique_ptr<Outputs::CRT::CRT> crt_;
std::shared_ptr<Speaker> speaker_;
unsigned int cycles_since_speaker_update_;
void update_audio()
{
speaker_->run_for_cycles(cycles_since_speaker_update_ >> 2);
cycles_since_speaker_update_ &= 3;
Concurrency::DeferringAsyncTaskQueue audio_queue_;
AudioGenerator audio_generator_;
Outputs::Speaker::LowpassSpeaker<AudioGenerator> speaker_;
Cycles cycles_since_speaker_update_;
void update_audio() {
speaker_.run_for(audio_queue_, Cycles(cycles_since_speaker_update_.divide(Cycles(4))));
}
// register state
@@ -444,7 +440,7 @@ template <class T> class MOS6560 {
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;
uint8_t backgroundColour, borderColour, auxiliary_colour;
uint16_t backgroundColour, borderColour, auxiliary_colour;
bool invertedCells;
uint8_t direct_values[16];
@@ -457,7 +453,7 @@ template <class T> class MOS6560 {
unsigned int cycles_in_state_;
// counters that cover an entire field
int horizontal_counter_, vertical_counter_, full_frame_counter_;
int horizontal_counter_ = 0, vertical_counter_ = 0, full_frame_counter_;
// latches dictating start and length of drawing
bool vertical_drawing_latch_, horizontal_drawing_latch_;
@@ -472,15 +468,14 @@ template <class T> class MOS6560 {
// data latched from the bus
uint8_t character_code_, character_colour_, character_value_;
bool is_odd_frame_, is_odd_line_;
bool is_odd_frame_ = false, is_odd_line_ = false;
// lookup table from 6560 colour index to appropriate PAL/NTSC value
uint8_t colours_[16];
uint16_t colours_[16];
uint8_t *pixel_pointer;
void output_border(unsigned int number_of_cycles)
{
uint8_t *colour_pointer = crt_->allocate_write_area(1);
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));
if(colour_pointer) *colour_pointer = registers_.borderColour;
crt_->output_level(number_of_cycles);
}

View File

@@ -0,0 +1,275 @@
//
// CRTC6845.hpp
// Clock Signal
//
// Created by Thomas Harte on 31/07/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef CRTC6845_hpp
#define CRTC6845_hpp
#include "../../ClockReceiver/ClockReceiver.hpp"
#include <cstdint>
#include <cstdio>
namespace Motorola {
namespace CRTC {
struct BusState {
bool display_enable = false;
bool hsync = false;
bool vsync = false;
bool cursor = false;
uint16_t refresh_address = 0;
uint16_t row_address = 0;
};
class BusHandler {
public:
/*!
Performs the first phase of a 6845 bus cycle; this is the phase in which it is intended that
systems using the 6845 respect the bus state and produce pixels, sync or whatever they require.
*/
void perform_bus_cycle_phase1(const BusState &) {}
/*!
Performs the second phase of a 6845 bus cycle. Some bus state — including sync — is updated
directly after phase 1 and hence is visible to an observer during phase 2. Handlers may therefore
implement @c perform_bus_cycle_phase2 to be notified of the availability of that state without
having to wait until the next cycle has begun.
*/
void perform_bus_cycle_phase2(const BusState &) {}
};
enum Personality {
HD6845S, // Type 0 in CPC parlance. Zero-width HSYNC available, no status, programmable VSYNC length.
// Considered exactly identical to the UM6845, so this enum covers both.
UM6845R, // Type 1 in CPC parlance. Status register, fixed-length VSYNC.
MC6845, // Type 2. No status register, fixed-length VSYNC, no zero-length HSYNC.
AMS40226 // Type 3. Status is get register, fixed-length VSYNC, no zero-length HSYNC.
};
// TODO UM6845R and R12/R13; see http://www.cpcwiki.eu/index.php/CRTC#CRTC_Differences
template <class T> class CRTC6845 {
public:
CRTC6845(Personality p, T &bus_handler) noexcept :
personality_(p), bus_handler_(bus_handler), status_(0) {}
void select_register(uint8_t r) {
selected_register_ = r;
}
uint8_t get_status() {
switch(personality_) {
case UM6845R: return status_ | (bus_state_.vsync ? 0x20 : 0x00);
case AMS40226: return get_register();
default: return 0xff;
}
return 0xff;
}
uint8_t get_register() {
if(selected_register_ == 31) status_ &= ~0x80;
if(selected_register_ == 16 || selected_register_ == 17) status_ &= ~0x40;
if(personality_ == UM6845R && selected_register_ == 31) return dummy_register_;
if(selected_register_ < 12 || selected_register_ > 17) return 0xff;
return registers_[selected_register_];
}
void set_register(uint8_t value) {
static uint8_t masks[] = {
0xff, 0xff, 0xff, 0xff, 0x7f, 0x1f, 0x7f, 0x7f,
0xff, 0x1f, 0x7f, 0x1f, 0x3f, 0xff, 0x3f, 0xff
};
// Per CPC documentation, skew doesn't work on a "type 1 or 2", i.e. an MC6845 or a UM6845R.
if(selected_register_ == 8 && personality_ != UM6845R && personality_ != MC6845) {
switch((value >> 4)&3) {
default: display_skew_mask_ = 1; break;
case 1: display_skew_mask_ = 2; break;
case 2: display_skew_mask_ = 4; break;
}
}
if(selected_register_ < 16) {
registers_[selected_register_] = value & masks[selected_register_];
}
if(selected_register_ == 31 && personality_ == UM6845R) {
dummy_register_ = value;
}
}
void trigger_light_pen() {
registers_[17] = bus_state_.refresh_address & 0xff;
registers_[16] = bus_state_.refresh_address >> 8;
status_ |= 0x40;
}
void run_for(Cycles cycles) {
int cyles_remaining = cycles.as_int();
while(cyles_remaining--) {
// check for end of visible characters
if(character_counter_ == registers_[1]) {
// TODO: consider skew in character_is_visible_. Or maybe defer until perform_bus_cycle?
character_is_visible_ = false;
end_of_line_address_ = bus_state_.refresh_address;
}
perform_bus_cycle_phase1();
bus_state_.refresh_address = (bus_state_.refresh_address + 1) & 0x3fff;
// check for end-of-line
if(character_counter_ == registers_[0]) {
character_counter_ = 0;
do_end_of_line();
character_is_visible_ = true;
} else {
// increment counter
character_counter_++;
}
// check for start of horizontal sync
if(character_counter_ == registers_[2]) {
hsync_counter_ = 0;
bus_state_.hsync = true;
}
// check for end of horizontal sync; note that a sync time of zero will result in an immediate
// cancellation of the plan to perform sync if this is an HD6845S or UM6845R; otherwise zero
// will end up counting as 16 as it won't be checked until after overflow.
if(bus_state_.hsync) {
switch(personality_) {
case HD6845S:
case UM6845R:
bus_state_.hsync = hsync_counter_ != (registers_[3] & 15);
hsync_counter_ = (hsync_counter_ + 1) & 15;
break;
default:
hsync_counter_ = (hsync_counter_ + 1) & 15;
bus_state_.hsync = hsync_counter_ != (registers_[3] & 15);
break;
}
}
perform_bus_cycle_phase2();
}
}
const BusState &get_bus_state() const {
return bus_state_;
}
private:
inline void perform_bus_cycle_phase1() {
// Skew theory of operation: keep a history of the last three states, and apply whichever is selected.
character_is_visible_shifter_ = (character_is_visible_shifter_ << 1) | static_cast<unsigned int>(character_is_visible_);
bus_state_.display_enable = (static_cast<int>(character_is_visible_shifter_) & display_skew_mask_) && line_is_visible_;
bus_handler_.perform_bus_cycle_phase1(bus_state_);
}
inline void perform_bus_cycle_phase2() {
bus_handler_.perform_bus_cycle_phase2(bus_state_);
}
inline void do_end_of_line() {
// check for end of vertical sync
if(bus_state_.vsync) {
vsync_counter_ = (vsync_counter_ + 1) & 15;
// on the UM6845R and AMS40226, honour the programmed vertical sync time; on the other CRTCs
// always use a vertical sync count of 16.
switch(personality_) {
case HD6845S:
case AMS40226:
bus_state_.vsync = vsync_counter_ != (registers_[3] >> 4);
break;
default:
bus_state_.vsync = vsync_counter_ != 0;
break;
}
}
if(is_in_adjustment_period_) {
line_counter_++;
if(line_counter_ == registers_[5]) {
is_in_adjustment_period_ = false;
do_end_of_frame();
}
} else {
// advance vertical counter
if(bus_state_.row_address == registers_[9]) {
bus_state_.row_address = 0;
line_address_ = end_of_line_address_;
// check for entry into the overflow area
if(line_counter_ == registers_[4]) {
if(registers_[5]) {
line_counter_ = 0;
is_in_adjustment_period_ = true;
} else {
do_end_of_frame();
}
} else {
line_counter_ = (line_counter_ + 1) & 0x7f;
// check for start of vertical sync
if(line_counter_ == registers_[7]) {
bus_state_.vsync = true;
vsync_counter_ = 0;
}
// check for end of visible lines
if(line_counter_ == registers_[6]) {
line_is_visible_ = false;
}
}
} else {
bus_state_.row_address = (bus_state_.row_address + 1) & 0x1f;
}
}
bus_state_.refresh_address = line_address_;
character_counter_ = 0;
character_is_visible_ = (registers_[1] != 0);
}
inline void do_end_of_frame() {
line_counter_ = 0;
line_is_visible_ = true;
line_address_ = static_cast<uint16_t>((registers_[12] << 8) | registers_[13]);
bus_state_.refresh_address = line_address_;
}
Personality personality_;
T &bus_handler_;
BusState bus_state_;
uint8_t registers_[18] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
uint8_t dummy_register_ = 0;
int selected_register_ = 0;
uint8_t character_counter_ = 0;
uint8_t line_counter_ = 0;
bool character_is_visible_ = false, line_is_visible_ = false;
int hsync_counter_ = 0;
int vsync_counter_ = 0;
bool is_in_adjustment_period_ = false;
uint16_t line_address_ = 0;
uint16_t end_of_line_address_ = 0;
uint8_t status_ = 0;
int display_skew_mask_ = 1;
unsigned int character_is_visible_shifter_ = 0;
};
}
}
#endif /* CRTC6845_hpp */

92
Components/8255/i8255.hpp Normal file
View File

@@ -0,0 +1,92 @@
//
// i8255.hpp
// Clock Signal
//
// Created by Thomas Harte on 01/08/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef i8255_hpp
#define i8255_hpp
namespace Intel {
namespace i8255 {
class PortHandler {
public:
void set_value(int port, uint8_t value) {}
uint8_t get_value(int port) { return 0xff; }
};
// TODO: Modes 1 and 2.
template <class T> class i8255 {
public:
i8255(T &port_handler) : control_(0), outputs_{0, 0, 0}, port_handler_(port_handler) {}
/*!
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) {
switch(address & 3) {
case 0:
if(!(control_ & 0x10)) {
// TODO: so what would output be when switching from input to output mode?
outputs_[0] = value; port_handler_.set_value(0, value);
}
break;
case 1:
if(!(control_ & 0x02)) {
outputs_[1] = value; port_handler_.set_value(1, value);
}
break;
case 2: outputs_[2] = value; port_handler_.set_value(2, value); break;
case 3:
if(value & 0x80) {
control_ = value;
} else {
if(value & 1) {
outputs_[2] |= 1 << ((value >> 1)&7);
} else {
outputs_[2] &= ~(1 << ((value >> 1)&7));
}
}
update_outputs();
break;
}
}
/*!
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) {
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];
case 2: {
if(!(control_ & 0x09)) return outputs_[2];
uint8_t input = port_handler_.get_value(2);
return ((control_ & 0x01) ? (input & 0x0f) : (outputs_[2] & 0x0f)) | ((control_ & 0x08) ? (input & 0xf0) : (outputs_[2] & 0xf0));
}
case 3: return control_;
}
return 0xff;
}
private:
void update_outputs() {
if(!(control_ & 0x10)) port_handler_.set_value(0, outputs_[0]);
if(!(control_ & 0x02)) port_handler_.set_value(1, outputs_[1]);
port_handler_.set_value(2, outputs_[2]);
}
uint8_t control_;
uint8_t outputs_[3];
T &port_handler_;
};
}
}
#endif /* i8255_hpp */

872
Components/8272/i8272.cpp Normal file
View File

@@ -0,0 +1,872 @@
//
// i8272.cpp
// Clock Signal
//
// Created by Thomas Harte on 05/08/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "i8272.hpp"
//#include "../../Storage/Disk/Encodings/MFM/Encoder.hpp"
#include <cstdio>
using namespace Intel::i8272;
#define SetDataRequest() (main_status_ |= 0x80)
#define ResetDataRequest() (main_status_ &= ~0x80)
#define DataRequest() (main_status_ & 0x80)
#define SetDataDirectionToProcessor() (main_status_ |= 0x40)
#define SetDataDirectionFromProcessor() (main_status_ &= ~0x40)
#define DataDirectionToProcessor() (main_status_ & 0x40)
#define SetNonDMAExecution() (main_status_ |= 0x20)
#define ResetNonDMAExecution() (main_status_ &= ~0x20)
#define SetBusy() (main_status_ |= 0x10)
#define ResetBusy() (main_status_ &= ~0x10)
#define Busy() (main_status_ & 0x10)
#define SetAbnormalTermination() (status_[0] |= 0x40)
#define SetInvalidCommand() (status_[0] |= 0x80)
#define SetReadyChanged() (status_[0] |= 0xc0)
#define SetSeekEnd() (status_[0] |= 0x20)
#define SetEquipmentCheck() (status_[0] |= 0x10)
#define SetNotReady() (status_[0] |= 0x08)
#define SetSide2() (status_[0] |= 0x04)
#define SetEndOfCylinder() (status_[1] |= 0x80)
#define SetDataError() (status_[1] |= 0x20)
#define SetOverrun() (status_[1] |= 0x10)
#define SetNoData() (status_[1] |= 0x04)
#define SetNotWriteable() (status_[1] |= 0x02)
#define SetMissingAddressMark() (status_[1] |= 0x01)
#define SetControlMark() (status_[2] |= 0x40)
#define ClearControlMark() (status_[2] &= ~0x40)
#define ControlMark() (status_[2] & 0x40)
#define SetDataFieldDataError() (status_[2] |= 0x20)
#define SetWrongCyinder() (status_[2] |= 0x10)
#define SetScanEqualHit() (status_[2] |= 0x08)
#define SetScanNotSatisfied() (status_[2] |= 0x04)
#define SetBadCylinder() (status_[2] |= 0x02)
#define SetMissingDataAddressMark() (status_[2] |= 0x01)
namespace {
const uint8_t CommandReadData = 0x06;
const uint8_t CommandReadDeletedData = 0x0c;
const uint8_t CommandWriteData = 0x05;
const uint8_t CommandWriteDeletedData = 0x09;
const uint8_t CommandReadTrack = 0x02;
const uint8_t CommandReadID = 0x0a;
const uint8_t CommandFormatTrack = 0x0d;
const uint8_t CommandScanLow = 0x11;
const uint8_t CommandScanLowOrEqual = 0x19;
const uint8_t CommandScanHighOrEqual = 0x1d;
const uint8_t CommandRecalibrate = 0x07;
const uint8_t CommandSeek = 0x0f;
const uint8_t CommandSenseInterruptStatus = 0x08;
const uint8_t CommandSpecify = 0x03;
const uint8_t CommandSenseDriveStatus = 0x04;
}
i8272::i8272(BusHandler &bus_handler, Cycles clock_rate) :
Storage::Disk::MFMController(clock_rate),
bus_handler_(bus_handler) {
posit_event(static_cast<int>(Event8272::CommandByte));
}
bool i8272::is_sleeping() {
return is_sleeping_ && Storage::Disk::MFMController::is_sleeping();
}
void i8272::run_for(Cycles cycles) {
Storage::Disk::MFMController::run_for(cycles);
if(is_sleeping_) return;
// check for an expired timer
if(delay_time_ > 0) {
if(cycles.as_int() >= delay_time_) {
delay_time_ = 0;
posit_event(static_cast<int>(Event8272::Timer));
} else {
delay_time_ -= cycles.as_int();
}
}
// update seek status of any drives presently seeking
if(drives_seeking_) {
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 %= (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);
select_drive(c);
get_drive().step(direction);
if(drives_[c].target_head_position >= 0) drives_[c].head_position += direction;
// Check for completion.
if(seek_is_satisfied(c)) {
drives_[c].phase = Drive::CompletedSeeking;
drives_seeking_--;
break;
}
}
drives_left--;
if(!drives_left) break;
}
}
}
// check for any head unloads
if(head_timers_running_) {
int timers_left = head_timers_running_;
for(int c = 0; c < 8; c++) {
int drive = (c >> 1);
int head = c&1;
if(drives_[drive].head_unload_delay[head] > 0) {
if(cycles.as_int() >= 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();
}
timers_left--;
if(!timers_left) break;
}
}
}
// check for busy plus ready disabled
if(is_executing_ && !get_drive().get_is_ready()) {
posit_event(static_cast<int>(Event8272::NoLongerReady));
}
is_sleeping_ = !delay_time_ && !drives_seeking_ && !head_timers_running_;
if(is_sleeping_) update_sleep_observer();
}
void i8272::set_register(int address, uint8_t value) {
// don't consider attempted sets to the status register
if(!address) return;
// if not ready for commands, do nothing
if(!DataRequest() || DataDirectionToProcessor()) return;
if(expects_input_) {
input_ = value;
has_input_ = true;
ResetDataRequest();
} else {
// accumulate latest byte in the command byte sequence
command_.push_back(value);
posit_event(static_cast<int>(Event8272::CommandByte));
}
}
uint8_t i8272::get_register(int address) {
if(address) {
if(result_stack_.empty()) return 0xff;
uint8_t result = result_stack_.back();
result_stack_.pop_back();
if(result_stack_.empty()) posit_event(static_cast<int>(Event8272::ResultEmpty));
return result;
} else {
return main_status_;
}
}
#define BEGIN_SECTION() switch(resume_point_) { default:
#define END_SECTION() }
#define MS_TO_CYCLES(x) x * 8000
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(mask); return; case __LINE__:
#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(Event8272::Timer); delay_time_ = MS_TO_CYCLES(ms); is_sleeping_ = false; update_sleep_observer(); case __LINE__: if(delay_time_) return;
#define PASTE(x, y) x##y
#define CONCAT(x, y) PASTE(x, y)
#define FIND_HEADER() \
set_data_mode(DataMode::Scanning); \
CONCAT(find_header, __LINE__): WAIT_FOR_EVENT(static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole)); \
if(event_type == static_cast<int>(Event::IndexHole)) { index_hole_limit_--; } \
else if(get_latest_token().type == Token::ID) goto CONCAT(header_found, __LINE__); \
\
if(index_hole_limit_) goto CONCAT(find_header, __LINE__); \
CONCAT(header_found, __LINE__): (void)0;\
#define FIND_DATA() \
set_data_mode(DataMode::Scanning); \
CONCAT(find_data, __LINE__): WAIT_FOR_EVENT(static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole)); \
if(event_type == static_cast<int>(Event::Token)) { \
if(get_latest_token().type == Token::Byte || get_latest_token().type == Token::Sync) goto CONCAT(find_data, __LINE__); \
}
#define READ_HEADER() \
distance_into_section_ = 0; \
set_data_mode(DataMode::Reading); \
CONCAT(read_header, __LINE__): WAIT_FOR_EVENT(Event::Token); \
header_[distance_into_section_] = get_latest_token().byte_value; \
distance_into_section_++; \
if(distance_into_section_ < 6) goto CONCAT(read_header, __LINE__); \
#define SET_DRIVE_HEAD_MFM() \
active_drive_ = command_[1]&3; \
active_head_ = (command_[1] >> 2)&1; \
status_[0] = (command_[1]&7); \
select_drive(active_drive_); \
get_drive().set_head(active_head_); \
set_is_double_density(command_[0] & 0x40);
#define WAIT_FOR_BYTES(n) \
distance_into_section_ = 0; \
CONCAT(wait_bytes, __LINE__): WAIT_FOR_EVENT(Event::Token); \
if(get_latest_token().type == Token::Byte) distance_into_section_++; \
if(distance_into_section_ < (n)) goto CONCAT(wait_bytes, __LINE__);
#define LOAD_HEAD() \
if(!drives_[active_drive_].head_is_loaded[active_head_]) { \
drives_[active_drive_].head_is_loaded[active_head_] = true; \
WAIT_FOR_TIME(head_load_time_); \
} else { \
if(drives_[active_drive_].head_unload_delay[active_head_] > 0) { \
drives_[active_drive_].head_unload_delay[active_head_] = 0; \
head_timers_running_--; \
} \
}
#define SCHEDULE_HEAD_UNLOAD() \
if(drives_[active_drive_].head_is_loaded[active_head_]) {\
if(drives_[active_drive_].head_unload_delay[active_head_] == 0) { \
head_timers_running_++; \
is_sleeping_ = false; \
update_sleep_observer(); \
} \
drives_[active_drive_].head_unload_delay[active_head_] = MS_TO_CYCLES(head_unload_time_);\
}
void i8272::posit_event(int event_type) {
if(event_type == static_cast<int>(Event::IndexHole)) index_hole_count_++;
if(event_type == static_cast<int>(Event8272::NoLongerReady)) {
SetNotReady();
goto abort;
}
if(!(interesting_event_mask_ & event_type)) return;
interesting_event_mask_ &= ~event_type;
BEGIN_SECTION();
// Resets busy and non-DMA execution, clears the command buffer, sets the data mode to scanning and flows
// into wait_for_complete_command_sequence.
wait_for_command:
expects_input_ = false;
set_data_mode(Storage::Disk::MFMController::DataMode::Scanning);
ResetBusy();
ResetNonDMAExecution();
command_.clear();
// Sets the data request bit, and waits for a byte. Then sets the busy bit. Continues accepting bytes
// until it has a quantity that make up an entire command, then resets the data request bit and
// branches to that command.
wait_for_complete_command_sequence:
SetDataRequest();
SetDataDirectionFromProcessor();
WAIT_FOR_EVENT(Event8272::CommandByte)
SetBusy();
static const 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,
0, 9, 0, 0, 0, 9, 0, 0,
};
if(command_.size() < required_lengths[command_[0] & 0x1f]) goto wait_for_complete_command_sequence;
if(command_.size() == 9) {
cylinder_ = command_[2];
head_ = command_[3];
sector_ = command_[4];
size_ = command_[5];
}
ResetDataRequest();
status_[0] = status_[1] = status_[2] = 0;
// If this is not clearly a command that's safe to carry out in parallel to a seek, end all seeks.
switch(command_[0] & 0x1f) {
case CommandReadData:
case CommandReadDeletedData:
case CommandWriteData:
case CommandWriteDeletedData:
case CommandReadTrack:
case CommandReadID:
case CommandFormatTrack:
case CommandScanLow:
case CommandScanLowOrEqual:
case CommandScanHighOrEqual:
is_access_command_ = true;
break;
default:
is_access_command_ = false;
break;
}
if(is_access_command_) {
for(int c = 0; c < 4; c++) {
if(drives_[c].phase == Drive::Seeking) {
drives_[c].phase = Drive::NotSeeking;
drives_seeking_--;
}
}
// Establishes the drive and head being addressed, and whether in double density mode; populates the internal
// cylinder, head, sector and size registers from the command stream.
is_executing_ = true;
if(!dma_mode_) SetNonDMAExecution();
SET_DRIVE_HEAD_MFM();
LOAD_HEAD();
if(!get_drive().get_is_ready()) {
SetNotReady();
goto abort;
}
}
// Jump to the proper place.
switch(command_[0] & 0x1f) {
case CommandReadData:
case CommandReadDeletedData:
goto read_data;
case CommandWriteData:
case CommandWriteDeletedData:
goto write_data;
case CommandReadTrack: goto read_track;
case CommandReadID: goto read_id;
case CommandFormatTrack: goto format_track;
case CommandScanLow: goto scan_low;
case CommandScanLowOrEqual: goto scan_low_or_equal;
case CommandScanHighOrEqual: goto scan_high_or_equal;
case CommandRecalibrate: goto recalibrate;
case CommandSeek: goto seek;
case CommandSenseInterruptStatus: goto sense_interrupt_status;
case CommandSpecify: goto specify;
case CommandSenseDriveStatus: goto sense_drive_status;
default: goto invalid;
}
// Decodes drive, head and density, loads the head, loads the internal cylinder, head, sector and size registers,
// and searches for a sector that meets those criteria. If one is found, inspects the instruction in use and
// jumps to an appropriate handler.
read_write_find_header:
// Sets a maximum index hole limit of 2 then performs a find header/read header loop, continuing either until
// 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_);
find_next_sector:
FIND_HEADER();
if(!index_hole_limit_) {
// Two index holes have passed wihout finding the header sought.
// printf("Not found\n");
SetNoData();
goto abort;
}
index_hole_count_ = 0;
// printf("Header\n");
READ_HEADER();
if(index_hole_count_) {
// This implies an index hole was sighted within the header. Error out.
SetEndOfCylinder();
goto abort;
}
if(get_crc_generator().get_value()) {
// 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());
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");
switch(command_[0] & 0x1f) {
case CommandReadData:
case CommandReadDeletedData:
goto read_data_found_header;
case CommandWriteData: // write data
case CommandWriteDeletedData: // write deleted data
goto write_data_found_header;
}
// 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]);
read_next_data:
goto read_write_find_header;
// Finds the next data block and sets data mode to reading, setting an error flag if the on-disk deleted
// flag doesn't match the sort the command was looking for.
read_data_found_header:
FIND_DATA();
ClearControlMark();
if(event_type == static_cast<int>(Event::Token)) {
if(get_latest_token().type != Token::Data && get_latest_token().type != Token::DeletedData) {
// Something other than a data mark came next — impliedly an ID or index mark.
SetMissingAddressMark();
SetMissingDataAddressMark();
goto abort; // TODO: or read_next_data?
} else {
if((get_latest_token().type == Token::Data) != ((command_[0] & 0x1f) == CommandReadData)) {
if(!(command_[0]&0x20)) {
// SK is not set; set the error flag but read this sector before finishing.
SetControlMark();
} else {
// SK is set; skip this sector.
goto read_next_data;
}
}
}
} else {
// An index hole appeared before the data mark.
SetEndOfCylinder();
goto abort; // TODO: or read_next_data?
}
distance_into_section_ = 0;
set_data_mode(Reading);
// Waits for the next token, then supplies it to the CPU by: (i) setting data request and direction; and (ii) resetting
// data request once the byte has been taken. Continues until all bytes have been read.
//
// TODO: consider DTL.
read_data_get_byte:
WAIT_FOR_EVENT(static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole));
if(event_type == static_cast<int>(Event::Token)) {
result_stack_.push_back(get_latest_token().byte_value);
distance_into_section_++;
SetDataRequest();
SetDataDirectionToProcessor();
WAIT_FOR_EVENT(static_cast<int>(Event8272::ResultEmpty) | static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole));
}
switch(event_type) {
case static_cast<int>(Event8272::ResultEmpty): // The caller read the byte in time; proceed as normal.
ResetDataRequest();
if(distance_into_section_ < (128 << size_)) goto read_data_get_byte;
break;
case static_cast<int>(Event::Token): // The caller hasn't read the old byte yet and a new one has arrived
SetOverrun();
goto abort;
break;
case static_cast<int>(Event::IndexHole):
SetEndOfCylinder();
goto abort;
break;
}
// read CRC, without transferring it, then check it
WAIT_FOR_EVENT(Event::Token);
WAIT_FOR_EVENT(Event::Token);
if(get_crc_generator().get_value()) {
// This implies a CRC error in the sector; mark as such and temrinate.
SetDataError();
SetDataFieldDataError();
goto abort;
}
// check whether that's it: either the final requested sector has been read, or because
// a sector that was [/wasn't] marked as deleted when it shouldn't [/should] have been
if(sector_ != command_[6] && !ControlMark()) {
sector_++;
goto read_next_data;
}
// For a final result phase, post the standard ST0, ST1, ST2, C, H, R, N
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]);
if(get_drive().get_is_read_only()) {
SetNotWriteable();
goto abort;
}
write_next_data:
goto read_write_find_header;
write_data_found_header:
WAIT_FOR_BYTES(get_is_double_density() ? 22 : 11);
begin_writing(true);
write_id_data_joiner((command_[0] & 0x1f) == CommandWriteDeletedData, true);
SetDataDirectionFromProcessor();
SetDataRequest();
expects_input_ = true;
distance_into_section_ = 0;
write_loop:
WAIT_FOR_EVENT(Event::DataWritten);
if(!has_input_) {
SetOverrun();
goto abort;
}
write_byte(input_);
has_input_ = false;
distance_into_section_++;
if(distance_into_section_ < (128 << size_)) {
SetDataRequest();
goto write_loop;
}
printf("Wrote %d bytes\n", distance_into_section_);
write_crc();
expects_input_ = false;
WAIT_FOR_EVENT(Event::DataWritten);
end_writing();
if(sector_ != command_[6]) {
sector_++;
goto write_next_data;
}
goto post_st012chrn;
// 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]);
// Sets a maximum index hole limit of 2 then waits either until it finds a header mark or sees too many index holes.
// If a header mark is found, reads in the following bytes that produce a header. Otherwise branches to data not found.
index_hole_limit_ = 2;
FIND_HEADER();
if(!index_hole_limit_) {
SetMissingAddressMark();
goto abort;
}
READ_HEADER();
// Sets internal registers from the discovered header and posts the standard ST0, ST1, ST2, C, H, R, N.
cylinder_ = header_[0];
head_ = header_[1];
sector_ = header_[2];
size_ = header_[3];
goto post_st012chrn;
// Performs read track.
read_track:
printf("Read track [%02x %02x %02x %02x]\n", command_[2], command_[3], command_[4], command_[5]);
// Wait for the index hole.
WAIT_FOR_EVENT(Event::IndexHole);
sector_ = 0;
index_hole_limit_ = 2;
// While not index hole again, stream all sector contents until EOT sectors have been read.
read_track_next_sector:
FIND_HEADER();
if(!index_hole_limit_) {
if(!sector_) {
SetMissingAddressMark();
goto abort;
} else {
goto post_st012chrn;
}
}
READ_HEADER();
FIND_DATA();
distance_into_section_ = 0;
SetDataDirectionToProcessor();
read_track_get_byte:
WAIT_FOR_EVENT(Event::Token);
result_stack_.push_back(get_latest_token().byte_value);
distance_into_section_++;
SetDataRequest();
// TODO: other possible exit conditions; find a way to merge with the read_data version of this.
WAIT_FOR_EVENT(static_cast<int>(Event8272::ResultEmpty));
ResetDataRequest();
if(distance_into_section_ < (128 << header_[2])) goto read_track_get_byte;
sector_++;
if(sector_ < command_[6]) goto read_track_next_sector;
goto post_st012chrn;
// Performs format [/write] track.
format_track:
printf("Format track\n");
if(get_drive().get_is_read_only()) {
SetNotWriteable();
goto abort;
}
// Wait for the index hole.
WAIT_FOR_EVENT(Event::IndexHole);
index_hole_count_ = 0;
begin_writing(true);
// Write start-of-track.
write_start_of_track();
WAIT_FOR_EVENT(Event::DataWritten);
sector_ = 0;
format_track_write_sector:
write_id_joiner();
// Write the sector header, obtaining its contents
// from the processor.
SetDataDirectionFromProcessor();
SetDataRequest();
expects_input_ = true;
distance_into_section_ = 0;
format_track_write_header:
WAIT_FOR_EVENT(static_cast<int>(Event::DataWritten) | static_cast<int>(Event::IndexHole));
switch(event_type) {
case static_cast<int>(Event::IndexHole):
SetOverrun();
goto abort;
break;
case static_cast<int>(Event::DataWritten):
header_[distance_into_section_] = input_;
write_byte(input_);
has_input_ = false;
distance_into_section_++;
if(distance_into_section_ < 4) {
SetDataRequest();
goto format_track_write_header;
}
break;
}
printf("W: %02x %02x %02x %02x, %04x\n", header_[0], header_[1], header_[2], header_[3], get_crc_generator().get_value());
write_crc();
// Write the sector body.
write_id_data_joiner(false, false);
write_n_bytes(128 << command_[2], command_[5]);
write_crc();
// Write the prescribed gap.
write_n_bytes(command_[4], get_is_double_density() ? 0x4e : 0xff);
// Consider repeating.
sector_++;
if(sector_ < command_[3] && !index_hole_count_)
goto format_track_write_sector;
// Otherwise, pad out to the index hole.
format_track_pad:
write_byte(get_is_double_density() ? 0x4e : 0xff);
WAIT_FOR_EVENT(static_cast<int>(Event::DataWritten) | static_cast<int>(Event::IndexHole));
if(event_type != static_cast<int>(Event::IndexHole)) goto format_track_pad;
end_writing();
cylinder_ = header_[0];
head_ = header_[1];
sector_ = header_[2] + 1;
size_ = header_[3];
goto post_st012chrn;
scan_low:
printf("Scan low unimplemented!!\n");
goto wait_for_command;
scan_low_or_equal:
printf("Scan low or equal unimplemented!!\n");
goto wait_for_command;
scan_high_or_equal:
printf("Scan high or equal unimplemented!!\n");
goto wait_for_command;
// Performs both recalibrate and seek commands. These commands occur asynchronously, so the actual work
// occurs in ::run_for; this merely establishes that seeking should be ongoing.
recalibrate:
seek:
{
int drive = command_[1]&3;
select_drive(drive);
// Increment the seeking count if this drive wasn't already seeking.
if(drives_[drive].phase != Drive::Seeking) {
drives_seeking_++;
is_sleeping_ = false;
update_sleep_observer();
}
// Set currently seeking, with a step to occur right now (yes, it sounds like jamming these
// in could damage your drive motor).
drives_[drive].phase = Drive::Seeking;
drives_[drive].step_rate_counter = 8000 * step_rate_time_;
drives_[drive].steps_taken = 0;
drives_[drive].seek_failed = false;
main_status_ |= 1 << (command_[1]&3);
// If this is a seek, set the processor-supplied target location; otherwise it is a recalibrate,
// which means resetting the current state now but aiming to hit '-1' (which the stepping code
// 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]);
} else {
drives_[drive].target_head_position = -1;
drives_[drive].head_position = 0;
printf("Recalibrate\n");
}
// Check whether any steps are even needed; if not then mark as completed already.
if(seek_is_satisfied(drive)) {
drives_[drive].phase = Drive::CompletedSeeking;
drives_seeking_--;
}
}
goto wait_for_command;
// Performs sense interrupt status.
sense_interrupt_status:
printf("Sense interrupt status\n");
{
// Find the first drive that is in the CompletedSeeking state.
int found_drive = -1;
for(int c = 0; c < 4; c++) {
if(drives_[c].phase == Drive::CompletedSeeking) {
found_drive = c;
break;
}
}
// If a drive was found, return its results. Otherwise return a single 0x80.
if(found_drive != -1) {
drives_[found_drive].phase = Drive::NotSeeking;
status_[0] = static_cast<uint8_t>(found_drive);
main_status_ &= ~(1 << found_drive);
SetSeekEnd();
result_stack_ = { drives_[found_drive].head_position, status_[0]};
} else {
result_stack_ = { 0x80 };
}
}
goto post_result;
// Performs specify.
specify:
// Just store the values, and terminate the command.
printf("Specify\n");
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
if(!head_unload_time_) head_unload_time_ = 16;
if(!head_load_time_) head_load_time_ = 2;
dma_mode_ = !(command_[2] & 1);
goto wait_for_command;
sense_drive_status:
printf("Sense drive status\n");
{
int drive = command_[1] & 3;
select_drive(drive);
result_stack_= {
static_cast<uint8_t>(
(command_[1] & 7) | // drive and head number
0x08 | // single sided
(get_drive().get_is_track_zero() ? 0x10 : 0x00) |
(get_drive().get_is_ready() ? 0x20 : 0x00) |
(get_drive().get_is_read_only() ? 0x40 : 0x00)
)
};
}
goto post_result;
// Performs any invalid command.
invalid:
// A no-op, but posts ST0 (but which ST0?)
result_stack_ = {0x80};
goto post_result;
// Sets abnormal termination of the current command and proceeds to an ST0, ST1, ST2, C, H, R, N result phase.
abort:
end_writing();
SetAbnormalTermination();
goto post_st012chrn;
// Posts ST0, ST1, ST2, C, H, R and N as a result phase.
post_st012chrn:
SCHEDULE_HEAD_UNLOAD();
result_stack_ = {size_, sector_, head_, cylinder_, status_[2], status_[1], status_[0]};
goto post_result;
// Posts whatever is in result_stack_ as a result phase. Be aware that it is a stack — the
// last thing in it will be returned first.
post_result:
printf("Result to %02x, main %02x: ", command_[0] & 0x1f, main_status_);
for(std::size_t c = 0; c < result_stack_.size(); c++) {
printf("%02x ", result_stack_[result_stack_.size() - 1 - c]);
}
printf("\n");
// Set ready to send data to the processor, no longer in non-DMA execution phase.
is_executing_ = false;
ResetNonDMAExecution();
SetDataRequest();
SetDataDirectionToProcessor();
// The actual stuff of unwinding result_stack_ is handled by ::get_register; wait
// until the processor has read all result bytes.
WAIT_FOR_EVENT(Event8272::ResultEmpty);
// Reset data direction and end the command.
goto wait_for_command;
END_SECTION()
}
bool i8272::seek_is_satisfied(int drive) {
return (drives_[drive].target_head_position == drives_[drive].head_position) ||
(drives_[drive].target_head_position == -1 && get_drive().get_is_track_zero());
}
void i8272::set_dma_acknowledge(bool dack) {
}
void i8272::set_terminal_count(bool tc) {
}
void i8272::set_data_input(uint8_t value) {
}
uint8_t i8272::get_data_output() {
return 0xff;
}

135
Components/8272/i8272.hpp Normal file
View File

@@ -0,0 +1,135 @@
//
// i8272.hpp
// Clock Signal
//
// Created by Thomas Harte on 05/08/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef i8272_hpp
#define i8272_hpp
#include "../../Storage/Disk/Controller/MFMDiskController.hpp"
#include <cstdint>
#include <memory>
#include <vector>
namespace Intel {
namespace i8272 {
class BusHandler {
public:
virtual void set_dma_data_request(bool drq) {}
virtual void set_interrupt(bool irq) {}
};
class i8272: public Storage::Disk::MFMController {
public:
i8272(BusHandler &bus_handler, Cycles clock_rate);
void run_for(Cycles);
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 set_dma_acknowledge(bool dack);
void set_terminal_count(bool tc);
bool is_sleeping();
protected:
virtual void select_drive(int number) = 0;
private:
// The bus handler, for interrupt and DMA-driven usage.
BusHandler &bus_handler_;
std::unique_ptr<BusHandler> allocated_bus_handler_;
// Status registers.
uint8_t main_status_ = 0;
uint8_t status_[3] = {0, 0, 0};
// A buffer for accumulating the incoming command, and one for accumulating the result.
std::vector<uint8_t> command_;
std::vector<uint8_t> result_stack_;
uint8_t input_ = 0;
bool has_input_ = false;
bool expects_input_ = false;
// Event stream: the 8272-specific events, plus the current event state.
enum class Event8272: int {
CommandByte = (1 << 3),
Timer = (1 << 4),
ResultEmpty = (1 << 5),
NoLongerReady = (1 << 6)
};
void posit_event(int type);
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;
// The connected drives.
struct Drive {
uint8_t head_position = 0;
// Seeking: persistent state.
enum Phase {
NotSeeking,
Seeking,
CompletedSeeking
} phase = NotSeeking;
bool did_seek = false;
bool seek_failed = false;
// Seeking: transient state.
int 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};
bool head_is_loaded[2] = {false, false};
} drives_[4];
int drives_seeking_ = 0;
/// @returns @c true if the selected drive, which is number @c drive, can stop seeking.
bool seek_is_satisfied(int drive);
// User-supplied parameters; as per the specify command.
int step_rate_time_ = 1;
int head_unload_time_ = 1;
int head_load_time_ = 1;
bool dma_mode_ = false;
bool is_executing_ = false;
// A count of head unload timers currently running.
int head_timers_running_ = 0;
// Transient storage and counters used while reading the disk.
uint8_t header_[6] = {0, 0, 0, 0, 0, 0};
int distance_into_section_ = 0;
int index_hole_count_ = 0, index_hole_limit_ = 0;
// Keeps track of the drive and head in use during commands.
int active_drive_ = 0;
int active_head_ = 0;
// Internal registers.
uint8_t cylinder_ = 0, head_ = 0, sector_ = 0, size_ = 0;
// Master switch on not performing any work.
bool is_sleeping_ = false;
};
}
}
#endif /* i8272_hpp */

712
Components/9918/9918.cpp Normal file
View File

@@ -0,0 +1,712 @@
//
// 9918.cpp
// Clock Signal
//
// Created by Thomas Harte on 25/11/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "9918.hpp"
#include <cassert>
#include <cstring>
using namespace TI;
namespace {
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;
}
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),
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)
};
const uint8_t StatusInterrupt = 0x80;
const uint8_t StatusFifthSprite = 0x40;
const int StatusSpriteCollisionShift = 5;
const uint8_t StatusSpriteCollision = 0x20;
struct ReverseTable {
std::uint8_t map[256];
ReverseTable() {
for(int c = 0; c < 256; ++c) {
map[c] = static_cast<uint8_t>(
((c & 0x80) >> 7) |
((c & 0x40) >> 5) |
((c & 0x20) >> 3) |
((c & 0x10) >> 1) |
((c & 0x08) << 1) |
((c & 0x04) << 3) |
((c & 0x02) << 5) |
((c & 0x01) << 7)
);
}
}
} reverse_table;
// Bits are reversed in the internal mode value; they're stored
// in the order M1 M2 M3. Hence the definitions below.
enum ScreenMode {
Text = 4,
MultiColour = 2,
ColouredText = 0,
Graphics = 1
};
}
TMS9918Base::TMS9918Base() :
// 342 internal cycles are 228/227.5ths of a line, so 341.25 cycles should be a whole
// line. Therefore multiply everything by four, but set line length to 1365 rather than 342*4 = 1368.
crt_(new Outputs::CRT::CRT(1365, 4, Outputs::CRT::DisplayType::NTSC60, 4)) {}
TMS9918::TMS9918(Personality p) {
// Unimaginatively, this class just passes RGB through to the shader. Investigation is needed
// into whether there's a more natural form.
crt_->set_rgb_sampling_function(
"vec3 rgb_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate)"
"{"
"return texture(sampler, coordinate).rgb / vec3(255.0);"
"}");
crt_->set_video_signal(Outputs::CRT::VideoSignal::RGB);
crt_->set_visible_area(Outputs::CRT::Rect(0.055f, 0.025f, 0.9f, 0.9f));
crt_->set_input_gamma(2.8f);
// The TMS remains in-phase with the NTSC colour clock; this is an empirical measurement
// intended to produce the correct relationship between the hard edges between pixels and
// the colour clock. It was eyeballed rather than derived from any knowledge of the TMS
// colour burst generator because I've yet to find any.
crt_->set_immediate_default_phase(0.85f);
}
Outputs::CRT::CRT *TMS9918::get_crt() {
return crt_.get();
}
void TMS9918Base::test_sprite(int sprite_number, int screen_row) {
if(!(status_ & StatusFifthSprite)) {
status_ = static_cast<uint8_t>((status_ & ~31) | sprite_number);
}
if(sprites_stopped_)
return;
const int sprite_position = ram_[sprite_attribute_table_address_ + (sprite_number << 2)];
// A sprite Y of 208 means "don't scan the list any further".
if(sprite_position == 208) {
sprites_stopped_ = true;
return;
}
const int sprite_row = (screen_row - sprite_position)&255;
if(sprite_row < 0 || sprite_row >= sprite_height_) return;
const int active_sprite_slot = sprite_sets_[active_sprite_set_].active_sprite_slot;
if(active_sprite_slot == 4) {
status_ |= StatusFifthSprite;
return;
}
SpriteSet::ActiveSprite &sprite = sprite_sets_[active_sprite_set_].active_sprites[active_sprite_slot];
sprite.index = sprite_number;
sprite.row = sprite_row >> (sprites_magnified_ ? 1 : 0);
sprite_sets_[active_sprite_set_].active_sprite_slot++;
}
void TMS9918Base::get_sprite_contents(int field, int cycles_left, int screen_row) {
int sprite_id = field / 6;
field %= 6;
while(true) {
const int cycles_in_sprite = std::min(cycles_left, 6 - field);
cycles_left -= cycles_in_sprite;
const int final_field = cycles_in_sprite + field;
assert(sprite_id < 4);
SpriteSet::ActiveSprite &sprite = sprite_sets_[active_sprite_set_].active_sprites[sprite_id];
if(field < 4) {
std::memcpy(
&sprite.info[field],
&ram_[sprite_attribute_table_address_ + (sprite.index << 2) + field],
static_cast<size_t>(std::min(4, final_field) - field));
}
field = std::min(4, final_field);
const int sprite_offset = sprite.info[2] & ~(sprites_16x16_ ? 3 : 0);
const int sprite_address = sprite_generator_table_address_ + (sprite_offset << 3) + sprite.row; // TODO: recalclate sprite.row from screen_row (?)
while(field < final_field) {
sprite.image[field - 4] = ram_[sprite_address + ((field - 4) << 4)];
field++;
}
if(!cycles_left) return;
field = 0;
sprite_id++;
}
}
void TMS9918::run_for(const HalfCycles cycles) {
// As specific as I've been able to get:
// Scanline time is always 228 cycles.
// PAL output is 313 lines total. NTSC output is 262 lines total.
// Interrupt is signalled upon entering the lower border.
// Keep a count of cycles separate from internal counts to avoid
// potential errors mapping back and forth.
half_cycles_into_frame_ = (half_cycles_into_frame_ + cycles) % HalfCycles(frame_lines_ * 228 * 2);
// Convert 456 clocked half cycles per line to 342 internal cycles per line;
// the internal clock is 1.5 times the nominal 3.579545 Mhz that I've advertised
// for this part. So multiply by three quarters.
int int_cycles = (cycles.as_int() * 3) + cycles_error_;
cycles_error_ = int_cycles & 3;
int_cycles >>= 2;
if(!int_cycles) return;
while(int_cycles) {
// Determine how much time has passed in the remainder of this line, and proceed.
int cycles_left = std::min(342 - column_, int_cycles);
// ------------------------------------
// Potentially perform a memory access.
// ------------------------------------
if(queued_access_ != MemoryAccess::None) {
int time_until_access_slot = 0;
switch(line_mode_) {
case LineMode::Refresh:
if(column_ < 53 || column_ >= 307) time_until_access_slot = column_&1;
else time_until_access_slot = 3 - ((column_ - 53)&3);
// i.e. 53 -> 3, 52 -> 2, 51 -> 1, 50 -> 0, etc
break;
case LineMode::Text:
if(column_ < 59 || column_ >= 299) time_until_access_slot = column_&1;
else time_until_access_slot = 5 - ((column_ + 3)%6);
// i.e. 59 -> 3, 60 -> 2, 61 -> 1, etc
break;
case LineMode::Character:
if(column_ < 9) time_until_access_slot = column_&1;
else if(column_ < 30) time_until_access_slot = 30 - column_;
else if(column_ < 37) time_until_access_slot = column_&1;
else if(column_ < 311) time_until_access_slot = 31 - ((column_ + 7)&31);
// i.e. 53 -> 3, 54 -> 2, 55 -> 1, 56 -> 0, 57 -> 31, etc
else if(column_ < 313) time_until_access_slot = column_&1;
else time_until_access_slot = 342 - column_;
break;
}
if(cycles_left >= time_until_access_slot) {
if(queued_access_ == MemoryAccess::Write) {
ram_[ram_pointer_ & 16383] = read_ahead_buffer_;
} else {
read_ahead_buffer_ = ram_[ram_pointer_ & 16383];
}
ram_pointer_++;
queued_access_ = MemoryAccess::None;
}
}
column_ += cycles_left; // column_ is now the column that has been reached in this line.
int_cycles -= cycles_left; // Count down duration to run for.
// ------------------------------
// Perform video memory accesses.
// ------------------------------
if(((row_ < 192) || (row_ == frame_lines_-1)) && !blank_screen_) {
const int sprite_row = (row_ < 192) ? row_ : -1;
const int access_slot = column_ >> 1; // There are only 171 available memory accesses per line.
switch(line_mode_) {
default: break;
case LineMode::Text:
access_pointer_ = std::min(30, access_slot);
if(access_pointer_ >= 30 && access_pointer_ < 150) {
const int row_base = pattern_name_address_ + (row_ >> 3) * 40;
const int end = std::min(150, access_slot);
// Pattern names are collected every third window starting from window 30.
const int pattern_names_start = (access_pointer_ - 30 + 2) / 3;
const int pattern_names_end = (end - 30 + 2) / 3;
std::memcpy(&pattern_names_[pattern_names_start], &ram_[row_base + pattern_names_start], static_cast<size_t>(pattern_names_end - pattern_names_start));
// Patterns are collected every third window starting from window 32.
const int pattern_buffer_start = (access_pointer_ - 32 + 2) / 3;
const int pattern_buffer_end = (end - 32 + 2) / 3;
for(int column = pattern_buffer_start; column < pattern_buffer_end; ++column) {
pattern_buffer_[column] = ram_[pattern_generator_table_address_ + (pattern_names_[column] << 3) + (row_ & 7)];
}
}
break;
case LineMode::Character:
// Four access windows: no collection.
if(access_pointer_ < 5)
access_pointer_ = std::min(5, access_slot);
// Then ten access windows are filled with collection of sprite 3 and 4 details.
if(access_pointer_ >= 5 && access_pointer_ < 15) {
int end = std::min(15, access_slot);
get_sprite_contents(access_pointer_ - 5 + 14, end - access_pointer_, sprite_row - 1);
access_pointer_ = std::min(15, access_slot);
}
// Four more access windows: no collection.
if(access_pointer_ >= 15 && access_pointer_ < 19) {
access_pointer_ = std::min(19, access_slot);
// Start new sprite set if this is location 19.
if(access_pointer_ == 19) {
active_sprite_set_ ^= 1;
sprite_sets_[active_sprite_set_].active_sprite_slot = 0;
sprites_stopped_ = false;
}
}
// Then eight access windows fetch the y position for the first eight sprites.
while(access_pointer_ < 27 && access_pointer_ < access_slot) {
test_sprite(access_pointer_ - 19, sprite_row);
access_pointer_++;
}
// The next 128 access slots are video and sprite collection interleaved.
if(access_pointer_ >= 27 && access_pointer_ < 155) {
int end = std::min(155, access_slot);
int row_base = pattern_name_address_;
int pattern_base = pattern_generator_table_address_;
int colour_base = colour_table_address_;
if(screen_mode_ == ScreenMode::Graphics) {
// If this is high resolution mode, allow the row number to affect the pattern and colour addresses.
pattern_base &= 0x2000 | ((row_ & 0xc0) << 5);
colour_base &= 0x2000 | ((row_ & 0xc0) << 5);
}
row_base += (row_ << 2)&~31;
// Pattern names are collected every fourth window starting from window 27.
const int pattern_names_start = (access_pointer_ - 27 + 3) >> 2;
const int pattern_names_end = (end - 27 + 3) >> 2;
std::memcpy(&pattern_names_[pattern_names_start], &ram_[row_base + pattern_names_start], static_cast<size_t>(pattern_names_end - pattern_names_start));
// Colours are collected every fourth window starting from window 29.
const int colours_start = (access_pointer_ - 29 + 3) >> 2;
const int colours_end = (end - 29 + 3) >> 2;
if(screen_mode_ != 1) {
for(int column = colours_start; column < colours_end; ++column) {
colour_buffer_[column] = ram_[colour_base + (pattern_names_[column] >> 3)];
}
} else {
for(int column = colours_start; column < colours_end; ++column) {
colour_buffer_[column] = ram_[colour_base + (pattern_names_[column] << 3) + (row_ & 7)];
}
}
// Patterns are collected ever fourth window starting from window 30.
const int pattern_buffer_start = (access_pointer_ - 30 + 3) >> 2;
const int pattern_buffer_end = (end - 30 + 3) >> 2;
// Multicolour mode uss a different function of row to pick bytes
const int row = (screen_mode_ != 2) ? (row_ & 7) : ((row_ >> 2) & 7);
for(int column = pattern_buffer_start; column < pattern_buffer_end; ++column) {
pattern_buffer_[column] = ram_[pattern_base + (pattern_names_[column] << 3) + row];
}
// Sprite slots occur in three quarters of ever fourth window starting from window 28.
const int sprite_start = (access_pointer_ - 28 + 3) >> 2;
const int sprite_end = (end - 28 + 3) >> 2;
for(int column = sprite_start; column < sprite_end; ++column) {
if(column&3) {
test_sprite(7 + column - (column >> 2), sprite_row);
}
}
access_pointer_ = std::min(155, access_slot);
}
// Two access windows: no collection.
if(access_pointer_ < 157)
access_pointer_ = std::min(157, access_slot);
// Fourteen access windows: collect initial sprite information.
if(access_pointer_ >= 157 && access_pointer_ < 171) {
int end = std::min(171, access_slot);
get_sprite_contents(access_pointer_ - 157, end - access_pointer_, sprite_row);
access_pointer_ = std::min(171, access_slot);
}
break;
}
}
// --------------------------
// End video memory accesses.
// --------------------------
// --------------------
// Output video stream.
// --------------------
if(row_ < 192 && !blank_screen_) {
// ----------------------
// Output horizontal sync
// ----------------------
if(!output_column_ && column_ >= 26) {
crt_->output_sync(13 * 4);
crt_->output_default_colour_burst(13 * 4);
output_column_ = 26;
}
// -------------------
// Output left border.
// -------------------
if(output_column_ >= 26) {
int pixels_end = std::min(first_pixel_column_, column_);
if(output_column_ < pixels_end) {
output_border(pixels_end - output_column_);
output_column_ = pixels_end;
// Grab a pointer for drawing pixels to, if the moment has arrived.
if(pixels_end == first_pixel_column_) {
pixel_base_ = pixel_target_ = reinterpret_cast<uint32_t *>(crt_->allocate_write_area(static_cast<unsigned int>(first_right_border_column_ - first_pixel_column_)));
}
}
}
// --------------
// Output pixels.
// --------------
if(output_column_ >= first_pixel_column_) {
int pixels_end = std::min(first_right_border_column_, column_);
if(output_column_ < pixels_end) {
switch(line_mode_) {
default: break;
case LineMode::Text: {
const uint32_t colours[2] = { palette[background_colour_], palette[text_colour_] };
const int shift = (output_column_ - first_pixel_column_) % 6;
int byte_column = (output_column_ - first_pixel_column_) / 6;
int pattern = reverse_table.map[pattern_buffer_[byte_column]] >> shift;
int pixels_left = pixels_end - output_column_;
int length = std::min(pixels_left, 6 - shift);
while(true) {
pixels_left -= length;
for(int c = 0; c < length; ++c) {
pixel_target_[c] = colours[pattern&0x01];
pattern >>= 1;
}
pixel_target_ += length;
if(!pixels_left) break;
length = std::min(6, pixels_left);
byte_column++;
pattern = reverse_table.map[pattern_buffer_[byte_column]];
}
output_column_ = pixels_end;
} break;
case LineMode::Character: {
// If this is the start of the visible area, seed sprite shifter positions.
SpriteSet &sprite_set = sprite_sets_[active_sprite_set_ ^ 1];
if(output_column_ == first_pixel_column_) {
int c = sprite_set.active_sprite_slot;
while(c--) {
SpriteSet::ActiveSprite &sprite = sprite_set.active_sprites[c];
sprite.shift_position = -sprite.info[1];
if(sprite.info[3] & 0x80) {
sprite.shift_position += 32;
if(sprite.shift_position > 0 && !sprites_magnified_)
sprite.shift_position *= 2;
}
}
}
// Paint the background tiles.
const int pixels_left = pixels_end - output_column_;
if(screen_mode_ == ScreenMode::MultiColour) {
int pixel_location = output_column_ - first_pixel_column_;
for(int c = 0; c < pixels_left; ++c) {
pixel_target_[c] = palette[
(pattern_buffer_[(pixel_location + c) >> 3] >> (((pixel_location + c) & 4)^4)) & 15
];
}
pixel_target_ += pixels_left;
} else {
const int shift = (output_column_ - first_pixel_column_) & 7;
int byte_column = (output_column_ - first_pixel_column_) >> 3;
int length = std::min(pixels_left, 8 - shift);
int pattern = reverse_table.map[pattern_buffer_[byte_column]] >> shift;
uint8_t colour = colour_buffer_[byte_column];
uint32_t colours[2] = {
palette[(colour & 15) ? (colour & 15) : background_colour_],
palette[(colour >> 4) ? (colour >> 4) : background_colour_]
};
int background_pixels_left = pixels_left;
while(true) {
background_pixels_left -= length;
for(int c = 0; c < length; ++c) {
pixel_target_[c] = colours[pattern&0x01];
pattern >>= 1;
}
pixel_target_ += length;
if(!background_pixels_left) break;
length = std::min(8, background_pixels_left);
byte_column++;
pattern = reverse_table.map[pattern_buffer_[byte_column]];
colour = colour_buffer_[byte_column];
colours[0] = palette[(colour & 15) ? (colour & 15) : background_colour_];
colours[1] = palette[(colour >> 4) ? (colour >> 4) : background_colour_];
}
}
// Paint sprites and check for collisions.
if(sprite_set.active_sprite_slot) {
int sprite_pixels_left = pixels_left;
const int shift_advance = sprites_magnified_ ? 1 : 2;
const uint32_t sprite_colour_selection_masks[2] = {0x00000000, 0xffffffff};
const int colour_masks[16] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
while(sprite_pixels_left--) {
uint32_t sprite_colour = pixel_base_[output_column_ - first_pixel_column_];
int sprite_mask = 0;
int c = sprite_set.active_sprite_slot;
while(c--) {
SpriteSet::ActiveSprite &sprite = sprite_set.active_sprites[c];
if(sprite.shift_position < 0) {
sprite.shift_position++;
continue;
} else if(sprite.shift_position < 32) {
int mask = sprite.image[sprite.shift_position >> 4] << ((sprite.shift_position&15) >> 1);
mask = (mask >> 7) & 1;
status_ |= (mask & sprite_mask) << StatusSpriteCollisionShift;
sprite_mask |= mask;
sprite.shift_position += shift_advance;
mask &= colour_masks[sprite.info[3]&15];
sprite_colour = (sprite_colour & sprite_colour_selection_masks[mask^1]) | (palette[sprite.info[3]&15] & sprite_colour_selection_masks[mask]);
}
}
pixel_base_[output_column_ - first_pixel_column_] = sprite_colour;
output_column_++;
}
}
output_column_ = pixels_end;
} break;
}
if(output_column_ == first_right_border_column_) {
crt_->output_data(static_cast<unsigned int>(first_right_border_column_ - first_pixel_column_) * 4, 4);
pixel_target_ = nullptr;
}
}
}
// --------------------
// Output right border.
// --------------------
if(output_column_ >= first_right_border_column_) {
output_border(column_ - output_column_);
output_column_ = column_;
}
} else if(row_ >= first_vsync_line_ && row_ < first_vsync_line_+3) {
// Vertical sync.
if(column_ == 342) {
crt_->output_sync(342 * 4);
}
} else {
// Blank.
if(!output_column_ && column_ >= 26) {
crt_->output_sync(13 * 4);
crt_->output_default_colour_burst(13 * 4);
output_column_ = 26;
}
if(output_column_ >= 26) {
output_border(column_ - output_column_);
output_column_ = column_;
}
}
// -----------------
// End video stream.
// -----------------
// -----------------------------------
// Prepare for next line, potentially.
// -----------------------------------
if(column_ == 342) {
access_pointer_ = column_ = output_column_ = 0;
row_ = (row_ + 1) % frame_lines_;
if(row_ == 192) status_ |= StatusInterrupt;
screen_mode_ = next_screen_mode_;
blank_screen_ = next_blank_screen_;
switch(screen_mode_) {
case ScreenMode::Text:
line_mode_ = LineMode::Text;
first_pixel_column_ = 69;
first_right_border_column_ = 309;
break;
default:
line_mode_ = LineMode::Character;
first_pixel_column_ = 63;
first_right_border_column_ = 319;
break;
}
if(blank_screen_ || (row_ >= 192 && row_ != frame_lines_-1)) line_mode_ = LineMode::Refresh;
}
}
}
void TMS9918Base::output_border(int cycles) {
pixel_target_ = reinterpret_cast<uint32_t *>(crt_->allocate_write_area(1));
if(pixel_target_) *pixel_target_ = palette[background_colour_];
crt_->output_level(static_cast<unsigned int>(cycles) * 4);
}
void TMS9918::set_register(int address, uint8_t value) {
// Writes to address 0 are writes to the video RAM. Store
// the value and return.
if(!(address & 1)) {
write_phase_ = false;
// Enqueue the write to occur at the next available slot.
read_ahead_buffer_ = value;
queued_access_ = MemoryAccess::Write;
return;
}
// Writes to address 1 are performed in pairs; if this is the
// low byte of a value, store it and wait for the high byte.
if(!write_phase_) {
low_write_ = value;
write_phase_ = true;
return;
}
write_phase_ = false;
if(value & 0x80) {
// This is a write to a register.
switch(value & 7) {
case 0:
next_screen_mode_ = (next_screen_mode_ & 6) | ((low_write_ & 2) >> 1);
break;
case 1:
next_blank_screen_ = !(low_write_ & 0x40);
generate_interrupts_ = !!(low_write_ & 0x20);
next_screen_mode_ = (next_screen_mode_ & 1) | ((low_write_ & 0x18) >> 2);
sprites_16x16_ = !!(low_write_ & 0x02);
sprites_magnified_ = !!(low_write_ & 0x01);
sprite_height_ = 8;
if(sprites_16x16_) sprite_height_ <<= 1;
if(sprites_magnified_) sprite_height_ <<= 1;
break;
case 2:
pattern_name_address_ = static_cast<uint16_t>((low_write_ & 0xf) << 10);
break;
case 3:
colour_table_address_ = static_cast<uint16_t>(low_write_ << 6);
break;
case 4:
pattern_generator_table_address_ = static_cast<uint16_t>((low_write_ & 0x07) << 11);
break;
case 5:
sprite_attribute_table_address_ = static_cast<uint16_t>((low_write_ & 0x7f) << 7);
break;
case 6:
sprite_generator_table_address_ = static_cast<uint16_t>((low_write_ & 0x07) << 11);
break;
case 7:
text_colour_ = low_write_ >> 4;
background_colour_ = low_write_ & 0xf;
break;
}
} else {
// This is a write to the RAM pointer.
ram_pointer_ = static_cast<uint16_t>(low_write_ | (value << 8));
if(!(value & 0x40)) {
// Officially a 'read' set, so perform lookahead.
queued_access_ = MemoryAccess::Read;
}
}
}
uint8_t TMS9918::get_register(int address) {
write_phase_ = false;
// Reads from address 0 read video RAM, via the read-ahead buffer.
if(!(address & 1)) {
// Enqueue the write to occur at the next available slot.
uint8_t result = read_ahead_buffer_;
queued_access_ = MemoryAccess::Read;
return result;
}
// Reads from address 1 get the status register.
uint8_t result = status_;
status_ &= ~(StatusInterrupt | StatusFifthSprite | StatusSpriteCollision);
return result;
}
HalfCycles TMS9918::get_time_until_interrupt() {
if(!generate_interrupts_) return HalfCycles(-1);
if(get_interrupt_line()) return HalfCycles(0);
const int half_cycles_per_frame = frame_lines_ * 228 * 2;
int half_cycles_remaining = (192 * 228 * 2 + half_cycles_per_frame - half_cycles_into_frame_.as_int()) % half_cycles_per_frame;
return HalfCycles(half_cycles_remaining ? half_cycles_remaining : half_cycles_per_frame);
}
bool TMS9918::get_interrupt_line() {
return (status_ & StatusInterrupt) && generate_interrupts_;
}

86
Components/9918/9918.hpp Normal file
View File

@@ -0,0 +1,86 @@
//
// 9918.hpp
// Clock Signal
//
// Created by Thomas Harte on 25/11/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef TMS9918_hpp
#define TMS9918_hpp
#include "../../Outputs/CRT/CRT.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "Implementation/9918Base.hpp"
#include <cstdint>
namespace TI {
/*!
Provides emulation of the TMS9918a, TMS9928 and TMS9929. Likely in the future to be the
vessel for emulation of sufficiently close derivatives, such as the Master System VDP.
The TMS9918 and descendants are video display generators that own their own RAM, making it
accessible through an implicitly-timed register interface, and (depending on model) can generate
PAL and NTSC component and composite video.
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 {
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();
/*!
Runs the VCP for the number of cycles indicate; it is an implicit assumption of the code
that the input clock rate is 3579545 Hz — the NTSC colour clock rate.
*/
void run_for(const HalfCycles cycles);
/*! Sets a register value. */
void set_register(int address, uint8_t value);
/*! Gets a register value. */
uint8_t get_register(int address);
/*!
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.
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 @c true if the interrupt line is currently active; @c false otherwise.
*/
bool get_interrupt_line();
};
};
#endif /* TMS9918_hpp */

View File

@@ -0,0 +1,101 @@
//
// 9918Base.hpp
// Clock Signal
//
// Created by Thomas Harte on 14/12/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef TMS9918Base_hpp
#define TMS9918Base_hpp
#include "../../../Outputs/CRT/CRT.hpp"
#include "../../../ClockReceiver/ClockReceiver.hpp"
#include <cstdint>
#include <memory>
namespace TI {
class TMS9918Base {
protected:
TMS9918Base();
std::unique_ptr<Outputs::CRT::CRT> crt_;
uint8_t ram_[16384];
uint16_t ram_pointer_ = 0;
uint8_t read_ahead_buffer_ = 0;
enum class MemoryAccess {
Read, Write, None
} queued_access_ = MemoryAccess::None;
uint8_t status_ = 0;
bool write_phase_ = false;
uint8_t low_write_ = 0;
// The various register flags.
int next_screen_mode_ = 0, screen_mode_ = 0;
bool next_blank_screen_ = true, blank_screen_ = true;
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;
uint8_t text_colour_ = 0;
uint8_t background_colour_ = 0;
HalfCycles half_cycles_into_frame_;
int column_ = 0, row_ = 0, output_column_ = 0;
int cycles_error_ = 0;
uint32_t *pixel_target_ = nullptr, *pixel_base_ = nullptr;
void output_border(int cycles);
// Vertical timing details.
int frame_lines_ = 262;
int first_vsync_line_ = 227;
// Horizontal selections.
enum class LineMode {
Text = 0,
Character = 1,
Refresh = 2
} line_mode_ = LineMode::Text;
int first_pixel_column_, first_right_border_column_;
uint8_t pattern_names_[40];
uint8_t pattern_buffer_[40];
uint8_t colour_buffer_[40];
struct SpriteSet {
struct ActiveSprite {
int index = 0;
int row = 0;
uint8_t info[4];
uint8_t image[2];
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 access_pointer_ = 0;
inline void test_sprite(int sprite_number, int screen_row);
inline void get_sprite_contents(int start, int cycles, int screen_row);
};
}
#endif /* TMS9918Base_hpp */

View File

@@ -8,25 +8,15 @@
#include "AY38910.hpp"
using namespace GI;
#include <cmath>
AY38910::AY38910() :
selected_register_(0),
tone_counters_{0, 0, 0}, tone_periods_{0, 0, 0}, tone_outputs_{0, 0, 0},
noise_shift_register_(0xffff), noise_period_(0), noise_counter_(0), noise_output_(0),
envelope_divider_(0), envelope_period_(0), envelope_position_(0),
master_divider_(0),
output_registers_{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
{
output_registers_[8] = output_registers_[9] = output_registers_[10] = 0;
using namespace GI::AY38910;
AY38910::AY38910(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {
// set up envelope lookup tables
for(int c = 0; c < 16; c++)
{
for(int p = 0; p < 32; p++)
{
switch(c)
{
for(int c = 0; c < 16; c++) {
for(int p = 0; p < 32; 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;
@@ -66,37 +56,32 @@ AY38910::AY38910() :
}
}
set_sample_volume_range(0);
}
void AY38910::set_sample_volume_range(std::int16_t range) {
// set up volume lookup table
float max_volume = 8192;
float root_two = sqrtf(2.0f);
for(int v = 0; v < 16; v++)
{
volumes_[v] = (int)(max_volume / powf(root_two, (float)(v ^ 0xf)));
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)));
}
volumes_[0] = 0;
evaluate_output_volume();
}
void AY38910::set_clock_rate(double clock_rate)
{
set_input_rate((float)clock_rate);
}
void AY38910::get_samples(unsigned int number_of_samples, int16_t *target)
{
int c = 0;
while((master_divider_&7) && c < number_of_samples)
{
void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) {
std::size_t c = 0;
while((master_divider_&7) && c < number_of_samples) {
target[c] = output_volume_;
master_divider_++;
c++;
}
while(c < number_of_samples)
{
while(c < number_of_samples) {
#define step_channel(c) \
if(tone_counters_[c]) tone_counters_[c]--;\
else\
{\
else {\
tone_outputs_[c] ^= 1;\
tone_counters_[c] = tone_periods_[c];\
}
@@ -111,8 +96,7 @@ void AY38910::get_samples(unsigned int number_of_samples, int16_t *target)
// ... 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
{
else {
noise_counter_ = noise_period_;
noise_output_ ^= noise_shift_register_&1;
noise_shift_register_ |= ((noise_shift_register_ ^ (noise_shift_register_ >> 3))&1) << 17;
@@ -122,8 +106,7 @@ void AY38910::get_samples(unsigned int number_of_samples, int16_t *target)
// ... 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.
if(envelope_divider_) envelope_divider_--;
else
{
else {
envelope_divider_ = envelope_period_;
envelope_position_ ++;
if(envelope_position_ == 32) envelope_position_ = envelope_overflow_masks_[output_registers_[13]];
@@ -131,8 +114,7 @@ void AY38910::get_samples(unsigned int number_of_samples, int16_t *target)
evaluate_output_volume();
for(int ic = 0; ic < 8 && c < number_of_samples; ic++)
{
for(int ic = 0; ic < 8 && c < number_of_samples; ic++) {
target[c] = output_volume_;
c++;
master_divider_++;
@@ -142,8 +124,7 @@ void AY38910::get_samples(unsigned int number_of_samples, int16_t *target)
master_divider_ &= 7;
}
void AY38910::evaluate_output_volume()
{
void AY38910::evaluate_output_volume() {
int envelope_volume = envelope_shapes_[output_registers_[13]][envelope_position_];
// The output level for a channel is:
@@ -173,54 +154,53 @@ void AY38910::evaluate_output_volume()
#undef channel_volume
// Mix additively.
output_volume_ = (int16_t)(
output_volume_ = static_cast<int16_t>(
volumes_[volumes[0]] * channel_levels[0] +
volumes_[volumes[1]] * channel_levels[1] +
volumes_[volumes[2]] * channel_levels[2]
);
}
void AY38910::select_register(uint8_t r)
{
selected_register_ = r & 0xf;
bool AY38910::is_zero_level() {
// Confirm that the AY is trivially at the zero level if all three volume controls are set to fixed zero.
return output_registers_[0x8] == 0 && output_registers_[0x9] == 0 && output_registers_[0xa] == 0;
}
void AY38910::set_register_value(uint8_t value)
{
// MARK: - Register manipulation
void AY38910::select_register(uint8_t r) {
selected_register_ = r;
}
void AY38910::set_register_value(uint8_t value) {
if(selected_register_ > 15) return;
registers_[selected_register_] = value;
if(selected_register_ < 14)
{
if(selected_register_ < 14) {
int selected_register = selected_register_;
enqueue([=] () {
task_queue_.defer([=] () {
uint8_t masked_value = value;
switch(selected_register)
{
switch(selected_register) {
case 0: case 2: case 4:
case 1: case 3: case 5:
{
case 1: case 3: case 5: {
int channel = selected_register >> 1;
if(selected_register & 1)
tone_periods_[channel] = (tone_periods_[channel] & 0xff) | (uint16_t)((value&0xf) << 8);
tone_periods_[channel] = (tone_periods_[channel] & 0xff) | static_cast<uint16_t>((value&0xf) << 8);
else
tone_periods_[channel] = (tone_periods_[channel] & ~0xff) | value;
tone_counters_[channel] = tone_periods_[channel];
}
break;
case 6:
noise_period_ = value & 0x1f;
noise_counter_ = noise_period_;
break;
case 11:
envelope_period_ = (envelope_period_ & ~0xff) | value;
envelope_divider_ = envelope_period_;
break;
case 12:
envelope_period_ = (envelope_period_ & 0xff) | (int)(value << 8);
envelope_divider_ = envelope_period_;
envelope_period_ = (envelope_period_ & 0xff) | static_cast<int>(value << 8);
break;
case 13:
@@ -231,61 +211,75 @@ void AY38910::set_register_value(uint8_t value)
output_registers_[selected_register] = masked_value;
evaluate_output_volume();
});
} else {
if(port_handler_) port_handler_->set_port_output(selected_register_ == 15, value);
}
}
uint8_t AY38910::get_register_value()
{
// This table ensures that bits that aren't defined within the AY are returned as 1s
// when read. I can't find documentation on this and don't have a machine to test, so
// this is provisionally a guess. TODO: investigate.
uint8_t AY38910::get_register_value() {
// This table ensures that bits that aren't defined within the AY are returned as 0s
// when read, conforming to CPC-sourced unit tests.
const uint8_t register_masks[16] = {
0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0xe0, 0x00,
0xe0, 0xe0, 0xe0, 0x00, 0x00, 0xf0, 0x00, 0x00
0xff, 0x0f, 0xff, 0x0f, 0xff, 0x0f, 0x1f, 0xff,
0x1f, 0x1f, 0x1f, 0xff, 0xff, 0x0f, 0xff, 0xff
};
return registers_[selected_register_] | register_masks[selected_register_];
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];
}
}
uint8_t AY38910::get_port_output(bool port_b)
{
// MARK: - Port handling
uint8_t AY38910::get_port_output(bool port_b) {
return registers_[port_b ? 15 : 14];
}
void AY38910::set_data_input(uint8_t r)
{
data_input_ = r;
// MARK: - Bus handling
void AY38910::set_port_handler(PortHandler *handler) {
port_handler_ = handler;
}
uint8_t AY38910::get_data_output()
{
void AY38910::set_data_input(uint8_t r) {
data_input_ = r;
update_bus();
}
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;
}
}
return data_output_;
}
void AY38910::set_control_lines(ControlLines control_lines)
{
ControlState new_state;
switch((int)control_lines)
{
default: new_state = Inactive; break;
void AY38910::set_control_lines(ControlLines control_lines) {
switch(static_cast<int>(control_lines)) {
default: control_state_ = Inactive; break;
case (int)(BCDIR | BC2 | BC1):
case BCDIR:
case BC1: new_state = LatchAddress; break;
case static_cast<int>(BDIR | BC2 | BC1):
case BDIR:
case BC1: control_state_ = LatchAddress; break;
case (int)(BC2 | BC1): new_state = Read; break;
case (int)(BCDIR | BC2): new_state = Write; break;
case static_cast<int>(BC2 | BC1): control_state_ = Read; break;
case static_cast<int>(BDIR | BC2): control_state_ = Write; break;
}
if(new_state != control_state_)
{
control_state_ = new_state;
switch(new_state)
{
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;
}
update_bus();
}
void AY38910::update_bus() {
switch(control_state_) {
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

@@ -9,28 +9,57 @@
#ifndef AY_3_8910_hpp
#define AY_3_8910_hpp
#include "../../Outputs/Speaker.hpp"
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
#include "../../Concurrency/AsyncTaskQueue.hpp"
namespace GI {
namespace AY38910 {
/*!
A port handler provides all input for an AY's two 8-bit ports, and may optionally receive
active notification of changes in output.
Machines with an AY without ports or with nothing wired to them need not supply a port handler.
Machines that use the AY ports as output but for which polling for changes is acceptable can
instead use AY38910.get_port_output.
*/
class PortHandler {
public:
/*!
Requests the current input on an AY port.
@param port_b @c true if the input being queried is Port B. @c false if it is Port A.
*/
virtual uint8_t get_port_input(bool port_b) {
return 0xff;
}
/*!
Requests the current input on an AY port.
@param port_b @c true if the input being queried is Port B. @c false if it is Port A.
*/
virtual void set_port_output(bool port_b, uint8_t value) {}
};
/*!
Names the control lines used as input to the AY, which uses CP1600 bus semantics.
*/
enum ControlLines {
BC1 = (1 << 0),
BC2 = (1 << 1),
BDIR = (1 << 2)
};
/*!
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
interface ports.
*/
class AY38910: public ::Outputs::Filter<AY38910> {
class AY38910: public ::Outputs::Speaker::SampleSource {
public:
/// Creates a new AY38910.
AY38910();
/// Sets the clock rate at which this AY38910 will be run.
void set_clock_rate(double clock_rate);
enum ControlLines {
BC1 = (1 << 0),
BC2 = (1 << 1),
BCDIR = (1 << 2)
};
AY38910(Concurrency::DeferringAsyncTaskQueue &task_queue);
/// Sets the value the AY would read from its data lines if it were not outputting.
void set_data_input(uint8_t r);
@@ -38,7 +67,7 @@ class AY38910: public ::Outputs::Filter<AY38910> {
/// Gets the value that would appear on the data lines if only the AY is outputting.
uint8_t get_data_output();
/// Sets the
/// Sets the current control line state, as a bit field.
void set_control_lines(ControlLines control_lines);
/*!
@@ -47,27 +76,40 @@ class AY38910: public ::Outputs::Filter<AY38910> {
*/
uint8_t get_port_output(bool port_b);
/*!
Sets the port handler, which will receive a call every time the AY either wants to sample
input or else declare new output. As a convenience, current port output can be obtained
without installing a port handler via get_port_output.
*/
void set_port_handler(PortHandler *);
// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter; not for public consumption
void get_samples(unsigned int number_of_samples, int16_t *target);
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);
private:
int selected_register_;
uint8_t registers_[16], output_registers_[16];
Concurrency::DeferringAsyncTaskQueue &task_queue_;
int master_divider_;
int selected_register_ = 0;
uint8_t registers_[16];
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 tone_periods_[3];
int tone_counters_[3];
int tone_outputs_[3];
int master_divider_ = 0;
int noise_period_;
int noise_counter_;
int noise_shift_register_;
int noise_output_;
int tone_periods_[3] = {0, 0, 0};
int tone_counters_[3] = {0, 0, 0};
int tone_outputs_[3] = {0, 0, 0};
int envelope_period_;
int envelope_divider_;
int envelope_position_;
int noise_period_ = 0;
int noise_counter_ = 0;
int noise_shift_register_ = 0xffff;
int noise_output_ = 0;
int envelope_period_ = 0;
int envelope_divider_ = 0;
int envelope_position_ = 0;
int envelope_shapes_[16][32];
int envelope_overflow_masks_[16];
@@ -88,8 +130,12 @@ class AY38910: public ::Outputs::Filter<AY38910> {
int16_t output_volume_;
inline void evaluate_output_volume();
inline void update_bus();
PortHandler *port_handler_ = nullptr;
};
};
}
}
#endif /* AY_3_8910_hpp */

View File

@@ -0,0 +1,115 @@
//
// KonamiSCC.cpp
// Clock Signal
//
// Created by Thomas Harte on 06/01/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#include "KonamiSCC.hpp"
#include <cstring>
using namespace Konami;
SCC::SCC(Concurrency::DeferringAsyncTaskQueue &task_queue) :
task_queue_(task_queue) {}
bool SCC::is_zero_level() {
return !(channel_enable_ & 0x1f);
}
void SCC::get_samples(std::size_t number_of_samples, std::int16_t *target) {
if(is_zero_level()) {
std::memset(target, 0, sizeof(std::int16_t) * number_of_samples);
return;
}
std::size_t c = 0;
while((master_divider_&7) && c < number_of_samples) {
target[c] = transient_output_level_;
master_divider_++;
c++;
}
while(c < number_of_samples) {
for(int channel = 0; channel < 5; ++channel) {
if(channels_[channel].tone_counter) channels_[channel].tone_counter--;
else {
channels_[channel].offset = (channels_[channel].offset + 1) & 0x1f;
channels_[channel].tone_counter = channels_[channel].period;
}
}
evaluate_output_volume();
for(int ic = 0; ic < 8 && c < number_of_samples; ++ic) {
target[c] = transient_output_level_;
c++;
master_divider_++;
}
}
}
void SCC::write(uint16_t address, uint8_t value) {
address &= 0xff;
if(address < 0x80) ram_[address] = value;
task_queue_.defer([=] {
// Check for a write into waveform memory.
if(address < 0x80) {
waves_[address >> 5].samples[address & 0x1f] = value;
} else switch(address) {
default: break;
case 0x80: case 0x82: case 0x84: case 0x86: case 0x88: {
int channel = (address - 0x80) >> 1;
channels_[channel].period = (channels_[channel].period & ~0xff) | value;
} break;
case 0x81: case 0x83: case 0x85: case 0x87: case 0x89: {
int channel = (address - 0x80) >> 1;
channels_[channel].period = (channels_[channel].period & 0xff) | ((value & 0xf) << 8);
} break;
case 0x8a: case 0x8b: case 0x8c: case 0x8d: case 0x8e:
channels_[address - 0x8a].amplitude = value & 0xf;
break;
case 0x8f:
channel_enable_ = value;
break;
}
evaluate_output_volume();
});
}
void SCC::evaluate_output_volume() {
transient_output_level_ =
static_cast<int16_t>(
((
(channel_enable_ & 0x01) ? static_cast<int8_t>(waves_[0].samples[channels_[0].offset]) * channels_[0].amplitude : 0 +
(channel_enable_ & 0x02) ? static_cast<int8_t>(waves_[1].samples[channels_[1].offset]) * channels_[1].amplitude : 0 +
(channel_enable_ & 0x04) ? static_cast<int8_t>(waves_[2].samples[channels_[2].offset]) * channels_[2].amplitude : 0 +
(channel_enable_ & 0x08) ? static_cast<int8_t>(waves_[3].samples[channels_[3].offset]) * channels_[3].amplitude : 0 +
(channel_enable_ & 0x10) ? static_cast<int8_t>(waves_[3].samples[channels_[4].offset]) * channels_[4].amplitude : 0
) * master_volume_) / (255*15*5)
// Five channels, each with 8-bit samples and 4-bit volumes implies a natural range of 0 to 255*15*5.
);
}
void SCC::set_sample_volume_range(std::int16_t range) {
master_volume_ = range;
evaluate_output_volume();
}
uint8_t SCC::read(uint16_t address) {
address &= 0xff;
if(address < 0x80) {
return ram_[address];
}
return 0xff;
}

View File

@@ -0,0 +1,74 @@
//
// KonamiSCC.hpp
// Clock Signal
//
// Created by Thomas Harte on 06/01/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef KonamiSCC_hpp
#define KonamiSCC_hpp
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
#include "../../Concurrency/AsyncTaskQueue.hpp"
namespace Konami {
/*!
Provides an emulation of Konami's Sound Creative Chip ('SCC').
The SCC is a primitive wavetable synthesis chip, offering 32-sample tables,
and five channels of output. The original SCC uses the same wave for channels
four and five, the SCC+ supports different waves for the two channels.
*/
class SCC: public ::Outputs::Speaker::SampleSource {
public:
/// Creates a new SCC.
SCC(Concurrency::DeferringAsyncTaskQueue &task_queue);
/// As per ::SampleSource; provides a broadphase test for silence.
bool is_zero_level();
/// As per ::SampleSource; provides audio output.
void get_samples(std::size_t number_of_samples, std::int16_t *target);
void set_sample_volume_range(std::int16_t range);
/// Writes to the SCC.
void write(uint16_t address, uint8_t value);
/// Reads from the SCC.
uint8_t read(uint16_t address);
private:
Concurrency::DeferringAsyncTaskQueue &task_queue_;
// State from here on down is accessed ony from the audio thread.
int master_divider_ = 0;
std::int16_t master_volume_ = 0;
int16_t transient_output_level_ = 0;
struct Channel {
int period = 0;
int amplitude = 0;
int tone_counter = 0;
int offset = 0;
} channels_[5];
struct Wavetable {
std::uint8_t samples[32];
} waves_[4];
std::uint8_t channel_enable_ = 0;
std::uint8_t test_register_ = 0;
void evaluate_output_volume();
// This keeps a copy of wave memory that is accessed from the
// main emulation thread.
std::uint8_t ram_[128];
};
}
#endif /* KonamiSCC_hpp */

View File

@@ -0,0 +1,161 @@
//
// SN76489.cpp
// Clock Signal
//
// Created by Thomas Harte on 26/02/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#include "SN76489.hpp"
#include <cassert>
#include <cmath>
using namespace TI;
SN76489::SN76489(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue, int additional_divider) : task_queue_(task_queue) {
set_sample_volume_range(0);
switch(personality) {
case Personality::SN76494:
master_divider_period_ = 2;
shifter_is_16bit_ = false;
break;
case Personality::SN76489:
master_divider_period_ = 16;
shifter_is_16bit_ = false;
break;
case Personality::SMS:
master_divider_period_ = 16;
shifter_is_16bit_ = true;
break;
}
assert((master_divider_period_ % additional_divider) == 0);
assert(additional_divider < master_divider_period_);
master_divider_period_ /= additional_divider;
}
void SN76489::set_sample_volume_range(std::int16_t range) {
// Build a volume table.
double multiplier = pow(10.0, -0.1);
double volume = static_cast<float>(range) / 4.0f; // As there are four channels.
for(int c = 0; c < 16; ++c) {
volumes_[c] = (int)round(volume);
volume *= multiplier;
}
volumes_[15] = 0;
evaluate_output_volume();
}
void SN76489::set_register(uint8_t value) {
task_queue_.defer([value, this] () {
if(value & 0x80) {
active_register_ = value;
}
const int channel = (active_register_ >> 5)&3;
if(active_register_ & 0x10) {
// latch for volume
channels_[channel].volume = value & 0xf;
evaluate_output_volume();
} else {
// latch for tone/data
if(channel < 3) {
if(value & 0x80) {
channels_[channel].divider = (channels_[channel].divider & ~0xf) | (value & 0xf);
} else {
channels_[channel].divider = static_cast<uint16_t>((channels_[channel].divider & 0xf) | ((value & 0x3f) << 4));
}
} else {
// writes to the noise register always reset the shifter
noise_shifter_ = shifter_is_16bit_ ? 0x8000 : 0x4000;
if(value & 4) {
noise_mode_ = shifter_is_16bit_ ? Noise16 : Noise15;
} else {
noise_mode_ = shifter_is_16bit_ ? Periodic16 : Periodic15;
}
channels_[3].divider = static_cast<uint16_t>(0x10 << (value & 3));
// Special case: if these bits are both set, the noise channel should track channel 2,
// which is marked with a divider of 0xffff.
if(channels_[3].divider == 0x80) channels_[3].divider = 0xffff;
}
}
});
}
bool SN76489::is_zero_level() {
return channels_[0].volume == 0xf && channels_[1].volume == 0xf && channels_[2].volume == 0xf && channels_[3].volume == 0xf;
}
void SN76489::evaluate_output_volume() {
output_volume_ = static_cast<int16_t>(
channels_[0].level * volumes_[channels_[0].volume] +
channels_[1].level * volumes_[channels_[1].volume] +
channels_[2].level * volumes_[channels_[2].volume] +
channels_[3].level * volumes_[channels_[3].volume]
);
}
void SN76489::get_samples(std::size_t number_of_samples, std::int16_t *target) {
std::size_t c = 0;
while((master_divider_& (master_divider_period_ - 1)) && c < number_of_samples) {
target[c] = output_volume_;
master_divider_++;
c++;
}
while(c < number_of_samples) {
bool did_flip = false;
#define step_channel(x, s) \
if(channels_[x].counter) channels_[x].counter--;\
else {\
channels_[x].level ^= 1;\
channels_[x].counter = channels_[x].divider;\
s;\
}
step_channel(0, /**/);
step_channel(1, /**/);
step_channel(2, did_flip = true);
#undef step_channel
if(channels_[3].divider != 0xffff) {
if(channels_[3].counter) channels_[3].counter--;
else {
did_flip = true;
channels_[3].counter = channels_[3].divider;
}
}
if(did_flip) {
channels_[3].level = noise_shifter_ & 1;
int new_bit = channels_[3].level;
switch(noise_mode_) {
default: break;
case Noise15:
new_bit ^= (noise_shifter_ >> 1);
break;
case Noise16:
new_bit ^= (noise_shifter_ >> 3);
break;
}
noise_shifter_ >>= 1;
noise_shifter_ |= (new_bit & 1) << (shifter_is_16bit_ ? 15 : 14);
}
evaluate_output_volume();
for(int ic = 0; ic < master_divider_period_ && c < number_of_samples; ++ic) {
target[c] = output_volume_;
c++;
master_divider_++;
}
}
master_divider_ &= (master_divider_period_ - 1);
}

View File

@@ -0,0 +1,68 @@
//
// SN76489.hpp
// Clock Signal
//
// Created by Thomas Harte on 26/02/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef SN76489_hpp
#define SN76489_hpp
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
#include "../../Concurrency/AsyncTaskQueue.hpp"
namespace TI {
class SN76489: public Outputs::Speaker::SampleSource {
public:
enum class Personality {
SN76489,
SN76494,
SMS
};
/// Creates a new SN76489.
SN76489(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue, int additional_divider = 1);
/// Writes a new value to the SN76489.
void set_register(uint8_t value);
// As per SampleSource.
void get_samples(std::size_t number_of_samples, std::int16_t *target);
bool is_zero_level();
void set_sample_volume_range(std::int16_t range);
private:
int master_divider_ = 0;
int master_divider_period_ = 16;
int16_t output_volume_ = 0;
void evaluate_output_volume();
int volumes_[16];
Concurrency::DeferringAsyncTaskQueue &task_queue_;
struct ToneChannel {
// Programmatically-set state; updated by the processor.
uint16_t divider = 0;
uint8_t volume = 0xf;
// Active state; self-evolving as a function of time.
uint16_t counter = 0;
int level = 0;
} channels_[4];
enum {
Periodic15,
Periodic16,
Noise15,
Noise16
} noise_mode_ = Periodic15;
uint16_t noise_shifter_ = 0;
int active_register_ = 0;
bool shifter_is_16bit_ = false;
};
}
#endif /* SN76489_hpp */

View File

@@ -19,26 +19,21 @@ AsyncTaskQueue::AsyncTaskQueue()
serial_dispatch_queue_ = dispatch_queue_create("com.thomasharte.clocksignal.asyntaskqueue", DISPATCH_QUEUE_SERIAL);
#else
thread_.reset(new std::thread([this]() {
while(!should_destruct_)
{
while(!should_destruct_) {
std::function<void(void)> next_function;
// Take lock, check for a new task
std::unique_lock<std::mutex> lock(queue_mutex_);
if(!pending_tasks_.empty())
{
if(!pending_tasks_.empty()) {
next_function = pending_tasks_.front();
pending_tasks_.pop_front();
}
if(next_function)
{
if(next_function) {
// If there is a task, release lock and perform it
lock.unlock();
next_function();
}
else
{
} else {
// If there isn't a task, atomically block on the processing condition and release the lock
// until there's something pending (and then release it again via scope)
processing_condition_.wait(lock);
@@ -48,10 +43,11 @@ AsyncTaskQueue::AsyncTaskQueue()
#endif
}
AsyncTaskQueue::~AsyncTaskQueue()
{
AsyncTaskQueue::~AsyncTaskQueue() {
#ifdef __APPLE__
flush();
dispatch_release(serial_dispatch_queue_);
serial_dispatch_queue_ = nullptr;
#else
should_destruct_ = true;
enqueue([](){});
@@ -60,8 +56,7 @@ AsyncTaskQueue::~AsyncTaskQueue()
#endif
}
void AsyncTaskQueue::enqueue(std::function<void(void)> function)
{
void AsyncTaskQueue::enqueue(std::function<void(void)> function) {
#ifdef __APPLE__
dispatch_async(serial_dispatch_queue_, ^{function();});
#else
@@ -71,8 +66,7 @@ void AsyncTaskQueue::enqueue(std::function<void(void)> function)
#endif
}
void AsyncTaskQueue::flush()
{
void AsyncTaskQueue::flush() {
#ifdef __APPLE__
dispatch_sync(serial_dispatch_queue_, ^{});
#else
@@ -86,3 +80,26 @@ void AsyncTaskQueue::flush()
flush_condition->wait(lock);
#endif
}
DeferringAsyncTaskQueue::~DeferringAsyncTaskQueue() {
perform();
flush();
}
void DeferringAsyncTaskQueue::defer(std::function<void(void)> function) {
if(!deferred_tasks_) {
deferred_tasks_.reset(new std::list<std::function<void(void)>>);
}
deferred_tasks_->push_back(function);
}
void DeferringAsyncTaskQueue::perform() {
if(!deferred_tasks_) return;
std::shared_ptr<std::list<std::function<void(void)>>> deferred_tasks = deferred_tasks_;
deferred_tasks_.reset();
enqueue([deferred_tasks] {
for(auto &function : *deferred_tasks) {
function();
}
});
}

View File

@@ -9,10 +9,12 @@
#ifndef AsyncTaskQueue_hpp
#define AsyncTaskQueue_hpp
#include <atomic>
#include <condition_variable>
#include <functional>
#include <list>
#include <memory>
#include <thread>
#include <list>
#include <condition_variable>
#ifdef __APPLE__
#include <dispatch/dispatch.h>
@@ -22,14 +24,13 @@ namespace Concurrency {
/*!
An async task queue allows a caller to enqueue void(void) functions. Those functions are guaranteed
to be performed serially and asynchronously from the caller. A caller may also request to synchronise,
to be performed serially and asynchronously from the caller. A caller may also request to flush,
causing it to block until all previously-enqueued functions are complete.
*/
class AsyncTaskQueue {
public:
AsyncTaskQueue();
~AsyncTaskQueue();
virtual ~AsyncTaskQueue();
/*!
Adds @c function to the queue.
@@ -58,6 +59,39 @@ class AsyncTaskQueue {
#endif
};
/*!
A deferring async task queue is one that accepts a list of functions to be performed but defers
any action until told to perform. It performs them by enquing a single asynchronous task that will
perform the deferred tasks in order.
It therefore offers similar semantics to an asynchronous task queue, but allows for management of
synchronisation costs, since neither defer nor perform make any effort to be thread safe.
*/
class DeferringAsyncTaskQueue: public AsyncTaskQueue {
public:
~DeferringAsyncTaskQueue();
/*!
Adds a function to the deferral list.
This is not thread safe; it should be serialised with other calls to itself and to perform.
*/
void defer(std::function<void(void)> function);
/*!
Enqueues a function that will perform all currently deferred functions, in the
order that they were deferred.
This is not thread safe; it should be serialised with other calls to itself and to defer.
*/
void perform();
private:
// TODO: this is a shared_ptr because of the issues capturing moveables in C++11;
// switch to a unique_ptr if/when adapting to C++14
std::shared_ptr<std::list<std::function<void(void)>>> deferred_tasks_;
};
}
#endif /* Concurrency_hpp */

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