1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-09-11 10:28:07 +00:00

Compare commits

...

67 Commits

Author SHA1 Message Date
Thomas Harte
49ec6d19a5 Merge pull request #1167 from TomHarte/NextSequencePoint
Simplify 'get_next_sequence_point' -> 'next_sequence_point'.
2023-09-10 18:08:11 -04:00
Thomas Harte
8efb6a9226 Simplify 'get_next_sequence_point' -> 'next_sequence_point'. 2023-09-10 18:00:49 -04:00
Thomas Harte
211a6e5114 Merge pull request #1166 from TomHarte/6502Exhaustive 2023-09-10 17:02:20 -04:00
Thomas Harte
e42a2578da Enable 6502 for 'exhaustive' disassembly. 2023-09-10 14:30:39 -04:00
Thomas Harte
34c631ed3b Avoid ranges entirely in favour of implicit entries. 2023-09-10 14:25:28 -04:00
Thomas Harte
2f5d710441 Keep map small. 2023-09-07 11:09:33 -04:00
Thomas Harte
b7a27fbc6b Merge pull request #1165 from TomHarte/MSX2Detection
Improve MSX cartridge type detection.
2023-09-06 22:50:53 -04:00
Thomas Harte
e98f78316b Accept a paging scheme if it becomes 60% likely. 2023-09-06 22:40:39 -04:00
Thomas Harte
8c3ebe23f6 Use ranges properly to apply address mapping. 2023-09-06 22:26:15 -04:00
Thomas Harte
251b8e69ad Attempt to support 'exhaustive' disassemblies (i.e. ones that chase every byte). 2023-09-04 15:13:06 -04:00
Thomas Harte
a21fe92b7a Merge pull request #1164 from TomHarte/MacOS1013
Add manual rpath for macOS 10.13.
2023-09-04 14:45:53 -04:00
Thomas Harte
3d5a351306 Add manual rpath for macOS 10.13. 2023-09-03 15:46:35 -04:00
Thomas Harte
43dfb729d3 Explain even better. 2023-09-02 14:45:53 -04:00
Thomas Harte
ecec9ff6dc Merge pull request #1162 from TomHarte/65C02BCDTest
Extend BCD testing to the 65C02; clean up implementation
2023-09-02 11:45:35 -04:00
Thomas Harte
543be49cf8 Merge branch 'master' into 65C02BCDTest 2023-09-01 16:39:18 -04:00
Thomas Harte
4f0adc4d5d Merge pull request #1163 from TomHarte/AbsoluteNOPs
Fix abs,x NOP length.
2023-09-01 16:38:54 -04:00
Thomas Harte
1fb278c9f1 Fix abs,x NOP length. 2023-09-01 14:31:21 -04:00
Thomas Harte
19ec63b4fb Add exposition, slightly simplify, unbreak INS. 2023-09-01 09:29:35 -04:00
Thomas Harte
4d6ffa7a2e With some degree of hit and hope, correct 65C02 results. 2023-08-31 15:28:59 -04:00
Thomas Harte
39ee75d94a Clean up decimal SBC implementation. 2023-08-31 15:02:17 -04:00
Thomas Harte
13be247495 Comment. 2023-08-30 23:08:42 -04:00
Thomas Harte
cdcac7c11c Simplify top nibble handling. 2023-08-30 23:07:54 -04:00
Thomas Harte
67cd5dd63b Simplify top nibble decision. 2023-08-30 23:06:00 -04:00
Thomas Harte
139a1a2acc Clean up decimal ADC. 2023-08-30 23:04:38 -04:00
Thomas Harte
7b569b1a6c Merge branch 'master' into 65C02BCDTest 2023-08-29 21:32:25 -04:00
Thomas Harte
3e666a08ae Merge pull request #1161 from TomHarte/6502Idling
6502: add final read cycle to illegal NOPs.
2023-08-29 21:24:04 -04:00
Thomas Harte
74b416f985 Clean up output. 2023-08-29 17:07:35 -04:00
Thomas Harte
c160482b0a Exploit test's 65C02 abilities. 2023-08-29 17:04:52 -04:00
Thomas Harte
ec8f1b0fe0 Vary seed between processors. 2023-08-29 16:55:39 -04:00
Thomas Harte
5dae726857 Differentiate non-fetching and fetching NOPs. 2023-08-29 16:50:39 -04:00
Thomas Harte
598a889c6d Merge pull request #1160 from TomHarte/ADBKeyboard
Avoid flurry of startup events, repeats.
2023-08-22 09:42:03 -04:00
Thomas Harte
e5d3140cd1 Avoid flurry of startup events, repeats. 2023-08-22 09:28:57 -04:00
Thomas Harte
525e5ce8b0 Merge pull request #1159 from TomHarte/PixelOrder
Flip order of byte usage in double high res mono.
2023-08-21 22:21:18 -04:00
Thomas Harte
79e9de34b6 Flip order of byte usage in double high res mono. 2023-08-21 22:20:42 -04:00
Thomas Harte
0a547355db Merge pull request #1158 from TomHarte/FasterMouse
Switch to maximal signalling rate.
2023-08-21 22:13:39 -04:00
Thomas Harte
2b58f64161 Switch to maximal signalling rate. 2023-08-21 22:12:55 -04:00
Thomas Harte
6cbd152ff5 Merge pull request #1157 from TomHarte/ADBRate
Add basic ADB controller interrupts.
2023-08-21 20:22:51 -04:00
Thomas Harte
a5038259bc Add admission. 2023-08-21 19:30:34 -04:00
Thomas Harte
bb84a5a474 Enable various ADB-controller interrupts. 2023-08-21 15:35:13 -04:00
Thomas Harte
b5dc84c431 Merge pull request #1156 from TomHarte/IIgsMouseMystery
Without rhythm or rhyme, fix IIgs GSOS mouse movement.
2023-08-20 16:19:04 -04:00
Thomas Harte
357a324e87 Add exposition. 2023-08-20 15:34:40 -04:00
Thomas Harte
fa82fb46b9 Acknowledge ever-revolving earth. 2023-08-20 15:33:47 -04:00
Thomas Harte
b8e7c2b8ac Remove printf. 2023-08-20 15:33:30 -04:00
Thomas Harte
3e2a82b638 Add delta capper. 2023-08-20 15:32:48 -04:00
Thomas Harte
1125286b96 Add note to self. 2023-08-20 15:03:28 -04:00
Thomas Harte
17f1f05064 Hit and hope appears to have fixed mouse input. 2023-08-20 15:02:25 -04:00
Thomas Harte
ae56da2b0d Merge pull request #1155 from TomHarte/Templates
Show failing operations in human form.
2023-08-19 15:58:15 -04:00
Thomas Harte
90f16026bc Merge branch 'master' into Templates 2023-08-19 15:57:37 -04:00
Thomas Harte
d0284917cf Merge pull request #1154 from TomHarte/65816StackAgain
Clarify SH=1 upon TCS.
2023-08-19 15:56:30 -04:00
Thomas Harte
7815d18676 Merge branch 'master' into 65816StackAgain 2023-08-19 15:55:45 -04:00
Thomas Harte
222f6e92fb Merge pull request #1153 from TomHarte/IIgsInterrupts
IIgS: abstract VGC interrupt register; fix clearing bug.
2023-08-18 22:14:13 -04:00
Thomas Harte
b34403164e Abstract out VGC interrupt register; fix clearing bug. 2023-08-18 14:30:40 -04:00
Thomas Harte
3bd931937f Merge pull request #1152 from TomHarte/New6502TestGenerator
Generalise 65816 test generator to handle all 6502esques.
2023-08-18 11:28:57 -04:00
Thomas Harte
d207c13b6b Merge pull request #1151 from TomHarte/STopByteAgain
Fix S top byte overwrite.
2023-08-18 11:28:51 -04:00
Thomas Harte
ca75822dbe Fix restart_operation_fetch. 2023-08-17 15:42:34 -04:00
Thomas Harte
d9df568dab Add faulty restart_operation_fetch. 2023-08-17 15:38:28 -04:00
Thomas Harte
26343148ae Use simplified control lines when appropriate. 2023-08-17 15:32:02 -04:00
Thomas Harte
fd0fe66851 Omit unsupported registers and flags. 2023-08-17 15:24:08 -04:00
Thomas Harte
c41ed191dc Fix S top byte overwrite. 2023-08-17 14:51:13 -04:00
Thomas Harte
833613b68a Fix S top byte overwrite. 2023-08-17 14:50:55 -04:00
Thomas Harte
0a336baae2 Perform minor generalisation. 2023-08-17 14:50:43 -04:00
Thomas Harte
b9bd3f9b8c Merge pull request #1150 from TomHarte/65816Setter
Don't allow setting of an invalid S.
2023-08-07 09:19:59 -04:00
Thomas Harte
42024c1573 Don't allow setting of an invalid S. 2023-08-07 09:19:20 -04:00
Thomas Harte
0222dcf5ce Merge pull request #1149 from TomHarte/65816StackAgain
Add a between-instructions enforcement of SH = 1.
2023-08-05 15:14:53 -04:00
Thomas Harte
54103f1f34 Fix SH=1 reset; appropriate TCS. 2023-08-05 15:06:18 -04:00
Thomas Harte
c0eb401d04 Add a between-instructions enforcement of SH = 1. 2023-08-05 14:57:43 -04:00
Thomas Harte
ad5047dbd5 Show failing operations as strings. 2022-10-19 22:25:09 -04:00
51 changed files with 587 additions and 364 deletions

View File

@@ -236,7 +236,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
case Instruction::Relative: {
std::size_t operand_address = address_mapper(address);
if(operand_address >= memory.size()) return;
address++;
++address;
instruction.operand = memory[operand_address];
}
@@ -291,20 +291,34 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
}
// Decide on overall flow control.
if(instruction.operation == Instruction::RTS || instruction.operation == Instruction::RTI) return;
if(instruction.operation == Instruction::BRK) return; // TODO: check whether IRQ vector is within memory range
if(instruction.operation == Instruction::JSR) {
disassembly.remaining_entry_points.push_back(instruction.operand);
}
if(instruction.operation == Instruction::JMP) {
if(instruction.addressing_mode == Instruction::Absolute)
disassembly.remaining_entry_points.push_back(instruction.operand);
return;
}
// All relative instructions are flow control.
if(instruction.addressing_mode == Instruction::Relative) {
uint16_t destination = uint16_t(address + int8_t(instruction.operand));
disassembly.remaining_entry_points.push_back(destination);
}
switch(instruction.operation) {
default: break;
case Instruction::KIL:
case Instruction::RTS:
case Instruction::RTI:
case Instruction::BRK: // TODO: check whether IRQ vector is within memory range.
disassembly.implicit_entry_points.push_back(address);
return;
case Instruction::JMP:
// Adding a new entry point for relative jumps was handled above.
if(instruction.addressing_mode == Instruction::Absolute) {
disassembly.remaining_entry_points.push_back(instruction.operand);
}
return;
case Instruction::JSR:
disassembly.remaining_entry_points.push_back(instruction.operand);
break;
}
}
}
@@ -316,5 +330,5 @@ Disassembly Analyser::Static::MOS6502::Disassemble(
const std::vector<uint8_t> &memory,
const std::function<std::size_t(uint16_t)> &address_mapper,
std::vector<uint16_t> entry_points) {
return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, MOS6502Disassembler>(memory, address_mapper, entry_points);
return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, MOS6502Disassembler>(memory, address_mapper, entry_points, false);
}

