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

Compare commits

...

117 Commits

Author SHA1 Message Date
Thomas Harte
3d2490b774 Merge pull request #869 from TomHarte/OricReads
Flips conditionals to ensure 65802 safety.
2021-02-02 21:03:08 -05:00
Thomas Harte
1e041f1adf Flips conditionals to ensure 65802 safety. 2021-02-02 20:52:34 -05:00
Thomas Harte
4fdf01a1a8 Merge pull request #868 from TomHarte/ElectronSCSI
Adds Electron hard disk support.
2021-02-02 20:43:08 -05:00
Thomas Harte
beb514b231 Adds an additional mapping for copy. 2021-02-02 20:37:15 -05:00
Thomas Harte
f57e897085 Corrects visibility of SCSI output. 2021-02-02 20:24:39 -05:00
Thomas Harte
2a8e8a4982 Slightly increases logging. 2021-02-02 20:24:19 -05:00
Thomas Harte
9f202d4238 Adds SCSI interrupt support. 2021-02-01 17:40:11 -05:00
Thomas Harte
1a40cc048e Niceties: include AP6 ROM for hard-disk users; show SCSI activity indicator. 2021-01-31 21:41:11 -05:00
Thomas Harte
53514c7fdc Ensures non-breakage of Qt interface. 2021-01-31 21:28:55 -05:00
Thomas Harte
274b3c7d24 Handles SCSI changes on-demand. 2021-01-31 21:24:54 -05:00
Thomas Harte
07df7572b3 Switch to preferred Acorn-world extension: DAT. 2021-01-31 21:03:09 -05:00
Thomas Harte
906b6ccdb7 This appears to be sufficient for the Electron to _read_ SCSI.
So that's step one.
2021-01-31 18:36:29 -05:00
Thomas Harte
f1ba040dd8 This is probably how Acorn hard disk images look (?) 2021-01-31 16:00:52 -05:00
Thomas Harte
8db289e229 Adds some notes-to-self on SCSI and a route to using Acorn's ADFS. 2021-01-31 13:12:59 -05:00
Thomas Harte
8142487d57 Merge pull request #867 from TomHarte/ElectronStarCommand
Pause longer for Electron commands that start with a modifier.
2021-01-31 12:34:19 -05:00
Thomas Harte
2860be7068 Permit a longer pause at startup for Electron commands that start with shift, control or func. 2021-01-31 12:25:22 -05:00
Thomas Harte
7e720e754b Merge pull request #866 from TomHarte/ElectronUI
Adds UI for the new Electron configuration options.
2021-01-31 11:44:47 -05:00
Thomas Harte
41a618c957 Adds new Electron configuration options to the Qt UI. 2021-01-31 10:13:32 -05:00
Thomas Harte
3d85e6bb97 Adds Mac UI for new Electron configuration options. 2021-01-31 09:49:51 -05:00
Thomas Harte
d54085c7fd Merge pull request #865 from TomHarte/ADL
Electron: adds support for the ADL file format, and logic for AP6 and sideways RAM selection
2021-01-31 09:37:24 -05:00
Thomas Harte
0bb8bdf938 Switch to O(1) test, which avoids an extra #include. 2021-01-30 23:33:03 -05:00
Thomas Harte
865058b8d6 Adds basic text search to achieve AP6 detection. 2021-01-30 23:32:04 -05:00
Thomas Harte
b6bc0a21fb Adds a TODO on intended logic around the AP6 ROM.
... plus a promise as to intent in the Electron-specific ROM readme.
2021-01-30 23:20:43 -05:00
Thomas Harte
8311ac4a7c Adds parsing of the top-level directory for ADFS images. 2021-01-30 23:10:59 -05:00
Thomas Harte
4636d8dfb7 Adds support for installing the AP6 ROM and/or sideways RAM. 2021-01-30 19:38:19 -05:00
Thomas Harte
ac95e4d758 Adds support for ADL-format disk images. 2021-01-30 18:39:29 -05:00
Thomas Harte
4bd6ffa9e4 Merge pull request #863 from TomHarte/DecodersAhoy
Sketches out the concept of a `Decoder`
2021-01-15 21:14:49 -05:00
Thomas Harte
9c2c918760 Better sorts by function, corrects TEST description. 2021-01-15 21:07:02 -05:00
Thomas Harte
47d20699d8 Completes list, ensures POP acts as documented. 2021-01-15 20:48:31 -05:00
Thomas Harte
e8ce70dccb Chips further away at documentation. 2021-01-15 18:52:59 -05:00
Thomas Harte
fa4938f29c Establishes the reason I'm sort-of documenting these. 2021-01-15 18:27:55 -05:00
Thomas Harte
ddb4bb1421 Better plans project layout. 2021-01-15 18:16:01 -05:00
Thomas Harte
ca94e9038e Introduces 'far' test, fixes parsing. 2021-01-14 22:15:38 -05:00
Thomas Harte
2c72a77a25 Adds byte-by-byte decoder test; corrects divergences. 2021-01-13 21:51:18 -05:00
Thomas Harte
8c0e06e645 Adds a test for 0x83 and fixes sign extension.
ODA doesn't seem to accept 0x82, but testing 0x83 adds some confidence.
2021-01-13 20:42:21 -05:00
Thomas Harte
a24ae727a7 Takes a run at 0x82 and 0x83, completing the set. 2021-01-13 20:29:44 -05:00
Thomas Harte
5058a8b96a Completes the first test stream.
... and improves decoding consistency in conjunction.
2021-01-12 21:49:22 -05:00
Thomas Harte
762ecab3aa Adds operand/displacement capture.
This gets unit test as far as a disagreement over how to handle bad 0xc4 suffixes.
2021-01-10 22:55:25 -05:00
Thomas Harte
9ba5b7c1d4 Adds a few more asserts.
It's still just operands and displacements failing, which is nice.
2021-01-08 23:21:01 -05:00
Thomas Harte
5f807b6e47 Ensures that the operand is the only thing failing in decoding of the first instruction. 2021-01-08 23:02:06 -05:00
Thomas Harte
718f950071 Implements 80 and 81. 2021-01-08 22:50:59 -05:00
Thomas Harte
68fe16a092 Marks intent for operand/displacement. 2021-01-08 22:45:27 -05:00
Thomas Harte
97a64db5e0 Edges closer towards full x86 recording. 2021-01-08 22:38:56 -05:00
Thomas Harte
86577b772b Rethinks size; packs all captured information into an x86 Instruction.
Albeit that operand and displacement are't yet captured. Or extractable.
2021-01-08 22:22:07 -05:00
Thomas Harte
306df7554e Starts trying to find a good packing for X86 instructions.
To consider: do I really need `size` on every instruction?
2021-01-08 21:33:01 -05:00
Thomas Harte
30c2c0f050 Attempts to complete operand recognition. 2021-01-07 21:59:00 -05:00
Thomas Harte
205649cac2 Decodes 8e. 2021-01-07 21:36:05 -05:00
Thomas Harte
fd49b72e31 Simplifies macros, implements d0, d1, d2 and d3. 2021-01-07 21:30:01 -05:00
Thomas Harte
995904993d Fills in 8f, c2, c3, ca and cb.
Also switches to RETN and RETF for near/far RET as this seems idiomatic.
2021-01-06 21:18:24 -05:00
Thomas Harte
17cbba85fc Formalises what's missing in terms of opcodes and fills in some blanks. 2021-01-05 21:47:12 -05:00
Thomas Harte
9d7d45338f Ostensibly gets the instruction stream correct for test case 1.
Subject to operand and displacement currently being absent, and likely inconsistencies in field population, most of which are omitted from the Instruction anyway.
2021-01-05 21:34:35 -05:00
Thomas Harte
3b55d3f158 Nudges up to a need to decode operation from the ModRegRM byte. 2021-01-05 21:25:12 -05:00
Thomas Harte
fda2293d6b Improves constness. 2021-01-04 22:36:39 -05:00
Thomas Harte
d4095b1b3b Merge branch 'master' into DecodersAhoy 2021-01-03 20:56:47 -05:00
Thomas Harte
ed41154338 Merge pull request #862 from MaddTheSane/madds-patch-1
Madd's improvements
2021-01-03 20:53:39 -05:00
Thomas Harte
38bca5f0f0 Finally runs into the wall of trying to merge operands and offsets. 2021-01-03 20:08:13 -05:00
Thomas Harte
a8738b533a Switch for now to block-level decoding.
It's easier to step debug.
2021-01-03 20:07:46 -05:00
Thomas Harte
29cf96c703 Adds decoding of disp16 RETs. 2021-01-03 19:39:28 -05:00
Thomas Harte
782dc3d046 Distinguishes inter- and intra-segment RET. 2021-01-03 19:37:37 -05:00
Thomas Harte
0ae217f51d Improves exposition, adds decoding of the 0xbx patch of MOVs. 2021-01-03 19:33:16 -05:00
Thomas Harte
adcb2e03e8 Attempts to consolidate source/destination ordering. 2021-01-03 17:28:29 -05:00
Thomas Harte
11b6c1d4b5 Proceeds to three instructions correctly decoded. 'Wow'. 2021-01-03 17:03:50 -05:00
Thomas Harte
367cb1789d Starts building an x86 test. 2021-01-03 16:37:35 -05:00
Thomas Harte
adf1484ecc Introduces third test sequence, uneventfully. 2021-01-03 16:21:23 -05:00
Thomas Harte
5401ff6c78 Proactively fixes li sign extension. 2021-01-03 11:14:43 -05:00
Thomas Harte
eb8d0eefd5 Factors out some boilerplate and introduces second sequence. 2021-01-03 11:14:30 -05:00
Thomas Harte
c934e22cee Introduces a first test of PowerPC decoding.
Corrected as a result: the bcx conditional, that stdu is 64-bit only, extraction of the li field.
2021-01-02 22:47:42 -05:00
Thomas Harte
1a3effc692 Modifies contract again. This is why I'm doing this now. 2021-01-02 21:19:45 -05:00
Thomas Harte
32c942d154 Muddles drunkenly towards decoding ModRM. 2021-01-02 21:11:19 -05:00
Thomas Harte
9c5dc0ed29 Deferring ModRM work, proceeds to 0x9f. 2021-01-02 19:29:43 -05:00
Thomas Harte
290972cedf Adds health warning. 2021-01-02 19:16:21 -05:00
Thomas Harte
dc9d370952 Does the easier part of the easier half of 8086 decoding. 2021-01-02 19:16:07 -05:00
Thomas Harte
a41be61f99 Slightly fleshes out models, for a sensible beginning. 2021-01-01 17:36:47 -05:00
Thomas Harte
3d1783ddae Add exposition as to the purpose of decoders. 2021-01-01 17:32:57 -05:00
Thomas Harte
8151c8e409 Rounds out field list. 2021-01-01 16:38:40 -05:00
Thomas Harte
0ef42f93ff Further rounds out decoder. 2021-01-01 11:46:26 -05:00
Thomas Harte
d318ab4e70 Edges further onwards. 2020-12-31 21:12:36 -05:00
Thomas Harte
ebfa35c2c7 Conquers another page of instructions; adds supervisor flag. 2020-12-31 18:14:38 -05:00
Thomas Harte
db50b0fe23 Gets started on 6+10 decoding, places stake as to other fields. 2020-12-31 16:51:31 -05:00
Thomas Harte
233a69a1d8 Decodes operations for the simplest 45. 2020-12-31 16:02:52 -05:00
C.W. Betts
3749b7b776 My improvements:
Use synthesized properties for CSMissingROM.
Remove openGLView from the xib: that will quiet a warning.
Add nullability metadata to CSStaticAnalyser.
2020-12-31 13:23:46 -07:00
Thomas Harte
ed63e7ea75 Starts building out a PowerPC decoder. 2020-12-30 22:55:59 -05:00
Thomas Harte
3d79b11f92 Merge pull request #861 from TomHarte/DiskIIOtherROM
Ensure proper in-memory ordering of the b72a2c70 ROM.
2020-12-29 22:13:24 -05:00
Thomas Harte
dfe4e49110 Ensure proper in-memory ordering of the b72a2c70 ROM. 2020-12-29 22:08:48 -05:00
Thomas Harte
c5c56f9d05 Mention my manual list sorting. 2020-12-23 11:15:57 -04:00
Thomas Harte
9f0129cab8 Merge pull request #859 from MaddTheSane/gcJoystick
Initial GameController joystick support.
2020-12-16 21:39:28 -04:00
C.W. Betts
5a48e50355 Use isEqual: to compare GCController when connecting/disconnecting.
Only remove observers for GCController notifications.
2020-12-14 15:41:11 -07:00
C.W. Betts
86283b1815 Actually write the setup code. 2020-12-14 01:14:40 -07:00
C.W. Betts
a38d964f62 Initial GameController joystick support. 2020-12-13 11:23:33 -07:00
Thomas Harte
2f86d5ebaf Merge pull request #858 from TomHarte/M1ForLife
Corrects Metal buffer sizing on Retina displays.
2020-12-09 19:18:56 -05:00
Thomas Harte
b589d6e3ef Fixes retina-display buffer size. 2020-12-09 18:51:10 -05:00
Thomas Harte
db8b265e80 Enable M1 release builds. 2020-12-09 18:38:14 -05:00
Thomas Harte
8560b38ffa Reduce to less-daunting URL. 2020-12-09 16:38:59 -05:00
Thomas Harte
94eb17db0c Add sponsorship exposition; improve general wording 2020-12-08 16:35:00 -05:00
Thomas Harte
9577c8e27f Experiment with F 2020-12-08 16:08:25 -05:00
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
55 changed files with 3715 additions and 310 deletions

13
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,13 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: ['https://www.amazon.com/hz/wishlist/ls/8WPVFLQQDPTA']
# Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View File

@@ -50,7 +50,10 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::s
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;
if(names->samples[0][file_offset + 7] & 0x80) {
// File is locked; it may not be altered or deleted.
new_file.flags |= File::Flags::Locked;
}
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);
@@ -69,11 +72,16 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::s
new_file.data.insert(new_file.data.end(), next_sector->samples[0].begin(), next_sector->samples[0].begin() + length_from_sector);
data_length -= length_from_sector;
}
if(!data_length) catalogue->files.push_back(new_file);
if(!data_length) catalogue->files.push_back(std::move(new_file));
}
return catalogue;
}
/*
Primary resource used: "Acorn 8-Bit ADFS Filesystem Structure";
http://mdfs.net/Docs/Comp/Disk/Format/ADFS
*/
std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) {
auto catalogue = std::make_unique<Catalogue>();
Storage::Encodings::MFM::Parser parser(true, disk);
@@ -101,5 +109,73 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::
case 3: catalogue->bootOption = Catalogue::BootOption::ExecBOOT; break;
}
// Parse the root directory, at least.
for(std::size_t file_offset = 0x005; file_offset < 0x4cb; file_offset += 0x1a) {
// Obtain the name, which will be at most ten characters long, and will
// be terminated by either a NULL character or a \r.
char name[11];
std::size_t c = 0;
for(; c < 10; c++) {
const char next = root_directory[file_offset + c] & 0x7f;
name[c] = next;
if(next == '\0' || next == '\r') break;
}
name[c] = '\0';
// Skip if the name is empty.
if(name[0] == '\0') continue;
// Populate a file then.
File new_file;
new_file.name = name;
new_file.flags =
(root_directory[file_offset + 0] & 0x80 ? File::Flags::Readable : 0) |
(root_directory[file_offset + 1] & 0x80 ? File::Flags::Writable : 0) |
(root_directory[file_offset + 2] & 0x80 ? File::Flags::Locked : 0) |
(root_directory[file_offset + 3] & 0x80 ? File::Flags::IsDirectory : 0) |
(root_directory[file_offset + 4] & 0x80 ? File::Flags::ExecuteOnly : 0) |
(root_directory[file_offset + 5] & 0x80 ? File::Flags::PubliclyReadable : 0) |
(root_directory[file_offset + 6] & 0x80 ? File::Flags::PubliclyWritable : 0) |
(root_directory[file_offset + 7] & 0x80 ? File::Flags::PubliclyExecuteOnly : 0) |
(root_directory[file_offset + 8] & 0x80 ? File::Flags::IsPrivate : 0);
new_file.load_address =
(uint32_t(root_directory[file_offset + 0x0a]) << 0) |
(uint32_t(root_directory[file_offset + 0x0b]) << 8) |
(uint32_t(root_directory[file_offset + 0x0c]) << 16) |
(uint32_t(root_directory[file_offset + 0x0d]) << 24);
new_file.execution_address =
(uint32_t(root_directory[file_offset + 0x0e]) << 0) |
(uint32_t(root_directory[file_offset + 0x0f]) << 8) |
(uint32_t(root_directory[file_offset + 0x10]) << 16) |
(uint32_t(root_directory[file_offset + 0x11]) << 24);
new_file.sequence_number = root_directory[file_offset + 0x19];
const uint32_t size =
(uint32_t(root_directory[file_offset + 0x12]) << 0) |
(uint32_t(root_directory[file_offset + 0x13]) << 8) |
(uint32_t(root_directory[file_offset + 0x14]) << 16) |
(uint32_t(root_directory[file_offset + 0x15]) << 24);
uint32_t start_sector =
(uint32_t(root_directory[file_offset + 0x16]) << 0) |
(uint32_t(root_directory[file_offset + 0x17]) << 8) |
(uint32_t(root_directory[file_offset + 0x18]) << 16);
new_file.data.reserve(size);
while(new_file.data.size() < size) {
const Storage::Encodings::MFM::Sector *const sector = parser.get_sector(start_sector / (80 * 16), (start_sector / 16) % 80, start_sector % 16);
if(!sector) break;
const auto length_from_sector = std::min(size - new_file.data.size(), sector->samples[0].size());
new_file.data.insert(new_file.data.end(), sector->samples[0].begin(), sector->samples[0].begin() + ssize_t(length_from_sector));
++start_sector;
}
catalogue->files.push_back(std::move(new_file));
}
return catalogue;
}

View File

