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

Compare commits

...

645 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
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
170 changed files with 23295 additions and 1503 deletions

View File

@@ -59,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();

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;

View File

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

View File

@@ -290,6 +290,7 @@ Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(const Media &medi
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

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

@@ -21,6 +21,7 @@
#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"
@@ -35,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"
@@ -85,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") {
@@ -129,16 +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("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
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
@@ -173,9 +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::Sega) Append(Sega);
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

@@ -1,26 +1,26 @@
//
// ClockDeferrer.hpp
// DeferredQueue.hpp
// Clock Signal
//
// Created by Thomas Harte on 23/08/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#ifndef ClockDeferrer_h
#define ClockDeferrer_h
#ifndef DeferredQueue_h
#define DeferredQueue_h
#include <functional>
#include <vector>
/*!
A ClockDeferrer maintains a list of ordered actions and the times at which
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 ClockDeferrer {
template <typename TimeUnit> class DeferredQueue {
public:
/// Constructs a ClockDeferrer that will call target(period) in between deferred actions.
ClockDeferrer(std::function<void(TimeUnit)> &&target) : target_(std::move(target)) {}
/// 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.
@@ -79,4 +79,4 @@ template <typename TimeUnit> class ClockDeferrer {
std::vector<DeferredAction> pending_actions_;
};
#endif /* ClockDeferrer_h */
#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

@@ -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,11 +102,39 @@ 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();
};
}

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

@@ -11,29 +11,58 @@
namespace MOS {
namespace MOS6522 {
template <typename T> void MOS6522<T>::set_register(int address, uint8_t value) {
address &= 0xf;
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;
@@ -59,31 +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:
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: LOG("Unimplemented control line mode " << int((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: LOG("Unimplemented control line mode " << int((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:
@@ -102,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();
@@ -132,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;
@@ -145,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);
}
@@ -158,9 +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;
}
};
}

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

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);
@@ -211,7 +211,7 @@ 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.

View File

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

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

@@ -6,8 +6,8 @@
// 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>
@@ -75,4 +75,4 @@ class Keyboard {
}
#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

@@ -781,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];

View File

@@ -8,33 +8,34 @@
#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 "../../../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 "../../../Processors/6502/6502.hpp"
#include "../../../Components/AudioToggle/AudioToggle.hpp"
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#include "../../Outputs/Log.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 "../../../Analyser/Static/AppleII/Target.hpp"
#include "../../../ClockReceiver/ForceInline.hpp"
#include "../../../Configurable/StandardOptions.hpp"
#include <algorithm>
#include <array>
#include <memory>
namespace AppleII {
namespace Apple {
namespace II {
std::vector<std::unique_ptr<Configurable::Option>> get_options() {
return Configurable::standard_options(
@@ -51,12 +52,12 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
public CPU::MOS6502::BusHandler,
public Inputs::Keyboard,
public Configurable::Device,
public AppleII::Machine,
public Apple::II::Machine,
public Activity::Source,
public JoystickMachine::Machine,
public AppleII::Card::Delegate {
public Apple::II::Card::Delegate {
private:
struct VideoBusHandler : public AppleII::Video::BusHandler {
struct VideoBusHandler : public Apple::II::Video::BusHandler {
public:
VideoBusHandler(uint8_t *ram, uint8_t *aux_ram) : ram_(ram), aux_ram_(aux_ram) {}
@@ -71,12 +72,12 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
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_;
AppleII::Video::Video<VideoBusHandler, is_iie()> video_;
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());
video_.run_for(cycles_since_video_update_.flush<Cycles>());
}
static const int audio_divider = 8;
void update_audio() {
@@ -109,28 +110,28 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
Cycles cycles_since_audio_update_;
// MARK: - Cards
std::array<std::unique_ptr<AppleII::Card>, 7> cards_;
std::array<std::unique_ptr<Apple::II::Card>, 7> cards_;
Cycles cycles_since_card_update_;
std::vector<AppleII::Card *> every_cycle_cards_;
std::vector<AppleII::Card *> just_in_time_cards_;
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, AppleII::Card *card) {
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(AppleII::Card *card) {
bool is_every_cycle_card(Apple::II::Card *card) {
return !card->get_select_constraints();
}
void pick_card_messaging_group(AppleII::Card *card) {
void pick_card_messaging_group(Apple::II::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_;
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);
@@ -138,12 +139,12 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
intended.push_back(card);
}
void card_did_change_select_constraints(AppleII::Card *card) override {
void card_did_change_select_constraints(Apple::II::Card *card) override {
pick_card_messaging_group(card);
}
AppleII::DiskIICard *diskii_card() {
return dynamic_cast<AppleII::DiskIICard *>(cards_[5].get());
Apple::II::DiskIICard *diskii_card() {
return dynamic_cast<Apple::II::DiskIICard *>(cards_[5].get());
}
// MARK: - Memory Map.
@@ -346,30 +347,39 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
// Pick the required ROMs.
using Target = Analyser::Static::AppleII::Target;
std::vector<std::string> rom_names;
const std::string machine_name = "AppleII";
std::vector<ROMMachine::ROM> rom_descriptions;
size_t rom_size = 12*1024;
switch(target.model) {
default:
rom_names.push_back("apple2-character.rom");
rom_names.push_back("apple2o.rom");
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_names.push_back("apple2-character.rom");
rom_names.push_back("apple2.rom");
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_names.push_back("apple2eu-character.rom");
rom_names.push_back("apple2eu.rom");
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_names.push_back("apple2e-character.rom");
rom_names.push_back("apple2e.rom");
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("AppleII", rom_names);
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;
}
@@ -381,11 +391,6 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
video_.set_character_rom(*roms[0]);
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. 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();
@@ -700,7 +705,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
// 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;
Apple::II::Card::Select select = Apple::II::Card::None;
if(address >= 0xc100) {
/*
@@ -708,20 +713,20 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
0xCn00 to 0xCnff: card n.
*/
card_number = (address - 0xc100) >> 8;
select = AppleII::Card::Device;
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 = AppleII::Card::IO;
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);
AppleII::Card *const target = cards_[static_cast<size_t>(card_number)].get();
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);
@@ -732,7 +737,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
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,
(card == target) ? select : Apple::II::Card::None,
is_read, address, value);
}
has_updated_cards = true;
@@ -744,7 +749,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
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);
card->perform_bus_operation(Apple::II::Card::None, is_read, address, value);
}
}
@@ -818,7 +823,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
// MARK:: Configuration options.
std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
return AppleII::get_options();
return Apple::II::get_options();
}
void set_selections(const Configurable::SelectionSet &selections_by_option) override {
@@ -860,9 +865,10 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
}
};
}
}
using namespace AppleII;
using namespace Apple::II;
Machine *Machine::AppleII(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
using Target = Analyser::Static::AppleII::Target;

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

@@ -8,7 +8,7 @@
#include "Video.hpp"
using namespace AppleII::Video;
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),

View File

@@ -9,14 +9,15 @@
#ifndef Video_hpp
#define Video_hpp
#include "../../Outputs/CRT/CRT.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../ClockReceiver/ClockDeferrer.hpp"
#include "../../../Outputs/CRT/CRT.hpp"
#include "../../../ClockReceiver/ClockReceiver.hpp"
#include "../../../ClockReceiver/DeferredQueue.hpp"
#include <array>
#include <vector>
namespace AppleII {
namespace Apple {
namespace II {
namespace Video {
class BusHandler {
@@ -250,8 +251,8 @@ class VideoBase {
*/
void output_fat_low_resolution(uint8_t *target, const uint8_t *source, size_t length, int column, int row) const;
// Maintain a ClockDeferrer for delayed mode switches.
ClockDeferrer<Cycles> deferrer_;
// Maintain a DeferredQueue for delayed mode switches.
DeferredQueue<Cycles> deferrer_;
};
template <class BusHandler, bool is_iie> class Video: public VideoBase {
@@ -600,6 +601,7 @@ template <class BusHandler, bool is_iie> class Video: public VideoBase {
BusHandler &bus_handler_;
};
}
}
}

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

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

@@ -19,6 +19,7 @@
#include "../../Configurable/StandardOptions.hpp"
#include "../../ClockReceiver/ForceInline.hpp"
#include "../../ClockReceiver/JustInTime.hpp"
#include "../../Outputs/Speaker/Implementation/CompoundSource.hpp"
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
@@ -131,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;
@@ -170,7 +170,7 @@ class ConcreteMachine:
}
// ColecoVisions have composite output only.
vdp_.set_display_type(Outputs::Display::DisplayType::CompositeColour);
vdp_->set_display_type(Outputs::Display::DisplayType::CompositeColour);
}
~ConcreteMachine() {
@@ -182,11 +182,11 @@ class ConcreteMachine:
}
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override {
vdp_.set_scan_target(scan_target);
vdp_->set_scan_target(scan_target);
}
void set_display_type(Outputs::Display::DisplayType display_type) override {
vdp_.set_display_type(display_type);
vdp_->set_display_type(display_type);
}
Outputs::Speaker::Speaker *get_speaker() override {
@@ -211,7 +211,7 @@ class ConcreteMachine:
}
const HalfCycles length = cycle.length + penalty;
time_since_vdp_update_ += length;
vdp_ += length;
time_since_sn76489_update_ += length;
// Act only if necessary.
@@ -256,10 +256,9 @@ class ConcreteMachine:
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();
*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: {
@@ -300,10 +299,9 @@ class ConcreteMachine:
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();
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:
@@ -355,7 +353,7 @@ class ConcreteMachine:
}
void flush() {
update_video();
vdp_.flush();
update_audio();
audio_queue_.perform();
}
@@ -397,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_;
TI::TMS::TMS9918 vdp_;
JustInTimeActor<TI::TMS::TMS9918, HalfCycles> vdp_;
Concurrency::DeferringAsyncTaskQueue audio_queue_;
TI::SN76489 sn76489_;
@@ -425,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

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

View File

@@ -323,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) {
@@ -699,7 +702,7 @@ 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<CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_;

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

@@ -62,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) {
@@ -505,7 +510,7 @@ 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>());
}
}

View File

@@ -34,7 +34,7 @@ enum Key: uint16_t {
};
struct KeyboardMapper: public KeyboardMachine::MappedMachine::KeyboardMapper {
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key);
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) override;
};
struct CharacterMapper: public ::Utility::CharacterMapper {

View File

@@ -45,6 +45,7 @@
#include "../../Configurable/StandardOptions.hpp"
#include "../../ClockReceiver/ForceInline.hpp"
#include "../../ClockReceiver/JustInTime.hpp"
#include "../../Analyser/Static/MSX/Target.hpp"
@@ -173,33 +174,37 @@ class ConcreteMachine:
mixer_.set_relative_volumes({0.5f, 0.1f, 0.4f});
// Install the proper TV standard and select an ideal BIOS name.
std::vector<std::string> rom_names = {"msx.rom"};
const std::string machine_name = "MSX";
std::vector<ROMMachine::ROM> required_roms = {
{machine_name, "any MSX BIOS", "msx.rom", 32*1024, 0x94ee12f3}
};
bool is_ntsc = true;
uint8_t character_generator = 1; /* 0 = Japan, 1 = USA, etc, 2 = USSR */
uint8_t date_format = 1; /* 0 = Y/M/D, 1 = M/D/Y, 2 = D/M/Y */
uint8_t keyboard = 1; /* 0 = Japan, 1 = USA, 2 = France, 3 = UK, 4 = Germany, 5 = USSR, 6 = Spain */
// TODO: CRCs below are incomplete, at best.
switch(target.region) {
case Target::Region::Japan:
rom_names.push_back("msx-japanese.rom");
vdp_.set_tv_standard(TI::TMS::TVStandard::NTSC);
required_roms.emplace_back(machine_name, "a Japanese MSX BIOS", "msx-japanese.rom", 32*1024, 0xee229390);
vdp_->set_tv_standard(TI::TMS::TVStandard::NTSC);
is_ntsc = true;
character_generator = 0;
date_format = 0;
break;
case Target::Region::USA:
rom_names.push_back("msx-american.rom");
vdp_.set_tv_standard(TI::TMS::TVStandard::NTSC);
required_roms.emplace_back(machine_name, "an American MSX BIOS", "msx-american.rom", 32*1024, 0);
vdp_->set_tv_standard(TI::TMS::TVStandard::NTSC);
is_ntsc = true;
character_generator = 1;
date_format = 1;
break;
case Target::Region::Europe:
rom_names.push_back("msx-european.rom");
vdp_.set_tv_standard(TI::TMS::TVStandard::PAL);
required_roms.emplace_back(machine_name, "a European MSX BIOS", "msx-european.rom", 32*1024, 0);
vdp_->set_tv_standard(TI::TMS::TVStandard::PAL);
is_ntsc = false;
character_generator = 1;
@@ -209,10 +214,12 @@ class ConcreteMachine:
// Fetch the necessary ROMs; try the region-specific ROM first,
// but failing that fall back on patching the main one.
size_t disk_index = 0;
if(target.has_disk_drive) {
rom_names.push_back("disk.rom");
disk_index = required_roms.size();
required_roms.emplace_back(machine_name, "the MSX-DOS ROM", "disk.rom", 16*1024, 0x721f61df);
}
const auto roms = rom_fetcher("MSX", rom_names);
const auto roms = rom_fetcher(required_roms);
if((!roms[0] && !roms[1]) || (target.has_disk_drive && !roms[2])) {
throw ROMMachine::Error::MissingROMs;
@@ -227,7 +234,6 @@ class ConcreteMachine:
memory_slots_[0].source = std::move(*roms[0]);
memory_slots_[0].source.resize(32768);
memory_slots_[0].source[0x2b] = uint8_t(
(is_ntsc ? 0x00 : 0x80) |
(date_format << 4) |
@@ -252,7 +258,7 @@ class ConcreteMachine:
// Add a disk cartridge if any disks were supplied.
if(target.has_disk_drive) {
memory_slots_[2].set_handler(new DiskROM(memory_slots_[2].source));
memory_slots_[2].source = std::move(*roms[1]);
memory_slots_[2].source = std::move(*roms[disk_index]);
memory_slots_[2].source.resize(16384);
map(2, 0, 0x4000, 0x2000);
@@ -273,11 +279,11 @@ class ConcreteMachine:
}
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override {
vdp_.set_scan_target(scan_target);
vdp_->set_scan_target(scan_target);
}
void set_display_type(Outputs::Display::DisplayType display_type) override {
vdp_.set_display_type(display_type);
vdp_->set_display_type(display_type);
}
Outputs::Speaker::Speaker *get_speaker() override {
@@ -406,7 +412,7 @@ class ConcreteMachine:
// but otherwise runs without pause.
const HalfCycles addition((cycle.operation == CPU::Z80::PartialMachineCycle::ReadOpcode) ? 2 : 0);
const HalfCycles total_length = addition + cycle.length;
time_since_vdp_update_ += total_length;
vdp_ += total_length;
time_since_ay_update_ += total_length;
memory_slots_[0].cycles_since_update += total_length;
memory_slots_[1].cycles_since_update += total_length;
@@ -484,7 +490,7 @@ class ConcreteMachine:
*cycle.value = read_pointers_[address >> 13][address & 8191];
} else {
int slot_hit = (paged_memory_ >> ((address >> 14) * 2)) & 3;
memory_slots_[slot_hit].handler->run_for(memory_slots_[slot_hit].cycles_since_update.flush());
memory_slots_[slot_hit].handler->run_for(memory_slots_[slot_hit].cycles_since_update.flush<HalfCycles>());
*cycle.value = memory_slots_[slot_hit].handler->read(address);
}
break;
@@ -495,7 +501,7 @@ class ConcreteMachine:
int slot_hit = (paged_memory_ >> ((address >> 14) * 2)) & 3;
if(memory_slots_[slot_hit].handler) {
update_audio();
memory_slots_[slot_hit].handler->run_for(memory_slots_[slot_hit].cycles_since_update.flush());
memory_slots_[slot_hit].handler->run_for(memory_slots_[slot_hit].cycles_since_update.flush<HalfCycles>());
memory_slots_[slot_hit].handler->write(address, *cycle.value, read_pointers_[pc_address_ >> 13] != memory_slots_[0].read_pointers[pc_address_ >> 13]);
}
} break;
@@ -503,10 +509,9 @@ class ConcreteMachine:
case CPU::Z80::PartialMachineCycle::Input:
switch(address & 0xff) {
case 0x98: case 0x99:
vdp_.run_for(time_since_vdp_update_.flush());
*cycle.value = vdp_.get_register(address);
z80_.set_interrupt_line(vdp_.get_interrupt_line());
time_until_interrupt_ = vdp_.get_time_until_interrupt();
*cycle.value = vdp_->get_register(address);
z80_.set_interrupt_line(vdp_->get_interrupt_line());
time_until_interrupt_ = vdp_->get_time_until_interrupt();
break;
case 0xa2:
@@ -531,10 +536,9 @@ class ConcreteMachine:
const int port = address & 0xff;
switch(port) {
case 0x98: case 0x99:
vdp_.run_for(time_since_vdp_update_.flush());
vdp_.set_register(address, *cycle.value);
z80_.set_interrupt_line(vdp_.get_interrupt_line());
time_until_interrupt_ = vdp_.get_time_until_interrupt();
vdp_->set_register(address, *cycle.value);
z80_.set_interrupt_line(vdp_->get_interrupt_line());
time_until_interrupt_ = vdp_->get_time_until_interrupt();
break;
case 0xa0: case 0xa1:
@@ -607,7 +611,7 @@ class ConcreteMachine:
}
void flush() {
vdp_.run_for(time_since_vdp_update_.flush());
vdp_.flush();
update_audio();
audio_queue_.perform();
}
@@ -748,7 +752,7 @@ class ConcreteMachine:
};
CPU::Z80::Processor<ConcreteMachine, false, false> z80_;
TI::TMS::TMS9918 vdp_;
JustInTimeActor<TI::TMS::TMS9918, HalfCycles> vdp_;
Intel::i8255::i8255<i8255PortHandler> i8255_;
Concurrency::DeferringAsyncTaskQueue audio_queue_;
@@ -792,7 +796,6 @@ class ConcreteMachine:
uint8_t scratch_[8192];
uint8_t unpopulated_[8192];
HalfCycles time_since_vdp_update_;
HalfCycles time_since_ay_update_;
HalfCycles time_until_interrupt_;

View File

@@ -18,6 +18,7 @@
#include "../KeyboardMachine.hpp"
#include "../../ClockReceiver/ForceInline.hpp"
#include "../../ClockReceiver/JustInTime.hpp"
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#include "../../Outputs/Log.hpp"
@@ -132,7 +133,12 @@ class ConcreteMachine:
// Load the BIOS if relevant.
if(has_bios()) {
const auto roms = rom_fetcher("MasterSystem", {"bios.sms"});
// TODO: there's probably a million other versions of the Master System BIOS; try to build a
// CRC32 catalogue of those. So far:
//
// 0072ed54 = US/European BIOS 1.3
// 48d44a13 = Japanese BIOS 2.1
const auto roms = rom_fetcher({ {"MasterSystem", "the Master System BIOS", "bios.sms", 8*1024, { 0x0072ed54, 0x48d44a13 } } });
if(!roms[0]) {
// No BIOS found; attempt to boot as though it has already disabled itself.
memory_control_ |= 0x08;
@@ -163,16 +169,16 @@ class ConcreteMachine:
}
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override {
vdp_.set_tv_standard(
vdp_->set_tv_standard(
(region_ == Target::Region::Europe) ?
TI::TMS::TVStandard::PAL : TI::TMS::TVStandard::NTSC);
time_until_debounce_ = vdp_.get_time_until_line(-1);
time_until_debounce_ = vdp_->get_time_until_line(-1);
vdp_.set_scan_target(scan_target);
vdp_->set_scan_target(scan_target);
}
void set_display_type(Outputs::Display::DisplayType display_type) override {
vdp_.set_display_type(display_type);
vdp_->set_display_type(display_type);
}
Outputs::Speaker::Speaker *get_speaker() override {
@@ -184,7 +190,7 @@ class ConcreteMachine:
}
forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
time_since_vdp_update_ += cycle.length;
vdp_ += cycle.length;
time_since_sn76489_update_ += cycle.length;
if(cycle.is_terminal()) {
@@ -228,17 +234,15 @@ class ConcreteMachine:
*cycle.value = 0xff;
break;
case 0x40:
update_video();
*cycle.value = vdp_.get_current_line();
*cycle.value = vdp_->get_current_line();
break;
case 0x41:
*cycle.value = vdp_.get_latched_horizontal_counter();
*cycle.value = vdp_.last_valid()->get_latched_horizontal_counter();
break;
case 0x80: case 0x81:
update_video();
*cycle.value = vdp_.get_register(address);
z80_.set_interrupt_line(vdp_.get_interrupt_line());
time_until_interrupt_ = vdp_.get_time_until_interrupt();
*cycle.value = vdp_->get_register(address);
z80_.set_interrupt_line(vdp_->get_interrupt_line());
time_until_interrupt_ = vdp_->get_time_until_interrupt();
break;
case 0xc0: {
Joystick *const joypad1 = static_cast<Joystick *>(joysticks_[0].get());
@@ -279,8 +283,7 @@ class ConcreteMachine:
// Latch if either TH has newly gone to 1.
if((new_ths^previous_ths)&new_ths) {
update_video();
vdp_.latch_horizontal_counter();
vdp_->latch_horizontal_counter();
}
} break;
case 0x40: case 0x41:
@@ -288,10 +291,9 @@ class ConcreteMachine:
sn76489_.set_register(*cycle.value);
break;
case 0x80: case 0x81:
update_video();
vdp_.set_register(address, *cycle.value);
z80_.set_interrupt_line(vdp_.get_interrupt_line());
time_until_interrupt_ = vdp_.get_time_until_interrupt();
vdp_->set_register(address, *cycle.value);
z80_.set_interrupt_line(vdp_->get_interrupt_line());
time_until_interrupt_ = vdp_->get_time_until_interrupt();
break;
case 0xc0:
LOG("TODO: [output] I/O port A/N; " << int(*cycle.value));
@@ -326,15 +328,14 @@ class ConcreteMachine:
time_until_debounce_ -= cycle.length;
if(time_until_debounce_ <= HalfCycles(0)) {
z80_.set_non_maskable_interrupt_line(pause_is_pressed_);
update_video();
time_until_debounce_ = vdp_.get_time_until_line(-1);
time_until_debounce_ = vdp_->get_time_until_line(-1);
}
return HalfCycles(0);
}
void flush() {
update_video();
vdp_.flush();
update_audio();
audio_queue_.perform();
}
@@ -407,16 +408,13 @@ 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());
}
using Target = Analyser::Static::Sega::Target;
Target::Model model_;
Target::Region region_;
Target::PagingScheme paging_scheme_;
CPU::Z80::Processor<ConcreteMachine, false, false> z80_;
TI::TMS::TMS9918 vdp_;
JustInTimeActor<TI::TMS::TMS9918, HalfCycles> vdp_;
Concurrency::DeferringAsyncTaskQueue audio_queue_;
TI::SN76489 sn76489_;
@@ -426,7 +424,6 @@ class ConcreteMachine:
Inputs::Keyboard keyboard_;
bool reset_is_pressed_ = false, pause_is_pressed_ = false;
HalfCycles time_since_vdp_update_;
HalfCycles time_since_sn76489_update_;
HalfCycles time_until_interrupt_;
HalfCycles time_until_debounce_;

23
Machines/MouseMachine.hpp Normal file
View File

@@ -0,0 +1,23 @@
//
// MouseMachine.hpp
// Clock Signal
//
// Created by Thomas Harte on 11/06/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef MouseMachine_hpp
#define MouseMachine_hpp
#include "../Inputs/Mouse.hpp"
namespace MouseMachine {
class Machine {
public:
virtual Inputs::Mouse &get_mouse() = 0;
};
}
#endif /* MouseMachine_hpp */

View File

@@ -170,7 +170,7 @@ class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
/*!
Advances time. This class manages the AY's concept of time to permit updating-on-demand.
*/
inline void run_for(const Cycles cycles) {
inline void run_for(const HalfCycles cycles) {
cycles_since_ay_update_ += cycles;
}
@@ -182,11 +182,11 @@ class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
private:
void update_ay() {
speaker_.run_for(audio_queue_, cycles_since_ay_update_.flush());
speaker_.run_for(audio_queue_, cycles_since_ay_update_.flush<Cycles>());
}
bool ay_bdir_ = false;
bool ay_bc1_ = false;
Cycles cycles_since_ay_update_;
HalfCycles cycles_since_ay_update_;
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
GI::AY38910::AY38910 &ay8910_;
@@ -228,19 +228,34 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
diskii_.set_clocking_hint_observer(this);
}
std::vector<std::string> rom_names = {"colour.rom"};
const std::string machine_name = "Oric";
std::vector<ROMMachine::ROM> rom_names = { {machine_name, "the Oric colour ROM", "colour.rom", 128, 0xd50fca65} };
switch(target.rom) {
case Analyser::Static::Oric::Target::ROM::BASIC10: rom_names.push_back("basic10.rom"); break;
case Analyser::Static::Oric::Target::ROM::BASIC11: rom_names.push_back("basic11.rom"); break;
case Analyser::Static::Oric::Target::ROM::Pravetz: rom_names.push_back("pravetz.rom"); break;
case Analyser::Static::Oric::Target::ROM::BASIC10:
rom_names.emplace_back(machine_name, "Oric BASIC 1.0", "basic10.rom", 16*1024, 0xf18710b4);
break;
case Analyser::Static::Oric::Target::ROM::BASIC11:
rom_names.emplace_back(machine_name, "Oric BASIC 1.1", "basic11.rom", 16*1024, 0xc3a92bef);
break;
case Analyser::Static::Oric::Target::ROM::Pravetz:
rom_names.emplace_back(machine_name, "Pravetz BASIC", "pravetz.rom", 16*1024, 0x58079502);
break;
}
size_t diskii_state_machine_index = 0;
switch(disk_interface) {
default: break;
case Analyser::Static::Oric::Target::DiskInterface::Microdisc: rom_names.push_back("microdisc.rom"); break;
case Analyser::Static::Oric::Target::DiskInterface::Pravetz: rom_names.push_back("8dos.rom"); break;
case Analyser::Static::Oric::Target::DiskInterface::Microdisc:
rom_names.emplace_back(machine_name, "the ORIC Microdisc ROM", "microdisc.rom", 8*1024, 0xa9664a9c);
break;
case Analyser::Static::Oric::Target::DiskInterface::Pravetz:
rom_names.emplace_back(machine_name, "the 8DOS boot ROM", "8dos.rom", 512, 0x49a74c06);
// These ROM details are coupled with those in the DiskIICard.
diskii_state_machine_index = rom_names.size();
rom_names.push_back({"DiskII", "the Disk II 16-sector state machine ROM", "state-machine-16.rom", 256, { 0x9796a238, 0xb72a2c70 }});
break;
}
const auto roms = rom_fetcher("Oric", rom_names);
const auto roms = rom_fetcher(rom_names);
for(std::size_t index = 0; index < roms.size(); ++index) {
if(!roms[index]) {
@@ -261,11 +276,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
pravetz_rom_ = std::move(*roms[2]);
pravetz_rom_.resize(512);
auto state_machine_rom = rom_fetcher("DiskII", {"state-machine-16.rom"});
if(!state_machine_rom[0]) {
throw ROMMachine::Error::MissingROMs;
}
diskii_.set_state_machine(*state_machine_rom[0]);
diskii_.set_state_machine(*roms[diskii_state_machine_index]);
} break;
}
@@ -434,7 +445,6 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
}
via_.run_for(Cycles(1));
via_port_handler_.run_for(Cycles(1));
tape_player_.run_for(Cycles(1));
switch(disk_interface) {
default: break;
@@ -456,7 +466,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
forceinline void flush() {
update_video();
via_port_handler_.flush();
via_.flush();
flush_diskii();
}
@@ -575,7 +585,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
uint8_t ram_[65536];
Cycles cycles_since_video_update_;
inline void update_video() {
video_output_.run_for(cycles_since_video_update_.flush());
video_output_.run_for(cycles_since_video_update_.flush<Cycles>());
}
// ROM bookkeeping
@@ -607,7 +617,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
Apple::DiskII diskii_;
Cycles cycles_since_diskii_update_;
void flush_diskii() {
diskii_.run_for(cycles_since_diskii_update_.flush());
diskii_.run_for(cycles_since_diskii_update_.flush<Cycles>());
}
std::vector<uint8_t> pravetz_rom_;
std::size_t pravetz_rom_base_pointer_ = 0;

View File

@@ -16,15 +16,41 @@
namespace ROMMachine {
/*!
Describes a ROM image; this term is used in this emulator strictly in the sense of firmware —
system software that is an inherent part of a machine.
*/
struct ROM {
/// The machine with which this ROM is associated, in a form that is safe for using as
/// part of a file name.
std::string machine_name;
/// A descriptive name for this ROM, suitable for use in a bullet-point list, a bracket
/// clause, etc, e.g. "the Electron MOS 1.0".
std::string descriptive_name;
/// An idiomatic file name for this ROM, e.g. "os10.rom".
std::string file_name;
/// The expected size of this ROM in bytes, e.g. 32768.
size_t size = 0;
/// CRC32s for all known acceptable copies of this ROM; intended to allow a host platform
/// to test user-provided ROMs of unknown provenance. **Not** intended to be used
/// to exclude ROMs where the user's intent is otherwise clear.
std::vector<uint32_t> crc32s;
ROM(std::string machine_name, std::string descriptive_name, std::string file_name, size_t size, uint32_t crc32) :
machine_name(machine_name), descriptive_name(descriptive_name), file_name(file_name), size(size), crc32s({crc32}) {}
ROM(std::string machine_name, std::string descriptive_name, std::string file_name, size_t size, std::initializer_list<uint32_t> crc32s) :
machine_name(machine_name), descriptive_name(descriptive_name), file_name(file_name), size(size), crc32s(crc32s) {}
};
/*!
Defines the signature for a function that must be supplied by the host environment in order to give machines
a route for fetching any system ROMs they might need.
The caller will supply the idiomatic name of the machine plus a vector of the names of ROM files that it expects
to be present. The recevier should return a vector of unique_ptrs that either contain the contents of the
ROM from @c names that corresponds by index, or else are the nullptr
The caller will supply a vector of the names of ROM files that it would like to inspect. The recevier should
return a vector of unique_ptrs that either contain the contents of the ROM from @c names that corresponds by
index, or else are @c nullptr.
*/
typedef std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> ROMFetcher;
typedef std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::vector<ROM> &roms)> ROMFetcher;
enum class Error {
MissingROMs

View File

@@ -9,7 +9,8 @@
#include "MachineForTarget.hpp"
#include "../AmstradCPC/AmstradCPC.hpp"
#include "../AppleII/AppleII.hpp"
#include "../Apple/AppleII/AppleII.hpp"
#include "../Apple/Macintosh/Macintosh.hpp"
#include "../Atari2600/Atari2600.hpp"
#include "../ColecoVision/ColecoVision.hpp"
#include "../Commodore/Vic-20/Vic20.hpp"
@@ -33,14 +34,15 @@ namespace {
#define Bind(m) BindD(m, m)
switch(target->machine) {
Bind(AmstradCPC)
Bind(AppleII)
BindD(Apple::II, AppleII)
BindD(Apple::Macintosh, Macintosh)
Bind(Atari2600)
BindD(Coleco::Vision, ColecoVision)
BindD(Commodore::Vic20, Vic20)
Bind(Electron)
BindD(Sega::MasterSystem, MasterSystem)
Bind(MSX)
Bind(Oric)
BindD(Commodore::Vic20, Vic20)
BindD(Sega::MasterSystem, MasterSystem)
Bind(ZX8081)
default:
@@ -103,6 +105,7 @@ std::string Machine::ShortNameForTargetMachine(const Analyser::Machine machine)
case Analyser::Machine::Atari2600: return "Atari2600";
case Analyser::Machine::ColecoVision: return "ColecoVision";
case Analyser::Machine::Electron: return "Electron";
case Analyser::Machine::Macintosh: return "Macintosh";
case Analyser::Machine::MSX: return "MSX";
case Analyser::Machine::Oric: return "Oric";
case Analyser::Machine::Vic20: return "Vic20";
@@ -119,6 +122,7 @@ std::string Machine::LongNameForTargetMachine(Analyser::Machine machine) {
case Analyser::Machine::Atari2600: return "Atari 2600";
case Analyser::Machine::ColecoVision: return "ColecoVision";
case Analyser::Machine::Electron: return "Acorn Electron";
case Analyser::Machine::Macintosh: return "Apple Macintosh";
case Analyser::Machine::MSX: return "MSX";
case Analyser::Machine::Oric: return "Oric";
case Analyser::Machine::Vic20: return "Vic 20";
@@ -132,7 +136,7 @@ std::map<std::string, std::vector<std::unique_ptr<Configurable::Option>>> Machin
std::map<std::string, std::vector<std::unique_ptr<Configurable::Option>>> options;
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AmstradCPC), AmstradCPC::get_options()));
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AppleII), AppleII::get_options()));
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AppleII), Apple::II::get_options()));
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::ColecoVision), Coleco::Vision::get_options()));
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Electron), Electron::get_options()));
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::MSX), MSX::get_options()));

