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

Compare commits

...

591 Commits

Author SHA1 Message Date
Thomas Harte
fd10c42433 Merge pull request #263 from TomHarte/WriteableDSK
Makes CPC-style .DSK files writeable
2017-11-03 21:59:06 -04:00
Thomas Harte
794437f20f Corrects fixed buffer size error in FileHolder::check_signature. 2017-11-03 21:43:31 -04:00
Thomas Harte
23d5849cda Attempts to map recognised [M]FM errors back to FDC status codes. 2017-11-03 21:29:42 -04:00
Thomas Harte
5070a8414f Improves FileHolder documentation 2017-11-03 21:29:15 -04:00
Thomas Harte
5a3ca0e447 Adds output for modified CPC DSKs. 2017-11-03 21:10:22 -04:00
Thomas Harte
e384c50580 Switches FileHolder to have a usage much closer to FILE *.
Thereby opens a route for file format implementations such as that appearing for CPC DSK that create an in-memory copy and perform a full rewrite.
2017-11-02 22:32:00 -04:00
Thomas Harte
b9734278f6 Provides an up-front evaluation of performance versus objectices via README.MD. 2017-11-02 12:22:27 -04:00
Thomas Harte
f807a6b608 Generalises the concept of multiple samplings of an FM/MFM sector, simplifying CPC DSK support and paving the way for generic weak/fuzzy bit support. 2017-10-31 21:32:28 -04:00
Thomas Harte
833f8c02a4 Switches the CPC DSK implementation to building an in-memory version of the structure up front.
Preparatory to making these things writeable.
2017-10-31 19:41:16 -04:00
Thomas Harte
0248c6a282 Merge pull request #262 from TomHarte/BookEnds
Adds a facility for 'bookending' data runs, eliminating an occasional Electron rendering error
2017-10-23 18:36:54 -04:00
Thomas Harte
218b976dbc Adds through route for setting a texture bookender, and exploits it from the Electron. 2017-10-23 18:35:37 -04:00
Thomas Harte
513903890e Corrects definition of Bookender and provides the default implementation. 2017-10-22 17:24:41 -04:00
Thomas Harte
1157bde453 Sketches interface for a GPU data bookender, to avoid stray errors with packed pixel formats. 2017-10-22 10:48:10 -04:00
Thomas Harte
46345c6a3e Merge pull request #261 from TomHarte/UIntCasts
Continues the process of conversion to functional casts.
2017-10-21 22:53:14 -04:00
Thomas Harte
c13f8e5390 Corrects a couple of cast conversion errors. 2017-10-21 22:42:19 -04:00
Thomas Harte
ad9df4bb90 Commutes uint8_t *, uint16_t *, uint32_t *, size_t, off_t and long to functional-style casts. 2017-10-21 22:30:15 -04:00
Thomas Harte
e983854e71 Converts all uint8_t and uint16_t casts to the functional style. 2017-10-21 21:50:53 -04:00
Thomas Harte
ec999446e8 Commutes int and unsigned casts to the functional style. 2017-10-21 21:00:40 -04:00
Thomas Harte
5e3e91373a Switches all unsigned int and double casts to functional style. 2017-10-21 19:49:04 -04:00
Thomas Harte
c52348d8d7 Merge pull request #260 from TomHarte/KeyboardCleanup
Cleans up after keyboard formalisation.
2017-10-21 10:53:46 -04:00
Thomas Harte
9e0907ee76 Completes clean-up of post-formalisation per-machine keyboard code.
At least for now. Standardising on how column + row is encoded might be helpful.
2017-10-21 10:52:35 -04:00
Thomas Harte
9ad4025138 Relocates things that were in Machines/ for machine usage.
Leaving only those things intended to be visible interface.
2017-10-21 10:30:02 -04:00
Thomas Harte
405f58d6a3 Corrects write guard names. 2017-10-21 10:21:40 -04:00
Thomas Harte
afbd1c425c Merge pull request #259 from TomHarte/Vic20Keyboard
Consolidates Vic-20 keyboard code.
2017-10-19 22:28:11 -04:00
Thomas Harte
b2c1b83fcd Consolidates Vic-20 keyboard code. 2017-10-19 22:27:30 -04:00
Thomas Harte
8d2b9a581a Merge pull request #256 from TomHarte/UniversalInput
Standardises the host-side interface for joystick and keyboard input
2017-10-19 22:15:47 -04:00
Thomas Harte
1825af0dd3 Eliminates dead code in the Vic-20 and Inputs::Joystick. 2017-10-19 22:15:21 -04:00
Thomas Harte
c2f6799f0c Implements Vic-20 restore key. 2017-10-19 22:02:34 -04:00
Thomas Harte
b5b6219cb7 Slightly simplifies TextureBuilder arithmetic. 2017-10-19 22:02:00 -04:00
Thomas Harte
185a699279 Fixes off-by-one keyboard state accumulation error. 2017-10-19 22:01:24 -04:00
Thomas Harte
96b8f9ae9f Merge branch 'master' into UniversalInput 2017-10-17 22:54:17 -04:00
Thomas Harte
88e2350b8f Prevents undefined behaviour from the CPC's timer. 2017-10-17 22:53:52 -04:00
Thomas Harte
5c141af734 Prevents undefined behaviour from the CPC's timer. 2017-10-17 22:40:32 -04:00
Thomas Harte
da580e4186 Merge branch 'master' into UniversalInput 2017-10-17 22:36:22 -04:00
Thomas Harte
57ee09dffb Merge pull request #258 from TomHarte/UndefinedBehaviour
Corrects large swathes of undefined behaviour
2017-10-17 22:35:59 -04:00
Thomas Harte
7c8e830b90 Adjusted the Acorn tape parser to avoid signed left shifts. 2017-10-17 22:34:49 -04:00
Thomas Harte
ba5f668338 Ensured full CRT instance initialisation. 2017-10-17 22:34:10 -04:00
Thomas Harte
2c1e99858b Fixed HalfCycles to allow conversion from Cycles without relying on undefined behaviour.
Specifically: left shifting a negative number.
2017-10-17 22:22:51 -04:00
Thomas Harte
7f2febeec9 Ensures complete DPLL initial state assignment. 2017-10-17 22:13:37 -04:00
Thomas Harte
2d7a4fe5f0 Switches the MFM shifter to unsigned accumulation.
Since left shifting signed numbers is undefined behaviour.
2017-10-17 22:12:04 -04:00
Thomas Harte
91b867a7b3 Ensures full 8272 instance state initialisation. 2017-10-17 22:11:01 -04:00
Thomas Harte
3944e734d3 Ensures full 6845 instance state initialisation and uses an unsigned shifter. 2017-10-17 22:10:28 -04:00
Thomas Harte
ce78d9d12c Introduces buffer alignment when writing to textures.
To avoid cross-boundary writes and hopefully to eke out a little better performance.
2017-10-17 22:09:48 -04:00
Thomas Harte
edbc60a3fb Various undefined behaviour fixes.
Primarily around uninitialised variables, but also with an attempted use of a negative pointer.
2017-10-17 21:29:19 -04:00
Thomas Harte
6ea3ff62df Merge branch 'master' into UniversalInput 2017-10-17 21:28:40 -04:00
Thomas Harte
88959571f1 Merge pull request #257 from TomHarte/CPMReading
Corrects CPM reader buffer overwrites
2017-10-17 20:54:02 -04:00
Thomas Harte
b4583e976e Corrects buffer overwrites resulting from failure to treat a number of records of 0x80 as a special case. 2017-10-17 20:52:16 -04:00
Thomas Harte
92d9805f09 Removes dead Objective-C protocol references. 2017-10-17 20:51:40 -04:00
Thomas Harte
0c2dd62328 Various undefined behaviour fixes.
Primarily around uninitialised variables, but also with an attempted use of a negative pointer.
2017-10-17 20:50:46 -04:00
Thomas Harte
3f4d90d775 Corrects buffer overwrites resulting from failure to treat a number of records of 0x80 as a special case. 2017-10-17 20:49:12 -04:00
Thomas Harte
542ec4312f Switched the Objective-C code to using dynamic_cast alone to decide whether to post keyboard or joystick events. 2017-10-15 21:25:56 -04:00
Thomas Harte
18798c9886 Corrects joystick memory leaks. 2017-10-15 20:49:47 -04:00
Thomas Harte
7aaf27389c Commutes the Atari 2600 to the JoystickMachine interface. 2017-10-15 20:44:59 -04:00
Thomas Harte
ee179aa7bd Introduces a joystick analogue to the shared keyboard interface, and implements it for the Vic-20. 2017-10-14 22:36:31 -04:00
Thomas Harte
3a05ce36de Adds a reference to the calling keyboard in reset_all_keys. 2017-10-14 22:07:11 -04:00
Thomas Harte
4f289ab10b Corrects some deficiencies in Vic-20 keyboard mapping.
... albeit without yet being clear on the wiring behind restore.
2017-10-12 22:33:00 -04:00
Thomas Harte
78ee46270b Transfers possession of keyboard mappings from the Mac side over to individual machines.
Specifically by establishing an intermediate representation of a useful mix between the American and British IBM and Mac keyboard layouts, and routing through that.
2017-10-12 22:25:02 -04:00
Thomas Harte
edb632af52 Sketches first design for generalising keyboard input. 2017-10-09 22:26:39 -04:00
Thomas Harte
19c03a08a6 Merge pull request #255 from TomHarte/BatchDriveUpdates
Rewires so as to give disk images visibility of large change sets rather than per-sector track rewrites.
2017-10-07 19:42:14 -04:00
Thomas Harte
44cdc124af Switches to providing a full record of changes to disk images, rather than feeding them a track at a time.
Gets explicit about `override`s while doing so, to ensure full adaptation.
2017-10-07 19:37:36 -04:00
Thomas Harte
b37787a414 Ensures lifetime-linked track flushing without relying on virtual calls within a destructor. 2017-10-07 19:14:18 -04:00
Thomas Harte
53b99ea248 Uses Disk::flush_tracks to elide replacement of dirty tracks. 2017-10-06 22:07:42 -04:00
Thomas Harte
97a2be71e3 Introduces flush_tracks to Drive, while switching its interface to using Track::Address and adjusting associated integer types. 2017-10-06 21:45:12 -04:00
Thomas Harte
f623bff5c3 Removes unnecessary call. 2017-10-06 18:48:51 -04:00
Thomas Harte
2511fc8401 Merge pull request #254 from TomHarte/C++BestEffortUpdater
Commutes the best-effort updater into C++11.
2017-10-05 18:28:46 -04:00
Thomas Harte
d37ec9e5b0 Attempts to ensure good behaviour if dealt an adjustable clock, and consts where possible. 2017-10-05 18:23:56 -04:00
Thomas Harte
95c82f5b36 Merge branch 'C++BestEffortUpdater' of github.com:TomHarte/CLK into C++BestEffortUpdater 2017-10-05 18:17:52 -04:00
Thomas Harte
ec202ed8be Merge branch 'master' into C++BestEffortUpdater 2017-10-05 18:17:35 -04:00
Thomas Harte
7190225603 Merge branch 'master' into C++BestEffortUpdater 2017-10-05 18:12:33 -04:00
Thomas Harte
52e7cabd4e Merge pull request #253 from TomHarte/Swift4UnitTests
Removes usages of deprecated Swift initialiser within unit tests.
2017-10-05 18:12:12 -04:00
Thomas Harte
064f1dfdbc Removes usages of deprecated initialiser. 2017-10-05 18:10:47 -04:00
Thomas Harte
f40e1fd840 Commutes the best-effort updater into C++11. 2017-10-05 18:09:58 -04:00
Thomas Harte
e194a2a015 Removes usages of deprecated initialiser. 2017-10-05 16:45:13 -04:00
Thomas Harte
c39759333a Merge pull request #252 from TomHarte/Casts
Begins this project's conversion to functional-style casts.
2017-10-03 22:05:22 -04:00
Thomas Harte
edb9fd301c Begins this project's conversion to functional-style casts. 2017-10-03 22:04:15 -04:00
Thomas Harte
ea5023ac26 Merge pull request #251 from TomHarte/HFEWriteable
Makes HFE files writeable
2017-10-03 21:32:05 -04:00
Thomas Harte
0fb363ea0e Adds writing support for HFEs. 2017-10-03 21:24:20 -04:00
Thomas Harte
1cc85615d5 Factors HFE track seeking out from the track fetching method. 2017-10-03 20:33:55 -04:00
Thomas Harte
7b01c1bee6 Revokes direct visibility of is_read_only_ to subclasses of FileHolder. 2017-10-03 19:36:06 -04:00
Thomas Harte
35705c5345 Factors out bit reversing from the HFE class. 2017-10-03 19:12:45 -04:00
Thomas Harte
f41da83d97 Seeks to eliminate race conditions on the best-effort updater. 2017-09-30 21:34:43 -04:00
Thomas Harte
cd1e5dea4d Merge pull request #250 from TomHarte/TrackToBits
Refactors MFM support, breaking it into components
2017-09-30 20:31:43 -04:00
Thomas Harte
ef605eda51 Factors out commonalities in SSD/DSD and ADF implementations. 2017-09-30 20:30:15 -04:00
Thomas Harte
2f48ee59fa Merge branch 'TrackToBits' of github.com:TomHarte/CLK into TrackToBits 2017-09-30 20:12:56 -04:00
Thomas Harte
f86729c4ac Ensures safe machine release upon window closure. 2017-09-30 20:12:46 -04:00
Thomas Harte
5f99f4442c Ensures safe machine release upon window closure. 2017-09-30 20:07:04 -04:00
Thomas Harte
326857a84d Corrects FM/MFM selection when looking for sectors. 2017-09-29 22:48:00 -04:00
Thomas Harte
5dd3945695 Factors out the more egregious similarities between ADF and SSD. 2017-09-29 22:07:23 -04:00
Thomas Harte
19eb975c73 Adds an intermediate step in CP/M directory parsing.
To reduce amount of time spent allocating and reallocating buffers.
2017-09-29 21:38:16 -04:00
Thomas Harte
698ffca51b Recasts the [M]FM parser in terms of the new factoring.
Temporarily breaks SSD writing support.
2017-09-29 20:08:36 -04:00
Thomas Harte
fe3cc5c57c Removes dead pragma. 2017-09-28 20:47:25 -04:00
Thomas Harte
f488854720 Switches Oric MFM DSK serialisation to feeding a track serialisation to a shifter.
Thereby eliminates the parser's need to offer get_track.
2017-09-27 22:14:50 -04:00
Thomas Harte
51c0c45e04 Turns MFM bit length into a globally-available constant. 2017-09-27 21:30:09 -04:00
Thomas Harte
c3e1489a8e Introduces Track::Address, a parallel to Sector::Address to enable more uniform storage. 2017-09-27 21:29:06 -04:00
Thomas Harte
e3420f62c6 Switches the Acorn ADF implementation to using the new track_serialisation/sectors_from_segment route for decomposition of a track into sectors. 2017-09-26 22:05:33 -04:00
Thomas Harte
970c80f2e3 Adds TrackSerialiser.cpp to the project and reorders section. 2017-09-26 22:03:42 -04:00
Thomas Harte
9f4a407f94 Switches the track serialiser to a more standard header + implementation separation.
Also introduces a full priming of the PLL before deserialisation begins.
2017-09-26 22:01:32 -04:00
Thomas Harte
5dda897334 Changes function name to sector_size — into line with idioms. 2017-09-26 22:00:19 -04:00
Thomas Harte
3982e375e3 Introduces a route from a PCMSegment to a list of [M]FM sectors. 2017-09-25 19:57:11 -04:00
Thomas Harte
a8524daecb Marks the move constructor as noexcept, to improve usage with vector. 2017-09-25 19:53:22 -04:00
Thomas Harte
d1ce764201 Provides SectorsFromSegment, a bitstream to sector converter. 2017-09-24 22:41:16 -04:00
Thomas Harte
8875982e1f Ensures Sectors are move constructible (and still default constructible), and adds proper const qualifiers to Sector::Address. 2017-09-24 22:40:38 -04:00
Thomas Harte
3319a4f589 Isolates those Sector fields that describe its address and makes them usable as a set key. 2017-09-24 21:57:21 -04:00
Thomas Harte
c7f27b2db4 Renames MFM.[c/h]pp as per its new remit: encoding only. 2017-09-24 21:40:43 -04:00
Thomas Harte
631f630549 Severs the MFM parser from the overweight single MFM.hpp. 2017-09-24 20:31:19 -04:00
Thomas Harte
2a08bd9ecc Factors shifting plus stateful [M]FM token recognition out of the MFMDiskController.
Given the proliferation of MFM-related classes, establishes a subdirectory for them.
2017-09-24 20:07:56 -04:00
Thomas Harte
f789ee4ff0 Introduces a track to segment decoder.
This will be needed to make formats like G64 and HFE writeable, but probably also will be usable to speed up static analysis.
2017-09-23 22:39:19 -04:00
Thomas Harte
a295b42497 Merge pull request #248 from TomHarte/BetterCPCShot
Adds a better example of correct-aspect-ratio CPC output
2017-09-22 23:05:01 -04:00
Thomas Harte
d8337492cc Bowdlerised images. 2017-09-22 23:02:17 -04:00
Thomas Harte
15c8debc16 Added larger CPC screenshots. 2017-09-22 22:58:18 -04:00
Thomas Harte
67af153c16 Merge pull request #247 from TomHarte/WriteableHFE
Cleans up the `Disk` hierarchy
2017-09-22 22:55:22 -04:00
Thomas Harte
d72dad2d1a Severs the DiskImage implementation from its public header file. 2017-09-22 22:46:31 -04:00
Thomas Harte
698e4fe550 Tidies the Disk file hierarchy. 2017-09-22 22:39:23 -04:00
Thomas Harte
b5406b90cd Introduces a new class hierarchy for disk images.
Increasing independence of format-specific stuff and generic caching without mangling them into a common namespace, and allowing in some cases for a decrease in read/write blocking.
2017-09-22 20:28:11 -04:00
Thomas Harte
05a93ba237 Merge pull request #246 from TomHarte/MainThreadBackingSize
Ensures self.bounds and -convertSizeToBacking: are called only on the main queue.
2017-09-20 20:00:45 -04:00
Thomas Harte
77548d14db Ensures self.bounds and -convertSizeToBacking: are called only on the main queue. 2017-09-20 19:59:34 -04:00
Thomas Harte
b85dd608e7 Merge pull request #245 from TomHarte/Xcode9
Updates to Swift 4 and Xcode 9's recommended project settings.
2017-09-20 19:54:31 -04:00
Thomas Harte
231f13d810 Updates to Swift 4 and Xcode 9's recommended project settings. 2017-09-19 23:06:37 -04:00
Thomas Harte
704bfa114c Merge pull request #244 from TomHarte/FasterStartup
Improves CPC analysis times
2017-09-16 22:07:43 -04:00
Thomas Harte
44a56724cb Speeds up byte decoding within sectors for the ahead-of-time MFM parser. 2017-09-16 20:28:24 -04:00
Thomas Harte
5fbea625ae Switches the CPC static analyser to maintaining a vector of pointers rather than a complete copy of files.
Hence saves a lot of copying and moving — around a second's worth when dealing with the selected test disk.
2017-09-16 20:15:06 -04:00
Thomas Harte
ac57b37e96 Eliminates repetition of the 'untypable character' test. 2017-09-16 19:46:41 -04:00
Thomas Harte
e3e9baeaa4 Merge pull request #243 from TomHarte/Detection
Adds a test that file extension also be typeable.
2017-09-16 19:11:53 -04:00
Thomas Harte
e071123f90 Adds a test that file extension also be typeable. 2017-09-16 19:10:17 -04:00
Thomas Harte
98adb01721 Merge pull request #242 from TomHarte/8272ReadyInterruption
Improves CPC disk emulation
2017-09-16 18:28:00 -04:00
Thomas Harte
d6a5f9a29e Revokes unnecessary change. 2017-09-16 18:24:13 -04:00
Thomas Harte
0d84b4b9dd Removes some redundant end_writing calls. 2017-09-16 17:09:17 -04:00
Thomas Harte
a85909198f Adds defences against double calls to end writing. 2017-09-16 17:07:36 -04:00
Thomas Harte
98751e6ac8 Ensures that all result phases are exactly the intended length by replacing accumulation with assignment.
Also attempts a different version of control mark behaviour. Experiments.
2017-09-15 22:59:26 -04:00
Thomas Harte
da082673d7 Drives now have a finite number of heads.
The Amstrad volunteers itself to be single sided. Everything else stays as it was.
2017-09-15 21:18:36 -04:00
Thomas Harte
35fe4d50d4 Adds command termination upon drive becoming unready, and copies head and drive selection into ST0. 2017-09-15 20:26:41 -04:00
Thomas Harte
b835cb73e2 Merge pull request #241 from TomHarte/DriveEvents
Devolves `TimedEventLoop` ownership from disk controllers to drives
2017-09-15 19:15:44 -04:00
Thomas Harte
662d031e3c Adds exposition on the meaning of a disk controller being in write mode. 2017-09-15 19:14:36 -04:00
Thomas Harte
bf20c717fb The Drive now no longer produces input when in writing mode — other than announcing the index hole. 2017-09-14 22:32:13 -04:00
Thomas Harte
4d4a0cf1d2 Puts the disk controller back into the loop with knowledge about reading mode, and uses that knowledge to cut off the PLL. 2017-09-14 22:30:40 -04:00
Thomas Harte
b62f3e726a Adds a start-of-execution-phase get-out for drives that aren't ready. 2017-09-12 20:43:53 -04:00
Thomas Harte
82b13e98f2 Implements the real hardware ready test for Drives — motor on plus two index holes. 2017-09-11 22:27:50 -04:00
Thomas Harte
9ac831b09c Added an additional protection against overflow. 2017-09-11 22:24:24 -04:00
Thomas Harte
42616da7ff Adjusts the Oric Microdisc to propagate motor control more widely. 2017-09-11 22:15:54 -04:00
Thomas Harte
2f13517f38 Adjusts the 1770 not to talk directly to the drive about motor status. 2017-09-11 22:10:56 -04:00
Thomas Harte
fb9fd26af7 Updates the 1540 for the slightly-more modern world of decoupled drives and disks (!). 2017-09-11 22:08:10 -04:00
Thomas Harte
d3c385b471 Separates the 8272's drive selection signalling from actual drive ownership.
Thereby returns working motor control to the CPC.
2017-09-11 21:25:26 -04:00
Thomas Harte
96bf133924 Withdraws requirement for DiskController users to specify a PLL multiplier or to provide rotation speed.
In the latter case because it's no longer of any interest to the controller, and in the former because I'd rather it be picked automatically.
2017-09-10 22:56:05 -04:00
Thomas Harte
6d6cac429d Fixes extra time accumulation during track running.
Introduces a bunch of further asserts, which aided me in determining the fix, i.e. that Drives being responsible for their own setup_track could double-pump the event loop.
2017-09-10 22:44:14 -04:00
Thomas Harte
dc0b65f9c9 Corrects initial event loop timing state. 2017-09-10 20:51:21 -04:00
Thomas Harte
8882aa496f Corrected wiring to get advance signals through to Drive event delegates. 2017-09-10 20:51:05 -04:00
Thomas Harte
0622187ddf Strips Controller of all capabilities now housed on the Drive. 2017-09-10 19:23:23 -04:00
Thomas Harte
523e1288fa Updates the MFM parser to use SingleTrackDisk rather than the equivalent withdrawn Drive functionality. 2017-09-10 17:34:52 -04:00
Thomas Harte
1a96cce26f Implements SingleTrackDisk, a Disk that contains only a single, specified, track. 2017-09-10 17:34:14 -04:00
Thomas Harte
a4e275e1fc Provides an implementation of Drive's new interface.
Mostly lifted from DiskController. `set_disk_with_track` has been withdrawn in favour of providing a suitable wrapper `Disk` subclass, as being an unnecessary complexity and intermingling of concerns.
2017-09-10 17:33:01 -04:00
Thomas Harte
6075064400 Adds the ability to query a TimedEventLoop for its input clock rate. 2017-09-10 17:31:43 -04:00
Thomas Harte
ff6e65cca9 Introduces necessary storage and interface for writing. 2017-09-10 16:23:31 -04:00
Thomas Harte
90d2347c90 Extended to permit subclasses that are interested to get sub-run_for information about event times. 2017-09-10 14:44:38 -04:00
Thomas Harte
90c7056d12 Started devolving timed event loop logic down to the drives, moving them closer to modelling real life. 2017-09-10 14:43:20 -04:00
Thomas Harte
fed2bc9fc9 Merge pull request #240 from TomHarte/C1540
Simplifies the published C1540 interface and corrects a transcription bug.
2017-09-05 21:22:25 -04:00
Thomas Harte
ff510f3b84 Explicitly disallows copying of VIAs, and marks the constructor as noexcept. 2017-09-05 21:21:23 -04:00
Thomas Harte
3b12fca417 Corrects non-recurring-pattern adaptation bug: the 'SerialPortVIA' should keep a reference to its VIA, not a copy of it. 2017-09-05 21:19:56 -04:00
Thomas Harte
8eeb7e73cd Adds a commented-out printf that I might like to use again later. 2017-09-05 21:15:56 -04:00
Thomas Harte
7fd6699e0b Corrects comment indentation. 2017-09-05 21:15:15 -04:00
Thomas Harte
ed70b15fc9 Merge pull request #239 from TomHarte/6522Tests
Corrects 6522 bridge per has-a-not-is-a template switch.
2017-09-04 21:58:07 -04:00
Thomas Harte
ff24e1de31 Corrects 6522 bridge per has-a-not-is-a template switch. 2017-09-04 21:56:21 -04:00
Thomas Harte
6547102511 Attempts better to hide C1540 implementation details from the reader.
In this case not from the compiler, as it's desireable to keep `run_for` as a non-virtual call, and therefore everything else comes alone for the ride.
2017-09-04 20:58:00 -04:00
Thomas Harte
d538ff5039 Merge pull request #238 from TomHarte/C1540
Minor tweaks to re-enable proper file selection in the Vic-20.
2017-09-04 20:56:54 -04:00
Thomas Harte
a49594c6a3 Tweaks Vic20 Machine parent class order so that when turned into a CRTMachine, still successfully dynamically casts as a ConfigurationTarget.
More thorough thought is required.
2017-09-04 20:56:00 -04:00
Thomas Harte
3544c0f014 Switches from testing size() != 0 to empty() != true.
Partly as size() is O(n) but empty is O(1), but primarily for style.
2017-09-04 20:54:38 -04:00
Thomas Harte
f26fe3756c Merge pull request #237 from TomHarte/6522CleanUp
Significantly cleans up the 6522.
2017-09-04 18:23:33 -04:00
Thomas Harte
a42ca290cb Reformulates the Oric more cleanly into the modern world.
Specifically: now that the implementation is contained within the CPP file, there's no need to embed the keyboard, tape player and VIA port handler as private classes. Also the pain of additional syntax is reduced, so the keyboard has been bumped up to a fully data-hiding class. I've also transferred overall ownership of the tape player, AY and keyboard up to the Oric itself, with the VIA merely being wired to them, and added a whole bunch of extra documentation.
2017-09-04 18:22:14 -04:00
Thomas Harte
da09098e49 Updates clipped area per latest CRT response to vertical sync. 2017-09-04 17:51:02 -04:00
Thomas Harte
450712f39c Improves and corrects 6522 header documentation. 2017-09-04 14:32:34 -04:00
Thomas Harte
24b3faa427 Deconstitutes the 6522 into component parts, templated and non-templated.
Adjusts the Oric, Vic-20 and C-1540 accordingly, albeit with the quickest possible solutions.
2017-09-04 14:26:04 -04:00
Thomas Harte
40d11ea0e3 Merge pull request #236 from TomHarte/CPUSeparation
Further cements CPU file separation.
2017-09-04 11:20:00 -04:00
Thomas Harte
ab2bcb939f Separates 6502Base into its constituent parts. 2017-09-04 11:08:33 -04:00
Thomas Harte
45499050b6 Separates Z80Base.cpp into its component classes. 2017-09-04 11:04:01 -04:00
Thomas Harte
0c9197df30 Merge pull request #235 from TomHarte/Reencapsulation
Further strips back the amount exposed in Z80-related headers.
2017-09-01 22:38:33 -04:00
Thomas Harte
a1e200cc65 Further strips back the amount exposed in Z80-related headers.
Almost all opcode table generation macros and code now resides neatly in the world of .CPP.
2017-09-01 22:19:16 -04:00
Thomas Harte
8a612bb6ab Merge pull request #234 from TomHarte/TidyZ80
Separates interface and implementation of the Z80
2017-09-01 20:54:01 -04:00
Thomas Harte
e6ac939ae0 Reintroduces missing noexcept specifier. 2017-09-01 20:51:31 -04:00
Thomas Harte
b034d4e6f8 Refactors the Z80 to separate out interface and implementation.
Following the pattern just established by the 6502, puts all implementation specifics beyond the visibility of a human reading Z80.hpp and in subfolders so as to promote the idea that they shouldn't go out of their way.
2017-09-01 20:50:24 -04:00
Thomas Harte
de218611e4 Corrects possible confusion as documentation recommends Cycles(0) as default, but then gives Cycles(1). 2017-09-01 20:49:24 -04:00
Thomas Harte
615f7ce176 Merge pull request #233 from TomHarte/BetterYet6502
Removes from 6502.hpp all remaining implementation details.
2017-09-01 19:47:49 -04:00
Thomas Harte
b306776ba9 Removes from 6502.hpp all remaining implementation details, making it purely an interface document.
Though those details remain visible to files including 6502.hpp through necessity.
2017-09-01 19:46:29 -04:00
Thomas Harte
0f85cffc78 Merge pull request #232 from TomHarte/ElectronShift
Ensures all parts of the Electron have a fully-defined initial state.
2017-08-31 22:29:50 -04:00
Thomas Harte
96648df5fe Ensures all parts of the Electron have a fully-defined initial state.
Specifically to resolve an error with shift being pressed at startup due to a failure to establish a default value for that flag, but applying the same principle across the board.
2017-08-31 22:29:24 -04:00
Thomas Harte
2c99a2d6ec Merge pull request #231 from TomHarte/NeaterTemplates
Tidies the 6502 template and folder hierarchy.
2017-08-31 22:17:08 -04:00
Thomas Harte
4af333d5ec Tidies the 6502 template and folder hierarchy.
Specifically: there's now just the one .h file at the top level, giving a clear indication of what a user should read. That separates implementation from interface. It also devolves a lot more to the base class because doing so makes debug builds less of a hassle. The all-RAM 6502 has been shuffled off into a subfolder, to indicate that it's not something you necessarily need know about. Also general documentation improvements have been applied: incorrect citing of the recurring-template pattern has been removed and the meaning of the two BusHandler methods has now accrued at the bus handler.
2017-08-31 22:10:27 -04:00
Thomas Harte
a5f9869769 Merge pull request #230 from TomHarte/CyclicShutdown
Eliminates potential cyclic entry into CSMachine during its `-dealloc`.
2017-08-31 21:23:15 -04:00
Thomas Harte
f10be2a18a Eliminates potential cyclic entry into CSMachine during its -dealloc.
Explicit cause: dealloc calls close_output(). That may decide to flush work, indiscriminately. Some of the flushed work might be audio generation. Audio generation might cause the audio queue to react with an out-of-data announcement. Which would cause a fresh attempt to update the CSMachine.
2017-08-31 21:22:23 -04:00
Thomas Harte
c88d627b4e Merge pull request #229 from TomHarte/Skew
Adds an initial implementation of display skew to the 6845
2017-08-29 22:32:26 -04:00
Thomas Harte
b30bb2a234 Adds an initial implementation of display skew, as a completely live property. 2017-08-29 22:16:40 -04:00
Thomas Harte
d498080eb4 Merge pull request #228 from TomHarte/CRTCStatus
Takes initial steps towards supporting CRTC manufacturer diversity.
2017-08-27 22:26:40 -04:00
Thomas Harte
334afbc710 Removes const from get_status and get_register, as both may now logically mutate the object. 2017-08-27 18:13:55 -04:00
Thomas Harte
17c13624e5 Improved comments. 2017-08-27 18:11:40 -04:00
Thomas Harte
113349d272 Started making some formal admissions that different CRTC models exist. Plenty yet to do. 2017-08-27 18:10:07 -04:00
Thomas Harte
0ced7866fc Merge pull request #227 from TomHarte/NoCPCOptions
Removes the CPC options panel.
2017-08-27 17:12:24 -04:00
Thomas Harte
d06031dfcb Removes the options panel for CPC display. 2017-08-27 17:11:35 -04:00
Thomas Harte
3f22a71276 Merge pull request #226 from TomHarte/TargetAwareness
Substantially rewires Mac-side target selection and as proof-of-concept adapts the generic-side ZX80 to instantiate without wait line support
2017-08-27 16:55:01 -04:00
Thomas Harte
53a88a7e12 Causes the ZX80/81 to omit support for the wait line if being configured as a ZX80. 2017-08-27 16:45:36 -04:00
Thomas Harte
4a66dd9e82 Arranges for the ZX80/81 to get a peek at target configuration prior to construction. I'm as yet undecided on whether to make this the norm. 2017-08-27 16:42:16 -04:00
Thomas Harte
522839143f Revokes -[CSMachine init] and the slightly troubling create-on-demand semantics it places upon subclasses via .machine. Therefore each machine must announce its own implementation of -init. 2017-08-27 16:36:21 -04:00
Thomas Harte
b4c532c0d5 Merge pull request #225 from TomHarte/TargetHints
Factors the concept of a target platform out from the static analyser, allowing file formats to opine
2017-08-27 15:46:55 -04:00
Thomas Harte
a3e2d142e3 Extends UEF support to include chunk 0005, the target platform description, which is exposed via TargetPlatform::TypeDistinguisher. 2017-08-27 15:43:09 -04:00
Thomas Harte
63ee8c9d58 Uses file containers' type distinguishers where available, and supplies potential insight to the ZX80/81 analyser as now required. 2017-08-27 15:20:58 -04:00
Thomas Harte
437023bff6 Expands to take an already-accrued list of potential platforms, as that may indicate that one or the other of the ZX80 and ZX81 is already out of contention and therefore save the need to attempt analysis. 2017-08-27 15:20:22 -04:00
Thomas Harte
4465098157 Since it has descendants, gives Storage::Cartridge a virtual destructor. 2017-08-27 15:19:30 -04:00
Thomas Harte
56dd677e9c Creates a virtual interface that can be adopted by classes that are able to provide some insight as to target machine. 2017-08-27 15:19:03 -04:00
Thomas Harte
9aa150c338 Abstracts the target platform type out from the static analyser's ownership. 2017-08-27 15:02:13 -04:00
Thomas Harte
fab6908129 Corrects the all-RAM Z80 to declare that it needs the wait line to be implemented. 2017-08-26 23:18:11 -04:00
Thomas Harte
e34d4ce903 Merge pull request #224 from TomHarte/OptionalWait
Makes the Z80's support for WAIT input optional
2017-08-26 23:16:22 -04:00
Thomas Harte
d411827733 Merge branch 'master' into OptionalWait 2017-08-26 23:11:23 -04:00
Thomas Harte
f1ba7755dd Merge pull request #223 from TomHarte/cpctest
Moves test for 6845 horizontal sync timing into the time after phase 1 and before phase 2
2017-08-26 23:11:03 -04:00
Thomas Harte
57bfec285f Makes it optional whether the Z80 supports the wait line. If the wait line isn't in use, runtime costs are decreased because the optional wait cycles need not be iterated over. 2017-08-26 23:08:57 -04:00
Thomas Harte
bdda701207 Reverts previous unevidenced change. 2017-08-26 22:58:16 -04:00
Thomas Harte
487fe83dca Ensures that vertical sync and end-of-visible-lines conditions potentially trigger whenever line_counter_ changes, not only when it increments. 2017-08-26 17:54:54 -04:00
Thomas Harte
6c5a03187b Relocates the HSYNC start test, in order to pass Arnold's cpctest HSYNC start position conformance test. 2017-08-26 17:22:48 -04:00
Thomas Harte
97f57a3948 Merge pull request #222 from TomHarte/6845GetState
Refines observable 6845 behaviour
2017-08-26 14:46:29 -04:00
Thomas Harte
7d7aa2f5d5 Eliminates repetition of the unpacking of register 3 into a horizontal sync count. 2017-08-26 14:37:03 -04:00
Thomas Harte
e7ad79c79a Breaks apart the CPC's 6845 bus handler to obey phase 1 and phase 2, and now back-dates interrupts when appropriate. 2017-08-26 14:07:51 -04:00
Thomas Harte
28550c0227 Breaks the 6845 bus cycle into a phase 1 and a phase 2 per the belief that sync line changes, which are observable, happen at the end of the first phase rather than at the beginning of the next. This may have interrupt timing effects, as machines often derive an interrupt from sync. 2017-08-26 13:56:23 -04:00
Thomas Harte
6e99169348 Permits the 6845's bus state to be examined by an owner, eliminating the need to buffer it in the bus handler. But more than that it allows the CRTC to decide when it adjusts the various outputs respective to the main phase. So a net effect of the change is that the CPC now sees vsync a cycle earlier, because my current reading of the 6845 datasheet is that it is set at the end of phase 1, not the beginning of the next phase 1. 2017-08-26 12:59:59 -04:00
Thomas Harte
1017bb9f6b Merge pull request #221 from TomHarte/6845UpCount
Regularises the 6845 sync counters
2017-08-26 12:51:39 -04:00
Thomas Harte
3caa4705ca Limits sync counter size. 2017-08-26 12:31:19 -04:00
Thomas Harte
039aed1bd1 Switches the two sync counters to upward-going rather than downward, as a more likely match to the way the rest of the 6845 implementation. 2017-08-25 21:26:01 -04:00
Thomas Harte
d77d7fdd78 Merge pull request #220 from TomHarte/Analysis
Resolves all current analyser warnings.
2017-08-24 22:19:51 -04:00
Thomas Harte
c6e6c3fcfb Resolves all current analyser warnings. 2017-08-24 22:18:44 -04:00
Thomas Harte
ecd3350a6f Merge pull request #219 from TomHarte/ConstSafety
Makes all of PartialMachineCycle const
2017-08-24 22:04:06 -04:00
Thomas Harte
fa19e2d9c2 Removes some detritus. 2017-08-24 22:00:21 -04:00
Thomas Harte
95d360251d Makes all of PartialMachineCycle const, with the exception of the target of *value, since that's intended to be writeable by recipients. 2017-08-24 21:32:33 -04:00
Thomas Harte
7af3de010e Suspected my mode 1 interrupt timing might be off. Reminded myself of the sources. Persuaded myself that it wasn't. Added appropriate comments. 2017-08-23 22:25:31 -04:00
Thomas Harte
cefd421992 Merge pull request #218 from TomHarte/6845Factored
Refactors the 6845 to make end-of-line and end-of-frame conditions more explicit and to reduce repetition
2017-08-22 22:21:17 -04:00
Thomas Harte
a914eadc85 Ensured that register 6 is checked on every loop. 2017-08-22 22:17:45 -04:00
Thomas Harte
131b340d75 Dodges a lambda copy. 2017-08-22 21:55:10 -04:00
Thomas Harte
e956740c56 Refactors the 6845 more clearly to break out the acts of ending a line and ending a frame, changing the way the memory address is altered — the end-of-line value is provisionally stored and then used if necessary — in order to do so. 2017-08-22 21:54:48 -04:00
Thomas Harte
8afd83b91f Merge pull request #217 from TomHarte/CompiletimeOptions
Introduces compile-time selection of minor CPU core features and applies forceinline when appropriate
2017-08-21 22:29:24 -04:00
Thomas Harte
40d7a603db Ensured that forceinline does nothing in debug builds. 2017-08-21 22:04:15 -04:00
Thomas Harte
ee71be0e7e Added the option not to include ready line support in the 6502 core, and took advantage of it in the Electron, Oric and Vic-20 implementations. Also tagged those as forceinline and/or override final where applicable. 2017-08-21 21:56:42 -04:00
Thomas Harte
cde29c4bf4 Added forceinlines and properly declared finals and overrides. 2017-08-21 21:07:10 -04:00
Thomas Harte
e1aded0d95 Allows Z80 users to opt out of support for the bus request line. Which both now do. 2017-08-21 20:43:12 -04:00
Thomas Harte
1237f174fe Merge pull request #216 from TomHarte/NoiseReduciton
Cleans up issues affecting the sleeper mechanism and the CPC
2017-08-20 13:26:56 -04:00
Thomas Harte
0cbc1753b9 Quick fixes: the binary tape player now considers talk to the sleep observer only if motor control changes. The Amstrad CPC no longer attempts to use the component argument to identify the caller, since this will often be that of the superclass and not that of the derived class known to the CPC. 2017-08-20 13:18:46 -04:00
Thomas Harte
5cf0395936 Merge pull request #215 from TomHarte/Z80BusReq
Removes repeated checking of bus_request_line_ by the Z80.
2017-08-20 12:40:37 -04:00
Thomas Harte
6315c22b80 Removed repeated checking of bus_request_line_. It's now checked only after each outward perform_machine_cycle. 2017-08-20 12:39:45 -04:00
Thomas Harte
4614a56843 Merge pull request #214 from TomHarte/Sleeper
Experimentally introduces the concept of a 'sleeper' — a component that will volunteer to be unclocked for a period
2017-08-20 12:29:32 -04:00
Thomas Harte
8f5ae4a326 The CPC now responds to tape-originating sleeper observations. 2017-08-20 12:21:02 -04:00
Thomas Harte
8fdc5012e4 Updated TapePlayer and BinaryTapePlayer to be sleepers. 2017-08-20 12:18:36 -04:00
Thomas Harte
e88a51e75e Worked logic all the way down to the CPC. If the 8272 announces that it is asleep, it is now no longer clocked. Also very slightly cut down on IRQ line chatter to the Z80. 2017-08-20 12:05:00 -04:00
Thomas Harte
49285e9caa Attempted to implement Sleeper in Drive and therefore in DiskController. Also corrected a couple of nonconformant file names. 2017-08-20 11:54:54 -04:00
Thomas Harte
e3f2118757 Merge branch 'master' into Sleeper 2017-08-20 10:58:03 -04:00
Thomas Harte
daeaa4752f Merge pull request #213 from TomHarte/MinorPalette
Makes a variety of minor performance improvements to the CPC
2017-08-20 10:57:41 -04:00
Thomas Harte
5344e3098b Minor: made has_disk something that is decided on insertion/deletion. 2017-08-20 10:55:08 -04:00
Thomas Harte
cedb809c21 Sketched out a protocol designed to save processing time on anything that may sleep — probably just disk controllers for now but one can easily imagine it being applicable to printers, and possibly sound chips with suitable changes in guarantee for sound packet receivers. 2017-08-20 10:53:25 -04:00
Thomas Harte
2d9efccc98 Introduced a master 'is sleeping' flag. I'm starting to think there's a pattern forming here. 2017-08-20 10:43:53 -04:00
Thomas Harte
8ce46b6e49 Having spotted that I was using my single-character loop counter names incorrectly (quelle surprise!), got a bit more explicit. Also flattened into a single loop so that I can break rather than returning. 2017-08-20 10:32:09 -04:00
Thomas Harte
f2699a3f2b Okay, even if releasing it is unsafe, I can at least move the typer so that it is no longer called. 2017-08-20 10:24:01 -04:00
Thomas Harte
85253a5876 Sought further to reduce the processing footprint of palette changes by updating only those table entries that are affected by a change. 2017-08-20 10:13:23 -04:00
Thomas Harte
911ee5a0d3 At least added a fast return. 2017-08-19 22:22:51 -04:00
Thomas Harte
57c5b38a6d Step one towards cutting much of this cost: build only the table that's appropriate for the current mode, and at least declare when a more minimal change would be sufficient. 2017-08-19 22:19:46 -04:00
Thomas Harte
669e0caff5 Ensured the head_unload_delay values are properly seeded, and generalised the quick escape. 2017-08-19 22:06:56 -04:00
Thomas Harte
b24d04fc09 Merge pull request #212 from TomHarte/MFMParserDensity
Improves the variability of the MFM parser used for static analysis
2017-08-18 15:57:37 -04:00
Thomas Harte
ef07c33741 Merge branch 'Plus10' into MFMParserDensity 2017-08-18 15:48:20 -04:00
Thomas Harte
e559a65ede Ideally I would be able to kill this multiplier, as it could easily be derived at runtime. But, for now, just turned it up so that the analysis-oriented parser is better at parsing different bit rates. 2017-08-18 15:47:46 -04:00
Thomas Harte
5bdd24d93f Merge pull request #211 from TomHarte/HFE
Introduces support for the HFE file format.
2017-08-17 22:43:23 -04:00
Thomas Harte
af61a7fa28 Two quick fixes: correctly set segment size, and flip bytes to match HFE's bit ordering to PCMTrack's. 2017-08-17 22:28:00 -04:00
Thomas Harte
c8c1792c3f Made a first attempt at HFE support. 2017-08-17 22:20:02 -04:00
Thomas Harte
e6683e7f2d Added the base skeletal stuff of HFE support. 2017-08-17 21:48:48 -04:00
Thomas Harte
0c1714b695 Relaxed a little to allow +10% in track length. 2017-08-17 21:36:14 -04:00
Thomas Harte
dc0ca83003 Merge pull request #210 from TomHarte/TrackSize
Various MFM and DSK fixes
2017-08-17 15:50:27 -04:00
Thomas Harte
2c2dd8073c Modified to return nullptr if asked for an extended disk image track that doesn't exist. 2017-08-17 15:32:24 -04:00
Thomas Harte
4f8b89772e Improved logic for detecting when all sense has been derived from a track to spot any repeated track, not necessarily the first one. That avoids sectors that run over the index hold and obscure the first throwing things. 2017-08-17 15:31:53 -04:00
Thomas Harte
733ee5a5c3 Ensured no attempt to put a null track into the cache 2017-08-17 15:30:02 -04:00
Thomas Harte
fedf5a44a6 Imposes a maximum track length. 2017-08-17 15:20:49 -04:00
Thomas Harte
6a09022896 Merge pull request #209 from TomHarte/BootOnly
Generalises: an acceptable boot sector is always acceptable
2017-08-17 14:29:37 -04:00
Thomas Harte
5b3c707959 Generalised: an acceptable boot sector is acceptable even if no valid CP/M catalogue is found anywhere. 2017-08-17 14:28:16 -04:00
Thomas Harte
9b21ef1507 Merge pull request #208 from TomHarte/SpecialCharacters
Improves the CPC static analyser's correct file name predictions
2017-08-17 13:25:30 -04:00
Thomas Harte
da3e8655e9 Withdrew some caveman debugging nonsense. 2017-08-17 13:25:19 -04:00
Thomas Harte
41e4386164 Added another "one thing is different" test: one thing has a different file name. Also decided to right-time the type (/extension) as well as the file name. 2017-08-17 13:21:48 -04:00
Thomas Harte
b0a98bd239 Added nuance: file names with unprintables are filtered, and then system files are considered if there are no remaining non-system files. 2017-08-17 12:48:15 -04:00
Thomas Harte
42ad670ec8 Fixed: catalogue bitmap is in blocks, not sectors. 2017-08-17 12:47:47 -04:00
Thomas Harte
58063b69a6 Merge pull request #207 from TomHarte/DragAndDrop
Establishes drag and drop as a mechanism to change the media inserted into a machine while it is running
2017-08-17 11:19:56 -04:00
Thomas Harte
378f231499 Fully wired in drag-and-drop for media insertion. 2017-08-17 11:00:08 -04:00
Thomas Harte
f68565a33f Split the static analyser functionality so that it's possible just to ask for the set of media implied by a particular file. Extended ConfigurationTarget so that media alone can be pushed to a machine. 2017-08-17 10:48:29 -04:00
Thomas Harte
175faebdc9 Merge pull request #206 from TomHarte/AnalysisFailure
Corrects a couple of cases in which the CPC analyser could pick an incorrect loading command
2017-08-16 22:26:26 -04:00
Thomas Harte
76c6b715a2 Adjusted rules so as not to type unnecessary spaces in the name, and to include the extension if AMSDOS won't imply it. 2017-08-16 22:24:37 -04:00
Thomas Harte
b476f06524 Slowed the typer, having discovered that otherwise it has problems transitioning from a shifted to an unshifted character. 2017-08-16 22:12:16 -04:00
Thomas Harte
48290a8bbe Added a prefilter to catalogues to remove system files. They're not listed when you CAT, so almost certainly aren't what a user would be expected to load. 2017-08-16 22:11:49 -04:00
Thomas Harte
9d9a1c341d Added an extra test to try to avoid spurious |cpm launches. 2017-08-16 21:55:31 -04:00
Thomas Harte
952da1e581 Merge pull request #205 from TomHarte/CPCShots
Adds a CPC screenshot, to show that this isn't just about composite video
2017-08-16 21:05:55 -04:00
Thomas Harte
a988255558 Adjusted brightness better to match its comparison. And to try partly to obscure its source. This isn't meant to be about software X versus software Y. 2017-08-16 21:04:09 -04:00
Thomas Harte
cca66ab450 Added a CPC grab, to show that it's not all about being composite. 2017-08-16 21:01:22 -04:00
Thomas Harte
bcd7a312a4 Merge pull request #204 from TomHarte/VicInline
Brings the Vic-20 into line with the new idiom on machine declaration
2017-08-16 16:24:38 -04:00
Thomas Harte
925e774015 Added a decent portion of documentation. But started feeling like I should address my various ownership decisions. Which would justify a separate pull request. 2017-08-16 16:23:33 -04:00
Thomas Harte
4c15e46fd1 Performed the normative removal from public view of Vic-20 implementation details. Which were hefty. 2017-08-16 16:05:30 -04:00
Thomas Harte
3c50903a2b Merge pull request #203 from TomHarte/ElectronInline
Adds the Electron to the pantheon of machines that reveal very little in their public interface
2017-08-16 15:35:11 -04:00
Thomas Harte
75208b0762 Moves the Electron implementation behind a more opaque interface, in line with changes elsewhere. 2017-08-16 15:33:40 -04:00
Thomas Harte
f1e64169cd Merge pull request #202 from TomHarte/AtariInline
Catches the Atari 2600 up with the no-internals-published trend.
2017-08-16 14:54:33 -04:00
Thomas Harte
903a17ae11 Corrected typo and removed replication of what's already declared formally. 2017-08-16 14:53:03 -04:00
Thomas Harte
de1c526789 Cut the amount disclosed by the Atari 2600 for public inspection down to the minimum, relocating implementation into the .cpp. 2017-08-16 14:52:40 -04:00
Thomas Harte
b7e0f64892 Merge pull request #201 from TomHarte/OricInline
Hides the Oric implementation innards
2017-08-16 14:37:42 -04:00
Thomas Harte
148591b7f2 Hid most of the Oric innards, and corrected a potential multi-thread access error emanating from the Mac side of the world. 2017-08-16 14:35:53 -04:00
Thomas Harte
2105597910 Merge pull request #200 from TomHarte/6502BusHandler
Converts the 6502 into a bus-handler-type template, and makes appropriate adjustments to all 6502 machines
2017-08-16 14:10:08 -04:00
Thomas Harte
3c148f5721 Fixed clanger of an error. 2017-08-16 14:02:46 -04:00
Thomas Harte
360c8a99a3 Adjusted Atari2600 actually to use the nominated type of bus extender. 2017-08-16 12:57:32 -04:00
Thomas Harte
06e31f5102 Consequential to the 6502 change, severs the Atari 2600's cartridge container from its former attempt at runtime polymorphism, in favour of each cartridge's specific hardware being defined as a 'bus extender'. 2017-08-16 12:39:15 -04:00
Thomas Harte
42b5b66305 Remove the 6502's use of runtime polymorphism in favour of ordinary templating. 2017-08-16 11:56:52 -04:00
Thomas Harte
27018ba64e Merge pull request #199 from TomHarte/VicColoursUSA
Corrects Vic-I NTSC colour output
2017-08-16 10:00:40 -04:00
Thomas Harte
e208f03636 Corrects the US colour palette, effectively undoing what was a mistaken adjustment for the time when Oric-centric phase alignment was built into the CRT based on a false calculation that it wouldn't affect the machines that generate chrominance functionally. 2017-08-16 09:58:34 -04:00
Thomas Harte
cc9d23f23b Inverted meaning of register_masks, as it's a bit weird that the mask is inverted immediately upon usage. It's a left-over from thinking the unused bits should be 1s; unit tests reveal they should be 0s. Comment updated appropriately. 2017-08-16 09:29:48 -04:00
Thomas Harte
1a831bcf9b Quick fix: supply the port being written to correctly. 2017-08-16 09:15:57 -04:00
Thomas Harte
82367a2246 Added documentation. 2017-08-16 09:14:56 -04:00
Thomas Harte
f18206767f Merge pull request #198 from TomHarte/LiveKeyboard
Introduces active input handling for the AY and uses it for CPC keyboard input
2017-08-15 22:48:09 -04:00
Thomas Harte
3947347d88 Introduces active input handling for the AY and uses it in the CPC to give proper, active keyboard input, rather than push-on-select, which was only ever a temporary hack. Also maps a few more keys for the Amstrad. 2017-08-15 22:47:17 -04:00
Thomas Harte
8a37a0ff2e Merge pull request #197 from TomHarte/MoreFDC
Improves the 8272 and the whole disk infrastructure behind it
2017-08-15 22:09:32 -04:00
Thomas Harte
468770b382 Removed debugging nonsense. 2017-08-15 22:06:58 -04:00
Thomas Harte
6cfc3daacb Introduced a test within the disk controller so as not to request illegal tracks from disks, instead automatically substituting an 'unformatted' track. Which is just empty. 2017-08-15 21:52:12 -04:00
Thomas Harte
aefbafa18d Centralised the 8272's actions of setting the non-DMA execution flag, picking the drive and head and loading the head for accessing commands, and switched error flag if read ID doesn't find anything. 2017-08-15 21:49:10 -04:00
Thomas Harte
d12c834f9c Increased amount reported. 2017-08-15 20:35:33 -04:00
Thomas Harte
56de5cb2b3 Removed one further logging event. 2017-08-15 20:18:32 -04:00
Thomas Harte
709257a0c5 Quick fix: also treat reception of sync as a reason not to stop looking for a data address mark. 2017-08-15 20:16:56 -04:00
Thomas Harte
75a9d2bb33 Started withdrawing logging, reduced index hole count back to the correct number and tried to increase read_data rigour. 2017-08-15 20:12:01 -04:00
Thomas Harte
7b92b235e1 Further upped asserts, thereby discovering the mistake I'd recently introduced: seeking properly within the event source as per its potential left-clipping, but then not allowing for that in the calculated current time. 2017-08-15 16:25:46 -04:00
Thomas Harte
c196f0018f Upped the assert quotient. 2017-08-15 16:15:09 -04:00
Thomas Harte
73080d6c36 Added an easy way for disk controllers to clamp termination of written data exactly to the index hole.
This commit also temporarily provides a whole load of extra logging and minor logic improvements from the 8272. I'm mid-flow on finding a particularly vicious error in its handling of writing; wait for the pull request. But, at least: now waits for the first part of a post-ID gap before writing data, and attempts partially to handle appearance of the index hole during writing a track. More work to do on that though.
2017-08-15 16:05:10 -04:00
Thomas Harte
9541a2a5f0 Corrections: seek_to now takes the segment_start_time into account, correcting a windowing error where segments overlay other segments. Also added some asserts while bug hunting, and corrected the steps taken when inserting a longer-than-a-track segment so that each is correctly windowed. 2017-08-15 15:54:09 -04:00
Thomas Harte
944222eba4 Added: write_id_data_joiner can now be instructed not to write the first portion of gap. Which makes more sense as an option, to avoiding splicing errors. 2017-08-15 15:29:23 -04:00
Thomas Harte
9d77f33611 Dealt with another source of repeating magic constants: the command numbers. 2017-08-15 11:06:10 -04:00
Thomas Harte
7d132f81f7 Increased logging by quite a distance and made an attempt once again to allow the processor some time to supply the first byte when writing before declaring overrun. 2017-08-15 10:50:28 -04:00
Thomas Harte
0972f19fc5 Merge pull request #196 from TomHarte/Minor6845
Corrects 8272 multi-sector reads and deleted data reads, and ensures that a zero-height 6845 display shows nothing
2017-08-14 22:31:24 -04:00
Thomas Harte
6553bf05b4 Corrected multi-sector reads: ensured the incremented sector number isn't replaced by the original, and that the controller returns to scanning mode. 2017-08-14 22:27:31 -04:00
Thomas Harte
0816d3f5a9 Corrected 'read deleted data' command. It's 0xc, not 0xb. 2017-08-14 21:41:20 -04:00
Thomas Harte
55055c7847 Minor: ensured immediate line comparison works. But I think my problem might be trying to do this as straight line logic? 2017-08-14 19:08:20 -04:00
Thomas Harte
113da93796 Merge pull request #195 from TomHarte/8272Tidying
Brings the 1770 into usage of those parts factored out of it for the 8272
2017-08-14 16:34:15 -04:00
Thomas Harte
cddcd0fb79 Put my money where my mouth is and switched the superclass of WD1770 to MFMController, eliminating duplicated (/factored out) code. 2017-08-14 16:32:53 -04:00
Thomas Harte
a366298022 Factored out the standard [M]FM gap and mark groups, to increase 8272 readability and because it's pretty-much certain I'll need them again if ever I try to tackle e.g. the 8271. 2017-08-14 16:03:35 -04:00
Thomas Harte
4df9307d25 Factored out the dull and repetitious stuff of writing n bytes of the same value. 2017-08-14 15:50:36 -04:00
Thomas Harte
d7bed958b3 Merge pull request #194 from TomHarte/8272Write
Introduces initial implementations of the 8272's write data, write deleted data, read track and format track
2017-08-14 14:35:45 -04:00
Thomas Harte
9038ba622e Added a quick version of read track. 2017-08-14 14:34:56 -04:00
Thomas Harte
7b8bb0297a Implemented single density version of format track. 2017-08-14 13:03:17 -04:00
Thomas Harte
0da02d3902 Added read/write escape clauses if faced with a read-only disk. 2017-08-14 12:53:18 -04:00
Thomas Harte
334872d374 Clarified, slightly. 2017-08-14 12:47:11 -04:00
Thomas Harte
2e5ad19fe1 Minor tidying. 2017-08-14 12:42:48 -04:00
Thomas Harte
a10389a22c Factored out the stuff of stuffing the bus. 2017-08-14 12:42:22 -04:00
Thomas Harte
cefec7a19f Sought more robustly (i.e. less repetitively) to handle dispatch, including cancelling seeks where appropriate. 2017-08-14 10:37:39 -04:00
Thomas Harte
7264fbb3d2 read_id now clears status. I probably need to find a way to generalise this. 2017-08-14 09:58:55 -04:00
Thomas Harte
0e083e9dc6 Factored composition of a run command out, as I think I need to worry about extensions, and can trim spaces. 2017-08-14 09:48:56 -04:00
Thomas Harte
8a7b23dc9e Ensured data-accessing commands cancel seeks on their drives. Also introduced a count of drives currently seeking in order to make for a slightly better broad-phase test in run_for. 2017-08-14 09:45:39 -04:00
Thomas Harte
b7065575f3 Added (empty) call-ins for DMA usage; switched to having the 'is seeking' bit in the status register stay high until sense interrupt status, but now it goes high even for seeks that don't actually go anywhere, and corrected interpretation of the specify command, with a positive result: the received step rate time, now that it's being interpreted correctly, is much shorter. 2017-08-14 09:04:22 -04:00
Thomas Harte
7ea703f150 Started making provisions for a DMA-compatible implementation. Re: the CPC, it sounds like DMA acknowledge might be permanently wired, causing DMA mode seemingly to work from the 8272's point of view. 2017-08-14 08:38:00 -04:00
Thomas Harte
ea64125124 Added an explicit nilling, to help with debugging. 2017-08-13 22:15:25 -04:00
Thomas Harte
1011143dbe Sought to correct my interpretation of 'gap 3'. 2017-08-13 21:52:48 -04:00
Thomas Harte
9ace6e1f71 Applied minimum constraints for specified parameters. 2017-08-13 19:25:57 -04:00
Thomas Harte
750f2cb883 Flagged as not read-only, at least for now, to allow 8272 writing tests definitively to function. 2017-08-13 18:54:39 -04:00
Thomas Harte
5221837be8 Fixed Non-DMA flag for the format track execution phase. The emulated machine now provides sector details. 2017-08-13 18:51:06 -04:00
Thomas Harte
1576b4500b Added documentation. 2017-08-13 18:27:00 -04:00
Thomas Harte
e1e9a06712 Made an attempt at format a track. 2017-08-13 18:05:19 -04:00
Thomas Harte
6e36f8ffa4 Removed index-hole announcement. 2017-08-13 12:50:24 -04:00
Thomas Harte
b0a7208cc7 Strung together a very basic version of 8272 write [/deleted] data. Lots of cases as-yet unhandled. 2017-08-13 12:50:07 -04:00
Thomas Harte
eec42aa7ae Entrusted further status to drives; also adjusted them to report read only if diskless, which I now believe to be correct. 2017-08-13 11:50:49 -04:00
Thomas Harte
6d2e969e7d Merge pull request #193 from TomHarte/8272Style
Improves 8272 implementation style
2017-08-12 18:05:27 -04:00
Thomas Harte
5f42022c1d Added a tester for the control mark. 2017-08-12 17:35:14 -04:00
Thomas Harte
11d0c37506 Attempted to find a more expressive way for maintaining state — macros for all conditions, to bind both values and destinations. 2017-08-12 17:33:52 -04:00
Thomas Harte
58bad1e2a3 Merge branch 'PerDriveStatus' 2017-08-12 16:49:38 -04:00
Thomas Harte
27d1dc5c37 Removed some old printfs. 2017-08-12 16:49:20 -04:00
Thomas Harte
e7345c7a20 Merge pull request #192 from TomHarte/PerDriveStatus
Expands 8272 emulation further
2017-08-12 16:49:06 -04:00
Thomas Harte
186048a88e Made an attempt to fix the condition for setting a broken header CRC. 2017-08-12 16:39:32 -04:00
Thomas Harte
7135259cc1 Sought to flesh out error conditions. 2017-08-12 16:36:37 -04:00
Thomas Harte
4909325e79 Implemented read deleted data. 2017-08-12 13:01:17 -04:00
Thomas Harte
a4ee697ed1 Quickie: head unload is scheduled only if the head is presently loaded. 2017-08-12 12:53:45 -04:00
Thomas Harte
0f15a2f97f Relented: it actually looks like status bytes aren't per-drive. But each drive may fail at seeking individually. So that piece of state accumulates at the 8272 drive. 2017-08-12 12:52:36 -04:00
Thomas Harte
89ace671a4 Corrected unload time. Was 8000 times too short. 2017-08-12 09:44:01 -04:00
Thomas Harte
e7db2a2f6d Sought to introduce head loading and unloading delays. 2017-08-12 09:36:21 -04:00
Thomas Harte
8c33ac71ee Merge pull request #191 from TomHarte/Precache
Introduces more aggressive caching of sectors in the MFM decoder, improves CPC static analysis further
2017-08-12 09:10:29 -04:00
Thomas Harte
69914faf02 Fixed comments. 2017-08-11 20:22:14 -04:00
Thomas Harte
daafebe7ac Moved curly bracket. 2017-08-11 19:19:04 -04:00
Thomas Harte
2d81acb82e Upped C++ standard to C++14 and added an #if that's intended to use the built-in std::gcd when compiled on C++17 or better. Fixed for new signedness warnings resulting for taking the step to C++14. 2017-08-11 19:18:45 -04:00
Thomas Harte
82ca49c840 Adjusted to avoid calls to ::greatest_common_divisor(numerator % denominator, denominator) unless necessary. 2017-08-11 19:05:46 -04:00
Thomas Harte
bfe297052d Picked up another subtlety: disk names may be outside of the ones a user could type, in which case they definitely don't affect the decision. 2017-08-11 18:59:38 -04:00
Thomas Harte
ffb1a14ace Minor: clear status registers before a read data. 2017-08-11 18:56:33 -04:00
Thomas Harte
7e35e44934 Added an extra sanity check on treating system disks as system disks. 2017-08-11 18:46:39 -04:00
Thomas Harte
0c8769e335 Just to be safe. 2017-08-11 18:41:08 -04:00
Thomas Harte
83c7d34df2 Switched to populating the sector cache with everything in a track the first time anything on that track is requested. That avoids the problem whereby each request of a non-existent sector costs two spins. 2017-08-11 18:40:16 -04:00
Thomas Harte
ad3c9842d7 Merge pull request #190 from TomHarte/SingleImplicit
Corrects a couple of CPC static analysis pitfalls
2017-08-11 16:41:03 -04:00
Thomas Harte
44dace2eef Made an attempt not to interrogate files that definitely don't have the normal header. 2017-08-11 16:34:29 -04:00
Thomas Harte
a12671010a Sector size is now reported, and CRC failures are merely indicated, not cause for a sector to be thrown away. 2017-08-11 16:23:33 -04:00
Thomas Harte
23c149368b Broadened CPC data disk analysis to spot when there is only one implicitly-runnable file, rather than only one without suffix. 2017-08-11 16:23:00 -04:00
Thomas Harte
09716d4716 Merge pull request #189 from TomHarte/CPCSystemDisks
Corrects analysis of CPC system disks
2017-08-11 15:56:35 -04:00
Thomas Harte
4b7c504d22 Corrects analysis of system disks — they have a catalogue that is correct read, but can be launched without reference to it. 2017-08-11 15:55:33 -04:00
Thomas Harte
1e4f9d4eda Merge pull request #188 from TomHarte/AYFidelity
Switches to guessing that the AY doesn't reset its dividers upon frequency changes
2017-08-11 14:50:11 -04:00
Thomas Harte
e4f04d0977 Merge branch 'master' into AYFidelity 2017-08-11 14:41:08 -04:00
Thomas Harte
0f75525640 Merge pull request #187 from TomHarte/CRCErrors
Extends the built-in [M]FM encoder to be able to produce incorrect CRC values, and adjusts the CPC .DSK handler to request them when appropriate
2017-08-11 14:34:31 -04:00
Thomas Harte
edb088526f Simplified slightly, and updated TODO as to still-missing functionality. 2017-08-11 14:33:34 -04:00
Thomas Harte
80ebc63101 Updated the SSD file format container to specify sector sizes, now that it's no longer implicit. 2017-08-11 14:30:35 -04:00
Thomas Harte
cf1403bc79 Increased documentation. 2017-08-11 14:27:07 -04:00
Thomas Harte
fcf63a7547 Expands the [M]FM encoder to respect some new Sector flags: it will now wilfully make CRC errors, omit data, include data that is different than the ID's declared length, write deleted data, and can be commanded as to header/data gaps and what should be within them. All based around expanding towards the needs for reproduction of the CPC's .DSK file format. 2017-08-11 14:24:50 -04:00
Thomas Harte
9c0b75faba Merge pull request #186 from TomHarte/CPCTyper
Introduces typer support for the Amstrad CPC.
2017-08-11 12:35:43 -04:00
Thomas Harte
1f2bfc9581 Ensured tape loading really begins. 2017-08-11 12:30:36 -04:00
Thomas Harte
14ab03d1e0 Added a further fallback: if all files have an extension but one doesn't, take that one. 2017-08-11 12:27:50 -04:00
Thomas Harte
3831fbaca2 Ensured the ZX80 and '81 also provide the necessary hook for destruction. 2017-08-11 12:11:01 -04:00
Thomas Harte
1d8edf58dd Ensured that a virtual destructor is declared, so that the various automatically-generated real constructors get in on the action. 2017-08-11 12:07:48 -04:00
Thomas Harte
4785e316ff Now with exposition. 2017-08-11 11:36:03 -04:00
Thomas Harte
44da9de5b0 Tweaked typing timing expectations. 2017-08-11 11:35:28 -04:00
Thomas Harte
4ecd093891 Fixed test for termination of a key sequence; the previous error will have seen this reduce all multi-key sequences to just the one, and expand single-key sequences to "probably" two, posting an out-of-bounds code to the machine at completion. 2017-08-11 11:35:14 -04:00
Thomas Harte
dd4bc87d52 Fixed: should be a full-path #ifdef guard, given that this is one of the classes named relative to its namespace. 2017-08-11 11:21:33 -04:00
Thomas Harte
570d25214e Made an initial attempt at typer support for the CPC. 2017-08-11 11:21:07 -04:00
Thomas Harte
f0b7e58968 Merge pull request #185 from TomHarte/CPMCatalogue
Introduces a CP/M catalogue parser, as a basic for the CPC's static analyser
2017-08-11 11:00:49 -04:00
Thomas Harte
0411b51582 Added an attempt to deal with 16-bit allocation units, and to ensure middle-of-file holes are respected. 2017-08-11 10:59:37 -04:00
Thomas Harte
dea782cff9 Added a "yeah, I don't know" fallback. 2017-08-11 10:47:45 -04:00
Thomas Harte
388dd99762 Advanced this just enough to suggest a loading command for most things. 2017-08-11 10:47:12 -04:00
Thomas Harte
026101a268 Killed logic_extents_per_physical, since I don't know how to handle it, and instituted tracks, to allow a decision about short versus long allocation units. 2017-08-11 10:46:50 -04:00
Thomas Harte
734099a956 Threw a sector cache into my MFM parser, in an attempt to cut down analysis costs. Also made it aware of multiple heads. 2017-08-11 10:29:13 -04:00
Thomas Harte
6be5851484 Cleaned up. 2017-08-10 22:34:29 -04:00
Thomas Harte
994179f188 Taking a whole bunch of guesses, this might be correct. 2017-08-10 22:33:08 -04:00
Thomas Harte
6a65c7a52a Started working on a CPC-oriented analyser; for now I just want to be able to make a good guess at the appropriate file to load from a disk. As it turns out, the CPC simply adopts the CP/M format, so a generic parser is appropriate. This is its beginning. 2017-08-10 17:10:21 -04:00
Thomas Harte
0d2d3ea17c Merge branch 'master' into DeferredVideo 2017-08-10 16:01:02 -04:00
Thomas Harte
5d374ebb18 Merge pull request #184 from TomHarte/DeferredVideo
Reduces CRTC counter sizes to match real hardware; introduces a CRT gamma transform
2017-08-10 16:00:39 -04:00
Thomas Harte
62eadbb51a Adjusted gamma ratio to be the correct way around. The PAL midrange should be slightly darker now. 2017-08-10 15:36:27 -04:00
Thomas Harte
ad8c8166bc Built in gamma conversion for all machines, assuming an output of 2.8 for PAL, 2.2 for NTSC. 2017-08-10 15:17:08 -04:00
Thomas Harte
a5593bec79 Threw in support for the light-pen trigger. 2017-08-10 15:00:14 -04:00
Thomas Harte
a1e2646301 Imposed counter size limits. 2017-08-10 14:58:24 -04:00
Thomas Harte
cf810d8357 Minor: ensure the CRT is set to output as a monitor. 2017-08-10 14:42:47 -04:00
Thomas Harte
f258d6fbb2 Merge pull request #183 from TomHarte/6845Address
Corrects 6845 address loading
2017-08-10 12:51:40 -04:00
Thomas Harte
4961fda2a9 Ensured counter-intuitive CRTC writes get through, taking the opportunity to correct my handling of port IO in general: selecting multiple devices for input results in a logical AND (i.e. open collector mode), and both the CRTC and gate array will receive data from 'input's if applicable. 2017-08-10 12:39:19 -04:00
Thomas Harte
6a6e5ae79c Forced users of the 6845 to be explicit about which type. So far with no effect. 2017-08-10 12:28:57 -04:00
Thomas Harte
02d792c003 Simplified logic slightly, avoiding repetition. 2017-08-10 11:48:37 -04:00
Thomas Harte
be8e7a4144 Eliminated false register aliasing, restricted register sizes and locked out reading and writing where appropriate. 2017-08-10 11:22:30 -04:00
Thomas Harte
b1dbd7833a Merge branch 'master' into 6845Address 2017-08-10 11:15:08 -04:00
Thomas Harte
84ab05c5ef Merge branch 'BetterFDC' 2017-08-10 11:14:53 -04:00
Thomas Harte
78138261c2 Merge pull request #182 from TomHarte/BetterFDC
Continues building up the 8272
2017-08-10 11:14:08 -04:00
Thomas Harte
a4c910f1de This appears to be a more accurate take on 6845 address advancement — it is necessary that character output has finished for the line address to be updated. 2017-08-10 11:12:53 -04:00
Thomas Harte
2eed24e859 Made an initial attempt at [a subset of] multi-sector reads. 2017-08-10 11:11:26 -04:00
Thomas Harte
7d1023ea98 Added a 'ready' getter to Drive, formally to let the drive take ownership of that test. 2017-08-08 21:15:56 -04:00
Thomas Harte
b11d142cff Switched to descriptive names. 2017-08-08 20:35:41 -04:00
Thomas Harte
021ff8674e Added something for sense drive status. 2017-08-08 20:30:54 -04:00
Thomas Harte
1b86bc21ab Might as well get official on my ongoing efforts at CPC emulation. 2017-08-08 17:58:54 -04:00
Thomas Harte
e3d1f4fe1e Subjectively, this might be more correct. It definitely prevents intermediate frequencies. More research required. 2017-08-08 17:58:35 -04:00
Thomas Harte
a7452aebff Merge pull request #181 from TomHarte/6128
Introduces support for 128kb CPCs.
2017-08-08 16:06:31 -04:00
Thomas Harte
484524d781 Implements RAM paging. The 6128 is now emulated. 2017-08-08 16:01:56 -04:00
Thomas Harte
dbf9927caf Merge branch '8255Modes' 2017-08-08 07:44:56 -04:00
Thomas Harte
3bdedfd749 Improved comments. 2017-08-08 07:44:46 -04:00
Thomas Harte
005084af3d Merge pull request #180 from TomHarte/6845FrameLength
Corrects an off-by-one error in [character] line counting within the 6845
2017-08-08 07:37:48 -04:00
Thomas Harte
46278ff297 Experimental: is this meant to be a compare-before-increment? 2017-08-07 23:02:29 -04:00
Thomas Harte
d73dcbb268 Merge pull request #179 from TomHarte/AYDirection
Makes various AY input/output fixes, and a few 8255 fixes along the way
2017-08-07 19:58:39 -04:00
Thomas Harte
390ecec3d9 Added: now declines to pass on output if in input mode for ports A and B. 2017-08-07 19:56:22 -04:00
Thomas Harte
41a30c147d Adjusted: invalid register selection simply deselects all registers. 2017-08-07 19:51:36 -04:00
Thomas Harte
4709ae80cb Added port direction tests. 2017-08-07 19:36:55 -04:00
Thomas Harte
7fbb455836 Per the CPC test I'm checking, 0s should be returned for non-retained bits, not 1s. 2017-08-07 19:07:12 -04:00
Thomas Harte
745afd217f The port input/output flags are now honoured; reading a port that is set as an output returns the current output value. 2017-08-07 19:01:18 -04:00
Thomas Harte
4427e9a254 Merge pull request #178 from TomHarte/CPC664
Introduces the beginning seeds 8272 emulation, and therefore floppy emulation for the CPC
2017-08-07 18:39:33 -04:00
Thomas Harte
2b0dcf8573 Transcribed the status bits that I think actually need to be obeyed. 2017-08-07 12:37:45 -04:00
Thomas Harte
47732ffb98 Prevented the 8272 from overreading ID fields (and, by doing so, overrunning its internal buffer). Exposed the MFMController's CRC generator for inspection. 2017-08-07 12:37:22 -04:00
Thomas Harte
d07f3216ab Added a broad phase on whether seeking is ongoing. 2017-08-07 12:12:59 -04:00
Thomas Harte
56d65ba6f3 Adapted slightly, to retain the ability to advertise an incorrect size, to adjust the confusion I've created by having two different types of thing both called new_sector, and to print a warning when ignoring error flags. 2017-08-07 12:12:04 -04:00
Thomas Harte
895a3cbf24 Corrected reading of the track size table for extended disks. My first extended disk has now loaded. 2017-08-07 11:38:19 -04:00
Thomas Harte
d951c8c1c2 Fixed search for track start position with extended disks: it's no longer an infinite loop. So that's a pretty good performance improvement. 2017-08-07 11:36:29 -04:00
Thomas Harte
a294963e98 Made an absolutely basic attempt to accommodate some extended disk images. 2017-08-07 11:26:15 -04:00
Thomas Harte
68c73184b1 Had failed to spot that by taking control of stepping at this level, the appropriate invalidate_tracks were not being sent. 2017-08-07 10:36:53 -04:00
Thomas Harte
7f824d6494 Ensured seeks and recalibrates end immediately if no seeking is required. 2017-08-07 10:31:32 -04:00
Thomas Harte
3219212f03 A closer inspection of the data sheet seems to suggest that invalid command sequences will post ST0. 2017-08-07 07:35:41 -04:00
Thomas Harte
d90e35e5bd Added a bunch of comments, and ensured that the data request bit remains set for the entire period that command bytes are accepted. 2017-08-07 07:27:00 -04:00
Thomas Harte
73f8488150 Reaching the end of the usable part of my day, decided to tidy up a little before bed with indentation that reflects a distinction between top-level entry points and mere loops. 2017-08-06 22:14:18 -04:00
Thomas Harte
3853966a1e Removed formal storage of ST3, as it just seems to be composed live. This may turn out also to be the best way to deal with ST0–2, time will tell. Also took a stab at the error in responding properly to the ROM's intended use of seek might be accepting new commands as replacements for old ones rather than rejecting them. That didn't seem to do the trick. 2017-08-06 22:10:12 -04:00
Thomas Harte
d63893a437 Collapsed implementations of recalibrate and seek, and decided to intend to go for an upward count on steps taken rather than a downward one. But seek continues presently to fail. 2017-08-06 21:52:52 -04:00
Thomas Harte
90c74043f5 Remembered to toggle off RQM between bytes. CAT now works. 2017-08-06 21:21:59 -04:00
Thomas Harte
600445d90a Made a first attempt to return sector contents. 2017-08-06 20:40:29 -04:00
Thomas Harte
e4b405fd3d With the ROM now using a read ID to set its expectations, implemented that and fixed FIND/READ_HEADER macros for multiple use. Execution now reaches the unimplemented section of read data. 2017-08-06 20:32:46 -04:00
Thomas Harte
3b7ecbdf0d Renamed result_ to result_stack_ to emphasis the fact that it goes backwards. Switched meaning of CB so that it is set for the entire command, execution and result phases. 2017-08-06 20:17:12 -04:00
Thomas Harte
01efb645cb Took a reasonable gamble that the CHRN reported is from internal registers, not from the last-found header. 2017-08-06 19:57:34 -04:00
Thomas Harte
b5ec1f42d5 Started resetting 'busy' when entering the result phase. AMSDOS now complains of a missing disk after failing to find sector 01. My belief is that it should end up asking for C1. So this is not even getting through a failure to find a sector correctly yet. 2017-08-06 19:48:17 -04:00
Thomas Harte
c839556a27 Fixed: rewind the file to check for 'EXTENDED' if 'MV - CPC' failed. 2017-08-06 19:47:10 -04:00
Thomas Harte
e9972aa0dd Added respect for the index-hole limit on reading, and an error phase. 2017-08-06 19:25:44 -04:00
Thomas Harte
1c9a744b01 Made an effort to start inspecting ID fields, at least. Discovered that my emulation has somehow stopped proceeding beyond sense interrupt status though. Fix one in that area: adjust ST0 just in time for the sense interrupt status response, as that'll need to specify the drive number properly. 2017-08-06 18:06:20 -04:00
Thomas Harte
e6d4bb29d8 Discovered correct sense interrupt status result if nobody is in the completed seeking state, and switched to it. It's a single 0x80 rather than two bytes. 2017-08-06 15:34:33 -04:00
Thomas Harte
6c5b562d97 Made an attempt at some of the correct seek/recalibrate behaviour: it's now asynchronous from command processing and able to work on up to four drives at once. I just probably am not yet hitting all the status flags I need to hit. 2017-08-06 15:22:07 -04:00
Thomas Harte
a7103f9333 Disks are now communicated to the 8272. Which is able to handle four of them. 2017-08-06 13:24:14 -04:00
Thomas Harte
c12425e141 Added storage for the extended four status registers, and made an attempt at implementing the two most trivial result-phase commands. Am slightly paused momentarily trying to figure out whether seek activity is orthogonal to read/write activity. 2017-08-06 12:55:57 -04:00
Thomas Harte
89f6de1383 Started on a real ugly-implementation coroutine approach, and implemented specify as a fairly trivial first command: it has no result phase, and is the only thing called by AMSDOS as part of the initialisation sequence. 2017-08-06 12:36:18 -04:00
Thomas Harte
77da582e88 Switched the container in which events are passed to int, with the intention of subclasses extending the receivable range. 2017-08-06 12:35:20 -04:00
Thomas Harte
34eaf75352 Fixed WAIT_FOR_TIME macro. 2017-08-06 12:08:54 -04:00
Thomas Harte
ffadb04761 Documented, and removed a couple of Event types that are WD-specific but had accidentally flown into here. Will need to figure out how best to expose the CRC result too, but I'm willing to let that one drop out naturally as I implement the 8272. 2017-08-06 11:36:36 -04:00
Thomas Harte
29288b690e Switched disk controllers to be instantiated explicitly in terms of cycles, created an Amstrad-specific subclass of the 8272 to record the direct programmatic availability of all disk motors bundled together, and otherwise adjusted to ensure the thing is clocked and that the motor is enabled and disabled appropriately. The 8272 is also now formally a subclass of the incoming MDM controller. 2017-08-06 09:45:16 -04:00
Thomas Harte
25fd3f7e50 Mildly increased work in here, still primarily oriented towards logging what I actually need to get done. 2017-08-05 22:26:59 -04:00
Thomas Harte
4d60b8801c Started trying to factor out just the PLL stream -> FM/MFM events part that is presently in the WD1770. 2017-08-05 22:26:15 -04:00
Thomas Harte
3e984e75b6 Strung up an empty shell that eventually should contain the 8272, and added appropriate IO decoding to the Amstrad. 2017-08-05 19:45:52 -04:00
Thomas Harte
9e8645ca7a Fixed ROM paging port decoding. It should have been fd00 if completely decoded, not df00, but also shouldn't be completely decoded. 2017-08-05 19:24:03 -04:00
Thomas Harte
caf3ac0645 Sought: (i) to instruct the CPC that it should be a 664, not a 464, if given a disk image (at least until I have RAM paging implemented for a 6128); (ii) to support ROM selection within the CPC and allow paging in of AMSDOS. 2017-08-05 19:20:38 -04:00
Thomas Harte
0f4343cd84 Merge pull request #177 from TomHarte/DSKFileFormat
Introduces parsing of the file format support for the simplest of CPC disks
2017-08-05 11:55:13 -04:00
Thomas Harte
192f232d3f Silenced warnings. 2017-08-05 11:53:29 -04:00
Thomas Harte
6e4d3b8a77 Added enough logic to produce some sort of version of a completely unprotected DSK. So enough to start bootstrapping an FDC emulation, at least. 2017-08-05 11:44:53 -04:00
Thomas Harte
8eda24261c Removed unnecessary header — it's implied by being a child of FileHolder. 2017-08-05 11:44:06 -04:00
Thomas Harte
75c59fefab Added an empty husk to begin support for Amstrad CPC disk image formats. 2017-08-05 10:02:10 -04:00
Thomas Harte
4b19cf60df Added omitted semicolon. 2017-08-05 09:18:55 -04:00
Thomas Harte
2b476554e7 Merge pull request #176 from TomHarte/AYDeferrer
Corrects the CPC's handling of AY time accumulation
2017-08-05 09:13:47 -04:00
Thomas Harte
b3788fed41 Fixed AY queuing behaviour as handled by the Amstrad. I think I need to come up with clearer semantics here. 2017-08-05 09:12:17 -04:00
Thomas Harte
b999c1d8aa Merge branch 'FilteredSync' 2017-08-04 16:52:14 -04:00
Thomas Harte
a63aa80dc9 Merge branch 'master' of github.com:TomHarte/CLK 2017-08-04 16:51:52 -04:00
Thomas Harte
63f57c8c4f Adjusted visible portion of frame; completely empirical, as I'm chasing a machine that shipped with a monitor. 2017-08-04 16:51:46 -04:00
Thomas Harte
7e6a6365c9 Adjusted visible portion of frame; completely empirical, as I'm chasing a machine that shipped with a monitor. 2017-08-04 16:50:58 -04:00
Thomas Harte
3dbf26ac49 Merge pull request #175 from TomHarte/FilteredSync
Introduces filtering of the CRTC's hsync signal into the gate array.
2017-08-04 16:38:52 -04:00
Thomas Harte
f075fea78c Introduces filtering of the CRTC's vsync signal into the gate array. 2017-08-04 16:36:55 -04:00
Thomas Harte
cc8380c286 Merge pull request #174 from TomHarte/ProperBorder
Removes quick-hack solution for border drawing
2017-08-04 12:15:22 -04:00
Thomas Harte
c0f0c68f4f Corrects quick-hack version of border drawing: the assumption that the colour must be the same over a plotted period. Also corrects my entry for colour 15. 2017-08-04 12:13:05 -04:00
Thomas Harte
c2bb019381 Merge pull request #173 from TomHarte/TimingFixes
Corrects two misimplementations of the CPC's interrupt counter
2017-08-04 09:06:42 -04:00
Thomas Harte
26ce6cdab2 Permitted register 3 to dictate vertical sync length. 2017-08-04 08:56:36 -04:00
Thomas Harte
d9097facf1 Found documentation that makes more sense, and in practice seems to be more correct: the test after vertical sync is for greater than 32, not less. Also I decided to chance my arm on counter reset also resetting interrupt request. The raster effects of Ghouls 'n' Ghosts is now pretty much correct but one line off. I think probably either something is off in my wait-two logic on the post-vsync timer event, or possibly the vsync bit exposed via the PPI doesn't mean exactly what I think it means. 2017-08-04 08:56:09 -04:00
Thomas Harte
b927500487 Clarified code a little, but this is mostly fiddling in the margins. 2017-08-03 22:00:30 -04:00
Thomas Harte
e71eabedf9 Fixed timer clearing tet. 2017-08-03 21:30:04 -04:00
Thomas Harte
33ed27c3ad Minor tidiness: included missing headers, and spaced out the ROM type and key lists for readability. 2017-08-03 12:45:42 -04:00
Thomas Harte
45cbab6751 Merge pull request #172 from TomHarte/KeyboardMachines
Publicly declares the ZX80/81 and Amstrad CPC as keyboard machines
2017-08-03 12:39:22 -04:00
Thomas Harte
a7b74d6164 Merge pull request #171 from TomHarte/EverMoreTZX
Further expands the implemented subset of the TZX file format
2017-08-03 12:38:44 -04:00
Thomas Harte
575b1dba75 Formally declared the ZX80/81 and Amstrad CPC as keyboard machines in their public interface. Which means not having to repeat the meaning of set_key_state and clear_all_keys. So: a minor DRY improvement. 2017-08-03 12:38:22 -04:00
Thomas Harte
a3b16b6dfa Further beefs up the list of chunks that this TZX parser can either comprehend or skip. 2017-08-03 12:13:41 -04:00
Thomas Harte
c8f4de6f11 Merge pull request #170 from TomHarte/KeyboardCraziness
Separates knowing the mapping from ASCII to machine keys from the act of typing them
2017-08-03 11:53:42 -04:00
Thomas Harte
bbb17acf3a Expanded interface so that an external machine caller can request a string be typed without any knowledge of whatever it intends to do re: CharacterMappers. Which is immediately useful in paste functionality. 2017-08-03 11:50:50 -04:00
Thomas Harte
ad3a98387f Within the Typer framework: hatched out CharacterMapper as a distinct thing from the target for keypresses, better to formalise responsibility but also to make it easy cleanly to sever that stuff into its own little part. 2017-08-03 11:42:31 -04:00
Thomas Harte
985fbf59c2 Merge pull request #169 from TomHarte/Z80HasA
Converts the Z80 into a BusAdaptor-type component
2017-08-02 22:42:49 -04:00
Thomas Harte
2f2071be8a These should actually both be in the public header, as the types are used in an exposed method. 2017-08-02 22:18:30 -04:00
Thomas Harte
6d510e4e70 Made it no longer public knowledge that any sort of Typer is involved in being a ZX80/81. 2017-08-02 22:17:22 -04:00
Thomas Harte
8e0736fbe6 Reinstated typing ability, albeit with an ugly inline insertion. But I think I can defer dealing with typers to another pull request. The whole issue of keyboard mapping probably needs reappraisal. 2017-08-02 22:16:09 -04:00
Thomas Harte
681d1e2f8d Breaking its typer for now, adapted the ZX80/81 to having a Z80, not being one. 2017-08-02 22:12:59 -04:00
Thomas Harte
42e70ef993 Adjusted slightly as per Z80 change, and to pull everything internally declared into the Amstrad CPC namespace. 2017-08-02 22:11:03 -04:00
Thomas Harte
039811ce6a Switched the Z80 to being something a machine has, not something a machine is. 2017-08-02 22:09:59 -04:00
Thomas Harte
a54ccd1969 Merge pull request #168 from TomHarte/CPC
Adds an initial Amstrad CPC 464 emuation
2017-08-02 20:50:02 -04:00
Thomas Harte
707821ca55 Added the normal readme to explain what's omitted here. 2017-08-02 20:45:14 -04:00
Thomas Harte
d3bf8fa53b Upped the documentation. 2017-08-02 20:37:26 -04:00
Thomas Harte
f5e2dd410e Constrained output to the centre 90%. 2017-08-02 19:55:44 -04:00
Thomas Harte
3ca9c38777 Attempted to move to more accurate bus reading — if control lines are set then all subsequent data inputs should act according to the current control lines; changes to port input should be reflected live upon readings, etc. 2017-08-02 19:45:58 -04:00
Thomas Harte
2d2cefb0b0 Adjusted factoring to introduce support for block 10. 2017-08-02 14:36:47 -04:00
Thomas Harte
2fd071e45d Made an honest attempt at outputting turbo speed data block data. The CPC now at least starts to load. 2017-08-02 14:24:34 -04:00
Thomas Harte
d7a5c3f49a Added support for the ID 20 block and fixed a minor error in my skip-the-contents version of block 11: length is three bytes long, not two. This gives me enough structure properly to get to the end of my current test CDT, albeit without making any of the noises. 2017-08-02 14:12:34 -04:00
Thomas Harte
819761f9fb Fixed another uninitialised pointer. 2017-08-02 13:56:35 -04:00
Thomas Harte
e50adf1cc8 Were my TZX support up to it, this would likely be sufficient for tape emulation. 2017-08-02 13:50:14 -04:00
Thomas Harte
dcab10f53e Ensured the AY's async queue doesn't just fill and fill. 2017-08-02 07:38:35 -04:00
Thomas Harte
633d8965e2 Removed accidental logging commit. 2017-08-02 07:38:14 -04:00
Thomas Harte
f602f9b6ec Adds an attempt to clock the AY. 2017-08-02 07:21:33 -04:00
Thomas Harte
f7e66dea61 Added a compound divide and convert. 2017-08-02 07:21:21 -04:00
Thomas Harte
bda9441620 Made an attempt to clock the AY. 2017-08-02 07:20:59 -04:00
Thomas Harte
4d5d5041df Attempted to ensure a clean startup. 2017-08-01 22:18:42 -04:00
Thomas Harte
587eb3a67c Factored interrupt counting out of the CRTCBusHandler. 2017-08-01 22:15:39 -04:00
Thomas Harte
6ca07f1e28 I guess it might end up living somewhere else, but introduced a header with the compiler-specific stuff to allow me to force things inline. 2017-08-01 22:04:58 -04:00
Thomas Harte
8d39a20088 Added proper output of mode 3, were anything ever to try to use it. 2017-08-01 21:51:41 -04:00
Thomas Harte
4b6370eb86 Realised my colour error: mapping the ROM numbers as though they were the hardware numbers. Having fixed that, spotted that I was deserialising R and B the wrong way around and dividing by too much. Colours now appear to be correct. 2017-08-01 21:47:52 -04:00
Thomas Harte
c6e340a8a2 Wired up the vsync signal. Pen 15 no longer flashes like crazy. Still can't figure out why the palette is so askew; was looking for perhaps some sort of detection of a green screen rather than a colour one, but there's no obvious input for that. 2017-08-01 21:21:59 -04:00
Thomas Harte
31c7153301 Corrected bit to colour mapping for modes 0 and 1. The total palette is still way off but there's consistency between modes now. 2017-08-01 20:52:42 -04:00
Thomas Harte
7e04d00cc1 Fixed key values, causing the new set of keys to work, decreased quantity of output and ensured that pixels appear in modes 0 and 2. 2017-08-01 20:39:10 -04:00
Thomas Harte
9d43784c65 Significantly increased quantity of keys forwarded. 2017-08-01 20:37:55 -04:00
Thomas Harte
eca9586a0f Fixed: input value is no longer overwritten by 0xff. The '0' key now works. 2017-08-01 20:19:02 -04:00
Thomas Harte
0267bc237f Added the ability to set a port input, and relaxed bus state testing. I think my on-demand bus reactions here are inappropriate, so more work to do here probably. 2017-08-01 18:04:51 -04:00
Thomas Harte
2e4577f741 Made a game attempt at implementing a (sticky) keyboard. No effect yet. 2017-08-01 17:52:05 -04:00
Thomas Harte
f5b278d683 Added enough stuff to put the emulated Amstrad CPC in a state of knowing whether its '0' key is pressed. 2017-08-01 17:31:56 -04:00
Thomas Harte
e6854ff8db Corrected typo: the input to an AY is BDIR, not BCDIR. 2017-08-01 17:06:57 -04:00
Thomas Harte
3b292273c7 Fixed: BC2 is always implicitly set. The machine is now periodically checking the AY's register 14 (i.e. the first input port), so probably there's enough here now to implement keyboard input. 2017-08-01 17:05:11 -04:00
Thomas Harte
cb732e5d5f Made an attempt to wire in an [unclocked] AY, in an endeavour to get to keyboard reading. 2017-08-01 17:01:58 -04:00
Thomas Harte
2d4e202be3 Completed dangling comment. 2017-08-01 17:01:36 -04:00
Thomas Harte
64da8e17d1 Fixed: of course this should take a reference to an existing port handler rather than hatching its own; otherwise additional communication with a port handler by an i8255 owner doesn't work as intended. 2017-08-01 17:01:20 -04:00
Thomas Harte
08ad35efd9 It's barely an implementation of the 8255, but ensured that data is bounced into the PortHandler, conveniently assuming the interaction mode used by the CPC. 2017-08-01 16:34:13 -04:00
Thomas Harte
58b98267fc Formally transferred ownership of PIO accesses to an incoming template, and decided to start being explicit about how to specify the interfaces and provide fallbacks for optional behaviour for the new, clean generation of interfaces. A full-project sweep will inevitably occur but I'll try to tie off this branch first. 2017-08-01 16:15:19 -04:00
Thomas Harte
ace71280a0 Removed implementation file; this is only ever going to be a template. 2017-08-01 16:00:17 -04:00
Thomas Harte
a27946102a Took a shot at the interrupt counter. Attempts at keyboard reading now recur so it'll probably do for now. I think that next puts me into the realm of needing to implement the 8255. 2017-08-01 15:49:16 -04:00
Thomas Harte
1d99c116e7 Actually, this is probably more correct: increment and then compare, but increment the refresh address once more after the final character, to avoid repeating it. 2017-08-01 15:29:37 -04:00
Thomas Harte
ee27e16fb1 Switched to post-tests increment. Seems to give proper screen width, but also eliminates that 'compare to +1' step that felt unlikely. 2017-08-01 15:19:25 -04:00
Thomas Harte
6ac7132799 Had a quick go at properly outputting Mode 1, adding wiring to communicate palette and mode changes to the CRTC bus handler. Colours are off but it's sufficient for now. 2017-08-01 15:16:13 -04:00
Thomas Harte
ca42abab70 Doubled up to ensure that every byte that should be inspected is represented. This makes it clearer that I'm on the right road. A garbled version of 'Amstrad 64k Microcomputer' can be discerned, in a weird grayscale and with the right-hand column missing and skewed output as a result. 2017-08-01 07:56:44 -04:00
Thomas Harte
933d69a256 Fixed slightly: the CPC wiki has a typo. It's 12 and 13 that move up to 14 and 15. 2017-08-01 07:51:13 -04:00
Thomas Harte
3b1db14817 Made a quick attempt at properly updating the refresh address. 2017-08-01 07:36:03 -04:00
Thomas Harte
10a5581aea Made first attempt at offering some sort of pictographic of actual RAM contents. 2017-08-01 07:34:12 -04:00
Thomas Harte
3ae699964f Ensured an actual pixel stream is supplied for pixel regions. Though it's just a long stream of white pixels for now. So visual output is unchanged. 2017-08-01 07:24:29 -04:00
Thomas Harte
9d953421d8 After a quick check, added a couple of other _delegate initialisations. I should probably find a way to template this. 2017-08-01 07:07:43 -04:00
Thomas Harte
763e3b65d1 Ensured a proper initial value for delegate_. 2017-07-31 22:46:06 -04:00
Thomas Harte
42dd27c9b1 Shunted method bodies inline, given that there's no need for a declaration/definition distinction. 2017-07-31 22:39:25 -04:00
Thomas Harte
2b168f7383 Disabled the address sanitiser as an every-time run again, as it just pushes my computer a bit too far. 2017-07-31 22:32:56 -04:00
Thomas Harte
0536f089e1 Eliminated old-[personal-]fashioned line break. 2017-07-31 22:32:26 -04:00
Thomas Harte
3df13cddd4 As per my keenness for cleanliness improvements corresponding to my ever-increasing C++ ability: turned the Amstrad into something that a factory produces, allowing me completely to hide a bunch of implementation details. 2017-07-31 22:32:04 -04:00
Thomas Harte
e3f677fa37 I was under-counting row lines. Adjusted comparison. The emulator now produces a solid white square of approximately correct proportions. I'm sure that filling in pixels will reveal the next set of bugs. 2017-07-31 22:21:46 -04:00
Thomas Harte
c2253c1e0f Fixed multiplier: the dot clock I've used to instantiate the CRT is the pixel clock, not the character clock. 2017-07-31 22:17:46 -04:00
Thomas Harte
5c68b6cc21 Fixed display enable reset when there's no adjustment area. A practical lesson in failure to factor. 2017-07-31 22:16:08 -04:00
Thomas Harte
ffaa627820 Fixed frame restart when there is no adjustment period. 2017-07-31 22:13:45 -04:00
Thomas Harte
f742fd5d4a Made basic attempt to get something on screen: white where the display is enabled, black for the border. 2017-07-31 22:13:20 -04:00
Thomas Harte
69b99fe127 Transferred ownership of the CRT to the CRTC bus handler, to give it easy access. 2017-07-31 22:04:52 -04:00
Thomas Harte
5a396f6787 Added an explicit cast. 2017-07-31 22:04:31 -04:00
Thomas Harte
cb0dc7b434 I'm sure it's not going to be this easy, but this is a genuine attempt at full horizontal and vertical timing. 2017-07-31 22:01:54 -04:00
Thomas Harte
e28829bd1b Corrected CRTC timing, gave it someone to talk to and a means with which to talk. 2017-07-31 20:14:46 -04:00
Thomas Harte
68ceeab610 Created a 6845 class and started pushing data at it and clocking it. It doesn't currently have the concept of a bus but will do, hence the in-header implementation. 2017-07-31 19:56:59 -04:00
Thomas Harte
68dca9d047 Made a first attempt at ROM paging, with pretty much the same scheme that'll be needed for 128kb support. 2017-07-31 19:37:28 -04:00
Thomas Harte
d88ca151f4 Added a first attempt at output port decoding. Just logging for now. 2017-07-31 19:25:10 -04:00
Thomas Harte
3c90218c3d With a very basic stab at something a bit like the memory map (sans paging), execution begins. 2017-07-31 19:15:43 -04:00
Thomas Harte
afd409c883 Ensured that ROM images are loaded and passed to the Amstrad CPC. 2017-07-31 18:44:49 -04:00
Thomas Harte
26b6c03a2a Re-enabled the address sanitiser as a development tool. 2017-07-31 07:30:07 -04:00
Thomas Harte
9c04d851e4 Added the basics necessary to get the CPU ticking over, at a nominal 4Mhz but with the wait states that I currently believe to be accurate. 2017-07-31 07:29:50 -04:00
Thomas Harte
1d6fe11906 Added an instance of Outputs::CRT::CRT. So progress is now: select CDT, up comes a blank window. 2017-07-31 07:16:51 -04:00
Thomas Harte
c0f1313830 Performed sufficient wiring to get to the point where attempting to load a CDT creates an instance of the Amstrad CPC and then fails only because the thing vends a nullptr CRT. 2017-07-30 22:05:29 -04:00
Thomas Harte
fb51fadf00 Merge branch 'master' into CPC 2017-07-30 21:29:31 -04:00
Thomas Harte
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
284 changed files with 16703 additions and 9343 deletions

