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

Compare commits

...

432 Commits

Author SHA1 Message Date
Thomas Harte
1d821ad459 Corrected name of build tool. 2017-11-22 20:11:02 -05:00
Thomas Harte
c60a9ee3c3 Merge pull request #298 from TomHarte/ReadMeUpdates
Provided exposition of new platform support.
2017-11-22 17:08:58 -08:00
Thomas Harte
ffcbd1e94d Provided exposition of new platform support. 2017-11-22 20:08:07 -05:00
Thomas Harte
6c8b503402 Merge pull request #297 from TomHarte/Instructions
Adds build instructions and references the special SDL key combinations.
2017-11-22 17:04:01 -08:00
Thomas Harte
55e1d25966 Adds build instructions and references the special SDL key combinations. 2017-11-22 20:03:28 -05:00
Thomas Harte
0bdd776114 Merge pull request #296 from TomHarte/SDLAudioRejig
Switches to using the supply-on-demand audio route through SDL.
2017-11-22 16:45:01 -08:00
Thomas Harte
c1b7bceec8 Switches to using the supply-on-demand audio route through SDL.
This gives an additional hook from which machine updates can be hooked, so separates that buffer size from any implicit frame rate assumptions.
2017-11-22 19:36:39 -05:00
Thomas Harte
dc4f58e40c Hides the mouse cursor when in SDL fullscreen mode. 2017-11-21 21:52:32 -05:00
Thomas Harte
3b8cdd620c Merge pull request #295 from TomHarte/SDLPaste
Adds acceptance of paste and fullscreen toggle to SDL target.
2017-11-21 18:50:17 -08:00
Thomas Harte
3365ff0200 Adds type recipient as a dynamic type, and accepts paste and fullscreen toggle in SDL. 2017-11-21 21:44:29 -05:00
Thomas Harte
89c3e2ba5a Merge pull request #294 from TomHarte/Vic20Startup
Corrects application Vic-20 startup issues.
2017-11-21 18:26:16 -08:00
Thomas Harte
c6306db47c Ensures the 6560 is fully initialised by setup_output. 2017-11-21 21:24:06 -05:00
Thomas Harte
8ddc64c82a Ensures well-defined default speaker clock rate values. 2017-11-21 21:18:58 -05:00
Thomas Harte
b887cb7255 Merge pull request #293 from TomHarte/ROMExposition
Adds user-facing information about which ROMs a machine attempted to load if it fails.
2017-11-21 16:24:23 -08:00
Thomas Harte
d54ee2af82 Adds user-facing information about which ROMs a machine attempted to load if it fails. 2017-11-21 19:22:33 -05:00
Thomas Harte
723c113186 Merge pull request #292 from TomHarte/Help
Introduces command-line help and reduces code duplicity in those options.
2017-11-20 19:01:06 -08:00
Thomas Harte
c368c4443e Improves both internal and external exposition for the SDL version. 2017-11-20 21:59:53 -05:00
Thomas Harte
7b25b03cd5 Formally standardises machine options and introduces a --help option for the SDL target. 2017-11-20 21:55:32 -05:00
Thomas Harte
9961d13e2d Merge pull request #290 from TomHarte/DragAndDrop
Adds drag and drop receivership to the SDL target.
2017-11-19 15:21:25 -08:00
Thomas Harte
29b5ccc767 Removes redundant logging on the Mac. 2017-11-19 18:05:39 -05:00
Thomas Harte
90af395df2 Adds support for receiving dragged and dropped files under SDL. 2017-11-19 18:05:31 -05:00
Thomas Harte
6f8d4d6c5c Merge pull request #282 from TomHarte/BooleanSelections
Boolean selections
2017-11-18 18:16:33 -08:00
Thomas Harte
63381ff505 Fixes accidental typographic quote in SConstruct. 2017-11-18 21:13:55 -05:00
Thomas Harte
2ea050556b Adds transcoding of ostensible list selections to Boolean selections, and vice versa. 2017-11-18 21:09:26 -05:00
Thomas Harte
8dcac6561e Merge pull request #281 from TomHarte/MachineOptions
Introduces reflective machine options and a command-line parser for them.
2017-11-18 17:03:53 -08:00
Thomas Harte
90d33949f9 Adds a mapping of backspace for the Electron. 2017-11-18 20:02:04 -05:00
Thomas Harte
d3e68914dd Removes uninteresting logging. 2017-11-18 20:00:40 -05:00
Thomas Harte
82ad0354c4 Adds configuration options to the Vic-20, Oric and ZX80/81. 2017-11-18 19:48:10 -05:00
Thomas Harte
073e439518 Adds a basic argument parser, allowing machine options to be set. 2017-11-18 19:34:38 -05:00
Thomas Harte
27b123549b Adds missing #include. 2017-11-17 23:15:37 -05:00
Thomas Harte
de9db724a7 Introduces Configurable::Device and implements it for the Electron.
Configurable::Device covers devices that have user-facing configuration options, listing them and accepting them.
2017-11-17 23:02:00 -05:00
Thomas Harte
532ea35ee9 Merge pull request #280 from TomHarte/AttributeBindings
Corrects intermediate shader attribute bindings.
2017-11-16 17:20:23 -08:00
Thomas Harte
e9ddec35d6 Corrects intermediate shader attribute bindings. 2017-11-16 20:19:54 -05:00
Thomas Harte
7647f8089b Merge pull request #279 from TomHarte/StringStream
Substitutes std::osringstream for C-esque `asprintf`.
2017-11-15 18:49:16 -08:00
Thomas Harte
f00f0353a6 Removes unnecessary temporaries. 2017-11-15 21:48:10 -05:00
Thomas Harte
e19ae5d43d Merge branch 'StringStream' of github.com:TomHarte/CLK into StringStream 2017-11-15 21:30:16 -05:00
Thomas Harte
0a9622435c Merge branch 'StringStream' of github.com:TomHarte/CLK into StringStream 2017-11-15 21:30:04 -05:00
Thomas Harte
f704932475 Merge branch 'StringStream' of github.com:TomHarte/CLK into StringStream 2017-11-15 21:29:22 -05:00
Thomas Harte
d0f096a20b Substitutes std::osringstream for C-esque asprintf. 2017-11-15 21:28:48 -05:00
Thomas Harte
949d0f3928 Substitutes std::osringstream for C-esque asprintf. 2017-11-15 21:25:01 -05:00
Thomas Harte
a2d48223c3 Merge pull request #278 from TomHarte/OpenGL32
Adds an explicit request for OpenGL 3.2 under SDL.
2017-11-14 16:02:33 -08:00
Thomas Harte
fc080c773f Adds an explicit request for OpenGL 3.2. 2017-11-14 18:59:18 -05:00
Thomas Harte
adb3811847 Ensures deterministic initial state for the atomic flag. 2017-11-13 22:51:42 -05:00
Thomas Harte
dbbea78b76 Merge branch 'master' of github.com:TomHarte/CLK 2017-11-13 22:40:05 -05:00
Thomas Harte
fd96e3e657 Eliminates all unused #ifdef GL_NV_texture_barrier code. 2017-11-13 22:39:18 -05:00
Thomas Harte
06d81b3a97 Eliminates all unused #ifdef GL_NV_texture_barrier code. 2017-11-13 22:38:33 -05:00
Thomas Harte
88551607a6 Ensures the GL error flag is cleared after a potential error-raising call. 2017-11-13 22:31:41 -05:00
Thomas Harte
2a9dccff26 Fixes typo. 2017-11-13 22:28:11 -05:00
Thomas Harte
1027f85683 Merge pull request #277 from TomHarte/MappingFallback
Adds a fallback route for the array builder if it can't map a buffer.
2017-11-13 19:27:48 -08:00
Thomas Harte
9bb9cb4a65 Adds a fallback route for the array builder if it can't map a buffer. 2017-11-13 22:27:04 -05:00
Thomas Harte
2de80646ec Merge pull request #276 from TomHarte/SafeTextureTarget
Updates style of OpenGL::TextureTarget for instance variable names and RAII.
2017-11-13 19:13:32 -08:00
Thomas Harte
bf4ed57f68 Updates style of OpenGL::TextureTarget for instance variable names and preference for RAII. 2017-11-13 22:04:13 -05:00
Thomas Harte
9578f3dc44 Merge pull request #275 from TomHarte/SDLLogging
Adds some very basic logging to the SDL target.
2017-11-12 21:24:39 -05:00
Thomas Harte
a97c478a34 Adds some very basic logging to the SDL target. 2017-11-12 21:23:48 -05:00
Thomas Harte
e0113d5dce Merge pull request #274 from TomHarte/TargetFramebuffer
Attempts more cleanly to deal with window resizing in SDL.
2017-11-12 19:48:23 -05:00
Thomas Harte
980cf541d2 Attempts more cleanly to deal with window resizing in SDL. 2017-11-12 19:47:18 -05:00
Thomas Harte
69c983f9ee Merge pull request #273 from TomHarte/TargetFramebuffer
Allows a CRT machine owner to set the target frame buffer for OpenGL output.
2017-11-12 19:30:06 -05:00
Thomas Harte
70039d22f1 Allows a CRT machine owner to set the target frame buffer for OpenGL output, breaking the assumption that it'll be zero. 2017-11-12 19:29:22 -05:00
Thomas Harte
ebdb80c908 Merge pull request #272 from TomHarte/UnusedResults
Resolves all GCC warnings
2017-11-12 17:55:23 -05:00
Thomas Harte
0eaac99d74 Avoids implicit signed/unsigned comparison in the G64 reader. 2017-11-12 17:48:11 -05:00
Thomas Harte
792061a82b Corrects warnings in the CSW, CPC DSK, ZX8081 data encoding, and PRG and binary cartridges. 2017-11-12 17:46:06 -05:00
Thomas Harte
d2ba7d7430 Corrects GCC warnings in Commodore::File and the FileHolder. 2017-11-12 17:38:21 -05:00
Thomas Harte
8713cfa613 Ensured all asprintf return values are checked. 2017-11-12 17:29:20 -05:00
Thomas Harte
aa77be1c10 Introduces missing include. 2017-11-12 17:20:37 -05:00
Thomas Harte
e6aa2321cd Merge branch 'UnusedResults' of github.com:TomHarte/CLK into UnusedResults 2017-11-12 17:17:49 -05:00
Thomas Harte
c827d14d97 Corrects various GCC warnings across the 6560, CPC, TIA, Oric video and elsewhere. 2017-11-12 17:17:27 -05:00
Thomas Harte
2979d19621 Enables all warnings for the SDL build. 2017-11-12 16:46:10 -05:00
Thomas Harte
282e5c9d3e For GCC's benefit, added impossible default options. 2017-11-12 16:45:31 -05:00
Thomas Harte
ede47d4ba7 Improves type safety within CSW file support. 2017-11-12 16:42:53 -05:00
Thomas Harte
5408efe9b5 Flags obvious default options within the 6560, Vic-20 and DynamicMachine. 2017-11-12 16:41:09 -05:00
Thomas Harte
d6141cb020 Increases number of warnings in Xcode. 2017-11-12 16:37:39 -05:00
Thomas Harte
198d0fd1de Makes it obvious to GCC that a return result is always supplied. 2017-11-12 16:37:18 -05:00
Thomas Harte
6d80856f02 Attempts to eliminate warnings around a meaningless value and an unused label in the 8272. 2017-11-12 16:34:51 -05:00
Thomas Harte
4778616fd7 Eliminates unused result and unused label. 2017-11-12 16:30:23 -05:00
Thomas Harte
2e025d85eb Added check in SDL main that the expected number of bytes is read. 2017-11-12 16:26:42 -05:00
Thomas Harte
61f2191c86 Merge branch 'PragmaMark' 2017-11-12 16:11:36 -05:00
Thomas Harte
c1eab8d5f3 Corrects a pragma mark that escaped detection through typo. 2017-11-12 16:11:24 -05:00
Thomas Harte
91d2d59ae5 Merge pull request #271 from TomHarte/PragmaMark
Commutes cross-platform `#pragma mark`s to `//MARK:`s.
2017-11-12 16:02:18 -05:00
Thomas Harte
5aef81cf24 Commutes cross-platform #pragma marks to //MARK:s. 2017-11-12 15:59:11 -05:00
Thomas Harte
3550196bed Merge pull request #270 from TomHarte/TrackCloning
Corrects `insert` explicitly to supply a `shared_ptr` rather than a raw one.
2017-11-11 18:23:49 -05:00
Thomas Harte
bce58683fa Corrects insert explicitly to supply a shared_ptr rather than a raw one. 2017-11-11 18:22:41 -05:00
Thomas Harte
c91a5875b2 Merge pull request #269 from TomHarte/StdNamespace
Starts doubling down on <cX> over <X.h> for C includes, plus appropriate namespace usage.
2017-11-11 15:32:50 -05:00
Thomas Harte
2e15fab651 Doubles down on <cX> over <X.h> for C includes, and usage of the namespace for those types and functions. 2017-11-11 15:28:40 -05:00
Thomas Harte
6a176082a0 Switches a couple of overlooked C-style casts to functional style. 2017-11-11 12:41:49 -05:00
Thomas Harte
fd346bac3e Merge pull request #267 from TomHarte/AudioCleanup
Resolves dangling C-isms in my FIR filter, and introduces composition.
2017-11-11 12:38:45 -05:00
Thomas Harte
25e9dcc800 Merge pull request #268 from TomHarte/SerialPortVIAInitialisation
Resolvws out-of-order initialisation within the C1540.
2017-11-11 12:37:48 -05:00
Thomas Harte
792cbb1536 Resolvws out-of-order initialisation within the C1540. 2017-11-11 12:35:51 -05:00
Thomas Harte
2e12370251 Resolves some of the dangling C-isms remaining in my FIR filter, and introduces filter composition. 2017-11-11 12:30:45 -05:00
Thomas Harte
7adc25694a Merge pull request #266 from TomHarte/SDLScons
Introduces an SCons build file and corrects remaining Ubuntu build errors
2017-11-10 23:43:44 -05:00
Thomas Harte
ca80da7fbe Merge branch 'SDLScons' of github.com:TomHarte/CLK into SDLScons 2017-11-10 23:17:05 -05:00
Thomas Harte
f853d87884 Switches SConstruct build file to producing an optimised result. 2017-11-10 23:16:05 -05:00
Thomas Harte
524087805f Switches SConstruct build file to producing an optimised result. 2017-11-10 23:11:40 -05:00
Thomas Harte
916eb96b47 Makes buffer size restriction explicit in the Vic-20. 2017-11-10 22:59:11 -05:00
Thomas Harte
4add2c1051 Corrects order-of-initialisation errors in the TIA. 2017-11-10 22:57:43 -05:00
Thomas Harte
cb0f58ab7a Corrects order-of-initialisation errors in the CPC (again), TextureBuilder, TextureTarget, Z80, MFM parser and binary tape player. 2017-11-10 22:57:03 -05:00
Thomas Harte
d9e56711ce Corrects order-of-initialisation errors in the Amstrad CPC, Vic-20, Oric, Commodore File, MFM disk controller, UEF and Commodore tape parser. 2017-11-10 22:47:10 -05:00
Thomas Harte
d60692b6fd Corrects order of initialisation for the Typer and Oric video. 2017-11-10 22:35:05 -05:00
Thomas Harte
5b6ea35d96 Corrects initialisation ordering for the ZX80/81, C1540 and AY-3-8910. 2017-11-10 22:31:27 -05:00
Thomas Harte
4cbc87a17d Corrects out-of-order initialisations for the 1770, Atari 2600 joystick, Pitfall II bus extender, Microdisc and 6502. 2017-11-10 22:20:44 -05:00
Thomas Harte
46e7c199b2 Corrects improper initialisation order of the Commodore .tap and CRTMachine::Machine. 2017-11-10 22:08:40 -05:00
Thomas Harte
ff7ba526fb Corrects improper initialisation order on the 6560. 2017-11-10 22:05:35 -05:00
Thomas Harte
a825da3715 Reinstates missing include file. 2017-11-10 22:02:02 -05:00
Thomas Harte
fabaf4e607 Adds missing include files, corrects bad include paths and eliminates the Clang-specific __undefined. 2017-11-10 21:56:53 -05:00
Thomas Harte
153067c018 Adds missing files to SConstruct. 2017-11-10 21:56:15 -05:00
Thomas Harte
f7f2736d4d Corrects missing includes in the SerialBus, Electron Video and Typer. 2017-11-10 20:37:18 -05:00
Thomas Harte
a16ca65825 Adds object files and SConstruct intermediaries to .gitignore. 2017-11-10 20:36:47 -05:00
Thomas Harte
cb015c83e1 Eliminated C99-style struct initialisations. 2017-11-10 19:14:19 -05:00
Thomas Harte
2203499215 Enables -Wreorder and corrects a few of the more trivial fixes thereby suggested. 2017-11-09 22:14:22 -05:00
Thomas Harte
c0055a5a5f Further builds up SConstruct, correcting many missed imports and a couple of improper uses of C99 in C++ code. 2017-11-09 22:04:49 -05:00
Thomas Harte
62218e81bf Fixes the FIR filter again from the Apple side. 2017-11-08 22:48:44 -05:00
Thomas Harte
c45d4831ec Introduces an SConstruct file and corrects those errors and warnings that arise in Ubuntu. 2017-11-08 22:36:41 -05:00
Thomas Harte
9fd33bdfde Merge pull request #265 from TomHarte/Whitespace
Eliminates a large number of instance of end-of-line white space.
2017-11-07 22:54:46 -05:00
Thomas Harte
6e1d69581c Eliminates a variety of end-of-line spaces. 2017-11-07 22:54:22 -05:00
Thomas Harte
f95515ae81 Eliminates a large number of instance of end-of-line tabs. 2017-11-07 22:51:06 -05:00
Thomas Harte
09c855a659 Merge pull request #264 from TomHarte/SDLKiosk
SDL kiosk
2017-11-07 22:44:48 -05:00
Thomas Harte
16c96b605a Xcode 9.1 auto-change. 2017-11-07 22:43:25 -05:00
Thomas Harte
e10d369e53 Ensures that execution doesn't proceed if ROMs are missing. 2017-11-07 22:32:59 -05:00
Thomas Harte
0d1b63a8c5 Switches the Objective-C machine bindings to use the set_rom_fetcher path for supplying ROMs, simplifying and unifying. 2017-11-07 22:29:57 -05:00
Thomas Harte
ddcdd07dd0 Modifies the Vic-20 and C1540 to bring them into the realm of self-ROM fetching.
Hence enables Vic-20 support within kiosk mode as currently drafted.
2017-11-07 21:19:51 -05:00
Thomas Harte
35da3edf60 Implements install_roms on the Electron, Oric and ZX80/81. 2017-11-06 22:14:15 -05:00
Thomas Harte
d605022ea3 Moves output setup to after the machine has been configured as its target. 2017-11-06 22:13:38 -05:00
Thomas Harte
0da78065ce Eliminates some dangling cases of undefined initial state in the TIA. 2017-11-06 22:12:39 -05:00
Thomas Harte
4b68c372c6 Adds a first attempt at audio via SDL. 2017-11-05 22:29:25 -05:00
Thomas Harte
13406fedd8 Explains commenting. 2017-11-05 21:29:20 -05:00
Thomas Harte
a209ae76ca Adds keyboard input from SDL. 2017-11-05 21:16:14 -05:00
Thomas Harte
0116d7f071 Added a platform-neutral route for feeding ROMs to machines, in a platform-dependant fashion; implemented for the CPC. 2017-11-05 20:12:01 -05:00
Thomas Harte
512e877d06 Ensures proper initialisation of the delegate pointer. 2017-11-05 20:11:18 -05:00
Thomas Harte
1e1efcdcb8 Pushes far enough along the path of having the SDL version do work that it becomes obvious I've never figured out the correct course of action if there is no sound output. 2017-11-05 12:49:28 -05:00
Thomas Harte
bc2f58e9de Starts the process of adding an SDL-based 'kiosk' (i.e. TV UI, or even UI-less for now) mode.
Specifically, introduces to the Mac side of things an SDL target with, so far, enough logic to create a window and pump SDL's events, after having decided which machine and configuration it should use.
2017-11-04 19:36:46 -04:00
Thomas Harte
fd10c42433 Merge pull request #263 from TomHarte/WriteableDSK
Makes CPC-style .DSK files writeable
2017-11-03 21:59:06 -04:00
Thomas Harte
794437f20f Corrects fixed buffer size error in FileHolder::check_signature. 2017-11-03 21:43:31 -04:00
Thomas Harte
23d5849cda Attempts to map recognised [M]FM errors back to FDC status codes. 2017-11-03 21:29:42 -04:00
Thomas Harte
5070a8414f Improves FileHolder documentation 2017-11-03 21:29:15 -04:00
Thomas Harte
5a3ca0e447 Adds output for modified CPC DSKs. 2017-11-03 21:10:22 -04:00
Thomas Harte
e384c50580 Switches FileHolder to have a usage much closer to FILE *.
Thereby opens a route for file format implementations such as that appearing for CPC DSK that create an in-memory copy and perform a full rewrite.
2017-11-02 22:32:00 -04:00
Thomas Harte
b9734278f6 Provides an up-front evaluation of performance versus objectices via README.MD. 2017-11-02 12:22:27 -04:00
Thomas Harte
f807a6b608 Generalises the concept of multiple samplings of an FM/MFM sector, simplifying CPC DSK support and paving the way for generic weak/fuzzy bit support. 2017-10-31 21:32:28 -04:00
Thomas Harte
833f8c02a4 Switches the CPC DSK implementation to building an in-memory version of the structure up front.
Preparatory to making these things writeable.
2017-10-31 19:41:16 -04:00
Thomas Harte
0248c6a282 Merge pull request #262 from TomHarte/BookEnds
Adds a facility for 'bookending' data runs, eliminating an occasional Electron rendering error
2017-10-23 18:36:54 -04:00
Thomas Harte
218b976dbc Adds through route for setting a texture bookender, and exploits it from the Electron. 2017-10-23 18:35:37 -04:00
Thomas Harte
513903890e Corrects definition of Bookender and provides the default implementation. 2017-10-22 17:24:41 -04:00
Thomas Harte
1157bde453 Sketches interface for a GPU data bookender, to avoid stray errors with packed pixel formats. 2017-10-22 10:48:10 -04:00
Thomas Harte
46345c6a3e Merge pull request #261 from TomHarte/UIntCasts
Continues the process of conversion to functional casts.
2017-10-21 22:53:14 -04:00
Thomas Harte
c13f8e5390 Corrects a couple of cast conversion errors. 2017-10-21 22:42:19 -04:00
Thomas Harte
ad9df4bb90 Commutes uint8_t *, uint16_t *, uint32_t *, size_t, off_t and long to functional-style casts. 2017-10-21 22:30:15 -04:00
Thomas Harte
e983854e71 Converts all uint8_t and uint16_t casts to the functional style. 2017-10-21 21:50:53 -04:00
Thomas Harte
ec999446e8 Commutes int and unsigned casts to the functional style. 2017-10-21 21:00:40 -04:00
Thomas Harte
5e3e91373a Switches all unsigned int and double casts to functional style. 2017-10-21 19:49:04 -04:00
Thomas Harte
c52348d8d7 Merge pull request #260 from TomHarte/KeyboardCleanup
Cleans up after keyboard formalisation.
2017-10-21 10:53:46 -04:00
Thomas Harte
9e0907ee76 Completes clean-up of post-formalisation per-machine keyboard code.
At least for now. Standardising on how column + row is encoded might be helpful.
2017-10-21 10:52:35 -04:00
Thomas Harte
9ad4025138 Relocates things that were in Machines/ for machine usage.
Leaving only those things intended to be visible interface.
2017-10-21 10:30:02 -04:00
Thomas Harte
405f58d6a3 Corrects write guard names. 2017-10-21 10:21:40 -04:00
Thomas Harte
afbd1c425c Merge pull request #259 from TomHarte/Vic20Keyboard
Consolidates Vic-20 keyboard code.
2017-10-19 22:28:11 -04:00
Thomas Harte
b2c1b83fcd Consolidates Vic-20 keyboard code. 2017-10-19 22:27:30 -04:00
Thomas Harte
8d2b9a581a Merge pull request #256 from TomHarte/UniversalInput
Standardises the host-side interface for joystick and keyboard input
2017-10-19 22:15:47 -04:00
Thomas Harte
1825af0dd3 Eliminates dead code in the Vic-20 and Inputs::Joystick. 2017-10-19 22:15:21 -04:00
Thomas Harte
c2f6799f0c Implements Vic-20 restore key. 2017-10-19 22:02:34 -04:00
Thomas Harte
b5b6219cb7 Slightly simplifies TextureBuilder arithmetic. 2017-10-19 22:02:00 -04:00
Thomas Harte
185a699279 Fixes off-by-one keyboard state accumulation error. 2017-10-19 22:01:24 -04:00
Thomas Harte
96b8f9ae9f Merge branch 'master' into UniversalInput 2017-10-17 22:54:17 -04:00
Thomas Harte
88e2350b8f Prevents undefined behaviour from the CPC's timer. 2017-10-17 22:53:52 -04:00
Thomas Harte
5c141af734 Prevents undefined behaviour from the CPC's timer. 2017-10-17 22:40:32 -04:00
Thomas Harte
da580e4186 Merge branch 'master' into UniversalInput 2017-10-17 22:36:22 -04:00
Thomas Harte
57ee09dffb Merge pull request #258 from TomHarte/UndefinedBehaviour
Corrects large swathes of undefined behaviour
2017-10-17 22:35:59 -04:00
Thomas Harte
7c8e830b90 Adjusted the Acorn tape parser to avoid signed left shifts. 2017-10-17 22:34:49 -04:00
Thomas Harte
ba5f668338 Ensured full CRT instance initialisation. 2017-10-17 22:34:10 -04:00
Thomas Harte
2c1e99858b Fixed HalfCycles to allow conversion from Cycles without relying on undefined behaviour.
Specifically: left shifting a negative number.
2017-10-17 22:22:51 -04:00
Thomas Harte
7f2febeec9 Ensures complete DPLL initial state assignment. 2017-10-17 22:13:37 -04:00
Thomas Harte
2d7a4fe5f0 Switches the MFM shifter to unsigned accumulation.
Since left shifting signed numbers is undefined behaviour.
2017-10-17 22:12:04 -04:00
Thomas Harte
91b867a7b3 Ensures full 8272 instance state initialisation. 2017-10-17 22:11:01 -04:00
Thomas Harte
3944e734d3 Ensures full 6845 instance state initialisation and uses an unsigned shifter. 2017-10-17 22:10:28 -04:00
Thomas Harte
ce78d9d12c Introduces buffer alignment when writing to textures.
To avoid cross-boundary writes and hopefully to eke out a little better performance.
2017-10-17 22:09:48 -04:00
Thomas Harte
edbc60a3fb Various undefined behaviour fixes.
Primarily around uninitialised variables, but also with an attempted use of a negative pointer.
2017-10-17 21:29:19 -04:00
Thomas Harte
6ea3ff62df Merge branch 'master' into UniversalInput 2017-10-17 21:28:40 -04:00
Thomas Harte
88959571f1 Merge pull request #257 from TomHarte/CPMReading
Corrects CPM reader buffer overwrites
2017-10-17 20:54:02 -04:00
Thomas Harte
b4583e976e Corrects buffer overwrites resulting from failure to treat a number of records of 0x80 as a special case. 2017-10-17 20:52:16 -04:00
Thomas Harte
92d9805f09 Removes dead Objective-C protocol references. 2017-10-17 20:51:40 -04:00
Thomas Harte
0c2dd62328 Various undefined behaviour fixes.
Primarily around uninitialised variables, but also with an attempted use of a negative pointer.
2017-10-17 20:50:46 -04:00
Thomas Harte
3f4d90d775 Corrects buffer overwrites resulting from failure to treat a number of records of 0x80 as a special case. 2017-10-17 20:49:12 -04:00
Thomas Harte
542ec4312f Switched the Objective-C code to using dynamic_cast alone to decide whether to post keyboard or joystick events. 2017-10-15 21:25:56 -04:00
Thomas Harte
18798c9886 Corrects joystick memory leaks. 2017-10-15 20:49:47 -04:00
Thomas Harte
7aaf27389c Commutes the Atari 2600 to the JoystickMachine interface. 2017-10-15 20:44:59 -04:00
Thomas Harte
ee179aa7bd Introduces a joystick analogue to the shared keyboard interface, and implements it for the Vic-20. 2017-10-14 22:36:31 -04:00
Thomas Harte
3a05ce36de Adds a reference to the calling keyboard in reset_all_keys. 2017-10-14 22:07:11 -04:00
Thomas Harte
4f289ab10b Corrects some deficiencies in Vic-20 keyboard mapping.
... albeit without yet being clear on the wiring behind restore.
2017-10-12 22:33:00 -04:00
Thomas Harte
78ee46270b Transfers possession of keyboard mappings from the Mac side over to individual machines.
Specifically by establishing an intermediate representation of a useful mix between the American and British IBM and Mac keyboard layouts, and routing through that.
2017-10-12 22:25:02 -04:00
Thomas Harte
edb632af52 Sketches first design for generalising keyboard input. 2017-10-09 22:26:39 -04:00
Thomas Harte
19c03a08a6 Merge pull request #255 from TomHarte/BatchDriveUpdates
Rewires so as to give disk images visibility of large change sets rather than per-sector track rewrites.
2017-10-07 19:42:14 -04:00
Thomas Harte
44cdc124af Switches to providing a full record of changes to disk images, rather than feeding them a track at a time.
Gets explicit about `override`s while doing so, to ensure full adaptation.
2017-10-07 19:37:36 -04:00
Thomas Harte
b37787a414 Ensures lifetime-linked track flushing without relying on virtual calls within a destructor. 2017-10-07 19:14:18 -04:00
Thomas Harte
53b99ea248 Uses Disk::flush_tracks to elide replacement of dirty tracks. 2017-10-06 22:07:42 -04:00
Thomas Harte
97a2be71e3 Introduces flush_tracks to Drive, while switching its interface to using Track::Address and adjusting associated integer types. 2017-10-06 21:45:12 -04:00
Thomas Harte
f623bff5c3 Removes unnecessary call. 2017-10-06 18:48:51 -04:00
Thomas Harte
2511fc8401 Merge pull request #254 from TomHarte/C++BestEffortUpdater
Commutes the best-effort updater into C++11.
2017-10-05 18:28:46 -04:00
Thomas Harte
d37ec9e5b0 Attempts to ensure good behaviour if dealt an adjustable clock, and consts where possible. 2017-10-05 18:23:56 -04:00
Thomas Harte
95c82f5b36 Merge branch 'C++BestEffortUpdater' of github.com:TomHarte/CLK into C++BestEffortUpdater 2017-10-05 18:17:52 -04:00
Thomas Harte
ec202ed8be Merge branch 'master' into C++BestEffortUpdater 2017-10-05 18:17:35 -04:00
Thomas Harte
7190225603 Merge branch 'master' into C++BestEffortUpdater 2017-10-05 18:12:33 -04:00
Thomas Harte
52e7cabd4e Merge pull request #253 from TomHarte/Swift4UnitTests
Removes usages of deprecated Swift initialiser within unit tests.
2017-10-05 18:12:12 -04:00
Thomas Harte
064f1dfdbc Removes usages of deprecated initialiser. 2017-10-05 18:10:47 -04:00
Thomas Harte
f40e1fd840 Commutes the best-effort updater into C++11. 2017-10-05 18:09:58 -04:00
Thomas Harte
e194a2a015 Removes usages of deprecated initialiser. 2017-10-05 16:45:13 -04:00
Thomas Harte
c39759333a Merge pull request #252 from TomHarte/Casts
Begins this project's conversion to functional-style casts.
2017-10-03 22:05:22 -04:00
Thomas Harte
edb9fd301c Begins this project's conversion to functional-style casts. 2017-10-03 22:04:15 -04:00
Thomas Harte
ea5023ac26 Merge pull request #251 from TomHarte/HFEWriteable
Makes HFE files writeable
2017-10-03 21:32:05 -04:00
Thomas Harte
0fb363ea0e Adds writing support for HFEs. 2017-10-03 21:24:20 -04:00
Thomas Harte
1cc85615d5 Factors HFE track seeking out from the track fetching method. 2017-10-03 20:33:55 -04:00
Thomas Harte
7b01c1bee6 Revokes direct visibility of is_read_only_ to subclasses of FileHolder. 2017-10-03 19:36:06 -04:00
Thomas Harte
35705c5345 Factors out bit reversing from the HFE class. 2017-10-03 19:12:45 -04:00
Thomas Harte
f41da83d97 Seeks to eliminate race conditions on the best-effort updater. 2017-09-30 21:34:43 -04:00
Thomas Harte
cd1e5dea4d Merge pull request #250 from TomHarte/TrackToBits
Refactors MFM support, breaking it into components
2017-09-30 20:31:43 -04:00
Thomas Harte
ef605eda51 Factors out commonalities in SSD/DSD and ADF implementations. 2017-09-30 20:30:15 -04:00
Thomas Harte
2f48ee59fa Merge branch 'TrackToBits' of github.com:TomHarte/CLK into TrackToBits 2017-09-30 20:12:56 -04:00
Thomas Harte
f86729c4ac Ensures safe machine release upon window closure. 2017-09-30 20:12:46 -04:00
Thomas Harte
5f99f4442c Ensures safe machine release upon window closure. 2017-09-30 20:07:04 -04:00
Thomas Harte
326857a84d Corrects FM/MFM selection when looking for sectors. 2017-09-29 22:48:00 -04:00
Thomas Harte
5dd3945695 Factors out the more egregious similarities between ADF and SSD. 2017-09-29 22:07:23 -04:00
Thomas Harte
19eb975c73 Adds an intermediate step in CP/M directory parsing.
To reduce amount of time spent allocating and reallocating buffers.
2017-09-29 21:38:16 -04:00
Thomas Harte
698ffca51b Recasts the [M]FM parser in terms of the new factoring.
Temporarily breaks SSD writing support.
2017-09-29 20:08:36 -04:00
Thomas Harte
fe3cc5c57c Removes dead pragma. 2017-09-28 20:47:25 -04:00
Thomas Harte
f488854720 Switches Oric MFM DSK serialisation to feeding a track serialisation to a shifter.
Thereby eliminates the parser's need to offer get_track.
2017-09-27 22:14:50 -04:00
Thomas Harte
51c0c45e04 Turns MFM bit length into a globally-available constant. 2017-09-27 21:30:09 -04:00
Thomas Harte
c3e1489a8e Introduces Track::Address, a parallel to Sector::Address to enable more uniform storage. 2017-09-27 21:29:06 -04:00
Thomas Harte
e3420f62c6 Switches the Acorn ADF implementation to using the new track_serialisation/sectors_from_segment route for decomposition of a track into sectors. 2017-09-26 22:05:33 -04:00
Thomas Harte
970c80f2e3 Adds TrackSerialiser.cpp to the project and reorders section. 2017-09-26 22:03:42 -04:00
Thomas Harte
9f4a407f94 Switches the track serialiser to a more standard header + implementation separation.
Also introduces a full priming of the PLL before deserialisation begins.
2017-09-26 22:01:32 -04:00
Thomas Harte
5dda897334 Changes function name to sector_size — into line with idioms. 2017-09-26 22:00:19 -04:00
Thomas Harte
3982e375e3 Introduces a route from a PCMSegment to a list of [M]FM sectors. 2017-09-25 19:57:11 -04:00
Thomas Harte
a8524daecb Marks the move constructor as noexcept, to improve usage with vector. 2017-09-25 19:53:22 -04:00
Thomas Harte
d1ce764201 Provides SectorsFromSegment, a bitstream to sector converter. 2017-09-24 22:41:16 -04:00
Thomas Harte
8875982e1f Ensures Sectors are move constructible (and still default constructible), and adds proper const qualifiers to Sector::Address. 2017-09-24 22:40:38 -04:00
Thomas Harte
3319a4f589 Isolates those Sector fields that describe its address and makes them usable as a set key. 2017-09-24 21:57:21 -04:00
Thomas Harte
c7f27b2db4 Renames MFM.[c/h]pp as per its new remit: encoding only. 2017-09-24 21:40:43 -04:00
Thomas Harte
631f630549 Severs the MFM parser from the overweight single MFM.hpp. 2017-09-24 20:31:19 -04:00
Thomas Harte
2a08bd9ecc Factors shifting plus stateful [M]FM token recognition out of the MFMDiskController.
Given the proliferation of MFM-related classes, establishes a subdirectory for them.
2017-09-24 20:07:56 -04:00
Thomas Harte
f789ee4ff0 Introduces a track to segment decoder.
This will be needed to make formats like G64 and HFE writeable, but probably also will be usable to speed up static analysis.
2017-09-23 22:39:19 -04:00
Thomas Harte
a295b42497 Merge pull request #248 from TomHarte/BetterCPCShot
Adds a better example of correct-aspect-ratio CPC output
2017-09-22 23:05:01 -04:00
Thomas Harte
d8337492cc Bowdlerised images. 2017-09-22 23:02:17 -04:00
Thomas Harte
15c8debc16 Added larger CPC screenshots. 2017-09-22 22:58:18 -04:00
Thomas Harte
67af153c16 Merge pull request #247 from TomHarte/WriteableHFE
Cleans up the `Disk` hierarchy
2017-09-22 22:55:22 -04:00
Thomas Harte
d72dad2d1a Severs the DiskImage implementation from its public header file. 2017-09-22 22:46:31 -04:00
Thomas Harte
698e4fe550 Tidies the Disk file hierarchy. 2017-09-22 22:39:23 -04:00
Thomas Harte
b5406b90cd Introduces a new class hierarchy for disk images.
Increasing independence of format-specific stuff and generic caching without mangling them into a common namespace, and allowing in some cases for a decrease in read/write blocking.
2017-09-22 20:28:11 -04:00
Thomas Harte
05a93ba237 Merge pull request #246 from TomHarte/MainThreadBackingSize
Ensures self.bounds and -convertSizeToBacking: are called only on the main queue.
2017-09-20 20:00:45 -04:00
Thomas Harte
77548d14db Ensures self.bounds and -convertSizeToBacking: are called only on the main queue. 2017-09-20 19:59:34 -04:00
Thomas Harte
b85dd608e7 Merge pull request #245 from TomHarte/Xcode9
Updates to Swift 4 and Xcode 9's recommended project settings.
2017-09-20 19:54:31 -04:00
Thomas Harte
231f13d810 Updates to Swift 4 and Xcode 9's recommended project settings. 2017-09-19 23:06:37 -04:00
Thomas Harte
704bfa114c Merge pull request #244 from TomHarte/FasterStartup
Improves CPC analysis times
2017-09-16 22:07:43 -04:00
Thomas Harte
44a56724cb Speeds up byte decoding within sectors for the ahead-of-time MFM parser. 2017-09-16 20:28:24 -04:00
Thomas Harte
5fbea625ae Switches the CPC static analyser to maintaining a vector of pointers rather than a complete copy of files.
Hence saves a lot of copying and moving — around a second's worth when dealing with the selected test disk.
2017-09-16 20:15:06 -04:00
Thomas Harte
ac57b37e96 Eliminates repetition of the 'untypable character' test. 2017-09-16 19:46:41 -04:00
Thomas Harte
e3e9baeaa4 Merge pull request #243 from TomHarte/Detection
Adds a test that file extension also be typeable.
2017-09-16 19:11:53 -04:00
Thomas Harte
e071123f90 Adds a test that file extension also be typeable. 2017-09-16 19:10:17 -04:00
Thomas Harte
98adb01721 Merge pull request #242 from TomHarte/8272ReadyInterruption
Improves CPC disk emulation
2017-09-16 18:28:00 -04:00
Thomas Harte
d6a5f9a29e Revokes unnecessary change. 2017-09-16 18:24:13 -04:00
Thomas Harte
0d84b4b9dd Removes some redundant end_writing calls. 2017-09-16 17:09:17 -04:00
Thomas Harte
a85909198f Adds defences against double calls to end writing. 2017-09-16 17:07:36 -04:00
Thomas Harte
98751e6ac8 Ensures that all result phases are exactly the intended length by replacing accumulation with assignment.
Also attempts a different version of control mark behaviour. Experiments.
2017-09-15 22:59:26 -04:00
Thomas Harte
da082673d7 Drives now have a finite number of heads.
The Amstrad volunteers itself to be single sided. Everything else stays as it was.
2017-09-15 21:18:36 -04:00
Thomas Harte
35fe4d50d4 Adds command termination upon drive becoming unready, and copies head and drive selection into ST0. 2017-09-15 20:26:41 -04:00
Thomas Harte
b835cb73e2 Merge pull request #241 from TomHarte/DriveEvents
Devolves `TimedEventLoop` ownership from disk controllers to drives
2017-09-15 19:15:44 -04:00
Thomas Harte
662d031e3c Adds exposition on the meaning of a disk controller being in write mode. 2017-09-15 19:14:36 -04:00
Thomas Harte
bf20c717fb The Drive now no longer produces input when in writing mode — other than announcing the index hole. 2017-09-14 22:32:13 -04:00
Thomas Harte
4d4a0cf1d2 Puts the disk controller back into the loop with knowledge about reading mode, and uses that knowledge to cut off the PLL. 2017-09-14 22:30:40 -04:00
Thomas Harte
b62f3e726a Adds a start-of-execution-phase get-out for drives that aren't ready. 2017-09-12 20:43:53 -04:00
Thomas Harte
82b13e98f2 Implements the real hardware ready test for Drives — motor on plus two index holes. 2017-09-11 22:27:50 -04:00
Thomas Harte
9ac831b09c Added an additional protection against overflow. 2017-09-11 22:24:24 -04:00
Thomas Harte
42616da7ff Adjusts the Oric Microdisc to propagate motor control more widely. 2017-09-11 22:15:54 -04:00
Thomas Harte
2f13517f38 Adjusts the 1770 not to talk directly to the drive about motor status. 2017-09-11 22:10:56 -04:00
Thomas Harte
fb9fd26af7 Updates the 1540 for the slightly-more modern world of decoupled drives and disks (!). 2017-09-11 22:08:10 -04:00
Thomas Harte
d3c385b471 Separates the 8272's drive selection signalling from actual drive ownership.
Thereby returns working motor control to the CPC.
2017-09-11 21:25:26 -04:00
Thomas Harte
96bf133924 Withdraws requirement for DiskController users to specify a PLL multiplier or to provide rotation speed.
In the latter case because it's no longer of any interest to the controller, and in the former because I'd rather it be picked automatically.
2017-09-10 22:56:05 -04:00
Thomas Harte
6d6cac429d Fixes extra time accumulation during track running.
Introduces a bunch of further asserts, which aided me in determining the fix, i.e. that Drives being responsible for their own setup_track could double-pump the event loop.
2017-09-10 22:44:14 -04:00
Thomas Harte
dc0b65f9c9 Corrects initial event loop timing state. 2017-09-10 20:51:21 -04:00
Thomas Harte
8882aa496f Corrected wiring to get advance signals through to Drive event delegates. 2017-09-10 20:51:05 -04:00
Thomas Harte
0622187ddf Strips Controller of all capabilities now housed on the Drive. 2017-09-10 19:23:23 -04:00
Thomas Harte
523e1288fa Updates the MFM parser to use SingleTrackDisk rather than the equivalent withdrawn Drive functionality. 2017-09-10 17:34:52 -04:00
Thomas Harte
1a96cce26f Implements SingleTrackDisk, a Disk that contains only a single, specified, track. 2017-09-10 17:34:14 -04:00
Thomas Harte
a4e275e1fc Provides an implementation of Drive's new interface.
Mostly lifted from DiskController. `set_disk_with_track` has been withdrawn in favour of providing a suitable wrapper `Disk` subclass, as being an unnecessary complexity and intermingling of concerns.
2017-09-10 17:33:01 -04:00
Thomas Harte
6075064400 Adds the ability to query a TimedEventLoop for its input clock rate. 2017-09-10 17:31:43 -04:00
Thomas Harte
ff6e65cca9 Introduces necessary storage and interface for writing. 2017-09-10 16:23:31 -04:00
Thomas Harte
90d2347c90 Extended to permit subclasses that are interested to get sub-run_for information about event times. 2017-09-10 14:44:38 -04:00
Thomas Harte
90c7056d12 Started devolving timed event loop logic down to the drives, moving them closer to modelling real life. 2017-09-10 14:43:20 -04:00
Thomas Harte
fed2bc9fc9 Merge pull request #240 from TomHarte/C1540
Simplifies the published C1540 interface and corrects a transcription bug.
2017-09-05 21:22:25 -04:00
Thomas Harte
ff510f3b84 Explicitly disallows copying of VIAs, and marks the constructor as noexcept. 2017-09-05 21:21:23 -04:00
Thomas Harte
3b12fca417 Corrects non-recurring-pattern adaptation bug: the 'SerialPortVIA' should keep a reference to its VIA, not a copy of it. 2017-09-05 21:19:56 -04:00
Thomas Harte
8eeb7e73cd Adds a commented-out printf that I might like to use again later. 2017-09-05 21:15:56 -04:00
Thomas Harte
7fd6699e0b Corrects comment indentation. 2017-09-05 21:15:15 -04:00
Thomas Harte
ed70b15fc9 Merge pull request #239 from TomHarte/6522Tests
Corrects 6522 bridge per has-a-not-is-a template switch.
2017-09-04 21:58:07 -04:00
Thomas Harte
ff24e1de31 Corrects 6522 bridge per has-a-not-is-a template switch. 2017-09-04 21:56:21 -04:00
Thomas Harte
6547102511 Attempts better to hide C1540 implementation details from the reader.
In this case not from the compiler, as it's desireable to keep `run_for` as a non-virtual call, and therefore everything else comes alone for the ride.
2017-09-04 20:58:00 -04:00
Thomas Harte
d538ff5039 Merge pull request #238 from TomHarte/C1540
Minor tweaks to re-enable proper file selection in the Vic-20.
2017-09-04 20:56:54 -04:00
Thomas Harte
a49594c6a3 Tweaks Vic20 Machine parent class order so that when turned into a CRTMachine, still successfully dynamically casts as a ConfigurationTarget.
More thorough thought is required.
2017-09-04 20:56:00 -04:00
Thomas Harte
3544c0f014 Switches from testing size() != 0 to empty() != true.
Partly as size() is O(n) but empty is O(1), but primarily for style.
2017-09-04 20:54:38 -04:00
Thomas Harte
f26fe3756c Merge pull request #237 from TomHarte/6522CleanUp
Significantly cleans up the 6522.
2017-09-04 18:23:33 -04:00
Thomas Harte
a42ca290cb Reformulates the Oric more cleanly into the modern world.
Specifically: now that the implementation is contained within the CPP file, there's no need to embed the keyboard, tape player and VIA port handler as private classes. Also the pain of additional syntax is reduced, so the keyboard has been bumped up to a fully data-hiding class. I've also transferred overall ownership of the tape player, AY and keyboard up to the Oric itself, with the VIA merely being wired to them, and added a whole bunch of extra documentation.
2017-09-04 18:22:14 -04:00
Thomas Harte
da09098e49 Updates clipped area per latest CRT response to vertical sync. 2017-09-04 17:51:02 -04:00
Thomas Harte
450712f39c Improves and corrects 6522 header documentation. 2017-09-04 14:32:34 -04:00
Thomas Harte
24b3faa427 Deconstitutes the 6522 into component parts, templated and non-templated.
Adjusts the Oric, Vic-20 and C-1540 accordingly, albeit with the quickest possible solutions.
2017-09-04 14:26:04 -04:00
Thomas Harte
40d11ea0e3 Merge pull request #236 from TomHarte/CPUSeparation
Further cements CPU file separation.
2017-09-04 11:20:00 -04:00
Thomas Harte
ab2bcb939f Separates 6502Base into its constituent parts. 2017-09-04 11:08:33 -04:00
Thomas Harte
45499050b6 Separates Z80Base.cpp into its component classes. 2017-09-04 11:04:01 -04:00
Thomas Harte
0c9197df30 Merge pull request #235 from TomHarte/Reencapsulation
Further strips back the amount exposed in Z80-related headers.
2017-09-01 22:38:33 -04:00
Thomas Harte
a1e200cc65 Further strips back the amount exposed in Z80-related headers.
Almost all opcode table generation macros and code now resides neatly in the world of .CPP.
2017-09-01 22:19:16 -04:00
Thomas Harte
8a612bb6ab Merge pull request #234 from TomHarte/TidyZ80
Separates interface and implementation of the Z80
2017-09-01 20:54:01 -04:00
Thomas Harte
e6ac939ae0 Reintroduces missing noexcept specifier. 2017-09-01 20:51:31 -04:00
Thomas Harte
b034d4e6f8 Refactors the Z80 to separate out interface and implementation.
Following the pattern just established by the 6502, puts all implementation specifics beyond the visibility of a human reading Z80.hpp and in subfolders so as to promote the idea that they shouldn't go out of their way.
2017-09-01 20:50:24 -04:00
Thomas Harte
de218611e4 Corrects possible confusion as documentation recommends Cycles(0) as default, but then gives Cycles(1). 2017-09-01 20:49:24 -04:00
Thomas Harte
615f7ce176 Merge pull request #233 from TomHarte/BetterYet6502
Removes from 6502.hpp all remaining implementation details.
2017-09-01 19:47:49 -04:00
Thomas Harte
b306776ba9 Removes from 6502.hpp all remaining implementation details, making it purely an interface document.
Though those details remain visible to files including 6502.hpp through necessity.
2017-09-01 19:46:29 -04:00
Thomas Harte
0f85cffc78 Merge pull request #232 from TomHarte/ElectronShift
Ensures all parts of the Electron have a fully-defined initial state.
2017-08-31 22:29:50 -04:00
Thomas Harte
96648df5fe Ensures all parts of the Electron have a fully-defined initial state.
Specifically to resolve an error with shift being pressed at startup due to a failure to establish a default value for that flag, but applying the same principle across the board.
2017-08-31 22:29:24 -04:00
Thomas Harte
2c99a2d6ec Merge pull request #231 from TomHarte/NeaterTemplates
Tidies the 6502 template and folder hierarchy.
2017-08-31 22:17:08 -04:00
Thomas Harte
4af333d5ec Tidies the 6502 template and folder hierarchy.
Specifically: there's now just the one .h file at the top level, giving a clear indication of what a user should read. That separates implementation from interface. It also devolves a lot more to the base class because doing so makes debug builds less of a hassle. The all-RAM 6502 has been shuffled off into a subfolder, to indicate that it's not something you necessarily need know about. Also general documentation improvements have been applied: incorrect citing of the recurring-template pattern has been removed and the meaning of the two BusHandler methods has now accrued at the bus handler.
2017-08-31 22:10:27 -04:00
Thomas Harte
a5f9869769 Merge pull request #230 from TomHarte/CyclicShutdown
Eliminates potential cyclic entry into CSMachine during its `-dealloc`.
2017-08-31 21:23:15 -04:00
Thomas Harte
f10be2a18a Eliminates potential cyclic entry into CSMachine during its -dealloc.
Explicit cause: dealloc calls close_output(). That may decide to flush work, indiscriminately. Some of the flushed work might be audio generation. Audio generation might cause the audio queue to react with an out-of-data announcement. Which would cause a fresh attempt to update the CSMachine.
2017-08-31 21:22:23 -04:00
Thomas Harte
c88d627b4e Merge pull request #229 from TomHarte/Skew
Adds an initial implementation of display skew to the 6845
2017-08-29 22:32:26 -04:00
Thomas Harte
b30bb2a234 Adds an initial implementation of display skew, as a completely live property. 2017-08-29 22:16:40 -04:00
Thomas Harte
d498080eb4 Merge pull request #228 from TomHarte/CRTCStatus
Takes initial steps towards supporting CRTC manufacturer diversity.
2017-08-27 22:26:40 -04:00
Thomas Harte
334afbc710 Removes const from get_status and get_register, as both may now logically mutate the object. 2017-08-27 18:13:55 -04:00
Thomas Harte
17c13624e5 Improved comments. 2017-08-27 18:11:40 -04:00
Thomas Harte
113349d272 Started making some formal admissions that different CRTC models exist. Plenty yet to do. 2017-08-27 18:10:07 -04:00
Thomas Harte
0ced7866fc Merge pull request #227 from TomHarte/NoCPCOptions
Removes the CPC options panel.
2017-08-27 17:12:24 -04:00
Thomas Harte
d06031dfcb Removes the options panel for CPC display. 2017-08-27 17:11:35 -04:00
Thomas Harte
3f22a71276 Merge pull request #226 from TomHarte/TargetAwareness
Substantially rewires Mac-side target selection and as proof-of-concept adapts the generic-side ZX80 to instantiate without wait line support
2017-08-27 16:55:01 -04:00
Thomas Harte
53a88a7e12 Causes the ZX80/81 to omit support for the wait line if being configured as a ZX80. 2017-08-27 16:45:36 -04:00
Thomas Harte
4a66dd9e82 Arranges for the ZX80/81 to get a peek at target configuration prior to construction. I'm as yet undecided on whether to make this the norm. 2017-08-27 16:42:16 -04:00
Thomas Harte
522839143f Revokes -[CSMachine init] and the slightly troubling create-on-demand semantics it places upon subclasses via .machine. Therefore each machine must announce its own implementation of -init. 2017-08-27 16:36:21 -04:00
Thomas Harte
b4c532c0d5 Merge pull request #225 from TomHarte/TargetHints
Factors the concept of a target platform out from the static analyser, allowing file formats to opine
2017-08-27 15:46:55 -04:00
Thomas Harte
a3e2d142e3 Extends UEF support to include chunk 0005, the target platform description, which is exposed via TargetPlatform::TypeDistinguisher. 2017-08-27 15:43:09 -04:00
Thomas Harte
63ee8c9d58 Uses file containers' type distinguishers where available, and supplies potential insight to the ZX80/81 analyser as now required. 2017-08-27 15:20:58 -04:00
Thomas Harte
437023bff6 Expands to take an already-accrued list of potential platforms, as that may indicate that one or the other of the ZX80 and ZX81 is already out of contention and therefore save the need to attempt analysis. 2017-08-27 15:20:22 -04:00
Thomas Harte
4465098157 Since it has descendants, gives Storage::Cartridge a virtual destructor. 2017-08-27 15:19:30 -04:00
Thomas Harte
56dd677e9c Creates a virtual interface that can be adopted by classes that are able to provide some insight as to target machine. 2017-08-27 15:19:03 -04:00
Thomas Harte
9aa150c338 Abstracts the target platform type out from the static analyser's ownership. 2017-08-27 15:02:13 -04:00
Thomas Harte
fab6908129 Corrects the all-RAM Z80 to declare that it needs the wait line to be implemented. 2017-08-26 23:18:11 -04:00
Thomas Harte
e34d4ce903 Merge pull request #224 from TomHarte/OptionalWait
Makes the Z80's support for WAIT input optional
2017-08-26 23:16:22 -04:00
Thomas Harte
d411827733 Merge branch 'master' into OptionalWait 2017-08-26 23:11:23 -04:00
Thomas Harte
f1ba7755dd Merge pull request #223 from TomHarte/cpctest
Moves test for 6845 horizontal sync timing into the time after phase 1 and before phase 2
2017-08-26 23:11:03 -04:00
Thomas Harte
57bfec285f Makes it optional whether the Z80 supports the wait line. If the wait line isn't in use, runtime costs are decreased because the optional wait cycles need not be iterated over. 2017-08-26 23:08:57 -04:00
Thomas Harte
bdda701207 Reverts previous unevidenced change. 2017-08-26 22:58:16 -04:00
Thomas Harte
487fe83dca Ensures that vertical sync and end-of-visible-lines conditions potentially trigger whenever line_counter_ changes, not only when it increments. 2017-08-26 17:54:54 -04:00
Thomas Harte
6c5a03187b Relocates the HSYNC start test, in order to pass Arnold's cpctest HSYNC start position conformance test. 2017-08-26 17:22:48 -04:00
Thomas Harte
97f57a3948 Merge pull request #222 from TomHarte/6845GetState
Refines observable 6845 behaviour
2017-08-26 14:46:29 -04:00
Thomas Harte
7d7aa2f5d5 Eliminates repetition of the unpacking of register 3 into a horizontal sync count. 2017-08-26 14:37:03 -04:00
Thomas Harte
e7ad79c79a Breaks apart the CPC's 6845 bus handler to obey phase 1 and phase 2, and now back-dates interrupts when appropriate. 2017-08-26 14:07:51 -04:00
Thomas Harte
28550c0227 Breaks the 6845 bus cycle into a phase 1 and a phase 2 per the belief that sync line changes, which are observable, happen at the end of the first phase rather than at the beginning of the next. This may have interrupt timing effects, as machines often derive an interrupt from sync. 2017-08-26 13:56:23 -04:00
Thomas Harte
6e99169348 Permits the 6845's bus state to be examined by an owner, eliminating the need to buffer it in the bus handler. But more than that it allows the CRTC to decide when it adjusts the various outputs respective to the main phase. So a net effect of the change is that the CPC now sees vsync a cycle earlier, because my current reading of the 6845 datasheet is that it is set at the end of phase 1, not the beginning of the next phase 1. 2017-08-26 12:59:59 -04:00
Thomas Harte
1017bb9f6b Merge pull request #221 from TomHarte/6845UpCount
Regularises the 6845 sync counters
2017-08-26 12:51:39 -04:00
Thomas Harte
3caa4705ca Limits sync counter size. 2017-08-26 12:31:19 -04:00
Thomas Harte
039aed1bd1 Switches the two sync counters to upward-going rather than downward, as a more likely match to the way the rest of the 6845 implementation. 2017-08-25 21:26:01 -04:00
Thomas Harte
d77d7fdd78 Merge pull request #220 from TomHarte/Analysis
Resolves all current analyser warnings.
2017-08-24 22:19:51 -04:00
Thomas Harte
c6e6c3fcfb Resolves all current analyser warnings. 2017-08-24 22:18:44 -04:00
Thomas Harte
ecd3350a6f Merge pull request #219 from TomHarte/ConstSafety
Makes all of PartialMachineCycle const
2017-08-24 22:04:06 -04:00
Thomas Harte
fa19e2d9c2 Removes some detritus. 2017-08-24 22:00:21 -04:00
Thomas Harte
95d360251d Makes all of PartialMachineCycle const, with the exception of the target of *value, since that's intended to be writeable by recipients. 2017-08-24 21:32:33 -04:00
Thomas Harte
7af3de010e Suspected my mode 1 interrupt timing might be off. Reminded myself of the sources. Persuaded myself that it wasn't. Added appropriate comments. 2017-08-23 22:25:31 -04:00
Thomas Harte
cefd421992 Merge pull request #218 from TomHarte/6845Factored
Refactors the 6845 to make end-of-line and end-of-frame conditions more explicit and to reduce repetition
2017-08-22 22:21:17 -04:00
Thomas Harte
a914eadc85 Ensured that register 6 is checked on every loop. 2017-08-22 22:17:45 -04:00
Thomas Harte
131b340d75 Dodges a lambda copy. 2017-08-22 21:55:10 -04:00
Thomas Harte
e956740c56 Refactors the 6845 more clearly to break out the acts of ending a line and ending a frame, changing the way the memory address is altered — the end-of-line value is provisionally stored and then used if necessary — in order to do so. 2017-08-22 21:54:48 -04:00
Thomas Harte
8afd83b91f Merge pull request #217 from TomHarte/CompiletimeOptions
Introduces compile-time selection of minor CPU core features and applies forceinline when appropriate
2017-08-21 22:29:24 -04:00
Thomas Harte
40d7a603db Ensured that forceinline does nothing in debug builds. 2017-08-21 22:04:15 -04:00
Thomas Harte
ee71be0e7e Added the option not to include ready line support in the 6502 core, and took advantage of it in the Electron, Oric and Vic-20 implementations. Also tagged those as forceinline and/or override final where applicable. 2017-08-21 21:56:42 -04:00
Thomas Harte
cde29c4bf4 Added forceinlines and properly declared finals and overrides. 2017-08-21 21:07:10 -04:00
Thomas Harte
e1aded0d95 Allows Z80 users to opt out of support for the bus request line. Which both now do. 2017-08-21 20:43:12 -04:00
Thomas Harte
1237f174fe Merge pull request #216 from TomHarte/NoiseReduciton
Cleans up issues affecting the sleeper mechanism and the CPC
2017-08-20 13:26:56 -04:00
Thomas Harte
0cbc1753b9 Quick fixes: the binary tape player now considers talk to the sleep observer only if motor control changes. The Amstrad CPC no longer attempts to use the component argument to identify the caller, since this will often be that of the superclass and not that of the derived class known to the CPC. 2017-08-20 13:18:46 -04:00
Thomas Harte
5cf0395936 Merge pull request #215 from TomHarte/Z80BusReq
Removes repeated checking of bus_request_line_ by the Z80.
2017-08-20 12:40:37 -04:00
Thomas Harte
6315c22b80 Removed repeated checking of bus_request_line_. It's now checked only after each outward perform_machine_cycle. 2017-08-20 12:39:45 -04:00
Thomas Harte
4614a56843 Merge pull request #214 from TomHarte/Sleeper
Experimentally introduces the concept of a 'sleeper' — a component that will volunteer to be unclocked for a period
2017-08-20 12:29:32 -04:00
Thomas Harte
8f5ae4a326 The CPC now responds to tape-originating sleeper observations. 2017-08-20 12:21:02 -04:00
Thomas Harte
8fdc5012e4 Updated TapePlayer and BinaryTapePlayer to be sleepers. 2017-08-20 12:18:36 -04:00
Thomas Harte
e88a51e75e Worked logic all the way down to the CPC. If the 8272 announces that it is asleep, it is now no longer clocked. Also very slightly cut down on IRQ line chatter to the Z80. 2017-08-20 12:05:00 -04:00
Thomas Harte
49285e9caa Attempted to implement Sleeper in Drive and therefore in DiskController. Also corrected a couple of nonconformant file names. 2017-08-20 11:54:54 -04:00
Thomas Harte
e3f2118757 Merge branch 'master' into Sleeper 2017-08-20 10:58:03 -04:00
Thomas Harte
daeaa4752f Merge pull request #213 from TomHarte/MinorPalette
Makes a variety of minor performance improvements to the CPC
2017-08-20 10:57:41 -04:00
Thomas Harte
5344e3098b Minor: made has_disk something that is decided on insertion/deletion. 2017-08-20 10:55:08 -04:00
Thomas Harte
cedb809c21 Sketched out a protocol designed to save processing time on anything that may sleep — probably just disk controllers for now but one can easily imagine it being applicable to printers, and possibly sound chips with suitable changes in guarantee for sound packet receivers. 2017-08-20 10:53:25 -04:00
Thomas Harte
2d9efccc98 Introduced a master 'is sleeping' flag. I'm starting to think there's a pattern forming here. 2017-08-20 10:43:53 -04:00
Thomas Harte
8ce46b6e49 Having spotted that I was using my single-character loop counter names incorrectly (quelle surprise!), got a bit more explicit. Also flattened into a single loop so that I can break rather than returning. 2017-08-20 10:32:09 -04:00
Thomas Harte
f2699a3f2b Okay, even if releasing it is unsafe, I can at least move the typer so that it is no longer called. 2017-08-20 10:24:01 -04:00
Thomas Harte
85253a5876 Sought further to reduce the processing footprint of palette changes by updating only those table entries that are affected by a change. 2017-08-20 10:13:23 -04:00
Thomas Harte
911ee5a0d3 At least added a fast return. 2017-08-19 22:22:51 -04:00
Thomas Harte
57c5b38a6d Step one towards cutting much of this cost: build only the table that's appropriate for the current mode, and at least declare when a more minimal change would be sufficient. 2017-08-19 22:19:46 -04:00
Thomas Harte
669e0caff5 Ensured the head_unload_delay values are properly seeded, and generalised the quick escape. 2017-08-19 22:06:56 -04:00
Thomas Harte
b24d04fc09 Merge pull request #212 from TomHarte/MFMParserDensity
Improves the variability of the MFM parser used for static analysis
2017-08-18 15:57:37 -04:00
Thomas Harte
ef07c33741 Merge branch 'Plus10' into MFMParserDensity 2017-08-18 15:48:20 -04:00
Thomas Harte
e559a65ede Ideally I would be able to kill this multiplier, as it could easily be derived at runtime. But, for now, just turned it up so that the analysis-oriented parser is better at parsing different bit rates. 2017-08-18 15:47:46 -04:00
Thomas Harte
5bdd24d93f Merge pull request #211 from TomHarte/HFE
Introduces support for the HFE file format.
2017-08-17 22:43:23 -04:00
Thomas Harte
af61a7fa28 Two quick fixes: correctly set segment size, and flip bytes to match HFE's bit ordering to PCMTrack's. 2017-08-17 22:28:00 -04:00
Thomas Harte
c8c1792c3f Made a first attempt at HFE support. 2017-08-17 22:20:02 -04:00
Thomas Harte
e6683e7f2d Added the base skeletal stuff of HFE support. 2017-08-17 21:48:48 -04:00
Thomas Harte
0c1714b695 Relaxed a little to allow +10% in track length. 2017-08-17 21:36:14 -04:00
Thomas Harte
dc0ca83003 Merge pull request #210 from TomHarte/TrackSize
Various MFM and DSK fixes
2017-08-17 15:50:27 -04:00
Thomas Harte
2c2dd8073c Modified to return nullptr if asked for an extended disk image track that doesn't exist. 2017-08-17 15:32:24 -04:00
Thomas Harte
4f8b89772e Improved logic for detecting when all sense has been derived from a track to spot any repeated track, not necessarily the first one. That avoids sectors that run over the index hold and obscure the first throwing things. 2017-08-17 15:31:53 -04:00
Thomas Harte
733ee5a5c3 Ensured no attempt to put a null track into the cache 2017-08-17 15:30:02 -04:00
Thomas Harte
fedf5a44a6 Imposes a maximum track length. 2017-08-17 15:20:49 -04:00
Thomas Harte
6a09022896 Merge pull request #209 from TomHarte/BootOnly
Generalises: an acceptable boot sector is always acceptable
2017-08-17 14:29:37 -04:00
Thomas Harte
5b3c707959 Generalised: an acceptable boot sector is acceptable even if no valid CP/M catalogue is found anywhere. 2017-08-17 14:28:16 -04:00
Thomas Harte
9b21ef1507 Merge pull request #208 from TomHarte/SpecialCharacters
Improves the CPC static analyser's correct file name predictions
2017-08-17 13:25:30 -04:00
Thomas Harte
da3e8655e9 Withdrew some caveman debugging nonsense. 2017-08-17 13:25:19 -04:00
Thomas Harte
41e4386164 Added another "one thing is different" test: one thing has a different file name. Also decided to right-time the type (/extension) as well as the file name. 2017-08-17 13:21:48 -04:00
Thomas Harte
b0a98bd239 Added nuance: file names with unprintables are filtered, and then system files are considered if there are no remaining non-system files. 2017-08-17 12:48:15 -04:00
Thomas Harte
42ad670ec8 Fixed: catalogue bitmap is in blocks, not sectors. 2017-08-17 12:47:47 -04:00
Thomas Harte
58063b69a6 Merge pull request #207 from TomHarte/DragAndDrop
Establishes drag and drop as a mechanism to change the media inserted into a machine while it is running
2017-08-17 11:19:56 -04:00
Thomas Harte
378f231499 Fully wired in drag-and-drop for media insertion. 2017-08-17 11:00:08 -04:00
Thomas Harte
f68565a33f Split the static analyser functionality so that it's possible just to ask for the set of media implied by a particular file. Extended ConfigurationTarget so that media alone can be pushed to a machine. 2017-08-17 10:48:29 -04:00
Thomas Harte
175faebdc9 Merge pull request #206 from TomHarte/AnalysisFailure
Corrects a couple of cases in which the CPC analyser could pick an incorrect loading command
2017-08-16 22:26:26 -04:00
Thomas Harte
76c6b715a2 Adjusted rules so as not to type unnecessary spaces in the name, and to include the extension if AMSDOS won't imply it. 2017-08-16 22:24:37 -04:00
Thomas Harte
b476f06524 Slowed the typer, having discovered that otherwise it has problems transitioning from a shifted to an unshifted character. 2017-08-16 22:12:16 -04:00
Thomas Harte
48290a8bbe Added a prefilter to catalogues to remove system files. They're not listed when you CAT, so almost certainly aren't what a user would be expected to load. 2017-08-16 22:11:49 -04:00
Thomas Harte
9d9a1c341d Added an extra test to try to avoid spurious |cpm launches. 2017-08-16 21:55:31 -04:00
Thomas Harte
952da1e581 Merge pull request #205 from TomHarte/CPCShots
Adds a CPC screenshot, to show that this isn't just about composite video
2017-08-16 21:05:55 -04:00
Thomas Harte
a988255558 Adjusted brightness better to match its comparison. And to try partly to obscure its source. This isn't meant to be about software X versus software Y. 2017-08-16 21:04:09 -04:00
Thomas Harte
cca66ab450 Added a CPC grab, to show that it's not all about being composite. 2017-08-16 21:01:22 -04:00
Thomas Harte
bcd7a312a4 Merge pull request #204 from TomHarte/VicInline
Brings the Vic-20 into line with the new idiom on machine declaration
2017-08-16 16:24:38 -04:00
Thomas Harte
925e774015 Added a decent portion of documentation. But started feeling like I should address my various ownership decisions. Which would justify a separate pull request. 2017-08-16 16:23:33 -04:00
Thomas Harte
4c15e46fd1 Performed the normative removal from public view of Vic-20 implementation details. Which were hefty. 2017-08-16 16:05:30 -04:00
Thomas Harte
3c50903a2b Merge pull request #203 from TomHarte/ElectronInline
Adds the Electron to the pantheon of machines that reveal very little in their public interface
2017-08-16 15:35:11 -04:00
Thomas Harte
75208b0762 Moves the Electron implementation behind a more opaque interface, in line with changes elsewhere. 2017-08-16 15:33:40 -04:00
Thomas Harte
f1e64169cd Merge pull request #202 from TomHarte/AtariInline
Catches the Atari 2600 up with the no-internals-published trend.
2017-08-16 14:54:33 -04:00
Thomas Harte
903a17ae11 Corrected typo and removed replication of what's already declared formally. 2017-08-16 14:53:03 -04:00
Thomas Harte
de1c526789 Cut the amount disclosed by the Atari 2600 for public inspection down to the minimum, relocating implementation into the .cpp. 2017-08-16 14:52:40 -04:00
Thomas Harte
b7e0f64892 Merge pull request #201 from TomHarte/OricInline
Hides the Oric implementation innards
2017-08-16 14:37:42 -04:00
Thomas Harte
148591b7f2 Hid most of the Oric innards, and corrected a potential multi-thread access error emanating from the Mac side of the world. 2017-08-16 14:35:53 -04:00
Thomas Harte
2105597910 Merge pull request #200 from TomHarte/6502BusHandler
Converts the 6502 into a bus-handler-type template, and makes appropriate adjustments to all 6502 machines
2017-08-16 14:10:08 -04:00
Thomas Harte
3c148f5721 Fixed clanger of an error. 2017-08-16 14:02:46 -04:00
Thomas Harte
360c8a99a3 Adjusted Atari2600 actually to use the nominated type of bus extender. 2017-08-16 12:57:32 -04:00
Thomas Harte
06e31f5102 Consequential to the 6502 change, severs the Atari 2600's cartridge container from its former attempt at runtime polymorphism, in favour of each cartridge's specific hardware being defined as a 'bus extender'. 2017-08-16 12:39:15 -04:00
Thomas Harte
42b5b66305 Remove the 6502's use of runtime polymorphism in favour of ordinary templating. 2017-08-16 11:56:52 -04:00
322 changed files with 15606 additions and 10910 deletions

