mirror of
https://github.com/TomHarte/CLK.git
synced 2025-09-12 02:24:31 +00:00
Compare commits
67 Commits
SeparateFe
...
2023-09-10
Author | SHA1 | Date | |
---|---|---|---|
|
49ec6d19a5 | ||
|
8efb6a9226 | ||
|
211a6e5114 | ||
|
e42a2578da | ||
|
34c631ed3b | ||
|
2f5d710441 | ||
|
b7a27fbc6b | ||
|
e98f78316b | ||
|
8c3ebe23f6 | ||
|
251b8e69ad | ||
|
a21fe92b7a | ||
|
3d5a351306 | ||
|
43dfb729d3 | ||
|
ecec9ff6dc | ||
|
543be49cf8 | ||
|
4f0adc4d5d | ||
|
1fb278c9f1 | ||
|
19ec63b4fb | ||
|
4d6ffa7a2e | ||
|
39ee75d94a | ||
|
13be247495 | ||
|
cdcac7c11c | ||
|
67cd5dd63b | ||
|
139a1a2acc | ||
|
7b569b1a6c | ||
|
3e666a08ae | ||
|
74b416f985 | ||
|
c160482b0a | ||
|
ec8f1b0fe0 | ||
|
5dae726857 | ||
|
598a889c6d | ||
|
e5d3140cd1 | ||
|
525e5ce8b0 | ||
|
79e9de34b6 | ||
|
0a547355db | ||
|
2b58f64161 | ||
|
6cbd152ff5 | ||
|
a5038259bc | ||
|
bb84a5a474 | ||
|
b5dc84c431 | ||
|
357a324e87 | ||
|
fa82fb46b9 | ||
|
b8e7c2b8ac | ||
|
3e2a82b638 | ||
|
1125286b96 | ||
|
17f1f05064 | ||
|
ae56da2b0d | ||
|
90f16026bc | ||
|
d0284917cf | ||
|
7815d18676 | ||
|
222f6e92fb | ||
|
b34403164e | ||
|
3bd931937f | ||
|
d207c13b6b | ||
|
ca75822dbe | ||
|
d9df568dab | ||
|
26343148ae | ||
|
fd0fe66851 | ||
|
c41ed191dc | ||
|
833613b68a | ||
|
0a336baae2 | ||
|
b9bd3f9b8c | ||
|
42024c1573 | ||
|
0222dcf5ce | ||
|
54103f1f34 | ||
|
c0eb401d04 | ||
|
ad5047dbd5 |
@@ -236,7 +236,7 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
|
|||||||
case Instruction::Relative: {
|
case Instruction::Relative: {
|
||||||
std::size_t operand_address = address_mapper(address);
|
std::size_t operand_address = address_mapper(address);
|
||||||
if(operand_address >= memory.size()) return;
|
if(operand_address >= memory.size()) return;
|
||||||
address++;
|
++address;
|
||||||
|
|
||||||
instruction.operand = memory[operand_address];
|
instruction.operand = memory[operand_address];
|
||||||
}
|
}
|
||||||
@@ -291,20 +291,34 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Decide on overall flow control.
|
// Decide on overall flow control.
|
||||||
if(instruction.operation == Instruction::RTS || instruction.operation == Instruction::RTI) return;
|
|
||||||
if(instruction.operation == Instruction::BRK) return; // TODO: check whether IRQ vector is within memory range
|
// All relative instructions are flow control.
|
||||||
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;
|
|
||||||
}
|
|
||||||
if(instruction.addressing_mode == Instruction::Relative) {
|
if(instruction.addressing_mode == Instruction::Relative) {
|
||||||
uint16_t destination = uint16_t(address + int8_t(instruction.operand));
|
uint16_t destination = uint16_t(address + int8_t(instruction.operand));
|
||||||
disassembly.remaining_entry_points.push_back(destination);
|
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::vector<uint8_t> &memory,
|
||||||
const std::function<std::size_t(uint16_t)> &address_mapper,
|
const std::function<std::size_t(uint16_t)> &address_mapper,
|
||||||
std::vector<uint16_t> entry_points) {
|
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);
|
||||||
}
|
}
|
||||||
|
@@ -14,25 +14,30 @@ namespace Analyser::Static::Disassembly {
|
|||||||
template <typename D, typename S> struct PartialDisassembly {
|
template <typename D, typename S> struct PartialDisassembly {
|
||||||
D disassembly;
|
D disassembly;
|
||||||
std::vector<S> remaining_entry_points;
|
std::vector<S> remaining_entry_points;
|
||||||
|
std::vector<S> implicit_entry_points;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename D, typename S, typename Disassembler> D Disassemble(
|
template <typename D, typename S, typename Disassembler> D Disassemble(
|
||||||
const std::vector<uint8_t> &memory,
|
const std::vector<uint8_t> &memory,
|
||||||
const std::function<std::size_t(S)> &address_mapper,
|
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;
|
PartialDisassembly<D, S> partial_disassembly;
|
||||||
partial_disassembly.remaining_entry_points = entry_points;
|
partial_disassembly.remaining_entry_points = entry_points;
|
||||||
|
|
||||||
while(!partial_disassembly.remaining_entry_points.empty()) {
|
while(!partial_disassembly.remaining_entry_points.empty()) {
|
||||||
// pull the next entry point from the back of the vector
|
// Do a recursive-style disassembly for all current entry points.
|
||||||
S next_entry_point = partial_disassembly.remaining_entry_points.back();
|
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();
|
partial_disassembly.remaining_entry_points.pop_back();
|
||||||
|
|
||||||
// if that address has already been visited, forget about it
|
// If that address has already been visited, forget about it.
|
||||||
if( partial_disassembly.disassembly.instructions_by_address.find(next_entry_point)
|
if( partial_disassembly.disassembly.instructions_by_address.find(next_entry_point)
|
||||||
!= partial_disassembly.disassembly.instructions_by_address.end()) continue;
|
!= partial_disassembly.disassembly.instructions_by_address.end()) continue;
|
||||||
|
|
||||||
// if it's outgoing, log it as such and forget about it; otherwise disassemble
|
// 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);
|
std::size_t mapped_entry_point = address_mapper(next_entry_point);
|
||||||
if(mapped_entry_point >= memory.size())
|
if(mapped_entry_point >= memory.size())
|
||||||
partial_disassembly.disassembly.outward_calls.insert(next_entry_point);
|
partial_disassembly.disassembly.outward_calls.insert(next_entry_point);
|
||||||
@@ -40,6 +45,22 @@ template <typename D, typename S, typename Disassembler> D Disassemble(
|
|||||||
Disassembler::AddToDisassembly(partial_disassembly, memory, address_mapper, next_entry_point);
|
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;
|
return partial_disassembly.disassembly;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -589,7 +589,7 @@ struct Z80Disassembler {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add any (potentially) newly discovered entry point.
|
// Add any (potentially) newly-discovered entry point.
|
||||||
if( instruction.operation == Instruction::Operation::JP ||
|
if( instruction.operation == Instruction::Operation::JP ||
|
||||||
instruction.operation == Instruction::Operation::JR ||
|
instruction.operation == Instruction::Operation::JR ||
|
||||||
instruction.operation == Instruction::Operation::CALL ||
|
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.
|
// 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;
|
case Instruction::Operation::RET:
|
||||||
if(instruction.operation == Instruction::Operation::RETI) return;
|
case Instruction::Operation::RETI:
|
||||||
if(instruction.operation == Instruction::Operation::RETN) return;
|
case Instruction::Operation::RETN:
|
||||||
if(instruction.operation == Instruction::Operation::JP) return;
|
case Instruction::Operation::JP:
|
||||||
if(instruction.operation == Instruction::Operation::JR) return;
|
case Instruction::Operation::JR:
|
||||||
|
if(instruction.condition == Instruction::Condition::None) {
|
||||||
|
disassembly.implicit_entry_points.push_back(accessor.address());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // end of anonymous namespace
|
} // end of anonymous namespace
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Disassembly Analyser::Static::Z80::Disassemble(
|
Disassembly Analyser::Static::Z80::Disassemble(
|
||||||
const std::vector<uint8_t> &memory,
|
const std::vector<uint8_t> &memory,
|
||||||
const std::function<std::size_t(uint16_t)> &address_mapper,
|
const std::function<std::size_t(uint16_t)> &address_mapper,
|
||||||
std::vector<uint16_t> entry_points) {
|
std::vector<uint16_t> entry_points,
|
||||||
return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, Z80Disassembler>(memory, address_mapper, entry_points);
|
Approach approach)
|
||||||
|
{
|
||||||
|
return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, Z80Disassembler>(
|
||||||
|
memory,
|
||||||
|
address_mapper,
|
||||||
|
entry_points,
|
||||||
|
approach == Approach::Exhaustive
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@@ -76,10 +76,19 @@ struct Disassembly {
|
|||||||
std::set<uint16_t> internal_stores, internal_loads, internal_modifies;
|
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(
|
Disassembly Disassemble(
|
||||||
const std::vector<uint8_t> &memory,
|
const std::vector<uint8_t> &memory,
|
||||||
const std::function<std::size_t(uint16_t)> &address_mapper,
|
const std::function<std::size_t(uint16_t)> &address_mapper,
|
||||||
std::vector<uint16_t> entry_points);
|
std::vector<uint16_t> entry_points,
|
||||||
|
Approach approach);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -115,97 +115,16 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
|
|||||||
// be at play; disassemble to try to figure it out.
|
// be at play; disassemble to try to figure it out.
|
||||||
std::vector<uint8_t> first_8k;
|
std::vector<uint8_t> first_8k;
|
||||||
first_8k.insert(first_8k.begin(), segment.data.begin(), segment.data.begin() + 8192);
|
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(
|
Analyser::Static::Z80::Disassemble(
|
||||||
first_8k,
|
first_8k,
|
||||||
Analyser::Static::Disassembler::OffsetMapper(start_address),
|
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;
|
using Instruction = Analyser::Static::Z80::Instruction;
|
||||||
std::map<uint16_t, Instruction> &instructions = disassembly.instructions_by_address;
|
const 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;
|
|
||||||
|
|
||||||
// Look for LD (nnnn), A instructions, and collate those addresses.
|
// Look for LD (nnnn), A instructions, and collate those addresses.
|
||||||
std::map<uint16_t, int> address_counts;
|
std::map<uint16_t, int> address_counts;
|
||||||
@@ -217,49 +136,46 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Weight confidences by number of observed hits.
|
// Weight confidences by number of observed hits; if any is above 60% confidence, just use it.
|
||||||
float total_hits =
|
const auto ascii_8kb_total = address_counts[0x6000] + address_counts[0x6800] + address_counts[0x7000] + address_counts[0x7800];
|
||||||
float(
|
const auto ascii_16kb_total = address_counts[0x6000] + address_counts[0x7000] + address_counts[0x77ff];
|
||||||
address_counts[0x6000] + address_counts[0x6800] +
|
const auto konami_total = address_counts[0x6000] + address_counts[0x8000] + address_counts[0xa000];
|
||||||
address_counts[0x7000] + address_counts[0x7800] +
|
const auto konami_with_scc_total = address_counts[0x5000] + address_counts[0x7000] + address_counts[0x9000] + address_counts[0xb000];
|
||||||
address_counts[0x77ff] + address_counts[0x8000] +
|
|
||||||
address_counts[0xa000] + address_counts[0x5000] +
|
|
||||||
address_counts[0x9000] + address_counts[0xb000]
|
|
||||||
);
|
|
||||||
|
|
||||||
|
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(
|
targets.push_back(CartridgeTarget(
|
||||||
segment,
|
segment,
|
||||||
start_address,
|
start_address,
|
||||||
Analyser::Static::MSX::Cartridge::ASCII8kb,
|
Analyser::Static::MSX::Cartridge::ASCII8kb,
|
||||||
float( address_counts[0x6000] +
|
float(ascii_8kb_total) / float(total_hits)));
|
||||||
address_counts[0x6800] +
|
}
|
||||||
address_counts[0x7000] +
|
if(!is_ascii_8kb && !is_konami && !is_konami_with_scc) {
|
||||||
address_counts[0x7800]) / total_hits));
|
|
||||||
targets.push_back(CartridgeTarget(
|
targets.push_back(CartridgeTarget(
|
||||||
segment,
|
segment,
|
||||||
start_address,
|
start_address,
|
||||||
Analyser::Static::MSX::Cartridge::ASCII16kb,
|
Analyser::Static::MSX::Cartridge::ASCII16kb,
|
||||||
float( address_counts[0x6000] +
|
float(ascii_16kb_total) / float(total_hits)));
|
||||||
address_counts[0x7000] +
|
}
|
||||||
address_counts[0x77ff]) / total_hits));
|
if(!is_ascii_8kb && !is_ascii_16kb && !is_konami_with_scc) {
|
||||||
if(!is_ascii) {
|
|
||||||
targets.push_back(CartridgeTarget(
|
targets.push_back(CartridgeTarget(
|
||||||
segment,
|
segment,
|
||||||
start_address,
|
start_address,
|
||||||
Analyser::Static::MSX::Cartridge::Konami,
|
Analyser::Static::MSX::Cartridge::Konami,
|
||||||
float( address_counts[0x6000] +
|
float(konami_total) / float(total_hits)));
|
||||||
address_counts[0x8000] +
|
|
||||||
address_counts[0xa000]) / total_hits));
|
|
||||||
}
|
}
|
||||||
if(!is_ascii) {
|
if(!is_ascii_8kb && !is_ascii_16kb && !is_konami) {
|
||||||
targets.push_back(CartridgeTarget(
|
targets.push_back(CartridgeTarget(
|
||||||
segment,
|
segment,
|
||||||
start_address,
|
start_address,
|
||||||
Analyser::Static::MSX::Cartridge::KonamiWithSCC,
|
Analyser::Static::MSX::Cartridge::KonamiWithSCC,
|
||||||
float( address_counts[0x5000] +
|
float(konami_with_scc_total) / float(total_hits)));
|
||||||
address_counts[0x7000] +
|
|
||||||
address_counts[0x9000] +
|
|
||||||
address_counts[0xb000]) / total_hits));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -26,7 +26,7 @@
|
|||||||
Machines that accumulate HalfCycle time but supply to a Cycle-counted device may supply a
|
Machines that accumulate HalfCycle time but supply to a Cycle-counted device may supply a
|
||||||
separate @c TargetTimeScale at template declaration.
|
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.
|
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
|
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:
|
private:
|
||||||
/*!
|
/*!
|
||||||
A std::unique_ptr deleter which causes an update_sequence_point to occur on the actor supplied
|
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.**
|
**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
|
// going to be applied then do a direct max -> max translation rather than
|
||||||
// allowing the arithmetic to overflow.
|
// allowing the arithmetic to overflow.
|
||||||
if constexpr (divider == 1 && std::is_same_v<LocalTimeScale, TargetTimeScale>) {
|
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 {
|
} else {
|
||||||
const auto time = object_.get_next_sequence_point();
|
const auto time = object_.next_sequence_point();
|
||||||
if(time == TargetTimeScale::max()) {
|
if(time == TargetTimeScale::max()) {
|
||||||
time_until_event_ = LocalTimeScale::max();
|
time_until_event_ = LocalTimeScale::max();
|
||||||
} else {
|
} else {
|
||||||
@@ -272,7 +272,7 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
|
|||||||
bool did_flush_ = false;
|
bool did_flush_ = false;
|
||||||
|
|
||||||
template <typename S, typename = void> struct has_sequence_points : std::false_type {};
|
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;
|
ClockingHint::Preference clocking_preference_ = ClockingHint::Preference::JustInTime;
|
||||||
void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference clocking) {
|
void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference clocking) {
|
||||||
|
@@ -207,7 +207,7 @@ void MFP68901::run_for(HalfCycles time) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HalfCycles MFP68901::get_next_sequence_point() {
|
HalfCycles MFP68901::next_sequence_point() {
|
||||||
return HalfCycles::max();
|
return HalfCycles::max();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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.
|
/// so that mechanism can also be used to reduce the quantity of calls into this class.
|
||||||
///
|
///
|
||||||
/// @discussion TODO, alas.
|
/// @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.
|
/// 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);
|
void set_timer_event_input(int channel, bool value);
|
||||||
|
@@ -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
|
If get_interrupt_line is true now of if get_interrupt_line would
|
||||||
never return true, returns HalfCycles::max().
|
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
|
Returns the amount of time until the nominated line interrupt position is
|
||||||
|
@@ -1268,7 +1268,7 @@ uint8_t TMS9918<personality>::get_current_line() const {
|
|||||||
return uint8_t(source_row);
|
return uint8_t(source_row);
|
||||||
}
|
}
|
||||||
template <Personality personality>
|
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(!this->generate_interrupts_ && !this->enable_line_interrupts_) return HalfCycles::max();
|
||||||
if(get_interrupt_line()) return HalfCycles::max();
|
if(get_interrupt_line()) return HalfCycles::max();
|
||||||
|
|
||||||
|
@@ -68,11 +68,9 @@ void Executor::set_interrupt_line(bool line) {
|
|||||||
// is active, amongst other things.
|
// is active, amongst other things.
|
||||||
if(interrupt_line_ != line) {
|
if(interrupt_line_ != line) {
|
||||||
interrupt_line_ = line;
|
interrupt_line_ = line;
|
||||||
|
if(line) {
|
||||||
// TODO: verify interrupt connection. Externally, but stubbed off here.
|
set_interrupt_request(interrupt_control_, 0x02, 0x1ff4);
|
||||||
// if(!interrupt_disable_ && line) {
|
}
|
||||||
// perform_interrupt<false>(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)));
|
set_program_counter(uint16_t(memory_[vector] | (memory_[vector+1] << 8)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Executor::set_interrupt_request(uint8_t ®, 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() {
|
template <Operation operation, AddressingMode addressing_mode> void Executor::perform() {
|
||||||
// Post cycle cost; this emulation _does not provide accurate timing_.
|
// 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;
|
#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.
|
// this additional divide by 4 produces the correct net divide by 16.
|
||||||
|
|
||||||
timer_divider_ += duration;
|
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);
|
timer_divider_ &= (t12_divider-1);
|
||||||
|
|
||||||
// Update timers 1 and 2. TODO: interrupts (elsewhere?).
|
// Update timers 1 and 2.
|
||||||
if(update_timer(timers_[0], t12_ticks)) interrupt_control_ |= 0x20;
|
const int t12_ticks = update_timer(prescalers_[0], timer_divider_ / t12_divider);
|
||||||
if(update_timer(timers_[1], t12_ticks)) interrupt_control_ |= 0x08;
|
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 X is disabled, stop.
|
||||||
if(timer_control_&0x20) {
|
if(timer_control_&0x20) {
|
||||||
@@ -804,9 +817,10 @@ inline void Executor::subtract_duration(int duration) {
|
|||||||
// Update timer X prescaler.
|
// Update timer X prescaler.
|
||||||
switch(timer_control_ & 0x0c) {
|
switch(timer_control_ & 0x0c) {
|
||||||
default: {
|
default: {
|
||||||
const int tx_ticks = update_timer(prescalers_[1], t12_ticks); // TODO: don't hard code this. And is this even right?
|
const int tx_ticks = update_timer(prescalers_[1], clock_ticks);
|
||||||
if(update_timer(timers_[2], tx_ticks))
|
if(update_timer(timers_[2], tx_ticks)) {
|
||||||
timer_control_ |= 0x80; // TODO: interrupt result of this.
|
set_interrupt_request(timer_control_, 0x80, 0x1ffa);
|
||||||
|
}
|
||||||
} break;
|
} break;
|
||||||
case 0x04:
|
case 0x04:
|
||||||
LOG("TODO: Timer X; Pulse output mode");
|
LOG("TODO: Timer X; Pulse output mode");
|
||||||
|
@@ -165,6 +165,8 @@ class Executor: public CachingExecutor {
|
|||||||
template<bool is_brk> inline void perform_interrupt(uint16_t vector);
|
template<bool is_brk> inline void perform_interrupt(uint16_t vector);
|
||||||
inline void set_port_output(int port);
|
inline void set_port_output(int port);
|
||||||
|
|
||||||
|
void set_interrupt_request(uint8_t ®, uint8_t value, uint16_t vector);
|
||||||
|
|
||||||
// MARK: - Execution time
|
// MARK: - Execution time
|
||||||
|
|
||||||
Cycles cycles_;
|
Cycles cycles_;
|
||||||
|
@@ -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) {
|
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_);
|
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));
|
pending_events_.push_back(uint8_t(key) | (is_pressed ? 0x00 : 0x80));
|
||||||
pressed_keys_[size_t(key)] = is_pressed;
|
pressed_keys_[size_t(key)] = is_pressed;
|
||||||
|
|
||||||
|
@@ -106,7 +106,7 @@ class Keyboard: public ReactiveDevice {
|
|||||||
void did_receive_data(const Command &, const std::vector<uint8_t> &) override;
|
void did_receive_data(const Command &, const std::vector<uint8_t> &) override;
|
||||||
|
|
||||||
std::mutex keys_mutex_;
|
std::mutex keys_mutex_;
|
||||||
std::array<bool, 128> pressed_keys_;
|
std::array<bool, 128> pressed_keys_{};
|
||||||
std::vector<uint8_t> pending_events_;
|
std::vector<uint8_t> pending_events_;
|
||||||
uint16_t modifiers_ = 0xffff;
|
uint16_t modifiers_ = 0xffff;
|
||||||
};
|
};
|
||||||
|
@@ -15,24 +15,36 @@ using namespace Apple::ADB;
|
|||||||
Mouse::Mouse(Bus &bus) : ReactiveDevice(bus, 3) {}
|
Mouse::Mouse(Bus &bus) : ReactiveDevice(bus, 3) {}
|
||||||
|
|
||||||
void Mouse::perform_command(const Command &command) {
|
void Mouse::perform_command(const Command &command) {
|
||||||
if(command.type == Command::Type::Talk && command.reg == 0) {
|
// Mouse deltas are confined to a seven-bit signed field; this implementation keeps things symmetrical by
|
||||||
// Read current deltas and buttons, thread safely.
|
// limiting them to a maximum absolute value of 63 in any direction.
|
||||||
auto delta_x = delta_x_.exchange(0);
|
static constexpr int16_t max_delta = 63;
|
||||||
auto delta_y = delta_y_.exchange(0);
|
|
||||||
const int buttons = button_flags_;
|
|
||||||
|
|
||||||
// Clamp deltas.
|
if(command.type == Command::Type::Talk && command.reg == 0) {
|
||||||
delta_x = std::clamp(delta_x, int16_t(-128), int16_t(127));
|
// Read and clamp current deltas and buttons.
|
||||||
delta_y = std::clamp(delta_y, int16_t(-128), int16_t(127));
|
//
|
||||||
|
// 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
|
// 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 =
|
const uint16_t reg0 =
|
||||||
((buttons & 1) ? 0x0000 : 0x8000) |
|
((buttons & 1) ? 0x0000 : 0x8000) |
|
||||||
((buttons & 2) ? 0x0000 : 0x0080) |
|
((buttons & 2) ? 0x0000 : 0x0080) |
|
||||||
uint16_t(delta_x & 0x7f) |
|
uint16_t(delta_x & 0x7f) |
|
||||||
uint16_t((delta_y & 0x7f) << 8);
|
uint16_t((delta_y & 0x7f) << 8);
|
||||||
if(reg0 == last_posted_reg0_) return;
|
if(!(reg0 & 0x7f7f) && (reg0 & 0x8080) == (last_posted_reg0_ & 0x8080)) return;
|
||||||
|
|
||||||
// Post change.
|
// Post change.
|
||||||
last_posted_reg0_ = reg0;
|
last_posted_reg0_ = reg0;
|
||||||
|
@@ -76,11 +76,15 @@ uint8_t GLU::get_mouse_data() {
|
|||||||
// b5–b0: mouse delta.
|
// b5–b0: mouse delta.
|
||||||
|
|
||||||
const uint8_t result = registers_[visible_mouse_register_];
|
const uint8_t result = registers_[visible_mouse_register_];
|
||||||
if(visible_mouse_register_ == 2) {
|
if(visible_mouse_register_ == 3) {
|
||||||
++visible_mouse_register_;
|
|
||||||
} else {
|
|
||||||
status_ &= ~uint8_t(CPUFlags::MouseDataFull);
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,7 +120,7 @@ uint8_t GLU::get_status() {
|
|||||||
// b2: 1 = keyboard data interrupt is enabled.
|
// b2: 1 = keyboard data interrupt is enabled.
|
||||||
// b1: 1 = mouse x-data is available; 0 = y.
|
// 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).
|
// 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) {
|
void GLU::set_status(uint8_t status) {
|
||||||
@@ -223,7 +227,6 @@ void GLU::set_port_output(int port, uint8_t value) {
|
|||||||
case 3:
|
case 3:
|
||||||
status_ |= uint8_t(CPUFlags::MouseDataFull);
|
status_ |= uint8_t(CPUFlags::MouseDataFull);
|
||||||
visible_mouse_register_ = 2;
|
visible_mouse_register_ = 2;
|
||||||
printf("Mouse: %d <- %02x\n", register_address_, register_latch_);
|
|
||||||
break;
|
break;
|
||||||
case 7: status_ |= uint8_t(CPUFlags::CommandDataIsValid); 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) {
|
void GLU::set_vertical_blank(bool is_blank) {
|
||||||
|
vertical_blank_ = is_blank;
|
||||||
executor_.set_interrupt_line(is_blank);
|
executor_.set_interrupt_line(is_blank);
|
||||||
}
|
}
|
||||||
|
@@ -752,7 +752,7 @@ class ConcreteMachine:
|
|||||||
is_1Mhz = true;
|
is_1Mhz = true;
|
||||||
break;
|
break;
|
||||||
case Read(0xc045):
|
case Read(0xc045):
|
||||||
// MMDELTAX byte.
|
// MMDELTAY byte.
|
||||||
*value = 0;
|
*value = 0;
|
||||||
is_1Mhz = true;
|
is_1Mhz = true;
|
||||||
break;
|
break;
|
||||||
@@ -1171,7 +1171,7 @@ class ConcreteMachine:
|
|||||||
machine_->update_audio();
|
machine_->update_audio();
|
||||||
}
|
}
|
||||||
~AudioUpdater() {
|
~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:
|
private:
|
||||||
ConcreteMachine *machine_;
|
ConcreteMachine *machine_;
|
||||||
|
@@ -218,7 +218,7 @@ uint8_t GLU::get_address_high() {
|
|||||||
|
|
||||||
// MARK: - Update logic.
|
// 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();
|
uint32_t result = std::numeric_limits<decltype(result)>::max();
|
||||||
|
|
||||||
for(int c = 0; c < local_.oscillator_count; c++) {
|
for(int c = 0; c < local_.oscillator_count; c++) {
|
||||||
|
@@ -31,7 +31,7 @@ class GLU: public Outputs::Speaker::SampleSource {
|
|||||||
uint8_t get_address_high();
|
uint8_t get_address_high();
|
||||||
|
|
||||||
void run_for(Cycles);
|
void run_for(Cycles);
|
||||||
Cycles get_next_sequence_point() const;
|
Cycles next_sequence_point() const;
|
||||||
bool get_interrupt_line();
|
bool get_interrupt_line();
|
||||||
|
|
||||||
// SampleSource.
|
// SampleSource.
|
||||||
|
@@ -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 cycles_into_row = cycles_into_frame_ % CyclesPerLine;
|
||||||
const int 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.
|
// Post an interrupt if requested.
|
||||||
if(line_control_ & 0x40) {
|
if(line_control_ & 0x40) {
|
||||||
set_interrupts(0x20);
|
interrupts_.add(0x20);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up appropriately for fill mode (or not).
|
// 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) {
|
void Video::clear_interrupts(uint8_t mask) {
|
||||||
set_interrupts(interrupts_ & ~(mask & 0x60));
|
interrupts_.clear(mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Video::set_interrupt_register(uint8_t mask) {
|
void Video::set_interrupt_register(uint8_t mask) {
|
||||||
set_interrupts(interrupts_ | (mask & 0x6));
|
interrupts_.set_control(mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t Video::get_interrupt_register() {
|
uint8_t Video::get_interrupt_register() {
|
||||||
return interrupts_;
|
return interrupts_.status();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Video::get_interrupt_line() {
|
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) {
|
void Video::set_megaii_interrupts_enabled(uint8_t mask) {
|
||||||
@@ -526,13 +526,7 @@ void Video::clear_megaii_interrupts() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Video::notify_clock_tick() {
|
void Video::notify_clock_tick() {
|
||||||
set_interrupts(interrupts_ | 0x40);
|
interrupts_.add(0x40);
|
||||||
}
|
|
||||||
|
|
||||||
void Video::set_interrupts(uint8_t new_value) {
|
|
||||||
interrupts_ = new_value & 0x7f;
|
|
||||||
if((interrupts_ >> 4) & interrupts_ & 0x6)
|
|
||||||
interrupts_ |= 0x80;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Video::set_border_colour(uint8_t colour) {
|
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],
|
ram_[row_address + c],
|
||||||
};
|
};
|
||||||
|
|
||||||
target[0] = colours[(source[1] >> 0) & 0x1];
|
target[0] = colours[(source[0] >> 0) & 0x1];
|
||||||
target[1] = colours[(source[1] >> 1) & 0x1];
|
target[1] = colours[(source[0] >> 1) & 0x1];
|
||||||
target[2] = colours[(source[1] >> 2) & 0x1];
|
target[2] = colours[(source[0] >> 2) & 0x1];
|
||||||
target[3] = colours[(source[1] >> 3) & 0x1];
|
target[3] = colours[(source[0] >> 3) & 0x1];
|
||||||
target[4] = colours[(source[1] >> 4) & 0x1];
|
target[4] = colours[(source[0] >> 4) & 0x1];
|
||||||
target[5] = colours[(source[1] >> 5) & 0x1];
|
target[5] = colours[(source[0] >> 5) & 0x1];
|
||||||
target[6] = colours[(source[1] >> 6) & 0x1];
|
target[6] = colours[(source[0] >> 6) & 0x1];
|
||||||
|
|
||||||
target[7] = colours[(source[0] >> 0) & 0x1];
|
target[7] = colours[(source[1] >> 0) & 0x1];
|
||||||
target[8] = colours[(source[0] >> 1) & 0x1];
|
target[8] = colours[(source[1] >> 1) & 0x1];
|
||||||
target[9] = colours[(source[0] >> 2) & 0x1];
|
target[9] = colours[(source[1] >> 2) & 0x1];
|
||||||
target[10] = colours[(source[0] >> 3) & 0x1];
|
target[10] = colours[(source[1] >> 3) & 0x1];
|
||||||
target[11] = colours[(source[0] >> 4) & 0x1];
|
target[11] = colours[(source[1] >> 4) & 0x1];
|
||||||
target[12] = colours[(source[0] >> 5) & 0x1];
|
target[12] = colours[(source[1] >> 5) & 0x1];
|
||||||
target[13] = colours[(source[0] >> 6) & 0x1];
|
target[13] = colours[(source[1] >> 6) & 0x1];
|
||||||
|
|
||||||
target += 14;
|
target += 14;
|
||||||
}
|
}
|
||||||
|
@@ -60,7 +60,7 @@ class Video: public Apple::II::VideoSwitches<Cycles> {
|
|||||||
Outputs::Display::DisplayType get_display_type() const;
|
Outputs::Display::DisplayType get_display_type() const;
|
||||||
|
|
||||||
/// Determines the period until video might autonomously update its interrupt lines.
|
/// 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
|
/// Sets the Mega II interrupt enable state — 1/4-second and VBL interrupts are
|
||||||
/// generated here.
|
/// generated here.
|
||||||
@@ -123,8 +123,56 @@ class Video: public Apple::II::VideoSwitches<Cycles> {
|
|||||||
void advance(Cycles);
|
void advance(Cycles);
|
||||||
|
|
||||||
uint8_t new_video_ = 0x01;
|
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;
|
int cycles_into_frame_ = 0;
|
||||||
const uint8_t *ram_ = nullptr;
|
const uint8_t *ram_ = nullptr;
|
||||||
|
@@ -575,7 +575,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
|||||||
|
|
||||||
video_.run_for(time_until_video_event_);
|
video_.run_for(time_until_video_event_);
|
||||||
time_since_video_update_ -= 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());
|
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() {
|
forceinline void update_video() {
|
||||||
video_.run_for(time_since_video_update_.flush<HalfCycles>());
|
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 {
|
Inputs::Mouse &get_mouse() final {
|
||||||
|
@@ -170,7 +170,7 @@ bool Video::vsync() {
|
|||||||
return line >= 353 && line < 356;
|
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();
|
const auto line = (frame_position_ / line_length).as_integral();
|
||||||
if(line >= 353 && line < 356) {
|
if(line >= 353 && line < 356) {
|
||||||
// Currently in vsync, so get time until start of line 357,
|
// Currently in vsync, so get time until start of line 357,
|
||||||
|
@@ -80,7 +80,7 @@ class Video {
|
|||||||
@returns the amount of time until there is next a transition on the
|
@returns the amount of time until there is next a transition on the
|
||||||
vsync signal.
|
vsync signal.
|
||||||
*/
|
*/
|
||||||
HalfCycles get_next_sequence_point();
|
HalfCycles next_sequence_point();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DeferredAudio &audio_;
|
DeferredAudio &audio_;
|
||||||
|
@@ -395,7 +395,7 @@ bool Video::display_enabled() {
|
|||||||
return public_state_.display_enable;
|
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.
|
// The next sequence point will be whenever display_enabled, vsync or hsync next changes.
|
||||||
|
|
||||||
// Sequence of events within a standard line:
|
// Sequence of events within a standard line:
|
||||||
|
@@ -68,7 +68,7 @@ class Video {
|
|||||||
@returns the number of cycles until there is next a change in the hsync,
|
@returns the number of cycles until there is next a change in the hsync,
|
||||||
vsync or display_enable outputs.
|
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.
|
@returns @c true if the horizontal sync output is currently active; @c false otherwise.
|
||||||
|
@@ -370,7 +370,7 @@ void VideoOutput::setup_base_address() {
|
|||||||
|
|
||||||
// MARK: - Interrupts
|
// MARK: - Interrupts
|
||||||
|
|
||||||
Cycles VideoOutput::get_next_sequence_point() {
|
Cycles VideoOutput::next_sequence_point() {
|
||||||
if(output_position_ < real_time_clock_interrupt_1) {
|
if(output_position_ < real_time_clock_interrupt_1) {
|
||||||
return real_time_clock_interrupt_1 - output_position_;
|
return real_time_clock_interrupt_1 - output_position_;
|
||||||
}
|
}
|
||||||
|
@@ -61,7 +61,7 @@ class VideoOutput {
|
|||||||
|
|
||||||
This result may be mutated by calls to @c write.
|
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().
|
@returns a bit mask of all interrupts that have been triggered since the last call to get_interrupt().
|
||||||
|
@@ -315,7 +315,7 @@ void TimedInterruptSource::run_for(Cycles duration) {
|
|||||||
update_channel(1, rate_ == InterruptRate::ToneGenerator1, cycles.as<int>());
|
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
|
// 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.
|
// to factor that one in when determining the next sequence point for either of those.
|
||||||
switch(rate_) {
|
switch(rate_) {
|
||||||
|
@@ -144,7 +144,7 @@ class TimedInterruptSource {
|
|||||||
|
|
||||||
/// @returns The amount of time from now until the earliest that
|
/// @returns The amount of time from now until the earliest that
|
||||||
/// @c get_new_interrupts() _might_ have new interrupts to report.
|
/// @c get_new_interrupts() _might_ have new interrupts to report.
|
||||||
Cycles get_next_sequence_point() const;
|
Cycles next_sequence_point() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr Cycles clock_rate{250000};
|
static constexpr Cycles clock_rate{250000};
|
||||||
|
@@ -467,7 +467,7 @@ void Nick::set_output_type(OutputType type, bool force_flush) {
|
|||||||
|
|
||||||
// MARK: - Sequence points.
|
// 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
|
constexpr int load_point = 16; // i.e. 16 cycles after the start of the line, the
|
||||||
// interrupt line may change. That is, after the
|
// interrupt line may change. That is, after the
|
||||||
// second byte of the mode line has been read.
|
// second byte of the mode line has been read.
|
||||||
|
@@ -38,7 +38,7 @@ class Nick {
|
|||||||
Outputs::Display::ScanStatus get_scaled_scan_status() const;
|
Outputs::Display::ScanStatus get_scaled_scan_status() const;
|
||||||
|
|
||||||
/// @returns The amount of time until the next potential change in interrupt output.
|
/// @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;
|
@returns The current state of the interrupt line — @c true for active;
|
||||||
|
@@ -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.
|
@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();
|
constexpr auto timings = get_timings();
|
||||||
|
|
||||||
// Is the frame still ahead of this interrupt?
|
// Is the frame still ahead of this interrupt?
|
||||||
|
31
Numeric/Carry.hpp
Normal file
31
Numeric/Carry.hpp
Normal 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 */
|
@@ -1101,6 +1101,7 @@
|
|||||||
/* End PBXCopyFilesBuildPhase section */
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference 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>"; };
|
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>"; };
|
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>"; };
|
42AD552E2A0C4D5000ACE410 /* 68000.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000.hpp; sourceTree = "<group>"; };
|
||||||
@@ -3313,6 +3314,7 @@
|
|||||||
4B66E1A8297719270057ED0F /* NumericCoder.hpp */,
|
4B66E1A8297719270057ED0F /* NumericCoder.hpp */,
|
||||||
4BB5B995281B1D3E00522DA9 /* RegisterSizes.hpp */,
|
4BB5B995281B1D3E00522DA9 /* RegisterSizes.hpp */,
|
||||||
4BFEA2F12682A90200EBF94C /* Sizes.hpp */,
|
4BFEA2F12682A90200EBF94C /* Sizes.hpp */,
|
||||||
|
4281572E2AA0334300E16AA1 /* Carry.hpp */,
|
||||||
);
|
);
|
||||||
name = Numeric;
|
name = Numeric;
|
||||||
path = ../../Numeric;
|
path = ../../Numeric;
|
||||||
@@ -6539,6 +6541,10 @@
|
|||||||
GCC_WARN_UNKNOWN_PRAGMAS = YES;
|
GCC_WARN_UNKNOWN_PRAGMAS = YES;
|
||||||
GCC_WARN_UNUSED_LABEL = YES;
|
GCC_WARN_UNUSED_LABEL = YES;
|
||||||
INFOPLIST_FILE = "Clock Signal/Info.plist";
|
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;
|
MTL_TREAT_WARNINGS_AS_ERRORS = YES;
|
||||||
OTHER_CPLUSPLUSFLAGS = (
|
OTHER_CPLUSPLUSFLAGS = (
|
||||||
"$(OTHER_CFLAGS)",
|
"$(OTHER_CFLAGS)",
|
||||||
@@ -6580,6 +6586,10 @@
|
|||||||
GCC_WARN_UNKNOWN_PRAGMAS = YES;
|
GCC_WARN_UNKNOWN_PRAGMAS = YES;
|
||||||
GCC_WARN_UNUSED_LABEL = YES;
|
GCC_WARN_UNUSED_LABEL = YES;
|
||||||
INFOPLIST_FILE = "Clock Signal/Info.plist";
|
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;
|
MTL_TREAT_WARNINGS_AS_ERRORS = YES;
|
||||||
OTHER_CPLUSPLUSFLAGS = (
|
OTHER_CPLUSPLUSFLAGS = (
|
||||||
"$(OTHER_CFLAGS)",
|
"$(OTHER_CFLAGS)",
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1400"
|
LastUpgradeVersion = "1400"
|
||||||
version = "1.3">
|
version = "1.8">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
buildImplicitDependencies = "YES">
|
buildImplicitDependencies = "YES">
|
||||||
|
@@ -14,20 +14,27 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include "6502Selector.hpp"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
struct StopException {};
|
struct StopException {};
|
||||||
|
|
||||||
struct BusHandler: public CPU::MOS6502Esque::BusHandler<uint32_t> {
|
template <CPU::MOS6502Esque::Type type>
|
||||||
// Use a map to store RAM contents, in order to preserve initialised state.
|
struct BusHandler: public CPU::MOS6502Esque::BusHandlerT<type> {
|
||||||
std::unordered_map<uint32_t, uint8_t> ram;
|
using AddressType = typename CPU::MOS6502Esque::BusHandlerT<type>::AddressType;
|
||||||
std::unordered_map<uint32_t, uint8_t> inventions;
|
|
||||||
|
|
||||||
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.
|
// Record the basics of the operation.
|
||||||
auto &cycle = cycles.emplace_back();
|
auto &cycle = cycles.emplace_back();
|
||||||
cycle.operation = operation;
|
cycle.operation = operation;
|
||||||
|
if constexpr (has_extended_bus_output(type)) {
|
||||||
cycle.extended_bus = processor.get_extended_bus_output();
|
cycle.extended_bus = processor.get_extended_bus_output();
|
||||||
|
}
|
||||||
|
|
||||||
// Perform the operation, and fill in the cycle's value.
|
// Perform the operation, and fill in the cycle's value.
|
||||||
using BusOperation = CPU::MOS6502Esque::BusOperation;
|
using BusOperation = CPU::MOS6502Esque::BusOperation;
|
||||||
@@ -92,25 +99,27 @@ struct BusHandler: public CPU::MOS6502Esque::BusHandler<uint32_t> {
|
|||||||
allow_pc_repetition = opcode == 0x54 || opcode == 0x44;
|
allow_pc_repetition = opcode == 0x54 || opcode == 0x44;
|
||||||
|
|
||||||
using Register = CPU::MOS6502Esque::Register;
|
using Register = CPU::MOS6502Esque::Register;
|
||||||
const uint32_t pc =
|
const auto pc =
|
||||||
|
AddressType(
|
||||||
processor.value_of(Register::ProgramCounter) |
|
processor.value_of(Register::ProgramCounter) |
|
||||||
(processor.value_of(Register::ProgramBank) << 16);
|
(processor.value_of(Register::ProgramBank) << 16)
|
||||||
|
);
|
||||||
inventions[pc] = ram[pc] = opcode;
|
inventions[pc] = ram[pc] = opcode;
|
||||||
}
|
}
|
||||||
|
|
||||||
int pc_overshoot = 0;
|
int pc_overshoot = 0;
|
||||||
std::optional<uint32_t> initial_pc;
|
std::optional<AddressType> initial_pc;
|
||||||
bool allow_pc_repetition = false;
|
bool allow_pc_repetition = false;
|
||||||
|
|
||||||
struct Cycle {
|
struct Cycle {
|
||||||
CPU::MOS6502Esque::BusOperation operation;
|
CPU::MOS6502Esque::BusOperation operation;
|
||||||
std::optional<uint32_t> address;
|
std::optional<AddressType> address;
|
||||||
std::optional<uint8_t> value;
|
std::optional<uint8_t> value;
|
||||||
int extended_bus;
|
int extended_bus = 0;
|
||||||
};
|
};
|
||||||
std::vector<Cycle> cycles;
|
std::vector<Cycle> cycles;
|
||||||
|
|
||||||
CPU::WDC65816::Processor<BusHandler, false> processor;
|
CPU::MOS6502Esque::Processor<type, BusHandler<type>, false> processor;
|
||||||
|
|
||||||
BusHandler() : processor(*this) {
|
BusHandler() : processor(*this) {
|
||||||
// Never run the official reset procedure.
|
// 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;
|
using Register = CPU::MOS6502Esque::Register;
|
||||||
fprintf(file, "\"pc\": %d, ", (processor.value_of(Register::ProgramCounter) + pc_offset) & 65535);
|
fprintf(file, "\"pc\": %d, ", (processor.value_of(Register::ProgramCounter) + pc_offset) & 65535);
|
||||||
fprintf(file, "\"s\": %d, ", processor.value_of(Register::StackPointer));
|
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, "\"a\": %d, ", processor.value_of(Register::A));
|
||||||
fprintf(file, "\"x\": %d, ", processor.value_of(Register::X));
|
fprintf(file, "\"x\": %d, ", processor.value_of(Register::X));
|
||||||
fprintf(file, "\"y\": %d, ", processor.value_of(Register::Y));
|
fprintf(file, "\"y\": %d, ", processor.value_of(Register::Y));
|
||||||
|
fprintf(file, "\"p\": %d, ", processor.value_of(Register::Flags));
|
||||||
|
if constexpr (has_emulation) {
|
||||||
fprintf(file, "\"dbr\": %d, ", processor.value_of(Register::DataBank));
|
fprintf(file, "\"dbr\": %d, ", processor.value_of(Register::DataBank));
|
||||||
fprintf(file, "\"d\": %d, ", processor.value_of(Register::Direct));
|
fprintf(file, "\"d\": %d, ", processor.value_of(Register::Direct));
|
||||||
fprintf(file, "\"pbr\": %d, ", processor.value_of(Register::ProgramBank));
|
fprintf(file, "\"pbr\": %d, ", processor.value_of(Register::ProgramBank));
|
||||||
fprintf(file, "\"e\": %d, ", processor.value_of(Register::EmulationFlag));
|
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\": [");
|
fprintf(file, "\"ram\": [");
|
||||||
bool is_first = true;
|
bool is_first = true;
|
||||||
for(const auto &pair: data) {
|
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, "]");
|
fprintf(file, "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - New test generator.
|
// MARK: - New test generator.
|
||||||
|
|
||||||
@interface TestGenerator : NSObject
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation TestGenerator
|
template <CPU::MOS6502Esque::Type type> void generate() {
|
||||||
|
BusHandler<type> handler;
|
||||||
- (void)generate {
|
constexpr bool has_emulation = has(type, CPU::MOS6502Esque::Register::EmulationFlag);
|
||||||
BusHandler handler;
|
|
||||||
|
|
||||||
NSString *const tempDir = NSTemporaryDirectory();
|
NSString *const tempDir = NSTemporaryDirectory();
|
||||||
NSLog(@"Outputting to %@", tempDir);
|
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
|
// Make tests repeatable, at least for any given instance of
|
||||||
// the runtime.
|
// 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 bool is_emulated = operation & 256;
|
||||||
const uint8_t opcode = operation & 255;
|
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");
|
FILE *const target = fopen(targetName.UTF8String, "wt");
|
||||||
|
|
||||||
bool is_first_test = true;
|
bool is_first_test = true;
|
||||||
@@ -186,6 +198,8 @@ 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::Y, rand() >> 8);
|
||||||
handler.processor.set_value_of(Register::ProgramCounter, 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::StackPointer, rand() >> 8);
|
||||||
|
|
||||||
|
if(has_emulation) {
|
||||||
handler.processor.set_value_of(Register::DataBank, 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::ProgramBank, rand() >> 8);
|
||||||
handler.processor.set_value_of(Register::Direct, rand() >> 8);
|
handler.processor.set_value_of(Register::Direct, rand() >> 8);
|
||||||
@@ -193,14 +207,19 @@ void print_ram(FILE *file, const std::unordered_map<uint32_t, uint8_t> &data) {
|
|||||||
// ... except for emulation mode, which is a given.
|
// ... except for emulation mode, which is a given.
|
||||||
// And is set last to ensure proper internal state is applied.
|
// And is set last to ensure proper internal state is applied.
|
||||||
handler.processor.set_value_of(Register::EmulationFlag, is_emulated);
|
handler.processor.set_value_of(Register::EmulationFlag, is_emulated);
|
||||||
|
}
|
||||||
|
|
||||||
// Establish the opcode.
|
// Establish the opcode.
|
||||||
handler.setup(opcode);
|
handler.setup(opcode);
|
||||||
|
|
||||||
// Dump initial state.
|
// Dump initial state.
|
||||||
|
if(has_emulation) {
|
||||||
fprintf(target, "{ \"name\": \"%02x %c %d\", ", opcode, is_emulated ? 'e' : 'n', test + 1);
|
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\": {");
|
fprintf(target, "\"initial\": {");
|
||||||
print_registers(target, handler.processor, 0);
|
print_registers<has_emulation>(target, handler.processor, 0);
|
||||||
|
|
||||||
// Run to the second opcode fetch.
|
// Run to the second opcode fetch.
|
||||||
try {
|
try {
|
||||||
@@ -212,7 +231,7 @@ void print_ram(FILE *file, const std::unordered_map<uint32_t, uint8_t> &data) {
|
|||||||
|
|
||||||
// Dump final state.
|
// Dump final state.
|
||||||
fprintf(target, "}, \"final\": {");
|
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);
|
print_ram(target, handler.ram);
|
||||||
fprintf(target, "}, ");
|
fprintf(target, "}, ");
|
||||||
|
|
||||||
@@ -247,12 +266,6 @@ void print_ram(FILE *file, const std::unordered_map<uint32_t, uint8_t> &data) {
|
|||||||
assert(false);
|
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, "[");
|
fprintf(target, "[");
|
||||||
if(cycle.address) {
|
if(cycle.address) {
|
||||||
fprintf(target, "%d, ", *cycle.address);
|
fprintf(target, "%d, ", *cycle.address);
|
||||||
@@ -264,6 +277,14 @@ void print_ram(FILE *file, const std::unordered_map<uint32_t, uint8_t> &data) {
|
|||||||
} else {
|
} else {
|
||||||
fprintf(target, "null, ");
|
fprintf(target, "null, ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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\"]",
|
fprintf(target, "\"%c%c%c%c%c%c%c%c\"]",
|
||||||
vda ? 'd' : '-',
|
vda ? 'd' : '-',
|
||||||
vpa ? 'p' : '-',
|
vpa ? 'p' : '-',
|
||||||
@@ -274,6 +295,13 @@ void print_ram(FILE *file, const std::unordered_map<uint32_t, uint8_t> &data) {
|
|||||||
wait ? '-' : (index_size ? 'x' : '-'),
|
wait ? '-' : (index_size ? 'x' : '-'),
|
||||||
wait ? '-' : (memory_lock ? 'l' : '-')
|
wait ? '-' : (memory_lock ? 'l' : '-')
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
if(read) {
|
||||||
|
fprintf(target, "\"read\"]");
|
||||||
|
} else {
|
||||||
|
fprintf(target, "\"write\"]");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Terminate object.
|
// 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.
|
// 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.
|
// A generator for tests; not intended to be a permanent fixture.
|
||||||
//- (void)testGenerate {
|
//- (void)testGenerate {
|
||||||
// [[[TestGenerator alloc] init] generate];
|
// generate<CPU::MOS6502Esque::Type::TWDC65816>();
|
||||||
//}
|
//}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
@@ -685,7 +685,7 @@ void print_transactions(FILE *target, const std::vector<Transaction> &transactio
|
|||||||
} else {
|
} else {
|
||||||
printf("\nAll failing operations:\n");
|
printf("\nAll failing operations:\n");
|
||||||
for(const auto operation: failing_operations) {
|
for(const auto operation: failing_operations) {
|
||||||
printf("%d,\n", int(operation));
|
printf("%s,\n", InstructionSet::M68k::to_string(operation));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -124,7 +124,7 @@ struct VideoTester {
|
|||||||
display_enable = _video->display_enabled();
|
display_enable = _video->display_enabled();
|
||||||
vsync = _video->vsync();
|
vsync = _video->vsync();
|
||||||
hsync = _video->hsync();
|
hsync = _video->hsync();
|
||||||
next_event = _video->get_next_sequence_point();
|
next_event = _video->next_sequence_point();
|
||||||
} else {
|
} 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(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>());
|
NSAssert(vsync == _video->vsync(), @"Unannounced change in vsync at cycle %zu [%d before next sequence point]", c, next_event.as<int>());
|
||||||
|
@@ -11,10 +11,10 @@ import XCTest
|
|||||||
|
|
||||||
class BCDTest: XCTestCase, CSTestMachineTrapHandler {
|
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 filename = Bundle(for: type(of: self)).path(forResource: "BCDTEST_beeb", ofType: nil) {
|
||||||
if let bcdTest = try? Data(contentsOf: URL(fileURLWithPath: filename)) {
|
if let bcdTest = try? Data(contentsOf: URL(fileURLWithPath: filename)) {
|
||||||
let machine = CSTestMachine6502(processor: .processor6502)
|
let machine = CSTestMachine6502(processor: processor)
|
||||||
machine.trapHandler = self
|
machine.trapHandler = self
|
||||||
|
|
||||||
machine.setData(bcdTest, atAddress: 0x2900)
|
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 = ""
|
private var output: String = ""
|
||||||
func testMachine(_ testMachine: CSTestMachine, didTrapAtAddress address: UInt16) {
|
func testMachine(_ testMachine: CSTestMachine, didTrapAtAddress address: UInt16) {
|
||||||
let machine6502 = testMachine as! CSTestMachine6502
|
let machine6502 = testMachine as! CSTestMachine6502
|
||||||
|
|
||||||
// Only OSWRCH is trapped, so...
|
// Only OSWRCH is trapped, so...
|
||||||
let character = machine6502.value(for: .A)
|
let character = Character(UnicodeScalar(machine6502.value(for: .A))!)
|
||||||
output.append(Character(UnicodeScalar(character)!))
|
if character != "\r" { // The test internally uses \r\n; keep only one of those.
|
||||||
|
output.append(character)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -38,7 +38,7 @@
|
|||||||
int toggles = 0;
|
int toggles = 0;
|
||||||
int interrupts = 0;
|
int interrupts = 0;
|
||||||
uint8_t dividerState = _interruptSource->get_divider_state() & 1;
|
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++) {
|
for(int c = 0; c < 250000 * 5; c++) {
|
||||||
// Advance one cycle. Clock is 500,000 Hz.
|
// Advance one cycle. Clock is 500,000 Hz.
|
||||||
@@ -55,7 +55,7 @@
|
|||||||
const uint8_t newInterrupts = _interruptSource->get_new_interrupts();
|
const uint8_t newInterrupts = _interruptSource->get_new_interrupts();
|
||||||
if(newInterrupts) {
|
if(newInterrupts) {
|
||||||
XCTAssertEqual(nextSequencePoint, 0);
|
XCTAssertEqual(nextSequencePoint, 0);
|
||||||
nextSequencePoint = _interruptSource->get_next_sequence_point().as<int>();
|
nextSequencePoint = _interruptSource->next_sequence_point().as<int>();
|
||||||
|
|
||||||
if(newInterrupts & 0x02) {
|
if(newInterrupts & 0x02) {
|
||||||
++interrupts;
|
++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));
|
XCTAssertEqual(toggles, int(expectedInterruptsPerSecond * 5.0));
|
||||||
|
@@ -46,7 +46,7 @@
|
|||||||
|
|
||||||
- (void)testInterruptPrediction {
|
- (void)testInterruptPrediction {
|
||||||
// Run for the number of cycles implied by the number of lines.
|
// 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();
|
bool last_interrupt_line = _nick->get_interrupt_line();
|
||||||
|
|
||||||
for(int c = 0; c < _totalLines*912; c++) {
|
for(int c = 0; c < _totalLines*912; c++) {
|
||||||
@@ -61,9 +61,9 @@
|
|||||||
last_interrupt_line = interrupt_line;
|
last_interrupt_line = interrupt_line;
|
||||||
|
|
||||||
if(!next_sequence_point) {
|
if(!next_sequence_point) {
|
||||||
next_sequence_point = _nick->get_next_sequence_point().as<int>();
|
next_sequence_point = _nick->next_sequence_point().as<int>();
|
||||||
} else {
|
} 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);
|
XCTAssertEqual(next_sequence_point, expected_next_sequence_point);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -31,14 +31,14 @@
|
|||||||
int c = 5;
|
int c = 5;
|
||||||
bool vsync = _video->vsync();
|
bool vsync = _video->vsync();
|
||||||
while(c--) {
|
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));
|
NSLog(@"Vsync %@ expected for %@ half-cycles", vsync ? @"on" : @"off", @(remaining_time_in_state));
|
||||||
while(remaining_time_in_state--) {
|
while(remaining_time_in_state--) {
|
||||||
XCTAssertEqual(vsync, _video->vsync());
|
XCTAssertEqual(vsync, _video->vsync());
|
||||||
_video->run_for(HalfCycles(1));
|
_video->run_for(HalfCycles(1));
|
||||||
|
|
||||||
if(remaining_time_in_state)
|
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;
|
vsync ^= true;
|
||||||
}
|
}
|
||||||
|
@@ -36,7 +36,7 @@ using VDP = TI::TMS::TMS9918<TI::TMS::Personality::SMSVDP>;
|
|||||||
vdp.write(1, 0x8a);
|
vdp.write(1, 0x8a);
|
||||||
|
|
||||||
// Get time until interrupt.
|
// 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.
|
// Check that an interrupt is now scheduled.
|
||||||
NSAssert(time_until_interrupt != HalfCycles::max().as_integral() - 1, @"No interrupt 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");
|
NSAssert(!vdp.get_interrupt_line(), @"Interrupt wasn't reset by status read");
|
||||||
|
|
||||||
// Check interrupt flag isn't set prior to the reported time.
|
// 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));
|
vdp.run_for(HalfCycles(time_until_interrupt));
|
||||||
NSAssert(!vdp.get_interrupt_line(), @"Interrupt line went active early [2]");
|
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).
|
// Clear the pending interrupt and ask about the next one (i.e. the first one).
|
||||||
vdp.read(1);
|
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.
|
// Check that an interrupt is now scheduled.
|
||||||
NSAssert(time_until_interrupt != HalfCycles::max().as_integral() - 1, @"No interrupt 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...
|
// Now run through an entire frame...
|
||||||
int half_cycles = 262*228*2;
|
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--) {
|
while(half_cycles--) {
|
||||||
// Validate that an interrupt happened if one was expected, and clear anything that's present.
|
// 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);
|
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));
|
vdp.run_for(HalfCycles(1));
|
||||||
|
|
||||||
// Get the time until interrupt.
|
// 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 != 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);
|
NSAssert(time_until_interrupt >= 0, @"Interrupt is scheduled in the past; position %d %d @ %d", c, with_eof, half_cycles);
|
||||||
|
|
||||||
|
@@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
#include "../6502Esque/6502Esque.hpp"
|
#include "../6502Esque/6502Esque.hpp"
|
||||||
#include "../6502Esque/Implementation/LazyFlags.hpp"
|
#include "../6502Esque/Implementation/LazyFlags.hpp"
|
||||||
|
#include "../../Numeric/Carry.hpp"
|
||||||
#include "../../Numeric/RegisterSizes.hpp"
|
#include "../../Numeric/RegisterSizes.hpp"
|
||||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||||
|
|
||||||
@@ -124,6 +125,12 @@ class ProcessorBase: public ProcessorStorage {
|
|||||||
@returns @c true if the 6502 is jammed; @c false otherwise.
|
@returns @c true if the 6502 is jammed; @c false otherwise.
|
||||||
*/
|
*/
|
||||||
inline bool is_jammed() const;
|
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();
|
||||||
};
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@@ -282,55 +282,103 @@ template <Personality personality, typename T, bool uses_ready_line> void Proces
|
|||||||
// MARK: - ADC/SBC (and INS)
|
// MARK: - ADC/SBC (and INS)
|
||||||
|
|
||||||
case OperationINS:
|
case OperationINS:
|
||||||
operand_++;
|
++operand_;
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
case OperationSBC:
|
case OperationSBC:
|
||||||
|
operand_ = ~operand_;
|
||||||
|
|
||||||
if(flags_.decimal && has_decimal_mode(personality)) {
|
if(flags_.decimal && has_decimal_mode(personality)) {
|
||||||
const uint16_t notCarry = flags_.carry ^ 0x1;
|
uint8_t result = a_ + operand_ + flags_.carry;
|
||||||
const uint16_t decimalResult = uint16_t(a_) - uint16_t(operand_) - notCarry;
|
|
||||||
uint16_t temp16;
|
|
||||||
|
|
||||||
temp16 = (a_&0xf) - (operand_&0xf) - notCarry;
|
// All flags are set based only on the decimal result.
|
||||||
if(temp16 > 0xf) temp16 -= 0x6;
|
flags_.zero_result = result;
|
||||||
temp16 = (temp16&0x0f) | ((temp16 > 0x0f) ? 0xfff0 : 0x00);
|
flags_.carry = Numeric::carried_out<7>(a_, operand_, result);
|
||||||
temp16 += (a_&0xf0) - (operand_&0xf0);
|
flags_.negative_result = result;
|
||||||
|
flags_.overflow = (( (result ^ a_) & (result ^ operand_) ) & 0x80) >> 1;
|
||||||
|
|
||||||
flags_.overflow = ( ( (decimalResult^a_)&(~decimalResult^operand_) )&0x80) >> 1;
|
// General SBC logic:
|
||||||
flags_.negative_result = uint8_t(temp16);
|
//
|
||||||
flags_.zero_result = uint8_t(decimalResult);
|
// 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;
|
// The top nibble is adjusted only if there was borrow out of the whole byte.
|
||||||
a_ = uint8_t(temp16);
|
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_);
|
flags_.set_nz(a_);
|
||||||
read_mem(operand_, address_.full);
|
read_mem(operation_, address_.full);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
} else {
|
|
||||||
operand_ = ~operand_;
|
|
||||||
}
|
}
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
|
|
||||||
case OperationADC:
|
case OperationADC:
|
||||||
if(flags_.decimal && has_decimal_mode(personality)) {
|
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;
|
// General ADC logic:
|
||||||
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);
|
// Detecting decimal carry means finding occasions when two digits added together totalled
|
||||||
flags_.negative_result = uint8_t(result);
|
// 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.
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
flags_.overflow = (( (result ^ a_) & (result ^ operand_) ) & 0x80) >> 1;
|
||||||
if(result >= 0xa0) result += 0x60;
|
|
||||||
|
|
||||||
flags_.carry = (result >> 8) ? 1 : 0;
|
// i.e. fix high nibble if there was carry out of bit 7 already, or if the
|
||||||
a_ = uint8_t(result);
|
// top nibble is too large (in which case there will be carry after the fix-up).
|
||||||
flags_.zero_result = uint8_t(decimalResult);
|
flags_.carry |= result >= 0xa0;
|
||||||
|
if(flags_.carry) {
|
||||||
|
result += 0x60;
|
||||||
|
}
|
||||||
|
|
||||||
if(is_65c02(personality)) {
|
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_);
|
flags_.set_nz(a_);
|
||||||
read_mem(operand_, address_.full);
|
read_mem(operand_, address_.full);
|
||||||
break;
|
break;
|
||||||
@@ -342,7 +390,7 @@ template <Personality personality, typename T, bool uses_ready_line> void Proces
|
|||||||
flags_.carry = (result >> 8)&1;
|
flags_.carry = (result >> 8)&1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// fix up in case this was INS
|
// fix up in case this was INS.
|
||||||
if(cycle == OperationINS) operand_ = ~operand_;
|
if(cycle == OperationINS) operand_ = ~operand_;
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -728,3 +776,8 @@ void ProcessorBase::set_value_of(Register r, uint16_t value) {
|
|||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ProcessorBase::restart_operation_fetch() {
|
||||||
|
scheduled_program_counter_ = nullptr;
|
||||||
|
next_bus_operation_ = BusOperation::None;
|
||||||
|
}
|
||||||
|
@@ -17,15 +17,15 @@ using namespace CPU::MOS6502;
|
|||||||
#define Absolute CycleLoadAddressAbsolute
|
#define Absolute CycleLoadAddressAbsolute
|
||||||
#define AbsoluteXr CycleLoadAddressAbsolute, CycleAddXToAddressLow, OperationCorrectAddressHigh
|
#define AbsoluteXr CycleLoadAddressAbsolute, CycleAddXToAddressLow, OperationCorrectAddressHigh
|
||||||
#define AbsoluteYr CycleLoadAddressAbsolute, CycleAddYToAddressLow, OperationCorrectAddressHigh
|
#define AbsoluteYr CycleLoadAddressAbsolute, CycleAddYToAddressLow, OperationCorrectAddressHigh
|
||||||
#define AbsoluteX CycleLoadAddressAbsolute, CycleAddXToAddressLowRead, OperationCorrectAddressHigh
|
#define AbsoluteXw CycleLoadAddressAbsolute, CycleAddXToAddressLowRead, OperationCorrectAddressHigh
|
||||||
#define AbsoluteY CycleLoadAddressAbsolute, CycleAddYToAddressLowRead, OperationCorrectAddressHigh
|
#define AbsoluteYw CycleLoadAddressAbsolute, CycleAddYToAddressLowRead, OperationCorrectAddressHigh
|
||||||
#define Zero OperationLoadAddressZeroPage
|
#define Zero OperationLoadAddressZeroPage
|
||||||
#define ZeroX CycleLoadAddessZeroX
|
#define ZeroX CycleLoadAddessZeroX
|
||||||
#define ZeroY CycleLoadAddessZeroY
|
#define ZeroY CycleLoadAddessZeroY
|
||||||
#define ZeroIndirect OperationLoadAddressZeroPage, CycleFetchAddressLowFromOperand, CycleIncrementOperandFetchAddressHigh
|
#define ZeroIndirect OperationLoadAddressZeroPage, CycleFetchAddressLowFromOperand, CycleIncrementOperandFetchAddressHigh
|
||||||
#define IndexedIndirect CycleIncrementPCFetchAddressLowFromOperand, CycleAddXToOperandFetchAddressLow, CycleIncrementOperandFetchAddressHigh
|
#define IndexedIndirect CycleIncrementPCFetchAddressLowFromOperand, CycleAddXToOperandFetchAddressLow, CycleIncrementOperandFetchAddressHigh
|
||||||
#define IndirectIndexedr CycleIncrementPCFetchAddressLowFromOperand, CycleIncrementOperandFetchAddressHigh, CycleAddYToAddressLow, OperationCorrectAddressHigh
|
#define IndirectIndexedr CycleIncrementPCFetchAddressLowFromOperand, CycleIncrementOperandFetchAddressHigh, CycleAddYToAddressLow, OperationCorrectAddressHigh
|
||||||
#define IndirectIndexed CycleIncrementPCFetchAddressLowFromOperand, CycleIncrementOperandFetchAddressHigh, CycleAddYToAddressLowRead, OperationCorrectAddressHigh
|
#define IndirectIndexedw CycleIncrementPCFetchAddressLowFromOperand, CycleIncrementOperandFetchAddressHigh, CycleAddYToAddressLowRead, OperationCorrectAddressHigh
|
||||||
|
|
||||||
#define Read(...) CycleFetchOperandFromAddress, __VA_ARGS__
|
#define Read(...) CycleFetchOperandFromAddress, __VA_ARGS__
|
||||||
#define Write(...) __VA_ARGS__, CycleWriteOperandToAddress
|
#define Write(...) __VA_ARGS__, CycleWriteOperandToAddress
|
||||||
@@ -42,23 +42,23 @@ using namespace CPU::MOS6502;
|
|||||||
#define IndirectIndexedRead(op) Program(IndirectIndexedr, Read(op))
|
#define IndirectIndexedRead(op) Program(IndirectIndexedr, Read(op))
|
||||||
|
|
||||||
#define AbsoluteWrite(op) Program(Absolute, Write(op))
|
#define AbsoluteWrite(op) Program(Absolute, Write(op))
|
||||||
#define AbsoluteXWrite(op) Program(AbsoluteX, Write(op))
|
#define AbsoluteXWrite(op) Program(AbsoluteXw, Write(op))
|
||||||
#define AbsoluteYWrite(op) Program(AbsoluteY, Write(op))
|
#define AbsoluteYWrite(op) Program(AbsoluteYw, Write(op))
|
||||||
#define ZeroWrite(op) Program(Zero, Write(op))
|
#define ZeroWrite(op) Program(Zero, Write(op))
|
||||||
#define ZeroXWrite(op) Program(ZeroX, Write(op))
|
#define ZeroXWrite(op) Program(ZeroX, Write(op))
|
||||||
#define ZeroYWrite(op) Program(ZeroY, Write(op))
|
#define ZeroYWrite(op) Program(ZeroY, Write(op))
|
||||||
#define ZeroIndirectWrite(op) Program(ZeroIndirect, Write(op))
|
#define ZeroIndirectWrite(op) Program(ZeroIndirect, Write(op))
|
||||||
#define IndexedIndirectWrite(op) Program(IndexedIndirect, 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 AbsoluteReadModifyWrite(...) Program(Absolute, ReadModifyWrite(__VA_ARGS__))
|
||||||
#define AbsoluteXReadModifyWrite(...) Program(AbsoluteX, ReadModifyWrite(__VA_ARGS__))
|
#define AbsoluteXReadModifyWrite(...) Program(AbsoluteXw, ReadModifyWrite(__VA_ARGS__))
|
||||||
#define AbsoluteYReadModifyWrite(...) Program(AbsoluteY, ReadModifyWrite(__VA_ARGS__))
|
#define AbsoluteYReadModifyWrite(...) Program(AbsoluteYw, ReadModifyWrite(__VA_ARGS__))
|
||||||
#define ZeroReadModifyWrite(...) Program(Zero, ReadModifyWrite(__VA_ARGS__))
|
#define ZeroReadModifyWrite(...) Program(Zero, ReadModifyWrite(__VA_ARGS__))
|
||||||
#define ZeroXReadModifyWrite(...) Program(ZeroX, ReadModifyWrite(__VA_ARGS__))
|
#define ZeroXReadModifyWrite(...) Program(ZeroX, ReadModifyWrite(__VA_ARGS__))
|
||||||
#define ZeroYReadModifyWrite(...) Program(ZeroY, ReadModifyWrite(__VA_ARGS__))
|
#define ZeroYReadModifyWrite(...) Program(ZeroY, ReadModifyWrite(__VA_ARGS__))
|
||||||
#define IndexedIndirectReadModifyWrite(...) Program(IndexedIndirect, 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 FastAbsoluteXReadModifyWrite(...) Program(AbsoluteXr, ReadModifyWrite(__VA_ARGS__))
|
||||||
#define FastAbsoluteYReadModifyWrite(...) Program(AbsoluteYr, ReadModifyWrite(__VA_ARGS__))
|
#define FastAbsoluteYReadModifyWrite(...) Program(AbsoluteYr, ReadModifyWrite(__VA_ARGS__))
|
||||||
@@ -68,8 +68,8 @@ using namespace CPU::MOS6502;
|
|||||||
|
|
||||||
#define ZeroNop() Program(Zero, CycleFetchOperandFromAddress)
|
#define ZeroNop() Program(Zero, CycleFetchOperandFromAddress)
|
||||||
#define ZeroXNop() Program(ZeroX, CycleFetchOperandFromAddress)
|
#define ZeroXNop() Program(ZeroX, CycleFetchOperandFromAddress)
|
||||||
#define AbsoluteNop() Program(Absolute)
|
#define AbsoluteNop() Program(Absolute, CycleFetchOperandFromAddress)
|
||||||
#define AbsoluteXNop() Program(AbsoluteX)
|
#define AbsoluteXNop() Program(AbsoluteXr, CycleFetchOperandFromAddress)
|
||||||
#define ImpliedNop() {OperationMoveToNextProgram}
|
#define ImpliedNop() {OperationMoveToNextProgram}
|
||||||
#define ImmediateNop() Program(OperationIncrementPC)
|
#define ImmediateNop() Program(OperationIncrementPC)
|
||||||
|
|
||||||
|
@@ -9,6 +9,7 @@
|
|||||||
#ifndef _502Selector_h
|
#ifndef _502Selector_h
|
||||||
#define _502Selector_h
|
#define _502Selector_h
|
||||||
|
|
||||||
|
#include "6502Esque.hpp"
|
||||||
#include "../6502/6502.hpp"
|
#include "../6502/6502.hpp"
|
||||||
#include "../65816/65816.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 <Type processor_type> class BusHandlerT: public BusHandler<uint16_t> {};
|
||||||
template <> class BusHandlerT<Type::TWDC65816>: public BusHandler<uint32_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 */
|
#endif /* _502Selector_h */
|
||||||
|
@@ -30,7 +30,10 @@ uint16_t ProcessorBase::value_of(Register r) const {
|
|||||||
void ProcessorBase::set_value_of(Register r, uint16_t value) {
|
void ProcessorBase::set_value_of(Register r, uint16_t value) {
|
||||||
switch (r) {
|
switch (r) {
|
||||||
case Register::ProgramCounter: registers_.pc = value; break;
|
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::Flags: set_flags(uint8_t(value)); break;
|
||||||
case Register::A: registers_.a.full = value; break;
|
case Register::A: registers_.a.full = value; break;
|
||||||
case Register::X: registers_.x.full = value & registers_.x_mask; break;
|
case Register::X: registers_.x.full = value & registers_.x_mask; break;
|
||||||
|
@@ -67,6 +67,9 @@ template <typename BusHandler, bool uses_ready_line> void Processor<BusHandler,
|
|||||||
last_operation_pc_ = registers_.pc;
|
last_operation_pc_ = registers_.pc;
|
||||||
last_operation_program_bank_ = uint8_t(registers_.program_bank >> 16);
|
last_operation_program_bank_ = uint8_t(registers_.program_bank >> 16);
|
||||||
memory_lock_ = false;
|
memory_lock_ = false;
|
||||||
|
|
||||||
|
// Reenforce the top byte of S if applicable.
|
||||||
|
registers_.s.full = stack_address();
|
||||||
} continue;
|
} continue;
|
||||||
|
|
||||||
case OperationDecode: {
|
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).
|
// (and make reasonable guesses as to the N flag).
|
||||||
|
|
||||||
case TXS:
|
case TXS:
|
||||||
registers_.s = registers_.x.full;
|
registers_.s = registers_.x;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TSX:
|
case TSX:
|
||||||
@@ -668,10 +671,8 @@ template <typename BusHandler, bool uses_ready_line> void Processor<BusHandler,
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case TCS:
|
case TCS:
|
||||||
registers_.s.full = registers_.a.full;
|
registers_.s = registers_.a;
|
||||||
// No need to worry about byte masking here;
|
registers_.s.full = stack_address();
|
||||||
// for the stack it's handled as the emulation runs.
|
|
||||||
// Cf. the stack_address() macro.
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TSC:
|
case TSC:
|
||||||
|
@@ -1131,11 +1131,10 @@ void ProcessorStorage::set_emulation_mode(bool enabled) {
|
|||||||
set_m_x_flags(true, true);
|
set_m_x_flags(true, true);
|
||||||
registers_.e_masks[0] = 0xff00;
|
registers_.e_masks[0] = 0xff00;
|
||||||
registers_.e_masks[1] = 0x00ff;
|
registers_.e_masks[1] = 0x00ff;
|
||||||
|
registers_.s.halves.high = 1;
|
||||||
} else {
|
} else {
|
||||||
registers_.e_masks[0] = 0x0000;
|
registers_.e_masks[0] = 0x0000;
|
||||||
registers_.e_masks[1] = 0xffff;
|
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.
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user