View File

@@ -95,9 +95,17 @@ template <class T> class WrappedInt {
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_; }
@@ -153,7 +161,7 @@ class HalfCycles: public WrappedInt<HalfCycles> {
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 Cycles cycles) : WrappedInt<HalfCycles>(cycles.as_int() * 2) {}
inline HalfCycles(const HalfCycles &half_cycles) : WrappedInt<HalfCycles>(half_cycles.length_) {}
/// @returns The number of whole cycles completely covered by this span of half cycles.
@@ -167,6 +175,17 @@ class HalfCycles: public WrappedInt<HalfCycles> {
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;
}
};
/*!

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

@@ -7,7 +7,7 @@
//
#include "1770.hpp"
#include "../../Storage/Disk/Encodings/MFM.hpp"
#include "../../Storage/Disk/Encodings/MFM/Constants.hpp"
using namespace WD;
@@ -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),
interesting_event_mask_(static_cast<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(static_cast<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(static_cast<int>(Event1770::Command));
}
}
break;
@@ -88,7 +75,7 @@ uint8_t WD1770::get_register(int address) {
switch(status_.type) {
case Status::One:
status |=
(get_is_track_zero() ? Flag::TrackZero : 0) |
(get_drive().get_is_track_zero() ? Flag::TrackZero : 0) |
(status_.seek_error ? Flag::SeekError : 0);
// TODO: index hole
break;
@@ -104,11 +91,11 @@ uint8_t WD1770::get_register(int address) {
}
if(!has_motor_on_line()) {
status |= get_drive_is_ready() ? 0 : Flag::NotReady;
status |= get_drive().get_is_ready() ? 0 : Flag::NotReady;
if(status_.type == Status::One)
status |= (head_is_loaded_ ? Flag::HeadLoaded : 0);
} else {
status |= (get_motor_on() ? Flag::MotorOn : 0);
status |= (get_drive().get_motor_on() ? Flag::MotorOn : 0);
if(status_.type == Status::One)
status |= (status_.spin_up ? Flag::SpinUp : 0);
}
@@ -128,151 +115,27 @@ 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();
unsigned int number_of_cycles = static_cast<unsigned int>(cycles.as_int());
if(delay_time_ <= number_of_cycles) {
delay_time_ = 0;
posit_event(Event::Timer);
posit_event(static_cast<int>(Event1770::Timer));
} else {
delay_time_ -= number_of_cycles;
}
}
}
void WD1770::process_input_bit(int value, unsigned int cycles_since_index_hole) {
if(data_mode_ == DataMode::Writing) return;
shift_register_ = (shift_register_ << 1) | value;
bits_since_token_++;
if(data_mode_ == DataMode::Scanning) {
Token::Type token_type = Token::Byte;
if(!is_double_density_) {
switch(shift_register_ & 0xffff) {
case Storage::Encodings::MFM::FMIndexAddressMark:
token_type = Token::Index;
crc_generator_.reset();
crc_generator_.add(latest_token_.byte_value = Storage::Encodings::MFM::IndexAddressByte);
break;
case Storage::Encodings::MFM::FMIDAddressMark:
token_type = Token::ID;
crc_generator_.reset();
crc_generator_.add(latest_token_.byte_value = Storage::Encodings::MFM::IDAddressByte);
break;
case Storage::Encodings::MFM::FMDataAddressMark:
token_type = Token::Data;
crc_generator_.reset();
crc_generator_.add(latest_token_.byte_value = Storage::Encodings::MFM::DataAddressByte);
break;
case Storage::Encodings::MFM::FMDeletedDataAddressMark:
token_type = Token::DeletedData;
crc_generator_.reset();
crc_generator_.add(latest_token_.byte_value = Storage::Encodings::MFM::DeletedDataAddressByte);
break;
default:
break;
}
} else {
switch(shift_register_ & 0xffff) {
case Storage::Encodings::MFM::MFMIndexSync:
bits_since_token_ = 0;
is_awaiting_marker_value_ = true;
token_type = Token::Sync;
latest_token_.byte_value = Storage::Encodings::MFM::MFMIndexSyncByteValue;
break;
case Storage::Encodings::MFM::MFMSync:
bits_since_token_ = 0;
is_awaiting_marker_value_ = true;
crc_generator_.set_value(Storage::Encodings::MFM::MFMPostSyncCRCValue);
token_type = Token::Sync;
latest_token_.byte_value = Storage::Encodings::MFM::MFMSyncByteValue;
break;
default:
break;
}
}
if(token_type != Token::Byte) {
latest_token_.type = token_type;
bits_since_token_ = 0;
posit_event(Event::Token);
return;
}
}
if(bits_since_token_ == 16) {
latest_token_.type = Token::Byte;
latest_token_.byte_value = (uint8_t)(
((shift_register_ & 0x0001) >> 0) |
((shift_register_ & 0x0004) >> 1) |
((shift_register_ & 0x0010) >> 2) |
((shift_register_ & 0x0040) >> 3) |
((shift_register_ & 0x0100) >> 4) |
((shift_register_ & 0x0400) >> 5) |
((shift_register_ & 0x1000) >> 6) |
((shift_register_ & 0x4000) >> 7));
bits_since_token_ = 0;
if(is_awaiting_marker_value_ && is_double_density_) {
is_awaiting_marker_value_ = false;
switch(latest_token_.byte_value) {
case Storage::Encodings::MFM::IndexAddressByte:
latest_token_.type = Token::Index;
break;
case Storage::Encodings::MFM::IDAddressByte:
latest_token_.type = Token::ID;
break;
case Storage::Encodings::MFM::DataAddressByte:
latest_token_.type = Token::Data;
break;
case Storage::Encodings::MFM::DeletedDataAddressByte:
latest_token_.type = Token::DeletedData;
break;
default: break;
}
}
crc_generator_.add(latest_token_.byte_value);
posit_event(Event::Token);
return;
}
}
void WD1770::process_index_hole() {
index_hole_count_++;
posit_event(Event::IndexHole);
if(index_hole_count_target_ == index_hole_count_) {
posit_event(Event::IndexHoleTarget);
index_hole_count_target_ = -1;
}
// motor power-down
if(index_hole_count_ == 9 && !status_.busy && has_motor_on_line()) {
set_motor_on(false);
}
// head unload
if(index_hole_count_ == 15 && !status_.busy && has_head_load_line()) {
set_head_load_request(false);
}
}
void WD1770::process_write_completed() {
posit_event(Event::DataWritten);
}
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = mask; return; case __LINE__:
#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; interesting_event_mask_ = Event::Timer; delay_time_ = ms * 8000; if(delay_time_) return; case __LINE__:
#define WAIT_FOR_BYTES(count) resume_point_ = __LINE__; interesting_event_mask_ = Event::Token; distance_into_section_ = 0; return; case __LINE__: if(latest_token_.type == Token::Byte) distance_into_section_++; if(distance_into_section_ < count) { interesting_event_mask_ = Event::Token; return; }
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(mask); return; case __LINE__:
#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; delay_time_ = ms * 8000; WAIT_FOR_EVENT(Event1770::Timer);
#define WAIT_FOR_BYTES(count) resume_point_ = __LINE__; distance_into_section_ = 0; WAIT_FOR_EVENT(Event::Token); if(get_latest_token().type == Token::Byte) distance_into_section_++; if(distance_into_section_ < count) { interesting_event_mask_ = static_cast<int>(Event::Token); return; }
#define BEGIN_SECTION() switch(resume_point_) { default:
#define END_SECTION() 0; }
#define READ_ID() \
if(new_event_type == Event::Token) { \
if(!distance_into_section_ && latest_token_.type == Token::ID) {data_mode_ = DataMode::Reading; distance_into_section_++; } \
else if(distance_into_section_ && distance_into_section_ < 7 && latest_token_.type == Token::Byte) { \
header_[distance_into_section_ - 1] = latest_token_.byte_value; \
if(new_event_type == static_cast<int>(Event::Token)) { \
if(!distance_into_section_ && get_latest_token().type == Token::ID) {set_data_mode(DataMode::Reading); distance_into_section_++; } \
else if(distance_into_section_ && distance_into_section_ < 7 && get_latest_token().type == Token::Byte) { \
header_[distance_into_section_ - 1] = get_latest_token().byte_value; \
distance_into_section_++; \
} \
}
@@ -285,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;
// +--------+----------+-------------------------+
@@ -305,8 +168,26 @@ void WD1770::process_write_completed() {
// ! 4 ! Forc int ! 1 1 0 1 i3 i2 i1 i0 !
// +--------+----------+-------------------------+
void WD1770::posit_event(Event new_event_type) {
if(!(interesting_event_mask_ & (int)new_event_type)) return;
void WD1770::posit_event(int new_event_type) {
if(new_event_type == static_cast<int>(Event::IndexHole)) {
index_hole_count_++;
if(index_hole_count_target_ == index_hole_count_) {
posit_event(static_cast<int>(Event1770::IndexHoleTarget));
index_hole_count_target_ = -1;
}
// motor power-down
if(index_hole_count_ == 9 && !status_.busy && has_motor_on_line()) {
set_motor_on(false);
}
// head unload
if(index_hole_count_ == 15 && !status_.busy && has_head_load_line()) {
set_head_load_request(false);
}
}
if(!(interesting_event_mask_ & static_cast<int>(new_event_type))) return;
interesting_event_mask_ &= ~new_event_type;
Status new_status;
@@ -315,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) {
@@ -323,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;
@@ -372,11 +253,11 @@ 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:
if((command_&0x08) || get_motor_on()) goto test_type1_type;
if((command_&0x08) || get_drive().get_motor_on()) goto test_type1_type;
SPIN_UP();
test_type1_type:
@@ -399,11 +280,11 @@ void WD1770::posit_event(Event new_event_type) {
if(step_direction_) track_++; else track_--;
perform_step:
if(!step_direction_ && get_is_track_zero()) {
if(!step_direction_ && get_drive().get_is_track_zero()) {
track_ = 0;
goto verify;
}
step(step_direction_ ? 1 : -1);
get_drive().step(step_direction_ ? 1 : -1);
unsigned int time_to_wait;
switch(command_ & 3) {
default:
@@ -429,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(static_cast<int>(Event::IndexHole) | static_cast<int>(Event::Token));
READ_ID();
if(index_hole_count_ == 6) {
@@ -439,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;
});
@@ -491,11 +372,11 @@ void WD1770::posit_event(Event new_event_type) {
begin_type2_load_head:
set_head_load_request(true);
if(head_is_loaded_) goto test_type2_delay;
WAIT_FOR_EVENT(Event::HeadLoad);
WAIT_FOR_EVENT(Event1770::HeadLoad);
goto test_type2_delay;
begin_type2_spin_up:
if(get_motor_on()) goto test_type2_delay;
if(get_drive().get_motor_on()) goto test_type2_delay;
// Perform spin up.
SPIN_UP();
@@ -505,7 +386,7 @@ void WD1770::posit_event(Event new_event_type) {
WAIT_FOR_TIME(30);
test_type2_write_protection:
if(command_&0x20 && get_drive_is_read_only()) {
if(command_&0x20 && get_drive().get_is_read_only()) {
update_status([] (Status &status) {
status.write_protect = true;
});
@@ -513,7 +394,7 @@ void WD1770::posit_event(Event new_event_type) {
}
type2_get_header:
WAIT_FOR_EVENT(Event::IndexHole | Event::Token);
WAIT_FOR_EVENT(static_cast<int>(Event::IndexHole) | static_cast<int>(Event::Token));
READ_ID();
if(index_hole_count_ == 5) {
@@ -525,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;
@@ -554,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;
@@ -581,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;
@@ -616,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);
}
@@ -669,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();
@@ -712,11 +590,11 @@ void WD1770::posit_event(Event new_event_type) {
begin_type3_load_head:
set_head_load_request(true);
if(head_is_loaded_) goto type3_test_delay;
WAIT_FOR_EVENT(Event::HeadLoad);
WAIT_FOR_EVENT(Event1770::HeadLoad);
goto type3_test_delay;
begin_type3_spin_up:
if((command_&0x08) || get_motor_on()) goto type3_test_delay;
if((command_&0x08) || get_drive().get_motor_on()) goto type3_test_delay;
SPIN_UP();
type3_test_delay:
@@ -733,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(static_cast<int>(Event::IndexHole) | static_cast<int>(Event::Token));
if(new_event_type == static_cast<int>(Event::Token)) {
if(!distance_into_section_ && get_latest_token().type == Token::ID) {set_data_mode(DataMode::Reading); distance_into_section_++; }
else if(distance_into_section_ && distance_into_section_ < 7 && get_latest_token().type == Token::Byte) {
if(status_.data_request) {
update_status([] (Status &status) {
status.lost_data = true;
});
goto wait_for_command;
}
header_[distance_into_section_ - 1] = data_ = latest_token_.byte_value;
header_[distance_into_section_ - 1] = data_ = get_latest_token().byte_value;
track_ = header_[0];
update_status([] (Status &status) {
status.data_request = true;
@@ -751,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;
});
@@ -774,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(static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole));
if(index_hole_count_) {
goto wait_for_command;
}
@@ -784,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;
});
@@ -797,7 +675,7 @@ void WD1770::posit_event(Event new_event_type) {
});
write_track_test_write_protect:
if(get_drive_is_read_only()) {
if(get_drive().get_is_read_only()) {
update_status([] (Status &status) {
status.write_protect = true;
});
@@ -815,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;
@@ -844,7 +720,7 @@ void WD1770::posit_event(Event new_event_type) {
case 0xfd: case 0xfe:
// clock is 0xc7 = 1010 0000 0010 1010 = 0xa022
write_raw_short(
(uint16_t)(
static_cast<uint16_t>(
0xa022 |
((data_ & 0x80) << 7) |
((data_ & 0x40) << 6) |
@@ -856,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;
@@ -907,30 +781,9 @@ void WD1770::update_status(std::function<void(Status &)> updater) {
}
void WD1770::set_head_load_request(bool head_load) {}
void WD1770::set_motor_on(bool motor_on) {}
void WD1770::set_head_loaded(bool head_loaded) {
head_is_loaded_ = head_loaded;
if(head_loaded) posit_event(Event::HeadLoad);
}
void WD1770::write_bit(int bit) {
if(is_double_density_) {
Controller::write_bit(!bit && !last_bit_);
Controller::write_bit(!!bit);
last_bit_ = bit;
} else {
Controller::write_bit(true);
Controller::write_bit(!!bit);
}
}
void WD1770::write_byte(uint8_t byte) {
for(int c = 0; c < 8; c++) write_bit((byte << c)&0x80);
crc_generator_.add(byte);
}
void WD1770::write_raw_short(uint16_t value) {
for(int c = 0; c < 16; c++) {
Controller::write_bit(!!((value << c)&0x8000));
}
if(head_loaded) posit_event(static_cast<int>(Event1770::HeadLoad));
}

View File

@@ -9,8 +9,7 @@
#ifndef _770_hpp
#define _770_hpp
#include "../../Storage/Disk/DiskController.hpp"
#include "../../NumberTheory/CRC.hpp"
#include "../../Storage/Disk/Controller/MFMDiskController.hpp"
namespace WD {
@@ -18,7 +17,7 @@ namespace WD {
Provides an emulation of various Western Digital drive controllers, including the
WD1770, WD1772, FDC1773 and FDC1793.
*/
class WD1770: public Storage::Disk::Controller {
class WD1770: public Storage::Disk::MFMController {
public:
enum Personality {
P1770, // implies automatic motor-on management, with Type 2 commands offering a spin-up disable
@@ -34,7 +33,7 @@ class WD1770: public Storage::Disk::Controller {
WD1770(Personality p);
/// Sets the value of the double-density input; when @c is_double_density is @c true, reads and writes double-density format data.
void set_is_double_density(bool is_double_density);
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);
@@ -77,6 +76,7 @@ class WD1770: public Storage::Disk::Controller {
protected:
virtual void set_head_load_request(bool head_load);
virtual void set_motor_on(bool motor_on);
void set_head_loaded(bool head_loaded);
private:
@@ -107,66 +107,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_;
unsigned 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);
// 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,9 +13,83 @@
#include <typeinfo>
#include <cstdio>
#include "Implementation/6522Storage.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
namespace MOS {
namespace MOS6522 {
enum Port {
A = 0,
B = 1
};
enum Line {
One = 0,
Two = 1
};
/*!
Provides the mechanism for just-int-time communication from a 6522; the normal use case is to compose a
6522 and a subclass of PortHandler in order to reproduce a 6522 and its original bus wiring.
*/
class PortHandler {
public:
/// Requests the current input value of @c port from the port handler.
uint8_t get_port_input(Port port) { return 0xff; }
/// Sets the current output value of @c port and provides @c direction_mask, indicating which pins are marked as output.
void set_port_output(Port port, uint8_t value, uint8_t direction_mask) {}
/// Sets the current logical output level for line @c line on port @c port.
void set_control_line_output(Port port, Line line, bool value) {}
/// Sets the current logical value of the interrupt line.
void set_interrupt_status(bool status) {}
};
/*!
Provided as an optional alternative base to @c PortHandler for port handlers; via the delegate pattern adds
a virtual level of indirection for receiving changes to the interrupt line.
*/
class IRQDelegatePortHandler: public PortHandler {
public:
class Delegate {
public:
/// Indicates that the interrupt status has changed for the IRQDelegatePortHandler provided.
virtual void mos6522_did_change_interrupt_status(void *irq_delegate) = 0;
};
/// Sets the delegate that will receive notification of changes in the interrupt line.
void set_interrupt_delegate(Delegate *delegate);
/// Overrides PortHandler::set_interrupt_status, notifying the delegate if one is set.
void set_interrupt_status(bool new_status);
private:
Delegate *delegate_ = nullptr;
};
class MOS6522Base: public MOS6522Storage {
public:
/// Sets the input value of line @c line on port @c port.
void set_control_line_input(Port port, Line line, bool value);
/// Runs for a specified number of half cycles.
void run_for(const HalfCycles half_cycles);
/// Runs for a specified number of cycles.
void run_for(const Cycles cycles);
/// @returns @c true if the IRQ line is currently active; @c false otherwise.
bool get_interrupt_line();
private:
inline void do_phase1();
inline void do_phase2();
virtual void reevaluate_interrupts() = 0;
};
/*!
Implements a template for emulation of the MOS 6522 Versatile Interface Adaptor ('VIA').
@@ -28,353 +102,27 @@ namespace MOS {
Consumers should derive their own curiously-recurring-template-pattern subclass,
implementing bus communications as required.
*/
template <class T> class MOS6522 {
private:
enum InterruptFlag: uint8_t {
CA2ActiveEdge = 1 << 0,
CA1ActiveEdge = 1 << 1,
ShiftRegister = 1 << 2,
CB2ActiveEdge = 1 << 3,
CB1ActiveEdge = 1 << 4,
Timer2 = 1 << 5,
Timer1 = 1 << 6,
};
template <class T> class MOS6522: public MOS6522Base {
public:
enum Port {
A = 0,
B = 1
};
enum Line {
One = 0,
Two = 1
};
MOS6522(T &bus_handler) noexcept : bus_handler_(bus_handler) {}
MOS6522(const MOS6522 &) = delete;
/*! Sets a register value. */
inline void set_register(int address, uint8_t value) {
address &= 0xf;
// printf("6522 [%s]: %0x <- %02x\n", typeid(*this).name(), address, value);
switch(address) {
case 0x0:
registers_.output[1] = value;
static_cast<T *>(this)->set_port_output(Port::B, value, registers_.data_direction[1]); // TODO: handshake
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | ((registers_.peripheral_control&0x20) ? 0 : InterruptFlag::CB2ActiveEdge));
reevaluate_interrupts();
break;
case 0xf:
case 0x1:
registers_.output[0] = value;
static_cast<T *>(this)->set_port_output(Port::A, value, registers_.data_direction[0]); // TODO: handshake
registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | ((registers_.peripheral_control&0x02) ? 0 : InterruptFlag::CB2ActiveEdge));
reevaluate_interrupts();
break;
// // No handshake, so write directly
// registers_.output[0] = value;
// static_cast<T *>(this)->set_port_output(0, value);
// break;
case 0x2:
registers_.data_direction[1] = value;
break;
case 0x3:
registers_.data_direction[0] = value;
break;
// Timer 1
case 0x6: case 0x4: registers_.timer_latch[0] = (registers_.timer_latch[0]&0xff00) | value; break;
case 0x5: case 0x7:
registers_.timer_latch[0] = (registers_.timer_latch[0]&0x00ff) | (uint16_t)(value << 8);
registers_.interrupt_flags &= ~InterruptFlag::Timer1;
if(address == 0x05) {
registers_.next_timer[0] = registers_.timer_latch[0];
timer_is_running_[0] = true;
}
reevaluate_interrupts();
break;
// Timer 2
case 0x8: registers_.timer_latch[1] = value; break;
case 0x9:
registers_.interrupt_flags &= ~InterruptFlag::Timer2;
registers_.next_timer[1] = registers_.timer_latch[1] | (uint16_t)(value << 8);
timer_is_running_[1] = true;
reevaluate_interrupts();
break;
// Shift
case 0xa: registers_.shift = value; break;
// Control
case 0xb:
registers_.auxiliary_control = value;
break;
case 0xc:
// printf("Peripheral control %02x\n", value);
registers_.peripheral_control = value;
// TODO: simplify below; trying to avoid improper logging of unimplemented warnings in input mode
if(value & 0x08) {
switch(value & 0x0e) {
default: printf("Unimplemented control line mode %d\n", (value >> 1)&7); break;
case 0x0c: static_cast<T *>(this)->set_control_line_output(Port::A, Line::Two, false); break;
case 0x0e: static_cast<T *>(this)->set_control_line_output(Port::A, Line::Two, true); break;
}
}
if(value & 0x80) {
switch(value & 0xe0) {
default: printf("Unimplemented control line mode %d\n", (value >> 5)&7); break;
case 0xc0: static_cast<T *>(this)->set_control_line_output(Port::B, Line::Two, false); break;
case 0xe0: static_cast<T *>(this)->set_control_line_output(Port::B, Line::Two, true); break;
}
}
break;
// Interrupt control
case 0xd:
registers_.interrupt_flags &= ~value;
reevaluate_interrupts();
break;
case 0xe:
if(value&0x80)
registers_.interrupt_enable |= value;
else
registers_.interrupt_enable &= ~value;
reevaluate_interrupts();
break;
}
}
void set_register(int address, uint8_t value);
/*! Gets a register value. */
inline uint8_t get_register(int address) {
address &= 0xf;
// printf("6522 %p: %d\n", this, address);
switch(address) {
case 0x0:
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge);
reevaluate_interrupts();
return get_port_input(Port::B, registers_.data_direction[1], registers_.output[1]);
case 0xf: // TODO: handshake, latching
case 0x1:
registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | InterruptFlag::CA2ActiveEdge);
reevaluate_interrupts();
return get_port_input(Port::A, registers_.data_direction[0], registers_.output[0]);
case 0x2: return registers_.data_direction[1];
case 0x3: return registers_.data_direction[0];
// Timer 1
case 0x4:
registers_.interrupt_flags &= ~InterruptFlag::Timer1;
reevaluate_interrupts();
return registers_.timer[0] & 0x00ff;
case 0x5: return registers_.timer[0] >> 8;
case 0x6: return registers_.timer_latch[0] & 0x00ff;
case 0x7: return registers_.timer_latch[0] >> 8;
// Timer 2
case 0x8:
registers_.interrupt_flags &= ~InterruptFlag::Timer2;
reevaluate_interrupts();
return registers_.timer[1] & 0x00ff;
case 0x9: return registers_.timer[1] >> 8;
case 0xa: return registers_.shift;
case 0xb: return registers_.auxiliary_control;
case 0xc: return registers_.peripheral_control;
case 0xd: return registers_.interrupt_flags | (get_interrupt_line() ? 0x80 : 0x00);
case 0xe: return registers_.interrupt_enable | 0x80;
}
return 0xff;
}
inline void set_control_line_input(Port port, Line line, bool value) {
switch(line) {
case Line::One:
if( value != control_inputs_[port].line_one &&
value == !!(registers_.peripheral_control & (port ? 0x10 : 0x01))
) {
registers_.interrupt_flags |= port ? InterruptFlag::CB1ActiveEdge : InterruptFlag::CA1ActiveEdge;
reevaluate_interrupts();
}
control_inputs_[port].line_one = value;
break;
case Line::Two:
// TODO: output modes, but probably elsewhere?
if( value != control_inputs_[port].line_two && // i.e. value has changed ...
!(registers_.peripheral_control & (port ? 0x80 : 0x08)) && // ... and line is input ...
value == !!(registers_.peripheral_control & (port ? 0x40 : 0x04)) // ... and it's either high or low, as required
) {
registers_.interrupt_flags |= port ? InterruptFlag::CB2ActiveEdge : InterruptFlag::CA2ActiveEdge;
reevaluate_interrupts();
}
control_inputs_[port].line_two = value;
break;
}
}
#define phase2() \
registers_.last_timer[0] = registers_.timer[0];\
registers_.last_timer[1] = registers_.timer[1];\
\
if(registers_.timer_needs_reload) {\
registers_.timer_needs_reload = false;\
registers_.timer[0] = registers_.timer_latch[0];\
}\
else\
registers_.timer[0] --;\
\
registers_.timer[1] --; \
if(registers_.next_timer[0] >= 0) { registers_.timer[0] = (uint16_t)registers_.next_timer[0]; registers_.next_timer[0] = -1; }\
if(registers_.next_timer[1] >= 0) { registers_.timer[1] = (uint16_t)registers_.next_timer[1]; registers_.next_timer[1] = -1; }\
// IRQ is raised on the half cycle after overflow
#define phase1() \
if((registers_.timer[1] == 0xffff) && !registers_.last_timer[1] && timer_is_running_[1]) {\
timer_is_running_[1] = false;\
registers_.interrupt_flags |= InterruptFlag::Timer2;\
reevaluate_interrupts();\
}\
\
if((registers_.timer[0] == 0xffff) && !registers_.last_timer[0] && timer_is_running_[0]) {\
registers_.interrupt_flags |= InterruptFlag::Timer1;\
reevaluate_interrupts();\
\
if(registers_.auxiliary_control&0x40)\
registers_.timer_needs_reload = true;\
else\
timer_is_running_[0] = false;\
}
/*! Runs for a specified number of half cycles. */
inline void run_for(const HalfCycles half_cycles) {
int number_of_half_cycles = half_cycles.as_int();
if(is_phase2_) {
phase2();
number_of_half_cycles--;
}
while(number_of_half_cycles >= 2) {
phase1();
phase2();
number_of_half_cycles -= 2;
}
if(number_of_half_cycles) {
phase1();
is_phase2_ = true;
} else {
is_phase2_ = false;
}
}
/*! 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();
}
}
#undef phase1
#undef phase2
/*! @returns @c true if the IRQ line is currently active; @c false otherwise. */
inline bool get_interrupt_line() {
uint8_t interrupt_status = registers_.interrupt_flags & registers_.interrupt_enable & 0x7f;
return !!interrupt_status;
}
MOS6522() :
timer_is_running_{false, false},
last_posted_interrupt_status_(false),
is_phase2_(false) {}
uint8_t get_register(int address);
private:
// Expected to be overridden
uint8_t get_port_input(Port port) { return 0xff; }
void set_port_output(Port port, uint8_t value, uint8_t direction_mask) {}
void set_control_line_output(Port port, Line line, bool value) {}
void set_interrupt_status(bool status) {}
T &bus_handler_;
// Input/output multiplexer
uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output) {
uint8_t input = static_cast<T *>(this)->get_port_input(port);
return (input & ~output_mask) | (output & output_mask);
}
// Phase toggle
bool is_phase2_;
// Delegate and communications
bool last_posted_interrupt_status_;
inline void reevaluate_interrupts() {
bool new_interrupt_status = get_interrupt_line();
if(new_interrupt_status != last_posted_interrupt_status_) {
last_posted_interrupt_status_ = new_interrupt_status;
static_cast<T *>(this)->set_interrupt_status(new_interrupt_status);
}
}
// The registers
struct Registers {
uint8_t output[2], input[2], data_direction[2];
uint16_t timer[2], timer_latch[2], last_timer[2];
int next_timer[2];
uint8_t shift;
uint8_t auxiliary_control, peripheral_control;
uint8_t interrupt_flags, interrupt_enable;
bool timer_needs_reload;
// "A low reset (RES) input clears all R6522 internal registers to logic 0"
Registers() :
output{0, 0}, input{0, 0}, data_direction{0, 0},
auxiliary_control(0), peripheral_control(0),
interrupt_flags(0), interrupt_enable(0),
last_timer{0, 0}, timer_needs_reload(false),
next_timer{-1, -1} {}
} registers_;
// control state
struct {
bool line_one, line_two;
} control_inputs_[2];
// Internal state other than the registers
bool timer_is_running_[2];
uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output);
inline void reevaluate_interrupts();
};
/*!
Provided for optional composition with @c MOS6522, @c MOS6522IRQDelegate provides for a delegate
that will receive IRQ line change notifications.
*/
class MOS6522IRQDelegate {
public:
class Delegate {
public:
virtual void mos6522_did_change_interrupt_status(void *mos6522) = 0;
};
inline void set_interrupt_delegate(Delegate *delegate) {
delegate_ = delegate;
}
inline void set_interrupt_status(bool new_status) {
if(delegate_) delegate_->mos6522_did_change_interrupt_status(this);
}
private:
Delegate *delegate_;
};
#include "Implementation/6522Implementation.hpp"
}
}
#endif /* _522_hpp */