View File

@@ -14,30 +14,51 @@ namespace Analyser::Static::Disassembly {
template <typename D, typename S> struct PartialDisassembly {
D disassembly;
std::vector<S> remaining_entry_points;
std::vector<S> implicit_entry_points;
};
template <typename D, typename S, typename Disassembler> D Disassemble(
const std::vector<uint8_t> &memory,
const std::function<std::size_t(S)> &address_mapper,
std::vector<S> entry_points) {
std::vector<S> entry_points,
bool exhaustive)
{
PartialDisassembly<D, S> partial_disassembly;
partial_disassembly.remaining_entry_points = entry_points;
while(!partial_disassembly.remaining_entry_points.empty()) {
// pull the next entry point from the back of the vector
S next_entry_point = partial_disassembly.remaining_entry_points.back();
partial_disassembly.remaining_entry_points.pop_back();
// Do a recursive-style disassembly for all current entry points.
while(!partial_disassembly.remaining_entry_points.empty()) {
// Pull the next entry point from the back of the vector.
const S next_entry_point = partial_disassembly.remaining_entry_points.back();
partial_disassembly.remaining_entry_points.pop_back();
// if that address has already been visited, forget about it
if( partial_disassembly.disassembly.instructions_by_address.find(next_entry_point)
!= partial_disassembly.disassembly.instructions_by_address.end()) continue;
// If that address has already been visited, forget about it.
if( partial_disassembly.disassembly.instructions_by_address.find(next_entry_point)
!= partial_disassembly.disassembly.instructions_by_address.end()) continue;
// if it's outgoing, log it as such and forget about it; otherwise disassemble
std::size_t mapped_entry_point = address_mapper(next_entry_point);
if(mapped_entry_point >= memory.size())
partial_disassembly.disassembly.outward_calls.insert(next_entry_point);
else
Disassembler::AddToDisassembly(partial_disassembly, memory, address_mapper, next_entry_point);
// If it's outgoing, log it as such and forget about it; otherwise disassemble.
std::size_t mapped_entry_point = address_mapper(next_entry_point);
if(mapped_entry_point >= memory.size())
partial_disassembly.disassembly.outward_calls.insert(next_entry_point);
else
Disassembler::AddToDisassembly(partial_disassembly, memory, address_mapper, next_entry_point);
}
// If this is not an exhaustive disassembly, that's your lot.
if(!exhaustive) {
break;
}
// Otherwise, copy in the new 'implicit entry points' (i.e. all locations that are one after
// a disassembled region). There's a test above that'll ignore any which have already been
// disassembled from.
std::move(
partial_disassembly.implicit_entry_points.begin(),
partial_disassembly.implicit_entry_points.end(),
std::back_inserter(partial_disassembly.remaining_entry_points)
);
partial_disassembly.implicit_entry_points.clear();
}
return partial_disassembly.disassembly;

View File

@@ -589,7 +589,7 @@ struct Z80Disassembler {
break;
}
// Add any (potentially) newly discovered entry point.
// Add any (potentially) newly-discovered entry point.
if( instruction.operation == Instruction::Operation::JP ||
instruction.operation == Instruction::Operation::JR ||
instruction.operation == Instruction::Operation::CALL ||
@@ -598,22 +598,37 @@ struct Z80Disassembler {
}
// This is it if: an unconditional RET, RETI, RETN, JP or JR is found.
if(instruction.condition != Instruction::Condition::None) continue;
switch(instruction.operation) {
default: break;
if(instruction.operation == Instruction::Operation::RET) return;
if(instruction.operation == Instruction::Operation::RETI) return;
if(instruction.operation == Instruction::Operation::RETN) return;
if(instruction.operation == Instruction::Operation::JP) return;
if(instruction.operation == Instruction::Operation::JR) return;
case Instruction::Operation::RET:
case Instruction::Operation::RETI:
case Instruction::Operation::RETN:
case Instruction::Operation::JP:
case Instruction::Operation::JR:
if(instruction.condition == Instruction::Condition::None) {
disassembly.implicit_entry_points.push_back(accessor.address());
return;
}
}
}
}
};
} // end of anonymous namespace
Disassembly Analyser::Static::Z80::Disassemble(
const std::vector<uint8_t> &memory,
const std::function<std::size_t(uint16_t)> &address_mapper,
std::vector<uint16_t> entry_points) {
return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, Z80Disassembler>(memory, address_mapper, entry_points);
std::vector<uint16_t> entry_points,
Approach approach)
{
return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, Z80Disassembler>(
memory,
address_mapper,
entry_points,
approach == Approach::Exhaustive
);
}

View File

@@ -76,10 +76,19 @@ struct Disassembly {
std::set<uint16_t> internal_stores, internal_loads, internal_modifies;
};
enum class Approach {
/// Disassemble from the supplied entry points until an indeterminate branch or return only, adding other fully-static
/// entry points as they are observed.
Recursive,
/// Disassemble all supplied bytes, regardless of what nonsense may be encountered by accidental parsing of data areas.
Exhaustive,
};
Disassembly Disassemble(
const std::vector<uint8_t> &memory,
const std::function<std::size_t(uint16_t)> &address_mapper,
std::vector<uint16_t> entry_points);
std::vector<uint16_t> entry_points,
Approach approach);
}

View File

@@ -115,97 +115,16 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
// be at play; disassemble to try to figure it out.
std::vector<uint8_t> first_8k;
first_8k.insert(first_8k.begin(), segment.data.begin(), segment.data.begin() + 8192);
Analyser::Static::Z80::Disassembly disassembly =
const Analyser::Static::Z80::Disassembly disassembly =
Analyser::Static::Z80::Disassemble(
first_8k,
Analyser::Static::Disassembler::OffsetMapper(start_address),
{ init_address }
{ init_address },
Analyser::Static::Z80::Approach::Exhaustive
);
// // Look for a indirect store followed by an unconditional JP or CALL into another
// // segment, that's a fairly explicit sign where found.
using Instruction = Analyser::Static::Z80::Instruction;
std::map<uint16_t, Instruction> &instructions = disassembly.instructions_by_address;
bool is_ascii = false;
// auto iterator = instructions.begin();
// while(iterator != instructions.end()) {
// auto next_iterator = iterator;
// next_iterator++;
// if(next_iterator == instructions.end()) break;
//
// if( iterator->second.operation == Instruction::Operation::LD &&
// iterator->second.destination == Instruction::Location::Operand_Indirect &&
// (
// iterator->second.operand == 0x5000 ||
// iterator->second.operand == 0x6000 ||
// iterator->second.operand == 0x6800 ||
// iterator->second.operand == 0x7000 ||
// iterator->second.operand == 0x77ff ||
// iterator->second.operand == 0x7800 ||
// iterator->second.operand == 0x8000 ||
// iterator->second.operand == 0x9000 ||
// iterator->second.operand == 0xa000
// ) &&
// (
// next_iterator->second.operation == Instruction::Operation::CALL ||
// next_iterator->second.operation == Instruction::Operation::JP
// ) &&
// ((next_iterator->second.operand >> 13) != (0x4000 >> 13))
// ) {
// const uint16_t address = uint16_t(next_iterator->second.operand);
// switch(iterator->second.operand) {
// case 0x6000:
// if(address >= 0x6000 && address < 0x8000) {
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
// }
// break;
// case 0x6800:
// if(address >= 0x6000 && address < 0x6800) {
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::ASCII8kb;
// }
// break;
// case 0x7000:
// if(address >= 0x6000 && address < 0x8000) {
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
// }
// if(address >= 0x7000 && address < 0x7800) {
// is_ascii = true;
// }
// break;
// case 0x77ff:
// if(address >= 0x7000 && address < 0x7800) {
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::ASCII16kb;
// }
// break;
// case 0x7800:
// if(address >= 0xa000 && address < 0xc000) {
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::ASCII8kb;
// }
// break;
// case 0x8000:
// if(address >= 0x8000 && address < 0xa000) {
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
// }
// break;
// case 0x9000:
// if(address >= 0x8000 && address < 0xa000) {
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
// }
// break;
// case 0xa000:
// if(address >= 0xa000 && address < 0xc000) {
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::Konami;
// }
// break;
// case 0xb000:
// if(address >= 0xa000 && address < 0xc000) {
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
// }
// break;
// }
// }
//
// iterator = next_iterator;
const std::map<uint16_t, Instruction> &instructions = disassembly.instructions_by_address;
// Look for LD (nnnn), A instructions, and collate those addresses.
std::map<uint16_t, int> address_counts;
@@ -217,49 +136,46 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
}
}
// Weight confidences by number of observed hits.
float total_hits =
float(
address_counts[0x6000] + address_counts[0x6800] +
address_counts[0x7000] + address_counts[0x7800] +
address_counts[0x77ff] + address_counts[0x8000] +
address_counts[0xa000] + address_counts[0x5000] +
address_counts[0x9000] + address_counts[0xb000]
);
// Weight confidences by number of observed hits; if any is above 60% confidence, just use it.
const auto ascii_8kb_total = address_counts[0x6000] + address_counts[0x6800] + address_counts[0x7000] + address_counts[0x7800];
const auto ascii_16kb_total = address_counts[0x6000] + address_counts[0x7000] + address_counts[0x77ff];
const auto konami_total = address_counts[0x6000] + address_counts[0x8000] + address_counts[0xa000];
const auto konami_with_scc_total = address_counts[0x5000] + address_counts[0x7000] + address_counts[0x9000] + address_counts[0xb000];
targets.push_back(CartridgeTarget(
segment,
start_address,
Analyser::Static::MSX::Cartridge::ASCII8kb,
float( address_counts[0x6000] +
address_counts[0x6800] +
address_counts[0x7000] +
address_counts[0x7800]) / total_hits));
targets.push_back(CartridgeTarget(
segment,
start_address,
Analyser::Static::MSX::Cartridge::ASCII16kb,
float( address_counts[0x6000] +
address_counts[0x7000] +
address_counts[0x77ff]) / total_hits));
if(!is_ascii) {
const auto total_hits = ascii_8kb_total + ascii_16kb_total + konami_total + konami_with_scc_total;
const bool is_ascii_8kb = (ascii_8kb_total * 5) / (total_hits * 3);
const bool is_ascii_16kb = (ascii_16kb_total * 5) / (total_hits * 3);
const bool is_konami = (konami_total * 5) / (total_hits * 3);
const bool is_konami_with_scc = (konami_with_scc_total * 5) / (total_hits * 3);
if(!is_ascii_16kb && !is_konami && !is_konami_with_scc) {
targets.push_back(CartridgeTarget(
segment,
start_address,
Analyser::Static::MSX::Cartridge::ASCII8kb,
float(ascii_8kb_total) / float(total_hits)));
}
if(!is_ascii_8kb && !is_konami && !is_konami_with_scc) {
targets.push_back(CartridgeTarget(
segment,
start_address,
Analyser::Static::MSX::Cartridge::ASCII16kb,
float(ascii_16kb_total) / float(total_hits)));
}
if(!is_ascii_8kb && !is_ascii_16kb && !is_konami_with_scc) {
targets.push_back(CartridgeTarget(
segment,
start_address,
Analyser::Static::MSX::Cartridge::Konami,
float( address_counts[0x6000] +
address_counts[0x8000] +
address_counts[0xa000]) / total_hits));
float(konami_total) / float(total_hits)));
}
if(!is_ascii) {
if(!is_ascii_8kb && !is_ascii_16kb && !is_konami) {
targets.push_back(CartridgeTarget(
segment,
start_address,
Analyser::Static::MSX::Cartridge::KonamiWithSCC,
float( address_counts[0x5000] +
address_counts[0x7000] +
address_counts[0x9000] +
address_counts[0xb000]) / total_hits));
float(konami_with_scc_total) / float(total_hits)));
}
}