@@ -19,19 +19,38 @@ namespace Acorn {
struct File {
std::string name;
uint32_t load_address;
uint32_t execution_address;
bool is_protected;
uint32_t load_address = 0;
uint32_t execution_address = 0;
enum Flags: uint16_t {
Readable = 1 << 0,
Writable = 1 << 1,
Locked = 1 << 2,
IsDirectory = 1 << 3,
ExecuteOnly = 1 << 4,
PubliclyReadable = 1 << 5,
PubliclyWritable = 1 << 6,
PubliclyExecuteOnly = 1 << 7,
IsPrivate = 1 << 8,
};
uint16_t flags = Flags::Readable | Flags::Readable | Flags::PubliclyReadable | Flags::PubliclyWritable;
uint8_t sequence_number = 0;
std::vector<uint8_t> data;
/// Describes a single chunk of file data; these relate to the tape and ROM filing system.
/// The File-level fields contain a 'definitive' version of the load and execution addresses,
/// but both of those filing systems also store them per chunk.
///
/// Similarly, the file-level data will contain the aggregate data of all chunks.
struct Chunk {
std::string name;
uint32_t load_address;
uint32_t execution_address;
uint16_t block_number;
uint16_t block_length;
uint8_t block_flag;
uint32_t next_address;
uint32_t load_address = 0;
uint32_t execution_address = 0;
uint16_t block_number = 0;
uint16_t block_length = 0;
uint32_t next_address = 0;
uint8_t block_flag = 0;
bool header_crc_matched;
bool data_crc_matched;

View File

@@ -12,6 +12,8 @@
#include "Tape.hpp"
#include "Target.hpp"
#include <algorithm>
using namespace Analyser::Static::Acorn;
static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
@@ -59,10 +61,6 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
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;
target->should_shift_restart = false;
// strip out inappropriate cartridges
target->media.cartridges = AcornCartridgesFrom(media.cartridges);
@@ -77,8 +75,8 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
if(!files.empty()) {
bool is_basic = true;
// protected files are always for *RUNning only
if(files.front().is_protected) is_basic = false;
// If a file is execute-only, that means *RUN.
if(files.front().flags & File::Flags::ExecuteOnly) is_basic = false;
// 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
@@ -108,15 +106,60 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
dfs_catalogue = GetDFSCatalogue(disk);
if(dfs_catalogue == nullptr) adfs_catalogue = GetADFSCatalogue(disk);
if(dfs_catalogue || adfs_catalogue) {
// Accept the disk and determine whether DFS or ADFS ROMs are implied.
// Use the Pres ADFS if using an ADFS, as it leaves Page at &EOO.
target->media.disks = media.disks;
target->has_dfs = !!dfs_catalogue;
target->has_adfs = !!adfs_catalogue;
target->has_dfs = bool(dfs_catalogue);
target->has_pres_adfs = bool(adfs_catalogue);
// Check whether a simple shift+break will do for loading this disk.
Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption;
if(bootOption != Catalogue::BootOption::None)
if(bootOption != Catalogue::BootOption::None) {
target->should_shift_restart = true;
else
} else {
target->loading_command = "*CAT\n";
}
// Check whether adding the AP6 ROM is justified.
// For now this is an incredibly dense text search;
// if any of the commands that aren't usually present
// on a stock Electron are here, add the AP6 ROM and
// some sideways RAM such that the SR commands are useful.
for(const auto &file: dfs_catalogue ? dfs_catalogue->files : adfs_catalogue->files) {
for(const auto &command: {
"AQRPAGE", "BUILD", "DUMP", "FORMAT", "INSERT", "LANG", "LIST", "LOADROM",
"LOCK", "LROMS", "RLOAD", "ROMS", "RSAVE", "SAVEROM", "SRLOAD", "SRPAGE",
"SRUNLOCK", "SRWIPE", "TUBE", "TYPE", "UNLOCK", "UNPLUG", "UROMS",
"VERIFY", "ZERO"
}) {
if(std::search(file.data.begin(), file.data.end(), command, command+strlen(command)) != file.data.end()) {
target->has_ap6_rom = true;
target->has_sideways_ram = true;
}
}
}
}
}
// Enable the Acorn ADFS if a mass-storage device is attached;
// unlike the Pres ADFS it retains SCSI logic.
if(!media.mass_storage_devices.empty()) {
target->has_pres_adfs = false; // To override a floppy selection, if one was made.
target->has_acorn_adfs = true;
// Assume some sort of later-era Acorn work is likely to happen;
// so ensure *TYPE, etc are present.
target->has_ap6_rom = true;
target->has_sideways_ram = true;
target->media.mass_storage_devices = media.mass_storage_devices;
// Check for a boot option.
const auto sector = target->media.mass_storage_devices.front()->get_block(1);
if(sector[0xfd]) {
target->should_shift_restart = true;
} else {
target->loading_command = "*CAT\n";
}
}

View File

@@ -109,7 +109,12 @@ static std::unique_ptr<File> GetNextFile(std::deque<File::Chunk> &chunks) {
file->name = file->chunks.front().name;
file->load_address = file->chunks.front().load_address;
file->execution_address = file->chunks.front().execution_address;
file->is_protected = !!(file->chunks.back().block_flag & 0x01); // I think the last flags are the ones that count; TODO: check.
// I think the final chunk's flags are the ones that count; TODO: check.
if(file->chunks.back().block_flag & 0x01) {
// File is locked, which in more generalised terms means it is
// for execution only.
file->flags |= File::Flags::ExecuteOnly;
}
// copy all data into a single big block
for(File::Chunk chunk : file->chunks) {

View File

@@ -18,15 +18,21 @@ namespace Static {
namespace Acorn {
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
bool has_adfs = false;
bool has_acorn_adfs = false;
bool has_pres_adfs = false;
bool has_dfs = false;
bool has_ap6_rom = false;
bool has_sideways_ram = false;
bool should_shift_restart = false;
std::string loading_command;
Target() : Analyser::Static::Target(Machine::Electron) {
if(needs_declare()) {
DeclareField(has_adfs);
DeclareField(has_pres_adfs);
DeclareField(has_acorn_adfs);
DeclareField(has_dfs);
DeclareField(has_ap6_rom);
DeclareField(has_sideways_ram);
}
}
};

View File

@@ -51,6 +51,7 @@
#include "../../Storage/Disk/DiskImage/Formats/WOZ.hpp"
// Mass Storage Devices (i.e. usually, hard disks)
#include "../../Storage/MassStorage/Formats/DAT.hpp"
#include "../../Storage/MassStorage/Formats/HFV.hpp"
// Tapes
@@ -98,12 +99,14 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
Format("81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 81
Format("a26", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // A26
Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn) // ADF
Format("adl", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn) // ADL
Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::AllCartridge) // BIN (cartridge dump)
Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX) // CAS
Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC) // CDT
Format("col", result.cartridges, Cartridge::BinaryDump, TargetPlatform::ColecoVision) // COL
Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape) // CSW
Format("d64", result.disks, Disk::DiskImageHolder<Storage::Disk::D64>, TargetPlatform::Commodore) // D64
Format("dat", result.mass_storage_devices, MassStorage::DAT, TargetPlatform::Acorn) // DAT
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

View File

@@ -180,29 +180,40 @@ void DiskII::set_state_machine(const std::vector<uint8_t> &state_machine) {
if(state_machine[0] != 0x18) {
for(size_t source_address = 0; source_address < 256; ++source_address) {
// Remap into Beneath Apple Pro-DOS address form.
size_t destination_address =
((source_address&0x80) ? 0x10 : 0x00) |
((source_address&0x01) ? 0x20 : 0x00) |
((source_address&0x40) ? 0x40 : 0x00) |
const size_t destination_address =
((source_address&0x20) ? 0x80 : 0x00) |
((source_address&0x10) ? 0x01 : 0x00) |
((source_address&0x40) ? 0x40 : 0x00) |
((source_address&0x01) ? 0x20 : 0x00) |
((source_address&0x80) ? 0x10 : 0x00) |
((source_address&0x08) ? 0x08 : 0x00) |
((source_address&0x04) ? 0x04 : 0x00) |
((source_address&0x02) ? 0x02 : 0x00);
uint8_t source_value = state_machine[source_address];
((source_address&0x02) ? 0x02 : 0x00) |
((source_address&0x10) ? 0x01 : 0x00);
source_value =
// Store.
const uint8_t source_value = state_machine[source_address];
state_machine_[destination_address] =
((source_value & 0x80) ? 0x10 : 0x0) |
((source_value & 0x40) ? 0x20 : 0x0) |
((source_value & 0x20) ? 0x40 : 0x0) |
((source_value & 0x10) ? 0x80 : 0x0) |
(source_value & 0x0f);
// Store.
state_machine_[destination_address] = source_value;
}
} else {
memcpy(&state_machine_[0], &state_machine[0], 128);
for(size_t source_address = 0; source_address < 256; ++source_address) {
// Reshuffle ordering of bytes only, to retain indexing by the high nibble.
const size_t destination_address =
((source_address&0x80) ? 0x80 : 0x00) |
((source_address&0x40) ? 0x40 : 0x00) |
((source_address&0x01) ? 0x20 : 0x00) |
((source_address&0x20) ? 0x10 : 0x00) |
((source_address&0x08) ? 0x08 : 0x00) |
((source_address&0x04) ? 0x04 : 0x00) |
((source_address&0x02) ? 0x02 : 0x00) |
((source_address&0x10) ? 0x01 : 0x00);
state_machine_[destination_address] = state_machine[source_address];
}
}
}

View File

@@ -0,0 +1,347 @@
//
// Decoder.cpp
// Clock Signal
//
// Created by Thomas Harte on 12/30/20.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#include "Decoder.hpp"
using namespace CPU::Decoder::PowerPC;
Decoder::Decoder(Model model) : model_(model) {}
Instruction Decoder::decode(uint32_t opcode) {
// Quick bluffer's guide to PowerPC instruction encoding:
//
// There is a six-bit field at the very top of the instruction.
// Sometimes that fully identifies an instruction, but usually
// it doesn't.
//
// There is an addition 9- or 10-bit field starting one bit above
// least significant that disambiguates the rest. Strictly speaking
// it's a 10-bit field, but the mnemonics for many instructions treat
// it as a 9-bit field with a flag at the top.
//
// I've decided to hew directly to the mnemonics.
//
// Various opcodes in the 1995 documentation define reserved bits,
// which are given the nominal value of 0. It does not give a formal
// definition of a reserved bit. As a result this code does not
// currently check the value of reserved bits. That may need to change
// if/when I add support for extended instruction sets.
#define Bind(mask, operation) case mask: return Instruction(Operation::operation, opcode);
#define BindSupervisor(mask, operation) case mask: return Instruction(Operation::operation, opcode, true);
#define BindConditional(condition, mask, operation) \
case mask: \
if(condition()) return Instruction(Operation::operation, opcode); \
return Instruction(opcode);
#define BindSupervisorConditional(condition, mask, operation) \
case mask: \
if(condition()) return Instruction(Operation::operation, opcode, true); \
return Instruction(opcode);
#define Six(x) (unsigned(x) << 26)
#define SixTen(x, y) (Six(x) | ((y) << 1))
// First pass: weed out all those instructions identified entirely by the
// top six bits.
switch(opcode & Six(0b111111)) {
default: break;
BindConditional(is64bit, Six(0b000010), tdi);
Bind(Six(0b000011), twi);
Bind(Six(0b000111), mulli);
Bind(Six(0b001000), subfic);
Bind(Six(0b001100), addic); Bind(Six(0b001101), addic_);
Bind(Six(0b001110), addi); Bind(Six(0b001111), addis);
case Six(0b010000): {
// This might be a bcx, but check for a valid bo field.
switch((opcode >> 21) & 0x1f) {
case 0: case 1: case 2: case 3: case 4: case 5:
case 8: case 9: case 10: case 11: case 12: case 13:
case 16: case 17: case 18: case 19: case 20:
return Instruction(Operation::bcx, opcode);
default: return Instruction(opcode);
}
} break;
Bind(Six(0b010010), bx);
Bind(Six(0b010100), rlwimix);
Bind(Six(0b010101), rlwinmx);
Bind(Six(0b010111), rlwnmx);
Bind(Six(0b011000), ori); Bind(Six(0b011001), oris);
Bind(Six(0b011010), xori); Bind(Six(0b011011), xoris);
Bind(Six(0b011100), andi_); Bind(Six(0b011101), andis_);
Bind(Six(0b100000), lwz); Bind(Six(0b100001), lwzu);
Bind(Six(0b100010), lbz); Bind(Six(0b100011), lbzu);
Bind(Six(0b100100), stw); Bind(Six(0b100101), stwu);
Bind(Six(0b100110), stb); Bind(Six(0b100111), stbu);
Bind(Six(0b101000), lhz); Bind(Six(0b101001), lhzu);
Bind(Six(0b101010), lha); Bind(Six(0b101011), lhau);
Bind(Six(0b101100), sth); Bind(Six(0b101101), sthu);
Bind(Six(0b101110), lmw); Bind(Six(0b101111), stmw);
Bind(Six(0b110000), lfs); Bind(Six(0b110001), lfsu);
Bind(Six(0b110010), lfd); Bind(Six(0b110011), lfdu);
Bind(Six(0b110100), stfs); Bind(Six(0b110101), stfsu);
Bind(Six(0b110110), stfd); Bind(Six(0b110111), stfdu);
BindConditional(is601, Six(9), dozi);
BindConditional(is601, Six(22), rlmix);
Bind(Six(0b001010), cmpli); Bind(Six(0b001011), cmpi);
}
// Second pass: all those with a top six bits and a bottom nine or ten.
switch(opcode & SixTen(0b111111, 0b1111111111)) {
default: break;
// 64-bit instructions.
BindConditional(is64bit, SixTen(0b011111, 0b0000001001), mulhdux); BindConditional(is64bit, SixTen(0b011111, 0b1000001001), mulhdux);
BindConditional(is64bit, SixTen(0b011111, 0b0000010101), ldx);
BindConditional(is64bit, SixTen(0b011111, 0b0000011011), sldx);
BindConditional(is64bit, SixTen(0b011111, 0b0000110101), ldux);
BindConditional(is64bit, SixTen(0b011111, 0b0000111010), cntlzdx);
BindConditional(is64bit, SixTen(0b011111, 0b0001000100), td);
BindConditional(is64bit, SixTen(0b011111, 0b0001001001), mulhdx); BindConditional(is64bit, SixTen(0b011111, 0b1001001001), mulhdx);
BindConditional(is64bit, SixTen(0b011111, 0b0001010100), ldarx);
BindConditional(is64bit, SixTen(0b011111, 0b0010010101), stdx);
BindConditional(is64bit, SixTen(0b011111, 0b0010110101), stdux);
BindConditional(is64bit, SixTen(0b011111, 0b0011101001), mulld); BindConditional(is64bit, SixTen(0b011111, 0b1011101001), mulld);
BindConditional(is64bit, SixTen(0b011111, 0b0101010101), lwax);
BindConditional(is64bit, SixTen(0b011111, 0b0101110101), lwaux);
BindConditional(is64bit, SixTen(0b011111, 0b1100111011), sradix); BindConditional(is64bit, SixTen(0b011111, 0b1100111010), sradix);
BindConditional(is64bit, SixTen(0b011111, 0b0110110010), slbie);
BindConditional(is64bit, SixTen(0b011111, 0b0111001001), divdux); BindConditional(is64bit, SixTen(0b011111, 0b1111001001), divdux);
BindConditional(is64bit, SixTen(0b011111, 0b0111101001), divdx); BindConditional(is64bit, SixTen(0b011111, 0b1111101001), divdx);
BindConditional(is64bit, SixTen(0b011111, 0b1000011011), srdx);
BindConditional(is64bit, SixTen(0b011111, 0b1100011010), sradx);
BindConditional(is64bit, SixTen(0b111111, 0b1111011010), extsw);
// Power instructions; these are all taken from the MPC601 manual rather than
// the PowerPC Programmer's Reference Guide, hence the decimal encoding of the
// ten-bit field.
BindConditional(is601, SixTen(0b011111, 360), absx); BindConditional(is601, SixTen(0b011111, 512 + 360), absx);
BindConditional(is601, SixTen(0b011111, 531), clcs);
BindConditional(is601, SixTen(0b011111, 331), divx); BindConditional(is601, SixTen(0b011111, 512 + 331), divx);
BindConditional(is601, SixTen(0b011111, 363), divsx); BindConditional(is601, SixTen(0b011111, 512 + 363), divsx);
BindConditional(is601, SixTen(0b011111, 264), dozx); BindConditional(is601, SixTen(0b011111, 512 + 264), dozx);
BindConditional(is601, SixTen(0b011111, 277), lscbxx);
BindConditional(is601, SixTen(0b011111, 29), maskgx);
BindConditional(is601, SixTen(0b011111, 541), maskirx);
BindConditional(is601, SixTen(0b011111, 107), mulx); BindConditional(is601, SixTen(0b011111, 512 + 107), mulx);
BindConditional(is601, SixTen(0b011111, 488), nabsx); BindConditional(is601, SixTen(0b011111, 512 + 488), nabsx);
BindConditional(is601, SixTen(0b011111, 537), rribx);
BindConditional(is601, SixTen(0b011111, 153), slex);
BindConditional(is601, SixTen(0b011111, 217), sleqx);
BindConditional(is601, SixTen(0b011111, 184), sliqx);
BindConditional(is601, SixTen(0b011111, 248), slliqx);
BindConditional(is601, SixTen(0b011111, 216), sllqx);
BindConditional(is601, SixTen(0b011111, 152), slqx);
BindConditional(is601, SixTen(0b011111, 952), sraiqx);
BindConditional(is601, SixTen(0b011111, 920), sraqx);
BindConditional(is601, SixTen(0b011111, 665), srex);
BindConditional(is601, SixTen(0b011111, 921), sreax);
BindConditional(is601, SixTen(0b011111, 729), sreqx);
BindConditional(is601, SixTen(0b011111, 696), sriqx);
BindConditional(is601, SixTen(0b011111, 760), srliqx);
BindConditional(is601, SixTen(0b011111, 728), srlqx);
BindConditional(is601, SixTen(0b011111, 664), srqx);
// 32-bit instructions.
Bind(SixTen(0b010011, 0b0000000000), mcrf);
Bind(SixTen(0b010011, 0b0000010000), bclrx);
Bind(SixTen(0b010011, 0b0000100001), crnor);
Bind(SixTen(0b010011, 0b0000110010), rfi);
Bind(SixTen(0b010011, 0b0010000001), crandc);
Bind(SixTen(0b010011, 0b0010010110), isync);
Bind(SixTen(0b010011, 0b0011000001), crxor);
Bind(SixTen(0b010011, 0b0011100001), crnand);
Bind(SixTen(0b010011, 0b0100000001), crand);
Bind(SixTen(0b010011, 0b0100100001), creqv);
Bind(SixTen(0b010011, 0b0110100001), crorc);
Bind(SixTen(0b010011, 0b0111000001), cror);
Bind(SixTen(0b010011, 0b1000010000), bcctrx);
Bind(SixTen(0b011111, 0b0000000000), cmp);
Bind(SixTen(0b011111, 0b0000000100), tw);
Bind(SixTen(0b011111, 0b0000001000), subfcx); Bind(SixTen(0b011111, 0b1000001000), subfcx);
Bind(SixTen(0b011111, 0b0000001010), addcx); Bind(SixTen(0b011111, 0b1000001010), addcx);
Bind(SixTen(0b011111, 0b0000001011), mulhwux); Bind(SixTen(0b011111, 0b1000001011), mulhwux);
Bind(SixTen(0b011111, 0b0000010011), mfcr);
Bind(SixTen(0b011111, 0b0000010100), lwarx);
Bind(SixTen(0b011111, 0b0000010111), lwzx);
Bind(SixTen(0b011111, 0b0000011000), slwx);
Bind(SixTen(0b011111, 0b0000011010), cntlzwx);
Bind(SixTen(0b011111, 0b0000011100), andx);
Bind(SixTen(0b011111, 0b0000100000), cmpl);
Bind(SixTen(0b011111, 0b0000101000), subfx); Bind(SixTen(0b011111, 0b1000101000), subfx);
Bind(SixTen(0b011111, 0b0000110110), dcbst);
Bind(SixTen(0b011111, 0b0000110111), lwzux);
Bind(SixTen(0b011111, 0b0000111100), andcx);
Bind(SixTen(0b011111, 0b0001001011), mulhwx); Bind(SixTen(0b011111, 0b1001001011), mulhwx);
Bind(SixTen(0b011111, 0b0001010011), mfmsr);
Bind(SixTen(0b011111, 0b0001010110), dcbf);
Bind(SixTen(0b011111, 0b0001010111), lbzx);
Bind(SixTen(0b011111, 0b0001101000), negx); Bind(SixTen(0b011111, 0b1001101000), negx);
Bind(SixTen(0b011111, 0b0001110111), lbzux);
Bind(SixTen(0b011111, 0b0001111100), norx);
Bind(SixTen(0b011111, 0b0010001000), subfex); Bind(SixTen(0b011111, 0b1010001000), subfex);
Bind(SixTen(0b011111, 0b0010001010), addex); Bind(SixTen(0b011111, 0b1010001010), addex);
Bind(SixTen(0b011111, 0b0010010000), mtcrf);
Bind(SixTen(0b011111, 0b0010010010), mtmsr);
Bind(SixTen(0b011111, 0b0010010111), stwx);
Bind(SixTen(0b011111, 0b0010110111), stwux);
Bind(SixTen(0b011111, 0b0011001000), subfzex); Bind(SixTen(0b011111, 0b1011001000), subfzex);
Bind(SixTen(0b011111, 0b0011001010), addzex); Bind(SixTen(0b011111, 0b1011001010), addzex);
Bind(SixTen(0b011111, 0b0011010111), stbx);
Bind(SixTen(0b011111, 0b0011101000), subfmex); Bind(SixTen(0b011111, 0b1011101000), subfmex);
Bind(SixTen(0b011111, 0b0011101010), addmex); Bind(SixTen(0b011111, 0b1011101010), addmex);
Bind(SixTen(0b011111, 0b0011101011), mullwx); Bind(SixTen(0b011111, 0b1011101011), mullwx);
Bind(SixTen(0b011111, 0b0011110110), dcbtst);
Bind(SixTen(0b011111, 0b0011110111), stbux);
Bind(SixTen(0b011111, 0b0100001010), addx); Bind(SixTen(0b011111, 0b1100001010), addx);
Bind(SixTen(0b011111, 0b0100010110), dcbt);
Bind(SixTen(0b011111, 0b0100010111), lhzx);
Bind(SixTen(0b011111, 0b0100011100), eqvx);
Bind(SixTen(0b011111, 0b0100110110), eciwx);
Bind(SixTen(0b011111, 0b0100110111), lhzux);
Bind(SixTen(0b011111, 0b0100111100), xorx);
Bind(SixTen(0b011111, 0b0101010111), lhax);
Bind(SixTen(0b011111, 0b0101110011), mftb);
Bind(SixTen(0b011111, 0b0101110111), lhaux);
Bind(SixTen(0b011111, 0b0110010111), sthx);
Bind(SixTen(0b011111, 0b0110011100), orcx);
Bind(SixTen(0b011111, 0b0110110110), ecowx);
Bind(SixTen(0b011111, 0b0110110111), sthux);
Bind(SixTen(0b011111, 0b0110111100), orx);
Bind(SixTen(0b011111, 0b0111001011), divwux); Bind(SixTen(0b011111, 0b1111001011), divwux);
Bind(SixTen(0b011111, 0b0111010110), dcbi);
Bind(SixTen(0b011111, 0b0111011100), nandx);
Bind(SixTen(0b011111, 0b0111101011), divwx); Bind(SixTen(0b011111, 0b1111101011), divwx);
Bind(SixTen(0b011111, 0b1000000000), mcrxr);
Bind(SixTen(0b011111, 0b1000010101), lswx);
Bind(SixTen(0b011111, 0b1000010110), lwbrx);
Bind(SixTen(0b011111, 0b1000010111), lfsx);
Bind(SixTen(0b011111, 0b1000011000), srwx);
Bind(SixTen(0b011111, 0b1000110111), lfsux);
Bind(SixTen(0b011111, 0b1001010101), lswi);
Bind(SixTen(0b011111, 0b1001010110), sync);
Bind(SixTen(0b011111, 0b1001010111), lfdx);
Bind(SixTen(0b011111, 0b1001110111), lfdux);
Bind(SixTen(0b011111, 0b1010010101), stswx);
Bind(SixTen(0b011111, 0b1010010110), stwbrx);
Bind(SixTen(0b011111, 0b1010010111), stfsx);
Bind(SixTen(0b011111, 0b1010110111), stfsux);
Bind(SixTen(0b011111, 0b1011010101), stswi);
Bind(SixTen(0b011111, 0b1011010111), stfdx);
Bind(SixTen(0b011111, 0b1011110111), stfdux);
Bind(SixTen(0b011111, 0b1100010110), lhbrx);
Bind(SixTen(0b011111, 0b1100011000), srawx);
Bind(SixTen(0b011111, 0b1100111000), srawix);
Bind(SixTen(0b011111, 0b1101010110), eieio);
Bind(SixTen(0b011111, 0b1110010110), sthbrx);
Bind(SixTen(0b011111, 0b1110011010), extshx);
Bind(SixTen(0b011111, 0b1110111010), extsbx);
Bind(SixTen(0b011111, 0b1111010110), icbi);
Bind(SixTen(0b011111, 0b1111010111), stfiwx);
Bind(SixTen(0b011111, 0b1111110110), dcbz);
Bind(SixTen(0b111111, 0b0000000000), fcmpu);
Bind(SixTen(0b111111, 0b0000001100), frspx);
Bind(SixTen(0b111111, 0b0000001110), fctiwx);
Bind(SixTen(0b111111, 0b0000001111), fctiwzx);
Bind(SixTen(0b111111, 0b0000100000), fcmpo);
Bind(SixTen(0b111111, 0b0000100110), mtfsb1x);
Bind(SixTen(0b111111, 0b0000101000), fnegx);
Bind(SixTen(0b111111, 0b0001000000), mcrfs);
Bind(SixTen(0b111111, 0b0001000110), mtfsb0x);
Bind(SixTen(0b111111, 0b0001001000), fmrx);
Bind(SixTen(0b111111, 0b0010000110), mtfsfix);
Bind(SixTen(0b111111, 0b0010001000), fnabsx);
Bind(SixTen(0b111111, 0b0100001000), fabsx);
Bind(SixTen(0b111111, 0b1001000111), mffsx);
Bind(SixTen(0b111111, 0b1011000111), mtfsfx);
Bind(SixTen(0b111111, 0b1100101110), fctidx);
Bind(SixTen(0b111111, 0b1100101111), fctidzx);
Bind(SixTen(0b111111, 0b1101001110), fcfidx);
Bind(SixTen(0b011111, 0b0101010011), mfspr); // Flagged as "supervisor and user"?
Bind(SixTen(0b011111, 0b0111010011), mtspr); // Flagged as "supervisor and user"?
BindSupervisorConditional(is32bit, SixTen(0b011111, 0b0011010010), mtsr);
BindSupervisorConditional(is32bit, SixTen(0b011111, 0b0011110010), mtsrin);
BindSupervisorConditional(is32bit, SixTen(0b011111, 0b1001010011), mfsr);
BindSupervisorConditional(is32bit, SixTen(0b011111, 0b1010010011), mfsrin);
BindSupervisorConditional(is64bit, SixTen(0b011111, 0b0111110010), slbia); // optional
// The following are all optional; should I record that?
BindSupervisor(SixTen(0b011111, 0b0100110010), tlbie);
BindSupervisor(SixTen(0b011111, 0b0101110010), tlbia);
BindSupervisor(SixTen(0b011111, 0b1000110110), tlbsync);
}
// Third pass: like six-ten except that the top five of the final ten
// are reserved (i.e. ignored here).
switch(opcode & SixTen(0b111111, 0b11111)) {
default: break;
Bind(SixTen(0b111011, 0b10010), fdivsx);
Bind(SixTen(0b111011, 0b10100), fsubsx);
Bind(SixTen(0b111011, 0b10101), faddsx);
Bind(SixTen(0b111011, 0b11001), fmulsx);
Bind(SixTen(0b111011, 0b11100), fmsubsx);
Bind(SixTen(0b111011, 0b11101), fmaddsx);
Bind(SixTen(0b111011, 0b11110), fnmsubsx);
Bind(SixTen(0b111011, 0b11111), fnmaddsx);
Bind(SixTen(0b111111, 0b10010), fdivx);
Bind(SixTen(0b111111, 0b10100), fsubx);
Bind(SixTen(0b111111, 0b10101), faddx);
Bind(SixTen(0b111111, 0b11001), fmulx);
Bind(SixTen(0b111111, 0b11100), fmsubx);
Bind(SixTen(0b111111, 0b11101), fmaddx);
Bind(SixTen(0b111111, 0b11110), fnmsubx);
Bind(SixTen(0b111111, 0b11111), fnmaddx);
BindConditional(is64bit, SixTen(0b111011, 0b10110), fsqrtsx);
BindConditional(is64bit, SixTen(0b111011, 0b11000), fresx);
// Optional...
Bind(SixTen(0b111111, 0b10110), fsqrtx);
Bind(SixTen(0b111111, 0b10111), fselx);
Bind(SixTen(0b111111, 0b11010), frsqrtex);
}
// stwcx. and stdcx.
switch(opcode & 0b111111'00'00000000'000'111111111'1){
case 0b011111'00'00000000'00000'0010010110'1: return Instruction(Operation::stwcx_, opcode);
case 0b011111'00'00000000'00000'0011010110'1:
if(is64bit()) return Instruction(Operation::stdcx_, opcode);
return Instruction(opcode);
}
// std and stdu
switch(opcode & 0b111111'00'00000000'00000000'000000'11){
case 0b111110'00'00000000'00000000'000000'00: return Instruction(Operation::std, opcode);
case 0b111110'00'00000000'00000000'000000'01:
if(is64bit()) return Instruction(Operation::stdu, opcode);
return Instruction(opcode);
}
// sc
if((opcode & 0b111111'00'00000000'00000000'000000'1'0) == 0b010001'00'00000000'00000000'000000'1'0) {
return Instruction(Operation::sc, opcode);
}
#undef Six
#undef SixTen
#undef Bind
#undef BindConditional
return Instruction(opcode);
}

View File

@@ -0,0 +1,58 @@
//
// Decoder.hpp
// Clock Signal
//
// Created by Thomas Harte on 12/30/20.
// Copyright © 2020 Thomas Harte. All rights reserved.
//
#ifndef InstructionSets_PowerPC_Decoder_hpp
#define InstructionSets_PowerPC_Decoder_hpp
#include "Instruction.hpp"
namespace CPU {
namespace Decoder {
namespace PowerPC {
enum class Model {
/// i.e. 32-bit, with POWER carry-over instructions.
MPC601,
/// i.e. 32-bit, no POWER instructions.
MPC603,
/// i.e. 64-bit.
MPC620,
};
/*!
Implements PowerPC instruction decoding.
This is an experimental implementation; it has not yet undergone significant testing.
*/
struct Decoder {
public:
Decoder(Model model);
Instruction decode(uint32_t opcode);
private:
Model model_;
bool is64bit() const {
return model_ == Model::MPC620;
}
bool is32bit() const {
return !is64bit();
}
bool is601() const {
return model_ == Model::MPC601;
}
};
}
}
}
#endif /* InstructionSets_PowerPC_Decoder_hpp */

View File

@@ -0,0 +1,192 @@
//
// Instruction.hpp
// Clock Signal
//
// Created by Thomas Harte on 1/15/21.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef InstructionSets_PowerPC_Instruction_h
#define InstructionSets_PowerPC_Instruction_h
#include <cstdint>
namespace CPU {
namespace Decoder {
namespace PowerPC {
enum class Operation: uint8_t {
Undefined,
// These 601-exclusive instructions; a lot of them are carry-overs
// from POWER.
absx, clcs, divx, divsx, dozx, dozi, lscbxx, maskgx, maskirx, mulx,
nabsx, rlmix, rribx, slex, sleqx, sliqx, slliqx, sllqx, slqx,
sraiqx, sraqx, srex, sreax, sreqx, sriqx, srliqx, srlqx, srqx,
// 32- and 64-bit PowerPC instructions.
addx, addcx, addex, addi, addic, addic_, addis, addmex, addzex, andx,
andcx, andi_, andis_, bx, bcx, bcctrx, bclrx, cmp, cmpi, cmpl, cmpli,
cntlzwx, crand, crandc, creqv, crnand, crnor, cror, crorc, crxor, dcbf,
dcbst, dcbt, dcbtst, dcbz, divwx, divwux, eciwx, ecowx, eieio, eqvx,
extsbx, extshx, fabsx, faddx, faddsx, fcmpo, fcmpu, fctiwx, fctiwzx,
fdivx, fdivsx, fmaddx, fmaddsx, fmrx, fmsubx, fmsubsx, fmulx, fmulsx,
fnabsx, fnegx, fnmaddx, fnmaddsx, fnmsubx, fnmsubsx, frspx, fsubx, fsubsx,
icbi, isync, lbz, lbzu, lbzux, lbzx, lfd, lfdu, lfdux, lfdx, lfs, lfsu,
lfsux, lfsx, lha, lhau, lhaux, lhax, lhbrx, lhz, lhzu, lhzux, lhzx, lmw,
lswi, lswx, lwarx, lwbrx, lwz, lwzu, lwzux, lwzx, mcrf, mcrfs, mcrxr,
mfcr, mffsx, mfmsr, mfspr, mfsr, mfsrin, mtcrf, mtfsb0x, mtfsb1x, mtfsfx,
mtfsfix, mtmsr, mtspr, mtsr, mtsrin, mulhwx, mulhwux, mulli, mullwx,
nandx, negx, norx, orx, orcx, ori, oris, rfi, rlwimix, rlwinmx, rlwnmx,
sc, slwx, srawx, srawix, srwx, stb, stbu, stbux, stbx, stfd, stfdu,
stfdux, stfdx, stfs, stfsu, stfsux, stfsx, sth, sthbrx, sthu, sthux, sthx,
stmw, stswi, stswx, stw, stwbrx, stwcx_, stwu, stwux, stwx, subfx, subfcx,
subfex, subfic, subfmex, subfzex, sync, tw, twi, xorx, xori, xoris, mftb,
// 32-bit, supervisor level.
dcbi,
// Supervisor, optional.
tlbia, tlbie, tlbsync,
// Optional.
fresx, frsqrtex, fselx, fsqrtx, slbia, slbie, stfiwx,
// 64-bit only PowerPC instructions.
cntlzdx, divdx, divdux, extswx, fcfidx, fctidx, fctidzx, tdi, mulhdux,
ldx, sldx, ldux, td, mulhdx, ldarx, stdx, stdux, mulld, lwax, lwaux,
sradix, srdx, sradx, extsw, fsqrtsx, std, stdu, stdcx_,
};
/*!
Holds a decoded PowerPC instruction.
Implementation note: because the PowerPC encoding is particularly straightforward,
only the operation has been decoded ahead of time; all other fields are decoded on-demand.
It would be possible to partition the ordering of Operations into user followed by supervisor,
eliminating the storage necessary for a flag, but it wouldn't save anything due to alignment.
*/
struct Instruction {
Operation operation = Operation::Undefined;
bool is_supervisor = false;
uint32_t opcode = 0;
Instruction() noexcept {}
Instruction(uint32_t opcode) noexcept : opcode(opcode) {}
Instruction(Operation operation, uint32_t opcode, bool is_supervisor = false) noexcept : operation(operation), is_supervisor(is_supervisor), opcode(opcode) {}
// Instruction fields are decoded below; naming is a compromise between
// Motorola's documentation and IBM's.
//
// I've dutifully implemented various synonyms with unique entry points,
// in order to capture that information here rather than thrusting it upon
// the reader of whatever implementation may follow.
// Currently omitted: OPCD and XO, which I think are unnecessary given that
// full decoding has already occurred.
/// Immediate field used to specify an unsigned 16-bit integer.
uint16_t uimm() const { return uint16_t(opcode & 0xffff); }
/// Immediate field used to specify a signed 16-bit integer.
int16_t simm() const { return int16_t(opcode & 0xffff); }
/// Immediate field used to specify a signed 16-bit integer.
int16_t d() const { return int16_t(opcode & 0xffff); }
/// Immediate field used to specify a signed 14-bit integer [64-bit only].
int16_t ds() const { return int16_t(opcode & 0xfffc); }
/// Immediate field used as data to be placed into a field in the floating point status and condition register.
int32_t imm() const { return (opcode >> 12) & 0xf; }
/// Specifies the conditions on which to trap.
int32_t to() const { return (opcode >> 21) & 0x1f; }
/// Register source A or destination.
uint32_t rA() const { return (opcode >> 16) & 0x1f; }
/// Register source B.
uint32_t rB() const { return (opcode >> 11) & 0x1f; }
/// Register destination.
uint32_t rD() const { return (opcode >> 21) & 0x1f; }
/// Register source.
uint32_t rS() const { return (opcode >> 21) & 0x1f; }
/// Floating point register source A.
uint32_t frA() const { return (opcode >> 16) & 0x1f; }
/// Floating point register source B.
uint32_t frB() const { return (opcode >> 11) & 0x1f; }
/// Floating point register source C.
uint32_t frC() const { return (opcode >> 6) & 0x1f; }
/// Floating point register source.
uint32_t frS() const { return (opcode >> 21) & 0x1f; }
/// Floating point register destination.
uint32_t frD() const { return (opcode >> 21) & 0x1f; }
/// Branch conditional options.
uint32_t bo() const { return (opcode >> 21) & 0x1f; }
/// Source condition register bit for branch conditionals.
uint32_t bi() const { return (opcode >> 16) & 0x1f; }
/// Branch displacement; provided as already sign extended.
int16_t bd() const { return int16_t(opcode & 0xfffc); }
/// Specifies the first 1 bit of a 32/64-bit mask for rotate operations.
uint32_t mb() const { return (opcode >> 6) & 0x1f; }
/// Specifies the first 1 bit of a 32/64-bit mask for rotate operations.
uint32_t me() const { return (opcode >> 1) & 0x1f; }
/// Condition register source bit A.
uint32_t crbA() const { return (opcode >> 16) & 0x1f; }
/// Condition register source bit B.
uint32_t crbB() const { return (opcode >> 11) & 0x1f; }
/// Condition register (or floating point status & condition register) destination bit.
uint32_t crbD() const { return (opcode >> 21) & 0x1f; }
/// Condition register (or floating point status & condition register) destination field.
uint32_t crfD() const { return (opcode >> 23) & 0x07; }
/// Condition register (or floating point status & condition register) source field.
uint32_t crfS() const { return (opcode >> 18) & 0x07; }
/// Mask identifying fields to be updated by mtcrf.
uint32_t crm() const { return (opcode >> 12) & 0xff; }
/// Mask identifying fields to be updated by mtfsf.
uint32_t fm() const { return (opcode >> 17) & 0xff; }
/// Specifies the number of bytes to move in an immediate string load or store.
uint32_t nb() const { return (opcode >> 11) & 0x1f; }
/// Specifies a shift amount.
/// TODO: possibly bit 30 is also used in 64-bit mode, find out.
uint32_t sh() const { return (opcode >> 11) & 0x1f; }
/// Specifies one of the 16 segment registers [32-bit only].
uint32_t sr() const { return (opcode >> 16) & 0xf; }
/// A 24-bit signed number; provided as already sign extended.
int32_t li() const {
constexpr uint32_t extensions[2] = {
0x0000'0000,
0xfc00'0000
};
const uint32_t value = (opcode & 0x03ff'fffc) | extensions[(opcode >> 25) & 1];
return int32_t(value);
}
/// Absolute address bit; @c 0 or @c non-0.
uint32_t aa() const { return opcode & 0x02; }
/// Link bit; @c 0 or @c non-0.
uint32_t lk() const { return opcode & 0x01; }
/// Record bit; @c 0 or @c non-0.
uint32_t rc() const { return opcode & 0x01; }
/// Whether to compare 32-bit or 64-bit numbers [for 64-bit implementations only]; @c 0 or @c non-0.
uint32_t l() const { return opcode & 0x200000; }
/// Enables setting of OV and SO in the XER; @c 0 or @c non-0.
uint32_t oe() const { return opcode & 0x800; }
};
// Sanity check on Instruction size.
static_assert(sizeof(Instruction) <= 8);
}
}
}
#endif /* InstructionSets_PowerPC_Instruction_h */

54
InstructionSets/README.md Normal file
View File

@@ -0,0 +1,54 @@
# Instruction Sets
## Decoders
A decoder extracts fully-decoded instructions from a data stream for its associated architecture.
The meaning of 'fully-decoded' is flexible but it means that a caller can easily discern at least:
* the operation in use;
* its addressing mode; and
* relevant registers.
It may be assumed that callers will have access to the original data stream for immediate values, if it is sensible to do so.
In deciding what to expose, what to store ahead of time and what to obtain just-in-time a decoder should have an eye on two principal consumers:
1. disassemblers; and
2. instruction executors.
It may also be reasonable to make allowances for bus-centric CPU emulators, but those will be tightly coupled to specific decoders so no general rules need apply.
Disassemblers are likely to decode an instruction, output it, and then immediately forget about it.
Instruction executors may opt to cache decoded instructions to reduce recurrent costs, but will always be dealing with an actual instruction stream. The chance of caching means that decoded instructions should seek to be small. If helpful then a decoder might prefer to return a `std::pair` or similar of ephemeral information and stuff that it is meaningful to store.
## Likely Interfaces
These examples assume that the processor itself doesn't hold any state that affects instruction parsing. Whether processors with such state offer more than one decoder or take state as an argument will be a question of measure and effect.
### Fixed-size instruction words
If the instructions are a fixed size, the decoder can provide what is functionally a simple lookup, whether implemented as such or not:
Instruction decode(word_type instruction) { ... }
For now I have preferred not to make this a simple constructor on `Instruction` because I'm reserving the option of switching to an ephemeral/permanent split in what's returned. More consideration needs to be applied here.
### Variable-size instruction words
If instructions are a variable size, the decoder should maintain internal state such that it can be provided with fragments of instructions until a full decoding has occurred — this avoids an assumption that all source bytes will always be laid out linearly in memory.
A sample interface:
std::pair<int, Instruction> decode(word_type *stream, size_t length) { ... }
In this sample the returned pair provides an `int` size that is one of:
* a positive number, indicating a completed decoding that consumed that many `word_type`s; or
* a negative number, indicating the [negatived] minimum number of `word_type`s that the caller should try to get hold of before calling `decode` again.
A caller is permitted to react in any way it prefers to negative numbers; they're a hint potentially to reduce calling overhead only. A size of `0` would be taken to have the same meaning as a size of `-1`.
## Tying Decoders into Instruction Executors
It is assumed that disassemblers and bus-centric CPU emulators have limited generic functionality; for executors it is assumed that a processor-specific instruction fetcher and a dispatcher will be provided to couple with the decoder.
Therefore decoders should adopt whatever interface is most natural; the expected uses information above is to provide a motivation for the scope of responsibilities and hints as to likely performance objectives only. Beyond requiring that decoded instructions be a tangible struct or class, it is not intended to be prescriptive as to form or interface.

View File

@@ -0,0 +1,625 @@
//
// x86.cpp
// Clock Signal
//
// Created by Thomas Harte on 1/1/21.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "Decoder.hpp"
#include <algorithm>
#include <cassert>
#include <utility>
using namespace CPU::Decoder::x86;
// Only 8086 is suppoted for now.
Decoder::Decoder(Model) {}
std::pair<int, CPU::Decoder::x86::Instruction> Decoder::decode(const uint8_t *source, size_t length) {
const uint8_t *const end = source + length;
// MARK: - Prefixes (if present) and the opcode.
/// Helper macro for those that follow.
#define SetOpSrcDestSize(op, src, dest, size) \
operation_ = Operation::op; \
source_ = Source::src; \
destination_ = Source::dest; \
operation_size_ = size
/// Covers anything which is complete as soon as the opcode is encountered.
#define Complete(op, src, dest, size) \
SetOpSrcDestSize(op, src, dest, size); \
phase_ = Phase::ReadyToPost
/// Handles instructions of the form rr, kk and rr, jjkk, i.e. a destination register plus an operand.
#define RegData(op, dest, size) \
SetOpSrcDestSize(op, DirectAddress, dest, size); \
source_ = Source::Immediate; \
operand_size_ = size; \
phase_ = Phase::AwaitingDisplacementOrOperand
/// Handles instructions of the form Ax, jjkk where the latter is implicitly an address.
#define RegAddr(op, dest, op_size, addr_size) \
SetOpSrcDestSize(op, DirectAddress, dest, op_size); \
operand_size_ = addr_size; \
phase_ = Phase::AwaitingDisplacementOrOperand
/// Handles instructions of the form jjkk, Ax where the former is implicitly an address.
#define AddrReg(op, source, op_size, addr_size) \
SetOpSrcDestSize(op, source, DirectAddress, op_size); \
operand_size_ = addr_size; \
destination_ = Source::DirectAddress; \
phase_ = Phase::AwaitingDisplacementOrOperand
/// Covers both `mem/reg, reg` and `reg, mem/reg`.
#define MemRegReg(op, format, size) \
operation_ = Operation::op; \
phase_ = Phase::ModRegRM; \
modregrm_format_ = ModRegRMFormat::format; \
operand_size_ = 0; \
operation_size_ = size
/// Handles JO, JNO, JB, etc — jumps with a single byte displacement.
#define Jump(op) \
operation_ = Operation::op; \
phase_ = Phase::AwaitingDisplacementOrOperand; \
displacement_size_ = 1
/// Handles far CALL and far JMP — fixed four byte operand operations.
#define Far(op) \
operation_ = Operation::op; \
phase_ = Phase::AwaitingDisplacementOrOperand; \
operand_size_ = 4; \
while(phase_ == Phase::Instruction && source != end) {
// Retain the instruction byte, in case additional decoding is deferred
// to the ModRegRM byte.
instr_ = *source;
++source;
++consumed_;
switch(instr_) {
default: {
const auto result = std::make_pair(consumed_, Instruction());
reset_parsing();
return result;
}
#define PartialBlock(start, operation) \
case start + 0x00: MemRegReg(operation, MemReg_Reg, 1); break; \
case start + 0x01: MemRegReg(operation, MemReg_Reg, 2); break; \
case start + 0x02: MemRegReg(operation, Reg_MemReg, 1); break; \
case start + 0x03: MemRegReg(operation, Reg_MemReg, 2); break; \
case start + 0x04: RegData(operation, AL, 1); break; \
case start + 0x05: RegData(operation, AX, 2)
PartialBlock(0x00, ADD); break;
case 0x06: Complete(PUSH, ES, None, 2); break;
case 0x07: Complete(POP, None, ES, 2); break;
PartialBlock(0x08, OR); break;
case 0x0e: Complete(PUSH, CS, None, 2); break;
PartialBlock(0x10, ADC); break;
case 0x16: Complete(PUSH, SS, None, 2); break;
case 0x17: Complete(POP, None, SS, 2); break;
PartialBlock(0x18, SBB); break;
case 0x1e: Complete(PUSH, DS, None, 2); break;
case 0x1f: Complete(POP, None, DS, 2); break;
PartialBlock(0x20, AND); break;
case 0x26: segment_override_ = Source::ES; break;
case 0x27: Complete(DAA, AL, AL, 1); break;
PartialBlock(0x28, SUB); break;
case 0x2e: segment_override_ = Source::CS; break;
case 0x2f: Complete(DAS, AL, AL, 1); break;
PartialBlock(0x30, XOR); break;
case 0x36: segment_override_ = Source::SS; break;
case 0x37: Complete(AAA, AL, AX, 1); break;
PartialBlock(0x38, CMP); break;
case 0x3e: segment_override_ = Source::DS; break;
case 0x3f: Complete(AAS, AL, AX, 1); break;
#undef PartialBlock
#define RegisterBlock(start, operation) \
case start + 0x00: Complete(operation, AX, AX, 2); break; \
case start + 0x01: Complete(operation, CX, CX, 2); break; \
case start + 0x02: Complete(operation, DX, DX, 2); break; \
case start + 0x03: Complete(operation, BX, BX, 2); break; \
case start + 0x04: Complete(operation, SP, SP, 2); break; \
case start + 0x05: Complete(operation, BP, BP, 2); break; \
case start + 0x06: Complete(operation, SI, SI, 2); break; \
case start + 0x07: Complete(operation, DI, DI, 2)
RegisterBlock(0x40, INC); break;
RegisterBlock(0x48, DEC); break;
RegisterBlock(0x50, PUSH); break;
RegisterBlock(0x58, POP); break;
#undef RegisterBlock
// 0x600x6f: not used.
case 0x70: Jump(JO); break;
case 0x71: Jump(JNO); break;
case 0x72: Jump(JB); break;
case 0x73: Jump(JNB); break;
case 0x74: Jump(JE); break;
case 0x75: Jump(JNE); break;
case 0x76: Jump(JBE); break;
case 0x77: Jump(JNBE); break;
case 0x78: Jump(JS); break;
case 0x79: Jump(JNS); break;
case 0x7a: Jump(JP); break;
case 0x7b: Jump(JNP); break;
case 0x7c: Jump(JL); break;
case 0x7d: Jump(JNL); break;
case 0x7e: Jump(JLE); break;
case 0x7f: Jump(JNLE); break;
case 0x80: MemRegReg(Invalid, MemRegADD_to_CMP, 1); break;
case 0x81: MemRegReg(Invalid, MemRegADD_to_CMP, 2); break;
case 0x82: MemRegReg(Invalid, MemRegADC_to_CMP, 1); break;
case 0x83: MemRegReg(Invalid, MemRegADC_to_CMP, 2); break;
case 0x84: MemRegReg(TEST, MemReg_Reg, 1); break;
case 0x85: MemRegReg(TEST, MemReg_Reg, 2); break;
case 0x86: MemRegReg(XCHG, Reg_MemReg, 1); break;
case 0x87: MemRegReg(XCHG, Reg_MemReg, 2); break;
case 0x88: MemRegReg(MOV, MemReg_Reg, 1); break;
case 0x89: MemRegReg(MOV, MemReg_Reg, 2); break;
case 0x8a: MemRegReg(MOV, Reg_MemReg, 1); break;
case 0x8b: MemRegReg(MOV, Reg_MemReg, 2); break;
// 0x8c: not used.
case 0x8d: MemRegReg(LEA, Reg_MemReg, 2); break;
case 0x8e: MemRegReg(MOV, SegReg, 2); break;
case 0x8f: MemRegReg(POP, MemRegPOP, 2); break;
case 0x90: Complete(NOP, None, None, 0); break; // Or XCHG AX, AX?
case 0x91: Complete(XCHG, AX, CX, 2); break;
case 0x92: Complete(XCHG, AX, DX, 2); break;
case 0x93: Complete(XCHG, AX, BX, 2); break;
case 0x94: Complete(XCHG, AX, SP, 2); break;
case 0x95: Complete(XCHG, AX, BP, 2); break;
case 0x96: Complete(XCHG, AX, SI, 2); break;
case 0x97: Complete(XCHG, AX, DI, 2); break;
case 0x98: Complete(CBW, AL, AH, 1); break;
case 0x99: Complete(CWD, AX, DX, 2); break;
case 0x9a: Far(CALLF); break;
case 0x9b: Complete(WAIT, None, None, 0); break;
case 0x9c: Complete(PUSHF, None, None, 2); break;
case 0x9d: Complete(POPF, None, None, 2); break;
case 0x9e: Complete(SAHF, None, None, 1); break;
case 0x9f: Complete(LAHF, None, None, 1); break;
case 0xa0: RegAddr(MOV, AL, 1, 1); break;
case 0xa1: RegAddr(MOV, AX, 2, 2); break;
case 0xa2: AddrReg(MOV, AL, 1, 1); break;
case 0xa3: AddrReg(MOV, AX, 2, 2); break;
case 0xa4: Complete(MOVS, None, None, 1); break;
case 0xa5: Complete(MOVS, None, None, 2); break;
case 0xa6: Complete(CMPS, None, None, 1); break;
case 0xa7: Complete(CMPS, None, None, 2); break;
case 0xa8: RegData(TEST, AL, 1); break;
case 0xa9: RegData(TEST, AX, 2); break;
case 0xaa: Complete(STOS, None, None, 1); break;
case 0xab: Complete(STOS, None, None, 2); break;
case 0xac: Complete(LODS, None, None, 1); break;
case 0xad: Complete(LODS, None, None, 2); break;
case 0xae: Complete(SCAS, None, None, 1); break;
case 0xaf: Complete(SCAS, None, None, 2); break;
case 0xb0: RegData(MOV, AL, 1); break;
case 0xb1: RegData(MOV, CL, 1); break;
case 0xb2: RegData(MOV, DL, 1); break;
case 0xb3: RegData(MOV, BL, 1); break;
case 0xb4: RegData(MOV, AH, 1); break;
case 0xb5: RegData(MOV, CH, 1); break;
case 0xb6: RegData(MOV, DH, 1); break;
case 0xb7: RegData(MOV, BH, 1); break;
case 0xb8: RegData(MOV, AX, 2); break;
case 0xb9: RegData(MOV, CX, 2); break;
case 0xba: RegData(MOV, DX, 2); break;
case 0xbb: RegData(MOV, BX, 2); break;
case 0xbc: RegData(MOV, SP, 2); break;
case 0xbd: RegData(MOV, BP, 2); break;
case 0xbe: RegData(MOV, SI, 2); break;
case 0xbf: RegData(MOV, DI, 2); break;
case 0xc2: RegData(RETN, None, 2); break;
case 0xc3: Complete(RETN, None, None, 2); break;
case 0xc4: MemRegReg(LES, Reg_MemReg, 2); break;
case 0xc5: MemRegReg(LDS, Reg_MemReg, 2); break;
case 0xc6: MemRegReg(MOV, MemRegMOV, 1); break;
case 0xc7: MemRegReg(MOV, MemRegMOV, 2); break;
case 0xca: RegData(RETF, None, 2); break;
case 0xcb: Complete(RETF, None, None, 4); break;
case 0xcc: Complete(INT3, None, None, 0); break;
case 0xcd: RegData(INT, None, 1); break;
case 0xce: Complete(INTO, None, None, 0); break;
case 0xcf: Complete(IRET, None, None, 0); break;
case 0xd0: case 0xd1:
phase_ = Phase::ModRegRM;
modregrm_format_ = ModRegRMFormat::MemRegROL_to_SAR;
operation_size_ = 1 + (instr_ & 1);
source_ = Source::Immediate;
operand_ = 1;
break;
case 0xd2: case 0xd3:
phase_ = Phase::ModRegRM;
modregrm_format_ = ModRegRMFormat::MemRegROL_to_SAR;
operation_size_ = 1 + (instr_ & 1);
source_ = Source::CL;
break;
case 0xd4: RegData(AAM, AX, 1); break;
case 0xd5: RegData(AAD, AX, 1); break;
case 0xd7: Complete(XLAT, None, None, 1); break;
case 0xd8: MemRegReg(ESC, MemReg_Reg, 0); break;
case 0xd9: MemRegReg(ESC, MemReg_Reg, 0); break;
case 0xda: MemRegReg(ESC, MemReg_Reg, 0); break;
case 0xdb: MemRegReg(ESC, MemReg_Reg, 0); break;
case 0xdc: MemRegReg(ESC, MemReg_Reg, 0); break;
case 0xdd: MemRegReg(ESC, MemReg_Reg, 0); break;
case 0xde: MemRegReg(ESC, MemReg_Reg, 0); break;
case 0xdf: MemRegReg(ESC, MemReg_Reg, 0); break;
case 0xe0: Jump(LOOPNE); break;
case 0xe1: Jump(LOOPE); break;
case 0xe2: Jump(LOOP); break;
case 0xe3: Jump(JPCX); break;
case 0xe4: RegAddr(IN, AL, 1, 1); break;
case 0xe5: RegAddr(IN, AX, 2, 1); break;
case 0xe6: AddrReg(OUT, AL, 1, 1); break;
case 0xe7: AddrReg(OUT, AX, 2, 1); break;
case 0xe8: RegData(CALLD, None, 2); break;
case 0xe9: RegData(JMPN, None, 2); break;
case 0xea: Far(JMPF); break;
case 0xeb: Jump(JMPN); break;
case 0xec: Complete(IN, DX, AL, 1); break;
case 0xed: Complete(IN, DX, AX, 1); break;
case 0xee: Complete(OUT, AL, DX, 1); break;
case 0xef: Complete(OUT, AX, DX, 2); break;
case 0xf4: Complete(HLT, None, None, 1); break;
case 0xf5: Complete(CMC, None, None, 1); break;
case 0xf6: MemRegReg(Invalid, MemRegTEST_to_IDIV, 1); break;
case 0xf7: MemRegReg(Invalid, MemRegTEST_to_IDIV, 2); break;
case 0xf8: Complete(CLC, None, None, 1); break;
case 0xf9: Complete(STC, None, None, 1); break;
case 0xfa: Complete(CLI, None, None, 1); break;
case 0xfb: Complete(STI, None, None, 1); break;
case 0xfc: Complete(CLD, None, None, 1); break;
case 0xfd: Complete(STD, None, None, 1); break;
case 0xfe: MemRegReg(Invalid, MemRegINC_DEC, 1); break;
case 0xff: MemRegReg(Invalid, MemRegINC_to_PUSH, 1); break;
// Other prefix bytes.
case 0xf0: lock_ = true; break;
case 0xf2: repetition_ = Repetition::RepNE; break;
case 0xf3: repetition_ = Repetition::RepE; break;
}
}
#undef Far
#undef Jump
#undef MemRegReg
#undef AddrReg
#undef RegAddr
#undef RegData
#undef Complete
#undef SetOpSrcDestSize
// MARK: - ModRegRM byte, if any.
if(phase_ == Phase::ModRegRM && source != end) {
const uint8_t mod = *source >> 6; // i.e. mode.
const uint8_t reg = (*source >> 3) & 7; // i.e. register.
const uint8_t rm = *source & 7; // i.e. register/memory.
++source;
++consumed_;
Source memreg;
constexpr Source reg_table[3][8] = {
{},
{
Source::AL, Source::CL, Source::DL, Source::BL,
Source::AH, Source::CH, Source::DH, Source::BH,
}, {
Source::AX, Source::CX, Source::DX, Source::BX,
Source::SP, Source::BP, Source::SI, Source::DI,
}
};
switch(mod) {
case 0: {
constexpr Source rm_table[8] = {
Source::IndBXPlusSI, Source::IndBXPlusDI,
Source::IndBPPlusSI, Source::IndBPPlusDI,
Source::IndSI, Source::IndDI,
Source::DirectAddress, Source::IndBX,
};
memreg = rm_table[rm];
} break;
default: {
constexpr Source rm_table[8] = {
Source::IndBXPlusSI, Source::IndBXPlusDI,
Source::IndBPPlusSI, Source::IndBPPlusDI,
Source::IndSI, Source::IndDI,
Source::IndBP, Source::IndBX,
};
memreg = rm_table[rm];
displacement_size_ = 1 + (mod == 2);
} break;
// Other operand is just a register.
case 3:
memreg = reg_table[operation_size_][rm];
// LES and LDS accept a memory argument only, not a register.
if(operation_ == Operation::LES || operation_ == Operation::LDS) {
const auto result = std::make_pair(consumed_, Instruction());
reset_parsing();
return result;
}
break;
}
switch(modregrm_format_) {
case ModRegRMFormat::Reg_MemReg:
case ModRegRMFormat::MemReg_Reg: {
if(modregrm_format_ == ModRegRMFormat::Reg_MemReg) {
source_ = memreg;
destination_ = reg_table[operation_size_][reg];
} else {
source_ = reg_table[operation_size_][reg];
destination_ = memreg;
}
} break;
case ModRegRMFormat::MemRegTEST_to_IDIV:
source_ = destination_ = memreg;
switch(reg) {
default: {
const auto result = std::make_pair(consumed_, Instruction());
reset_parsing();
return result;
}
case 0: operation_ = Operation::TEST; break;
case 2: operation_ = Operation::NOT; break;
case 3: operation_ = Operation::NEG; break;
case 4: operation_ = Operation::MUL; break;
case 5: operation_ = Operation::IMUL; break;
case 6: operation_ = Operation::DIV; break;
case 7: operation_ = Operation::IDIV; break;
}
break;
case ModRegRMFormat::SegReg: {
source_ = memreg;
constexpr Source seg_table[4] = {
Source::ES, Source::CS,
Source::SS, Source::DS,
};
if(reg & 4) {
const auto result = std::make_pair(consumed_, Instruction());
reset_parsing();
return result;
}
destination_ = seg_table[reg];
} break;
case ModRegRMFormat::MemRegROL_to_SAR:
destination_ = memreg;
switch(reg) {
default: {
const auto result = std::make_pair(consumed_, Instruction());
reset_parsing();
return result;
}
case 0: operation_ = Operation::ROL; break;
case 2: operation_ = Operation::ROR; break;
case 3: operation_ = Operation::RCL; break;
case 4: operation_ = Operation::RCR; break;
case 5: operation_ = Operation::SAL; break;
case 6: operation_ = Operation::SHR; break;
case 7: operation_ = Operation::SAR; break;
}
break;
case ModRegRMFormat::MemRegINC_DEC:
source_ = destination_ = memreg;
switch(reg) {
default: {
const auto result = std::make_pair(consumed_, Instruction());
reset_parsing();
return result;
}
case 0: operation_ = Operation::INC; break;
case 1: operation_ = Operation::DEC; break;
}
break;
case ModRegRMFormat::MemRegINC_to_PUSH:
source_ = destination_ = memreg;
switch(reg) {
default: {
const auto result = std::make_pair(consumed_, Instruction());
reset_parsing();
return result;
}
case 0: operation_ = Operation::INC; break;
case 1: operation_ = Operation::DEC; break;
case 2: operation_ = Operation::CALLN; break;
case 3:
operation_ = Operation::CALLF;
operand_size_ = 4;
source_ = Source::Immediate;
break;
case 4: operation_ = Operation::JMPN; break;
case 5:
operation_ = Operation::JMPF;
operand_size_ = 4;
source_ = Source::Immediate;
break;
case 6: operation_ = Operation::PUSH; break;
}
break;
case ModRegRMFormat::MemRegPOP:
source_ = destination_ = memreg;
if(reg != 0) {
reset_parsing();
return std::make_pair(consumed_, Instruction());
}
break;
case ModRegRMFormat::MemRegMOV:
source_ = Source::Immediate;
destination_ = memreg;
operand_size_ = operation_size_;
break;
case ModRegRMFormat::MemRegADD_to_CMP:
destination_ = memreg;
operand_size_ = operation_size_;
switch(reg) {
default: operation_ = Operation::ADD; break;
case 1: operation_ = Operation::OR; break;
case 2: operation_ = Operation::ADC; break;
case 3: operation_ = Operation::SBB; break;
case 4: operation_ = Operation::AND; break;
case 5: operation_ = Operation::SUB; break;
case 6: operation_ = Operation::XOR; break;
case 7: operation_ = Operation::CMP; break;
}
break;
case ModRegRMFormat::MemRegADC_to_CMP:
destination_ = memreg;
source_ = Source::Immediate;
operand_size_ = 1; // ... and always 1; it'll be sign extended if
// the operation requires it.
switch(reg) {
default: {
const auto result = std::make_pair(consumed_, Instruction());
reset_parsing();
return result;
}
case 0: operation_ = Operation::ADD; break;
case 2: operation_ = Operation::ADC; break;
case 3: operation_ = Operation::SBB; break;
case 5: operation_ = Operation::SUB; break;
case 7: operation_ = Operation::CMP; break;
}
break;
default: assert(false);
}
phase_ = (displacement_size_ + operand_size_) ? Phase::AwaitingDisplacementOrOperand : Phase::ReadyToPost;
}
// MARK: - Displacement and operand.
if(phase_ == Phase::AwaitingDisplacementOrOperand && source != end) {
const int required_bytes = displacement_size_ + operand_size_;
const int outstanding_bytes = required_bytes - operand_bytes_;
const int bytes_to_consume = std::min(int(end - source), outstanding_bytes);
// TODO: I can surely do better than this?
for(int c = 0; c < bytes_to_consume; c++) {
inward_data_ = (inward_data_ >> 8) | (uint64_t(source[0]) << 56);
++source;
}
consumed_ += bytes_to_consume;
operand_bytes_ += bytes_to_consume;
if(bytes_to_consume == outstanding_bytes) {
phase_ = Phase::ReadyToPost;
switch(operand_size_) {
default: operand_ = 0; break;
case 1:
operand_ = inward_data_ >> 56; inward_data_ <<= 8;
// Sign extend if a single byte operand is feeding a two-byte instruction.
if(operation_size_ == 2 && operation_ != Operation::IN && operation_ != Operation::OUT) {
operand_ |= (operand_ & 0x80) ? 0xff00 : 0x0000;
}
break;
case 4: displacement_size_ = 2; [[fallthrough]];
case 2: operand_ = inward_data_ >> 48; inward_data_ <<= 16; break;
break;
}
switch(displacement_size_) {
default: displacement_ = 0; break;
case 1: displacement_ = int8_t(inward_data_ >> 56); break;
case 2: displacement_ = int16_t(inward_data_ >> 48); break;
}
} else {
// Provide a genuine measure of further bytes required.
return std::make_pair(-(outstanding_bytes - bytes_to_consume), Instruction());
}
}
// MARK: - Check for completion.
if(phase_ == Phase::ReadyToPost) {
const auto result = std::make_pair(
consumed_,
Instruction(
operation_,
source_,
destination_,
lock_,
segment_override_,
repetition_,
Size(operation_size_),
displacement_,
operand_)
);
reset_parsing();
return result;
}
// i.e. not done yet.
return std::make_pair(0, Instruction());
}

View File

@@ -0,0 +1,156 @@
//
// Decoder.hpp
// Clock Signal
//
// Created by Thomas Harte on 1/1/21.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef InstructionSets_x86_Decoder_hpp
#define InstructionSets_x86_Decoder_hpp
#include "Instruction.hpp"
#include <utility>
namespace CPU {
namespace Decoder {
namespace x86 {
enum class Model {
i8086,
};
/*!
Implements Intel x86 instruction decoding.
This is an experimental implementation; it has not yet undergone significant testing.
*/
struct Decoder {
public:
Decoder(Model model);
/*!
@returns an @c Instruction plus a size; a positive size to indicate successful decoding; a
negative size specifies the [negatived] number of further bytes the caller should ideally
collect before calling again. The caller is free to call with fewer, but may not get a decoded
instruction in response, and the decoder may still not be able to complete decoding
even if given that number of bytes.
*/
std::pair<int, Instruction> decode(const uint8_t *source, size_t length);
private:
enum class Phase {
/// Captures all prefixes and continues until an instruction byte is encountered.
Instruction,
/// Receives a ModRegRM byte and either populates the source_ and dest_ fields appropriately
/// or completes decoding of the instruction, as per the instruction format.
ModRegRM,
/// Waits for sufficiently many bytes to pass for the required displacement and operand to be captured.
/// Cf. displacement_size_ and operand_size_.
AwaitingDisplacementOrOperand,
/// Forms and returns an Instruction, and resets parsing state.
ReadyToPost
} phase_ = Phase::Instruction;
/// During the ModRegRM phase, format dictates interpretation of the ModRegRM byte.
///
/// During the ReadyToPost phase, format determines how transiently-recorded fields
/// are packaged into an Instruction.
enum class ModRegRMFormat: uint8_t {
// Parse the ModRegRM for mode, register and register/memory fields
// and populate the source_ and destination_ fields appropriate.
MemReg_Reg,
Reg_MemReg,
// Parse for mode and register/memory fields, populating both
// source_ and destination_ fields with the result. Use the 'register'
// field to pick an operation from the TEST/NOT/NEG/MUL/IMUL/DIV/IDIV group.
MemRegTEST_to_IDIV,
// Parse for mode and register/memory fields, populating both
// source_ and destination_ fields with the result. Use the 'register'
// field to check for the POP operation.
MemRegPOP,
// Parse for mode and register/memory fields, populating both
// the destination_ field with the result and setting source_ to Immediate.
// Use the 'register' field to check for the MOV operation.
MemRegMOV,
// Parse for mode and register/memory fields, populating the
// destination_ field with the result. Use the 'register' field
// to pick an operation from the ROL/ROR/RCL/RCR/SAL/SHR/SAR group.
MemRegROL_to_SAR,
// Parse for mode and register/memory fields, populating the
// destination_ field with the result. Use the 'register' field
// to pick an operation from the ADD/OR/ADC/SBB/AND/SUB/XOR/CMP group and
// waits for an operand equal to the operation size.
MemRegADD_to_CMP,
// Parse for mode and register/memory fields, populating the
// source_ field with the result. Fills destination_ with a segment
// register based on the reg field.
SegReg,
// Parse for mode and register/memory fields, populating the
// source_ and destination_ fields with the result. Uses the
// 'register' field to pick INC or DEC.
MemRegINC_DEC,
// Parse for mode and register/memory fields, populating the
// source_ and destination_ fields with the result. Uses the
// 'register' field to pick from INC/DEC/CALL/JMP/PUSH, altering
// the source to ::Immediate and setting an operand size if necessary.
MemRegINC_to_PUSH,
// Parse for mode and register/memory fields, populating the
// source_ and destination_ fields with the result. Uses the
// 'register' field to pick from ADD/ADC/SBB/SUB/CMP, altering
// the source to ::Immediate and setting an appropriate operand size.
MemRegADC_to_CMP,
} modregrm_format_ = ModRegRMFormat::MemReg_Reg;
// Ephemeral decoding state.
Operation operation_ = Operation::Invalid;
uint8_t instr_ = 0x00; // TODO: is this desired, versus loading more context into ModRegRMFormat?
int consumed_ = 0, operand_bytes_ = 0;
// Source and destination locations.
Source source_ = Source::None;
Source destination_ = Source::None;
// Immediate fields.
int16_t displacement_ = 0;
uint16_t operand_ = 0;
uint64_t inward_data_ = 0;
// Facts about the instruction.
int displacement_size_ = 0; // i.e. size of in-stream displacement, if any.
int operand_size_ = 0; // i.e. size of in-stream operand, if any.
int operation_size_ = 0; // i.e. size of data manipulated by the operation.
// Prefix capture fields.
Repetition repetition_ = Repetition::None;
bool lock_ = false;
Source segment_override_ = Source::None;
/// Resets size capture and all fields with default values.
void reset_parsing() {
consumed_ = operand_bytes_ = 0;
displacement_size_ = operand_size_ = 0;
displacement_ = operand_ = 0;
lock_ = false;
segment_override_ = Source::None;
repetition_ = Repetition::None;
phase_ = Phase::Instruction;
source_ = destination_ = Source::None;
}
};
}
}
}
#endif /* InstructionSets_x86_Decoder_hpp */

View File

@@ -0,0 +1,305 @@
//
// Instruction.hpp
// Clock Signal
//
// Created by Thomas Harte on 1/15/21.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef InstructionSets_x86_Instruction_h
#define InstructionSets_x86_Instruction_h
#include <cstdint>
namespace CPU {
namespace Decoder {
namespace x86 {
/*
Operations are documented below to establish expectations as to which
instruction fields will be meaningful for each; this is a work-in-progress
and may currently contain errors in the opcode descriptions — especially
where implicit register dependencies are afoot.
*/
enum class Operation: uint8_t {
Invalid,
/// ASCII adjust after addition; source will be AL and destination will be AX.
AAA,
/// ASCII adjust before division; destination will be AX and source will be a multiplier.
AAD,
/// ASCII adjust after multiplication; destination will be AX and source will be a divider.
AAM,
/// ASCII adjust after subtraction; source will be AL and destination will be AX.
AAS,
/// Decimal adjust after addition; source and destination will be AL.
DAA,
/// Decimal adjust after subtraction; source and destination will be AL.
DAS,
/// Convert byte into word; source will be AL, destination will be AH.
CBW,
/// Convert word to double word; source will be AX and destination will be DX.
CWD,
/// Escape, for a coprocessor; perform the bus cycles necessary to read the source and destination and perform a NOP.
ESC,
/// Stops the processor until the next interrupt is fired.
HLT,
/// Waits until the WAIT input is asserted; if an interrupt occurs then it is serviced but returns to the WAIT.
WAIT,
/// Add with carry; source, destination, operand and displacement will be populated appropriately.
ADC,
/// Add; source, destination, operand and displacement will be populated appropriately.
ADD,
/// Subtract with borrow; source, destination, operand and displacement will be populated appropriately.
SBB,
/// Subtract; source, destination, operand and displacement will be populated appropriately.
SUB,
/// Unsigned multiply; multiplies the source value by AX or AL, storing the result in DX:AX or AX.
MUL,
/// Signed multiply; multiplies the source value by AX or AL, storing the result in DX:AX or AX.
IMUL,
/// Unsigned divide; divide the source value by AX or AL, storing the quotient in AL and the remainder in AH.
DIV,
/// Signed divide; divide the source value by AX or AL, storing the quotient in AL and the remainder in AH.
IDIV,
/// Increment; source, destination, operand and displacement will be populated appropriately.
INC,
/// Decrement; source, destination, operand and displacement will be populated appropriately.
DEC,
/// Reads from the port specified by source to the destination.
IN,
/// Writes from the port specified by destination from the source.
OUT,
// Various jumps; see the displacement to calculate targets.
JO, JNO, JB, JNB, JE, JNE, JBE, JNBE,
JS, JNS, JP, JNP, JL, JNL, JLE, JNLE,
/// Far call; see the segment() and offset() fields.
CALLF,
/// Displacement call; followed by a 16-bit operand providing a call offset.
CALLD,
/// Near call.
CALLN,
/// Return from interrupt.
IRET,
/// Near return; if source is not ::None then it will be an ::Immediate indicating how many additional bytes to remove from the stack.
RETF,
/// Far return; if source is not ::None then it will be an ::Immediate indicating how many additional bytes to remove from the stack.
RETN,
/// Near jump; if an operand is not ::None then it gives an absolute destination; otherwise see the displacement.
JMPN,
/// Far jump to the indicated segment and offset.
JMPF,
/// Relative jump performed only if CX = 0; see the displacement.
JPCX,
/// Generates a software interrupt of the level stated in the operand.
INT,
/// Generates a software interrupt of level 3.
INT3,
/// Generates a software interrupt of level 4 if overflow is set.
INTO,
/// Load status flags to AH.
LAHF,
/// Load status flags from AH.
SAHF,
/// Load a segment and offset from the source into DS and the destination.
LDS,
/// Load a segment and offset from the source into ES and the destination.
LES,
/// Computes the effective address of the source and loads it into the destination.
LEA,
/// Compare [bytes or words, per operation size]; source and destination implied to be DS:[SI] and ES:[DI].
CMPS,
/// Load string; reads from DS:SI into AL or AX, subject to segment override.
LODS,
/// Move string; moves a byte or word from DS:SI to ES:DI. If a segment override is provided, it overrides the the source.
MOVS,
/// Scan string; reads a byte or word from DS:SI and compares it to AL or AX.
SCAS,
/// Store string; store AL or AX to ES:DI.
STOS,
// Perform a possibly-conditional loop, decrementing CX. See the displacement.
LOOP, LOOPE, LOOPNE,
/// Loads the destination with the source.
MOV,
/// Negatives; source and destination point to the same thing, to negative.
NEG,
/// Logical NOT; source and destination point to the same thing, to negative.
NOT,
/// Logical AND; source, destination, operand and displacement will be populated appropriately.
AND,
/// Logical OR of source onto destination.
OR,
/// Logical XOR of source onto destination.
XOR,
/// NOP; no further fields.
NOP,
/// POP from the stack to destination.
POP,
/// POP from the stack to the flags register.
POPF,
/// PUSH the source to the stack.
PUSH,
/// PUSH the flags register to the stack.
PUSHF,
/// Rotate the destination left through carry the number of bits indicated by source.
RCL,
/// Rotate the destination right through carry the number of bits indicated by source.
RCR,
/// Rotate the destination left the number of bits indicated by source.
ROL,
/// Rotate the destination right the number of bits indicated by source.
ROR,
/// Arithmetic shift left the destination by the number of bits indicated by source.
SAL,
/// Arithmetic shift right the destination by the number of bits indicated by source.
SAR,
/// Logical shift right the destination by the number of bits indicated by source.
SHR,
/// Clear carry flag; no source or destination provided.
CLC,
/// Clear direction flag; no source or destination provided.
CLD,
/// Clear interrupt flag; no source or destination provided.
CLI,
/// Set carry flag.
STC,
/// Set decimal flag.
STD,
/// Set interrupt flag.
STI,
/// Complement carry flag; no source or destination provided.
CMC,
/// Compare; source, destination, operand and displacement will be populated appropriately.
CMP,
/// Sets flags based on the result of a logical AND of source and destination.
TEST,
/// Exchanges the contents of the source and destination.
XCHG,
/// Load AL with DS:[AL+BX].
XLAT,
};
enum class Size: uint8_t {
Implied = 0,
Byte = 1,
Word = 2,
DWord = 4,
};
enum class Source: uint8_t {
None,
CS, DS, ES, SS,
AL, AH, AX,
BL, BH, BX,
CL, CH, CX,
DL, DH, DX,
SI, DI,
BP, SP,
IndBXPlusSI,
IndBXPlusDI,
IndBPPlusSI,
IndBPPlusDI,
IndSI,
IndDI,
DirectAddress,
IndBP,
IndBX,
Immediate
};
enum class Repetition: uint8_t {
None, RepE, RepNE
};
class Instruction {
public:
Operation operation = Operation::Invalid;
bool operator ==(const Instruction &rhs) const {
return
repetition_size_ == rhs.repetition_size_ &&
sources_ == rhs.sources_ &&
displacement_ == rhs.displacement_ &&
operand_ == rhs.operand_;
}
private:
// b0, b1: a Repetition;
// b2+: operation size.
uint8_t repetition_size_ = 0;
// b0b5: source;
// b6b11: destination;
// b12b14: segment override;
// b15: lock.
uint16_t sources_ = 0;
// Unpackable fields.
int16_t displacement_ = 0;
uint16_t operand_ = 0; // ... or used to store a segment for far operations.
public:
Source source() const { return Source(sources_ & 0x3f); }
Source destination() const { return Source((sources_ >> 6) & 0x3f); }
bool lock() const { return sources_ & 0x8000; }
Source segment_override() const { return Source((sources_ >> 12) & 7); }
Repetition repetition() const { return Repetition(repetition_size_ & 3); }
Size operation_size() const { return Size(repetition_size_ >> 2); }
uint16_t segment() const { return uint16_t(operand_); }
uint16_t offset() const { return uint16_t(displacement_); }
int16_t displacement() const { return displacement_; }
uint16_t operand() const { return operand_; }
Instruction() noexcept {}
Instruction(
Operation operation,
Source source,
Source destination,
bool lock,
Source segment_override,
Repetition repetition,
Size operation_size,
int16_t displacement,
uint16_t operand) noexcept :
operation(operation),
repetition_size_(uint8_t((int(operation_size) << 2) | int(repetition))),
sources_(uint16_t(
int(source) |
(int(destination) << 6) |
(int(segment_override) << 12) |
(int(lock) << 15)
)),
displacement_(displacement),
operand_(operand) {}
};
static_assert(sizeof(Instruction) <= 8);
}
}
}
#endif /* InstructionSets_x86_Instruction_h */

View File

@@ -1089,7 +1089,7 @@ template <bool has_fdc> class ConcreteMachine:
return Utility::TypeRecipient<CharacterMapper>::can_type(c);
}
HalfCycles get_typer_delay() const final {
HalfCycles get_typer_delay(const std::string &) const final {
return z80_.get_is_resetting() ? Cycles(3'400'000) : Cycles(0);
}

View File

@@ -804,38 +804,62 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
m6502_.run_for(cycles);
}
void reset_all_keys(Inputs::Keyboard *) final {
void reset_all_keys() final {
open_apple_is_pressed_ = closed_apple_is_pressed_ = key_is_down_ = false;
}
bool prefers_logical_input() final {
return true;
}
bool set_key_pressed(Key key, char value, bool is_pressed) final {
// If no ASCII value is supplied, look for a few special cases.
switch(key) {
default: break;
case Key::F12:
m6502_.set_reset_line(is_pressed);
return true;
case Key::Left: value = 0x08; break;
case Key::Right: value = 0x15; break;
case Key::Down: value = 0x0a; break;
case Key::Up: value = 0x0b; break;
case Key::Backspace: value = 0x7f; break;
case Key::Enter: value = 0x0d; break;
case Key::Tab: value = '\t'; break;
case Key::Escape: value = 0x1b; break;
case Key::LeftOption:
case Key::RightMeta:
open_apple_is_pressed_ = is_pressed;
return true;
case Key::RightOption:
case Key::LeftMeta:
closed_apple_is_pressed_ = is_pressed;
return true;
}
// If no ASCII value is supplied, look for a few special cases.
if(!value) {
switch(key) {
case Key::Left: value = 0x08; break;
case Key::Right: value = 0x15; break;
case Key::Down: value = 0x0a; break;
case Key::Up: value = 0x0b; break;
case Key::Backspace: value = 0x7f; break;
default: return false;
}
}
case Key::F1: case Key::F2: case Key::F3: case Key::F4:
case Key::F5: case Key::F6: case Key::F7: case Key::F8:
case Key::F9: case Key::F10: case Key::F11: case Key::F12:
case Key::PrintScreen:
case Key::ScrollLock:
case Key::Pause:
case Key::Insert:
case Key::Home:
case Key::PageUp:
case Key::PageDown:
case Key::End:
// Accept a bunch non-symbolic other keys, as
// reset, in the hope that the user can find
// at least one usable key.
m6502_.set_reset_line(is_pressed);
return true;
// Prior to the IIe, the keyboard could produce uppercase only.
if(!is_iie()) value = char(toupper(value));
default:
if(!value) {
return false;
}
// Prior to the IIe, the keyboard could produce uppercase only.
if(!is_iie()) value = char(toupper(value));
break;
}
if(is_pressed) {
keyboard_input_ = uint8_t(value | 0x80);

View File

@@ -17,6 +17,9 @@
#include "../../Configurable/StandardOptions.hpp"
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#include "../../Processors/6502/6502.hpp"
#include "../../Storage/MassStorage/SCSI/SCSI.hpp"
#include "../../Storage/MassStorage/SCSI/DirectAccessDevice.hpp"
#include "../../Storage/Tape/Tape.hpp"
#include "../Utility/Typer.hpp"
@@ -31,7 +34,7 @@
namespace Electron {
class ConcreteMachine:
template <bool has_scsi_bus> class ConcreteMachine:
public Machine,
public MachineTypes::TimedMachine,
public MachineTypes::ScanProducer,
@@ -42,10 +45,15 @@ class ConcreteMachine:
public CPU::MOS6502::BusHandler,
public Tape::Delegate,
public Utility::TypeRecipient<CharacterMapper>,
public Activity::Source {
public Activity::Source,
public SCSI::Bus::Observer,
public ClockingHint::Observer {
public:
ConcreteMachine(const Analyser::Static::Acorn::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
m6502_(*this),
scsi_bus_(4'000'000),
hard_drive_(scsi_bus_, 0),
scsi_device_(scsi_bus_.add_device()),
video_output_(ram_),
sound_generator_(audio_queue_),
speaker_(sound_generator_) {
@@ -64,14 +72,23 @@ class ConcreteMachine:
{machine_name, "the Acorn BASIC II ROM", "basic.rom", 16*1024, 0x79434781},
{machine_name, "the Electron MOS ROM", "os.rom", 16*1024, 0xbf63fb1f}
};
if(target.has_adfs) {
const size_t pres_adfs_rom_position = required_roms.size();
if(target.has_pres_adfs) {
required_roms.emplace_back(machine_name, "the E00 ADFS ROM, first slot", "ADFS-E00_1.rom", 16*1024, 0x51523993);
required_roms.emplace_back(machine_name, "the E00 ADFS ROM, second slot", "ADFS-E00_2.rom", 16*1024, 0x8d17de0e);
}
const size_t acorn_adfs_rom_position = required_roms.size();
if(target.has_acorn_adfs) {
required_roms.emplace_back(machine_name, "the Acorn ADFS ROM", "adfs.rom", 16*1024, 0x3289bdc6);
}
const size_t dfs_rom_position = required_roms.size();
if(target.has_dfs) {
required_roms.emplace_back(machine_name, "the 1770 DFS ROM", "DFS-1770-2.20.rom", 16*1024, 0xf3dc9bc5);
}
const size_t ap6_rom_position = required_roms.size();
if(target.has_ap6_rom) {
required_roms.emplace_back(machine_name, "the 8kb Advanced Plus 6 ROM", "AP6v133.rom", 8*1024, 0xe0013cfc);
}
const auto roms = rom_fetcher(required_roms);
for(const auto &rom: roms) {
@@ -82,15 +99,40 @@ class ConcreteMachine:
set_rom(ROM::BASIC, *roms[0], false);
set_rom(ROM::OS, *roms[1], false);
if(target.has_dfs || target.has_adfs) {
/*
ROM slot mapping applied:
* the keyboard and BASIC ROMs occupy slots 8, 9, 10 and 11;
* the DFS, if in use, occupies slot 1;
* the Pres ADFS, if in use, occupies slots 4 and 5;
* the Acorn ADFS, if in use, occupies slot 6;
* the AP6, if in use, occupies slot 15; and
* if sideways RAM was asked for, all otherwise unused slots are populated with sideways RAM.
*/
if(target.has_dfs || target.has_acorn_adfs || target.has_pres_adfs) {
plus3_ = std::make_unique<Plus3>();
if(target.has_dfs) {
set_rom(ROM::Slot0, *roms[dfs_rom_position], true);
}
if(target.has_adfs) {
set_rom(ROM::Slot4, *roms[2], true);
set_rom(ROM::Slot5, *roms[3], true);
if(target.has_pres_adfs) {
set_rom(ROM::Slot4, *roms[pres_adfs_rom_position], true);
set_rom(ROM::Slot5, *roms[pres_adfs_rom_position+1], true);
}
if(target.has_acorn_adfs) {
set_rom(ROM::Slot6, *roms[acorn_adfs_rom_position], true);
}
}
if(target.has_ap6_rom) {
set_rom(ROM::Slot15, *roms[ap6_rom_position], true);
}
if(target.has_sideways_ram) {
for(int c = 0; c < 16; c++) {
if(rom_inserted_[c]) continue;
if(c >= int(ROM::Keyboard) && c < int(ROM::BASIC)+1) continue;
set_sideways_ram(ROM(c));
}
}
@@ -103,6 +145,11 @@ class ConcreteMachine:
if(target.should_shift_restart) {
shift_restart_counter_ = 1000000;
}
if(has_scsi_bus) {
scsi_bus_.add_observer(this);
scsi_bus_.set_clocking_hint_observer(this);
}
}
~ConcreteMachine() {
@@ -168,7 +215,12 @@ class ConcreteMachine:
set_rom(slot, cartridge->get_segments().front().data, false);
}
return !media.tapes.empty() || !media.disks.empty() || !media.cartridges.empty();
// TODO: allow this only at machine startup?
if(!media.mass_storage_devices.empty()) {
hard_drive_->set_storage(media.mass_storage_devices.front());
}
return !media.empty();
}
forceinline Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
@@ -290,7 +342,73 @@ class ConcreteMachine:
plus3_->set_control_register(*value);
} else *value = 1;
}
if(has_scsi_bus && (address&0x00f0) == 0x0040) {
scsi_acknowledge_ = true;
if(!isReadOperation(operation)) {
scsi_data_ = *value;
push_scsi_output();
} else {
*value = SCSI::data_lines(scsi_bus_.get_state());
push_scsi_output();
}
}
break;
case 0xfc03:
if(has_scsi_bus && (address&0x00f0) == 0x0040) {
scsi_interrupt_state_ = false;
scsi_interrupt_mask_ = *value & 1;
evaluate_interrupts();
}
break;
case 0xfc01:
if(has_scsi_bus && (address&0x00f0) == 0x0040 && isReadOperation(operation)) {
// Status byte is:
//
// b7: SCSI C/D
// b6: SCSI I/O
// b5: SCSI REQ
// b4: interrupt flag
// b3: 0
// b2: 0
// b1: SCSI BSY
// b0: SCSI MSG
const auto state = scsi_bus_.get_state();
*value =
(state & SCSI::Line::Control ? 0x80 : 0x00) |
(state & SCSI::Line::Input ? 0x40 : 0x00) |
(state & SCSI::Line::Request ? 0x20 : 0x00) |
((scsi_interrupt_state_ && scsi_interrupt_mask_) ? 0x10 : 0x00) |
(state & SCSI::Line::Busy ? 0x02 : 0x00) |
(state & SCSI::Line::Message ? 0x01 : 0x00);
// Empirical guess: this is also the trigger to affect busy/request/acknowledge
// signalling. Maybe?
if(scsi_select_ && scsi_bus_.get_state() & SCSI::Line::Busy) {
scsi_select_ = false;
push_scsi_output();
}
}
break;
case 0xfc02:
if(has_scsi_bus && (address&0x00f0) == 0x0040) {
scsi_select_ = true;
push_scsi_output();
}
break;
// SCSI locations:
//
// fc40: data, read and write
// fc41: status read
// fc42: select write
// fc43: interrupt latch
//
//
// Interrupt latch is:
//
// b0: enable or disable IRQ on REQ
// (and, possibly, writing to the latch acknowledges?)
default:
if(address >= 0xc000) {
@@ -389,6 +507,12 @@ class ConcreteMachine:
}
}
if constexpr (has_scsi_bus) {
if(scsi_is_clocked_) {
scsi_bus_.run_for(Cycles(int(cycles)));
}
}
return Cycles(int(cycles));
}
@@ -422,13 +546,44 @@ class ConcreteMachine:
m6502_.run_for(cycles);
}
void scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double) final {
// Release acknowledge when request is released.
if(scsi_acknowledge_ && !(new_state & SCSI::Line::Request)) {
scsi_acknowledge_ = false;
push_scsi_output();
}
// Output occurs only while SCSI::Line::Input is inactive; therefore a change
// in that line affects what's on the bus.
if(((new_state^previous_bus_state_)&SCSI::Line::Input)) {
push_scsi_output();
}
scsi_interrupt_state_ |= (new_state^previous_bus_state_)&new_state & SCSI::Line::Request;
previous_bus_state_ = new_state;
evaluate_interrupts();
}
void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference preference) final {
scsi_is_clocked_ = preference != ClockingHint::Preference::None;
}
void tape_did_change_interrupt_status(Tape *) final {
interrupt_status_ = (interrupt_status_ & ~(Interrupt::TransmitDataEmpty | Interrupt::ReceiveDataFull | Interrupt::HighToneDetect)) | tape_.get_interrupt_status();
evaluate_interrupts();
}
HalfCycles get_typer_delay() const final {
return m6502_.get_is_resetting() ? Cycles(750'000) : Cycles(0);
HalfCycles get_typer_delay(const std::string &text) const final {
if(!m6502_.get_is_resetting()) {
return Cycles(0);
}
// Add a longer delay for a command at reset that involves pressing a modifier;
// empirically this seems to be a requirement, in order to avoid a collision with
// the system's built-in modifier-at-startup test (e.g. to perform shift+break).
CharacterMapper test_mapper;
const uint16_t *const sequence = test_mapper.sequence_for_character(text[0]);
return is_modifier(Key(sequence[0])) ? Cycles(1'000'000) : Cycles(750'000);
}
HalfCycles get_typer_frequency() const final {
@@ -469,10 +624,14 @@ class ConcreteMachine:
if(activity_observer_) {
activity_observer_->register_led(caps_led);
activity_observer_->set_led_status(caps_led, caps_led_state_);
}
if(plus3_) {
plus3_->set_activity_observer(observer);
}
if(plus3_) {
plus3_->set_activity_observer(observer);
}
if(has_scsi_bus) {
scsi_bus_.set_activity_observer(observer);
}
}
@@ -517,8 +676,20 @@ class ConcreteMachine:
rom_ptr += size_to_copy;
}
if(int(slot) < 16)
if(int(slot) < 16) {
rom_inserted_[int(slot)] = true;
}
}
/*!
Enables @c slot as sideways RAM; ensures that it does not currently contain a valid ROM signature.
*/
void set_sideways_ram(ROM slot) {
std::memset(roms_[int(slot)], 0xff, 16*1024);
if(int(slot) < 16) {
rom_inserted_[int(slot)] = true;
rom_write_masks_[int(slot)] = true;
}
}
// MARK: - Work deferral updates.
@@ -554,7 +725,12 @@ class ConcreteMachine:
} else {
interrupt_status_ &= ~1;
}
m6502_.set_irq_line(interrupt_status_ & 1);
if constexpr (has_scsi_bus) {
m6502_.set_irq_line((scsi_interrupt_state_ && scsi_interrupt_mask_) | (interrupt_status_ & 1));
} else {
m6502_.set_irq_line(interrupt_status_ & 1);
}
}
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_;
@@ -598,6 +774,25 @@ class ConcreteMachine:
bool is_holding_shift_ = false;
int shift_restart_counter_ = 0;
// Hard drive.
SCSI::Bus scsi_bus_;
SCSI::Target::Target<SCSI::DirectAccessDevice> hard_drive_;
SCSI::BusState previous_bus_state_ = SCSI::DefaultBusState;
const size_t scsi_device_ = 0;
uint8_t scsi_data_ = 0;
bool scsi_select_ = false;
bool scsi_acknowledge_ = false;
bool scsi_is_clocked_ = false;
bool scsi_interrupt_state_ = false;
bool scsi_interrupt_mask_ = false;
void push_scsi_output() {
scsi_bus_.set_device_output(scsi_device_,
(scsi_bus_.get_state()&SCSI::Line::Input ? 0 : scsi_data_) |
(scsi_select_ ? SCSI::Line::SelectTarget : 0) |
(scsi_acknowledge_ ? SCSI::Line::Acknowledge : 0)
);
}
// Outputs
VideoOutput video_output_;
@@ -620,7 +815,12 @@ using namespace Electron;
Machine *Machine::Electron(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
using Target = Analyser::Static::Acorn::Target;
const Target *const acorn_target = dynamic_cast<const Target *>(target);
return new Electron::ConcreteMachine(*acorn_target, rom_fetcher);
if(acorn_target->media.mass_storage_devices.empty()) {
return new Electron::ConcreteMachine<false>(*acorn_target, rom_fetcher);
} else {
return new Electron::ConcreteMachine<true>(*acorn_target, rom_fetcher);
}
}
Machine::~Machine() {}

View File

@@ -16,6 +16,7 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) const {
default: break;
BIND(BackTick, KeyCopy);
BIND(Backslash, KeyCopy);
BIND(k0, Key0); BIND(k1, Key1); BIND(k2, Key2); BIND(k3, Key3); BIND(k4, Key4);
BIND(k5, Key5); BIND(k6, Key6); BIND(k7, Key7); BIND(k8, Key8); BIND(k9, Key9);

View File

@@ -31,11 +31,15 @@ enum Key: uint16_t {
KeyShift = 0x00d0 | 0x08, KeyControl = 0x00d0 | 0x04, KeyFunc = 0x00d0 | 0x02, KeyEscape = 0x00d0 | 0x01,
// Virtual keys.
KeyF1 = 0xfff0, KeyF2, KeyF3, KeyF4, KeyF5, KeyF6, KeyF7, KeyF8, KeyF9, KeyF0,
KeyF1 = 0xfff0, KeyF2, KeyF3, KeyF4, KeyF5, KeyF6, KeyF7, KeyF8, KeyF9, KeyF0,
KeyBreak = 0xfffd,
};
constexpr bool is_modifier(Key key) {
return (key == KeyShift) || (key == KeyControl) || (key == KeyFunc);
}
struct KeyboardMapper: public MachineTypes::MappedKeyboardMachine::KeyboardMapper {
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) const final;
};

View File

@@ -32,6 +32,11 @@ struct KeyActions {
Instructs that all keys should now be treated as released.
*/
virtual void clear_all_keys() {}
/*!
Indicates whether a machine most naturally accepts logical rather than physical input.
*/
virtual bool prefers_logical_input() { return false; }
};
/*!

View File

@@ -413,7 +413,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface, CPU::MOS
// to satisfy CPU::MOS6502::BusHandler
forceinline Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
if(address > ram_top_) {
if(isReadOperation(operation)) *value = paged_rom_[address - ram_top_ - 1];
if(!isWriteOperation(operation)) *value = paged_rom_[address - ram_top_ - 1];
// 024D = 0 => fast; otherwise slow
// E6C9 = read byte: return byte in A
@@ -432,39 +432,39 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface, CPU::MOS
} else {
if((address & 0xff00) == 0x0300) {
if(address < 0x0310 || (disk_interface == DiskInterface::None)) {
if(isReadOperation(operation)) *value = via_.read(address);
if(!isWriteOperation(operation)) *value = via_.read(address);
else via_.write(address, *value);
} else {
switch(disk_interface) {
default: break;
case DiskInterface::BD500:
if(isReadOperation(operation)) *value = bd500_.read(address);
if(!isWriteOperation(operation)) *value = bd500_.read(address);
else bd500_.write(address, *value);
break;
case DiskInterface::Jasmin:
if(address >= 0x3f4) {
if(isReadOperation(operation)) *value = jasmin_.read(address);
if(!isWriteOperation(operation)) *value = jasmin_.read(address);
else jasmin_.write(address, *value);
}
break;
case DiskInterface::Microdisc:
switch(address) {
case 0x0310: case 0x0311: case 0x0312: case 0x0313:
if(isReadOperation(operation)) *value = microdisc_.read(address);
if(!isWriteOperation(operation)) *value = microdisc_.read(address);
else microdisc_.write(address, *value);
break;
case 0x314: case 0x315: case 0x316: case 0x317:
if(isReadOperation(operation)) *value = microdisc_.get_interrupt_request_register();
if(!isWriteOperation(operation)) *value = microdisc_.get_interrupt_request_register();
else microdisc_.set_control_register(*value);
break;
case 0x318: case 0x319: case 0x31a: case 0x31b:
if(isReadOperation(operation)) *value = microdisc_.get_data_request_register();
if(!isWriteOperation(operation)) *value = microdisc_.get_data_request_register();
break;
}
break;
case DiskInterface::Pravetz:
if(address >= 0x0320) {
if(isReadOperation(operation)) *value = pravetz_rom_[pravetz_rom_base_pointer_ + (address & 0xff)];
if(!isWriteOperation(operation)) *value = pravetz_rom_[pravetz_rom_base_pointer_ + (address & 0xff)];
else {
switch(address) {
case 0x380: case 0x381: case 0x382: case 0x383:
@@ -476,13 +476,13 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface, CPU::MOS
} else {
flush_diskii();
const int disk_value = diskii_.read_address(address);
if(isReadOperation(operation) && disk_value != diskii_.DidNotLoad) *value = uint8_t(disk_value);
if(!isWriteOperation(operation) && disk_value != diskii_.DidNotLoad) *value = uint8_t(disk_value);
}
break;
}
}
} else {
if(isReadOperation(operation))
if(!isWriteOperation(operation))
*value = ram_[address];
else {
if(address >= 0x9800 && address <= 0xc000) update_video();

View File

@@ -109,7 +109,7 @@ class TypeRecipient: public Typer::Delegate {
/// Attaches a typer to this class that will type @c string using @c character_mapper as a source.
void add_typer(const std::string &string) {
if(!typer_) {
typer_ = std::make_unique<Typer>(string, get_typer_delay(), get_typer_frequency(), character_mapper, this);
typer_ = std::make_unique<Typer>(string, get_typer_delay(string), get_typer_frequency(), character_mapper, this);
} else {
typer_->append(string);
}
@@ -137,7 +137,7 @@ class TypeRecipient: public Typer::Delegate {
typer_ = nullptr;
}
virtual HalfCycles get_typer_delay() const { return HalfCycles(0); }
virtual HalfCycles get_typer_delay(const std::string &) const { return HalfCycles(0); }
virtual HalfCycles get_typer_frequency() const { return HalfCycles(0); }
std::unique_ptr<Typer> typer_;

View File

@@ -387,7 +387,7 @@ template<bool is_zx81> class ConcreteMachine:
}
// MARK: - Typer timing
HalfCycles get_typer_delay() const final {
HalfCycles get_typer_delay(const std::string &) const final {
return z80_.get_is_resetting() ? Cycles(7'000'000) : Cycles(0);
}

View File

@@ -183,6 +183,7 @@
4B3BA0D01D318B44005DD7A7 /* MOS6532Bridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0CB1D318B44005DD7A7 /* MOS6532Bridge.mm */; };
4B3BA0D11D318B44005DD7A7 /* TestMachine6502.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0CD1D318B44005DD7A7 /* TestMachine6502.mm */; };
4B3BF5B01F146265005B6C36 /* CSW.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BF5AE1F146264005B6C36 /* CSW.cpp */; };
4B3F76B925A1635300178AEC /* PowerPCDecoderTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B3F76B825A1635300178AEC /* PowerPCDecoderTests.mm */; };
4B3FCC40201EC24200960631 /* MultiMachine.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3FCC3F201EC24200960631 /* MultiMachine.cpp */; };
4B3FE75E1F3CF68B00448EE4 /* CPM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3FE75C1F3CF68B00448EE4 /* CPM.cpp */; };
4B448E811F1C45A00009ABD6 /* TZX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B448E7F1F1C45A00009ABD6 /* TZX.cpp */; };
@@ -849,6 +850,7 @@
4BE211DE253E4E4800435408 /* 65C02_no_Rockwell_test.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4BE211DD253E4E4800435408 /* 65C02_no_Rockwell_test.bin */; };
4BE34438238389E10058E78F /* AtariSTVideoTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BE34437238389E10058E78F /* AtariSTVideoTests.mm */; };
4BE76CF922641ED400ACD6FA /* QLTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BE76CF822641ED300ACD6FA /* QLTests.mm */; };
4BE8EB6625C750B50040BC40 /* DAT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE8EB6425C750B50040BC40 /* DAT.cpp */; };
4BE90FFD22D5864800FB464D /* MacintoshVideoTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BE90FFC22D5864800FB464D /* MacintoshVideoTests.mm */; };
4BE9A6B11EDE293000CBCB47 /* zexdoc.com in Resources */ = {isa = PBXBuildFile; fileRef = 4BE9A6B01EDE293000CBCB47 /* zexdoc.com */; };
4BEA525E1DF33323007E74F2 /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA525D1DF33323007E74F2 /* Tape.cpp */; };
@@ -857,11 +859,20 @@
4BEBFB4E2002C4BF000708CC /* MSXDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEBFB4B2002C4BF000708CC /* MSXDSK.cpp */; };
4BEBFB512002DB30000708CC /* DiskROM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEBFB4F2002DB30000708CC /* DiskROM.cpp */; };
4BEBFB522002DB30000708CC /* DiskROM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEBFB4F2002DB30000708CC /* DiskROM.cpp */; };
4BEDA3BA25B25563000C2DBD /* Decoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEDA3B425B25563000C2DBD /* Decoder.cpp */; };
4BEDA3BB25B25563000C2DBD /* Decoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEDA3B425B25563000C2DBD /* Decoder.cpp */; };
4BEDA3BC25B25563000C2DBD /* Decoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEDA3B425B25563000C2DBD /* Decoder.cpp */; };
4BEDA3BD25B25563000C2DBD /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 4BEDA3B625B25563000C2DBD /* README.md */; };
4BEDA3BE25B25563000C2DBD /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 4BEDA3B625B25563000C2DBD /* README.md */; };
4BEDA3BF25B25563000C2DBD /* Decoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEDA3B925B25563000C2DBD /* Decoder.cpp */; };
4BEDA3C025B25563000C2DBD /* Decoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEDA3B925B25563000C2DBD /* Decoder.cpp */; };
4BEDA3C125B25563000C2DBD /* Decoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEDA3B925B25563000C2DBD /* Decoder.cpp */; };
4BEE0A6F1D72496600532C7B /* Cartridge.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE0A6A1D72496600532C7B /* Cartridge.cpp */; };
4BEE0A701D72496600532C7B /* PRG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE0A6D1D72496600532C7B /* PRG.cpp */; };
4BEE149A227FC0EA00133682 /* IWM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE1498227FC0EA00133682 /* IWM.cpp */; };
4BEE1EC022B5E236000A26A6 /* MacGCRTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE1EBF22B5E236000A26A6 /* MacGCRTests.mm */; };
4BEE1EC122B5E2FD000A26A6 /* Encoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD67DCE209BF27B00AB2146 /* Encoder.cpp */; };
4BEE4BD425A26E2B00011BD2 /* x86DecoderTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE4BD325A26E2B00011BD2 /* x86DecoderTests.mm */; };
4BEEE6BD20DC72EB003723BF /* CompositeOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BEEE6BB20DC72EA003723BF /* CompositeOptions.xib */; };
4BEF6AAA1D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */; };
4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */; };
@@ -1074,6 +1085,7 @@
4B3BA0CD1D318B44005DD7A7 /* TestMachine6502.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TestMachine6502.mm; sourceTree = "<group>"; };
4B3BF5AE1F146264005B6C36 /* CSW.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CSW.cpp; sourceTree = "<group>"; };
4B3BF5AF1F146264005B6C36 /* CSW.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CSW.hpp; sourceTree = "<group>"; };
4B3F76B825A1635300178AEC /* PowerPCDecoderTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PowerPCDecoderTests.mm; sourceTree = "<group>"; };
4B3FCC3E201EC24200960631 /* MultiMachine.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MultiMachine.hpp; sourceTree = "<group>"; };
4B3FCC3F201EC24200960631 /* MultiMachine.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MultiMachine.cpp; sourceTree = "<group>"; };
4B3FE75C1F3CF68B00448EE4 /* CPM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CPM.cpp; path = Parsers/CPM.cpp; sourceTree = "<group>"; };
@@ -1332,7 +1344,6 @@
4B9378E322A199C600973513 /* Audio.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Audio.hpp; sourceTree = "<group>"; };
4B95FA9C1F11893B0008E395 /* ZX8081OptionsPanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZX8081OptionsPanel.swift; sourceTree = "<group>"; };
4B961408222760E0001A7BF2 /* Screenshot.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Screenshot.hpp; sourceTree = "<group>"; };
4B97ADC722C6FD9B00A22A41 /* 68000ArithmeticTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = 68000ArithmeticTests.mm; path = "/Users/thomasharte/Projects/CLK/OSBindings/Mac/Clock SignalTests/68000ArithmeticTests.mm"; sourceTree = "<absolute>"; };
4B98A05C1FFAD3F600ADF63B /* CSROMFetcher.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CSROMFetcher.hpp; sourceTree = "<group>"; };
4B98A05D1FFAD3F600ADF63B /* CSROMFetcher.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = CSROMFetcher.mm; sourceTree = "<group>"; };
4B98A0601FFADCDE00ADF63B /* MSXStaticAnalyserTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MSXStaticAnalyserTests.mm; sourceTree = "<group>"; };
@@ -1778,6 +1789,8 @@
4BE34437238389E10058E78F /* AtariSTVideoTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AtariSTVideoTests.mm; sourceTree = "<group>"; };
4BE76CF822641ED300ACD6FA /* QLTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = QLTests.mm; sourceTree = "<group>"; };
4BE845201F2FF7F100A5EA22 /* CRTC6845.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CRTC6845.hpp; path = 6845/CRTC6845.hpp; sourceTree = "<group>"; };
4BE8EB6425C750B50040BC40 /* DAT.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DAT.cpp; sourceTree = "<group>"; };
4BE8EB6525C750B50040BC40 /* DAT.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DAT.hpp; sourceTree = "<group>"; };
4BE90FFC22D5864800FB464D /* MacintoshVideoTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MacintoshVideoTests.mm; sourceTree = "<group>"; };
4BE9A6B01EDE293000CBCB47 /* zexdoc.com */ = {isa = PBXFileReference; lastKnownFileType = file; name = zexdoc.com; path = Zexall/zexdoc.com; sourceTree = "<group>"; };
4BEA525D1DF33323007E74F2 /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Tape.cpp; path = Electron/Tape.cpp; sourceTree = "<group>"; };
@@ -1789,6 +1802,13 @@
4BEBFB4C2002C4BF000708CC /* MSXDSK.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MSXDSK.hpp; sourceTree = "<group>"; };
4BEBFB4F2002DB30000708CC /* DiskROM.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = DiskROM.cpp; path = MSX/DiskROM.cpp; sourceTree = "<group>"; };
4BEBFB502002DB30000708CC /* DiskROM.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = DiskROM.hpp; path = MSX/DiskROM.hpp; sourceTree = "<group>"; };
4BEDA3B425B25563000C2DBD /* Decoder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Decoder.cpp; sourceTree = "<group>"; };
4BEDA3B525B25563000C2DBD /* Decoder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Decoder.hpp; sourceTree = "<group>"; };
4BEDA3B625B25563000C2DBD /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
4BEDA3B825B25563000C2DBD /* Decoder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Decoder.hpp; sourceTree = "<group>"; };
4BEDA3B925B25563000C2DBD /* Decoder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Decoder.cpp; sourceTree = "<group>"; };
4BEDA3D225B257F2000C2DBD /* Instruction.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Instruction.hpp; sourceTree = "<group>"; };
4BEDA3DB25B2588F000C2DBD /* Instruction.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Instruction.hpp; sourceTree = "<group>"; };
4BEE0A6A1D72496600532C7B /* Cartridge.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Cartridge.cpp; sourceTree = "<group>"; };
4BEE0A6B1D72496600532C7B /* Cartridge.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Cartridge.hpp; sourceTree = "<group>"; };
4BEE0A6D1D72496600532C7B /* PRG.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PRG.cpp; sourceTree = "<group>"; };
@@ -1796,6 +1816,7 @@
4BEE1498227FC0EA00133682 /* IWM.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = IWM.cpp; sourceTree = "<group>"; };
4BEE1499227FC0EA00133682 /* IWM.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = IWM.hpp; sourceTree = "<group>"; };
4BEE1EBF22B5E236000A26A6 /* MacGCRTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MacGCRTests.mm; sourceTree = "<group>"; };
4BEE4BD325A26E2B00011BD2 /* x86DecoderTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = x86DecoderTests.mm; sourceTree = "<group>"; };
4BEEE6BC20DC72EA003723BF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/CompositeOptions.xib"; sourceTree = SOURCE_ROOT; };
4BEF6AA81D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DigitalPhaseLockedLoopBridge.h; sourceTree = "<group>"; };
4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DigitalPhaseLockedLoopBridge.mm; sourceTree = "<group>"; };
@@ -2715,6 +2736,8 @@
children = (
4B74CF7F2312FA9C00500CE8 /* HFV.hpp */,
4B74CF802312FA9C00500CE8 /* HFV.cpp */,
4BE8EB6425C750B50040BC40 /* DAT.cpp */,
4BE8EB6525C750B50040BC40 /* DAT.hpp */,
);
path = Formats;
sourceTree = "<group>";
@@ -3385,6 +3408,7 @@
4B31B88E1FBFBCD800C140D5 /* Configurable */,
4B055A761FAE78210060FFFF /* Frameworks */,
4B86E2581F8C628F006FAA45 /* Inputs */,
4BEDA3B225B25563000C2DBD /* InstructionSets */,
4BB73EDC1B587CA500552FC2 /* Machines */,
4B7BA03C23D55E7900B98D9E /* Numeric */,
4B366DFD1B5C165F0026627B /* Outputs */,
@@ -3442,7 +3466,7 @@
children = (
4B85322922778E4200F26553 /* Comparative68000.hpp */,
4B90467222C6FA31000E2074 /* TestRunner68000.hpp */,
4B97ADC722C6FD9B00A22A41 /* 68000ArithmeticTests.mm */,
4B90467522C6FD6E000E2074 /* 68000ArithmeticTests.mm */,
4B9D0C4A22C7D70900DE1AD3 /* 68000BCDTests.mm */,
4B90467322C6FADD000E2074 /* 68000BitwiseTests.mm */,
4B680CE123A5553100451D43 /* 68000ComparativeTests.mm */,
@@ -3461,9 +3485,11 @@
4BC0CB272446BC7B00A79DBB /* OPLTests.mm */,
4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */,
4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */,
4B3F76B825A1635300178AEC /* PowerPCDecoderTests.mm */,
4BE76CF822641ED300ACD6FA /* QLTests.mm */,
4B2AF8681E513FC20027EE29 /* TIATests.mm */,
4B1D08051E0F7A1100763741 /* TimeTests.mm */,
4BEE4BD325A26E2B00011BD2 /* x86DecoderTests.mm */,
4BB73EB81B587A5100552FC2 /* Info.plist */,
4BC9E1ED1D23449A003FCEE4 /* 6502InterruptTests.swift */,
4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */,
@@ -3485,7 +3511,6 @@
4BFCA12A1ECBE7C400AC40C1 /* ZexallTests.swift */,
4B3BA0C41D318B44005DD7A7 /* Bridges */,
4B1414631B588A1100E04248 /* Test Binaries */,
4B90467522C6FD6E000E2074 /* 68000ArithmeticTests.mm */,
);
path = "Clock SignalTests";
sourceTree = "<group>";
@@ -3907,6 +3932,37 @@
name = Zexall;
sourceTree = "<group>";
};
4BEDA3B225B25563000C2DBD /* InstructionSets */ = {
isa = PBXGroup;
children = (
4BEDA3B625B25563000C2DBD /* README.md */,
4BEDA3B325B25563000C2DBD /* PowerPC */,
4BEDA3B725B25563000C2DBD /* x86 */,
);
name = InstructionSets;
path = ../../InstructionSets;
sourceTree = "<group>";
};
4BEDA3B325B25563000C2DBD /* PowerPC */ = {
isa = PBXGroup;
children = (
4BEDA3B425B25563000C2DBD /* Decoder.cpp */,
4BEDA3B525B25563000C2DBD /* Decoder.hpp */,
4BEDA3D225B257F2000C2DBD /* Instruction.hpp */,
);
path = PowerPC;
sourceTree = "<group>";
};
4BEDA3B725B25563000C2DBD /* x86 */ = {
isa = PBXGroup;
children = (
4BEDA3B925B25563000C2DBD /* Decoder.cpp */,
4BEDA3B825B25563000C2DBD /* Decoder.hpp */,
4BEDA3DB25B2588F000C2DBD /* Instruction.hpp */,
);
path = x86;
sourceTree = "<group>";
};
4BEE0A691D72496600532C7B /* Cartridge */ = {
isa = PBXGroup;
children = (
@@ -4137,6 +4193,7 @@
4B2C45421E3C3896002A2389 /* cartridge.png in Resources */,
4BB73EA91B587A5100552FC2 /* Assets.xcassets in Resources */,
4B79E4451E3AF38600141F11 /* floppy35.png in Resources */,
4BEDA3BD25B25563000C2DBD /* README.md in Resources */,
4B55DD8420DF06680043F2E5 /* MachinePicker.xib in Resources */,
4BDA00DA22E60EE300AC3CD0 /* ROMRequester.xib in Resources */,
4BC5FC3020CDDDEF00410AA0 /* AppleIIOptions.xib in Resources */,
@@ -4173,6 +4230,7 @@
4BB2994E1B587D8400A49093 /* dexn in Resources */,
4BB299971B587D8400A49093 /* nopa in Resources */,
4B018B89211930DE002A3937 /* 65C02_extended_opcodes_test.bin in Resources */,
4BEDA3BE25B25563000C2DBD /* README.md in Resources */,
4BFCA1291ECBE7A700AC40C1 /* zexall.com in Resources */,
4BB299521B587D8400A49093 /* eoray in Resources */,
4BB299411B587D8400A49093 /* cpyb in Resources */,
@@ -4607,6 +4665,7 @@
4BD67DD1209BF27B00AB2146 /* Encoder.cpp in Sources */,
4B89451F201967B4007DE474 /* Tape.cpp in Sources */,
4B055AA81FAE85EF0060FFFF /* Shifter.cpp in Sources */,
4BEDA3BC25B25563000C2DBD /* Decoder.cpp in Sources */,
4B8318B422D3E546006DB630 /* DriveSpeedAccumulator.cpp in Sources */,
4B055AC81FAE9AFB0060FFFF /* C1540.cpp in Sources */,
4B055A8F1FAE85A90060FFFF /* FileHolder.cpp in Sources */,
@@ -4641,6 +4700,7 @@
4B0F94FF208C1A1600FE41D9 /* NIB.cpp in Sources */,
4B0E04EB1FC9E78800F43484 /* CAS.cpp in Sources */,
4BB0A65D2045009000FB3688 /* ColecoVision.cpp in Sources */,
4BEDA3C125B25563000C2DBD /* Decoder.cpp in Sources */,
4BB0A65C2044FD3000FB3688 /* SN76489.cpp in Sources */,
4B595FAE2086DFBA0083CAA8 /* AudioToggle.cpp in Sources */,
4B055AB91FAE86170060FFFF /* Acorn.cpp in Sources */,
@@ -4713,7 +4773,9 @@
4B54C0BF1F8D8F450050900F /* Keyboard.cpp in Sources */,
4B3FE75E1F3CF68B00448EE4 /* CPM.cpp in Sources */,
4B2BFDB21DAEF5FF001A68B8 /* Video.cpp in Sources */,
4BEDA3BF25B25563000C2DBD /* Decoder.cpp in Sources */,
4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */,
4BE8EB6625C750B50040BC40 /* DAT.cpp in Sources */,
4BBFFEE61F7B27F1005F3FEB /* TrackSerialiser.cpp in Sources */,
4BAE49582032881E004BE78E /* CSZX8081.mm in Sources */,
4B0333AF2094081A0050B93D /* AppleDSK.cpp in Sources */,
@@ -4749,6 +4811,7 @@
4B45189F1F75FD1C00926311 /* AcornADF.cpp in Sources */,
4B7BA03023C2B19C00B98D9E /* Jasmin.cpp in Sources */,
4B7136911F789C93008B8ED9 /* SegmentParser.cpp in Sources */,
4BEDA3BA25B25563000C2DBD /* Decoder.cpp in Sources */,
4B4518A21F75FD1C00926311 /* G64.cpp in Sources */,
4B89452C201967B4007DE474 /* Tape.cpp in Sources */,
4B448E811F1C45A00009ABD6 /* TZX.cpp in Sources */,
@@ -4877,6 +4940,7 @@
4B778F4223A5F1A70000D260 /* MemoryFuzzer.cpp in Sources */,
4B778F0123A5EBA00000D260 /* MacintoshIMG.cpp in Sources */,
4BFF1D3D2235C3C100838EA1 /* EmuTOSTests.mm in Sources */,
4B3F76B925A1635300178AEC /* PowerPCDecoderTests.mm in Sources */,
4B778F0A23A5EC150000D260 /* TapePRG.cpp in Sources */,
4B778F0823A5EC150000D260 /* CSW.cpp in Sources */,
4B778F5323A5F23F0000D260 /* SerialBus.cpp in Sources */,
@@ -4933,6 +4997,7 @@
4B778F4823A5F1E70000D260 /* StaticAnalyser.cpp in Sources */,
4B92EACA1B7C112B00246143 /* 6502TimingTests.swift in Sources */,
4B778F2823A5EEF80000D260 /* Cartridge.cpp in Sources */,
4BEDA3C025B25563000C2DBD /* Decoder.cpp in Sources */,
4B778F4C23A5F2090000D260 /* StaticAnalyser.cpp in Sources */,
4B778F2623A5EE350000D260 /* Acorn.cpp in Sources */,
4B778F5B23A5F2DE0000D260 /* Tape.cpp in Sources */,
@@ -4943,6 +5008,7 @@
4BEE1EC122B5E2FD000A26A6 /* Encoder.cpp in Sources */,
4B121F9B1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm in Sources */,
4B778EFF23A5EB940000D260 /* D64.cpp in Sources */,
4BEDA3BB25B25563000C2DBD /* Decoder.cpp in Sources */,
4B778F2423A5EDEE0000D260 /* PRG.cpp in Sources */,
4B778F5A23A5F2D50000D260 /* 6502.cpp in Sources */,
4B778F6223A5F35F0000D260 /* File.cpp in Sources */,
@@ -5001,6 +5067,7 @@
4B778F2123A5EDD50000D260 /* TrackSerialiser.cpp in Sources */,
4B049CDD1DA3C82F00322067 /* BCDTest.swift in Sources */,
4B778F3923A5F11C0000D260 /* Shifter.cpp in Sources */,
4BEE4BD425A26E2B00011BD2 /* x86DecoderTests.mm in Sources */,
4B778F3623A5F1040000D260 /* Target.cpp in Sources */,
4B1D08061E0F7A1100763741 /* TimeTests.mm in Sources */,
4B778F3D23A5F1750000D260 /* ncr5380.cpp in Sources */,
@@ -5175,7 +5242,7 @@
"$(USER_LIBRARY_DIR)/Frameworks",
);
GCC_C_LANGUAGE_STANDARD = gnu11;
MACOSX_DEPLOYMENT_TARGET = 10.10;
MACOSX_DEPLOYMENT_TARGET = 10.13;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
@@ -5198,7 +5265,8 @@
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_OPTIMIZATION_LEVEL = 2;
GCC_PREPROCESSOR_DEFINITIONS = NDEBUG;
MACOSX_DEPLOYMENT_TARGET = 10.10;
MACOSX_DEPLOYMENT_TARGET = 10.13;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
@@ -5252,7 +5320,7 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_PARAMETER = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.13;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
@@ -5305,7 +5373,7 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_PARAMETER = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.13;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = macosx;
@@ -5327,7 +5395,6 @@
CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = DV3346VVUN;
ENABLE_HARDENED_RUNTIME = YES;
FRAMEWORK_SEARCH_PATHS = (
@@ -5373,7 +5440,6 @@
CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = DV3346VVUN;
ENABLE_HARDENED_RUNTIME = YES;
FRAMEWORK_SEARCH_PATHS = (
@@ -5416,7 +5482,6 @@
CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = "";
ENABLE_HARDENED_RUNTIME = NO;
INFOPLIST_FILE = "Clock SignalTests/Info.plist";
@@ -5440,7 +5505,6 @@
CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = "";
ENABLE_HARDENED_RUNTIME = NO;
GCC_OPTIMIZATION_LEVEL = 2;
@@ -5458,7 +5522,6 @@
4BB73ECD1B587A5100552FC2 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = CP2SKEB3XT;
INFOPLIST_FILE = "Clock SignalUITests/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
@@ -5473,7 +5536,6 @@
4BB73ECE1B587A5100552FC2 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = CP2SKEB3XT;
INFOPLIST_FILE = "Clock SignalUITests/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";

View File

@@ -31,7 +31,7 @@
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Release"
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
disableMainThreadChecker = "YES"
@@ -58,8 +58,16 @@
</CommandLineArgument>
<CommandLineArgument
argument = "&quot;/Users/thomasharte/Library/Mobile Documents/com~apple~CloudDocs/Desktop/Soft/Macintosh/MusicWorks 0.42.image&quot;"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "/Users/thomasharte/Library/Mobile\ Documents/com\~apple\~CloudDocs/Desktop/Soft/Apple\ II/Keplermatik.dsk"
isEnabled = "YES">
</CommandLineArgument>
<CommandLineArgument
argument = "/Users/thomasharte/Library/Mobile\ Documents/com\~apple\~CloudDocs/Desktop/Soft/Apple\ II/WOZs/Prince\ of\ Persia\ side\ A.woz"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "--volume=0.001"
isEnabled = "NO">

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="16097.2" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="15705" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="16097.2"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="15705"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@@ -21,15 +21,15 @@
<windowCollectionBehavior key="collectionBehavior" fullScreenPrimary="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="80" y="250" width="600" height="450"/>
<rect key="screenRect" x="0.0" y="0.0" width="3840" height="2137"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="877"/>
<value key="minSize" type="size" width="228" height="171"/>
<view key="contentView" id="gIp-Ho-8D9">
<rect key="frame" x="0.0" y="0.0" width="600" height="450"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<openGLView hidden="YES" wantsLayer="YES" useAuxiliaryDepthBufferStencil="NO" allowOffline="YES" wantsBestResolutionOpenGLSurface="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DEG-fq-cjd" customClass="CSScanTargetView">
<view hidden="YES" wantsLayer="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DEG-fq-cjd" customClass="CSScanTargetView">
<rect key="frame" x="0.0" y="0.0" width="600" height="450"/>
</openGLView>
</view>
<box hidden="YES" boxType="custom" cornerRadius="4" title="Box" titlePosition="noTitle" translatesAutoresizingMaskIntoConstraints="NO" id="4ap-Gi-2AO">
<rect key="frame" x="150" y="20" width="300" height="48"/>
<view key="contentView" id="gwO-Ty-LCX">

View File

@@ -13,7 +13,7 @@
@class CSApplication;
@protocol CSApplicationEventDelegate
@protocol CSApplicationEventDelegate <NSObject>
- (BOOL)application:(nonnull CSApplication *)application shouldSendEvent:(nonnull NSEvent *)event;
@end

View File

@@ -88,14 +88,30 @@ class MachineDocument:
}
override func close() {
// Close any dangling sheets.
//
// Be warned: in 11.0 at least, if there are any panels then posting the endSheet request
// will defer the close(), and close() will be called again at the end of that animation.
//
// So: MAKE SURE IT'S SAFE TO ENTER THIS FUNCTION TWICE. Hence the non-assumption here about
// any windows still existing.
if let window = self.windowControllers.first?.window {
for sheet in window.sheets {
window.endSheet(sheet)
}
}
// Stop the machine, if any.
machine?.stop()
// Dismiss panels.
activityPanel?.setIsVisible(false)
activityPanel = nil
optionsPanel?.setIsVisible(false)
optionsPanel = nil
// End the update cycle.
actionLock.lock()
drawLock.lock()
machine = nil
@@ -103,6 +119,7 @@ class MachineDocument:
actionLock.unlock()
drawLock.unlock()
// Let the document controller do its thing.
super.close()
}
@@ -129,10 +146,7 @@ class MachineDocument:
} else {
// Store the selected machine and list of missing ROMs, and
// show the missing ROMs dialogue.
self.missingROMs = []
for untypedMissingROM in missingROMs {
self.missingROMs.append(untypedMissingROM as! CSMissingROM)
}
self.missingROMs = missingROMs.map({$0 as! CSMissingROM})
requestRoms()
}
@@ -144,35 +158,37 @@ class MachineDocument:
private var interactionMode: InteractionMode = .notStarted
// Attempting to show a sheet before the window is visible (such as when the NIB is loaded) results in
// a sheet mysteriously floating on its own. For now, use windowDidUpdate as a proxy to know that the window
// is visible, though it's a little premature.
// a sheet mysteriously floating on its own. For now, use windowDidUpdate as a proxy to check whether
// the window is visible.
func windowDidUpdate(_ notification: Notification) {
// Grab the regular window title, if it's not already stored.
if self.unadornedWindowTitle.count == 0 {
self.unadornedWindowTitle = self.windowControllers[0].window!.title
}
// If an interaction mode is not yet in effect, pick the proper one and display the relevant thing.
if self.interactionMode == .notStarted {
// If a full machine exists, just continue showing it.
if self.machine != nil {
self.interactionMode = .showingMachine
setupMachineOutput()
return
if self.windowControllers.count > 0, let window = self.windowControllers[0].window, window.isVisible {
// Grab the regular window title, if it's not already stored.
if self.unadornedWindowTitle.count == 0 {
self.unadornedWindowTitle = window.title
}
// If a machine has been picked but is not showing, there must be ROMs missing.
if self.machineDescription != nil {
self.interactionMode = .showingROMRequester
requestRoms()
return
}
// If an interaction mode is not yet in effect, pick the proper one and display the relevant thing.
if self.interactionMode == .notStarted {
// If a full machine exists, just continue showing it.
if self.machine != nil {
self.interactionMode = .showingMachine
setupMachineOutput()
return
}
// If a machine hasn't even been picked yet, show the machine picker.
self.interactionMode = .showingMachinePicker
Bundle.main.loadNibNamed("MachinePicker", owner: self, topLevelObjects: nil)
self.machinePicker?.establishStoredOptions()
self.windowControllers[0].window?.beginSheet(self.machinePickerPanel!, completionHandler: nil)
// If a machine has been picked but is not showing, there must be ROMs missing.
if self.machineDescription != nil {
self.interactionMode = .showingROMRequester
requestRoms()
return
}
// If a machine hasn't even been picked yet, show the machine picker.
self.interactionMode = .showingMachinePicker
Bundle.main.loadNibNamed("MachinePicker", owner: self, topLevelObjects: nil)
self.machinePicker?.establishStoredOptions()
window.beginSheet(self.machinePickerPanel!, completionHandler: nil)
}
}
}
@@ -233,7 +249,7 @@ class MachineDocument:
// TODO: this needs to be threadsafe. FIX!
let maximumSamplingRate = CSAudioQueue.preferredSamplingRate()
let selectedSamplingRate = Float64(self.machine.idealSamplingRate(from: NSRange(location: 0, length: NSInteger(maximumSamplingRate))))
let isStereo = self.machine.isStereo()
let isStereo = self.machine.isStereo
if selectedSamplingRate > 0 {
// [Re]create the audio queue only if necessary.
if self.audioQueue == nil || self.audioQueue.samplingRate != selectedSamplingRate {
@@ -265,9 +281,7 @@ class MachineDocument:
/// Delegate message to receive drag and drop files.
final func scanTargetView(_ view: CSScanTargetView, didReceiveFileAt URL: URL) {
let mediaSet = CSMediaSet(fileAt: URL)
if let mediaSet = mediaSet {
mediaSet.apply(to: self.machine)
}
mediaSet.apply(to: self.machine)
}
/// Action for the insert menu command; displays an NSOpenPanel and then segues into the same process
@@ -279,9 +293,7 @@ class MachineDocument:
if response == .OK {
for url in openPanel.urls {
let mediaSet = CSMediaSet(fileAt: url)
if let mediaSet = mediaSet {
mediaSet.apply(to: self.machine)
}
mediaSet.apply(to: self.machine)
}
}
}

View File

@@ -552,6 +552,26 @@
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>dat</string>
</array>
<key>CFBundleTypeName</key>
<string>Electron/BBC Hard Disk Image</string>
<key>CFBundleTypeOSTypes</key>
<array>
<string>????</string>
</array>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSTypeIsPackage</key>
<false/>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
</dict>
</array>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>

View File

@@ -10,17 +10,35 @@
@import IOKit;
#include <IOKit/hid/IOHIDLib.h>
@import GameController;
#pragma mark - CSJoystickButton
@implementation CSJoystickButton {
@package
bool _isPressed;
}
- (instancetype)initWithIndex:(NSInteger)index {
if (self = [super init]) {
_index = index;
}
return self;
}
@end
@interface CSIOJoystickButton: CSJoystickButton
@end
@implementation CSIOJoystickButton {
IOHIDElementRef _element;
}
- (instancetype)initWithElement:(IOHIDElementRef)element index:(NSInteger)index {
self = [super init];
self = [super initWithIndex:index];
if(self) {
_index = index;
_element = (IOHIDElementRef)CFRetain(element);
}
return self;
@@ -31,7 +49,7 @@
}
- (NSString *)description {
return [NSString stringWithFormat:@"<CSJoystickButton: %p>; button %ld, %@", self, (long)self.index, self.isPressed ? @"pressed" : @"released"];
return [NSString stringWithFormat:@"<CSIOJoystickButton: %p>; button %ld, %@", self, (long)self.index, self.isPressed ? @"pressed" : @"released"];
}
- (IOHIDElementRef)element {
@@ -47,14 +65,33 @@
#pragma mark - CSJoystickAxis
@implementation CSJoystickAxis {
@package
float _position;
}
- (instancetype)initWithType:(CSJoystickAxisType)type
{
if (self = [super init]) {
_type = type;
}
return self;
}
@end
@interface CSIOJoystickAxis: CSJoystickAxis
@end
@implementation CSIOJoystickAxis {
IOHIDElementRef _element;
}
- (instancetype)initWithElement:(IOHIDElementRef)element type:(CSJoystickAxisType)type {
self = [super init];
self = [super initWithType:type];
if(self) {
_element = (IOHIDElementRef)CFRetain(element);
_type = type;
_position = 0.5f;
}
return self;
@@ -65,7 +102,7 @@
}
- (NSString *)description {
return [NSString stringWithFormat:@"<CSJoystickAxis: %p>; type %d, value %0.2f", self, (int)self.type, self.position];
return [NSString stringWithFormat:@"<CSIOJoystickAxis: %p>; type %d, value %0.2f", self, (int)self.type, self.position];
}
- (IOHIDElementRef)element {
@@ -81,6 +118,18 @@
#pragma mark - CSJoystickHat
@implementation CSJoystickHat {
@package
CSJoystickHatDirection _direction;
}
@end
@interface CSIOJoystickHat: CSJoystickHat
@end
@implementation CSIOJoystickHat {
IOHIDElementRef _element;
}
@@ -97,7 +146,7 @@
}
- (NSString *)description {
return [NSString stringWithFormat:@"<CSJoystickHat: %p>; direction %ld", self, (long)self.direction];
return [NSString stringWithFormat:@"<CSIOJoystickHat: %p>; direction %ld", self, (long)self.direction];
}
- (IOHIDElementRef)element {
@@ -113,6 +162,24 @@
#pragma mark - CSJoystick
@implementation CSJoystick {
@package
NSArray<CSJoystickButton *> *_buttons;
NSArray<CSJoystickAxis *> *_axes;
NSArray<CSJoystickHat *> *_hats;
}
- (void)update
{
//subclass!
}
@end
@interface CSIOJoystick: CSJoystick
@end
@implementation CSIOJoystick {
IOHIDDeviceRef _device;
}
@@ -142,12 +209,12 @@
}
- (NSString *)description {
return [NSString stringWithFormat:@"<CSJoystick: %p>; buttons %@, axes %@, hats %@", self, self.buttons, self.axes, self.hats];
return [NSString stringWithFormat:@"<CSIOJoystick: %p>; buttons %@, axes %@, hats %@", self, self.buttons, self.axes, self.hats];
}
- (void)update {
// Update buttons.
for(CSJoystickButton *button in _buttons) {
for(CSIOJoystickButton *button in _buttons) {
IOHIDValueRef value;
if(IOHIDDeviceGetValue(_device, button.element, &value) == kIOReturnSuccess) {
// Some pressure-sensitive buttons return values greater than 1 for hard presses,
@@ -157,7 +224,7 @@
}
// Update hats.
for(CSJoystickHat *hat in _hats) {
for(CSIOJoystickHat *hat in _hats) {
IOHIDValueRef value;
if(IOHIDDeviceGetValue(_device, hat.element, &value) == kIOReturnSuccess) {
// Hats report a direction, which is either one of eight or one of four.
@@ -181,7 +248,7 @@
}
// Update axes.
for(CSJoystickAxis *axis in _axes) {
for(CSIOJoystickAxis *axis in _axes) {
IOHIDValueRef value;
if(IOHIDDeviceGetValue(_device, axis.element, &value) == kIOReturnSuccess) {
const CFIndex integerValue = IOHIDValueGetIntegerValue(value) - IOHIDElementGetLogicalMin(axis.element);
@@ -197,11 +264,157 @@
@end
#pragma mark - GameController subclasses
API_AVAILABLE(macos(11.0))
@interface CSGCJoystickHat: CSJoystickHat
@property (readonly, strong) GCDeviceDirectionPad *directionPad;
@end
API_AVAILABLE(macos(11.0))
@interface CSGCJoystickAxis: CSJoystickAxis
@property (readonly, strong) GCDeviceAxisInput *axis;
@end
API_AVAILABLE(macos(11.0))
@interface CSGCJoystickButton: CSJoystickButton
@property (readonly, strong) GCDeviceButtonInput *button;
@end
API_AVAILABLE(macos(11.0))
@interface CSGCJoystick: CSJoystick
@property (readonly, strong) GCController *device;
@end
@implementation CSGCJoystickHat
- (instancetype)initWithDirectionPad:(GCDeviceDirectionPad*)dPad {
if (self = [super init]) {
_directionPad = dPad;
}
return self;
}
- (NSString *)description {
return [NSString stringWithFormat:@"<CSGCJoystickHat: %p>; direction %ld", self, (long)self.direction];
}
- (void)setDirection:(CSJoystickHatDirection)direction {
_direction = direction;
}
@end
@implementation CSGCJoystickAxis
- (instancetype)initWithAxis:(GCDeviceAxisInput*)element type:(CSJoystickAxisType)type {
self = [super initWithType:type];
if(self) {
_axis = element;
_position = 0.5f;
}
return self;
}
- (NSString *)description {
return [NSString stringWithFormat:@"<CSGCJoystickAxis: %p>; type %d, value %0.2f", self, (int)self.type, self.position];
}
- (void)setPosition:(float)position {
_position = position;
}
@end
@implementation CSGCJoystickButton
- (instancetype)initWithButton:(GCDeviceButtonInput*)element index:(NSInteger)index {
self = [super initWithIndex:index];
if(self) {
_button = element;
}
return self;
}
- (NSString *)description {
return [NSString stringWithFormat:@"<CSGCJoystickButton: %p>; button %ld, %@", self, (long)self.index, self.isPressed ? @"pressed" : @"released"];
}
- (void)setIsPressed:(bool)isPressed {
_isPressed = isPressed;
}
@end
@implementation CSGCJoystick
- (instancetype)initWithButtons:(NSArray<CSJoystickButton *> *)buttons
axes:(NSArray<CSJoystickAxis *> *)axes
hats:(NSArray<CSJoystickHat *> *)hats
device:(GCController*)device {
if (self = [super init]) {
// Sort buttons by index.
_buttons = [buttons sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"index" ascending:YES]]];
// Sort axes by enum value.
_axes = [axes sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"type" ascending:YES]]];
// Hats have no guaranteed ordering.
_hats = hats;
// Keep hold of the device.
_device = device;
}
return self;
}
-(void)update {
// Update buttons.
for(CSGCJoystickButton *button in _buttons) {
// This assumes that the values provided by GCDeviceButtonInput are
// digital. This might not always be the case.
button.isPressed = button.button.pressed;
}
for(CSGCJoystickAxis *axis in _axes) {
float val = axis.axis.value;
val += 1;
val /= 2;
axis.position = val;
}
for(CSGCJoystickHat *hat in _hats) {
// This assumes that the values provided by GCDeviceDirectionPad are
// digital. this might not always be the case.
CSJoystickHatDirection hatDir = 0;
if (hat.directionPad.down.pressed) {
hatDir |= CSJoystickHatDirectionDown;
}
if (hat.directionPad.up.pressed) {
hatDir |= CSJoystickHatDirectionUp;
}
if (hat.directionPad.left.pressed) {
hatDir |= CSJoystickHatDirectionLeft;
}
if (hat.directionPad.right.pressed) {
hatDir |= CSJoystickHatDirectionRight;
}
// There shouldn't be any conflicting directions.
hat.direction = hatDir;
}
}
- (NSString *)description {
return [NSString stringWithFormat:@"<CSGCJoystick: %p>; buttons %@, axes %@, hats %@", self, self.buttons, self.axes, self.hats];
}
@end
#pragma mark - CSJoystickManager
@interface CSJoystickManager ()
- (void)deviceMatched:(IOHIDDeviceRef)device result:(IOReturn)result sender:(void *)sender;
- (void)deviceRemoved:(IOHIDDeviceRef)device result:(IOReturn)result sender:(void *)sender;
- (void)controllerDidConnect:(NSNotification *)note API_AVAILABLE(macos(11.0));
- (void)controllerDidDisconnect:(NSNotification *)note API_AVAILABLE(macos(11.0));
@end
static void DeviceMatched(void *context, IOReturn result, void *sender, IOHIDDeviceRef device) {
@@ -235,6 +448,10 @@ static void DeviceRemoved(void *context, IOReturn result, void *sender, IOHIDDev
IOHIDManagerRegisterDeviceMatchingCallback(_hidManager, DeviceMatched, (__bridge void *)self);
IOHIDManagerRegisterDeviceRemovalCallback(_hidManager, DeviceRemoved, (__bridge void *)self);
IOHIDManagerScheduleWithRunLoop(_hidManager, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
if (@available(macOS 11.0, *)) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(controllerDidConnect:) name:GCControllerDidConnectNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(controllerDidDisconnect:) name:GCControllerDidDisconnectNotification object:nil];
}
if(IOHIDManagerOpen(_hidManager, kIOHIDOptionsTypeNone) != kIOReturnSuccess) {
NSLog(@"Failed to open HID manager");
@@ -246,15 +463,80 @@ static void DeviceRemoved(void *context, IOReturn result, void *sender, IOHIDDev
return self;
}
- (void)controllerDidConnect:(NSNotification *)note {
GCController *controller = note.object;
// Double check this joystick isn't already known.
for(CSGCJoystick *joystick in _joysticks) {
if (![joystick isKindOfClass:[CSGCJoystick class]]) {
continue;
}
if([joystick.device isEqual:controller]) return;
}
// Prepare to collate a list of buttons, axes and hats for the new device.
NSMutableArray<CSJoystickButton *> *buttons = [[NSMutableArray alloc] init];
NSMutableArray<CSJoystickAxis *> *axes = [[NSMutableArray alloc] init];
NSMutableArray<CSJoystickHat *> *hats = [[NSMutableArray alloc] init];
if (controller.extendedGamepad) {
GCExtendedGamepad *gp = controller.extendedGamepad;
// Let's go a b x y
// 1 2 3 4
[buttons addObject:[[CSGCJoystickButton alloc] initWithButton:gp.buttonA index:1]];
[buttons addObject:[[CSGCJoystickButton alloc] initWithButton:gp.buttonB index:2]];
[buttons addObject:[[CSGCJoystickButton alloc] initWithButton:gp.buttonX index:3]];
[buttons addObject:[[CSGCJoystickButton alloc] initWithButton:gp.buttonY index:4]];
[hats addObject:[[CSGCJoystickHat alloc] initWithDirectionPad:gp.dpad]];
[axes addObject:[[CSGCJoystickAxis alloc] initWithAxis:gp.leftThumbstick.xAxis type:CSJoystickAxisTypeX]];
[axes addObject:[[CSGCJoystickAxis alloc] initWithAxis:gp.leftThumbstick.yAxis type:CSJoystickAxisTypeY]];
[axes addObject:[[CSGCJoystickAxis alloc] initWithAxis:gp.rightThumbstick.xAxis type:CSJoystickAxisTypeZ]];
} else {
return;
}
// Add this joystick to the list.
[_joysticks addObject:[[CSGCJoystick alloc] initWithButtons:buttons axes:axes hats:hats device:controller]];
}
- (void)controllerDidDisconnect:(NSNotification *)note {
GCController *controller = note.object;
// If this joystick was recorded, remove it.
for(CSGCJoystick *joystick in [_joysticks copy]) {
if (![joystick isKindOfClass:[CSGCJoystick class]]) {
continue;
}
if([joystick.device isEqual:controller]) {
[_joysticks removeObject:joystick];
return;
}
}
}
- (void)dealloc {
IOHIDManagerUnscheduleFromRunLoop(_hidManager, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
IOHIDManagerClose(_hidManager, kIOHIDOptionsTypeNone);
CFRelease(_hidManager);
if (@available(macOS 11.0, *)) {
[[NSNotificationCenter defaultCenter] removeObserver:self name:GCControllerDidConnectNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:GCControllerDidDisconnectNotification object:nil];
}
}
- (void)deviceMatched:(IOHIDDeviceRef)device result:(IOReturn)result sender:(void *)sender {
if (@available(macOS 11.0, *)) {
if ([GCController supportsHIDDevice:device]) {
return;
}
}
// Double check this joystick isn't already known.
for(CSJoystick *joystick in _joysticks) {
for(CSIOJoystick *joystick in _joysticks) {
if (![joystick isKindOfClass:[CSIOJoystick class]]) {
continue;
}
if(joystick.device == device) return;
}
@@ -279,7 +561,7 @@ static void DeviceRemoved(void *context, IOReturn result, void *sender, IOHIDDev
case kIOHIDElementTypeInput_Button: {
// Add a button; pretty easy stuff. 'Usage' provides a button index.
const uint32_t usage = IOHIDElementGetUsage(element);
[buttons addObject:[[CSJoystickButton alloc] initWithElement:element index:usage]];
[buttons addObject:[[CSIOJoystickButton alloc] initWithElement:element index:usage]];
} break;
case kIOHIDElementTypeInput_Misc:
@@ -296,25 +578,28 @@ static void DeviceRemoved(void *context, IOReturn result, void *sender, IOHIDDev
// A hatswitch is a multi-directional control all of its own.
case kHIDUsage_GD_Hatswitch:
[hats addObject:[[CSJoystickHat alloc] initWithElement:element]];
[hats addObject:[[CSIOJoystickHat alloc] initWithElement:element]];
continue;
}
// Add the axis; if it was a hat switch or unrecognised then the code doesn't
// reach here.
[axes addObject:[[CSJoystickAxis alloc] initWithElement:element type:axisType]];
[axes addObject:[[CSIOJoystickAxis alloc] initWithElement:element type:axisType]];
} break;
}
}
CFRelease(elements);
// Add this joystick to the list.
[_joysticks addObject:[[CSJoystick alloc] initWithButtons:buttons axes:axes hats:hats device:device]];
[_joysticks addObject:[[CSIOJoystick alloc] initWithButtons:buttons axes:axes hats:hats device:device]];
}
- (void)deviceRemoved:(IOHIDDeviceRef)device result:(IOReturn)result sender:(void *)sender {
// If this joystick was recorded, remove it.
for(CSJoystick *joystick in [_joysticks copy]) {
for(CSIOJoystick *joystick in [_joysticks copy]) {
if (![joystick isKindOfClass:[CSIOJoystick class]]) {
continue;
}
if(joystick.device == device) {
[_joysticks removeObject:joystick];
return;

View File

@@ -14,7 +14,7 @@
#import "CSStaticAnalyser.h"
@class CSMachine;
@protocol CSMachineDelegate
@protocol CSMachineDelegate <NSObject>
- (void)machineSpeakerDidChangeInputClock:(nonnull CSMachine *)machine;
- (void)machine:(nonnull CSMachine *)machine led:(nonnull NSString *)led didChangeToLit:(BOOL)isLit;
- (void)machine:(nonnull CSMachine *)machine ledShouldBlink:(nonnull NSString *)led;
@@ -59,7 +59,7 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {
- (nullable instancetype)initWithAnalyser:(nonnull CSStaticAnalyser *)result missingROMs:(nullable inout NSMutableArray<CSMissingROM *> *)missingROMs NS_DESIGNATED_INITIALIZER;
- (float)idealSamplingRateFromRange:(NSRange)range;
- (BOOL)isStereo;
@property (readonly, getter=isStereo) BOOL stereo;
- (void)setAudioSamplingRate:(float)samplingRate bufferSize:(NSUInteger)bufferSize stereo:(BOOL)stereo;
- (void)setView:(nullable CSScanTargetView *)view aspectRatio:(float)aspectRatio;

View File

@@ -74,7 +74,7 @@ struct ActivityObserver: public Activity::Observer {
__unsafe_unretained CSMachine *machine;
};
@interface CSMissingROM (Mutability)
@interface CSMissingROM (/*Mutability*/)
@property (nonatomic, nonnull, copy) NSString *machineName;
@property (nonatomic, nonnull, copy) NSString *fileName;
@property (nonatomic, nullable, copy) NSString *descriptiveName;
@@ -82,53 +82,13 @@ struct ActivityObserver: public Activity::Observer {
@property (nonatomic, copy) NSArray<NSNumber *> *crc32s;
@end
@implementation CSMissingROM {
NSString *_machineName;
NSString *_fileName;
NSString *_descriptiveName;
NSUInteger _size;
NSArray<NSNumber *> *_crc32s;
}
@implementation CSMissingROM
- (NSString *)machineName {
return _machineName;
}
- (void)setMachineName:(NSString *)machineName {
_machineName = [machineName copy];
}
- (NSString *)fileName {
return _fileName;
}
- (void)setFileName:(NSString *)fileName {
_fileName = [fileName copy];
}
- (NSString *)descriptiveName {
return _descriptiveName;
}
- (void)setDescriptiveName:(NSString *)descriptiveName {
_descriptiveName = [descriptiveName copy];
}
- (NSUInteger)size {
return _size;
}
- (void)setSize:(NSUInteger)size {
_size = size;
}
- (NSArray<NSNumber *> *)crc32s {
return _crc32s;
}
- (void)setCrc32s:(NSArray<NSNumber *> *)crc32s {
_crc32s = [crc32s copy];
}
@synthesize machineName=_machineName;
@synthesize fileName=_fileName;
@synthesize descriptiveName=_descriptiveName;
@synthesize size=_size;
@synthesize crc32s=_crc32s;
- (NSString *)description {
return [NSString stringWithFormat:@"%@/%@, %lu bytes, CRCs: %@", _fileName, _descriptiveName, (unsigned long)_size, _crc32s];
@@ -179,17 +139,17 @@ struct ActivityObserver: public Activity::Observer {
CSMissingROM *rom = [[CSMissingROM alloc] init];
// Copy/convert the primitive fields.
rom.machineName = [NSString stringWithUTF8String:missing_rom.machine_name.c_str()];
rom.fileName = [NSString stringWithUTF8String:missing_rom.file_name.c_str()];
rom.descriptiveName = missing_rom.descriptive_name.empty() ? nil : [NSString stringWithUTF8String:missing_rom.descriptive_name.c_str()];
rom.machineName = @(missing_rom.machine_name.c_str());
rom.fileName = @(missing_rom.file_name.c_str());
rom.descriptiveName = missing_rom.descriptive_name.empty() ? nil : @(missing_rom.descriptive_name.c_str());
rom.size = missing_rom.size;
// Convert the CRC list.
NSMutableArray<NSNumber *> *crc32s = [[NSMutableArray alloc] init];
NSMutableArray<NSNumber *> *crc32s = [[NSMutableArray alloc] initWithCapacity:missing_rom.crc32s.size()];
for(const auto &crc : missing_rom.crc32s) {
[crc32s addObject:@(crc)];
}
rom.crc32s = [crc32s copy];
rom.crc32s = crc32s;
// Add to the missing list.
[missingROMs addObject:rom];

View File

@@ -8,7 +8,7 @@
#import "CSStaticAnalyser.h"
@interface CSStaticAnalyser (ResultVector)
@interface CSStaticAnalyser ()
- (Analyser::Static::TargetList &)targets;

View File

@@ -8,6 +8,8 @@
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@class CSMachine;
typedef NS_ENUM(NSInteger, CSMachineAppleIIModel) {
@@ -72,9 +74,9 @@ typedef int Kilobytes;
@interface CSStaticAnalyser : NSObject
- (instancetype)initWithFileAtURL:(NSURL *)url;
- (nullable instancetype)initWithFileAtURL:(NSURL *)url;
- (instancetype)initWithElectronDFS:(BOOL)dfs adfs:(BOOL)adfs;
- (instancetype)initWithElectronDFS:(BOOL)dfs adfs:(BOOL)adfs ap6:(BOOL)ap6 sidewaysRAM:(BOOL)sidewaysRAM;
- (instancetype)initWithAmstradCPCModel:(CSMachineCPCModel)model;
- (instancetype)initWithMSXRegion:(CSMachineMSXRegion)region hasDiskDrive:(BOOL)hasDiskDrive;
- (instancetype)initWithOricModel:(CSMachineOricModel)model diskInterface:(CSMachineOricDiskInterface)diskInterface;
@@ -85,7 +87,7 @@ typedef int Kilobytes;
- (instancetype)initWithMacintoshModel:(CSMachineMacintoshModel)model;
- (instancetype)initWithAtariSTModel:(CSMachineAtariSTModel)model;
@property(nonatomic, readonly) NSString *optionsPanelNibName;
@property(nonatomic, readonly, nullable) NSString *optionsPanelNibName;
@property(nonatomic, readonly) NSString *displayName;
@end
@@ -96,3 +98,5 @@ typedef int Kilobytes;
- (void)applyToMachine:(CSMachine *)machine;
@end
NS_ASSUME_NONNULL_END

View File

@@ -39,7 +39,7 @@
// TODO: could this better be supplied by the analyser? A hypothetical file format might
// provide a better name for it contents than the file name?
_displayName = [[url pathComponents] lastObject];
_displayName = [url.lastPathComponent copy];
}
return self;
}
@@ -93,13 +93,15 @@
return self;
}
- (instancetype)initWithElectronDFS:(BOOL)dfs adfs:(BOOL)adfs {
- (instancetype)initWithElectronDFS:(BOOL)dfs adfs:(BOOL)adfs ap6:(BOOL)ap6 sidewaysRAM:(BOOL)sidewaysRAM {
self = [super init];
if(self) {
using Target = Analyser::Static::Acorn::Target;
auto target = std::make_unique<Target>();
target->has_dfs = !!dfs;
target->has_adfs = !!adfs;
target->has_dfs = dfs;
target->has_pres_adfs = adfs;
target->has_ap6_rom = ap6;
target->has_sideways_ram = sidewaysRAM;
_targets.push_back(std::move(target));
}
return self;

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="17506" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="17701" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17506"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17701"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@@ -18,7 +18,7 @@
<windowStyleMask key="styleMask" titled="YES" documentModal="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="196" y="240" width="725" height="205"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1440"/>
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
<rect key="frame" x="0.0" y="0.0" width="725" height="205"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
@@ -189,31 +189,49 @@ Gw
</tabViewItem>
<tabViewItem label="Electron" identifier="electron" id="muc-z9-Vqc">
<view key="view" id="SRc-2D-95G">
<rect key="frame" x="10" y="33" width="674" height="94"/>
<rect key="frame" x="10" y="33" width="679" height="95"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="JqM-IK-FMP">
<rect key="frame" x="15" y="75" width="164" height="18"/>
<rect key="frame" x="15" y="75" width="168" height="18"/>
<buttonCell key="cell" type="check" title="With Disk Filing System" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="tpW-5C-xKp">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="945-wU-JOH">
<rect key="frame" x="15" y="55" width="228" height="18"/>
<rect key="frame" x="15" y="53" width="232" height="18"/>
<buttonCell key="cell" type="check" title="With Advanced Disk Filing System" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="S0c-Jg-7Pu">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="cG2-Ph-S3Z">
<rect key="frame" x="15" y="31" width="231" height="18"/>
<buttonCell key="cell" type="check" title="With Advanced Plus 6 Utility ROM" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="yjF-XS-zx6">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="lzo-8g-o4S">
<rect key="frame" x="15" y="9" width="284" height="18"/>
<buttonCell key="cell" type="check" title="Fill unused ROM banks with sideways RAM" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="JEz-eK-uWp">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
</subviews>
<constraints>
<constraint firstItem="945-wU-JOH" firstAttribute="leading" secondItem="SRc-2D-95G" secondAttribute="leading" constant="17" id="1iM-70-oZq"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="945-wU-JOH" secondAttribute="bottom" constant="17" id="7ZL-rR-aaz"/>
<constraint firstItem="cG2-Ph-S3Z" firstAttribute="top" secondItem="945-wU-JOH" secondAttribute="bottom" constant="6" symbolic="YES" id="E9b-RP-9vj"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="lzo-8g-o4S" secondAttribute="bottom" constant="8" id="FM6-AA-Vhf"/>
<constraint firstItem="JqM-IK-FMP" firstAttribute="leading" secondItem="SRc-2D-95G" secondAttribute="leading" constant="17" id="NfY-dE-aJw"/>
<constraint firstItem="lzo-8g-o4S" firstAttribute="top" secondItem="cG2-Ph-S3Z" secondAttribute="bottom" constant="6" symbolic="YES" id="S45-42-Gtv"/>
<constraint firstItem="lzo-8g-o4S" firstAttribute="leading" secondItem="SRc-2D-95G" secondAttribute="leading" constant="17" id="b5a-SX-2ty"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="945-wU-JOH" secondAttribute="trailing" constant="20" id="dmY-PV-ap4"/>
<constraint firstItem="JqM-IK-FMP" firstAttribute="top" secondItem="SRc-2D-95G" secondAttribute="top" constant="3" id="ggl-QH-mV4"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="JqM-IK-FMP" secondAttribute="trailing" constant="20" id="mvO-UZ-BtT"/>
<constraint firstItem="cG2-Ph-S3Z" firstAttribute="leading" secondItem="SRc-2D-95G" secondAttribute="leading" constant="17" id="npw-IZ-6xU"/>
<constraint firstItem="945-wU-JOH" firstAttribute="top" secondItem="JqM-IK-FMP" secondAttribute="bottom" constant="6" id="pes-xi-zkv"/>
</constraints>
</view>
@@ -257,18 +275,18 @@ Gw
</tabViewItem>
<tabViewItem label="MSX" identifier="msx" id="6SR-DY-zdI">
<view key="view" id="mWD-An-tR7">
<rect key="frame" x="10" y="33" width="674" height="94"/>
<rect key="frame" x="10" y="33" width="679" height="95"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="8xT-Pr-8SE">
<rect key="frame" x="15" y="48" width="124" height="18"/>
<rect key="frame" x="15" y="49" width="128" height="18"/>
<buttonCell key="cell" type="check" title="Attach disk drive" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="CB3-nA-VTM">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="LG6-mP-SeG">
<rect key="frame" x="69" y="67" width="145" height="25"/>
<rect key="frame" x="68" y="68" width="146" height="25"/>
<popUpButtonCell key="cell" type="push" title="European (PAL)" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="xAh-Ch-tby" id="yR4-yv-Lvu">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@@ -282,7 +300,7 @@ Gw
</popUpButtonCell>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ZaD-7v-rMS">
<rect key="frame" x="15" y="73" width="50" height="16"/>
<rect key="frame" x="15" y="74" width="50" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Region:" id="x4m-eh-Nif">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@@ -551,7 +569,7 @@ Gw
<constraint firstItem="VUb-QG-x7c" firstAttribute="top" secondItem="EiT-Mj-1SZ" secondAttribute="top" constant="20" id="zT3-Ea-QQJ"/>
</constraints>
</view>
<point key="canvasLocation" x="34" y="88.5"/>
<point key="canvasLocation" x="33.5" y="88.5"/>
</window>
<customObject id="192-Eb-Rpg" customClass="MachinePicker" customModule="Clock_Signal" customModuleProvider="target">
<connections>
@@ -559,7 +577,9 @@ Gw
<outlet property="appleIIModelButton" destination="jli-ac-Sij" id="Jm3-f7-C17"/>
<outlet property="cpcModelTypeButton" destination="00d-sg-Krh" id="VyV-b1-A6x"/>
<outlet property="electronADFSButton" destination="945-wU-JOH" id="Fjm-W8-kvh"/>
<outlet property="electronAP6Button" destination="cG2-Ph-S3Z" id="vkq-1J-KBG"/>
<outlet property="electronDFSButton" destination="JqM-IK-FMP" id="C80-1k-TdQ"/>
<outlet property="electronSidewaysRAMButton" destination="lzo-8g-o4S" id="LtS-wv-tMf"/>
<outlet property="machineSelector" destination="VUb-QG-x7c" id="crR-hB-jGd"/>
<outlet property="macintoshModelTypeButton" destination="xa6-NA-JY5" id="2jX-PY-v2z"/>
<outlet property="msxHasDiskDriveButton" destination="8xT-Pr-8SE" id="zGH-GA-9QF"/>

View File

@@ -18,6 +18,8 @@ class MachinePicker: NSObject {
// MARK: - Electron properties
@IBOutlet var electronDFSButton: NSButton?
@IBOutlet var electronADFSButton: NSButton?
@IBOutlet var electronAP6Button: NSButton?
@IBOutlet var electronSidewaysRAMButton: NSButton?
// MARK: - CPC properties
@IBOutlet var cpcModelTypeButton: NSPopUpButton?
@@ -51,7 +53,11 @@ class MachinePicker: NSObject {
// Machine type
if let machineIdentifier = standardUserDefaults.string(forKey: "new.machine") {
machineSelector?.selectTabViewItem(withIdentifier: machineIdentifier as Any)
// If I've changed my mind about visible tabs between versions, there may not be one that corresponds
// to the stored identifier. Make sure not to raise an NSRangeException in that scenario.
if let index = machineSelector?.indexOfTabViewItem(withIdentifier: machineIdentifier as Any), index != NSNotFound {
machineSelector?.selectTabViewItem(at: index)
}
}
// Apple II settings
@@ -61,6 +67,8 @@ class MachinePicker: NSObject {
// Electron settings
electronDFSButton?.state = standardUserDefaults.bool(forKey: "new.electronDFS") ? .on : .off
electronADFSButton?.state = standardUserDefaults.bool(forKey: "new.electronADFS") ? .on : .off
electronAP6Button?.state = standardUserDefaults.bool(forKey: "new.electronAP6") ? .on : .off
electronSidewaysRAMButton?.state = standardUserDefaults.bool(forKey: "new.electronSidewaysRAM") ? .on : .off
// CPC settings
cpcModelTypeButton?.selectItem(withTag: standardUserDefaults.integer(forKey: "new.cpcModel"))
@@ -102,6 +110,8 @@ class MachinePicker: NSObject {
// Electron settings
standardUserDefaults.set(electronDFSButton!.state == .on, forKey: "new.electronDFS")
standardUserDefaults.set(electronADFSButton!.state == .on, forKey: "new.electronADFS")
standardUserDefaults.set(electronAP6Button!.state == .on, forKey: "new.electronAP6")
standardUserDefaults.set(electronSidewaysRAMButton!.state == .on, forKey: "new.electronSidewaysRAM")
// CPC settings
standardUserDefaults.set(cpcModelTypeButton!.selectedTag(), forKey: "new.cpcModel")
@@ -136,7 +146,11 @@ class MachinePicker: NSObject {
switch machineSelector!.selectedTabViewItem!.identifier as! String {
case "electron":
return CSStaticAnalyser(electronDFS: electronDFSButton!.state == .on, adfs: electronADFSButton!.state == .on)!
return CSStaticAnalyser(
electronDFS: electronDFSButton!.state == .on,
adfs: electronADFSButton!.state == .on,
ap6: electronAP6Button!.state == .on,
sidewaysRAM: electronSidewaysRAMButton!.state == .on)
case "appleii":
var model: CSMachineAppleIIModel = .appleII

View File

@@ -10,7 +10,7 @@
@class CSROMReceiverView;
@protocol CSROMReciverViewDelegate
@protocol CSROMReciverViewDelegate <NSObject>
/*!
Announces receipt of a file by drag and drop to the delegate.
@param view The view making the request.

View File

@@ -396,8 +396,13 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget;
}
- (void)updateSizeBuffersToSize:(CGSize)size {
const NSUInteger frameBufferWidth = NSUInteger(size.width * _view.layer.contentsScale) * (_isUsingSupersampling ? 2 : 1);
const NSUInteger frameBufferHeight = NSUInteger(size.height * _view.layer.contentsScale) * (_isUsingSupersampling ? 2 : 1);
// Anecdotally, the size provided here, which ultimately is from _view.drawableSize,
// already factors in Retina-style scaling.
//
// 16384 has been the maximum texture size in all Mac versions of Metal so far, and
// I haven't yet found a way to query it dynamically. So it's hard-coded.
const NSUInteger frameBufferWidth = MIN(NSUInteger(size.width) * (_isUsingSupersampling ? 2 : 1), 16384);
const NSUInteger frameBufferHeight = MIN(NSUInteger(size.height) * (_isUsingSupersampling ? 2 : 1), 16384);
// Generate a framebuffer and a stencil.
MTLTextureDescriptor *const textureDescriptor = [MTLTextureDescriptor

View File

@@ -0,0 +1,390 @@
//
// PowerPCDecoderTests.m
// Clock Signal
//
// Created by Thomas Harte on 02/01/2021.
// Copyright 2021 Thomas Harte. All rights reserved.
//
#import <XCTest/XCTest.h>
#include "../../../InstructionSets/PowerPC/Decoder.hpp"
namespace {
using Operation = CPU::Decoder::PowerPC::Operation;
using Instruction = CPU::Decoder::PowerPC::Instruction;
}
@interface PowerPCDecoderTests : XCTestCase
@end
/*!
Tests PowerPC decoding by throwing a bunch of randomly-generated
word streams and checking that the result matches what I got from a
disassembler elsewhere.
*/
@implementation PowerPCDecoderTests {
Instruction instructions[32];
}
// MARK: - Specific instruction asserts.
- (void)assertUndefined:(Instruction &)instruction {
XCTAssertEqual(instruction.operation, Operation::Undefined);
}
- (void)assert:(Instruction &)instruction operation:(Operation)operation rD:(int)rD rA:(int)rA simm:(int)simm {
XCTAssertEqual(instruction.operation, operation);
XCTAssertEqual(instruction.rD(), rD);
XCTAssertEqual(instruction.rA(), rA);
XCTAssertEqual(instruction.simm(), simm);
}
- (void)assert:(Instruction &)instruction operation:(Operation)operation rD:(int)rD rA:(int)rA d:(int)d {
XCTAssertEqual(instruction.operation, operation);
XCTAssertEqual(instruction.rD(), rD);
XCTAssertEqual(instruction.rA(), rA);
XCTAssertEqual(instruction.d(), d);
}
- (void)assert:(Instruction &)instruction operation:(Operation)operation rA:(int)rA rS:(int)rS uimm:(int)uimm {
XCTAssertEqual(instruction.operation, operation);
XCTAssertEqual(instruction.rA(), rA);
XCTAssertEqual(instruction.rS(), rS);
XCTAssertEqual(instruction.uimm(), uimm);
}
- (void)assert:(Instruction &)instruction operation:(Operation)operation rS:(int)rS rA:(int)rA d:(int)d {
XCTAssertEqual(instruction.operation, operation);
XCTAssertEqual(instruction.rS(), rS);
XCTAssertEqual(instruction.rA(), rA);
XCTAssertEqual(instruction.d(), d);
}
- (void)assert:(Instruction &)instruction operation:(Operation)operation frD:(int)frD rA:(int)rA d:(int)d {
XCTAssertEqual(instruction.operation, operation);
XCTAssertEqual(instruction.frD(), frD);
XCTAssertEqual(instruction.rA(), rA);
XCTAssertEqual(instruction.d(), d);
}
- (void)assert:(Instruction &)instruction operation:(Operation)operation frS:(int)frS rA:(int)rA d:(int)d {
XCTAssertEqual(instruction.operation, operation);
XCTAssertEqual(instruction.frS(), frS);
XCTAssertEqual(instruction.rA(), rA);
XCTAssertEqual(instruction.d(), d);
}
- (void)assert:(Instruction &)instruction operation:(Operation)operation li:(uint32_t)li lk:(BOOL)lk aa:(BOOL)aa {
XCTAssertEqual(instruction.operation, operation);
XCTAssertEqual(instruction.li(), li);
XCTAssertEqual(!!instruction.lk(), lk);
XCTAssertEqual(!!instruction.aa(), aa);
}
- (void)assert:(Instruction &)instruction operation:(Operation)operation bo:(int)bo bi:(int)bi bd:(int)bd lk:(BOOL)lk aa:(BOOL)aa {
XCTAssertEqual(instruction.operation, operation);
XCTAssertEqual(instruction.bo(), bo);
XCTAssertEqual(instruction.bi(), bi);
XCTAssertEqual(instruction.bd(), bd);
XCTAssertEqual(!!instruction.lk(), lk);
XCTAssertEqual(!!instruction.aa(), aa);
}
- (void)assert:(Instruction &)instruction operation:(Operation)operation rA:(int)rA rS:(int)rS rB:(int)rB mb:(int)mb me:(int)me rc:(BOOL)rc {
XCTAssertEqual(instruction.operation, operation);
XCTAssertEqual(instruction.rA(), rA);
XCTAssertEqual(instruction.rS(), rS);
XCTAssertEqual(instruction.rB(), rB);
XCTAssertEqual(instruction.mb(), mb);
XCTAssertEqual(instruction.me(), me);
XCTAssertEqual(!!instruction.rc(), rc);
}
- (void)assert:(Instruction &)instruction operation:(Operation)operation to:(int)to rA:(int)rA simm:(int)simm {
XCTAssertEqual(instruction.operation, operation);
XCTAssertEqual(instruction.to(), to);
XCTAssertEqual(instruction.rA(), rA);
XCTAssertEqual(instruction.simm(), simm);
}
- (void)assert:(Instruction &)instruction operation:(Operation)operation crfD:(int)crfD l:(BOOL)l rA:(int)rA uimm:(int)uimm {
XCTAssertEqual(instruction.operation, operation);
XCTAssertEqual(instruction.crfD(), crfD);
XCTAssertEqual(!!instruction.l(), l);
XCTAssertEqual(instruction.rA(), rA);
XCTAssertEqual(instruction.uimm(), uimm);
}
// MARK: - Decoder
- (void)decode:(const uint32_t *)stream {
CPU::Decoder::PowerPC::Decoder decoder(CPU::Decoder::PowerPC::Model::MPC601);
for(int c = 0; c < 32; c++) {
instructions[c] = decoder.decode(stream[c]);
}
}
// MARK: - Tests
- (void)testSequence1 {
const uint32_t sequence[] = {
0x32eeefa5, 0xc2ee0786, 0x80ce552c, 0x88d5f02a,
0xf8c2e801, 0xe83d5cdf, 0x7fa51fbb, 0xaacea8b0,
0x7d4d4d21, 0x1314cf89, 0x47e0014b, 0xdf67566d,
0xfb29a33e, 0x312cbf53, 0x706f1a15, 0xa87b7011,
0x2107090c, 0xce04935e, 0x642e464a, 0x931e8eba,
0xee396d5b, 0x4901183d, 0x31ccaa9a, 0x42b61a86,
0x1ed4751d, 0x86af76e4, 0x151405a9, 0xca0ac015,
0x60dd1f9d, 0xecff44f6, 0xf2c1110e, 0x9aa6653b,
};
[self decode:sequence];
// addic r23,r14,-4187
// lfs f23,1926(r14)
// lwz r6,21804(r14)
// lbz r6,-4054(r21)
[self assert:instructions[0] operation:Operation::addic rD:23 rA:14 simm:-4187];
[self assert:instructions[1] operation:Operation::lfs frD:23 rA:14 d:1926];
[self assert:instructions[2] operation:Operation::lwz rD:6 rA:14 d:21804];
[self assert:instructions[3] operation:Operation::lbz rD:6 rA:21 d:-4054];
// .long 0xf8c2e801
// .long 0xe83d5cdf
// .long 0x7fa51fbb
// lha r22,-22352(r14)
[self assertUndefined:instructions[4]];
[self assertUndefined:instructions[5]];
[self assertUndefined:instructions[6]];
[self assert:instructions[7] operation:Operation::lha rD:22 rA:14 d:-22352];
// .long 0x7d4d4d21
// .long 0x1314cf89
// .long 0x47e0014b
[self assertUndefined:instructions[8]];
[self assertUndefined:instructions[9]];
// CLK decodes this as sc because it ignores reserved bits; the disassembler
// I used checks the reserved bits. For now: don't test.
// XCTAssertEqual(instructions[10].operation, Operation::Undefined);
// stfdu f27,22125(r7)
// .long 0xfb29a33e
// addic r9,r12,-16557
// andi. r15,r3,6677
[self assert:instructions[11] operation:Operation::stfdu frS:27 rA:7 d:22125];
[self assertUndefined:instructions[12]];
[self assert:instructions[13] operation:Operation::addic rD:9 rA:12 simm:-16557];
[self assert:instructions[14] operation:Operation::andi_ rA:15 rS:3 uimm:6677];
// lha r3,28689(r27)
// subfic r8,r7,2316
// lfdu f16,-27810(r4)
// oris r14,r1,17994
[self assert:instructions[15] operation:Operation::lha rD:3 rA:27 d:28689];
[self assert:instructions[16] operation:Operation::subfic rD:8 rA:7 simm:2316];
[self assert:instructions[17] operation:Operation::lfdu frD:16 rA:4 d:-27810];
[self assert:instructions[18] operation:Operation::oris rA:14 rS:1 uimm:17994];
// stw r24,-28998(r30)
// .long 0xee396d5b
// bl 0x01011890 [disassmebled at address 0x54]
// addic r14,r12,-21862
[self assert:instructions[19] operation:Operation::stw rS:24 rA:30 d:-28998];
[self assertUndefined:instructions[20]];
[self assert:instructions[21] operation:Operation::bx li:0x01011890 - 0x54 lk:TRUE aa:FALSE];
[self assert:instructions[22] operation:Operation::addic rD:14 rA:12 simm:-21862];
// .long 0x42b61a86 [10101]
// mulli r22,r20,29981
// lwzu r21,30436(r15)
// .long 0x151405a9
[self assertUndefined:instructions[23]];
[self assert:instructions[24] operation:Operation::mulli rD:22 rA:20 simm:29981];
[self assert:instructions[25] operation:Operation::lwzu rD:21 rA:15 d:30436];
[self assertUndefined:instructions[26]];
// lfd f16,-16363(r10)
// ori r29,r6,8093
// .long 0xecff44f6
// .long 0xf2c1110e
// stb r21,25915(r6)
[self assert:instructions[27] operation:Operation::lfd frD:16 rA:10 d:-16363];
[self assert:instructions[28] operation:Operation::ori rA:29 rS:6 uimm:8093];
[self assertUndefined:instructions[29]];
[self assertUndefined:instructions[30]];
[self assert:instructions[31] operation:Operation::stb rS:21 rA:6 d:25915];
}
- (void)testSequence2 {
const uint32_t sequence[] = {
0x90252dae, 0x7429ee14, 0x618935bc, 0xd6c94af0,
0xba1d295f, 0x649e3869, 0x6def742c, 0x5c64cdce,
0x762d59ee, 0x565c8189, 0xc7c59f81, 0xce1157fd,
0xc86aef59, 0x81325882, 0x1336fad6, 0xe1ddfa2b,
0x18c60357, 0x4c122cb5, 0xccb1f749, 0xdbdcebc3,
0x0fc60187, 0x117eb911, 0x80334c43, 0xe65371e8,
0xa047c94d, 0xe671dd0b, 0xe07992bb, 0x6a332fe8,
0xfc361c6b, 0x5e8b5a28, 0xb2b64a22, 0x045dd156,
};
[self decode:sequence];
// stw r1,11694(r5)
// andis. r9,r1,60948
// ori r9,r12,13756
// stfsu f22,19184(r9)
[self assert:instructions[0] operation:Operation::stw rS:1 rA:5 d:11694];
[self assert:instructions[1] operation:Operation::andis_ rA:9 rS:1 uimm:60948];
[self assert:instructions[2] operation:Operation::ori rA:9 rS:12 uimm:13756];
[self assert:instructions[3] operation:Operation::stfsu frS:22 rA:9 d:19184];
// lmw r16,10591(r29)
// oris r30,r4,14441
// xoris r15,r15,29740
// rlwnm r4,r3,r25,23,7
[self assert:instructions[4] operation:Operation::lmw rD:16 rA:29 d:10591];
[self assert:instructions[5] operation:Operation::oris rA:30 rS:4 uimm:14441];
[self assert:instructions[6] operation:Operation::xoris rA:15 rS:15 uimm:29740];
[self assert:instructions[7] operation:Operation::rlwnmx rA:4 rS:3 rB:25 mb:23 me:7 rc:FALSE];
// andis. r13,r17,23022
// rlwinm. r28,r18,16,6,4
// lfsu f30,-24703(r5)
// lfdu f16,22525(r17)
[self assert:instructions[8] operation:Operation::andis_ rA:13 rS:17 uimm:23022];
[self assert:instructions[9] operation:Operation::rlwinmx rA:28 rS:18 rB:16 mb:6 me:4 rc:TRUE];
[self assert:instructions[10] operation:Operation::lfsu frD:30 rA:5 d:-24703];
[self assert:instructions[11] operation:Operation::lfdu frD:16 rA:17 d:22525];
// lfd f3,-4263(r10)
// lwz r9,22658(r18)
// .long 0x1336fad6
// .long 0xe1ddfa2b
[self assert:instructions[12] operation:Operation::lfd frD:3 rA:10 d:-4263];
[self assert:instructions[13] operation:Operation::lwz rD:9 rA:18 d:22658];
[self assertUndefined:instructions[14]];
[self assertUndefined:instructions[15]];
// .long 0x18c60357
// .long 0x4c122cb5
// lfdu f5,-2231(r17)
// stfd f30,-5181(r28)
[self assertUndefined:instructions[16]];
[self assertUndefined:instructions[17]];
[self assert:instructions[18] operation:Operation::lfdu frD:5 rA:17 d:-2231];
[self assert:instructions[19] operation:Operation::stfd frD:30 rA:28 d:-5181];
// twi 30,r6,391
// .long 0x117eb911
// lwz r1,19523(r19)
// .long 0xe65371e8
[self assert:instructions[20] operation:Operation::twi to:30 rA:6 simm:391];
[self assertUndefined:instructions[21]];
[self assert:instructions[22] operation:Operation::lwz rD:1 rA:19 d:19523];
[self assertUndefined:instructions[23]];
// lhz r2,-14003(r7)
// .long 0xe671dd0b
// .long 0xe07992bb
// xori r19,r17,12264
[self assert:instructions[24] operation:Operation::lhz rD:2 rA:7 d:-14003];
[self assertUndefined:instructions[25]];
[self assertUndefined:instructions[26]];
[self assert:instructions[27] operation:Operation::xori rA:19 rS:17 uimm:12264];
// .long 0xfc361c6b
// rlwnm r11,r20,r11,8,20
// sth r21,18978(r22)
// .long 0x45dd156
// [self assertUndefined:instructions[28]]; // Disabled due to reserved field; I'm decoding this as faddx.
[self assert:instructions[29] operation:Operation::rlwnmx rA:11 rS:20 rB:11 mb:8 me:20 rc:FALSE];
[self assert:instructions[30] operation:Operation::sth rS:21 rA:22 d:18978];
[self assertUndefined:instructions[31]];
}
- (void)testSequence3 {
const uint32_t sequence[] = {
0xbcaf3520, 0xfa9df12d, 0xc631efca, 0xa3e7f409,
0x3ddca273, 0x3cfb234d, 0x551dc325, 0x8c1a0f37,
0x5b3ca99b, 0xce08cc1e, 0x7b1dfd3a, 0xf19aee7c,
0x52c852e9, 0xc681c0c1, 0xd3b1fda5, 0xe2b401cb,
0x433cb83d, 0x54412f41, 0x532d624a, 0x0b3117c5,
0x988144ba, 0xc7a96ad0, 0x28331474, 0x5620c367,
0xab0a2607, 0xe826acf4, 0x41969154, 0x6471d09f,
0x6a25f04f, 0x4a15996d, 0x272c96ef, 0xab3171a9,
};
[self decode:sequence];
// stmw r5,13600(r15)
// .long 0xfa9df12d
// lfsu f17,-4150(r17)
// lhz r31,-3063(r7)
[self assert:instructions[0] operation:Operation::stmw rS:5 rA:15 d:13600];
[self assertUndefined:instructions[1]];
[self assert:instructions[2] operation:Operation::lfsu frD:17 rA:17 d:-4150];
[self assert:instructions[3] operation:Operation::lhz rD:31 rA:7 d:-3063];
// addis r14,r28,-23949
// addis r7,r27,9037
// rlwinm. r29,r8,24,12,18
// lbzu r0,3895(r26)
[self assert:instructions[4] operation:Operation::addis rD:14 rA:28 simm:-23949];
[self assert:instructions[5] operation:Operation::addis rD:7 rA:27 simm:9037];
[self assert:instructions[6] operation:Operation::rlwinmx rA:29 rS:8 rB:24 mb:12 me:18 rc:TRUE];
[self assert:instructions[7] operation:Operation::lbzu rD:0 rA:26 d:3895];
// rlmi. r28,r25,r21,6,13
// lfdu f16,-13282(r8)
// .long 0x7b1dfd3a
// .long 0xf19aee7c
[self assert:instructions[8] operation:Operation::rlmix rA:28 rS:25 rB:21 mb:6 me:13 rc:TRUE];
[self assert:instructions[9] operation:Operation::lfdu frD:16 rA:8 d:-13282];
[self assertUndefined:instructions[10]];
[self assertUndefined:instructions[11]];
// rlwimi. r8,r22,10,11,20
// lfsu f20,-16191(r1)
// stfs f29,-603(r17)
// .long 0xe2b401cb
[self assert:instructions[12] operation:Operation::rlwimix rA:8 rS:22 rB:10 mb:11 me:20 rc:TRUE];
[self assert:instructions[13] operation:Operation::lfsu frD:20 rA:1 d:-16191];
[self assert:instructions[14] operation:Operation::stfs frS:29 rA:17 d:-603];
[self assertUndefined:instructions[15]];
// .long 0x433cb83d
// rlwinm. r1,r2,5,29,0
// rlwimi r13,r25,12,9,5
// .long 0xb3117c5
[self assertUndefined:instructions[16]];
[self assert:instructions[17] operation:Operation::rlwinmx rA:1 rS:2 rB:5 mb:29 me:0 rc:TRUE];
[self assert:instructions[18] operation:Operation::rlwimix rA:13 rS:25 rB:12 mb:9 me:5 rc:FALSE];
[self assertUndefined:instructions[19]];
// stb r4,17594(r1)
// lfsu f29,27344(r9)
// cmpli cr0,1,r19,5236
// rlwinm. r0,r17,24,13,19
[self assert:instructions[20] operation:Operation::stb rS:4 rA:1 d:17594];
[self assert:instructions[21] operation:Operation::lfsu frD:29 rA:9 d:27344];
[self assert:instructions[22] operation:Operation::cmpli crfD:0 l:TRUE rA:19 uimm:5236];
[self assert:instructions[23] operation:Operation::rlwinmx rA:0 rS:17 rB:24 mb:13 me:19 rc:TRUE];
// lha r24,9735(r10)
// .long 0xe826acf4
// beq+ cr5,0xffffffffffff91bc [at address 0x68]
// oris r17,r3,53407
[self assert:instructions[24] operation:Operation::lha rD:24 rA:10 d:9735];
[self assertUndefined:instructions[25]];
[self assert:instructions[26] operation:Operation::bcx bo:12 bi:22 bd:0xffff91bc - 0x68 lk:FALSE aa:FALSE];
[self assert:instructions[27] operation:Operation::oris rA:17 rS:3 uimm:53407];
// xori r5,r17,61519
// bl 0xfffffffffe1599e0 [at address 0x74]
// dozi r25,r12,-26897
// lha r25,29097(r17)
[self assert:instructions[28] operation:Operation::xori rA:5 rS:17 uimm:61519];
[self assert:instructions[29] operation:Operation::bx li:0xfe1599e0 - 0x74 lk:TRUE aa:FALSE];
[self assert:instructions[30] operation:Operation::dozi rD:25 rA:12 simm:-26897];
[self assert:instructions[31] operation:Operation::lha rD:25 rA:17 d:29097];
}
@end

View File

@@ -0,0 +1,336 @@
//
// x86DecoderTests.m
// Clock Signal
//
// Created by Thomas Harte on 03/01/2021.
// Copyright 2021 Thomas Harte. All rights reserved.
//
#import <XCTest/XCTest.h>
#include <initializer_list>
#include <vector>
#include "../../../InstructionSets/x86/Decoder.hpp"
namespace {
using Operation = CPU::Decoder::x86::Operation;
using Instruction = CPU::Decoder::x86::Instruction;
using Source = CPU::Decoder::x86::Source;
using Size = CPU::Decoder::x86::Size;
}
@interface x86DecoderTests : XCTestCase
@end
/*!
Tests 8086 decoding by throwing a bunch of randomly-generated
word streams and checking that the result matches what I got from a
disassembler elsewhere.
*/
@implementation x86DecoderTests {
std::vector<Instruction> instructions;
}
// MARK: - Specific instruction asserts.
- (void)assert:(Instruction &)instruction operation:(Operation)operation {
XCTAssertEqual(instruction.operation, operation);
}
- (void)assert:(Instruction &)instruction operation:(Operation)operation size:(int)size {
XCTAssertEqual(instruction.operation, operation);
XCTAssertEqual(instruction.operation_size(), CPU::Decoder::x86::Size(size));
}
- (void)assert:(Instruction &)instruction operation:(Operation)operation size:(int)size source:(Source)source destination:(Source)destination displacement:(int16_t)displacement {
XCTAssertEqual(instruction.operation, operation);
XCTAssertEqual(instruction.operation_size(), CPU::Decoder::x86::Size(size));
XCTAssertEqual(instruction.source(), source);
XCTAssertEqual(instruction.destination(), destination);
XCTAssertEqual(instruction.displacement(), displacement);
}
- (void)assert:(Instruction &)instruction operation:(Operation)operation size:(int)size source:(Source)source destination:(Source)destination displacement:(int16_t)displacement operand:(uint16_t)operand {
[self assert:instruction operation:operation size:size source:source destination:destination displacement:displacement];
XCTAssertEqual(instruction.operand(), operand);
}
- (void)assert:(Instruction &)instruction operation:(Operation)operation size:(int)size source:(Source)source destination:(Source)destination operand:(uint16_t)operand {
[self assert:instruction operation:operation size:size source:source destination:destination displacement:0 operand:operand];
}
- (void)assert:(Instruction &)instruction operation:(Operation)operation size:(int)size source:(Source)source destination:(Source)destination {
[self assert:instruction operation:operation size:size source:source destination:destination displacement:0];
}
- (void)assert:(Instruction &)instruction operation:(Operation)operation size:(int)size source:(Source)source {
XCTAssertEqual(instruction.operation, operation);
XCTAssertEqual(instruction.operation_size(), CPU::Decoder::x86::Size(size));
XCTAssertEqual(instruction.source(), source);
}
- (void)assert:(Instruction &)instruction operation:(Operation)operation size:(int)size destination:(Source)destination {
XCTAssertEqual(instruction.operation, operation);
XCTAssertEqual(instruction.operation_size(), CPU::Decoder::x86::Size(size));
XCTAssertEqual(instruction.destination(), destination);
}
- (void)assert:(Instruction &)instruction operation:(Operation)operation size:(int)size operand:(uint16_t)operand destination:(Source)destination {
XCTAssertEqual(instruction.operation, operation);
XCTAssertEqual(instruction.operation_size(), CPU::Decoder::x86::Size(size));
XCTAssertEqual(instruction.destination(), destination);
XCTAssertEqual(instruction.source(), Source::Immediate);
XCTAssertEqual(instruction.operand(), operand);
XCTAssertEqual(instruction.displacement(), 0);
}
- (void)assert:(Instruction &)instruction operation:(Operation)operation displacement:(int16_t)displacement {
XCTAssertEqual(instruction.operation, operation);
XCTAssertEqual(instruction.displacement(), displacement);
}
- (void)assert:(Instruction &)instruction operation:(Operation)operation operand:(uint16_t)operand {
XCTAssertEqual(instruction.operation, operation);
XCTAssertEqual(instruction.operand(), operand);
XCTAssertEqual(instruction.displacement(), 0);
}
- (void)assert:(Instruction &)instruction operation:(Operation)operation segment:(uint16_t)segment offset:(uint16_t)offset {
XCTAssertEqual(instruction.operation, operation);
XCTAssertEqual(instruction.segment(), segment);
XCTAssertEqual(instruction.offset(), offset);
}
// MARK: - Decoder
- (void)decode:(const std::initializer_list<uint8_t> &)stream {
// Decode by offering up all data at once.
CPU::Decoder::x86::Decoder decoder(CPU::Decoder::x86::Model::i8086);
instructions.clear();
const uint8_t *byte = stream.begin();
while(byte != stream.end()) {
const auto [size, next] = decoder.decode(byte, stream.end() - byte);
if(size <= 0) break;
instructions.push_back(next);
byte += size;
}
// Grab a byte-at-a-time decoding and check that it matches the previous.
{
CPU::Decoder::x86::Decoder decoder(CPU::Decoder::x86::Model::i8086);
auto previous_instruction = instructions.begin();
for(auto item: stream) {
const auto [size, next] = decoder.decode(&item, 1);
if(size > 0) {
XCTAssert(next == *previous_instruction);
++previous_instruction;
}
}
}
}
// MARK: - Tests
- (void)testSequence1 {
// Sequences the Online Disassembler believes to exist but The 8086 Book does not:
//
// 0x6a 0x65 push $65
// 0x65 0x6d gs insw (%dx),%es:(%di)
// 0x67 0x61 addr32 popa
// 0x6c insb (%dx), %es:(%di)
// 0xc9 leave
//
[self decode:{
0x2d, 0x77, 0xea, 0x72, 0xfc, 0x4b, 0xb5, 0x28, 0xc3, 0xca, 0x26, 0x48, /* 0x65, 0x6d, */ 0x7b, 0x9f,
0xc2, 0x65, 0x42, 0x4e, 0xef, 0x70, 0x20, 0x94, 0xc4, 0xd4, 0x93, 0x43, 0x3c, 0x8e, /* 0x6a, 0x65, */
0x1a, 0x78, 0x45, 0x10, 0x7f, 0x3c, 0x19, 0x5a, 0x16, 0x31, 0x64, 0x2c, 0xe7, 0xc6, 0x7d, 0xb0,
0xb5, 0x49, /* 0x67, 0x61, */ 0xba, 0xc0, 0xcb, 0x14, 0x7e, 0x71, 0xd0, 0x50, 0x78, 0x3d, 0x03, 0x1d,
0xe5, 0xc9, 0x97, 0xc3, 0x9b, 0xe6, 0xd3, /* 0x6c, */ 0x58, 0x4d, 0x76, 0x80, 0x44, 0xd6, 0x9f, 0xa5,
0xbd, 0xa1, 0x12, 0xc5, 0x29, /* 0xc9, */ 0x9e, 0xd8, 0xf3, 0xcf, 0x92, 0x39, 0x5d, 0x90, 0x15, 0xc3,
0xb8, 0xad, 0xe8, 0xc8, 0x16, 0x4a, 0xb0, 0x9e, 0xf9, 0xbf, 0x56, 0xea, 0x4e, 0xfd, 0xe4, 0x5a,
0x23, 0xaa, 0x2c, 0x5b, 0x2a, 0xd2, 0xf7, 0x5f, 0x18, 0x86, 0x90, 0x25, 0x64, 0xb7, 0xc3
}];
// 63 instructions are expected.
XCTAssertEqual(instructions.size(), 63);
// sub $0xea77,%ax
// jb 0x00000001
// dec %bx
// mov $0x28,%ch
[self assert:instructions[0] operation:Operation::SUB size:2 operand:0xea77 destination:Source::AX];
[self assert:instructions[1] operation:Operation::JB displacement:0xfffc];
[self assert:instructions[2] operation:Operation::DEC size:2 source:Source::BX destination:Source::BX];
[self assert:instructions[3] operation:Operation::MOV size:1 operand:0x28 destination:Source::CH];
// ret
// lret $0x4826
// [[ omitted: gs insw (%dx),%es:(%di) ]]
// jnp 0xffffffaf
// ret $0x4265
[self assert:instructions[4] operation:Operation::RETN];
[self assert:instructions[5] operation:Operation::RETF operand:0x4826];
[self assert:instructions[6] operation:Operation::JNP displacement:0xff9f];
[self assert:instructions[7] operation:Operation::RETN operand:0x4265];
// dec %si
// out %ax,(%dx)
// jo 0x00000037
// xchg %ax,%sp
[self assert:instructions[8] operation:Operation::DEC size:2 source:Source::SI destination:Source::SI];
[self assert:instructions[9] operation:Operation::OUT size:2 source:Source::AX destination:Source::DX];
[self assert:instructions[10] operation:Operation::JO displacement:0x20];
[self assert:instructions[11] operation:Operation::XCHG size:2 source:Source::AX destination:Source::SP];
// ODA has:
// c4 (bad)
// d4 93 aam $0x93
//
// That assumes that upon discovering that the d4 doesn't make a valid LES,
// it can become an instruction byte. I'm not persuaded. So I'm taking:
//
// c4 d4 (bad)
// 93 XCHG AX, BX
[self assert:instructions[12] operation:Operation::Invalid];
[self assert:instructions[13] operation:Operation::XCHG size:2 source:Source::AX destination:Source::BX];
// inc %bx
// cmp $0x8e,%al
// [[ omitted: push $0x65 ]]
// sbb 0x45(%bx,%si),%bh
// adc %bh,0x3c(%bx)
[self assert:instructions[14] operation:Operation::INC size:2 source:Source::BX destination:Source::BX];
[self assert:instructions[15] operation:Operation::CMP size:1 operand:0x8e destination:Source::AL];
[self assert:instructions[16] operation:Operation::SBB size:1 source:Source::IndBXPlusSI destination:Source::BH displacement:0x45];
[self assert:instructions[17] operation:Operation::ADC size:1 source:Source::BH destination:Source::IndBX displacement:0x3c];
// sbb %bx,0x16(%bp,%si)
// xor %sp,0x2c(%si)
// out %ax,$0xc6
// jge 0xffffffe0
[self assert:instructions[18] operation:Operation::SBB size:2 source:Source::BX destination:Source::IndBPPlusSI displacement:0x16];
[self assert:instructions[19] operation:Operation::XOR size:2 source:Source::SP destination:Source::IndSI displacement:0x2c];
[self assert:instructions[20] operation:Operation::OUT size:2 source:Source::AX destination:Source::DirectAddress operand:0xc6];
[self assert:instructions[21] operation:Operation::JNL displacement:0xffb0];
// mov $0x49,%ch
// [[ omitted: addr32 popa ]]
// mov $0xcbc0,%dx
// adc $0x7e,%al
// jno 0x0000000b
[self assert:instructions[22] operation:Operation::MOV size:1 operand:0x49 destination:Source::CH];
[self assert:instructions[23] operation:Operation::MOV size:2 operand:0xcbc0 destination:Source::DX];
[self assert:instructions[24] operation:Operation::ADC size:1 operand:0x7e destination:Source::AL];
[self assert:instructions[25] operation:Operation::JNO displacement:0xffd0];
// push %ax
// js 0x0000007b
// add (%di),%bx
// in $0xc9,%ax
[self assert:instructions[26] operation:Operation::PUSH size:2 source:Source::AX];
[self assert:instructions[27] operation:Operation::JS displacement:0x3d];
[self assert:instructions[28] operation:Operation::ADD size:2 source:Source::IndDI destination:Source::BX];
[self assert:instructions[29] operation:Operation::IN size:2 source:Source::DirectAddress destination:Source::AX operand:0xc9];
// xchg %ax,%di
// ret
// fwait
// out %al,$0xd3
[self assert:instructions[30] operation:Operation::XCHG size:2 source:Source::AX destination:Source::DI];
[self assert:instructions[31] operation:Operation::RETN];
[self assert:instructions[32] operation:Operation::WAIT];
[self assert:instructions[33] operation:Operation::OUT size:1 source:Source::AL destination:Source::DirectAddress operand:0xd3];
// [[ omitted: insb (%dx),%es:(%di) ]]
// pop %ax
// dec %bp
// jbe 0xffffffcc
// inc %sp
[self assert:instructions[34] operation:Operation::POP size:2 destination:Source::AX];
[self assert:instructions[35] operation:Operation::DEC size:2 source:Source::BP destination:Source::BP];
[self assert:instructions[36] operation:Operation::JBE displacement:0xff80];
[self assert:instructions[37] operation:Operation::INC size:2 source:Source::SP destination:Source::SP];
// (bad)
// lahf
// movsw %ds:(%si),%es:(%di)
// mov $0x12a1,%bp
[self assert:instructions[38] operation:Operation::Invalid];
[self assert:instructions[39] operation:Operation::LAHF];
[self assert:instructions[40] operation:Operation::MOVS size:2];
[self assert:instructions[41] operation:Operation::MOV size:2 operand:0x12a1 destination:Source::BP];
// lds (%bx,%di),%bp
// [[ omitted: leave ]]
// sahf
// fdiv %st(3),%st
// iret
[self assert:instructions[42] operation:Operation::LDS size:2];
[self assert:instructions[43] operation:Operation::SAHF];
[self assert:instructions[44] operation:Operation::ESC];
[self assert:instructions[45] operation:Operation::IRET];
// xchg %ax,%dx
// cmp %bx,-0x70(%di)
// adc $0xb8c3,%ax
// lods %ds:(%si),%ax
[self assert:instructions[46] operation:Operation::XCHG size:2 source:Source::AX destination:Source::DX];
[self assert:instructions[47] operation:Operation::CMP size:2 source:Source::BX destination:Source::IndDI displacement:0xff90];
[self assert:instructions[48] operation:Operation::ADC size:2 operand:0xb8c3 destination:Source::AX];
[self assert:instructions[49] operation:Operation::LODS size:2];
// call 0x0000172d
// dec %dx
// mov $0x9e,%al
// stc
[self assert:instructions[50] operation:Operation::CALLD operand:0x16c8];
[self assert:instructions[51] operation:Operation::DEC size:2 source:Source::DX destination:Source::DX];
[self assert:instructions[52] operation:Operation::MOV size:1 operand:0x9e destination:Source::AL];
[self assert:instructions[53] operation:Operation::STC];
// mov $0xea56,%di
// dec %si
// std
// in $0x5a,%al
[self assert:instructions[54] operation:Operation::MOV size:2 operand:0xea56 destination:Source::DI];
[self assert:instructions[55] operation:Operation::DEC size:2 source:Source::SI destination:Source::SI];
[self assert:instructions[56] operation:Operation::STD];
[self assert:instructions[57] operation:Operation::IN size:1 source:Source::DirectAddress destination:Source::AL operand:0x5a];
// and 0x5b2c(%bp,%si),%bp
// sub %dl,%dl
// negw 0x18(%bx)
// xchg %dl,0x6425(%bx,%si)
[self assert:instructions[58] operation:Operation::AND size:2 source:Source::IndBPPlusSI destination:Source::BP displacement:0x5b2c];
[self assert:instructions[59] operation:Operation::SUB size:1 source:Source::DL destination:Source::DL];
[self assert:instructions[60] operation:Operation::NEG size:2 source:Source::IndBX destination:Source::IndBX displacement:0x18];
[self assert:instructions[61] operation:Operation::XCHG size:1 source:Source::IndBXPlusSI destination:Source::DL displacement:0x6425];
// mov $0xc3,%bh
[self assert:instructions[62] operation:Operation::MOV size:1 operand:0xc3 destination:Source::BH];
}
- (void)test83 {
[self decode:{
0x83, 0x10, 0x80, // adcw $0xff80,(%bx,%si)
0x83, 0x3b, 0x04, // cmpw $0x4,(%bp,%di)
0x83, 0x2f, 0x09, // subw $0x9,(%bx)
}];
XCTAssertEqual(instructions.size(), 3);
[self assert:instructions[0] operation:Operation::ADC size:2 source:Source::Immediate destination:Source::IndBXPlusSI operand:0xff80];
[self assert:instructions[1] operation:Operation::CMP size:2 source:Source::Immediate destination:Source::IndBPPlusDI operand:0x4];
[self assert:instructions[2] operation:Operation::SUB size:2 source:Source::Immediate destination:Source::IndBX operand:0x9];
}
- (void)testFar {
[self decode:{
0x9a, 0x12, 0x34, 0x56, 0x78, // lcall 0x7856, 0x3412
}];
XCTAssertEqual(instructions.size(), 1);
[self assert:instructions[0] operation:Operation::CALLF segment:0x7856 offset:0x3412];
}
@end

View File

@@ -1020,7 +1020,7 @@ bool MainWindow::processEvent(QKeyEvent *event) {
const auto keyboardMachine = machine->keyboard_machine();
if(!keyboardMachine) return true;
auto keyboard = keyboardMachine->get_keyboard();
auto &keyboard = keyboardMachine->get_keyboard();
keyboard.set_key_pressed(*key, event->text().size() ? event->text()[0].toLatin1() : '\0', isPressed);
if(keyboard.is_exclusive() || keyboard.observed_keys().find(*key) != keyboard.observed_keys().end()) {
return false;
@@ -1163,7 +1163,9 @@ void MainWindow::start_electron() {
auto target = std::make_unique<Target>();
target->has_dfs = ui->electronDFSCheckBox->isChecked();
target->has_adfs = ui->electronADFSCheckBox->isChecked();
target->has_pres_adfs = ui->electronADFSCheckBox->isChecked();
target->has_ap6_rom = ui->electronAP6CheckBox->isChecked();
target->has_sideways_ram = ui->electronSidewaysRAMCheckBox->isChecked();
launchTarget(std::move(target));
}
@@ -1298,6 +1300,8 @@ void MainWindow::launchTarget(std::unique_ptr<Analyser::Static::Target> &&target
/* Electron. */ \
CheckBox(electronDFSCheckBox, "electron.hasDFS"); \
CheckBox(electronADFSCheckBox, "electron.hasADFS"); \
CheckBox(electronAP6CheckBox, "electron.hasAP6"); \
CheckBox(electronSidewaysRAMCheckBox, "electron.fillSidewaysRAM"); \
\
/* Macintosh. */ \
ComboBox(macintoshModelComboBox, "macintosh.model"); \

View File

@@ -201,6 +201,20 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="electronAP6CheckBox">
<property name="text">
<string>With Advanced Plus 6 Utility ROM</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="electronSidewaysRAMCheckBox">
<property name="text">
<string>Fill unused ROM banks with sideways RAM</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">

View File

@@ -783,8 +783,10 @@ int main(int argc, char *argv[]) {
}
}
// Check whether a 'logical' keyboard has been requested.
const bool logical_keyboard = arguments.selections.find("logical-keyboard") != arguments.selections.end();
// Check whether a 'logical' keyboard has been requested, or the machine would prefer one anyway.
const bool logical_keyboard =
(arguments.selections.find("logical-keyboard") != arguments.selections.end()) ||
(machine->keyboard_machine() && machine->keyboard_machine()->prefers_logical_input());
if(logical_keyboard) {
SDL_StartTextInput();
}
@@ -1162,13 +1164,8 @@ int main(int argc, char *argv[]) {
} else {
// This is a slightly terrible way of obtaining a symbol for the key, e.g. for letters it will always return
// the capital letter version, at least empirically. But it'll have to do for now.
//
// TODO: ideally have a keyboard machine declare whether it wants either key events or text input? But that
// doesn't match machines like the IIe that, to some extent, expose both. So then eliding as attempted above,
// and keeping ephemeral track of which symbols have been tied to which keys for the benefit of future key up
// events is probably the way forward?
const char *key_name = SDL_GetKeyName(keypress.keycode);
if(keyboard_machine->get_keyboard().set_key_pressed(key, key_name[0], keypress.is_down)) {
if(keyboard_machine->get_keyboard().set_key_pressed(key, (strlen(key_name) == 1) ? key_name[0] : 0, keypress.is_down)) {
continue;
}
}

View File

@@ -194,8 +194,9 @@ std::vector<std::string> ScanTarget::bindings(ShaderType type) const {
std::string ScanTarget::sampling_function() const {
std::string fragment_shader;
const auto modals = BufferingScanTarget::modals();
const bool is_svideo = modals.display_type == DisplayType::SVideo;
if(modals.display_type == DisplayType::SVideo) {
if(is_svideo) {
fragment_shader +=
"vec2 svideo_sample(vec2 coordinate, float angle) {";
} else {
@@ -203,7 +204,6 @@ std::string ScanTarget::sampling_function() const {
"float composite_sample(vec2 coordinate, float angle) {";
}
const bool is_svideo = modals.display_type == DisplayType::SVideo;
switch(modals.input_data_type) {
case InputDataType::Luminance1:
case InputDataType::Luminance8:
@@ -341,7 +341,7 @@ std::unique_ptr<Shader> ScanTarget::conversion_shader() const {
vertex_shader +=
"compositeAngle = (mix(startCompositeAngle, endCompositeAngle, lateral) / 32.0) * 3.141592654;"
"compositeAmplitude = lineCompositeAmplitude / 255.0;"
"oneOverCompositeAmplitude = mix(0.0, 255.0 / lineCompositeAmplitude, step(0.01, lineCompositeAmplitude));";
"oneOverCompositeAmplitude = mix(0.0, 255.0 / lineCompositeAmplitude, step(0.95, lineCompositeAmplitude));";
}
vertex_shader +=
@@ -379,40 +379,42 @@ std::unique_ptr<Shader> ScanTarget::conversion_shader() const {
switch(modals.display_type) {
case DisplayType::CompositeColour:
fragment_shader +=
"vec4 angles = compositeAngle + compositeAngleOffsets;"
fragment_shader += R"x(
vec4 angles = compositeAngle + compositeAngleOffsets;
// Sample four times over, at proper angle offsets.
"vec4 samples = vec4("
"composite_sample(textureCoordinates[0], angles.x),"
"composite_sample(textureCoordinates[1], angles.y),"
"composite_sample(textureCoordinates[2], angles.z),"
"composite_sample(textureCoordinates[3], angles.w)"
");"
vec4 samples = vec4(
composite_sample(textureCoordinates[0], angles.x),
composite_sample(textureCoordinates[1], angles.y),
composite_sample(textureCoordinates[2], angles.z),
composite_sample(textureCoordinates[3], angles.w)
);
// Compute a luminance for use if there's no colour information, now, before
// modifying samples.
"float mono_luminance = dot(samples, vec4(0.15, 0.35, 0.35, 0.15));"
// The outer structure of the OpenGL scan target means in practice that
// compositeAmplitude will be the same value across a piece of
// geometry. I am therefore optimistic that this conditional will not
// cause a divergence in fragment execution.
if(compositeAmplitude < 0.01) {
// Compute only a luminance for use if there's no colour information.
fragColour3 = vec3(dot(samples, vec4(0.15, 0.35, 0.35, 0.15)));
} else {
// Take the average to calculate luminance, then subtract that from all four samples to
// give chrominance.
float luminance = dot(samples, vec4(0.25));
// Take the average to calculate luminance, then subtract that from all four samples to
// give chrominance.
"float luminance = dot(samples, vec4(0.25));"
// Split and average chrominance.
vec2 chrominances[4] = vec2[4](
textureLod(qamTextureName, qamTextureCoordinates[0], 0).gb,
textureLod(qamTextureName, qamTextureCoordinates[1], 0).gb,
textureLod(qamTextureName, qamTextureCoordinates[2], 0).gb,
textureLod(qamTextureName, qamTextureCoordinates[3], 0).gb
);
vec2 channels = (chrominances[0] + chrominances[1] + chrominances[2] + chrominances[3])*0.5 - vec2(1.0);
// Split and average chrominance.
"vec2 chrominances[4] = vec2[4]("
"textureLod(qamTextureName, qamTextureCoordinates[0], 0).gb,"
"textureLod(qamTextureName, qamTextureCoordinates[1], 0).gb,"
"textureLod(qamTextureName, qamTextureCoordinates[2], 0).gb,"
"textureLod(qamTextureName, qamTextureCoordinates[3], 0).gb"
");"
"vec2 channels = (chrominances[0] + chrominances[1] + chrominances[2] + chrominances[3])*0.5 - vec2(1.0);"
// Apply a colour space conversion to get RGB.
"fragColour3 = mix("
"lumaChromaToRGB * vec3(luminance / (1.0 - compositeAmplitude), channels),"
"vec3(mono_luminance),"
"step(oneOverCompositeAmplitude, 0.01)"
");";
// Apply a colour space conversion to get RGB.
fragColour3 = lumaChromaToRGB * vec3(luminance / (1.0 - compositeAmplitude), channels);
}
)x";
break;
case DisplayType::CompositeMonochrome:
@@ -622,7 +624,7 @@ std::unique_ptr<Shader> ScanTarget::qam_separation_shader() const {
"compositeAngle = compositeAngle * 2.0 * 3.141592654;"
"compositeAmplitude = lineCompositeAmplitude / 255.0;"
"oneOverCompositeAmplitude = mix(0.0, 255.0 / lineCompositeAmplitude, step(0.01, lineCompositeAmplitude));";
"oneOverCompositeAmplitude = mix(0.0, 255.0 / lineCompositeAmplitude, step(0.95, lineCompositeAmplitude));";
if(is_svideo) {
vertex_shader +=

View File

@@ -2,15 +2,12 @@
# Clock Signal
Clock Signal ('CLK') is an emulator for tourists that seeks to be invisible. Users directly launch classic software with no emulator or per-emulated-machine learning curve.
macOS and source releases are [hosted on GitHub](https://github.com/TomHarte/CLK/releases). For desktop Linux it is also available as a [Snap](https://snapcraft.io/clock-signal).
macOS and source releases are [hosted on GitHub](https://github.com/TomHarte/CLK/releases). For desktop Linux it is also available as a [Snap](https://snapcraft.io/clock-signal). On the Mac it is a native Cocoa and Metal application; under Linux, BSD and other UNIXes and UNIX-alikes it uses OpenGL and can be built either with Qt or with SDL.
On the Mac it is a native Cocoa and Metal application; under Linux, BSD and other UNIXes and UNIX-alikes it uses OpenGL and can be built either with Qt or with SDL.
So its aims are:
This emulator seeks to offer:
* single-click load of any piece of source media for any supported platform;
* with a heavy signal processing tilt for accurate reproduction of original outputs;
* that aims for the lowest possible latency; and
* 100% accurate emulations, naturally.
* while minimising latency.
It currently contains emulations of the:
* Acorn Electron;
@@ -28,7 +25,7 @@ It currently contains emulations of the:
## Single-click Loading
Through the combination of static analysis and runtime analysis, CLK seeks to be able automatically to select and configure the appropriate machine to run any provided disk, tape or ROM; to issue any commands necessary to run the software contained on the disk, tape or ROM; and to provide accelerated loading where feasible.
Through static and runtime analysis CLK seeks automatically to select and configure the appropriate machine to run any provided disk, tape or ROM; to issue any commands necessary to run the software contained on the disk, tape or ROM; and to provide accelerated loading where feasible.
The full process of loading a title — even if you've never used the emulated machine before — is therefore:
@@ -37,12 +34,10 @@ The full process of loading a title — even if you've never used the emulated m
## Signal Processing
Consider an ordinary, unmodified Commodore Vic-20. Its only video output is composite. Therefore the emulated machine's only video output is composite. In order to display the video output, your GPU then decodes composite video. Therefore all composite video artefacts are present and exactly correct, not because of a post hoc filter combining all the subjective effects that this author associates with composite video but because the real signal is really being processed.
Consider an ordinary, unmodified Commodore Vic-20. Its only video output is composite. Therefore the emulated machine's only video output is composite. In order to display the video output, your GPU must decode composite video. Therefore composite video artefacts are present and correct not because of a post hoc filter but because the real signal is really being processed.
Similar effort is put into audio generation. If the real machine normally generates audio at 192Khz then the emulator generates a 192Khz source signal and filters it down to whatever the host machine can output.
If your machine has a 4k monitor and a 96Khz audio output? Then you'll get a 4k rendering of a composite display and, assuming the emulated machine produces source audio at or above 96Khz, 96,000 individual distinct audio samples a second. Interlaced video also works and looks much as it always did on those machines that produce it.
### Samples
| 1:1 Pixel Copying | Composite Decoded |
@@ -61,15 +56,15 @@ If your machine has a 4k monitor and a 96Khz audio output? Then you'll get a 4k
## Low Latency
The display produced is an emulated CRT, with phosphor decay. Therefore if you have a 140Hz monitor it can produce 140 distinct frames per second. Latency is dictated by the output hardware, not the emulated machine.
The display produced is an emulated CRT, with phosphor decay. Therefore if you have a 140Hz 4k monitor it can produce 140 distinct frames per second at 4k resolution. Latency is dictated by the host hardware, not the emulated machine or emulator.
The machine update mechanism is influenced separately by both screen refresh and audio stream processing; audio latency is therefore generally restrained to 510ms regardless of your screen's refresh rate.
A corollary of emulating the continuous nature CRT, not merely performing end-of-frame transcriptions, is that the most common motion aliasing effects of displaying 50Hz video on a 60Hz display are minimised; you don't have to own niche equipment to benefit.
Audio latency is disjoint from frame rate and is generlaly restrained to 510ms.
## Accurate Emulation
Cycle-accurate emulation for the supported target machines is fairly trite; this emulator seeks to follow that precedent. All emulation logic is written in C++ for explicit control over costs but, where a conflict arises, the presumption is towards clarity and simplicity of code. This emulator is willing to spend the processing resources available on modern hardware.
Accuracy affects usability; the more accurate an emulator, the more likely that a user can run every piece of software they're interested in without further intervention.
This emulator attempts cycle-accurate emulation of all supported machines. In some cases it succeeds.
## Additional Screenshots
| | |
@@ -83,3 +78,9 @@ Cycle-accurate emulation for the supported target machines is fairly trite; this
![macOS Version](READMEImages/MultipleSystems.png)
![Qt Version](READMEImages/MultipleSystems-Ubuntu.png)
## Sponsorship
I've been asked several times whether it is possible to sponsor this project; I think that's a poor fit for this emulator's highly-malleable scope, and it makes me uncomfortable because as the author I primarily see only its defects.
An Amazon US wishlist is now attached in the hope of avoiding the question in future. A lot of it is old books now available only secondhand — I like to read about potential future additions well in advance of starting on them. Per the optimism of some book sellers, please don't purchase anything that is currnetly listed only at an absurd price; they were sorted by secondhand price when added to the list, with cheapest being $5.

View File

@@ -4,13 +4,40 @@ Expected files:
basic.rom
os.rom
plus1.rom
DFS-1770-2.20.rom
ADFS-E00_1.rom
DFS-1770-2.20.rom — used only if the user opens a DFS disk image
ADFS-E00_1.rom — used only if the user opens an ADFS disk image
ADFS-E00_2.rom
AP6v133.rom — used only if the user opens a disk image that makes use of any of the commands given below.
adfs.rom - used only if the user opens a hard disk image
Likely to be desired in the future:
Possibly to be desired in the future:
* os300.rom
adfs.rom
ElectronExpansionRomPresAP2-v1.23.rom
os300.rom
Commands that trigger a request for the AP6v133 ROM:
* *AQRPAGE
* *BUILD
* *DUMP
* *FORMAT
* *INSERT
* *LANG
* *LIST
* *LOADROM
* *LOCK
* *LROMS
* *RLOAD
* *ROMS
* *RSAVE
* *SAVEROM
* *SRLOAD
* *SRLOCK
* *SRPAGE
* *SRSAVE
* *SRUNLOCK
* *SRWIPE
* *TUBE
* *TYPE
* *UNLOCK
* *UNPLUG
* *UROMS
* *VERIFY
* *ZERO

View File

@@ -18,12 +18,13 @@ namespace {
using namespace Storage::Disk;
AcornADF::AcornADF(const std::string &file_name) : MFMSectorDump(file_name) {
// very loose validation: the file needs to be a multiple of 256 bytes
// and not ungainly large
// Check that the disk image contains a whole number of sector.
if(file_.stats().st_size % off_t(128 << sector_size)) throw Error::InvalidFormat;
// Check that the disk image is at least large enough to hold an ADFS catalogue.
if(file_.stats().st_size < 7 * off_t(128 << sector_size)) throw Error::InvalidFormat;
// check that the initial directory's 'Hugo's are present
// Check that the initial directory's 'Hugo's are present.
file_.seek(513, SEEK_SET);
uint8_t bytes[4];
file_.read(bytes, 4);
@@ -33,6 +34,10 @@ AcornADF::AcornADF(const std::string &file_name) : MFMSectorDump(file_name) {
file_.read(bytes, 4);
if(bytes[0] != 'H' || bytes[1] != 'u' || bytes[2] != 'g' || bytes[3] != 'o') throw Error::InvalidFormat;
// Pick a number of heads; treat this image as double sided if it's too large to be single-sided.
head_count_ = 1 + (file_.stats().st_size > sectors_per_track * off_t(128 << sector_size) * 80);
// Announce disk geometry.
set_geometry(sectors_per_track, sector_size, 0, true);
}
@@ -41,9 +46,9 @@ HeadPosition AcornADF::get_maximum_head_position() {
}
int AcornADF::get_head_count() {
return 1;
return head_count_;
}
long AcornADF::get_file_offset_for_position(Track::Address address) {
return address.position.as_int() * (128 << sector_size) * sectors_per_track;
return (address.position.as_int() * head_count_ + address.head) * (128 << sector_size) * sectors_per_track;
}

View File

@@ -34,6 +34,7 @@ class AcornADF: public MFMSectorDump {
private:
long get_file_offset_for_position(Track::Address address) final;
int head_count_ = 1;
};
}

View File

@@ -0,0 +1,41 @@
//
// DAT.cpp
// Clock Signal
//
// Created by Thomas Harte on 31/01/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "DAT.hpp"
using namespace Storage::MassStorage;
DAT::DAT(const std::string &file_name) : file_(file_name) {
// Is the file a multiple of 256 bytes in size?
const auto file_size = file_.stats().st_size;
if(file_size & 255) throw std::exception();
// Does it contain the 'Hugo' signature?
file_.seek(0x201, SEEK_SET);
if(!file_.check_signature("Hugo")) {
throw std::exception();
}
}
size_t DAT::get_block_size() {
return 256;
}
size_t DAT::get_number_of_blocks() {
return size_t(file_.stats().st_size) / 256;
}
std::vector<uint8_t> DAT::get_block(size_t address) {
file_.seek(long(address * 256), SEEK_SET);
return file_.read(256);
}
void DAT::set_block(size_t address, const std::vector<uint8_t> &contents) {
file_.seek(long(address * 256), SEEK_SET);
file_.write(contents);
}

View File

@@ -0,0 +1,40 @@
//
// DAT.hpp
// Clock Signal
//
// Created by Thomas Harte on 31/01/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef MassStorage_DAT_hpp
#define MassStorage_DAT_hpp
#include "../MassStorageDevice.hpp"
#include "../../FileHolder.hpp"
namespace Storage {
namespace MassStorage {
/*!
Provides a @c MassStorageDevice containing an Acorn ADFS image, which is just a
sector dump of an ADFS volume. It will be validated for an ADFS catalogue and communicate
in 256-byte blocks.
*/
class DAT: public MassStorageDevice {
public:
DAT(const std::string &file_name);
private:
FileHolder file_;
/* MassStorageDevices overrides. */
size_t get_block_size() final;
size_t get_number_of_blocks() final;
std::vector<uint8_t> get_block(size_t address) final;
void set_block(size_t address, const std::vector<uint8_t> &) final;
};
}
}
#endif /* MassStorage_DAT_hpp */

View File

@@ -38,6 +38,7 @@ bool DirectAccessDevice::write(const Target::CommandState &state, Target::Respon
if(!device_) return false;
const auto specs = state.read_write_specs();
LOG("Write: " << specs.number_of_blocks << " to " << specs.address);
responder.receive_data(device_->get_block_size() * specs.number_of_blocks, [this, specs] (const Target::CommandState &state, Target::Responder &responder) {
const auto received_data = state.received_data();

View File

@@ -90,6 +90,13 @@ constexpr double DeskewDelay = ns(45.0);
/// any two devices.
constexpr double CableSkew = ns(10.0);
/*!
@returns The value of the data lines per @c state.
*/
constexpr uint8_t data_lines(BusState state) {
return uint8_t(state & 0xff);
}
#undef ns
#undef us

View File

@@ -17,8 +17,8 @@ template <typename Executor> void Target<Executor>::scsi_bus_did_change(Bus *, B
/*
"The target determines that it is selected when the SEL# signal
and its SCSI ID bit are active and the BSY# and I#/O signals
are false. It then asserts the signal within a selection abort
time."
are false. It then asserts the signal within a selection
abort time."
*/
// Wait for deskew, at the very least.