View File

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

View File

@@ -0,0 +1,155 @@
//
// Implementation.hpp
// Clock Signal
//
// Created by Thomas Harte on 04/09/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
template <typename T> void MOS6522<T>::set_register(int address, uint8_t value) {
address &= 0xf;
switch(address) {
case 0x0:
registers_.output[1] = value;
bus_handler_.set_port_output(Port::B, value, registers_.data_direction[1]); // TODO: handshake
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | ((registers_.peripheral_control&0x20) ? 0 : InterruptFlag::CB2ActiveEdge));
reevaluate_interrupts();
break;
case 0xf:
case 0x1:
registers_.output[0] = value;
bus_handler_.set_port_output(Port::A, value, registers_.data_direction[0]); // TODO: handshake
registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | ((registers_.peripheral_control&0x02) ? 0 : InterruptFlag::CB2ActiveEdge));
reevaluate_interrupts();
break;
case 0x2:
registers_.data_direction[1] = value;
break;
case 0x3:
registers_.data_direction[0] = value;
break;
// Timer 1
case 0x6: case 0x4: registers_.timer_latch[0] = (registers_.timer_latch[0]&0xff00) | value; break;
case 0x5: case 0x7:
registers_.timer_latch[0] = (registers_.timer_latch[0]&0x00ff) | static_cast<uint16_t>(value << 8);
registers_.interrupt_flags &= ~InterruptFlag::Timer1;
if(address == 0x05) {
registers_.next_timer[0] = registers_.timer_latch[0];
timer_is_running_[0] = true;
}
reevaluate_interrupts();
break;
// Timer 2
case 0x8: registers_.timer_latch[1] = value; break;
case 0x9:
registers_.interrupt_flags &= ~InterruptFlag::Timer2;
registers_.next_timer[1] = registers_.timer_latch[1] | static_cast<uint16_t>(value << 8);
timer_is_running_[1] = true;
reevaluate_interrupts();
break;
// Shift
case 0xa: registers_.shift = value; break;
// Control
case 0xb:
registers_.auxiliary_control = value;
break;
case 0xc:
// printf("Peripheral control %02x\n", value);
registers_.peripheral_control = value;
// TODO: simplify below; trying to avoid improper logging of unimplemented warnings in input mode
if(value & 0x08) {
switch(value & 0x0e) {
default: printf("Unimplemented control line mode %d\n", (value >> 1)&7); break;
case 0x0c: bus_handler_.set_control_line_output(Port::A, Line::Two, false); break;
case 0x0e: bus_handler_.set_control_line_output(Port::A, Line::Two, true); break;
}
}
if(value & 0x80) {
switch(value & 0xe0) {
default: printf("Unimplemented control line mode %d\n", (value >> 5)&7); break;
case 0xc0: bus_handler_.set_control_line_output(Port::B, Line::Two, false); break;
case 0xe0: bus_handler_.set_control_line_output(Port::B, Line::Two, true); break;
}
}
break;
// Interrupt control
case 0xd:
registers_.interrupt_flags &= ~value;
reevaluate_interrupts();
break;
case 0xe:
if(value&0x80)
registers_.interrupt_enable |= value;
else
registers_.interrupt_enable &= ~value;
reevaluate_interrupts();
break;
}
}
template <typename T> uint8_t MOS6522<T>::get_register(int address) {
address &= 0xf;
switch(address) {
case 0x0:
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge);
reevaluate_interrupts();
return get_port_input(Port::B, registers_.data_direction[1], registers_.output[1]);
case 0xf: // TODO: handshake, latching
case 0x1:
registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | InterruptFlag::CA2ActiveEdge);
reevaluate_interrupts();
return get_port_input(Port::A, registers_.data_direction[0], registers_.output[0]);
case 0x2: return registers_.data_direction[1];
case 0x3: return registers_.data_direction[0];
// Timer 1
case 0x4:
registers_.interrupt_flags &= ~InterruptFlag::Timer1;
reevaluate_interrupts();
return registers_.timer[0] & 0x00ff;
case 0x5: return registers_.timer[0] >> 8;
case 0x6: return registers_.timer_latch[0] & 0x00ff;
case 0x7: return registers_.timer_latch[0] >> 8;
// Timer 2
case 0x8:
registers_.interrupt_flags &= ~InterruptFlag::Timer2;
reevaluate_interrupts();
return registers_.timer[1] & 0x00ff;
case 0x9: return registers_.timer[1] >> 8;
case 0xa: return registers_.shift;
case 0xb: return registers_.auxiliary_control;
case 0xc: return registers_.peripheral_control;
case 0xd: return registers_.interrupt_flags | (get_interrupt_line() ? 0x80 : 0x00);
case 0xe: return registers_.interrupt_enable | 0x80;
}
return 0xff;
}
template <typename T> uint8_t MOS6522<T>::get_port_input(Port port, uint8_t output_mask, uint8_t output) {
uint8_t input = bus_handler_.get_port_input(port);
return (input & ~output_mask) | (output & output_mask);
}
// Delegate and communications
template <typename T> void MOS6522<T>::reevaluate_interrupts() {
bool new_interrupt_status = get_interrupt_line();
if(new_interrupt_status != last_posted_interrupt_status_) {
last_posted_interrupt_status_ = new_interrupt_status;
bus_handler_.set_interrupt_status(new_interrupt_status);
}
}