View File

@@ -23,6 +23,10 @@ void Memory::Fuzz(uint8_t *buffer, std::size_t size) {
}
}
void Memory::Fuzz(uint16_t *buffer, std::size_t size) {
Fuzz(reinterpret_cast<uint8_t *>(buffer), size * sizeof(uint16_t));
}
void Memory::Fuzz(std::vector<uint8_t> &buffer) {
Fuzz(buffer.data(), buffer.size());
}

View File

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

View File

@@ -0,0 +1,15 @@
//
// MemoryPacker.cpp
// Clock Signal
//
// Created by Thomas Harte on 03/05/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#include "MemoryPacker.hpp"
void Memory::PackBigEndian16(const std::vector<uint8_t> &source, uint16_t *target) {
for(std::size_t c = 0; c < source.size(); c += 2) {
target[c >> 1] = uint16_t(source[c] << 8) | uint16_t(source[c+1]);
}
}

View File

@@ -0,0 +1,24 @@
//
// MemoryPacker.hpp
// Clock Signal
//
// Created by Thomas Harte on 03/05/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef MemoryPacker_hpp
#define MemoryPacker_hpp
#include <cstdint>
#include <vector>
namespace Memory {
/*!
Copies the bytes from @c source into @c target, interpreting them
as big-endian 16-bit data.
*/
void PackBigEndian16(const std::vector<uint8_t> &source, uint16_t *target);
}
#endif /* MemoryPacker_hpp */

View File

@@ -45,6 +45,10 @@ template<typename T> class TypedDynamicMachine: public ::Machine::DynamicMachine
return get<KeyboardMachine::Machine>();
}
MouseMachine::Machine *mouse_machine() override {
return get<MouseMachine::Machine>();
}
Configurable::Device *configurable_device() override {
return get<Configurable::Device>();
}

View File

@@ -76,7 +76,11 @@ template<bool is_zx81> class ConcreteMachine:
clear_all_keys();
const bool use_zx81_rom = target.is_ZX81 || target.ZX80_uses_ZX81_ROM;
const auto roms = rom_fetcher("ZX8081", { use_zx81_rom ? "zx81.rom" : "zx80.rom" });
const auto roms =
use_zx81_rom ?
rom_fetcher({ {"ZX8081", "the ZX81 BASIC ROM", "zx81.rom", 8 * 1024, 0x4b1dd6eb} }) :
rom_fetcher({ {"ZX8081", "the ZX80 BASIC ROM", "zx80.rom", 4 * 1024, 0x4c7fc597} });
if(!roms[0]) throw ROMMachine::Error::MissingROMs;
rom_ = std::move(*roms[0]);

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:/Users/thomasharte/Projects/CLK/OSBindings/Mac/Clock SignalTests/68000ArithmeticTests.mm">
</FileRef>
<FileRef
location = "self:Clock Signal.xcodeproj">
</FileRef>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0930"
LastUpgradeVersion = "1030"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -26,6 +26,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
disableMainThreadChecker = "YES"
codeCoverageEnabled = "YES"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@@ -14,7 +14,7 @@
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="default" id="xOd-HO-29H" userLabel="Window">
<window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="xOd-HO-29H" userLabel="Window">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<windowCollectionBehavior key="collectionBehavior" fullScreenPrimary="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
@@ -25,7 +25,7 @@
<rect key="frame" x="0.0" y="0.0" width="600" height="450"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<openGLView hidden="YES" useAuxiliaryDepthBufferStencil="NO" allowOffline="YES" wantsBestResolutionOpenGLSurface="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DEG-fq-cjd" customClass="CSOpenGLView">
<openGLView hidden="YES" wantsLayer="YES" useAuxiliaryDepthBufferStencil="NO" allowOffline="YES" wantsBestResolutionOpenGLSurface="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DEG-fq-cjd" customClass="CSOpenGLView">
<rect key="frame" x="0.0" y="0.0" width="600" height="450"/>
</openGLView>
</subviews>
@@ -36,6 +36,11 @@
<constraint firstItem="DEG-fq-cjd" firstAttribute="width" secondItem="gIp-Ho-8D9" secondAttribute="width" id="mYS-bH-DST"/>
</constraints>
</view>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="color" keyPath="backgroundColor">
<color key="value" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
<connections>
<outlet property="delegate" destination="-2" id="0bl-1N-x8E"/>
<outlet property="initialFirstResponder" destination="DEG-fq-cjd" id="9RI-Kx-QeN"/>

View File

@@ -10,10 +10,13 @@
#import "CSStaticAnalyser.h"
#import "CSOpenGLView.h"
#import "CSAudioQueue.h"
#import "CSOpenGLView.h"
#import "CSROMReceiverView.h"
#import "CSBestEffortUpdater.h"
#import "CSJoystickManager.h"
#import "NSData+CRC32.h"
#include "KeyCodes.h"

View File

