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

Compare commits

...

1261 Commits

Author SHA1 Message Date
Thomas Harte
0c8e313fd5 Merge pull request #635 from TomHarte/Cleanup
Improves code quality, particularly the NSDocument subclass.
2019-08-01 14:32:49 -04:00
Thomas Harte
f64ec11668 Tidies up and simplifies panel flow. 2019-08-01 14:31:45 -04:00
Thomas Harte
9bbccd89d3 Adds an extended rationale for current implementation.
Also strips some cruft of prior guesses.
2019-07-31 23:19:46 -04:00
Thomas Harte
1ae3799aee Merge pull request #634 from TomHarte/OpenCrash
Resolves a potential crash at launch on the Mac
2019-07-31 14:11:58 -04:00
Thomas Harte
260843e5b1 Starts poking at a pure total-based formula.
On the assumption that any relevant DC offset will fall out in the wash. This causes one 400kb disk to boot in a Macintosh 128kb, another couple to do reasonably well. So it's better than what was here before.
2019-07-31 12:42:23 -04:00
Thomas Harte
e3f22e5787 This should actually be PWM, I think.
I'm just unsure of exactly the proper formula, owing to my ignorance on basic electrical engineering. Research ahoy!
2019-07-30 22:02:41 -04:00
Thomas Harte
2aa308efdd Tweaks magic formulas.
The computer now at least seeks outward, until this attempt at drive speed calculation fails.
2019-07-30 16:18:36 -04:00
Thomas Harte
74c18d7861 Attempts a full wiring up of 400kb drive speed. 2019-07-30 15:08:55 -04:00
Thomas Harte
c41cccd9a6 Adds a workaround to display the ROM import banner even from File -> Open... . 2019-07-30 13:07:33 -04:00
Thomas Harte
bba34b28b8 Merge pull request #633 from TomHarte/Deferrer
Starts to formalise just-in-time handling
2019-07-30 11:35:13 -04:00
Thomas Harte
d8a41575c8 Picks a better function name. 2019-07-30 10:54:56 -04:00
Thomas Harte
0521de668a Omits redundant qualifier.
This code can't be anything but `inline`.
2019-07-29 23:07:02 -04:00
Thomas Harte
12441bddab Tries operator overloading as a workaround. 2019-07-29 23:04:02 -04:00
Thomas Harte
bc25c52683 Although duplicative, resolves function redefinition. 2019-07-29 22:49:02 -04:00
Thomas Harte
eb3fb70ea1 Merge branch 'Deferrer' of github.com:TomHarte/CLK into Deferrer 2019-07-29 21:24:21 -04:00
Thomas Harte
2f90ed1f32 Attempts to reformulate into valid C++. 2019-07-29 21:23:37 -04:00
Thomas Harte
f3dd4b028d Rolls out JustInTime acting to the MSX and ColecoVision. 2019-07-29 21:22:31 -04:00
Thomas Harte
7dcad516bd Undoes incorrect project change. 2019-07-29 17:21:34 -04:00
Thomas Harte
9859f99513 Adds a route to not bumping time. 2019-07-29 17:21:27 -04:00
Thomas Harte
51b7f2777d Adds a route to not bumping time. 2019-07-29 17:17:39 -04:00
Thomas Harte
2f2478d2d3 Implements AsyncJustInTimeActor, experimentally. 2019-07-29 16:38:57 -04:00
Thomas Harte
a43ada82b2 Experiments with a JustInTimeActor in the Master System. 2019-07-29 15:38:41 -04:00
Thomas Harte
5149f290d0 Starts trying to formalise just-in-time execution.
Which, at least, simplifies Cycle/HalfCycle to Cycle run_for usage via template.
2019-07-28 21:49:54 -04:00
Thomas Harte
0dc6f08deb Merge pull request #632 from TomHarte/BINSwitch
Eliminates the crutch of PlusToo BIN files.
2019-07-28 16:08:54 -04:00
Thomas Harte
b1f04ed96d Eliminates the crutch of PlusToo BIN files.
Thereby returning the .bin extension to the various consoles.
2019-07-28 16:07:16 -04:00
Thomas Harte
cd49b3c89b Merge pull request #631 from TomHarte/MacPageFlipping
Removes the video and audio base address latches.
2019-07-27 22:25:12 -04:00
Thomas Harte
f894d43111 Removes the video and audio base address latches.
It now seems to me that these take effect immediately.
2019-07-27 22:23:40 -04:00
Thomas Harte
4033c0c754 Merge pull request #630 from TomHarte/IWMWrites
Makes various fixes to `Drive` and `Track`.
2019-07-26 23:41:20 -04:00
Thomas Harte
786b26d23e Adds an admission of incompleteness.
I really need to figure out the 5-and-3 encoding.
2019-07-26 23:23:40 -04:00
Thomas Harte
d08d8ed22c Adds a further drift safeguard. 2019-07-26 23:23:01 -04:00
Thomas Harte
b7b62aa3f6 Resolves some type conversion warnings. 2019-07-26 23:20:40 -04:00
Thomas Harte
39d7e3c62c Ensures relative_exponents less than or equal to -32 don't overflow. 2019-07-26 22:19:40 -04:00
Thomas Harte
81b57ecf7c Adds noexcept. 2019-07-26 22:18:40 -04:00
Thomas Harte
572e8b52e1 At the cost of extra storage, attempts to simplify away potential rounding issues around the index hole. 2019-07-26 17:20:32 -04:00
Thomas Harte
9b634472c6 Gets a little more rigorous on "high resolution". 2019-07-26 15:26:51 -04:00
Thomas Harte
d8bc20b1ab Ensures quieter Release behaviour. 2019-07-25 22:55:27 -04:00
Thomas Harte
d2bfd59953 Ensures positional continuity across rotation speed changes. 2019-07-25 22:29:54 -04:00
Thomas Harte
3d20ae47ea Ensures cycles_since_index_hole_ is wrapped to track length. 2019-07-25 21:48:01 -04:00
Thomas Harte
85cf8d89bc Ensures an initial non-zero value. 2019-07-25 21:47:44 -04:00
Thomas Harte
50e954223a Merge pull request #629 from TomHarte/MinorSpeedImprovements
Attempts to improve 68000 (and Macintosh) emulation speed
2019-07-25 10:39:41 -04:00
Thomas Harte
109d5d16bd Withdraws optimisation, for further testing in the future. 2019-07-25 10:33:38 -04:00
Thomas Harte
1672dc5946 Reduces frequency of update_video() calls. 2019-07-25 10:14:52 -04:00
Thomas Harte
5769944918 Shrinks MicroOp struct size from 16 bytes to 4. 2019-07-25 10:14:36 -04:00
Thomas Harte
9ef1211d53 Adds missing header file. 2019-07-24 22:13:32 -04:00
Thomas Harte
f2ae04597f Updates test case. 2019-07-24 22:07:17 -04:00
Thomas Harte
1327de1c82 Slims the Program struct down to 8 bytes total. 2019-07-24 22:02:50 -04:00
Thomas Harte
827c4e172a Cuts a third from the Program struct.
Observation: [source/destination]_address are always one of the address registers. So you can fit both within a single byte.

Net effect: around a 12% reduction in execution costs, given that this reduces the size of the instructions table from 3mb to 2mb.
2019-07-24 18:39:36 -04:00
Thomas Harte
c300bd17fa Regularises as many source/destination sets as fit the current setter. 2019-07-24 18:22:44 -04:00
Thomas Harte
0187fd8eae Hides all runtime Program member accesses behind macros.
... and fixes unit tests.
2019-07-24 12:01:30 -04:00
Thomas Harte
0469f0240b Moves interrupt level selection outside the loop. 2019-07-23 23:13:03 -04:00
Thomas Harte
4aca6c5ef8 Adds a note of admission here. 2019-07-23 23:03:15 -04:00
Thomas Harte
d69aee4972 Removes stray \n. 2019-07-23 22:17:46 -04:00
Thomas Harte
3da47318b1 Updates copyright year. 2019-07-23 18:03:37 -04:00
Thomas Harte
ef036df2bc Merge pull request #628 from TomHarte/XCodeUpdate
Completes Xcode 10.3 upgrade checks.
2019-07-23 16:27:46 -04:00
Thomas Harte
579f68cf11 Completes Xcode 10.3 upgrade checks. 2019-07-23 16:27:18 -04:00
Thomas Harte
90f6ca4635 Merge pull request #627 from TomHarte/ExtraROMDetails
Extends system ROM details; provides for manual import on the Mac
2019-07-23 16:24:55 -04:00
Thomas Harte
374cac0107 Adds negative feedback to ROM installation process.
As an ugly kludge, code wise.
2019-07-23 16:24:23 -04:00
Thomas Harte
4d361b1952 Adds MIME type for Apple-recognised disk images. 2019-07-23 11:36:47 -04:00
Thomas Harte
fcee7779b0 Inserts missing spaces. 2019-07-22 23:11:37 -04:00
Thomas Harte
b4191b6225 Corrects DiskII boot ROM CRCs and improves corresponding declarations. 2019-07-22 23:07:23 -04:00
Thomas Harte
dbee37ab34 Provides extended ROM details for the VIC-20 and Oric. 2019-07-22 22:15:44 -04:00
Thomas Harte
a3ad0ab09b Completes the successful import path. 2019-07-22 21:46:28 -04:00
Thomas Harte
ed0c4c117b Ensures that machine name reaches Swift. 2019-07-22 21:18:30 -04:00
Thomas Harte
2432151bf8 Puts machine name into ROMMachine::ROM.
Also switches to idiomatic exit codes.
2019-07-22 21:14:21 -04:00
Thomas Harte
2129bfc570 Gets as far as testing ROMs against the missing list.
Though now it strikes me that I've forgotten to retain the machine name.
2019-07-22 18:02:48 -04:00
Thomas Harte
8de6cd3f44 Ensures that ROM files can be dragged and dropped into Swift.
Also adjusts the main window background colour, better to bridge the time between selecting a machine and it starting.
2019-07-22 17:18:31 -04:00
Thomas Harte
9b9831f28b The Mac port will now at least display a list of missing ROMs.
It doesn't yet offer the drag-and-drop functionality it promises, however.
2019-07-22 13:00:17 -04:00
Thomas Harte
8a2cac0d0c Fixes layout constraints. 2019-07-22 11:30:26 -04:00
Thomas Harte
e17b105574 Adds a quick label in exposition. 2019-07-22 11:18:39 -04:00
Thomas Harte
67c5f6b7cb Ensures the missing ROM list bubbles up to Swift. 2019-07-21 22:05:22 -04:00
Thomas Harte
d452d070a1 Extends the Mac ROM fetcher to return a missing-ROMs list. 2019-07-21 18:41:00 -04:00
Thomas Harte
a846c3245d Checks the application support directory before the application bundle for ROM images. 2019-07-20 23:04:46 -04:00
Thomas Harte
4ffa3c1b49 Provides an output for some of the extended ROM information. 2019-07-20 22:52:57 -04:00
Thomas Harte
b2a6682798 Adds extended ROM information for the ZX80 and '81. 2019-07-20 22:46:49 -04:00
Thomas Harte
f3aac603f8 Adds extended Introduces extended ROM details for the C1540, Electron, Master System and MSX. 2019-07-20 21:30:37 -04:00
Thomas Harte
712cb473f7 Adds extended ROM information for the CPC and ColecoVision. 2019-07-20 17:07:59 -04:00
Thomas Harte
3c68a5ca65 Enhances the amount of ROM information posted by the Apple machines. 2019-07-20 16:08:40 -04:00
Thomas Harte
20670bab2f Expands information included in ROM load requests. 2019-07-19 22:35:22 -04:00
Thomas Harte
86d709ae01 Merge pull request #626 from TomHarte/MacScreenshot
Adds a Macintosh screenshot
2019-07-18 20:43:26 -04:00
Thomas Harte
0aba95cc9d Recondenses image placement. 2019-07-18 20:40:25 -04:00
Thomas Harte
de3c8373fd Adds a Macintosh screenshot to the rogue's gallery. 2019-07-18 20:37:14 -04:00
Thomas Harte
75ecd4e72d Adds mention of the Macintosh. 2019-07-17 16:31:24 -04:00
Thomas Harte
56555a4d99 Merge pull request #621 from TomHarte/Mac128k
Adds preliminary emulation of the 512ke Macintosh.
2019-07-17 16:29:58 -04:00
Thomas Harte
cfad20bb33 Surfaces missing Macintosh types. 2019-07-17 16:02:25 -04:00
Thomas Harte
fa226bb1b9 Seeks to reduce enquiry costs. 2019-07-17 15:09:26 -04:00
Thomas Harte
77333ff9f7 It appears that file checksums are not reliable. 2019-07-17 14:56:50 -04:00
Thomas Harte
b9a34bee51 Substitutes a more efficient inner loop for audio generation. 2019-07-17 14:54:06 -04:00
Thomas Harte
22ee51c12c Corrects clocking issues around audio, and cuts down queue costs. 2019-07-17 14:41:36 -04:00
Thomas Harte
ee8d853fcb Ensures you can't get a phase 2 for free with run_for(0). 2019-07-17 14:20:27 -04:00
Thomas Harte
19198ea665 Improves const usage. 2019-07-16 22:13:47 -04:00
Thomas Harte
bcbda4d855 Adds .image as a synonym of .img. 2019-07-16 21:44:59 -04:00
Thomas Harte
79a624e696 Applies more rigorous logic to deciding when to stop parsing. 2019-07-16 18:06:54 -04:00
Thomas Harte
c123ca1054 Slightly improves syntax. 2019-07-16 18:05:58 -04:00
Thomas Harte
9f0f35033d Introduces sector interleaving. 2019-07-16 18:05:40 -04:00
Thomas Harte
3633285aaa Ensures a trailing zero bit isn't dropped. 2019-07-16 16:36:00 -04:00
Thomas Harte
cb16790330 Improves qualifiers. 2019-07-15 22:40:45 -04:00
Thomas Harte
67055d8b56 Reduces CheckingWriteProtect costs, negligibly. 2019-07-15 22:39:55 -04:00
Thomas Harte
ca37fd8f4c Corrects tag preservation. 2019-07-15 17:15:06 -04:00
Thomas Harte
46b98dab70 Bumps up the amount of reserved storage.
To avoid a reallocation when reading Mac data.
2019-07-15 17:12:31 -04:00
Thomas Harte
0568996264 Fixes a couple of data arrangement issues on output. 2019-07-15 17:11:58 -04:00
Thomas Harte
7baad61746 Attempts a full implementation of asynchronous write mode. 2019-07-15 17:11:12 -04:00
Thomas Harte
1d1e0d74f8 Corrects and introduces new parts. 2019-07-12 21:37:33 -04:00
Thomas Harte
d53d1c616f Continues trying to get to write support. 2019-07-12 21:20:05 -04:00
Thomas Harte
5b05a9bc61 Extends Drive to report is_writing and so that writing works as the first action. 2019-07-12 18:53:41 -04:00
Thomas Harte
2c39229b13 Adds has-new-disk flag, allowing mounting of software from the desktop. 2019-07-12 13:17:24 -04:00
Thomas Harte
59b5dfddec Added logic to allow a second disk to be inserted, at least. 2019-07-11 23:03:02 -04:00
Thomas Harte
b730ac5d5a Reintroduces 1-second disable implementation. 2019-07-11 23:02:47 -04:00
Thomas Harte
4860d8a7df Adds ask as a synonym of img. 2019-07-11 22:56:29 -04:00
Thomas Harte
9f0cde3d69 Improves mouse capture behaviour. 2019-07-11 22:56:08 -04:00
Thomas Harte
c8917e677b Edging towards implementing IWM write support, but mainly tidied up. 2019-07-11 21:42:34 -04:00
Thomas Harte
6c2cc206a6 Takes a first stab at write support for Macintosh disk images.
Albeit that the Macintosh itself can't yet write.
2019-07-11 18:09:37 -04:00
Thomas Harte
5a9f3cfc1e Completes Mac GCR decoding and its associated test. 2019-07-11 17:37:07 -04:00
Thomas Harte
8f28b33342 Starts work on Macintosh GCR decoding. 2019-07-11 16:28:52 -04:00
Thomas Harte
cac97a9663 Devolves drive responsibility. 2019-07-10 22:39:56 -04:00
Thomas Harte
2ccb564a7b Throws some extra logging into place, to test the IWM changeover. 2019-07-10 21:39:45 -04:00
Thomas Harte
d1d0430fce Eliminates the SonyDrive class. 2019-07-10 17:38:05 -04:00
Thomas Harte
be251d6b03 Begins substituting the DoubleDensityDrive for the Sony. 2019-07-10 16:24:48 -04:00
Thomas Harte
6cfaf920ee Added attribution and commentary on rotation speeds. 2019-07-10 16:22:06 -04:00
Thomas Harte
1657f8768c Transfers and slightly extends drive logic into the drive. 2019-07-10 16:17:51 -04:00
Thomas Harte
c4ab0bb867 Starts sketching out an interface for IWM drives, eliminating a dangling use of unsigned as it goes. 2019-07-10 16:05:59 -04:00
Thomas Harte
886946cc8c Rejigs time-until-event tracking. 2019-07-09 23:27:27 -04:00
Thomas Harte
ed4ddcfda8 Reduces call/return overhead on Microcycle methods. 2019-07-09 19:55:30 -04:00
Thomas Harte
7886cd63bd Flattens the Macintosh's perform_bus_operation, for legibility. 2019-07-09 19:49:06 -04:00
Thomas Harte
69b94719a1 Switches to faster bit count logic. 2019-07-09 18:41:20 -04:00
Thomas Harte
b4a3f66773 Restores just-in-time processing of video. 2019-07-09 18:08:07 -04:00
Thomas Harte
ab14433151 Tweaks optimisation level. 2019-07-09 18:07:43 -04:00
Thomas Harte
5078f6fb5c Marginally reduces MOVE heft. 2019-07-09 18:07:11 -04:00
Thomas Harte
fc6d62aefb Removes non-functioning workaround. 2019-07-09 16:41:15 -04:00
Thomas Harte
f73bccfec8 Adds a potential workaround for SDL mouse motion. 2019-07-09 16:38:16 -04:00
Thomas Harte
96be1a3f62 Corrects SDL mouse button up/down capture. 2019-07-09 16:32:38 -04:00
Thomas Harte
52e96e3d2a Documents and extends the Video interface.
With the intention of returning it soon to JIT execution.
2019-07-08 22:28:05 -04:00
Thomas Harte
33e2721eb2 Fully embraces forceinline. 2019-07-08 21:11:31 -04:00
Thomas Harte
4bc44666e5 Adds status notes. 2019-07-08 21:11:12 -04:00
Thomas Harte
3d8e4f96c8 Merge branch 'Mac128k' of github.com:TomHarte/CLK into Mac128k 2019-07-08 18:34:45 -04:00
Thomas Harte
94457d81b6 Eliminates redundant and integer-size-troubling AND on ASL. 2019-07-08 18:33:50 -04:00
Thomas Harte
c212bf27db Eliminates redundant and integer-size-troubling AND on ASL. 2019-07-08 18:28:36 -04:00
Thomas Harte
59b5ee65d4 Adds the Zilog SCC to SConstruct. 2019-07-08 18:18:49 -04:00
Thomas Harte
60cedca97b Adds cmath in support of ceilf. 2019-07-08 18:14:03 -04:00
Thomas Harte
1a9aa60bf7 Ensures no compiler will think this can exit without returning. 2019-07-08 18:13:23 -04:00
Thomas Harte
6438a5ca1f Updated SConstruct as per new Apple grouping. 2019-07-08 18:10:39 -04:00
Thomas Harte
3f303511bd Adds cstring include, in support of memcpy. 2019-07-08 18:06:58 -04:00
Thomas Harte
fb352a8d40 Ensures assert is completely excluded if NDEBUG. 2019-07-08 18:00:37 -04:00
Thomas Harte
ea7899f47d Updates the SConstruct in obvious ways. 2019-07-08 17:38:43 -04:00
Thomas Harte
fb6da1de4a Reduces logging temporarily. 2019-07-08 17:37:15 -04:00
Thomas Harte
2651b15db1 Takes a first stab at mouse input support from SDL.
There seems to be something odd going on with mouse buttons though; I'm going to test elsewhere.
2019-07-08 17:36:55 -04:00
Thomas Harte
6e7a733c3c Adds appropriate files to the Mac kiosk build. 2019-07-08 16:57:13 -04:00
Thomas Harte
245e27c893 Solidifies belief that the shift register bit is cleared on read/write. 2019-07-08 16:45:15 -04:00
Thomas Harte
793c2df7ee Fixes keypad keys. 2019-07-08 16:38:06 -04:00
Thomas Harte
28de629c08 Fixes the 6522 sufficiently to fix keyboard input. 2019-07-08 15:29:34 -04:00
Thomas Harte
210bcaa56d Introduces an initial shift unit test, and makes it pass. 2019-07-07 22:13:36 -04:00
Thomas Harte
d7329c1bdd Experiments with a timeout on keyboard interactions. 2019-07-07 14:13:55 -04:00
Thomas Harte
a5f0761a43 Copies in notes for required test functions. 2019-07-07 14:13:00 -04:00
Thomas Harte
dd963d6161 Eliminates call/return cost on WrappedInts. 2019-07-07 14:12:20 -04:00
Thomas Harte
96c0253ee2 Fixes mouse input when a button is pressed; attempts keyboard input.
I think the VIA is somehow sending spurious commands.
2019-07-02 21:14:33 -04:00
Thomas Harte
191a7a9386 Reintroduces an empty second drive.
This prevents the uninitialised disk error. Which is a clue.
2019-07-02 16:59:00 -04:00
Thomas Harte
387be4a0a6 Ensures mouse button presses propagate correctly.
Beyond the one that initiates mouse capture, that is.
2019-07-02 16:57:51 -04:00
Thomas Harte
b9c2c42bc0 Switches drives to using floats for time counting.
Hopefully to eliminate a lot of unnecessary `Time` work; inaccuracies should still be within tolerable range.
2019-07-02 15:43:03 -04:00
Thomas Harte
fffe6ed2df Chops the Macintosh down to a single drive to aid in development. 2019-07-02 13:59:30 -04:00
Thomas Harte
c4cbe9476c Corrects EA selection logic, fixing MOVEP. 2019-07-02 13:54:21 -04:00
Thomas Harte
0a67cc3dab Goes nuclear on ROXL and ROXR. 2019-07-01 23:05:48 -04:00
Thomas Harte
726e07ed5b Corrects ASL overflow flag. 2019-07-01 19:46:58 -04:00
Thomas Harte
ebb6313eef Corrects missing file. 2019-07-01 18:18:46 -04:00
Thomas Harte
11d8f765b2 Corrects divide-by-zero exception length, enables all other DIVS checks. 2019-07-01 15:46:04 -04:00
Thomas Harte
514e57b3e9 Corrects DIVU timing and flags, improves DIVS. 2019-07-01 14:24:32 -04:00
Thomas Harte
d8fb6fb951 Corrects MULU timing. 2019-06-30 22:40:10 -04:00
Thomas Harte
255f0d4b2a Corrects MULS timing. 2019-06-30 22:33:54 -04:00
Thomas Harte
d30e7504c2 Factors out MOVE tests, and ensures test machine RAM is zero initialised. 2019-06-30 21:43:30 -04:00
Thomas Harte
8d0cd356fd Corrects TRAP, TRAPV and CHK timing. 2019-06-29 21:25:22 -04:00
Thomas Harte
aff40bf00a Imports AND tests. 2019-06-29 20:16:10 -04:00
Thomas Harte
eedf7358b4 Imports first part of AND tests. 2019-06-29 16:29:47 -04:00
Thomas Harte
26aebcc167 Imports ROXL and ROXR tests.
Confirming the significant deficiencies I suspected.
2019-06-29 15:26:09 -04:00
Thomas Harte
9d420c727e Factors out rolls and shifts. 2019-06-29 14:12:52 -04:00
Thomas Harte
60fe84ad16 Imports Bcc tests. 2019-06-29 14:07:21 -04:00
Thomas Harte
6a44c682ad Factors out control flow tests. 2019-06-29 13:47:05 -04:00
Thomas Harte
60df44f0ca Imports CMPI tests. 2019-06-29 13:40:02 -04:00
Thomas Harte
ac926f5070 Factors BCD out of general arithmetic. 2019-06-29 13:31:24 -04:00
Thomas Harte
6e9a4a48f7 Imports TAS tests. 2019-06-28 22:56:35 -04:00
Thomas Harte
a8894b308a Splits out arithmetic tests, as so far implemented.
Further subdivision may be advisable.
2019-06-28 22:08:32 -04:00
Thomas Harte
7cc91e1bc5 Factors the bitwise tests out of the main bundle, as that pushes up towards 6,000 lines. 2019-06-28 21:58:38 -04:00
Thomas Harte
9eb51f164c Imports ANDI, ORI and EORI tests. 2019-06-28 21:42:58 -04:00
Thomas Harte
a1c00e9318 Adds BSR tests. 2019-06-28 21:31:41 -04:00
Thomas Harte
17666bc059 Corrects CHK flags. 2019-06-28 19:48:53 -04:00
Thomas Harte
241d29ff7c Imports SBCD and NBCD tests, and fixes corresponding operation. 2019-06-28 19:39:08 -04:00
Thomas Harte
c5039a4719 Imports ANDI, ORI and EORI to SR tests.
Hence corrects supervisor/user privileges for SR/CCR.
2019-06-28 15:05:46 -04:00
Thomas Harte
fd604048db Imports SUBX tests. 2019-06-28 14:30:26 -04:00
Thomas Harte
6a77ed1e07 Imports SUBI test. 2019-06-28 13:53:53 -04:00
Thomas Harte
9e38815ec4 Imports SUBQ tests. 2019-06-28 13:48:02 -04:00
Thomas Harte
86c325c4ec Imports MOVEA tests. 2019-06-28 13:41:37 -04:00
Thomas Harte
bfcc6cf12c Imports MULU tests.
Timing is wrong for now.
2019-06-28 13:33:41 -04:00
Thomas Harte
8ba8cf7c23 Imports TST tests. 2019-06-28 13:17:21 -04:00
Thomas Harte
6c588a1510 Makes some further random swings at tracking the startup procedure. 2019-06-28 13:03:47 -04:00
Thomas Harte
651fd9c4a5 Imports EOR tests. 2019-06-28 13:03:27 -04:00
Thomas Harte
5d0db2198c Imports BRA, EORI CCR and ORI CCR tests, extends PEA tests. 2019-06-27 23:05:00 -04:00
Thomas Harte
d81053ea38 Invents some additional PEA tests, and further fixes PEA. 2019-06-27 17:59:03 -04:00
Thomas Harte
8d39c3bc98 Takes a shot at fixing PEA for A7-relative addresses.
Unit tests required. Tomorrow.
2019-06-26 23:24:54 -04:00
Thomas Harte
da351a3e32 Imports MOVEQ tests. 2019-06-26 22:36:48 -04:00
Thomas Harte
c0591090f5 Imports DIVU tests. 2019-06-26 22:25:48 -04:00
Thomas Harte
538aecb46e Imports CMP tests, and fixes CMP.l timing. 2019-06-26 22:02:04 -04:00
Thomas Harte
dbdbea85c2 Imports CMPA tests, and fixes CMPA.w. 2019-06-26 21:42:48 -04:00
Thomas Harte
ba2224dd06 Imports NEGX tests and thereby fixes NEGX's zero flag. 2019-06-26 19:39:04 -04:00
Thomas Harte
44e2aa9183 Imports MOVEP tests; code corrections to come. 2019-06-26 19:01:09 -04:00
Thomas Harte
202bff70fe Imports BCLR and BTST tests. 2019-06-26 17:51:07 -04:00
Thomas Harte
26c0cd7f7c Imports ADDI tests. 2019-06-26 16:42:23 -04:00
Thomas Harte
cb76301fbe Imports BCHG tests. 2019-06-26 16:33:23 -04:00
Thomas Harte
8bfa12edf1 Adds lengths to ADD tests, imports ANDI ,CCR and MOVE to CCR. 2019-06-26 16:12:27 -04:00
Thomas Harte
7daa969a5a Imports SUBA tests. 2019-06-26 15:47:59 -04:00
Thomas Harte
4aeb60100d Completes import of MOVEM tests. 2019-06-26 15:31:21 -04:00
Thomas Harte
e2c7aaac5a Imports CLR tests. 2019-06-25 22:47:30 -04:00
Thomas Harte
6ff661c30d Imports OR tests. 2019-06-25 22:34:04 -04:00
Thomas Harte
79066f8628 Imports NOT tests, fixes NOT overflow and carry flags. 2019-06-25 22:18:11 -04:00
Thomas Harte
2c813a2692 Imports CMPM tests and fixes CMPM.bw source/destination order. 2019-06-25 21:46:01 -04:00
Thomas Harte
d2cb595b83 Proactively attempts to fix CMPM PostInc addressing. 2019-06-25 21:24:03 -04:00
Thomas Harte
cc4abcb00a Imports ADDQ tests. 2019-06-25 21:19:04 -04:00
Thomas Harte
c1ca85987f Incorporates MOVE to SR test. 2019-06-25 19:30:51 -04:00
Thomas Harte
ecb5a0b8cc Incorporates ADDX tests and fixes ADDX PreDec. 2019-06-25 19:18:07 -04:00
Thomas Harte
e12e8fc616 Incorporates ASR tests, and fixes ASR (xxx).w.
... which was re-injecting the wrong bit to preserve sign.
2019-06-25 18:44:31 -04:00
Thomas Harte
1fbbf32cd2 Adds ASL tests, and corrects ASL (xxx).w.
Overflow is wrong on other ASLs though, I think.
2019-06-25 18:09:01 -04:00
Thomas Harte
31edb15369 Reduces 68000 startup costs a little further. 2019-06-25 17:41:13 -04:00
Thomas Harte
d7883d18d4 Imports CHK tests.
Proving that I need to do some research on CHK's flags.
2019-06-25 14:55:03 -04:00
Thomas Harte
40100773d3 Imports LSR tests. 2019-06-25 13:57:42 -04:00
Thomas Harte
4048ed3a33 Imports ROR tests. 2019-06-25 13:16:44 -04:00
Thomas Harte
11f2d3cea7 Imports EXT tests. 2019-06-24 22:12:29 -04:00
Thomas Harte
aa656a39b8 Imports SUB tests. 2019-06-24 22:00:37 -04:00
Thomas Harte
e830d23533 Incorporates TRAPV tests. 2019-06-24 21:21:35 -04:00
Thomas Harte
9a666fb8cc Imports NEG tests and fixes NEG.l Dn timing. 2019-06-24 19:43:30 -04:00
Thomas Harte
0e208ed432 Fixes cycle counting in the test machine. 2019-06-24 17:55:09 -04:00
Thomas Harte
c8b769de8a Completes import of LSL tests and fixes various LSL issues.
Including LSL (xxx).w actually being LSR, and the carry flag generally being questionable.
2019-06-24 17:45:38 -04:00
Thomas Harte
c447655047 Resolves assumption that shifts greater than the bit count of the relevant int are well-defined in C. 2019-06-24 16:51:43 -04:00
Thomas Harte
3ec9a1d869 Incorporates JMP tests, fixes JSR (xxx).l timing. 2019-06-24 15:36:33 -04:00
Thomas Harte
d326886852 Completes BSET tests. 2019-06-24 14:04:08 -04:00
Thomas Harte
faef917cbd Improves resizeable microcycle test. 2019-06-24 10:55:22 -04:00
Thomas Harte
d27ba90c07 Attempts to introduce more rigour to variable-length instruction handling. 2019-06-24 10:43:28 -04:00
Thomas Harte
db4ca746e3 Introduces BSET tests, fixes BSET timing. 2019-06-23 22:53:37 -04:00
Thomas Harte
d50fbfb506 Imports EXG and PEA tests, and fixes EXG timing. 2019-06-23 22:21:25 -04:00
Thomas Harte
5d283a9f1f Imports LEA tests. 2019-06-23 21:48:47 -04:00
Thomas Harte
86fdc75feb Incorporates RTR test, adding a ProcessorState helper. 2019-06-23 18:37:32 -04:00
Thomas Harte
b63231523a Completes import of ROL tests. 2019-06-23 17:33:12 -04:00
Thomas Harte
70e296674d Starts import of ROL tests.
Including time tests, this time.
2019-06-22 22:42:57 -04:00
Thomas Harte
5089fcd2f6 Makes a slightly futile attempt to resolve Heisen-failures. 2019-06-22 18:52:06 -04:00
Thomas Harte
df2ce8ca6f Imports MOVE tests. 2019-06-21 22:03:27 -04:00
Thomas Harte
7e209353bb Imports UNLINK and NOP tests. 2019-06-21 21:29:02 -04:00
Thomas Harte
c2806a94e2 Imports further MOVEM tests. 2019-06-21 21:20:13 -04:00
Thomas Harte
d428120776 Completes import of LINK tests. 2019-06-21 18:33:44 -04:00
Thomas Harte
8c8493bc9d Ensures proper loading of the SP at reset. 2019-06-21 18:20:26 -04:00
Thomas Harte
6b996ae57d Improves test machine and incorporates a first test of LINK. 2019-06-21 18:20:13 -04:00
Thomas Harte
ccfe1b13cb Imports DIVS, MULS and MOVE from SR tests.
Not all passing.
2019-06-21 16:03:11 -04:00
Thomas Harte
0c1c10bc66 Introduces a test that proves that DIVS' attempt to set proper timing isn't working. 2019-06-20 19:29:02 -04:00
Thomas Harte
fafd1801fe Introduces first DIVS test, and associated fixes. 2019-06-20 19:02:03 -04:00
Thomas Harte
bcf6f665b8 Simplifies and completes DBcc tests.
Subject to omitting a few that look to me like duplicates.
2019-06-20 17:19:25 -04:00
Thomas Harte
bd069490b5 Incorporates approximately half of the DBcc tests. 2019-06-20 16:29:32 -04:00
Thomas Harte
79d8d27b4c Reintroduces use of locations_by_bus_step_ to decrease 68000 construction time. 2019-06-20 15:10:11 -04:00
Thomas Harte
624b0b6372 Adds Scc tests. No implementation fixes required. 2019-06-19 21:42:54 -04:00
Thomas Harte
7976cf5b3c Adds ADDA tests. All passing without 68000 changes. 2019-06-19 21:31:14 -04:00
Thomas Harte
440f52c943 Incorporates TRAP test. 2019-06-19 21:18:30 -04:00
Thomas Harte
47b1218a68 Adds a couple of the one-shots: SWAP, MOVE USP. 2019-06-19 19:10:36 -04:00
Thomas Harte
91ced056d2 Adds tests for ADD. No failures. 2019-06-19 18:56:21 -04:00
Thomas Harte
8dace34e63 Imports third-party tests for ABCD, and thereby fixes ABCD. 2019-06-19 18:13:06 -04:00
Thomas Harte
8182b0363f Adds enum to help with status decoding. 2019-06-19 17:01:49 -04:00
Thomas Harte
c5b036fedf Ensures aborted decodes don't overwrite prior correct ones. 2019-06-19 17:00:44 -04:00
Thomas Harte
e26ddd0ed5 Corrects address fetches for CMPI.l #, (xxx).w. 2019-06-19 13:52:56 -04:00
Thomas Harte
ca83431e54 Fixed: Scc is a byte operation.
It was, until now, post-incrementing and pre-decrementing registers other than A7 incorrectly.
2019-06-19 13:15:12 -04:00
Thomas Harte
68a3e5a739 Renamed DiskCopy42 to MacintoshIMG, now that it's not just DiskCopy 4.2 files. 2019-06-18 14:32:58 -04:00
Thomas Harte
b98f10cb45 Substitutes working GCR test. 2019-06-18 14:24:55 -04:00
Thomas Harte
9730800b6a Adds support for raw sector dumps. 2019-06-18 14:14:25 -04:00
Thomas Harte
506276a2bd Corrected: use format tag as intended. 2019-06-18 14:04:28 -04:00
Thomas Harte
00c32e4b59 Further miscellaneous changes to debug logging. All temporary. 2019-06-18 10:34:31 -04:00
Thomas Harte
df56e6fe53 Fixed: the sector number also goes into sector bodies.
Also the checksum is written in the other order, and the final byte of data isn't output.
2019-06-18 10:34:10 -04:00
Thomas Harte
756641e837 Fixed: tags go first, then data. 2019-06-16 22:00:12 -04:00
Thomas Harte
05c2854dbc Makes at least some attempt at producing real disk tracks. 2019-06-16 21:17:24 -04:00
Thomas Harte
5c8aacdc17 Fixes the more obvious issues with GCR encoding: byte order, top bit selection. 2019-06-16 17:17:24 -04:00
Thomas Harte
745a5ab749 Introduces failing test of Macintosh GCR data encoding. 2019-06-16 16:53:03 -04:00
Thomas Harte
fe0dc4df88 Starts building out some tests for Apple GCR encoding. 2019-06-15 22:48:24 -04:00
Thomas Harte
33f2664fe9 Makes a first attempt at Macintosh GCR encoding. 2019-06-15 22:29:02 -04:00
Thomas Harte
a17e47fa43 Apple's GCR header varies between the Mac and the Apple II. 2019-06-15 16:32:56 -04:00
Thomas Harte
877b46d2c1 Advances IWM/drive emulation very close to the point of 'Welcome to Macintosh'. 2019-06-15 16:08:54 -04:00
Thomas Harte
cc7226ae9f Starts trying to get a bit more rigorous about collected meanings. 2019-06-13 22:48:10 -04:00
Thomas Harte
bde975a3b9 Possibly mights the tiniest bit of headway with 'the IWM'.
I'm now pretty sure that my 3.5" drive, which for now is implemented in the IWM (yuck) is just responding to queries incorrectly.
2019-06-13 22:38:09 -04:00
Thomas Harte
f6f9024631 Corrects Macintosh aspect ratio (and framing). 2019-06-13 18:41:38 -04:00
Thomas Harte
39aae34323 Avoids multiple calls to -[NSCursor hide] and -unhide.
Those are reference counted.
2019-06-13 13:39:35 -04:00
Thomas Harte
5630141ad7 Ensures randomised memory contents at startup. 2019-06-13 13:35:16 -04:00
Thomas Harte
535747e3f2 Restores single-line logging format. 2019-06-13 13:35:03 -04:00
Thomas Harte
59a94943aa Resolves final set of build warnings. 2019-06-13 10:55:29 -04:00
Thomas Harte
bf4889f238 Reduces warnings to 6. 2019-06-13 10:43:00 -04:00
Thomas Harte
7cc5afd798 Eliminates another couple of implicit type conversion warnings. 2019-06-13 10:30:26 -04:00
Thomas Harte
11ab021672 Further reduces implicit conversion warnings, to 17. 2019-06-13 10:27:49 -04:00
Thomas Harte
feafd4bdae Eliminates further type conversion warnings. 2019-06-13 10:20:17 -04:00
Thomas Harte
d6150645c0 By hook or by crook, mouse input now works. 2019-06-12 22:19:25 -04:00
Thomas Harte
ccd2cb44a2 Fills in enough of the SCC to allow completion of the Macintosh side of that relationship. 2019-06-12 17:51:50 -04:00
Thomas Harte
ec5701459c Makes various temporary logging changes. 2019-06-11 19:54:07 -04:00
Thomas Harte
ad8b68c998 Switches to a proper form of zero-upon-read data.
Not that it's necessarily correct.
2019-06-11 19:53:51 -04:00
Thomas Harte
c8066b01b6 Restores attempt at proper audio behaviour. 2019-06-11 19:53:22 -04:00
Thomas Harte
ebd59f4dd3 Performs the trivial part of wiring up the Macintosh mouse.
SCC still to go.
2019-06-11 19:52:37 -04:00
Thomas Harte
109953ef49 Ensures proper routing of mouse events from Cocoa. 2019-06-11 18:41:41 -04:00
Thomas Harte
124c7bcbb0 Makes the Macintosh a mouse machine, and makes mouse machines detectable. 2019-06-11 18:21:56 -04:00
Thomas Harte
a0321aa6ff Starts sketching out an emulator interface for mice. 2019-06-11 17:47:24 -04:00
Thomas Harte
567feaac10 Adds a proper shout out for releasing the mouse. 2019-06-11 16:35:04 -04:00
Thomas Harte
15c38e2f15 Adds the option for mouse capture. 2019-06-11 16:30:53 -04:00
Thomas Harte
3c075e9542 Switches drives 0 and 1. 2019-06-10 14:58:39 -04:00
Thomas Harte
9230969f43 Corrects enough of the 6522 and Keyboard to get an initial command seemingly working. 2019-06-10 09:28:27 -04:00
Thomas Harte
0e16c67805 Improves shift register connection, towards having the keyboard function properly.
It now seems not to receive a command terminator, but is at least getting a command.
2019-06-08 23:04:55 -04:00
Thomas Harte
697e094a4e Sketches out the absolute basics of an SCC interface. 2019-06-08 18:47:11 -04:00
Thomas Harte
50d37798a2 Eradicates magic constants. 2019-06-06 21:37:43 -04:00
Thomas Harte
e9d0676e75 Fiddles further with the tachometer. 2019-06-06 21:36:19 -04:00
Thomas Harte
7591906777 Numerous IWM fixes: the machine now seems to be trying to measure the tachometer. 2019-06-06 18:32:11 -04:00
Thomas Harte
08671ed69c Fixes setting of a Time to a float. 2019-06-05 14:43:34 -04:00
Thomas Harte
511d292e73 Ensures gain noise is forgotten upon assumption of a new track. 2019-06-05 14:43:17 -04:00
Thomas Harte
a413ae11cb Makes some sort of first attempt at having the IWM read. 2019-06-04 22:13:00 -04:00
Thomas Harte
833258f3d7 Sets things up to allow variable rotation rates, and especially Sony 800kb-style self-selecting rates. 2019-06-04 21:41:54 -04:00
Thomas Harte
b8a1553368 Adds putative support for PlusToo-style BIN files.
Albeit a bit of a guess, since it's not intended to be an emulator file format.
2019-06-04 21:41:09 -04:00
Thomas Harte
058fe3e986 Fixes some other low-hanging warning fruit. 2019-06-04 16:47:10 -04:00
Thomas Harte
51ee83a427 Resolves a further 11 conversion errors. 2019-06-04 16:34:45 -04:00
Thomas Harte
5b21da7874 Reduces number of warnings to 70. 2019-06-04 16:27:09 -04:00
Thomas Harte
bd7f00bd9c Resolves a further handful of implicit type conversion warnings. 2019-06-04 15:43:44 -04:00
Thomas Harte
517cca251f Corrected: the repository shouldn't default to a Release build. 2019-06-04 15:41:36 -04:00
Thomas Harte
1033abd9fe Starts making some space for Macintosh-style GCR encoding. 2019-06-04 15:41:15 -04:00
Thomas Harte
113d022741 Merge branch 'master' into Mac128k 2019-06-03 21:58:22 -04:00
Thomas Harte
299a7b99ae Merge pull request #624 from TomHarte/BookendCrash
Permits end_data only after a begin_data.
2019-06-03 21:57:59 -04:00
Thomas Harte
66540ff86f Permits end_data only after a begin_data. 2019-06-03 21:56:53 -04:00
Thomas Harte
8557558bd8 Mildly improves investigatory reporting. 2019-06-03 21:51:45 -04:00
Thomas Harte
376cf08c71 Merge branch 'master' into Mac128k 2019-06-03 15:59:33 -04:00
Thomas Harte
83e5e650d2 Merge pull request #623 from TomHarte/SharpEdges
Disallows smaller buffer use for 'sharp' displays and tightens sampling window.
2019-06-03 15:59:05 -04:00
Thomas Harte
b860ba2ee3 Disallows smaller buffer use for 'sharp' displays and tightens sampling window. 2019-06-03 15:58:14 -04:00
Thomas Harte
661fe1e649 Disables logging, for now. 2019-06-03 15:57:53 -04:00
Thomas Harte
5b8375f0a0 Disallows smaller buffer use for 'sharp' displays and tightens sampling window. 2019-06-03 15:57:31 -04:00
Thomas Harte
abe55fe950 Adds Timer 1 toggling of PB7. 2019-06-03 15:39:20 -04:00
Thomas Harte
4d4ddded6d Fixes register-relative JMP and JSR. 2019-06-03 15:29:50 -04:00
Thomas Harte
1328708a70 Switches to testing against the Mac Plus ROM.
Immediately uncovering an issue with JMP.
2019-06-03 14:54:18 -04:00
Thomas Harte
85298319fa Expands towards supporting multiple Macintosh models.
To provide another variable to help with bug isolation.
2019-06-03 14:50:36 -04:00
Thomas Harte
881feb1bd3 Adds preliminary parsing of the Disk Copy 4.2 format. 2019-06-02 13:39:25 -04:00
Thomas Harte
3e9fa63799 Adds a receiver for drive-motor control bytes.
My new belief is that I'm either reading the buffer from the wrong place, or the 68000 isn't filling it for some reason.
2019-06-01 19:31:32 -04:00
Thomas Harte
da2b190288 Stores expected bit length. 2019-06-01 19:08:29 -04:00
Thomas Harte
48d837c636 Attempts to respond more sensibly to various queries.
Including adding a 1-second delay on motor off.
2019-06-01 18:43:47 -04:00
Thomas Harte
983407896c Ensures consistent audio pipeline. 2019-06-01 17:29:57 -04:00
Thomas Harte
5c08bb810e In theory provides a full implementation of audio.
Albeit seemingly ineffective.
2019-06-01 15:44:29 -04:00
Thomas Harte
17635da812 Pushes Mac audio further towards being able to function. 2019-06-01 15:18:27 -04:00
Thomas Harte
6d985866ee All proper inputs are now provided to the audio generator.
Hopefully. The next job is to generate audio. If that sounds correct, then the disk motor speed question can be tackled.
2019-06-01 15:03:15 -04:00
Thomas Harte
723137c0d4 With some time additions to the 6522, starts wiring in Macintosh audio.
The audio buffer is also the disk motor buffer, so this is preparatory to further disk work.
2019-06-01 14:39:40 -04:00
Thomas Harte
938928865d Merge branch 'master' into Mac128k 2019-05-30 22:29:56 -04:00
Thomas Harte
d80b0cbf90 Merge pull request #622 from TomHarte/SConstructUTF
Adds recommended fix for 0xc3 in position 12 error.
2019-05-30 22:25:57 -04:00
Thomas Harte
e88ef30ce6 Adds recommended fix for 0xc3 in position 12 error. 2019-05-30 22:20:15 -04:00
Thomas Harte
4197c6f149 Attempts to make some further semantic sense of the various IWM controls. 2019-05-30 22:17:49 -04:00
Thomas Harte
035f07877c Reduces conversions to vector. 2019-05-30 12:08:35 -04:00
Thomas Harte
4632be4fe5 Wires up the final IWM signal, SEL, preparatory to an implementation. 2019-05-30 12:08:00 -04:00
Thomas Harte
b3d2b4cd37 Fixes the interrupt return address. 2019-05-29 20:27:46 -04:00
Thomas Harte
c86fe9ada9 Ensures replace_write_values works in release builds. 2019-05-29 19:00:53 -04:00
Thomas Harte
ecf93b7822 Eliminates some type conversion warnings. 2019-05-29 14:56:50 -04:00
Thomas Harte
541b75ee6e Further fixes PEA, and OR/AND/EOR Dn, (An). 2019-05-29 14:37:15 -04:00
Thomas Harte
77b08febdb Corrects PEA and adds an additional debugging aid. 2019-05-29 12:47:17 -04:00
Thomas Harte
fcda376f33 Removes three further type conversion warnings. 2019-05-28 21:56:49 -04:00
Thomas Harte
0848fc7e03 Ensures the Mac uses auto vectored interrupts. 2019-05-28 16:24:41 -04:00
Thomas Harte
3bb8d6717f Ensures A7 is correct at end of an UNLINK. 2019-05-28 16:02:42 -04:00
Thomas Harte
5e2496d59c Simplifies and corrects MOVE logic. 2019-05-28 15:17:03 -04:00
Thomas Harte
c52da9d802 Adds some logging preparatory to a MOVE change. 2019-05-28 15:05:42 -04:00
Thomas Harte
1d3dde32f2 Ensures final byte of data can be accessed. 2019-05-09 07:24:26 -04:00
Thomas Harte
0b999ce0e4 Attempts to fix register-relative JSRs. 2019-05-09 06:43:07 -04:00
Thomas Harte
b04bd7069d Corrects Scc and DBcc (xxx).l and (xxx).w. 2019-05-09 06:28:55 -04:00
Thomas Harte
249b0fbb32 Corrects PC on stack after an illegal instruction.
Also fixed LOG_TRACE functionality.
2019-05-08 22:36:25 -04:00
Thomas Harte
41740fb45e Implements video position feedback.
At a substantial performance cost for now, but I'll worry about that once things are working.
2019-05-08 16:54:19 -04:00
Thomas Harte
0ad88508f7 Removes ROM mirroring above $600000. 2019-05-08 15:07:03 -04:00
Thomas Harte
8293b18278 Adds a TODO on what I think might be an incorrect implementation? 2019-05-08 15:06:40 -04:00
Thomas Harte
2ba0364850 Adds the shift register interrupt. 2019-05-08 15:02:07 -04:00
Thomas Harte
8b72043f33 Ensures no uninitialised variables. 2019-05-08 14:54:54 -04:00
Thomas Harte
2e7bc0b98a Attempts the shift register. 2019-05-08 14:54:40 -04:00
Thomas Harte
f0f9722ca6 Takes a first crack at the keyboard's serial protocol.
Albeit that without a working shift register in the VIA, this shouldn't really work yet.
2019-05-08 14:20:28 -04:00
Thomas Harte
b5ef88902b Edges further towards a functioning keyboard. 2019-05-08 13:58:52 -04:00
Thomas Harte
8278809383 Attempts to get more rigorous on communicating outward control line changes. 2019-05-08 13:33:22 -04:00
Thomas Harte
4367459cf2 Takes a first go at handshake and pulse modes. 2019-05-08 12:48:29 -04:00
Thomas Harte
254132b83d Eliminates 6522Base in pursuit of working handshake modes.
Specifically: this means that the places from which the BusHandler may be called are more numerous.
2019-05-08 12:35:17 -04:00
Thomas Harte
7b466e6d0a Begins work on a functioning keyboard. 2019-05-08 12:34:26 -04:00
Thomas Harte
7e6d4f5a3e Adds emulation of the real-time clock. 2019-05-08 00:12:19 -04:00
Thomas Harte
ce099a297a Eliminates RAM writes in ROM area.
I no longer think that logic is correct.
2019-05-07 17:16:22 -04:00
Thomas Harte
949c848815 Broadens address decoding.
To no obvious change in output.
2019-05-06 22:57:29 -04:00
Thomas Harte
9bf9b9ea8c Ensures unmapped peripherals return a consistent value. 2019-05-06 21:32:10 -04:00
Thomas Harte
d8ed8b66f3 Improves carry/extend for ROXL and ROXR. 2019-05-06 21:14:16 -04:00
Thomas Harte
a131d39451 I now believe only the 6522 is on the synchronous bus. 2019-05-06 14:10:13 -04:00
Thomas Harte
b540f58457 Sets a more appropriate display type. 2019-05-05 23:22:05 -04:00
Thomas Harte
4f5a38b5c5 Adds support for the alternate video buffer. 2019-05-05 23:05:24 -04:00
Thomas Harte
cefc3af08b Corrects RAM read decoding when the ROM overlay is enabled. 2019-05-05 22:48:40 -04:00
Thomas Harte
e6ed50383c Corrects PEA and MOVE.l (An)[+], (xxx).L; also adds an extra test that caught the latter. 2019-05-05 22:47:54 -04:00
Thomas Harte
96facc103a Adds an IWM shim and corrects graphics output.
... now that there is some.
2019-05-05 21:55:34 -04:00
Thomas Harte
407bbfb379 Pretending the Disk II is an IWM doesn't seem to achieve much. 2019-05-05 18:12:25 -04:00
Thomas Harte
a99ebda513 Takes a first shot at (inverted) Mac video output. 2019-05-04 22:27:58 -04:00
Thomas Harte
537b604fc9 It looks like writes should always go to RAM.
Now I see the screen buffer being filled with `0xffff`s, along with what is probably disk motor control data.
2019-05-04 17:29:30 -04:00
Thomas Harte
98bc570bf7 Adds further boilerplate around VIA and IWM decoding. 2019-05-04 17:12:26 -04:00
Thomas Harte
181b77c490 Adds decoding of IWM accesses and respect for the ROM overlay bit. 2019-05-04 16:38:01 -04:00
Thomas Harte
bc9eb82e6f Adds in VIA access decoding, and a note to self on video.
The Mac now proceeds to try to talk to the IWM.
2019-05-04 14:23:37 -04:00
Thomas Harte
29fc024ecd Starts negotiating the Macintosh memory map. 2019-05-04 12:33:27 -04:00
Thomas Harte
c1695d0910 Adds various notes to self. 2019-05-03 23:55:28 -04:00
Thomas Harte
6d6a4e79c9 Adds the absolute basics to include a 6522 in the Macintosh.
Not yet wired to anything.
2019-05-03 23:40:22 -04:00
Thomas Harte
417a3e1540 Adds missing call to flush. 2019-05-03 23:31:12 -04:00
Thomas Harte
fa8c804d47 Makes explicit a few implicit type conversions.
There's plenty more down this well, alas.
2019-05-03 23:26:03 -04:00
Thomas Harte
68392ce6f5 Adds enough of a concept of Mac video to get a properly initialised display.
Completely empty at present, naturally. Also this is the very first time I've run my 68000 at live speed. From just one data point, it's not terrible. Phew!
2019-05-03 23:25:42 -04:00
Thomas Harte
6873f62ad8 Ensures that the Mac now retains its ROM properly. 2019-05-03 22:39:09 -04:00
Thomas Harte
5f385e15f6 Adds the bare bones necessary to be able to create a Macintosh from File -> New... . 2019-05-03 22:16:07 -04:00
Thomas Harte
8c5d37b6ee Refactors the AppleII into a sub-namespace to make room for other Apple machines. 2019-05-03 18:14:10 -04:00
Thomas Harte
9c3c2192dd Merge pull request #611 from TomHarte/68000
Adds an Initial Emulation of the 68000
2019-05-03 15:08:24 -04:00
Thomas Harte
4f9f73ca81 Corrects tests affected by change in run_for_instructions semantics and new program base address. 2019-05-03 15:05:14 -04:00
Thomas Harte
2c9a1f7b16 Restores vector. 2019-05-03 14:50:07 -04:00
Thomas Harte
0ea4c1ac80 Evicts #includes from my namespace. 2019-05-03 14:48:39 -04:00
Thomas Harte
a873ec97eb Also previously missing: vector.h. 2019-05-03 14:43:31 -04:00
Thomas Harte
cc8a65780e Adds further missing includes. 2019-05-03 14:42:36 -04:00
Thomas Harte
c117deb43b Introduces a couple of missing #includes. 2019-05-03 14:37:05 -04:00
Thomas Harte
ae31d45c88 Introduces the 68000 to SConstruct. 2019-05-03 14:31:09 -04:00
Thomas Harte
a0eb20ff1f Tweaks divide-by-zero timing. 2019-05-03 14:29:36 -04:00
Thomas Harte
34fe9981e4 Added necessary mea culpas. 2019-05-03 14:25:25 -04:00
Thomas Harte
291e91375f Takes a shot at the synchronous bus. 2019-05-03 14:20:59 -04:00
Thomas Harte
857f74b320 Fixed: the accepted interrupt level now appears on the bus. 2019-05-02 15:47:12 -04:00
Thomas Harte
1d9608efc7 Alters the order of interrupt bus activity, to bring it into line with a real 68000. 2019-05-02 15:25:43 -04:00
Thomas Harte
93616a4903 Completes test of a vectored interrupt.
Correcting issues uncovered.
2019-05-02 00:00:09 -04:00
Thomas Harte
bb07206c55 Corrects internet response to work as currently implemented.
Also makes corrections to the bus error and address error exceptions.
2019-05-01 21:59:06 -04:00
Thomas Harte
2e5c0811e7 Makes some effort at getting into interrupt processing. 2019-05-01 15:26:36 -04:00
Thomas Harte
f6ac407e4d Takes further steps towards supporting interrupts.
Specifically:
* introduces the necessary bus signalling; and
* adds corresponding functional steps.

Still to figure out: getting into and out of an interrupt cycle.
2019-05-01 15:19:24 -04:00
Thomas Harte
078c3135df The 5/3 split of microcycles appears not accurately to model when lines are tested.
Therefore I've reverted to a more normative 4:4 form.
2019-04-30 22:09:13 -04:00
Thomas Harte
92568c90c8 Adds support for HALT as an input, and puts some effort into how to calculate E. 2019-04-30 22:07:48 -04:00
Thomas Harte
f1879c5fbc Corrects interrupt level test within STOP. 2019-04-30 19:32:35 -04:00
Thomas Harte
31bb770fdd Implement STOPpages, waits for DTack, and bus and address error exceptions. 2019-04-30 19:24:22 -04:00
Thomas Harte
e430f2658f Adds a test and by that means fixes divide-by-zero exception return addresses. 2019-04-29 23:09:50 -04:00
Thomas Harte
3060175ff5 Eliminates constructions of std::tuple for performance reasons.
Specifically: reduces 68000 construction time from 10+ seconds to more like 2.8.
2019-04-29 22:43:15 -04:00
Thomas Harte
eb4233e2fd Joins some commonalities, shaving about 150 lines of code. 2019-04-29 22:37:23 -04:00
Thomas Harte
6b4c656849 Reverses order of instruction instantiation, reducing total bus step heft by about 11%.
... since that means inserting more complicated instructions before simpler ones in general, making subset finds more likely.
2019-04-29 22:20:18 -04:00
Thomas Harte
1b8fada6aa Restores accidentally-cropped functionality. 2019-04-29 22:10:00 -04:00
Thomas Harte
7332c64964 Improves testing of function as distinct from timing. 2019-04-29 22:08:37 -04:00
Thomas Harte
977f9ee831 Takes a run at divide-by-zero exceptions and starts looking towards ways to improve startup time. 2019-04-29 22:08:16 -04:00
Thomas Harte
16fb3b49a5 It leads to a TODO, but implemented decoding and initial setup of STOPpages. 2019-04-29 19:30:00 -04:00
Thomas Harte
3da1b3bf9b Introduces storage for various bus inputs. 2019-04-29 19:22:05 -04:00
Thomas Harte
bc00856c05 Removed TODO; it appears this is just the standard stack frame. 2019-04-29 19:09:20 -04:00
Thomas Harte
52e3dece81 Improves exposition. 2019-04-29 19:07:14 -04:00
Thomas Harte
2c1d8fa18a Adds a check for instruction privilege violation, albeit that I think I need different bus steps. 2019-04-29 19:06:10 -04:00
Thomas Harte
3e34ae67f6 Implements support for the trace flag. 2019-04-29 19:02:59 -04:00
Thomas Harte
d6e16d0042 Adds a test of TOS 1.00, as far as it goes without meaningful hardware. 2019-04-29 18:04:57 -04:00
Thomas Harte
8e02d29ae6 Trims test to length of trace capture. 2019-04-29 17:56:49 -04:00
Thomas Harte
ceebecec8d Corrects zero and negative flags for EXT.w. 2019-04-29 17:54:33 -04:00
Thomas Harte
05d1eda422 Fixes crossed-over decoding of EORI and ORI. 2019-04-29 17:45:52 -04:00
Thomas Harte
31f318ad43 Fixes MOVE.bw #, (xxx).w. 2019-04-29 17:41:46 -04:00
Thomas Harte
270f46e147 Normalises CMPl. 2019-04-29 17:27:56 -04:00
Thomas Harte
c0e9c37cc7 Improves memory map model, as far as it goes. 2019-04-29 17:27:44 -04:00
Thomas Harte
8564945713 Corrects vector nomination for unrecognised opcodes. 2019-04-29 17:10:33 -04:00
Thomas Harte
7bd7f3fb73 Sign-extends (xxx).w addresses. 2019-04-29 16:55:43 -04:00
Thomas Harte
5b5bfc8445 Applies trace testing to EmuTOS. 2019-04-29 16:55:21 -04:00
Thomas Harte
c466b6f9e7 Factors out the [unit testing] stuff of being a trace-checking 68000 bus handler. 2019-04-29 16:11:01 -04:00
Thomas Harte
407643c575 Tweaks test length slightly to ensure this doesn't run beyond the final line's end. 2019-04-29 15:40:17 -04:00
Thomas Harte
d9071ee9f1 Starts sketching out the asynchronous bus. 2019-04-29 13:45:53 -04:00
Thomas Harte
97e118abfa Corrects accidental exclusion of MOVE.bw (xxx).w, [(xxx).w/(xxx).l]. 2019-04-28 23:25:46 -04:00
Thomas Harte
412f091d76 Implements a missing form of BTST. 2019-04-28 23:20:50 -04:00
Thomas Harte
d9278e9827 Attempts to complete the list of things I can't disassemble.
Mysteries to be solved here, definitely. But: 13 missing opcodes remaining.
2019-04-28 23:11:49 -04:00
Thomas Harte
ca1f669e64 Implements MOVEP.
371 is now the alleged number of missing opcodes. But I'd dare imagine it's more like three or four.
2019-04-28 22:52:54 -04:00
Thomas Harte
0298b1b3b7 Implements LINK and UNLINK.
Also starts excluding opcodes that I can't determine the mapping of from the list of those tested against.

Due to those two things together, the latter incomplete: 627 opcodes outstanding. But only STOP and MOVEP remain on my list of things to implement prior to exceptions.
2019-04-28 17:12:31 -04:00
Thomas Harte
4b1324de77 Takes a run at TRAPV.
... to leave 1466 as the unimplemented count.
2019-04-28 15:52:58 -04:00
Thomas Harte
8e8dce9bec Attempts an implementation of CHK.
1467 is now the official count of things to implement, though I'm starting to get suspicious.
2019-04-28 15:47:21 -04:00
Thomas Harte
f4350522bf Implements NBCD.
Now outstanding: 1891.
2019-04-27 21:29:50 -04:00
Thomas Harte
e2abb66a11 Adds missing addressing modes for ADDA and SUBA.
... reducing missing opcodes to 1941.
2019-04-27 17:22:26 -04:00
Thomas Harte
ab5fcab9bf Attempts an implementation of ADDX and SUBX.
Leaving 2005 non-[A/F]-line instructions.
2019-04-27 16:57:47 -04:00
Thomas Harte
cf547ef569 Improves semantic communications and temporarily omits A- and F-line instructions.
So it looks like 2773 instructions left to go.
2019-04-27 15:15:03 -04:00
Thomas Harte
e75b386f7d Attempts DIVU and DIVS.
Reportedly leaving 10965 operations now unimplemented.
2019-04-26 22:22:35 -04:00
Thomas Harte
796203859f Implements PEA.
This decreases the unimplemented count by 28 from 11841 to 11813.
2019-04-26 13:49:59 -04:00
Thomas Harte
40f68b70c1 Adds quantification of reports.
Depressingly; 11,841 opcodes are still missing. Better get on with it!
2019-04-26 13:25:34 -04:00
Thomas Harte
40b2fe7339 Merge branch 'master' into 68000 2019-04-26 00:02:35 -04:00
Thomas Harte
a3b6d2d16e Corrects test and resolves all instances of opcodes that are valid but shouldn't be.
The converse case will require implementation of the remaining instructions.
2019-04-25 22:54:58 -04:00
Thomas Harte
3983f8303f Introduces failing test of 68000 opcode coverage. 2019-04-25 22:06:05 -04:00
Thomas Harte
7cbd5e0ef6 Imports additional files used as test cases. 2019-04-25 21:43:47 -04:00
Thomas Harte
dab9bb6575 Implements EXT. 2019-04-25 18:22:19 -04:00
Thomas Harte
7df85ea695 Cleans up and formally introduces a comparative source for QL startup. 2019-04-25 15:42:41 -04:00
Thomas Harte
c132bda01c Implements MOVE from SR. 2019-04-25 14:39:32 -04:00
Thomas Harte
4e25bcfcdc Corrects decoding of AND/OR x, Dn. 2019-04-25 14:19:13 -04:00
Thomas Harte
ea463549c7 Corrects overflow flag for LSL and LSR. 2019-04-25 13:59:10 -04:00
Thomas Harte
723acb31b3 Corrects various flag issues with ADD, SUB and NEG. 2019-04-25 13:53:23 -04:00
Thomas Harte
5725db9234 Corrects calculated-address TAS. 2019-04-25 12:42:05 -04:00
Thomas Harte
8557e563bc Takes a run at TAS, clarifying bus cycles. 2019-04-25 12:19:40 -04:00
Thomas Harte
d2491633ce Ensures MOVEM to M .w correctly updates A7. 2019-04-24 23:21:15 -04:00
Thomas Harte
002796e5f5 Takes a run at BSET and BCHG. 2019-04-24 23:01:32 -04:00
Thomas Harte
fa0accf251 Attempts to correct flags for ASL, ASR, LSL, LSR. 2019-04-24 21:04:47 -04:00
Thomas Harte
dcb8176d90 Corrects potential failure properly to set stack pointer state. 2019-04-24 17:58:27 -04:00
Thomas Harte
be32b1a198 Fixes JSR (An) return address [again]. 2019-04-24 17:50:38 -04:00
Thomas Harte
582e4acc11 Implements ANDI/ORI/EOR to SR/CCR. 2019-04-24 17:38:59 -04:00
Thomas Harte
10f75acf71 Causes EXG to function. 2019-04-24 16:32:16 -04:00
Thomas Harte
b9933f512f Fixed: the word/long-word bit works the other way around. 2019-04-24 16:30:15 -04:00
Thomas Harte
75a7f7ab22 Inserts missing program fetch for CMPI.bw #, (d8/16...). 2019-04-24 14:45:24 -04:00
Thomas Harte
757be2906e Merge pull request #620 from rzumer/fix_typos
Correct typos in Z80.hpp
2019-04-24 13:25:06 -04:00
Thomas Harte
e214584c76 SWAP should clear overflow and carry. 2019-04-24 13:19:56 -04:00
Thomas Harte
0bb6b498ce Simplifies and fixes post-inc MOVE behaviour. 2019-04-24 13:14:25 -04:00
Thomas Harte
958d44a20d Causes SWAP actually to perform. 2019-04-24 13:06:12 -04:00
Thomas Harte
bb9424d944 Corrects byte increment/decrement actions for A7. 2019-04-24 13:01:08 -04:00
Thomas Harte
11bf706aa2 Attempts to fix LT and LTE conditions. 2019-04-24 10:07:17 -04:00
Thomas Harte
033b8e6b36 ADD/SUBQ #, An shouldn't set flags.
Also, temporarily at least, adds a new means for observing CPU behaviour.
2019-04-24 09:59:54 -04:00
Thomas Harte
7c3ea7b2ea Resolves additional byte accesses being signalled as word. 2019-04-23 21:23:20 -04:00
Thomas Harte
a08043ae88 Ensures that MOVE.b #, (xxx).l writes only a byte.
Also rearranges some of the temporary logging functionality.
2019-04-23 19:01:58 -04:00
Thomas Harte
7c132a3ed5 Ensures 16-bit values of Xn for (d8, An, Xn) are sign extended. 2019-04-22 22:13:02 -04:00
Thomas Harte
20e774be1e Corrects return address of JSR (An). 2019-04-22 21:11:49 -04:00
Thomas Harte
6d6046757d Fixes predecrementing MOVEM to leave the proper address in the relevant register. 2019-04-22 15:41:09 -04:00
Thomas Harte
55073b0a52 Corrects a bunch of MOVEs to (d8/16, PC/An, [Xn]). 2019-04-21 22:55:23 -04:00
Thomas Harte
44eb4e51ed Ensures DBcc properly signals program fetches. 2019-04-21 22:54:20 -04:00
Thomas Harte
3cb042a49d Corrects the carry and extend flags for various long-word operations. 2019-04-21 22:08:18 -04:00
Thomas Harte
b78ea7d24c Further simplifies CMPA. 2019-04-20 21:23:36 -04:00
Thomas Harte
c66728dce2 Corrects decoding of CMPA. 2019-04-20 21:21:33 -04:00
Thomas Harte
0be9a0cb88 Corrects Scc (and other conditionals) for complex addressing modes. 2019-04-20 18:35:19 -04:00
Thomas Harte
a90f12dab7 Corrects return address for TRAP. 2019-04-20 15:49:32 -04:00
Thomas Harte
ef33b004f9 Corrects word access order of MOVEM.l. 2019-04-20 15:13:12 -04:00
Thomas Harte
2cac4b0d74 Corrects EA usage for ADDA and SUBA. 2019-04-19 23:02:41 -04:00
Thomas Harte
a49f516265 Corrects direction of MOVE [to/from] USP. 2019-04-19 22:41:06 -04:00
Raphaël Zumer
71ac26944d Correct typos in Z80.hpp 2019-04-19 17:44:52 -04:00
Thomas Harte
2d97fc1f59 Beefs up documentation and developer support. 2019-04-19 13:29:35 -04:00
Thomas Harte
9ef7743205 Attempts to unify type decoding a little further. 2019-04-19 13:29:20 -04:00
Thomas Harte
ee7ae11e90 Implements EXG and SWAP. 2019-04-19 11:27:43 -04:00
Thomas Harte
f67d7f1db5 Adds the final (!) set of missing MOVEs. 2019-04-19 11:11:38 -04:00
Thomas Harte
99981751a2 Adds the official NOP.
Which is a freebie.
2019-04-18 23:46:01 -04:00
Thomas Harte
ffdf02c5df Adds MOVE XXX.lw, -(An) 2019-04-18 23:40:54 -04:00
Thomas Harte
27c7d00a05 Commutes final missing MOVEs to TODOs. 2019-04-18 23:35:32 -04:00
Thomas Harte
64c4137e5b Begins a cleanup procedure on MOVE. 2019-04-18 23:25:19 -04:00
Thomas Harte
8c26d0c6e6 Makes an attempt at RTE and RTR. 2019-04-18 20:50:58 -04:00
Thomas Harte
81dcfd9f85 Implements AND, OR and EOR.
As well as introducing a little more nuance to the double-decoding test.
2019-04-18 16:34:48 -04:00
Thomas Harte
9334557fbf Added important TODO. 2019-04-17 23:12:32 -04:00
Thomas Harte
b09de8efce Attempts to fill in the rest of MOVE x, -(An). 2019-04-17 23:05:16 -04:00
Thomas Harte
5a50eb56dd Marginally increases coverage of MOVE x, -(An). 2019-04-17 22:30:07 -04:00
Thomas Harte
e49b257e94 Takes a run at TRAP. 2019-04-17 22:21:56 -04:00
Thomas Harte
b8a0f4e831 Implements MOVE to/from USP. 2019-04-17 16:58:59 -04:00
Thomas Harte
c265ea9847 Corrects byte writes in both test machines. 2019-04-17 16:39:10 -04:00
Thomas Harte
29f8dcfb40 Fixes a bunch of (d16, An)-type MOVEs and implements MOVE (XXX).wl, (d16,An)/etc. 2019-04-17 16:13:35 -04:00
Thomas Harte
0c05983617 Shortens impact of MULU on the instruction stream to correct parsing.
I need to look into this.
2019-04-17 15:15:48 -04:00
Thomas Harte
0bd653708c Corrects MOVE.bw Dn, (An)[+]. 2019-04-17 14:31:20 -04:00
Thomas Harte
41d800cb63 Fixes ADD/SUB Dn,x to use the proper destination value. 2019-04-17 10:23:47 -04:00
Thomas Harte
cadc0bd509 Mental delusion lifted: JSR doesn't look enough like BSR. 2019-04-17 10:02:14 -04:00
Thomas Harte
b64da2710a Corrects a few MOVE #s. 2019-04-17 10:00:14 -04:00
Thomas Harte
82b08d0e3a Corrects addressing behaviour of nRd[+-]. 2019-04-17 08:53:34 -04:00
Thomas Harte
8f77d1831b Implements MULU and MULS. 2019-04-16 22:16:43 -04:00
Thomas Harte
be722143e1 Completes addressing modes for ADDI/etc/etc. 2019-04-16 21:34:16 -04:00
Thomas Harte
d8d974e2d7 Consolidates JSR and BSR preparation. 2019-04-16 21:29:37 -04:00
Thomas Harte
9b7ca6f271 Implements the basics of EORI, ORI, ANDI, SUBI and ADDI.
Also corrects the BSR return address.
2019-04-16 19:50:10 -04:00
Thomas Harte
8ce018dbab Adds the necessary runtime support for AND, EOR and OR. 2019-04-16 15:17:40 -04:00
Thomas Harte
180062c58c Finishes fleshing out [ADD/SUB]Q. 2019-04-16 14:28:31 -04:00
Thomas Harte
6076b8df69 Merge branch 'master' into 68000 2019-04-16 14:07:23 -04:00
Thomas Harte
5e65ee79b1 Merge pull request #617 from TomHarte/MSXDisk
Removes hard-coded assumption about disk ROM list placement.
2019-04-16 11:22:52 -04:00
Thomas Harte
c0861c7362 Removes hard-coded assumption about disk ROM list placement. 2019-04-16 11:22:03 -04:00
Thomas Harte
37656f14d8 Adds basic addressing modes for [ADD/SUB]Q. 2019-04-16 11:19:45 -04:00
Thomas Harte
dec5535e54 Implements (arguably: fixes) BSR. 2019-04-15 23:20:36 -04:00
Thomas Harte
1f0e3b157a Corrects a couple of JSR and JMP addressing modes. 2019-04-15 22:37:11 -04:00
Thomas Harte
d802e83f49 Fills in further MOVEs. 2019-04-15 22:25:22 -04:00
Thomas Harte
ebcae25762 Adjusts JSR behaviour and further extends MOVE. 2019-04-15 22:02:52 -04:00
Thomas Harte
5330267d16 Implements BCLR. 2019-04-15 18:11:02 -04:00
Thomas Harte
892476973b Attempts RO{X}[L/R]. 2019-04-15 17:31:58 -04:00
Thomas Harte
84f4a25bc9 Completes TST. 2019-04-15 16:28:20 -04:00
Thomas Harte
1460a88bb3 Takes a run at JSR and RTS. 2019-04-15 15:14:38 -04:00
Thomas Harte
62e4c23961 Corrects memory map, causing the RAM test no longer to fail. 2019-04-15 13:03:32 -04:00
Thomas Harte
d25ab35d58 Finally gets setw usage correct. 2019-04-15 12:41:56 -04:00
Thomas Harte
a223cd90a1 Adds predecrement TSTs, increases QL running time, reduces logging. 2019-04-15 12:36:08 -04:00
Thomas Harte
aef92ba29c Corrects immediate shift count. 2019-04-15 12:25:45 -04:00
Thomas Harte
328d297490 Implements the first few addressing modes for TST. 2019-04-15 10:03:52 -04:00
Thomas Harte
3d240f3f18 Corrects decoding of DBcc. 2019-04-15 09:49:23 -04:00
Thomas Harte
45f35236a7 Corrects decoding of ADDA and SUBA. 2019-04-15 09:44:06 -04:00
Thomas Harte
fba210f7ce Corrects MOVE.l Dn, (An)[+]. 2019-04-15 09:30:49 -04:00
Thomas Harte
8a09e5fc16 Implements Scc. 2019-04-14 22:39:13 -04:00
Thomas Harte
52e33e861c Starts to introduce the QL as a second source for 68000 testing.
It's advantageous over the ST in that a commented disassembly of the ROM is available.
2019-04-14 22:15:09 -04:00
Thomas Harte
75d8824e6b Eliminates implicit type conversion. 2019-04-14 21:02:28 -04:00
Thomas Harte
325af677d3 Implements MOVEM to M with an implicit type conversion. 2019-04-14 20:53:27 -04:00
Thomas Harte
1003e70b5e Implements MOVEM to R. 2019-04-14 20:02:18 -04:00
Thomas Harte
d70229201d Advances right up to the lack of MOVEM actions being the final piece. 2019-04-14 14:45:29 -04:00
Thomas Harte
823f91605b Still slow pedalling slightly, adds further MOVEM storage. 2019-04-14 14:31:13 -04:00
Thomas Harte
53f75034fc Commits at least to decoding MOVEM. 2019-04-14 14:09:28 -04:00
Thomas Harte
78649a5b54 Fleshes out MOVE, (XXX) a little further. 2019-04-12 17:16:03 -04:00
Thomas Harte
f48db625a0 Corrects write-back and zero flag for ADD/SUB.l. 2019-04-12 16:41:00 -04:00
Thomas Harte
2ba66c4457 Corrects MOVEA, adds extra test safeguards. 2019-04-12 16:10:17 -04:00
Thomas Harte
2c78ea1a4e Completes conversion away from magic constants. 2019-04-12 15:48:29 -04:00
Thomas Harte
73f50ac44e Commits further to elimination of magic constants. 2019-04-12 13:45:28 -04:00
Thomas Harte
9ce48953c1 Improves debugging printout. 2019-04-12 13:45:03 -04:00
Thomas Harte
1098cd0c6b Begins rooting out magic constants. 2019-04-11 22:31:17 -04:00
Thomas Harte
652ebd143c Corrects addressing mode support for LEA. 2019-04-11 11:58:34 -04:00
Thomas Harte
8e9d7c0f40 Corrects register-relative address calculation. 2019-04-10 23:09:03 -04:00
Thomas Harte
a64948a2ba Permits zero-bus-op non-terminals. 2019-04-10 22:42:43 -04:00
Thomas Harte
43f619a081 Implements ASL, ASR, LSL and LSR. 2019-04-10 22:31:04 -04:00
Thomas Harte
a07de97df4 Implements the fixed part of register shifts. 2019-04-09 22:12:37 -04:00
Thomas Harte
85d25068a8 Attempts a full implementation of memory shifts. 2019-04-09 22:04:25 -04:00
Thomas Harte
7a0319cfe5 Kicks the work of dealing with ASL/etc into the runtime. 2019-04-09 21:48:08 -04:00
Thomas Harte
f750671f33 Stepping gingerly onwards, adds a double-decoding test.
As a result of that, collapses BRA into Bcc. Which provisionally looks correct.
2019-04-09 16:54:41 -04:00
Thomas Harte
7886fe677a Cleans up commenting. 2019-04-08 22:51:18 -04:00
Thomas Harte
73c027f8e3 Implements CMPA and CMPM. [Provisionally] completing the CMPs. 2019-04-08 22:40:38 -04:00
Thomas Harte
eda88cc462 Implements MOVE to CCR. 2019-04-07 22:24:17 -04:00
Thomas Harte
652f4ebfed Implements CLR, NEG, NEGX and NOT. 2019-04-07 22:07:39 -04:00
Thomas Harte
06a2f59bd0 Implements DBcc. 2019-04-06 23:21:01 -04:00
Thomas Harte
0af57806da Adds a hard-coded value sufficient to advance in TOS startup. 2019-04-06 20:00:34 -04:00
Thomas Harte
03f365e696 Corrects source/destination order of CMP setup. 2019-04-06 20:00:15 -04:00
Thomas Harte
49a22674ba Corrects MOVE destinations. 2019-04-06 18:33:53 -04:00
Thomas Harte
ec494511ec Implements CMP. 2019-04-06 10:41:19 -04:00
Thomas Harte
af02ce9c6e Attempts to correct various instances of PC-relative addressing. 2019-04-05 23:49:13 -04:00
Thomas Harte
56e42859ab Ensures the supervisor flag is updated properly on MOVE to SR. 2019-04-05 23:21:50 -04:00
Thomas Harte
2d153359f8 Adds BTST. 2019-04-04 21:43:22 -04:00
Thomas Harte
068ce23716 Adds a few more MOVEs. 2019-04-04 19:49:19 -04:00
Thomas Harte
03be2e3652 Adds decoding of ADDA and SUBA. 2019-04-03 22:39:01 -04:00
Thomas Harte
4ef2c0bed8 Completes ADD and SUB. 2019-04-03 21:41:59 -04:00
Thomas Harte
bfd405613c Reuse of addresses is also no longer implicit. 2019-04-03 21:27:11 -04:00
Thomas Harte
73e1c8c780 Corrects now-unimplemented ADD/SUB. 2019-04-03 19:43:54 -04:00
Thomas Harte
689ba1d4a2 Effective address adjustments now have to be explicit. 2019-04-03 19:13:10 -04:00
Thomas Harte
39b9d00550 Moves some way towards mapping out ADD and SUB, fixing a bug with address register modification. 2019-04-02 21:50:58 -04:00
Thomas Harte
64f99d83a4 Takes a stab at offering ADD, ADDA, SUB and SUBA operations.
Not yet decoded.
2019-04-01 21:21:26 -04:00
Thomas Harte
8f1faefa1c Implements further MOVEs and fixes a potential error in program formation. 2019-03-31 22:34:28 -04:00
Thomas Harte
2c5ff9ada0 Switches to running the real TOS, at least temporarily, and enables better testing. 2019-03-31 22:27:57 -04:00
Thomas Harte
a9ceef5c37 Improves communication slightly. 2019-03-31 22:27:33 -04:00
Thomas Harte
c6f977ed4b Corrects CMPI and documentation; implements JMP. 2019-03-31 21:13:26 -04:00
Thomas Harte
cb240cd32a Switches to a more explicit tokeniser, to allow for greater flexibility momentarily. 2019-03-30 23:11:39 -04:00
Thomas Harte
bc6349f823 Adds RESET, fixes branches and attempts to fix CMPI. 2019-03-29 23:40:54 -04:00
Thomas Harte
a93a1ae40f Completes MOVE.blw <ea>, Dn/An/(An)/(An)+, implements MOVEq. 2019-03-29 23:13:41 -04:00
Thomas Harte
25254255fe Implements a few additional MOVEs. 2019-03-27 21:26:04 -04:00
Thomas Harte
b0b2798f39 Updates to track Swift. 2019-03-27 21:25:51 -04:00
Thomas Harte
7f5c637aeb Updates to Swift 5. 2019-03-26 22:15:38 -04:00
Thomas Harte
42634b500c Implements LEA. 2019-03-26 22:07:28 -04:00
Thomas Harte
6f0eb5eccd Merge branch 'master' into 68000 2019-03-26 21:03:57 -04:00
Thomas Harte
3d83891eb0 Merge pull request #613 from TomHarte/Swift5
Performs basic migration to Xcode 10.2.
2019-03-26 21:03:30 -04:00
Thomas Harte
69a2a133d5 Performs basic migration to Xcode 10.2 — project settings, the one new warning, etc. 2019-03-26 19:47:41 -04:00
Thomas Harte
be4b38c76a Adds BRA and Bcc. 2019-03-25 22:54:49 -04:00
Thomas Harte
7163b1132c Takes a run at CMPI.
Also factors out a couple of mode things, clarifies on where things from the
prefetch are assembled to, and switches to ordering implemented instructions
alphabetically.
2019-03-24 23:05:57 -04:00
Thomas Harte
3ccec1c996 Implements MOVE to SR, fleshing out the final bits of storage for the status word. 2019-03-24 18:20:54 -04:00
Thomas Harte
47359dc8f1 Adds tests for MOVE.l (An), Dn, and thereby correct their implementation. 2019-03-23 21:41:47 -04:00
Thomas Harte
43532c8455 Starts to make incursions into MOVE[A].l. 2019-03-23 21:03:52 -04:00
Thomas Harte
d7c3d4ce52 Adds test for MOVEA.w (0x1000), A1 and fixes implementation thereof. 2019-03-22 23:27:48 -04:00
Thomas Harte
ed7060a105 Made an initial stab at completing MOVEA.w.
I think I'm probably peeking into the prefetch queue incorrectly.
2019-03-22 21:43:51 -04:00
Thomas Harte
db0da4b741 Improves get/set state. 2019-03-22 19:34:17 -04:00
Thomas Harte
c9c16968bb Implements MOVEA as distinct from MOVE.
At least as far as MOVE is implemented, that is.
2019-03-22 19:25:53 -04:00
Thomas Harte
87420881c8 Extends to a failing test. 2019-03-21 23:32:03 -04:00
Thomas Harte
fdc598f2e1 Starts MOVE tests; in pursuit of which talks the 68000 into obeying run lengths. 2019-03-21 22:30:41 -04:00
Thomas Harte
f679145bd1 Makes a further push into the MOVEs.
With some quick notation shortening.
2019-03-20 23:21:02 -04:00
Thomas Harte
eeb161ec51 Converts the prefetch queue into a 32-bit quantity. 2019-03-19 21:33:52 -04:00
Thomas Harte
21cb7307d0 Adds MOVE #, Dn and MOVEA An, An.
As well as the scheduling for `(d16,PC), Dd` and `MOVE (d8,As,Xn), Dd` other than the .ls.
2019-03-19 11:53:37 -04:00
Thomas Harte
412a1eb7ee Takes an initial run at (An)+, -(An), (d16,An) and (d8,An,Xn) addressing modes.
With only MOVEs from those to a data register implemented so far.
2019-03-18 22:51:32 -04:00
Thomas Harte
1d801acf72 Switched to a better ABCD fix. 2019-03-17 22:04:32 -04:00
Thomas Harte
0d7bbdad54 Begins a basic get/set state API, allowing some actual unit tests, implying an ABCD fix. 2019-03-17 21:57:00 -04:00
Thomas Harte
53b3d9cf9d Implements a few more MOVE variants, plus MOVEA. 2019-03-17 14:34:16 -04:00
Thomas Harte
c3ebbfb10e Implements all MOVE Dn, Dn. 2019-03-16 23:14:18 -04:00
Thomas Harte
58f035e31a Makes error more communicative. 2019-03-16 23:05:12 -04:00
Thomas Harte
a8f1d98d40 Small further adjustments; seems likely to be correct now. 2019-03-16 23:01:56 -04:00
Thomas Harte
cf6fa98433 Corrects detection of terminal micro-ops. 2019-03-16 22:50:44 -04:00
Thomas Harte
937b3ca81d Attempts properly to honour the bus-op and microcycle contract. 2019-03-16 22:36:09 -04:00
Thomas Harte
d0c5cf0d2d Starts attempting to kill the need to prepare all bus step sequences in advance. 2019-03-16 21:47:46 -04:00
Thomas Harte
4cbf2bef82 By way of a friend, clears a bunch of transient stuff out of 68000Storage.hpp.
As, even if not in the programmer's eye, this does affect recompilation times.
2019-03-16 19:41:07 -04:00
Thomas Harte
388d808536 Switches to providing UDS and LDS implicitly via address.
Also makes sure that the difference between a non-data cycle that starts without the address strobe active and one that starts with it active can be discerned.
2019-03-16 17:54:58 -04:00
Thomas Harte
720aba3f2d Adds an implementation of SBCD and slightly neatens syntax for building programs. 2019-03-14 21:22:02 -04:00
Thomas Harte
f9101de956 This might very well be the 68000's first real gasp: performing an ABCD. 2019-03-14 19:32:15 -04:00
Thomas Harte
bb04981280 I'm still dithering on address management, but this seeks fully to implement ABCD and SUBD bus programs. 2019-03-13 21:08:13 -04:00
Thomas Harte
57898ed6dd This is where my thinking now resides. Two levels of indirection, and consolidated collections. 2019-03-12 22:46:31 -04:00
Thomas Harte
33b53e7605 Settles upon disassembly as the route in, and begins work in that direction. 2019-03-11 22:47:58 -04:00
Thomas Harte
9e8928aad9 Implements as much as I currently care about of the Atari ST memory map. 2019-03-11 22:47:37 -04:00
Thomas Harte
89c71f9119 Introduces EmuTOS, and starts constructing test cases around it. 2019-03-10 18:40:12 -04:00
Thomas Harte
a4f6db6719 Removes ArrayBuilderTests as the ArrayBuilder is long gone. Disables TIA tests for now. 2019-03-10 18:07:23 -04:00
Thomas Harte
2d8e65ea32 Merge branch 'master' into 68000 2019-03-10 17:56:58 -04:00
Thomas Harte
48d1d27067 Merge pull request #612 from TomHarte/HighPrecisionTimer
Sketches a high-precision timer class.
2019-03-10 17:47:20 -04:00
Thomas Harte
98aa597510 A theoretical 68000 could now perform its /RESET. That's all though. 2019-03-10 17:42:13 -04:00
Thomas Harte
de56d48b2f Embraces a more communicative 68000 bus. 2019-03-10 17:27:34 -04:00
Thomas Harte
4aeb9a7c56 Genericises RegisterPair. 2019-03-09 21:16:11 -05:00
Thomas Harte
b9b52b7c8b Begins some very early sketching out of a 68000. 2019-03-09 00:00:23 -05:00
Thomas Harte
dc464a0b7b Introduces a wrapper class for high-precision timers. 2019-03-07 22:04:29 -05:00
Thomas Harte
13b6079826 Merge pull request #609 from TomHarte/ReducedScanTargetContention
Reduces draw/update contention.
2019-03-07 19:31:35 -05:00
Thomas Harte
6f7dd10d95 Reduces draw/update contention.
This won't yet have any effect on either port owing to the way they handle contexts, but here it is.
2019-03-07 19:28:32 -05:00
Thomas Harte
24fb95291a Reverts to support a full RGBA colour buffer. 2019-03-07 19:22:40 -05:00
Thomas Harte
48430bee60 Merge pull request #606 from TomHarte/MouseHiding
Causes the Mac mouse pointer to hide after 3 seconds.
2019-03-06 22:52:40 -05:00
Thomas Harte
42997dcb80 Switches brace style, to bring this into line with other source files. 2019-03-06 21:54:21 -05:00
Thomas Harte
0ace189e38 Takes a basic stab at mouse cursor hiding. 2019-03-06 21:49:50 -05:00
Thomas Harte
d03a7911b5 Merge pull request #605 from TomHarte/DisplayMetrics
UNREADY! Introduces dynamic output quality selection.
2019-03-06 19:20:35 -05:00
Thomas Harte
84422676cb Switches to more coherent logic about buffer sizing. 2019-03-06 19:19:30 -05:00
Thomas Harte
7441e3f4c5 Corrects aspect ratio when changing accumulation texture size. 2019-03-05 22:10:32 -05:00
Thomas Harte
f18132d674 Makes effort to round out draft 1 of Outputs::Display::Metrics. 2019-03-05 22:01:58 -05:00
Thomas Harte
5660007221 Experimentally introduces adaptive quality intermediate buffers. 2019-03-05 21:41:20 -05:00
Thomas Harte
cfebf1dc4a Merge branch 'master' into DisplayMetrics 2019-03-05 20:21:44 -05:00
Thomas Harte
5b0111b4c8 Merge pull request #604 from TomHarte/AYInputOutput
Implements proper AY IO output behaviour.
2019-03-05 20:21:21 -05:00
Thomas Harte
62a1d69cee Implements proper AY IO output behaviour. 2019-03-05 20:20:26 -05:00
Thomas Harte
86a6b04d4a Begins attempts to keep track of display metrics.
i.e. a system that can both make smart decisions about when to use a lower resolution, and hopefully allow some sort of flywheel-esque horizontal retrace synchronisation. And possibly some raster beam chasing?
2019-03-04 21:54:50 -05:00
Thomas Harte
8915950c7d Merge pull request #601 from TomHarte/8ppStencil
Switches to an 8bpp stencil, for Nvidia compatibility.
2019-03-03 20:39:50 -05:00
Thomas Harte
641e349f33 Switches to an 8bpp stencil, for Nvidia compatibility. 2019-03-03 20:38:24 -05:00
Thomas Harte
72b4bf9c98 Merge pull request #600 from TomHarte/MacCrash
Reintroduces proper locking of the Mac OpenGL context.
2019-03-03 15:25:26 -05:00
Thomas Harte
ccdeb3fbc8 Ensures draw is a no-op prior to pipeline setup. 2019-03-03 15:04:14 -05:00
Thomas Harte
34047fa60a Reintroduces proper locking of the OpenGL context in macOS. 2019-03-03 14:49:20 -05:00
Thomas Harte
05d483bc5b Corrects potential machine shutdown race condition. 2019-03-02 23:17:31 -05:00
Thomas Harte
113efd9b16 Merge pull request #598 from TomHarte/SVideoColeco
Introduces S-Video support for the ColecoVision.
2019-03-02 23:03:02 -05:00
Thomas Harte
c11a1f9679 Introduces S-Video support for the ColecoVision. 2019-03-02 23:02:37 -05:00
Thomas Harte
2beeaa513b Ensures a machine exists before messaging it. 2019-03-02 21:27:34 -05:00
Thomas Harte
5b56ad0d78 Merge pull request #597 from TomHarte/MacRaceCondition
Splits OpenGL ScanTarget update and draw functions.
2019-03-02 19:36:19 -05:00
Thomas Harte
bee0d09877 Splits display update and draw functions.
On the Mac, draw is now called without an update for resizing events, and
anything else requested by AppKit. In all other cases — including from
the SDL version — both are called as if they were still a single function.
2019-03-02 19:33:28 -05:00
Thomas Harte
42d8d187b3 Merge pull request #596 from TomHarte/MSXLogging
Eliminates dangling uses of `printf`.
2019-03-02 18:23:32 -05:00
Thomas Harte
d97348dd38 Eliminates dangling uses of printf. 2019-03-02 18:07:05 -05:00
Thomas Harte
e1ebb7ce9c Ensures no attempt to call nullptr. 2019-03-02 17:37:56 -05:00
Thomas Harte
47dd8ad069 Minor grammar fix. 2019-03-02 17:31:11 -05:00
Thomas Harte
6a55d75b3d Merge pull request #595 from TomHarte/MSXTapeMotor
Fixes various MSX tape-handling bugs, and adds a status LED.
2019-03-02 15:00:03 -05:00
Thomas Harte
d5b4ddd9e5 Simplifies use_fast_tape_ logic. 2019-03-02 14:54:26 -05:00
Thomas Harte
9c8a2265b5 Breaks infinite loop where signature[0] == 0x1f but some of the rest doesn't match. 2019-03-02 14:47:52 -05:00
Thomas Harte
84d7157dfb Corrects arithmetic on raw data blocks. 2019-03-02 14:40:48 -05:00
Thomas Harte
ddce4fb46b Ensures that unexpected padding goes somewhere. 2019-03-02 14:35:16 -05:00
Thomas Harte
1ccee036c4 Switches complete logic behind CAS to wave conversion to parsing tape files. 2019-03-02 14:19:54 -05:00
Thomas Harte
ef085e3f93 MSX: introduces a tape motor LED, and limits the fast-tape hack to the BIOS. 2019-03-01 18:49:21 -05:00
Thomas Harte
3862a93ff9 Removes mapping of the equals key to break.
... because I keep pressing it by accident.
2019-02-28 21:47:12 -05:00
Thomas Harte
fc21fbd1f1 Merge pull request #594 from TomHarte/MSXRegions
Introduces region support to the MSX.
2019-02-28 21:03:34 -05:00
Thomas Harte
903f9b5240 Gives the static analyser an opinion, at least. 2019-02-28 20:59:07 -05:00
Thomas Harte
816ad0a94c Introduces region support to the MSX. 2019-02-28 20:54:43 -05:00
Thomas Harte
0536697d8f Corrects scope of delay. 2019-02-28 18:46:28 -05:00
Thomas Harte
0dbd8a667d Corrects delay for SN access. 2019-02-27 22:58:43 -05:00
Thomas Harte
56e691f256 Merge pull request #592 from TomHarte/RecursiveMultiMachine
Resolves a potential deadlock on multi machine handover.
2019-02-27 22:42:40 -05:00
Thomas Harte
b0503efa3d Resolves a potential deadlock on multi machine handover. 2019-02-27 22:39:33 -05:00
Thomas Harte
b81e59fd8f Merge pull request #591 from TomHarte/ColecoVisionM1
Adds a single-cycle M1 delay to the ColecoVision.
2019-02-27 22:02:58 -05:00
Thomas Harte
d2da55aa03 Adds a single-cycle M1 delay to the ColecoVision. 2019-02-27 22:01:30 -05:00
Thomas Harte
28e69152d8 Merge pull request #590 from TomHarte/Screenshots
Unifies the OpenGL screenshot code and corrects it for arbitrary alignment.
2019-02-27 21:14:22 -05:00
Thomas Harte
d122535a65 Unifies the OpenGL screenshot code and corrects it for arbitrary alignment. 2019-02-27 21:05:02 -05:00
Thomas Harte
c8c24f81c8 Merge pull request #581 from TomHarte/ScanTarget
Decouples output of raster scans from their generation
2019-02-27 18:52:19 -05:00
Thomas Harte
db078c7363 Minor tweak: don't start counting phase from zero.
This should ensure no first pixel issues resulting from clamping.
2019-02-27 18:51:51 -05:00
Thomas Harte
6b4f6971de Disables upper limit on frame buffer size.
Filtering is sufficiently imperfect as to make this justifiable only when performance requires it. So I need a test for that. Marked as TODO.
2019-02-26 22:39:07 -05:00
Thomas Harte
79707a3c66 Improves filtering slightly, and ensures coefficients are always set when needed. 2019-02-26 22:35:55 -05:00
Thomas Harte
694783efe9 Brings S-Video inside the group that filters luminance.
Thereby revealing some sort of error in offset selection.
2019-02-26 22:27:40 -05:00
Thomas Harte
68c5474e36 Reintroduces basic filtering for RGB mode (and introduces it for monochrome composite). 2019-02-26 22:21:49 -05:00
Thomas Harte
cd055a0298 Introduces an upper bound on output resolution, and resolves full-screen clear colour. 2019-02-25 22:07:48 -05:00
Thomas Harte
8f2abab0d9 Ensures texture targets are initially clear. 2019-02-25 21:55:14 -05:00
Thomas Harte
4c5dee866b Ensures a proper black fill for Luminance8Phase8 input data. 2019-02-25 21:32:15 -05:00
Thomas Harte
7030abca97 Corrects PAL colours for the Vic-20. 2019-02-25 19:28:52 -05:00
Thomas Harte
c7c21a7e2c Sorry, ColecoVision, it's composite only for you. 2019-02-24 22:37:24 -05:00
Thomas Harte
b23e10e261 Improves error messaging and avoids trying to use a null window. 2019-02-24 22:31:59 -05:00
Thomas Harte
16731661e8 Switches back to being explicit about the colour burst phase.
Some sort of phase inversion otherwise seems to be achievable by software that switch modes often.
2019-02-24 22:28:11 -05:00
Thomas Harte
7bb90c78d9 Resolves out-of-bounds initial condition whenever this loop began with start_line = 2047.
This, I believe, was the remaining cause of screen flashes.
2019-02-24 19:50:19 -05:00
Thomas Harte
a6e61ef83b Reverts the clear colour to black.
The change was related to debugging; it was not intentionally committed.
2019-02-24 14:36:08 -05:00
Thomas Harte
d4134cd0d8 Restores proper colour phase to the Apple II.
Given that its timing errors were fixed, this also switches back to using 'default' colour bursts — i.e. ones with implicit phase. The Apple II continues to be an excellent bellwether for issues in the pipeline, and this helps further to ensure that.
2019-02-24 14:35:13 -05:00
Thomas Harte
c775a6c0f8 Introduces but disables a couple of bits of logging that might be helpful again in the future. 2019-02-24 14:30:39 -05:00
Thomas Harte
2f9e825728 Forces the outward-communicated composite angles to have the same precision as the cycle counts.
While also making a minor improvement to output range. Which is neither here nor there.
2019-02-24 14:29:43 -05:00
Thomas Harte
2f491b5be1 Reintroduces fragment snapping for composite colour sampling.
Thereby uncovers some sort of slightly-off recording of scan lines. On the Apple II, individual scans reach the ScanTarget at a density of exactly 0.25 colour cycles per pixel. So that timing information propagates exactly. But the whole lines that are composed via ::announce end up trying to fit 0.250154 colour cycles per pixel. Which creates a phase error as the display progresses from left to right.

This will need to be resolved in order to be able to fix the Apple II's intended colour phase. But, also, it's probably what was wrong with the Oric. And, quite possibly, why the single-step shader didn't work.
2019-02-24 13:39:14 -05:00
Thomas Harte
de7ebead23 Ensures the line_allocation_has_failed_ condition can be exited. 2019-02-21 22:30:41 -05:00
Thomas Harte
c0c4704419 Ensures that failure to allocate a line blocks all other allocations. 2019-02-21 21:38:48 -05:00
Thomas Harte
ec14750ff1 Minor text improvement. 2019-02-20 22:32:42 -05:00
Thomas Harte
e43de5f1ba Allows for failure to get a GL context as a reportable issue. 2019-02-20 22:06:22 -05:00
Thomas Harte
080f949f89 Ensures OpenGL version is logged prior to any other GL calls. 2019-02-20 20:21:17 -05:00
Thomas Harte
9f6956bd87 Awards default values to Scan, to appease GCC 7.3. 2019-02-19 21:40:42 -05:00
Thomas Harte
ddf5e1632d Ensures log memory is automatically initialised. 2019-02-18 22:08:03 -05:00
Thomas Harte
40bfde41cb Adds an OpenGL version shout out. 2019-02-18 22:01:56 -05:00
Thomas Harte
e0751af56d Handles the 0 return case. 2019-02-18 21:56:49 -05:00
Thomas Harte
3979faf43b Gets more explicit about potential causes of failure. 2019-02-18 21:53:35 -05:00
Thomas Harte
878b480a44 Tidies up marginally. 2019-02-18 21:37:07 -05:00
Thomas Harte
b35b6b2ba8 Resolves a couple of missing #includes for cassert. 2019-02-18 21:29:39 -05:00
Thomas Harte
d0b967ce53 Corrects typo; disables original colour ROM usage for now. 2019-02-18 20:49:54 -05:00
Thomas Harte
e5addb27ec Corrects log output. 2019-02-18 20:49:01 -05:00
Thomas Harte
ac8d43cc4a Improves use of const. 2019-02-18 20:21:41 -05:00
Thomas Harte
40ee215b1b By #define provides a means not to use the real composite samples.
To aid with debugging.
2019-02-18 17:20:52 -05:00
Thomas Harte
6c1d94beaa Adds composite monochrome output for the Oric. At least temporarily. 2019-02-18 16:56:48 -05:00
Thomas Harte
6b2e1fe62b Makes error reporting more communicative. 2019-02-18 11:13:54 -05:00
Thomas Harte
8ecf885629 Attempts to put in better OpenGL safety rails. 2019-02-18 10:29:40 -05:00
Thomas Harte
6d76b7cd94 Attempts to ensure proper colour output during alternating PAL lines. 2019-02-17 21:50:15 -05:00
Thomas Harte
7bd721f334 Resolves improper state if an end-of-frame clear is triggered by the first new line. 2019-02-17 21:49:53 -05:00
Thomas Harte
7939897622 Fixes announced timing difference between pixel and border lines.
The Apple II sync fault is now fixed!
2019-02-12 22:32:02 -05:00
Thomas Harte
77bebd4a65 Accounts for periods near an expected sync after a sync actually occurred. 2019-02-12 22:30:40 -05:00
Thomas Harte
5d68a5bdd0 Merge pull request #588 from TomHarte/SeparateChromaBuffer
Reintroduces a separate chrominance buffer
2019-02-12 19:52:54 -05:00
Thomas Harte
3e0b5433b9 Institutes colour/monochrome screen selection as an Apple II option.
Allowing me to test that straight-through composite still works.
2019-02-12 19:52:32 -05:00
Thomas Harte
ec8f1157c8 Corrects S-Video output. 2019-02-12 19:31:12 -05:00
Thomas Harte
037cbd534e Corrects phase error in chrominance separation. 2019-02-12 19:24:28 -05:00
Thomas Harte
208ef70e31 Corrects documentation. 2019-02-12 18:55:58 -05:00
Thomas Harte
2fa4c59523 Correction: use the QAM texture for colours. 2019-02-12 18:42:28 -05:00
Thomas Harte
cda0a2de79 Establishes QAM colour buffer lookups within the composite colour path.
Subject to errors in channel scaling and absolute position.
2019-02-10 23:02:31 -05:00
Thomas Harte
008f50832c Fixed: the two shaders that use a common input array should use common bindings. 2019-02-10 22:39:24 -05:00
Thomas Harte
c94acb1ca2 With a little more debug logging, discovered an issue with incrementing by four. 2019-02-09 22:45:20 -05:00
Thomas Harte
d341f98b09 Corrects horizontal scale. 2019-02-09 18:52:43 -05:00
Thomas Harte
e35a3ab566 Ensures proper uniforms and varyings for the qam_separation_shader. 2019-02-09 18:35:14 -05:00
Thomas Harte
b3b4b7cf0c Corrects QAM texture generation logic. 2019-02-09 17:20:13 -05:00
Thomas Harte
1cd6d58f17 Restores S-Video through line, as monochrome. 2019-02-09 17:13:43 -05:00
Thomas Harte
eecd4417e7 Bites the bullet and accepts that an additional texture will be useful for QAM separation. 2019-02-09 16:54:31 -05:00
Thomas Harte
21908dfcef Restores Oric audio. 2019-02-05 21:43:07 -05:00
Thomas Harte
75987f64ec Restores Oric audio. 2019-02-05 21:42:39 -05:00
Thomas Harte
798cc58f76 Simplifies the composite colour shader no longer to handle colour. 2019-02-05 19:22:35 -05:00
Thomas Harte
6ba1194d74 Sets a clear colour appropriate for phase-linked luminance clearing. 2019-02-03 22:33:04 -05:00
Thomas Harte
e5f75b5df2 Resolves repetition between svideo_sample and composite_sample. 2019-02-03 22:09:16 -05:00
Thomas Harte
b75ad3def2 Updates the multimachine for the ScanTarget world. 2019-02-03 15:07:22 -05:00
Thomas Harte
10c98f0a15 Switches TapeUEF to using LOG.
Reducing console noise for release builds.
2019-02-02 22:30:10 -05:00
Thomas Harte
caf72afcb4 Switches to a seven-point scheme, to determine whether falsely-shared luminance is at fault.
It doesn't seem to be, alas.
2019-01-31 21:19:30 -05:00
Thomas Harte
687e0b376e Enhances error checking around setting of uniforms. 2019-01-31 21:17:49 -05:00
Thomas Harte
122857e5b5 Improves automatic index generation, to allow for matrices implicitly taking up to four slots. 2019-01-31 18:49:01 -05:00
Thomas Harte
5002290428 Makes consistent use of textureLod rather than texture. 2019-01-26 22:05:15 -05:00
Thomas Harte
d09ac3384f Eliminates some old-school manual memory management.
In favour of additional copying, but I still think this is safer.
2019-01-25 22:54:23 -05:00
Thomas Harte
b6a4a7e0a5 This is no longer TODO. 2019-01-25 22:47:15 -05:00
Thomas Harte
c87994336c Switches the Shader class to using LOG. 2019-01-25 22:45:47 -05:00
Thomas Harte
85ad490089 Offers a less error-prone route to attribute binding. 2019-01-25 21:56:55 -05:00
Thomas Harte
73e32a9c76 Adds a missing directory. 2019-01-25 20:26:20 -05:00
Thomas Harte
a321ff3037 Adds some default values. 2019-01-25 20:21:24 -05:00
Thomas Harte
68d6feaa03 Adds missing includes and gets more explicit about exceptions. 2019-01-25 20:19:50 -05:00
Thomas Harte
74e1a9a621 Declines improper use of offset within loops and adds missing header. 2019-01-25 20:14:53 -05:00
Thomas Harte
097bc7055e Adds a default selection, for invalid models. 2019-01-25 19:31:44 -05:00
Thomas Harte
6a43fc5df0 Resolves a GCC-troubling circular declaration issue vs. atomic.h. 2019-01-25 19:30:39 -05:00
Thomas Harte
312f38906b Corrects two improper include paths. 2019-01-25 19:19:23 -05:00
Thomas Harte
f0ec9fa5d2 Updates the SConstruct file for new Outputs. 2019-01-25 19:11:57 -05:00
Thomas Harte
20b4896940 Eliminates the dead stuff of CRTConstants.hpp. 2019-01-25 19:11:39 -05:00
Thomas Harte
6a93d2d006 Corrects some minor spaces-instead-of-tabs errors. 2019-01-24 22:59:03 -05:00
Thomas Harte
ae0bc7e7aa Calculates sampling offsets up front. 2019-01-23 20:53:10 -05:00
Thomas Harte
a8acadbe13 Gives the shader builders freer rein over what to use as inputs, and turns angles into a varying.
All dropping out during the never-ending diagnosis at play here.
2019-01-22 22:20:12 -05:00
Thomas Harte
727f2e2ba0 Updates to the ScanTarget world. 2019-01-17 22:28:02 -05:00
Thomas Harte
a6683cb9b8 Avoids scaling luminance prior to extracting chrominance. 2019-01-17 20:52:33 -05:00
Thomas Harte
5ceb711bd3 Allows amplitude to be specified even for a default colour burst. 2019-01-17 20:47:42 -05:00
Thomas Harte
4748b09721 Ensures safe OpenGL shutdown. 2019-01-17 20:44:18 -05:00
Thomas Harte
d593796dae Reintroduces less-filtered black and white video where there's no colour burst. 2019-01-16 22:22:29 -05:00
Thomas Harte
ef0dbc2a41 Undoes hard-coding of target framebuffer and display gamma. 2019-01-15 21:33:30 -05:00
Thomas Harte
6c49953115 Returns gamma correction, and corrects Amstrad CPC brightness. 2019-01-14 22:56:08 -05:00
Thomas Harte
55290f4dad Attempts a fix of frame_was_complete_ logic, to try to eliminate black flashes. 2019-01-14 21:42:45 -05:00
Thomas Harte
f373a3fbb1 Merge branch 'TrigonometricDecode' into ScanTarget 2019-01-13 23:08:21 -05:00
Thomas Harte
bb03d2f2ad Removes redundant enumeration. 2019-01-13 23:07:50 -05:00
Thomas Harte
82922aa2c7 Merge pull request #585 from TomHarte/TrigonometricDecode
Collapses video pipeline down to two stages.
2019-01-13 23:07:09 -05:00
Thomas Harte
7aec5be61a Cleans up and simplifies shader creation. 2019-01-13 22:49:01 -05:00
Thomas Harte
2ef6d4327c Resolves further build warnings. 2019-01-13 20:37:50 -05:00
Thomas Harte
cc95e587db Adds virtual destructors for various interface classes. 2019-01-13 19:19:01 -05:00
Thomas Harte
e89e55a9bb Attempts to factor actual composite amplitude into output. 2019-01-13 14:45:17 -05:00
Thomas Harte
7c2c243985 Corrects sample spacing, and removes a lot of detritus. 2019-01-12 18:36:54 -05:00
Thomas Harte
25a1f23fc0 Takes a first shot at re[re,re]-implementing composite colour decoding. 2019-01-12 17:59:24 -05:00
Thomas Harte
27541196cc Corrects Luminance8Phase8 and PhaseLinkedLuminance8 composite encodings. 2019-01-11 22:46:50 -05:00
Thomas Harte
5d9521fcb9 Advances back to a semi-complete monochrome composite output.
i.e. composite phase and amplitude is ostensibly flowing to its new destination.
2019-01-11 22:02:15 -05:00
Thomas Harte
ccb52fb625 Ensures no writes to pixel_pointer_ when allocation has failed. 2019-01-11 22:00:44 -05:00
Thomas Harte
028e530232 Shunts output shader to its proper place. 2019-01-06 22:59:14 -05:00
Thomas Harte
906a2ff6eb Switches to using clock times for buffer merging and output. 2019-01-06 18:47:01 -05:00
Thomas Harte
248a8efd2f Corrects declared pixel clock GCD. 2019-01-06 16:32:13 -05:00
Thomas Harte
c392c819c1 Switches to using the announce is_visible flag to spot line ends. 2019-01-06 13:37:34 -05:00
Thomas Harte
e9d9ff0da0 Enhances ScanTarget to provide additional timing information. 2019-01-05 23:09:17 -05:00
Thomas Harte
46d756d298 Starts towards a flattening of the intermediate video processing.
Immediate issue: using x position to index into a bitmap sampled at the input data rate doesn't allow for the disconnection between input rate and output speed provided by the flywheels.
2019-01-05 18:11:39 -05:00
Thomas Harte
fd0ffc7085 Attempts an initial flattening of the pipeline, seemingly losing all output. 2019-01-01 21:02:21 -05:00
Thomas Harte
601961deeb Wires through set_display_type. 2018-11-29 20:44:21 -08:00
Thomas Harte
557a2a0ddf Moves pipeline setup into draw(), where there'll definitely be an OpenGL context. 2018-11-29 19:41:54 -08:00
Thomas Harte
b723740f64 Improves PAL colours. 2018-11-29 19:12:20 -08:00
Thomas Harte
6be46ae921 Mostly restores Atari 2600 output. PAL colours need work. 2018-11-29 18:26:05 -08:00
Thomas Harte
a25470ee41 Permits tweaking of PhaseLinkedLuminance8 sampling offset. 2018-11-29 16:29:28 -08:00
Thomas Harte
fd579a019b Introduces a new scan source data type, motivated by the reasoning used by the Oric.
Specifically: it'll allow PCM sampling of the potentially arbitrary composite generation logic of various machines.
2018-11-28 20:40:22 -08:00
Thomas Harte
e39ecf59ef Restores RGB mode to the Oric. More thought required for composite. 2018-11-28 18:40:43 -08:00
Thomas Harte
5f90941e4e Starts nudging the Oric back to functionality under the new regime.
i.e. one where it can't invent internal pixel formats.
2018-11-28 18:16:13 -08:00
Thomas Harte
64465f97b6 Starts towards reintroducing the proper mechanisms for selecting a display type at runtime. 2018-11-28 17:53:33 -08:00
Thomas Harte
aa22af6f05 Corrects regression in VDP type selection. 2018-11-26 22:40:01 -05:00
Thomas Harte
a6383247fc Attempts further to ensure proper CRT signalling. 2018-11-26 22:36:22 -05:00
Thomas Harte
d45c2a1f28 Settles, at least for now, on 15-tap notch filtering. 2018-11-26 22:34:31 -05:00
Thomas Harte
61a63a673c Adds a negative operator. 2018-11-26 22:34:04 -05:00
Thomas Harte
5618288459 Reduces visible area, producing a tighter crop. 2018-11-25 22:32:12 -05:00
Thomas Harte
b69ac4ec2f Ensures video stability is no longer affected by transient allocation failures. 2018-11-25 22:04:04 -05:00
Thomas Harte
f3174069fa Attempts a linear comb filter for YC separation, plus post-separation Y filtering. 2018-11-25 21:54:12 -05:00
Thomas Harte
cd1e796093 Attempts to add clearing of the destination framebuffer too. 2018-11-24 23:31:56 -05:00
Thomas Harte
dd4af4f0df Removes dead files. 2018-11-24 22:40:06 -05:00
Thomas Harte
76656fab23 Applies harsher filtering. 2018-11-24 22:39:53 -05:00
Thomas Harte
cf49603a9e Makes first reintroduction of colour composite decoding. 2018-11-24 22:30:39 -05:00
Thomas Harte
6c92853461 Corrects monochrome composite generation. 2018-11-24 21:55:15 -05:00
Thomas Harte
6a62cf9146 Corrects shader generation for S-Video input to S-Video output. 2018-11-24 21:40:34 -05:00
Thomas Harte
4fa6bc0ad1 Corrects S-Video decoding for most machines.
Ironically, that being those other than luminance/chrominance input machines. Further investigation required.
2018-11-24 21:30:09 -05:00
Thomas Harte
95685749ad Attempts fully to implement the S-Video pipeline, without success. 2018-11-24 18:51:07 -05:00
Thomas Harte
d7c0f0c804 Switches to an ordinary sampler for scan processing. 2018-11-24 18:03:44 -05:00
Thomas Harte
6b42b92930 Kills CRTOpenGL.cpp and simplifies shader output very slightly. 2018-11-24 17:37:58 -05:00
Thomas Harte
f4764ea680 Fixes divider. 2018-11-24 16:56:41 -05:00
Thomas Harte
538c57664f Establishes attribute bindings to allow multiple shaders to use the same vertex array. 2018-11-24 16:06:26 -05:00
Thomas Harte
a66a20f7fe Manages to get a brilliant white out of the new pipeline. 2018-11-23 22:54:52 -05:00
Thomas Harte
d4ac79b0af Attempts to introduce a full-on processing pipeline, in theory putting me two shaders away from completion.
Well, subject to finding the last flashing bug and updating the multimachine, anyway.
2018-11-23 22:34:38 -05:00
Thomas Harte
a5a3769a0f Reaches for conceptual const correctness. 2018-11-23 22:33:28 -05:00
Thomas Harte
dc4b5cc37d Effects DefaultAttenuation as an explicit default. 2018-11-23 22:33:01 -05:00
Thomas Harte
ee89be6730 Removes many stray spaces. 2018-11-23 22:32:32 -05:00
Thomas Harte
770d7e90e9 Removes stale sampling functions. 2018-11-22 22:47:29 -05:00
Thomas Harte
b9aca39eb0 Reintroduces Vic-20 output.
Resolving errors in shader generation while I'm here.
2018-11-22 22:43:42 -05:00
Thomas Harte
c0454ff101 Corrects chrominance scale. 2018-11-22 18:18:16 -05:00
Thomas Harte
a697a2e4f6 Attempts to complete all input processing — an RGB, S-Video or composite input buffer is now produced.
... for all input data types.
2018-11-22 17:20:31 -05:00
Thomas Harte
396cf72029 Renames OutputType as DisplayType and promotes it to a scan target modal. 2018-11-22 14:36:46 -05:00
Thomas Harte
bfe9704829 Reintroduces respect of each machine's nominated visible area. 2018-11-22 13:22:04 -05:00
Thomas Harte
43ee540233 Avoids race condition on .is_first_in_frame 2018-11-21 18:27:04 -05:00
Thomas Harte
817aa186c2 Revokes 'synchronous' as a function of onlyIfDirty, as it doesn't allow for double buffering. 2018-11-20 22:00:40 -05:00
Thomas Harte
38ffc4fdb3 Invalidates the stencil buffer upon buffer resizes. 2018-11-20 19:51:11 -05:00
Thomas Harte
f12d734957 Disables multisampling, since there's no way it's being helpful. 2018-11-19 23:36:29 -05:00
Thomas Harte
a70991d50e Eliminates minor gap. 2018-11-19 23:35:12 -05:00
Thomas Harte
4c00456166 Makes first attempt to draw only new lines. 2018-11-19 23:25:26 -05:00
Thomas Harte
26219213d7 Marginally increases scan size. 2018-11-18 23:03:56 -05:00
Thomas Harte
97c5ee6c0a Corrects stencil buffer creation, and edges towards using it for [guaranteed] full-screen decay. 2018-11-18 22:22:43 -05:00
Thomas Harte
75bc0e451d Reintroduces the accumulation texture.
Disables automatic clearing of the texture target, as the profiler indicates the vector instantiation to be a huge time sink.
2018-11-18 21:39:11 -05:00
Thomas Harte
6496b6313c Attempts to fix random stray noise lines. 2018-11-17 23:27:25 -05:00
Thomas Harte
c5d9bf2c12 Optimises slightly for black borders.
Specifically to help to debug proper display of unused lines in the new scan target.
2018-11-17 18:23:42 -05:00
Thomas Harte
8f05560dd7 Corrects right-edge bookending. 2018-11-17 17:46:57 -05:00
Thomas Harte
06c0c64c1a Shifts intermediate buffer sampling into the middle of each pixel row. 2018-11-17 17:31:32 -05:00
Thomas Harte
c173777d12 Extends TextureTarget so that targets can be created with a one-bit stencil. 2018-11-17 15:51:12 -05:00
Thomas Harte
16dfeb3fc8 Discards empty lines, yet makes some attempt at restoring transparency.
The two things conflict more than a little, so work to do.
2018-11-15 21:51:27 -05:00
Thomas Harte
5a31891048 Returns Amstrad CPC output.
Which is probably it until I get some more composite processing back in.
2018-11-15 21:32:22 -05:00
Thomas Harte
8b37496447 Restores video output to the Master System. 2018-11-15 21:21:54 -05:00
Thomas Harte
8f6664f0d7 Starts towards picking an input shader based on data type and pipeline. 2018-11-15 21:02:46 -05:00
Thomas Harte
15b1176841 Ensures no border output if space is not allocated. 2018-11-14 22:32:33 -05:00
Thomas Harte
3eab1f8f7c Removes a little cruft. 2018-11-14 22:26:31 -05:00
Thomas Harte
9dff13cbbf Re-establishes output from the machines with 9918s and derivatives. 2018-11-14 22:25:19 -05:00
Thomas Harte
a47de9a884 Returns the Apple II to submitting video. 2018-11-14 22:04:57 -05:00
Thomas Harte
8a699b6072 Kills setup_output definitively, saving some indirection. set_scan_target takes its place. 2018-11-14 21:52:57 -05:00
Thomas Harte
87df8b9e85 Makes an attempt at pre-emptive line buffer clearing. 2018-11-14 21:19:14 -05:00
Thomas Harte
91b19c5c70 Adds bookending, and finally kills the TextureBuilder. Farewell. 2018-11-14 20:49:06 -05:00
Thomas Harte
0487580a1a Corrects initial state of is_drawing_ and expands lines to full display. 2018-11-14 20:10:38 -05:00
Thomas Harte
3dca836571 Ensures no overflow, and adds a couple of consts. 2018-11-14 20:09:57 -05:00
Thomas Harte
6ba02c44d0 Better binds buffer sizes. 2018-11-13 23:08:51 -05:00
Thomas Harte
bf3ab4e260 Proceeds as drawing to the unprocessed line buffer and drawing from it.
Very, very slowly, and without yet clearing.
2018-11-13 21:15:33 -05:00
Thomas Harte
02f9cada43 Communicates the colour subcarrier frequency, and uses it to pick a buffer width. 2018-11-13 18:33:44 -05:00
Thomas Harte
654a19ea15 Switches back to working on the scan shaders.
Pixels from the emulated machine are now starting to appear.
2018-11-12 22:52:26 -05:00
Thomas Harte
ecb5504bd1 Switches enable_vertex_attribute_with_pointer to silent failure (versus glGetError). 2018-11-12 22:51:44 -05:00
Thomas Harte
2adf3d353e Subtracts retrace periods from output scale. 2018-11-12 20:20:09 -05:00
Thomas Harte
3045e85004 Ensures redraws when resizing; declines to busy wait otherwise. 2018-11-12 20:15:38 -05:00
Thomas Harte
e9d1afd515 Appears to demonstrates that the line buffer is approximately working. 2018-11-12 19:10:48 -05:00
Thomas Harte
833ab7945b Slow steps towards switching to line output. 2018-11-12 18:56:54 -05:00
Thomas Harte
0af1d668a6 Takes a first step towards generality, and thereby starts submitting lines. 2018-11-12 18:47:55 -05:00
Thomas Harte
0ac62e3805 Flips and properly sizes output scans. 2018-11-12 18:28:09 -05:00
Thomas Harte
938d09f34a Corrects scan outline generation. 2018-11-12 18:23:45 -05:00
Thomas Harte
dce52d740d Finally gets some pixels back on screen.
For now, just the raw scans, direct to the framebuffer, with no intermediate processing. But it seems to prove that at least some of the proper data is reaching the GPU.
2018-11-11 23:23:42 -05:00
Thomas Harte
3ae333fa84 Edges further towards reviving the shaders. 2018-11-11 21:41:13 -05:00
Thomas Harte
d5af1f3948 Removes some migrated work. 2018-11-11 16:22:14 -05:00
Thomas Harte
0ba3ae53ab Connects up the necessary recording to use intermediate composite buffers. 2018-11-11 15:20:18 -05:00
Thomas Harte
be12d78c83 Corrects vertical event announcement, and adjusts namespaces for OpenGL primitives. 2018-11-11 15:11:32 -05:00
Thomas Harte
b70227ac1b Ensures proper write area locations end up in the scans. 2018-11-10 21:10:33 -05:00
Thomas Harte
6d277fecd5 Makes ScanTarget a little more communicative and orthogonal. 2018-11-10 19:52:57 -05:00
Thomas Harte
491817d85c Corrects allocation error and begins submitting raw textures. 2018-11-08 23:02:36 -05:00
Thomas Harte
20faf4e477 Adds submission of scans to the GPU. 2018-11-08 22:21:11 -05:00
Thomas Harte
4fe5c7c24e Conspires to handle multithreading side of things in a lockless fashion.
At least on x86-64.
2018-11-08 21:57:28 -05:00
Thomas Harte
36bf640c6f Acts as if it is going to submit scans, at least. 2018-11-07 22:53:46 -05:00
Thomas Harte
7881e40e0b Shuffles the OpenGL primitives into their own collection. 2018-11-07 19:11:01 -05:00
Thomas Harte
55da1e9c0f Simplifies semantics a little and starts accepting a single buffer of pixel data. 2018-11-06 22:23:38 -05:00
Thomas Harte
9799aa0975 Completes documentation and rounds out implementation. 2018-11-04 22:17:33 -05:00
Thomas Harte
1effb97b74 Reintroduces colour phase acquisition from the colour burst. 2018-11-04 21:57:46 -05:00
Thomas Harte
eb28095041 Ensures proper accumulation and reporting of colour phase across lines. 2018-11-04 21:44:22 -05:00
Thomas Harte
014da41471 Ensures scan positions are communicated with a specified range, and switches manner of pixel clock communication. 2018-11-04 21:06:25 -05:00
Thomas Harte
0446e350d3 Resolves sizing of texture coordinates, and improves constness slightly. 2018-11-03 23:51:26 -04:00
Thomas Harte
05fb7db147 Reduces CRT chattiness. 2018-11-03 23:47:41 -04:00
Thomas Harte
f6562de325 Possibly adds enough for the Electron and ZX80 to start outputting dummy lines.
Let's see!
2018-11-03 23:40:39 -04:00
Thomas Harte
b40211d2c0 Starts to bend 'CRTMachine' to a world farther from owning the GPU relationship. 2018-11-03 21:54:25 -04:00
Thomas Harte
da4d883321 Adds first, incomplete attempts to talk to a ScanTarget from the CRT.
Does away with the hassle of `unsigned` while I'm here; that was a schoolboy error.
2018-11-03 19:58:44 -04:00
Thomas Harte
373820f080 Attempts to establish interface to decouple scan output from generation.
Restores some functionality that had dropped out in the interim: diagonal scans, decoupling of scan scaling from timing of the composite subcarrier.
2018-10-30 21:50:35 -04:00
Thomas Harte
6e517983b9 Merge branch 'master' into ScanTarget 2018-10-30 18:15:55 -04:00
Thomas Harte
b9a752fda1 Merge pull request #580 from TomHarte/NameInitialisation
Ensures offset and flags are initialised to 0.
2018-10-29 22:10:28 -04:00
Thomas Harte
f65d80b7d1 Ensures offset and flags are initialised to 0.
This prevents a potential crash at startup.
2018-10-29 22:09:32 -04:00
Thomas Harte
4701aa149a Adds first draft of an interface to separate CRT logic from the GPU-side stuff. 2018-10-29 22:08:17 -04:00
Thomas Harte
0d051502e2 Merge pull request #579 from TomHarte/MasterSystemOfficial
Promotes the Master System to full mention.
2018-10-26 21:25:09 -04:00
Thomas Harte
2e28a8e51c Promotes the Master System to full mention. 2018-10-26 21:24:40 -04:00
Thomas Harte
4af0b74a42 Merge pull request #578 from TomHarte/SMSBIOSFallback
Attempts to carry on even if no BIOS is found.
2018-10-26 21:20:43 -04:00
Thomas Harte
d1fc39d6e5 Attempts to carry on even if no BIOS is found. 2018-10-26 21:19:16 -04:00
Thomas Harte
4f0d324a6b Merge pull request #577 from TomHarte/9918RandomStart
(Mostly) randomises the 9918 start position.
2018-10-26 21:10:54 -04:00
Thomas Harte
8652d8b23d (Mostly) randomises the 9918 start position. 2018-10-26 21:02:56 -04:00
Thomas Harte
e02aa885d8 Testing against the ColecoVision suggests this is probably always 7. 2018-10-26 20:59:12 -04:00
Thomas Harte
1fc9356796 Merge pull request #576 from TomHarte/CRAMDots
Adds display of CRAM dots and enforces VRAM delays.
2018-10-26 20:32:00 -04:00
Thomas Harte
bb09762029 Introduces extra delays to VRAM access. 2018-10-26 20:19:08 -04:00
Thomas Harte
05a5c7120e Shunts CRAM dots into their proper place. 2018-10-26 20:06:51 -04:00
Thomas Harte
521d603902 Adds a first attempt at CRAM dot output. With a TODO. 2018-10-26 19:26:46 -04:00
Thomas Harte
916710353a Makes it explicit that I want the reference. 2018-10-25 23:18:34 -04:00
Thomas Harte
53b00dea3f Adds missing include. 2018-10-25 23:12:41 -04:00
Thomas Harte
0587b9f257 Edges to within millimetres of CRAM dots.
... but all the way up to bedtime.
2018-10-25 23:12:03 -04:00
Thomas Harte
9621ba59ae Merge pull request #574 from TomHarte/MulticolourMode
Fixes broken implementation of 9918 multicolour mode.
2018-10-24 22:41:18 -04:00
Thomas Harte
5accd8cf08 Fixes broken implementation of 9918 multicolour mode. 2018-10-24 22:40:38 -04:00
Thomas Harte
38c130df2b Merge pull request #573 from TomHarte/SmallKeyboard
Extends the concept of a 'keyboard' to sets of keys less than a full keyboard in size
2018-10-24 22:32:55 -04:00
Thomas Harte
8730ffb4e2 Restores multi-machine keyboard propagation. 2018-10-24 22:20:58 -04:00
Thomas Harte
a8645f80bf Introduces 'non-exclusive' emulator-space keyboards.
i.e. sets of keys that don't amount to an entire keyboard in the modern sense. Experimentally used by the Master System for its reset key.
2018-10-24 21:59:30 -04:00
Thomas Harte
278585fd94 Merge pull request #572 from TomHarte/TallModeSprites
Fixes sprite list termination in 224- and 240-line modes.
2018-10-24 19:56:42 -04:00
Thomas Harte
d61c3a9442 Fixes sprite list termination in 224- and 240-line modes. 2018-10-24 19:53:46 -04:00
Thomas Harte
2cdeaa2575 Moves misplaced bracket. 2018-10-23 22:37:19 -04:00
Thomas Harte
286783e880 Accepts GCC's suggestion of extra clarity brackets. 2018-10-23 22:36:23 -04:00
Thomas Harte
f7b0f1af70 Merge pull request #571 from TomHarte/DisplaySelection
Adds composite/RGB selection for the Master System.
2018-10-23 22:32:59 -04:00
Thomas Harte
f69cb28933 Reverts accidental project configuration change. 2018-10-23 22:32:05 -04:00
Thomas Harte
e3fd63b2d7 Adds composite/RGB selection for the Master System. 2018-10-23 22:30:24 -04:00
Thomas Harte
6cb956d1d6 Merge pull request #570 from TomHarte/TecToyEtc
Separates request for an SMS2 VDP from current graphics mode.
2018-10-23 22:20:11 -04:00
Thomas Harte
00e7958a97 Separates request for an SMS2 VDP from current graphics mode.
Thereby fixes various minor segments of Codemasters games.
2018-10-23 22:19:45 -04:00
Thomas Harte
cba8e6814f Merge pull request #569 from TomHarte/224px
Adds 'full' support for 224- and 240-line SMS modes
2018-10-23 21:22:19 -04:00
Thomas Harte
2f995eb622 Adjusts vertical timing for display height. 2018-10-23 21:20:44 -04:00
Thomas Harte
90fbad0f1c Implements SMS2-style addressing if in a 224 or 240-line mode.
This isn't quite accurate, but it'll do for development.
2018-10-23 20:30:08 -04:00
Thomas Harte
2cbd28478d Allows the sprite terminator to be specified. 2018-10-23 20:01:47 -04:00
Thomas Harte
7eeefd2602 Ensures LOGs look like statements even in release builds. 2018-10-22 22:37:11 -04:00
Thomas Harte
1331457314 Merge pull request #567 from TomHarte/VDPDelay
Slightly adjusts pixel output time.
2018-10-22 22:10:47 -04:00
Thomas Harte
7855145ebd Slightly adjusts pixel output time.
i.e. respective to reading; sprite collision times now seem correct.
2018-10-22 19:58:33 -04:00
Thomas Harte
6ab30e9cac Adds a mention of the Master System.
Given that Mac users are only one constituency now; others are directly tracking the repository.
2018-10-22 13:47:43 -04:00
Thomas Harte
027e9c7816 Merge pull request #563 from TomHarte/ResizeCrash
Corrects likely crash shortly after starting a TMS9918 or derivative
2018-10-22 10:19:20 -04:00
Thomas Harte
7c65cfd932 Adds default values for WriteArea. 2018-10-21 21:18:54 -04:00
Thomas Harte
883680731a Uses explicit state to determine whether a pixel target has been requested. 2018-10-21 21:18:41 -04:00
Thomas Harte
fb3171f366 Merge pull request #562 from TomHarte/TimingTweaks
Corrects residual Master System interrupt timing issues.
2018-10-21 18:47:25 -04:00
Thomas Harte
c07f9fed99 Corrects test and implementation to pass the exhaustive VDP interrupt prediction test. 2018-10-21 18:42:49 -04:00
Thomas Harte
616777517d Makes the failing test more communicative, in the hope of more easily debugging errors. 2018-10-21 14:35:44 -04:00
Thomas Harte
b3f1677da5 Introduces new failing test for rational continuous interrupt prediction. 2018-10-21 13:59:14 -04:00
Thomas Harte
16f08eb654 Slightly tweaks Master System timing numbers. 2018-10-21 13:58:34 -04:00
Thomas Harte
a38974ef2e Merge pull request #559 from TomHarte/RowPhase
Corrects row reporting for modes other than 192-line NTSC
2018-10-20 18:28:26 -04:00
Thomas Harte
725b364bbc Improves testing; now tests for time to the first interrupt. 2018-10-20 18:25:55 -04:00
Thomas Harte
30b99f0049 Fixes a couple of interrupt prediction errors. 2018-10-20 18:25:28 -04:00
Thomas Harte
b61de65b43 Restores proper phase with the CPU. 2018-10-19 23:18:16 -04:00
Thomas Harte
0822c96ce0 Implements the proper row counter values for > 192 row modes. 2018-10-19 22:37:56 -04:00
Thomas Harte
3b164e5ffe Adds missing #includes. 2018-10-19 22:20:23 -04:00
Thomas Harte
d5f1e76707 Merge pull request #558 from TomHarte/CodemastersDetection
Implements the Codemasters paging scheme
2018-10-19 22:11:33 -04:00
Thomas Harte
f49718e94b Ensures Codemasters games have the proper initial state. 2018-10-19 22:10:14 -04:00
Thomas Harte
b18db78cce Merge pull request #557 from TomHarte/MasterSystemScreenshot
Sneaks in a Master System screenshot.
2018-10-19 21:57:07 -04:00
Thomas Harte
c39fd17e54 Corrects extension. 2018-10-19 21:55:35 -04:00
Thomas Harte
78fff5bdd9 Sneaks a Sonic picture into the readme.
Without even yet claiming to be a Master System emulator, given that I've still quite a few things to do there.
2018-10-19 21:54:21 -04:00
Thomas Harte
6fff514901 Honours the region by implementing Japanese (no BIOS) and European (PAL) paths. 2018-10-19 21:37:05 -04:00
Thomas Harte
f9a6c00493 Makes first attempt to support PAL timings. 2018-10-19 21:36:13 -04:00
Thomas Harte
fa77d81813 Corrects test for whether to consider a European or American region. 2018-10-19 21:35:52 -04:00
Thomas Harte
f0b6c406ff The Sega static analyser now attempts to differentiate region and paging scheme. 2018-10-19 20:32:09 -04:00
Thomas Harte
c365cca38a Makes order of operations explicit. 2018-10-18 22:37:04 -04:00
Thomas Harte
4cd65eab5c Seeks to avoid bad macro expansion. 2018-10-18 22:36:25 -04:00
Thomas Harte
2ee360e6ba Merge pull request #553 from TomHarte/MasterSystemVDP
Adds Initial Sega SG1000 and Master System emulation
2018-10-18 22:30:57 -04:00
Thomas Harte
9bc09046c0 Attempts to ensure that sprites can go off the top of the screen. 2018-10-18 21:48:57 -04:00
Thomas Harte
10d9cbdeb1 Adds an extra LOG to track the memory map as a potential cause of emulation failure. 2018-10-18 21:48:37 -04:00
Thomas Harte
57f03e660c Ensures console output only in debug builds. 2018-10-18 21:16:56 -04:00
Thomas Harte
512f085891 Ensures proper left clipping of sprites. 2018-10-18 21:14:16 -04:00
Thomas Harte
6a2db52adb Ensures safe Megacart cartridge sizes too. 2018-10-18 21:09:05 -04:00
Thomas Harte
34e13d0d4d Clears top bit when reading the keypad and ensures no undefined behaviour reading the cartridge. 2018-10-18 21:05:58 -04:00
Thomas Harte
da00c832f5 Corrects colour fetching for multicolour text mode. 2018-10-18 20:38:00 -04:00
Thomas Harte
8ff265c3a1 Corrects multicolour text mode. 2018-10-18 20:25:42 -04:00
Thomas Harte
0278d5b61c Restores SG1000 compatibility. 2018-10-18 19:13:15 -04:00
Thomas Harte
1fc88c4eff Corrects off-by-one error in line fetching coroutines. 2018-10-16 21:36:31 -04:00
Thomas Harte
58ca74c68a Resolves right-side TMS sprite droppages. 2018-10-16 21:25:08 -04:00
Thomas Harte
b4f871a2ef Corrects first line sprite row selection. 2018-10-16 21:16:29 -04:00
Thomas Harte
0f7bf6d6c6 Resolves attempt to output graphics on the line one before the display. 2018-10-16 21:02:31 -04:00
Thomas Harte
5dfe7d8596 Corrects most of TMS sprite drawing. 2018-10-16 20:49:04 -04:00
Thomas Harte
231009b901 Makes faulty attempt to reintroduce TMS-mode sprites. 2018-10-16 20:00:06 -04:00
Thomas Harte
1c5f939aea Reintroduces tiles and some element of sprites in regular TMS mode. 2018-10-14 21:52:13 -04:00
Thomas Harte
c1e6406fc9 Corrects sprite accumulation. 2018-10-14 19:56:09 -04:00
Thomas Harte
d66979c68f Switched to a very large number of buffers, and resolved stupid attempt to reassign a reference. 2018-10-14 18:19:11 -04:00
Thomas Harte
6c09abc6cb Makes a flawed attempt to reformulate this exactly as two separate processes on a common clock with an interchange buffer.
Specifically because closer inspection of the TMS modes shows it isn't quite valid to model output of one line as having fully completed prior to fetching of the next. So some sort of extra buffer is required. At which point it is most natural to continue with the logic that each fetch routine is oriented around the fetching process for a single line, and each output routine has the same view, suggesting separate read/write addresses.

Something is wrong though, as video data is being output too rapidly (I think) and with occasional sync issues (again: subject to investigation).
2018-10-14 16:23:45 -04:00
Thomas Harte
9e52ead09a Ensures sprite scanning doesn't improperly set collision flag; that slot 151 is filled. 2018-10-12 19:50:48 -04:00
Thomas Harte
9ab0c54426 Eliminates faulty attempt to satisfy SMSVDP vertical counter test. 2018-10-12 18:57:07 -04:00
Thomas Harte
f6af6778ab Moves scrolling latch to proper position and implements 4-window fetching offset. 2018-10-11 22:36:27 -04:00
Thomas Harte
6a94dda60d Selects potentially-correct interrupt times. 2018-10-11 21:42:09 -04:00
Thomas Harte
82b7944599 Fixes horizontal counter wrapping. 2018-10-11 20:37:29 -04:00
Thomas Harte
52e02db5c8 Introduces horizontal counter latching and reading.
Then makes a new guess at frame IRQ position. But gets it wrong. Hmmm.
2018-10-11 19:56:32 -04:00
Thomas Harte
9a933993f5 Added TODO. 2018-10-10 22:17:17 -04:00
Thomas Harte
062b2ae8d3 Corrects calculation of [NTSC, 192 line] current row. 2018-10-10 22:15:38 -04:00
Thomas Harte
9f69dbf31a Adds half-updating of RAM pointer.
This emulator now passes the first screen of the SMS VDP test.
2018-10-10 21:59:08 -04:00
Thomas Harte
63fb3f03d1 Corrects address loading upon accesses of registers other than 0. 2018-10-10 21:47:48 -04:00
Thomas Harte
2e379b0834 Adds latching of scroll values. 2018-10-10 21:28:18 -04:00
Thomas Harte
f00f6c8c23 Allows the frame interrupt to be placed anywhere in the frame. 2018-10-10 21:07:39 -04:00
Thomas Harte
50e23f4a2e Fixes 16px-high sprites. 2018-10-10 20:34:00 -04:00
Thomas Harte
acdc84e08c Improves test slightly, and fixes line interrupt reload value setting. 2018-10-09 22:14:35 -04:00
Thomas Harte
c128ddb549 Introduces a first unit test for line interrupts and corrects backup behaviour. 2018-10-09 21:49:21 -04:00
Thomas Harte
dccf17e770 Makes a first serious attempt at Master System line interrupts. 2018-10-09 20:51:09 -04:00
Thomas Harte
2d8ab72e22 Fixed proper starting position for (interrupted) tile drawing. 2018-10-08 23:13:37 -04:00
Thomas Harte
748366c70e Corrects buffer overrun when the horizontal scroll lock is on. 2018-10-08 23:06:22 -04:00
Thomas Harte
7a74fe2ff7 Corrects tile plotting window and eliminates a redundant local. 2018-10-08 22:56:31 -04:00
Thomas Harte
e410302237 Switches to real SMS line output composition.
Including setting the sprite collision bit.
2018-10-08 22:43:10 -04:00
Thomas Harte
bca2161a05 Fixes TMS text mode for the new addressing order. 2018-10-07 21:09:01 -04:00
Thomas Harte
5f789092be Flips sprite priority in the temporary renderer.
The better to test other issues in the interim.
2018-10-07 19:16:35 -04:00
Thomas Harte
6975ed22c0 Doubles down on address-storage format, and implements the vertical scrolling lock. 2018-10-07 18:55:35 -04:00
Thomas Harte
24644f1dd1 Adds a low-pass filter, picked entirely by ear, and switches to composite output, at least for now. 2018-10-07 18:39:03 -04:00
Thomas Harte
3bead07043 Introduces proper indirection for sprite patterns.
This seems to work, so the onus is now back on the rendering loop.
2018-10-07 17:15:42 -04:00
Thomas Harte
ee20e42372 Makes initial attempt at collecting sprite contents.
With test plotting, indicating some sort of issue.
2018-10-07 16:53:25 -04:00
Thomas Harte
df411b4ede Corrects storage of visible sprites. 2018-10-07 16:40:32 -04:00
Thomas Harte
bfb9d8ccb6 At least attempts to use proper addressing for sprite info fetches. 2018-10-07 14:32:20 -04:00
Thomas Harte
338aec2930 Groups background fetches and experimentally seeks to daub sprites as white. 2018-10-06 22:07:04 -04:00
Thomas Harte
e6510dc87b Attempts to get at least as far as picking visible sprite indices. 2018-10-06 19:27:19 -04:00
Thomas Harte
76f3b9f6ba Fixed: paging writes don't obstruct RAM. 2018-10-06 14:26:00 -04:00
Thomas Harte
7830cda912 Implements line querying and most of line interrupts. 2018-10-04 22:50:35 -04:00
Thomas Harte
aac97a8983 Re-revokes fine scroll on the top two lines when requested. 2018-10-04 19:18:15 -04:00
Thomas Harte
ca26dfcd61 Correct Master System palette writes. 2018-10-04 19:12:31 -04:00
Thomas Harte
858721a7a5 Added left border hiding. 2018-10-04 18:52:23 -04:00
Thomas Harte
89db1d6a6a Switches to a more accurate means of left-padding. 2018-10-04 18:44:49 -04:00
Thomas Harte
de4e5c40aa Implements horizontal scrolling lock. 2018-10-03 23:28:33 -04:00
Thomas Harte
05248ab990 Starts to reimplement Master System output. 2018-10-03 23:13:21 -04:00
Thomas Harte
252f47a425 Ensures no pixel output on line one before end, and adds a temporary debugging test. 2018-10-02 22:59:20 -04:00
Thomas Harte
be52b31b5c Attempts fully to revive text mode. 2018-10-02 22:05:58 -04:00
Thomas Harte
23c3fa6993 Fixed: it's the SMS that has 8 sprites, not text mode (which has none). 2018-10-02 22:01:43 -04:00
Thomas Harte
499fc62187 Sets things up for implementation of the inner mode-specific logic. 2018-10-02 21:58:09 -04:00
Thomas Harte
1dd5272190 Ensures real-time output of all areas, to ensure proper palette response. 2018-10-02 21:18:28 -04:00
Thomas Harte
5361120353 Restores a stable frame. 2018-10-02 21:05:30 -04:00
Thomas Harte
60bab8fdf1 Starts to reformulate TMS collection as coroutines.
For the time being, thereby breaks all video. A static screen of the border colour is all you'll see.
2018-10-01 23:03:17 -04:00
Thomas Harte
cc99b0f532 Fixes typo. 2018-09-30 20:48:55 -04:00
Thomas Harte
91aa8f9295 Amps up colour content a little. 2018-09-30 20:47:26 -04:00
Thomas Harte
e9328d819e Switches to RGB output, at least for development. 2018-09-30 20:47:03 -04:00
Thomas Harte
48ece623e7 Adds the Sega Master System to SConstruct. 2018-09-30 20:46:38 -04:00
Thomas Harte
23191efc05 Starts writing and referring to colour RAM for colours. 2018-09-29 19:50:13 -04:00
Thomas Harte
0d8af010b6 Takes a stab at tile reversal and vertical scrolling. 2018-09-28 22:37:10 -04:00
Thomas Harte
7b9bb772ca Corrected to give a not-exactly-indexed-correctly approximation of what's on display. 2018-09-28 21:03:51 -04:00
Thomas Harte
f7e211c245 Makes first attempt to put something vaguely like the Master System tile map on screen. 2018-09-28 20:39:14 -04:00
Thomas Harte
43bcb6415b Merge branch 'master' into MasterSystemVDP 2018-09-27 22:38:15 -04:00
Thomas Harte
15ec4aaa43 Merge pull request #554 from TomHarte/65C02s
Corrects Rockwell and WDC references.
2018-09-27 22:37:45 -04:00
Thomas Harte
364859467f Corrects Rockwell and WDC references.
Also shuffles the NES CPU type up into the top position, so this is a strict progression in terms of functionality.
2018-09-27 22:36:45 -04:00
Thomas Harte
35c2e74af8 Attempts to establish a coroutine-ish structure for access patterns.
The Master System mode, inevitably, is the test case.
2018-09-27 22:33:41 -04:00
Thomas Harte
19482a563f Attempts to explicitly make room for the SMS VDP mode. 2018-09-27 21:22:57 -04:00
Thomas Harte
2e4c4c3e91 Makes some attempt to implement paging.
This causes several of the 32kb games to be recognised by the BIOS and permitted to start, so it really really may be time to stop deferring work on the VDP.
2018-09-24 21:34:42 -04:00
Thomas Harte
7515fa8a98 Ensures the SG1000 gets an unadulterated TMS and SN. 2018-09-23 22:24:29 -04:00
Thomas Harte
5b9e7213dd Adds a couple of joystick inputs.
SG1000 titles all seem to work now.
2018-09-23 21:55:07 -04:00
Thomas Harte
2253341904 This now goes far enough for the only SG1000 game I'm testing to start up.
Which hopefully gives me as much as I need to implement joypads, etc, and definitively get to just the VDP being outstanding.
2018-09-23 17:42:42 -04:00
Thomas Harte
e155dc8d6e Adds fairly standard memory map indirection. 2018-09-23 17:36:30 -04:00
Thomas Harte
00b2db4fb9 Ensures the Master System is informed when it should pretend to be an SG1000. 2018-09-23 16:34:47 -04:00
Thomas Harte
f59386f523 Adds just enough input logic that the Sega sound now plays. 2018-09-23 16:05:37 -04:00
Thomas Harte
9683c8f664 Advances towards the Master System actually receiving interrupts. 2018-09-23 15:58:23 -04:00
Thomas Harte
38a1fde3bf Attempts to permit Master System interrupts. 2018-09-23 00:07:46 -04:00
Thomas Harte
40c7a63fb5 Makes a first attempt at Master System IO decoding. 2018-09-22 23:45:29 -04:00
Thomas Harte
d9e65cd758 Ensures neither the ColecoVision nor the MSX processes mid-cycles. 2018-09-21 22:53:35 -04:00
Thomas Harte
e511261b04 Adds a Master System class, so that SMSs can end up somewhere. 2018-09-21 22:13:07 -04:00
Thomas Harte
0d01346ad4 Advertises SMS support and goes as far as realising it needs to spawn a Master System. 2018-09-20 22:04:28 -04:00
Thomas Harte
e7f4babf41 Starts taking steps towards SMS/GG and V9938/9958 support.
Specifically: routine namespace stuff, plus the intention to move to a table-based operation+cost version of timing. Reordering works fine for the TMS, and probably would also for the SMS/GG, but it'd be problematic with the command engine of the V9938/9958 and maintaining a consistent set of code is easier.
2018-09-17 22:59:16 -04:00
Thomas Harte
a29a8e292b Merge pull request #552 from TomHarte/AppleIIBrightness
Turns down the Apple II brightness a little.
2018-09-17 20:09:06 -04:00
Thomas Harte
a8b116e217 Turns down the Apple II brightness a little.
So that light blue is more like a blue.
2018-09-17 20:08:20 -04:00
Thomas Harte
e582b4c8ca Eliminates some dangling references to iCoordinate. 2018-09-13 19:35:15 -04:00
Thomas Harte
3b70dbfebe Merge pull request #550 from TomHarte/ElectronCorruption
Resolves potential Electron output errors
2018-09-12 21:04:20 -04:00
Thomas Harte
868cd5cb09 Improves alignment request. 2018-09-12 20:27:02 -04:00
Thomas Harte
dec18d9acc Restores full pixel output to the Electron. 2018-09-12 20:25:30 -04:00
Thomas Harte
a7508bc2ae Switching explicitly to one pixel per sample eliminates the need for a bookender. 2018-09-12 20:11:17 -04:00
Thomas Harte
a38639d099 Eliminates the concept of an iCoordinate.
Real-life precision appears not to support the idea of sub-sample pixel storage.
2018-09-12 20:05:39 -04:00
Thomas Harte
c6e94bc2a6 Adds missing #include. 2018-09-11 21:55:03 -04:00
Thomas Harte
12e9478a81 Merge pull request #549 from TomHarte/MSXPaste
Simplifies and corrects MSX pasting behaviour.
2018-09-11 21:47:41 -04:00
Thomas Harte
09dafb1a79 Simplifies and corrects MSX pasting behaviour.
Now including mapping \n -> \r.
2018-09-11 21:46:28 -04:00
Thomas Harte
3358c07107 Merge pull request #548 from TomHarte/MoreBooleans
Adds 'false' and 'f' to the list of acceptable command-line option falses.
2018-09-11 20:38:11 -04:00
Thomas Harte
36ff2105fb Updates C-style (bool) casts. 2018-09-11 20:37:15 -04:00
Thomas Harte
1739d18433 Adds 'false' and 'f' to the list of acceptable refusals. 2018-09-11 20:36:49 -04:00
Thomas Harte
61272cfe20 Merge pull request #547 from TomHarte/VicUB
Fixes undefined behaviour resulting from uninitialised VIC state.
2018-09-10 22:35:14 -04:00
Thomas Harte
31b048f966 Ensures all bools start in a valid state. 2018-09-10 22:21:03 -04:00
Thomas Harte
7e29d49451 Merge branch 'master' of github.com:TomHarte/CLK 2018-09-10 21:38:38 -04:00
Thomas Harte
5445081c96 It's eight pixels that aren't written in double output mode, not four. 2018-09-10 21:38:05 -04:00
Thomas Harte
d791facc87 Update README.md
Promoted my estimation of the Apple IIs.
2018-09-10 17:57:40 -04:00
Thomas Harte
21a9bd927a Merge pull request #545 from TomHarte/LeftBorder
Adds a left gutter to complement the right.
2018-09-09 21:53:25 -04:00
Thomas Harte
d73d3b4480 Adds a left border to complement the right. 2018-09-09 21:52:48 -04:00
Thomas Harte
7b9c1bb69c Makes minor layout improvements. 2018-09-09 21:02:31 -04:00
Thomas Harte
224b3163f2 Merge pull request #544 from TomHarte/MSXColours
Corrects composition-time over-saturation.
2018-09-09 20:39:13 -04:00
Thomas Harte
fc84ae611e Resolves various instances of spaces in place of tabs. 2018-09-09 20:33:56 -04:00
Thomas Harte
22a52bdca2 Merge branch 'master' into MSXColours 2018-09-09 20:31:24 -04:00
Thomas Harte
6e9cd5cb21 Resolves over-brightness created by over-composition. 2018-09-09 20:30:43 -04:00
Thomas Harte
c73445199c Eliminates a couple of instances of manual memory management. 2018-09-09 20:29:58 -04:00
Thomas Harte
ab02f82470 Merge pull request #543 from TomHarte/CFBundleTypeOSTypes
Removes `LSItemContentTypes` so as not to reject files.
2018-09-09 17:49:16 -04:00
Thomas Harte
1e3318816c Removes LSItemContentTypes so as not to reject files. 2018-09-09 17:47:03 -04:00
Thomas Harte
4c8781c762 Increases documentation slightly. 2018-09-09 17:17:38 -04:00
Thomas Harte
3a3dec92c7 Merge pull request #540 from MaddTheSane/plistFix
Remove LSItemContentTypes
2018-09-09 10:07:19 -04:00
Thomas Harte
5a5fc1ae1a Merge pull request #541 from TomHarte/Annunciator3
Implements the two undocumented annunciator 3 graphics modes
2018-09-09 10:06:52 -04:00
Thomas Harte
8d79a1e381 Corrected fat low-res implementation.
As per comment of awanderin that "the odd addresses don't get their pixels auto-shifted by the hardware as with normal lo-res".
2018-09-09 10:06:21 -04:00
Thomas Harte
d70f5da94e Attempts an implementation of the undocumented low res + annunciator 3 graphics mode. 2018-09-08 20:51:15 -04:00
C.W. Betts
05d4274019 Remove LSItemContentTypes: they should be unique identifiers, not generic types like public.item or public.data.
This can result in strange icons showing up in the wrong places.

Also added a category type.
2018-09-07 16:39:52 -06:00
Thomas Harte
afeec09902 Gets explicit about DHIRES being annunciator 3; implements four-colour high res mode. 2018-09-06 23:23:19 -04:00
Thomas Harte
0526ac2ee2 Slightly increases const correctness.
The converters from source data to output pixels do not modify the source data. It's a shame there's no `restrict` in C++.
2018-09-05 11:36:40 -04:00
Thomas Harte
6725ee2190 Merge pull request #539 from TomHarte/40ColumnTextCorruption
Corrects 40-column alternative text mode corruption
2018-09-05 10:27:09 -04:00
Thomas Harte
8b661fb90f Introduces an extra level of indirection for text mapping. 2018-09-05 10:26:08 -04:00
Thomas Harte
dab7d3db1b Merge branch 'master' into 40ColumnTextCorruption 2018-08-30 20:24:47 -04:00
Thomas Harte
1cba3d48d9 Merge pull request #538 from TomHarte/AppleDecodingAgain
Correction: 0xc011 et al get the keyboard value in bits 0 to 6...
2018-08-30 20:19:48 -04:00
Thomas Harte
d53b38ec7e Correction: 0xc011 et al get the keyboard value in bits 0 to 6 and the switch value in bit 7. 2018-08-30 20:18:36 -04:00
Thomas Harte
5d0f47eda2 Merge pull request #536 from TomHarte/AppleDecoding
Adds mirrors for keyboard input and the audio toggle.
2018-08-27 21:14:48 -04:00
Thomas Harte
2e04c4442c Adds mirrors for keyboard input and the audio toggle. 2018-08-27 21:14:21 -04:00
Thomas Harte
f639cdc8ad Merge pull request #535 from TomHarte/DSKFixes
Corrects Apple DSK track length, inter-track skew, and Pro-DOS volume number.
2018-08-27 21:07:11 -04:00
Thomas Harte
71ec7624ca Corrects Apple DSK track length, inter-track skew, and Pro-DOS volume number. 2018-08-27 20:56:25 -04:00
Thomas Harte
0599d9602e Ensures no out-of-bounds accesses to inverses on a IIe. 2018-08-26 23:02:31 -04:00
Thomas Harte
234bef2a88 Adds default to make it explicit that fetch_address is initialised. 2018-08-24 22:26:03 -04:00
Thomas Harte
adb574e1cd Merge pull request #529 from TomHarte/AppleDelay
Corrects Apple II video defects
2018-08-24 22:11:41 -04:00
Thomas Harte
1f491e764e Nudges visible area slightly to the right. 2018-08-24 22:08:11 -04:00
Thomas Harte
114a43a662 Corrects improper indexing for byte shift. 2018-08-24 21:58:43 -04:00
Thomas Harte
5547c39c91 Corrects documentation. 2018-08-24 20:06:40 -04:00
Thomas Harte
97a89aaf4d Factors out the stuff of deferred action interleaving, as I suspect it'll come in handy. 2018-08-24 20:04:26 -04:00
Thomas Harte
61e46399dc About face! There should be no delay on serialisation, but a delay on interpretation-affecting soft switches. 2018-08-22 21:56:45 -04:00
Thomas Harte
e802f6ecc2 Rearranges draw loop around a fixed-size 568-sample line buffer. 2018-08-19 22:31:04 -04:00
Thomas Harte
4209f0e044 Moves memory collection into a separate loop. 2018-08-18 21:54:24 -04:00
Thomas Harte
33576aa2c4 Uses const to ensure output_* are properly constrained. 2018-08-18 21:36:48 -04:00
Thomas Harte
17bf1a64bf Moves the stuff of generating pixels out of the main loop. 2018-08-18 18:44:31 -04:00
Thomas Harte
f8d46f8f3d Merge branch 'master' into AppleDelay 2018-08-18 14:11:21 -04:00
Thomas Harte
8787d85e64 Eliminates #undefs as being (i) unnecessary, now this is a source file; and (ii) incomplete in any case. 2018-08-17 22:24:42 -04:00
Thomas Harte
7f0f17f435 Merge pull request #523 from TomHarte/Further65C02
Further corrects 65C02 behaviour
2018-08-17 21:58:38 -04:00
Thomas Harte
0e7f54f375 Implements STP and WAI, and ensures all unimplemented 65C02 instructions are NOP for all 65C02s. 2018-08-17 21:49:06 -04:00
Thomas Harte
b3bdfa9f46 Corrected: it's three-cycle 65C02 branches that ignore interrupts, not two. 2018-08-16 20:47:49 -04:00
Thomas Harte
592ec69d36 Causes the 65C02 not to accept interrupts immediately after untaken branches. 2018-08-15 22:42:04 -04:00
Thomas Harte
60e00ddd02 Correction: the test for not skipping an operand fetch requires a 65C02. 2018-08-15 22:07:17 -04:00
Thomas Harte
6806193dc2 Ensures that "Read/Modify/Write instructions absolute indexed in same page" take only six cycles on a 65C02. 2018-08-15 19:17:37 -04:00
Thomas Harte
c35dca783f Ensures that page-crossing indexing no longer causes an extra read of an invalid address on the 65C02.
It rereads the last byte of the instruction stream instead.
2018-08-15 18:47:53 -04:00
Thomas Harte
901e0d65b9 Documents all 6502 micro-operations.
Also makes sure 1-cycle NOPs really, definitely are one cycle only on a 65C02 and eliminates OperationCopyOperandFromA as a redundant copy of OperationSTA.
2018-08-14 22:17:53 -04:00
Thomas Harte
ddf45a0010 Ensures NMI and RST reset D on 65C02s. 2018-08-14 19:49:14 -04:00
Thomas Harte
1eca4463b3 Ensures NMI can no longer usurp BRK on 65C02s. 2018-08-14 19:33:48 -04:00
Thomas Harte
be01203cc1 Starts to expand the range of supported 6502s.
This fully implements the NES 6502 because, well, it's virtually no extra work, and ensures that RDY takes effect on write cycles on 65C02s.
2018-08-13 22:17:22 -04:00
Thomas Harte
4d1d19a464 Introduces an intermediate buffer for Apple II video data. 2018-08-12 20:36:08 -04:00
Thomas Harte
760817eb3b Merge pull request #521 from TomHarte/AppleVideo
Fixes Apple II double low resolution graphics
2018-08-11 23:20:40 -04:00
Thomas Harte
cb47575860 Eliminates stdout chatter. 2018-08-11 22:57:54 -04:00
Thomas Harte
434d184503 Corrects deserialisation order in double low res mode. 2018-08-11 22:53:06 -04:00
Thomas Harte
7374c665e8 Corrects regression in video flushing. 2018-08-11 19:57:39 -04:00
Thomas Harte
10c930a59d Merge pull request #520 from TomHarte/EnhancedIIe
Adds Enhanced IIe emulation.
2018-08-11 19:42:47 -04:00
Thomas Harte
60ab6f0c2a Entrusts IIe-esque character logic fully to the ROM. 2018-08-11 18:45:39 -04:00
Thomas Harte
a13eb351da Implements the Enhanced IIe, other than some text selection errors. 2018-08-11 10:26:30 -04:00
Thomas Harte
4b91910fab Removes erroneous addition. 2018-08-10 23:27:09 -04:00
Thomas Harte
f46d52364c Merge pull request #519 from TomHarte/65C02
Makes an initial pass at 65C02 emulation
2018-08-10 23:21:45 -04:00
Thomas Harte
878c63dcd2 Ensures ADC and SBC decimal take an extra cycle on the 65C02. 2018-08-10 22:52:55 -04:00
Thomas Harte
261fb3d4f8 Implements proper test for ADC/SBC 65C02 NZ, though not yet the proper timing.
This gets Klaus Dorman's test to pass.
2018-08-10 22:42:35 -04:00
Thomas Harte
b63e0cff72 Improves has-completed test. 2018-08-10 22:27:01 -04:00
Thomas Harte
5d6e479338 Implements RMB and SMB, and fixes SBC (zero). 2018-08-10 22:13:51 -04:00
Thomas Harte
90094529a5 Implements TSB and TRB, and adds the extra BIT instructions. 2018-08-10 22:04:45 -04:00
Thomas Harte
aed4c0539e Implements STZ. 2018-08-10 21:17:02 -04:00
Thomas Harte
8b50ab2593 Corrects (zero) behaviour. 2018-08-10 21:12:55 -04:00
Thomas Harte
95164b79c9 Attempted implementation of (zp) addressing mode. 2018-08-09 21:51:14 -04:00
Thomas Harte
6f838fe190 Implements INA and DEA. 2018-08-08 22:30:19 -04:00
Thomas Harte
bb680b40d8 Implements the 65C02's JMPs. 2018-08-08 22:26:57 -04:00
Thomas Harte
e3f6da6994 Implements the 65C02 NOPs. 2018-08-08 20:00:14 -04:00
Thomas Harte
e46bde35f5 Implements BBS and BBR. 2018-08-07 21:52:17 -04:00
Thomas Harte
32338bea4d Implements BRA. 2018-08-06 22:37:30 -04:00
Thomas Harte
5c881bd19d Implements PLX, PLY, PHX and PHY. 2018-08-06 22:00:23 -04:00
Thomas Harte
1a44ef0469 Introduces Klaus Dorman's 65C02 tests. All failing. 2018-08-06 21:48:43 -04:00
Thomas Harte
ebce9a2e51 Fixes test target. 2018-08-06 21:15:13 -04:00
Thomas Harte
633af4d404 The operations table is now per-instance. 2018-08-06 20:47:14 -04:00
Thomas Harte
76a73c835c Forces 6502 consumers to declare which model — the original, 65C02 or 65SC02.
All present machines use a regular 6502.
2018-08-06 20:06:07 -04:00
Thomas Harte
c1d1c451ef Merge pull request #518 from TomHarte/MacInsertDisplay
Tweaks the Mac UI
2018-08-06 19:12:17 -04:00
Thomas Harte
3be30d8c71 Tries once again to introduce file type icons. 2018-08-06 19:08:27 -04:00
Thomas Harte
d4c1244485 Adds a hint for users. 2018-08-06 18:56:59 -04:00
Thomas Harte
c61b9dca17 Ensures the Mac doesn't show the 'Insert...' option for machines that can't accept an insertion. 2018-08-06 18:52:42 -04:00
Thomas Harte
39bf682016 Adds mentions of the IIe. 2018-08-06 12:03:54 -04:00
Thomas Harte
60ac9b49ea Merge pull request #517 from TomHarte/MacInsertUI
Completes Mac UI 'Insert...' change
2018-08-05 22:58:16 -04:00
Thomas Harte
a8bb18e2cf Merge branch 'master' into MacInsertUI 2018-08-05 22:57:28 -04:00
Thomas Harte
1852786609 Merge pull request #516 from TomHarte/MacInsertUI
Adds an 'Insert...' menu option.
2018-08-05 22:51:00 -04:00
Thomas Harte
31df8c7e91 Corrects improper NSWindowController sheet stack manipulation.
As a result, 'Insert...' now seems to work properly.
2018-08-05 22:47:51 -04:00
Thomas Harte
832939f5b7 Merge branch 'master' into MacInsertUI 2018-08-05 22:39:00 -04:00
Thomas Harte
c2d9e1ec81 Merge pull request #515 from TomHarte/POPImage
Adds an Apple II screenshot to the readme.
2018-08-05 17:58:53 -04:00
Thomas Harte
673b915ee8 Reduces post-table image size a little. 2018-08-05 17:57:37 -04:00
Thomas Harte
032a62dfff Adds An Apple II screenshot to the mix. 2018-08-05 17:56:10 -04:00
Thomas Harte
f2d78182a3 Merge pull request #514 from TomHarte/VideoFixes
Fixes various IIe video deficiencies.
2018-08-05 17:49:13 -04:00
Thomas Harte
de68e70246 Fixes various IIe video deficiencies.
Specifically:
* the double-high resolution switches should be read/write; and
* the other IIe-specific switches should cause a video update for real-time effect.
2018-08-05 17:47:23 -04:00
Thomas Harte
e07447eb9a Merge pull request #513 from TomHarte/JoystickRange
Significant improves Apple II joystick compatibility
2018-08-05 17:37:38 -04:00
Thomas Harte
5cdeb58571 Makes digital to analogue conversion more extreme. 2018-08-05 17:36:20 -04:00
Thomas Harte
ce14cc8677 Flips meaning of analogue input bits, correcting most joystick titles.
Mysteriously, some functioned correctly before this. But they continue to do so.
2018-08-05 17:36:01 -04:00
Thomas Harte
bcd0479074 This in principle completes the insert action.
With the caveat that 'New...' machines seem to have blocked the window's panel queue somehow.
2018-08-05 13:12:02 -04:00
Thomas Harte
d72dd8c4ff Merge branch 'master' into macInsertUI 2018-08-05 11:54:26 -04:00
Thomas Harte
f7ce86fef8 Merge pull request #512 from TomHarte/80Text
Extends correct text handling to 80-column mode.
2018-08-04 22:28:59 -04:00
Thomas Harte
55f2fccf5e Extends correct text handling to 80-column mode. 2018-08-04 22:25:29 -04:00
Thomas Harte
c939a274be Makes first attempt to connect up an in-machine open panel. 2018-08-04 22:21:23 -04:00
Thomas Harte
101fb5d7bf Merge pull request #511 from TomHarte/ColecoSizeCheck
Relaxes ColecoVision cartridge image size check
2018-08-04 21:47:30 -04:00
Thomas Harte
3c51e335c3 Makes extra sure not to try to read from an empty characters list. 2018-08-04 21:40:26 -04:00
Thomas Harte
33ea90678c Relaxes ColecoVision cartridge size test. 2018-08-04 21:40:02 -04:00
Thomas Harte
11ae2c64ba Merge pull request #502 from TomHarte/IIe
Extends Apple II emulation to include the IIe
2018-08-04 21:02:45 -04:00
Thomas Harte
26624d7652 Fixes vertical blank signal; it should be the other way around. 2018-08-04 20:57:02 -04:00
Thomas Harte
85fb4773b0 Tweaks Apple key mapping and implements reset_all_keys. 2018-08-04 20:31:37 -04:00
Thomas Harte
099d66804e Makes colour burst phase explicit. 2018-08-04 19:29:34 -04:00
Thomas Harte
086596c28e Adds reading of vertical blank and implements the full IIe keyboard logic.
i.e. there are now two Apple keys, and shift isn't assumed.
2018-08-04 19:17:04 -04:00
Thomas Harte
3aeb4213fe Implements the C010 read value. 2018-08-04 17:57:02 -04:00
Thomas Harte
558b96bc05 Corrects IIe text display. 2018-08-04 16:52:29 -04:00
Thomas Harte
e97cc40a2c Corrects typo in Cx-page ROM paging. 2018-08-04 12:44:58 -04:00
Thomas Harte
94503ed771 Disables the macOS Apple II options panel, since it now has no options. 2018-08-04 12:37:55 -04:00
Thomas Harte
c4f86cc324 The Disk II now being its proper speed, withdraws the quickload option. 2018-08-03 21:20:21 -04:00
Thomas Harte
70c4d6b9b3 Adds a one second delay between controller and drive motor off. 2018-08-03 21:13:18 -04:00
Thomas Harte
78c7137427 Avoids observer communication if motor status hasn't changed. 2018-08-03 21:11:22 -04:00
Thomas Harte
74a2f717b3 Turns down the composite signal amplitude a little, to help colour distinctness. 2018-08-01 18:52:42 -04:00
Thomas Harte
98bb5bd9f1 Ensures flux bits are observable for two cycles rather than one; it should be 1us. 2018-07-31 23:01:11 -04:00
Thomas Harte
c91eaaf8da Takes a stab at double low-res graphics. 2018-07-31 21:45:09 -04:00
Thomas Harte
a36f37d240 Introduces a 1/14th delay in output of double high res. 2018-07-31 21:29:51 -04:00
Thomas Harte
c773d3501a Implements the INTC8ROM switch.
Finally causing the Zellyn tests to pass! Is this nightmare behind me?
2018-07-31 19:00:46 -04:00
Thomas Harte
5810f9b3f9 Fixes high resolution address range and switching logic. 2018-07-30 23:23:18 -04:00
Thomas Harte
3f56683342 Fixes order of deserialisation between auxiliary and base RAM. 2018-07-30 23:08:45 -04:00
Thomas Harte
16ccbdefd6 Of course, | has higher precedence than ?. Classic! 2018-07-30 23:08:22 -04:00
Thomas Harte
a533d09fe7 Sets the IIe as the default model. 2018-07-30 23:07:34 -04:00
Thomas Harte
e9aaa5bbdf Factors out the page-mapping function.
For one less potential source of failure.
2018-07-30 22:23:48 -04:00
Thomas Harte
ecb26e3281 Corrections: slot_C3_rom_ works the other way around; 80STORE doesn't affect most of RAM but does always affect the text screen.
Also factored out `set_zero_page_paging` for consistency.
2018-07-30 19:54:25 -04:00
Thomas Harte
5aa0b17720 Improves IIe paging further. 2018-07-29 23:02:27 -04:00
Thomas Harte
632b37ecec Attempts an implementation of auxiliary memory. 2018-07-29 10:41:12 -04:00
Thomas Harte
c905de2e40 Restores IIe ROM-over-card paging. 2018-07-28 13:31:25 -04:00
Thomas Harte
bc2afe69e1 Accepting that memory mapping on a IIe is more complicated than I anticiapted, introduces mapping for all pages.
Also picks a name for the Unenhanced Apple IIe ROM.
2018-07-28 13:02:49 -04:00
Thomas Harte
894998b163 Merge branch 'master' into IIe 2018-07-28 10:54:04 -04:00
Thomas Harte
51192d8397 Merge pull request #508 from TomHarte/Whitespace
Eliminates various blank lines.
2018-07-28 10:53:17 -04:00
Thomas Harte
3c33ccd730 Eliminates various blank lines. 2018-07-28 10:52:34 -04:00
Thomas Harte
3e35109d63 Merge pull request #507 from TomHarte/BetterBMPDestination
Use `xdg-user-dir PICTURES` instead of $HOME for screenshots
2018-07-28 10:48:28 -04:00
Thomas Harte
99c770eab4 Ensure that the output of xdg-user-dir is properly filtered. 2018-07-28 10:45:50 -04:00
Thomas Harte
34aa78b7ce Attempts to use xdg-user-dir PICTURES in preference to $HOME for pictures. 2018-07-28 09:14:18 -04:00
Thomas Harte
8cca9c2055 Merge branch 'master' into IIe 2018-07-27 23:52:39 -04:00
Thomas Harte
85ce21c79f Merge pull request #505 from TomHarte/MacScreenshots
Attempts to introduce screenshot capture for macOS.
2018-07-27 23:43:13 -04:00
Thomas Harte
d19d949b9c Removes unnecessary import. 2018-07-27 23:41:55 -04:00
Thomas Harte
1cb3713b84 Attempts to introduce screenshot capture for macOS. 2018-07-27 23:37:24 -04:00
Thomas Harte
689850d698 Merge pull request #504 from TomHarte/SDLBMPByteOrder
Ensures SDL is properly informed of buffer byte order.
2018-07-27 18:53:16 -04:00
Thomas Harte
c572a52049 Ensures SDL is properly informed of buffer byte order. 2018-07-27 18:51:38 -04:00
Thomas Harte
41765e00c4 Merge branch 'master' into IIe 2018-07-26 21:24:46 -04:00
Thomas Harte
080aa0acc5 Merge pull request #503 from TomHarte/SDLScreenshots
Adds screenshot saving upon ctrl+shift+d.
2018-07-26 20:58:35 -04:00
Thomas Harte
5e7c46a72a Adds screenshot saving upon ctrl+shift+d. 2018-07-26 20:53:12 -04:00
Thomas Harte
5f2b9b2d5a Implements the alternative zero page soft switch. 2018-07-25 22:10:21 -04:00
Thomas Harte
5c4506a9db Talks the IIe into proceeding to a beep and an improperly-formed logo. 2018-07-25 21:43:12 -04:00
Thomas Harte
55a6431fb3 Puts in enough logic to be able to launch a non-functional IIe. 2018-07-25 18:58:34 -04:00
Thomas Harte
ede2696a77 Edges further towards implementing the IIe video subsystem.
All video-specific switches are in place, and mostly honoured, and a IIe machine configuration is advertised at least.
2018-07-24 22:15:42 -04:00
Thomas Harte
59b9e39022 Starts the process of supporting the Apple IIe graphics modes.
Albeit that I'm not yet even up on the proper soft switches.
2018-07-23 22:14:41 -04:00
Thomas Harte
6b2970f2f2 Ensures no-hat input doesn't override analogue axes. 2018-07-22 17:29:37 -04:00
Thomas Harte
6a73fe7d65 Merge pull request #500 from TomHarte/MacJoysticks
Implements initial joystick support for the Mac
2018-07-22 16:56:40 -04:00
Thomas Harte
1362906f94 Wires joystick support all the way through to machines.
Ensures there's only one joystick manager, which is shared by all machines, with input going only to the key window.
2018-07-22 16:55:47 -04:00
Thomas Harte
8f4042c4bb Permits joysticks to be queried for number of fire buttons. 2018-07-22 16:52:58 -04:00
Thomas Harte
c05b6397b0 Attempts a full implementation of the joystick manager.
So it currently vends a list of existing joysticks plus their states. More work will be required for a UI — e.g. there is no way to identify one joystick from another — but this'll do for now.
2018-07-22 15:23:26 -04:00
Thomas Harte
8d18808efe Walks a few steps further along device inspection. 2018-07-20 23:33:04 -04:00
Thomas Harte
09950d9414 Gamely starts to create a HID input manager for joysticks/pads/etc. 2018-07-19 22:43:01 -04:00
Thomas Harte
badbbdf155 Merge pull request #498 from TomHarte/DisplayBorder
Resolves border issues in fullscreen mode
2018-07-16 22:01:08 -04:00
Thomas Harte
2832792fed Corrects improper use of doubles. 2018-07-16 21:55:19 -04:00
Thomas Harte
efa45b9504 Adds a right gutter to clip persistence errors.
Also uncovers and corrects a long-standing centring error.
2018-07-16 21:52:31 -04:00
Thomas Harte
523749edf8 Merge branch 'master' into DisplayBorder 2018-07-16 20:00:52 -04:00
Thomas Harte
5a0499e8a7 Merge pull request #499 from TomHarte/EditorConfig
Adds a .editorconfig to aid Github display.
2018-07-16 20:00:27 -04:00
Thomas Harte
258c8b5900 Adds a .editorconfig to aid Github display. 2018-07-16 19:59:03 -04:00
Thomas Harte
24b861f056 Eliminates make_unique as this is presently a C++11 project. 2018-07-15 22:52:36 -04:00
Thomas Harte
29f7f4d432 Adds missing #include. 2018-07-15 22:47:50 -04:00
Thomas Harte
21080a1149 Merge branch 'master' into DisplayBorder 2018-07-15 22:31:33 -04:00
Thomas Harte
1d068fd09b Merge pull request #497 from TomHarte/RobocopSprites
Ensures only the first 8px of sprites is output in 8x8 mode.
2018-07-15 22:30:42 -04:00
Thomas Harte
92065813ef Ensures only the first 8px of sprites is output in 8x8 mode.
Also adds a little extra documentation.
2018-07-15 22:21:29 -04:00
Thomas Harte
3e9ef6b8cb Adds indicator lights for the SDL port.
To complete #426
2018-07-15 20:19:06 -04:00
Thomas Harte
c9451a5382 Introduces an object for drawing OpenGL rectangles. 2018-07-14 17:42:23 -04:00
Thomas Harte
2be3b027db Merge branch 'master' into DisplayBorder 2018-07-14 13:13:29 -04:00
Thomas Harte
e339d169c5 Ensures the joystick doesn't obstruct tape input. 2018-07-12 22:10:05 -04:00
Thomas Harte
87001f86ee Merge pull request #495 from TomHarte/MSXJoysticks
Adds joystick support for the MSX.
2018-07-12 21:44:26 -04:00
Thomas Harte
58484e8f37 Adds joystick support for the MSX. 2018-07-12 21:42:47 -04:00
Thomas Harte
00cb4d26b3 Corrects typo. 2018-07-11 19:52:55 -04:00
309 changed files with 34800 additions and 8002 deletions

6
.editorconfig Normal file
View File

@@ -0,0 +1,6 @@
[*]
charset = utf-8
indent_style = tab
indent_size = 4
trim_trailing_whitespace = true

View File

@@ -24,13 +24,13 @@ namespace Activity {
class Observer {
public:
/// Announces to the receiver that there is an LED of name @c name.
virtual void register_led(const std::string &name) = 0;
virtual void register_led(const std::string &name) {}
/// Announces to the receiver that there is a drive of name @c name.
virtual void register_drive(const std::string &name) = 0;
virtual void register_drive(const std::string &name) {}
/// Informs the receiver of the new state of the LED with name @c name.
virtual void set_led_status(const std::string &name, bool lit) = 0;
virtual void set_led_status(const std::string &name, bool lit) {}
enum class DriveEvent {
StepNormal,
@@ -39,11 +39,10 @@ class Observer {
};
/// Informs the receiver that the named event just occurred for the drive with name @c name.
virtual void announce_drive_event(const std::string &name, DriveEvent event) = 0;
virtual void announce_drive_event(const std::string &name, DriveEvent event) {}
/// Informs the receiver of the motor-on status of the drive with name @c name.
virtual void set_drive_motor_status(const std::string &name, bool is_on) = 0;
virtual void set_drive_motor_status(const std::string &name, bool is_on) {}
};
}

View File

@@ -13,7 +13,7 @@
using namespace Analyser::Dynamic;
MultiCRTMachine::MultiCRTMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::mutex &machines_mutex) :
MultiCRTMachine::MultiCRTMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex) :
machines_(machines), machines_mutex_(machines_mutex), queues_(machines.size()) {
speaker_ = MultiSpeaker::create(machines);
}
@@ -25,7 +25,7 @@ void MultiCRTMachine::perform_parallel(const std::function<void(::CRTMachine::Ma
std::condition_variable condition;
std::mutex mutex;
{
std::lock_guard<std::mutex> machines_lock(machines_mutex_);
std::lock_guard<decltype(machines_mutex_)> machines_lock(machines_mutex_);
std::lock_guard<std::mutex> lock(mutex);
outstanding_machines = machines_.size();
@@ -46,29 +46,18 @@ void MultiCRTMachine::perform_parallel(const std::function<void(::CRTMachine::Ma
}
void MultiCRTMachine::perform_serial(const std::function<void (::CRTMachine::Machine *)> &function) {
std::lock_guard<std::mutex> machines_lock(machines_mutex_);
std::lock_guard<decltype(machines_mutex_)> machines_lock(machines_mutex_);
for(const auto &machine: machines_) {
CRTMachine::Machine *crt_machine = machine->crt_machine();
CRTMachine::Machine *const crt_machine = machine->crt_machine();
if(crt_machine) function(crt_machine);
}
}
void MultiCRTMachine::setup_output(float aspect_ratio) {
perform_serial([=](::CRTMachine::Machine *machine) {
machine->setup_output(aspect_ratio);
});
}
void MultiCRTMachine::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
scan_target_ = scan_target;
void MultiCRTMachine::close_output() {
perform_serial([=](::CRTMachine::Machine *machine) {
machine->close_output();
});
}
Outputs::CRT::CRT *MultiCRTMachine::get_crt() {
std::lock_guard<std::mutex> machines_lock(machines_mutex_);
CRTMachine::Machine *crt_machine = machines_.front()->crt_machine();
return crt_machine ? crt_machine->get_crt() : nullptr;
CRTMachine::Machine *const crt_machine = machines_.front()->crt_machine();
if(crt_machine) crt_machine->set_scan_target(scan_target);
}
Outputs::Speaker::Speaker *MultiCRTMachine::get_speaker() {
@@ -84,6 +73,14 @@ void MultiCRTMachine::run_for(Time::Seconds duration) {
}
void MultiCRTMachine::did_change_machine_order() {
if(scan_target_) scan_target_->will_change_owner();
perform_serial([=](::CRTMachine::Machine *machine) {
machine->set_scan_target(nullptr);
});
CRTMachine::Machine *const crt_machine = machines_.front()->crt_machine();
if(crt_machine) crt_machine->set_scan_target(scan_target_);
if(speaker_) {
speaker_->set_new_front_machine(machines_.front().get());
}

View File

@@ -31,7 +31,7 @@ namespace Dynamic {
*/
class MultiCRTMachine: public CRTMachine::Machine {
public:
MultiCRTMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::mutex &machines_mutex);
MultiCRTMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex);
/*!
Informs the MultiCRTMachine that the order of machines has changed; the MultiCRTMachine
@@ -53,19 +53,18 @@ class MultiCRTMachine: public CRTMachine::Machine {
}
// Below is the standard CRTMachine::Machine interface; see there for documentation.
void setup_output(float aspect_ratio) override;
void close_output() override;
Outputs::CRT::CRT *get_crt() override;
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override;
Outputs::Speaker::Speaker *get_speaker() override;
void run_for(Time::Seconds duration) override;
private:
void run_for(const Cycles cycles) override {}
const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines_;
std::mutex &machines_mutex_;
std::recursive_mutex &machines_mutex_;
std::vector<Concurrency::AsyncTaskQueue> queues_;
MultiSpeaker *speaker_ = nullptr;
Delegate *delegate_ = nullptr;
Outputs::Display::ScanTarget *scan_target_ = nullptr;
/*!
Performs a parallel for operation across all machines, performing the supplied

View File

@@ -10,7 +10,8 @@
using namespace Analyser::Dynamic;
MultiKeyboardMachine::MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
MultiKeyboardMachine::MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) :
keyboard_(machines_) {
for(const auto &machine: machines) {
KeyboardMachine::Machine *keyboard_machine = machine->keyboard_machine();
if(keyboard_machine) machines_.push_back(keyboard_machine);
@@ -35,9 +36,34 @@ void MultiKeyboardMachine::type_string(const std::string &string) {
}
}
void MultiKeyboardMachine::keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) {
Inputs::Keyboard &MultiKeyboardMachine::get_keyboard() {
return keyboard_;
}
MultiKeyboardMachine::MultiKeyboard::MultiKeyboard(const std::vector<::KeyboardMachine::Machine *> &machines)
: machines_(machines) {
for(const auto &machine: machines_) {
uint16_t mapped_key = machine->get_keyboard_mapper()->mapped_key_for_key(key);
if(mapped_key != KeyNotMapped) machine->set_key_state(mapped_key, is_pressed);
observed_keys_.insert(machine->get_keyboard().observed_keys().begin(), machine->get_keyboard().observed_keys().end());
is_exclusive_ |= machine->get_keyboard().is_exclusive();
}
}
void MultiKeyboardMachine::MultiKeyboard::set_key_pressed(Key key, char value, bool is_pressed) {
for(const auto &machine: machines_) {
machine->get_keyboard().set_key_pressed(key, value, is_pressed);
}
}
void MultiKeyboardMachine::MultiKeyboard::reset_all_keys() {
for(const auto &machine: machines_) {
machine->get_keyboard().reset_all_keys();
}
}
const std::set<Inputs::Keyboard::Key> &MultiKeyboardMachine::MultiKeyboard::observed_keys() {
return observed_keys_;
}
bool MultiKeyboardMachine::MultiKeyboard::is_exclusive() {
return is_exclusive_;
}

View File

@@ -25,6 +25,25 @@ namespace Dynamic {
order of delivered messages.
*/
class MultiKeyboardMachine: public KeyboardMachine::Machine {
private:
std::vector<::KeyboardMachine::Machine *> machines_;
class MultiKeyboard: public Inputs::Keyboard {
public:
MultiKeyboard(const std::vector<::KeyboardMachine::Machine *> &machines);
void set_key_pressed(Key key, char value, bool is_pressed) override;
void reset_all_keys() override;
const std::set<Key> &observed_keys() override;
bool is_exclusive() override;
private:
const std::vector<::KeyboardMachine::Machine *> &machines_;
std::set<Key> observed_keys_;
bool is_exclusive_ = false;
};
MultiKeyboard keyboard_;
public:
MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
@@ -32,10 +51,7 @@ class MultiKeyboardMachine: public KeyboardMachine::Machine {
void clear_all_keys() override;
void set_key_state(uint16_t key, bool is_pressed) override;
void type_string(const std::string &) override;
void keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) override;
private:
std::vector<::KeyboardMachine::Machine *> machines_;
Inputs::Keyboard &get_keyboard() override;
};
}

View File

@@ -7,6 +7,7 @@
//
#include "MultiMachine.hpp"
#include "../../../Outputs/Log.hpp"
#include <algorithm>
@@ -58,6 +59,11 @@ KeyboardMachine::Machine *MultiMachine::keyboard_machine() {
}
}
MouseMachine::Machine *MultiMachine::mouse_machine() {
// TODO.
return nullptr;
}
Configurable::Device *MultiMachine::configurable_device() {
if(has_picked_) {
return machines_.front()->configurable_device();
@@ -73,15 +79,13 @@ bool MultiMachine::would_collapse(const std::vector<std::unique_ptr<DynamicMachi
}
void MultiMachine::multi_crt_did_run_machines() {
std::lock_guard<std::mutex> machines_lock(machines_mutex_);
#ifdef DEBUG
std::lock_guard<decltype(machines_mutex_)> machines_lock(machines_mutex_);
#ifndef NDEBUG
for(const auto &machine: machines_) {
CRTMachine::Machine *crt = machine->crt_machine();
printf("%0.2f ", crt->get_confidence());
crt->print_type();
printf("; ");
LOGNBR(PADHEX(2) << crt->get_confidence() << " " << crt->debug_type() << "; ");
}
printf("\n");
LOGNBR(std::endl);
#endif
DynamicMachine *front = machines_.front().get();

View File

@@ -54,6 +54,7 @@ class MultiMachine: public ::Machine::DynamicMachine, public MultiCRTMachine::De
Configurable::Device *configurable_device() override;
CRTMachine::Machine *crt_machine() override;
JoystickMachine::Machine *joystick_machine() override;
MouseMachine::Machine *mouse_machine() override;
KeyboardMachine::Machine *keyboard_machine() override;
MediaTarget::Machine *media_target() override;
void *raw_pointer() override;
@@ -62,7 +63,7 @@ class MultiMachine: public ::Machine::DynamicMachine, public MultiCRTMachine::De
void multi_crt_did_run_machines() override;
std::vector<std::unique_ptr<DynamicMachine>> machines_;
std::mutex machines_mutex_;
std::recursive_mutex machines_mutex_;
MultiConfigurable configurable_;
MultiCRTMachine crt_machine_;

View File

@@ -17,6 +17,8 @@ enum class Machine {
Atari2600,
ColecoVision,
Electron,
Macintosh,
MasterSystem,
MSX,
Oric,
Vic20,

View File

@@ -18,7 +18,9 @@ namespace AppleII {
struct Target: public ::Analyser::Static::Target {
enum class Model {
II,
IIplus
IIplus,
IIe,
EnhancedIIe
};
enum class DiskController {
None,
@@ -26,7 +28,7 @@ struct Target: public ::Analyser::Static::Target {
ThirteenSector
};
Model model = Model::IIplus;
Model model = Model::IIe;
DiskController disk_controller = DiskController::None;
};

View File

@@ -17,13 +17,8 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
// only one mapped item is allowed
if(segments.size() != 1) continue;
// which must be 8, 12, 16, 24 or 32 kb in size
const Storage::Cartridge::Cartridge::Segment &segment = segments.front();
const std::size_t data_size = segment.data.size();
const std::size_t overflow = data_size&8191;
if(overflow > 8 && overflow != 512 && (data_size != 12*1024)) continue;
if(data_size < 8192) continue;
// the two bytes that will be first must be 0xaa and 0x55, either way around
auto *start = &segment.data[0];
@@ -34,19 +29,24 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
if(start[0] == start[1]) continue;
// probability of a random binary blob that isn't a Coleco ROM proceeding to here is 1 - 1/32768.
if(!overflow) {
coleco_cartridges.push_back(cartridge);
// Round up to the next multiple of 8kb if this image is less than 32kb. Otherwise round down if
// this image is within a short distance of 32kb.
std::vector<Storage::Cartridge::Cartridge::Segment> output_segments;
size_t target_size;
if(data_size >= 32*1024 && data_size < 32*1024 + 512) {
target_size = 32 * 1024;
} else {
// Size down to a multiple of 8kb and apply the start address.
std::vector<Storage::Cartridge::Cartridge::Segment> output_segments;
std::vector<uint8_t> truncated_data;
std::vector<uint8_t>::difference_type truncated_size = static_cast<std::vector<uint8_t>::difference_type>(segment.data.size()) & ~8191;
truncated_data.insert(truncated_data.begin(), segment.data.begin(), segment.data.begin() + truncated_size);
output_segments.emplace_back(0x8000, truncated_data);
coleco_cartridges.emplace_back(new Storage::Cartridge::Cartridge(output_segments));
target_size = data_size + ((8192 - (data_size & 8191)) & 8191);
}
std::vector<uint8_t> truncated_data;
truncated_data = segment.data;
truncated_data.resize(target_size);
output_segments.emplace_back(0x8000, truncated_data);
coleco_cartridges.emplace_back(new Storage::Cartridge::Cartridge(output_segments));
}
return coleco_cartridges;

View File

@@ -23,5 +23,4 @@ TargetList GetTargets(const Media &media, const std::string &file_name, TargetPl
}
}
#endif /* StaticAnalyser_hpp */

View File

@@ -13,6 +13,7 @@
#include "Tape.hpp"
#include "Target.hpp"
#include "../../../Storage/Cartridge/Encodings/CommodoreROM.hpp"
#include "../../../Outputs/Log.hpp"
#include <algorithm>
#include <sstream>
@@ -80,7 +81,7 @@ Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media
target->memory_model = Target::MemoryModel::Unexpanded;
std::ostringstream string_stream;
string_stream << "LOAD\"" << (is_disk ? "*" : "") << "\"," << device << ",";
if(files.front().is_basic()) {
if(files.front().is_basic()) {
string_stream << "0";
} else {
string_stream << "1";
@@ -91,7 +92,7 @@ Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media
// make a first guess based on loading address
switch(files.front().starting_address) {
default:
printf("Starting address %04x?\n", files.front().starting_address);
LOG("Unrecognised loading address for Commodore program: " << PADHEX(4) << files.front().starting_address);
case 0x1001:
target->memory_model = Target::MemoryModel::Unexpanded;
break;

View File

@@ -63,9 +63,9 @@ class Accessor {
#define z(v) (v & 7)
Instruction::Condition condition_table[] = {
Instruction::Condition::NZ, Instruction::Condition::Z,
Instruction::Condition::NC, Instruction::Condition::C,
Instruction::Condition::PO, Instruction::Condition::PE,
Instruction::Condition::NZ, Instruction::Condition::Z,
Instruction::Condition::NC, Instruction::Condition::C,
Instruction::Condition::PO, Instruction::Condition::PE,
Instruction::Condition::P, Instruction::Condition::M
};

View File

@@ -285,7 +285,12 @@ Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(const Media &medi
}
}
// Region selection: for now, this as simple as:
// "If a tape is involved, be European. Otherwise be American (i.e. English, but 60Hz)".
target->region = target->media.tapes.empty() ? Target::Region::USA : Target::Region::Europe;
// Blindly accept disks for now.
// TODO: how to spot an MSX disk?
target->media.disks = media.disks;
target->has_disk_drive = !media.disks.empty();

View File

@@ -19,6 +19,12 @@ namespace MSX {
struct Target: public ::Analyser::Static::Target {
bool has_disk_drive = false;
std::string loading_command;
enum class Region {
Japan,
USA,
Europe
} region = Region::USA;
};
}

View File

@@ -0,0 +1,26 @@
//
// StaticAnalyser.cpp
// Clock Signal
//
// Created by Thomas Harte on 02/06/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#include "StaticAnalyser.hpp"
#include "Target.hpp"
Analyser::Static::TargetList Analyser::Static::Macintosh::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
// This analyser can comprehend disks only.
if(media.disks.empty()) return {};
// If there is at least one disk, wave it through.
Analyser::Static::TargetList targets;
using Target = Analyser::Static::Macintosh::Target;
auto *target = new Target;
target->machine = Analyser::Machine::Macintosh;
target->media = media;
targets.push_back(std::unique_ptr<Analyser::Static::Target>(target));
return targets;
}

View File

@@ -0,0 +1,27 @@
//
// StaticAnalyser.hpp
// Clock Signal
//
// Created by Thomas Harte on 02/06/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_Macintosh_StaticAnalyser_hpp
#define Analyser_Static_Macintosh_StaticAnalyser_hpp
#include "../StaticAnalyser.hpp"
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser {
namespace Static {
namespace Macintosh {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}
}
}
#endif /* Analyser_Static_Macintosh_StaticAnalyser_hpp */

View File

@@ -0,0 +1,31 @@
//
// Target.hpp
// Clock Signal
//
// Created by Thomas Harte on 03/06/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_Macintosh_Target_h
#define Analyser_Static_Macintosh_Target_h
namespace Analyser {
namespace Static {
namespace Macintosh {
struct Target: public ::Analyser::Static::Target {
enum class Model {
Mac128k,
Mac512k,
Mac512ke,
MacPlus
};
Model model = Model::Mac512ke;
};
}
}
}
#endif /* Analyser_Static_Macintosh_Target_h */

View File

@@ -0,0 +1,82 @@
//
// StaticAnalyser.cpp
// Clock Signal
//
// Created by Thomas Harte on 20/09/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#include "StaticAnalyser.hpp"
#include "Target.hpp"
#include <algorithm>
#include <cstring>
Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
if(media.cartridges.empty())
return {};
TargetList targets;
std::unique_ptr<Target> target(new Target);
target->machine = Machine::MasterSystem;
// Files named .sg are treated as for the SG1000; otherwise assume a Master System.
if(file_name.size() >= 2 && *(file_name.end() - 2) == 's' && *(file_name.end() - 1) == 'g') {
target->model = Target::Model::SG1000;
} else {
target->model = Target::Model::MasterSystem;
}
// If this is a Master System title, look for a ROM header.
if(target->model == Target::Model::MasterSystem) {
const auto &data = media.cartridges.front()->get_segments()[0].data;
// First try to locate a header.
size_t header_offset = 0;
size_t potential_offsets[] = {0x1ff0, 0x3ff0, 0x7ff0};
for(auto potential_offset: potential_offsets) {
if(!memcmp(&data[potential_offset], "TMR SEGA", 8)) {
header_offset = potential_offset;
break;
}
}
// If a header was found, use it to crib region.
if(header_offset) {
// Treat export titles as European by default; decline to
// do so only if (US) or (NTSC) is in the file name.
const uint8_t region = data[header_offset + 0x0f] >> 4;
switch(region) {
default: break;
case 4: {
std::string lowercase_name = file_name;
std::transform(lowercase_name.begin(), lowercase_name.end(), lowercase_name.begin(), ::tolower);
if(lowercase_name.find("(jp)") == std::string::npos) {
target->region =
(lowercase_name.find("(us)") == std::string::npos &&
lowercase_name.find("(ntsc)") == std::string::npos) ? Target::Region::Europe : Target::Region::USA;
}
} break;
}
// Also check for a Codemasters header.
// If one is found, set the paging scheme appropriately.
const uint16_t inverse_checksum = uint16_t(0x10000 - (data[0x7fe6] | (data[0x7fe7] << 8)));
if(
data[0x7fe3] >= 0x87 && data[0x7fe3] < 0x96 && // i.e. game is dated between 1987 and 1996
(inverse_checksum&0xff) == data[0x7fe8] &&
(inverse_checksum >> 8) == data[0x7fe9] && // i.e. the standard checksum appears to be present
!data[0x7fea] && !data[0x7feb] && !data[0x7fec] && !data[0x7fed] && !data[0x7fee] && !data[0x7fef]
) {
target->paging_scheme = Target::PagingScheme::Codemasters;
target->model = Target::Model::MasterSystem2;
}
}
}
target->media.cartridges = media.cartridges;
targets.push_back(std::move(target));
return targets;
}

View File

@@ -0,0 +1,26 @@
//
// StaticAnalyser.hpp
// Clock Signal
//
// Created by Thomas Harte on 20/09/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef StaticAnalyser_Sega_StaticAnalyser_hpp
#define StaticAnalyser_Sega_StaticAnalyser_hpp
#include "../StaticAnalyser.hpp"
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser {
namespace Static {
namespace Sega {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}
}
}
#endif /* StaticAnalyser_hpp */

View File

@@ -0,0 +1,46 @@
//
// Target.hpp
// Clock Signal
//
// Created by Thomas Harte on 23/09/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_Sega_Target_h
#define Analyser_Static_Sega_Target_h
namespace Analyser {
namespace Static {
namespace Sega {
struct Target: public ::Analyser::Static::Target {
enum class Model {
SG1000,
MasterSystem,
MasterSystem2,
};
enum class Region {
Japan,
USA,
Europe,
Brazil
};
enum class PagingScheme {
Sega,
Codemasters
};
Model model = Model::MasterSystem;
Region region = Region::Japan;
PagingScheme paging_scheme = PagingScheme::Sega;
};
#define is_master_system(v) v >= Analyser::Static::Sega::Target::Model::MasterSystem
}
}
}
#endif /* Analyser_Static_Sega_Target_h */

View File

@@ -21,8 +21,10 @@
#include "Coleco/StaticAnalyser.hpp"
#include "Commodore/StaticAnalyser.hpp"
#include "DiskII/StaticAnalyser.hpp"
#include "Macintosh/StaticAnalyser.hpp"
#include "MSX/StaticAnalyser.hpp"
#include "Oric/StaticAnalyser.hpp"
#include "Sega/StaticAnalyser.hpp"
#include "ZX8081/StaticAnalyser.hpp"
// Cartridges
@@ -34,6 +36,7 @@
#include "../../Storage/Disk/DiskImage/Formats/AppleDSK.hpp"
#include "../../Storage/Disk/DiskImage/Formats/CPCDSK.hpp"
#include "../../Storage/Disk/DiskImage/Formats/D64.hpp"
#include "../../Storage/Disk/DiskImage/Formats/MacintoshIMG.hpp"
#include "../../Storage/Disk/DiskImage/Formats/G64.hpp"
#include "../../Storage/Disk/DiskImage/Formats/DMK.hpp"
#include "../../Storage/Disk/DiskImage/Formats/HFE.hpp"
@@ -84,34 +87,37 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
TryInsert(list, class, platforms) \
}
Format("80", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 80
Format("81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 81
Format("a26", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // A26
Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn) // ADF
Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::AllCartridge) // BIN
Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX) // CAS
Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC) // CDT
Format("col", result.cartridges, Cartridge::BinaryDump, TargetPlatform::ColecoVision) // COL
Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape) // CSW
Format("d64", result.disks, Disk::DiskImageHolder<Storage::Disk::D64>, TargetPlatform::Commodore) // D64
Format("dmk", result.disks, Disk::DiskImageHolder<Storage::Disk::DMK>, TargetPlatform::MSX) // DMK
Format("do", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // DO
Format("dsd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // DSD
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::CPCDSK>, TargetPlatform::AmstradCPC) // DSK (Amstrad CPC)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // DSK (Apple)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MSXDSK>, TargetPlatform::MSX) // DSK (MSX)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::OricMFMDSK>, TargetPlatform::Oric) // DSK (Oric)
Format("g64", result.disks, Disk::DiskImageHolder<Storage::Disk::G64>, TargetPlatform::Commodore) // G64
Format("80", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 80
Format("81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 81
Format("a26", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // A26
Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn) // ADF
Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::AllCartridge) // BIN (cartridge dump)
Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX) // CAS
Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC) // CDT
Format("col", result.cartridges, Cartridge::BinaryDump, TargetPlatform::ColecoVision) // COL
Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape) // CSW
Format("d64", result.disks, Disk::DiskImageHolder<Storage::Disk::D64>, TargetPlatform::Commodore) // D64
Format("dmk", result.disks, Disk::DiskImageHolder<Storage::Disk::DMK>, TargetPlatform::MSX) // DMK
Format("do", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // DO
Format("dsd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // DSD
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::CPCDSK>, TargetPlatform::AmstradCPC) // DSK (Amstrad CPC)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // DSK (Apple II)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // DSK (Macintosh)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MSXDSK>, TargetPlatform::MSX) // DSK (MSX)
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::OricMFMDSK>, TargetPlatform::Oric) // DSK (Oric)
Format("g64", result.disks, Disk::DiskImageHolder<Storage::Disk::G64>, TargetPlatform::Commodore) // G64
Format( "hfe",
result.disks,
Disk::DiskImageHolder<Storage::Disk::HFE>,
TargetPlatform::Acorn | TargetPlatform::AmstradCPC | TargetPlatform::Commodore | TargetPlatform::Oric)
// HFE (TODO: switch to AllDisk once the MSX stops being so greedy)
Format("nib", result.disks, Disk::DiskImageHolder<Storage::Disk::NIB>, TargetPlatform::DiskII) // NIB
Format("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // O
Format("p", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P
Format("po", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // PO
Format("p81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P81
Format("img", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // IMG (DiskCopy 4.2)
Format("image", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // IMG (DiskCopy 4.2)
Format("nib", result.disks, Disk::DiskImageHolder<Storage::Disk::NIB>, TargetPlatform::DiskII) // NIB
Format("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // O
Format("p", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P
Format("po", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // PO
Format("p81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // P81
// PRG
if(extension == "prg") {
@@ -128,14 +134,16 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
Format( "rom",
result.cartridges,
Cartridge::BinaryDump,
TargetPlatform::AcornElectron | TargetPlatform::ColecoVision | TargetPlatform::MSX) // ROM
Format("ssd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // SSD
Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP (Commodore)
Format("tap", result.tapes, Tape::OricTAP, TargetPlatform::Oric) // TAP (Oric)
Format("tsx", result.tapes, Tape::TZX, TargetPlatform::MSX) // TSX
Format("tzx", result.tapes, Tape::TZX, TargetPlatform::ZX8081) // TZX
Format("uef", result.tapes, Tape::UEF, TargetPlatform::Acorn) // UEF (tape)
Format("woz", result.disks, Disk::DiskImageHolder<Storage::Disk::WOZ>, TargetPlatform::DiskII) // WOZ
TargetPlatform::AcornElectron | TargetPlatform::ColecoVision | TargetPlatform::MSX) // ROM
Format("sg", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega) // SG
Format("sms", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega) // SMS
Format("ssd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // SSD
Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP (Commodore)
Format("tap", result.tapes, Tape::OricTAP, TargetPlatform::Oric) // TAP (Oric)
Format("tsx", result.tapes, Tape::TZX, TargetPlatform::MSX) // TSX
Format("tzx", result.tapes, Tape::TZX, TargetPlatform::ZX8081) // TZX
Format("uef", result.tapes, Tape::UEF, TargetPlatform::Acorn) // UEF (tape)
Format("woz", result.disks, Disk::DiskImageHolder<Storage::Disk::WOZ>, TargetPlatform::DiskII) // WOZ
#undef Format
#undef Insert
@@ -170,8 +178,10 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) {
if(potential_platforms & TargetPlatform::ColecoVision) Append(Coleco);
if(potential_platforms & TargetPlatform::Commodore) Append(Commodore);
if(potential_platforms & TargetPlatform::DiskII) Append(DiskII);
if(potential_platforms & TargetPlatform::Macintosh) Append(Macintosh);
if(potential_platforms & TargetPlatform::MSX) Append(MSX);
if(potential_platforms & TargetPlatform::Oric) Append(Oric);
if(potential_platforms & TargetPlatform::Sega) Append(Sega);
if(potential_platforms & TargetPlatform::ZX8081) Append(ZX8081);
#undef Append

View File

@@ -9,6 +9,8 @@
#ifndef ClockReceiver_hpp
#define ClockReceiver_hpp
#include "ForceInline.hpp"
/*
Informal pattern for all classes that run from a clock cycle:
@@ -52,79 +54,92 @@
*/
template <class T> class WrappedInt {
public:
constexpr WrappedInt(int l) : length_(l) {}
constexpr WrappedInt() : length_(0) {}
forceinline constexpr WrappedInt(int l) noexcept : length_(l) {}
forceinline constexpr WrappedInt() noexcept : length_(0) {}
T &operator =(const T &rhs) {
forceinline T &operator =(const T &rhs) {
length_ = rhs.length_;
return *this;
}
T &operator +=(const T &rhs) {
forceinline T &operator +=(const T &rhs) {
length_ += rhs.length_;
return *static_cast<T *>(this);
}
T &operator -=(const T &rhs) {
forceinline T &operator -=(const T &rhs) {
length_ -= rhs.length_;
return *static_cast<T *>(this);
}
T &operator ++() {
forceinline T &operator ++() {
++ length_;
return *static_cast<T *>(this);
}
T &operator ++(int) {
forceinline T &operator ++(int) {
length_ ++;
return *static_cast<T *>(this);
}
T &operator --() {
forceinline T &operator --() {
-- length_;
return *static_cast<T *>(this);
}
T &operator --(int) {
forceinline T &operator --(int) {
length_ --;
return *static_cast<T *>(this);
}
T &operator %=(const T &rhs) {
forceinline T &operator *=(const T &rhs) {
length_ *= rhs.length_;
return *static_cast<T *>(this);
}
forceinline T &operator /=(const T &rhs) {
length_ /= rhs.length_;
return *static_cast<T *>(this);
}
forceinline T &operator %=(const T &rhs) {
length_ %= rhs.length_;
return *static_cast<T *>(this);
}
T &operator &=(const T &rhs) {
forceinline T &operator &=(const T &rhs) {
length_ &= rhs.length_;
return *static_cast<T *>(this);
}
constexpr T operator +(const T &rhs) const { return T(length_ + rhs.length_); }
constexpr T operator -(const T &rhs) const { return T(length_ - rhs.length_); }
forceinline constexpr T operator +(const T &rhs) const { return T(length_ + rhs.length_); }
forceinline constexpr T operator -(const T &rhs) const { return T(length_ - rhs.length_); }
constexpr T operator %(const T &rhs) const { return T(length_ % rhs.length_); }
constexpr T operator &(const T &rhs) const { return T(length_ & rhs.length_); }
forceinline constexpr T operator *(const T &rhs) const { return T(length_ * rhs.length_); }
forceinline constexpr T operator /(const T &rhs) const { return T(length_ / rhs.length_); }
constexpr T operator -() const { return T(- length_); }
forceinline constexpr T operator %(const T &rhs) const { return T(length_ % rhs.length_); }
forceinline constexpr T operator &(const T &rhs) const { return T(length_ & rhs.length_); }
constexpr bool operator <(const T &rhs) const { return length_ < rhs.length_; }
constexpr bool operator >(const T &rhs) const { return length_ > rhs.length_; }
constexpr bool operator <=(const T &rhs) const { return length_ <= rhs.length_; }
constexpr bool operator >=(const T &rhs) const { return length_ >= rhs.length_; }
constexpr bool operator ==(const T &rhs) const { return length_ == rhs.length_; }
constexpr bool operator !=(const T &rhs) const { return length_ != rhs.length_; }
forceinline constexpr T operator -() const { return T(- length_); }
constexpr bool operator !() const { return !length_; }
forceinline constexpr bool operator <(const T &rhs) const { return length_ < rhs.length_; }
forceinline constexpr bool operator >(const T &rhs) const { return length_ > rhs.length_; }
forceinline constexpr bool operator <=(const T &rhs) const { return length_ <= rhs.length_; }
forceinline constexpr bool operator >=(const T &rhs) const { return length_ >= rhs.length_; }
forceinline constexpr bool operator ==(const T &rhs) const { return length_ == rhs.length_; }
forceinline constexpr bool operator !=(const T &rhs) const { return length_ != rhs.length_; }
forceinline constexpr bool operator !() const { return !length_; }
// bool operator () is not supported because it offers an implicit cast to int, which is prone silently to permit misuse
constexpr int as_int() const { return length_; }
forceinline constexpr int as_int() const { return length_; }
/*!
Severs from @c this the effect of dividing by @c divisor; @c this will end up with
the value of @c this modulo @c divisor and @c divided by @c divisor is returned.
*/
T divide(const T &divisor) {
forceinline T divide(const T &divisor) {
T result(length_ / divisor.length_);
length_ %= divisor.length_;
return result;
@@ -134,10 +149,12 @@ template <class T> class WrappedInt {
Flushes the value in @c this. The current value is returned, and the internal value
is reset to zero.
*/
T flush() {
T result(length_);
length_ = 0;
return result;
template <typename Result> Result flush() {
// Jiggery pokery here; switching to function overloading avoids
// the namespace-level requirement for template specialisation.
Result r;
static_cast<T *>(this)->fill(r);
return r;
}
// operator int() is deliberately not provided, to avoid accidental subtitution of
@@ -150,51 +167,59 @@ template <class T> class WrappedInt {
/// Describes an integer number of whole cycles: pairs of clock signal transitions.
class Cycles: public WrappedInt<Cycles> {
public:
constexpr Cycles(int l) : WrappedInt<Cycles>(l) {}
constexpr Cycles() : WrappedInt<Cycles>() {}
constexpr Cycles(const Cycles &cycles) : WrappedInt<Cycles>(cycles.length_) {}
forceinline constexpr Cycles(int l) noexcept : WrappedInt<Cycles>(l) {}
forceinline constexpr Cycles() noexcept : WrappedInt<Cycles>() {}
forceinline constexpr Cycles(const Cycles &cycles) noexcept : WrappedInt<Cycles>(cycles.length_) {}
private:
friend WrappedInt;
void fill(Cycles &result) {
result.length_ = length_;
length_ = 0;
}
};
/// Describes an integer number of half cycles: single clock signal transitions.
class HalfCycles: public WrappedInt<HalfCycles> {
public:
constexpr HalfCycles(int l) : WrappedInt<HalfCycles>(l) {}
constexpr HalfCycles() : WrappedInt<HalfCycles>() {}
forceinline constexpr HalfCycles(int l) noexcept : WrappedInt<HalfCycles>(l) {}
forceinline constexpr HalfCycles() noexcept : WrappedInt<HalfCycles>() {}
constexpr HalfCycles(const Cycles cycles) : WrappedInt<HalfCycles>(cycles.as_int() * 2) {}
constexpr HalfCycles(const HalfCycles &half_cycles) : WrappedInt<HalfCycles>(half_cycles.length_) {}
forceinline constexpr HalfCycles(const Cycles &cycles) noexcept : WrappedInt<HalfCycles>(cycles.as_int() * 2) {}
forceinline constexpr HalfCycles(const HalfCycles &half_cycles) noexcept : WrappedInt<HalfCycles>(half_cycles.length_) {}
/// @returns The number of whole cycles completely covered by this span of half cycles.
constexpr Cycles cycles() const {
forceinline constexpr Cycles cycles() const {
return Cycles(length_ >> 1);
}
/// Flushes the whole cycles in @c this, subtracting that many from the total stored here.
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.
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.
*/
Cycles divide_cycles(const Cycles &divisor) {
HalfCycles half_divisor = HalfCycles(divisor);
Cycles result(length_ / half_divisor.length_);
forceinline Cycles divide_cycles(const Cycles &divisor) {
const HalfCycles half_divisor = HalfCycles(divisor);
const Cycles result(length_ / half_divisor.length_);
length_ %= half_divisor.length_;
return result;
}
private:
friend WrappedInt;
void fill(Cycles &result) {
result = Cycles(length_ >> 1);
length_ &= 1;
}
void fill(HalfCycles &result) {
result.length_ = length_;
length_ = 0;
}
};
// Create a specialisation of WrappedInt::flush for converting HalfCycles to Cycles
// without losing the fractional part.
/*!
If a component implements only run_for(Cycles), an owner can wrap it in HalfClockReceiver
automatically to gain run_for(HalfCycles).
@@ -203,9 +228,9 @@ template <class T> class HalfClockReceiver: public T {
public:
using T::T;
inline void run_for(const HalfCycles half_cycles) {
forceinline void run_for(const HalfCycles half_cycles) {
half_cycles_ += half_cycles;
T::run_for(half_cycles_.flush_cycles());
T::run_for(half_cycles_.flush<Cycles>());
}
private:

View File

@@ -0,0 +1,82 @@
//
// DeferredQueue.hpp
// Clock Signal
//
// Created by Thomas Harte on 23/08/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef DeferredQueue_h
#define DeferredQueue_h
#include <functional>
#include <vector>
/*!
A DeferredQueue maintains a list of ordered actions and the times at which
they should happen, and divides a total execution period up into the portions
that occur between those actions, triggering each action when it is reached.
*/
template <typename TimeUnit> class DeferredQueue {
public:
/// Constructs a DeferredQueue that will call target(period) in between deferred actions.
DeferredQueue(std::function<void(TimeUnit)> &&target) : target_(std::move(target)) {}
/*!
Schedules @c action to occur in @c delay units of time.
Actions must be scheduled in the order they will occur. It is undefined behaviour
to schedule them out of order.
*/
void defer(TimeUnit delay, const std::function<void(void)> &action) {
pending_actions_.emplace_back(delay, action);
}
/*!
Runs for @c length units of time.
The constructor-supplied target will be called with one or more periods that add up to @c length;
any scheduled actions will be called between periods.
*/
void run_for(TimeUnit length) {
// If there are no pending actions, just run for the entire length.
// This should be the normal branch.
if(pending_actions_.empty()) {
target_(length);
return;
}
// Divide the time to run according to the pending actions.
while(length > TimeUnit(0)) {
TimeUnit next_period = pending_actions_.empty() ? length : std::min(length, pending_actions_[0].delay);
target_(next_period);
length -= next_period;
off_t performances = 0;
for(auto &action: pending_actions_) {
action.delay -= next_period;
if(!action.delay) {
action.action();
++performances;
}
}
if(performances) {
pending_actions_.erase(pending_actions_.begin(), pending_actions_.begin() + performances);
}
}
}
private:
std::function<void(TimeUnit)> target_;
// The list of deferred actions.
struct DeferredAction {
TimeUnit delay;
std::function<void(void)> action;
DeferredAction(TimeUnit delay, const std::function<void(void)> &action) : delay(delay), action(std::move(action)) {}
};
std::vector<DeferredAction> pending_actions_;
};
#endif /* DeferredQueue_h */

View File

@@ -0,0 +1,110 @@
//
// JustInTime.hpp
// Clock Signal
//
// Created by Thomas Harte on 28/07/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef JustInTime_h
#define JustInTime_h
#include "../Concurrency/AsyncTaskQueue.hpp"
/*!
A JustInTimeActor holds (i) an embedded object with a run_for method; and (ii) an amount
of time since run_for was last called.
Time can be added using the += operator. The -> operator can be used to access the
embedded object. All time accumulated will be pushed to object before the pointer is returned.
Machines that accumulate HalfCycle time but supply to a Cycle-counted device may supply a
separate @c TargetTimeScale at template declaration.
*/
template <class T, class LocalTimeScale, class TargetTimeScale = LocalTimeScale> class JustInTimeActor {
public:
/// Constructs a new JustInTimeActor using the same construction arguments as the included object.
template<typename... Args> JustInTimeActor(Args&&... args) : object_(std::forward<Args>(args)...) {}
/// Adds time to the actor.
inline void operator += (const LocalTimeScale &rhs) {
time_since_update_ += rhs;
is_flushed_ = false;
}
/// Flushes all accumulated time and returns a pointer to the included object.
inline T *operator->() {
flush();
return &object_;
}
/// Returns a pointer to the included object without flushing time.
inline T *last_valid() {
return &object_;
}
/// Flushes all accumulated time.
inline void flush() {
if(!is_flushed_) object_.run_for(time_since_update_.template flush<TargetTimeScale>());
is_flushed_ = true;
}
private:
T object_;
LocalTimeScale time_since_update_;
bool is_flushed_ = true;
};
/*!
A AsyncJustInTimeActor acts like a JustInTimeActor but additionally contains an AsyncTaskQueue.
Any time the amount of accumulated time crosses a threshold provided at construction time,
the object will be updated on the AsyncTaskQueue.
*/
template <class T, class LocalTimeScale, class TargetTimeScale = LocalTimeScale> class AsyncJustInTimeActor {
public:
/// Constructs a new AsyncJustInTimeActor using the same construction arguments as the included object.
template<typename... Args> AsyncJustInTimeActor(TargetTimeScale threshold, Args&&... args) :
object_(std::forward<Args>(args)...),
threshold_(threshold) {}
/// Adds time to the actor.
inline void operator += (const LocalTimeScale &rhs) {
time_since_update_ += rhs;
if(time_since_update_ >= threshold_) {
time_since_update_ -= threshold_;
task_queue_.enqueue([this] () {
object_.run_for(threshold_);
});
}
is_flushed_ = false;
}
/// Flushes all accumulated time and returns a pointer to the included object.
inline T *operator->() {
flush();
return &object_;
}
/// Returns a pointer to the included object without flushing time.
inline T *last_valid() {
return &object_;
}
/// Flushes all accumulated time.
inline void flush() {
if(!is_flushed_) {
task_queue_.flush();
object_.run_for(time_since_update_.template flush<TargetTimeScale>());
is_flushed_ = true;
}
}
private:
T object_;
LocalTimeScale time_since_update_;
TargetTimeScale threshold_;
bool is_flushed_ = true;
Concurrency::AsyncTaskQueue task_queue_;
};
#endif /* JustInTime_h */

View File

@@ -141,22 +141,22 @@ void WD1770::run_for(const Cycles cycles) {
WAIT_FOR_EVENT(Event1770::IndexHoleTarget); \
status_.spin_up = true;
// +--------+----------+-------------------------+
// ! ! ! BITS !
// ! TYPE ! COMMAND ! 7 6 5 4 3 2 1 0 !
// +--------+----------+-------------------------+
// ! 1 ! Restore ! 0 0 0 0 h v r1 r0 !
// ! 1 ! Seek ! 0 0 0 1 h v r1 r0 !
// ! 1 ! Step ! 0 0 1 u h v r1 r0 !
// ! 1 ! Step-in ! 0 1 0 u h v r1 r0 !
// ! 1 ! Step-out ! 0 1 1 u h v r1 r0 !
// ! 2 ! Rd sectr ! 1 0 0 m h E 0 0 !
// ! 2 ! Wt sectr ! 1 0 1 m h E P a0 !
// ! 3 ! Rd addr ! 1 1 0 0 h E 0 0 !
// ! 3 ! Rd track ! 1 1 1 0 h E 0 0 !
// ! 3 ! Wt track ! 1 1 1 1 h E P 0 !
// ! 4 ! Forc int ! 1 1 0 1 i3 i2 i1 i0 !
// +--------+----------+-------------------------+
// +--------+----------+-------------------------+
// ! ! ! BITS !
// ! TYPE ! COMMAND ! 7 6 5 4 3 2 1 0 !
// +--------+----------+-------------------------+
// ! 1 ! Restore ! 0 0 0 0 h v r1 r0 !
// ! 1 ! Seek ! 0 0 0 1 h v r1 r0 !
// ! 1 ! Step ! 0 0 1 u h v r1 r0 !
// ! 1 ! Step-in ! 0 1 0 u h v r1 r0 !
// ! 1 ! Step-out ! 0 1 1 u h v r1 r0 !
// ! 2 ! Rd sectr ! 1 0 0 m h E 0 0 !
// ! 2 ! Wt sectr ! 1 0 1 m h E P a0 !
// ! 3 ! Rd addr ! 1 1 0 0 h E 0 0 !
// ! 3 ! Rd track ! 1 1 1 0 h E 0 0 !
// ! 3 ! Wt track ! 1 1 1 1 h E P 0 !
// ! 4 ! Forc int ! 1 1 0 1 i3 i2 i1 i0 !
// +--------+----------+-------------------------+
void WD1770::posit_event(int new_event_type) {
if(new_event_type == static_cast<int>(Event::IndexHole)) {
@@ -189,7 +189,6 @@ void WD1770::posit_event(int new_event_type) {
interesting_event_mask_ &= ~new_event_type;
}
Status new_status;
BEGIN_SECTION()
// Wait for a new command, branch to the appropriate handler.
@@ -211,7 +210,7 @@ void WD1770::posit_event(int new_event_type) {
status.interrupt_request = false;
});
LOG("Starting " << std::hex << command_ << std::endl);
LOG("Starting " << PADHEX(2) << int(command_));
if(!(command_ & 0x80)) goto begin_type_1;
if(!(command_ & 0x40)) goto begin_type_2;
@@ -221,16 +220,16 @@ void WD1770::posit_event(int new_event_type) {
/*
Type 1 entry point.
*/
// +--------+----------+-------------------------+
// ! ! ! BITS !
// ! TYPE ! COMMAND ! 7 6 5 4 3 2 1 0 !
// +--------+----------+-------------------------+
// ! 1 ! Restore ! 0 0 0 0 h v r1 r0 !
// ! 1 ! Seek ! 0 0 0 1 h v r1 r0 !
// ! 1 ! Step ! 0 0 1 u h v r1 r0 !
// ! 1 ! Step-in ! 0 1 0 u h v r1 r0 !
// ! 1 ! Step-out ! 0 1 1 u h v r1 r0 !
// +--------+----------+-------------------------+
// +--------+----------+-------------------------+
// ! ! ! BITS !
// ! TYPE ! COMMAND ! 7 6 5 4 3 2 1 0 !
// +--------+----------+-------------------------+
// ! 1 ! Restore ! 0 0 0 0 h v r1 r0 !
// ! 1 ! Seek ! 0 0 0 1 h v r1 r0 !
// ! 1 ! Step ! 0 0 1 u h v r1 r0 !
// ! 1 ! Step-in ! 0 1 0 u h v r1 r0 !
// ! 1 ! Step-out ! 0 1 1 u h v r1 r0 !
// +--------+----------+-------------------------+
begin_type_1:
// Set initial flags, skip spin-up if possible.
@@ -329,7 +328,7 @@ void WD1770::posit_event(int new_event_type) {
}
if(header_[0] == track_) {
LOG("Reached track " << std::dec << track_);
LOG("Reached track " << std::dec << int(track_));
update_status([] (Status &status) {
status.crc_error = false;
});
@@ -344,13 +343,13 @@ void WD1770::posit_event(int new_event_type) {
/*
Type 2 entry point.
*/
// +--------+----------+-------------------------+
// ! ! ! BITS !
// ! TYPE ! COMMAND ! 7 6 5 4 3 2 1 0 !
// +--------+----------+-------------------------+
// ! 2 ! Rd sectr ! 1 0 0 m h E 0 0 !
// ! 2 ! Wt sectr ! 1 0 1 m h E P a0 !
// +--------+----------+-------------------------+
// +--------+----------+-------------------------+
// ! ! ! BITS !
// ! TYPE ! COMMAND ! 7 6 5 4 3 2 1 0 !
// +--------+----------+-------------------------+
// ! 2 ! Rd sectr ! 1 0 0 m h E 0 0 !
// ! 2 ! Wt sectr ! 1 0 1 m h E P a0 !
// +--------+----------+-------------------------+
begin_type_2:
update_status([] (Status &status) {
@@ -398,18 +397,18 @@ void WD1770::posit_event(int new_event_type) {
READ_ID();
if(index_hole_count_ == 5) {
LOG("Failed to find sector " << std::dec << sector_);
LOG("Failed to find sector " << std::dec << int(sector_));
update_status([] (Status &status) {
status.record_not_found = true;
});
goto wait_for_command;
}
if(distance_into_section_ == 7) {
LOG("Considering " << std::dec << header_[0] << "/" << header_[2]);
LOG("Considering " << std::dec << int(header_[0]) << "/" << int(header_[2]));
set_data_mode(DataMode::Scanning);
if( header_[0] == track_ && header_[2] == sector_ &&
(has_motor_on_line() || !(command_&0x02) || ((command_&0x08) >> 3) == header_[1])) {
LOG("Found " << std::dec << header_[0] << "/" << header_[2]);
LOG("Found " << std::dec << int(header_[0]) << "/" << int(header_[2]));
if(get_crc_generator().get_value()) {
LOG("CRC error; back to searching");
update_status([] (Status &status) {
@@ -478,7 +477,7 @@ void WD1770::posit_event(int new_event_type) {
sector_++;
goto test_type2_write_protection;
}
LOG("Finished reading sector " << std::dec << sector_);
LOG("Finished reading sector " << std::dec << int(sector_));
goto wait_for_command;
}
goto type2_check_crc;
@@ -560,21 +559,21 @@ void WD1770::posit_event(int new_event_type) {
sector_++;
goto test_type2_write_protection;
}
LOG("Wrote sector " << std::dec << sector_);
LOG("Wrote sector " << std::dec << int(sector_));
goto wait_for_command;
/*
Type 3 entry point.
*/
// +--------+----------+-------------------------+
// ! ! ! BITS !
// ! TYPE ! COMMAND ! 7 6 5 4 3 2 1 0 !
// +--------+----------+-------------------------+
// ! 3 ! Rd addr ! 1 1 0 0 h E 0 0 !
// ! 3 ! Rd track ! 1 1 1 0 h E 0 0 !
// ! 3 ! Wt track ! 1 1 1 1 h E P 0 !
// +--------+----------+-------------------------+
// +--------+----------+-------------------------+
// ! ! ! BITS !
// ! TYPE ! COMMAND ! 7 6 5 4 3 2 1 0 !
// +--------+----------+-------------------------+
// ! 3 ! Rd addr ! 1 1 0 0 h E 0 0 !
// ! 3 ! Rd track ! 1 1 1 0 h E 0 0 !
// ! 3 ! Wt track ! 1 1 1 1 h E P 0 !
// +--------+----------+-------------------------+
begin_type_3:
update_status([] (Status &status) {
status.type = Status::Three;

View File

@@ -47,6 +47,12 @@ class PortHandler {
/// Sets the current logical value of the interrupt line.
void set_interrupt_status(bool status) {}
/// Provides a measure of time elapsed between other calls.
void run_for(HalfCycles duration) {}
/// Receives passed-on flush() calls from the 6522.
void flush() {}
};
/*!
@@ -71,26 +77,6 @@ class IRQDelegatePortHandler: public PortHandler {
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').
@@ -102,7 +88,7 @@ class MOS6522Base: public MOS6522Storage {
Consumers should derive their own curiously-recurring-template-pattern subclass,
implementing bus communications as required.
*/
template <class T> class MOS6522: public MOS6522Base {
template <class T> class MOS6522: public MOS6522Storage {
public:
MOS6522(T &bus_handler) noexcept : bus_handler_(bus_handler) {}
MOS6522(const MOS6522 &) = delete;
@@ -116,16 +102,44 @@ template <class T> class MOS6522: public MOS6522Base {
/*! @returns the bus handler. */
T &bus_handler();
/// 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();
/// Updates the port handler to the current time and then requests that it flush.
void flush();
private:
void do_phase1();
void do_phase2();
void shift_in();
void shift_out();
T &bus_handler_;
HalfCycles time_since_bus_handler_call_;
void access(int address);
uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output);
inline void reevaluate_interrupts();
/// Sets the current intended output value for the port and line;
/// if this affects the visible output, it will be passed to the handler.
void set_control_line_output(Port port, Line line, LineState value);
void evaluate_cb2_output();
};
}
}
#include "Implementation/6522Implementation.hpp"
}
}
#endif /* _522_hpp */

View File

@@ -1,116 +0,0 @@
//
// 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

@@ -6,29 +6,63 @@
// Copyright 2017 Thomas Harte. All rights reserved.
//
template <typename T> void MOS6522<T>::set_register(int address, uint8_t value) {
address &= 0xf;
#include "../../../Outputs/Log.hpp"
namespace MOS {
namespace MOS6522 {
template <typename T> void MOS6522<T>::access(int address) {
switch(address) {
case 0x0:
// In both handshake and pulse modes, CB2 goes low on any read or write of Port B.
if(handshake_modes_[1] != HandshakeMode::None) {
set_control_line_output(Port::B, Line::Two, LineState::Off);
}
break;
case 0xf:
case 0x1:
// In both handshake and pulse modes, CA2 goes low on any read or write of Port A.
if(handshake_modes_[0] != HandshakeMode::None) {
set_control_line_output(Port::A, Line::Two, LineState::Off);
}
break;
}
}
template <typename T> void MOS6522<T>::set_register(int address, uint8_t value) {
address &= 0xf;
access(address);
switch(address) {
case 0x0: // Write Port B.
// Store locally and communicate outwards.
registers_.output[1] = value;
bus_handler_.set_port_output(Port::B, value, registers_.data_direction[1]); // TODO: handshake
bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
bus_handler_.set_port_output(Port::B, value, registers_.data_direction[1]);
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | ((registers_.peripheral_control&0x20) ? 0 : InterruptFlag::CB2ActiveEdge));
reevaluate_interrupts();
break;
case 0xf:
case 0x1:
case 0x1: // Write Port A.
registers_.output[0] = value;
bus_handler_.set_port_output(Port::A, value, registers_.data_direction[0]); // TODO: handshake
bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
bus_handler_.set_port_output(Port::A, value, registers_.data_direction[0]);
if(handshake_modes_[1] != HandshakeMode::None) {
set_control_line_output(Port::A, Line::Two, LineState::Off);
}
registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | ((registers_.peripheral_control&0x02) ? 0 : InterruptFlag::CB2ActiveEdge));
reevaluate_interrupts();
break;
case 0x2:
case 0x2: // Port B direction.
registers_.data_direction[1] = value;
break;
case 0x3:
case 0x3: // Port A direction.
registers_.data_direction[0] = value;
break;
@@ -54,32 +88,57 @@ template <typename T> void MOS6522<T>::set_register(int address, uint8_t value)
break;
// Shift
case 0xa: registers_.shift = value; break;
case 0xa:
registers_.shift = value;
shift_bits_remaining_ = 8;
registers_.interrupt_flags &= ~InterruptFlag::ShiftRegister;
reevaluate_interrupts();
break;
// Control
case 0xb:
registers_.auxiliary_control = value;
evaluate_cb2_output();
break;
case 0xc:
// printf("Peripheral control %02x\n", value);
case 0xc: {
// const auto old_peripheral_control = registers_.peripheral_control;
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;
int shift = 0;
for(int port = 0; port < 2; ++port) {
handshake_modes_[port] = HandshakeMode::None;
switch((value >> shift) & 0x0e) {
default: break;
case 0x00: // Negative interrupt input; set Cx2 interrupt on negative Cx2 transition, clear on access to Port x register.
case 0x02: // Independent negative interrupt input; set Cx2 interrupt on negative transition, don't clear automatically.
case 0x04: // Positive interrupt input; set Cx2 interrupt on positive Cx2 transition, clear on access to Port x register.
case 0x06: // Independent positive interrupt input; set Cx2 interrupt on positive transition, don't clear automatically.
set_control_line_output(Port(port), Line::Two, LineState::Input);
break;
case 0x08: // Handshake: set Cx2 to low on any read or write of Port x; set to high on an active transition of Cx1.
handshake_modes_[port] = HandshakeMode::Handshake;
set_control_line_output(Port(port), Line::Two, LineState::Off); // At a guess.
break;
case 0x0a: // Pulse output: Cx2 is low for one cycle following a read or write of Port x.
handshake_modes_[port] = HandshakeMode::Pulse;
set_control_line_output(Port(port), Line::Two, LineState::On);
break;
case 0x0c: // Manual output: Cx2 low.
set_control_line_output(Port(port), Line::Two, LineState::Off);
break;
case 0x0e: // Manual output: Cx2 high.
set_control_line_output(Port(port), Line::Two, LineState::On);
break;
}
shift += 4;
}
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;
} break;
// Interrupt control
case 0xd:
@@ -98,12 +157,13 @@ template <typename T> void MOS6522<T>::set_register(int address, uint8_t value)
template <typename T> uint8_t MOS6522<T>::get_register(int address) {
address &= 0xf;
access(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 0xf:
case 0x1:
registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | InterruptFlag::CA2ActiveEdge);
reevaluate_interrupts();
@@ -128,7 +188,11 @@ template <typename T> uint8_t MOS6522<T>::get_register(int address) {
return registers_.timer[1] & 0x00ff;
case 0x9: return registers_.timer[1] >> 8;
case 0xa: return registers_.shift;
case 0xa:
shift_bits_remaining_ = 8;
registers_.interrupt_flags &= ~InterruptFlag::ShiftRegister;
reevaluate_interrupts();
return registers_.shift;
case 0xb: return registers_.auxiliary_control;
case 0xc: return registers_.peripheral_control;
@@ -141,7 +205,8 @@ template <typename T> uint8_t MOS6522<T>::get_register(int address) {
}
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);
bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
const uint8_t input = bus_handler_.get_port_input(port);
return (input & ~output_mask) | (output & output_mask);
}
@@ -154,6 +219,238 @@ 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_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
bus_handler_.set_interrupt_status(new_interrupt_status);
}
}
template <typename T> void MOS6522<T>::set_control_line_input(Port port, Line line, bool value) {
switch(line) {
case Line::One:
if(value != control_inputs_[port].lines[line]) {
// In handshake mode, any transition on C[A/B]1 sets output high on C[A/B]2.
if(handshake_modes_[port] == HandshakeMode::Handshake) {
set_control_line_output(port, Line::Two, LineState::On);
}
// Set the proper transition interrupt bit if enabled.
if(value == !!(registers_.peripheral_control & (port ? 0x10 : 0x01))) {
registers_.interrupt_flags |= port ? InterruptFlag::CB1ActiveEdge : InterruptFlag::CA1ActiveEdge;
reevaluate_interrupts();
}
// If this is a transition on CB1, consider updating the shift register.
// TODO: and at least one full clock since the shift register was written?
if(port == Port::B) {
switch(shift_mode()) {
default: break;
case ShiftMode::InUnderCB1: if(value) shift_in(); break; // Shifts in are captured on a low-to-high transition.
case ShiftMode::OutUnderCB1: if(!value) shift_out(); break; // Shifts out are updated on a high-to-low transition.
}
}
}
control_inputs_[port].lines[line] = value;
break;
case Line::Two:
if( value != control_inputs_[port].lines[line] && // 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].lines[line] = value;
break;
}
}
template <typename T> void MOS6522<T>::do_phase2() {
++ time_since_bus_handler_call_;
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;
}
// In pulse modes, CA2 and CB2 go high again on the next clock edge.
if(handshake_modes_[1] == HandshakeMode::Pulse) {
set_control_line_output(Port::B, Line::Two, LineState::On);
}
if(handshake_modes_[0] == HandshakeMode::Pulse) {
set_control_line_output(Port::A, Line::Two, LineState::On);
}
// If the shift register is shifting according to the input clock, do a shift.
switch(shift_mode()) {
default: break;
case ShiftMode::InUnderPhase2: shift_in(); break;
case ShiftMode::OutUnderPhase2: shift_out(); break;
}
}
template <typename T> void MOS6522<T>::do_phase1() {
++ time_since_bus_handler_call_;
// 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;
// If the shift register is shifting according to this timer, do a shift.
// TODO: "shift register is driven by only the low order 8 bits of timer 2"?
switch(shift_mode()) {
default: break;
case ShiftMode::InUnderT2: shift_in(); break;
case ShiftMode::OutUnderT2FreeRunning: shift_out(); break;
case ShiftMode::OutUnderT2: shift_out(); break; // TODO: present a clock on CB1.
}
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();
// Determine whether to reload.
if(registers_.auxiliary_control&0x40)
registers_.timer_needs_reload = true;
else
timer_is_running_[0] = false;
// Determine whether to toggle PB7.
if(registers_.auxiliary_control&0x80) {
registers_.output[1] ^= 0x80;
bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
bus_handler_.set_port_output(Port::B, registers_.output[1], registers_.data_direction[1]);
}
}
}
/*! Runs for a specified number of half cycles. */
template <typename T> void MOS6522<T>::run_for(const HalfCycles half_cycles) {
int number_of_half_cycles = half_cycles.as_int();
if(!number_of_half_cycles) return;
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;
}
}
template <typename T> void MOS6522<T>::flush() {
bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
bus_handler_.flush();
}
/*! Runs for a specified number of cycles. */
template <typename T> void MOS6522<T>::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. */
template <typename T> bool MOS6522<T>::get_interrupt_line() {
uint8_t interrupt_status = registers_.interrupt_flags & registers_.interrupt_enable & 0x7f;
return !!interrupt_status;
}
template <typename T> void MOS6522<T>::evaluate_cb2_output() {
// CB2 is a special case, being both the line the shift register can output to,
// and one that can be used as an input or handshaking output according to the
// peripheral control register.
// My guess: other CB2 functions work only if the shift register is disabled (?).
if(shift_mode() != ShiftMode::Disabled) {
// Shift register is enabled, one way or the other; but announce only output.
if(is_shifting_out()) {
// Output mode; set the level according to the current top of the shift register.
bus_handler_.set_control_line_output(Port::B, Line::Two, !!(registers_.shift & 0x80));
} else {
// Input mode.
bus_handler_.set_control_line_output(Port::B, Line::Two, true);
}
} else {
// Shift register is disabled.
bus_handler_.set_control_line_output(Port::B, Line::Two, control_outputs_[1].lines[1] != LineState::Off);
}
}
template <typename T> void MOS6522<T>::set_control_line_output(Port port, Line line, LineState value) {
if(port == Port::B && line == Line::Two) {
control_outputs_[port].lines[line] = value;
evaluate_cb2_output();
} else {
// Do nothing if unchanged.
if(value == control_outputs_[port].lines[line]) {
return;
}
control_outputs_[port].lines[line] = value;
if(value != LineState::Input) {
bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
bus_handler_.set_control_line_output(port, line, value != LineState::Off);
}
}
}
template <typename T> void MOS6522<T>::shift_in() {
registers_.shift = uint8_t((registers_.shift << 1) | (control_inputs_[1].lines[1] ? 1 : 0));
--shift_bits_remaining_;
if(!shift_bits_remaining_) {
registers_.interrupt_flags |= InterruptFlag::ShiftRegister;
reevaluate_interrupts();
}
}
template <typename T> void MOS6522<T>::shift_out() {
// When shifting out, the shift register rotates rather than strictly shifts.
// TODO: is that true for all modes?
if(shift_mode() == ShiftMode::OutUnderT2FreeRunning || shift_bits_remaining_) {
registers_.shift = uint8_t((registers_.shift << 1) | (registers_.shift >> 7));
evaluate_cb2_output();
--shift_bits_remaining_;
if(!shift_bits_remaining_) {
registers_.interrupt_flags |= InterruptFlag::ShiftRegister;
reevaluate_interrupts();
}
}
}
}
}

View File

@@ -21,7 +21,7 @@ class MOS6522Storage {
// The registers
struct Registers {
// "A low reset (RES) input clears all R6522 internal registers to logic 0"
// "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};
@@ -37,14 +37,27 @@ class MOS6522Storage {
bool timer_needs_reload = false;
} registers_;
// control state
// Control state.
struct {
bool line_one = false;
bool line_two = false;
bool lines[2] = {false, false};
} control_inputs_[2];
enum class LineState {
On, Off, Input
};
struct {
LineState lines[2] = {LineState::Input, LineState::Input};
} control_outputs_[2];
enum class HandshakeMode {
None,
Handshake,
Pulse
} handshake_modes_[2] = { HandshakeMode::None, HandshakeMode::None };
bool timer_is_running_[2] = {false, false};
bool last_posted_interrupt_status_ = false;
int shift_bits_remaining_ = 8;
enum InterruptFlag: uint8_t {
CA2ActiveEdge = 1 << 0,
@@ -55,6 +68,23 @@ class MOS6522Storage {
Timer2 = 1 << 5,
Timer1 = 1 << 6,
};
enum class ShiftMode {
Disabled = 0,
InUnderT2 = 1,
InUnderPhase2 = 2,
InUnderCB1 = 3,
OutUnderT2FreeRunning = 4,
OutUnderT2 = 5,
OutUnderPhase2 = 6,
OutUnderCB1 = 7
};
ShiftMode shift_mode() const {
return ShiftMode((registers_.auxiliary_control >> 2) & 7);
}
bool is_shifting_out() const {
return registers_.auxiliary_control & 0x10;
}
};
}

View File

@@ -34,7 +34,7 @@ class AudioGenerator: public ::Outputs::Speaker::SampleSource {
private:
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
unsigned int counters_[4] = {2, 1, 0, 0}; // create a slight phase offset for the three channels
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};
int16_t volume_ = 0;
@@ -64,23 +64,12 @@ template <class BusHandler> class MOS6560 {
public:
MOS6560(BusHandler &bus_handler) :
bus_handler_(bus_handler),
crt_(new Outputs::CRT::CRT(65*4, 4, Outputs::CRT::DisplayType::NTSC60, 2)),
crt_(65*4, 1, Outputs::Display::Type::NTSC60, Outputs::Display::InputDataType::Luminance8Phase8),
audio_generator_(audio_queue_),
speaker_(audio_generator_)
{
crt_->set_svideo_sampling_function(
"vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
"{"
"vec2 yc = texture(texID, coordinate).rg / vec2(255.0);"
"float phaseOffset = 6.283185308 * 2.0 * yc.y;"
"float chroma = step(yc.y, 0.75) * cos(phase + phaseOffset);"
"return vec2(yc.x, chroma);"
"}");
// default to s-video output
crt_->set_video_signal(Outputs::CRT::VideoSignal::SVideo);
crt_.set_display_type(Outputs::Display::DisplayType::SVideo);
// default to NTSC
set_output_mode(OutputMode::NTSC);
@@ -94,7 +83,8 @@ template <class BusHandler> class MOS6560 {
speaker_.set_input_rate(static_cast<float>(clock_rate / 4.0));
}
Outputs::CRT::CRT *get_crt() { return crt_.get(); }
void set_scan_target(Outputs::Display::ScanTarget *scan_target) { crt_.set_scan_target(scan_target); }
void set_display_type(Outputs::Display::DisplayType display_type) { crt_.set_display_type(display_type); }
Outputs::Speaker::Speaker *get_speaker() { return &speaker_; }
void set_high_frequency_cutoff(float cutoff) {
@@ -117,12 +107,12 @@ template <class BusHandler> class MOS6560 {
// Chrominances are encoded such that 0-128 is a complete revolution of phase;
// anything above 191 disables the colour subcarrier. Phase is relative to the
// colour burst, so 0 is green.
// colour burst, so 0 is green (NTSC) or blue/violet (PAL).
const uint8_t pal_chrominances[16] = {
255, 255, 37, 101,
19, 86, 123, 59,
46, 53, 37, 101,
19, 86, 123, 59,
255, 255, 90, 20,
96, 42, 8, 72,
84, 90, 90, 20,
96, 42, 8, 72,
};
const uint8_t ntsc_chrominances[16] = {
255, 255, 121, 57,
@@ -131,12 +121,12 @@ template <class BusHandler> class MOS6560 {
103, 42, 80, 16,
};
const uint8_t *chrominances;
Outputs::CRT::DisplayType display_type;
Outputs::Display::Type display_type;
switch(output_mode) {
default:
chrominances = pal_chrominances;
display_type = Outputs::CRT::DisplayType::PAL50;
display_type = Outputs::Display::Type::PAL50;
timing_.cycles_per_line = 71;
timing_.line_counter_increment_offset = 4;
timing_.final_line_increment_position = timing_.cycles_per_line - timing_.line_counter_increment_offset;
@@ -146,7 +136,7 @@ template <class BusHandler> class MOS6560 {
case OutputMode::NTSC:
chrominances = ntsc_chrominances;
display_type = Outputs::CRT::DisplayType::NTSC60;
display_type = Outputs::Display::Type::NTSC60;
timing_.cycles_per_line = 65;
timing_.line_counter_increment_offset = 40;
timing_.final_line_increment_position = 58;
@@ -155,14 +145,14 @@ template <class BusHandler> class MOS6560 {
break;
}
crt_->set_new_display_type(static_cast<unsigned int>(timing_.cycles_per_line*4), display_type);
crt_.set_new_display_type(timing_.cycles_per_line*4, display_type);
switch(output_mode) {
case OutputMode::PAL:
crt_->set_visible_area(Outputs::CRT::Rect(0.1f, 0.07f, 0.9f, 0.9f));
crt_.set_visible_area(Outputs::Display::Rect(0.1f, 0.07f, 0.9f, 0.9f));
break;
case OutputMode::NTSC:
crt_->set_visible_area(Outputs::CRT::Rect(0.05f, 0.05f, 0.9f, 0.9f));
crt_.set_visible_area(Outputs::Display::Rect(0.05f, 0.05f, 0.9f, 0.9f));
break;
}
@@ -284,17 +274,17 @@ template <class BusHandler> class MOS6560 {
// update the CRT
if(this_state_ != output_state_) {
switch(output_state_) {
case State::Sync: crt_->output_sync(cycles_in_state_ * 4); break;
case State::ColourBurst: crt_->output_colour_burst(cycles_in_state_ * 4, (is_odd_frame_ || is_odd_line_) ? 128 : 0); break;
case State::Border: output_border(cycles_in_state_ * 4); break;
case State::Pixels: crt_->output_data(cycles_in_state_ * 4); break;
case State::Sync: crt_.output_sync(cycles_in_state_ * 4); break;
case State::ColourBurst: crt_.output_colour_burst(cycles_in_state_ * 4, (is_odd_frame_ || is_odd_line_) ? 128 : 0); break;
case State::Border: output_border(cycles_in_state_ * 4); break;
case State::Pixels: crt_.output_data(cycles_in_state_ * 4); break;
}
output_state_ = this_state_;
cycles_in_state_ = 0;
pixel_pointer = nullptr;
if(output_state_ == State::Pixels) {
pixel_pointer = reinterpret_cast<uint16_t *>(crt_->allocate_write_area(260));
pixel_pointer = reinterpret_cast<uint16_t *>(crt_.begin_data(260));
}
}
cycles_in_state_++;
@@ -438,7 +428,7 @@ template <class BusHandler> class MOS6560 {
private:
BusHandler &bus_handler_;
std::unique_ptr<Outputs::CRT::CRT> crt_;
Outputs::CRT::CRT crt_;
Concurrency::DeferringAsyncTaskQueue audio_queue_;
AudioGenerator audio_generator_;
@@ -451,12 +441,12 @@ template <class BusHandler> class MOS6560 {
// register state
struct {
bool interlaced, tall_characters;
bool interlaced = false, tall_characters = false;
uint8_t first_column_location, first_row_location;
uint8_t number_of_columns, number_of_rows;
uint16_t character_cell_start_address, video_matrix_start_address;
uint16_t backgroundColour, borderColour, auxiliary_colour;
bool invertedCells;
bool invertedCells = false;
uint8_t direct_values[16];
} registers_;
@@ -465,7 +455,7 @@ template <class BusHandler> class MOS6560 {
enum State {
Sync, ColourBurst, Border, Pixels
} this_state_, output_state_;
unsigned int cycles_in_state_;
int cycles_in_state_;
// counters that cover an entire field
int horizontal_counter_ = 0, vertical_counter_ = 0;
@@ -493,7 +483,7 @@ template <class BusHandler> class MOS6560 {
}
// latches dictating start and length of drawing
bool vertical_drawing_latch_, horizontal_drawing_latch_;
bool vertical_drawing_latch_ = false, horizontal_drawing_latch_ = false;
int rows_this_field_, columns_this_line_;
// current drawing position counter
@@ -511,10 +501,10 @@ template <class BusHandler> class MOS6560 {
uint16_t colours_[16];
uint16_t *pixel_pointer;
void output_border(unsigned int number_of_cycles) {
uint16_t *colour_pointer = reinterpret_cast<uint16_t *>(crt_->allocate_write_area(1));
void output_border(int number_of_cycles) {
uint16_t *colour_pointer = reinterpret_cast<uint16_t *>(crt_.begin_data(1));
if(colour_pointer) *colour_pointer = registers_.borderColour;
crt_->output_level(number_of_cycles);
crt_.output_level(number_of_cycles);
}
struct {

268
Components/8530/z8530.cpp Normal file
View File

@@ -0,0 +1,268 @@
//
// 8530.cpp
// Clock Signal
//
// Created by Thomas Harte on 07/06/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#include "z8530.hpp"
#include "../../Outputs/Log.hpp"
using namespace Zilog::SCC;
void z8530::reset() {
// TODO.
}
bool z8530::get_interrupt_line() {
return
(master_interrupt_control_ & 0x8) &&
(
channels_[0].get_interrupt_line() ||
channels_[1].get_interrupt_line()
);
}
std::uint8_t z8530::read(int address) {
if(address & 2) {
// Read data register for channel
return 0x00;
} else {
// Read control register for channel.
uint8_t result = 0;
switch(pointer_) {
default:
result = channels_[address & 1].read(address & 2, pointer_);
break;
case 2: // Handled non-symmetrically between channels.
if(address & 1) {
LOG("[SCC] Unimplemented: register 2 status bits");
} else {
result = interrupt_vector_;
// Modify the vector if permitted.
// if(master_interrupt_control_ & 1) {
for(int port = 0; port < 2; ++port) {
// TODO: the logic below assumes that DCD is the only implemented interrupt. Fix.
if(channels_[port].get_interrupt_line()) {
const uint8_t shift = 1 + 3*((master_interrupt_control_ & 0x10) >> 4);
const uint8_t mask = uint8_t(~(7 << shift));
result = uint8_t(
(result & mask) |
((1 | ((port == 1) ? 4 : 0)) << shift)
);
break;
}
}
// }
}
break;
}
pointer_ = 0;
update_delegate();
return result;
}
return 0x00;
}
void z8530::write(int address, std::uint8_t value) {
if(address & 2) {
// Write data register for channel.
} else {
// Write control register for channel.
// Most registers are per channel, but a couple are shared; sever
// them here.
switch(pointer_) {
default:
channels_[address & 1].write(address & 2, pointer_, value);
break;
case 2: // Interrupt vector register; shared between both channels.
interrupt_vector_ = value;
LOG("[SCC] Interrupt vector set to " << PADHEX(2) << int(value));
break;
case 9: // Master interrupt and reset register; also shared between both channels.
LOG("[SCC] Master interrupt and reset register: " << PADHEX(2) << int(value));
master_interrupt_control_ = value;
break;
}
// The pointer number resets to 0 after every access, but if it is zero
// then crib at least the next set of pointer bits (which, similarly, are shared
// between the two channels).
if(pointer_) {
pointer_ = 0;
} else {
// The lowest three bits are the lowest three bits of the pointer.
pointer_ = value & 7;
// If the command part of the byte is a 'point high', also set the
// top bit of the pointer.
if(((value >> 3)&7) == 1) {
pointer_ |= 8;
}
}
}
update_delegate();
}
void z8530::set_dcd(int port, bool level) {
channels_[port].set_dcd(level);
update_delegate();
}
// MARK: - Channel implementations
uint8_t z8530::Channel::read(bool data, uint8_t pointer) {
// If this is a data read, just return it.
if(data) {
return data_;
} else {
// Otherwise, this is a control read...
switch(pointer) {
default:
LOG("[SCC] Unrecognised control read from register " << int(pointer));
return 0x00;
case 0:
return dcd_ ? 0x8 : 0x0;
case 0xf:
return external_interrupt_status_;
}
}
return 0x00;
}
void z8530::Channel::write(bool data, uint8_t pointer, uint8_t value) {
if(data) {
data_ = value;
return;
} else {
switch(pointer) {
default:
LOG("[SCC] Unrecognised control write: " << PADHEX(2) << int(value) << " to register " << int(pointer));
break;
case 0x0: // Write register 0 — CRC reset and other functions.
// Decode CRC reset instructions.
switch(value >> 6) {
default: /* Do nothing. */ break;
case 1:
LOG("[SCC] TODO: reset Rx CRC checker.");
break;
case 2:
LOG("[SCC] TODO: reset Tx CRC checker.");
break;
case 3:
LOG("[SCC] TODO: reset Tx underrun/EOM latch.");
break;
}
// Decode command code.
switch((value >> 3)&7) {
default: /* Do nothing. */ break;
case 2:
// LOG("[SCC] reset ext/status interrupts.");
external_status_interrupt_ = false;
external_interrupt_status_ = 0;
break;
case 3:
LOG("[SCC] TODO: send abort (SDLC).");
break;
case 4:
LOG("[SCC] TODO: enable interrupt on next Rx character.");
break;
case 5:
LOG("[SCC] TODO: reset Tx interrupt pending.");
break;
case 6:
LOG("[SCC] TODO: reset error.");
break;
case 7:
LOG("[SCC] TODO: reset highest IUS.");
break;
}
break;
case 0x1: // Write register 1 — Transmit/Receive Interrupt and Data Transfer Mode Definition.
interrupt_mask_ = value;
break;
case 0x4: // Write register 4 — Transmit/Receive Miscellaneous Parameters and Modes.
// Bits 0 and 1 select parity mode.
if(!(value&1)) {
parity_ = Parity::Off;
} else {
parity_ = (value&2) ? Parity::Even : Parity::Odd;
}
// Bits 2 and 3 select stop bits.
switch((value >> 2)&3) {
default: stop_bits_ = StopBits::Synchronous; break;
case 1: stop_bits_ = StopBits::OneBit; break;
case 2: stop_bits_ = StopBits::OneAndAHalfBits; break;
case 3: stop_bits_ = StopBits::TwoBits; break;
}
// Bits 4 and 5 pick a sync mode.
switch((value >> 4)&3) {
default: sync_mode_ = Sync::Monosync; break;
case 1: sync_mode_ = Sync::Bisync; break;
case 2: sync_mode_ = Sync::SDLC; break;
case 3: sync_mode_ = Sync::External; break;
}
// Bits 6 and 7 select a clock rate multiplier, unless synchronous
// mode is enabled (and this is ignored if sync mode is external).
if(stop_bits_ == StopBits::Synchronous) {
clock_rate_multiplier_ = 1;
} else {
switch((value >> 6)&3) {
default: clock_rate_multiplier_ = 1; break;
case 1: clock_rate_multiplier_ = 16; break;
case 2: clock_rate_multiplier_ = 32; break;
case 3: clock_rate_multiplier_ = 64; break;
}
}
break;
case 0xf: // Write register 15 — External/Status Interrupt Control.
external_interrupt_mask_ = value;
break;
}
}
}
void z8530::Channel::set_dcd(bool level) {
if(dcd_ == level) return;
dcd_ = level;
if(external_interrupt_mask_ & 0x8) {
external_status_interrupt_ = true;
external_interrupt_status_ |= 0x8;
}
}
bool z8530::Channel::get_interrupt_line() {
return
(interrupt_mask_ & 1) && external_status_interrupt_;
// TODO: other potential causes of an interrupt.
}
void z8530::update_delegate() {
const bool interrupt_line = get_interrupt_line();
if(interrupt_line != previous_interrupt_line_) {
previous_interrupt_line_ = interrupt_line;
if(delegate_) delegate_->did_change_interrupt_status(this, interrupt_line);
}
}

99
Components/8530/z8530.hpp Normal file
View File

@@ -0,0 +1,99 @@
//
// z8530.hpp
// Clock Signal
//
// Created by Thomas Harte on 07/06/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef z8530_hpp
#define z8530_hpp
#include <cstdint>
namespace Zilog {
namespace SCC {
/*!
Models the Zilog 8530 SCC, a serial adaptor.
*/
class z8530 {
public:
/*
**Interface for emulated machine.**
Notes on addressing below:
There's no inherent ordering of the two 'address' lines,
A/B and C/D, but the methods below assume:
A/B = A0
C/D = A1
*/
std::uint8_t read(int address);
void write(int address, std::uint8_t value);
void reset();
bool get_interrupt_line();
struct Delegate {
virtual void did_change_interrupt_status(z8530 *, bool new_status) = 0;
};
void set_delegate(Delegate *delegate) {
delegate_ = delegate;
}
/*
**Interface for serial port input.**
*/
void set_dcd(int port, bool level);
private:
class Channel {
public:
uint8_t read(bool data, uint8_t pointer);
void write(bool data, uint8_t pointer, uint8_t value);
void set_dcd(bool level);
bool get_interrupt_line();
private:
uint8_t data_ = 0xff;
enum class Parity {
Even, Odd, Off
} parity_ = Parity::Off;
enum class StopBits {
Synchronous, OneBit, OneAndAHalfBits, TwoBits
} stop_bits_ = StopBits::Synchronous;
enum class Sync {
Monosync, Bisync, SDLC, External
} sync_mode_ = Sync::Monosync;
int clock_rate_multiplier_ = 1;
uint8_t interrupt_mask_ = 0; // i.e. Write Register 0x1.
uint8_t external_interrupt_mask_ = 0; // i.e. Write Register 0xf.
bool external_status_interrupt_ = false;
uint8_t external_interrupt_status_ = 0;
bool dcd_ = false;
} channels_[2];
uint8_t pointer_ = 0;
uint8_t interrupt_vector_ = 0;
uint8_t master_interrupt_control_ = 0;
bool previous_interrupt_line_ = false;
void update_delegate();
Delegate *delegate_ = nullptr;
};
}
}
#endif /* z8530_hpp */

File diff suppressed because it is too large Load Diff

View File

@@ -17,6 +17,7 @@
#include <cstdint>
namespace TI {
namespace TMS {
/*!
Provides emulation of the TMS9918a, TMS9928 and TMS9929. Likely in the future to be the
@@ -29,30 +30,22 @@ namespace TI {
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 {
class TMS9918: public Base {
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();
/*! Sets the scan target this TMS will post content to. */
void set_scan_target(Outputs::Display::ScanTarget *);
/*! Sets the type of display the CRT will request. */
void set_display_type(Outputs::Display::DisplayType);
/*!
Runs the VCP for the number of cycles indicate; it is an implicit assumption of the code
@@ -66,6 +59,15 @@ class TMS9918: public TMS9918Base {
/*! Gets a register value. */
uint8_t get_register(int address);
/*! Gets the current scan line; provided by the Master System only. */
uint8_t get_current_line();
/*! Gets the current latched horizontal counter; provided by the Master System only. */
uint8_t get_latched_horizontal_counter();
/*! Latches the current horizontal counter. */
void latch_horizontal_counter();
/*!
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.
@@ -75,12 +77,23 @@ class TMS9918: public TMS9918Base {
*/
HalfCycles get_time_until_interrupt();
/*!
Returns the amount of time until the nominated line interrupt position is
reached on line @c line. If no line interrupt position is defined for
this VDP, returns the time until the 'beginning' of that line, whatever
that may mean.
@line is relative to the first pixel line of the display and may be negative.
*/
HalfCycles get_time_until_line(int line);
/*!
@returns @c true if the interrupt line is currently active; @c false otherwise.
*/
bool get_interrupt_line();
};
};
}
}
#endif /* TMS9918_hpp */

View File

@@ -12,90 +12,848 @@
#include "../../../Outputs/CRT/CRT.hpp"
#include "../../../ClockReceiver/ClockReceiver.hpp"
#include <cassert>
#include <cstdint>
#include <memory>
#include <vector>
namespace TI {
namespace TMS {
enum Personality {
TMS9918A, // includes the 9928 and 9929; set TV standard and output device as desired.
V9938,
V9958,
SMSVDP,
SMS2VDP,
GGVDP,
};
enum class TVStandard {
/*! i.e. 50Hz output at around 312.5 lines/field */
PAL,
/*! i.e. 60Hz output at around 262.5 lines/field */
NTSC
};
#define is_sega_vdp(x) ((x) >= SMSVDP)
class Base {
public:
static 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;
}
class TMS9918Base {
protected:
TMS9918Base();
const static int output_lag = 11; // i.e. pixel output will occur 11 cycles after corresponding data read.
std::unique_ptr<Outputs::CRT::CRT> crt_;
// The default TMS palette.
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),
uint8_t ram_[16384];
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)
};
Base(Personality p);
const Personality personality_;
Outputs::CRT::CRT crt_;
TVStandard tv_standard_ = TVStandard::NTSC;
// Holds the contents of this VDP's connected DRAM.
std::vector<uint8_t> ram_;
// Holds the state of the DRAM/CRAM-access mechanism.
uint16_t ram_pointer_ = 0;
uint8_t read_ahead_buffer_ = 0;
enum class MemoryAccess {
Read, Write, None
} queued_access_ = MemoryAccess::None;
int cycles_until_access_ = 0;
int minimum_access_column_ = 0;
int vram_access_delay() {
// This seems to be correct for all currently-modelled VDPs;
// it's the delay between an external device scheduling a
// read or write and the very first time that can occur
// (though, in practice, it won't happen until the next
// external slot after this number of cycles after the
// device has requested the read or write).
return 7;
}
// Holds the main status register.
uint8_t status_ = 0;
bool write_phase_ = false;
uint8_t low_write_ = 0;
// Current state of programmer input.
bool write_phase_ = false; // Determines whether the VDP is expecting the low or high byte of a write.
uint8_t low_write_ = 0; // Buffers the low byte of a write.
// The various register flags.
int next_screen_mode_ = 0, screen_mode_ = 0;
bool next_blank_screen_ = true, blank_screen_ = true;
// Various programmable flags.
bool mode1_enable_ = false;
bool mode2_enable_ = false;
bool mode3_enable_ = false;
bool blank_display_ = false;
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;
size_t pattern_name_address_ = 0; // i.e. address of the tile map.
size_t colour_table_address_ = 0; // address of the colour map (if applicable).
size_t pattern_generator_table_address_ = 0; // address of the tile contents.
size_t sprite_attribute_table_address_ = 0; // address of the sprite list.
size_t sprite_generator_table_address_ = 0; // address of the sprite contents.
uint8_t text_colour_ = 0;
uint8_t background_colour_ = 0;
HalfCycles half_cycles_into_frame_;
int column_ = 0, row_ = 0, output_column_ = 0;
// This implementation of this chip officially accepts a 3.58Mhz clock, but runs
// internally at 5.37Mhz. The following two help to maintain a lossless conversion
// from the one to the other.
int cycles_error_ = 0;
uint32_t *pixel_target_ = nullptr, *pixel_base_ = nullptr;
HalfCycles half_cycles_before_internal_cycles(int internal_cycles);
void output_border(int cycles);
// Internal mechanisms for position tracking.
int latched_column_ = 0;
// Vertical timing details.
int frame_lines_ = 262;
int first_vsync_line_ = 227;
// A helper function to output the current border colour for
// the number of cycles supplied.
void output_border(int cycles, uint32_t cram_dot);
// A struct to contain timing information for the current mode.
struct {
/*
Vertical layout:
Lines 0 to [pixel_lines]: standard data fetch and drawing will occur.
... to [first_vsync_line]: refresh fetches will occur and border will be output.
.. to [2.5 or 3 lines later]: vertical sync is output.
... to [total lines - 1]: refresh fetches will occur and border will be output.
... for one line: standard data fetch will occur, without drawing.
*/
int total_lines = 262;
int pixel_lines = 192;
int first_vsync_line = 227;
// Maximum number of sprite slots to populate;
// if sprites beyond this number should be visible
// then the appropriate status information will be set.
int maximum_visible_sprites = 4;
// Set the position, in cycles, of the two interrupts,
// within a line.
struct {
int column = 4;
int row = 193;
} end_of_frame_interrupt_position;
int line_interrupt_position = -1;
// Enables or disabled the recognition of the sprite
// list terminator, and sets the terminator value.
bool allow_sprite_terminator = true;
uint8_t sprite_terminator = 0xd0;
} mode_timing_;
uint8_t line_interrupt_target = 0xff;
uint8_t line_interrupt_counter = 0;
bool enable_line_interrupts_ = false;
bool line_interrupt_pending_ = false;
// The screen mode is a necessary predecessor to picking the line mode,
// which is the thing latched per line.
enum class ScreenMode {
Blank,
Text,
MultiColour,
ColouredText,
Graphics,
SMSMode4
} screen_mode_;
// Horizontal selections.
enum class LineMode {
Text = 0,
Character = 1,
Refresh = 2
} line_mode_ = LineMode::Text;
int first_pixel_column_, first_right_border_column_;
Text,
Character,
Refresh,
SMS
};
uint8_t pattern_names_[40];
uint8_t pattern_buffer_[40];
uint8_t colour_buffer_[40];
// Temporary buffers collect a representation of this line prior to pixel serialisation.
struct LineBuffer {
// The line mode describes the proper timing diagram for this line.
LineMode line_mode = LineMode::Text;
struct SpriteSet {
// Holds the horizontal scroll position to apply to this line;
// of those VDPs currently implemented, affects the Master System only.
uint8_t latched_horizontal_scroll = 0;
// The names array holds pattern names, as an offset into memory, and
// potentially flags also.
struct {
size_t offset = 0;
uint8_t flags = 0;
} names[40];
// The patterns array holds tile patterns, corresponding 1:1 with names.
// Four bytes per pattern is the maximum required by any
// currently-implemented VDP.
uint8_t patterns[40][4];
/*
Horizontal layout (on a 342-cycle clock):
15 cycles right border
58 cycles blanking & sync
13 cycles left border
... i.e. to cycle 86, then:
border up to first_pixel_output_column;
pixels up to next_border_column;
border up to the end.
e.g. standard 256-pixel modes will want to set
first_pixel_output_column = 86, next_border_column = 342.
*/
int first_pixel_output_column = 94;
int next_border_column = 334;
// An active sprite is one that has been selected for composition onto
// this line.
struct ActiveSprite {
int index = 0;
int row = 0;
int index = 0; // The original in-table index of this sprite.
int row = 0; // The row of the sprite that should be drawn.
int x = 0; // The sprite's x position on screen.
uint8_t info[4];
uint8_t image[2];
uint8_t image[4]; // Up to four bytes of image information.
int shift_position = 0; // An offset representing how much of the image information has already been drawn.
} active_sprites[8];
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 active_sprite_slot = 0; // A pointer to the slot into which a new active sprite will be deposited, if required.
bool sprites_stopped = false; // A special TMS feature is that a sentinel value can be used to prevent any further sprites
// being evaluated for display. This flag determines whether the sentinel has yet been reached.
int access_pointer_ = 0;
void reset_sprite_collection();
} line_buffers_[313];
void posit_sprite(LineBuffer &buffer, int sprite_number, int sprite_y, int screen_row);
inline void test_sprite(int sprite_number, int screen_row);
inline void get_sprite_contents(int start, int cycles, int screen_row);
// There is a delay between reading into the line buffer and outputting from there to the screen. That delay
// is observeable because reading time affects availability of memory accesses and therefore time in which
// to update sprites and tiles, but writing time affects when the palette is used and when the collision flag
// may end up being set. So the two processes are slightly decoupled. The end of reading one line may overlap
// with the beginning of writing the next, hence the two separate line buffers.
struct LineBufferPointer {
int row, column;
} read_pointer_, write_pointer_;
// The SMS VDP has a programmer-set colour palette, with a dedicated patch of RAM. But the RAM is only exactly
// fast enough for the pixel clock. So when the programmer writes to it, that causes a one-pixel glitch; there
// isn't the bandwidth for the read both write to occur simultaneously. The following buffer therefore keeps
// track of pending collisions, for visual reproduction.
struct CRAMDot {
LineBufferPointer location;
uint32_t value;
};
std::vector<CRAMDot> upcoming_cram_dots_;
// Extra information that affects the Master System output mode.
struct {
// Programmer-set flags.
bool vertical_scroll_lock = false;
bool horizontal_scroll_lock = false;
bool hide_left_column = false;
bool shift_sprites_8px_left = false;
bool mode4_enable = false;
uint8_t horizontal_scroll = 0;
uint8_t vertical_scroll = 0;
// The Master System's additional colour RAM.
uint32_t colour_ram[32];
bool cram_is_selected = false;
// Holds the vertical scroll position for this frame; this is latched
// once and cannot dynamically be changed until the next frame.
uint8_t latched_vertical_scroll = 0;
size_t pattern_name_address;
size_t sprite_attribute_table_address;
size_t sprite_generator_table_address;
} master_system_;
void set_current_screen_mode() {
if(blank_display_) {
screen_mode_ = ScreenMode::Blank;
return;
}
if(is_sega_vdp(personality_) && master_system_.mode4_enable) {
screen_mode_ = ScreenMode::SMSMode4;
mode_timing_.maximum_visible_sprites = 8;
return;
}
mode_timing_.maximum_visible_sprites = 4;
if(!mode1_enable_ && !mode2_enable_ && !mode3_enable_) {
screen_mode_ = ScreenMode::ColouredText;
return;
}
if(mode1_enable_ && !mode2_enable_ && !mode3_enable_) {
screen_mode_ = ScreenMode::Text;
return;
}
if(!mode1_enable_ && mode2_enable_ && !mode3_enable_) {
screen_mode_ = ScreenMode::Graphics;
return;
}
if(!mode1_enable_ && !mode2_enable_ && mode3_enable_) {
screen_mode_ = ScreenMode::MultiColour;
return;
}
// TODO: undocumented TMS modes.
screen_mode_ = ScreenMode::Blank;
}
void do_external_slot(int access_column) {
// Don't do anything if the required time for the access to become executable
// has yet to pass.
if(access_column < minimum_access_column_) {
return;
}
switch(queued_access_) {
default: return;
case MemoryAccess::Write:
if(master_system_.cram_is_selected) {
// Adjust the palette.
master_system_.colour_ram[ram_pointer_ & 0x1f] = palette_pack(
static_cast<uint8_t>(((read_ahead_buffer_ >> 0) & 3) * 255 / 3),
static_cast<uint8_t>(((read_ahead_buffer_ >> 2) & 3) * 255 / 3),
static_cast<uint8_t>(((read_ahead_buffer_ >> 4) & 3) * 255 / 3)
);
// Schedule a CRAM dot; this is scheduled for wherever it should appear
// on screen. So it's wherever the output stream would be now. Which
// is output_lag cycles ago from the point of view of the input stream.
upcoming_cram_dots_.emplace_back();
CRAMDot &dot = upcoming_cram_dots_.back();
dot.location.column = write_pointer_.column - output_lag;
dot.location.row = write_pointer_.row;
// Handle before this row conditionally; then handle after (or, more realistically,
// exactly at the end of) naturally.
if(dot.location.column < 0) {
--dot.location.row;
dot.location.column += 342;
}
dot.location.row += dot.location.column / 342;
dot.location.column %= 342;
dot.value = master_system_.colour_ram[ram_pointer_ & 0x1f];
} else {
ram_[ram_pointer_ & 16383] = read_ahead_buffer_;
}
break;
case MemoryAccess::Read:
read_ahead_buffer_ = ram_[ram_pointer_ & 16383];
break;
}
++ram_pointer_;
queued_access_ = MemoryAccess::None;
}
/*
Fetching routines follow below; they obey the following rules:
1) input is a start position and an end position; they should perform the proper
operations for the period: start <= time < end.
2) times are measured relative to a 172-cycles-per-line clock (so: they directly
count access windows on the TMS and Master System).
3) time 0 is the beginning of the access window immediately after the last pattern/data
block fetch that would contribute to this line, in a normal 32-column mode. So:
* it's cycle 309 on Mattias' TMS diagram;
* it's cycle 1238 on his V9938 diagram;
* it's after the last background render block in Mask of Destiny's Master System timing diagram.
That division point was selected, albeit arbitrarily, because it puts all the tile
fetches for a single line into the same [0, 171] period.
4) all of these functions are templated with a `use_end` parameter. That will be true if
end is < 172, false otherwise. So functions can use it to eliminate should-exit-not checks,
for the more usual path of execution.
Provided for the benefit of the methods below:
* the function external_slot(), which will perform any pending VRAM read/write.
* the macros slot(n) and external_slot(n) which can be used to schedule those things inside a
switch(start)-based implementation.
All functions should just spool data to intermediary storage. This is because for most VDPs there is
a decoupling between fetch pattern and output pattern, and it's neater to keep the same division
for the exceptions.
*/
#define slot(n) \
if(use_end && end == n) return;\
case n
#define external_slot(n) \
slot(n): do_external_slot((n)*2);
#define external_slots_2(n) \
external_slot(n); \
external_slot(n+1);
#define external_slots_4(n) \
external_slots_2(n); \
external_slots_2(n+2);
#define external_slots_8(n) \
external_slots_4(n); \
external_slots_4(n+4);
#define external_slots_16(n) \
external_slots_8(n); \
external_slots_8(n+8);
#define external_slots_32(n) \
external_slots_16(n); \
external_slots_16(n+16);
/***********************************************
TMS9918 Fetching Code
************************************************/
template<bool use_end> void fetch_tms_refresh(int start, int end) {
#define refresh(location) \
slot(location): \
external_slot(location+1);
#define refreshes_2(location) \
refresh(location); \
refresh(location+2);
#define refreshes_4(location) \
refreshes_2(location); \
refreshes_2(location+4);
#define refreshes_8(location) \
refreshes_4(location); \
refreshes_4(location+8);
switch(start) {
default: assert(false);
/* 44 external slots */
external_slots_32(0)
external_slots_8(32)
external_slots_4(40)
/* 64 refresh/external slot pairs (= 128 windows) */
refreshes_8(44);
refreshes_8(60);
refreshes_8(76);
refreshes_8(92);
refreshes_8(108);
refreshes_8(124);
refreshes_8(140);
refreshes_8(156);
return;
}
#undef refreshes_8
#undef refreshes_4
#undef refreshes_2
#undef refresh
}
template<bool use_end> void fetch_tms_text(int start, int end) {
#define fetch_tile_name(location, column) slot(location): line_buffer.names[column].offset = ram_[row_base + column];
#define fetch_tile_pattern(location, column) slot(location): line_buffer.patterns[column][0] = ram_[row_offset + size_t(line_buffer.names[column].offset << 3)];
#define fetch_column(location, column) \
fetch_tile_name(location, column); \
external_slot(location+1); \
fetch_tile_pattern(location+2, column);
#define fetch_columns_2(location, column) \
fetch_column(location, column); \
fetch_column(location+3, column+1);
#define fetch_columns_4(location, column) \
fetch_columns_2(location, column); \
fetch_columns_2(location+6, column+2);
#define fetch_columns_8(location, column) \
fetch_columns_4(location, column); \
fetch_columns_4(location+12, column+4);
LineBuffer &line_buffer = line_buffers_[write_pointer_.row];
const size_t row_base = pattern_name_address_ & (0x3c00 | static_cast<size_t>(write_pointer_.row >> 3) * 40);
const size_t row_offset = pattern_generator_table_address_ & (0x3800 | (write_pointer_.row & 7));
switch(start) {
default: assert(false);
/* 47 external slots (= 47 windows) */
external_slots_32(0)
external_slots_8(32)
external_slots_4(40)
external_slots_2(44)
external_slot(46)
/* 40 column fetches (= 120 windows) */
fetch_columns_8(47, 0);
fetch_columns_8(71, 8);
fetch_columns_8(95, 16);
fetch_columns_8(119, 24);
fetch_columns_8(143, 32);
/* 5 more external slots */
external_slots_4(167);
external_slot(171);
return;
}
#undef fetch_columns_8
#undef fetch_columns_4
#undef fetch_columns_2
#undef fetch_column
#undef fetch_tile_pattern
#undef fetch_tile_name
}
template<bool use_end> void fetch_tms_character(int start, int end) {
#define sprite_fetch_coordinates(location, sprite) \
slot(location): \
slot(location+1): \
line_buffer.active_sprites[sprite].x = \
ram_[\
sprite_attribute_table_address_ & size_t(0x3f81 | (line_buffer.active_sprites[sprite].index << 2))\
];
// This implementation doesn't refetch Y; it's unclear to me
// whether it's refetched.
#define sprite_fetch_graphics(location, sprite) \
slot(location): \
slot(location+1): \
slot(location+2): \
slot(location+3): {\
const uint8_t name = ram_[\
sprite_attribute_table_address_ & size_t(0x3f82 | (line_buffer.active_sprites[sprite].index << 2))\
] & (sprites_16x16_ ? ~3 : ~0);\
line_buffer.active_sprites[sprite].image[2] = ram_[\
sprite_attribute_table_address_ & size_t(0x3f83 | (line_buffer.active_sprites[sprite].index << 2))\
];\
line_buffer.active_sprites[sprite].x -= (line_buffer.active_sprites[sprite].image[2] & 0x80) >> 2;\
const size_t graphic_location = sprite_generator_table_address_ & size_t(0x3800 | (name << 3) | line_buffer.active_sprites[sprite].row); \
line_buffer.active_sprites[sprite].image[0] = ram_[graphic_location];\
line_buffer.active_sprites[sprite].image[1] = ram_[graphic_location+16];\
}
#define sprite_fetch_block(location, sprite) \
sprite_fetch_coordinates(location, sprite) \
sprite_fetch_graphics(location+2, sprite)
#define sprite_y_read(location, sprite) \
slot(location): posit_sprite(sprite_selection_buffer, sprite, ram_[sprite_attribute_table_address_ & (((sprite) << 2) | 0x3f80)], write_pointer_.row);
#define fetch_tile_name(column) line_buffer.names[column].offset = ram_[(row_base + column) & 0x3fff];
#define fetch_tile(column) {\
line_buffer.patterns[column][1] = ram_[(colour_base + size_t((line_buffer.names[column].offset << 3) >> colour_name_shift)) & 0x3fff]; \
line_buffer.patterns[column][0] = ram_[(pattern_base + size_t(line_buffer.names[column].offset << 3)) & 0x3fff]; \
}
#define background_fetch_block(location, column, sprite) \
slot(location): fetch_tile_name(column) \
external_slot(location+1); \
slot(location+2): \
slot(location+3): fetch_tile(column) \
slot(location+4): fetch_tile_name(column+1) \
sprite_y_read(location+5, sprite); \
slot(location+6): \
slot(location+7): fetch_tile(column+1) \
slot(location+8): fetch_tile_name(column+2) \
sprite_y_read(location+9, sprite+1); \
slot(location+10): \
slot(location+11): fetch_tile(column+2) \
slot(location+12): fetch_tile_name(column+3) \
sprite_y_read(location+13, sprite+2); \
slot(location+14): \
slot(location+15): fetch_tile(column+3)
LineBuffer &line_buffer = line_buffers_[write_pointer_.row];
LineBuffer &sprite_selection_buffer = line_buffers_[(write_pointer_.row + 1) % mode_timing_.total_lines];
const size_t row_base = pattern_name_address_ & (size_t((write_pointer_.row << 2)&~31) | 0x3c00);
size_t pattern_base = pattern_generator_table_address_;
size_t colour_base = colour_table_address_;
int colour_name_shift = 6;
if(screen_mode_ == ScreenMode::Graphics) {
// If this is high resolution mode, allow the row number to affect the pattern and colour addresses.
pattern_base &= size_t(0x2000 | ((write_pointer_.row & 0xc0) << 5));
colour_base &= size_t(0x2000 | ((write_pointer_.row & 0xc0) << 5));
colour_base += size_t(write_pointer_.row & 7);
colour_name_shift = 0;
} else {
colour_base &= size_t(0xffc0);
pattern_base &= size_t(0x3800);
}
if(screen_mode_ == ScreenMode::MultiColour) {
pattern_base += size_t((write_pointer_.row >> 2) & 7);
} else {
pattern_base += size_t(write_pointer_.row & 7);
}
switch(start) {
default: assert(false);
external_slots_2(0);
sprite_fetch_block(2, 0);
sprite_fetch_block(8, 1);
sprite_fetch_coordinates(14, 2);
external_slots_4(16);
external_slot(20);
sprite_fetch_graphics(21, 2);
sprite_fetch_block(25, 3);
slot(31):
sprite_selection_buffer.reset_sprite_collection();
do_external_slot(31*2);
external_slots_2(32);
external_slot(34);
sprite_y_read(35, 0);
sprite_y_read(36, 1);
sprite_y_read(37, 2);
sprite_y_read(38, 3);
sprite_y_read(39, 4);
sprite_y_read(40, 5);
sprite_y_read(41, 6);
sprite_y_read(42, 7);
background_fetch_block(43, 0, 8);
background_fetch_block(59, 4, 11);
background_fetch_block(75, 8, 14);
background_fetch_block(91, 12, 17);
background_fetch_block(107, 16, 20);
background_fetch_block(123, 20, 23);
background_fetch_block(139, 24, 26);
background_fetch_block(155, 28, 29);
return;
}
#undef background_fetch_block
#undef fetch_tile
#undef fetch_tile_name
#undef sprite_y_read
#undef sprite_fetch_block
#undef sprite_fetch_graphics
#undef sprite_fetch_coordinates
}
/***********************************************
Master System Fetching Code
************************************************/
template<bool use_end> void fetch_sms(int start, int end) {
#define sprite_fetch(sprite) {\
line_buffer.active_sprites[sprite].x = \
ram_[\
master_system_.sprite_attribute_table_address & size_t(0x3f80 | (line_buffer.active_sprites[sprite].index << 1))\
] - (master_system_.shift_sprites_8px_left ? 8 : 0); \
const uint8_t name = ram_[\
master_system_.sprite_attribute_table_address & size_t(0x3f81 | (line_buffer.active_sprites[sprite].index << 1))\
] & (sprites_16x16_ ? ~1 : ~0);\
const size_t graphic_location = master_system_.sprite_generator_table_address & size_t(0x2000 | (name << 5) | (line_buffer.active_sprites[sprite].row << 2)); \
line_buffer.active_sprites[sprite].image[0] = ram_[graphic_location]; \
line_buffer.active_sprites[sprite].image[1] = ram_[graphic_location+1]; \
line_buffer.active_sprites[sprite].image[2] = ram_[graphic_location+2]; \
line_buffer.active_sprites[sprite].image[3] = ram_[graphic_location+3]; \
}
#define sprite_fetch_block(location, sprite) \
slot(location): \
slot(location+1): \
slot(location+2): \
slot(location+3): \
slot(location+4): \
slot(location+5): \
sprite_fetch(sprite);\
sprite_fetch(sprite+1);
#define sprite_y_read(location, sprite) \
slot(location): \
posit_sprite(sprite_selection_buffer, sprite, ram_[master_system_.sprite_attribute_table_address & ((sprite) | 0x3f00)], write_pointer_.row); \
posit_sprite(sprite_selection_buffer, sprite+1, ram_[master_system_.sprite_attribute_table_address & ((sprite + 1) | 0x3f00)], write_pointer_.row); \
#define fetch_tile_name(column, row_info) {\
const size_t scrolled_column = (column - horizontal_offset) & 0x1f;\
const size_t address = row_info.pattern_address_base + (scrolled_column << 1); \
line_buffer.names[column].flags = ram_[address+1]; \
line_buffer.names[column].offset = static_cast<size_t>( \
(((line_buffer.names[column].flags&1) << 8) | ram_[address]) << 5 \
) + row_info.sub_row[(line_buffer.names[column].flags&4) >> 2]; \
}
#define fetch_tile(column) \
line_buffer.patterns[column][0] = ram_[line_buffer.names[column].offset]; \
line_buffer.patterns[column][1] = ram_[line_buffer.names[column].offset+1]; \
line_buffer.patterns[column][2] = ram_[line_buffer.names[column].offset+2]; \
line_buffer.patterns[column][3] = ram_[line_buffer.names[column].offset+3];
#define background_fetch_block(location, column, sprite, row_info) \
slot(location): fetch_tile_name(column, row_info) \
external_slot(location+1); \
slot(location+2): \
slot(location+3): \
slot(location+4): \
fetch_tile(column) \
fetch_tile_name(column+1, row_info) \
sprite_y_read(location+5, sprite); \
slot(location+6): \
slot(location+7): \
slot(location+8): \
fetch_tile(column+1) \
fetch_tile_name(column+2, row_info) \
sprite_y_read(location+9, sprite+2); \
slot(location+10): \
slot(location+11): \
slot(location+12): \
fetch_tile(column+2) \
fetch_tile_name(column+3, row_info) \
sprite_y_read(location+13, sprite+4); \
slot(location+14): \
slot(location+15): fetch_tile(column+3)
// Determine the coarse horizontal scrolling offset; this isn't applied on the first two lines if the programmer has requested it.
LineBuffer &line_buffer = line_buffers_[write_pointer_.row];
LineBuffer &sprite_selection_buffer = line_buffers_[(write_pointer_.row + 1) % mode_timing_.total_lines];
const int horizontal_offset = (write_pointer_.row >= 16 || !master_system_.horizontal_scroll_lock) ? (line_buffer.latched_horizontal_scroll >> 3) : 0;
// Limit address bits in use if this is a SMS2 mode.
const bool is_tall_mode = mode_timing_.pixel_lines != 192;
const size_t pattern_name_address = master_system_.pattern_name_address | (is_tall_mode ? 0x800 : 0);
const size_t pattern_name_offset = is_tall_mode ? 0x100 : 0;
// Determine row info for the screen both (i) if vertical scrolling is applied; and (ii) if it isn't.
// The programmer can opt out of applying vertical scrolling to the right-hand portion of the display.
const int scrolled_row = (write_pointer_.row + master_system_.latched_vertical_scroll) % (is_tall_mode ? 256 : 224);
struct RowInfo {
size_t pattern_address_base;
size_t sub_row[2];
};
const RowInfo scrolled_row_info = {
(pattern_name_address & size_t(((scrolled_row & ~7) << 3) | 0x3800)) - pattern_name_offset,
{static_cast<size_t>((scrolled_row & 7) << 2), 28 ^ static_cast<size_t>((scrolled_row & 7) << 2)}
};
RowInfo row_info;
if(master_system_.vertical_scroll_lock) {
row_info.pattern_address_base = (pattern_name_address & size_t(((write_pointer_.row & ~7) << 3) | 0x3800)) - pattern_name_offset;
row_info.sub_row[0] = size_t((write_pointer_.row & 7) << 2);
row_info.sub_row[1] = 28 ^ size_t((write_pointer_.row & 7) << 2);
} else row_info = scrolled_row_info;
// ... and do the actual fetching, which follows this routine:
switch(start) {
default: assert(false);
sprite_fetch_block(0, 0);
sprite_fetch_block(6, 2);
external_slots_4(12);
external_slot(16);
sprite_fetch_block(17, 4);
sprite_fetch_block(23, 6);
slot(29):
sprite_selection_buffer.reset_sprite_collection();
do_external_slot(29*2);
external_slot(30);
sprite_y_read(31, 0);
sprite_y_read(32, 2);
sprite_y_read(33, 4);
sprite_y_read(34, 6);
sprite_y_read(35, 8);
sprite_y_read(36, 10);
sprite_y_read(37, 12);
sprite_y_read(38, 14);
background_fetch_block(39, 0, 16, scrolled_row_info);
background_fetch_block(55, 4, 22, scrolled_row_info);
background_fetch_block(71, 8, 28, scrolled_row_info);
background_fetch_block(87, 12, 34, scrolled_row_info);
background_fetch_block(103, 16, 40, scrolled_row_info);
background_fetch_block(119, 20, 46, scrolled_row_info);
background_fetch_block(135, 24, 52, row_info);
background_fetch_block(151, 28, 58, row_info);
external_slots_4(167);
return;
}
#undef background_fetch_block
#undef fetch_tile
#undef fetch_tile_name
#undef sprite_y_read
#undef sprite_fetch_block
#undef sprite_fetch
}
#undef external_slot
#undef slot
uint32_t *pixel_target_ = nullptr, *pixel_origin_ = nullptr;
bool asked_for_write_area_ = false;
void draw_tms_character(int start, int end);
void draw_tms_text(int start, int end);
void draw_sms(int start, int end, uint32_t cram_dot);
};
}
}
#endif /* TMS9918Base_hpp */

View File

@@ -173,11 +173,15 @@ void AY38910::select_register(uint8_t r) {
}
void AY38910::set_register_value(uint8_t value) {
// There are only 16 registers.
if(selected_register_ > 15) return;
registers_[selected_register_] = value;
// If this is a register that affects audio output, enqueue a mutation onto the
// audio generation thread.
if(selected_register_ < 14) {
int selected_register = selected_register_;
const int selected_register = selected_register_;
task_queue_.defer([=] () {
// Perform any register-specific mutation to output generation.
uint8_t masked_value = value;
switch(selected_register) {
case 0: case 2: case 4:
@@ -208,12 +212,34 @@ void AY38910::set_register_value(uint8_t value) {
envelope_position_ = 0;
break;
}
// Store a copy of the current register within the storage used by the audio generation
// thread, and apply any changes to output volume.
output_registers_[selected_register] = masked_value;
evaluate_output_volume();
});
} else {
if(port_handler_) port_handler_->set_port_output(selected_register_ == 15, value);
}
// Decide which outputs are going to need updating (if any).
bool update_port_a = false;
bool update_port_b = true;
if(port_handler_) {
if(selected_register_ == 7) {
const uint8_t io_change = registers_[7] ^ value;
update_port_b = !!(io_change&0x80);
update_port_a = !!(io_change&0x40);
} else {
update_port_b = selected_register_ == 15;
update_port_a = selected_register_ != 15;
}
}
// Keep a copy of the new value that is usable from the emulation thread.
registers_[selected_register_] = value;
// Update ports as required.
if(update_port_b) set_port_output(true);
if(update_port_a) set_port_output(false);
}
uint8_t AY38910::get_register_value() {
@@ -238,6 +264,8 @@ uint8_t AY38910::get_port_output(bool port_b) {
void AY38910::set_port_handler(PortHandler *handler) {
port_handler_ = handler;
set_port_output(true);
set_port_output(false);
}
void AY38910::set_data_input(uint8_t r) {
@@ -245,6 +273,16 @@ void AY38910::set_data_input(uint8_t r) {
update_bus();
}
void AY38910::set_port_output(bool port_b) {
// Per the data sheet: "each [IO] pin is provided with an on-chip pull-up resistor,
// so that when in the "input" mode, all pins will read normally high". Therefore,
// report programmer selection of input mode as creating an output of 0xff.
if(port_handler_) {
const bool is_output = !!(registers_[7] & (port_b ? 0x80 : 0x40));
port_handler_->set_port_output(port_b, is_output ? registers_[port_b ? 15 : 14] : 0xff);
}
}
uint8_t AY38910::get_data_output() {
if(control_state_ == Read && selected_register_ >= 14 && selected_register_ < 16) {
// Per http://cpctech.cpc-live.com/docs/psgnotes.htm if a port is defined as output then the
@@ -253,7 +291,7 @@ uint8_t AY38910::get_data_output() {
const uint8_t mask = port_handler_ ? port_handler_->get_port_input(selected_register_ == 15) : 0xff;
switch(selected_register_) {
default: break;
default: break;
case 14: return mask & ((registers_[0x7] & 0x40) ? registers_[14] : 0xff);
case 15: return mask & ((registers_[0x7] & 0x80) ? registers_[15] : 0xff);
}
@@ -280,7 +318,7 @@ void AY38910::update_bus() {
// Assume no output, unless this turns out to be a read.
data_output_ = 0xff;
switch(control_state_) {
default: break;
default: break;
case LatchAddress: select_register(data_input_); break;
case Write: set_register_value(data_input_); break;
case Read: data_output_ = get_register_value(); break;

View File

@@ -83,7 +83,7 @@ class AY38910: public ::Outputs::Speaker::SampleSource {
*/
void set_port_handler(PortHandler *);
// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter; not for public consumption
// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter.
void get_samples(std::size_t number_of_samples, int16_t *target);
bool is_zero_level();
void set_sample_volume_range(std::int16_t range);
@@ -128,10 +128,11 @@ class AY38910: public ::Outputs::Speaker::SampleSource {
uint8_t data_input_, data_output_;
int16_t output_volume_;
inline void evaluate_output_volume();
void evaluate_output_volume();
inline void update_bus();
void update_bus();
PortHandler *port_handler_ = nullptr;
void set_port_output(bool port_b);
};
}

View File

@@ -22,7 +22,7 @@ namespace {
DiskII::DiskII(int clock_rate) :
clock_rate_(clock_rate),
inputs_(input_command),
drives_{{static_cast<unsigned int>(clock_rate), 300, 1}, {static_cast<unsigned int>(clock_rate), 300, 1}}
drives_{{clock_rate, 300, 1}, {clock_rate, 300, 1}}
{
drives_[0].set_clocking_hint_observer(this);
drives_[1].set_clocking_hint_observer(this);
@@ -73,13 +73,18 @@ void DiskII::select_drive(int drive) {
drives_[active_drive_].set_motor_on(motor_is_enabled_);
}
// The read pulse is controlled by a special IC that outputs a 1us pulse for every field reversal on the disk.
void DiskII::run_for(const Cycles cycles) {
if(preferred_clocking() == ClockingHint::Preference::None) return;
int integer_cycles = cycles.as_int();
while(integer_cycles--) {
const int address = (state_ & 0xf0) | inputs_ | ((shift_register_&0x80) >> 6);
inputs_ |= input_flux;
if(flux_duration_) {
--flux_duration_;
if(!flux_duration_) inputs_ |= input_flux;
}
state_ = state_machine_[static_cast<std::size_t>(address)];
switch(state_ & 0xf) {
default: shift_register_ = 0; break; // clear
@@ -115,6 +120,15 @@ void DiskII::run_for(const Cycles cycles) {
if(!drive_is_sleeping_[1]) drives_[1].run_for(Cycles(1));
}
// Per comp.sys.apple2.programmer there is a delay between the controller
// motor switch being flipped and the drive motor actually switching off.
// This models that, accepting overrun as a risk.
if(motor_off_time_ >= 0) {
motor_off_time_ -= cycles.as_int();
if(motor_off_time_ < 0) {
set_control(Control::Motor, false);
}
}
decide_clocking_preference();
}
@@ -197,9 +211,10 @@ void DiskII::set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int driv
drives_[drive].set_disk(disk);
}
void DiskII::process_event(const Storage::Disk::Track::Event &event) {
void DiskII::process_event(const Storage::Disk::Drive::Event &event) {
if(event.type == Storage::Disk::Track::Event::FluxTransition) {
inputs_ &= ~input_flux;
flux_duration_ = 2; // Upon detection of a flux transition, the flux flag should stay set for 1us. Emulate that as two cycles.
decide_clocking_preference();
}
}
@@ -232,9 +247,12 @@ int DiskII::read_address(int address) {
case 0x8:
shift_register_ = 0;
set_control(Control::Motor, false);
motor_off_time_ = clock_rate_;
break;
case 0x9:
set_control(Control::Motor, true);
motor_off_time_ = -1;
break;
case 0x9: set_control(Control::Motor, true); break;
case 0xa: select_drive(0); break;
case 0xb: select_drive(1); break;

View File

@@ -54,7 +54,7 @@ class DiskII:
void run_for(const Cycles cycles);
/*!
Supplies the image of the state machine (i.e. P6) ROM,
Supplies the image of the state machine (i.e. P6) ROM,
which dictates how the Disk II will respond to input.
To reduce processing costs, some assumptions are made by
@@ -98,7 +98,7 @@ class DiskII:
void select_drive(int drive);
uint8_t trigger_address(int address, uint8_t value);
void process_event(const Storage::Disk::Track::Event &event) override;
void process_event(const Storage::Disk::Drive::Event &event) override;
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) override;
const int clock_rate_ = 0;
@@ -109,6 +109,7 @@ class DiskII:
int stepper_mask_ = 0;
int stepper_position_ = 0;
int motor_off_time_ = -1;
bool is_write_protected();
std::array<uint8_t, 256> state_machine_;
@@ -121,6 +122,7 @@ class DiskII:
ClockingHint::Preference clocking_preference_ = ClockingHint::Preference::RealTime;
uint8_t data_input_ = 0;
int flux_duration_ = 0;
};
}

376
Components/DiskII/IWM.cpp Normal file
View File

@@ -0,0 +1,376 @@
//
// IWM.cpp
// Clock Signal
//
// Created by Thomas Harte on 05/05/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#include "IWM.hpp"
#include "../../Outputs/Log.hpp"
using namespace Apple;
namespace {
const int CA0 = 1 << 0;
const int CA1 = 1 << 1;
const int CA2 = 1 << 2;
const int LSTRB = 1 << 3;
const int ENABLE = 1 << 4;
const int DRIVESEL = 1 << 5; /* This means drive select, like on the original Disk II. */
const int Q6 = 1 << 6;
const int Q7 = 1 << 7;
const int SEL = 1 << 8; /* This is an additional input, not available on a Disk II, with a confusingly-similar name to SELECT but a distinct purpose. */
}
IWM::IWM(int clock_rate) :
clock_rate_(clock_rate) {}
// MARK: - Bus accessors
uint8_t IWM::read(int address) {
access(address);
// Per Inside Macintosh:
//
// "Before you can read from any of the disk registers you must set up the state of the IWM so that it
// can pass the data through to the MC68000's address space where you'll be able to read it. To do that,
// you must first turn off Q7 by reading or writing dBase+q7L. Then turn on Q6 by accessing dBase+q6H.
// After that, the IWM will be able to pass data from the disk's RD/SENSE line through to you."
//
// My understanding:
//
// Q6 = 1, Q7 = 0 reads the status register. The meaning of the top 'SENSE' bit is then determined by
// the CA0,1,2 and SEL switches as described in Inside Macintosh, summarised above as RD/SENSE.
if(address&1) {
return 0xff;
}
switch(state_ & (Q6 | Q7 | ENABLE)) {
default:
LOG("[IWM] Invalid read\n");
return 0xff;
// "Read all 1s".
// printf("Reading all 1s\n");
// return 0xff;
case 0:
case ENABLE: { /* Read data register. Zeroing afterwards is a guess. */
const auto result = data_register_;
if(data_register_ & 0x80) {
// printf("\n\nIWM:%02x\n\n", data_register_);
// printf(".");
data_register_ = 0;
}
// LOG("Reading data register: " << PADHEX(2) << int(result));
return result;
}
case Q6: case Q6|ENABLE: {
/*
[If A = 0], Read status register:
bits 0-4: same as mode register.
bit 5: 1 = either /ENBL1 or /ENBL2 is currently low.
bit 6: 1 = MZ (reserved for future compatibility; should always be read as 0).
bit 7: 1 = SENSE input high; 0 = SENSE input low.
(/ENBL1 is low when the first drive's motor is on; /ENBL2 is low when the second drive's motor is on.
If the 1-second timer is enabled, motors remain on for one second after being programmatically disabled.)
*/
return uint8_t(
(mode_&0x1f) |
((state_ & ENABLE) ? 0x20 : 0x00) |
(sense() & 0x80)
);
} break;
case Q7: case Q7|ENABLE:
/*
Read write-handshake register:
bits 0-5: reserved for future use (currently read as 1).
bit 6: 1 = write state (0 = underrun has occurred; 1 = no underrun so far).
bit 7: 1 = write data buffer ready for data (1 = ready; 0 = busy).
*/
// LOG("Reading write handshake: " << PADHEX(2) << (0x3f | write_handshake_));
return 0x3f | write_handshake_;
}
return 0xff;
}
void IWM::write(int address, uint8_t input) {
access(address);
switch(state_ & (Q6 | Q7 | ENABLE)) {
default: break;
case Q7|Q6:
/*
Write mode register:
bit 0: 1 = latch mode (should be set in asynchronous mode).
bit 1: 0 = synchronous handshake protocol; 1 = asynchronous.
bit 2: 0 = 1-second on-board timer enable; 1 = timer disable.
bit 3: 0 = slow mode; 1 = fast mode.
bit 4: 0 = 7Mhz; 1 = 8Mhz (7 or 8 mHz clock descriptor).
bit 5: 1 = test mode; 0 = normal operation.
bit 6: 1 = MZ-reset.
bit 7: reserved for future expansion.
*/
mode_ = input;
switch(mode_ & 0x18) {
case 0x00: bit_length_ = Cycles(24); break; // slow mode, 7Mhz
case 0x08: bit_length_ = Cycles(12); break; // fast mode, 7Mhz
case 0x10: bit_length_ = Cycles(32); break; // slow mode, 8Mhz
case 0x18: bit_length_ = Cycles(16); break; // fast mode, 8Mhz
}
LOG("IWM mode is now " << PADHEX(2) << int(mode_));
break;
case Q7|Q6|ENABLE: // Write data register.
next_output_ = input;
write_handshake_ &= ~0x80;
break;
}
}
// MARK: - Switch access
void IWM::access(int address) {
// Keep a record of switch state; bits in state_
// should correlate with the anonymous namespace constants
// defined at the top of this file — CA0, CA1, etc.
address &= 0xf;
const auto mask = 1 << (address >> 1);
const auto old_state = state_;
if(address & 1) {
state_ |= mask;
} else {
state_ &= ~mask;
}
// React appropriately to ENABLE and DRIVESEL changes, and changes into/out of write mode.
if(old_state != state_) {
push_drive_state();
switch(mask) {
default: break;
case ENABLE:
if(address & 1) {
if(drives_[active_drive_]) drives_[active_drive_]->set_enabled(true);
} else {
// If the 1-second delay is enabled, set up a timer for that.
if(!(mode_ & 4)) {
cycles_until_disable_ = Cycles(clock_rate_);
} else {
if(drives_[active_drive_]) drives_[active_drive_]->set_enabled(false);
}
}
break;
case DRIVESEL: {
const int new_drive = address & 1;
if(new_drive != active_drive_) {
if(drives_[active_drive_]) drives_[active_drive_]->set_enabled(false);
active_drive_ = new_drive;
if(drives_[active_drive_]) {
drives_[active_drive_]->set_enabled(state_ & ENABLE || (cycles_until_disable_ > Cycles(0)));
push_drive_state();
}
}
} break;
case Q6:
case Q7:
select_shift_mode();
break;
}
}
}
void IWM::set_select(bool enabled) {
// Store SEL as an extra state bit.
if(enabled) state_ |= SEL;
else state_ &= ~SEL;
push_drive_state();
}
void IWM::push_drive_state() {
if(drives_[active_drive_]) {
const uint8_t drive_control_lines =
((state_ & CA0) ? IWMDrive::CA0 : 0) |
((state_ & CA1) ? IWMDrive::CA1 : 0) |
((state_ & CA2) ? IWMDrive::CA2 : 0) |
((state_ & SEL) ? IWMDrive::SEL : 0) |
((state_ & LSTRB) ? IWMDrive::LSTRB : 0);
drives_[active_drive_]->set_control_lines(drive_control_lines);
}
}
// MARK: - Active logic
void IWM::run_for(const Cycles cycles) {
// Check for a timeout of the motor-off timer.
if(cycles_until_disable_ > Cycles(0)) {
cycles_until_disable_ -= cycles;
if(cycles_until_disable_ <= Cycles(0)) {
cycles_until_disable_ = Cycles(0);
if(drives_[active_drive_])
drives_[active_drive_]->set_enabled(false);
}
}
// Activity otherwise depends on mode and motor state.
int integer_cycles = cycles.as_int();
switch(shift_mode_) {
case ShiftMode::Reading:
if(drive_is_rotating_[active_drive_]) {
while(integer_cycles--) {
drives_[active_drive_]->run_for(Cycles(1));
++cycles_since_shift_;
if(cycles_since_shift_ == bit_length_ + Cycles(2)) {
propose_shift(0);
}
}
} else {
while(cycles_since_shift_ + integer_cycles >= bit_length_ + Cycles(2)) {
propose_shift(0);
integer_cycles -= bit_length_.as_int() + 2 - cycles_since_shift_.as_int();
}
cycles_since_shift_ += Cycles(integer_cycles);
}
break;
case ShiftMode::Writing:
if(drives_[active_drive_]->is_writing()) {
while(cycles_since_shift_ + integer_cycles >= bit_length_) {
const auto cycles_until_write = bit_length_ - cycles_since_shift_;
drives_[active_drive_]->run_for(cycles_until_write);
// Output a flux transition if the top bit is set.
drives_[active_drive_]->write_bit(shift_register_ & 0x80);
shift_register_ <<= 1;
integer_cycles -= cycles_until_write.as_int();
cycles_since_shift_ = Cycles(0);
--output_bits_remaining_;
if(!output_bits_remaining_) {
if(!(write_handshake_ & 0x80)) {
write_handshake_ |= 0x80;
shift_register_ = next_output_;
output_bits_remaining_ = 8;
// LOG("Next byte: " << PADHEX(2) << int(shift_register_));
} else {
write_handshake_ &= ~0x40;
drives_[active_drive_]->end_writing();
// printf("\n");
LOG("Overrun; done.");
select_shift_mode();
}
}
}
cycles_since_shift_ = integer_cycles;
if(integer_cycles) {
drives_[active_drive_]->run_for(cycles_since_shift_);
}
} else {
drives_[active_drive_]->run_for(cycles);
}
break;
case ShiftMode::CheckingWriteProtect:
if(integer_cycles < 8) {
shift_register_ = (shift_register_ >> integer_cycles) | (sense() & (0xff << (8 - integer_cycles)));
} else {
shift_register_ = sense();
}
/* Deliberate fallthrough. */
default:
if(drive_is_rotating_[active_drive_]) drives_[active_drive_]->run_for(cycles);
break;
}
}
void IWM::select_shift_mode() {
// Don't allow an ongoing write to be interrupted.
if(shift_mode_ == ShiftMode::Writing && drives_[active_drive_] && drives_[active_drive_]->is_writing()) return;
const auto old_shift_mode = shift_mode_;
switch(state_ & (Q6|Q7)) {
default: shift_mode_ = ShiftMode::CheckingWriteProtect; break;
case 0: shift_mode_ = ShiftMode::Reading; break;
case Q7:
// "The IWM is put into the write state by a transition from the write protect sense state to the
// write load state".
if(shift_mode_ == ShiftMode::CheckingWriteProtect) shift_mode_ = ShiftMode::Writing;
break;
}
// If writing mode just began, set the drive into write mode and cue up the first output byte.
if(drives_[active_drive_] && old_shift_mode != ShiftMode::Writing && shift_mode_ == ShiftMode::Writing) {
drives_[active_drive_]->begin_writing(Storage::Time(1, clock_rate_ / bit_length_.as_int()), false);
shift_register_ = next_output_;
write_handshake_ |= 0x80 | 0x40;
output_bits_remaining_ = 8;
LOG("Seeding output with " << PADHEX(2) << shift_register_);
}
}
uint8_t IWM::sense() {
return drives_[active_drive_] ? (drives_[active_drive_]->read() ? 0xff : 0x00) : 0xff;
}
void IWM::process_event(const Storage::Disk::Drive::Event &event) {
if(shift_mode_ != ShiftMode::Reading) return;
switch(event.type) {
case Storage::Disk::Track::Event::IndexHole: return;
case Storage::Disk::Track::Event::FluxTransition:
propose_shift(1);
break;
}
}
void IWM::propose_shift(uint8_t bit) {
// TODO: synchronous mode.
// LOG("Shifting input");
shift_register_ = uint8_t((shift_register_ << 1) | bit);
if(shift_register_ & 0x80) {
data_register_ = shift_register_;
shift_register_ = 0;
}
cycles_since_shift_ = Cycles(0);
}
void IWM::set_drive(int slot, IWMDrive *drive) {
drives_[slot] = drive;
drive->set_event_delegate(this);
drive->set_clocking_hint_observer(this);
}
void IWM::set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) {
const bool is_rotating = clocking != ClockingHint::Preference::None;
if(component == static_cast<ClockingHint::Source *>(drives_[0])) {
drive_is_rotating_[0] = is_rotating;
} else {
drive_is_rotating_[1] = is_rotating;
}
}

117
Components/DiskII/IWM.hpp Normal file
View File

@@ -0,0 +1,117 @@
//
// IWM.hpp
// Clock Signal
//
// Created by Thomas Harte on 05/05/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef IWM_hpp
#define IWM_hpp
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../ClockReceiver/ClockingHintSource.hpp"
#include "../../Storage/Disk/Drive.hpp"
#include <cstdint>
namespace Apple {
/*!
Defines the drive interface used by the IWM, derived from the external pinout as
per e.g. https://old.pinouts.ru/HD/MacExtDrive_pinout.shtml
These are subclassed of Storage::Disk::Drive, so accept any disk the emulator supports,
and provide the usual read/write interface for on-disk data.
*/
struct IWMDrive: public Storage::Disk::Drive {
IWMDrive(int input_clock_rate, int number_of_heads) : Storage::Disk::Drive(input_clock_rate, number_of_heads) {}
enum Line: int {
CA0 = 1 << 0,
CA1 = 1 << 1,
CA2 = 1 << 2,
LSTRB = 1 << 3,
SEL = 1 << 4,
};
virtual void set_enabled(bool) = 0;
virtual void set_control_lines(int) = 0;
virtual bool read() = 0;
};
class IWM:
public Storage::Disk::Drive::EventDelegate,
public ClockingHint::Observer {
public:
IWM(int clock_rate);
/// Sets the current external value of the data bus.
void write(int address, uint8_t value);
/*!
Submits an access to address @c address.
@returns The 8-bit value loaded to the data bus by the IWM.
*/
uint8_t read(int address);
/*!
Sets the current input of the IWM's SEL line.
*/
void set_select(bool enabled);
/// Advances the controller by @c cycles.
void run_for(const Cycles cycles);
/// Connects a drive to the IWM.
void set_drive(int slot, IWMDrive *drive);
private:
// Storage::Disk::Drive::EventDelegate.
void process_event(const Storage::Disk::Drive::Event &event) override;
const int clock_rate_;
uint8_t data_register_ = 0;
uint8_t mode_ = 0;
bool read_write_ready_ = true;
bool write_overran_ = false;
int state_ = 0;
int active_drive_ = 0;
IWMDrive *drives_[2] = {nullptr, nullptr};
bool drive_is_rotating_[2] = {false, false};
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override;
Cycles cycles_until_disable_;
uint8_t write_handshake_ = 0x80;
void access(int address);
uint8_t shift_register_ = 0;
uint8_t next_output_ = 0;
int output_bits_remaining_ = 0;
void propose_shift(uint8_t bit);
Cycles cycles_since_shift_;
Cycles bit_length_ = Cycles(16);
void push_drive_state();
enum class ShiftMode {
Reading,
Writing,
CheckingWriteProtect
} shift_mode_;
uint8_t sense();
void select_shift_mode();
};
}
#endif /* IWM_hpp */

View File

@@ -0,0 +1,179 @@
//
// MacintoshDoubleDensityDrive.cpp
// Clock Signal
//
// Created by Thomas Harte on 10/07/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#include "MacintoshDoubleDensityDrive.hpp"
/*
Sources used pervasively:
http://members.iinet.net.au/~kalandi/apple/AUG/1991/11%20NOV.DEC/DISK.STUFF.html
Apple Guide to the Macintosh Family Hardware
Inside Macintosh III
*/
using namespace Apple::Macintosh;
DoubleDensityDrive::DoubleDensityDrive(int input_clock_rate, bool is_800k) :
IWMDrive(input_clock_rate, is_800k ? 2 : 1), // Only 800kb drives are double sided.
is_800k_(is_800k) {
// Start with a valid rotation speed.
if(is_800k) {
Drive::set_rotation_speed(393.3807f);
}
}
// MARK: - Speed Selection
void DoubleDensityDrive::did_step(Storage::Disk::HeadPosition to_position) {
// printf("At track %d\n", to_position.as_int());
// The 800kb drive automatically selects rotation speed as a function of
// head position; the 400kb drive doesn't do so.
if(is_800k_) {
/*
Numbers below cribbed from the Kryoflux forums; specifically:
https://forum.kryoflux.com/viewtopic.php?t=1090
They can almost be worked out algorithmically, since the point is to
produce an almost-constant value for speed*(number of sectors), and:
393.3807 * 12 = 4720.5684
429.1723 * 11 = 4720.895421
472.1435 * 10 = 4721.435
524.5672 * 9 = 4721.1048
590.1098 * 8 = 4720.8784
So 4721 / (number of sectors per track in zone) would give essentially
the same results.
*/
const int zone = to_position.as_int() >> 4;
switch(zone) {
case 0: Drive::set_rotation_speed(393.3807f); break;
case 1: Drive::set_rotation_speed(429.1723f); break;
case 2: Drive::set_rotation_speed(472.1435f); break;
case 3: Drive::set_rotation_speed(524.5672f); break;
default: Drive::set_rotation_speed(590.1098f); break;
}
}
}
void DoubleDensityDrive::set_rotation_speed(float revolutions_per_minute) {
if(!is_800k_) {
// Don't allow drive speeds to drop below 10 RPM, as a temporary sop
// to sanity.
Drive::set_rotation_speed(std::max(10.0f, revolutions_per_minute));
}
}
// MARK: - Control input/output.
void DoubleDensityDrive::set_enabled(bool) {
}
void DoubleDensityDrive::set_control_lines(int lines) {
const auto old_state = control_state_;
control_state_ = lines;
// Catch low-to-high LSTRB transitions.
if((old_state ^ control_state_) & control_state_ & Line::LSTRB) {
switch(control_state_ & (Line::CA2 | Line::CA1 | Line::CA0 | Line::SEL)) {
default:
break;
case 0: // Set step direction — CA2 set => step outward.
case Line::CA2:
step_direction_ = (control_state_ & Line::CA2) ? -1 : 1;
break;
case Line::CA1: // Set drive motor — CA2 set => motor off.
case Line::CA1|Line::CA2:
set_motor_on(!(control_state_ & Line::CA2));
break;
case Line::CA0: // Initiate a step.
step(Storage::Disk::HeadPosition(step_direction_));
break;
case Line::SEL|Line::CA2: // Reset new disk flag.
has_new_disk_ = false;
break;
case Line::CA2 | Line::CA1 | Line::CA0: // Eject the disk.
set_disk(nullptr);
break;
}
}
}
bool DoubleDensityDrive::read() {
switch(control_state_ & (CA2 | CA1 | CA0 | SEL)) {
default:
return false;
case 0: // Head step direction.
// (0 = inward)
return step_direction_ <= 0;
case SEL: // Disk in place.
// (0 = disk present)
return !has_disk();
case CA0: // Disk head step completed.
// (0 = still stepping)
return true; // TODO: stepping delay. But at the main Drive level.
case CA0|SEL: // Disk locked.
// (0 = write protected)
return !get_is_read_only();
case CA1: // Disk motor running.
// (0 = motor on)
return !get_motor_on();
case CA1|SEL: // Head at track 0.
// (0 = at track 0)
// "This bit becomes valid beginning 12 msec after the step that places the head at track 0."
return !get_is_track_zero();
case CA1|CA0: // Disk has been ejected.
// (0 = user has ejected disk)
return !has_new_disk_;
case CA1|CA0|SEL: // Tachometer.
// (arbitrary)
return get_tachometer();
case CA2: // Read data, lower head.
set_head(0);
return false;
case CA2|SEL: // Read data, upper head.
set_head(1);
return false;
case CA2|CA1: // Single- or double-sided drive.
// (0 = single sided)
return get_head_count() != 1;
case CA2|CA1|CA0: // "Present/HD" (per the Mac Plus ROM)
// (0 = ??HD??)
//
// Alternative explanation: "Disk ready for reading?"
// (0 = ready)
return false;
case CA2|CA1|CA0|SEL: // Drive installed.
// (0 = present, 1 = missing)
//
// TODO: why do I need to return this the wrong way around for the Mac Plus?
return true;
}
}
void DoubleDensityDrive::did_set_disk() {
has_new_disk_ = true;
}

View File

@@ -0,0 +1,53 @@
//
// MacintoshDoubleDensityDrive.hpp
// Clock Signal
//
// Created by Thomas Harte on 10/07/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef MacintoshDoubleDensityDrive_hpp
#define MacintoshDoubleDensityDrive_hpp
#include "IWM.hpp"
namespace Apple {
namespace Macintosh {
class DoubleDensityDrive: public IWMDrive {
public:
DoubleDensityDrive(int input_clock_rate, bool is_800k);
/*!
@returns @c true if this is an 800kb drive; @c false otherwise.
*/
bool is_800k() {
return is_800k_;
}
/*!
Sets the current rotation speed of this drive only if it is a 400kb drive.
800kb drives select their own rotation speed based on head position,
and ignore this input.
*/
void set_rotation_speed(float revolutions_per_minute);
void set_enabled(bool) override;
void set_control_lines(int) override;
bool read() override;
private:
// To receive the proper notifications from Storage::Disk::Drive.
void did_step(Storage::Disk::HeadPosition to_position) override;
void did_set_disk() override;
const bool is_800k_;
bool has_new_disk_ = false;
int control_state_ = 0;
int step_direction_ = 1;
};
}
}
#endif /* MacintoshDoubleDensityDrive_hpp */

View File

@@ -19,7 +19,7 @@ ListSelection *ListSelection::list_selection() {
}
BooleanSelection *ListSelection::boolean_selection() {
return new BooleanSelection(value != "no" && value != "n");
return new BooleanSelection(value != "no" && value != "n" && value != "false" && value != "f");
}
BooleanSelection *BooleanSelection::boolean_selection() {

View File

@@ -69,7 +69,7 @@ struct BooleanSelection: public Selection {
struct ListSelection: public Selection {
std::string value;
ListSelection *list_selection();
BooleanSelection *boolean_selection();
ListSelection(const std::string value) : value(value) {}

View File

@@ -33,11 +33,12 @@ bool get_bool(const Configurable::SelectionSet &selections_by_option, const std:
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 & (DisplayRGB | DisplayComposite | DisplaySVideo)) {
if(mask & (DisplayRGB | DisplayCompositeColour | DisplayCompositeMonochrome | DisplaySVideo)) {
std::vector<std::string> display_options;
if(mask & DisplayComposite) display_options.emplace_back("composite");
if(mask & DisplaySVideo) display_options.emplace_back("svideo");
if(mask & DisplayRGB) display_options.emplace_back("rgb");
if(mask & DisplayCompositeColour) display_options.emplace_back("composite");
if(mask & DisplayCompositeMonochrome) display_options.emplace_back("composite-mono");
if(mask & DisplaySVideo) display_options.emplace_back("svideo");
if(mask & DisplayRGB) display_options.emplace_back("rgb");
options.emplace_back(new Configurable::ListOption("Display", "display", display_options));
}
if(mask & AutomaticTapeMotorControl) options.emplace_back(new Configurable::BooleanOption("Automatic Tape Motor Control", "autotapemotor"));
@@ -57,9 +58,10 @@ void Configurable::append_display_selection(Configurable::SelectionSet &selectio
std::string string_selection;
switch(selection) {
default:
case Display::RGB: string_selection = "rgb"; break;
case Display::SVideo: string_selection = "svideo"; break;
case Display::Composite: string_selection = "composite"; break;
case Display::RGB: string_selection = "rgb"; break;
case Display::SVideo: string_selection = "svideo"; break;
case Display::CompositeMonochrome: string_selection = "composite-mono"; break;
case Display::CompositeColour: string_selection = "composite"; break;
}
selection_set["display"] = std::unique_ptr<Configurable::Selection>(new Configurable::ListSelection(string_selection));
}
@@ -85,7 +87,11 @@ bool Configurable::get_display(const Configurable::SelectionSet &selections_by_o
return true;
}
if(display->value == "composite") {
result = Configurable::Display::Composite;
result = Configurable::Display::CompositeColour;
return true;
}
if(display->value == "composite-mono") {
result = Configurable::Display::CompositeMonochrome;
return true;
}
}

View File

@@ -16,15 +16,17 @@ namespace Configurable {
enum StandardOptions {
DisplayRGB = (1 << 0),
DisplaySVideo = (1 << 1),
DisplayComposite = (1 << 2),
QuickLoadTape = (1 << 3),
AutomaticTapeMotorControl = (1 << 4)
DisplayCompositeColour = (1 << 2),
DisplayCompositeMonochrome = (1 << 3),
QuickLoadTape = (1 << 4),
AutomaticTapeMotorControl = (1 << 5)
};
enum class Display {
RGB,
SVideo,
Composite
CompositeColour,
CompositeMonochrome
};
/*!

View File

@@ -128,6 +128,24 @@ class Joystick {
set_input(input, 0.5f);
}
}
/*!
Gets the number of input fire buttons.
This is cached by default, but it's virtual so overridable.
*/
virtual int get_number_of_fire_buttons() {
if(number_of_buttons_ >= 0) return number_of_buttons_;
number_of_buttons_ = 0;
for(const auto &input: get_inputs()) {
if(input.type == Input::Type::Fire) ++number_of_buttons_;
}
return number_of_buttons_;
}
private:
int number_of_buttons_ = -1;
};
/*!
@@ -167,11 +185,11 @@ class ConcreteJoystick: public Joystick {
// convenient hard-coded values. TODO: make these a function of time.
using Type = Joystick::Input::Type;
switch(input.type) {
default: did_set_input(input, is_active ? 1.0f : 0.0f); break;
case Type::Left: did_set_input(Input(Type::Horizontal, input.info.control.index), is_active ? 0.25f : 0.5f); break;
case Type::Right: did_set_input(Input(Type::Horizontal, input.info.control.index), is_active ? 0.75f : 0.5f); break;
case Type::Up: did_set_input(Input(Type::Vertical, input.info.control.index), is_active ? 0.25f : 0.5f); break;
case Type::Down: did_set_input(Input(Type::Vertical, input.info.control.index), is_active ? 0.75f : 0.5f); break;
default: did_set_input(input, is_active ? 1.0f : 0.0f); break;
case Type::Left: did_set_input(Input(Type::Horizontal, input.info.control.index), is_active ? 0.1f : 0.5f); break;
case Type::Right: did_set_input(Input(Type::Horizontal, input.info.control.index), is_active ? 0.9f : 0.5f); break;
case Type::Up: did_set_input(Input(Type::Vertical, input.info.control.index), is_active ? 0.1f : 0.5f); break;
case Type::Down: did_set_input(Input(Type::Vertical, input.info.control.index), is_active ? 0.9f : 0.5f); break;
}
}
@@ -185,7 +203,7 @@ class ConcreteJoystick: public Joystick {
// Otherwise apply a threshold test to convert to digital, with remapping from axes to digital inputs.
using Type = Joystick::Input::Type;
switch(input.type) {
default: did_set_input(input, value > 0.5f); break;
default: did_set_input(input, value > 0.5f); break;
case Type::Horizontal:
did_set_input(Input(Type::Left, input.info.control.index), value <= 0.25f);
did_set_input(Input(Type::Right, input.info.control.index), value >= 0.75f);

View File

@@ -10,7 +10,13 @@
using namespace Inputs;
Keyboard::Keyboard() {}
Keyboard::Keyboard() {
for(int k = 0; k < int(Key::Help); ++k) {
observed_keys_.insert(Key(k));
}
}
Keyboard::Keyboard(const std::set<Key> &observed_keys) : observed_keys_(observed_keys), is_exclusive_(false) {}
void Keyboard::set_key_pressed(Key key, char value, bool is_pressed) {
std::size_t key_offset = static_cast<std::size_t>(key);
@@ -36,3 +42,11 @@ bool Keyboard::get_key_state(Key key) {
if(key_offset >= key_states_.size()) return false;
return key_states_[key_offset];
}
const std::set<Keyboard::Key> &Keyboard::observed_keys() {
return observed_keys_;
}
bool Keyboard::is_exclusive() {
return is_exclusive_;
}

View File

@@ -6,10 +6,11 @@
// Copyright 2017 Thomas Harte. All rights reserved.
//
#ifndef Keyboard_hpp
#define Keyboard_hpp
#ifndef Inputs_Keyboard_hpp
#define Inputs_Keyboard_hpp
#include <vector>
#include <set>
namespace Inputs {
@@ -20,8 +21,6 @@ namespace Inputs {
*/
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,
@@ -39,10 +38,26 @@ class Keyboard {
Help
};
/// Constructs a Keyboard that declares itself to observe all keys.
Keyboard();
/// Constructs a Keyboard that declares itself to observe only members of @c observed_keys.
Keyboard(const std::set<Key> &observed_keys);
// Host interface.
virtual void set_key_pressed(Key key, char value, bool is_pressed);
virtual void reset_all_keys();
/// @returns a set of all Keys that this keyboard responds to.
virtual const std::set<Key> &observed_keys();
/*
@returns @c true if this keyboard, on its original machine, looked
like a complete keyboard — i.e. if a user would expect this keyboard
to be the only thing a real keyboard maps to.
*/
virtual bool is_exclusive();
// Delegate interface.
struct Delegate {
virtual void keyboard_did_change_key(Keyboard *keyboard, Key key, bool is_pressed) = 0;
@@ -52,10 +67,12 @@ class Keyboard {
bool get_key_state(Key key);
private:
std::set<Key> observed_keys_;
std::vector<bool> key_states_;
Delegate *delegate_ = nullptr;
bool is_exclusive_ = true;
};
}
#endif /* Keyboard_hpp */
#endif /* Inputs_Keyboard_hpp */

47
Inputs/Mouse.hpp Normal file
View File

@@ -0,0 +1,47 @@
//
// Mouse.hpp
// Clock Signal
//
// Created by Thomas Harte on 11/06/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef Mouse_h
#define Mouse_h
namespace Inputs {
/*!
Models a classic-era mouse: something that provides 2d relative motion plus
some quantity of buttons.
*/
class Mouse {
public:
/*!
Indicates a movement of the mouse.
*/
virtual void move(int x, int y) {}
/*!
@returns the number of buttons on this mouse.
*/
virtual int get_number_of_buttons() {
return 1;
}
/*!
Indicates that button @c index is now either pressed or unpressed.
The intention is that @c index be semantic, not positional:
0 for the primary button, 1 for the secondary, 2 for the tertiary, etc.
*/
virtual void set_button_pressed(int index, bool is_pressed) {}
/*!
Releases all depressed buttons.
*/
virtual void reset_all_buttons() {}
};
}
#endif /* Mouse_h */

View File

@@ -0,0 +1,123 @@
//
// QuadratureMouse.hpp
// Clock Signal
//
// Created by Thomas Harte on 11/06/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef QuadratureMouse_hpp
#define QuadratureMouse_hpp
#include "../Mouse.hpp"
#include <atomic>
namespace Inputs {
/*!
Provides a simple implementation of a Mouse, designed for simple
thread-safe feeding to a machine that accepts quadrature-encoded input.
TEMPORARY SIMPLIFICATION: it is assumed that the caller will be interested
in observing a signal that dictates velocity, sampling the other to
obtain direction only on transitions in the velocity signal.
Or, more concretely, of the two channels per axis, one is accurate only when
the other transitions. Hence the discussion of 'primary' and 'secondary'
channels below. This is intended to be fixed.
*/
class QuadratureMouse: public Mouse {
public:
QuadratureMouse(int number_of_buttons) :
number_of_buttons_(number_of_buttons) {}
/*
Inputs, to satisfy the Mouse interface.
*/
void move(int x, int y) override {
// Accumulate all provided motion.
axes_[0] += x;
axes_[1] += y;
}
int get_number_of_buttons() override {
return number_of_buttons_;
}
void set_button_pressed(int index, bool is_pressed) override {
if(is_pressed)
button_flags_ |= (1 << index);
else
button_flags_ &= ~(1 << index);
}
void reset_all_buttons() override {
button_flags_ = 0;
}
/*
Outputs.
*/
/*!
Applies a single step from the current accumulated mouse movement, which
might involve the mouse moving right, or left, or not at all.
*/
void prepare_step() {
for(int axis = 0; axis < 2; ++axis) {
// Do nothing if there's no motion to communicate.
const int axis_value = axes_[axis];
if(!axis_value) continue;
// Toggle the primary channel and set the secondary for
// negative motion. At present the y axis signals the
// secondary channel the opposite way around from the
// primary.
primaries_[axis] ^= 1;
secondaries_[axis] = primaries_[axis] ^ axis;
if(axis_value > 0) {
-- axes_[axis];
secondaries_[axis] ^= 1; // Switch to positive motion.
} else {
++ axes_[axis];
}
}
}
/*!
@returns the two quadrature channels — bit 0 is the 'primary' channel
(i.e. the one that can be monitored to observe velocity) and
bit 1 is the 'secondary' (i.e. that which can be queried to
observe direction).
*/
int get_channel(int axis) {
return primaries_[axis] | (secondaries_[axis] << 1);
}
/*!
@returns a bit mask of the currently pressed buttons.
*/
int get_button_mask() {
return button_flags_;
}
/*!
@returns @c true if any mouse motion is waiting to be communicated;
@c false otherwise.
*/
bool has_steps() {
return axes_[0] || axes_[1];
}
private:
int number_of_buttons_ = 0;
std::atomic<int> button_flags_;
std::atomic<int> axes_[2];
int primaries_[2] = {0, 0};
int secondaries_[2] = {0, 0};
};
}
#endif /* QuadratureMouse_hpp */

View File

@@ -30,6 +30,7 @@
#include "../../ClockReceiver/ForceInline.hpp"
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#include "../../Outputs/CRT/CRT.hpp"
#include "../../Analyser/Static/AmstradCPC/Target.hpp"
@@ -40,7 +41,7 @@ namespace AmstradCPC {
std::vector<std::unique_ptr<Configurable::Option>> get_options() {
return Configurable::standard_options(
static_cast<Configurable::StandardOptions>(Configurable::DisplayRGB | Configurable::DisplayComposite)
static_cast<Configurable::StandardOptions>(Configurable::DisplayRGB | Configurable::DisplayCompositeColour)
);
}
@@ -171,10 +172,14 @@ class AYDeferrer {
class CRTCBusHandler {
public:
CRTCBusHandler(uint8_t *ram, InterruptTimer &interrupt_timer) :
crt_(1024, 1, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red2Green2Blue2),
ram_(ram),
interrupt_timer_(interrupt_timer) {
establish_palette_hits();
build_mode_table();
crt_.set_visible_area(Outputs::Display::Rect(0.1072f, 0.1f, 0.842105263157895f, 0.842105263157895f));
crt_.set_brightness(3.0f / 2.0f); // As only the values 0, 1 and 2 will be used in each channel,
// whereas Red2Green2Blue2 defines a range of 0-3.
}
/*!
@@ -217,12 +222,12 @@ class CRTCBusHandler {
if(cycles_) {
switch(previous_output_mode_) {
default:
case OutputMode::Blank: crt_->output_blank(cycles_ * 16); break;
case OutputMode::Sync: crt_->output_sync(cycles_ * 16); break;
case OutputMode::Blank: crt_.output_blank(cycles_ * 16); break;
case OutputMode::Sync: crt_.output_sync(cycles_ * 16); break;
case OutputMode::Border: output_border(cycles_); break;
case OutputMode::ColourBurst: crt_->output_default_colour_burst(cycles_ * 16); break;
case OutputMode::ColourBurst: crt_.output_default_colour_burst(cycles_ * 16); break;
case OutputMode::Pixels:
crt_->output_data(cycles_ * 16, cycles_ * 16 / pixel_divider_);
crt_.output_data(cycles_ * 16, size_t(cycles_ * 16 / pixel_divider_));
pixel_pointer_ = pixel_data_ = nullptr;
break;
}
@@ -238,7 +243,7 @@ class CRTCBusHandler {
// collect some more pixels if output is ongoing
if(previous_output_mode_ == OutputMode::Pixels) {
if(!pixel_data_) {
pixel_pointer_ = pixel_data_ = crt_->allocate_write_area(320, 8);
pixel_pointer_ = pixel_data_ = crt_.begin_data(320, 8);
}
if(pixel_pointer_) {
// the CPC shuffles output lines as:
@@ -283,7 +288,7 @@ class CRTCBusHandler {
// widths so it's not necessarily possible to predict the correct number in advance
// and using the upper bound could lead to inefficient behaviour
if(pixel_pointer_ == pixel_data_ + 320) {
crt_->output_data(cycles_ * 16, cycles_ * 16 / pixel_divider_);
crt_.output_data(cycles_ * 16, size_t(cycles_ * 16 / pixel_divider_));
pixel_pointer_ = pixel_data_ = nullptr;
cycles_ = 0;
}
@@ -323,27 +328,14 @@ class CRTCBusHandler {
was_hsync_ = state.hsync;
}
/// Constructs an appropriate CRT for video output.
void setup_output(float aspect_ratio) {
crt_.reset(new Outputs::CRT::CRT(1024, 16, Outputs::CRT::DisplayType::PAL50, 1));
crt_->set_rgb_sampling_function(
"vec3 rgb_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate)"
"{"
"uint sample = texture(texID, coordinate).r;"
"return vec3(float((sample >> 4) & 3u), float((sample >> 2) & 3u), float(sample & 3u)) / 2.0;"
"}");
crt_->set_visible_area(Outputs::CRT::Rect(0.1072f, 0.1f, 0.842105263157895f, 0.842105263157895f));
crt_->set_video_signal(Outputs::CRT::VideoSignal::RGB);
/// Sets the destination for output.
void set_scan_target(Outputs::Display::ScanTarget *scan_target) {
crt_.set_scan_target(scan_target);
}
/// Destructs the CRT.
void close_output() {
crt_.reset();
}
/// @returns the CRT.
Outputs::CRT::CRT *get_crt() {
return crt_.get();
/// Sets the type of display.
void set_display_type(Outputs::Display::DisplayType display_type) {
crt_.set_display_type(display_type);
}
/*!
@@ -376,10 +368,10 @@ class CRTCBusHandler {
}
private:
void output_border(unsigned int length) {
uint8_t *colour_pointer = static_cast<uint8_t *>(crt_->allocate_write_area(1));
void output_border(int length) {
uint8_t *colour_pointer = static_cast<uint8_t *>(crt_.begin_data(1));
if(colour_pointer) *colour_pointer = border_;
crt_->output_level(length * 16);
crt_.output_level(length * 16);
}
#define Mode0Colour0(c) ((c & 0x80) >> 7) | ((c & 0x20) >> 3) | ((c & 0x08) >> 2) | ((c & 0x02) << 2)
@@ -528,19 +520,19 @@ class CRTCBusHandler {
Border,
Pixels
} previous_output_mode_ = OutputMode::Sync;
unsigned int cycles_ = 0;
int cycles_ = 0;
bool was_hsync_ = false, was_vsync_ = false;
int cycles_into_hsync_ = 0;
std::unique_ptr<Outputs::CRT::CRT> crt_;
Outputs::CRT::CRT crt_;
uint8_t *pixel_data_ = nullptr, *pixel_pointer_ = nullptr;
uint8_t *ram_ = nullptr;
int next_mode_ = 2, mode_ = 2;
unsigned int pixel_divider_ = 1;
int pixel_divider_ = 1;
uint16_t mode0_output_[256];
uint32_t mode1_output_[256];
uint64_t mode2_output_[256];
@@ -757,7 +749,7 @@ class i8255PortHandler : public Intel::i8255::PortHandler {
template <bool has_fdc> class ConcreteMachine:
public CRTMachine::Machine,
public MediaTarget::Machine,
public KeyboardMachine::Machine,
public KeyboardMachine::MappedMachine,
public Utility::TypeRecipient,
public CPU::Z80::BusHandler,
public ClockingHint::Observer,
@@ -789,27 +781,37 @@ template <bool has_fdc> class ConcreteMachine:
ay_.ay().set_port_handler(&key_state_);
// construct the list of necessary ROMs
std::vector<std::string> required_roms = {"amsdos.rom"};
const std::string machine_name = "AmstradCPC";
std::vector<ROMMachine::ROM> required_roms = {
ROMMachine::ROM(machine_name, "the Amstrad Disk Operating System", "amsdos.rom", 16*1024, 0x1fe22ecd)
};
std::string model_number;
uint32_t crcs[2];
switch(target.model) {
default:
model_number = "6128";
has_128k_ = true;
crcs[0] = 0x0219bb74;
crcs[1] = 0xca6af63d;
break;
case Analyser::Static::AmstradCPC::Target::Model::CPC464:
model_number = "464";
has_128k_ = false;
crcs[0] = 0x815752df;
crcs[1] = 0x7d9a3bac;
break;
case Analyser::Static::AmstradCPC::Target::Model::CPC664:
model_number = "664";
has_128k_ = false;
crcs[0] = 0x3f5a6dc4;
crcs[1] = 0x32fee492;
break;
}
required_roms.push_back("os" + model_number + ".rom");
required_roms.push_back("basic" + model_number + ".rom");
required_roms.emplace_back(machine_name, "the CPC " + model_number + " firmware", "os" + model_number + ".rom", 16*1024, crcs[0]);
required_roms.emplace_back(machine_name, "the CPC " + model_number + " BASIC ROM", "basic" + model_number + ".rom", 16*1024, crcs[1]);
// fetch and verify the ROMs
const auto roms = rom_fetcher("AmstradCPC", required_roms);
const auto roms = rom_fetcher(required_roms);
for(std::size_t index = 0; index < roms.size(); ++index) {
auto &data = roms[index];
@@ -981,19 +983,14 @@ template <bool has_fdc> class ConcreteMachine:
flush_fdc();
}
/// A CRTMachine function; indicates that outputs should be created now.
void setup_output(float aspect_ratio) override final {
crtc_bus_handler_.setup_output(aspect_ratio);
/// A CRTMachine function; sets the destination for video.
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override final {
crtc_bus_handler_.set_scan_target(scan_target);
}
/// A CRTMachine function; indicates that outputs should be destroyed now.
void close_output() override final {
crtc_bus_handler_.close_output();
}
/// @returns the CRT in use.
Outputs::CRT::CRT *get_crt() override final {
return crtc_bus_handler_.get_crt();
/// A CRTMachine function; sets the output display type.
void set_display_type(Outputs::Display::DisplayType display_type) override final {
crtc_bus_handler_.set_display_type(display_type);
}
/// @returns the speaker in use.
@@ -1192,7 +1189,7 @@ Machine *Machine::AmstradCPC(const Analyser::Static::Target *target, const ROMMa
using Target = Analyser::Static::AmstradCPC::Target;
const Target *const cpc_target = dynamic_cast<const Target *>(target);
switch(cpc_target->model) {
default: return new AmstradCPC::ConcreteMachine<true>(*cpc_target, rom_fetcher);
default: return new AmstradCPC::ConcreteMachine<true>(*cpc_target, rom_fetcher);
case Target::Model::CPC464: return new AmstradCPC::ConcreteMachine<false>(*cpc_target, rom_fetcher);
}
}

View File

@@ -75,9 +75,9 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
}
uint16_t *CharacterMapper::sequence_for_character(char character) {
#define KEYS(...) {__VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
#define SHIFT(...) {KeyShift, __VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
#define X {KeyboardMachine::Machine::KeyNotMapped}
#define KEYS(...) {__VA_ARGS__, KeyboardMachine::MappedMachine::KeyEndSequence}
#define SHIFT(...) {KeyShift, __VA_ARGS__, KeyboardMachine::MappedMachine::KeyEndSequence}
#define X {KeyboardMachine::MappedMachine::KeyNotMapped}
static KeySequence key_sequences[] = {
/* NUL */ X, /* SOH */ X,
/* STX */ X, /* ETX */ X,

View File

@@ -33,7 +33,7 @@ enum Key: uint16_t {
#undef Line
};
struct KeyboardMapper: public KeyboardMachine::Machine::KeyboardMapper {
struct KeyboardMapper: public KeyboardMachine::MappedMachine::KeyboardMapper {
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key);
};

View File

@@ -0,0 +1,885 @@
//
// AppleII.cpp
// Clock Signal
//
// Created by Thomas Harte on 14/04/2018.
// Copyright 2018 Thomas Harte. All rights reserved.
//
#include "AppleII.hpp"
#include "../../../Activity/Source.hpp"
#include "../../MediaTarget.hpp"
#include "../../CRTMachine.hpp"
#include "../../JoystickMachine.hpp"
#include "../../KeyboardMachine.hpp"
#include "../../Utility/MemoryFuzzer.hpp"
#include "../../Utility/StringSerialiser.hpp"
#include "../../../Processors/6502/6502.hpp"
#include "../../../Components/AudioToggle/AudioToggle.hpp"
#include "../../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#include "../../../Outputs/Log.hpp"
#include "Card.hpp"
#include "DiskIICard.hpp"
#include "Video.hpp"
#include "../../../Analyser/Static/AppleII/Target.hpp"
#include "../../../ClockReceiver/ForceInline.hpp"
#include "../../../Configurable/StandardOptions.hpp"
#include <algorithm>
#include <array>
#include <memory>
namespace Apple {
namespace II {
std::vector<std::unique_ptr<Configurable::Option>> get_options() {
return Configurable::standard_options(
static_cast<Configurable::StandardOptions>(Configurable::DisplayCompositeMonochrome | Configurable::DisplayCompositeColour)
);
}
#define is_iie() ((model == Analyser::Static::AppleII::Target::Model::IIe) || (model == Analyser::Static::AppleII::Target::Model::EnhancedIIe))
template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
public CRTMachine::Machine,
public MediaTarget::Machine,
public KeyboardMachine::MappedMachine,
public CPU::MOS6502::BusHandler,
public Inputs::Keyboard,
public Configurable::Device,
public Apple::II::Machine,
public Activity::Source,
public JoystickMachine::Machine,
public Apple::II::Card::Delegate {
private:
struct VideoBusHandler : public Apple::II::Video::BusHandler {
public:
VideoBusHandler(uint8_t *ram, uint8_t *aux_ram) : ram_(ram), aux_ram_(aux_ram) {}
void perform_read(uint16_t address, size_t count, uint8_t *base_target, uint8_t *auxiliary_target) {
memcpy(base_target, &ram_[address], count);
memcpy(auxiliary_target, &aux_ram_[address], count);
}
private:
uint8_t *ram_, *aux_ram_;
};
CPU::MOS6502::Processor<(model == Analyser::Static::AppleII::Target::Model::EnhancedIIe) ? CPU::MOS6502::Personality::PSynertek65C02 : CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_;
VideoBusHandler video_bus_handler_;
Apple::II::Video::Video<VideoBusHandler, is_iie()> video_;
int cycles_into_current_line_ = 0;
Cycles cycles_since_video_update_;
void update_video() {
video_.run_for(cycles_since_video_update_.flush<Cycles>());
}
static const int audio_divider = 8;
void update_audio() {
speaker_.run_for(audio_queue_, cycles_since_audio_update_.divide(Cycles(audio_divider)));
}
void update_just_in_time_cards() {
for(const auto &card : just_in_time_cards_) {
card->run_for(cycles_since_card_update_, stretched_cycles_since_card_update_);
}
cycles_since_card_update_ = 0;
stretched_cycles_since_card_update_ = 0;
}
uint8_t ram_[65536], aux_ram_[65536];
std::vector<uint8_t> rom_;
uint8_t keyboard_input_ = 0x00;
bool key_is_down_ = false;
uint8_t get_keyboard_input() {
if(string_serialiser_) {
return string_serialiser_->head() | 0x80;
} else {
return keyboard_input_;
}
}
Concurrency::DeferringAsyncTaskQueue audio_queue_;
Audio::Toggle audio_toggle_;
Outputs::Speaker::LowpassSpeaker<Audio::Toggle> speaker_;
Cycles cycles_since_audio_update_;
// MARK: - Cards
std::array<std::unique_ptr<Apple::II::Card>, 7> cards_;
Cycles cycles_since_card_update_;
std::vector<Apple::II::Card *> every_cycle_cards_;
std::vector<Apple::II::Card *> just_in_time_cards_;
int stretched_cycles_since_card_update_ = 0;
void install_card(std::size_t slot, Apple::II::Card *card) {
assert(slot >= 1 && slot < 8);
cards_[slot - 1].reset(card);
card->set_delegate(this);
pick_card_messaging_group(card);
}
bool is_every_cycle_card(Apple::II::Card *card) {
return !card->get_select_constraints();
}
void pick_card_messaging_group(Apple::II::Card *card) {
const bool is_every_cycle = is_every_cycle_card(card);
std::vector<Apple::II::Card *> &intended = is_every_cycle ? every_cycle_cards_ : just_in_time_cards_;
std::vector<Apple::II::Card *> &undesired = is_every_cycle ? just_in_time_cards_ : every_cycle_cards_;
if(std::find(intended.begin(), intended.end(), card) != intended.end()) return;
auto old_membership = std::find(undesired.begin(), undesired.end(), card);
if(old_membership != undesired.end()) undesired.erase(old_membership);
intended.push_back(card);
}
void card_did_change_select_constraints(Apple::II::Card *card) override {
pick_card_messaging_group(card);
}
Apple::II::DiskIICard *diskii_card() {
return dynamic_cast<Apple::II::DiskIICard *>(cards_[5].get());
}
// MARK: - Memory Map.
/*
The Apple II's paging mechanisms are byzantine to say the least. Painful is
another appropriate adjective.
On a II and II+ there are five distinct zones of memory:
0000 to c000 : the main block of RAM
c000 to d000 : the IO area, including card ROMs
d000 to e000 : the low ROM area, which can alternatively contain either one of two 4kb blocks of RAM with a language card
e000 onward : the rest of ROM, also potentially replaced with RAM by a language card
On a IIe with auxiliary memory the following orthogonal changes also need to be factored in:
0000 to 0200 : can be paged independently of the rest of RAM, other than part of the language card area which pages with it
0400 to 0800 : the text screen, can be configured to write to auxiliary RAM
2000 to 4000 : the graphics screen, which can be configured to write to auxiliary RAM
c100 to d000 : can be used to page an additional 3.75kb of ROM, replacing the IO area
c300 to c400 : can contain the same 256-byte segment of the ROM as if the whole IO area were switched, but while leaving cards visible in the rest
c800 to d000 : can contain ROM separately from the region below c800
If dealt with as individual blocks in the inner loop, that would therefore imply mapping
an address to one of 13 potential pageable zones. So I've gone reductive and surrendered
to paging every 6502 page of memory independently. It makes the paging events more expensive,
but hopefully more clear.
*/
uint8_t *read_pages_[256]; // each is a pointer to the 256-block of memory the CPU should read when accessing that page of memory
uint8_t *write_pages_[256]; // as per read_pages_, but this is where the CPU should write. If a pointer is nullptr, don't write.
void page(int start, int end, uint8_t *read, uint8_t *write) {
for(int position = start; position < end; ++position) {
read_pages_[position] = read;
if(read) read += 256;
write_pages_[position] = write;
if(write) write += 256;
}
}
// MARK: - The language card.
struct {
bool bank1 = false;
bool read = false;
bool pre_write = false;
bool write = false;
} language_card_;
bool has_language_card_ = true;
void set_language_card_paging() {
uint8_t *const ram = alternative_zero_page_ ? aux_ram_ : ram_;
uint8_t *const rom = is_iie() ? &rom_[3840] : rom_.data();
page(0xd0, 0xe0,
language_card_.read ? &ram[language_card_.bank1 ? 0xd000 : 0xc000] : rom,
language_card_.write ? nullptr : &ram[language_card_.bank1 ? 0xd000 : 0xc000]);
page(0xe0, 0x100,
language_card_.read ? &ram[0xe000] : &rom[0x1000],
language_card_.write ? nullptr : &ram[0xe000]);
}
// MARK - The IIe's ROM controls.
bool internal_CX_rom_ = false;
bool slot_C3_rom_ = false;
bool internal_c8_rom_ = false;
void set_card_paging() {
page(0xc1, 0xc8, internal_CX_rom_ ? rom_.data() : nullptr, nullptr);
if(!internal_CX_rom_) {
if(!slot_C3_rom_) read_pages_[0xc3] = &rom_[0xc300 - 0xc100];
}
page(0xc8, 0xd0, (internal_CX_rom_ || internal_c8_rom_) ? &rom_[0xc800 - 0xc100] : nullptr, nullptr);
}
// MARK - The IIe's auxiliary RAM controls.
bool alternative_zero_page_ = false;
void set_zero_page_paging() {
if(alternative_zero_page_) {
read_pages_[0] = aux_ram_;
} else {
read_pages_[0] = ram_;
}
read_pages_[1] = read_pages_[0] + 256;
write_pages_[0] = read_pages_[0];
write_pages_[1] = read_pages_[1];
}
bool read_auxiliary_memory_ = false;
bool write_auxiliary_memory_ = false;
void set_main_paging() {
page(0x02, 0xc0,
read_auxiliary_memory_ ? &aux_ram_[0x0200] : &ram_[0x0200],
write_auxiliary_memory_ ? &aux_ram_[0x0200] : &ram_[0x0200]);
if(video_.get_80_store()) {
bool use_aux_ram = video_.get_page2();
page(0x04, 0x08,
use_aux_ram ? &aux_ram_[0x0400] : &ram_[0x0400],
use_aux_ram ? &aux_ram_[0x0400] : &ram_[0x0400]);
if(video_.get_high_resolution()) {
page(0x20, 0x40,
use_aux_ram ? &aux_ram_[0x2000] : &ram_[0x2000],
use_aux_ram ? &aux_ram_[0x2000] : &ram_[0x2000]);
}
}
}
// MARK - typing
std::unique_ptr<Utility::StringSerialiser> string_serialiser_;
// MARK - joysticks
class Joystick: public Inputs::ConcreteJoystick {
public:
Joystick() :
ConcreteJoystick({
Input(Input::Horizontal),
Input(Input::Vertical),
// The Apple II offers three buttons between two joysticks;
// this emulator puts three buttons on each joystick and
// combines them.
Input(Input::Fire, 0),
Input(Input::Fire, 1),
Input(Input::Fire, 2),
}) {}
void did_set_input(const Input &input, float value) override {
if(!input.info.control.index && (input.type == Input::Type::Horizontal || input.type == Input::Type::Vertical))
axes[(input.type == Input::Type::Horizontal) ? 0 : 1] = 1.0f - value;
}
void did_set_input(const Input &input, bool value) override {
if(input.type == Input::Type::Fire && input.info.control.index < 3) {
buttons[input.info.control.index] = value;
}
}
bool buttons[3] = {false, false, false};
float axes[2] = {0.5f, 0.5f};
};
// On an Apple II, the programmer strobes 0xc070 and that causes each analogue input
// to begin a charge and discharge cycle **if they are not already charging**.
// The greater the analogue input, the faster they will charge and therefore the sooner
// they will discharge.
//
// This emulator models that with analogue_charge_ being essentially the amount of time,
// in charge threshold units, since 0xc070 was last strobed. But if any of the analogue
// inputs were already partially charged then they gain a bias in analogue_biases_.
//
// It's a little indirect, but it means only having to increment the one value in the
// main loop.
float analogue_charge_ = 0.0f;
float analogue_biases_[4] = {0.0f, 0.0f, 0.0f, 0.0f};
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
bool analogue_channel_is_discharged(size_t channel) {
return (1.0f - static_cast<Joystick *>(joysticks_[channel >> 1].get())->axes[channel & 1]) < analogue_charge_ + analogue_biases_[channel];
}
// The IIe has three keys that are wired directly to the same input as the joystick buttons.
bool open_apple_is_pressed_ = false;
bool closed_apple_is_pressed_ = false;
public:
ConcreteMachine(const Analyser::Static::AppleII::Target &target, const ROMMachine::ROMFetcher &rom_fetcher):
m6502_(*this),
video_bus_handler_(ram_, aux_ram_),
video_(video_bus_handler_),
audio_toggle_(audio_queue_),
speaker_(audio_toggle_) {
// The system's master clock rate.
const float master_clock = 14318180.0;
// This is where things get slightly convoluted: establish the machine as having a clock rate
// equal to the number of cycles of work the 6502 will actually achieve. Which is less than
// the master clock rate divided by 14 because every 65th cycle is extended by one seventh.
set_clock_rate((master_clock / 14.0) * 65.0 / (65.0 + 1.0 / 7.0));
// The speaker, however, should think it is clocked at half the master clock, per a general
// decision to sample it at seven times the CPU clock (plus stretches).
speaker_.set_input_rate(static_cast<float>(master_clock / (2.0 * static_cast<float>(audio_divider))));
// Apply a 6Khz low-pass filter. This was picked by ear and by an attempt to understand the
// Apple II schematic but, well, I don't claim much insight on the latter. This is definitely
// something to review in the future.
speaker_.set_high_frequency_cutoff(6000);
// Also, start with randomised memory contents.
Memory::Fuzz(ram_, sizeof(ram_));
Memory::Fuzz(aux_ram_, sizeof(aux_ram_));
// Add a couple of joysticks.
joysticks_.emplace_back(new Joystick);
joysticks_.emplace_back(new Joystick);
// Pick the required ROMs.
using Target = Analyser::Static::AppleII::Target;
const std::string machine_name = "AppleII";
std::vector<ROMMachine::ROM> rom_descriptions;
size_t rom_size = 12*1024;
switch(target.model) {
default:
rom_descriptions.emplace_back(machine_name, "the basic Apple II character ROM", "apple2-character.rom", 2*1024, 0x64f415c6);
rom_descriptions.emplace_back(machine_name, "the original Apple II ROM", "apple2o.rom", 12*1024, 0xba210588);
break;
case Target::Model::IIplus:
rom_descriptions.emplace_back(machine_name, "the basic Apple II character ROM", "apple2-character.rom", 2*1024, 0x64f415c6);
rom_descriptions.emplace_back(machine_name, "the Apple II+ ROM", "apple2.rom", 12*1024, 0xf66f9c26);
break;
case Target::Model::IIe:
rom_size += 3840;
rom_descriptions.emplace_back(machine_name, "the Apple IIe character ROM", "apple2eu-character.rom", 4*1024, 0x816a86f1);
rom_descriptions.emplace_back(machine_name, "the Apple IIe ROM", "apple2eu.rom", 32*1024, 0xe12be18d);
break;
case Target::Model::EnhancedIIe:
rom_size += 3840;
rom_descriptions.emplace_back(machine_name, "the Enhanced Apple IIe character ROM", "apple2e-character.rom", 4*1024, 0x2651014d);
rom_descriptions.emplace_back(machine_name, "the Enhanced Apple IIe ROM", "apple2e.rom", 32*1024, 0x65989942);
break;
}
const auto roms = rom_fetcher(rom_descriptions);
// Try to install a Disk II card now, before checking the ROM list,
// to make sure that Disk II dependencies have been communicated.
if(target.disk_controller != Target::DiskController::None) {
// Apple recommended slot 6 for the (first) Disk II.
install_card(6, new Apple::II::DiskIICard(rom_fetcher, target.disk_controller == Target::DiskController::SixteenSector));
}
// Now, check and move the ROMs.
if(!roms[0] || !roms[1]) {
throw ROMMachine::Error::MissingROMs;
}
rom_ = std::move(*roms[1]);
if(rom_.size() > rom_size) {
rom_.erase(rom_.begin(), rom_.end() - static_cast<off_t>(rom_size));
}
video_.set_character_rom(*roms[0]);
// Set up the default memory blocks. On a II or II+ these values will never change.
// On a IIe they'll be affected by selection of auxiliary RAM.
set_main_paging();
set_zero_page_paging();
// Set the whole card area to initially backed by nothing.
page(0xc0, 0xd0, nullptr, nullptr);
// Set proper values for the language card/ROM area.
set_language_card_paging();
insert_media(target.media);
}
~ConcreteMachine() {
audio_queue_.flush();
}
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override {
video_.set_scan_target(scan_target);
}
/// Sets the type of display.
void set_display_type(Outputs::Display::DisplayType display_type) override {
video_.set_display_type(display_type);
}
Outputs::Speaker::Speaker *get_speaker() override {
return &speaker_;
}
forceinline Cycles perform_bus_operation(const CPU::MOS6502::BusOperation operation, const uint16_t address, uint8_t *const value) {
++ cycles_since_video_update_;
++ cycles_since_card_update_;
cycles_since_audio_update_ += Cycles(7);
// The Apple II has a slightly weird timing pattern: every 65th CPU cycle is stretched
// by an extra 1/7th. That's because one cycle lasts 3.5 NTSC colour clocks, so after
// 65 cycles a full line of 227.5 colour clocks have passed. But the high-rate binary
// signal approximation that produces colour needs to be in phase, so a stretch of exactly
// 0.5 further colour cycles is added. The video class handles that implicitly, but it
// needs to be accumulated here for the audio.
cycles_into_current_line_ = (cycles_into_current_line_ + 1) % 65;
const bool is_stretched_cycle = !cycles_into_current_line_;
if(is_stretched_cycle) {
++ cycles_since_audio_update_;
++ stretched_cycles_since_card_update_;
}
bool has_updated_cards = false;
if(read_pages_[address >> 8]) {
if(isReadOperation(operation)) *value = read_pages_[address >> 8][address & 0xff];
else {
if(address >= 0x200 && address < 0x6000) update_video();
if(write_pages_[address >> 8]) write_pages_[address >> 8][address & 0xff] = *value;
}
if(is_iie() && address >= 0xc300 && address < 0xd000) {
bool internal_c8_rom = internal_c8_rom_;
internal_c8_rom |= ((address >> 8) == 0xc3) && !slot_C3_rom_;
internal_c8_rom &= (address != 0xcfff);
if(internal_c8_rom != internal_c8_rom_) {
internal_c8_rom_ = internal_c8_rom;
set_card_paging();
}
}
} else {
// Assume a vapour read unless it turns out otherwise; this is a little
// wasteful but works for now.
//
// Longer version: like many other machines, when the Apple II reads from
// an address at which no hardware loads the data bus, through a process of
// practical analogue effects it'll end up receiving whatever was last on
// the bus. Which will always be whatever the video circuit fetched because
// that fetches in between every instruction.
//
// So this code assumes that'll happen unless it later determines that it
// doesn't. The call into the video isn't free because it's a just-in-time
// actor, but this will actually be the result most of the time so it's not
// too terrible.
if(isReadOperation(operation) && address != 0xc000) {
*value = video_.get_last_read_value(cycles_since_video_update_);
}
switch(address) {
default:
if(isReadOperation(operation)) {
// Read-only switches.
switch(address) {
default: break;
case 0xc000:
*value = get_keyboard_input();
break;
case 0xc001: case 0xc002: case 0xc003: case 0xc004: case 0xc005: case 0xc006: case 0xc007:
case 0xc008: case 0xc009: case 0xc00a: case 0xc00b: case 0xc00c: case 0xc00d: case 0xc00e: case 0xc00f:
*value = (*value & 0x80) | (get_keyboard_input() & 0x7f);
break;
case 0xc061: // Switch input 0.
*value &= 0x7f;
if(
static_cast<Joystick *>(joysticks_[0].get())->buttons[0] || static_cast<Joystick *>(joysticks_[1].get())->buttons[2] ||
(is_iie() && open_apple_is_pressed_)
)
*value |= 0x80;
break;
case 0xc062: // Switch input 1.
*value &= 0x7f;
if(
static_cast<Joystick *>(joysticks_[0].get())->buttons[1] || static_cast<Joystick *>(joysticks_[1].get())->buttons[1] ||
(is_iie() && closed_apple_is_pressed_)
)
*value |= 0x80;
break;
case 0xc063: // Switch input 2.
*value &= 0x7f;
if(static_cast<Joystick *>(joysticks_[0].get())->buttons[2] || static_cast<Joystick *>(joysticks_[1].get())->buttons[0])
*value |= 0x80;
break;
case 0xc064: // Analogue input 0.
case 0xc065: // Analogue input 1.
case 0xc066: // Analogue input 2.
case 0xc067: { // Analogue input 3.
const size_t input = address - 0xc064;
*value &= 0x7f;
if(!analogue_channel_is_discharged(input)) {
*value |= 0x80;
}
} break;
// The IIe-only state reads follow...
#define IIeSwitchRead(s) *value = get_keyboard_input(); if(is_iie()) *value = (*value & 0x7f) | (s ? 0x80 : 0x00);
case 0xc011: IIeSwitchRead(language_card_.bank1); break;
case 0xc012: IIeSwitchRead(language_card_.read); break;
case 0xc013: IIeSwitchRead(read_auxiliary_memory_); break;
case 0xc014: IIeSwitchRead(write_auxiliary_memory_); break;
case 0xc015: IIeSwitchRead(internal_CX_rom_); break;
case 0xc016: IIeSwitchRead(alternative_zero_page_); break;
case 0xc017: IIeSwitchRead(slot_C3_rom_); break;
case 0xc018: IIeSwitchRead(video_.get_80_store()); break;
case 0xc019: IIeSwitchRead(video_.get_is_vertical_blank(cycles_since_video_update_)); break;
case 0xc01a: IIeSwitchRead(video_.get_text()); break;
case 0xc01b: IIeSwitchRead(video_.get_mixed()); break;
case 0xc01c: IIeSwitchRead(video_.get_page2()); break;
case 0xc01d: IIeSwitchRead(video_.get_high_resolution()); break;
case 0xc01e: IIeSwitchRead(video_.get_alternative_character_set()); break;
case 0xc01f: IIeSwitchRead(video_.get_80_columns()); break;
#undef IIeSwitchRead
case 0xc07f:
if(is_iie()) *value = (*value & 0x7f) | (video_.get_annunciator_3() ? 0x80 : 0x00);
break;
}
} else {
// Write-only switches. All IIe as currently implemented.
if(is_iie()) {
switch(address) {
default: break;
case 0xc000:
case 0xc001:
update_video();
video_.set_80_store(!!(address&1));
set_main_paging();
break;
case 0xc002:
case 0xc003:
read_auxiliary_memory_ = !!(address&1);
set_main_paging();
break;
case 0xc004:
case 0xc005:
write_auxiliary_memory_ = !!(address&1);
set_main_paging();
break;
case 0xc006:
case 0xc007:
internal_CX_rom_ = !!(address&1);
set_card_paging();
break;
case 0xc008:
case 0xc009:
// The alternative zero page setting affects both bank 0 and any RAM
// that's paged as though it were on a language card.
alternative_zero_page_ = !!(address&1);
set_zero_page_paging();
set_language_card_paging();
break;
case 0xc00a:
case 0xc00b:
slot_C3_rom_ = !!(address&1);
set_card_paging();
break;
case 0xc00c:
case 0xc00d:
update_video();
video_.set_80_columns(!!(address&1));
break;
case 0xc00e:
case 0xc00f:
update_video();
video_.set_alternative_character_set(!!(address&1));
break;
}
}
}
break;
case 0xc070: { // Permit analogue inputs that are currently discharged to begin a charge cycle.
// Ensure those that were still charging retain that state.
for(size_t c = 0; c < 4; ++c) {
if(analogue_channel_is_discharged(c)) {
analogue_biases_[c] = 0.0f;
} else {
analogue_biases_[c] += analogue_charge_;
}
}
analogue_charge_ = 0.0f;
} break;
/* Switches triggered by reading or writing. */
case 0xc050:
case 0xc051:
update_video();
video_.set_text(!!(address&1));
break;
case 0xc052: update_video(); video_.set_mixed(false); break;
case 0xc053: update_video(); video_.set_mixed(true); break;
case 0xc054:
case 0xc055:
update_video();
video_.set_page2(!!(address&1));
set_main_paging();
break;
case 0xc056:
case 0xc057:
update_video();
video_.set_high_resolution(!!(address&1));
set_main_paging();
break;
case 0xc05e:
case 0xc05f:
if(is_iie()) {
update_video();
video_.set_annunciator_3(!(address&1));
}
break;
case 0xc010:
keyboard_input_ &= 0x7f;
if(string_serialiser_) {
if(!string_serialiser_->advance())
string_serialiser_.reset();
}
// On the IIe, reading C010 returns additional key info.
if(is_iie() && isReadOperation(operation)) {
*value = (key_is_down_ ? 0x80 : 0x00) | (keyboard_input_ & 0x7f);
}
break;
case 0xc030: case 0xc031: case 0xc032: case 0xc033: case 0xc034: case 0xc035: case 0xc036: case 0xc037:
case 0xc038: case 0xc039: case 0xc03a: case 0xc03b: case 0xc03c: case 0xc03d: case 0xc03e: case 0xc03f:
update_audio();
audio_toggle_.set_output(!audio_toggle_.get_output());
break;
case 0xc080: case 0xc084: case 0xc088: case 0xc08c:
case 0xc081: case 0xc085: case 0xc089: case 0xc08d:
case 0xc082: case 0xc086: case 0xc08a: case 0xc08e:
case 0xc083: case 0xc087: case 0xc08b: case 0xc08f:
// Quotes below taken from Understanding the Apple II, p. 5-28 and 5-29.
// "A3 controls the 4K bank selection"
language_card_.bank1 = (address&8);
// "Access to $C080, $C083, $C084, $0087, $C088, $C08B, $C08C, or $C08F sets the READ ENABLE flip-flop"
// (other accesses reset it)
language_card_.read = !(((address&2) >> 1) ^ (address&1));
// "The WRITE ENABLE' flip-flop is reset by an odd read access to the $C08X range when the PRE-WRITE flip-flop is set."
if(language_card_.pre_write && isReadOperation(operation) && (address&1)) language_card_.write = false;
// "[The WRITE ENABLE' flip-flop] is set by an even access in the $C08X range."
if(!(address&1)) language_card_.write = true;
// ("Any other type of access causes the WRITE ENABLE' flip-flop to hold its current state.")
// "The PRE-WRITE flip-flop is set by an odd read access in the $C08X range. It is reset by an even access or a write access."
language_card_.pre_write = isReadOperation(operation) ? (address&1) : false;
// Apply whatever the net effect of all that is to the memory map.
set_language_card_paging();
break;
}
/*
Communication with cards follows.
*/
if(!read_pages_[address >> 8] && address >= 0xc090 && address < 0xc800) {
// If this is a card access, figure out which card is at play before determining
// the totality of who needs messaging.
size_t card_number = 0;
Apple::II::Card::Select select = Apple::II::Card::None;
if(address >= 0xc100) {
/*
Decode the area conventionally used by cards for ROMs:
0xCn00 to 0xCnff: card n.
*/
card_number = (address - 0xc100) >> 8;
select = Apple::II::Card::Device;
} else {
/*
Decode the area conventionally used by cards for registers:
C0n0 to C0nF: card n - 8.
*/
card_number = (address - 0xc090) >> 4;
select = Apple::II::Card::IO;
}
// If the selected card is a just-in-time card, update the just-in-time cards,
// and then message it specifically.
const bool is_read = isReadOperation(operation);
Apple::II::Card *const target = cards_[static_cast<size_t>(card_number)].get();
if(target && !is_every_cycle_card(target)) {
update_just_in_time_cards();
target->perform_bus_operation(select, is_read, address, value);
}
// Update all the every-cycle cards regardless, but send them a ::None select if they're
// not the one actually selected.
for(const auto &card: every_cycle_cards_) {
card->run_for(Cycles(1), is_stretched_cycle);
card->perform_bus_operation(
(card == target) ? select : Apple::II::Card::None,
is_read, address, value);
}
has_updated_cards = true;
}
}
if(!has_updated_cards && !every_cycle_cards_.empty()) {
// Update all every-cycle cards and give them the cycle.
const bool is_read = isReadOperation(operation);
for(const auto &card: every_cycle_cards_) {
card->run_for(Cycles(1), is_stretched_cycle);
card->perform_bus_operation(Apple::II::Card::None, is_read, address, value);
}
}
// Update analogue charge level.
analogue_charge_ = std::min(analogue_charge_ + 1.0f / 2820.0f, 1.1f);
return Cycles(1);
}
void flush() {
update_video();
update_audio();
update_just_in_time_cards();
audio_queue_.perform();
}
void run_for(const Cycles cycles) override {
m6502_.run_for(cycles);
}
void reset_all_keys() override {
open_apple_is_pressed_ = closed_apple_is_pressed_ = key_is_down_ = false;
}
void set_key_pressed(Key key, char value, bool is_pressed) override {
switch(key) {
default: break;
case Key::F12:
m6502_.set_reset_line(is_pressed);
return;
case Key::LeftOption:
open_apple_is_pressed_ = is_pressed;
return;
case Key::RightOption:
closed_apple_is_pressed_ = is_pressed;
return;
}
// If no ASCII value is supplied, look for a few special cases.
if(!value) {
switch(key) {
case Key::Left: value = 0x08; break;
case Key::Right: value = 0x15; break;
case Key::Down: value = 0x0a; break;
case Key::Up: value = 0x0b; break;
case Key::BackSpace: value = 0x7f; break;
default: return;
}
}
// Prior to the IIe, the keyboard could produce uppercase only.
if(!is_iie()) value = static_cast<char>(toupper(value));
if(is_pressed) {
keyboard_input_ = static_cast<uint8_t>(value | 0x80);
key_is_down_ = true;
} else {
if((keyboard_input_ & 0x7f) == value) {
key_is_down_ = false;
}
}
}
Inputs::Keyboard &get_keyboard() override {
return *this;
}
void type_string(const std::string &string) override {
string_serialiser_.reset(new Utility::StringSerialiser(string, true));
}
// MARK:: Configuration options.
std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
return Apple::II::get_options();
}
void set_selections(const Configurable::SelectionSet &selections_by_option) override {
Configurable::Display display;
if(Configurable::get_display(selections_by_option, display)) {
set_video_signal_configurable(display);
}
}
Configurable::SelectionSet get_accurate_selections() override {
Configurable::SelectionSet selection_set;
Configurable::append_display_selection(selection_set, Configurable::Display::CompositeColour);
return selection_set;
}
Configurable::SelectionSet get_user_friendly_selections() override {
return get_accurate_selections();
}
// MARK: MediaTarget
bool insert_media(const Analyser::Static::Media &media) override {
if(!media.disks.empty()) {
auto diskii = diskii_card();
if(diskii) diskii->set_disk(media.disks[0], 0);
}
return true;
}
// MARK: Activity::Source
void set_activity_observer(Activity::Observer *observer) override {
for(const auto &card: cards_) {
if(card) card->set_activity_observer(observer);
}
}
// MARK: JoystickMachine
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
return joysticks_;
}
};
}
}
using namespace Apple::II;
Machine *Machine::AppleII(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
using Target = Analyser::Static::AppleII::Target;
const Target *const appleii_target = dynamic_cast<const Target *>(target);
switch(appleii_target->model) {
default: return nullptr;
case Target::Model::II: return new ConcreteMachine<Target::Model::II>(*appleii_target, rom_fetcher);
case Target::Model::IIplus: return new ConcreteMachine<Target::Model::IIplus>(*appleii_target, rom_fetcher);
case Target::Model::IIe: return new ConcreteMachine<Target::Model::IIe>(*appleii_target, rom_fetcher);
case Target::Model::EnhancedIIe: return new ConcreteMachine<Target::Model::EnhancedIIe>(*appleii_target, rom_fetcher);
}
}
Machine::~Machine() {}

View File

@@ -9,14 +9,15 @@
#ifndef AppleII_hpp
#define AppleII_hpp
#include "../../Configurable/Configurable.hpp"
#include "../../Analyser/Static/StaticAnalyser.hpp"
#include "../ROMMachine.hpp"
#include "../../../Configurable/Configurable.hpp"
#include "../../../Analyser/Static/StaticAnalyser.hpp"
#include "../../ROMMachine.hpp"
#include <memory>
#include <vector>
namespace AppleII {
namespace Apple {
namespace II {
/// @returns The options available for an Apple II.
std::vector<std::unique_ptr<Configurable::Option>> get_options();
@@ -29,6 +30,7 @@ class Machine {
static Machine *AppleII(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher);
};
};
}
}
#endif /* AppleII_hpp */

View File

@@ -9,11 +9,12 @@
#ifndef Card_h
#define Card_h
#include "../../Processors/6502/6502.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../Activity/Observer.hpp"
#include "../../../Processors/6502/6502.hpp"
#include "../../../ClockReceiver/ClockReceiver.hpp"
#include "../../../Activity/Observer.hpp"
namespace AppleII {
namespace Apple {
namespace II {
/*!
This provides a small subset of the interface offered to cards installed in
@@ -39,6 +40,7 @@ namespace AppleII {
*/
class Card {
public:
virtual ~Card() {}
enum Select: int {
None = 0, // No select line is active
IO = 1 << 0, // IO select is active
@@ -108,6 +110,7 @@ class Card {
}
};
}
}
#endif /* Card_h */

View File

@@ -8,15 +8,25 @@
#include "DiskIICard.hpp"
using namespace AppleII;
using namespace Apple::II;
DiskIICard::DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector) : diskii_(2045454) {
const auto roms = rom_fetcher(
"DiskII",
{
is_16_sector ? "boot-16.rom" : "boot-13.rom",
is_16_sector ? "state-machine-16.rom" : "state-machine-13.rom"
std::vector<std::unique_ptr<std::vector<uint8_t>>> roms;
if(is_16_sector) {
roms = rom_fetcher({
{"DiskII", "the Disk II 16-sector boot ROM", "boot-16.rom", 256, 0xce7144f6},
{"DiskII", "the Disk II 16-sector state machine ROM", "state-machine-16.rom", 256, { 0x9796a238, 0xb72a2c70 } }
});
} else {
roms = rom_fetcher({
{"DiskII", "the Disk II 13-sector boot ROM", "boot-13.rom", 256, 0xd34eb2ff},
{"DiskII", "the Disk II 13-sector state machine ROM", "state-machine-13.rom", 256, 0x62e22620 }
});
}
if(!roms[0] || !roms[1]) {
throw ROMMachine::Error::MissingROMs;
}
boot_ = std::move(*roms[0]);
diskii_.set_state_machine(*roms[1]);
set_select_constraints(None);

View File

@@ -10,17 +10,18 @@
#define DiskIICard_hpp
#include "Card.hpp"
#include "../ROMMachine.hpp"
#include "../../ROMMachine.hpp"
#include "../../Components/DiskII/DiskII.hpp"
#include "../../Storage/Disk/Disk.hpp"
#include "../../ClockReceiver/ClockingHintSource.hpp"
#include "../../../Components/DiskII/DiskII.hpp"
#include "../../../Storage/Disk/Disk.hpp"
#include "../../../ClockReceiver/ClockingHintSource.hpp"
#include <cstdint>
#include <memory>
#include <vector>
namespace AppleII {
namespace Apple {
namespace II {
class DiskIICard: public Card, public ClockingHint::Observer {
public:
@@ -41,6 +42,7 @@ class DiskIICard: public Card, public ClockingHint::Observer {
ClockingHint::Preference diskii_clocking_preference_ = ClockingHint::Preference::RealTime;
};
}
}
#endif /* DiskIICard_hpp */

View File

@@ -0,0 +1,340 @@
//
// Video.cpp
// Clock Signal
//
// Created by Thomas Harte on 14/04/2018.
// Copyright 2018 Thomas Harte. All rights reserved.
//
#include "Video.hpp"
using namespace Apple::II::Video;
VideoBase::VideoBase(bool is_iie, std::function<void(Cycles)> &&target) :
crt_(910, 1, Outputs::Display::Type::NTSC60, Outputs::Display::InputDataType::Luminance1),
is_iie_(is_iie),
deferrer_(std::move(target)) {
// Show only the centre 75% of the TV frame.
crt_.set_display_type(Outputs::Display::DisplayType::CompositeColour);
crt_.set_visible_area(Outputs::Display::Rect(0.118f, 0.122f, 0.77f, 0.77f));
// TODO: there seems to be some sort of bug whereby switching modes can cause
// a signal discontinuity that knocks phase out of whack. So it isn't safe to
// use default_colour_bursts elsewhere, though it otherwise should be. If/when
// it is, start doing so and return to setting the immediate phase up here.
// crt_.set_immediate_default_phase(0.5f);
character_zones[0].xor_mask = 0;
character_zones[0].address_mask = 0x3f;
character_zones[1].xor_mask = 0;
character_zones[1].address_mask = 0x3f;
character_zones[2].xor_mask = 0;
character_zones[2].address_mask = 0x3f;
character_zones[3].xor_mask = 0;
character_zones[3].address_mask = 0x3f;
if(is_iie) {
character_zones[0].xor_mask =
character_zones[2].xor_mask =
character_zones[3].xor_mask = 0xff;
character_zones[2].address_mask =
character_zones[3].address_mask = 0xff;
}
}
void VideoBase::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
crt_.set_scan_target(scan_target);
}
void VideoBase::set_display_type(Outputs::Display::DisplayType display_type) {
crt_.set_display_type(display_type);
}
/*
Rote setters and getters.
*/
void VideoBase::set_alternative_character_set(bool alternative_character_set) {
set_alternative_character_set_ = alternative_character_set;
deferrer_.defer(Cycles(2), [=] {
alternative_character_set_ = alternative_character_set;
if(alternative_character_set) {
character_zones[1].address_mask = 0xff;
character_zones[1].xor_mask = 0;
} else {
character_zones[1].address_mask = 0x3f;
character_zones[1].xor_mask = flash_mask();
}
});
}
bool VideoBase::get_alternative_character_set() {
return set_alternative_character_set_;
}
void VideoBase::set_80_columns(bool columns_80) {
set_columns_80_ = columns_80;
deferrer_.defer(Cycles(2), [=] {
columns_80_ = columns_80;
});
}
bool VideoBase::get_80_columns() {
return set_columns_80_;
}
void VideoBase::set_80_store(bool store_80) {
set_store_80_ = store_80_ = store_80;
}
bool VideoBase::get_80_store() {
return set_store_80_;
}
void VideoBase::set_page2(bool page2) {
set_page2_ = page2_ = page2;
}
bool VideoBase::get_page2() {
return set_page2_;
}
void VideoBase::set_text(bool text) {
set_text_ = text;
deferrer_.defer(Cycles(2), [=] {
text_ = text;
});
}
bool VideoBase::get_text() {
return set_text_;
}
void VideoBase::set_mixed(bool mixed) {
set_mixed_ = mixed;
deferrer_.defer(Cycles(2), [=] {
mixed_ = mixed;
});
}
bool VideoBase::get_mixed() {
return set_mixed_;
}
void VideoBase::set_high_resolution(bool high_resolution) {
set_high_resolution_ = high_resolution;
deferrer_.defer(Cycles(2), [=] {
high_resolution_ = high_resolution;
});
}
bool VideoBase::get_high_resolution() {
return set_high_resolution_;
}
void VideoBase::set_annunciator_3(bool annunciator_3) {
set_annunciator_3_ = annunciator_3;
deferrer_.defer(Cycles(2), [=] {
annunciator_3_ = annunciator_3;
high_resolution_mask_ = annunciator_3_ ? 0x7f : 0xff;
});
}
bool VideoBase::get_annunciator_3() {
return set_annunciator_3_;
}
void VideoBase::set_character_rom(const std::vector<uint8_t> &character_rom) {
character_rom_ = character_rom;
// Flip all character contents based on the second line of the $ graphic.
if(character_rom_[0x121] == 0x3c || character_rom_[0x122] == 0x3c) {
for(auto &graphic : character_rom_) {
graphic =
((graphic & 0x01) ? 0x40 : 0x00) |
((graphic & 0x02) ? 0x20 : 0x00) |
((graphic & 0x04) ? 0x10 : 0x00) |
((graphic & 0x08) ? 0x08 : 0x00) |
((graphic & 0x10) ? 0x04 : 0x00) |
((graphic & 0x20) ? 0x02 : 0x00) |
((graphic & 0x40) ? 0x01 : 0x00);
}
}
}
void VideoBase::output_text(uint8_t *target, const uint8_t *const source, size_t length, size_t pixel_row) const {
for(size_t c = 0; c < length; ++c) {
const int character = source[c] & character_zones[source[c] >> 6].address_mask;
const uint8_t xor_mask = character_zones[source[c] >> 6].xor_mask;
const std::size_t character_address = static_cast<std::size_t>(character << 3) + pixel_row;
const uint8_t character_pattern = character_rom_[character_address] ^ xor_mask;
// The character ROM is output MSB to LSB rather than LSB to MSB.
target[0] = target[1] = character_pattern & 0x40;
target[2] = target[3] = character_pattern & 0x20;
target[4] = target[5] = character_pattern & 0x10;
target[6] = target[7] = character_pattern & 0x08;
target[8] = target[9] = character_pattern & 0x04;
target[10] = target[11] = character_pattern & 0x02;
target[12] = target[13] = character_pattern & 0x01;
graphics_carry_ = character_pattern & 0x01;
target += 14;
}
}
void VideoBase::output_double_text(uint8_t *target, const uint8_t *const source, const uint8_t *const auxiliary_source, size_t length, size_t pixel_row) const {
for(size_t c = 0; c < length; ++c) {
const std::size_t character_addresses[2] = {
static_cast<std::size_t>(
(auxiliary_source[c] & character_zones[auxiliary_source[c] >> 6].address_mask) << 3
) + pixel_row,
static_cast<std::size_t>(
(source[c] & character_zones[source[c] >> 6].address_mask) << 3
) + pixel_row
};
const uint8_t character_patterns[2] = {
static_cast<uint8_t>(
character_rom_[character_addresses[0]] ^ character_zones[auxiliary_source[c] >> 6].xor_mask
),
static_cast<uint8_t>(
character_rom_[character_addresses[1]] ^ character_zones[source[c] >> 6].xor_mask
)
};
// The character ROM is output MSB to LSB rather than LSB to MSB.
target[0] = character_patterns[0] & 0x40;
target[1] = character_patterns[0] & 0x20;
target[2] = character_patterns[0] & 0x10;
target[3] = character_patterns[0] & 0x08;
target[4] = character_patterns[0] & 0x04;
target[5] = character_patterns[0] & 0x02;
target[6] = character_patterns[0] & 0x01;
target[7] = character_patterns[1] & 0x40;
target[8] = character_patterns[1] & 0x20;
target[9] = character_patterns[1] & 0x10;
target[10] = character_patterns[1] & 0x08;
target[11] = character_patterns[1] & 0x04;
target[12] = character_patterns[1] & 0x02;
target[13] = character_patterns[1] & 0x01;
graphics_carry_ = character_patterns[1] & 0x01;
target += 14;
}
}
void VideoBase::output_low_resolution(uint8_t *target, const uint8_t *const source, size_t length, int column, int row) const {
const int row_shift = row&4;
for(size_t c = 0; c < length; ++c) {
// Low-resolution graphics mode shifts the colour code on a loop, but has to account for whether this
// 14-sample output window is starting at the beginning of a colour cycle or halfway through.
if((column + static_cast<int>(c))&1) {
target[0] = target[4] = target[8] = target[12] = (source[c] >> row_shift) & 4;
target[1] = target[5] = target[9] = target[13] = (source[c] >> row_shift) & 8;
target[2] = target[6] = target[10] = (source[c] >> row_shift) & 1;
target[3] = target[7] = target[11] = (source[c] >> row_shift) & 2;
graphics_carry_ = (source[c] >> row_shift) & 8;
} else {
target[0] = target[4] = target[8] = target[12] = (source[c] >> row_shift) & 1;
target[1] = target[5] = target[9] = target[13] = (source[c] >> row_shift) & 2;
target[2] = target[6] = target[10] = (source[c] >> row_shift) & 4;
target[3] = target[7] = target[11] = (source[c] >> row_shift) & 8;
graphics_carry_ = (source[c] >> row_shift) & 2;
}
target += 14;
}
}
void VideoBase::output_fat_low_resolution(uint8_t *target, const uint8_t *const source, size_t length, int column, int row) const {
const int row_shift = row&4;
for(size_t c = 0; c < length; ++c) {
// Fat low-resolution mode appears not to do anything to try to make odd and
// even columns compatible.
target[0] = target[1] = target[8] = target[9] = (source[c] >> row_shift) & 1;
target[2] = target[3] = target[10] = target[11] = (source[c] >> row_shift) & 2;
target[4] = target[5] = target[12] = target[13] = (source[c] >> row_shift) & 4;
target[6] = target[7] = (source[c] >> row_shift) & 8;
graphics_carry_ = (source[c] >> row_shift) & 4;
target += 14;
}
}
void VideoBase::output_double_low_resolution(uint8_t *target, const uint8_t *const source, const uint8_t *const auxiliary_source, size_t length, int column, int row) const {
const int row_shift = row&4;
for(size_t c = 0; c < length; ++c) {
if((column + static_cast<int>(c))&1) {
target[0] = target[4] = (auxiliary_source[c] >> row_shift) & 2;
target[1] = target[5] = (auxiliary_source[c] >> row_shift) & 4;
target[2] = target[6] = (auxiliary_source[c] >> row_shift) & 8;
target[3] = (auxiliary_source[c] >> row_shift) & 1;
target[8] = target[12] = (source[c] >> row_shift) & 4;
target[9] = target[13] = (source[c] >> row_shift) & 8;
target[10] = (source[c] >> row_shift) & 1;
target[7] = target[11] = (source[c] >> row_shift) & 2;
graphics_carry_ = (source[c] >> row_shift) & 8;
} else {
target[0] = target[4] = (auxiliary_source[c] >> row_shift) & 8;
target[1] = target[5] = (auxiliary_source[c] >> row_shift) & 1;
target[2] = target[6] = (auxiliary_source[c] >> row_shift) & 2;
target[3] = (auxiliary_source[c] >> row_shift) & 4;
target[8] = target[12] = (source[c] >> row_shift) & 1;
target[9] = target[13] = (source[c] >> row_shift) & 2;
target[10] = (source[c] >> row_shift) & 4;
target[7] = target[11] = (source[c] >> row_shift) & 8;
graphics_carry_ = (source[c] >> row_shift) & 2;
}
target += 14;
}
}
void VideoBase::output_high_resolution(uint8_t *target, const uint8_t *const source, size_t length) const {
for(size_t c = 0; c < length; ++c) {
// High resolution graphics shift out LSB to MSB, optionally with a delay of half a pixel.
// If there is a delay, the previous output level is held to bridge the gap.
// Delays may be ignored on a IIe if Annunciator 3 is set; that's the state that
// high_resolution_mask_ models.
if(source[c] & high_resolution_mask_ & 0x80) {
target[0] = graphics_carry_;
target[1] = target[2] = source[c] & 0x01;
target[3] = target[4] = source[c] & 0x02;
target[5] = target[6] = source[c] & 0x04;
target[7] = target[8] = source[c] & 0x08;
target[9] = target[10] = source[c] & 0x10;
target[11] = target[12] = source[c] & 0x20;
target[13] = source[c] & 0x40;
} else {
target[0] = target[1] = source[c] & 0x01;
target[2] = target[3] = source[c] & 0x02;
target[4] = target[5] = source[c] & 0x04;
target[6] = target[7] = source[c] & 0x08;
target[8] = target[9] = source[c] & 0x10;
target[10] = target[11] = source[c] & 0x20;
target[12] = target[13] = source[c] & 0x40;
}
graphics_carry_ = source[c] & 0x40;
target += 14;
}
}
void VideoBase::output_double_high_resolution(uint8_t *target, const uint8_t *const source, const uint8_t *const auxiliary_source, size_t length) const {
for(size_t c = 0; c < length; ++c) {
target[0] = auxiliary_source[c] & 0x01;
target[1] = auxiliary_source[c] & 0x02;
target[2] = auxiliary_source[c] & 0x04;
target[3] = auxiliary_source[c] & 0x08;
target[4] = auxiliary_source[c] & 0x10;
target[5] = auxiliary_source[c] & 0x20;
target[6] = auxiliary_source[c] & 0x40;
target[7] = source[c] & 0x01;
target[8] = source[c] & 0x02;
target[9] = source[c] & 0x04;
target[10] = source[c] & 0x08;
target[11] = source[c] & 0x10;
target[12] = source[c] & 0x20;
target[13] = source[c] & 0x40;
graphics_carry_ = auxiliary_source[c] & 0x40;
target += 14;
}
}

View File

@@ -0,0 +1,608 @@
//
// Video.hpp
// Clock Signal
//
// Created by Thomas Harte on 14/04/2018.
// Copyright 2018 Thomas Harte. All rights reserved.
//
#ifndef Video_hpp
#define Video_hpp
#include "../../../Outputs/CRT/CRT.hpp"
#include "../../../ClockReceiver/ClockReceiver.hpp"
#include "../../../ClockReceiver/DeferredQueue.hpp"
#include <array>
#include <vector>
namespace Apple {
namespace II {
namespace Video {
class BusHandler {
public:
/*!
Requests fetching of the @c count bytes starting from @c address.
The handler should write the values from base memory to @c base_target, and those
from auxiliary memory to @c auxiliary_target. If the machine has no axiliary memory,
it needn't write anything to auxiliary_target.
*/
void perform_read(uint16_t address, size_t count, uint8_t *base_target, uint8_t *auxiliary_target) {
}
};
class VideoBase {
public:
VideoBase(bool is_iie, std::function<void(Cycles)> &&target);
/// Sets the scan target.
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
/// Sets the type of output.
void set_display_type(Outputs::Display::DisplayType);
/*
Descriptions for the setters below are taken verbatim from
the Apple IIe Technical Reference. Addresses are the conventional
locations within the Apple II memory map. Only those which affect
video output are implemented here.
Those registers which don't exist on a II/II+ are marked.
*/
/*!
Setter for ALTCHAR ($C00E/$C00F; triggers on write only):
* Off: display text using primary character set.
* On: display text using alternate character set.
Doesn't exist on a II/II+.
*/
void set_alternative_character_set(bool);
bool get_alternative_character_set();
/*!
Setter for 80COL ($C00C/$C00D; triggers on write only).
* Off: display 40 columns.
* On: display 80 columns.
Doesn't exist on a II/II+.
*/
void set_80_columns(bool);
bool get_80_columns();
/*!
Setter for 80STORE ($C000/$C001; triggers on write only).
* Off: cause PAGE2 to select auxiliary RAM.
* On: cause PAGE2 to switch main RAM areas.
Doesn't exist on a II/II+.
*/
void set_80_store(bool);
bool get_80_store();
/*!
Setter for PAGE2 ($C054/$C055; triggers on read or write).
* Off: select Page 1.
* On: select Page 2 or, if 80STORE on, Page 1 in auxiliary memory.
80STORE doesn't exist on a II/II+; therefore this always selects
either Page 1 or Page 2 on those machines.
*/
void set_page2(bool);
bool get_page2();
/*!
Setter for TEXT ($C050/$C051; triggers on read or write).
* Off: display graphics or, if MIXED on, mixed.
* On: display text.
*/
void set_text(bool);
bool get_text();
/*!
Setter for MIXED ($C052/$C053; triggers on read or write).
* Off: display only text or only graphics.
* On: if TEXT off, display text and graphics.
*/
void set_mixed(bool);
bool get_mixed();
/*!
Setter for HIRES ($C056/$C057; triggers on read or write).
* Off: if TEXT off, display low-resolution graphics.
* On: if TEXT off, display high-resolution or, if DHIRES on, double high-resolution graphics.
DHIRES doesn't exist on a II/II+; therefore this always selects
either high- or low-resolution graphics on those machines.
Despite Apple's documentation, the IIe also supports double low-resolution
graphics, which are the 80-column analogue to ordinary low-resolution 40-column
low-resolution graphics.
*/
void set_high_resolution(bool);
bool get_high_resolution();
/*!
Setter for annunciator 3.
* On: turn on annunciator 3.
* Off: turn off annunciator 3.
This exists on both the II/II+ and the IIe, but has no effect on
video on the older machines. It's intended to be used on the IIe
to confirm double-high resolution mode but has side effects in
selecting mixed mode output and discarding high-resolution
delay bits.
*/
void set_annunciator_3(bool);
bool get_annunciator_3();
// Setup for text mode.
void set_character_rom(const std::vector<uint8_t> &);
protected:
Outputs::CRT::CRT crt_;
// State affecting output video stream generation.
uint8_t *pixel_pointer_ = nullptr;
// State affecting logical state.
int row_ = 0, column_ = 0, flash_ = 0;
uint8_t flash_mask() {
return static_cast<uint8_t>((flash_ / flash_length) * 0xff);
}
// Enumerates all Apple II and IIe display modes.
enum class GraphicsMode {
Text = 0,
DoubleText,
HighRes,
DoubleHighRes,
LowRes,
DoubleLowRes,
FatLowRes
};
bool is_text_mode(GraphicsMode m) { return m <= GraphicsMode::DoubleText; }
bool is_double_mode(GraphicsMode m) { return !!(static_cast<int>(m)&1); }
// Various soft-switch values.
bool alternative_character_set_ = false, set_alternative_character_set_ = false;
bool columns_80_ = false, set_columns_80_ = false;
bool store_80_ = false, set_store_80_ = false;
bool page2_ = false, set_page2_ = false;
bool text_ = true, set_text_ = true;
bool mixed_ = false, set_mixed_ = false;
bool high_resolution_ = false, set_high_resolution_ = false;
bool annunciator_3_ = false, set_annunciator_3_ = false;
// Graphics carry is the final level output in a fetch window;
// it carries on into the next if it's high resolution with
// the delay bit set.
mutable uint8_t graphics_carry_ = 0;
bool was_double_ = false;
uint8_t high_resolution_mask_ = 0xff;
// This holds a copy of the character ROM. The regular character
// set is assumed to be in the first 64*8 bytes; the alternative
// is in the 128*8 bytes after that.
std::vector<uint8_t> character_rom_;
// Memory is fetched ahead of time into this array;
// this permits the correct delay between fetching
// without having to worry about a rolling buffer.
std::array<uint8_t, 40> base_stream_;
std::array<uint8_t, 40> auxiliary_stream_;
bool is_iie_ = false;
static const int flash_length = 8406;
// Describes the current text mode mapping from in-memory character index
// to output character.
struct CharacterMapping {
uint8_t address_mask;
uint8_t xor_mask;
};
CharacterMapping character_zones[4];
/*!
Outputs 40-column text to @c target, using @c length bytes from @c source.
*/
void output_text(uint8_t *target, const uint8_t *source, size_t length, size_t pixel_row) const;
/*!
Outputs 80-column text to @c target, drawing @c length columns from @c source and @c auxiliary_source.
*/
void output_double_text(uint8_t *target, const uint8_t *source, const uint8_t *auxiliary_source, size_t length, size_t pixel_row) const;
/*!
Outputs 40-column low-resolution graphics to @c target, drawing @c length columns from @c source.
*/
void output_low_resolution(uint8_t *target, const uint8_t *source, size_t length, int column, int row) const;
/*!
Outputs 80-column low-resolution graphics to @c target, drawing @c length columns from @c source and @c auxiliary_source.
*/
void output_double_low_resolution(uint8_t *target, const uint8_t *source, const uint8_t *auxiliary_source, size_t length, int column, int row) const;
/*!
Outputs 40-column high-resolution graphics to @c target, drawing @c length columns from @c source.
*/
void output_high_resolution(uint8_t *target, const uint8_t *source, size_t length) const;
/*!
Outputs 80-column double-high-resolution graphics to @c target, drawing @c length columns from @c source.
*/
void output_double_high_resolution(uint8_t *target, const uint8_t *source, const uint8_t *auxiliary_source, size_t length) const;
/*!
Outputs 40-column "fat low resolution" graphics to @c target, drawing @c length columns from @c source.
Fat low-resolution mode is like regular low-resolution mode except that data is shifted out on the 7M
clock rather than the 14M.
*/
void output_fat_low_resolution(uint8_t *target, const uint8_t *source, size_t length, int column, int row) const;
// Maintain a DeferredQueue for delayed mode switches.
DeferredQueue<Cycles> deferrer_;
};
template <class BusHandler, bool is_iie> class Video: public VideoBase {
public:
/// Constructs an instance of the video feed; a CRT is also created.
Video(BusHandler &bus_handler) :
VideoBase(is_iie, [=] (Cycles cycles) { advance(cycles); }),
bus_handler_(bus_handler) {}
/*!
Runs video for @c cycles.
*/
void run_for(Cycles cycles) {
deferrer_.run_for(cycles);
}
/*!
Obtains the last value the video read prior to time now+offset.
*/
uint8_t get_last_read_value(Cycles offset) {
// Rules of generation:
// (1) a complete sixty-five-cycle scan line consists of sixty-five consecutive bytes of
// display buffer memory that starts twenty-five bytes prior to the actual data to be displayed.
// (2) During VBL the data acts just as if it were starting a whole new frame from the beginning, but
// it never finishes this pseudo-frame. After getting one third of the way through the frame (to
// scan line $3F), it suddenly repeats the previous six scan lines ($3A through $3F) before aborting
// to begin the next true frame.
//
// Source: Have an Apple Split by Bob Bishop; http://rich12345.tripod.com/aiivideo/softalk.html
// Determine column at offset.
int mapped_column = column_ + offset.as_int();
// Map that backwards from the internal pixels-at-start generation to pixels-at-end
// (so what was column 0 is now column 25).
mapped_column += 25;
// Apply carry into the row counter.
int mapped_row = row_ + (mapped_column / 65);
mapped_column %= 65;
mapped_row %= 262;
// Apple out-of-bounds row logic.
if(mapped_row >= 256) {
mapped_row = 0x3a + (mapped_row&255);
} else {
mapped_row %= 192;
}
// Calculate the address and return the value.
uint16_t read_address = static_cast<uint16_t>(get_row_address(mapped_row) + mapped_column - 25);
uint8_t value, aux_value;
bus_handler_.perform_read(read_address, 1, &value, &aux_value);
return value;
}
/*!
@returns @c true if the display will be within vertical blank at now + @c offset; @c false otherwise.
*/
bool get_is_vertical_blank(Cycles offset) {
// Map that backwards from the internal pixels-at-start generation to pixels-at-end
// (so what was column 0 is now column 25).
int mapped_column = column_ + offset.as_int();
// Map that backwards from the internal pixels-at-start generation to pixels-at-end
// (so what was column 0 is now column 25).
mapped_column += 25;
// Apply carry into the row counter and test it for location.
int mapped_row = row_ + (mapped_column / 65);
return (mapped_row % 262) >= 192;
}
private:
/*!
Advances time by @c cycles; expects to be fed by the CPU clock.
Implicitly adds an extra half a colour clock at the end of
line.
*/
void advance(Cycles cycles) {
/*
Addressing scheme used throughout is that column 0 is the first column with pixels in it;
row 0 is the first row with pixels in it.
A frame is oriented around 65 cycles across, 262 lines down.
*/
static const int first_sync_line = 220; // A complete guess. Information needed.
static const int first_sync_column = 49; // Also a guess.
static const int sync_length = 4; // One of the two likely candidates.
int int_cycles = cycles.as_int();
while(int_cycles) {
const int cycles_this_line = std::min(65 - column_, int_cycles);
const int ending_column = column_ + cycles_this_line;
const bool is_vertical_sync_line = (row_ >= first_sync_line && row_ < first_sync_line + 3);
if(is_vertical_sync_line) {
// In effect apply an XOR to HSYNC and VSYNC flags in order to include equalising
// pulses (and hencce keep hsync approximately where it should be during vsync).
const int blank_start = std::max(first_sync_column - sync_length, column_);
const int blank_end = std::min(first_sync_column, ending_column);
if(blank_end > blank_start) {
if(blank_start > column_) {
crt_.output_sync((blank_start - column_) * 14);
}
crt_.output_blank((blank_end - blank_start) * 14);
if(blank_end < ending_column) {
crt_.output_sync((ending_column - blank_end) * 14);
}
} else {
crt_.output_sync(cycles_this_line * 14);
}
} else {
const GraphicsMode line_mode = graphics_mode(row_);
// Determine whether there's any fetching to do. Fetching occurs during the first
// 40 columns of rows prior to 192.
if(row_ < 192 && column_ < 40) {
const int character_row = row_ >> 3;
const uint16_t row_address = static_cast<uint16_t>((character_row >> 3) * 40 + ((character_row&7) << 7));
// Grab the memory contents that'll be needed momentarily.
const int fetch_end = std::min(40, ending_column);
uint16_t fetch_address;
switch(line_mode) {
default:
case GraphicsMode::Text:
case GraphicsMode::DoubleText:
case GraphicsMode::LowRes:
case GraphicsMode::FatLowRes:
case GraphicsMode::DoubleLowRes: {
const uint16_t text_address = static_cast<uint16_t>(((video_page()+1) * 0x400) + row_address);
fetch_address = static_cast<uint16_t>(text_address + column_);
} break;
case GraphicsMode::HighRes:
case GraphicsMode::DoubleHighRes:
fetch_address = static_cast<uint16_t>(((video_page()+1) * 0x2000) + row_address + ((row_&7) << 10) + column_);
break;
}
bus_handler_.perform_read(
fetch_address,
static_cast<size_t>(fetch_end - column_),
&base_stream_[static_cast<size_t>(column_)],
&auxiliary_stream_[static_cast<size_t>(column_)]);
}
if(row_ < 192) {
// The pixel area is the first 40.5 columns; base contents
// remain where they would naturally be but auxiliary
// graphics appear to the left of that.
if(!column_) {
pixel_pointer_ = crt_.begin_data(568);
graphics_carry_ = 0;
was_double_ = true;
}
if(column_ < 40) {
const int pixel_start = std::max(0, column_);
const int pixel_end = std::min(40, ending_column);
const int pixel_row = row_ & 7;
const bool is_double = Video::is_double_mode(line_mode);
if(!is_double && was_double_ && pixel_pointer_) {
pixel_pointer_[pixel_start*14 + 0] =
pixel_pointer_[pixel_start*14 + 1] =
pixel_pointer_[pixel_start*14 + 2] =
pixel_pointer_[pixel_start*14 + 3] =
pixel_pointer_[pixel_start*14 + 4] =
pixel_pointer_[pixel_start*14 + 5] =
pixel_pointer_[pixel_start*14 + 6] = 0;
}
was_double_ = is_double;
if(pixel_pointer_) {
switch(line_mode) {
case GraphicsMode::Text:
output_text(
&pixel_pointer_[pixel_start * 14 + 7],
&base_stream_[static_cast<size_t>(pixel_start)],
static_cast<size_t>(pixel_end - pixel_start),
static_cast<size_t>(pixel_row));
break;
case GraphicsMode::DoubleText:
output_double_text(
&pixel_pointer_[pixel_start * 14],
&base_stream_[static_cast<size_t>(pixel_start)],
&auxiliary_stream_[static_cast<size_t>(pixel_start)],
static_cast<size_t>(pixel_end - pixel_start),
static_cast<size_t>(pixel_row));
break;
case GraphicsMode::LowRes:
output_low_resolution(
&pixel_pointer_[pixel_start * 14 + 7],
&base_stream_[static_cast<size_t>(pixel_start)],
static_cast<size_t>(pixel_end - pixel_start),
pixel_start,
pixel_row);
break;
case GraphicsMode::FatLowRes:
output_fat_low_resolution(
&pixel_pointer_[pixel_start * 14 + 7],
&base_stream_[static_cast<size_t>(pixel_start)],
static_cast<size_t>(pixel_end - pixel_start),
pixel_start,
pixel_row);
break;
case GraphicsMode::DoubleLowRes:
output_double_low_resolution(
&pixel_pointer_[pixel_start * 14],
&base_stream_[static_cast<size_t>(pixel_start)],
&auxiliary_stream_[static_cast<size_t>(pixel_start)],
static_cast<size_t>(pixel_end - pixel_start),
pixel_start,
pixel_row);
break;
case GraphicsMode::HighRes:
output_high_resolution(
&pixel_pointer_[pixel_start * 14 + 7],
&base_stream_[static_cast<size_t>(pixel_start)],
static_cast<size_t>(pixel_end - pixel_start));
break;
case GraphicsMode::DoubleHighRes:
output_double_high_resolution(
&pixel_pointer_[pixel_start * 14],
&base_stream_[static_cast<size_t>(pixel_start)],
&auxiliary_stream_[static_cast<size_t>(pixel_start)],
static_cast<size_t>(pixel_end - pixel_start));
break;
default: break;
}
}
if(pixel_end == 40) {
if(pixel_pointer_) {
if(was_double_) {
pixel_pointer_[560] = pixel_pointer_[561] = pixel_pointer_[562] = pixel_pointer_[563] =
pixel_pointer_[564] = pixel_pointer_[565] = pixel_pointer_[566] = pixel_pointer_[567] = 0;
} else {
if(line_mode == GraphicsMode::HighRes && base_stream_[39]&0x80)
pixel_pointer_[567] = graphics_carry_;
else
pixel_pointer_[567] = 0;
}
}
crt_.output_data(568, 568);
pixel_pointer_ = nullptr;
}
}
} else {
if(column_ < 40 && ending_column >= 40) {
crt_.output_blank(568);
}
}
/*
The left border, sync, right border pattern doesn't depend on whether
there were pixels this row and is output as soon as it is known.
*/
if(column_ < first_sync_column && ending_column >= first_sync_column) {
crt_.output_blank(first_sync_column*14 - 568);
}
if(column_ < (first_sync_column + sync_length) && ending_column >= (first_sync_column + sync_length)) {
crt_.output_sync(sync_length*14);
}
int second_blank_start;
if(!is_text_mode(graphics_mode(row_+1))) {
const int colour_burst_start = std::max(first_sync_column + sync_length + 1, column_);
const int colour_burst_end = std::min(first_sync_column + sync_length + 4, ending_column);
if(colour_burst_end > colour_burst_start) {
crt_.output_colour_burst((colour_burst_end - colour_burst_start) * 14, 0);
}
second_blank_start = std::max(first_sync_column + sync_length + 3, column_);
} else {
second_blank_start = std::max(first_sync_column + sync_length, column_);
}
if(ending_column > second_blank_start) {
crt_.output_blank((ending_column - second_blank_start) * 14);
}
}
int_cycles -= cycles_this_line;
column_ = (column_ + cycles_this_line) % 65;
if(!column_) {
row_ = (row_ + 1) % 262;
flash_ = (flash_ + 1) % (2 * flash_length);
if(!alternative_character_set_) {
character_zones[1].xor_mask = flash_mask();
}
// Add an extra half a colour cycle of blank; this isn't counted in the run_for
// count explicitly but is promised. If this is a vertical sync line, output sync
// instead of blank, taking that to be the default level.
if(is_vertical_sync_line) {
crt_.output_sync(2);
} else {
crt_.output_blank(2);
}
}
}
}
GraphicsMode graphics_mode(int row) {
if(
text_ ||
(mixed_ && row >= 160 && row < 192)
) return columns_80_ ? GraphicsMode::DoubleText : GraphicsMode::Text;
if(high_resolution_) {
return (annunciator_3_ && columns_80_) ? GraphicsMode::DoubleHighRes : GraphicsMode::HighRes;
} else {
if(columns_80_) return GraphicsMode::DoubleLowRes;
if(annunciator_3_) return GraphicsMode::FatLowRes;
return GraphicsMode::LowRes;
}
}
int video_page() {
return (store_80_ || !page2_) ? 0 : 1;
}
uint16_t get_row_address(int row) {
const int character_row = row >> 3;
const int pixel_row = row & 7;
const uint16_t row_address = static_cast<uint16_t>((character_row >> 3) * 40 + ((character_row&7) << 7));
const GraphicsMode pixel_mode = graphics_mode(row);
return ((pixel_mode == GraphicsMode::HighRes) || (pixel_mode == GraphicsMode::DoubleHighRes)) ?
static_cast<uint16_t>(((video_page()+1) * 0x2000) + row_address + ((pixel_row&7) << 10)) :
static_cast<uint16_t>(((video_page()+1) * 0x400) + row_address);
}
BusHandler &bus_handler_;
};
}
}
}
#endif /* Video_hpp */

View File

@@ -0,0 +1,97 @@
//
// Audio.cpp
// Clock Signal
//
// Created by Thomas Harte on 31/05/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#include "Audio.hpp"
using namespace Apple::Macintosh;
namespace {
// The sample_length is coupled with the clock rate selected within the Macintosh proper;
// as per the header-declaration a divide-by-two clock is expected to arrive here.
const std::size_t sample_length = 352 / 2;
}
Audio::Audio(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {}
// MARK: - Inputs
void Audio::post_sample(uint8_t sample) {
// Store sample directly indexed by current write pointer; this ensures that collected samples
// directly map to volume and enabled/disabled states.
sample_queue_.buffer[sample_queue_.write_pointer] = sample;
sample_queue_.write_pointer = (sample_queue_.write_pointer + 1) % sample_queue_.buffer.size();
}
void Audio::set_volume(int volume) {
// Do nothing if the volume hasn't changed.
if(posted_volume_ == volume) return;
posted_volume_ = volume;
// Post the volume change as a deferred event.
task_queue_.defer([=] () {
volume_ = volume;
set_volume_multiplier();
});
}
void Audio::set_enabled(bool on) {
// Do nothing if the mask hasn't changed.
if(posted_enable_mask_ == int(on)) return;
posted_enable_mask_ = int(on);
// Post the enabled mask change as a deferred event.
task_queue_.defer([=] () {
enabled_mask_ = int(on);
set_volume_multiplier();
});
}
// MARK: - Output generation
bool Audio::is_zero_level() {
return !volume_ || !enabled_mask_;
}
void Audio::set_sample_volume_range(std::int16_t range) {
// Some underflow here doesn't really matter.
output_volume_ = range / (7 * 255);
set_volume_multiplier();
}
void Audio::set_volume_multiplier() {
volume_multiplier_ = int16_t(output_volume_ * volume_ * enabled_mask_);
}
void Audio::get_samples(std::size_t number_of_samples, int16_t *target) {
// TODO: the implementation below acts as if the hardware uses pulse-amplitude modulation;
// in fact it uses pulse-width modulation. But the scale for pulses isn't specified, so
// that's something to return to.
while(number_of_samples) {
// Determine how many output samples will be at the same level.
const auto cycles_left_in_sample = std::min(number_of_samples, sample_length - subcycle_offset_);
// Determine the output level, and output that many samples.
// (Hoping that the copiler substitutes an effective memset16-type operation here).
const int16_t output_level = volume_multiplier_ * (int16_t(sample_queue_.buffer[sample_queue_.read_pointer]) - 128);
for(size_t c = 0; c < cycles_left_in_sample; ++c) {
target[c] = output_level;
}
target += cycles_left_in_sample;
// Advance the sample pointer.
subcycle_offset_ += cycles_left_in_sample;
sample_queue_.read_pointer = (sample_queue_.read_pointer + (subcycle_offset_ / sample_length)) % sample_queue_.buffer.size();
subcycle_offset_ %= sample_length;
// Decreate the number of samples left to write.
number_of_samples -= cycles_left_in_sample;
}
}

View File

@@ -0,0 +1,88 @@
//
// Audio.hpp
// Clock Signal
//
// Created by Thomas Harte on 31/05/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef Audio_hpp
#define Audio_hpp
#include "../../../Concurrency/AsyncTaskQueue.hpp"
#include "../../../ClockReceiver/ClockReceiver.hpp"
#include "../../../Outputs/Speaker/Implementation/SampleSource.hpp"
#include <array>
#include <atomic>
namespace Apple {
namespace Macintosh {
/*!
Implements the Macintosh's audio output hardware.
Designed to be clocked at half the rate of the real hardware — i.e.
a shade less than 4Mhz.
*/
class Audio: public ::Outputs::Speaker::SampleSource {
public:
Audio(Concurrency::DeferringAsyncTaskQueue &task_queue);
/*!
Macintosh audio is (partly) sourced by the same scanning
hardware as the video; each line it collects an additional
word of memory, half of which is used for audio output.
Use this method to add a newly-collected sample to the queue.
*/
void post_sample(uint8_t sample);
/*!
Macintosh audio also separately receives an output volume
level, in the range 0 to 7.
Use this method to set the current output volume.
*/
void set_volume(int volume);
/*!
A further factor in audio output is the on-off toggle.
*/
void set_enabled(bool on);
// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter.
void get_samples(std::size_t number_of_samples, int16_t *target);
bool is_zero_level();
void set_sample_volume_range(std::int16_t range);
private:
Concurrency::DeferringAsyncTaskQueue &task_queue_;
// A queue of fetched samples; read from by one thread,
// written to by another.
struct {
std::array<uint8_t, 740> buffer;
size_t read_pointer = 0, write_pointer = 0;
} sample_queue_;
// Emulator-thread stateful variables, to avoid work posting
// deferral updates if possible.
int posted_volume_ = 0;
int posted_enable_mask_ = 0;
// Stateful variables, modified from the audio generation
// thread only.
int volume_ = 0;
int enabled_mask_ = 0;
std::int16_t output_volume_ = 0;
std::int16_t volume_multiplier_ = 0;
std::size_t subcycle_offset_ = 0;
void set_volume_multiplier();
};
}
}
#endif /* Audio_hpp */

View File

@@ -0,0 +1,34 @@
//
// DeferredAudio.hpp
// Clock Signal
//
// Created by Thomas Harte on 01/06/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef DeferredAudio_h
#define DeferredAudio_h
#include "Audio.hpp"
#include "../../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
namespace Apple {
namespace Macintosh {
struct DeferredAudio {
Concurrency::DeferringAsyncTaskQueue queue;
Audio audio;
Outputs::Speaker::LowpassSpeaker<Audio> speaker;
HalfCycles time_since_update;
DeferredAudio() : audio(queue), speaker(audio) {}
void flush() {
speaker.run_for(queue, time_since_update.flush<Cycles>());
}
};
}
}
#endif /* DeferredAudio_h */

View File

@@ -0,0 +1,63 @@
//
// DriveSpeedAccumulator.cpp
// Clock Signal
//
// Created by Thomas Harte on 01/06/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#include "DriveSpeedAccumulator.hpp"
using namespace Apple::Macintosh;
void DriveSpeedAccumulator::post_sample(uint8_t sample) {
if(!number_of_drives_) return;
// An Euler-esque approximation is used here: just collect all
// the samples until there is a certain small quantity of them,
// then produce a new estimate of rotation speed and start the
// buffer afresh.
samples_[sample_pointer_] = sample;
++sample_pointer_;
if(sample_pointer_ == samples_.size()) {
sample_pointer_ = 0;
// The below fits for a function like `a + bc`; it encapsultes the following
// beliefs:
//
// (i) motor speed is proportional to voltage supplied;
// (ii) with pulse-width modulation it's therefore proportional to the duty cycle;
// (iii) the Mac pulse-width modulates whatever it reads from the disk speed buffer;
// (iv) ... subject to software pulse-width modulation of that pulse-width modulation.
//
// So, I believe current motor speed is proportional to a low-pass filtering of
// the speed buffer. Which I've implemented very coarsely via 'large' bucketed-averages,
// noting also that exact disk motor speed is always a little approximate.
// Sum all samples.
// TODO: if the above is the correct test, do it on sample receipt rather than
// bothering with an intermediate buffer.
int sum = 0;
for(auto s: samples_) {
sum += s;
}
// The formula below was derived from observing values the Mac wrote into its
// disk-speed buffer. Given that it runs a calibration loop before doing so,
// I cannot guarantee the accuracy of these numbers beyond being within the
// range that the computer would accept.
const float normalised_sum = float(sum) / float(samples_.size());
const float rotation_speed = (normalised_sum * 27.08f) - 259.0f;
for(int c = 0; c < number_of_drives_; ++c) {
drives_[c]->set_rotation_speed(rotation_speed);
}
// printf("RPM: %0.2f (%d sum)\n", rotation_speed, sum);
}
}
void DriveSpeedAccumulator::add_drive(Apple::Macintosh::DoubleDensityDrive *drive) {
drives_[number_of_drives_] = drive;
++number_of_drives_;
}

View File

@@ -0,0 +1,45 @@
//
// DriveSpeedAccumulator.hpp
// Clock Signal
//
// Created by Thomas Harte on 01/06/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef DriveSpeedAccumulator_hpp
#define DriveSpeedAccumulator_hpp
#include <array>
#include <cstddef>
#include <cstdint>
#include "../../../Components/DiskII/MacintoshDoubleDensityDrive.hpp"
namespace Apple {
namespace Macintosh {
class DriveSpeedAccumulator {
public:
/*!
Accepts fetched motor control values.
*/
void post_sample(uint8_t sample);
/*!
Adds a connected drive. Up to two of these
can be supplied. Only Macintosh DoubleDensityDrives
are supported.
*/
void add_drive(Apple::Macintosh::DoubleDensityDrive *drive);
private:
std::array<uint8_t, 20> samples_;
std::size_t sample_pointer_ = 0;
Apple::Macintosh::DoubleDensityDrive *drives_[2] = {nullptr, nullptr};
int number_of_drives_ = 0;
};
}
}
#endif /* DriveSpeedAccumulator_hpp */

View File

@@ -0,0 +1,294 @@
//
// Keyboard.h
// Clock Signal
//
// Created by Thomas Harte on 08/05/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef Apple_Macintosh_Keyboard_hpp
#define Apple_Macintosh_Keyboard_hpp
#include "../../KeyboardMachine.hpp"
#include <mutex>
#include <vector>
namespace Apple {
namespace Macintosh {
class Keyboard {
public:
void set_input(bool data) {
switch(mode_) {
case Mode::Waiting:
/*
"Only the computer can initiate communication over the keyboard lines. When the computer and keyboard
are turned on, the computer is in charge of the keyboard interface and the keyboard is passive. The
computer signals that it is ready to begin communication by pulling the Keyboard Data line low."
*/
if(!data) {
mode_ = Mode::AcceptingCommand;
phase_ = 0;
command_ = 0;
}
break;
case Mode::AcceptingCommand:
/* Note value, so that it can be latched upon a clock transition. */
data_input_ = data;
break;
case Mode::AwaitingEndOfCommand:
/*
The last bit of the command leaves the Keyboard Data line low; the computer then indicates that it is ready
to receive the keyboard's response by setting the Keyboard Data line high.
*/
if(data) {
mode_ = Mode::PerformingCommand;
phase_ = 0;
}
break;
default:
case Mode::SendingResponse:
/* This line isn't currently an input; do nothing. */
break;
}
}
bool get_clock() {
return clock_output_;
}
bool get_data() {
return !!(response_ & 0x80);
}
/*!
The keyboard expects ~10 µs-frequency ticks, i.e. a clock rate of just around 100 kHz.
*/
void run_for(HalfCycles cycle) {
switch(mode_) {
default:
case Mode::Waiting: return;
case Mode::AcceptingCommand: {
/*
"When the computer is sending data to the keyboard, the keyboard transmits eight cycles of 400 µS each (180 µS low,
220 µS high) on the Keyboard Clock line. On the falling edge of each keyboard clock cycle, the Macintosh Plus places
a data bit on the data line and holds it there for 400 µS. The keyboard reads the data bit 80 µS after the rising edge
of the Keyboard Clock signal."
*/
const auto offset = phase_ % 40;
clock_output_ = offset >= 18;
if(offset == 26) {
command_ = (command_ << 1) | (data_input_ ? 1 : 0);
}
++phase_;
if(phase_ == 8*40) {
mode_ = Mode::AwaitingEndOfCommand;
phase_ = 0;
clock_output_ = false;
}
} break;
case Mode::AwaitingEndOfCommand:
// Time out if the end-of-command seems not to be forthcoming.
// This is an elaboration on my part; a guess.
++phase_;
if(phase_ == 1000) {
clock_output_ = false;
mode_ = Mode::Waiting;
phase_ = 0;
}
return;
case Mode::PerformingCommand: {
response_ = perform_command(command_);
// Inquiry has a 0.25-second timeout; everything else is instant.
++phase_;
if(phase_ == 25000 || command_ != 0x10 || response_ != 0x7b) {
mode_ = Mode::SendingResponse;
phase_ = 0;
}
} break;
case Mode::SendingResponse: {
/*
"When sending data to the computer, the keyboard transmits eight cycles of 330 µS each (160 µS low, 170 µS high)
on the normally high Keyboard Clock line. It places a data bit on the data line 40 µS before the falling edge of each
clock cycle and maintains it for 330 µS. The VIA in the computer latches the data bit into its shift register on the
rising edge of the Keyboard Clock signal."
*/
const auto offset = phase_ % 33;
clock_output_ = offset >= 16;
if(offset == 29) {
response_ <<= 1;
}
++phase_;
if(phase_ == 8*33) {
clock_output_ = false;
mode_ = Mode::Waiting;
phase_ = 0;
}
} break;
}
}
void enqueue_key_state(uint16_t key, bool is_pressed) {
// Front insert; messages will be pop_back'd.
std::lock_guard<decltype(key_queue_mutex_)> lock(key_queue_mutex_);
// Keys on the keypad are preceded by a $79 keycode; in the internal naming scheme
// they are indicated by having bit 8 set. So add the $79 prefix if required.
if(key & 0x100) {
key_queue_.insert(key_queue_.begin(), 0x79);
}
key_queue_.insert(key_queue_.begin(), (is_pressed ? 0x00 : 0x80) | uint8_t(key));
}
private:
int perform_command(int command) {
switch(command) {
case 0x10: // Inquiry.
case 0x14: { // Instant.
std::lock_guard<decltype(key_queue_mutex_)> lock(key_queue_mutex_);
if(!key_queue_.empty()) {
const auto new_message = key_queue_.back();
key_queue_.pop_back();
return new_message;
}
} break;
case 0x16: // Model number.
return
0x01 | // b0: always 1
(1 << 1) | // keyboard model number
(1 << 4); // next device number
// (b7 not set => no next device)
case 0x36: // Test
return 0x7d; // 0x7d = ACK, 0x77 = not ACK.
}
return 0x7b; // No key transition.
}
enum class Mode {
Waiting,
AcceptingCommand,
AwaitingEndOfCommand,
SendingResponse,
PerformingCommand
} mode_ = Mode::Waiting;
int phase_ = 0;
int command_ = 0;
int response_ = 0;
bool data_input_ = false;
bool clock_output_ = false;
// TODO: improve this very, very simple implementation.
std::mutex key_queue_mutex_;
std::vector<uint8_t> key_queue_;
};
/*!
Provides a mapping from idiomatic PC keys to Macintosh keys.
*/
class KeyboardMapper: public KeyboardMachine::MappedMachine::KeyboardMapper {
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) override {
using Key = Inputs::Keyboard::Key;
switch(key) {
default: return KeyboardMachine::MappedMachine::KeyNotMapped;
/*
See p284 of the Apple Guide to the Macintosh Family Hardware
for documentation of the mapping below.
*/
case Key::BackTick: return 0x65;
case Key::k1: return 0x25;
case Key::k2: return 0x27;
case Key::k3: return 0x29;
case Key::k4: return 0x2b;
case Key::k5: return 0x2f;
case Key::k6: return 0x2d;
case Key::k7: return 0x35;
case Key::k8: return 0x39;
case Key::k9: return 0x33;
case Key::k0: return 0x3b;
case Key::Hyphen: return 0x37;
case Key::Equals: return 0x31;
case Key::BackSpace: return 0x67;
case Key::Tab: return 0x61;
case Key::Q: return 0x19;
case Key::W: return 0x1b;
case Key::E: return 0x1d;
case Key::R: return 0x1f;
case Key::T: return 0x23;
case Key::Y: return 0x21;
case Key::U: return 0x41;
case Key::I: return 0x45;
case Key::O: return 0x3f;
case Key::P: return 0x47;
case Key::OpenSquareBracket: return 0x43;
case Key::CloseSquareBracket: return 0x3d;
case Key::CapsLock: return 0x73;
case Key::A: return 0x01;
case Key::S: return 0x03;
case Key::D: return 0x05;
case Key::F: return 0x07;
case Key::G: return 0x0b;
case Key::H: return 0x09;
case Key::J: return 0x4d;
case Key::K: return 0x51;
case Key::L: return 0x4b;
case Key::Semicolon: return 0x53;
case Key::Quote: return 0x4f;
case Key::Enter: return 0x49;
case Key::LeftShift: return 0x71;
case Key::Z: return 0x0d;
case Key::X: return 0x0f;
case Key::C: return 0x11;
case Key::V: return 0x13;
case Key::B: return 0x17;
case Key::N: return 0x5b;
case Key::M: return 0x5d;
case Key::Comma: return 0x57;
case Key::FullStop: return 0x5f;
case Key::ForwardSlash: return 0x59;
case Key::RightShift: return 0x71;
case Key::Left: return 0x100 | 0x0d;
case Key::Right: return 0x100 | 0x05;
case Key::Up: return 0x100 | 0x1b;
case Key::Down: return 0x100 | 0x11;
case Key::LeftOption:
case Key::RightOption: return 0x75;
case Key::LeftMeta:
case Key::RightMeta: return 0x6f;
case Key::Space: return 0x63;
case Key::BackSlash: return 0x55;
/* TODO: the numeric keypad. */
}
}
};
}
}
#endif /* Apple_Macintosh_Keyboard_hpp */

View File

@@ -0,0 +1,653 @@
//
// Macintosh.cpp
// Clock Signal
//
// Created by Thomas Harte on 03/05/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#include "Macintosh.hpp"
#include <array>
#include "DeferredAudio.hpp"
#include "DriveSpeedAccumulator.hpp"
#include "Keyboard.hpp"
#include "RealTimeClock.hpp"
#include "Video.hpp"
#include "../../CRTMachine.hpp"
#include "../../KeyboardMachine.hpp"
#include "../../MediaTarget.hpp"
#include "../../MouseMachine.hpp"
#include "../../../Inputs/QuadratureMouse/QuadratureMouse.hpp"
#include "../../../Outputs/Log.hpp"
#include "../../../ClockReceiver/JustInTime.hpp"
//#define LOG_TRACE
#include "../../../Components/6522/6522.hpp"
#include "../../../Components/8530/z8530.hpp"
#include "../../../Components/DiskII/IWM.hpp"
#include "../../../Components/DiskII/MacintoshDoubleDensityDrive.hpp"
#include "../../../Processors/68000/68000.hpp"
#include "../../../Analyser/Static/Macintosh/Target.hpp"
#include "../../Utility/MemoryPacker.hpp"
#include "../../Utility/MemoryFuzzer.hpp"
namespace {
const int CLOCK_RATE = 7833600;
}
namespace Apple {
namespace Macintosh {
template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachine:
public Machine,
public CRTMachine::Machine,
public MediaTarget::Machine,
public MouseMachine::Machine,
public CPU::MC68000::BusHandler,
public KeyboardMachine::MappedMachine,
public Zilog::SCC::z8530::Delegate {
public:
using Target = Analyser::Static::Macintosh::Target;
ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
mc68000_(*this),
iwm_(CLOCK_RATE),
video_(ram_, audio_, drive_speed_accumulator_),
via_(via_port_handler_),
via_port_handler_(*this, clock_, keyboard_, video_, audio_, iwm_, mouse_),
drives_{
{CLOCK_RATE, model >= Analyser::Static::Macintosh::Target::Model::Mac512ke},
{CLOCK_RATE, model >= Analyser::Static::Macintosh::Target::Model::Mac512ke}
},
mouse_(1) {
// Select a ROM name and determine the proper ROM and RAM sizes
// based on the machine model.
using Model = Analyser::Static::Macintosh::Target::Model;
const std::string machine_name = "Macintosh";
uint32_t ram_size, rom_size;
std::vector<ROMMachine::ROM> rom_descriptions;
switch(model) {
default:
case Model::Mac128k:
ram_size = 128*1024;
rom_size = 64*1024;
rom_descriptions.emplace_back(machine_name, "the Macintosh 128k ROM", "mac128k.rom", 64*1024, 0x6d0c8a28);
break;
case Model::Mac512k:
ram_size = 512*1024;
rom_size = 64*1024;
rom_descriptions.emplace_back(machine_name, "the Macintosh 512k ROM", "mac512k.rom", 64*1024, 0xcf759e0d);
break;
case Model::Mac512ke:
case Model::MacPlus: {
ram_size = 512*1024;
rom_size = 128*1024;
const std::initializer_list<uint32_t> crc32s = { 0x4fa5b399, 0x7cacd18f, 0xb2102e8e };
rom_descriptions.emplace_back(machine_name, "the Macintosh Plus ROM", "macplus.rom", 128*1024, crc32s);
} break;
}
ram_mask_ = (ram_size >> 1) - 1;
rom_mask_ = (rom_size >> 1) - 1;
video_.set_ram_mask(ram_mask_);
// Grab a copy of the ROM and convert it into big-endian data.
const auto roms = rom_fetcher(rom_descriptions);
if(!roms[0]) {
throw ROMMachine::Error::MissingROMs;
}
roms[0]->resize(rom_size);
Memory::PackBigEndian16(*roms[0], rom_);
// Randomise memory contents.
Memory::Fuzz(ram_, sizeof(ram_) / sizeof(*ram_));
// Attach the drives to the IWM.
iwm_.iwm.set_drive(0, &drives_[0]);
iwm_.iwm.set_drive(1, &drives_[1]);
// If they are 400kb drives, also attach them to the drive-speed accumulator.
if(!drives_[0].is_800k()) drive_speed_accumulator_.add_drive(&drives_[0]);
if(!drives_[1].is_800k()) drive_speed_accumulator_.add_drive(&drives_[1]);
// Make sure interrupt changes from the SCC are observed.
scc_.set_delegate(this);
// The Mac runs at 7.8336mHz.
set_clock_rate(double(CLOCK_RATE));
audio_.speaker.set_input_rate(float(CLOCK_RATE) / 2.0f);
// Insert any supplied media.
insert_media(target.media);
}
~ConcreteMachine() {
audio_.queue.flush();
}
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override {
video_.set_scan_target(scan_target);
}
Outputs::Speaker::Speaker *get_speaker() override {
return &audio_.speaker;
}
void run_for(const Cycles cycles) override {
mc68000_.run_for(cycles);
}
using Microcycle = CPU::MC68000::Microcycle;
HalfCycles perform_bus_operation(const Microcycle &cycle, int is_supervisor) {
// TODO: pick a delay if this is a video-clashing memory fetch.
HalfCycles delay(0);
time_since_video_update_ += cycle.length;
iwm_.time_since_update += cycle.length;
// The VIA runs at one-tenth of the 68000's clock speed, in sync with the E clock.
// See: Guide to the Macintosh Hardware Family p149 (PDF p188). Some extra division
// may occur here in order to provide VSYNC at a proper moment.
// Possibly route vsync.
if(time_since_video_update_ < time_until_video_event_) {
via_clock_ += cycle.length;
via_.run_for(via_clock_.divide(HalfCycles(10)));
} else {
auto via_time_base = time_since_video_update_ - cycle.length;
auto via_cycles_outstanding = cycle.length;
while(time_until_video_event_ < time_since_video_update_) {
const auto via_cycles = time_until_video_event_ - via_time_base;
via_time_base = HalfCycles(0);
via_cycles_outstanding -= via_cycles;
via_clock_ += via_cycles;
via_.run_for(via_clock_.divide(HalfCycles(10)));
video_.run_for(time_until_video_event_);
time_since_video_update_ -= time_until_video_event_;
time_until_video_event_ = video_.get_next_sequence_point();
via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !video_.vsync());
}
via_clock_ += via_cycles_outstanding;
via_.run_for(via_clock_.divide(HalfCycles(10)));
}
// The keyboard also has a clock, albeit a very slow one — 100,000 cycles/second.
// Its clock and data lines are connected to the VIA.
keyboard_clock_ += cycle.length;
const auto keyboard_ticks = keyboard_clock_.divide(HalfCycles(CLOCK_RATE / 100000));
if(keyboard_ticks > HalfCycles(0)) {
keyboard_.run_for(keyboard_ticks);
via_.set_control_line_input(MOS::MOS6522::Port::B, MOS::MOS6522::Line::Two, keyboard_.get_data());
via_.set_control_line_input(MOS::MOS6522::Port::B, MOS::MOS6522::Line::One, keyboard_.get_clock());
}
// Feed mouse inputs within at most 1250 cycles of each other.
if(mouse_.has_steps()) {
time_since_mouse_update_ += cycle.length;
const auto mouse_ticks = time_since_mouse_update_.divide(HalfCycles(2500));
if(mouse_ticks > HalfCycles(0)) {
mouse_.prepare_step();
scc_.set_dcd(0, mouse_.get_channel(1) & 1);
scc_.set_dcd(1, mouse_.get_channel(0) & 1);
}
}
// TODO: SCC should be clocked at a divide-by-two, if and when it actually has
// anything connected.
// Consider updating the real-time clock.
real_time_clock_ += cycle.length;
auto ticks = real_time_clock_.divide_cycles(Cycles(CLOCK_RATE)).as_int();
while(ticks--) {
clock_.update();
// TODO: leave a delay between toggling the input rather than using this coupled hack.
via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::Two, true);
via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::Two, false);
}
// A null cycle leaves nothing else to do.
if(!(cycle.operation & (Microcycle::NewAddress | Microcycle::SameAddress))) return delay;
auto word_address = cycle.active_operation_word_address();
// Everything above E0 0000 is signalled as being on the peripheral bus.
mc68000_.set_is_peripheral_address(word_address >= 0x700000);
// All code below deals only with reads and writes — cycles in which a
// data select is active. So quit now if this is not the active part of
// a read or write.
if(!cycle.data_select_active()) return delay;
// Check whether this access maps into the IO area; if so then
// apply more complicated decoding logic.
if(word_address >= 0x400000) {
const int register_address = word_address >> 8;
switch(word_address & 0x78f000) {
case 0x70f000:
// VIA accesses are via address 0xefe1fe + register*512,
// which at word precision is 0x77f0ff + register*256.
if(cycle.operation & Microcycle::Read) {
cycle.value->halves.low = via_.get_register(register_address);
} else {
via_.set_register(register_address, cycle.value->halves.low);
}
break;
case 0x68f000:
// The IWM; this is a purely polled device, so can be run on demand.
iwm_.flush();
if(cycle.operation & Microcycle::Read) {
cycle.value->halves.low = iwm_.iwm.read(register_address);
} else {
iwm_.iwm.write(register_address, cycle.value->halves.low);
}
break;
case 0x780000:
// Phase read.
if(cycle.operation & Microcycle::Read) {
cycle.value->halves.low = phase_ & 7;
}
break;
case 0x480000: case 0x48f000:
case 0x580000: case 0x58f000:
// Any word access here adjusts phase.
if(cycle.operation & Microcycle::SelectWord) {
++phase_;
} else {
if(word_address < 0x500000) {
// A0 = 1 => reset; A0 = 0 => read.
if(*cycle.address & 1) {
scc_.reset();
} else {
const auto read = scc_.read(int(word_address));
if(cycle.operation & Microcycle::Read) {
cycle.value->halves.low = read;
}
}
} else {
if(*cycle.address & 1) {
if(cycle.operation & Microcycle::Read) {
scc_.write(int(word_address), 0xff);
} else {
scc_.write(int(word_address), cycle.value->halves.low);
}
}
}
}
break;
default:
if(cycle.operation & Microcycle::Read) {
LOG("Unrecognised read " << PADHEX(6) << (*cycle.address & 0xffffff));
cycle.value->halves.low = 0x00;
} else {
LOG("Unrecognised write %06x" << PADHEX(6) << (*cycle.address & 0xffffff));
}
break;
}
if(cycle.operation & Microcycle::SelectWord) cycle.value->halves.high = 0xff;
return delay;
}
// Having reached here, this is a RAM or ROM access.
// When ROM overlay is enabled, the ROM begins at both $000000 and $400000,
// and RAM is available at $600000.
//
// Otherwise RAM is mapped at $000000 and ROM from $400000.
uint16_t *memory_base;
if(
(!ROM_is_overlay_ && word_address < 0x200000) ||
(ROM_is_overlay_ && word_address >= 0x300000)
) {
memory_base = ram_;
word_address &= ram_mask_;
// This is coupled with the Macintosh implementation of video; the magic
// constant should probably be factored into the Video class.
// It embodies knowledge of the fact that video (and audio) will always
// be fetched from the final $d900 bytes (i.e. $6c80 words) of memory.
// (And that ram_mask_ = ram size - 1).
// if(word_address > ram_mask_ - 0x6c80)
update_video();
} else {
memory_base = rom_;
word_address &= rom_mask_;
// Writes to ROM have no effect, and it doesn't mirror above 0x60000.
if(!(cycle.operation & Microcycle::Read)) return delay;
if(word_address >= 0x300000) {
if(cycle.operation & Microcycle::SelectWord) {
cycle.value->full = 0xffff;
} else {
cycle.value->halves.low = 0xff;
}
return delay;
}
}
switch(cycle.operation & (Microcycle::SelectWord | Microcycle::SelectByte | Microcycle::Read | Microcycle::InterruptAcknowledge)) {
default:
break;
// Catches the deliberation set of operation to 0 above.
case 0: break;
case Microcycle::InterruptAcknowledge | Microcycle::SelectByte:
// The Macintosh uses autovectored interrupts.
mc68000_.set_is_peripheral_address(true);
break;
case Microcycle::SelectWord | Microcycle::Read:
cycle.value->full = memory_base[word_address];
break;
case Microcycle::SelectByte | Microcycle::Read:
cycle.value->halves.low = uint8_t(memory_base[word_address] >> cycle.byte_shift());
break;
case Microcycle::SelectWord:
memory_base[word_address] = cycle.value->full;
break;
case Microcycle::SelectByte:
memory_base[word_address] = uint16_t(
(cycle.value->halves.low << cycle.byte_shift()) |
(memory_base[word_address] & cycle.untouched_byte_mask())
);
break;
}
/*
Normal memory map:
000000: RAM
400000: ROM
9FFFF8+: SCC read operations
BFFFF8+: SCC write operations
DFE1FF+: IWM
EFE1FE+: VIA
*/
return delay;
}
void flush() {
// Flush the video before the audio queue; in a Mac the
// video is responsible for providing part of the
// audio signal, so the two aren't as distinct as in
// most machines.
update_video();
// As above: flush audio after video.
via_.flush();
audio_.queue.perform();
// Experimental?
iwm_.flush();
}
void set_rom_is_overlay(bool rom_is_overlay) {
ROM_is_overlay_ = rom_is_overlay;
}
bool video_is_outputting() {
return video_.is_outputting(time_since_video_update_);
}
void set_use_alternate_buffers(bool use_alternate_screen_buffer, bool use_alternate_audio_buffer) {
update_video();
video_.set_use_alternate_buffers(use_alternate_screen_buffer, use_alternate_audio_buffer);
}
bool insert_media(const Analyser::Static::Media &media) override {
if(media.disks.empty())
return false;
// TODO: shouldn't allow disks to be replaced like this, as the Mac
// uses software eject. Will need to expand messaging ability of
// insert_media.
if(drives_[0].has_disk())
drives_[1].set_disk(media.disks[0]);
else
drives_[0].set_disk(media.disks[0]);
return true;
}
// MARK: Keyboard input.
KeyboardMapper *get_keyboard_mapper() override {
return &keyboard_mapper_;
}
void set_key_state(uint16_t key, bool is_pressed) override {
keyboard_.enqueue_key_state(key, is_pressed);
}
// TODO: clear all keys.
// MARK: Interrupt updates.
void did_change_interrupt_status(Zilog::SCC::z8530 *sender, bool new_status) override {
update_interrupt_input();
}
void update_interrupt_input() {
// Update interrupt input.
// TODO: does this really cascade like this?
if(scc_.get_interrupt_line()) {
mc68000_.set_interrupt_level(2);
} else if(via_.get_interrupt_line()) {
mc68000_.set_interrupt_level(1);
} else {
mc68000_.set_interrupt_level(0);
}
}
private:
void update_video() {
video_.run_for(time_since_video_update_.flush<HalfCycles>());
time_until_video_event_ = video_.get_next_sequence_point();
}
Inputs::Mouse &get_mouse() override {
return mouse_;
}
struct IWM {
IWM(int clock_rate) : iwm(clock_rate) {}
HalfCycles time_since_update;
Apple::IWM iwm;
void flush() {
iwm.run_for(time_since_update.flush<Cycles>());
}
};
class VIAPortHandler: public MOS::MOS6522::PortHandler {
public:
VIAPortHandler(ConcreteMachine &machine, RealTimeClock &clock, Keyboard &keyboard, Video &video, DeferredAudio &audio, IWM &iwm, Inputs::QuadratureMouse &mouse) :
machine_(machine), clock_(clock), keyboard_(keyboard), video_(video), audio_(audio), iwm_(iwm), mouse_(mouse) {}
using Port = MOS::MOS6522::Port;
using Line = MOS::MOS6522::Line;
void set_port_output(Port port, uint8_t value, uint8_t direction_mask) {
/*
Peripheral lines: keyboard data, interrupt configuration.
(See p176 [/215])
*/
switch(port) {
case Port::A:
/*
Port A:
b7: [input] SCC wait/request (/W/REQA and /W/REQB wired together for a logical OR)
b6: 0 = alternate screen buffer, 1 = main screen buffer
b5: floppy disk SEL state control (upper/lower head "among other things")
b4: 1 = use ROM overlay memory map, 0 = use ordinary memory map
b3: 0 = use alternate sound buffer, 1 = use ordinary sound buffer
b2b0: audio output volume
*/
iwm_.flush();
iwm_.iwm.set_select(!!(value & 0x20));
machine_.set_use_alternate_buffers(!(value & 0x40), !(value&0x08));
machine_.set_rom_is_overlay(!!(value & 0x10));
audio_.flush();
audio_.audio.set_volume(value & 7);
break;
case Port::B:
/*
Port B:
b7: 0 = sound enabled, 1 = sound disabled
b6: [input] 0 = video beam in visible portion of line, 1 = outside
b5: [input] mouse y2
b4: [input] mouse x2
b3: [input] 0 = mouse button down, 1 = up
b2: 0 = real-time clock enabled, 1 = disabled
b1: clock's data-clock line
b0: clock's serial data line
*/
if(value & 0x4) clock_.abort();
else clock_.set_input(!!(value & 0x2), !!(value & 0x1));
audio_.flush();
audio_.audio.set_enabled(!(value & 0x80));
break;
}
}
uint8_t get_port_input(Port port) {
switch(port) {
case Port::A:
// printf("6522 r A\n");
return 0x00; // TODO: b7 = SCC wait/request
case Port::B:
return uint8_t(
((mouse_.get_button_mask() & 1) ? 0x00 : 0x08) |
((mouse_.get_channel(0) & 2) << 3) |
((mouse_.get_channel(1) & 2) << 4) |
(clock_.get_data() ? 0x02 : 0x00) |
(machine_.video_is_outputting() ? 0x00 : 0x40)
);
}
// Should be unreachable.
return 0xff;
}
void set_control_line_output(Port port, Line line, bool value) {
/*
Keyboard wiring (I believe):
CB2 = data (input/output)
CB1 = clock (input)
CA2 is used for receiving RTC interrupts.
CA1 is used for receiving vsync.
*/
if(port == Port::B && line == Line::Two) {
keyboard_.set_input(value);
}
else LOG("Unhandled control line output: " << (port ? 'B' : 'A') << int(line));
}
void run_for(HalfCycles duration) {
// The 6522 enjoys a divide-by-ten, so multiply back up here to make the
// divided-by-two clock the audio works on.
audio_.time_since_update += HalfCycles(duration.as_int() * 5);
}
void flush() {
audio_.flush();
}
void set_interrupt_status(bool status) {
machine_.update_interrupt_input();
}
private:
ConcreteMachine &machine_;
RealTimeClock &clock_;
Keyboard &keyboard_;
Video &video_;
DeferredAudio &audio_;
IWM &iwm_;
Inputs::QuadratureMouse &mouse_;
};
CPU::MC68000::Processor<ConcreteMachine, true> mc68000_;
DriveSpeedAccumulator drive_speed_accumulator_;
IWM iwm_;
DeferredAudio audio_;
Video video_;
RealTimeClock clock_;
Keyboard keyboard_;
MOS::MOS6522::MOS6522<VIAPortHandler> via_;
VIAPortHandler via_port_handler_;
Zilog::SCC::z8530 scc_;
HalfCycles via_clock_;
HalfCycles real_time_clock_;
HalfCycles keyboard_clock_;
HalfCycles time_since_video_update_;
HalfCycles time_until_video_event_;
HalfCycles time_since_mouse_update_;
bool ROM_is_overlay_ = true;
int phase_ = 1;
DoubleDensityDrive drives_[2];
Inputs::QuadratureMouse mouse_;
Apple::Macintosh::KeyboardMapper keyboard_mapper_;
uint32_t ram_mask_ = 0;
uint32_t rom_mask_ = 0;
uint16_t rom_[64*1024];
uint16_t ram_[256*1024];
};
}
}
using namespace Apple::Macintosh;
Machine *Machine::Macintosh(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
auto *const mac_target = dynamic_cast<const Analyser::Static::Macintosh::Target *>(target);
using Model = Analyser::Static::Macintosh::Target::Model;
switch(mac_target->model) {
default:
case Model::Mac128k: return new ConcreteMachine<Model::Mac128k>(*mac_target, rom_fetcher);
case Model::Mac512k: return new ConcreteMachine<Model::Mac512k>(*mac_target, rom_fetcher);
case Model::Mac512ke: return new ConcreteMachine<Model::Mac512ke>(*mac_target, rom_fetcher);
case Model::MacPlus: return new ConcreteMachine<Model::MacPlus>(*mac_target, rom_fetcher);
}
}
Machine::~Machine() {}

View File

@@ -0,0 +1,30 @@
//
// Macintosh.hpp
// Clock Signal
//
// Created by Thomas Harte on 03/05/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef Macintosh_hpp
#define Macintosh_hpp
#include "../../../Analyser/Static/StaticAnalyser.hpp"
#include "../../ROMMachine.hpp"
namespace Apple {
namespace Macintosh {
class Machine {
public:
virtual ~Machine();
/// Creates and returns a Macintosh.
static Machine *Macintosh(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher);
};
}
}
#endif /* Macintosh_hpp */

View File

@@ -0,0 +1,166 @@
//
// RealTimeClock.hpp
// Clock Signal
//
// Created by Thomas Harte on 07/05/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef RealTimeClock_hpp
#define RealTimeClock_hpp
#include "../../Utility/MemoryFuzzer.hpp"
namespace Apple {
namespace Macintosh {
/*!
Models the storage component of Apple's real-time clock.
Since tracking of time is pushed to this class, it is assumed
that whomever is translating real time into emulated time
will notify the VIA of a potential interrupt.
*/
class RealTimeClock {
public:
RealTimeClock() {
// TODO: this should persist, if possible, rather than
// being randomly initialised.
Memory::Fuzz(data_, sizeof(data_));
}
/*!
Advances the clock by 1 second.
The caller should also notify the VIA.
*/
void update() {
for(int c = 0; c < 4; ++c) {
++seconds_[c];
if(seconds_[c]) break;
}
}
/*!
Sets the current clock and data inputs to the clock.
*/
void set_input(bool clock, bool data) {
/*
Documented commands:
z0000001 Seconds register 0 (lowest order byte)
z0000101 Seconds register 1
z0001001 Seconds register 2
z0001101 Seconds register 3
00110001 Test register (write only)
00110101 Write-protect register (write only)
z010aa01 RAM addresses 0x10 - 0x13
z1aaaa01 RAM addresses 0x00 0x0f
z = 1 => a read; z = 0 => a write.
The top bit of the write-protect register enables (0) or disables (1)
writes to other locations.
All the documentation says about the test register is to set the top
two bits to 0 for normal operation. Abnormal operation is undefined.
The data line is valid when the clock transitions to level 0.
*/
if(clock && !previous_clock_) {
// Shift into the command_ register, no matter what.
command_ = uint16_t((command_ << 1) | (data ? 1 : 0));
result_ <<= 1;
// Increment phase.
++phase_;
// When phase hits 8, inspect the command.
// If it's a read, prepare a result.
if(phase_ == 8) {
if(command_ & 0x80) {
// A read.
const auto address = (command_ >> 2) & 0x1f;
// Begin pessimistically.
result_ = 0xff;
if(address < 4) {
result_ = seconds_[address];
} else if(address >= 0x10) {
result_ = data_[address & 0xf];
} else if(address >= 0x8 && address <= 0xb) {
result_ = data_[0x10 + (address & 0x3)];
}
}
}
// If phase hits 16 and this was a read command,
// just stop. If it was a write command, do the
// actual write.
if(phase_ == 16) {
if(!(command_ & 0x8000)) {
// A write.
const auto address = (command_ >> 10) & 0x1f;
const uint8_t value = uint8_t(command_ & 0xff);
// First test: is this to the write-protect register?
if(address == 0xd) {
write_protect_ = value;
}
// No other writing is permitted if the write protect
// register won't allow it.
if(!(write_protect_ & 0x80)) {
if(address < 4) {
seconds_[address] = value;
} else if(address >= 0x10) {
data_[address & 0xf] = value;
} else if(address >= 0x8 && address <= 0xb) {
data_[0x10 + (address & 0x3)] = value;
}
}
}
// A phase of 16 always ends the command, so reset here.
abort();
}
}
previous_clock_ = clock;
}
/*!
Reads the current data output level from the clock.
*/
bool get_data() {
return !!(result_ & 0x80);
}
/*!
Announces that a serial command has been aborted.
*/
void abort() {
result_ = 0;
phase_ = 0;
command_ = 0;
}
private:
uint8_t data_[0x14];
uint8_t seconds_[4];
uint8_t write_protect_;
int phase_ = 0;
uint16_t command_;
uint8_t result_ = 0;
bool previous_clock_ = false;
};
}
}
#endif /* RealTimeClock_hpp */

View File

@@ -0,0 +1,201 @@
//
// Video.cpp
// Clock Signal
//
// Created by Thomas Harte on 03/05/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#include "Video.hpp"
#include <algorithm>
using namespace Apple::Macintosh;
namespace {
const HalfCycles line_length(704);
const int number_of_lines = 370;
const HalfCycles frame_length(line_length * HalfCycles(number_of_lines));
const int sync_start = 36;
const int sync_end = 38;
}
// Re: CRT timings, see the Apple Guide to the Macintosh Hardware Family,
// bottom of page 400:
//
// "For each scan line, 512 pixels are drawn on the screen ...
// The horizontal blanking interval takes the time of an additional 192 pixels"
//
// And, at the top of 401:
//
// "The visible portion of a full-screen display consists of 342 horizontal scan lines...
// During the vertical blanking interval, the turned-off beam ... traces out an additional 28 scan lines,"
//
Video::Video(uint16_t *ram, DeferredAudio &audio, DriveSpeedAccumulator &drive_speed_accumulator) :
audio_(audio),
drive_speed_accumulator_(drive_speed_accumulator),
crt_(704, 1, 370, Outputs::Display::ColourSpace::YIQ, 1, 1, 6, false, Outputs::Display::InputDataType::Luminance1),
ram_(ram) {
crt_.set_display_type(Outputs::Display::DisplayType::RGB);
crt_.set_visible_area(Outputs::Display::Rect(0.08f, -0.025f, 0.82f, 0.82f));
crt_.set_aspect_ratio(1.73f); // The Mac uses a non-standard scanning area.
}
void Video::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
crt_.set_scan_target(scan_target);
}
void Video::run_for(HalfCycles duration) {
// Determine the current video and audio bases. These values don't appear to be latched, they apply immediately.
const size_t video_base = (use_alternate_screen_buffer_ ? (0xffff2700 >> 1) : (0xffffa700 >> 1)) & ram_mask_;
const size_t audio_base = (use_alternate_audio_buffer_ ? (0xffffa100 >> 1) : (0xfffffd00 >> 1)) & ram_mask_;
// The number of HalfCycles is literally the number of pixel clocks to move through,
// since pixel output occurs at twice the processor clock. So divide by 16 to get
// the number of fetches.
while(duration > HalfCycles(0)) {
const auto pixel_start = frame_position_ % line_length;
const int line = (frame_position_ / line_length).as_int();
const auto cycles_left_in_line = std::min(line_length - pixel_start, duration);
// Line timing, entirely invented as I can find exactly zero words of documentation:
//
// First 342 lines:
//
// First 32 words = pixels;
// next 5 words = right border;
// next 2 words = sync level;
// final 5 words = left border.
//
// Then 12 lines of border, 3 of sync, 11 more of border.
const int first_word = pixel_start.as_int() >> 4;
const int final_word = (pixel_start + cycles_left_in_line).as_int() >> 4;
if(first_word != final_word) {
if(line < 342) {
// If there are any pixels left to output, do so.
if(first_word < 32) {
const int final_pixel_word = std::min(final_word, 32);
if(!first_word) {
pixel_buffer_ = crt_.begin_data(512);
}
if(pixel_buffer_) {
for(int c = first_word; c < final_pixel_word; ++c) {
uint16_t pixels = ram_[video_base + video_address_] ^ 0xffff;
++video_address_;
pixel_buffer_[15] = pixels & 0x01;
pixel_buffer_[14] = pixels & 0x02;
pixel_buffer_[13] = pixels & 0x04;
pixel_buffer_[12] = pixels & 0x08;
pixel_buffer_[11] = pixels & 0x10;
pixel_buffer_[10] = pixels & 0x20;
pixel_buffer_[9] = pixels & 0x40;
pixel_buffer_[8] = pixels & 0x80;
pixels >>= 8;
pixel_buffer_[7] = pixels & 0x01;
pixel_buffer_[6] = pixels & 0x02;
pixel_buffer_[5] = pixels & 0x04;
pixel_buffer_[4] = pixels & 0x08;
pixel_buffer_[3] = pixels & 0x10;
pixel_buffer_[2] = pixels & 0x20;
pixel_buffer_[1] = pixels & 0x40;
pixel_buffer_[0] = pixels & 0x80;
pixel_buffer_ += 16;
}
}
if(final_pixel_word == 32) {
crt_.output_data(512);
}
}
if(first_word < sync_start && final_word >= sync_start) crt_.output_blank((sync_start - 32) * 16);
if(first_word < sync_end && final_word >= sync_end) crt_.output_sync((sync_end - sync_start) * 16);
if(final_word == 44) crt_.output_blank((44 - sync_end) * 16);
} else if(final_word == 44) {
if(line >= 353 && line < 356) {
/* Output a sync line. */
crt_.output_sync(sync_start * 16);
crt_.output_blank((sync_end - sync_start) * 16);
crt_.output_sync((44 - sync_end) * 16);
} else {
/* Output a blank line. */
crt_.output_blank(sync_start * 16);
crt_.output_sync((sync_end - sync_start) * 16);
crt_.output_blank((44 - sync_end) * 16);
}
}
// Audio and disk fetches occur "just before video data".
if(final_word == 44) {
const uint16_t audio_word = ram_[audio_address_ + audio_base];
++audio_address_;
audio_.audio.post_sample(audio_word >> 8);
drive_speed_accumulator_.post_sample(audio_word & 0xff);
}
}
duration -= cycles_left_in_line;
frame_position_ = frame_position_ + cycles_left_in_line;
if(frame_position_ == frame_length) {
frame_position_ = HalfCycles(0);
/*
Video: $1A700 and the alternate buffer starts at $12700; for a 512K Macintosh, add $60000 to these numbers.
*/
video_address_ = 0;
/*
"The main sound buffer is at $1FD00 in a 128K Macintosh, and the alternate buffer is at $1A100;
for a 512K Macintosh, add $60000 to these values."
*/
audio_address_ = 0;
}
}
}
bool Video::vsync() {
const int line = (frame_position_ / line_length).as_int();
return line >= 353 && line < 356;
}
HalfCycles Video::get_next_sequence_point() {
const int line = (frame_position_ / line_length).as_int();
if(line >= 353 && line < 356) {
// Currently in vsync, so get time until start of line 357,
// when vsync will end.
return HalfCycles(356) * line_length - frame_position_;
} else {
// Not currently in vsync, so get time until start of line 353.
const auto start_of_vsync = HalfCycles(353) * line_length;
if(frame_position_ < start_of_vsync)
return start_of_vsync - frame_position_;
else
return start_of_vsync + HalfCycles(number_of_lines) * line_length - frame_position_;
}
}
bool Video::is_outputting(HalfCycles offset) {
const auto offset_position = frame_position_ + offset % frame_length;
const int column = (offset_position % line_length).as_int() >> 4;
const int line = (offset_position / line_length).as_int();
return line < 342 && column < 32;
}
void Video::set_use_alternate_buffers(bool use_alternate_screen_buffer, bool use_alternate_audio_buffer) {
use_alternate_screen_buffer_ = use_alternate_screen_buffer;
use_alternate_audio_buffer_ = use_alternate_audio_buffer;
}
void Video::set_ram_mask(uint32_t mask) {
ram_mask_ = mask;
}

View File

@@ -0,0 +1,94 @@
//
// Video.hpp
// Clock Signal
//
// Created by Thomas Harte on 03/05/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef Video_hpp
#define Video_hpp
#include "../../../Outputs/CRT/CRT.hpp"
#include "../../../ClockReceiver/ClockReceiver.hpp"
#include "DeferredAudio.hpp"
#include "DriveSpeedAccumulator.hpp"
namespace Apple {
namespace Macintosh {
/*!
Models the 68000-era Macintosh video hardware, producing a 512x348 pixel image,
within a total scanning area of 370 lines, at 352 cycles per line.
This class also collects audio and 400kb drive-speed data, forwarding those values.
*/
class Video {
public:
/*!
Constructs an instance of @c Video sourcing its pixel data from @c ram and
providing audio and drive-speed bytes to @c audio and @c drive_speed_accumulator.
*/
Video(uint16_t *ram, DeferredAudio &audio, DriveSpeedAccumulator &drive_speed_accumulator);
/*!
Sets the target device for video data.
*/
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
/*!
Produces the next @c duration period of pixels.
*/
void run_for(HalfCycles duration);
/*!
Sets whether the alternate screen and/or audio buffers should be used to source data.
*/
void set_use_alternate_buffers(bool use_alternate_screen_buffer, bool use_alternate_audio_buffer);
/*!
Provides a mask indicating which parts of the generated video and audio/drive addresses are
actually decoded, accessing *word-sized memory*; e.g. for a 128kb Macintosh this should be (1 << 16) - 1 = 0xffff.
*/
void set_ram_mask(uint32_t);
/*!
@returns @c true if the video is currently outputting a vertical sync, @c false otherwise.
*/
bool vsync();
/*
@returns @c true if in @c offset half cycles from now, the video will be outputting pixels;
@c false otherwise.
*/
bool is_outputting(HalfCycles offset = HalfCycles(0));
/*!
@returns the amount of time until there is next a transition on the
vsync signal.
*/
HalfCycles get_next_sequence_point();
private:
DeferredAudio &audio_;
DriveSpeedAccumulator &drive_speed_accumulator_;
Outputs::CRT::CRT crt_;
uint16_t *ram_ = nullptr;
uint32_t ram_mask_ = 0;
HalfCycles frame_position_;
size_t video_address_ = 0;
size_t audio_address_ = 0;
uint8_t *pixel_buffer_ = nullptr;
bool use_alternate_screen_buffer_ = false;
bool use_alternate_audio_buffer_ = false;
};
}
}
#endif /* Video_hpp */

View File

@@ -1,722 +0,0 @@
//
// AppleII.cpp
// Clock Signal
//
// Created by Thomas Harte on 14/04/2018.
// Copyright 2018 Thomas Harte. All rights reserved.
//
#include "AppleII.hpp"
#include "../../Activity/Source.hpp"
#include "../MediaTarget.hpp"
#include "../CRTMachine.hpp"
#include "../JoystickMachine.hpp"
#include "../KeyboardMachine.hpp"
#include "../Utility/MemoryFuzzer.hpp"
#include "../Utility/StringSerialiser.hpp"
#include "../../Processors/6502/6502.hpp"
#include "../../Components/AudioToggle/AudioToggle.hpp"
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#include "Card.hpp"
#include "DiskIICard.hpp"
#include "Video.hpp"
#include "../../Analyser/Static/AppleII/Target.hpp"
#include "../../ClockReceiver/ForceInline.hpp"
#include "../../Configurable/Configurable.hpp"
#include "../../Storage/Disk/Track/TrackSerialiser.hpp"
#include "../../Storage/Disk/Encodings/AppleGCR/SegmentParser.hpp"
#include <algorithm>
#include <array>
#include <memory>
std::vector<std::unique_ptr<Configurable::Option>> AppleII::get_options() {
std::vector<std::unique_ptr<Configurable::Option>> options;
options.emplace_back(new Configurable::BooleanOption("Accelerate DOS 3.3", "quickload"));
return options;
}
namespace {
class ConcreteMachine:
public CRTMachine::Machine,
public MediaTarget::Machine,
public KeyboardMachine::Machine,
public Configurable::Device,
public CPU::MOS6502::BusHandler,
public Inputs::Keyboard,
public AppleII::Machine,
public Activity::Source,
public JoystickMachine::Machine,
public AppleII::Card::Delegate {
private:
struct VideoBusHandler : public AppleII::Video::BusHandler {
public:
VideoBusHandler(uint8_t *ram) : ram_(ram) {}
uint8_t perform_read(uint16_t address) {
return ram_[address];
}
private:
uint8_t *ram_;
};
CPU::MOS6502::Processor<ConcreteMachine, false> m6502_;
VideoBusHandler video_bus_handler_;
std::unique_ptr<AppleII::Video::Video<VideoBusHandler>> video_;
int cycles_into_current_line_ = 0;
Cycles cycles_since_video_update_;
void update_video() {
video_->run_for(cycles_since_video_update_.flush());
}
static const int audio_divider = 8;
void update_audio() {
speaker_.run_for(audio_queue_, cycles_since_audio_update_.divide(Cycles(audio_divider)));
}
void update_just_in_time_cards() {
for(const auto &card : just_in_time_cards_) {
card->run_for(cycles_since_card_update_, stretched_cycles_since_card_update_);
}
cycles_since_card_update_ = 0;
stretched_cycles_since_card_update_ = 0;
}
uint8_t ram_[65536], aux_ram_[65536];
std::vector<uint8_t> rom_;
std::vector<uint8_t> character_rom_;
uint8_t keyboard_input_ = 0x00;
Concurrency::DeferringAsyncTaskQueue audio_queue_;
Audio::Toggle audio_toggle_;
Outputs::Speaker::LowpassSpeaker<Audio::Toggle> speaker_;
Cycles cycles_since_audio_update_;
// MARK: - Cards
std::array<std::unique_ptr<AppleII::Card>, 7> cards_;
Cycles cycles_since_card_update_;
std::vector<AppleII::Card *> every_cycle_cards_;
std::vector<AppleII::Card *> just_in_time_cards_;
int stretched_cycles_since_card_update_ = 0;
void install_card(std::size_t slot, AppleII::Card *card) {
assert(slot >= 1 && slot < 8);
cards_[slot - 1].reset(card);
card->set_delegate(this);
pick_card_messaging_group(card);
}
bool is_every_cycle_card(AppleII::Card *card) {
return !card->get_select_constraints();
}
void pick_card_messaging_group(AppleII::Card *card) {
const bool is_every_cycle = is_every_cycle_card(card);
std::vector<AppleII::Card *> &intended = is_every_cycle ? every_cycle_cards_ : just_in_time_cards_;
std::vector<AppleII::Card *> &undesired = is_every_cycle ? just_in_time_cards_ : every_cycle_cards_;
if(std::find(intended.begin(), intended.end(), card) != intended.end()) return;
auto old_membership = std::find(undesired.begin(), undesired.end(), card);
if(old_membership != undesired.end()) undesired.erase(old_membership);
intended.push_back(card);
}
void card_did_change_select_constraints(AppleII::Card *card) override {
pick_card_messaging_group(card);
}
AppleII::DiskIICard *diskii_card() {
return dynamic_cast<AppleII::DiskIICard *>(cards_[5].get());
}
// MARK: - Memory Map
struct MemoryBlock {
uint8_t *read_pointer = nullptr;
uint8_t *write_pointer = nullptr;
} memory_blocks_[4]; // The IO page isn't included.
// MARK: - The language card.
struct {
bool bank1 = false;
bool read = false;
bool pre_write = false;
bool write = false;
} language_card_;
bool has_language_card_ = true;
void set_language_card_paging() {
if(has_language_card_ && !language_card_.write) {
memory_blocks_[2].write_pointer = &ram_[48*1024 + (language_card_.bank1 ? 0x1000 : 0x0000)];
memory_blocks_[3].write_pointer = &ram_[56*1024];
} else {
memory_blocks_[2].write_pointer = memory_blocks_[3].write_pointer = nullptr;
}
if(has_language_card_ && language_card_.read) {
memory_blocks_[2].read_pointer = &ram_[48*1024 + (language_card_.bank1 ? 0x1000 : 0x0000)];
memory_blocks_[3].read_pointer = &ram_[56*1024];
} else {
memory_blocks_[2].read_pointer = rom_.data();
memory_blocks_[3].read_pointer = rom_.data() + 0x1000;
}
}
// MARK - typing
std::unique_ptr<Utility::StringSerialiser> string_serialiser_;
// MARK - quick loading
bool should_load_quickly_ = false;
// MARK - joysticks
class Joystick: public Inputs::ConcreteJoystick {
public:
Joystick() :
ConcreteJoystick({
Input(Input::Horizontal),
Input(Input::Vertical),
// The Apple II offers three buttons between two joysticks;
// this emulator puts three buttons on each joystick and
// combines them.
Input(Input::Fire, 0),
Input(Input::Fire, 1),
Input(Input::Fire, 2),
}) {}
void did_set_input(const Input &input, float value) override {
if(!input.info.control.index && (input.type == Input::Type::Horizontal || input.type == Input::Type::Vertical))
axes[(input.type == Input::Type::Horizontal) ? 0 : 1] = 1.0f - value;
}
void did_set_input(const Input &input, bool value) override {
if(input.type == Input::Type::Fire && input.info.control.index < 3) {
buttons[input.info.control.index] = value;
}
}
bool buttons[3] = {false, false, false};
float axes[2] = {0.5f, 0.5f};
};
// On an Apple II, the programmer strobes 0xc070 and that causes each analogue input
// to begin a charge and discharge cycle **if they are not already charging**.
// The greater the analogue input, the faster they will charge and therefore the sooner
// they will discharge.
//
// This emulator models that with analogue_charge_ being essentially the amount of time,
// in charge threshold units, since 0xc070 was last strobed. But if any of the analogue
// inputs were already partially charged then they gain a bias in analogue_biases_.
//
// It's a little indirect, but it means only having to increment the one value in the
// main loop.
float analogue_charge_ = 0.0f;
float analogue_biases_[4] = {0.0f, 0.0f, 0.0f, 0.0f};
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
bool analogue_channel_is_discharged(size_t channel) {
return static_cast<Joystick *>(joysticks_[channel >> 1].get())->axes[channel & 1] < analogue_charge_ + analogue_biases_[channel];
}
public:
ConcreteMachine(const Analyser::Static::AppleII::Target &target, const ROMMachine::ROMFetcher &rom_fetcher):
m6502_(*this),
video_bus_handler_(ram_),
audio_toggle_(audio_queue_),
speaker_(audio_toggle_) {
// The system's master clock rate.
const float master_clock = 14318180.0;
// This is where things get slightly convoluted: establish the machine as having a clock rate
// equal to the number of cycles of work the 6502 will actually achieve. Which is less than
// the master clock rate divided by 14 because every 65th cycle is extended by one seventh.
set_clock_rate((master_clock / 14.0) * 65.0 / (65.0 + 1.0 / 7.0));
// The speaker, however, should think it is clocked at half the master clock, per a general
// decision to sample it at seven times the CPU clock (plus stretches).
speaker_.set_input_rate(static_cast<float>(master_clock / (2.0 * static_cast<float>(audio_divider))));
// Apply a 6Khz low-pass filter. This was picked by ear and by an attempt to understand the
// Apple II schematic but, well, I don't claim much insight on the latter. This is definitely
// something to review in the future.
speaker_.set_high_frequency_cutoff(6000);
// Also, start with randomised memory contents.
Memory::Fuzz(ram_, sizeof(ram_));
// Add a couple of joysticks.
joysticks_.emplace_back(new Joystick);
joysticks_.emplace_back(new Joystick);
// Pick the required ROMs.
using Target = Analyser::Static::AppleII::Target;
std::vector<std::string> rom_names = {"apple2-character.rom"};
switch(target.model) {
default:
rom_names.push_back("apple2o.rom");
break;
case Target::Model::IIplus:
rom_names.push_back("apple2.rom");
break;
}
const auto roms = rom_fetcher("AppleII", rom_names);
if(!roms[0] || !roms[1]) {
throw ROMMachine::Error::MissingROMs;
}
character_rom_ = std::move(*roms[0]);
rom_ = std::move(*roms[1]);
if(rom_.size() > 12*1024) {
rom_.erase(rom_.begin(), rom_.begin() + static_cast<off_t>(rom_.size()) - 12*1024);
}
if(target.disk_controller != Target::DiskController::None) {
// Apple recommended slot 6 for the (first) Disk II.
install_card(6, new AppleII::DiskIICard(rom_fetcher, target.disk_controller == Target::DiskController::SixteenSector));
}
// Set up the default memory blocks.
memory_blocks_[0].read_pointer = memory_blocks_[0].write_pointer = ram_;
memory_blocks_[1].read_pointer = memory_blocks_[1].write_pointer = &ram_[0x200];
set_language_card_paging();
insert_media(target.media);
}
~ConcreteMachine() {
audio_queue_.flush();
}
void setup_output(float aspect_ratio) override {
video_.reset(new AppleII::Video::Video<VideoBusHandler>(video_bus_handler_));
video_->set_character_rom(character_rom_);
}
void close_output() override {
video_.reset();
}
Outputs::CRT::CRT *get_crt() override {
return video_->get_crt();
}
Outputs::Speaker::Speaker *get_speaker() override {
return &speaker_;
}
forceinline Cycles perform_bus_operation(const CPU::MOS6502::BusOperation operation, const uint16_t address, uint8_t *const value) {
++ cycles_since_video_update_;
++ cycles_since_card_update_;
cycles_since_audio_update_ += Cycles(7);
// The Apple II has a slightly weird timing pattern: every 65th CPU cycle is stretched
// by an extra 1/7th. That's because one cycle lasts 3.5 NTSC colour clocks, so after
// 65 cycles a full line of 227.5 colour clocks have passed. But the high-rate binary
// signal approximation that produces colour needs to be in phase, so a stretch of exactly
// 0.5 further colour cycles is added. The video class handles that implicitly, but it
// needs to be accumulated here for the audio.
cycles_into_current_line_ = (cycles_into_current_line_ + 1) % 65;
const bool is_stretched_cycle = !cycles_into_current_line_;
if(is_stretched_cycle) {
++ cycles_since_audio_update_;
++ stretched_cycles_since_card_update_;
}
/*
There are five distinct zones of memory on an Apple II:
0000 to 0200 : the zero and stack pages, which can be paged independently on a IIe
0200 to c000 : the main block of RAM, which can be paged on a IIe
c000 to d000 : the IO area, including card ROMs
d000 to e000 : the low ROM area, which can contain indepdently-paged RAM with a language card
e000 onward : the rest of ROM, also potentially replaced with RAM by a language card
*/
uint16_t accessed_address = address;
MemoryBlock *block = nullptr;
if(address < 0x200) block = &memory_blocks_[0];
else if(address < 0xc000) {
if(address < 0x6000 && !isReadOperation(operation)) update_video();
block = &memory_blocks_[1];
accessed_address -= 0x200;
}
else if(address < 0xd000) block = nullptr;
else if(address < 0xe000) {block = &memory_blocks_[2]; accessed_address -= 0xd000; }
else { block = &memory_blocks_[3]; accessed_address -= 0xe000; }
bool has_updated_cards = false;
if(block) {
if(isReadOperation(operation)) *value = block->read_pointer[accessed_address];
else if(block->write_pointer) block->write_pointer[accessed_address] = *value;
if(should_load_quickly_) {
// Check for a prima facie entry into RWTS.
if(operation == CPU::MOS6502::BusOperation::ReadOpcode && address == 0xb7b5) {
// Grab the IO control block address for inspection.
uint16_t io_control_block_address =
static_cast<uint16_t>(
(m6502_.get_value_of_register(CPU::MOS6502::Register::A) << 8) |
m6502_.get_value_of_register(CPU::MOS6502::Register::Y)
);
// Verify that this is table type one, for execution on card six,
// against drive 1 or 2, and that the command is either a seek or a sector read.
if(
ram_[io_control_block_address+0x00] == 0x01 &&
ram_[io_control_block_address+0x01] == 0x60 &&
ram_[io_control_block_address+0x02] > 0 && ram_[io_control_block_address+0x02] < 3 &&
ram_[io_control_block_address+0x0c] < 2
) {
const uint8_t iob_track = ram_[io_control_block_address+4];
const uint8_t iob_sector = ram_[io_control_block_address+5];
const uint8_t iob_drive = ram_[io_control_block_address+2] - 1;
// Get the track identified and store the new head position.
auto track = diskii_card()->get_drive(iob_drive).step_to(Storage::Disk::HeadPosition(iob_track));
// DOS 3.3 keeps the current track (unspecified drive) in 0x478; the current track for drive 1 and drive 2
// is also kept in that Disk II card's screen hole.
ram_[0x478] = iob_track;
if(ram_[io_control_block_address+0x02] == 1) {
ram_[0x47e] = iob_track;
} else {
ram_[0x4fe] = iob_track;
}
// Check whether this is a read, not merely a seek.
if(ram_[io_control_block_address+0x0c] == 1) {
// Apple the DOS 3.3 formula to map the requested logical sector to a physical sector.
const int physical_sector = (iob_sector == 15) ? 15 : ((iob_sector * 13) % 15);
// Parse the entire track. TODO: cache these.
auto sector_map = Storage::Encodings::AppleGCR::sectors_from_segment(
Storage::Disk::track_serialisation(*track, Storage::Time(1, 50000)));
bool found_sector = false;
for(const auto &pair: sector_map) {
if(pair.second.address.sector == physical_sector) {
found_sector = true;
// Copy the sector contents to their destination.
uint16_t target = static_cast<uint16_t>(
ram_[io_control_block_address+8] |
(ram_[io_control_block_address+9] << 8)
);
for(size_t c = 0; c < 256; ++c) {
ram_[target] = pair.second.data[c];
++target;
}
// Set no error encountered.
ram_[io_control_block_address + 0xd] = 0;
break;
}
}
if(found_sector) {
// Set no error in the flags register too, and RTS.
m6502_.set_value_of_register(CPU::MOS6502::Register::Flags, m6502_.get_value_of_register(CPU::MOS6502::Register::Flags) & ~1);
*value = 0x60;
}
} else {
// No error encountered; RTS.
m6502_.set_value_of_register(CPU::MOS6502::Register::Flags, m6502_.get_value_of_register(CPU::MOS6502::Register::Flags) & ~1);
*value = 0x60;
}
}
}
}
} else {
// Assume a vapour read unless it turns out otherwise; this is a little
// wasteful but works for now.
//
// Longer version: like many other machines, when the Apple II reads from
// an address at which no hardware loads the data bus, through a process of
// practical analogue effects it'll end up receiving whatever was last on
// the bus. Which will always be whatever the video circuit fetched because
// that fetches in between every instruction.
//
// So this code assumes that'll happen unless it later determines that it
// doesn't. The call into the video isn't free because it's a just-in-time
// actor, but this will actually be the result most of the time so it's not
// too terrible.
if(isReadOperation(operation) && address != 0xc000) {
*value = video_->get_last_read_value(cycles_since_video_update_);
}
switch(address) {
default:
if(isReadOperation(operation)) {
// Read-only switches.
switch(address) {
default: break;
case 0xc000:
if(string_serialiser_) {
*value = string_serialiser_->head() | 0x80;
} else {
*value = keyboard_input_;
}
break;
case 0xc061: // Switch input 0.
*value &= 0x7f;
if(static_cast<Joystick *>(joysticks_[0].get())->buttons[0] || static_cast<Joystick *>(joysticks_[1].get())->buttons[2])
*value |= 0x80;
break;
case 0xc062: // Switch input 1.
*value &= 0x7f;
if(static_cast<Joystick *>(joysticks_[0].get())->buttons[1] || static_cast<Joystick *>(joysticks_[1].get())->buttons[1])
*value |= 0x80;
break;
case 0xc063: // Switch input 2.
*value &= 0x7f;
if(static_cast<Joystick *>(joysticks_[0].get())->buttons[2] || static_cast<Joystick *>(joysticks_[1].get())->buttons[0])
*value |= 0x80;
break;
case 0xc064: // Analogue input 0.
case 0xc065: // Analogue input 1.
case 0xc066: // Analogue input 2.
case 0xc067: { // Analogue input 3.
const size_t input = address - 0xc064;
*value &= 0x7f;
if(analogue_channel_is_discharged(input)) {
*value |= 0x80;
}
} break;
}
} else {
// Write-only switches.
}
break;
case 0xc070: { // Permit analogue inputs that are currently discharged to begin a charge cycle.
// Ensure those that were still charging retain that state.
for(size_t c = 0; c < 4; ++c) {
if(analogue_channel_is_discharged(c)) {
analogue_biases_[c] = 0.0f;
} else {
analogue_biases_[c] += analogue_charge_;
}
}
analogue_charge_ = 0.0f;
} break;
/* Read-write switches. */
case 0xc050: update_video(); video_->set_graphics_mode(); break;
case 0xc051: update_video(); video_->set_text_mode(); break;
case 0xc052: update_video(); video_->set_mixed_mode(false); break;
case 0xc053: update_video(); video_->set_mixed_mode(true); break;
case 0xc054: update_video(); video_->set_video_page(0); break;
case 0xc055: update_video(); video_->set_video_page(1); break;
case 0xc056: update_video(); video_->set_low_resolution(); break;
case 0xc057: update_video(); video_->set_high_resolution(); break;
case 0xc010:
keyboard_input_ &= 0x7f;
if(string_serialiser_) {
if(!string_serialiser_->advance())
string_serialiser_.reset();
}
break;
case 0xc030:
update_audio();
audio_toggle_.set_output(!audio_toggle_.get_output());
break;
case 0xc080: case 0xc084: case 0xc088: case 0xc08c:
case 0xc081: case 0xc085: case 0xc089: case 0xc08d:
case 0xc082: case 0xc086: case 0xc08a: case 0xc08e:
case 0xc083: case 0xc087: case 0xc08b: case 0xc08f:
// Quotes below taken from Understanding the Apple II, p. 5-28 and 5-29.
// "A3 controls the 4K bank selection"
language_card_.bank1 = (address&8);
// "Access to $C080, $C083, $C084, $0087, $C088, $C08B, $C08C, or $C08F sets the READ ENABLE flip-flop"
// (other accesses reset it)
language_card_.read = !(((address&2) >> 1) ^ (address&1));
// "The WRITE ENABLE' flip-flop is reset by an odd read access to the $C08X range when the PRE-WRITE flip-flop is set."
if(language_card_.pre_write && isReadOperation(operation) && (address&1)) language_card_.write = false;
// "[The WRITE ENABLE' flip-flop] is set by an even access in the $C08X range."
if(!(address&1)) language_card_.write = true;
// ("Any other type of access causes the WRITE ENABLE' flip-flop to hold its current state.")
// "The PRE-WRITE flip-flop is set by an odd read access in the $C08X range. It is reset by an even access or a write access."
language_card_.pre_write = isReadOperation(operation) ? (address&1) : false;
set_language_card_paging();
break;
}
/*
Communication with cards follows.
*/
if(address >= 0xc090 && address < 0xc800) {
// If this is a card access, figure out which card is at play before determining
// the totality of who needs messaging.
size_t card_number = 0;
AppleII::Card::Select select = AppleII::Card::None;
if(address >= 0xc100) {
/*
Decode the area conventionally used by cards for ROMs:
0xCn00 to 0xCnff: card n.
*/
card_number = (address - 0xc100) >> 8;
select = AppleII::Card::Device;
} else {
/*
Decode the area conventionally used by cards for registers:
C0n0 to C0nF: card n - 8.
*/
card_number = (address - 0xc090) >> 4;
select = AppleII::Card::IO;
}
// If the selected card is a just-in-time card, update the just-in-time cards,
// and then message it specifically.
const bool is_read = isReadOperation(operation);
AppleII::Card *const target = cards_[card_number].get();
if(target && !is_every_cycle_card(target)) {
update_just_in_time_cards();
target->perform_bus_operation(select, is_read, address, value);
}
// Update all the every-cycle cards regardless, but send them a ::None select if they're
// not the one actually selected.
for(const auto &card: every_cycle_cards_) {
card->run_for(Cycles(1), is_stretched_cycle);
card->perform_bus_operation(
(card == target) ? select : AppleII::Card::None,
is_read, address, value);
}
has_updated_cards = true;
}
}
if(!has_updated_cards && !every_cycle_cards_.empty()) {
// Update all every-cycle cards and give them the cycle.
const bool is_read = isReadOperation(operation);
for(const auto &card: every_cycle_cards_) {
card->run_for(Cycles(1), is_stretched_cycle);
card->perform_bus_operation(AppleII::Card::None, is_read, address, value);
}
}
// Update analogue charge level.
analogue_charge_ = std::min(analogue_charge_ + 1.0f / 2820.0f, 1.1f);
return Cycles(1);
}
void flush() {
update_video();
update_audio();
update_just_in_time_cards();
audio_queue_.perform();
}
void run_for(const Cycles cycles) override {
m6502_.run_for(cycles);
}
void set_key_pressed(Key key, char value, bool is_pressed) override {
if(key == Key::F12) {
m6502_.set_reset_line(is_pressed);
return;
}
if(is_pressed) {
// If no ASCII value is supplied, look for a few special cases.
if(!value) {
switch(key) {
case Key::Left: value = 8; break;
case Key::Right: value = 21; break;
case Key::Down: value = 10; break;
default: break;
}
}
keyboard_input_ = static_cast<uint8_t>(toupper(value) | 0x80);
}
}
Inputs::Keyboard &get_keyboard() override {
return *this;
}
void type_string(const std::string &string) override {
string_serialiser_.reset(new Utility::StringSerialiser(string, true));
}
// MARK: MediaTarget
bool insert_media(const Analyser::Static::Media &media) override {
if(!media.disks.empty()) {
auto diskii = diskii_card();
if(diskii) diskii->set_disk(media.disks[0], 0);
}
return true;
}
// MARK: Activity::Source
void set_activity_observer(Activity::Observer *observer) override {
for(const auto &card: cards_) {
if(card) card->set_activity_observer(observer);
}
}
// MARK: Options
std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
return AppleII::get_options();
}
void set_selections(const Configurable::SelectionSet &selections_by_option) override {
bool quickload;
if(Configurable::get_quick_load_tape(selections_by_option, quickload)) {
should_load_quickly_ = quickload;
}
}
Configurable::SelectionSet get_accurate_selections() override {
Configurable::SelectionSet selection_set;
Configurable::append_quick_load_tape_selection(selection_set, false);
return selection_set;
}
Configurable::SelectionSet get_user_friendly_selections() override {
Configurable::SelectionSet selection_set;
Configurable::append_quick_load_tape_selection(selection_set, true);
return selection_set;
}
// MARK: JoystickMachine
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
return joysticks_;
}
};
}
using namespace AppleII;
Machine *Machine::AppleII(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
using Target = Analyser::Static::AppleII::Target;
const Target *const appleii_target = dynamic_cast<const Target *>(target);
return new ConcreteMachine(*appleii_target, rom_fetcher);
}
Machine::~Machine() {}

View File

@@ -1,60 +0,0 @@
//
// Video.cpp
// Clock Signal
//
// Created by Thomas Harte on 14/04/2018.
// Copyright 2018 Thomas Harte. All rights reserved.
//
#include "Video.hpp"
using namespace AppleII::Video;
VideoBase::VideoBase() :
crt_(new Outputs::CRT::CRT(910, 1, Outputs::CRT::DisplayType::NTSC60, 1)) {
// Set a composite sampling function that assumes one byte per pixel input, and
// accepts any non-zero value as being fully on, zero being fully off.
crt_->set_composite_sampling_function(
"float composite_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate, float phase, float amplitude)"
"{"
"return texture(sampler, coordinate).r;"
"}");
// Show only the centre 75% of the TV frame.
crt_->set_video_signal(Outputs::CRT::VideoSignal::Composite);
crt_->set_visible_area(Outputs::CRT::Rect(0.115f, 0.122f, 0.77f, 0.77f));
crt_->set_immediate_default_phase(0.0f);
}
Outputs::CRT::CRT *VideoBase::get_crt() {
return crt_.get();
}
void VideoBase::set_graphics_mode() {
use_graphics_mode_ = true;
}
void VideoBase::set_text_mode() {
use_graphics_mode_ = false;
}
void VideoBase::set_mixed_mode(bool mixed_mode) {
mixed_mode_ = mixed_mode;
}
void VideoBase::set_video_page(int page) {
video_page_ = page;
}
void VideoBase::set_low_resolution() {
graphics_mode_ = GraphicsMode::LowRes;
}
void VideoBase::set_high_resolution() {
graphics_mode_ = GraphicsMode::HighRes;
}
void VideoBase::set_character_rom(const std::vector<uint8_t> &character_rom) {
character_rom_ = character_rom;
}

View File

@@ -1,337 +0,0 @@
//
// Video.hpp
// Clock Signal
//
// Created by Thomas Harte on 14/04/2018.
// Copyright 2018 Thomas Harte. All rights reserved.
//
#ifndef Video_hpp
#define Video_hpp
#include "../../Outputs/CRT/CRT.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
#include <vector>
namespace AppleII {
namespace Video {
class BusHandler {
public:
uint8_t perform_read(uint16_t address) {
return 0xff;
}
};
class VideoBase {
public:
VideoBase();
/// @returns The CRT this video feed is feeding.
Outputs::CRT::CRT *get_crt();
// Inputs for the various soft switches.
void set_graphics_mode();
void set_text_mode();
void set_mixed_mode(bool);
void set_video_page(int);
void set_low_resolution();
void set_high_resolution();
// Setup for text mode.
void set_character_rom(const std::vector<uint8_t> &);
protected:
std::unique_ptr<Outputs::CRT::CRT> crt_;
uint8_t *pixel_pointer_ = nullptr;
int pixel_pointer_column_ = 0;
bool pixels_are_high_density_ = false;
int video_page_ = 0;
int row_ = 0, column_ = 0, flash_ = 0;
std::vector<uint8_t> character_rom_;
enum class GraphicsMode {
LowRes,
HighRes,
Text
} graphics_mode_ = GraphicsMode::LowRes;
bool use_graphics_mode_ = false;
bool mixed_mode_ = false;
uint8_t graphics_carry_ = 0;
};
template <class BusHandler> class Video: public VideoBase {
public:
/// Constructs an instance of the video feed; a CRT is also created.
Video(BusHandler &bus_handler) :
VideoBase(),
bus_handler_(bus_handler) {}
/*!
Advances time by @c cycles; expects to be fed by the CPU clock.
Implicitly adds an extra half a colour clock at the end of every
line.
*/
void run_for(const Cycles cycles) {
/*
Addressing scheme used throughout is that column 0 is the first column with pixels in it;
row 0 is the first row with pixels in it.
A frame is oriented around 65 cycles across, 262 lines down.
*/
static const int first_sync_line = 220; // A complete guess. Information needed.
static const int first_sync_column = 49; // Also a guess.
static const int sync_length = 4; // One of the two likely candidates.
int int_cycles = cycles.as_int();
while(int_cycles) {
const int cycles_this_line = std::min(65 - column_, int_cycles);
const int ending_column = column_ + cycles_this_line;
if(row_ >= first_sync_line && row_ < first_sync_line + 3) {
// In effect apply an XOR to HSYNC and VSYNC flags in order to include equalising
// pulses (and hencce keep hsync approximately where it should be during vsync).
const int blank_start = std::max(first_sync_column - sync_length, column_);
const int blank_end = std::min(first_sync_column, ending_column);
if(blank_end > blank_start) {
if(blank_start > column_) {
crt_->output_sync(static_cast<unsigned int>(blank_start - column_) * 14);
}
crt_->output_blank(static_cast<unsigned int>(blank_end - blank_start) * 14);
if(blank_end < ending_column) {
crt_->output_sync(static_cast<unsigned int>(ending_column - blank_end) * 14);
}
} else {
crt_->output_sync(static_cast<unsigned int>(cycles_this_line) * 14);
}
} else {
const GraphicsMode line_mode = use_graphics_mode_ ? graphics_mode_ : GraphicsMode::Text;
// The first 40 columns are submitted to the CRT only upon completion;
// they'll be either graphics or blank, depending on which side we are
// of line 192.
if(column_ < 40) {
if(row_ < 192) {
GraphicsMode pixel_mode = (!mixed_mode_ || row_ < 160) ? line_mode : GraphicsMode::Text;
bool requires_high_density = pixel_mode != GraphicsMode::Text;
if(!column_ || requires_high_density != pixels_are_high_density_) {
if(column_) output_data_to_column(column_);
pixel_pointer_ = crt_->allocate_write_area(561);
pixel_pointer_column_ = column_;
pixels_are_high_density_ = requires_high_density;
graphics_carry_ = 0;
}
const int pixel_end = std::min(40, ending_column);
const int character_row = row_ >> 3;
const int pixel_row = row_ & 7;
const uint16_t row_address = static_cast<uint16_t>((character_row >> 3) * 40 + ((character_row&7) << 7));
const uint16_t text_address = static_cast<uint16_t>(((video_page_+1) * 0x400) + row_address);
switch(pixel_mode) {
case GraphicsMode::Text: {
const uint8_t inverses[] = {
0xff,
static_cast<uint8_t>((flash_ / flash_length) * 0xff),
0x00,
0x00
};
for(int c = column_; c < pixel_end; ++c) {
const uint8_t character = bus_handler_.perform_read(static_cast<uint16_t>(text_address + c));
const std::size_t character_address = static_cast<std::size_t>(((character & 0x3f) << 3) + pixel_row);
const uint8_t character_pattern = character_rom_[character_address] ^ inverses[character >> 6];
// The character ROM is output MSB to LSB rather than LSB to MSB.
pixel_pointer_[0] = character_pattern & 0x40;
pixel_pointer_[1] = character_pattern & 0x20;
pixel_pointer_[2] = character_pattern & 0x10;
pixel_pointer_[3] = character_pattern & 0x08;
pixel_pointer_[4] = character_pattern & 0x04;
pixel_pointer_[5] = character_pattern & 0x02;
pixel_pointer_[6] = character_pattern & 0x01;
graphics_carry_ = character_pattern & 0x40;
pixel_pointer_ += 7;
}
} break;
case GraphicsMode::LowRes: {
const int row_shift = (row_&4);
// TODO: decompose into two loops, possibly.
for(int c = column_; c < pixel_end; ++c) {
const uint8_t nibble = (bus_handler_.perform_read(static_cast<uint16_t>(text_address + c)) >> row_shift) & 0x0f;
// Low-resolution graphics mode shifts the colour code on a loop, but has to account for whether this
// 14-sample output window is starting at the beginning of a colour cycle or halfway through.
if(c&1) {
pixel_pointer_[0] = pixel_pointer_[4] = pixel_pointer_[8] = pixel_pointer_[12] = nibble & 4;
pixel_pointer_[1] = pixel_pointer_[5] = pixel_pointer_[9] = pixel_pointer_[13] = nibble & 8;
pixel_pointer_[2] = pixel_pointer_[6] = pixel_pointer_[10] = nibble & 1;
pixel_pointer_[3] = pixel_pointer_[7] = pixel_pointer_[11] = nibble & 2;
graphics_carry_ = nibble & 8;
} else {
pixel_pointer_[0] = pixel_pointer_[4] = pixel_pointer_[8] = pixel_pointer_[12] = nibble & 1;
pixel_pointer_[1] = pixel_pointer_[5] = pixel_pointer_[9] = pixel_pointer_[13] = nibble & 2;
pixel_pointer_[2] = pixel_pointer_[6] = pixel_pointer_[10] = nibble & 4;
pixel_pointer_[3] = pixel_pointer_[7] = pixel_pointer_[11] = nibble & 8;
graphics_carry_ = nibble & 2;
}
pixel_pointer_ += 14;
}
} break;
case GraphicsMode::HighRes: {
const uint16_t graphics_address = static_cast<uint16_t>(((video_page_+1) * 0x2000) + row_address + ((pixel_row&7) << 10));
for(int c = column_; c < pixel_end; ++c) {
const uint8_t graphic = bus_handler_.perform_read(static_cast<uint16_t>(graphics_address + c));
// High resolution graphics shift out LSB to MSB, optionally with a delay of half a pixel.
// If there is a delay, the previous output level is held to bridge the gap.
if(graphic & 0x80) {
pixel_pointer_[0] = graphics_carry_;
pixel_pointer_[1] = pixel_pointer_[2] = graphic & 0x01;
pixel_pointer_[3] = pixel_pointer_[4] = graphic & 0x02;
pixel_pointer_[5] = pixel_pointer_[6] = graphic & 0x04;
pixel_pointer_[7] = pixel_pointer_[8] = graphic & 0x08;
pixel_pointer_[9] = pixel_pointer_[10] = graphic & 0x10;
pixel_pointer_[11] = pixel_pointer_[12] = graphic & 0x20;
pixel_pointer_[13] = graphic & 0x40;
} else {
pixel_pointer_[0] = pixel_pointer_[1] = graphic & 0x01;
pixel_pointer_[2] = pixel_pointer_[3] = graphic & 0x02;
pixel_pointer_[4] = pixel_pointer_[5] = graphic & 0x04;
pixel_pointer_[6] = pixel_pointer_[7] = graphic & 0x08;
pixel_pointer_[8] = pixel_pointer_[9] = graphic & 0x10;
pixel_pointer_[10] = pixel_pointer_[11] = graphic & 0x20;
pixel_pointer_[12] = pixel_pointer_[13] = graphic & 0x40;
}
graphics_carry_ = graphic & 0x40;
pixel_pointer_ += 14;
}
} break;
}
if(ending_column >= 40) {
output_data_to_column(40);
}
} else {
if(ending_column >= 40) {
crt_->output_blank(560);
}
}
}
/*
The left border, sync, right border pattern doesn't depend on whether
there were pixels this row and is output as soon as it is known.
*/
const int first_blank_start = std::max(40, column_);
const int first_blank_end = std::min(first_sync_column, ending_column);
if(first_blank_end > first_blank_start) {
crt_->output_blank(static_cast<unsigned int>(first_blank_end - first_blank_start) * 14);
}
const int sync_start = std::max(first_sync_column, column_);
const int sync_end = std::min(first_sync_column + sync_length, ending_column);
if(sync_end > sync_start) {
crt_->output_sync(static_cast<unsigned int>(sync_end - sync_start) * 14);
}
int second_blank_start;
if(line_mode != GraphicsMode::Text && (!mixed_mode_ || row_ < 159 || row_ >= 192)) {
const int colour_burst_start = std::max(first_sync_column + sync_length + 1, column_);
const int colour_burst_end = std::min(first_sync_column + sync_length + 4, ending_column);
if(colour_burst_end > colour_burst_start) {
crt_->output_default_colour_burst(static_cast<unsigned int>(colour_burst_end - colour_burst_start) * 14);
}
second_blank_start = std::max(first_sync_column + 7, column_);
} else {
second_blank_start = std::max(first_sync_column + 4, column_);
}
if(ending_column > second_blank_start) {
crt_->output_blank(static_cast<unsigned int>(ending_column - second_blank_start) * 14);
}
}
int_cycles -= cycles_this_line;
column_ = (column_ + cycles_this_line) % 65;
if(!column_) {
row_ = (row_ + 1) % 262;
flash_ = (flash_ + 1) % (2 * flash_length);
// Add an extra half a colour cycle of blank; this isn't counted in the run_for
// count explicitly but is promised.
crt_->output_blank(2);
}
}
}
/*!
Obtains the last value the video read prior to time now+offset.
*/
uint8_t get_last_read_value(Cycles offset) {
// Rules of generation:
// (1) a complete sixty-five-cycle scan line consists of sixty-five consecutive bytes of
// display buffer memory that starts twenty-five bytes prior to the actual data to be displayed.
// (2) During VBL the data acts just as if it were starting a whole new frame from the beginning, but
// it never finishes this pseudo-frame. After getting one third of the way through the frame (to
// scan line $3F), it suddenly repeats the previous six scan lines ($3A through $3F) before aborting
// to begin the next true frame.
//
// Source: Have an Apple Split by Bob Bishop; http://rich12345.tripod.com/aiivideo/softalk.html
// Determine column at offset.
int mapped_column = column_ + offset.as_int();
// Map that backwards from the internal pixels-at-start generation to pixels-at-end
// (so what was column 0 is now column 25).
mapped_column += 25;
// Apply carry into the row counter.
int mapped_row = row_ + (mapped_column / 65);
mapped_column %= 65;
mapped_row %= 262;
// Apple out-of-bounds row logic.
if(mapped_row >= 256) {
mapped_row = 0x3a + (mapped_row&255);
} else {
mapped_row %= 192;
}
// Calculate the address and return the value.
uint16_t read_address = static_cast<uint16_t>(get_row_address(mapped_row) + mapped_column - 25);
return bus_handler_.perform_read(read_address);
}
private:
uint16_t get_row_address(int row) {
const int character_row = row >> 3;
const int pixel_row = row & 7;
const uint16_t row_address = static_cast<uint16_t>((character_row >> 3) * 40 + ((character_row&7) << 7));
GraphicsMode pixel_mode = ((!mixed_mode_ || row < 160) && use_graphics_mode_) ? graphics_mode_ : GraphicsMode::Text;
return (pixel_mode == GraphicsMode::HighRes) ?
static_cast<uint16_t>(((video_page_+1) * 0x2000) + row_address + ((pixel_row&7) << 10)) :
static_cast<uint16_t>(((video_page_+1) * 0x400) + row_address);
}
static const int flash_length = 8406;
BusHandler &bus_handler_;
void output_data_to_column(int column) {
int length = column - pixel_pointer_column_;
crt_->output_data(static_cast<unsigned int>(length*14), static_cast<unsigned int>(length * (pixels_are_high_density_ ? 14 : 7)));
pixel_pointer_ = nullptr;
}
};
}
}
#endif /* Video_hpp */

View File

@@ -122,10 +122,6 @@ class ConcreteMachine:
joysticks_.emplace_back(new Joystick(bus_.get(), 4, 1));
}
~ConcreteMachine() {
close_output();
}
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
return joysticks_;
}
@@ -157,18 +153,10 @@ class ConcreteMachine:
}
// to satisfy CRTMachine::Machine
void setup_output(float aspect_ratio) override {
bus_->tia_.reset(new TIA);
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override {
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();
bus_->tia_.set_crt_delegate(this);
bus_->tia_.set_scan_target(scan_target);
}
Outputs::Speaker::Speaker *get_speaker() override {
@@ -181,15 +169,15 @@ class ConcreteMachine:
}
// 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 {
void crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, int number_of_frames, 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;
int total_number_of_frames = 0;
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;
@@ -205,10 +193,10 @@ class ConcreteMachine:
double clock_rate;
if(is_ntsc_) {
clock_rate = NTSC_clock_rate;
bus_->tia_->set_output_mode(TIA::OutputMode::NTSC);
bus_->tia_.set_output_mode(TIA::OutputMode::NTSC);
} else {
clock_rate = PAL_clock_rate;
bus_->tia_->set_output_mode(TIA::OutputMode::PAL);
bus_->tia_.set_output_mode(TIA::OutputMode::PAL);
}
bus_->speaker_.set_input_rate(static_cast<float>(clock_rate / static_cast<double>(CPUTicksPerAudioTick)));
@@ -228,10 +216,8 @@ class ConcreteMachine:
// 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) {}
int number_of_frames = 0;
int number_of_unexpected_vertical_syncs = 0;
} frame_records_[4];
unsigned int frame_record_pointer_ = 0;
bool is_ntsc_ = true;

View File

@@ -36,7 +36,7 @@ class Bus {
// the RIOT, TIA and speaker
PIA mos6532_;
std::shared_ptr<TIA> tia_;
TIA tia_;
Concurrency::DeferringAsyncTaskQueue audio_queue_;
TIASound tia_sound_;
@@ -55,13 +55,13 @@ class Bus {
// video backlog accumulation counter
Cycles cycles_since_video_update_;
inline void update_video() {
tia_->run_for(cycles_since_video_update_.flush());
tia_.run_for(cycles_since_video_update_.flush<Cycles>());
}
// RIOT backlog accumulation counter
Cycles cycles_since_6532_update_;
inline void update_6532() {
mos6532_.run_for(cycles_since_6532_update_.flush());
mos6532_.run_for(cycles_since_6532_update_.flush<Cycles>());
}
};

View File

@@ -68,7 +68,7 @@ template<class T> class Cartridge:
// effect until the next read; therefore it isn't safe to assume that signalling ready immediately
// skips to the end of the line.
if(operation == CPU::MOS6502::BusOperation::Ready)
cycles_run_for = tia_->get_cycles_until_horizontal_blank(cycles_since_video_update_);
cycles_run_for = tia_.get_cycles_until_horizontal_blank(cycles_since_video_update_);
cycles_since_speaker_update_ += Cycles(cycles_run_for);
cycles_since_video_update_ += Cycles(cycles_run_for);
@@ -101,7 +101,7 @@ template<class T> class Cartridge:
case 0x05: // missile 1 / playfield / ball collisions
case 0x06: // ball / playfield collisions
case 0x07: // player / player, missile / missile collisions
returnValue &= tia_->get_collision_flags(decodedAddress);
returnValue &= tia_.get_collision_flags(decodedAddress);
break;
case 0x08:
@@ -120,52 +120,52 @@ template<class T> class Cartridge:
} else {
const uint16_t decodedAddress = address & 0x3f;
switch(decodedAddress) {
case 0x00: update_video(); tia_->set_sync(*value & 0x02); break;
case 0x01: update_video(); tia_->set_blank(*value & 0x02); break;
case 0x00: update_video(); tia_.set_sync(*value & 0x02); break;
case 0x01: update_video(); tia_.set_blank(*value & 0x02); break;
case 0x02: m6502_.set_ready_line(true); break;
case 0x03:
update_video();
tia_->reset_horizontal_counter();
tia_.reset_horizontal_counter();
horizontal_counter_resets_++;
break;
// TODO: audio will now be out of synchronisation. Fix.
case 0x04:
case 0x05: update_video(); tia_->set_player_number_and_size(decodedAddress - 0x04, *value); break;
case 0x05: update_video(); tia_.set_player_number_and_size(decodedAddress - 0x04, *value); break;
case 0x06:
case 0x07: update_video(); tia_->set_player_missile_colour(decodedAddress - 0x06, *value); break;
case 0x08: update_video(); tia_->set_playfield_ball_colour(*value); break;
case 0x09: update_video(); tia_->set_background_colour(*value); break;
case 0x0a: update_video(); tia_->set_playfield_control_and_ball_size(*value); break;
case 0x07: update_video(); tia_.set_player_missile_colour(decodedAddress - 0x06, *value); break;
case 0x08: update_video(); tia_.set_playfield_ball_colour(*value); break;
case 0x09: update_video(); tia_.set_background_colour(*value); break;
case 0x0a: update_video(); tia_.set_playfield_control_and_ball_size(*value); break;
case 0x0b:
case 0x0c: update_video(); tia_->set_player_reflected(decodedAddress - 0x0b, !((*value)&8)); break;
case 0x0c: update_video(); tia_.set_player_reflected(decodedAddress - 0x0b, !((*value)&8)); break;
case 0x0d:
case 0x0e:
case 0x0f: update_video(); tia_->set_playfield(decodedAddress - 0x0d, *value); break;
case 0x0f: update_video(); tia_.set_playfield(decodedAddress - 0x0d, *value); break;
case 0x10:
case 0x11: update_video(); tia_->set_player_position(decodedAddress - 0x10); break;
case 0x11: update_video(); tia_.set_player_position(decodedAddress - 0x10); break;
case 0x12:
case 0x13: update_video(); tia_->set_missile_position(decodedAddress - 0x12); break;
case 0x14: update_video(); tia_->set_ball_position(); break;
case 0x13: update_video(); tia_.set_missile_position(decodedAddress - 0x12); break;
case 0x14: update_video(); tia_.set_ball_position(); break;
case 0x1b:
case 0x1c: update_video(); tia_->set_player_graphic(decodedAddress - 0x1b, *value); break;
case 0x1c: update_video(); tia_.set_player_graphic(decodedAddress - 0x1b, *value); break;
case 0x1d:
case 0x1e: update_video(); tia_->set_missile_enable(decodedAddress - 0x1d, (*value)&2); break;
case 0x1f: update_video(); tia_->set_ball_enable((*value)&2); break;
case 0x1e: update_video(); tia_.set_missile_enable(decodedAddress - 0x1d, (*value)&2); break;
case 0x1f: update_video(); tia_.set_ball_enable((*value)&2); break;
case 0x20:
case 0x21: update_video(); tia_->set_player_motion(decodedAddress - 0x20, *value); break;
case 0x21: update_video(); tia_.set_player_motion(decodedAddress - 0x20, *value); break;
case 0x22:
case 0x23: update_video(); tia_->set_missile_motion(decodedAddress - 0x22, *value); break;
case 0x24: update_video(); tia_->set_ball_motion(*value); break;
case 0x23: update_video(); tia_.set_missile_motion(decodedAddress - 0x22, *value); break;
case 0x24: update_video(); tia_.set_ball_motion(*value); break;
case 0x25:
case 0x26: tia_->set_player_delay(decodedAddress - 0x25, (*value)&1); break;
case 0x27: tia_->set_ball_delay((*value)&1); break;
case 0x26: tia_.set_player_delay(decodedAddress - 0x25, (*value)&1); break;
case 0x27: tia_.set_ball_delay((*value)&1); break;
case 0x28:
case 0x29: update_video(); tia_->set_missile_position_to_player(decodedAddress - 0x28, (*value)&2); break;
case 0x2a: update_video(); tia_->move(); break;
case 0x2b: update_video(); tia_->clear_motion(); break;
case 0x2c: update_video(); tia_->clear_collision_flags(); break;
case 0x29: update_video(); tia_.set_missile_position_to_player(decodedAddress - 0x28, (*value)&2); break;
case 0x2a: update_video(); tia_.move(); break;
case 0x2b: update_video(); tia_.clear_motion(); break;
case 0x2c: update_video(); tia_.clear_collision_flags(); break;
case 0x15:
case 0x16: update_audio(); tia_sound_.set_control(decodedAddress - 0x15, *value); break;
@@ -192,7 +192,7 @@ template<class T> class Cartridge:
}
}
if(!tia_->get_cycles_until_horizontal_blank(cycles_since_video_update_)) m6502_.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);
}
@@ -204,7 +204,7 @@ template<class T> class Cartridge:
}
protected:
CPU::MOS6502::Processor<Cartridge<T>, true> m6502_;
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, Cartridge<T>, true> m6502_;
std::vector<uint8_t> rom_;
private:

View File

@@ -22,12 +22,10 @@ namespace {
uint8_t reverse_table[256];
}
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_video_signal(Outputs::CRT::VideoSignal::Composite);
set_output_mode(OutputMode::NTSC);
}
TIA::TIA():
crt_(cycles_per_line * 2 - 1, 1, Outputs::Display::Type::NTSC60, Outputs::Display::InputDataType::Luminance8Phase8) {
set_output_mode(OutputMode::NTSC);
for(int c = 0; c < 256; c++) {
reverse_table[c] = static_cast<uint8_t>(
@@ -113,51 +111,35 @@ TIA::TIA(bool create_crt) {
}
}
TIA::TIA() : TIA(true) {}
TIA::TIA(std::function<void(uint8_t *output_buffer)> line_end_function) : TIA(false) {
line_end_function_ = line_end_function;
}
void TIA::set_output_mode(Atari2600::TIA::OutputMode output_mode) {
Outputs::CRT::DisplayType display_type;
Outputs::Display::Type display_type;
tv_standard_ = output_mode;
if(output_mode == OutputMode::NTSC) {
crt_->set_svideo_sampling_function(
"vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
"{"
"uint c = texture(texID, coordinate).r;"
"uint y = c & 14u;"
"uint iPhase = (c >> 4);"
"float phaseOffset = 6.283185308 * float(iPhase) / 13.0 + 5.074880441076923;"
"return vec2(float(y) / 14.0, step(1, iPhase) * cos(phase - phaseOffset));"
"}");
display_type = Outputs::CRT::DisplayType::NTSC60;
display_type = Outputs::Display::Type::NTSC60;
} else {
crt_->set_svideo_sampling_function(
"vec2 svideo_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)"
"{"
"uint c = texture(texID, coordinate).r;"
"uint y = c & 14u;"
"uint iPhase = (c >> 4);"
"uint direction = iPhase & 1u;"
"float phaseOffset = float(7u - direction) + (float(direction) - 0.5) * 2.0 * float(iPhase >> 1);"
"phaseOffset *= 6.283185308 / 12.0;"
"return vec2(float(y) / 14.0, step(4, (iPhase + 2u) & 15u) * cos(phase + phaseOffset));"
"}");
display_type = Outputs::CRT::DisplayType::PAL50;
display_type = Outputs::Display::Type::PAL50;
}
crt_->set_video_signal(Outputs::CRT::VideoSignal::Composite);
crt_.set_display_type(Outputs::Display::DisplayType::CompositeColour);
// line number of cycles in a line of video is one less than twice the number of clock cycles per line; the Atari
// outputs 228 colour cycles of material per line when an NTSC line 227.5. Since all clock numbers will be doubled
// later, cycles_per_line * 2 - 1 is therefore the real length of an NTSC line, even though we're going to supply
// 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);
crt_.set_new_display_type(cycles_per_line * 2 - 1, display_type);
/* speaker_->set_input_rate(static_cast<float>(get_clock_rate() / 38.0));*/
// Update the luminance/phase mappings of the current palette.
for(size_t c = 0; c < colour_palette_.size(); ++c) {
set_colour_palette_entry(c, colour_palette_[c].original);
}
}
void TIA::set_crt_delegate(Outputs::CRT::Delegate *delegate) {
crt_.set_delegate(delegate);
}
void TIA::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
crt_.set_scan_target(scan_target);
}
void TIA::run_for(const Cycles cycles) {
@@ -198,7 +180,40 @@ int TIA::get_cycles_until_horizontal_blank(const Cycles from_offset) {
}
void TIA::set_background_colour(uint8_t colour) {
colour_palette_[static_cast<int>(ColourIndex::Background)] = colour;
set_colour_palette_entry(size_t(ColourIndex::Background), colour);
}
void TIA::set_colour_palette_entry(size_t index, uint8_t colour) {
const uint8_t luminance = ((colour & 14) * 255) / 14;
uint8_t phase = colour >> 4;
if(tv_standard_ == OutputMode::NTSC) {
if(!phase) phase = 255;
else {
phase = -(phase * 127) / 13;
phase -= 102;
phase &= 127;
}
} else {
if(phase < 2 || phase > 13) {
phase = 255;
} else {
const auto direction = phase & 1;
phase >>= 1;
if(direction) phase ^= 0xf;
phase = (phase + 6 + direction) & 0xf;
phase = (phase * 127) / 12;
phase &= 127;
}
}
colour_palette_[index].original = colour;
uint8_t *target = reinterpret_cast<uint8_t *>(&colour_palette_[index].luminance_phase);
target[0] = luminance;
target[1] = phase;
}
void TIA::set_playfield(uint16_t offset, uint8_t value) {
@@ -238,7 +253,7 @@ void TIA::set_playfield_control_and_ball_size(uint8_t value) {
}
void TIA::set_playfield_ball_colour(uint8_t colour) {
colour_palette_[static_cast<int>(ColourIndex::PlayfieldBall)] = colour;
set_colour_palette_entry(size_t(ColourIndex::PlayfieldBall), colour);
}
void TIA::set_player_number_and_size(int player, uint8_t value) {
@@ -300,7 +315,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_[static_cast<int>(ColourIndex::PlayerMissile0) + player] = colour;
set_colour_palette_entry(size_t(ColourIndex::PlayerMissile0) + size_t(player), colour);
}
void TIA::set_missile_enable(int missile, bool enabled) {
@@ -382,7 +397,6 @@ void TIA::output_for_cycles(int number_of_cycles) {
bool is_reset = output_cursor < 224 && horizontal_counter_ >= 224;
if(!output_cursor) {
if(line_end_function_) line_end_function_(collision_buffer_);
std::memset(collision_buffer_, 0, sizeof(collision_buffer_));
ball_.motion_time %= 228;
@@ -407,11 +421,11 @@ void TIA::output_for_cycles(int number_of_cycles) {
#define Period(function, target) \
if(output_cursor < target) { \
if(horizontal_counter_ <= target) { \
if(crt_) crt_->function(static_cast<unsigned int>((horizontal_counter_ - output_cursor) * 2)); \
crt_.function((horizontal_counter_ - output_cursor) * 2); \
horizontal_counter_ %= cycles_per_line; \
return; \
} else { \
if(crt_) crt_->function(static_cast<unsigned int>((target - output_cursor) * 2)); \
crt_.function((target - output_cursor) * 2); \
output_cursor = target; \
} \
}
@@ -437,19 +451,17 @@ 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_) {
const unsigned int data_length = static_cast<unsigned int>(output_cursor - pixels_start_location_);
crt_->output_data(data_length * 2, data_length);
}
const int data_length = int(output_cursor - pixels_start_location_);
crt_.output_data(data_length * 2, size_t(data_length));
pixel_target_ = nullptr;
pixels_start_location_ = 0;
}
int duration = std::min(228, horizontal_counter_) - output_cursor;
if(crt_) crt_->output_blank(static_cast<unsigned int>(duration * 2));
crt_.output_blank(duration * 2);
} else {
if(!pixels_start_location_ && crt_) {
if(!pixels_start_location_) {
pixels_start_location_ = output_cursor;
pixel_target_ = crt_->allocate_write_area(160);
pixel_target_ = reinterpret_cast<uint16_t *>(crt_.begin_data(160));
}
// convert that into pixels
@@ -461,9 +473,9 @@ void TIA::output_for_cycles(int number_of_cycles) {
output_cursor++;
}
if(horizontal_counter_ == cycles_per_line && crt_) {
const unsigned int data_length = static_cast<unsigned int>(output_cursor - pixels_start_location_);
crt_->output_data(data_length * 2, data_length);
if(horizontal_counter_ == cycles_per_line) {
const int data_length = int(output_cursor - pixels_start_location_);
crt_.output_data(data_length * 2, size_t(data_length));
pixel_target_ = nullptr;
pixels_start_location_ = 0;
}
@@ -480,7 +492,7 @@ void TIA::output_pixels(int start, int end) {
if(start < first_pixel_cycle+8 && horizontal_blank_extend_) {
while(start < end && start < first_pixel_cycle+8) {
pixel_target_[target_position] = 0;
pixel_target_[target_position] = 0xff00; // TODO: this assumes little endianness.
start++;
target_position++;
}
@@ -489,13 +501,13 @@ 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_[static_cast<int>(ColourMode::ScoreLeft)][buffer_value]];
pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreLeft)][buffer_value]].luminance_phase;
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_[static_cast<int>(ColourMode::ScoreRight)][buffer_value]];
pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreRight)][buffer_value]].luminance_phase;
start++;
target_position++;
}
@@ -503,7 +515,7 @@ void TIA::output_pixels(int start, int end) {
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]];
pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[table_index][buffer_value]].luminance_phase;
start++;
target_position++;
}
@@ -518,20 +530,16 @@ void TIA::output_line() {
break;
case sync_flag:
case sync_flag | blank_flag:
if(crt_) {
crt_->output_sync(32);
crt_->output_blank(32);
crt_->output_sync(392);
}
crt_.output_sync(32);
crt_.output_blank(32);
crt_.output_sync(392);
horizontal_blank_extend_ = false;
break;
case blank_flag:
if(crt_) {
crt_->output_blank(32);
crt_->output_sync(32);
crt_->output_default_colour_burst(32);
crt_->output_blank(360);
}
crt_.output_blank(32);
crt_.output_sync(32);
crt_.output_default_colour_burst(32);
crt_.output_blank(360);
horizontal_blank_extend_ = false;
break;
}

View File

@@ -9,19 +9,19 @@
#ifndef TIA_hpp
#define TIA_hpp
#include <algorithm>
#include <array>
#include <cstdint>
#include <functional>
#include "../CRTMachine.hpp"
#include "../../Outputs/CRT/CRT.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
namespace Atari2600 {
class TIA {
public:
TIA();
// The supplied hook is for unit testing only; if instantiated with a line_end_function then it will
// be called with the latest collision buffer upon the conclusion of each line. What's a collision
// buffer? It's an implementation detail. If you're not writing a unit test, leave it alone.
TIA(std::function<void(uint8_t *output_buffer)> line_end_function);
enum class OutputMode {
NTSC, PAL
@@ -35,7 +35,7 @@ class TIA {
void set_sync(bool sync);
void set_blank(bool blank);
void reset_horizontal_counter(); // Reset is delayed by four cycles.
void reset_horizontal_counter(); // Reset is delayed by four cycles.
/*!
@returns the number of cycles between (current TIA time) + from_offset to the current or
@@ -73,12 +73,11 @@ class TIA {
uint8_t get_collision_flags(int offset);
void clear_collision_flags();
Outputs::CRT::CRT *get_crt() { return crt_.get(); }
void set_crt_delegate(Outputs::CRT::Delegate *);
void set_scan_target(Outputs::Display::ScanTarget *);
private:
TIA(bool create_crt);
std::unique_ptr<Outputs::CRT::CRT> crt_;
std::function<void(uint8_t *output_buffer)> line_end_function_;
Outputs::CRT::CRT crt_;
// the master counter; counts from 0 to 228 with all visible pixels being in the final 160
int horizontal_counter_ = 0;
@@ -107,7 +106,7 @@ class TIA {
ScoreRight,
OnTop
};
uint8_t colour_mask_by_mode_collision_flags_[4][64]; // maps from [ColourMode][CollisionMark] to colour_pallete_ entry
uint8_t colour_mask_by_mode_collision_flags_[4][64]; // maps from [ColourMode][CollisionMark] to colour_palette_ entry
enum class ColourIndex {
Background = 0,
@@ -115,7 +114,13 @@ class TIA {
PlayerMissile0,
PlayerMissile1
};
uint8_t colour_palette_[4];
struct Colour {
uint16_t luminance_phase;
uint8_t original;
};
std::array<Colour, 4> colour_palette_;
void set_colour_palette_entry(size_t index, uint8_t colour);
OutputMode tv_standard_;
// playfield state
int background_half_mask_ = 0;
@@ -302,7 +307,7 @@ class TIA {
inline void output_line();
int pixels_start_location_ = 0;
uint8_t *pixel_target_ = nullptr;
uint16_t *pixel_target_ = nullptr;
inline void output_pixels(int start, int end);
};

View File

@@ -9,7 +9,7 @@
#ifndef CRTMachine_hpp
#define CRTMachine_hpp
#include "../Outputs/CRT/CRT.hpp"
#include "../Outputs/ScanTarget.hpp"
#include "../Outputs/Speaker/Speaker.hpp"
#include "../ClockReceiver/ClockReceiver.hpp"
#include "../ClockReceiver/TimeTypes.hpp"
@@ -19,6 +19,7 @@
#include <cmath>
// TODO: rename.
namespace CRTMachine {
/*!
@@ -29,26 +30,19 @@ namespace CRTMachine {
class Machine {
public:
/*!
Causes the machine to set up its CRT and, if it has one, speaker. The caller guarantees
that an OpenGL context is bound.
*/
virtual void setup_output(float aspect_ratio) = 0;
Causes the machine to set up its display and, if it has one, speaker.
/*!
Gives the machine a chance to release all owned resources. The caller guarantees that the
OpenGL context is bound.
The @c scan_target will receive all video output; the caller guarantees
that it is non-null.
*/
virtual void close_output() = 0;
/// @returns The CRT this machine is drawing to. Should not be @c nullptr.
virtual Outputs::CRT::CRT *get_crt() = 0;
virtual void set_scan_target(Outputs::Display::ScanTarget *scan_target) = 0;
/// @returns The speaker that receives this machine's output, or @c nullptr if this machine is mute.
virtual Outputs::Speaker::Speaker *get_speaker() = 0;
/// @returns The confidence that this machine is running content it understands.
virtual float get_confidence() { return 0.5f; }
virtual void print_type() {}
virtual std::string debug_type() { return ""; }
/// Runs the machine for @c duration seconds.
virtual void run_for(Time::Seconds duration) {
@@ -68,32 +62,33 @@ class Machine {
}
/*!
Maps from Configurable::Display to Outputs::CRT::VideoSignal and calls
@c set_video_signal with the result.
Maps from Configurable::Display to Outputs::Display::VideoSignal and calls
@c set_display_type with the result.
*/
void set_video_signal_configurable(Configurable::Display type) {
Outputs::CRT::VideoSignal signal;
Outputs::Display::DisplayType display_type;
switch(type) {
default:
case Configurable::Display::RGB:
signal = Outputs::CRT::VideoSignal::RGB;
display_type = Outputs::Display::DisplayType::RGB;
break;
case Configurable::Display::SVideo:
signal = Outputs::CRT::VideoSignal::SVideo;
display_type = Outputs::Display::DisplayType::SVideo;
break;
case Configurable::Display::Composite:
signal = Outputs::CRT::VideoSignal::Composite;
case Configurable::Display::CompositeColour:
display_type = Outputs::Display::DisplayType::CompositeColour;
break;
case Configurable::Display::CompositeMonochrome:
display_type = Outputs::Display::DisplayType::CompositeMonochrome;
break;
}
set_video_signal(signal);
set_display_type(display_type);
}
/*!
Forwards the video signal to the CRT returned by get_crt().
Forwards the video signal to the target returned by get_crt().
*/
virtual void set_video_signal(Outputs::CRT::VideoSignal video_signal) {
get_crt()->set_video_signal(video_signal);
}
virtual void set_display_type(Outputs::Display::DisplayType display_type) {}
private:
double clock_rate_ = 1.0;

View File

@@ -17,7 +17,9 @@
#include "../CRTMachine.hpp"
#include "../JoystickMachine.hpp"
#include "../../Configurable/StandardOptions.hpp"
#include "../../ClockReceiver/ForceInline.hpp"
#include "../../ClockReceiver/JustInTime.hpp"
#include "../../Outputs/Speaker/Implementation/CompoundSource.hpp"
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
@@ -31,6 +33,12 @@ const int sn76489_divider = 2;
namespace Coleco {
namespace Vision {
std::vector<std::unique_ptr<Configurable::Option>> get_options() {
return Configurable::standard_options(
static_cast<Configurable::StandardOptions>(Configurable::DisplaySVideo | Configurable::DisplayCompositeColour)
);
}
class Joystick: public Inputs::ConcreteJoystick {
public:
Joystick() :
@@ -76,7 +84,7 @@ class Joystick: public Inputs::ConcreteJoystick {
}
break;
case Input::Up: if(is_active) direction_ &= ~0x01; else direction_ |= 0x01; break;
case Input::Up: if(is_active) direction_ &= ~0x01; else direction_ |= 0x01; break;
case Input::Right: if(is_active) direction_ &= ~0x02; else direction_ |= 0x02; break;
case Input::Down: if(is_active) direction_ &= ~0x04; else direction_ |= 0x04; break;
case Input::Left: if(is_active) direction_ &= ~0x08; else direction_ |= 0x08; break;
@@ -100,18 +108,20 @@ class Joystick: public Inputs::ConcreteJoystick {
private:
uint8_t direction_ = 0xff;
uint8_t keypad_ = 0xff;
uint8_t keypad_ = 0x7f;
};
class ConcreteMachine:
public Machine,
public CPU::Z80::BusHandler,
public CRTMachine::Machine,
public Configurable::Device,
public JoystickMachine::Machine {
public:
ConcreteMachine(const Analyser::Static::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
z80_(*this),
vdp_(TI::TMS::TMS9918A),
sn76489_(TI::SN76489::Personality::SN76489, audio_queue_, sn76489_divider),
ay_(audio_queue_),
mixer_(sn76489_, ay_),
@@ -122,8 +132,7 @@ class ConcreteMachine:
joysticks_.emplace_back(new Joystick);
const auto roms = rom_fetcher(
"ColecoVision",
{ "coleco.rom" });
{ {"ColecoVision", "the ColecoVision BIOS", "coleco.rom", 8*1024, 0x3aa93ef3} });
if(!roms[0]) {
throw ROMMachine::Error::MissingROMs;
@@ -141,15 +150,27 @@ class ConcreteMachine:
cartridge_address_limit_ = static_cast<uint16_t>(0x8000 + cartridge_.size() - 1);
if(cartridge_.size() > 32768) {
// Ensure the cartrige is a multiple of 16kb in size, as that won't
// be checked when paging.
const size_t extension = (16384 - (cartridge_.size() & 16383)) % 16384;
cartridge_.resize(cartridge_.size() + extension);
cartridge_pages_[0] = &cartridge_[cartridge_.size() - 16384];
cartridge_pages_[1] = cartridge_.data();
is_megacart_ = true;
} else {
// Ensure at least 32kb is allocated to the cartrige so that
// reads are never out of bounds.
cartridge_.resize(32768);
cartridge_pages_[0] = cartridge_.data();
cartridge_pages_[1] = cartridge_.data() + 16384;
is_megacart_ = false;
}
}
// ColecoVisions have composite output only.
vdp_->set_display_type(Outputs::Display::DisplayType::CompositeColour);
}
~ConcreteMachine() {
@@ -160,17 +181,12 @@ class ConcreteMachine:
return joysticks_;
}
void setup_output(float aspect_ratio) override {
vdp_.reset(new TI::TMS9918(TI::TMS9918::TMS9918A));
get_crt()->set_video_signal(Outputs::CRT::VideoSignal::Composite);
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override {
vdp_->set_scan_target(scan_target);
}
void close_output() override {
vdp_.reset();
}
Outputs::CRT::CRT *get_crt() override {
return vdp_->get_crt();
void set_display_type(Outputs::Display::DisplayType display_type) override {
vdp_->set_display_type(display_type);
}
Outputs::Speaker::Speaker *get_speaker() override {
@@ -183,145 +199,147 @@ class ConcreteMachine:
// MARK: Z80::BusHandler
forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
// The SN76489 will use its ready line to trigger the Z80's wait for three
// cycles when accessed. Everything else runs at full speed. Short-circuit
// that whole piece of communications by just accruing the time here if applicable.
const HalfCycles penalty(
(
cycle.operation == CPU::Z80::PartialMachineCycle::Output &&
((*cycle.address >> 5) & 7) == 7
) ? 6 : 0
);
// The SN76489 will use its ready line to trigger the Z80's wait, which will add
// thirty-one (!) cycles when accessed. M1 cycles are extended by a single cycle.
// This code works out the delay up front in order to simplify execution flow, though
// technically this is a little duplicative.
HalfCycles penalty(0);
if(cycle.operation == CPU::Z80::PartialMachineCycle::Output && ((*cycle.address >> 5) & 7) == 7) {
penalty = HalfCycles(62);
} else if(cycle.operation == CPU::Z80::PartialMachineCycle::ReadOpcode) {
penalty = HalfCycles(2);
}
const HalfCycles length = cycle.length + penalty;
time_since_vdp_update_ += length;
vdp_ += length;
time_since_sn76489_update_ += length;
uint16_t address = cycle.address ? *cycle.address : 0x0000;
switch(cycle.operation) {
case CPU::Z80::PartialMachineCycle::ReadOpcode:
if(!address) pc_zero_accesses_++;
case CPU::Z80::PartialMachineCycle::Read:
if(address < 0x2000) {
if(super_game_module_.replace_bios) {
// Act only if necessary.
if(cycle.is_terminal()) {
uint16_t address = cycle.address ? *cycle.address : 0x0000;
switch(cycle.operation) {
case CPU::Z80::PartialMachineCycle::ReadOpcode:
if(!address) pc_zero_accesses_++;
case CPU::Z80::PartialMachineCycle::Read:
if(address < 0x2000) {
if(super_game_module_.replace_bios) {
*cycle.value = super_game_module_.ram[address];
} else {
*cycle.value = bios_[address];
}
} else if(super_game_module_.replace_ram && address < 0x8000) {
*cycle.value = super_game_module_.ram[address];
} else if(address >= 0x6000 && address < 0x8000) {
*cycle.value = ram_[address & 1023];
} else if(address >= 0x8000 && address <= cartridge_address_limit_) {
if(is_megacart_ && address >= 0xffc0) {
page_megacart(address);
}
*cycle.value = cartridge_pages_[(address >> 14)&1][address&0x3fff];
} else {
*cycle.value = bios_[address];
*cycle.value = 0xff;
}
} else if(super_game_module_.replace_ram && address < 0x8000) {
*cycle.value = super_game_module_.ram[address];
} else if(address >= 0x6000 && address < 0x8000) {
*cycle.value = ram_[address & 1023];
} else if(address >= 0x8000 && address <= cartridge_address_limit_) {
if(is_megacart_ && address >= 0xffc0) {
break;
case CPU::Z80::PartialMachineCycle::Write:
if(super_game_module_.replace_bios && address < 0x2000) {
super_game_module_.ram[address] = *cycle.value;
} else if(super_game_module_.replace_ram && address >= 0x2000 && address < 0x8000) {
super_game_module_.ram[address] = *cycle.value;
} else if(address >= 0x6000 && address < 0x8000) {
ram_[address & 1023] = *cycle.value;
} else if(is_megacart_ && address >= 0xffc0) {
page_megacart(address);
}
*cycle.value = cartridge_pages_[(address >> 14)&1][address&0x3fff];
} else {
*cycle.value = 0xff;
}
break;
break;
case CPU::Z80::PartialMachineCycle::Write:
if(super_game_module_.replace_bios && address < 0x2000) {
super_game_module_.ram[address] = *cycle.value;
} else if(super_game_module_.replace_ram && address >= 0x2000 && address < 0x8000) {
super_game_module_.ram[address] = *cycle.value;
} else if(address >= 0x6000 && address < 0x8000) {
ram_[address & 1023] = *cycle.value;
} else if(is_megacart_ && address >= 0xffc0) {
page_megacart(address);
}
break;
case CPU::Z80::PartialMachineCycle::Input:
switch((address >> 5) & 7) {
case 5:
*cycle.value = vdp_->get_register(address);
z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line());
time_until_interrupt_ = vdp_->get_time_until_interrupt();
break;
case CPU::Z80::PartialMachineCycle::Input:
switch((address >> 5) & 7) {
case 5:
update_video();
*cycle.value = vdp_->get_register(address);
z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line());
time_until_interrupt_ = vdp_->get_time_until_interrupt();
break;
case 7: {
const std::size_t joystick_id = (address&2) >> 1;
Joystick *joystick = static_cast<Joystick *>(joysticks_[joystick_id].get());
if(joysticks_in_keypad_mode_) {
*cycle.value = joystick->get_keypad_input();
} else {
*cycle.value = joystick->get_direction_input();
}
case 7: {
const std::size_t joystick_id = (address&2) >> 1;
Joystick *joystick = static_cast<Joystick *>(joysticks_[joystick_id].get());
if(joysticks_in_keypad_mode_) {
*cycle.value = joystick->get_keypad_input();
} else {
*cycle.value = joystick->get_direction_input();
}
// Hitting exactly the recommended joypad input port is an indicator that
// this really is a ColecoVision game. The BIOS won't do this when just waiting
// to start a game (unlike accessing the VDP and SN).
if((address&0xfc) == 0xfc) confidence_counter_.add_hit();
} break;
// Hitting exactly the recommended joypad input port is an indicator that
// this really is a ColecoVision game. The BIOS won't do this when just waiting
// to start a game (unlike accessing the VDP and SN).
if((address&0xfc) == 0xfc) confidence_counter_.add_hit();
} break;
default:
switch(address&0xff) {
default: *cycle.value = 0xff; break;
case 0x52:
// Read AY data.
update_audio();
ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BC1));
*cycle.value = ay_.get_data_output();
ay_.set_control_lines(GI::AY38910::ControlLines(0));
break;
}
break;
}
break;
default:
switch(address&0xff) {
default: *cycle.value = 0xff; break;
case 0x52:
// Read AY data.
update_audio();
ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BC1));
*cycle.value = ay_.get_data_output();
ay_.set_control_lines(GI::AY38910::ControlLines(0));
break;
}
break;
}
break;
case CPU::Z80::PartialMachineCycle::Output: {
const int eighth = (address >> 5) & 7;
switch(eighth) {
case 4: case 6:
joysticks_in_keypad_mode_ = eighth == 4;
break;
case CPU::Z80::PartialMachineCycle::Output: {
const int eighth = (address >> 5) & 7;
switch(eighth) {
case 4: case 6:
joysticks_in_keypad_mode_ = eighth == 4;
break;
case 5:
vdp_->set_register(address, *cycle.value);
z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line());
time_until_interrupt_ = vdp_->get_time_until_interrupt();
break;
case 5:
update_video();
vdp_->set_register(address, *cycle.value);
z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line());
time_until_interrupt_ = vdp_->get_time_until_interrupt();
break;
case 7:
update_audio();
sn76489_.set_register(*cycle.value);
break;
case 7:
update_audio();
sn76489_.set_register(*cycle.value);
break;
default:
// Catch Super Game Module accesses; it decodes more thoroughly.
switch(address&0xff) {
default: break;
case 0x7f:
super_game_module_.replace_bios = !((*cycle.value)&0x2);
break;
case 0x50:
// Set AY address.
update_audio();
ay_.set_control_lines(GI::AY38910::BC1);
ay_.set_data_input(*cycle.value);
ay_.set_control_lines(GI::AY38910::ControlLines(0));
break;
case 0x51:
// Set AY data.
update_audio();
ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BDIR));
ay_.set_data_input(*cycle.value);
ay_.set_control_lines(GI::AY38910::ControlLines(0));
break;
case 0x53:
super_game_module_.replace_ram = !!((*cycle.value)&0x1);
break;
}
break;
}
} break;
default:
// Catch Super Game Module accesses; it decodes more thoroughly.
switch(address&0xff) {
default: break;
case 0x7f:
super_game_module_.replace_bios = !((*cycle.value)&0x2);
break;
case 0x50:
// Set AY address.
update_audio();
ay_.set_control_lines(GI::AY38910::BC1);
ay_.set_data_input(*cycle.value);
ay_.set_control_lines(GI::AY38910::ControlLines(0));
break;
case 0x51:
// Set AY data.
update_audio();
ay_.set_control_lines(GI::AY38910::ControlLines(GI::AY38910::BC2 | GI::AY38910::BDIR));
ay_.set_data_input(*cycle.value);
ay_.set_control_lines(GI::AY38910::ControlLines(0));
break;
case 0x53:
super_game_module_.replace_ram = !!((*cycle.value)&0x1);
break;
}
break;
}
} break;
default: break;
default: break;
}
}
if(time_until_interrupt_ > 0) {
@@ -335,7 +353,7 @@ class ConcreteMachine:
}
void flush() {
update_video();
vdp_.flush();
update_audio();
audio_queue_.perform();
}
@@ -345,6 +363,30 @@ class ConcreteMachine:
return confidence_counter_.get_confidence();
}
// MARK: - Configuration options.
std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
return Coleco::Vision::get_options();
}
void set_selections(const Configurable::SelectionSet &selections_by_option) override {
Configurable::Display display;
if(Configurable::get_display(selections_by_option, display)) {
set_video_signal_configurable(display);
}
}
Configurable::SelectionSet get_accurate_selections() override {
Configurable::SelectionSet selection_set;
Configurable::append_display_selection(selection_set, Configurable::Display::CompositeColour);
return selection_set;
}
Configurable::SelectionSet get_user_friendly_selections() override {
Configurable::SelectionSet selection_set;
Configurable::append_display_selection(selection_set, Configurable::Display::SVideo);
return selection_set;
}
private:
inline void page_megacart(uint16_t address) {
const std::size_t selected_start = (static_cast<std::size_t>(address&63) << 14) % cartridge_.size();
@@ -353,12 +395,9 @@ class ConcreteMachine:
inline void update_audio() {
speaker_.run_for(audio_queue_, time_since_sn76489_update_.divide_cycles(Cycles(sn76489_divider)));
}
inline void update_video() {
vdp_->run_for(time_since_vdp_update_.flush());
}
CPU::Z80::Processor<ConcreteMachine, false, false> z80_;
std::unique_ptr<TI::TMS9918> vdp_;
JustInTimeActor<TI::TMS::TMS9918, HalfCycles> vdp_;
Concurrency::DeferringAsyncTaskQueue audio_queue_;
TI::SN76489 sn76489_;
@@ -381,7 +420,6 @@ class ConcreteMachine:
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
bool joysticks_in_keypad_mode_ = false;
HalfCycles time_since_vdp_update_;
HalfCycles time_since_sn76489_update_;
HalfCycles time_until_interrupt_;

View File

@@ -9,12 +9,15 @@
#ifndef ColecoVision_hpp
#define ColecoVision_hpp
#include "../../Configurable/Configurable.hpp"
#include "../../Analyser/Static/StaticAnalyser.hpp"
#include "../ROMMachine.hpp"
namespace Coleco {
namespace Vision {
std::vector<std::unique_ptr<Configurable::Option>> get_options();
class Machine {
public:
virtual ~Machine();

View File

@@ -39,13 +39,20 @@ MachineBase::MachineBase(Personality personality, const ROMMachine::ROMFetcher &
// attach the only drive there is
set_drive(drive_);
std::string rom_name;
std::string device_name;
uint32_t crc = 0;
switch(personality) {
case Personality::C1540: rom_name = "1540.bin"; break;
case Personality::C1541: rom_name = "1541.bin"; break;
case Personality::C1540:
device_name = "1540";
crc = 0x718d42b1;
break;
case Personality::C1541:
device_name = "1541";
crc = 0xfb760019;
break;
}
auto roms = rom_fetcher("Commodore1540", {rom_name});
auto roms = rom_fetcher({ {"Commodore1540", "the " + device_name + " ROM", device_name + ".bin", 16*1024, crc} });
if(!roms[0]) {
throw ROMMachine::Error::MissingROMs;
}
@@ -170,7 +177,6 @@ void SerialPortVIA::set_port_output(MOS::MOS6522::Port port, uint8_t value, uint
attention_acknowledge_level_ = !(value&0x10);
data_level_output_ = (value&0x02);
// printf("[C1540] %s output is %s\n", StringForLine(::Commodore::Serial::Line::Clock), value ? "high" : "low");
serialPort->set_output(::Commodore::Serial::Line::Clock, static_cast<::Commodore::Serial::LineLevel>(!(value&0x08)));
update_data_line();
}
@@ -178,8 +184,6 @@ void SerialPortVIA::set_port_output(MOS::MOS6522::Port port, uint8_t value, uint
}
void SerialPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool value) {
// printf("[C1540] %s input 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;
@@ -200,7 +204,6 @@ void SerialPortVIA::set_serial_port(const std::shared_ptr<::Commodore::Serial::P
void SerialPortVIA::update_data_line() {
std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock();
if(serialPort) {
// printf("[C1540] %s output is %s\n", StringForLine(::Commodore::Serial::Line::Data), (!data_level_output_ && (attention_level_input_ != attention_acknowledge_level_)) ? "high" : "low");
// "ATN (Attention) is an input on pin 3 of P2 and P3 that is sensed at PB7 and CA1 of UC3 after being inverted by UA1"
serialPort->set_output(::Commodore::Serial::Line::Data,
static_cast<::Commodore::Serial::LineLevel>(!data_level_output_ && (attention_level_input_ != attention_acknowledge_level_)));
@@ -278,7 +281,7 @@ void DriveVIA::set_activity_observer(Activity::Observer *observer) {
void SerialPort::set_input(::Commodore::Serial::Line line, ::Commodore::Serial::LineLevel level) {
std::shared_ptr<SerialPortVIA> serialPortVIA = serial_port_VIA_.lock();
if(serialPortVIA) serialPortVIA->set_serial_line_state(line, (bool)level);
if(serialPortVIA) serialPortVIA->set_serial_line_state(line, static_cast<bool>(level));
}
void SerialPort::set_serial_port_via(const std::shared_ptr<SerialPortVIA> &serialPortVIA) {

View File

@@ -143,7 +143,7 @@ class MachineBase:
void set_activity_observer(Activity::Observer *observer);
protected:
CPU::MOS6502::Processor<MachineBase, false> m6502_;
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, MachineBase, false> m6502_;
std::shared_ptr<Storage::Disk::Drive> drive_;
uint8_t ram_[0x800];

View File

@@ -9,6 +9,7 @@
#include "SerialBus.hpp"
#include <cstdio>
#include <iostream>
using namespace Commodore::Serial;
@@ -45,12 +46,10 @@ void Bus::set_line_output_did_change(Line line) {
for(std::weak_ptr<Port> port : ports_) {
std::shared_ptr<Port> locked_port = port.lock();
if(locked_port) {
new_line_level = (LineLevel)((bool)new_line_level & (bool)locked_port->get_output(line));
new_line_level = (LineLevel)(static_cast<bool>(new_line_level) & static_cast<bool>(locked_port->get_output(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;
@@ -69,7 +68,7 @@ void Bus::set_line_output_did_change(Line line) {
void DebugPort::set_input(Line line, LineLevel value) {
input_levels_[line] = value;
printf("[Bus] %s is %s\n", StringForLine(line), value ? "high" : "low");
std::cout << "[Bus] " << StringForLine(line) << " is " << (value ? "high" : "low");
if(!incoming_count_) {
incoming_count_ = (!input_levels_[Line::Clock] && !input_levels_[Line::Data]) ? 8 : 0;
} else {
@@ -77,6 +76,6 @@ void DebugPort::set_input(Line line, LineLevel value) {
incoming_byte_ = (incoming_byte_ >> 1) | (input_levels_[Line::Data] ? 0x80 : 0x00);
}
incoming_count_--;
if(incoming_count_ == 0) printf("[Bus] Observed %02x\n", incoming_byte_);
if(incoming_count_ == 0) std::cout << "[Bus] Observed value " << std::hex << int(incoming_byte_);
}
}

View File

@@ -68,13 +68,13 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
BIND(F7, KeyF7);
}
#undef BIND
return KeyboardMachine::Machine::KeyNotMapped;
return KeyboardMachine::MappedMachine::KeyNotMapped;
}
uint16_t *CharacterMapper::sequence_for_character(char character) {
#define KEYS(...) {__VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
#define SHIFT(...) {KeyLShift, __VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
#define X {KeyboardMachine::Machine::KeyNotMapped}
#define KEYS(...) {__VA_ARGS__, KeyboardMachine::MappedMachine::KeyEndSequence}
#define SHIFT(...) {KeyLShift, __VA_ARGS__, KeyboardMachine::MappedMachine::KeyEndSequence}
#define X {KeyboardMachine::MappedMachine::KeyNotMapped}
static KeySequence key_sequences[] = {
/* NUL */ X, /* SOH */ X,
/* STX */ X, /* ETX */ X,

View File

@@ -34,11 +34,11 @@ enum Key: uint16_t {
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
KeyRestore = 0xfffd
#undef key
};
struct KeyboardMapper: public KeyboardMachine::Machine::KeyboardMapper {
struct KeyboardMapper: public KeyboardMachine::MappedMachine::KeyboardMapper {
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key);
};

View File

@@ -21,6 +21,7 @@
#include "../../../Components/6522/6522.hpp"
#include "../../../ClockReceiver/ForceInline.hpp"
#include "../../../Outputs/Log.hpp"
#include "../../../Storage/Tape/Parsers/Commodore.hpp"
@@ -50,7 +51,7 @@ enum ROMSlot {
std::vector<std::unique_ptr<Configurable::Option>> get_options() {
return Configurable::standard_options(
static_cast<Configurable::StandardOptions>(Configurable::DisplaySVideo | Configurable::DisplayComposite | Configurable::QuickLoadTape)
static_cast<Configurable::StandardOptions>(Configurable::DisplaySVideo | Configurable::DisplayCompositeColour | Configurable::QuickLoadTape)
);
}
@@ -214,7 +215,7 @@ class SerialPort : public ::Commodore::Serial::Port {
/// Receives an input change from the base serial port class, and communicates it to the user-port VIA.
void set_input(::Commodore::Serial::Line line, ::Commodore::Serial::LineLevel level) {
std::shared_ptr<UserPortVIA> userPortVIA = user_port_via_.lock();
if(userPortVIA) userPortVIA->set_serial_line_state(line, (bool)level);
if(userPortVIA) userPortVIA->set_serial_line_state(line, static_cast<bool>(level));
}
/// Sets the user-port VIA with which this serial port communicates.
@@ -281,7 +282,7 @@ class Joystick: public Inputs::ConcreteJoystick {
class ConcreteMachine:
public CRTMachine::Machine,
public MediaTarget::Machine,
public KeyboardMachine::Machine,
public KeyboardMachine::MappedMachine,
public JoystickMachine::Machine,
public Configurable::Device,
public CPU::MOS6502::BusHandler,
@@ -294,6 +295,7 @@ class ConcreteMachine:
public:
ConcreteMachine(const Analyser::Static::Commodore::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
m6502_(*this),
mos6560_(mos6560_bus_handler_),
user_port_via_port_handler_(new UserPortVIA),
keyboard_via_port_handler_(new KeyboardVIA),
serial_port_(new SerialPort),
@@ -321,31 +323,34 @@ class ConcreteMachine:
// install a joystick
joysticks_.emplace_back(new Joystick(*user_port_via_port_handler_, *keyboard_via_port_handler_));
std::vector<std::string> rom_names = { "basic.bin" };
const std::string machine_name = "Vic20";
std::vector<ROMMachine::ROM> rom_names = {
{machine_name, "the VIC-20 BASIC ROM", "basic.bin", 8*1024, 0xdb4c43c1}
};
switch(target.region) {
default:
rom_names.push_back("characters-english.bin");
rom_names.push_back("kernel-pal.bin");
rom_names.emplace_back(machine_name, "the English-language VIC-20 character ROM", "characters-english.bin", 4*1024, 0x83e032a6);
rom_names.emplace_back(machine_name, "the English-language PAL VIC-20 kernel ROM", "kernel-pal.bin", 8*1024, 0x4be07cb4);
break;
case Analyser::Static::Commodore::Target::Region::American:
rom_names.push_back("characters-english.bin");
rom_names.push_back("kernel-ntsc.bin");
rom_names.emplace_back(machine_name, "the English-language VIC-20 character ROM", "characters-english.bin", 4*1024, 0x83e032a6);
rom_names.emplace_back(machine_name, "the English-language NTSC VIC-20 kernel ROM", "kernel-ntsc.bin", 8*1024, 0xe5e7c174);
break;
case Analyser::Static::Commodore::Target::Region::Danish:
rom_names.push_back("characters-danish.bin");
rom_names.push_back("kernel-danish.bin");
rom_names.emplace_back(machine_name, "the Danish VIC-20 character ROM", "characters-danish.bin", 4*1024, 0x7fc11454);
rom_names.emplace_back(machine_name, "the Danish VIC-20 kernel ROM", "kernel-danish.bin", 8*1024, 0x02adaf16);
break;
case Analyser::Static::Commodore::Target::Region::Japanese:
rom_names.push_back("characters-japanese.bin");
rom_names.push_back("kernel-japanese.bin");
rom_names.emplace_back(machine_name, "the Japanese VIC-20 character ROM", "characters-japanese.bin", 4*1024, 0xfcfd8a4b);
rom_names.emplace_back(machine_name, "the Japanese VIC-20 kernel ROM", "kernel-japanese.bin", 8*1024, 0x336900d7);
break;
case Analyser::Static::Commodore::Target::Region::Swedish:
rom_names.push_back("characters-swedish.bin");
rom_names.push_back("kernel-japanese.bin");
rom_names.emplace_back(machine_name, "the Swedish VIC-20 character ROM", "characters-swedish.bin", 4*1024, 0xd808551d);
rom_names.emplace_back(machine_name, "the Swedish VIC-20 kernel ROM", "kernel-swedish.bin", 8*1024, 0xb2a60662);
break;
}
const auto roms = rom_fetcher("Vic20", rom_names);
const auto roms = rom_fetcher(rom_names);
for(const auto &rom: roms) {
if(!rom) {
@@ -377,13 +382,16 @@ class ConcreteMachine:
if(target.region == Analyser::Static::Commodore::Target::Region::American || target.region == Analyser::Static::Commodore::Target::Region::Japanese) {
// NTSC
set_clock_rate(1022727);
output_mode_ = MOS::MOS6560::OutputMode::NTSC;
mos6560_.set_output_mode(MOS::MOS6560::OutputMode::NTSC);
} else {
// PAL
set_clock_rate(1108404);
output_mode_ = MOS::MOS6560::OutputMode::PAL;
mos6560_.set_output_mode(MOS::MOS6560::OutputMode::PAL);
}
mos6560_.set_high_frequency_cutoff(1600); // There is a 1.6Khz low-pass filter in the Vic-20.
mos6560_.set_clock_rate(get_clock_rate());
// Initialise the memory maps as all pointing to nothing
memset(processor_read_memory_map_, 0, sizeof(processor_read_memory_map_));
memset(processor_write_memory_map_, 0, sizeof(processor_write_memory_map_));
@@ -501,7 +509,7 @@ class ConcreteMachine:
if((address&0xfc00) == 0x9000) {
if(!(address&0x100)) {
update_video();
result &= mos6560_->get_register(address);
result &= mos6560_.get_register(address);
}
if(address & 0x10) result &= user_port_via_.get_register(address);
if(address & 0x20) result &= keyboard_via_.get_register(address);
@@ -522,12 +530,12 @@ class ConcreteMachine:
const uint16_t tape_buffer_pointer = static_cast<uint16_t>(ram_[0xb2]) | static_cast<uint16_t>(ram_[0xb3] << 8);
header->serialise(&ram_[tape_buffer_pointer], 0x8000 - tape_buffer_pointer);
hold_tape_ = true;
printf("Found header\n");
LOG("Vic-20: Found header");
} else {
// no header found, so pretend this hack never interceded
tape_->get_tape()->set_offset(tape_position);
hold_tape_ = false;
printf("Didn't find header\n");
LOG("Vic-20: Didn't find header");
}
// clear status and the verify flag
@@ -568,11 +576,11 @@ class ConcreteMachine:
m6502_.set_value_of_register(CPU::MOS6502::Register::ProgramCounter, 0xfccf);
*value = 0xea; // i.e. NOP implied
hold_tape_ = true;
printf("Found data\n");
LOG("Vic-20: Found data");
} else {
tape_->get_tape()->set_offset(tape_position);
hold_tape_ = false;
printf("Didn't find data\n");
LOG("Vic-20: Didn't find data");
}
}
}
@@ -588,7 +596,7 @@ class ConcreteMachine:
// The VIC is selected by bit 8 = 0
if(!(address&0x100)) {
update_video();
mos6560_->set_register(address, *value);
mos6560_.set_register(address, *value);
}
// The first VIA is selected by bit 4 = 1.
if(address & 0x10) user_port_via_.set_register(address, *value);
@@ -613,30 +621,23 @@ class ConcreteMachine:
void flush() {
update_video();
mos6560_->flush();
mos6560_.flush();
}
void run_for(const Cycles cycles) override final {
m6502_.run_for(cycles);
}
void setup_output(float aspect_ratio) override final {
mos6560_.reset(new MOS::MOS6560::MOS6560<Vic6560BusHandler>(mos6560_bus_handler_));
mos6560_->set_high_frequency_cutoff(1600); // There is a 1.6Khz low-pass filter in the Vic-20.
mos6560_->set_output_mode(output_mode_);
mos6560_->set_clock_rate(get_clock_rate());
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override final {
mos6560_.set_scan_target(scan_target);
}
void close_output() override final {
mos6560_ = nullptr;
}
Outputs::CRT::CRT *get_crt() override final {
return mos6560_->get_crt();
void set_display_type(Outputs::Display::DisplayType display_type) override final {
mos6560_.set_display_type(display_type);
}
Outputs::Speaker::Speaker *get_speaker() override final {
return mos6560_->get_speaker();
return mos6560_.get_speaker();
}
void mos6522_did_change_interrupt_status(void *mos6522) override final {
@@ -678,7 +679,7 @@ class ConcreteMachine:
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);
Configurable::append_display_selection(selection_set, Configurable::Display::CompositeColour);
return selection_set;
}
@@ -701,9 +702,9 @@ class ConcreteMachine:
private:
void update_video() {
mos6560_->run_for(cycles_since_mos6560_update_.flush());
mos6560_.run_for(cycles_since_mos6560_update_.flush<Cycles>());
}
CPU::MOS6502::Processor<ConcreteMachine, false> m6502_;
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_;
std::vector<uint8_t> character_rom_;
std::vector<uint8_t> basic_rom_;
@@ -731,8 +732,7 @@ class ConcreteMachine:
Cycles cycles_since_mos6560_update_;
Vic6560BusHandler mos6560_bus_handler_;
MOS::MOS6560::OutputMode output_mode_;
std::unique_ptr<MOS::MOS6560::MOS6560<Vic6560BusHandler>> mos6560_;
MOS::MOS6560::MOS6560<Vic6560BusHandler> mos6560_;
std::shared_ptr<UserPortVIA> user_port_via_port_handler_;
std::shared_ptr<KeyboardVIA> keyboard_via_port_handler_;
std::shared_ptr<SerialPort> serial_port_;

View File

@@ -11,10 +11,13 @@
#include "../Configurable/Configurable.hpp"
#include "../Activity/Source.hpp"
#include "MediaTarget.hpp"
#include "CRTMachine.hpp"
#include "JoystickMachine.hpp"
#include "KeyboardMachine.hpp"
#include "MediaTarget.hpp"
#include "MouseMachine.hpp"
#include "Utility/Typer.hpp"
namespace Machine {
@@ -31,6 +34,7 @@ struct DynamicMachine {
virtual CRTMachine::Machine *crt_machine() = 0;
virtual JoystickMachine::Machine *joystick_machine() = 0;
virtual KeyboardMachine::Machine *keyboard_machine() = 0;
virtual MouseMachine::Machine *mouse_machine() = 0;
virtual MediaTarget::Machine *media_target() = 0;
/*!

View File

@@ -34,7 +34,7 @@ namespace Electron {
std::vector<std::unique_ptr<Configurable::Option>> get_options() {
return Configurable::standard_options(
static_cast<Configurable::StandardOptions>(Configurable::DisplayRGB | Configurable::DisplayComposite | Configurable::QuickLoadTape)
static_cast<Configurable::StandardOptions>(Configurable::DisplayRGB | Configurable::DisplayCompositeColour | Configurable::QuickLoadTape)
);
}
@@ -42,7 +42,7 @@ class ConcreteMachine:
public Machine,
public CRTMachine::Machine,
public MediaTarget::Machine,
public KeyboardMachine::Machine,
public KeyboardMachine::MappedMachine,
public Configurable::Device,
public CPU::MOS6502::BusHandler,
public Tape::Delegate,
@@ -51,6 +51,7 @@ class ConcreteMachine:
public:
ConcreteMachine(const Analyser::Static::Acorn::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
m6502_(*this),
video_output_(ram_),
sound_generator_(audio_queue_),
speaker_(sound_generator_) {
memset(key_states_, 0, sizeof(key_states_));
@@ -61,17 +62,22 @@ class ConcreteMachine:
set_clock_rate(2000000);
speaker_.set_input_rate(2000000 / SoundGenerator::clock_rate_divider);
speaker_.set_high_frequency_cutoff(7000);
std::vector<std::string> rom_names = {"basic.rom", "os.rom"};
const std::string machine_name = "Electron";
std::vector<ROMMachine::ROM> required_roms = {
{machine_name, "the Acorn BASIC II ROM", "basic.rom", 16*1024, 0x79434781},
{machine_name, "the Electron MOS ROM", "os.rom", 16*1024, 0xbf63fb1f}
};
if(target.has_adfs) {
rom_names.push_back("ADFS-E00_1.rom");
rom_names.push_back("ADFS-E00_2.rom");
required_roms.emplace_back(machine_name, "the E00 ADFS ROM, first slot", "ADFS-E00_1.rom", 16*1024, 0x51523993);
required_roms.emplace_back(machine_name, "the E00 ADFS ROM, second slot", "ADFS-E00_2.rom", 16*1024, 0x8d17de0e);
}
const size_t dfs_rom_position = rom_names.size();
const size_t dfs_rom_position = required_roms.size();
if(target.has_dfs) {
rom_names.push_back("DFS-1770-2.20.rom");
required_roms.emplace_back(machine_name, "the 1770 DFS ROM", "DFS-1770-2.20.rom", 16*1024, 0xf3dc9bc5);
}
const auto roms = rom_fetcher("Electron", rom_names);
const auto roms = rom_fetcher(required_roms);
for(const auto &rom: roms) {
if(!rom) {
@@ -160,7 +166,7 @@ class ConcreteMachine:
// 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);
cycles += video_output_.get_cycles_until_next_ram_availability(cycles_since_display_update_.as_int() + 1);
} else {
switch(address & 0xff0f) {
case 0xfe00:
@@ -198,8 +204,8 @@ class ConcreteMachine:
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();
video_output_.set_register(address, *value);
video_access_range_ = video_output_.get_memory_access_range();
queue_next_display_interrupt();
}
break;
@@ -373,16 +379,12 @@ class ConcreteMachine:
audio_queue_.perform();
}
void setup_output(float aspect_ratio) override final {
video_output_.reset(new VideoOutput(ram_));
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override final {
video_output_.set_scan_target(scan_target);
}
void close_output() override final {
video_output_.reset();
}
Outputs::CRT::CRT *get_crt() override final {
return video_output_->get_crt();
void set_display_type(Outputs::Display::DisplayType display_type) override {
video_output_.set_display_type(display_type);
}
Outputs::Speaker::Speaker *get_speaker() override final {
@@ -436,7 +438,7 @@ class ConcreteMachine:
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);
Configurable::append_display_selection(selection_set, Configurable::Display::CompositeColour);
return selection_set;
}
@@ -508,12 +510,12 @@ class ConcreteMachine:
// MARK: - Work deferral updates.
inline void update_display() {
if(cycles_since_display_update_ > 0) {
video_output_->run_for(cycles_since_display_update_.flush());
video_output_.run_for(cycles_since_display_update_.flush<Cycles>());
}
}
inline void queue_next_display_interrupt() {
VideoOutput::Interrupt next_interrupt = video_output_->get_next_interrupt();
VideoOutput::Interrupt next_interrupt = video_output_.get_next_interrupt();
cycles_until_display_interrupt_ = next_interrupt.cycles;
next_display_interrupt_ = next_interrupt.interrupt;
}
@@ -541,7 +543,7 @@ class ConcreteMachine:
m6502_.set_irq_line(interrupt_status_ & 1);
}
CPU::MOS6502::Processor<ConcreteMachine, false> m6502_;
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_;
// Things that directly constitute the memory map.
uint8_t roms_[16][16384];
@@ -583,7 +585,7 @@ class ConcreteMachine:
int shift_restart_counter_ = 0;
// Outputs
std::unique_ptr<VideoOutput> video_output_;
VideoOutput video_output_;
Concurrency::DeferringAsyncTaskQueue audio_queue_;
SoundGenerator sound_generator_;

View File

@@ -31,7 +31,6 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
BIND(Quote, KeyColon);
BIND(Escape, KeyEscape);
BIND(Equals, KeyBreak);
BIND(F12, KeyBreak);
BIND(Left, KeyLeft); BIND(Right, KeyRight); BIND(Up, KeyUp); BIND(Down, KeyDown);
@@ -56,10 +55,10 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
}
uint16_t *CharacterMapper::sequence_for_character(char character) {
#define KEYS(...) {__VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
#define SHIFT(...) {KeyShift, __VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
#define CTRL(...) {KeyControl, __VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
#define X {KeyboardMachine::Machine::KeyNotMapped}
#define KEYS(...) {__VA_ARGS__, KeyboardMachine::MappedMachine::KeyEndSequence}
#define SHIFT(...) {KeyShift, __VA_ARGS__, KeyboardMachine::MappedMachine::KeyEndSequence}
#define CTRL(...) {KeyControl, __VA_ARGS__, KeyboardMachine::MappedMachine::KeyEndSequence}
#define X {KeyboardMachine::MappedMachine::KeyNotMapped}
static KeySequence key_sequences[] = {
/* NUL */ X, /* SOH */ X,
/* STX */ X, /* ETX */ X,

View File

@@ -33,8 +33,8 @@ enum Key: uint16_t {
KeyBreak = 0xfffd,
};
struct KeyboardMapper: public KeyboardMachine::Machine::KeyboardMapper {
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key);
struct KeyboardMapper: public KeyboardMachine::MappedMachine::KeyboardMapper {
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) override;
};
struct CharacterMapper: public ::Utility::CharacterMapper {

View File

@@ -34,41 +34,30 @@ 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));
}
};
}
// MARK: - Lifecycle
VideoOutput::VideoOutput(uint8_t *memory) : ram_(memory) {
VideoOutput::VideoOutput(uint8_t *memory) :
ram_(memory),
crt_(crt_cycles_per_line,
1,
Outputs::Display::Type::PAL50,
Outputs::Display::InputDataType::Red1Green1Blue1) {
memset(palette_, 0xf, sizeof(palette_));
setup_screen_map();
setup_base_address();
crt_.reset(new Outputs::CRT::CRT(crt_cycles_per_line, 8, Outputs::CRT::DisplayType::PAL50, 1));
crt_->set_rgb_sampling_function(
"vec3 rgb_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate)"
"{"
"uint texValue = texture(sampler, coordinate).r;"
"texValue >>= 4 - (int(icoordinate.x) & 4);"
"return vec3( uvec3(texValue) & uvec3(4u, 2u, 1u));"
"}");
crt_->set_integer_coordinate_multiplier(8.0f);
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 - 1, 256, (first_graphics_cycle+1) * crt_cycles_multiplier, 80 * crt_cycles_multiplier, 4.0f / 3.0f));
crt_.set_visible_area(crt_.get_rect_for_area(first_graphics_line - 1, 256, (first_graphics_cycle+1) * crt_cycles_multiplier, 80 * crt_cycles_multiplier, 4.0f / 3.0f));
}
// MARK: - CRT getter
void VideoOutput::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
crt_.set_scan_target(scan_target);
}
Outputs::CRT::CRT *VideoOutput::get_crt() {
return crt_.get();
void VideoOutput::set_display_type(Outputs::Display::DisplayType display_type) {
crt_.set_display_type(display_type);
}
// MARK: - Display update methods
@@ -98,33 +87,33 @@ void VideoOutput::start_pixel_line() {
}
void VideoOutput::end_pixel_line() {
if(current_output_target_) {
const unsigned int data_length = static_cast<unsigned int>(current_output_target_ - initial_output_target_);
crt_->output_data(data_length * current_output_divider_, data_length);
const int data_length = int(current_output_target_ - initial_output_target_);
if(data_length) {
crt_.output_data(data_length * current_output_divider_, size_t(data_length));
}
current_character_row_++;
}
void VideoOutput::output_pixels(unsigned int number_of_cycles) {
void VideoOutput::output_pixels(int number_of_cycles) {
if(!number_of_cycles) return;
if(is_blank_line_) {
crt_->output_blank(number_of_cycles * crt_cycles_multiplier);
crt_.output_blank(number_of_cycles * crt_cycles_multiplier);
} else {
unsigned int divider = 1;
int divider = 1;
switch(screen_mode_) {
case 0: case 3: divider = 2; break;
case 1: case 4: case 6: divider = 4; break;
case 2: case 5: divider = 8; break;
case 0: case 3: divider = 1; break;
case 1: case 4: case 6: divider = 2; break;
case 2: case 5: divider = 4; break;
}
if(!initial_output_target_ || divider != current_output_divider_) {
if(current_output_target_) {
const unsigned int data_length = static_cast<unsigned int>(current_output_target_ - initial_output_target_);
crt_->output_data(data_length * current_output_divider_, data_length);
const int data_length = int(current_output_target_ - initial_output_target_);
if(data_length) {
crt_.output_data(data_length * current_output_divider_, size_t(data_length));
}
current_output_divider_ = divider;
initial_output_target_ = current_output_target_ = crt_->allocate_write_area(640 / current_output_divider_, 4);
initial_output_target_ = current_output_target_ = crt_.begin_data(size_t(640 / current_output_divider_), size_t(8 / divider));
}
#define get_pixel() \
@@ -139,95 +128,95 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) {
if(initial_output_target_) {
while(number_of_cycles--) {
get_pixel();
*reinterpret_cast<uint32_t *>(current_output_target_) = palette_tables_.eighty1bpp[last_pixel_byte_];
current_output_target_ += 4;
*reinterpret_cast<uint64_t *>(current_output_target_) = palette_tables_.eighty1bpp[last_pixel_byte_];
current_output_target_ += 8;
current_pixel_column_++;
}
} else current_output_target_ += 4*number_of_cycles;
} else current_output_target_ += 8*number_of_cycles;
break;
case 1:
if(initial_output_target_) {
while(number_of_cycles--) {
get_pixel();
*reinterpret_cast<uint16_t *>(current_output_target_) = palette_tables_.eighty2bpp[last_pixel_byte_];
current_output_target_ += 2;
*reinterpret_cast<uint32_t *>(current_output_target_) = palette_tables_.eighty2bpp[last_pixel_byte_];
current_output_target_ += 4;
current_pixel_column_++;
}
} else current_output_target_ += 2*number_of_cycles;
} else current_output_target_ += 4*number_of_cycles;
break;
case 2:
if(initial_output_target_) {
while(number_of_cycles--) {
get_pixel();
*current_output_target_ = palette_tables_.eighty4bpp[last_pixel_byte_];
current_output_target_ += 1;
*reinterpret_cast<uint16_t *>(current_output_target_) = palette_tables_.eighty4bpp[last_pixel_byte_];
current_output_target_ += 2;
current_pixel_column_++;
}
} else current_output_target_ += number_of_cycles;
} else current_output_target_ += 2*number_of_cycles;
break;
case 4: case 6:
if(initial_output_target_) {
if(current_pixel_column_&1) {
last_pixel_byte_ <<= 4;
*reinterpret_cast<uint16_t *>(current_output_target_) = palette_tables_.forty1bpp[last_pixel_byte_];
current_output_target_ += 2;
*reinterpret_cast<uint32_t *>(current_output_target_) = palette_tables_.forty1bpp[last_pixel_byte_];
current_output_target_ += 4;
number_of_cycles--;
current_pixel_column_++;
}
while(number_of_cycles > 1) {
get_pixel();
*reinterpret_cast<uint16_t *>(current_output_target_) = palette_tables_.forty1bpp[last_pixel_byte_];
current_output_target_ += 2;
*reinterpret_cast<uint32_t *>(current_output_target_) = palette_tables_.forty1bpp[last_pixel_byte_];
current_output_target_ += 4;
last_pixel_byte_ <<= 4;
*reinterpret_cast<uint16_t *>(current_output_target_) = palette_tables_.forty1bpp[last_pixel_byte_];
current_output_target_ += 2;
*reinterpret_cast<uint32_t *>(current_output_target_) = palette_tables_.forty1bpp[last_pixel_byte_];
current_output_target_ += 4;
number_of_cycles -= 2;
current_pixel_column_+=2;
}
if(number_of_cycles) {
get_pixel();
*reinterpret_cast<uint16_t *>(current_output_target_) = palette_tables_.forty1bpp[last_pixel_byte_];
current_output_target_ += 2;
*reinterpret_cast<uint32_t *>(current_output_target_) = palette_tables_.forty1bpp[last_pixel_byte_];
current_output_target_ += 4;
current_pixel_column_++;
}
} else current_output_target_ += 2 * number_of_cycles;
} else current_output_target_ += 4 * number_of_cycles;
break;
case 5:
if(initial_output_target_) {
if(current_pixel_column_&1) {
last_pixel_byte_ <<= 2;
*current_output_target_ = palette_tables_.forty2bpp[last_pixel_byte_];
current_output_target_ += 1;
*reinterpret_cast<uint16_t *>(current_output_target_) = palette_tables_.forty2bpp[last_pixel_byte_];
current_output_target_ += 2;
number_of_cycles--;
current_pixel_column_++;
}
while(number_of_cycles > 1) {
get_pixel();
*current_output_target_ = palette_tables_.forty2bpp[last_pixel_byte_];
current_output_target_ += 1;
*reinterpret_cast<uint16_t *>(current_output_target_) = palette_tables_.forty2bpp[last_pixel_byte_];
current_output_target_ += 2;
last_pixel_byte_ <<= 2;
*current_output_target_ = palette_tables_.forty2bpp[last_pixel_byte_];
current_output_target_ += 1;
*reinterpret_cast<uint16_t *>(current_output_target_) = palette_tables_.forty2bpp[last_pixel_byte_];
current_output_target_ += 2;
number_of_cycles -= 2;
current_pixel_column_+=2;
}
if(number_of_cycles) {
get_pixel();
*current_output_target_ = palette_tables_.forty2bpp[last_pixel_byte_];
current_output_target_ += 1;
*reinterpret_cast<uint16_t *>(current_output_target_) = palette_tables_.forty2bpp[last_pixel_byte_];
current_output_target_ += 2;
current_pixel_column_++;
}
} else current_output_target_ += number_of_cycles;
} else current_output_target_ += 2*number_of_cycles;
break;
}
@@ -241,16 +230,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(static_cast<unsigned int>(time_left_in_action));
if(screen_map_[screen_map_pointer_].type == DrawAction::Pixels) output_pixels(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(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;
case DrawAction::Sync: crt_.output_sync(draw_action_length * crt_cycles_multiplier); break;
case DrawAction::ColourBurst: crt_.output_default_colour_burst(draw_action_length * crt_cycles_multiplier); break;
case DrawAction::Blank: crt_.output_blank(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;
@@ -310,27 +299,37 @@ void VideoOutput::set_register(int address, uint8_t value) {
}
// regenerate all palette tables for now
#define pack(a, b) static_cast<uint8_t>((a << 4) | (b))
for(int byte = 0; byte < 256; 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[0] = palette_[(byte&0x80) >> 4];
target[1] = palette_[(byte&0x40) >> 3];
target[2] = palette_[(byte&0x20) >> 2];
target[3] = palette_[(byte&0x10) >> 1];
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[0] = palette_[((byte&0x80) >> 4) | ((byte&0x08) >> 2)];
target[1] = palette_[((byte&0x40) >> 3) | ((byte&0x04) >> 1)];
target[2] = palette_[((byte&0x20) >> 2) | ((byte&0x02) >> 0)];
target[3] = palette_[((byte&0x10) >> 1) | ((byte&0x01) << 1)];
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]);
target[3] = pack(palette_[(byte&0x02) << 2], palette_[(byte&0x01) << 3]);
target[0] = palette_[(byte&0x80) >> 4];
target[1] = palette_[(byte&0x40) >> 3];
target[2] = palette_[(byte&0x20) >> 2];
target[3] = palette_[(byte&0x10) >> 1];
target[4] = palette_[(byte&0x08) >> 0];
target[5] = palette_[(byte&0x04) << 1];
target[6] = palette_[(byte&0x02) << 2];
target[7] = palette_[(byte&0x01) << 3];
palette_tables_.forty2bpp[byte] = pack( palette_[((byte&0x80) >> 4) | ((byte&0x08) >> 2)], palette_[((byte&0x40) >> 3) | ((byte&0x04) >> 1)]);
palette_tables_.eighty4bpp[byte] = pack( palette_[((byte&0x80) >> 4) | ((byte&0x20) >> 3) | ((byte&0x08) >> 2) | ((byte&0x02) >> 1)],
palette_[((byte&0x40) >> 3) | ((byte&0x10) >> 2) | ((byte&0x04) >> 1) | ((byte&0x01) >> 0)]);
target = reinterpret_cast<uint8_t *>(&palette_tables_.forty2bpp[byte]);
target[0] = palette_[((byte&0x80) >> 4) | ((byte&0x08) >> 2)];
target[1] = palette_[((byte&0x40) >> 3) | ((byte&0x04) >> 1)];
target = reinterpret_cast<uint8_t *>(&palette_tables_.eighty4bpp[byte]);
target[0] = palette_[((byte&0x80) >> 4) | ((byte&0x20) >> 3) | ((byte&0x08) >> 2) | ((byte&0x02) >> 1)];
target[1] = palette_[((byte&0x40) >> 3) | ((byte&0x10) >> 2) | ((byte&0x04) >> 1) | ((byte&0x01) >> 0)];
}
#undef pack
}
break;
}
@@ -404,7 +403,7 @@ unsigned int VideoOutput::get_cycles_until_next_ram_availability(int from_time)
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 {

View File

@@ -13,6 +13,8 @@
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "Interrupts.hpp"
#include <vector>
namespace Electron {
/*!
@@ -25,17 +27,21 @@ namespace Electron {
class VideoOutput {
public:
/*!
Instantiates a VideoOutput that will read its pixels from @c memory. The pointer supplied
should be to address 0 in the unexpanded Electron's memory map.
Instantiates a VideoOutput that will read its pixels from @c memory.
The pointer supplied should be to address 0 in the unexpanded Electron's memory map.
*/
VideoOutput(uint8_t *memory);
/// @returns the CRT to which output is being painted.
Outputs::CRT::CRT *get_crt();
/// Produces the next @c cycles of video output.
void run_for(const Cycles cycles);
/// Sets the destination for output.
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
/// Sets the type of output.
void set_display_type(Outputs::Display::DisplayType);
/*!
Writes @c value to the register at @c address. May mutate the results of @c get_next_interrupt,
@c get_cycles_until_next_ram_availability and @c get_memory_access_range.
@@ -77,7 +83,7 @@ class VideoOutput {
private:
inline void start_pixel_line();
inline void end_pixel_line();
inline void output_pixels(unsigned int number_of_cycles);
inline void output_pixels(int number_of_cycles);
inline void setup_base_address();
int output_position_ = 0;
@@ -90,11 +96,11 @@ class VideoOutput {
uint8_t *ram_;
struct {
uint16_t forty1bpp[256];
uint8_t forty2bpp[256];
uint32_t eighty1bpp[256];
uint16_t eighty2bpp[256];
uint8_t eighty4bpp[256];
uint32_t forty1bpp[256];
uint16_t forty2bpp[256];
uint64_t eighty1bpp[256];
uint32_t eighty2bpp[256];
uint16_t eighty4bpp[256];
} palette_tables_;
// Display generation.
@@ -109,9 +115,8 @@ class VideoOutput {
// CRT output
uint8_t *current_output_target_ = nullptr;
uint8_t *initial_output_target_ = nullptr;
unsigned int current_output_divider_ = 1;
std::unique_ptr<Outputs::CRT::CRT> crt_;
int current_output_divider_ = 1;
Outputs::CRT::CRT crt_;
struct DrawAction {
enum Type {

View File

@@ -10,27 +10,27 @@
using namespace KeyboardMachine;
Machine::Machine() {
MappedMachine::MappedMachine() {
keyboard_.set_delegate(this);
}
void Machine::keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) {
void MappedMachine::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) {
void MappedMachine::reset_all_keys(Inputs::Keyboard *keyboard) {
// TODO: unify naming.
clear_all_keys();
}
Inputs::Keyboard &Machine::get_keyboard() {
Inputs::Keyboard &MappedMachine::get_keyboard() {
return keyboard_;
}
void Machine::type_string(const std::string &) {
}
Machine::KeyboardMapper *Machine::get_keyboard_mapper() {
MappedMachine::KeyboardMapper *MappedMachine::get_keyboard_mapper() {
return nullptr;
}

View File

@@ -33,13 +33,10 @@ struct KeyActions {
};
/*!
Describes the full functionality of being an emulated machine with a keyboard: not just being
able to receive key actions, but being able to vend a generic keyboard and a keyboard mapper.
Describes an emulated machine which exposes a keyboard and accepts a typed string.
*/
class Machine: public Inputs::Keyboard::Delegate, public KeyActions {
class Machine: public KeyActions {
public:
Machine();
/*!
Causes the machine to attempt to type the supplied string.
@@ -50,7 +47,16 @@ class Machine: public Inputs::Keyboard::Delegate, public KeyActions {
/*!
Provides a destination for keyboard input.
*/
virtual Inputs::Keyboard &get_keyboard();
virtual Inputs::Keyboard &get_keyboard() = 0;
};
/*!
Provides a base class for machines that want to provide a keyboard mapper,
allowing automatic mapping from keyboard inputs to KeyActions.
*/
class MappedMachine: public Inputs::Keyboard::Delegate, public Machine {
public:
MappedMachine();
/*!
A keyboard mapper attempts to provide a physical mapping between host keys and emulated keys.
@@ -76,6 +82,12 @@ class Machine: public Inputs::Keyboard::Delegate, public KeyActions {
*/
virtual KeyboardMapper *get_keyboard_mapper();
/*!
Provides a keyboard that obtains this machine's keyboard mapper, maps
the key and supplies it via the KeyActions.
*/
virtual Inputs::Keyboard &get_keyboard() override;
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;

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