View File

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

View File

@@ -0,0 +1,19 @@
//
// IRQDelegatePortHandler.cpp
// Clock Signal
//
// Created by Thomas Harte on 04/09/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "../6522.hpp"
using namespace MOS::MOS6522;
void IRQDelegatePortHandler::set_interrupt_delegate(Delegate *delegate) {
delegate_ = delegate;
}
void IRQDelegatePortHandler::set_interrupt_status(bool new_status) {
if(delegate_) delegate_->mos6522_did_change_interrupt_status(this);
}

View File

@@ -51,7 +51,7 @@ template <class T> class MOS6532 {
case 0x04: case 0x05: case 0x06: case 0x07:
if(address & 0x10) {
timer_.writtenShift = timer_.activeShift = (decodedAddress - 0x04) * 3 + (decodedAddress / 0x07); // i.e. 0, 3, 6, 10
timer_.value = ((unsigned int)value << timer_.activeShift) ;
timer_.value = (static_cast<unsigned int>(value) << timer_.activeShift) ;
timer_.interrupt_enabled = !!(address&0x08);
interrupt_status_ &= ~InterruptFlag::Timer;
evaluate_interrupts();
@@ -79,7 +79,7 @@ template <class T> class MOS6532 {
// Timer and interrupt control
case 0x04: case 0x06: {
uint8_t value = (uint8_t)(timer_.value >> timer_.activeShift);
uint8_t value = static_cast<uint8_t>(timer_.value >> timer_.activeShift);
timer_.interrupt_enabled = !!(address&0x08);
interrupt_status_ &= ~InterruptFlag::Timer;
evaluate_interrupts();
@@ -107,7 +107,7 @@ template <class T> class MOS6532 {
}
inline void run_for(const Cycles cycles) {
unsigned int number_of_cycles = (unsigned int)cycles.as_int();
unsigned int number_of_cycles = static_cast<unsigned int>(cycles.as_int());
// permit counting _to_ zero; counting _through_ zero initiates the other behaviour
if(timer_.value >= number_of_cycles) {
@@ -126,7 +126,7 @@ template <class T> class MOS6532 {
port_{{.output_mask = 0, .output = 0}, {.output_mask = 0, .output = 0}},
a7_interrupt_({.last_port_value = 0, .enabled = false}),
interrupt_line_(false),
timer_{.value = (unsigned int)((rand() & 0xff) << 10), .activeShift = 10, .writtenShift = 10, .interrupt_enabled = false} {}
timer_{.value = static_cast<unsigned int>((rand() & 0xff) << 10), .activeShift = 10, .writtenShift = 10, .interrupt_enabled = false} {}
inline void set_port_did_change(int port) {
if(!port) {

View File

@@ -98,7 +98,7 @@ static uint8_t noise_pattern[] = {
#define shift(r) shift_registers_[r] = (shift_registers_[r] << 1) | (((shift_registers_[r]^0x80)&control_registers_[r]) >> 7)
#define increment(r) shift_registers_[r] = (shift_registers_[r]+1)%8191
#define update(r, m, up) counters_[r]++; if((counters_[r] >> m) == 0x80) { up(r); counters_[r] = (unsigned int)(control_registers_[r]&0x7f) << m; }
#define update(r, m, up) counters_[r]++; if((counters_[r] >> m) == 0x80) { up(r); counters_[r] = static_cast<unsigned int>(control_registers_[r]&0x7f) << m; }
// Note on slightly askew test: as far as I can make out, if the value in the register is 0x7f then what's supposed to happen
// is that the 0x7f is loaded, on the next clocked cycle the Vic spots a 0x7f, pumps the output, reloads, etc. No increment
// ever occurs. It's conditional. I don't really want two conditionals if I can avoid it so I'm incrementing regardless and

View File

@@ -66,7 +66,7 @@ template <class T> class MOS6560 {
}
void set_clock_rate(double clock_rate) {
speaker_->set_input_rate((float)(clock_rate / 4.0));
speaker_->set_input_rate(static_cast<float>(clock_rate / 4.0));
}
std::shared_ptr<Outputs::CRT::CRT> get_crt() { return crt_; }
@@ -100,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;
@@ -128,7 +128,7 @@ template <class T> class MOS6560 {
break;
}
crt_->set_new_display_type((unsigned int)(timing_.cycles_per_line*4), display_type);
crt_->set_new_display_type(static_cast<unsigned int>(timing_.cycles_per_line*4), display_type);
crt_->set_visible_area(Outputs::CRT::Rect(0.05f, 0.05f, 0.9f, 0.9f));
// switch(output_mode) {
@@ -141,7 +141,7 @@ template <class T> class MOS6560 {
// }
for(int c = 0; c < 16; c++) {
uint8_t *colour = (uint8_t *)&colours_[c];
uint8_t *colour = reinterpret_cast<uint8_t *>(&colours_[c]);
colour[0] = luminances[c];
colour[1] = chrominances[c];
}
@@ -218,7 +218,7 @@ template <class T> class MOS6560 {
if(column_counter_&1) {
fetch_address = registers_.character_cell_start_address + (character_code_*(registers_.tall_characters ? 16 : 8)) + current_character_row_;
} else {
fetch_address = (uint16_t)(registers_.video_matrix_start_address + video_matrix_address_counter_);
fetch_address = static_cast<uint16_t>(registers_.video_matrix_start_address + video_matrix_address_counter_);
video_matrix_address_counter_++;
if(
(current_character_row_ == 15) ||
@@ -270,7 +270,7 @@ template <class T> class MOS6560 {
pixel_pointer = nullptr;
if(output_state_ == State::Pixels) {
pixel_pointer = (uint16_t *)crt_->allocate_write_area(260);
pixel_pointer = reinterpret_cast<uint16_t *>(crt_->allocate_write_area(260));
}
}
cycles_in_state_++;
@@ -345,7 +345,7 @@ template <class T> class MOS6560 {
case 0x2:
registers_.number_of_columns = value & 0x7f;
registers_.video_matrix_start_address = (uint16_t)((registers_.video_matrix_start_address & 0x3c00) | ((value & 0x80) << 2));
registers_.video_matrix_start_address = static_cast<uint16_t>((registers_.video_matrix_start_address & 0x3c00) | ((value & 0x80) << 2));
break;
case 0x3:
@@ -354,8 +354,8 @@ template <class T> class MOS6560 {
break;
case 0x5:
registers_.character_cell_start_address = (uint16_t)((value & 0x0f) << 10);
registers_.video_matrix_start_address = (uint16_t)((registers_.video_matrix_start_address & 0x0200) | ((value & 0xf0) << 6));
registers_.character_cell_start_address = static_cast<uint16_t>((value & 0x0f) << 10);
registers_.video_matrix_start_address = static_cast<uint16_t>((registers_.video_matrix_start_address & 0x0200) | ((value & 0xf0) << 6));
break;
case 0xa:
@@ -399,7 +399,7 @@ template <class T> class MOS6560 {
int current_line = (full_frame_counter_ + timing_.line_counter_increment_offset) / timing_.cycles_per_line;
switch(address) {
default: return registers_.direct_values[address];
case 0x03: return (uint8_t)(current_line << 7) | (registers_.direct_values[3] & 0x7f);
case 0x03: return static_cast<uint8_t>(current_line << 7) | (registers_.direct_values[3] & 0x7f);
case 0x04: return (current_line >> 1) & 0xff;
}
}
@@ -454,7 +454,7 @@ template <class T> class MOS6560 {
uint16_t *pixel_pointer;
void output_border(unsigned int number_of_cycles) {
uint16_t *colour_pointer = (uint16_t *)crt_->allocate_write_area(1);
uint16_t *colour_pointer = reinterpret_cast<uint16_t *>(crt_->allocate_write_area(1));
if(colour_pointer) *colour_pointer = registers_.borderColour;
crt_->output_level(number_of_cycles);
}

View File

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

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

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

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

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

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

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

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
@@ -66,13 +67,13 @@ AY38910::AY38910() :
float max_volume = 8192;
float root_two = sqrtf(2.0f);
for(int v = 0; v < 16; v++) {
volumes_[v] = (int)(max_volume / powf(root_two, (float)(v ^ 0xf)));
volumes_[v] = static_cast<int>(max_volume / powf(root_two, static_cast<float>(v ^ 0xf)));
}
volumes_[0] = 0;
}
void AY38910::set_clock_rate(double clock_rate) {
set_input_rate((float)clock_rate);
set_input_rate(static_cast<float>(clock_rate));
}
void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) {
@@ -159,18 +160,21 @@ void AY38910::evaluate_output_volume() {
#undef channel_volume
// Mix additively.
output_volume_ = (int16_t)(
output_volume_ = static_cast<int16_t>(
volumes_[volumes[0]] * channel_levels[0] +
volumes_[volumes[1]] * channel_levels[1] +
volumes_[volumes[2]] * channel_levels[2]
);
}
#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_;
@@ -182,26 +186,22 @@ void AY38910::set_register_value(uint8_t value) {
int channel = selected_register >> 1;
if(selected_register & 1)
tone_periods_[channel] = (tone_periods_[channel] & 0xff) | (uint16_t)((value&0xf) << 8);
tone_periods_[channel] = (tone_periods_[channel] & 0xff) | static_cast<uint16_t>((value&0xf) << 8);
else
tone_periods_[channel] = (tone_periods_[channel] & ~0xff) | value;
tone_counters_[channel] = tone_periods_[channel];
}
break;
case 6:
noise_period_ = value & 0x1f;
noise_counter_ = noise_period_;
break;
case 11:
envelope_period_ = (envelope_period_ & ~0xff) | value;
envelope_divider_ = envelope_period_;
break;
case 12:
envelope_period_ = (envelope_period_ & 0xff) | (int)(value << 8);
envelope_divider_ = envelope_period_;
envelope_period_ = (envelope_period_ & 0xff) | static_cast<int>(value << 8);
break;
case 13:
@@ -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;
switch(static_cast<int>(control_lines)) {
default: control_state_ = Inactive; break;
case (int)(BCDIR | BC2 | BC1):
case BCDIR:
case BC1: new_state = LatchAddress; break;
case static_cast<int>(BDIR | BC2 | BC1):
case BDIR:
case BC1: control_state_ = LatchAddress; break;
case (int)(BC2 | BC1): new_state = Read; break;
case (int)(BCDIR | BC2): new_state = Write; break;
case static_cast<int>(BC2 | BC1): control_state_ = Read; break;
case static_cast<int>(BDIR | BC2): control_state_ = Write; break;
}
if(new_state != control_state_) {
control_state_ = new_state;
switch(new_state) {
default: break;
case LatchAddress: select_register(data_input_); break;
case Write: set_register_value(data_input_); break;
case Read: data_output_ = get_register_value(); break;
}
update_bus();
}
void AY38910::update_bus() {
switch(control_state_) {
default: break;
case LatchAddress: select_register(data_input_); break;
case Write: set_register_value(data_input_); break;
case Read: data_output_ = get_register_value(); break;
}
}

View File

@@ -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

@@ -0,0 +1,67 @@
//
// BestEffortUpdater.cpp
// Clock Signal
//
// Created by Thomas Harte on 04/10/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "BestEffortUpdater.hpp"
#include <cmath>
using namespace Concurrency;
void BestEffortUpdater::update() {
// Perform an update only if one is not currently ongoing.
if(!update_is_ongoing_.test_and_set()) {
async_task_queue_.enqueue([this]() {
// Get time now using the highest-resolution clock provided by the implementation, and determine
// the duration since the last time this section was entered.
const std::chrono::time_point<std::chrono::high_resolution_clock> now = std::chrono::high_resolution_clock::now();
const auto elapsed = now - previous_time_point_;
previous_time_point_ = now;
if(has_previous_time_point_) {
// If the duration is valid, convert it to integer cycles, maintaining a rolling error and call the delegate
// if there is one. Proceed only if the number of cycles is positive, and cap it to the per-second maximum —
// it's possible this is an adjustable clock so be ready to swallow unexpected adjustments.
const int64_t duration = std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed).count();
if(duration > 0) {
double cycles = ((static_cast<double>(duration) * clock_rate_) / 1e9) + error_;
error_ = fmod(cycles, 1.0);
if(delegate_) {
delegate_->update(this, static_cast<int>(std::min(cycles, clock_rate_)), has_skipped_);
}
has_skipped_ = false;
}
} else {
has_previous_time_point_ = true;
}
// Allow furthers updates to occur.
update_is_ongoing_.clear();
});
} else {
async_task_queue_.enqueue([this]() {
has_skipped_ = true;
});
}
}
void BestEffortUpdater::flush() {
async_task_queue_.flush();
}
void BestEffortUpdater::set_delegate(Delegate *const delegate) {
async_task_queue_.enqueue([this, delegate]() {
delegate_ = delegate;
});
}
void BestEffortUpdater::set_clock_rate(const double clock_rate) {
async_task_queue_.enqueue([this, clock_rate]() {
this->clock_rate_ = clock_rate;
});
}

View File

@@ -0,0 +1,63 @@
//
// BestEffortUpdater.hpp
// Clock Signal
//
// Created by Thomas Harte on 04/10/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef BestEffortUpdater_hpp
#define BestEffortUpdater_hpp
#include <atomic>
#include <chrono>
#include "AsyncTaskQueue.hpp"
namespace Concurrency {
/*!
Accepts timing cues from multiple threads and ensures that a delegate receives calls to total
a certain number of cycles per second, that those calls are strictly serialised, and that no
backlog of calls accrues.
No guarantees about the thread that the delegate will be called on are made.
*/
class BestEffortUpdater {
public:
/// A delegate receives timing cues.
struct Delegate {
virtual void update(BestEffortUpdater *updater, int cycles, bool did_skip_previous_update) = 0;
};
/// Sets the current delegate.
void set_delegate(Delegate *);
/// Sets the clock rate of the delegate.
void set_clock_rate(double clock_rate);
/*!
If the delegate is not currently in the process of an `update` call, calls it now to catch up to the current time.
The call is asynchronous; this method will return immediately.
*/
void update();
/// Blocks until any ongoing update is complete.
void flush();
private:
std::atomic_flag update_is_ongoing_;
AsyncTaskQueue async_task_queue_;
std::chrono::time_point<std::chrono::high_resolution_clock> previous_time_point_;
bool has_previous_time_point_ = false;
double error_ = 0.0;
bool has_skipped_ = false;
Delegate *delegate_ = nullptr;
double clock_rate_ = 1.0;
};
}
#endif /* BestEffortUpdater_hpp */

41
Inputs/Joystick.hpp Normal file
View File

@@ -0,0 +1,41 @@
//
// Joystick.hpp
// Clock Signal
//
// Created by Thomas Harte on 14/10/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef Joystick_hpp
#define Joystick_hpp
#include <vector>
namespace Inputs {
/*!
Provides an intermediate idealised model of a simple joystick, allowing a host
machine to toggle states, while an interested party either observes or polls.
*/
class Joystick {
public:
virtual ~Joystick() {}
enum class DigitalInput {
Up, Down, Left, Right, Fire
};
// Host interface.
virtual void set_digital_input(DigitalInput digital_input, bool is_active) = 0;
virtual void reset_all_inputs() {
set_digital_input(DigitalInput::Up, false);
set_digital_input(DigitalInput::Down, false);
set_digital_input(DigitalInput::Left, false);
set_digital_input(DigitalInput::Right, false);
set_digital_input(DigitalInput::Fire, false);
}
};
}
#endif /* Joystick_hpp */

38
Inputs/Keyboard.cpp Normal file
View File

@@ -0,0 +1,38 @@
//
// Keyboard.cpp
// Clock Signal
//
// Created by Thomas Harte on 10/9/17.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "Keyboard.hpp"
using namespace Inputs;
Keyboard::Keyboard() {}
void Keyboard::set_key_pressed(Key key, bool is_pressed) {
size_t key_offset = static_cast<size_t>(key);
if(key_offset >= key_states_.size()) {
key_states_.resize(key_offset+1, false);
}
key_states_[key_offset] = is_pressed;
if(delegate_) delegate_->keyboard_did_change_key(this, key, is_pressed);
}
void Keyboard::reset_all_keys() {
std::fill(key_states_.begin(), key_states_.end(), false);
if(delegate_) delegate_->reset_all_keys(this);
}
void Keyboard::set_delegate(Delegate *delegate) {
delegate_ = delegate;
}
bool Keyboard::get_key_state(Key key) {
size_t key_offset = static_cast<size_t>(key);
if(key_offset >= key_states_.size()) return false;
return key_states_[key_offset];
}

61
Inputs/Keyboard.hpp Normal file
View File

@@ -0,0 +1,61 @@
//
// Keyboard.hpp
// Clock Signal
//
// Created by Thomas Harte on 10/9/17.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef Keyboard_hpp
#define Keyboard_hpp
#include <vector>
namespace Inputs {
/*!
Provides an intermediate idealised model of a modern-era computer keyboard
(so, heavily indebted to the current Windows and Mac layouts), allowing a host
machine to toggle states, while an interested party either observes or polls.
*/
class Keyboard {
public:
Keyboard();
enum class Key {
Escape, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, PrintScreen, ScrollLock, Pause,
BackTick, k1, k2, k3, k4, k5, k6, k7, k8, k9, k0, Hyphen, Equals, BackSpace,
Tab, Q, W, E, R, T, Y, U, I, O, P, OpenSquareBracket, CloseSquareBracket, BackSlash,
CapsLock, A, S, D, F, G, H, J, K, L, Semicolon, Quote, Hash, Enter,
LeftShift, Z, X, C, V, B, N, M, Comma, FullStop, ForwardSlash, RightShift,
LeftControl, LeftOption, LeftMeta, Space, RightMeta, RightOption, RightControl,
Left, Right, Up, Down,
Insert, Home, PageUp, Delete, End, PageDown,
NumLock, KeyPadSlash, KeyPadAsterisk, KeyPadDelete,
KeyPad7, KeyPad8, KeyPad9, KeyPadPlus,
KeyPad4, KeyPad5, KeyPad6, KeyPadMinus,
KeyPad1, KeyPad2, KeyPad3, KeyPadEnter,
KeyPad0, KeyPadDecimalPoint, KeyPadEquals,
Help
};
// Host interface.
virtual void set_key_pressed(Key key, bool is_pressed);
virtual void reset_all_keys();
// Delegate interface.
struct Delegate {
virtual void keyboard_did_change_key(Keyboard *keyboard, Key key, bool is_pressed) = 0;
virtual void reset_all_keys(Keyboard *keyboard) = 0;
};
void set_delegate(Delegate *delegate);
bool get_key_state(Key key);
private:
std::vector<bool> key_states_;
Delegate *delegate_ = nullptr;
};
}
#endif /* Keyboard_hpp */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,47 @@
//
// 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
};
/*!
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,152 @@
//
// Keyboard.cpp
// Clock Signal
//
// Created by Thomas Harte on 10/10/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "Keyboard.hpp"
using namespace AmstradCPC;
uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
#define BIND(source, dest) case Inputs::Keyboard::Key::source: return dest
switch(key) {
default: return KeyCopy;
BIND(k0, Key0); BIND(k1, Key1); BIND(k2, Key2); BIND(k3, Key3); BIND(k4, Key4);
BIND(k5, Key5); BIND(k6, Key6); BIND(k7, Key7); BIND(k8, Key8); BIND(k9, Key9);
BIND(Q, KeyQ); BIND(W, KeyW); BIND(E, KeyE); BIND(R, KeyR); BIND(T, KeyT);
BIND(Y, KeyY); BIND(U, KeyU); BIND(I, KeyI); BIND(O, KeyO); BIND(P, KeyP);
BIND(A, KeyA); BIND(S, KeyS); BIND(D, KeyD); BIND(F, KeyF); BIND(G, KeyG);
BIND(H, KeyH); BIND(J, KeyJ); BIND(K, KeyK); BIND(L, KeyL);
BIND(Z, KeyZ); BIND(X, KeyX); BIND(C, KeyC); BIND(V, KeyV);
BIND(B, KeyB); BIND(N, KeyN); BIND(M, KeyM);
BIND(Escape, KeyEscape);
BIND(F1, KeyF1); BIND(F2, KeyF2); BIND(F3, KeyF3); BIND(F4, KeyF4); BIND(F5, KeyF5);
BIND(F6, KeyF6); BIND(F7, KeyF7); BIND(F8, KeyF8); BIND(F9, KeyF9); BIND(F10, KeyF0);
BIND(F11, KeyRightSquareBracket);
BIND(F12, KeyClear);
BIND(Hyphen, KeyMinus); BIND(Equals, KeyCaret); BIND(BackSpace, KeyDelete);
BIND(Tab, KeyTab);
BIND(OpenSquareBracket, KeyAt);
BIND(CloseSquareBracket, KeyLeftSquareBracket);
BIND(BackSlash, KeyBackSlash);
BIND(CapsLock, KeyCapsLock);
BIND(Semicolon, KeyColon);
BIND(Quote, KeySemicolon);
BIND(Hash, KeyRightSquareBracket);
BIND(Enter, KeyReturn);
BIND(LeftShift, KeyShift);
BIND(Comma, KeyComma);
BIND(FullStop, KeyFullStop);
BIND(ForwardSlash, KeyForwardSlash);
BIND(RightShift, KeyShift);
BIND(LeftControl, KeyControl); BIND(LeftOption, KeyControl); BIND(LeftMeta, KeyControl);
BIND(Space, KeySpace);
BIND(RightMeta, KeyControl); BIND(RightOption, KeyControl); BIND(RightControl, KeyControl);
BIND(Left, KeyLeft); BIND(Right, KeyRight);
BIND(Up, KeyUp); BIND(Down, KeyDown);
BIND(KeyPad0, KeyF0);
BIND(KeyPad1, KeyF1); BIND(KeyPad2, KeyF2); BIND(KeyPad3, KeyF3);
BIND(KeyPad4, KeyF4); BIND(KeyPad5, KeyF5); BIND(KeyPad6, KeyF6);
BIND(KeyPad7, KeyF7); BIND(KeyPad8, KeyF8); BIND(KeyPad9, KeyF9);
BIND(KeyPadPlus, KeySemicolon);
BIND(KeyPadMinus, KeyMinus);
BIND(KeyPadEnter, KeyEnter);
BIND(KeyPadDecimalPoint, KeyFullStop);
BIND(KeyPadEquals, KeyMinus);
BIND(KeyPadSlash, KeyForwardSlash);
BIND(KeyPadAsterisk, KeyColon);
BIND(KeyPadDelete, KeyDelete);
}
#undef BIND
}
uint16_t *CharacterMapper::sequence_for_character(char character) {
#define KEYS(...) {__VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
#define SHIFT(...) {KeyShift, __VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
#define X {KeyboardMachine::Machine::KeyNotMapped}
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,46 @@
//
// Keyboard.hpp
// Clock Signal
//
// Created by Thomas Harte on 10/10/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef Machines_AmstradCPC_Keyboard_hpp
#define Machines_AmstradCPC_Keyboard_hpp
#include "../KeyboardMachine.hpp"
#include "../Utility/Typer.hpp"
namespace AmstradCPC {
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
};
struct KeyboardMapper: public KeyboardMachine::Machine::KeyboardMapper {
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key);
};
struct CharacterMapper: public ::Utility::CharacterMapper {
uint16_t *sequence_for_character(char character);
};
};
#endif /* KeyboardMapper_hpp */

View File

@@ -10,148 +10,213 @@
#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);
}
class Joystick: public Inputs::Joystick {
public:
Joystick(Bus *bus, size_t shift, size_t fire_tia_input) :
bus_(bus), shift_(shift), fire_tia_input_(fire_tia_input) {}
void Machine::close_output() {
bus_.reset();
}
void set_digital_input(DigitalInput digital_input, bool is_active) {
switch(digital_input) {
case DigitalInput::Up: bus_->mos6532_.update_port_input(0, 0x10 >> shift_, is_active); break;
case DigitalInput::Down: bus_->mos6532_.update_port_input(0, 0x20 >> shift_, is_active); break;
case DigitalInput::Left: bus_->mos6532_.update_port_input(0, 0x40 >> shift_, is_active); break;
case DigitalInput::Right: bus_->mos6532_.update_port_input(0, 0x80 >> shift_, is_active); break;
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));
// TODO: latching
case DigitalInput::Fire:
if(is_active)
bus_->tia_input_value_[fire_tia_input_] &= ~0x80;
else
bus_->tia_input_value_[fire_tia_input_] |= 0x80;
break;
}
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;
}
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;
private:
size_t shift_, fire_tia_input_;
Bus *bus_;
};
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);
class ConcreteMachine:
public Machine,
public Outputs::CRT::Delegate {
public:
ConcreteMachine() :
frame_record_pointer_(0),
is_ntsc_(true) {
set_clock_rate(NTSC_clock_rate);
}
}
~ConcreteMachine() {
close_output();
}
void configure_as_target(const StaticAnalyser::Target &target) override {
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;
}
joysticks_.emplace_back(new Joystick(bus_.get(), 0, 0));
joysticks_.emplace_back(new Joystick(bus_.get(), 4, 1));
}
bool insert_media(const StaticAnalyser::Media &media) override {
return false;
}
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
return joysticks_;
}
void set_switch_is_enabled(Atari2600Switch input, bool state) override {
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) override {
bus_->set_reset_line(state);
}
// to satisfy CRTMachine::Machine
void setup_output(float aspect_ratio) override {
bus_->tia_.reset(new TIA);
bus_->speaker_.reset(new Speaker);
bus_->speaker_->set_input_rate(static_cast<float>(get_clock_rate() / static_cast<double>(CPUTicksPerAudioTick)));
bus_->tia_->get_crt()->set_delegate(this);
}
void close_output() override {
bus_.reset();
}
std::shared_ptr<Outputs::CRT::CRT> get_crt() override {
return bus_->tia_->get_crt();
}
std::shared_ptr<Outputs::Speaker> get_speaker() override {
return bus_->speaker_;
}
void run_for(const Cycles cycles) override {
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) override {
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(static_cast<float>(clock_rate / static_cast<double>(CPUTicksPerAudioTick)));
bus_->speaker_->set_high_frequency_cut_off(static_cast<float>(clock_rate / (static_cast<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_;
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
};
}
using namespace Atari2600;
Machine *Machine::Atari2600() {
return new Atari2600::ConcreteMachine;
}
Machine::~Machine() {}

View File

@@ -9,59 +9,32 @@
#ifndef Atari2600_cpp
#define Atari2600_cpp
#include <stdint.h>
#include "../../Processors/6502/6502.hpp"
#include "../CRTMachine.hpp"
#include "Bus.hpp"
#include "PIA.hpp"
#include "Speaker.hpp"
#include "TIA.hpp"
#include "../ConfigurationTarget.hpp"
#include "../CRTMachine.hpp"
#include "../JoystickMachine.hpp"
#include "Atari2600Inputs.h"
namespace Atari2600 {
/*!
Models an Atari 2600.
*/
class Machine:
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public Outputs::CRT::Delegate {
public JoystickMachine::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 the switch @c input to @c state.
virtual void set_switch_is_enabled(Atari2600Switch 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(const Cycles cycles) { bus_->run_for(cycles); }
// 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

@@ -6,18 +6,18 @@
// 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(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
if(!(address & 0x1000)) return;
@@ -27,9 +27,9 @@ class CartridgeActivisionStack: public Cartridge<CartridgeActivisionStack> {
// RST or JSR.
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;
}
}
@@ -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(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(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(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(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(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(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(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

@@ -13,18 +13,34 @@
#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 CPU::MOS6502::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(const Cycles cycles) { CPU::MOS6502::Processor<Cartridge<T>>::run_for(cycles); }
void set_reset_line(bool state) { CPU::MOS6502::Processor<Cartridge<T>>::set_reset_line(state); }
void advance_cycles(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 CPU::MOS6502::Processor
Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
@@ -41,11 +57,11 @@ template<class T> class Cartridge:
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);
static_cast<T *>(this)->advance_cycles(cycles_run_for / 3);
bus_extender_.advance_cycles(cycles_run_for / 3);
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: CPU::MOS6502::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,7 +172,7 @@ template<class T> class Cartridge:
}
}
if(!tia_->get_cycles_until_horizontal_blank(cycles_since_video_update_)) CPU::MOS6502::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(cycles_run_for / 3);
}
@@ -168,9 +184,14 @@ template<class T> class Cartridge:
}
protected:
CPU::MOS6502::Processor<Cartridge<T>, true> m6502_;
std::vector<uint8_t> rom_;
private:
T bus_extender_;
};
}
}
#endif /* Atari2600_Cartridge_hpp */

View File

@@ -9,12 +9,14 @@
#ifndef Atari2600_CartridgeCommaVid_hpp
#define Atari2600_CartridgeCommaVid_hpp
namespace Atari2600 {
#include "Cartridge.hpp"
class CartridgeCommaVid: public Cartridge<CartridgeCommaVid> {
namespace Atari2600 {
namespace Cartridge {
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(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
if(!(address & 0x1000)) return;
@@ -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,12 +12,13 @@
#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_;
}
@@ -27,7 +28,7 @@ class CartridgeMNetwork: public Cartridge<CartridgeMNetwork> {
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,13 +12,14 @@
#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(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
@@ -27,7 +28,7 @@ class CartridgeMegaBoy: public Cartridge<CartridgeMegaBoy> {
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,12 +12,13 @@
#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;
@@ -29,7 +30,7 @@ class CartridgeParkerBros: public Cartridge<CartridgeParkerBros> {
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,17 +10,17 @@
#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(int cycles) {
cycles_since_audio_update_ += cycles;
@@ -39,7 +39,7 @@ class CartridgePitfall2: public Cartridge<CartridgePitfall2> {
if(isReadOperation(operation)) {
*value = random_number_generator_;
}
random_number_generator_ = (uint8_t)(
random_number_generator_ = static_cast<uint8_t>(
(random_number_generator_ << 1) |
(~( (random_number_generator_ >> 7) ^
(random_number_generator_ >> 5) ^
@@ -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
@@ -73,7 +73,7 @@ class CartridgePitfall2: public Cartridge<CartridgePitfall2> {
mask_[address & 7] = 0x00;
break;
case 0x1058: case 0x1059: case 0x105a: case 0x105b: case 0x105c: case 0x105d: case 0x105e: case 0x105f:
featcher_address_[address & 7] = (featcher_address_[address & 7] & 0x00ff) | (uint16_t)(*value << 8);
featcher_address_[address & 7] = (featcher_address_[address & 7] & 0x00ff) | static_cast<uint16_t>(*value << 8);
break;
case 0x1070: case 0x1071: case 0x1072: case 0x1073: case 0x1074: case 0x1075: case 0x1076: case 0x1077:
random_number_generator_ = 0;
@@ -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
@@ -128,6 +128,7 @@ class CartridgePitfall2: public Cartridge<CartridgePitfall2> {
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(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(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

@@ -37,19 +37,19 @@ TIA::TIA(bool create_crt) :
}
for(int c = 0; c < 256; c++) {
reverse_table[c] = (uint8_t)(
reverse_table[c] = static_cast<uint8_t>(
((c & 0x01) << 7) | ((c & 0x02) << 5) | ((c & 0x04) << 3) | ((c & 0x08) << 1) |
((c & 0x10) >> 1) | ((c & 0x20) >> 3) | ((c & 0x40) >> 5) | ((c & 0x80) >> 7)
);
}
for(int c = 0; c < 64; c++) {
bool has_playfield = c & (int)(CollisionType::Playfield);
bool has_ball = c & (int)(CollisionType::Ball);
bool has_player0 = c & (int)(CollisionType::Player0);
bool has_player1 = c & (int)(CollisionType::Player1);
bool has_missile0 = c & (int)(CollisionType::Missile0);
bool has_missile1 = c & (int)(CollisionType::Missile1);
bool has_playfield = c & static_cast<int>(CollisionType::Playfield);
bool has_ball = c & static_cast<int>(CollisionType::Ball);
bool has_player0 = c & static_cast<int>(CollisionType::Player0);
bool has_player1 = c & static_cast<int>(CollisionType::Player1);
bool has_missile0 = c & static_cast<int>(CollisionType::Missile0);
bool has_missile1 = c & static_cast<int>(CollisionType::Missile1);
uint8_t collision_registers[8];
collision_registers[0] = ((has_missile0 && has_player1) ? 0x80 : 0x00) | ((has_missile0 && has_player0) ? 0x40 : 0x00);
@@ -71,51 +71,51 @@ TIA::TIA(bool create_crt) :
(collision_registers[7] << 8);
// all priority modes show the background if nothing else is present
colour_mask_by_mode_collision_flags_[(int)ColourMode::Standard][c] =
colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreLeft][c] =
colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreRight][c] =
colour_mask_by_mode_collision_flags_[(int)ColourMode::OnTop][c] = (uint8_t)ColourIndex::Background;
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::Standard)][c] =
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreLeft)][c] =
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreRight)][c] =
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::OnTop)][c] = static_cast<uint8_t>(ColourIndex::Background);
// test 1 for standard priority: if there is a playfield or ball pixel, plot that colour
if(has_playfield || has_ball) {
colour_mask_by_mode_collision_flags_[(int)ColourMode::Standard][c] = (uint8_t)ColourIndex::PlayfieldBall;
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::Standard)][c] = static_cast<uint8_t>(ColourIndex::PlayfieldBall);
}
// test 1 for score mode: if there is a ball pixel, plot that colour
if(has_ball) {
colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreLeft][c] =
colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreRight][c] = (uint8_t)ColourIndex::PlayfieldBall;
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreLeft)][c] =
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreRight)][c] = static_cast<uint8_t>(ColourIndex::PlayfieldBall);
}
// test 1 for on-top mode, test 2 for everbody else: if there is a player 1 or missile 1 pixel, plot that colour
if(has_player1 || has_missile1) {
colour_mask_by_mode_collision_flags_[(int)ColourMode::Standard][c] =
colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreLeft][c] =
colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreRight][c] =
colour_mask_by_mode_collision_flags_[(int)ColourMode::OnTop][c] = (uint8_t)ColourIndex::PlayerMissile1;
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::Standard)][c] =
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreLeft)][c] =
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreRight)][c] =
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::OnTop)][c] = static_cast<uint8_t>(ColourIndex::PlayerMissile1);
}
// in the right-hand side of score mode, the playfield has the same priority as player 1
if(has_playfield) {
colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreRight][c] = (uint8_t)ColourIndex::PlayerMissile1;
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreRight)][c] = static_cast<uint8_t>(ColourIndex::PlayerMissile1);
}
// next test for everybody: if there is a player 0 or missile 0 pixel, plot that colour instead
if(has_player0 || has_missile0) {
colour_mask_by_mode_collision_flags_[(int)ColourMode::Standard][c] =
colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreLeft][c] =
colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreRight][c] =
colour_mask_by_mode_collision_flags_[(int)ColourMode::OnTop][c] = (uint8_t)ColourIndex::PlayerMissile0;
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::Standard)][c] =
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreLeft)][c] =
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreRight)][c] =
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::OnTop)][c] = static_cast<uint8_t>(ColourIndex::PlayerMissile0);
}
// if this is the left-hand side of score mode, the playfield has the same priority as player 0
if(has_playfield) {
colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreLeft][c] = (uint8_t)ColourIndex::PlayerMissile0;
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreLeft)][c] = static_cast<uint8_t>(ColourIndex::PlayerMissile0);
}
// a final test for 'on top' priority mode: if the playfield or ball are visible, prefer that colour to all others
if(has_playfield || has_ball) {
colour_mask_by_mode_collision_flags_[(int)ColourMode::OnTop][c] = (uint8_t)ColourIndex::PlayfieldBall;
colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::OnTop)][c] = static_cast<uint8_t>(ColourIndex::PlayfieldBall);
}
}
}
@@ -162,7 +162,7 @@ void TIA::set_output_mode(Atari2600::TIA::OutputMode output_mode) {
// cycles_per_line * 2 cycles of information from one sync edge to the next
crt_->set_new_display_type(cycles_per_line * 2 - 1, display_type);
/* speaker_->set_input_rate((float)(get_clock_rate() / 38.0));*/
/* speaker_->set_input_rate(static_cast<float>(get_clock_rate() / 38.0));*/
}
void TIA::run_for(const Cycles cycles) {
@@ -203,23 +203,23 @@ int TIA::get_cycles_until_horizontal_blank(const Cycles from_offset) {
}
void TIA::set_background_colour(uint8_t colour) {
colour_palette_[(int)ColourIndex::Background] = colour;
colour_palette_[static_cast<int>(ColourIndex::Background)] = colour;
}
void TIA::set_playfield(uint16_t offset, uint8_t value) {
assert(offset >= 0 && offset < 3);
switch(offset) {
case 0:
background_[1] = (background_[1] & 0x0ffff) | ((uint32_t)reverse_table[value & 0xf0] << 16);
background_[0] = (background_[0] & 0xffff0) | (uint32_t)(value >> 4);
background_[1] = (background_[1] & 0x0ffff) | (static_cast<uint32_t>(reverse_table[value & 0xf0]) << 16);
background_[0] = (background_[0] & 0xffff0) | static_cast<uint32_t>(value >> 4);
break;
case 1:
background_[1] = (background_[1] & 0xf00ff) | ((uint32_t)value << 8);
background_[0] = (background_[0] & 0xff00f) | ((uint32_t)reverse_table[value] << 4);
background_[1] = (background_[1] & 0xf00ff) | (static_cast<uint32_t>(value) << 8);
background_[0] = (background_[0] & 0xff00f) | (static_cast<uint32_t>(reverse_table[value]) << 4);
break;
case 2:
background_[1] = (background_[1] & 0xfff00) | reverse_table[value];
background_[0] = (background_[0] & 0x00fff) | ((uint32_t)value << 12);
background_[0] = (background_[0] & 0x00fff) | (static_cast<uint32_t>(value) << 12);
break;
}
}
@@ -243,7 +243,7 @@ void TIA::set_playfield_control_and_ball_size(uint8_t value) {
}
void TIA::set_playfield_ball_colour(uint8_t colour) {
colour_palette_[(int)ColourIndex::PlayfieldBall] = colour;
colour_palette_[static_cast<int>(ColourIndex::PlayfieldBall)] = colour;
}
void TIA::set_player_number_and_size(int player, uint8_t value) {
@@ -305,7 +305,7 @@ void TIA::set_player_motion(int player, uint8_t motion) {
void TIA::set_player_missile_colour(int player, uint8_t colour) {
assert(player >= 0 && player < 2);
colour_palette_[(int)ColourIndex::PlayerMissile0 + player] = colour;
colour_palette_[static_cast<int>(ColourIndex::PlayerMissile0) + player] = colour;
}
void TIA::set_missile_enable(int missile, bool enabled) {
@@ -360,7 +360,7 @@ void TIA::clear_motion() {
}
uint8_t TIA::get_collision_flags(int offset) {
return (uint8_t)((collision_flags_ >> (offset << 1)) << 6) & 0xc0;
return static_cast<uint8_t>((collision_flags_ >> (offset << 1)) << 6) & 0xc0;
}
void TIA::clear_collision_flags() {
@@ -401,22 +401,22 @@ void TIA::output_for_cycles(int number_of_cycles) {
int latent_start = output_cursor + 4;
int latent_end = horizontal_counter_ + 4;
draw_playfield(latent_start, latent_end);
draw_object<Player>(player_[0], (uint8_t)CollisionType::Player0, output_cursor, horizontal_counter_);
draw_object<Player>(player_[1], (uint8_t)CollisionType::Player1, output_cursor, horizontal_counter_);
draw_missile(missile_[0], player_[0], (uint8_t)CollisionType::Missile0, output_cursor, horizontal_counter_);
draw_missile(missile_[1], player_[1], (uint8_t)CollisionType::Missile1, output_cursor, horizontal_counter_);
draw_object<Ball>(ball_, (uint8_t)CollisionType::Ball, output_cursor, horizontal_counter_);
draw_object<Player>(player_[0], static_cast<uint8_t>(CollisionType::Player0), output_cursor, horizontal_counter_);
draw_object<Player>(player_[1], static_cast<uint8_t>(CollisionType::Player1), output_cursor, horizontal_counter_);
draw_missile(missile_[0], player_[0], static_cast<uint8_t>(CollisionType::Missile0), output_cursor, horizontal_counter_);
draw_missile(missile_[1], player_[1], static_cast<uint8_t>(CollisionType::Missile1), output_cursor, horizontal_counter_);
draw_object<Ball>(ball_, static_cast<uint8_t>(CollisionType::Ball), output_cursor, horizontal_counter_);
// convert to television signals
#define Period(function, target) \
if(output_cursor < target) { \
if(horizontal_counter_ <= target) { \
if(crt_) crt_->function((unsigned int)((horizontal_counter_ - output_cursor) * 2)); \
if(crt_) crt_->function(static_cast<unsigned int>((horizontal_counter_ - output_cursor) * 2)); \
horizontal_counter_ %= cycles_per_line; \
return; \
} else { \
if(crt_) crt_->function((unsigned int)((target - output_cursor) * 2)); \
if(crt_) crt_->function(static_cast<unsigned int>((target - output_cursor) * 2)); \
output_cursor = target; \
} \
}
@@ -442,12 +442,12 @@ void TIA::output_for_cycles(int number_of_cycles) {
if(output_mode_ & blank_flag) {
if(pixel_target_) {
output_pixels(pixels_start_location_, output_cursor);
if(crt_) crt_->output_data((unsigned int)(output_cursor - pixels_start_location_) * 2, 2);
if(crt_) crt_->output_data(static_cast<unsigned int>(output_cursor - pixels_start_location_) * 2, 2);
pixel_target_ = nullptr;
pixels_start_location_ = 0;
}
int duration = std::min(228, horizontal_counter_) - output_cursor;
if(crt_) crt_->output_blank((unsigned int)(duration * 2));
if(crt_) crt_->output_blank(static_cast<unsigned int>(duration * 2));
} else {
if(!pixels_start_location_ && crt_) {
pixels_start_location_ = output_cursor;
@@ -464,7 +464,7 @@ void TIA::output_for_cycles(int number_of_cycles) {
}
if(horizontal_counter_ == cycles_per_line && crt_) {
crt_->output_data((unsigned int)(output_cursor - pixels_start_location_) * 2, 2);
crt_->output_data(static_cast<unsigned int>(output_cursor - pixels_start_location_) * 2, 2);
pixel_target_ = nullptr;
pixels_start_location_ = 0;
}
@@ -490,18 +490,18 @@ void TIA::output_pixels(int start, int end) {
if(playfield_priority_ == PlayfieldPriority::Score) {
while(start < end && start < first_pixel_cycle + 80) {
uint8_t buffer_value = collision_buffer_[start - first_pixel_cycle];
pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreLeft][buffer_value]];
pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreLeft)][buffer_value]];
start++;
target_position++;
}
while(start < end) {
uint8_t buffer_value = collision_buffer_[start - first_pixel_cycle];
pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[(int)ColourMode::ScoreRight][buffer_value]];
pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreRight)][buffer_value]];
start++;
target_position++;
}
} else {
int table_index = (int)((playfield_priority_ == PlayfieldPriority::Standard) ? ColourMode::Standard : ColourMode::OnTop);
int table_index = static_cast<int>((playfield_priority_ == PlayfieldPriority::Standard) ? ColourMode::Standard : ColourMode::OnTop);
while(start < end) {
uint8_t buffer_value = collision_buffer_[start - first_pixel_cycle];
pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[table_index][buffer_value]];
@@ -553,7 +553,7 @@ void TIA::draw_playfield(int start, int end) {
while(aligned_position < end) {
int offset = (aligned_position - first_pixel_cycle) >> 2;
uint32_t value = ((background_[(offset/20)&background_half_mask_] >> (offset%20))&1) * 0x01010101;
*(uint32_t *)&collision_buffer_[aligned_position - first_pixel_cycle] |= value;
*reinterpret_cast<uint32_t *>(&collision_buffer_[aligned_position - first_pixel_cycle]) |= value;
aligned_position += 4;
}
}

View File

@@ -22,7 +22,7 @@ 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
@@ -57,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

@@ -9,127 +9,20 @@
#ifndef Commodore1540_hpp
#define Commodore1540_hpp
#include "../../../Processors/6502/6502.hpp"
#include "../../../Components/6522/6522.hpp"
#include "../SerialBus.hpp"
#include "../../../Storage/Disk/Disk.hpp"
#include "../../../Storage/Disk/DiskController.hpp"
#include "Implementation/C1540Base.hpp"
namespace Commodore {
namespace C1540 {
/*!
An implementation of the serial-port VIA in a Commodore 1540 — the VIA that facilitates all
IEC bus communications.
It is wired up such that Port B contains:
Bit 0: data input; 1 if the line is low, 0 if it is high;
Bit 1: data output; 1 if the line should be low, 0 if it should be high;
Bit 2: clock input; 1 if the line is low, 0 if it is high;
Bit 3: clock output; 1 if the line is low, 0 if it is high;
Bit 4: attention acknowledge output; exclusive ORd with the attention input and ORd onto the data output;
Bits 5/6: device select input; the 1540 will act as device 8 + [value of bits]
Bit 7: attention input; 1 if the line is low, 0 if it is high
The attention input is also connected to CA1, similarly inverted — the CA1 wire will be high when the bus is low and vice versa.
*/
class SerialPortVIA: public MOS::MOS6522<SerialPortVIA>, public MOS::MOS6522IRQDelegate {
public:
using MOS6522IRQDelegate::set_interrupt_status;
SerialPortVIA();
uint8_t get_port_input(Port);
void set_port_output(Port, uint8_t value, uint8_t mask);
void set_serial_line_state(::Commodore::Serial::Line, bool);
void set_serial_port(const std::shared_ptr<::Commodore::Serial::Port> &);
private:
uint8_t port_b_;
std::weak_ptr<::Commodore::Serial::Port> serial_port_;
bool attention_acknowledge_level_, attention_level_input_, data_level_output_;
void update_data_line();
};
/*!
An implementation of the drive VIA in a Commodore 1540 — the VIA that is used to interface with the disk.
It is wired up such that Port B contains:
Bits 0/1: head step direction
Bit 2: motor control
Bit 3: LED control (TODO)
Bit 4: write protect photocell status (TODO)
Bits 5/6: read/write density
Bit 7: 0 if sync marks are currently being detected, 1 otherwise.
... and Port A contains the byte most recently read from the disk or the byte next to write to the disk, depending on data direction.
It is implied that CA2 might be used to set processor overflow, CA1 a strobe for data input, and one of the CBs being definitive on
whether the disk head is being told to read or write, but it's unclear and I've yet to investigate. So, TODO.
*/
class DriveVIA: public MOS::MOS6522<DriveVIA>, public MOS::MOS6522IRQDelegate {
public:
class Delegate {
public:
virtual void drive_via_did_step_head(void *driveVIA, int direction) = 0;
virtual void drive_via_did_set_data_density(void *driveVIA, int density) = 0;
};
void set_delegate(Delegate *);
using MOS6522IRQDelegate::set_interrupt_status;
DriveVIA();
uint8_t get_port_input(Port port);
void set_sync_detected(bool);
void set_data_input(uint8_t);
bool get_should_set_overflow();
bool get_motor_enabled();
void set_control_line_output(Port, Line, bool value);
void set_port_output(Port, uint8_t value, uint8_t direction_mask);
private:
uint8_t port_b_, port_a_;
bool should_set_overflow_;
bool drive_motor_;
uint8_t previous_port_b_output_;
Delegate *delegate_;
};
/*!
An implementation of the C1540's serial port; this connects incoming line levels to the serial-port VIA.
*/
class SerialPort : public ::Commodore::Serial::Port {
public:
void set_input(::Commodore::Serial::Line, ::Commodore::Serial::LineLevel);
void set_serial_port_via(const std::shared_ptr<SerialPortVIA> &);
private:
std::weak_ptr<SerialPortVIA> serial_port_VIA_;
};
/*!
Provides an emulation of the C1540.
*/
class Machine:
public CPU::MOS6502::Processor<Machine>,
public MOS::MOS6522IRQDelegate::Delegate,
public DriveVIA::Delegate,
public Storage::Disk::Controller {
class Machine: public MachineBase {
public:
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.
Sets the ROM image to use for this drive; it is asserted that the buffer provided is 16 kb in size.
*/
void set_rom(const std::vector<uint8_t> &rom);
@@ -138,30 +31,11 @@ class Machine:
*/
void set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus);
/// Advances time.
void run_for(const Cycles cycles);
/// Inserts @c disk into the drive.
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk);
// 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);
// to satisfy DriveVIA::Delegate
void drive_via_did_step_head(void *driveVIA, int direction);
void drive_via_did_set_data_density(void *driveVIA, int density);
private:
uint8_t ram_[0x800];
uint8_t rom_[0x4000];
std::shared_ptr<SerialPortVIA> serial_port_VIA_;
std::shared_ptr<SerialPort> serial_port_;
DriveVIA drive_VIA_;
int shift_register_, bit_window_offset_;
virtual void process_input_bit(int value, unsigned int cycles_since_index_hole);
virtual void process_index_hole();
};
}

View File

@@ -7,34 +7,44 @@
//
#include "C1540.hpp"
#include <string>
#include "../../../Storage/Disk/Encodings/CommodoreGCR.hpp"
#include <cassert>
#include "../../../../Storage/Disk/Encodings/CommodoreGCR.hpp"
using namespace Commodore::C1540;
Machine::Machine() :
MachineBase::MachineBase() :
m6502_(*this),
shift_register_(0),
Storage::Disk::Controller(1000000, 4, 300),
Storage::Disk::Controller(1000000),
serial_port_(new SerialPort),
serial_port_VIA_(new SerialPortVIA) {
serial_port_VIA_port_handler_(new SerialPortVIA(serial_port_VIA_)),
drive_VIA_(drive_VIA_port_handler_),
serial_port_VIA_(*serial_port_VIA_port_handler_),
drive_(new Storage::Disk::Drive(1000000, 300, 2)) {
// attach the serial port to its VIA and vice versa
serial_port_->set_serial_port_via(serial_port_VIA_);
serial_port_VIA_->set_serial_port(serial_port_);
serial_port_->set_serial_port_via(serial_port_VIA_port_handler_);
serial_port_VIA_port_handler_->set_serial_port(serial_port_);
// set this instance as the delegate to receive interrupt requests from both VIAs
serial_port_VIA_->set_interrupt_delegate(this);
drive_VIA_.set_interrupt_delegate(this);
drive_VIA_.set_delegate(this);
serial_port_VIA_port_handler_->set_interrupt_delegate(this);
drive_VIA_port_handler_.set_interrupt_delegate(this);
drive_VIA_port_handler_.set_delegate(this);
// set a bit rate
set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(3));
// attach the only drive there is
set_drive(drive_);
}
void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus) {
Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus);
}
Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
Cycles MachineBase::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):
@@ -49,13 +59,14 @@ Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint
else
ram_[address] = *value;
} else if(address >= 0xc000) {
if(isReadOperation(operation))
if(isReadOperation(operation)) {
*value = rom_[address & 0x3fff];
}
} else if(address >= 0x1800 && address <= 0x180f) {
if(isReadOperation(operation))
*value = serial_port_VIA_->get_register(address);
*value = serial_port_VIA_.get_register(address);
else
serial_port_VIA_->set_register(address, *value);
serial_port_VIA_.set_register(address, *value);
} else if(address >= 0x1c00 && address <= 0x1c0f) {
if(isReadOperation(operation))
*value = drive_VIA_.get_register(address);
@@ -63,81 +74,82 @@ Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint
drive_VIA_.set_register(address, *value);
}
serial_port_VIA_->run_for(Cycles(1));
serial_port_VIA_.run_for(Cycles(1));
drive_VIA_.run_for(Cycles(1));
return Cycles(1);
}
void Machine::set_rom(const std::vector<uint8_t> &rom) {
assert(rom.size() == sizeof(rom_));
memcpy(rom_, rom.data(), std::min(sizeof(rom_), rom.size()));
}
void Machine::set_disk(std::shared_ptr<Storage::Disk::Disk> disk) {
std::shared_ptr<Storage::Disk::Drive> drive(new Storage::Disk::Drive);
drive->set_disk(disk);
set_drive(drive);
drive_->set_disk(disk);
}
void Machine::run_for(const Cycles cycles) {
CPU::MOS6502::Processor<Machine>::run_for(cycles);
set_motor_on(drive_VIA_.get_motor_enabled());
if(drive_VIA_.get_motor_enabled()) // TODO: motor speed up/down
m6502_.run_for(cycles);
bool drive_motor = drive_VIA_port_handler_.get_motor_enabled();
drive_->set_motor_on(drive_motor);
if(drive_motor)
Storage::Disk::Controller::run_for(cycles);
}
#pragma mark - 6522 delegate
void Machine::mos6522_did_change_interrupt_status(void *mos6522) {
void MachineBase::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
void Machine::process_input_bit(int value, unsigned int cycles_since_index_hole) {
void MachineBase::process_input_bit(int value) {
shift_register_ = (shift_register_ << 1) | value;
if((shift_register_ & 0x3ff) == 0x3ff) {
drive_VIA_.set_sync_detected(true);
drive_VIA_port_handler_.set_sync_detected(true);
bit_window_offset_ = -1; // i.e. this bit isn't the first within a data window, but the next might be
} else {
drive_VIA_.set_sync_detected(false);
drive_VIA_port_handler_.set_sync_detected(false);
}
bit_window_offset_++;
if(bit_window_offset_ == 8) {
drive_VIA_.set_data_input((uint8_t)shift_register_);
drive_VIA_port_handler_.set_data_input(static_cast<uint8_t>(shift_register_));
bit_window_offset_ = 0;
if(drive_VIA_.get_should_set_overflow()) {
set_overflow_line(true);
if(drive_VIA_port_handler_.get_should_set_overflow()) {
m6502_.set_overflow_line(true);
}
}
else set_overflow_line(false);
else m6502_.set_overflow_line(false);
}
// the 1540 does not recognise index holes
void Machine::process_index_hole() {}
void MachineBase::process_index_hole() {}
#pragma mak - Drive VIA delegate
void Machine::drive_via_did_step_head(void *driveVIA, int direction) {
step(direction);
void MachineBase::drive_via_did_step_head(void *driveVIA, int direction) {
drive_->step(direction);
}
void Machine::drive_via_did_set_data_density(void *driveVIA, int density) {
set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone((unsigned int)density));
void MachineBase::drive_via_did_set_data_density(void *driveVIA, int density) {
set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(static_cast<unsigned int>(density)));
}
#pragma mark - SerialPortVIA
SerialPortVIA::SerialPortVIA() :
port_b_(0x00), attention_acknowledge_level_(false), attention_level_input_(true), data_level_output_(false) {}
SerialPortVIA::SerialPortVIA(MOS::MOS6522::MOS6522<SerialPortVIA> &via) :
port_b_(0x00), attention_acknowledge_level_(false), attention_level_input_(true), data_level_output_(false), via_(via) {}
uint8_t SerialPortVIA::get_port_input(Port port) {
uint8_t SerialPortVIA::get_port_input(MOS::MOS6522::Port port) {
if(port) return port_b_;
return 0xff;
}
void SerialPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask) {
void SerialPortVIA::set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t mask) {
if(port) {
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
if(serialPort) {
@@ -151,6 +163,8 @@ void SerialPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask) {
}
void SerialPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool value) {
// printf("[C1540] %s is %s\n", StringForLine(line), value ? "high" : "low");
switch(line) {
default: break;
case ::Commodore::Serial::Line::Data: port_b_ = (port_b_ & ~0x01) | (value ? 0x00 : 0x01); break;
@@ -158,7 +172,7 @@ void SerialPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool v
case ::Commodore::Serial::Line::Attention:
attention_level_input_ = !value;
port_b_ = (port_b_ & ~0x80) | (value ? 0x00 : 0x80);
set_control_line_input(Port::A, Line::One, !value);
via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !value);
update_data_line();
break;
}
@@ -186,7 +200,7 @@ void DriveVIA::set_delegate(Delegate *delegate) {
// write protect tab uncovered
DriveVIA::DriveVIA() : port_b_(0xff), port_a_(0xff), delegate_(nullptr) {}
uint8_t DriveVIA::get_port_input(Port port) {
uint8_t DriveVIA::get_port_input(MOS::MOS6522::Port port) {
return port ? port_b_ : port_a_;
}
@@ -206,33 +220,35 @@ bool DriveVIA::get_motor_enabled() {
return drive_motor_;
}
void DriveVIA::set_control_line_output(Port port, Line line, bool value) {
if(port == Port::A && line == Line::Two) {
void DriveVIA::set_control_line_output(MOS::MOS6522::Port port, MOS::MOS6522::Line line, bool value) {
if(port == MOS::MOS6522::Port::A && line == MOS::MOS6522::Line::Two) {
should_set_overflow_ = value;
}
}
void DriveVIA::set_port_output(Port port, uint8_t value, uint8_t direction_mask) {
void DriveVIA::set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t direction_mask) {
if(port) {
// record drive motor state
drive_motor_ = !!(value&4);
if(previous_port_b_output_ != value) {
// record drive motor state
drive_motor_ = !!(value&4);
// check for a head step
int step_difference = ((value&3) - (previous_port_b_output_&3))&3;
if(step_difference) {
if(delegate_) delegate_->drive_via_did_step_head(this, (step_difference == 1) ? 1 : -1);
// check for a head step
int step_difference = ((value&3) - (previous_port_b_output_&3))&3;
if(step_difference) {
if(delegate_) delegate_->drive_via_did_step_head(this, (step_difference == 1) ? 1 : -1);
}
// check for a change in density
int density_difference = (previous_port_b_output_^value) & (3 << 5);
if(density_difference && delegate_) {
delegate_->drive_via_did_set_data_density(this, (value >> 5)&3);
}
// TODO: something with the drive LED
// printf("LED: %s\n", value&8 ? "On" : "Off");
previous_port_b_output_ = value;
}
// check for a change in density
int density_difference = (previous_port_b_output_^value) & (3 << 5);
if(density_difference && delegate_) {
delegate_->drive_via_did_set_data_density(this, (value >> 5)&3);
}
// TODO: something with the drive LED
// printf("LED: %s\n", value&8 ? "On" : "Off");
previous_port_b_output_ = value;
}
}

View File

@@ -0,0 +1,158 @@
//
// C1540Base.hpp
// Clock Signal
//
// Created by Thomas Harte on 04/09/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef C1540Base_hpp
#define C1540Base_hpp
#include "../../../../Processors/6502/6502.hpp"
#include "../../../../Components/6522/6522.hpp"
#include "../../SerialBus.hpp"
#include "../../../../Storage/Disk/Disk.hpp"
#include "../../../../Storage/Disk/Controller/DiskController.hpp"
namespace Commodore {
namespace C1540 {
/*!
An implementation of the serial-port VIA in a Commodore 1540 — the VIA that facilitates all
IEC bus communications.
It is wired up such that Port B contains:
Bit 0: data input; 1 if the line is low, 0 if it is high;
Bit 1: data output; 1 if the line should be low, 0 if it should be high;
Bit 2: clock input; 1 if the line is low, 0 if it is high;
Bit 3: clock output; 1 if the line is low, 0 if it is high;
Bit 4: attention acknowledge output; exclusive ORd with the attention input and ORd onto the data output;
Bits 5/6: device select input; the 1540 will act as device 8 + [value of bits]
Bit 7: attention input; 1 if the line is low, 0 if it is high
The attention input is also connected to CA1, similarly inverted — the CA1 wire will be high when the bus is low and vice versa.
*/
class SerialPortVIA: public MOS::MOS6522::IRQDelegatePortHandler {
public:
SerialPortVIA(MOS::MOS6522::MOS6522<SerialPortVIA> &via);
uint8_t get_port_input(MOS::MOS6522::Port);
void set_port_output(MOS::MOS6522::Port, uint8_t value, uint8_t mask);
void set_serial_line_state(::Commodore::Serial::Line, bool);
void set_serial_port(const std::shared_ptr<::Commodore::Serial::Port> &);
private:
MOS::MOS6522::MOS6522<SerialPortVIA> &via_;
uint8_t port_b_;
std::weak_ptr<::Commodore::Serial::Port> serial_port_;
bool attention_acknowledge_level_, attention_level_input_, data_level_output_;
void update_data_line();
};
/*!
An implementation of the drive VIA in a Commodore 1540 — the VIA that is used to interface with the disk.
It is wired up such that Port B contains:
Bits 0/1: head step direction
Bit 2: motor control
Bit 3: LED control (TODO)
Bit 4: write protect photocell status (TODO)
Bits 5/6: read/write density
Bit 7: 0 if sync marks are currently being detected, 1 otherwise.
... and Port A contains the byte most recently read from the disk or the byte next to write to the disk, depending on data direction.
It is implied that CA2 might be used to set processor overflow, CA1 a strobe for data input, and one of the CBs being definitive on
whether the disk head is being told to read or write, but it's unclear and I've yet to investigate. So, TODO.
*/
class DriveVIA: public MOS::MOS6522::IRQDelegatePortHandler {
public:
class Delegate {
public:
virtual void drive_via_did_step_head(void *driveVIA, int direction) = 0;
virtual void drive_via_did_set_data_density(void *driveVIA, int density) = 0;
};
void set_delegate(Delegate *);
DriveVIA();
uint8_t get_port_input(MOS::MOS6522::Port port);
void set_sync_detected(bool);
void set_data_input(uint8_t);
bool get_should_set_overflow();
bool get_motor_enabled();
void set_control_line_output(MOS::MOS6522::Port, MOS::MOS6522::Line, bool value);
void set_port_output(MOS::MOS6522::Port, uint8_t value, uint8_t direction_mask);
private:
uint8_t port_b_, port_a_;
bool should_set_overflow_;
bool drive_motor_;
uint8_t previous_port_b_output_;
Delegate *delegate_;
};
/*!
An implementation of the C1540's serial port; this connects incoming line levels to the serial-port VIA.
*/
class SerialPort : public ::Commodore::Serial::Port {
public:
void set_input(::Commodore::Serial::Line, ::Commodore::Serial::LineLevel);
void set_serial_port_via(const std::shared_ptr<SerialPortVIA> &);
private:
std::weak_ptr<SerialPortVIA> serial_port_VIA_;
};
class MachineBase:
public CPU::MOS6502::BusHandler,
public MOS::MOS6522::IRQDelegatePortHandler::Delegate,
public DriveVIA::Delegate,
public Storage::Disk::Controller {
public:
MachineBase();
// 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);
// to satisfy DriveVIA::Delegate
void drive_via_did_step_head(void *driveVIA, int direction);
void drive_via_did_set_data_density(void *driveVIA, int density);
protected:
CPU::MOS6502::Processor<MachineBase, false> m6502_;
std::shared_ptr<Storage::Disk::Drive> drive_;
uint8_t ram_[0x800];
uint8_t rom_[0x4000];
std::shared_ptr<SerialPortVIA> serial_port_VIA_port_handler_;
std::shared_ptr<SerialPort> serial_port_;
DriveVIA drive_VIA_port_handler_;
MOS::MOS6522::MOS6522<DriveVIA> drive_VIA_;
MOS::MOS6522::MOS6522<SerialPortVIA> serial_port_VIA_;
int shift_register_, bit_window_offset_;
virtual void process_input_bit(int value);
virtual void process_index_hole();
};
}
}
#endif /* C1540Base_hpp */

View File

@@ -27,12 +27,12 @@ void ::Commodore::Serial::AttachPortAndBus(std::shared_ptr<Port> port, std::shar
void Bus::add_port(std::shared_ptr<Port> port) {
ports_.push_back(port);
for(int line = (int)ServiceRequest; line <= (int)Reset; line++) {
for(int line = static_cast<int>(ServiceRequest); line <= static_cast<int>(Reset); line++) {
// the addition of a new device may change the line output...
set_line_output_did_change((Line)line);
set_line_output_did_change(static_cast<Line>(line));
// ... but the new device will need to be told the current state regardless
port->set_input((Line)line, line_levels_[line]);
port->set_input(static_cast<Line>(line), line_levels_[line]);
}
}
@@ -46,6 +46,8 @@ void Bus::set_line_output_did_change(Line line) {
}
}
// printf("[Bus] %s is %s\n", StringForLine(line), new_line_level ? "high" : "low");
// post an update only if one occurred
if(new_line_level != line_levels_[line]) {
line_levels_[line] = new_line_level;

View File

@@ -1,17 +1,80 @@
//
// Typer.cpp
// Keyboard.cpp
// Clock Signal
//
// Created by Thomas Harte on 05/11/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
// Created by Thomas Harte on 10/10/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "Vic20.hpp"
#include "Keyboard.hpp"
uint16_t *Commodore::Vic20::Machine::sequence_for_character(Utility::Typer *typer, char character) {
#define KEYS(...) {__VA_ARGS__, EndSequence}
#define SHIFT(...) {KeyLShift, __VA_ARGS__, EndSequence}
#define X {NotMapped}
using namespace Commodore::Vic20;
uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
#define BIND(source, dest) case Inputs::Keyboard::Key::source: return Commodore::Vic20::dest
switch(key) {
default: break;
BIND(k0, Key0); BIND(k1, Key1); BIND(k2, Key2); BIND(k3, Key3); BIND(k4, Key4);
BIND(k5, Key5); BIND(k6, Key6); BIND(k7, Key7); BIND(k8, Key8); BIND(k9, Key9);
BIND(Q, KeyQ); BIND(W, KeyW); BIND(E, KeyE); BIND(R, KeyR); BIND(T, KeyT);
BIND(Y, KeyY); BIND(U, KeyU); BIND(I, KeyI); BIND(O, KeyO); BIND(P, KeyP);
BIND(A, KeyA); BIND(S, KeyS); BIND(D, KeyD); BIND(F, KeyF); BIND(G, KeyG);
BIND(H, KeyH); BIND(J, KeyJ); BIND(K, KeyK); BIND(L, KeyL);
BIND(Z, KeyZ); BIND(X, KeyX); BIND(C, KeyC); BIND(V, KeyV);
BIND(B, KeyB); BIND(N, KeyN); BIND(M, KeyM);
BIND(BackTick, KeyLeft);
BIND(Hyphen, KeyPlus);
BIND(Equals, KeyDash);
BIND(F11, KeyGBP);
BIND(F12, KeyHome);
BIND(Tab, KeyControl);
BIND(OpenSquareBracket, KeyAt);
BIND(CloseSquareBracket, KeyAsterisk);
BIND(BackSlash, KeyRestore);
BIND(Hash, KeyUp);
BIND(F10, KeyUp);
BIND(Semicolon, KeyColon);
BIND(Quote, KeySemicolon);
BIND(F9, KeyEquals);
BIND(LeftMeta, KeyCBM);
BIND(LeftOption, KeyCBM);
BIND(RightOption, KeyCBM);
BIND(RightMeta, KeyCBM);
BIND(LeftShift, KeyLShift);
BIND(RightShift, KeyRShift);
BIND(Comma, KeyComma);
BIND(FullStop, KeyFullStop);
BIND(ForwardSlash, KeySlash);
BIND(Right, KeyRight);
BIND(Down, KeyDown);
BIND(Enter, KeyReturn);
BIND(Space, KeySpace);
BIND(BackSpace, KeyDelete);
BIND(Escape, KeyRunStop);
BIND(F1, KeyF1);
BIND(F3, KeyF3);
BIND(F5, KeyF5);
BIND(F7, KeyF7);
}
#undef BIND
return KeyboardMachine::Machine::KeyNotMapped;
}
uint16_t *CharacterMapper::sequence_for_character(char character) {
#define KEYS(...) {__VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
#define SHIFT(...) {KeyLShift, __VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
#define X {KeyboardMachine::Machine::KeyNotMapped}
static KeySequence key_sequences[] = {
/* NUL */ X, /* SOH */ X,
/* STX */ X, /* ETX */ X,

View File

@@ -0,0 +1,52 @@
//
// Keyboard.hpp
// Clock Signal
//
// Created by Thomas Harte on 10/10/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef Machines_Commodore_Vic20_Keyboard_hpp
#define Machines_Commodore_Vic20_Keyboard_hpp
#include "../../KeyboardMachine.hpp"
#include "../../Utility/Typer.hpp"
namespace Commodore {
namespace Vic20 {
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),
KeyO = key(6, 0x10), KeyAt = key(6, 0x20), KeyUp = key(6, 0x40), KeyF5 = key(6, 0x80),
KeyCBM = key(5, 0x01), KeyS = key(5, 0x02), KeyF = key(5, 0x04), KeyH = key(5, 0x08),
KeyK = key(5, 0x10), KeyColon = key(5, 0x20), KeyEquals = key(5, 0x40), KeyF3 = key(5, 0x80),
KeySpace = key(4, 0x01), KeyZ = key(4, 0x02), KeyC = key(4, 0x04), KeyB = key(4, 0x08),
KeyM = key(4, 0x10), KeyFullStop = key(4, 0x20), KeyRShift = key(4, 0x40), KeyF1 = key(4, 0x80),
KeyRunStop = key(3, 0x01), KeyLShift = key(3, 0x02), KeyX = key(3, 0x04), KeyV = key(3, 0x08),
KeyN = key(3, 0x10), KeyComma = key(3, 0x20), KeySlash = key(3, 0x40), KeyDown = key(3, 0x80),
KeyControl = key(2, 0x01), KeyA = key(2, 0x02), KeyD = key(2, 0x04), KeyG = key(2, 0x08),
KeyJ = key(2, 0x10), KeyL = key(2, 0x20), KeySemicolon = key(2, 0x40), KeyRight = key(2, 0x80),
KeyLeft = key(1, 0x01), KeyW = key(1, 0x02), KeyR = key(1, 0x04), KeyY = key(1, 0x08),
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),
KeyRestore = 0xfffd
#undef key
};
struct KeyboardMapper: public KeyboardMachine::Machine::KeyboardMapper {
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key);
};
struct CharacterMapper: public ::Utility::CharacterMapper {
uint16_t *sequence_for_character(char character);
};
}
}
#endif /* Keyboard_hpp */

File diff suppressed because it is too large Load Diff

View File

@@ -11,17 +11,10 @@
#include "../../ConfigurationTarget.hpp"
#include "../../CRTMachine.hpp"
#include "../../Typer.hpp"
#include "../../KeyboardMachine.hpp"
#include "../../JoystickMachine.hpp"
#include "../../../Processors/6502/6502.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,181 +37,29 @@ enum Region {
PAL
};
#define key(line, mask) (((mask) << 3) | (line))
enum Key: uint16_t {
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),
KeyO = key(6, 0x10), KeyAt = key(6, 0x20), KeyUp = key(6, 0x40), KeyF5 = key(6, 0x80),
KeyCBM = key(5, 0x01), KeyS = key(5, 0x02), KeyF = key(5, 0x04), KeyH = key(5, 0x08),
KeyK = key(5, 0x10), KeyColon = key(5, 0x20), KeyEquals = key(5, 0x40), KeyF3 = key(5, 0x80),
KeySpace = key(4, 0x01), KeyZ = key(4, 0x02), KeyC = key(4, 0x04), KeyB = key(4, 0x08),
KeyM = key(4, 0x10), KeyFullStop = key(4, 0x20), KeyRShift = key(4, 0x40), KeyF1 = key(4, 0x80),
KeyRunStop = key(3, 0x01), KeyLShift = key(3, 0x02), KeyX = key(3, 0x04), KeyV = key(3, 0x08),
KeyN = key(3, 0x10), KeyComma = key(3, 0x20), KeySlash = key(3, 0x40), KeyDown = key(3, 0x80),
KeyControl = key(2, 0x01), KeyA = key(2, 0x02), KeyD = key(2, 0x04), KeyG = key(2, 0x08),
KeyJ = key(2, 0x10), KeyL = key(2, 0x20), KeySemicolon = key(2, 0x40), KeyRight = key(2, 0x80),
KeyLeft = key(1, 0x01), KeyW = key(1, 0x02), KeyR = key(1, 0x04), KeyY = key(1, 0x08),
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),
};
enum JoystickInput {
Up = 0x04,
Down = 0x08,
Left = 0x10,
Right = 0x80,
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 CPU::MOS6502::Processor<Machine>,
public CRTMachine::Machine,
public MOS::MOS6522IRQDelegate::Delegate,
public Utility::TypeRecipient,
public Storage::Tape::BinaryTapePlayer::Delegate,
public ConfigurationTarget::Machine {
public ConfigurationTarget::Machine,
public KeyboardMachine::Machine,
public JoystickMachine::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 memory size of this Vic-20.
virtual void set_memory_size(MemorySize size) = 0;
inline void set_use_fast_tape_hack(bool activate) { use_fast_tape_hack_ = activate; }
/// Sets the region of this Vic-20.
virtual void set_region(Region region) = 0;
// to satisfy CPU::MOS6502::Processor
Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value);
void flush() { mos6560_->flush(); }
// 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(const Cycles cycles) { CPU::MOS6502::Processor<Machine>::run_for(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::vector<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

@@ -8,390 +8,475 @@
#include "Electron.hpp"
#include "../../Processors/6502/6502.hpp"
#include "../../Storage/Tape/Tape.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../ClockReceiver/ForceInline.hpp"
#include "../Utility/Typer.hpp"
#include "Interrupts.hpp"
#include "Keyboard.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) {
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(static_cast<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 = static_cast<ROMSlot>((static_cast<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 = static_cast<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(static_cast<int>(cycles));
cycles_since_audio_update_ += Cycles(static_cast<int>(cycles));
if(cycles_since_audio_update_ > Cycles(16384)) update_audio();
tape_.run_for(Cycles(static_cast<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(static_cast<int>(cycles)));
if(plus3_) plus3_->run_for(Cycles(4*static_cast<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(static_cast<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));
}
KeyboardMapper &get_keyboard_mapper() override {
return keyboard_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] = {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false};
uint8_t os_[16384], ram_[32768];
std::vector<uint8_t> dfs_, adfs_;
// Paging
ROMSlot active_rom_ = ROMSlot::ROMSlot0;
bool keyboard_is_active_ = false;
bool basic_is_active_ = false;
// Interrupt and keyboard state
uint8_t interrupt_status_ = Interrupt::PowerOnReset | Interrupt::TransmitDataEmpty | 0x80;
uint8_t interrupt_control_ = 0;
uint8_t key_states_[14];
Electron::KeyboardMapper keyboard_mapper_;
// Counters related to simultaneous subsystems
Cycles cycles_since_display_update_ = 0;
Cycles cycles_since_audio_update_ = 0;
int cycles_until_display_interrupt_ = 0;
Interrupt next_display_interrupt_ = Interrupt::RealTimeClock;
VideoOutput::Range video_access_range_ = {0, 0xffff};
// Tape
Tape tape_;
bool use_fast_tape_hack_ = false;
bool fast_load_is_in_data_ = false;
// Disk
std::unique_ptr<Plus3> plus3_;
bool is_holding_shift_ = false;
int shift_restart_counter_ = 0;
// Outputs
std::unique_ptr<VideoOutput> video_output_;
std::shared_ptr<Speaker> speaker_;
bool speaker_is_enabled_ = false;
};
}
using namespace Electron;
#pragma mark - Lifecycle
Machine::Machine() :
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);
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()) {
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
Cycles Machine::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)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;
set_value_of_register(CPU::MOS6502::Register::A, 0);
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;
set_power_on(true);
set_key_state(KeyShift, true);
is_holding_shift_ = true;
}
}
return Cycles((int)cycles);
}
void Machine::flush() {
update_display();
update_audio();
speaker_->flush();
}
#pragma mark - Deferred scheduling
inline void Machine::update_display() {
if(cycles_since_display_update_ > 0) {
video_output_->run_for(cycles_since_display_update_.flush());
}
}
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_ > 0) {
speaker_->run_for(cycles_since_audio_update_.divide(Cycles(Speaker::clock_rate_divider)));
}
}
#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,19 +9,9 @@
#ifndef Electron_hpp
#define Electron_hpp
#include "../../Processors/6502/6502.hpp"
#include "../../Storage/Tape/Tape.hpp"
#include "../../ClockReceiver/ClockReceiver.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>
@@ -41,25 +31,6 @@ enum ROMSlot: uint8_t {
ROMSlotOS, ROMSlotDFS, ROMSlotADFS
};
enum Key: uint16_t {
KeySpace = 0x0000 | 0x08, KeyCopy = 0x0000 | 0x02, KeyRight = 0x0000 | 0x01,
KeyDelete = 0x0010 | 0x08, KeyReturn = 0x0010 | 0x04, KeyDown = 0x0010 | 0x02, KeyLeft = 0x0010 | 0x01,
KeyColon = 0x0020 | 0x04, KeyUp = 0x0020 | 0x02, KeyMinus = 0x0020 | 0x01,
KeySlash = 0x0030 | 0x08, KeySemiColon = 0x0030 | 0x04, KeyP = 0x0030 | 0x02, Key0 = 0x0030 | 0x01,
KeyFullStop = 0x0040 | 0x08, KeyL = 0x0040 | 0x04, KeyO = 0x0040 | 0x02, Key9 = 0x0040 | 0x01,
KeyComma = 0x0050 | 0x08, KeyK = 0x0050 | 0x04, KeyI = 0x0050 | 0x02, Key8 = 0x0050 | 0x01,
KeyM = 0x0060 | 0x08, KeyJ = 0x0060 | 0x04, KeyU = 0x0060 | 0x02, Key7 = 0x0060 | 0x01,
KeyN = 0x0070 | 0x08, KeyH = 0x0070 | 0x04, KeyY = 0x0070 | 0x02, Key6 = 0x0070 | 0x01,
KeyB = 0x0080 | 0x08, KeyG = 0x0080 | 0x04, KeyT = 0x0080 | 0x02, Key5 = 0x0080 | 0x01,
KeyV = 0x0090 | 0x08, KeyF = 0x0090 | 0x04, KeyR = 0x0090 | 0x02, Key4 = 0x0090 | 0x01,
KeyC = 0x00a0 | 0x08, KeyD = 0x00a0 | 0x04, KeyE = 0x00a0 | 0x02, Key3 = 0x00a0 | 0x01,
KeyX = 0x00b0 | 0x08, KeyS = 0x00b0 | 0x04, KeyW = 0x00b0 | 0x02, Key2 = 0x00b0 | 0x01,
KeyZ = 0x00c0 | 0x08, KeyA = 0x00c0 | 0x04, KeyQ = 0x00c0 | 0x02, Key1 = 0x00c0 | 0x01,
KeyShift = 0x00d0 | 0x08, KeyControl = 0x00d0 | 0x04, KeyFunc = 0x00d0 | 0x02, KeyEscape = 0x00d0 | 0x01,
KeyBreak = 0xfffd,
};
/*!
@abstract Represents an Acorn Electron.
@@ -67,88 +38,23 @@ enum Key: uint16_t {
Acorn Electron.
*/
class Machine:
public CPU::MOS6502::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 CPU::MOS6502::Processor
Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value);
void flush();
// 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(const Cycles cycles) { CPU::MOS6502::Processor<Machine>::run_for(cycles); }
// to satisfy Tape::Delegate
virtual void tape_did_change_interrupt_status(Tape *tape);
// for Utility::TypeRecipient
virtual HalfCycles get_typer_delay();
virtual HalfCycles 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
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_;
/// Enables or disables turbo-speed tape loading.
virtual void set_use_fast_tape_hack(bool activate) = 0;
};
}

