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

Compare commits

...

619 Commits

Author SHA1 Message Date
Thomas Harte
4cf2e16b5c Merge pull request #338 from TomHarte/MSXComposite
onsolidates Mac presentation of composite video selection.
2018-01-15 15:38:45 -08:00
Thomas Harte
9cbd61e709 Replaces CRT quantity assert with test.
Primarily to handle television/composite target switches that can unsync the buffers.
2018-01-15 18:37:09 -05:00
Thomas Harte
0202c7afb2 Consolidates Mac presentation of composite video selection.
Moves handling of an RGB/composite into `MachinePanel`, eliminating the need for `ElectronOptionsPanel` and `OricOptionsPanel`; similarly merges the MSX and Electron options panels so as to provide television/monitor selection for the MSX.
2018-01-15 18:36:22 -05:00
Thomas Harte
c187c5a637 Merge pull request #337 from TomHarte/DoublePhase
Corrects calculation of intermediate buffer width multiplier.
2018-01-15 13:57:26 -08:00
Thomas Harte
23c34a8c14 Corrects calculation of intermediate buffer width multiplier.
Specifically: I had failed to factor in that the multiplied-up input frequency might be less than than the full width of the bitmap.

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

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

9
.gitignore vendored
View File

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

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.
@@ -169,13 +169,20 @@ class HalfCycles: public WrappedInt<HalfCycles> {
return Cycles(length_ >> 1);
}
///Flushes the whole cycles in @c this, subtracting that many from the total stored here.
/// Flushes the whole cycles in @c this, subtracting that many from the total stored here.
inline Cycles flush_cycles() {
Cycles result(length_ >> 1);
length_ &= 1;
return result;
}
/// Flushes the half cycles in @c this, returning the number stored and setting this total to zero.
inline HalfCycles flush() {
HalfCycles result(length_);
length_ = 0;
return result;
}
/*!
Severs from @c this the effect of dividing by @c divisor — @c this will end up with
the value of @c this modulo @c divisor and @c divided by @c divisor is returned.

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,47 +7,35 @@
//
#include "1770.hpp"
#include "../../Storage/Disk/Encodings/MFM.hpp"
#include "../../Storage/Disk/Encodings/MFM/Constants.hpp"
using namespace WD;
WD1770::Status::Status() :
type(Status::One),
write_protect(false),
record_type(false),
spin_up(false),
record_not_found(false),
crc_error(false),
seek_error(false),
lost_data(false),
data_request(false),
interrupt_request(false),
busy(false) {}
WD1770::WD1770(Personality p) :
Storage::Disk::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) {
switch(address&3) {
case 0: {
if((value&0xf0) == 0xd0) {
printf("!!!TODO: force interrupt!!!\n");
update_status([] (Status &status) {
status.type = Status::One;
});
if(value == 0xd0) {
// Force interrupt **immediately**.
printf("Force interrupt immediately\n");
posit_event(static_cast<int>(Event1770::ForceInterrupt));
} else {
printf("!!!TODO: force interrupt!!!\n");
update_status([] (Status &status) {
status.type = Status::One;
});
}
} else {
command_ = value;
posit_event((int)Event1770::Command);
posit_event(static_cast<int>(Event1770::Command));
}
}
break;
@@ -75,7 +63,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 +79,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 +103,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 +157,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,13 +175,23 @@ void WD1770::posit_event(int new_event_type) {
}
}
if(!(interesting_event_mask_ & (int)new_event_type)) return;
interesting_event_mask_ &= ~new_event_type;
if(new_event_type == static_cast<int>(Event1770::ForceInterrupt)) {
interesting_event_mask_ = 0;
resume_point_ = 0;
update_status([] (Status &status) {
status.type = Status::One;
status.data_request = false;
});
} else {
if(!(interesting_event_mask_ & static_cast<int>(new_event_type))) return;
interesting_event_mask_ &= ~new_event_type;
}
Status new_status;
BEGIN_SECTION()
// Wait for a new command, branch to the appropriate handler.
case 0:
wait_for_command:
printf("Idle...\n");
set_data_mode(DataMode::Scanning);
@@ -257,7 +255,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 +278,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 +308,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 +374,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 +384,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 +392,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) {
@@ -478,7 +476,7 @@ void WD1770::posit_event(int new_event_type) {
sector_++;
goto test_type2_write_protection;
}
printf("Read sector %d\n", sector_);
printf("Finished reading sector %d\n", sector_);
goto wait_for_command;
}
goto type2_check_crc;
@@ -594,7 +592,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 +609,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 +650,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 +672,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 +717,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 +778,9 @@ void WD1770::update_status(std::function<void(Status &)> updater) {
}
void WD1770::set_head_load_request(bool head_load) {}
void WD1770::set_motor_on(bool motor_on) {}
void WD1770::set_head_loaded(bool head_loaded) {
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_;
@@ -116,21 +116,22 @@ class WD1770: public Storage::Disk::MFMController {
Command = (1 << 3), // Indicates receipt of a new command.
HeadLoad = (1 << 4), // Indicates the head has been loaded (1973 only).
Timer = (1 << 5), // Indicates that the delay_time_-powered timer has timed out.
IndexHoleTarget = (1 << 6) // Indicates that index_hole_count_ has reached index_hole_count_target_.
IndexHoleTarget = (1 << 6), // Indicates that index_hole_count_ has reached index_hole_count_target_.
ForceInterrupt = (1 << 7) // Indicates a forced interrupt.
};
void posit_event(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,22 +8,22 @@
#include "6560.hpp"
#include <cstring>
using namespace MOS;
Speaker::Speaker() :
volume_(0),
control_registers_{0, 0, 0, 0},
shift_registers_{0, 0, 0, 0},
counters_{2, 1, 0, 0} {} // create a slight phase offset for the three channels
AudioGenerator::AudioGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue) :
audio_queue_(audio_queue) {}
void Speaker::set_volume(uint8_t volume) {
enqueue([=]() {
void AudioGenerator::set_volume(uint8_t volume) {
audio_queue_.defer([=]() {
volume_ = volume;
});
}
void Speaker::set_control(int channel, uint8_t value) {
enqueue([=]() {
void AudioGenerator::set_control(int channel, uint8_t value) {
audio_queue_.defer([=]() {
control_registers_[channel] = value;
});
}
@@ -98,14 +98,14 @@ static uint8_t noise_pattern[] = {
#define shift(r) shift_registers_[r] = (shift_registers_[r] << 1) | (((shift_registers_[r]^0x80)&control_registers_[r]) >> 7)
#define increment(r) shift_registers_[r] = (shift_registers_[r]+1)%8191
#define update(r, m, up) counters_[r]++; if((counters_[r] >> m) == 0x80) { up(r); counters_[r] = (unsigned int)(control_registers_[r]&0x7f) << m; }
#define update(r, m, up) counters_[r]++; if((counters_[r] >> m) == 0x80) { up(r); counters_[r] = static_cast<unsigned int>(control_registers_[r]&0x7f) << m; }
// Note on slightly askew test: as far as I can make out, if the value in the register is 0x7f then what's supposed to happen
// is that the 0x7f is loaded, on the next clocked cycle the Vic spots a 0x7f, pumps the output, reloads, etc. No increment
// ever occurs. It's conditional. I don't really want two conditionals if I can avoid it so I'm incrementing regardless and
// testing against 0x80. The effect should be the same: loading with 0x7f means an output update every cycle, loading with 0x7e
// means every second cycle, etc.
void Speaker::get_samples(unsigned int number_of_samples, int16_t *target) {
void AudioGenerator::get_samples(std::size_t number_of_samples, int16_t *target) {
for(unsigned int c = 0; c < number_of_samples; c++) {
update(0, 2, shift);
update(1, 1, shift);
@@ -123,7 +123,7 @@ void Speaker::get_samples(unsigned int number_of_samples, int16_t *target) {
}
}
void Speaker::skip_samples(unsigned int number_of_samples) {
void AudioGenerator::skip_samples(std::size_t number_of_samples) {
for(unsigned int c = 0; c < number_of_samples; c++) {
update(0, 2, shift);
update(1, 1, shift);

View File

@@ -9,28 +9,32 @@
#ifndef _560_hpp
#define _560_hpp
#include "../../Outputs/CRT/CRT.hpp"
#include "../../Outputs/Speaker.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../Concurrency/AsyncTaskQueue.hpp"
#include "../../Outputs/CRT/CRT.hpp"
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
namespace MOS {
// audio state
class Speaker: public ::Outputs::Filter<Speaker> {
class AudioGenerator: public ::Outputs::Speaker::SampleSource {
public:
Speaker();
AudioGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue);
void set_volume(uint8_t volume);
void set_control(int channel, uint8_t value);
void get_samples(unsigned int number_of_samples, int16_t *target);
void skip_samples(unsigned int number_of_samples);
void get_samples(std::size_t number_of_samples, int16_t *target);
void skip_samples(std::size_t number_of_samples);
private:
unsigned int counters_[4];
unsigned int shift_registers_[4];
uint8_t control_registers_[4];
uint8_t volume_;
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
unsigned int counters_[4] = {2, 1, 0, 0}; // create a slight phase offset for the three channels
unsigned int shift_registers_[4] = {0, 0, 0, 0};
uint8_t control_registers_[4] = {0, 0, 0, 0};
uint8_t volume_ = 0;
};
/*!
@@ -44,13 +48,10 @@ 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)),
audio_generator_(audio_queue_),
speaker_(audio_generator_)
{
crt_->set_composite_sampling_function(
"float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
"{"
@@ -66,11 +67,15 @@ 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_; }
std::shared_ptr<Outputs::Speaker> get_speaker() { return speaker_; }
Outputs::CRT::CRT *get_crt() { return crt_.get(); }
Outputs::Speaker::Speaker *get_speaker() { return &speaker_; }
void set_high_frequency_cutoff(float cutoff) {
speaker_.set_high_frequency_cutoff(cutoff);
}
enum OutputMode {
PAL, NTSC
@@ -109,9 +114,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 +125,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 +133,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 +146,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 +223,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 +275,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_++;
@@ -325,7 +330,10 @@ template <class T> class MOS6560 {
/*!
Causes the 6560 to flush as much pending CRT and speaker communications as possible.
*/
inline void flush() { update_audio(); speaker_->flush(); }
inline void flush() {
update_audio();
audio_queue_.perform();
}
/*!
Writes to a 6560 register.
@@ -345,7 +353,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 +362,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:
@@ -363,13 +371,13 @@ template <class T> class MOS6560 {
case 0xc:
case 0xd:
update_audio();
speaker_->set_control(address - 0xa, value);
audio_generator_.set_control(address - 0xa, value);
break;
case 0xe:
update_audio();
registers_.auxiliary_colour = colours_[value >> 4];
speaker_->set_volume(value & 0xf);
audio_generator_.set_volume(value & 0xf);
break;
case 0xf: {
@@ -399,18 +407,21 @@ 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;
}
}
private:
std::shared_ptr<Outputs::CRT::CRT> crt_;
std::unique_ptr<Outputs::CRT::CRT> crt_;
Concurrency::DeferringAsyncTaskQueue audio_queue_;
AudioGenerator audio_generator_;
Outputs::Speaker::LowpassSpeaker<AudioGenerator> speaker_;
std::shared_ptr<Speaker> speaker_;
Cycles cycles_since_speaker_update_;
void update_audio() {
speaker_->run_for(Cycles(cycles_since_speaker_update_.divide(Cycles(4))));
speaker_.run_for(audio_queue_, Cycles(cycles_since_speaker_update_.divide(Cycles(4))));
}
// register state
@@ -432,7 +443,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 +458,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

@@ -76,8 +76,8 @@ template <class T> class i8255 {
private:
void update_outputs() {
port_handler_.set_value(0, outputs_[0]);
port_handler_.set_value(1, outputs_[1]);
if(!(control_ & 0x10)) port_handler_.set_value(0, outputs_[0]);
if(!(control_ & 0x02)) port_handler_.set_value(1, outputs_[1]);
port_handler_.set_value(2, outputs_[2]);
}

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

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

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

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

@@ -0,0 +1,86 @@
//
// 9918.hpp
// Clock Signal
//
// Created by Thomas Harte on 25/11/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef TMS9918_hpp
#define TMS9918_hpp
#include "../../Outputs/CRT/CRT.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "Implementation/9918Base.hpp"
#include <cstdint>
namespace TI {
/*!
Provides emulation of the TMS9918a, TMS9928 and TMS9929. Likely in the future to be the
vessel for emulation of sufficiently close derivatives, such as the Master System VDP.
The TMS9918 and descendants are video display generators that own their own RAM, making it
accessible through an implicitly-timed register interface, and (depending on model) can generate
PAL and NTSC component and composite video.
These chips have only one non-on-demand interaction with the outside world: an interrupt line.
See get_time_until_interrupt and get_interrupt_line for asynchronous operation options.
*/
class TMS9918: public TMS9918Base {
public:
enum Personality {
TMS9918A, // includes the 9928 and 9929; set TV standard and output device as desired.
};
/*!
Constructs an instance of the drive controller that behaves according to personality @c p.
@param p The type of controller to emulate.
*/
TMS9918(Personality p);
enum TVStandard {
/*! i.e. 50Hz output at around 312.5 lines/field */
PAL,
/*! i.e. 60Hz output at around 262.5 lines/field */
NTSC
};
/*! Sets the TV standard for this TMS, if that is hard-coded in hardware. */
void set_tv_standard(TVStandard standard);
/*! Provides the CRT this TMS is connected to. */
Outputs::CRT::CRT *get_crt();
/*!
Runs the VCP for the number of cycles indicate; it is an implicit assumption of the code
that the input clock rate is 3579545 Hz — the NTSC colour clock rate.
*/
void run_for(const HalfCycles cycles);
/*! Sets a register value. */
void set_register(int address, uint8_t value);
/*! Gets a register value. */
uint8_t get_register(int address);
/*!
Returns the amount of time until get_interrupt_line would next return true if
there are no interceding calls to set_register or get_register.
If get_interrupt_line is true now, returns zero. If get_interrupt_line would
never return true, returns -1.
*/
HalfCycles get_time_until_interrupt();
/*!
@returns @c true if the interrupt line is currently active; @c false otherwise.
*/
bool get_interrupt_line();
};
};
#endif /* TMS9918_hpp */