@@ -16,109 +16,76 @@ class MachineDocument:
CSOpenGLViewDelegate,
CSOpenGLViewResponderDelegate,
CSBestEffortUpdaterDelegate,
CSAudioQueueDelegate
CSAudioQueueDelegate,
CSROMReciverViewDelegate
{
fileprivate let actionLock = NSLock()
fileprivate let drawLock = NSLock()
fileprivate let bestEffortLock = NSLock()
// MARK: - Mutual Exclusion.
var machine: CSMachine!
var name: String! {
get {
return nil
}
}
var optionsPanelNibName: String?
/// Ensures exclusive access between calls to self.machine.run and close().
private let actionLock = NSLock()
/// Ensures exclusive access between calls to machine.updateView and machine.drawView, and close().
private let drawLock = NSLock()
/// Ensures exclusive access to the best-effort updater.
private let bestEffortLock = NSLock()
func aspectRatio() -> NSSize {
// MARK: - Machine details.
/// A description of the machine this document should represent once fully set up.
private var machineDescription: CSStaticAnalyser?
/// The active machine, following its successful creation.
private var machine: CSMachine!
/// @returns the appropriate window content aspect ratio for this @c self.machine.
private func aspectRatio() -> NSSize {
return NSSize(width: 4.0, height: 3.0)
}
/// The output audio queue, if any.
private var audioQueue: CSAudioQueue!
/// The best-effort updater.
private var bestEffortUpdater: CSBestEffortUpdater?
// MARK: - Main NIB connections.
/// The OpenGL view to receive this machine's display.
@IBOutlet weak var openGLView: CSOpenGLView!
/// The options panel, if any.
@IBOutlet var optionsPanel: MachinePanel!
/// An action to display the options panel, if there is one.
@IBAction func showOptions(_ sender: AnyObject!) {
optionsPanel?.setIsVisible(true)
}
/// The activity panel, if one is deemed appropriate.
@IBOutlet var activityPanel: NSPanel!
/// An action to display the activity panel, if there is one.
@IBAction func showActivity(_ sender: AnyObject!) {
activityPanel.setIsVisible(true)
}
fileprivate var audioQueue: CSAudioQueue! = nil
fileprivate var bestEffortUpdater: CSBestEffortUpdater?
// MARK: - NSDocument Overrides and NSWindowDelegate methods.
/// Links this class to the MachineDocument NIB.
override var windowNibName: NSNib.Name? {
return NSNib.Name(rawValue: "MachineDocument")
return "MachineDocument"
}
override func windowControllerDidLoadNib(_ aController: NSWindowController) {
super.windowControllerDidLoadNib(aController)
aController.window?.contentAspectRatio = self.aspectRatio()
setupMachineOutput()
convenience init(type typeName: String) throws {
self.init()
self.fileType = typeName
}
// Attempting to show a sheet before the window is visible (such as when the NIB is loaded) results in
// a sheet mysteriously floating on its own. For now, use windowDidUpdate as a proxy to know that the window
// is visible, though it's a little premature.
func windowDidUpdate(_ notification: Notification) {
if self.shouldShowNewMachinePanel {
self.shouldShowNewMachinePanel = false
Bundle.main.loadNibNamed(NSNib.Name(rawValue: "MachinePicker"), owner: self, topLevelObjects: nil)
self.machinePicker?.establishStoredOptions()
self.windowControllers[0].window?.beginSheet(self.machinePickerPanel!, completionHandler: nil)
}
}
fileprivate func setupMachineOutput() {
if let machine = self.machine, let openGLView = self.openGLView {
// establish the output aspect ratio and audio
let aspectRatio = self.aspectRatio()
openGLView.perform(glContext: {
machine.setView(openGLView, aspectRatio: Float(aspectRatio.width / aspectRatio.height))
})
// attach an options panel if one is available
if let optionsPanelNibName = self.optionsPanelNibName {
Bundle.main.loadNibNamed(NSNib.Name(rawValue: optionsPanelNibName), owner: self, topLevelObjects: nil)
self.optionsPanel.machine = machine
self.optionsPanel?.establishStoredOptions()
showOptions(self)
}
machine.delegate = self
self.bestEffortUpdater = CSBestEffortUpdater()
// callbacks from the OpenGL may come on a different thread, immediately following the .delegate set;
// hence the full setup of the best-effort updater prior to setting self as a delegate
openGLView.delegate = self
openGLView.responderDelegate = self
setupAudioQueueClockRate()
// bring OpenGL view-holding window on top of the options panel and show the content
openGLView.isHidden = false
openGLView.window!.makeKeyAndOrderFront(self)
openGLView.window!.makeFirstResponder(openGLView)
// start accepting best effort updates
self.bestEffortUpdater!.delegate = self
}
}
func machineSpeakerDidChangeInputClock(_ machine: CSMachine) {
setupAudioQueueClockRate()
}
fileprivate func setupAudioQueueClockRate() {
// establish and provide the audio queue, taking advice as to an appropriate sampling rate
let maximumSamplingRate = CSAudioQueue.preferredSamplingRate()
let selectedSamplingRate = self.machine.idealSamplingRate(from: NSRange(location: 0, length: NSInteger(maximumSamplingRate)))
if selectedSamplingRate > 0 {
audioQueue = CSAudioQueue(samplingRate: Float64(selectedSamplingRate))
audioQueue.delegate = self
self.machine.audioQueue = self.audioQueue
self.machine.setAudioSamplingRate(selectedSamplingRate, bufferSize:audioQueue.preferredBufferSize)
override func read(from url: URL, ofType typeName: String) throws {
if let analyser = CSStaticAnalyser(fileAt: url) {
self.displayName = analyser.displayName
self.configureAs(analyser)
} else {
throw NSError(domain: "MachineDocument", code: -1, userInfo: nil)
}
}
@@ -148,56 +115,135 @@ class MachineDocument:
super.close()
}
// MARK: configuring
override func data(ofType typeName: String) throws -> Data {
throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil)
}
override func windowControllerDidLoadNib(_ aController: NSWindowController) {
super.windowControllerDidLoadNib(aController)
aController.window?.contentAspectRatio = self.aspectRatio()
}
private var missingROMs: [CSMissingROM] = []
func configureAs(_ analysis: CSStaticAnalyser) {
if let machine = CSMachine(analyser: analysis) {
self.machineDescription = analysis
let missingROMs = NSMutableArray()
if let machine = CSMachine(analyser: analysis, missingROMs: missingROMs) {
self.machine = machine
self.optionsPanelNibName = analysis.optionsPanelNibName
setupMachineOutput()
setupActivityDisplay()
}
}
fileprivate var shouldShowNewMachinePanel = false
override func read(from url: URL, ofType typeName: String) throws {
if let analyser = CSStaticAnalyser(fileAt: url) {
self.displayName = analyser.displayName
self.configureAs(analyser)
} else {
throw NSError(domain: "MachineDocument", code: -1, userInfo: nil)
// Store the selected machine and list of missing ROMs, and
// show the missing ROMs dialogue.
self.missingROMs = []
for untypedMissingROM in missingROMs {
self.missingROMs.append(untypedMissingROM as! CSMissingROM)
}
requestRoms()
}
}
convenience init(type typeName: String) throws {
self.init()
self.fileType = typeName
self.shouldShowNewMachinePanel = true
enum InteractionMode {
case notStarted, showingMachinePicker, showingROMRequester, showingMachine
}
private var interactionMode: InteractionMode = .notStarted
// MARK: the pasteboard
func paste(_ sender: Any) {
let pasteboard = NSPasteboard.general
if let string = pasteboard.string(forType: .string) {
self.machine.paste(string)
// Attempting to show a sheet before the window is visible (such as when the NIB is loaded) results in
// a sheet mysteriously floating on its own. For now, use windowDidUpdate as a proxy to know that the window
// is visible, though it's a little premature.
func windowDidUpdate(_ notification: Notification) {
// If an interaction mode is not yet in effect, pick the proper one and display the relevant thing.
if self.interactionMode == .notStarted {
// If a full machine exists, just continue showing it.
if self.machine != nil {
self.interactionMode = .showingMachine
setupMachineOutput()
return
}
// If a machine has been picked but is not showing, there must be ROMs missing.
if self.machineDescription != nil {
self.interactionMode = .showingROMRequester
requestRoms()
return
}
// If a machine hasn't even been picked yet, show the machine picker.
self.interactionMode = .showingMachinePicker
Bundle.main.loadNibNamed("MachinePicker", owner: self, topLevelObjects: nil)
self.machinePicker?.establishStoredOptions()
self.windowControllers[0].window?.beginSheet(self.machinePickerPanel!, completionHandler: nil)
}
}
// MARK: CSBestEffortUpdaterDelegate
final func bestEffortUpdater(_ bestEffortUpdater: CSBestEffortUpdater!, runForInterval duration: TimeInterval, didSkipPreviousUpdate: Bool) {
if actionLock.try() {
self.machine.run(forInterval: duration)
actionLock.unlock()
// MARK: - Connections Between Machine and the Outside World
private func setupMachineOutput() {
if let machine = self.machine, let openGLView = self.openGLView {
// Establish the output aspect ratio and audio.
let aspectRatio = self.aspectRatio()
openGLView.perform(glContext: {
machine.setView(openGLView, aspectRatio: Float(aspectRatio.width / aspectRatio.height))
})
// Attach an options panel if one is available.
if let optionsPanelNibName = self.machineDescription?.optionsPanelNibName {
Bundle.main.loadNibNamed(optionsPanelNibName, owner: self, topLevelObjects: nil)
self.optionsPanel.machine = machine
self.optionsPanel?.establishStoredOptions()
showOptions(self)
}
machine.delegate = self
self.bestEffortUpdater = CSBestEffortUpdater()
// Callbacks from the OpenGL may come on a different thread, immediately following the .delegate set;
// hence the full setup of the best-effort updater prior to setting self as a delegate.
openGLView.delegate = self
openGLView.responderDelegate = self
// If this machine has a mouse, enable mouse capture.
openGLView.shouldCaptureMouse = machine.hasMouse
setupAudioQueueClockRate()
// Bring OpenGL view-holding window on top of the options panel and show the content.
openGLView.isHidden = false
openGLView.window!.makeKeyAndOrderFront(self)
openGLView.window!.makeFirstResponder(openGLView)
// Start accepting best effort updates.
self.bestEffortUpdater!.delegate = self
}
}
// MARK: CSAudioQueueDelegate
func machineSpeakerDidChangeInputClock(_ machine: CSMachine) {
setupAudioQueueClockRate()
}
private func setupAudioQueueClockRate() {
// establish and provide the audio queue, taking advice as to an appropriate sampling rate
let maximumSamplingRate = CSAudioQueue.preferredSamplingRate()
let selectedSamplingRate = self.machine.idealSamplingRate(from: NSRange(location: 0, length: NSInteger(maximumSamplingRate)))
if selectedSamplingRate > 0 {
audioQueue = CSAudioQueue(samplingRate: Float64(selectedSamplingRate))
audioQueue.delegate = self
self.machine.audioQueue = self.audioQueue
self.machine.setAudioSamplingRate(selectedSamplingRate, bufferSize:audioQueue.preferredBufferSize)
}
}
/// Responds to the CSAudioQueueDelegate dry-queue warning message by requesting a machine update.
final func audioQueueIsRunningDry(_ audioQueue: CSAudioQueue) {
bestEffortLock.lock()
bestEffortUpdater?.update()
bestEffortLock.unlock()
}
// MARK: CSOpenGLViewDelegate
/// Responds to the CSOpenGLViewDelegate redraw message by requesting a machine update if this is a timed
/// request, and ordering a redraw regardless of the motivation.
final func openGLViewRedraw(_ view: CSOpenGLView, event redrawEvent: CSOpenGLViewRedrawEvent) {
if redrawEvent == .timer {
bestEffortLock.lock()
@@ -218,7 +264,27 @@ class MachineDocument:
}
}
// MARK: Runtime media insertion.
/// Responds to CSBestEffortUpdaterDelegate update message by running the machine.
final func bestEffortUpdater(_ bestEffortUpdater: CSBestEffortUpdater!, runForInterval duration: TimeInterval, didSkipPreviousUpdate: Bool) {
if let machine = self.machine, actionLock.try() {
machine.run(forInterval: duration)
actionLock.unlock()
}
}
// MARK: - Pasteboard Forwarding.
/// Forwards any text currently on the pasteboard into the active machine.
func paste(_ sender: Any) {
let pasteboard = NSPasteboard.general
if let string = pasteboard.string(forType: .string), let machine = self.machine {
machine.paste(string)
}
}
// MARK: - Runtime Media Insertion.
/// Delegate message to receive drag and drop files.
final func openGLView(_ view: CSOpenGLView, didReceiveFileAt URL: URL) {
let mediaSet = CSMediaSet(fileAt: URL)
if let mediaSet = mediaSet {
@@ -226,6 +292,8 @@ class MachineDocument:
}
}
/// Action for the insert menu command; displays an NSOpenPanel and then segues into the same process
/// as if a file had been received via drag and drop.
@IBAction final func insertMedia(_ sender: AnyObject!) {
let openPanel = NSOpenPanel()
openPanel.message = "Hint: you can also insert media by dragging and dropping it onto the machine's window."
@@ -241,37 +309,40 @@ class MachineDocument:
}
}
// MARK: NSDocument overrides
override func data(ofType typeName: String) throws -> Data {
throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil)
}
// MARK: - Input Management.
// MARK: Input management
/// Upon a resign key, immediately releases all ongoing input mechanisms any currently pressed keys,
/// and joystick and mouse inputs.
func windowDidResignKey(_ notification: Notification) {
if let machine = self.machine {
machine.clearAllKeys()
machine.joystickManager = nil
}
self.openGLView.releaseMouse()
}
/// Upon becoming key, attaches joystick input to the machine.
func windowDidBecomeKey(_ notification: Notification) {
if let machine = self.machine {
machine.joystickManager = (DocumentController.shared as! DocumentController).joystickManager
}
}
/// Forwards key down events directly to the machine.
func keyDown(_ event: NSEvent) {
if let machine = self.machine {
machine.setKey(event.keyCode, characters: event.characters, isPressed: true)
}
}
/// Forwards key up events directly to the machine.
func keyUp(_ event: NSEvent) {
if let machine = self.machine {
machine.setKey(event.keyCode, characters: event.characters, isPressed: false)
}
}
/// Synthesies appropriate key up and key down events upon any change in modifiers.
func flagsChanged(_ newModifiers: NSEvent) {
if let machine = self.machine {
machine.setKey(VK_Shift, characters: nil, isPressed: newModifiers.modifierFlags.contains(.shift))
@@ -281,19 +352,199 @@ class MachineDocument:
}
}
// MARK: New machine creation
/// Forwards mouse movement events to the mouse.
func mouseMoved(_ event: NSEvent) {
if let machine = self.machine {
machine.addMouseMotionX(event.deltaX, y: event.deltaY)
}
}
/// Forwards mouse button down events to the mouse.
func mouseUp(_ event: NSEvent) {
if let machine = self.machine {
machine.setMouseButton(Int32(event.buttonNumber), isPressed: false)
}
}
/// Forwards mouse button up events to the mouse.
func mouseDown(_ event: NSEvent) {
if let machine = self.machine {
machine.setMouseButton(Int32(event.buttonNumber), isPressed: true)
}
}
// MARK: - MachinePicker Outlets and Actions
@IBOutlet var machinePicker: MachinePicker?
@IBOutlet var machinePickerPanel: NSWindow?
@IBAction func createMachine(_ sender: NSButton?) {
self.configureAs(machinePicker!.selectedMachine())
machinePicker = nil
let selectedMachine = machinePicker!.selectedMachine()
self.windowControllers[0].window?.endSheet(self.machinePickerPanel!)
self.machinePicker = nil
self.configureAs(selectedMachine)
}
@IBAction func cancelCreateMachine(_ sender: NSButton?) {
close()
}
// MARK: - ROMRequester Outlets and Actions
@IBOutlet var romRequesterPanel: NSWindow?
@IBOutlet var romRequesterText: NSTextField?
@IBOutlet var romReceiverErrorField: NSTextField?
@IBOutlet var romReceiverView: CSROMReceiverView?
private var romRequestBaseText = ""
func requestRoms() {
// Don't act yet if there's no window controller yet.
if self.windowControllers.count == 0 {
return
}
// Load the ROM requester dialogue.
Bundle.main.loadNibNamed("ROMRequester", owner: self, topLevelObjects: nil)
self.romReceiverView!.delegate = self
self.romRequestBaseText = romRequesterText!.stringValue
romReceiverErrorField?.alphaValue = 0.0
// Populate the current absentee list.
populateMissingRomList()
// Show the thing.
self.windowControllers[0].window?.beginSheet(self.romRequesterPanel!, completionHandler: nil)
}
@IBAction func cancelRequestROMs(_ sender: NSButton?) {
close()
}
func populateMissingRomList() {
// Fill in the missing details; first build a list of all the individual
// line items.
var requestLines: [String] = []
for missingROM in self.missingROMs {
if let descriptiveName = missingROM.descriptiveName {
requestLines.append("" + descriptiveName)
} else {
requestLines.append("" + missingROM.fileName)
}
}
// Suffix everything up to the penultimate line with a semicolon;
// the penultimate line with a semicolon and a conjunctive; the final
// line with a full stop.
for x in 0 ..< requestLines.count {
if x < requestLines.count - 2 {
requestLines[x].append(";")
} else if x < requestLines.count - 1 {
requestLines[x].append("; and")
} else {
requestLines[x].append(".")
}
}
romRequesterText!.stringValue = self.romRequestBaseText + requestLines.joined(separator: "\n")
}
func romReceiverView(_ view: CSROMReceiverView, didReceiveFileAt URL: URL) {
// Test whether the file identified matches any of the currently missing ROMs.
// If so then remove that ROM from the missing list and update the request screen.
// If no ROMs are still missing, start the machine.
do {
let fileData = try Data(contentsOf: URL)
var didInstallRom = false
// Try to match by size first, CRC second. Accept that some ROMs may have
// some additional appended data. Arbitrarily allow them to be up to 10kb
// too large.
var index = 0
for missingROM in self.missingROMs {
if fileData.count >= missingROM.size && fileData.count < missingROM.size + 10*1024 {
// Trim to size.
let trimmedData = fileData[0 ..< missingROM.size]
// Get CRC.
if missingROM.crc32s.contains( (trimmedData as NSData).crc32 ) {
// This ROM matches; copy it into the application library,
// strike it from the missing ROM list and decide how to
// proceed.
let fileManager = FileManager.default
let targetPath = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]
.appendingPathComponent("ROMImages")
.appendingPathComponent(missingROM.machineName)
let targetFile = targetPath
.appendingPathComponent(missingROM.fileName)
do {
try fileManager.createDirectory(atPath: targetPath.path, withIntermediateDirectories: true, attributes: nil)
try trimmedData.write(to: targetFile)
} catch let error {
showRomReceiverError(error: "Couldn't write to application support directory: \(error)")
}
self.missingROMs.remove(at: index)
didInstallRom = true
break
}
}
index = index + 1
}
if didInstallRom {
if self.missingROMs.count == 0 {
self.windowControllers[0].window?.endSheet(self.romRequesterPanel!)
configureAs(self.machineDescription!)
} else {
populateMissingRomList()
}
} else {
showRomReceiverError(error: "Didn't recognise contents of \(URL.lastPathComponent)")
}
} catch let error {
showRomReceiverError(error: "Couldn't read file at \(URL.absoluteString): \(error)")
}
}
// Yucky ugliness follows; my experience as an iOS developer intersects poorly with
// NSAnimationContext hence the various stateful diplications below. isShowingError
// should be essentially a duplicate of the current alphaValue, and animationCount
// is to resolve my inability to figure out how to cancel scheduled animations.
private var errorText = ""
private var isShowingError = false
private var animationCount = 0
private func showRomReceiverError(error: String) {
// Set or append the new error.
if self.errorText.count > 0 {
self.errorText = self.errorText + "\n" + error
} else {
self.errorText = error
}
// Apply the new complete text.
romReceiverErrorField!.stringValue = self.errorText
if !isShowingError {
// Schedule the box's appearance.
NSAnimationContext.beginGrouping()
NSAnimationContext.current.duration = 0.1
romReceiverErrorField?.animator().alphaValue = 1.0
NSAnimationContext.endGrouping()
isShowingError = true
}
// Schedule the box to disappear.
self.animationCount = self.animationCount + 1
let capturedAnimationCount = animationCount
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .seconds(2)) {
if self.animationCount == capturedAnimationCount {
NSAnimationContext.beginGrouping()
NSAnimationContext.current.duration = 1.0
self.romReceiverErrorField?.animator().alphaValue = 0.0
NSAnimationContext.endGrouping()
self.isShowingError = false
self.errorText = ""
}
}
}
// MARK: Joystick-via-the-keyboard selection
@IBAction func useKeyboardAsKeyboard(_ sender: NSMenuItem?) {
machine.inputMode = .keyboard
@@ -303,6 +554,9 @@ class MachineDocument:
machine.inputMode = .joystick
}
/// Determines which of the menu items to enable and disable based on the ability of the
/// current machine to handle keyboard and joystick input, accept new media and whether
/// it has an associted activity window.
override func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool {
if let menuItem = item as? NSMenuItem {
switch item.action {
@@ -336,7 +590,7 @@ class MachineDocument:
return super.validateUserInterfaceItem(item)
}
// Screenshot capture.
/// Saves a screenshot of the
@IBAction func saveScreenshot(_ sender: AnyObject!) {
// Grab a date formatter and form a file name.
let dateFormatter = DateFormatter()
@@ -360,7 +614,8 @@ class MachineDocument:
}
// MARK: Activity display.
class LED {
private class LED {
let levelIndicator: NSLevelIndicator
init(levelIndicator: NSLevelIndicator) {
self.levelIndicator = levelIndicator
@@ -368,11 +623,12 @@ class MachineDocument:
var isLit = false
var isBlinking = false
}
fileprivate var leds: [String: LED] = [:]
private var leds: [String: LED] = [:]
func setupActivityDisplay() {
var leds = machine.leds
if leds.count > 0 {
Bundle.main.loadNibNamed(NSNib.Name(rawValue: "Activity"), owner: self, topLevelObjects: nil)
Bundle.main.loadNibNamed("Activity", owner: self, topLevelObjects: nil)
showActivity(nil)
// Inspect the activity panel for indicators.

View File

@@ -14,42 +14,42 @@
</array>
<key>CFBundleTypeIconFile</key>
<string>cartridge.png</string>
<key>CFBundleTypeName</key>
<string>Atari 2600 Cartridge</string>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeName</key>
<string>Atari 2600 Cartridge</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSTypeIsPackage</key>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>rom</string>
</array>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>chip.png</string>
<key>CFBundleTypeName</key>
<string>ROM Image</string>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSTypeIsPackage</key>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
@@ -57,110 +57,110 @@
<string>uef</string>
<string>uef.gz</string>
</array>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>cassette.png</string>
<key>CFBundleTypeName</key>
<string>Electron/BBC UEF Image</string>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSTypeIsPackage</key>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>prg</string>
</array>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>floppy525.png</string>
<key>CFBundleTypeName</key>
<string>Commodore Program</string>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSTypeIsPackage</key>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>tap</string>
</array>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>cassette.png</string>
<key>CFBundleTypeName</key>
<string>Tape Image</string>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSTypeIsPackage</key>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>g64</string>
</array>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>floppy525.png</string>
<key>CFBundleTypeName</key>
<string>Commodore Disk</string>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSTypeIsPackage</key>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>d64</string>
</array>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>floppy525.png</string>
<key>CFBundleTypeName</key>
<string>Commodore 1540/1 Disk</string>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSTypeIsPackage</key>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
@@ -171,44 +171,44 @@
<string>adl</string>
<string>adm</string>
</array>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>floppy35.png</string>
<key>CFBundleTypeName</key>
<string>Electron/BBC Disk Image</string>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSTypeIsPackage</key>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>dsk</string>
</array>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>floppy35.png</string>
<key>CFBundleTypeName</key>
<string>Disk Image</string>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSTypeIsPackage</key>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
@@ -216,22 +216,22 @@
<string>o</string>
<string>80</string>
</array>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>cassette.png</string>
<key>CFBundleTypeName</key>
<string>ZX80 Tape Image</string>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSTypeIsPackage</key>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
@@ -240,240 +240,240 @@
<string>81</string>
<string>p81</string>
</array>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>cassette.png</string>
<key>CFBundleTypeName</key>
<string>ZX81 Tape Image</string>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSTypeIsPackage</key>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>csw</string>
</array>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>cassette.png</string>
<key>CFBundleTypeName</key>
<string>Tape Image</string>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSTypeIsPackage</key>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>tzx</string>
</array>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>cassette.png</string>
<key>CFBundleTypeName</key>
<string>Tape Image</string>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSTypeIsPackage</key>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>cdt</string>
</array>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>cassette.png</string>
<key>CFBundleTypeName</key>
<string>Amstrad CPC Tape Image</string>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSTypeIsPackage</key>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>hfe</string>
</array>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>floppy35.png</string>
<key>CFBundleTypeName</key>
<string>HxC Disk Image</string>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSTypeIsPackage</key>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>cas</string>
</array>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>cassette.png</string>
<key>CFBundleTypeName</key>
<string>MSX Tape Image</string>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSTypeIsPackage</key>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>dmk</string>
</array>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>floppy35.png</string>
<key>CFBundleTypeName</key>
<string>Disk Image</string>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSTypeIsPackage</key>
<false/>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSTypeIsPackage</key>
<false/>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>tsx</string>
</array>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>cassette.png</string>
<key>CFBundleTypeName</key>
<string>MSX Tape Image</string>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSTypeIsPackage</key>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>col</string>
</array>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>cartridge.png</string>
<key>CFBundleTypeName</key>
<string>ColecoVision Cartridge</string>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSTypeIsPackage</key>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>sms</string>
</array>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>cartridge.png</string>
<key>CFBundleTypeName</key>
<string>Master System Cartridge</string>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSTypeIsPackage</key>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>sg</string>
</array>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>cartridge.png</string>
<key>CFBundleTypeName</key>
<string>SG1000 Cartridge</string>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSTypeIsPackage</key>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
@@ -483,22 +483,48 @@
<string>do</string>
<string>po</string>
</array>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>floppy525.png</string>
<key>CFBundleTypeName</key>
<string>Apple II Disk Image</string>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSTypeIsPackage</key>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>img</string>
<string>hfv</string>
<string>image</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>floppy35</string>
<key>CFBundleTypeMIMETypes</key>
<array>
<string>application/x-apple-diskimage</string>
</array>
<key>CFBundleTypeName</key>
<string>DiskCopy 4.2 Disk Image</string>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>
</array>
<key>CFBundleExecutable</key>
@@ -524,7 +550,7 @@
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright 2018 Thomas Harte. All rights reserved.</string>
<string>Copyright 2019 Thomas Harte. All rights reserved.</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>

View File

@@ -33,6 +33,14 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {
CSMachineKeyboardInputModeJoystick
};
@interface CSMissingROM: NSObject
@property (nonatomic, readonly, nonnull) NSString *machineName;
@property (nonatomic, readonly, nonnull) NSString *fileName;
@property (nonatomic, readonly, nullable) NSString *descriptiveName;
@property (nonatomic, readonly) NSUInteger size;
@property (nonatomic, readonly, nonnull) NSArray<NSNumber *> *crc32s;
@end
// Deliberately low; to ensure CSMachine has been declared as an @class already.
#import "CSAtari2600.h"
#import "CSZX8081.h"
@@ -45,8 +53,10 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {
Initialises an instance of CSMachine.
@param result The CSStaticAnalyser result that describes the machine needed.
@param missingROMs An array that is filled with a list of ROMs that the machine requested but which
were not found; populated only if this `init` has failed.
*/
- (nullable instancetype)initWithAnalyser:(nonnull CSStaticAnalyser *)result NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithAnalyser:(nonnull CSStaticAnalyser *)result missingROMs:(nullable inout NSMutableArray<CSMissingROM *> *)missingROMs NS_DESIGNATED_INITIALIZER;
- (void)runForInterval:(NSTimeInterval)interval;
@@ -61,6 +71,9 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {
- (void)setKey:(uint16_t)key characters:(nullable NSString *)characters isPressed:(BOOL)isPressed;
- (void)clearAllKeys;
- (void)setMouseButton:(int)button isPressed:(BOOL)isPressed;
- (void)addMouseMotionX:(CGFloat)deltaX y:(CGFloat)deltaY;
@property (nonatomic, strong, nullable) CSAudioQueue *audioQueue;
@property (nonatomic, readonly, nonnull) CSOpenGLView *view;
@property (nonatomic, weak, nullable) id<CSMachineDelegate> delegate;
@@ -81,6 +94,7 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {
// Input control.
@property (nonatomic, readonly) BOOL hasExclusiveKeyboard;
@property (nonatomic, readonly) BOOL hasJoystick;
@property (nonatomic, readonly) BOOL hasMouse;
@property (nonatomic, assign) CSMachineKeyboardInputMode inputMode;
@property (nonatomic, nullable) CSJoystickManager *joystickManager;

View File

@@ -74,6 +74,68 @@ struct ActivityObserver: public Activity::Observer {
__unsafe_unretained CSMachine *machine;
};
@interface CSMissingROM (Mutability)
@property (nonatomic, nonnull, copy) NSString *machineName;
@property (nonatomic, nonnull, copy) NSString *fileName;
@property (nonatomic, nullable, copy) NSString *descriptiveName;
@property (nonatomic, readwrite) NSUInteger size;
@property (nonatomic, copy) NSArray<NSNumber *> *crc32s;
@end
@implementation CSMissingROM {
NSString *_machineName;
NSString *_fileName;
NSString *_descriptiveName;
NSUInteger _size;
NSArray<NSNumber *> *_crc32s;
}
- (NSString *)machineName {
return _machineName;
}
- (void)setMachineName:(NSString *)machineName {
_machineName = [machineName copy];
}
- (NSString *)fileName {
return _fileName;
}
- (void)setFileName:(NSString *)fileName {
_fileName = [fileName copy];
}
- (NSString *)descriptiveName {
return _descriptiveName;
}
- (void)setDescriptiveName:(NSString *)descriptiveName {
_descriptiveName = [descriptiveName copy];
}
- (NSUInteger)size {
return _size;
}
- (void)setSize:(NSUInteger)size {
_size = size;
}
- (NSArray<NSNumber *> *)crc32s {
return _crc32s;
}
- (void)setCrc32s:(NSArray<NSNumber *> *)crc32s {
_crc32s = [crc32s copy];
}
- (NSString *)description {
return [NSString stringWithFormat:@"%@/%@, %@ bytes, CRCs: %@", _fileName, _descriptiveName, @(_size), _crc32s];
}
@end
@implementation CSMachine {
SpeakerDelegate _speakerDelegate;
ActivityObserver _activityObserver;
@@ -90,14 +152,37 @@ struct ActivityObserver: public Activity::Observer {
std::unique_ptr<Outputs::Display::OpenGL::ScanTarget> _scanTarget;
}
- (instancetype)initWithAnalyser:(CSStaticAnalyser *)result {
- (instancetype)initWithAnalyser:(CSStaticAnalyser *)result missingROMs:(inout NSMutableArray<CSMissingROM *> *)missingROMs {
self = [super init];
if(self) {
_analyser = result;
Machine::Error error;
_machine.reset(Machine::MachineForTargets(_analyser.targets, CSROMFetcher(), error));
if(!_machine) return nil;
std::vector<ROMMachine::ROM> missing_roms;
_machine.reset(Machine::MachineForTargets(_analyser.targets, CSROMFetcher(&missing_roms), error));
if(!_machine) {
for(const auto &missing_rom : missing_roms) {
CSMissingROM *rom = [[CSMissingROM alloc] init];
// Copy/convert the primitive fields.
rom.machineName = [NSString stringWithUTF8String:missing_rom.machine_name.c_str()];
rom.fileName = [NSString stringWithUTF8String:missing_rom.file_name.c_str()];
rom.descriptiveName = missing_rom.descriptive_name.empty() ? nil : [NSString stringWithUTF8String:missing_rom.descriptive_name.c_str()];
rom.size = missing_rom.size;
// Convert the CRC list.
NSMutableArray<NSNumber *> *crc32s = [[NSMutableArray alloc] init];
for(const auto &crc : missing_rom.crc32s) {
[crc32s addObject:@(crc)];
}
rom.crc32s = [crc32s copy];
// Add to the missing list.
[missingROMs addObject:rom];
}
return nil;
}
_inputMode =
(_machine->keyboard_machine() && _machine->keyboard_machine()->get_keyboard().is_exclusive())
@@ -410,14 +495,14 @@ struct ActivityObserver: public Activity::Observer {
}
- (void)clearAllKeys {
auto keyboard_machine = _machine->keyboard_machine();
const auto keyboard_machine = _machine->keyboard_machine();
if(keyboard_machine) {
@synchronized(self) {
keyboard_machine->get_keyboard().reset_all_keys();
}
}
auto joystick_machine = _machine->joystick_machine();
const auto joystick_machine = _machine->joystick_machine();
if(joystick_machine) {
@synchronized(self) {
for(auto &joystick : joystick_machine->get_joysticks()) {
@@ -425,6 +510,31 @@ struct ActivityObserver: public Activity::Observer {
}
}
}
const auto mouse_machine = _machine->mouse_machine();
if(mouse_machine) {
@synchronized(self) {
mouse_machine->get_mouse().reset_all_buttons();
}
}
}
- (void)setMouseButton:(int)button isPressed:(BOOL)isPressed {
auto mouse_machine = _machine->mouse_machine();
if(mouse_machine) {
@synchronized(self) {
mouse_machine->get_mouse().set_button_pressed(button % mouse_machine->get_mouse().get_number_of_buttons(), isPressed);
}
}
}
- (void)addMouseMotionX:(CGFloat)deltaX y:(CGFloat)deltaY {
auto mouse_machine = _machine->mouse_machine();
if(mouse_machine) {
@synchronized(self) {
mouse_machine->get_mouse().move(int(deltaX), int(deltaY));
}
}
}
#pragma mark - Options
@@ -540,6 +650,10 @@ struct ActivityObserver: public Activity::Observer {
return !!_machine->joystick_machine();
}
- (BOOL)hasMouse {
return !!_machine->mouse_machine();
}
- (BOOL)hasExclusiveKeyboard {
return !!_machine->keyboard_machine() && _machine->keyboard_machine()->get_keyboard().is_exclusive();
}

View File

@@ -8,4 +8,4 @@
#include "ROMMachine.hpp"
ROMMachine::ROMFetcher CSROMFetcher();
ROMMachine::ROMFetcher CSROMFetcher(std::vector<ROMMachine::ROM> *missing_roms = nullptr);

View File

@@ -14,15 +14,39 @@
#include <string>
ROMMachine::ROMFetcher CSROMFetcher() {
return [] (const std::string &machine, const std::vector<std::string> &names) -> std::vector<std::unique_ptr<std::vector<std::uint8_t>>> {
NSString *subDirectory = [@"ROMImages/" stringByAppendingString:[NSString stringWithUTF8String:machine.c_str()]];
std::vector<std::unique_ptr<std::vector<std::uint8_t>>> results;
for(const auto &name: names) {
NSData *fileData = [[NSBundle mainBundle] dataForResource:[NSString stringWithUTF8String:name.c_str()] withExtension:nil subdirectory:subDirectory];
ROMMachine::ROMFetcher CSROMFetcher(std::vector<ROMMachine::ROM> *missing_roms) {
return [missing_roms] (const std::vector<ROMMachine::ROM> &roms) -> std::vector<std::unique_ptr<std::vector<std::uint8_t>>> {
NSArray<NSURL *> *const supportURLs = [[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask];
if(!fileData)
std::vector<std::unique_ptr<std::vector<std::uint8_t>>> results;
for(const auto &rom: roms) {
NSData *fileData;
NSString *const subdirectory = [@"ROMImages/" stringByAppendingString:[NSString stringWithUTF8String:rom.machine_name.c_str()]];
// Check for this file first within the application support directories.
for(NSURL *supportURL in supportURLs) {
NSURL *const fullURL = [[supportURL URLByAppendingPathComponent:subdirectory]
URLByAppendingPathComponent:[NSString stringWithUTF8String:rom.file_name.c_str()]];
fileData = [NSData dataWithContentsOfURL:fullURL];
if(fileData) break;
}
// Failing that, check inside the application bundle.
if(!fileData) {
fileData = [[NSBundle mainBundle]
dataForResource:[NSString stringWithUTF8String:rom.file_name.c_str()]
withExtension:nil
subdirectory:subdirectory];
}
// Store an appropriate result, accumulating a list of the missing if requested.
if(!fileData) {
results.emplace_back(nullptr);
if(missing_roms) {
missing_roms->push_back(rom);
}
}
else {
std::unique_ptr<std::vector<std::uint8_t>> data(new std::vector<std::uint8_t>);
*data = fileData.stdVector8;

View File

@@ -0,0 +1,17 @@
//
// NSData+CRC32.m
// Clock Signal
//
// Created by Thomas Harte on 22/07/2019.
// Copyright 2019 Thomas Harte. All rights reserved.
//
#import <Foundation/Foundation.h>
#include <stdint.h>
@interface NSData (CRC32)
@property(nonnull, nonatomic, readonly) NSNumber *crc32;
@end

View File

@@ -0,0 +1,19 @@
//
// NSData+CRC32.m
// Clock Signal
//
// Created by Thomas Harte on 22/07/2019.
// Copyright 2019 Thomas Harte. All rights reserved.
//
#import "NSData+CRC32.h"
#include <zlib.h>
@implementation NSData (StdVector)
- (NSNumber *)crc32 {
return @(crc32(crc32(0, Z_NULL, 0), self.bytes, (uInt)self.length));
}
@end

View File

@@ -29,6 +29,13 @@ typedef NS_ENUM(NSInteger, CSMachineCPCModel) {
CSMachineCPCModel6128
};
typedef NS_ENUM(NSInteger, CSMachineMacintoshModel) {
CSMachineMacintoshModel128k,
CSMachineMacintoshModel512k,
CSMachineMacintoshModel512ke,
CSMachineMacintoshModelPlus,
};
typedef NS_ENUM(NSInteger, CSMachineOricModel) {
CSMachineOricModelOric1,
CSMachineOricModelOricAtmos,
@@ -69,6 +76,7 @@ typedef int Kilobytes;
- (instancetype)initWithZX80MemorySize:(Kilobytes)memorySize useZX81ROM:(BOOL)useZX81ROM;
- (instancetype)initWithZX81MemorySize:(Kilobytes)memorySize;
- (instancetype)initWithAppleIIModel:(CSMachineAppleIIModel)model diskController:(CSMachineAppleIIDiskController)diskController;
- (instancetype)initWithMacintoshModel:(CSMachineMacintoshModel)model;
@property(nonatomic, readonly) NSString *optionsPanelNibName;
@property(nonatomic, readonly) NSString *displayName;

View File

@@ -17,6 +17,7 @@
#include "../../../../../Analyser/Static/AmstradCPC/Target.hpp"
#include "../../../../../Analyser/Static/AppleII/Target.hpp"
#include "../../../../../Analyser/Static/Commodore/Target.hpp"
#include "../../../../../Analyser/Static/Macintosh/Target.hpp"
#include "../../../../../Analyser/Static/MSX/Target.hpp"
#include "../../../../../Analyser/Static/Oric/Target.hpp"
#include "../../../../../Analyser/Static/ZX8081/Target.hpp"
@@ -188,7 +189,27 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K
_targets.push_back(std::move(target));
}
return self;
}
- (instancetype)initWithMacintoshModel:(CSMachineMacintoshModel)model {
self = [super init];
if(self) {
using Target = Analyser::Static::Macintosh::Target;
std::unique_ptr<Target> target(new Target);
target->machine = Analyser::Machine::Macintosh;
using Model = Target::Model;
switch(model) {
default:
case CSMachineMacintoshModel128k: target->model = Model::Mac128k; break;
case CSMachineMacintoshModel512k: target->model = Model::Mac512k; break;
case CSMachineMacintoshModel512ke: target->model = Model::Mac512ke; break;
case CSMachineMacintoshModelPlus: target->model = Model::MacPlus; break;
}
_targets.push_back(std::move(target));
}
return self;
}
- (NSString *)optionsPanelNibName {

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14460.31"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@@ -17,14 +17,14 @@
<window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" titleVisibility="hidden" id="QvC-M9-y7g">
<windowStyleMask key="styleMask" titled="YES" documentModal="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="196" y="240" width="600" height="205"/>
<rect key="contentRect" x="196" y="240" width="650" height="205"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
<rect key="frame" x="0.0" y="0.0" width="600" height="205"/>
<rect key="frame" x="0.0" y="0.0" width="650" height="205"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="hKn-1l-OSN">
<rect key="frame" x="499" y="13" width="87" height="32"/>
<rect key="frame" x="549" y="13" width="87" height="32"/>
<buttonCell key="cell" type="push" title="Choose" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="MnM-xo-4Qa">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
@@ -37,7 +37,7 @@ DQ
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="JQy-Cj-AOK">
<rect key="frame" x="418" y="13" width="82" height="32"/>
<rect key="frame" x="468" y="13" width="82" height="32"/>
<buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="sub-rB-Req">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
@@ -59,12 +59,12 @@ Gw
</textFieldCell>
</textField>
<tabView translatesAutoresizingMaskIntoConstraints="NO" id="VUb-QG-x7c">
<rect key="frame" x="13" y="51" width="574" height="140"/>
<rect key="frame" x="13" y="51" width="624" height="140"/>
<font key="font" metaFont="system"/>
<tabViewItems>
<tabViewItem label="Apple II" identifier="appleii" id="P59-QG-LOa">
<view key="view" id="dHz-Yv-GNq">
<rect key="frame" x="10" y="33" width="554" height="94"/>
<rect key="frame" x="10" y="33" width="604" height="94"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="V5Z-dX-Ns4">
@@ -130,7 +130,7 @@ Gw
</tabViewItem>
<tabViewItem label="Amstrad CPC" identifier="cpc" id="JmB-OF-xcM">
<view key="view" id="5zS-Nj-Ynx">
<rect key="frame" x="10" y="33" width="554" height="94"/>
<rect key="frame" x="10" y="33" width="604" height="94"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="00d-sg-Krh">
@@ -168,18 +168,18 @@ Gw
</tabViewItem>
<tabViewItem label="Electron" identifier="electron" id="muc-z9-Vqc">
<view key="view" id="SRc-2D-95G">
<rect key="frame" x="10" y="33" width="554" height="93"/>
<rect key="frame" x="10" y="33" width="604" height="94"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="JqM-IK-FMP">
<rect key="frame" x="15" y="74" width="164" height="18"/>
<rect key="frame" x="15" y="75" width="164" height="18"/>
<buttonCell key="cell" type="check" title="With Disk Filing System" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="tpW-5C-xKp">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="945-wU-JOH">
<rect key="frame" x="15" y="54" width="228" height="18"/>
<rect key="frame" x="15" y="55" width="228" height="18"/>
<buttonCell key="cell" type="check" title="With Advanced Disk Filing System" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="S0c-Jg-7Pu">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
@@ -197,9 +197,31 @@ Gw
</constraints>
</view>
</tabViewItem>
<tabViewItem label="Macintosh" identifier="mac" id="lmR-z3-xSm">
<view key="view" id="7Yf-vi-Q0W">
<rect key="frame" x="10" y="33" width="604" height="94"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="F6e-UC-25u">
<rect key="frame" x="15" y="74" width="574" height="17"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="At present Clock Signal emulates only the Macintosh 512ke." id="IGV-Yp-6Af">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="F6e-UC-25u" secondAttribute="trailing" constant="17" id="42z-hS-aPq"/>
<constraint firstItem="F6e-UC-25u" firstAttribute="leading" secondItem="7Yf-vi-Q0W" secondAttribute="leading" constant="17" id="bIg-C3-xdz"/>
<constraint firstItem="F6e-UC-25u" firstAttribute="top" secondItem="7Yf-vi-Q0W" secondAttribute="top" constant="3" id="cxs-OP-oH5"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="F6e-UC-25u" secondAttribute="bottom" constant="17" id="vpF-ER-pmD"/>
</constraints>
</view>
</tabViewItem>
<tabViewItem label="MSX" identifier="msx" id="6SR-DY-zdI">
<view key="view" id="mWD-An-tR7">
<rect key="frame" x="10" y="33" width="554" height="94"/>
<rect key="frame" x="10" y="33" width="604" height="94"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="8xT-Pr-8SE">
@@ -233,6 +255,7 @@ Gw
</textField>
</subviews>
<constraints>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="LG6-mP-SeG" secondAttribute="trailing" constant="17" id="0Oc-n7-gaM"/>
<constraint firstItem="8xT-Pr-8SE" firstAttribute="top" secondItem="LG6-mP-SeG" secondAttribute="bottom" constant="6" id="LBt-4m-GDc"/>
<constraint firstItem="LG6-mP-SeG" firstAttribute="top" secondItem="mWD-An-tR7" secondAttribute="top" constant="3" id="bcb-ZZ-VpQ"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="8xT-Pr-8SE" secondAttribute="trailing" constant="17" id="l8P-UW-8ig"/>
@@ -246,7 +269,7 @@ Gw
</tabViewItem>
<tabViewItem label="Oric" identifier="oric" id="NSx-DC-p4M">
<view key="view" id="sOR-e0-8iZ">
<rect key="frame" x="10" y="33" width="554" height="94"/>
<rect key="frame" x="10" y="33" width="604" height="94"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="0ct-tf-uRH">
@@ -311,7 +334,7 @@ Gw
</tabViewItem>
<tabViewItem label="Vic-20" identifier="vic20" id="cyO-PU-hSU">
<view key="view" id="fLI-XB-QCr">
<rect key="frame" x="10" y="33" width="554" height="94"/>
<rect key="frame" x="10" y="33" width="604" height="94"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ueK-gq-gaF">
@@ -388,11 +411,11 @@ Gw
</tabViewItem>
<tabViewItem label="ZX80" identifier="zx80" id="tMH-kF-GUz">
<view key="view" id="8hL-Vn-Hg0">
<rect key="frame" x="10" y="33" width="554" height="93"/>
<rect key="frame" x="10" y="33" width="554" height="94"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="I1a-Eu-5UB">
<rect key="frame" x="106" y="66" width="115" height="25"/>
<rect key="frame" x="106" y="67" width="115" height="25"/>
<popUpButtonCell key="cell" type="push" title="Unexpanded" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="4Sa-jR-xOd" id="B8M-do-Yod">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@@ -406,7 +429,7 @@ Gw
</popUpButtonCell>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="NCX-4e-lSu">
<rect key="frame" x="15" y="71" width="87" height="17"/>
<rect key="frame" x="15" y="72" width="87" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory Size:" id="e6x-TE-OC5">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@@ -414,7 +437,7 @@ Gw
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ReP-bV-Thu">
<rect key="frame" x="15" y="47" width="114" height="18"/>
<rect key="frame" x="15" y="48" width="114" height="18"/>
<buttonCell key="cell" type="check" title="Use ZX81 ROM" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="VQH-nv-Pfm">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
@@ -436,11 +459,11 @@ Gw
</tabViewItem>
<tabViewItem label="ZX81" identifier="zx81" id="Wnn-nQ-gZ6">
<view key="view" id="bmd-gL-gzT">
<rect key="frame" x="10" y="33" width="554" height="93"/>
<rect key="frame" x="10" y="33" width="554" height="94"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="5aO-UX-HnX">
<rect key="frame" x="106" y="66" width="115" height="25"/>
<rect key="frame" x="106" y="67" width="115" height="25"/>
<popUpButtonCell key="cell" type="push" title="Unexpanded" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="7QC-Ij-hES" id="d3W-Gl-3Mf">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@@ -454,7 +477,7 @@ Gw
</popUpButtonCell>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="8tU-73-XEE">
<rect key="frame" x="15" y="71" width="87" height="17"/>
<rect key="frame" x="15" y="72" width="87" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory Size:" id="z4b-oR-Yl2">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@@ -490,7 +513,7 @@ Gw
<constraint firstItem="VUb-QG-x7c" firstAttribute="top" secondItem="EiT-Mj-1SZ" secondAttribute="top" constant="20" id="zT3-Ea-QQJ"/>
</constraints>
</view>
<point key="canvasLocation" x="34" y="89"/>
<point key="canvasLocation" x="34" y="88.5"/>
</window>
<customObject id="192-Eb-Rpg" customClass="MachinePicker" customModule="Clock_Signal" customModuleProvider="target">
<connections>

View File

@@ -157,6 +157,9 @@ class MachinePicker: NSObject {
default: return CSStaticAnalyser(amstradCPCModel: .model6128)
}
case "mac":
return CSStaticAnalyser(macintoshModel: .model512ke)
case "msx":
let hasDiskDrive = msxHasDiskDriveButton!.state == .on
switch msxRegionButton!.selectedItem?.tag {

View File

@@ -0,0 +1,27 @@
//
// CSROMReceiverView.h
// Clock Signal
//
// Created by Thomas Harte on 22/07/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@class CSROMReceiverView;
@protocol CSROMReciverViewDelegate
/*!
Announces receipt of a file by drag and drop to the delegate.
@param view The view making the request.
@param URL The file URL of the received file.
*/
- (void)romReceiverView:(nonnull CSROMReceiverView *)view didReceiveFileAtURL:(nonnull NSURL *)URL;
@end
@interface CSROMReceiverView : NSView
@property(nonatomic, weak, nullable) id <CSROMReciverViewDelegate> delegate;
@end

View File

@@ -0,0 +1,40 @@
//
// CSROMReceiverView.m
// Clock Signal
//
// Created by Thomas Harte on 22/07/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#import "CSROMReceiverView.h"
@interface CSROMReceiverView () <NSDraggingDestination>
@end
@implementation CSROMReceiverView
- (void)awakeFromNib {
[super awakeFromNib];
// Accept file URLs by drag and drop.
[self registerForDraggedTypes:@[(__bridge NSString *)kUTTypeFileURL]];
}
#pragma mark - NSDraggingDestination
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender {
// Just forward the URLs.
for(NSPasteboardItem *item in [[sender draggingPasteboard] pasteboardItems]) {
NSURL *URL = [NSURL URLWithString:[item stringForType:(__bridge NSString *)kUTTypeFileURL]];
[self.delegate romReceiverView:self didReceiveFileAtURL:URL];
}
return YES;
}
- (NSDragOperation)draggingEntered:(id < NSDraggingInfo >)sender {
// The emulator will take a copy of any deposited ROM files.
return NSDragOperationCopy;
}
@end

View File

@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="MachineDocument" customModule="Clock_Signal" customModuleProvider="target">
<connections>
<outlet property="romReceiverErrorField" destination="Bia-0m-GxK" id="wnH-Kz-uXD"/>
<outlet property="romReceiverView" destination="EiT-Mj-1SZ" id="Y2M-Qd-i7y"/>
<outlet property="romRequesterPanel" destination="QvC-M9-y7g" id="saI-YH-9NP"/>
<outlet property="romRequesterText" destination="5qG-I3-Qav" id="J9M-T8-Xyy"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" titleVisibility="hidden" id="QvC-M9-y7g">
<windowStyleMask key="styleMask" titled="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="196" y="240" width="480" height="270"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ" customClass="CSROMReceiverView">
<rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="5qG-I3-Qav">
<rect key="frame" x="18" y="148" width="444" height="102"/>
<textFieldCell key="cell" enabled="NO" allowsUndo="NO" id="itJ-2T-0ia">
<font key="font" usesAppearanceFont="YES"/>
<string key="title">Clock Signal requires you to provide images of the system ROMs for this machine. They will be stored permanently; you need do this only once.Please drag and drop the following over this view:
</string>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="nvl-dm-8bK">
<rect key="frame" x="384" y="13" width="82" height="32"/>
<buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="oS1-Pk-LsO">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
DQ
</string>
</buttonCell>
<connections>
<action selector="cancelRequestROMs:" target="-2" id="58q-yi-dlv"/>
</connections>
</button>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Bia-0m-GxK">
<rect key="frame" x="20" y="61" width="442" height="17"/>
<textFieldCell key="cell" allowsUndo="NO" alignment="center" title="Multiline Label" drawsBackground="YES" id="8jl-xs-LjP">
<font key="font" metaFont="message"/>
<color key="textColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="tertiaryLabelColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstItem="Bia-0m-GxK" firstAttribute="leading" secondItem="EiT-Mj-1SZ" secondAttribute="leading" constant="20" id="9Lj-3p-7Vr"/>
<constraint firstAttribute="trailing" secondItem="5qG-I3-Qav" secondAttribute="trailing" constant="20" id="AwT-WO-Nyf"/>
<constraint firstAttribute="bottom" secondItem="nvl-dm-8bK" secondAttribute="bottom" constant="20" id="BU0-5T-YRH"/>
<constraint firstAttribute="bottom" secondItem="Bia-0m-GxK" secondAttribute="bottom" constant="61" id="ENn-7a-NGa"/>
<constraint firstAttribute="trailing" secondItem="Bia-0m-GxK" secondAttribute="trailing" constant="18" id="UUy-bf-87w"/>
<constraint firstItem="5qG-I3-Qav" firstAttribute="leading" secondItem="EiT-Mj-1SZ" secondAttribute="leading" constant="20" id="bRM-z1-cRf"/>
<constraint firstAttribute="trailing" secondItem="nvl-dm-8bK" secondAttribute="trailing" constant="20" id="eQI-Nj-ane"/>
<constraint firstItem="nvl-dm-8bK" firstAttribute="top" relation="greaterThanOrEqual" secondItem="5qG-I3-Qav" secondAttribute="bottom" constant="17" id="plB-x4-He5"/>
<constraint firstItem="5qG-I3-Qav" firstAttribute="top" secondItem="EiT-Mj-1SZ" secondAttribute="top" constant="20" id="qeC-Rh-hmr"/>
</constraints>
</view>
</window>
</objects>
</document>

View File

@@ -63,6 +63,29 @@ typedef NS_ENUM(NSInteger, CSOpenGLViewRedrawEvent) {
*/
- (void)paste:(nonnull id)sender;
@optional
/*!
Supplies a mouse moved event to the delegate. This functions only if
shouldCaptureMouse is set to YES, in which case the view will ensure it captures
the mouse and returns only relative motion
(Cf. CGAssociateMouseAndMouseCursorPosition). It will also elide mouseDragged:
(and rightMouseDragged:, etc) and mouseMoved: events.
*/
- (void)mouseMoved:(nonnull NSEvent *)event;
/*!
Supplies a mouse button down event. This elides mouseDown, rightMouseDown and otherMouseDown.
@c shouldCaptureMouse must be set to @c YES to receive these events.
*/
- (void)mouseDown:(nonnull NSEvent *)event;
/*!
Supplies a mouse button up event. This elides mouseUp, rightMouseUp and otherMouseUp.
@c shouldCaptureMouse must be set to @c YES to receive these events.
*/
- (void)mouseUp:(nonnull NSEvent *)event;
@end
/*!
@@ -74,6 +97,8 @@ typedef NS_ENUM(NSInteger, CSOpenGLViewRedrawEvent) {
@property (atomic, weak, nullable) id <CSOpenGLViewDelegate> delegate;
@property (nonatomic, weak, nullable) id <CSOpenGLViewResponderDelegate> responderDelegate;
@property (nonatomic, assign) BOOL shouldCaptureMouse;
/*!
Ends the timer tracking time; should be called prior to giving up the last owning reference
to ensure that any retain cycles implied by the timer are resolved.
@@ -89,4 +114,9 @@ typedef NS_ENUM(NSInteger, CSOpenGLViewRedrawEvent) {
*/
- (void)performWithGLContext:(nonnull dispatch_block_t)action;
/*!
Instructs that the mouse cursor, if currently captured, should be released.
*/
- (void)releaseMouse;
@end

View File

@@ -19,9 +19,12 @@
NSTrackingArea *_mouseTrackingArea;
NSTimer *_mouseHideTimer;
BOOL _mouseIsCaptured;
}
- (void)prepareOpenGL {
[super prepareOpenGL];
// Synchronize buffer swaps with vertical refresh rate
GLint swapInt = 1;
[[self openGLContext] setValues:&swapInt forParameter:NSOpenGLCPSwapInterval];
@@ -146,6 +149,13 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
- (void)flagsChanged:(NSEvent *)theEvent {
[self.responderDelegate flagsChanged:theEvent];
// Release the mouse upon a control + command.
if(_mouseIsCaptured &&
theEvent.modifierFlags & NSEventModifierFlagControl &&
theEvent.modifierFlags & NSEventModifierFlagCommand) {
[self releaseMouse];
}
}
- (void)paste:(id)sender {
@@ -168,6 +178,10 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
#pragma mark - Mouse hiding
- (void)setShouldCaptureMouse:(BOOL)shouldCaptureMouse {
_shouldCaptureMouse = shouldCaptureMouse;
}
- (void)updateTrackingAreas {
[super updateTrackingAreas];
@@ -183,9 +197,14 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
[self addTrackingArea:_mouseTrackingArea];
}
- (void)mouseMoved:(NSEvent *)event {
[super mouseMoved:event];
[self scheduleMouseHide];
- (void)scheduleMouseHide {
if(!self.shouldCaptureMouse) {
[_mouseHideTimer invalidate];
_mouseHideTimer = [NSTimer scheduledTimerWithTimeInterval:3.0 repeats:NO block:^(NSTimer * _Nonnull timer) {
[NSCursor setHiddenUntilMouseMoves:YES];
}];
}
}
- (void)mouseEntered:(NSEvent *)event {
@@ -193,18 +212,119 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
[self scheduleMouseHide];
}
- (void)scheduleMouseHide {
[_mouseHideTimer invalidate];
_mouseHideTimer = [NSTimer scheduledTimerWithTimeInterval:3.0 repeats:NO block:^(NSTimer * _Nonnull timer) {
[NSCursor setHiddenUntilMouseMoves:YES];
}];
}
- (void)mouseExited:(NSEvent *)event {
[super mouseExited:event];
[_mouseHideTimer invalidate];
_mouseHideTimer = nil;
}
- (void)releaseMouse {
if(_mouseIsCaptured) {
_mouseIsCaptured = NO;
CGAssociateMouseAndMouseCursorPosition(true);
[NSCursor unhide];
}
}
#pragma mark - Mouse motion
- (void)applyMouseMotion:(NSEvent *)event {
if(!self.shouldCaptureMouse) {
// Mouse capture is off, so don't play games with the cursor, just schedule it to
// hide in the near future.
[self scheduleMouseHide];
} else {
if(_mouseIsCaptured) {
// Mouse capture is on, so move the cursor back to the middle of the window, and
// forward the deltas to the listener.
//
// TODO: should I really need to invert the y coordinate myself? It suggests I
// might have an error in mapping here.
const NSPoint windowCentre = [self convertPoint:CGPointMake(self.bounds.size.width * 0.5, self.bounds.size.height * 0.5) toView:nil];
const NSPoint screenCentre = [self.window convertPointToScreen:windowCentre];
const CGRect screenFrame = self.window.screen.frame;
CGWarpMouseCursorPosition(NSMakePoint(
screenFrame.origin.x + screenCentre.x,
screenFrame.origin.y + screenFrame.size.height - screenCentre.y
));
[self.responderDelegate mouseMoved:event];
}
}
}
- (void)mouseDragged:(NSEvent *)event {
[self applyMouseMotion:event];
[super mouseDragged:event];
}
- (void)rightMouseDragged:(NSEvent *)event {
[self applyMouseMotion:event];
[super rightMouseDragged:event];
}
- (void)otherMouseDragged:(NSEvent *)event {
[self applyMouseMotion:event];
[super otherMouseDragged:event];
}
- (void)mouseMoved:(NSEvent *)event {
[self applyMouseMotion:event];
[super mouseMoved:event];
}
#pragma mark - Mouse buttons
- (void)applyButtonDown:(NSEvent *)event {
if(self.shouldCaptureMouse) {
if(!_mouseIsCaptured) {
_mouseIsCaptured = YES;
[NSCursor hide];
CGAssociateMouseAndMouseCursorPosition(false);
// Don't report the first click to the delegate; treat that as merely
// an invitation to capture the cursor.
return;
}
[self.responderDelegate mouseDown:event];
}
}
- (void)applyButtonUp:(NSEvent *)event {
if(self.shouldCaptureMouse) {
[self.responderDelegate mouseUp:event];
}
}
- (void)mouseDown:(NSEvent *)event {
[self applyButtonDown:event];
[super mouseDown:event];
}
- (void)rightMouseDown:(NSEvent *)event {
[self applyButtonDown:event];
[super rightMouseDown:event];
}
- (void)otherMouseDown:(NSEvent *)event {
[self applyButtonDown:event];
[super otherMouseDown:event];
}
- (void)mouseUp:(NSEvent *)event {
[self applyButtonUp:event];
[super mouseUp:event];
}
- (void)rightMouseUp:(NSEvent *)event {
[self applyButtonUp:event];
[super rightMouseUp:event];
}
- (void)otherMouseUp:(NSEvent *)event {
[self applyButtonUp:event];
[super otherMouseUp:event];
}
@end

View File

@@ -11,118 +11,244 @@ import Foundation
class MOS6522Tests: XCTestCase {
fileprivate func with6522(_ action: (MOS6522Bridge) -> ()) {
let bridge = MOS6522Bridge()
action(bridge)
private var m6522: MOS6522Bridge!
override func setUp() {
m6522 = MOS6522Bridge()
}
// MARK: Timer tests
func testTimerCount() {
with6522 {
// set timer 1 to a value of $000a
$0.setValue(10, forRegister: 4)
$0.setValue(0, forRegister: 5)
// set timer 1 to a value of m652200a
m6522.setValue(10, forRegister: 4)
m6522.setValue(0, forRegister: 5)
// complete the setting cycle
$0.run(forHalfCycles: 2)
// complete the setting cycle
m6522.run(forHalfCycles: 2)
// run for 5 cycles
$0.run(forHalfCycles: 10)
// run for 5 cycles
m6522.run(forHalfCycles: 10)
// check that the timer has gone down by 5
XCTAssert($0.value(forRegister: 4) == 5, "Low order byte should be 5; was \($0.value(forRegister: 4))")
XCTAssert($0.value(forRegister: 5) == 0, "High order byte should be 0; was \($0.value(forRegister: 5))")
}
// check that the timer has gone down by 5
XCTAssert(m6522.value(forRegister: 4) == 5, "Low order byte should be 5; was \(m6522.value(forRegister: 4))")
XCTAssert(m6522.value(forRegister: 5) == 0, "High order byte should be 0; was \(m6522.value(forRegister: 5))")
}
func testTimerLatches() {
with6522 {
// set timer 2 to $1020
$0.setValue(0x10, forRegister: 8)
$0.setValue(0x20, forRegister: 9)
// set timer 2 to $1020
m6522.setValue(0x10, forRegister: 8)
m6522.setValue(0x20, forRegister: 9)
// change the low-byte latch
$0.setValue(0x40, forRegister: 8)
// change the low-byte latch
m6522.setValue(0x40, forRegister: 8)
// complete the cycle
$0.run(forHalfCycles: 2)
// complete the cycle
m6522.run(forHalfCycles: 2)
// chek that the new latched value hasn't been copied
XCTAssert($0.value(forRegister: 8) == 0x10, "Low order byte should be 0x10; was \($0.value(forRegister: 8))")
XCTAssert($0.value(forRegister: 9) == 0x20, "High order byte should be 0x20; was \($0.value(forRegister: 9))")
// chek that the new latched value hasn't been copied
XCTAssert(m6522.value(forRegister: 8) == 0x10, "Low order byte should be 0x10; was \(m6522.value(forRegister: 8))")
XCTAssert(m6522.value(forRegister: 9) == 0x20, "High order byte should be 0x20; was \(m6522.value(forRegister: 9))")
// write the low-byte latch
$0.setValue(0x50, forRegister: 9)
// write the low-byte latch
m6522.setValue(0x50, forRegister: 9)
// complete the cycle
$0.run(forHalfCycles: 2)
// complete the cycle
m6522.run(forHalfCycles: 2)
// chek that the latched value has been copied
XCTAssert($0.value(forRegister: 8) == 0x40, "Low order byte should be 0x50; was \($0.value(forRegister: 8))")
XCTAssert($0.value(forRegister: 9) == 0x50, "High order byte should be 0x40; was \($0.value(forRegister: 9))")
}
// chek that the latched value has been copied
XCTAssert(m6522.value(forRegister: 8) == 0x40, "Low order byte should be 0x50; was \(m6522.value(forRegister: 8))")
XCTAssert(m6522.value(forRegister: 9) == 0x50, "High order byte should be 0x40; was \(m6522.value(forRegister: 9))")
}
func testTimerReload() {
with6522 {
// set timer 1 to a value of $0010, enable repeating mode
$0.setValue(16, forRegister: 4)
$0.setValue(0, forRegister: 5)
$0.setValue(0x40, forRegister: 11)
$0.setValue(0x40 | 0x80, forRegister: 14)
// set timer 1 to a value of m6522010, enable repeating mode
m6522.setValue(16, forRegister: 4)
m6522.setValue(0, forRegister: 5)
m6522.setValue(0x40, forRegister: 11)
m6522.setValue(0x40 | 0x80, forRegister: 14)
// complete the cycle to set initial values
$0.run(forHalfCycles: 2)
// complete the cycle to set initial values
m6522.run(forHalfCycles: 2)
// run for 16 cycles
$0.run(forHalfCycles: 32)
// run for 16 cycles
m6522.run(forHalfCycles: 32)
// check that the timer has gone down to 0 but not yet triggered an interrupt
XCTAssert($0.value(forRegister: 4) == 0, "Low order byte should be 0; was \($0.value(forRegister: 4))")
XCTAssert($0.value(forRegister: 5) == 0, "High order byte should be 0; was \($0.value(forRegister: 5))")
XCTAssert(!$0.irqLine, "IRQ should not yet be active")
// check that the timer has gone down to 0 but not yet triggered an interrupt
XCTAssert(m6522.value(forRegister: 4) == 0, "Low order byte should be 0; was \(m6522.value(forRegister: 4))")
XCTAssert(m6522.value(forRegister: 5) == 0, "High order byte should be 0; was \(m6522.value(forRegister: 5))")
XCTAssert(!m6522.irqLine, "IRQ should not yet be active")
// check that two half-cycles later the timer is $ffff but IRQ still hasn't triggered
$0.run(forHalfCycles: 2)
XCTAssert($0.value(forRegister: 4) == 0xff, "Low order byte should be 0xff; was \($0.value(forRegister: 4))")
XCTAssert($0.value(forRegister: 5) == 0xff, "High order byte should be 0xff; was \($0.value(forRegister: 5))")
XCTAssert(!$0.irqLine, "IRQ should not yet be active")
// check that two half-cycles later the timer is $ffff but IRQ still hasn't triggered
m6522.run(forHalfCycles: 2)
XCTAssert(m6522.value(forRegister: 4) == 0xff, "Low order byte should be 0xff; was \(m6522.value(forRegister: 4))")
XCTAssert(m6522.value(forRegister: 5) == 0xff, "High order byte should be 0xff; was \(m6522.value(forRegister: 5))")
XCTAssert(!m6522.irqLine, "IRQ should not yet be active")
// check that one half-cycle later the timer is still $ffff and IRQ has triggered...
$0.run(forHalfCycles: 1)
XCTAssert($0.irqLine, "IRQ should be active")
XCTAssert($0.value(forRegister: 4) == 0xff, "Low order byte should be 0xff; was \($0.value(forRegister: 4))")
XCTAssert($0.value(forRegister: 5) == 0xff, "High order byte should be 0xff; was \($0.value(forRegister: 5))")
// check that one half-cycle later the timer is still $ffff and IRQ has triggered...
m6522.run(forHalfCycles: 1)
XCTAssert(m6522.irqLine, "IRQ should be active")
XCTAssert(m6522.value(forRegister: 4) == 0xff, "Low order byte should be 0xff; was \(m6522.value(forRegister: 4))")
XCTAssert(m6522.value(forRegister: 5) == 0xff, "High order byte should be 0xff; was \(m6522.value(forRegister: 5))")
// ... but that reading the timer cleared the interrupt
XCTAssert(!$0.irqLine, "IRQ should be active")
// ... but that reading the timer cleared the interrupt
XCTAssert(!m6522.irqLine, "IRQ should be active")
// check that one half-cycles later the timer has reloaded
$0.run(forHalfCycles: 1)
XCTAssert($0.value(forRegister: 4) == 0x10, "Low order byte should be 0x10; was \($0.value(forRegister: 4))")
XCTAssert($0.value(forRegister: 5) == 0x00, "High order byte should be 0x00; was \($0.value(forRegister: 5))")
}
// check that one half-cycles later the timer has reloaded
m6522.run(forHalfCycles: 1)
XCTAssert(m6522.value(forRegister: 4) == 0x10, "Low order byte should be 0x10; was \(m6522.value(forRegister: 4))")
XCTAssert(m6522.value(forRegister: 5) == 0x00, "High order byte should be 0x00; was \(m6522.value(forRegister: 5))")
}
// MARK: Data direction tests
func testDataDirection() {
with6522 {
// set low four bits of register B as output, the top four as input
$0.setValue(0xf0, forRegister: 2)
// set low four bits of register B as output, the top four as input
m6522.setValue(0xf0, forRegister: 2)
// ask to output 0x8c
$0.setValue(0x8c, forRegister: 0)
// ask to output 0x8c
m6522.setValue(0x8c, forRegister: 0)
// complete the cycle
$0.run(forHalfCycles: 2)
// complete the cycle
m6522.run(forHalfCycles: 2)
// set current input as 0xda
$0.portBInput = 0xda
// set current input as 0xda
m6522.portBInput = 0xda
// test that the result of reading register B is therefore 0x8a
XCTAssert($0.value(forRegister: 0) == 0x8a, "Data direction register should mix input and output; got \($0.value(forRegister: 0))")
// test that the result of reading register B is therefore 0x8a
XCTAssert(m6522.value(forRegister: 0) == 0x8a, "Data direction register should mix input and output; got \(m6522.value(forRegister: 0))")
}
func testShiftDisabled() {
/*
Mode 0 disables the Shift Register. In this mode the microprocessor can
write or read the SR and the SR will shift on each CB1 positive edge
shifting in the value on CB2. In this mode the SR Interrupt Flag is
disabled (held to a logic 0).
*/
}
func testShiftInUnderT2() {
/*
In mode 1, the shifting rate is controlled by the low order 8 bits of T2
(Figure 22). Shift pulses are generated on the CB1 pin to control shifting
in external devices. The time between transitions of this output clock is a
function of the system clock period and the contents of the low order T2
latch (N).
The shifting operation is triggered by the read or write of the SR if the
SR flag is set in the IFR. Otherwise the first shift will occur at the next
time-out of T2 after a read or write of the SR. Data is shifted first into
the low order bit of SR and is then shifted into the next higher order bit
of the shift register on the negative-going edge of each clock pulse. The
input data should change before the positive-going edge of the CB1 clock
pulse. This data is shifted into shift register during the 02 clock cycle
following the positive-going edge of the CB1 clock pulse. After 8 CB1 clock
pulses, the shift register interrupt flag will set and IRQ will go low.
*/
}
func testShiftInUnderPhase2() {
/*
In mode 2, the shift rate is a direct function of the system clock
frequency (Figure 23). CB1 becomes an output which generates shift pulses
for controlling external devices. Timer 2 operates as an independent
interval timer and has no effect on SR. The shifting operation is triggered
by reading or writing the Shift Register. Data is shifted, first into bit 0
and is then shifted into the next higher order bit of the shift register on
the trailing edge of each 02 clock pulse. After 8 clock pulses, the shift
register interrupt flag will be set, and the output clock pulses on CB1
will stop.
*/
}
func testShiftInUnderCB1() {
/*
In mode 3, external pin CB1 becomes an input (Figure 24). This allows an
external device to load the shift register at its own pace. The shift
register counter will interrupt the processor each time 8 bits have been
shifted in. However the shift register counter does not stop the shifting
operation; it acts simply as a pulse counter. Reading or writing the Shift
Register resets the Interrupt Flag and initializes the SR counter to count
another 8 pulses.
Note that the data is shifted during the first system clock cycle
following the positive-going edge of the CB1 shift pulse. For this reason,
data must be held stable during the first full cycle following CB1 going
high.
*/
}
func testShiftOutUnderT2FreeRunning() {
/*
Mode 4 is very similar to mode 5 in which the shifting rate is set by T2.
However, in mode 4 the SR Counter does not stop the shifting operation
(Figure 25). Since the Shift Register bit 7 (SR7) is recirculated back into
bit 0, the 8 bits loaded into the Shift Register will be clocked onto CB2
repetitively. In this mode the Shift Register Counter is disabled.
*/
}
func testShiftOutUnderT2() {
/*
In mode 5, the shift rate is controlled by T2 (as in mode 4). The shifting
operation is triggered by the read or write of the SR if the SR flag is set
in the IFR (Figure 26). Otherwise the first shift will occur at the next
time-out of T2 after a read or write of the SR. However, with each read or
write of the shift register the SR Counter is reset and 8 bits are shifted
onto CB2. At the same time, 8 shift pulses are generated on CB1 to control
shifting in external devices. After the 8 shift pulses, the shifting is
disabled, the SR Interrupt Flag is set and CB2 remains at the last data
level.
*/
}
func testShiftOutUnderPhase2() {
/*
In mode 6, the shift rate is controlled by the 02 system clock (Figure 27).
(... and I'm assuming the same behaviour as shift out under control of T2
otherwise, based on original context)
*/
// Set the shift register to a non-zero something.
m6522.setValue(0xaa, forRegister: 10)
// Set shift register mode 6.
m6522.setValue(6 << 2, forRegister: 11)
// Make sure the shift register's interrupt bit is set.
m6522.run(forHalfCycles: 16)
XCTAssertEqual(m6522.value(forRegister: 13) & 0x04, 0x04)
// Test that output is now inhibited: CB2 should remain unchanged.
let initialOutput = m6522.value(forControlLine: .two, port: .B)
for _ in 1...8 {
m6522.run(forHalfCycles: 2)
XCTAssertEqual(m6522.value(forControlLine: .two, port: .B), initialOutput)
}
// Set a new value to the shift register.
m6522.setValue(0x16, forRegister: 10)
// Test that the new value is shifted out.
var output = 0
for _ in 1..<8 {
m6522.run(forHalfCycles: 2)
output = (output << 1) | (m6522.value(forControlLine: .two, port: .B) ? 1 : 0)
}
XCTAssertEqual(output, 0x16)
}
func testShiftOutUnderCB1() {
/*
In mode 7, shifting is controlled by pulses applied to the CB1 pin by an
external device (Figure 28). The SR counter sets the SR Interrupt Flag each
time it counts 8 pulses but it does not disable the shifting function. Each
time the microprocessor, writes or reads the shift register, the SR
Interrupt Flag is reset and the SR counter is initialized to begin counting
the next 8 shift pulses on pin CB1. After 8 shift pulses, the Interrupt
Flag is set. The microprocessor can then load the shift register with the
next byte of data.
*/
}
}

Binary file not shown.

View File

@@ -0,0 +1,5 @@
This file provides a record of which opcodes are valid and which are invalid on a
real 68000. It was generated by AtariZoll and made available via
http://www.atari-forum.com/viewtopic.php?f=68&t=26820 .
No licence was specified.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,291 @@
//
// 68000BCDTests.m
// Clock SignalTests
//
// Created by Thomas Harte on 29/06/2019.
//
// Largely ported from the tests of the Portable 68k Emulator.
//
#import <XCTest/XCTest.h>
#include "TestRunner68000.hpp"
@interface M68000BCDTests : XCTestCase
@end
@implementation M68000BCDTests {
std::unique_ptr<RAM68000> _machine;
}
- (void)setUp {
_machine.reset(new RAM68000());
}
- (void)tearDown {
_machine.reset();
}
// MARK: ABCD
- (void)testABCD {
_machine->set_program({
0xc302, // ABCD D2, D1
});
auto state = _machine->get_processor_state();
state.data[1] = 0x1234567a;
state.data[2] = 0xf745ff78;
_machine->set_processor_state(state);
_machine->run_for_instructions(1);
state = _machine->get_processor_state();
XCTAssert(state.status & Flag::Carry);
XCTAssertEqual(state.data[1], 0x12345658);
XCTAssertEqual(state.data[2], 0xf745ff78);
}
- (void)testABCDZero {
_machine->set_program({
0xc302, // ABCD D2, D1
});
auto state = _machine->get_processor_state();
state.data[1] = 0x12345600;
state.data[2] = 0x12345600;
state.status = Flag::Zero;
_machine->set_processor_state(state);
_machine->run_for_instructions(1);
state = _machine->get_processor_state();
XCTAssert(state.status & Flag::Zero);
XCTAssertEqual(state.data[1], 0x12345600);
XCTAssertEqual(state.data[2], 0x12345600);
}
- (void)testABCDNegative {
_machine->set_program({
0xc302, // ABCD D2, D1
});
auto state = _machine->get_processor_state();
state.data[1] = 0x12345645;
state.data[2] = 0x12345654;
state.status = Flag::Zero;
_machine->set_processor_state(state);
_machine->run_for_instructions(1);
state = _machine->get_processor_state();
XCTAssert(state.status & Flag::Negative);
XCTAssertEqual(state.data[1], 0x12345699);
XCTAssertEqual(state.data[2], 0x12345654);
}
- (void)testABCDWithX {
_machine->set_program({
0xc302, // ABCD D2, D1
});
auto state = _machine->get_processor_state();
state.data[1] = 0x12345645;
state.data[2] = 0x12345654;
state.status = Flag::Extend;
_machine->set_processor_state(state);
_machine->run_for_instructions(1);
state = _machine->get_processor_state();
XCTAssert(state.status & Flag::Carry);
XCTAssertEqual(state.data[1], 0x12345600);
XCTAssertEqual(state.data[2], 0x12345654);
}
- (void)testABCDOverflow {
_machine->set_program({
0xc302, // ABCD D2, D1
});
auto state = _machine->get_processor_state();
state.data[1] = 0x1234563e;
state.data[2] = 0x1234563e;
state.status = Flag::Extend;
_machine->set_processor_state(state);
_machine->run_for_instructions(1);
state = _machine->get_processor_state();
XCTAssert(state.status & Flag::Overflow);
XCTAssertEqual(state.data[1], 0x12345683);
XCTAssertEqual(state.data[2], 0x1234563e);
}
- (void)testABCDPredecDifferent {
_machine->set_program({
0xc30a, // ABCD -(A2), -(A1)
});
*_machine->ram_at(0x3000) = 0xa200;
*_machine->ram_at(0x4000) = 0x1900;
auto state = _machine->get_processor_state();
state.address[1] = 0x3001;
state.address[2] = 0x4001;
state.status = Flag::Extend;
_machine->set_processor_state(state);
_machine->run_for_instructions(1);
state = _machine->get_processor_state();
XCTAssert(state.status & Flag::Carry);
XCTAssert(state.status & Flag::Extend);
XCTAssertEqual(state.address[1], 0x3000);
XCTAssertEqual(state.address[2], 0x4000);
XCTAssertEqual(*_machine->ram_at(0x3000), 0x2200);
XCTAssertEqual(*_machine->ram_at(0x4000), 0x1900);
}
- (void)testABCDPredecSame {
_machine->set_program({
0xc309, // ABCD -(A1), -(A1)
});
*_machine->ram_at(0x3000) = 0x19a2;
auto state = _machine->get_processor_state();
state.address[1] = 0x3002;
state.status = Flag::Extend;
_machine->set_processor_state(state);
_machine->run_for_instructions(1);
state = _machine->get_processor_state();
XCTAssert(state.status & Flag::Carry);
XCTAssert(state.status & Flag::Extend);
XCTAssertEqual(state.address[1], 0x3000);
XCTAssertEqual(*_machine->ram_at(0x3000), 0x22a2);
}
// MARK: NBCD
- (void)performNBCDd1:(uint32_t)d1 ccr:(uint8_t)ccr {
_machine->set_program({
0x4801 // NBCD D1
});
auto state = _machine->get_processor_state();
state.status |= ccr;
state.data[1] = d1;
_machine->set_processor_state(state);
_machine->run_for_instructions(1);
XCTAssertEqual(6, _machine->get_cycle_count());
}
- (void)testNBCD_Dn {
[self performNBCDd1:0x7a ccr:0];
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.data[1], 0x20);
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Carry | Flag::Overflow);
}
- (void)testNBCD_Dn_extend {
[self performNBCDd1:0x1234567a ccr:Flag::Extend | Flag::Zero];
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.data[1], 0x1234561f);
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Carry | Flag::Overflow);
}
- (void)testNBCD_Dn_zero {
[self performNBCDd1:0x12345600 ccr:Flag::Zero];
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.data[1], 0x12345600);
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Zero);
}
- (void)testNBCD_Dn_negative {
[self performNBCDd1:0x123456ff ccr:Flag::Extend | Flag::Zero];
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.data[1], 0x1234569a);
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Carry | Flag::Negative);
}
- (void)testNBCD_Dn_XXXw {
_machine->set_program({
0x4838, 0x3000 // NBCD ($3000).w
});
*_machine->ram_at(0x3000) = 0x0100;
_machine->run_for_instructions(1);
const auto state = _machine->get_processor_state();
XCTAssertEqual(16, _machine->get_cycle_count());
XCTAssertEqual(*_machine->ram_at(0x3000), 0x9900);
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Carry | Flag::Negative);
}
// MARK: SBCD
- (void)performSBCDd1:(uint32_t)d1 d2:(uint32_t)d2 ccr:(uint8_t)ccr {
_machine->set_program({
0x8302 // SBCD D2, D1
});
auto state = _machine->get_processor_state();
state.status |= ccr;
state.data[1] = d1;
state.data[2] = d2;
_machine->set_processor_state(state);
_machine->run_for_instructions(1);
state = _machine->get_processor_state();
XCTAssertEqual(6, _machine->get_cycle_count());
XCTAssertEqual(state.data[2], d2);
}
- (void)testSBCD_Dn {
[self performSBCDd1:0x12345689 d2:0xf745ff78 ccr:Flag::Zero];
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.data[1], 0x12345611);
XCTAssertEqual(state.status & Flag::ConditionCodes, 0);
}
- (void)testSBCD_Dn_zero {
[self performSBCDd1:0x123456ff d2:0xf745ffff ccr:Flag::Zero];
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.data[1], 0x12345600);
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Zero);
}
- (void)testSBCD_Dn_negative {
[self performSBCDd1:0x12345634 d2:0xf745ff45 ccr:Flag::Extend];
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.data[1], 0x12345688);
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Carry | Flag::Negative);
}
- (void)testSBCD_Dn_overflow {
[self performSBCDd1:0x123456a9 d2:0xf745ffff ccr:Flag::Extend];
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.data[1], 0x12345643);
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Carry | Flag::Overflow);
}
- (void)testSBCD_Dn_PreDec {
_machine->set_program({
0x830a // SBCD -(A2), -(A1)
});
*_machine->ram_at(0x3000) = 0xa200;
*_machine->ram_at(0x4000) = 0x1900;
auto state = _machine->get_processor_state();
state.address[1] = 0x3001;
state.address[2] = 0x4001;
state.status |= Flag::Extend;
_machine->set_processor_state(state);
_machine->run_for_instructions(1);
state = _machine->get_processor_state();
XCTAssertEqual(18, _machine->get_cycle_count());
XCTAssertEqual(*_machine->ram_at(0x3000), 0x8200);
XCTAssertEqual(*_machine->ram_at(0x4000), 0x1900);
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Negative);
}
@end

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,557 @@
//
// 68000ControlFlowTests.m
// Clock SignalTests
//
// Created by Thomas Harte on 28/06/2019.
//
// Largely ported from the tests of the Portable 68k Emulator.
//
#import <XCTest/XCTest.h>
#include "TestRunner68000.hpp"
@interface M68000ControlFlowTests : XCTestCase
@end
@implementation M68000ControlFlowTests {
std::unique_ptr<RAM68000> _machine;
}
- (void)setUp {
_machine.reset(new RAM68000());
}
- (void)tearDown {
_machine.reset();
}
// MARK: Bcc
- (void)performBccb:(uint16_t)opcode {
_machine->set_program({
uint16_t(opcode | 6) // Bcc.b +6
});
_machine->run_for_instructions(1);
}
- (void)performBccw:(uint16_t)opcode {
_machine->set_program({
opcode, 0x0006 // Bcc.w +6
});
_machine->run_for_instructions(1);
}
- (void)testBHIb {
[self performBccb:0x6200];
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.program_counter, 0x1008 + 4);
XCTAssertEqual(_machine->get_cycle_count(), 10);
}
- (void)testBLOb {
[self performBccb:0x6500];
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.program_counter, 0x1002 + 4);
XCTAssertEqual(_machine->get_cycle_count(), 8);
}
- (void)testBHIw {
[self performBccw:0x6200];
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.program_counter, 0x1008 + 4);
XCTAssertEqual(_machine->get_cycle_count(), 10);
}
- (void)testBLOw {
[self performBccw:0x6500];
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.program_counter, 0x1004 + 4);
XCTAssertEqual(_machine->get_cycle_count(), 12);
}
// MARK: BRA
- (void)testBRAb {
_machine->set_program({
0x6004 // BRA.b +4
});
_machine->run_for_instructions(1);
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.program_counter, 0x1006 + 4);
XCTAssertEqual(_machine->get_cycle_count(), 10);
}
- (void)testBRAw {
_machine->set_program({
0x6000, 0x0004 // BRA.b +4
});
_machine->run_for_instructions(1);
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.program_counter, 0x1006 + 4);
XCTAssertEqual(_machine->get_cycle_count(), 10);
}
// MARK: BSR
- (void)testBSRw {
_machine->set_program({
0x6100, 0x0006 // BSR.w $1008
});
_machine->set_initial_stack_pointer(0x3000);
_machine->run_for_instructions(1);
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.program_counter, 0x1008 + 4);
XCTAssertEqual(state.stack_pointer(), 0x2ffc);
XCTAssertEqual(state.status & Flag::ConditionCodes, 0);
XCTAssertEqual(*_machine->ram_at(0x2ffc), 0);
XCTAssertEqual(*_machine->ram_at(0x2ffe), 0x1004);
XCTAssertEqual(_machine->get_cycle_count(), 18);
}
- (void)testBSRb {
_machine->set_program({
0x6106 // BSR.b $1008
});
_machine->set_initial_stack_pointer(0x3000);
_machine->run_for_instructions(1);
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.program_counter, 0x1008 + 4);
XCTAssertEqual(state.stack_pointer(), 0x2ffc);
XCTAssertEqual(state.status & Flag::ConditionCodes, 0);
XCTAssertEqual(*_machine->ram_at(0x2ffc), 0);
XCTAssertEqual(*_machine->ram_at(0x2ffe), 0x1002);
XCTAssertEqual(_machine->get_cycle_count(), 18);
}
// MARK: CHK
- (void)performCHKd1:(uint32_t)d1 d2:(uint32_t)d2 {
_machine->set_program({
0x4581 // CHK D1, D2
});
auto state = _machine->get_processor_state();
state.data[1] = d1;
state.data[2] = d2;
state.status |= Flag::ConditionCodes;
_machine->set_initial_stack_pointer(0);
_machine->set_processor_state(state);
_machine->run_for_instructions(1);
state = _machine->get_processor_state();
XCTAssertEqual(state.data[1], d1);
XCTAssertEqual(state.data[2], d2);
}
- (void)testCHK_1111v1111 {
[self performCHKd1:0x1111 d2:0x1111];
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.program_counter, 0x1002 + 4);
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend);
XCTAssertEqual(10, _machine->get_cycle_count());
}
- (void)testCHK_1111v0000 {
[self performCHKd1:0x1111 d2:0x0000];
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.program_counter, 0x1002 + 4);
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Zero);
XCTAssertEqual(10, _machine->get_cycle_count());
}
- (void)testCHK_8000v8001 {
[self performCHKd1:0x8000 d2:0x8001];
const auto state = _machine->get_processor_state();
XCTAssertNotEqual(state.program_counter, 0x1002 + 4);
XCTAssertEqual(state.stack_pointer(), 0xfffffffa);
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend);
XCTAssertEqual(42, _machine->get_cycle_count());
}
- (void)testCHK_8000v8000 {
[self performCHKd1:0x8000 d2:0x8000];
const auto state = _machine->get_processor_state();
XCTAssertNotEqual(state.program_counter, 0x1002 + 4);
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::Extend | Flag::Negative);
XCTAssertEqual(44, _machine->get_cycle_count());
}
// MARK: DBcc
- (void)performDBccTestOpcode:(uint16_t)opcode status:(uint16_t)status d2Outcome:(uint32_t)d2Output {
_machine->set_program({
opcode, 0x0008 // DBcc D2, +8
});
auto state = _machine->get_processor_state();
state.status = status;
state.data[2] = 1;
_machine->set_processor_state(state);
_machine->run_for_instructions(1);
state = _machine->get_processor_state();
XCTAssertEqual(state.data[2], d2Output);
XCTAssertEqual(state.status, status);
}
- (void)testDBT {
[self performDBccTestOpcode:0x50ca status:0 d2Outcome:1];
}
- (void)testDBF {
[self performDBccTestOpcode:0x51ca status:0 d2Outcome:0];
}
- (void)testDBHI {
[self performDBccTestOpcode:0x52ca status:0 d2Outcome:1];
}
- (void)testDBHI_Carry {
[self performDBccTestOpcode:0x52ca status:Flag::Carry d2Outcome:0];
}
- (void)testDBHI_Zero {
[self performDBccTestOpcode:0x52ca status:Flag::Zero d2Outcome:0];
}
- (void)testDBLS_CarryOverflow {
[self performDBccTestOpcode:0x53ca status:Flag::Carry | Flag::Overflow d2Outcome:1];
}
- (void)testDBLS_Carry {
[self performDBccTestOpcode:0x53ca status:Flag::Carry d2Outcome:1];
}
- (void)testDBLS_Overflow {
[self performDBccTestOpcode:0x53ca status:Flag::Overflow d2Outcome:0];
}
- (void)testDBCC_Carry {
[self performDBccTestOpcode:0x54ca status:Flag::Carry d2Outcome:0];
}
- (void)testDBCC {
[self performDBccTestOpcode:0x54ca status:0 d2Outcome:1];
}
- (void)testDBCS {
[self performDBccTestOpcode:0x55ca status:0 d2Outcome:0];
}
- (void)testDBCS_Carry {
[self performDBccTestOpcode:0x55ca status:Flag::Carry d2Outcome:1];
}
- (void)testDBNE {
[self performDBccTestOpcode:0x56ca status:0 d2Outcome:1];
}
- (void)testDBNE_Zero {
[self performDBccTestOpcode:0x56ca status:Flag::Zero d2Outcome:0];
}
- (void)testDBEQ {
[self performDBccTestOpcode:0x57ca status:0 d2Outcome:0];
}
- (void)testDBEQ_Zero {
[self performDBccTestOpcode:0x57ca status:Flag::Zero d2Outcome:1];
}
- (void)testDBVC {
[self performDBccTestOpcode:0x58ca status:0 d2Outcome:1];
}
- (void)testDBVC_Overflow {
[self performDBccTestOpcode:0x58ca status:Flag::Overflow d2Outcome:0];
}
- (void)testDBVS {
[self performDBccTestOpcode:0x59ca status:0 d2Outcome:0];
}
- (void)testDBVS_Overflow {
[self performDBccTestOpcode:0x59ca status:Flag::Overflow d2Outcome:1];
}
- (void)testDBPL {
[self performDBccTestOpcode:0x5aca status:0 d2Outcome:1];
}
- (void)testDBPL_Negative {
[self performDBccTestOpcode:0x5aca status:Flag::Negative d2Outcome:0];
}
- (void)testDBMI {
[self performDBccTestOpcode:0x5bca status:0 d2Outcome:0];
}
- (void)testDBMI_Negative {
[self performDBccTestOpcode:0x5bca status:Flag::Negative d2Outcome:1];
}
- (void)testDBGE_NegativeOverflow {
[self performDBccTestOpcode:0x5cca status:Flag::Negative | Flag::Overflow d2Outcome:1];
}
- (void)testDBGE {
[self performDBccTestOpcode:0x5cca status:0 d2Outcome:1];
}
- (void)testDBGE_Negative {
[self performDBccTestOpcode:0x5cca status:Flag::Negative d2Outcome:0];
}
- (void)testDBGE_Overflow {
[self performDBccTestOpcode:0x5cca status:Flag::Overflow d2Outcome:0];
}
- (void)testDBLT_NegativeOverflow {
[self performDBccTestOpcode:0x5dca status:Flag::Negative | Flag::Overflow d2Outcome:0];
}
- (void)testDBLT {
[self performDBccTestOpcode:0x5dca status:0 d2Outcome:0];
}
- (void)testDBLT_Negative {
[self performDBccTestOpcode:0x5dca status:Flag::Negative d2Outcome:1];
}
- (void)testDBLT_Overflow {
[self performDBccTestOpcode:0x5dca status:Flag::Overflow d2Outcome:1];
}
- (void)testDBGT {
[self performDBccTestOpcode:0x5eca status:0 d2Outcome:1];
}
- (void)testDBGT_ZeroNegativeOverflow {
[self performDBccTestOpcode:0x5eca status:Flag::Zero | Flag::Negative | Flag::Overflow d2Outcome:0];
}
- (void)testDBGT_NegativeOverflow {
[self performDBccTestOpcode:0x5eca status:Flag::Negative | Flag::Overflow d2Outcome:1];
}
- (void)testDBGT_Zero {
[self performDBccTestOpcode:0x5eca status:Flag::Zero d2Outcome:0];
}
- (void)testDBLE {
[self performDBccTestOpcode:0x5fca status:0 d2Outcome:0];
}
- (void)testDBLE_Zero {
[self performDBccTestOpcode:0x5fca status:Flag::Zero d2Outcome:1];
}
- (void)testDBLE_Negative {
[self performDBccTestOpcode:0x5fca status:Flag::Negative d2Outcome:1];
}
- (void)testDBLE_NegativeOverflow {
[self performDBccTestOpcode:0x5fca status:Flag::Negative | Flag::Overflow d2Outcome:0];
}
/* Further DBF tests omitted; they seemed to be duplicative, assuming I'm not suffering a failure of comprehension. */
// MARK: JMP
- (void)testJMP_A1 {
_machine->set_program({
0x4ed1 // JMP (A1)
});
auto state = _machine->get_processor_state();
state.address[1] = 0x3000;
_machine->set_processor_state(state);
_machine->run_for_instructions(1);
state = _machine->get_processor_state();
XCTAssertEqual(state.address[1], 0x3000);
XCTAssertEqual(state.program_counter, 0x3000 + 4);
XCTAssertEqual(8, _machine->get_cycle_count());
}
- (void)testJMP_PC {
_machine->set_program({
0x4efa, 0x000a // JMP PC+a (i.e. to 0x100c)
});
_machine->run_for_instructions(1);
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.program_counter, 0x100c + 4);
XCTAssertEqual(10, _machine->get_cycle_count());
}
// MARK: JSR
- (void)testJSR_PC {
_machine->set_program({
0x4eba, 0x000a // JSR (+a)PC ; JSR to $100c
});
_machine->set_initial_stack_pointer(0x2000);
_machine->run_for_instructions(1);
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.stack_pointer(), 0x1ffc);
XCTAssertEqual(state.program_counter, 0x100c + 4);
XCTAssertEqual(*_machine->ram_at(0x1ffc), 0x0000);
XCTAssertEqual(*_machine->ram_at(0x1ffe), 0x1004);
XCTAssertEqual(18, _machine->get_cycle_count());
}
- (void)testJSR_XXXl {
_machine->set_program({
0x4eb9, 0x0000, 0x1008 // JSR ($1008).l
});
_machine->set_initial_stack_pointer(0x2000);
_machine->run_for_instructions(1);
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.stack_pointer(), 0x1ffc);
XCTAssertEqual(state.program_counter, 0x1008 + 4);
XCTAssertEqual(*_machine->ram_at(0x1ffc), 0x0000);
XCTAssertEqual(*_machine->ram_at(0x1ffe), 0x1006);
XCTAssertEqual(20, _machine->get_cycle_count());
}
// MARK: NOP
- (void)testNOP {
_machine->set_program({
0x4e71 // NOP
});
_machine->run_for_instructions(1);
XCTAssertEqual(4, _machine->get_cycle_count());
}
// MARK: RTR
- (void)testRTR {
_machine->set_program({
0x4e77 // RTR
});
_machine->set_initial_stack_pointer(0x2000);
*_machine->ram_at(0x2000) = 0x7fff;
*_machine->ram_at(0x2002) = 0;
*_machine->ram_at(0x2004) = 0xc;
_machine->run_for_instructions(1);
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.stack_pointer(), 0x2006);
XCTAssertEqual(state.program_counter, 0x10);
XCTAssertEqual(state.status & Flag::ConditionCodes, Flag::ConditionCodes);
XCTAssertEqual(20, _machine->get_cycle_count());
}
// MARK: RTS
- (void)testRTS {
_machine->set_program({
0x4e75 // RTS
});
_machine->set_initial_stack_pointer(0x2000);
*_machine->ram_at(0x2000) = 0x0000;
*_machine->ram_at(0x2002) = 0x000c;
_machine->run_for_instructions(1);
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.stack_pointer(), 0x2004);
XCTAssertEqual(state.program_counter, 0x000c + 4);
XCTAssertEqual(16, _machine->get_cycle_count());
}
// MARK: TRAP
- (void)testTRAP {
_machine->set_program({
0x4e41 // TRAP #1
});
auto state = _machine->get_processor_state();
state.status = 0x700;
state.user_stack_pointer = 0x200;
state.supervisor_stack_pointer = 0x206;
*_machine->ram_at(0x84) = 0xfffe;
*_machine->ram_at(0xfffe) = 0x4e71;
_machine->set_processor_state(state);
_machine->run_for_instructions(1);
state = _machine->get_processor_state();
XCTAssertEqual(state.status, 0x2700);
XCTAssertEqual(*_machine->ram_at(0x200), 0x700);
XCTAssertEqual(*_machine->ram_at(0x202), 0x0000);
XCTAssertEqual(*_machine->ram_at(0x204), 0x1002);
XCTAssertEqual(state.supervisor_stack_pointer, 0x200);
XCTAssertEqual(34, _machine->get_cycle_count());
}
// MARK: TRAPV
- (void)testTRAPV_taken {
_machine->set_program({
0x4e76 // TRAPV
});
_machine->set_initial_stack_pointer(0x206);
auto state = _machine->get_processor_state();
state.status = 0x702;
state.supervisor_stack_pointer = 0x206;
*_machine->ram_at(0x1e) = 0xfffe;
*_machine->ram_at(0xfffe) = 0x4e71;
_machine->set_processor_state(state);
_machine->run_for_instructions(1);
state = _machine->get_processor_state();
XCTAssertEqual(state.status, 0x2702);
XCTAssertEqual(state.stack_pointer(), 0x200);
XCTAssertEqual(*_machine->ram_at(0x202), 0x0000);
XCTAssertEqual(*_machine->ram_at(0x204), 0x1002);
XCTAssertEqual(*_machine->ram_at(0x200), 0x702);
XCTAssertEqual(34, _machine->get_cycle_count());
}
- (void)testTRAPV_untaken {
_machine->set_program({
0x4e76 // TRAPV
});
_machine->run_for_instructions(1);
const auto state = _machine->get_processor_state();
XCTAssertEqual(state.program_counter, 0x1002 + 4);
XCTAssertEqual(4, _machine->get_cycle_count());
}
@end

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,425 @@
//
// 68000Tests.m
// Clock SignalTests
//
// Created by Thomas Harte on 13/03/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#import <XCTest/XCTest.h>
#include <cassert>
#define LOG_TRACE
#include "TestRunner68000.hpp"
class CPU::MC68000::ProcessorStorageTests {
public:
ProcessorStorageTests(const CPU::MC68000::ProcessorStorage &storage, const char *coverage_file_name) {
false_valids_ = [NSMutableSet set];
false_invalids_ = [NSMutableSet set];
FILE *source = fopen(coverage_file_name, "rb");
// The file format here is [2 bytes opcode][2 ASCII characters:VA for valid, IN for invalid]...
// The file terminates with four additional bytes that begin with two zero bytes.
//
// The version of the file I grabbed seems to cover all opcodes, making their enumeration
// arguably redundant; the code below nevertheless uses the codes from the file.
//
// Similarly, I'm testing for exactly the strings VA or IN to ensure no further
// types creep into any updated version of the table that I then deal with incorrectly.
uint16_t last_observed = 0;
while(true) {
// Fetch opcode number.
uint16_t next_opcode = fgetc(source) << 8;
next_opcode |= fgetc(source);
if(next_opcode < last_observed) break;
last_observed = next_opcode;
// Determine whether it's meant to be valid.
char type[3];
type[0] = fgetc(source);
type[1] = fgetc(source);
type[2] = '\0';
// TEMPORARY: factor out A- and F-line exceptions.
if((next_opcode&0xf000) == 0xa000) continue;
if((next_opcode&0xf000) == 0xf000) continue;
if(!strcmp(type, "VA")) {
// Test for validity.
if(storage.instructions[next_opcode].micro_operations == std::numeric_limits<uint32_t>::max()) {
[false_invalids_ addObject:@(next_opcode)];
}
continue;
}
if(!strcmp(type, "IN")) {
// Test for invalidity.
if(storage.instructions[next_opcode].micro_operations != std::numeric_limits<uint32_t>::max()) {
[false_valids_ addObject:@(next_opcode)];
}
continue;
}
assert(false);
}
fclose(source);
}
NSSet<NSNumber *> *false_valids() const {
return false_valids_;
}
NSSet<NSNumber *> *false_invalids() const {
return false_invalids_;
}
private:
NSMutableSet<NSNumber *> *false_invalids_;
NSMutableSet<NSNumber *> *false_valids_;
};
@interface NSSet (CSHexDump)
- (NSString *)hexDump;
@end
@implementation NSSet (CSHexDump)
- (NSString *)hexDump {
NSMutableArray<NSString *> *components = [NSMutableArray array];
for(NSNumber *number in [[self allObjects] sortedArrayUsingSelector:@selector(compare:)]) {
[components addObject:[NSString stringWithFormat:@"%04x", number.intValue]];
}
return [components componentsJoinedByString:@" "];
}
@end
@interface M68000Tests : XCTestCase
@end
@implementation M68000Tests {
std::unique_ptr<RAM68000> _machine;
}
- (void)setUp {
_machine.reset(new RAM68000());
}
- (void)tearDown {
_machine.reset();
}
- (void)testABCDLong {
for(int d = 0; d < 100; ++d) {
_machine.reset(new RAM68000());
_machine->set_program({
0xc100 // ABCD D0, D0
});
auto state = _machine->get_processor_state();
const uint8_t bcd_d = ((d / 10) * 16) + (d % 10);
state.data[0] = bcd_d;
_machine->set_processor_state(state);
_machine->run_for_instructions(1);
state = _machine->get_processor_state();
const uint8_t double_d = (d * 2) % 100;
const uint8_t bcd_double_d = ((double_d / 10) * 16) + (double_d % 10);
XCTAssert(state.data[0] == bcd_double_d, "%02x + %02x = %02x; should equal %02x", bcd_d, bcd_d, state.data[0], bcd_double_d);
}
}
- (void)testDivideByZero {
_machine->set_program({
0x7000, // MOVE #0, D0; location 0x400
0x3200, // MOVE D0, D1; location 0x402
0x82C0, // DIVU; location 0x404
/* Next instruction would be at 0x406 */
});
_machine->set_initial_stack_pointer(0x1000);
_machine->run_for_instructions(4);
const auto state = _machine->get_processor_state();
XCTAssert(state.supervisor_stack_pointer == 0x1000 - 6, @"Exception information should have been pushed to stack.");
const uint16_t *const stack_top = _machine->ram_at(state.supervisor_stack_pointer);
XCTAssert(stack_top[1] == 0x0000 && stack_top[2] == 0x1006, @"Return address should point to instruction after DIVU.");
}
- (void)testMOVE {
_machine->set_program({
0x303c, 0xfb2e, // MOVE #fb2e, D0
0x3200, // MOVE D0, D1
0x3040, // MOVEA D0, A0
0x3278, 0x1000, // MOVEA.w (0x1000), A1
0x387c, 0x1000, // MOVE #$1000, A4
0x2414, // MOVE.l (A4), D2
});
// run_for_instructions technically runs up to the next instruction
// fetch; therefore run for '1' to get past the implied RESET.
_machine->run_for_instructions(1);
// Perform MOVE #fb2e, D0
_machine->run_for_instructions(1);
auto state = _machine->get_processor_state();
XCTAssert(state.data[0] == 0xfb2e);
// Perform MOVE D0, D1
_machine->run_for_instructions(1);
state = _machine->get_processor_state();
XCTAssert(state.data[1] == 0xfb2e);
// Perform MOVEA D0, A0
_machine->run_for_instructions(1);
state = _machine->get_processor_state();
XCTAssert(state.address[0] == 0xfffffb2e, "A0 was %08x instead of 0xfffffb2e", state.address[0]);
// Perform MOVEA.w (0x1000), A1
_machine->run_for_instructions(1);
state = _machine->get_processor_state();
XCTAssert(state.address[1] == 0x0000303c, "A1 was %08x instead of 0x0000303c", state.address[1]);
// Perform MOVE #$400, A4; MOVE.l (A4), D2
_machine->run_for_instructions(2);
state = _machine->get_processor_state();
XCTAssert(state.address[4] == 0x1000, "A4 was %08x instead of 0x00001000", state.address[4]);
XCTAssert(state.data[2] == 0x303cfb2e, "D2 was %08x instead of 0x303cfb2e", state.data[2]);
}
- (void)testVectoredInterrupt {
_machine->set_program({
0x46fc, 0x2000, // MOVE.w #$2000, SR
0x4e71, // NOP
0x4e71, // NOP
0x4e71, // NOP
0x4e71, // NOP
0x4e71, // NOP
});
// Set the vector that will be supplied back to the start of the
// program; this will ensure no further exceptions following
// the interrupt.
const auto vector = _machine->ram_at(40);
vector[0] = 0x0000;
vector[1] = 0x1004;
_machine->run_for_instructions(3);
_machine->processor().set_interrupt_level(1);
_machine->run_for_instructions(1);
const auto state = _machine->processor().get_state();
XCTAssert(state.program_counter == 0x1008); // i.e. the interrupt happened, the instruction performed was the one at 1004, and therefore
// by the wonders of prefetch the program counter is now at 1008.
}
- (void)testOpcodeCoverage {
// Perform an audit of implemented instructions.
CPU::MC68000::ProcessorStorageTests storage_tests(
_machine->processor(),
[[NSBundle bundleForClass:[self class]] pathForResource:@"OPCLOGR2" ofType:@"BIN"].UTF8String
);
// This is a list of instructions nominated as valid with OPCLOGR2.BIN but with no obvious decoding —
// the disassemblers I tried couldn't figure them out, and I didn't spot them anywhere in the PRM.
NSSet<NSNumber *> *const undecodables = [NSSet setWithArray:@[
// These look like malformed MOVEs.
@(0x2e7d), @(0x2e7e), @(0x2e7f), @(0x2efd), @(0x2efe), @(0x2eff), @(0x2f7d), @(0x2f7e),
@(0x2f7f), @(0x2fc0), @(0x2fc1), @(0x2fc2), @(0x2fc3), @(0x2fc4), @(0x2fc5), @(0x2fc6),
@(0x2fc7), @(0x2fc8), @(0x2fc9), @(0x2fca), @(0x2fcb), @(0x2fcc), @(0x2fcd), @(0x2fce),
@(0x2fcf), @(0x2fd0), @(0x2fd1), @(0x2fd2), @(0x2fd3), @(0x2fd4), @(0x2fd5), @(0x2fd6),
@(0x2fd7), @(0x2fd8), @(0x2fd9), @(0x2fda), @(0x2fdb), @(0x2fdc), @(0x2fdd), @(0x2fde),
@(0x2fdf), @(0x2fe0), @(0x2fe1), @(0x2fe2), @(0x2fe3), @(0x2fe4), @(0x2fe5), @(0x2fe6),
@(0x2fe7), @(0x2fe8), @(0x2fe9), @(0x2fea), @(0x2feb), @(0x2fec), @(0x2fed), @(0x2fee),
@(0x2fef), @(0x2ff0), @(0x2ff1), @(0x2ff2), @(0x2ff3), @(0x2ff4), @(0x2ff5), @(0x2ff6),
@(0x2ff7), @(0x2ff8), @(0x2ff9), @(0x2ffa), @(0x2ffb), @(0x2ffc), @(0x2ffd), @(0x2ffe),
@(0x2fff),
@(0x3e7d), @(0x3e7e), @(0x3e7f), @(0x3efd), @(0x3efe), @(0x3eff), @(0x3f7d), @(0x3f7e),
@(0x3f7f), @(0x3fc0), @(0x3fc1), @(0x3fc2), @(0x3fc3), @(0x3fc4), @(0x3fc5), @(0x3fc6),
@(0x3fc7), @(0x3fc8), @(0x3fc9), @(0x3fca), @(0x3fcb), @(0x3fcc), @(0x3fcd), @(0x3fce),
@(0x3fcf), @(0x3fd0), @(0x3fd1), @(0x3fd2), @(0x3fd3), @(0x3fd4), @(0x3fd5), @(0x3fd6),
@(0x3fd7), @(0x3fd8), @(0x3fd9), @(0x3fda), @(0x3fdb), @(0x3fdc), @(0x3fdd), @(0x3fde),
@(0x3fdf), @(0x3fe0), @(0x3fe1), @(0x3fe2), @(0x3fe3), @(0x3fe4), @(0x3fe5), @(0x3fe6),
@(0x3fe7), @(0x3fe8), @(0x3fe9), @(0x3fea), @(0x3feb), @(0x3fec), @(0x3fed), @(0x3fee),
@(0x3fef), @(0x3ff0), @(0x3ff1), @(0x3ff2), @(0x3ff3), @(0x3ff4), @(0x3ff5), @(0x3ff6),
@(0x3ff7), @(0x3ff8), @(0x3ff9), @(0x3ffa), @(0x3ffb), @(0x3ffc), @(0x3ffd), @(0x3ffe),
@(0x3fff),
@(0x46c8), @(0x46c9), @(0x46ca), @(0x46cb), @(0x46cc), @(0x46cd), @(0x46ce), @(0x46cf),
@(0x46fd), @(0x46fe), @(0x46ff), @(0x47c0), @(0x47c1), @(0x47c2), @(0x47c3), @(0x47c4),
@(0x47c5), @(0x47c6), @(0x47c7), @(0x47c8), @(0x47c9), @(0x47ca), @(0x47cb), @(0x47cc),
@(0x47cd), @(0x47ce), @(0x47cf), @(0x47d8), @(0x47d9), @(0x47da), @(0x47db), @(0x47dc),
@(0x47dd), @(0x47de), @(0x47df), @(0x47e0), @(0x47e1), @(0x47e2), @(0x47e3), @(0x47e4),
@(0x47e5), @(0x47e6), @(0x47e7), @(0x47fc), @(0x47fd), @(0x47fe), @(0x47ff), @(0x4e80),
@(0x4e81), @(0x4e82), @(0x4e83), @(0x4e84), @(0x4e85), @(0x4e86), @(0x4e87), @(0x4e88),
@(0x4e89), @(0x4e8a), @(0x4e8b), @(0x4e8c), @(0x4e8d), @(0x4e8e), @(0x4e8f), @(0x4e98),
@(0x4e99), @(0x4e9a), @(0x4e9b), @(0x4e9c), @(0x4e9d), @(0x4e9e), @(0x4e9f), @(0x4ea0),
@(0x4ea1), @(0x4ea2), @(0x4ea3), @(0x4ea4), @(0x4ea5), @(0x4ea6), @(0x4ea7), @(0x4ebc),
@(0x4ebd), @(0x4ebe), @(0x4ebf), @(0x4ec0), @(0x4ec1), @(0x4ec2), @(0x4ec3), @(0x4ec4),
@(0x4ec5), @(0x4ec6), @(0x4ec7), @(0x4ec8), @(0x4ec9), @(0x4eca), @(0x4ecb), @(0x4ecc),
@(0x4ecd), @(0x4ece), @(0x4ecf), @(0x4ed8), @(0x4ed9), @(0x4eda), @(0x4edb), @(0x4edc),
@(0x4edd), @(0x4ede), @(0x4edf), @(0x4ee0), @(0x4ee1), @(0x4ee2), @(0x4ee3), @(0x4ee4),
@(0x4ee5), @(0x4ee6), @(0x4ee7), @(0x4efc), @(0x4efd), @(0x4efe), @(0x4eff), @(0x4f88),
@(0x4f89), @(0x4f8a), @(0x4f8b), @(0x4f8c), @(0x4f8d), @(0x4f8e), @(0x4f8f), @(0x4fbd),
@(0x4fbe), @(0x4fbf), @(0x4fc0), @(0x4fc1), @(0x4fc2), @(0x4fc3), @(0x4fc4), @(0x4fc5),
@(0x4fc6), @(0x4fc7), @(0x4fc8), @(0x4fc9), @(0x4fca), @(0x4fcb), @(0x4fcc), @(0x4fcd),
@(0x4fce), @(0x4fcf), @(0x4fd8), @(0x4fd9), @(0x4fda), @(0x4fdb), @(0x4fdc), @(0x4fdd),
@(0x4fde), @(0x4fdf), @(0x4fe0), @(0x4fe1), @(0x4fe2), @(0x4fe3), @(0x4fe4), @(0x4fe5),
@(0x4fe6), @(0x4fe7), @(0x4ffc), @(0x4ffd), @(0x4ffe), @(0x4fff),
@(0x50fa), @(0x50fb), @(0x50fc), @(0x50fd), @(0x50fe), @(0x50ff), @(0x51fa), @(0x51fb),
@(0x51fc), @(0x51fd), @(0x51fe), @(0x51ff), @(0x52fa), @(0x52fb), @(0x52fc), @(0x52fd),
@(0x52fe), @(0x52ff), @(0x53fa), @(0x53fb), @(0x53fc), @(0x53fd), @(0x53fe), @(0x53ff),
@(0x54fa), @(0x54fb), @(0x54fc), @(0x54fd), @(0x54fe), @(0x54ff), @(0x55fa), @(0x55fb),
@(0x55fc), @(0x55fd), @(0x55fe), @(0x55ff), @(0x56fa), @(0x56fb), @(0x56fc), @(0x56fd),
@(0x56fe), @(0x56ff), @(0x57fa), @(0x57fb), @(0x57fc), @(0x57fd), @(0x57fe), @(0x57ff),
@(0x58fa), @(0x58fb), @(0x58fc), @(0x58fd), @(0x58fe), @(0x58ff), @(0x59fa), @(0x59fb),
@(0x59fc), @(0x59fd), @(0x59fe), @(0x59ff), @(0x5afa), @(0x5afb), @(0x5afc), @(0x5afd),
@(0x5afe), @(0x5aff), @(0x5bfa), @(0x5bfb), @(0x5bfc), @(0x5bfd), @(0x5bfe), @(0x5bff),
@(0x5cfa), @(0x5cfb), @(0x5cfc), @(0x5cfd), @(0x5cfe), @(0x5cff), @(0x5dfa), @(0x5dfb),
@(0x5dfc), @(0x5dfd), @(0x5dfe), @(0x5dff), @(0x5eba), @(0x5ebb), @(0x5ebc), @(0x5ebd),
@(0x5ebe), @(0x5ebf), @(0x5efa), @(0x5efb), @(0x5efc), @(0x5efd), @(0x5efe), @(0x5eff),
@(0x5fba), @(0x5fbb), @(0x5fbc), @(0x5fbd), @(0x5fbe), @(0x5fbf), @(0x5ffa), @(0x5ffb),
@(0x5ffc), @(0x5ffd), @(0x5ffe), @(0x5fff),
// These are almost MOVEQs if only bit 8 weren't set.
@(0x71c8), @(0x71c9), @(0x71ca), @(0x71cb), @(0x71cc), @(0x71cd), @(0x71ce), @(0x71cf),
@(0x71d8), @(0x71d9), @(0x71da), @(0x71db), @(0x71dc), @(0x71dd), @(0x71de), @(0x71df),
@(0x71e8), @(0x71e9), @(0x71ea), @(0x71eb), @(0x71ec), @(0x71ed), @(0x71ee), @(0x71ef),
@(0x71f8), @(0x71f9), @(0x71fa), @(0x71fb), @(0x71fc), @(0x71fd), @(0x71fe), @(0x71ff),
@(0x73c8), @(0x73c9), @(0x73ca), @(0x73cb), @(0x73cc), @(0x73cd), @(0x73ce), @(0x73cf),
@(0x73d8), @(0x73d9), @(0x73da), @(0x73db), @(0x73dc), @(0x73dd), @(0x73de), @(0x73df),
@(0x73e8), @(0x73e9), @(0x73ea), @(0x73eb), @(0x73ec), @(0x73ed), @(0x73ee), @(0x73ef),
@(0x73f8), @(0x73f9), @(0x73fa), @(0x73fb), @(0x73fc), @(0x73fd), @(0x73fe), @(0x73ff),
@(0x75c8), @(0x75c9), @(0x75ca), @(0x75cb), @(0x75cc), @(0x75cd), @(0x75ce), @(0x75cf),
@(0x75d8), @(0x75d9), @(0x75da), @(0x75db), @(0x75dc), @(0x75dd), @(0x75de), @(0x75df),
@(0x75e8), @(0x75e9), @(0x75ea), @(0x75eb), @(0x75ec), @(0x75ed), @(0x75ee), @(0x75ef),
@(0x75f8), @(0x75f9), @(0x75fa), @(0x75fb), @(0x75fc), @(0x75fd), @(0x75fe), @(0x75ff),
@(0x77c0), @(0x77c1), @(0x77c2), @(0x77c3), @(0x77c4), @(0x77c5), @(0x77c6), @(0x77c7),
@(0x77c8), @(0x77c9), @(0x77ca), @(0x77cb), @(0x77cc), @(0x77cd), @(0x77ce), @(0x77cf),
@(0x77d0), @(0x77d1), @(0x77d2), @(0x77d3), @(0x77d4), @(0x77d5), @(0x77d6), @(0x77d7),
@(0x77d8), @(0x77d9), @(0x77da), @(0x77db), @(0x77dc), @(0x77dd), @(0x77de), @(0x77df),
@(0x77e0), @(0x77e1), @(0x77e2), @(0x77e3), @(0x77e4), @(0x77e5), @(0x77e6), @(0x77e7),
@(0x77e8), @(0x77e9), @(0x77ea), @(0x77eb), @(0x77ec), @(0x77ed), @(0x77ee), @(0x77ef),
@(0x77f0), @(0x77f1), @(0x77f2), @(0x77f3), @(0x77f4), @(0x77f5), @(0x77f6), @(0x77f7),
@(0x77f8), @(0x77f9), @(0x77fa), @(0x77fb), @(0x77fc), @(0x77fd), @(0x77fe), @(0x77ff),
@(0x79c8), @(0x79c9), @(0x79ca), @(0x79cb), @(0x79cc), @(0x79cd), @(0x79ce), @(0x79cf),
@(0x79d8), @(0x79d9), @(0x79da), @(0x79db), @(0x79dc), @(0x79dd), @(0x79de), @(0x79df),
@(0x79e8), @(0x79e9), @(0x79ea), @(0x79eb), @(0x79ec), @(0x79ed), @(0x79ee), @(0x79ef),
@(0x79f8), @(0x79f9), @(0x79fa), @(0x79fb), @(0x79fc), @(0x79fd), @(0x79fe), @(0x79ff),
@(0x7bc8), @(0x7bc9), @(0x7bca), @(0x7bcb), @(0x7bcc), @(0x7bcd), @(0x7bce), @(0x7bcf),
@(0x7bd8), @(0x7bd9), @(0x7bda), @(0x7bdb), @(0x7bdc), @(0x7bdd), @(0x7bde), @(0x7bdf),
@(0x7be8), @(0x7be9), @(0x7bea), @(0x7beb), @(0x7bec), @(0x7bed), @(0x7bee), @(0x7bef),
@(0x7bf8), @(0x7bf9), @(0x7bfa), @(0x7bfb), @(0x7bfc), @(0x7bfd), @(0x7bfe), @(0x7bff),
@(0x7dc8), @(0x7dc9), @(0x7dca), @(0x7dcb), @(0x7dcc), @(0x7dcd), @(0x7dce), @(0x7dcf),
@(0x7dd8), @(0x7dd9), @(0x7dda), @(0x7ddb), @(0x7ddc), @(0x7ddd), @(0x7dde), @(0x7ddf),
@(0x7de8), @(0x7de9), @(0x7dea), @(0x7deb), @(0x7dec), @(0x7ded), @(0x7dee), @(0x7def),
@(0x7df8), @(0x7df9), @(0x7dfa), @(0x7dfb), @(0x7dfc), @(0x7dfd), @(0x7dfe), @(0x7dff),
@(0x7f40), @(0x7f41), @(0x7f42), @(0x7f43), @(0x7f44), @(0x7f45), @(0x7f46), @(0x7f47),
@(0x7f48), @(0x7f49), @(0x7f4a), @(0x7f4b), @(0x7f4c), @(0x7f4d), @(0x7f4e), @(0x7f4f),
@(0x7f50), @(0x7f51), @(0x7f52), @(0x7f53), @(0x7f54), @(0x7f55), @(0x7f56), @(0x7f57),
@(0x7f58), @(0x7f59), @(0x7f5a), @(0x7f5b), @(0x7f5c), @(0x7f5d), @(0x7f5e), @(0x7f5f),
@(0x7f60), @(0x7f61), @(0x7f62), @(0x7f63), @(0x7f64), @(0x7f65), @(0x7f66), @(0x7f67),
@(0x7f68), @(0x7f69), @(0x7f6a), @(0x7f6b), @(0x7f6c), @(0x7f6d), @(0x7f6e), @(0x7f6f),
@(0x7f70), @(0x7f71), @(0x7f72), @(0x7f73), @(0x7f74), @(0x7f75), @(0x7f76), @(0x7f77),
@(0x7f78), @(0x7f79), @(0x7f7a), @(0x7f7b), @(0x7f7c), @(0x7f7d), @(0x7f7e), @(0x7f7f),
@(0x7f80), @(0x7f81), @(0x7f82), @(0x7f83), @(0x7f84), @(0x7f85), @(0x7f86), @(0x7f87),
@(0x7f88), @(0x7f89), @(0x7f8a), @(0x7f8b), @(0x7f8c), @(0x7f8d), @(0x7f8e), @(0x7f8f),
@(0x7f90), @(0x7f91), @(0x7f92), @(0x7f93), @(0x7f94), @(0x7f95), @(0x7f96), @(0x7f97),
@(0x7f98), @(0x7f99), @(0x7f9a), @(0x7f9b), @(0x7f9c), @(0x7f9d), @(0x7f9e), @(0x7f9f),
@(0x7fa0), @(0x7fa1), @(0x7fa2), @(0x7fa3), @(0x7fa4), @(0x7fa5), @(0x7fa6), @(0x7fa7),
@(0x7fa8), @(0x7fa9), @(0x7faa), @(0x7fab), @(0x7fac), @(0x7fad), @(0x7fae), @(0x7faf),
@(0x7fb0), @(0x7fb1), @(0x7fb2), @(0x7fb3), @(0x7fb4), @(0x7fb5), @(0x7fb6), @(0x7fb7),
@(0x7fb8), @(0x7fb9), @(0x7fba), @(0x7fbb), @(0x7fbc), @(0x7fbd), @(0x7fbe), @(0x7fbf),
@(0x7fc0), @(0x7fc1), @(0x7fc2), @(0x7fc3), @(0x7fc4), @(0x7fc5), @(0x7fc6), @(0x7fc7),
@(0x7fc8), @(0x7fc9), @(0x7fca), @(0x7fcb), @(0x7fcc), @(0x7fcd), @(0x7fce), @(0x7fcf),
@(0x7fd0), @(0x7fd1), @(0x7fd2), @(0x7fd3), @(0x7fd4), @(0x7fd5), @(0x7fd6), @(0x7fd7),
@(0x7fd8), @(0x7fd9), @(0x7fda), @(0x7fdb), @(0x7fdc), @(0x7fdd), @(0x7fde), @(0x7fdf),
@(0x7fe0), @(0x7fe1), @(0x7fe2), @(0x7fe3), @(0x7fe4), @(0x7fe5), @(0x7fe6), @(0x7fe7),
@(0x7fe8), @(0x7fe9), @(0x7fea), @(0x7feb), @(0x7fec), @(0x7fed), @(0x7fee), @(0x7fef),
@(0x7ff0), @(0x7ff1), @(0x7ff2), @(0x7ff3), @(0x7ff4), @(0x7ff5), @(0x7ff6), @(0x7ff7),
@(0x7ff8), @(0x7ff9), @(0x7ffa), @(0x7ffb), @(0x7ffc), @(0x7ffd), @(0x7ffe), @(0x7fff),
@(0xbe7d), @(0xbe7e), @(0xbe7f), @(0xbefd), @(0xbefe), @(0xbeff), @(0xbf7a), @(0xbf7b),
@(0xbf7c), @(0xbf7d), @(0xbf7e), @(0xbf7f), @(0xbffd), @(0xbffe), @(0xbfff),
//
@(0xc6c8), @(0xc6c9), @(0xc6ca), @(0xc6cb), @(0xc6cc), @(0xc6cd), @(0xc6ce), @(0xc6cf),
@(0xc6fd), @(0xc6fe), @(0xc6ff), @(0xc7c8), @(0xc7c9), @(0xc7ca), @(0xc7cb), @(0xc7cc),
@(0xc7cd), @(0xc7ce), @(0xc7cf), @(0xc7fd), @(0xc7fe), @(0xc7ff), @(0xce88), @(0xce89),
@(0xce8a), @(0xce8b), @(0xce8c), @(0xce8d), @(0xce8e), @(0xce8f), @(0xcebd), @(0xcebe),
@(0xcebf), @(0xcec8), @(0xcec9), @(0xceca), @(0xcecb), @(0xcecc), @(0xcecd), @(0xcece),
@(0xcecf), @(0xcefd), @(0xcefe), @(0xceff), @(0xcf80), @(0xcf81), @(0xcf82), @(0xcf83),
@(0xcf84), @(0xcf85), @(0xcf86), @(0xcf87), @(0xcfba), @(0xcfbb), @(0xcfbc), @(0xcfbd),
@(0xcfbe), @(0xcfbf), @(0xcfc8), @(0xcfc9), @(0xcfca), @(0xcfcb), @(0xcfcc), @(0xcfcd),
@(0xcfce), @(0xcfcf), @(0xcffd), @(0xcffe), @(0xcfff),
// These are from the Bcc/BRA/BSR page.
@(0xd0fd), @(0xd0fe), @(0xd0ff), @(0xd1fd), @(0xd1fe), @(0xd1ff), @(0xd2fd), @(0xd2fe),
@(0xd2ff), @(0xd3fd), @(0xd3fe), @(0xd3ff), @(0xd4fd), @(0xd4fe), @(0xd4ff), @(0xd5fd),
@(0xd5fe), @(0xd5ff), @(0xd6fd), @(0xd6fe), @(0xd6ff), @(0xd7fd), @(0xd7fe), @(0xd7ff),
@(0xd8fd), @(0xd8fe), @(0xd8ff), @(0xd9fd), @(0xd9fe), @(0xd9ff), @(0xdafd), @(0xdafe),
@(0xdaff), @(0xdbfd), @(0xdbfe), @(0xdbff), @(0xdcfd), @(0xdcfe), @(0xdcff), @(0xddfd),
@(0xddfe), @(0xddff), @(0xdebd), @(0xdebe), @(0xdebf), @(0xdefd), @(0xdefe), @(0xdeff),
@(0xdfba), @(0xdfbb), @(0xdfbc), @(0xdfbd), @(0xdfbe), @(0xdfbf), @(0xdffd), @(0xdffe),
@(0xdfff),
// The E line is for shifts and rolls; none of the those listed below appear to nominate valid
// addressing modes.
@(0xe6c0), @(0xe6c1), @(0xe6c2), @(0xe6c3), @(0xe6c4), @(0xe6c5), @(0xe6c6), @(0xe6c7),
@(0xe6c8), @(0xe6c9), @(0xe6ca), @(0xe6cb), @(0xe6cc), @(0xe6cd), @(0xe6ce), @(0xe6cf),
@(0xe6fa), @(0xe6fb), @(0xe6fc), @(0xe6fd), @(0xe6fe), @(0xe6ff), @(0xe7c0), @(0xe7c1),
@(0xe7c2), @(0xe7c3), @(0xe7c4), @(0xe7c5), @(0xe7c6), @(0xe7c7), @(0xe7c8), @(0xe7c9),
@(0xe7ca), @(0xe7cb), @(0xe7cc), @(0xe7cd), @(0xe7ce), @(0xe7cf), @(0xe7fa), @(0xe7fb),
@(0xe7fc), @(0xe7fd), @(0xe7fe), @(0xe7ff), @(0xeec0), @(0xeec1), @(0xeec2), @(0xeec3),
@(0xeec4), @(0xeec5), @(0xeec6), @(0xeec7), @(0xeec8), @(0xeec9), @(0xeeca), @(0xeecb),
@(0xeecc), @(0xeecd), @(0xeece), @(0xeecf), @(0xeed0), @(0xeed1), @(0xeed2), @(0xeed3),
@(0xeed4), @(0xeed5), @(0xeed6), @(0xeed7), @(0xeed8), @(0xeed9), @(0xeeda), @(0xeedb),
@(0xeedc), @(0xeedd), @(0xeede), @(0xeedf), @(0xeee0), @(0xeee1), @(0xeee2), @(0xeee3),
@(0xeee4), @(0xeee5), @(0xeee6), @(0xeee7), @(0xeee8), @(0xeee9), @(0xeeea), @(0xeeeb),
@(0xeeec), @(0xeeed), @(0xeeee), @(0xeeef), @(0xeef0), @(0xeef1), @(0xeef2), @(0xeef3),
@(0xeef4), @(0xeef5), @(0xeef6), @(0xeef7), @(0xeef8), @(0xeef9), @(0xeefa), @(0xeefb),
@(0xeefc), @(0xeefd), @(0xeefe), @(0xeeff), @(0xefc0), @(0xefc1), @(0xefc2), @(0xefc3),
@(0xefc4), @(0xefc5), @(0xefc6), @(0xefc7), @(0xefc8), @(0xefc9), @(0xefca), @(0xefcb),
@(0xefcc), @(0xefcd), @(0xefce), @(0xefcf), @(0xefd0), @(0xefd1), @(0xefd2), @(0xefd3),
@(0xefd4), @(0xefd5), @(0xefd6), @(0xefd7), @(0xefd8), @(0xefd9), @(0xefda), @(0xefdb),
@(0xefdc), @(0xefdd), @(0xefde), @(0xefdf), @(0xefe0), @(0xefe1), @(0xefe2), @(0xefe3),
@(0xefe4), @(0xefe5), @(0xefe6), @(0xefe7), @(0xefe8), @(0xefe9), @(0xefea), @(0xefeb),
@(0xefec), @(0xefed), @(0xefee), @(0xefef), @(0xeff0), @(0xeff1), @(0xeff2), @(0xeff3),
@(0xeff4), @(0xeff5), @(0xeff6), @(0xeff7), @(0xeff8), @(0xeff9), @(0xeffa), @(0xeffb),
@(0xeffc), @(0xeffd), @(0xeffe), @(0xefff)
]];
NSSet<NSNumber *> *const falseValids = storage_tests.false_valids();
NSSet<NSNumber *> *const falseInvalids = storage_tests.false_invalids();
XCTAssert(!falseValids.count, "%@ opcodes should be invalid but aren't: %@", @(falseValids.count), falseValids.hexDump);
NSMutableSet<NSNumber *> *const decodedUndecodables = [undecodables mutableCopy];
[decodedUndecodables minusSet:falseInvalids];
XCTAssert(!decodedUndecodables.count, "This test considers these undecodable but they were decoded: %@", decodedUndecodables.hexDump);
NSMutableSet<NSNumber *> *const trimmedInvalids = [falseInvalids mutableCopy];
[trimmedInvalids minusSet:undecodables];
XCTAssert(!trimmedInvalids.count, "%@ opcodes should be valid but aren't: %@", @(trimmedInvalids.count), trimmedInvalids.hexDump);
// XCTAssert(!falseInvalids.count, "%@ opcodes should be valid but aren't: %@", @(falseInvalids.count), falseInvalids.hexDump);
}
@end