View File

@@ -1,26 +1,65 @@
//
// Typer.cpp
// Keyboard.cpp
// Clock Signal
//
// Created by Thomas Harte on 05/11/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
// Created by Thomas Harte on 10/10/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "Electron.hpp"
#include "Keyboard.hpp"
HalfCycles Electron::Machine::get_typer_delay() {
return get_is_resetting() ? Cycles(625*25*128) : Cycles(0); // wait one second if resetting
using namespace Electron;
uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
#define BIND(source, dest) case Inputs::Keyboard::Key::source: return Electron::Key::dest
switch(key) {
default: return KeyCopy;
BIND(k0, Key0); BIND(k1, Key1); BIND(k2, Key2); BIND(k3, Key3); BIND(k4, Key4);
BIND(k5, Key5); BIND(k6, Key6); BIND(k7, Key7); BIND(k8, Key8); BIND(k9, Key9);
BIND(Q, KeyQ); BIND(W, KeyW); BIND(E, KeyE); BIND(R, KeyR); BIND(T, KeyT);
BIND(Y, KeyY); BIND(U, KeyU); BIND(I, KeyI); BIND(O, KeyO); BIND(P, KeyP);
BIND(A, KeyA); BIND(S, KeyS); BIND(D, KeyD); BIND(F, KeyF); BIND(G, KeyG);
BIND(H, KeyH); BIND(J, KeyJ); BIND(K, KeyK); BIND(L, KeyL);
BIND(Z, KeyZ); BIND(X, KeyX); BIND(C, KeyC); BIND(V, KeyV);
BIND(B, KeyB); BIND(N, KeyN); BIND(M, KeyM);
BIND(Comma, KeyComma);
BIND(FullStop, KeyFullStop);
BIND(ForwardSlash, KeySlash);
BIND(Semicolon, KeySemiColon);
BIND(Quote, KeyColon);
BIND(Escape, KeyEscape);
BIND(Equals, KeyBreak);
BIND(F12, KeyBreak);
BIND(Left, KeyLeft); BIND(Right, KeyRight); BIND(Up, KeyUp); BIND(Down, KeyDown);
BIND(Tab, KeyFunc); BIND(LeftOption, KeyFunc); BIND(RightOption, KeyFunc);
BIND(LeftMeta, KeyFunc); BIND(RightMeta, KeyFunc);
BIND(CapsLock, KeyControl); BIND(LeftControl, KeyControl); BIND(RightControl, KeyControl);
BIND(LeftShift, KeyShift); BIND(RightShift, KeyShift);
BIND(Hyphen, KeyMinus);
BIND(Delete, KeyDelete);
BIND(Enter, KeyReturn); BIND(KeyPadEnter, KeyReturn);
BIND(KeyPad0, Key0); BIND(KeyPad1, Key1); BIND(KeyPad2, Key2); BIND(KeyPad3, Key3); BIND(KeyPad4, Key4);
BIND(KeyPad5, Key5); BIND(KeyPad6, Key6); BIND(KeyPad7, Key7); BIND(KeyPad8, Key8); BIND(KeyPad9, Key9);
BIND(KeyPadMinus, KeyMinus); BIND(KeyPadPlus, KeyColon);
BIND(Space, KeySpace);
}
#undef BIND
}
HalfCycles Electron::Machine::get_typer_frequency() {
return Cycles(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__, EndSequence}
#define SHIFT(...) {KeyShift, __VA_ARGS__, EndSequence}
#define CTRL(...) {KeyControl, __VA_ARGS__, EndSequence}
#define X {NotMapped}
uint16_t *CharacterMapper::sequence_for_character(char character) {
#define KEYS(...) {__VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
#define SHIFT(...) {KeyShift, __VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
#define CTRL(...) {KeyControl, __VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
#define X {KeyboardMachine::Machine::KeyNotMapped}
static KeySequence key_sequences[] = {
/* NUL */ X, /* SOH */ X,
/* STX */ X, /* ETX */ X,

View File

@@ -0,0 +1,46 @@
//
// Keyboard.hpp
// Clock Signal
//
// Created by Thomas Harte on 10/10/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef Machines_Electron_Keyboard_hpp
#define Machines_Electron_Keyboard_hpp
#include "../KeyboardMachine.hpp"
#include "../Utility/Typer.hpp"
namespace Electron {
enum Key: uint16_t {
KeySpace = 0x0000 | 0x08, KeyCopy = 0x0000 | 0x02, KeyRight = 0x0000 | 0x01,
KeyDelete = 0x0010 | 0x08, KeyReturn = 0x0010 | 0x04, KeyDown = 0x0010 | 0x02, KeyLeft = 0x0010 | 0x01,
KeyColon = 0x0020 | 0x04, KeyUp = 0x0020 | 0x02, KeyMinus = 0x0020 | 0x01,
KeySlash = 0x0030 | 0x08, KeySemiColon = 0x0030 | 0x04, KeyP = 0x0030 | 0x02, Key0 = 0x0030 | 0x01,
KeyFullStop = 0x0040 | 0x08, KeyL = 0x0040 | 0x04, KeyO = 0x0040 | 0x02, Key9 = 0x0040 | 0x01,
KeyComma = 0x0050 | 0x08, KeyK = 0x0050 | 0x04, KeyI = 0x0050 | 0x02, Key8 = 0x0050 | 0x01,
KeyM = 0x0060 | 0x08, KeyJ = 0x0060 | 0x04, KeyU = 0x0060 | 0x02, Key7 = 0x0060 | 0x01,
KeyN = 0x0070 | 0x08, KeyH = 0x0070 | 0x04, KeyY = 0x0070 | 0x02, Key6 = 0x0070 | 0x01,
KeyB = 0x0080 | 0x08, KeyG = 0x0080 | 0x04, KeyT = 0x0080 | 0x02, Key5 = 0x0080 | 0x01,
KeyV = 0x0090 | 0x08, KeyF = 0x0090 | 0x04, KeyR = 0x0090 | 0x02, Key4 = 0x0090 | 0x01,
KeyC = 0x00a0 | 0x08, KeyD = 0x00a0 | 0x04, KeyE = 0x00a0 | 0x02, Key3 = 0x00a0 | 0x01,
KeyX = 0x00b0 | 0x08, KeyS = 0x00b0 | 0x04, KeyW = 0x00b0 | 0x02, Key2 = 0x00b0 | 0x01,
KeyZ = 0x00c0 | 0x08, KeyA = 0x00c0 | 0x04, KeyQ = 0x00c0 | 0x02, Key1 = 0x00c0 | 0x01,
KeyShift = 0x00d0 | 0x08, KeyControl = 0x00d0 | 0x04, KeyFunc = 0x00d0 | 0x02, KeyEscape = 0x00d0 | 0x01,
KeyBreak = 0xfffd,
};
struct KeyboardMapper: public KeyboardMachine::Machine::KeyboardMapper {
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key);
};
struct CharacterMapper: public ::Utility::CharacterMapper {
uint16_t *sequence_for_character(char character);
};
};
#endif /* KeyboardMapper_hpp */

View File

@@ -10,13 +10,13 @@
using namespace Electron;
Plus3::Plus3() : WD1770(P1770), last_control_(0) {
Plus3::Plus3() : WD1770(P1770) {
set_control_register(last_control_, 0xff);
}
void Plus3::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
if(!drives_[drive]) {
drives_[drive].reset(new Storage::Disk::Drive);
drives_[drive].reset(new Storage::Disk::Drive(8000000, 300, 2));
if(drive == selected_drive_) set_drive(drives_[drive]);
}
drives_[drive]->set_disk(disk);
@@ -42,9 +42,14 @@ void Plus3::set_control_register(uint8_t control, uint8_t changes) {
}
}
if(changes & 0x04) {
invalidate_track();
if(drives_[0]) drives_[0]->set_head((control & 0x04) ? 1 : 0);
if(drives_[1]) drives_[1]->set_head((control & 0x04) ? 1 : 0);
}
if(changes & 0x08) set_is_double_density(!(control & 0x08));
}
void Plus3::set_motor_on(bool on) {
// TODO: this status should transfer if the selected drive changes. But the same goes for
// writing state, so plenty of work to do in general here.
get_drive().set_motor_on(on);
}

View File

@@ -23,8 +23,10 @@ class Plus3 : public WD::WD1770 {
private:
void set_control_register(uint8_t control, uint8_t changes);
std::shared_ptr<Storage::Disk::Drive> drives_[2];
int selected_drive_;
uint8_t last_control_;
int selected_drive_ = 0;
uint8_t last_control_ = 0;
void set_motor_on(bool on);
};
}

