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

Compare commits

...

965 Commits

Author SHA1 Message Date
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
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
246 changed files with 134006 additions and 4317 deletions

View File

@@ -0,0 +1,209 @@
//
// 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() << 1) {}
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;
}
/*!
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

@@ -25,29 +25,16 @@ WD1770::Status::Status() :
busy(false) {}
WD1770::WD1770(Personality p) :
Storage::Disk::Controller(8000000, 16, 300),
crc_generator_(0x1021, 0xffff),
interesting_event_mask_(Event::Command),
Storage::Disk::MFMController(8000000, 16, 300),
interesting_event_mask_((int)Event1770::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) {
set_is_double_density(false);
posit_event(Event::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;
posit_event((int)Event1770::Command);
}
void WD1770::set_register(int address, uint8_t value) {
@@ -60,7 +47,7 @@ void WD1770::set_register(int address, uint8_t value) {
});
} else {
command_ = value;
posit_event(Event::Command);
posit_event((int)Event1770::Command);
}
}
break;
@@ -124,154 +111,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_) {
unsigned int number_of_cycles = (unsigned int)cycles.as_int();
if(delay_time_ <= number_of_cycles) {
delay_time_ = 0;
posit_event(Event::Timer);
posit_event((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_ = (int)mask; return; case __LINE__:
#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; delay_time_ = ms * 8000; WAIT_FOR_EVENT(Event1770::Timer);
#define WAIT_FOR_BYTES(count) resume_point_ = __LINE__; distance_into_section_ = 0; WAIT_FOR_EVENT(Event::Token); if(get_latest_token().type == Token::Byte) distance_into_section_++; if(distance_into_section_ < count) { interesting_event_mask_ = (int)Event::Token; return; }
#define BEGIN_SECTION() switch(resume_point_) { default:
#define END_SECTION() 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 == (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_++; \
} \
}
@@ -284,7 +148,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;
// +--------+----------+-------------------------+
@@ -304,7 +168,25 @@ void WD1770::process_write_completed() {
// ! 4 ! Forc int ! 1 1 0 1 i3 i2 i1 i0 !
// +--------+----------+-------------------------+
void WD1770::posit_event(Event new_event_type) {
void WD1770::posit_event(int new_event_type) {
if(new_event_type == (int)Event::IndexHole) {
index_hole_count_++;
if(index_hole_count_target_ == index_hole_count_) {
posit_event((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(!(interesting_event_mask_ & (int)new_event_type)) return;
interesting_event_mask_ &= ~new_event_type;
@@ -314,7 +196,7 @@ void WD1770::posit_event(Event new_event_type) {
// Wait for a new command, branch to the appropriate handler.
wait_for_command:
printf("Idle...\n");
data_mode_ = DataMode::Scanning;
set_data_mode(DataMode::Scanning);
index_hole_count_ = 0;
update_status([] (Status &status) {
@@ -322,7 +204,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;
@@ -371,7 +253,7 @@ void WD1770::posit_event(Event new_event_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:
@@ -403,7 +285,7 @@ void WD1770::posit_event(Event new_event_type) {
goto verify;
}
step(step_direction_ ? 1 : -1);
int time_to_wait;
unsigned int time_to_wait;
switch(command_ & 3) {
default:
case 0: time_to_wait = 6; break;
@@ -428,7 +310,7 @@ 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((int)Event::IndexHole | (int)Event::Token);
READ_ID();
if(index_hole_count_ == 6) {
@@ -438,8 +320,8 @@ void WD1770::posit_event(Event new_event_type) {
goto wait_for_command;
}
if(distance_into_section_ == 7) {
data_mode_ = DataMode::Scanning;
if(crc_generator_.get_value()) {
set_data_mode(DataMode::Scanning);
if(get_crc_generator().get_value()) {
update_status([] (Status &status) {
status.crc_error = true;
});
@@ -490,7 +372,7 @@ 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:
@@ -512,7 +394,7 @@ void WD1770::posit_event(Event new_event_type) {
}
type2_get_header:
WAIT_FOR_EVENT(Event::IndexHole | Event::Token);
WAIT_FOR_EVENT((int)Event::IndexHole | (int)Event::Token);
READ_ID();
if(index_hole_count_ == 5) {
@@ -524,11 +406,11 @@ void WD1770::posit_event(Event new_event_type) {
}
if(distance_into_section_ == 7) {
printf("Considering %d/%d\n", header_[0], header_[2]);
data_mode_ = DataMode::Scanning;
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;
@@ -553,20 +435,20 @@ 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;
@@ -580,11 +462,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(get_crc_generator().get_value()) {
printf("CRC error; terminating\n");
update_status([this] (Status &status) {
status.crc_error = true;
@@ -615,24 +497,24 @@ void WD1770::posit_event(Event new_event_type) {
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);
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);
}
@@ -668,11 +550,8 @@ 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);
}
type2_write_crc:
write_crc();
write_byte(0xff);
WAIT_FOR_EVENT(Event::DataWritten);
end_writing();
@@ -711,7 +590,7 @@ 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:
@@ -732,17 +611,17 @@ 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) {
WAIT_FOR_EVENT((int)Event::IndexHole | (int)Event::Token);
if(new_event_type == (int)Event::Token) {
if(!distance_into_section_ && get_latest_token().type == Token::ID) {set_data_mode(DataMode::Reading); distance_into_section_++; }
else if(distance_into_section_ && distance_into_section_ < 7 && get_latest_token().type == Token::Byte) {
if(status_.data_request) {
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;
@@ -750,7 +629,7 @@ void WD1770::posit_event(Event new_event_type) {
distance_into_section_++;
if(distance_into_section_ == 7) {
if(crc_generator_.get_value()) {
if(get_crc_generator().get_value()) {
update_status([] (Status &status) {
status.crc_error = true;
});
@@ -773,7 +652,7 @@ void WD1770::posit_event(Event new_event_type) {
index_hole_count_ = 0;
read_track_read_byte:
WAIT_FOR_EVENT(Event::Token | Event::IndexHole);
WAIT_FOR_EVENT((int)Event::Token | (int)Event::IndexHole);
if(index_hole_count_) {
goto wait_for_command;
}
@@ -783,7 +662,7 @@ void WD1770::posit_event(Event new_event_type) {
});
goto wait_for_command;
}
data_ = latest_token_.byte_value;
data_ = get_latest_token().byte_value;
update_status([] (Status &status) {
status.data_request = true;
});
@@ -814,25 +693,23 @@ void WD1770::posit_event(Event new_event_type) {
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_) {
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;
@@ -855,17 +732,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;
@@ -909,27 +784,5 @@ void WD1770::set_head_load_request(bool head_load) {}
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((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/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;
@@ -87,66 +106,31 @@ class WD1770: public Storage::Disk::Controller {
int index_hole_count_;
int index_hole_count_target_;
int bits_since_token_;
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_.
};
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);
unsigned int delay_time_;
// ID buffer
uint8_t header_[6];
// CRC generator
NumberTheory::CRC16 crc_generator_;
// 1793 head-loading logic
bool head_is_loaded_;
// 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();
};
}

View File

@@ -13,6 +13,8 @@
#include <typeinfo>
#include <cstdio>
#include "../../ClockReceiver/ClockReceiver.hpp"
namespace MOS {
/*!
@@ -250,32 +252,22 @@ template <class T> class MOS6522 {
timer_is_running_[0] = false;\
}
/*!
Runs for a specified number of half cycles.
/*! Runs for a specified number of half cycles. */
inline void run_for(const HalfCycles half_cycles) {
int number_of_half_cycles = half_cycles.as_int();
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--;
number_of_half_cycles--;
}
while(number_of_cycles >= 2) {
while(number_of_half_cycles >= 2) {
phase1();
phase2();
number_of_cycles -= 2;
number_of_half_cycles -= 2;
}
if(number_of_cycles) {
if(number_of_half_cycles) {
phase1();
is_phase2_ = true;
} else {
@@ -283,13 +275,9 @@ template <class T> class MOS6522 {
}
}
/*!
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) {
/*! Runs for a specified number of cycles. */
inline void run_for(const Cycles cycles) {
int number_of_cycles = cycles.as_int();
while(number_of_cycles--) {
phase1();
phase2();

View File

@@ -12,6 +12,8 @@
#include <cstdint>
#include <cstdio>
#include "../../ClockReceiver/ClockReceiver.hpp"
namespace MOS {
/*!
@@ -104,7 +106,9 @@ 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 = (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;

View File

@@ -11,6 +11,7 @@
#include "../../Outputs/CRT/CRT.hpp"
#include "../../Outputs/Speaker.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
namespace MOS {
@@ -99,10 +100,10 @@ template <class T> class MOS6560 {
8, 88, 120, 56,
};
const uint8_t ntsc_chrominances[16] = {
255, 255, 40, 104,
64, 120, 80, 16,
32, 32, 40, 104,
64, 120, 80, 16,
255, 255, 8, 72,
32, 88, 48, 112,
0, 0, 8, 72,
32, 88, 48, 112,
};
const uint8_t *chrominances;
Outputs::CRT::DisplayType display_type;
@@ -149,10 +150,11 @@ template <class T> class MOS6560 {
/*!
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;
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_;
@@ -259,7 +261,7 @@ template <class T> class MOS6560 {
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;
}
@@ -323,7 +325,7 @@ 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(); speaker_->flush(); }
/*!
Writes to a 6560 register.
@@ -406,10 +408,9 @@ template <class T> class MOS6560 {
std::shared_ptr<Outputs::CRT::CRT> crt_;
std::shared_ptr<Speaker> speaker_;
unsigned int cycles_since_speaker_update_;
Cycles cycles_since_speaker_update_;
void update_audio() {
speaker_->run_for_cycles(cycles_since_speaker_update_ >> 2);
cycles_since_speaker_update_ &= 3;
speaker_->run_for(Cycles(cycles_since_speaker_update_.divide(Cycles(4))));
}
// register state

View File

@@ -0,0 +1,226 @@
//
// 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;
bool hsync;
bool vsync;
bool cursor;
uint16_t refresh_address;
uint16_t row_address;
};
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, //
UM6845R, //
MC6845, //
AMS40226 //
};
template <class T> class CRTC6845 {
public:
CRTC6845(Personality p, T &bus_handler) noexcept :
personality_(p), bus_handler_(bus_handler) {}
void select_register(uint8_t r) {
selected_register_ = r;
}
uint8_t get_status() const {
return 0xff;
}
uint8_t get_register() const {
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
};
if(selected_register_ < 16) {
registers_[selected_register_] = value & masks[selected_register_];
}
}
void trigger_light_pen() {
registers_[17] = bus_state_.refresh_address & 0xff;
registers_[16] = bus_state_.refresh_address >> 8;
}
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(bus_state_.hsync) {
bus_state_.hsync = hsync_counter_ != (registers_[3] & 15);
hsync_counter_ = (hsync_counter_ + 1) & 15;
}
perform_bus_cycle_phase2();
}
}
const BusState &get_bus_state() const {
return bus_state_;
}
private:
inline void perform_bus_cycle_phase1() {
bus_state_.display_enable = character_is_visible_ && line_is_visible_;
bus_handler_.perform_bus_cycle_phase1(bus_state_);
}
inline void perform_bus_cycle_phase2() {
bus_state_.display_enable = character_is_visible_ && line_is_visible_;
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;
if(vsync_counter_ == (registers_[3] >> 4)) {
bus_state_.vsync = false;
}
}
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_ = (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];
int selected_register_;
uint8_t character_counter_;
uint8_t line_counter_;
bool character_is_visible_, line_is_visible_;
int hsync_counter_;
int vsync_counter_;
bool is_in_adjustment_period_;
uint16_t line_address_;
uint16_t end_of_line_address_;
};
}
}
#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() {
port_handler_.set_value(0, outputs_[0]);
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.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 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 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, int clock_rate_multiplier, int revolutions_per_minute) :
Storage::Disk::MFMController(clock_rate, clock_rate_multiplier, revolutions_per_minute),
bus_handler_(bus_handler),
main_status_(0),
interesting_event_mask_((int)Event8272::CommandByte),
resume_point_(0),
delay_time_(0),
head_timers_running_(0),
expects_input_(false),
drives_seeking_(0) {
posit_event((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((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);
drives_[c].drive->step(direction);
if(drives_[c].target_head_position >= 0) drives_[c].head_position += direction;
// Check for completion.
if(drives_[c].seek_is_satisfied()) {
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;
}
}
}
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((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((int)Event8272::ResultEmpty);
return result;
} else {
return main_status_;
}
}
void i8272::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
if(drive < 4 && drive >= 0) {
drives_[drive].drive->set_disk(disk);
}
}
#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_ = (int)mask; return; case __LINE__:
#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; interesting_event_mask_ = (int)Event8272::Timer; delay_time_ = MS_TO_CYCLES(ms); is_sleeping_ = false; update_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((int)Event::Token | (int)Event::IndexHole); \
if(event_type == (int)Event::IndexHole) { index_hole_limit_--; } \
else if(get_latest_token().type == Token::ID) goto CONCAT(header_found, __LINE__); \
\
if(index_hole_limit_) goto CONCAT(find_header, __LINE__); \
CONCAT(header_found, __LINE__): 0;\
#define FIND_DATA() \
set_data_mode(DataMode::Scanning); \
CONCAT(find_data, __LINE__): WAIT_FOR_EVENT((int)Event::Token | (int)Event::IndexHole); \
if(event_type == (int)Event::Token) { \
if(get_latest_token().type == Token::Byte || get_latest_token().type == Token::Sync) goto CONCAT(find_data, __LINE__); \
}
#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; \
set_drive(drives_[active_drive_].drive); \
drives_[active_drive_].drive->set_head((unsigned int)active_head_); \
set_is_double_density(command_[0] & 0x40); \
invalidate_track();
#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 == (int)Event::IndexHole) index_hole_count_++;
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 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.
if(!dma_mode_) SetNonDMAExecution();
SET_DRIVE_HEAD_MFM();
LOAD_HEAD();
}
// 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();
if(event_type == (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((int)Event::Token | (int)Event::IndexHole);
if(event_type == (int)Event::Token) {
result_stack_.push_back(get_latest_token().byte_value);
distance_into_section_++;
SetDataRequest();
SetDataDirectionToProcessor();
WAIT_FOR_EVENT((int)Event8272::ResultEmpty | (int)Event::Token | (int)Event::IndexHole);
}
switch(event_type) {
case (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 (int)Event::Token: // The caller hasn't read the old byte yet and a new one has arrived
SetOverrun();
goto abort;
break;
case (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(drives_[active_drive_].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();
end_writing();
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;
read_id_find_next_sector:
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((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(drives_[active_drive_].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((int)Event::DataWritten | (int)Event::IndexHole);
switch(event_type) {
case (int)Event::IndexHole:
SetOverrun();
end_writing();
goto abort;
break;
case (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((int)Event::DataWritten | (int)Event::IndexHole);
if(event_type != (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;
// 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(drives_[drive].seek_is_satisfied()) {
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] = (uint8_t)found_drive;
main_status_ &= ~(1 << found_drive);
SetSeekEnd();
result_stack_.push_back(drives_[found_drive].head_position);
result_stack_.push_back(status_[0]);
} else {
result_stack_.push_back(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;
result_stack_.push_back(
(command_[1] & 7) | // drive and head number
0x08 | // single sided
(drives_[drive].drive->get_is_track_zero() ? 0x10 : 0x00) |
(drives_[drive].drive->get_is_ready() ? 0x20 : 0x00) |
(drives_[drive].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_.push_back(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:
SetAbnormalTermination();
goto post_st012chrn;
// Posts ST0, ST1, ST2, C, H, R and N as a result phase.
post_st012chrn:
SCHEDULE_HEAD_UNLOAD();
result_stack_.push_back(size_);
result_stack_.push_back(sector_);
result_stack_.push_back(head_);
result_stack_.push_back(cylinder_);
result_stack_.push_back(status_[2]);
result_stack_.push_back(status_[1]);
result_stack_.push_back(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(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.
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::Drive::seek_is_satisfied() {
return (target_head_position == head_position) ||
(target_head_position == -1 && 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;
}

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

@@ -0,0 +1,140 @@
//
// 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/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, int clock_rate_multiplier, int revolutions_per_minute);
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);
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive);
bool is_sleeping();
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_;
uint8_t status_[3];
// 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_;
bool has_input_;
bool expects_input_;
// Event stream: the 8272-specific events, plus the current event state.
enum class Event8272: int {
CommandByte = (1 << 3),
Timer = (1 << 4),
ResultEmpty = (1 << 5),
};
void posit_event(int type);
int interesting_event_mask_;
int resume_point_;
bool is_access_command_;
// The counter used for ::Timer events.
int delay_time_;
// The connected drives.
struct Drive {
uint8_t head_position;
// Seeking: persistent state.
enum Phase {
NotSeeking,
Seeking,
CompletedSeeking
} phase;
bool did_seek;
bool seek_failed;
// Seeking: transient state.
int step_rate_counter;
int steps_taken;
int target_head_position; // either an actual number, or -1 to indicate to step until track zero
/// @returns @c true if the currently queued-up seek or recalibrate has reached where it should be.
bool seek_is_satisfied();
// Head state.
int head_unload_delay[2];
bool head_is_loaded[2];
// The connected drive.
std::shared_ptr<Storage::Disk::Drive> drive;
Drive() :
head_position(0), phase(NotSeeking),
drive(new Storage::Disk::Drive),
head_is_loaded{false, false},
head_unload_delay{0, 0} {};
} drives_[4];
int drives_seeking_;
// User-supplied parameters; as per the specify command.
int step_rate_time_;
int head_unload_time_;
int head_load_time_;
bool dma_mode_;
// A count of head unload timers currently running.
int head_timers_running_;
// Transient storage and counters used while reading the disk.
uint8_t header_[6];
int distance_into_section_;
int index_hole_count_, index_hole_limit_;
// Keeps track of the drive and head in use during commands.
int active_drive_;
int active_head_;
// Internal registers.
uint8_t cylinder_, head_, sector_, size_;
// Master switch on not performing any work.
bool is_sleeping_;
};
}
}
#endif /* i8272_hpp */

View File

@@ -8,7 +8,7 @@
#include "AY38910.hpp"
using namespace GI;
using namespace GI::AY38910;
AY38910::AY38910() :
selected_register_(0),
@@ -16,7 +16,8 @@ AY38910::AY38910() :
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_{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
port_handler_(nullptr) {
output_registers_[8] = output_registers_[9] = output_registers_[10] = 0;
// set up envelope lookup tables
@@ -76,7 +77,7 @@ void AY38910::set_clock_rate(double clock_rate) {
}
void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) {
int c = 0;
unsigned int c = 0;
while((master_divider_&7) && c < number_of_samples) {
target[c] = output_volume_;
master_divider_++;
@@ -166,11 +167,14 @@ void AY38910::evaluate_output_volume() {
);
}
#pragma mark - Register manipulation
void AY38910::select_register(uint8_t r) {
selected_register_ = r & 0xf;
selected_register_ = r;
}
void AY38910::set_register_value(uint8_t value) {
if(selected_register_ > 15) return;
registers_[selected_register_] = value;
if(selected_register_ < 14) {
int selected_register = selected_register_;
@@ -185,23 +189,19 @@ void AY38910::set_register_value(uint8_t value) {
tone_periods_[channel] = (tone_periods_[channel] & 0xff) | (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_;
break;
case 13:
@@ -212,53 +212,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.
// 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];
}
}
#pragma mark - Port handling
uint8_t AY38910::get_port_output(bool port_b) {
return registers_[port_b ? 15 : 14];
}
#pragma mark - Bus handling
void AY38910::set_port_handler(PortHandler *handler) {
port_handler_ = handler;
}
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;
default: control_state_ = Inactive; break;
case (int)(BCDIR | BC2 | BC1):
case BCDIR:
case BC1: new_state = LatchAddress; break;
case (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 (int)(BC2 | BC1): control_state_ = Read; break;
case (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

@@ -12,6 +12,43 @@
#include "../../Outputs/Speaker.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
@@ -26,19 +63,13 @@ class AY38910: public ::Outputs::Filter<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)
};
/// Sets the value the AY would read from its data lines if it were not outputting.
void set_data_input(uint8_t r);
/// 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,12 +78,20 @@ 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);
private:
int selected_register_;
uint8_t registers_[16], output_registers_[16];
uint8_t port_inputs_[2];
int master_divider_;
@@ -88,8 +127,12 @@ class AY38910: public ::Outputs::Filter<AY38910> {
int16_t output_volume_;
inline void evaluate_output_volume();
inline void update_bus();
PortHandler *port_handler_;
};
};
}
}
#endif /* AY_3_8910_hpp */

View File

@@ -46,6 +46,7 @@ AsyncTaskQueue::AsyncTaskQueue()
AsyncTaskQueue::~AsyncTaskQueue() {
#ifdef __APPLE__
dispatch_release(serial_dispatch_queue_);
serial_dispatch_queue_ = nullptr;
#else
should_destruct_ = true;
enqueue([](){});

View File

@@ -22,11 +22,10 @@ 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();

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,66 @@
//
// AmstradCPC.hpp
// Clock Signal
//
// Created by Thomas Harte on 30/07/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef AmstradCPC_hpp
#define AmstradCPC_hpp
#include "../ConfigurationTarget.hpp"
#include "../CRTMachine.hpp"
#include "../KeyboardMachine.hpp"
#include <cstdint>
#include <vector>
namespace AmstradCPC {
enum ROMType: int {
OS464 = 0, BASIC464,
OS664, BASIC664,
OS6128, BASIC6128,
AMSDOS
};
enum Key: uint16_t {
#define Line(l, k1, k2, k3, k4, k5, k6, k7, k8) \
k1 = (l << 4) | 0x07, k2 = (l << 4) | 0x06, k3 = (l << 4) | 0x05, k4 = (l << 4) | 0x04,\
k5 = (l << 4) | 0x03, k6 = (l << 4) | 0x02, k7 = (l << 4) | 0x01, k8 = (l << 4) | 0x00,
Line(0, KeyFDot, KeyEnter, KeyF3, KeyF6, KeyF9, KeyDown, KeyRight, KeyUp)
Line(1, KeyF0, KeyF2, KeyF1, KeyF5, KeyF8, KeyF7, KeyCopy, KeyLeft)
Line(2, KeyControl, KeyBackSlash, KeyShift, KeyF4, KeyRightSquareBracket, KeyReturn, KeyLeftSquareBracket, KeyClear)
Line(3, KeyFullStop, KeyForwardSlash, KeyColon, KeySemicolon, KeyP, KeyAt, KeyMinus, KeyCaret)
Line(4, KeyComma, KeyM, KeyK, KeyL, KeyI, KeyO, Key9, Key0)
Line(5, KeySpace, KeyN, KeyJ, KeyH, KeyY, KeyU, Key7, Key8)
Line(6, KeyV, KeyB, KeyF, KeyG, KeyT, KeyR, Key5, Key6)
Line(7, KeyX, KeyC, KeyD, KeyS, KeyW, KeyE, Key3, Key4)
Line(8, KeyZ, KeyCapsLock, KeyA, KeyTab, KeyQ, KeyEscape, Key2, Key1)
Line(9, KeyDelete, KeyJoy1Fire3, KeyJoy2Fire2, KeyJoy1Fire1, KeyJoy1Right, KeyJoy1Left, KeyJoy1Down, KeyJoy1Up)
#undef Line
};
/*!
Models an Amstrad CPC.
*/
class Machine:
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public KeyboardMachine::Machine {
public:
virtual ~Machine();
/// Creates and returns an Amstrad CPC.
static Machine *AmstradCPC();
/// Sets the contents of rom @c type to @c data. Assumed to be a setup step; has no effect once a machine is running.
virtual void set_rom(ROMType type, std::vector<uint8_t> data) = 0;
};
}
#endif /* AmstradCPC_hpp */

View File

@@ -0,0 +1,89 @@
//
// CharacterMapper.cpp
// Clock Signal
//
// Created by Thomas Harte on 11/08/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "CharacterMapper.hpp"
#include "AmstradCPC.hpp"
using namespace AmstradCPC;
uint16_t *CharacterMapper::sequence_for_character(char character) {
#define KEYS(...) {__VA_ARGS__, EndSequence}
#define SHIFT(...) {KeyShift, __VA_ARGS__, EndSequence}
#define X {NotMapped}
static KeySequence key_sequences[] = {
/* NUL */ X, /* SOH */ X,
/* STX */ X, /* ETX */ X,
/* EOT */ X, /* ENQ */ X,
/* ACK */ X, /* BEL */ X,
/* BS */ KEYS(KeyDelete), /* HT */ X,
/* LF */ KEYS(KeyReturn), /* VT */ X,
/* FF */ X, /* CR */ X,
/* SO */ X, /* SI */ X,
/* DLE */ X, /* DC1 */ X,
/* DC2 */ X, /* DC3 */ X,
/* DC4 */ X, /* NAK */ X,
/* SYN */ X, /* ETB */ X,
/* CAN */ X, /* EM */ X,
/* SUB */ X, /* ESC */ X,
/* FS */ X, /* GS */ X,
/* RS */ X, /* US */ X,
/* space */ KEYS(KeySpace), /* ! */ SHIFT(Key1),
/* " */ SHIFT(Key2), /* # */ SHIFT(Key3),
/* $ */ SHIFT(Key4), /* % */ SHIFT(Key5),
/* & */ SHIFT(Key6), /* ' */ SHIFT(Key7),
/* ( */ SHIFT(Key8), /* ) */ SHIFT(Key9),
/* * */ SHIFT(KeyColon), /* + */ SHIFT(KeySemicolon),
/* , */ KEYS(KeyComma), /* - */ KEYS(KeyMinus),
/* . */ KEYS(KeyFullStop), /* / */ KEYS(KeyForwardSlash),
/* 0 */ KEYS(Key0), /* 1 */ KEYS(Key1),
/* 2 */ KEYS(Key2), /* 3 */ KEYS(Key3),
/* 4 */ KEYS(Key4), /* 5 */ KEYS(Key5),
/* 6 */ KEYS(Key6), /* 7 */ KEYS(Key7),
/* 8 */ KEYS(Key8), /* 9 */ KEYS(Key9),
/* : */ KEYS(KeyColon), /* ; */ KEYS(KeySemicolon),
/* < */ SHIFT(KeyComma), /* = */ SHIFT(KeyMinus),
/* > */ SHIFT(KeyFullStop), /* ? */ SHIFT(KeyForwardSlash),
/* @ */ SHIFT(KeyAt), /* A */ SHIFT(KeyA),
/* B */ SHIFT(KeyB), /* C */ SHIFT(KeyC),
/* D */ SHIFT(KeyD), /* E */ SHIFT(KeyE),
/* F */ SHIFT(KeyF), /* G */ SHIFT(KeyG),
/* H */ SHIFT(KeyH), /* I */ SHIFT(KeyI),
/* J */ SHIFT(KeyJ), /* K */ SHIFT(KeyK),
/* L */ SHIFT(KeyL), /* M */ SHIFT(KeyM),
/* N */ SHIFT(KeyN), /* O */ SHIFT(KeyO),
/* P */ SHIFT(KeyP), /* Q */ SHIFT(KeyQ),
/* R */ SHIFT(KeyR), /* S */ SHIFT(KeyS),
/* T */ SHIFT(KeyT), /* U */ SHIFT(KeyU),
/* V */ SHIFT(KeyV), /* W */ SHIFT(KeyW),
/* X */ SHIFT(KeyX), /* Y */ SHIFT(KeyY),
/* Z */ SHIFT(KeyZ), /* [ */ KEYS(KeyLeftSquareBracket),
/* \ */ KEYS(KeyBackSlash), /* ] */ KEYS(KeyRightSquareBracket),
/* ^ */ SHIFT(KeyCaret), /* _ */ SHIFT(Key0),
/* ` */ X, /* a */ KEYS(KeyA),
/* b */ KEYS(KeyB), /* c */ KEYS(KeyC),
/* d */ KEYS(KeyD), /* e */ KEYS(KeyE),
/* f */ KEYS(KeyF), /* g */ KEYS(KeyG),
/* h */ KEYS(KeyH), /* i */ KEYS(KeyI),
/* j */ KEYS(KeyJ), /* k */ KEYS(KeyK),
/* l */ KEYS(KeyL), /* m */ KEYS(KeyM),
/* n */ KEYS(KeyN), /* o */ KEYS(KeyO),
/* p */ KEYS(KeyP), /* q */ KEYS(KeyQ),
/* r */ KEYS(KeyR), /* s */ KEYS(KeyS),
/* t */ KEYS(KeyT), /* u */ KEYS(KeyU),
/* v */ KEYS(KeyV), /* w */ KEYS(KeyW),
/* x */ KEYS(KeyX), /* y */ KEYS(KeyY),
/* z */ KEYS(KeyZ), /* { */ X,
/* | */ SHIFT(KeyAt), /* } */ X,
/* ~ */ X
};
#undef KEYS
#undef SHIFT
#undef X
return table_lookup_sequence_for_character(key_sequences, sizeof(key_sequences), character);
}

View File

@@ -0,0 +1,23 @@
//
// CharacterMapper.hpp
// Clock Signal
//
// Created by Thomas Harte on 11/08/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef Machines_AmstradCPC_CharacterMapper_hpp
#define Machines_AmstradCPC_CharacterMapper_hpp
#include "../Typer.hpp"
namespace AmstradCPC {
class CharacterMapper: public ::Utility::CharacterMapper {
public:
uint16_t *sequence_for_character(char character);
};
}
#endif /* CharacterMapper_hpp */

View File

@@ -10,148 +10,200 @@
#include <algorithm>
#include <stdio.h>
#include "Cartridges/CartridgeAtari8k.hpp"
#include "Cartridges/CartridgeAtari16k.hpp"
#include "Cartridges/CartridgeAtari32k.hpp"
#include "Cartridges/CartridgeActivisionStack.hpp"
#include "Cartridges/CartridgeCBSRAMPlus.hpp"
#include "Cartridges/CartridgeCommaVid.hpp"
#include "Cartridges/CartridgeMegaBoy.hpp"
#include "Cartridges/CartridgeMNetwork.hpp"
#include "Cartridges/CartridgeParkerBros.hpp"
#include "Cartridges/CartridgePitfall2.hpp"
#include "Cartridges/CartridgeTigervision.hpp"
#include "Cartridges/CartridgeUnpaged.hpp"
#include "Cartridges/Atari8k.hpp"
#include "Cartridges/Atari16k.hpp"
#include "Cartridges/Atari32k.hpp"
#include "Cartridges/ActivisionStack.hpp"
#include "Cartridges/CBSRAMPlus.hpp"
#include "Cartridges/CommaVid.hpp"
#include "Cartridges/MegaBoy.hpp"
#include "Cartridges/MNetwork.hpp"
#include "Cartridges/ParkerBros.hpp"
#include "Cartridges/Pitfall2.hpp"
#include "Cartridges/Tigervision.hpp"
#include "Cartridges/Unpaged.hpp"
using namespace Atari2600;
namespace {
static const double NTSC_clock_rate = 1194720;
static const double PAL_clock_rate = 1182298;
}
Machine::Machine() :
frame_record_pointer_(0),
is_ntsc_(true) {
set_clock_rate(NTSC_clock_rate);
}
namespace Atari2600 {
void Machine::setup_output(float aspect_ratio) {
bus_->tia_.reset(new TIA);
bus_->speaker_.reset(new Speaker);
bus_->speaker_->set_input_rate((float)(get_clock_rate() / (double)CPUTicksPerAudioTick));
bus_->tia_->get_crt()->set_delegate(this);
}
void Machine::close_output() {
bus_.reset();
}
Machine::~Machine() {
close_output();
}
void Machine::set_digital_input(Atari2600DigitalInput input, bool state) {
switch (input) {
case Atari2600DigitalInputJoy1Up: bus_->mos6532_.update_port_input(0, 0x10, state); break;
case Atari2600DigitalInputJoy1Down: bus_->mos6532_.update_port_input(0, 0x20, state); break;
case Atari2600DigitalInputJoy1Left: bus_->mos6532_.update_port_input(0, 0x40, state); break;
case Atari2600DigitalInputJoy1Right: bus_->mos6532_.update_port_input(0, 0x80, state); break;
case Atari2600DigitalInputJoy2Up: bus_->mos6532_.update_port_input(0, 0x01, state); break;
case Atari2600DigitalInputJoy2Down: bus_->mos6532_.update_port_input(0, 0x02, state); break;
case Atari2600DigitalInputJoy2Left: bus_->mos6532_.update_port_input(0, 0x04, state); break;
case Atari2600DigitalInputJoy2Right: bus_->mos6532_.update_port_input(0, 0x08, state); break;
// TODO: latching
case Atari2600DigitalInputJoy1Fire: if(state) bus_->tia_input_value_[0] &= ~0x80; else bus_->tia_input_value_[0] |= 0x80; break;
case Atari2600DigitalInputJoy2Fire: if(state) bus_->tia_input_value_[1] &= ~0x80; else bus_->tia_input_value_[1] |= 0x80; break;
default: break;
}
}
void Machine::set_switch_is_enabled(Atari2600Switch input, bool state) {
switch(input) {
case Atari2600SwitchReset: bus_->mos6532_.update_port_input(1, 0x01, state); break;
case Atari2600SwitchSelect: bus_->mos6532_.update_port_input(1, 0x02, state); break;
case Atari2600SwitchColour: bus_->mos6532_.update_port_input(1, 0x08, state); break;
case Atari2600SwitchLeftPlayerDifficulty: bus_->mos6532_.update_port_input(1, 0x40, state); break;
case Atari2600SwitchRightPlayerDifficulty: bus_->mos6532_.update_port_input(1, 0x80, state); break;
}
}
void Machine::configure_as_target(const StaticAnalyser::Target &target) {
const std::vector<uint8_t> &rom = target.cartridges.front()->get_segments().front().data;
switch(target.atari.paging_model) {
case StaticAnalyser::Atari2600PagingModel::ActivisionStack: bus_.reset(new CartridgeActivisionStack(rom)); break;
case StaticAnalyser::Atari2600PagingModel::CBSRamPlus: bus_.reset(new CartridgeCBSRAMPlus(rom)); break;
case StaticAnalyser::Atari2600PagingModel::CommaVid: bus_.reset(new CartridgeCommaVid(rom)); break;
case StaticAnalyser::Atari2600PagingModel::MegaBoy: bus_.reset(new CartridgeMegaBoy(rom)); break;
case StaticAnalyser::Atari2600PagingModel::MNetwork: bus_.reset(new CartridgeMNetwork(rom)); break;
case StaticAnalyser::Atari2600PagingModel::None: bus_.reset(new CartridgeUnpaged(rom)); break;
case StaticAnalyser::Atari2600PagingModel::ParkerBros: bus_.reset(new CartridgeParkerBros(rom)); break;
case StaticAnalyser::Atari2600PagingModel::Pitfall2: bus_.reset(new CartridgePitfall2(rom)); break;
case StaticAnalyser::Atari2600PagingModel::Tigervision: bus_.reset(new CartridgeTigervision(rom)); break;
case StaticAnalyser::Atari2600PagingModel::Atari8k:
if(target.atari.uses_superchip) {
bus_.reset(new CartridgeAtari8kSuperChip(rom));
} else {
bus_.reset(new CartridgeAtari8k(rom));
}
break;
case StaticAnalyser::Atari2600PagingModel::Atari16k:
if(target.atari.uses_superchip) {
bus_.reset(new CartridgeAtari16kSuperChip(rom));
} else {
bus_.reset(new CartridgeAtari16k(rom));
}
break;
case StaticAnalyser::Atari2600PagingModel::Atari32k:
if(target.atari.uses_superchip) {
bus_.reset(new CartridgeAtari32kSuperChip(rom));
} else {
bus_.reset(new CartridgeAtari32k(rom));
}
break;
}
}
#pragma mark - CRT delegate
void Machine::crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, unsigned int number_of_frames, unsigned int number_of_unexpected_vertical_syncs) {
const size_t number_of_frame_records = sizeof(frame_records_) / sizeof(frame_records_[0]);
frame_records_[frame_record_pointer_ % number_of_frame_records].number_of_frames = number_of_frames;
frame_records_[frame_record_pointer_ % number_of_frame_records].number_of_unexpected_vertical_syncs = number_of_unexpected_vertical_syncs;
frame_record_pointer_ ++;
if(frame_record_pointer_ >= 6) {
unsigned int total_number_of_frames = 0;
unsigned int total_number_of_unexpected_vertical_syncs = 0;
for(size_t c = 0; c < number_of_frame_records; c++) {
total_number_of_frames += frame_records_[c].number_of_frames;
total_number_of_unexpected_vertical_syncs += frame_records_[c].number_of_unexpected_vertical_syncs;
class ConcreteMachine:
public Machine,
public Outputs::CRT::Delegate {
public:
ConcreteMachine() :
frame_record_pointer_(0),
is_ntsc_(true) {
set_clock_rate(NTSC_clock_rate);
}
if(total_number_of_unexpected_vertical_syncs >= total_number_of_frames >> 1) {
for(size_t c = 0; c < number_of_frame_records; c++) {
frame_records_[c].number_of_frames = 0;
frame_records_[c].number_of_unexpected_vertical_syncs = 0;
}
is_ntsc_ ^= true;
double clock_rate;
if(is_ntsc_) {
clock_rate = NTSC_clock_rate;
bus_->tia_->set_output_mode(TIA::OutputMode::NTSC);
} else {
clock_rate = PAL_clock_rate;
bus_->tia_->set_output_mode(TIA::OutputMode::PAL);
}
bus_->speaker_->set_input_rate((float)(clock_rate / (double)CPUTicksPerAudioTick));
bus_->speaker_->set_high_frequency_cut_off((float)(clock_rate / ((double)CPUTicksPerAudioTick * 2.0)));
set_clock_rate(clock_rate);
~ConcreteMachine() {
close_output();
}
}
void configure_as_target(const StaticAnalyser::Target &target) {
const std::vector<uint8_t> &rom = target.media.cartridges.front()->get_segments().front().data;
switch(target.atari.paging_model) {
case StaticAnalyser::Atari2600PagingModel::ActivisionStack: bus_.reset(new Cartridge::Cartridge<Cartridge::ActivisionStack>(rom)); break;
case StaticAnalyser::Atari2600PagingModel::CBSRamPlus: bus_.reset(new Cartridge::Cartridge<Cartridge::CBSRAMPlus>(rom)); break;
case StaticAnalyser::Atari2600PagingModel::CommaVid: bus_.reset(new Cartridge::Cartridge<Cartridge::CommaVid>(rom)); break;
case StaticAnalyser::Atari2600PagingModel::MegaBoy: bus_.reset(new Cartridge::Cartridge<Cartridge::MegaBoy>(rom)); break;
case StaticAnalyser::Atari2600PagingModel::MNetwork: bus_.reset(new Cartridge::Cartridge<Cartridge::MNetwork>(rom)); break;
case StaticAnalyser::Atari2600PagingModel::None: bus_.reset(new Cartridge::Cartridge<Cartridge::Unpaged>(rom)); break;
case StaticAnalyser::Atari2600PagingModel::ParkerBros: bus_.reset(new Cartridge::Cartridge<Cartridge::ParkerBros>(rom)); break;
case StaticAnalyser::Atari2600PagingModel::Pitfall2: bus_.reset(new Cartridge::Cartridge<Cartridge::Pitfall2>(rom)); break;
case StaticAnalyser::Atari2600PagingModel::Tigervision: bus_.reset(new Cartridge::Cartridge<Cartridge::Tigervision>(rom)); break;
case StaticAnalyser::Atari2600PagingModel::Atari8k:
if(target.atari.uses_superchip) {
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari8kSuperChip>(rom));
} else {
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari8k>(rom));
}
break;
case StaticAnalyser::Atari2600PagingModel::Atari16k:
if(target.atari.uses_superchip) {
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari16kSuperChip>(rom));
} else {
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari16k>(rom));
}
break;
case StaticAnalyser::Atari2600PagingModel::Atari32k:
if(target.atari.uses_superchip) {
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari32kSuperChip>(rom));
} else {
bus_.reset(new Cartridge::Cartridge<Cartridge::Atari32k>(rom));
}
break;
}
}
bool insert_media(const StaticAnalyser::Media &media) {
return false;
}
void set_digital_input(Atari2600DigitalInput input, bool state) {
switch (input) {
case Atari2600DigitalInputJoy1Up: bus_->mos6532_.update_port_input(0, 0x10, state); break;
case Atari2600DigitalInputJoy1Down: bus_->mos6532_.update_port_input(0, 0x20, state); break;
case Atari2600DigitalInputJoy1Left: bus_->mos6532_.update_port_input(0, 0x40, state); break;
case Atari2600DigitalInputJoy1Right: bus_->mos6532_.update_port_input(0, 0x80, state); break;
case Atari2600DigitalInputJoy2Up: bus_->mos6532_.update_port_input(0, 0x01, state); break;
case Atari2600DigitalInputJoy2Down: bus_->mos6532_.update_port_input(0, 0x02, state); break;
case Atari2600DigitalInputJoy2Left: bus_->mos6532_.update_port_input(0, 0x04, state); break;
case Atari2600DigitalInputJoy2Right: bus_->mos6532_.update_port_input(0, 0x08, state); break;
// TODO: latching
case Atari2600DigitalInputJoy1Fire: if(state) bus_->tia_input_value_[0] &= ~0x80; else bus_->tia_input_value_[0] |= 0x80; break;
case Atari2600DigitalInputJoy2Fire: if(state) bus_->tia_input_value_[1] &= ~0x80; else bus_->tia_input_value_[1] |= 0x80; break;
default: break;
}
}
void set_switch_is_enabled(Atari2600Switch input, bool state) {
switch(input) {
case Atari2600SwitchReset: bus_->mos6532_.update_port_input(1, 0x01, state); break;
case Atari2600SwitchSelect: bus_->mos6532_.update_port_input(1, 0x02, state); break;
case Atari2600SwitchColour: bus_->mos6532_.update_port_input(1, 0x08, state); break;
case Atari2600SwitchLeftPlayerDifficulty: bus_->mos6532_.update_port_input(1, 0x40, state); break;
case Atari2600SwitchRightPlayerDifficulty: bus_->mos6532_.update_port_input(1, 0x80, state); break;
}
}
void set_reset_switch(bool state) {
bus_->set_reset_line(state);
}
// to satisfy CRTMachine::Machine
void setup_output(float aspect_ratio) {
bus_->tia_.reset(new TIA);
bus_->speaker_.reset(new Speaker);
bus_->speaker_->set_input_rate((float)(get_clock_rate() / (double)CPUTicksPerAudioTick));
bus_->tia_->get_crt()->set_delegate(this);
}
void close_output() {
bus_.reset();
}
std::shared_ptr<Outputs::CRT::CRT> get_crt() {
return bus_->tia_->get_crt();
}
std::shared_ptr<Outputs::Speaker> get_speaker() {
return bus_->speaker_;
}
void run_for(const Cycles cycles) {
bus_->run_for(cycles);
}
// to satisfy Outputs::CRT::Delegate
void crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, unsigned int number_of_frames, unsigned int number_of_unexpected_vertical_syncs) {
const size_t number_of_frame_records = sizeof(frame_records_) / sizeof(frame_records_[0]);
frame_records_[frame_record_pointer_ % number_of_frame_records].number_of_frames = number_of_frames;
frame_records_[frame_record_pointer_ % number_of_frame_records].number_of_unexpected_vertical_syncs = number_of_unexpected_vertical_syncs;
frame_record_pointer_ ++;
if(frame_record_pointer_ >= 6) {
unsigned int total_number_of_frames = 0;
unsigned int total_number_of_unexpected_vertical_syncs = 0;
for(size_t c = 0; c < number_of_frame_records; c++) {
total_number_of_frames += frame_records_[c].number_of_frames;
total_number_of_unexpected_vertical_syncs += frame_records_[c].number_of_unexpected_vertical_syncs;
}
if(total_number_of_unexpected_vertical_syncs >= total_number_of_frames >> 1) {
for(size_t c = 0; c < number_of_frame_records; c++) {
frame_records_[c].number_of_frames = 0;
frame_records_[c].number_of_unexpected_vertical_syncs = 0;
}
is_ntsc_ ^= true;
double clock_rate;
if(is_ntsc_) {
clock_rate = NTSC_clock_rate;
bus_->tia_->set_output_mode(TIA::OutputMode::NTSC);
} else {
clock_rate = PAL_clock_rate;
bus_->tia_->set_output_mode(TIA::OutputMode::PAL);
}
bus_->speaker_->set_input_rate((float)(clock_rate / (double)CPUTicksPerAudioTick));
bus_->speaker_->set_high_frequency_cut_off((float)(clock_rate / ((double)CPUTicksPerAudioTick * 2.0)));
set_clock_rate(clock_rate);
}
}
}
private:
// the bus
std::unique_ptr<Bus> bus_;
// output frame rate tracker
struct FrameRecord {
unsigned int number_of_frames;
unsigned int number_of_unexpected_vertical_syncs;
FrameRecord() : number_of_frames(0), number_of_unexpected_vertical_syncs(0) {}
} frame_records_[4];
unsigned int frame_record_pointer_;
bool is_ntsc_;
};
}
using namespace Atari2600;
Machine *Machine::Atari2600() {
return new Atari2600::ConcreteMachine;
}
Machine::~Machine() {}

View File

@@ -9,59 +9,33 @@
#ifndef Atari2600_cpp
#define Atari2600_cpp
#include <stdint.h>
#include "../../Processors/6502/CPU6502.hpp"
#include "../CRTMachine.hpp"
#include "Bus.hpp"
#include "PIA.hpp"
#include "Speaker.hpp"
#include "TIA.hpp"
#include "../ConfigurationTarget.hpp"
#include "../CRTMachine.hpp"
#include "Atari2600Inputs.h"
namespace Atari2600 {
/*!
Models an Atari 2600.
*/
class Machine:
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public Outputs::CRT::Delegate {
public ConfigurationTarget::Machine {
public:
Machine();
~Machine();
virtual ~Machine();
void configure_as_target(const StaticAnalyser::Target &target);
void switch_region();
/// Creates and returns an Atari 2600 on the heap.
static Machine *Atari2600();
void set_digital_input(Atari2600DigitalInput input, bool state);
void set_switch_is_enabled(Atari2600Switch input, bool state);
void set_reset_line(bool state) { bus_->set_reset_line(state); }
/// Sets @c input to @c state.
virtual void set_digital_input(Atari2600DigitalInput input, bool state) = 0;
// to satisfy CRTMachine::Machine
virtual void setup_output(float aspect_ratio);
virtual void close_output();
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() { return bus_->tia_->get_crt(); }
virtual std::shared_ptr<Outputs::Speaker> get_speaker() { return bus_->speaker_; }
virtual void run_for_cycles(int number_of_cycles) { bus_->run_for_cycles(number_of_cycles); }
/// Sets the switch @c input to @c state.
virtual void set_switch_is_enabled(Atari2600Switch input, bool state) = 0;
// to satisfy Outputs::CRT::Delegate
virtual void crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, unsigned int number_of_frames, unsigned int number_of_unexpected_vertical_syncs);
private:
// the bus
std::unique_ptr<Bus> bus_;
// output frame rate tracker
struct FrameRecord {
unsigned int number_of_frames;
unsigned int number_of_unexpected_vertical_syncs;
FrameRecord() : number_of_frames(0), number_of_unexpected_vertical_syncs(0) {}
} frame_records_[4];
unsigned int frame_record_pointer_;
bool is_ntsc_;
// Presses or releases the reset button.
virtual void set_reset_switch(bool state) = 0;
};
}

View File

@@ -14,17 +14,17 @@
#include "Speaker.hpp"
#include "TIA.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
namespace Atari2600 {
class Bus {
public:
Bus() :
tia_input_value_{0xff, 0xff},
cycles_since_speaker_update_(0),
cycles_since_video_update_(0),
cycles_since_6532_update_(0) {}
cycles_since_speaker_update_(0) {}
virtual void run_for_cycles(int number_of_cycles) = 0;
virtual void run_for(const Cycles cycles) = 0;
virtual void set_reset_line(bool state) = 0;
// the RIOT, TIA and speaker
@@ -37,25 +37,21 @@ class Bus {
protected:
// speaker backlog accumlation counter
unsigned int cycles_since_speaker_update_;
Cycles cycles_since_speaker_update_;
inline void update_audio() {
unsigned int audio_cycles = cycles_since_speaker_update_ / (CPUTicksPerAudioTick * 3);
cycles_since_speaker_update_ %= (CPUTicksPerAudioTick * 3);
speaker_->run_for_cycles(audio_cycles);
speaker_->run_for(cycles_since_speaker_update_.divide(Cycles(CPUTicksPerAudioTick * 3)));
}
// video backlog accumulation counter
unsigned int cycles_since_video_update_;
Cycles cycles_since_video_update_;
inline void update_video() {
tia_->run_for_cycles((int)cycles_since_video_update_);
cycles_since_video_update_ = 0;
tia_->run_for(cycles_since_video_update_.flush());
}
// RIOT backlog accumulation counter
unsigned int cycles_since_6532_update_;
Cycles cycles_since_6532_update_;
inline void update_6532() {
mos6532_.run_for_cycles(cycles_since_6532_update_);
cycles_since_6532_update_ = 0;
mos6532_.run_for(cycles_since_6532_update_.flush());
}
};

View File

@@ -6,30 +6,30 @@
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef Atari2600_CartridgeActivisionStack_hpp
#define Atari2600_CartridgeActivisionStack_hpp
#ifndef Atari2600_ActivisionStack_hpp
#define Atari2600_ActivisionStack_hpp
namespace Atari2600 {
namespace Cartridge {
class CartridgeActivisionStack: public Cartridge<CartridgeActivisionStack> {
class ActivisionStack: public BusExtender {
public:
CartridgeActivisionStack(const std::vector<uint8_t> &rom) :
Cartridge(rom),
last_opcode_(0x00) {
rom_ptr_ = rom_.data();
}
ActivisionStack(uint8_t *rom_base, size_t rom_size) :
BusExtender(rom_base, rom_size),
rom_ptr_(rom_base),
last_opcode_(0x00) {}
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
if(!(address & 0x1000)) return;
// This is a bit of a hack; a real cartridge can't see either the sync or read lines, and can't see
// address line 13. Instead it looks for a pattern in recent address accesses that would imply an
// RST or JSR.
if(operation == CPU6502::BusOperation::ReadOpcode && (last_opcode_ == 0x20 || last_opcode_ == 0x60)) {
if(operation == CPU::MOS6502::BusOperation::ReadOpcode && (last_opcode_ == 0x20 || last_opcode_ == 0x60)) {
if(address & 0x2000) {
rom_ptr_ = rom_.data();
rom_ptr_ = rom_base_;
} else {
rom_ptr_ = rom_.data() + 4096;
rom_ptr_ = rom_base_ + 4096;
}
}
@@ -37,7 +37,7 @@ class CartridgeActivisionStack: public Cartridge<CartridgeActivisionStack> {
*value = rom_ptr_[address & 4095];
}
if(operation == CPU6502::BusOperation::ReadOpcode) last_opcode_ = *value;
if(operation == CPU::MOS6502::BusOperation::ReadOpcode) last_opcode_ = *value;
}
private:
@@ -45,6 +45,7 @@ class CartridgeActivisionStack: public Cartridge<CartridgeActivisionStack> {
uint8_t last_opcode_;
};
}
}
#endif /* Atari2600_CartridgeActivisionStack_hpp */

View File

@@ -12,19 +12,19 @@
#include "Cartridge.hpp"
namespace Atari2600 {
namespace Cartridge {
class CartridgeAtari16k: public Cartridge<CartridgeAtari16k> {
class Atari16k: public BusExtender {
public:
CartridgeAtari16k(const std::vector<uint8_t> &rom) :
Cartridge(rom) {
rom_ptr_ = rom_.data();
}
Atari16k(uint8_t *rom_base, size_t rom_size) :
BusExtender(rom_base, rom_size),
rom_ptr_(rom_base) {}
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
address &= 0x1fff;
if(!(address & 0x1000)) return;
if(address >= 0x1ff6 && address <= 0x1ff9) rom_ptr_ = rom_.data() + (address - 0x1ff6) * 4096;
if(address >= 0x1ff6 && address <= 0x1ff9) rom_ptr_ = rom_base_ + (address - 0x1ff6) * 4096;
if(isReadOperation(operation)) {
*value = rom_ptr_[address & 4095];
@@ -35,18 +35,17 @@ class CartridgeAtari16k: public Cartridge<CartridgeAtari16k> {
uint8_t *rom_ptr_;
};
class CartridgeAtari16kSuperChip: public Cartridge<CartridgeAtari16kSuperChip> {
class Atari16kSuperChip: public BusExtender {
public:
CartridgeAtari16kSuperChip(const std::vector<uint8_t> &rom) :
Cartridge(rom) {
rom_ptr_ = rom_.data();
}
Atari16kSuperChip(uint8_t *rom_base, size_t rom_size) :
BusExtender(rom_base, rom_size),
rom_ptr_(rom_base) {}
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
address &= 0x1fff;
if(!(address & 0x1000)) return;
if(address >= 0x1ff6 && address <= 0x1ff9) rom_ptr_ = rom_.data() + (address - 0x1ff6) * 4096;
if(address >= 0x1ff6 && address <= 0x1ff9) rom_ptr_ = rom_base_ + (address - 0x1ff6) * 4096;
if(isReadOperation(operation)) {
*value = rom_ptr_[address & 4095];
@@ -61,6 +60,7 @@ class CartridgeAtari16kSuperChip: public Cartridge<CartridgeAtari16kSuperChip> {
uint8_t ram_[128];
};
}
}
#endif /* Atari2600_CartridgeAtari16k_hpp */

View File

@@ -12,19 +12,17 @@
#include "Cartridge.hpp"
namespace Atari2600 {
namespace Cartridge {
class CartridgeAtari32k: public Cartridge<CartridgeAtari32k> {
class Atari32k: public BusExtender {
public:
CartridgeAtari32k(const std::vector<uint8_t> &rom) :
Cartridge(rom) {
rom_ptr_ = rom_.data();
}
Atari32k(uint8_t *rom_base, size_t rom_size) : BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {}
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
address &= 0x1fff;
if(!(address & 0x1000)) return;
if(address >= 0x1ff4 && address <= 0x1ffb) rom_ptr_ = rom_.data() + (address - 0x1ff4) * 4096;
if(address >= 0x1ff4 && address <= 0x1ffb) rom_ptr_ = rom_base_ + (address - 0x1ff4) * 4096;
if(isReadOperation(operation)) {
*value = rom_ptr_[address & 4095];
@@ -35,18 +33,15 @@ class CartridgeAtari32k: public Cartridge<CartridgeAtari32k> {
uint8_t *rom_ptr_;
};
class CartridgeAtari32kSuperChip: public Cartridge<CartridgeAtari32kSuperChip> {
class Atari32kSuperChip: public BusExtender {
public:
CartridgeAtari32kSuperChip(const std::vector<uint8_t> &rom) :
Cartridge(rom) {
rom_ptr_ = rom_.data();
}
Atari32kSuperChip(uint8_t *rom_base, size_t rom_size) : BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {}
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
address &= 0x1fff;
if(!(address & 0x1000)) return;
if(address >= 0x1ff4 && address <= 0x1ffb) rom_ptr_ = rom_.data() + (address - 0x1ff4) * 4096;
if(address >= 0x1ff4 && address <= 0x1ffb) rom_ptr_ = rom_base_ + (address - 0x1ff4) * 4096;
if(isReadOperation(operation)) {
*value = rom_ptr_[address & 4095];
@@ -61,6 +56,7 @@ class CartridgeAtari32kSuperChip: public Cartridge<CartridgeAtari32kSuperChip> {
uint8_t ram_[128];
};
}
}
#endif /* Atari2600_CartridgeAtari32k_hpp */

View File

@@ -12,20 +12,18 @@
#include "Cartridge.hpp"
namespace Atari2600 {
namespace Cartridge {
class CartridgeAtari8k: public Cartridge<CartridgeAtari8k> {
class Atari8k: public BusExtender {
public:
CartridgeAtari8k(const std::vector<uint8_t> &rom) :
Cartridge(rom) {
rom_ptr_ = rom_.data();
}
Atari8k(uint8_t *rom_base, size_t rom_size) : BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {}
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
address &= 0x1fff;
if(!(address & 0x1000)) return;
if(address == 0x1ff8) rom_ptr_ = rom_.data();
else if(address == 0x1ff9) rom_ptr_ = rom_.data() + 4096;
if(address == 0x1ff8) rom_ptr_ = rom_base_;
else if(address == 0x1ff9) rom_ptr_ = rom_base_ + 4096;
if(isReadOperation(operation)) {
*value = rom_ptr_[address & 4095];
@@ -36,19 +34,16 @@ class CartridgeAtari8k: public Cartridge<CartridgeAtari8k> {
uint8_t *rom_ptr_;
};
class CartridgeAtari8kSuperChip: public Cartridge<CartridgeAtari8kSuperChip> {
class Atari8kSuperChip: public BusExtender {
public:
CartridgeAtari8kSuperChip(const std::vector<uint8_t> &rom) :
Cartridge(rom) {
rom_ptr_ = rom_.data();
}
Atari8kSuperChip(uint8_t *rom_base, size_t rom_size) : BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {}
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
address &= 0x1fff;
if(!(address & 0x1000)) return;
if(address == 0x1ff8) rom_ptr_ = rom_.data();
if(address == 0x1ff9) rom_ptr_ = rom_.data() + 4096;
if(address == 0x1ff8) rom_ptr_ = rom_base_;
if(address == 0x1ff9) rom_ptr_ = rom_base_ + 4096;
if(isReadOperation(operation)) {
*value = rom_ptr_[address & 4095];
@@ -63,6 +58,7 @@ class CartridgeAtari8kSuperChip: public Cartridge<CartridgeAtari8kSuperChip> {
uint8_t ram_[128];
};
}
}
#endif /* Atari2600_CartridgeAtari8k_hpp */

View File

@@ -12,19 +12,17 @@
#include "Cartridge.hpp"
namespace Atari2600 {
namespace Cartridge {
class CartridgeCBSRAMPlus: public Cartridge<CartridgeCBSRAMPlus> {
class CBSRAMPlus: public BusExtender {
public:
CartridgeCBSRAMPlus(const std::vector<uint8_t> &rom) :
Cartridge(rom) {
rom_ptr_ = rom_.data();
}
CBSRAMPlus(uint8_t *rom_base, size_t rom_size) : BusExtender(rom_base, rom_size), rom_ptr_(rom_base) {}
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
address &= 0x1fff;
if(!(address & 0x1000)) return;
if(address >= 0x1ff8 && address <= 0x1ffa) rom_ptr_ = rom_.data() + (address - 0x1ff8) * 4096;
if(address >= 0x1ff8 && address <= 0x1ffa) rom_ptr_ = rom_base_ + (address - 0x1ff8) * 4096;
if(isReadOperation(operation)) {
*value = rom_ptr_[address & 4095];
@@ -39,6 +37,7 @@ class CartridgeCBSRAMPlus: public Cartridge<CartridgeCBSRAMPlus> {
uint8_t ram_[256];
};
}
}
#endif /* Atari2600_CartridgeCBSRAMPlus_hpp */

View File

@@ -9,43 +9,59 @@
#ifndef Atari2600_Cartridge_hpp
#define Atari2600_Cartridge_hpp
#include "../../../Processors/6502/CPU6502.hpp"
#include "../../../Processors/6502/6502.hpp"
#include "../Bus.hpp"
namespace Atari2600 {
namespace Cartridge {
class BusExtender: public CPU::MOS6502::BusHandler {
public:
BusExtender(uint8_t *rom_base, size_t rom_size) : rom_base_(rom_base), rom_size_(rom_size) {}
void advance_cycles(int cycles) {}
protected:
uint8_t *rom_base_;
size_t rom_size_;
};
template<class T> class Cartridge:
public CPU6502::Processor<Cartridge<T>>,
public CPU::MOS6502::BusHandler,
public Bus {
public:
Cartridge(const std::vector<uint8_t> &rom) :
rom_(rom) {}
m6502_(*this),
rom_(rom),
bus_extender_(rom_.data(), rom.size()) {
// The above works because bus_extender_ is declared after rom_ in the instance storage list;
// consider doing something less fragile.
}
void run_for_cycles(int number_of_cycles) { CPU6502::Processor<Cartridge<T>>::run_for_cycles(number_of_cycles); }
void set_reset_line(bool state) { CPU6502::Processor<Cartridge<T>>::set_reset_line(state); }
void advance_cycles(unsigned int cycles) {}
void run_for(const Cycles cycles) { m6502_.run_for(cycles); }
void set_reset_line(bool state) { m6502_.set_reset_line(state); }
// to satisfy CPU6502::Processor
unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
// to satisfy CPU::MOS6502::Processor
Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
uint8_t returnValue = 0xff;
unsigned int cycles_run_for = 3;
int cycles_run_for = 3;
// this occurs as a feedback loop — the 2600 requests ready, then performs the cycles_run_for
// leap to the end of ready only once ready is signalled — because on a 6502 ready doesn't take
// effect until the next read; therefore it isn't safe to assume that signalling ready immediately
// skips to the end of the line.
if(operation == CPU6502::BusOperation::Ready)
cycles_run_for = (unsigned int)tia_->get_cycles_until_horizontal_blank(cycles_since_video_update_);
if(operation == CPU::MOS6502::BusOperation::Ready)
cycles_run_for = tia_->get_cycles_until_horizontal_blank(cycles_since_video_update_);
cycles_since_speaker_update_ += cycles_run_for;
cycles_since_video_update_ += cycles_run_for;
cycles_since_6532_update_ += (cycles_run_for / 3);
static_cast<T *>(this)->advance_cycles(cycles_run_for / 3);
cycles_since_speaker_update_ += Cycles(cycles_run_for);
cycles_since_video_update_ += Cycles(cycles_run_for);
cycles_since_6532_update_ += Cycles(cycles_run_for / 3);
bus_extender_.advance_cycles(cycles_run_for / 3);
if(operation != CPU6502::BusOperation::Ready) {
if(operation != CPU::MOS6502::BusOperation::Ready) {
// give the cartridge a chance to respond to the bus access
static_cast<T *>(this)->perform_bus_operation(operation, address, value);
bus_extender_.perform_bus_operation(operation, address, value);
// check for a RIOT RAM access
if((address&0x1280) == 0x80) {
@@ -91,7 +107,7 @@ template<class T> class Cartridge:
case 0x00: update_video(); tia_->set_sync(*value & 0x02); break;
case 0x01: update_video(); tia_->set_blank(*value & 0x02); break;
case 0x02: CPU6502::Processor<Cartridge<T>>::set_ready_line(true); break;
case 0x02: m6502_.set_ready_line(true); break;
case 0x03: update_video(); tia_->reset_horizontal_counter(); break;
// TODO: audio will now be out of synchronisation — fix
@@ -156,21 +172,26 @@ template<class T> class Cartridge:
}
}
if(!tia_->get_cycles_until_horizontal_blank(cycles_since_video_update_)) CPU6502::Processor<Cartridge<T>>::set_ready_line(false);
if(!tia_->get_cycles_until_horizontal_blank(cycles_since_video_update_)) m6502_.set_ready_line(false);
return cycles_run_for / 3;
return Cycles(cycles_run_for / 3);
}
void synchronise() {
void flush() {
update_audio();
update_video();
speaker_->flush();
}
protected:
CPU::MOS6502::Processor<Cartridge<T>, true> m6502_;
std::vector<uint8_t> rom_;
private:
T bus_extender_;
};
}
}
#endif /* Atari2600_Cartridge_hpp */

View File

@@ -9,14 +9,16 @@
#ifndef Atari2600_CartridgeCommaVid_hpp
#define Atari2600_CartridgeCommaVid_hpp
#include "Cartridge.hpp"
namespace Atari2600 {
namespace Cartridge {
class CartridgeCommaVid: public Cartridge<CartridgeCommaVid> {
class CommaVid: public BusExtender {
public:
CartridgeCommaVid(const std::vector<uint8_t> &rom) :
Cartridge(rom) {}
CommaVid(uint8_t *rom_base, size_t rom_size) : BusExtender(rom_base, rom_size) {}
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
if(!(address & 0x1000)) return;
address &= 0x1fff;
@@ -30,13 +32,14 @@ class CartridgeCommaVid: public Cartridge<CartridgeCommaVid> {
return;
}
if(isReadOperation(operation)) *value = rom_[address & 2047];
if(isReadOperation(operation)) *value = rom_base_[address & 2047];
}
private:
uint8_t ram_[1024];
};
}
}
#endif /* Atari2600_CartridgeCommaVid_hpp */

View File

@@ -12,22 +12,23 @@
#include "Cartridge.hpp"
namespace Atari2600 {
namespace Cartridge {
class CartridgeMNetwork: public Cartridge<CartridgeMNetwork> {
class MNetwork: public BusExtender {
public:
CartridgeMNetwork(const std::vector<uint8_t> &rom) :
Cartridge(rom) {
rom_ptr_[0] = rom_.data() + rom_.size() - 4096;
MNetwork(uint8_t *rom_base, size_t rom_size) :
BusExtender(rom_base, rom_size) {
rom_ptr_[0] = rom_base + rom_size_ - 4096;
rom_ptr_[1] = rom_ptr_[0] + 2048;
high_ram_ptr_ = high_ram_;
}
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
address &= 0x1fff;
if(!(address & 0x1000)) return;
if(address >= 0x1fe0 && address <= 0x1fe6) {
rom_ptr_[0] = rom_.data() + (address - 0x1fe0) * 2048;
rom_ptr_[0] = rom_base_ + (address - 0x1fe0) * 2048;
} else if(address == 0x1fe7) {
rom_ptr_[0] = nullptr;
} else if(address >= 0x1ff8 && address <= 0x1ffb) {
@@ -54,7 +55,6 @@ class CartridgeMNetwork: public Cartridge<CartridgeMNetwork> {
}
}
}
}
private:
@@ -63,6 +63,7 @@ class CartridgeMNetwork: public Cartridge<CartridgeMNetwork> {
uint8_t low_ram_[1024], high_ram_[1024];
};
}
}
#endif /* Atari2600_CartridgeMNetwork_hpp */

View File

@@ -12,22 +12,23 @@
#include "Cartridge.hpp"
namespace Atari2600 {
namespace Cartridge {
class CartridgeMegaBoy: public Cartridge<CartridgeMegaBoy> {
class MegaBoy: public BusExtender {
public:
CartridgeMegaBoy(const std::vector<uint8_t> &rom) :
Cartridge(rom),
MegaBoy(uint8_t *rom_base, size_t rom_size) :
BusExtender(rom_base, rom_size),
rom_ptr_(rom_base),
current_page_(0) {
rom_ptr_ = rom_.data();
}
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
address &= 0x1fff;
if(!(address & 0x1000)) return;
if(address == 0x1ff0) {
current_page_ = (current_page_ + 1) & 15;
rom_ptr_ = rom_.data() + current_page_ * 4096;
rom_ptr_ = rom_base_ + current_page_ * 4096;
}
if(isReadOperation(operation)) {
@@ -40,6 +41,7 @@ class CartridgeMegaBoy: public Cartridge<CartridgeMegaBoy> {
uint8_t current_page_;
};
}
}
#endif /* CartridgeMegaBoy_h */

View File

@@ -12,24 +12,25 @@
#include "Cartridge.hpp"
namespace Atari2600 {
namespace Cartridge {
class CartridgeParkerBros: public Cartridge<CartridgeParkerBros> {
class ParkerBros: public BusExtender {
public:
CartridgeParkerBros(const std::vector<uint8_t> &rom) :
Cartridge(rom) {
rom_ptr_[0] = rom_.data() + 4096;
ParkerBros(uint8_t *rom_base, size_t rom_size) :
BusExtender(rom_base, rom_size) {
rom_ptr_[0] = rom_base + 4096;
rom_ptr_[1] = rom_ptr_[0] + 1024;
rom_ptr_[2] = rom_ptr_[1] + 1024;
rom_ptr_[3] = rom_ptr_[2] + 1024;
}
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
address &= 0x1fff;
if(!(address & 0x1000)) return;
if(address >= 0x1fe0 && address < 0x1ff8) {
int slot = (address >> 3)&3;
rom_ptr_[slot] = rom_.data() + ((address & 7) * 1024);
rom_ptr_[slot] = rom_base_ + ((address & 7) * 1024);
}
if(isReadOperation(operation)) {
@@ -41,6 +42,7 @@ class CartridgeParkerBros: public Cartridge<CartridgeParkerBros> {
uint8_t *rom_ptr_[4];
};
}
}
#endif /* Atari2600_CartridgeParkerBros_hpp */

View File

@@ -10,23 +10,23 @@
#define Atari2600_CartridgePitfall2_hpp
namespace Atari2600 {
namespace Cartridge {
class CartridgePitfall2: public Cartridge<CartridgePitfall2> {
class Pitfall2: public BusExtender {
public:
CartridgePitfall2(const std::vector<uint8_t> &rom) :
Cartridge(rom),
Pitfall2(uint8_t *rom_base, size_t rom_size) :
BusExtender(rom_base, rom_size),
rom_ptr_(rom_base),
random_number_generator_(0),
featcher_address_{0, 0, 0, 0, 0, 0, 0, 0},
mask_{0, 0, 0, 0, 0, 0, 0, 0},
cycles_since_audio_update_(0) {
rom_ptr_ = rom_.data();
}
cycles_since_audio_update_(0) {}
void advance_cycles(unsigned int cycles) {
void advance_cycles(int cycles) {
cycles_since_audio_update_ += cycles;
}
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
address &= 0x1fff;
if(!(address & 0x1000)) return;
@@ -53,11 +53,11 @@ class CartridgePitfall2: public Cartridge<CartridgePitfall2> {
break;
case 0x1008: case 0x1009: case 0x100a: case 0x100b: case 0x100c: case 0x100d: case 0x100e: case 0x100f:
*value = rom_[8192 + address_for_counter(address & 7)];
*value = rom_base_[8192 + address_for_counter(address & 7)];
break;
case 0x1010: case 0x1011: case 0x1012: case 0x1013: case 0x1014: case 0x1015: case 0x1016: case 0x1017:
*value = rom_[8192 + address_for_counter(address & 7)] & mask_[address & 7];
*value = rom_base_[8192 + address_for_counter(address & 7)] & mask_[address & 7];
break;
#pragma mark - Writes
@@ -81,8 +81,8 @@ class CartridgePitfall2: public Cartridge<CartridgePitfall2> {
#pragma mark - Paging
case 0x1ff8: rom_ptr_ = rom_.data(); break;
case 0x1ff9: rom_ptr_ = rom_.data() + 4096; break;
case 0x1ff8: rom_ptr_ = rom_base_; break;
case 0x1ff9: rom_ptr_ = rom_base_ + 4096; break;
#pragma mark - Business as usual
@@ -105,8 +105,7 @@ class CartridgePitfall2: public Cartridge<CartridgePitfall2> {
inline uint8_t update_audio() {
const unsigned int clock_divisor = 57;
unsigned int cycles_to_run_for = cycles_since_audio_update_ / clock_divisor;
cycles_since_audio_update_ %= clock_divisor;
int cycles_to_run_for = cycles_since_audio_update_.divide(clock_divisor).as_int();
int table_position = 0;
for(int c = 0; c < 3; c++) {
@@ -126,9 +125,10 @@ class CartridgePitfall2: public Cartridge<CartridgePitfall2> {
uint8_t random_number_generator_;
uint8_t *rom_ptr_;
uint8_t audio_channel_[3];
unsigned int cycles_since_audio_update_;
Cycles cycles_since_audio_update_;
};
}
}
#endif /* Atari2600_CartridgePitfall2_hpp */

View File

@@ -12,19 +12,20 @@
#include "Cartridge.hpp"
namespace Atari2600 {
namespace Cartridge {
class CartridgeTigervision: public Cartridge<CartridgeTigervision> {
class Tigervision: public BusExtender {
public:
CartridgeTigervision(const std::vector<uint8_t> &rom) :
Cartridge(rom) {
rom_ptr_[0] = rom_.data() + rom_.size() - 4096;
Tigervision(uint8_t *rom_base, size_t rom_size) :
BusExtender(rom_base, rom_size) {
rom_ptr_[0] = rom_base + rom_size - 4096;
rom_ptr_[1] = rom_ptr_[0] + 2048;
}
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
if((address&0x1fff) == 0x3f) {
int offset = ((*value) * 2048) & (rom_.size() - 1);
rom_ptr_[0] = rom_.data() + offset;
int offset = ((*value) * 2048) & (rom_size_ - 1);
rom_ptr_[0] = rom_base_ + offset;
return;
} else if((address&0x1000) && isReadOperation(operation)) {
*value = rom_ptr_[(address >> 11)&1][address & 2047];
@@ -35,6 +36,7 @@ class CartridgeTigervision: public Cartridge<CartridgeTigervision> {
uint8_t *rom_ptr_[2];
};
}
}
#endif /* Atari2600_CartridgeTigervision_hpp */

View File

@@ -12,19 +12,20 @@
#include "Cartridge.hpp"
namespace Atari2600 {
namespace Cartridge {
class CartridgeUnpaged: public Cartridge<CartridgeUnpaged> {
class Unpaged: public BusExtender {
public:
CartridgeUnpaged(const std::vector<uint8_t> &rom) :
Cartridge(rom) {}
Unpaged(uint8_t *rom_base, size_t rom_size) : BusExtender(rom_base, rom_size) {}
void perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
if(isReadOperation(operation) && (address & 0x1000)) {
*value = rom_[address & (rom_.size() - 1)];
*value = rom_base_[address & (rom_size_ - 1)];
}
}
};
}
}
#endif /* Atari2600_CartridgeUnpaged_hpp */

View File

@@ -165,13 +165,14 @@ void TIA::set_output_mode(Atari2600::TIA::OutputMode output_mode) {
/* speaker_->set_input_rate((float)(get_clock_rate() / 38.0));*/
}
void TIA::run_for_cycles(int number_of_cycles)
{
void TIA::run_for(const Cycles cycles) {
int number_of_cycles = cycles.as_int();
// if part way through a line, definitely perform a partial, at most up to the end of the line
if(horizontal_counter_) {
int cycles = std::min(number_of_cycles, cycles_per_line - horizontal_counter_);
output_for_cycles(cycles);
number_of_cycles -= cycles;
int output_cycles = std::min(number_of_cycles, cycles_per_line - horizontal_counter_);
output_for_cycles(output_cycles);
number_of_cycles -= output_cycles;
}
// output full lines for as long as possible
@@ -197,8 +198,8 @@ void TIA::set_blank(bool blank) {
void TIA::reset_horizontal_counter() {
}
int TIA::get_cycles_until_horizontal_blank(unsigned int from_offset) {
return (cycles_per_line - (horizontal_counter_ + (int)from_offset) % cycles_per_line) % cycles_per_line;
int TIA::get_cycles_until_horizontal_blank(const Cycles from_offset) {
return (cycles_per_line - (horizontal_counter_ + from_offset.as_int()) % cycles_per_line) % cycles_per_line;
}
void TIA::set_background_colour(uint8_t colour) {

View File

@@ -10,6 +10,7 @@
#define TIA_hpp
#include <cstdint>
#include "../CRTMachine.hpp"
namespace Atari2600 {
@@ -27,10 +28,9 @@ class TIA {
};
/*!
Advances the TIA by @c number_of_cycles cycles. Any queued setters take effect in the
first cycle performed.
Advances the TIA by @c cycles. Any queued setters take effect in the first cycle performed.
*/
void run_for_cycles(int number_of_cycles);
void run_for(const Cycles cycles);
void set_output_mode(OutputMode output_mode);
void set_sync(bool sync);
@@ -41,7 +41,7 @@ class TIA {
@returns the number of cycles between (current TIA time) + from_offset to the current or
next horizontal blanking period. Returns numbers in the range [0, 227].
*/
int get_cycles_until_horizontal_blank(unsigned int from_offset);
int get_cycles_until_horizontal_blank(const Cycles from_offset);
void set_background_colour(uint8_t colour);
@@ -229,14 +229,14 @@ class TIA {
} queue_[4];
int queue_read_pointer_, queue_write_pointer_;
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int pixel_position, int adder, int reverse_mask) {
if(pixel_position == 32 || !graphic[graphic_index]) return;
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int output_pixel_position, int output_adder, int output_reverse_mask) {
if(output_pixel_position == 32 || !graphic[graphic_index]) return;
int output_cursor = 0;
while(pixel_position < 32 && output_cursor < count) {
int shift = (pixel_position >> 2) ^ reverse_mask;
while(output_pixel_position < 32 && output_cursor < count) {
int shift = (output_pixel_position >> 2) ^ output_reverse_mask;
target[output_cursor] |= ((graphic[graphic_index] >> shift)&1) * collision_identity;
output_cursor++;
pixel_position += adder;
output_pixel_position += output_adder;
}
}

View File

@@ -11,6 +11,7 @@
#include "../Outputs/CRT/CRT.hpp"
#include "../Outputs/Speaker.hpp"
#include "../ClockReceiver/ClockReceiver.hpp"
namespace CRTMachine {
@@ -21,15 +22,28 @@ namespace CRTMachine {
*/
class Machine {
public:
Machine() : clock_is_unlimited_(false) {}
Machine() : clock_is_unlimited_(false), delegate_(nullptr) {}
/*!
Causes the machine to set up its CRT and, if it has one, speaker. The caller guarantees
that an OpenGL context is bound.
*/
virtual void setup_output(float aspect_ratio) = 0;
/*!
Gives the machine a chance to release all owned resources. The caller guarantees that the
OpenGL context is bound.
*/
virtual void close_output() = 0;
/// @returns The CRT this machine is drawing to. Should not be @c nullptr.
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() = 0;
/// @returns The speaker that receives this machine's output, or @c nullptr if this machine is mute.
virtual std::shared_ptr<Outputs::Speaker> get_speaker() = 0;
virtual void run_for_cycles(int number_of_cycles) = 0;
/// Runs the machine for @c cycles.
virtual void run_for(const Cycles cycles) = 0;
// TODO: sever the clock-rate stuff.
double get_clock_rate() {
@@ -43,7 +57,7 @@ class Machine {
virtual void machine_did_change_clock_rate(Machine *machine) = 0;
virtual void machine_did_change_clock_is_unlimited(Machine *machine) = 0;
};
void set_delegate(Delegate *delegate) { this->delegate_ = delegate; }
void set_delegate(Delegate *delegate) { delegate_ = delegate; }
protected:
void set_clock_rate(double clock_rate) {

View File

@@ -13,6 +13,7 @@
using namespace Commodore::C1540;
Machine::Machine() :
m6502_(*this),
shift_register_(0),
Storage::Disk::Controller(1000000, 4, 300),
serial_port_(new SerialPort),
@@ -34,7 +35,7 @@ void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bu
Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus);
}
unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
/*
Memory map (given that I'm unsure yet on any potential mirroring):
@@ -63,14 +64,14 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
drive_VIA_.set_register(address, *value);
}
serial_port_VIA_->run_for_cycles(1);
drive_VIA_.run_for_cycles(1);
serial_port_VIA_->run_for(Cycles(1));
drive_VIA_.run_for(Cycles(1));
return 1;
return Cycles(1);
}
void Machine::set_rom(const uint8_t *rom) {
memcpy(rom_, rom, sizeof(rom_));
void Machine::set_rom(const std::vector<uint8_t> &rom) {
memcpy(rom_, rom.data(), std::min(sizeof(rom_), rom.size()));
}
void Machine::set_disk(std::shared_ptr<Storage::Disk::Disk> disk) {
@@ -79,18 +80,18 @@ void Machine::set_disk(std::shared_ptr<Storage::Disk::Disk> disk) {
set_drive(drive);
}
void Machine::run_for_cycles(int number_of_cycles) {
CPU6502::Processor<Machine>::run_for_cycles(number_of_cycles);
void Machine::run_for(const Cycles cycles) {
m6502_.run_for(cycles);
set_motor_on(drive_VIA_.get_motor_enabled());
if(drive_VIA_.get_motor_enabled()) // TODO: motor speed up/down
Storage::Disk::Controller::run_for_cycles(number_of_cycles);
Storage::Disk::Controller::run_for(cycles);
}
#pragma mark - 6522 delegate
void Machine::mos6522_did_change_interrupt_status(void *mos6522) {
// both VIAs are connected to the IRQ line
set_irq_line(serial_port_VIA_->get_interrupt_line() || drive_VIA_.get_interrupt_line());
m6502_.set_irq_line(serial_port_VIA_->get_interrupt_line() || drive_VIA_.get_interrupt_line());
}
#pragma mark - Disk drive
@@ -108,10 +109,10 @@ void Machine::process_input_bit(int value, unsigned int cycles_since_index_hole)
drive_VIA_.set_data_input((uint8_t)shift_register_);
bit_window_offset_ = 0;
if(drive_VIA_.get_should_set_overflow()) {
set_overflow_line(true);
m6502_.set_overflow_line(true);
}
}
else set_overflow_line(false);
else m6502_.set_overflow_line(false);
}
// the 1540 does not recognise index holes

View File

@@ -9,7 +9,7 @@
#ifndef Commodore1540_hpp
#define Commodore1540_hpp
#include "../../../Processors/6502/CPU6502.hpp"
#include "../../../Processors/6502/6502.hpp"
#include "../../../Components/6522/6522.hpp"
#include "../SerialBus.hpp"
@@ -120,7 +120,7 @@ class SerialPort : public ::Commodore::Serial::Port {
Provides an emulation of the C1540.
*/
class Machine:
public CPU6502::Processor<Machine>,
public CPU::MOS6502::BusHandler,
public MOS::MOS6522IRQDelegate::Delegate,
public DriveVIA::Delegate,
public Storage::Disk::Controller {
@@ -131,18 +131,18 @@ class Machine:
/*!
Sets the ROM image to use for this drive; it is assumed that the buffer provided will be at least 16 kb in size.
*/
void set_rom(const uint8_t *rom);
void set_rom(const std::vector<uint8_t> &rom);
/*!
Sets the serial bus to which this drive should attach itself.
*/
void set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus);
void run_for_cycles(int number_of_cycles);
void run_for(const Cycles cycles);
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk);
// to satisfy CPU6502::Processor
unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value);
// to satisfy CPU::MOS6502::Processor
Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value);
// to satisfy MOS::MOS6522::Delegate
virtual void mos6522_did_change_interrupt_status(void *mos6522);
@@ -152,6 +152,8 @@ class Machine:
void drive_via_did_set_data_density(void *driveVIA, int density);
private:
CPU::MOS6502::Processor<Machine, false> m6502_;
uint8_t ram_[0x800];
uint8_t rom_[0x4000];

View File

@@ -1,18 +1,21 @@
//
// Typer.cpp
// CharacterMapper.cpp
// Clock Signal
//
// Created by Thomas Harte on 05/11/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
// Created by Thomas Harte on 03/08/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "CharacterMapper.hpp"
#include "Vic20.hpp"
uint16_t *Commodore::Vic20::Machine::sequence_for_character(Utility::Typer *typer, char character) {
#define KEYS(...) {__VA_ARGS__, TerminateSequence}
#define SHIFT(...) {KeyLShift, __VA_ARGS__, TerminateSequence}
using namespace Commodore::Vic20;
uint16_t *CharacterMapper::sequence_for_character(char character) {
#define KEYS(...) {__VA_ARGS__, EndSequence}
#define SHIFT(...) {KeyLShift, __VA_ARGS__, EndSequence}
#define X {NotMapped}
static Key key_sequences[][3] = {
static KeySequence key_sequences[] = {
/* NUL */ X, /* SOH */ X,
/* STX */ X, /* ETX */ X,
/* EOT */ X, /* ENQ */ X,
@@ -80,7 +83,5 @@ uint16_t *Commodore::Vic20::Machine::sequence_for_character(Utility::Typer *type
#undef SHIFT
#undef X
if(character > sizeof(key_sequences) / sizeof(*key_sequences)) return nullptr;
if(key_sequences[character][0] == NotMapped) return nullptr;
return (uint16_t *)key_sequences[character];
return table_lookup_sequence_for_character(key_sequences, sizeof(key_sequences), character);
}

View File

@@ -0,0 +1,25 @@
//
// CharacterMapper.hpp
// Clock Signal
//
// Created by Thomas Harte on 03/08/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef Machines_Commodore_Vic20_CharacterMapper_hpp
#define Machines_Commodore_Vic20_CharacterMapper_hpp
#include "../../Typer.hpp"
namespace Commodore {
namespace Vic20 {
class CharacterMapper: public ::Utility::CharacterMapper {
public:
uint16_t *sequence_for_character(char character);
};
}
}
#endif /* CharacterMapper_hpp */

File diff suppressed because it is too large Load Diff

View File

@@ -13,15 +13,7 @@
#include "../../CRTMachine.hpp"
#include "../../Typer.hpp"
#include "../../../Processors/6502/CPU6502.hpp"
#include "../../../Components/6560/6560.hpp"
#include "../../../Components/6522/6522.hpp"
#include "../SerialBus.hpp"
#include "../1540/C1540.hpp"
#include "../../../Storage/Tape/Tape.hpp"
#include "../../../Storage/Disk/Disk.hpp"
#include <cstdint>
namespace Commodore {
namespace Vic20 {
@@ -44,9 +36,8 @@ enum Region {
PAL
};
#define key(line, mask) (((mask) << 3) | (line))
enum Key: uint16_t {
#define key(line, mask) (((mask) << 3) | (line))
Key2 = key(7, 0x01), Key4 = key(7, 0x02), Key6 = key(7, 0x04), Key8 = key(7, 0x08),
Key0 = key(7, 0x10), KeyDash = key(7, 0x20), KeyHome = key(7, 0x40), KeyF7 = key(7, 0x80),
KeyQ = key(6, 0x01), KeyE = key(6, 0x02), KeyT = key(6, 0x04), KeyU = key(6, 0x08),
@@ -63,8 +54,7 @@ enum Key: uint16_t {
KeyI = key(1, 0x10), KeyP = key(1, 0x20), KeyAsterisk = key(1, 0x40), KeyReturn = key(1, 0x80),
Key1 = key(0, 0x01), Key3 = key(0, 0x02), Key5 = key(0, 0x04), Key7 = key(0, 0x08),
Key9 = key(0, 0x10), KeyPlus = key(0, 0x20), KeyGBP = key(0, 0x40), KeyDelete = key(0, 0x80),
TerminateSequence = 0xffff, NotMapped = 0xfffe
#undef key
};
enum JoystickInput {
@@ -75,152 +65,31 @@ enum JoystickInput {
Fire = 0x20
};
class UserPortVIA: public MOS::MOS6522<UserPortVIA>, public MOS::MOS6522IRQDelegate {
public:
UserPortVIA();
using MOS6522IRQDelegate::set_interrupt_status;
uint8_t get_port_input(Port port);
void set_control_line_output(Port port, Line line, bool value);
void set_serial_line_state(::Commodore::Serial::Line line, bool value);
void set_joystick_state(JoystickInput input, bool value);
void set_port_output(Port port, uint8_t value, uint8_t mask);
void set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort);
void set_tape(std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape);
private:
uint8_t port_a_;
std::weak_ptr<::Commodore::Serial::Port> serial_port_;
std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape_;
};
class KeyboardVIA: public MOS::MOS6522<KeyboardVIA>, public MOS::MOS6522IRQDelegate {
public:
KeyboardVIA();
using MOS6522IRQDelegate::set_interrupt_status;
void set_key_state(uint16_t key, bool isPressed);
void clear_all_keys();
// to satisfy MOS::MOS6522
uint8_t get_port_input(Port port);
void set_port_output(Port port, uint8_t value, uint8_t mask);
void set_control_line_output(Port port, Line line, bool value);
void set_joystick_state(JoystickInput input, bool value);
void set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort);
private:
uint8_t port_b_;
uint8_t columns_[8];
uint8_t activation_mask_;
std::weak_ptr<::Commodore::Serial::Port> serial_port_;
};
class SerialPort : public ::Commodore::Serial::Port {
public:
void set_input(::Commodore::Serial::Line line, ::Commodore::Serial::LineLevel level);
void set_user_port_via(std::shared_ptr<UserPortVIA> userPortVIA);
private:
std::weak_ptr<UserPortVIA> user_port_via_;
};
class Vic6560: public MOS::MOS6560<Vic6560> {
public:
inline void perform_read(uint16_t address, uint8_t *pixel_data, uint8_t *colour_data) {
*pixel_data = video_memory_map[address >> 10] ? video_memory_map[address >> 10][address & 0x3ff] : 0xff; // TODO
*colour_data = colour_memory[address & 0x03ff];
}
uint8_t *video_memory_map[16];
uint8_t *colour_memory;
};
class Machine:
public CPU6502::Processor<Machine>,
public ConfigurationTarget::Machine,
public CRTMachine::Machine,
public MOS::MOS6522IRQDelegate::Delegate,
public Utility::TypeRecipient,
public Storage::Tape::BinaryTapePlayer::Delegate,
public ConfigurationTarget::Machine {
public KeyboardMachine::Machine {
public:
Machine();
~Machine();
virtual ~Machine();
void set_rom(ROMSlot slot, size_t length, const uint8_t *data);
void configure_as_target(const StaticAnalyser::Target &target);
/// Creates and returns a Vic-20.
static Machine *Vic20();
void set_key_state(uint16_t key, bool isPressed) { keyboard_via_->set_key_state(key, isPressed); }
void clear_all_keys() { keyboard_via_->clear_all_keys(); }
void set_joystick_state(JoystickInput input, bool isPressed) {
user_port_via_->set_joystick_state(input, isPressed);
keyboard_via_->set_joystick_state(input, isPressed);
}
/// Sets the contents of the rom in @c slot to the buffer @c data of length @c length.
virtual void set_rom(ROMSlot slot, size_t length, const uint8_t *data) = 0;
// TODO: take a std::vector<uint8_t> to collapse length and data.
void set_memory_size(MemorySize size);
void set_region(Region region);
/// Sets the state of joystick input @c input.
virtual void set_joystick_state(JoystickInput input, bool isPressed) = 0;
inline void set_use_fast_tape_hack(bool activate) { use_fast_tape_hack_ = activate; }
/// Sets the memory size of this Vic-20.
virtual void set_memory_size(MemorySize size) = 0;
// to satisfy CPU6502::Processor
unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value);
void synchronise() { mos6560_->synchronise(); }
/// Sets the region of this Vic-20.
virtual void set_region(Region region) = 0;
// to satisfy CRTMachine::Machine
virtual void setup_output(float aspect_ratio);
virtual void close_output();
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() { return mos6560_->get_crt(); }
virtual std::shared_ptr<Outputs::Speaker> get_speaker() { return mos6560_->get_speaker(); }
virtual void run_for_cycles(int number_of_cycles) { CPU6502::Processor<Machine>::run_for_cycles(number_of_cycles); }
// to satisfy MOS::MOS6522::Delegate
virtual void mos6522_did_change_interrupt_status(void *mos6522);
// for Utility::TypeRecipient
uint16_t *sequence_for_character(Utility::Typer *typer, char character);
// for Tape::Delegate
virtual void tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape);
private:
uint8_t character_rom_[0x1000];
uint8_t basic_rom_[0x2000];
uint8_t kernel_rom_[0x2000];
uint8_t expansion_ram_[0x8000];
uint8_t *rom_;
uint16_t rom_address_, rom_length_;
uint8_t user_basic_memory_[0x0400];
uint8_t screen_memory_[0x1000];
uint8_t colour_memory_[0x0400];
std::unique_ptr<uint8_t> drive_rom_;
uint8_t *processor_read_memory_map_[64];
uint8_t *processor_write_memory_map_[64];
void write_to_map(uint8_t **map, uint8_t *area, uint16_t address, uint16_t length);
Region region_;
std::unique_ptr<Vic6560> mos6560_;
std::shared_ptr<UserPortVIA> user_port_via_;
std::shared_ptr<KeyboardVIA> keyboard_via_;
std::shared_ptr<SerialPort> serial_port_;
std::shared_ptr<::Commodore::Serial::Bus> serial_bus_;
// Tape
std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape_;
bool use_fast_tape_hack_;
bool is_running_at_zero_cost_;
// Disk
std::shared_ptr<::Commodore::C1540::Machine> c1540_;
void install_disk_rom();
/// Enables or disables turbo-speed tape loading.
virtual void set_use_fast_tape_hack(bool activate) = 0;
};
}

View File

@@ -15,11 +15,19 @@ namespace ConfigurationTarget {
/*!
A ConfigurationTarget::Machine is anything that can accept a StaticAnalyser::Target
and configure itself appropriately.
and configure itself appropriately, or accept a list of media subsequently to insert.
*/
class Machine {
public:
/// Instructs the machine to configure itself as described by @c target and insert the included media.
virtual void configure_as_target(const StaticAnalyser::Target &target) = 0;
/*!
Requests that the machine insert @c media as a modification to current state
@returns @c true if any media was inserted; @c false otherwise.
*/
virtual bool insert_media(const StaticAnalyser::Media &media) = 0;
};
}

View File

@@ -1,27 +1,22 @@
//
// Typer.cpp
// CharacterMapper.cpp
// Clock Signal
//
// Created by Thomas Harte on 05/11/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
// Created by Thomas Harte on 03/08/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "CharacterMapper.hpp"
#include "Electron.hpp"
int Electron::Machine::get_typer_delay() {
return get_is_resetting() ? 625*25*128 : 0; // wait one second if resetting
}
using namespace Electron;
int Electron::Machine::get_typer_frequency() {
return 625*128*2; // accept a new character every two frames
}
uint16_t *Electron::Machine::sequence_for_character(Utility::Typer *typer, char character) {
#define KEYS(...) {__VA_ARGS__, TerminateSequence}
#define SHIFT(...) {KeyShift, __VA_ARGS__, TerminateSequence}
#define CTRL(...) {KeyControl, __VA_ARGS__, TerminateSequence}
uint16_t *CharacterMapper::sequence_for_character(char character) {
#define KEYS(...) {__VA_ARGS__, EndSequence}
#define SHIFT(...) {KeyShift, __VA_ARGS__, EndSequence}
#define CTRL(...) {KeyControl, __VA_ARGS__, EndSequence}
#define X {NotMapped}
static Key key_sequences[][3] = {
static KeySequence key_sequences[] = {
/* NUL */ X, /* SOH */ X,
/* STX */ X, /* ETX */ X,
/* EOT */ X, /* ENQ */ X,
@@ -91,7 +86,5 @@ uint16_t *Electron::Machine::sequence_for_character(Utility::Typer *typer, char
#undef SHIFT
#undef X
if(character > sizeof(key_sequences) / sizeof(*key_sequences)) return nullptr;
if(key_sequences[character][0] == NotMapped) return nullptr;
return (uint16_t *)key_sequences[character];
return table_lookup_sequence_for_character(key_sequences, sizeof(key_sequences), character);
}

View File

@@ -0,0 +1,23 @@
//
// CharacterMapper.hpp
// Clock Signal
//
// Created by Thomas Harte on 03/08/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef Machines_Electron_CharacterMapper_hpp
#define Machines_Electron_CharacterMapper_hpp
#include "../Typer.hpp"
namespace Electron {
class CharacterMapper: public ::Utility::CharacterMapper {
public:
uint16_t *sequence_for_character(char character);
};
}
#endif /* Machines_Electron_CharacterMapper_hpp */

View File

@@ -8,394 +8,474 @@
#include "Electron.hpp"
#include "../../Processors/6502/6502.hpp"
#include "../../Storage/Tape/Tape.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../ClockReceiver/ForceInline.hpp"
#include "../Typer.hpp"
#include "CharacterMapper.hpp"
#include "Interrupts.hpp"
#include "Plus3.hpp"
#include "Speaker.hpp"
#include "Tape.hpp"
#include "Video.hpp"
namespace Electron {
class ConcreteMachine:
public Machine,
public CPU::MOS6502::BusHandler,
public Tape::Delegate,
public Utility::TypeRecipient {
public:
ConcreteMachine() :
m6502_(*this),
interrupt_control_(0),
interrupt_status_(Interrupt::PowerOnReset | Interrupt::TransmitDataEmpty | 0x80),
cycles_since_audio_update_(0),
use_fast_tape_hack_(false),
cycles_until_display_interrupt_(0) {
memset(key_states_, 0, sizeof(key_states_));
for(int c = 0; c < 16; c++)
memset(roms_[c], 0xff, 16384);
tape_.set_delegate(this);
set_clock_rate(2000000);
}
void set_rom(ROMSlot slot, std::vector<uint8_t> data, bool is_writeable) override final {
uint8_t *target = nullptr;
switch(slot) {
case ROMSlotDFS: dfs_ = data; return;
case ROMSlotADFS: adfs_ = data; return;
case ROMSlotOS: target = os_; break;
default:
target = roms_[slot];
rom_write_masks_[slot] = is_writeable;
break;
}
memcpy(target, &data[0], std::min((size_t)16384, data.size()));
}
void set_key_state(uint16_t key, bool isPressed) override final {
if(key == KeyBreak) {
m6502_.set_reset_line(isPressed);
} else {
if(isPressed)
key_states_[key >> 4] |= key&0xf;
else
key_states_[key >> 4] &= ~(key&0xf);
}
}
void clear_all_keys() override final {
memset(key_states_, 0, sizeof(key_states_));
if(is_holding_shift_) set_key_state(KeyShift, true);
}
void set_use_fast_tape_hack(bool activate) override final {
use_fast_tape_hack_ = activate;
}
void configure_as_target(const StaticAnalyser::Target &target) override final {
if(target.loadingCommand.length()) {
set_typer_for_string(target.loadingCommand.c_str());
}
if(target.acorn.should_shift_restart) {
shift_restart_counter_ = 1000000;
}
if(target.acorn.has_dfs || target.acorn.has_adfs) {
plus3_.reset(new Plus3);
if(target.acorn.has_dfs) {
set_rom(ROMSlot0, dfs_, true);
}
if(target.acorn.has_adfs) {
set_rom(ROMSlot4, adfs_, true);
set_rom(ROMSlot5, std::vector<uint8_t>(adfs_.begin() + 16384, adfs_.end()), true);
}
}
insert_media(target.media);
}
bool insert_media(const StaticAnalyser::Media &media) override final {
if(!media.tapes.empty()) {
tape_.set_tape(media.tapes.front());
}
if(!media.disks.empty() && plus3_) {
plus3_->set_disk(media.disks.front(), 0);
}
ROMSlot slot = ROMSlot12;
for(std::shared_ptr<Storage::Cartridge::Cartridge> cartridge : media.cartridges) {
set_rom(slot, cartridge->get_segments().front().data, false);
slot = (ROMSlot)(((int)slot + 1)&15);
}
return !media.tapes.empty() || !media.disks.empty() || !media.cartridges.empty();
}
forceinline Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
unsigned int cycles = 1;
if(address < 0x8000) {
if(isReadOperation(operation)) {
*value = ram_[address];
} else {
if(address >= video_access_range_.low_address && address <= video_access_range_.high_address) update_display();
ram_[address] = *value;
}
// for the entire frame, RAM is accessible only on odd cycles; in modes below 4
// it's also accessible only outside of the pixel regions
cycles += video_output_->get_cycles_until_next_ram_availability(cycles_since_display_update_.as_int() + 1);
} else {
switch(address & 0xff0f) {
case 0xfe00:
if(isReadOperation(operation)) {
*value = interrupt_status_;
interrupt_status_ &= ~PowerOnReset;
} else {
interrupt_control_ = (*value) & ~1;
evaluate_interrupts();
}
break;
case 0xfe07:
if(!isReadOperation(operation)) {
// update speaker mode
bool new_speaker_is_enabled = (*value & 6) == 2;
if(new_speaker_is_enabled != speaker_is_enabled_) {
update_audio();
speaker_->set_is_enabled(new_speaker_is_enabled);
speaker_is_enabled_ = new_speaker_is_enabled;
}
tape_.set_is_enabled((*value & 6) != 6);
tape_.set_is_in_input_mode((*value & 6) == 0);
tape_.set_is_running(((*value)&0x40) ? true : false);
// TODO: caps lock LED
}
// deliberate fallthrough
case 0xfe02: case 0xfe03:
case 0xfe08: case 0xfe09: case 0xfe0a: case 0xfe0b:
case 0xfe0c: case 0xfe0d: case 0xfe0e: case 0xfe0f:
if(!isReadOperation(operation)) {
update_display();
video_output_->set_register(address, *value);
video_access_range_ = video_output_->get_memory_access_range();
queue_next_display_interrupt();
}
break;
case 0xfe04:
if(isReadOperation(operation)) {
*value = tape_.get_data_register();
tape_.clear_interrupts(Interrupt::ReceiveDataFull);
} else {
tape_.set_data_register(*value);
tape_.clear_interrupts(Interrupt::TransmitDataEmpty);
}
break;
case 0xfe05:
if(!isReadOperation(operation)) {
const uint8_t interruptDisable = (*value)&0xf0;
if( interruptDisable ) {
if( interruptDisable&0x10 ) interrupt_status_ &= ~Interrupt::DisplayEnd;
if( interruptDisable&0x20 ) interrupt_status_ &= ~Interrupt::RealTimeClock;
if( interruptDisable&0x40 ) interrupt_status_ &= ~Interrupt::HighToneDetect;
evaluate_interrupts();
// TODO: NMI
}
// latch the paged ROM in case external hardware is being emulated
active_rom_ = (Electron::ROMSlot)(*value & 0xf);
// apply the ULA's test
if(*value & 0x08) {
if(*value & 0x04) {
keyboard_is_active_ = false;
basic_is_active_ = false;
} else {
keyboard_is_active_ = !(*value & 0x02);
basic_is_active_ = !keyboard_is_active_;
}
}
}
break;
case 0xfe06:
if(!isReadOperation(operation)) {
update_audio();
speaker_->set_divider(*value);
tape_.set_counter(*value);
}
break;
case 0xfc04: case 0xfc05: case 0xfc06: case 0xfc07:
if(plus3_ && (address&0x00f0) == 0x00c0) {
if(is_holding_shift_ && address == 0xfcc4) {
is_holding_shift_ = false;
set_key_state(KeyShift, false);
}
if(isReadOperation(operation))
*value = plus3_->get_register(address);
else
plus3_->set_register(address, *value);
}
break;
case 0xfc00:
if(plus3_ && (address&0x00f0) == 0x00c0) {
if(!isReadOperation(operation)) {
plus3_->set_control_register(*value);
} else *value = 1;
}
break;
default:
if(address >= 0xc000) {
if(isReadOperation(operation)) {
if(
use_fast_tape_hack_ &&
tape_.has_tape() &&
(operation == CPU::MOS6502::BusOperation::ReadOpcode) &&
(
(address == 0xf4e5) || (address == 0xf4e6) || // double NOPs at 0xf4e5, 0xf6de, 0xf6fa and 0xfa51
(address == 0xf6de) || (address == 0xf6df) || // act to disable the normal branch into tape-handling
(address == 0xf6fa) || (address == 0xf6fb) || // code, forcing the OS along the serially-accessed ROM
(address == 0xfa51) || (address == 0xfa52) || // pathway.
(address == 0xf0a8) // 0xf0a8 is from where a service call would normally be
// dispatched; we can check whether it would be call 14
// (i.e. read byte) and, if so, whether the OS was about to
// issue a read byte call to a ROM despite being the tape
// FS being selected. If so then this is a get byte that
// we should service synthetically. Put the byte into Y
// and set A to zero to report that action was taken, then
// allow the PC read to return an RTS.
)
) {
uint8_t service_call = (uint8_t)m6502_.get_value_of_register(CPU::MOS6502::Register::X);
if(address == 0xf0a8) {
if(!ram_[0x247] && service_call == 14) {
tape_.set_delegate(nullptr);
// TODO: handle tape wrap around.
int cycles_left_while_plausibly_in_data = 50;
tape_.clear_interrupts(Interrupt::ReceiveDataFull);
while(!tape_.get_tape()->is_at_end()) {
tape_.run_for_input_pulse();
cycles_left_while_plausibly_in_data--;
if(!cycles_left_while_plausibly_in_data) fast_load_is_in_data_ = false;
if( (tape_.get_interrupt_status() & Interrupt::ReceiveDataFull) &&
(fast_load_is_in_data_ || tape_.get_data_register() == 0x2a)
) break;
}
tape_.set_delegate(this);
tape_.clear_interrupts(Interrupt::ReceiveDataFull);
interrupt_status_ |= tape_.get_interrupt_status();
fast_load_is_in_data_ = true;
m6502_.set_value_of_register(CPU::MOS6502::Register::A, 0);
m6502_.set_value_of_register(CPU::MOS6502::Register::Y, tape_.get_data_register());
*value = 0x60; // 0x60 is RTS
}
else *value = os_[address & 16383];
}
else *value = 0xea;
} else {
*value = os_[address & 16383];
}
}
} else {
if(isReadOperation(operation)) {
*value = roms_[active_rom_][address & 16383];
if(keyboard_is_active_) {
*value &= 0xf0;
for(int address_line = 0; address_line < 14; address_line++) {
if(!(address&(1 << address_line))) *value |= key_states_[address_line];
}
}
if(basic_is_active_) {
*value &= roms_[ROMSlotBASIC][address & 16383];
}
} else if(rom_write_masks_[active_rom_]) {
roms_[active_rom_][address & 16383] = *value;
}
}
break;
}
}
cycles_since_display_update_ += Cycles((int)cycles);
cycles_since_audio_update_ += Cycles((int)cycles);
if(cycles_since_audio_update_ > Cycles(16384)) update_audio();
tape_.run_for(Cycles((int)cycles));
cycles_until_display_interrupt_ -= cycles;
if(cycles_until_display_interrupt_ < 0) {
signal_interrupt(next_display_interrupt_);
update_display();
queue_next_display_interrupt();
}
if(typer_) typer_->run_for(Cycles((int)cycles));
if(plus3_) plus3_->run_for(Cycles(4*(int)cycles));
if(shift_restart_counter_) {
shift_restart_counter_ -= cycles;
if(shift_restart_counter_ <= 0) {
shift_restart_counter_ = 0;
m6502_.set_power_on(true);
set_key_state(KeyShift, true);
is_holding_shift_ = true;
}
}
return Cycles((int)cycles);
}
forceinline void flush() {
update_display();
update_audio();
speaker_->flush();
}
void setup_output(float aspect_ratio) override final {
video_output_.reset(new VideoOutput(ram_));
// The maximum output frequency is 62500Hz and all other permitted output frequencies are integral divisions of that;
// however setting the speaker on or off can happen on any 2Mhz cycle, and probably (?) takes effect immediately. So
// run the speaker at a 2000000Hz input rate, at least for the time being.
speaker_.reset(new Speaker);
speaker_->set_input_rate(2000000 / Speaker::clock_rate_divider);
}
void close_output() override final {
video_output_.reset();
}
std::shared_ptr<Outputs::CRT::CRT> get_crt() override final {
return video_output_->get_crt();
}
std::shared_ptr<Outputs::Speaker> get_speaker() override final {
return speaker_;
}
void run_for(const Cycles cycles) override final {
m6502_.run_for(cycles);
}
void tape_did_change_interrupt_status(Tape *tape) override final {
interrupt_status_ = (interrupt_status_ & ~(Interrupt::TransmitDataEmpty | Interrupt::ReceiveDataFull | Interrupt::HighToneDetect)) | tape_.get_interrupt_status();
evaluate_interrupts();
}
HalfCycles get_typer_delay() override final {
return m6502_.get_is_resetting() ? Cycles(625*25*128) : Cycles(0); // wait one second if resetting
}
HalfCycles get_typer_frequency() override final {
return Cycles(625*128*2); // accept a new character every two frames
}
void set_typer_for_string(const char *string) override final {
std::unique_ptr<CharacterMapper> mapper(new CharacterMapper());
Utility::TypeRecipient::set_typer_for_string(string, std::move(mapper));
}
private:
inline void update_display() {
if(cycles_since_display_update_ > 0) {
video_output_->run_for(cycles_since_display_update_.flush());
}
}
inline void queue_next_display_interrupt() {
VideoOutput::Interrupt next_interrupt = video_output_->get_next_interrupt();
cycles_until_display_interrupt_ = next_interrupt.cycles;
next_display_interrupt_ = next_interrupt.interrupt;
}
inline void update_audio() {
if(cycles_since_audio_update_ > 0) {
speaker_->run_for(cycles_since_audio_update_.divide(Cycles(Speaker::clock_rate_divider)));
}
}
inline void signal_interrupt(Interrupt interrupt) {
interrupt_status_ |= interrupt;
evaluate_interrupts();
}
inline void clear_interrupt(Interrupt interrupt) {
interrupt_status_ &= ~interrupt;
evaluate_interrupts();
}
inline void evaluate_interrupts() {
if(interrupt_status_ & interrupt_control_) {
interrupt_status_ |= 1;
} else {
interrupt_status_ &= ~1;
}
m6502_.set_irq_line(interrupt_status_ & 1);
}
CPU::MOS6502::Processor<ConcreteMachine, false> m6502_;
// Things that directly constitute the memory map.
uint8_t roms_[16][16384];
bool rom_write_masks_[16];
uint8_t os_[16384], ram_[32768];
std::vector<uint8_t> dfs_, adfs_;
// Paging
ROMSlot active_rom_;
bool keyboard_is_active_, basic_is_active_;
// Interrupt and keyboard state
uint8_t interrupt_status_, interrupt_control_;
uint8_t key_states_[14];
// Counters related to simultaneous subsystems
Cycles cycles_since_display_update_;
Cycles cycles_since_audio_update_;
int cycles_until_display_interrupt_;
Interrupt next_display_interrupt_;
VideoOutput::Range video_access_range_;
// Tape
Tape tape_;
bool use_fast_tape_hack_;
bool fast_load_is_in_data_;
// Disk
std::unique_ptr<Plus3> plus3_;
bool is_holding_shift_;
int shift_restart_counter_;
// Outputs
std::unique_ptr<VideoOutput> video_output_;
std::shared_ptr<Speaker> speaker_;
bool speaker_is_enabled_;
};
}
using namespace Electron;
#pragma mark - Lifecycle
Machine::Machine() :
interrupt_control_(0),
interrupt_status_(Interrupt::PowerOnReset | Interrupt::TransmitDataEmpty | 0x80),
cycles_since_display_update_(0),
cycles_since_audio_update_(0),
use_fast_tape_hack_(false),
cycles_until_display_interrupt_(0) {
memset(key_states_, 0, sizeof(key_states_));
for(int c = 0; c < 16; c++)
memset(roms_[c], 0xff, 16384);
tape_.set_delegate(this);
set_clock_rate(2000000);
Machine *Machine::Electron() {
return new Electron::ConcreteMachine;
}
#pragma mark - Output
void Machine::setup_output(float aspect_ratio) {
video_output_.reset(new VideoOutput(ram_));
// The maximum output frequency is 62500Hz and all other permitted output frequencies are integral divisions of that;
// however setting the speaker on or off can happen on any 2Mhz cycle, and probably (?) takes effect immediately. So
// run the speaker at a 2000000Hz input rate, at least for the time being.
speaker_.reset(new Speaker);
speaker_->set_input_rate(2000000 / Speaker::clock_rate_divider);
}
void Machine::close_output() {
video_output_.reset();
}
std::shared_ptr<Outputs::CRT::CRT> Machine::get_crt() {
return video_output_->get_crt();
}
std::shared_ptr<Outputs::Speaker> Machine::get_speaker() {
return speaker_;
}
#pragma mark - The keyboard
void Machine::clear_all_keys() {
memset(key_states_, 0, sizeof(key_states_));
if(is_holding_shift_) set_key_state(KeyShift, true);
}
void Machine::set_key_state(uint16_t key, bool isPressed) {
if(key == KeyBreak) {
set_reset_line(isPressed);
} else {
if(isPressed)
key_states_[key >> 4] |= key&0xf;
else
key_states_[key >> 4] &= ~(key&0xf);
}
}
#pragma mark - Machine configuration
void Machine::configure_as_target(const StaticAnalyser::Target &target) {
if(target.tapes.size()) {
tape_.set_tape(target.tapes.front());
}
if(target.disks.size()) {
plus3_.reset(new Plus3);
if(target.acorn.has_dfs) {
set_rom(ROMSlot0, dfs_, true);
}
if(target.acorn.has_adfs) {
set_rom(ROMSlot4, adfs_, true);
set_rom(ROMSlot5, std::vector<uint8_t>(adfs_.begin() + 16384, adfs_.end()), true);
}
plus3_->set_disk(target.disks.front(), 0);
}
ROMSlot slot = ROMSlot12;
for(std::shared_ptr<Storage::Cartridge::Cartridge> cartridge : target.cartridges) {
set_rom(slot, cartridge->get_segments().front().data, false);
slot = (ROMSlot)(((int)slot + 1)&15);
}
if(target.loadingCommand.length()) { // TODO: and automatic loading option enabled
set_typer_for_string(target.loadingCommand.c_str());
}
if(target.acorn.should_shift_restart) {
shift_restart_counter_ = 1000000;
}
}
void Machine::set_rom(ROMSlot slot, std::vector<uint8_t> data, bool is_writeable) {
uint8_t *target = nullptr;
switch(slot) {
case ROMSlotDFS: dfs_ = data; return;
case ROMSlotADFS: adfs_ = data; return;
case ROMSlotOS: target = os_; break;
default:
target = roms_[slot];
rom_write_masks_[slot] = is_writeable;
break;
}
memcpy(target, &data[0], std::min((size_t)16384, data.size()));
}
#pragma mark - The bus
unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
unsigned int cycles = 1;
if(address < 0x8000) {
if(isReadOperation(operation)) {
*value = ram_[address];
} else {
if(address >= video_access_range_.low_address && address <= video_access_range_.high_address) update_display();
ram_[address] = *value;
}
// for the entire frame, RAM is accessible only on odd cycles; in modes below 4
// it's also accessible only outside of the pixel regions
cycles += video_output_->get_cycles_until_next_ram_availability((int)(cycles_since_display_update_ + 1));
} else {
switch(address & 0xff0f) {
case 0xfe00:
if(isReadOperation(operation)) {
*value = interrupt_status_;
interrupt_status_ &= ~PowerOnReset;
} else {
interrupt_control_ = (*value) & ~1;
evaluate_interrupts();
}
break;
case 0xfe07:
if(!isReadOperation(operation)) {
// update speaker mode
bool new_speaker_is_enabled = (*value & 6) == 2;
if(new_speaker_is_enabled != speaker_is_enabled_) {
update_audio();
speaker_->set_is_enabled(new_speaker_is_enabled);
speaker_is_enabled_ = new_speaker_is_enabled;
}
tape_.set_is_enabled((*value & 6) != 6);
tape_.set_is_in_input_mode((*value & 6) == 0);
tape_.set_is_running(((*value)&0x40) ? true : false);
// TODO: caps lock LED
}
// deliberate fallthrough
case 0xfe02: case 0xfe03:
case 0xfe08: case 0xfe09: case 0xfe0a: case 0xfe0b:
case 0xfe0c: case 0xfe0d: case 0xfe0e: case 0xfe0f:
if(!isReadOperation(operation)) {
update_display();
video_output_->set_register(address, *value);
video_access_range_ = video_output_->get_memory_access_range();
queue_next_display_interrupt();
}
break;
case 0xfe04:
if(isReadOperation(operation)) {
*value = tape_.get_data_register();
tape_.clear_interrupts(Interrupt::ReceiveDataFull);
} else {
tape_.set_data_register(*value);
tape_.clear_interrupts(Interrupt::TransmitDataEmpty);
}
break;
case 0xfe05:
if(!isReadOperation(operation)) {
const uint8_t interruptDisable = (*value)&0xf0;
if( interruptDisable ) {
if( interruptDisable&0x10 ) interrupt_status_ &= ~Interrupt::DisplayEnd;
if( interruptDisable&0x20 ) interrupt_status_ &= ~Interrupt::RealTimeClock;
if( interruptDisable&0x40 ) interrupt_status_ &= ~Interrupt::HighToneDetect;
evaluate_interrupts();
// TODO: NMI
}
// latch the paged ROM in case external hardware is being emulated
active_rom_ = (Electron::ROMSlot)(*value & 0xf);
// apply the ULA's test
if(*value & 0x08) {
if(*value & 0x04) {
keyboard_is_active_ = false;
basic_is_active_ = false;
} else {
keyboard_is_active_ = !(*value & 0x02);
basic_is_active_ = !keyboard_is_active_;
}
}
}
break;
case 0xfe06:
if(!isReadOperation(operation)) {
update_audio();
speaker_->set_divider(*value);
tape_.set_counter(*value);
}
break;
case 0xfc04: case 0xfc05: case 0xfc06: case 0xfc07:
if(plus3_ && (address&0x00f0) == 0x00c0) {
if(is_holding_shift_ && address == 0xfcc4) {
is_holding_shift_ = false;
set_key_state(KeyShift, false);
}
if(isReadOperation(operation))
*value = plus3_->get_register(address);
else
plus3_->set_register(address, *value);
}
break;
case 0xfc00:
if(plus3_ && (address&0x00f0) == 0x00c0) {
if(!isReadOperation(operation)) {
plus3_->set_control_register(*value);
} else *value = 1;
}
break;
default:
if(address >= 0xc000) {
if(isReadOperation(operation)) {
if(
use_fast_tape_hack_ &&
tape_.has_tape() &&
(operation == CPU6502::BusOperation::ReadOpcode) &&
(
(address == 0xf4e5) || (address == 0xf4e6) || // double NOPs at 0xf4e5, 0xf6de, 0xf6fa and 0xfa51
(address == 0xf6de) || (address == 0xf6df) || // act to disable the normal branch into tape-handling
(address == 0xf6fa) || (address == 0xf6fb) || // code, forcing the OS along the serially-accessed ROM
(address == 0xfa51) || (address == 0xfa52) || // pathway.
(address == 0xf0a8) // 0xf0a8 is from where a service call would normally be
// dispatched; we can check whether it would be call 14
// (i.e. read byte) and, if so, whether the OS was about to
// issue a read byte call to a ROM despite being the tape
// FS being selected. If so then this is a get byte that
// we should service synthetically. Put the byte into Y
// and set A to zero to report that action was taken, then
// allow the PC read to return an RTS.
)
) {
uint8_t service_call = (uint8_t)get_value_of_register(CPU6502::Register::X);
if(address == 0xf0a8) {
if(!ram_[0x247] && service_call == 14) {
tape_.set_delegate(nullptr);
// TODO: handle tape wrap around.
int cycles_left_while_plausibly_in_data = 50;
tape_.clear_interrupts(Interrupt::ReceiveDataFull);
while(!tape_.get_tape()->is_at_end()) {
tape_.run_for_input_pulse();
cycles_left_while_plausibly_in_data--;
if(!cycles_left_while_plausibly_in_data) fast_load_is_in_data_ = false;
if( (tape_.get_interrupt_status() & Interrupt::ReceiveDataFull) &&
(fast_load_is_in_data_ || tape_.get_data_register() == 0x2a)
) break;
}
tape_.set_delegate(this);
tape_.clear_interrupts(Interrupt::ReceiveDataFull);
interrupt_status_ |= tape_.get_interrupt_status();
fast_load_is_in_data_ = true;
set_value_of_register(CPU6502::Register::A, 0);
set_value_of_register(CPU6502::Register::Y, tape_.get_data_register());
*value = 0x60; // 0x60 is RTS
}
else *value = os_[address & 16383];
}
else *value = 0xea;
} else {
*value = os_[address & 16383];
}
}
} else {
if(isReadOperation(operation)) {
*value = roms_[active_rom_][address & 16383];
if(keyboard_is_active_) {
*value &= 0xf0;
for(int address_line = 0; address_line < 14; address_line++) {
if(!(address&(1 << address_line))) *value |= key_states_[address_line];
}
}
if(basic_is_active_) {
*value &= roms_[ROMSlotBASIC][address & 16383];
}
} else if(rom_write_masks_[active_rom_]) {
roms_[active_rom_][address & 16383] = *value;
}
}
break;
}
}
cycles_since_display_update_ += cycles;
cycles_since_audio_update_ += cycles;
if(cycles_since_audio_update_ > 16384) update_audio();
tape_.run_for_cycles(cycles);
cycles_until_display_interrupt_ -= cycles;
if(cycles_until_display_interrupt_ < 0) {
signal_interrupt(next_display_interrupt_);
update_display();
queue_next_display_interrupt();
}
if(typer_) typer_->update((int)cycles);
if(plus3_) plus3_->run_for_cycles(4*cycles);
if(shift_restart_counter_) {
shift_restart_counter_ -= cycles;
if(shift_restart_counter_ <= 0) {
shift_restart_counter_ = 0;
set_power_on(true);
set_key_state(KeyShift, true);
is_holding_shift_ = true;
}
}
return cycles;
}
void Machine::synchronise() {
update_display();
update_audio();
speaker_->flush();
}
#pragma mark - Deferred scheduling
inline void Machine::update_display() {
if(cycles_since_display_update_) {
video_output_->run_for_cycles((int)cycles_since_display_update_);
cycles_since_display_update_ = 0;
}
}
inline void Machine::queue_next_display_interrupt() {
VideoOutput::Interrupt next_interrupt = video_output_->get_next_interrupt();
cycles_until_display_interrupt_ = next_interrupt.cycles;
next_display_interrupt_ = next_interrupt.interrupt;
}
inline void Machine::update_audio() {
if(cycles_since_audio_update_) {
unsigned int difference = cycles_since_audio_update_ / Speaker::clock_rate_divider;
cycles_since_audio_update_ %= Speaker::clock_rate_divider;
speaker_->run_for_cycles(difference);
}
}
#pragma mark - Interrupts
inline void Machine::signal_interrupt(Electron::Interrupt interrupt) {
interrupt_status_ |= interrupt;
evaluate_interrupts();
}
inline void Machine::clear_interrupt(Electron::Interrupt interrupt) {
interrupt_status_ &= ~interrupt;
evaluate_interrupts();
}
inline void Machine::evaluate_interrupts() {
if(interrupt_status_ & interrupt_control_) {
interrupt_status_ |= 1;
} else {
interrupt_status_ &= ~1;
}
set_irq_line(interrupt_status_ & 1);
}
#pragma mark - Tape::Delegate
void Machine::tape_did_change_interrupt_status(Tape *tape) {
interrupt_status_ = (interrupt_status_ & ~(Interrupt::TransmitDataEmpty | Interrupt::ReceiveDataFull | Interrupt::HighToneDetect)) | tape_.get_interrupt_status();
evaluate_interrupts();
}
Machine::~Machine() {}

View File

@@ -9,18 +9,9 @@
#ifndef Electron_hpp
#define Electron_hpp
#include "../../Processors/6502/CPU6502.hpp"
#include "../../Storage/Tape/Tape.hpp"
#include "../ConfigurationTarget.hpp"
#include "../CRTMachine.hpp"
#include "../Typer.hpp"
#include "Interrupts.hpp"
#include "Plus3.hpp"
#include "Speaker.hpp"
#include "Tape.hpp"
#include "Video.hpp"
#include "../KeyboardMachine.hpp"
#include <cstdint>
#include <vector>
@@ -57,8 +48,6 @@ enum Key: uint16_t {
KeyShift = 0x00d0 | 0x08, KeyControl = 0x00d0 | 0x04, KeyFunc = 0x00d0 | 0x02, KeyEscape = 0x00d0 | 0x01,
KeyBreak = 0xfffd,
TerminateSequence = 0xffff, NotMapped = 0xfffe,
};
/*!
@@ -68,88 +57,23 @@ enum Key: uint16_t {
Acorn Electron.
*/
class Machine:
public CPU6502::Processor<Machine>,
public CRTMachine::Machine,
public Tape::Delegate,
public Utility::TypeRecipient,
public ConfigurationTarget::Machine {
public ConfigurationTarget::Machine,
public KeyboardMachine::Machine {
public:
Machine();
virtual ~Machine();
void set_rom(ROMSlot slot, std::vector<uint8_t> data, bool is_writeable);
/// Creates and returns an Electron.
static Machine *Electron();
void set_key_state(uint16_t key, bool isPressed);
void clear_all_keys();
/*!
Sets the contents of @c slot to @c data. If @c is_writeable is @c true then writing to the slot
is enabled — it acts as if it were sideways RAM. Otherwise the slot is modelled as containing ROM.
*/
virtual void set_rom(ROMSlot slot, std::vector<uint8_t> data, bool is_writeable) = 0;
inline void set_use_fast_tape_hack(bool activate) { use_fast_tape_hack_ = activate; }
// to satisfy ConfigurationTarget::Machine
void configure_as_target(const StaticAnalyser::Target &target);
// to satisfy CPU6502::Processor
unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value);
void synchronise();
// to satisfy CRTMachine::Machine
virtual void setup_output(float aspect_ratio);
virtual void close_output();
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt();
virtual std::shared_ptr<Outputs::Speaker> get_speaker();
virtual void run_for_cycles(int number_of_cycles) { CPU6502::Processor<Machine>::run_for_cycles(number_of_cycles); }
// to satisfy Tape::Delegate
virtual void tape_did_change_interrupt_status(Tape *tape);
// for Utility::TypeRecipient
virtual int get_typer_delay();
virtual int get_typer_frequency();
uint16_t *sequence_for_character(Utility::Typer *typer, char character);
private:
inline void update_display();
inline void queue_next_display_interrupt();
inline void update_audio();
inline void signal_interrupt(Interrupt interrupt);
inline void clear_interrupt(Interrupt interrupt);
inline void evaluate_interrupts();
// Things that directly constitute the memory map.
uint8_t roms_[16][16384];
bool rom_write_masks_[16];
uint8_t os_[16384], ram_[32768];
std::vector<uint8_t> dfs_, adfs_;
// Paging
ROMSlot active_rom_;
bool keyboard_is_active_, basic_is_active_;
// Interrupt and keyboard state
uint8_t interrupt_status_, interrupt_control_;
uint8_t key_states_[14];
// Counters related to simultaneous subsystems
unsigned int cycles_since_display_update_;
unsigned int cycles_since_audio_update_;
int cycles_until_display_interrupt_;
Interrupt next_display_interrupt_;
VideoOutput::Range video_access_range_;
// Tape
Tape tape_;
bool use_fast_tape_hack_;
bool fast_load_is_in_data_;
// Disk
std::unique_ptr<Plus3> plus3_;
bool is_holding_shift_;
int shift_restart_counter_;
// Outputs
std::unique_ptr<VideoOutput> video_output_;
std::shared_ptr<Speaker> speaker_;
bool speaker_is_enabled_;
/// Enables or disables turbo-speed tape loading.
virtual void set_use_fast_tape_hack(bool activate) = 0;
};
}

View File

@@ -17,7 +17,9 @@ Tape::Tape() :
delegate_(nullptr),
output_({.bits_remaining_until_empty = 0, .cycles_into_pulse = 0}),
last_posted_interrupt_status_(0),
interrupt_status_(0) {}
interrupt_status_(0) {
shifter_.set_delegate(this);
}
void Tape::push_tape_bit(uint16_t bit) {
data_register_ = (uint16_t)((data_register_ >> 1) | (bit << 10));
@@ -70,38 +72,22 @@ uint8_t Tape::get_data_register() {
return (uint8_t)(data_register_ >> 2);
}
void Tape::process_input_pulse(Storage::Tape::Tape::Pulse pulse) {
crossings_[0] = crossings_[1];
crossings_[1] = crossings_[2];
crossings_[2] = crossings_[3];
crossings_[3] = Tape::Unrecognised;
if(pulse.type != Storage::Tape::Tape::Pulse::Zero) {
float pulse_length = (float)pulse.length.length / (float)pulse.length.clock_rate;
if(pulse_length >= 0.35 / 2400.0 && pulse_length < 0.7 / 2400.0) crossings_[3] = Tape::Short;
if(pulse_length >= 0.35 / 1200.0 && pulse_length < 0.7 / 1200.0) crossings_[3] = Tape::Long;
}
if(crossings_[0] == Tape::Long && crossings_[1] == Tape::Long) {
push_tape_bit(0);
crossings_[0] = crossings_[1] = Tape::Recognised;
} else {
if(crossings_[0] == Tape::Short && crossings_[1] == Tape::Short && crossings_[2] == Tape::Short && crossings_[3] == Tape::Short) {
push_tape_bit(1);
crossings_[0] = crossings_[1] =
crossings_[2] = crossings_[3] = Tape::Recognised;
}
}
void Tape::process_input_pulse(const Storage::Tape::Tape::Pulse &pulse) {
shifter_.process_pulse(pulse);
}
void Tape::run_for_cycles(unsigned int number_of_cycles) {
void Tape::acorn_shifter_output_bit(int value) {
push_tape_bit((uint16_t)value);
}
void Tape::run_for(const Cycles cycles) {
if(is_enabled_) {
if(is_in_input_mode_) {
if(is_running_) {
TapePlayer::run_for_cycles((int)number_of_cycles);
TapePlayer::run_for(cycles);
}
} else {
output_.cycles_into_pulse += number_of_cycles;
output_.cycles_into_pulse += (unsigned int)cycles.as_int();
while(output_.cycles_into_pulse > 1664) { // 1664 = the closest you can get to 1200 baud if you're looking for something
output_.cycles_into_pulse -= 1664; // that divides the 125,000Hz clock that the sound divider runs off.
push_tape_bit(1);

View File

@@ -9,18 +9,23 @@
#ifndef Electron_Tape_h
#define Electron_Tape_h
#include "../../Storage/Tape/Tape.hpp"
#include "Interrupts.hpp"
#include <cstdint>
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../Storage/Tape/Tape.hpp"
#include "../../Storage/Tape/Parsers/Acorn.hpp"
#include "Interrupts.hpp"
namespace Electron {
class Tape: public Storage::Tape::TapePlayer {
class Tape:
public Storage::Tape::TapePlayer,
public Storage::Tape::Acorn::Shifter::Delegate {
public:
Tape();
void run_for_cycles(unsigned int number_of_cycles);
void run_for(const Cycles cycles);
using Storage::Tape::TapePlayer::run_for;
uint8_t get_data_register();
void set_data_register(uint8_t value);
@@ -39,8 +44,10 @@ class Tape: public Storage::Tape::TapePlayer {
inline void set_is_enabled(bool is_enabled) { is_enabled_ = is_enabled; }
void set_is_in_input_mode(bool is_in_input_mode);
void acorn_shifter_output_bit(int value);
private:
void process_input_pulse(Storage::Tape::Tape::Pulse pulse);
void process_input_pulse(const Storage::Tape::Tape::Pulse &pulse);
inline void push_tape_bit(uint16_t bit);
inline void get_next_tape_pulse();
@@ -62,9 +69,7 @@ class Tape: public Storage::Tape::TapePlayer {
uint8_t interrupt_status_, last_posted_interrupt_status_;
Delegate *delegate_;
enum {
Long, Short, Unrecognised, Recognised
} crossings_[4];
::Storage::Tape::Acorn::Shifter shifter_;
};
}

View File

@@ -223,7 +223,8 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) {
}
}
void VideoOutput::run_for_cycles(int number_of_cycles) {
void VideoOutput::run_for(const Cycles cycles) {
int number_of_cycles = cycles.as_int();
output_position_ = (output_position_ + number_of_cycles) % cycles_per_frame;
while(number_of_cycles) {
int draw_action_length = screen_map_[screen_map_pointer_].length;
@@ -425,9 +426,9 @@ void VideoOutput::setup_screen_map() {
screen_map_.emplace_back(DrawAction::Blank, cycles_per_line >> 1);
screen_map_.emplace_back(DrawAction::Sync, (cycles_per_line * 5) >> 1);
}
for(int c = 0; c < first_graphics_line - 3; c++) emplace_blank_line();
for(int c = 0; c < 256; c++) emplace_pixel_line();
for(int c = 256 + first_graphics_line; c < 312; c++) emplace_blank_line();
for(int l = 0; l < first_graphics_line - 3; l++) emplace_blank_line();
for(int l = 0; l < 256; l++) emplace_pixel_line();
for(int l = 256 + first_graphics_line; l < 312; l++) emplace_blank_line();
if(c&1) emplace_blank_line();
}
}

View File

@@ -10,6 +10,7 @@
#define Machines_Electron_Video_hpp
#include "../../Outputs/CRT/CRT.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "Interrupts.hpp"
namespace Electron {
@@ -32,8 +33,8 @@ class VideoOutput {
/// @returns the CRT to which output is being painted.
std::shared_ptr<Outputs::CRT::CRT> get_crt();
/// Produces the next @c number_of_cycles cycles of video output.
void run_for_cycles(int number_of_cycles);
/// Produces the next @c cycles of video output.
void run_for(const Cycles cycles);
/*!
Writes @c value to the register at @c address. May mutate the results of @c get_next_interrupt,
@@ -53,14 +54,14 @@ class VideoOutput {
/*!
@returns the next interrupt that should be generated as a result of the video hardware.
The time until signalling returned is the number of cycles after the final one triggered
by the most recent call to @c run_for_cycles.
by the most recent call to @c run_for.
This result may be mutated by calls to @c set_register.
*/
Interrupt get_next_interrupt();
/*!
@returns the number of cycles after (final cycle of last run_for_cycles batch + @c from_time)
@returns the number of cycles after (final cycle of last run_for batch + @c from_time)
before the video circuits will allow the CPU to access RAM.
*/
unsigned int get_cycles_until_next_ram_availability(int from_time);

View File

@@ -13,7 +13,15 @@ namespace KeyboardMachine {
class Machine {
public:
/*!
Indicates that the key @c key has been either pressed or released, according to
the state of @c isPressed.
*/
virtual void set_key_state(uint16_t key, bool isPressed) = 0;
/*!
Instructs that all keys should now be treated as released.
*/
virtual void clear_all_keys() = 0;
};

View File

@@ -22,3 +22,7 @@ void Memory::Fuzz(uint8_t *buffer, size_t size) {
buffer[c] = (uint8_t)(std::rand() >> shift);
}
}
void Memory::Fuzz(std::vector<uint8_t> &buffer) {
Fuzz(buffer.data(), buffer.size());
}

View File

@@ -11,11 +11,16 @@
#include <cstdint>
#include <cstddef>
#include <vector>
namespace Memory {
/// Stores @c size random bytes from @c buffer onwards.
void Fuzz(uint8_t *buffer, size_t size);
// Replaces all existing vector contents with random bytes.
void Fuzz(std::vector<uint8_t> &buffer);
}
#endif /* MemoryFuzzer_hpp */

View File

@@ -1,10 +1,21 @@
//
// CharacterMapper.cpp
// Clock Signal
//
// Created by Thomas Harte on 03/08/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "CharacterMapper.hpp"
#include "Oric.hpp"
uint16_t *Oric::Machine::sequence_for_character(Utility::Typer *typer, char character) {
#define KEYS(...) {__VA_ARGS__, TerminateSequence}
#define SHIFT(...) {KeyLeftShift, __VA_ARGS__, TerminateSequence}
using namespace Oric;
uint16_t *CharacterMapper::sequence_for_character(char character) {
#define KEYS(...) {__VA_ARGS__, EndSequence}
#define SHIFT(...) {KeyLeftShift, __VA_ARGS__, EndSequence}
#define X {NotMapped}
static Key key_sequences[][3] = {
static KeySequence key_sequences[] = {
/* NUL */ X, /* SOH */ X,
/* STX */ X, /* ETX */ X,
/* EOT */ X, /* ENQ */ X,
@@ -73,7 +84,5 @@ uint16_t *Oric::Machine::sequence_for_character(Utility::Typer *typer, char char
#undef SHIFT
#undef X
if(character > sizeof(key_sequences) / sizeof(*key_sequences)) return nullptr;
if(key_sequences[character][0] == NotMapped) return nullptr;
return (uint16_t *)key_sequences[character];
return table_lookup_sequence_for_character(key_sequences, sizeof(key_sequences), character);
}

View File

@@ -0,0 +1,23 @@
//
// CharacterMapper.hpp
// Clock Signal
//
// Created by Thomas Harte on 03/08/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef Machines_Oric_CharacterMapper_hpp
#define Machines_Oric_CharacterMapper_hpp
#include "../Typer.hpp"
namespace Oric {
class CharacterMapper: public ::Utility::CharacterMapper {
public:
uint16_t *sequence_for_character(char character);
};
}
#endif /* Machines_Oric_CharacterMapper_hpp */

View File

@@ -104,12 +104,12 @@ void Microdisc::set_head_load_request(bool head_load) {
}
}
void Microdisc::run_for_cycles(unsigned int number_of_cycles) {
void Microdisc::run_for(const Cycles cycles) {
if(head_load_request_counter_ < head_load_request_counter_target) {
head_load_request_counter_ += number_of_cycles;
head_load_request_counter_ += cycles.as_int();
if(head_load_request_counter_ >= head_load_request_counter_target) set_head_loaded(true);
}
WD::WD1770::run_for_cycles(number_of_cycles);
WD::WD1770::run_for(cycles);
}
bool Microdisc::get_drive_is_ready() {

View File

@@ -24,7 +24,8 @@ class Microdisc: public WD::WD1770 {
bool get_interrupt_request_line();
void run_for_cycles(unsigned int number_of_cycles);
void run_for(const Cycles cycles);
using WD::WD1770::run_for;
enum PagingFlags {
BASICDisable = (1 << 0),

View File

@@ -7,283 +7,381 @@
//
#include "Oric.hpp"
#include "Video.hpp"
#include "Microdisc.hpp"
#include "CharacterMapper.hpp"
#include "../MemoryFuzzer.hpp"
#include "../Typer.hpp"
#include "../../Processors/6502/6502.hpp"
#include "../../Components/6522/6522.hpp"
#include "../../Components/AY38910/AY38910.hpp"
#include "../../Storage/Tape/Tape.hpp"
#include "../../Storage/Tape/Parsers/Oric.hpp"
#include "../../ClockReceiver/ForceInline.hpp"
#include <cstdint>
#include <memory>
#include <vector>
namespace Oric {
class ConcreteMachine:
public CPU::MOS6502::BusHandler,
public MOS::MOS6522IRQDelegate::Delegate,
public Utility::TypeRecipient,
public Storage::Tape::BinaryTapePlayer::Delegate,
public Microdisc::Delegate,
public Machine {
public:
ConcreteMachine() :
m6502_(*this),
use_fast_tape_hack_(false),
typer_delay_(2500000),
keyboard_read_count_(0),
keyboard_(new Keyboard),
ram_top_(0xbfff),
paged_rom_(rom_),
microdisc_is_enabled_(false) {
set_clock_rate(1000000);
via_.set_interrupt_delegate(this);
via_.keyboard = keyboard_;
clear_all_keys();
via_.tape->set_delegate(this);
Memory::Fuzz(ram_, sizeof(ram_));
}
void set_rom(ROM rom, const std::vector<uint8_t> &data) override final {
switch(rom) {
case BASIC11: basic11_rom_ = std::move(data); break;
case BASIC10: basic10_rom_ = std::move(data); break;
case Microdisc: microdisc_rom_ = std::move(data); break;
case Colour:
colour_rom_ = std::move(data);
if(video_output_) video_output_->set_colour_rom(colour_rom_);
break;
}
}
void set_key_state(uint16_t key, bool isPressed) override final {
if(key == KeyNMI) {
m6502_.set_nmi_line(isPressed);
} else {
if(isPressed)
keyboard_->rows[key >> 8] |= (key & 0xff);
else
keyboard_->rows[key >> 8] &= ~(key & 0xff);
}
}
void clear_all_keys() override final {
memset(keyboard_->rows, 0, sizeof(keyboard_->rows));
}
void set_use_fast_tape_hack(bool activate) override final {
use_fast_tape_hack_ = activate;
}
void set_output_device(Outputs::CRT::OutputDevice output_device) override final {
video_output_->set_output_device(output_device);
}
// to satisfy ConfigurationTarget::Machine
void configure_as_target(const StaticAnalyser::Target &target) override final {
if(target.oric.has_microdisc) {
microdisc_is_enabled_ = true;
microdisc_did_change_paging_flags(&microdisc_);
microdisc_.set_delegate(this);
}
if(target.loadingCommand.length()) {
set_typer_for_string(target.loadingCommand.c_str());
}
if(target.oric.use_atmos_rom) {
memcpy(rom_, basic11_rom_.data(), std::min(basic11_rom_.size(), sizeof(rom_)));
is_using_basic11_ = true;
tape_get_byte_address_ = 0xe6c9;
scan_keyboard_address_ = 0xf495;
tape_speed_address_ = 0x024d;
} else {
memcpy(rom_, basic10_rom_.data(), std::min(basic10_rom_.size(), sizeof(rom_)));
is_using_basic11_ = false;
tape_get_byte_address_ = 0xe630;
scan_keyboard_address_ = 0xf43c;
tape_speed_address_ = 0x67;
}
insert_media(target.media);
}
bool insert_media(const StaticAnalyser::Media &media) override final {
if(media.tapes.size()) {
via_.tape->set_tape(media.tapes.front());
}
int drive_index = 0;
for(auto disk : media.disks) {
if(drive_index < 4) microdisc_.set_disk(disk, drive_index);
drive_index++;
}
return !media.tapes.empty() || (!media.disks.empty() && microdisc_is_enabled_);
}
// to satisfy CPU::MOS6502::BusHandler
forceinline Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
if(address > ram_top_) {
if(isReadOperation(operation)) *value = paged_rom_[address - ram_top_ - 1];
// 024D = 0 => fast; otherwise slow
// E6C9 = read byte: return byte in A
if(address == tape_get_byte_address_ && paged_rom_ == rom_ && use_fast_tape_hack_ && operation == CPU::MOS6502::BusOperation::ReadOpcode && via_.tape->has_tape() && !via_.tape->get_tape()->is_at_end()) {
uint8_t next_byte = via_.tape->get_next_byte(!ram_[tape_speed_address_]);
m6502_.set_value_of_register(CPU::MOS6502::A, next_byte);
m6502_.set_value_of_register(CPU::MOS6502::Flags, next_byte ? 0 : CPU::MOS6502::Flag::Zero);
*value = 0x60; // i.e. RTS
}
} else {
if((address & 0xff00) == 0x0300) {
if(microdisc_is_enabled_ && address >= 0x0310) {
switch(address) {
case 0x0310: case 0x0311: case 0x0312: case 0x0313:
if(isReadOperation(operation)) *value = microdisc_.get_register(address);
else microdisc_.set_register(address, *value);
break;
case 0x314: case 0x315: case 0x316: case 0x317:
if(isReadOperation(operation)) *value = microdisc_.get_interrupt_request_register();
else microdisc_.set_control_register(*value);
break;
case 0x318: case 0x319: case 0x31a: case 0x31b:
if(isReadOperation(operation)) *value = microdisc_.get_data_request_register();
break;
}
} else {
if(isReadOperation(operation)) *value = via_.get_register(address);
else via_.set_register(address, *value);
}
} else {
if(isReadOperation(operation))
*value = ram_[address];
else {
if(address >= 0x9800 && address <= 0xc000) { update_video(); typer_delay_ = 0; }
ram_[address] = *value;
}
}
}
if(typer_ && address == scan_keyboard_address_ && operation == CPU::MOS6502::BusOperation::ReadOpcode) {
// the Oric 1 misses any key pressed on the very first entry into the read keyboard routine, so don't
// do anything until at least the second, regardless of machine
if(!keyboard_read_count_) keyboard_read_count_++;
else if(!typer_->type_next_character()) {
clear_all_keys();
typer_.reset();
}
}
via_.run_for(Cycles(1));
if(microdisc_is_enabled_) microdisc_.run_for(Cycles(8));
cycles_since_video_update_++;
return Cycles(1);
}
forceinline void flush() {
update_video();
via_.flush();
}
// to satisfy CRTMachine::Machine
void setup_output(float aspect_ratio) override final {
via_.ay8910.reset(new GI::AY38910::AY38910());
via_.ay8910->set_clock_rate(1000000);
video_output_.reset(new VideoOutput(ram_));
if(!colour_rom_.empty()) video_output_->set_colour_rom(colour_rom_);
}
void close_output() override final {
video_output_.reset();
via_.ay8910.reset();
}
std::shared_ptr<Outputs::CRT::CRT> get_crt() override final {
return video_output_->get_crt();
}
std::shared_ptr<Outputs::Speaker> get_speaker() override final {
return via_.ay8910;
}
void run_for(const Cycles cycles) override final {
m6502_.run_for(cycles);
}
// to satisfy MOS::MOS6522IRQDelegate::Delegate
void mos6522_did_change_interrupt_status(void *mos6522) override final {
set_interrupt_line();
}
// to satisfy Storage::Tape::BinaryTapePlayer::Delegate
void tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape_player) override final {
// set CB1
via_.set_control_line_input(VIA::Port::B, VIA::Line::One, !tape_player->get_input());
}
// for Utility::TypeRecipient::Delegate
void set_typer_for_string(const char *string) override final {
std::unique_ptr<CharacterMapper> mapper(new CharacterMapper);
Utility::TypeRecipient::set_typer_for_string(string, std::move(mapper));
}
// for Microdisc::Delegate
void microdisc_did_change_paging_flags(class Microdisc *microdisc) override final {
int flags = microdisc->get_paging_flags();
if(!(flags&Microdisc::PagingFlags::BASICDisable)) {
ram_top_ = 0xbfff;
paged_rom_ = rom_;
} else {
if(flags&Microdisc::PagingFlags::MicrodscDisable) {
ram_top_ = 0xffff;
} else {
ram_top_ = 0xdfff;
paged_rom_ = microdisc_rom_.data();
}
}
}
void wd1770_did_change_output(WD::WD1770 *wd1770) override final {
set_interrupt_line();
}
private:
CPU::MOS6502::Processor<ConcreteMachine, false> m6502_;
// RAM and ROM
std::vector<uint8_t> basic11_rom_, basic10_rom_, microdisc_rom_, colour_rom_;
uint8_t ram_[65536], rom_[16384];
Cycles cycles_since_video_update_;
inline void update_video() {
video_output_->run_for(cycles_since_video_update_.flush());
}
// ROM bookkeeping
bool is_using_basic11_;
uint16_t tape_get_byte_address_, scan_keyboard_address_, tape_speed_address_;
int keyboard_read_count_;
// Outputs
std::unique_ptr<VideoOutput> video_output_;
// Keyboard
class Keyboard {
public:
uint8_t row;
uint8_t rows[8];
};
int typer_delay_;
// The tape
class TapePlayer: public Storage::Tape::BinaryTapePlayer {
public:
TapePlayer() : Storage::Tape::BinaryTapePlayer(1000000) {}
inline uint8_t get_next_byte(bool fast) {
return (uint8_t)parser_.get_next_byte(get_tape(), fast);
}
private:
Storage::Tape::Oric::Parser parser_;
};
bool use_fast_tape_hack_;
// VIA (which owns the tape and the AY)
class VIA: public MOS::MOS6522<VIA>, public MOS::MOS6522IRQDelegate {
public:
VIA() :
MOS::MOS6522<VIA>(),
tape(new TapePlayer) {}
using MOS6522IRQDelegate::set_interrupt_status;
void set_control_line_output(Port port, Line line, bool value) {
if(line) {
if(port) ay_bdir_ = value; else ay_bc1_ = value;
update_ay();
}
}
void set_port_output(Port port, uint8_t value, uint8_t direction_mask) {
if(port) {
keyboard->row = value;
tape->set_motor_control(value & 0x40);
} else {
ay8910->set_data_input(value);
}
}
uint8_t get_port_input(Port port) {
if(port) {
uint8_t column = ay8910->get_port_output(false) ^ 0xff;
return (keyboard->rows[keyboard->row & 7] & column) ? 0x08 : 0x00;
} else {
return ay8910->get_data_output();
}
}
inline void run_for(const Cycles cycles) {
cycles_since_ay_update_ += cycles;
MOS::MOS6522<VIA>::run_for(cycles);
tape->run_for(cycles);
}
void flush() {
ay8910->run_for(cycles_since_ay_update_.flush());
ay8910->flush();
}
std::shared_ptr<GI::AY38910::AY38910> ay8910;
std::unique_ptr<TapePlayer> tape;
std::shared_ptr<Keyboard> keyboard;
private:
void update_ay() {
ay8910->run_for(cycles_since_ay_update_.flush());
ay8910->set_control_lines( (GI::AY38910::ControlLines)((ay_bdir_ ? GI::AY38910::BDIR : 0) | (ay_bc1_ ? GI::AY38910::BC1 : 0) | GI::AY38910::BC2));
}
bool ay_bdir_, ay_bc1_;
Cycles cycles_since_ay_update_;
};
VIA via_;
std::shared_ptr<Keyboard> keyboard_;
// the Microdisc, if in use
class Microdisc microdisc_;
bool microdisc_is_enabled_;
uint16_t ram_top_;
uint8_t *paged_rom_;
inline void set_interrupt_line() {
m6502_.set_irq_line(
via_.get_interrupt_line() ||
(microdisc_is_enabled_ && microdisc_.get_interrupt_request_line()));
}
};
}
using namespace Oric;
Machine::Machine() :
cycles_since_video_update_(0),
use_fast_tape_hack_(false),
typer_delay_(2500000),
keyboard_read_count_(0),
keyboard_(new Keyboard),
ram_top_(0xbfff),
paged_rom_(rom_),
microdisc_is_enabled_(false) {
set_clock_rate(1000000);
via_.set_interrupt_delegate(this);
via_.keyboard = keyboard_;
clear_all_keys();
via_.tape->set_delegate(this);
Memory::Fuzz(ram_, sizeof(ram_));
Machine *Machine::Oric() {
return new ConcreteMachine;
}
void Machine::configure_as_target(const StaticAnalyser::Target &target) {
if(target.tapes.size()) {
via_.tape->set_tape(target.tapes.front());
}
if(target.loadingCommand.length()) { // TODO: and automatic loading option enabled
set_typer_for_string(target.loadingCommand.c_str());
}
if(target.oric.has_microdisc) {
microdisc_is_enabled_ = true;
microdisc_did_change_paging_flags(&microdisc_);
microdisc_.set_delegate(this);
}
int drive_index = 0;
for(auto disk : target.disks) {
if(drive_index < 4) microdisc_.set_disk(disk, drive_index);
drive_index++;
}
if(target.oric.use_atmos_rom) {
memcpy(rom_, basic11_rom_.data(), std::min(basic11_rom_.size(), sizeof(rom_)));
is_using_basic11_ = true;
tape_get_byte_address_ = 0xe6c9;
scan_keyboard_address_ = 0xf495;
tape_speed_address_ = 0x024d;
} else {
memcpy(rom_, basic10_rom_.data(), std::min(basic10_rom_.size(), sizeof(rom_)));
is_using_basic11_ = false;
tape_get_byte_address_ = 0xe630;
scan_keyboard_address_ = 0xf43c;
tape_speed_address_ = 0x67;
}
}
void Machine::set_rom(ROM rom, const std::vector<uint8_t> &data) {
switch(rom) {
case BASIC11: basic11_rom_ = std::move(data); break;
case BASIC10: basic10_rom_ = std::move(data); break;
case Microdisc: microdisc_rom_ = std::move(data); break;
case Colour:
colour_rom_ = std::move(data);
if(video_output_) video_output_->set_colour_rom(colour_rom_);
break;
}
}
unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) {
if(address > ram_top_) {
if(isReadOperation(operation)) *value = paged_rom_[address - ram_top_ - 1];
// 024D = 0 => fast; otherwise slow
// E6C9 = read byte: return byte in A
if(address == tape_get_byte_address_ && paged_rom_ == rom_ && use_fast_tape_hack_ && operation == CPU6502::BusOperation::ReadOpcode && via_.tape->has_tape() && !via_.tape->get_tape()->is_at_end()) {
uint8_t next_byte = via_.tape->get_next_byte(!ram_[tape_speed_address_]);
set_value_of_register(CPU6502::A, next_byte);
set_value_of_register(CPU6502::Flags, next_byte ? 0 : CPU6502::Flag::Zero);
*value = 0x60; // i.e. RTS
}
} else {
if((address & 0xff00) == 0x0300) {
if(microdisc_is_enabled_ && address >= 0x0310) {
switch(address) {
case 0x0310: case 0x0311: case 0x0312: case 0x0313:
if(isReadOperation(operation)) *value = microdisc_.get_register(address);
else microdisc_.set_register(address, *value);
break;
case 0x314: case 0x315: case 0x316: case 0x317:
if(isReadOperation(operation)) *value = microdisc_.get_interrupt_request_register();
else microdisc_.set_control_register(*value);
break;
case 0x318: case 0x319: case 0x31a: case 0x31b:
if(isReadOperation(operation)) *value = microdisc_.get_data_request_register();
break;
}
} else {
if(isReadOperation(operation)) *value = via_.get_register(address);
else via_.set_register(address, *value);
}
} else {
if(isReadOperation(operation))
*value = ram_[address];
else {
if(address >= 0x9800 && address <= 0xc000) { update_video(); typer_delay_ = 0; }
ram_[address] = *value;
}
}
}
if(typer_ && address == scan_keyboard_address_ && operation == CPU6502::BusOperation::ReadOpcode) {
// the Oric 1 misses any key pressed on the very first entry into the read keyboard routine, so don't
// do anything until at least the second, regardless of machine
if(!keyboard_read_count_) keyboard_read_count_++;
else if(!typer_->type_next_character()) {
clear_all_keys();
typer_.reset();
}
}
via_.run_for_cycles(1);
if(microdisc_is_enabled_) microdisc_.run_for_cycles(8);
cycles_since_video_update_++;
return 1;
}
void Machine::synchronise() {
update_video();
via_.synchronise();
}
void Machine::update_video() {
video_output_->run_for_cycles(cycles_since_video_update_);
cycles_since_video_update_ = 0;
}
void Machine::setup_output(float aspect_ratio) {
via_.ay8910.reset(new GI::AY38910());
via_.ay8910->set_clock_rate(1000000);
video_output_.reset(new VideoOutput(ram_));
if(!colour_rom_.empty()) video_output_->set_colour_rom(colour_rom_);
}
void Machine::close_output() {
video_output_.reset();
via_.ay8910.reset();
}
void Machine::mos6522_did_change_interrupt_status(void *mos6522) {
set_interrupt_line();
}
void Machine::set_key_state(uint16_t key, bool isPressed) {
if(key == KeyNMI) {
set_nmi_line(isPressed);
} else {
if(isPressed)
keyboard_->rows[key >> 8] |= (key & 0xff);
else
keyboard_->rows[key >> 8] &= ~(key & 0xff);
}
}
void Machine::clear_all_keys() {
memset(keyboard_->rows, 0, sizeof(keyboard_->rows));
}
void Machine::set_use_fast_tape_hack(bool activate) {
use_fast_tape_hack_ = activate;
}
void Machine::set_output_device(Outputs::CRT::OutputDevice output_device) {
video_output_->set_output_device(output_device);
}
void Machine::tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape_player) {
// set CB1
via_.set_control_line_input(VIA::Port::B, VIA::Line::One, tape_player->get_input());
}
std::shared_ptr<Outputs::CRT::CRT> Machine::get_crt() {
return video_output_->get_crt();
}
std::shared_ptr<Outputs::Speaker> Machine::get_speaker() {
return via_.ay8910;
}
void Machine::run_for_cycles(int number_of_cycles) {
CPU6502::Processor<Machine>::run_for_cycles(number_of_cycles);
}
#pragma mark - The 6522
Machine::VIA::VIA() :
MOS::MOS6522<Machine::VIA>(),
cycles_since_ay_update_(0),
tape(new TapePlayer) {}
void Machine::VIA::set_control_line_output(Port port, Line line, bool value) {
if(line) {
if(port) ay_bdir_ = value; else ay_bc1_ = value;
update_ay();
}
}
void Machine::VIA::set_port_output(Port port, uint8_t value, uint8_t direction_mask) {
if(port) {
keyboard->row = value;
tape->set_motor_control(value & 0x40);
} else {
ay8910->set_data_input(value);
}
}
uint8_t Machine::VIA::get_port_input(Port port) {
if(port) {
uint8_t column = ay8910->get_port_output(false) ^ 0xff;
return (keyboard->rows[keyboard->row & 7] & column) ? 0x08 : 0x00;
} else {
return ay8910->get_data_output();
}
}
void Machine::VIA::synchronise() {
ay8910->run_for_cycles(cycles_since_ay_update_);
ay8910->flush();
cycles_since_ay_update_ = 0;
}
void Machine::VIA::run_for_cycles(unsigned int number_of_cycles) {
cycles_since_ay_update_ += number_of_cycles;
MOS::MOS6522<VIA>::run_for_cycles(number_of_cycles);
tape->run_for_cycles((int)number_of_cycles);
}
void Machine::VIA::update_ay() {
ay8910->run_for_cycles(cycles_since_ay_update_);
cycles_since_ay_update_ = 0;
ay8910->set_control_lines( (GI::AY38910::ControlLines)((ay_bdir_ ? GI::AY38910::BCDIR : 0) | (ay_bc1_ ? GI::AY38910::BC1 : 0) | GI::AY38910::BC2));
}
#pragma mark - TapePlayer
Machine::TapePlayer::TapePlayer() :
Storage::Tape::BinaryTapePlayer(1000000) {}
uint8_t Machine::TapePlayer::get_next_byte(bool fast) {
return (uint8_t)parser_.get_next_byte(get_tape(), fast);
}
#pragma mark - Microdisc
void Machine::microdisc_did_change_paging_flags(class Microdisc *microdisc) {
int flags = microdisc->get_paging_flags();
if(!(flags&Microdisc::PagingFlags::BASICDisable)) {
ram_top_ = 0xbfff;
paged_rom_ = rom_;
} else {
if(flags&Microdisc::PagingFlags::MicrodscDisable) {
ram_top_ = 0xffff;
} else {
ram_top_ = 0xdfff;
paged_rom_ = microdisc_rom_.data();
}
}
}
void Machine::wd1770_did_change_output(WD::WD1770 *wd1770) {
set_interrupt_line();
}
void Machine::set_interrupt_line() {
set_irq_line(
via_.get_interrupt_line() ||
(microdisc_is_enabled_ && microdisc_.get_interrupt_request_line()));
}
Machine::~Machine() {}

View File

@@ -11,24 +11,17 @@
#include "../ConfigurationTarget.hpp"
#include "../CRTMachine.hpp"
#include "../Typer.hpp"
#include "../../Processors/6502/CPU6502.hpp"
#include "../../Components/6522/6522.hpp"
#include "../../Components/AY38910/AY38910.hpp"
#include "../../Storage/Tape/Parsers/Oric.hpp"
#include "Video.hpp"
#include "Microdisc.hpp"
#include "../../Storage/Tape/Tape.hpp"
#include "../KeyboardMachine.hpp"
#include <cstdint>
#include <vector>
#include <memory>
namespace Oric {
enum ROM {
BASIC10, BASIC11, Microdisc, Colour
};
enum Key: uint16_t {
Key3 = 0x0000 | 0x80, KeyX = 0x0000 | 0x40, Key1 = 0x0000 | 0x20,
KeyV = 0x0000 | 0x08, Key5 = 0x0000 | 0x04, KeyN = 0x0000 | 0x02, Key7 = 0x0000 | 0x01,
@@ -48,126 +41,29 @@ enum Key: uint16_t {
KeyForwardSlash = 0x0700 | 0x08, Key0 = 0x0700 | 0x04, KeyL = 0x0700 | 0x02, Key8 = 0x0700 | 0x01,
KeyNMI = 0xfffd,
TerminateSequence = 0xffff, NotMapped = 0xfffe
};
enum ROM {
BASIC10, BASIC11, Microdisc, Colour
};
/*!
Models an Oric 1/Atmos with or without a Microdisc.
*/
class Machine:
public CPU6502::Processor<Machine>,
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public MOS::MOS6522IRQDelegate::Delegate,
public Utility::TypeRecipient,
public Storage::Tape::BinaryTapePlayer::Delegate,
public Microdisc::Delegate {
public KeyboardMachine::Machine {
public:
Machine();
virtual ~Machine();
void set_rom(ROM rom, const std::vector<uint8_t> &data);
void set_key_state(uint16_t key, bool isPressed);
void clear_all_keys();
/// Creates and returns an Oric.
static Machine *Oric();
void set_use_fast_tape_hack(bool activate);
void set_output_device(Outputs::CRT::OutputDevice output_device);
/// Sets the contents of @c rom to @c data. Assumed to be a setup step; has no effect once a machine is running.
virtual void set_rom(ROM rom, const std::vector<uint8_t> &data) = 0;
// to satisfy ConfigurationTarget::Machine
void configure_as_target(const StaticAnalyser::Target &target);
/// Enables or disables turbo-speed tape loading.
virtual void set_use_fast_tape_hack(bool activate) = 0;
// to satisfy CPU6502::Processor
unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value);
void synchronise();
// to satisfy CRTMachine::Machine
virtual void setup_output(float aspect_ratio);
virtual void close_output();
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt();
virtual std::shared_ptr<Outputs::Speaker> get_speaker();
virtual void run_for_cycles(int number_of_cycles);
// to satisfy MOS::MOS6522IRQDelegate::Delegate
void mos6522_did_change_interrupt_status(void *mos6522);
// to satisfy Storage::Tape::BinaryTapePlayer::Delegate
void tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape_player);
// for Utility::TypeRecipient::Delegate
uint16_t *sequence_for_character(Utility::Typer *typer, char character);
// for Microdisc::Delegate
void microdisc_did_change_paging_flags(class Microdisc *microdisc);
void wd1770_did_change_output(WD::WD1770 *wd1770);
private:
// RAM and ROM
std::vector<uint8_t> basic11_rom_, basic10_rom_, microdisc_rom_, colour_rom_;
uint8_t ram_[65536], rom_[16384];
int cycles_since_video_update_;
inline void update_video();
// ROM bookkeeping
bool is_using_basic11_;
uint16_t tape_get_byte_address_, scan_keyboard_address_, tape_speed_address_;
int keyboard_read_count_;
// Outputs
std::unique_ptr<VideoOutput> video_output_;
// Keyboard
class Keyboard {
public:
uint8_t row;
uint8_t rows[8];
};
int typer_delay_;
// The tape
class TapePlayer: public Storage::Tape::BinaryTapePlayer {
public:
TapePlayer();
uint8_t get_next_byte(bool fast);
private:
Storage::Tape::Oric::Parser parser_;
};
bool use_fast_tape_hack_;
// VIA (which owns the tape and the AY)
class VIA: public MOS::MOS6522<VIA>, public MOS::MOS6522IRQDelegate {
public:
VIA();
using MOS6522IRQDelegate::set_interrupt_status;
void set_control_line_output(Port port, Line line, bool value);
void set_port_output(Port port, uint8_t value, uint8_t direction_mask);
uint8_t get_port_input(Port port);
inline void run_for_cycles(unsigned int number_of_cycles);
std::shared_ptr<GI::AY38910> ay8910;
std::unique_ptr<TapePlayer> tape;
std::shared_ptr<Keyboard> keyboard;
void synchronise();
private:
void update_ay();
bool ay_bdir_, ay_bc1_;
unsigned int cycles_since_ay_update_;
};
VIA via_;
std::shared_ptr<Keyboard> keyboard_;
// the Microdisc, if in use
class Microdisc microdisc_;
bool microdisc_is_enabled_;
uint16_t ram_top_;
uint8_t *paged_rom_;
inline void set_interrupt_line();
/// Sets the type of display the Oric is connected to.
virtual void set_output_device(Outputs::CRT::OutputDevice output_device) = 0;
};
}

View File

@@ -74,13 +74,14 @@ std::shared_ptr<Outputs::CRT::CRT> VideoOutput::get_crt() {
return crt_;
}
void VideoOutput::run_for_cycles(int number_of_cycles) {
void VideoOutput::run_for(const Cycles cycles) {
// Vertical: 039: pixels; otherwise blank; 4853 sync, 5456 colour burst
// Horizontal: 0223: pixels; otherwise blank; 256259 sync
#define clamp(action) \
if(cycles_run_for <= number_of_cycles) { action; } else cycles_run_for = number_of_cycles;
int number_of_cycles = cycles.as_int();
while(number_of_cycles) {
int h_counter = counter_ & 63;
int cycles_run_for = 0;

View File

@@ -10,6 +10,7 @@
#define Machines_Oric_Video_hpp
#include "../../Outputs/CRT/CRT.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
namespace Oric {
@@ -17,7 +18,7 @@ class VideoOutput {
public:
VideoOutput(uint8_t *memory);
std::shared_ptr<Outputs::CRT::CRT> get_crt();
void run_for_cycles(int number_of_cycles);
void run_for(const Cycles cycles);
void set_colour_rom(const std::vector<uint8_t> &rom);
void set_output_device(Outputs::CRT::OutputDevice output_device);

View File

@@ -11,14 +11,19 @@
using namespace Utility;
Typer::Typer(const char *string, int delay, int frequency, Delegate *delegate) :
counter_(-delay), frequency_(frequency), string_pointer_(0), delegate_(delegate), phase_(0) {
Typer::Typer(const char *string, HalfCycles delay, HalfCycles frequency, std::unique_ptr<CharacterMapper> character_mapper, Delegate *delegate) :
counter_(-delay),
frequency_(frequency),
string_pointer_(0),
delegate_(delegate),
phase_(0),
character_mapper_(std::move(character_mapper)) {
size_t string_size = strlen(string) + 3;
string_ = (char *)malloc(string_size);
snprintf(string_, strlen(string) + 3, "%c%s%c", Typer::BeginString, string, Typer::EndString);
snprintf(string_, string_size, "%c%s%c", Typer::BeginString, string, Typer::EndString);
}
void Typer::update(int duration) {
void Typer::run_for(const HalfCycles duration) {
if(string_) {
if(counter_ < 0 && counter_ + duration >= 0) {
if(!type_next_character()) {
@@ -36,10 +41,26 @@ void Typer::update(int duration) {
}
}
bool Typer::try_type_next_character() {
uint16_t *sequence = character_mapper_->sequence_for_character(string_[string_pointer_]);
if(!sequence || sequence[0] == CharacterMapper::NotMapped) {
return false;
}
if(!phase_) delegate_->clear_all_keys();
else {
delegate_->set_key_state(sequence[phase_ - 1], true);
return sequence[phase_] != CharacterMapper::EndSequence;
}
return true;
}
bool Typer::type_next_character() {
if(string_ == nullptr) return false;
if(delegate_->typer_set_next_character(this, string_[string_pointer_], phase_)) {
if(!try_type_next_character()) {
phase_ = 0;
if(!string_[string_pointer_]) {
free(string_);
@@ -59,21 +80,11 @@ Typer::~Typer() {
free(string_);
}
#pragma mark - Delegate
#pragma mark - Character mapper
bool Typer::Delegate::typer_set_next_character(Utility::Typer *typer, char character, int phase) {
uint16_t *sequence = sequence_for_character(typer, character);
if(!sequence) return true;
if(!phase) clear_all_keys();
else {
set_key_state(sequence[phase - 1], true);
return sequence[phase] == Typer::Delegate::EndSequence;
}
return false;
}
uint16_t *Typer::Delegate::sequence_for_character(Typer *typer, char character) {
return nullptr;
uint16_t *CharacterMapper::table_lookup_sequence_for_character(KeySequence *sequences, size_t length, char character) {
size_t ucharacter = (size_t)((unsigned char)character);
if(ucharacter > (length / sizeof(KeySequence))) return nullptr;
if(sequences[ucharacter][0] == NotMapped) return nullptr;
return sequences[ucharacter];
}

View File

@@ -11,53 +11,117 @@
#include <memory>
#include "KeyboardMachine.hpp"
#include "../ClockReceiver/ClockReceiver.hpp"
namespace Utility {
/*!
An interface that provides a mapping from logical characters to the sequence of keys
necessary to type that character on a given machine.
*/
class CharacterMapper {
public:
/// @returns The EndSequence-terminated sequence of keys that would cause @c character to be typed.
virtual uint16_t *sequence_for_character(char character) = 0;
/// Terminates a key sequence.
static const uint16_t EndSequence = 0xffff;
/*!
If returned as the first entry in a key sequence, indicates that the requested character
cannot be mapped.
*/
static const uint16_t NotMapped = 0xfffe;
protected:
typedef uint16_t KeySequence[16];
/*!
Provided in the base class as a convenience: given the lookup table of key sequences @c sequences,
with @c length entries, returns the sequence for character @c character if it exists; otherwise
returns @c nullptr.
*/
uint16_t *table_lookup_sequence_for_character(KeySequence *sequences, size_t length, char character);
};
/*!
Provides a stateful mechanism for typing a sequence of characters. Each character is mapped to a key sequence
by a character mapper. That key sequence is then replayed to a delegate.
Being given a delay and frequency at construction, the run_for interface can be used to produce time-based
typing. Alternatively, an owner may decline to use run_for and simply call type_next_character each time a
fresh key transition is ready to be consumed.
*/
class Typer {
public:
class Delegate: public KeyboardMachine::Machine {
public:
virtual bool typer_set_next_character(Typer *typer, char character, int phase);
virtual void typer_reset(Typer *typer) = 0;
virtual uint16_t *sequence_for_character(Typer *typer, char character);
const uint16_t EndSequence = 0xffff;
};
Typer(const char *string, int delay, int frequency, Delegate *delegate);
Typer(const char *string, HalfCycles delay, HalfCycles frequency, std::unique_ptr<CharacterMapper> character_mapper, Delegate *delegate);
~Typer();
void update(int duration);
void run_for(const HalfCycles duration);
bool type_next_character();
bool is_completed();
const char BeginString = 0x02; // i.e. ASCII start of text
const char EndString = 0x03; // i.e. ASCII end of text
private:
char *string_;
int frequency_;
int counter_;
int phase_;
Delegate *delegate_;
size_t string_pointer_;
HalfCycles frequency_;
HalfCycles counter_;
int phase_;
Delegate *delegate_;
std::unique_ptr<CharacterMapper> character_mapper_;
bool try_type_next_character();
};
/*!
Provides a default base class for type recipients: classes that want to attach a single typer at a time and
which may or may not want to nominate an initial delay and typing frequency.
*/
class TypeRecipient: public Typer::Delegate {
public:
void set_typer_for_string(const char *string) {
typer_.reset(new Typer(string, get_typer_delay(), get_typer_frequency(), this));
/// Attaches a typer to this class that will type @c string using @c character_mapper as a source.
void set_typer_for_string(const char *string, std::unique_ptr<CharacterMapper> character_mapper) {
typer_.reset(new Typer(string, get_typer_delay(), get_typer_frequency(), std::move(character_mapper), this));
}
/*!
Provided as a hook for subclasses to implement so that external callers can install a typer
without needing inside knowledge as to where the character mapper comes from.
*/
virtual void set_typer_for_string(const char *string) = 0;
/*!
Provided in order to conform to that part of the Typer::Delegate interface that goes above and
beyond KeyboardMachine::Machine; responds to the end of typing by clearing all keys.
*/
void typer_reset(Typer *typer) {
clear_all_keys();
typer_.reset();
// It's unsafe to deallocate typer right now, since it is the caller, but also it has a small
// memory footprint and it's desireable not to imply that the subclass need call it any more.
// So shuffle it off into a siding.
previous_typer_ = std::move(typer_);
typer_ = nullptr;
}
protected:
virtual int get_typer_delay() { return 0; }
virtual int get_typer_frequency() { return 0; }
virtual HalfCycles get_typer_delay() { return HalfCycles(0); }
virtual HalfCycles get_typer_frequency() { return HalfCycles(0); }
std::unique_ptr<Typer> typer_;
private:
std::unique_ptr<Typer> previous_typer_;
};
}

View File

@@ -0,0 +1,159 @@
//
// CharacterMapper.cpp
// Clock Signal
//
// Created by Thomas Harte on 03/08/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "CharacterMapper.hpp"
#include "ZX8081.hpp"
using namespace ZX8081;
CharacterMapper::CharacterMapper(bool is_zx81) : is_zx81_(is_zx81) {}
uint16_t *CharacterMapper::sequence_for_character(char character) {
#define KEYS(...) {__VA_ARGS__, EndSequence}
#define SHIFT(...) {KeyShift, __VA_ARGS__, EndSequence}
#define X {NotMapped}
static KeySequence zx81_key_sequences[] = {
/* NUL */ X, /* SOH */ X,
/* STX */ X, /* ETX */ X,
/* EOT */ X, /* ENQ */ X,
/* ACK */ X, /* BEL */ X,
/* BS */ SHIFT(Key0), /* HT */ X,
/* LF */ KEYS(KeyEnter), /* VT */ X,
/* FF */ X, /* CR */ X,
/* SO */ X, /* SI */ X,
/* DLE */ X, /* DC1 */ X,
/* DC2 */ X, /* DC3 */ X,
/* DC4 */ X, /* NAK */ X,
/* SYN */ X, /* ETB */ X,
/* CAN */ X, /* EM */ X,
/* SUB */ X, /* ESC */ X,
/* FS */ X, /* GS */ X,
/* RS */ X, /* US */ X,
/* space */ KEYS(KeySpace), /* ! */ X,
/* " */ SHIFT(KeyP), /* # */ X,
/* $ */ SHIFT(KeyU), /* % */ X,
/* & */ X, /* ' */ X,
/* ( */ SHIFT(KeyI), /* ) */ SHIFT(KeyO),
/* * */ SHIFT(KeyB), /* + */ SHIFT(KeyK),
/* , */ SHIFT(KeyDot), /* - */ SHIFT(KeyJ),
/* . */ KEYS(KeyDot), /* / */ SHIFT(KeyV),
/* 0 */ KEYS(Key0), /* 1 */ KEYS(Key1),
/* 2 */ KEYS(Key2), /* 3 */ KEYS(Key3),
/* 4 */ KEYS(Key4), /* 5 */ KEYS(Key5),
/* 6 */ KEYS(Key6), /* 7 */ KEYS(Key7),
/* 8 */ KEYS(Key8), /* 9 */ KEYS(Key9),
/* : */ SHIFT(KeyZ), /* ; */ SHIFT(KeyX),
/* < */ SHIFT(KeyN), /* = */ SHIFT(KeyL),
/* > */ SHIFT(KeyM), /* ? */ SHIFT(KeyC),
/* @ */ X, /* A */ KEYS(KeyA),
/* B */ KEYS(KeyB), /* C */ KEYS(KeyC),
/* D */ KEYS(KeyD), /* E */ KEYS(KeyE),
/* F */ KEYS(KeyF), /* G */ KEYS(KeyG),
/* H */ KEYS(KeyH), /* I */ KEYS(KeyI),
/* J */ KEYS(KeyJ), /* K */ KEYS(KeyK),
/* L */ KEYS(KeyL), /* M */ KEYS(KeyM),
/* N */ KEYS(KeyN), /* O */ KEYS(KeyO),
/* P */ KEYS(KeyP), /* Q */ KEYS(KeyQ),
/* R */ KEYS(KeyR), /* S */ KEYS(KeyS),
/* T */ KEYS(KeyT), /* U */ KEYS(KeyU),
/* V */ KEYS(KeyV), /* W */ KEYS(KeyW),
/* X */ KEYS(KeyX), /* Y */ KEYS(KeyY),
/* Z */ KEYS(KeyZ), /* [ */ X,
/* \ */ X, /* ] */ X,
/* ^ */ X, /* _ */ X,
/* ` */ X, /* a */ KEYS(KeyA),
/* b */ KEYS(KeyB), /* c */ KEYS(KeyC),
/* d */ KEYS(KeyD), /* e */ KEYS(KeyE),
/* f */ KEYS(KeyF), /* g */ KEYS(KeyG),
/* h */ KEYS(KeyH), /* i */ KEYS(KeyI),
/* j */ KEYS(KeyJ), /* k */ KEYS(KeyK),
/* l */ KEYS(KeyL), /* m */ KEYS(KeyM),
/* n */ KEYS(KeyN), /* o */ KEYS(KeyO),
/* p */ KEYS(KeyP), /* q */ KEYS(KeyQ),
/* r */ KEYS(KeyR), /* s */ KEYS(KeyS),
/* t */ KEYS(KeyT), /* u */ KEYS(KeyU),
/* v */ KEYS(KeyV), /* w */ KEYS(KeyW),
/* x */ KEYS(KeyX), /* y */ KEYS(KeyY),
/* z */ KEYS(KeyZ), /* { */ X,
/* | */ X, /* } */ X,
};
static KeySequence zx80_key_sequences[] = {
/* NUL */ X, /* SOH */ X,
/* STX */ X, /* ETX */ X,
/* EOT */ X, /* ENQ */ X,
/* ACK */ X, /* BEL */ X,
/* BS */ SHIFT(Key0), /* HT */ X,
/* LF */ KEYS(KeyEnter), /* VT */ X,
/* FF */ X, /* CR */ X,
/* SO */ X, /* SI */ X,
/* DLE */ X, /* DC1 */ X,
/* DC2 */ X, /* DC3 */ X,
/* DC4 */ X, /* NAK */ X,
/* SYN */ X, /* ETB */ X,
/* CAN */ X, /* EM */ X,
/* SUB */ X, /* ESC */ X,
/* FS */ X, /* GS */ X,
/* RS */ X, /* US */ X,
/* space */ KEYS(KeySpace), /* ! */ X,
/* " */ SHIFT(KeyY), /* # */ X,
/* $ */ SHIFT(KeyU), /* % */ X,
/* & */ X, /* ' */ X,
/* ( */ SHIFT(KeyI), /* ) */ SHIFT(KeyO),
/* * */ SHIFT(KeyP), /* + */ SHIFT(KeyK),
/* , */ SHIFT(KeyDot), /* - */ SHIFT(KeyJ),
/* . */ KEYS(KeyDot), /* / */ SHIFT(KeyV),
/* 0 */ KEYS(Key0), /* 1 */ KEYS(Key1),
/* 2 */ KEYS(Key2), /* 3 */ KEYS(Key3),
/* 4 */ KEYS(Key4), /* 5 */ KEYS(Key5),
/* 6 */ KEYS(Key6), /* 7 */ KEYS(Key7),
/* 8 */ KEYS(Key8), /* 9 */ KEYS(Key9),
/* : */ SHIFT(KeyZ), /* ; */ SHIFT(KeyX),
/* < */ SHIFT(KeyN), /* = */ SHIFT(KeyL),
/* > */ SHIFT(KeyM), /* ? */ SHIFT(KeyC),
/* @ */ X, /* A */ KEYS(KeyA),
/* B */ KEYS(KeyB), /* C */ KEYS(KeyC),
/* D */ KEYS(KeyD), /* E */ KEYS(KeyE),
/* F */ KEYS(KeyF), /* G */ KEYS(KeyG),
/* H */ KEYS(KeyH), /* I */ KEYS(KeyI),
/* J */ KEYS(KeyJ), /* K */ KEYS(KeyK),
/* L */ KEYS(KeyL), /* M */ KEYS(KeyM),
/* N */ KEYS(KeyN), /* O */ KEYS(KeyO),
/* P */ KEYS(KeyP), /* Q */ KEYS(KeyQ),
/* R */ KEYS(KeyR), /* S */ KEYS(KeyS),
/* T */ KEYS(KeyT), /* U */ KEYS(KeyU),
/* V */ KEYS(KeyV), /* W */ KEYS(KeyW),
/* X */ KEYS(KeyX), /* Y */ KEYS(KeyY),
/* Z */ KEYS(KeyZ), /* [ */ X,
/* \ */ X, /* ] */ X,
/* ^ */ X, /* _ */ X,
/* ` */ X, /* a */ KEYS(KeyA),
/* b */ KEYS(KeyB), /* c */ KEYS(KeyC),
/* d */ KEYS(KeyD), /* e */ KEYS(KeyE),
/* f */ KEYS(KeyF), /* g */ KEYS(KeyG),
/* h */ KEYS(KeyH), /* i */ KEYS(KeyI),
/* j */ KEYS(KeyJ), /* k */ KEYS(KeyK),
/* l */ KEYS(KeyL), /* m */ KEYS(KeyM),
/* n */ KEYS(KeyN), /* o */ KEYS(KeyO),
/* p */ KEYS(KeyP), /* q */ KEYS(KeyQ),
/* r */ KEYS(KeyR), /* s */ KEYS(KeyS),
/* t */ KEYS(KeyT), /* u */ KEYS(KeyU),
/* v */ KEYS(KeyV), /* w */ KEYS(KeyW),
/* x */ KEYS(KeyX), /* y */ KEYS(KeyY),
/* z */ KEYS(KeyZ), /* { */ X,
/* | */ X, /* } */ X,
};
#undef KEYS
#undef SHIFT
#undef X
if(is_zx81_)
return table_lookup_sequence_for_character(zx81_key_sequences, sizeof(zx81_key_sequences), character);
else
return table_lookup_sequence_for_character(zx80_key_sequences, sizeof(zx80_key_sequences), character);
}

View File

@@ -0,0 +1,27 @@
//
// CharacterMapper.hpp
// Clock Signal
//
// Created by Thomas Harte on 03/08/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef Machines_ZX8081_CharacterMapper_hpp
#define Machines_ZX8081_CharacterMapper_hpp
#include "../Typer.hpp"
namespace ZX8081 {
class CharacterMapper: public ::Utility::CharacterMapper {
public:
CharacterMapper(bool is_zx81);
uint16_t *sequence_for_character(char character);
private:
bool is_zx81_;
};
}
#endif /* CharacterMapper_hpp */

109
Machines/ZX8081/Video.cpp Normal file
View File

@@ -0,0 +1,109 @@
//
// Video.cpp
// Clock Signal
//
// Created by Thomas Harte on 06/06/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "Video.hpp"
using namespace ZX8081;
Video::Video() :
crt_(new Outputs::CRT::CRT(207 * 2, 1, Outputs::CRT::DisplayType::PAL50, 1)),
line_data_(nullptr),
line_data_pointer_(nullptr),
cycles_since_update_(0),
sync_(false) {
// Set a composite sampling function that assumes 8bpp input grayscale.
// TODO: lessen this to 1bpp.
crt_->set_composite_sampling_function(
"float composite_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate, float phase, float amplitude)"
"{"
"return float(texture(texID, coordinate).r) / 255.0;"
"}");
// Show only the centre 80% of the TV frame.
crt_->set_visible_area(Outputs::CRT::Rect(0.1f, 0.1f, 0.8f, 0.8f));
}
void Video::run_for(const HalfCycles half_cycles) {
// Just keep a running total of the amount of time that remains owed to the CRT.
cycles_since_update_ += (unsigned int)half_cycles.as_int();
}
void Video::flush() {
flush(sync_);
}
void Video::flush(bool next_sync) {
if(sync_) {
// If in sync, that takes priority. Output the proper amount of sync.
crt_->output_sync(cycles_since_update_);
} else {
// If not presently in sync, then...
if(line_data_) {
// If there is output data queued, output it either if it's being interrupted by
// sync, or if we're past its end anyway. Otherwise let it be.
unsigned int data_length = (unsigned int)(line_data_pointer_ - line_data_);
if(data_length < cycles_since_update_ || next_sync) {
unsigned int output_length = std::min(data_length, cycles_since_update_);
crt_->output_data(output_length, 1);
line_data_pointer_ = line_data_ = nullptr;
cycles_since_update_ -= output_length;
} else return;
}
// Any pending pixels being dealt with, pad with the white level.
uint8_t *colour_pointer = (uint8_t *)crt_->allocate_write_area(1);
if(colour_pointer) *colour_pointer = 0xff;
crt_->output_level(cycles_since_update_);
}
cycles_since_update_ = 0;
}
void Video::set_sync(bool sync) {
// Do nothing if sync hasn't changed.
if(sync_ == sync) return;
// Complete whatever was being drawn, and update sync.
flush(sync);
sync_ = sync;
}
void Video::output_byte(uint8_t byte) {
// Complete whatever was going on.
if(sync_) return;
flush();
// Grab a buffer if one isn't already available.
if(!line_data_) {
line_data_pointer_ = line_data_ = crt_->allocate_write_area(320);
}
// If a buffer was obtained, serialise the new pixels.
if(line_data_) {
// If the buffer is full, output it now and obtain a new one
if(line_data_pointer_ - line_data_ == 320) {
crt_->output_data(320, 1);
cycles_since_update_ -= 320;
line_data_pointer_ = line_data_ = crt_->allocate_write_area(320);
if(!line_data_) return;
}
uint8_t mask = 0x80;
for(int c = 0; c < 8; c++) {
line_data_pointer_[c] = (byte & mask) ? 0xff : 0x00;
mask >>= 1;
}
line_data_pointer_ += 8;
}
}
std::shared_ptr<Outputs::CRT::CRT> Video::get_crt() {
return crt_;
}

55
Machines/ZX8081/Video.hpp Normal file
View File

@@ -0,0 +1,55 @@
//
// Video.hpp
// Clock Signal
//
// Created by Thomas Harte on 06/06/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef Machines_ZX8081_Video_hpp
#define Machines_ZX8081_Video_hpp
#include "../../Outputs/CRT/CRT.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
namespace ZX8081 {
/*!
Packages a ZX80/81-style video feed into a CRT-compatible waveform.
While sync is active, this feed will output the sync level.
While sync is inactive, this feed will output the white level unless it is supplied
with a byte to output. When a byte is supplied for output, it will be interpreted as
a 1-bit graphic and output over the next 4 cycles, picking between the white level
and the black level.
*/
class Video {
public:
/// Constructs an instance of the video feed; a CRT is also created.
Video();
/// @returns The CRT this video feed is feeding.
std::shared_ptr<Outputs::CRT::CRT> get_crt();
/// Advances time by @c cycles.
void run_for(const HalfCycles);
/// Forces output to catch up to the current output position.
void flush();
/// Sets the current sync output.
void set_sync(bool sync);
/// Causes @c byte to be serialised into pixels and output over the next four cycles.
void output_byte(uint8_t byte);
private:
bool sync_;
uint8_t *line_data_, *line_data_pointer_;
unsigned int cycles_since_update_;
std::shared_ptr<Outputs::CRT::CRT> crt_;
void flush(bool next_sync);
};
}
#endif /* Video_hpp */

400
Machines/ZX8081/ZX8081.cpp Normal file
View File

@@ -0,0 +1,400 @@
//
// ZX8081.cpp
// Clock Signal
//
// Created by Thomas Harte on 04/06/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "ZX8081.hpp"
#include "../../Processors/Z80/Z80.hpp"
#include "../../Storage/Tape/Tape.hpp"
#include "../../Storage/Tape/Parsers/ZX8081.hpp"
#include "../../ClockReceiver/ForceInline.hpp"
#include "../MemoryFuzzer.hpp"
#include "../Typer.hpp"
#include "CharacterMapper.hpp"
#include "Video.hpp"
#include <memory>
namespace {
// The clock rate is 3.25Mhz.
const unsigned int ZX8081ClockRate = 3250000;
}
namespace ZX8081 {
template<bool is_zx81> class ConcreteMachine:
public Utility::TypeRecipient,
public CPU::Z80::BusHandler,
public Machine {
public:
ConcreteMachine() :
z80_(*this),
vsync_(false),
hsync_(false),
nmi_is_enabled_(false),
tape_player_(ZX8081ClockRate),
use_fast_tape_hack_(false),
tape_advance_delay_(0),
has_latched_video_byte_(false) {
set_clock_rate(ZX8081ClockRate);
clear_all_keys();
}
forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
HalfCycles previous_counter = horizontal_counter_;
horizontal_counter_ += cycle.length;
if(previous_counter < vsync_start_ && horizontal_counter_ >= vsync_start_) {
video_->run_for(vsync_start_ - previous_counter);
set_hsync(true);
line_counter_ = (line_counter_ + 1) & 7;
if(nmi_is_enabled_) {
z80_.set_non_maskable_interrupt_line(true);
}
video_->run_for(horizontal_counter_ - vsync_start_);
} else if(previous_counter < vsync_end_ && horizontal_counter_ >= vsync_end_) {
video_->run_for(vsync_end_ - previous_counter);
set_hsync(false);
if(nmi_is_enabled_) {
z80_.set_non_maskable_interrupt_line(false);
z80_.set_wait_line(false);
}
video_->run_for(horizontal_counter_ - vsync_end_);
} else {
video_->run_for(cycle.length);
}
if(is_zx81_) horizontal_counter_ %= HalfCycles(Cycles(207));
if(!tape_advance_delay_) {
tape_player_.run_for(cycle.length);
} else {
tape_advance_delay_ = std::max(tape_advance_delay_ - cycle.length, HalfCycles(0));
}
if(nmi_is_enabled_ && !z80_.get_halt_line() && z80_.get_non_maskable_interrupt_line()) {
z80_.set_wait_line(true);
}
if(!cycle.is_terminal()) {
return Cycles(0);
}
uint16_t address = cycle.address ? *cycle.address : 0;
bool is_opcode_read = false;
switch(cycle.operation) {
case CPU::Z80::PartialMachineCycle::Output:
if(!(address & 2)) nmi_is_enabled_ = false;
if(!(address & 1)) nmi_is_enabled_ = is_zx81_;
if(!nmi_is_enabled_) {
// Line counter reset is held low while vsync is active; simulate that lazily by performing
// an instant reset upon the transition from active to inactive.
if(vsync_) line_counter_ = 0;
set_vsync(false);
}
break;
case CPU::Z80::PartialMachineCycle::Input: {
uint8_t value = 0xff;
if(!(address&1)) {
if(!nmi_is_enabled_) set_vsync(true);
uint16_t mask = 0x100;
for(int c = 0; c < 8; c++) {
if(!(address & mask)) value &= key_states_[c];
mask <<= 1;
}
value &= ~(tape_player_.get_input() ? 0x00 : 0x80);
}
*cycle.value = value;
} break;
case CPU::Z80::PartialMachineCycle::Interrupt:
// resetting event is M1 and IOREQ both simultaneously having leading edges;
// that happens 2 cycles before the end of INTACK. So the timer was reset and
// now has advanced twice.
horizontal_counter_ = HalfCycles(2);
*cycle.value = 0xff;
break;
case CPU::Z80::PartialMachineCycle::Refresh:
// The ZX80 and 81 signal an interrupt while refresh is active and bit 6 of the refresh
// address is low. The Z80 signals a refresh, providing the refresh address during the
// final two cycles of an opcode fetch. Therefore communicate a transient signalling
// of the IRQ line if necessary.
if(!(address & 0x40)) {
z80_.set_interrupt_line(true, Cycles(-2));
z80_.set_interrupt_line(false);
}
if(has_latched_video_byte_) {
size_t char_address = (size_t)((address & 0xfe00) | ((latched_video_byte_ & 0x3f) << 3) | line_counter_);
uint8_t mask = (latched_video_byte_ & 0x80) ? 0x00 : 0xff;
if(char_address < ram_base_) {
latched_video_byte_ = rom_[char_address & rom_mask_] ^ mask;
} else {
latched_video_byte_ = ram_[address & ram_mask_] ^ mask;
}
video_->output_byte(latched_video_byte_);
has_latched_video_byte_ = false;
}
break;
case CPU::Z80::PartialMachineCycle::ReadOpcode:
// Check for use of the fast tape hack.
if(use_fast_tape_hack_ && address == tape_trap_address_ && tape_player_.has_tape()) {
uint64_t prior_offset = tape_player_.get_tape()->get_offset();
int next_byte = parser_.get_next_byte(tape_player_.get_tape());
if(next_byte != -1) {
uint16_t hl = z80_.get_value_of_register(CPU::Z80::Register::HL);
ram_[hl & ram_mask_] = (uint8_t)next_byte;
*cycle.value = 0x00;
z80_.set_value_of_register(CPU::Z80::Register::ProgramCounter, tape_return_address_ - 1);
// Assume that having read one byte quickly, we're probably going to be asked to read
// another shortly. Therefore, temporarily disable the tape motor for 1000 cycles in order
// to avoid fighting with real time. This is a stop-gap fix.
tape_advance_delay_ = 1000;
return 0;
} else {
tape_player_.get_tape()->set_offset(prior_offset);
}
}
// Check for automatic tape control.
if(use_automatic_tape_motor_control_) {
tape_player_.set_motor_control((address >= automatic_tape_motor_start_address_) && (address < automatic_tape_motor_end_address_));
}
is_opcode_read = true;
case CPU::Z80::PartialMachineCycle::Read:
if(address < ram_base_) {
*cycle.value = rom_[address & rom_mask_];
} else {
uint8_t value = ram_[address & ram_mask_];
// If this is an M1 cycle reading from above the 32kb mark and HALT is not
// currently active, latch for video output and return a NOP. Otherwise,
// just return the value as read.
if(is_opcode_read && address&0x8000 && !(value & 0x40) && !z80_.get_halt_line()) {
latched_video_byte_ = value;
has_latched_video_byte_ = true;
*cycle.value = 0;
} else *cycle.value = value;
}
break;
case CPU::Z80::PartialMachineCycle::Write:
if(address >= ram_base_) {
ram_[address & ram_mask_] = *cycle.value;
}
break;
default: break;
}
if(typer_) typer_->run_for(cycle.length);
return HalfCycles(0);
}
forceinline void flush() {
video_->flush();
}
void setup_output(float aspect_ratio) override final {
video_.reset(new Video);
}
void close_output() override final {
video_.reset();
}
std::shared_ptr<Outputs::CRT::CRT> get_crt() override final {
return video_->get_crt();
}
std::shared_ptr<Outputs::Speaker> get_speaker() override final {
return nullptr;
}
void run_for(const Cycles cycles) override final {
z80_.run_for(cycles);
}
void configure_as_target(const StaticAnalyser::Target &target) override final {
is_zx81_ = target.zx8081.isZX81;
if(is_zx81_) {
rom_ = zx81_rom_;
tape_trap_address_ = 0x37c;
tape_return_address_ = 0x380;
vsync_start_ = HalfCycles(32);
vsync_end_ = HalfCycles(64);
automatic_tape_motor_start_address_ = 0x0340;
automatic_tape_motor_end_address_ = 0x03c3;
} else {
rom_ = zx80_rom_;
tape_trap_address_ = 0x220;
tape_return_address_ = 0x248;
vsync_start_ = HalfCycles(26);
vsync_end_ = HalfCycles(66);
automatic_tape_motor_start_address_ = 0x0206;
automatic_tape_motor_end_address_ = 0x024d;
}
rom_mask_ = (uint16_t)(rom_.size() - 1);
switch(target.zx8081.memory_model) {
case StaticAnalyser::ZX8081MemoryModel::Unexpanded:
ram_.resize(1024);
ram_base_ = 16384;
ram_mask_ = 1023;
break;
case StaticAnalyser::ZX8081MemoryModel::SixteenKB:
ram_.resize(16384);
ram_base_ = 16384;
ram_mask_ = 16383;
break;
case StaticAnalyser::ZX8081MemoryModel::SixtyFourKB:
ram_.resize(65536);
ram_base_ = 8192;
ram_mask_ = 65535;
break;
}
Memory::Fuzz(ram_);
if(target.loadingCommand.length()) {
set_typer_for_string(target.loadingCommand.c_str());
}
insert_media(target.media);
}
bool insert_media(const StaticAnalyser::Media &media) override final {
if(!media.tapes.empty()) {
tape_player_.set_tape(media.tapes.front());
}
return !media.tapes.empty();
}
void set_typer_for_string(const char *string) override final {
std::unique_ptr<CharacterMapper> mapper(new CharacterMapper(is_zx81_));
Utility::TypeRecipient::set_typer_for_string(string, std::move(mapper));
}
void set_rom(ROMType type, std::vector<uint8_t> data) override final {
switch(type) {
case ZX80: zx80_rom_ = data; break;
case ZX81: zx81_rom_ = data; break;
}
}
#pragma mark - Keyboard
void set_key_state(uint16_t key, bool isPressed) override final {
if(isPressed)
key_states_[key >> 8] &= (uint8_t)(~key);
else
key_states_[key >> 8] |= (uint8_t)key;
}
void clear_all_keys() override final {
memset(key_states_, 0xff, 8);
}
#pragma mark - Tape control
void set_use_fast_tape_hack(bool activate) override final {
use_fast_tape_hack_ = activate;
}
void set_use_automatic_tape_motor_control(bool enabled) override final {
use_automatic_tape_motor_control_ = enabled;
if(!enabled) {
tape_player_.set_motor_control(false);
}
}
void set_tape_is_playing(bool is_playing) override final {
tape_player_.set_motor_control(is_playing);
}
#pragma mark - Typer timing
HalfCycles get_typer_delay() override final { return Cycles(7000000); }
HalfCycles get_typer_frequency() override final { return Cycles(390000); }
private:
CPU::Z80::Processor<ConcreteMachine, false, is_zx81> z80_;
std::shared_ptr<Video> video_;
std::vector<uint8_t> zx81_rom_, zx80_rom_;
uint16_t tape_trap_address_, tape_return_address_;
uint16_t automatic_tape_motor_start_address_, automatic_tape_motor_end_address_;
std::vector<uint8_t> ram_;
uint16_t ram_mask_, ram_base_;
std::vector<uint8_t> rom_;
uint16_t rom_mask_;
bool vsync_, hsync_;
int line_counter_;
uint8_t key_states_[8];
HalfClockReceiver<Storage::Tape::BinaryTapePlayer> tape_player_;
Storage::Tape::ZX8081::Parser parser_;
bool is_zx81_;
bool nmi_is_enabled_;
HalfCycles vsync_start_, vsync_end_;
HalfCycles horizontal_counter_;
uint8_t latched_video_byte_;
bool has_latched_video_byte_;
bool use_fast_tape_hack_;
bool use_automatic_tape_motor_control_;
HalfCycles tape_advance_delay_;
#pragma mark - Video
inline void set_vsync(bool sync) {
vsync_ = sync;
update_sync();
}
inline void set_hsync(bool sync) {
hsync_ = sync;
update_sync();
}
inline void update_sync() {
video_->set_sync(vsync_ || hsync_);
}
};
}
using namespace ZX8081;
// See header; constructs and returns an instance of the ZX80 or 81.
Machine *Machine::ZX8081(const StaticAnalyser::Target &target_hint) {
// Instantiate the correct type of machine.
if(target_hint.zx8081.isZX81)
return new ZX8081::ConcreteMachine<true>();
else
return new ZX8081::ConcreteMachine<false>();
}
Machine::~Machine() {}

View File

@@ -0,0 +1,53 @@
//
// ZX8081.hpp
// Clock Signal
//
// Created by Thomas Harte on 04/06/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef ZX8081_hpp
#define ZX8081_hpp
#include "../ConfigurationTarget.hpp"
#include "../CRTMachine.hpp"
#include "../KeyboardMachine.hpp"
#include <cstdint>
#include <vector>
namespace ZX8081 {
enum ROMType: uint8_t {
ZX80, ZX81
};
enum Key: uint16_t {
KeyShift = 0x0000 | 0x01, KeyZ = 0x0000 | 0x02, KeyX = 0x0000 | 0x04, KeyC = 0x0000 | 0x08, KeyV = 0x0000 | 0x10,
KeyA = 0x0100 | 0x01, KeyS = 0x0100 | 0x02, KeyD = 0x0100 | 0x04, KeyF = 0x0100 | 0x08, KeyG = 0x0100 | 0x10,
KeyQ = 0x0200 | 0x01, KeyW = 0x0200 | 0x02, KeyE = 0x0200 | 0x04, KeyR = 0x0200 | 0x08, KeyT = 0x0200 | 0x10,
Key1 = 0x0300 | 0x01, Key2 = 0x0300 | 0x02, Key3 = 0x0300 | 0x04, Key4 = 0x0300 | 0x08, Key5 = 0x0300 | 0x10,
Key0 = 0x0400 | 0x01, Key9 = 0x0400 | 0x02, Key8 = 0x0400 | 0x04, Key7 = 0x0400 | 0x08, Key6 = 0x0400 | 0x10,
KeyP = 0x0500 | 0x01, KeyO = 0x0500 | 0x02, KeyI = 0x0500 | 0x04, KeyU = 0x0500 | 0x08, KeyY = 0x0500 | 0x10,
KeyEnter = 0x0600 | 0x01, KeyL = 0x0600 | 0x02, KeyK = 0x0600 | 0x04, KeyJ = 0x0600 | 0x08, KeyH = 0x0600 | 0x10,
KeySpace = 0x0700 | 0x01, KeyDot = 0x0700 | 0x02, KeyM = 0x0700 | 0x04, KeyN = 0x0700 | 0x08, KeyB = 0x0700 | 0x10,
};
class Machine:
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public KeyboardMachine::Machine {
public:
static Machine *ZX8081(const StaticAnalyser::Target &target_hint);
virtual ~Machine();
virtual void set_rom(ROMType type, std::vector<uint8_t> data) = 0;
virtual void set_use_fast_tape_hack(bool activate) = 0;
virtual void set_tape_is_playing(bool is_playing) = 0;
virtual void set_use_automatic_tape_motor_control(bool enabled) = 0;
};
}
#endif /* ZX8081_hpp */

View File

@@ -13,8 +13,13 @@
namespace NumberTheory {
/*! Provides a class capable of accumulating a CRC16 from source data. */
class CRC16 {
public:
/*!
Instantiates a CRC16 that will compute the CRC16 specified by the supplied
@c polynomial and @c reset_value.
*/
CRC16(uint16_t polynomial, uint16_t reset_value) :
reset_value_(reset_value), value_(reset_value) {
for(int c = 0; c < 256; c++) {
@@ -27,11 +32,18 @@ class CRC16 {
}
}
/// Resets the CRC to the reset value.
inline void reset() { value_ = reset_value_; }
/// Updates the CRC to include @c byte.
inline void add(uint8_t byte) {
value_ = (uint16_t)((value_ << 8) ^ xor_table[(value_ >> 8) ^ byte]);
}
/// @returns The current value of the CRC.
inline uint16_t get_value() const { return value_; }
/// Sets the current value of the CRC.
inline void set_value(uint16_t value) { value_ = value; }
private:

View File

@@ -9,15 +9,19 @@
#ifndef Factors_hpp
#define Factors_hpp
#include <numeric>
#include <utility>
namespace NumberTheory {
/*!
@returns The greatest common divisor of @c a and @c b as computed by Euclid's algorithm.
@returns The greatest common divisor of @c a and @c b.
*/
template<class T> T greatest_common_divisor(T a, T b) {
#if __cplusplus > 201402L
return std::gcd(a, b);
#else
if(a < b) {
T swap = b;
b = a;
a = swap;
std::swap(a, b);
}
while(1) {
@@ -28,11 +32,12 @@ namespace NumberTheory {
a = b;
b = remainder;
}
#endif
}
/*!
@returns The least common multiple of @c a and @c b computed indirectly via Euclid's greatest
common divisor algorithm.
@returns The least common multiple of @c a and @c b computed indirectly via the greatest
common divisor.
*/
template<class T> T least_common_multiple(T a, T b) {
if(a == b) return a;

File diff suppressed because it is too large Load Diff

View File

@@ -38,6 +38,11 @@
BlueprintName = "Clock SignalTests"
ReferencedContainer = "container:Clock Signal.xcodeproj">
</BuildableReference>
<SkippedTests>
<Test
Identifier = "ZexallTests">
</Test>
</SkippedTests>
</TestableReference>
<TestableReference
skipped = "YES">

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="12121" systemVersion="16F73" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="12121"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="MachineDocument" customModule="Clock_Signal" customModuleProvider="target">
<connections>
<outlet property="optionsPanel" destination="ZW7-Bw-4RP" id="JpE-wG-zRR"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="ZW7-Bw-4RP" customClass="MachinePanel" customModule="Clock_Signal" customModuleProvider="target">
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="83" y="102" width="200" height="54"/>
<rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/>
<view key="contentView" id="tpZ-0B-QQu">
<rect key="frame" x="0.0" y="0.0" width="200" height="54"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<button translatesAutoresizingMaskIntoConstraints="NO" id="e1J-pw-zGw">
<rect key="frame" x="18" y="18" width="164" height="18"/>
<buttonCell key="cell" type="check" title="Load Quickly" bezelStyle="regularSquare" imagePosition="left" alignment="left" state="on" inset="2" id="tD6-UB-ESB">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="setFastLoading:" target="ZW7-Bw-4RP" id="JmG-Ks-jSh"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="e1J-pw-zGw" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="20" id="HSD-3d-Bl7"/>
<constraint firstAttribute="trailing" secondItem="e1J-pw-zGw" secondAttribute="trailing" constant="20" id="Q9M-FH-92N"/>
<constraint firstAttribute="bottom" secondItem="e1J-pw-zGw" secondAttribute="bottom" constant="20" id="sdh-oJ-ZIQ"/>
<constraint firstItem="e1J-pw-zGw" firstAttribute="top" secondItem="tpZ-0B-QQu" secondAttribute="top" constant="20" id="ul9-lf-Y3u"/>
</constraints>
</view>
<connections>
<outlet property="fastLoadingButton" destination="e1J-pw-zGw" id="jj7-OZ-mOH"/>
</connections>
<point key="canvasLocation" x="175" y="30"/>
</window>
</objects>
</document>

View File

@@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="12121" systemVersion="16F73" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="12121"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="MachineDocument" customModule="Clock_Signal" customModuleProvider="target">
<connections>
<outlet property="optionsPanel" destination="ota-g7-hOL" id="zeO-di-9i3"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="ota-g7-hOL" customClass="ZX8081OptionsPanel" customModule="Clock_Signal" customModuleProvider="target">
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="83" y="102" width="261" height="100"/>
<rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/>
<view key="contentView" id="7Pv-WL-2Rq">
<rect key="frame" x="0.0" y="0.0" width="261" height="100"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button translatesAutoresizingMaskIntoConstraints="NO" id="sBT-cU-h7s">
<rect key="frame" x="18" y="64" width="225" height="18"/>
<buttonCell key="cell" type="check" title="Load Tapes Quickly" bezelStyle="regularSquare" imagePosition="left" alignment="left" state="on" inset="2" id="w0l-ha-esm">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="setFastLoading:" target="ota-g7-hOL" id="me0-h2-Ga5"/>
</connections>
</button>
<button translatesAutoresizingMaskIntoConstraints="NO" id="qSb-72-6Os">
<rect key="frame" x="18" y="44" width="225" height="18"/>
<buttonCell key="cell" type="check" title="Control Tape Motor Automatically" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="CzC-YT-lgA">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="setAutomaticTapeMotorConrol:" target="ota-g7-hOL" id="bpF-1P-tga"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="tkN-gI-RmT">
<rect key="frame" x="20" y="19" width="221" height="19"/>
<buttonCell key="cell" type="roundRect" title="Play Tape" bezelStyle="roundedRect" alignment="center" enabled="NO" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="cTq-f9-Gzx">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="cellTitle"/>
</buttonCell>
<connections>
<action selector="playOrPauseTape:" target="ota-g7-hOL" id="O0K-pL-nOr"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="qSb-72-6Os" firstAttribute="leading" secondItem="7Pv-WL-2Rq" secondAttribute="leading" constant="20" id="05p-Jn-ueX"/>
<constraint firstAttribute="trailing" secondItem="sBT-cU-h7s" secondAttribute="trailing" constant="20" id="79b-2A-2c7"/>
<constraint firstItem="sBT-cU-h7s" firstAttribute="top" secondItem="7Pv-WL-2Rq" secondAttribute="top" constant="20" id="E5m-wo-X92"/>
<constraint firstItem="qSb-72-6Os" firstAttribute="top" secondItem="sBT-cU-h7s" secondAttribute="bottom" constant="6" id="WxD-kP-vwf"/>
<constraint firstAttribute="bottom" secondItem="tkN-gI-RmT" secondAttribute="bottom" constant="20" id="Xnu-On-nOA"/>
<constraint firstItem="tkN-gI-RmT" firstAttribute="leading" secondItem="7Pv-WL-2Rq" secondAttribute="leading" constant="20" id="fHf-K0-PsU"/>
<constraint firstItem="tkN-gI-RmT" firstAttribute="top" secondItem="qSb-72-6Os" secondAttribute="bottom" constant="8" id="gLh-vE-Cqk"/>
<constraint firstAttribute="trailing" secondItem="qSb-72-6Os" secondAttribute="trailing" constant="20" id="mQz-p8-aYf"/>
<constraint firstItem="sBT-cU-h7s" firstAttribute="leading" secondItem="7Pv-WL-2Rq" secondAttribute="leading" constant="20" id="nDy-Xc-Ug9"/>
<constraint firstAttribute="trailing" secondItem="tkN-gI-RmT" secondAttribute="trailing" constant="20" id="vgD-A3-m6T"/>
</constraints>
</view>
<connections>
<outlet property="automaticTapeMotorControlButton" destination="qSb-72-6Os" id="bB6-FP-TKM"/>
<outlet property="fastLoadingButton" destination="sBT-cU-h7s" id="uWa-EB-mbd"/>
<outlet property="playOrPauseTapeButton" destination="tkN-gI-RmT" id="UnJ-nb-3mv"/>
</connections>
<point key="canvasLocation" x="28.5" y="15"/>
</window>
</objects>
</document>

View File

@@ -10,6 +10,7 @@
#import "CSElectron.h"
#import "CSOric.h"
#import "CSVic20.h"
#import "CSZX8081.h"
#import "CSStaticAnalyser.h"

View File

@@ -164,6 +164,13 @@ class MachineDocument:
}
}
final func openGLView(_ view: CSOpenGLView, didReceiveFileAt URL: URL) {
let mediaSet = CSMediaSet(fileAt: URL)
if let mediaSet = mediaSet {
mediaSet.apply(to: self.machine)
}
}
// MARK: NSDocument overrides
override func data(ofType typeName: String) throws -> Data {
throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil)

View File

@@ -0,0 +1,49 @@
//
// ZX8081OptionsPanel.swift
// Clock Signal
//
// Created by Thomas Harte on 08/07/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
class ZX8081OptionsPanel: MachinePanel {
var zx8081: CSZX8081! {
get {
return self.machine as! CSZX8081
}
}
@IBOutlet var automaticTapeMotorControlButton: NSButton!
var automaticTapeMotorControlDefaultsKey: String {
get { return prefixedUserDefaultsKey("automaticTapeMotorControl") }
}
@IBAction func setAutomaticTapeMotorConrol(_ sender: NSButton!) {
let isEnabled = sender.state == NSOnState
UserDefaults.standard.set(isEnabled, forKey: self.automaticTapeMotorControlDefaultsKey)
self.playOrPauseTapeButton.isEnabled = !isEnabled
self.zx8081.useAutomaticTapeMotorControl = isEnabled
}
@IBOutlet var playOrPauseTapeButton: NSButton!
@IBAction func playOrPauseTape(_ sender: NSButton!) {
self.zx8081.tapeIsPlaying = !self.zx8081.tapeIsPlaying
self.playOrPauseTapeButton.title = self.zx8081.tapeIsPlaying
? NSLocalizedString("Stop Tape", comment: "Text for a button that will stop tape playback")
: NSLocalizedString("Play Tape", comment: "Text for a button that will start tape playback")
}
// MARK: option restoration
override func establishStoredOptions() {
super.establishStoredOptions()
let standardUserDefaults = UserDefaults.standard
standardUserDefaults.register(defaults: [
self.automaticTapeMotorControlDefaultsKey: true
])
let automaticTapeMotorControlIsEnabled = standardUserDefaults.bool(forKey: self.automaticTapeMotorControlDefaultsKey)
self.automaticTapeMotorControlButton.state = automaticTapeMotorControlIsEnabled ? NSOnState : NSOffState
self.playOrPauseTapeButton.isEnabled = !automaticTapeMotorControlIsEnabled
self.zx8081.useAutomaticTapeMotorControl = automaticTapeMotorControlIsEnabled
}
}

View File

@@ -104,7 +104,7 @@
<key>CFBundleTypeName</key>
<string>Commodore Disk</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<string>Editor</string>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<key>NSDocumentClass</key>
@@ -120,7 +120,7 @@
<key>CFBundleTypeName</key>
<string>Commodore 1540/1 Disk</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<string>Editor</string>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<key>NSDocumentClass</key>
@@ -140,7 +140,9 @@
<key>CFBundleTypeName</key>
<string>Electron/BBC Disk Image</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<string>Editor</string>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>
@@ -154,7 +156,108 @@
<key>CFBundleTypeName</key>
<string>Disk Image</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>o</string>
<string>80</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>cassette</string>
<key>CFBundleTypeName</key>
<string>ZX80 Tape Image</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>p</string>
<string>81</string>
<string>p81</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>cassette</string>
<key>CFBundleTypeName</key>
<string>ZX81 Tape Image</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>csw</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>cassette</string>
<key>CFBundleTypeName</key>
<string>Tape Image</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>tzx</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>cassette</string>
<key>CFBundleTypeName</key>
<string>Tape Image</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>cdt</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>cassette</string>
<key>CFBundleTypeName</key>
<string>Amstrad CPC Tape Image</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>hfe</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>floppy35</string>
<key>CFBundleTypeName</key>
<string>HxC Disk Image</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>

View File

@@ -11,7 +11,5 @@
@interface CSMachine (Subclassing)
- (CRTMachine::Machine * const)machine;
- (void)setupOutputWithAspectRatio:(float)aspectRatio;
@end

View File

@@ -12,6 +12,7 @@
@interface CSMachine(Target)
- (void)applyTarget:(StaticAnalyser::Target)target;
- (void)applyTarget:(const StaticAnalyser::Target &)target;
- (void)applyMedia:(const StaticAnalyser::Media &)media;
@end

View File

@@ -18,6 +18,15 @@
@interface CSMachine : NSObject
- (instancetype)init NS_UNAVAILABLE;
/*!
Initialises an instance of CSMachine.
@param machine The pointer to an instance of @c CRTMachine::Machine* . C++ type is omitted because
this header is visible to Swift, and the designated initialiser cannot be placed into a category.
*/
- (instancetype)initWithMachine:(void *)machine NS_DESIGNATED_INITIALIZER;
- (void)runForNumberOfCycles:(int)numberOfCycles;
- (float)idealSamplingRateFromRange:(NSRange)range;

View File

@@ -21,8 +21,8 @@
struct SpeakerDelegate: public Outputs::Speaker::Delegate {
__weak CSMachine *machine;
void speaker_did_complete_samples(Outputs::Speaker *speaker, const int16_t *buffer, int buffer_size) {
[machine speaker:speaker didCompleteSamples:buffer length:buffer_size];
void speaker_did_complete_samples(Outputs::Speaker *speaker, const std::vector<int16_t> &buffer) {
[machine speaker:speaker didCompleteSamples:buffer.data() length:(int)buffer.size()];
}
};
@@ -39,15 +39,17 @@ struct MachineDelegate: CRTMachine::Machine::Delegate {
@implementation CSMachine {
SpeakerDelegate _speakerDelegate;
MachineDelegate _machineDelegate;
CRTMachine::Machine *_machine;
}
- (instancetype)init {
- (instancetype)initWithMachine:(void *)machine {
self = [super init];
if(self)
{
if(self) {
_machine = (CRTMachine::Machine *)machine;
_machineDelegate.machine = self;
self.machine->set_delegate(&_machineDelegate);
_speakerDelegate.machine = self;
_machine->set_delegate(&_machineDelegate);
}
return self;
}
@@ -67,14 +69,14 @@ struct MachineDelegate: CRTMachine::Machine::Delegate {
- (void)dealloc {
[_view performWithGLContext:^{
@synchronized(self) {
self.machine->close_output();
_machine->close_output();
}
}];
}
- (float)idealSamplingRateFromRange:(NSRange)range {
@synchronized(self) {
std::shared_ptr<Outputs::Speaker> speaker = self.machine->get_speaker();
std::shared_ptr<Outputs::Speaker> speaker = _machine->get_speaker();
if(speaker)
{
return speaker->get_ideal_clock_rate_in_range((float)range.location, (float)(range.location + range.length));
@@ -91,7 +93,7 @@ struct MachineDelegate: CRTMachine::Machine::Delegate {
- (BOOL)setSpeakerDelegate:(Outputs::Speaker::Delegate *)delegate sampleRate:(float)sampleRate bufferSize:(NSUInteger)bufferSize {
@synchronized(self) {
std::shared_ptr<Outputs::Speaker> speaker = self.machine->get_speaker();
std::shared_ptr<Outputs::Speaker> speaker = _machine->get_speaker();
if(speaker)
{
speaker->set_output_rate(sampleRate, (int)bufferSize);
@@ -104,7 +106,7 @@ struct MachineDelegate: CRTMachine::Machine::Delegate {
- (void)runForNumberOfCycles:(int)numberOfCycles {
@synchronized(self) {
self.machine->run_for_cycles(numberOfCycles);
_machine->run_for(Cycles(numberOfCycles));
}
}
@@ -116,33 +118,44 @@ struct MachineDelegate: CRTMachine::Machine::Delegate {
}
- (void)setupOutputWithAspectRatio:(float)aspectRatio {
self.machine->setup_output(aspectRatio);
_machine->setup_output(aspectRatio);
// Since OS X v10.6, Macs have had a gamma of 2.2.
_machine->get_crt()->set_output_gamma(2.2f);
}
- (void)drawViewForPixelSize:(CGSize)pixelSize onlyIfDirty:(BOOL)onlyIfDirty {
self.machine->get_crt()->draw_frame((unsigned int)pixelSize.width, (unsigned int)pixelSize.height, onlyIfDirty ? true : false);
_machine->get_crt()->draw_frame((unsigned int)pixelSize.width, (unsigned int)pixelSize.height, onlyIfDirty ? true : false);
}
- (double)clockRate {
return self.machine->get_clock_rate();
return _machine->get_clock_rate();
}
- (BOOL)clockIsUnlimited {
return self.machine->get_clock_is_unlimited() ? YES : NO;
return _machine->get_clock_is_unlimited() ? YES : NO;
}
- (void)paste:(NSString *)paste {
Utility::TypeRecipient *typeRecipient = dynamic_cast<Utility::TypeRecipient *>(self.machine);
Utility::TypeRecipient *typeRecipient = dynamic_cast<Utility::TypeRecipient *>(_machine);
if(typeRecipient)
typeRecipient->set_typer_for_string([paste UTF8String]);
}
- (void)applyTarget:(StaticAnalyser::Target)target {
- (void)applyTarget:(const StaticAnalyser::Target &)target {
@synchronized(self) {
ConfigurationTarget::Machine *const configurationTarget =
dynamic_cast<ConfigurationTarget::Machine *>(self.machine);
dynamic_cast<ConfigurationTarget::Machine *>(_machine);
if(configurationTarget) configurationTarget->configure_as_target(target);
}
}
- (void)applyMedia:(const StaticAnalyser::Media &)media {
@synchronized(self) {
ConfigurationTarget::Machine *const configurationTarget =
dynamic_cast<ConfigurationTarget::Machine *>(_machine);
if(configurationTarget) configurationTarget->insert_media(media);
}
}
@end

View File

@@ -22,3 +22,10 @@
- (void)applyToMachine:(CSMachine *)machine;
@end
@interface CSMediaSet : NSObject
- (instancetype)initWithFileAtURL:(NSURL *)url;
- (void)applyToMachine:(CSMachine *)machine;
@end

View File

@@ -14,23 +14,22 @@
#include "StaticAnalyser.hpp"
#import "CSAmstradCPC.h"
#import "CSAtari2600.h"
#import "CSElectron.h"
#import "CSOric.h"
#import "CSVic20.h"
#import "CSZX8081+Instantiation.h"
#import "Clock_Signal-Swift.h"
@implementation CSStaticAnalyser
{
@implementation CSStaticAnalyser {
StaticAnalyser::Target _target;
}
- (instancetype)initWithFileAtURL:(NSURL *)url
{
- (instancetype)initWithFileAtURL:(NSURL *)url {
self = [super init];
if(self)
{
if(self) {
std::list<StaticAnalyser::Target> targets = StaticAnalyser::GetTargets([url fileSystemRepresentation]);
if(!targets.size()) return nil;
_target = targets.front();
@@ -41,33 +40,50 @@
return self;
}
- (NSString *)optionsPanelNibName
{
switch(_target.machine)
{
case StaticAnalyser::Target::Atari2600: return @"Atari2600Options";
case StaticAnalyser::Target::Electron: return @"ElectronOptions";
case StaticAnalyser::Target::Oric: return @"OricOptions";
case StaticAnalyser::Target::Vic20: return @"Vic20Options";
- (NSString *)optionsPanelNibName {
switch(_target.machine) {
case StaticAnalyser::Target::AmstradCPC: return nil;
case StaticAnalyser::Target::Atari2600: return @"Atari2600Options";
case StaticAnalyser::Target::Electron: return @"ElectronOptions";
case StaticAnalyser::Target::Oric: return @"OricOptions";
case StaticAnalyser::Target::Vic20: return @"Vic20Options";
case StaticAnalyser::Target::ZX8081: return @"ZX8081Options";
default: return nil;
}
}
- (CSMachine *)newMachine
{
switch(_target.machine)
{
case StaticAnalyser::Target::Atari2600: return [[CSAtari2600 alloc] init];
case StaticAnalyser::Target::Electron: return [[CSElectron alloc] init];
case StaticAnalyser::Target::Oric: return [[CSOric alloc] init];
case StaticAnalyser::Target::Vic20: return [[CSVic20 alloc] init];
- (CSMachine *)newMachine {
switch(_target.machine) {
case StaticAnalyser::Target::AmstradCPC: return [[CSAmstradCPC alloc] init];
case StaticAnalyser::Target::Atari2600: return [[CSAtari2600 alloc] init];
case StaticAnalyser::Target::Electron: return [[CSElectron alloc] init];
case StaticAnalyser::Target::Oric: return [[CSOric alloc] init];
case StaticAnalyser::Target::Vic20: return [[CSVic20 alloc] init];
case StaticAnalyser::Target::ZX8081: return [[CSZX8081 alloc] initWithIntendedTarget:_target];
default: return nil;
}
}
- (void)applyToMachine:(CSMachine *)machine
{
- (void)applyToMachine:(CSMachine *)machine {
[machine applyTarget:_target];
}
@end
@implementation CSMediaSet {
StaticAnalyser::Media _media;
}
- (instancetype)initWithFileAtURL:(NSURL *)url {
self = [super init];
if(self) {
_media = StaticAnalyser::GetMedia([url fileSystemRepresentation]);
}
return self;
}
- (void)applyToMachine:(CSMachine *)machine {
[machine applyMedia:_media];
}
@end

View File

@@ -0,0 +1,16 @@
//
// CSAmstradCPC.h
// Clock Signal
//
// Created by Thomas Harte on 30/07/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#import "CSMachine.h"
#import "CSKeyboardMachine.h"
@interface CSAmstradCPC : CSMachine <CSKeyboardMachine>
- (instancetype)init;
@end

View File

@@ -0,0 +1,152 @@
//
// CSAmstradCPC.m
// Clock Signal
//
// Created by Thomas Harte on 30/07/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#import "CSAmstradCPC.h"
#include "AmstradCPC.hpp"
#import "CSMachine+Subclassing.h"
#import "NSData+StdVector.h"
#import "NSBundle+DataResource.h"
@implementation CSAmstradCPC {
std::unique_ptr<AmstradCPC::Machine> _amstradCPC;
}
- (instancetype)init {
AmstradCPC::Machine *machine = AmstradCPC::Machine::AmstradCPC();
self = [super initWithMachine:machine];
if(self) {
_amstradCPC.reset(machine);
NSDictionary *roms = @{
@(AmstradCPC::ROMType::OS464) : @"os464",
@(AmstradCPC::ROMType::OS664) : @"os664",
@(AmstradCPC::ROMType::OS6128) : @"os6128",
@(AmstradCPC::ROMType::BASIC464) : @"basic464",
@(AmstradCPC::ROMType::BASIC664) : @"basic664",
@(AmstradCPC::ROMType::BASIC6128) : @"basic6128",
@(AmstradCPC::ROMType::AMSDOS) : @"amsdos",
};
for(NSNumber *key in roms.allKeys) {
AmstradCPC::ROMType type = (AmstradCPC::ROMType)key.integerValue;
NSString *name = roms[key];
NSData *data = [self rom:name];
if(data) {
_amstradCPC->set_rom(type, data.stdVector8);
} else {
NSLog(@"Amstrad CPC ROM missing: %@", name);
}
}
}
return self;
}
- (NSData *)rom:(NSString *)name {
return [[NSBundle mainBundle] dataForResource:name withExtension:@"rom" subdirectory:@"ROMImages/AmstradCPC"];
}
- (NSString *)userDefaultsPrefix { return @"amstradCPC"; }
#pragma mark - Keyboard Mapping
- (void)clearAllKeys {
@synchronized(self) {
_amstradCPC->clear_all_keys();
}
}
- (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed {
@synchronized(self) {
switch(key) {
case VK_ANSI_0: _amstradCPC->set_key_state(AmstradCPC::Key::Key0, isPressed); break;
case VK_ANSI_1: _amstradCPC->set_key_state(AmstradCPC::Key::Key1, isPressed); break;
case VK_ANSI_2: _amstradCPC->set_key_state(AmstradCPC::Key::Key2, isPressed); break;
case VK_ANSI_3: _amstradCPC->set_key_state(AmstradCPC::Key::Key3, isPressed); break;
case VK_ANSI_4: _amstradCPC->set_key_state(AmstradCPC::Key::Key4, isPressed); break;
case VK_ANSI_5: _amstradCPC->set_key_state(AmstradCPC::Key::Key5, isPressed); break;
case VK_ANSI_6: _amstradCPC->set_key_state(AmstradCPC::Key::Key6, isPressed); break;
case VK_ANSI_7: _amstradCPC->set_key_state(AmstradCPC::Key::Key7, isPressed); break;
case VK_ANSI_8: _amstradCPC->set_key_state(AmstradCPC::Key::Key8, isPressed); break;
case VK_ANSI_9: _amstradCPC->set_key_state(AmstradCPC::Key::Key9, isPressed); break;
case VK_ANSI_Q: _amstradCPC->set_key_state(AmstradCPC::Key::KeyQ, isPressed); break;
case VK_ANSI_W: _amstradCPC->set_key_state(AmstradCPC::Key::KeyW, isPressed); break;
case VK_ANSI_E: _amstradCPC->set_key_state(AmstradCPC::Key::KeyE, isPressed); break;
case VK_ANSI_R: _amstradCPC->set_key_state(AmstradCPC::Key::KeyR, isPressed); break;
case VK_ANSI_T: _amstradCPC->set_key_state(AmstradCPC::Key::KeyT, isPressed); break;
case VK_ANSI_Y: _amstradCPC->set_key_state(AmstradCPC::Key::KeyY, isPressed); break;
case VK_ANSI_U: _amstradCPC->set_key_state(AmstradCPC::Key::KeyU, isPressed); break;
case VK_ANSI_I: _amstradCPC->set_key_state(AmstradCPC::Key::KeyI, isPressed); break;
case VK_ANSI_O: _amstradCPC->set_key_state(AmstradCPC::Key::KeyO, isPressed); break;
case VK_ANSI_P: _amstradCPC->set_key_state(AmstradCPC::Key::KeyP, isPressed); break;
case VK_ANSI_A: _amstradCPC->set_key_state(AmstradCPC::Key::KeyA, isPressed); break;
case VK_ANSI_S: _amstradCPC->set_key_state(AmstradCPC::Key::KeyS, isPressed); break;
case VK_ANSI_D: _amstradCPC->set_key_state(AmstradCPC::Key::KeyD, isPressed); break;
case VK_ANSI_F: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF, isPressed); break;
case VK_ANSI_G: _amstradCPC->set_key_state(AmstradCPC::Key::KeyG, isPressed); break;
case VK_ANSI_H: _amstradCPC->set_key_state(AmstradCPC::Key::KeyH, isPressed); break;
case VK_ANSI_J: _amstradCPC->set_key_state(AmstradCPC::Key::KeyJ, isPressed); break;
case VK_ANSI_K: _amstradCPC->set_key_state(AmstradCPC::Key::KeyK, isPressed); break;
case VK_ANSI_L: _amstradCPC->set_key_state(AmstradCPC::Key::KeyL, isPressed); break;
case VK_ANSI_Z: _amstradCPC->set_key_state(AmstradCPC::Key::KeyZ, isPressed); break;
case VK_ANSI_X: _amstradCPC->set_key_state(AmstradCPC::Key::KeyX, isPressed); break;
case VK_ANSI_C: _amstradCPC->set_key_state(AmstradCPC::Key::KeyC, isPressed); break;
case VK_ANSI_V: _amstradCPC->set_key_state(AmstradCPC::Key::KeyV, isPressed); break;
case VK_ANSI_B: _amstradCPC->set_key_state(AmstradCPC::Key::KeyB, isPressed); break;
case VK_ANSI_N: _amstradCPC->set_key_state(AmstradCPC::Key::KeyN, isPressed); break;
case VK_ANSI_M: _amstradCPC->set_key_state(AmstradCPC::Key::KeyM, isPressed); break;
case VK_Space: _amstradCPC->set_key_state(AmstradCPC::Key::KeySpace, isPressed); break;
case VK_ANSI_Grave: _amstradCPC->set_key_state(AmstradCPC::Key::KeyCopy, isPressed); break;
case VK_Return: _amstradCPC->set_key_state(AmstradCPC::Key::KeyReturn, isPressed); break;
case VK_ANSI_Minus: _amstradCPC->set_key_state(AmstradCPC::Key::KeyMinus, isPressed); break;
case VK_RightArrow: _amstradCPC->set_key_state(AmstradCPC::Key::KeyRight, isPressed); break;
case VK_LeftArrow: _amstradCPC->set_key_state(AmstradCPC::Key::KeyLeft, isPressed); break;
case VK_DownArrow: _amstradCPC->set_key_state(AmstradCPC::Key::KeyDown, isPressed); break;
case VK_UpArrow: _amstradCPC->set_key_state(AmstradCPC::Key::KeyUp, isPressed); break;
case VK_Delete: _amstradCPC->set_key_state(AmstradCPC::Key::KeyDelete, isPressed); break;
case VK_Escape: _amstradCPC->set_key_state(AmstradCPC::Key::KeyEscape, isPressed); break;
case VK_ANSI_Comma: _amstradCPC->set_key_state(AmstradCPC::Key::KeyComma, isPressed); break;
case VK_ANSI_Period: _amstradCPC->set_key_state(AmstradCPC::Key::KeyFullStop, isPressed); break;
case VK_ANSI_Semicolon:
_amstradCPC->set_key_state(AmstradCPC::Key::KeySemicolon, isPressed); break;
case VK_ANSI_Quote: _amstradCPC->set_key_state(AmstradCPC::Key::KeyColon, isPressed); break;
case VK_ANSI_Slash: _amstradCPC->set_key_state(AmstradCPC::Key::KeyForwardSlash, isPressed); break;
case VK_ANSI_Backslash: _amstradCPC->set_key_state(AmstradCPC::Key::KeyBackSlash, isPressed); break;
case VK_Shift: _amstradCPC->set_key_state(AmstradCPC::Key::KeyShift, isPressed); break;
case VK_Control: _amstradCPC->set_key_state(AmstradCPC::Key::KeyControl, isPressed); break;
case VK_F1: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF1, isPressed); break;
case VK_F2: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF2, isPressed); break;
case VK_F3: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF3, isPressed); break;
case VK_F4: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF4, isPressed); break;
case VK_F5: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF5, isPressed); break;
case VK_F6: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF6, isPressed); break;
case VK_F7: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF7, isPressed); break;
case VK_F8: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF8, isPressed); break;
case VK_F9: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF9, isPressed); break;
case VK_F10: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF0, isPressed); break;
case VK_F12: _amstradCPC->set_key_state(AmstradCPC::Key::KeyFDot, isPressed); break;
default:
// printf("%02x\n", key);
break;
}
}
}
@end

View File

@@ -12,6 +12,8 @@
@interface CSAtari2600 : CSMachine <CSJoystickMachine>
- (instancetype)init;
- (void)setResetLineEnabled:(BOOL)enabled;
@property (nonatomic, assign) BOOL colourButton;

View File

@@ -12,7 +12,16 @@
#import "CSMachine+Subclassing.h"
@implementation CSAtari2600 {
Atari2600::Machine _atari2600;
std::unique_ptr<Atari2600::Machine> _atari2600;
}
- (instancetype)init {
Atari2600::Machine *machine = Atari2600::Machine::Atari2600();
self = [super initWithMachine:machine];
if(self) {
_atari2600.reset(machine);
}
return self;
}
- (void)setDirection:(CSJoystickDirection)direction onPad:(NSUInteger)pad isPressed:(BOOL)isPressed {
@@ -25,19 +34,19 @@
case CSJoystickDirectionRight: input = pad ? Atari2600DigitalInputJoy2Right : Atari2600DigitalInputJoy1Right; break;
}
@synchronized(self) {
_atari2600.set_digital_input(input, isPressed ? true : false);
_atari2600->set_digital_input(input, isPressed ? true : false);
}
}
- (void)setButtonAtIndex:(NSUInteger)button onPad:(NSUInteger)pad isPressed:(BOOL)isPressed {
@synchronized(self) {
_atari2600.set_digital_input(pad ? Atari2600DigitalInputJoy2Fire : Atari2600DigitalInputJoy1Fire, isPressed ? true : false);
_atari2600->set_digital_input(pad ? Atari2600DigitalInputJoy2Fire : Atari2600DigitalInputJoy1Fire, isPressed ? true : false);
}
}
- (void)setResetLineEnabled:(BOOL)enabled {
@synchronized(self) {
_atari2600.set_reset_line(enabled ? true : false);
_atari2600->set_reset_switch(enabled ? true : false);
}
}
@@ -47,40 +56,36 @@
}
}
- (CRTMachine::Machine * const)machine {
return &_atari2600;
}
#pragma mark - Switches
- (void)setColourButton:(BOOL)colourButton {
_colourButton = colourButton;
@synchronized(self) {
_atari2600.set_switch_is_enabled(Atari2600SwitchColour, colourButton);
_atari2600->set_switch_is_enabled(Atari2600SwitchColour, colourButton);
}
}
- (void)setLeftPlayerDifficultyButton:(BOOL)leftPlayerDifficultyButton {
_leftPlayerDifficultyButton = leftPlayerDifficultyButton;
@synchronized(self) {
_atari2600.set_switch_is_enabled(Atari2600SwitchLeftPlayerDifficulty, leftPlayerDifficultyButton);
_atari2600->set_switch_is_enabled(Atari2600SwitchLeftPlayerDifficulty, leftPlayerDifficultyButton);
}
}
- (void)setRightPlayerDifficultyButton:(BOOL)rightPlayerDifficultyButton {
_rightPlayerDifficultyButton = rightPlayerDifficultyButton;
@synchronized(self) {
_atari2600.set_switch_is_enabled(Atari2600SwitchRightPlayerDifficulty, rightPlayerDifficultyButton);
_atari2600->set_switch_is_enabled(Atari2600SwitchRightPlayerDifficulty, rightPlayerDifficultyButton);
}
}
- (void)toggleSwitch:(Atari2600Switch)toggleSwitch {
@synchronized(self) {
_atari2600.set_switch_is_enabled(toggleSwitch, true);
_atari2600->set_switch_is_enabled(toggleSwitch, true);
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
@synchronized(self) {
_atari2600.set_switch_is_enabled(toggleSwitch, false);
_atari2600->set_switch_is_enabled(toggleSwitch, false);
}
});
}

View File

@@ -12,6 +12,8 @@
@interface CSElectron : CSMachine <CSKeyboardMachine, CSFastLoading>
- (instancetype)init;
@property (nonatomic, assign) BOOL useFastLoadingHack;
@property (nonatomic, assign) BOOL useTelevisionOutput;

View File

@@ -16,17 +16,16 @@
#import "NSBundle+DataResource.h"
@implementation CSElectron {
Electron::Machine _electron;
}
- (CRTMachine::Machine * const)machine {
return &_electron;
std::unique_ptr<Electron::Machine> _electron;
}
- (instancetype)init {
self = [super init];
if(self)
{
Electron::Machine *machine = Electron::Machine::Electron();
self = [super initWithMachine:machine];
if(self) {
_electron.reset(machine);
[self setOSROM:[self rom:@"os"]];
[self setBASICROM:[self rom:@"basic"]];
[self setDFSROM:[self rom:@"DFS-1770-2.20"]];
@@ -38,8 +37,7 @@
return self;
}
- (NSData *)rom:(NSString *)name
{
- (NSData *)rom:(NSString *)name {
return [[NSBundle mainBundle] dataForResource:name withExtension:@"rom" subdirectory:@"ROMImages/Electron"];
}
@@ -54,7 +52,7 @@
if(rom)
{
@synchronized(self) {
_electron.set_rom((Electron::ROMSlot)slot, rom.stdVector8, false);
_electron->set_rom((Electron::ROMSlot)slot, rom.stdVector8, false);
}
}
}
@@ -63,7 +61,7 @@
- (void)clearAllKeys {
@synchronized(self) {
_electron.clear_all_keys();
_electron->clear_all_keys();
}
}
@@ -71,74 +69,74 @@
@synchronized(self) {
switch(key)
{
case VK_ANSI_0: _electron.set_key_state(Electron::Key::Key0, isPressed); break;
case VK_ANSI_1: _electron.set_key_state(Electron::Key::Key1, isPressed); break;
case VK_ANSI_2: _electron.set_key_state(Electron::Key::Key2, isPressed); break;
case VK_ANSI_3: _electron.set_key_state(Electron::Key::Key3, isPressed); break;
case VK_ANSI_4: _electron.set_key_state(Electron::Key::Key4, isPressed); break;
case VK_ANSI_5: _electron.set_key_state(Electron::Key::Key5, isPressed); break;
case VK_ANSI_6: _electron.set_key_state(Electron::Key::Key6, isPressed); break;
case VK_ANSI_7: _electron.set_key_state(Electron::Key::Key7, isPressed); break;
case VK_ANSI_8: _electron.set_key_state(Electron::Key::Key8, isPressed); break;
case VK_ANSI_9: _electron.set_key_state(Electron::Key::Key9, isPressed); break;
case VK_ANSI_0: _electron->set_key_state(Electron::Key::Key0, isPressed); break;
case VK_ANSI_1: _electron->set_key_state(Electron::Key::Key1, isPressed); break;
case VK_ANSI_2: _electron->set_key_state(Electron::Key::Key2, isPressed); break;
case VK_ANSI_3: _electron->set_key_state(Electron::Key::Key3, isPressed); break;
case VK_ANSI_4: _electron->set_key_state(Electron::Key::Key4, isPressed); break;
case VK_ANSI_5: _electron->set_key_state(Electron::Key::Key5, isPressed); break;
case VK_ANSI_6: _electron->set_key_state(Electron::Key::Key6, isPressed); break;
case VK_ANSI_7: _electron->set_key_state(Electron::Key::Key7, isPressed); break;
case VK_ANSI_8: _electron->set_key_state(Electron::Key::Key8, isPressed); break;
case VK_ANSI_9: _electron->set_key_state(Electron::Key::Key9, isPressed); break;
case VK_ANSI_Q: _electron.set_key_state(Electron::Key::KeyQ, isPressed); break;
case VK_ANSI_W: _electron.set_key_state(Electron::Key::KeyW, isPressed); break;
case VK_ANSI_E: _electron.set_key_state(Electron::Key::KeyE, isPressed); break;
case VK_ANSI_R: _electron.set_key_state(Electron::Key::KeyR, isPressed); break;
case VK_ANSI_T: _electron.set_key_state(Electron::Key::KeyT, isPressed); break;
case VK_ANSI_Y: _electron.set_key_state(Electron::Key::KeyY, isPressed); break;
case VK_ANSI_U: _electron.set_key_state(Electron::Key::KeyU, isPressed); break;
case VK_ANSI_I: _electron.set_key_state(Electron::Key::KeyI, isPressed); break;
case VK_ANSI_O: _electron.set_key_state(Electron::Key::KeyO, isPressed); break;
case VK_ANSI_P: _electron.set_key_state(Electron::Key::KeyP, isPressed); break;
case VK_ANSI_A: _electron.set_key_state(Electron::Key::KeyA, isPressed); break;
case VK_ANSI_S: _electron.set_key_state(Electron::Key::KeyS, isPressed); break;
case VK_ANSI_D: _electron.set_key_state(Electron::Key::KeyD, isPressed); break;
case VK_ANSI_F: _electron.set_key_state(Electron::Key::KeyF, isPressed); break;
case VK_ANSI_G: _electron.set_key_state(Electron::Key::KeyG, isPressed); break;
case VK_ANSI_H: _electron.set_key_state(Electron::Key::KeyH, isPressed); break;
case VK_ANSI_J: _electron.set_key_state(Electron::Key::KeyJ, isPressed); break;
case VK_ANSI_K: _electron.set_key_state(Electron::Key::KeyK, isPressed); break;
case VK_ANSI_L: _electron.set_key_state(Electron::Key::KeyL, isPressed); break;
case VK_ANSI_Z: _electron.set_key_state(Electron::Key::KeyZ, isPressed); break;
case VK_ANSI_X: _electron.set_key_state(Electron::Key::KeyX, isPressed); break;
case VK_ANSI_C: _electron.set_key_state(Electron::Key::KeyC, isPressed); break;
case VK_ANSI_V: _electron.set_key_state(Electron::Key::KeyV, isPressed); break;
case VK_ANSI_B: _electron.set_key_state(Electron::Key::KeyB, isPressed); break;
case VK_ANSI_N: _electron.set_key_state(Electron::Key::KeyN, isPressed); break;
case VK_ANSI_M: _electron.set_key_state(Electron::Key::KeyM, isPressed); break;
case VK_ANSI_Q: _electron->set_key_state(Electron::Key::KeyQ, isPressed); break;
case VK_ANSI_W: _electron->set_key_state(Electron::Key::KeyW, isPressed); break;
case VK_ANSI_E: _electron->set_key_state(Electron::Key::KeyE, isPressed); break;
case VK_ANSI_R: _electron->set_key_state(Electron::Key::KeyR, isPressed); break;
case VK_ANSI_T: _electron->set_key_state(Electron::Key::KeyT, isPressed); break;
case VK_ANSI_Y: _electron->set_key_state(Electron::Key::KeyY, isPressed); break;
case VK_ANSI_U: _electron->set_key_state(Electron::Key::KeyU, isPressed); break;
case VK_ANSI_I: _electron->set_key_state(Electron::Key::KeyI, isPressed); break;
case VK_ANSI_O: _electron->set_key_state(Electron::Key::KeyO, isPressed); break;
case VK_ANSI_P: _electron->set_key_state(Electron::Key::KeyP, isPressed); break;
case VK_ANSI_A: _electron->set_key_state(Electron::Key::KeyA, isPressed); break;
case VK_ANSI_S: _electron->set_key_state(Electron::Key::KeyS, isPressed); break;
case VK_ANSI_D: _electron->set_key_state(Electron::Key::KeyD, isPressed); break;
case VK_ANSI_F: _electron->set_key_state(Electron::Key::KeyF, isPressed); break;
case VK_ANSI_G: _electron->set_key_state(Electron::Key::KeyG, isPressed); break;
case VK_ANSI_H: _electron->set_key_state(Electron::Key::KeyH, isPressed); break;
case VK_ANSI_J: _electron->set_key_state(Electron::Key::KeyJ, isPressed); break;
case VK_ANSI_K: _electron->set_key_state(Electron::Key::KeyK, isPressed); break;
case VK_ANSI_L: _electron->set_key_state(Electron::Key::KeyL, isPressed); break;
case VK_ANSI_Z: _electron->set_key_state(Electron::Key::KeyZ, isPressed); break;
case VK_ANSI_X: _electron->set_key_state(Electron::Key::KeyX, isPressed); break;
case VK_ANSI_C: _electron->set_key_state(Electron::Key::KeyC, isPressed); break;
case VK_ANSI_V: _electron->set_key_state(Electron::Key::KeyV, isPressed); break;
case VK_ANSI_B: _electron->set_key_state(Electron::Key::KeyB, isPressed); break;
case VK_ANSI_N: _electron->set_key_state(Electron::Key::KeyN, isPressed); break;
case VK_ANSI_M: _electron->set_key_state(Electron::Key::KeyM, isPressed); break;
case VK_Space: _electron.set_key_state(Electron::Key::KeySpace, isPressed); break;
case VK_Space: _electron->set_key_state(Electron::Key::KeySpace, isPressed); break;
case VK_ANSI_Grave:
case VK_ANSI_Backslash:
_electron.set_key_state(Electron::Key::KeyCopy, isPressed); break;
case VK_Return: _electron.set_key_state(Electron::Key::KeyReturn, isPressed); break;
case VK_ANSI_Minus: _electron.set_key_state(Electron::Key::KeyMinus, isPressed); break;
_electron->set_key_state(Electron::Key::KeyCopy, isPressed); break;
case VK_Return: _electron->set_key_state(Electron::Key::KeyReturn, isPressed); break;
case VK_ANSI_Minus: _electron->set_key_state(Electron::Key::KeyMinus, isPressed); break;
case VK_RightArrow: _electron.set_key_state(Electron::Key::KeyRight, isPressed); break;
case VK_LeftArrow: _electron.set_key_state(Electron::Key::KeyLeft, isPressed); break;
case VK_DownArrow: _electron.set_key_state(Electron::Key::KeyDown, isPressed); break;
case VK_UpArrow: _electron.set_key_state(Electron::Key::KeyUp, isPressed); break;
case VK_RightArrow: _electron->set_key_state(Electron::Key::KeyRight, isPressed); break;
case VK_LeftArrow: _electron->set_key_state(Electron::Key::KeyLeft, isPressed); break;
case VK_DownArrow: _electron->set_key_state(Electron::Key::KeyDown, isPressed); break;
case VK_UpArrow: _electron->set_key_state(Electron::Key::KeyUp, isPressed); break;
case VK_Delete: _electron.set_key_state(Electron::Key::KeyDelete, isPressed); break;
case VK_Escape: _electron.set_key_state(Electron::Key::KeyEscape, isPressed); break;
case VK_Delete: _electron->set_key_state(Electron::Key::KeyDelete, isPressed); break;
case VK_Escape: _electron->set_key_state(Electron::Key::KeyEscape, isPressed); break;
case VK_ANSI_Comma: _electron.set_key_state(Electron::Key::KeyComma, isPressed); break;
case VK_ANSI_Period: _electron.set_key_state(Electron::Key::KeyFullStop, isPressed); break;
case VK_ANSI_Comma: _electron->set_key_state(Electron::Key::KeyComma, isPressed); break;
case VK_ANSI_Period: _electron->set_key_state(Electron::Key::KeyFullStop, isPressed); break;
case VK_ANSI_Semicolon:
_electron.set_key_state(Electron::Key::KeySemiColon, isPressed); break;
case VK_ANSI_Quote: _electron.set_key_state(Electron::Key::KeyColon, isPressed); break;
_electron->set_key_state(Electron::Key::KeySemiColon, isPressed); break;
case VK_ANSI_Quote: _electron->set_key_state(Electron::Key::KeyColon, isPressed); break;
case VK_ANSI_Slash: _electron.set_key_state(Electron::Key::KeySlash, isPressed); break;
case VK_ANSI_Slash: _electron->set_key_state(Electron::Key::KeySlash, isPressed); break;
case VK_Shift: _electron.set_key_state(Electron::Key::KeyShift, isPressed); break;
case VK_Control: _electron.set_key_state(Electron::Key::KeyControl, isPressed); break;
case VK_Shift: _electron->set_key_state(Electron::Key::KeyShift, isPressed); break;
case VK_Control: _electron->set_key_state(Electron::Key::KeyControl, isPressed); break;
case VK_Command:
case VK_Option: _electron.set_key_state(Electron::Key::KeyFunc, isPressed); break;
case VK_Option: _electron->set_key_state(Electron::Key::KeyFunc, isPressed); break;
case VK_F12: _electron.set_key_state(Electron::Key::KeyBreak, isPressed); break;
case VK_F12: _electron->set_key_state(Electron::Key::KeyBreak, isPressed); break;
default:
// printf("%02x\n", key);
@@ -154,19 +152,14 @@
- (void)setUseFastLoadingHack:(BOOL)useFastLoadingHack {
@synchronized(self) {
_useFastLoadingHack = useFastLoadingHack;
_electron.set_use_fast_tape_hack(useFastLoadingHack ? true : false);
_electron->set_use_fast_tape_hack(useFastLoadingHack ? true : false);
}
}
- (void)setUseTelevisionOutput:(BOOL)useTelevisionOutput {
@synchronized(self) {
_useTelevisionOutput = useTelevisionOutput;
_electron.get_crt()->set_output_device(useTelevisionOutput ? Outputs::CRT::Television : Outputs::CRT::Monitor);
_electron->get_crt()->set_output_device(useTelevisionOutput ? Outputs::CRT::Television : Outputs::CRT::Monitor);
}
}
//override func aspectRatio() -> NSSize {
// return NSSize(width: 11.0, height: 10.0)
// }
@end

View File

@@ -12,6 +12,8 @@
@interface CSOric : CSMachine <CSKeyboardMachine, CSFastLoading>
- (instancetype)init;
@property (nonatomic, assign) BOOL useFastLoadingHack;
@property (nonatomic, assign) BOOL useCompositeOutput;

View File

@@ -15,118 +15,113 @@
#import "NSData+StdVector.h"
#import "NSBundle+DataResource.h"
@implementation CSOric
{
Oric::Machine _oric;
@implementation CSOric {
std::unique_ptr<Oric::Machine> _oric;
}
- (instancetype)init
{
self = [super init];
if(self)
{
- (instancetype)init {
Oric::Machine *machine = Oric::Machine::Oric();
self = [super initWithMachine:machine];
if(self) {
_oric.reset(machine);
NSData *basic10 = [self rom:@"basic10"];
NSData *basic11 = [self rom:@"basic11"];
NSData *colour = [self rom:@"colour"];
NSData *microdisc = [self rom:@"microdisc"];
if(basic10) _oric.set_rom(Oric::BASIC10, basic10.stdVector8);
if(basic11) _oric.set_rom(Oric::BASIC11, basic11.stdVector8);
if(colour) _oric.set_rom(Oric::Colour, colour.stdVector8);
if(microdisc) _oric.set_rom(Oric::Microdisc, microdisc.stdVector8);
if(basic10) _oric->set_rom(Oric::BASIC10, basic10.stdVector8);
if(basic11) _oric->set_rom(Oric::BASIC11, basic11.stdVector8);
if(colour) _oric->set_rom(Oric::Colour, colour.stdVector8);
if(microdisc) _oric->set_rom(Oric::Microdisc, microdisc.stdVector8);
}
return self;
}
- (NSData *)rom:(NSString *)name
{
- (NSData *)rom:(NSString *)name {
return [[NSBundle mainBundle] dataForResource:name withExtension:@"rom" subdirectory:@"ROMImages/Oric"];
}
- (CRTMachine::Machine * const)machine
{
return &_oric;
}
#pragma mark - CSKeyboardMachine
- (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed
{
- (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed {
@synchronized(self) {
switch(key)
{
case VK_ANSI_0: _oric.set_key_state(Oric::Key::Key0, isPressed); break;
case VK_ANSI_1: _oric.set_key_state(Oric::Key::Key1, isPressed); break;
case VK_ANSI_2: _oric.set_key_state(Oric::Key::Key2, isPressed); break;
case VK_ANSI_3: _oric.set_key_state(Oric::Key::Key3, isPressed); break;
case VK_ANSI_4: _oric.set_key_state(Oric::Key::Key4, isPressed); break;
case VK_ANSI_5: _oric.set_key_state(Oric::Key::Key5, isPressed); break;
case VK_ANSI_6: _oric.set_key_state(Oric::Key::Key6, isPressed); break;
case VK_ANSI_7: _oric.set_key_state(Oric::Key::Key7, isPressed); break;
case VK_ANSI_8: _oric.set_key_state(Oric::Key::Key8, isPressed); break;
case VK_ANSI_9: _oric.set_key_state(Oric::Key::Key9, isPressed); break;
switch(key) {
case VK_ANSI_0: _oric->set_key_state(Oric::Key::Key0, isPressed); break;
case VK_ANSI_1: _oric->set_key_state(Oric::Key::Key1, isPressed); break;
case VK_ANSI_2: _oric->set_key_state(Oric::Key::Key2, isPressed); break;
case VK_ANSI_3: _oric->set_key_state(Oric::Key::Key3, isPressed); break;
case VK_ANSI_4: _oric->set_key_state(Oric::Key::Key4, isPressed); break;
case VK_ANSI_5: _oric->set_key_state(Oric::Key::Key5, isPressed); break;
case VK_ANSI_6: _oric->set_key_state(Oric::Key::Key6, isPressed); break;
case VK_ANSI_7: _oric->set_key_state(Oric::Key::Key7, isPressed); break;
case VK_ANSI_8: _oric->set_key_state(Oric::Key::Key8, isPressed); break;
case VK_ANSI_9: _oric->set_key_state(Oric::Key::Key9, isPressed); break;
case VK_ANSI_Q: _oric.set_key_state(Oric::Key::KeyQ, isPressed); break;
case VK_ANSI_W: _oric.set_key_state(Oric::Key::KeyW, isPressed); break;
case VK_ANSI_E: _oric.set_key_state(Oric::Key::KeyE, isPressed); break;
case VK_ANSI_R: _oric.set_key_state(Oric::Key::KeyR, isPressed); break;
case VK_ANSI_T: _oric.set_key_state(Oric::Key::KeyT, isPressed); break;
case VK_ANSI_Y: _oric.set_key_state(Oric::Key::KeyY, isPressed); break;
case VK_ANSI_U: _oric.set_key_state(Oric::Key::KeyU, isPressed); break;
case VK_ANSI_I: _oric.set_key_state(Oric::Key::KeyI, isPressed); break;
case VK_ANSI_O: _oric.set_key_state(Oric::Key::KeyO, isPressed); break;
case VK_ANSI_P: _oric.set_key_state(Oric::Key::KeyP, isPressed); break;
case VK_ANSI_A: _oric.set_key_state(Oric::Key::KeyA, isPressed); break;
case VK_ANSI_S: _oric.set_key_state(Oric::Key::KeyS, isPressed); break;
case VK_ANSI_D: _oric.set_key_state(Oric::Key::KeyD, isPressed); break;
case VK_ANSI_F: _oric.set_key_state(Oric::Key::KeyF, isPressed); break;
case VK_ANSI_G: _oric.set_key_state(Oric::Key::KeyG, isPressed); break;
case VK_ANSI_H: _oric.set_key_state(Oric::Key::KeyH, isPressed); break;
case VK_ANSI_J: _oric.set_key_state(Oric::Key::KeyJ, isPressed); break;
case VK_ANSI_K: _oric.set_key_state(Oric::Key::KeyK, isPressed); break;
case VK_ANSI_L: _oric.set_key_state(Oric::Key::KeyL, isPressed); break;
case VK_ANSI_Z: _oric.set_key_state(Oric::Key::KeyZ, isPressed); break;
case VK_ANSI_X: _oric.set_key_state(Oric::Key::KeyX, isPressed); break;
case VK_ANSI_C: _oric.set_key_state(Oric::Key::KeyC, isPressed); break;
case VK_ANSI_V: _oric.set_key_state(Oric::Key::KeyV, isPressed); break;
case VK_ANSI_B: _oric.set_key_state(Oric::Key::KeyB, isPressed); break;
case VK_ANSI_N: _oric.set_key_state(Oric::Key::KeyN, isPressed); break;
case VK_ANSI_M: _oric.set_key_state(Oric::Key::KeyM, isPressed); break;
case VK_ANSI_Q: _oric->set_key_state(Oric::Key::KeyQ, isPressed); break;
case VK_ANSI_W: _oric->set_key_state(Oric::Key::KeyW, isPressed); break;
case VK_ANSI_E: _oric->set_key_state(Oric::Key::KeyE, isPressed); break;
case VK_ANSI_R: _oric->set_key_state(Oric::Key::KeyR, isPressed); break;
case VK_ANSI_T: _oric->set_key_state(Oric::Key::KeyT, isPressed); break;
case VK_ANSI_Y: _oric->set_key_state(Oric::Key::KeyY, isPressed); break;
case VK_ANSI_U: _oric->set_key_state(Oric::Key::KeyU, isPressed); break;
case VK_ANSI_I: _oric->set_key_state(Oric::Key::KeyI, isPressed); break;
case VK_ANSI_O: _oric->set_key_state(Oric::Key::KeyO, isPressed); break;
case VK_ANSI_P: _oric->set_key_state(Oric::Key::KeyP, isPressed); break;
case VK_ANSI_A: _oric->set_key_state(Oric::Key::KeyA, isPressed); break;
case VK_ANSI_S: _oric->set_key_state(Oric::Key::KeyS, isPressed); break;
case VK_ANSI_D: _oric->set_key_state(Oric::Key::KeyD, isPressed); break;
case VK_ANSI_F: _oric->set_key_state(Oric::Key::KeyF, isPressed); break;
case VK_ANSI_G: _oric->set_key_state(Oric::Key::KeyG, isPressed); break;
case VK_ANSI_H: _oric->set_key_state(Oric::Key::KeyH, isPressed); break;
case VK_ANSI_J: _oric->set_key_state(Oric::Key::KeyJ, isPressed); break;
case VK_ANSI_K: _oric->set_key_state(Oric::Key::KeyK, isPressed); break;
case VK_ANSI_L: _oric->set_key_state(Oric::Key::KeyL, isPressed); break;
case VK_ANSI_Z: _oric->set_key_state(Oric::Key::KeyZ, isPressed); break;
case VK_ANSI_X: _oric->set_key_state(Oric::Key::KeyX, isPressed); break;
case VK_ANSI_C: _oric->set_key_state(Oric::Key::KeyC, isPressed); break;
case VK_ANSI_V: _oric->set_key_state(Oric::Key::KeyV, isPressed); break;
case VK_ANSI_B: _oric->set_key_state(Oric::Key::KeyB, isPressed); break;
case VK_ANSI_N: _oric->set_key_state(Oric::Key::KeyN, isPressed); break;
case VK_ANSI_M: _oric->set_key_state(Oric::Key::KeyM, isPressed); break;
case VK_Space: _oric.set_key_state(Oric::Key::KeySpace, isPressed); break;
case VK_Return: _oric.set_key_state(Oric::Key::KeyReturn, isPressed); break;
case VK_ANSI_Minus: _oric.set_key_state(Oric::Key::KeyMinus, isPressed); break;
case VK_ANSI_Equal: _oric.set_key_state(Oric::Key::KeyEquals, isPressed); break;
case VK_Space: _oric->set_key_state(Oric::Key::KeySpace, isPressed); break;
case VK_Return: _oric->set_key_state(Oric::Key::KeyReturn, isPressed); break;
case VK_ANSI_Minus: _oric->set_key_state(Oric::Key::KeyMinus, isPressed); break;
case VK_ANSI_Equal: _oric->set_key_state(Oric::Key::KeyEquals, isPressed); break;
case VK_ANSI_Backslash:
_oric.set_key_state(Oric::Key::KeyBackSlash, isPressed); break;
case VK_ANSI_Slash: _oric.set_key_state(Oric::Key::KeyForwardSlash, isPressed); break;
_oric->set_key_state(Oric::Key::KeyBackSlash, isPressed); break;
case VK_ANSI_Slash: _oric->set_key_state(Oric::Key::KeyForwardSlash, isPressed); break;
case VK_ANSI_LeftBracket:
_oric.set_key_state(Oric::Key::KeyOpenSquare, isPressed); break;
_oric->set_key_state(Oric::Key::KeyOpenSquare, isPressed); break;
case VK_ANSI_RightBracket:
_oric.set_key_state(Oric::Key::KeyCloseSquare, isPressed); break;
case VK_ANSI_Quote: _oric.set_key_state(Oric::Key::KeyQuote, isPressed); break;
_oric->set_key_state(Oric::Key::KeyCloseSquare, isPressed); break;
case VK_ANSI_Quote: _oric->set_key_state(Oric::Key::KeyQuote, isPressed); break;
case VK_RightArrow: _oric.set_key_state(Oric::Key::KeyRight, isPressed); break;
case VK_LeftArrow: _oric.set_key_state(Oric::Key::KeyLeft, isPressed); break;
case VK_DownArrow: _oric.set_key_state(Oric::Key::KeyDown, isPressed); break;
case VK_UpArrow: _oric.set_key_state(Oric::Key::KeyUp, isPressed); break;
case VK_RightArrow: _oric->set_key_state(Oric::Key::KeyRight, isPressed); break;
case VK_LeftArrow: _oric->set_key_state(Oric::Key::KeyLeft, isPressed); break;
case VK_DownArrow: _oric->set_key_state(Oric::Key::KeyDown, isPressed); break;
case VK_UpArrow: _oric->set_key_state(Oric::Key::KeyUp, isPressed); break;
case VK_Delete: _oric.set_key_state(Oric::Key::KeyDelete, isPressed); break;
case VK_Escape: _oric.set_key_state(Oric::Key::KeyEscape, isPressed); break;
case VK_Delete: _oric->set_key_state(Oric::Key::KeyDelete, isPressed); break;
case VK_Escape: _oric->set_key_state(Oric::Key::KeyEscape, isPressed); break;
case VK_ANSI_Comma: _oric.set_key_state(Oric::Key::KeyComma, isPressed); break;
case VK_ANSI_Period: _oric.set_key_state(Oric::Key::KeyFullStop, isPressed); break;
case VK_ANSI_Comma: _oric->set_key_state(Oric::Key::KeyComma, isPressed); break;
case VK_ANSI_Period: _oric->set_key_state(Oric::Key::KeyFullStop, isPressed); break;
case VK_ANSI_Semicolon:
_oric.set_key_state(Oric::Key::KeySemiColon, isPressed); break;
case VK_ANSI_Semicolon: _oric->set_key_state(Oric::Key::KeySemiColon, isPressed); break;
case VK_Shift: _oric.set_key_state(Oric::Key::KeyLeftShift, isPressed); break;
case VK_RightShift: _oric.set_key_state(Oric::Key::KeyRightShift, isPressed); break;
case VK_Control: _oric.set_key_state(Oric::Key::KeyControl, isPressed); break;
case VK_Shift:
_oric->set_key_state(Oric::Key::KeyLeftShift, isPressed);
_oric->set_key_state(Oric::Key::KeyRightShift, isPressed);
break;
case VK_RightShift: _oric->set_key_state(Oric::Key::KeyRightShift, isPressed); break;
case VK_Control: _oric->set_key_state(Oric::Key::KeyControl, isPressed); break;
case VK_ANSI_Grave:
case VK_F12: _oric.set_key_state(Oric::Key::KeyNMI, isPressed); break;
case VK_F12: _oric->set_key_state(Oric::Key::KeyNMI, isPressed); break;
default:
printf("%02x\n", key);
@@ -135,9 +130,10 @@
}
}
- (void)clearAllKeys
{
_oric.clear_all_keys();
- (void)clearAllKeys {
@synchronized(self) {
_oric->clear_all_keys();
}
}
#pragma mark - Options
@@ -145,14 +141,14 @@
- (void)setUseFastLoadingHack:(BOOL)useFastLoadingHack {
@synchronized(self) {
_useFastLoadingHack = useFastLoadingHack;
_oric.set_use_fast_tape_hack(useFastLoadingHack ? true : false);
_oric->set_use_fast_tape_hack(useFastLoadingHack ? true : false);
}
}
- (void)setUseCompositeOutput:(BOOL)useCompositeOutput {
@synchronized(self) {
_useCompositeOutput = useCompositeOutput;
_oric.set_output_device(useCompositeOutput ? Outputs::CRT::Television : Outputs::CRT::Monitor);
_oric->set_output_device(useCompositeOutput ? Outputs::CRT::Television : Outputs::CRT::Monitor);
}
}

View File

@@ -28,6 +28,8 @@ typedef NS_ENUM(NSInteger, CSVic20MemorySize)
@interface CSVic20 : CSMachine <CSKeyboardMachine, CSFastLoading>
- (instancetype)init;
@property (nonatomic, assign) BOOL useFastLoadingHack;
@property (nonatomic, assign) CSVic20Country country;
@property (nonatomic, assign) CSVic20MemorySize memorySize;

View File

@@ -13,22 +13,24 @@
#include "G64.hpp"
#include "D64.hpp"
#import "CSmachine+Subclassing.h"
#import "NSBundle+DataResource.h"
using namespace Commodore::Vic20;
@implementation CSVic20 {
Machine _vic20;
std::unique_ptr<Machine> _vic20;
BOOL _joystickMode;
}
- (CRTMachine::Machine * const)machine { return &_vic20; }
- (NSString *)userDefaultsPrefix { return @"vic20"; }
- (instancetype)init {
self = [super init];
if(self)
{
Machine *machine = Commodore::Vic20::Machine::Vic20();
self = [super initWithMachine:machine];
if(self) {
_vic20.reset(machine);
[self setDriveROM:[[NSBundle mainBundle] dataForResource:@"1540" withExtension:@"bin" subdirectory:@"ROMImages/Commodore1540"]];
[self setBASICROM:[self rom:@"basic"]];
[self setCountry:CSVic20CountryEuropean];
@@ -36,8 +38,7 @@ using namespace Commodore::Vic20;
return self;
}
- (NSData *)rom:(NSString *)name
{
- (NSData *)rom:(NSString *)name {
return [[NSBundle mainBundle] dataForResource:name withExtension:@"bin" subdirectory:@"ROMImages/Vic20"];
}
@@ -45,7 +46,7 @@ using namespace Commodore::Vic20;
- (void)setROM:(nonnull NSData *)rom slot:(ROMSlot)slot {
@synchronized(self) {
_vic20.set_rom(slot, rom.length, (const uint8_t *)rom.bytes);
_vic20->set_rom(slot, rom.length, (const uint8_t *)rom.bytes);
}
}
@@ -120,32 +121,26 @@ using namespace Commodore::Vic20;
// KeyPlus
// KeyGBP
if(key == VK_Tab && isPressed)
{
if(key == VK_Tab && isPressed) {
_joystickMode ^= YES;
}
@synchronized(self) {
if(_joystickMode)
{
switch(key)
{
case VK_UpArrow: _vic20.set_joystick_state(JoystickInput::Up, isPressed); break;
case VK_DownArrow: _vic20.set_joystick_state(JoystickInput::Down, isPressed); break;
case VK_LeftArrow: _vic20.set_joystick_state(JoystickInput::Left, isPressed); break;
case VK_RightArrow: _vic20.set_joystick_state(JoystickInput::Right, isPressed); break;
case VK_ANSI_A: _vic20.set_joystick_state(JoystickInput::Fire, isPressed); break;
if(_joystickMode) {
switch(key) {
case VK_UpArrow: _vic20->set_joystick_state(JoystickInput::Up, isPressed); break;
case VK_DownArrow: _vic20->set_joystick_state(JoystickInput::Down, isPressed); break;
case VK_LeftArrow: _vic20->set_joystick_state(JoystickInput::Left, isPressed); break;
case VK_RightArrow: _vic20->set_joystick_state(JoystickInput::Right, isPressed); break;
case VK_ANSI_A: _vic20->set_joystick_state(JoystickInput::Fire, isPressed); break;
}
}
else
{
switch(key)
{
} else {
switch(key) {
default: {
NSNumber *targetKey = vicKeysByKeys[@(key)];
if(targetKey)
{
_vic20.set_key_state((Key)targetKey.integerValue, isPressed);
_vic20->set_key_state((Key)targetKey.integerValue, isPressed);
}
else
{
@@ -155,8 +150,8 @@ using namespace Commodore::Vic20;
case VK_Shift:
// Yuck
_vic20.set_key_state(Key::KeyLShift, isPressed);
_vic20.set_key_state(Key::KeyRShift, isPressed);
_vic20->set_key_state(Key::KeyLShift, isPressed);
_vic20->set_key_state(Key::KeyRShift, isPressed);
break;
}
}
@@ -165,7 +160,7 @@ using namespace Commodore::Vic20;
- (void)clearAllKeys {
@synchronized(self) {
_vic20.clear_all_keys();
_vic20->clear_all_keys();
}
}
@@ -174,7 +169,7 @@ using namespace Commodore::Vic20;
- (void)setUseFastLoadingHack:(BOOL)useFastLoadingHack {
_useFastLoadingHack = useFastLoadingHack;
@synchronized(self) {
_vic20.set_use_fast_tape_hack(useFastLoadingHack ? true : false);
_vic20->set_use_fast_tape_hack(useFastLoadingHack ? true : false);
}
}
@@ -182,8 +177,7 @@ using namespace Commodore::Vic20;
_country = country;
NSString *charactersROM, *kernelROM;
Commodore::Vic20::Region region;
switch(country)
{
switch(country) {
case CSVic20CountryDanish:
region = Commodore::Vic20::Region::PAL;
charactersROM = @"characters-danish";
@@ -212,7 +206,7 @@ using namespace Commodore::Vic20;
}
@synchronized(self) {
_vic20.set_region(region);
_vic20->set_region(region);
[self setCharactersROM:[self rom:charactersROM]];
[self setKernelROM:[self rom:kernelROM]];
}
@@ -222,9 +216,9 @@ using namespace Commodore::Vic20;
_memorySize = memorySize;
@synchronized(self) {
switch(memorySize) {
case CSVic20MemorySize5Kb: _vic20.set_memory_size(Commodore::Vic20::Default); break;
case CSVic20MemorySize8Kb: _vic20.set_memory_size(Commodore::Vic20::ThreeKB); break;
case CSVic20MemorySize32Kb: _vic20.set_memory_size(Commodore::Vic20::ThirtyTwoKB); break;
case CSVic20MemorySize5Kb: _vic20->set_memory_size(Commodore::Vic20::Default); break;
case CSVic20MemorySize8Kb: _vic20->set_memory_size(Commodore::Vic20::ThreeKB); break;
case CSVic20MemorySize32Kb: _vic20->set_memory_size(Commodore::Vic20::ThirtyTwoKB); break;
}
}
}

View File

@@ -0,0 +1,16 @@
//
// CSZX8081+Instantiation.h
// Clock Signal
//
// Created by Thomas Harte on 27/08/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "StaticAnalyser.hpp"
#import "CSZX8081.h"
@interface CSZX8081 (Instantiation)
- (instancetype)initWithIntendedTarget:(const StaticAnalyser::Target &)target;
@end

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