View File

@@ -1,140 +0,0 @@
//
// ArrayBuilderTests.m
// Clock Signal
//
// Created by Thomas Harte on 19/11/2016.
// Copyright 2016 Thomas Harte. All rights reserved.
//
#import <XCTest/XCTest.h>
#include "ArrayBuilder.hpp"
static NSData *inputData, *outputData;
static void setData(bool is_input, uint8_t *data, size_t size)
{
NSData *dataObject = [NSData dataWithBytes:data length:size];
if(is_input) inputData = dataObject; else outputData = dataObject;
}
@interface ArrayBuilderTests : XCTestCase
@end
@implementation ArrayBuilderTests
+ (void)setUp
{
inputData = nil;
outputData = nil;
}
- (void)assertMonotonicForInputSize:(size_t)inputSize outputSize:(size_t)outputSize
{
XCTAssert(inputData != nil, @"Should have received some input data");
XCTAssert(outputData != nil, @"Should have received some output data");
XCTAssert(inputData.length == inputSize, @"Input data should be %lu bytes long, was %lu", inputSize, (unsigned long)inputData.length);
XCTAssert(outputData.length == outputSize, @"Output data should be %lu bytes long, was %lu", outputSize, (unsigned long)outputData.length);
if(inputData.length == inputSize && outputData.length == outputSize)
{
uint8_t *input = (uint8_t *)inputData.bytes;
uint8_t *output = (uint8_t *)outputData.bytes;
for(int c = 0; c < inputSize; c++) XCTAssert(input[c] == c, @"Input item %d should be %d, was %d", c, c, input[c]);
for(int c = 0; c < outputSize; c++) XCTAssert(output[c] == c + 0x80, @"Output item %d should be %d, was %d", c, c+0x80, output[c]);
}
}
- (std::function<void(uint8_t *input, size_t input_size, uint8_t *output, size_t output_size)>)emptyFlushFunction
{
return [=] (uint8_t *input, size_t input_size, uint8_t *output, size_t output_size) {};
}
- (void)testSingleWriteSingleFlush
{
Outputs::CRT::ArrayBuilder arrayBuilder(200, 100, setData);
uint8_t *input = arrayBuilder.get_input_storage(5);
uint8_t *output = arrayBuilder.get_output_storage(3);
for(int c = 0; c < 5; c++) input[c] = c;
for(int c = 0; c < 3; c++) output[c] = c + 0x80;
arrayBuilder.flush(self.emptyFlushFunction);
arrayBuilder.submit();
[self assertMonotonicForInputSize:5 outputSize:3];
}
- (void)testDoubleWriteSingleFlush
{
Outputs::CRT::ArrayBuilder arrayBuilder(200, 100, setData);
uint8_t *input;
uint8_t *output;
input = arrayBuilder.get_input_storage(2);
output = arrayBuilder.get_output_storage(2);
for(int c = 0; c < 2; c++) input[c] = c;
for(int c = 0; c < 2; c++) output[c] = c + 0x80;
input = arrayBuilder.get_input_storage(2);
output = arrayBuilder.get_output_storage(2);
for(int c = 0; c < 2; c++) input[c] = c+2;
for(int c = 0; c < 2; c++) output[c] = c+2 + 0x80;
arrayBuilder.flush(self.emptyFlushFunction);
arrayBuilder.submit();
[self assertMonotonicForInputSize:4 outputSize:4];
}
- (void)testSubmitWithoutFlush
{
Outputs::CRT::ArrayBuilder arrayBuilder(200, 100, setData);
arrayBuilder.get_input_storage(5);
arrayBuilder.get_input_storage(8);
arrayBuilder.get_output_storage(6);
arrayBuilder.get_input_storage(12);
arrayBuilder.get_output_storage(3);
arrayBuilder.submit();
XCTAssert(inputData.length == 0, @"No input data should have been received; %lu bytes were received", (unsigned long)inputData.length);
XCTAssert(outputData.length == 0, @"No output data should have been received; %lu bytes were received", (unsigned long)outputData.length);
arrayBuilder.flush(self.emptyFlushFunction);
arrayBuilder.submit();
XCTAssert(inputData.length == 25, @"All input data should have been received; %lu bytes were received", (unsigned long)inputData.length);
XCTAssert(outputData.length == 9, @"All output data should have been received; %lu bytes were received", (unsigned long)outputData.length);
}
- (void)testSubmitContinuity
{
Outputs::CRT::ArrayBuilder arrayBuilder(200, 100, setData);
arrayBuilder.get_input_storage(5);
arrayBuilder.get_output_storage(5);
arrayBuilder.flush(self.emptyFlushFunction);
uint8_t *input = arrayBuilder.get_input_storage(5);
uint8_t *output = arrayBuilder.get_output_storage(5);
arrayBuilder.submit();
for(int c = 0; c < 5; c++) input[c] = c;
for(int c = 0; c < 5; c++) output[c] = c + 0x80;
arrayBuilder.flush(self.emptyFlushFunction);
arrayBuilder.submit();
[self assertMonotonicForInputSize:5 outputSize:5];
}
@end