View File

@@ -13,7 +13,7 @@ using namespace Electron;
void Speaker::get_samples(unsigned int number_of_samples, int16_t *target) {
if(is_enabled_) {
while(number_of_samples--) {
*target = (int16_t)((counter_ / (divider_+1)) * 8192);
*target = static_cast<int16_t>((counter_ / (divider_+1)) * 8192);
target++;
counter_ = (counter_ + 1) % ((divider_+1) * 2);
}

View File

@@ -25,9 +25,9 @@ class Speaker: public ::Outputs::Filter<Speaker> {
static const unsigned int clock_rate_divider = 8;
private:
unsigned int counter_;
unsigned int divider_;
bool is_enabled_;
unsigned int counter_ = 0;
unsigned int divider_ = 0;
bool is_enabled_ = false;
};
}

View File

@@ -10,19 +10,12 @@
using namespace Electron;
Tape::Tape() :
TapePlayer(2000000),
is_running_(false),
data_register_(0),
delegate_(nullptr),
output_({.bits_remaining_until_empty = 0, .cycles_into_pulse = 0}),
last_posted_interrupt_status_(0),
interrupt_status_(0) {
Tape::Tape() : TapePlayer(2000000) {
shifter_.set_delegate(this);
}
void Tape::push_tape_bit(uint16_t bit) {
data_register_ = (uint16_t)((data_register_ >> 1) | (bit << 10));
data_register_ = static_cast<uint16_t>((data_register_ >> 1) | (bit << 10));
if(input_.minimum_bits_until_full) input_.minimum_bits_until_full--;
if(input_.minimum_bits_until_full == 8) interrupt_status_ &= ~Interrupt::ReceiveDataFull;
@@ -64,12 +57,12 @@ void Tape::set_counter(uint8_t value) {
}
void Tape::set_data_register(uint8_t value) {
data_register_ = (uint16_t)((value << 2) | 1);
data_register_ = static_cast<uint16_t>((value << 2) | 1);
output_.bits_remaining_until_empty = 9;
}
uint8_t Tape::get_data_register() {
return (uint8_t)(data_register_ >> 2);
return static_cast<uint8_t>(data_register_ >> 2);
}
void Tape::process_input_pulse(const Storage::Tape::Tape::Pulse &pulse) {
@@ -77,7 +70,7 @@ void Tape::process_input_pulse(const Storage::Tape::Tape::Pulse &pulse) {
}
void Tape::acorn_shifter_output_bit(int value) {
push_tape_bit((uint16_t)value);
push_tape_bit(static_cast<uint16_t>(value));
}
void Tape::run_for(const Cycles cycles) {
@@ -87,7 +80,7 @@ void Tape::run_for(const Cycles cycles) {
TapePlayer::run_for(cycles);
}
} else {
output_.cycles_into_pulse += (unsigned int)cycles.as_int();
output_.cycles_into_pulse += static_cast<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

@@ -53,21 +53,22 @@ class Tape:
struct {
int minimum_bits_until_full;
} input_;
} input_ = {0};
struct {
unsigned int cycles_into_pulse;
unsigned int bits_remaining_until_empty;
} output_;
} output_ = {.bits_remaining_until_empty = 0, .cycles_into_pulse = 0};
bool is_running_;
bool is_enabled_;
bool is_in_input_mode_;
bool is_running_ = false;
bool is_enabled_ = false;
bool is_in_input_mode_ = false;
inline void evaluate_interrupts();
uint16_t data_register_;
uint16_t data_register_ = 0;
uint8_t interrupt_status_, last_posted_interrupt_status_;
Delegate *delegate_;
uint8_t interrupt_status_ = 0;
uint8_t last_posted_interrupt_status_ = 0;
Delegate *delegate_ = nullptr;
::Storage::Tape::Acorn::Shifter shifter_;
};

View File

@@ -32,17 +32,18 @@ namespace {
static const int real_time_clock_interrupt_2 = 56704;
static const int display_end_interrupt_1 = (first_graphics_line + display_end_interrupt_line)*cycles_per_line;
static const int display_end_interrupt_2 = (first_graphics_line + field_divider_line + display_end_interrupt_line)*cycles_per_line;
struct FourBPPBookender: public Outputs::CRT::TextureBuilder::Bookender {
void add_bookends(uint8_t *const left_value, uint8_t *const right_value, uint8_t *left_bookend, uint8_t *right_bookend) {
*left_bookend = static_cast<uint8_t>(((*left_value) & 0x0f) | (((*left_value) & 0x0f) << 4));
*right_bookend = static_cast<uint8_t>(((*right_value) & 0xf0) | (((*right_value) & 0xf0) >> 4));
}
};
}
#pragma mark - Lifecycle
VideoOutput::VideoOutput(uint8_t *memory) :
ram_(memory),
current_pixel_line_(-1),
output_position_(0),
screen_mode_(6),
screen_map_pointer_(0),
cycles_into_draw_action_(0) {
VideoOutput::VideoOutput(uint8_t *memory) : ram_(memory) {
memset(palette_, 0xf, sizeof(palette_));
setup_screen_map();
setup_base_address();
@@ -55,6 +56,8 @@ VideoOutput::VideoOutput(uint8_t *memory) :
"texValue >>= 4 - (int(icoordinate.x * 8) & 4);"
"return vec3( uvec3(texValue) & uvec3(4u, 2u, 1u));"
"}");
std::unique_ptr<Outputs::CRT::TextureBuilder::Bookender> bookender(new FourBPPBookender);
crt_->set_bookender(std::move(bookender));
// TODO: as implied below, I've introduced a clock's latency into the graphics pipeline somehow. Investigate.
crt_->set_visible_area(crt_->get_rect_for_area(first_graphics_line - 3, 256, (first_graphics_cycle+1) * crt_cycles_multiplier, 80 * crt_cycles_multiplier, 4.0f / 3.0f));
}
@@ -92,7 +95,7 @@ void VideoOutput::start_pixel_line() {
}
void VideoOutput::end_pixel_line() {
if(current_output_target_) crt_->output_data((unsigned int)((current_output_target_ - initial_output_target_) * current_output_divider_), current_output_divider_);
if(current_output_target_) crt_->output_data(static_cast<unsigned int>((current_output_target_ - initial_output_target_) * current_output_divider_), current_output_divider_);
current_character_row_++;
}
@@ -110,9 +113,9 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) {
}
if(!initial_output_target_ || divider != current_output_divider_) {
if(current_output_target_) crt_->output_data((unsigned int)((current_output_target_ - initial_output_target_) * current_output_divider_), current_output_divider_);
if(current_output_target_) crt_->output_data(static_cast<unsigned int>((current_output_target_ - initial_output_target_) * current_output_divider_), current_output_divider_);
current_output_divider_ = divider;
initial_output_target_ = current_output_target_ = crt_->allocate_write_area(640 / current_output_divider_);
initial_output_target_ = current_output_target_ = crt_->allocate_write_area(640 / current_output_divider_, 4);
}
#define get_pixel() \
@@ -127,7 +130,7 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) {
if(initial_output_target_) {
while(number_of_cycles--) {
get_pixel();
*(uint32_t *)current_output_target_ = palette_tables_.eighty1bpp[last_pixel_byte_];
*reinterpret_cast<uint32_t *>(current_output_target_) = palette_tables_.eighty1bpp[last_pixel_byte_];
current_output_target_ += 4;
current_pixel_column_++;
}
@@ -138,7 +141,7 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) {
if(initial_output_target_) {
while(number_of_cycles--) {
get_pixel();
*(uint16_t *)current_output_target_ = palette_tables_.eighty2bpp[last_pixel_byte_];
*reinterpret_cast<uint16_t *>(current_output_target_) = palette_tables_.eighty2bpp[last_pixel_byte_];
current_output_target_ += 2;
current_pixel_column_++;
}
@@ -160,7 +163,7 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) {
if(initial_output_target_) {
if(current_pixel_column_&1) {
last_pixel_byte_ <<= 4;
*(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_];
*reinterpret_cast<uint16_t *>(current_output_target_) = palette_tables_.forty1bpp[last_pixel_byte_];
current_output_target_ += 2;
number_of_cycles--;
@@ -168,11 +171,11 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) {
}
while(number_of_cycles > 1) {
get_pixel();
*(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_];
*reinterpret_cast<uint16_t *>(current_output_target_) = palette_tables_.forty1bpp[last_pixel_byte_];
current_output_target_ += 2;
last_pixel_byte_ <<= 4;
*(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_];
*reinterpret_cast<uint16_t *>(current_output_target_) = palette_tables_.forty1bpp[last_pixel_byte_];
current_output_target_ += 2;
number_of_cycles -= 2;
@@ -180,7 +183,7 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) {
}
if(number_of_cycles) {
get_pixel();
*(uint16_t *)current_output_target_ = palette_tables_.forty1bpp[last_pixel_byte_];
*reinterpret_cast<uint16_t *>(current_output_target_) = palette_tables_.forty1bpp[last_pixel_byte_];
current_output_target_ += 2;
current_pixel_column_++;
}
@@ -229,16 +232,16 @@ void VideoOutput::run_for(const Cycles cycles) {
while(number_of_cycles) {
int draw_action_length = screen_map_[screen_map_pointer_].length;
int time_left_in_action = std::min(number_of_cycles, draw_action_length - cycles_into_draw_action_);
if(screen_map_[screen_map_pointer_].type == DrawAction::Pixels) output_pixels((unsigned int)time_left_in_action);
if(screen_map_[screen_map_pointer_].type == DrawAction::Pixels) output_pixels(static_cast<unsigned int>(time_left_in_action));
number_of_cycles -= time_left_in_action;
cycles_into_draw_action_ += time_left_in_action;
if(cycles_into_draw_action_ == draw_action_length) {
switch(screen_map_[screen_map_pointer_].type) {
case DrawAction::Sync: crt_->output_sync((unsigned int)(draw_action_length * crt_cycles_multiplier)); break;
case DrawAction::ColourBurst: crt_->output_default_colour_burst((unsigned int)(draw_action_length * crt_cycles_multiplier)); break;
case DrawAction::Blank: crt_->output_blank((unsigned int)(draw_action_length * crt_cycles_multiplier)); break;
case DrawAction::Pixels: end_pixel_line(); break;
case DrawAction::Sync: crt_->output_sync(static_cast<unsigned int>(draw_action_length * crt_cycles_multiplier)); break;
case DrawAction::ColourBurst: crt_->output_default_colour_burst(static_cast<unsigned int>(draw_action_length * crt_cycles_multiplier)); break;
case DrawAction::Blank: crt_->output_blank(static_cast<unsigned int>(draw_action_length * crt_cycles_multiplier)); break;
case DrawAction::Pixels: end_pixel_line(); break;
}
screen_map_pointer_ = (screen_map_pointer_ + 1) % screen_map_.size();
cycles_into_draw_action_ = 0;
@@ -252,11 +255,11 @@ void VideoOutput::run_for(const Cycles cycles) {
void VideoOutput::set_register(int address, uint8_t value) {
switch(address & 0xf) {
case 0x02:
start_screen_address_ = (start_screen_address_ & 0xfe00) | (uint16_t)((value & 0xe0) << 1);
start_screen_address_ = (start_screen_address_ & 0xfe00) | static_cast<uint16_t>((value & 0xe0) << 1);
if(!start_screen_address_) start_screen_address_ |= 0x8000;
break;
case 0x03:
start_screen_address_ = (start_screen_address_ & 0x01ff) | (uint16_t)((value & 0x3f) << 9);
start_screen_address_ = (start_screen_address_ & 0x01ff) | static_cast<uint16_t>((value & 0x3f) << 9);
if(!start_screen_address_) start_screen_address_ |= 0x8000;
break;
case 0x07: {
@@ -298,17 +301,17 @@ void VideoOutput::set_register(int address, uint8_t value) {
}
// regenerate all palette tables for now
#define pack(a, b) (uint8_t)((a << 4) | (b))
#define pack(a, b) static_cast<uint8_t>((a << 4) | (b))
for(int byte = 0; byte < 256; byte++) {
uint8_t *target = (uint8_t *)&palette_tables_.forty1bpp[byte];
uint8_t *target = reinterpret_cast<uint8_t *>(&palette_tables_.forty1bpp[byte]);
target[0] = pack(palette_[(byte&0x80) >> 4], palette_[(byte&0x40) >> 3]);
target[1] = pack(palette_[(byte&0x20) >> 2], palette_[(byte&0x10) >> 1]);
target = (uint8_t *)&palette_tables_.eighty2bpp[byte];
target = reinterpret_cast<uint8_t *>(&palette_tables_.eighty2bpp[byte]);
target[0] = pack(palette_[((byte&0x80) >> 4) | ((byte&0x08) >> 2)], palette_[((byte&0x40) >> 3) | ((byte&0x04) >> 1)]);
target[1] = pack(palette_[((byte&0x20) >> 2) | ((byte&0x02) >> 0)], palette_[((byte&0x10) >> 1) | ((byte&0x01) << 1)]);
target = (uint8_t *)&palette_tables_.eighty1bpp[byte];
target = reinterpret_cast<uint8_t *>(&palette_tables_.eighty1bpp[byte]);
target[0] = pack(palette_[(byte&0x80) >> 4], palette_[(byte&0x40) >> 3]);
target[1] = pack(palette_[(byte&0x20) >> 2], palette_[(byte&0x10) >> 1]);
target[2] = pack(palette_[(byte&0x08) >> 0], palette_[(byte&0x04) << 1]);
@@ -382,9 +385,9 @@ unsigned int VideoOutput::get_cycles_until_next_ram_availability(int from_time)
int output_position_line = graphics_line(output_position_);
int implied_row = current_character_row_ + (current_line - output_position_line) % 10;
if(implied_row < 8)
result += (unsigned int)(80 - current_column);
result += static_cast<unsigned int>(80 - current_column);
}
else result += (unsigned int)(80 - current_column);
else result += static_cast<unsigned int>(80 - current_column);
}
}
return result;

View File

@@ -80,12 +80,13 @@ class VideoOutput {
inline void output_pixels(unsigned int number_of_cycles);
inline void setup_base_address();
int output_position_, unused_cycles_;
int output_position_ = 0;
int unused_cycles_ = 0;
uint8_t palette_[16];
uint8_t screen_mode_;
uint16_t screen_mode_base_address_;
uint16_t start_screen_address_;
uint8_t screen_mode_ = 6;
uint16_t screen_mode_base_address_ = 0;
uint16_t start_screen_address_ = 0;
uint8_t *ram_;
struct {
@@ -97,14 +98,18 @@ class VideoOutput {
} palette_tables_;
// Display generation.
uint16_t start_line_address_, current_screen_address_;
int current_pixel_line_, current_pixel_column_, current_character_row_;
uint8_t last_pixel_byte_;
bool is_blank_line_;
uint16_t start_line_address_ = 0;
uint16_t current_screen_address_ = 0;
int current_pixel_line_ = -1;
int current_pixel_column_ = 0;
int current_character_row_ = 0;
uint8_t last_pixel_byte_ = 0;
bool is_blank_line_ = false;
// CRT output
uint8_t *current_output_target_, *initial_output_target_;
unsigned int current_output_divider_;
uint8_t *current_output_target_ = nullptr;
uint8_t *initial_output_target_ = nullptr;
unsigned int current_output_divider_ = 1;
std::shared_ptr<Outputs::CRT::CRT> crt_;
@@ -119,8 +124,8 @@ class VideoOutput {
void setup_screen_map();
void emplace_blank_line();
void emplace_pixel_line();
size_t screen_map_pointer_;
int cycles_into_draw_action_;
size_t screen_map_pointer_ = 0;
int cycles_into_draw_action_ = 0;
};
}

View File

@@ -0,0 +1,24 @@
//
// JoystickMachine.hpp
// Clock Signal
//
// Created by Thomas Harte on 14/10/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef JoystickMachine_hpp
#define JoystickMachine_hpp
#include "../Inputs/Joystick.hpp"
#include <vector>
namespace JoystickMachine {
class Machine {
public:
virtual std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() = 0;
};
}
#endif /* JoystickMachine_hpp */

View File

@@ -0,0 +1,29 @@
//
// KeyboardMachine.cpp
// Clock Signal
//
// Created by Thomas Harte on 10/10/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "KeyboardMachine.hpp"
using namespace KeyboardMachine;
Machine::Machine() {
keyboard_.set_delegate(this);
}
void Machine::keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) {
uint16_t mapped_key = get_keyboard_mapper().mapped_key_for_key(key);
if(mapped_key != KeyNotMapped) set_key_state(mapped_key, is_pressed);
}
void Machine::reset_all_keys(Inputs::Keyboard *keyboard) {
// TODO: unify naming.
clear_all_keys();
}
Inputs::Keyboard &Machine::get_keyboard() {
return keyboard_;
}

View File

@@ -9,20 +9,59 @@
#ifndef KeyboardMachine_h
#define KeyboardMachine_h
#include "../Inputs/Keyboard.hpp"
namespace KeyboardMachine {
class Machine {
class Machine: public Inputs::Keyboard::Delegate {
public:
Machine();
/*!
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;
virtual void set_key_state(uint16_t key, bool is_pressed) = 0;
/*!
Instructs that all keys should now be treated as released.
*/
virtual void clear_all_keys() = 0;
/*!
Provides a destination for keyboard input.
*/
virtual Inputs::Keyboard &get_keyboard();
/*!
A keyboard mapper attempts to provide a physical mapping between host keys and emulated keys.
See the character mapper for logical mapping.
*/
class KeyboardMapper {
public:
virtual uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) = 0;
};
/// Terminates a key sequence from the character mapper.
static const uint16_t KeyEndSequence = 0xffff;
/*!
Indicates that a key is not mapped (for the keyboard mapper) or that a
character cannot be typed (for the character mapper).
*/
static const uint16_t KeyNotMapped = 0xfffe;
protected:
/*!
Allows individual machines to provide the mapping between host keys
as per Inputs::Keyboard and their native scheme.
*/
virtual KeyboardMapper &get_keyboard_mapper() = 0;
private:
void keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) override;
void reset_all_keys(Inputs::Keyboard *keyboard) override;
Inputs::Keyboard keyboard_;
};
}

View File

@@ -1,9 +1,60 @@
#include "Oric.hpp"
//
// Keyboard.cpp
// Clock Signal
//
// Created by Thomas Harte on 10/10/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
uint16_t *Oric::Machine::sequence_for_character(Utility::Typer *typer, char character) {
#define KEYS(...) {__VA_ARGS__, EndSequence}
#define SHIFT(...) {KeyLeftShift, __VA_ARGS__, EndSequence}
#define X {NotMapped}
#include "Keyboard.hpp"
using namespace Oric;
uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
#define BIND(source, dest) case Inputs::Keyboard::Key::source: return Oric::dest
switch(key) {
default: break;
BIND(k0, Key0); BIND(k1, Key1); BIND(k2, Key2); BIND(k3, Key3); BIND(k4, Key4);
BIND(k5, Key5); BIND(k6, Key6); BIND(k7, Key7); BIND(k8, Key8); BIND(k9, Key9);
BIND(Q, KeyQ); BIND(W, KeyW); BIND(E, KeyE); BIND(R, KeyR); BIND(T, KeyT);
BIND(Y, KeyY); BIND(U, KeyU); BIND(I, KeyI); BIND(O, KeyO); BIND(P, KeyP);
BIND(A, KeyA); BIND(S, KeyS); BIND(D, KeyD); BIND(F, KeyF); BIND(G, KeyG);
BIND(H, KeyH); BIND(J, KeyJ); BIND(K, KeyK); BIND(L, KeyL);
BIND(Z, KeyZ); BIND(X, KeyX); BIND(C, KeyC); BIND(V, KeyV);
BIND(B, KeyB); BIND(N, KeyN); BIND(M, KeyM);
BIND(Left, KeyLeft); BIND(Right, KeyRight); BIND(Up, KeyUp); BIND(Down, KeyDown);
BIND(Hyphen, KeyMinus); BIND(Equals, KeyEquals); BIND(BackSlash, KeyBackSlash);
BIND(OpenSquareBracket, KeyOpenSquare); BIND(CloseSquareBracket, KeyCloseSquare);
BIND(BackSpace, KeyDelete); BIND(Delete, KeyDelete);
BIND(Semicolon, KeySemiColon); BIND(Quote, KeyQuote);
BIND(Comma, KeyComma); BIND(FullStop, KeyFullStop); BIND(ForwardSlash, KeyForwardSlash);
BIND(Escape, KeyEscape); BIND(Tab, KeyEscape);
BIND(CapsLock, KeyControl); BIND(LeftControl, KeyControl); BIND(RightControl, KeyControl);
BIND(LeftOption, KeyFunction);
BIND(RightOption, KeyFunction);
BIND(LeftMeta, KeyFunction);
BIND(RightMeta, KeyFunction);
BIND(LeftShift, KeyLeftShift);
BIND(RightShift, KeyRightShift);
BIND(Space, KeySpace);
BIND(Enter, KeyReturn);
}
#undef BIND
return KeyboardMachine::Machine::KeyNotMapped;
}
uint16_t *CharacterMapper::sequence_for_character(char character) {
#define KEYS(...) {__VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
#define SHIFT(...) {KeyLeftShift, __VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
#define X {KeyboardMachine::Machine::KeyNotMapped}
static KeySequence key_sequences[] = {
/* NUL */ X, /* SOH */ X,
/* STX */ X, /* ETX */ X,

View File

@@ -0,0 +1,48 @@
//
// Keyboard.hpp
// Clock Signal
//
// Created by Thomas Harte on 10/10/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef Machines_Oric_Keyboard_hpp
#define Machines_Oric_Keyboard_hpp
#include "../KeyboardMachine.hpp"
#include "../Utility/Typer.hpp"
namespace Oric {
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,
KeyD = 0x0100 | 0x80, KeyQ = 0x0100 | 0x40, KeyEscape = 0x0100 | 0x20,
KeyF = 0x0100 | 0x08, KeyR = 0x0100 | 0x04, KeyT = 0x0100 | 0x02, KeyJ = 0x0100 | 0x01,
KeyC = 0x0200 | 0x80, Key2 = 0x0200 | 0x40, KeyZ = 0x0200 | 0x20, KeyControl = 0x0200 | 0x10,
Key4 = 0x0200 | 0x08, KeyB = 0x0200 | 0x04, Key6 = 0x0200 | 0x02, KeyM = 0x0200 | 0x01,
KeyQuote = 0x0300 | 0x80, KeyBackSlash = 0x0300 | 0x40,
KeyMinus = 0x0300 | 0x08, KeySemiColon = 0x0300 | 0x04, Key9 = 0x0300 | 0x02, KeyK = 0x0300 | 0x01,
KeyRight = 0x0400 | 0x80, KeyDown = 0x0400 | 0x40, KeyLeft = 0x0400 | 0x20, KeyLeftShift = 0x0400 | 0x10,
KeyUp = 0x0400 | 0x08, KeyFullStop = 0x0400 | 0x04, KeyComma = 0x0400 | 0x02, KeySpace = 0x0400 | 0x01,
KeyOpenSquare = 0x0500 | 0x80, KeyCloseSquare = 0x0500 | 0x40, KeyDelete = 0x0500 | 0x20, KeyFunction = 0x0500 | 0x10,
KeyP = 0x0500 | 0x08, KeyO = 0x0500 | 0x04, KeyI = 0x0500 | 0x02, KeyU = 0x0500 | 0x01,
KeyW = 0x0600 | 0x80, KeyS = 0x0600 | 0x40, KeyA = 0x0600 | 0x20,
KeyE = 0x0600 | 0x08, KeyG = 0x0600 | 0x04, KeyH = 0x0600 | 0x02, KeyY = 0x0600 | 0x01,
KeyEquals = 0x0700 | 0x80, KeyReturn = 0x0700 | 0x20, KeyRightShift = 0x0700 | 0x10,
KeyForwardSlash = 0x0700 | 0x08, Key0 = 0x0700 | 0x04, KeyL = 0x0700 | 0x02, Key8 = 0x0700 | 0x01,
KeyNMI = 0xfffd,
};
struct KeyboardMapper: public KeyboardMachine::Machine::KeyboardMapper {
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key);
};
struct CharacterMapper: public ::Utility::CharacterMapper {
uint16_t *sequence_for_character(char character);
};
};
#endif /* KeyboardMapper_hpp */

View File

@@ -30,7 +30,7 @@ Microdisc::Microdisc() :
void Microdisc::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
if(!drives_[drive]) {
drives_[drive].reset(new Storage::Disk::Drive);
drives_[drive].reset(new Storage::Disk::Drive(8000000, 300, 2));
if(drive == selected_drive_) set_drive(drives_[drive]);
}
drives_[drive]->set_disk(disk);
@@ -53,7 +53,7 @@ void Microdisc::set_control_register(uint8_t control, uint8_t changes) {
// b4: side select
if(changes & 0x10) {
unsigned int head = (control & 0x10) ? 1 : 0;
int head = (control & 0x10) ? 1 : 0;
for(int c = 0; c < 4; c++) {
if(drives_[c]) drives_[c]->set_head(head);
}
@@ -95,7 +95,14 @@ uint8_t Microdisc::get_data_request_register() {
}
void Microdisc::set_head_load_request(bool head_load) {
set_motor_on(head_load);
// The drive motors (at present: I believe **all drive motors** regardless of the selected drive) receive
// the current head load request state.
for(int c = 0; c < 4; c++) {
if(drives_[c]) drives_[c]->set_motor_on(head_load);
}
// A request to load the head results in a delay until the head is confirmed loaded. This delay is handled
// in ::run_for. A request to unload the head results in an instant answer that the head is unloaded.
if(head_load) {
head_load_request_counter_ = 0;
} else {

View File

@@ -7,278 +7,458 @@
//
#include "Oric.hpp"
#include "../MemoryFuzzer.hpp"
#include "Keyboard.hpp"
#include "Microdisc.hpp"
#include "Video.hpp"
#include "../Utility/MemoryFuzzer.hpp"
#include "../Utility/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 <memory>
namespace Oric {
/*!
Models the Oric's keyboard: eight key rows, containing a bitfield of keys set.
Active line is selected through a port on the Oric's VIA, and a column mask is
selected via a port on the AY, returning a single Boolean representation of the
logical OR of every key selected by the column mask on the active row.
*/
class Keyboard {
public:
Keyboard() {
clear_all_keys();
}
/// Sets whether @c key is or is not pressed, per @c is_pressed.
void set_key_state(uint16_t key, bool is_pressed) {
uint8_t mask = key & 0xff;
int line = key >> 8;
if(is_pressed) rows_[line] |= mask;
else rows_[line] &= ~mask;
}
/// Sets all keys as unpressed.
void clear_all_keys() {
memset(rows_, 0, sizeof(rows_));
}
/// Selects the active row.
void set_active_row(uint8_t row) {
row_ = row & 7;
}
/// Queries the keys on the active row specified by @c mask.
bool query_column(uint8_t column_mask) {
return !!(rows_[row_] & column_mask);
}
private:
uint8_t row_ = 0;
uint8_t rows_[8];
};
/*!
Provide's the Oric's tape player: a standard binary-sampled tape which also holds
an instance of the Oric tape parser, to provide fast-tape loading.
*/
class TapePlayer: public Storage::Tape::BinaryTapePlayer {
public:
TapePlayer() : Storage::Tape::BinaryTapePlayer(1000000) {}
/*!
Parses the incoming tape event stream to obtain the next stored byte.
@param use_fast_encoding If set to @c true , inspects the tape as though it
is encoded in the Oric's fast-loading scheme. Otherwise looks for a slow-encoded byte.
@returns The next byte from the tape.
*/
uint8_t get_next_byte(bool use_fast_encoding) {
return static_cast<uint8_t>(parser_.get_next_byte(get_tape(), use_fast_encoding));
}
private:
Storage::Tape::Oric::Parser parser_;
};
/*!
Implements the Oric's VIA's port handler. On the Oric the VIA's ports connect
to the AY, the tape's motor control signal and the keyboard.
*/
class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
public:
VIAPortHandler(TapePlayer &tape_player, Keyboard &keyboard) : tape_player_(tape_player), keyboard_(keyboard) {}
/*!
Reponds to the 6522's control line output change signal; on an Oric A2 is connected to
the AY's BDIR, and B2 is connected to the AY's A2.
*/
void set_control_line_output(MOS::MOS6522::Port port, MOS::MOS6522::Line line, bool value) {
if(line) {
if(port) ay_bdir_ = value; else ay_bc1_ = value;
update_ay();
ay8910_->set_control_lines( (GI::AY38910::ControlLines)((ay_bdir_ ? GI::AY38910::BDIR : 0) | (ay_bc1_ ? GI::AY38910::BC1 : 0) | GI::AY38910::BC2));
}
}
/*!
Reponds to changes in the 6522's port output. On an Oric port B sets the tape motor control
and the keyboard's active row. Port A is connected to the AY's data bus.
*/
void set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t direction_mask) {
if(port) {
keyboard_.set_active_row(value);
tape_player_.set_motor_control(value & 0x40);
} else {
update_ay();
ay8910_->set_data_input(value);
}
}
/*!
Provides input data for the 6522. Port B reads the keyboard, and port A reads from the AY.
*/
uint8_t get_port_input(MOS::MOS6522::Port port) {
if(port) {
uint8_t column = ay8910_->get_port_output(false) ^ 0xff;
return keyboard_.query_column(column) ? 0x08 : 0x00;
} else {
return ay8910_->get_data_output();
}
}
/*!
Advances time. This class manages the AY's concept of time to permit updating-on-demand.
*/
inline void run_for(const Cycles cycles) {
cycles_since_ay_update_ += cycles;
}
/// Flushes any queued behaviour (which, specifically, means on the AY).
void flush() {
ay8910_->run_for(cycles_since_ay_update_.flush());
ay8910_->flush();
}
/// Sets the AY in use by the machine the VIA that uses this port handler sits within.
void set_ay(GI::AY38910::AY38910 *ay) {
ay8910_ = ay;
}
private:
void update_ay() {
ay8910_->run_for(cycles_since_ay_update_.flush());
}
bool ay_bdir_ = false;
bool ay_bc1_ = false;
Cycles cycles_since_ay_update_;
GI::AY38910::AY38910 *ay8910_ = nullptr;
TapePlayer &tape_player_;
Keyboard &keyboard_;
};
class ConcreteMachine:
public CPU::MOS6502::BusHandler,
public MOS::MOS6522::IRQDelegatePortHandler::Delegate,
public Utility::TypeRecipient,
public Storage::Tape::BinaryTapePlayer::Delegate,
public Microdisc::Delegate,
public Machine {
public:
ConcreteMachine() :
m6502_(*this),
paged_rom_(rom_),
via_(via_port_handler_),
via_port_handler_(tape_player_, keyboard_) {
set_clock_rate(1000000);
via_port_handler_.set_interrupt_delegate(this);
tape_player_.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 is_pressed) override final {
if(key == KeyNMI) {
m6502_.set_nmi_line(is_pressed);
} else {
keyboard_.set_key_state(key, is_pressed);
}
}
void clear_all_keys() override final {
keyboard_.clear_all_keys();
}
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()) {
tape_player_.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 &&
tape_player_.has_tape() &&
!tape_player_.get_tape()->is_at_end()) {
uint8_t next_byte = tape_player_.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();
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));
via_port_handler_.run_for(Cycles(1));
tape_player_.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_port_handler_.flush();
}
// to satisfy CRTMachine::Machine
void setup_output(float aspect_ratio) override final {
ay8910_.reset(new GI::AY38910::AY38910());
ay8910_->set_clock_rate(1000000);
via_port_handler_.set_ay(ay8910_.get());
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();
ay8910_.reset();
via_port_handler_.set_ay(nullptr);
}
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 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(MOS::MOS6522::Port::B, MOS::MOS6522::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();
}
KeyboardMapper &get_keyboard_mapper() override {
return keyboard_mapper_;
}
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_ = false;
uint16_t tape_get_byte_address_ = 0, scan_keyboard_address_ = 0, tape_speed_address_ = 0;
int keyboard_read_count_ = 0;
// Outputs
std::unique_ptr<VideoOutput> video_output_;
std::shared_ptr<GI::AY38910::AY38910> ay8910_;
// Inputs
Oric::KeyboardMapper keyboard_mapper_;
// The tape
TapePlayer tape_player_;
bool use_fast_tape_hack_ = false;
VIAPortHandler via_port_handler_;
MOS::MOS6522::MOS6522<VIAPortHandler> via_;
Keyboard keyboard_;
// the Microdisc, if in use
class Microdisc microdisc_;
bool microdisc_is_enabled_ = false;
uint16_t ram_top_ = 0xbfff;
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() :
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;
}
}
Cycles Machine::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_]);
set_value_of_register(CPU::MOS6502::A, next_byte);
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);
}
void Machine::flush() {
update_video();
via_.flush();
}
void Machine::update_video() {
video_output_->run_for(cycles_since_video_update_.flush());
}
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(const Cycles cycles) {
CPU::MOS6502::Processor<Machine>::run_for(cycles);
}
#pragma mark - The 6522
Machine::VIA::VIA() :
MOS::MOS6522<Machine::VIA>(),
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::flush() {
ay8910->run_for(cycles_since_ay_update_.flush());
ay8910->flush();
}
void Machine::VIA::run_for(const Cycles cycles) {
cycles_since_ay_update_ += cycles;
MOS::MOS6522<VIA>::run_for(cycles);
tape->run_for(cycles);
}
void Machine::VIA::update_ay() {
ay8910->run_for(cycles_since_ay_update_.flush());
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,161 +11,38 @@
#include "../ConfigurationTarget.hpp"
#include "../CRTMachine.hpp"
#include "../Typer.hpp"
#include "../../Processors/6502/6502.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 Key: uint16_t {
Key3 = 0x0000 | 0x80, KeyX = 0x0000 | 0x40, Key1 = 0x0000 | 0x20,
KeyV = 0x0000 | 0x08, Key5 = 0x0000 | 0x04, KeyN = 0x0000 | 0x02, Key7 = 0x0000 | 0x01,
KeyD = 0x0100 | 0x80, KeyQ = 0x0100 | 0x40, KeyEscape = 0x0100 | 0x20,
KeyF = 0x0100 | 0x08, KeyR = 0x0100 | 0x04, KeyT = 0x0100 | 0x02, KeyJ = 0x0100 | 0x01,
KeyC = 0x0200 | 0x80, Key2 = 0x0200 | 0x40, KeyZ = 0x0200 | 0x20, KeyControl = 0x0200 | 0x10,
Key4 = 0x0200 | 0x08, KeyB = 0x0200 | 0x04, Key6 = 0x0200 | 0x02, KeyM = 0x0200 | 0x01,
KeyQuote = 0x0300 | 0x80, KeyBackSlash = 0x0300 | 0x40,
KeyMinus = 0x0300 | 0x08, KeySemiColon = 0x0300 | 0x04, Key9 = 0x0300 | 0x02, KeyK = 0x0300 | 0x01,
KeyRight = 0x0400 | 0x80, KeyDown = 0x0400 | 0x40, KeyLeft = 0x0400 | 0x20, KeyLeftShift = 0x0400 | 0x10,
KeyUp = 0x0400 | 0x08, KeyFullStop = 0x0400 | 0x04, KeyComma = 0x0400 | 0x02, KeySpace = 0x0400 | 0x01,
KeyOpenSquare = 0x0500 | 0x80, KeyCloseSquare = 0x0500 | 0x40, KeyDelete = 0x0500 | 0x20, KeyFunction = 0x0500 | 0x10,
KeyP = 0x0500 | 0x08, KeyO = 0x0500 | 0x04, KeyI = 0x0500 | 0x02, KeyU = 0x0500 | 0x01,
KeyW = 0x0600 | 0x80, KeyS = 0x0600 | 0x40, KeyA = 0x0600 | 0x20,
KeyE = 0x0600 | 0x08, KeyG = 0x0600 | 0x04, KeyH = 0x0600 | 0x02, KeyY = 0x0600 | 0x01,
KeyEquals = 0x0700 | 0x80, KeyReturn = 0x0700 | 0x20, KeyRightShift = 0x0700 | 0x10,
KeyForwardSlash = 0x0700 | 0x08, Key0 = 0x0700 | 0x04, KeyL = 0x0700 | 0x02, Key8 = 0x0700 | 0x01,
KeyNMI = 0xfffd,
};
enum ROM {
BASIC10, BASIC11, Microdisc, Colour
};
/*!
Models an Oric 1/Atmos with or without a Microdisc.
*/
class Machine:
public CPU::MOS6502::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 CPU::MOS6502::Processor
Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value);
void flush();
// 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(const Cycles 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];
Cycles 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(const Cycles cycles);
std::shared_ptr<GI::AY38910> ay8910;
std::unique_ptr<TapePlayer> tape;
std::shared_ptr<Keyboard> keyboard;
void flush();
private:
void update_ay();
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();
/// Sets the type of display the Oric is connected to.
virtual void set_output_device(Outputs::CRT::OutputDevice output_device) = 0;
};
}

