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

Compare commits

...

193 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
240 changed files with 10317 additions and 8080 deletions

View File

@@ -161,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.

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,8 +25,8 @@ WD1770::Status::Status() :
busy(false) {}
WD1770::WD1770(Personality p) :
Storage::Disk::MFMController(8000000, 16, 300),
interesting_event_mask_((int)Event1770::Command),
Storage::Disk::MFMController(8000000),
interesting_event_mask_(static_cast<int>(Event1770::Command)),
resume_point_(0),
delay_time_(0),
index_hole_count_target_(-1),
@@ -34,7 +34,7 @@ WD1770::WD1770(Personality p) :
personality_(p),
head_is_loaded_(false) {
set_is_double_density(false);
posit_event((int)Event1770::Command);
posit_event(static_cast<int>(Event1770::Command));
}
void WD1770::set_register(int address, uint8_t value) {
@@ -47,7 +47,7 @@ void WD1770::set_register(int address, uint8_t value) {
});
} else {
command_ = value;
posit_event((int)Event1770::Command);
posit_event(static_cast<int>(Event1770::Command));
}
}
break;
@@ -75,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;
@@ -91,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);
}
@@ -115,24 +115,24 @@ 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((int)Event1770::Timer);
posit_event(static_cast<int>(Event1770::Timer));
} else {
delay_time_ -= number_of_cycles;
}
}
}
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = (int)mask; return; case __LINE__:
#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_ = (int)Event::Token; return; }
#define WAIT_FOR_BYTES(count) resume_point_ = __LINE__; distance_into_section_ = 0; WAIT_FOR_EVENT(Event::Token); if(get_latest_token().type == Token::Byte) distance_into_section_++; if(distance_into_section_ < count) { interesting_event_mask_ = static_cast<int>(Event::Token); return; }
#define BEGIN_SECTION() switch(resume_point_) { default:
#define END_SECTION() 0; }
#define READ_ID() \
if(new_event_type == (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) { \
header_[distance_into_section_ - 1] = get_latest_token().byte_value; \
@@ -169,10 +169,10 @@ void WD1770::run_for(const Cycles cycles) {
// +--------+----------+-------------------------+
void WD1770::posit_event(int new_event_type) {
if(new_event_type == (int)Event::IndexHole) {
if(new_event_type == static_cast<int>(Event::IndexHole)) {
index_hole_count_++;
if(index_hole_count_target_ == index_hole_count_) {
posit_event((int)Event1770::IndexHoleTarget);
posit_event(static_cast<int>(Event1770::IndexHoleTarget));
index_hole_count_target_ = -1;
}
@@ -187,7 +187,7 @@ void WD1770::posit_event(int new_event_type) {
}
}
if(!(interesting_event_mask_ & (int)new_event_type)) return;
if(!(interesting_event_mask_ & static_cast<int>(new_event_type))) return;
interesting_event_mask_ &= ~new_event_type;
Status new_status;
@@ -257,7 +257,7 @@ void WD1770::posit_event(int new_event_type) {
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:
@@ -280,11 +280,11 @@ void WD1770::posit_event(int 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:
@@ -310,7 +310,7 @@ void WD1770::posit_event(int new_event_type) {
distance_into_section_ = 0;
verify_read_data:
WAIT_FOR_EVENT((int)Event::IndexHole | (int)Event::Token);
WAIT_FOR_EVENT(static_cast<int>(Event::IndexHole) | static_cast<int>(Event::Token));
READ_ID();
if(index_hole_count_ == 6) {
@@ -376,7 +376,7 @@ void WD1770::posit_event(int new_event_type) {
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();
@@ -386,7 +386,7 @@ void WD1770::posit_event(int 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;
});
@@ -394,7 +394,7 @@ void WD1770::posit_event(int new_event_type) {
}
type2_get_header:
WAIT_FOR_EVENT((int)Event::IndexHole | (int)Event::Token);
WAIT_FOR_EVENT(static_cast<int>(Event::IndexHole) | static_cast<int>(Event::Token));
READ_ID();
if(index_hole_count_ == 5) {
@@ -594,7 +594,7 @@ void WD1770::posit_event(int new_event_type) {
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:
@@ -611,8 +611,8 @@ void WD1770::posit_event(int new_event_type) {
distance_into_section_ = 0;
read_address_get_header:
WAIT_FOR_EVENT((int)Event::IndexHole | (int)Event::Token);
if(new_event_type == (int)Event::Token) {
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) {
@@ -652,7 +652,7 @@ void WD1770::posit_event(int new_event_type) {
index_hole_count_ = 0;
read_track_read_byte:
WAIT_FOR_EVENT((int)Event::Token | (int)Event::IndexHole);
WAIT_FOR_EVENT(static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole));
if(index_hole_count_) {
goto wait_for_command;
}
@@ -675,7 +675,7 @@ void WD1770::posit_event(int 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;
});
@@ -720,7 +720,7 @@ void WD1770::posit_event(int 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) |
@@ -781,8 +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((int)Event1770::HeadLoad);
if(head_loaded) posit_event(static_cast<int>(Event1770::HeadLoad));
}

View File

@@ -9,7 +9,7 @@
#ifndef _770_hpp
#define _770_hpp
#include "../../Storage/Disk/MFMDiskController.hpp"
#include "../../Storage/Disk/Controller/MFMDiskController.hpp"
namespace WD {
@@ -76,6 +76,7 @@ class WD1770: public Storage::Disk::MFMController {
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:

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_; }
@@ -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

@@ -18,12 +18,12 @@ namespace Motorola {
namespace CRTC {
struct BusState {
bool display_enable;
bool hsync;
bool vsync;
bool cursor;
uint16_t refresh_address;
uint16_t row_address;
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 {
@@ -44,27 +44,39 @@ class BusHandler {
};
enum Personality {
HD6845S, //
UM6845R, //
MC6845, //
AMS40226 //
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) {}
personality_(p), bus_handler_(bus_handler), status_(0) {}
void select_register(uint8_t r) {
selected_register_ = r;
}
uint8_t get_status() const {
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() const {
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_];
}
@@ -75,14 +87,27 @@ template <class T> class CRTC6845 {
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) {
@@ -115,10 +140,20 @@ template <class T> class CRTC6845 {
}
// 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
// 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) {
bus_state_.hsync = hsync_counter_ != (registers_[3] & 15);
hsync_counter_ = (hsync_counter_ + 1) & 15;
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();
@@ -131,12 +166,13 @@ template <class T> class CRTC6845 {
private:
inline void perform_bus_cycle_phase1() {
bus_state_.display_enable = character_is_visible_ && line_is_visible_;
// 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_state_.display_enable = character_is_visible_ && line_is_visible_;
bus_handler_.perform_bus_cycle_phase2(bus_state_);
}
@@ -144,8 +180,16 @@ template <class T> class CRTC6845 {
// check for end of vertical sync
if(bus_state_.vsync) {
vsync_counter_ = (vsync_counter_ + 1) & 15;
if(vsync_counter_ == (registers_[3] >> 4)) {
bus_state_.vsync = false;
// 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;
}
}
@@ -196,7 +240,7 @@ template <class T> class CRTC6845 {
inline void do_end_of_frame() {
line_counter_ = 0;
line_is_visible_ = true;
line_address_ = (uint16_t)((registers_[12] << 8) | registers_[13]);
line_address_ = static_cast<uint16_t>((registers_[12] << 8) | registers_[13]);
bus_state_.refresh_address = line_address_;
}
@@ -204,20 +248,25 @@ template <class T> class CRTC6845 {
T &bus_handler_;
BusState bus_state_;
uint8_t registers_[18];
int selected_register_;
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_;
uint8_t line_counter_;
uint8_t character_counter_ = 0;
uint8_t line_counter_ = 0;
bool character_is_visible_, line_is_visible_;
bool character_is_visible_ = false, line_is_visible_ = false;
int hsync_counter_;
int vsync_counter_;
bool is_in_adjustment_period_;
int hsync_counter_ = 0;
int vsync_counter_ = 0;
bool is_in_adjustment_period_ = false;
uint16_t line_address_;
uint16_t end_of_line_address_;
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;
};
}

View File

@@ -7,7 +7,7 @@
//
#include "i8272.hpp"
#include "../../Storage/Disk/Encodings/MFM.hpp"
//#include "../../Storage/Disk/Encodings/MFM/Encoder.hpp"
#include <cstdio>
@@ -34,6 +34,7 @@ using namespace Intel::i8272;
#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)
@@ -43,6 +44,7 @@ using namespace Intel::i8272;
#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)
@@ -75,17 +77,10 @@ namespace {
const uint8_t CommandSenseDriveStatus = 0x04;
}
i8272::i8272(BusHandler &bus_handler, Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute) :
Storage::Disk::MFMController(clock_rate, clock_rate_multiplier, revolutions_per_minute),
bus_handler_(bus_handler),
main_status_(0),
interesting_event_mask_((int)Event8272::CommandByte),
resume_point_(0),
delay_time_(0),
head_timers_running_(0),
expects_input_(false),
drives_seeking_(0) {
posit_event((int)Event8272::CommandByte);
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() {
@@ -101,7 +96,7 @@ void i8272::run_for(Cycles cycles) {
if(delay_time_ > 0) {
if(cycles.as_int() >= delay_time_) {
delay_time_ = 0;
posit_event((int)Event8272::Timer);
posit_event(static_cast<int>(Event8272::Timer));
} else {
delay_time_ -= cycles.as_int();
}
@@ -119,11 +114,12 @@ void i8272::run_for(Cycles cycles) {
// Perform a step.
int direction = (drives_[c].target_head_position < drives_[c].head_position) ? -1 : 1;
printf("Target %d versus believed %d\n", drives_[c].target_head_position, drives_[c].head_position);
drives_[c].drive->step(direction);
select_drive(c);
get_drive().step(direction);
if(drives_[c].target_head_position >= 0) drives_[c].head_position += direction;
// Check for completion.
if(drives_[c].seek_is_satisfied()) {
if(seek_is_satisfied(c)) {
drives_[c].phase = Drive::CompletedSeeking;
drives_seeking_--;
break;
@@ -157,6 +153,11 @@ void i8272::run_for(Cycles cycles) {
}
}
// 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();
}
@@ -175,7 +176,7 @@ void i8272::set_register(int address, uint8_t value) {
} else {
// accumulate latest byte in the command byte sequence
command_.push_back(value);
posit_event((int)Event8272::CommandByte);
posit_event(static_cast<int>(Event8272::CommandByte));
}
}
@@ -184,7 +185,7 @@ uint8_t i8272::get_register(int address) {
if(result_stack_.empty()) return 0xff;
uint8_t result = result_stack_.back();
result_stack_.pop_back();
if(result_stack_.empty()) posit_event((int)Event8272::ResultEmpty);
if(result_stack_.empty()) posit_event(static_cast<int>(Event8272::ResultEmpty));
return result;
} else {
@@ -192,26 +193,20 @@ uint8_t i8272::get_register(int address) {
}
}
void i8272::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
if(drive < 4 && drive >= 0) {
drives_[drive].drive->set_disk(disk);
}
}
#define BEGIN_SECTION() switch(resume_point_) { default:
#define END_SECTION() }
#define MS_TO_CYCLES(x) x * 8000
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = (int)mask; return; case __LINE__:
#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; interesting_event_mask_ = (int)Event8272::Timer; delay_time_ = MS_TO_CYCLES(ms); is_sleeping_ = false; update_sleep_observer(); case __LINE__: if(delay_time_) return;
#define 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((int)Event::Token | (int)Event::IndexHole); \
if(event_type == (int)Event::IndexHole) { index_hole_limit_--; } \
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__); \
@@ -219,8 +214,8 @@ void i8272::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
#define FIND_DATA() \
set_data_mode(DataMode::Scanning); \
CONCAT(find_data, __LINE__): WAIT_FOR_EVENT((int)Event::Token | (int)Event::IndexHole); \
if(event_type == (int)Event::Token) { \
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__); \
}
@@ -235,10 +230,10 @@ void i8272::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
#define SET_DRIVE_HEAD_MFM() \
active_drive_ = command_[1]&3; \
active_head_ = (command_[1] >> 2)&1; \
set_drive(drives_[active_drive_].drive); \
drives_[active_drive_].drive->set_head((unsigned int)active_head_); \
set_is_double_density(command_[0] & 0x40); \
invalidate_track();
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; \
@@ -268,7 +263,11 @@ void i8272::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
}
void i8272::posit_event(int event_type) {
if(event_type == (int)Event::IndexHole) index_hole_count_++;
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;
@@ -338,9 +337,14 @@ void i8272::posit_event(int event_type) {
}
// 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.
@@ -427,7 +431,8 @@ void i8272::posit_event(int event_type) {
// flag doesn't match the sort the command was looking for.
read_data_found_header:
FIND_DATA();
if(event_type == (int)Event::Token) {
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();
@@ -458,24 +463,24 @@ void i8272::posit_event(int event_type) {
//
// TODO: consider DTL.
read_data_get_byte:
WAIT_FOR_EVENT((int)Event::Token | (int)Event::IndexHole);
if(event_type == (int)Event::Token) {
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((int)Event8272::ResultEmpty | (int)Event::Token | (int)Event::IndexHole);
WAIT_FOR_EVENT(static_cast<int>(Event8272::ResultEmpty) | static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole));
}
switch(event_type) {
case (int)Event8272::ResultEmpty: // The caller read the byte in time; proceed as normal.
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 (int)Event::Token: // The caller hasn't read the old byte yet and a new one has arrived
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 (int)Event::IndexHole:
case static_cast<int>(Event::IndexHole):
SetEndOfCylinder();
goto abort;
break;
@@ -504,7 +509,7 @@ void i8272::posit_event(int event_type) {
write_data:
printf("Write [deleted] data [%02x %02x %02x %02x ... %02x %02x]\n", command_[2], command_[3], command_[4], command_[5], command_[6], command_[8]);
if(drives_[active_drive_].drive->get_is_read_only()) {
if(get_drive().get_is_read_only()) {
SetNotWriteable();
goto abort;
}
@@ -527,7 +532,6 @@ void i8272::posit_event(int event_type) {
WAIT_FOR_EVENT(Event::DataWritten);
if(!has_input_) {
SetOverrun();
end_writing();
goto abort;
}
write_byte(input_);
@@ -607,7 +611,7 @@ void i8272::posit_event(int event_type) {
distance_into_section_++;
SetDataRequest();
// TODO: other possible exit conditions; find a way to merge with the read_data version of this.
WAIT_FOR_EVENT((int)Event8272::ResultEmpty);
WAIT_FOR_EVENT(static_cast<int>(Event8272::ResultEmpty));
ResetDataRequest();
if(distance_into_section_ < (128 << header_[2])) goto read_track_get_byte;
@@ -619,7 +623,7 @@ void i8272::posit_event(int event_type) {
// Performs format [/write] track.
format_track:
printf("Format track\n");
if(drives_[active_drive_].drive->get_is_read_only()) {
if(get_drive().get_is_read_only()) {
SetNotWriteable();
goto abort;
}
@@ -644,14 +648,13 @@ void i8272::posit_event(int event_type) {
expects_input_ = true;
distance_into_section_ = 0;
format_track_write_header:
WAIT_FOR_EVENT((int)Event::DataWritten | (int)Event::IndexHole);
WAIT_FOR_EVENT(static_cast<int>(Event::DataWritten) | static_cast<int>(Event::IndexHole));
switch(event_type) {
case (int)Event::IndexHole:
case static_cast<int>(Event::IndexHole):
SetOverrun();
end_writing();
goto abort;
break;
case (int)Event::DataWritten:
case static_cast<int>(Event::DataWritten):
header_[distance_into_section_] = input_;
write_byte(input_);
has_input_ = false;
@@ -682,8 +685,8 @@ void i8272::posit_event(int event_type) {
// Otherwise, pad out to the index hole.
format_track_pad:
write_byte(get_is_double_density() ? 0x4e : 0xff);
WAIT_FOR_EVENT((int)Event::DataWritten | (int)Event::IndexHole);
if(event_type != (int)Event::IndexHole) goto format_track_pad;
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();
@@ -712,6 +715,7 @@ void i8272::posit_event(int event_type) {
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) {
@@ -741,7 +745,7 @@ void i8272::posit_event(int event_type) {
}
// Check whether any steps are even needed; if not then mark as completed already.
if(drives_[drive].seek_is_satisfied()) {
if(seek_is_satisfied(drive)) {
drives_[drive].phase = Drive::CompletedSeeking;
drives_seeking_--;
}
@@ -764,14 +768,13 @@ void i8272::posit_event(int event_type) {
// If a drive was found, return its results. Otherwise return a single 0x80.
if(found_drive != -1) {
drives_[found_drive].phase = Drive::NotSeeking;
status_[0] = (uint8_t)found_drive;
status_[0] = static_cast<uint8_t>(found_drive);
main_status_ &= ~(1 << found_drive);
SetSeekEnd();
result_stack_.push_back(drives_[found_drive].head_position);
result_stack_.push_back(status_[0]);
result_stack_ = { drives_[found_drive].head_position, status_[0]};
} else {
result_stack_.push_back(0x80);
result_stack_ = { 0x80 };
}
}
goto post_result;
@@ -793,24 +796,28 @@ void i8272::posit_event(int event_type) {
printf("Sense drive status\n");
{
int drive = command_[1] & 3;
result_stack_.push_back(
(command_[1] & 7) | // drive and head number
0x08 | // single sided
(drives_[drive].drive->get_is_track_zero() ? 0x10 : 0x00) |
(drives_[drive].drive->get_is_ready() ? 0x20 : 0x00) |
(drives_[drive].drive->get_is_read_only() ? 0x40 : 0x00)
);
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_.push_back(0x80);
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;
@@ -818,14 +825,7 @@ void i8272::posit_event(int event_type) {
post_st012chrn:
SCHEDULE_HEAD_UNLOAD();
result_stack_.push_back(size_);
result_stack_.push_back(sector_);
result_stack_.push_back(head_);
result_stack_.push_back(cylinder_);
result_stack_.push_back(status_[2]);
result_stack_.push_back(status_[1]);
result_stack_.push_back(status_[0]);
result_stack_ = {size_, sector_, head_, cylinder_, status_[2], status_[1], status_[0]};
goto post_result;
@@ -839,6 +839,7 @@ void i8272::posit_event(int event_type) {
printf("\n");
// Set ready to send data to the processor, no longer in non-DMA execution phase.
is_executing_ = false;
ResetNonDMAExecution();
SetDataRequest();
SetDataDirectionToProcessor();
@@ -853,9 +854,9 @@ void i8272::posit_event(int event_type) {
END_SECTION()
}
bool i8272::Drive::seek_is_satisfied() {
return (target_head_position == head_position) ||
(target_head_position == -1 && drive->get_is_track_zero());
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) {

View File

@@ -9,7 +9,7 @@
#ifndef i8272_hpp
#define i8272_hpp
#include "../../Storage/Disk/MFMDiskController.hpp"
#include "../../Storage/Disk/Controller/MFMDiskController.hpp"
#include <cstdint>
#include <memory>
@@ -26,7 +26,7 @@ class BusHandler {
class i8272: public Storage::Disk::MFMController {
public:
i8272(BusHandler &bus_handler, Cycles clock_rate, int clock_rate_multiplier, int revolutions_per_minute);
i8272(BusHandler &bus_handler, Cycles clock_rate);
void run_for(Cycles);
@@ -39,99 +39,94 @@ class i8272: public Storage::Disk::MFMController {
void set_dma_acknowledge(bool dack);
void set_terminal_count(bool tc);
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive);
bool is_sleeping();
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_;
uint8_t status_[3];
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_;
bool has_input_;
bool expects_input_;
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_;
int resume_point_;
bool is_access_command_;
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_;
int delay_time_ = 0;
// The connected drives.
struct Drive {
uint8_t head_position;
uint8_t head_position = 0;
// Seeking: persistent state.
enum Phase {
NotSeeking,
Seeking,
CompletedSeeking
} phase;
bool did_seek;
bool seek_failed;
} phase = NotSeeking;
bool did_seek = false;
bool seek_failed = false;
// Seeking: transient state.
int step_rate_counter;
int steps_taken;
int target_head_position; // either an actual number, or -1 to indicate to step until track zero
/// @returns @c true if the currently queued-up seek or recalibrate has reached where it should be.
bool seek_is_satisfied();
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];
bool head_is_loaded[2];
int head_unload_delay[2] = {0, 0};
bool head_is_loaded[2] = {false, false};
// The connected drive.
std::shared_ptr<Storage::Disk::Drive> drive;
Drive() :
head_position(0), phase(NotSeeking),
drive(new Storage::Disk::Drive),
head_is_loaded{false, false},
head_unload_delay{0, 0} {};
} drives_[4];
int drives_seeking_;
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_;
int head_unload_time_;
int head_load_time_;
bool dma_mode_;
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_;
int head_timers_running_ = 0;
// Transient storage and counters used while reading the disk.
uint8_t header_[6];
int distance_into_section_;
int index_hole_count_, index_hole_limit_;
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_;
int active_head_;
int active_drive_ = 0;
int active_head_ = 0;
// Internal registers.
uint8_t cylinder_, head_, sector_, size_;
uint8_t cylinder_ = 0, head_ = 0, sector_ = 0, size_ = 0;
// Master switch on not performing any work.
bool is_sleeping_;
bool is_sleeping_ = false;
};
}

View File

@@ -67,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) {
@@ -160,7 +160,7 @@ 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]
@@ -186,7 +186,7 @@ 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;
}
@@ -201,7 +201,7 @@ void AY38910::set_register_value(uint8_t value) {
break;
case 12:
envelope_period_ = (envelope_period_ & 0xff) | (int)(value << 8);
envelope_period_ = (envelope_period_ & 0xff) | static_cast<int>(value << 8);
break;
case 13:
@@ -262,15 +262,15 @@ uint8_t AY38910::get_data_output() {
}
void AY38910::set_control_lines(ControlLines control_lines) {
switch((int)control_lines) {
switch(static_cast<int>(control_lines)) {
default: control_state_ = Inactive; break;
case (int)(BDIR | BC2 | BC1):
case static_cast<int>(BDIR | BC2 | BC1):
case BDIR:
case BC1: control_state_ = LatchAddress; break;
case (int)(BC2 | BC1): control_state_ = Read; break;
case (int)(BDIR | BC2): control_state_ = Write; break;
case static_cast<int>(BC2 | BC1): control_state_ = Read; break;
case static_cast<int>(BDIR | BC2): control_state_ = Write; break;
}
update_bus();

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 */

View File

@@ -8,7 +8,7 @@
#include "AmstradCPC.hpp"
#include "CharacterMapper.hpp"
#include "Keyboard.hpp"
#include "../../Processors/Z80/Z80.hpp"
@@ -17,8 +17,8 @@
#include "../../Components/8272/i8272.hpp"
#include "../../Components/AY38910/AY38910.hpp"
#include "../MemoryFuzzer.hpp"
#include "../Typer.hpp"
#include "../Utility/MemoryFuzzer.hpp"
#include "../Utility/Typer.hpp"
#include "../../Storage/Tape/Tape.hpp"
@@ -35,8 +35,6 @@ namespace AmstradCPC {
*/
class InterruptTimer {
public:
InterruptTimer() : timer_(0), interrupt_request_(false) {}
/*!
Indicates that a new hsync pulse has been recognised. This should be
supplied on the falling edge of the CRTC HSYNC signal, which is the
@@ -93,10 +91,10 @@ class InterruptTimer {
}
private:
int reset_counter_;
bool interrupt_request_;
bool last_interrupt_request_;
int timer_;
int reset_counter_ = 0;
bool interrupt_request_ = false;
bool last_interrupt_request_ = false;
int timer_ = 0;
};
/*!
@@ -155,18 +153,8 @@ class AYDeferrer {
class CRTCBusHandler {
public:
CRTCBusHandler(uint8_t *ram, InterruptTimer &interrupt_timer) :
cycles_(0),
was_enabled_(false),
was_sync_(false),
pixel_data_(nullptr),
pixel_pointer_(nullptr),
was_hsync_(false),
ram_(ram),
interrupt_timer_(interrupt_timer),
pixel_divider_(1),
mode_(2),
next_mode_(2),
cycles_into_hsync_(0) {
interrupt_timer_(interrupt_timer) {
establish_palette_hits();
build_mode_table();
}
@@ -217,14 +205,14 @@ class CRTCBusHandler {
// collect some more pixels if output is ongoing
if(!is_sync && state.display_enable) {
if(!pixel_data_) {
pixel_pointer_ = pixel_data_ = crt_->allocate_write_area(320);
pixel_pointer_ = pixel_data_ = crt_->allocate_write_area(320, 8);
}
if(pixel_pointer_) {
// the CPC shuffles output lines as:
// MA13 MA12 RA2 RA1 RA0 MA9 MA8 MA7 MA6 MA5 MA4 MA3 MA2 MA1 MA0 CCLK
// ... so form the real access address.
uint16_t address =
(uint16_t)(
static_cast<uint16_t>(
((state.refresh_address & 0x3ff) << 1) |
((state.row_address & 0x7) << 11) |
((state.refresh_address & 0x3000) << 2)
@@ -233,26 +221,26 @@ class CRTCBusHandler {
// fetch two bytes and translate into pixels
switch(mode_) {
case 0:
((uint16_t *)pixel_pointer_)[0] = mode0_output_[ram_[address]];
((uint16_t *)pixel_pointer_)[1] = mode0_output_[ram_[address+1]];
reinterpret_cast<uint16_t *>(pixel_pointer_)[0] = mode0_output_[ram_[address]];
reinterpret_cast<uint16_t *>(pixel_pointer_)[1] = mode0_output_[ram_[address+1]];
pixel_pointer_ += 4;
break;
case 1:
((uint32_t *)pixel_pointer_)[0] = mode1_output_[ram_[address]];
((uint32_t *)pixel_pointer_)[1] = mode1_output_[ram_[address+1]];
reinterpret_cast<uint32_t *>(pixel_pointer_)[0] = mode1_output_[ram_[address]];
reinterpret_cast<uint32_t *>(pixel_pointer_)[1] = mode1_output_[ram_[address+1]];
pixel_pointer_ += 8;
break;
case 2:
((uint64_t *)pixel_pointer_)[0] = mode2_output_[ram_[address]];
((uint64_t *)pixel_pointer_)[1] = mode2_output_[ram_[address+1]];
reinterpret_cast<uint64_t *>(pixel_pointer_)[0] = mode2_output_[ram_[address]];
reinterpret_cast<uint64_t *>(pixel_pointer_)[1] = mode2_output_[ram_[address+1]];
pixel_pointer_ += 16;
break;
case 3:
((uint16_t *)pixel_pointer_)[0] = mode3_output_[ram_[address]];
((uint16_t *)pixel_pointer_)[1] = mode3_output_[ram_[address+1]];
reinterpret_cast<uint16_t *>(pixel_pointer_)[0] = mode3_output_[ram_[address]];
reinterpret_cast<uint16_t *>(pixel_pointer_)[1] = mode3_output_[ram_[address+1]];
pixel_pointer_ += 4;
break;
@@ -356,7 +344,7 @@ class CRTCBusHandler {
private:
void output_border(unsigned int length) {
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 = border_;
crt_->output_level(length * 16);
}
@@ -374,16 +362,16 @@ class CRTCBusHandler {
void establish_palette_hits() {
for(int c = 0; c < 256; c++) {
mode0_palette_hits_[Mode0Colour0(c)].push_back((uint8_t)c);
mode0_palette_hits_[Mode0Colour1(c)].push_back((uint8_t)c);
mode0_palette_hits_[Mode0Colour0(c)].push_back(static_cast<uint8_t>(c));
mode0_palette_hits_[Mode0Colour1(c)].push_back(static_cast<uint8_t>(c));
mode1_palette_hits_[Mode1Colour0(c)].push_back((uint8_t)c);
mode1_palette_hits_[Mode1Colour1(c)].push_back((uint8_t)c);
mode1_palette_hits_[Mode1Colour2(c)].push_back((uint8_t)c);
mode1_palette_hits_[Mode1Colour3(c)].push_back((uint8_t)c);
mode1_palette_hits_[Mode1Colour0(c)].push_back(static_cast<uint8_t>(c));
mode1_palette_hits_[Mode1Colour1(c)].push_back(static_cast<uint8_t>(c));
mode1_palette_hits_[Mode1Colour2(c)].push_back(static_cast<uint8_t>(c));
mode1_palette_hits_[Mode1Colour3(c)].push_back(static_cast<uint8_t>(c));
mode3_palette_hits_[Mode3Colour0(c)].push_back((uint8_t)c);
mode3_palette_hits_[Mode3Colour1(c)].push_back((uint8_t)c);
mode3_palette_hits_[Mode3Colour0(c)].push_back(static_cast<uint8_t>(c));
mode3_palette_hits_[Mode3Colour1(c)].push_back(static_cast<uint8_t>(c));
}
}
@@ -393,7 +381,7 @@ class CRTCBusHandler {
// Mode 0: abcdefgh -> [gcea] [hdfb]
for(int c = 0; c < 256; c++) {
// prepare mode 0
uint8_t *mode0_pixels = (uint8_t *)&mode0_output_[c];
uint8_t *mode0_pixels = reinterpret_cast<uint8_t *>(&mode0_output_[c]);
mode0_pixels[0] = palette_[Mode0Colour0(c)];
mode0_pixels[1] = palette_[Mode0Colour1(c)];
}
@@ -402,7 +390,7 @@ class CRTCBusHandler {
case 1:
for(int c = 0; c < 256; c++) {
// prepare mode 1
uint8_t *mode1_pixels = (uint8_t *)&mode1_output_[c];
uint8_t *mode1_pixels = reinterpret_cast<uint8_t *>(&mode1_output_[c]);
mode1_pixels[0] = palette_[Mode1Colour0(c)];
mode1_pixels[1] = palette_[Mode1Colour1(c)];
mode1_pixels[2] = palette_[Mode1Colour2(c)];
@@ -413,7 +401,7 @@ class CRTCBusHandler {
case 2:
for(int c = 0; c < 256; c++) {
// prepare mode 2
uint8_t *mode2_pixels = (uint8_t *)&mode2_output_[c];
uint8_t *mode2_pixels = reinterpret_cast<uint8_t *>(&mode2_output_[c]);
mode2_pixels[0] = palette_[((c & 0x80) >> 7)];
mode2_pixels[1] = palette_[((c & 0x40) >> 6)];
mode2_pixels[2] = palette_[((c & 0x20) >> 5)];
@@ -428,7 +416,7 @@ class CRTCBusHandler {
case 3:
for(int c = 0; c < 256; c++) {
// prepare mode 3
uint8_t *mode3_pixels = (uint8_t *)&mode3_output_[c];
uint8_t *mode3_pixels = reinterpret_cast<uint8_t *>(&mode3_output_[c]);
mode3_pixels[0] = palette_[Mode3Colour0(c)];
mode3_pixels[1] = palette_[Mode3Colour1(c)];
}
@@ -440,7 +428,7 @@ class CRTCBusHandler {
switch(mode_) {
case 0: {
for(uint8_t c : mode0_palette_hits_[pen]) {
uint8_t *mode0_pixels = (uint8_t *)&mode0_output_[c];
uint8_t *mode0_pixels = reinterpret_cast<uint8_t *>(&mode0_output_[c]);
mode0_pixels[0] = palette_[Mode0Colour0(c)];
mode0_pixels[1] = palette_[Mode0Colour1(c)];
}
@@ -448,7 +436,7 @@ class CRTCBusHandler {
case 1:
if(pen > 3) return;
for(uint8_t c : mode1_palette_hits_[pen]) {
uint8_t *mode1_pixels = (uint8_t *)&mode1_output_[c];
uint8_t *mode1_pixels = reinterpret_cast<uint8_t *>(&mode1_output_[c]);
mode1_pixels[0] = palette_[Mode1Colour0(c)];
mode1_pixels[1] = palette_[Mode1Colour1(c)];
mode1_pixels[2] = palette_[Mode1Colour2(c)];
@@ -465,7 +453,7 @@ class CRTCBusHandler {
if(pen > 3) return;
// Same argument applies here as to case 1, as the unused bits aren't masked out.
for(uint8_t c : mode3_palette_hits_[pen]) {
uint8_t *mode3_pixels = (uint8_t *)&mode3_output_[c];
uint8_t *mode3_pixels = reinterpret_cast<uint8_t *>(&mode3_output_[c]);
mode3_pixels[0] = palette_[Mode3Colour0(c)];
mode3_pixels[1] = palette_[Mode3Colour1(c)];
}
@@ -500,19 +488,19 @@ class CRTCBusHandler {
return mapping[colour];
}
unsigned int cycles_;
unsigned int cycles_ = 0;
bool was_enabled_, was_sync_, was_hsync_, was_vsync_;
int cycles_into_hsync_;
bool was_enabled_ = false, was_sync_ = false, was_hsync_ = false, was_vsync_ = false;
int cycles_into_hsync_ = 0;
std::shared_ptr<Outputs::CRT::CRT> crt_;
uint8_t *pixel_data_, *pixel_pointer_;
uint8_t *pixel_data_ = nullptr, *pixel_pointer_ = nullptr;
uint8_t *ram_;
uint8_t *ram_ = nullptr;
int next_mode_, mode_;
int next_mode_ = 2, mode_ = 2;
unsigned int pixel_divider_;
unsigned int pixel_divider_ = 1;
uint16_t mode0_output_[256];
uint32_t mode1_output_[256];
uint64_t mode2_output_[256];
@@ -522,9 +510,9 @@ class CRTCBusHandler {
std::vector<uint8_t> mode1_palette_hits_[4];
std::vector<uint8_t> mode3_palette_hits_[4];
int pen_;
int pen_ = 0;
uint8_t palette_[16];
uint8_t border_;
uint8_t border_ = 0;
InterruptTimer &interrupt_timer_;
};
@@ -582,12 +570,25 @@ class KeyboardState: public GI::AY38910::PortHandler {
class FDC: public Intel::i8272::i8272 {
private:
Intel::i8272::BusHandler bus_handler_;
std::shared_ptr<Storage::Disk::Drive> drive_;
public:
FDC() : i8272(bus_handler_, Cycles(8000000), 16, 300) {}
FDC() :
i8272(bus_handler_, Cycles(8000000)),
drive_(new Storage::Disk::Drive(8000000, 300, 1)) {
set_drive(drive_);
}
void set_motor_on(bool on) {
Intel::i8272::i8272::set_motor_on(on);
drive_->set_motor_on(on);
}
void select_drive(int c) {
// TODO: support more than one drive.
}
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
drive_->set_disk(disk);
}
};
@@ -919,7 +920,7 @@ class ConcreteMachine:
// See header; provides the system ROMs.
void set_rom(ROMType type, std::vector<uint8_t> data) override final {
roms_[(int)type] = data;
roms_[static_cast<int>(type)] = data;
}
void set_component_is_sleeping(void *component, bool is_sleeping) override final {
@@ -952,6 +953,10 @@ class ConcreteMachine:
key_state_.clear_all_keys();
}
KeyboardMapper &get_keyboard_mapper() override {
return keyboard_mapper_;
}
private:
inline void write_to_gate_array(uint8_t value) {
switch(value >> 6) {
@@ -1031,6 +1036,7 @@ class ConcreteMachine:
uint8_t *write_pointers_[4];
KeyboardState key_state_;
AmstradCPC::KeyboardMapper keyboard_mapper_;
};
}

View File

@@ -25,25 +25,6 @@ enum ROMType: int {
AMSDOS
};
enum Key: uint16_t {
#define Line(l, k1, k2, k3, k4, k5, k6, k7, k8) \
k1 = (l << 4) | 0x07, k2 = (l << 4) | 0x06, k3 = (l << 4) | 0x05, k4 = (l << 4) | 0x04,\
k5 = (l << 4) | 0x03, k6 = (l << 4) | 0x02, k7 = (l << 4) | 0x01, k8 = (l << 4) | 0x00,
Line(0, KeyFDot, KeyEnter, KeyF3, KeyF6, KeyF9, KeyDown, KeyRight, KeyUp)
Line(1, KeyF0, KeyF2, KeyF1, KeyF5, KeyF8, KeyF7, KeyCopy, KeyLeft)
Line(2, KeyControl, KeyBackSlash, KeyShift, KeyF4, KeyRightSquareBracket, KeyReturn, KeyLeftSquareBracket, KeyClear)
Line(3, KeyFullStop, KeyForwardSlash, KeyColon, KeySemicolon, KeyP, KeyAt, KeyMinus, KeyCaret)
Line(4, KeyComma, KeyM, KeyK, KeyL, KeyI, KeyO, Key9, Key0)
Line(5, KeySpace, KeyN, KeyJ, KeyH, KeyY, KeyU, Key7, Key8)
Line(6, KeyV, KeyB, KeyF, KeyG, KeyT, KeyR, Key5, Key6)
Line(7, KeyX, KeyC, KeyD, KeyS, KeyW, KeyE, Key3, Key4)
Line(8, KeyZ, KeyCapsLock, KeyA, KeyTab, KeyQ, KeyEscape, Key2, Key1)
Line(9, KeyDelete, KeyJoy1Fire3, KeyJoy2Fire2, KeyJoy1Fire1, KeyJoy1Right, KeyJoy1Left, KeyJoy1Down, KeyJoy1Up)
#undef Line
};
/*!
Models an Amstrad CPC.
*/

View File

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

View File

@@ -1,20 +1,83 @@
//
// CharacterMapper.cpp
// Keyboard.cpp
// Clock Signal
//
// Created by Thomas Harte on 11/08/2017.
// Created by Thomas Harte on 10/10/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "CharacterMapper.hpp"
#include "AmstradCPC.hpp"
#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__, EndSequence}
#define SHIFT(...) {KeyShift, __VA_ARGS__, EndSequence}
#define X {NotMapped}
#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,

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

@@ -30,6 +30,33 @@ namespace {
namespace Atari2600 {
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 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;
// 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;
}
}
private:
size_t shift_, fire_tia_input_;
Bus *bus_;
};
class ConcreteMachine:
public Machine,
public Outputs::CRT::Delegate {
@@ -44,7 +71,7 @@ class ConcreteMachine:
close_output();
}
void configure_as_target(const StaticAnalyser::Target &target) {
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;
@@ -79,34 +106,20 @@ class ConcreteMachine:
}
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) {
bool insert_media(const StaticAnalyser::Media &media) override {
return false;
}
void set_digital_input(Atari2600DigitalInput input, bool state) {
switch (input) {
case Atari2600DigitalInputJoy1Up: bus_->mos6532_.update_port_input(0, 0x10, state); break;
case Atari2600DigitalInputJoy1Down: bus_->mos6532_.update_port_input(0, 0x20, state); break;
case Atari2600DigitalInputJoy1Left: bus_->mos6532_.update_port_input(0, 0x40, state); break;
case Atari2600DigitalInputJoy1Right: bus_->mos6532_.update_port_input(0, 0x80, state); break;
case Atari2600DigitalInputJoy2Up: bus_->mos6532_.update_port_input(0, 0x01, state); break;
case Atari2600DigitalInputJoy2Down: bus_->mos6532_.update_port_input(0, 0x02, state); break;
case Atari2600DigitalInputJoy2Left: bus_->mos6532_.update_port_input(0, 0x04, state); break;
case Atari2600DigitalInputJoy2Right: bus_->mos6532_.update_port_input(0, 0x08, state); break;
// TODO: latching
case Atari2600DigitalInputJoy1Fire: if(state) bus_->tia_input_value_[0] &= ~0x80; else bus_->tia_input_value_[0] |= 0x80; break;
case Atari2600DigitalInputJoy2Fire: if(state) bus_->tia_input_value_[1] &= ~0x80; else bus_->tia_input_value_[1] |= 0x80; break;
default: break;
}
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
return joysticks_;
}
void set_switch_is_enabled(Atari2600Switch input, bool state) {
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;
@@ -116,36 +129,36 @@ class ConcreteMachine:
}
}
void set_reset_switch(bool state) {
void set_reset_switch(bool state) override {
bus_->set_reset_line(state);
}
// to satisfy CRTMachine::Machine
void setup_output(float aspect_ratio) {
void setup_output(float aspect_ratio) override {
bus_->tia_.reset(new TIA);
bus_->speaker_.reset(new Speaker);
bus_->speaker_->set_input_rate((float)(get_clock_rate() / (double)CPUTicksPerAudioTick));
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() {
void close_output() override {
bus_.reset();
}
std::shared_ptr<Outputs::CRT::CRT> get_crt() {
std::shared_ptr<Outputs::CRT::CRT> get_crt() override {
return bus_->tia_->get_crt();
}
std::shared_ptr<Outputs::Speaker> get_speaker() {
std::shared_ptr<Outputs::Speaker> get_speaker() override {
return bus_->speaker_;
}
void run_for(const Cycles cycles) {
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) {
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;
@@ -175,14 +188,13 @@ class ConcreteMachine:
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)));
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_;
@@ -196,6 +208,7 @@ class ConcreteMachine:
} frame_records_[4];
unsigned int frame_record_pointer_;
bool is_ntsc_;
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
};
}

View File

@@ -11,6 +11,7 @@
#include "../ConfigurationTarget.hpp"
#include "../CRTMachine.hpp"
#include "../JoystickMachine.hpp"
#include "Atari2600Inputs.h"
@@ -21,16 +22,14 @@ namespace Atari2600 {
*/
class Machine:
public CRTMachine::Machine,
public ConfigurationTarget::Machine {
public ConfigurationTarget::Machine,
public JoystickMachine::Machine {
public:
virtual ~Machine();
/// Creates and returns an Atari 2600 on the heap.
static Machine *Atari2600();
/// Sets @c input to @c state.
virtual void set_digital_input(Atari2600DigitalInput input, bool state) = 0;
/// Sets the switch @c input to @c state.
virtual void set_switch_is_enabled(Atari2600Switch input, bool state) = 0;

View File

@@ -39,7 +39,7 @@ class Pitfall2: public BusExtender {
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) ^
@@ -73,7 +73,7 @@ class Pitfall2: public BusExtender {
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;

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

@@ -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::BusHandler,
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,32 +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:
CPU::MOS6502::Processor<Machine, false> m6502_;
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,35 +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):
@@ -50,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);
@@ -64,51 +74,52 @@ 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) {
m6502_.run_for(cycles);
set_motor_on(drive_VIA_.get_motor_enabled());
if(drive_VIA_.get_motor_enabled()) // TODO: motor speed up/down
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
m6502_.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()) {
if(drive_VIA_port_handler_.get_should_set_overflow()) {
m6502_.set_overflow_line(true);
}
}
@@ -116,29 +127,29 @@ void Machine::process_input_bit(int value, unsigned int cycles_since_index_hole)
}
// 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) {
@@ -152,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;
@@ -159,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;
}
@@ -187,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_;
}
@@ -207,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,25 +0,0 @@
//
// CharacterMapper.hpp
// Clock Signal
//
// Created by Thomas Harte on 03/08/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef Machines_Commodore_Vic20_CharacterMapper_hpp
#define Machines_Commodore_Vic20_CharacterMapper_hpp
#include "../../Typer.hpp"
namespace Commodore {
namespace Vic20 {
class CharacterMapper: public ::Utility::CharacterMapper {
public:
uint16_t *sequence_for_character(char character);
};
}
}
#endif /* CharacterMapper_hpp */

View File

@@ -1,20 +1,80 @@
//
// CharacterMapper.cpp
// Keyboard.cpp
// Clock Signal
//
// Created by Thomas Harte on 03/08/2017.
// Created by Thomas Harte on 10/10/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "CharacterMapper.hpp"
#include "Vic20.hpp"
#include "Keyboard.hpp"
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__, EndSequence}
#define SHIFT(...) {KeyLShift, __VA_ARGS__, EndSequence}
#define X {NotMapped}
#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 */

View File

@@ -8,7 +8,7 @@
#include "Vic20.hpp"
#include "CharacterMapper.hpp"
#include "Keyboard.hpp"
#include "../../../Processors/6502/6502.hpp"
#include "../../../Components/6560/6560.hpp"
@@ -29,18 +29,25 @@
namespace Commodore {
namespace Vic20 {
enum JoystickInput {
Up = 0x04,
Down = 0x08,
Left = 0x10,
Right = 0x80,
Fire = 0x20
};
/*!
Models the user-port VIA, which is the Vic's connection point for controlling its tape recorder —
sensing the presence or absence of a tape and controlling the tape motor — and reading the current
state from its serial port. Most of the joystick input is also exposed here.
*/
class UserPortVIA: public MOS::MOS6522<UserPortVIA>, public MOS::MOS6522IRQDelegate {
class UserPortVIA: public MOS::MOS6522::IRQDelegatePortHandler {
public:
UserPortVIA() : port_a_(0xbf) {}
using MOS6522IRQDelegate::set_interrupt_status;
/// Reports the current input to the 6522 port @c port.
uint8_t get_port_input(Port port) {
uint8_t get_port_input(MOS::MOS6522::Port port) {
// Port A provides information about the presence or absence of a tape, and parts of
// the joystick and serial port state, both of which have been statefully collected
// into port_a_.
@@ -51,9 +58,9 @@ class UserPortVIA: public MOS::MOS6522<UserPortVIA>, public MOS::MOS6522IRQDeleg
}
/// Receives announcements of control line output change from the 6522.
void set_control_line_output(Port port, Line line, bool value) {
void set_control_line_output(MOS::MOS6522::Port port, MOS::MOS6522::Line line, bool value) {
// The CA2 output is used to control the tape motor.
if(port == Port::A && line == Line::Two) {
if(port == MOS::MOS6522::Port::A && line == MOS::MOS6522::Line::Two) {
tape_->set_motor_control(!value);
}
}
@@ -75,7 +82,7 @@ class UserPortVIA: public MOS::MOS6522<UserPortVIA>, public MOS::MOS6522IRQDeleg
}
/// Receives announcements from the 6522 of user-port output, which might affect what's currently being presented onto the serial bus.
void set_port_output(Port port, uint8_t value, uint8_t mask) {
void set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t mask) {
// Line 7 of port A is inverted and output as serial ATN.
if(!port) {
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
@@ -103,14 +110,12 @@ class UserPortVIA: public MOS::MOS6522<UserPortVIA>, public MOS::MOS6522IRQDeleg
Models the keyboard VIA, which is used by the Vic for reading its keyboard, to output to its serial port,
and for the small portion of joystick input not connected to the user-port VIA.
*/
class KeyboardVIA: public MOS::MOS6522<KeyboardVIA>, public MOS::MOS6522IRQDelegate {
class KeyboardVIA: public MOS::MOS6522::IRQDelegatePortHandler {
public:
KeyboardVIA() : port_b_(0xff) {
clear_all_keys();
}
using MOS6522IRQDelegate::set_interrupt_status;
/// Sets whether @c key @c is_pressed.
void set_key_state(uint16_t key, bool is_pressed) {
if(is_pressed)
@@ -125,7 +130,7 @@ class KeyboardVIA: public MOS::MOS6522<KeyboardVIA>, public MOS::MOS6522IRQDeleg
}
/// Called by the 6522 to get input. Reads the keyboard on Port A, returns a small amount of joystick state on Port B.
uint8_t get_port_input(Port port) {
uint8_t get_port_input(MOS::MOS6522::Port port) {
if(!port) {
uint8_t result = 0xff;
for(int c = 0; c < 8; c++) {
@@ -139,17 +144,17 @@ class KeyboardVIA: public MOS::MOS6522<KeyboardVIA>, public MOS::MOS6522IRQDeleg
}
/// Called by the 6522 to set output. The value of Port B selects which part of the keyboard to read.
void set_port_output(Port port, uint8_t value, uint8_t mask) {
void set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t mask) {
if(port) activation_mask_ = (value & mask) | (~mask);
}
/// Called by the 6522 to set control line output. Which affects the serial port.
void set_control_line_output(Port port, Line line, bool value) {
if(line == Line::Two) {
void set_control_line_output(MOS::MOS6522::Port port, MOS::MOS6522::Line line, bool value) {
if(line == MOS::MOS6522::Line::Two) {
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
if(serialPort) {
// CB2 is inverted to become serial data; CA2 is inverted to become serial clock
if(port == Port::A)
if(port == MOS::MOS6522::Port::A)
serialPort->set_output(::Commodore::Serial::Line::Clock, (::Commodore::Serial::LineLevel)!value);
else
serialPort->set_output(::Commodore::Serial::Line::Data, (::Commodore::Serial::LineLevel)!value);
@@ -212,9 +217,38 @@ class Vic6560: public MOS::MOS6560<Vic6560> {
uint8_t *colour_memory; // Colour memory must be contiguous.
};
/*!
Interfaces a joystick to the two VIAs.
*/
class Joystick: public Inputs::Joystick {
public:
Joystick(UserPortVIA &user_port_via_port_handler, KeyboardVIA &keyboard_via_port_handler) :
user_port_via_port_handler_(user_port_via_port_handler),
keyboard_via_port_handler_(keyboard_via_port_handler) {}
void set_digital_input(DigitalInput digital_input, bool is_active) override {
JoystickInput mapped_input;
switch (digital_input) {
default: return;
case DigitalInput::Up: mapped_input = Up; break;
case DigitalInput::Down: mapped_input = Down; break;
case DigitalInput::Left: mapped_input = Left; break;
case DigitalInput::Right: mapped_input = Right; break;
case DigitalInput::Fire: mapped_input = Fire; break;
}
user_port_via_port_handler_.set_joystick_state(mapped_input, is_active);
keyboard_via_port_handler_.set_joystick_state(mapped_input, is_active);
}
private:
UserPortVIA &user_port_via_port_handler_;
KeyboardVIA &keyboard_via_port_handler_;
};
class ConcreteMachine:
public CPU::MOS6502::BusHandler,
public MOS::MOS6522IRQDelegate::Delegate,
public MOS::MOS6522::IRQDelegatePortHandler::Delegate,
public Utility::TypeRecipient,
public Storage::Tape::BinaryTapePlayer::Delegate,
public Machine {
@@ -224,24 +258,26 @@ class ConcreteMachine:
rom_(nullptr),
is_running_at_zero_cost_(false),
tape_(new Storage::Tape::BinaryTapePlayer(1022727)),
user_port_via_(new UserPortVIA),
keyboard_via_(new KeyboardVIA),
user_port_via_port_handler_(new UserPortVIA),
keyboard_via_port_handler_(new KeyboardVIA),
serial_port_(new SerialPort),
serial_bus_(new ::Commodore::Serial::Bus) {
serial_bus_(new ::Commodore::Serial::Bus),
user_port_via_(*user_port_via_port_handler_),
keyboard_via_(*keyboard_via_port_handler_) {
// communicate the tape to the user-port VIA
user_port_via_->set_tape(tape_);
user_port_via_port_handler_->set_tape(tape_);
// wire up the serial bus and serial port
Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus_);
// wire up 6522s and serial port
user_port_via_->set_serial_port(serial_port_);
keyboard_via_->set_serial_port(serial_port_);
serial_port_->set_user_port_via(user_port_via_);
user_port_via_port_handler_->set_serial_port(serial_port_);
keyboard_via_port_handler_->set_serial_port(serial_port_);
serial_port_->set_user_port_via(user_port_via_port_handler_);
// wire up the 6522s, tape and machine
user_port_via_->set_interrupt_delegate(this);
keyboard_via_->set_interrupt_delegate(this);
user_port_via_port_handler_->set_interrupt_delegate(this);
keyboard_via_port_handler_->set_interrupt_delegate(this);
tape_->set_delegate(this);
// establish the memory maps
@@ -249,6 +285,9 @@ class ConcreteMachine:
// set the NTSC clock rate
set_region(NTSC);
// install a joystick
joysticks_.emplace_back(new Joystick(*user_port_via_port_handler_, *keyboard_via_port_handler_));
}
~ConcreteMachine() {
@@ -318,7 +357,7 @@ class ConcreteMachine:
if(!media.cartridges.empty()) {
rom_address_ = 0xa000;
std::vector<uint8_t> rom_image = media.cartridges.front()->get_segments().front().data;
rom_length_ = (uint16_t)(rom_image.size());
rom_length_ = static_cast<uint16_t>(rom_image.size());
rom_ = new uint8_t[0x2000];
memcpy(rom_, rom_image.data(), rom_image.size());
@@ -328,17 +367,19 @@ class ConcreteMachine:
return !media.tapes.empty() || (!media.disks.empty() && c1540_ != nullptr) || !media.cartridges.empty();
}
void set_key_state(uint16_t key, bool isPressed) override final {
keyboard_via_->set_key_state(key, isPressed);
void set_key_state(uint16_t key, bool is_pressed) override final {
if(key != KeyRestore)
keyboard_via_port_handler_->set_key_state(key, is_pressed);
else
user_port_via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !is_pressed);
}
void clear_all_keys() override final {
keyboard_via_->clear_all_keys();
keyboard_via_port_handler_->clear_all_keys();
}
void set_joystick_state(JoystickInput input, bool isPressed) override final {
user_port_via_->set_joystick_state(input, isPressed);
keyboard_via_->set_joystick_state(input, isPressed);
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
return joysticks_;
}
void set_memory_size(MemorySize size) override final {
@@ -409,8 +450,8 @@ class ConcreteMachine:
uint8_t result = processor_read_memory_map_[address >> 10] ? processor_read_memory_map_[address >> 10][address & 0x3ff] : 0xff;
if((address&0xfc00) == 0x9000) {
if((address&0xff00) == 0x9000) result &= mos6560_->get_register(address);
if((address&0xfc10) == 0x9010) result &= user_port_via_->get_register(address);
if((address&0xfc20) == 0x9020) result &= keyboard_via_->get_register(address);
if((address&0xfc10) == 0x9010) result &= user_port_via_.get_register(address);
if((address&0xfc20) == 0x9020) result &= keyboard_via_.get_register(address);
}
*value = result;
@@ -426,7 +467,7 @@ class ConcreteMachine:
std::unique_ptr<Storage::Tape::Commodore::Header> header = parser.get_next_header(tape_->get_tape());
// serialise to wherever b2:b3 points
uint16_t tape_buffer_pointer = (uint16_t)user_basic_memory_[0xb2] | (uint16_t)(user_basic_memory_[0xb3] << 8);
uint16_t tape_buffer_pointer = static_cast<uint16_t>(user_basic_memory_[0xb2]) | static_cast<uint16_t>(user_basic_memory_[0xb3] << 8);
if(header) {
header->serialise(&user_basic_memory_[tape_buffer_pointer], 0x8000 - tape_buffer_pointer);
} else {
@@ -440,13 +481,13 @@ class ConcreteMachine:
*value = 0x0c; // i.e. NOP abs
} else if(address == 0xf90b) {
uint8_t x = (uint8_t)m6502_.get_value_of_register(CPU::MOS6502::Register::X);
uint8_t x = static_cast<uint8_t>(m6502_.get_value_of_register(CPU::MOS6502::Register::X));
if(x == 0xe) {
Storage::Tape::Commodore::Parser parser;
std::unique_ptr<Storage::Tape::Commodore::Data> data = parser.get_next_data(tape_->get_tape());
uint16_t start_address, end_address;
start_address = (uint16_t)(user_basic_memory_[0xc1] | (user_basic_memory_[0xc2] << 8));
end_address = (uint16_t)(user_basic_memory_[0xae] | (user_basic_memory_[0xaf] << 8));
start_address = static_cast<uint16_t>(user_basic_memory_[0xc1] | (user_basic_memory_[0xc2] << 8));
end_address = static_cast<uint16_t>(user_basic_memory_[0xae] | (user_basic_memory_[0xaf] << 8));
// perform a via-processor_write_memory_map_ memcpy
uint8_t *data_ptr = data->data.data();
@@ -461,8 +502,8 @@ class ConcreteMachine:
// set tape status, carry and flag
user_basic_memory_[0x90] |= 0x40;
uint8_t flags = (uint8_t)m6502_.get_value_of_register(CPU::MOS6502::Register::Flags);
flags &= ~(uint8_t)(CPU::MOS6502::Flag::Carry | CPU::MOS6502::Flag::Interrupt);
uint8_t flags = static_cast<uint8_t>(m6502_.get_value_of_register(CPU::MOS6502::Register::Flags));
flags &= ~static_cast<uint8_t>((CPU::MOS6502::Flag::Carry | CPU::MOS6502::Flag::Interrupt));
m6502_.set_value_of_register(CPU::MOS6502::Register::Flags, flags);
// to ensure that execution proceeds to 0xfccf, pretend a NOP was here and
@@ -477,13 +518,13 @@ class ConcreteMachine:
if(ram) ram[address & 0x3ff] = *value;
if((address&0xfc00) == 0x9000) {
if((address&0xff00) == 0x9000) mos6560_->set_register(address, *value);
if((address&0xfc10) == 0x9010) user_port_via_->set_register(address, *value);
if((address&0xfc20) == 0x9020) keyboard_via_->set_register(address, *value);
if((address&0xfc10) == 0x9010) user_port_via_.set_register(address, *value);
if((address&0xfc20) == 0x9020) keyboard_via_.set_register(address, *value);
}
}
user_port_via_->run_for(Cycles(1));
keyboard_via_->run_for(Cycles(1));
user_port_via_.run_for(Cycles(1));
keyboard_via_.run_for(Cycles(1));
if(typer_ && operation == CPU::MOS6502::BusOperation::ReadOpcode && address == 0xEB1E) {
if(!typer_->type_next_character()) {
clear_all_keys();
@@ -529,8 +570,8 @@ class ConcreteMachine:
}
void mos6522_did_change_interrupt_status(void *mos6522) override final {
m6502_.set_nmi_line(user_port_via_->get_interrupt_line());
m6502_.set_irq_line(keyboard_via_->get_interrupt_line());
m6502_.set_nmi_line(user_port_via_.get_interrupt_line());
m6502_.set_irq_line(keyboard_via_.get_interrupt_line());
}
void set_typer_for_string(const char *string) override final {
@@ -539,7 +580,11 @@ class ConcreteMachine:
}
void tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape) override final {
keyboard_via_->set_control_line_input(KeyboardVIA::Port::A, KeyboardVIA::Line::One, !tape->get_input());
keyboard_via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !tape->get_input());
}
KeyboardMapper &get_keyboard_mapper() override {
return keyboard_mapper_;
}
private:
@@ -571,13 +616,18 @@ class ConcreteMachine:
}
Region region_;
Commodore::Vic20::KeyboardMapper keyboard_mapper_;
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
std::unique_ptr<Vic6560> mos6560_;
std::shared_ptr<UserPortVIA> user_port_via_;
std::shared_ptr<KeyboardVIA> keyboard_via_;
std::shared_ptr<UserPortVIA> user_port_via_port_handler_;
std::shared_ptr<KeyboardVIA> keyboard_via_port_handler_;
std::shared_ptr<SerialPort> serial_port_;
std::shared_ptr<::Commodore::Serial::Bus> serial_bus_;
MOS::MOS6522::MOS6522<UserPortVIA> user_port_via_;
MOS::MOS6522::MOS6522<KeyboardVIA> keyboard_via_;
// Tape
std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape_;
bool use_fast_tape_hack_;

View File

@@ -11,7 +11,8 @@
#include "../../ConfigurationTarget.hpp"
#include "../../CRTMachine.hpp"
#include "../../Typer.hpp"
#include "../../KeyboardMachine.hpp"
#include "../../JoystickMachine.hpp"
#include <cstdint>
@@ -36,39 +37,11 @@ enum Region {
PAL
};
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),
#undef key
};
enum JoystickInput {
Up = 0x04,
Down = 0x08,
Left = 0x10,
Right = 0x80,
Fire = 0x20
};
class Machine:
public ConfigurationTarget::Machine,
public CRTMachine::Machine,
public KeyboardMachine::Machine {
public ConfigurationTarget::Machine,
public KeyboardMachine::Machine,
public JoystickMachine::Machine {
public:
virtual ~Machine();
@@ -79,9 +52,6 @@ class Machine:
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.
/// Sets the state of joystick input @c input.
virtual void set_joystick_state(JoystickInput input, bool isPressed) = 0;
/// Sets the memory size of this Vic-20.
virtual void set_memory_size(MemorySize size) = 0;

View File

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

View File

@@ -13,10 +13,10 @@
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../ClockReceiver/ForceInline.hpp"
#include "../Typer.hpp"
#include "../Utility/Typer.hpp"
#include "CharacterMapper.hpp"
#include "Interrupts.hpp"
#include "Keyboard.hpp"
#include "Plus3.hpp"
#include "Speaker.hpp"
#include "Tape.hpp"
@@ -30,13 +30,7 @@ class ConcreteMachine:
public Tape::Delegate,
public Utility::TypeRecipient {
public:
ConcreteMachine() :
m6502_(*this),
interrupt_control_(0),
interrupt_status_(Interrupt::PowerOnReset | Interrupt::TransmitDataEmpty | 0x80),
cycles_since_audio_update_(0),
use_fast_tape_hack_(false),
cycles_until_display_interrupt_(0) {
ConcreteMachine() : m6502_(*this) {
memset(key_states_, 0, sizeof(key_states_));
for(int c = 0; c < 16; c++)
memset(roms_[c], 0xff, 16384);
@@ -58,7 +52,7 @@ class ConcreteMachine:
break;
}
memcpy(target, &data[0], std::min((size_t)16384, data.size()));
memcpy(target, &data[0], std::min(static_cast<size_t>(16384), data.size()));
}
void set_key_state(uint16_t key, bool isPressed) override final {
@@ -117,7 +111,7 @@ class ConcreteMachine:
ROMSlot slot = ROMSlot12;
for(std::shared_ptr<Storage::Cartridge::Cartridge> cartridge : media.cartridges) {
set_rom(slot, cartridge->get_segments().front().data, false);
slot = (ROMSlot)(((int)slot + 1)&15);
slot = static_cast<ROMSlot>((static_cast<int>(slot) + 1)&15);
}
return !media.tapes.empty() || !media.disks.empty() || !media.cartridges.empty();
@@ -263,7 +257,7 @@ class ConcreteMachine:
// allow the PC read to return an RTS.
)
) {
uint8_t service_call = (uint8_t)m6502_.get_value_of_register(CPU::MOS6502::Register::X);
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);
@@ -316,10 +310,10 @@ class ConcreteMachine:
}
}
cycles_since_display_update_ += Cycles((int)cycles);
cycles_since_audio_update_ += Cycles((int)cycles);
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((int)cycles));
tape_.run_for(Cycles(static_cast<int>(cycles)));
cycles_until_display_interrupt_ -= cycles;
if(cycles_until_display_interrupt_ < 0) {
@@ -328,8 +322,8 @@ class ConcreteMachine:
queue_next_display_interrupt();
}
if(typer_) typer_->run_for(Cycles((int)cycles));
if(plus3_) plus3_->run_for(Cycles(4*(int)cycles));
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) {
@@ -340,7 +334,7 @@ class ConcreteMachine:
}
}
return Cycles((int)cycles);
return Cycles(static_cast<int>(cycles));
}
forceinline void flush() {
@@ -393,6 +387,10 @@ class ConcreteMachine:
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) {
@@ -435,39 +433,42 @@ class ConcreteMachine:
// Things that directly constitute the memory map.
uint8_t roms_[16][16384];
bool rom_write_masks_[16];
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_;
bool keyboard_is_active_, basic_is_active_;
ROMSlot active_rom_ = ROMSlot::ROMSlot0;
bool keyboard_is_active_ = false;
bool basic_is_active_ = false;
// Interrupt and keyboard state
uint8_t interrupt_status_, interrupt_control_;
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_;
Cycles cycles_since_audio_update_;
int cycles_until_display_interrupt_;
Interrupt next_display_interrupt_;
VideoOutput::Range video_access_range_;
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_;
bool fast_load_is_in_data_;
bool use_fast_tape_hack_ = false;
bool fast_load_is_in_data_ = false;
// Disk
std::unique_ptr<Plus3> plus3_;
bool is_holding_shift_;
int shift_restart_counter_;
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_;
bool speaker_is_enabled_ = false;
};
}

View File

@@ -31,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.

View File

@@ -1,21 +1,65 @@
//
// CharacterMapper.cpp
// Keyboard.cpp
// Clock Signal
//
// Created by Thomas Harte on 03/08/2017.
// Created by Thomas Harte on 10/10/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "CharacterMapper.hpp"
#include "Electron.hpp"
#include "Keyboard.hpp"
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
}
uint16_t *CharacterMapper::sequence_for_character(char character) {
#define KEYS(...) {__VA_ARGS__, EndSequence}
#define SHIFT(...) {KeyShift, __VA_ARGS__, EndSequence}
#define CTRL(...) {KeyControl, __VA_ARGS__, EndSequence}
#define X {NotMapped}
#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,23 +0,0 @@
//
// CharacterMapper.hpp
// Clock Signal
//
// Created by Thomas Harte on 03/08/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef Machines_Oric_CharacterMapper_hpp
#define Machines_Oric_CharacterMapper_hpp
#include "../Typer.hpp"
namespace Oric {
class CharacterMapper: public ::Utility::CharacterMapper {
public:
uint16_t *sequence_for_character(char character);
};
}
#endif /* Machines_Oric_CharacterMapper_hpp */

View File

@@ -1,20 +1,60 @@
//
// CharacterMapper.cpp
// Keyboard.cpp
// Clock Signal
//
// Created by Thomas Harte on 03/08/2017.
// Created by Thomas Harte on 10/10/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "CharacterMapper.hpp"
#include "Oric.hpp"
#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__, EndSequence}
#define SHIFT(...) {KeyLeftShift, __VA_ARGS__, EndSequence}
#define X {NotMapped}
#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

@@ -8,12 +8,12 @@
#include "Oric.hpp"
#include "Video.hpp"
#include "Keyboard.hpp"
#include "Microdisc.hpp"
#include "CharacterMapper.hpp"
#include "Video.hpp"
#include "../MemoryFuzzer.hpp"
#include "../Typer.hpp"
#include "../Utility/MemoryFuzzer.hpp"
#include "../Utility/Typer.hpp"
#include "../../Processors/6502/6502.hpp"
#include "../../Components/6522/6522.hpp"
@@ -24,15 +24,156 @@
#include "../../ClockReceiver/ForceInline.hpp"
#include <cstdint>
#include <memory>
#include <vector>
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::MOS6522IRQDelegate::Delegate,
public MOS::MOS6522::IRQDelegatePortHandler::Delegate,
public Utility::TypeRecipient,
public Storage::Tape::BinaryTapePlayer::Delegate,
public Microdisc::Delegate,
@@ -41,18 +182,12 @@ class ConcreteMachine:
public:
ConcreteMachine() :
m6502_(*this),
use_fast_tape_hack_(false),
typer_delay_(2500000),
keyboard_read_count_(0),
keyboard_(new Keyboard),
ram_top_(0xbfff),
paged_rom_(rom_),
microdisc_is_enabled_(false) {
via_(via_port_handler_),
via_port_handler_(tape_player_, keyboard_) {
set_clock_rate(1000000);
via_.set_interrupt_delegate(this);
via_.keyboard = keyboard_;
clear_all_keys();
via_.tape->set_delegate(this);
via_port_handler_.set_interrupt_delegate(this);
tape_player_.set_delegate(this);
Memory::Fuzz(ram_, sizeof(ram_));
}
@@ -68,19 +203,16 @@ class ConcreteMachine:
}
}
void set_key_state(uint16_t key, bool isPressed) override final {
void set_key_state(uint16_t key, bool is_pressed) override final {
if(key == KeyNMI) {
m6502_.set_nmi_line(isPressed);
m6502_.set_nmi_line(is_pressed);
} else {
if(isPressed)
keyboard_->rows[key >> 8] |= (key & 0xff);
else
keyboard_->rows[key >> 8] &= ~(key & 0xff);
keyboard_.set_key_state(key, is_pressed);
}
}
void clear_all_keys() override final {
memset(keyboard_->rows, 0, sizeof(keyboard_->rows));
keyboard_.clear_all_keys();
}
void set_use_fast_tape_hack(bool activate) override final {
@@ -124,7 +256,7 @@ class ConcreteMachine:
bool insert_media(const StaticAnalyser::Media &media) override final {
if(media.tapes.size()) {
via_.tape->set_tape(media.tapes.front());
tape_player_.set_tape(media.tapes.front());
}
int drive_index = 0;
@@ -143,8 +275,14 @@ class ConcreteMachine:
// 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_]);
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
@@ -173,7 +311,7 @@ class ConcreteMachine:
if(isReadOperation(operation))
*value = ram_[address];
else {
if(address >= 0x9800 && address <= 0xc000) { update_video(); typer_delay_ = 0; }
if(address >= 0x9800 && address <= 0xc000) update_video();
ram_[address] = *value;
}
}
@@ -190,6 +328,8 @@ class ConcreteMachine:
}
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);
@@ -197,20 +337,23 @@ class ConcreteMachine:
forceinline void flush() {
update_video();
via_.flush();
via_port_handler_.flush();
}
// to satisfy CRTMachine::Machine
void setup_output(float aspect_ratio) override final {
via_.ay8910.reset(new GI::AY38910::AY38910());
via_.ay8910->set_clock_rate(1000000);
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();
via_.ay8910.reset();
ay8910_.reset();
via_port_handler_.set_ay(nullptr);
}
std::shared_ptr<Outputs::CRT::CRT> get_crt() override final {
@@ -218,7 +361,7 @@ class ConcreteMachine:
}
std::shared_ptr<Outputs::Speaker> get_speaker() override final {
return via_.ay8910;
return ay8910_;
}
void run_for(const Cycles cycles) override final {
@@ -233,7 +376,7 @@ class ConcreteMachine:
// to satisfy Storage::Tape::BinaryTapePlayer::Delegate
void tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape_player) override final {
// set CB1
via_.set_control_line_input(VIA::Port::B, VIA::Line::One, !tape_player->get_input());
via_.set_control_line_input(MOS::MOS6522::Port::B, MOS::MOS6522::Line::One, !tape_player->get_input());
}
// for Utility::TypeRecipient::Delegate
@@ -262,6 +405,10 @@ class ConcreteMachine:
set_interrupt_line();
}
KeyboardMapper &get_keyboard_mapper() override {
return keyboard_mapper_;
}
private:
CPU::MOS6502::Processor<ConcreteMachine, false> m6502_;
@@ -274,99 +421,29 @@ class ConcreteMachine:
}
// ROM bookkeeping
bool is_using_basic11_;
uint16_t tape_get_byte_address_, scan_keyboard_address_, tape_speed_address_;
int keyboard_read_count_;
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_;
// Keyboard
class Keyboard {
public:
uint8_t row;
uint8_t rows[8];
};
int typer_delay_;
// Inputs
Oric::KeyboardMapper keyboard_mapper_;
// The tape
class TapePlayer: public Storage::Tape::BinaryTapePlayer {
public:
TapePlayer() : Storage::Tape::BinaryTapePlayer(1000000) {}
TapePlayer tape_player_;
bool use_fast_tape_hack_ = false;
inline uint8_t get_next_byte(bool fast) {
return (uint8_t)parser_.get_next_byte(get_tape(), fast);
}
private:
Storage::Tape::Oric::Parser parser_;
};
bool use_fast_tape_hack_;
// VIA (which owns the tape and the AY)
class VIA: public MOS::MOS6522<VIA>, public MOS::MOS6522IRQDelegate {
public:
VIA() :
MOS::MOS6522<VIA>(),
tape(new TapePlayer) {}
using MOS6522IRQDelegate::set_interrupt_status;
void set_control_line_output(Port port, Line line, bool value) {
if(line) {
if(port) ay_bdir_ = value; else ay_bc1_ = value;
update_ay();
}
}
void set_port_output(Port port, uint8_t value, uint8_t direction_mask) {
if(port) {
keyboard->row = value;
tape->set_motor_control(value & 0x40);
} else {
ay8910->set_data_input(value);
}
}
uint8_t get_port_input(Port port) {
if(port) {
uint8_t column = ay8910->get_port_output(false) ^ 0xff;
return (keyboard->rows[keyboard->row & 7] & column) ? 0x08 : 0x00;
} else {
return ay8910->get_data_output();
}
}
inline void run_for(const Cycles cycles) {
cycles_since_ay_update_ += cycles;
MOS::MOS6522<VIA>::run_for(cycles);
tape->run_for(cycles);
}
void flush() {
ay8910->run_for(cycles_since_ay_update_.flush());
ay8910->flush();
}
std::shared_ptr<GI::AY38910::AY38910> ay8910;
std::unique_ptr<TapePlayer> tape;
std::shared_ptr<Keyboard> keyboard;
private:
void update_ay() {
ay8910->run_for(cycles_since_ay_update_.flush());
ay8910->set_control_lines( (GI::AY38910::ControlLines)((ay_bdir_ ? GI::AY38910::BDIR : 0) | (ay_bc1_ ? GI::AY38910::BC1 : 0) | GI::AY38910::BC2));
}
bool ay_bdir_, ay_bc1_;
Cycles cycles_since_ay_update_;
};
VIA via_;
std::shared_ptr<Keyboard> keyboard_;
VIAPortHandler via_port_handler_;
MOS::MOS6522::MOS6522<VIAPortHandler> via_;
Keyboard keyboard_;
// the Microdisc, if in use
class Microdisc microdisc_;
bool microdisc_is_enabled_;
uint16_t ram_top_;
bool microdisc_is_enabled_ = false;
uint16_t ram_top_ = 0xbfff;
uint8_t *paged_rom_;
inline void set_interrupt_line() {

View File

@@ -22,27 +22,6 @@ enum ROM {
BASIC10, BASIC11, Microdisc, Colour
};
enum Key: uint16_t {
Key3 = 0x0000 | 0x80, KeyX = 0x0000 | 0x40, Key1 = 0x0000 | 0x20,
KeyV = 0x0000 | 0x08, Key5 = 0x0000 | 0x04, KeyN = 0x0000 | 0x02, Key7 = 0x0000 | 0x01,
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,
};
/*!
Models an Oric 1/Atmos with or without a Microdisc.
*/

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

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

@@ -44,14 +44,14 @@ 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] == CharacterMapper::NotMapped) {
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_] != CharacterMapper::EndSequence;
return sequence[phase_] != KeyboardMachine::Machine::KeyEndSequence;
}
return true;
@@ -83,8 +83,8 @@ Typer::~Typer() {
#pragma mark - Character mapper
uint16_t *CharacterMapper::table_lookup_sequence_for_character(KeySequence *sequences, size_t length, char character) {
size_t ucharacter = (size_t)((unsigned 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];
}

View File

@@ -10,8 +10,8 @@
#define Typer_hpp
#include <memory>
#include "KeyboardMachine.hpp"
#include "../ClockReceiver/ClockReceiver.hpp"
#include "../KeyboardMachine.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
namespace Utility {
@@ -24,15 +24,6 @@ class CharacterMapper {
/// @returns The EndSequence-terminated sequence of keys that would cause @c character to be typed.
virtual uint16_t *sequence_for_character(char character) = 0;
/// Terminates a key sequence.
static const uint16_t EndSequence = 0xffff;
/*!
If returned as the first entry in a key sequence, indicates that the requested character
cannot be mapped.
*/
static const uint16_t NotMapped = 0xfffe;
protected:
typedef uint16_t KeySequence[16];

View File

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

View File

@@ -1,22 +1,44 @@
//
// CharacterMapper.cpp
// Keyboard.cpp
// Clock Signal
//
// Created by Thomas Harte on 03/08/2017.
// Created by Thomas Harte on 10/10/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "CharacterMapper.hpp"
#include "ZX8081.hpp"
#include "Keyboard.hpp"
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__, EndSequence}
#define SHIFT(...) {KeyShift, __VA_ARGS__, EndSequence}
#define X {NotMapped}
#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

@@ -14,10 +14,10 @@
#include "../../ClockReceiver/ForceInline.hpp"
#include "../MemoryFuzzer.hpp"
#include "../Typer.hpp"
#include "../Utility/MemoryFuzzer.hpp"
#include "../Utility/Typer.hpp"
#include "CharacterMapper.hpp"
#include "Keyboard.hpp"
#include "Video.hpp"
#include <memory>
@@ -135,7 +135,7 @@ template<bool is_zx81> class ConcreteMachine:
z80_.set_interrupt_line(false);
}
if(has_latched_video_byte_) {
size_t char_address = (size_t)((address & 0xfe00) | ((latched_video_byte_ & 0x3f) << 3) | line_counter_);
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;
@@ -155,7 +155,7 @@ template<bool is_zx81> class ConcreteMachine:
int next_byte = parser_.get_next_byte(tape_player_.get_tape());
if(next_byte != -1) {
uint16_t hl = z80_.get_value_of_register(CPU::Z80::Register::HL);
ram_[hl & ram_mask_] = (uint8_t)next_byte;
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);
@@ -249,7 +249,7 @@ template<bool is_zx81> class ConcreteMachine:
automatic_tape_motor_start_address_ = 0x0206;
automatic_tape_motor_end_address_ = 0x024d;
}
rom_mask_ = (uint16_t)(rom_.size() - 1);
rom_mask_ = static_cast<uint16_t>(rom_.size() - 1);
switch(target.zx8081.memory_model) {
case StaticAnalyser::ZX8081MemoryModel::Unexpanded:
@@ -301,9 +301,9 @@ template<bool is_zx81> class ConcreteMachine:
void set_key_state(uint16_t key, bool isPressed) override final {
if(isPressed)
key_states_[key >> 8] &= (uint8_t)(~key);
key_states_[key >> 8] &= static_cast<uint8_t>(~key);
else
key_states_[key >> 8] |= (uint8_t)key;
key_states_[key >> 8] |= static_cast<uint8_t>(key);
}
void clear_all_keys() override final {
@@ -331,6 +331,10 @@ template<bool is_zx81> class ConcreteMachine:
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_;
@@ -350,6 +354,7 @@ template<bool is_zx81> class ConcreteMachine:
int line_counter_;
uint8_t key_states_[8];
ZX8081::KeyboardMapper keyboard_mapper_;
HalfClockReceiver<Storage::Tape::BinaryTapePlayer> tape_player_;
Storage::Tape::ZX8081::Parser parser_;

View File

@@ -22,17 +22,6 @@ enum ROMType: uint8_t {
ZX80, ZX81
};
enum Key: uint16_t {
KeyShift = 0x0000 | 0x01, KeyZ = 0x0000 | 0x02, KeyX = 0x0000 | 0x04, KeyC = 0x0000 | 0x08, KeyV = 0x0000 | 0x10,
KeyA = 0x0100 | 0x01, KeyS = 0x0100 | 0x02, KeyD = 0x0100 | 0x04, KeyF = 0x0100 | 0x08, KeyG = 0x0100 | 0x10,
KeyQ = 0x0200 | 0x01, KeyW = 0x0200 | 0x02, KeyE = 0x0200 | 0x04, KeyR = 0x0200 | 0x08, KeyT = 0x0200 | 0x10,
Key1 = 0x0300 | 0x01, Key2 = 0x0300 | 0x02, Key3 = 0x0300 | 0x04, Key4 = 0x0300 | 0x08, Key5 = 0x0300 | 0x10,
Key0 = 0x0400 | 0x01, Key9 = 0x0400 | 0x02, Key8 = 0x0400 | 0x04, Key7 = 0x0400 | 0x08, Key6 = 0x0400 | 0x10,
KeyP = 0x0500 | 0x01, KeyO = 0x0500 | 0x02, KeyI = 0x0500 | 0x04, KeyU = 0x0500 | 0x08, KeyY = 0x0500 | 0x10,
KeyEnter = 0x0600 | 0x01, KeyL = 0x0600 | 0x02, KeyK = 0x0600 | 0x04, KeyJ = 0x0600 | 0x08, KeyH = 0x0600 | 0x10,
KeySpace = 0x0700 | 0x01, KeyDot = 0x0700 | 0x02, KeyM = 0x0700 | 0x04, KeyN = 0x0700 | 0x08, KeyB = 0x0700 | 0x10,
};
class Machine:
public CRTMachine::Machine,
public ConfigurationTarget::Machine,

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.

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

@@ -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,28 @@ 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()
}
}
}
@@ -177,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

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

@@ -35,6 +35,9 @@
- (void)setView:(CSOpenGLView *)view aspectRatio:(float)aspectRatio;
- (void)drawViewForPixelSize:(CGSize)pixelSize onlyIfDirty:(BOOL)onlyIfDirty;
- (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed;
- (void)clearAllKeys;
@property (nonatomic, strong) CSAudioQueue *audioQueue;
@property (nonatomic, readonly) CSOpenGLView *view;
@property (nonatomic, weak) id<CSMachineDelegate> delegate;

View File

@@ -10,8 +10,11 @@
#import "CSMachine+Subclassing.h"
#import "CSMachine+Target.h"
#include "KeyCodes.h"
#include "Typer.hpp"
#include "ConfigurationTarget.hpp"
#include "JoystickMachine.hpp"
#include "KeyboardMachine.hpp"
@interface CSMachine()
- (void)speaker:(Outputs::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length;
@@ -19,26 +22,38 @@
- (void)machineDidChangeClockIsUnlimited;
@end
struct SpeakerDelegate: public Outputs::Speaker::Delegate {
__weak CSMachine *machine;
struct LockProtectedDelegate {
// Contractual promise is: machine — the pointer **and** the object ** — may be accessed only
// in sections protected by the machineAccessLock;
NSLock *machineAccessLock;
__unsafe_unretained CSMachine *machine;
};
struct SpeakerDelegate: public Outputs::Speaker::Delegate, public LockProtectedDelegate {
void speaker_did_complete_samples(Outputs::Speaker *speaker, const std::vector<int16_t> &buffer) {
[machineAccessLock lock];
[machine speaker:speaker didCompleteSamples:buffer.data() length:(int)buffer.size()];
[machineAccessLock unlock];
}
};
struct MachineDelegate: CRTMachine::Machine::Delegate {
__weak CSMachine *machine;
struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDelegate {
void machine_did_change_clock_rate(CRTMachine::Machine *sender) {
[machineAccessLock lock];
[machine machineDidChangeClockRate];
[machineAccessLock unlock];
}
void machine_did_change_clock_is_unlimited(CRTMachine::Machine *sender) {
[machineAccessLock lock];
[machine machineDidChangeClockIsUnlimited];
[machineAccessLock unlock];
}
};
@implementation CSMachine {
SpeakerDelegate _speakerDelegate;
MachineDelegate _machineDelegate;
NSLock *_delegateMachineAccessLock;
CRTMachine::Machine *_machine;
}
@@ -46,8 +61,12 @@ struct MachineDelegate: CRTMachine::Machine::Delegate {
self = [super init];
if(self) {
_machine = (CRTMachine::Machine *)machine;
_delegateMachineAccessLock = [[NSLock alloc] init];
_machineDelegate.machine = self;
_speakerDelegate.machine = self;
_machineDelegate.machineAccessLock = _delegateMachineAccessLock;
_speakerDelegate.machineAccessLock = _delegateMachineAccessLock;
_machine->set_delegate(&_machineDelegate);
}
@@ -67,6 +86,17 @@ struct MachineDelegate: CRTMachine::Machine::Delegate {
}
- (void)dealloc {
// The two delegate's references to this machine are nilled out here because close_output may result
// in a data flush, which might cause an audio callback, which could cause the audio queue to decide
// that it's out of data, resulting in an attempt further to run the machine while it is dealloc'ing.
//
// They are nilled inside an explicit lock because that allows the delegates to protect their entire
// call into the machine, not just the pointer access.
[_delegateMachineAccessLock lock];
_machineDelegate.machine = nil;
_speakerDelegate.machine = nil;
[_delegateMachineAccessLock unlock];
[_view performWithGLContext:^{
@synchronized(self) {
_machine->close_output();
@@ -77,8 +107,7 @@ struct MachineDelegate: CRTMachine::Machine::Delegate {
- (float)idealSamplingRateFromRange:(NSRange)range {
@synchronized(self) {
std::shared_ptr<Outputs::Speaker> speaker = _machine->get_speaker();
if(speaker)
{
if(speaker) {
return speaker->get_ideal_clock_rate_in_range((float)range.location, (float)(range.location + range.length));
}
return 0;
@@ -94,8 +123,7 @@ struct MachineDelegate: CRTMachine::Machine::Delegate {
- (BOOL)setSpeakerDelegate:(Outputs::Speaker::Delegate *)delegate sampleRate:(float)sampleRate bufferSize:(NSUInteger)bufferSize {
@synchronized(self) {
std::shared_ptr<Outputs::Speaker> speaker = _machine->get_speaker();
if(speaker)
{
if(speaker) {
speaker->set_output_rate(sampleRate, (int)bufferSize);
speaker->set_delegate(delegate);
return YES;
@@ -158,4 +186,106 @@ struct MachineDelegate: CRTMachine::Machine::Delegate {
}
}
- (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed {
auto keyboard_machine = dynamic_cast<KeyboardMachine::Machine *>(_machine);
if(keyboard_machine) {
@synchronized(self) {
Inputs::Keyboard &keyboard = keyboard_machine->get_keyboard();
// Connect the Carbon-era Mac keyboard scancodes to Clock Signal's 'universal' enumeration in order
// to pass into the platform-neutral realm.
#define BIND(source, dest) case source: keyboard.set_key_pressed(Inputs::Keyboard::Key::dest, isPressed); break
switch(key) {
BIND(VK_ANSI_0, k0); BIND(VK_ANSI_1, k1); BIND(VK_ANSI_2, k2); BIND(VK_ANSI_3, k3); BIND(VK_ANSI_4, k4);
BIND(VK_ANSI_5, k5); BIND(VK_ANSI_6, k6); BIND(VK_ANSI_7, k7); BIND(VK_ANSI_8, k8); BIND(VK_ANSI_9, k9);
BIND(VK_ANSI_Q, Q); BIND(VK_ANSI_W, W); BIND(VK_ANSI_E, E); BIND(VK_ANSI_R, R); BIND(VK_ANSI_T, T);
BIND(VK_ANSI_Y, Y); BIND(VK_ANSI_U, U); BIND(VK_ANSI_I, I); BIND(VK_ANSI_O, O); BIND(VK_ANSI_P, P);
BIND(VK_ANSI_A, A); BIND(VK_ANSI_S, S); BIND(VK_ANSI_D, D); BIND(VK_ANSI_F, F); BIND(VK_ANSI_G, G);
BIND(VK_ANSI_H, H); BIND(VK_ANSI_J, J); BIND(VK_ANSI_K, K); BIND(VK_ANSI_L, L);
BIND(VK_ANSI_Z, Z); BIND(VK_ANSI_X, X); BIND(VK_ANSI_C, C); BIND(VK_ANSI_V, V);
BIND(VK_ANSI_B, B); BIND(VK_ANSI_N, N); BIND(VK_ANSI_M, M);
BIND(VK_F1, F1); BIND(VK_F2, F2); BIND(VK_F3, F3); BIND(VK_F4, F4);
BIND(VK_F5, F5); BIND(VK_F6, F6); BIND(VK_F7, F7); BIND(VK_F8, F8);
BIND(VK_F9, F9); BIND(VK_F10, F10); BIND(VK_F11, F11); BIND(VK_F12, F12);
BIND(VK_ANSI_Keypad0, KeyPad0); BIND(VK_ANSI_Keypad1, KeyPad1); BIND(VK_ANSI_Keypad2, KeyPad2);
BIND(VK_ANSI_Keypad3, KeyPad3); BIND(VK_ANSI_Keypad4, KeyPad4); BIND(VK_ANSI_Keypad5, KeyPad5);
BIND(VK_ANSI_Keypad6, KeyPad6); BIND(VK_ANSI_Keypad7, KeyPad7); BIND(VK_ANSI_Keypad8, KeyPad8);
BIND(VK_ANSI_Keypad9, KeyPad9);
BIND(VK_ANSI_Equal, Equals); BIND(VK_ANSI_Minus, Hyphen);
BIND(VK_ANSI_RightBracket, CloseSquareBracket); BIND(VK_ANSI_LeftBracket, OpenSquareBracket);
BIND(VK_ANSI_Quote, Quote); BIND(VK_ANSI_Grave, BackTick);
BIND(VK_ANSI_Semicolon, Semicolon);
BIND(VK_ANSI_Backslash, BackSlash); BIND(VK_ANSI_Slash, ForwardSlash);
BIND(VK_ANSI_Comma, Comma); BIND(VK_ANSI_Period, FullStop);
BIND(VK_ANSI_KeypadDecimal, KeyPadDecimalPoint); BIND(VK_ANSI_KeypadEquals, KeyPadEquals);
BIND(VK_ANSI_KeypadMultiply, KeyPadAsterisk); BIND(VK_ANSI_KeypadDivide, KeyPadSlash);
BIND(VK_ANSI_KeypadPlus, KeyPadPlus); BIND(VK_ANSI_KeypadMinus, KeyPadMinus);
BIND(VK_ANSI_KeypadClear, KeyPadDelete); BIND(VK_ANSI_KeypadEnter, KeyPadEnter);
BIND(VK_Return, Enter); BIND(VK_Tab, Tab);
BIND(VK_Space, Space); BIND(VK_Delete, BackSpace);
BIND(VK_Control, LeftControl); BIND(VK_Option, LeftOption);
BIND(VK_Command, LeftMeta); BIND(VK_Shift, LeftShift);
BIND(VK_RightControl, RightControl); BIND(VK_RightOption, RightOption);
BIND(VK_Escape, Escape); BIND(VK_CapsLock, CapsLock);
BIND(VK_Home, Home); BIND(VK_End, End);
BIND(VK_PageUp, PageUp); BIND(VK_PageDown, PageDown);
BIND(VK_RightShift, RightShift);
BIND(VK_Help, Help);
BIND(VK_ForwardDelete, Delete);
BIND(VK_LeftArrow, Left); BIND(VK_RightArrow, Right);
BIND(VK_DownArrow, Down); BIND(VK_UpArrow, Up);
}
#undef BIND
}
return;
}
auto joystick_machine = dynamic_cast<JoystickMachine::Machine *>(_machine);
if(joystick_machine) {
@synchronized(self) {
std::vector<std::unique_ptr<Inputs::Joystick>> &joysticks = joystick_machine->get_joysticks();
if(!joysticks.empty()) {
switch(key) {
case VK_LeftArrow: joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput::Left, isPressed); break;
case VK_RightArrow: joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput::Right, isPressed); break;
case VK_UpArrow: joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput::Up, isPressed); break;
case VK_DownArrow: joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput::Down, isPressed); break;
default:
joysticks[0]->set_digital_input(Inputs::Joystick::DigitalInput::Fire, isPressed); break;
break;
}
}
}
}
}
- (void)clearAllKeys {
auto keyboard_machine = dynamic_cast<KeyboardMachine::Machine *>(_machine);
if(keyboard_machine) {
@synchronized(self) {
keyboard_machine->get_keyboard().reset_all_keys();
}
}
auto joystick_machine = dynamic_cast<JoystickMachine::Machine *>(_machine);
if(joystick_machine) {
@synchronized(self) {
for(auto &joystick : joystick_machine->get_joysticks()) {
joystick->reset_all_inputs();
}
}
}
}
@end

View File

@@ -7,9 +7,8 @@
//
#import "CSMachine.h"
#import "CSKeyboardMachine.h"
@interface CSAmstradCPC : CSMachine <CSKeyboardMachine>
@interface CSAmstradCPC : CSMachine
- (instancetype)init;

View File

@@ -55,98 +55,4 @@
- (NSString *)userDefaultsPrefix { return @"amstradCPC"; }
#pragma mark - Keyboard Mapping
- (void)clearAllKeys {
@synchronized(self) {
_amstradCPC->clear_all_keys();
}
}
- (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed {
@synchronized(self) {
switch(key) {
case VK_ANSI_0: _amstradCPC->set_key_state(AmstradCPC::Key::Key0, isPressed); break;
case VK_ANSI_1: _amstradCPC->set_key_state(AmstradCPC::Key::Key1, isPressed); break;
case VK_ANSI_2: _amstradCPC->set_key_state(AmstradCPC::Key::Key2, isPressed); break;
case VK_ANSI_3: _amstradCPC->set_key_state(AmstradCPC::Key::Key3, isPressed); break;
case VK_ANSI_4: _amstradCPC->set_key_state(AmstradCPC::Key::Key4, isPressed); break;
case VK_ANSI_5: _amstradCPC->set_key_state(AmstradCPC::Key::Key5, isPressed); break;
case VK_ANSI_6: _amstradCPC->set_key_state(AmstradCPC::Key::Key6, isPressed); break;
case VK_ANSI_7: _amstradCPC->set_key_state(AmstradCPC::Key::Key7, isPressed); break;
case VK_ANSI_8: _amstradCPC->set_key_state(AmstradCPC::Key::Key8, isPressed); break;
case VK_ANSI_9: _amstradCPC->set_key_state(AmstradCPC::Key::Key9, isPressed); break;
case VK_ANSI_Q: _amstradCPC->set_key_state(AmstradCPC::Key::KeyQ, isPressed); break;
case VK_ANSI_W: _amstradCPC->set_key_state(AmstradCPC::Key::KeyW, isPressed); break;
case VK_ANSI_E: _amstradCPC->set_key_state(AmstradCPC::Key::KeyE, isPressed); break;
case VK_ANSI_R: _amstradCPC->set_key_state(AmstradCPC::Key::KeyR, isPressed); break;
case VK_ANSI_T: _amstradCPC->set_key_state(AmstradCPC::Key::KeyT, isPressed); break;
case VK_ANSI_Y: _amstradCPC->set_key_state(AmstradCPC::Key::KeyY, isPressed); break;
case VK_ANSI_U: _amstradCPC->set_key_state(AmstradCPC::Key::KeyU, isPressed); break;
case VK_ANSI_I: _amstradCPC->set_key_state(AmstradCPC::Key::KeyI, isPressed); break;
case VK_ANSI_O: _amstradCPC->set_key_state(AmstradCPC::Key::KeyO, isPressed); break;
case VK_ANSI_P: _amstradCPC->set_key_state(AmstradCPC::Key::KeyP, isPressed); break;
case VK_ANSI_A: _amstradCPC->set_key_state(AmstradCPC::Key::KeyA, isPressed); break;
case VK_ANSI_S: _amstradCPC->set_key_state(AmstradCPC::Key::KeyS, isPressed); break;
case VK_ANSI_D: _amstradCPC->set_key_state(AmstradCPC::Key::KeyD, isPressed); break;
case VK_ANSI_F: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF, isPressed); break;
case VK_ANSI_G: _amstradCPC->set_key_state(AmstradCPC::Key::KeyG, isPressed); break;
case VK_ANSI_H: _amstradCPC->set_key_state(AmstradCPC::Key::KeyH, isPressed); break;
case VK_ANSI_J: _amstradCPC->set_key_state(AmstradCPC::Key::KeyJ, isPressed); break;
case VK_ANSI_K: _amstradCPC->set_key_state(AmstradCPC::Key::KeyK, isPressed); break;
case VK_ANSI_L: _amstradCPC->set_key_state(AmstradCPC::Key::KeyL, isPressed); break;
case VK_ANSI_Z: _amstradCPC->set_key_state(AmstradCPC::Key::KeyZ, isPressed); break;
case VK_ANSI_X: _amstradCPC->set_key_state(AmstradCPC::Key::KeyX, isPressed); break;
case VK_ANSI_C: _amstradCPC->set_key_state(AmstradCPC::Key::KeyC, isPressed); break;
case VK_ANSI_V: _amstradCPC->set_key_state(AmstradCPC::Key::KeyV, isPressed); break;
case VK_ANSI_B: _amstradCPC->set_key_state(AmstradCPC::Key::KeyB, isPressed); break;
case VK_ANSI_N: _amstradCPC->set_key_state(AmstradCPC::Key::KeyN, isPressed); break;
case VK_ANSI_M: _amstradCPC->set_key_state(AmstradCPC::Key::KeyM, isPressed); break;
case VK_Space: _amstradCPC->set_key_state(AmstradCPC::Key::KeySpace, isPressed); break;
case VK_ANSI_Grave: _amstradCPC->set_key_state(AmstradCPC::Key::KeyCopy, isPressed); break;
case VK_Return: _amstradCPC->set_key_state(AmstradCPC::Key::KeyReturn, isPressed); break;
case VK_ANSI_Minus: _amstradCPC->set_key_state(AmstradCPC::Key::KeyMinus, isPressed); break;
case VK_RightArrow: _amstradCPC->set_key_state(AmstradCPC::Key::KeyRight, isPressed); break;
case VK_LeftArrow: _amstradCPC->set_key_state(AmstradCPC::Key::KeyLeft, isPressed); break;
case VK_DownArrow: _amstradCPC->set_key_state(AmstradCPC::Key::KeyDown, isPressed); break;
case VK_UpArrow: _amstradCPC->set_key_state(AmstradCPC::Key::KeyUp, isPressed); break;
case VK_Delete: _amstradCPC->set_key_state(AmstradCPC::Key::KeyDelete, isPressed); break;
case VK_Escape: _amstradCPC->set_key_state(AmstradCPC::Key::KeyEscape, isPressed); break;
case VK_ANSI_Comma: _amstradCPC->set_key_state(AmstradCPC::Key::KeyComma, isPressed); break;
case VK_ANSI_Period: _amstradCPC->set_key_state(AmstradCPC::Key::KeyFullStop, isPressed); break;
case VK_ANSI_Semicolon:
_amstradCPC->set_key_state(AmstradCPC::Key::KeySemicolon, isPressed); break;
case VK_ANSI_Quote: _amstradCPC->set_key_state(AmstradCPC::Key::KeyColon, isPressed); break;
case VK_ANSI_Slash: _amstradCPC->set_key_state(AmstradCPC::Key::KeyForwardSlash, isPressed); break;
case VK_ANSI_Backslash: _amstradCPC->set_key_state(AmstradCPC::Key::KeyBackSlash, isPressed); break;
case VK_Shift: _amstradCPC->set_key_state(AmstradCPC::Key::KeyShift, isPressed); break;
case VK_Control: _amstradCPC->set_key_state(AmstradCPC::Key::KeyControl, isPressed); break;
case VK_F1: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF1, isPressed); break;
case VK_F2: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF2, isPressed); break;
case VK_F3: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF3, isPressed); break;
case VK_F4: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF4, isPressed); break;
case VK_F5: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF5, isPressed); break;
case VK_F6: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF6, isPressed); break;
case VK_F7: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF7, isPressed); break;
case VK_F8: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF8, isPressed); break;
case VK_F9: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF9, isPressed); break;
case VK_F10: _amstradCPC->set_key_state(AmstradCPC::Key::KeyF0, isPressed); break;
case VK_F12: _amstradCPC->set_key_state(AmstradCPC::Key::KeyFDot, isPressed); break;
default:
// printf("%02x\n", key);
break;
}
}
}
@end

View File

@@ -8,9 +8,8 @@
#include "CSMachine.h"
#include "Atari2600Inputs.h"
#import "CSJoystickMachine.h"
@interface CSAtari2600 : CSMachine <CSJoystickMachine>
@interface CSAtari2600 : CSMachine
- (instancetype)init;

View File

@@ -24,26 +24,6 @@
return self;
}
- (void)setDirection:(CSJoystickDirection)direction onPad:(NSUInteger)pad isPressed:(BOOL)isPressed {
Atari2600DigitalInput input;
switch(direction)
{
case CSJoystickDirectionUp: input = pad ? Atari2600DigitalInputJoy2Up : Atari2600DigitalInputJoy1Up; break;
case CSJoystickDirectionDown: input = pad ? Atari2600DigitalInputJoy2Down : Atari2600DigitalInputJoy1Down; break;
case CSJoystickDirectionLeft: input = pad ? Atari2600DigitalInputJoy2Left : Atari2600DigitalInputJoy1Left; break;
case CSJoystickDirectionRight: input = pad ? Atari2600DigitalInputJoy2Right : Atari2600DigitalInputJoy1Right; break;
}
@synchronized(self) {
_atari2600->set_digital_input(input, isPressed ? true : false);
}
}
- (void)setButtonAtIndex:(NSUInteger)button onPad:(NSUInteger)pad isPressed:(BOOL)isPressed {
@synchronized(self) {
_atari2600->set_digital_input(pad ? Atari2600DigitalInputJoy2Fire : Atari2600DigitalInputJoy1Fire, isPressed ? true : false);
}
}
- (void)setResetLineEnabled:(BOOL)enabled {
@synchronized(self) {
_atari2600->set_reset_switch(enabled ? true : false);

View File

@@ -7,10 +7,9 @@
//
#import "CSMachine.h"
#import "CSKeyboardMachine.h"
#import "CSFastLoading.h"
@interface CSElectron : CSMachine <CSKeyboardMachine, CSFastLoading>
@interface CSElectron : CSMachine <CSFastLoading>
- (instancetype)init;

View File

@@ -57,94 +57,6 @@
}
}
#pragma mark - Keyboard Mapping
- (void)clearAllKeys {
@synchronized(self) {
_electron->clear_all_keys();
}
}
- (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed {
@synchronized(self) {
switch(key)
{
case VK_ANSI_0: _electron->set_key_state(Electron::Key::Key0, isPressed); break;
case VK_ANSI_1: _electron->set_key_state(Electron::Key::Key1, isPressed); break;
case VK_ANSI_2: _electron->set_key_state(Electron::Key::Key2, isPressed); break;
case VK_ANSI_3: _electron->set_key_state(Electron::Key::Key3, isPressed); break;
case VK_ANSI_4: _electron->set_key_state(Electron::Key::Key4, isPressed); break;
case VK_ANSI_5: _electron->set_key_state(Electron::Key::Key5, isPressed); break;
case VK_ANSI_6: _electron->set_key_state(Electron::Key::Key6, isPressed); break;
case VK_ANSI_7: _electron->set_key_state(Electron::Key::Key7, isPressed); break;
case VK_ANSI_8: _electron->set_key_state(Electron::Key::Key8, isPressed); break;
case VK_ANSI_9: _electron->set_key_state(Electron::Key::Key9, isPressed); break;
case VK_ANSI_Q: _electron->set_key_state(Electron::Key::KeyQ, isPressed); break;
case VK_ANSI_W: _electron->set_key_state(Electron::Key::KeyW, isPressed); break;
case VK_ANSI_E: _electron->set_key_state(Electron::Key::KeyE, isPressed); break;
case VK_ANSI_R: _electron->set_key_state(Electron::Key::KeyR, isPressed); break;
case VK_ANSI_T: _electron->set_key_state(Electron::Key::KeyT, isPressed); break;
case VK_ANSI_Y: _electron->set_key_state(Electron::Key::KeyY, isPressed); break;
case VK_ANSI_U: _electron->set_key_state(Electron::Key::KeyU, isPressed); break;
case VK_ANSI_I: _electron->set_key_state(Electron::Key::KeyI, isPressed); break;
case VK_ANSI_O: _electron->set_key_state(Electron::Key::KeyO, isPressed); break;
case VK_ANSI_P: _electron->set_key_state(Electron::Key::KeyP, isPressed); break;
case VK_ANSI_A: _electron->set_key_state(Electron::Key::KeyA, isPressed); break;
case VK_ANSI_S: _electron->set_key_state(Electron::Key::KeyS, isPressed); break;
case VK_ANSI_D: _electron->set_key_state(Electron::Key::KeyD, isPressed); break;
case VK_ANSI_F: _electron->set_key_state(Electron::Key::KeyF, isPressed); break;
case VK_ANSI_G: _electron->set_key_state(Electron::Key::KeyG, isPressed); break;
case VK_ANSI_H: _electron->set_key_state(Electron::Key::KeyH, isPressed); break;
case VK_ANSI_J: _electron->set_key_state(Electron::Key::KeyJ, isPressed); break;
case VK_ANSI_K: _electron->set_key_state(Electron::Key::KeyK, isPressed); break;
case VK_ANSI_L: _electron->set_key_state(Electron::Key::KeyL, isPressed); break;
case VK_ANSI_Z: _electron->set_key_state(Electron::Key::KeyZ, isPressed); break;
case VK_ANSI_X: _electron->set_key_state(Electron::Key::KeyX, isPressed); break;
case VK_ANSI_C: _electron->set_key_state(Electron::Key::KeyC, isPressed); break;
case VK_ANSI_V: _electron->set_key_state(Electron::Key::KeyV, isPressed); break;
case VK_ANSI_B: _electron->set_key_state(Electron::Key::KeyB, isPressed); break;
case VK_ANSI_N: _electron->set_key_state(Electron::Key::KeyN, isPressed); break;
case VK_ANSI_M: _electron->set_key_state(Electron::Key::KeyM, isPressed); break;
case VK_Space: _electron->set_key_state(Electron::Key::KeySpace, isPressed); break;
case VK_ANSI_Grave:
case VK_ANSI_Backslash:
_electron->set_key_state(Electron::Key::KeyCopy, isPressed); break;
case VK_Return: _electron->set_key_state(Electron::Key::KeyReturn, isPressed); break;
case VK_ANSI_Minus: _electron->set_key_state(Electron::Key::KeyMinus, isPressed); break;
case VK_RightArrow: _electron->set_key_state(Electron::Key::KeyRight, isPressed); break;
case VK_LeftArrow: _electron->set_key_state(Electron::Key::KeyLeft, isPressed); break;
case VK_DownArrow: _electron->set_key_state(Electron::Key::KeyDown, isPressed); break;
case VK_UpArrow: _electron->set_key_state(Electron::Key::KeyUp, isPressed); break;
case VK_Delete: _electron->set_key_state(Electron::Key::KeyDelete, isPressed); break;
case VK_Escape: _electron->set_key_state(Electron::Key::KeyEscape, isPressed); break;
case VK_ANSI_Comma: _electron->set_key_state(Electron::Key::KeyComma, isPressed); break;
case VK_ANSI_Period: _electron->set_key_state(Electron::Key::KeyFullStop, isPressed); break;
case VK_ANSI_Semicolon:
_electron->set_key_state(Electron::Key::KeySemiColon, isPressed); break;
case VK_ANSI_Quote: _electron->set_key_state(Electron::Key::KeyColon, isPressed); break;
case VK_ANSI_Slash: _electron->set_key_state(Electron::Key::KeySlash, isPressed); break;
case VK_Shift: _electron->set_key_state(Electron::Key::KeyShift, isPressed); break;
case VK_Control: _electron->set_key_state(Electron::Key::KeyControl, isPressed); break;
case VK_Command:
case VK_Option: _electron->set_key_state(Electron::Key::KeyFunc, isPressed); break;
case VK_F12: _electron->set_key_state(Electron::Key::KeyBreak, isPressed); break;
default:
// printf("%02x\n", key);
break;
}
}
}
- (NSString *)userDefaultsPrefix { return @"electron"; }
#pragma mark - Options

View File

@@ -7,10 +7,9 @@
//
#import "CSMachine.h"
#import "CSKeyboardMachine.h"
#import "CSFastLoading.h"
@interface CSOric : CSMachine <CSKeyboardMachine, CSFastLoading>
@interface CSOric : CSMachine <CSFastLoading>
- (instancetype)init;

View File

@@ -43,99 +43,6 @@
return [[NSBundle mainBundle] dataForResource:name withExtension:@"rom" subdirectory:@"ROMImages/Oric"];
}
#pragma mark - CSKeyboardMachine
- (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed {
@synchronized(self) {
switch(key) {
case VK_ANSI_0: _oric->set_key_state(Oric::Key::Key0, isPressed); break;
case VK_ANSI_1: _oric->set_key_state(Oric::Key::Key1, isPressed); break;
case VK_ANSI_2: _oric->set_key_state(Oric::Key::Key2, isPressed); break;
case VK_ANSI_3: _oric->set_key_state(Oric::Key::Key3, isPressed); break;
case VK_ANSI_4: _oric->set_key_state(Oric::Key::Key4, isPressed); break;
case VK_ANSI_5: _oric->set_key_state(Oric::Key::Key5, isPressed); break;
case VK_ANSI_6: _oric->set_key_state(Oric::Key::Key6, isPressed); break;
case VK_ANSI_7: _oric->set_key_state(Oric::Key::Key7, isPressed); break;
case VK_ANSI_8: _oric->set_key_state(Oric::Key::Key8, isPressed); break;
case VK_ANSI_9: _oric->set_key_state(Oric::Key::Key9, isPressed); break;
case VK_ANSI_Q: _oric->set_key_state(Oric::Key::KeyQ, isPressed); break;
case VK_ANSI_W: _oric->set_key_state(Oric::Key::KeyW, isPressed); break;
case VK_ANSI_E: _oric->set_key_state(Oric::Key::KeyE, isPressed); break;
case VK_ANSI_R: _oric->set_key_state(Oric::Key::KeyR, isPressed); break;
case VK_ANSI_T: _oric->set_key_state(Oric::Key::KeyT, isPressed); break;
case VK_ANSI_Y: _oric->set_key_state(Oric::Key::KeyY, isPressed); break;
case VK_ANSI_U: _oric->set_key_state(Oric::Key::KeyU, isPressed); break;
case VK_ANSI_I: _oric->set_key_state(Oric::Key::KeyI, isPressed); break;
case VK_ANSI_O: _oric->set_key_state(Oric::Key::KeyO, isPressed); break;
case VK_ANSI_P: _oric->set_key_state(Oric::Key::KeyP, isPressed); break;
case VK_ANSI_A: _oric->set_key_state(Oric::Key::KeyA, isPressed); break;
case VK_ANSI_S: _oric->set_key_state(Oric::Key::KeyS, isPressed); break;
case VK_ANSI_D: _oric->set_key_state(Oric::Key::KeyD, isPressed); break;
case VK_ANSI_F: _oric->set_key_state(Oric::Key::KeyF, isPressed); break;
case VK_ANSI_G: _oric->set_key_state(Oric::Key::KeyG, isPressed); break;
case VK_ANSI_H: _oric->set_key_state(Oric::Key::KeyH, isPressed); break;
case VK_ANSI_J: _oric->set_key_state(Oric::Key::KeyJ, isPressed); break;
case VK_ANSI_K: _oric->set_key_state(Oric::Key::KeyK, isPressed); break;
case VK_ANSI_L: _oric->set_key_state(Oric::Key::KeyL, isPressed); break;
case VK_ANSI_Z: _oric->set_key_state(Oric::Key::KeyZ, isPressed); break;
case VK_ANSI_X: _oric->set_key_state(Oric::Key::KeyX, isPressed); break;
case VK_ANSI_C: _oric->set_key_state(Oric::Key::KeyC, isPressed); break;
case VK_ANSI_V: _oric->set_key_state(Oric::Key::KeyV, isPressed); break;
case VK_ANSI_B: _oric->set_key_state(Oric::Key::KeyB, isPressed); break;
case VK_ANSI_N: _oric->set_key_state(Oric::Key::KeyN, isPressed); break;
case VK_ANSI_M: _oric->set_key_state(Oric::Key::KeyM, isPressed); break;
case VK_Space: _oric->set_key_state(Oric::Key::KeySpace, isPressed); break;
case VK_Return: _oric->set_key_state(Oric::Key::KeyReturn, isPressed); break;
case VK_ANSI_Minus: _oric->set_key_state(Oric::Key::KeyMinus, isPressed); break;
case VK_ANSI_Equal: _oric->set_key_state(Oric::Key::KeyEquals, isPressed); break;
case VK_ANSI_Backslash:
_oric->set_key_state(Oric::Key::KeyBackSlash, isPressed); break;
case VK_ANSI_Slash: _oric->set_key_state(Oric::Key::KeyForwardSlash, isPressed); break;
case VK_ANSI_LeftBracket:
_oric->set_key_state(Oric::Key::KeyOpenSquare, isPressed); break;
case VK_ANSI_RightBracket:
_oric->set_key_state(Oric::Key::KeyCloseSquare, isPressed); break;
case VK_ANSI_Quote: _oric->set_key_state(Oric::Key::KeyQuote, isPressed); break;
case VK_RightArrow: _oric->set_key_state(Oric::Key::KeyRight, isPressed); break;
case VK_LeftArrow: _oric->set_key_state(Oric::Key::KeyLeft, isPressed); break;
case VK_DownArrow: _oric->set_key_state(Oric::Key::KeyDown, isPressed); break;
case VK_UpArrow: _oric->set_key_state(Oric::Key::KeyUp, isPressed); break;
case VK_Delete: _oric->set_key_state(Oric::Key::KeyDelete, isPressed); break;
case VK_Escape: _oric->set_key_state(Oric::Key::KeyEscape, isPressed); break;
case VK_ANSI_Comma: _oric->set_key_state(Oric::Key::KeyComma, isPressed); break;
case VK_ANSI_Period: _oric->set_key_state(Oric::Key::KeyFullStop, isPressed); break;
case VK_ANSI_Semicolon: _oric->set_key_state(Oric::Key::KeySemiColon, isPressed); break;
case VK_Shift:
_oric->set_key_state(Oric::Key::KeyLeftShift, isPressed);
_oric->set_key_state(Oric::Key::KeyRightShift, isPressed);
break;
case VK_RightShift: _oric->set_key_state(Oric::Key::KeyRightShift, isPressed); break;
case VK_Control: _oric->set_key_state(Oric::Key::KeyControl, isPressed); break;
case VK_ANSI_Grave:
case VK_F12: _oric->set_key_state(Oric::Key::KeyNMI, isPressed); break;
default:
printf("%02x\n", key);
break;
}
}
}
- (void)clearAllKeys {
@synchronized(self) {
_oric->clear_all_keys();
}
}
#pragma mark - Options
- (void)setUseFastLoadingHack:(BOOL)useFastLoadingHack {

View File

@@ -7,7 +7,6 @@
//
#import "CSMachine.h"
#import "CSKeyboardMachine.h"
#import "CSFastLoading.h"
typedef NS_ENUM(NSInteger, CSVic20Country)
@@ -26,7 +25,7 @@ typedef NS_ENUM(NSInteger, CSVic20MemorySize)
CSVic20MemorySize32Kb,
};
@interface CSVic20 : CSMachine <CSKeyboardMachine, CSFastLoading>
@interface CSVic20 : CSMachine <CSFastLoading>
- (instancetype)init;

View File

@@ -68,101 +68,15 @@ using namespace Commodore::Vic20;
#pragma mark - Keyboard map
- (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed {
static NSDictionary<NSNumber *, NSNumber *> *vicKeysByKeys = @{
@(VK_ANSI_1): @(Key::Key1), @(VK_ANSI_2): @(Key::Key2),
@(VK_ANSI_3): @(Key::Key3), @(VK_ANSI_4): @(Key::Key4),
@(VK_ANSI_5): @(Key::Key5), @(VK_ANSI_6): @(Key::Key6),
@(VK_ANSI_7): @(Key::Key7), @(VK_ANSI_8): @(Key::Key8),
@(VK_ANSI_9): @(Key::Key9), @(VK_ANSI_0): @(Key::Key0),
@(VK_ANSI_Q): @(Key::KeyQ), @(VK_ANSI_W): @(Key::KeyW),
@(VK_ANSI_E): @(Key::KeyE), @(VK_ANSI_R): @(Key::KeyR),
@(VK_ANSI_T): @(Key::KeyT), @(VK_ANSI_Y): @(Key::KeyY),
@(VK_ANSI_U): @(Key::KeyU), @(VK_ANSI_I): @(Key::KeyI),
@(VK_ANSI_O): @(Key::KeyO), @(VK_ANSI_P): @(Key::KeyP),
@(VK_ANSI_A): @(Key::KeyA), @(VK_ANSI_S): @(Key::KeyS),
@(VK_ANSI_D): @(Key::KeyD), @(VK_ANSI_F): @(Key::KeyF),
@(VK_ANSI_G): @(Key::KeyG), @(VK_ANSI_H): @(Key::KeyH),
@(VK_ANSI_J): @(Key::KeyJ), @(VK_ANSI_K): @(Key::KeyK),
@(VK_ANSI_L): @(Key::KeyL), @(VK_ANSI_Z): @(Key::KeyZ),
@(VK_ANSI_X): @(Key::KeyX), @(VK_ANSI_C): @(Key::KeyC),
@(VK_ANSI_V): @(Key::KeyV), @(VK_ANSI_B): @(Key::KeyB),
@(VK_ANSI_N): @(Key::KeyN), @(VK_ANSI_M): @(Key::KeyM),
@(VK_Space): @(Key::KeySpace),
@(VK_Return): @(Key::KeyReturn),
@(VK_Delete): @(Key::KeyDelete),
@(VK_ANSI_Comma): @(Key::KeyComma),
@(VK_ANSI_Period): @(Key::KeyFullStop),
@(VK_ANSI_Minus): @(Key::KeyDash),
@(VK_ANSI_Equal): @(Key::KeyEquals),
@(VK_ANSI_Semicolon): @(Key::KeyColon),
@(VK_ANSI_Quote): @(Key::KeySemicolon),
@(VK_ANSI_Slash): @(Key::KeySlash),
@(VK_Option): @(Key::KeyCBM),
@(VK_Control): @(Key::KeyControl),
@(VK_F1): @(Key::KeyF1), @(VK_F3): @(Key::KeyF3),
@(VK_F5): @(Key::KeyF5), @(VK_F7): @(Key::KeyF7),
@(VK_ANSI_Grave): @(Key::KeyLeft),
@(VK_Tab): @(Key::KeyRunStop),
@(VK_ANSI_LeftBracket): @(Key::KeyAt),
@(VK_ANSI_RightBracket): @(Key::KeyAsterisk),
@(VK_ANSI_Backslash): @(Key::KeyUp),
@(VK_RightArrow): @(Key::KeyRight),
@(VK_DownArrow): @(Key::KeyDown),
};
// Not yet mapped:
// KeyHome
// KeyPlus
// KeyGBP
if(key == VK_Tab && isPressed) {
_joystickMode ^= YES;
}
@synchronized(self) {
if(_joystickMode) {
switch(key) {
case VK_UpArrow: _vic20->set_joystick_state(JoystickInput::Up, isPressed); break;
case VK_DownArrow: _vic20->set_joystick_state(JoystickInput::Down, isPressed); break;
case VK_LeftArrow: _vic20->set_joystick_state(JoystickInput::Left, isPressed); break;
case VK_RightArrow: _vic20->set_joystick_state(JoystickInput::Right, isPressed); break;
case VK_ANSI_A: _vic20->set_joystick_state(JoystickInput::Fire, isPressed); break;
}
} else {
switch(key) {
default: {
NSNumber *targetKey = vicKeysByKeys[@(key)];
if(targetKey)
{
_vic20->set_key_state((Key)targetKey.integerValue, isPressed);
}
else
{
NSLog(@"Unmapped: %02x", key);
}
} break;
case VK_Shift:
// Yuck
_vic20->set_key_state(Key::KeyLShift, isPressed);
_vic20->set_key_state(Key::KeyRShift, isPressed);
break;
}
/*- (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed {
switch(key) {
case VK_UpArrow: _vic20->set_joystick_state(JoystickInput::Up, isPressed); break;
case VK_DownArrow: _vic20->set_joystick_state(JoystickInput::Down, isPressed); break;
case VK_LeftArrow: _vic20->set_joystick_state(JoystickInput::Left, isPressed); break;
case VK_RightArrow: _vic20->set_joystick_state(JoystickInput::Right, isPressed); break;
case VK_ANSI_A: _vic20->set_joystick_state(JoystickInput::Fire, isPressed); break;
}
}
}
- (void)clearAllKeys {
@synchronized(self) {
_vic20->clear_all_keys();
}
}
}*/
#pragma mark - Public configuration options

View File

@@ -7,10 +7,9 @@
//
#import "CSMachine.h"
#import "CSKeyboardMachine.h"
#import "CSFastLoading.h"
@interface CSZX8081 : CSMachine <CSKeyboardMachine, CSFastLoading>
@interface CSZX8081 : CSMachine <CSFastLoading>
@property (nonatomic, assign) BOOL useFastLoadingHack;

View File

@@ -34,69 +34,6 @@
return [[NSBundle mainBundle] dataForResource:name withExtension:@"rom" subdirectory:@"ROMImages/ZX8081"];
}
#pragma mark - Keyboard Mapping
- (void)clearAllKeys {
@synchronized(self) {
_zx8081->clear_all_keys();
}
}
- (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed {
@synchronized(self) {
switch(key) {
case VK_ANSI_0: _zx8081->set_key_state(ZX8081::Key::Key0, isPressed); break;
case VK_ANSI_1: _zx8081->set_key_state(ZX8081::Key::Key1, isPressed); break;
case VK_ANSI_2: _zx8081->set_key_state(ZX8081::Key::Key2, isPressed); break;
case VK_ANSI_3: _zx8081->set_key_state(ZX8081::Key::Key3, isPressed); break;
case VK_ANSI_4: _zx8081->set_key_state(ZX8081::Key::Key4, isPressed); break;
case VK_ANSI_5: _zx8081->set_key_state(ZX8081::Key::Key5, isPressed); break;
case VK_ANSI_6: _zx8081->set_key_state(ZX8081::Key::Key6, isPressed); break;
case VK_ANSI_7: _zx8081->set_key_state(ZX8081::Key::Key7, isPressed); break;
case VK_ANSI_8: _zx8081->set_key_state(ZX8081::Key::Key8, isPressed); break;
case VK_ANSI_9: _zx8081->set_key_state(ZX8081::Key::Key9, isPressed); break;
case VK_ANSI_Q: _zx8081->set_key_state(ZX8081::Key::KeyQ, isPressed); break;
case VK_ANSI_W: _zx8081->set_key_state(ZX8081::Key::KeyW, isPressed); break;
case VK_ANSI_E: _zx8081->set_key_state(ZX8081::Key::KeyE, isPressed); break;
case VK_ANSI_R: _zx8081->set_key_state(ZX8081::Key::KeyR, isPressed); break;
case VK_ANSI_T: _zx8081->set_key_state(ZX8081::Key::KeyT, isPressed); break;
case VK_ANSI_Y: _zx8081->set_key_state(ZX8081::Key::KeyY, isPressed); break;
case VK_ANSI_U: _zx8081->set_key_state(ZX8081::Key::KeyU, isPressed); break;
case VK_ANSI_I: _zx8081->set_key_state(ZX8081::Key::KeyI, isPressed); break;
case VK_ANSI_O: _zx8081->set_key_state(ZX8081::Key::KeyO, isPressed); break;
case VK_ANSI_P: _zx8081->set_key_state(ZX8081::Key::KeyP, isPressed); break;
case VK_ANSI_A: _zx8081->set_key_state(ZX8081::Key::KeyA, isPressed); break;
case VK_ANSI_S: _zx8081->set_key_state(ZX8081::Key::KeyS, isPressed); break;
case VK_ANSI_D: _zx8081->set_key_state(ZX8081::Key::KeyD, isPressed); break;
case VK_ANSI_F: _zx8081->set_key_state(ZX8081::Key::KeyF, isPressed); break;
case VK_ANSI_G: _zx8081->set_key_state(ZX8081::Key::KeyG, isPressed); break;
case VK_ANSI_H: _zx8081->set_key_state(ZX8081::Key::KeyH, isPressed); break;
case VK_ANSI_J: _zx8081->set_key_state(ZX8081::Key::KeyJ, isPressed); break;
case VK_ANSI_K: _zx8081->set_key_state(ZX8081::Key::KeyK, isPressed); break;
case VK_ANSI_L: _zx8081->set_key_state(ZX8081::Key::KeyL, isPressed); break;
case VK_ANSI_Z: _zx8081->set_key_state(ZX8081::Key::KeyZ, isPressed); break;
case VK_ANSI_X: _zx8081->set_key_state(ZX8081::Key::KeyX, isPressed); break;
case VK_ANSI_C: _zx8081->set_key_state(ZX8081::Key::KeyC, isPressed); break;
case VK_ANSI_V: _zx8081->set_key_state(ZX8081::Key::KeyV, isPressed); break;
case VK_ANSI_B: _zx8081->set_key_state(ZX8081::Key::KeyB, isPressed); break;
case VK_ANSI_N: _zx8081->set_key_state(ZX8081::Key::KeyN, isPressed); break;
case VK_ANSI_M: _zx8081->set_key_state(ZX8081::Key::KeyM, isPressed); break;
case VK_Shift:
case VK_RightShift:
_zx8081->set_key_state(ZX8081::Key::KeyShift, isPressed); break;
break;
case VK_ANSI_Period:_zx8081->set_key_state(ZX8081::Key::KeyDot, isPressed); break;
case VK_Return: _zx8081->set_key_state(ZX8081::Key::KeyEnter, isPressed); break;
case VK_Space: _zx8081->set_key_state(ZX8081::Key::KeySpace, isPressed); break;
}
}
}
- (NSString *)userDefaultsPrefix { return @"zx8081"; }
#pragma mark - Options

View File

@@ -1,75 +0,0 @@
//
// CSBestEffortUpdater.m
// Clock Signal
//
// Created by Thomas Harte on 16/06/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#import "CSBestEffortUpdater.h"
#include <stdatomic.h>
@implementation CSBestEffortUpdater
{
// these are inherently handled only by thread-safe constructions
atomic_flag _updateIsOngoing;
dispatch_queue_t _serialDispatchQueue;
// these are permitted for modification on _serialDispatchQueue only
NSTimeInterval _previousTimeInterval;
NSTimeInterval _cyclesError;
BOOL _hasSkipped;
}
- (instancetype)init
{
if(self = [super init])
{
_serialDispatchQueue = dispatch_queue_create("Best Effort Updater", DISPATCH_QUEUE_SERIAL);
// This is a workaround for assigning the correct initial value within Objective-C's form.
atomic_flag initialFlagValue = ATOMIC_FLAG_INIT;
_updateIsOngoing = initialFlagValue;
}
return self;
}
- (void)update
{
// Always post an -openGLView:didUpdateToTime: if a previous one isn't still ongoing. This is the hook upon which the substantial processing occurs.
if(!atomic_flag_test_and_set(&_updateIsOngoing))
{
dispatch_async(_serialDispatchQueue, ^{
NSTimeInterval timeInterval = [NSDate timeIntervalSinceReferenceDate];
if(_previousTimeInterval > DBL_EPSILON && timeInterval > _previousTimeInterval)
{
NSTimeInterval timeToRunFor = timeInterval - _previousTimeInterval;
double cyclesToRunFor = timeToRunFor * self.clockRate + _cyclesError;
_cyclesError = fmod(cyclesToRunFor, 1.0);
NSUInteger integerCyclesToRunFor = (NSUInteger)MIN(cyclesToRunFor, self.clockRate * 0.5);
// treat 'unlimited' as running at a factor of 10
if(self.runAsUnlimited) integerCyclesToRunFor *= 10;
[self.delegate bestEffortUpdater:self runForCycles:integerCyclesToRunFor didSkipPreviousUpdate:_hasSkipped];
}
_previousTimeInterval = timeInterval;
_hasSkipped = NO;
atomic_flag_clear(&_updateIsOngoing);
});
}
else
{
dispatch_async(_serialDispatchQueue, ^{
_hasSkipped = YES;
});
}
}
- (void)flush
{
dispatch_sync(_serialDispatchQueue, ^{});
}
@end

View File

@@ -0,0 +1,73 @@
//
// CSBestEffortUpdater.m
// Clock Signal
//
// Created by Thomas Harte on 16/06/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#import "CSBestEffortUpdater.h"
#include "BestEffortUpdater.hpp"
struct UpdaterDelegate: public Concurrency::BestEffortUpdater::Delegate {
__weak id<CSBestEffortUpdaterDelegate> delegate;
NSLock *delegateLock;
void update(Concurrency::BestEffortUpdater *updater, int cycles, bool did_skip_previous_update) {
[delegateLock lock];
[delegate bestEffortUpdater:nil runForCycles:(NSUInteger)cycles didSkipPreviousUpdate:did_skip_previous_update];
[delegateLock unlock];
}
};
@implementation CSBestEffortUpdater {
Concurrency::BestEffortUpdater _updater;
UpdaterDelegate _updaterDelegate;
NSLock *_delegateLock;
}
- (instancetype)init {
self = [super init];
if(self) {
_delegateLock = [[NSLock alloc] init];
_updaterDelegate.delegateLock = _delegateLock;
_updater.set_delegate(&_updaterDelegate);
}
return self;
}
- (void)dealloc {
_updater.flush();
}
- (void)update {
_updater.update();
}
- (void)flush {
_updater.flush();
}
- (void)setClockRate:(double)clockRate {
_clockRate = clockRate;
_updater.set_clock_rate(clockRate);
}
- (void)setDelegate:(id<CSBestEffortUpdaterDelegate>)delegate {
[_delegateLock lock];
_updaterDelegate.delegate = delegate;
[_delegateLock unlock];
}
- (id<CSBestEffortUpdaterDelegate>)delegate {
id<CSBestEffortUpdaterDelegate> delegate;
[_delegateLock lock];
delegate = _updaterDelegate.delegate;
[_delegateLock unlock];
return delegate;
}
@end

View File

@@ -15,6 +15,7 @@
@implementation CSOpenGLView {
CVDisplayLinkRef _displayLink;
CGSize _backingSize;
}
- (void)prepareOpenGL
@@ -68,12 +69,17 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
- (CGSize)backingSize
{
return [self convertSizeToBacking:self.bounds.size];
@synchronized(self) {
return _backingSize;
}
}
- (void)reshape
{
[super reshape];
@synchronized(self) {
_backingSize = [self convertSizeToBacking:self.bounds.size];
}
[self performWithGLContext:^{
CGSize viewSize = [self backingSize];

View File

@@ -7,11 +7,13 @@
//
#import "MOS6522Bridge.h"
#include "6522.hpp"
#include <memory>
@class MOS6522Bridge;
class VanillaVIA: public MOS::MOS6522<VanillaVIA> {
class VanillaVIAPortHandler: public MOS::MOS6522::PortHandler {
public:
MOS6522Bridge *bridge;
bool irq_line;
@@ -22,53 +24,55 @@ class VanillaVIA: public MOS::MOS6522<VanillaVIA> {
irq_line = new_status;
}
uint8_t get_port_input(Port port) {
uint8_t get_port_input(MOS::MOS6522::Port port) {
return port ? port_b_value : port_a_value;
}
};
@implementation MOS6522Bridge {
VanillaVIA _via;
VanillaVIAPortHandler _viaPortHandler;
std::unique_ptr<MOS::MOS6522::MOS6522<VanillaVIAPortHandler>> _via;
}
- (instancetype)init {
self = [super init];
if(self) {
_via.bridge = self;
_via.reset(new MOS::MOS6522::MOS6522<VanillaVIAPortHandler>(_viaPortHandler));
_viaPortHandler.bridge = self;
}
return self;
}
- (void)setValue:(uint8_t)value forRegister:(NSUInteger)registerNumber {
_via.set_register((int)registerNumber, value);
_via->set_register((int)registerNumber, value);
}
- (uint8_t)valueForRegister:(NSUInteger)registerNumber {
return _via.get_register((int)registerNumber);
return _via->get_register((int)registerNumber);
}
- (void)runForHalfCycles:(NSUInteger)numberOfHalfCycles {
_via.run_for(HalfCycles((int)numberOfHalfCycles));
_via->run_for(HalfCycles((int)numberOfHalfCycles));
}
- (BOOL)irqLine {
return _via.irq_line;
return _viaPortHandler.irq_line;
}
- (void)setPortAInput:(uint8_t)portAInput {
_via.port_a_value = portAInput;
_viaPortHandler.port_a_value = portAInput;
}
- (uint8_t)portAInput {
return _via.port_a_value;
return _viaPortHandler.port_a_value;
}
- (void)setPortBInput:(uint8_t)portBInput {
_via.port_b_value = portBInput;
_viaPortHandler.port_b_value = portBInput;
}
- (uint8_t)portBInput {
return _via.port_b_value;
return _viaPortHandler.port_b_value;
}
@end

View File

@@ -59,24 +59,24 @@ fileprivate struct RegisterState {
init(dictionary: [String: Any]) {
// don't test bits 3 and 5 for now
af = UInt16(dictionary["af"] as! NSNumber)
bc = UInt16(dictionary["bc"] as! NSNumber)
de = UInt16(dictionary["de"] as! NSNumber)
hl = UInt16(dictionary["hl"] as! NSNumber)
af = UInt16(truncating: dictionary["af"] as! NSNumber)
bc = UInt16(truncating: dictionary["bc"] as! NSNumber)
de = UInt16(truncating: dictionary["de"] as! NSNumber)
hl = UInt16(truncating: dictionary["hl"] as! NSNumber)
afDash = UInt16(dictionary["afDash"] as! NSNumber)
bcDash = UInt16(dictionary["bcDash"] as! NSNumber)
deDash = UInt16(dictionary["deDash"] as! NSNumber)
hlDash = UInt16(dictionary["hlDash"] as! NSNumber)
afDash = UInt16(truncating: dictionary["afDash"] as! NSNumber)
bcDash = UInt16(truncating: dictionary["bcDash"] as! NSNumber)
deDash = UInt16(truncating: dictionary["deDash"] as! NSNumber)
hlDash = UInt16(truncating: dictionary["hlDash"] as! NSNumber)
ix = UInt16(dictionary["ix"] as! NSNumber)
iy = UInt16(dictionary["iy"] as! NSNumber)
ix = UInt16(truncating: dictionary["ix"] as! NSNumber)
iy = UInt16(truncating: dictionary["iy"] as! NSNumber)
sp = UInt16(dictionary["sp"] as! NSNumber)
pc = UInt16(dictionary["pc"] as! NSNumber)
sp = UInt16(truncating: dictionary["sp"] as! NSNumber)
pc = UInt16(truncating: dictionary["pc"] as! NSNumber)
i = UInt8(dictionary["i"] as! NSNumber)
r = UInt8(dictionary["r"] as! NSNumber)
i = UInt8(truncating: dictionary["i"] as! NSNumber)
r = UInt8(truncating: dictionary["r"] as! NSNumber)
iff1 = (dictionary["iff1"] as! NSNumber).boolValue
iff2 = (dictionary["iff2"] as! NSNumber).boolValue
@@ -183,10 +183,10 @@ class FUSETests: XCTestCase {
if let inputMemoryGroups = inputMemoryGroups {
for group in inputMemoryGroups {
let groupDictionary = group as! [String: Any]
var address = UInt16(groupDictionary["address"] as! NSNumber)
var address = UInt16(truncating: groupDictionary["address"] as! NSNumber)
let data = groupDictionary["data"] as! [NSNumber]
for value in data {
machine.setValue(UInt8(value), atAddress: address)
machine.setValue(UInt8(truncating: value), atAddress: address)
address = address + 1
}
}
@@ -208,10 +208,10 @@ class FUSETests: XCTestCase {
if let outputMemoryGroups = outputMemoryGroups {
for group in outputMemoryGroups {
let groupDictionary = group as! [String: Any]
var address = UInt16(groupDictionary["address"] as! NSNumber)
var address = UInt16(truncating: groupDictionary["address"] as! NSNumber)
let data = groupDictionary["data"] as! [NSNumber]
for value in data {
XCTAssert(machine.value(atAddress: address) == UInt8(value), "Failed memory state \(name)")
XCTAssert(machine.value(atAddress: address) == UInt8(truncating: value), "Failed memory state \(name)")
address = address + 1
}
}

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