View File

@@ -26,7 +26,7 @@
Machines that accumulate HalfCycle time but supply to a Cycle-counted device may supply a
separate @c TargetTimeScale at template declaration.
If the held object implements get_next_sequence_point() then it'll be used to flush implicitly
If the held object implements @c next_sequence_point() then it'll be used to flush implicitly
as and when sequence points are hit. Callers can use will_flush() to predict these.
If the held object is a subclass of ClockingHint::Source, this template will register as an
@@ -40,7 +40,7 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
private:
/*!
A std::unique_ptr deleter which causes an update_sequence_point to occur on the actor supplied
to it at construction if it implements get_next_sequence_point(). Otherwise destruction is a no-op.
to it at construction if it implements @c next_sequence_point(). Otherwise destruction is a no-op.
**Does not delete the object.**
@@ -247,9 +247,9 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
// going to be applied then do a direct max -> max translation rather than
// allowing the arithmetic to overflow.
if constexpr (divider == 1 && std::is_same_v<LocalTimeScale, TargetTimeScale>) {
time_until_event_ = object_.get_next_sequence_point();
time_until_event_ = object_.next_sequence_point();
} else {
const auto time = object_.get_next_sequence_point();
const auto time = object_.next_sequence_point();
if(time == TargetTimeScale::max()) {
time_until_event_ = LocalTimeScale::max();
} else {
@@ -272,7 +272,7 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
bool did_flush_ = false;
template <typename S, typename = void> struct has_sequence_points : std::false_type {};
template <typename S> struct has_sequence_points<S, decltype(void(std::declval<S &>().get_next_sequence_point()))> : std::true_type {};
template <typename S> struct has_sequence_points<S, decltype(void(std::declval<S &>().next_sequence_point()))> : std::true_type {};
ClockingHint::Preference clocking_preference_ = ClockingHint::Preference::JustInTime;
void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference clocking) {

View File

@@ -207,7 +207,7 @@ void MFP68901::run_for(HalfCycles time) {
}
}
HalfCycles MFP68901::get_next_sequence_point() {
HalfCycles MFP68901::next_sequence_point() {
return HalfCycles::max();
}

View File

@@ -40,7 +40,7 @@ class MFP68901: public ClockingHint::Source {
/// so that mechanism can also be used to reduce the quantity of calls into this class.
///
/// @discussion TODO, alas.
HalfCycles get_next_sequence_point();
HalfCycles next_sequence_point();
/// Sets the current level of either of the timer event inputs — TAI and TBI in datasheet terms.
void set_timer_event_input(int channel, bool value);

View File

@@ -108,7 +108,7 @@ template <Personality personality> class TMS9918: private Base<personality> {
If get_interrupt_line is true now of if get_interrupt_line would
never return true, returns HalfCycles::max().
*/
HalfCycles get_next_sequence_point() const;
HalfCycles next_sequence_point() const;
/*!
Returns the amount of time until the nominated line interrupt position is

View File

@@ -1268,7 +1268,7 @@ uint8_t TMS9918<personality>::get_current_line() const {
return uint8_t(source_row);
}
template <Personality personality>
HalfCycles TMS9918<personality>::get_next_sequence_point() const {
HalfCycles TMS9918<personality>::next_sequence_point() const {
if(!this->generate_interrupts_ && !this->enable_line_interrupts_) return HalfCycles::max();
if(get_interrupt_line()) return HalfCycles::max();

View File

@@ -68,11 +68,9 @@ void Executor::set_interrupt_line(bool line) {
// is active, amongst other things.
if(interrupt_line_ != line) {
interrupt_line_ = line;
// TODO: verify interrupt connection. Externally, but stubbed off here.
// if(!interrupt_disable_ && line) {
// perform_interrupt<false>(0x1ff4);
// }
if(line) {
set_interrupt_request(interrupt_control_, 0x02, 0x1ff4);
}
}
}
@@ -225,6 +223,16 @@ template<bool is_brk> inline void Executor::perform_interrupt(uint16_t vector) {
set_program_counter(uint16_t(memory_[vector] | (memory_[vector+1] << 8)));
}
void Executor::set_interrupt_request(uint8_t &reg, uint8_t value, uint16_t vector) {
// TODO: this allows interrupts through only if fully enabled at the time they
// signal. Which isn't quite correct, albeit that it seems sufficient for the
// IIgs ADB controller.
reg |= value;
if(!interrupt_disable_ && (reg & (value >> 1))) {
perform_interrupt<false>(vector);
}
}
template <Operation operation, AddressingMode addressing_mode> void Executor::perform() {
// Post cycle cost; this emulation _does not provide accurate timing_.
#define TLength(mode, base) case AddressingMode::mode: subtract_duration(base + t_lengths[index_mode_]); break;
@@ -789,12 +797,17 @@ inline void Executor::subtract_duration(int duration) {
// this additional divide by 4 produces the correct net divide by 16.
timer_divider_ += duration;
const int t12_ticks = update_timer(prescalers_[0], timer_divider_ / t12_divider);
const int clock_ticks = timer_divider_ / t12_divider;
timer_divider_ &= (t12_divider-1);
// Update timers 1 and 2. TODO: interrupts (elsewhere?).
if(update_timer(timers_[0], t12_ticks)) interrupt_control_ |= 0x20;
if(update_timer(timers_[1], t12_ticks)) interrupt_control_ |= 0x08;
// Update timers 1 and 2.
const int t12_ticks = update_timer(prescalers_[0], timer_divider_ / t12_divider);
if(update_timer(timers_[0], t12_ticks)) {
set_interrupt_request(interrupt_control_, 0x20, 0x1ff8);
}
if(update_timer(timers_[1], t12_ticks)) {
set_interrupt_request(interrupt_control_, 0x08, 0x1ff6);
}
// If timer X is disabled, stop.
if(timer_control_&0x20) {
@@ -804,9 +817,10 @@ inline void Executor::subtract_duration(int duration) {
// Update timer X prescaler.
switch(timer_control_ & 0x0c) {
default: {
const int tx_ticks = update_timer(prescalers_[1], t12_ticks); // TODO: don't hard code this. And is this even right?
if(update_timer(timers_[2], tx_ticks))
timer_control_ |= 0x80; // TODO: interrupt result of this.
const int tx_ticks = update_timer(prescalers_[1], clock_ticks);
if(update_timer(timers_[2], tx_ticks)) {
set_interrupt_request(timer_control_, 0x80, 0x1ffa);
}
} break;
case 0x04:
LOG("TODO: Timer X; Pulse output mode");

View File

@@ -165,6 +165,8 @@ class Executor: public CachingExecutor {
template<bool is_brk> inline void perform_interrupt(uint16_t vector);
inline void set_port_output(int port);
void set_interrupt_request(uint8_t &reg, uint8_t value, uint16_t vector);
// MARK: - Execution time
Cycles cycles_;

View File

@@ -72,8 +72,13 @@ void Keyboard::did_receive_data(const Command &, const std::vector<uint8_t> &dat
bool Keyboard::set_key_pressed(Key key, bool is_pressed) {
// ADB keyboard events: low 7 bits are a key code; bit 7 is either 0 for pressed or 1 for released.
std::lock_guard lock_guard(keys_mutex_);
if(pressed_keys_[size_t(key)] == is_pressed) {
return true;
}
// ADB keyboard events: low 7 bits are a key code;
// bit 7 is either 0 for pressed or 1 for released.
pending_events_.push_back(uint8_t(key) | (is_pressed ? 0x00 : 0x80));
pressed_keys_[size_t(key)] = is_pressed;

View File

@@ -106,7 +106,7 @@ class Keyboard: public ReactiveDevice {
void did_receive_data(const Command &, const std::vector<uint8_t> &) override;
std::mutex keys_mutex_;
std::array<bool, 128> pressed_keys_;
std::array<bool, 128> pressed_keys_{};
std::vector<uint8_t> pending_events_;
uint16_t modifiers_ = 0xffff;
};

View File

@@ -15,24 +15,36 @@ using namespace Apple::ADB;
Mouse::Mouse(Bus &bus) : ReactiveDevice(bus, 3) {}
void Mouse::perform_command(const Command &command) {
if(command.type == Command::Type::Talk && command.reg == 0) {
// Read current deltas and buttons, thread safely.
auto delta_x = delta_x_.exchange(0);
auto delta_y = delta_y_.exchange(0);
const int buttons = button_flags_;
// Mouse deltas are confined to a seven-bit signed field; this implementation keeps things symmetrical by
// limiting them to a maximum absolute value of 63 in any direction.
static constexpr int16_t max_delta = 63;
// Clamp deltas.
delta_x = std::clamp(delta_x, int16_t(-128), int16_t(127));
delta_y = std::clamp(delta_y, int16_t(-128), int16_t(127));
if(command.type == Command::Type::Talk && command.reg == 0) {
// Read and clamp current deltas and buttons.
//
// There's some small chance of creating negative feedback here — taking too much off delta_x_ or delta_y_
// due to a change in the underlying value between the load and the arithmetic. But if that occurs it means
// the user moved the mouse again in the interim, so it'll just play out as very slight latency.
auto delta_x = delta_x_.load(std::memory_order::memory_order_relaxed);
auto delta_y = delta_y_.load(std::memory_order::memory_order_relaxed);
if(abs(delta_x) > max_delta || abs(delta_y) > max_delta) {
int max = std::max(abs(delta_x), abs(delta_y));
delta_x = delta_x * max_delta / max;
delta_y = delta_y * max_delta / max;
}
const int buttons = button_flags_.load(std::memory_order::memory_order_relaxed);
delta_x_ -= delta_x;
delta_y_ -= delta_y;
// Figure out what that would look like, and don't respond if there's
// no change to report.
// no change or deltas to report.
const uint16_t reg0 =
((buttons & 1) ? 0x0000 : 0x8000) |
((buttons & 2) ? 0x0000 : 0x0080) |
uint16_t(delta_x & 0x7f) |
uint16_t((delta_y & 0x7f) << 8);
if(reg0 == last_posted_reg0_) return;
if(!(reg0 & 0x7f7f) && (reg0 & 0x8080) == (last_posted_reg0_ & 0x8080)) return;
// Post change.
last_posted_reg0_ = reg0;

View File

@@ -76,11 +76,15 @@ uint8_t GLU::get_mouse_data() {
// b5b0: mouse delta.
const uint8_t result = registers_[visible_mouse_register_];
if(visible_mouse_register_ == 2) {
++visible_mouse_register_;
} else {
if(visible_mouse_register_ == 3) {
status_ &= ~uint8_t(CPUFlags::MouseDataFull);
}
// Spelt out at tedious length because Clang has trust issues.
static constexpr int first_register = 2;
static constexpr int second_register = 3;
static constexpr int flip_mask = first_register ^ second_register;
visible_mouse_register_ ^= flip_mask;
return result;
}
@@ -116,7 +120,7 @@ uint8_t GLU::get_status() {
// b2: 1 = keyboard data interrupt is enabled.
// b1: 1 = mouse x-data is available; 0 = y.
// b0: 1 = command register is full (set when command is written); 0 = empty (cleared when data is read).
return status_ | ((visible_mouse_register_ == 2) ? uint8_t(CPUFlags::MouseXIsAvailable) : 0);
return status_ | ((visible_mouse_register_ == 2) ? 0 : uint8_t(CPUFlags::MouseXIsAvailable));
}
void GLU::set_status(uint8_t status) {
@@ -223,7 +227,6 @@ void GLU::set_port_output(int port, uint8_t value) {
case 3:
status_ |= uint8_t(CPUFlags::MouseDataFull);
visible_mouse_register_ = 2;
printf("Mouse: %d <- %02x\n", register_address_, register_latch_);
break;
case 7: status_ |= uint8_t(CPUFlags::CommandDataIsValid); break;
}
@@ -286,5 +289,6 @@ void GLU::run_ports_for(Cycles cycles) {
}
void GLU::set_vertical_blank(bool is_blank) {
vertical_blank_ = is_blank;
executor_.set_interrupt_line(is_blank);
}

View File

@@ -752,7 +752,7 @@ class ConcreteMachine:
is_1Mhz = true;
break;
case Read(0xc045):
// MMDELTAX byte.
// MMDELTAY byte.
*value = 0;
is_1Mhz = true;
break;
@@ -1171,7 +1171,7 @@ class ConcreteMachine:
machine_->update_audio();
}
~AudioUpdater() {
machine_->cycles_until_audio_event_ = machine_->sound_glu_.get_next_sequence_point();
machine_->cycles_until_audio_event_ = machine_->sound_glu_.next_sequence_point();
}
private:
ConcreteMachine *machine_;

View File

@@ -218,7 +218,7 @@ uint8_t GLU::get_address_high() {
// MARK: - Update logic.
Cycles GLU::get_next_sequence_point() const {
Cycles GLU::next_sequence_point() const {
uint32_t result = std::numeric_limits<decltype(result)>::max();
for(int c = 0; c < local_.oscillator_count; c++) {

View File

@@ -31,7 +31,7 @@ class GLU: public Outputs::Speaker::SampleSource {
uint8_t get_address_high();
void run_for(Cycles);
Cycles get_next_sequence_point() const;
Cycles next_sequence_point() const;
bool get_interrupt_line();
// SampleSource.

View File

@@ -191,7 +191,7 @@ void Video::advance(Cycles cycles) {
}
}
Cycles Video::get_next_sequence_point() const {
Cycles Video::next_sequence_point() const {
const int cycles_into_row = cycles_into_frame_ % CyclesPerLine;
const int row = cycles_into_frame_ / CyclesPerLine;
@@ -285,7 +285,7 @@ void Video::output_row(int row, int start, int end) {
// Post an interrupt if requested.
if(line_control_ & 0x40) {
set_interrupts(0x20);
interrupts_.add(0x20);
}
// Set up appropriately for fill mode (or not).
@@ -498,19 +498,19 @@ uint8_t Video::get_new_video() {
}
void Video::clear_interrupts(uint8_t mask) {
set_interrupts(interrupts_ & ~(mask & 0x60));
interrupts_.clear(mask);
}
void Video::set_interrupt_register(uint8_t mask) {
set_interrupts(interrupts_ | (mask & 0x6));
interrupts_.set_control(mask);
}
uint8_t Video::get_interrupt_register() {
return interrupts_;
return interrupts_.status();
}
bool Video::get_interrupt_line() {
return (interrupts_&0x80) || (megaii_interrupt_mask_&megaii_interrupt_state_);
return interrupts_.active() || (megaii_interrupt_mask_ & megaii_interrupt_state_);
}
void Video::set_megaii_interrupts_enabled(uint8_t mask) {
@@ -526,13 +526,7 @@ void Video::clear_megaii_interrupts() {
}
void Video::notify_clock_tick() {
set_interrupts(interrupts_ | 0x40);
}
void Video::set_interrupts(uint8_t new_value) {
interrupts_ = new_value & 0x7f;
if((interrupts_ >> 4) & interrupts_ & 0x6)
interrupts_ |= 0x80;
interrupts_.add(0x40);
}
void Video::set_border_colour(uint8_t colour) {
@@ -635,21 +629,21 @@ uint16_t *Video::output_double_high_resolution_mono(uint16_t *target, int start,
ram_[row_address + c],
};
target[0] = colours[(source[1] >> 0) & 0x1];
target[1] = colours[(source[1] >> 1) & 0x1];
target[2] = colours[(source[1] >> 2) & 0x1];
target[3] = colours[(source[1] >> 3) & 0x1];
target[4] = colours[(source[1] >> 4) & 0x1];
target[5] = colours[(source[1] >> 5) & 0x1];
target[6] = colours[(source[1] >> 6) & 0x1];
target[0] = colours[(source[0] >> 0) & 0x1];
target[1] = colours[(source[0] >> 1) & 0x1];
target[2] = colours[(source[0] >> 2) & 0x1];
target[3] = colours[(source[0] >> 3) & 0x1];
target[4] = colours[(source[0] >> 4) & 0x1];
target[5] = colours[(source[0] >> 5) & 0x1];
target[6] = colours[(source[0] >> 6) & 0x1];
target[7] = colours[(source[0] >> 0) & 0x1];
target[8] = colours[(source[0] >> 1) & 0x1];
target[9] = colours[(source[0] >> 2) & 0x1];
target[10] = colours[(source[0] >> 3) & 0x1];
target[11] = colours[(source[0] >> 4) & 0x1];
target[12] = colours[(source[0] >> 5) & 0x1];
target[13] = colours[(source[0] >> 6) & 0x1];
target[7] = colours[(source[1] >> 0) & 0x1];
target[8] = colours[(source[1] >> 1) & 0x1];
target[9] = colours[(source[1] >> 2) & 0x1];
target[10] = colours[(source[1] >> 3) & 0x1];
target[11] = colours[(source[1] >> 4) & 0x1];
target[12] = colours[(source[1] >> 5) & 0x1];
target[13] = colours[(source[1] >> 6) & 0x1];
target += 14;
}

View File

@@ -60,7 +60,7 @@ class Video: public Apple::II::VideoSwitches<Cycles> {
Outputs::Display::DisplayType get_display_type() const;
/// Determines the period until video might autonomously update its interrupt lines.
Cycles get_next_sequence_point() const;
Cycles next_sequence_point() const;
/// Sets the Mega II interrupt enable state — 1/4-second and VBL interrupts are
/// generated here.
@@ -123,8 +123,56 @@ class Video: public Apple::II::VideoSwitches<Cycles> {
void advance(Cycles);
uint8_t new_video_ = 0x01;
uint8_t interrupts_ = 0x00;
void set_interrupts(uint8_t);
class Interrupts {
public:
void add(uint8_t value) {
// Taken literally, status accumulates regardless of being enabled,
// potentially to be polled, it simply doesn't trigger an interrupt.
value_ |= value;
test();
}
void clear(uint8_t value) {
// Zeroes in bits 5 or 6 clear the respective interrupts.
value_ &= value | ~0x60;
test();
}
void set_control(uint8_t value) {
// Ones in bits 1 or 2 enable the respective interrupts.
value_ = (value_ & ~0x6) | (value & 0x6);
test();
}
uint8_t status() const {
return value_;
}
bool active() const {
return value_ & 0x80;
}
private:
void test() {
value_ &= 0x7f;
if((value_ >> 4) & value_ & 0x6) {
value_ |= 0x80;
}
}
// Overall meaning of value is as per the VGC interrupt register, i.e.
//
// b7: interrupt status;
// b6: 1-second interrupt status;
// b5: scan-line interrupt status;
// b4: reserved;
// b3: reserved;
// b2: 1-second interrupt enable;
// b1: scan-line interrupt enable;
// b0: reserved.
uint8_t value_ = 0x00;
} interrupts_;
int cycles_into_frame_ = 0;
const uint8_t *ram_ = nullptr;

View File

@@ -575,7 +575,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
video_.run_for(time_until_video_event_);
time_since_video_update_ -= time_until_video_event_;
time_until_video_event_ = video_.get_next_sequence_point();
time_until_video_event_ = video_.next_sequence_point();
via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !video_.vsync());
}
@@ -626,7 +626,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
forceinline void update_video() {
video_.run_for(time_since_video_update_.flush<HalfCycles>());
time_until_video_event_ = video_.get_next_sequence_point();
time_until_video_event_ = video_.next_sequence_point();
}
Inputs::Mouse &get_mouse() final {

View File

@@ -170,7 +170,7 @@ bool Video::vsync() {
return line >= 353 && line < 356;
}
HalfCycles Video::get_next_sequence_point() {
HalfCycles Video::next_sequence_point() {
const auto line = (frame_position_ / line_length).as_integral();
if(line >= 353 && line < 356) {
// Currently in vsync, so get time until start of line 357,

View File

@@ -80,7 +80,7 @@ class Video {
@returns the amount of time until there is next a transition on the
vsync signal.
*/
HalfCycles get_next_sequence_point();
HalfCycles next_sequence_point();
private:
DeferredAudio &audio_;