View File

@@ -45,7 +45,7 @@ VideoOutput::VideoOutput(uint8_t *memory) :
crt_->set_composite_function_type(Outputs::CRT::CRT::CompositeSourceType::DiscreteFourSamplesPerCycle, 0.0f);
set_output_device(Outputs::CRT::Television);
crt_->set_visible_area(crt_->get_rect_for_area(50, 224, 16 * 6, 40 * 6, 4.0f / 3.0f));
crt_->set_visible_area(crt_->get_rect_for_area(53, 224, 16 * 6, 40 * 6, 4.0f / 3.0f));
}
void VideoOutput::set_output_device(Outputs::CRT::OutputDevice output_device) {
@@ -56,16 +56,16 @@ void VideoOutput::set_output_device(Outputs::CRT::OutputDevice output_device) {
void VideoOutput::set_colour_rom(const std::vector<uint8_t> &rom) {
for(size_t c = 0; c < 8; c++) {
size_t index = (c << 2);
uint16_t rom_value = (uint16_t)(((uint16_t)rom[index] << 8) | (uint16_t)rom[index+1]);
uint16_t rom_value = static_cast<uint16_t>((static_cast<uint16_t>(rom[index]) << 8) | static_cast<uint16_t>(rom[index+1]));
rom_value = (rom_value & 0xff00) | ((rom_value >> 4)&0x000f) | ((rom_value << 4)&0x00f0);
colour_forms_[c] = rom_value;
}
// check for big endianness and byte swap if required
uint16_t test_value = 0x0001;
if(*(uint8_t *)&test_value != 0x01) {
if(*reinterpret_cast<uint8_t *>(&test_value) != 0x01) {
for(size_t c = 0; c < 8; c++) {
colour_forms_[c] = (uint16_t)((colour_forms_[c] >> 8) | (colour_forms_[c] << 8));
colour_forms_[c] = static_cast<uint16_t>((colour_forms_[c] >> 8) | (colour_forms_[c] << 8));
}
}
}
@@ -89,7 +89,7 @@ void VideoOutput::run_for(const Cycles cycles) {
if(counter_ >= v_sync_start_position_ && counter_ < v_sync_end_position_) {
// this is a sync line
cycles_run_for = v_sync_end_position_ - counter_;
clamp(crt_->output_sync((unsigned int)(v_sync_end_position_ - v_sync_start_position_) * 6));
clamp(crt_->output_sync(static_cast<unsigned int>(v_sync_end_position_ - v_sync_start_position_) * 6));
} else if(counter_ < 224*64 && h_counter < 40) {
// this is a pixel line
if(!h_counter) {
@@ -97,7 +97,7 @@ void VideoOutput::run_for(const Cycles cycles) {
paper_ = 0x0;
use_alternative_character_set_ = use_double_height_characters_ = blink_text_ = false;
set_character_set_base_address();
pixel_target_ = (uint16_t *)crt_->allocate_write_area(240);
pixel_target_ = reinterpret_cast<uint16_t *>(crt_->allocate_write_area(240));
if(!counter_) {
frame_counter_++;
@@ -133,8 +133,8 @@ void VideoOutput::run_for(const Cycles cycles) {
if(pixel_target_) {
uint16_t colours[2];
if(output_device_ == Outputs::CRT::Monitor) {
colours[0] = (uint8_t)(paper_ ^ inverse_mask);
colours[1] = (uint8_t)(ink_ ^ inverse_mask);
colours[0] = static_cast<uint8_t>(paper_ ^ inverse_mask);
colours[1] = static_cast<uint8_t>(ink_ ^ inverse_mask);
} else {
colours[0] = colour_forms_[paper_ ^ inverse_mask];
colours[1] = colour_forms_[ink_ ^ inverse_mask];
@@ -202,7 +202,7 @@ void VideoOutput::run_for(const Cycles cycles) {
cycles_run_for = 48 - h_counter;
clamp(
int period = (counter_ < 224*64) ? 8 : 48;
crt_->output_blank((unsigned int)period * 6);
crt_->output_blank(static_cast<unsigned int>(period) * 6);
);
} else if(h_counter < 54) {
cycles_run_for = 54 - h_counter;

View File

@@ -1,69 +0,0 @@
//
// Typer.hpp
// Clock Signal
//
// Created by Thomas Harte on 19/06/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef Typer_hpp
#define Typer_hpp
#include <memory>
#include "KeyboardMachine.hpp"
#include "../ClockReceiver/ClockReceiver.hpp"
namespace Utility {
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);
typedef uint16_t KeySequence[16];
uint16_t *table_lookup_sequence_for_character(KeySequence *sequences, size_t length, char character);
const uint16_t EndSequence = 0xffff;
const uint16_t NotMapped = 0xfffe;
};
Typer(const char *string, HalfCycles delay, HalfCycles frequency, Delegate *delegate);
~Typer();
void run_for(const HalfCycles duration);
bool type_next_character();
const char BeginString = 0x02; // i.e. ASCII start of text
const char EndString = 0x03; // i.e. ASCII end of text
private:
char *string_;
HalfCycles frequency_;
HalfCycles counter_;
int phase_;
Delegate *delegate_;
size_t string_pointer_;
};
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));
}
void typer_reset(Typer *typer) {
clear_all_keys();
}
protected:
virtual HalfCycles get_typer_delay() { return HalfCycles(0); }
virtual HalfCycles get_typer_frequency() { return HalfCycles(0); }
std::unique_ptr<Typer> typer_;
};
}
#endif /* Typer_hpp */

View File

@@ -11,7 +11,7 @@
#include <cstdlib>
void Memory::Fuzz(uint8_t *buffer, size_t size) {
unsigned int divider = ((unsigned int)RAND_MAX + 1) / 256;
unsigned int divider = (static_cast<unsigned int>(RAND_MAX) + 1) / 256;
unsigned int shift = 1, value = 1;
while(value < divider) {
value <<= 1;
@@ -19,7 +19,7 @@ void Memory::Fuzz(uint8_t *buffer, size_t size) {
}
for(size_t c = 0; c < size; c++) {
buffer[c] = (uint8_t)(std::rand() >> shift);
buffer[c] = static_cast<uint8_t>(std::rand() >> shift);
}
}

View File

@@ -11,8 +11,13 @@
using namespace Utility;
Typer::Typer(const char *string, HalfCycles delay, HalfCycles 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_, string_size, "%c%s%c", Typer::BeginString, string, Typer::EndString);
@@ -36,10 +41,26 @@ void Typer::run_for(const HalfCycles duration) {
}
}
bool Typer::try_type_next_character() {
uint16_t *sequence = character_mapper_->sequence_for_character(string_[string_pointer_]);
if(!sequence || sequence[0] == KeyboardMachine::Machine::KeyNotMapped) {
return false;
}
if(!phase_) delegate_->clear_all_keys();
else {
delegate_->set_key_state(sequence[phase_ - 1], true);
return sequence[phase_] != KeyboardMachine::Machine::KeyEndSequence;
}
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,28 +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 *Typer::Delegate::table_lookup_sequence_for_character(KeySequence *sequences, size_t length, char character) {
size_t ucharacter = (size_t)((unsigned char)character);
uint16_t *CharacterMapper::table_lookup_sequence_for_character(KeySequence *sequences, size_t length, char character) {
size_t ucharacter = static_cast<size_t>((unsigned char)character);
if(ucharacter > (length / sizeof(KeySequence))) return nullptr;
if(sequences[ucharacter][0] == NotMapped) return nullptr;
if(sequences[ucharacter][0] == KeyboardMachine::Machine::KeyNotMapped) return nullptr;
return sequences[ucharacter];
}

120
Machines/Utility/Typer.hpp Normal file
View File

@@ -0,0 +1,120 @@
//
// Typer.hpp
// Clock Signal
//
// Created by Thomas Harte on 19/06/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef Typer_hpp
#define Typer_hpp
#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;
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 void typer_reset(Typer *typer) = 0;
};
Typer(const char *string, HalfCycles delay, HalfCycles frequency, std::unique_ptr<CharacterMapper> character_mapper, Delegate *delegate);
~Typer();
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_;
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:
/// 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();
// 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 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_;
};
}
#endif /* Typer_hpp */

View File

@@ -1,17 +1,44 @@
//
// Typer.cpp
// Keyboard.cpp
// Clock Signal
//
// Created by Thomas Harte on 09/07/2017.
// Created by Thomas Harte on 10/10/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "ZX8081.hpp"
#include "Keyboard.hpp"
uint16_t *ZX8081::Machine::sequence_for_character(Utility::Typer *typer, char character) {
#define KEYS(...) {__VA_ARGS__, EndSequence}
#define SHIFT(...) {KeyShift, __VA_ARGS__, EndSequence}
#define X {NotMapped}
using namespace ZX8081;
uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
#define BIND(source, dest) case Inputs::Keyboard::Key::source: return ZX8081::dest
switch(key) {
default: break;
BIND(k0, Key0); BIND(k1, Key1); BIND(k2, Key2); BIND(k3, Key3); BIND(k4, Key4);
BIND(k5, Key5); BIND(k6, Key6); BIND(k7, Key7); BIND(k8, Key8); BIND(k9, Key9);
BIND(Q, KeyQ); BIND(W, KeyW); BIND(E, KeyE); BIND(R, KeyR); BIND(T, KeyT);
BIND(Y, KeyY); BIND(U, KeyU); BIND(I, KeyI); BIND(O, KeyO); BIND(P, KeyP);
BIND(A, KeyA); BIND(S, KeyS); BIND(D, KeyD); BIND(F, KeyF); BIND(G, KeyG);
BIND(H, KeyH); BIND(J, KeyJ); BIND(K, KeyK); BIND(L, KeyL);
BIND(Z, KeyZ); BIND(X, KeyX); BIND(C, KeyC); BIND(V, KeyV);
BIND(B, KeyB); BIND(N, KeyN); BIND(M, KeyM);
BIND(LeftShift, KeyShift); BIND(RightShift, KeyShift);
BIND(FullStop, KeyDot);
BIND(Enter, KeyEnter);
BIND(Space, KeySpace);
}
#undef BIND
return KeyboardMachine::Machine::KeyNotMapped;
}
CharacterMapper::CharacterMapper(bool is_zx81) : is_zx81_(is_zx81) {}
uint16_t *CharacterMapper::sequence_for_character(char character) {
#define KEYS(...) {__VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
#define SHIFT(...) {KeyShift, __VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
#define X {KeyboardMachine::Machine::KeyNotMapped}
static KeySequence zx81_key_sequences[] = {
/* NUL */ X, /* SOH */ X,
/* STX */ X, /* ETX */ X,

View File

@@ -0,0 +1,43 @@
//
// Keyboard.hpp
// Clock Signal
//
// Created by Thomas Harte on 10/10/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef Machines_ZX8081_Keyboard_hpp
#define Machines_ZX8081_Keyboard_hpp
#include "../KeyboardMachine.hpp"
#include "../Utility/Typer.hpp"
namespace ZX8081 {
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,
};
struct KeyboardMapper: public KeyboardMachine::Machine::KeyboardMapper {
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key);
};
class CharacterMapper: public ::Utility::CharacterMapper {
public:
CharacterMapper(bool is_zx81);
uint16_t *sequence_for_character(char character);
private:
bool is_zx81_;
};
};
#endif /* KeyboardMapper_hpp */

View File

@@ -31,7 +31,7 @@ Video::Video() :
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();
cycles_since_update_ += static_cast<unsigned int>(half_cycles.as_int());
}
void Video::flush() {
@@ -48,7 +48,7 @@ void Video::flush(bool next_sync) {
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_);
unsigned int data_length = static_cast<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);
@@ -58,7 +58,7 @@ void Video::flush(bool next_sync) {
}
// Any pending pixels being dealt with, pad with the white level.
uint8_t *colour_pointer = (uint8_t *)crt_->allocate_write_area(1);
uint8_t *colour_pointer = static_cast<uint8_t *>(crt_->allocate_write_area(1));
if(colour_pointer) *colour_pointer = 0xff;
crt_->output_level(cycles_since_update_);
}

View File

@@ -8,291 +8,398 @@
#include "ZX8081.hpp"
#include "../MemoryFuzzer.hpp"
#include "../../Processors/Z80/Z80.hpp"
#include "../../Storage/Tape/Tape.hpp"
#include "../../Storage/Tape/Parsers/ZX8081.hpp"
using namespace ZX8081;
#include "../../ClockReceiver/ForceInline.hpp"
#include "../Utility/MemoryFuzzer.hpp"
#include "../Utility/Typer.hpp"
#include "Keyboard.hpp"
#include "Video.hpp"
#include <memory>
namespace {
// The clock rate is 3.25Mhz.
const unsigned int ZX8081ClockRate = 3250000;
}
Machine::Machine() :
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();
}
namespace ZX8081 {
HalfCycles Machine::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_) {
set_non_maskable_interrupt_line(true);
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();
}
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_) {
set_non_maskable_interrupt_line(false);
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));
}
forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
HalfCycles previous_counter = horizontal_counter_;
horizontal_counter_ += cycle.length;
if(nmi_is_enabled_ && !get_halt_line() && get_non_maskable_interrupt_line()) {
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;
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);
}
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)) {
set_interrupt_line(true, Cycles(-2));
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_->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_->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 = get_value_of_register(CPU::Z80::Register::HL);
ram_[hl & ram_mask_] = (uint8_t)next_byte;
*cycle.value = 0x00;
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_];
video_->run_for(horizontal_counter_ - vsync_end_);
} 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) && !get_halt_line()) {
latched_video_byte_ = value;
has_latched_video_byte_ = true;
*cycle.value = 0;
} else *cycle.value = value;
video_->run_for(cycle.length);
}
break;
case CPU::Z80::PartialMachineCycle::Write:
if(address >= ram_base_) {
ram_[address & ram_mask_] = *cycle.value;
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));
}
break;
default: break;
}
if(nmi_is_enabled_ && !z80_.get_halt_line() && z80_.get_non_maskable_interrupt_line()) {
z80_.set_wait_line(true);
}
if(typer_) typer_->run_for(cycle.length);
if(!cycle.is_terminal()) {
return Cycles(0);
}
return HalfCycles(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;
void Machine::flush() {
video_->flush();
}
case CPU::Z80::PartialMachineCycle::Input: {
uint8_t value = 0xff;
if(!(address&1)) {
if(!nmi_is_enabled_) set_vsync(true);
void Machine::setup_output(float aspect_ratio) {
video_.reset(new Video);
}
uint16_t mask = 0x100;
for(int c = 0; c < 8; c++) {
if(!(address & mask)) value &= key_states_[c];
mask <<= 1;
}
void Machine::close_output() {
video_.reset();
}
value &= ~(tape_player_.get_input() ? 0x00 : 0x80);
}
*cycle.value = value;
} break;
std::shared_ptr<Outputs::CRT::CRT> Machine::get_crt() {
return video_->get_crt();
}
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);
std::shared_ptr<Outputs::Speaker> Machine::get_speaker() {
return nullptr;
}
*cycle.value = 0xff;
break;
void Machine::run_for(const Cycles cycles) {
CPU::Z80::Processor<Machine>::run_for(cycles);
}
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 = static_cast<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;
}
void Machine::configure_as_target(const StaticAnalyser::Target &target) {
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);
video_->output_byte(latched_video_byte_);
has_latched_video_byte_ = false;
}
break;
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_);
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_] = static_cast<uint8_t>(next_byte);
*cycle.value = 0x00;
z80_.set_value_of_register(CPU::Z80::Register::ProgramCounter, tape_return_address_ - 1);
if(target.tapes.size()) {
tape_player_.set_tape(target.tapes.front());
}
// 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);
}
}
if(target.loadingCommand.length()) {
set_typer_for_string(target.loadingCommand.c_str());
}
}
// 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;
void Machine::set_rom(ROMType type, std::vector<uint8_t> data) {
switch(type) {
case ZX80: zx80_rom_ = data; break;
case ZX81: zx81_rom_ = data; break;
}
}
case CPU::Z80::PartialMachineCycle::Read:
if(address < ram_base_) {
*cycle.value = rom_[address & rom_mask_];
} else {
uint8_t value = ram_[address & ram_mask_];
#pragma mark - Video
// 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;
void Machine::set_vsync(bool sync) {
vsync_ = sync;
update_sync();
}
case CPU::Z80::PartialMachineCycle::Write:
if(address >= ram_base_) {
ram_[address & ram_mask_] = *cycle.value;
}
break;
void Machine::set_hsync(bool sync) {
hsync_ = sync;
update_sync();
}
default: break;
}
void Machine::update_sync() {
video_->set_sync(vsync_ || hsync_);
}
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_ = static_cast<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 Machine::set_key_state(uint16_t key, bool isPressed) {
if(isPressed)
key_states_[key >> 8] &= (uint8_t)(~key);
else
key_states_[key >> 8] |= (uint8_t)key;
void set_key_state(uint16_t key, bool isPressed) override final {
if(isPressed)
key_states_[key >> 8] &= static_cast<uint8_t>(~key);
else
key_states_[key >> 8] |= static_cast<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); }
KeyboardMapper &get_keyboard_mapper() override {
return keyboard_mapper_;
}
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];
ZX8081::KeyboardMapper keyboard_mapper_;
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_);
}
};
}
void Machine::clear_all_keys() {
memset(key_states_, 0xff, 8);
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

@@ -11,13 +11,7 @@
#include "../ConfigurationTarget.hpp"
#include "../CRTMachine.hpp"
#include "../Typer.hpp"
#include "../../Processors/Z80/Z80.hpp"
#include "../../Storage/Tape/Tape.hpp"
#include "../../Storage/Tape/Parsers/ZX8081.hpp"
#include "Video.hpp"
#include "../KeyboardMachine.hpp"
#include <cstdint>
#include <vector>
@@ -28,93 +22,19 @@ 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 CPU::Z80::Processor<Machine>,
public CRTMachine::Machine,
public Utility::TypeRecipient,
public ConfigurationTarget::Machine {
public ConfigurationTarget::Machine,
public KeyboardMachine::Machine {
public:
Machine();
static Machine *ZX8081(const StaticAnalyser::Target &target_hint);
virtual ~Machine();
HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle);
void flush();
virtual void set_rom(ROMType type, std::vector<uint8_t> data) = 0;
void setup_output(float aspect_ratio);
void close_output();
std::shared_ptr<Outputs::CRT::CRT> get_crt();
std::shared_ptr<Outputs::Speaker> get_speaker();
void run_for(const Cycles cycles);
void configure_as_target(const StaticAnalyser::Target &target);
void set_rom(ROMType type, std::vector<uint8_t> data);
void set_key_state(uint16_t key, bool isPressed);
void clear_all_keys();
inline void set_use_fast_tape_hack(bool activate) { use_fast_tape_hack_ = activate; }
inline void set_use_automatic_tape_motor_control(bool enabled) {
use_automatic_tape_motor_control_ = enabled;
if(!enabled) {
tape_player_.set_motor_control(false);
}
}
inline void set_tape_is_playing(bool is_playing) { tape_player_.set_motor_control(is_playing); }
// for Utility::TypeRecipient::Delegate
uint16_t *sequence_for_character(Utility::Typer *typer, char character);
HalfCycles get_typer_delay() { return Cycles(7000000); }
HalfCycles get_typer_frequency() { return Cycles(390000); }
private:
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];
void set_vsync(bool sync);
void set_hsync(bool sync);
void update_sync();
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_;
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;
};
}

