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

Compare commits

...

1706 Commits

Author SHA1 Message Date
Thomas Harte
32ccce3040 Merge pull request #855 from TomHarte/QtNoKeyboardCopy
Qt: don't copy the result of get_keyboard().
2020-11-29 00:05:26 -05:00
Thomas Harte
ab3fcb3ea0 Qt: don't copy the result of get_keyboard(). 2020-11-29 00:01:11 -05:00
Thomas Harte
9610672615 Merge pull request #854 from TomHarte/OpenGLNoColourBurst
Avoids all risk of infinities when there is no colour burst
2020-11-28 23:54:29 -05:00
Thomas Harte
5ee9630624 Use compositeAmplitude in favour of its reciprocal. 2020-11-28 19:53:34 -05:00
Thomas Harte
1b3836eb1c Adds an overt branch for mono/colour composite selection. 2020-11-28 19:47:04 -05:00
Thomas Harte
1302a046e9 Merge branch 'OpenGLNoColourBurst' of github.com:TomHarte/CLK into OpenGLNoColourBurst 2020-11-28 17:19:42 -05:00
Thomas Harte
33dec3c220 Given that lineCompositeAmplitude is not normalised, ups threshold. 2020-11-28 17:19:28 -05:00
Thomas Harte
7c29c3a944 Given that lineCompositeAmplitude is not normalised, ups threshold. 2020-11-28 17:13:18 -05:00
Thomas Harte
c9ca1fc7a0 Merge pull request #853 from TomHarte/AppleIIReset
Improves Apple II keyboard input, especially under SDL.
2020-11-28 12:43:32 -05:00
Thomas Harte
a965c8de9f Resolves intended reset_all_keys. 2020-11-27 21:53:34 -05:00
Thomas Harte
0b4b271e3d Pulls out redundant check. 2020-11-27 21:04:20 -05:00
Thomas Harte
5fc6dd1a4d Regresses macOS deployment target for kiosk mode to avoid OpenGL warning. 2020-11-27 21:02:04 -05:00
Thomas Harte
79ef026b93 Allows machines to declare a preference for logical input.
It's only a preference, and the Apple II does prefer it.
2020-11-27 21:00:48 -05:00
Thomas Harte
a4ab5b0b49 Does a better job of ensuring sensible key mappings. 2020-11-27 20:49:38 -05:00
Thomas Harte
3207183f05 Merge pull request #850 from TomHarte/BigSurAgain
Takes a second stab at resolving Big Sur File -> New...
2020-11-13 20:02:29 -05:00
Thomas Harte
e803f993b7 Increases minimum macOS version to 10.14.
This is lazy, but it means I definitely don't need non-Metal fallback code.
2020-11-13 19:48:45 -05:00
Thomas Harte
5dbc87caf0 Smarter: just ensures any attached panels are closed at close(). 2020-11-13 19:09:30 -05:00
Thomas Harte
4862ccc947 Dismisses ROM requester upon that cancel too. 2020-11-13 19:01:53 -05:00
Thomas Harte
e1ecf66485 Dismisses sheet before closing document. 2020-11-13 19:00:37 -05:00
Thomas Harte
2c71ba0744 Ameliorates against a potential NSRangeException. 2020-11-13 18:29:48 -05:00
Thomas Harte
a7aeb779e9 Disables Apple Silicon binaries until I have some means to test. 2020-11-13 18:07:45 -05:00
Thomas Harte
e72cfbf447 Stop assuming that NSNotification => window.isVisible. 2020-11-13 18:04:31 -05:00
Thomas Harte
5d154e3d0c Merge pull request #849 from TomHarte/BigSur
Slightly tweaks machine picker for macOS Big Sur.
2020-11-13 12:37:36 -05:00
Thomas Harte
86a24cc928 Allows Xcode to bump its versioning on the ROM requester too. 2020-11-13 12:23:48 -05:00
Thomas Harte
e8b52d20e9 Slightly tweaks machine picker for macOS Big Sur. 2020-11-13 12:14:35 -05:00
Thomas Harte
b5fa574686 Merge pull request #847 from TomHarte/MetalTones
Corrects R4G4B4 and R2G2B2 output in Metal.
2020-11-07 23:22:36 -05:00
Thomas Harte
7aea3dc124 Corrects R4G4B4 and R2G2B2 output. 2020-11-07 23:15:48 -05:00
Thomas Harte
3aa47f9c68 Merge pull request #843 from TomHarte/MoreDormann
Introduces a build of Dormann's 65C02 tests that is 65816 compatible.
2020-10-19 21:13:46 -04:00
Thomas Harte
ab07814614 Eliminates now-broken 65816 flow test. 2020-10-19 21:02:46 -04:00
Thomas Harte
1653abdf88 Adds the .lst; otherwise I'll probably just lose it. 2020-10-19 20:58:24 -04:00
Thomas Harte
b3ab9fff9b Imports a custom-built copy of Klaus Dormann's 65C02 test, with only 65816-compatible parts.
Thereby fixes another couple of 65816 issues — BRK(, etc) not clearing the decimal flag, and `TRB d` being mismapped.
2020-10-19 19:27:16 -04:00
Thomas Harte
14718b93a4 Improve commentary. 2020-10-19 09:32:50 -04:00
Thomas Harte
69450e27ad Merge pull request #839 from TomHarte/65816
Adds emulation of the 65816.
2020-10-18 21:49:46 -04:00
Thomas Harte
0cd08aa79d Permits the Oric analyser to check CPC-style DSKs.
Oric Mist seems to use that format, so some of these now exist out in the wild.
2020-10-18 21:44:44 -04:00
Thomas Harte
1fa94e1b08 Adds the 65816 as an in-code option for Oric emulation.
This also means it'll be exposed via the SDL build, but that's okay.
2020-10-18 21:43:08 -04:00
Thomas Harte
76d9893866 Declares address-bus sizes formally.
This allows me to fix the final two implicit conversion warnings, albeit that it would have been nice to find a templatey way just to get the type directly from the declaration of `perform_bus_operation`.
2020-10-18 15:08:21 -04:00
Thomas Harte
c3f8982c62 Resolves all internal implicit type-conversion warnings.
Chasing those down, it looks like flags were wrong for PLB and PLD. So it's official: warnings help.
2020-10-18 14:55:17 -04:00
Thomas Harte
99eba2f8ba Ensures intended 65816 exception behaviour.
i.e. the relevant micro-op sequence exists, and its operation isn't lost. Also sets the 65816 by default to jump straight into power-on, not to execute an instruction first. That shouldn't make a functional difference, but it makes debugging easier because it makes startup fully deterministic.
2020-10-18 14:43:47 -04:00
Thomas Harte
69509f6502 Attempts to bring a little more consistency to my use of Swift in test code. 2020-10-17 22:42:54 -04:00
Thomas Harte
c3187fdbe1 Makes minor formatting improvement. 2020-10-17 22:31:51 -04:00
Thomas Harte
42228ea955 Adds 65C02As6502 test, to round out the set. 2020-10-17 22:31:32 -04:00
Thomas Harte
e5f57ea743 Make isReadOperation more overt. 2020-10-17 22:27:04 -04:00
Thomas Harte
3b398f7a9a Attempts to complete all 65816 bus signalling. 2020-10-16 21:56:20 -04:00
Thomas Harte
096add7551 Exposes non-BusOperation bus outputs. 2020-10-16 21:05:42 -04:00
Thomas Harte
334e0666b7 Reports ::Ready upon a WAI. 2020-10-15 21:37:37 -04:00
Thomas Harte
98c81749c8 Adds the conventional flush. 2020-10-15 21:36:04 -04:00
Thomas Harte
5dcf720bb5 Extends list of BusOperations.
Now to retest, widely.
2020-10-15 21:35:01 -04:00
Thomas Harte
9c0c0255f6 Ensures data/program bank can't accidentally be set to 16-bit values. 2020-10-15 21:10:32 -04:00
Thomas Harte
68c15bd605 Updates Qt project; catches another couple of issues via its compiler. 2020-10-15 21:09:22 -04:00
Thomas Harte
9a2f32795f Revokes stack-local storage non-optimisation. 2020-10-15 21:03:10 -04:00
Thomas Harte
7aa6cf4c6b Tidies up layout very slightly. 2020-10-15 20:51:23 -04:00
Thomas Harte
dfda2adf0d Attempts implementations of both ready and abort.
Which I think concludes the inputs?
2020-10-15 20:46:18 -04:00
Thomas Harte
c0a1c34012 Wraps all registers into a struct, so that I can implement abort.
Makes some preparations for ready too.
2020-10-15 18:42:38 -04:00
Thomas Harte
3c6adc1ff4 Completes 65816 addressing mode tests and corresponding fixes. 2020-10-14 22:00:52 -04:00
Thomas Harte
e511d33a7c Adds test for [d], y; fixes implementation. 2020-10-14 21:42:41 -04:00
Thomas Harte
c35969d677 Adds tests for (d, x) and (d), y. Both amply tested in emulation mode, so no problems.
Five to go, all potentially troublesome.
2020-10-14 21:38:00 -04:00
Thomas Harte
27afb8f0a7 Adds direct indirect long test, and thereby fixes addressing mode.
Nine to go!
2020-10-14 21:26:20 -04:00
Thomas Harte
327ab81436 Fills in direct, x and (direct) tests, fixing implementation of the latter.
10 to go.
2020-10-14 21:17:28 -04:00
Thomas Harte
db7178495f Implements direct and final absolute test.
14 to go.
2020-10-14 20:57:47 -04:00
Thomas Harte
979186e71d Transcribes the English-language versions of the outstanding tests.
Passing these will make me willing to call the 65816 functionality provisionally done, other than making sureI signal VPA, VDA, VPB, etc, correctly.
2020-10-14 13:56:37 -04:00
Thomas Harte
f05e0d956b Adds a TODO list in order to keep an end in sight. 2020-10-13 21:43:42 -04:00
Thomas Harte
b22aa5d699 Starts transcribing the addressing examples I have into tests.
Correspondingly extends the exposed register set and test-machine addressing range.
2020-10-13 21:38:30 -04:00
Thomas Harte
3e6a2adaaf Corrects absolute, x and absolute, y addressing modes. 2020-10-13 20:30:39 -04:00
Thomas Harte
8f5537aaaa Attempts to resolve my direct-indirect addressing stumble. 2020-10-13 20:21:53 -04:00
Thomas Harte
a15d4a156b Starts trying to ensure appropriate address wrapping. 2020-10-12 22:33:43 -04:00
Thomas Harte
6a47571d17 Stops truncating tests by two bytes.
Not that it seems to have been problematic.
2020-10-12 21:53:27 -04:00
Thomas Harte
7479dc74ed Removes printf. It's no longer telling me anything. 2020-10-12 21:52:58 -04:00
Thomas Harte
28da1a724a Introduces Jeek816 test case. 2020-10-12 21:43:44 -04:00
Thomas Harte
f529eadbec Corrects 16-bit read-modify-write.
Subject to the TODO proviso on 'correct'; has my 6502 prejudice pushed me into unrealistic bus signalling?
2020-10-12 18:36:09 -04:00
Thomas Harte
5dc3cd3a2f Starts using Jeek816 for a basic native-mode audit. Fixes absolute long addressing. 2020-10-11 22:02:46 -04:00
Thomas Harte
3039a445f0 Ups the 65816 test machine to a full 16mb RAM. 2020-10-11 21:18:01 -04:00
Thomas Harte
82797fd395 Attempts to do the proper thing for interrupts. 2020-10-11 21:10:44 -04:00
Thomas Harte
a0885ab7d0 Implements STP and WAI.
Albeit still without fully-implemented reactions to exceptions in general.
2020-10-11 17:56:55 -04:00
Thomas Harte
8eaf1303a3 Attempts proactively to ensure proper RTI behaviour on the 65816. 2020-10-11 15:25:13 -04:00
Thomas Harte
20cbe72985 Ties to 8- or 16-bit those instructions that aren't M/X-dependent.
This is technically redundant for PEI, PEA and PER since they have dedicated bus programs anyway, but it's good to be explicit.
2020-10-11 14:38:35 -04:00
Thomas Harte
071ad6b767 I don't think RTL is needed; JML looks like it covers it. 2020-10-10 22:16:35 -04:00
Thomas Harte
0619e49eac Takes a short at TSB and TRB.
Three to go.
2020-10-10 22:00:17 -04:00
Thomas Harte
b8848d8580 Implements TCD, TDC, TCS, TSC. 2020-10-10 21:43:05 -04:00
Thomas Harte
aface1f8be Implements XBA and XCE. 2020-10-10 21:34:22 -04:00
Thomas Harte
ae87728770 Ensures M and X are exposed to the public interface. 2020-10-10 21:33:56 -04:00
Thomas Harte
28c8ba70c1 Implements REP and SEP and exposes the MX flags generally. 2020-10-10 21:23:59 -04:00
Thomas Harte
486324ecab This test isn't actually 65816-compatible. 2020-10-10 18:19:48 -04:00
Thomas Harte
6892ac13e8 Corrects BIT. All 65816-applicable Wolfgang Lorenz tests now pass. 2020-10-10 17:47:33 -04:00
Thomas Harte
340ad093a6 Adds 65816 runs of the final tranche of applicable tests. 2020-10-10 17:26:41 -04:00
Thomas Harte
0fe09cd1e4 Knocks SBC into producing likely results; disables Lorenz testing. 2020-10-10 17:13:16 -04:00
Thomas Harte
da4702851f Fixes ADC. 2020-10-10 16:29:48 -04:00
Thomas Harte
09fba72d58 Adds flag manipulation, ADC and SBC 65816 tests.
The latter two fail.
2020-10-10 11:30:15 -04:00
Thomas Harte
d17c90edf7 Corrects ROL d, x. 2020-10-10 11:25:14 -04:00
Thomas Harte
7966592fae Corrects ROL d. 2020-10-10 11:22:23 -04:00
Thomas Harte
6efe4e1753 Fixes AND, EOR, ORA. Takes an unsuccessful shot at ROL. 2020-10-10 10:53:17 -04:00
Thomas Harte
536c4d45c1 Adds additional 65816 tests, some failing; seeks to improve carry behaviour in ASL and ROL. 2020-10-10 10:11:57 -04:00
Thomas Harte
a02f88fe7c Confirms a couple more of the easy sets. 2020-10-10 09:34:29 -04:00
Thomas Harte
d9be6ab806 Confirms that a few other simple tests work immediately on the 65816. 2020-10-09 23:26:35 -04:00
Thomas Harte
290598429a Applies indirect page zero emulation mode addressing constraint to ix addressing.
Lorenz's LDA tests now pass in emulation mode.
2020-10-09 23:22:48 -04:00
Thomas Harte
92e72959c3 Makes corrections to ix addressing mode and shift/roll flags. 2020-10-09 23:12:20 -04:00
Thomas Harte
776f014dbe Attempts LDA tests against the 65816.
Result: ix is faulty. Which we already knew.
2020-10-09 22:23:54 -04:00
Thomas Harte
c01bc784b9 Slightly reduces branching. 2020-10-09 22:21:55 -04:00
Thomas Harte
abcd86a294 Fixes accumulator instructions. 2020-10-09 22:18:22 -04:00
Thomas Harte
451f83ba51 Corrects emulation-mode read-modify-writes not to empty the data buffer. 2020-10-09 22:14:42 -04:00
Thomas Harte
b439f40fe2 Corrects INC and DEC. 2020-10-09 22:04:25 -04:00
Thomas Harte
968166b06d Resolves incorrectly flow after setting up an absolute address. 2020-10-09 21:48:35 -04:00
Thomas Harte
88293909f4 Enables running of a first test on the 65816. 2020-10-09 21:44:47 -04:00
Thomas Harte
9b6c48631d Removes usage of a JAM instruction to spot end-of-tests. 2020-10-09 21:39:34 -04:00
Thomas Harte
0ed98cbfac Attempts to fix direct indirect indexed; not yet successful I think. 2020-10-08 22:15:19 -04:00
Thomas Harte
7dde7cc743 Implements altered direct indexed addressing in emulation mode. 2020-10-08 22:02:14 -04:00
Thomas Harte
755627f12d Corrects direct addressing. 2020-10-08 20:00:01 -04:00
Thomas Harte
f8004d7096 Implements RTI, corrects TAY. 2020-10-08 18:06:11 -04:00
Thomas Harte
0418f51ef2 Takes a shot at emulation-mode 'exceptions'.
It's just RTI and correct decimal SBC left of the official 6502s now, I think.
2020-10-08 17:52:13 -04:00
Thomas Harte
054e0af071 Corrects RTS behaviour: the return address on the stack is off by one.
Dormann's tests now proceed to a BRK.
2020-10-08 16:55:45 -04:00
Thomas Harte
907c3374c3 Attempts to clean up my JMP/JSR mess.
Also takes a step forwards in decimal SBC, but it's not right yet.
2020-10-08 16:48:46 -04:00
Thomas Harte
b578240993 Adds a further error.
Clearly I've severely overloaded 'JMP' and not fully thought through where it gets its addresses from.
2020-10-07 21:47:58 -04:00
Thomas Harte
f83ee97439 PHP pushes with the BRK flag set in emulation mode. 2020-10-07 21:37:50 -04:00
Thomas Harte
19aea85184 Corrects CMP, CPX, CPY carry flags. 2020-10-07 21:23:29 -04:00
Thomas Harte
1ba0a117e7 Corrects PLB, PLD, PLP. 2020-10-07 20:23:53 -04:00
Thomas Harte
b510b9d337 Adds PHD, PHK and 8-bit PHP and PLP. 2020-10-07 20:13:12 -04:00
Thomas Harte
b608e11965 Realises that not all non-incrementing PC fetches should be thrown away. 2020-10-07 20:06:27 -04:00
Thomas Harte
e68b3a2f32 Corrects JMP program. 2020-10-07 19:59:29 -04:00
Thomas Harte
f7b119ffe1 Moves temporary logging, fixes branch instructions. 2020-10-07 19:57:58 -04:00
Thomas Harte
a4cec95db1 Corrects load and transfer flag oversights. 2020-10-07 19:36:23 -04:00
Thomas Harte
84c4fa197b Corrects DEX mapping, notes new Dormann failure case. 2020-10-07 18:48:03 -04:00
Thomas Harte
eac722cf59 Implements enough of ADC and SBC for the Dormann test definitively to fail. 2020-10-07 18:36:17 -04:00
Thomas Harte
7439a326a6 Implements BIT (in regular and immediate forms). 2020-10-07 18:15:18 -04:00
Thomas Harte
5ca1c0747f Generalises CMP to implement CPX and CPY. 2020-10-07 18:09:56 -04:00
Thomas Harte
466ca38dfa Corrects TXY and TYX; kudos to PatrickvL for the spot! 2020-10-07 18:05:42 -04:00
Thomas Harte
93b0839036 Knocks out some transfer operations.
I'm possibly only seven or eight away from being able to test with complete official-opcode-only 6502 code?
2020-10-06 22:29:34 -04:00
Thomas Harte
e068cbc103 Implements CMP and fixes a zero-flag error on 16-bit operations. 2020-10-06 21:47:26 -04:00
Thomas Harte
5c809e5fbf Implements rolls and shifts. 2020-10-06 21:34:39 -04:00
Thomas Harte
3933bf49cf Implements BRL. 2020-10-06 21:28:54 -04:00
Thomas Harte
7065ba4857 Implements the single-byte branches. 2020-10-06 21:24:43 -04:00
Thomas Harte
ebff83018e Implements the bitwise operators. 2020-10-06 20:17:03 -04:00
Thomas Harte
9ce9167e3c Formalises work left to do. 2020-10-06 19:12:19 -04:00
Thomas Harte
993eff1d3d Starts slowly, with flag manipulation. 2020-10-06 16:25:30 -04:00
Thomas Harte
7be983ec00 Slightly improve exposition. 2020-10-05 22:25:20 -04:00
Thomas Harte
18e8d6ce06 Makes an effort to factor out the 6502's [lazy] flags.
This is preparatory to deciding which instructions, if any, are worth factoring out.
2020-10-05 22:23:33 -04:00
Thomas Harte
b7ba0d4327 Attempts to complete all addressing modes.
So, if bugs didn't exist, it'd just be members of the Operation enum to go.
2020-10-05 17:04:57 -04:00
Thomas Harte
825201f4f2 Adds direct indirect. 2020-10-04 22:11:41 -04:00
Thomas Harte
9a05c68ce7 Attempts direct and direct indexed indirect. 2020-10-04 22:06:25 -04:00
Thomas Harte
d8dccf2500 Attempts a full implementation of MVN and MVP. 2020-10-04 19:21:04 -04:00
Thomas Harte
b416aa640f Slightly tidies up, eliminating some store bugs. 2020-10-04 19:12:04 -04:00
Thomas Harte
4ebf594b3b This should bring me up to absolute, y.
i.e. next is datasheet program 7.
2020-10-04 19:02:47 -04:00
Thomas Harte
8a83024962 Starts a dash towards just completing the addressing modes for now.
This brings me up to the end of absolute long (i.e. 4a on the datasheet).
2020-10-04 18:52:46 -04:00
Thomas Harte
bdc1136b96 Edges towards working short absolute addressing mode. 2020-10-03 21:30:24 -04:00
Thomas Harte
da78dea98f Merge branch 'master' into 65816 2020-10-03 21:00:29 -04:00
Thomas Harte
dcf8cb14e2 Merge pull request #842 from TomHarte/QtMouseEscape
Adds F8+F12 as an alternative mouse-release combo for Qt.
2020-10-02 20:37:46 -04:00
Thomas Harte
38912859e1 Adds F8+F12 as an alternative mouse-release combo for Qt. 2020-10-02 20:31:47 -04:00
Thomas Harte
b83d93abc2 Accepts that whether instructions do 8- or 16-bit bus accesses depends on either M or X depending on the operation. 2020-10-02 17:08:30 -04:00
Thomas Harte
36f843bc6e Ensure std::function is visible to 65816Storage.cpp. 2020-09-29 19:23:38 -04:00
Thomas Harte
15c87e02e9 Ditto for printf. 2020-09-29 18:53:02 -04:00
Thomas Harte
00923eac7c Ensure assert is visible to 65816Implementation.hpp. 2020-09-29 18:52:25 -04:00
Thomas Harte
a72ac8294c Adds 65816 alternates to Klaus Dormann's tests.
While also correcting a couple of misspellings of his name. Apologies, Klaus!
2020-09-29 18:49:58 -04:00
Thomas Harte
4f03bf754d Adds the 65816 to SConstruct. 2020-09-29 18:43:39 -04:00
Thomas Harte
78b3ec4b10 The actual work begins: starts implementing 65816 micro-ops. 2020-09-29 18:42:07 -04:00
Thomas Harte
ef1a514785 Introduces 6502Selector, for picking either a 6502 or a 65816 based on a single template parameter. 2020-09-28 21:35:46 -04:00
Thomas Harte
6635876e7e Performs a bare factoring out of the 6502 bus handler. 2020-09-28 18:43:53 -04:00
Thomas Harte
5645f90abe Takes a minor first step towards actually performing 65816 instructions. 2020-09-27 22:20:58 -04:00
Thomas Harte
b96cd4d18b Resolves another unsafe pointer assumption. 2020-09-27 22:20:13 -04:00
Thomas Harte
ad8a2e2cb9 Corrects a long-standing naming obscurity. 2020-09-27 22:19:42 -04:00
Thomas Harte
fa438e5113 Merge branch 'master' into 65816 2020-09-27 15:10:54 -04:00
Thomas Harte
8641494809 Resolve various test-case warnings. 2020-09-27 15:10:29 -04:00
Thomas Harte
f4a23af5d6 Merge pull request #840 from MaddTheSane/patch-1
Update 6502TimingTests.swift
2020-09-27 15:02:10 -04:00
Thomas Harte
5449e90b34 Edges towards offering the 65816 as another type of 6502 for testing. 2020-09-26 22:31:50 -04:00
Thomas Harte
1cd664ad85 Adds a sanity check. 2020-09-26 21:43:26 -04:00
Thomas Harte
e680022b1f Completes the opcode set.
A million bugs yet to find.
2020-09-26 21:35:31 -04:00
Thomas Harte
67c2ce2174 Takes a run at completing the stack section.
I'm not really sure about BRK though — does it gain a signature on the 65816?
2020-09-26 21:20:01 -04:00
Thomas Harte
596e700b60 Drags myself onto the final page of bus programs.
233 opcodes now complete; six bus programs to go.
2020-09-26 20:57:24 -04:00
Thomas Harte
4a53b6e538 Adds push and pull, reaching 229/256 opcodes. 2020-09-26 20:38:29 -04:00
Thomas Harte
687f4bb3bb Adds relative and relative long bus patterns.
Many of the rest cover only one or two opcodes so this puts me at 216/256 opcodes covered; 35/47 bus programs; just more than 5/7 pages.
2020-09-26 20:24:50 -04:00
Thomas Harte
473799cb62 There's not a lot to STP and WAI from a bus program point of view. 2020-09-26 20:18:30 -04:00
C.W. Betts
ce0536cdfa Update 6502TimingTests.swift 2020-09-26 16:13:27 -06:00
Thomas Harte
3dc22a9fd5 Adds implied and immediate modes.
... for 204/256 opcodes covered.
2020-09-26 17:42:42 -04:00
Thomas Harte
f54b655606 Adds d, x and d, y. 2020-09-26 17:26:17 -04:00
Thomas Harte
d2e868ea2b Adds (d), y; [d], y; and [d].
Now covered: 146/256 opcodes, 4/7 pages, 25/47 bus programs.
2020-09-26 16:55:58 -04:00
Thomas Harte
3fc649359a Transcribes the titles of all remaining bus programs.
Thereby frames the distance yet to travel.
2020-09-25 22:29:19 -04:00
Thomas Harte
1512ac11da Adds (d, x) and (d) modes. Albeit by deferring the hard work.
That's: 122/256 opcodes; 22/47 bus programs, ~3.5/7 pages transcribed. Maybe I'll be able to get to the runtime stuff sooner rather than later?
2020-09-25 22:22:30 -04:00
Thomas Harte
5039cc7bb2 Adds direct page.
... to cover 106 opcodes.
2020-09-25 22:01:36 -04:00
Thomas Harte
5360a7b4ce Adds block moves.
These are fairly specialised, dealing in two data addresses simultaneously.
2020-09-25 21:49:03 -04:00
Thomas Harte
2957a31f40 Adds absolute, x; absolute,y; and accumulator addressing modes.
Now covered: 80/256 opcodes, from 2/6 pages of the data sheet; or 16/47 bus programs.
2020-09-25 21:16:36 -04:00
Thomas Harte
8c11df52bf Adds absolute long, x.
Factors out the commonality of a closing read/write while I'm here.
2020-09-25 19:27:17 -04:00
Thomas Harte
2b7ffcd48f Takes a run at JSL al. 2020-09-25 18:35:00 -04:00
Thomas Harte
7980a9033e Adds two-thirds of absolute long.
Working total: 31 opcodes covered; 10/47ths of bus patterns.

Next is JSL, which I think will require additional operations.
2020-09-25 18:16:49 -04:00
Thomas Harte
125ddfa513 Pays a little attention to runtime storage; completes the first page of bus patterns. 2020-09-25 18:00:02 -04:00
Thomas Harte
636e929607 Adds a check for 8/16-bit redundancy. 2020-09-25 17:42:42 -04:00
Thomas Harte
22c792dc46 Adds enough logic to start serialising instructions to somewhere.
Possibly extraneous for now, but it means I can start stepping and testing.
2020-09-25 17:18:25 -04:00
Thomas Harte
95af1815c8 Completes absolute indexed indirect micro-ops.
For the record: this is just six out of forty-seven codes complete. Or about two-thirds of six pages. Plenty to do even before I start trying to interpret these things.
2020-09-24 22:37:31 -04:00
Thomas Harte
d707c5ac95 Switches to generators with stable pointers; adds 2a. 2020-09-24 22:27:20 -04:00
Thomas Harte
5c9192e5e6 Switches to generators for spitting out micro-ops.
Hopefully with a lot of parts to factor out naturally.
2020-09-24 17:36:11 -04:00
Thomas Harte
72b5584042 Immediately runs afoul of a read/write difference in the specs between 8/16-bit mode that suggests maybe this isn't a good structure.
Perhaps generators of some sort?
2020-09-23 22:28:15 -04:00
Thomas Harte
f9045b5352 Rounds out declaration of the absolutes. 2020-09-23 22:23:23 -04:00
Thomas Harte
f87fe92bc8 Begins a meandering road towards the 65816. 2020-09-23 22:14:42 -04:00
Thomas Harte
669d8e64ab Merge pull request #838 from TomHarte/MTKViewRace
Resolves a minor MTKView main-thread race condition.
2020-09-22 22:18:57 -04:00
Thomas Harte
9447aa38be Removes debugging printf. 2020-09-22 22:13:54 -04:00
Thomas Harte
a781c3eb4d Resolves thread-unsafe access of _view.bounds. 2020-09-22 22:13:37 -04:00
Thomas Harte
c0b1308dfd Merge branch 'Vic20Tests' 2020-09-22 22:08:29 -04:00
Thomas Harte
2d9dd6704a Merge branch 'master' of github.com:TomHarte/CLK 2020-09-22 22:07:47 -04:00
Thomas Harte
94dba70bbe Merge pull request #837 from TomHarte/Vic20Tests
Further improves 6522 emulation.
2020-09-22 22:07:34 -04:00
Thomas Harte
022ec20e75 Tries to add semantic meaning to the various auxiliary control fields.
To consider: decoding at set?
2020-09-22 20:50:39 -04:00
Thomas Harte
41f69405d8 Don't decrement timer 1 from the system clock when in PB6 mode.
TODO: rest of PB6 mode.
2020-09-21 22:39:49 -04:00
Thomas Harte
5741e22e29 Switch back to debug-by-default builds. 2020-09-20 18:22:13 -04:00
Thomas Harte
8e242eea54 Ensures timer-linked PB7 output is actually output. 2020-09-20 15:03:26 -04:00
Thomas Harte
703065a0a5 Takes a run at timer-linked PB7 output behaviour.
Seemingly sufficiently to pass the VICE test (which I've transcribed), though with some guesswork.
2020-09-20 14:51:59 -04:00
Thomas Harte
291aa42fe1 Corrects test target. 2020-09-19 21:20:37 -04:00
Thomas Harte
8fc3496cc9 Merge pull request #836 from TomHarte/Vic20Tests
Corrects a couple of minor VIA timer issues
2020-09-17 21:56:43 -04:00
Thomas Harte
e807a462a1 My new reading is that only a write to the counter should affect the interrupt flag. 2020-09-17 21:31:29 -04:00
Thomas Harte
18790a90ae Ensures timer 2 doesn't use timed behaviour when in pulse mode. 2020-09-17 21:09:32 -04:00
Thomas Harte
21afc70261 Adds formal data-sheet names. 2020-09-17 19:00:46 -04:00
Thomas Harte
7bb74af478 Merge pull request #835 from TomHarte/ErrantScan
Allows for permitted 1/32nd timing error in `time_multiplier_`.
2020-09-17 18:17:37 -04:00
Thomas Harte
894269aa06 Allows for permitted 1/32nd timing error in time_multiplier_. 2020-09-17 18:12:21 -04:00
Thomas Harte
8b16da9695 Merge pull request #834 from TomHarte/FloatingSpeaker
Resolves lowpass-speaker position aliasing
2020-09-16 19:05:54 -04:00
Thomas Harte
f783ec6269 Since input and output are floating point, using an integer Stepper is not appropriate. 2020-09-16 18:53:44 -04:00
Thomas Harte
22c9734874 Merge pull request #832 from TomHarte/MetalScanTarget
Adds a Metal ScanTarget, for macOS.
2020-09-16 18:19:58 -04:00
Thomas Harte
a17d0e428f Protects against some further uninitialised values. 2020-09-16 18:15:57 -04:00
Thomas Harte
bb57f0bcc7 Ensures all 6560 properties have a valid default value. 2020-09-16 17:24:18 -04:00
Thomas Harte
b1aefbfe85 Separates asserts. 2020-09-15 23:24:06 -04:00
Thomas Harte
061288f5a7 Add the Macintosh to the Mac kiosk mode informal test set. 2020-09-15 22:49:00 -04:00
Thomas Harte
5a53474536 Ensure MultiKeyboard deconstructs properly. 2020-09-15 22:48:44 -04:00
Thomas Harte
18d0fff8da Graduates the Atari ST. 2020-09-15 22:46:38 -04:00
Thomas Harte
0ac2145740 Add Metal/OpenGL distinction. 2020-09-15 22:43:39 -04:00
Thomas Harte
bc8787ded6 Improves macro safety. 2020-09-15 22:26:33 -04:00
Thomas Harte
69d21daaa3 Improves commentary. 2020-09-15 22:21:05 -04:00
Thomas Harte
5651ef606d Resolves failure to advance video address when output is blocked. 2020-09-15 22:20:06 -04:00
Thomas Harte
b831b31382 Adds a further sanity check. 2020-09-15 17:04:04 -04:00
Thomas Harte
2fd5cc056c Adds std::atomic_thread_fences, but these seem not to be a magic bullet. 2020-09-15 16:34:34 -04:00
Thomas Harte
82dbdf7dfc Switches to using regular linear interpolation for supersampling. 2020-09-14 22:36:00 -04:00
Thomas Harte
eb9903cd10 Defensively disables allocation of anything outside of visible lines. 2020-09-14 22:29:05 -04:00
Thomas Harte
227e98d6d7 Slightly simplifies control flow. 2020-09-14 22:27:25 -04:00
Thomas Harte
35476063b7 Resolves potential data races. 2020-09-14 21:07:50 -04:00
Thomas Harte
8557bb2136 Adds minor exposition. 2020-09-14 20:39:52 -04:00
Thomas Harte
c0c7818d5d Reintroduces screenshots. 2020-09-14 20:33:05 -04:00
Thomas Harte
ceeadd6a33 Edges up towards reimplementing screenshots. 2020-09-13 22:30:17 -04:00
Thomas Harte
1a2545fdea Excises dangling references to OpenGLView, reinstates display link. 2020-09-13 22:11:51 -04:00
Thomas Harte
c5e9a74c88 Uses DisplayMetrics to disable supersampling when too slow. 2020-09-13 21:07:59 -04:00
Thomas Harte
d7972a7b86 Enforces across-the-board supersampling.
I'm damned if I can figure out how to talk an MTKView, or Metal in general, into supersampling so as a first effort this does it in software.
2020-09-13 19:30:26 -04:00
Thomas Harte
7dd4c67304 Corrects access to data_type_size, adds sanity check on output area return. 2020-09-13 18:59:27 -04:00
Thomas Harte
e113780fd1 Minor: ensures no possibility of a dangling(-ish) pointer within the Mac video. 2020-09-10 22:13:19 -04:00
Thomas Harte
e32ae6c191 Adds UGLY HACKs to workaround uncovered issues in the OpenGL scan target. 2020-09-10 22:10:24 -04:00
Thomas Harte
bcaceff378 Simplifies in-Metal transform logic, loading responsibility for setup onto the CPU.
I think I've also finally excised whatever order-of-operations issue I was having with regard to non-4:3 displays.
2020-09-10 20:32:58 -04:00
Thomas Harte
d7b405c6f8 Ensures direct luminance -> 'RGB' video doesn't go down the composition pipeline. 2020-09-10 13:20:40 -04:00
Thomas Harte
edf8cf4dc6 Completes the set of with/without gamma, and ensures correct alpha selection.
Also culls some other repetitive TODOs.
2020-09-09 19:28:38 -04:00
Thomas Harte
dfcc8e9822 Switches some of the interpolated fields to half precision. 2020-09-09 18:17:05 -04:00
Thomas Harte
016e96e6f8 Extends usage of half. Possibly towards its conclusion. 2020-09-09 15:10:19 -04:00
Thomas Harte
e7ce03c418 Attempts to ensure initial finalised line texture state.
This was an attempt to remove the vertical line on the left of a composite display. Obviously the cause is not as suspected.
2020-09-09 13:15:21 -04:00
Thomas Harte
3d392dd81d Completes conversion of composite & S-Video per-pixel processing to 16-bit floats. 2020-09-09 13:02:04 -04:00
Thomas Harte
42d810db7f Switches per-pixel uniforms to halfs. 2020-09-09 10:53:09 -04:00
Thomas Harte
18571e8351 Also calculates a chroma kernel size, though it isn't used for anything yet. 2020-09-08 20:08:56 -04:00
Thomas Harte
dda1649ab7 Introduces smaller luma kernel functions where useable. 2020-09-08 19:55:37 -04:00
Thomas Harte
c82e0df071 Starts a transition towards half-precision arithmetic. 2020-09-08 19:37:36 -04:00
Thomas Harte
06b7ea5a6e Strips the luma kernel back to 1d. 2020-09-08 19:15:19 -04:00
Thomas Harte
c49fcb9ec9 Based on further play: one box filter to separate luma/chroma, another to filter chroma, plus a FIR sharpen on luma. 2020-09-08 16:35:05 -04:00
Thomas Harte
0e44d6d214 Experiments with an all-box filter. 2020-09-08 16:19:08 -04:00
Thomas Harte
6adad7fbf5 Starts experimenting again with box filters. 2020-09-07 22:47:49 -04:00
Thomas Harte
de6ed7b615 Corrects phase-linked luminance support. 2020-09-07 20:53:28 -04:00
Thomas Harte
07dcb4dbb1 Starts reintroducing brightness, gamma and transparency for composite and S-Video pipelines. 2020-09-07 18:19:13 -04:00
Thomas Harte
e99896eadc At least nominates alpha, gamma and brightness to metal. 2020-09-04 16:07:58 -04:00
Thomas Harte
489701afcb Fixes window resizing. 2020-09-03 21:28:39 -04:00
Thomas Harte
55e576cc57 Ensures unpainted areas in composite displays have a non-asymptotic effect on luminance calculations. 2020-09-03 21:10:30 -04:00
Thomas Harte
6bd8ec9545 Alas, 1.0 seems to be the limit for proper artefact colour. 2020-09-03 20:53:45 -04:00
Thomas Harte
5cd8d86eef Switches to dynamic generation of the 'sharpness' filter, correcting issues with the Apple II (amongst others). 2020-09-03 20:48:44 -04:00
Thomas Harte
74d0acdaec Fixes non-RGB colour composite generation.
The hard-coded sharpen filter proves to be a really bad fit for the Apple II though.
2020-09-03 19:04:17 -04:00
Thomas Harte
0288a1974b Tries: separate filters for chroma and luma, plus a post-separation sharpen filter on the latter. 2020-09-03 13:18:21 -04:00
Thomas Harte
6efd8782fe Tweaks coefficients some more; makes sure that data is never larger than the intermediate buffers. 2020-09-02 20:14:41 -04:00
Thomas Harte
8bab9d5d60 Corrects S-Video and composite generation for RGB[1/2/4] sources.
Also toys with a double luminance filter in order to try to sharpen chrominance. But maybe I should be looking at other convolutions entirely?
2020-09-02 19:13:54 -04:00
Thomas Harte
6ef1dfd8be Sets a more realistic colour subcarrier amplitude. 2020-09-02 15:52:05 -04:00
Thomas Harte
7e58648743 Corrects front-running bug, plays further with colour amplitude. 2020-09-02 15:51:48 -04:00
Thomas Harte
0f0c3e616d Tweaks some numbers.
I'm largely treading water here. Probably time to think about the race.
2020-09-02 08:17:01 -04:00
Thomas Harte
c7ce65ea4c Attempts fully to restore composite video.
Subject to some sort of nasty race condition for the time being.
2020-09-02 08:03:10 -04:00
Thomas Harte
c36247b609 Ensures reuse of offset buffers.
There seems to be some sort of epic race condition as the drawing pipeline lags though. Will need to investigate.
2020-09-01 22:11:48 -04:00
Thomas Harte
15296e43a4 Attempts correctly to set up the CPU side of a composite video pipeline, at least.
So: I think this is really close, but I'm out of time for the day.
2020-09-01 21:58:33 -04:00
Thomas Harte
f2929230a2 [Experimentally] introduces blending between computed S-Video fragments. 2020-09-01 21:37:36 -04:00
Thomas Harte
bf252b8061 Fixes sizing of buffers to the current output. 2020-09-01 21:33:54 -04:00
Thomas Harte
9e2bf2af7e Restricts S-Video processing to updated lines. 2020-09-01 21:27:40 -04:00
Thomas Harte
245f2654f0 Shifts S-Video processing into the compute shader. 2020-09-01 20:37:11 -04:00
Thomas Harte
67ca298a72 Forces a no-op compute shader into the S-Video pipeline.
The intention is to restrict the area acted over, and to do the S-Video filtering in there. Then I'll just need two such stages for composite.
2020-09-01 18:39:52 -04:00
Thomas Harte
67d4dbf91a Starts girding for a third pipeline. 2020-08-31 20:01:59 -04:00
Thomas Harte
b344269140 I think I accept the need for intermediate steps now.
This allocates storage.
2020-08-30 20:21:01 -04:00
Thomas Harte
bb547610f2 Adds commentary, shrinks some intermediate texture sizes. 2020-08-30 12:06:29 -04:00
Thomas Harte
1e1f007bb7 Possibly convinces myself that no-separation chroma/luma isn't practical.
... as appealing as it may be, were filters perfect.
2020-08-29 21:25:49 -04:00
Thomas Harte
c40d858f02 Switches back to angular stuff at input resolution; ensures all S-Video modes work.
Now to roll back onto composite. Fingers crossed!
2020-08-29 20:54:46 -04:00
Thomas Harte
3d564d85fd Proves that per-pixel sine/cos evaluation avoids phase issues.
Even in PAL mode. But I'd rather not _require_ this as it kind of negates directly-sampled input.
2020-08-29 18:53:37 -04:00
Thomas Harte
02cea40ffa Attempts to avoid introducing phase error in scanToComposition.
Also brightens S-Video up to RGB levels.
2020-08-25 22:41:37 -04:00
Thomas Harte
e502d336db Having decided that these things probably need to be separate, starts drilling down on S-Video. 2020-08-25 22:05:19 -04:00
Thomas Harte
807cb99f6d Composite angles are signed. 2020-08-23 21:39:04 -04:00
Thomas Harte
8b6879a782 Brief detour: introduces myself to C++11 multiline string literals.
A full cleaning coming soon, I imagine.
2020-08-23 21:18:38 -04:00
Thomas Harte
7ca0362f23 Treads water.
Difficult current question: why does the Atari 2600's display change colours as the display tries to achieve horizontal lock? Phase should be unchanged. Ergo something is amiss.
2020-08-23 21:03:26 -04:00
Thomas Harte
56c7bd242a Marginally tidies. 2020-08-22 16:38:36 -04:00
Thomas Harte
5c6112415a Sets appropriate clear colour for composition render pass. 2020-08-21 22:41:54 -04:00
Thomas Harte
bf6a0c9fc4 Achieves a return of composite colour for RGB-producing machines. 2020-08-21 22:06:36 -04:00
Thomas Harte
d54b937ab6 Starts trying to do actual composite processing. 2020-08-21 21:11:25 -04:00
Thomas Harte
7c23c32e44 Corrects composition colour phase. 2020-08-20 20:45:45 -04:00
Thomas Harte
4e21d24b5f Corrects composition colour amplitude. 2020-08-20 20:34:37 -04:00
Thomas Harte
ad6fb85fda Corrects use of composition buffer.
Something is still very obviously amiss in colour processing somewhere down the line, but the correct forms are once again visibly in evidence.
2020-08-20 20:21:28 -04:00
Thomas Harte
5dc39a5d24 Adds the composition render pass.
Albeit that something here doesn't work at present.
2020-08-19 21:56:53 -04:00
Thomas Harte
3597f687de Continues sidling towards composite & S-Video handling. 2020-08-19 21:20:06 -04:00
Thomas Harte
8811506adf Starts towards building a compound[/composition?] buffer.
I now need to discover whether I can use natural integer texture coordinates.
2020-08-17 22:10:02 -04:00
Thomas Harte
11dec6fc0f Avoids a redundant clear. 2020-08-17 22:09:15 -04:00
Thomas Harte
59c4c8233f Generalises existing scanToDisplay to add lineToDisplay. 2020-08-17 21:15:19 -04:00
Thomas Harte
9da79d2d81 Clarifies scaling logic. 2020-08-17 20:29:46 -04:00
Thomas Harte
246b474a25 Removes ONE_BIG_LOCK, having effectively neutered it anyway.
Starts work on more explicit API usage validation. Maybe the issue isn't a race condition?
2020-08-16 22:09:25 -04:00
Thomas Harte
27e8a3a1b5 Obeys modals' zoom.
Subject to an attempt at factoring aspect ratio differences.
2020-08-16 21:11:43 -04:00
Thomas Harte
745797b596 Introduces a stencil buffer plus the inter-frame clearing it allows. 2020-08-16 16:42:32 -04:00
Thomas Harte
940e9e037e Adds first_scan to LineMetadata.
Also reorders `Line` fields to match `Scan` fields, just for visual consistency.
2020-08-16 08:59:37 -04:00
Thomas Harte
512c0079a9 Makes thread safe. 2020-08-15 21:52:55 -04:00
Thomas Harte
645c29f853 Adds an intermediate buffer to correct inter-frame smoothing.
Also goes someway back to the old scan output scheduling, albeit presently with limited thread safety.
2020-08-15 21:24:10 -04:00
Thomas Harte
e55945674d Reduces main thread blocking. 2020-08-14 22:16:49 -04:00
Thomas Harte
7ac88536dd Respects machine aspect ratio.
To an extent. Doesn't currently deal with cropping of machines when the window aspect ratio is smaller.
2020-08-14 21:24:25 -04:00
Thomas Harte
230b9fc9e6 Permits multiple simultaneous scan reading ranges.
Also updates the OpenGL scan target as per the latest movements of things.
2020-08-12 22:08:41 -04:00
Thomas Harte
27ca782cac Enables blending; attempts to enable frame preservation.
The latter seems to be evidencing a double buffer at play. More investigation required.

On the plus side, the direct route is still well within GPU budget at 4k on my Core M. So a huge improvement there.
2020-08-12 19:34:07 -04:00
Thomas Harte
a136a00a2f Takes a shot at adding RGB -> S-Video and composite conversion, for all RGB types. 2020-08-11 22:11:50 -04:00
Thomas Harte
637ec35d6a Adds getters for standard colour-space conversion matrices.
These are just more details on the meaning of the colour spaces, so I think they belong here.
2020-08-11 19:58:57 -04:00
Thomas Harte
4b55df1cb4 Starts upon a macro-oriented means of RGB input function generation. 2020-08-10 22:03:39 -04:00
Thomas Harte
b9309268ba Possibly finally succeeds at moving Accelerate.framework to where it should be. 2020-08-10 21:46:11 -04:00
Thomas Harte
8fa89baf54 Slightly cleans up Xcode project; reenables kiosk-for-Mac builds. 2020-08-10 21:43:32 -04:00
Thomas Harte
8374a5e579 Adds superficially correct compositeSampleLuminance8Phase8 function.
Thereby uncovering a minor error in my decoding of colour phase.
2020-08-10 21:33:59 -04:00
Thomas Harte
525233e10b Ensures all input data types are parseable in Metal.
Though now I need to think a bit more about the best way to compose signal-type conversions, and whether output-type calculations (i.e. gamma, brightness) are applied.
2020-08-10 19:47:47 -04:00
Thomas Harte
eadda6a967 Further strips OpenGL from the macOS target. 2020-08-09 22:17:27 -04:00
Thomas Harte
3d6590af89 Throws out a little more OpenGL. 2020-08-09 22:11:31 -04:00
Thomas Harte
28d933d5d6 Does just enough to get 8-bit RGB and 1-bit luminance machines to display.
Assuming an 'RGB' output.
2020-08-09 21:19:07 -04:00
Thomas Harte
c1dc42a094 Add comment on latent design aim.
In the hope that I don't forget why I did this.
2020-08-09 21:18:23 -04:00
Thomas Harte
6384ff3ee7 Add fix for data_type_size_ for owners that don't change texture pointer upon new modals. 2020-08-09 21:17:51 -04:00
Thomas Harte
a118594c8b Hacks to make RGB1 visible (in a fashion). 2020-08-09 20:45:51 -04:00
Thomas Harte
93c6105442 Corrects calculation of dirty texture area. 2020-08-09 20:45:14 -04:00
Thomas Harte
ced4a75a1a Adds note on the buffering scan target's minor adaptation of data_offset. 2020-08-09 20:44:46 -04:00
Thomas Harte
57fecdc09e Ties everything together in an attempt to display RGB scans.
I'm actually just getting a mess of pixels, but it's something!
2020-08-09 18:41:15 -04:00
Thomas Harte
cd491bb6e0 Cleans up project file; macOS 10.13 is definitely the deployment target. 2020-08-09 18:27:57 -04:00
Thomas Harte
f16ad8f71d Takes a shot at submitting texture changes. 2020-08-09 17:59:52 -04:00
Thomas Harte
e340685a99 Seemingly proves that proper geometry is reaching Metal by drawing scans.
No in-buffer accumulation yet, but this is progress. If I can add accumulation and stencil clearing, I'm not doing badly.
2020-08-08 23:11:44 -04:00
Thomas Harte
df89a8771c Makes an attempt to have the emulator fill the actual GPU buffers.
Not that they're drawn from correctly yet. I might first take a run at a new quick-path output route for emulated RGB displays, that just seeks to use the scans directly. No intermediate buffers. Besides probably being a good feature, it'll be a good way to ramp further up with Metal.
2020-08-08 22:49:02 -04:00
Thomas Harte
bdcf266e45 Having learnt a bit more: eliminates Metal attribute tags, switches to more natural expression of structs.
Also thereby eliminates the need for a forced alignas(4) on various structs.
2020-08-08 17:27:32 -04:00
Thomas Harte
edf41b06fd Eliminates the quad buffer.
Vertices can be adduced from vertex ID.
2020-08-08 17:12:49 -04:00
Thomas Harte
38960a08d6 Adds adjustment for display aspect ratio.
While also realising that I appear to be getting away without an MTLVertexDescriptor for Scans. Maybe OpenGL has prejudiced me, and they're actually optional for interleaved data?
2020-08-07 22:29:24 -04:00
Thomas Harte
fbda7aab23 Does just enough to get the correct (aspect ratio aside) output of scan outlines.
So, up next, can I start streaming these things?
2020-08-07 22:20:01 -04:00
Thomas Harte
c575aa0640 Adds a buffer for scans, and posts two test instances. 2020-08-07 22:03:54 -04:00
Thomas Harte
583f6b1ba2 Modifies BufferingScanTarget to allow has-a relationship.
I might switch fully to has-a. Further consideration required.
2020-08-07 22:03:27 -04:00
Thomas Harte
bb55ecc101 Disables --volume for kiosk mode testing. 2020-08-07 21:19:53 -04:00
Thomas Harte
4421acef34 Gets some uniforms in on the action.
With some effort towards scans, but incompletely so.
2020-08-07 21:19:17 -04:00
Thomas Harte
4c9418f59a Guarantees alignof(4) on all GPU-bound structures.
Taken as given: Metal's requirement here is reasonable enough that it'll either be the same as other frameworks, or at least possibly help them down a fast path.
2020-08-07 21:18:08 -04:00
Thomas Harte
219923bd63 Reduces vertex size, draws a quad. 2020-08-05 21:33:25 -04:00
Thomas Harte
7551782a25 Switches to interleaved vertex data.
This more closely relates to what I actually want to do.
2020-08-05 17:27:43 -04:00
Thomas Harte
5c836604c0 Reenable MaserSystem code.
Accidental/poor branch management is evidenced here.
2020-08-04 21:50:54 -04:00
Thomas Harte
eff24a8726 My first baby steps in Metal continue; here's a triangle. 2020-08-04 21:49:01 -04:00
Thomas Harte
72df6e52cd This is possibly at least dispatching an empty command buffer correctly. 2020-08-04 19:44:56 -04:00
Thomas Harte
e235a45abb Breaks all output.
... by switching out NSOpenGLView for MKLView with no drawing infrastructure yet in place.
2020-08-04 18:22:14 -04:00
Thomas Harte
d20c11e401 Merge pull request #831 from TomHarte/MultiKeyboard
Ensures that the MultiKeyboard functions.
2020-07-31 22:08:08 -04:00
Thomas Harte
693b889fdd Ensures that the MultiKeyboard functions. 2020-07-31 21:48:20 -04:00
Thomas Harte
671f48dc10 Merge pull request #830 from TomHarte/MSXCrash
Restores audio to multimachines
2020-07-31 18:31:23 -04:00
Thomas Harte
7b1708f0bc Gets explicit that the delegate_ doesn't need a memory barrier. 2020-07-31 18:21:47 -04:00
Thomas Harte
f34a9b4346 Corrects audio output from the multi-speaker.
Specifically: local duplication of the delegate is unnecessary, and leads to confusion.
2020-07-31 18:18:19 -04:00
Thomas Harte
1e6d03246b Merge pull request #829 from TomHarte/MSXCrash
Ensures proper handover of speaker state when picking in a multimachine.
2020-07-30 23:04:55 -04:00
Thomas Harte
cdde57fcf2 Remove unused code. 2020-07-30 23:02:01 -04:00
Thomas Harte
c0a61ac1ee Ensures proper handover of speaker state when picking in a multimachine. 2020-07-30 22:50:32 -04:00
Thomas Harte
9c97c0a906 Merge pull request #828 from TomHarte/LockFreeQueue
Completes LockFreeQueue branch.
2020-07-30 21:46:56 -04:00
Thomas Harte
8cacab196d Merge branch 'master' into LockFreeQueue 2020-07-30 21:43:25 -04:00
Thomas Harte
b14bedbe29 Merge pull request #817 from TomHarte/LockFreeQueue
Fully splits buffering from drawing for the existing OpenGL scan target.
2020-07-30 21:42:20 -04:00
Thomas Harte
6bc66d8b96 Tidies, ensures ::will_change_owner acquires the producer mutex. 2020-07-29 23:18:03 -04:00
Thomas Harte
23f381f381 Fixes frame_is_complete_, gets rid of active_line_, explains ONE_BIG_LOCK in set_write_area. 2020-07-29 23:03:38 -04:00
Thomas Harte
51ad423eca Resolves off-by-one error in line writing, adds diagnostic one-big-lock option. 2020-07-29 22:45:13 -04:00
Thomas Harte
72a8fef989 Switches to much more straightforward Line/LineMetadata storage.
Spoiler: covering this whole segment behind producer_mutex_ seems to resolve all output issues, so clearly the existing logic isn't functioning correctly. Making it simpler seems like a pretty obvious way to get to the bottom of that.
2020-07-29 21:49:17 -04:00
Thomas Harte
02f41ee513 This has become the general producer mutex, might as well name it as such. 2020-07-29 21:34:07 -04:00
Thomas Harte
9410594486 Merge branch 'master' into LockFreeQueue 2020-07-29 21:22:19 -04:00
Thomas Harte
1c6223cc11 Merge pull request #825 from TomHarte/Microdisc
Gives Qt disk controllers independent ROM/RAM selection logic.
2020-07-29 21:21:29 -04:00
Thomas Harte
82d6a5387f Gives Qt disk controllers independent ROM/RAM selection logic.
In particular, this fixes the Microdisc.
2020-07-29 21:06:41 -04:00
Thomas Harte
5165e65021 Reduces scan_buffer_ to a saner size.
Albeit still probably overspecified.
2020-07-28 22:36:57 -04:00
Thomas Harte
1942742d73 Resolves thread data race on Macintosh audio output. 2020-07-28 22:21:52 -04:00
Thomas Harte
b7760bb052 Reorders code, gets explicit about memory ordering. 2020-07-28 22:02:22 -04:00
Thomas Harte
2470055d90 Hides the modals. 2020-07-27 23:33:39 -04:00
Thomas Harte
62be2a2eec Merge branch 'master' into LockFreeQueue 2020-07-27 23:18:45 -04:00
Thomas Harte
b1e062945e Merge pull request #821 from TomHarte/QtThreading
Qt issue mega ticket
2020-07-27 21:19:12 -04:00
Thomas Harte
3db4a8c312 Attempts to improve vsync deadline estimation.
Also increases probability of bad estimation being discarded.
2020-07-27 21:08:00 -04:00
Thomas Harte
db8e1b0edf Adds feedback on unidentified ROMs. 2020-07-27 20:45:47 -04:00
Thomas Harte
71c3f58c99 Provides user feedback upon improper command-line usage. 2020-07-27 20:40:38 -04:00
Thomas Harte
7c05b1788e Ensures proper thread confinement for updateStatusBarText. 2020-07-27 20:25:52 -04:00
Thomas Harte
77c5b86acc Moves ownership of the scan and line buffers out of the BufferingScanTarget. 2020-07-26 22:46:03 -04:00
Thomas Harte
bc6426313e Localises three of the four macros. 2020-07-26 17:54:33 -04:00
Thomas Harte
8bef7ff4c5 Makes all three PointerSets and is_updating_ private. 2020-07-26 17:27:19 -04:00
Thomas Harte
a2db6ddea5 Add link to Snap releases. 2020-07-25 23:00:29 -04:00
Thomas Harte
f9f500c194 Merge branch 'master' into LockFreeQueue 2020-07-24 22:29:45 -04:00
Thomas Harte
6ad1e3e17e Merge pull request #819 from TomHarte/Warnings
Resolves GCC 7 warnings.
2020-07-24 22:17:50 -04:00
Thomas Harte
e097a841d2 Adds a c++1z fallback for SDL builds, too. 2020-07-24 22:01:22 -04:00
Thomas Harte
fa95a17af5 Resolves receive_bit_count-unused warnings. 2020-07-24 21:59:27 -04:00
Thomas Harte
b961665985 Ensures WOZ2 behaviour even if type_ has an invalid value.
This pleases GCC 7.
2020-07-24 21:56:20 -04:00
Thomas Harte
8af35bc6bb Resolves signed comparison mismatches. 2020-07-24 21:55:33 -04:00
Thomas Harte
9b75287a52 Merge pull request #818 from TomHarte/QtC++1z
Add c++1z as a config option, for older versions of Qt.
2020-07-24 16:34:04 -04:00
Thomas Harte
84d5316aa7 Add c++1z as a config option, for older versions of Qt. 2020-07-24 16:32:59 -04:00
Thomas Harte
89acb70091 Slightly reorganise. 2020-07-24 16:20:20 -04:00
Thomas Harte
66165a6dea Add missing include files. 2020-07-23 23:24:24 -04:00
Thomas Harte
84dcf9925b Updates Scons and Qt projects to include new files. 2020-07-23 23:14:10 -04:00
Thomas Harte
ee1d7eb61f Makes more buffer-specific stuff private. 2020-07-23 23:06:14 -04:00
Thomas Harte
e260f92988 Privatises write_pointers_mutex_ and write_pointers_.
Also gives subclasses control over write-area texture space allocation.
2020-07-23 22:54:40 -04:00
Thomas Harte
74788ccf8e Pulls the BufferingScanTarget into a separate file. 2020-07-22 22:16:47 -04:00
Thomas Harte
0da5c07942 Starts splitting ring-buffer stuff from OpenGL stuff.
Initially via two very codependent classes.
2020-07-21 22:49:46 -04:00
Thomas Harte
e8cd5a0511 Merge pull request #816 from TomHarte/RelaxedTracks
Corrects a regression in disk image handling; liberalises Disk II analyser
2020-07-20 19:53:34 -04:00
Thomas Harte
5ebbab6f35 Further relax Apple GCR static analysis requirements. 2020-07-20 19:50:33 -04:00
Thomas Harte
84dd194afd Corrects test for ::tracks_differ. 2020-07-20 19:48:20 -04:00
Thomas Harte
e1ad1a4cb6 Update Qt status. 2020-07-20 09:17:43 -04:00
Thomas Harte
9de43dac95 Merge pull request #815 from TomHarte/WOZ2
Support WOZ 2 disk images
2020-07-19 23:22:55 -04:00
Thomas Harte
47f121ee4c Mark WOZs as read-only, with exposition as to why. 2020-07-19 00:08:49 -04:00
Thomas Harte
d8b699c869 Corrects index pulse signalling. 2020-07-19 00:06:27 -04:00
Thomas Harte
a7855e8c98 Ensure float literals are floats. 2020-07-17 23:18:41 -04:00
Thomas Harte
8dcb48254a Simplifies calculations very slightly. 2020-07-17 23:18:08 -04:00
Thomas Harte
f6b7467d75 Implement custom tracks_differ; support WOZ 2 3.5" drive geometry properly. 2020-07-17 22:09:55 -04:00
Thomas Harte
9d1d162cc8 Add an ability to avoid track flushing when file formats have sub-track precision. 2020-07-17 22:09:21 -04:00
Thomas Harte
4ee29b3266 Switches disk seeking logic fully to floating point. 2020-07-17 22:08:58 -04:00
Thomas Harte
cbb0594e6b Use 16-sector state machine even with the 13-sector boot ROM.
I think I've proven that the Disk II doesn't decode the 13-sector state machine correctly. Work to do there.
2020-07-16 23:27:27 -04:00
Thomas Harte
8aeebdbc99 Remove redundant comment. 2020-07-16 23:26:45 -04:00
Thomas Harte
c7ef258494 Ensures that five-and-three sectors pass static analysis. 2020-07-16 21:44:14 -04:00
Thomas Harte
4fec7c82ab Very minor grammar improvement. 2020-07-16 21:43:03 -04:00
Thomas Harte
9a952c889f Fixes exit from random gain noise. 2020-07-15 22:44:54 -04:00
Thomas Harte
8da7806ee9 Liberalises segment parser not necessarily to require a standard epilogue.
It seems that real disks don't always have them; I guess the boot ROM doesn't require one.
2020-07-15 22:27:04 -04:00
Thomas Harte
aed61f6251 Implements latest advocated MC3470 behaviour. 2020-07-15 19:34:05 -04:00
Thomas Harte
d065d6d98f Adds read-only WOZ 2 support. 2020-07-15 19:15:03 -04:00
Thomas Harte
ab20a23f2b Merge pull request #814 from TomHarte/ZX81Autosrun
Ensures the ZX80 and ZX81 automatically RUN software that doesn't do that itself
2020-07-14 23:53:17 -04:00
Thomas Harte
e1c57b6fbe Further ensures both ZX80 and ZX81 functionality. 2020-07-14 23:45:51 -04:00
Thomas Harte
371c26251c Switches strategy for the ZX80. 2020-07-14 22:36:04 -04:00
Thomas Harte
645d198bee Causes ZX80 and ZX81 software that doesn't already autorun to do so. 2020-07-14 22:17:56 -04:00
Thomas Harte
ab1a999df4 Merge pull request #812 from TomHarte/QtLoadAtLaunch
Qt: Attempt to load a file if passed on the command line.
2020-07-13 22:00:11 -04:00
Thomas Harte
42f2bf05bf Attempt to load a file if passed on the command line. 2020-07-13 21:51:55 -04:00
Thomas Harte
5e55d3d7c7 Merge pull request #660 from TomHarte/FurtherSCC
IN PROGRESS. More fully implements the Macintosh's SCC.
2020-07-12 00:05:59 -04:00
Thomas Harte
1288369865 Merge branch 'master' into FurtherSCC 2020-07-11 23:54:40 -04:00
Thomas Harte
d86b0d4213 Merge pull request #811 from TomHarte/QtPaths
Ensures qmake paths are explicit.
2020-07-11 17:47:29 -04:00
Thomas Harte
d91cf598be Ensures qmake paths are explicit. 2020-07-11 17:46:32 -04:00
Thomas Harte
9be56aa4a2 Merge pull request #809 from TomHarte/QtActivity
Exposes activity lights in Qt.
2020-07-10 23:32:38 -04:00
Thomas Harte
86737454a0 Exposes activity lights in Qt.
As a status bar, which is a bit of a quick fix, but it's better than not displaying this information.
2020-07-10 23:18:38 -04:00
Thomas Harte
f0c0caf800 Merge pull request #807 from TomHarte/QtSDL
Works around Qt's keyboard limitations, under X11 at least
2020-07-10 22:36:27 -04:00
Thomas Harte
223a960a06 Implements standard keyboard -> joystick mapping. 2020-07-10 22:30:43 -04:00
Thomas Harte
f72570386c Installs and removes an 'Input' menu where required.
Also ensures safe shutdown of a second machine.
2020-07-09 23:47:38 -04:00
Thomas Harte
56e5491e5c Ensures safe startup. 2020-07-09 23:06:32 -04:00
Thomas Harte
be1c3e9136 Ensures key state is cleared upon activation changes. 2020-07-08 21:31:29 -04:00
Thomas Harte
2d223305eb Correct subject of switch. 2020-07-08 00:49:29 -04:00
Thomas Harte
48c2dcf50e Introduce provisional X11 bindings. 2020-07-08 00:46:29 -04:00
Thomas Harte
fa26c82273 Undoes extra dependency, checks for X11 at runtime. 2020-07-08 00:15:44 -04:00
Thomas Harte
0763ae38dd Attempts to add conditional include for non-Mac UNIX only. 2020-07-07 23:57:32 -04:00
Thomas Harte
2230ac6c38 Merge pull request #800 from TomHarte/QtProject
UNREADY. Adds a provisional Qt target.
2020-07-06 22:33:39 -04:00
Thomas Harte
abe1e7f244 Provide current thoughts on Qt and the keyboard. 2020-07-06 22:27:50 -04:00
Thomas Harte
24b03f733f Switches to an image more distinct from the existing.
i.e. less heavy on blue and green.
2020-07-04 22:03:08 -04:00
Thomas Harte
5a729f92c1 Attempts to move the 'Help' menu to the correct place. 2020-07-04 19:19:41 -04:00
Thomas Harte
366793498a Ensures ScanTargetWidget doesn't eat irrelevant keypresses. 2020-07-04 19:12:34 -04:00
Thomas Harte
cdda3f74ab Attempts mouse event capture. 2020-07-04 00:29:37 -04:00
Thomas Harte
51f4242e9b Reapplies image optimisation, saving a few bytes. 2020-07-04 00:29:10 -04:00
Thomas Harte
d97ebae200 Request Qt deprecation warnings. 2020-07-03 23:16:49 -04:00
Thomas Harte
daa195a7fb Add Qt to the main README. 2020-07-03 23:16:35 -04:00
Thomas Harte
2d5e9bf1bb Declines to set up audio output if none is available. 2020-07-02 22:58:15 -04:00
Thomas Harte
402f2ddbd9 Increases likelihood of 68000 Program offset-size assumptions being met. 2020-07-02 22:24:04 -04:00
Thomas Harte
8bf5ed52ea Ensures keyboard events are restricted to single windows. 2020-07-02 22:03:12 -04:00
Thomas Harte
b850183a1e Switches to an alternative to window(), for older Qt support. 2020-07-02 21:53:30 -04:00
Thomas Harte
f7e13356c4 FunctionThreads no longer automatically start.
Improvements as a result: audio works in a second machine started in an existing window; there is no audio thread footprint if there is no audio.
2020-07-01 18:55:42 -04:00
Thomas Harte
55cc3089f9 Ensures complete deallocation of the QAudioOutput. 2020-06-30 23:33:41 -04:00
Thomas Harte
a096a09c72 Trusts Qt to supply a refresh rate, and handles retina <-> non-retina window transitions. 2020-06-30 23:03:39 -04:00
Thomas Harte
365e1f2e85 Merge pull request #803 from Margen67/actions
ccpp.yml: Changes
2020-06-29 15:26:17 -04:00
Thomas Harte
b9e117cdcf Centralises window title responsibility. 2020-06-28 23:08:40 -04:00
Margen67
fc3a03a856 ccpp.yml: Changes
Build all branches.
Delete whitespace.
Use checkout v2.
Change ; to &&.
Use working-directory instead of cd.
Add -j to speed up build.
2020-06-28 19:53:19 -07:00
Thomas Harte
f6e5a2fb04 Resolves duplicative enums. 2020-06-28 22:50:24 -04:00
Thomas Harte
404c35feb5 Implements Atari 2600 switches menu. 2020-06-28 17:57:20 -04:00
Thomas Harte
b5962c58bb Completes ZX80/81-specific menu. 2020-06-28 16:23:35 -04:00
Thomas Harte
74da762ae1 Starts sketching out the ZX80/81 menu items. 2020-06-28 01:04:32 -04:00
Thomas Harte
d87c840b76 Adds quick load and quick boot options.
This should leave only the ZX80/81 and 2600 as special cases.
2020-06-27 17:08:29 -04:00
Thomas Harte
afb835398f Ensures display selection is preserved in the app settings. 2020-06-27 16:26:39 -04:00
Thomas Harte
c982c78285 Ensures Reflection::set is widely available. 2020-06-27 16:26:01 -04:00
Thomas Harte
b4cdf8d987 Separates runtime TypeInfo into its own header. 2020-06-27 15:53:06 -04:00
Thomas Harte
6925a04088 Ensures 'Display' menu is removed if machine is closed. 2020-06-26 23:27:14 -04:00
Thomas Harte
a0e534b309 Starts towards offering display-type selection. 2020-06-26 23:04:45 -04:00
Thomas Harte
74d1ca4fa8 Simplifies indentation, correcting flow while there. 2020-06-26 21:16:15 -04:00
Thomas Harte
3c896050fb Ensures proper output sizeing on HiDPI displays. 2020-06-26 21:14:43 -04:00
Thomas Harte
387500f01a Implements 'Insert...' menu item. 2020-06-26 18:25:56 -04:00
Thomas Harte
21c41ed4cb Reduces boilerplate and key repetition. 2020-06-26 00:39:30 -04:00
Thomas Harte
293ab25634 Ensures complete machine picker state is stored. 2020-06-26 00:23:52 -04:00
Thomas Harte
3ddc1a1722 Eliminates hard-coded concept of timer jitter. 2020-06-25 23:59:44 -04:00
Thomas Harte
478d081095 Ensures machines take user-friendly default settings. 2020-06-23 23:27:56 -04:00
Thomas Harte
9d4b49bbb5 Attempts to be more rigorous in vsync prediction. 2020-06-23 22:59:12 -04:00
Thomas Harte
4417f81014 Attempts to set a meaningful window title. 2020-06-22 22:58:58 -04:00
Thomas Harte
b96f7711e3 Corrects attempt at back-to-UI final window SDI behaviour.
Maybe it'll turn out to be not what I want, but at least now it works.
2020-06-22 22:36:36 -04:00
Thomas Harte
1875a03757 Plugs a per-window memory leak.
While also ensuring proper OpenGL resource destruction.
2020-06-22 20:32:44 -04:00
Thomas Harte
13336b8ad5 Consolidates and disables failed attempt at final-window close behaviour. 2020-06-21 23:52:41 -04:00
Thomas Harte
b17cceaeaf Tidies up and makes a failing attempt at SDI improvements. 2020-06-21 23:50:18 -04:00
Thomas Harte
782a62585e Preserves open path between launches. 2020-06-21 19:10:06 -04:00
Thomas Harte
c5d8d9127b Rejigs ScanTarget relationship from pull to push, so it can be set whenever it is safe. 2020-06-21 18:25:38 -04:00
Thomas Harte
336dffefe0 Ensures changes in the framebuffer are passed onward. 2020-06-21 17:25:21 -04:00
Thomas Harte
e297d4cced Decouples scan target drawing and lifetime. 2020-06-21 17:20:44 -04:00
Thomas Harte
b052ca5ca2 Switch to Qt-style member naming. 2020-06-21 17:16:11 -04:00
Thomas Harte
68d4d7d10a Ensures no out-of-bounds access for unlabelled keys. 2020-06-21 17:11:24 -04:00
Thomas Harte
a03211c410 Makes an attempt at the single document interface. 2020-06-21 12:30:18 -04:00
Thomas Harte
c953ab09db Ensures overloaded assignments work. 2020-06-20 00:17:16 -04:00
Thomas Harte
68645742f7 This is a deliberate fallthrough. 2020-06-20 00:12:08 -04:00
Thomas Harte
1fbb733f7f Expands upon comment. 2020-06-20 00:05:41 -04:00
Thomas Harte
6e4b8d58a5 Completes [[fallthrough]]s. 2020-06-19 23:50:37 -04:00
Thomas Harte
7af8646470 Allowing for the constexpr, this is maybe_unused. 2020-06-19 23:47:43 -04:00
Thomas Harte
945a9da94f Adds further [[fallthrough]]s. 2020-06-19 23:44:20 -04:00
Thomas Harte
2477752fa4 Adds further [[fallthrough]] attributes. 2020-06-19 23:36:51 -04:00
Thomas Harte
240d3c482b Removes redundant constructors. 2020-06-19 23:26:22 -04:00
Thomas Harte
91229a1dbd Adds overt fallthrough attributes. 2020-06-19 23:22:29 -04:00
Thomas Harte
4f9b3259d5 Adds explicit conversions to qint64. 2020-06-19 23:12:18 -04:00
Thomas Harte
3cb1072c29 Adds an explicit [[fallthrough]] tag. 2020-06-19 23:10:25 -04:00
Thomas Harte
12ee8e4db4 Ensures audio is not being pumped while the AudioBuffer is being destructed. 2020-06-19 23:09:39 -04:00
Thomas Harte
95e98323c5 Adds missing header for lock_guard and mutex. 2020-06-19 23:09:20 -04:00
Thomas Harte
7431d56166 Ensures is_in_retrace is properly initialised. 2020-06-19 23:09:02 -04:00
Thomas Harte
222c16c5b8 Ensures newly-hidden widgets aren't still in focus. 2020-06-19 22:01:53 -04:00
Thomas Harte
4e83e80962 Goes further in ensuring safe shutdown.
Especially if no machine has been started.
2020-06-19 20:17:47 -04:00
Thomas Harte
4fdbe578cc Wires up all new machine options. 2020-06-18 23:34:37 -04:00
Thomas Harte
c5cad865d7 Tidies up: arranges initialisers into alphabetical order, shortens some enum references. 2020-06-18 23:34:28 -04:00
Thomas Harte
ae5fe9225f Fills in all machine options. 2020-06-18 22:24:45 -04:00
Thomas Harte
327b9051c8 Adds necessary layouts for Apple II type selection. 2020-06-18 20:30:50 -04:00
Thomas Harte
8151c24cf5 Starts the machine-picker side of the interface. 2020-06-18 20:05:46 -04:00
Thomas Harte
ee659095c2 Retains the default window background colour until a machine is running. 2020-06-17 23:16:29 -04:00
Thomas Harte
8c35fe1062 Finally succeeds at making the missingROMsBox resize with the window. 2020-06-17 22:22:15 -04:00
Thomas Harte
9ca6a1031c Adds an 'about' box and a hypothetical 'New' file option. 2020-06-16 23:15:47 -04:00
Thomas Harte
59458f6444 Resolves errant spaces. 2020-06-16 23:15:38 -04:00
Thomas Harte
e8939aada4 Now that this spin blocks at startup, I can use a standard atomic_flag. 2020-06-16 23:12:58 -04:00
Thomas Harte
17bb3dce26 Makes a firmer attempt at enforcing safe shutdown. 2020-06-16 22:33:50 -04:00
Thomas Harte
495024d6fe Cleans up all redundant lock/unique_guard declarations. 2020-06-15 00:24:10 -04:00
Thomas Harte
902b33d25d Makes more failing attempts at a clean shutdown. 2020-06-15 00:00:44 -04:00
Thomas Harte
ac732e2e7b Attempts to ensure clean shutdown. 2020-06-14 23:38:44 -04:00
Thomas Harte
d08ffd6c8b Makes sure the timer really, really is on a different thread.
Thereby allows me substantially to reduce audio latency.
2020-06-14 23:22:00 -04:00
Thomas Harte
530ff7471d Adds a virtual destructor, given how these things might be held. 2020-06-14 21:14:51 -04:00
Thomas Harte
79833deeaf With some attempt at vsync prediction, seeks to smooth audio/video output.
There's plenty more work to do here, but hopefully it takes the issue immediately off the table.
2020-06-14 19:26:56 -04:00
Thomas Harte
405e9e7c68 Shunts audio into its own QThread.
For the record, this was the first means I found of attempting that which actually seemed to work. A plain QThread, with something `connect`ed to its `started` signal didn't seem to work (perhaps `connect` is smart at thread confinement?), `moveToThread` didn't work on the audio output after the fact, etc.
2020-06-10 22:14:54 -04:00
Thomas Harte
5f13ee7c19 Simplifies AudioBuffer by consolidating logic into writes.
This is kind of fiddling in the margins though; I'm having a lot of difficulty determining the semantically-correct way to get Qt not to funnel all activity through a single thread.
2020-06-09 23:56:08 -04:00
Thomas Harte
d9f02aecdf Adds an additional buffer. To reduce latency. No, really.
Specifically: there's no way to guarantee no overbuffering due to the startup race, other than having QAudioOutput obtain data by pull rather than push. But if it's pulling then that implies an extra buffer. And since the sizes it may pull are not explicit, there's guesswork involved there.

So: no extra buffer => uncontrollable risk of over-buffering. Extra buffer => a controllable risk of over-buffering.
2020-06-09 00:01:22 -04:00
Thomas Harte
bcb23d9a15 Attempt to reduce audio latency. Unsuccessfully. 2020-06-08 21:30:35 -04:00
Thomas Harte
d027450502 Consolidates timer/thread ownership for the timer. 2020-06-07 00:31:46 -04:00
Thomas Harte
63ad1290f4 Actually, QIODevice is listed as reentrant. So no need to forward audio.
That said, latency is still absurd for some reason.
2020-06-06 23:47:57 -04:00
Thomas Harte
7c7cb61d2f Corrects missing audio, at the cost of frame rate.
I'm now spinning on the ability of QAudioOutput to accept additional data.
2020-06-06 22:35:50 -04:00
Thomas Harte
68b165e244 QIODevice isn't guaranteed thread safe, so use of it is now thread confined. 2020-06-06 21:14:04 -04:00
Thomas Harte
fe1b6812f1 Fixes processing cap and attempts full-rate video output.
Audio now seems to be present, though hugely stuttered.
2020-06-06 19:47:35 -04:00
Thomas Harte
378ff39e5e Makes an unsuccessful attempt at producing audio.
On the plus side, it does seem successfully to sniff out an appropriate audio format and to communicate mono/stereo onward.
2020-06-06 19:19:01 -04:00
Thomas Harte
e47cb91223 Pushes rudimentary keyboard input. 2020-06-05 23:06:28 -04:00
Thomas Harte
d62fb16a58 Adds an eventFilter, in order to steal keypresses. 2020-06-05 22:11:17 -04:00
Thomas Harte
235efcb2d4 Attempts to silence asserts, etc, for release builds. 2020-06-04 23:14:51 -04:00
Thomas Harte
a6ada129e8 Adds very low quality, race-condition infested video output. 2020-06-04 22:58:02 -04:00
Thomas Harte
a681576d6c Adds redraw logic.
If you sit around and constantly reeize the window, you can now see that a machine is running.
2020-06-04 22:39:32 -04:00
Thomas Harte
fdc234ed3b Advances to having a selected machine actually run.
Albeit, invisibly.
2020-06-03 23:39:16 -04:00
Thomas Harte
e2ceb77501 Attempts to start updating a started machine.
No real progress on graphics output though.
2020-06-03 00:21:37 -04:00
Thomas Harte
11c28357a1 Implements a basic ROM installation loop.
Albeit that I need to figure out how layouts work to keep that request view at least centred.
2020-06-02 23:35:01 -04:00
Thomas Harte
f215405beb Corrects capitalisation errors. 2020-06-02 23:27:29 -04:00
Thomas Harte
ba2a0600dc Adds a basic Qt ROM fetcher and attempt to create a machine. 2020-06-01 23:14:57 -04:00
Thomas Harte
ab53165b34 Adds note on implementation obstacle. 2020-06-01 22:08:21 -04:00
Thomas Harte
a30723c3d4 Cleans up a little and ensures a safe exit of the timer thread. 2020-05-31 23:58:19 -04:00
Thomas Harte
d64b4fbc26 Adds a Qt timer class. Precision seems to be 'acceptable'. 2020-05-31 23:39:08 -04:00
Thomas Harte
73131735fa Further qmake warning corrections. 2020-05-30 19:31:17 -04:00
Thomas Harte
5e0bea9d1c Adds all header files to the QMake project. 2020-05-30 16:48:52 -04:00
Thomas Harte
48afc54af6 Cuts down unused parameter warnings to just a few that may well indicate implementation errors. 2020-05-30 01:06:43 -04:00
Thomas Harte
d066dd2b44 Resolves further unused parameter warnings.
Also adds warning to Xcode build, for better symmetry with Qt defaults.
2020-05-30 00:58:10 -04:00
Thomas Harte
0bf7de9d43 Advances to actually completing a build.
Many more warnings to iron out, however.
2020-05-30 00:47:43 -04:00
Thomas Harte
267006782f Starts to add Qt target; resolves many build warnings. 2020-05-30 00:37:06 -04:00
Thomas Harte
a9de745e51 Merge pull request #798 from qeeg/master
Fix Windows MSYS2 build (mostly)
2020-05-27 21:52:49 -04:00
Thomas Harte
ca1f3c600d Merge branch 'master' into master 2020-05-27 21:52:39 -04:00
Thomas Harte
743353a0ed Merge pull request #799 from TomHarte/WindowsBuildErrors
Fixes a couple of Windows build errors.
2020-05-27 21:42:06 -04:00
Thomas Harte
f7c10ef9e9 Replaces POSIX stpncpy with ANSI strlen, memcpy and memset. 2020-05-27 21:31:46 -04:00
Thomas Harte
ecb44711d1 Add glext.h. 2020-05-27 21:20:43 -04:00
Melissa Goad
603b747ac5 Fix Windows MSYS2 build (mostly) 2020-05-27 18:09:56 -05:00
Thomas Harte
0f2f776e6a Merge pull request #797 from TomHarte/Serialisation
Adds BSON serialisation and deserialisation for all reflectable structs.
2020-05-26 23:18:41 -04:00
Thomas Harte
1308f119a6 Relocates cassert. 2020-05-26 23:07:26 -04:00
Thomas Harte
51d684820f Attempts to add array support to ::set and BSON deserialisation. 2020-05-26 22:55:55 -04:00
Thomas Harte
023d76a3e7 Permits int truncation, adds double decoder. Arrays still TODO. 2020-05-26 22:20:15 -04:00
Thomas Harte
4d34d9ae2b Implements BSON deserialisation, other than arrays. 2020-05-25 23:39:00 -04:00
Thomas Harte
c83c827484 Adds necessary header for math. 2020-05-24 12:19:20 -04:00
Thomas Harte
b8b880a91d Extends encoding to handle vector<uint8_t>, floats and doubles. 2020-05-24 01:20:48 -04:00
Thomas Harte
bb2f21a22e Encodes enumerated values as strings. 2020-05-23 22:54:43 -04:00
Thomas Harte
b3587d4cde Corrects: logic for int promotion, object sizes, int64_t gets, sizes prefixed to objects. 2020-05-22 23:38:07 -04:00
Thomas Harte
39ffe45f3c Attempts to add support for arrays. 2020-05-22 21:55:12 -04:00
Thomas Harte
d36e592afb Starts towards BSON serialisation for all deflectable structs.
Still to be tackled: arrays, enumerated types should probably be encoded as strings, deserialisation, probably distinguish get and fuzzy_get...
2020-05-22 00:31:40 -04:00
Thomas Harte
74fb697fa6 Merge pull request #796 from TomHarte/MintBuildIssues
Increases const correctness.
2020-05-20 23:52:16 -04:00
Thomas Harte
512a52e88d Increases const correctness, marks some additional constructors as constexpr, switches std::atomic construction style. 2020-05-20 23:34:26 -04:00
Thomas Harte
41fc6c20a0 Merge pull request #794 from TomHarte/68000State
Adds a `State` for the 68000.
2020-05-19 22:33:57 -04:00
Thomas Harte
28881cb391 Implements apply. 2020-05-19 18:27:10 -04:00
Thomas Harte
a16b710d22 Removes <cassert> from Struct.h (which means it's needed in the 68000's State). 2020-05-19 00:06:29 -04:00
Thomas Harte
a3d4c7599b Attempts fully to capture 68000 state.
Albeit that it can't be put back yet.
2020-05-18 23:55:54 -04:00
Thomas Harte
6f16928215 Adds all remaining simple scalar fields. 2020-05-16 22:47:04 -04:00
Thomas Harte
ff3c2fdc59 Adds 68000 state to SConstruct. 2020-05-16 18:33:36 -04:00
Thomas Harte
57edfe8751 Formalises TODO list and marches onward into execution state. 2020-05-16 18:31:43 -04:00
Thomas Harte
dcc0ee3679 Adds input line capture. 2020-05-16 17:44:15 -04:00
Thomas Harte
f7a16762b4 Starts populating the 68000 state registers. 2020-05-16 00:06:04 -04:00
Thomas Harte
375835a950 Extends .description() to handle arrays. 2020-05-14 23:58:17 -04:00
Thomas Harte
4481386a3d Extends Reflection::Struct slightly to capture the lengths of arrays. 2020-05-14 22:59:44 -04:00
Thomas Harte
8b76d4007e Starts adding State for the 68000. 2020-05-14 22:46:40 -04:00
Thomas Harte
4f30118b37 Merge pull request #793 from TomHarte/Z80State
Adds reflective state for the Z80.
2020-05-14 00:18:58 -04:00
Thomas Harte
c5b746543b Factors the half mask into steps count. 2020-05-14 00:09:01 -04:00
Thomas Harte
11d936331d Attempts to preserve scheduled_program_counter_. 2020-05-13 23:58:04 -04:00
Thomas Harte
4f619de675 Permits ::get from a reflective enum to an int. 2020-05-13 23:48:28 -04:00
Thomas Harte
80f2836cb8 Adds Z80 state to SConstruct. 2020-05-13 22:05:23 -04:00
Thomas Harte
3709aa7555 Edges almost up to an initially complete implementation. 2020-05-13 22:04:04 -04:00
Thomas Harte
7c9d9ee048 Adds basic Z80 state. 2020-05-13 20:15:22 -04:00
Thomas Harte
e4335577ca Merge pull request #792 from TomHarte/BIOSFreeMasterSystem
Ensures the Master System makes a genuine attempt to boot sans BIOS
2020-05-12 22:35:17 -04:00
Thomas Harte
66c2eb0414 Further tightens const and constexpr usage. 2020-05-12 22:22:21 -04:00
Thomas Harte
f82e4ee923 Makes additional minor const improvements. 2020-05-12 00:31:16 -04:00
Thomas Harte
b62ee33318 Improves constness of Joystick interface. 2020-05-12 00:19:48 -04:00
Thomas Harte
8596a9826f Whether the BIOS is available in hardware is now decided entirely based on whether it is on disk. 2020-05-12 00:11:46 -04:00
Thomas Harte
3f2fb1fa58 Merge pull request #790 from ajacocks/master
Create RPM package and man page
2020-05-11 13:06:47 -04:00
Alexander Jacocks
5f39938a19 add Ansible build playbook to create RPM package for clksignal and create basic man page 2020-05-11 00:52:51 -04:00
Thomas Harte
d964ebd4c1 Merge pull request #789 from TomHarte/OPLLDrums
Softens OPLL tremolo and vibrato; adds drum damping.
2020-05-10 15:51:34 -04:00
Thomas Harte
9458963311 Factors out shift by 7. 2020-05-10 13:57:50 -04:00
Thomas Harte
44690b1066 Halves effect of vibrato. 2020-05-10 12:05:14 -04:00
Thomas Harte
c41028cdc7 Adds further exposition. 2020-05-10 00:44:03 -04:00
Thomas Harte
64c62c16fb Adjusts tremolo scale. 2020-05-10 00:43:46 -04:00
Thomas Harte
afef4f05fe Adds damping and phase resets for the rhythm section. 2020-05-10 00:10:51 -04:00
Thomas Harte
fc0f290c85 Merge pull request #788 from TomHarte/ConstFun
Cleans up a variety of dangling issues.
2020-05-09 23:57:22 -04:00
Thomas Harte
81d70ee325 Adds in a few further consts. 2020-05-09 23:49:37 -04:00
Thomas Harte
6dc7a4471d Removes unused .cpp file. 2020-05-09 23:43:05 -04:00
Thomas Harte
fcb8bd00b6 Adds further costs. 2020-05-09 23:42:42 -04:00
Thomas Harte
05c3f2a30d Adds some further `costs. 2020-05-09 23:03:33 -04:00
Thomas Harte
25996ce180 Further doubles down on construction syntax for type conversions. 2020-05-09 23:00:39 -04:00
Thomas Harte
3729bddb2a Farewell, BestEffortUpdater. 2020-05-09 21:48:04 -04:00
Thomas Harte
4136428db3 Removes dead StandardOptions.cpp. 2020-05-09 21:35:15 -04:00
Thomas Harte
31c6faf3c8 Adds a bunch of consts. 2020-05-09 21:23:52 -04:00
Thomas Harte
5c1ae40a9c Merge pull request #783 from TomHarte/OPL2
Adds provisional OPLL emulation.
2020-05-09 18:28:03 -04:00
Thomas Harte
4c6d0f7fa0 Corrects SConstruct; applies default initialisation in Struct.cpp. 2020-05-09 18:11:50 -04:00
Thomas Harte
40b60fe5d4 Renames folder as per intended scope. 2020-05-09 18:04:11 -04:00
Thomas Harte
eed357abb4 Introduces concept of 'average peak volume' in order better to normalise audio sources like the OPLL. 2020-05-09 17:57:21 -04:00
Thomas Harte
8f541602c1 Moves modulator updates a sample behind operator updates. 2020-05-08 21:14:25 -04:00
Thomas Harte
668f4b77f3 Implements feedback. 2020-05-08 21:05:23 -04:00
Thomas Harte
303965fbb8 Removes the crutch of my first-attempt implementation. 2020-05-08 20:53:34 -04:00
Thomas Harte
792aed242d Fixes the use-sustain flag. 2020-05-08 20:49:39 -04:00
Thomas Harte
dc5654b941 Attempts to implement the proper attack phase.
It's sounding pretty good now, but for sustain.
2020-05-08 18:59:05 -04:00
Thomas Harte
e51e2425cc Attempts to implement decay and release the right way around and with full precision.
Higher numbers = decay/release more quickly, not more slowly.
2020-05-08 18:40:49 -04:00
Thomas Harte
95c6b9b55d Declare proper envelope precision. 2020-05-08 17:58:50 -04:00
Thomas Harte
ea25ead19d Ensures rhythm envelope generators don't pick up should_damp state. 2020-05-08 00:18:31 -04:00
Thomas Harte
24100ec3b0 Switches snare and high-hat envelope generators. 2020-05-08 00:08:14 -04:00
Thomas Harte
32437fbf8b Attempts to use the proper rhythm mode envelope generators. 2020-05-07 23:56:15 -04:00
Thomas Harte
5219a86a41 In principle fully implements rhythm mode. 2020-05-07 23:38:51 -04:00
Thomas Harte
e12dc5d894 Reduce the amount of time spent installing instruments. 2020-05-06 00:15:28 -04:00
Thomas Harte
75315406bb Ensure all channels begin in 'release' phase, which is currently code for 'off' in conjunction with attenuation of 511. 2020-05-06 00:13:01 -04:00
Thomas Harte
ea42fe638a Corrects channel attenuation and carrier sustain level settings. 2020-05-05 23:41:15 -04:00
Thomas Harte
744211cec0 Ensures rhythm instruments are installed. 2020-05-05 23:13:13 -04:00
Thomas Harte
1a4321d7d0 Attempts better to balance attenuations. 2020-05-05 22:14:11 -04:00
Thomas Harte
b943441901 Marks up more specific TODOs.
I think I'm already much happier with this factoring.
2020-05-05 00:35:03 -04:00
Thomas Harte
0505b82384 Restores top bit of channel period, propagates it to the envelope generator. 2020-05-05 00:28:24 -04:00
Thomas Harte
c9fb5721cd Makes first attempt to reintroduce full-melodic output. 2020-05-05 00:16:45 -04:00
Thomas Harte
386a7ca442 Continues doing away with the attempt heavily to interleave the OPLL and OPL2, creating a new OPLL class. 2020-05-04 21:14:51 -04:00
Thomas Harte
e929d5d819 Ensures proper dereferencing of the std::optional. 2020-05-03 21:57:15 -04:00
Thomas Harte
94614ae4c3 Shifts the LFO implementation inline. 2020-05-03 21:44:22 -04:00
Thomas Harte
1223c99e0f Adds waveform generation logic to the new factoring. 2020-05-03 21:38:20 -04:00
Thomas Harte
1ff5ea0a6e Adds KeyLevelScaler, implements EnvelopeGenerator, adds reset to PhaseGenerator. 2020-05-03 16:24:55 -04:00
Thomas Harte
9d2691d1d2 Taking it as given that outstanding deficiencies are mostly due to poor design, starts breaking out the envelope and phase generators. 2020-05-01 23:46:42 -04:00
Thomas Harte
e4ef2c68bb Feeds through drum volume levels. 2020-04-30 19:35:09 -04:00
Thomas Harte
7fffafdfd4 Wires the high-hat through, possibly incorrectly. 2020-04-29 22:44:15 -04:00
Thomas Harte
5896288edd Adapts to new interface. 2020-04-29 22:08:36 -04:00
Thomas Harte
c4135fad2b Attempts completely to decouple updates and audio outputs. 2020-04-29 22:07:40 -04:00
Thomas Harte
1f34214fb3 Imagines a future of being able to boot into the BIOS. 2020-04-29 22:07:20 -04:00
Thomas Harte
f899af0eef Fixes OPL tests. 2020-04-28 20:17:16 -04:00
Thomas Harte
9f0c8bcae7 Attempts to add the missing noise generators. I think I may still be astray on volumes. 2020-04-26 15:51:33 -04:00
Thomas Harte
2bc36a6cde Eliminates branch within snare output. 2020-04-26 00:21:15 -04:00
Thomas Harte
ee10fe3d2c Fully separates updates and outputs in operators; takes a shot at the snare. 2020-04-26 00:18:09 -04:00
Thomas Harte
a424e867f9 Continues factoring this apart, albeit with a decision on whether to retain update-and-output still pending. 2020-04-25 23:07:40 -04:00
Thomas Harte
f52b40396a Re-ups output level.
Though it's still quiet compared to the SN.
2020-04-25 23:07:06 -04:00
Thomas Harte
cd2ab70a58 Moves the LFSR to the LowFrequencyOscillator.
Possibly I should come up with a better name for that?
2020-04-25 22:21:42 -04:00
Thomas Harte
a5d1941d28 Adds necessary standalone #imports; makes safe for signed types. 2020-04-25 22:21:10 -04:00
Thomas Harte
65a3783dd2 Attempts the tom tom. 2020-04-25 19:21:55 -04:00
Thomas Harte
b9b5c2a3bc Takes a first run at proper slot mixing and the bass drum. 2020-04-25 18:01:05 -04:00
Thomas Harte
12c618642e Corrects output range. 2020-04-25 00:07:58 -04:00
Thomas Harte
6ebc93c995 Switches to maximum-rate multiplexing. Hopefully to eliminate the mixer as a consideration for now. 2020-04-24 23:50:06 -04:00
Thomas Harte
6d4e29c851 Strips mixer back to basics in search of audio issues. 2020-04-24 23:32:02 -04:00
Thomas Harte
b3979e2fda Looking towards rhythm mode, and in search of bugs: factors out ADSR.
Further factorings to come.
2020-04-24 18:48:32 -04:00
Thomas Harte
983c32bf75 Adds vibrato.
This would complete melodic output, subject to bug fixes.
2020-04-24 18:02:41 -04:00
Thomas Harte
9e3614066a Adds tremolo support, switches to global timer for ADSR stages other than attack. 2020-04-23 23:55:49 -04:00
Thomas Harte
c7ad6b1b50 Minor layout and commenting improvements. 2020-04-21 23:35:48 -04:00
Thomas Harte
676dcf7fbb Calculates the proper key scale rate, though ADSR itself is still lacking that precision. 2020-04-21 22:57:56 -04:00
Thomas Harte
50d725330c Adds missing header. 2020-04-21 22:48:52 -04:00
Thomas Harte
2886dd1dae Collapses key-level scaling to a single 2d table.
I dare imagine I can do better; the columns in particular look like arithmetic progressions.
2020-04-21 20:19:02 -04:00
Thomas Harte
40424ac38b Re-enables key-level scaling, with 3db and 1.5db the correct way around. 2020-04-21 20:10:40 -04:00
Thomas Harte
a4d3865394 Decreases sustain level attenuation; disables key-level scaling for now.
The latter was definitely wrong, I also think I don't need the big four tables.
2020-04-21 19:58:40 -04:00
Thomas Harte
0ac99e8d42 Disables low low-pass filter, honours audio control bits for better volume usage. 2020-04-21 19:57:13 -04:00
Thomas Harte
bdce1c464a Takes a shot at key-level scaling. Testing to come. 2020-04-21 00:09:42 -04:00
Thomas Harte
475d75c16a Preserves fractional part of modulator phase. 2020-04-20 23:35:37 -04:00
Thomas Harte
32fd1897d0 Via a unit test, confirms and fixes relative volumes of OPLL channels.
Also rejigs responsibility for scaling to emulator-standard volume.
2020-04-20 23:17:29 -04:00
Thomas Harte
39e6a28730 Rearranges file. 2020-04-20 19:41:04 -04:00
Thomas Harte
3852e119aa Adds test data for FM wave generation. 2020-04-20 19:33:03 -04:00
Thomas Harte
f19fd7c166 Pulls out common melodic update calls. 2020-04-20 18:58:31 -04:00
Thomas Harte
100fddcee1 Corrects divider, takes another whack at ADSR. 2020-04-20 18:58:10 -04:00
Thomas Harte
99fa86a67e Adds a test for lookup sine. And fixes lookup sine. 2020-04-20 18:40:47 -04:00
Thomas Harte
6568c29c54 Improves commentary. 2020-04-19 22:42:25 -04:00
Thomas Harte
c54bbc5a04 Rename Table.h; LogSin -> LogSign and make it a bit more typer. 2020-04-19 13:33:17 -04:00
Thomas Harte
92d0c466c2 Moves complete phase -> output calculation inside Operator.
Reasoning being: otherwise I wasn't currently enforcing non-sine waveforms.
2020-04-19 13:27:24 -04:00
Thomas Harte
020c760976 Simplifies the phase counter. 2020-04-19 00:30:14 -04:00
Thomas Harte
cdfd7de221 Minor: enables all melodic channels when rhythm mode is disabled; supports non-modulated channels. 2020-04-18 17:48:29 -04:00
Thomas Harte
3da2e91acf Adjusts range of output, makes declaration of level full owner of type information. 2020-04-17 23:29:09 -04:00
Thomas Harte
3948304172 Attempts to use table-based maths. 2020-04-17 23:23:16 -04:00
Thomas Harte
4a295cd95e Wraps log_sin in an access function to enshrine sign and mask rules; switches both functions to non-math.h clashing names. 2020-04-17 23:22:42 -04:00
Thomas Harte
6f7c8b35c5 Applies an ahead-of-time transformation to the exp table, and wraps it in a helper function. 2020-04-17 22:33:13 -04:00
Thomas Harte
e58ba27c00 Clarifies meaning of scaling. Though it isn't yet applied. 2020-04-17 22:30:10 -04:00
Thomas Harte
0aceddd088 Starts tidying up the OPL2.
This is as a precursor to switching to using the proper table lookups, which I hope will automatically fix my range issues.
2020-04-15 22:10:50 -04:00
Thomas Harte
30ff399218 With some fixes for scale, I think possibly this is close for melodic channels. 2020-04-15 21:27:27 -04:00
Thomas Harte
a7e63b61eb Just from printing numbers: corrects transition from attack to decay. 2020-04-15 00:26:01 -04:00
Thomas Harte
b13b0d9311 Starts towards implementing some OPL test cases. 2020-04-14 23:51:45 -04:00
Thomas Harte
d8380dc3e2 Tries to be a little neater in spelling out the work here.
I think I'm somewhat circling here now; I need to think of a way of getting clean comparison data.
2020-04-14 21:55:42 -04:00
Thomas Harte
d805e9a8f0 Actually, octave probably works this way around? Higher octaves = higher frequencies. 2020-04-14 21:39:12 -04:00
Thomas Harte
aa45142728 Endeavours to fix attenuation and add FM synthesis.
I now definitely think my frequency counting is wrong.
2020-04-14 18:32:06 -04:00
Thomas Harte
09d1aed3a5 Attempts to voice the current attenuation (and, therefore, the ADSR output), even if linearly rather than logarithmically. 2020-04-13 22:12:55 -04:00
Thomas Harte
a1f80b5142 Takes a stab at per-operator ADSR.
Heavy caveats apply: no KSR is applied, non-ADSR attenuation isn't applied, attenuation isn't voiced in general.
2020-04-13 21:39:06 -04:00
Thomas Harte
cb1970ebab Switch to more compact form of output for bool.
This also will hopefully deal with GCC's slightly confused claim that 'value' may be used without having been initialised down at #define OutputIntC (i.e. after it's out of scope, but I can sort of see why GCC might get confused while it remains in scope).
2020-04-12 14:40:32 -04:00
Thomas Harte
d3fbdba77c Add missing #include. 2020-04-12 14:20:02 -04:00
Thomas Harte
632d797c9d Adjusts frequency formula. This could be close.
I guess next I need to get ADSR/volume in general working, before I can go FM? Then I'll worry about using the proper log-sin/exp tables.
2020-04-12 14:15:09 -04:00
Thomas Harte
559a2d81c1 Baby step: starts trying to output the raw FM carrier, no modulation, no ADSR. 2020-04-12 12:46:40 -04:00
Thomas Harte
7a5f23c0a5 Adds accommodations for the OPLL. 2020-04-10 22:05:22 -04:00
Thomas Harte
84b115f15f Attempts to move forward in defining what the parts of an OPL are meant to do. 2020-04-10 19:13:52 -04:00
Thomas Harte
a0d14f4030 Starts trying to make sense of the various fields at play. 2020-04-08 23:15:44 -04:00
Thomas Harte
dd6769bfbc Splits OPLL and OPL2 classes.
Logic is: they have different mixers (additive in the OPL2, time-division multiplexing in the OPLL) as well as different register sets. So I'll put operator and channel logic directly into those structs.
2020-04-07 23:15:26 -04:00
Thomas Harte
027af5acca Allow LFSR to be instantiated with a given value. 2020-04-05 22:58:09 -04:00
Thomas Harte
db4b71fc9a Adds correct LSFR, something of OPLL -> OPL2 logic. 2020-04-05 22:57:53 -04:00
Thomas Harte
d9e41d42b5 Adds the OPL2 to SConstruct. 2020-04-05 21:34:19 -04:00
Thomas Harte
0ed7d257e1 Add some extra notes, implement correct mapping to only 18 operators. Not 22. 2020-04-05 14:32:55 -04:00
Thomas Harte
335a68396f Attempts to complete OPL2 register decoding. 2020-04-04 23:39:09 -04:00
Thomas Harte
84cdf6130f Starts at least trying to decode OPL2 register writes. 2020-04-04 23:29:25 -04:00
Thomas Harte
b0abc4f7bb Implements enough wiring that the Master System will instantiate and talk to an OPLL. 2020-04-03 20:05:36 -04:00
Thomas Harte
ab81d1093d Merge pull request #782 from TomHarte/6502Tidy
Makes `State`, and therefore the 'Reflection' dependency, an optional adjunct to the 6502.
2020-04-02 20:49:18 -04:00
Thomas Harte
e4d4e4e002 Adds 6502 State to the SConstruct file.
On the assumption I'll actually use it at some point.
2020-04-02 19:16:22 -04:00
Thomas Harte
cc357a6afa Removes boilerplate from header. 2020-04-02 19:15:57 -04:00
Thomas Harte
dfc1c7d358 Separates 6502 State object to make it optional.
Also makes a few minor const improvements while I'm poking around.
2020-04-02 19:11:27 -04:00
Thomas Harte
7ed8e33622 Eliminates unused 6502 counter. 2020-04-02 18:49:28 -04:00
Thomas Harte
474822e83d Merge pull request #781 from TomHarte/NoMoreCRTMachine
Splits 'CRTMachine' into three parts: ScanProducer, AudioProducer, TimedMachine.
2020-04-02 09:46:54 -04:00
Thomas Harte
fe3942c5b3 Updates comments. 2020-04-01 23:49:07 -04:00
Thomas Harte
f417fa82a4 Splits 'CRTMachine' into three parts: ScanProducer, AudioProducer, TimedMachine.
Simultaneously cleans up some of the naming conventions and tries to make things a bit more template-compatible.
2020-04-01 23:19:34 -04:00
Thomas Harte
c4b114133a Merge pull request #779 from TomHarte/6502State
Provisionally adds `State` and `get/set_state` to the 6502.
2020-03-31 21:05:29 -04:00
Thomas Harte
2f4b0c2b9a Removes non-functional assert. 2020-03-30 21:48:07 -04:00
Thomas Harte
a491650c8b Adds safety asserts. 2020-03-30 21:39:31 -04:00
Thomas Harte
6805acd74f Adds padding for all integer types. 2020-03-30 00:31:25 -04:00
Thomas Harte
95c68c76e1 Corrects use of StructImpl. 2020-03-30 00:27:40 -04:00
Thomas Harte
60aa383c95 Makes a not-quite-correct attempt at a .description for reflective structs. 2020-03-30 00:24:49 -04:00
Thomas Harte
edc553fa1d Removes duplicative 'register'. 2020-03-29 22:58:00 -04:00
Thomas Harte
4f2ebad8e0 Takes a shot a set_state. 2020-03-29 22:50:30 -04:00
Thomas Harte
1810ef60be Adds --fix-missing in the hope of catching more issues automatically. 2020-03-29 18:41:30 -04:00
Thomas Harte
f720a6201b Adds explicit type cast. 2020-03-29 18:36:57 -04:00
Thomas Harte
cfb75b58ca Pulls all 6502 MicroOp sequences into the main operations_ table.
This will make state restoration somewhat more tractable.
2020-03-29 18:36:41 -04:00
Thomas Harte
4fbe983527 Provisionally adds State and get_state to the 6502.
`set_state` may be a little more complicated, requiring a way to advance in single-cycle steps **without applying bus accesses**.
2020-03-28 00:33:27 -04:00
Thomas Harte
272383cac7 Merge pull request #778 from TomHarte/AppleIIDisks
Resolves a potential crash with NIB files
2020-03-25 21:52:26 -04:00
Thomas Harte
39380c63cb Throws in some consts. 2020-03-25 21:25:50 -04:00
Thomas Harte
ea26f4f7bf Eliminates test code, adds a caveat. 2020-03-25 21:22:30 -04:00
Thomas Harte
5fd2be3c8e Makes a genuine attempt at five and three decoding. 2020-03-25 20:50:26 -04:00
Thomas Harte
2320b5c1fe Takes some steps towards five-and-three decoding.
Now I 'just' need to figure out how bits are distributed within the decoded sector. The XORing and data checksum seem the same (?)
2020-03-25 00:15:31 -04:00
Thomas Harte
e5cbdfc67c It turns out that 5-and-3 disks have a different header prologue. 2020-03-24 21:59:55 -04:00
Thomas Harte
894d196b64 Avoids massive overallocation where sync blocks overlap the index hole. 2020-03-24 21:34:33 -04:00
Thomas Harte
af037649c3 Merge pull request #777 from TomHarte/ShowCRCs
Show CRC32s of missing ROMs.
2020-03-23 21:33:10 -04:00
Thomas Harte
cfca3e2507 Adds missing header for std::setw, std::set fill. 2020-03-23 21:26:50 -04:00
Thomas Harte
7a12a0149a Ensures BIOS is really not paged if not loaded. 2020-03-23 20:00:31 -04:00
Thomas Harte
fcdc1bfbd0 Prints the CRC32(s) of any missing ROMs. 2020-03-23 20:00:13 -04:00
Thomas Harte
d1d14ba9a0 Merge pull request #775 from TomHarte/SavedVolume
Ensures the macOS version retains volume.
2020-03-23 00:18:51 -04:00
Thomas Harte
0e502f6d5c Ensures the macOS version retains volume. 2020-03-23 00:10:56 -04:00
Thomas Harte
d3bac57d6a Merge pull request #774 from TomHarte/VolumeControl
Adds output volume control.
2020-03-22 21:23:49 -04:00
Thomas Harte
bd1b4b8a9f Increases volume fade-out speed. 2020-03-22 21:13:55 -04:00
Thomas Harte
38d81c394f Switches OSAtomics to stdatomics. The former were deprecated by macOS 10.12. 2020-03-22 21:11:04 -04:00
Thomas Harte
72103a4adb Corrects execution cap for splitAndSync ticks. 2020-03-22 19:25:02 -04:00
Thomas Harte
e6bae261c4 Ensures volume controls appear for mouse-capture machines when not capturing. 2020-03-22 19:06:38 -04:00
Thomas Harte
5edb0c0ee7 Adds animated fade-out to volume control. Bumps macOS version to 10.12.2. 2020-03-22 18:45:24 -04:00
Thomas Harte
442ce403f9 It's a bit jarring, but ensures volume control shows and hides according to mouse cursor. 2020-03-22 16:25:07 -04:00
Thomas Harte
7398cb44e2 Adds a functioning volume control for macOS, it just doesn't know how to hide yet. 2020-03-22 13:24:23 -04:00
Thomas Harte
15d54dfb4c Adds 'volume' command-line parameter for kiosk mode. 2020-03-21 22:24:31 -04:00
Thomas Harte
9087bb9b08 Allows audio volume to be set. 2020-03-21 22:00:47 -04:00
Thomas Harte
0c689e85a5 Use screen number for spotting screen changes.
NSScreen implements Swift Equatable but doesn't seem officially to implement -isEqual:.
2020-03-21 17:01:57 -04:00
Thomas Harte
75f2b0487e Merge pull request #773 from TomHarte/MacCrashAgain
Ensures proper NSScreen comparison...
2020-03-20 23:19:53 -04:00
Thomas Harte
5a1bae8a9c Ensures proper NSScreen comparison, and no never-ending setupDisplayLink loop on exit. 2020-03-20 23:00:16 -04:00
Thomas Harte
129bc485bf Merge pull request #772 from TomHarte/ReflectiveEnum
Endeavours to bring introspection to machine selection options.
2020-03-19 23:30:19 -04:00
Thomas Harte
69277bbb27 Renames files to match project convention. 2020-03-19 23:24:06 -04:00
Thomas Harte
b8b335f67d Exposes the Master System's region for SDL selection. 2020-03-19 21:46:42 -04:00
Thomas Harte
eef7868199 Ensures 'new' overrides default selection; doesn't try to propagate multiple files if machines won't take them. 2020-03-19 21:15:38 -04:00
Thomas Harte
23aa7ea85f Revives MultiConfigurable. 2020-03-19 21:02:14 -04:00
Thomas Harte
c1b69fd091 Attempts to support multiple pieces of media on the SDL command line, ensures proper window titling. 2020-03-19 20:40:43 -04:00
Thomas Harte
7ab7efdbc1 Ensures consistent ordering. 2020-03-19 19:41:50 -04:00
Thomas Harte
b8ebdc012f Ensure normative construction declaration ordering. 2020-03-19 18:58:36 -04:00
Thomas Harte
9995d776de Attempts to fix the macOS version, plus some implicit type conversions. 2020-03-18 23:29:09 -04:00
Thomas Harte
c6f35c9aac Rejigs help output. 2020-03-18 23:11:25 -04:00
Thomas Harte
615ea2f573 Applies parsed arguments. 2020-03-18 22:31:32 -04:00
Thomas Harte
311458f41f Restores Macintosh 'runtime' options.
Also cleans up some leftover parts elsewhere.
2020-03-18 21:50:02 -04:00
Thomas Harte
b2a381d401 Restores Vic-20 runtime options. 2020-03-18 20:23:55 -04:00
Thomas Harte
ffc1b0ff29 Reintroduces Oric runtime options. 2020-03-18 18:31:31 -04:00
Thomas Harte
ead2823322 Reintroduces MSX and Master System runtime options. 2020-03-18 18:26:22 -04:00
Thomas Harte
a7e1920597 Restores ColecoVision runtime options. 2020-03-18 00:06:52 -04:00
Thomas Harte
ec6664f590 Takes steps to guarantee property naming; reintroduces Electron runtime options. 2020-03-17 23:52:55 -04:00
Thomas Harte
8c6ca89da2 Restores runtime options for the Acorn Electron. 2020-03-17 22:06:20 -04:00
Thomas Harte
b6e81242e7 Reintroduces Apple II runtime options. 2020-03-17 21:53:26 -04:00
Thomas Harte
f9ca443667 Adds the ability for reflective structs to limit the permitted values to enumerated properties. 2020-03-17 21:44:04 -04:00
Thomas Harte
394ee61c78 Starts a switch to reflectable-style runtime options.
The Amstrad CPC and ZX80/81 have made the jump so far, subject to caveats. The macOS build is unlikely currently to work properly.
2020-03-16 23:25:05 -04:00
Thomas Harte
1d40aa687e Adds necessary include for unique_ptr. 2020-03-15 23:52:24 -04:00
Thomas Harte
8e3bf0dbca Starts moving towards a Deflectable-based system of runtime options. 2020-03-15 23:48:53 -04:00
Thomas Harte
2031a33edf Technically SDL users can now start a new machine.
Missing though: all the old per-machine command-line options, and any control over the new one.
2020-03-15 21:50:43 -04:00
Thomas Harte
fc3d3c76f8 Edges further towards providing enough information for dynamic user-provided machine creation. 2020-03-15 12:54:55 -04:00
Thomas Harte
880bed04f5 Adds AllMachines, rounds out ConstructionOptionsByMachineName. 2020-03-15 00:15:19 -04:00
Thomas Harte
f9c8470b20 Ensure targets always nominate a machine. 2020-03-15 00:13:38 -04:00
Thomas Harte
36acc2dddd Add necessary include for std::find. 2020-03-14 00:22:23 -04:00
Thomas Harte
a59963b6a0 Adds necessary header for memcpy. 2020-03-14 00:17:58 -04:00
Thomas Harte
cab4bead72 Promotes explicit specialisations to namespace scope. 2020-03-13 23:38:29 -04:00
Thomas Harte
1a2872c815 Starts to build an easy set interface. 2020-03-13 22:42:37 -04:00
Thomas Harte
f27e0a141d Sketches but doesn't implement an interface for serialisation. 2020-03-13 20:16:36 -04:00
Thomas Harte
52f644c4f1 Ensures that reflection is completely blind; starts adding SDL instantiation logic. 2020-03-12 20:56:02 -04:00
Thomas Harte
06c08a0574 Merge branch 'ReflectiveEnum' of github.com:TomHarte/CLK into ReflectiveEnum 2020-03-11 23:30:27 -04:00
Thomas Harte
724e2e6d27 Withdraws ability to select an integer size for ReflectableEnums.
It isn't that useful, and this'll help if/when I get to serialisation.
2020-03-11 23:28:38 -04:00
Thomas Harte
fd052189ca Adds reflection to all of the other computer targets. 2020-03-11 23:25:29 -04:00
Thomas Harte
044a2b67e1 Beefs up documentation on this miniature sort-of reflection. 2020-03-11 23:03:05 -04:00
Thomas Harte
7e8b86e9bb Attempts to flesh out Reflection::Enum. 2020-03-11 23:03:05 -04:00
Thomas Harte
ce80825abb Starts working towards a registration-based model of reflective enums. 2020-03-11 23:03:05 -04:00
Thomas Harte
a99bb3ba6d Switches to class storage. 2020-03-11 23:03:05 -04:00
Thomas Harte
3428e9887d Starts experimenting with declared reflection. 2020-03-11 23:03:05 -04:00
Thomas Harte
5a8fcac4dc Gives function overloading a try. 2020-03-11 23:03:05 -04:00
Thomas Harte
6a9b14f7d1 Adds a prototype reflective enum.
I need to make this scopeable before it is acceptable.
2020-03-11 23:03:05 -04:00
Thomas Harte
a74d8bd6e8 Merge pull request #771 from TomHarte/MacShutdownRace
Ensure race condition workaround is applied for all CVDisplayLinkStops.
2020-03-11 22:47:17 -04:00
Thomas Harte
3c70f056ed Ensure race condition workaround is applied for all CVDisplayLinkStops.
This also centralises the workaround, the better for replacing it when I discover a safer alternative.
2020-03-11 22:09:36 -04:00
Thomas Harte
a546880a65 Beefs up documentation on this miniature sort-of reflection. 2020-03-11 22:06:16 -04:00
Thomas Harte
238145f27f Attempts to flesh out Reflection::Enum. 2020-03-10 23:36:52 -04:00
Thomas Harte
0502e6be67 Starts working towards a registration-based model of reflective enums. 2020-03-10 22:32:55 -04:00
Thomas Harte
6a8c6f5a06 Switches to class storage. 2020-03-10 22:32:55 -04:00
Thomas Harte
5248475e73 Starts experimenting with declared reflection. 2020-03-10 22:32:55 -04:00
Thomas Harte
d6c6b9bdb8 Gives function overloading a try. 2020-03-10 22:32:55 -04:00
Thomas Harte
7bf04d5338 Adds a prototype reflective enum.
I need to make this scopeable before it is acceptable.
2020-03-10 22:32:55 -04:00
Thomas Harte
9668ec789a Merge pull request #769 from TomHarte/SDLKeyboardAgain
Return to old SDL behaviour if --logical-keyboard isn't specified.
2020-03-09 23:18:14 -04:00
Thomas Harte
ead32fb6b2 Return to old behaviour if --logical-keyboard isn't specified.
This is at least until I'm more confident in the keypress/text input merging. Also, switches to a vector for intermediate keypresses, to ensure order is retained even if timestamps are absent.
2020-03-09 23:10:39 -04:00
Thomas Harte
2ee24d29e5 Merge pull request #766 from TomHarte/6502CleanUp
Standardises 6502 casts.
2020-03-06 22:03:37 -05:00
Thomas Harte
a560601338 Corrects virtual F keys.
They're FUNC+, not SHIFT+.
2020-03-06 21:56:08 -05:00
Thomas Harte
a51fe70498 Standardises cast syntax. 2020-03-06 21:55:00 -05:00
Thomas Harte
e47aa7653b Merge pull request #765 from TomHarte/SDLKeyInput
Attempts to make use of SDL_StartTextInput.
2020-03-05 22:05:13 -05:00
Thomas Harte
58b8dfb929 Attempts to improve SDL key merging. 2020-03-05 21:56:26 -05:00
Thomas Harte
462a76dd96 Adds virtual keys for F1, F2, etc. 2020-03-05 21:01:30 -05:00
Thomas Harte
3758ec79ac Adds potential test argument. 2020-03-03 23:09:22 -05:00
Thomas Harte
a0311858f9 Adds mappings for curly brackets. 2020-03-03 23:04:10 -05:00
Thomas Harte
f08d500fd6 Attempts to factor out the latest keyboard logic and hook it in from SDL also. 2020-03-03 22:58:15 -05:00
Thomas Harte
df76d57c47 Experimentally attempts to tie together input and keypresses by timestamp. 2020-03-03 21:30:30 -05:00
Thomas Harte
0ef953a1ea Adjusts EDIT for the ZX80. 2020-03-02 23:36:38 -05:00
Thomas Harte
05cbed6b6c Merge pull request #764 from TomHarte/ZX80Cursors
Adds better guesses for unmapped physical keys.
2020-03-02 23:23:25 -05:00
Thomas Harte
9225c4ef70 Lowers audio frequency cut-off. Still doing this by ear. 2020-03-02 23:11:09 -05:00
Thomas Harte
32136b75cd Modifies mappings to improve key repeat on backspace and potentially allow mapping of other keys. 2020-03-02 23:10:18 -05:00
Thomas Harte
1f41d9c5f5 Further improvement: if in physical mode, but pressing an unrecognised key, attempt to 'type' it. 2020-03-02 22:08:54 -05:00
Thomas Harte
dc47a2b7d7 Adds virtual key for EDIT. 2020-03-02 21:44:15 -05:00
Thomas Harte
1a539521f2 Merge pull request #763 from TomHarte/LogicalKeyboards
Adds support for 'logical' keyboard entry
2020-03-01 23:21:46 -05:00
Thomas Harte
2db30a91c6 'Corrects' but disables SDL logical keyboard entry.
I'm just not sure that SDL supports what I want.
2020-03-01 23:11:14 -05:00
Thomas Harte
b2c07b3110 The Atari ST doesn't offer quick loading. 2020-03-01 22:10:41 -05:00
Thomas Harte
90e6bef6d7 Adds virtual keys for F2, F4, F6 and F8. 2020-03-01 21:47:28 -05:00
Thomas Harte
535634daca Introduces virtual left and up keys for the Vic-20.
Thereby allowing all cursor keys to be mapped.
2020-03-01 21:42:30 -05:00
Thomas Harte
575d0da4d1 Attempts better to describe options. 2020-03-01 21:31:40 -05:00
Thomas Harte
ed18092088 Extends logic for when to fall back on standard keypress logic even in logical mode. 2020-03-01 20:25:12 -05:00
Thomas Harte
611182910a Slightly rejigs character mapper ownership. 2020-03-01 18:44:26 -05:00
Thomas Harte
9273e9b6ed Adds a second virtual key, for break. 2020-02-29 23:11:02 -05:00
Thomas Harte
77c0cc8b5f Provisionally adds logical keyboard support to SDL. 2020-02-29 23:07:14 -05:00
Thomas Harte
0705a99ea0 Adds a virtual delete key to the ZX80 and ZX81. 2020-02-29 22:51:42 -05:00
Thomas Harte
560394fead Ensures keys without symbols are forwarded. 2020-02-29 22:37:15 -05:00
Thomas Harte
86a09b5e7d Slightly improves ZX80 and ZX81 typing speed. 2020-02-29 22:31:45 -05:00
Thomas Harte
32b2026734 Alters shortcut. 2020-02-29 20:01:21 -05:00
Thomas Harte
b33f568fdd Makes basic typing adaptations. 2020-02-29 19:59:51 -05:00
Thomas Harte
6e4bd4f505 Ensures new text is appended to any existing buffer.
TODO: move this into add_typer?
2020-02-29 19:58:56 -05:00
Thomas Harte
b971e2a42c Adds get_is_resetting to the Z80, eliminating the CPC's custom version. 2020-02-29 19:58:25 -05:00
Thomas Harte
3c103506c9 Optimises Electron typer speed. 2020-02-29 19:26:15 -05:00
Thomas Harte
41d2062342 Ensures that sequences of the same character are broken up properly. 2020-02-29 19:22:54 -05:00
Thomas Harte
672c59f970 Adds use of append with typer. 2020-02-29 18:52:47 -05:00
Thomas Harte
99229df017 Slightly improves syntax. 2020-02-29 18:52:12 -05:00
Thomas Harte
346d80e30b Corrects phase counting for machines that pause after clear. Which is all of them by default. 2020-02-29 18:51:55 -05:00
Thomas Harte
54b3e511e9 Extends mapping slightly for potential duplicate delete and return. 2020-02-29 18:40:41 -05:00
Thomas Harte
f25683ebec Fixes off-by-one range test. 2020-02-29 18:35:13 -05:00
Thomas Harte
d5e781e8e1 Adds macOS UI option to use logical keyboard input. 2020-02-29 18:30:58 -05:00
Thomas Harte
4572c86f0f Adds a third keyboard input mode, which maps to posting things as a typer. 2020-02-29 18:17:39 -05:00
Thomas Harte
8a5c4e384a Minimises typer timing. 2020-02-29 18:13:05 -05:00
Thomas Harte
4594a3c02b Ensures final thing in a key sequence is fully typed; adds ability to quicken input. 2020-02-29 18:12:32 -05:00
Thomas Harte
bd45c1c963 Adds append and generally seeks to improve string accumulation. 2020-02-29 17:34:21 -05:00
Thomas Harte
5f8bb92f36 Merge pull request #761 from TomHarte/Z80Tests
Imports and satisfies additional Z80 unit tests
2020-02-27 22:43:30 -05:00
Thomas Harte
3f64cdaff8 Improves documentation. 2020-02-27 22:33:34 -05:00
Thomas Harte
7ac0ea8529 Corrects test cases, as far as they go. 2020-02-27 22:33:18 -05:00
Thomas Harte
a3569d7201 Corrects so as not to test header. Both Zexall and Zexdoc pass. 2020-02-27 22:09:56 -05:00
Thomas Harte
01faffd5bf Corrects memptr behaviour of OTIR/OTDR and INIR/INDR.
This seemingly perfects memptr.
2020-02-27 20:55:43 -05:00
Thomas Harte
26de5be07c Corrects memptr behaviour of LDIR/LDDR and CPIR/CPDR. 2020-02-27 20:44:53 -05:00
Thomas Harte
87474d5916 Corrects memptr behaviour of OUT (C), 0. 2020-02-27 20:38:27 -05:00
Thomas Harte
a366077509 Updates failure count. 2020-02-26 22:26:23 -05:00
Thomas Harte
06163165d9 Corrects memptr effect of LD rr, (nn). 2020-02-26 22:22:54 -05:00
Thomas Harte
ec82c075be Fixes memptr for IN C, (C). 2020-02-26 22:19:37 -05:00
Thomas Harte
3b0df172a7 Corrects memptr behaviour of JP nn. 2020-02-26 22:02:15 -05:00
Thomas Harte
7058dbc3cc Corrects memptr for LD HL, (nn). 2020-02-26 21:54:49 -05:00
Thomas Harte
b64de89d2d Corrects JR memptrs. 2020-02-26 21:47:34 -05:00
Thomas Harte
8878396339 Corrects DJNZ memptr behaviour. 2020-02-26 21:42:31 -05:00
Thomas Harte
da6d5e2e24 Adds memptr testing.
30 failures, for the record.
2020-02-26 20:05:14 -05:00
Thomas Harte
18bb90329a Apparently tStates is decimal. Of course it is. 2020-02-26 20:04:55 -05:00
Thomas Harte
604bb50adf Imports and converts updated FUSE tests.
Now with added MEMPTR.
2020-02-25 23:15:27 -05:00
Thomas Harte
e4887c0c56 Corrects JR cc tests. 2020-02-24 23:36:05 -05:00
Thomas Harte
3097c4ccae Improves MEMPTR testing and some results. 2020-02-24 23:32:18 -05:00
Thomas Harte
7959d243f6 Adds single-stepping. Of a kind. 2020-02-24 23:31:42 -05:00
Thomas Harte
79dd402bc8 Consolidates different test port input selection. 2020-02-23 16:12:28 -05:00
Thomas Harte
3f3229851b Implements MEMPTR for IN. 2020-02-23 00:32:33 -05:00
Thomas Harte
989628a024 Switches to looking for "Result: all tests passed." as a success/failure test. 2020-02-22 23:07:14 -05:00
Thomas Harte
e0475343f5 Makes collated text easier to read. 2020-02-22 18:58:24 -05:00
Thomas Harte
da0a9113d4 Introduces the full range of tests.
Albeit that I don't know the correct output yet.
2020-02-22 18:44:15 -05:00
Thomas Harte
cf7ab97451 Gets the first test to run (and terminate). 2020-02-22 18:42:23 -05:00
Thomas Harte
2370575eb5 Starts introducing the Patrik Rak tests. 2020-02-22 15:49:36 -05:00
Thomas Harte
825b68e5c4 Adds separate entry points for zexall and zexdoc. 2020-02-22 12:34:47 -05:00
Thomas Harte
851cba0b25 Corrects lambda capture. 2020-02-22 12:34:16 -05:00
Thomas Harte
f0ec168ac7 Merge pull request #760 from TomHarte/MoreAsserts
Throws in additional asserts.
2020-02-20 11:19:03 -05:00
Thomas Harte
fa933952f7 Throws in additional asserts, so far without uncovering anything. 2020-02-19 23:14:18 -05:00
Thomas Harte
ba6e23784c Merge pull request #759 from TomHarte/CPCCorruption
Resolves a variety of potential startup data races.
2020-02-19 21:28:22 -05:00
Thomas Harte
614032198e Ensures no divide by zero during initial construction. 2020-02-18 22:58:37 -05:00
Thomas Harte
3715e6b48a Resolves potential data race on write_area_texture_. 2020-02-18 22:41:46 -05:00
Thomas Harte
91e7400bbb Avoids double-setting of the OpenGL view. 2020-02-18 22:33:16 -05:00
Thomas Harte
a8d082c7d2 Makes audioQueue atomic to avoid potential data race. 2020-02-18 22:31:24 -05:00
Thomas Harte
95756f9716 Resolves data race on write_pointers_ close to machine setup. 2020-02-18 20:41:51 -05:00
Thomas Harte
a5e1765ce4 Eliminates potential race conditions on validity of delegate_. 2020-02-18 20:33:31 -05:00
Thomas Harte
f43c31da1f Tries a new arrangement of hsync response. 2020-02-17 22:24:01 -05:00
Thomas Harte
95d0adf10e Moves the icon off the first text line.
Again, in the hope of having GitHub's search pick it up.
2020-02-17 00:35:06 -05:00
Thomas Harte
2e1b245cd8 Merge pull request #758 from TomHarte/JasminDriveSelection
Switches Jasmin drive selection logic.
2020-02-16 21:21:41 -05:00
Thomas Harte
5400c47f07 Merge pull request #757 from TomHarte/ByteDrive
Updates Byte Drive implementation as per latest information.
2020-02-16 21:16:58 -05:00
Thomas Harte
4153442703 Switches Jasmin drive selection logic. 2020-02-16 21:15:16 -05:00
Thomas Harte
5e4b721e97 Updates Byte Drive implementation as per latest information. 2020-02-16 21:07:03 -05:00
Thomas Harte
aca41ac089 Merge pull request #756 from TomHarte/Stereo
Adds stereo sound processing.
2020-02-16 19:20:21 -05:00
Thomas Harte
01a883e669 Corrects fullscreen switch. 2020-02-16 19:07:13 -05:00
Thomas Harte
1e4356f83a Adds a sensible is_stereo to the MultiSpeaker. 2020-02-16 18:50:34 -05:00
Thomas Harte
545a6177bb Makes CompoundSource mono/stereo-aware. 2020-02-16 18:45:36 -05:00
Thomas Harte
50d356be2f Ensures all audio sources, including compound sources, announce whether they're stereo correctly. 2020-02-16 18:31:45 -05:00
Thomas Harte
9835e800ec Fixed: individual audio generators now either are or are not stereo. The speaker acts accordingly. 2020-02-16 18:28:03 -05:00
Thomas Harte
5242362f31 Slightly shuffles preprocessor use, the better for testing. 2020-02-16 17:53:26 -05:00
Thomas Harte
808e4e8537 Adds comment to explain channel allocations. 2020-02-16 15:04:52 -05:00
Thomas Harte
43740a4b2f Adds support for stereo output. 2020-02-16 14:14:10 -05:00
Thomas Harte
f99d672237 The macOS port now selects stereo output if appropriate. 2020-02-16 14:05:50 -05:00
Thomas Harte
337cb4fb86 Resolves implicit type conversion warnings. 2020-02-16 14:05:23 -05:00
Thomas Harte
90856a0e7a Adds mixdown/up capability to Speaker.
To deal with occasions when the host machine just always is either mono or stereo, and the emulated machine must cope.
2020-02-16 13:50:18 -05:00
Thomas Harte
ea1c8a3b81 Ensures the stereo audio queue is the same length (in time) as the mono. 2020-02-16 12:46:25 -05:00
Thomas Harte
d55d077a95 Fixes the input buffer partial-keep step in stereo. 2020-02-16 00:20:22 -05:00
Thomas Harte
f760a68173 Corrects stereo audio generation. 2020-02-16 00:19:49 -05:00
Thomas Harte
e66a3523b6 Makes some attempt at stereo support, with the Amstrad CPC being the test case. 2020-02-15 18:55:19 -05:00
Thomas Harte
89d6b85b83 Adds optional stereo output for the AY.
The real chip provides the three tone channels as separate outputs, so a variety of different mixings can exist.
2020-02-15 18:09:17 -05:00
Thomas Harte
e02d109864 Nudges the LowpassSpeaker towards supporting stereo generation. 2020-02-15 18:03:12 -05:00
Thomas Harte
743981e9ad Adds stereo output for SDL. 2020-02-15 17:23:40 -05:00
Thomas Harte
49b8e771b5 Adds the messaging that would allow a Speaker to output stereo, semantically. 2020-02-15 13:40:19 -05:00
Thomas Harte
dde672701f Merge pull request #755 from TomHarte/ExpliticLambdas
Tries to be less lazy with lambda captures.
2020-02-15 12:38:12 -05:00
Thomas Harte
9ca2d8f9f2 Tried to be less lazy with lambda captures.
This is primarily defensive.
2020-02-14 23:39:08 -05:00
Thomas Harte
fd786412aa Merge pull request #754 from TomHarte/AYVolume
Introduces more correct AY volume levels.
2020-02-14 23:38:17 -05:00
Thomas Harte
eb88c7cfba Allows up to half a second of hard processing. 2020-02-14 23:24:51 -05:00
Thomas Harte
e1892ff370 Resolves crash upon File -> New..., Cancel; also ensures slow performance can't equal no progression. 2020-02-14 23:16:44 -05:00
Thomas Harte
763159a6f6 More neatly ties volume level 0 to silence. 2020-02-14 23:16:10 -05:00
Thomas Harte
6810a6ee58 Adjusts the AY volume scale.
Hopefully more accurately to model the real thing.
2020-02-14 22:51:20 -05:00
Thomas Harte
65e6c3a9fe Merge pull request #753 from TomHarte/IconForReal
Experimentally introduces an application icon to the README
2020-02-13 23:39:06 -05:00
Thomas Harte
dcbbf988c1 Crops empty space around icon, and increases size slightly. 2020-02-13 23:27:33 -05:00
Thomas Harte
199cafebcf Attempts direct HTML tag for image sizing. 2020-02-13 23:25:26 -05:00
Thomas Harte
555d807d76 Attempt smaller icon. 2020-02-13 23:23:09 -05:00
Thomas Harte
003c6ad11b Fixes image link. 2020-02-13 23:21:48 -05:00
Thomas Harte
dc77d87427 Experiments with a README icon. 2020-02-13 23:20:19 -05:00
Thomas Harte
cfc44cf778 Merge pull request #752 from TomHarte/MacintoshBus
Reduces 8-bit memory access costs for the Macintosh.
2020-02-13 23:03:07 -05:00
Thomas Harte
3df99788ff Removes TODOs, as I think they're probably inappropriate. 2020-02-13 21:19:23 -05:00
Thomas Harte
3600d2d193 Starts switching towards a byte-oriented bus. 2020-02-13 21:14:13 -05:00
Thomas Harte
5f661adb7f Merge pull request #750 from TomHarte/CatchupCap
Avoids trying to paper over huge gaps in running time.
2020-02-12 23:57:01 -05:00
Thomas Harte
109d072cb6 Avoids trying to paper over huge gaps in running time. Also attempts to improve SDL shutdown reliability. 2020-02-12 23:47:04 -05:00
Thomas Harte
0c1c5a0ab8 Merge pull request #749 from TomHarte/DiskIndicator
Moves ownership of drives inside Disk::Controller.
2020-02-12 23:37:32 -05:00
Thomas Harte
e01c66fd65 Implements multidrive support. 2020-02-12 23:32:01 -05:00
Thomas Harte
9f32fa7f5b Resolves potential random RAM writes at startup. 2020-02-12 23:31:48 -05:00
Thomas Harte
91a3d42919 Ensures no DMA clocking whatsoever when asleep. 2020-02-12 23:23:42 -05:00
Thomas Harte
3cb6bbf771 Uses the union of all drive statuses to determine Drive::Controller's preferred clocking. 2020-02-12 22:28:42 -05:00
Thomas Harte
452e281009 Ensures what is currently the only drive is selected. 2020-02-11 22:13:13 -05:00
Thomas Harte
3da948db52 Eliminates local drive. They're not local any more. 2020-02-11 22:12:54 -05:00
Thomas Harte
0c2f77305f Eliminates dangling printf. 2020-02-11 22:12:30 -05:00
Thomas Harte
05bcd73f82 Attempts to pull drive ownership into DiskController.
For the sake of being more intelligent as to drive clocking, hopefully. And, eventually, to support multiple drive selection.
2020-02-11 21:59:13 -05:00
Thomas Harte
654f5b0478 Merge pull request #748 from TomHarte/SDLLatency
Introduces sync matching to the SDL version.
2020-02-10 23:38:24 -05:00
Thomas Harte
886d923e30 Attempts to permit fixed speed multiplication. 2020-02-10 23:30:32 -05:00
Thomas Harte
6624cb7a78 Corrects set_ram macro to act more like a function. 2020-02-10 23:19:47 -05:00
Thomas Harte
6147134423 Introduces frame locking for SDL. 2020-02-10 23:07:09 -05:00
Thomas Harte
bf6bc7c684 Adds speed control into the SDL build.
If I can just figure out how to manipulate OpenGL from the timer thread to SDL's satisfaction, this'll be as good as it probably gets via SDL.
2020-02-09 22:27:02 -05:00
Thomas Harte
0b0a7e241b Factors out the stuff of time warping. 2020-02-09 22:11:06 -05:00
Thomas Harte
705d14259c Experimentally switches to a 'high-resolution' clock for SDL. 2020-02-09 21:44:55 -05:00
Thomas Harte
f1cd35fa16 Merge pull request #746 from TomHarte/LatencyChop
Reduces latency in macOS, improves concurrency
2020-02-09 21:07:40 -05:00
Thomas Harte
6bda4034c6 Ensures no input data is dropped when changing output rates.
I think this 'completely' deals with the problem. At least until someone wants dynamic output buffer sizes or something like that. We'll see.
2020-02-09 19:14:25 -05:00
Thomas Harte
b04daca98e Picks a safer default construction. 2020-02-09 19:13:21 -05:00
Thomas Harte
85dcdbfe9e Adopts a log prefix for the Master System. 2020-02-09 19:12:44 -05:00
Thomas Harte
24340d1d4f Resolves fetch errors. 2020-02-09 17:04:49 -05:00
Thomas Harte
6ae42d07a7 Retains existing output when switching filter coefficients.
This eliminates an issue with dynamic rate matching and throwing away the beginnings of buffers.
2020-02-09 16:42:07 -05:00
Thomas Harte
2ea1e059a8 Softens swings in emulated machine speed. 2020-02-09 16:34:13 -05:00
Thomas Harte
b5d6126a2d Avoids unnecessary filter recalculation. 2020-02-09 16:32:32 -05:00
Thomas Harte
dac217c98c Defers starting the macOS audio queue, and attempts to restart it upon packet loss.
Hopefully forever to vanish permanent audio loss?
2020-02-08 22:08:27 -05:00
Thomas Harte
c26c8992ae Reintroduces joystick support; eliminates CSBestEffortUpdater. 2020-02-08 21:27:04 -05:00
Thomas Harte
b76a5870b3 Moves drawing into the next timer tick after retrace if sync locked.
... which should mean it occurs within 1/600th of a second of announced retrace, which I assume always will be less than the retrace period. So: does the frame buffer update during retrace.

This should completely eliminate tearing for machines that can be synced to the native output rate.
2020-02-08 18:07:13 -05:00
Thomas Harte
7c0f3bb237 Gets to slightly adjusting execution speed and matching up respective vertical syncs.
I probably still need to move the ->draw inline.
2020-02-08 18:01:48 -05:00
Thomas Harte
f615d096ca Switch to obtaining refresh periods ephemerally.
Which simplifies the necessary delegate protocol.
2020-02-08 15:03:18 -05:00
Thomas Harte
09132306e4 Removes two temporary debugging steps. 2020-02-06 23:35:23 -05:00
Thomas Harte
f95b07efea Continues edging towards raster racing and/or time warping. 2020-02-06 23:35:03 -05:00
Thomas Harte
14d976eecb Starts towards an implementation of time warping. 2020-02-04 23:08:54 -05:00
Thomas Harte
e1cbad0b6d Ensures new displayLinkDelegates get a nudge with the initial display link. 2020-02-04 23:08:25 -05:00
Thomas Harte
e7410b8ed8 Uses objective clock for updates. 2020-02-04 22:24:54 -05:00
Thomas Harte
5caf74b930 Corrects typo. 2020-02-04 22:24:37 -05:00
Thomas Harte
b41920990f Moves submit step to end of line, rather than end of scan. 2020-02-04 22:15:20 -05:00
Thomas Harte
709c229cd7 Gets a bit more explicit with ScanTarget documentation. 2020-02-04 20:19:46 -05:00
Thomas Harte
01fd1b1a2e Pulls out ticks as a macro constant.
For playing.
2020-02-03 22:44:39 -05:00
Thomas Harte
96769c52f6 Prevents an endless queue of backlogged updates. 2020-02-03 22:08:07 -05:00
Thomas Harte
cf9729c74f Takes a first shot at running OpenGL work throughout a frame.
Rather than en masse at the end. But it seems I've been lazy with my threading. Work to do!
2020-02-03 21:58:29 -05:00
Thomas Harte
0f2783075f Moves responsibility for timed updates to CSMachine, which gives the CSHighPrecisionTimer a shot. 2020-02-02 21:39:20 -05:00
Thomas Harte
256f4a6679 Fixes -invalidate: cancel the dispatch source, don't just suspend it, and wait until that is done. 2020-02-02 21:29:22 -05:00
Thomas Harte
0310f94f0c Merge pull request #745 from TomHarte/STMonochrome
Adds some amount of 1bpp/72Hz output support for the Atari ST.
2020-02-02 17:46:36 -05:00
Thomas Harte
085529ed72 Makes the shifter behaviour conform to its documentation. 2020-02-02 17:26:39 -05:00
Thomas Harte
8aabf1b374 Allows receivers of nullptr from begin_data to output any quantity of data. 2020-02-01 21:43:48 -05:00
Thomas Harte
ff39f71ca0 Eliminates meaningless constants from the Macintosh video's CRT setup. 2020-01-30 23:29:04 -05:00
Thomas Harte
019474300d Centralises responsibility for picking irrelevant numbers for a computer-style monitor. 2020-01-30 23:26:02 -05:00
Thomas Harte
af976b8b3d Eliminates modulus operation per ROM access. 2020-01-30 23:09:24 -05:00
Thomas Harte
f3db1a0c60 Eliminates ad hoc scheduling for delayed DE -> LOAD. 2020-01-29 22:50:22 -05:00
Thomas Harte
ce28213a5e [Mostly] unifies deferral process. 2020-01-29 22:46:08 -05:00
Thomas Harte
f9ce50d2bb Adds some debugging `asserts. 2020-01-29 22:45:44 -05:00
Thomas Harte
ee16095863 Withdraws advance_to_next; once it has to cope with simultaneous events it stops being faster than advance.
I could possibly try to deal with those at insertion time, but it'd get messy.
2020-01-29 22:45:10 -05:00
Thomas Harte
f0a6e0f3d5 Splits out the queue management stuff from queue+action.
Temporarily breaks ST video in the endeavour.
2020-01-29 22:18:41 -05:00
Thomas Harte
8c4fb0f688 Extends the DeferredQueue to allow out-of-order enqueing. 2020-01-29 21:49:52 -05:00
Thomas Harte
baa51853c4 Introduces RealTimeActor, providing the same interface as JustInTimeActor. 2020-01-29 21:26:15 -05:00
Thomas Harte
0e29c6b0ab On further reflection, I think events should occur after the running period.
I'm testing this now for sanity in 2/4bpp mode.
2020-01-28 23:26:37 -05:00
Thomas Harte
1b27eedf6b Ensure this can definitely never divide by 0. 2020-01-28 23:25:21 -05:00
Thomas Harte
8b1f183198 Reduce test duration much closer to two frames. 2020-01-28 23:25:01 -05:00
Thomas Harte
4766ec55fe Documents units. 2020-01-28 23:23:51 -05:00
Thomas Harte
c5edc879b6 Switches back to testing the monochrome monitor. 2020-01-28 22:12:57 -05:00
Thomas Harte
65309e60c4 Corrects sequence point generation by allowing for hsync_end != end of line. 2020-01-28 20:38:20 -05:00
Thomas Harte
5c4623e9f7 Adds a sequence-point test for 72Hz mode.
Which immediately appears to trigger the hsync issue I'm also seeing in manual testing.
2020-01-28 20:27:24 -05:00
Thomas Harte
2c0cab9e4d Adds line length latching as a line event. 2020-01-28 20:22:37 -05:00
Thomas Harte
d0117556d1 Reintroduces CSHighPrecisionTimer. 2020-01-28 20:09:46 -05:00
Thomas Harte
b1ff031b54 Fixes runtime test. 2020-01-27 23:41:08 -05:00
Thomas Harte
7e8405e68a Makes 72Hz horizontal sync independently relocatable.
... and moves and shortens it, based on my guesswork as to requirements.
2020-01-27 23:40:01 -05:00
Thomas Harte
c8fd00217d Resolves loss of horizontal resolution in 1bpp mode. 2020-01-27 23:08:28 -05:00
Thomas Harte
9d340599a6 Towards ST 1bpp support: puts vsync in an appropriate location, starts experimenting with proper CRT timings. 2020-01-27 23:00:30 -05:00
Thomas Harte
8e094598ca Merge pull request #744 from TomHarte/CRCTemplate
Better templates the CRC generator.
2020-01-27 21:54:32 -05:00
Thomas Harte
189122ab84 Fixes test units. 2020-01-27 20:35:58 -05:00
Thomas Harte
4b53f6a9f0 Renames T to the more-communicative IntType, adds some explicit constexpra. 2020-01-27 08:28:20 -05:00
Thomas Harte
561e149058 Better templates the CRC generator. 2020-01-27 00:03:01 -05:00
Thomas Harte
5975fc8e63 Merge pull request #738 from TomHarte/FinalOverride
Switches to [just] `final` where relevant to mark overrides.
2020-01-26 23:50:05 -05:00
Thomas Harte
7316a3aa88 Merge branch 'master' into FinalOverride 2020-01-26 23:42:25 -05:00
Thomas Harte
50be991415 Merge pull request #743 from TomHarte/macOSScreens
Ensures macOS window can properly be dragged between screens, Retina or not.
2020-01-26 23:33:50 -05:00
Thomas Harte
52e49439a6 Recreates display link upon a screen change.
Different screens may have different refresh rates, and I can find no guarantees about how Apple handles that.
2020-01-26 23:23:33 -05:00
Thomas Harte
6bcdd3177d Ensures that a change of screen issues a reshape. Just in case.
Thereby resolves display mis-sizing when dragging from a Retina display to a regular one, or vice versa.
2020-01-26 18:04:25 -05:00
Thomas Harte
83dbd257e1 Merge pull request #742 from TomHarte/SpeedMultiplier
Adds the option to run machines at a multiple of their real speeds.
2020-01-26 13:30:43 -05:00
Thomas Harte
b514756272 Adds the option to run machines at a multiple of their real speeds.
Exposed to SDL users only, for now.
2020-01-26 13:25:23 -05:00
Thomas Harte
7e4c13c43e Merge pull request #741 from TomHarte/ZX80RateReporting
Corrects ZX80, ZX81 and Amstrad CPC scan status scales.
2020-01-26 11:42:54 -05:00
Thomas Harte
79bb0f8222 Updates comment. 2020-01-26 11:36:06 -05:00
Thomas Harte
43bf6aca67 Corrects reported scan status for the Amstrad CPC. 2020-01-25 23:46:18 -05:00
Thomas Harte
03d23aad41 Corrects reported ZX80/81 scan status. 2020-01-25 23:27:09 -05:00
Thomas Harte
c398aa60c1 Merge pull request #739 from TomHarte/SDLThreadSafety
Resolves thread safety issues in SDK kiosk mode.
2020-01-25 14:56:29 -05:00
Thomas Harte
9666193c67 Pulls the call to .update out of the critical section. 2020-01-25 14:50:28 -05:00
Thomas Harte
3f57020b00 Resolves thread safety oversights in SDK kiosk mode. 2020-01-25 14:48:00 -05:00
Thomas Harte
294e09f275 All these 'override's can be 'final's.
At least for the purpose of being communicative. I doubt there's much to gain in terms of compiler output — the DiskImageHolder can avoid some virtual lookups but nothing else leaps out.
2020-01-23 22:57:51 -05:00
Thomas Harte
ba516387ba In all these instances, final => override. So no need to repeat myself. 2020-01-23 22:35:39 -05:00
Thomas Harte
2103e1b470 Merge pull request #737 from TomHarte/Multisync
Adds multisync monitor support to the Oric.
2020-01-23 22:20:34 -05:00
Thomas Harte
7bac439e95 Adds, and comments out, a useful temporary piece of debugging logging. 2020-01-23 22:14:22 -05:00
Thomas Harte
9136917f00 Enables the Oric for 50/60Hz mode switching, inventing PAL60 for the purpose. 2020-01-23 22:14:02 -05:00
Thomas Harte
6802318784 Removes audio_queue_.flush() calls; I don't think I really need to block. At least, not usually. 2020-01-23 20:13:16 -05:00
Thomas Harte
428d141bc9 Factors out the logic behind the Atari 2600's frequency switching. 2020-01-23 20:12:44 -05:00
Thomas Harte
a86fb33789 Ensures that the ColecoVision, MSX and Master System fully flush. 2020-01-22 22:57:16 -05:00
Thomas Harte
beefb70f75 Adds vertical sync as something that can be run_until. 2020-01-22 22:20:56 -05:00
Thomas Harte
3c6a00dc3c Breaks a potential deadlock. 2020-01-22 22:10:20 -05:00
Thomas Harte
8404409c0d Causes the Atari 2600 to obey normal flush semantics.
This stuff is going to become more important with run_until.
2020-01-22 22:05:51 -05:00
Thomas Harte
a5f285b4ce Enhances reported data. 2020-01-22 22:01:17 -05:00
Thomas Harte
9d97a294a7 Corrects the TMS' get_scaled_scan_status.
I think all platforms are now returning credible numbers.
2020-01-22 19:34:10 -05:00
Thomas Harte
56448373ae Splits one line into two, for the benefit of step debugging. 2020-01-22 19:32:23 -05:00
Thomas Harte
a71c5946f0 Ensures proper manipulation of scan_statuses, leading to the correct result out of a CRTMachine.
Possibly with the exception of the TMS, as I appear to have uncovered an unrelated issue there.
2020-01-21 22:28:25 -05:00
Thomas Harte
e7fff6e123 Minor step towards correct answers: divide by time_multiplier_. 2020-01-20 22:33:51 -05:00
Thomas Harte
82e5def7c4 Implements get_scan_status, but for scale being incorrect. 2020-01-20 21:58:34 -05:00
Thomas Harte
d97a073d1b Adds the necessary routine for all machines to be able to respond to get_scan_status.
They all just as the CRT, as all are currently based on the CRT. Which doesn't currently know the total clock rate it would need to in order properly to scale the answer to the question. Further thought coming.
2020-01-20 21:45:10 -05:00
Thomas Harte
e74f37d6ed Merge pull request #736 from TomHarte/RunUntil
Implements a nascent `run_until`
2020-01-20 17:48:13 -05:00
Thomas Harte
3aa2c297a2 Adds feedback to the best-effort updater; enables the Cocoa port for audio event requests. 2020-01-20 17:38:25 -05:00
Thomas Harte
290db67f09 Adds a forward route for event flags. Doesn't yet account for extra time expended. 2020-01-20 17:09:01 -05:00
Thomas Harte
4de121142b Adds a flags parameter to the BestEffortUpdater delegate.
On the Cocoa side, cuts Swift out of the update loop, as that seems merely to add code.
2020-01-20 16:21:53 -05:00
Thomas Harte
3c760e585a Switches to accepting a bit mask of events to run_until. 2020-01-20 16:08:21 -05:00
Thomas Harte
8adb2283b5 Rewrites the best-effort updater to try to get better thread affinity. 2020-01-20 13:38:46 -05:00
Thomas Harte
cb61e84868 Starts building out higher-level run_until functionality.
Specifically: you can now run until the next set of speaker samples has been delivered.
2020-01-20 12:12:23 -05:00
Thomas Harte
8349005c4b Adds CRTMachine::run_until, which will run until a condition is true.
I want to get to being able to say "run until the beam is 60% of the way down", "run until a new packet of audio has been delivered", etc.
2020-01-19 23:52:47 -05:00
Thomas Harte
a2847f4f8e Merge pull request #735 from TomHarte/Numeric
Eliminates homegrown factoring code
2020-01-19 23:32:50 -05:00
Thomas Harte
add3ebcb44 Updates Xcode project. 2020-01-19 23:23:44 -05:00
Thomas Harte
98daad45c7 Removers Factors.hpp; now this is a C++17 project. 2020-01-19 23:18:59 -05:00
Thomas Harte
1b4b6b0aee Renames: NumberTheory -> Numeric. 2020-01-19 23:14:35 -05:00
Thomas Harte
8f94da9daf Merge pull request #734 from TomHarte/FuzzyBits
Adds PCMSegementEventSource support for 'fuzzy' bits
2020-01-19 21:48:07 -05:00
Thomas Harte
357137918d Adds fuzzy but marking through the GetTrackWithSectors interface. 2020-01-19 21:41:10 -05:00
Thomas Harte
b0f7b762af Adds a possible const. 2020-01-19 21:40:30 -05:00
Thomas Harte
da3ee381f4 Attempts a full wiring up of fuzzy bits. 2020-01-19 21:20:21 -05:00
Thomas Harte
d27d14d2b0 Supplies fuzzy masks where specified. 2020-01-19 21:08:49 -05:00
Thomas Harte
b0326530d6 Allows fuzzy masks to be fed into the FM and MFM encoders. 2020-01-19 21:08:15 -05:00
Thomas Harte
c2bd5be51a This seems to be the proper interpretation of speeds? 2020-01-19 20:42:51 -05:00
Thomas Harte
84f5feab70 Properly flags up overloads. 2020-01-19 20:37:54 -05:00
Thomas Harte
4b2c68c3d3 Documents next. 2020-01-19 20:32:58 -05:00
Thomas Harte
5391a699a4 Adds the ability for a PCMSegment to maintain 'fuzzy' (i.e. random) bits. Implements an LFSR for bit generation.
I'm not necessarily happy with the idea of just shoving in a [pseudo-]random number generator in rather than emulating the proper process underneath, but for now I throw my arms up.
2020-01-19 20:09:11 -05:00
Thomas Harte
f3f8345e5e Corrects spelling mistake. 2020-01-19 20:05:52 -05:00
Thomas Harte
c755411636 Slightly improves comments. 2020-01-19 20:05:22 -05:00
Thomas Harte
f02759b76b Merge pull request #733 from TomHarte/STXTiming
Adds support for STX speed zones.
2020-01-19 12:41:31 -05:00
Thomas Harte
f34ddce28f Adds support for STX speed zones. 2020-01-19 12:38:33 -05:00
Thomas Harte
50348c9fe7 Adds the ability to substitute a target during encoding. 2020-01-19 12:11:56 -05:00
Thomas Harte
3bfeebf2a1 Merge pull request #732 from TomHarte/TraceFlag
Improves 68000 trace support
2020-01-18 23:17:21 -05:00
Thomas Harte
dca79ea10e Requires trace flag currently set. 2020-01-18 22:52:53 -05:00
Thomas Harte
b7fd4de32f Ensures a one-instruction latency on the trace flag. 2020-01-18 22:06:00 -05:00
Thomas Harte
78d08278ed Merge pull request #731 from TomHarte/ShifterSync
Improves STX track locating, plus minor WD emulation improvements
2020-01-18 14:59:07 -05:00
Thomas Harte
d4be052e76 Switch to matching fragments. 2020-01-18 14:18:59 -05:00
Thomas Harte
d674fd0e67 The WD uses only the low two bits for sector size. 2020-01-18 13:40:50 -05:00
Thomas Harte
229b7b36ed Merge branch 'master' into ShifterSync 2020-01-18 13:38:56 -05:00
Thomas Harte
8a8b8db5d1 Merge pull request #729 from TomHarte/JasminLED
Corrects Jasmin activity light.
2020-01-16 23:16:55 -05:00
Thomas Harte
d30f83871d Corrects Jasmin activity light. 2020-01-16 22:59:43 -05:00
Thomas Harte
1422f8a93a Merge pull request #728 from TomHarte/HardenedRuntime
Opts in for the hardened macOS runtime.
2020-01-16 22:27:58 -05:00
Thomas Harte
f0da75f8e9 Opts in for the hardened macOS runtime.
Seemingly with no ill effects.
2020-01-16 22:18:18 -05:00
Thomas Harte
cb8a7a4137 Merge pull request #727 from TomHarte/RDYs
Adds emulation of non-default types of floppy drive RDY output
2020-01-16 22:07:41 -05:00
Thomas Harte
efd684dc56 Opts the BD-500 in for modified Shugart RDY.
Hopefully this is correct. I'm presently mystified as to other options.
2020-01-16 21:34:57 -05:00
Thomas Harte
aeac6b5888 Allows the type of RDY signal to be specified. 2020-01-16 21:34:48 -05:00
Thomas Harte
9bb294a023 Merge pull request #726 from TomHarte/BD-DOS
Implements highly-provisional Byte Drive 500 support for the Oric.
2020-01-16 00:09:34 -05:00
Thomas Harte
1972ca00a4 Fixes quick-NTSC-avoidance fix.
I suspect this is very temporary, but here it is.
2020-01-16 00:01:16 -05:00
Thomas Harte
6a185a574a Adds the BD-500 to the Mac GUI. 2020-01-15 23:56:56 -05:00
Thomas Harte
c606931c93 Ensures a safe default-selected drive. 2020-01-15 23:56:44 -05:00
Thomas Harte
93cecf0882 Ensures no possible initial NTSC, removes printfs. 2020-01-15 23:47:45 -05:00
Thomas Harte
aac3d27c10 Adds activity indicators for the BD-500 and Jasmin.
Also slightly cleans up DiskController a little further.
2020-01-15 23:39:15 -05:00
Thomas Harte
99122efbbc Adds a slight cool-down period on end-of-rotation.
Along with the corresponding inactive transition of the ready signal.
2020-01-15 23:29:52 -05:00
Thomas Harte
30e856b9e4 Renames motor_is_on_ to motor_input_is_on_ to start to disambiguate the two things. 2020-01-15 23:16:25 -05:00
Thomas Harte
91fae86e73 Factors out paging, implements a bit more of the BD500.
That is, enough seemingly fully to work, if I force the drive to report ready.
2020-01-15 23:15:39 -05:00
Thomas Harte
f5c194386c Ties head load to ready.
BD-DOS no longer perpetually retries.
2020-01-14 23:45:36 -05:00
Thomas Harte
98f7662185 Force BASIC 1.0 for the BD-500. 2020-01-14 23:33:52 -05:00
Thomas Harte
62c3720c97 Adds status register and shout-outs on other address access. 2020-01-14 23:24:11 -05:00
Thomas Harte
6b08239199 Adapts slightly; it would seem that BD-DOS disks really fill up space. 2020-01-14 23:16:06 -05:00
Thomas Harte
f258fc2971 Adds enough of a BD500 for the boot sector seemingly to load. 2020-01-14 23:15:27 -05:00
Thomas Harte
6b84ae3095 Makes the Microdisc also a DiskController, and simplifies delegate interface. 2020-01-14 22:53:27 -05:00
Thomas Harte
5dd8c677f1 Factors out from the Jasmin the stuff that I'm going to need to repeat for the BD-500. 2020-01-14 22:23:00 -05:00
Thomas Harte
1cbcd5355f Adds a detector and enumerated Byte Drive 500 disk interface type. 2020-01-14 21:55:04 -05:00
Thomas Harte
9799250f2c Updates to mention the Jasmin's ROM and list the BD-DOS. 2020-01-14 21:54:37 -05:00
Thomas Harte
ecb5807ec0 Enssures STX interprets sector sizes correctly. 2020-01-14 21:35:37 -05:00
Thomas Harte
942986aadc Insures against badly-placed locations. 2020-01-13 22:49:12 -05:00
Thomas Harte
1f539822ee Adds better support for WD-esque false sync, improves STX track patching. 2020-01-13 22:19:48 -05:00
Thomas Harte
fab35b360a Ensure an encoder is created even if no sectors are placed. 2020-01-12 22:37:00 -05:00
Thomas Harte
80fcf5b5c0 Merge pull request #724 from TomHarte/STX2
Adds some support for the STX file format.
2020-01-12 22:28:50 -05:00
Thomas Harte
b3b2e18c4b Ensures head and track counts are reported accurately. 2020-01-12 22:23:34 -05:00
Thomas Harte
2d233b6358 Makes a more concrete attempt at track/sector combination. 2020-01-12 22:18:31 -05:00
Thomas Harte
83ed36eb08 Add missing #include. 2020-01-12 17:56:04 -05:00
Thomas Harte
89f4032ffc Merge branch 'master' into STX2 2020-01-12 17:55:19 -05:00
Thomas Harte
8c90ec4636 Merge pull request #725 from TomHarte/FasterDPLL
Improve DPLL implementation.
2020-01-12 17:54:55 -05:00
Thomas Harte
514141f8c5 Eliminates the optionality of a DPLL receiver. 2020-01-12 17:45:02 -05:00
Thomas Harte
8e3a618619 Corrects Mac build, shrinks default history [back] to 3 slots. 2020-01-12 17:33:34 -05:00
Thomas Harte
6df6af09de Remove dead .cpp. 2020-01-12 17:25:59 -05:00
Thomas Harte
f42655a0fc Promote DigitalPhaseLockedLoop to a template, simplify to O(1) add_pulse. 2020-01-12 17:25:21 -05:00
Thomas Harte
f81a7f0faf Ensures prefixes are MFM encoded and decoded. 2020-01-11 22:10:41 -05:00
Thomas Harte
2b4c924399 Makes an effort to locate address and data bodies within track.
"Not completely successful" would be the polite term.
2020-01-09 23:28:07 -05:00
Thomas Harte
64517a02b7 Adds code to deal with sector-free tracks. 2020-01-09 21:50:32 -05:00
Thomas Harte
b4befd57a9 Advances to being able to cope with STXs with no special features whatsoever.
Well, other than perhaps a broken data CRC. Fuzzy bits, timing differences and the stuff between sectors are all currently absent.
2020-01-09 21:03:01 -05:00
Thomas Harte
2c742a051e Merge pull request #723 from TomHarte/LSLTiming
Introduces a timing test for LSL. Which already passes.
2020-01-08 22:43:50 -05:00
Thomas Harte
6595f8f527 Introduces a timing test for LSL. Which already passes. 2020-01-08 22:35:28 -05:00
Thomas Harte
985b36da73 Starts towards STX support. 2020-01-07 23:21:32 -05:00
Thomas Harte
cdb31b1c2b Merge pull request #721 from TomHarte/AYZero
Ensures programmatic AY volume level 0 is completely off.
2020-01-05 22:50:42 -05:00
Thomas Harte
6a44936a7c Ensures programmatic volume level 0 is completely off. 2020-01-05 22:44:52 -05:00
Thomas Harte
45afb13a54 Merge pull request #720 from TomHarte/Jasmin
Adds emulation of the Oric's Jasmin disk interface.
2020-01-05 22:13:11 -05:00
Thomas Harte
3ced31043a Makes Jasmin autoboot optional, adds a Jasmin reset key, adds the Jasmin to File -> New... .
Also finally implements KeyNMI.
2020-01-05 21:57:33 -05:00
Thomas Harte
7361e7ec34 Fixed: the issue was failing to propagate motor control.
Also it seems to be incorrect to have the Jasmin paged at initial boot.
2020-01-05 21:35:20 -05:00
Thomas Harte
533729638c It seems like Jasmin paged in at boot, and button = page back in and reset works?
At least, that gets me a 'boot failed' error. Which is something.
2020-01-05 20:34:15 -05:00
Thomas Harte
9f30be1c13 Attempts to implement most of a Jasmin disk interface.
With one obvious omission: there's no way to start it? The real interface had a dedicated button, but I don't yet know what that button did. Research needed.
2020-01-05 20:05:55 -05:00
Thomas Harte
09289f383d Makes an effort to detect Jasmin disks, and flags the target if found.
It'll try the Jasmin only if the Microdisc check fails; this is because the latter is preferable — it automatically boots, and is much better tested in Clock Signal terms.
2020-01-05 18:44:58 -05:00
Thomas Harte
20b25ce866 Merge pull request #719 from TomHarte/CleanUps
Standardises on `read` and `write` for bus accesses.
2020-01-05 13:59:02 -05:00
Thomas Harte
c1bae49a92 Standardises on read and write for bus accesses.
Logic being: name these things for the bus action they model, not the effect they have.
2020-01-05 13:40:02 -05:00
Thomas Harte
b3f806201b Merge pull request #718 from TomHarte/BusErrorStack
Adds a test and fixes for the bus error stack frame.
2020-01-05 00:08:53 -05:00
Thomas Harte
9f2f547932 Adds and satisfies test on the function code word.
Thanks to ijor's "68000 Address and Bus Error Stack Frame" re: contents.
2020-01-04 23:58:07 -05:00
Thomas Harte
f0d5bbecf2 Introduces a test of stack contents after an address error.
Fixes: stacked PC, address of fault.
2020-01-04 23:22:07 -05:00
Thomas Harte
3d7ef43293 Merge pull request #717 from TomHarte/JSRA7
Fixes A7-relative JSRs.
2020-01-04 22:29:04 -05:00
Thomas Harte
4578b65487 Merge pull request #716 from TomHarte/PartialDecoding
Clarifies IO decoding, adds YM mirrors.
2020-01-04 22:23:22 -05:00
Thomas Harte
a28c52c250 Fixes A7-relative JSRs.
I completely withdraw my earlier statement re: the test cases.
2020-01-04 22:22:33 -05:00
Thomas Harte
e4349f5e05 Slightly clarifies logic. 2020-01-04 21:32:34 -05:00
Thomas Harte
7b2777ac08 Sorts cases into order; adds copious audio mirrors. 2020-01-04 21:06:21 -05:00
Thomas Harte
0fbcbfc61b Switches to more idiomatic address listing. 2020-01-04 20:35:47 -05:00
Thomas Harte
3ab4fb8c79 Enables an assumption of partial address decoding at the ACIA and PSG. 2020-01-04 17:27:55 -05:00
Thomas Harte
42a9585321 Merge pull request #715 from TomHarte/TestsRedux
After rerunning all tests, adds some notes on questionable results.
2020-01-04 16:41:01 -05:00
Thomas Harte
937cba8978 After rerunning all tests, adds some notes on questionable results.
Also renames a file. But no code changes are currently suggested, at least until I can learn more about DIVU/DIVS.
2020-01-04 16:31:45 -05:00
Thomas Harte
627d3c28ea Merge pull request #714 from TomHarte/STJoystick
Adds Joystick key code mode, ensures events aren't posted in interrogation mode.
2020-01-04 10:01:57 -05:00
Thomas Harte
19ddfae6d6 Adds Joystick key code mode, ensures events aren't posted in interrogation mode.
This should fix Turrican due to the latter change; I'm not aware of software that uses the former.
2020-01-04 09:45:59 -05:00
Thomas Harte
56ebd08af0 Merge pull request #713 from TomHarte/MULUS
Adds DIV and MUL tests, correcting some DIV flags.
2020-01-04 09:22:03 -05:00
Thomas Harte
7de1181213 Make a new guess at post-overflow DIV flags, based on tests.
Specifically: for DIVU, stick with the current guess of a fixed set. For DIVS, leave N and Z alone.
2020-01-03 23:44:49 -05:00
Thomas Harte
c7a5b054db There's no TODO here; overflow is always 0 for a 16x16 multiply.
... and the original 68000 doesn't support 32x32 multiplies.
2020-01-03 22:44:19 -05:00
Thomas Harte
ca12ba297b Renames all files that test multiple opcodes; introduces DIV and MUL tests. 2020-01-03 22:43:24 -05:00
Thomas Harte
7abf527084 Merge pull request #712 from TomHarte/MercsTweaks
Corrects vsync placement and BPP-change pipeline flushing.
2020-01-02 23:50:30 -05:00
Thomas Harte
c0b5bfe726 Ensure no possible return without value. 2020-01-02 23:43:53 -05:00
Thomas Harte
414b0cc234 Reintroduces sync write delay. 2020-01-02 23:36:11 -05:00
Thomas Harte
134e828336 Updates note to self. 2020-01-02 23:33:35 -05:00
Thomas Harte
455e831b87 Corrects bug whereby changing pixel mode mid-line will produce an improper amount of data. 2020-01-02 23:18:21 -05:00
Thomas Harte
617e0bada9 Adds some minor extra testing. Highly duplicative, to be honest. 2020-01-02 23:14:05 -05:00
Thomas Harte
7dea99b1cc Update comment, for sense. 2020-01-02 23:13:12 -05:00
Thomas Harte
42ccf48966 Judging by Pompey Pirates Menu 88, vsync should occur a line earlier, ending during line 0. 2020-01-02 20:16:28 -05:00
Thomas Harte
2f8078db22 Switches to should_log as a global when I'm hacking about. 2020-01-02 20:15:48 -05:00
Thomas Harte
ea45ae78d1 Merge pull request #711 from TomHarte/MoreTests
Introduces further comparative tests, prompting a new CHK fix.
2020-01-01 20:12:07 -05:00
Thomas Harte
cb7d6c185c Further expands test coverage. 2020-01-01 20:00:37 -05:00
Thomas Harte
5be30b1f7b Introduces further comparative tests, prompting a new CHK fix.
Specifically: how to set N when both is_under and is_over are true, and to eliminate a failure fully to prefetch in the longer addressing modes.
2020-01-01 19:11:36 -05:00
Thomas Harte
0bf1a87f4c Merge pull request #710 from TomHarte/STOP
Ensure that an interrupt from a STOP doesn't return to the STOP.
2020-01-01 15:00:11 -05:00
Thomas Harte
b184426f2b Ensure that an interrupt from a STOP doesn't return to the STOP. 2020-01-01 14:51:47 -05:00
Thomas Harte
2456fb120d Merge pull request #709 from TomHarte/MoreMouse
Adds Atari ST mouse support for absolute positioning and inverted scales.
2020-01-01 13:56:25 -05:00
Thomas Harte
23ed9ad2de Corrects application of negative relative scale. 2020-01-01 13:22:21 -05:00
Thomas Harte
017681a97c Now honours permitted mouse range. 2020-01-01 12:48:38 -05:00
Thomas Harte
153f60735d Banishes redefined macro warning. 2020-01-01 12:38:30 -05:00
Thomas Harte
90b899c00e Attempts to implement absolute mouse positioning mode.
Along with mouse direction.
2020-01-01 12:29:33 -05:00
Thomas Harte
5ce8d7c0e5 Merge pull request #708 from TomHarte/KeyboardLogs
Adds necessary logging for further IKYB work.
2019-12-30 23:41:38 -05:00
Thomas Harte
c11fe25537 Merge branch 'master' into EnchantedWoods 2019-12-30 23:32:45 -05:00
Thomas Harte
c4edd635c5 Merge pull request #707 from TomHarte/STGraphicsAgain
Further improves the ST graphics subsystem
2019-12-30 23:31:21 -05:00
Thomas Harte
0a12893d63 Shunts vsync back down to top of frame.
It's guess after guess, basically.
2019-12-30 23:01:31 -05:00
Thomas Harte
8e777c299f Switches to latching video interrupts until acknowledged.
Seems to fix Cisco Heat, at least. I have no idea whether I'm latching the correct thing, whether IACK should clear both or only one, etc.
2019-12-30 23:00:55 -05:00
Thomas Harte
09513ec14c Gets explicit about constexpr expectations here. 2019-12-30 22:58:19 -05:00
Thomas Harte
e23d1a2958 Restores vsync active. 2019-12-29 22:03:36 -05:00
Thomas Harte
6449403f6a Corrects pending_events_ test for sequence points.
Simplifies around as possible.
2019-12-29 21:53:45 -05:00
Thomas Harte
c8fe66092b Attempts to correct insertion logic (and mostly bypasses it). 2019-12-29 21:42:41 -05:00
Thomas Harte
b33218c61e Fixes reload test, which really needs to sense the CRT-headed vsync output.
i.e. not the one heading back to the CPU.
2019-12-29 20:55:34 -05:00
Thomas Harte
8ce26e7182 Adds a delay on vsync visibility (i.e. as to generating an interrupt). 2019-12-29 19:03:08 -05:00
Thomas Harte
47068ee081 Ensures visible hsync end generates a sequence point. 2019-12-29 17:51:50 -05:00
Thomas Harte
5361ee2526 Adds specific Union Demo test. 2019-12-29 17:48:43 -05:00
Thomas Harte
214b6a254a Adds a delay on visibility of the hsync signal, and a test on address reload. 2019-12-29 17:37:09 -05:00
Thomas Harte
93f6964d8a Introduces some preliminary line length unit tests.
Thereby fixes one potential issue with load_ toggling.
2019-12-28 22:50:34 -05:00
Thomas Harte
13f11e071a Simplifies border colour change propagation.
I'm not sure it was even technically correct as was.
2019-12-28 10:45:10 -05:00
Thomas Harte
f7825dd2a2 Pulls out address reload position as a separate constant. 2019-12-28 10:36:50 -05:00
Thomas Harte
a9d1f5d925 Pulls out address reload as something I can position independently.
Sadly receding it by 3 did not have the effect I was hoping for, of receding Enchanted Land's first register tweaking.
2019-12-27 23:47:19 -05:00
Thomas Harte
2757e5d600 Removes untrue comment. 2019-12-27 22:51:11 -05:00
Thomas Harte
5026de9653 Rejigs the video stream to ensure shifter really is continuous.
... and definitively to avoid potential buffer overruns. Or, at least, to have a mechanism in place definitively to avoid them. Which will be tested and debugged as necessary.

Also simplifies the colour burst and border/pixels selection logic.
2019-12-27 22:47:34 -05:00
Thomas Harte
5fa8e046d8 It's inaccurrate to call this _the_ shifter. So don't. 2019-12-27 19:03:10 -05:00
Thomas Harte
ec9357e080 Merge pull request #706 from TomHarte/Vic20Flags
Permits Vic-20 memory to be specified in banks;
2019-12-26 23:04:56 -05:00
Thomas Harte
f8dd33b645 Adds necessary header for strcmp. 2019-12-26 22:53:09 -05:00
Thomas Harte
de43e86310 Permits Vic-20 memory to be specified in banks; adds recognition of TheC64-style file tags to specify them. 2019-12-26 22:49:48 -05:00
Thomas Harte
314973a5ef Merge pull request #704 from TomHarte/RTR
Corrects implementation of RTR
2019-12-25 20:47:21 -05:00
Thomas Harte
d26ce65236 Introduces an RTR test. 2019-12-25 19:50:12 -05:00
Thomas Harte
1de4f179c0 Adds more thorough comment on the bus program used. 2019-12-25 19:49:49 -05:00
Thomas Harte
3cb5684d95 Fixes RTR: the whole top half of the SR should be preserved.
Specifically, the 68000 Reference Manual says: "The supervisor portion of the status register is unaffected." Clearly when I first read that I misread it as the supervisor _flag_ (rather than _portion_) should be preserved.
2019-12-25 19:49:20 -05:00
Thomas Harte
a9a92de954 Adds a bunch of shout-outs for unimplemented behaviour. 2019-12-25 15:32:33 -05:00
Thomas Harte
daacd6805e Merge pull request #703 from TomHarte/Sup133
Fixed: the final track field in an MSA is inclusive, not exclusive.
2019-12-24 23:30:04 -05:00
Thomas Harte
54fe01b532 Fixed: the final track is inclusive, not exclusive. 2019-12-24 23:08:16 -05:00
Thomas Harte
42dd70dbff Merge pull request #702 from TomHarte/NZStory
Corrects WD track-zero and write-protect flags.
2019-12-24 22:22:24 -05:00
Thomas Harte
e59de71d79 Disables status logging, at least until next needed. 2019-12-24 21:44:50 -05:00
Thomas Harte
a8ba3607b7 Adds (and disables) a minor additional piece of logging. 2019-12-24 21:43:39 -05:00
Thomas Harte
4205e95883 Switches to capture of the track 0 flag during a type 1 operation. 2019-12-24 21:43:20 -05:00
Thomas Harte
f633cf4c3f Adds a basic implementation of the non-instantaneous index pulse. 2019-12-24 21:05:17 -05:00
Thomas Harte
dfa6b11737 Adds responsibility for an ongoing index pulse to the drive. 2019-12-24 20:53:37 -05:00
Thomas Harte
42926e72cc Adjusted: Flag::WriteProtect works in real time for a type-1 status. 2019-12-24 19:57:12 -05:00
Thomas Harte
80cb06eb33 It provisionally seems as though spin_up should be reset by a force interrupt? 2019-12-24 19:37:37 -05:00
Thomas Harte
5068328a15 Fixes debugging output. 2019-12-24 19:15:58 -05:00
Thomas Harte
adc2b77833 Enhances with constexpr. 2019-12-24 18:53:50 -05:00
Thomas Harte
99415217dc Merge pull request #701 from TomHarte/TestsSyntax
Corrects syntax errors in test suite.
2019-12-23 22:15:16 -05:00
Thomas Harte
48d519d475 Merge branch 'master' into TestsSyntax 2019-12-23 22:13:55 -05:00
Thomas Harte
ed831e5912 Fixes test syntax errors. 2019-12-23 22:13:25 -05:00
Thomas Harte
1db7c7989b Merge pull request #700 from TomHarte/NoNew
Embraces std::make_[unique/shared] in place of .reset(new .
2019-12-23 22:05:57 -05:00
Thomas Harte
b2bed82da6 Switches to standard logging. 2019-12-23 22:00:40 -05:00
Thomas Harte
afae1443b4 Merge branch 'master' into NoNew 2019-12-23 21:32:17 -05:00
Thomas Harte
0dae608da5 Embraces std::make_[unique/shared] in place of .reset(new . 2019-12-23 21:31:46 -05:00
Thomas Harte
8a1fe99fa4 Merge pull request #699 from TomHarte/SpuriousCRCErrors
Ensure the WD won't confuse sector contents for header content.
2019-12-23 21:15:15 -05:00
Thomas Harte
ac604b30f3 Eliminates dangling static_casts in favour of construction. 2019-12-22 20:59:20 -05:00
Thomas Harte
b035b92f33 Corrects accidental use of sector contents as addresses in multi-sector reads and writes.
As a secondary defect, this was also causing erroneous CRC error reports.
2019-12-22 19:58:02 -05:00
Thomas Harte
d25b48878c Cleans up READ_ID macro, inter alia. 2019-12-22 17:58:33 -05:00
Thomas Harte
34a3790e11 Minor static_cast clean-ups. 2019-12-22 17:56:59 -05:00
Thomas Harte
f3378f3e3e Merge pull request #698 from TomHarte/MoreScreenshots
Adds many additional screenshots.
2019-12-22 14:43:08 -05:00
Thomas Harte
78accc1db1 Seeks to fix macOS desktop picture. 2019-12-22 14:41:13 -05:00
Thomas Harte
a756985e18 Makes a further attempt at this table. 2019-12-22 14:40:02 -05:00
Thomas Harte
30e0d4aa30 Attempts a table fix. 2019-12-22 14:37:59 -05:00
Thomas Harte
de72c66c64 Adds a full image gallery, trying to hit every supported system.
... that isn't already pictured, that is.
2019-12-22 14:36:33 -05:00
Thomas Harte
6edd3c9698 Merge pull request #697 from TomHarte/MoreConstexpr
Further propagates `constexpr`.
2019-12-22 13:53:44 -05:00
Thomas Harte
5456a4a39d Eliminates static where constexpra aren't class members; adds some if constexprs for clarity. 2019-12-22 13:42:24 -05:00
Thomas Harte
66d9b60b98 Merge pull request #696 from TomHarte/make_shared
Makes a variety of minor style improvements
2019-12-22 00:27:57 -05:00
Thomas Harte
274867579b Deploys constexpr as a stricter const. 2019-12-22 00:22:17 -05:00
Thomas Harte
a847654ef2 Corrects various old-fashioned bits of indentation, plus the odd const. 2019-12-22 00:00:23 -05:00
Thomas Harte
05d77d3297 Also deploys make_unique/shared to avoid type repetition. 2019-12-21 23:52:04 -05:00
Thomas Harte
e9318efeb6 Switches to std::make_shared/make_unique in a bunch of applicable places.
No doubt many more similar improvements are available, these are just the ones that were easy to find.
2019-12-21 23:34:25 -05:00
Thomas Harte
25da5ebdae Merge pull request #695 from TomHarte/68000ByteAccess
Corrects 16-bit view of the 68000 bus during 8-bit operations.
2019-12-21 21:08:39 -05:00
Thomas Harte
cf16f41939 Makes value8_high/low and value16 branchless. 2019-12-21 20:58:37 -05:00
Thomas Harte
08f2877382 I think the 68000 actually loads a byte value onto both the upper and lower data lines. 2019-12-21 20:37:03 -05:00
Thomas Harte
6f4444d834 Merge pull request #694 from TomHarte/C++17
Standardises on -O2, C++17.
2019-12-21 20:32:04 -05:00
Thomas Harte
993dfeae1b Standardises on -O2, C++17. 2019-12-21 20:25:43 -05:00
Thomas Harte
b4fd506361 Merge pull request #693 from TomHarte/STComposite
Adds colour composite output to the ST
2019-12-21 00:04:54 -05:00
Thomas Harte
e5440a4146 Hacks in a colour burst.
With a major flaw: it's implicit. I think I need a minor rethink of various components here.
2019-12-20 23:49:38 -05:00
Thomas Harte
57ce10418f Switches prescale logic, the better to deal with changes in prescaler.
According to my assumptions about the behaviour, anyway.
2019-12-20 23:33:14 -05:00
Thomas Harte
47508d50a7 Wires through a composite video option for the ST.
Which is great and all, except that I've not yet inserted a colour burst. So it's monochrome.
2019-12-20 20:49:14 -05:00
Thomas Harte
56cc191a8b Merge pull request #692 from TomHarte/11Sectors
Compacts gaps when necessary to fit more sectors.
2019-12-19 23:33:25 -05:00
Thomas Harte
2a1520c04e Removes mostly-uninformative piece of logging. 2019-12-19 22:58:28 -05:00
Thomas Harte
3d83f5ab49 Ensures a proper size handoff and implements a ripple feature I happened to find a forum post about. 2019-12-19 22:58:07 -05:00
Thomas Harte
0007dc23b3 Eliminates bit 0 of the DMA address. 2019-12-19 22:44:21 -05:00
Thomas Harte
416d68ab3a Installs some additional safety guards. 2019-12-19 22:27:50 -05:00
Thomas Harte
ed7f171736 Moves address reload to end of vertical sync.
I have no information as to when it should be, so this is as valid a guess as any other.
2019-12-19 22:20:43 -05:00
Thomas Harte
0e066f0f70 Removes 'done' TODO.
For certain values of done.
2019-12-19 22:19:59 -05:00
Thomas Harte
3e6f51f5cf Merge branch '11Sectors' of github.com:TomHarte/CLK into 11Sectors 2019-12-19 19:36:33 -05:00
Thomas Harte
797abae4b3 Compacts gaps when necessary to fit more sectors. 2019-12-19 19:36:19 -05:00
Thomas Harte
4605b1b264 Compacts gaps when necessary to fit more sectors. 2019-12-19 19:22:48 -05:00
Thomas Harte
45a391d69e Increases quantity of annotations.
I'm now at almost 500 lines, and I haven't even really written anything yet.
2019-12-18 22:57:12 -05:00
Thomas Harte
15bc18b64f Merge branch 'master' into FurtherSCC 2019-12-18 22:17:10 -05:00
Thomas Harte
d802e8aee3 Merge pull request #690 from TomHarte/YMNotAY
Adds explicit emulation of the YM2149F.
2019-12-18 22:10:16 -05:00
Thomas Harte
206ab380c7 Introduces double-resolution envelopes for the Atari ST. 2019-12-18 22:03:02 -05:00
Thomas Harte
d85ae21b2f Adds an explicit declaration of chip type to all AY users. 2019-12-18 19:28:41 -05:00
Thomas Harte
470cc572fd Merge pull request #689 from TomHarte/STScreenshot
Adds a token Atari ST screenshot.
2019-12-17 23:29:43 -05:00
Thomas Harte
f0d9d8542b Adds a token Atari ST screenshot. 2019-12-17 23:28:38 -05:00
Thomas Harte
d2390fcb11 Merge pull request #688 from TomHarte/NewAtari
Adds the Atari ST to File -> New in Cocoa world.
2019-12-17 23:18:13 -05:00
Thomas Harte
5ce612cb38 Adds the Atari ST to File -> New in Cocoa world. 2019-12-17 23:04:12 -05:00
Thomas Harte
fb0343cafb Merge branch 'FurtherSCC' of github.com:TomHarte/CLK into FurtherSCC 2019-12-17 22:39:49 -05:00
Thomas Harte
7d9bedf7de Merge branch 'master' into FurtherSCC 2019-12-17 22:39:39 -05:00
Thomas Harte
ec7aa2d355 Merge pull request #687 from TomHarte/68000Tests
Significantly increases 68000 testing
2019-12-17 22:31:54 -05:00
Thomas Harte
9464658d1e Adds a count summary. 2019-12-17 22:19:23 -05:00
Thomas Harte
a3e64cae41 Corrects SBCD carry. 2019-12-17 22:16:02 -05:00
Thomas Harte
e969b386f1 Eliminates DIVU/S and MULU/S from this file. 2019-12-17 20:15:11 -05:00
Thomas Harte
af9c0aca97 Added mention of the Atari ST. 2019-12-17 14:36:08 -05:00
Thomas Harte
8a2ac87209 Reverted SBCD/NBCD V behaviour. 2019-12-16 23:08:59 -05:00
Thomas Harte
096b447b4b Corrects MOVE -(An), SR/CCR, which was not previously decrementing.
Also adds a safety check against other instances of the same error. There seem to be none.
2019-12-16 22:38:54 -05:00
Thomas Harte
0d23f141d6 Regenerates without accidentally hitting MODE to SR. 2019-12-16 22:37:57 -05:00
Thomas Harte
84167af54f Corrects CHK N flag. 2019-12-16 20:01:33 -05:00
Thomas Harte
8be26502c4 Fixes NBCD -(An)+, adds some additional comments. 2019-12-16 20:01:19 -05:00
Thomas Harte
ba2436206f Withdraws test of CHK (exception taken). 2019-12-16 20:00:42 -05:00
Thomas Harte
60a9b260b1 Corrects collection of instruction codes. 2019-12-16 00:01:18 -05:00
Thomas Harte
e603fc6aaa Simplifies failure output for me. 2019-12-15 21:26:47 -05:00
Thomas Harte
81cc278b98 Introduces a barrage of further tests. 2019-12-15 21:26:35 -05:00
Thomas Harte
4c068e9bb8 Corrects flags on CMPA.w. 2019-12-15 20:39:47 -05:00
Thomas Harte
f23c5ada31 Ensures tests can be built as a release target. 2019-12-14 23:53:12 -05:00
Thomas Harte
dc1abd874e Corrects indentation typo. 2019-12-14 23:52:53 -05:00
Thomas Harte
1bf4686c59 Adds plentiful additional tests. Though still only a fraction of the anticipated total. 2019-12-14 22:58:51 -05:00
Thomas Harte
a500fbcd73 Expands tests to most of ORI, EORI, ANDI, ADDI and SUBI. 2019-12-14 22:23:40 -05:00
Thomas Harte
d0ef41f11e Adds a temporary manual escape clause for testing specific features. 2019-12-14 21:40:21 -05:00
Thomas Harte
adf6723bf6 Ensures state is evaluated directly at opcode end. 2019-12-14 15:09:06 -05:00
Thomas Harte
37e26c0c37 Eliminates a class of incorrect sign comparison errors. 2019-12-14 14:50:39 -05:00
Thomas Harte
ac1575be27 Resolves false negatives from checking wrong state. 2019-12-14 14:46:00 -05:00
Thomas Harte
923287bf01 Attempts to introduce a basic means for comparative 68000 testing.
i.e. mine versus another source.
2019-12-14 14:26:33 -05:00
Thomas Harte
77fe14cdb3 Merge pull request #685 from TomHarte/LoadDelay
Adds a delay on load following DE
2019-12-13 21:38:10 -05:00
Thomas Harte
c00ae7ce6a Adds a one-cycle delay on frequency changes. 2019-12-13 19:57:54 -05:00
Thomas Harte
d5b2e6514a Merge branch 'master' into LoadDelay 2019-12-13 19:41:35 -05:00
Thomas Harte
fc7f46006e Merge pull request #686 from TomHarte/AptGet
Adds an apt-get update.
2019-12-13 13:32:18 -05:00
Thomas Harte
41503d7253 Allow releaseinfo changes. 2019-12-13 13:16:24 -05:00
Thomas Harte
f88c942fef Adds an apt-get update. 2019-12-12 23:26:12 -05:00
Thomas Harte
4bcf217324 Ensures delayed loading isn't interrupted by blank, hsync. 2019-12-12 23:20:28 -05:00
Thomas Harte
f6f2b4b90f Removes double DE edge test. 2019-12-12 22:50:35 -05:00
Thomas Harte
95b5db4d87 Tweaks timings yet further, adds a FIFO reset.
The accuracy of this may require further research.
2019-12-11 23:22:20 -05:00
Thomas Harte
de4403e021 Corrects blank timing. 2019-12-10 22:17:57 -05:00
Thomas Harte
0a405d1c06 Introduces a latency between DE and load. 2019-12-10 21:24:15 -05:00
Thomas Harte
768b3709b8 Corrects audio clock rate. 2019-12-10 20:25:27 -05:00
Thomas Harte
7cc5d0b209 Merge pull request #683 from TomHarte/MFPPerformance
Switch to faster timer implementation; it seems to work.
2019-12-09 19:52:16 -05:00
Thomas Harte
c2646a415f Switch to faster timer implementation; it seems to work. 2019-12-09 19:23:08 -05:00
Thomas Harte
e1c7a140d0 Merge pull request #682 from TomHarte/AddressError
ST: Adds some initial bus error logic, plus some optimisations.
2019-12-08 22:53:40 -05:00
Thomas Harte
7cd11ecb7f Adds necessary #include for assert. 2019-12-08 22:43:39 -05:00
Thomas Harte
4dd235f677 Adds supervisor/user to logged flags in trace mode. 2019-12-08 22:39:10 -05:00
Thomas Harte
a7cfb840ef Adds but presently disables a diagnostic for border elimination. 2019-12-08 22:34:42 -05:00
Thomas Harte
acfe2c63b8 Adds an assert to verify the interrupt line is clear after a full reset. 2019-12-08 22:34:19 -05:00
Thomas Harte
b192381928 Implements a fuller reset, takes a run at the overran flag. 2019-12-08 21:20:06 -05:00
Thomas Harte
c785797da6 Adds a warning for unhandled reset. 2019-12-08 21:01:30 -05:00
Thomas Harte
0408592ada Switches to byte buffers and seeks to reduce unnecessary video flushing. 2019-12-08 20:20:13 -05:00
Thomas Harte
407cc78c78 Extends to offer simpler 8-bit access handling. 2019-12-08 20:19:44 -05:00
Thomas Harte
4536c6a224 Resolves printf type errors. 2019-12-08 11:56:05 -05:00
Thomas Harte
0ed87c61bd Introduces an explicit area of floating bus, starts adding bus errors. 2019-12-08 11:52:43 -05:00
Thomas Harte
332f0d6167 Ensures MSAs are explicitly read-only. 2019-12-08 11:52:15 -05:00
Thomas Harte
08a27bdec7 NTSC frame length is correct; removes TODO. 2019-12-08 11:51:12 -05:00
Thomas Harte
288cabbad1 Merge pull request #680 from TomHarte/EventCountReload
Implements MFP timer reload when in event counting mode.
2019-11-19 22:54:34 -05:00
Thomas Harte
7ff57f8cdf Starts to flesh out documentation. 2019-11-19 22:32:07 -05:00
Thomas Harte
06edeea866 Adds reload during event count mode.
Plus a helpful bit of TODO.
2019-11-19 22:24:32 -05:00
Thomas Harte
3c77d3bda0 Merge pull request #679 from TomHarte/OffByOne
Corrects off-by-one error in the ST's vertical state machine
2019-11-19 22:02:41 -05:00
Thomas Harte
72cb3a1cf6 Integrates basic unit test for Atari ST video event prediction. 2019-11-19 21:54:13 -05:00
Thomas Harte
e0ceab6642 Pivots towards looking at Timer B as a cause of in-frame inaccuracy. 2019-11-19 21:52:50 -05:00
Thomas Harte
894066984c Moves beginning and end of vertical sync to what I now believe is its proper place.
At least one demo now successfully opens the top border.
2019-11-19 20:13:47 -05:00
Thomas Harte
c91495d068 Merge pull request #678 from TomHarte/DEDelay
Introduces a 28-cycle delay on DE propagation
2019-11-18 23:53:46 -05:00
Thomas Harte
e787c03530 Slightly shortens NTSC frame.
Either: (i) 263 is incorrect; or (ii) my logic as to frame height is incorrect. Given that the horizontal side of things is really well documented, I'm currently guessing (i). Research to do.
2019-11-18 23:47:27 -05:00
Thomas Harte
b12136691a Corrects comment. 2019-11-18 23:46:33 -05:00
Thomas Harte
c04d2f6c6e Restricts DTack delay to RAM and Shifter accesses. 2019-11-18 22:57:13 -05:00
Thomas Harte
6990abc0d3 Tweaks selected output mode when both BPP bits are set. 2019-11-18 22:56:40 -05:00
Thomas Harte
0ce5057fd9 Attempts to factor in event counting direction. 2019-11-18 22:37:20 -05:00
Thomas Harte
ade8df7217 Permits a delay on DE propagation back to the CPU. Plus tests.
Currently set at 28 cycles, but I don't know.
2019-11-18 22:12:24 -05:00
Thomas Harte
b98703bd5b Corrects lack of const. 2019-11-18 22:11:52 -05:00
Thomas Harte
82c984afa4 Switches the joysticks around.
Thereby finally allowing me to control mode games.
2019-11-18 20:02:27 -05:00
Thomas Harte
1202b0a65f Establishes a pipeline for delayed public state visibility. 2019-11-17 23:28:00 -05:00
Thomas Harte
facc0a1976 Amps up the documentation. 2019-11-17 21:28:51 -05:00
Thomas Harte
25da8b7787 Merge pull request #677 from TomHarte/SyncDisturbance
Corrects accidental dropping of pixel residue.
2019-11-17 18:47:27 -05:00
Thomas Harte
253dd84109 Corrects accidental dropping of pixel residue.
Specific issue: the repeated (start_column != end_column) test, which can no longer be correct if start_column has been incremented in the (x_&7) test.

The visible effect was to omit pixels from the output wave, which also affected observed sync timing.
2019-11-17 18:34:13 -05:00
Thomas Harte
9d07765823 Ensures proper precedence of * over %. 2019-11-14 23:19:31 -05:00
Thomas Harte
11de0e198f Removes dead travis.yml.
Travis never worked; GitHub actions do; that's CI.
2019-11-14 19:58:24 -05:00
Thomas Harte
f16f0897d5 Merge pull request #676 from TomHarte/BuildWorkflow
Adds continuous integration via GitHub actions.
2019-11-14 13:59:52 -05:00
Thomas Harte
3d4d45ef62 Remove redundant 'build' 2019-11-14 13:54:24 -05:00
Thomas Harte
04c4f5f321 Removes syntax violating colon. 2019-11-14 13:53:24 -05:00
Thomas Harte
fa900d22e8 Adds a more manageable title. 2019-11-14 13:52:24 -05:00
Thomas Harte
aee2890b25 Switch to title case.
This seems to fit better with the fixed named steps.
2019-11-14 13:50:03 -05:00
Thomas Harte
40e1ec28fb Attempt sudo.
Sorry for the noise; there's no obvious better way to test this stuff.
2019-11-14 13:42:56 -05:00
Thomas Harte
c6e2b1237c Add build workflow 2019-11-14 13:40:06 -05:00
Thomas Harte
efdd27a435 Merge pull request #675 from TomHarte/STFileFormat
Adds support for .ST files.
2019-11-12 23:32:47 -05:00
Thomas Harte
2c4f372872 Adds support for the .ST file format. 2019-11-12 23:23:14 -05:00
Thomas Harte
74be876d72 Corrects track count calculation for DSD disks. 2019-11-12 23:22:56 -05:00
Thomas Harte
e8e166eec5 Ensures no out-of-disk-bounds mirroring. 2019-11-12 23:22:25 -05:00
Thomas Harte
4ec8fa0d20 Merge branch 'LessACIAState' 2019-11-12 22:34:02 -05:00
Thomas Harte
b019c6f8dd Merge pull request #674 from TomHarte/LessACIAState
Reduces redundant ACIA state.
2019-11-12 22:32:47 -05:00
Thomas Harte
6ec3c47cc0 Ensures same-level interrupts don't double trigger. 2019-11-12 22:18:13 -05:00
Thomas Harte
ccce127f13 Merge branch 'master' into LessACIAState 2019-11-12 19:41:18 -05:00
Thomas Harte
f4cfca0451 Merge branch 'master' of github.com:TomHarte/CLK 2019-11-12 19:38:44 -05:00
Thomas Harte
eb287605f7 Switches to a default of TOS 1.04. 2019-11-12 19:38:30 -05:00
Thomas Harte
2026761a07 Merge pull request #673 from TomHarte/FewerSpans
Reduces Atari ST output heft
2019-11-12 19:37:53 -05:00
Thomas Harte
6a82c87320 Withdraws border optimisation temporarily; I think I may be onto an output bug here. 2019-11-12 19:33:13 -05:00
Thomas Harte
f0478225f0 Adjusts logic to reduce number of output spans. 2019-11-12 19:30:28 -05:00
Thomas Harte
d6edfa5c6d Removes the redundant state encased within interrupt_causes_. 2019-11-11 21:49:02 -05:00
Thomas Harte
e7253a8713 Merge pull request #672 from TomHarte/BetterShifter
Introduces a cleaner, separated shifter.
2019-11-10 21:56:45 -05:00
Thomas Harte
c6f6bc68e1 Undoes non-insertion of media. 2019-11-10 21:52:06 -05:00
Thomas Harte
ab34fad8ca Introduces a cleaner, separated shifter. 2019-11-10 21:39:40 -05:00
Thomas Harte
e4c77614c1 Merge pull request #671 from TomHarte/STJoystick
Makes joysticks a little more const correct
2019-11-09 22:11:11 -05:00
Thomas Harte
072b0266af It seems status reads are not required to clear the interrupt line. 2019-11-09 20:12:09 -05:00
Thomas Harte
7ae0902103 Adds additional joystick commands to the dispatcher. 2019-11-09 20:10:54 -05:00
Thomas Harte
8e9428623e Adds joystick events to the Atari ST. 2019-11-09 18:39:22 -05:00
Thomas Harte
2c25135d8a Fixes const correctness for joystick machines; the ST is now one such. 2019-11-09 18:19:05 -05:00
Thomas Harte
3741cba88c Merge pull request #670 from TomHarte/STKeyboard
Corrects: KeyPad -> Keypad. Also fleshes out Atari ST keyboard mapping.
2019-11-09 18:05:52 -05:00
Thomas Harte
860837d894 Corrects: KeyPad -> Keypad. Also fleshes out Atari ST keyboard mapping. 2019-11-09 18:02:14 -05:00
Thomas Harte
cef07038c1 Merge pull request #662 from TomHarte/AtariST
Adds basic and very buggy Atari ST emulation.
2019-11-09 16:18:50 -05:00
Thomas Harte
0bf61c9c99 Updated: there is some ST emulation here now. 2019-11-09 16:18:29 -05:00
Thomas Harte
837dfd1ab8 Corrects StaticAnalyser references. 2019-11-09 16:14:00 -05:00
Thomas Harte
0204003680 Resolves GCC warnings. 2019-11-09 16:12:37 -05:00
Thomas Harte
5fc4e57db7 Eliminates non-portable use of fls. 2019-11-09 16:03:00 -05:00
Thomas Harte
c4fefe1eb3 Updates SConstruct. 2019-11-09 15:42:19 -05:00
Thomas Harte
77ef7dc8fc Shuffles ST and 2600 into a common parent. 2019-11-09 15:31:41 -05:00
Thomas Harte
e3abbc9966 Renames what didn't end up being a whole SerialPort. 2019-11-09 15:21:51 -05:00
Thomas Harte
70c6010fe0 Expands visible area and adds a few more safety barriers. 2019-11-09 15:00:42 -05:00
Thomas Harte
8c736a639a Eliminates unexpected bottleneck created by ACIA. 2019-11-09 15:00:12 -05:00
Thomas Harte
cc7ff1ec9e Corrects typo. 2019-11-09 14:59:35 -05:00
Thomas Harte
9d12ca691f Makes meaning of 2048 here explicit. 2019-11-09 14:59:16 -05:00
Thomas Harte
db03b03276 Corrects [AND/OR/EOR].bw Dn, -(An) to decrement destination.
It was previously doing a predecrement on the internal source address, which is unused. This fixes at least Dan Dare III and Silkworm.
2019-11-09 11:25:23 -05:00
Thomas Harte
45375fb5d0 Makes endian aware. 2019-11-09 09:48:59 -05:00
Thomas Harte
d2324e413d Clarifies ownership of bpp-has-changed test. 2019-11-09 00:10:59 -05:00
Thomas Harte
e0c15f43bb Avoids massive over-flushing of pixel buffers. 2019-11-09 00:05:02 -05:00
Thomas Harte
8b0d550b81 Attempts to move vertical sync out to cycle 30. 2019-11-08 22:18:47 -05:00
Thomas Harte
d1259f829e Moves vertical state decisions back to cycle 502. 2019-11-08 21:42:05 -05:00
Thomas Harte
7caef46c05 Switches back to hsync for interrupts; corrects current address reads. 2019-11-08 21:25:28 -05:00
Thomas Harte
6902251d8b Fixed: returns video address in bytes, not words. 2019-11-08 20:47:08 -05:00
Thomas Harte
0b683b0360 Adds some sanity checks. 2019-11-08 20:46:24 -05:00
Thomas Harte
5d5dc79f2c Corrects raster race condition with CPU. 2019-11-07 23:27:05 -05:00
Thomas Harte
68f9084d9e Fixes timing of wrap. 2019-11-07 23:24:51 -05:00
Thomas Harte
b7c407be10 The palette is meant to be read/write. 2019-11-07 23:11:06 -05:00
Thomas Harte
f45798faf0 Corrects initial safe value of line_length_. 2019-11-07 22:53:26 -05:00
Thomas Harte
7c66d7a13c Corrects sync and line-length latch timings. 2019-11-07 22:53:04 -05:00
Thomas Harte
f9a35c6636 Corrects potential wrap error for non-single-byte targets. 2019-11-07 22:23:26 -05:00
Thomas Harte
5e1570258d I think the horizontal interrupt is blank, not sync. 2019-11-07 22:00:50 -05:00
Thomas Harte
fc8021c0b0 Adds a centre crop. 2019-11-07 20:02:45 -05:00
Thomas Harte
c9cd56915e Corrects typo that was adding an extra line of PAL video. 2019-11-07 19:55:49 -05:00
Thomas Harte
1fd19c5786 Attempts to add proper bus timing. 2019-11-07 19:55:00 -05:00
Thomas Harte
ce66b5fd9c Corrected member variable names. 2019-11-07 19:44:22 -05:00
Thomas Harte
8aa425c9d8 Fixes medium resolution mode. 2019-11-06 23:25:36 -05:00
Thomas Harte
ec68bc5047 Corrects output glitches: channel de sync and improper border beginnings. 2019-11-06 22:37:05 -05:00
Thomas Harte
0971bbeebe Merge branch 'master' into AtariST 2019-11-05 23:24:26 -05:00
Thomas Harte
9292f66c2d Merge pull request #669 from TomHarte/WOZDoc
Update explanation of NIB processing.
2019-11-05 23:21:25 -05:00
Thomas Harte
f3e2e88986 Update explanation of NIB processing. 2019-11-05 23:20:20 -05:00
Thomas Harte
6afefa107e Resolves unused variable warning. 2019-11-05 23:18:25 -05:00
Thomas Harte
0ce807805d Eliminates most masks, at least for now. 2019-11-05 23:17:59 -05:00
Thomas Harte
41f3c29e30 Attempts to switch to correct video state machine.
Some glitches remain to be ironed out.
2019-11-05 23:02:25 -05:00
Thomas Harte
6c75c60149 Merge branch 'master' into AtariST 2019-11-05 22:05:51 -05:00
Thomas Harte
015f2101f8 Merge pull request #668 from TomHarte/OverAllocation
Resolves a potential out-of-bounds array access.
2019-11-05 21:52:44 -05:00
Thomas Harte
35f1a7ab10 Resolves a potential out-of-bounds array access.
Risk was: allocation exactly joins end of buffer. In which case the next get_data won't wrap the texture y coordinate since it won't spot an x overage.
2019-11-05 21:47:40 -05:00
Thomas Harte
8df1eea955 Goes big on flushing. 2019-11-03 23:03:50 -05:00
Thomas Harte
eeafdf2c03 Slightly expands list of recognised Intelligent Keyboard commands. 2019-11-03 21:58:15 -05:00
Thomas Harte
befe2c2929 Adds floppy drive activity indicators. 2019-11-03 21:57:54 -05:00
Thomas Harte
46ec3510be Fixed: ST sectors are numbered from 1, not 0. 2019-11-03 21:18:29 -05:00
Thomas Harte
e9965c2738 Obeys stated memory size. 2019-11-03 21:18:17 -05:00
Thomas Harte
48b0d8c329 Adds bus req/ack to the DMA controller; hacks support into the ST. 2019-11-03 21:11:25 -05:00
Thomas Harte
07582cee4a BusGrant is a further signal I will need. 2019-11-03 21:10:42 -05:00
Thomas Harte
4dbd2a805a Nudges closer to DMA support. 2019-11-03 17:24:36 -05:00
Thomas Harte
20bf425f98 Drive select lines are active low. 2019-11-02 23:37:56 -04:00
Thomas Harte
0567410bcf Attempts to start getting the WDC working. 2019-11-02 23:26:42 -04:00
Thomas Harte
6d1e09ba55 Connects up the AY to floppy drive/side selection. 2019-11-02 23:04:08 -04:00
Thomas Harte
f40dbefa67 Implements most of keyboard input. 2019-11-02 22:30:02 -04:00
Thomas Harte
f93cdd21de Reverses bit order.
So, for the first time: a green desktop.
2019-11-02 21:53:04 -04:00
Thomas Harte
e1dc3b1915 Reverses mouse buttons.
So I can now navigate the disk-less GEM desktop and click on things.
2019-11-02 21:38:57 -04:00
Thomas Harte
cbf25a16dc Adds relative mouse motion input. 2019-11-02 21:25:45 -04:00
Thomas Harte
14e790746b Fixes return value when reading received data. 2019-11-02 21:25:00 -04:00
Thomas Harte
bf7e9cfd62 Pulls the intelligent keyboard into its own file. 2019-11-02 19:47:44 -04:00
Thomas Harte
a67e0014a4 Fixes video base address and mono/colour monitor value.
Now I see a GEM desktop. In blue.
2019-11-02 19:36:15 -04:00
Thomas Harte
c070f2100c Attempts to regularise data bus access. 2019-11-01 23:01:06 -04:00
Thomas Harte
75e34b4215 Reacts to no acknowledgement. 2019-10-31 21:00:05 -04:00
Thomas Harte
a5bbf54a27 Adds the ability for the 68901 to decline an interrupt acknowledgement. 2019-10-31 19:57:36 -04:00
Thomas Harte
5309ac7c30 Annotates JustInTimeActor as force inline. 2019-10-30 23:18:42 -04:00
Thomas Harte
731dc350b4 Adds sometime real-time clocking for DMA. 2019-10-30 22:59:32 -04:00
Thomas Harte
635e18a50d Ensures the MFP requests and receives real-time clocking when needed. 2019-10-30 22:42:06 -04:00
Thomas Harte
4857ceb3eb Attempts to get a bit more systematic.
Spotted that interrupt_enable_ isn't being used properly while doing so, hopefully that's now correct.
2019-10-29 23:16:08 -04:00
Thomas Harte
1c154131f9 Expands size of storage in Cycles/HalfCycles; adjusts widely to compensate. 2019-10-29 22:36:29 -04:00
Thomas Harte
fd02b6fc18 Corrects in-service test; adds pending clearing upon enabled clearing. 2019-10-28 22:51:00 -04:00
Thomas Harte
553f3b6d8b Properly conforms to GPIP input/output blending. 2019-10-28 22:37:11 -04:00
Thomas Harte
1135576a3a Comments in slightly more detail. 2019-10-28 22:12:56 -04:00
Thomas Harte
a5057e6540 Ensures that stop means stop. 2019-10-28 22:12:45 -04:00
Thomas Harte
1d790ec2a9 Adds the option for a clock conversion to JustInTimeActor and slows the MFP's clock rate. 2019-10-28 21:35:10 -04:00
Thomas Harte
0f2d72c436 Ensures receipt of output line changes. 2019-10-28 21:21:53 -04:00
Thomas Harte
aa52652027 Adds a const. 2019-10-28 21:21:35 -04:00
Thomas Harte
4a1fa8fc13 Adds some const qualifiers. 2019-10-28 21:13:42 -04:00
Thomas Harte
95d3b6e79f Adds a through route for the FDC interrupt line. 2019-10-28 21:13:21 -04:00
Thomas Harte
5f6711b72c Ensures interrupt changes are notified to the delegate. 2019-10-28 21:13:06 -04:00
Thomas Harte
d44734d105 Attempts a fuller setting of GPIP inputs. 2019-10-27 22:39:21 -04:00
Thomas Harte
1aaa6331a0 Stores and returns video mode. 2019-10-27 22:39:00 -04:00
Thomas Harte
de1bfb4e24 Stores and returns timer configuration. 2019-10-27 22:38:49 -04:00
Thomas Harte
0cb19421e8 Adds prefix to mouse position response. 2019-10-27 21:46:03 -04:00
Thomas Harte
92847037b3 Merge branch 'master' into AtariST 2019-10-27 21:40:51 -04:00
Thomas Harte
f4556ef6b0 Merge pull request #666 from TomHarte/CPCSafety
Fixes unit test target
2019-10-27 21:40:06 -04:00
Thomas Harte
4266264449 Switches to the more idiomatic .empty(). 2019-10-27 21:31:31 -04:00
Thomas Harte
1aba1db62c Corrects test. 2019-10-27 21:30:58 -04:00
Thomas Harte
0fc191c87d Switched a few static_cast to constructor syntax. 2019-10-27 14:21:22 -04:00
Thomas Harte
dc4a0e4e3b Video only ever reads from RAM, so it can be const *.
(it can also be *const, since I set it only once)
2019-10-27 14:09:38 -04:00
Thomas Harte
3794d94b68 Adds a sanity check on alignment. 2019-10-27 14:09:02 -04:00
Thomas Harte
0082dc4411 Improves logging. 2019-10-27 00:02:55 -04:00
Thomas Harte
22754683f8 Ensures timer divisor values don't go out of range, adds timer interrupts.
I suspect further timer issues remain.
2019-10-26 23:20:13 -04:00
Thomas Harte
909685d87d A drive with no disk is now happy to spin its motor.
It'll continue to produce index-hole events, which might not be accurate for 5.25" drives. Research to do.
2019-10-26 22:57:05 -04:00
Thomas Harte
55710ea00e Switches the presumption to requiring NDEBUG to avoid forced inlines. 2019-10-26 22:43:25 -04:00
Thomas Harte
36a9a5288b Adds drives to the FDC. 2019-10-26 22:39:11 -04:00
Thomas Harte
e89be6249d Adds a logging prefix. 2019-10-26 22:38:56 -04:00
Thomas Harte
ac39fd0235 Starts work on the DMA controller. 2019-10-26 21:33:57 -04:00
Thomas Harte
ecc0cea5a1 Added a potential branch for the newer TOS memory map. 2019-10-26 16:52:06 -04:00
Thomas Harte
eae11cbf17 Adds a dummy response for mouse interrogation. 2019-10-26 16:14:24 -04:00
Thomas Harte
e96386f572 Takes another stab at MFP interrupt management. 2019-10-26 15:55:19 -04:00
Thomas Harte
a8d481a764 Writes to the pending register appear to be able to clear interrupts too. 2019-10-25 22:46:30 -04:00
Thomas Harte
2207638287 Adds hsync and vsync interrupts. 2019-10-25 22:42:13 -04:00
Thomas Harte
872897029e Attempts a complete wiring of 68901 interrupts. 2019-10-25 22:36:01 -04:00
Thomas Harte
51b4b5551d Actually, I think the 6850 is active low for interrupts. 2019-10-24 22:37:53 -04:00
Thomas Harte
7a2de47f58 Corrects interrupt mask generation. 2019-10-24 22:37:32 -04:00
Thomas Harte
f2f98ed60c Attempts some part of interrupt decision making. 2019-10-24 22:33:42 -04:00
Thomas Harte
77f14fa638 Starts trying to make sense of interrupts. 2019-10-23 23:09:49 -04:00
Thomas Harte
f09a240e6c Gives myself more trace details. 2019-10-21 23:20:03 -04:00
Thomas Harte
092a61f93e Does a better job of having just 512kb. 2019-10-21 23:10:30 -04:00
Thomas Harte
e30ba58e0d Attempts to wire ACIA interrupt signals into the MFP. 2019-10-21 23:02:30 -04:00
Thomas Harte
7cb82fccc0 Attempts properly to maintain interrupt flag; adds delegate. 2019-10-21 22:40:38 -04:00
Thomas Harte
ed9a5b0430 Adds receipt interrupt. 2019-10-21 21:27:57 -04:00
Thomas Harte
8f59a73425 Corrects incoming data capture. 2019-10-21 20:18:52 -04:00
Thomas Harte
91223b9ec8 Sets default level to high. 2019-10-21 20:18:33 -04:00
Thomas Harte
83f5f0e2ad Begins trying to receive ACIA data. 2019-10-21 20:10:19 -04:00
Thomas Harte
cf37e9f5de Remove source control markers. 2019-10-20 23:40:51 -04:00
Thomas Harte
e4f7ead894 Merge branch 'AtariST' of github.com:TomHarte/CLK into AtariST 2019-10-20 23:40:01 -04:00
Thomas Harte
4134463094 The ACIA now receives bits. 2019-10-20 23:34:30 -04:00
Thomas Harte
83d73fb088 The keyboard now responds to a reset on its serial line. 2019-10-20 23:13:44 -04:00
Thomas Harte
75c3e2dacd Adds basic, incomplete dispatcher for the intelligent keyboard. 2019-10-20 23:07:19 -04:00
Thomas Harte
cf07982a9b Ensures good serial line and ACIA behaviour.
Next stop: having the intelligent keyboard react.
2019-10-20 22:10:05 -04:00
Thomas Harte
313aaa8f95 Silences temporarily. 2019-10-20 20:38:56 -04:00
Thomas Harte
2e86dada1d Ensures updates even when the event queue is empty. 2019-10-20 20:38:56 -04:00
Thomas Harte
696af5c3a6 Starts to transfer serial line decoding logic into the line itself. 2019-10-20 20:38:56 -04:00
Thomas Harte
f08b38d0ae Silences, temporarily. 2019-10-20 20:38:55 -04:00
Thomas Harte
9a8352282d Mostly but not quite fixes serial work. 2019-10-20 20:38:55 -04:00
Thomas Harte
3d03cce6b1 Starts working on the GPIP functionality block. 2019-10-20 20:38:55 -04:00
Thomas Harte
34075a7674 Attempts to tie an intelligent keyboard to the other end of its serial line. 2019-10-20 20:38:55 -04:00
Thomas Harte
f79c87659f Corrects documentation error. 2019-10-20 20:38:55 -04:00
Thomas Harte
c10b64e1c0 Adds a received_data_ register, that presently can never fill. 2019-10-20 20:38:55 -04:00
Thomas Harte
5d5fe52144 Corrects transmission logic — exactly hitting write_data_time_remaining now works properly. 2019-10-20 20:38:55 -04:00
Thomas Harte
d461331fd2 Ensures remaining_delays_ is set properly after [reset/flush]_writing. 2019-10-20 20:38:55 -04:00
Thomas Harte
ff62eb6dce The ACIA actually has two clocks, though on an ST they're both 500,000 Hz. 2019-10-20 20:38:55 -04:00
Thomas Harte
374439693e Ensures serial lines know their writer's clock rate. 2019-10-20 20:38:55 -04:00
Thomas Harte
c4ef33b23f JustInTimeActors can now specify a clock divider. 2019-10-20 20:38:55 -04:00
Thomas Harte
a7ed357569 Attempts to implement transmission interrupts and ClockingHint::Source. 2019-10-20 20:38:55 -04:00
Thomas Harte
4e5b440145 Attempts mostly to implement 6850 output. 2019-10-20 20:38:55 -04:00
Thomas Harte
2bd7be13b5 Decodes the 6850 control register, and starts working on standardised serial ports. 2019-10-20 20:38:55 -04:00
Thomas Harte
4b09d7c41d Nudges 6850 towards coherence. 2019-10-20 20:38:55 -04:00
Thomas Harte
97d44129cb Ensures all 16 data lines reach the video. 2019-10-20 20:38:55 -04:00
Thomas Harte
b0f5f7bd37 Attempts to start producing actual video. 2019-10-20 20:38:55 -04:00
Thomas Harte
d1dd6876b5 Adds the option to affix a standard prefix to log messages. 2019-10-20 20:38:55 -04:00
Thomas Harte
a59ec9e818 Provides a token something where DMA should be. 2019-10-20 20:38:55 -04:00
Thomas Harte
4ead905c3c Adds an empty shell for the ACIA. 2019-10-20 20:38:55 -04:00
Thomas Harte
127bb043e7 Adds enough logic to advance to an ACIA access error. 2019-10-20 20:38:55 -04:00
Thomas Harte
42ebe06474 Makes an attempt at tracking video sequence points. 2019-10-20 20:38:55 -04:00
Thomas Harte
74fe32da23 Takes a shot at other display outputs. 2019-10-20 20:38:55 -04:00
Thomas Harte
780916551f Corrects sync generation. 2019-10-20 20:38:54 -04:00
Thomas Harte
305b1211ba Makes a first attempt to box out the ST display area. 2019-10-20 20:38:54 -04:00
Thomas Harte
2cf52fb89c Makes an unsuccessful first attempt at some timer functionality. 2019-10-20 20:38:54 -04:00
Thomas Harte
6e1b606adf Adds a target for MFP read/write operations.
Completely without any implementation, so far.
2019-10-20 20:38:54 -04:00
Thomas Harte
3bb0bf9e14 Adds some semblance of an AY. 2019-10-20 20:38:54 -04:00
Thomas Harte
87a6d22894 Starts to formalise the ST memory map a little. 2019-10-20 20:38:54 -04:00
Thomas Harte
484a0ceeb8 Starts forming an Atari ST memory map. 2019-10-20 20:38:54 -04:00
Thomas Harte
da1436abd2 Gifts the Atari ST a 68000 and non-functional video. 2019-10-20 20:38:54 -04:00
Thomas Harte
125f781ced Starts to create an actual shell of a machine. 2019-10-20 20:38:54 -04:00
Thomas Harte
c66c484c54 Removes unused includes. 2019-10-20 20:38:54 -04:00
Thomas Harte
345b32d6e3 Implements read-only MSA support. 2019-10-20 20:38:54 -04:00
Thomas Harte
8b397626bf Adds a route through the static analyser to the Atari ST. 2019-10-20 20:38:54 -04:00
Thomas Harte
0da1881a07 Adds an Atari ST enumeration and factory method. 2019-10-20 20:38:54 -04:00
Thomas Harte
d4077afd30 Merge pull request #665 from TomHarte/CPCCrash
Slightly improves CPC performance
2019-10-20 20:19:29 -04:00
Thomas Harte
95c45b5515 This can be const. 2019-10-20 17:22:56 -04:00
Thomas Harte
684644420a Increases scan buffer availability. 2019-10-20 17:22:41 -04:00
Thomas Harte
735586f5f8 Corrects tabs; adds potential output_border optimisation. 2019-10-19 21:20:34 -04:00
Thomas Harte
ddae086661 Merge pull request #664 from TomHarte/DataAllocationGuards
Adds safety checks around video data allocation
2019-10-19 18:36:05 -04:00
Thomas Harte
9c7aa5f3fc Attempts also to spot data writes without allocations. 2019-10-19 18:26:56 -04:00
Thomas Harte
418cd07e17 Adds a check against overrunning data. 2019-10-19 18:17:44 -04:00
Thomas Harte
2ae5739b8b Silences temporarily. 2019-10-17 23:59:51 -04:00
Thomas Harte
e095a622d3 Ensures updates even when the event queue is empty. 2019-10-17 23:59:43 -04:00
Thomas Harte
9ab49065cd Starts to transfer serial line decoding logic into the line itself. 2019-10-17 23:34:39 -04:00
Thomas Harte
ab50f17d87 Silences, temporarily. 2019-10-16 23:34:49 -04:00
Thomas Harte
f5a2e180f9 Mostly but not quite fixes serial work. 2019-10-16 23:34:37 -04:00
Thomas Harte
f2e1584275 Starts working on the GPIP functionality block. 2019-10-16 23:21:25 -04:00
Thomas Harte
0fd8813ddb Attempts to tie an intelligent keyboard to the other end of its serial line. 2019-10-16 23:21:14 -04:00
Thomas Harte
b69180ba01 Corrects documentation error. 2019-10-16 23:19:42 -04:00
Thomas Harte
c352d8ae8c Adds a received_data_ register, that presently can never fill. 2019-10-13 23:04:57 -04:00
Thomas Harte
530e831064 Corrects transmission logic — exactly hitting write_data_time_remaining now works properly. 2019-10-13 21:40:46 -04:00
Thomas Harte
3b165a78f2 Ensures remaining_delays_ is set properly after [reset/flush]_writing. 2019-10-13 21:39:25 -04:00
Thomas Harte
8d87e9eb1c The ACIA actually has two clocks, though on an ST they're both 500,000 Hz. 2019-10-13 21:32:34 -04:00
Thomas Harte
f86dc082bb Ensures serial lines know their writer's clock rate. 2019-10-13 20:41:08 -04:00
Thomas Harte
d7982aa84e JustInTimeActors can now specify a clock divider. 2019-10-13 18:19:39 -04:00
Thomas Harte
516d78f5a8 Attempts to implement transmission interrupts and ClockingHint::Source. 2019-10-12 23:46:57 -04:00
Thomas Harte
8b50a7d6e3 Attempts mostly to implement 6850 output. 2019-10-12 23:14:29 -04:00
Thomas Harte
4bf81d3b90 Decodes the 6850 control register, and starts working on standardised serial ports. 2019-10-12 18:19:55 -04:00
Thomas Harte
cd75978e4e Nudges 6850 towards coherence. 2019-10-12 00:04:02 -04:00
Thomas Harte
fda99d9c5f Ensures all 16 data lines reach the video. 2019-10-10 23:29:46 -04:00
Thomas Harte
c5ebf75351 Attempts to start producing actual video. 2019-10-10 22:46:58 -04:00
Thomas Harte
2581b520af Adds the option to affix a standard prefix to log messages. 2019-10-10 22:45:03 -04:00
Thomas Harte
52e5296544 Provides a token something where DMA should be. 2019-10-10 21:04:41 -04:00
Thomas Harte
d7ce2c26e8 Adds an empty shell for the ACIA. 2019-10-10 20:54:29 -04:00
Thomas Harte
f88e1b1373 Adds enough logic to advance to an ACIA access error. 2019-10-09 23:01:11 -04:00
Thomas Harte
021d4dbaf1 Makes an attempt at tracking video sequence points. 2019-10-08 23:06:50 -04:00
Thomas Harte
dbde8f2ee7 Takes a shot at other display outputs. 2019-10-08 22:29:58 -04:00
Thomas Harte
5d06930df4 Corrects sync generation. 2019-10-08 21:29:17 -04:00
Thomas Harte
7722596a3b Makes a first attempt to box out the ST display area. 2019-10-08 21:18:08 -04:00
Thomas Harte
1de1818ebb Makes an unsuccessful first attempt at some timer functionality. 2019-10-07 22:44:35 -04:00
Thomas Harte
885f890df1 Adds a target for MFP read/write operations.
Completely without any implementation, so far.
2019-10-06 23:14:05 -04:00
Thomas Harte
e195497ab7 Adds some semblance of an AY. 2019-10-06 22:30:48 -04:00
Thomas Harte
fcd2143697 Starts to formalise the ST memory map a little. 2019-10-06 17:15:29 -04:00
Thomas Harte
3f45cd2380 Starts forming an Atari ST memory map. 2019-10-04 22:38:46 -04:00
Thomas Harte
a8a34497ff Gifts the Atari ST a 68000 and non-functional video. 2019-10-04 21:34:15 -04:00
Thomas Harte
953423cc02 Starts to create an actual shell of a machine. 2019-10-03 22:47:57 -04:00
Thomas Harte
a2ca887b99 Removes unused includes. 2019-10-03 22:47:41 -04:00
Thomas Harte
3c5ae9cf8e Implements read-only MSA support. 2019-10-03 22:41:20 -04:00
Thomas Harte
fe621d7e52 Adds a route through the static analyser to the Atari ST. 2019-10-03 22:10:10 -04:00
Thomas Harte
814bb4ec63 Adds an Atari ST enumeration and factory method. 2019-10-03 20:17:26 -04:00
Thomas Harte
6c99048211 Copies in a few more hardware notes. 2019-10-02 19:18:09 -04:00
Thomas Harte
2638a901d9 Improves documentation of existing degree of implementation. 2019-09-30 21:36:37 -04:00
Thomas Harte
e8bc254f3f Merge pull request #661 from TomHarte/ListMasterSystemOptions
Adds missing Master System option enumerations.
2019-09-30 21:17:20 -04:00
Thomas Harte
3c146a3fb2 Adds missing Master System enumerations. 2019-09-30 21:10:30 -04:00
Thomas Harte
71083fd0f7 Improves documentation of existing degree of implementation. 2019-09-29 22:08:16 -04:00
Thomas Harte
b609ce6fcb Merge pull request #658 from TomHarte/Afterburner
Minor TMS timing correction.
2019-09-28 23:50:28 -04:00
Thomas Harte
929475d31e Minor correction: round down, not up. 2019-09-28 23:49:32 -04:00
Thomas Harte
f14d98452e Merge pull request #657 from TomHarte/DiskIIAgain
Corrects time propagation for Apple II cards
2019-09-28 23:40:07 -04:00
Thomas Harte
9d17d48bca Ensures cycles_per_revolution_ is populated for fixed-rate drives. 2019-09-28 23:23:15 -04:00
Thomas Harte
4ac3839185 Seeks to ensure that card transitions between real-time and just-in-time don't break timing. 2019-09-28 18:34:04 -04:00
Thomas Harte
c089d1cd09 Improves text; nobody normal knows that this is "a view". 2019-09-24 22:52:08 -04:00
Thomas Harte
cb85ec25cc Merge pull request #656 from TomHarte/MacMenu
Adds Macintosh choice to File -> New...
2019-09-24 22:50:43 -04:00
Thomas Harte
fbf95ec2b8 Removes the now empty local namespace. 2019-09-24 22:48:47 -04:00
Thomas Harte
6adca98f34 Adds Macintosh choice to File -> New... 2019-09-24 22:48:34 -04:00
Thomas Harte
48f4d8b875 Merge pull request #655 from TomHarte/EventProtocol
Rationalises protocol for application-level event theft.
2019-09-24 22:32:22 -04:00
Thomas Harte
7758f9d0a9 Improves nomenclature. 2019-09-24 22:31:36 -04:00
Thomas Harte
7112f0336c Rationalises protocol for application-level event theft. 2019-09-24 22:31:20 -04:00
Thomas Harte
298694a881 Merge pull request #654 from TomHarte/GoodbyeFastTapeProtocol
Eliminates fast loading Objective-C/Swift protocol.
2019-09-24 20:22:08 -04:00
Thomas Harte
7ff4594f09 Eliminates fast loading Objective-C/Swift protocol.
A very long time ago, when each machine had its own Objective-C class, this protocol was used to indicate which machines support that feature. It no longer communicates anything.
2019-09-24 20:13:09 -04:00
Thomas Harte
e8bd538182 Merge pull request #653 from TomHarte/CommandCapture
Allows machines to declare modifiers as 'essential'.
2019-09-22 14:04:06 -04:00
Thomas Harte
8489e8650f Attempts another draft of not inundating the user with open file dialogues. 2019-09-22 13:59:31 -04:00
Thomas Harte
114f81941e Completes the wiring necessary for capture of the command key.
At least when coupled with mouse capture.
2019-09-22 13:53:38 -04:00
Thomas Harte
077c7d767f Shifts essential modifiers up to the Keyboard class.
I had forgotten that mappers are not exposed.
2019-09-22 13:48:50 -04:00
Thomas Harte
8f88addf9f Establishes an interface for requesting shortcut theft. Not yet implemented. 2019-09-22 13:15:35 -04:00
Thomas Harte
f28c124039 Adds a route to divert key events before they reach Cocoa.
If you were to use this, it would have the effect of disabling in-app keyboard short-cuts in favour of routing keys to the emulated machine.
2019-09-22 13:15:01 -04:00
Thomas Harte
a416bc0058 Adds an interface allowing keyboard mappers to declare modifiers that are 'essential'.
i.e. ones that, if not delivered reliably, will cause the related machine to behave unexpectedly.
2019-09-22 13:14:09 -04:00
Thomas Harte
e78b1dcf3c Merge pull request #652 from TomHarte/Xcode11
Updates to Xcode11 recommended project settings.
2019-09-22 12:19:39 -04:00
Thomas Harte
8a14f5d814 Updates to Xcode11 recommended project settings.
The updated compiler also flagged a potential issue with CPU::Z80::Register not being a namespace re: 'Refresh' versus CPU::Z80::PartialMachineCycle. I don't entirely see it, but this fixes the problem.

I also finally figured out what the compiler was trying to tell me about ROMRequester.xib.
2019-09-22 12:13:56 -04:00
Thomas Harte
e5f983fbac Merge pull request #651 from TomHarte/MacOpenWindow
Shows the auto-open dialogue only at most once.
2019-09-21 18:01:54 -04:00
Thomas Harte
3e639e96e7 Shows the auto-open dialogue only at most once. 2019-09-21 18:01:16 -04:00
Thomas Harte
61993f0687 Merge pull request #650 from TomHarte/DeadReference
Removes dead reference to video from VIAPortHandler.
2019-09-21 17:40:22 -04:00
Thomas Harte
5f16fa8c08 Removes dead reference to video from VIAPortHandler. 2019-09-21 17:39:45 -04:00
Thomas Harte
dcea9c9ab2 Merge pull request #649 from TomHarte/BetterTiming
Implements every-other-cycle-during-pixels RAM timing.
2019-09-21 17:35:28 -04:00
Thomas Harte
e7bf0799b6 Implements every-other-cycle-during-pixels RAM timing. 2019-09-21 17:25:20 -04:00
Thomas Harte
e760421f6f Merge pull request #648 from TomHarte/QuickBoot
Adds the option to skip the Mac Plus memory test
2019-09-19 22:47:07 -04:00
Thomas Harte
8ea4c17315 Completes Mac GUI wiring. 2019-09-19 22:37:23 -04:00
Thomas Harte
2e24da4614 Implements quick booting, and edges towards exposing it on the Mac.
It should already work in kiosk mode.
2019-09-19 22:32:12 -04:00
Thomas Harte
e46601872b Establishes that the Macintosh offers the quick-boot option. 2019-09-19 21:50:39 -04:00
Thomas Harte
6d0e41b760 Adds "quick boot" as a standard option.
I assume I'm going to reuse this.
2019-09-19 19:41:43 -04:00
Thomas Harte
5a82df837d Merge pull request #647 from TomHarte/SCSIActivity
Adds the SCSI bus as an Activity::Source.
2019-09-19 19:33:06 -04:00
Thomas Harte
776b819a5a Adds the SCSI bus as an Activity::Source. 2019-09-19 19:31:22 -04:00
Thomas Harte
1783f6c84b Update README.md
Corrects alphabetical order.
2019-09-18 22:11:37 -04:00
Thomas Harte
2ef2c73efe The early Macintoshes are less experimental now. 2019-09-18 22:11:04 -04:00
Thomas Harte
55e003ccc1 Merge pull request #646 from TomHarte/LinuxFixes
Corrects hasty merge for more standards-compliant targets
2019-09-18 22:07:57 -04:00
Thomas Harte
3d54d55dbb Adds missing #include for assert. 2019-09-18 22:06:13 -04:00
Thomas Harte
72c0a631f7 Moves includes to correct file. 2019-09-18 22:04:54 -04:00
Thomas Harte
1608a90d5d Takes another stab at finding ssize_t. 2019-09-18 22:03:51 -04:00
Thomas Harte
4f8a45a6ce Adds #include for ssize_t. 2019-09-18 22:02:59 -04:00
Thomas Harte
4f0f1dcf18 Corrects accidental use of #import. 2019-09-18 21:53:22 -04:00
Thomas Harte
839e51d92d Adds newest files to SConstruct. 2019-09-18 21:49:57 -04:00
Thomas Harte
e470cf23d8 Merge pull request #645 from TomHarte/SCSI
Adds basic SCSI support, along with a Mac Plus to use it.
2019-09-18 21:45:48 -04:00
Thomas Harte
8d4a96683a Reduces output noise. 2019-09-18 21:41:29 -04:00
Thomas Harte
f53411a319 Removes local NDEBUG. 2019-09-18 21:35:26 -04:00
Thomas Harte
128a1da626 Enables write support. 2019-09-18 20:18:02 -04:00
Thomas Harte
962275c22a Removes clock for NCR 5380.
It doesn't have one in real life, and now can live off the time counting that occurs on the SCSI bus.
2019-09-18 20:17:47 -04:00
Thomas Harte
3002ac8a4a Adds mapping of READ(8) size 0 to size 256. 2019-09-17 21:59:32 -04:00
Thomas Harte
ff43674638 Corrects partition map: string fields are 32 bytes long. 2019-09-17 21:46:14 -04:00
Thomas Harte
2f6c366668 Makes a concerted effort at properly wrapping a hard disk image. 2019-09-17 21:30:04 -04:00
Thomas Harte
2ce1f0a3b1 Implements multi-sector read/write.
This once again unblocks Apple HD SC Setup. Progress!
2019-09-16 22:20:42 -04:00
Thomas Harte
210129c3a1 Updated as per Inside Macintosh IV.
Of which I now own a copy.
2019-09-16 21:31:43 -04:00
Thomas Harte
934901447a Adds a temporary version of block access writing.
Whatever I'm doing, it's still not correct. The Macintosh ostensibly appears to 0-fill the direct-access device, then reads a sector back and hangs.
2019-09-15 22:06:45 -04:00
Thomas Harte
960b289e70 Edges closer towards proper DMA operation.
Specifically: differentiates the three kinds of DMA operation. Still doesn't act correctly with regard to DACK though, and leaves the bus instantaneously improperly formed. Which I'm tempted to try to fix on the target side by properly obeying delays.
2019-09-15 15:03:06 -04:00
Thomas Harte
243e40cd79 Adds signalling of DACK. 2019-09-14 13:48:33 -04:00
Thomas Harte
c849188016 Adds format and write to the SCSI target.
Now I think I need to switch back to the 5380 to ensure proper DMA mode interactions when writing.
2019-09-12 21:58:09 -04:00
Thomas Harte
87e8dade2f Implements READ BUFFER to do, you know, *something*. Plus READ CAPACITY.
The HD SC utility now offers up drive 6 for formatting. That's progress.
2019-09-11 21:52:02 -04:00
Thomas Harte
6fc5b4e825 Simplifies INQUIRY for future targets; implements enough of SENSE MODE to advance.
The HD SC setup utility is now looking to read buffer.
2019-09-08 21:59:56 -04:00
Thomas Harte
00ce7f8ae0 Takes a first shot at INQUIRY. 2019-09-07 22:04:44 -04:00
Thomas Harte
6e0e9afe2f Fixed: to post a message, I want message in, not message out. 2019-09-07 13:35:38 -04:00
Thomas Harte
cb0d994827 Provides empty data for the unimplemented sectors. 2019-09-07 13:17:53 -04:00
Thomas Harte
bee782234a Ensures no state transitions while acknowledge is still asserted. 2019-09-07 13:17:34 -04:00
Thomas Harte
64dad35026 Decreases logging, at least temporarily. 2019-09-03 22:40:32 -04:00
Thomas Harte
cbd1a8cf78 Factors out command termination, adds a default implementation of test unit ready. 2019-09-03 22:40:18 -04:00
Thomas Harte
a4ab0afce3 Takes a shot at completing a full SCSI interaction. 2019-09-03 21:15:30 -04:00
Thomas Harte
1c7e0f3c9d Fixes control line modification by the 5380 and SCSI target command chaining.
So now I'm back to trying to guess how a SCSI command terminates re: the relative meanings of a message phase and a status phase.
2019-09-02 23:14:37 -04:00
Thomas Harte
318cdb41ea Adds SCSI bus clocking to the Macintosh, and fixes its internal counting. 2019-09-02 16:03:33 -04:00
Thomas Harte
2f8e31bc8b Takes a first bash at implementing the new SCSI::Bus timing infrastructure. 2019-09-02 13:00:01 -04:00
Thomas Harte
310c722cc0 Starts a transition to bus-level knowledge of SCSI-specific bus timing thresholds.
The idea being that bus attachees need not all count time for themselves. They can be very plain finite state machines.

New semantics are not yet implemented within the Bus. The plan is to do that, remove the internal counting of time within the NCR, then adjust the Target to be more explicitly stateful.
2019-08-31 21:44:22 -04:00
Thomas Harte
25956bd90f Mildly improves temporary logging.
A deckchair shuffle, at best.
2019-08-26 22:35:11 -04:00
Thomas Harte
1a60ced61b Starts trying to deal with creating a whole volume from merely a partition. 2019-08-25 23:03:54 -04:00
Thomas Harte
081316c071 Adds some additional commentary as this takes shape. 2019-08-25 17:46:05 -04:00
Thomas Harte
eafbc12cc1 Ensures a clean entry into the command phase. 2019-08-25 17:43:14 -04:00
Thomas Harte
ca08716c52 Introduces real hard disk images to the nascent world of SCSI. 2019-08-25 17:03:41 -04:00
Thomas Harte
30cef1ee22 Adds write support. 2019-08-25 15:16:35 -04:00
Thomas Harte
5598802439 Makes the static analyser aware of mass-storage devices. 2019-08-25 15:10:09 -04:00
Thomas Harte
1c6720b0db Adds new class to Xcode project. 2019-08-25 15:09:43 -04:00
Thomas Harte
404b088199 Adds a trivial mass-storage device, for Macintosh HFV volumes. 2019-08-25 15:09:27 -04:00
Thomas Harte
7d61df238a Localises #include. 2019-08-25 15:09:04 -04:00
Thomas Harte
c86db12f1c Starts implementing DMA support on the 5380.
The Macintosh doesn't actually use the DMA signals, but uses pseudo-DMA mode so they nevertheless need to be appropriate.
2019-08-24 22:47:11 -04:00
Thomas Harte
ce2e85af8b Adds missing bus state callouts. 2019-08-22 23:27:00 -04:00
Thomas Harte
2d82855f26 Attempts to provide a data out phase. 2019-08-22 23:16:58 -04:00
Thomas Harte
faec516a2c Starts pushing towards figuring out a proper infrastructure for mass storage. 2019-08-21 23:22:58 -04:00
Thomas Harte
8e274ec5d0 Merge branch 'master' into SCSI 2019-08-21 22:38:18 -04:00
Thomas Harte
bb1a0a0b76 Sketches out further SCSI infrastructure. 2019-08-21 22:37:39 -04:00
Thomas Harte
252650808d Starts seeking to unbind SCSI bus logic and command performance. 2019-08-19 22:47:01 -04:00
Thomas Harte
e3d9254555 Implements phase-match bit.
Seemingly causing the command phase to proceed.
2019-08-18 23:15:54 -04:00
Thomas Harte
90cf99b626 Takes a wild swings at speeding up startup.
With no success.
2019-08-18 22:40:16 -04:00
Thomas Harte
955e909e61 Attempts to nudge the command phase further towards functioning. 2019-08-18 22:39:27 -04:00
Thomas Harte
8339e2044c Switches to proper SCSI terminology and better attempts a command phase. 2019-08-18 15:10:07 -04:00
Thomas Harte
0e0c789b02 Starts attempting to introduce a direct access device.
Without having access to the SCSI-1 standard, a lot of this is guesswork.
2019-08-17 23:43:42 -04:00
Thomas Harte
7e001c1d03 Corrects data line loading.
Also adds some extra temporary logging. Outstanding question: why is ATN not being signalled? Is SEL enough?
2019-08-17 21:30:59 -04:00
Thomas Harte
9047932b81 Corrected basic error. Arbitration now seems to succeed.
This is seemingly followed by a pattern of signalling BUSY+SEL followed by just SEL with the various other potential device IDs in turn. To which nothing ever responds as currently implemented.
2019-08-15 23:28:30 -04:00
Thomas Harte
f668e4a54c Makes an attempt at getting the 5380 past arbitration.
Not entirely successful. Also gets a bit smarter with `final` on ClockingHint::Sources.
2019-08-15 23:14:40 -04:00
Thomas Harte
ce1c96d68c Starts thinking out the mechanics of emulating a SCSI-1 bus. 2019-08-13 23:09:11 -04:00
Thomas Harte
0f67e490e8 Adjusts NCR address decoding to produce a more plausible initial interaction. 2019-08-11 22:43:25 -04:00
Thomas Harte
895c315fa5 Increases the Mac Plus too 4mb. 2019-08-11 21:41:12 -04:00
Thomas Harte
a90a74a512 Stubs in just enough of the 5380 to get a Mac Plus too boot. 2019-08-11 20:55:20 -04:00
Thomas Harte
3e1286cbef Merge pull request #644 from MaddTheSane/patch-1
Update CSMachine.mm
2019-08-11 19:25:16 -04:00
Thomas Harte
949c1e1668 Adds an empty shell for what will be my 5380 implementation. 2019-08-10 23:53:52 -04:00
Thomas Harte
bbd4e4d3dc Enhances memory map fidelity to allow for ROM holes on the Mac Plus.
This is how the ROM detects the difference between the Plus and the 512ke, it seems.
2019-08-10 23:53:34 -04:00
C.W. Betts
4c5f596533 Update CSMachine.mm
No need to create a temporary NSNumber object to be passed to a variadic method.
2019-08-10 00:43:30 -06:00
Thomas Harte
4859d3781b Merge pull request #643 from TomHarte/Mac512
Simplifies just-in-time usage of the IWM, and the disk-speed accumulator
2019-08-07 21:51:07 -04:00
Thomas Harte
bac0461f7f Switches the drive-speed accumulator to the delegate pattern.
This allows the Macintosh to ensure that the IWM is kept up just-in-time with drive speed changes.
2019-08-07 21:39:23 -04:00
Thomas Harte
f26a200d78 Switches to a JustInTimeActor to wrap the IWM.
Also simplifies potential future usage of the IWM template.
2019-08-07 21:28:02 -04:00
Thomas Harte
28ccb7b54e Merge branch 'master' of github.com:TomHarte/CLK 2019-08-04 21:34:49 -04:00
Thomas Harte
b6e4c8209b Switches to showing 'File -> Open...' at launch.
As per the prevailing wind.
2019-08-04 21:34:30 -04:00
Thomas Harte
16548f0765 Merge pull request #642 from TomHarte/InterruptSampling
Moves timing of interrupt sampling into prefetch queue advancement.
2019-08-04 21:20:22 -04:00
Thomas Harte
6a80832140 Moves timing of interrupt sampling into prefetch queue advancement.
As per comment, that is definitely the only place it can occur; I don't know whether it always occurs there.
2019-08-04 21:06:34 -04:00
Thomas Harte
c6cf0e914b Merge pull request #641 from TomHarte/DIVSTiming
Substantially improves DIVS timing.
2019-08-04 20:46:50 -04:00
Thomas Harte
35b1a55c12 Corrects DIVS negative flag. 2019-08-04 20:36:33 -04:00
Thomas Harte
e3794c0c0e Takes a second pass at DIVS timing, seeming to correct that side of things. 2019-08-04 20:33:43 -04:00
Thomas Harte
f88dc23c71 Corrects comment. 2019-08-04 20:30:41 -04:00
Thomas Harte
0e293e4983 Relocates RAM delay test in order to scrape out a minor performance win. 2019-08-03 21:46:45 -04:00
Thomas Harte
e334abfe20 Partitions the 68000 arithmetic tests, to allow easier per-instruction execution. 2019-08-03 17:44:47 -04:00
Thomas Harte
fd2fbe0e59 Merge pull request #640 from TomHarte/InterruptSignalling
Corrects 68000 address lines during interrupt acknowledgement.
2019-08-03 15:42:15 -04:00
Thomas Harte
330b27d085 Merge branch 'master' into InterruptSignalling 2019-08-03 15:39:22 -04:00
Thomas Harte
478f2533b5 Corrects 68000 address bus during interrupt acknowledge.
All unused bits should be 1, not 0.
2019-08-03 15:38:36 -04:00
Thomas Harte
b96972a4b9 Merge pull request #639 from InvisibleUp/master
Allow for scons to run on Python 3
2019-08-03 08:51:45 -04:00
InvisibleUp
f2b083f4de Allow for scons to run on Python 3 2019-08-03 00:33:53 -04:00
Thomas Harte
80f6d665d9 Merge pull request #638 from TomHarte/SimplifiedBus
Simplifies code around Mac bus decoding.
2019-08-02 22:33:54 -04:00
Thomas Harte
a07488cf1b Introduces the Mac Plus memory map.
Albeit with no SCSI support yet.
2019-08-02 22:26:40 -04:00
Thomas Harte
d67c5145c0 Introduces RAM access delays. 2019-08-02 22:12:34 -04:00
Thomas Harte
5e76d593af Switches to table-based address decoding. 2019-08-02 21:30:04 -04:00
Thomas Harte
83393e8e91 Merge branch 'master' into SimplifiedBus 2019-08-02 21:05:45 -04:00
Thomas Harte
e08a64d455 Fixes erroneous instruction. 2019-08-02 21:04:53 -04:00
Thomas Harte
b93f9b3973 Distinguishes time advancement from bus response. 2019-08-02 19:48:41 -04:00
Thomas Harte
9c517d07d4 Merge pull request #637 from TomHarte/UserInfo
Improves Macintosh user communications
2019-08-02 17:29:44 -04:00
Thomas Harte
f45de5b87a Adds how-to-release-the-mouse instructions for Cocoa. 2019-08-02 17:07:51 -04:00
Thomas Harte
011d76175c Adds mouse release instructions for SDL. 2019-08-02 16:38:05 -04:00
Thomas Harte
96005261c7 Adds activity lights for Macintosh disk activity.
Prompting a quick fix to drives not spinning down.
2019-08-02 16:26:23 -04:00
Thomas Harte
c8177af45a Introduces missing implementation file. 2019-08-02 16:26:08 -04:00
Thomas Harte
97eff5b16d Formally distinguishes Macintosh keys from virtual keys.
Also: adds mappings for keypad keys, and corrects a couple of
long-standing capitalisation errors in my virtual key set.
2019-08-02 16:15:34 -04:00
Thomas Harte
917520fb1e Merge pull request #636 from TomHarte/PWM
Attempts more accurately to match Apple's IWM windowing logic.
2019-08-02 15:15:11 -04:00
Thomas Harte
335dda3d55 Attempts more accurately to match Apple's windowing logic. 2019-08-02 12:49:45 -04:00
641 changed files with 110007 additions and 14819 deletions

16
.github/workflows/ccpp.yml vendored Normal file
View File

@@ -0,0 +1,16 @@
name: SDL/Ubuntu
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install dependencies
run: sudo apt-get --allow-releaseinfo-change update && sudo apt-get --fix-missing install libsdl2-dev scons
- name: Make
working-directory: OSBindings/SDL
run: scons -j$(nproc --all)

View File

@@ -1,13 +0,0 @@
# language: objective-c
# osx_image: xcode8.2
# xcode_project: OSBindings/Mac/Clock Signal.xcodeproj
# xcode_scheme: Clock Signal
# xcode_sdk: macosx10.12
language: cpp
before_install:
- sudo apt-get install libsdl2-dev
script: cd OSBindings/SDL && scons
compiler:
- clang
- gcc

View File

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

View File

@@ -11,20 +11,20 @@
using namespace Analyser::Dynamic;
float ConfidenceCounter::get_confidence() {
return static_cast<float>(hits_) / static_cast<float>(hits_ + misses_);
return float(hits_) / float(hits_ + misses_);
}
void ConfidenceCounter::add_hit() {
hits_++;
++hits_;
}
void ConfidenceCounter::add_miss() {
misses_++;
++misses_;
}
void ConfidenceCounter::add_equivocal() {
if(hits_ > misses_) {
hits_++;
misses_++;
++hits_;
++misses_;
}
}

View File

@@ -22,7 +22,7 @@ namespace Dynamic {
class ConfidenceCounter: public ConfidenceSource {
public:
/*! @returns The computed probability, based on the history of events. */
float get_confidence() override;
float get_confidence() final;
/*! Records an event that implies this is the appropriate class: pushes probability up towards 1.0. */
void add_hit();

View File

@@ -32,11 +32,11 @@ class ConfidenceSummary: public ConfidenceSource {
const std::vector<float> &weights);
/*! @returns The weighted sum of all sources. */
float get_confidence() override;
float get_confidence() final;
private:
std::vector<ConfidenceSource *> sources_;
std::vector<float> weights_;
const std::vector<ConfidenceSource *> sources_;
const std::vector<float> weights_;
float weight_sum_;
};

View File

@@ -1,87 +0,0 @@
//
// MultiCRTMachine.cpp
// Clock Signal
//
// Created by Thomas Harte on 29/01/2018.
// Copyright 2018 Thomas Harte. All rights reserved.
//
#include "MultiCRTMachine.hpp"
#include <condition_variable>
#include <mutex>
using namespace Analyser::Dynamic;
MultiCRTMachine::MultiCRTMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex) :
machines_(machines), machines_mutex_(machines_mutex), queues_(machines.size()) {
speaker_ = MultiSpeaker::create(machines);
}
void MultiCRTMachine::perform_parallel(const std::function<void(::CRTMachine::Machine *)> &function) {
// Apply a blunt force parallelisation of the machines; each run_for is dispatched
// to a separate queue and this queue will block until all are done.
volatile std::size_t outstanding_machines;
std::condition_variable condition;
std::mutex mutex;
{
std::lock_guard<decltype(machines_mutex_)> machines_lock(machines_mutex_);
std::lock_guard<std::mutex> lock(mutex);
outstanding_machines = machines_.size();
for(std::size_t index = 0; index < machines_.size(); ++index) {
CRTMachine::Machine *crt_machine = machines_[index]->crt_machine();
queues_[index].enqueue([&mutex, &condition, crt_machine, function, &outstanding_machines]() {
if(crt_machine) function(crt_machine);
std::lock_guard<std::mutex> lock(mutex);
outstanding_machines--;
condition.notify_all();
});
}
}
std::unique_lock<std::mutex> lock(mutex);
condition.wait(lock, [&outstanding_machines] { return !outstanding_machines; });
}
void MultiCRTMachine::perform_serial(const std::function<void (::CRTMachine::Machine *)> &function) {
std::lock_guard<decltype(machines_mutex_)> machines_lock(machines_mutex_);
for(const auto &machine: machines_) {
CRTMachine::Machine *const crt_machine = machine->crt_machine();
if(crt_machine) function(crt_machine);
}
}
void MultiCRTMachine::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
scan_target_ = scan_target;
CRTMachine::Machine *const crt_machine = machines_.front()->crt_machine();
if(crt_machine) crt_machine->set_scan_target(scan_target);
}
Outputs::Speaker::Speaker *MultiCRTMachine::get_speaker() {
return speaker_;
}
void MultiCRTMachine::run_for(Time::Seconds duration) {
perform_parallel([=](::CRTMachine::Machine *machine) {
if(machine->get_confidence() >= 0.01f) machine->run_for(duration);
});
if(delegate_) delegate_->multi_crt_did_run_machines();
}
void MultiCRTMachine::did_change_machine_order() {
if(scan_target_) scan_target_->will_change_owner();
perform_serial([=](::CRTMachine::Machine *machine) {
machine->set_scan_target(nullptr);
});
CRTMachine::Machine *const crt_machine = machines_.front()->crt_machine();
if(crt_machine) crt_machine->set_scan_target(scan_target_);
if(speaker_) {
speaker_->set_new_front_machine(machines_.front().get());
}
}

View File

@@ -12,6 +12,97 @@
using namespace Analyser::Dynamic;
namespace {
class MultiStruct: public Reflection::Struct {
public:
MultiStruct(const std::vector<Configurable::Device *> &devices) : devices_(devices) {
for(auto device: devices) {
options_.emplace_back(device->get_options());
}
}
void apply() {
auto options = options_.begin();
for(auto device: devices_) {
device->set_options(*options);
++options;
}
}
std::vector<std::string> all_keys() const final {
std::set<std::string> keys;
for(auto &options: options_) {
const auto new_keys = options->all_keys();
keys.insert(new_keys.begin(), new_keys.end());
}
return std::vector<std::string>(keys.begin(), keys.end());
}
std::vector<std::string> values_for(const std::string &name) const final {
std::set<std::string> values;
for(auto &options: options_) {
const auto new_values = options->values_for(name);
values.insert(new_values.begin(), new_values.end());
}
return std::vector<std::string>(values.begin(), values.end());
}
const std::type_info *type_of(const std::string &name) const final {
for(auto &options: options_) {
auto info = options->type_of(name);
if(info) return info;
}
return nullptr;
}
size_t count_of(const std::string &name) const final {
for(auto &options: options_) {
auto info = options->type_of(name);
if(info) return options->count_of(name);
}
return 0;
}
const void *get(const std::string &name) const final {
for(auto &options: options_) {
auto value = options->get(name);
if(value) return value;
}
return nullptr;
}
void *get(const std::string &name) final {
for(auto &options: options_) {
auto value = options->get(name);
if(value) return value;
}
return nullptr;
}
void set(const std::string &name, const void *value, size_t offset) final {
const auto safe_type = type_of(name);
if(!safe_type) return;
// Set this property only where the child's type is the same as that
// which was returned from here for type_of.
for(auto &options: options_) {
const auto type = options->type_of(name);
if(!type) continue;
if(*type == *safe_type) {
options->set(name, value, offset);
}
}
}
private:
const std::vector<Configurable::Device *> &devices_;
std::vector<std::unique_ptr<Reflection::Struct>> options_;
};
}
MultiConfigurable::MultiConfigurable(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
for(const auto &machine: machines) {
Configurable::Device *device = machine->configurable_device();
@@ -19,46 +110,11 @@ MultiConfigurable::MultiConfigurable(const std::vector<std::unique_ptr<::Machine
}
}
std::vector<std::unique_ptr<Configurable::Option>> MultiConfigurable::get_options() {
std::vector<std::unique_ptr<Configurable::Option>> options;
// Produce the list of unique options.
for(const auto &device : devices_) {
std::vector<std::unique_ptr<Configurable::Option>> device_options = device->get_options();
for(auto &option : device_options) {
if(std::find(options.begin(), options.end(), option) == options.end()) {
options.push_back(std::move(option));
}
}
}
return options;
void MultiConfigurable::set_options(const std::unique_ptr<Reflection::Struct> &str) {
const auto options = dynamic_cast<MultiStruct *>(str.get());
options->apply();
}
void MultiConfigurable::set_selections(const Configurable::SelectionSet &selection_by_option) {
for(const auto &device : devices_) {
device->set_selections(selection_by_option);
}
}
Configurable::SelectionSet MultiConfigurable::get_accurate_selections() {
Configurable::SelectionSet set;
for(const auto &device : devices_) {
Configurable::SelectionSet device_set = device->get_accurate_selections();
for(auto &selection : device_set) {
set.insert(std::move(selection));
}
}
return set;
}
Configurable::SelectionSet MultiConfigurable::get_user_friendly_selections() {
Configurable::SelectionSet set;
for(const auto &device : devices_) {
Configurable::SelectionSet device_set = device->get_user_friendly_selections();
for(auto &selection : device_set) {
set.insert(std::move(selection));
}
}
return set;
std::unique_ptr<Reflection::Struct> MultiConfigurable::get_options() {
return std::make_unique<MultiStruct>(devices_);
}

View File

@@ -10,6 +10,7 @@
#define MultiConfigurable_hpp
#include "../../../../Machines/DynamicMachine.hpp"
#include "../../../../Configurable/Configurable.hpp"
#include <memory>
#include <vector>
@@ -28,10 +29,8 @@ class MultiConfigurable: public Configurable::Device {
MultiConfigurable(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
// Below is the standard Configurable::Device interface; see there for documentation.
std::vector<std::unique_ptr<Configurable::Option>> get_options() override;
void set_selections(const Configurable::SelectionSet &selection_by_option) override;
Configurable::SelectionSet get_accurate_selections() override;
Configurable::SelectionSet get_user_friendly_selections() override;
void set_options(const std::unique_ptr<Reflection::Struct> &options) final;
std::unique_ptr<Reflection::Struct> get_options() final;
private:
std::vector<Configurable::Device *> devices_;

View File

@@ -16,7 +16,7 @@ namespace {
class MultiJoystick: public Inputs::Joystick {
public:
MultiJoystick(std::vector<JoystickMachine::Machine *> &machines, std::size_t index) {
MultiJoystick(std::vector<MachineTypes::JoystickMachine *> &machines, std::size_t index) {
for(const auto &machine: machines) {
const auto &joysticks = machine->get_joysticks();
if(joysticks.size() >= index) {
@@ -25,7 +25,7 @@ class MultiJoystick: public Inputs::Joystick {
}
}
std::vector<Input> &get_inputs() override {
const std::vector<Input> &get_inputs() final {
if(inputs.empty()) {
for(const auto &joystick: joysticks_) {
std::vector<Input> joystick_inputs = joystick->get_inputs();
@@ -40,19 +40,19 @@ class MultiJoystick: public Inputs::Joystick {
return inputs;
}
void set_input(const Input &digital_input, bool is_active) override {
void set_input(const Input &digital_input, bool is_active) final {
for(const auto &joystick: joysticks_) {
joystick->set_input(digital_input, is_active);
}
}
void set_input(const Input &digital_input, float value) override {
void set_input(const Input &digital_input, float value) final {
for(const auto &joystick: joysticks_) {
joystick->set_input(digital_input, value);
}
}
void reset_all_inputs() override {
void reset_all_inputs() final {
for(const auto &joystick: joysticks_) {
joystick->reset_all_inputs();
}
@@ -67,9 +67,9 @@ class MultiJoystick: public Inputs::Joystick {
MultiJoystickMachine::MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
std::size_t total_joysticks = 0;
std::vector<JoystickMachine::Machine *> joystick_machines;
std::vector<MachineTypes::JoystickMachine *> joystick_machines;
for(const auto &machine: machines) {
JoystickMachine::Machine *joystick_machine = machine->joystick_machine();
auto joystick_machine = machine->joystick_machine();
if(joystick_machine) {
joystick_machines.push_back(joystick_machine);
total_joysticks = std::max(total_joysticks, joystick_machine->get_joysticks().size());
@@ -81,6 +81,6 @@ MultiJoystickMachine::MultiJoystickMachine(const std::vector<std::unique_ptr<::M
}
}
std::vector<std::unique_ptr<Inputs::Joystick>> &MultiJoystickMachine::get_joysticks() {
const std::vector<std::unique_ptr<Inputs::Joystick>> &MultiJoystickMachine::get_joysticks() {
return joysticks_;
}

View File

@@ -23,12 +23,12 @@ namespace Dynamic {
Makes a static internal copy of the list of machines; makes no guarantees about the
order of delivered messages.
*/
class MultiJoystickMachine: public JoystickMachine::Machine {
class MultiJoystickMachine: public MachineTypes::JoystickMachine {
public:
MultiJoystickMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
// Below is the standard JoystickMachine::Machine interface; see there for documentation.
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override;
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final;
private:
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;

View File

@@ -10,12 +10,12 @@
using namespace Analyser::Dynamic;
MultiKeyboardMachine::MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) :
keyboard_(machines_) {
MultiKeyboardMachine::MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
for(const auto &machine: machines) {
KeyboardMachine::Machine *keyboard_machine = machine->keyboard_machine();
auto keyboard_machine = machine->keyboard_machine();
if(keyboard_machine) machines_.push_back(keyboard_machine);
}
keyboard_ = std::make_unique<MultiKeyboard>(machines_);
}
void MultiKeyboardMachine::clear_all_keys() {
@@ -36,11 +36,19 @@ void MultiKeyboardMachine::type_string(const std::string &string) {
}
}
Inputs::Keyboard &MultiKeyboardMachine::get_keyboard() {
return keyboard_;
bool MultiKeyboardMachine::can_type(char c) const {
bool can_type = true;
for(const auto &machine: machines_) {
can_type &= machine->can_type(c);
}
return can_type;
}
MultiKeyboardMachine::MultiKeyboard::MultiKeyboard(const std::vector<::KeyboardMachine::Machine *> &machines)
Inputs::Keyboard &MultiKeyboardMachine::get_keyboard() {
return *keyboard_;
}
MultiKeyboardMachine::MultiKeyboard::MultiKeyboard(const std::vector<::MachineTypes::KeyboardMachine *> &machines)
: machines_(machines) {
for(const auto &machine: machines_) {
observed_keys_.insert(machine->get_keyboard().observed_keys().begin(), machine->get_keyboard().observed_keys().end());
@@ -48,10 +56,12 @@ MultiKeyboardMachine::MultiKeyboard::MultiKeyboard(const std::vector<::KeyboardM
}
}
void MultiKeyboardMachine::MultiKeyboard::set_key_pressed(Key key, char value, bool is_pressed) {
bool MultiKeyboardMachine::MultiKeyboard::set_key_pressed(Key key, char value, bool is_pressed) {
bool was_consumed = false;
for(const auto &machine: machines_) {
machine->get_keyboard().set_key_pressed(key, value, is_pressed);
was_consumed |= machine->get_keyboard().set_key_pressed(key, value, is_pressed);
}
return was_consumed;
}
void MultiKeyboardMachine::MultiKeyboard::reset_all_keys() {
@@ -60,10 +70,10 @@ void MultiKeyboardMachine::MultiKeyboard::reset_all_keys() {
}
}
const std::set<Inputs::Keyboard::Key> &MultiKeyboardMachine::MultiKeyboard::observed_keys() {
const std::set<Inputs::Keyboard::Key> &MultiKeyboardMachine::MultiKeyboard::observed_keys() const {
return observed_keys_;
}
bool MultiKeyboardMachine::MultiKeyboard::is_exclusive() {
bool MultiKeyboardMachine::MultiKeyboard::is_exclusive() const {
return is_exclusive_;
}

View File

@@ -24,34 +24,35 @@ namespace Dynamic {
Makes a static internal copy of the list of machines; makes no guarantees about the
order of delivered messages.
*/
class MultiKeyboardMachine: public KeyboardMachine::Machine {
class MultiKeyboardMachine: public MachineTypes::KeyboardMachine {
private:
std::vector<::KeyboardMachine::Machine *> machines_;
std::vector<MachineTypes::KeyboardMachine *> machines_;
class MultiKeyboard: public Inputs::Keyboard {
public:
MultiKeyboard(const std::vector<::KeyboardMachine::Machine *> &machines);
MultiKeyboard(const std::vector<MachineTypes::KeyboardMachine *> &machines);
void set_key_pressed(Key key, char value, bool is_pressed) override;
void reset_all_keys() override;
const std::set<Key> &observed_keys() override;
bool is_exclusive() override;
bool set_key_pressed(Key key, char value, bool is_pressed) final;
void reset_all_keys() final;
const std::set<Key> &observed_keys() const final;
bool is_exclusive() const final;
private:
const std::vector<::KeyboardMachine::Machine *> &machines_;
const std::vector<MachineTypes::KeyboardMachine *> &machines_;
std::set<Key> observed_keys_;
bool is_exclusive_ = false;
};
MultiKeyboard keyboard_;
std::unique_ptr<MultiKeyboard> keyboard_;
public:
MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
// Below is the standard KeyboardMachine::Machine interface; see there for documentation.
void clear_all_keys() override;
void set_key_state(uint16_t key, bool is_pressed) override;
void type_string(const std::string &) override;
Inputs::Keyboard &get_keyboard() override;
void clear_all_keys() final;
void set_key_state(uint16_t key, bool is_pressed) final;
void type_string(const std::string &) final;
bool can_type(char c) const final;
Inputs::Keyboard &get_keyboard() final;
};
}

View File

@@ -12,7 +12,7 @@ using namespace Analyser::Dynamic;
MultiMediaTarget::MultiMediaTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
for(const auto &machine: machines) {
MediaTarget::Machine *media_target = machine->media_target();
auto media_target = machine->media_target();
if(media_target) targets_.push_back(media_target);
}
}

View File

@@ -24,15 +24,15 @@ namespace Dynamic {
Makes a static internal copy of the list of machines; makes no guarantees about the
order of delivered messages.
*/
struct MultiMediaTarget: public MediaTarget::Machine {
struct MultiMediaTarget: public MachineTypes::MediaTarget {
public:
MultiMediaTarget(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
// Below is the standard MediaTarget::Machine interface; see there for documentation.
bool insert_media(const Analyser::Static::Media &media) override;
bool insert_media(const Analyser::Static::Media &media) final;
private:
std::vector<MediaTarget::Machine *> targets_;
std::vector<MachineTypes::MediaTarget *> targets_;
};
}

View File

@@ -0,0 +1,105 @@
//
// MultiProducer.cpp
// Clock Signal
//
// Created by Thomas Harte on 29/01/2018.
// Copyright 2018 Thomas Harte. All rights reserved.
//
#include "MultiProducer.hpp"
#include <condition_variable>
#include <mutex>
using namespace Analyser::Dynamic;
// MARK: - MultiInterface
template <typename MachineType>
void MultiInterface<MachineType>::perform_parallel(const std::function<void(MachineType *)> &function) {
// Apply a blunt force parallelisation of the machines; each run_for is dispatched
// to a separate queue and this queue will block until all are done.
volatile std::size_t outstanding_machines;
std::condition_variable condition;
std::mutex mutex;
{
std::lock_guard machines_lock(machines_mutex_);
std::lock_guard lock(mutex);
outstanding_machines = machines_.size();
for(std::size_t index = 0; index < machines_.size(); ++index) {
const auto machine = ::Machine::get<MachineType>(*machines_[index].get());
queues_[index].enqueue([&mutex, &condition, machine, function, &outstanding_machines]() {
if(machine) function(machine);
std::lock_guard lock(mutex);
outstanding_machines--;
condition.notify_all();
});
}
}
std::unique_lock lock(mutex);
condition.wait(lock, [&outstanding_machines] { return !outstanding_machines; });
}
template <typename MachineType>
void MultiInterface<MachineType>::perform_serial(const std::function<void(MachineType *)> &function) {
std::lock_guard machines_lock(machines_mutex_);
for(const auto &machine: machines_) {
const auto typed_machine = ::Machine::get<MachineType>(*machine.get());
if(typed_machine) function(typed_machine);
}
}
// MARK: - MultiScanProducer
void MultiScanProducer::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
scan_target_ = scan_target;
std::lock_guard machines_lock(machines_mutex_);
const auto machine = machines_.front()->scan_producer();
if(machine) machine->set_scan_target(scan_target);
}
Outputs::Display::ScanStatus MultiScanProducer::get_scan_status() const {
std::lock_guard machines_lock(machines_mutex_);
const auto machine = machines_.front()->scan_producer();
if(machine) return machine->get_scan_status();
return Outputs::Display::ScanStatus();
}
void MultiScanProducer::did_change_machine_order() {
if(scan_target_) scan_target_->will_change_owner();
perform_serial([](MachineTypes::ScanProducer *machine) {
machine->set_scan_target(nullptr);
});
std::lock_guard machines_lock(machines_mutex_);
const auto machine = machines_.front()->scan_producer();
if(machine) machine->set_scan_target(scan_target_);
}
// MARK: - MultiAudioProducer
MultiAudioProducer::MultiAudioProducer(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex) : MultiInterface(machines, machines_mutex) {
speaker_ = MultiSpeaker::create(machines);
}
Outputs::Speaker::Speaker *MultiAudioProducer::get_speaker() {
return speaker_;
}
void MultiAudioProducer::did_change_machine_order() {
if(speaker_) {
speaker_->set_new_front_machine(machines_.front().get());
}
}
// MARK: - MultiTimedMachine
void MultiTimedMachine::run_for(Time::Seconds duration) {
perform_parallel([duration](::MachineTypes::TimedMachine *machine) {
if(machine->get_confidence() >= 0.01f) machine->run_for(duration);
});
if(delegate_) delegate_->did_run_machines(this);
}

View File

@@ -1,16 +1,16 @@
//
// MultiCRTMachine.hpp
// MultiProducer.hpp
// Clock Signal
//
// Created by Thomas Harte on 29/01/2018.
// Copyright 2018 Thomas Harte. All rights reserved.
//
#ifndef MultiCRTMachine_hpp
#define MultiCRTMachine_hpp
#ifndef MultiProducer_hpp
#define MultiProducer_hpp
#include "../../../../Concurrency/AsyncTaskQueue.hpp"
#include "../../../../Machines/CRTMachine.hpp"
#include "../../../../Machines/MachineTypes.hpp"
#include "../../../../Machines/DynamicMachine.hpp"
#include "MultiSpeaker.hpp"
@@ -22,6 +22,91 @@
namespace Analyser {
namespace Dynamic {
template <typename MachineType> class MultiInterface {
public:
MultiInterface(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex) :
machines_(machines), machines_mutex_(machines_mutex), queues_(machines.size()) {}
protected:
/*!
Performs a parallel for operation across all machines, performing the supplied
function on each and returning only once all applications have completed.
No guarantees are extended as to which thread operations will occur on.
*/
void perform_parallel(const std::function<void(MachineType *)> &);
/*!
Performs a serial for operation across all machines, performing the supplied
function on each on the calling thread.
*/
void perform_serial(const std::function<void(MachineType *)> &);
protected:
const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines_;
std::recursive_mutex &machines_mutex_;
private:
std::vector<Concurrency::AsyncTaskQueue> queues_;
};
class MultiTimedMachine: public MultiInterface<MachineTypes::TimedMachine>, public MachineTypes::TimedMachine {
public:
using MultiInterface::MultiInterface;
/*!
Provides a mechanism by which a delegate can be informed each time a call to run_for has
been received.
*/
struct Delegate {
virtual void did_run_machines(MultiTimedMachine *) = 0;
};
/// Sets @c delegate as the receiver of delegate messages.
void set_delegate(Delegate *delegate) {
delegate_ = delegate;
}
void run_for(Time::Seconds duration) final;
private:
void run_for(const Cycles) final {}
Delegate *delegate_ = nullptr;
};
class MultiScanProducer: public MultiInterface<MachineTypes::ScanProducer>, public MachineTypes::ScanProducer {
public:
using MultiInterface::MultiInterface;
/*!
Informs the MultiScanProducer that the order of machines has changed; it
uses this as an opportunity to synthesis any CRTMachine::Machine::Delegate messages that
are necessary to bridge the gap between one machine and the next.
*/
void did_change_machine_order();
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final;
Outputs::Display::ScanStatus get_scan_status() const final;
private:
Outputs::Display::ScanTarget *scan_target_ = nullptr;
};
class MultiAudioProducer: public MultiInterface<MachineTypes::AudioProducer>, public MachineTypes::AudioProducer {
public:
MultiAudioProducer(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex);
/*!
Informs the MultiAudio that the order of machines has changed; it
uses this as an opportunity to switch speaker delegates as appropriate.
*/
void did_change_machine_order();
Outputs::Speaker::Speaker *get_speaker() final;
private:
MultiSpeaker *speaker_ = nullptr;
};
/*!
Provides a class that multiplexes the CRT machine interface to multiple machines.
@@ -29,60 +114,9 @@ namespace Dynamic {
acquiring a supplied mutex. The owner should also call did_change_machine_order()
if the order of machines changes.
*/
class MultiCRTMachine: public CRTMachine::Machine {
public:
MultiCRTMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines, std::recursive_mutex &machines_mutex);
/*!
Informs the MultiCRTMachine that the order of machines has changed; the MultiCRTMachine
uses this as an opportunity to synthesis any CRTMachine::Machine::Delegate messages that
are necessary to bridge the gap between one machine and the next.
*/
void did_change_machine_order();
/*!
Provides a mechanism by which a delegate can be informed each time a call to run_for has
been received.
*/
struct Delegate {
virtual void multi_crt_did_run_machines() = 0;
};
/// Sets @c delegate as the receiver of delegate messages.
void set_delegate(Delegate *delegate) {
delegate_ = delegate;
}
// Below is the standard CRTMachine::Machine interface; see there for documentation.
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override;
Outputs::Speaker::Speaker *get_speaker() override;
void run_for(Time::Seconds duration) override;
private:
void run_for(const Cycles cycles) override {}
const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines_;
std::recursive_mutex &machines_mutex_;
std::vector<Concurrency::AsyncTaskQueue> queues_;
MultiSpeaker *speaker_ = nullptr;
Delegate *delegate_ = nullptr;
Outputs::Display::ScanTarget *scan_target_ = nullptr;
/*!
Performs a parallel for operation across all machines, performing the supplied
function on each and returning only once all applications have completed.
No guarantees are extended as to which thread operations will occur on.
*/
void perform_parallel(const std::function<void(::CRTMachine::Machine *)> &);
/*!
Performs a serial for operation across all machines, performing the supplied
function on each on the calling thread.
*/
void perform_serial(const std::function<void(::CRTMachine::Machine *)> &);
};
}
}
#endif /* MultiCRTMachine_hpp */
#endif /* MultiProducer_hpp */

View File

@@ -13,7 +13,7 @@ using namespace Analyser::Dynamic;
MultiSpeaker *MultiSpeaker::create(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
std::vector<Outputs::Speaker::Speaker *> speakers;
for(const auto &machine: machines) {
Outputs::Speaker::Speaker *speaker = machine->crt_machine()->get_speaker();
Outputs::Speaker::Speaker *speaker = machine->audio_producer()->get_speaker();
if(speaker) speakers.push_back(speaker);
}
if(speakers.empty()) return nullptr;
@@ -34,43 +34,59 @@ float MultiSpeaker::get_ideal_clock_rate_in_range(float minimum, float maximum)
ideal += speaker->get_ideal_clock_rate_in_range(minimum, maximum);
}
return ideal / static_cast<float>(speakers_.size());
return ideal / float(speakers_.size());
}
void MultiSpeaker::set_output_rate(float cycles_per_second, int buffer_size) {
void MultiSpeaker::set_computed_output_rate(float cycles_per_second, int buffer_size, bool stereo) {
stereo_output_ = stereo;
for(const auto &speaker: speakers_) {
speaker->set_output_rate(cycles_per_second, buffer_size);
speaker->set_computed_output_rate(cycles_per_second, buffer_size, stereo);
}
}
void MultiSpeaker::set_delegate(Outputs::Speaker::Speaker::Delegate *delegate) {
delegate_ = delegate;
bool MultiSpeaker::get_is_stereo() {
// Return as stereo if any subspeaker is stereo.
for(const auto &speaker: speakers_) {
if(speaker->get_is_stereo()) {
return true;
}
}
return false;
}
void MultiSpeaker::set_output_volume(float volume) {
for(const auto &speaker: speakers_) {
speaker->set_output_volume(volume);
}
}
void MultiSpeaker::speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) {
if(!delegate_) return;
auto delegate = delegate_.load(std::memory_order::memory_order_relaxed);
if(!delegate) return;
{
std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_);
std::lock_guard lock_guard(front_speaker_mutex_);
if(speaker != front_speaker_) return;
}
delegate_->speaker_did_complete_samples(this, buffer);
did_complete_samples(this, buffer, stereo_output_);
}
void MultiSpeaker::speaker_did_change_input_clock(Speaker *speaker) {
if(!delegate_) return;
auto delegate = delegate_.load(std::memory_order::memory_order_relaxed);
if(!delegate) return;
{
std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_);
std::lock_guard lock_guard(front_speaker_mutex_);
if(speaker != front_speaker_) return;
}
delegate_->speaker_did_change_input_clock(this);
delegate->speaker_did_change_input_clock(this);
}
void MultiSpeaker::set_new_front_machine(::Machine::DynamicMachine *machine) {
{
std::lock_guard<std::mutex> lock_guard(front_speaker_mutex_);
front_speaker_ = machine->crt_machine()->get_speaker();
std::lock_guard lock_guard(front_speaker_mutex_);
front_speaker_ = machine->audio_producer()->get_speaker();
}
if(delegate_) {
delegate_->speaker_did_change_input_clock(this);
auto delegate = delegate_.load(std::memory_order::memory_order_relaxed);
if(delegate) {
delegate->speaker_did_change_input_clock(this);
}
}

View File

@@ -39,18 +39,20 @@ class MultiSpeaker: public Outputs::Speaker::Speaker, Outputs::Speaker::Speaker:
// Below is the standard Outputs::Speaker::Speaker interface; see there for documentation.
float get_ideal_clock_rate_in_range(float minimum, float maximum) override;
void set_output_rate(float cycles_per_second, int buffer_size) override;
void set_delegate(Outputs::Speaker::Speaker::Delegate *delegate) override;
void set_computed_output_rate(float cycles_per_second, int buffer_size, bool stereo) override;
bool get_is_stereo() override;
void set_output_volume(float) override;
private:
void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) override;
void speaker_did_change_input_clock(Speaker *speaker) override;
void speaker_did_complete_samples(Speaker *speaker, const std::vector<int16_t> &buffer) final;
void speaker_did_change_input_clock(Speaker *speaker) final;
MultiSpeaker(const std::vector<Outputs::Speaker::Speaker *> &speakers);
std::vector<Outputs::Speaker::Speaker *> speakers_;
Outputs::Speaker::Speaker *front_speaker_ = nullptr;
Outputs::Speaker::Speaker::Delegate *delegate_ = nullptr;
std::mutex front_speaker_mutex_;
bool stereo_output_ = false;
};
}

View File

@@ -16,74 +16,55 @@ using namespace Analyser::Dynamic;
MultiMachine::MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines) :
machines_(std::move(machines)),
configurable_(machines_),
crt_machine_(machines_, machines_mutex_),
joystick_machine_(machines),
timed_machine_(machines_, machines_mutex_),
scan_producer_(machines_, machines_mutex_),
audio_producer_(machines_, machines_mutex_),
joystick_machine_(machines_),
keyboard_machine_(machines_),
media_target_(machines_) {
crt_machine_.set_delegate(this);
timed_machine_.set_delegate(this);
}
Activity::Source *MultiMachine::activity_source() {
return nullptr; // TODO
}
MediaTarget::Machine *MultiMachine::media_target() {
if(has_picked_) {
return machines_.front()->media_target();
} else {
return &media_target_;
#define Provider(type, name, member) \
type *MultiMachine::name() { \
if(has_picked_) { \
return machines_.front()->name(); \
} else { \
return &member; \
} \
}
}
CRTMachine::Machine *MultiMachine::crt_machine() {
if(has_picked_) {
return machines_.front()->crt_machine();
} else {
return &crt_machine_;
}
}
Provider(Configurable::Device, configurable_device, configurable_)
Provider(MachineTypes::TimedMachine, timed_machine, timed_machine_)
Provider(MachineTypes::ScanProducer, scan_producer, scan_producer_)
Provider(MachineTypes::AudioProducer, audio_producer, audio_producer_)
Provider(MachineTypes::JoystickMachine, joystick_machine, joystick_machine_)
Provider(MachineTypes::KeyboardMachine, keyboard_machine, keyboard_machine_)
Provider(MachineTypes::MediaTarget, media_target, media_target_)
JoystickMachine::Machine *MultiMachine::joystick_machine() {
if(has_picked_) {
return machines_.front()->joystick_machine();
} else {
return &joystick_machine_;
}
}
KeyboardMachine::Machine *MultiMachine::keyboard_machine() {
if(has_picked_) {
return machines_.front()->keyboard_machine();
} else {
return &keyboard_machine_;
}
}
MouseMachine::Machine *MultiMachine::mouse_machine() {
MachineTypes::MouseMachine *MultiMachine::mouse_machine() {
// TODO.
return nullptr;
}
Configurable::Device *MultiMachine::configurable_device() {
if(has_picked_) {
return machines_.front()->configurable_device();
} else {
return &configurable_;
}
}
#undef Provider
bool MultiMachine::would_collapse(const std::vector<std::unique_ptr<DynamicMachine>> &machines) {
return
(machines.front()->crt_machine()->get_confidence() > 0.9f) ||
(machines.front()->crt_machine()->get_confidence() >= 2.0f * machines[1]->crt_machine()->get_confidence());
(machines.front()->timed_machine()->get_confidence() > 0.9f) ||
(machines.front()->timed_machine()->get_confidence() >= 2.0f * machines[1]->timed_machine()->get_confidence());
}
void MultiMachine::multi_crt_did_run_machines() {
std::lock_guard<decltype(machines_mutex_)> machines_lock(machines_mutex_);
void MultiMachine::did_run_machines(MultiTimedMachine *) {
std::lock_guard machines_lock(machines_mutex_);
#ifndef NDEBUG
for(const auto &machine: machines_) {
CRTMachine::Machine *crt = machine->crt_machine();
LOGNBR(PADHEX(2) << crt->get_confidence() << " " << crt->debug_type() << "; ");
auto timed_machine = machine->timed_machine();
LOGNBR(PADHEX(2) << timed_machine->get_confidence() << " " << timed_machine->debug_type() << "; ");
}
LOGNBR(std::endl);
#endif
@@ -91,13 +72,14 @@ void MultiMachine::multi_crt_did_run_machines() {
DynamicMachine *front = machines_.front().get();
std::stable_sort(machines_.begin(), machines_.end(),
[] (const std::unique_ptr<DynamicMachine> &lhs, const std::unique_ptr<DynamicMachine> &rhs){
CRTMachine::Machine *lhs_crt = lhs->crt_machine();
CRTMachine::Machine *rhs_crt = rhs->crt_machine();
return lhs_crt->get_confidence() > rhs_crt->get_confidence();
auto lhs_timed = lhs->timed_machine();
auto rhs_timed = rhs->timed_machine();
return lhs_timed->get_confidence() > rhs_timed->get_confidence();
});
if(machines_.front().get() != front) {
crt_machine_.did_change_machine_order();
scan_producer_.did_change_machine_order();
audio_producer_.did_change_machine_order();
}
if(would_collapse(machines_)) {
@@ -107,9 +89,28 @@ void MultiMachine::multi_crt_did_run_machines() {
void MultiMachine::pick_first() {
has_picked_ = true;
// Ensure output rate specifics are properly copied; these may be set only once by the owner,
// but rather than being propagated directly by the MultiSpeaker only the derived computed
// output rate is propagated. So this ensures that if a new derivation is made, it's made correctly.
if(machines_[0]->audio_producer()) {
auto multi_speaker = audio_producer_.get_speaker();
auto specific_speaker = machines_[0]->audio_producer()->get_speaker();
if(specific_speaker && multi_speaker) {
specific_speaker->copy_output_rate(*multi_speaker);
}
}
// TODO: because it is not invalid for a caller to keep a reference to anything previously returned,
// this erase can be added only once the Multi machines that take static copies of the machines list
// are updated.
//
// Example failing use case otherwise: a caller still has reference to the MultiJoystickMachine, and
// it has dangling references to the various JoystickMachines.
//
// This gets into particularly long grass with the MultiConfigurable and its MultiStruct.
// machines_.erase(machines_.begin() + 1, machines_.end());
// TODO: this isn't quite correct, because it may leak OpenGL/etc resources through failure to
// request a close_output while the context is active.
}
void *MultiMachine::raw_pointer() {

View File

@@ -11,8 +11,9 @@
#include "../../../Machines/DynamicMachine.hpp"
#include "Implementation/MultiProducer.hpp"
#include "Implementation/MultiConfigurable.hpp"
#include "Implementation/MultiCRTMachine.hpp"
#include "Implementation/MultiProducer.hpp"
#include "Implementation/MultiJoystickMachine.hpp"
#include "Implementation/MultiKeyboardMachine.hpp"
#include "Implementation/MultiMediaTarget.hpp"
@@ -38,7 +39,7 @@ namespace Dynamic {
If confidence for any machine becomes disproportionately low compared to
the others in the set, that machine stops running.
*/
class MultiMachine: public ::Machine::DynamicMachine, public MultiCRTMachine::Delegate {
class MultiMachine: public ::Machine::DynamicMachine, public MultiTimedMachine::Delegate {
public:
/*!
Allows a potential MultiMachine creator to enquire as to whether there's any benefit in
@@ -50,23 +51,27 @@ class MultiMachine: public ::Machine::DynamicMachine, public MultiCRTMachine::De
static bool would_collapse(const std::vector<std::unique_ptr<DynamicMachine>> &machines);
MultiMachine(std::vector<std::unique_ptr<DynamicMachine>> &&machines);
Activity::Source *activity_source() override;
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;
Activity::Source *activity_source() final;
Configurable::Device *configurable_device() final;
MachineTypes::TimedMachine *timed_machine() final;
MachineTypes::ScanProducer *scan_producer() final;
MachineTypes::AudioProducer *audio_producer() final;
MachineTypes::JoystickMachine *joystick_machine() final;
MachineTypes::KeyboardMachine *keyboard_machine() final;
MachineTypes::MouseMachine *mouse_machine() final;
MachineTypes::MediaTarget *media_target() final;
void *raw_pointer() final;
private:
void multi_crt_did_run_machines() override;
void did_run_machines(MultiTimedMachine *) final;
std::vector<std::unique_ptr<DynamicMachine>> machines_;
std::recursive_mutex machines_mutex_;
MultiConfigurable configurable_;
MultiCRTMachine crt_machine_;
MultiTimedMachine timed_machine_;
MultiScanProducer scan_producer_;
MultiAudioProducer audio_producer_;
MultiJoystickMachine joystick_machine_;
MultiKeyboardMachine keyboard_machine_;
MultiMediaTarget media_target_;

View File

@@ -15,6 +15,7 @@ enum class Machine {
AmstradCPC,
AppleII,
Atari2600,
AtariST,
ColecoVision,
Electron,
Macintosh,

View File

@@ -10,7 +10,7 @@
#include "../../../Storage/Disk/Controller/DiskController.hpp"
#include "../../../Storage/Disk/Encodings/MFM/Parser.hpp"
#include "../../../NumberTheory/CRC.hpp"
#include "../../../Numeric/CRC.hpp"
#include <algorithm>
@@ -18,11 +18,11 @@ using namespace Analyser::Static::Acorn;
std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) {
// c.f. http://beebwiki.mdfs.net/Acorn_DFS_disc_format
std::unique_ptr<Catalogue> catalogue(new Catalogue);
auto catalogue = std::make_unique<Catalogue>();
Storage::Encodings::MFM::Parser parser(false, disk);
Storage::Encodings::MFM::Sector *names = parser.get_sector(0, 0, 0);
Storage::Encodings::MFM::Sector *details = parser.get_sector(0, 0, 1);
const Storage::Encodings::MFM::Sector *const names = parser.get_sector(0, 0, 0);
const Storage::Encodings::MFM::Sector *const details = parser.get_sector(0, 0, 1);
if(!names || !details) return nullptr;
if(names->samples.empty() || details->samples.empty()) return nullptr;
@@ -48,18 +48,18 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::s
char name[10];
snprintf(name, 10, "%c.%.7s", names->samples[0][file_offset + 7] & 0x7f, &names->samples[0][file_offset]);
new_file.name = name;
new_file.load_address = (uint32_t)(details->samples[0][file_offset] | (details->samples[0][file_offset+1] << 8) | ((details->samples[0][file_offset+6]&0x0c) << 14));
new_file.execution_address = (uint32_t)(details->samples[0][file_offset+2] | (details->samples[0][file_offset+3] << 8) | ((details->samples[0][file_offset+6]&0xc0) << 10));
new_file.is_protected = !!(names->samples[0][file_offset + 7] & 0x80);
new_file.load_address = uint32_t(details->samples[0][file_offset] | (details->samples[0][file_offset+1] << 8) | ((details->samples[0][file_offset+6]&0x0c) << 14));
new_file.execution_address = uint32_t(details->samples[0][file_offset+2] | (details->samples[0][file_offset+3] << 8) | ((details->samples[0][file_offset+6]&0xc0) << 10));
new_file.is_protected = names->samples[0][file_offset + 7] & 0x80;
long data_length = static_cast<long>(details->samples[0][file_offset+4] | (details->samples[0][file_offset+5] << 8) | ((details->samples[0][file_offset+6]&0x30) << 12));
long data_length = long(details->samples[0][file_offset+4] | (details->samples[0][file_offset+5] << 8) | ((details->samples[0][file_offset+6]&0x30) << 12));
int start_sector = details->samples[0][file_offset+7] | ((details->samples[0][file_offset+6]&0x03) << 8);
new_file.data.reserve(static_cast<std::size_t>(data_length));
new_file.data.reserve(size_t(data_length));
if(start_sector < 2) continue;
while(data_length > 0) {
uint8_t sector = static_cast<uint8_t>(start_sector % 10);
uint8_t track = static_cast<uint8_t>(start_sector / 10);
uint8_t sector = uint8_t(start_sector % 10);
uint8_t track = uint8_t(start_sector / 10);
start_sector++;
Storage::Encodings::MFM::Sector *next_sector = parser.get_sector(0, track, sector);
@@ -75,7 +75,7 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::s
return catalogue;
}
std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) {
std::unique_ptr<Catalogue> catalogue(new Catalogue);
auto catalogue = std::make_unique<Catalogue>();
Storage::Encodings::MFM::Parser parser(true, disk);
Storage::Encodings::MFM::Sector *free_space_map_second_half = parser.get_sector(0, 0, 1);
@@ -84,7 +84,7 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::
std::vector<uint8_t> root_directory;
root_directory.reserve(5 * 256);
for(uint8_t c = 2; c < 7; c++) {
Storage::Encodings::MFM::Sector *sector = parser.get_sector(0, 0, c);
const Storage::Encodings::MFM::Sector *const sector = parser.get_sector(0, 0, c);
if(!sector) return nullptr;
root_directory.insert(root_directory.end(), sector->samples[0].begin(), sector->samples[0].end());
}

View File

@@ -29,7 +29,7 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
if(segment.data.size() != 0x4000 && segment.data.size() != 0x2000) continue;
// is a copyright string present?
uint8_t copyright_offset = segment.data[7];
const uint8_t copyright_offset = segment.data[7];
if(
segment.data[copyright_offset] != 0x00 ||
segment.data[copyright_offset+1] != 0x28 ||
@@ -57,9 +57,8 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
return acorn_cartridges;
}
Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
std::unique_ptr<Target> target(new Target);
target->machine = Machine::Electron;
Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
auto target = std::make_unique<Target>();
target->confidence = 0.5; // TODO: a proper estimation
target->has_dfs = false;
target->has_adfs = false;
@@ -84,8 +83,8 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
// check also for a continuous threading of BASIC lines; if none then this probably isn't BASIC code,
// so that's also justification to *RUN
std::size_t pointer = 0;
uint8_t *data = &files.front().data[0];
std::size_t data_size = files.front().data.size();
uint8_t *const data = &files.front().data[0];
const std::size_t data_size = files.front().data.size();
while(1) {
if(pointer >= data_size-1 || data[pointer] != 13) {
is_basic = false;

View File

@@ -10,13 +10,13 @@
#include <deque>
#include "../../../NumberTheory/CRC.hpp"
#include "../../../Numeric/CRC.hpp"
#include "../../../Storage/Tape/Parsers/Acorn.hpp"
using namespace Analyser::Static::Acorn;
static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::Tape::Tape> &tape, Storage::Tape::Acorn::Parser &parser) {
std::unique_ptr<File::Chunk> new_chunk(new File::Chunk);
auto new_chunk = std::make_unique<File::Chunk>();
int shift_register = 0;
// TODO: move this into the parser
@@ -41,24 +41,24 @@ static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::
char name[11];
std::size_t name_ptr = 0;
while(!tape->is_at_end() && name_ptr < sizeof(name)) {
name[name_ptr] = (char)parser.get_next_byte(tape);
name[name_ptr] = char(parser.get_next_byte(tape));
if(!name[name_ptr]) break;
name_ptr++;
++name_ptr;
}
name[sizeof(name)-1] = '\0';
new_chunk->name = name;
// addresses
new_chunk->load_address = (uint32_t)parser.get_next_word(tape);
new_chunk->execution_address = (uint32_t)parser.get_next_word(tape);
new_chunk->block_number = static_cast<uint16_t>(parser.get_next_short(tape));
new_chunk->block_length = static_cast<uint16_t>(parser.get_next_short(tape));
new_chunk->block_flag = static_cast<uint8_t>(parser.get_next_byte(tape));
new_chunk->next_address = (uint32_t)parser.get_next_word(tape);
new_chunk->load_address = uint32_t(parser.get_next_word(tape));
new_chunk->execution_address = uint32_t(parser.get_next_word(tape));
new_chunk->block_number = uint16_t(parser.get_next_short(tape));
new_chunk->block_length = uint16_t(parser.get_next_short(tape));
new_chunk->block_flag = uint8_t(parser.get_next_byte(tape));
new_chunk->next_address = uint32_t(parser.get_next_word(tape));
uint16_t calculated_header_crc = parser.get_crc();
uint16_t stored_header_crc = static_cast<uint16_t>(parser.get_next_short(tape));
stored_header_crc = static_cast<uint16_t>((stored_header_crc >> 8) | (stored_header_crc << 8));
uint16_t stored_header_crc = uint16_t(parser.get_next_short(tape));
stored_header_crc = uint16_t((stored_header_crc >> 8) | (stored_header_crc << 8));
new_chunk->header_crc_matched = stored_header_crc == calculated_header_crc;
if(!new_chunk->header_crc_matched) return nullptr;
@@ -66,13 +66,13 @@ static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::
parser.reset_crc();
new_chunk->data.reserve(new_chunk->block_length);
for(int c = 0; c < new_chunk->block_length; c++) {
new_chunk->data.push_back(static_cast<uint8_t>(parser.get_next_byte(tape)));
new_chunk->data.push_back(uint8_t(parser.get_next_byte(tape)));
}
if(new_chunk->block_length && !(new_chunk->block_flag&0x40)) {
uint16_t calculated_data_crc = parser.get_crc();
uint16_t stored_data_crc = static_cast<uint16_t>(parser.get_next_short(tape));
stored_data_crc = static_cast<uint16_t>((stored_data_crc >> 8) | (stored_data_crc << 8));
uint16_t stored_data_crc = uint16_t(parser.get_next_short(tape));
stored_data_crc = uint16_t((stored_data_crc >> 8) | (stored_data_crc << 8));
new_chunk->data_crc_matched = stored_data_crc == calculated_data_crc;
} else {
new_chunk->data_crc_matched = true;
@@ -90,7 +90,7 @@ static std::unique_ptr<File> GetNextFile(std::deque<File::Chunk> &chunks) {
if(!chunks.size()) return nullptr;
// accumulate chunks for as long as block number is sequential and the end-of-file bit isn't set
std::unique_ptr<File> file(new File);
auto file = std::make_unique<File>();
uint16_t block_number = 0;

View File

@@ -9,6 +9,7 @@
#ifndef Analyser_Static_Acorn_Target_h
#define Analyser_Static_Acorn_Target_h
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
#include <string>
@@ -16,11 +17,18 @@ namespace Analyser {
namespace Static {
namespace Acorn {
struct Target: public ::Analyser::Static::Target {
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
bool has_adfs = false;
bool has_dfs = false;
bool should_shift_restart = false;
std::string loading_command;
Target() : Analyser::Static::Target(Machine::Electron) {
if(needs_declare()) {
DeclareField(has_adfs);
DeclareField(has_dfs);
}
}
};
}

View File

@@ -179,10 +179,9 @@ static bool CheckBootSector(const std::shared_ptr<Storage::Disk::Disk> &disk, co
return false;
}
Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
TargetList destination;
std::unique_ptr<Target> target(new Target);
target->machine = Machine::AmstradCPC;
auto target = std::make_unique<Target>();
target->confidence = 0.5;
target->model = Target::Model::CPC6128;

View File

@@ -9,6 +9,8 @@
#ifndef Analyser_Static_AmstradCPC_Target_h
#define Analyser_Static_AmstradCPC_Target_h
#include "../../../Reflection/Enum.hpp"
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
#include <string>
@@ -16,15 +18,17 @@ namespace Analyser {
namespace Static {
namespace AmstradCPC {
struct Target: public ::Analyser::Static::Target {
enum class Model {
CPC464,
CPC664,
CPC6128
};
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(Model, CPC464, CPC664, CPC6128);
Model model = Model::CPC464;
std::string loading_command;
Target() : Analyser::Static::Target(Machine::AmstradCPC) {
if(needs_declare()) {
DeclareField(model);
AnnounceEnum(Model);
}
}
};
}

View File

@@ -9,9 +9,8 @@
#include "StaticAnalyser.hpp"
#include "Target.hpp"
Analyser::Static::TargetList Analyser::Static::AppleII::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
auto target = std::unique_ptr<Target>(new Target);
target->machine = Machine::AppleII;
Analyser::Static::TargetList Analyser::Static::AppleII::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
auto target = std::make_unique<Target>();
target->media = media;
if(!target->media.disks.empty())

View File

@@ -9,27 +9,38 @@
#ifndef Target_h
#define Target_h
#include "../../../Reflection/Enum.hpp"
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
namespace Analyser {
namespace Static {
namespace AppleII {
struct Target: public ::Analyser::Static::Target {
enum class Model {
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(Model,
II,
IIplus,
IIe,
EnhancedIIe
};
enum class DiskController {
);
ReflectableEnum(DiskController,
None,
SixteenSector,
ThirteenSector
};
);
Model model = Model::IIe;
DiskController disk_controller = DiskController::None;
Target() : Analyser::Static::Target(Machine::AppleII) {
if(needs_declare()) {
DeclareField(model);
DeclareField(disk_controller);
AnnounceEnum(Model);
AnnounceEnum(DiskController);
}
}
};
}

View File

@@ -12,27 +12,26 @@
#include "../Disassembler/6502.hpp"
using namespace Analyser::Static::Atari;
using namespace Analyser::Static::Atari2600;
using Target = Analyser::Static::Atari2600::Target;
static void DeterminePagingFor2kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) {
// if this is a 2kb cartridge then it's definitely either unpaged or a CommaVid
uint16_t entry_address, break_address;
static void DeterminePagingFor2kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment) {
// If this is a 2kb cartridge then it's definitely either unpaged or a CommaVid.
const uint16_t entry_address = uint16_t(segment.data[0x7fc] | (segment.data[0x7fd] << 8)) & 0x1fff;
const uint16_t break_address = uint16_t(segment.data[0x7fe] | (segment.data[0x7ff] << 8)) & 0x1fff;
entry_address = (static_cast<uint16_t>(segment.data[0x7fc] | (segment.data[0x7fd] << 8))) & 0x1fff;
break_address = (static_cast<uint16_t>(segment.data[0x7fe] | (segment.data[0x7ff] << 8))) & 0x1fff;
// a CommaVid start address needs to be outside of its RAM
// A CommaVid start address needs to be outside of its RAM.
if(entry_address < 0x1800 || break_address < 0x1800) return;
std::function<std::size_t(uint16_t address)> high_location_mapper = [](uint16_t address) {
address &= 0x1fff;
return static_cast<std::size_t>(address - 0x1800);
return size_t(address - 0x1800);
};
Analyser::Static::MOS6502::Disassembly high_location_disassembly =
Analyser::Static::MOS6502::Disassemble(segment.data, high_location_mapper, {entry_address, break_address});
// assume that any kind of store that looks likely to be intended for large amounts of memory implies
// large amounts of memory
// Assume that any kind of store that looks likely to be intended for large amounts of memory implies
// large amounts of memory.
bool has_wide_area_store = false;
for(std::map<uint16_t, Analyser::Static::MOS6502::Instruction>::value_type &entry : high_location_disassembly.instructions_by_address) {
if(entry.second.operation == Analyser::Static::MOS6502::Instruction::STA) {
@@ -44,28 +43,28 @@ static void DeterminePagingFor2kCartridge(Analyser::Static::Atari::Target &targe
}
}
// conclude that this is a CommaVid if it attempted to write something to the CommaVid RAM locations;
// Conclude that this is a CommaVid if it attempted to write something to the CommaVid RAM locations;
// caveat: false positives aren't likely to be problematic; a false positive is a 2KB ROM that always addresses
// itself so as to land in ROM even if mapped as a CommaVid and this code is on the fence as to whether it
// attempts to modify itself but it probably doesn't
if(has_wide_area_store) target.paging_model = Analyser::Static::Atari::Target::PagingModel::CommaVid;
// attempts to modify itself but it probably doesn't.
if(has_wide_area_store) target.paging_model = Target::PagingModel::CommaVid;
}
static void DeterminePagingFor8kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
static void DeterminePagingFor8kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
// Activision stack titles have their vectors at the top of the low 4k, not the top, and
// always list 0xf000 as both vectors; they do not repeat them, and, inexplicably, they all
// issue an SEI as their first instruction (maybe some sort of relic of the development environment?)
// issue an SEI as their first instruction (maybe some sort of relic of the development environment?).
if(
segment.data[4095] == 0xf0 && segment.data[4093] == 0xf0 && segment.data[4094] == 0x00 && segment.data[4092] == 0x00 &&
(segment.data[8191] != 0xf0 || segment.data[8189] != 0xf0 || segment.data[8190] != 0x00 || segment.data[8188] != 0x00) &&
segment.data[0] == 0x78
) {
target.paging_model = Analyser::Static::Atari::Target::PagingModel::ActivisionStack;
target.paging_model = Target::PagingModel::ActivisionStack;
return;
}
// make an assumption that this is the Atari paging model
target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari8k;
// Make an assumption that this is the Atari paging model.
target.paging_model = Target::PagingModel::Atari8k;
std::set<uint16_t> internal_accesses;
internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end());
@@ -85,13 +84,13 @@ static void DeterminePagingFor8kCartridge(Analyser::Static::Atari::Target &targe
tigervision_access_count += masked_address == 0x3f;
}
if(parker_access_count > atari_access_count) target.paging_model = Analyser::Static::Atari::Target::PagingModel::ParkerBros;
else if(tigervision_access_count > atari_access_count) target.paging_model = Analyser::Static::Atari::Target::PagingModel::Tigervision;
if(parker_access_count > atari_access_count) target.paging_model = Target::PagingModel::ParkerBros;
else if(tigervision_access_count > atari_access_count) target.paging_model = Target::PagingModel::Tigervision;
}
static void DeterminePagingFor16kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
// make an assumption that this is the Atari paging model
target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari16k;
static void DeterminePagingFor16kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &, const Analyser::Static::MOS6502::Disassembly &disassembly) {
// Make an assumption that this is the Atari paging model.
target.paging_model = Target::PagingModel::Atari16k;
std::set<uint16_t> internal_accesses;
internal_accesses.insert(disassembly.internal_stores.begin(), disassembly.internal_stores.end());
@@ -106,33 +105,31 @@ static void DeterminePagingFor16kCartridge(Analyser::Static::Atari::Target &targ
mnetwork_access_count += masked_address >= 0x1fe0 && masked_address < 0x1ffb;
}
if(mnetwork_access_count > atari_access_count) target.paging_model = Analyser::Static::Atari::Target::PagingModel::MNetwork;
if(mnetwork_access_count > atari_access_count) target.paging_model = Target::PagingModel::MNetwork;
}
static void DeterminePagingFor64kCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment, const Analyser::Static::MOS6502::Disassembly &disassembly) {
// make an assumption that this is a Tigervision if there is a write to 3F
static void DeterminePagingFor64kCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &, const Analyser::Static::MOS6502::Disassembly &disassembly) {
// Make an assumption that this is a Tigervision if there is a write to 3F.
target.paging_model =
(disassembly.external_stores.find(0x3f) != disassembly.external_stores.end()) ?
Analyser::Static::Atari::Target::PagingModel::Tigervision : Analyser::Static::Atari::Target::PagingModel::MegaBoy;
Target::PagingModel::Tigervision : Target::PagingModel::MegaBoy;
}
static void DeterminePagingForCartridge(Analyser::Static::Atari::Target &target, const Storage::Cartridge::Cartridge::Segment &segment) {
static void DeterminePagingForCartridge(Target &target, const Storage::Cartridge::Cartridge::Segment &segment) {
if(segment.data.size() == 2048) {
DeterminePagingFor2kCartridge(target, segment);
return;
}
uint16_t entry_address, break_address;
entry_address = static_cast<uint16_t>(segment.data[segment.data.size() - 4] | (segment.data[segment.data.size() - 3] << 8));
break_address = static_cast<uint16_t>(segment.data[segment.data.size() - 2] | (segment.data[segment.data.size() - 1] << 8));
const uint16_t entry_address = uint16_t(segment.data[segment.data.size() - 4] | (segment.data[segment.data.size() - 3] << 8));
const uint16_t break_address = uint16_t(segment.data[segment.data.size() - 2] | (segment.data[segment.data.size() - 1] << 8));
std::function<std::size_t(uint16_t address)> address_mapper = [](uint16_t address) {
if(!(address & 0x1000)) return static_cast<std::size_t>(-1);
return static_cast<std::size_t>(address & 0xfff);
if(!(address & 0x1000)) return size_t(-1);
return size_t(address & 0xfff);
};
std::vector<uint8_t> final_4k(segment.data.end() - 4096, segment.data.end());
const std::vector<uint8_t> final_4k(segment.data.end() - 4096, segment.data.end());
Analyser::Static::MOS6502::Disassembly disassembly = Analyser::Static::MOS6502::Disassemble(final_4k, address_mapper, {entry_address, break_address});
switch(segment.data.size()) {
@@ -140,16 +137,16 @@ static void DeterminePagingForCartridge(Analyser::Static::Atari::Target &target,
DeterminePagingFor8kCartridge(target, segment, disassembly);
break;
case 10495:
target.paging_model = Analyser::Static::Atari::Target::PagingModel::Pitfall2;
target.paging_model = Target::PagingModel::Pitfall2;
break;
case 12288:
target.paging_model = Analyser::Static::Atari::Target::PagingModel::CBSRamPlus;
target.paging_model = Target::PagingModel::CBSRamPlus;
break;
case 16384:
DeterminePagingFor16kCartridge(target, segment, disassembly);
break;
case 32768:
target.paging_model = Analyser::Static::Atari::Target::PagingModel::Atari32k;
target.paging_model = Target::PagingModel::Atari32k;
break;
case 65536:
DeterminePagingFor64kCartridge(target, segment, disassembly);
@@ -158,11 +155,11 @@ static void DeterminePagingForCartridge(Analyser::Static::Atari::Target &target,
break;
}
// check for a Super Chip. Atari ROM images [almost] always have the same value stored over RAM
// Check for a Super Chip. Atari ROM images [almost] always have the same value stored over RAM
// regions; when they don't they at least seem to have the first 128 bytes be the same as the
// next 128 bytes. So check for that.
if( target.paging_model != Analyser::Static::Atari::Target::PagingModel::CBSRamPlus &&
target.paging_model != Analyser::Static::Atari::Target::PagingModel::MNetwork) {
if( target.paging_model != Target::PagingModel::CBSRamPlus &&
target.paging_model != Target::PagingModel::MNetwork) {
bool has_superchip = true;
for(std::size_t address = 0; address < 128; address++) {
if(segment.data[address] != segment.data[address+128]) {
@@ -173,20 +170,19 @@ static void DeterminePagingForCartridge(Analyser::Static::Atari::Target &target,
target.uses_superchip = has_superchip;
}
// check for a Tigervision or Tigervision-esque scheme
if(target.paging_model == Analyser::Static::Atari::Target::PagingModel::None && segment.data.size() > 4096) {
// Check for a Tigervision or Tigervision-esque scheme
if(target.paging_model == Target::PagingModel::None && segment.data.size() > 4096) {
bool looks_like_tigervision = disassembly.external_stores.find(0x3f) != disassembly.external_stores.end();
if(looks_like_tigervision) target.paging_model = Analyser::Static::Atari::Target::PagingModel::Tigervision;
if(looks_like_tigervision) target.paging_model = Target::PagingModel::Tigervision;
}
}
Analyser::Static::TargetList Analyser::Static::Atari::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
Analyser::Static::TargetList Analyser::Static::Atari2600::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
// TODO: sanity checking; is this image really for an Atari 2600?
std::unique_ptr<Analyser::Static::Atari::Target> target(new Analyser::Static::Atari::Target);
target->machine = Machine::Atari2600;
auto target = std::make_unique<Target>();
target->confidence = 0.5;
target->media.cartridges = media.cartridges;
target->paging_model = Analyser::Static::Atari::Target::PagingModel::None;
target->paging_model = Target::PagingModel::None;
target->uses_superchip = false;
// try to figure out the paging scheme

View File

@@ -15,7 +15,7 @@
namespace Analyser {
namespace Static {
namespace Atari {
namespace Atari2600 {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);

View File

@@ -6,14 +6,14 @@
// Copyright 2018 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_Atari_Target_h
#define Analyser_Static_Atari_Target_h
#ifndef Analyser_Static_Atari2600_Target_h
#define Analyser_Static_Atari2600_Target_h
#include "../StaticAnalyser.hpp"
namespace Analyser {
namespace Static {
namespace Atari {
namespace Atari2600 {
struct Target: public ::Analyser::Static::Target {
enum class PagingModel {
@@ -34,6 +34,8 @@ struct Target: public ::Analyser::Static::Target {
// TODO: shouldn't these be properties of the cartridge?
PagingModel paging_model = PagingModel::None;
bool uses_superchip = false;
Target() : Analyser::Static::Target(Machine::Atari2600) {}
};
}

View File

@@ -0,0 +1,25 @@
//
// StaticAnalyser.cpp
// Clock Signal
//
// Created by Thomas Harte on 03/10/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#include "StaticAnalyser.hpp"
#include "Target.hpp"
Analyser::Static::TargetList Analyser::Static::AtariST::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
// This analyser can comprehend disks and mass-storage devices only.
if(media.disks.empty()) return {};
// As there is at least one usable media image, wave it through.
Analyser::Static::TargetList targets;
using Target = Analyser::Static::AtariST::Target;
auto *const target = new Target();
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 03/10/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_AtariST_StaticAnalyser_hpp
#define Analyser_Static_AtariST_StaticAnalyser_hpp
#include "../StaticAnalyser.hpp"
#include "../../../Storage/TargetPlatforms.hpp"
#include <string>
namespace Analyser {
namespace Static {
namespace AtariST {
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
}
}
}
#endif /* Analyser_Static_AtariST_StaticAnalyser_hpp */

View File

@@ -0,0 +1,27 @@
//
// Target.hpp
// Clock Signal
//
// Created by Thomas Harte on 03/06/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef Analyser_Static_AtariST_Target_h
#define Analyser_Static_AtariST_Target_h
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
namespace Analyser {
namespace Static {
namespace AtariST {
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
Target() : Analyser::Static::Target(Machine::AtariST) {}
};
}
}
}
#endif /* Analyser_Static_AtariST_Target_h */

View File

@@ -22,7 +22,7 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
// the two bytes that will be first must be 0xaa and 0x55, either way around
auto *start = &segment.data[0];
if((data_size & static_cast<std::size_t>(~8191)) > 32768) {
if((data_size & size_t(~8191)) > 32768) {
start = &segment.data[segment.data.size() - 16384];
}
if(start[0] != 0xaa && start[0] != 0x55 && start[1] != 0xaa && start[1] != 0x55) continue;
@@ -52,10 +52,9 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
return coleco_cartridges;
}
Analyser::Static::TargetList Analyser::Static::Coleco::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
Analyser::Static::TargetList Analyser::Static::Coleco::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
TargetList targets;
std::unique_ptr<Target> target(new Target);
target->machine = Machine::ColecoVision;
auto target = std::make_unique<Target>(Machine::ColecoVision);
target->confidence = 1.0f - 1.0f / 32768.0f;
target->media.cartridges = ColecoCartridgesFrom(media.cartridges);
if(!target->media.empty())

View File

@@ -19,12 +19,10 @@ using namespace Analyser::Static::Commodore;
class CommodoreGCRParser: public Storage::Disk::Controller {
public:
std::shared_ptr<Storage::Disk::Drive> drive;
CommodoreGCRParser() : Storage::Disk::Controller(4000000), shift_register_(0), track_(1) {
drive.reset(new Storage::Disk::Drive(4000000, 300, 2));
set_drive(drive);
drive->set_motor_on(true);
emplace_drive(4000000, 300, 2);
set_drive(1);
get_drive().set_motor_on(true);
}
struct Sector {
@@ -40,7 +38,7 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
@returns a sector if one was found; @c nullptr otherwise.
*/
std::shared_ptr<Sector> get_sector(uint8_t track, uint8_t sector) {
int difference = static_cast<int>(track) - static_cast<int>(track_);
int difference = int(track) - int(track_);
track_ = track;
if(difference) {
@@ -61,6 +59,10 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
return get_sector(sector);
}
void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk) {
get_drive().set_disk(disk);
}
private:
unsigned int shift_register_;
int index_count_;
@@ -69,7 +71,7 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
std::shared_ptr<Sector> sector_cache_[65536];
void process_input_bit(int value) {
shift_register_ = ((shift_register_ << 1) | static_cast<unsigned int>(value)) & 0x3ff;
shift_register_ = ((shift_register_ << 1) | unsigned(value)) & 0x3ff;
bit_count_++;
}
@@ -110,22 +112,22 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
}
std::shared_ptr<Sector> get_sector(uint8_t sector) {
uint16_t sector_address = static_cast<uint16_t>((track_ << 8) | sector);
const uint16_t sector_address = uint16_t((track_ << 8) | sector);
if(sector_cache_[sector_address]) return sector_cache_[sector_address];
std::shared_ptr<Sector> first_sector = get_next_sector();
const std::shared_ptr<Sector> first_sector = get_next_sector();
if(!first_sector) return first_sector;
if(first_sector->sector == sector) return first_sector;
while(1) {
std::shared_ptr<Sector> next_sector = get_next_sector();
const std::shared_ptr<Sector> next_sector = get_next_sector();
if(next_sector->sector == first_sector->sector) return nullptr;
if(next_sector->sector == sector) return next_sector;
}
}
std::shared_ptr<Sector> get_next_sector() {
std::shared_ptr<Sector> sector(new Sector);
auto sector = std::make_shared<Sector>();
const int max_index_count = index_count_ + 2;
while(index_count_ < max_index_count) {
@@ -136,12 +138,12 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
}
// get sector details, skip if this looks malformed
uint8_t checksum = static_cast<uint8_t>(get_next_byte());
sector->sector = static_cast<uint8_t>(get_next_byte());
sector->track = static_cast<uint8_t>(get_next_byte());
uint8_t checksum = uint8_t(get_next_byte());
sector->sector = uint8_t(get_next_byte());
sector->track = uint8_t(get_next_byte());
uint8_t disk_id[2];
disk_id[0] = static_cast<uint8_t>(get_next_byte());
disk_id[1] = static_cast<uint8_t>(get_next_byte());
disk_id[0] = uint8_t(get_next_byte());
disk_id[1] = uint8_t(get_next_byte());
if(checksum != (sector->sector ^ sector->track ^ disk_id[0] ^ disk_id[1])) continue;
// look for the following data
@@ -152,12 +154,12 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
checksum = 0;
for(std::size_t c = 0; c < 256; c++) {
sector->data[c] = static_cast<uint8_t>(get_next_byte());
sector->data[c] = uint8_t(get_next_byte());
checksum ^= sector->data[c];
}
if(checksum == get_next_byte()) {
uint16_t sector_address = static_cast<uint16_t>((sector->track << 8) | sector->sector);
uint16_t sector_address = uint16_t((sector->track << 8) | sector->sector);
sector_cache_[sector_address] = sector;
return sector;
}
@@ -170,7 +172,7 @@ class CommodoreGCRParser: public Storage::Disk::Controller {
std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<Storage::Disk::Disk> &disk) {
std::vector<File> files;
CommodoreGCRParser parser;
parser.drive->set_disk(disk);
parser.set_disk(disk);
// find any sector whatsoever to establish the current track
std::shared_ptr<CommodoreGCRParser::Sector> sector;
@@ -190,7 +192,7 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<St
}
// parse directory
std::size_t header_pointer = static_cast<std::size_t>(-32);
std::size_t header_pointer = size_t(-32);
while(header_pointer+32+31 < directory.size()) {
header_pointer += 32;
@@ -214,7 +216,7 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<St
}
new_file.name = Storage::Data::Commodore::petscii_from_bytes(&new_file.raw_name[0], 16, false);
std::size_t number_of_sectors = static_cast<std::size_t>(directory[header_pointer + 0x1e]) + (static_cast<std::size_t>(directory[header_pointer + 0x1f]) << 8);
std::size_t number_of_sectors = size_t(directory[header_pointer + 0x1e]) + (size_t(directory[header_pointer + 0x1f]) << 8);
new_file.data.reserve((number_of_sectors - 1) * 254 + 252);
bool is_first_sector = true;
@@ -225,7 +227,7 @@ std::vector<File> Analyser::Static::Commodore::GetFiles(const std::shared_ptr<St
next_track = sector->data[0];
next_sector = sector->data[1];
if(is_first_sector) new_file.starting_address = static_cast<uint16_t>(sector->data[2]) | static_cast<uint16_t>(sector->data[3] << 8);
if(is_first_sector) new_file.starting_address = uint16_t(sector->data[2]) | uint16_t(sector->data[3] << 8);
if(next_track)
new_file.data.insert(new_file.data.end(), sector->data.begin() + (is_first_sector ? 4 : 2), sector->data.end());
else

View File

@@ -23,7 +23,7 @@ bool Analyser::Static::Commodore::File::is_basic() {
// ... null-terminated code ...
// (with a next line address of 0000 indicating end of program)
while(1) {
if(static_cast<size_t>(line_address - starting_address) >= data.size() + 2) break;
if(size_t(line_address - starting_address) >= data.size() + 2) break;
uint16_t next_line_address = data[line_address - starting_address];
next_line_address |= data[line_address - starting_address + 1] << 8;
@@ -33,13 +33,13 @@ bool Analyser::Static::Commodore::File::is_basic() {
}
if(next_line_address < line_address + 5) break;
if(static_cast<size_t>(line_address - starting_address) >= data.size() + 5) break;
if(size_t(line_address - starting_address) >= data.size() + 5) break;
uint16_t next_line_number = data[line_address - starting_address + 2];
next_line_number |= data[line_address - starting_address + 3] << 8;
if(next_line_number <= line_number) break;
line_number = static_cast<uint16_t>(next_line_number);
line_number = uint16_t(next_line_number);
line_address = next_line_address;
}

View File

@@ -16,6 +16,7 @@
#include "../../../Outputs/Log.hpp"
#include <algorithm>
#include <cstring>
#include <sstream>
using namespace Analyser::Static::Commodore;
@@ -41,10 +42,10 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
return vic20_cartridges;
}
Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType) {
TargetList destination;
std::unique_ptr<Target> target(new Target);
auto target = std::make_unique<Target>();
target->machine = Machine::Vic20; // TODO: machine estimation
target->confidence = 0.5; // TODO: a proper estimation
@@ -78,7 +79,7 @@ Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media
}
if(!files.empty()) {
target->memory_model = Target::MemoryModel::Unexpanded;
auto memory_model = Target::MemoryModel::Unexpanded;
std::ostringstream string_stream;
string_stream << "LOAD\"" << (is_disk ? "*" : "") << "\"," << device << ",";
if(files.front().is_basic()) {
@@ -93,17 +94,20 @@ Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media
switch(files.front().starting_address) {
default:
LOG("Unrecognised loading address for Commodore program: " << PADHEX(4) << files.front().starting_address);
[[fallthrough]];
case 0x1001:
target->memory_model = Target::MemoryModel::Unexpanded;
memory_model = Target::MemoryModel::Unexpanded;
break;
case 0x1201:
target->memory_model = Target::MemoryModel::ThirtyTwoKB;
memory_model = Target::MemoryModel::ThirtyTwoKB;
break;
case 0x0401:
target->memory_model = Target::MemoryModel::EightKB;
memory_model = Target::MemoryModel::EightKB;
break;
}
target->set_memory_model(memory_model);
// General approach: increase memory size conservatively such that the largest file found will fit.
// for(File &file : files) {
// std::size_t file_size = file.data.size();
@@ -145,13 +149,52 @@ Analyser::Static::TargetList Analyser::Static::Commodore::GetTargets(const Media
}
if(!target->media.empty()) {
// Inspect filename for a region hint.
// Inspect filename for configuration hints.
std::string lowercase_name = file_name;
std::transform(lowercase_name.begin(), lowercase_name.end(), lowercase_name.begin(), ::tolower);
// Hint 1: 'ntsc' anywhere in the name implies America.
if(lowercase_name.find("ntsc") != std::string::npos) {
target->region = Analyser::Static::Commodore::Target::Region::American;
}
// Potential additional hints: check for TheC64 tags.
auto final_underscore = lowercase_name.find_last_of('_');
if(final_underscore != std::string::npos) {
auto iterator = lowercase_name.begin() + ssize_t(final_underscore) + 1;
while(iterator != lowercase_name.end()) {
// Grab the next tag.
char next_tag[3] = {0, 0, 0};
next_tag[0] = *iterator++;
if(iterator == lowercase_name.end()) break;
next_tag[1] = *iterator++;
// Exit early if attempting to read another tag has run over the file extension.
if(next_tag[0] == '.' || next_tag[1] == '.') break;
// Check whether it's anything.
target->enabled_ram.bank0 |= !strcmp(next_tag, "b0");
target->enabled_ram.bank1 |= !strcmp(next_tag, "b1");
target->enabled_ram.bank2 |= !strcmp(next_tag, "b2");
target->enabled_ram.bank3 |= !strcmp(next_tag, "b3");
target->enabled_ram.bank5 |= !strcmp(next_tag, "b5");
if(!strcmp(next_tag, "tn")) { // i.e. NTSC.
target->region = Analyser::Static::Commodore::Target::Region::American;
}
if(!strcmp(next_tag, "tp")) { // i.e. PAL.
target->region = Analyser::Static::Commodore::Target::Region::European;
}
// Unhandled:
//
// M6: this is a C64 file.
// MV: this is a Vic-20 file.
// J1/J2: this C64 file should have the primary joystick in slot 1/2.
// RO: this disk image should be treated as read-only.
}
}
// Attach a 1540 if there are any disks here.
target->has_c1540 = !target->media.disks.empty();

View File

@@ -9,6 +9,8 @@
#ifndef Analyser_Static_Commodore_Target_h
#define Analyser_Static_Commodore_Target_h
#include "../../../Reflection/Enum.hpp"
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
#include <string>
@@ -16,25 +18,57 @@ namespace Analyser {
namespace Static {
namespace Commodore {
struct Target: public ::Analyser::Static::Target {
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
enum class MemoryModel {
Unexpanded,
EightKB,
ThirtyTwoKB
};
enum class Region {
ReflectableEnum(Region,
American,
Danish,
Japanese,
European,
Swedish
};
);
/// Maps from a named memory model to a bank enabled/disabled set.
void set_memory_model(MemoryModel memory_model) {
// This is correct for unexpanded and 32kb memory models.
enabled_ram.bank0 = enabled_ram.bank1 =
enabled_ram.bank2 = enabled_ram.bank3 =
enabled_ram.bank5 = memory_model == MemoryModel::ThirtyTwoKB;
// Bank 0 will need to be enabled if this is an 8kb machine.
enabled_ram.bank0 |= memory_model == MemoryModel::EightKB;
}
struct {
bool bank0 = false;
bool bank1 = false;
bool bank2 = false;
bool bank3 = false;
bool bank5 = false;
// Sic. There is no bank 4; this is because the area that logically would be
// bank 4 is occupied by the character ROM, colour RAM, hardware registers, etc.
} enabled_ram;
MemoryModel memory_model = MemoryModel::Unexpanded;
Region region = Region::European;
bool has_c1540 = false;
std::string loading_command;
Target() : Analyser::Static::Target(Machine::Vic20) {
if(needs_declare()) {
DeclareField(enabled_ram.bank0);
DeclareField(enabled_ram.bank1);
DeclareField(enabled_ram.bank2);
DeclareField(enabled_ram.bank3);
DeclareField(enabled_ram.bank5);
DeclareField(region);
DeclareField(has_c1540);
AnnounceEnum(Region);
}
}
};
}

View File

@@ -26,12 +26,12 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
Instruction instruction;
instruction.address = address;
address++;
++address;
// get operation
uint8_t operation = memory[local_address];
// Get operation.
const uint8_t operation = memory[local_address];
// decode addressing mode
// Decode addressing mode.
switch(operation&0x1f) {
case 0x00:
if(operation >= 0x80) instruction.addressing_mode = Instruction::Immediate;
@@ -74,7 +74,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
break;
}
// decode operation
// Decode operation.
#define RM_INSTRUCTION(base, op) \
case base+0x09: case base+0x05: case base+0x15: case base+0x01: case base+0x11: case base+0x0d: case base+0x1d: case base+0x19: \
instruction.operation = op; \
@@ -222,14 +222,14 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
#undef M_INSTRUCTION
#undef IM_INSTRUCTION
// get operand
// Get operand.
switch(instruction.addressing_mode) {
// zero-byte operands
// Zero-byte operands.
case Instruction::Implied:
instruction.operand = 0;
break;
// one-byte operands
// One-byte operands.
case Instruction::Immediate:
case Instruction::ZeroPage: case Instruction::ZeroPageX: case Instruction::ZeroPageY:
case Instruction::IndexedIndirectX: case Instruction::IndirectIndexedY:
@@ -242,7 +242,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
}
break;
// two-byte operands
// Two-byte operands.
case Instruction::Absolute: case Instruction::AbsoluteX: case Instruction::AbsoluteY:
case Instruction::Indirect: {
std::size_t low_operand_address = address_mapper(address);
@@ -250,18 +250,18 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
if(low_operand_address >= memory.size() || high_operand_address >= memory.size()) return;
address += 2;
instruction.operand = memory[low_operand_address] | static_cast<uint16_t>(memory[high_operand_address] << 8);
instruction.operand = memory[low_operand_address] | uint16_t(memory[high_operand_address] << 8);
}
break;
}
// store the instruction away
// Store the instruction.
disassembly.disassembly.instructions_by_address[instruction.address] = instruction;
// TODO: something wider-ranging than this
if(instruction.addressing_mode == Instruction::Absolute || instruction.addressing_mode == Instruction::ZeroPage) {
std::size_t mapped_address = address_mapper(instruction.operand);
bool is_external = mapped_address >= memory.size();
const size_t mapped_address = address_mapper(instruction.operand);
const bool is_external = mapped_address >= memory.size();
switch(instruction.operation) {
default: break;
@@ -290,7 +290,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
}
}
// decide on overall flow control
// Decide on overall flow control.
if(instruction.operation == Instruction::RTS || instruction.operation == Instruction::RTI) return;
if(instruction.operation == Instruction::BRK) return; // TODO: check whether IRQ vector is within memory range
if(instruction.operation == Instruction::JSR) {
@@ -302,7 +302,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
return;
}
if(instruction.addressing_mode == Instruction::Relative) {
uint16_t destination = static_cast<uint16_t>(address + (int8_t)instruction.operand);
uint16_t destination = uint16_t(address + int8_t(instruction.operand));
disassembly.remaining_entry_points.push_back(destination);
}
}

View File

@@ -1,9 +0,0 @@
//
// AddressMapper.cpp
// Clock Signal
//
// Created by Thomas Harte on 30/12/2017.
// Copyright 2017 Thomas Harte. All rights reserved.
//
#include "AddressMapper.hpp"

View File

@@ -21,7 +21,7 @@ namespace Disassembler {
*/
template <typename T> std::function<std::size_t(T)> OffsetMapper(T start_address) {
return [start_address](T argument) {
return static_cast<std::size_t>(argument - start_address);
return size_t(argument - start_address);
};
}

View File

@@ -33,7 +33,7 @@ class Accessor {
uint16_t word() {
uint8_t low = byte();
uint8_t high = byte();
return static_cast<uint16_t>(low | (high << 8));
return uint16_t(low | (high << 8));
}
bool overrun() {
@@ -562,7 +562,7 @@ struct Z80Disassembler {
int access_type =
((instruction.source == Instruction::Location::Operand_Indirect) ? 1 : 0) |
((instruction.destination == Instruction::Location::Operand_Indirect) ? 2 : 0);
uint16_t address = static_cast<uint16_t>(instruction.operand);
uint16_t address = uint16_t(instruction.operand);
bool is_internal = address_mapper(address) < memory.size();
switch(access_type) {
default: break;
@@ -594,7 +594,7 @@ struct Z80Disassembler {
instruction.operation == Instruction::Operation::JR ||
instruction.operation == Instruction::Operation::CALL ||
instruction.operation == Instruction::Operation::RST) {
disassembly.remaining_entry_points.push_back(static_cast<uint16_t>(instruction.operand));
disassembly.remaining_entry_points.push_back(uint16_t(instruction.operand));
}
// This is it if: an unconditional RET, RETI, RETN, JP or JR is found.

View File

@@ -20,8 +20,7 @@ namespace {
Analyser::Static::Target *AppleTarget(const Storage::Encodings::AppleGCR::Sector *sector_zero) {
using Target = Analyser::Static::AppleII::Target;
auto *target = new Target;
target->machine = Analyser::Machine::AppleII;
auto *const target = new Target;
if(sector_zero && sector_zero->encoding == Storage::Encodings::AppleGCR::Sector::Encoding::FiveAndThree) {
target->disk_controller = Target::DiskController::ThirteenSector;
@@ -32,10 +31,9 @@ Analyser::Static::Target *AppleTarget(const Storage::Encodings::AppleGCR::Sector
return target;
}
Analyser::Static::Target *OricTarget(const Storage::Encodings::AppleGCR::Sector *sector_zero) {
Analyser::Static::Target *OricTarget(const Storage::Encodings::AppleGCR::Sector *) {
using Target = Analyser::Static::Oric::Target;
auto *target = new Target;
target->machine = Analyser::Machine::Oric;
auto *const target = new Target;
target->rom = Target::ROM::Pravetz;
target->disk_interface = Target::DiskInterface::Pravetz;
target->loading_command = "CALL 800\n";
@@ -44,13 +42,13 @@ Analyser::Static::Target *OricTarget(const Storage::Encodings::AppleGCR::Sector
}
Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
// This analyser can comprehend disks only.
if(media.disks.empty()) return {};
// Grab track 0, sector 0: the boot sector.
auto track_zero = media.disks.front()->get_track_at_position(Storage::Disk::Track::Address(0, Storage::Disk::HeadPosition(0)));
auto sector_map = Storage::Encodings::AppleGCR::sectors_from_segment(
const auto track_zero = media.disks.front()->get_track_at_position(Storage::Disk::Track::Address(0, Storage::Disk::HeadPosition(0)));
const auto sector_map = Storage::Encodings::AppleGCR::sectors_from_segment(
Storage::Disk::track_serialisation(*track_zero, Storage::Time(1, 50000)));
const Storage::Encodings::AppleGCR::Sector *sector_zero = nullptr;
@@ -77,7 +75,7 @@ Analyser::Static::TargetList Analyser::Static::DiskII::GetTargets(const Media &m
// If the boot sector looks like it's intended for the Oric, create an Oric.
// Otherwise go with the Apple II.
auto disassembly = Analyser::Static::MOS6502::Disassemble(sector_zero->data, Analyser::Static::Disassembler::OffsetMapper(0xb800), {0xb800});
const auto disassembly = Analyser::Static::MOS6502::Disassemble(sector_zero->data, Analyser::Static::Disassembler::OffsetMapper(0xb800), {0xb800});
bool did_read_shift_register = false;
bool is_oric = false;

View File

@@ -27,15 +27,14 @@ static std::unique_ptr<Analyser::Static::Target> CartridgeTarget(
std::vector<Storage::Cartridge::Cartridge::Segment> output_segments;
if(segment.data.size() & 0x1fff) {
std::vector<uint8_t> truncated_data;
std::vector<uint8_t>::difference_type truncated_size = static_cast<std::vector<uint8_t>::difference_type>(segment.data.size()) & ~0x1fff;
std::vector<uint8_t>::difference_type truncated_size = std::vector<uint8_t>::difference_type(segment.data.size()) & ~0x1fff;
truncated_data.insert(truncated_data.begin(), segment.data.begin(), segment.data.begin() + truncated_size);
output_segments.emplace_back(start_address, truncated_data);
} else {
output_segments.emplace_back(start_address, segment.data);
}
std::unique_ptr<Analyser::Static::MSX::Target> target(new Analyser::Static::MSX::Target);
target->machine = Analyser::Machine::MSX;
auto target = std::make_unique<Analyser::Static::MSX::Target>();
target->confidence = confidence;
if(type == Analyser::Static::MSX::Cartridge::Type::None) {
@@ -97,7 +96,7 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
// Reject cartridge if the ROM header wasn't found.
if(!found_start) continue;
uint16_t init_address = static_cast<uint16_t>(segment.data[2] | (segment.data[3] << 8));
uint16_t init_address = uint16_t(segment.data[2] | (segment.data[3] << 8));
// TODO: check for a rational init address?
// If this ROM is less than 48kb in size then it's an ordinary ROM. Just emplace it and move on.
@@ -147,7 +146,7 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
// ) &&
// ((next_iterator->second.operand >> 13) != (0x4000 >> 13))
// ) {
// const uint16_t address = static_cast<uint16_t>(next_iterator->second.operand);
// const uint16_t address = uint16_t(next_iterator->second.operand);
// switch(iterator->second.operand) {
// case 0x6000:
// if(address >= 0x6000 && address < 0x8000) {
@@ -208,13 +207,13 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
if( instruction_pair.second.operation == Instruction::Operation::LD &&
instruction_pair.second.destination == Instruction::Location::Operand_Indirect &&
instruction_pair.second.source == Instruction::Location::A) {
address_counts[static_cast<uint16_t>(instruction_pair.second.operand)]++;
address_counts[uint16_t(instruction_pair.second.operand)]++;
}
}
// Weight confidences by number of observed hits.
float total_hits =
static_cast<float>(
float(
address_counts[0x6000] + address_counts[0x6800] +
address_counts[0x7000] + address_counts[0x7800] +
address_counts[0x77ff] + address_counts[0x8000] +
@@ -226,42 +225,42 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
segment,
start_address,
Analyser::Static::MSX::Cartridge::ASCII8kb,
static_cast<float>( address_counts[0x6000] +
address_counts[0x6800] +
address_counts[0x7000] +
address_counts[0x7800]) / total_hits));
float( address_counts[0x6000] +
address_counts[0x6800] +
address_counts[0x7000] +
address_counts[0x7800]) / total_hits));
targets.push_back(CartridgeTarget(
segment,
start_address,
Analyser::Static::MSX::Cartridge::ASCII16kb,
static_cast<float>( address_counts[0x6000] +
address_counts[0x7000] +
address_counts[0x77ff]) / total_hits));
float( address_counts[0x6000] +
address_counts[0x7000] +
address_counts[0x77ff]) / total_hits));
if(!is_ascii) {
targets.push_back(CartridgeTarget(
segment,
start_address,
Analyser::Static::MSX::Cartridge::Konami,
static_cast<float>( address_counts[0x6000] +
address_counts[0x8000] +
address_counts[0xa000]) / total_hits));
float( address_counts[0x6000] +
address_counts[0x8000] +
address_counts[0xa000]) / total_hits));
}
if(!is_ascii) {
targets.push_back(CartridgeTarget(
segment,
start_address,
Analyser::Static::MSX::Cartridge::KonamiWithSCC,
static_cast<float>( address_counts[0x5000] +
address_counts[0x7000] +
address_counts[0x9000] +
address_counts[0xb000]) / total_hits));
float( address_counts[0x5000] +
address_counts[0x7000] +
address_counts[0x9000] +
address_counts[0xb000]) / total_hits));
}
}
return targets;
}
Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
TargetList destination;
// Append targets for any cartridges that look correct.
@@ -269,7 +268,7 @@ Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(const Media &medi
std::move(cartridge_targets.begin(), cartridge_targets.end(), std::back_inserter(destination));
// Consider building a target for disks and/or tapes.
std::unique_ptr<Target> target(new Target);
auto target = std::make_unique<Target>();
// Check tapes for loadable files.
for(auto &tape : media.tapes) {
@@ -295,7 +294,6 @@ Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(const Media &medi
target->has_disk_drive = !media.disks.empty();
if(!target->media.empty()) {
target->machine = Machine::MSX;
target->confidence = 0.5;
destination.push_back(std::move(target));
}

View File

@@ -44,7 +44,7 @@ std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage:
for(std::size_t c = 0; c < sizeof(header); ++c) {
int next_byte = Parser::get_byte(*file_speed, tape_player);
if(next_byte == -1) break;
header[c] = static_cast<uint8_t>(next_byte);
header[c] = uint8_t(next_byte);
}
bool bytes_are_same = true;
@@ -67,7 +67,7 @@ std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage:
// Read file name.
char name[7];
for(std::size_t c = 1; c < 6; ++c)
name[c] = static_cast<char>(Parser::get_byte(*file_speed, tape_player));
name[c] = char(Parser::get_byte(*file_speed, tape_player));
name[6] = '\0';
file.name = name;
@@ -82,7 +82,7 @@ std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage:
int byte = Parser::get_byte(*file_speed, tape_player);
if(byte == -1) break;
contains_end_of_file |= (byte == 0x1a);
file.data.push_back(static_cast<uint8_t>(byte));
file.data.push_back(uint8_t(byte));
}
if(c != -1) break;
if(contains_end_of_file) {
@@ -105,13 +105,13 @@ std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage:
for(c = 0; c < sizeof(locations); ++c) {
int byte = Parser::get_byte(*file_speed, tape_player);
if(byte == -1) break;
locations[c] = static_cast<uint8_t>(byte);
locations[c] = uint8_t(byte);
}
if(c != sizeof(locations)) continue;
file.starting_address = static_cast<uint16_t>(locations[0] | (locations[1] << 8));
end_address = static_cast<uint16_t>(locations[2] | (locations[3] << 8));
file.entry_address = static_cast<uint16_t>(locations[4] | (locations[5] << 8));
file.starting_address = uint16_t(locations[0] | (locations[1] << 8));
end_address = uint16_t(locations[2] | (locations[3] << 8));
file.entry_address = uint16_t(locations[4] | (locations[5] << 8));
if(end_address < file.starting_address) continue;
@@ -119,7 +119,7 @@ std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage:
while(length--) {
int byte = Parser::get_byte(*file_speed, tape_player);
if(byte == -1) continue;
file.data.push_back(static_cast<uint8_t>(byte));
file.data.push_back(uint8_t(byte));
}
files.push_back(std::move(file));
@@ -135,10 +135,10 @@ std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage:
next_address_buffer[1] = Parser::get_byte(*file_speed, tape_player);
if(next_address_buffer[0] == -1 || next_address_buffer[1] == -1) break;
file.data.push_back(static_cast<uint8_t>(next_address_buffer[0]));
file.data.push_back(static_cast<uint8_t>(next_address_buffer[1]));
file.data.push_back(uint8_t(next_address_buffer[0]));
file.data.push_back(uint8_t(next_address_buffer[1]));
uint16_t next_address = static_cast<uint16_t>(next_address_buffer[0] | (next_address_buffer[1] << 8));
uint16_t next_address = uint16_t(next_address_buffer[0] | (next_address_buffer[1] << 8));
if(!next_address) {
files.push_back(std::move(file));
break;
@@ -155,7 +155,7 @@ std::vector<File> Analyser::Static::MSX::GetFiles(const std::shared_ptr<Storage:
found_error = true;
break;
}
file.data.push_back(static_cast<uint8_t>(byte));
file.data.push_back(uint8_t(byte));
}
if(found_error) break;
}

View File

@@ -9,6 +9,8 @@
#ifndef Analyser_Static_MSX_Target_h
#define Analyser_Static_MSX_Target_h
#include "../../../Reflection/Enum.hpp"
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
#include <string>
@@ -16,15 +18,24 @@ namespace Analyser {
namespace Static {
namespace MSX {
struct Target: public ::Analyser::Static::Target {
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
bool has_disk_drive = false;
std::string loading_command;
enum class Region {
ReflectableEnum(Region,
Japan,
USA,
Europe
} region = Region::USA;
);
Region region = Region::USA;
Target(): Analyser::Static::Target(Machine::MSX) {
if(needs_declare()) {
DeclareField(has_disk_drive);
DeclareField(region);
AnnounceEnum(Region);
}
}
};
}

View File

@@ -9,16 +9,15 @@
#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 {};
Analyser::Static::TargetList Analyser::Static::Macintosh::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
// This analyser can comprehend disks and mass-storage devices only.
if(media.disks.empty() && media.mass_storage_devices.empty()) return {};
// If there is at least one disk, wave it through.
// As there is at least one usable media image, wave it through.
Analyser::Static::TargetList targets;
using Target = Analyser::Static::Macintosh::Target;
auto *target = new Target;
target->machine = Analyser::Machine::Macintosh;
auto *const target = new Target;
target->media = media;
targets.push_back(std::unique_ptr<Analyser::Static::Target>(target));

View File

@@ -9,19 +9,25 @@
#ifndef Analyser_Static_Macintosh_Target_h
#define Analyser_Static_Macintosh_Target_h
#include "../../../Reflection/Enum.hpp"
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
namespace Analyser {
namespace Static {
namespace Macintosh {
struct Target: public ::Analyser::Static::Target {
enum class Model {
Mac128k,
Mac512k,
Mac512ke,
MacPlus
};
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(Model, Mac128k, Mac512k, Mac512ke, MacPlus);
Model model = Model::MacPlus;
Model model = Model::Mac512ke;
Target() : Analyser::Static::Target(Machine::Macintosh) {
// Boilerplate for declaring fields and potential values.
if(needs_declare()) {
DeclareField(model);
AnnounceEnum(Model);
}
}
};
}

View File

@@ -20,7 +20,9 @@
using namespace Analyser::Static::Oric;
static int Score(const Analyser::Static::MOS6502::Disassembly &disassembly, const std::set<uint16_t> &rom_functions, const std::set<uint16_t> &variable_locations) {
namespace {
int score(const Analyser::Static::MOS6502::Disassembly &disassembly, const std::set<uint16_t> &rom_functions, const std::set<uint16_t> &variable_locations) {
int score = 0;
for(const auto address : disassembly.outward_calls) score += (rom_functions.find(address) != rom_functions.end()) ? 1 : -1;
@@ -30,7 +32,7 @@ static int Score(const Analyser::Static::MOS6502::Disassembly &disassembly, cons
return score;
}
static int Basic10Score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
int basic10_score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
const std::set<uint16_t> rom_functions = {
0x0228, 0x022b,
0xc3ca, 0xc3f8, 0xc448, 0xc47c, 0xc4b5, 0xc4e3, 0xc4e0, 0xc524, 0xc56f, 0xc5a2, 0xc5f8, 0xc60a, 0xc6a5, 0xc6de, 0xc719, 0xc738,
@@ -51,10 +53,10 @@ static int Basic10Score(const Analyser::Static::MOS6502::Disassembly &disassembl
0x0228, 0x0229, 0x022a, 0x022b, 0x022c, 0x022d, 0x0230
};
return Score(disassembly, rom_functions, variable_locations);
return score(disassembly, rom_functions, variable_locations);
}
static int Basic11Score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
int basic11_score(const Analyser::Static::MOS6502::Disassembly &disassembly) {
const std::set<uint16_t> rom_functions = {
0x0238, 0x023b, 0x023e, 0x0241, 0x0244, 0x0247,
0xc3c6, 0xc3f4, 0xc444, 0xc47c, 0xc4a8, 0xc4d3, 0xc4e0, 0xc524, 0xc55f, 0xc592, 0xc5e8, 0xc5fa, 0xc692, 0xc6b3, 0xc6ee, 0xc70d,
@@ -76,10 +78,10 @@ static int Basic11Score(const Analyser::Static::MOS6502::Disassembly &disassembl
0x0244, 0x0245, 0x0246, 0x0247, 0x0248, 0x0249, 0x024a, 0x024b, 0x024c
};
return Score(disassembly, rom_functions, variable_locations);
return score(disassembly, rom_functions, variable_locations);
}
static bool IsMicrodisc(Storage::Encodings::MFM::Parser &parser) {
bool is_microdisc(Storage::Encodings::MFM::Parser &parser) {
/*
The Microdisc boot sector is sector 2 of track 0 and contains a 23-byte signature.
*/
@@ -100,9 +102,51 @@ static bool IsMicrodisc(Storage::Encodings::MFM::Parser &parser) {
return !std::memcmp(signature, first_sample.data(), sizeof(signature));
}
Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
std::unique_ptr<Target> target(new Target);
target->machine = Machine::Oric;
bool is_400_loader(Storage::Encodings::MFM::Parser &parser, uint16_t range_start, uint16_t range_end) {
/*
Both the Jasmin and BD-DOS boot sectors are sector 1 of track 0 and are loaded at $400;
use disassembly to test for likely matches.
*/
Storage::Encodings::MFM::Sector *sector = parser.get_sector(0, 0, 1);
if(!sector) return false;
if(sector->samples.empty()) return false;
// Take a copy of the first sampling, and keep only the final 256 bytes (assuming at least that many were found).
std::vector<uint8_t> first_sample = sector->samples[0];
if(first_sample.size() < 256) return false;
if(first_sample.size() > 256) {
first_sample.erase(first_sample.end() - 256, first_sample.end());
}
// Grab a disassembly.
const auto disassembly =
Analyser::Static::MOS6502::Disassemble(first_sample, Analyser::Static::Disassembler::OffsetMapper(0x400), {0x400});
// Check for references to the Jasmin registers.
int register_hits = 0;
for(auto list : {disassembly.external_stores, disassembly.external_loads, disassembly.external_modifies}) {
for(auto address : list) {
register_hits += (address >= range_start && address <= range_end);
}
}
// Arbitrary, sure, but as long as at least two accesses to the requested register range are found, accept this.
return register_hits >= 2;
}
bool is_jasmin(Storage::Encodings::MFM::Parser &parser) {
return is_400_loader(parser, 0x3f4, 0x3ff);
}
bool is_bd500(Storage::Encodings::MFM::Parser &parser) {
return is_400_loader(parser, 0x310, 0x323);
}
}
Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
auto target = std::make_unique<Target>();
target->confidence = 0.5;
int basic10_votes = 0;
@@ -115,12 +159,10 @@ Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &med
for(const auto &file : tape_files) {
if(file.data_type == File::MachineCode) {
std::vector<uint16_t> entry_points = {file.starting_address};
Analyser::Static::MOS6502::Disassembly disassembly =
const Analyser::Static::MOS6502::Disassembly disassembly =
Analyser::Static::MOS6502::Disassemble(file.data, Analyser::Static::Disassembler::OffsetMapper(file.starting_address), entry_points);
int basic10_score = Basic10Score(disassembly);
int basic11_score = Basic11Score(disassembly);
if(basic10_score > basic11_score) basic10_votes++; else basic11_votes++;
if(basic10_score(disassembly) > basic11_score(disassembly)) ++basic10_votes; else ++basic11_votes;
}
}
@@ -130,12 +172,22 @@ Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &med
}
if(!media.disks.empty()) {
// Only the Microdisc is emulated right now, so accept only disks that it can boot from.
// 8-DOS is recognised by a dedicated Disk II analyser, so check only for Microdisc,
// Jasmin and BD-DOS formats here.
for(auto &disk: media.disks) {
Storage::Encodings::MFM::Parser parser(true, disk);
if(IsMicrodisc(parser)) {
if(is_microdisc(parser)) {
target->disk_interface = Target::DiskInterface::Microdisc;
target->media.disks.push_back(disk);
} else if(is_jasmin(parser)) {
target->disk_interface = Target::DiskInterface::Jasmin;
target->should_start_jasmin = true;
target->media.disks.push_back(disk);
} else if(is_bd500(parser)) {
target->disk_interface = Target::DiskInterface::BD500;
target->media.disks.push_back(disk);
target->rom = Target::ROM::BASIC10;
}
}
}

View File

@@ -49,10 +49,10 @@ std::vector<File> Analyser::Static::Oric::GetFiles(const std::shared_ptr<Storage
}
// read end and start addresses
new_file.ending_address = static_cast<uint16_t>(parser.get_next_byte(tape, is_fast) << 8);
new_file.ending_address |= static_cast<uint16_t>(parser.get_next_byte(tape, is_fast));
new_file.starting_address = static_cast<uint16_t>(parser.get_next_byte(tape, is_fast) << 8);
new_file.starting_address |= static_cast<uint16_t>(parser.get_next_byte(tape, is_fast));
new_file.ending_address = uint16_t(parser.get_next_byte(tape, is_fast) << 8);
new_file.ending_address |= uint16_t(parser.get_next_byte(tape, is_fast));
new_file.starting_address = uint16_t(parser.get_next_byte(tape, is_fast) << 8);
new_file.starting_address |= uint16_t(parser.get_next_byte(tape, is_fast));
// skip an empty byte
parser.get_next_byte(tape, is_fast);
@@ -61,7 +61,7 @@ std::vector<File> Analyser::Static::Oric::GetFiles(const std::shared_ptr<Storage
char file_name[17];
int name_pos = 0;
while(name_pos < 16) {
file_name[name_pos] = (char)parser.get_next_byte(tape, is_fast);
file_name[name_pos] = char(parser.get_next_byte(tape, is_fast));
if(!file_name[name_pos]) break;
name_pos++;
}
@@ -72,7 +72,7 @@ std::vector<File> Analyser::Static::Oric::GetFiles(const std::shared_ptr<Storage
std::size_t body_length = new_file.ending_address - new_file.starting_address + 1;
new_file.data.reserve(body_length);
for(std::size_t c = 0; c < body_length; c++) {
new_file.data.push_back(static_cast<uint8_t>(parser.get_next_byte(tape, is_fast)));
new_file.data.push_back(uint8_t(parser.get_next_byte(tape, is_fast)));
}
// only one validation check: was there enough tape?

View File

@@ -9,6 +9,8 @@
#ifndef Analyser_Static_Oric_Target_h
#define Analyser_Static_Oric_Target_h
#include "../../../Reflection/Enum.hpp"
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
#include <string>
@@ -16,22 +18,42 @@ namespace Analyser {
namespace Static {
namespace Oric {
struct Target: public ::Analyser::Static::Target {
enum class ROM {
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(ROM,
BASIC10,
BASIC11,
Pravetz
};
);
enum class DiskInterface {
ReflectableEnum(DiskInterface,
None,
Microdisc,
Pravetz,
None
};
Jasmin,
BD500
);
ReflectableEnum(Processor,
MOS6502,
WDC65816
);
ROM rom = ROM::BASIC11;
DiskInterface disk_interface = DiskInterface::None;
Processor processor = Processor::MOS6502;
std::string loading_command;
bool should_start_jasmin = false;
Target(): Analyser::Static::Target(Machine::Oric) {
if(needs_declare()) {
DeclareField(rom);
DeclareField(disk_interface);
DeclareField(processor);
AnnounceEnum(ROM);
AnnounceEnum(DiskInterface);
AnnounceEnum(Processor);
}
}
};
}

View File

@@ -13,14 +13,12 @@
#include <algorithm>
#include <cstring>
Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType) {
if(media.cartridges.empty())
return {};
TargetList targets;
std::unique_ptr<Target> target(new Target);
target->machine = Machine::MasterSystem;
auto target = std::make_unique<Target>();
// Files named .sg are treated as for the SG1000; otherwise assume a Master System.
if(file_name.size() >= 2 && *(file_name.end() - 2) == 's' && *(file_name.end() - 1) == 'g') {

View File

@@ -9,23 +9,27 @@
#ifndef Analyser_Static_Sega_Target_h
#define Analyser_Static_Sega_Target_h
#include "../../../Reflection/Enum.hpp"
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
namespace Analyser {
namespace Static {
namespace Sega {
struct Target: public ::Analyser::Static::Target {
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
enum class Model {
SG1000,
MasterSystem,
MasterSystem2,
};
enum class Region {
ReflectableEnum(Region,
Japan,
USA,
Europe,
Brazil
};
);
enum class PagingScheme {
Sega,
@@ -35,6 +39,13 @@ struct Target: public ::Analyser::Static::Target {
Model model = Model::MasterSystem;
Region region = Region::Japan;
PagingScheme paging_scheme = PagingScheme::Sega;
Target() : Analyser::Static::Target(Machine::MasterSystem) {
if(needs_declare()) {
DeclareField(region);
AnnounceEnum(Region);
}
}
};
#define is_master_system(v) v >= Analyser::Static::Sega::Target::Model::MasterSystem

View File

@@ -17,7 +17,8 @@
#include "Acorn/StaticAnalyser.hpp"
#include "AmstradCPC/StaticAnalyser.hpp"
#include "AppleII/StaticAnalyser.hpp"
#include "Atari/StaticAnalyser.hpp"
#include "Atari2600/StaticAnalyser.hpp"
#include "AtariST/StaticAnalyser.hpp"
#include "Coleco/StaticAnalyser.hpp"
#include "Commodore/StaticAnalyser.hpp"
#include "DiskII/StaticAnalyser.hpp"
@@ -40,12 +41,18 @@
#include "../../Storage/Disk/DiskImage/Formats/G64.hpp"
#include "../../Storage/Disk/DiskImage/Formats/DMK.hpp"
#include "../../Storage/Disk/DiskImage/Formats/HFE.hpp"
#include "../../Storage/Disk/DiskImage/Formats/MSA.hpp"
#include "../../Storage/Disk/DiskImage/Formats/MSXDSK.hpp"
#include "../../Storage/Disk/DiskImage/Formats/NIB.hpp"
#include "../../Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp"
#include "../../Storage/Disk/DiskImage/Formats/SSD.hpp"
#include "../../Storage/Disk/DiskImage/Formats/ST.hpp"
#include "../../Storage/Disk/DiskImage/Formats/STX.hpp"
#include "../../Storage/Disk/DiskImage/Formats/WOZ.hpp"
// Mass Storage Devices (i.e. usually, hard disks)
#include "../../Storage/MassStorage/Formats/HFV.hpp"
// Tapes
#include "../../Storage/Tape/Formats/CAS.hpp"
#include "../../Storage/Tape/Formats/CommodoreTAP.hpp"
@@ -100,9 +107,13 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
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::CPCDSK>,
TargetPlatform::AmstradCPC | TargetPlatform::Oric) // 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::MacintoshIMG>, TargetPlatform::Macintosh) // DSK (Macintosh, floppy disk)
Format("dsk", result.mass_storage_devices, MassStorage::HFV, TargetPlatform::Macintosh) // DSK (Macintosh, hard disk)
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
@@ -113,6 +124,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
// HFE (TODO: switch to AllDisk once the MSX stops being so greedy)
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("msa", result.disks, Disk::DiskImageHolder<Storage::Disk::MSA>, TargetPlatform::AtariST) // MSA
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
@@ -138,6 +150,8 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
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("st", result.disks, Disk::DiskImageHolder<Storage::Disk::ST>, TargetPlatform::AtariST) // ST
Format("stx", result.disks, Disk::DiskImageHolder<Storage::Disk::STX>, TargetPlatform::AtariST) // STX
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
@@ -160,7 +174,7 @@ Media Analyser::Static::GetMedia(const std::string &file_name) {
TargetList Analyser::Static::GetTargets(const std::string &file_name) {
TargetList targets;
// Collect all disks, tapes and ROMs as can be extrapolated from this file, forming the
// Collect all disks, tapes ROMs, etc as can be extrapolated from this file, forming the
// union of all platforms this file might be a target for.
TargetPlatform::IntType potential_platforms = 0;
Media media = GetMediaAndPlatforms(file_name, potential_platforms);
@@ -174,7 +188,8 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) {
if(potential_platforms & TargetPlatform::Acorn) Append(Acorn);
if(potential_platforms & TargetPlatform::AmstradCPC) Append(AmstradCPC);
if(potential_platforms & TargetPlatform::AppleII) Append(AppleII);
if(potential_platforms & TargetPlatform::Atari2600) Append(Atari);
if(potential_platforms & TargetPlatform::Atari2600) Append(Atari2600);
if(potential_platforms & TargetPlatform::AtariST) Append(AtariST);
if(potential_platforms & TargetPlatform::ColecoVision) Append(Coleco);
if(potential_platforms & TargetPlatform::Commodore) Append(Commodore);
if(potential_platforms & TargetPlatform::DiskII) Append(DiskII);

View File

@@ -11,9 +11,10 @@
#include "../Machines.hpp"
#include "../../Storage/Tape/Tape.hpp"
#include "../../Storage/Disk/Disk.hpp"
#include "../../Storage/Cartridge/Cartridge.hpp"
#include "../../Storage/Disk/Disk.hpp"
#include "../../Storage/MassStorage/MassStorageDevice.hpp"
#include "../../Storage/Tape/Tape.hpp"
#include <memory>
#include <string>
@@ -29,9 +30,20 @@ struct Media {
std::vector<std::shared_ptr<Storage::Disk::Disk>> disks;
std::vector<std::shared_ptr<Storage::Tape::Tape>> tapes;
std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> cartridges;
std::vector<std::shared_ptr<Storage::MassStorage::MassStorageDevice>> mass_storage_devices;
bool empty() const {
return disks.empty() && tapes.empty() && cartridges.empty();
return disks.empty() && tapes.empty() && cartridges.empty() && mass_storage_devices.empty();
}
Media &operator +=(const Media &rhs) {
#define append(name) name.insert(name.end(), rhs.name.begin(), rhs.name.end());
append(disks);
append(tapes);
append(cartridges);
append(mass_storage_devices);
#undef append
return *this;
}
};
@@ -40,11 +52,12 @@ struct Media {
and instructions on how to launch the software attached, plus a measure of confidence in this target's correctness.
*/
struct Target {
Target(Machine machine) : machine(machine) {}
virtual ~Target() {}
Machine machine;
Media media;
float confidence;
float confidence = 0.0f;
};
typedef std::vector<std::unique_ptr<Target>> TargetList;

View File

@@ -28,13 +28,13 @@ static std::vector<Storage::Data::ZX8081::File> GetFiles(const std::shared_ptr<S
return files;
}
Analyser::Static::TargetList Analyser::Static::ZX8081::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
Analyser::Static::TargetList Analyser::Static::ZX8081::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType potential_platforms) {
TargetList destination;
if(!media.tapes.empty()) {
std::vector<Storage::Data::ZX8081::File> files = GetFiles(media.tapes.front());
media.tapes.front()->reset();
if(!files.empty()) {
Target *target = new Target;
Target *const target = new Target;
destination.push_back(std::unique_ptr<::Analyser::Static::Target>(target));
target->machine = Machine::ZX8081;

View File

@@ -9,6 +9,8 @@
#ifndef Analyser_Static_ZX8081_Target_h
#define Analyser_Static_ZX8081_Target_h
#include "../../../Reflection/Enum.hpp"
#include "../../../Reflection/Struct.hpp"
#include "../StaticAnalyser.hpp"
#include <string>
@@ -16,17 +18,26 @@ namespace Analyser {
namespace Static {
namespace ZX8081 {
struct Target: public ::Analyser::Static::Target {
enum class MemoryModel {
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
ReflectableEnum(MemoryModel,
Unexpanded,
SixteenKB,
SixtyFourKB
};
);
MemoryModel memory_model = MemoryModel::Unexpanded;
bool is_ZX81 = false;
bool ZX80_uses_ZX81_ROM = false;
std::string loading_command;
Target(): Analyser::Static::Target(Machine::ZX8081) {
if(needs_declare()) {
DeclareField(memory_model);
DeclareField(is_ZX81);
DeclareField(ZX80_uses_ZX81_ROM);
AnnounceEnum(MemoryModel);
}
}
};
}

View File

@@ -10,6 +10,7 @@
#define ClockReceiver_hpp
#include "ForceInline.hpp"
#include <cstdint>
/*
Informal pattern for all classes that run from a clock cycle:
@@ -54,7 +55,9 @@
*/
template <class T> class WrappedInt {
public:
forceinline constexpr WrappedInt(int l) noexcept : length_(l) {}
using IntType = int64_t;
forceinline constexpr WrappedInt(IntType l) noexcept : length_(l) {}
forceinline constexpr WrappedInt() noexcept : length_(0) {}
forceinline T &operator =(const T &rhs) {
@@ -133,16 +136,20 @@ template <class T> class WrappedInt {
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
forceinline constexpr int as_int() const { return length_; }
/// @returns The underlying int, cast to an integral type of your choosing.
template<typename Type = IntType> forceinline constexpr Type as() const { return Type(length_); }
/// @returns The underlying int, in its native form.
forceinline constexpr IntType as_integral() 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.
*/
forceinline T divide(const T &divisor) {
T result(length_ / divisor.length_);
length_ %= divisor.length_;
return result;
template <typename Result = T> forceinline Result divide(const T &divisor) {
Result r;
static_cast<T *>(this)->fill(r, divisor);
return r;
}
/*!
@@ -161,15 +168,14 @@ template <class T> class WrappedInt {
// classes that use this template.
protected:
int length_;
IntType length_;
};
/// Describes an integer number of whole cycles: pairs of clock signal transitions.
class Cycles: public WrappedInt<Cycles> {
public:
forceinline constexpr Cycles(int l) noexcept : WrappedInt<Cycles>(l) {}
forceinline constexpr Cycles(IntType 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;
@@ -177,16 +183,20 @@ class Cycles: public WrappedInt<Cycles> {
result.length_ = length_;
length_ = 0;
}
void fill(Cycles &result, const Cycles &divisor) {
result.length_ = length_ / divisor.length_;
length_ %= divisor.length_;
}
};
/// Describes an integer number of half cycles: single clock signal transitions.
class HalfCycles: public WrappedInt<HalfCycles> {
public:
forceinline constexpr HalfCycles(int l) noexcept : WrappedInt<HalfCycles>(l) {}
forceinline constexpr HalfCycles(IntType l) noexcept : WrappedInt<HalfCycles>(l) {}
forceinline constexpr HalfCycles() noexcept : WrappedInt<HalfCycles>() {}
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_) {}
forceinline constexpr HalfCycles(const Cycles &cycles) noexcept : WrappedInt<HalfCycles>(cycles.as_integral() * 2) {}
/// @returns The number of whole cycles completely covered by this span of half cycles.
forceinline constexpr Cycles cycles() const {
@@ -215,6 +225,16 @@ class HalfCycles: public WrappedInt<HalfCycles> {
result.length_ = length_;
length_ = 0;
}
void fill(Cycles &result, const HalfCycles &divisor) {
result = Cycles(length_ / (divisor.length_ << 1));
length_ %= (divisor.length_ << 1);
}
void fill(HalfCycles &result, const HalfCycles &divisor) {
result.length_ = length_ / divisor.length_;
length_ %= divisor.length_;
}
};
// Create a specialisation of WrappedInt::flush for converting HalfCycles to Cycles

View File

@@ -67,7 +67,7 @@ class Source {
}
/// @returns the current preferred clocking strategy.
virtual Preference preferred_clocking() = 0;
virtual Preference preferred_clocking() const = 0;
private:
Observer *observer_ = nullptr;

View File

@@ -13,62 +13,68 @@
#include <vector>
/*!
A DeferredQueue maintains a list of ordered actions and the times at which
they should happen, and divides a total execution period up into the portions
that occur between those actions, triggering each action when it is reached.
Provides the logic to insert into and traverse a list of future scheduled items.
*/
template <typename TimeUnit> class DeferredQueue {
public:
/// Constructs a DeferredQueue that will call target(period) in between deferred actions.
DeferredQueue(std::function<void(TimeUnit)> &&target) : target_(std::move(target)) {}
/*!
Schedules @c action to occur in @c delay units of time.
Actions must be scheduled in the order they will occur. It is undefined behaviour
to schedule them out of order.
*/
void defer(TimeUnit delay, const std::function<void(void)> &action) {
pending_actions_.emplace_back(delay, action);
}
/*!
Runs for @c length units of time.
The constructor-supplied target will be called with one or more periods that add up to @c length;
any scheduled actions will be called between periods.
*/
void run_for(TimeUnit length) {
// If there are no pending actions, just run for the entire length.
// This should be the normal branch.
if(pending_actions_.empty()) {
target_(length);
// Apply immediately if there's no delay (or a negative delay).
if(delay <= TimeUnit(0)) {
action();
return;
}
// Divide the time to run according to the pending actions.
while(length > TimeUnit(0)) {
TimeUnit next_period = pending_actions_.empty() ? length : std::min(length, pending_actions_[0].delay);
target_(next_period);
length -= next_period;
if(!pending_actions_.empty()) {
// Otherwise enqueue, having subtracted the delay for any preceding events,
// and subtracting from the subsequent, if any.
auto insertion_point = pending_actions_.begin();
while(insertion_point != pending_actions_.end() && insertion_point->delay < delay) {
delay -= insertion_point->delay;
++insertion_point;
}
if(insertion_point != pending_actions_.end()) {
insertion_point->delay -= delay;
}
off_t performances = 0;
for(auto &action: pending_actions_) {
action.delay -= next_period;
if(!action.delay) {
action.action();
++performances;
}
}
if(performances) {
pending_actions_.erase(pending_actions_.begin(), pending_actions_.begin() + performances);
pending_actions_.emplace(insertion_point, delay, action);
} else {
pending_actions_.emplace_back(delay, action);
}
}
/*!
@returns The amount of time until the next enqueued action will occur,
or TimeUnit(-1) if the queue is empty.
*/
TimeUnit time_until_next_action() const {
if(pending_actions_.empty()) return TimeUnit(-1);
return pending_actions_.front().delay;
}
/*!
Advances the queue the specified amount of time, performing any actions it reaches.
*/
void advance(TimeUnit time) {
auto erase_iterator = pending_actions_.begin();
while(erase_iterator != pending_actions_.end()) {
erase_iterator->delay -= time;
if(erase_iterator->delay <= TimeUnit(0)) {
time = -erase_iterator->delay;
erase_iterator->action();
++erase_iterator;
} else {
break;
}
}
if(erase_iterator != pending_actions_.begin()) {
pending_actions_.erase(pending_actions_.begin(), erase_iterator);
}
}
private:
std::function<void(TimeUnit)> target_;
// The list of deferred actions.
struct DeferredAction {
TimeUnit delay;
@@ -79,4 +85,40 @@ template <typename TimeUnit> class DeferredQueue {
std::vector<DeferredAction> pending_actions_;
};
/*!
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.
This list is efficient only for short queues.
*/
template <typename TimeUnit> class DeferredQueuePerformer: public DeferredQueue<TimeUnit> {
public:
/// Constructs a DeferredQueue that will call target(period) in between deferred actions.
constexpr DeferredQueuePerformer(std::function<void(TimeUnit)> &&target) : target_(std::move(target)) {}
/*!
Runs for @c length units of time.
The constructor-supplied target will be called with one or more periods that add up to @c length;
any scheduled actions will be called between periods.
*/
void run_for(TimeUnit length) {
auto time_to_next = DeferredQueue<TimeUnit>::time_until_next_action();
while(time_to_next != TimeUnit(-1) && time_to_next <= length) {
target_(time_to_next);
length -= time_to_next;
DeferredQueue<TimeUnit>::advance(time_to_next);
}
DeferredQueue<TimeUnit>::advance(length);
target_(length);
// TODO: optimise this to avoid the multiple std::vector deletes. Find a neat way to expose that solution, maybe?
}
private:
std::function<void(TimeUnit)> target_;
};
#endif /* DeferredQueue_h */

View File

@@ -9,9 +9,9 @@
#ifndef ForceInline_hpp
#define ForceInline_hpp
#ifdef DEBUG
#ifndef NDEBUG
#define forceinline
#define forceinline inline
#else

View File

@@ -10,6 +10,7 @@
#define JustInTime_h
#include "../Concurrency/AsyncTaskQueue.hpp"
#include "ForceInline.hpp"
/*!
A JustInTimeActor holds (i) an embedded object with a run_for method; and (ii) an amount
@@ -21,32 +22,52 @@
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 {
template <class T, int multiplier = 1, int divider = 1, class LocalTimeScale = HalfCycles, 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;
forceinline void operator += (const LocalTimeScale &rhs) {
if constexpr (multiplier != 1) {
time_since_update_ += rhs * multiplier;
} else {
time_since_update_ += rhs;
}
is_flushed_ = false;
}
/// Flushes all accumulated time and returns a pointer to the included object.
inline T *operator->() {
forceinline T *operator->() {
flush();
return &object_;
}
/// Acts exactly as per the standard ->, but preserves constness.
forceinline const T *operator->() const {
auto non_const_this = const_cast<JustInTimeActor<T, multiplier, divider, LocalTimeScale, TargetTimeScale> *>(this);
non_const_this->flush();
return &object_;
}
/// Returns a pointer to the included object without flushing time.
inline T *last_valid() {
forceinline 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;
forceinline void flush() {
if(!is_flushed_) {
is_flushed_ = true;
if constexpr (divider == 1) {
const auto duration = time_since_update_.template flush<TargetTimeScale>();
object_.run_for(duration);
} else {
const auto duration = time_since_update_.template divide<TargetTimeScale>(LocalTimeScale(divider));
if(duration > TargetTimeScale(0))
object_.run_for(duration);
}
}
}
private:
@@ -55,12 +76,54 @@ template <class T, class LocalTimeScale, class TargetTimeScale = LocalTimeScale>
bool is_flushed_ = true;
};
/*!
A RealTimeActor presents the same interface as a JustInTimeActor but doesn't defer work.
Time added will be performed immediately.
Its primary purpose is to allow consumers to remain flexible in their scheduling.
*/
template <class T, int multiplier = 1, int divider = 1, class LocalTimeScale = HalfCycles, class TargetTimeScale = LocalTimeScale> class RealTimeActor {
public:
template<typename... Args> RealTimeActor(Args&&... args) : object_(std::forward<Args>(args)...) {}
forceinline void operator += (const LocalTimeScale &rhs) {
if constexpr (multiplier == 1 && divider == 1) {
object_.run_for(TargetTimeScale(rhs));
return;
}
if constexpr (multiplier == 1) {
accumulated_time_ += rhs;
} else {
accumulated_time_ += rhs * multiplier;
}
if constexpr (divider == 1) {
const auto duration = accumulated_time_.template flush<TargetTimeScale>();
object_.run_for(duration);
} else {
const auto duration = accumulated_time_.template divide<TargetTimeScale>(LocalTimeScale(divider));
if(duration > TargetTimeScale(0))
object_.run_for(duration);
}
}
forceinline T *operator->() { return &object_; }
forceinline const T *operator->() const { return &object_; }
forceinline T *last_valid() { return &object_; }
forceinline void flush() {}
private:
T object_;
LocalTimeScale accumulated_time_;
};
/*!
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 {
template <class T, class LocalTimeScale = HalfCycles, 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) :

View File

@@ -0,0 +1,88 @@
//
// ScanSynchroniser.hpp
// Clock Signal
//
// Created by Thomas Harte on 09/02/2020.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#ifndef ScanSynchroniser_h
#define ScanSynchroniser_h
#include "../Outputs/ScanTarget.hpp"
#include <cmath>
namespace Time {
/*!
Where an emulated machine is sufficiently close to a host machine's frame rate that a small nudge in
its speed multiplier will bring it into frame synchronisation, the ScanSynchroniser provides a sequence of
speed multipliers designed both to adjust the machine to the proper speed and, in a reasonable amount
of time, to bring it into phase.
*/
class ScanSynchroniser {
public:
/*!
@returns @c true if the emulated machine can be synchronised with the host frame output based on its
current @c [scan]status and the host machine's @c frame_duration; @c false otherwise.
*/
bool can_synchronise(const Outputs::Display::ScanStatus &scan_status, double frame_duration) {
ratio_ = 1.0;
if(scan_status.field_duration_gradient < 0.00001) {
// Check out the machine's current frame time.
// If it's within 3% of a non-zero integer multiple of the
// display rate, mark this time window to be split over the sync.
ratio_ = (frame_duration * base_multiplier_) / scan_status.field_duration;
const double integer_ratio = round(ratio_);
if(integer_ratio > 0.0) {
ratio_ /= integer_ratio;
return ratio_ <= maximum_rate_adjustment && ratio_ >= 1.0 / maximum_rate_adjustment;
}
}
return false;
}
/*!
@returns The appropriate speed multiplier for the next frame based on the inputs previously supplied to @c can_synchronise.
Results are undefined if @c can_synchroise returned @c false.
*/
double next_speed_multiplier(const Outputs::Display::ScanStatus &scan_status) {
// The host versus emulated ratio is calculated based on the current perceived frame duration of the machine.
// Either that number is exactly correct or it's already the result of some sort of low-pass filter. So there's
// no benefit to second guessing it here — just take it to be correct.
//
// ... with one slight caveat, which is that it is desireable to adjust phase here, to align vertical sync points.
// So the set speed multiplier may be adjusted slightly to aim for that.
double speed_multiplier = 1.0 / (ratio_ / base_multiplier_);
if(scan_status.current_position > 0.0) {
if(scan_status.current_position < 0.5) speed_multiplier /= phase_adjustment_ratio;
else speed_multiplier *= phase_adjustment_ratio;
}
speed_multiplier_ = (speed_multiplier_ * 0.95) + (speed_multiplier * 0.05);
return speed_multiplier_ * base_multiplier_;
}
void set_base_speed_multiplier(double multiplier) {
base_multiplier_ = multiplier;
}
double get_base_speed_multiplier() {
return base_multiplier_;
}
private:
static constexpr double maximum_rate_adjustment = 1.03;
static constexpr double phase_adjustment_ratio = 1.005;
// Managed local state.
double speed_multiplier_ = 1.0;
double base_multiplier_ = 1.0;
// Temporary storage to bridge the can_synchronise -> next_speed_multiplier gap.
double ratio_ = 1.0;
};
}
#endif /* ScanSynchroniser_h */

View File

@@ -9,9 +9,16 @@
#ifndef TimeTypes_h
#define TimeTypes_h
#include <chrono>
namespace Time {
typedef double Seconds;
typedef int64_t Nanos;
inline Nanos nanos_now() {
return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count();
}
}

View File

@@ -0,0 +1,155 @@
//
// VSyncPredictor.hpp
// Clock Signal
//
// Created by Thomas Harte on 14/06/2020.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#ifndef VSyncPredictor_hpp
#define VSyncPredictor_hpp
#include "TimeTypes.hpp"
#include <cassert>
#include <cmath>
#include <cstdio>
namespace Time {
/*!
For platforms that provide no avenue into vsync tracking other than block-until-sync,
this class tracks: (i) how long frame draw takes; (ii) the apparent frame period; and
(iii) optionally, timer jitter; in order to suggest when you should next start drawing.
*/
class VSyncPredictor {
public:
/*!
Announces to the predictor that the work of producing an output frame has begun.
*/
void begin_redraw() {
redraw_begin_time_ = nanos_now();
}
/*!
Announces to the predictor that the work of producing an output frame has ended;
the predictor will use the amount of time between each begin/end pair to modify
its expectations as to how long it takes to draw a frame.
*/
void end_redraw() {
redraw_period_.post(nanos_now() - redraw_begin_time_);
}
/*!
Informs the predictor that a block-on-vsync has just ended, i.e. that the moment this
machine calls retrace is now. The predictor uses these notifications to estimate output
frame rate.
*/
void announce_vsync() {
const auto now = nanos_now();
if(last_vsync_) {
last_vsync_ += frame_duration_;
vsync_jitter_.post(last_vsync_ - now);
last_vsync_ = (last_vsync_ + now) >> 1;
} else {
last_vsync_ = now;
}
}
/*!
Sets the frame rate for the target display.
*/
void set_frame_rate(float rate) {
frame_duration_ = Nanos(1'000'000'000.0f / rate);
}
/*!
@returns The time this class currently believes a whole frame occupies.
*/
Time::Nanos frame_duration() {
return frame_duration_;
}
/*!
Adds a record of how much jitter was experienced in scheduling; these values will be
factored into the @c suggested_draw_time if supplied.
A positive number means the timer occurred late. A negative number means it occurred early.
*/
void add_timer_jitter(Time::Nanos jitter) {
timer_jitter_.post(jitter);
}
/*!
Announces to the vsync predictor that output is now paused. This ends frame period
calculations until the next announce_vsync() restarts frame-length counting.
*/
void pause() {
last_vsync_ = 0;
}
/*!
@return The time at which redrawing should begin, given the predicted frame period, how
long it appears to take to draw a frame and how much jitter there is in scheduling
(if those figures are being supplied).
*/
Nanos suggested_draw_time() {
const auto mean = redraw_period_.mean() + timer_jitter_.mean() + vsync_jitter_.mean();
const auto variance = redraw_period_.variance() + timer_jitter_.variance() + vsync_jitter_.variance();
// Permit three standard deviations from the mean, to cover 99.9% of cases.
const auto period = mean + Nanos(3.0f * sqrt(float(variance)));
return last_vsync_ + frame_duration_ - period;
}
private:
class VarianceCollector {
public:
VarianceCollector(Time::Nanos default_value) {
sum_ = default_value * 128;
for(int c = 0; c < 128; ++c) {
history_[c] = default_value;
}
}
void post(Time::Nanos value) {
sum_ -= history_[write_pointer_];
sum_ += value;
history_[write_pointer_] = value;
write_pointer_ = (write_pointer_ + 1) & 127;
}
Time::Nanos mean() {
return sum_ / 128;
}
Time::Nanos variance() {
// I haven't yet come up with a better solution that calculating this
// in whole every time, given the way that the mean mutates.
Time::Nanos variance = 0;
for(int c = 0; c < 128; ++c) {
const auto difference = ((history_[c] * 128) - sum_) / 128;
variance += (difference * difference);
}
return variance / 128;
}
private:
Time::Nanos sum_;
Time::Nanos history_[128];
size_t write_pointer_ = 0;
};
Nanos redraw_begin_time_ = 0;
Nanos last_vsync_ = 0;
Nanos frame_duration_ = 1'000'000'000 / 60;
VarianceCollector vsync_jitter_{0};
VarianceCollector redraw_period_{1'000'000'000 / 60}; // A less convincing first guess.
VarianceCollector timer_jitter_{0}; // Seed at 0 in case this feature isn't used by the owner.
};
}
#endif /* VSyncPredictor_hpp */

View File

@@ -9,6 +9,8 @@
#include "1770.hpp"
#include "../../Storage/Disk/Encodings/MFM/Constants.hpp"
#define LOG_PREFIX "[WD FDC] "
#include "../../Outputs/Log.hpp"
using namespace WD;
@@ -16,19 +18,19 @@ using namespace WD;
WD1770::WD1770(Personality p) :
Storage::Disk::MFMController(8000000),
personality_(p),
interesting_event_mask_(static_cast<int>(Event1770::Command)) {
interesting_event_mask_(int(Event1770::Command)) {
set_is_double_density(false);
posit_event(static_cast<int>(Event1770::Command));
posit_event(int(Event1770::Command));
}
void WD1770::set_register(int address, uint8_t value) {
void WD1770::write(int address, uint8_t value) {
switch(address&3) {
case 0: {
if((value&0xf0) == 0xd0) {
if(value == 0xd0) {
// Force interrupt **immediately**.
LOG("Force interrupt immediately");
posit_event(static_cast<int>(Event1770::ForceInterrupt));
posit_event(int(Event1770::ForceInterrupt));
} else {
ERROR("!!!TODO: force interrupt!!!");
update_status([] (Status &status) {
@@ -37,7 +39,7 @@ void WD1770::set_register(int address, uint8_t value) {
}
} else {
command_ = value;
posit_event(static_cast<int>(Event1770::Command));
posit_event(int(Event1770::Command));
}
}
break;
@@ -52,27 +54,35 @@ void WD1770::set_register(int address, uint8_t value) {
}
}
uint8_t WD1770::get_register(int address) {
uint8_t WD1770::read(int address) {
switch(address&3) {
default: {
update_status([] (Status &status) {
status.interrupt_request = false;
});
uint8_t status =
(status_.write_protect ? Flag::WriteProtect : 0) |
(status_.crc_error ? Flag::CRCError : 0) |
(status_.busy ? Flag::Busy : 0);
(status_.crc_error ? Flag::CRCError : 0) |
(status_.busy ? Flag::Busy : 0);
// Per Jean Louis-Guérin's documentation:
//
// * the write-protect bit is locked into place by a type 2 or type 3 command, but is
// read live after a type 1.
// * the track 0 bit is captured during a type 1 instruction and lost upon any other type,
// it is not live sampled.
switch(status_.type) {
case Status::One:
status |=
(get_drive().get_is_track_zero() ? Flag::TrackZero : 0) |
(status_.seek_error ? Flag::SeekError : 0);
// TODO: index hole
(status_.track_zero ? Flag::TrackZero : 0) |
(status_.seek_error ? Flag::SeekError : 0) |
(get_drive().get_is_read_only() ? Flag::WriteProtect : 0) |
(get_drive().get_index_pulse() ? Flag::Index : 0);
break;
case Status::Two:
case Status::Three:
status |=
(status_.write_protect ? Flag::WriteProtect : 0) |
(status_.record_type ? Flag::RecordType : 0) |
(status_.lost_data ? Flag::LostData : 0) |
(status_.data_request ? Flag::DataRequest : 0) |
@@ -89,10 +99,15 @@ uint8_t WD1770::get_register(int address) {
if(status_.type == Status::One)
status |= (status_.spin_up ? Flag::SpinUp : 0);
}
// LOG("Returned status " << PADHEX(2) << int(status) << " of type " << 1+int(status_.type));
return status;
}
case 1: return track_;
case 2: return sector_;
case 1:
LOG("Returned track " << int(track_));
return track_;
case 2:
LOG("Returned sector " << int(sector_));
return sector_;
case 3:
update_status([] (Status &status) {
status.data_request = false;
@@ -105,28 +120,30 @@ void WD1770::run_for(const Cycles cycles) {
Storage::Disk::Controller::run_for(cycles);
if(delay_time_) {
unsigned int number_of_cycles = static_cast<unsigned int>(cycles.as_int());
const auto number_of_cycles = cycles.as_integral();
if(delay_time_ <= number_of_cycles) {
delay_time_ = 0;
posit_event(static_cast<int>(Event1770::Timer));
posit_event(int(Event1770::Timer));
} else {
delay_time_ -= number_of_cycles;
}
}
}
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(mask); return; case __LINE__:
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = int(mask); return; case __LINE__:
#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; delay_time_ = ms * 8000; WAIT_FOR_EVENT(Event1770::Timer);
#define WAIT_FOR_BYTES(count) resume_point_ = __LINE__; distance_into_section_ = 0; WAIT_FOR_EVENT(Event::Token); if(get_latest_token().type == Token::Byte) distance_into_section_++; if(distance_into_section_ < count) { interesting_event_mask_ = static_cast<int>(Event::Token); return; }
#define WAIT_FOR_BYTES(count) resume_point_ = __LINE__; distance_into_section_ = 0; WAIT_FOR_EVENT(Event::Token); if(get_latest_token().type == Token::Byte) distance_into_section_++; if(distance_into_section_ < count) { interesting_event_mask_ = int(Event::Token); return; }
#define BEGIN_SECTION() switch(resume_point_) { default:
#define END_SECTION() (void)0; }
#define READ_ID() \
if(new_event_type == static_cast<int>(Event::Token)) { \
if(!distance_into_section_ && get_latest_token().type == Token::ID) {set_data_mode(DataMode::Reading); distance_into_section_++; } \
else if(distance_into_section_ && distance_into_section_ < 7 && get_latest_token().type == Token::Byte) { \
if(new_event_type == int(Event::Token)) { \
if(!distance_into_section_ && get_latest_token().type == Token::ID) {\
set_data_mode(DataMode::Reading); \
++distance_into_section_; \
} else if(distance_into_section_ && distance_into_section_ < 7 && get_latest_token().type == Token::Byte) { \
header_[distance_into_section_ - 1] = get_latest_token().byte_value; \
distance_into_section_++; \
++distance_into_section_; \
} \
}
@@ -159,10 +176,10 @@ void WD1770::run_for(const Cycles cycles) {
// +--------+----------+-------------------------+
void WD1770::posit_event(int new_event_type) {
if(new_event_type == static_cast<int>(Event::IndexHole)) {
if(new_event_type == int(Event::IndexHole)) {
index_hole_count_++;
if(index_hole_count_target_ == index_hole_count_) {
posit_event(static_cast<int>(Event1770::IndexHoleTarget));
posit_event(int(Event1770::IndexHoleTarget));
index_hole_count_target_ = -1;
}
@@ -177,15 +194,16 @@ void WD1770::posit_event(int new_event_type) {
}
}
if(new_event_type == static_cast<int>(Event1770::ForceInterrupt)) {
if(new_event_type == int(Event1770::ForceInterrupt)) {
interesting_event_mask_ = 0;
resume_point_ = 0;
update_status([] (Status &status) {
status.type = Status::One;
status.data_request = false;
status.spin_up = false;
});
} else {
if(!(interesting_event_mask_ & static_cast<int>(new_event_type))) return;
if(!(interesting_event_mask_ & int(new_event_type))) return;
interesting_event_mask_ &= ~new_event_type;
}
@@ -208,6 +226,7 @@ void WD1770::posit_event(int new_event_type) {
update_status([] (Status &status) {
status.busy = true;
status.interrupt_request = false;
status.track_zero = false; // Always reset by a non-type 1; so reset regardless and set properly later.
});
LOG("Starting " << PADHEX(2) << int(command_));
@@ -240,6 +259,7 @@ void WD1770::posit_event(int new_event_type) {
status.data_request = false;
});
LOG("Step/Seek/Restore with track " << int(track_) << " data " << int(data_));
if(!has_motor_on_line() && !has_head_load_line()) goto test_type1_type;
if(has_motor_on_line()) goto begin_type1_spin_up;
@@ -272,19 +292,19 @@ void WD1770::posit_event(int new_event_type) {
}
perform_seek_or_restore_command:
if(track_ == data_) goto verify;
if(track_ == data_) goto verify_seek;
step_direction_ = (data_ > track_);
adjust_track:
if(step_direction_) track_++; else track_--;
if(step_direction_) ++track_; else --track_;
perform_step:
if(!step_direction_ && get_drive().get_is_track_zero()) {
track_ = 0;
goto verify;
goto verify_seek;
}
get_drive().step(Storage::Disk::HeadPosition(step_direction_ ? 1 : -1));
unsigned int time_to_wait;
Cycles::IntType time_to_wait;
switch(command_ & 3) {
default:
case 0: time_to_wait = 6; break;
@@ -293,14 +313,17 @@ void WD1770::posit_event(int new_event_type) {
case 3: time_to_wait = (personality_ == P1772) ? 3 : 30; break;
}
WAIT_FOR_TIME(time_to_wait);
if(command_ >> 5) goto verify;
if(command_ >> 5) goto verify_seek;
goto perform_seek_or_restore_command;
perform_step_command:
if(command_ & 0x10) goto adjust_track;
goto perform_step;
verify:
verify_seek:
update_status([this] (Status &status) {
status.track_zero = get_drive().get_is_track_zero();
});
if(!(command_ & 0x04)) {
goto wait_for_command;
}
@@ -309,17 +332,20 @@ void WD1770::posit_event(int new_event_type) {
distance_into_section_ = 0;
verify_read_data:
WAIT_FOR_EVENT(static_cast<int>(Event::IndexHole) | static_cast<int>(Event::Token));
WAIT_FOR_EVENT(int(Event::IndexHole) | int(Event::Token));
READ_ID();
if(index_hole_count_ == 6) {
LOG("Nothing found to verify");
update_status([] (Status &status) {
status.seek_error = true;
});
goto wait_for_command;
}
if(distance_into_section_ == 7) {
distance_into_section_ = 0;
set_data_mode(DataMode::Scanning);
if(get_crc_generator().get_value()) {
update_status([] (Status &status) {
status.crc_error = true;
@@ -334,8 +360,6 @@ void WD1770::posit_event(int new_event_type) {
});
goto wait_for_command;
}
distance_into_section_ = 0;
}
goto verify_read_data;
@@ -392,8 +416,11 @@ void WD1770::posit_event(int new_event_type) {
goto wait_for_command;
}
distance_into_section_ = 0;
set_data_mode(DataMode::Scanning);
type2_get_header:
WAIT_FOR_EVENT(static_cast<int>(Event::IndexHole) | static_cast<int>(Event::Token));
WAIT_FOR_EVENT(int(Event::IndexHole) | int(Event::Token));
READ_ID();
if(index_hole_count_ == 5) {
@@ -404,8 +431,10 @@ void WD1770::posit_event(int new_event_type) {
goto wait_for_command;
}
if(distance_into_section_ == 7) {
LOG("Considering " << std::dec << int(header_[0]) << "/" << int(header_[2]));
distance_into_section_ = 0;
set_data_mode(DataMode::Scanning);
LOG("Considering " << std::dec << int(header_[0]) << "/" << int(header_[2]));
if( header_[0] == track_ && header_[2] == sector_ &&
(has_motor_on_line() || !(command_&0x02) || ((command_&0x08) >> 3) == header_[1])) {
LOG("Found " << std::dec << int(header_[0]) << "/" << int(header_[2]));
@@ -422,7 +451,6 @@ void WD1770::posit_event(int new_event_type) {
});
goto type2_read_or_write_data;
}
distance_into_section_ = 0;
}
goto type2_get_header;
@@ -453,7 +481,7 @@ void WD1770::posit_event(int new_event_type) {
status.data_request = true;
});
distance_into_section_++;
if(distance_into_section_ == 128 << header_[3]) {
if(distance_into_section_ == 128 << (header_[3]&3)) {
distance_into_section_ = 0;
goto type2_check_crc;
}
@@ -465,19 +493,24 @@ void WD1770::posit_event(int new_event_type) {
header_[distance_into_section_] = get_latest_token().byte_value;
distance_into_section_++;
if(distance_into_section_ == 2) {
distance_into_section_ = 0;
set_data_mode(DataMode::Scanning);
if(get_crc_generator().get_value()) {
LOG("CRC error; terminating");
update_status([this] (Status &status) {
update_status([] (Status &status) {
status.crc_error = true;
});
goto wait_for_command;
}
LOG("Finished reading sector " << std::dec << int(sector_));
if(command_ & 0x10) {
sector_++;
LOG("Advancing to search for sector " << std::dec << int(sector_));
goto test_type2_write_protection;
}
LOG("Finished reading sector " << std::dec << int(sector_));
goto wait_for_command;
}
goto type2_check_crc;
@@ -531,7 +564,7 @@ void WD1770::posit_event(int new_event_type) {
*/
write_byte(data_);
distance_into_section_++;
if(distance_into_section_ == 128 << header_[3]) {
if(distance_into_section_ == 128 << (header_[3]&3)) {
goto type2_write_crc;
}
@@ -610,8 +643,8 @@ void WD1770::posit_event(int new_event_type) {
distance_into_section_ = 0;
read_address_get_header:
WAIT_FOR_EVENT(static_cast<int>(Event::IndexHole) | static_cast<int>(Event::Token));
if(new_event_type == static_cast<int>(Event::Token)) {
WAIT_FOR_EVENT(int(Event::IndexHole) | int(Event::Token));
if(new_event_type == int(Event::Token)) {
if(!distance_into_section_ && get_latest_token().type == Token::ID) {set_data_mode(DataMode::Reading); distance_into_section_++; }
else if(distance_into_section_ && distance_into_section_ < 7 && get_latest_token().type == Token::Byte) {
if(status_.data_request) {
@@ -625,9 +658,11 @@ void WD1770::posit_event(int new_event_type) {
update_status([] (Status &status) {
status.data_request = true;
});
distance_into_section_++;
++distance_into_section_;
if(distance_into_section_ == 7) {
distance_into_section_ = 0;
if(get_crc_generator().get_value()) {
update_status([] (Status &status) {
status.crc_error = true;
@@ -651,7 +686,7 @@ void WD1770::posit_event(int new_event_type) {
index_hole_count_ = 0;
read_track_read_byte:
WAIT_FOR_EVENT(static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole));
WAIT_FOR_EVENT(int(Event::Token) | int(Event::IndexHole));
if(index_hole_count_) {
goto wait_for_command;
}
@@ -718,7 +753,7 @@ void WD1770::posit_event(int new_event_type) {
case 0xfd: case 0xfe:
// clock is 0xc7 = 1010 0000 0010 1010 = 0xa022
write_raw_short(
static_cast<uint16_t>(
uint16_t(
0xa022 |
((data_ & 0x80) << 7) |
((data_ & 0x40) << 6) |
@@ -767,21 +802,33 @@ void WD1770::posit_event(int new_event_type) {
}
void WD1770::update_status(std::function<void(Status &)> updater) {
const Status old_status = status_;
if(delegate_) {
Status old_status = status_;
updater(status_);
bool did_change =
const bool did_change =
(status_.busy != old_status.busy) ||
(status_.data_request != old_status.data_request);
(status_.data_request != old_status.data_request) ||
(status_.interrupt_request != old_status.interrupt_request);
if(did_change) delegate_->wd1770_did_change_output(this);
}
else updater(status_);
} else updater(status_);
if(status_.busy != old_status.busy) update_clocking_observer();
}
void WD1770::set_head_load_request(bool head_load) {}
void WD1770::set_motor_on(bool motor_on) {}
void WD1770::set_head_load_request(bool) {}
void WD1770::set_motor_on(bool) {}
void WD1770::set_head_loaded(bool head_loaded) {
head_is_loaded_ = head_loaded;
if(head_loaded) posit_event(static_cast<int>(Event1770::HeadLoad));
if(head_loaded) posit_event(int(Event1770::HeadLoad));
}
bool WD1770::get_head_loaded() const {
return head_is_loaded_;
}
ClockingHint::Preference WD1770::preferred_clocking() const {
if(status_.busy) return ClockingHint::Preference::RealTime;
return Storage::Disk::MFMController::preferred_clocking();
}

View File

@@ -31,57 +31,63 @@ class WD1770: public Storage::Disk::MFMController {
@param p The type of controller to emulate.
*/
WD1770(Personality p);
virtual ~WD1770() {}
/// Sets the value of the double-density input; when @c is_double_density is @c true, reads and writes double-density format data.
using Storage::Disk::MFMController::set_is_double_density;
/// Writes @c value to the register at @c address. Only the low two bits of the address are decoded.
void set_register(int address, uint8_t value);
void write(int address, uint8_t value);
/// Fetches the value of the register @c address. Only the low two bits of the address are decoded.
uint8_t get_register(int address);
uint8_t read(int address);
/// Runs the controller for @c number_of_cycles cycles.
void run_for(const Cycles cycles);
enum Flag: uint8_t {
NotReady = 0x80,
NotReady = 0x80, // 0x80
MotorOn = 0x80,
WriteProtect = 0x40,
RecordType = 0x20,
WriteProtect = 0x40, // 0x40
RecordType = 0x20, // 0x20
SpinUp = 0x20,
HeadLoaded = 0x20,
RecordNotFound = 0x10,
RecordNotFound = 0x10, // 0x10
SeekError = 0x10,
CRCError = 0x08,
LostData = 0x04,
CRCError = 0x08, // 0x08
LostData = 0x04, // 0x04
TrackZero = 0x04,
DataRequest = 0x02,
DataRequest = 0x02, // 0x02
Index = 0x02,
Busy = 0x01
Busy = 0x01 // 0x01
};
/// @returns The current value of the IRQ line output.
inline bool get_interrupt_request_line() { return status_.interrupt_request; }
inline bool get_interrupt_request_line() const { return status_.interrupt_request; }
/// @returns The current value of the DRQ line output.
inline bool get_data_request_line() { return status_.data_request; }
inline bool get_data_request_line() const { return status_.data_request; }
class Delegate {
public:
virtual void wd1770_did_change_output(WD1770 *wd1770) = 0;
};
inline void set_delegate(Delegate *delegate) { delegate_ = delegate; }
inline void set_delegate(Delegate *delegate) { delegate_ = delegate; }
ClockingHint::Preference preferred_clocking() const final;
protected:
virtual void set_head_load_request(bool head_load);
virtual void set_motor_on(bool motor_on);
void set_head_loaded(bool head_loaded);
/// @returns The last value posted to @c set_head_loaded.
bool get_head_loaded() const;
private:
Personality personality_;
inline bool has_motor_on_line() { return (personality_ != P1793 ) && (personality_ != P1773); }
inline bool has_head_load_line() { return (personality_ == P1793 ); }
const Personality personality_;
bool has_motor_on_line() const { return (personality_ != P1793 ) && (personality_ != P1773); }
bool has_head_load_line() const { return (personality_ == P1793 ); }
struct Status {
bool write_protect = false;
@@ -94,6 +100,7 @@ class WD1770: public Storage::Disk::MFMController {
bool data_request = false;
bool interrupt_request = false;
bool busy = false;
bool track_zero = false;
enum {
One, Two, Three
} type = One;
@@ -121,7 +128,7 @@ class WD1770: public Storage::Disk::MFMController {
void posit_event(int type);
int interesting_event_mask_;
int resume_point_ = 0;
unsigned int delay_time_ = 0;
Cycles::IntType delay_time_ = 0;
// ID buffer
uint8_t header_[6];

318
Components/5380/ncr5380.cpp Normal file
View File

@@ -0,0 +1,318 @@
//
// ncr5380.cpp
// Clock Signal
//
// Created by Thomas Harte on 10/08/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#include "ncr5380.hpp"
#include "../../Outputs/Log.hpp"
using namespace NCR::NCR5380;
using SCSI::Line;
NCR5380::NCR5380(SCSI::Bus &bus, int clock_rate) :
bus_(bus),
clock_rate_(clock_rate) {
device_id_ = bus_.add_device();
bus_.add_observer(this);
// TODO: use clock rate and expected phase. This implementation currently
// provides only CPU-driven polling behaviour.
(void)clock_rate_;
(void)expected_phase_;
}
void NCR5380::write(int address, uint8_t value, bool) {
switch(address & 7) {
case 0:
// LOG("[SCSI 0] Set current SCSI bus state to " << PADHEX(2) << int(value));
data_bus_ = value;
if(dma_request_ && dma_operation_ == DMAOperation::Send) {
// printf("w %02x\n", value);
dma_acknowledge_ = true;
dma_request_ = false;
update_control_output();
bus_.set_device_output(device_id_, bus_output_);
}
break;
case 1: {
// LOG("[SCSI 1] Initiator command register set: " << PADHEX(2) << int(value));
initiator_command_ = value;
bus_output_ &= ~(Line::Reset | Line::Acknowledge | Line::Busy | Line::SelectTarget | Line::Attention);
if(value & 0x80) bus_output_ |= Line::Reset;
if(value & 0x08) bus_output_ |= Line::Busy;
if(value & 0x04) bus_output_ |= Line::SelectTarget;
/* bit 5 = differential enable if this were a 5381 */
test_mode_ = value & 0x40;
assert_data_bus_ = value & 0x01;
update_control_output();
} break;
case 2:
// LOG("[SCSI 2] Set mode: " << PADHEX(2) << int(value));
mode_ = value;
// bit 7: 1 = use block mode DMA mode (if DMA mode is also enabled)
// bit 6: 1 = be a SCSI target; 0 = be an initiator
// bit 5: 1 = check parity
// bit 4: 1 = generate an interrupt if parity checking is enabled and an error is found
// bit 3: 1 = generate an interrupt when an EOP is received from the DMA controller
// bit 2: 1 = generate an interrupt and reset low 6 bits of register 1 if an unexpected loss of Line::Busy occurs
// bit 1: 1 = use DMA mode
// bit 0: 1 = begin arbitration mode (device ID should be in register 0)
arbitration_in_progress_ = false;
switch(mode_ & 0x3) {
case 0x0:
bus_output_ &= ~SCSI::Line::Busy;
dma_request_ = false;
set_execution_state(ExecutionState::None);
break;
case 0x1:
arbitration_in_progress_ = true;
set_execution_state(ExecutionState::WaitingForBusy);
lost_arbitration_ = false;
break;
default:
assert_data_bus_ = false;
set_execution_state(ExecutionState::PerformingDMA);
bus_.update_observers();
break;
}
update_control_output();
break;
case 3: {
// LOG("[SCSI 3] Set target command: " << PADHEX(2) << int(value));
target_command_ = value;
update_control_output();
} break;
case 4:
// LOG("[SCSI 4] Set select enabled: " << PADHEX(2) << int(value));
break;
case 5:
// LOG("[SCSI 5] Start DMA send: " << PADHEX(2) << int(value));
dma_operation_ = DMAOperation::Send;
break;
case 6:
// LOG("[SCSI 6] Start DMA target receive: " << PADHEX(2) << int(value));
dma_operation_ = DMAOperation::TargetReceive;
break;
case 7:
// LOG("[SCSI 7] Start DMA initiator receive: " << PADHEX(2) << int(value));
dma_operation_ = DMAOperation::InitiatorReceive;
break;
}
// Data is output only if the data bus is asserted.
if(assert_data_bus_) {
bus_output_ = (bus_output_ & ~SCSI::Line::Data) | data_bus_;
} else {
bus_output_ &= ~SCSI::Line::Data;
}
// In test mode, still nothing is output. Otherwise throw out
// the current value of bus_output_.
if(test_mode_) {
bus_.set_device_output(device_id_, SCSI::DefaultBusState);
} else {
bus_.set_device_output(device_id_, bus_output_);
}
}
uint8_t NCR5380::read(int address, bool) {
switch(address & 7) {
case 0:
// LOG("[SCSI 0] Get current SCSI bus state: " << PADHEX(2) << (bus_.get_state() & 0xff));
if(dma_request_ && dma_operation_ == DMAOperation::InitiatorReceive) {
dma_acknowledge_ = true;
dma_request_ = false;
update_control_output();
bus_.set_device_output(device_id_, bus_output_);
}
return uint8_t(bus_.get_state());
case 1:
// LOG("[SCSI 1] Initiator command register get: " << (arbitration_in_progress_ ? 'p' : '-') << (lost_arbitration_ ? 'l' : '-'));
return
// Bits repeated as they were set.
(initiator_command_ & ~0x60) |
// Arbitration in progress.
(arbitration_in_progress_ ? 0x40 : 0x00) |
// Lost arbitration.
(lost_arbitration_ ? 0x20 : 0x00);
case 2:
// LOG("[SCSI 2] Get mode");
return mode_;
case 3:
// LOG("[SCSI 3] Get target command");
return target_command_;
case 4: {
const auto bus_state = bus_.get_state();
const uint8_t result =
((bus_state & Line::Reset) ? 0x80 : 0x00) |
((bus_state & Line::Busy) ? 0x40 : 0x00) |
((bus_state & Line::Request) ? 0x20 : 0x00) |
((bus_state & Line::Message) ? 0x10 : 0x00) |
((bus_state & Line::Control) ? 0x08 : 0x00) |
((bus_state & Line::Input) ? 0x04 : 0x00) |
((bus_state & Line::SelectTarget) ? 0x02 : 0x00) |
((bus_state & Line::Parity) ? 0x01 : 0x00);
// LOG("[SCSI 4] Get current bus state: " << PADHEX(2) << int(result));
return result;
}
case 5: {
const auto bus_state = bus_.get_state();
const bool phase_matches =
(target_output() & (Line::Message | Line::Control | Line::Input)) ==
(bus_state & (Line::Message | Line::Control | Line::Input));
const uint8_t result =
/* b7 = end of DMA */
((dma_request_ && state_ == ExecutionState::PerformingDMA) ? 0x40 : 0x00) |
/* b5 = parity error */
/* b4 = IRQ active */
(phase_matches ? 0x08 : 0x00) |
/* b2 = busy error */
((bus_state & Line::Attention) ? 0x02 : 0x00) |
((bus_state & Line::Acknowledge) ? 0x01 : 0x00);
// LOG("[SCSI 5] Get bus and status: " << PADHEX(2) << int(result));
return result;
}
case 6:
// LOG("[SCSI 6] Get input data");
return 0xff;
case 7:
// LOG("[SCSI 7] Reset parity/interrupt");
return 0xff;
}
return 0;
}
SCSI::BusState NCR5380::target_output() {
SCSI::BusState output = SCSI::DefaultBusState;
if(target_command_ & 0x08) output |= Line::Request;
if(target_command_ & 0x04) output |= Line::Message;
if(target_command_ & 0x02) output |= Line::Control;
if(target_command_ & 0x01) output |= Line::Input;
return output;
}
void NCR5380::update_control_output() {
bus_output_ &= ~(Line::Request | Line::Message | Line::Control | Line::Input | Line::Acknowledge | Line::Attention);
if(mode_ & 0x40) {
// This is a target; C/D, I/O, /MSG and /REQ are signalled on the bus.
bus_output_ |= target_output();
} else {
// This is an initiator; /ATN and /ACK are signalled on the bus.
if(
(initiator_command_ & 0x10) ||
(state_ == ExecutionState::PerformingDMA && dma_acknowledge_)
) bus_output_ |= Line::Acknowledge;
if(initiator_command_ & 0x02) bus_output_ |= Line::Attention;
}
}
void NCR5380::scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double time_since_change) {
switch(state_) {
default: break;
/*
Official documentation:
Arbitration is accomplished using a bus-free filter to continuously monitor BSY.
If BSY remains inactive for at least 400 nsec then the SCSI bus is considered free
and arbitration may begin. Arbitration will begin if the bus is free, SEL is inactive
and the ARBITRATION bit (port 2, bit 0) is active. Once arbitration has begun
(BSY asserted), an arbitration delay of 2.2 /Lsec must elapse before the data bus
can be examined to deter- mine if arbitration has been won. This delay must be
implemented in the controlling software driver.
Personal notes:
I'm discounting that "arbitratation is accomplished" opening, and assuming that what needs
to happen is:
(i) wait for BSY to be inactive;
(ii) count 400 nsec;
(iii) check that BSY and SEL are inactive.
*/
case ExecutionState::WaitingForBusy:
if(!(new_state & SCSI::Line::Busy) || time_since_change < SCSI::DeskewDelay) return;
state_ = ExecutionState::WatchingBusy;
[[fallthrough]];
case ExecutionState::WatchingBusy:
if(!(new_state & SCSI::Line::Busy)) {
lost_arbitration_ = true;
set_execution_state(ExecutionState::None);
}
// Check for having hit 400ns (more or less) since BSY was inactive.
if(time_since_change >= SCSI::BusSettleDelay) {
// arbitration_in_progress_ = false;
if(new_state & SCSI::Line::SelectTarget) {
lost_arbitration_ = true;
set_execution_state(ExecutionState::None);
} else {
bus_output_ &= ~SCSI::Line::Busy;
set_execution_state(ExecutionState::None);
}
}
/* TODO: there's a bug here, given that the dropping of Busy isn't communicated onward. */
break;
case ExecutionState::PerformingDMA:
if(time_since_change < SCSI::DeskewDelay) return;
// Signal a DMA request if the request line is active, i.e. meaningful data is
// on the bus, and this device hasn't yet acknowledged it.
switch(new_state & (SCSI::Line::Request | SCSI::Line::Acknowledge)) {
case 0:
dma_request_ = false;
break;
case SCSI::Line::Request:
dma_request_ = true;
break;
case SCSI::Line::Request | SCSI::Line::Acknowledge:
dma_request_ = false;
break;
case SCSI::Line::Acknowledge:
dma_acknowledge_ = false;
dma_request_ = false;
update_control_output();
bus_.set_device_output(device_id_, bus_output_);
break;
}
break;
}
}
void NCR5380::set_execution_state(ExecutionState state) {
state_ = state;
if(state != ExecutionState::PerformingDMA) dma_operation_ = DMAOperation::Ready;
}

View File

@@ -0,0 +1,75 @@
//
// ncr5380.hpp
// Clock Signal
//
// Created by Thomas Harte on 10/08/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef ncr5380_hpp
#define ncr5380_hpp
#include <cstdint>
#include "../../Storage/MassStorage/SCSI/SCSI.hpp"
namespace NCR {
namespace NCR5380 {
/*!
Models the NCR 5380, a SCSI interface chip.
*/
class NCR5380 final: public SCSI::Bus::Observer {
public:
NCR5380(SCSI::Bus &bus, int clock_rate);
/*! Writes @c value to @c address. */
void write(int address, uint8_t value, bool dma_acknowledge = false);
/*! Reads from @c address. */
uint8_t read(int address, bool dma_acknowledge = false);
private:
SCSI::Bus &bus_;
const int clock_rate_;
size_t device_id_;
SCSI::BusState bus_output_ = SCSI::DefaultBusState;
SCSI::BusState expected_phase_ = SCSI::DefaultBusState;
uint8_t mode_ = 0xff;
uint8_t initiator_command_ = 0xff;
uint8_t data_bus_ = 0xff;
uint8_t target_command_ = 0xff;
bool test_mode_ = false;
bool assert_data_bus_ = false;
bool dma_request_ = false;
bool dma_acknowledge_ = false;
enum class ExecutionState {
None,
WaitingForBusy,
WatchingBusy,
PerformingDMA,
} state_ = ExecutionState::None;
enum class DMAOperation {
Ready,
Send,
TargetReceive,
InitiatorReceive
} dma_operation_ = DMAOperation::Ready;
bool lost_arbitration_ = false, arbitration_in_progress_ = false;
void set_execution_state(ExecutionState state);
SCSI::BusState target_output();
void update_control_output();
void scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double time_since_change) final;
};
}
}
#endif /* ncr5380_hpp */

View File

@@ -37,22 +37,22 @@ enum Line {
class PortHandler {
public:
/// Requests the current input value of @c port from the port handler.
uint8_t get_port_input(Port port) { return 0xff; }
uint8_t get_port_input([[maybe_unused]] Port port) { return 0xff; }
/// Sets the current output value of @c port and provides @c direction_mask, indicating which pins are marked as output.
void set_port_output(Port port, uint8_t value, uint8_t direction_mask) {}
void set_port_output([[maybe_unused]] Port port, [[maybe_unused]] uint8_t value, [[maybe_unused]] uint8_t direction_mask) {}
/// Sets the current logical output level for line @c line on port @c port.
void set_control_line_output(Port port, Line line, bool value) {}
void set_control_line_output([[maybe_unused]] Port port, [[maybe_unused]] Line line, [[maybe_unused]] bool value) {}
/// Sets the current logical value of the interrupt line.
void set_interrupt_status(bool status) {}
void set_interrupt_status([[maybe_unused]] bool status) {}
/// Provides a measure of time elapsed between other calls.
void run_for(HalfCycles duration) {}
void run_for([[maybe_unused]] HalfCycles duration) {}
/// Receives passed-on flush() calls from the 6522.
void flush() {}
void flush() {}
};
/*!
@@ -94,10 +94,10 @@ template <class T> class MOS6522: public MOS6522Storage {
MOS6522(const MOS6522 &) = delete;
/*! Sets a register value. */
void set_register(int address, uint8_t value);
void write(int address, uint8_t value);
/*! Gets a register value. */
uint8_t get_register(int address);
uint8_t read(int address);
/*! @returns the bus handler. */
T &bus_handler();
@@ -112,7 +112,7 @@ template <class T> class MOS6522: public MOS6522Storage {
void run_for(const Cycles cycles);
/// @returns @c true if the IRQ line is currently active; @c false otherwise.
bool get_interrupt_line();
bool get_interrupt_line() const;
/// Updates the port handler to the current time and then requests that it flush.
void flush();
@@ -128,13 +128,14 @@ template <class T> class MOS6522: public MOS6522Storage {
void access(int address);
uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output);
uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output, uint8_t timer_mask);
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();
void evaluate_port_b_output();
};
}

View File

@@ -8,6 +8,10 @@
#include "../../../Outputs/Log.hpp"
// As-yet unimplemented (incomplete list):
//
// PB6 count-down mode for timer 2.
namespace MOS {
namespace MOS6522 {
@@ -30,22 +34,22 @@ template <typename T> void MOS6522<T>::access(int address) {
}
}
template <typename T> void MOS6522<T>::set_register(int address, uint8_t value) {
template <typename T> void MOS6522<T>::write(int address, uint8_t value) {
address &= 0xf;
access(address);
switch(address) {
case 0x0: // Write Port B.
case 0x0: // Write Port B. ('ORB')
// Store locally and communicate outwards.
registers_.output[1] = value;
bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
bus_handler_.set_port_output(Port::B, value, registers_.data_direction[1]);
evaluate_port_b_output();
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | ((registers_.peripheral_control&0x20) ? 0 : InterruptFlag::CB2ActiveEdge));
reevaluate_interrupts();
break;
case 0xf:
case 0x1: // Write Port A.
case 0x1: // Write Port A. ('ORA')
registers_.output[0] = value;
bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
@@ -59,36 +63,52 @@ template <typename T> void MOS6522<T>::set_register(int address, uint8_t value)
reevaluate_interrupts();
break;
case 0x2: // Port B direction.
case 0x2: // Port B direction ('DDRB').
registers_.data_direction[1] = value;
break;
case 0x3: // Port A direction.
case 0x3: // Port A direction ('DDRA').
registers_.data_direction[0] = value;
break;
// Timer 1
case 0x6: case 0x4: registers_.timer_latch[0] = (registers_.timer_latch[0]&0xff00) | value; break;
case 0x5: case 0x7:
registers_.timer_latch[0] = (registers_.timer_latch[0]&0x00ff) | static_cast<uint16_t>(value << 8);
registers_.interrupt_flags &= ~InterruptFlag::Timer1;
if(address == 0x05) {
registers_.next_timer[0] = registers_.timer_latch[0];
timer_is_running_[0] = true;
case 0x6: case 0x4: // ('T1L-L' and 'T1C-L')
registers_.timer_latch[0] = (registers_.timer_latch[0]&0xff00) | value;
break;
case 0x7: // Timer 1 latch, high ('T1L-H').
registers_.timer_latch[0] = (registers_.timer_latch[0]&0x00ff) | uint16_t(value << 8);
break;
case 0x5: // Timer 1 counter, high ('T1C-H').
// Fill latch.
registers_.timer_latch[0] = (registers_.timer_latch[0]&0x00ff) | uint16_t(value << 8);
// Restart timer.
registers_.next_timer[0] = registers_.timer_latch[0];
timer_is_running_[0] = true;
// If PB7 output mode is active, set it low.
if(timer1_is_controlling_pb7()) {
registers_.timer_port_b_output &= 0x7f;
evaluate_port_b_output();
}
// Clear existing interrupt flag.
registers_.interrupt_flags &= ~InterruptFlag::Timer1;
reevaluate_interrupts();
break;
// Timer 2
case 0x8: registers_.timer_latch[1] = value; break;
case 0x9:
case 0x8: // ('T2C-L')
registers_.timer_latch[1] = value;
break;
case 0x9: // ('T2C-H')
registers_.interrupt_flags &= ~InterruptFlag::Timer2;
registers_.next_timer[1] = registers_.timer_latch[1] | static_cast<uint16_t>(value << 8);
registers_.next_timer[1] = registers_.timer_latch[1] | uint16_t(value << 8);
timer_is_running_[1] = true;
reevaluate_interrupts();
break;
// Shift
case 0xa:
case 0xa: // ('SR')
registers_.shift = value;
shift_bits_remaining_ = 8;
registers_.interrupt_flags &= ~InterruptFlag::ShiftRegister;
@@ -96,11 +116,18 @@ template <typename T> void MOS6522<T>::set_register(int address, uint8_t value)
break;
// Control
case 0xb:
case 0xb: // Auxiliary control ('ACR').
registers_.auxiliary_control = value;
evaluate_cb2_output();
// This is a bit of a guess: reset the timer-based PB7 output to its default high level
// any timer that timer-linked PB7 output is disabled.
if(!timer1_is_controlling_pb7()) {
registers_.timer_port_b_output |= 0x80;
}
evaluate_port_b_output();
break;
case 0xc: {
case 0xc: { // Peripheral control ('PCR').
// const auto old_peripheral_control = registers_.peripheral_control;
registers_.peripheral_control = value;
@@ -141,11 +168,11 @@ template <typename T> void MOS6522<T>::set_register(int address, uint8_t value)
} break;
// Interrupt control
case 0xd:
case 0xd: // Interrupt flag regiser ('IFR').
registers_.interrupt_flags &= ~value;
reevaluate_interrupts();
break;
case 0xe:
case 0xe: // Interrupt enable register ('IER').
if(value&0x80)
registers_.interrupt_enable |= value;
else
@@ -155,58 +182,59 @@ template <typename T> void MOS6522<T>::set_register(int address, uint8_t value)
}
}
template <typename T> uint8_t MOS6522<T>::get_register(int address) {
template <typename T> uint8_t MOS6522<T>::read(int address) {
address &= 0xf;
access(address);
switch(address) {
case 0x0:
case 0x0: // Read Port B ('IRB').
registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge);
reevaluate_interrupts();
return get_port_input(Port::B, registers_.data_direction[1], registers_.output[1]);
return get_port_input(Port::B, registers_.data_direction[1], registers_.output[1], registers_.auxiliary_control & 0x80);
case 0xf:
case 0x1:
case 0x1: // Read Port A ('IRA').
registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | InterruptFlag::CA2ActiveEdge);
reevaluate_interrupts();
return get_port_input(Port::A, registers_.data_direction[0], registers_.output[0]);
return get_port_input(Port::A, registers_.data_direction[0], registers_.output[0], 0);
case 0x2: return registers_.data_direction[1];
case 0x3: return registers_.data_direction[0];
case 0x2: return registers_.data_direction[1]; // Port B direction ('DDRB').
case 0x3: return registers_.data_direction[0]; // Port A direction ('DDRA').
// Timer 1
case 0x4:
case 0x4: // Timer 1 low-order latches ('T1L-L').
registers_.interrupt_flags &= ~InterruptFlag::Timer1;
reevaluate_interrupts();
return registers_.timer[0] & 0x00ff;
case 0x5: return registers_.timer[0] >> 8;
case 0x6: return registers_.timer_latch[0] & 0x00ff;
case 0x7: return registers_.timer_latch[0] >> 8;
case 0x5: return registers_.timer[0] >> 8; // Timer 1 high-order counter ('T1C-H')
case 0x6: return registers_.timer_latch[0] & 0x00ff; // Timer 1 low-order latches ('T1L-L').
case 0x7: return registers_.timer_latch[0] >> 8; // Timer 1 high-order latches ('T1L-H').
// Timer 2
case 0x8:
case 0x8: // Timer 2 low-order counter ('T2C-L').
registers_.interrupt_flags &= ~InterruptFlag::Timer2;
reevaluate_interrupts();
return registers_.timer[1] & 0x00ff;
case 0x9: return registers_.timer[1] >> 8;
case 0x9: return registers_.timer[1] >> 8; // Timer 2 high-order counter ('T2C-H').
case 0xa:
case 0xa: // Shift register ('SR').
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;
case 0xb: return registers_.auxiliary_control; // Auxiliary control ('ACR').
case 0xc: return registers_.peripheral_control; // Peripheral control ('PCR').
case 0xd: return registers_.interrupt_flags | (get_interrupt_line() ? 0x80 : 0x00);
case 0xe: return registers_.interrupt_enable | 0x80;
case 0xd: return registers_.interrupt_flags | (get_interrupt_line() ? 0x80 : 0x00); // Interrupt flag register ('IFR').
case 0xe: return registers_.interrupt_enable | 0x80; // Interrupt enable register ('IER').
}
return 0xff;
}
template <typename T> uint8_t MOS6522<T>::get_port_input(Port port, uint8_t output_mask, uint8_t output) {
template <typename T> uint8_t MOS6522<T>::get_port_input(Port port, uint8_t output_mask, uint8_t output, uint8_t timer_mask) {
bus_handler_.run_for(time_since_bus_handler_call_.flush<HalfCycles>());
const uint8_t input = bus_handler_.get_port_input(port);
output = (output & ~timer_mask) | (registers_.timer_port_b_output & timer_mask);
return (input & ~output_mask) | (output & output_mask);
}
@@ -276,16 +304,19 @@ template <typename T> void MOS6522<T>::do_phase2() {
registers_.timer_needs_reload = false;
registers_.timer[0] = registers_.timer_latch[0];
} else {
registers_.timer[0] --;
-- registers_.timer[0];
}
registers_.timer[1] --;
// Count down timer 2 if it is in timed interrupt mode (i.e. auxiliary control bit 5 is clear).
registers_.timer[1] -= timer2_clock_decrement();
// TODO: can eliminate conditional branches here.
if(registers_.next_timer[0] >= 0) {
registers_.timer[0] = static_cast<uint16_t>(registers_.next_timer[0]);
registers_.timer[0] = 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_.timer[1] = uint16_t(registers_.next_timer[1]);
registers_.next_timer[1] = -1;
}
@@ -330,23 +361,32 @@ template <typename T> void MOS6522<T>::do_phase1() {
reevaluate_interrupts();
// Determine whether to reload.
if(registers_.auxiliary_control&0x40)
if(timer1_is_continuous())
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;
if(timer1_is_controlling_pb7()) {
registers_.timer_port_b_output ^= 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]);
evaluate_port_b_output();
}
}
}
template <typename T> void MOS6522<T>::evaluate_port_b_output() {
// Apply current timer-linked PB7 output if any atop the stated output.
const uint8_t timer_control_bit = registers_.auxiliary_control & 0x80;
bus_handler_.set_port_output(
Port::B,
(registers_.output[1] & (0xff ^ timer_control_bit)) | timer_control_bit,
registers_.data_direction[1] | timer_control_bit);
}
/*! 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();
auto number_of_half_cycles = half_cycles.as_integral();
if(!number_of_half_cycles) return;
if(is_phase2_) {
@@ -375,7 +415,7 @@ template <typename T> void MOS6522<T>::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();
auto number_of_cycles = cycles.as_integral();
while(number_of_cycles--) {
do_phase1();
do_phase2();
@@ -383,9 +423,9 @@ template <typename T> void MOS6522<T>::run_for(const Cycles cycles) {
}
/*! @returns @c true if the IRQ line is currently active; @c false otherwise. */
template <typename T> bool MOS6522<T>::get_interrupt_line() {
template <typename T> bool MOS6522<T>::get_interrupt_line() const {
uint8_t interrupt_status = registers_.interrupt_flags & registers_.interrupt_enable & 0x7f;
return !!interrupt_status;
return interrupt_status;
}
template <typename T> void MOS6522<T>::evaluate_cb2_output() {
@@ -438,10 +478,11 @@ template <typename T> void MOS6522<T>::shift_in() {
}
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));
const bool is_free_running = shift_mode() == ShiftMode::OutUnderT2FreeRunning;
if(is_free_running || shift_bits_remaining_) {
// Recirculate bits only if in free-running mode (?)
const uint8_t incoming_bit = (registers_.shift >> 7) * is_free_running;
registers_.shift = uint8_t(registers_.shift << 1) | incoming_bit;
evaluate_cb2_output();
--shift_bits_remaining_;

View File

@@ -34,7 +34,9 @@ class MOS6522Storage {
uint8_t peripheral_control = 0;
uint8_t interrupt_flags = 0;
uint8_t interrupt_enable = 0;
bool timer_needs_reload = false;
uint8_t timer_port_b_output = 0xff;
} registers_;
// Control state.
@@ -79,12 +81,30 @@ class MOS6522Storage {
OutUnderPhase2 = 6,
OutUnderCB1 = 7
};
ShiftMode shift_mode() const {
return ShiftMode((registers_.auxiliary_control >> 2) & 7);
bool timer1_is_controlling_pb7() const {
return registers_.auxiliary_control & 0x80;
}
bool timer1_is_continuous() const {
return registers_.auxiliary_control & 0x40;
}
bool is_shifting_out() const {
return registers_.auxiliary_control & 0x10;
}
int timer2_clock_decrement() const {
return 1 ^ ((registers_.auxiliary_control >> 5)&1);
}
int timer2_pb6_decrement() const {
return (registers_.auxiliary_control >> 5)&1;
}
ShiftMode shift_mode() const {
return ShiftMode((registers_.auxiliary_control >> 2) & 7);
}
bool portb_is_latched() const {
return registers_.auxiliary_control & 0x02;
}
bool port1_is_latched() const {
return registers_.auxiliary_control & 0x01;
}
};
}

View File

@@ -14,6 +14,6 @@ void IRQDelegatePortHandler::set_interrupt_delegate(Delegate *delegate) {
delegate_ = delegate;
}
void IRQDelegatePortHandler::set_interrupt_status(bool new_status) {
void IRQDelegatePortHandler::set_interrupt_status(bool) {
if(delegate_) delegate_->mos6522_did_change_interrupt_status(this);
}

View File

@@ -32,7 +32,7 @@ template <class T> class MOS6532 {
inline void set_ram(uint16_t address, uint8_t value) { ram_[address&0x7f] = value; }
inline uint8_t get_ram(uint16_t address) { return ram_[address & 0x7f]; }
inline void set_register(int address, uint8_t value) {
inline void write(int address, uint8_t value) {
const uint8_t decodedAddress = address & 0x07;
switch(decodedAddress) {
// Port output
@@ -51,7 +51,7 @@ template <class T> class MOS6532 {
case 0x04: case 0x05: case 0x06: case 0x07:
if(address & 0x10) {
timer_.writtenShift = timer_.activeShift = (decodedAddress - 0x04) * 3 + (decodedAddress / 0x07); // i.e. 0, 3, 6, 10
timer_.value = (static_cast<unsigned int>(value) << timer_.activeShift) ;
timer_.value = (unsigned(value) << timer_.activeShift) ;
timer_.interrupt_enabled = !!(address&0x08);
interrupt_status_ &= ~InterruptFlag::Timer;
evaluate_interrupts();
@@ -63,7 +63,7 @@ template <class T> class MOS6532 {
}
}
inline uint8_t get_register(int address) {
inline uint8_t read(int address) {
const uint8_t decodedAddress = address & 0x7;
switch(decodedAddress) {
// Port input
@@ -79,7 +79,7 @@ template <class T> class MOS6532 {
// Timer and interrupt control
case 0x04: case 0x06: {
uint8_t value = static_cast<uint8_t>(timer_.value >> timer_.activeShift);
uint8_t value = uint8_t(timer_.value >> timer_.activeShift);
timer_.interrupt_enabled = !!(address&0x08);
interrupt_status_ &= ~InterruptFlag::Timer;
evaluate_interrupts();
@@ -107,7 +107,7 @@ template <class T> class MOS6532 {
}
inline void run_for(const Cycles cycles) {
unsigned int number_of_cycles = static_cast<unsigned int>(cycles.as_int());
unsigned int number_of_cycles = unsigned(cycles.as_integral());
// permit counting _to_ zero; counting _through_ zero initiates the other behaviour
if(timer_.value >= number_of_cycles) {
@@ -122,7 +122,7 @@ template <class T> class MOS6532 {
}
MOS6532() {
timer_.value = static_cast<unsigned int>((rand() & 0xff) << 10);
timer_.value = unsigned((rand() & 0xff) << 10);
}
inline void set_port_did_change(int port) {
@@ -142,7 +142,7 @@ template <class T> class MOS6532 {
}
}
inline bool get_inerrupt_line() {
inline bool get_inerrupt_line() const {
return interrupt_line_;
}
@@ -173,9 +173,11 @@ template <class T> class MOS6532 {
bool interrupt_line_ = false;
// expected to be overridden
uint8_t get_port_input(int port) { return 0xff; }
void set_port_output(int port, uint8_t value, uint8_t output_mask) {}
void set_irq_line(bool new_value) {}
void set_port_output([[maybe_unused]] int port, [[maybe_unused]] uint8_t value, [[maybe_unused]] uint8_t output_mask) {}
uint8_t get_port_input([[maybe_unused]] int port) {
return 0xff;
}
void set_irq_line(bool) {}
inline void evaluate_interrupts() {
interrupt_line_ =

View File

@@ -17,13 +17,13 @@ AudioGenerator::AudioGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue
void AudioGenerator::set_volume(uint8_t volume) {
audio_queue_.defer([=]() {
volume_ = static_cast<int16_t>(volume) * range_multiplier_;
audio_queue_.defer([this, volume]() {
volume_ = int16_t(volume) * range_multiplier_;
});
}
void AudioGenerator::set_control(int channel, uint8_t value) {
audio_queue_.defer([=]() {
audio_queue_.defer([this, channel, value]() {
control_registers_[channel] = value;
});
}
@@ -98,7 +98,7 @@ static uint8_t noise_pattern[] = {
#define shift(r) shift_registers_[r] = (shift_registers_[r] << 1) | (((shift_registers_[r]^0x80)&control_registers_[r]) >> 7)
#define increment(r) shift_registers_[r] = (shift_registers_[r]+1)%8191
#define update(r, m, up) counters_[r]++; if((counters_[r] >> m) == 0x80) { up(r); counters_[r] = static_cast<unsigned int>(control_registers_[r]&0x7f) << m; }
#define update(r, m, up) counters_[r]++; if((counters_[r] >> m) == 0x80) { up(r); counters_[r] = unsigned(control_registers_[r]&0x7f) << m; }
// Note on slightly askew test: as far as I can make out, if the value in the register is 0x7f then what's supposed to happen
// is that the 0x7f is loaded, on the next clocked cycle the Vic spots a 0x7f, pumps the output, reloads, etc. No increment
// ever occurs. It's conditional. I don't really want two conditionals if I can avoid it so I'm incrementing regardless and
@@ -114,7 +114,7 @@ void AudioGenerator::get_samples(std::size_t number_of_samples, int16_t *target)
// this sums the output of all three sounds channels plus a DC offset for volume;
// TODO: what's the real ratio of this stuff?
target[c] = static_cast<int16_t>(
target[c] = int16_t(
(shift_registers_[0]&1) +
(shift_registers_[1]&1) +
(shift_registers_[2]&1) +
@@ -133,7 +133,7 @@ void AudioGenerator::skip_samples(std::size_t number_of_samples) {
}
void AudioGenerator::set_sample_volume_range(std::int16_t range) {
range_multiplier_ = static_cast<int16_t>(range / 64);
range_multiplier_ = int16_t(range / 64);
}
#undef shift

View File

@@ -30,6 +30,7 @@ class AudioGenerator: public ::Outputs::Speaker::SampleSource {
void get_samples(std::size_t number_of_samples, int16_t *target);
void skip_samples(std::size_t number_of_samples);
void set_sample_volume_range(std::int16_t range);
static constexpr bool get_is_stereo() { return false; }
private:
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
@@ -42,7 +43,7 @@ class AudioGenerator: public ::Outputs::Speaker::SampleSource {
};
struct BusHandler {
void perform_read(uint16_t address, uint8_t *pixel_data, uint8_t *colour_data) {
void perform_read([[maybe_unused]] uint16_t address, [[maybe_unused]] uint8_t *pixel_data, [[maybe_unused]] uint8_t *colour_data) {
*pixel_data = 0xff;
*colour_data = 0xff;
}
@@ -58,7 +59,7 @@ enum class OutputMode {
To run the VIC for a cycle, the caller should call @c get_address, make the requested bus access
and call @c set_graphics_value with the result.
@c set_register and @c get_register provide register access.
@c write and @c read provide register access.
*/
template <class BusHandler> class MOS6560 {
public:
@@ -80,12 +81,14 @@ template <class BusHandler> class MOS6560 {
}
void set_clock_rate(double clock_rate) {
speaker_.set_input_rate(static_cast<float>(clock_rate / 4.0));
speaker_.set_input_rate(float(clock_rate / 4.0));
}
void set_scan_target(Outputs::Display::ScanTarget *scan_target) { crt_.set_scan_target(scan_target); }
void set_display_type(Outputs::Display::DisplayType display_type) { crt_.set_display_type(display_type); }
Outputs::Speaker::Speaker *get_speaker() { return &speaker_; }
void set_scan_target(Outputs::Display::ScanTarget *scan_target) { crt_.set_scan_target(scan_target); }
Outputs::Display::ScanStatus get_scaled_scan_status() const { return crt_.get_scaled_scan_status() / 4.0f; }
void set_display_type(Outputs::Display::DisplayType display_type) { crt_.set_display_type(display_type); }
Outputs::Display::DisplayType get_display_type() const { return crt_.get_display_type(); }
Outputs::Speaker::Speaker *get_speaker() { return &speaker_; }
void set_high_frequency_cutoff(float cutoff) {
speaker_.set_high_frequency_cutoff(cutoff);
@@ -170,7 +173,7 @@ template <class BusHandler> class MOS6560 {
// keep track of the amount of time since the speaker was updated; lazy updates are applied
cycles_since_speaker_update_ += cycles;
int number_of_cycles = cycles.as_int();
auto number_of_cycles = cycles.as_integral();
while(number_of_cycles--) {
// keep an old copy of the vertical count because that test is a cycle later than the actual changes
int previous_vertical_counter = vertical_counter_;
@@ -232,7 +235,7 @@ template <class BusHandler> class MOS6560 {
if(column_counter_&1) {
fetch_address = registers_.character_cell_start_address + (character_code_*(registers_.tall_characters ? 16 : 8)) + current_character_row_;
} else {
fetch_address = static_cast<uint16_t>(registers_.video_matrix_start_address + video_matrix_address_counter_);
fetch_address = uint16_t(registers_.video_matrix_start_address + video_matrix_address_counter_);
video_matrix_address_counter_++;
if(
(current_character_row_ == 15) ||
@@ -353,7 +356,7 @@ template <class BusHandler> class MOS6560 {
/*!
Writes to a 6560 register.
*/
void set_register(int address, uint8_t value) {
void write(int address, uint8_t value) {
address &= 0xf;
registers_.direct_values[address] = value;
switch(address) {
@@ -368,7 +371,7 @@ template <class BusHandler> class MOS6560 {
case 0x2:
registers_.number_of_columns = value & 0x7f;
registers_.video_matrix_start_address = static_cast<uint16_t>((registers_.video_matrix_start_address & 0x3c00) | ((value & 0x80) << 2));
registers_.video_matrix_start_address = uint16_t((registers_.video_matrix_start_address & 0x3c00) | ((value & 0x80) << 2));
break;
case 0x3:
@@ -377,8 +380,8 @@ template <class BusHandler> class MOS6560 {
break;
case 0x5:
registers_.character_cell_start_address = static_cast<uint16_t>((value & 0x0f) << 10);
registers_.video_matrix_start_address = static_cast<uint16_t>((registers_.video_matrix_start_address & 0x0200) | ((value & 0xf0) << 6));
registers_.character_cell_start_address = uint16_t((value & 0x0f) << 10);
registers_.video_matrix_start_address = uint16_t((registers_.video_matrix_start_address & 0x0200) | ((value & 0xf0) << 6));
break;
case 0xa:
@@ -417,11 +420,11 @@ template <class BusHandler> class MOS6560 {
/*
Reads from a 6560 register.
*/
uint8_t get_register(int address) {
uint8_t read(int address) const {
address &= 0xf;
switch(address) {
default: return registers_.direct_values[address];
case 0x03: return static_cast<uint8_t>(raster_value() << 7) | (registers_.direct_values[3] & 0x7f);
case 0x03: return uint8_t(raster_value() << 7) | (registers_.direct_values[3] & 0x7f);
case 0x04: return (raster_value() >> 1) & 0xff;
}
}
@@ -442,28 +445,28 @@ template <class BusHandler> class MOS6560 {
// register state
struct {
bool interlaced = false, tall_characters = false;
uint8_t first_column_location, first_row_location;
uint8_t number_of_columns, number_of_rows;
uint16_t character_cell_start_address, video_matrix_start_address;
uint16_t backgroundColour, borderColour, auxiliary_colour;
uint8_t first_column_location = 0, first_row_location = 0;
uint8_t number_of_columns = 0, number_of_rows = 0;
uint16_t character_cell_start_address = 0, video_matrix_start_address = 0;
uint16_t backgroundColour = 0, borderColour = 0, auxiliary_colour = 0;
bool invertedCells = false;
uint8_t direct_values[16];
uint8_t direct_values[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
} registers_;
// output state
enum State {
Sync, ColourBurst, Border, Pixels
} this_state_, output_state_;
int cycles_in_state_;
} this_state_ = State::Sync, output_state_ = State::Sync;
int cycles_in_state_ = 0;
// counters that cover an entire field
int horizontal_counter_ = 0, vertical_counter_ = 0;
const int lines_this_field() {
int lines_this_field() const {
// Necessary knowledge here: only the NTSC 6560 supports interlaced video.
return registers_.interlaced ? (is_odd_frame_ ? 262 : 263) : timing_.lines_per_progressive_field;
}
const int raster_value() {
int raster_value() const {
const int bonus_line = (horizontal_counter_ + timing_.line_counter_increment_offset) / timing_.cycles_per_line;
const int line = vertical_counter_ + bonus_line;
const int final_line = lines_this_field();
@@ -478,29 +481,29 @@ template <class BusHandler> class MOS6560 {
}
// Cf. http://www.sleepingelephant.com/ipw-web/bulletin/bb/viewtopic.php?f=14&t=7237&start=15#p80737
}
bool is_odd_frame() {
bool is_odd_frame() const {
return is_odd_frame_ || !registers_.interlaced;
}
// latches dictating start and length of drawing
bool vertical_drawing_latch_ = false, horizontal_drawing_latch_ = false;
int rows_this_field_, columns_this_line_;
int rows_this_field_ = 0, columns_this_line_ = 0;
// current drawing position counter
int pixel_line_cycle_, column_counter_;
int current_row_;
uint16_t current_character_row_;
uint16_t video_matrix_address_counter_, base_video_matrix_address_counter_;
int pixel_line_cycle_ = 0, column_counter_ = 0;
int current_row_ = 0;
uint16_t current_character_row_ = 0;
uint16_t video_matrix_address_counter_ = 0, base_video_matrix_address_counter_ = 0;
// data latched from the bus
uint8_t character_code_, character_colour_, character_value_;
uint8_t character_code_ = 0, character_colour_ = 0, character_value_ = 0;
bool is_odd_frame_ = false, is_odd_line_ = false;
// lookup table from 6560 colour index to appropriate PAL/NTSC value
uint16_t colours_[16];
uint16_t colours_[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
uint16_t *pixel_pointer;
uint16_t *pixel_pointer = nullptr;
void output_border(int number_of_cycles) {
uint16_t *colour_pointer = reinterpret_cast<uint16_t *>(crt_.begin_data(1));
if(colour_pointer) *colour_pointer = registers_.borderColour;
@@ -508,13 +511,13 @@ template <class BusHandler> class MOS6560 {
}
struct {
int cycles_per_line;
int line_counter_increment_offset;
int final_line_increment_position;
int lines_per_progressive_field;
bool supports_interlacing;
int cycles_per_line = 0;
int line_counter_increment_offset = 0;
int final_line_increment_position = 0;
int lines_per_progressive_field = 0;
bool supports_interlacing = 0;
} timing_;
OutputMode output_mode_;
OutputMode output_mode_ = OutputMode::NTSC;
};
}

View File

@@ -111,7 +111,7 @@ template <class T> class CRTC6845 {
}
void run_for(Cycles cycles) {
int cyles_remaining = cycles.as_int();
auto cyles_remaining = cycles.as_integral();
while(cyles_remaining--) {
// check for end of visible characters
if(character_counter_ == registers_[1]) {
@@ -167,8 +167,8 @@ template <class T> class CRTC6845 {
private:
inline void perform_bus_cycle_phase1() {
// Skew theory of operation: keep a history of the last three states, and apply whichever is selected.
character_is_visible_shifter_ = (character_is_visible_shifter_ << 1) | static_cast<unsigned int>(character_is_visible_);
bus_state_.display_enable = (static_cast<int>(character_is_visible_shifter_) & display_skew_mask_) && line_is_visible_;
character_is_visible_shifter_ = (character_is_visible_shifter_ << 1) | unsigned(character_is_visible_);
bus_state_.display_enable = (int(character_is_visible_shifter_) & display_skew_mask_) && line_is_visible_;
bus_handler_.perform_bus_cycle_phase1(bus_state_);
}
@@ -240,7 +240,7 @@ template <class T> class CRTC6845 {
inline void do_end_of_frame() {
line_counter_ = 0;
line_is_visible_ = true;
line_address_ = static_cast<uint16_t>((registers_[12] << 8) | registers_[13]);
line_address_ = uint16_t((registers_[12] << 8) | registers_[13]);
bus_state_.refresh_address = line_address_;
}

228
Components/6850/6850.cpp Normal file
View File

@@ -0,0 +1,228 @@
//
// 6850.cpp
// Clock Signal
//
// Created by Thomas Harte on 10/10/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#include "6850.hpp"
#include <cassert>
using namespace Motorola::ACIA;
const HalfCycles ACIA::SameAsTransmit;
ACIA::ACIA(HalfCycles transmit_clock_rate, HalfCycles receive_clock_rate) :
transmit_clock_rate_(transmit_clock_rate),
receive_clock_rate_((receive_clock_rate != SameAsTransmit) ? receive_clock_rate : transmit_clock_rate) {
transmit.set_writer_clock_rate(transmit_clock_rate);
request_to_send.set_writer_clock_rate(transmit_clock_rate);
}
uint8_t ACIA::read(int address) {
if(address&1) {
overran_ = false;
received_data_ |= NoValueMask;
update_interrupt_line();
return uint8_t(received_data_);
} else {
return get_status();
}
}
void ACIA::reset() {
transmit.reset_writing();
transmit.write(true);
request_to_send.reset_writing();
bits_received_ = bits_incoming_ = 0;
receive_interrupt_enabled_ = transmit_interrupt_enabled_ = false;
overran_ = false;
next_transmission_ = received_data_ = NoValueMask;
update_interrupt_line();
assert(!interrupt_line_);
}
void ACIA::write(int address, uint8_t value) {
if(address&1) {
next_transmission_ = value;
consider_transmission();
update_interrupt_line();
} else {
if((value&3) == 3) {
reset();
} else {
switch(value & 3) {
default:
case 0: divider_ = 1; break;
case 1: divider_ = 16; break;
case 2: divider_ = 64; break;
}
switch((value >> 2) & 7) {
default:
case 0: data_bits_ = 7; stop_bits_ = 2; parity_ = Parity::Even; break;
case 1: data_bits_ = 7; stop_bits_ = 2; parity_ = Parity::Odd; break;
case 2: data_bits_ = 7; stop_bits_ = 1; parity_ = Parity::Even; break;
case 3: data_bits_ = 7; stop_bits_ = 1; parity_ = Parity::Odd; break;
case 4: data_bits_ = 8; stop_bits_ = 2; parity_ = Parity::None; break;
case 5: data_bits_ = 8; stop_bits_ = 1; parity_ = Parity::None; break;
case 6: data_bits_ = 8; stop_bits_ = 1; parity_ = Parity::Even; break;
case 7: data_bits_ = 8; stop_bits_ = 1; parity_ = Parity::Odd; break;
}
switch((value >> 5) & 3) {
case 0: request_to_send.write(false); transmit_interrupt_enabled_ = false; break;
case 1: request_to_send.write(false); transmit_interrupt_enabled_ = true; break;
case 2: request_to_send.write(true); transmit_interrupt_enabled_ = false; break;
case 3:
request_to_send.write(false);
transmit_interrupt_enabled_ = false;
transmit.reset_writing();
transmit.write(false);
break;
}
receive.set_read_delegate(this, Storage::Time(divider_ * 2, int(receive_clock_rate_.as_integral())));
receive_interrupt_enabled_ = value & 0x80;
update_interrupt_line();
}
}
update_clocking_observer();
}
void ACIA::consider_transmission() {
if(next_transmission_ != NoValueMask && !transmit.write_data_time_remaining()) {
// Establish start bit and [7 or 8] data bits.
if(data_bits_ == 7) next_transmission_ &= 0x7f;
int transmission = next_transmission_ << 1;
// Add a parity bit, if any.
int mask = 0x2 << data_bits_;
if(parity_ != Parity::None) {
transmission |= parity(uint8_t(next_transmission_)) ? mask : 0;
mask <<= 1;
}
// Add stop bits.
for(int c = 0; c < stop_bits_; ++c) {
transmission |= mask;
mask <<= 1;
}
// Output all that.
const int total_bits = expected_bits();
transmit.write(divider_ * 2, total_bits, transmission);
// Mark the transmit register as empty again.
next_transmission_ = NoValueMask;
}
}
ClockingHint::Preference ACIA::preferred_clocking() const {
// Real-time clocking is required if a transmission is ongoing; this is a courtesy for whomever
// is on the receiving end.
if(transmit.transmission_data_time_remaining() > 0) return ClockingHint::Preference::RealTime;
// If a bit reception is ongoing that might lead to an interrupt, ask for real-time clocking
// because it's unclear when the interrupt might come.
if(bits_incoming_ && receive_interrupt_enabled_) return ClockingHint::Preference::RealTime;
// No clocking required then.
return ClockingHint::Preference::None;
}
bool ACIA::get_interrupt_line() const {
return interrupt_line_;
}
int ACIA::expected_bits() {
return 1 + data_bits_ + stop_bits_ + (parity_ != Parity::None);
}
uint8_t ACIA::parity(uint8_t value) {
value ^= value >> 4;
value ^= value >> 2;
value ^= value >> 1;
return value ^ (parity_ == Parity::Even);
}
bool ACIA::serial_line_did_produce_bit(Serial::Line *, int bit) {
// Shift this bit into the 11-bit input register; this is big enough to hold
// the largest transmission symbol.
++bits_received_;
bits_incoming_ = (bits_incoming_ >> 1) | (bit << 10);
// If that's the now-expected number of bits, update.
const int bit_target = expected_bits();
if(bits_received_ >= bit_target) {
bits_received_ = 0;
overran_ |= get_status() & 1;
received_data_ = uint8_t(bits_incoming_ >> (12 - bit_target));
update_interrupt_line();
update_clocking_observer();
return false;
}
// TODO: overrun, and parity.
// Keep receiving, and consider a potential clocking change.
if(bits_received_ == 1) update_clocking_observer();
return true;
}
void ACIA::set_interrupt_delegate(InterruptDelegate *delegate) {
interrupt_delegate_ = delegate;
}
void ACIA::update_interrupt_line() {
const bool old_line = interrupt_line_;
/*
"Bit 7 of the control register is the rie bit. When the rie bit is high, the rdrf, ndcd,
and ovr bits will assert the nirq output. When the rie bit is low, nirq generation is disabled."
rie = read interrupt enable
rdrf = receive data register full (status word bit 0)
ndcd = data carrier detect (status word bit 2)
over = receiver overrun (status word bit 5)
"Bit 1 of the status register is the tdre bit. When high, the tdre bit indicates that data has been
transferred from the transmitter data register to the output shift register. At this point, the a6850
is ready to accept a new transmit data byte. However, if the ncts signal is high, the tdre bit remains
low regardless of the status of the transmitter data register. Also, if transmit interrupt is enabled,
the nirq output is asserted."
tdre = transmitter data register empty
ncts = clear to send
*/
const auto status = get_status();
interrupt_line_ =
(receive_interrupt_enabled_ && (status & 0x25)) ||
(transmit_interrupt_enabled_ && (status & 0x02));
if(interrupt_delegate_ && old_line != interrupt_line_) {
interrupt_delegate_->acia6850_did_change_interrupt_status(this);
}
}
uint8_t ACIA::get_status() {
return
((received_data_ & NoValueMask) ? 0x00 : 0x01) |
((next_transmission_ == NoValueMask) ? 0x02 : 0x00) |
// (data_carrier_detect.read() ? 0x04 : 0x00) |
// (clear_to_send.read() ? 0x08 : 0x00) |
(overran_ ? 0x20 : 0x00) |
(interrupt_line_ ? 0x80 : 0x00)
;
// b0: receive data full.
// b1: transmit data empty.
// b2: DCD.
// b3: CTS.
// b4: framing error (i.e. no first stop bit where expected).
// b5: receiver overran.
// b6: parity error.
// b7: IRQ state.
}

132
Components/6850/6850.hpp Normal file
View File

@@ -0,0 +1,132 @@
//
// 6850.hpp
// Clock Signal
//
// Created by Thomas Harte on 10/10/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef Motorola_ACIA_6850_hpp
#define Motorola_ACIA_6850_hpp
#include <cstdint>
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../ClockReceiver/ForceInline.hpp"
#include "../../ClockReceiver/ClockingHintSource.hpp"
#include "../Serial/Line.hpp"
namespace Motorola {
namespace ACIA {
class ACIA: public ClockingHint::Source, private Serial::Line::ReadDelegate {
public:
static constexpr const HalfCycles SameAsTransmit = HalfCycles(0);
/*!
Constructs a new instance of ACIA which will receive a transmission clock at a rate of
@c transmit_clock_rate, and a receive clock at a rate of @c receive_clock_rate.
*/
ACIA(HalfCycles transmit_clock_rate, HalfCycles receive_clock_rate = SameAsTransmit);
/*!
Reads from the ACIA.
Bit 0 of the address is used as the ACIA's register select line —
so even addresses select control/status registers, odd addresses
select transmit/receive data registers.
*/
uint8_t read(int address);
/*!
Writes to the ACIA.
Bit 0 of the address is used as the ACIA's register select line —
so even addresses select control/status registers, odd addresses
select transmit/receive data registers.
*/
void write(int address, uint8_t value);
/*!
Advances @c transmission_cycles in time, which should be
counted relative to the @c transmit_clock_rate.
*/
forceinline void run_for(HalfCycles transmission_cycles) {
if(transmit.transmission_data_time_remaining() > HalfCycles(0)) {
const auto write_data_time_remaining = transmit.write_data_time_remaining();
// There's at most one further byte available to enqueue, so a single 'if'
// rather than a 'while' is correct here. It's the responsibilit of the caller
// to ensure run_for lengths are appropriate for longer sequences.
if(transmission_cycles >= write_data_time_remaining) {
if(next_transmission_ != NoValueMask) {
transmit.advance_writer(write_data_time_remaining);
consider_transmission();
transmit.advance_writer(transmission_cycles - write_data_time_remaining);
} else {
transmit.advance_writer(transmission_cycles);
update_clocking_observer();
update_interrupt_line();
}
} else {
transmit.advance_writer(transmission_cycles);
}
}
}
bool get_interrupt_line() const;
void reset();
// Input lines.
Serial::Line receive;
Serial::Line clear_to_send;
Serial::Line data_carrier_detect;
// Output lines.
Serial::Line transmit;
Serial::Line request_to_send;
// ClockingHint::Source.
ClockingHint::Preference preferred_clocking() const final;
struct InterruptDelegate {
virtual void acia6850_did_change_interrupt_status(ACIA *acia) = 0;
};
void set_interrupt_delegate(InterruptDelegate *delegate);
private:
int divider_ = 1;
enum class Parity {
Even, Odd, None
} parity_ = Parity::None;
int data_bits_ = 7, stop_bits_ = 2;
static constexpr int NoValueMask = 0x100;
int next_transmission_ = NoValueMask;
int received_data_ = NoValueMask;
int bits_received_ = 0;
int bits_incoming_ = 0;
bool overran_ = false;
void consider_transmission();
int expected_bits();
uint8_t parity(uint8_t value);
bool receive_interrupt_enabled_ = false;
bool transmit_interrupt_enabled_ = false;
HalfCycles transmit_clock_rate_;
HalfCycles receive_clock_rate_;
bool serial_line_did_produce_bit(Serial::Line *line, int bit) final;
bool interrupt_line_ = false;
void update_interrupt_line();
InterruptDelegate *interrupt_delegate_ = nullptr;
uint8_t get_status();
};
}
}
#endif /* Motorola_ACIA_6850_hpp */

View File

@@ -0,0 +1,374 @@
//
// MFP68901.cpp
// Clock Signal
//
// Created by Thomas Harte on 06/10/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#include "MFP68901.hpp"
#include <algorithm>
#include <cstring>
#ifndef NDEBUG
#define NDEBUG
#endif
#define LOG_PREFIX "[MFP] "
#include "../../Outputs/Log.hpp"
using namespace Motorola::MFP68901;
ClockingHint::Preference MFP68901::preferred_clocking() const {
// Rule applied: if any timer is actively running and permitted to produce an
// interrupt, request real-time running.
return
(timers_[0].mode >= TimerMode::Delay && interrupt_enable_&Interrupt::TimerA) ||
(timers_[1].mode >= TimerMode::Delay && interrupt_enable_&Interrupt::TimerB) ||
(timers_[2].mode >= TimerMode::Delay && interrupt_enable_&Interrupt::TimerC) ||
(timers_[3].mode >= TimerMode::Delay && interrupt_enable_&Interrupt::TimerD)
? ClockingHint::Preference::RealTime : ClockingHint::Preference::JustInTime;
}
uint8_t MFP68901::read(int address) {
address &= 0x1f;
// Interrupt block: various bits of state can be read, all passively.
if(address >= 0x03 && address <= 0x0b) {
const int shift = (address&1) << 3;
switch(address) {
case 0x03: case 0x04: return uint8_t(interrupt_enable_ >> shift);
case 0x05: case 0x06: return uint8_t(interrupt_pending_ >> shift);
case 0x07: case 0x08: return uint8_t(interrupt_in_service_ >> shift);
case 0x09: case 0x0a: return uint8_t(interrupt_mask_ >> shift);
case 0x0b: return interrupt_vector_;
default: break;
}
}
switch(address) {
// GPIP block: input, and configured active edge and direction values.
case 0x00: return (gpip_input_ & ~gpip_direction_) | (gpip_output_ & gpip_direction_);
case 0x01: return gpip_active_edge_;
case 0x02: return gpip_direction_;
/* Interrupt block dealt with above. */
default: break;
// Timer block: read back A, B and C/D control, and read current timer values.
case 0x0c: case 0x0d: return timer_ab_control_[address - 0xc];
case 0x0e: return timer_cd_control_;
case 0x0f: case 0x10:
case 0x11: case 0x12: return get_timer_data(address - 0xf);
// USART block: TODO.
case 0x13: LOG("Read: sync character generator"); break;
case 0x14: LOG("Read: USART control"); break;
case 0x15: LOG("Read: receiver status"); break;
case 0x16: LOG("Read: transmitter status"); break;
case 0x17: LOG("Read: USART data"); break;
}
return 0x00;
}
void MFP68901::write(int address, uint8_t value) {
address &= 0x1f;
// Interrupt block: enabled and masked interrupts can be set; pending and in-service interrupts can be masked.
if(address >= 0x03 && address <= 0x0b) {
const int shift = (address&1) << 3;
const int preserve = 0xff00 >> shift;
const int word_value = value << shift;
switch(address) {
default: break;
case 0x03: case 0x04: // Adjust enabled interrupts; disabled ones also cease to be pending.
interrupt_enable_ = (interrupt_enable_ & preserve) | word_value;
interrupt_pending_ &= interrupt_enable_;
break;
case 0x05: case 0x06: // Resolve pending interrupts.
interrupt_pending_ &= (preserve | word_value);
break;
case 0x07: case 0x08: // Resolve in-service interrupts.
interrupt_in_service_ &= (preserve | word_value);
break;
case 0x09: case 0x0a: // Adjust interrupt mask.
interrupt_mask_ = (interrupt_mask_ & preserve) | word_value;
break;
case 0x0b: // Set the interrupt vector, possibly changing end-of-interrupt mode.
interrupt_vector_ = value;
// If automatic end-of-interrupt mode has now been enabled, clear
// the in-process mask and re-evaluate.
if(interrupt_vector_ & 0x08) return;
interrupt_in_service_ = 0;
break;
}
// Whatever just happened may have affected the state of the interrupt line.
update_interrupts();
update_clocking_observer();
return;
}
constexpr int timer_prescales[] = {
1, 4, 10, 16, 50, 64, 100, 200
};
switch(address) {
// GPIP block: output and configuration of active edge and direction values.
case 0x00:
gpip_output_ = value;
break;
case 0x01:
gpip_active_edge_ = value;
reevaluate_gpip_interrupts();
break;
case 0x02:
gpip_direction_ = value;
reevaluate_gpip_interrupts();
break;
/* Interrupt block dealt with above. */
default: break;
// Timer block.
case 0x0c:
case 0x0d: {
const auto timer = address - 0xc;
const bool reset = value & 0x10;
timer_ab_control_[timer] = value;
switch(value & 0xf) {
case 0x0: set_timer_mode(timer, TimerMode::Stopped, 1, reset); break;
case 0x1: set_timer_mode(timer, TimerMode::Delay, 4, reset); break;
case 0x2: set_timer_mode(timer, TimerMode::Delay, 10, reset); break;
case 0x3: set_timer_mode(timer, TimerMode::Delay, 16, reset); break;
case 0x4: set_timer_mode(timer, TimerMode::Delay, 50, reset); break;
case 0x5: set_timer_mode(timer, TimerMode::Delay, 64, reset); break;
case 0x6: set_timer_mode(timer, TimerMode::Delay, 100, reset); break;
case 0x7: set_timer_mode(timer, TimerMode::Delay, 200, reset); break;
case 0x8: set_timer_mode(timer, TimerMode::EventCount, 1, reset); break;
case 0x9: set_timer_mode(timer, TimerMode::PulseWidth, 4, reset); break;
case 0xa: set_timer_mode(timer, TimerMode::PulseWidth, 10, reset); break;
case 0xb: set_timer_mode(timer, TimerMode::PulseWidth, 16, reset); break;
case 0xc: set_timer_mode(timer, TimerMode::PulseWidth, 50, reset); break;
case 0xd: set_timer_mode(timer, TimerMode::PulseWidth, 64, reset); break;
case 0xe: set_timer_mode(timer, TimerMode::PulseWidth, 100, reset); break;
case 0xf: set_timer_mode(timer, TimerMode::PulseWidth, 200, reset); break;
}
} break;
case 0x0e:
timer_cd_control_ = value;
set_timer_mode(3, (value & 7) ? TimerMode::Delay : TimerMode::Stopped, timer_prescales[value & 7], false);
set_timer_mode(2, ((value >> 4) & 7) ? TimerMode::Delay : TimerMode::Stopped, timer_prescales[(value >> 4) & 7], false);
break;
case 0x0f: case 0x10: case 0x11: case 0x12:
set_timer_data(address - 0xf, value);
break;
// USART block: TODO.
case 0x13: LOG("Write: sync character generator"); break;
case 0x14: LOG("Write: USART control"); break;
case 0x15: LOG("Write: receiver status"); break;
case 0x16: LOG("Write: transmitter status"); break;
case 0x17: LOG("Write: USART data"); break;
}
update_clocking_observer();
}
void MFP68901::run_for(HalfCycles time) {
cycles_left_ += time;
const int cycles = int(cycles_left_.flush<Cycles>().as_integral());
if(!cycles) return;
for(int c = 0; c < 4; ++c) {
if(timers_[c].mode >= TimerMode::Delay) {
// This code applies the timer prescaling only. prescale_count is used to count
// upwards rather than downwards for simplicity, but on the real hardware it's
// pretty safe to assume it actually counted downwards. So the clamp to 0 is
// because gymnastics may need to occur when the prescale value is altered, e.g.
// if a prescale of 256 is set and the prescale_count is currently 2 then the
// counter should roll over in 254 cycles. If the user at that point changes the
// prescale_count to 1 then the counter will need to be altered to -253 and
// allowed to keep counting up until it crosses both 0 and 1.
const int dividend = timers_[c].prescale_count + cycles;
const int decrements = std::max(dividend / timers_[c].prescale, 0);
if(decrements) {
decrement_timer(c, decrements);
timers_[c].prescale_count = dividend % timers_[c].prescale;
} else {
timers_[c].prescale_count += cycles;
}
}
}
}
HalfCycles MFP68901::get_next_sequence_point() {
return HalfCycles(-1);
}
// MARK: - Timers
void MFP68901::set_timer_mode(int timer, TimerMode mode, int prescale, bool reset_timer) {
LOG("Timer " << timer << " mode set: " << int(mode) << "; prescale: " << prescale);
timers_[timer].mode = mode;
if(reset_timer) {
timers_[timer].prescale_count = 0;
timers_[timer].value = timers_[timer].reload_value;
} else {
// This hoop is because the prescale_count here goes upward but I'm assuming it goes downward in
// real hardware. Therefore this deals with the "switched to a lower prescaling" case whereby the
// old cycle should be allowed naturally to expire.
timers_[timer].prescale_count = prescale - (timers_[timer].prescale - timers_[timer].prescale_count);
}
timers_[timer].prescale = prescale;
}
void MFP68901::set_timer_data(int timer, uint8_t value) {
if(timers_[timer].mode == TimerMode::Stopped) {
timers_[timer].value = value;
}
timers_[timer].reload_value = value;
}
uint8_t MFP68901::get_timer_data(int timer) {
return timers_[timer].value;
}
void MFP68901::set_timer_event_input(int channel, bool value) {
if(timers_[channel].event_input == value) return;
timers_[channel].event_input = value;
if(timers_[channel].mode == TimerMode::EventCount && (value == !!(gpip_active_edge_ & (0x10 >> channel)))) {
// "The active state of the signal on TAI or TBI is dependent upon the associated
// Interrupt Channels edge bit (GPIP 4 for TAI and GPIP 3 for TBI [...] ).
// If the edge bit associated with the TAI or TBI input is a one, it will be active high.
decrement_timer(channel, 1);
}
// TODO:
//
// Altering the edge bit while the timer is in the event count mode can produce a count pulse.
// The interrupt channel associated with the input (I3 for I4 for TAI) is allowed to function normally.
// To count transitions reliably, the input must remain in each state (1/O) for a length of time equal
// to four periods of the timer clock.
//
// (the final bit probably explains 13 cycles of the DE to interrupt latency; not sure about the other ~15)
}
void MFP68901::decrement_timer(int timer, int amount) {
while(amount--) {
--timers_[timer].value;
if(timers_[timer].value < 1) {
switch(timer) {
case 0: begin_interrupts(Interrupt::TimerA); break;
case 1: begin_interrupts(Interrupt::TimerB); break;
case 2: begin_interrupts(Interrupt::TimerC); break;
case 3: begin_interrupts(Interrupt::TimerD); break;
}
// Re: reloading when in event counting mode; I found the data sheet thoroughly unclear on
// this, but it appears empirically to be correct. See e.g. Pompey Pirates menu 27.
if(timers_[timer].mode == TimerMode::Delay || timers_[timer].mode == TimerMode::EventCount) {
timers_[timer].value += timers_[timer].reload_value; // TODO: properly.
}
}
}
}
// MARK: - GPIP
void MFP68901::set_port_input(uint8_t input) {
gpip_input_ = input;
reevaluate_gpip_interrupts();
}
uint8_t MFP68901::get_port_output() {
return 0xff; // TODO.
}
void MFP68901::reevaluate_gpip_interrupts() {
const uint8_t gpip_state = (gpip_input_ & ~gpip_direction_) ^ gpip_active_edge_;
// An interrupt is detected on any falling edge.
const uint8_t new_interrupt_mask = (gpip_state ^ gpip_interrupt_state_) & gpip_interrupt_state_;
if(new_interrupt_mask) {
begin_interrupts(
(new_interrupt_mask & 0x0f) |
((new_interrupt_mask & 0x30) << 2) |
((new_interrupt_mask & 0xc0) << 8)
);
}
gpip_interrupt_state_ = gpip_state;
}
// MARK: - Interrupts
void MFP68901::begin_interrupts(int interrupt) {
interrupt_pending_ |= interrupt & interrupt_enable_;
update_interrupts();
}
void MFP68901::end_interrupts(int interrupt) {
interrupt_pending_ &= ~interrupt;
update_interrupts();
}
void MFP68901::update_interrupts() {
const auto old_interrupt_line = interrupt_line_;
const auto firing_interrupts = interrupt_pending_ & interrupt_mask_;
if(!firing_interrupts) {
interrupt_line_ = false;
} else {
if(interrupt_vector_ & 0x8) {
// Software interrupt mode: permit only if neither this interrupt
// nor a higher interrupt is currently in service.
const int highest_bit = msb16(firing_interrupts);
interrupt_line_ = !(interrupt_in_service_ & ~(highest_bit + highest_bit - 1));
} else {
// Auto-interrupt mode; just signal.
interrupt_line_ = true;
}
}
// Update the delegate if necessary.
if(interrupt_delegate_ && interrupt_line_ != old_interrupt_line) {
interrupt_delegate_->mfp68901_did_change_interrupt_status(this);
}
}
bool MFP68901::get_interrupt_line() {
return interrupt_line_;
}
int MFP68901::acknowledge_interrupt() {
if(!(interrupt_pending_ & interrupt_mask_)) {
return NoAcknowledgement;
}
const int mask = msb16(interrupt_pending_ & interrupt_mask_);
// Clear the pending bit regardless.
interrupt_pending_ &= ~mask;
// If this is software interrupt mode, set the in-service bit.
if(interrupt_vector_ & 0x8) {
interrupt_in_service_ |= mask;
}
update_interrupts();
int selected = 0;
while((1 << selected) != mask) ++selected;
// LOG("Interrupt acknowledged: " << selected);
return (interrupt_vector_ & 0xf0) | uint8_t(selected);
}
void MFP68901::set_interrupt_delegate(InterruptDelegate *delegate) {
interrupt_delegate_ = delegate;
}

View File

@@ -0,0 +1,187 @@
//
// MFP68901.hpp
// Clock Signal
//
// Created by Thomas Harte on 06/10/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#ifndef MFP68901_hpp
#define MFP68901_hpp
#include <cstdint>
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../ClockReceiver/ClockingHintSource.hpp"
namespace Motorola {
namespace MFP68901 {
class PortHandler {
public:
// TODO: announce changes in output.
};
/*!
Models the Motorola 68901 Multi-Function Peripheral ('MFP').
*/
class MFP68901: public ClockingHint::Source {
public:
/// @returns the result of a read from @c address.
uint8_t read(int address);
/// Performs a write of @c value to @c address.
void write(int address, uint8_t value);
/// Advances the MFP by the supplied number of HalfCycles.
void run_for(HalfCycles);
/// @returns the number of cycles until the next possible sequence point — the next time
/// at which the interrupt line _might_ change. This object conforms to ClockingHint::Source
/// so that mechanism can also be used to reduce the quantity of calls into this class.
///
/// @discussion TODO, alas.
HalfCycles get_next_sequence_point();
/// Sets the current level of either of the timer event inputs — TAI and TBI in datasheet terms.
void set_timer_event_input(int channel, bool value);
/// Sets a port handler, a receiver that will be notified upon any change in GPIP output.
///
/// @discussion TODO.
void set_port_handler(PortHandler *);
/// Sets the current input GPIP values.
void set_port_input(uint8_t);
/// @returns the current GPIP output values.
///
/// @discussion TODO.
uint8_t get_port_output();
/// @returns @c true if the interrupt output is currently active; @c false otherwise.s
bool get_interrupt_line();
static constexpr int NoAcknowledgement = 0x100;
/// Communicates an interrupt acknowledge cycle.
///
/// @returns the vector placed on the bus if any; @c NoAcknowledgement if nothing is loaded.
int acknowledge_interrupt();
struct InterruptDelegate {
/// Informs the delegate of a change in the interrupt line of the nominated MFP.
virtual void mfp68901_did_change_interrupt_status(MFP68901 *) = 0;
};
/// Sets a delegate that will be notified upon any change in the interrupt line.
void set_interrupt_delegate(InterruptDelegate *delegate);
// ClockingHint::Source.
ClockingHint::Preference preferred_clocking() const final;
private:
// MARK: - Timers
enum class TimerMode {
Stopped, EventCount, Delay, PulseWidth
};
void set_timer_mode(int timer, TimerMode, int prescale, bool reset_timer);
void set_timer_data(int timer, uint8_t);
uint8_t get_timer_data(int timer);
void decrement_timer(int timer, int amount);
struct Timer {
TimerMode mode = TimerMode::Stopped;
uint8_t value = 0;
uint8_t reload_value = 0;
int prescale = 1;
int prescale_count = 1;
bool event_input = false;
} timers_[4];
uint8_t timer_ab_control_[2] = { 0, 0 };
uint8_t timer_cd_control_ = 0;
HalfCycles cycles_left_;
// MARK: - GPIP
uint8_t gpip_input_ = 0;
uint8_t gpip_output_ = 0;
uint8_t gpip_active_edge_ = 0;
uint8_t gpip_direction_ = 0;
uint8_t gpip_interrupt_state_ = 0;
void reevaluate_gpip_interrupts();
// MARK: - Interrupts
InterruptDelegate *interrupt_delegate_ = nullptr;
// Ad hoc documentation:
//
// An interrupt becomes pending if it is enabled at the time it occurs.
//
// If a pending interrupt is enabled in the interrupt mask, a processor
// interrupt is generated. Otherwise no processor interrupt is generated.
//
// (Disabling a bit in the enabled mask also instantaneously clears anything
// in the pending mask.)
//
// The user can write to the pending interrupt register; a write
// masks whatever is there — so you can disable bits but you cannot set them.
//
// If the vector register's 'S' bit is set then software end-of-interrupt mode applies:
// Acknowledgement of an interrupt clears that interrupt's pending bit, but also sets
// its in-service bit. That bit will remain set until the user writes a zero to its position.
// If any bits are set in the in-service register, then they will prevent lower-priority
// interrupts from being signalled to the CPU. Further interrupts of the same or a higher
// priority may occur.
//
// If the vector register's 'S' bit is clear then automatic end-of-interrupt mode applies:
// Acknowledgement of an interrupt will automatically clear the corresponding
// pending bit.
//
int interrupt_enable_ = 0;
int interrupt_pending_ = 0;
int interrupt_mask_ = 0;
int interrupt_in_service_ = 0;
bool interrupt_line_ = false;
uint8_t interrupt_vector_ = 0;
enum Interrupt {
GPIP0 = (1 << 0),
GPIP1 = (1 << 1),
GPIP2 = (1 << 2),
GPIP3 = (1 << 3),
TimerD = (1 << 4),
TimerC = (1 << 5),
GPIP4 = (1 << 6),
GPIP5 = (1 << 7),
TimerB = (1 << 8),
TransmitError = (1 << 9),
TransmitBufferEmpty = (1 << 10),
ReceiveError = (1 << 11),
ReceiveBufferFull = (1 << 12),
TimerA = (1 << 13),
GPIP6 = (1 << 14),
GPIP7 = (1 << 15),
};
void begin_interrupts(int interrupt);
void end_interrupts(int interrupt);
void update_interrupts();
/// @returns the most significant bit set in v, assuming it is one of the least significant 16.
inline static int msb16(int v) {
// Saturate all bits below the MSB.
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
// Throw away lesser bits.
return (v+1) >> 1;
}
};
}
}
#endif /* MFP68901_hpp */

View File

@@ -9,13 +9,15 @@
#ifndef i8255_hpp
#define i8255_hpp
#include <cstdint>
namespace Intel {
namespace i8255 {
class PortHandler {
public:
void set_value(int port, uint8_t value) {}
uint8_t get_value(int port) { return 0xff; }
void set_value([[maybe_unused]] int port, [[maybe_unused]] uint8_t value) {}
uint8_t get_value([[maybe_unused]] int port) { return 0xff; }
};
// TODO: Modes 1 and 2.
@@ -27,7 +29,7 @@ template <class T> class i8255 {
Stores the value @c value to the register at @c address. If this causes a change in 8255 output
then the PortHandler will be informed.
*/
void set_register(int address, uint8_t value) {
void write(int address, uint8_t value) {
switch(address & 3) {
case 0:
if(!(control_ & 0x10)) {
@@ -60,7 +62,7 @@ template <class T> class i8255 {
Obtains the current value for the register at @c address. If this provides a reading
of input then the PortHandler will be queried.
*/
uint8_t get_register(int address) {
uint8_t read(int address) {
switch(address & 3) {
case 0: return (control_ & 0x10) ? port_handler_.get_value(0) : outputs_[0];
case 1: return (control_ & 0x02) ? port_handler_.get_value(1) : outputs_[1];

View File

@@ -79,10 +79,14 @@ namespace {
i8272::i8272(BusHandler &bus_handler, Cycles clock_rate) :
Storage::Disk::MFMController(clock_rate),
bus_handler_(bus_handler) {
posit_event(static_cast<int>(Event8272::CommandByte));
posit_event(int(Event8272::CommandByte));
// TODO: implement DMA, etc. I have a vague intention to implement the IBM PC
// one day, that should help to force that stuff.
(void)bus_handler_;
}
ClockingHint::Preference i8272::preferred_clocking() {
ClockingHint::Preference i8272::preferred_clocking() const {
const auto mfm_controller_preferred_clocking = Storage::Disk::MFMController::preferred_clocking();
if(mfm_controller_preferred_clocking != ClockingHint::Preference::None) return mfm_controller_preferred_clocking;
return is_sleeping_ ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime;
@@ -95,11 +99,11 @@ void i8272::run_for(Cycles cycles) {
// check for an expired timer
if(delay_time_ > 0) {
if(cycles.as_int() >= delay_time_) {
if(cycles.as_integral() >= delay_time_) {
delay_time_ = 0;
posit_event(static_cast<int>(Event8272::Timer));
posit_event(int(Event8272::Timer));
} else {
delay_time_ -= cycles.as_int();
delay_time_ -= cycles.as_integral();
}
}
@@ -108,13 +112,13 @@ void i8272::run_for(Cycles cycles) {
int drives_left = drives_seeking_;
for(int c = 0; c < 4; c++) {
if(drives_[c].phase == Drive::Seeking) {
drives_[c].step_rate_counter += cycles.as_int();
int steps = drives_[c].step_rate_counter / (8000 * step_rate_time_);
drives_[c].step_rate_counter += cycles.as_integral();
auto steps = drives_[c].step_rate_counter / (8000 * step_rate_time_);
drives_[c].step_rate_counter %= (8000 * step_rate_time_);
while(steps--) {
// Perform a step.
int direction = (drives_[c].target_head_position < drives_[c].head_position) ? -1 : 1;
LOG("Target " << PADDEC(0) << drives_[c].target_head_position << " versus believed " << static_cast<int>(drives_[c].head_position));
LOG("Target " << PADDEC(0) << drives_[c].target_head_position << " versus believed " << int(drives_[c].head_position));
select_drive(c);
get_drive().step(Storage::Disk::HeadPosition(direction));
if(drives_[c].target_head_position >= 0) drives_[c].head_position += direction;
@@ -141,12 +145,12 @@ void i8272::run_for(Cycles cycles) {
int head = c&1;
if(drives_[drive].head_unload_delay[head] > 0) {
if(cycles.as_int() >= drives_[drive].head_unload_delay[head]) {
if(cycles.as_integral() >= drives_[drive].head_unload_delay[head]) {
drives_[drive].head_unload_delay[head] = 0;
drives_[drive].head_is_loaded[head] = false;
head_timers_running_--;
} else {
drives_[drive].head_unload_delay[head] -= cycles.as_int();
drives_[drive].head_unload_delay[head] -= cycles.as_integral();
}
timers_left--;
if(!timers_left) break;
@@ -156,14 +160,14 @@ void i8272::run_for(Cycles cycles) {
// check for busy plus ready disabled
if(is_executing_ && !get_drive().get_is_ready()) {
posit_event(static_cast<int>(Event8272::NoLongerReady));
posit_event(int(Event8272::NoLongerReady));
}
is_sleeping_ = !delay_time_ && !drives_seeking_ && !head_timers_running_;
if(is_sleeping_) update_clocking_observer();
}
void i8272::set_register(int address, uint8_t value) {
void i8272::write(int address, uint8_t value) {
// don't consider attempted sets to the status register
if(!address) return;
@@ -177,16 +181,16 @@ void i8272::set_register(int address, uint8_t value) {
} else {
// accumulate latest byte in the command byte sequence
command_.push_back(value);
posit_event(static_cast<int>(Event8272::CommandByte));
posit_event(int(Event8272::CommandByte));
}
}
uint8_t i8272::get_register(int address) {
uint8_t i8272::read(int address) {
if(address) {
if(result_stack_.empty()) return 0xff;
uint8_t result = result_stack_.back();
result_stack_.pop_back();
if(result_stack_.empty()) posit_event(static_cast<int>(Event8272::ResultEmpty));
if(result_stack_.empty()) posit_event(int(Event8272::ResultEmpty));
return result;
} else {
@@ -198,16 +202,16 @@ uint8_t i8272::get_register(int address) {
#define END_SECTION() }
#define MS_TO_CYCLES(x) x * 8000
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(mask); return; case __LINE__:
#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; interesting_event_mask_ = static_cast<int>(Event8272::Timer); delay_time_ = MS_TO_CYCLES(ms); is_sleeping_ = false; update_clocking_observer(); case __LINE__: if(delay_time_) return;
#define WAIT_FOR_EVENT(mask) resume_point_ = __LINE__; interesting_event_mask_ = int(mask); return; case __LINE__:
#define WAIT_FOR_TIME(ms) resume_point_ = __LINE__; interesting_event_mask_ = int(Event8272::Timer); delay_time_ = MS_TO_CYCLES(ms); is_sleeping_ = false; update_clocking_observer(); case __LINE__: if(delay_time_) return;
#define PASTE(x, y) x##y
#define CONCAT(x, y) PASTE(x, y)
#define FIND_HEADER() \
set_data_mode(DataMode::Scanning); \
CONCAT(find_header, __LINE__): WAIT_FOR_EVENT(static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole)); \
if(event_type == static_cast<int>(Event::IndexHole)) { index_hole_limit_--; } \
CONCAT(find_header, __LINE__): WAIT_FOR_EVENT(int(Event::Token) | int(Event::IndexHole)); \
if(event_type == int(Event::IndexHole)) { index_hole_limit_--; } \
else if(get_latest_token().type == Token::ID) goto CONCAT(header_found, __LINE__); \
\
if(index_hole_limit_) goto CONCAT(find_header, __LINE__); \
@@ -215,8 +219,8 @@ uint8_t i8272::get_register(int address) {
#define FIND_DATA() \
set_data_mode(DataMode::Scanning); \
CONCAT(find_data, __LINE__): WAIT_FOR_EVENT(static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole)); \
if(event_type == static_cast<int>(Event::Token)) { \
CONCAT(find_data, __LINE__): WAIT_FOR_EVENT(int(Event::Token) | int(Event::IndexHole)); \
if(event_type == int(Event::Token)) { \
if(get_latest_token().type == Token::Byte || get_latest_token().type == Token::Sync) goto CONCAT(find_data, __LINE__); \
}
@@ -264,8 +268,8 @@ uint8_t i8272::get_register(int address) {
}
void i8272::posit_event(int event_type) {
if(event_type == static_cast<int>(Event::IndexHole)) index_hole_count_++;
if(event_type == static_cast<int>(Event8272::NoLongerReady)) {
if(event_type == int(Event::IndexHole)) index_hole_count_++;
if(event_type == int(Event8272::NoLongerReady)) {
SetNotReady();
goto abort;
}
@@ -292,7 +296,7 @@ void i8272::posit_event(int event_type) {
WAIT_FOR_EVENT(Event8272::CommandByte)
SetBusy();
static const std::size_t required_lengths[32] = {
static constexpr std::size_t required_lengths[32] = {
0, 0, 9, 3, 2, 9, 9, 2,
1, 9, 2, 0, 9, 6, 0, 3,
0, 9, 0, 0, 0, 0, 0, 0,
@@ -425,12 +429,12 @@ void i8272::posit_event(int event_type) {
// Performs the read data or read deleted data command.
read_data:
LOG(PADHEX(2) << "Read [deleted] data ["
<< static_cast<int>(command_[2]) << " "
<< static_cast<int>(command_[3]) << " "
<< static_cast<int>(command_[4]) << " "
<< static_cast<int>(command_[5]) << " ... "
<< static_cast<int>(command_[6]) << " "
<< static_cast<int>(command_[8]) << "]");
<< int(command_[2]) << " "
<< int(command_[3]) << " "
<< int(command_[4]) << " "
<< int(command_[5]) << " ... "
<< int(command_[6]) << " "
<< int(command_[8]) << "]");
read_next_data:
goto read_write_find_header;
@@ -439,7 +443,7 @@ void i8272::posit_event(int event_type) {
read_data_found_header:
FIND_DATA();
ClearControlMark();
if(event_type == static_cast<int>(Event::Token)) {
if(event_type == int(Event::Token)) {
if(get_latest_token().type != Token::Data && get_latest_token().type != Token::DeletedData) {
// Something other than a data mark came next, impliedly an ID or index mark.
SetMissingAddressMark();
@@ -470,24 +474,24 @@ void i8272::posit_event(int event_type) {
//
// TODO: consider DTL.
read_data_get_byte:
WAIT_FOR_EVENT(static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole));
if(event_type == static_cast<int>(Event::Token)) {
WAIT_FOR_EVENT(int(Event::Token) | int(Event::IndexHole));
if(event_type == int(Event::Token)) {
result_stack_.push_back(get_latest_token().byte_value);
distance_into_section_++;
SetDataRequest();
SetDataDirectionToProcessor();
WAIT_FOR_EVENT(static_cast<int>(Event8272::ResultEmpty) | static_cast<int>(Event::Token) | static_cast<int>(Event::IndexHole));
WAIT_FOR_EVENT(int(Event8272::ResultEmpty) | int(Event::Token) | int(Event::IndexHole));
}
switch(event_type) {
case static_cast<int>(Event8272::ResultEmpty): // The caller read the byte in time; proceed as normal.
case int(Event8272::ResultEmpty): // The caller read the byte in time; proceed as normal.
ResetDataRequest();
if(distance_into_section_ < (128 << size_)) goto read_data_get_byte;
break;
case static_cast<int>(Event::Token): // The caller hasn't read the old byte yet and a new one has arrived
case int(Event::Token): // The caller hasn't read the old byte yet and a new one has arrived
SetOverrun();
goto abort;
break;
case static_cast<int>(Event::IndexHole):
case int(Event::IndexHole):
SetEndOfCylinder();
goto abort;
break;
@@ -515,12 +519,12 @@ void i8272::posit_event(int event_type) {
write_data:
LOG(PADHEX(2) << "Write [deleted] data ["
<< static_cast<int>(command_[2]) << " "
<< static_cast<int>(command_[3]) << " "
<< static_cast<int>(command_[4]) << " "
<< static_cast<int>(command_[5]) << " ... "
<< static_cast<int>(command_[6]) << " "
<< static_cast<int>(command_[8]) << "]");
<< int(command_[2]) << " "
<< int(command_[3]) << " "
<< int(command_[4]) << " "
<< int(command_[5]) << " ... "
<< int(command_[6]) << " "
<< int(command_[8]) << "]");
if(get_drive().get_is_read_only()) {
SetNotWriteable();
@@ -571,7 +575,7 @@ void i8272::posit_event(int event_type) {
// Performs the read ID command.
read_id:
// Establishes the drive and head being addressed, and whether in double density mode.
LOG(PADHEX(2) << "Read ID [" << static_cast<int>(command_[0]) << " " << static_cast<int>(command_[1]) << "]");
LOG(PADHEX(2) << "Read ID [" << int(command_[0]) << " " << int(command_[1]) << "]");
// Sets a maximum index hole limit of 2 then waits either until it finds a header mark or sees too many index holes.
// If a header mark is found, reads in the following bytes that produce a header. Otherwise branches to data not found.
@@ -594,10 +598,10 @@ void i8272::posit_event(int event_type) {
// Performs read track.
read_track:
LOG(PADHEX(2) << "Read track ["
<< static_cast<int>(command_[2]) << " "
<< static_cast<int>(command_[3]) << " "
<< static_cast<int>(command_[4]) << " "
<< static_cast<int>(command_[5]) << "]");
<< int(command_[2]) << " "
<< int(command_[3]) << " "
<< int(command_[4]) << " "
<< int(command_[5]) << "]");
// Wait for the index hole.
WAIT_FOR_EVENT(Event::IndexHole);
@@ -627,7 +631,7 @@ void i8272::posit_event(int event_type) {
distance_into_section_++;
SetDataRequest();
// TODO: other possible exit conditions; find a way to merge with the read_data version of this.
WAIT_FOR_EVENT(static_cast<int>(Event8272::ResultEmpty));
WAIT_FOR_EVENT(int(Event8272::ResultEmpty));
ResetDataRequest();
if(distance_into_section_ < (128 << header_[2])) goto read_track_get_byte;
@@ -664,13 +668,13 @@ void i8272::posit_event(int event_type) {
expects_input_ = true;
distance_into_section_ = 0;
format_track_write_header:
WAIT_FOR_EVENT(static_cast<int>(Event::DataWritten) | static_cast<int>(Event::IndexHole));
WAIT_FOR_EVENT(int(Event::DataWritten) | int(Event::IndexHole));
switch(event_type) {
case static_cast<int>(Event::IndexHole):
case int(Event::IndexHole):
SetOverrun();
goto abort;
break;
case static_cast<int>(Event::DataWritten):
case int(Event::DataWritten):
header_[distance_into_section_] = input_;
write_byte(input_);
has_input_ = false;
@@ -683,10 +687,10 @@ void i8272::posit_event(int event_type) {
}
LOG(PADHEX(2) << "W:"
<< static_cast<int>(header_[0]) << " "
<< static_cast<int>(header_[1]) << " "
<< static_cast<int>(header_[2]) << " "
<< static_cast<int>(header_[3]) << ", "
<< int(header_[0]) << " "
<< int(header_[1]) << " "
<< int(header_[2]) << " "
<< int(header_[3]) << ", "
<< get_crc_generator().get_value());
write_crc();
@@ -706,8 +710,8 @@ void i8272::posit_event(int event_type) {
// Otherwise, pad out to the index hole.
format_track_pad:
write_byte(get_is_double_density() ? 0x4e : 0xff);
WAIT_FOR_EVENT(static_cast<int>(Event::DataWritten) | static_cast<int>(Event::IndexHole));
if(event_type != static_cast<int>(Event::IndexHole)) goto format_track_pad;
WAIT_FOR_EVENT(int(Event::DataWritten) | int(Event::IndexHole));
if(event_type != int(Event::IndexHole)) goto format_track_pad;
end_writing();
@@ -758,7 +762,7 @@ void i8272::posit_event(int event_type) {
// up in run_for understands to mean 'keep going until track 0 is active').
if(command_.size() > 2) {
drives_[drive].target_head_position = command_[2];
LOG(PADHEX(2) << "Seek to " << static_cast<int>(command_[2]));
LOG(PADHEX(2) << "Seek to " << int(command_[2]));
} else {
drives_[drive].target_head_position = -1;
drives_[drive].head_position = 0;
@@ -789,7 +793,7 @@ void i8272::posit_event(int event_type) {
// If a drive was found, return its results. Otherwise return a single 0x80.
if(found_drive != -1) {
drives_[found_drive].phase = Drive::NotSeeking;
status_[0] = static_cast<uint8_t>(found_drive);
status_[0] = uint8_t(found_drive);
main_status_ &= ~(1 << found_drive);
SetSeekEnd();
@@ -819,7 +823,7 @@ void i8272::posit_event(int event_type) {
int drive = command_[1] & 3;
select_drive(drive);
result_stack_= {
static_cast<uint8_t>(
uint8_t(
(command_[1] & 7) | // drive and head number
0x08 | // single sided
(get_drive().get_is_track_zero() ? 0x10 : 0x00) |
@@ -853,9 +857,9 @@ void i8272::posit_event(int event_type) {
// Posts whatever is in result_stack_ as a result phase. Be aware that it is a stack, so the
// last thing in it will be returned first.
post_result:
LOGNBR(PADHEX(2) << "Result to " << static_cast<int>(command_[0] & 0x1f) << ", main " << static_cast<int>(main_status_) << "; ");
LOGNBR(PADHEX(2) << "Result to " << int(command_[0] & 0x1f) << ", main " << int(main_status_) << "; ");
for(std::size_t c = 0; c < result_stack_.size(); c++) {
LOGNBR(" " << static_cast<int>(result_stack_[result_stack_.size() - 1 - c]));
LOGNBR(" " << int(result_stack_[result_stack_.size() - 1 - c]));
}
LOGNBR(std::endl);
@@ -865,7 +869,7 @@ void i8272::posit_event(int event_type) {
SetDataRequest();
SetDataDirectionToProcessor();
// The actual stuff of unwinding result_stack_ is handled by ::get_register; wait
// The actual stuff of unwinding result_stack_ is handled by ::read; wait
// until the processor has read all result bytes.
WAIT_FOR_EVENT(Event8272::ResultEmpty);
@@ -880,13 +884,13 @@ bool i8272::seek_is_satisfied(int drive) {
(drives_[drive].target_head_position == -1 && get_drive().get_is_track_zero());
}
void i8272::set_dma_acknowledge(bool dack) {
void i8272::set_dma_acknowledge(bool) {
}
void i8272::set_terminal_count(bool tc) {
void i8272::set_terminal_count(bool) {
}
void i8272::set_data_input(uint8_t value) {
void i8272::set_data_input(uint8_t) {
}
uint8_t i8272::get_data_output() {

View File

@@ -20,11 +20,12 @@ namespace i8272 {
class BusHandler {
public:
virtual void set_dma_data_request(bool drq) {}
virtual void set_interrupt(bool irq) {}
virtual ~BusHandler() {}
virtual void set_dma_data_request([[maybe_unused]] bool drq) {}
virtual void set_interrupt([[maybe_unused]] bool irq) {}
};
class i8272: public Storage::Disk::MFMController {
class i8272 : public Storage::Disk::MFMController {
public:
i8272(BusHandler &bus_handler, Cycles clock_rate);
@@ -33,19 +34,19 @@ class i8272: public Storage::Disk::MFMController {
void set_data_input(uint8_t value);
uint8_t get_data_output();
void set_register(int address, uint8_t value);
uint8_t get_register(int address);
void write(int address, uint8_t value);
uint8_t read(int address);
void set_dma_acknowledge(bool dack);
void set_terminal_count(bool tc);
ClockingHint::Preference preferred_clocking() override;
ClockingHint::Preference preferred_clocking() const final;
protected:
virtual void select_drive(int number) = 0;
private:
// The bus handler, for interrupt and DMA-driven usage.
// The bus handler, for interrupt and DMA-driven usage. [TODO]
BusHandler &bus_handler_;
std::unique_ptr<BusHandler> allocated_bus_handler_;
@@ -67,13 +68,13 @@ class i8272: public Storage::Disk::MFMController {
ResultEmpty = (1 << 5),
NoLongerReady = (1 << 6)
};
void posit_event(int type) override;
int interesting_event_mask_ = static_cast<int>(Event8272::CommandByte);
void posit_event(int type) final;
int interesting_event_mask_ = int(Event8272::CommandByte);
int resume_point_ = 0;
bool is_access_command_ = false;
// The counter used for ::Timer events.
int delay_time_ = 0;
Cycles::IntType delay_time_ = 0;
// The connected drives.
struct Drive {
@@ -89,12 +90,12 @@ class i8272: public Storage::Disk::MFMController {
bool seek_failed = false;
// Seeking: transient state.
int step_rate_counter = 0;
Cycles::IntType step_rate_counter = 0;
int steps_taken = 0;
int target_head_position = 0; // either an actual number, or -1 to indicate to step until track zero
// Head state.
int head_unload_delay[2] = {0, 0};
Cycles::IntType head_unload_delay[2] = {0, 0};
bool head_is_loaded[2] = {false, false};
} drives_[4];

View File

@@ -8,6 +8,7 @@
#include "z8530.hpp"
#define LOG_PREFIX "[SCC] "
#include "../../Outputs/Log.hpp"
using namespace Zilog::SCC;
@@ -16,7 +17,7 @@ void z8530::reset() {
// TODO.
}
bool z8530::get_interrupt_line() {
bool z8530::get_interrupt_line() const {
return
(master_interrupt_control_ & 0x8) &&
(
@@ -25,22 +26,30 @@ bool z8530::get_interrupt_line() {
);
}
/*
Per the standard defined in the header file, this implementation follows
an addressing convention of:
A0 = A/B (i.e. channel select)
A1 = C/D (i.e. control or data)
*/
std::uint8_t z8530::read(int address) {
if(address & 2) {
// Read data register for channel
return 0x00;
// Read data register for channel.
return channels_[address & 1].read(true, pointer_);
} else {
// Read control register for channel.
uint8_t result = 0;
switch(pointer_) {
default:
result = channels_[address & 1].read(address & 2, pointer_);
result = channels_[address & 1].read(false, pointer_);
break;
case 2: // Handled non-symmetrically between channels.
if(address & 1) {
LOG("[SCC] Unimplemented: register 2 status bits");
LOG("Unimplemented: register 2 status bits");
} else {
result = interrupt_vector_;
@@ -63,7 +72,11 @@ std::uint8_t z8530::read(int address) {
break;
}
// Cf. the two-step control register selection process in ::write. Since this
// definitely wasn't a *write* to register 0, it follows that the next selected
// control register will be 0.
pointer_ = 0;
update_delegate();
return result;
}
@@ -73,24 +86,31 @@ std::uint8_t z8530::read(int address) {
void z8530::write(int address, std::uint8_t value) {
if(address & 2) {
// Write data register for channel.
// Write data register for channel. This is completely independent
// of whatever is going on over in the control realm.
channels_[address & 1].write(true, pointer_, value);
} else {
// Write control register for channel.
// Write control register for channel; there's a two-step sequence
// here for the programmer. Initially the selected register
// (i.e. `pointer_`) is zero. That register includes a field to
// set the next selected register. After any other register has
// been written to, register 0 is selected again.
// Most registers are per channel, but a couple are shared; sever
// them here.
// Most registers are per channel, but a couple are shared;
// sever them here, send the rest to the appropriate chnanel.
switch(pointer_) {
default:
channels_[address & 1].write(address & 2, pointer_, value);
channels_[address & 1].write(false, pointer_, value);
break;
case 2: // Interrupt vector register; shared between both channels.
case 2: // Interrupt vector register; used only by Channel B.
// So there's only one of these.
interrupt_vector_ = value;
LOG("[SCC] Interrupt vector set to " << PADHEX(2) << int(value));
LOG("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));
case 9: // Master interrupt and reset register; there is also only one of these.
LOG("Master interrupt and reset register: " << PADHEX(2) << int(value));
master_interrupt_control_ = value;
break;
}
@@ -105,7 +125,8 @@ void z8530::write(int address, std::uint8_t value) {
pointer_ = value & 7;
// If the command part of the byte is a 'point high', also set the
// top bit of the pointer.
// top bit of the pointer. Channels themselves therefore need not
// (/should not) respond to the point high command.
if(((value >> 3)&7) == 1) {
pointer_ |= 8;
}
@@ -126,16 +147,79 @@ uint8_t z8530::Channel::read(bool data, uint8_t pointer) {
if(data) {
return data_;
} else {
LOG("Control read from register " << int(pointer));
// Otherwise, this is a control read...
switch(pointer) {
default:
LOG("[SCC] Unrecognised control read from register " << int(pointer));
return 0x00;
case 0:
case 0x0: // Read Register 0; see p.37 (PDF p.45).
// b0: Rx character available.
// b1: zero count.
// b2: Tx buffer empty.
// b3: DCD.
// b4: sync/hunt.
// b5: CTS.
// b6: Tx underrun/EOM.
// b7: break/abort.
return dcd_ ? 0x8 : 0x0;
case 0xf:
case 0x1: // Read Register 1; see p.37 (PDF p.45).
// b0: all sent.
// b1: residue code 0.
// b2: residue code 1.
// b3: residue code 2.
// b4: parity error.
// b5: Rx overrun error.
// b6: CRC/framing error.
// b7: end of frame (SDLC).
return 0x01;
case 0x2: // Read Register 2; see p.37 (PDF p.45).
// Interrupt vector — modified by status information in B channel.
return 0x00;
case 0x3: // Read Register 3; see p.37 (PDF p.45).
// B channel: all bits are 0.
// A channel:
// b0: Channel B ext/status IP.
// b1: Channel B Tx IP.
// b2: Channel B Rx IP.
// b3: Channel A ext/status IP.
// b4: Channel A Tx IP.
// b5: Channel A Rx IP.
// b6, b7: 0.
return 0x00;
case 0xa: // Read Register 10; see p.37 (PDF p.45).
// b0: 0
// b1: On loop.
// b2: 0
// b3: 0
// b4: Loop sending.
// b5: 0
// b6: Two clocks missing.
// b7: One clock missing.
return 0x00;
case 0xc: // Read Register 12; see p.37 (PDF p.45).
// Lower byte of time constant.
return 0x00;
case 0xd: // Read Register 13; see p.38 (PDF p.46).
// Upper byte of time constant.
return 0x00;
case 0xf: // Read Register 15; see p.38 (PDF p.46).
// External interrupt status:
// b0: 0
// b1: Zero count.
// b2: 0
// b3: DCD.
// b4: Sync/hunt.
// b5: CTS.
// b6: Tx underrun/EOM.
// b7: Break/abort.
return external_interrupt_status_;
}
}
@@ -148,9 +232,10 @@ void z8530::Channel::write(bool data, uint8_t pointer, uint8_t value) {
data_ = value;
return;
} else {
LOG("Control write: " << PADHEX(2) << int(value) << " to register " << int(pointer));
switch(pointer) {
default:
LOG("[SCC] Unrecognised control write: " << PADHEX(2) << int(value) << " to register " << int(pointer));
LOG("Unrecognised control write: " << PADHEX(2) << int(value) << " to register " << int(pointer));
break;
case 0x0: // Write register 0 — CRC reset and other functions.
@@ -158,13 +243,13 @@ void z8530::Channel::write(bool data, uint8_t pointer, uint8_t value) {
switch(value >> 6) {
default: /* Do nothing. */ break;
case 1:
LOG("[SCC] TODO: reset Rx CRC checker.");
LOG("TODO: reset Rx CRC checker.");
break;
case 2:
LOG("[SCC] TODO: reset Tx CRC checker.");
LOG("TODO: reset Tx CRC checker.");
break;
case 3:
LOG("[SCC] TODO: reset Tx underrun/EOM latch.");
LOG("TODO: reset Tx underrun/EOM latch.");
break;
}
@@ -172,32 +257,84 @@ void z8530::Channel::write(bool data, uint8_t pointer, uint8_t value) {
switch((value >> 3)&7) {
default: /* Do nothing. */ break;
case 2:
// LOG("[SCC] reset ext/status interrupts.");
// LOG("reset ext/status interrupts.");
external_status_interrupt_ = false;
external_interrupt_status_ = 0;
break;
case 3:
LOG("[SCC] TODO: send abort (SDLC).");
LOG("TODO: send abort (SDLC).");
break;
case 4:
LOG("[SCC] TODO: enable interrupt on next Rx character.");
LOG("TODO: enable interrupt on next Rx character.");
break;
case 5:
LOG("[SCC] TODO: reset Tx interrupt pending.");
LOG("TODO: reset Tx interrupt pending.");
break;
case 6:
LOG("[SCC] TODO: reset error.");
LOG("TODO: reset error.");
break;
case 7:
LOG("[SCC] TODO: reset highest IUS.");
LOG("TODO: reset highest IUS.");
break;
}
break;
case 0x1: // Write register 1 — Transmit/Receive Interrupt and Data Transfer Mode Definition.
interrupt_mask_ = value;
/*
b7 = 0 => Wait/Request output is inactive; 1 => output is informative.
b6 = Wait/request output is for...
0 => wait: floating when inactive, low if CPU is attempting to transfer data the SCC isn't yet ready for.
1 => request: high if inactive, low if SCC is ready to transfer data.
b5 = 1 => wait/request is relative to read buffer; 0 => relative to write buffer.
b4/b3:
00 = disable receive interrupt
01 = interrupt on first character or special condition
10 = interrupt on all characters and special conditions
11 = interrupt only upon special conditions.
b2 = 1 => parity error is a special condition; 0 => it isn't.
b1 = 1 => transmit buffer empty interrupt is enabled; 0 => it isn't.
b0 = 1 => external interrupt is enabled; 0 => it isn't.
*/
LOG("Interrupt mask: " << PADHEX(2) << int(value));
break;
case 0x2: // Write register 2 - interrupt vector.
break;
case 0x3: { // Write register 3 — Receive Parameters and Control.
// Get bit count.
int receive_bit_count = 8;
switch(value >> 6) {
default: receive_bit_count = 5; break;
case 1: receive_bit_count = 7; break;
case 2: receive_bit_count = 6; break;
case 3: receive_bit_count = 8; break;
}
LOG("Receive bit count: " << receive_bit_count);
(void)receive_bit_count;
/*
b7,b6:
00 = 5 receive bits per character
01 = 7 bits
10 = 6 bits
11 = 8 bits
b5 = 1 => DCD and CTS outputs are set automatically; 0 => they're inputs to read register 0.
(DCD is ignored in local loopback; CTS is ignored in both auto echo and local loopback).
b4: enter hunt mode (if set to 1, presumably?)
b3 = 1 => enable receiver CRC generation; 0 => don't.
b2: address search mode (SDLC)
b1: sync character load inhibit.
b0: Rx enable.
*/
} break;
case 0x4: // Write register 4 — Transmit/Receive Miscellaneous Parameters and Modes.
// Bits 0 and 1 select parity mode.
if(!(value&1)) {
@@ -236,6 +373,23 @@ void z8530::Channel::write(bool data, uint8_t pointer, uint8_t value) {
}
break;
case 0x5:
// b7: DTR
// b6/b5:
// 00 = Tx 5 bits (or less) per character
// 01 = Tx 7 bits per character
// 10 = Tx 6 bits per character
// 11 = Tx 8 bits per character
// b4: send break.
// b3: Tx enable.
// b2: SDLC (if 0) / CRC-16 (if 1)
// b1: RTS
// b0: Tx CRC enable.
break;
case 0x6:
break;
case 0xf: // Write register 15 — External/Status Interrupt Control.
external_interrupt_mask_ = value;
break;
@@ -253,12 +407,17 @@ void z8530::Channel::set_dcd(bool level) {
}
}
bool z8530::Channel::get_interrupt_line() {
bool z8530::Channel::get_interrupt_line() const {
return
(interrupt_mask_ & 1) && external_status_interrupt_;
// TODO: other potential causes of an interrupt.
}
/*!
Evaluates the new level of the interrupt line and notifies the delegate if
both: (i) there is one; and (ii) the interrupt line has changed since last
the delegate was notified.
*/
void z8530::update_delegate() {
const bool interrupt_line = get_interrupt_line();
if(interrupt_line != previous_interrupt_line_) {

View File

@@ -30,16 +30,33 @@ class z8530 {
A/B = A0
C/D = A1
*/
/// Performs a read from the SCC; see above for conventions as to 'address'.
std::uint8_t read(int address);
/// Performs a write to the SCC; see above for conventions as to 'address'.
void write(int address, std::uint8_t value);
/// Resets the SCC.
void reset();
bool get_interrupt_line();
/// @returns The current value of the status output: @c true for active; @c false for inactive.
bool get_interrupt_line() const;
struct Delegate {
virtual void did_change_interrupt_status(z8530 *, bool new_status) = 0;
/*!
Communicates that @c scc now has the interrupt line status @c new_status.
*/
virtual void did_change_interrupt_status(z8530 *scc, bool new_status) = 0;
};
/*!
Sets the delegate for this SCC. If this is a new delegate it is sent
an immediate did_change_interrupt_status message, to get it
up to speed.
*/
void set_delegate(Delegate *delegate) {
if(delegate_ == delegate) return;
delegate_ = delegate;
delegate_->did_change_interrupt_status(this, get_interrupt_line());
}
/*
@@ -53,7 +70,7 @@ class z8530 {
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();
bool get_interrupt_line() const;
private:
uint8_t data_ = 0xff;

View File

@@ -17,23 +17,23 @@ using namespace TI::TMS;
namespace {
const uint8_t StatusInterrupt = 0x80;
const uint8_t StatusSpriteOverflow = 0x40;
constexpr uint8_t StatusInterrupt = 0x80;
constexpr uint8_t StatusSpriteOverflow = 0x40;
const int StatusSpriteCollisionShift = 5;
const uint8_t StatusSpriteCollision = 0x20;
constexpr int StatusSpriteCollisionShift = 5;
constexpr uint8_t StatusSpriteCollision = 0x20;
// 342 internal cycles are 228/227.5ths of a line, so 341.25 cycles should be a whole
// line. Therefore multiply everything by four, but set line length to 1365 rather than 342*4 = 1368.
const unsigned int CRTCyclesPerLine = 1365;
const unsigned int CRTCyclesDivider = 4;
constexpr unsigned int CRTCyclesPerLine = 1365;
constexpr unsigned int CRTCyclesDivider = 4;
struct ReverseTable {
std::uint8_t map[256];
ReverseTable() {
for(int c = 0; c < 256; ++c) {
map[c] = static_cast<uint8_t>(
map[c] = uint8_t(
((c & 0x80) >> 7) |
((c & 0x40) >> 5) |
((c & 0x20) >> 3) |
@@ -117,10 +117,22 @@ void TMS9918::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
crt_.set_scan_target(scan_target);
}
Outputs::Display::ScanStatus TMS9918::get_scaled_scan_status() const {
// The input was scaled by 3/4 to convert half cycles to internal ticks,
// so undo that and also allow for: (i) the multiply by 4 that it takes
// to reach the CRT; and (ii) the fact that the half-cycles value was scaled,
// and this should really reply in whole cycles.
return crt_.get_scaled_scan_status() * (4.0f / (3.0f * 8.0f));
}
void TMS9918::set_display_type(Outputs::Display::DisplayType display_type) {
crt_.set_display_type(display_type);
}
Outputs::Display::DisplayType TMS9918::get_display_type() const {
return crt_.get_display_type();
}
void Base::LineBuffer::reset_sprite_collection() {
sprites_stopped = false;
active_sprite_slot = 0;
@@ -132,7 +144,7 @@ void Base::LineBuffer::reset_sprite_collection() {
void Base::posit_sprite(LineBuffer &buffer, int sprite_number, int sprite_position, int screen_row) {
if(!(status_ & StatusSpriteOverflow)) {
status_ = static_cast<uint8_t>((status_ & ~0x1f) | (sprite_number & 0x1f));
status_ = uint8_t((status_ & ~0x1f) | (sprite_number & 0x1f));
}
if(buffer.sprites_stopped)
return;
@@ -166,7 +178,7 @@ void TMS9918::run_for(const HalfCycles cycles) {
// Convert 456 clocked half cycles per line to 342 internal cycles per line;
// the internal clock is 1.5 times the nominal 3.579545 Mhz that I've advertised
// for this part. So multiply by three quarters.
int int_cycles = (cycles.as_int() * 3) + cycles_error_;
int int_cycles = int(cycles.as_integral() * 3) + cycles_error_;
cycles_error_ = int_cycles & 3;
int_cycles >>= 2;
if(!int_cycles) return;
@@ -178,7 +190,9 @@ void TMS9918::run_for(const HalfCycles cycles) {
int read_cycles_pool = int_cycles;
while(write_cycles_pool || read_cycles_pool) {
#ifndef NDEBUG
LineBufferPointer backup = read_pointer_;
#endif
if(write_cycles_pool) {
// Determine how much writing to do.
@@ -317,8 +331,10 @@ void TMS9918::run_for(const HalfCycles cycles) {
}
#ifndef NDEBUG
assert(backup.row == read_pointer_.row && backup.column == read_pointer_.column);
backup = write_pointer_;
#endif
if(read_cycles_pool) {
@@ -352,8 +368,7 @@ void TMS9918::run_for(const HalfCycles cycles) {
// Output video stream.
// --------------------
#define intersect(left, right, code) \
{ \
#define intersect(left, right, code) { \
const int start = std::max(read_pointer_.column, left); \
const int end = std::min(end_column, right); \
if(end > start) {\
@@ -493,7 +508,7 @@ void Base::output_border(int cycles, uint32_t cram_dot) {
}
}
void TMS9918::set_register(int address, uint8_t value) {
void TMS9918::write(int address, uint8_t value) {
// Writes to address 0 are writes to the video RAM. Store
// the value and return.
if(!(address & 1)) {
@@ -520,7 +535,7 @@ void TMS9918::set_register(int address, uint8_t value) {
// The RAM pointer is always set on a second write, regardless of
// whether the caller is intending to enqueue a VDP operation.
ram_pointer_ = (ram_pointer_ & 0x00ff) | static_cast<uint16_t>(value << 8);
ram_pointer_ = (ram_pointer_ & 0x00ff) | uint16_t(value << 8);
write_phase_ = false;
if(value & 0x80) {
@@ -625,7 +640,7 @@ void TMS9918::set_register(int address, uint8_t value) {
uint8_t TMS9918::get_current_line() {
// Determine the row to return.
static const int row_change_position = 63; // This is the proper Master System value; substitute if any other VDPs turn out to have this functionality.
constexpr int row_change_position = 63; // This is the proper Master System value; substitute if any other VDPs turn out to have this functionality.
int source_row =
(write_pointer_.column < row_change_position)
? (write_pointer_.row + mode_timing_.total_lines - 1)%mode_timing_.total_lines
@@ -654,7 +669,7 @@ uint8_t TMS9918::get_current_line() {
}
}
return static_cast<uint8_t>(source_row);
return uint8_t(source_row);
}
uint8_t TMS9918::get_latched_horizontal_counter() {
@@ -671,7 +686,7 @@ void TMS9918::latch_horizontal_counter() {
latched_column_ = write_pointer_.column;
}
uint8_t TMS9918::get_register(int address) {
uint8_t TMS9918::read(int address) {
write_phase_ = false;
// Reads from address 0 read video RAM, via the read-ahead buffer.
@@ -830,8 +845,8 @@ void Base::draw_tms_character(int start, int end) {
int sprite_collision = 0;
memset(&sprite_buffer[start], 0, size_t(end - start)*sizeof(sprite_buffer[0]));
static const uint32_t sprite_colour_selection_masks[2] = {0x00000000, 0xffffffff};
static const int colour_masks[16] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
constexpr uint32_t sprite_colour_selection_masks[2] = {0x00000000, 0xffffffff};
constexpr int colour_masks[16] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
// Draw all sprites into the sprite buffer.
const int shifter_target = sprites_16x16_ ? 32 : 16;

View File

@@ -44,9 +44,15 @@ class TMS9918: public Base {
/*! Sets the scan target this TMS will post content to. */
void set_scan_target(Outputs::Display::ScanTarget *);
/// Gets the current scan status.
Outputs::Display::ScanStatus get_scaled_scan_status() const;
/*! Sets the type of display the CRT will request. */
void set_display_type(Outputs::Display::DisplayType);
/*! Gets the type of display the CRT will request. */
Outputs::Display::DisplayType get_display_type() const;
/*!
Runs the VCP for the number of cycles indicate; it is an implicit assumption of the code
that the input clock rate is 3579545 Hz, the NTSC colour clock rate.
@@ -54,10 +60,10 @@ class TMS9918: public Base {
void run_for(const HalfCycles cycles);
/*! Sets a register value. */
void set_register(int address, uint8_t value);
void write(int address, uint8_t value);
/*! Gets a register value. */
uint8_t get_register(int address);
uint8_t read(int address);
/*! Gets the current scan line; provided by the Master System only. */
uint8_t get_current_line();
@@ -69,8 +75,8 @@ class TMS9918: public Base {
void latch_horizontal_counter();
/*!
Returns the amount of time until get_interrupt_line would next return true if
there are no interceding calls to set_register or get_register.
Returns the amount of time until @c get_interrupt_line would next return true if
there are no interceding calls to @c write or to @c read.
If get_interrupt_line is true now, returns zero. If get_interrupt_line would
never return true, returns -1.

View File

@@ -40,7 +40,7 @@ enum class TVStandard {
class Base {
public:
static const uint32_t palette_pack(uint8_t r, uint8_t g, uint8_t b) {
static uint32_t palette_pack(uint8_t r, uint8_t g, uint8_t b) {
uint32_t result = 0;
uint8_t *const result_ptr = reinterpret_cast<uint8_t *>(&result);
result_ptr[0] = r;
@@ -51,7 +51,7 @@ class Base {
}
protected:
const static int output_lag = 11; // i.e. pixel output will occur 11 cycles after corresponding data read.
static constexpr int output_lag = 11; // i.e. pixel output will occur 11 cycles after corresponding data read.
// The default TMS palette.
const uint32_t palette[16] = {
@@ -100,7 +100,7 @@ class Base {
// (though, in practice, it won't happen until the next
// external slot after this number of cycles after the
// device has requested the read or write).
return 7;
return 6;
}
// Holds the main status register.
@@ -352,9 +352,9 @@ class Base {
if(master_system_.cram_is_selected) {
// Adjust the palette.
master_system_.colour_ram[ram_pointer_ & 0x1f] = palette_pack(
static_cast<uint8_t>(((read_ahead_buffer_ >> 0) & 3) * 255 / 3),
static_cast<uint8_t>(((read_ahead_buffer_ >> 2) & 3) * 255 / 3),
static_cast<uint8_t>(((read_ahead_buffer_ >> 4) & 3) * 255 / 3)
uint8_t(((read_ahead_buffer_ >> 0) & 3) * 255 / 3),
uint8_t(((read_ahead_buffer_ >> 2) & 3) * 255 / 3),
uint8_t(((read_ahead_buffer_ >> 4) & 3) * 255 / 3)
);
// Schedule a CRAM dot; this is scheduled for wherever it should appear
@@ -421,7 +421,8 @@ class Base {
*/
#define slot(n) \
if(use_end && end == n) return;\
if(use_end && end == n) return; \
[[fallthrough]]; \
case n
#define external_slot(n) \
@@ -449,7 +450,7 @@ class Base {
/***********************************************
TMS9918 Fetching Code
TMS9918 Fetching Code
************************************************/
template<bool use_end> void fetch_tms_refresh(int start, int end) {
@@ -518,7 +519,7 @@ class Base {
fetch_columns_4(location+12, column+4);
LineBuffer &line_buffer = line_buffers_[write_pointer_.row];
const size_t row_base = pattern_name_address_ & (0x3c00 | static_cast<size_t>(write_pointer_.row >> 3) * 40);
const size_t row_base = pattern_name_address_ & (0x3c00 | size_t(write_pointer_.row >> 3) * 40);
const size_t row_offset = pattern_generator_table_address_ & (0x3800 | (write_pointer_.row & 7));
switch(start) {
@@ -693,7 +694,7 @@ class Base {
/***********************************************
Master System Fetching Code
Master System Fetching Code
************************************************/
template<bool use_end> void fetch_sms(int start, int end) {
@@ -731,7 +732,7 @@ class Base {
const size_t scrolled_column = (column - horizontal_offset) & 0x1f;\
const size_t address = row_info.pattern_address_base + (scrolled_column << 1); \
line_buffer.names[column].flags = ram_[address+1]; \
line_buffer.names[column].offset = static_cast<size_t>( \
line_buffer.names[column].offset = size_t( \
(((line_buffer.names[column].flags&1) << 8) | ram_[address]) << 5 \
) + row_info.sub_row[(line_buffer.names[column].flags&4) >> 2]; \
}
@@ -785,7 +786,7 @@ class Base {
};
const RowInfo scrolled_row_info = {
(pattern_name_address & size_t(((scrolled_row & ~7) << 3) | 0x3800)) - pattern_name_offset,
{static_cast<size_t>((scrolled_row & 7) << 2), 28 ^ static_cast<size_t>((scrolled_row & 7) << 2)}
{size_t((scrolled_row & 7) << 2), 28 ^ size_t((scrolled_row & 7) << 2)}
};
RowInfo row_info;
if(master_system_.vertical_scroll_lock) {

View File

@@ -6,51 +6,66 @@
// Copyright 2016 Thomas Harte. All rights reserved.
//
#include <cmath>
#include "AY38910.hpp"
#include <cmath>
//namespace GI {
//namespace AY38910 {
using namespace GI::AY38910;
AY38910::AY38910(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {
// set up envelope lookup tables
template <bool is_stereo>
AY38910<is_stereo>::AY38910(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {
// Don't use the low bit of the envelope position if this is an AY.
envelope_position_mask_ |= personality == Personality::AY38910;
// Set up envelope lookup tables.
for(int c = 0; c < 16; c++) {
for(int p = 0; p < 32; p++) {
for(int p = 0; p < 64; p++) {
switch(c) {
case 0: case 1: case 2: case 3: case 9:
envelope_shapes_[c][p] = (p < 16) ? (p^0xf) : 0;
envelope_overflow_masks_[c] = 0x1f;
/* Envelope: \____ */
envelope_shapes_[c][p] = (p < 32) ? (p^0x1f) : 0;
envelope_overflow_masks_[c] = 0x3f;
break;
case 4: case 5: case 6: case 7: case 15:
envelope_shapes_[c][p] = (p < 16) ? p : 0;
envelope_overflow_masks_[c] = 0x1f;
/* Envelope: /____ */
envelope_shapes_[c][p] = (p < 32) ? p : 0;
envelope_overflow_masks_[c] = 0x3f;
break;
case 8:
envelope_shapes_[c][p] = (p & 0xf) ^ 0xf;
/* Envelope: \\\\\\\\ */
envelope_shapes_[c][p] = (p & 0x1f) ^ 0x1f;
envelope_overflow_masks_[c] = 0x00;
break;
case 12:
envelope_shapes_[c][p] = (p & 0xf);
/* Envelope: //////// */
envelope_shapes_[c][p] = (p & 0x1f);
envelope_overflow_masks_[c] = 0x00;
break;
case 10:
envelope_shapes_[c][p] = (p & 0xf) ^ ((p < 16) ? 0xf : 0x0);
/* Envelope: \/\/\/\/ */
envelope_shapes_[c][p] = (p & 0x1f) ^ ((p < 32) ? 0x1f : 0x0);
envelope_overflow_masks_[c] = 0x00;
break;
case 14:
envelope_shapes_[c][p] = (p & 0xf) ^ ((p < 16) ? 0x0 : 0xf);
/* Envelope: /\/\/\/\ */
envelope_shapes_[c][p] = (p & 0x1f) ^ ((p < 32) ? 0x0 : 0x1f);
envelope_overflow_masks_[c] = 0x00;
break;
case 11:
envelope_shapes_[c][p] = (p < 16) ? (p^0xf) : 0xf;
envelope_overflow_masks_[c] = 0x1f;
/* Envelope: \------ (if - is high) */
envelope_shapes_[c][p] = (p < 32) ? (p^0x1f) : 0x1f;
envelope_overflow_masks_[c] = 0x3f;
break;
case 13:
envelope_shapes_[c][p] = (p < 16) ? p : 0xf;
envelope_overflow_masks_[c] = 0x1f;
/* Envelope: /------- */
envelope_shapes_[c][p] = (p < 32) ? p : 0x1f;
envelope_overflow_masks_[c] = 0x3f;
break;
}
}
@@ -59,21 +74,50 @@ AY38910::AY38910(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_
set_sample_volume_range(0);
}
void AY38910::set_sample_volume_range(std::int16_t range) {
// set up volume lookup table
const float max_volume = static_cast<float>(range) / 3.0f; // As there are three channels.
const float root_two = sqrtf(2.0f);
for(int v = 0; v < 16; v++) {
volumes_[v] = static_cast<int>(max_volume / powf(root_two, static_cast<float>(v ^ 0xf)));
template <bool is_stereo> void AY38910<is_stereo>::set_sample_volume_range(std::int16_t range) {
// Set up volume lookup table; the function below is based on a combination of the graph
// from the YM's datasheet, showing a clear power curve, and fitting that to observed
// values reported elsewhere.
const float max_volume = float(range) / 3.0f; // As there are three channels.
constexpr float root_two = 1.414213562373095f;
for(int v = 0; v < 32; v++) {
volumes_[v] = int(max_volume / powf(root_two, float(v ^ 0x1f) / 3.18f));
}
volumes_[0] = 0;
// Tie level 0 to silence.
for(int v = 31; v >= 0; --v) {
volumes_[v] -= volumes_[0];
}
evaluate_output_volume();
}
void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) {
template <bool is_stereo> void AY38910<is_stereo>::set_output_mixing(float a_left, float b_left, float c_left, float a_right, float b_right, float c_right) {
a_left_ = uint8_t(a_left * 255.0f);
b_left_ = uint8_t(b_left * 255.0f);
c_left_ = uint8_t(c_left * 255.0f);
a_right_ = uint8_t(a_right * 255.0f);
b_right_ = uint8_t(b_right * 255.0f);
c_right_ = uint8_t(c_right * 255.0f);
}
template <bool is_stereo> void AY38910<is_stereo>::get_samples(std::size_t number_of_samples, int16_t *target) {
// Note on structure below: the real AY has a built-in divider of 8
// prior to applying its tone and noise dividers. But the YM fills the
// same total periods for noise and tone with double-precision envelopes.
// Therefore this class implements a divider of 4 and doubles the tone
// and noise periods. The envelope ticks along at the divide-by-four rate,
// but if this is an AY rather than a YM then its lowest bit is forced to 1,
// matching the YM datasheet's depiction of envelope level 31 as equal to
// programmatic volume 15, envelope level 29 as equal to programmatic 14, etc.
std::size_t c = 0;
while((master_divider_&7) && c < number_of_samples) {
target[c] = output_volume_;
while((master_divider_&3) && c < number_of_samples) {
if constexpr (is_stereo) {
reinterpret_cast<uint32_t *>(target)[c] = output_volume_;
} else {
target[c] = int16_t(output_volume_);
}
master_divider_++;
c++;
}
@@ -83,49 +127,53 @@ void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) {
if(tone_counters_[c]) tone_counters_[c]--;\
else {\
tone_outputs_[c] ^= 1;\
tone_counters_[c] = tone_periods_[c];\
tone_counters_[c] = tone_periods_[c] << 1;\
}
// update the tone channels
// Update the tone channels.
step_channel(0);
step_channel(1);
step_channel(2);
#undef step_channel
// ... the noise generator. This recomputes the new bit repeatedly but harmlessly, only shifting
// Update the noise generator. This recomputes the new bit repeatedly but harmlessly, only shifting
// it into the official 17 upon divider underflow.
if(noise_counter_) noise_counter_--;
else {
noise_counter_ = noise_period_;
noise_counter_ = noise_period_ << 1; // To cover the double resolution of envelopes.
noise_output_ ^= noise_shift_register_&1;
noise_shift_register_ |= ((noise_shift_register_ ^ (noise_shift_register_ >> 3))&1) << 17;
noise_shift_register_ >>= 1;
}
// ... and the envelope generator. Table based for pattern lookup, with a 'refill' step: a way of
// implementing non-repeating patterns by locking them to table position 0x1f.
// Update the envelope generator. Table based for pattern lookup, with a 'refill' step: a way of
// implementing non-repeating patterns by locking them to the final table position.
if(envelope_divider_) envelope_divider_--;
else {
envelope_divider_ = envelope_period_;
envelope_position_ ++;
if(envelope_position_ == 32) envelope_position_ = envelope_overflow_masks_[output_registers_[13]];
if(envelope_position_ == 64) envelope_position_ = envelope_overflow_masks_[output_registers_[13]];
}
evaluate_output_volume();
for(int ic = 0; ic < 8 && c < number_of_samples; ic++) {
target[c] = output_volume_;
for(int ic = 0; ic < 4 && c < number_of_samples; ic++) {
if constexpr (is_stereo) {
reinterpret_cast<uint32_t *>(target)[c] = output_volume_;
} else {
target[c] = int16_t(output_volume_);
}
c++;
master_divider_++;
}
}
master_divider_ &= 7;
master_divider_ &= 3;
}
void AY38910::evaluate_output_volume() {
int envelope_volume = envelope_shapes_[output_registers_[13]][envelope_position_];
template <bool is_stereo> void AY38910<is_stereo>::evaluate_output_volume() {
int envelope_volume = envelope_shapes_[output_registers_[13]][envelope_position_ | envelope_position_mask_];
// The output level for a channel is:
// 1 if neither tone nor noise is enabled;
@@ -142,9 +190,20 @@ void AY38910::evaluate_output_volume() {
};
#undef level
// Channel volume is a simple selection: if the bit at 0x10 is set, use the envelope volume; otherwise use the lower four bits
// This remapping table seeks to map 'channel volumes', i.e. the levels produced from the
// 16-step progammatic volumes set per channel to 'envelope volumes', i.e. the 32-step
// volumes that are produced by the envelope generators (on a YM at least). My reading of
// the data sheet is that '0' is still off, but 15 should be as loud as peak envelope. So
// I've thrown in the discontinuity at the low end, where it'll be very quiet.
const int channel_volumes[] = {
0, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31
};
static_assert(sizeof(channel_volumes) == 16*sizeof(int));
// Channel volume is a simple selection: if the bit at 0x10 is set, use the envelope volume; otherwise use the lower four bits,
// mapped to the range 131 in case this is a YM.
#define channel_volume(c) \
((output_registers_[c] >> 4)&1) * envelope_volume + (((output_registers_[c] >> 4)&1)^1) * (output_registers_[c]&0xf)
((output_registers_[c] >> 4)&1) * envelope_volume + (((output_registers_[c] >> 4)&1)^1) * channel_volumes[output_registers_[c]&0xf]
const int volumes[3] = {
channel_volume(8),
@@ -153,34 +212,47 @@ void AY38910::evaluate_output_volume() {
};
#undef channel_volume
// Mix additively.
output_volume_ = static_cast<int16_t>(
volumes_[volumes[0]] * channel_levels[0] +
volumes_[volumes[1]] * channel_levels[1] +
volumes_[volumes[2]] * channel_levels[2]
);
// Mix additively, weighting if in stereo.
if constexpr (is_stereo) {
int16_t *const output_volumes = reinterpret_cast<int16_t *>(&output_volume_);
output_volumes[0] = int16_t((
volumes_[volumes[0]] * channel_levels[0] * a_left_ +
volumes_[volumes[1]] * channel_levels[1] * b_left_ +
volumes_[volumes[2]] * channel_levels[2] * c_left_
) >> 8);
output_volumes[1] = int16_t((
volumes_[volumes[0]] * channel_levels[0] * a_right_ +
volumes_[volumes[1]] * channel_levels[1] * b_right_ +
volumes_[volumes[2]] * channel_levels[2] * c_right_
) >> 8);
} else {
output_volume_ = uint32_t(
volumes_[volumes[0]] * channel_levels[0] +
volumes_[volumes[1]] * channel_levels[1] +
volumes_[volumes[2]] * channel_levels[2]
);
}
}
bool AY38910::is_zero_level() {
template <bool is_stereo> bool AY38910<is_stereo>::is_zero_level() const {
// Confirm that the AY is trivially at the zero level if all three volume controls are set to fixed zero.
return output_registers_[0x8] == 0 && output_registers_[0x9] == 0 && output_registers_[0xa] == 0;
}
// MARK: - Register manipulation
void AY38910::select_register(uint8_t r) {
template <bool is_stereo> void AY38910<is_stereo>::select_register(uint8_t r) {
selected_register_ = r;
}
void AY38910::set_register_value(uint8_t value) {
template <bool is_stereo> void AY38910<is_stereo>::set_register_value(uint8_t value) {
// There are only 16 registers.
if(selected_register_ > 15) return;
// If this is a register that affects audio output, enqueue a mutation onto the
// audio generation thread.
if(selected_register_ < 14) {
const int selected_register = selected_register_;
task_queue_.defer([=] () {
task_queue_.defer([this, selected_register = selected_register_, value] () {
// Perform any register-specific mutation to output generation.
uint8_t masked_value = value;
switch(selected_register) {
@@ -189,7 +261,7 @@ void AY38910::set_register_value(uint8_t value) {
int channel = selected_register >> 1;
if(selected_register & 1)
tone_periods_[channel] = (tone_periods_[channel] & 0xff) | static_cast<uint16_t>((value&0xf) << 8);
tone_periods_[channel] = (tone_periods_[channel] & 0xff) | uint16_t((value&0xf) << 8);
else
tone_periods_[channel] = (tone_periods_[channel] & ~0xff) | value;
}
@@ -204,7 +276,7 @@ void AY38910::set_register_value(uint8_t value) {
break;
case 12:
envelope_period_ = (envelope_period_ & 0xff) | static_cast<int>(value << 8);
envelope_period_ = (envelope_period_ & 0xff) | int(value << 8);
break;
case 13:
@@ -242,7 +314,7 @@ void AY38910::set_register_value(uint8_t value) {
if(update_port_a) set_port_output(false);
}
uint8_t AY38910::get_register_value() {
template <bool is_stereo> uint8_t AY38910<is_stereo>::get_register_value() {
// This table ensures that bits that aren't defined within the AY are returned as 0s
// when read, conforming to CPC-sourced unit tests.
const uint8_t register_masks[16] = {
@@ -256,24 +328,24 @@ uint8_t AY38910::get_register_value() {
// MARK: - Port querying
uint8_t AY38910::get_port_output(bool port_b) {
template <bool is_stereo> uint8_t AY38910<is_stereo>::get_port_output(bool port_b) {
return registers_[port_b ? 15 : 14];
}
// MARK: - Bus handling
void AY38910::set_port_handler(PortHandler *handler) {
template <bool is_stereo> void AY38910<is_stereo>::set_port_handler(PortHandler *handler) {
port_handler_ = handler;
set_port_output(true);
set_port_output(false);
}
void AY38910::set_data_input(uint8_t r) {
template <bool is_stereo> void AY38910<is_stereo>::set_data_input(uint8_t r) {
data_input_ = r;
update_bus();
}
void AY38910::set_port_output(bool port_b) {
template <bool is_stereo> void AY38910<is_stereo>::set_port_output(bool port_b) {
// Per the data sheet: "each [IO] pin is provided with an on-chip pull-up resistor,
// so that when in the "input" mode, all pins will read normally high". Therefore,
// report programmer selection of input mode as creating an output of 0xff.
@@ -283,7 +355,7 @@ void AY38910::set_port_output(bool port_b) {
}
}
uint8_t AY38910::get_data_output() {
template <bool is_stereo> uint8_t AY38910<is_stereo>::get_data_output() {
if(control_state_ == Read && selected_register_ >= 14 && selected_register_ < 16) {
// Per http://cpctech.cpc-live.com/docs/psgnotes.htm if a port is defined as output then the
// value returned to the CPU when reading it is the and of the output value and any input.
@@ -299,22 +371,22 @@ uint8_t AY38910::get_data_output() {
return data_output_;
}
void AY38910::set_control_lines(ControlLines control_lines) {
switch(static_cast<int>(control_lines)) {
template <bool is_stereo> void AY38910<is_stereo>::set_control_lines(ControlLines control_lines) {
switch(int(control_lines)) {
default: control_state_ = Inactive; break;
case static_cast<int>(BDIR | BC2 | BC1):
case int(BDIR | BC2 | BC1):
case BDIR:
case BC1: control_state_ = LatchAddress; break;
case static_cast<int>(BC2 | BC1): control_state_ = Read; break;
case static_cast<int>(BDIR | BC2): control_state_ = Write; break;
case int(BC2 | BC1): control_state_ = Read; break;
case int(BDIR | BC2): control_state_ = Write; break;
}
update_bus();
}
void AY38910::update_bus() {
template <bool is_stereo> void AY38910<is_stereo>::update_bus() {
// Assume no output, unless this turns out to be a read.
data_output_ = 0xff;
switch(control_state_) {
@@ -324,3 +396,7 @@ void AY38910::update_bus() {
case Read: data_output_ = get_register_value(); break;
}
}
// Ensure both mono and stereo versions of the AY are built.
template class GI::AY38910::AY38910<true>;
template class GI::AY38910::AY38910<false>;

View File

@@ -30,16 +30,17 @@ class PortHandler {
@param port_b @c true if the input being queried is Port B. @c false if it is Port A.
*/
virtual uint8_t get_port_input(bool port_b) {
virtual uint8_t get_port_input([[maybe_unused]] bool port_b) {
return 0xff;
}
/*!
Requests the current input on an AY port.
Sets the current output on an AY port.
@param port_b @c true if the input being queried is Port B. @c false if it is Port A.
@param port_b @c true if the output being posted is Port B. @c false if it is Port A.
@param value the value now being output.
*/
virtual void set_port_output(bool port_b, uint8_t value) {}
virtual void set_port_output([[maybe_unused]] bool port_b, [[maybe_unused]] uint8_t value) {}
};
/*!
@@ -51,15 +52,24 @@ enum ControlLines {
BDIR = (1 << 2)
};
enum class Personality {
/// Provides 16 volume levels to envelopes.
AY38910,
/// Provides 32 volume levels to envelopes.
YM2149F
};
/*!
Provides emulation of an AY-3-8910 / YM2149, which is a three-channel sound chip with a
noise generator and a volume envelope generator, which also provides two bidirectional
interface ports.
This AY has an attached mono or stereo mixer.
*/
class AY38910: public ::Outputs::Speaker::SampleSource {
template <bool is_stereo> class AY38910: public ::Outputs::Speaker::SampleSource {
public:
/// Creates a new AY38910.
AY38910(Concurrency::DeferringAsyncTaskQueue &task_queue);
AY38910(Personality, Concurrency::DeferringAsyncTaskQueue &);
/// Sets the value the AY would read from its data lines if it were not outputting.
void set_data_input(uint8_t r);
@@ -83,10 +93,23 @@ class AY38910: public ::Outputs::Speaker::SampleSource {
*/
void set_port_handler(PortHandler *);
/*!
Enables or disables stereo output; if stereo output is enabled then also sets the weight of each of the AY's
channels in each of the output channels.
If a_left_ = b_left = c_left = a_right = b_right = c_right = 1.0 then you'll get output that's effectively mono.
a_left = 0.0, a_right = 1.0 will make A full volume on the right output, and silent on the left.
a_left = 0.5, a_right = 0.5 will make A half volume on both outputs.
*/
void set_output_mixing(float a_left, float b_left, float c_left, float a_right = 1.0, float b_right = 1.0, float c_right = 1.0);
// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter.
void get_samples(std::size_t number_of_samples, int16_t *target);
bool is_zero_level();
bool is_zero_level() const;
void set_sample_volume_range(std::int16_t range);
static constexpr bool get_is_stereo() { return is_stereo; }
private:
Concurrency::DeferringAsyncTaskQueue &task_queue_;
@@ -108,11 +131,11 @@ class AY38910: public ::Outputs::Speaker::SampleSource {
int envelope_period_ = 0;
int envelope_divider_ = 0;
int envelope_position_ = 0;
int envelope_shapes_[16][32];
int envelope_position_ = 0, envelope_position_mask_ = 0;
int envelope_shapes_[16][64];
int envelope_overflow_masks_[16];
int volumes_[16];
int volumes_[32];
enum ControlState {
Inactive,
@@ -127,14 +150,21 @@ class AY38910: public ::Outputs::Speaker::SampleSource {
uint8_t data_input_, data_output_;
int16_t output_volume_;
void evaluate_output_volume();
uint32_t output_volume_;
void update_bus();
PortHandler *port_handler_ = nullptr;
void set_port_output(bool port_b);
void evaluate_output_volume();
// Output mixing control.
uint8_t a_left_ = 255, a_right_ = 255;
uint8_t b_left_ = 255, b_right_ = 255;
uint8_t c_left_ = 255, c_right_ = 255;
};
}
}

View File

@@ -23,16 +23,16 @@ void Toggle::set_sample_volume_range(std::int16_t range) {
volume_ = range;
}
void Toggle::skip_samples(const std::size_t number_of_samples) {}
void Toggle::skip_samples(std::size_t) {}
void Toggle::set_output(bool enabled) {
if(is_enabled_ == enabled) return;
is_enabled_ = enabled;
audio_queue_.defer([=] {
audio_queue_.defer([this, enabled] {
level_ = enabled ? volume_ : 0;
});
}
bool Toggle::get_output() {
bool Toggle::get_output() const {
return is_enabled_;
}

View File

@@ -26,7 +26,7 @@ class Toggle: public Outputs::Speaker::SampleSource {
void skip_samples(const std::size_t number_of_samples);
void set_output(bool enabled);
bool get_output();
bool get_output() const;
private:
// Accessed on the calling thread.

View File

@@ -78,20 +78,20 @@ void DiskII::select_drive(int drive) {
void DiskII::run_for(const Cycles cycles) {
if(preferred_clocking() == ClockingHint::Preference::None) return;
int integer_cycles = cycles.as_int();
auto integer_cycles = cycles.as_integral();
while(integer_cycles--) {
const int address = (state_ & 0xf0) | inputs_ | ((shift_register_&0x80) >> 6);
if(flux_duration_) {
--flux_duration_;
if(!flux_duration_) inputs_ |= input_flux;
}
state_ = state_machine_[static_cast<std::size_t>(address)];
state_ = state_machine_[size_t(address)];
switch(state_ & 0xf) {
default: shift_register_ = 0; break; // clear
case 0x8: break; // nop
default: shift_register_ = 0; break; // clear
case 0x8: break; // nop
case 0x9: shift_register_ = static_cast<uint8_t>(shift_register_ << 1); break; // shift left, bringing in a zero
case 0xd: shift_register_ = static_cast<uint8_t>((shift_register_ << 1) | 1); break; // shift left, bringing in a one
case 0x9: shift_register_ = uint8_t(shift_register_ << 1); break; // shift left, bringing in a zero
case 0xd: shift_register_ = uint8_t((shift_register_ << 1) | 1); break; // shift left, bringing in a one
case 0xa: // shift right, bringing in write protected status
shift_register_ = (shift_register_ >> 1) | (is_write_protected() ? 0x80 : 0x00);
@@ -105,7 +105,7 @@ void DiskII::run_for(const Cycles cycles) {
return;
}
break;
case 0xb: shift_register_ = data_input_; break; // load data register from data bus
case 0xb: shift_register_ = data_input_; break; // load data register from data bus
}
// Currently writing?
@@ -124,7 +124,7 @@ void DiskII::run_for(const Cycles cycles) {
// motor switch being flipped and the drive motor actually switching off.
// This models that, accepting overrun as a risk.
if(motor_off_time_ >= 0) {
motor_off_time_ -= cycles.as_int();
motor_off_time_ -= cycles.as_integral();
if(motor_off_time_ < 0) {
set_control(Control::Motor, false);
}
@@ -191,7 +191,6 @@ void DiskII::set_state_machine(const std::vector<uint8_t> &state_machine) {
((source_address&0x02) ? 0x02 : 0x00);
uint8_t source_value = state_machine[source_address];
// Remap into Beneath Apple Pro-DOS value form.
source_value =
((source_value & 0x80) ? 0x10 : 0x0) |
((source_value & 0x40) ? 0x20 : 0x0) |
@@ -219,13 +218,13 @@ void DiskII::process_event(const Storage::Disk::Drive::Event &event) {
}
}
void DiskII::set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) {
void DiskII::set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference) {
drive_is_sleeping_[0] = drives_[0].preferred_clocking() == ClockingHint::Preference::None;
drive_is_sleeping_[1] = drives_[1].preferred_clocking() == ClockingHint::Preference::None;
decide_clocking_preference();
}
ClockingHint::Preference DiskII::preferred_clocking() {
ClockingHint::Preference DiskII::preferred_clocking() const {
return clocking_preference_;
}
@@ -266,7 +265,7 @@ int DiskII::read_address(int address) {
break;
case 0xf:
if(!(inputs_ & input_mode))
drives_[active_drive_].begin_writing(Storage::Time(1, clock_rate_), false);
drives_[active_drive_].begin_writing(Storage::Time(1, int(clock_rate_)), false);
inputs_ |= input_mode;
break;
}

View File

@@ -26,7 +26,7 @@ namespace Apple {
/*!
Provides an emulation of the Apple Disk II.
*/
class DiskII:
class DiskII :
public Storage::Disk::Drive::EventDelegate,
public ClockingHint::Source,
public ClockingHint::Observer {
@@ -48,7 +48,7 @@ class DiskII:
The value returned by @c read_address if accessing that address
didn't cause the disk II to place anything onto the bus.
*/
const int DidNotLoad = -1;
static constexpr int DidNotLoad = -1;
/// Advances the controller by @c cycles.
void run_for(const Cycles cycles);
@@ -76,7 +76,7 @@ class DiskII:
void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive);
// As per Sleeper.
ClockingHint::Preference preferred_clocking() override;
ClockingHint::Preference preferred_clocking() const final;
// The Disk II functions as a potential target for @c Activity::Sources.
void set_activity_observer(Activity::Observer *observer);
@@ -98,10 +98,10 @@ class DiskII:
void select_drive(int drive);
uint8_t trigger_address(int address, uint8_t value);
void process_event(const Storage::Disk::Drive::Event &event) override;
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) override;
void process_event(const Storage::Disk::Drive::Event &event) final;
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) final;
const int clock_rate_ = 0;
const Cycles::IntType clock_rate_ = 0;
uint8_t state_ = 0;
uint8_t inputs_ = 0;
@@ -109,7 +109,7 @@ class DiskII:
int stepper_mask_ = 0;
int stepper_position_ = 0;
int motor_off_time_ = -1;
Cycles::IntType motor_off_time_ = -1;
bool is_write_protected();
std::array<uint8_t, 256> state_machine_;

View File

@@ -13,15 +13,15 @@
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. */
constexpr int CA0 = 1 << 0;
constexpr int CA1 = 1 << 1;
constexpr int CA2 = 1 << 2;
constexpr int LSTRB = 1 << 3;
constexpr int ENABLE = 1 << 4;
constexpr int DRIVESEL = 1 << 5; /* This means drive select, like on the original Disk II. */
constexpr int Q6 = 1 << 6;
constexpr int Q7 = 1 << 7;
constexpr 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) :
@@ -233,25 +233,34 @@ void IWM::run_for(const Cycles cycles) {
}
// Activity otherwise depends on mode and motor state.
int integer_cycles = cycles.as_int();
auto integer_cycles = cycles.as_integral();
switch(shift_mode_) {
case ShiftMode::Reading:
case ShiftMode::Reading: {
// Per the IWM patent, column 7, around line 35 onwards: "The expected time
// is widened by approximately one-half an interval before and after the
// expected time since the data is not precisely spaced when read due to
// variations in drive speed and other external factors". The error_margin
// here implements the 'after' part of that contract.
const auto error_margin = Cycles(bit_length_.as_integral() >> 1);
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)) {
if(cycles_since_shift_ == bit_length_ + error_margin) {
propose_shift(0);
}
}
} else {
while(cycles_since_shift_ + integer_cycles >= bit_length_ + Cycles(2)) {
while(cycles_since_shift_ + integer_cycles >= bit_length_ + error_margin) {
const auto run_length = bit_length_ + error_margin - cycles_since_shift_;
integer_cycles -= run_length.as_integral();
cycles_since_shift_ += run_length;
propose_shift(0);
integer_cycles -= bit_length_.as_int() + 2 - cycles_since_shift_.as_int();
}
cycles_since_shift_ += Cycles(integer_cycles);
}
break;
} break;
case ShiftMode::Writing:
if(drives_[active_drive_]->is_writing()) {
@@ -263,7 +272,7 @@ void IWM::run_for(const Cycles cycles) {
drives_[active_drive_]->write_bit(shift_register_ & 0x80);
shift_register_ <<= 1;
integer_cycles -= cycles_until_write.as_int();
integer_cycles -= cycles_until_write.as_integral();
cycles_since_shift_ = Cycles(0);
--output_bits_remaining_;
@@ -298,8 +307,8 @@ void IWM::run_for(const Cycles cycles) {
} else {
shift_register_ = sense();
}
[[fallthrough]];
/* Deliberate fallthrough. */
default:
if(drive_is_rotating_[active_drive_]) drives_[active_drive_]->run_for(cycles);
break;
@@ -324,7 +333,7 @@ void IWM::select_shift_mode() {
// 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);
drives_[active_drive_]->begin_writing(Storage::Time(1, clock_rate_ / bit_length_.as_integral()), false);
shift_register_ = next_output_;
write_handshake_ |= 0x80 | 0x40;
output_bits_remaining_ = 8;
@@ -351,12 +360,28 @@ void IWM::propose_shift(uint8_t bit) {
// TODO: synchronous mode.
// LOG("Shifting input");
// See above for text from the IWM patent, column 7, around line 35 onwards.
// The error_margin here implements the 'before' part of that contract.
//
// Basic effective logic: if at least 1 is fozund in the bit_length_ cycles centred
// on the current expected bit delivery time as implied by cycles_since_shift_,
// shift in a 1 and start a new window wherever the first found 1 was.
//
// If no 1s are found, shift in a 0 and don't alter expectations as to window placement.
const auto error_margin = Cycles(bit_length_.as_integral() >> 1);
if(bit && cycles_since_shift_ < error_margin) return;
shift_register_ = uint8_t((shift_register_ << 1) | bit);
if(shift_register_ & 0x80) {
data_register_ = shift_register_;
shift_register_ = 0;
}
cycles_since_shift_ = Cycles(0);
if(bit)
cycles_since_shift_ = Cycles(0);
else
cycles_since_shift_ -= bit_length_;
}
void IWM::set_drive(int slot, IWMDrive *drive) {
@@ -374,3 +399,8 @@ void IWM::set_component_prefers_clocking(ClockingHint::Source *component, Clocki
drive_is_rotating_[1] = is_rotating;
}
}
void IWM::set_activity_observer(Activity::Observer *observer) {
if(drives_[0]) drives_[0]->set_activity_observer(observer, "Internal Floppy", true);
if(drives_[1]) drives_[1]->set_activity_observer(observer, "External Floppy", true);
}

View File

@@ -9,8 +9,11 @@
#ifndef IWM_hpp
#define IWM_hpp
#include "../../Activity/Observer.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
#include "../../ClockReceiver/ClockingHintSource.hpp"
#include "../../Storage/Disk/Drive.hpp"
#include <cstdint>
@@ -21,7 +24,7 @@ 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,
These are subclasses 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 {
@@ -67,16 +70,21 @@ class IWM:
/// Connects a drive to the IWM.
void set_drive(int slot, IWMDrive *drive);
/// Registers the currently-connected drives as @c Activity::Sources ;
/// the first will be declared 'Internal', the second 'External'.
void set_activity_observer(Activity::Observer *observer);
private:
// Storage::Disk::Drive::EventDelegate.
void process_event(const Storage::Disk::Drive::Event &event) override;
void process_event(const Storage::Disk::Drive::Event &event) final;
const int clock_rate_;
uint8_t data_register_ = 0;
uint8_t mode_ = 0;
bool read_write_ready_ = true;
bool write_overran_ = false;
// These related to functionality not-yet implemented.
// bool read_write_ready_ = true;
// bool write_overran_ = false;
int state_ = 0;
@@ -84,7 +92,7 @@ class IWM:
IWMDrive *drives_[2] = {nullptr, nullptr};
bool drive_is_rotating_[2] = {false, false};
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override;
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) final;
Cycles cycles_until_disable_;
uint8_t write_handshake_ = 0x80;

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