View File

@@ -8,6 +8,14 @@
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSInteger, MOS6522BridgePort) {
MOS6522BridgePortA = 0, MOS6522BridgePortB = 1
};
typedef NS_ENUM(NSInteger, MOS6522BridgeLine) {
MOS6522BridgeLineOne = 0, MOS6522BridgeLineTwo = 1
};
@interface MOS6522Bridge : NSObject
@property (nonatomic, readonly) BOOL irqLine;
@@ -16,6 +24,7 @@
- (void)setValue:(uint8_t)value forRegister:(NSUInteger)registerNumber;
- (uint8_t)valueForRegister:(NSUInteger)registerNumber;
- (BOOL)valueForControlLine:(MOS6522BridgeLine)line port:(MOS6522BridgePort)port;
- (void)runForHalfCycles:(NSUInteger)numberOfHalfCycles;

View File

@@ -19,7 +19,12 @@ class VanillaVIAPortHandler: public MOS::MOS6522::PortHandler {
bool irq_line;
uint8_t port_a_value;
uint8_t port_b_value;
bool control_line_values[2][2];
/*
All methods below here are to replace those defined by
MOS::MOS6522::PortHandler.
*/
void set_interrupt_status(bool new_status) {
irq_line = new_status;
}
@@ -27,6 +32,10 @@ class VanillaVIAPortHandler: public MOS::MOS6522::PortHandler {
uint8_t get_port_input(MOS::MOS6522::Port port) {
return port ? port_b_value : port_a_value;
}
void set_control_line_output(MOS::MOS6522::Port port, MOS::MOS6522::Line line, bool value) {
control_line_values[int(port)][int(line)] = value;
}
};
@implementation MOS6522Bridge {
@@ -75,4 +84,8 @@ class VanillaVIAPortHandler: public MOS::MOS6522::PortHandler {
return _viaPortHandler.port_b_value;
}
- (BOOL)valueForControlLine:(MOS6522BridgeLine)line port:(MOS6522BridgePort)port {
return _viaPortHandler.control_line_values[port][line];
}
@end

View File

@@ -0,0 +1,59 @@
//
// Comparative68000.hpp
// Clock SignalTests
//
// Created by Thomas Harte on 29/04/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef Comparative68000_hpp
#define Comparative68000_hpp
#include <zlib.h>
#include "68000.hpp"
class ComparativeBusHandler: public CPU::MC68000::BusHandler {
public:
ComparativeBusHandler(const char *trace_name) {
trace = gzopen(trace_name, "rt");
}
~ComparativeBusHandler() {
gzclose(trace);
}
void will_perform(uint32_t address, uint16_t opcode) {
// Obtain the next line from the trace file.
char correct_state[300] = "\n";
gzgets(trace, correct_state, sizeof(correct_state));
++line_count;
// Generate state locally.
const auto state = get_state();
char local_state[300];
sprintf(local_state, "%04x: %02x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n",
address,
state.status,
state.data[0], state.data[1], state.data[2], state.data[3], state.data[4], state.data[5], state.data[6], state.data[7],
state.address[0], state.address[1], state.address[2], state.address[3], state.address[4], state.address[5], state.address[6],
(state.status & 0x2000) ? state.supervisor_stack_pointer : state.user_stack_pointer
);
// Check that the two coincide.
if(strcmp(correct_state, local_state)) {
fprintf(stderr, "Diverges at line %d\n", line_count);
fprintf(stderr, "Good: %s", correct_state);
fprintf(stderr, "Bad: %s", local_state);
assert(false);
}
}
virtual CPU::MC68000::ProcessorState get_state() = 0;
private:
int line_count = 0;
gzFile trace;
};
#endif /* Comparative68000_hpp */

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