View File

@@ -23,12 +23,12 @@ class CRC16 {
CRC16(uint16_t polynomial, uint16_t reset_value) :
reset_value_(reset_value), value_(reset_value) {
for(int c = 0; c < 256; c++) {
uint16_t shift_value = (uint16_t)(c << 8);
uint16_t shift_value = static_cast<uint16_t>(c << 8);
for(int b = 0; b < 8; b++) {
uint16_t exclusive_or = (shift_value&0x8000) ? polynomial : 0x0000;
shift_value = (uint16_t)(shift_value << 1) ^ exclusive_or;
shift_value = static_cast<uint16_t>(shift_value << 1) ^ exclusive_or;
}
xor_table[c] = (uint16_t)shift_value;
xor_table[c] = static_cast<uint16_t>(shift_value);
}
}
@@ -37,7 +37,7 @@ class CRC16 {
/// Updates the CRC to include @c byte.
inline void add(uint8_t byte) {
value_ = (uint16_t)((value_ << 8) ^ xor_table[(value_ >> 8) ^ byte]);
value_ = static_cast<uint16_t>((value_ << 8) ^ xor_table[(value_ >> 8) ^ byte]);
}
/// @returns The current value of the CRC.

View File

@@ -9,14 +9,17 @@
#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) {
// TODO: replace with the C++17 GCD function, once available.
#if __cplusplus > 201402L
return std::gcd(a, b);
#else
if(a < b) {
std::swap(a, b);
}
@@ -29,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

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0800"
LastUpgradeVersion = "0900"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -26,6 +26,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES"
codeCoverageEnabled = "YES">
<Testables>
@@ -71,10 +72,13 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
enableUBSanitizer = "YES"
language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
stopOnEveryUBSanitizerIssue = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "NO">
<BuildableProductRunnable

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

@@ -3,7 +3,6 @@
//
#import "CSMachine.h"
#import "CSKeyboardMachine.h"
#import "CSFastLoading.h"
#import "CSAtari2600.h"
@@ -17,3 +16,5 @@
#import "CSOpenGLView.h"
#import "CSAudioQueue.h"
#import "CSBestEffortUpdater.h"
#include "KeyCodes.h"

View File

@@ -24,9 +24,9 @@ class Atari2600OptionsPanel: MachinePanel {
}
fileprivate func pushSwitchValues() {
atari2600.colourButton = colourButton.state == NSOnState
atari2600.leftPlayerDifficultyButton = leftPlayerDifficultyButton.state == NSOnState
atari2600.rightPlayerDifficultyButton = rightPlayerDifficultyButton.state == NSOnState
atari2600.colourButton = colourButton.state == .on
atari2600.leftPlayerDifficultyButton = leftPlayerDifficultyButton.state == .on
atari2600.rightPlayerDifficultyButton = rightPlayerDifficultyButton.state == .on
}
@IBAction func optionWasPressed(_ sender: NSButton!) {

View File

@@ -38,10 +38,10 @@ class MachineDocument:
}
fileprivate var audioQueue: CSAudioQueue! = nil
fileprivate var bestEffortUpdater: CSBestEffortUpdater!
fileprivate var bestEffortUpdater: CSBestEffortUpdater?
override var windowNibName: String? {
return "MachineDocument"
override var windowNibName: NSNib.Name? {
return NSNib.Name(rawValue: "MachineDocument")
}
override func windowControllerDidLoadNib(_ aController: NSWindowController) {
@@ -69,7 +69,7 @@ class MachineDocument:
self.openGLView.window!.makeKeyAndOrderFront(self)
// start accepting best effort updates
self.bestEffortUpdater.delegate = self
self.bestEffortUpdater!.delegate = self
}
func machineDidChangeClockRate(_ machine: CSMachine!) {
@@ -77,7 +77,7 @@ class MachineDocument:
}
func machineDidChangeClockIsUnlimited(_ machine: CSMachine!) {
self.bestEffortUpdater.runAsUnlimited = machine.clockIsUnlimited
self.bestEffortUpdater?.runAsUnlimited = machine.clockIsUnlimited
}
fileprivate func setupClockRate() {
@@ -91,15 +91,21 @@ class MachineDocument:
self.machine.setAudioSamplingRate(selectedSamplingRate, bufferSize:audioQueue.preferredBufferSize)
}
self.bestEffortUpdater.clockRate = self.machine.clockRate
self.bestEffortUpdater?.clockRate = self.machine.clockRate
}
override func close() {
bestEffortUpdater.flush()
optionsPanel?.setIsVisible(false)
optionsPanel = nil
openGLView.delegate = nil
bestEffortUpdater!.delegate = nil
bestEffortUpdater = nil
actionLock.lock()
drawLock.lock()
machine = nil
openGLView.invalidate()
openGLView.openGLContext!.makeCurrentContext()
actionLock.unlock()
drawLock.unlock()
@@ -114,7 +120,7 @@ class MachineDocument:
analysis.apply(to: self.machine)
if let optionsPanelNibName = analysis.optionsPanelNibName {
Bundle.main.loadNibNamed(optionsPanelNibName, owner: self, topLevelObjects: nil)
Bundle.main.loadNibNamed(NSNib.Name(rawValue: optionsPanelNibName), owner: self, topLevelObjects: nil)
self.optionsPanel.machine = self.machine
showOptions(self)
}
@@ -131,8 +137,8 @@ class MachineDocument:
// MARK: the pasteboard
func paste(_ sender: AnyObject!) {
let pasteboard = NSPasteboard.general()
if let string = pasteboard.string(forType: NSPasteboardTypeString) {
let pasteboard = NSPasteboard.general
if let string = pasteboard.string(forType: .string) {
self.machine.paste(string)
}
}
@@ -143,24 +149,35 @@ class MachineDocument:
}
func runForNumberOfCycles(_ numberOfCycles: Int32) {
let cyclesToRunFor = min(numberOfCycles, Int32(bestEffortUpdater.clockRate / 10))
if actionLock.try() {
self.machine.runForNumber(ofCycles: cyclesToRunFor)
actionLock.unlock()
if let bestEffortUpdater = bestEffortUpdater {
let cyclesToRunFor = min(numberOfCycles, Int32(bestEffortUpdater.clockRate / 10))
if actionLock.try() {
self.machine.runForNumber(ofCycles: cyclesToRunFor)
actionLock.unlock()
}
}
}
// MARK: CSAudioQueueDelegate
final func audioQueueIsRunningDry(_ audioQueue: CSAudioQueue) {
bestEffortUpdater.update()
bestEffortUpdater?.update()
}
// MARK: CSOpenGLViewDelegate
final func openGLView(_ view: CSOpenGLView, drawViewOnlyIfDirty onlyIfDirty: Bool) {
bestEffortUpdater.update()
if drawLock.try() {
self.machine.drawView(forPixelSize: view.backingSize, onlyIfDirty: onlyIfDirty)
drawLock.unlock()
if let bestEffortUpdater = bestEffortUpdater {
bestEffortUpdater.update()
if drawLock.try() {
self.machine.drawView(forPixelSize: view.backingSize, onlyIfDirty: onlyIfDirty)
drawLock.unlock()
}
}
}
final func openGLView(_ view: CSOpenGLView, didReceiveFileAt URL: URL) {
let mediaSet = CSMediaSet(fileAt: URL)
if let mediaSet = mediaSet {
mediaSet.apply(to: self.machine)
}
}
@@ -170,48 +187,22 @@ class MachineDocument:
}
// MARK: Input management
fileprivate func withKeyboardMachine(_ action: (CSKeyboardMachine) -> ()) {
if let keyboardMachine = self.machine as? CSKeyboardMachine {
action(keyboardMachine)
}
}
fileprivate func withJoystickMachine(_ action: (CSJoystickMachine) -> ()) {
if let joystickMachine = self.machine as? CSJoystickMachine {
action(joystickMachine)
}
}
fileprivate func sendJoystickEvent(_ machine: CSJoystickMachine, keyCode: UInt16, isPressed: Bool) {
switch keyCode {
case 123: machine.setDirection(.left, onPad: 0, isPressed: isPressed)
case 126: machine.setDirection(.up, onPad: 0, isPressed: isPressed)
case 124: machine.setDirection(.right, onPad: 0, isPressed: isPressed)
case 125: machine.setDirection(.down, onPad: 0, isPressed: isPressed)
default: machine.setButtonAt(0, onPad: 0, isPressed: isPressed)
}
}
func windowDidResignKey(_ notification: Notification) {
self.withKeyboardMachine { $0.clearAllKeys() }
self.machine.clearAllKeys()
}
func keyDown(_ event: NSEvent) {
self.withKeyboardMachine { $0.setKey(event.keyCode, isPressed: true) }
self.withJoystickMachine { sendJoystickEvent($0, keyCode: event.keyCode, isPressed: true) }
self.machine.setKey(event.keyCode, isPressed: true)
}
func keyUp(_ event: NSEvent) {
self.withKeyboardMachine { $0.setKey(event.keyCode, isPressed: false) }
self.withJoystickMachine { sendJoystickEvent($0, keyCode: event.keyCode, isPressed: false) }
self.machine.setKey(event.keyCode, isPressed: false)
}
func flagsChanged(_ newModifiers: NSEvent) {
self.withKeyboardMachine {
$0.setKey(VK_Shift, isPressed: newModifiers.modifierFlags.contains(.shift))
$0.setKey(VK_Control, isPressed: newModifiers.modifierFlags.contains(.control))
$0.setKey(VK_Command, isPressed: newModifiers.modifierFlags.contains(.command))
$0.setKey(VK_Option, isPressed: newModifiers.modifierFlags.contains(.option))
}
self.machine.setKey(VK_Shift, isPressed: newModifiers.modifierFlags.contains(.shift))
self.machine.setKey(VK_Control, isPressed: newModifiers.modifierFlags.contains(.control))
self.machine.setKey(VK_Command, isPressed: newModifiers.modifierFlags.contains(.command))
self.machine.setKey(VK_Option, isPressed: newModifiers.modifierFlags.contains(.option))
}
}

View File

@@ -24,7 +24,7 @@ class MachinePanel: NSPanel {
@IBOutlet var fastLoadingButton: NSButton?
@IBAction func setFastLoading(_ sender: NSButton!) {
if let fastLoadingMachine = machine as? CSFastLoading {
let useFastLoadingHack = sender.state == NSOnState
let useFastLoadingHack = sender.state == .on
fastLoadingMachine.useFastLoadingHack = useFastLoadingHack
UserDefaults.standard.set(useFastLoadingHack, forKey: fastLoadingUserDefaultsKey)
}
@@ -39,7 +39,7 @@ class MachinePanel: NSPanel {
if let fastLoadingMachine = machine as? CSFastLoading {
let useFastLoadingHack = standardUserDefaults.bool(forKey: self.fastLoadingUserDefaultsKey)
fastLoadingMachine.useFastLoadingHack = useFastLoadingHack
self.fastLoadingButton?.state = useFastLoadingHack ? NSOnState : NSOffState
self.fastLoadingButton?.state = useFastLoadingHack ? .on : .off
}
}
}

View File

@@ -18,7 +18,7 @@ class ZX8081OptionsPanel: MachinePanel {
get { return prefixedUserDefaultsKey("automaticTapeMotorControl") }
}
@IBAction func setAutomaticTapeMotorConrol(_ sender: NSButton!) {
let isEnabled = sender.state == NSOnState
let isEnabled = sender.state == .on
UserDefaults.standard.set(isEnabled, forKey: self.automaticTapeMotorControlDefaultsKey)
self.playOrPauseTapeButton.isEnabled = !isEnabled
self.zx8081.useAutomaticTapeMotorControl = isEnabled
@@ -42,7 +42,7 @@ class ZX8081OptionsPanel: MachinePanel {
])
let automaticTapeMotorControlIsEnabled = standardUserDefaults.bool(forKey: self.automaticTapeMotorControlDefaultsKey)
self.automaticTapeMotorControlButton.state = automaticTapeMotorControlIsEnabled ? NSOnState : NSOffState
self.automaticTapeMotorControlButton.state = automaticTapeMotorControlIsEnabled ? .on : .off
self.playOrPauseTapeButton.isEnabled = !automaticTapeMotorControlIsEnabled
self.zx8081.useAutomaticTapeMotorControl = automaticTapeMotorControlIsEnabled
}

View File

@@ -224,6 +224,40 @@
<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

@@ -1,22 +0,0 @@
//
// CSJoystickMachine.h
// Clock Signal
//
// Created by Thomas Harte on 03/10/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
typedef NS_ENUM(NSInteger, CSJoystickDirection)
{
CSJoystickDirectionUp,
CSJoystickDirectionDown,
CSJoystickDirectionLeft,
CSJoystickDirectionRight
};
@protocol CSJoystickMachine <NSObject>
- (void)setButtonAtIndex:(NSUInteger)button onPad:(NSUInteger)pad isPressed:(BOOL)isPressed;
- (void)setDirection:(CSJoystickDirection)direction onPad:(NSUInteger)pad isPressed:(BOOL)isPressed;
@end

View File

@@ -1,16 +0,0 @@
//
// CSKeyboardMachine.h
// Clock Signal
//
// Created by Thomas Harte on 05/06/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#import "KeyCodes.h"
@protocol CSKeyboardMachine <NSObject>
- (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed;
- (void)clearAllKeys;
@end

View File

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

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