View File

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

View File

@@ -8,18 +8,11 @@
#include "AY38910.hpp"
#include <cmath>
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(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {
// set up envelope lookup tables
for(int c = 0; c < 16; c++) {
for(int p = 0; p < 32; p++) {
@@ -67,17 +60,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);
}
void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) {
unsigned int c = 0;
void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) {
std::size_t c = 0;
while((master_divider_&7) && c < number_of_samples) {
target[c] = output_volume_;
master_divider_++;
@@ -160,14 +149,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;
@@ -178,7 +167,7 @@ void AY38910::set_register_value(uint8_t value) {
registers_[selected_register_] = value;
if(selected_register_ < 14) {
int selected_register = selected_register_;
enqueue([=] () {
task_queue_.defer([=] () {
uint8_t masked_value = value;
switch(selected_register) {
case 0: case 2: case 4:
@@ -186,7 +175,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 +190,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 +222,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 +251,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

@@ -9,7 +9,8 @@
#ifndef AY_3_8910_hpp
#define AY_3_8910_hpp
#include "../../Outputs/Speaker.hpp"
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
#include "../../Concurrency/AsyncTaskQueue.hpp"
namespace GI {
namespace AY38910 {
@@ -26,7 +27,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 +36,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) {}
@@ -55,13 +56,10 @@ enum ControlLines {
noise generator and a volume envelope generator, which also provides two bidirectional
interface ports.
*/
class AY38910: public ::Outputs::Filter<AY38910> {
class AY38910: public ::Outputs::Speaker::SampleSource {
public:
/// Creates a new AY38910.
AY38910();
/// Sets the clock rate at which this AY38910 will be run.
void set_clock_rate(double clock_rate);
AY38910(Concurrency::DeferringAsyncTaskQueue &task_queue);
/// Sets the value the AY would read from its data lines if it were not outputting.
void set_data_input(uint8_t r);
@@ -86,27 +84,30 @@ class AY38910: public ::Outputs::Filter<AY38910> {
void set_port_handler(PortHandler *);
// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter; not for public consumption
void get_samples(unsigned int number_of_samples, int16_t *target);
void get_samples(std::size_t number_of_samples, int16_t *target);
private:
int selected_register_;
uint8_t registers_[16], output_registers_[16];
Concurrency::DeferringAsyncTaskQueue &task_queue_;
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

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

View File

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

View File

@@ -79,3 +79,21 @@ void AsyncTaskQueue::flush() {
flush_condition->wait(lock);
#endif
}
void DeferringAsyncTaskQueue::defer(std::function<void(void)> function) {
if(!deferred_tasks_) {
deferred_tasks_.reset(new std::list<std::function<void(void)>>);
}
deferred_tasks_->push_back(function);
}
void DeferringAsyncTaskQueue::perform() {
if(!deferred_tasks_) return;
std::shared_ptr<std::list<std::function<void(void)>>> deferred_tasks = deferred_tasks_;
deferred_tasks_.reset();
enqueue([deferred_tasks] {
for(auto &function : *deferred_tasks) {
function();
}
});
}

View File

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

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,13 +17,26 @@
#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"
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#include <cstdint>
#include <vector>
namespace AmstradCPC {
enum ROMType: int {
OS464 = 0, BASIC464,
OS664, BASIC664,
OS6128, BASIC6128,
AMSDOS
};
/*!
Models the CPC's interrupt timer. Inputs are vsync, hsync, interrupt acknowledge and reset, and its output
is simply yes or no on whether an interupt is currently requested. Internally it uses a counter with a period
@@ -33,8 +46,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 +87,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 +102,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;
};
/*!
@@ -99,14 +116,8 @@ class InterruptTimer {
class AYDeferrer {
public:
/// Constructs a new AY instance and sets its clock rate.
inline void setup_output() {
ay_.reset(new GI::AY38910::AY38910);
ay_->set_input_rate(1000000);
}
/// Destructs the AY.
inline void close_output() {
ay_.reset();
AYDeferrer() : ay_(audio_queue_), speaker_(ay_) {
speaker_.set_input_rate(1000000);
}
/// Adds @c half_cycles half cycles to the amount of time that has passed.
@@ -116,26 +127,28 @@ class AYDeferrer {
/// Enqueues an update-to-now into the AY's deferred queue.
inline void update() {
ay_->run_for(cycles_since_update_.divide_cycles(Cycles(4)));
speaker_.run_for(audio_queue_, cycles_since_update_.divide_cycles(Cycles(4)));
}
/// Issues a request to the AY to perform all processing up to the current time.
inline void flush() {
ay_->flush();
audio_queue_.perform();
}
/// @returns the speaker the AY is using for output.
std::shared_ptr<Outputs::Speaker> get_speaker() {
return ay_;
Outputs::Speaker::Speaker *get_speaker() {
return &speaker_;
}
/// @returns the AY itself.
GI::AY38910::AY38910 *ay() {
return ay_.get();
GI::AY38910::AY38910 &ay() {
return ay_;
}
private:
std::shared_ptr<GI::AY38910::AY38910> ay_;
Concurrency::DeferringAsyncTaskQueue audio_queue_;
GI::AY38910::AY38910 ay_;
Outputs::Speaker::LowpassSpeaker<GI::AY38910::AY38910> speaker_;
HalfCycles cycles_since_update_;
};
@@ -147,26 +160,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 +212,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 +228,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 +263,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 +281,7 @@ class CRTCBusHandler {
case 1: pixel_divider_ = 2; break;
case 2: pixel_divider_ = 1; break;
}
build_mode_table();
}
interrupt_timer_.signal_hsync();
@@ -296,7 +307,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.
@@ -305,8 +316,8 @@ class CRTCBusHandler {
}
/// @returns the CRT.
std::shared_ptr<Outputs::CRT::CRT> get_crt() {
return crt_;
Outputs::CRT::CRT *get_crt() {
return crt_.get();
}
/*!
@@ -317,11 +328,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 +345,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 +495,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_;
std::unique_ptr<Outputs::CRT::CRT> crt_;
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 +577,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 +606,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.
@@ -507,7 +620,7 @@ class i8255PortHandler : public Intel::i8255::PortHandler {
case 0:
// Port A is connected to the AY's data bus.
ay_.update();
ay_.ay()->set_data_input(value);
ay_.ay().set_data_input(value);
break;
case 1:
// Port B is an input only. So output goes nowehere.
@@ -523,7 +636,7 @@ class i8255PortHandler : public Intel::i8255::PortHandler {
tape_player_.set_tape_output((value & 0x20) ? true : false);
// Bits 6 and 7 set BDIR and BC1 for the AY.
ay_.ay()->set_control_lines(
ay_.ay().set_control_lines(
(GI::AY38910::ControlLines)(
((value & 0x80) ? GI::AY38910::BDIR : 0) |
((value & 0x40) ? GI::AY38910::BC1 : 0) |
@@ -536,9 +649,9 @@ class i8255PortHandler : public Intel::i8255::PortHandler {
/// The i8255 will call this to obtain a new input for @c port.
uint8_t get_value(int port) {
switch(port) {
case 0: return ay_.ay()->get_data_output(); // Port A is wired to the AY
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 +665,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 +676,36 @@ 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();
ay_.ay().set_port_handler(&key_state_);
}
/// 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 +717,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 +842,32 @@ 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() {
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() {
Outputs::Speaker::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 +900,86 @@ class ConcreteMachine:
read_pointers_[2] = write_pointers_[2];
read_pointers_[3] = roms_[upper_rom_].data();
// Type whatever is required.
if(target.loading_command.length()) {
type_string(target.loading_command);
}
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_);
}
// 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"
});
for(std::size_t index = 0; index < roms.size(); ++index) {
auto &data = roms[index];
if(!data) return false;
roms_[static_cast<int>(index)] = std::move(*data);
roms_[static_cast<int>(index)].resize(16384);
}
return true;
}
// See header; provides the system ROMs.
void set_rom(ROMType type, std::vector<uint8_t> data) {
roms_[(int)type] = data;
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();
}
#pragma mark - Keyboard
// MARK: - Keyboard
void set_typer_for_string(const char *string) {
void type_string(const std::string &string) override final {
std::unique_ptr<CharacterMapper> mapper(new CharacterMapper());
Utility::TypeRecipient::set_typer_for_string(string, std::move(mapper));
Utility::TypeRecipient::add_typer(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 +1026,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 +1048,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 +1059,7 @@ class ConcreteMachine:
uint8_t *write_pointers_[4];
KeyboardState key_state_;
AmstradCPC::KeyboardMapper keyboard_mapper_;
};
}

View File

@@ -13,39 +13,10 @@
#include "../CRTMachine.hpp"
#include "../KeyboardMachine.hpp"
#include <cstdint>
#include <vector>
namespace AmstradCPC {
enum ROMType: int {
OS464 = 0, BASIC464,
OS664, BASIC664,
OS6128, BASIC6128,
AMSDOS
};
enum Key: uint16_t {
#define Line(l, k1, k2, k3, k4, k5, k6, k7, k8) \
k1 = (l << 4) | 0x07, k2 = (l << 4) | 0x06, k3 = (l << 4) | 0x05, k4 = (l << 4) | 0x04,\
k5 = (l << 4) | 0x03, k6 = (l << 4) | 0x02, k7 = (l << 4) | 0x01, k8 = (l << 4) | 0x00,
Line(0, KeyFDot, KeyEnter, KeyF3, KeyF6, KeyF9, KeyDown, KeyRight, KeyUp)
Line(1, KeyF0, KeyF2, KeyF1, KeyF5, KeyF8, KeyF7, KeyCopy, KeyLeft)
Line(2, KeyControl, KeyBackSlash, KeyShift, KeyF4, KeyRightSquareBracket, KeyReturn, KeyLeftSquareBracket, KeyClear)
Line(3, KeyFullStop, KeyForwardSlash, KeyColon, KeySemicolon, KeyP, KeyAt, KeyMinus, KeyCaret)
Line(4, KeyComma, KeyM, KeyK, KeyL, KeyI, KeyO, Key9, Key0)
Line(5, KeySpace, KeyN, KeyJ, KeyH, KeyY, KeyU, Key7, Key8)
Line(6, KeyV, KeyB, KeyF, KeyG, KeyT, KeyR, Key5, Key6)
Line(7, KeyX, KeyC, KeyD, KeyS, KeyW, KeyE, Key3, Key4)
Line(8, KeyZ, KeyCapsLock, KeyA, KeyTab, KeyQ, KeyEscape, Key2, Key1)
Line(9, KeyDelete, KeyJoy1Fire3, KeyJoy2Fire2, KeyJoy1Fire1, KeyJoy1Right, KeyJoy1Left, KeyJoy1Down, KeyJoy1Up)
#undef Line
};
/*!
Models an Amstrad CPC, a CRT-outputting machine with a keyboard that can accept configuration targets.
Models an Amstrad CPC.
*/
class Machine:
public CRTMachine::Machine,
@@ -54,11 +25,8 @@ 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;
};
}

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,216 @@
//
#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_.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();
}
Outputs::CRT::CRT *get_crt() override {
return bus_->tia_->get_crt();
}
Outputs::Speaker::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_cutoff(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

@@ -11,16 +11,19 @@
#include "Atari2600.hpp"
#include "PIA.hpp"
#include "Speaker.hpp"
#include "TIA.hpp"
#include "TIASound.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
namespace Atari2600 {
class Bus {
public:
Bus() :
tia_sound_(audio_queue_),
speaker_(tia_sound_),
tia_input_value_{0xff, 0xff},
cycles_since_speaker_update_(0) {}
@@ -30,7 +33,10 @@ class Bus {
// the RIOT, TIA and speaker
PIA mos6532_;
std::shared_ptr<TIA> tia_;
std::shared_ptr<Speaker> speaker_;
Concurrency::DeferringAsyncTaskQueue audio_queue_;
TIASound tia_sound_;
Outputs::Speaker::LowpassSpeaker<TIASound> speaker_;
// joystick state
uint8_t tia_input_value_[2];
@@ -39,7 +45,7 @@ class Bus {
// speaker backlog accumlation counter
Cycles cycles_since_speaker_update_;
inline void update_audio() {
speaker_->run_for(cycles_since_speaker_update_.divide(Cycles(CPUTicksPerAudioTick * 3)));
speaker_.run_for(audio_queue_, cycles_since_speaker_update_.divide(Cycles(CPUTicksPerAudioTick * 3)));
}
// video backlog accumulation counter

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
@@ -132,11 +148,11 @@ template<class T> class Cartridge:
case 0x2c: update_video(); tia_->clear_collision_flags(); break;
case 0x15:
case 0x16: update_audio(); speaker_->set_control(decodedAddress - 0x15, *value); break;
case 0x16: update_audio(); tia_sound_.set_control(decodedAddress - 0x15, *value); break;
case 0x17:
case 0x18: update_audio(); speaker_->set_divider(decodedAddress - 0x17, *value); break;
case 0x18: update_audio(); tia_sound_.set_divider(decodedAddress - 0x17, *value); break;
case 0x19:
case 0x1a: update_audio(); speaker_->set_volume(decodedAddress - 0x19, *value); break;
case 0x1a: update_audio(); tia_sound_.set_volume(decodedAddress - 0x19, *value); break;
}
}
}
@@ -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);
}
@@ -164,13 +180,18 @@ template<class T> class Cartridge:
void flush() {
update_audio();
update_video();
speaker_->flush();
audio_queue_.perform();
}
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

@@ -7,7 +7,9 @@
//
#include "TIA.hpp"
#include <cassert>
#include <cstring>
using namespace Atari2600;
namespace {
@@ -20,36 +22,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 +64,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 +155,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 +196,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 +236,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 +298,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 +353,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() {
@@ -388,7 +381,7 @@ void TIA::output_for_cycles(int number_of_cycles) {
if(!output_cursor) {
if(line_end_function_) line_end_function_(collision_buffer_);
memset(collision_buffer_, 0, sizeof(collision_buffer_));
std::memset(collision_buffer_, 0, sizeof(collision_buffer_));
ball_.motion_time %= 228;
player_[0].motion_time %= 228;
@@ -401,22 +394,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 +435,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 +457,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 +483,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 +531,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 +546,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 +659,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

@@ -73,18 +73,18 @@ class TIA {
uint8_t get_collision_flags(int offset);
void clear_collision_flags();
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() { return crt_; }
Outputs::CRT::CRT *get_crt() { return crt_.get(); }
private:
TIA(bool create_crt);
std::shared_ptr<Outputs::CRT::CRT> crt_;
std::unique_ptr<Outputs::CRT::CRT> crt_;
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

@@ -6,31 +6,32 @@
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "Speaker.hpp"
#include "TIASound.hpp"
using namespace Atari2600;
Atari2600::Speaker::Speaker() :
Atari2600::TIASound::TIASound(Concurrency::DeferringAsyncTaskQueue &audio_queue) :
audio_queue_(audio_queue),
poly4_counter_{0x00f, 0x00f},
poly5_counter_{0x01f, 0x01f},
poly9_counter_{0x1ff, 0x1ff}
{}
void Atari2600::Speaker::set_volume(int channel, uint8_t volume) {
enqueue([=]() {
void Atari2600::TIASound::set_volume(int channel, uint8_t volume) {
audio_queue_.defer([=]() {
volume_[channel] = volume & 0xf;
});
}
void Atari2600::Speaker::set_divider(int channel, uint8_t divider) {
enqueue([=]() {
void Atari2600::TIASound::set_divider(int channel, uint8_t divider) {
audio_queue_.defer([=]() {
divider_[channel] = divider & 0x1f;
divider_counter_[channel] = 0;
});
}
void Atari2600::Speaker::set_control(int channel, uint8_t control) {
enqueue([=]() {
void Atari2600::TIASound::set_control(int channel, uint8_t control) {
audio_queue_.defer([=]() {
control_[channel] = control & 0xf;
});
}
@@ -39,7 +40,7 @@ void Atari2600::Speaker::set_control(int channel, uint8_t control) {
#define advance_poly5(c) poly5_counter_[channel] = (poly5_counter_[channel] >> 1) | (((poly5_counter_[channel] << 4) ^ (poly5_counter_[channel] << 2))&0x010)
#define advance_poly9(c) poly9_counter_[channel] = (poly9_counter_[channel] >> 1) | (((poly9_counter_[channel] << 4) ^ (poly9_counter_[channel] << 8))&0x100)
void Atari2600::Speaker::get_samples(unsigned int number_of_samples, int16_t *target) {
void Atari2600::TIASound::get_samples(std::size_t number_of_samples, int16_t *target) {
for(unsigned int c = 0; c < number_of_samples; c++) {
target[c] = 0;
for(int channel = 0; channel < 2; channel++) {

View File

@@ -1,15 +1,16 @@
//
// Speaker.hpp
// TIASound.hpp
// Clock Signal
//
// Created by Thomas Harte on 03/12/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef Atari2600_Speaker_hpp
#define Atari2600_Speaker_hpp
#ifndef Atari2600_TIASound_hpp
#define Atari2600_TIASound_hpp
#include "../../Outputs/Speaker.hpp"
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
#include "../../Concurrency/AsyncTaskQueue.hpp"
namespace Atari2600 {
@@ -17,17 +18,19 @@ namespace Atari2600 {
// will give greater resolution to changes in audio state. 1, 2 and 19 are the only divisors of 38.
const int CPUTicksPerAudioTick = 2;
class Speaker: public ::Outputs::Filter<Speaker> {
class TIASound: public Outputs::Speaker::SampleSource {
public:
Speaker();
TIASound(Concurrency::DeferringAsyncTaskQueue &audio_queue);
void set_volume(int channel, uint8_t volume);
void set_divider(int channel, uint8_t divider);
void set_control(int channel, uint8_t control);
void get_samples(unsigned int number_of_samples, int16_t *target);
void get_samples(std::size_t number_of_samples, int16_t *target);
private:
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
uint8_t volume_[2];
uint8_t divider_[2];
uint8_t control_[2];

View File

@@ -10,8 +10,9 @@
#define CRTMachine_hpp
#include "../Outputs/CRT/CRT.hpp"
#include "../Outputs/Speaker.hpp"
#include "../Outputs/Speaker/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.
@@ -37,10 +36,10 @@ class Machine {
virtual void close_output() = 0;
/// @returns The CRT this machine is drawing to. Should not be @c nullptr.
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() = 0;
virtual Outputs::CRT::CRT *get_crt() = 0;
/// @returns The speaker that receives this machine's output, or @c nullptr if this machine is mute.
virtual std::shared_ptr<Outputs::Speaker> get_speaker() = 0;
virtual Outputs::Speaker::Speaker *get_speaker() = 0;
/// Runs the machine for @c cycles.
virtual void run_for(const Cycles cycles) = 0;
@@ -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,30 +9,15 @@
#ifndef Vic20_hpp
#define Vic20_hpp
#include "../../../Configurable/Configurable.hpp"
#include "../../ConfigurationTarget.hpp"
#include "../../CRTMachine.hpp"
#include "../../Typer.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 "../../KeyboardMachine.hpp"
#include "../../JoystickMachine.hpp"
namespace Commodore {
namespace Vic20 {
enum ROMSlot {
Kernel,
BASIC,
Characters,
Drive
};
enum MemorySize {
Default,
ThreeKB,
@@ -40,186 +25,33 @@ 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 memory size of this Vic-20.
virtual void set_memory_size(MemorySize size) = 0;
void set_memory_size(MemorySize size);
void set_region(Region region);
inline void set_use_fast_tape_hack(bool activate) { use_fast_tape_hack_ = activate; }
// 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();
/// Sets the region of this Vic-20.
virtual void set_region(Region region) = 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,541 @@
#include "Electron.hpp"
#include "CharacterMapper.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../ClockReceiver/ForceInline.hpp"
#include "../../Configurable/StandardOptions.hpp"
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#include "../../Processors/6502/6502.hpp"
#include "../../Storage/Tape/Tape.hpp"
#include "../Utility/Typer.hpp"
#include "Interrupts.hpp"
#include "Keyboard.hpp"
#include "Plus3.hpp"
#include "SoundGenerator.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),
sound_generator_(audio_queue_),
speaker_(sound_generator_) {
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);
speaker_.set_input_rate(2000000 / SoundGenerator::clock_rate_divider);
}
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) {
use_fast_tape_hack_ = activate;
}
void configure_as_target(const StaticAnalyser::Target &target) override final {
if(target.loading_command.length()) {
type_string(target.loading_command);
}
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();
sound_generator_.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();
sound_generator_.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();
audio_queue_.perform();
}
void setup_output(float aspect_ratio) override final {
video_output_.reset(new VideoOutput(ram_));
}
void close_output() override final {
video_output_.reset();
}
Outputs::CRT::CRT *get_crt() override final {
return video_output_->get_crt();
}
Outputs::Speaker::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 type_string(const std::string &string) override final {
std::unique_ptr<CharacterMapper> mapper(new CharacterMapper());
Utility::TypeRecipient::add_typer(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() {
speaker_.run_for(audio_queue_, cycles_since_audio_update_.divide(Cycles(SoundGenerator::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_;
Concurrency::DeferringAsyncTaskQueue audio_queue_;
SoundGenerator sound_generator_;
Outputs::Speaker::LowpassSpeaker<SoundGenerator> 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,35 @@ 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();
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_;
/*!
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;
};
}

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

@@ -0,0 +1,45 @@
//
// Speaker.cpp
// Clock Signal
//
// Created by Thomas Harte on 03/12/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "SoundGenerator.hpp"
#include <cstring>
using namespace Electron;
SoundGenerator::SoundGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue) :
audio_queue_(audio_queue) {}
void SoundGenerator::get_samples(std::size_t number_of_samples, int16_t *target) {
if(is_enabled_) {
while(number_of_samples--) {
*target = static_cast<int16_t>((counter_ / (divider_+1)) * 8192);
target++;
counter_ = (counter_ + 1) % ((divider_+1) * 2);
}
} else {
std::memset(target, 0, sizeof(int16_t) * number_of_samples);
}
}
void SoundGenerator::skip_samples(std::size_t number_of_samples) {
counter_ = (counter_ + number_of_samples) % ((divider_+1) * 2);
}
void SoundGenerator::set_divider(uint8_t divider) {
audio_queue_.defer([=]() {
divider_ = divider * 32 / clock_rate_divider;
});
}
void SoundGenerator::set_is_enabled(bool is_enabled) {
audio_queue_.defer([=]() {
is_enabled_ = is_enabled;
counter_ = 0;
});
}

View File

@@ -0,0 +1,39 @@
//
// SoundGenerator.hpp
// Clock Signal
//
// Created by Thomas Harte on 03/12/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef Electron_SoundGenerator_hpp
#define Electron_SoundGenerator_hpp
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
#include "../../Concurrency/AsyncTaskQueue.hpp"
namespace Electron {
class SoundGenerator: public ::Outputs::Speaker::SampleSource {
public:
SoundGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue);
void set_divider(uint8_t divider);
void set_is_enabled(bool is_enabled);
void get_samples(std::size_t number_of_samples, int16_t *target);
void skip_samples(std::size_t number_of_samples);
static const unsigned int clock_rate_divider = 8;
private:
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
unsigned int counter_ = 0;
unsigned int divider_ = 0;
bool is_enabled_ = false;
};
}
#endif /* Speaker_hpp */

View File

@@ -1,40 +0,0 @@
//
// Speaker.cpp
// Clock Signal
//
// Created by Thomas Harte on 03/12/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#include "Speaker.hpp"
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++;
counter_ = (counter_ + 1) % ((divider_+1) * 2);
}
} else {
memset(target, 0, sizeof(int16_t) * number_of_samples);
}
}
void Speaker::skip_samples(unsigned int number_of_samples) {
counter_ = (counter_ + number_of_samples) % ((divider_+1) * 2);
}
void Speaker::set_divider(uint8_t divider) {
enqueue([=]() {
divider_ = divider * 32 / clock_rate_divider;
});
}
void Speaker::set_is_enabled(bool is_enabled) {
enqueue([=]() {
is_enabled_ = is_enabled;
counter_ = 0;
});
}

View File

@@ -1,35 +0,0 @@
//
// Speaker.hpp
// Clock Signal
//
// Created by Thomas Harte on 03/12/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#ifndef Electron_Speaker_hpp
#define Electron_Speaker_hpp
#include "../../Outputs/Speaker.hpp"
namespace Electron {
class Speaker: public ::Outputs::Filter<Speaker> {
public:
void set_divider(uint8_t divider);
void set_is_enabled(bool is_enabled);
void get_samples(unsigned int number_of_samples, int16_t *target);
void skip_samples(unsigned int number_of_samples);
static const unsigned int clock_rate_divider = 8;
private:
unsigned int counter_;
unsigned int divider_;
bool is_enabled_;
};
}
#endif /* Speaker_hpp */

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_;
Outputs::CRT::CRT *VideoOutput::get_crt() {
return crt_.get();
}
#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,24 +372,43 @@ 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;
int position = output_position_ + from_time;
int position = (output_position_ + from_time) % cycles_per_frame;
// Apply the standard cost of aligning to the available 1Mhz of RAM bandwidth.
result += 1 + (position&1);
// In Modes 03 there is also a complete block on any access while pixels are being fetched.
if(screen_mode_ < 4) {
const int current_column = graphics_column(position + (position&1));
int current_line = graphics_line(position);
if(current_column < 80 && current_line < 256) {
// Mode 3 is a further special case: in 'every ten line block', the final two aren't painted,
// so the CPU is allowed access. But the offset of the ten-line blocks depends on when the
// user switched into Mode 3, so that needs to be calculated relative to current output.
if(screen_mode_ == 3) {
// Get the line the display was on.
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);
int implied_row;
if(current_line >= output_position_line) {
// Get the number of lines since then if still in the same frame.
int lines_since_output_position = current_line - output_position_line;
// Therefore get the character row at the proposed time, modulo 10.
implied_row = (current_character_row_ + lines_since_output_position) % 10;
} else {
// If the frame has rolled over, the implied row is just related to the current line.
implied_row = current_line % 10;
}
// Mode 3 ends after 250 lines, not the usual 256.
if(implied_row < 8 && current_line < 250) 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 +426,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

@@ -31,7 +31,7 @@ class VideoOutput {
VideoOutput(uint8_t *memory);
/// @returns the CRT to which output is being painted.
std::shared_ptr<Outputs::CRT::CRT> get_crt();
Outputs::CRT::CRT *get_crt();
/// Produces the next @c cycles of video output.
void run_for(const Cycles cycles);
@@ -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,16 +98,20 @@ 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_;
std::unique_ptr<Outputs::CRT::CRT> crt_;
struct DrawAction {
enum Type {
@@ -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,32 @@
//
// 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_;
}
void Machine::type_string(const std::string &) {
}

View File

@@ -9,20 +9,69 @@
#ifndef KeyboardMachine_h
#define KeyboardMachine_h
#include <cstdint>
#include <string>
#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;
/*!
Causes the machine to attempt to type the supplied string.
This is best effort. Success or failure is permitted to be a function of machine and current state.
*/
virtual void type_string(const std::string &);
/*!
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

@@ -0,0 +1,42 @@
//
// ASCII16kb.hpp
// Clock Signal
//
// Created by Thomas Harte on 04/01/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef ASCII16kb_hpp
#define ASCII16kb_hpp
#include "../ROMSlotHandler.hpp"
namespace MSX {
namespace Cartridge {
class ASCII16kbROMSlotHandler: public ROMSlotHandler {
public:
ASCII16kbROMSlotHandler(MSX::MemoryMap &map, int slot) :
map_(map), slot_(slot) {}
void write(uint16_t address, uint8_t value) {
switch(address >> 11) {
default: break;
case 0xc:
map_.map(slot_, value * 8192, 0x4000, 0x4000);
break;
case 0xe:
map_.map(slot_, value * 8192, 0x8000, 0x4000);
break;
}
}
private:
MSX::MemoryMap &map_;
int slot_;
};
}
}
#endif /* ASCII16kb_hpp */

View File

@@ -0,0 +1,48 @@
//
// ASCII8kb.hpp
// Clock Signal
//
// Created by Thomas Harte on 04/01/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef ASCII8kb_hpp
#define ASCII8kb_hpp
#include "../ROMSlotHandler.hpp"
namespace MSX {
namespace Cartridge {
class ASCII8kbROMSlotHandler: public ROMSlotHandler {
public:
ASCII8kbROMSlotHandler(MSX::MemoryMap &map, int slot) :
map_(map), slot_(slot) {}
void write(uint16_t address, uint8_t value) {
switch(address >> 11) {
default: break;
case 0xc:
map_.map(slot_, value * 8192, 0x4000, 0x2000);
break;
case 0xd:
map_.map(slot_, value * 8192, 0x6000, 0x2000);
break;
case 0xe:
map_.map(slot_, value * 8192, 0x8000, 0x2000);
break;
case 0xf:
map_.map(slot_, value * 8192, 0xa000, 0x2000);
break;
}
}
private:
MSX::MemoryMap &map_;
int slot_;
};
}
}
#endif /* ASCII8kb_hpp */

View File

@@ -0,0 +1,45 @@
//
// Konami.hpp
// Clock Signal
//
// Created by Thomas Harte on 04/01/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef Konami_hpp
#define Konami_hpp
#include "../ROMSlotHandler.hpp"
namespace MSX {
namespace Cartridge {
class KonamiROMSlotHandler: public ROMSlotHandler {
public:
KonamiROMSlotHandler(MSX::MemoryMap &map, int slot) :
map_(map), slot_(slot) {}
void write(uint16_t address, uint8_t value) {
switch(address >> 13) {
default: break;
case 3:
map_.map(slot_, value * 8192, 0x6000, 0x2000);
break;
case 4:
map_.map(slot_, value * 8192, 0x8000, 0x2000);
break;
case 5:
map_.map(slot_, value * 8192, 0xa000, 0x2000);
break;
}
}
private:
MSX::MemoryMap &map_;
int slot_;
};
}
}
#endif /* Konami_hpp */

View File

@@ -0,0 +1,67 @@
//
// KonamiWithSCC.hpp
// Clock Signal
//
// Created by Thomas Harte on 04/01/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef KonamiWithSCC_hpp
#define KonamiWithSCC_hpp
#include "../ROMSlotHandler.hpp"
#include "../../../Components/KonamiSCC/KonamiSCC.hpp"
namespace MSX {
namespace Cartridge {
class KonamiWithSCCROMSlotHandler: public ROMSlotHandler {
public:
KonamiWithSCCROMSlotHandler(MSX::MemoryMap &map, int slot, Konami::SCC &scc) :
map_(map), slot_(slot), scc_(scc) {}
void write(uint16_t address, uint8_t value) override {
switch(address >> 11) {
default: break;
case 0x0a:
map_.map(slot_, value * 8192, 0x4000, 0x2000);
break;
case 0x0e:
map_.map(slot_, value * 8192, 0x6000, 0x2000);
break;
case 0x12:
if((value&0x3f) == 0x3f) {
scc_is_visible_ = true;
map_.unmap(slot_, 0x8000, 0x2000);
} else {
scc_is_visible_ = false;
map_.map(slot_, value * 8192, 0x8000, 0x2000);
}
break;
case 0x13:
if(scc_is_visible_) scc_.write(address, value);
break;
case 0x16:
map_.map(slot_, value * 8192, 0xa000, 0x2000);
break;
}
}
uint8_t read(uint16_t address) override {
if(scc_is_visible_ && address >= 0x9800 && address < 0xa000) {
return scc_.read(address);
}
return 0xff;
}
private:
MSX::MemoryMap &map_;
int slot_;
Konami::SCC &scc_;
bool scc_is_visible_ = false;
};
}
}
#endif /* KonamiWithSCC_hpp */

71
Machines/MSX/DiskROM.cpp Normal file
View File

@@ -0,0 +1,71 @@
//
// DiskROM.cpp
// Clock Signal
//
// Created by Thomas Harte on 07/01/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#include "DiskROM.hpp"
using namespace MSX;
DiskROM::DiskROM(const std::vector<uint8_t> &rom) :
WD1770(P1793),
rom_(rom) {
set_is_double_density(true);
}
void DiskROM::write(uint16_t address, uint8_t value) {
switch(address) {
case 0x7ff8: case 0x7ff9: case 0x7ffa: case 0x7ffb:
set_register(address, value);
break;
case 0x7ffc:
selected_head_ = value & 1;
if(drives_[0]) drives_[0]->set_head(selected_head_);
if(drives_[1]) drives_[1]->set_head(selected_head_);
break;
case 0x7ffd: {
selected_drive_ = value & 1;
set_drive(drives_[selected_drive_]);
bool drive_motor = !!(value & 0x80);
if(drives_[0]) drives_[0]->set_motor_on(drive_motor);
if(drives_[1]) drives_[1]->set_motor_on(drive_motor);
} break;
}
}
uint8_t DiskROM::read(uint16_t address) {
if(address >= 0x7ff8 && address < 0x7ffc) {
return get_register(address);
}
if(address == 0x7fff) {
return (get_data_request_line() ? 0x00 : 0x80) | (get_interrupt_request_line() ? 0x00 : 0x40);
}
return rom_[address & 0x3fff];
}
void DiskROM::run_for(HalfCycles half_cycles) {
// Input clock is going to be 7159090/2 Mhz, but the drive controller
// needs an 8Mhz clock, so scale up. 8000000/7159090 simplifies to
// 800000/715909.
controller_cycles_ += 800000 * half_cycles.as_int();
WD::WD1770::run_for(Cycles(static_cast<int>(controller_cycles_ / 715909)));
controller_cycles_ %= 715909;
}
void DiskROM::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive) {
if(!drives_[drive]) {
drives_[drive].reset(new Storage::Disk::Drive(8000000, 300, 2));
drives_[drive]->set_head(selected_head_);
if(drive == selected_drive_) set_drive(drives_[drive]);
}
drives_[drive]->set_disk(disk);
}
void DiskROM::set_head_load_request(bool head_load) {
// Magic!
set_head_loaded(head_load);
}

44
Machines/MSX/DiskROM.hpp Normal file
View File

@@ -0,0 +1,44 @@
//
// DiskROM.hpp
// Clock Signal
//
// Created by Thomas Harte on 07/01/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef DiskROM_hpp
#define DiskROM_hpp
#include "ROMSlotHandler.hpp"
#include "../../Components/1770/1770.hpp"
#include <cstdint>
#include <vector>
namespace MSX {
class DiskROM: public ROMSlotHandler, public WD::WD1770 {
public:
DiskROM(const std::vector<uint8_t> &rom);
void write(uint16_t address, uint8_t value) override;
uint8_t read(uint16_t address) override;
void run_for(HalfCycles half_cycles) override;
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, int drive);
private:
const std::vector<uint8_t> &rom_;
long int controller_cycles_ = 0;
int selected_drive_ = 0;
int selected_head_ = 0;
std::shared_ptr<Storage::Disk::Drive> drives_[4];
void set_head_load_request(bool head_load) override;
};
}
#endif /* DiskROM_hpp */

61
Machines/MSX/Keyboard.cpp Normal file
View File

@@ -0,0 +1,61 @@
//
// Keyboard.cpp
// Clock Signal
//
// Created by Thomas Harte on 29/11/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "Keyboard.hpp"
uint16_t MSX::KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
#define BIND(source, dest) case Inputs::Keyboard::Key::source: return MSX::Key::dest
switch(key) {
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(F1, KeyF1); BIND(F2, KeyF2); BIND(F3, KeyF3); BIND(F4, KeyF4); BIND(F5, KeyF5);
BIND(F12, KeyStop);
BIND(F10, KeyDelete); BIND(F9, KeyInsert); BIND(F8, KeyHome);
BIND(Delete, KeyDelete); BIND(Insert, KeyInsert); BIND(Home, KeyHome);
BIND(Escape, KeyEscape);
BIND(Tab, KeyTab); BIND(CapsLock, KeyCaps);
BIND(LeftControl, KeyControl); BIND(RightControl, KeyControl);
BIND(LeftShift, KeyShift); BIND(RightShift, KeyShift);
BIND(LeftMeta, KeyCode); BIND(RightMeta, KeyGraph);
BIND(LeftOption, KeyCode); BIND(RightOption, KeySelect);
BIND(Semicolon, KeySemicolon);
BIND(Quote, KeyQuote);
BIND(OpenSquareBracket, KeyLeftSquareBracket);
BIND(CloseSquareBracket, KeyRightSquareBracket);
BIND(Hyphen, KeyMinus);
BIND(Equals, KeyEquals);
BIND(Left, KeyLeft);
BIND(Right, KeyRight);
BIND(Up, KeyUp);
BIND(Down, KeyDown);
BIND(FullStop, KeyFullStop);
BIND(Comma, KeyComma);
BIND(ForwardSlash, KeyForwardSlash);
BIND(BackSlash, KeyBackSlash);
BIND(BackTick, KeyGrave);
BIND(Enter, KeyEnter);
BIND(Space, KeySpace);
BIND(BackSpace, KeyBackspace);
default: break;
}
#undef BIND
return KeyboardMachine::Machine::KeyNotMapped;
}

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