4
.gitignore vendored
View File

@@ -22,6 +22,10 @@ DerivedData
ROMImages/*
OSBindings/Mac/Clock SignalTests/Atari\ ROMs
# Exclude intermediate build products
*.o
.sconsign.dblite
# CocoaPods
#
# We recommend against adding the Pods directory to your .gitignore. However

30
BUILD.txt Normal file
View File

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

View File

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

@@ -6,8 +6,14 @@
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef ForceInline_h
#define ForceInline_h
#ifndef ForceInline_hpp
#define ForceInline_hpp
#ifdef DEBUG
#define forceinline
#else
#ifdef __GNUC__
#define forceinline __attribute__((always_inline)) inline
@@ -15,4 +21,6 @@
#define forceinline __forceinline
#endif
#endif
#endif /* ForceInline_h */

60
ClockReceiver/Sleeper.hpp Normal file
View File

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

View File

@@ -7,34 +7,16 @@
//
#include "1770.hpp"
#include "../../Storage/Disk/Encodings/MFM.hpp"
#include "../../Storage/Disk/Encodings/MFM/Constants.hpp"
using namespace WD;
WD1770::Status::Status() :
type(Status::One),
write_protect(false),
record_type(false),
spin_up(false),
record_not_found(false),
crc_error(false),
seek_error(false),
lost_data(false),
data_request(false),
interrupt_request(false),
busy(false) {}
WD1770::WD1770(Personality p) :
Storage::Disk::MFMController(8000000, 16, 300),
interesting_event_mask_((int)Event1770::Command),
resume_point_(0),
delay_time_(0),
index_hole_count_target_(-1),
delegate_(nullptr),
Storage::Disk::MFMController(8000000),
personality_(p),
head_is_loaded_(false) {
interesting_event_mask_(static_cast<int>(Event1770::Command)) {
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 +29,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 +57,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 +73,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 +97,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 END_SECTION() (void)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 +151,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 +169,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 +239,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 +262,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 +292,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 +358,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 +368,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 +376,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 +576,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 +593,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 +634,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;
}
@@ -674,8 +656,7 @@ void WD1770::posit_event(int new_event_type) {
status.lost_data = false;
});
write_track_test_write_protect:
if(get_drive_is_read_only()) {
if(get_drive().get_is_read_only()) {
update_status([] (Status &status) {
status.write_protect = true;
});
@@ -720,7 +701,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 +762,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:
@@ -84,20 +85,19 @@ class WD1770: public Storage::Disk::MFMController {
inline bool has_head_load_line() { return (personality_ == P1793 ); }
struct Status {
Status();
bool write_protect;
bool record_type;
bool spin_up;
bool record_not_found;
bool crc_error;
bool seek_error;
bool lost_data;
bool data_request;
bool interrupt_request;
bool busy;
bool write_protect = false;
bool record_type = false;
bool spin_up = false;
bool record_not_found = false;
bool crc_error = false;
bool seek_error = false;
bool lost_data = false;
bool data_request = false;
bool interrupt_request = false;
bool busy = false;
enum {
One, Two, Three
} type;
} type = One;
} status_;
uint8_t track_;
uint8_t sector_;
@@ -105,7 +105,7 @@ class WD1770: public Storage::Disk::MFMController {
uint8_t command_;
int index_hole_count_;
int index_hole_count_target_;
int index_hole_count_target_ = -1;
int distance_into_section_;
int step_direction_;
@@ -120,17 +120,17 @@ class WD1770: public Storage::Disk::MFMController {
};
void posit_event(int type);
int interesting_event_mask_;
int resume_point_;
unsigned int delay_time_;
int resume_point_ = 0;
unsigned int delay_time_ = 0;
// ID buffer
uint8_t header_[6];
// 1793 head-loading logic
bool head_is_loaded_;
bool head_is_loaded_ = false;
// delegate
Delegate *delegate_;
Delegate *delegate_ = nullptr;
};
}

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) {
@@ -121,12 +121,9 @@ template <class T> class MOS6532 {
}
}
MOS6532() :
interrupt_status_(0),
port_{{.output_mask = 0, .output = 0}, {.output_mask = 0, .output = 0}},
a7_interrupt_({.last_port_value = 0, .enabled = false}),
interrupt_line_(false),
timer_{.value = (unsigned int)((rand() & 0xff) << 10), .activeShift = 10, .writtenShift = 10, .interrupt_enabled = false} {}
MOS6532() {
timer_.value = static_cast<unsigned int>((rand() & 0xff) << 10);
}
inline void set_port_did_change(int port) {
if(!port) {
@@ -154,26 +151,26 @@ template <class T> class MOS6532 {
struct {
unsigned int value;
unsigned int activeShift, writtenShift;
bool interrupt_enabled;
unsigned int activeShift = 10, writtenShift = 10;
bool interrupt_enabled = false;
} timer_;
struct {
bool enabled;
bool active_on_positive;
uint8_t last_port_value;
bool enabled = false;
bool active_on_positive = false;
uint8_t last_port_value = 0;
} a7_interrupt_;
struct {
uint8_t output_mask, output;
uint8_t output_mask = 0, output = 0;
} port_[2];
uint8_t interrupt_status_;
uint8_t interrupt_status_ = 0;
enum InterruptFlag: uint8_t {
Timer = 0x80,
PA7 = 0x40
};
bool interrupt_line_;
bool interrupt_line_ = false;
// expected to be overridden
uint8_t get_port_input(int port) { return 0xff; }

View File

@@ -8,13 +8,9 @@
#include "6560.hpp"
using namespace MOS;
#include <cstring>
Speaker::Speaker() :
volume_(0),
control_registers_{0, 0, 0, 0},
shift_registers_{0, 0, 0, 0},
counters_{2, 1, 0, 0} {} // create a slight phase offset for the three channels
using namespace MOS;
void Speaker::set_volume(uint8_t volume) {
enqueue([=]() {
@@ -98,7 +94,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

@@ -18,8 +18,6 @@ namespace MOS {
// audio state
class Speaker: public ::Outputs::Filter<Speaker> {
public:
Speaker();
void set_volume(uint8_t volume);
void set_control(int channel, uint8_t value);
@@ -27,10 +25,10 @@ class Speaker: public ::Outputs::Filter<Speaker> {
void skip_samples(unsigned int number_of_samples);
private:
unsigned int counters_[4];
unsigned int shift_registers_[4];
uint8_t control_registers_[4];
uint8_t volume_;
unsigned int counters_[4] = {2, 1, 0, 0}; // create a slight phase offset for the three channels
unsigned int shift_registers_[4] = {0, 0, 0, 0};
uint8_t control_registers_[4] = {0, 0, 0, 0};
uint8_t volume_ = 0;
};
/*!
@@ -44,13 +42,8 @@ class Speaker: public ::Outputs::Filter<Speaker> {
template <class T> class MOS6560 {
public:
MOS6560() :
crt_(new Outputs::CRT::CRT(65*4, 4, Outputs::CRT::NTSC60, 2)),
speaker_(new Speaker),
horizontal_counter_(0),
vertical_counter_(0),
cycles_since_speaker_update_(0),
is_odd_frame_(false),
is_odd_line_(false) {
crt_(new Outputs::CRT::CRT(65*4, 4, Outputs::CRT::DisplayType::NTSC60, 2)),
speaker_(new Speaker) {
crt_->set_composite_sampling_function(
"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
"{"
@@ -66,7 +59,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_; }
@@ -109,9 +102,9 @@ template <class T> class MOS6560 {
Outputs::CRT::DisplayType display_type;
switch(output_mode) {
case OutputMode::PAL:
default:
chrominances = pal_chrominances;
display_type = Outputs::CRT::PAL50;
display_type = Outputs::CRT::DisplayType::PAL50;
timing_.cycles_per_line = 71;
timing_.line_counter_increment_offset = 0;
timing_.lines_per_progressive_field = 312;
@@ -120,7 +113,7 @@ template <class T> class MOS6560 {
case OutputMode::NTSC:
chrominances = ntsc_chrominances;
display_type = Outputs::CRT::NTSC60;
display_type = Outputs::CRT::DisplayType::NTSC60;
timing_.cycles_per_line = 65;
timing_.line_counter_increment_offset = 65 - 33; // TODO: this is a bit of a hack; separate vertical and horizontal counting
timing_.lines_per_progressive_field = 261;
@@ -128,7 +121,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 +134,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 +211,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 +263,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 +338,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 +347,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 +392,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;
}
}
@@ -432,7 +425,7 @@ template <class T> class MOS6560 {
unsigned int cycles_in_state_;
// counters that cover an entire field
int horizontal_counter_, vertical_counter_, full_frame_counter_;
int horizontal_counter_ = 0, vertical_counter_ = 0, full_frame_counter_;
// latches dictating start and length of drawing
bool vertical_drawing_latch_, horizontal_drawing_latch_;
@@ -447,14 +440,14 @@ template <class T> class MOS6560 {
// data latched from the bus
uint8_t character_code_, character_colour_, character_value_;
bool is_odd_frame_, is_odd_line_;
bool is_odd_frame_ = false, is_odd_line_ = false;
// lookup table from 6560 colour index to appropriate PAL/NTSC value
uint16_t colours_[16];
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,141 +18,65 @@ 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 {
public:
void perform_bus_cycle(const BusState &) {}
/*!
Performs the first phase of a 6845 bus cycle; this is the phase in which it is intended that
systems using the 6845 respect the bus state and produce pixels, sync or whatever they require.
*/
void perform_bus_cycle_phase1(const BusState &) {}
/*!
Performs the second phase of a 6845 bus cycle. Some bus state — including sync — is updated
directly after phase 1 and hence is visible to an observer during phase 2. Handlers may therefore
implement @c perform_bus_cycle_phase2 to be notified of the availability of that state without
having to wait until the next cycle has begun.
*/
void perform_bus_cycle_phase2(const BusState &) {}
};
enum Personality {
HD6845S, //
UM6845R, //
MC6845, //
AMS40226 //
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) :
personality_(p), bus_handler_(bus_handler) {}
void run_for(Cycles cycles) {
int cyles_remaining = cycles.as_int();
while(cyles_remaining--) {
// check for end of horizontal sync
if(hsync_down_counter_) {
hsync_down_counter_--;
if(!hsync_down_counter_) {
bus_state_.hsync = false;
}
}
// check for end of line
bool is_end_of_line = character_counter_ == registers_[0];
// increment counter
character_counter_++;
// check for start of horizontal sync
if(character_counter_ == registers_[2]) {
hsync_down_counter_ = registers_[3] & 15;
if(hsync_down_counter_) bus_state_.hsync = true;
}
// update refresh address
if(character_is_visible_) {
bus_state_.refresh_address++;
}
// check for end of visible characters
if(character_counter_ == registers_[1]) {
character_is_visible_ = false;
}
// check for end-of-line
if(is_end_of_line) {
// check for end of vertical sync
if(vsync_down_counter_) {
vsync_down_counter_--;
if(!vsync_down_counter_) {
bus_state_.vsync = false;
}
}
if(is_in_adjustment_period_) {
line_counter_++;
if(line_counter_ == registers_[5]) {
line_counter_ = 0;
is_in_adjustment_period_ = false;
line_is_visible_ = true;
line_address_ = (uint16_t)((registers_[12] << 8) | registers_[13]);
bus_state_.refresh_address = line_address_;
}
} else {
// advance vertical counter
if(bus_state_.row_address == registers_[9]) {
if(!character_is_visible_)
line_address_ = bus_state_.refresh_address;
bus_state_.row_address = 0;
bool is_at_end_of_frame = line_counter_ == registers_[4];
line_counter_ = (line_counter_ + 1) & 0x7f;
// check for end of visible lines
if(line_counter_ == registers_[6]) {
line_is_visible_ = false;
}
// check for start of vertical sync
if(line_counter_ == registers_[7]) {
bus_state_.vsync = true;
vsync_down_counter_ = registers_[3] >> 4;
if(!vsync_down_counter_) vsync_down_counter_ = 16;
}
// check for entry into the overflow area
if(is_at_end_of_frame) {
if(registers_[5]) {
is_in_adjustment_period_ = true;
} else {
line_is_visible_ = true;
line_address_ = (uint16_t)((registers_[12] << 8) | registers_[13]);
bus_state_.refresh_address = line_address_;
}
bus_state_.row_address = 0;
line_counter_ = 0;
}
} else {
bus_state_.row_address = (bus_state_.row_address + 1) & 0x1f;
}
bus_state_.refresh_address = line_address_;
}
character_counter_ = 0;
character_is_visible_ = (registers_[1] != 0);
}
perform_bus_cycle();
}
}
CRTC6845(Personality p, T &bus_handler) noexcept :
personality_(p), bus_handler_(bus_handler), status_(0) {}
void select_register(uint8_t r) {
selected_register_ = r;
}
uint8_t get_status() {
switch(personality_) {
case UM6845R: return status_ | (bus_state_.vsync ? 0x20 : 0x00);
case AMS40226: return get_register();
default: return 0xff;
}
return 0xff;
}
uint8_t get_register() {
if(selected_register_ == 31) status_ &= ~0x80;
if(selected_register_ == 16 || selected_register_ == 17) status_ &= ~0x40;
if(personality_ == UM6845R && selected_register_ == 31) return dummy_register_;
if(selected_register_ < 12 || selected_register_ > 17) return 0xff;
return registers_[selected_register_];
}
@@ -163,38 +87,186 @@ template <class T> class CRTC6845 {
0xff, 0x1f, 0x7f, 0x1f, 0x3f, 0xff, 0x3f, 0xff
};
if(selected_register_ < 16)
// Per CPC documentation, skew doesn't work on a "type 1 or 2", i.e. an MC6845 or a UM6845R.
if(selected_register_ == 8 && personality_ != UM6845R && personality_ != MC6845) {
switch((value >> 4)&3) {
default: display_skew_mask_ = 1; break;
case 1: display_skew_mask_ = 2; break;
case 2: display_skew_mask_ = 4; break;
}
}
if(selected_register_ < 16) {
registers_[selected_register_] = value & masks[selected_register_];
}
if(selected_register_ == 31 && personality_ == UM6845R) {
dummy_register_ = value;
}
}
void trigger_light_pen() {
registers_[17] = bus_state_.refresh_address & 0xff;
registers_[16] = bus_state_.refresh_address >> 8;
status_ |= 0x40;
}
void run_for(Cycles cycles) {
int cyles_remaining = cycles.as_int();
while(cyles_remaining--) {
// check for end of visible characters
if(character_counter_ == registers_[1]) {
// TODO: consider skew in character_is_visible_. Or maybe defer until perform_bus_cycle?
character_is_visible_ = false;
end_of_line_address_ = bus_state_.refresh_address;
}
perform_bus_cycle_phase1();
bus_state_.refresh_address = (bus_state_.refresh_address + 1) & 0x3fff;
// check for end-of-line
if(character_counter_ == registers_[0]) {
character_counter_ = 0;
do_end_of_line();
character_is_visible_ = true;
} else {
// increment counter
character_counter_++;
}
// check for start of horizontal sync
if(character_counter_ == registers_[2]) {
hsync_counter_ = 0;
bus_state_.hsync = true;
}
// check for end of horizontal sync; note that a sync time of zero will result in an immediate
// cancellation of the plan to perform sync if this is an HD6845S or UM6845R; otherwise zero
// will end up counting as 16 as it won't be checked until after overflow.
if(bus_state_.hsync) {
switch(personality_) {
case HD6845S:
case UM6845R:
bus_state_.hsync = hsync_counter_ != (registers_[3] & 15);
hsync_counter_ = (hsync_counter_ + 1) & 15;
break;
default:
hsync_counter_ = (hsync_counter_ + 1) & 15;
bus_state_.hsync = hsync_counter_ != (registers_[3] & 15);
break;
}
}
perform_bus_cycle_phase2();
}
}
const BusState &get_bus_state() const {
return bus_state_;
}
private:
inline void perform_bus_cycle() {
bus_state_.display_enable = character_is_visible_ && line_is_visible_;
bus_state_.refresh_address &= 0x3fff;
bus_handler_.perform_bus_cycle(bus_state_);
inline void perform_bus_cycle_phase1() {
// Skew theory of operation: keep a history of the last three states, and apply whichever is selected.
character_is_visible_shifter_ = (character_is_visible_shifter_ << 1) | static_cast<unsigned int>(character_is_visible_);
bus_state_.display_enable = (static_cast<int>(character_is_visible_shifter_) & display_skew_mask_) && line_is_visible_;
bus_handler_.perform_bus_cycle_phase1(bus_state_);
}
inline void perform_bus_cycle_phase2() {
bus_handler_.perform_bus_cycle_phase2(bus_state_);
}
inline void do_end_of_line() {
// check for end of vertical sync
if(bus_state_.vsync) {
vsync_counter_ = (vsync_counter_ + 1) & 15;
// on the UM6845R and AMS40226, honour the programmed vertical sync time; on the other CRTCs
// always use a vertical sync count of 16.
switch(personality_) {
case HD6845S:
case AMS40226:
bus_state_.vsync = vsync_counter_ != (registers_[3] >> 4);
break;
default:
bus_state_.vsync = vsync_counter_ != 0;
break;
}
}
if(is_in_adjustment_period_) {
line_counter_++;
if(line_counter_ == registers_[5]) {
is_in_adjustment_period_ = false;
do_end_of_frame();
}
} else {
// advance vertical counter
if(bus_state_.row_address == registers_[9]) {
bus_state_.row_address = 0;
line_address_ = end_of_line_address_;
// check for entry into the overflow area
if(line_counter_ == registers_[4]) {
if(registers_[5]) {
line_counter_ = 0;
is_in_adjustment_period_ = true;
} else {
do_end_of_frame();
}
} else {
line_counter_ = (line_counter_ + 1) & 0x7f;
// check for start of vertical sync
if(line_counter_ == registers_[7]) {
bus_state_.vsync = true;
vsync_counter_ = 0;
}
// check for end of visible lines
if(line_counter_ == registers_[6]) {
line_is_visible_ = false;
}
}
} else {
bus_state_.row_address = (bus_state_.row_address + 1) & 0x1f;
}
}
bus_state_.refresh_address = line_address_;
character_counter_ = 0;
character_is_visible_ = (registers_[1] != 0);
}
inline void do_end_of_frame() {
line_counter_ = 0;
line_is_visible_ = true;
line_address_ = static_cast<uint16_t>((registers_[12] << 8) | registers_[13]);
bus_state_.refresh_address = line_address_;
}
Personality personality_;
T &bus_handler_;
BusState bus_state_;
uint8_t registers_[18];
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_down_counter_;
int vsync_down_counter_;
bool is_in_adjustment_period_;
uint16_t line_address_;
int hsync_counter_ = 0;
int vsync_counter_ = 0;
bool is_in_adjustment_period_ = false;
uint16_t line_address_ = 0;
uint16_t end_of_line_address_ = 0;
uint8_t status_ = 0;
int display_skew_mask_ = 1;
unsigned int character_is_visible_shifter_ = 0;
};
}

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,27 +77,26 @@ 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() {
return is_sleeping_ && Storage::Disk::MFMController::is_sleeping();
}
void i8272::run_for(Cycles cycles) {
Storage::Disk::MFMController::run_for(cycles);
if(is_sleeping_) return;
// check for an expired timer
if(delay_time_ > 0) {
if(cycles.as_int() >= delay_time_) {
delay_time_ = 0;
posit_event((int)Event8272::Timer);
posit_event(static_cast<int>(Event8272::Timer));
} else {
delay_time_ -= cycles.as_int();
}
@@ -103,6 +104,7 @@ void i8272::run_for(Cycles cycles) {
// update seek status of any drives presently seeking
if(drives_seeking_) {
int drives_left = drives_seeking_;
for(int c = 0; c < 4; c++) {
if(drives_[c].phase == Drive::Seeking) {
drives_[c].step_rate_counter += cycles.as_int();
@@ -112,37 +114,52 @@ 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;
}
}
drives_left--;
if(!drives_left) break;
}
}
}
// check for any head unloads
if(head_timers_running_) {
for(int c = 0; c < 4; c++) {
for(int h = 0; h < 2; h++) {
if(drives_[c].head_unload_delay[c] > 0) {
if(cycles.as_int() >= drives_[c].head_unload_delay[c]) {
drives_[c].head_unload_delay[c] = 0;
drives_[c].head_is_loaded[c] = false;
head_timers_running_--;
if(!head_timers_running_) return;
} else {
drives_[c].head_unload_delay[c] -= cycles.as_int();
}
int timers_left = head_timers_running_;
for(int c = 0; c < 8; c++) {
int drive = (c >> 1);
int head = c&1;
if(drives_[drive].head_unload_delay[head] > 0) {
if(cycles.as_int() >= drives_[drive].head_unload_delay[head]) {
drives_[drive].head_unload_delay[head] = 0;
drives_[drive].head_is_loaded[head] = false;
head_timers_running_--;
} else {
drives_[drive].head_unload_delay[head] -= cycles.as_int();
}
timers_left--;
if(!timers_left) break;
}
}
}
// check for busy plus ready disabled
if(is_executing_ && !get_drive().get_is_ready()) {
posit_event(static_cast<int>(Event8272::NoLongerReady));
}
is_sleeping_ = !delay_time_ && !drives_seeking_ && !head_timers_running_;
if(is_sleeping_) update_sleep_observer();
}
void i8272::set_register(int address, uint8_t value) {
@@ -159,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));
}
}
@@ -168,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 {
@@ -176,35 +193,29 @@ 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); 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__); \
CONCAT(header_found, __LINE__): 0;\
CONCAT(header_found, __LINE__): (void)0;\
#define FIND_DATA() \
set_data_mode(DataMode::Scanning); \
CONCAT(find_data, __LINE__): WAIT_FOR_EVENT((int)Event::Token | (int)Event::IndexHole); \
if(event_type == (int)Event::Token) { \
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__); \
}
@@ -219,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; \
@@ -245,12 +256,18 @@ void i8272::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
if(drives_[active_drive_].head_is_loaded[active_head_]) {\
if(drives_[active_drive_].head_unload_delay[active_head_] == 0) { \
head_timers_running_++; \
is_sleeping_ = false; \
update_sleep_observer(); \
} \
drives_[active_drive_].head_unload_delay[active_head_] = MS_TO_CYCLES(head_unload_time_);\
}
void i8272::posit_event(int event_type) {
if(event_type == (int)Event::IndexHole) index_hole_count_++;
if(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;
@@ -274,7 +291,7 @@ void i8272::posit_event(int event_type) {
WAIT_FOR_EVENT(Event8272::CommandByte)
SetBusy();
static const size_t required_lengths[32] = {
static const std::size_t required_lengths[32] = {
0, 0, 9, 3, 2, 9, 9, 2,
1, 9, 2, 0, 9, 6, 0, 3,
0, 9, 0, 0, 0, 0, 0, 0,
@@ -320,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.
@@ -409,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();
@@ -440,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;
@@ -486,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;
}
@@ -509,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_);
@@ -541,7 +563,6 @@ void i8272::posit_event(int event_type) {
// Sets a maximum index hole limit of 2 then waits either until it finds a header mark or sees too many index holes.
// If a header mark is found, reads in the following bytes that produce a header. Otherwise branches to data not found.
index_hole_limit_ = 2;
read_id_find_next_sector:
FIND_HEADER();
if(!index_hole_limit_) {
SetMissingAddressMark();
@@ -589,7 +610,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;
@@ -601,7 +622,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;
}
@@ -626,14 +647,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;
@@ -664,8 +684,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();
@@ -694,10 +714,13 @@ 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) {
drives_seeking_++;
is_sleeping_ = false;
update_sleep_observer();
}
// Set currently seeking, with a step to occur right now (yes, it sounds like jamming these
@@ -721,7 +744,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_--;
}
@@ -744,14 +767,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;
@@ -773,24 +795,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;
@@ -798,14 +824,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;
@@ -813,12 +832,13 @@ void i8272::posit_event(int event_type) {
// last thing in it will be returned first.
post_result:
printf("Result to %02x, main %02x: ", command_[0] & 0x1f, main_status_);
for(size_t c = 0; c < result_stack_.size(); c++) {
for(std::size_t c = 0; c < result_stack_.size(); c++) {
printf("%02x ", result_stack_[result_stack_.size() - 1 - c]);
}
printf("\n");
// Set ready to send data to the processor, no longer in non-DMA execution phase.
is_executing_ = false;
ResetNonDMAExecution();
SetDataRequest();
SetDataDirectionToProcessor();
@@ -833,9 +853,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,7 +39,10 @@ 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.
@@ -47,86 +50,83 @@ class i8272: public Storage::Disk::MFMController {
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} {};
} 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_ = false;
};
}

View File

@@ -10,16 +10,7 @@
using namespace GI::AY38910;
AY38910::AY38910() :
selected_register_(0),
tone_counters_{0, 0, 0}, tone_periods_{0, 0, 0}, tone_outputs_{0, 0, 0},
noise_shift_register_(0xffff), noise_period_(0), noise_counter_(0), noise_output_(0),
envelope_divider_(0), envelope_period_(0), envelope_position_(0),
master_divider_(0),
output_registers_{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
port_handler_(nullptr) {
output_registers_[8] = output_registers_[9] = output_registers_[10] = 0;
AY38910::AY38910() {
// set up envelope lookup tables
for(int c = 0; c < 16; c++) {
for(int p = 0; p < 32; p++) {
@@ -67,13 +58,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,14 +151,14 @@ void AY38910::evaluate_output_volume() {
#undef channel_volume
// Mix additively.
output_volume_ = (int16_t)(
output_volume_ = static_cast<int16_t>(
volumes_[volumes[0]] * channel_levels[0] +
volumes_[volumes[1]] * channel_levels[1] +
volumes_[volumes[2]] * channel_levels[2]
);
}
#pragma mark - Register manipulation
// MARK: - Register manipulation
void AY38910::select_register(uint8_t r) {
selected_register_ = r;
@@ -186,7 +177,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 +192,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:
@@ -233,13 +224,13 @@ uint8_t AY38910::get_register_value() {
}
}
#pragma mark - Port handling
// MARK: - Port handling
uint8_t AY38910::get_port_output(bool port_b) {
return registers_[port_b ? 15 : 14];
}
#pragma mark - Bus handling
// MARK: - Bus handling
void AY38910::set_port_handler(PortHandler *handler) {
port_handler_ = handler;
@@ -262,15 +253,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

@@ -26,7 +26,7 @@ class PortHandler {
public:
/*!
Requests the current input on an AY port.
@param port_b @c true if the input being queried is Port B. @c false if it is Port A.
*/
virtual uint8_t get_port_input(bool port_b) {
@@ -35,7 +35,7 @@ class PortHandler {
/*!
Requests the current input on an AY port.
@param port_b @c true if the input being queried is Port B. @c false if it is Port A.
*/
virtual void set_port_output(bool port_b, uint8_t value) {}
@@ -89,24 +89,25 @@ class AY38910: public ::Outputs::Filter<AY38910> {
void get_samples(unsigned int number_of_samples, int16_t *target);
private:
int selected_register_;
uint8_t registers_[16], output_registers_[16];
int selected_register_ = 0;
uint8_t registers_[16];
uint8_t output_registers_[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
uint8_t port_inputs_[2];
int master_divider_;
int master_divider_ = 0;
int tone_periods_[3];
int tone_counters_[3];
int tone_outputs_[3];
int tone_periods_[3] = {0, 0, 0};
int tone_counters_[3] = {0, 0, 0};
int tone_outputs_[3] = {0, 0, 0};
int noise_period_;
int noise_counter_;
int noise_shift_register_;
int noise_output_;
int noise_period_ = 0;
int noise_counter_ = 0;
int noise_shift_register_ = 0xffff;
int noise_output_ = 0;
int envelope_period_;
int envelope_divider_;
int envelope_position_;
int envelope_period_ = 0;
int envelope_divider_ = 0;
int envelope_position_ = 0;
int envelope_shapes_[16][32];
int envelope_overflow_masks_[16];
@@ -129,7 +130,7 @@ class AY38910: public ::Outputs::Filter<AY38910> {
inline void evaluate_output_volume();
inline void update_bus();
PortHandler *port_handler_;
PortHandler *port_handler_ = nullptr;
};
}

View File

@@ -9,10 +9,12 @@
#ifndef AsyncTaskQueue_hpp
#define AsyncTaskQueue_hpp
#include <atomic>
#include <condition_variable>
#include <functional>
#include <list>
#include <memory>
#include <thread>
#include <list>
#include <condition_variable>
#ifdef __APPLE__
#include <dispatch/dispatch.h>

View File

@@ -0,0 +1,72 @@
//
// 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;
BestEffortUpdater::BestEffortUpdater() {
// ATOMIC_FLAG_INIT isn't necessarily safe to use, so establish default state by other means.
update_is_ongoing_.clear();
}
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,65 @@
//
// 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:
BestEffortUpdater();
/// 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 */

View File

@@ -0,0 +1,27 @@
//
// Configurable.cpp
// Clock Signal
//
// Created by Thomas Harte on 18/11/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "Configurable.hpp"
using namespace Configurable;
ListSelection *BooleanSelection::list_selection() {
return new ListSelection(value ? "yes" : "no");
}
ListSelection *ListSelection::list_selection() {
return new ListSelection(value);
}
BooleanSelection *ListSelection::boolean_selection() {
return new BooleanSelection(value != "no" && value != "n");
}
BooleanSelection *BooleanSelection::boolean_selection() {
return new BooleanSelection(value);
}

View File

@@ -0,0 +1,88 @@
//
// Configurable.h
// Clock Signal
//
// Created by Thomas Harte on 17/11/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef Configurable_h
#define Configurable_h
#include <map>
#include <memory>
#include <string>
#include <vector>
namespace Configurable {
/*!
The Option class hierarchy provides a way for components, machines, etc, to provide a named
list of typed options to which they can respond.
*/
struct Option {
std::string long_name;
std::string short_name;
virtual ~Option() {}
Option(const std::string &long_name, const std::string &short_name) : long_name(long_name), short_name(short_name) {}
};
struct BooleanOption: public Option {
BooleanOption(const std::string &long_name, const std::string &short_name) : Option(long_name, short_name) {}
};
struct ListOption: public Option {
std::vector<std::string> options;
ListOption(const std::string &long_name, const std::string &short_name, const std::vector<std::string> &options) : Option(long_name, short_name), options(options) {}
};
struct BooleanSelection;
struct ListSelection;
/*!
Selections are responses to Options.
*/
struct Selection {
virtual ~Selection() {}
virtual ListSelection *list_selection() = 0;
virtual BooleanSelection *boolean_selection() = 0;
};
struct BooleanSelection: public Selection {
bool value;
ListSelection *list_selection();
BooleanSelection *boolean_selection();
BooleanSelection(bool value) : value(value) {}
};
struct ListSelection: public Selection {
std::string value;
ListSelection *list_selection();
BooleanSelection *boolean_selection();
ListSelection(const std::string value) : value(value) {}
};
using SelectionSet = std::map<std::string, std::unique_ptr<Selection>>;
/*!
A Configuratble provides the options that it responds to and allows selections to be set.
*/
struct Device {
virtual std::vector<std::unique_ptr<Option>> get_options() = 0;
virtual void set_selections(const SelectionSet &selection_by_option) = 0;
virtual SelectionSet get_accurate_selections() = 0;
virtual SelectionSet get_user_friendly_selections() = 0;
};
template <typename T> T *selection(const Configurable::SelectionSet &selections_by_option, const std::string &name) {
auto selection = selections_by_option.find(name);
if(selection == selections_by_option.end()) return nullptr;
return dynamic_cast<T *>(selection->second.get());
}
}
#endif /* Configurable_h */

View File

@@ -0,0 +1,76 @@
//
// StandardOptions.cpp
// Clock Signal
//
// Created by Thomas Harte on 20/11/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "StandardOptions.hpp"
namespace {
/*!
Appends a Boolean selection of @c selection for option @c name to @c selection_set.
*/
void append_bool(Configurable::SelectionSet &selection_set, const std::string &name, bool selection) {
selection_set[name] = std::unique_ptr<Configurable::Selection>(new Configurable::BooleanSelection(selection));
}
/*!
Enquires for a Boolean selection for option @c name from @c selections_by_option, storing it to @c result if found.
*/
bool get_bool(const Configurable::SelectionSet &selections_by_option, const std::string &name, bool &result) {
auto quickload = Configurable::selection<Configurable::BooleanSelection>(selections_by_option, "quickload");
if(!quickload) return false;
result = quickload->value;
return true;
}
}
// MARK: - Standard option list builder
std::vector<std::unique_ptr<Configurable::Option>> Configurable::standard_options(Configurable::StandardOptions mask) {
std::vector<std::unique_ptr<Configurable::Option>> options;
if(mask & QuickLoadTape) options.emplace_back(new Configurable::BooleanOption("Load Tapes Quickly", "quickload"));
if(mask & DisplayRGBComposite) options.emplace_back(new Configurable::ListOption("Display", "display", {"composite", "rgb"}));
if(mask & AutomaticTapeMotorControl) options.emplace_back(new Configurable::BooleanOption("Automatic Tape Motor Control", "autotapemotor"));
return options;
}
// MARK: - Selection appenders
void Configurable::append_quick_load_tape_selection(Configurable::SelectionSet &selection_set, bool selection) {
append_bool(selection_set, "quickload", selection);
}
void Configurable::append_automatic_tape_motor_control_selection(SelectionSet &selection_set, bool selection) {
append_bool(selection_set, "autotapemotor", selection);
}
void Configurable::append_display_selection(Configurable::SelectionSet &selection_set, Display selection) {
selection_set["display"] = std::unique_ptr<Configurable::Selection>(new Configurable::ListSelection((selection == Display::RGB) ? "rgb" : "composite"));
}
// MARK: - Selection parsers
bool Configurable::get_quick_load_tape(const Configurable::SelectionSet &selections_by_option, bool &result) {
return get_bool(selections_by_option, "quickload", result);
}
bool Configurable::get_automatic_tape_motor_control_selection(const SelectionSet &selections_by_option, bool &result) {
return get_bool(selections_by_option, "autotapemotor", result);
}
bool Configurable::get_display(const Configurable::SelectionSet &selections_by_option, Configurable::Display &result) {
auto display = Configurable::selection<Configurable::ListSelection>(selections_by_option, "display");
if(display) {
if(display->value == "rgb") {
result = Configurable::Display::RGB;
return true;
}
if(display->value == "composite") {
result = Configurable::Display::Composite;
return true;
}
}
return false;
}

View File

@@ -0,0 +1,76 @@
//
// StandardOptions.hpp
// Clock Signal
//
// Created by Thomas Harte on 20/11/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef StandardOptions_hpp
#define StandardOptions_hpp
#include "Configurable.hpp"
namespace Configurable {
enum StandardOptions {
DisplayRGBComposite = (1 << 0),
QuickLoadTape = (1 << 1),
AutomaticTapeMotorControl = (1 << 2)
};
enum class Display {
RGB,
Composite
};
/*!
@returns An option list comprised of the standard names for all the options indicated by @c mask.
*/
std::vector<std::unique_ptr<Option>> standard_options(StandardOptions mask);
/*!
Appends to @c selection_set a selection of @c selection for QuickLoadTape.
*/
void append_quick_load_tape_selection(SelectionSet &selection_set, bool selection);
/*!
Appends to @c selection_set a selection of @c selection for AutomaticTapeMotorControl.
*/
void append_automatic_tape_motor_control_selection(SelectionSet &selection_set, bool selection);
/*!
Appends to @c selection_set a selection of @c selection for DisplayRGBComposite.
*/
void append_display_selection(SelectionSet &selection_set, Display selection);
/*!
Attempts to discern a QuickLoadTape selection from @c selections_by_option.
@param selections_by_option The user selections.
@param result The location to which the selection will be stored if found.
@returns @c true if a selection is found; @c false otherwise.
*/
bool get_quick_load_tape(const SelectionSet &selections_by_option, bool &result);
/*!
Attempts to discern an AutomaticTapeMotorControl selection from @c selections_by_option.
@param selections_by_option The user selections.
@param result The location to which the selection will be stored if found.
@returns @c true if a selection is found; @c false otherwise.
*/
bool get_automatic_tape_motor_control_selection(const SelectionSet &selections_by_option, bool &result);
/*!
Attempts to discern a display RGB/composite selection from @c selections_by_option.
@param selections_by_option The user selections.
@param result The location to which the selection will be stored if found.
@returns @c true if a selection is found; @c false otherwise.
*/
bool get_display(const SelectionSet &selections_by_option, Display &result);
}
#endif /* StandardOptions_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) {
std::size_t key_offset = static_cast<std::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) {
std::size_t key_offset = static_cast<std::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,11 +17,13 @@
#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"
#include "../../ClockReceiver/ForceInline.hpp"
namespace AmstradCPC {
/*!
@@ -33,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
@@ -76,7 +76,12 @@ class InterruptTimer {
/// @returns @c true if an interrupt is currently requested; @c false otherwise.
inline bool get_request() {
return interrupt_request_;
return last_interrupt_request_ = interrupt_request_;
}
/// Asks whether the interrupt status has changed.
inline bool request_has_changed() {
return last_interrupt_request_ != interrupt_request_;
}
/// Resets the timer.
@@ -86,9 +91,10 @@ class InterruptTimer {
}
private:
int reset_counter_;
bool interrupt_request_;
int timer_;
int reset_counter_ = 0;
bool interrupt_request_ = false;
bool last_interrupt_request_ = false;
int timer_ = 0;
};
/*!
@@ -147,26 +153,17 @@ 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) {
build_mode_tables();
interrupt_timer_(interrupt_timer) {
establish_palette_hits();
build_mode_table();
}
/*!
The CRTC entry function; takes the current bus state and determines what output
to produce based on the current palette and mode.
The CRTC entry function for the main part of each clock cycle; takes the current
bus state and determines what output to produce based on the current palette and mode.
*/
inline void perform_bus_cycle(const Motorola::CRTC::BusState &state) {
forceinline void perform_bus_cycle_phase1(const Motorola::CRTC::BusState &state) {
// The gate array waits 2µs to react to the CRTC's vsync signal, and then
// caps output at 4µs. Since the clock rate is 1Mhz, that's 2 and 4 cycles,
// respectively.
@@ -208,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)
@@ -224,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;
@@ -259,7 +256,13 @@ class CRTCBusHandler {
}
}
}
}
/*!
The CRTC entry function for phase 2 of each bus cycle — in which the next sync line state becomes
visible early. The CPC uses changes in sync to clock the interrupt timer.
*/
void perform_bus_cycle_phase2(const Motorola::CRTC::BusState &state) {
// check for a trailing CRTC hsync; if one occurred then that's the trigger potentially to change
// modes, and should also be sent on to the interrupt timer
if(was_hsync_ && !state.hsync) {
@@ -271,6 +274,7 @@ class CRTCBusHandler {
case 1: pixel_divider_ = 2; break;
case 2: pixel_divider_ = 1; break;
}
build_mode_table();
}
interrupt_timer_.signal_hsync();
@@ -296,7 +300,7 @@ class CRTCBusHandler {
"return vec3(float((sample >> 4) & 3u), float((sample >> 2) & 3u), float(sample & 3u)) / 2.0;"
"}");
crt_->set_visible_area(Outputs::CRT::Rect(0.075f, 0.05f, 0.9f, 0.9f));
crt_->set_output_device(Outputs::CRT::Monitor);
crt_->set_output_device(Outputs::CRT::OutputDevice::Monitor);
}
/// Destructs the CRT.
@@ -317,11 +321,6 @@ class CRTCBusHandler {
next_mode_ = mode;
}
/// @returns the current value of the CRTC's vertical sync output.
bool get_vsync() const {
return was_vsync_;
}
/// Palette management: selects a pen to modify.
void select_pen(int pen) {
pen_ = pen;
@@ -339,50 +338,140 @@ class CRTCBusHandler {
border_ = mapped_palette_value(colour);
} else {
palette_[pen_] = mapped_palette_value(colour);
// TODO: no need for a full regeneration, of every mode, every time
build_mode_tables();
patch_mode_table(pen_);
}
}
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);
}
void build_mode_tables() {
#define Mode0Colour0(c) ((c & 0x80) >> 7) | ((c & 0x20) >> 3) | ((c & 0x08) >> 2) | ((c & 0x02) << 2)
#define Mode0Colour1(c) ((c & 0x40) >> 6) | ((c & 0x10) >> 2) | ((c & 0x04) >> 1) | ((c & 0x01) << 3)
#define Mode1Colour0(c) ((c & 0x80) >> 7) | ((c & 0x08) >> 2)
#define Mode1Colour1(c) ((c & 0x40) >> 6) | ((c & 0x04) >> 1)
#define Mode1Colour2(c) ((c & 0x20) >> 5) | ((c & 0x02) >> 0)
#define Mode1Colour3(c) ((c & 0x10) >> 4) | ((c & 0x01) << 1)
#define Mode3Colour0(c) ((c & 0x80) >> 7) | ((c & 0x08) >> 2)
#define Mode3Colour1(c) ((c & 0x40) >> 6) | ((c & 0x04) >> 1)
void establish_palette_hits() {
for(int c = 0; c < 256; c++) {
// prepare mode 0
uint8_t *mode0_pixels = (uint8_t *)&mode0_output_[c];
mode0_pixels[0] = palette_[((c & 0x80) >> 7) | ((c & 0x20) >> 3) | ((c & 0x08) >> 2) | ((c & 0x02) << 2)];
mode0_pixels[1] = palette_[((c & 0x40) >> 6) | ((c & 0x10) >> 2) | ((c & 0x04) >> 1) | ((c & 0x01) << 3)];
mode0_palette_hits_[Mode0Colour0(c)].push_back(static_cast<uint8_t>(c));
mode0_palette_hits_[Mode0Colour1(c)].push_back(static_cast<uint8_t>(c));
// prepare mode 1
uint8_t *mode1_pixels = (uint8_t *)&mode1_output_[c];
mode1_pixels[0] = palette_[((c & 0x80) >> 7) | ((c & 0x08) >> 2)];
mode1_pixels[1] = palette_[((c & 0x40) >> 6) | ((c & 0x04) >> 1)];
mode1_pixels[2] = palette_[((c & 0x20) >> 5) | ((c & 0x02) >> 0)];
mode1_pixels[3] = palette_[((c & 0x10) >> 4) | ((c & 0x01) << 1)];
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));
// prepare mode 2
uint8_t *mode2_pixels = (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)];
mode2_pixels[3] = palette_[((c & 0x10) >> 4)];
mode2_pixels[4] = palette_[((c & 0x08) >> 3)];
mode2_pixels[5] = palette_[((c & 0x04) >> 2)];
mode2_pixels[6] = palette_[((c & 0x03) >> 1)];
mode2_pixels[7] = palette_[((c & 0x01) >> 0)];
// prepare mode 3
uint8_t *mode3_pixels = (uint8_t *)&mode3_output_[c];
mode3_pixels[0] = palette_[((c & 0x80) >> 7) | ((c & 0x08) >> 2)];
mode3_pixels[1] = palette_[((c & 0x40) >> 6) | ((c & 0x04) >> 1)];
mode3_palette_hits_[Mode3Colour0(c)].push_back(static_cast<uint8_t>(c));
mode3_palette_hits_[Mode3Colour1(c)].push_back(static_cast<uint8_t>(c));
}
}
void build_mode_table() {
switch(mode_) {
case 0:
// Mode 0: abcdefgh -> [gcea] [hdfb]
for(int c = 0; c < 256; c++) {
// prepare mode 0
uint8_t *mode0_pixels = reinterpret_cast<uint8_t *>(&mode0_output_[c]);
mode0_pixels[0] = palette_[Mode0Colour0(c)];
mode0_pixels[1] = palette_[Mode0Colour1(c)];
}
break;
case 1:
for(int c = 0; c < 256; c++) {
// prepare mode 1
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)];
mode1_pixels[3] = palette_[Mode1Colour3(c)];
}
break;
case 2:
for(int c = 0; c < 256; c++) {
// prepare mode 2
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)];
mode2_pixels[3] = palette_[((c & 0x10) >> 4)];
mode2_pixels[4] = palette_[((c & 0x08) >> 3)];
mode2_pixels[5] = palette_[((c & 0x04) >> 2)];
mode2_pixels[6] = palette_[((c & 0x03) >> 1)];
mode2_pixels[7] = palette_[((c & 0x01) >> 0)];
}
break;
case 3:
for(int c = 0; c < 256; c++) {
// prepare mode 3
uint8_t *mode3_pixels = reinterpret_cast<uint8_t *>(&mode3_output_[c]);
mode3_pixels[0] = palette_[Mode3Colour0(c)];
mode3_pixels[1] = palette_[Mode3Colour1(c)];
}
break;
}
}
void patch_mode_table(int pen) {
switch(mode_) {
case 0: {
for(uint8_t c : mode0_palette_hits_[pen]) {
uint8_t *mode0_pixels = reinterpret_cast<uint8_t *>(&mode0_output_[c]);
mode0_pixels[0] = palette_[Mode0Colour0(c)];
mode0_pixels[1] = palette_[Mode0Colour1(c)];
}
} break;
case 1:
if(pen > 3) return;
for(uint8_t c : mode1_palette_hits_[pen]) {
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)];
mode1_pixels[3] = palette_[Mode1Colour3(c)];
}
break;
case 2:
if(pen > 1) return;
// Whichever pen this is, there's only one table entry it doesn't touch, so just
// rebuild the whole thing.
build_mode_table();
break;
case 3:
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 = reinterpret_cast<uint8_t *>(&mode3_output_[c]);
mode3_pixels[0] = palette_[Mode3Colour0(c)];
mode3_pixels[1] = palette_[Mode3Colour1(c)];
}
break;
}
}
#undef Mode0Colour0
#undef Mode0Colour1
#undef Mode1Colour0
#undef Mode1Colour1
#undef Mode1Colour2
#undef Mode1Colour3
#undef Mode3Colour0
#undef Mode3Colour1
uint8_t mapped_palette_value(uint8_t colour) {
#define COL(r, g, b) (r << 4) | (g << 2) | b
static const uint8_t mapping[32] = {
@@ -399,27 +488,31 @@ 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];
uint16_t mode3_output_[256];
int pen_;
std::vector<uint8_t> mode0_palette_hits_[16];
std::vector<uint8_t> mode1_palette_hits_[4];
std::vector<uint8_t> mode3_palette_hits_[4];
int pen_ = 0;
uint8_t palette_[16];
uint8_t border_;
uint8_t border_ = 0;
InterruptTimer &interrupt_timer_;
};
@@ -477,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);
}
};
@@ -493,12 +599,12 @@ class i8255PortHandler : public Intel::i8255::PortHandler {
public:
i8255PortHandler(
KeyboardState &key_state,
const CRTCBusHandler &crtc_bus_handler,
const Motorola::CRTC::CRTC6845<CRTCBusHandler> &crtc,
AYDeferrer &ay,
Storage::Tape::BinaryTapePlayer &tape_player) :
key_state_(key_state),
crtc_bus_handler_(crtc_bus_handler),
ay_(ay),
crtc_(crtc),
key_state_(key_state),
tape_player_(tape_player) {}
/// The i8255 will call this to set a new output value of @c value for @c port.
@@ -538,7 +644,7 @@ class i8255PortHandler : public Intel::i8255::PortHandler {
switch(port) {
case 0: return ay_.ay()->get_data_output(); // Port A is wired to the AY
case 1: return
(crtc_bus_handler_.get_vsync() ? 0x01 : 0x00) | // Bit 0 returns CRTC vsync.
(crtc_.get_bus_state().vsync ? 0x01 : 0x00) | // Bit 0 returns CRTC vsync.
(tape_player_.get_input() ? 0x80 : 0x00) | // Bit 7 returns cassette input.
0x7e; // Bits unimplemented:
//
@@ -552,8 +658,8 @@ class i8255PortHandler : public Intel::i8255::PortHandler {
private:
AYDeferrer &ay_;
const Motorola::CRTC::CRTC6845<CRTCBusHandler> &crtc_;
KeyboardState &key_state_;
const CRTCBusHandler &crtc_bus_handler_;
Storage::Tape::BinaryTapePlayer &tape_player_;
};
@@ -563,25 +669,34 @@ class i8255PortHandler : public Intel::i8255::PortHandler {
class ConcreteMachine:
public Utility::TypeRecipient,
public CPU::Z80::BusHandler,
public Sleeper::SleepObserver,
public Machine {
public:
ConcreteMachine() :
z80_(*this),
crtc_counter_(HalfCycles(4)), // This starts the CRTC exactly out of phase with the CPU's memory accesses
crtc_(Motorola::CRTC::HD6845S, crtc_bus_handler_),
crtc_bus_handler_(ram_, interrupt_timer_),
crtc_(Motorola::CRTC::HD6845S, crtc_bus_handler_),
i8255_port_handler_(key_state_, crtc_, ay_, tape_player_),
i8255_(i8255_port_handler_),
i8255_port_handler_(key_state_, crtc_bus_handler_, ay_, tape_player_),
tape_player_(8000000) {
tape_player_(8000000),
crtc_counter_(HalfCycles(4)) // This starts the CRTC exactly out of phase with the CPU's memory accesses
{
// primary clock is 4Mhz
set_clock_rate(4000000);
// ensure memory starts in a random state
Memory::Fuzz(ram_, sizeof(ram_));
// register this class as the sleep observer for the FDC and tape
fdc_.set_sleep_observer(this);
fdc_is_sleeping_ = fdc_.is_sleeping();
tape_player_.set_sleep_observer(this);
tape_player_is_sleeping_ = tape_player_.is_sleeping();
}
/// The entry point for performing a partial Z80 machine cycle.
inline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
// Amstrad CPC timing scheme: assert WAIT for three out of four cycles
clock_offset_ = (clock_offset_ + cycle.length) & HalfCycles(7);
z80_.set_wait_line(clock_offset_ >= HalfCycles(2));
@@ -593,17 +708,20 @@ class ConcreteMachine:
crtc_counter_ += cycle.length;
Cycles crtc_cycles = crtc_counter_.divide_cycles(Cycles(4));
if(crtc_cycles > Cycles(0)) crtc_.run_for(crtc_cycles);
z80_.set_interrupt_line(interrupt_timer_.get_request());
// Check whether that prompted a change in the interrupt line. If so then date
// it to whenever the cycle was triggered.
if(interrupt_timer_.request_has_changed()) z80_.set_interrupt_line(interrupt_timer_.get_request(), -crtc_counter_);
// TODO (in the player, not here): adapt it to accept an input clock rate and
// run_for as HalfCycles
tape_player_.run_for(cycle.length.as_int());
if(!tape_player_is_sleeping_) tape_player_.run_for(cycle.length.as_int());
// Pump the AY
ay_.run_for(cycle.length);
// Clock the FDC, if connected, using a lazy scale by two
if(has_fdc_) fdc_.run_for(Cycles(cycle.length.as_int()));
if(has_fdc_ && !fdc_is_sleeping_) fdc_.run_for(Cycles(cycle.length.as_int()));
// Update typing activity
if(typer_) typer_->run_for(cycle.length);
@@ -715,35 +833,35 @@ class ConcreteMachine:
}
/// A CRTMachine function; indicates that outputs should be created now.
void setup_output(float aspect_ratio) {
void setup_output(float aspect_ratio) override final {
crtc_bus_handler_.setup_output(aspect_ratio);
ay_.setup_output();
ay_.ay()->set_port_handler(&key_state_);
}
/// A CRTMachine function; indicates that outputs should be destroyed now.
void close_output() {
void close_output() override final {
crtc_bus_handler_.close_output();
ay_.close_output();
}
/// @returns the CRT in use.
std::shared_ptr<Outputs::CRT::CRT> get_crt() {
std::shared_ptr<Outputs::CRT::CRT> get_crt() override final {
return crtc_bus_handler_.get_crt();
}
/// @returns the speaker in use.
std::shared_ptr<Outputs::Speaker> get_speaker() {
std::shared_ptr<Outputs::Speaker> get_speaker() override final {
return ay_.get_speaker();
}
/// Wires virtual-dispatched CRTMachine run_for requests to the static Z80 method.
void run_for(const Cycles cycles) {
void run_for(const Cycles cycles) override final {
z80_.run_for(cycles);
}
/// The ConfigurationTarget entry point; should configure this meachine as described by @c target.
void configure_as_target(const StaticAnalyser::Target &target) {
void configure_as_target(const StaticAnalyser::Target &target) override final {
switch(target.amstradcpc.model) {
case StaticAnalyser::AmstradCPCModel::CPC464:
rom_model_ = ROMType::OS464;
@@ -776,55 +894,90 @@ class ConcreteMachine:
read_pointers_[2] = write_pointers_[2];
read_pointers_[3] = roms_[upper_rom_].data();
// Type whatever is required.
if(target.loadingCommand.length()) {
set_typer_for_string(target.loadingCommand.c_str());
}
insert_media(target.media);
}
bool insert_media(const StaticAnalyser::Media &media) override final {
// If there are any tapes supplied, use the first of them.
if(!target.tapes.empty()) {
tape_player_.set_tape(target.tapes.front());
if(!media.tapes.empty()) {
tape_player_.set_tape(media.tapes.front());
}
// Insert up to four disks.
int c = 0;
for(auto &disk : target.disks) {
for(auto &disk : media.disks) {
fdc_.set_disk(disk, c);
c++;
if(c == 4) break;
}
// Type whatever is required.
if(target.loadingCommand.length()) {
set_typer_for_string(target.loadingCommand.c_str());
}
return !media.tapes.empty() || (!media.disks.empty() && has_fdc_);
}
// See header; provides the system ROMs.
void set_rom(ROMType type, std::vector<uint8_t> data) {
roms_[(int)type] = data;
void set_rom(ROMType type, const std::vector<uint8_t> &data) override final {
roms_[static_cast<int>(type)] = data;
}
#pragma mark - Keyboard
// Obtains the system ROMs.
bool set_rom_fetcher(const std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> &roms_with_names) override {
auto roms = roms_with_names(
"AmstradCPC",
{
"os464.rom", "basic464.rom",
"os664.rom", "basic664.rom",
"os6128.rom", "basic6128.rom",
"amsdos.rom"
});
void set_typer_for_string(const char *string) {
for(std::size_t index = 0; index < roms.size(); ++index) {
auto &data = roms[index];
if(!data) return false;
set_rom(static_cast<ROMType>(index), *data);
}
return true;
}
void set_component_is_sleeping(void *component, bool is_sleeping) override final {
fdc_is_sleeping_ = fdc_.is_sleeping();
tape_player_is_sleeping_ = tape_player_.is_sleeping();
}
// MARK: - Keyboard
void set_typer_for_string(const char *string) override final {
std::unique_ptr<CharacterMapper> mapper(new CharacterMapper());
Utility::TypeRecipient::set_typer_for_string(string, std::move(mapper));
}
HalfCycles get_typer_delay() {
HalfCycles get_typer_delay() override final {
return Cycles(4000000); // Wait 1 second before typing.
}
HalfCycles get_typer_frequency() {
return Cycles(80000); // Type one character per frame.
HalfCycles get_typer_frequency() override final {
return Cycles(160000); // Type one character per frame.
}
// See header; sets a key as either pressed or released.
void set_key_state(uint16_t key, bool isPressed) {
void set_key_state(uint16_t key, bool isPressed) override final {
key_state_.set_is_pressed(isPressed, key >> 4, key & 7);
}
// See header; sets all keys to released.
void clear_all_keys() {
void clear_all_keys() override final {
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) {
@@ -871,7 +1024,7 @@ class ConcreteMachine:
}
}
CPU::Z80::Processor<ConcreteMachine> z80_;
CPU::Z80::Processor<ConcreteMachine, false, true> z80_;
CRTCBusHandler crtc_bus_handler_;
Motorola::CRTC::CRTC6845<CRTCBusHandler> crtc_;
@@ -893,7 +1046,8 @@ class ConcreteMachine:
std::vector<uint8_t> roms_[7];
int rom_model_;
bool has_fdc_;
bool has_fdc_, fdc_is_sleeping_;
bool tape_player_is_sleeping_;
bool has_128k_;
bool upper_rom_is_paged_;
int upper_rom_;
@@ -903,6 +1057,7 @@ class ConcreteMachine:
uint8_t *write_pointers_[4];
KeyboardState key_state_;
AmstradCPC::KeyboardMapper keyboard_mapper_;
};
}

View File

@@ -25,27 +25,8 @@ 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, a CRT-outputting machine with a keyboard that can accept configuration targets.
Models an Amstrad CPC.
*/
class Machine:
public CRTMachine::Machine,
@@ -54,11 +35,11 @@ class Machine:
public:
virtual ~Machine();
/// Creates an returns an Amstrad CPC on the heap.
/// Creates and returns an Amstrad CPC.
static Machine *AmstradCPC();
/// Sets the contents of rom @c type to @c data. Assumed to be a setup step; has no effect once a machine is running.
virtual void set_rom(ROMType type, std::vector<uint8_t> data) = 0;
virtual void set_rom(ROMType type, const std::vector<uint8_t> &data) = 0;
};
}

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

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

View File

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

View File

@@ -6,18 +6,18 @@
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef Atari2600_CartridgeActivisionStack_hpp
#define Atari2600_CartridgeActivisionStack_hpp
#ifndef Atari2600_ActivisionStack_hpp
#define Atari2600_ActivisionStack_hpp
namespace Atari2600 {
namespace Cartridge {
class CartridgeActivisionStack: public Cartridge<CartridgeActivisionStack> {
class ActivisionStack: public BusExtender {
public:
CartridgeActivisionStack(const std::vector<uint8_t> &rom) :
Cartridge(rom),
last_opcode_(0x00) {
rom_ptr_ = rom_.data();
}
ActivisionStack(uint8_t *rom_base, std::size_t rom_size) :
BusExtender(rom_base, rom_size),
rom_ptr_(rom_base),
last_opcode_(0x00) {}
void perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
if(!(address & 0x1000)) return;
@@ -27,9 +27,9 @@ class CartridgeActivisionStack: public Cartridge<CartridgeActivisionStack> {
// RST or JSR.
if(operation == CPU::MOS6502::BusOperation::ReadOpcode && (last_opcode_ == 0x20 || last_opcode_ == 0x60)) {
if(address & 0x2000) {
rom_ptr_ = rom_.data();
rom_ptr_ = rom_base_;
} else {
rom_ptr_ = rom_.data() + 4096;
rom_ptr_ = rom_base_ + 4096;
}
}
@@ -45,6 +45,7 @@ class CartridgeActivisionStack: public Cartridge<CartridgeActivisionStack> {
uint8_t last_opcode_;
};
}
}
#endif /* Atari2600_CartridgeActivisionStack_hpp */

View File

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

View File

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

View File

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

View File

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

View File

@@ -13,18 +13,34 @@
#include "../Bus.hpp"
namespace Atari2600 {
namespace Cartridge {
class BusExtender: public CPU::MOS6502::BusHandler {
public:
BusExtender(uint8_t *rom_base, std::size_t rom_size) : rom_base_(rom_base), rom_size_(rom_size) {}
void advance_cycles(int cycles) {}
protected:
uint8_t *rom_base_;
std::size_t rom_size_;
};
template<class T> class Cartridge:
public CPU::MOS6502::Processor<Cartridge<T>>,
public CPU::MOS6502::BusHandler,
public Bus {
public:
Cartridge(const std::vector<uint8_t> &rom) :
rom_(rom) {}
m6502_(*this),
rom_(rom),
bus_extender_(rom_.data(), rom.size()) {
// The above works because bus_extender_ is declared after rom_ in the instance storage list;
// consider doing something less fragile.
}
void run_for(const Cycles cycles) { CPU::MOS6502::Processor<Cartridge<T>>::run_for(cycles); }
void set_reset_line(bool state) { CPU::MOS6502::Processor<Cartridge<T>>::set_reset_line(state); }
void advance_cycles(int cycles) {}
void run_for(const Cycles cycles) { m6502_.run_for(cycles); }
void set_reset_line(bool state) { m6502_.set_reset_line(state); }
// to satisfy CPU::MOS6502::Processor
Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
@@ -41,11 +57,11 @@ template<class T> class Cartridge:
cycles_since_speaker_update_ += Cycles(cycles_run_for);
cycles_since_video_update_ += Cycles(cycles_run_for);
cycles_since_6532_update_ += Cycles(cycles_run_for / 3);
static_cast<T *>(this)->advance_cycles(cycles_run_for / 3);
bus_extender_.advance_cycles(cycles_run_for / 3);
if(operation != CPU::MOS6502::BusOperation::Ready) {
// give the cartridge a chance to respond to the bus access
static_cast<T *>(this)->perform_bus_operation(operation, address, value);
bus_extender_.perform_bus_operation(operation, address, value);
// check for a RIOT RAM access
if((address&0x1280) == 0x80) {
@@ -91,7 +107,7 @@ template<class T> class Cartridge:
case 0x00: update_video(); tia_->set_sync(*value & 0x02); break;
case 0x01: update_video(); tia_->set_blank(*value & 0x02); break;
case 0x02: CPU::MOS6502::Processor<Cartridge<T>>::set_ready_line(true); break;
case 0x02: m6502_.set_ready_line(true); break;
case 0x03: update_video(); tia_->reset_horizontal_counter(); break;
// TODO: audio will now be out of synchronisation — fix
@@ -156,7 +172,7 @@ template<class T> class Cartridge:
}
}
if(!tia_->get_cycles_until_horizontal_blank(cycles_since_video_update_)) CPU::MOS6502::Processor<Cartridge<T>>::set_ready_line(false);
if(!tia_->get_cycles_until_horizontal_blank(cycles_since_video_update_)) m6502_.set_ready_line(false);
return Cycles(cycles_run_for / 3);
}
@@ -168,9 +184,14 @@ template<class T> class Cartridge:
}
protected:
CPU::MOS6502::Processor<Cartridge<T>, true> m6502_;
std::vector<uint8_t> rom_;
private:
T bus_extender_;
};
}
}
#endif /* Atari2600_Cartridge_hpp */

View File

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

View File

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

View File

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

View File

@@ -12,12 +12,13 @@
#include "Cartridge.hpp"
namespace Atari2600 {
namespace Cartridge {
class CartridgeParkerBros: public Cartridge<CartridgeParkerBros> {
class ParkerBros: public BusExtender {
public:
CartridgeParkerBros(const std::vector<uint8_t> &rom) :
Cartridge(rom) {
rom_ptr_[0] = rom_.data() + 4096;
ParkerBros(uint8_t *rom_base, std::size_t rom_size) :
BusExtender(rom_base, rom_size) {
rom_ptr_[0] = rom_base + 4096;
rom_ptr_[1] = rom_ptr_[0] + 1024;
rom_ptr_[2] = rom_ptr_[1] + 1024;
rom_ptr_[3] = rom_ptr_[2] + 1024;
@@ -29,7 +30,7 @@ class CartridgeParkerBros: public Cartridge<CartridgeParkerBros> {
if(address >= 0x1fe0 && address < 0x1ff8) {
int slot = (address >> 3)&3;
rom_ptr_[slot] = rom_.data() + ((address & 7) * 1024);
rom_ptr_[slot] = rom_base_ + ((address & 7) * 1024);
}
if(isReadOperation(operation)) {
@@ -41,6 +42,7 @@ class CartridgeParkerBros: public Cartridge<CartridgeParkerBros> {
uint8_t *rom_ptr_[4];
};
}
}
#endif /* Atari2600_CartridgeParkerBros_hpp */

View File

@@ -10,17 +10,13 @@
#define Atari2600_CartridgePitfall2_hpp
namespace Atari2600 {
namespace Cartridge {
class CartridgePitfall2: public Cartridge<CartridgePitfall2> {
class Pitfall2: public BusExtender {
public:
CartridgePitfall2(const std::vector<uint8_t> &rom) :
Cartridge(rom),
random_number_generator_(0),
featcher_address_{0, 0, 0, 0, 0, 0, 0, 0},
mask_{0, 0, 0, 0, 0, 0, 0, 0},
cycles_since_audio_update_(0) {
rom_ptr_ = rom_.data();
}
Pitfall2(uint8_t *rom_base, std::size_t rom_size) :
BusExtender(rom_base, rom_size),
rom_ptr_(rom_base) {}
void advance_cycles(int cycles) {
cycles_since_audio_update_ += cycles;
@@ -32,14 +28,14 @@ class CartridgePitfall2: public Cartridge<CartridgePitfall2> {
switch(address) {
#pragma mark - Reads
// MARK: - Reads
// The random number generator
case 0x1000: case 0x1001: case 0x1002: case 0x1003: case 0x1004:
if(isReadOperation(operation)) {
*value = random_number_generator_;
}
random_number_generator_ = (uint8_t)(
random_number_generator_ = static_cast<uint8_t>(
(random_number_generator_ << 1) |
(~( (random_number_generator_ >> 7) ^
(random_number_generator_ >> 5) ^
@@ -53,14 +49,14 @@ class CartridgePitfall2: public Cartridge<CartridgePitfall2> {
break;
case 0x1008: case 0x1009: case 0x100a: case 0x100b: case 0x100c: case 0x100d: case 0x100e: case 0x100f:
*value = rom_[8192 + address_for_counter(address & 7)];
*value = rom_base_[8192 + address_for_counter(address & 7)];
break;
case 0x1010: case 0x1011: case 0x1012: case 0x1013: case 0x1014: case 0x1015: case 0x1016: case 0x1017:
*value = rom_[8192 + address_for_counter(address & 7)] & mask_[address & 7];
*value = rom_base_[8192 + address_for_counter(address & 7)] & mask_[address & 7];
break;
#pragma mark - Writes
// MARK: - Writes
case 0x1040: case 0x1041: case 0x1042: case 0x1043: case 0x1044: case 0x1045: case 0x1046: case 0x1047:
top_[address & 7] = *value;
@@ -73,18 +69,18 @@ class CartridgePitfall2: public Cartridge<CartridgePitfall2> {
mask_[address & 7] = 0x00;
break;
case 0x1058: case 0x1059: case 0x105a: case 0x105b: case 0x105c: case 0x105d: case 0x105e: case 0x105f:
featcher_address_[address & 7] = (featcher_address_[address & 7] & 0x00ff) | (uint16_t)(*value << 8);
featcher_address_[address & 7] = (featcher_address_[address & 7] & 0x00ff) | static_cast<uint16_t>(*value << 8);
break;
case 0x1070: case 0x1071: case 0x1072: case 0x1073: case 0x1074: case 0x1075: case 0x1076: case 0x1077:
random_number_generator_ = 0;
break;
#pragma mark - Paging
// MARK: - Paging
case 0x1ff8: rom_ptr_ = rom_.data(); break;
case 0x1ff9: rom_ptr_ = rom_.data() + 4096; break;
case 0x1ff8: rom_ptr_ = rom_base_; break;
case 0x1ff9: rom_ptr_ = rom_base_ + 4096; break;
#pragma mark - Business as usual
// MARK: - Business as usual
default:
if(isReadOperation(operation)) {
@@ -119,15 +115,16 @@ class CartridgePitfall2: public Cartridge<CartridgePitfall2> {
return level_table[table_position];
}
uint16_t featcher_address_[8];
uint8_t top_[8], bottom_[8], mask_[8];
uint16_t featcher_address_[8] = {0, 0, 0, 0, 0, 0, 0, 0};
uint8_t top_[8], bottom_[8], mask_[8] = {0, 0, 0, 0, 0, 0, 0, 0};
uint8_t music_mode_[3];
uint8_t random_number_generator_;
uint8_t random_number_generator_ = 0;
uint8_t *rom_ptr_;
uint8_t audio_channel_[3];
Cycles cycles_since_audio_update_;
Cycles cycles_since_audio_update_ = 0;
};
}
}
#endif /* Atari2600_CartridgePitfall2_hpp */

View File

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

View File

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

View File

@@ -9,6 +9,8 @@
#ifndef Atari2600_PIA_h
#define Atari2600_PIA_h
#include <cstdint>
#include "../../Components/6532/6532.hpp"
namespace Atari2600 {

View File

@@ -20,36 +20,27 @@ namespace {
uint8_t reverse_table[256];
}
TIA::TIA(bool create_crt) :
horizontal_counter_(0),
pixels_start_location_(0),
output_mode_(0),
pixel_target_(nullptr),
background_{0, 0},
background_half_mask_(0),
horizontal_blank_extend_(false),
collision_flags_(0)
{
TIA::TIA(bool create_crt) {
if(create_crt) {
crt_.reset(new Outputs::CRT::CRT(cycles_per_line * 2 - 1, 1, Outputs::CRT::DisplayType::NTSC60, 1));
crt_->set_output_device(Outputs::CRT::Television);
crt_->set_output_device(Outputs::CRT::OutputDevice::Television);
set_output_mode(OutputMode::NTSC);
}
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 +62,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 +153,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 +194,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 +234,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 +296,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 +351,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 +392,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 +433,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 +455,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 +481,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]];
@@ -538,7 +529,7 @@ void TIA::output_line() {
}
}
#pragma mark - Playfield output
// MARK: - Playfield output
void TIA::draw_playfield(int start, int end) {
// don't do anything if this window ends too early
@@ -553,12 +544,12 @@ 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;
}
}
#pragma mark - Motion
// MARK: - Motion
template<class T> void TIA::perform_motion_step(T &object) {
if((object.motion_step ^ (object.motion ^ 8)) == 0xf) {
@@ -666,7 +657,7 @@ template<class T> void TIA::draw_object_visible(T &object, const uint8_t collisi
}
}
#pragma mark - Missile drawing
// MARK: - Missile drawing
void TIA::draw_missile(Missile &missile, Player &player, const uint8_t collision_identity, int start, int end) {
if(!missile.locked_to_player || player.latched_pixel4_time < 0) {

View File

@@ -81,10 +81,10 @@ class TIA {
std::function<void(uint8_t *output_buffer)> line_end_function_;
// the master counter; counts from 0 to 228 with all visible pixels being in the final 160
int horizontal_counter_;
int horizontal_counter_ = 0;
// contains flags to indicate whether sync or blank are currently active
int output_mode_;
int output_mode_ = 0;
// keeps track of the target pixel buffer for this line and when it was acquired, and a corresponding collision buffer
alignas(alignof(uint32_t)) uint8_t collision_buffer_[160];
@@ -97,7 +97,7 @@ class TIA {
Missile1 = (1 << 5)
};
int collision_flags_;
int collision_flags_ = 0;
int collision_flags_by_buffer_vaules_[64];
// colour mapping tables
@@ -118,59 +118,45 @@ class TIA {
uint8_t colour_palette_[4];
// playfield state
int background_half_mask_;
int background_half_mask_ = 0;
enum class PlayfieldPriority {
Standard,
Score,
OnTop
} playfield_priority_;
uint32_t background_[2]; // contains two 20-bit bitfields representing the background state;
// at index 0 is the left-hand side of the playfield with bit 0 being
// the first bit to display, bit 1 the second, etc. Index 1 contains
// a mirror image of index 0. If the playfield is being displayed in
// mirroring mode, background_[0] will be output on the left and
// background_[1] on the right; otherwise background_[0] will be
// output twice.
uint32_t background_[2] = {0, 0};
// contains two 20-bit bitfields representing the background state;
// at index 0 is the left-hand side of the playfield with bit 0 being
// the first bit to display, bit 1 the second, etc. Index 1 contains
// a mirror image of index 0. If the playfield is being displayed in
// mirroring mode, background_[0] will be output on the left and
// background_[1] on the right; otherwise background_[0] will be
// output twice.
// objects
template<class T> struct Object {
// the two programmer-set values
int position;
int motion;
int position = 0;
int motion = 0;
// motion_step_ is the current motion counter value; motion_time_ is the next time it will fire
int motion_step;
int motion_time;
int motion_step = 0;
int motion_time = 0;
// indicates whether this object is currently undergoing motion
bool is_moving;
Object() : position(0), motion(0), motion_step(0), motion_time(0), is_moving(false) {};
bool is_moving = false;
};
// player state
struct Player: public Object<Player> {
Player() :
adder(4),
copy_flags(0),
graphic{0, 0},
reverse_mask(false),
graphic_index(0),
pixel_position(32),
pixel_counter(0),
latched_pixel4_time(-1),
copy_index_(0),
queue_read_pointer_(0),
queue_write_pointer_(0) {}
int adder = 4;
int copy_flags = 0; // a bit field, corresponding to the first few values of NUSIZ
uint8_t graphic[2] = {0, 0}; // the player graphic; 1 = new, 0 = current
int reverse_mask = false; // 7 for a reflected player, 0 for normal
int graphic_index = 0;
int adder;
int copy_flags; // a bit field, corresponding to the first few values of NUSIZ
uint8_t graphic[2]; // the player graphic; 1 = new, 0 = current
int reverse_mask; // 7 for a reflected player, 0 for normal
int graphic_index;
int pixel_position, pixel_counter;
int latched_pixel4_time;
int pixel_position = 32, pixel_counter = 0;
int latched_pixel4_time = -1;
const bool enqueues = true;
inline void skip_pixels(const int count, int from_horizontal_counter) {
@@ -219,15 +205,14 @@ class TIA {
}
private:
int copy_index_;
int copy_index_ = 0;
struct QueuedPixels {
int start, end;
int pixel_position;
int adder;
int reverse_mask;
QueuedPixels() : start(0), end(0), pixel_position(0), adder(0), reverse_mask(false) {}
int start = 0, end = 0;
int pixel_position = 0;
int adder = 0;
int reverse_mask = false;
} queue_[4];
int queue_read_pointer_, queue_write_pointer_;
int queue_read_pointer_ = 0, queue_write_pointer_ = 0;
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int output_pixel_position, int output_adder, int output_reverse_mask) {
if(output_pixel_position == 32 || !graphic[graphic_index]) return;
@@ -244,8 +229,8 @@ class TIA {
// common actor for things that appear as a horizontal run of pixels
struct HorizontalRun: public Object<HorizontalRun> {
int pixel_position;
int size;
int pixel_position = 0;
int size = 1;
const bool enqueues = false;
inline void skip_pixels(const int count, int from_horizontal_counter) {
@@ -268,16 +253,13 @@ class TIA {
void dequeue_pixels(uint8_t *const target, const uint8_t collision_identity, const int time_now) {}
void enqueue_pixels(const int start, const int end, int from_horizontal_counter) {}
HorizontalRun() : pixel_position(0), size(1) {}
};
// missile state
struct Missile: public HorizontalRun {
bool enabled;
bool locked_to_player;
int copy_flags;
bool enabled = false;
bool locked_to_player = false;
int copy_flags = 0;
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) {
if(!pixel_position) return;
@@ -287,14 +269,12 @@ class TIA {
skip_pixels(count, from_horizontal_counter);
}
}
Missile() : enabled(false), copy_flags(0) {}
} missile_[2];
// ball state
struct Ball: public HorizontalRun {
bool enabled[2];
int enabled_index;
bool enabled[2] = {false, false};
int enabled_index = 0;
const int copy_flags = 0;
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) {
@@ -305,12 +285,10 @@ class TIA {
skip_pixels(count, from_horizontal_counter);
}
}
Ball() : enabled{false, false}, enabled_index(0) {}
} ball_;
// motion
bool horizontal_blank_extend_;
bool horizontal_blank_extend_ = false;
template<class T> void perform_border_motion(T &object, int start, int end);
template<class T> void perform_motion_step(T &object);
@@ -323,8 +301,8 @@ class TIA {
inline void output_for_cycles(int number_of_cycles);
inline void output_line();
int pixels_start_location_;
uint8_t *pixel_target_;
int pixels_start_location_ = 0;
uint8_t *pixel_target_ = nullptr;
inline void output_pixels(int start, int end);
};

View File

@@ -12,6 +12,7 @@
#include "../Outputs/CRT/CRT.hpp"
#include "../Outputs/Speaker.hpp"
#include "../ClockReceiver/ClockReceiver.hpp"
#include "ROMMachine.hpp"
namespace CRTMachine {
@@ -20,10 +21,8 @@ namespace CRTMachine {
that optionally provide a speaker, and that nominate a clock rate and can announce to a delegate
should that clock rate change.
*/
class Machine {
class Machine: public ROMMachine::Machine {
public:
Machine() : clock_is_unlimited_(false), delegate_(nullptr) {}
/*!
Causes the machine to set up its CRT and, if it has one, speaker. The caller guarantees
that an OpenGL context is bound.
@@ -74,9 +73,9 @@ class Machine {
}
private:
Delegate *delegate_;
double clock_rate_;
bool clock_is_unlimited_;
Delegate *delegate_ = nullptr;
double clock_rate_ = 1.0;
bool clock_is_unlimited_ = false;
};
}

View File

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

View File

@@ -6,35 +6,47 @@
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "C1540.hpp"
#include "../C1540.hpp"
#include <cassert>
#include <cstring>
#include <string>
#include "../../../Storage/Disk/Encodings/CommodoreGCR.hpp"
#include "../../../../Storage/Disk/Encodings/CommodoreGCR.hpp"
using namespace Commodore::C1540;
Machine::Machine() :
shift_register_(0),
Storage::Disk::Controller(1000000, 4, 300),
MachineBase::MachineBase() :
Storage::Disk::Controller(1000000),
m6502_(*this),
drive_(new Storage::Disk::Drive(1000000, 300, 2)),
serial_port_VIA_port_handler_(new SerialPortVIA(serial_port_VIA_)),
serial_port_(new SerialPort),
serial_port_VIA_(new SerialPortVIA) {
drive_VIA_(drive_VIA_port_handler_),
serial_port_VIA_(*serial_port_VIA_port_handler_) {
// 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_);
}
Machine::Machine(Commodore::C1540::Machine::Personality personality) : personality_(personality) {}
void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus) {
Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus);
}
Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
Cycles MachineBase::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
/*
Memory map (given that I'm unsure yet on any potential mirroring):
@@ -49,13 +61,14 @@ Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint
else
ram_[address] = *value;
} else if(address >= 0xc000) {
if(isReadOperation(operation))
if(isReadOperation(operation)) {
*value = rom_[address & 0x3fff];
}
} else if(address >= 0x1800 && address <= 0x180f) {
if(isReadOperation(operation))
*value = serial_port_VIA_->get_register(address);
*value = serial_port_VIA_.get_register(address);
else
serial_port_VIA_->set_register(address, *value);
serial_port_VIA_.set_register(address, *value);
} else if(address >= 0x1c00 && address <= 0x1c0f) {
if(isReadOperation(operation))
*value = drive_VIA_.get_register(address);
@@ -63,81 +76,89 @@ 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) {
memcpy(rom_, rom.data(), std::min(sizeof(rom_), rom.size()));
bool Machine::set_rom_fetcher(const std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> &roms_with_names) {
std::string rom_name;
switch(personality_) {
case Personality::C1540: rom_name = "1540.bin"; break;
case Personality::C1541: rom_name = "1541.bin"; break;
}
auto roms = roms_with_names("Commodore1540", {rom_name});
if(!roms[0]) return false;
std::memcpy(rom_, roms[0]->data(), std::min(sizeof(rom_), roms[0]->size()));
return true;
}
void Machine::set_disk(std::shared_ptr<Storage::Disk::Disk> disk) {
std::shared_ptr<Storage::Disk::Drive> drive(new Storage::Disk::Drive);
drive->set_disk(disk);
set_drive(drive);
drive_->set_disk(disk);
}
void Machine::run_for(const Cycles cycles) {
CPU::MOS6502::Processor<Machine>::run_for(cycles);
set_motor_on(drive_VIA_.get_motor_enabled());
if(drive_VIA_.get_motor_enabled()) // TODO: motor speed up/down
m6502_.run_for(cycles);
bool drive_motor = drive_VIA_port_handler_.get_motor_enabled();
drive_->set_motor_on(drive_motor);
if(drive_motor)
Storage::Disk::Controller::run_for(cycles);
}
#pragma mark - 6522 delegate
// MARK: - 6522 delegate
void Machine::mos6522_did_change_interrupt_status(void *mos6522) {
void MachineBase::mos6522_did_change_interrupt_status(void *mos6522) {
// both VIAs are connected to the IRQ line
set_irq_line(serial_port_VIA_->get_interrupt_line() || drive_VIA_.get_interrupt_line());
m6502_.set_irq_line(serial_port_VIA_.get_interrupt_line() || drive_VIA_.get_interrupt_line());
}
#pragma mark - Disk drive
// MARK: - Disk drive
void Machine::process_input_bit(int value, unsigned int cycles_since_index_hole) {
void MachineBase::process_input_bit(int value) {
shift_register_ = (shift_register_ << 1) | value;
if((shift_register_ & 0x3ff) == 0x3ff) {
drive_VIA_.set_sync_detected(true);
drive_VIA_port_handler_.set_sync_detected(true);
bit_window_offset_ = -1; // i.e. this bit isn't the first within a data window, but the next might be
} else {
drive_VIA_.set_sync_detected(false);
drive_VIA_port_handler_.set_sync_detected(false);
}
bit_window_offset_++;
if(bit_window_offset_ == 8) {
drive_VIA_.set_data_input((uint8_t)shift_register_);
drive_VIA_port_handler_.set_data_input(static_cast<uint8_t>(shift_register_));
bit_window_offset_ = 0;
if(drive_VIA_.get_should_set_overflow()) {
set_overflow_line(true);
if(drive_VIA_port_handler_.get_should_set_overflow()) {
m6502_.set_overflow_line(true);
}
}
else set_overflow_line(false);
else m6502_.set_overflow_line(false);
}
// the 1540 does not recognise index holes
void Machine::process_index_hole() {}
void MachineBase::process_index_hole() {}
#pragma mak - Drive VIA delegate
// MARK: - 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
// 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) : via_(via) {}
uint8_t SerialPortVIA::get_port_input(Port port) {
uint8_t SerialPortVIA::get_port_input(MOS::MOS6522::Port port) {
if(port) return port_b_;
return 0xff;
}
void SerialPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask) {
void SerialPortVIA::set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t mask) {
if(port) {
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
if(serialPort) {
@@ -151,6 +172,8 @@ void SerialPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask) {
}
void SerialPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool value) {
// printf("[C1540] %s is %s\n", StringForLine(line), value ? "high" : "low");
switch(line) {
default: break;
case ::Commodore::Serial::Line::Data: port_b_ = (port_b_ & ~0x01) | (value ? 0x00 : 0x01); break;
@@ -158,7 +181,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;
}
@@ -177,7 +200,7 @@ void SerialPortVIA::update_data_line() {
}
}
#pragma mark - DriveVIA
// MARK: - DriveVIA
void DriveVIA::set_delegate(Delegate *delegate) {
delegate_ = delegate;
@@ -186,7 +209,7 @@ void DriveVIA::set_delegate(Delegate *delegate) {
// write protect tab uncovered
DriveVIA::DriveVIA() : port_b_(0xff), port_a_(0xff), delegate_(nullptr) {}
uint8_t DriveVIA::get_port_input(Port port) {
uint8_t DriveVIA::get_port_input(MOS::MOS6522::Port port) {
return port ? port_b_ : port_a_;
}
@@ -206,37 +229,39 @@ 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;
}
}
#pragma mark - SerialPort
// MARK: - SerialPort
void SerialPort::set_input(::Commodore::Serial::Line line, ::Commodore::Serial::LineLevel level) {
std::shared_ptr<SerialPortVIA> serialPortVIA = serial_port_VIA_.lock();

View File

@@ -0,0 +1,160 @@
//
// 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_ = 0x0;
std::weak_ptr<::Commodore::Serial::Port> serial_port_;
bool attention_acknowledge_level_ = false;
bool attention_level_input_ = true;
bool data_level_output_ = false;
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_ = 0, bit_window_offset_;
virtual void process_input_bit(int value);
virtual void process_index_hole();
};
}
}
#endif /* C1540Base_hpp */

View File

@@ -8,6 +8,8 @@
#include "SerialBus.hpp"
#include <cstdio>
using namespace Commodore::Serial;
const char *::Commodore::Serial::StringForLine(Line line) {
@@ -17,6 +19,7 @@ const char *::Commodore::Serial::StringForLine(Line line) {
case Clock: return "Clock";
case Data: return "Data";
case Reset: return "Reset";
default: return nullptr;
}
}
@@ -27,12 +30,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 +49,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;
@@ -59,7 +64,7 @@ void Bus::set_line_output_did_change(Line line) {
}
}
#pragma mark - The debug port
// MARK: - The debug port
void DebugPort::set_input(Line line, LineLevel value) {
input_levels_[line] = value;

View File

@@ -9,6 +9,8 @@
#ifndef SerialBus_hpp
#define SerialBus_hpp
#include <cstdint>
#include <memory>
#include <vector>
namespace Commodore {

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

File diff suppressed because it is too large Load Diff

View File

@@ -9,25 +9,19 @@
#ifndef Vic20_hpp
#define Vic20_hpp
#include "../../../Configurable/Configurable.hpp"
#include "../../ConfigurationTarget.hpp"
#include "../../CRTMachine.hpp"
#include "../../Typer.hpp"
#include "../../KeyboardMachine.hpp"
#include "../../JoystickMachine.hpp"
#include "../../../Processors/6502/6502.hpp"
#include "../../../Components/6560/6560.hpp"
#include "../../../Components/6522/6522.hpp"
#include "../SerialBus.hpp"
#include "../1540/C1540.hpp"
#include "../../../Storage/Tape/Tape.hpp"
#include "../../../Storage/Disk/Disk.hpp"
#include <cstdint>
namespace Commodore {
namespace Vic20 {
enum ROMSlot {
Kernel,
Kernel = 0,
BASIC,
Characters,
Drive
@@ -40,186 +34,39 @@ enum MemorySize {
};
enum Region {
NTSC,
PAL
American,
Danish,
Japanese,
European,
Swedish
};
#define key(line, mask) (((mask) << 3) | (line))
enum Key: uint16_t {
Key2 = key(7, 0x01), Key4 = key(7, 0x02), Key6 = key(7, 0x04), Key8 = key(7, 0x08),
Key0 = key(7, 0x10), KeyDash = key(7, 0x20), KeyHome = key(7, 0x40), KeyF7 = key(7, 0x80),
KeyQ = key(6, 0x01), KeyE = key(6, 0x02), KeyT = key(6, 0x04), KeyU = key(6, 0x08),
KeyO = key(6, 0x10), KeyAt = key(6, 0x20), KeyUp = key(6, 0x40), KeyF5 = key(6, 0x80),
KeyCBM = key(5, 0x01), KeyS = key(5, 0x02), KeyF = key(5, 0x04), KeyH = key(5, 0x08),
KeyK = key(5, 0x10), KeyColon = key(5, 0x20), KeyEquals = key(5, 0x40), KeyF3 = key(5, 0x80),
KeySpace = key(4, 0x01), KeyZ = key(4, 0x02), KeyC = key(4, 0x04), KeyB = key(4, 0x08),
KeyM = key(4, 0x10), KeyFullStop = key(4, 0x20), KeyRShift = key(4, 0x40), KeyF1 = key(4, 0x80),
KeyRunStop = key(3, 0x01), KeyLShift = key(3, 0x02), KeyX = key(3, 0x04), KeyV = key(3, 0x08),
KeyN = key(3, 0x10), KeyComma = key(3, 0x20), KeySlash = key(3, 0x40), KeyDown = key(3, 0x80),
KeyControl = key(2, 0x01), KeyA = key(2, 0x02), KeyD = key(2, 0x04), KeyG = key(2, 0x08),
KeyJ = key(2, 0x10), KeyL = key(2, 0x20), KeySemicolon = key(2, 0x40), KeyRight = key(2, 0x80),
KeyLeft = key(1, 0x01), KeyW = key(1, 0x02), KeyR = key(1, 0x04), KeyY = key(1, 0x08),
KeyI = key(1, 0x10), KeyP = key(1, 0x20), KeyAsterisk = key(1, 0x40), KeyReturn = key(1, 0x80),
Key1 = key(0, 0x01), Key3 = key(0, 0x02), Key5 = key(0, 0x04), Key7 = key(0, 0x08),
Key9 = key(0, 0x10), KeyPlus = key(0, 0x20), KeyGBP = key(0, 0x40), KeyDelete = key(0, 0x80),
};
enum JoystickInput {
Up = 0x04,
Down = 0x08,
Left = 0x10,
Right = 0x80,
Fire = 0x20
};
class UserPortVIA: public MOS::MOS6522<UserPortVIA>, public MOS::MOS6522IRQDelegate {
public:
UserPortVIA();
using MOS6522IRQDelegate::set_interrupt_status;
uint8_t get_port_input(Port port);
void set_control_line_output(Port port, Line line, bool value);
void set_serial_line_state(::Commodore::Serial::Line line, bool value);
void set_joystick_state(JoystickInput input, bool value);
void set_port_output(Port port, uint8_t value, uint8_t mask);
void set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort);
void set_tape(std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape);
private:
uint8_t port_a_;
std::weak_ptr<::Commodore::Serial::Port> serial_port_;
std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape_;
};
class KeyboardVIA: public MOS::MOS6522<KeyboardVIA>, public MOS::MOS6522IRQDelegate {
public:
KeyboardVIA();
using MOS6522IRQDelegate::set_interrupt_status;
void set_key_state(uint16_t key, bool isPressed);
void clear_all_keys();
// to satisfy MOS::MOS6522
uint8_t get_port_input(Port port);
void set_port_output(Port port, uint8_t value, uint8_t mask);
void set_control_line_output(Port port, Line line, bool value);
void set_joystick_state(JoystickInput input, bool value);
void set_serial_port(std::shared_ptr<::Commodore::Serial::Port> serialPort);
private:
uint8_t port_b_;
uint8_t columns_[8];
uint8_t activation_mask_;
std::weak_ptr<::Commodore::Serial::Port> serial_port_;
};
class SerialPort : public ::Commodore::Serial::Port {
public:
void set_input(::Commodore::Serial::Line line, ::Commodore::Serial::LineLevel level);
void set_user_port_via(std::shared_ptr<UserPortVIA> userPortVIA);
private:
std::weak_ptr<UserPortVIA> user_port_via_;
};
class Vic6560: public MOS::MOS6560<Vic6560> {
public:
inline void perform_read(uint16_t address, uint8_t *pixel_data, uint8_t *colour_data) {
*pixel_data = video_memory_map[address >> 10] ? video_memory_map[address >> 10][address & 0x3ff] : 0xff; // TODO
*colour_data = colour_memory[address & 0x03ff];
}
uint8_t *video_memory_map[16];
uint8_t *colour_memory;
};
/// @returns The options available for a Vic-20.
std::vector<std::unique_ptr<Configurable::Option>> get_options();
class Machine:
public CPU::MOS6502::Processor<Machine>,
public CRTMachine::Machine,
public MOS::MOS6522IRQDelegate::Delegate,
public Utility::TypeRecipient,
public Storage::Tape::BinaryTapePlayer::Delegate,
public ConfigurationTarget::Machine {
public ConfigurationTarget::Machine,
public KeyboardMachine::Machine,
public JoystickMachine::Machine,
public Configurable::Device {
public:
Machine();
~Machine();
virtual ~Machine();
void set_rom(ROMSlot slot, size_t length, const uint8_t *data);
void configure_as_target(const StaticAnalyser::Target &target);
/// Creates and returns a Vic-20.
static Machine *Vic20();
void set_key_state(uint16_t key, bool isPressed) { keyboard_via_->set_key_state(key, isPressed); }
void clear_all_keys() { keyboard_via_->clear_all_keys(); }
void set_joystick_state(JoystickInput input, bool isPressed) {
user_port_via_->set_joystick_state(input, isPressed);
keyboard_via_->set_joystick_state(input, isPressed);
}
/// Sets the contents of the rom in @c slot to the buffer @c data of length @c length.
virtual void set_rom(ROMSlot slot, const std::vector<uint8_t> &data) = 0;
void set_memory_size(MemorySize size);
void set_region(Region region);
/// Sets the memory size of this Vic-20.
virtual void set_memory_size(MemorySize size) = 0;
inline void set_use_fast_tape_hack(bool activate) { use_fast_tape_hack_ = activate; }
/// Sets the region of this Vic-20.
virtual void set_region(Region region) = 0;
// to satisfy CPU::MOS6502::Processor
Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value);
void flush() { mos6560_->flush(); }
// to satisfy CRTMachine::Machine
virtual void setup_output(float aspect_ratio);
virtual void close_output();
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() { return mos6560_->get_crt(); }
virtual std::shared_ptr<Outputs::Speaker> get_speaker() { return mos6560_->get_speaker(); }
virtual void run_for(const Cycles cycles) { CPU::MOS6502::Processor<Machine>::run_for(cycles); }
// to satisfy MOS::MOS6522::Delegate
virtual void mos6522_did_change_interrupt_status(void *mos6522);
// for Utility::TypeRecipient
uint16_t *sequence_for_character(Utility::Typer *typer, char character);
void set_typer_for_string(const char *string);
// for Tape::Delegate
virtual void tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape);
private:
uint8_t character_rom_[0x1000];
uint8_t basic_rom_[0x2000];
uint8_t kernel_rom_[0x2000];
uint8_t expansion_ram_[0x8000];
uint8_t *rom_;
uint16_t rom_address_, rom_length_;
uint8_t user_basic_memory_[0x0400];
uint8_t screen_memory_[0x1000];
uint8_t colour_memory_[0x0400];
std::vector<uint8_t> drive_rom_;
uint8_t *processor_read_memory_map_[64];
uint8_t *processor_write_memory_map_[64];
void write_to_map(uint8_t **map, uint8_t *area, uint16_t address, uint16_t length);
Region region_;
std::unique_ptr<Vic6560> mos6560_;
std::shared_ptr<UserPortVIA> user_port_via_;
std::shared_ptr<KeyboardVIA> keyboard_via_;
std::shared_ptr<SerialPort> serial_port_;
std::shared_ptr<::Commodore::Serial::Bus> serial_bus_;
// Tape
std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape_;
bool use_fast_tape_hack_;
bool is_running_at_zero_cost_;
// Disk
std::shared_ptr<::Commodore::C1540::Machine> c1540_;
void install_disk_rom();
/// Enables or disables turbo-speed tape loading.
virtual void set_use_fast_tape_hack(bool activate) = 0;
};
}

View File

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

View File

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

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

View File

@@ -9,19 +9,10 @@
#ifndef Electron_hpp
#define Electron_hpp
#include "../../Processors/6502/6502.hpp"
#include "../../Storage/Tape/Tape.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../Configurable/Configurable.hpp"
#include "../ConfigurationTarget.hpp"
#include "../CRTMachine.hpp"
#include "../Typer.hpp"
#include "Interrupts.hpp"
#include "Plus3.hpp"
#include "Speaker.hpp"
#include "Tape.hpp"
#include "Video.hpp"
#include "../KeyboardMachine.hpp"
#include <cstdint>
#include <vector>
@@ -38,117 +29,38 @@ enum ROMSlot: uint8_t {
ROMSlot12, ROMSlot13, ROMSlot14, ROMSlot15,
ROMSlotOS, ROMSlotDFS, ROMSlotADFS
ROMSlotOS, ROMSlotDFS,
ROMSlotADFS1, ROMSlotADFS2
};
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,
};
/// @returns The options available for an Electron.
std::vector<std::unique_ptr<Configurable::Option>> get_options();
/*!
@abstract Represents an Acorn Electron.
@discussion An instance of Electron::Machine represents the current state of an
Acorn Electron.
*/
class Machine:
public CPU::MOS6502::Processor<Machine>,
public CRTMachine::Machine,
public Tape::Delegate,
public Utility::TypeRecipient,
public ConfigurationTarget::Machine {
public ConfigurationTarget::Machine,
public KeyboardMachine::Machine,
public Configurable::Device {
public:
Machine();
virtual ~Machine();
void set_rom(ROMSlot slot, std::vector<uint8_t> data, bool is_writeable);
/// Creates and returns an Electron.
static Machine *Electron();
void set_key_state(uint16_t key, bool isPressed);
void clear_all_keys();
/*!
Sets the contents of @c slot to @c data. If @c is_writeable is @c true then writing to the slot
is enabled — it acts as if it were sideways RAM. Otherwise the slot is modelled as containing ROM.
*/
virtual void set_rom(ROMSlot slot, const std::vector<uint8_t> &data, bool is_writeable) = 0;
inline void set_use_fast_tape_hack(bool activate) { use_fast_tape_hack_ = activate; }
// to satisfy ConfigurationTarget::Machine
void configure_as_target(const StaticAnalyser::Target &target);
// to satisfy CPU::MOS6502::Processor
Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value);
void flush();
// to satisfy CRTMachine::Machine
virtual void setup_output(float aspect_ratio);
virtual void close_output();
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt();
virtual std::shared_ptr<Outputs::Speaker> get_speaker();
virtual void run_for(const Cycles cycles) { CPU::MOS6502::Processor<Machine>::run_for(cycles); }
// to satisfy Tape::Delegate
virtual void tape_did_change_interrupt_status(Tape *tape);
// for Utility::TypeRecipient
virtual HalfCycles get_typer_delay();
virtual HalfCycles get_typer_frequency();
virtual void set_typer_for_string(const char *string);
private:
inline void update_display();
inline void queue_next_display_interrupt();
inline void update_audio();
inline void signal_interrupt(Interrupt interrupt);
inline void clear_interrupt(Interrupt interrupt);
inline void evaluate_interrupts();
// Things that directly constitute the memory map.
uint8_t roms_[16][16384];
bool rom_write_masks_[16];
uint8_t os_[16384], ram_[32768];
std::vector<uint8_t> dfs_, adfs_;
// Paging
ROMSlot active_rom_;
bool keyboard_is_active_, basic_is_active_;
// Interrupt and keyboard state
uint8_t interrupt_status_, interrupt_control_;
uint8_t key_states_[14];
// Counters related to simultaneous subsystems
Cycles cycles_since_display_update_;
Cycles cycles_since_audio_update_;
int cycles_until_display_interrupt_;
Interrupt next_display_interrupt_;
VideoOutput::Range video_access_range_;
// Tape
Tape tape_;
bool use_fast_tape_hack_;
bool fast_load_is_in_data_;
// Disk
std::unique_ptr<Plus3> plus3_;
bool is_holding_shift_;
int shift_restart_counter_;
// Outputs
std::unique_ptr<VideoOutput> video_output_;
std::shared_ptr<Speaker> speaker_;
bool speaker_is_enabled_;
/// Enables or disables turbo-speed tape loading.
virtual void set_use_fast_tape_hack(bool activate) = 0;
};
}

View File

@@ -1,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(BackSpace, 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

@@ -52,22 +52,23 @@ class Tape:
inline void get_next_tape_pulse();
struct {
int minimum_bits_until_full;
int minimum_bits_until_full = 0;
} input_;
struct {
unsigned int cycles_into_pulse;
unsigned int bits_remaining_until_empty;
unsigned int cycles_into_pulse = 0;
unsigned int bits_remaining_until_empty = 0;
} output_;
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

@@ -8,6 +8,8 @@
#include "Video.hpp"
#include <cstring>
using namespace Electron;
#define graphics_line(v) ((((v) >> 7) - first_graphics_line + field_divider_line) % field_divider_line)
@@ -32,17 +34,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
// 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,17 +58,19 @@ 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));
}
#pragma mark - CRT getter
// MARK: - CRT getter
std::shared_ptr<Outputs::CRT::CRT> VideoOutput::get_crt() {
return crt_;
}
#pragma mark - Display update methods
// MARK: - Display update methods
void VideoOutput::start_pixel_line() {
current_pixel_line_ = (current_pixel_line_+1)&255;
@@ -92,7 +97,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 +115,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 +132,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 +143,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 +165,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 +173,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 +185,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 +234,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;
@@ -247,16 +252,16 @@ void VideoOutput::run_for(const Cycles cycles) {
}
}
#pragma mark - Register hub
// MARK: - Register hub
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 +303,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]);
@@ -333,7 +338,7 @@ void VideoOutput::setup_base_address() {
}
}
#pragma mark - Interrupts
// MARK: - Interrupts
VideoOutput::Interrupt VideoOutput::get_next_interrupt() {
VideoOutput::Interrupt interrupt;
@@ -367,7 +372,7 @@ VideoOutput::Interrupt VideoOutput::get_next_interrupt() {
return interrupt;
}
#pragma mark - RAM timing and access information
// MARK: - RAM timing and access information
unsigned int VideoOutput::get_cycles_until_next_ram_availability(int from_time) {
unsigned int result = 0;
@@ -382,9 +387,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;
@@ -402,7 +407,7 @@ VideoOutput::Range VideoOutput::get_memory_access_range() {
return range;
}
#pragma mark - The screen map
// MARK: - The screen map
void VideoOutput::setup_screen_map() {
/*

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_;
std::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,61 @@
#ifndef KeyboardMachine_h
#define KeyboardMachine_h
#include <cstdint>
#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

@@ -18,19 +18,13 @@ namespace {
const int head_load_request_counter_target = 7653333;
}
Microdisc::Microdisc() :
irq_enable_(false),
delegate_(nullptr),
paging_flags_(BASICDisable),
head_load_request_counter_(-1),
WD1770(P1793),
last_control_(0) {
Microdisc::Microdisc() : WD1770(P1793) {
set_control_register(last_control_, 0xff);
}
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 +47,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 +89,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

@@ -45,11 +45,11 @@ class Microdisc: public WD::WD1770 {
bool get_drive_is_ready();
std::shared_ptr<Storage::Disk::Drive> drives_[4];
int selected_drive_;
bool irq_enable_;
int paging_flags_;
int head_load_request_counter_;
Delegate *delegate_;
uint8_t last_control_;
bool irq_enable_ = false;
int paging_flags_ = BASICDisable;
int head_load_request_counter_ = -1;
Delegate *delegate_ = nullptr;
uint8_t last_control_ = 0;
};
}

View File

@@ -8,286 +8,513 @@
#include "Oric.hpp"
#include "CharacterMapper.hpp"
#include "../MemoryFuzzer.hpp"
#include "Keyboard.hpp"
#include "Microdisc.hpp"
#include "Video.hpp"
#include "../Utility/MemoryFuzzer.hpp"
#include "../Utility/Typer.hpp"
#include "../../Processors/6502/6502.hpp"
#include "../../Components/6522/6522.hpp"
#include "../../Components/AY38910/AY38910.hpp"
#include "../../Storage/Tape/Tape.hpp"
#include "../../Storage/Tape/Parsers/Oric.hpp"
#include "../../ClockReceiver/ForceInline.hpp"
#include "../../Configurable/StandardOptions.hpp"
#include <memory>
using namespace Oric;
namespace Oric {
Machine::Machine() :
use_fast_tape_hack_(false),
typer_delay_(2500000),
keyboard_read_count_(0),
keyboard_(new Keyboard),
ram_top_(0xbfff),
paged_rom_(rom_),
microdisc_is_enabled_(false) {
set_clock_rate(1000000);
via_.set_interrupt_delegate(this);
via_.keyboard = keyboard_;
clear_all_keys();
via_.tape->set_delegate(this);
Memory::Fuzz(ram_, sizeof(ram_));
std::vector<std::unique_ptr<Configurable::Option>> get_options() {
return Configurable::standard_options(
static_cast<Configurable::StandardOptions>(Configurable::DisplayRGBComposite | Configurable::QuickLoadTape)
);
}
void Machine::configure_as_target(const StaticAnalyser::Target &target) {
if(target.tapes.size()) {
via_.tape->set_tape(target.tapes.front());
}
/*!
Models the Oric's keyboard: eight key rows, containing a bitfield of keys set.
if(target.loadingCommand.length()) {
set_typer_for_string(target.loadingCommand.c_str());
}
if(target.oric.has_microdisc) {
microdisc_is_enabled_ = true;
microdisc_did_change_paging_flags(&microdisc_);
microdisc_.set_delegate(this);
}
int drive_index = 0;
for(auto disk : target.disks) {
if(drive_index < 4) microdisc_.set_disk(disk, drive_index);
drive_index++;
}
if(target.oric.use_atmos_rom) {
memcpy(rom_, basic11_rom_.data(), std::min(basic11_rom_.size(), sizeof(rom_)));
is_using_basic11_ = true;
tape_get_byte_address_ = 0xe6c9;
scan_keyboard_address_ = 0xf495;
tape_speed_address_ = 0x024d;
} else {
memcpy(rom_, basic10_rom_.data(), std::min(basic10_rom_.size(), sizeof(rom_)));
is_using_basic11_ = false;
tape_get_byte_address_ = 0xe630;
scan_keyboard_address_ = 0xf43c;
tape_speed_address_ = 0x67;
}
}
void Machine::set_typer_for_string(const char *string) {
std::unique_ptr<CharacterMapper> mapper(new CharacterMapper);
Utility::TypeRecipient::set_typer_for_string(string, std::move(mapper));
}
void Machine::set_rom(ROM rom, const std::vector<uint8_t> &data) {
switch(rom) {
case BASIC11: basic11_rom_ = std::move(data); break;
case BASIC10: basic10_rom_ = std::move(data); break;
case Microdisc: microdisc_rom_ = std::move(data); break;
case Colour:
colour_rom_ = std::move(data);
if(video_output_) video_output_->set_colour_rom(colour_rom_);
break;
}
}
Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
if(address > ram_top_) {
if(isReadOperation(operation)) *value = paged_rom_[address - ram_top_ - 1];
// 024D = 0 => fast; otherwise slow
// E6C9 = read byte: return byte in A
if(address == tape_get_byte_address_ && paged_rom_ == rom_ && use_fast_tape_hack_ && operation == CPU::MOS6502::BusOperation::ReadOpcode && via_.tape->has_tape() && !via_.tape->get_tape()->is_at_end()) {
uint8_t next_byte = via_.tape->get_next_byte(!ram_[tape_speed_address_]);
set_value_of_register(CPU::MOS6502::A, next_byte);
set_value_of_register(CPU::MOS6502::Flags, next_byte ? 0 : CPU::MOS6502::Flag::Zero);
*value = 0x60; // i.e. RTS
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();
}
} else {
if((address & 0xff00) == 0x0300) {
if(microdisc_is_enabled_ && address >= 0x0310) {
switch(address) {
case 0x0310: case 0x0311: case 0x0312: case 0x0313:
if(isReadOperation(operation)) *value = microdisc_.get_register(address);
else microdisc_.set_register(address, *value);
break;
case 0x314: case 0x315: case 0x316: case 0x317:
if(isReadOperation(operation)) *value = microdisc_.get_interrupt_request_register();
else microdisc_.set_control_register(*value);
break;
case 0x318: case 0x319: case 0x31a: case 0x31b:
if(isReadOperation(operation)) *value = microdisc_.get_data_request_register();
break;
/// Sets whether @c key is or is not pressed, per @c is_pressed.
void set_key_state(uint16_t key, bool is_pressed) {
uint8_t mask = key & 0xff;
int line = key >> 8;
if(is_pressed) rows_[line] |= mask;
else rows_[line] &= ~mask;
}
/// Sets all keys as unpressed.
void clear_all_keys() {
memset(rows_, 0, sizeof(rows_));
}
/// Selects the active row.
void set_active_row(uint8_t row) {
row_ = row & 7;
}
/// Queries the keys on the active row specified by @c mask.
bool query_column(uint8_t column_mask) {
return !!(rows_[row_] & column_mask);
}
private:
uint8_t row_ = 0;
uint8_t rows_[8];
};
/*!
Provide's the Oric's tape player: a standard binary-sampled tape which also holds
an instance of the Oric tape parser, to provide fast-tape loading.
*/
class TapePlayer: public Storage::Tape::BinaryTapePlayer {
public:
TapePlayer() : Storage::Tape::BinaryTapePlayer(1000000) {}
/*!
Parses the incoming tape event stream to obtain the next stored byte.
@param use_fast_encoding If set to @c true , inspects the tape as though it
is encoded in the Oric's fast-loading scheme. Otherwise looks for a slow-encoded byte.
@returns The next byte from the tape.
*/
uint8_t get_next_byte(bool use_fast_encoding) {
return static_cast<uint8_t>(parser_.get_next_byte(get_tape(), use_fast_encoding));
}
private:
Storage::Tape::Oric::Parser parser_;
};
/*!
Implements the Oric's VIA's port handler. On the Oric the VIA's ports connect
to the AY, the tape's motor control signal and the keyboard.
*/
class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
public:
VIAPortHandler(TapePlayer &tape_player, Keyboard &keyboard) : tape_player_(tape_player), keyboard_(keyboard) {}
/*!
Reponds to the 6522's control line output change signal; on an Oric A2 is connected to
the AY's BDIR, and B2 is connected to the AY's A2.
*/
void set_control_line_output(MOS::MOS6522::Port port, MOS::MOS6522::Line line, bool value) {
if(line) {
if(port) ay_bdir_ = value; else ay_bc1_ = value;
update_ay();
ay8910_->set_control_lines( (GI::AY38910::ControlLines)((ay_bdir_ ? GI::AY38910::BDIR : 0) | (ay_bc1_ ? GI::AY38910::BC1 : 0) | GI::AY38910::BC2));
}
}
/*!
Reponds to changes in the 6522's port output. On an Oric port B sets the tape motor control
and the keyboard's active row. Port A is connected to the AY's data bus.
*/
void set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t direction_mask) {
if(port) {
keyboard_.set_active_row(value);
tape_player_.set_motor_control(value & 0x40);
} else {
update_ay();
ay8910_->set_data_input(value);
}
}
/*!
Provides input data for the 6522. Port B reads the keyboard, and port A reads from the AY.
*/
uint8_t get_port_input(MOS::MOS6522::Port port) {
if(port) {
uint8_t column = ay8910_->get_port_output(false) ^ 0xff;
return keyboard_.query_column(column) ? 0x08 : 0x00;
} else {
return ay8910_->get_data_output();
}
}
/*!
Advances time. This class manages the AY's concept of time to permit updating-on-demand.
*/
inline void run_for(const Cycles cycles) {
cycles_since_ay_update_ += cycles;
}
/// Flushes any queued behaviour (which, specifically, means on the AY).
void flush() {
ay8910_->run_for(cycles_since_ay_update_.flush());
ay8910_->flush();
}
/// Sets the AY in use by the machine the VIA that uses this port handler sits within.
void set_ay(GI::AY38910::AY38910 *ay) {
ay8910_ = ay;
}
private:
void update_ay() {
ay8910_->run_for(cycles_since_ay_update_.flush());
}
bool ay_bdir_ = false;
bool ay_bc1_ = false;
Cycles cycles_since_ay_update_;
GI::AY38910::AY38910 *ay8910_ = nullptr;
TapePlayer &tape_player_;
Keyboard &keyboard_;
};
class ConcreteMachine:
public CPU::MOS6502::BusHandler,
public MOS::MOS6522::IRQDelegatePortHandler::Delegate,
public Utility::TypeRecipient,
public Storage::Tape::BinaryTapePlayer::Delegate,
public Microdisc::Delegate,
public Machine {
public:
ConcreteMachine() :
m6502_(*this),
via_port_handler_(tape_player_, keyboard_),
via_(via_port_handler_),
paged_rom_(rom_) {
set_clock_rate(1000000);
via_port_handler_.set_interrupt_delegate(this);
tape_player_.set_delegate(this);
Memory::Fuzz(ram_, sizeof(ram_));
}
void set_rom(ROM rom, const std::vector<uint8_t> &data) override final {
switch(rom) {
case BASIC11: basic11_rom_ = std::move(data); break;
case BASIC10: basic10_rom_ = std::move(data); break;
case Microdisc: microdisc_rom_ = std::move(data); break;
case Colour:
colour_rom_ = std::move(data);
if(video_output_) video_output_->set_colour_rom(colour_rom_);
break;
}
}
// Obtains the system ROMs.
bool set_rom_fetcher(const std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> &roms_with_names) override {
auto roms = roms_with_names(
"Oric",
{
"basic10.rom", "basic11.rom",
"microdisc.rom", "colour.rom"
});
for(std::size_t index = 0; index < roms.size(); ++index) {
auto &data = roms[index];
if(!data) return false;
set_rom(static_cast<ROM>(index), *data);
}
return true;
}
void set_key_state(uint16_t key, bool is_pressed) override final {
if(key == KeyNMI) {
m6502_.set_nmi_line(is_pressed);
} else {
keyboard_.set_key_state(key, is_pressed);
}
}
void clear_all_keys() override final {
keyboard_.clear_all_keys();
}
void set_use_fast_tape_hack(bool activate) override final {
use_fast_tape_hack_ = activate;
}
void set_output_device(Outputs::CRT::OutputDevice output_device) override final {
video_output_->set_output_device(output_device);
}
// to satisfy ConfigurationTarget::Machine
void configure_as_target(const StaticAnalyser::Target &target) override final {
if(target.oric.has_microdisc) {
microdisc_is_enabled_ = true;
microdisc_did_change_paging_flags(&microdisc_);
microdisc_.set_delegate(this);
}
if(target.loadingCommand.length()) {
set_typer_for_string(target.loadingCommand.c_str());
}
if(target.oric.use_atmos_rom) {
std::memcpy(rom_, basic11_rom_.data(), std::min(basic11_rom_.size(), sizeof(rom_)));
is_using_basic11_ = true;
tape_get_byte_address_ = 0xe6c9;
scan_keyboard_address_ = 0xf495;
tape_speed_address_ = 0x024d;
} else {
std::memcpy(rom_, basic10_rom_.data(), std::min(basic10_rom_.size(), sizeof(rom_)));
is_using_basic11_ = false;
tape_get_byte_address_ = 0xe630;
scan_keyboard_address_ = 0xf43c;
tape_speed_address_ = 0x67;
}
insert_media(target.media);
}
bool insert_media(const StaticAnalyser::Media &media) override final {
if(media.tapes.size()) {
tape_player_.set_tape(media.tapes.front());
}
int drive_index = 0;
for(auto disk : media.disks) {
if(drive_index < 4) microdisc_.set_disk(disk, drive_index);
drive_index++;
}
return !media.tapes.empty() || (!media.disks.empty() && microdisc_is_enabled_);
}
// to satisfy CPU::MOS6502::BusHandler
forceinline Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
if(address > ram_top_) {
if(isReadOperation(operation)) *value = paged_rom_[address - ram_top_ - 1];
// 024D = 0 => fast; otherwise slow
// E6C9 = read byte: return byte in A
if( address == tape_get_byte_address_ &&
paged_rom_ == rom_ &&
use_fast_tape_hack_ &&
operation == CPU::MOS6502::BusOperation::ReadOpcode &&
tape_player_.has_tape() &&
!tape_player_.get_tape()->is_at_end()) {
uint8_t next_byte = tape_player_.get_next_byte(!ram_[tape_speed_address_]);
m6502_.set_value_of_register(CPU::MOS6502::A, next_byte);
m6502_.set_value_of_register(CPU::MOS6502::Flags, next_byte ? 0 : CPU::MOS6502::Flag::Zero);
*value = 0x60; // i.e. RTS
}
} else {
if(isReadOperation(operation)) *value = via_.get_register(address);
else via_.set_register(address, *value);
if((address & 0xff00) == 0x0300) {
if(microdisc_is_enabled_ && address >= 0x0310) {
switch(address) {
case 0x0310: case 0x0311: case 0x0312: case 0x0313:
if(isReadOperation(operation)) *value = microdisc_.get_register(address);
else microdisc_.set_register(address, *value);
break;
case 0x314: case 0x315: case 0x316: case 0x317:
if(isReadOperation(operation)) *value = microdisc_.get_interrupt_request_register();
else microdisc_.set_control_register(*value);
break;
case 0x318: case 0x319: case 0x31a: case 0x31b:
if(isReadOperation(operation)) *value = microdisc_.get_data_request_register();
break;
}
} else {
if(isReadOperation(operation)) *value = via_.get_register(address);
else via_.set_register(address, *value);
}
} else {
if(isReadOperation(operation))
*value = ram_[address];
else {
if(address >= 0x9800 && address <= 0xc000) update_video();
ram_[address] = *value;
}
}
}
} else {
if(isReadOperation(operation))
*value = ram_[address];
else {
if(address >= 0x9800 && address <= 0xc000) { update_video(); typer_delay_ = 0; }
ram_[address] = *value;
if(typer_ && address == scan_keyboard_address_ && operation == CPU::MOS6502::BusOperation::ReadOpcode) {
// the Oric 1 misses any key pressed on the very first entry into the read keyboard routine, so don't
// do anything until at least the second, regardless of machine
if(!keyboard_read_count_) keyboard_read_count_++;
else if(!typer_->type_next_character()) {
clear_all_keys();
typer_.reset();
}
}
via_.run_for(Cycles(1));
via_port_handler_.run_for(Cycles(1));
tape_player_.run_for(Cycles(1));
if(microdisc_is_enabled_) microdisc_.run_for(Cycles(8));
cycles_since_video_update_++;
return Cycles(1);
}
forceinline void flush() {
update_video();
via_port_handler_.flush();
}
// to satisfy CRTMachine::Machine
void setup_output(float aspect_ratio) override final {
ay8910_.reset(new GI::AY38910::AY38910());
ay8910_->set_clock_rate(1000000);
via_port_handler_.set_ay(ay8910_.get());
video_output_.reset(new VideoOutput(ram_));
if(!colour_rom_.empty()) video_output_->set_colour_rom(colour_rom_);
}
void close_output() override final {
video_output_.reset();
ay8910_.reset();
via_port_handler_.set_ay(nullptr);
}
std::shared_ptr<Outputs::CRT::CRT> get_crt() override final {
return video_output_->get_crt();
}
std::shared_ptr<Outputs::Speaker> get_speaker() override final {
return ay8910_;
}
void run_for(const Cycles cycles) override final {
m6502_.run_for(cycles);
}
// to satisfy MOS::MOS6522IRQDelegate::Delegate
void mos6522_did_change_interrupt_status(void *mos6522) override final {
set_interrupt_line();
}
// to satisfy Storage::Tape::BinaryTapePlayer::Delegate
void tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape_player) override final {
// set CB1
via_.set_control_line_input(MOS::MOS6522::Port::B, MOS::MOS6522::Line::One, !tape_player->get_input());
}
// for Utility::TypeRecipient::Delegate
void set_typer_for_string(const char *string) override final {
std::unique_ptr<CharacterMapper> mapper(new CharacterMapper);
Utility::TypeRecipient::set_typer_for_string(string, std::move(mapper));
}
// for Microdisc::Delegate
void microdisc_did_change_paging_flags(class Microdisc *microdisc) override final {
int flags = microdisc->get_paging_flags();
if(!(flags&Microdisc::PagingFlags::BASICDisable)) {
ram_top_ = 0xbfff;
paged_rom_ = rom_;
} else {
if(flags&Microdisc::PagingFlags::MicrodscDisable) {
ram_top_ = 0xffff;
} else {
ram_top_ = 0xdfff;
paged_rom_ = microdisc_rom_.data();
}
}
}
}
if(typer_ && address == scan_keyboard_address_ && operation == CPU::MOS6502::BusOperation::ReadOpcode) {
// the Oric 1 misses any key pressed on the very first entry into the read keyboard routine, so don't
// do anything until at least the second, regardless of machine
if(!keyboard_read_count_) keyboard_read_count_++;
else if(!typer_->type_next_character()) {
clear_all_keys();
typer_.reset();
void wd1770_did_change_output(WD::WD1770 *wd1770) override final {
set_interrupt_line();
}
}
via_.run_for(Cycles(1));
if(microdisc_is_enabled_) microdisc_.run_for(Cycles(8));
cycles_since_video_update_++;
return Cycles(1);
}
void Machine::flush() {
update_video();
via_.flush();
}
void Machine::update_video() {
video_output_->run_for(cycles_since_video_update_.flush());
}
void Machine::setup_output(float aspect_ratio) {
via_.ay8910.reset(new GI::AY38910::AY38910());
via_.ay8910->set_clock_rate(1000000);
video_output_.reset(new VideoOutput(ram_));
if(!colour_rom_.empty()) video_output_->set_colour_rom(colour_rom_);
}
void Machine::close_output() {
video_output_.reset();
via_.ay8910.reset();
}
void Machine::mos6522_did_change_interrupt_status(void *mos6522) {
set_interrupt_line();
}
void Machine::set_key_state(uint16_t key, bool isPressed) {
if(key == KeyNMI) {
set_nmi_line(isPressed);
} else {
if(isPressed)
keyboard_->rows[key >> 8] |= (key & 0xff);
else
keyboard_->rows[key >> 8] &= ~(key & 0xff);
}
}
void Machine::clear_all_keys() {
memset(keyboard_->rows, 0, sizeof(keyboard_->rows));
}
void Machine::set_use_fast_tape_hack(bool activate) {
use_fast_tape_hack_ = activate;
}
void Machine::set_output_device(Outputs::CRT::OutputDevice output_device) {
video_output_->set_output_device(output_device);
}
void Machine::tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape_player) {
// set CB1
via_.set_control_line_input(VIA::Port::B, VIA::Line::One, !tape_player->get_input());
}
std::shared_ptr<Outputs::CRT::CRT> Machine::get_crt() {
return video_output_->get_crt();
}
std::shared_ptr<Outputs::Speaker> Machine::get_speaker() {
return via_.ay8910;
}
void Machine::run_for(const Cycles cycles) {
CPU::MOS6502::Processor<Machine>::run_for(cycles);
}
#pragma mark - The 6522
Machine::VIA::VIA() :
MOS::MOS6522<Machine::VIA>(),
tape(new TapePlayer) {}
void Machine::VIA::set_control_line_output(Port port, Line line, bool value) {
if(line) {
if(port) ay_bdir_ = value; else ay_bc1_ = value;
update_ay();
}
}
void Machine::VIA::set_port_output(Port port, uint8_t value, uint8_t direction_mask) {
if(port) {
keyboard->row = value;
tape->set_motor_control(value & 0x40);
} else {
ay8910->set_data_input(value);
}
}
uint8_t Machine::VIA::get_port_input(Port port) {
if(port) {
uint8_t column = ay8910->get_port_output(false) ^ 0xff;
return (keyboard->rows[keyboard->row & 7] & column) ? 0x08 : 0x00;
} else {
return ay8910->get_data_output();
}
}
void Machine::VIA::flush() {
ay8910->run_for(cycles_since_ay_update_.flush());
ay8910->flush();
}
void Machine::VIA::run_for(const Cycles cycles) {
cycles_since_ay_update_ += cycles;
MOS::MOS6522<VIA>::run_for(cycles);
tape->run_for(cycles);
}
void Machine::VIA::update_ay() {
ay8910->run_for(cycles_since_ay_update_.flush());
ay8910->set_control_lines( (GI::AY38910::ControlLines)((ay_bdir_ ? GI::AY38910::BDIR : 0) | (ay_bc1_ ? GI::AY38910::BC1 : 0) | GI::AY38910::BC2));
}
#pragma mark - TapePlayer
Machine::TapePlayer::TapePlayer() :
Storage::Tape::BinaryTapePlayer(1000000) {}
uint8_t Machine::TapePlayer::get_next_byte(bool fast) {
return (uint8_t)parser_.get_next_byte(get_tape(), fast);
}
#pragma mark - Microdisc
void Machine::microdisc_did_change_paging_flags(class Microdisc *microdisc) {
int flags = microdisc->get_paging_flags();
if(!(flags&Microdisc::PagingFlags::BASICDisable)) {
ram_top_ = 0xbfff;
paged_rom_ = rom_;
} else {
if(flags&Microdisc::PagingFlags::MicrodscDisable) {
ram_top_ = 0xffff;
} else {
ram_top_ = 0xdfff;
paged_rom_ = microdisc_rom_.data();
KeyboardMapper &get_keyboard_mapper() override {
return keyboard_mapper_;
}
}
// MARK: - Configuration options.
std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
return Oric::get_options();
}
void set_selections(const Configurable::SelectionSet &selections_by_option) override {
bool quickload;
if(Configurable::get_quick_load_tape(selections_by_option, quickload)) {
set_use_fast_tape_hack(quickload);
}
Configurable::Display display;
if(Configurable::get_display(selections_by_option, display)) {
get_crt()->set_output_device((display == Configurable::Display::RGB) ? Outputs::CRT::OutputDevice::Monitor : Outputs::CRT::OutputDevice::Television);
}
}
Configurable::SelectionSet get_accurate_selections() override {
Configurable::SelectionSet selection_set;
Configurable::append_quick_load_tape_selection(selection_set, false);
Configurable::append_display_selection(selection_set, Configurable::Display::Composite);
return selection_set;
}
Configurable::SelectionSet get_user_friendly_selections() override {
Configurable::SelectionSet selection_set;
Configurable::append_quick_load_tape_selection(selection_set, true);
Configurable::append_display_selection(selection_set, Configurable::Display::RGB);
return selection_set;
}
private:
CPU::MOS6502::Processor<ConcreteMachine, false> m6502_;
// RAM and ROM
std::vector<uint8_t> basic11_rom_, basic10_rom_, microdisc_rom_, colour_rom_;
uint8_t ram_[65536], rom_[16384];
Cycles cycles_since_video_update_;
inline void update_video() {
video_output_->run_for(cycles_since_video_update_.flush());
}
// ROM bookkeeping
bool is_using_basic11_ = false;
uint16_t tape_get_byte_address_ = 0, scan_keyboard_address_ = 0, tape_speed_address_ = 0;
int keyboard_read_count_ = 0;
// Outputs
std::unique_ptr<VideoOutput> video_output_;
std::shared_ptr<GI::AY38910::AY38910> ay8910_;
// Inputs
Oric::KeyboardMapper keyboard_mapper_;
// The tape
TapePlayer tape_player_;
bool use_fast_tape_hack_ = false;
VIAPortHandler via_port_handler_;
MOS::MOS6522::MOS6522<VIAPortHandler> via_;
Keyboard keyboard_;
// the Microdisc, if in use
class Microdisc microdisc_;
bool microdisc_is_enabled_ = false;
uint16_t ram_top_ = 0xbfff;
uint8_t *paged_rom_;
inline void set_interrupt_line() {
m6502_.set_irq_line(
via_.get_interrupt_line() ||
(microdisc_is_enabled_ && microdisc_.get_interrupt_request_line()));
}
};
}
void Machine::wd1770_did_change_output(WD::WD1770 *wd1770) {
set_interrupt_line();
using namespace Oric;
Machine *Machine::Oric() {
return new ConcreteMachine;
}
void Machine::set_interrupt_line() {
set_irq_line(
via_.get_interrupt_line() ||
(microdisc_is_enabled_ && microdisc_.get_interrupt_request_line()));
}
Machine::~Machine() {}

View File

@@ -9,163 +9,45 @@
#ifndef Oric_hpp
#define Oric_hpp
#include "../../Configurable/Configurable.hpp"
#include "../ConfigurationTarget.hpp"
#include "../CRTMachine.hpp"
#include "../Typer.hpp"
#include "../../Processors/6502/6502.hpp"
#include "../../Components/6522/6522.hpp"
#include "../../Components/AY38910/AY38910.hpp"
#include "../../Storage/Tape/Parsers/Oric.hpp"
#include "Video.hpp"
#include "Microdisc.hpp"
#include "../../Storage/Tape/Tape.hpp"
#include "../KeyboardMachine.hpp"
#include <cstdint>
#include <vector>
#include <memory>
namespace Oric {
enum Key: uint16_t {
Key3 = 0x0000 | 0x80, KeyX = 0x0000 | 0x40, Key1 = 0x0000 | 0x20,
KeyV = 0x0000 | 0x08, Key5 = 0x0000 | 0x04, KeyN = 0x0000 | 0x02, Key7 = 0x0000 | 0x01,
KeyD = 0x0100 | 0x80, KeyQ = 0x0100 | 0x40, KeyEscape = 0x0100 | 0x20,
KeyF = 0x0100 | 0x08, KeyR = 0x0100 | 0x04, KeyT = 0x0100 | 0x02, KeyJ = 0x0100 | 0x01,
KeyC = 0x0200 | 0x80, Key2 = 0x0200 | 0x40, KeyZ = 0x0200 | 0x20, KeyControl = 0x0200 | 0x10,
Key4 = 0x0200 | 0x08, KeyB = 0x0200 | 0x04, Key6 = 0x0200 | 0x02, KeyM = 0x0200 | 0x01,
KeyQuote = 0x0300 | 0x80, KeyBackSlash = 0x0300 | 0x40,
KeyMinus = 0x0300 | 0x08, KeySemiColon = 0x0300 | 0x04, Key9 = 0x0300 | 0x02, KeyK = 0x0300 | 0x01,
KeyRight = 0x0400 | 0x80, KeyDown = 0x0400 | 0x40, KeyLeft = 0x0400 | 0x20, KeyLeftShift = 0x0400 | 0x10,
KeyUp = 0x0400 | 0x08, KeyFullStop = 0x0400 | 0x04, KeyComma = 0x0400 | 0x02, KeySpace = 0x0400 | 0x01,
KeyOpenSquare = 0x0500 | 0x80, KeyCloseSquare = 0x0500 | 0x40, KeyDelete = 0x0500 | 0x20, KeyFunction = 0x0500 | 0x10,
KeyP = 0x0500 | 0x08, KeyO = 0x0500 | 0x04, KeyI = 0x0500 | 0x02, KeyU = 0x0500 | 0x01,
KeyW = 0x0600 | 0x80, KeyS = 0x0600 | 0x40, KeyA = 0x0600 | 0x20,
KeyE = 0x0600 | 0x08, KeyG = 0x0600 | 0x04, KeyH = 0x0600 | 0x02, KeyY = 0x0600 | 0x01,
KeyEquals = 0x0700 | 0x80, KeyReturn = 0x0700 | 0x20, KeyRightShift = 0x0700 | 0x10,
KeyForwardSlash = 0x0700 | 0x08, Key0 = 0x0700 | 0x04, KeyL = 0x0700 | 0x02, Key8 = 0x0700 | 0x01,
KeyNMI = 0xfffd,
};
enum ROM {
BASIC10, BASIC11, Microdisc, Colour
BASIC10 = 0, BASIC11, Microdisc, Colour
};
/// @returns The options available for an Oric.
std::vector<std::unique_ptr<Configurable::Option>> get_options();
/*!
Models an Oric 1/Atmos with or without a Microdisc.
*/
class Machine:
public CPU::MOS6502::Processor<Machine>,
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public MOS::MOS6522IRQDelegate::Delegate,
public Utility::TypeRecipient,
public Storage::Tape::BinaryTapePlayer::Delegate,
public Microdisc::Delegate {
public KeyboardMachine::Machine,
public Configurable::Device {
public:
Machine();
virtual ~Machine();
void set_rom(ROM rom, const std::vector<uint8_t> &data);
void set_key_state(uint16_t key, bool isPressed);
void clear_all_keys();
/// Creates and returns an Oric.
static Machine *Oric();
void set_use_fast_tape_hack(bool activate);
void set_output_device(Outputs::CRT::OutputDevice output_device);
/// Sets the contents of @c rom to @c data. Assumed to be a setup step; has no effect once a machine is running.
virtual void set_rom(ROM rom, const std::vector<uint8_t> &data) = 0;
// to satisfy ConfigurationTarget::Machine
void configure_as_target(const StaticAnalyser::Target &target);
/// Enables or disables turbo-speed tape loading.
virtual void set_use_fast_tape_hack(bool activate) = 0;
// to satisfy CPU::MOS6502::Processor
Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value);
void flush();
// to satisfy CRTMachine::Machine
virtual void setup_output(float aspect_ratio);
virtual void close_output();
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt();
virtual std::shared_ptr<Outputs::Speaker> get_speaker();
virtual void run_for(const Cycles cycles);
// to satisfy MOS::MOS6522IRQDelegate::Delegate
void mos6522_did_change_interrupt_status(void *mos6522);
// to satisfy Storage::Tape::BinaryTapePlayer::Delegate
void tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape_player);
// for Utility::TypeRecipient::Delegate
void set_typer_for_string(const char *string);
// for Microdisc::Delegate
void microdisc_did_change_paging_flags(class Microdisc *microdisc);
void wd1770_did_change_output(WD::WD1770 *wd1770);
private:
// RAM and ROM
std::vector<uint8_t> basic11_rom_, basic10_rom_, microdisc_rom_, colour_rom_;
uint8_t ram_[65536], rom_[16384];
Cycles cycles_since_video_update_;
inline void update_video();
// ROM bookkeeping
bool is_using_basic11_;
uint16_t tape_get_byte_address_, scan_keyboard_address_, tape_speed_address_;
int keyboard_read_count_;
// Outputs
std::unique_ptr<VideoOutput> video_output_;
// Keyboard
class Keyboard {
public:
uint8_t row;
uint8_t rows[8];
};
int typer_delay_;
// The tape
class TapePlayer: public Storage::Tape::BinaryTapePlayer {
public:
TapePlayer();
uint8_t get_next_byte(bool fast);
private:
Storage::Tape::Oric::Parser parser_;
};
bool use_fast_tape_hack_;
// VIA (which owns the tape and the AY)
class VIA: public MOS::MOS6522<VIA>, public MOS::MOS6522IRQDelegate {
public:
VIA();
using MOS6522IRQDelegate::set_interrupt_status;
void set_control_line_output(Port port, Line line, bool value);
void set_port_output(Port port, uint8_t value, uint8_t direction_mask);
uint8_t get_port_input(Port port);
inline void run_for(const Cycles cycles);
std::shared_ptr<GI::AY38910::AY38910> ay8910;
std::unique_ptr<TapePlayer> tape;
std::shared_ptr<Keyboard> keyboard;
void flush();
private:
void update_ay();
bool ay_bdir_, ay_bc1_;
Cycles cycles_since_ay_update_;
};
VIA via_;
std::shared_ptr<Keyboard> keyboard_;
// the Microdisc, if in use
class Microdisc microdisc_;
bool microdisc_is_enabled_;
uint16_t ram_top_;
uint8_t *paged_rom_;
inline void set_interrupt_line();
/// Sets the type of display the Oric is connected to.
virtual void set_output_device(Outputs::CRT::OutputDevice output_device) = 0;
};
}

View File

@@ -21,12 +21,9 @@ namespace {
VideoOutput::VideoOutput(uint8_t *memory) :
ram_(memory),
frame_counter_(0), counter_(0),
is_graphics_mode_(false),
character_set_base_address_(0xb400),
crt_(new Outputs::CRT::CRT(64*6, 6, Outputs::CRT::DisplayType::PAL50, 2)),
v_sync_start_position_(PAL50VSyncStartPosition), v_sync_end_position_(PAL50VSyncEndPosition),
counter_period_(PAL50Period), next_frame_is_sixty_hertz_(false),
crt_(new Outputs::CRT::CRT(64*6, 6, Outputs::CRT::DisplayType::PAL50, 2)) {
counter_period_(PAL50Period) {
crt_->set_rgb_sampling_function(
"vec3 rgb_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate)"
"{"
@@ -44,8 +41,8 @@ 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));
set_output_device(Outputs::CRT::OutputDevice::Television);
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) {
@@ -54,18 +51,18 @@ 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]);
for(std::size_t c = 0; c < 8; c++) {
std::size_t index = (c << 2);
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) {
for(size_t c = 0; c < 8; c++) {
colour_forms_[c] = (uint16_t)((colour_forms_[c] >> 8) | (colour_forms_[c] << 8));
if(*reinterpret_cast<uint8_t *>(&test_value) != 0x01) {
for(std::size_t c = 0; c < 8; c++) {
colour_forms_[c] = static_cast<uint16_t>((colour_forms_[c] >> 8) | (colour_forms_[c] << 8));
}
}
}
@@ -89,7 +86,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 +94,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_++;
@@ -132,9 +129,9 @@ void VideoOutput::run_for(const Cycles cycles) {
if(control_byte & 0x60) {
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);
if(output_device_ == Outputs::CRT::OutputDevice::Monitor) {
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];
@@ -186,7 +183,7 @@ void VideoOutput::run_for(const Cycles cycles) {
pixel_target_[0] = pixel_target_[1] =
pixel_target_[2] = pixel_target_[3] =
pixel_target_[4] = pixel_target_[5] =
(output_device_ == Outputs::CRT::Monitor) ? paper_ ^ inverse_mask : colour_forms_[paper_ ^ inverse_mask];
(output_device_ == Outputs::CRT::OutputDevice::Monitor) ? paper_ ^ inverse_mask : colour_forms_[paper_ ^ inverse_mask];
}
}
if(pixel_target_) pixel_target_ += 6;
@@ -202,7 +199,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

@@ -27,7 +27,7 @@ class VideoOutput {
std::shared_ptr<Outputs::CRT::CRT> crt_;
// Counters and limits
int counter_, frame_counter_;
int counter_ = 0, frame_counter_ = 0;
int v_sync_start_position_, v_sync_end_position_, counter_period_;
// Output target and device
@@ -38,11 +38,11 @@ class VideoOutput {
// Registers
uint8_t ink_, paper_;
int character_set_base_address_;
int character_set_base_address_ = 0xb400;
inline void set_character_set_base_address();
bool is_graphics_mode_;
bool next_frame_is_sixty_hertz_;
bool is_graphics_mode_ = false;
bool next_frame_is_sixty_hertz_ = false;
bool use_alternative_character_set_;
bool use_double_height_characters_;
bool blink_text_;

27
Machines/ROMMachine.hpp Normal file
View File

@@ -0,0 +1,27 @@
//
// ROMMachine.hpp
// Clock Signal
//
// Created by Thomas Harte on 07/11/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef ROMMachine_hpp
#define ROMMachine_hpp
#include <functional>
#include <memory>
#include <vector>
namespace ROMMachine {
struct Machine {
/*!
Provides the machine with a way to obtain such ROMs as it needs.
*/
virtual bool set_rom_fetcher(const std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> &rom_with_name) { return true; }
};
}
#endif /* ROMMachine_h */

View File

@@ -0,0 +1,105 @@
//
// MachineForTarget.cpp
// Clock Signal
//
// Created by Thomas Harte on 04/11/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "MachineForTarget.hpp"
#include "../AmstradCPC/AmstradCPC.hpp"
#include "../Atari2600/Atari2600.hpp"
#include "../Commodore/Vic-20/Vic20.hpp"
#include "../Electron/Electron.hpp"
#include "../Oric/Oric.hpp"
#include "../ZX8081/ZX8081.hpp"
namespace {
template<typename T> class TypedDynamicMachine: public ::Machine::DynamicMachine {
public:
TypedDynamicMachine(T *machine) : machine_(machine) {}
ConfigurationTarget::Machine *configuration_target() override {
return get<ConfigurationTarget::Machine>();
}
CRTMachine::Machine *crt_machine() override {
return get<CRTMachine::Machine>();
}
JoystickMachine::Machine *joystick_machine() override {
return get<JoystickMachine::Machine>();
}
KeyboardMachine::Machine *keyboard_machine() override {
return get<KeyboardMachine::Machine>();
}
Configurable::Device *configurable_device() override {
return get<Configurable::Device>();
}
Utility::TypeRecipient *type_recipient() override {
return get<Utility::TypeRecipient>();
}
private:
template <typename Class> Class *get() {
return dynamic_cast<Class *>(machine_.get());
}
std::unique_ptr<T> machine_;
};
}
::Machine::DynamicMachine *::Machine::MachineForTarget(const StaticAnalyser::Target &target) {
switch(target.machine) {
case StaticAnalyser::Target::AmstradCPC: return new TypedDynamicMachine<AmstradCPC::Machine>(AmstradCPC::Machine::AmstradCPC());
case StaticAnalyser::Target::Atari2600: return new TypedDynamicMachine<Atari2600::Machine>(Atari2600::Machine::Atari2600());
case StaticAnalyser::Target::Electron: return new TypedDynamicMachine<Electron::Machine>(Electron::Machine::Electron());
case StaticAnalyser::Target::Oric: return new TypedDynamicMachine<Oric::Machine>(Oric::Machine::Oric());
case StaticAnalyser::Target::Vic20: return new TypedDynamicMachine<Commodore::Vic20::Machine>(Commodore::Vic20::Machine::Vic20());
case StaticAnalyser::Target::ZX8081: return new TypedDynamicMachine<ZX8081::Machine>(ZX8081::Machine::ZX8081(target));
default: return nullptr;
}
}
std::string Machine::ShortNameForTargetMachine(const StaticAnalyser::Target::Machine machine) {
switch(machine) {
case StaticAnalyser::Target::AmstradCPC: return "AmstradCPC";
case StaticAnalyser::Target::Atari2600: return "Atari2600";
case StaticAnalyser::Target::Electron: return "Electron";
case StaticAnalyser::Target::Oric: return "Oric";
case StaticAnalyser::Target::Vic20: return "Vic20";
case StaticAnalyser::Target::ZX8081: return "ZX8081";
default: return "";
}
}
std::string Machine::LongNameForTargetMachine(StaticAnalyser::Target::Machine machine) {
switch(machine) {
case StaticAnalyser::Target::AmstradCPC: return "Amstrad CPC";
case StaticAnalyser::Target::Atari2600: return "Atari 2600";
case StaticAnalyser::Target::Electron: return "Acorn Electron";
case StaticAnalyser::Target::Oric: return "Oric";
case StaticAnalyser::Target::Vic20: return "Vic 20";
case StaticAnalyser::Target::ZX8081: return "ZX80/81";
default: return "";
}
}
std::map<std::string, std::vector<std::unique_ptr<Configurable::Option>>> Machine::AllOptionsByMachineName() {
std::map<std::string, std::vector<std::unique_ptr<Configurable::Option>>> options;
options.emplace(std::make_pair(LongNameForTargetMachine(StaticAnalyser::Target::Electron), Electron::get_options()));
options.emplace(std::make_pair(LongNameForTargetMachine(StaticAnalyser::Target::Oric), Oric::get_options()));
options.emplace(std::make_pair(LongNameForTargetMachine(StaticAnalyser::Target::Vic20), Commodore::Vic20::get_options()));
options.emplace(std::make_pair(LongNameForTargetMachine(StaticAnalyser::Target::ZX8081), ZX8081::get_options()));
return options;
}

View File

@@ -0,0 +1,67 @@
//
// MachineForTarget.hpp
// Clock Signal
//
// Created by Thomas Harte on 04/11/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef MachineForTarget_hpp
#define MachineForTarget_hpp
#include "../../StaticAnalyser/StaticAnalyser.hpp"
#include "../../Configurable/Configurable.hpp"
#include "../ConfigurationTarget.hpp"
#include "../CRTMachine.hpp"
#include "../JoystickMachine.hpp"
#include "../KeyboardMachine.hpp"
#include "Typer.hpp"
#include <map>
#include <string>
namespace Machine {
/*!
Provides the structure for owning a machine and dynamically casting it as desired without knowledge of
the machine's parent class or, therefore, the need to establish a common one.
*/
struct DynamicMachine {
virtual ConfigurationTarget::Machine *configuration_target() = 0;
virtual CRTMachine::Machine *crt_machine() = 0;
virtual JoystickMachine::Machine *joystick_machine() = 0;
virtual KeyboardMachine::Machine *keyboard_machine() = 0;
virtual Configurable::Device *configurable_device() = 0;
virtual Utility::TypeRecipient *type_recipient() = 0;
};
/*!
Allocates an instance of DynamicMachine holding a machine that can
receive the supplied target. The machine has been allocated on the heap.
It is the caller's responsibility to delete the class when finished.
*/
DynamicMachine *MachineForTarget(const StaticAnalyser::Target &target);
/*!
Returns a short string name for the machine identified by the target,
which is guaranteed not to have any spaces or other potentially
filesystem-bothering contents.
*/
std::string ShortNameForTargetMachine(const StaticAnalyser::Target::Machine target);
/*!
Returns a long string name for the machine identified by the target,
usable for presentation to a human.
*/
std::string LongNameForTargetMachine(const StaticAnalyser::Target::Machine target);
/*!
Returns a map from machine name to the list of options that machine
exposes, for all machines.
*/
std::map<std::string, std::vector<std::unique_ptr<Configurable::Option>>> AllOptionsByMachineName();
}
#endif /* MachineForTarget_hpp */

View File

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

View File

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

View File

@@ -7,19 +7,19 @@
//
#include "Typer.hpp"
#include <stdlib.h>
#include <cstdlib>
#include <cstring>
using namespace Utility;
Typer::Typer(const char *string, HalfCycles delay, HalfCycles frequency, std::unique_ptr<CharacterMapper> character_mapper, Delegate *delegate) :
counter_(-delay),
frequency_(frequency),
string_pointer_(0),
counter_(-delay),
delegate_(delegate),
phase_(0),
character_mapper_(std::move(character_mapper)) {
size_t string_size = strlen(string) + 3;
string_ = (char *)malloc(string_size);
std::size_t string_size = std::strlen(string) + 3;
string_ = (char *)std::malloc(string_size);
snprintf(string_, string_size, "%c%s%c", Typer::BeginString, string, Typer::EndString);
}
@@ -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;
@@ -63,7 +63,7 @@ bool Typer::type_next_character() {
if(!try_type_next_character()) {
phase_ = 0;
if(!string_[string_pointer_]) {
free(string_);
std::free(string_);
string_ = nullptr;
return false;
}
@@ -77,14 +77,14 @@ bool Typer::type_next_character() {
}
Typer::~Typer() {
free(string_);
std::free(string_);
}
#pragma mark - Character mapper
// 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);
uint16_t *CharacterMapper::table_lookup_sequence_for_character(KeySequence *sequences, std::size_t length, char character) {
std::size_t ucharacter = static_cast<std::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];
@@ -41,7 +32,7 @@ class CharacterMapper {
with @c length entries, returns the sequence for character @c character if it exists; otherwise
returns @c nullptr.
*/
uint16_t *table_lookup_sequence_for_character(KeySequence *sequences, size_t length, char character);
uint16_t *table_lookup_sequence_for_character(KeySequence *sequences, std::size_t length, char character);
};
/*!
@@ -72,11 +63,11 @@ class Typer {
private:
char *string_;
size_t string_pointer_;
std::size_t string_pointer_ = 0;
HalfCycles frequency_;
HalfCycles counter_;
int phase_;
int phase_ = 0;
Delegate *delegate_;
std::unique_ptr<CharacterMapper> character_mapper_;
@@ -107,12 +98,21 @@ class TypeRecipient: public Typer::Delegate {
*/
void typer_reset(Typer *typer) {
clear_all_keys();
// It's unsafe to deallocate typer right now, since it is the caller, but also it has a small
// memory footprint and it's desireable not to imply that the subclass need call it any more.
// So shuffle it off into a siding.
previous_typer_ = std::move(typer_);
typer_ = nullptr;
}
protected:
virtual HalfCycles get_typer_delay() { return HalfCycles(0); }
virtual HalfCycles get_typer_frequency() { return HalfCycles(0); }
std::unique_ptr<Typer> typer_;
private:
std::unique_ptr<Typer> previous_typer_;
};
}

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

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