View File

@@ -395,7 +395,7 @@ bool Video::display_enabled() {
return public_state_.display_enable;
}
HalfCycles Video::get_next_sequence_point() {
HalfCycles Video::next_sequence_point() {
// The next sequence point will be whenever display_enabled, vsync or hsync next changes.
// Sequence of events within a standard line:

View File

@@ -68,7 +68,7 @@ class Video {
@returns the number of cycles until there is next a change in the hsync,
vsync or display_enable outputs.
*/
HalfCycles get_next_sequence_point();
HalfCycles next_sequence_point();
/*!
@returns @c true if the horizontal sync output is currently active; @c false otherwise.

View File

@@ -370,7 +370,7 @@ void VideoOutput::setup_base_address() {
// MARK: - Interrupts
Cycles VideoOutput::get_next_sequence_point() {
Cycles VideoOutput::next_sequence_point() {
if(output_position_ < real_time_clock_interrupt_1) {
return real_time_clock_interrupt_1 - output_position_;
}

View File

@@ -61,7 +61,7 @@ class VideoOutput {
This result may be mutated by calls to @c write.
*/
Cycles get_next_sequence_point();
Cycles next_sequence_point();
/*!
@returns a bit mask of all interrupts that have been triggered since the last call to get_interrupt().

View File

@@ -315,7 +315,7 @@ void TimedInterruptSource::run_for(Cycles duration) {
update_channel(1, rate_ == InterruptRate::ToneGenerator1, cycles.as<int>());
}
Cycles TimedInterruptSource::get_next_sequence_point() const {
Cycles TimedInterruptSource::next_sequence_point() const {
// Since both the 1kHz and 50Hz timers are integer dividers of the 1Hz timer, there's no need
// to factor that one in when determining the next sequence point for either of those.
switch(rate_) {

View File

@@ -144,7 +144,7 @@ class TimedInterruptSource {
/// @returns The amount of time from now until the earliest that
/// @c get_new_interrupts() _might_ have new interrupts to report.
Cycles get_next_sequence_point() const;
Cycles next_sequence_point() const;
private:
static constexpr Cycles clock_rate{250000};

View File

@@ -467,7 +467,7 @@ void Nick::set_output_type(OutputType type, bool force_flush) {
// MARK: - Sequence points.
Cycles Nick::get_next_sequence_point() const {
Cycles Nick::next_sequence_point() const {
constexpr int load_point = 16; // i.e. 16 cycles after the start of the line, the
// interrupt line may change. That is, after the
// second byte of the mode line has been read.

View File

@@ -38,7 +38,7 @@ class Nick {
Outputs::Display::ScanStatus get_scaled_scan_status() const;
/// @returns The amount of time until the next potential change in interrupt output.
Cycles get_next_sequence_point() const;
Cycles next_sequence_point() const;
/*!
@returns The current state of the interrupt line — @c true for active;

View File

@@ -315,7 +315,7 @@ template <Timing timing> class Video {
/*!
@returns The amount of time until the next change in the interrupt line, that being the only internally-observeable output.
*/
HalfCycles get_next_sequence_point() {
HalfCycles next_sequence_point() {
constexpr auto timings = get_timings();
// Is the frame still ahead of this interrupt?

31
Numeric/Carry.hpp Normal file
View File

@@ -0,0 +1,31 @@
//
// Carry.hpp
// Clock Signal
//
// Created by Thomas Harte on 30/08/2023.
// Copyright © 2023 Thomas Harte. All rights reserved.
//
#ifndef Carry_hpp
#define Carry_hpp
namespace Numeric {
/// @returns @c true if there was carry out of @c bit when @c source1 and @c source2 were added, producing @c result.
template <int bit, typename IntT> bool carried_out(IntT source1, IntT source2, IntT result) {
// 0 and 0 => didn't.
// 0 and 1 or 1 and 0 => did if 0.
// 1 and 1 => did.
return IntT(1 << bit) & (source1 | source2) & ((source1 & source2) | ~result);
}
/// @returns @c true if there was carry into @c bit when @c source1 and @c source2 were added, producing @c result.
template <int bit, typename IntT> bool carried_in(IntT source1, IntT source2, IntT result) {
// 0 and 0 or 1 and 1 => did if 1
// 0 and 1 or 1 and 0 => did if 0
return IntT(1 << bit) & (source1 ^ source2 ^ result);
}
}
#endif /* Carry_hpp */

View File

@@ -1101,6 +1101,7 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
4281572E2AA0334300E16AA1 /* Carry.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Carry.hpp; sourceTree = "<group>"; };
428168372A16C25C008ECD27 /* LineLayout.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = LineLayout.hpp; sourceTree = "<group>"; };
428168392A37AFB4008ECD27 /* DispatcherTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DispatcherTests.mm; sourceTree = "<group>"; };
42AD552E2A0C4D5000ACE410 /* 68000.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000.hpp; sourceTree = "<group>"; };
@@ -3313,6 +3314,7 @@
4B66E1A8297719270057ED0F /* NumericCoder.hpp */,
4BB5B995281B1D3E00522DA9 /* RegisterSizes.hpp */,
4BFEA2F12682A90200EBF94C /* Sizes.hpp */,
4281572E2AA0334300E16AA1 /* Carry.hpp */,
);
name = Numeric;
path = ../../Numeric;
@@ -6539,6 +6541,10 @@
GCC_WARN_UNKNOWN_PRAGMAS = YES;
GCC_WARN_UNUSED_LABEL = YES;
INFOPLIST_FILE = "Clock Signal/Info.plist";
LD_RUNPATH_SEARCH_PATHS = (
"$(LD_RUNPATH_SEARCH_PATHS_$(IS_MACCATALYST))",
/System/Library/PrivateFrameworks/Swift/libswiftCore.dylib,
);
MTL_TREAT_WARNINGS_AS_ERRORS = YES;
OTHER_CPLUSPLUSFLAGS = (
"$(OTHER_CFLAGS)",
@@ -6580,6 +6586,10 @@
GCC_WARN_UNKNOWN_PRAGMAS = YES;
GCC_WARN_UNUSED_LABEL = YES;
INFOPLIST_FILE = "Clock Signal/Info.plist";
LD_RUNPATH_SEARCH_PATHS = (
"$(LD_RUNPATH_SEARCH_PATHS_$(IS_MACCATALYST))",
/System/Library/PrivateFrameworks/Swift/libswiftCore.dylib,
);
MTL_TREAT_WARNINGS_AS_ERRORS = YES;
OTHER_CPLUSPLUSFLAGS = (
"$(OTHER_CFLAGS)",

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1400"
version = "1.3">
version = "1.8">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">

View File

@@ -14,20 +14,27 @@
#include <vector>
#include <unordered_map>
#include "6502Selector.hpp"
namespace {
struct StopException {};
struct BusHandler: public CPU::MOS6502Esque::BusHandler<uint32_t> {
// Use a map to store RAM contents, in order to preserve initialised state.
std::unordered_map<uint32_t, uint8_t> ram;
std::unordered_map<uint32_t, uint8_t> inventions;
template <CPU::MOS6502Esque::Type type>
struct BusHandler: public CPU::MOS6502Esque::BusHandlerT<type> {
using AddressType = typename CPU::MOS6502Esque::BusHandlerT<type>::AddressType;
Cycles perform_bus_operation(CPU::MOS6502Esque::BusOperation operation, uint32_t address, uint8_t *value) {
// Use a map to store RAM contents, in order to preserve initialised state.
std::unordered_map<AddressType, uint8_t> ram;
std::unordered_map<AddressType, uint8_t> inventions;
Cycles perform_bus_operation(CPU::MOS6502Esque::BusOperation operation, AddressType address, uint8_t *value) {
// Record the basics of the operation.
auto &cycle = cycles.emplace_back();
cycle.operation = operation;
cycle.extended_bus = processor.get_extended_bus_output();
if constexpr (has_extended_bus_output(type)) {
cycle.extended_bus = processor.get_extended_bus_output();
}
// Perform the operation, and fill in the cycle's value.
using BusOperation = CPU::MOS6502Esque::BusOperation;
@@ -92,25 +99,27 @@ struct BusHandler: public CPU::MOS6502Esque::BusHandler<uint32_t> {
allow_pc_repetition = opcode == 0x54 || opcode == 0x44;
using Register = CPU::MOS6502Esque::Register;
const uint32_t pc =
processor.value_of(Register::ProgramCounter) |
(processor.value_of(Register::ProgramBank) << 16);
const auto pc =
AddressType(
processor.value_of(Register::ProgramCounter) |
(processor.value_of(Register::ProgramBank) << 16)
);
inventions[pc] = ram[pc] = opcode;
}
int pc_overshoot = 0;
std::optional<uint32_t> initial_pc;
std::optional<AddressType> initial_pc;
bool allow_pc_repetition = false;
struct Cycle {
CPU::MOS6502Esque::BusOperation operation;
std::optional<uint32_t> address;
std::optional<AddressType> address;
std::optional<uint8_t> value;
int extended_bus;
int extended_bus = 0;
};
std::vector<Cycle> cycles;
CPU::WDC65816::Processor<BusHandler, false> processor;
CPU::MOS6502Esque::Processor<type, BusHandler<type>, false> processor;
BusHandler() : processor(*this) {
// Never run the official reset procedure.
@@ -118,21 +127,24 @@ struct BusHandler: public CPU::MOS6502Esque::BusHandler<uint32_t> {
}
};
template <typename Processor> void print_registers(FILE *file, const Processor &processor, int pc_offset) {
template <bool has_emulation, typename Processor> void print_registers(FILE *file, const Processor &processor, int pc_offset) {
using Register = CPU::MOS6502Esque::Register;
fprintf(file, "\"pc\": %d, ", (processor.value_of(Register::ProgramCounter) + pc_offset) & 65535);
fprintf(file, "\"s\": %d, ", processor.value_of(Register::StackPointer));
fprintf(file, "\"p\": %d, ", processor.value_of(Register::Flags));
fprintf(file, "\"a\": %d, ", processor.value_of(Register::A));
fprintf(file, "\"x\": %d, ", processor.value_of(Register::X));
fprintf(file, "\"y\": %d, ", processor.value_of(Register::Y));
fprintf(file, "\"dbr\": %d, ", processor.value_of(Register::DataBank));
fprintf(file, "\"d\": %d, ", processor.value_of(Register::Direct));
fprintf(file, "\"pbr\": %d, ", processor.value_of(Register::ProgramBank));
fprintf(file, "\"e\": %d, ", processor.value_of(Register::EmulationFlag));
fprintf(file, "\"p\": %d, ", processor.value_of(Register::Flags));
if constexpr (has_emulation) {
fprintf(file, "\"dbr\": %d, ", processor.value_of(Register::DataBank));
fprintf(file, "\"d\": %d, ", processor.value_of(Register::Direct));
fprintf(file, "\"pbr\": %d, ", processor.value_of(Register::ProgramBank));
fprintf(file, "\"e\": %d, ", processor.value_of(Register::EmulationFlag));
}
}
void print_ram(FILE *file, const std::unordered_map<uint32_t, uint8_t> &data) {
template <typename IntT>
void print_ram(FILE *file, const std::unordered_map<IntT, uint8_t> &data) {
fprintf(file, "\"ram\": [");
bool is_first = true;
for(const auto &pair: data) {
@@ -143,30 +155,30 @@ void print_ram(FILE *file, const std::unordered_map<uint32_t, uint8_t> &data) {
fprintf(file, "]");
}
}
// MARK: - New test generator.
@interface TestGenerator : NSObject
@end
@implementation TestGenerator
- (void)generate {
BusHandler handler;
template <CPU::MOS6502Esque::Type type> void generate() {
BusHandler<type> handler;
constexpr bool has_emulation = has(type, CPU::MOS6502Esque::Register::EmulationFlag);
NSString *const tempDir = NSTemporaryDirectory();
NSLog(@"Outputting to %@", tempDir);
for(int operation = 0; operation < 512; operation++) {
for(int operation = 0; operation < (has_emulation ? 512 : 256); operation++) {
// Make tests repeatable, at least for any given instance of
// the runtime.
srand(65816 + operation);
constexpr auto type_offset = int(CPU::MOS6502Esque::Type::TWDC65816) - int(type);
srand(65816 + operation + type_offset);
const bool is_emulated = operation & 256;
const uint8_t opcode = operation & 255;
NSString *const targetName = [NSString stringWithFormat:@"%@%02x.%c.json", tempDir, opcode, is_emulated ? 'e' : 'n'];
NSString *const targetName =
has_emulation ?
[NSString stringWithFormat:@"%@%02x.%c.json", tempDir, opcode, is_emulated ? 'e' : 'n'] :
[NSString stringWithFormat:@"%@%02x.json", tempDir, opcode];
FILE *const target = fopen(targetName.UTF8String, "wt");
bool is_first_test = true;
@@ -186,21 +198,28 @@ void print_ram(FILE *file, const std::unordered_map<uint32_t, uint8_t> &data) {
handler.processor.set_value_of(Register::Y, rand() >> 8);
handler.processor.set_value_of(Register::ProgramCounter, rand() >> 8);
handler.processor.set_value_of(Register::StackPointer, rand() >> 8);
handler.processor.set_value_of(Register::DataBank, rand() >> 8);
handler.processor.set_value_of(Register::ProgramBank, rand() >> 8);
handler.processor.set_value_of(Register::Direct, rand() >> 8);
// ... except for emulation mode, which is a given.
// And is set last to ensure proper internal state is applied.
handler.processor.set_value_of(Register::EmulationFlag, is_emulated);
if(has_emulation) {
handler.processor.set_value_of(Register::DataBank, rand() >> 8);
handler.processor.set_value_of(Register::ProgramBank, rand() >> 8);
handler.processor.set_value_of(Register::Direct, rand() >> 8);
// ... except for emulation mode, which is a given.
// And is set last to ensure proper internal state is applied.
handler.processor.set_value_of(Register::EmulationFlag, is_emulated);
}
// Establish the opcode.
handler.setup(opcode);
// Dump initial state.
fprintf(target, "{ \"name\": \"%02x %c %d\", ", opcode, is_emulated ? 'e' : 'n', test + 1);
if(has_emulation) {
fprintf(target, "{ \"name\": \"%02x %c %d\", ", opcode, is_emulated ? 'e' : 'n', test + 1);
} else {
fprintf(target, "{ \"name\": \"%02x %d\", ", opcode, test + 1);
}
fprintf(target, "\"initial\": {");
print_registers(target, handler.processor, 0);
print_registers<has_emulation>(target, handler.processor, 0);
// Run to the second opcode fetch.
try {
@@ -212,7 +231,7 @@ void print_ram(FILE *file, const std::unordered_map<uint32_t, uint8_t> &data) {
// Dump final state.
fprintf(target, "}, \"final\": {");
print_registers(target, handler.processor, handler.pc_overshoot);
print_registers<has_emulation>(target, handler.processor, handler.pc_overshoot);
print_ram(target, handler.ram);
fprintf(target, "}, ");
@@ -247,12 +266,6 @@ void print_ram(FILE *file, const std::unordered_map<uint32_t, uint8_t> &data) {
assert(false);
}
using ExtendedBusOutput = CPU::WDC65816::ExtendedBusOutput;
const bool emulation = cycle.extended_bus & ExtendedBusOutput::Emulation;
const bool memory_size = cycle.extended_bus & ExtendedBusOutput::MemorySize;
const bool index_size = cycle.extended_bus & ExtendedBusOutput::IndexSize;
const bool memory_lock = cycle.extended_bus & ExtendedBusOutput::MemoryLock;
fprintf(target, "[");
if(cycle.address) {
fprintf(target, "%d, ", *cycle.address);
@@ -264,16 +277,31 @@ void print_ram(FILE *file, const std::unordered_map<uint32_t, uint8_t> &data) {
} else {
fprintf(target, "null, ");
}
fprintf(target, "\"%c%c%c%c%c%c%c%c\"]",
vda ? 'd' : '-',
vpa ? 'p' : '-',
vpb ? 'v' : '-',
wait ? '-' : (read ? 'r' : 'w'),
wait ? '-' : (emulation ? 'e' : '-'),
wait ? '-' : (memory_size ? 'm' : '-'),
wait ? '-' : (index_size ? 'x' : '-'),
wait ? '-' : (memory_lock ? 'l' : '-')
);
if(has_emulation) {
using ExtendedBusOutput = CPU::WDC65816::ExtendedBusOutput;
const bool emulation = cycle.extended_bus & ExtendedBusOutput::Emulation;
const bool memory_size = cycle.extended_bus & ExtendedBusOutput::MemorySize;
const bool index_size = cycle.extended_bus & ExtendedBusOutput::IndexSize;
const bool memory_lock = cycle.extended_bus & ExtendedBusOutput::MemoryLock;
fprintf(target, "\"%c%c%c%c%c%c%c%c\"]",
vda ? 'd' : '-',
vpa ? 'p' : '-',
vpb ? 'v' : '-',
wait ? '-' : (read ? 'r' : 'w'),
wait ? '-' : (emulation ? 'e' : '-'),
wait ? '-' : (memory_size ? 'm' : '-'),
wait ? '-' : (index_size ? 'x' : '-'),
wait ? '-' : (memory_lock ? 'l' : '-')
);
} else {
if(read) {
fprintf(target, "\"read\"]");
} else {
fprintf(target, "\"write\"]");
}
}
}
// Terminate object.
@@ -285,7 +313,7 @@ void print_ram(FILE *file, const std::unordered_map<uint32_t, uint8_t> &data) {
}
}
@end
}
// MARK: - Existing test evaluator.
@@ -296,7 +324,7 @@ void print_ram(FILE *file, const std::unordered_map<uint32_t, uint8_t> &data) {
// A generator for tests; not intended to be a permanent fixture.
//- (void)testGenerate {
// [[[TestGenerator alloc] init] generate];
// generate<CPU::MOS6502Esque::Type::TWDC65816>();
//}
@end

View File

@@ -685,7 +685,7 @@ void print_transactions(FILE *target, const std::vector<Transaction> &transactio
} else {
printf("\nAll failing operations:\n");
for(const auto operation: failing_operations) {
printf("%d,\n", int(operation));
printf("%s,\n", InstructionSet::M68k::to_string(operation));
}
}

View File

@@ -124,7 +124,7 @@ struct VideoTester {
display_enable = _video->display_enabled();
vsync = _video->vsync();
hsync = _video->hsync();
next_event = _video->get_next_sequence_point();
next_event = _video->next_sequence_point();
} else {
NSAssert(display_enable == _video->display_enabled(), @"Unannounced change in display enabled at cycle %zu [%d before next sequence point]", c, next_event.as<int>());
NSAssert(vsync == _video->vsync(), @"Unannounced change in vsync at cycle %zu [%d before next sequence point]", c, next_event.as<int>());

View File

@@ -11,10 +11,10 @@ import XCTest
class BCDTest: XCTestCase, CSTestMachineTrapHandler {
func testBCD() {
func testBCD(processor: CSTestMachine6502Processor) {
if let filename = Bundle(for: type(of: self)).path(forResource: "BCDTEST_beeb", ofType: nil) {
if let bcdTest = try? Data(contentsOf: URL(fileURLWithPath: filename)) {
let machine = CSTestMachine6502(processor: .processor6502)
let machine = CSTestMachine6502(processor: processor)
machine.trapHandler = self
machine.setData(bcdTest, atAddress: 0x2900)
@@ -40,13 +40,23 @@ class BCDTest: XCTestCase, CSTestMachineTrapHandler {
}
}
func test6502BCD() {
testBCD(processor: .processor6502)
}
func test65C02BCD() {
testBCD(processor: .processor65C02)
}
private var output: String = ""
func testMachine(_ testMachine: CSTestMachine, didTrapAtAddress address: UInt16) {
let machine6502 = testMachine as! CSTestMachine6502
// Only OSWRCH is trapped, so...
let character = machine6502.value(for: .A)
output.append(Character(UnicodeScalar(character)!))
let character = Character(UnicodeScalar(machine6502.value(for: .A))!)
if character != "\r" { // The test internally uses \r\n; keep only one of those.
output.append(character)
}
}
}

View File

@@ -38,7 +38,7 @@
int toggles = 0;
int interrupts = 0;
uint8_t dividerState = _interruptSource->get_divider_state() & 1;
int nextSequencePoint = _interruptSource->get_next_sequence_point().as<int>();
int nextSequencePoint = _interruptSource->next_sequence_point().as<int>();
for(int c = 0; c < 250000 * 5; c++) {
// Advance one cycle. Clock is 500,000 Hz.
@@ -55,7 +55,7 @@
const uint8_t newInterrupts = _interruptSource->get_new_interrupts();
if(newInterrupts) {
XCTAssertEqual(nextSequencePoint, 0);
nextSequencePoint = _interruptSource->get_next_sequence_point().as<int>();
nextSequencePoint = _interruptSource->next_sequence_point().as<int>();
if(newInterrupts & 0x02) {
++interrupts;
@@ -66,7 +66,7 @@
}
}
XCTAssertEqual(nextSequencePoint, _interruptSource->get_next_sequence_point().as<int>(), @"At cycle %d", c);
XCTAssertEqual(nextSequencePoint, _interruptSource->next_sequence_point().as<int>(), @"At cycle %d", c);
}
XCTAssertEqual(toggles, int(expectedInterruptsPerSecond * 5.0));

View File

@@ -46,7 +46,7 @@
- (void)testInterruptPrediction {
// Run for the number of cycles implied by the number of lines.
int next_sequence_point = _nick->get_next_sequence_point().as<int>();
int next_sequence_point = _nick->next_sequence_point().as<int>();
bool last_interrupt_line = _nick->get_interrupt_line();
for(int c = 0; c < _totalLines*912; c++) {
@@ -61,9 +61,9 @@
last_interrupt_line = interrupt_line;
if(!next_sequence_point) {
next_sequence_point = _nick->get_next_sequence_point().as<int>();
next_sequence_point = _nick->next_sequence_point().as<int>();
} else {
const int expected_next_sequence_point = _nick->get_next_sequence_point().as<int>();
const int expected_next_sequence_point = _nick->next_sequence_point().as<int>();
XCTAssertEqual(next_sequence_point, expected_next_sequence_point);
}
}

View File

@@ -31,14 +31,14 @@
int c = 5;
bool vsync = _video->vsync();
while(c--) {
auto remaining_time_in_state = _video->get_next_sequence_point().as_integral();
auto remaining_time_in_state = _video->next_sequence_point().as_integral();
NSLog(@"Vsync %@ expected for %@ half-cycles", vsync ? @"on" : @"off", @(remaining_time_in_state));
while(remaining_time_in_state--) {
XCTAssertEqual(vsync, _video->vsync());
_video->run_for(HalfCycles(1));
if(remaining_time_in_state)
XCTAssertEqual(remaining_time_in_state, _video->get_next_sequence_point().as_integral());
XCTAssertEqual(remaining_time_in_state, _video->next_sequence_point().as_integral());
}
vsync ^= true;
}

View File

@@ -36,7 +36,7 @@ using VDP = TI::TMS::TMS9918<TI::TMS::Personality::SMSVDP>;
vdp.write(1, 0x8a);
// Get time until interrupt.
auto time_until_interrupt = vdp.get_next_sequence_point().as_integral() - 1;
auto time_until_interrupt = vdp.next_sequence_point().as_integral() - 1;
// Check that an interrupt is now scheduled.
NSAssert(time_until_interrupt != HalfCycles::max().as_integral() - 1, @"No interrupt scheduled");
@@ -55,7 +55,7 @@ using VDP = TI::TMS::TMS9918<TI::TMS::Personality::SMSVDP>;
NSAssert(!vdp.get_interrupt_line(), @"Interrupt wasn't reset by status read");
// Check interrupt flag isn't set prior to the reported time.
time_until_interrupt = vdp.get_next_sequence_point().as_integral() - 1;
time_until_interrupt = vdp.next_sequence_point().as_integral() - 1;
vdp.run_for(HalfCycles(time_until_interrupt));
NSAssert(!vdp.get_interrupt_line(), @"Interrupt line went active early [2]");
@@ -82,7 +82,7 @@ using VDP = TI::TMS::TMS9918<TI::TMS::Personality::SMSVDP>;
// Clear the pending interrupt and ask about the next one (i.e. the first one).
vdp.read(1);
auto time_until_interrupt = vdp.get_next_sequence_point().as_integral() - 1;
auto time_until_interrupt = vdp.next_sequence_point().as_integral() - 1;
// Check that an interrupt is now scheduled.
NSAssert(time_until_interrupt != HalfCycles::max().as_integral() - 1, @"No interrupt scheduled");
@@ -116,7 +116,7 @@ using VDP = TI::TMS::TMS9918<TI::TMS::Personality::SMSVDP>;
// Now run through an entire frame...
int half_cycles = 262*228*2;
auto last_time_until_interrupt = vdp.get_next_sequence_point().as_integral();
auto last_time_until_interrupt = vdp.next_sequence_point().as_integral();
while(half_cycles--) {
// Validate that an interrupt happened if one was expected, and clear anything that's present.
NSAssert(vdp.get_interrupt_line() == (last_time_until_interrupt == HalfCycles::max().as_integral()), @"Unexpected interrupt state change; expected %d but got %d; position %d %d @ %d", (last_time_until_interrupt == 0), vdp.get_interrupt_line(), c, with_eof, half_cycles);
@@ -129,7 +129,7 @@ using VDP = TI::TMS::TMS9918<TI::TMS::Personality::SMSVDP>;
vdp.run_for(HalfCycles(1));
// Get the time until interrupt.
auto time_until_interrupt = vdp.get_next_sequence_point().as_integral();
auto time_until_interrupt = vdp.next_sequence_point().as_integral();
NSAssert(time_until_interrupt != HalfCycles::max().as_integral() || vdp.get_interrupt_line(), @"No interrupt scheduled; position %d %d @ %d", c, with_eof, half_cycles);
NSAssert(time_until_interrupt >= 0, @"Interrupt is scheduled in the past; position %d %d @ %d", c, with_eof, half_cycles);

View File

@@ -15,6 +15,7 @@
#include "../6502Esque/6502Esque.hpp"
#include "../6502Esque/Implementation/LazyFlags.hpp"
#include "../../Numeric/Carry.hpp"
#include "../../Numeric/RegisterSizes.hpp"
#include "../../ClockReceiver/ClockReceiver.hpp"
@@ -124,6 +125,12 @@ class ProcessorBase: public ProcessorStorage {
@returns @c true if the 6502 is jammed; @c false otherwise.
*/
inline bool is_jammed() const;
/*!
FOR TESTING PURPOSES ONLY: forces the processor into a state where
the next thing it intends to do is fetch a new opcode.
*/
inline void restart_operation_fetch();
};
/*!

View File

@@ -282,55 +282,103 @@ template <Personality personality, typename T, bool uses_ready_line> void Proces
// MARK: - ADC/SBC (and INS)
case OperationINS:
operand_++;
++operand_;
[[fallthrough]];
case OperationSBC:
operand_ = ~operand_;
if(flags_.decimal && has_decimal_mode(personality)) {
const uint16_t notCarry = flags_.carry ^ 0x1;
const uint16_t decimalResult = uint16_t(a_) - uint16_t(operand_) - notCarry;
uint16_t temp16;
uint8_t result = a_ + operand_ + flags_.carry;
temp16 = (a_&0xf) - (operand_&0xf) - notCarry;
if(temp16 > 0xf) temp16 -= 0x6;
temp16 = (temp16&0x0f) | ((temp16 > 0x0f) ? 0xfff0 : 0x00);
temp16 += (a_&0xf0) - (operand_&0xf0);
// All flags are set based only on the decimal result.
flags_.zero_result = result;
flags_.carry = Numeric::carried_out<7>(a_, operand_, result);
flags_.negative_result = result;
flags_.overflow = (( (result ^ a_) & (result ^ operand_) ) & 0x80) >> 1;
flags_.overflow = ( ( (decimalResult^a_)&(~decimalResult^operand_) )&0x80) >> 1;
flags_.negative_result = uint8_t(temp16);
flags_.zero_result = uint8_t(decimalResult);
// General SBC logic:
//
// Because the range of valid numbers starts at 0, any subtraction that should have
// caused decimal carry and which requires a digit fix up will definitely have caused
// binary carry: the subtraction will have crossed zero and gone into negative numbers.
//
// So just test for carry (well, actually borrow, which is !carry).
if(temp16 > 0xff) temp16 -= 0x60;
// The bottom nibble is adjusted if there was borrow into the top nibble;
// on a 6502 additional borrow isn't propagated but on a 65C02 it is.
// This difference affects invalid BCD numbers only — valid numbers will
// never be less than -9 so adding 10 will always generate carry.
if(!Numeric::carried_in<4>(a_, operand_, result)) {
if constexpr (is_65c02(personality)) {
result += 0xfa;
} else {
result = (result & 0xf0) | ((result + 0xfa) & 0xf);
}
}
flags_.carry = (temp16 > 0xff) ? 0 : Flag::Carry;
a_ = uint8_t(temp16);
// The top nibble is adjusted only if there was borrow out of the whole byte.
if(!flags_.carry) {
result += 0xa0;
}
if(is_65c02(personality)) {
a_ = result;
// fix up in case this was INS.
if(cycle == OperationINS) operand_ = ~operand_;
if constexpr (is_65c02(personality)) {
// 65C02 fix: set the N and Z flags based on the final, decimal result.
// Read into `operation_` for the sake of reading somewhere; the value isn't
// used and INS will write `operand_` back to memory.
flags_.set_nz(a_);
read_mem(operand_, address_.full);
read_mem(operation_, address_.full);
break;
}
continue;
} else {
operand_ = ~operand_;
}
[[fallthrough]];
case OperationADC:
if(flags_.decimal && has_decimal_mode(personality)) {
const uint16_t decimalResult = uint16_t(a_) + uint16_t(operand_) + uint16_t(flags_.carry);
uint8_t result = a_ + operand_ + flags_.carry;
flags_.zero_result = result;
flags_.carry = Numeric::carried_out<7>(a_, operand_, result);
uint8_t low_nibble = (a_ & 0xf) + (operand_ & 0xf) + flags_.carry;
if(low_nibble >= 0xa) low_nibble = ((low_nibble + 0x6) & 0xf) + 0x10;
uint16_t result = uint16_t(a_ & 0xf0) + uint16_t(operand_ & 0xf0) + uint16_t(low_nibble);
flags_.negative_result = uint8_t(result);
flags_.overflow = (( (result^a_)&(result^operand_) )&0x80) >> 1;
if(result >= 0xa0) result += 0x60;
// General ADC logic:
//
// Detecting decimal carry means finding occasions when two digits added together totalled
// more than 9. Within each four-bit window that means testing the digit itself and also
// testing for carry — e.g. 5 + 5 = 0xA, which is detectable only by the value of the final
// digit, but 9 + 9 = 0x18, which is detectable only by spotting the carry.
flags_.carry = (result >> 8) ? 1 : 0;
a_ = uint8_t(result);
flags_.zero_result = uint8_t(decimalResult);
// Only a single bit of carry can flow from the bottom nibble to the top.
//
// So if that carry already happened, fix up the bottom without permitting another;
// otherwise permit the carry to happen (and check whether carry then rippled out of bit 7).
if(Numeric::carried_in<4>(a_, operand_, result)) {
result = (result & 0xf0) | ((result + 0x06) & 0x0f);
} else if((result & 0xf) > 0x9) {
flags_.carry |= result >= 0x100 - 0x6;
result += 0x06;
}
if(is_65c02(personality)) {
// 6502 quirk: N and V are set before the full result is computed but
// after the low nibble has been corrected.
flags_.negative_result = result;
flags_.overflow = (( (result ^ a_) & (result ^ operand_) ) & 0x80) >> 1;
// i.e. fix high nibble if there was carry out of bit 7 already, or if the
// top nibble is too large (in which case there will be carry after the fix-up).
flags_.carry |= result >= 0xa0;
if(flags_.carry) {
result += 0x60;
}
a_ = result;
if constexpr (is_65c02(personality)) {
// 65C02 fix: N and Z are set correctly based on the final BCD result, at the cost of
// an extra cycle.
flags_.set_nz(a_);
read_mem(operand_, address_.full);
break;
@@ -342,7 +390,7 @@ template <Personality personality, typename T, bool uses_ready_line> void Proces
flags_.carry = (result >> 8)&1;
}
// fix up in case this was INS
// fix up in case this was INS.
if(cycle == OperationINS) operand_ = ~operand_;
continue;
@@ -728,3 +776,8 @@ void ProcessorBase::set_value_of(Register r, uint16_t value) {
default: break;
}
}
void ProcessorBase::restart_operation_fetch() {
scheduled_program_counter_ = nullptr;
next_bus_operation_ = BusOperation::None;
}

View File

@@ -17,15 +17,15 @@ using namespace CPU::MOS6502;
#define Absolute CycleLoadAddressAbsolute
#define AbsoluteXr CycleLoadAddressAbsolute, CycleAddXToAddressLow, OperationCorrectAddressHigh
#define AbsoluteYr CycleLoadAddressAbsolute, CycleAddYToAddressLow, OperationCorrectAddressHigh
#define AbsoluteX CycleLoadAddressAbsolute, CycleAddXToAddressLowRead, OperationCorrectAddressHigh
#define AbsoluteY CycleLoadAddressAbsolute, CycleAddYToAddressLowRead, OperationCorrectAddressHigh
#define AbsoluteXw CycleLoadAddressAbsolute, CycleAddXToAddressLowRead, OperationCorrectAddressHigh
#define AbsoluteYw CycleLoadAddressAbsolute, CycleAddYToAddressLowRead, OperationCorrectAddressHigh
#define Zero OperationLoadAddressZeroPage
#define ZeroX CycleLoadAddessZeroX
#define ZeroY CycleLoadAddessZeroY
#define ZeroIndirect OperationLoadAddressZeroPage, CycleFetchAddressLowFromOperand, CycleIncrementOperandFetchAddressHigh
#define IndexedIndirect CycleIncrementPCFetchAddressLowFromOperand, CycleAddXToOperandFetchAddressLow, CycleIncrementOperandFetchAddressHigh
#define IndirectIndexedr CycleIncrementPCFetchAddressLowFromOperand, CycleIncrementOperandFetchAddressHigh, CycleAddYToAddressLow, OperationCorrectAddressHigh
#define IndirectIndexed CycleIncrementPCFetchAddressLowFromOperand, CycleIncrementOperandFetchAddressHigh, CycleAddYToAddressLowRead, OperationCorrectAddressHigh
#define IndirectIndexedw CycleIncrementPCFetchAddressLowFromOperand, CycleIncrementOperandFetchAddressHigh, CycleAddYToAddressLowRead, OperationCorrectAddressHigh
#define Read(...) CycleFetchOperandFromAddress, __VA_ARGS__
#define Write(...) __VA_ARGS__, CycleWriteOperandToAddress
@@ -42,23 +42,23 @@ using namespace CPU::MOS6502;
#define IndirectIndexedRead(op) Program(IndirectIndexedr, Read(op))
#define AbsoluteWrite(op) Program(Absolute, Write(op))
#define AbsoluteXWrite(op) Program(AbsoluteX, Write(op))
#define AbsoluteYWrite(op) Program(AbsoluteY, Write(op))
#define AbsoluteXWrite(op) Program(AbsoluteXw, Write(op))
#define AbsoluteYWrite(op) Program(AbsoluteYw, Write(op))
#define ZeroWrite(op) Program(Zero, Write(op))
#define ZeroXWrite(op) Program(ZeroX, Write(op))
#define ZeroYWrite(op) Program(ZeroY, Write(op))
#define ZeroIndirectWrite(op) Program(ZeroIndirect, Write(op))
#define IndexedIndirectWrite(op) Program(IndexedIndirect, Write(op))
#define IndirectIndexedWrite(op) Program(IndirectIndexed, Write(op))
#define IndirectIndexedWrite(op) Program(IndirectIndexedw, Write(op))
#define AbsoluteReadModifyWrite(...) Program(Absolute, ReadModifyWrite(__VA_ARGS__))
#define AbsoluteXReadModifyWrite(...) Program(AbsoluteX, ReadModifyWrite(__VA_ARGS__))
#define AbsoluteYReadModifyWrite(...) Program(AbsoluteY, ReadModifyWrite(__VA_ARGS__))
#define AbsoluteXReadModifyWrite(...) Program(AbsoluteXw, ReadModifyWrite(__VA_ARGS__))
#define AbsoluteYReadModifyWrite(...) Program(AbsoluteYw, ReadModifyWrite(__VA_ARGS__))
#define ZeroReadModifyWrite(...) Program(Zero, ReadModifyWrite(__VA_ARGS__))
#define ZeroXReadModifyWrite(...) Program(ZeroX, ReadModifyWrite(__VA_ARGS__))
#define ZeroYReadModifyWrite(...) Program(ZeroY, ReadModifyWrite(__VA_ARGS__))
#define IndexedIndirectReadModifyWrite(...) Program(IndexedIndirect, ReadModifyWrite(__VA_ARGS__))
#define IndirectIndexedReadModifyWrite(...) Program(IndirectIndexed, ReadModifyWrite(__VA_ARGS__))
#define IndirectIndexedReadModifyWrite(...) Program(IndirectIndexedw, ReadModifyWrite(__VA_ARGS__))
#define FastAbsoluteXReadModifyWrite(...) Program(AbsoluteXr, ReadModifyWrite(__VA_ARGS__))
#define FastAbsoluteYReadModifyWrite(...) Program(AbsoluteYr, ReadModifyWrite(__VA_ARGS__))
@@ -68,8 +68,8 @@ using namespace CPU::MOS6502;
#define ZeroNop() Program(Zero, CycleFetchOperandFromAddress)
#define ZeroXNop() Program(ZeroX, CycleFetchOperandFromAddress)
#define AbsoluteNop() Program(Absolute)
#define AbsoluteXNop() Program(AbsoluteX)
#define AbsoluteNop() Program(Absolute, CycleFetchOperandFromAddress)
#define AbsoluteXNop() Program(AbsoluteXr, CycleFetchOperandFromAddress)
#define ImpliedNop() {OperationMoveToNextProgram}
#define ImmediateNop() Program(OperationIncrementPC)

View File

@@ -9,6 +9,7 @@
#ifndef _502Selector_h
#define _502Selector_h
#include "6502Esque.hpp"
#include "../6502/6502.hpp"
#include "../65816/65816.hpp"
@@ -45,6 +46,32 @@ template <typename BusHandler, bool uses_ready_line> class Processor<Type::TWDC6
template <Type processor_type> class BusHandlerT: public BusHandler<uint16_t> {};
template <> class BusHandlerT<Type::TWDC65816>: public BusHandler<uint32_t> {};
/*
Query for implemented registers.
*/
constexpr bool has(Type processor_type, Register r) {
switch(r) {
case Register::LastOperationAddress:
case Register::ProgramCounter:
case Register::StackPointer:
case Register::Flags:
case Register::A:
case Register::X:
case Register::Y:
return true;
case Register::EmulationFlag:
case Register::DataBank:
case Register::ProgramBank:
case Register::Direct:
return processor_type == Type::TWDC65816;
}
}
constexpr bool has_extended_bus_output(Type processor_type) {
return processor_type == Type::TWDC65816;
}
}
#endif /* _502Selector_h */

View File

@@ -30,7 +30,10 @@ uint16_t ProcessorBase::value_of(Register r) const {
void ProcessorBase::set_value_of(Register r, uint16_t value) {
switch (r) {
case Register::ProgramCounter: registers_.pc = value; break;
case Register::StackPointer: registers_.s.full = value; break;
case Register::StackPointer:
registers_.s.full = value;
if(registers_.emulation_flag) registers_.s.halves.high = 1;
break;
case Register::Flags: set_flags(uint8_t(value)); break;
case Register::A: registers_.a.full = value; break;
case Register::X: registers_.x.full = value & registers_.x_mask; break;

View File

@@ -67,6 +67,9 @@ template <typename BusHandler, bool uses_ready_line> void Processor<BusHandler,
last_operation_pc_ = registers_.pc;
last_operation_program_bank_ = uint8_t(registers_.program_bank >> 16);
memory_lock_ = false;
// Reenforce the top byte of S if applicable.
registers_.s.full = stack_address();
} continue;
case OperationDecode: {
@@ -619,7 +622,7 @@ template <typename BusHandler, bool uses_ready_line> void Processor<BusHandler,
// (and make reasonable guesses as to the N flag).
case TXS:
registers_.s = registers_.x.full;
registers_.s = registers_.x;
break;
case TSX:
@@ -668,10 +671,8 @@ template <typename BusHandler, bool uses_ready_line> void Processor<BusHandler,
break;
case TCS:
registers_.s.full = registers_.a.full;
// No need to worry about byte masking here;
// for the stack it's handled as the emulation runs.
// Cf. the stack_address() macro.
registers_.s = registers_.a;
registers_.s.full = stack_address();
break;
case TSC:

View File

@@ -1131,11 +1131,10 @@ void ProcessorStorage::set_emulation_mode(bool enabled) {
set_m_x_flags(true, true);
registers_.e_masks[0] = 0xff00;
registers_.e_masks[1] = 0x00ff;
registers_.s.halves.high = 1;
} else {
registers_.e_masks[0] = 0x0000;
registers_.e_masks[1] = 0xffff;
registers_.s.halves.high = 1; // To pretend it was 1 all along; this implementation actually ignores
// the top byte while in emulation mode.
}
}