mirror of
https://github.com/TomHarte/CLK.git
synced 2025-09-11 10:28:07 +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: {
|
||||
std::size_t operand_address = address_mapper(address);
|
||||
if(operand_address >= memory.size()) return;
|
||||
address++;
|
||||
++address;
|
||||
|
||||
instruction.operand = memory[operand_address];
|
||||
}
|
||||
@@ -291,20 +291,34 @@ static void AddToDisassembly(PartialDisassembly &disassembly, const std::vector<
|
||||
}
|
||||
|
||||
// Decide on overall flow control.
|
||||
if(instruction.operation == Instruction::RTS || instruction.operation == Instruction::RTI) return;
|
||||
if(instruction.operation == Instruction::BRK) return; // TODO: check whether IRQ vector is within memory range
|
||||
if(instruction.operation == Instruction::JSR) {
|
||||
disassembly.remaining_entry_points.push_back(instruction.operand);
|
||||
}
|
||||
if(instruction.operation == Instruction::JMP) {
|
||||
if(instruction.addressing_mode == Instruction::Absolute)
|
||||
disassembly.remaining_entry_points.push_back(instruction.operand);
|
||||
return;
|
||||
}
|
||||
|
||||
// All relative instructions are flow control.
|
||||
if(instruction.addressing_mode == Instruction::Relative) {
|
||||
uint16_t destination = uint16_t(address + int8_t(instruction.operand));
|
||||
disassembly.remaining_entry_points.push_back(destination);
|
||||
}
|
||||
|
||||
switch(instruction.operation) {
|
||||
default: break;
|
||||
|
||||
case Instruction::KIL:
|
||||
case Instruction::RTS:
|
||||
case Instruction::RTI:
|
||||
case Instruction::BRK: // TODO: check whether IRQ vector is within memory range.
|
||||
disassembly.implicit_entry_points.push_back(address);
|
||||
return;
|
||||
|
||||
case Instruction::JMP:
|
||||
// Adding a new entry point for relative jumps was handled above.
|
||||
if(instruction.addressing_mode == Instruction::Absolute) {
|
||||
disassembly.remaining_entry_points.push_back(instruction.operand);
|
||||
}
|
||||
return;
|
||||
|
||||
case Instruction::JSR:
|
||||
disassembly.remaining_entry_points.push_back(instruction.operand);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,5 +330,5 @@ Disassembly Analyser::Static::MOS6502::Disassemble(
|
||||
const std::vector<uint8_t> &memory,
|
||||
const std::function<std::size_t(uint16_t)> &address_mapper,
|
||||
std::vector<uint16_t> entry_points) {
|
||||
return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, MOS6502Disassembler>(memory, address_mapper, entry_points);
|
||||
return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, MOS6502Disassembler>(memory, address_mapper, entry_points, false);
|
||||
}
|
||||
|
@@ -14,30 +14,51 @@ namespace Analyser::Static::Disassembly {
|
||||
template <typename D, typename S> struct PartialDisassembly {
|
||||
D disassembly;
|
||||
std::vector<S> remaining_entry_points;
|
||||
std::vector<S> implicit_entry_points;
|
||||
};
|
||||
|
||||
template <typename D, typename S, typename Disassembler> D Disassemble(
|
||||
const std::vector<uint8_t> &memory,
|
||||
const std::function<std::size_t(S)> &address_mapper,
|
||||
std::vector<S> entry_points) {
|
||||
std::vector<S> entry_points,
|
||||
bool exhaustive)
|
||||
{
|
||||
PartialDisassembly<D, S> partial_disassembly;
|
||||
partial_disassembly.remaining_entry_points = entry_points;
|
||||
|
||||
while(!partial_disassembly.remaining_entry_points.empty()) {
|
||||
// pull the next entry point from the back of the vector
|
||||
S next_entry_point = partial_disassembly.remaining_entry_points.back();
|
||||
partial_disassembly.remaining_entry_points.pop_back();
|
||||
// Do a recursive-style disassembly for all current entry points.
|
||||
while(!partial_disassembly.remaining_entry_points.empty()) {
|
||||
// Pull the next entry point from the back of the vector.
|
||||
const S next_entry_point = partial_disassembly.remaining_entry_points.back();
|
||||
partial_disassembly.remaining_entry_points.pop_back();
|
||||
|
||||
// if that address has already been visited, forget about it
|
||||
if( partial_disassembly.disassembly.instructions_by_address.find(next_entry_point)
|
||||
!= partial_disassembly.disassembly.instructions_by_address.end()) continue;
|
||||
// If that address has already been visited, forget about it.
|
||||
if( partial_disassembly.disassembly.instructions_by_address.find(next_entry_point)
|
||||
!= partial_disassembly.disassembly.instructions_by_address.end()) continue;
|
||||
|
||||
// if it's outgoing, log it as such and forget about it; otherwise disassemble
|
||||
std::size_t mapped_entry_point = address_mapper(next_entry_point);
|
||||
if(mapped_entry_point >= memory.size())
|
||||
partial_disassembly.disassembly.outward_calls.insert(next_entry_point);
|
||||
else
|
||||
Disassembler::AddToDisassembly(partial_disassembly, memory, address_mapper, next_entry_point);
|
||||
// If it's outgoing, log it as such and forget about it; otherwise disassemble.
|
||||
std::size_t mapped_entry_point = address_mapper(next_entry_point);
|
||||
if(mapped_entry_point >= memory.size())
|
||||
partial_disassembly.disassembly.outward_calls.insert(next_entry_point);
|
||||
else
|
||||
Disassembler::AddToDisassembly(partial_disassembly, memory, address_mapper, next_entry_point);
|
||||
}
|
||||
|
||||
// If this is not an exhaustive disassembly, that's your lot.
|
||||
if(!exhaustive) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Otherwise, copy in the new 'implicit entry points' (i.e. all locations that are one after
|
||||
// a disassembled region). There's a test above that'll ignore any which have already been
|
||||
// disassembled from.
|
||||
std::move(
|
||||
partial_disassembly.implicit_entry_points.begin(),
|
||||
partial_disassembly.implicit_entry_points.end(),
|
||||
std::back_inserter(partial_disassembly.remaining_entry_points)
|
||||
);
|
||||
partial_disassembly.implicit_entry_points.clear();
|
||||
}
|
||||
|
||||
return partial_disassembly.disassembly;
|
||||
|
@@ -589,7 +589,7 @@ struct Z80Disassembler {
|
||||
break;
|
||||
}
|
||||
|
||||
// Add any (potentially) newly discovered entry point.
|
||||
// Add any (potentially) newly-discovered entry point.
|
||||
if( instruction.operation == Instruction::Operation::JP ||
|
||||
instruction.operation == Instruction::Operation::JR ||
|
||||
instruction.operation == Instruction::Operation::CALL ||
|
||||
@@ -598,22 +598,37 @@ struct Z80Disassembler {
|
||||
}
|
||||
|
||||
// This is it if: an unconditional RET, RETI, RETN, JP or JR is found.
|
||||
if(instruction.condition != Instruction::Condition::None) continue;
|
||||
switch(instruction.operation) {
|
||||
default: break;
|
||||
|
||||
if(instruction.operation == Instruction::Operation::RET) return;
|
||||
if(instruction.operation == Instruction::Operation::RETI) return;
|
||||
if(instruction.operation == Instruction::Operation::RETN) return;
|
||||
if(instruction.operation == Instruction::Operation::JP) return;
|
||||
if(instruction.operation == Instruction::Operation::JR) return;
|
||||
case Instruction::Operation::RET:
|
||||
case Instruction::Operation::RETI:
|
||||
case Instruction::Operation::RETN:
|
||||
case Instruction::Operation::JP:
|
||||
case Instruction::Operation::JR:
|
||||
if(instruction.condition == Instruction::Condition::None) {
|
||||
disassembly.implicit_entry_points.push_back(accessor.address());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // end of anonymous namespace
|
||||
|
||||
|
||||
|
||||
Disassembly Analyser::Static::Z80::Disassemble(
|
||||
const std::vector<uint8_t> &memory,
|
||||
const std::function<std::size_t(uint16_t)> &address_mapper,
|
||||
std::vector<uint16_t> entry_points) {
|
||||
return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, Z80Disassembler>(memory, address_mapper, entry_points);
|
||||
std::vector<uint16_t> entry_points,
|
||||
Approach approach)
|
||||
{
|
||||
return Analyser::Static::Disassembly::Disassemble<Disassembly, uint16_t, Z80Disassembler>(
|
||||
memory,
|
||||
address_mapper,
|
||||
entry_points,
|
||||
approach == Approach::Exhaustive
|
||||
);
|
||||
}
|
||||
|
@@ -76,10 +76,19 @@ struct Disassembly {
|
||||
std::set<uint16_t> internal_stores, internal_loads, internal_modifies;
|
||||
};
|
||||
|
||||
enum class Approach {
|
||||
/// Disassemble from the supplied entry points until an indeterminate branch or return only, adding other fully-static
|
||||
/// entry points as they are observed.
|
||||
Recursive,
|
||||
/// Disassemble all supplied bytes, regardless of what nonsense may be encountered by accidental parsing of data areas.
|
||||
Exhaustive,
|
||||
};
|
||||
|
||||
Disassembly Disassemble(
|
||||
const std::vector<uint8_t> &memory,
|
||||
const std::function<std::size_t(uint16_t)> &address_mapper,
|
||||
std::vector<uint16_t> entry_points);
|
||||
std::vector<uint16_t> entry_points,
|
||||
Approach approach);
|
||||
|
||||
}
|
||||
|
||||
|
@@ -115,97 +115,16 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
|
||||
// be at play; disassemble to try to figure it out.
|
||||
std::vector<uint8_t> first_8k;
|
||||
first_8k.insert(first_8k.begin(), segment.data.begin(), segment.data.begin() + 8192);
|
||||
Analyser::Static::Z80::Disassembly disassembly =
|
||||
const Analyser::Static::Z80::Disassembly disassembly =
|
||||
Analyser::Static::Z80::Disassemble(
|
||||
first_8k,
|
||||
Analyser::Static::Disassembler::OffsetMapper(start_address),
|
||||
{ init_address }
|
||||
{ init_address },
|
||||
Analyser::Static::Z80::Approach::Exhaustive
|
||||
);
|
||||
|
||||
// // Look for a indirect store followed by an unconditional JP or CALL into another
|
||||
// // segment, that's a fairly explicit sign where found.
|
||||
using Instruction = Analyser::Static::Z80::Instruction;
|
||||
std::map<uint16_t, Instruction> &instructions = disassembly.instructions_by_address;
|
||||
bool is_ascii = false;
|
||||
// auto iterator = instructions.begin();
|
||||
// while(iterator != instructions.end()) {
|
||||
// auto next_iterator = iterator;
|
||||
// next_iterator++;
|
||||
// if(next_iterator == instructions.end()) break;
|
||||
//
|
||||
// if( iterator->second.operation == Instruction::Operation::LD &&
|
||||
// iterator->second.destination == Instruction::Location::Operand_Indirect &&
|
||||
// (
|
||||
// iterator->second.operand == 0x5000 ||
|
||||
// iterator->second.operand == 0x6000 ||
|
||||
// iterator->second.operand == 0x6800 ||
|
||||
// iterator->second.operand == 0x7000 ||
|
||||
// iterator->second.operand == 0x77ff ||
|
||||
// iterator->second.operand == 0x7800 ||
|
||||
// iterator->second.operand == 0x8000 ||
|
||||
// iterator->second.operand == 0x9000 ||
|
||||
// iterator->second.operand == 0xa000
|
||||
// ) &&
|
||||
// (
|
||||
// next_iterator->second.operation == Instruction::Operation::CALL ||
|
||||
// next_iterator->second.operation == Instruction::Operation::JP
|
||||
// ) &&
|
||||
// ((next_iterator->second.operand >> 13) != (0x4000 >> 13))
|
||||
// ) {
|
||||
// const uint16_t address = uint16_t(next_iterator->second.operand);
|
||||
// switch(iterator->second.operand) {
|
||||
// case 0x6000:
|
||||
// if(address >= 0x6000 && address < 0x8000) {
|
||||
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
|
||||
// }
|
||||
// break;
|
||||
// case 0x6800:
|
||||
// if(address >= 0x6000 && address < 0x6800) {
|
||||
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::ASCII8kb;
|
||||
// }
|
||||
// break;
|
||||
// case 0x7000:
|
||||
// if(address >= 0x6000 && address < 0x8000) {
|
||||
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
|
||||
// }
|
||||
// if(address >= 0x7000 && address < 0x7800) {
|
||||
// is_ascii = true;
|
||||
// }
|
||||
// break;
|
||||
// case 0x77ff:
|
||||
// if(address >= 0x7000 && address < 0x7800) {
|
||||
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::ASCII16kb;
|
||||
// }
|
||||
// break;
|
||||
// case 0x7800:
|
||||
// if(address >= 0xa000 && address < 0xc000) {
|
||||
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::ASCII8kb;
|
||||
// }
|
||||
// break;
|
||||
// case 0x8000:
|
||||
// if(address >= 0x8000 && address < 0xa000) {
|
||||
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
|
||||
// }
|
||||
// break;
|
||||
// case 0x9000:
|
||||
// if(address >= 0x8000 && address < 0xa000) {
|
||||
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
|
||||
// }
|
||||
// break;
|
||||
// case 0xa000:
|
||||
// if(address >= 0xa000 && address < 0xc000) {
|
||||
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::Konami;
|
||||
// }
|
||||
// break;
|
||||
// case 0xb000:
|
||||
// if(address >= 0xa000 && address < 0xc000) {
|
||||
// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC;
|
||||
// }
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// iterator = next_iterator;
|
||||
const std::map<uint16_t, Instruction> &instructions = disassembly.instructions_by_address;
|
||||
|
||||
// Look for LD (nnnn), A instructions, and collate those addresses.
|
||||
std::map<uint16_t, int> address_counts;
|
||||
@@ -217,49 +136,46 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
|
||||
}
|
||||
}
|
||||
|
||||
// Weight confidences by number of observed hits.
|
||||
float total_hits =
|
||||
float(
|
||||
address_counts[0x6000] + address_counts[0x6800] +
|
||||
address_counts[0x7000] + address_counts[0x7800] +
|
||||
address_counts[0x77ff] + address_counts[0x8000] +
|
||||
address_counts[0xa000] + address_counts[0x5000] +
|
||||
address_counts[0x9000] + address_counts[0xb000]
|
||||
);
|
||||
// Weight confidences by number of observed hits; if any is above 60% confidence, just use it.
|
||||
const auto ascii_8kb_total = address_counts[0x6000] + address_counts[0x6800] + address_counts[0x7000] + address_counts[0x7800];
|
||||
const auto ascii_16kb_total = address_counts[0x6000] + address_counts[0x7000] + address_counts[0x77ff];
|
||||
const auto konami_total = address_counts[0x6000] + address_counts[0x8000] + address_counts[0xa000];
|
||||
const auto konami_with_scc_total = address_counts[0x5000] + address_counts[0x7000] + address_counts[0x9000] + address_counts[0xb000];
|
||||
|
||||
targets.push_back(CartridgeTarget(
|
||||
segment,
|
||||
start_address,
|
||||
Analyser::Static::MSX::Cartridge::ASCII8kb,
|
||||
float( address_counts[0x6000] +
|
||||
address_counts[0x6800] +
|
||||
address_counts[0x7000] +
|
||||
address_counts[0x7800]) / total_hits));
|
||||
targets.push_back(CartridgeTarget(
|
||||
segment,
|
||||
start_address,
|
||||
Analyser::Static::MSX::Cartridge::ASCII16kb,
|
||||
float( address_counts[0x6000] +
|
||||
address_counts[0x7000] +
|
||||
address_counts[0x77ff]) / total_hits));
|
||||
if(!is_ascii) {
|
||||
const auto total_hits = ascii_8kb_total + ascii_16kb_total + konami_total + konami_with_scc_total;
|
||||
|
||||
const bool is_ascii_8kb = (ascii_8kb_total * 5) / (total_hits * 3);
|
||||
const bool is_ascii_16kb = (ascii_16kb_total * 5) / (total_hits * 3);
|
||||
const bool is_konami = (konami_total * 5) / (total_hits * 3);
|
||||
const bool is_konami_with_scc = (konami_with_scc_total * 5) / (total_hits * 3);
|
||||
|
||||
if(!is_ascii_16kb && !is_konami && !is_konami_with_scc) {
|
||||
targets.push_back(CartridgeTarget(
|
||||
segment,
|
||||
start_address,
|
||||
Analyser::Static::MSX::Cartridge::ASCII8kb,
|
||||
float(ascii_8kb_total) / float(total_hits)));
|
||||
}
|
||||
if(!is_ascii_8kb && !is_konami && !is_konami_with_scc) {
|
||||
targets.push_back(CartridgeTarget(
|
||||
segment,
|
||||
start_address,
|
||||
Analyser::Static::MSX::Cartridge::ASCII16kb,
|
||||
float(ascii_16kb_total) / float(total_hits)));
|
||||
}
|
||||
if(!is_ascii_8kb && !is_ascii_16kb && !is_konami_with_scc) {
|
||||
targets.push_back(CartridgeTarget(
|
||||
segment,
|
||||
start_address,
|
||||
Analyser::Static::MSX::Cartridge::Konami,
|
||||
float( address_counts[0x6000] +
|
||||
address_counts[0x8000] +
|
||||
address_counts[0xa000]) / total_hits));
|
||||
float(konami_total) / float(total_hits)));
|
||||
}
|
||||
if(!is_ascii) {
|
||||
if(!is_ascii_8kb && !is_ascii_16kb && !is_konami) {
|
||||
targets.push_back(CartridgeTarget(
|
||||
segment,
|
||||
start_address,
|
||||
Analyser::Static::MSX::Cartridge::KonamiWithSCC,
|
||||
float( address_counts[0x5000] +
|
||||
address_counts[0x7000] +
|
||||
address_counts[0x9000] +
|
||||
address_counts[0xb000]) / total_hits));
|
||||
float(konami_with_scc_total) / float(total_hits)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -26,7 +26,7 @@
|
||||
Machines that accumulate HalfCycle time but supply to a Cycle-counted device may supply a
|
||||
separate @c TargetTimeScale at template declaration.
|
||||
|
||||
If the held object implements get_next_sequence_point() then it'll be used to flush implicitly
|
||||
If the held object implements @c next_sequence_point() then it'll be used to flush implicitly
|
||||
as and when sequence points are hit. Callers can use will_flush() to predict these.
|
||||
|
||||
If the held object is a subclass of ClockingHint::Source, this template will register as an
|
||||
@@ -40,7 +40,7 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
|
||||
private:
|
||||
/*!
|
||||
A std::unique_ptr deleter which causes an update_sequence_point to occur on the actor supplied
|
||||
to it at construction if it implements get_next_sequence_point(). Otherwise destruction is a no-op.
|
||||
to it at construction if it implements @c next_sequence_point(). Otherwise destruction is a no-op.
|
||||
|
||||
**Does not delete the object.**
|
||||
|
||||
@@ -247,9 +247,9 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
|
||||
// going to be applied then do a direct max -> max translation rather than
|
||||
// allowing the arithmetic to overflow.
|
||||
if constexpr (divider == 1 && std::is_same_v<LocalTimeScale, TargetTimeScale>) {
|
||||
time_until_event_ = object_.get_next_sequence_point();
|
||||
time_until_event_ = object_.next_sequence_point();
|
||||
} else {
|
||||
const auto time = object_.get_next_sequence_point();
|
||||
const auto time = object_.next_sequence_point();
|
||||
if(time == TargetTimeScale::max()) {
|
||||
time_until_event_ = LocalTimeScale::max();
|
||||
} else {
|
||||
@@ -272,7 +272,7 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
|
||||
bool did_flush_ = false;
|
||||
|
||||
template <typename S, typename = void> struct has_sequence_points : std::false_type {};
|
||||
template <typename S> struct has_sequence_points<S, decltype(void(std::declval<S &>().get_next_sequence_point()))> : std::true_type {};
|
||||
template <typename S> struct has_sequence_points<S, decltype(void(std::declval<S &>().next_sequence_point()))> : std::true_type {};
|
||||
|
||||
ClockingHint::Preference clocking_preference_ = ClockingHint::Preference::JustInTime;
|
||||
void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference clocking) {
|
||||
|
@@ -207,7 +207,7 @@ void MFP68901::run_for(HalfCycles time) {
|
||||
}
|
||||
}
|
||||
|
||||
HalfCycles MFP68901::get_next_sequence_point() {
|
||||
HalfCycles MFP68901::next_sequence_point() {
|
||||
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.
|
||||
///
|
||||
/// @discussion TODO, alas.
|
||||
HalfCycles get_next_sequence_point();
|
||||
HalfCycles next_sequence_point();
|
||||
|
||||
/// Sets the current level of either of the timer event inputs — TAI and TBI in datasheet terms.
|
||||
void set_timer_event_input(int channel, bool value);
|
||||
|
@@ -108,7 +108,7 @@ template <Personality personality> class TMS9918: private Base<personality> {
|
||||
If get_interrupt_line is true now of if get_interrupt_line would
|
||||
never return true, returns HalfCycles::max().
|
||||
*/
|
||||
HalfCycles get_next_sequence_point() const;
|
||||
HalfCycles next_sequence_point() const;
|
||||
|
||||
/*!
|
||||
Returns the amount of time until the nominated line interrupt position is
|
||||
|
@@ -1268,7 +1268,7 @@ uint8_t TMS9918<personality>::get_current_line() const {
|
||||
return uint8_t(source_row);
|
||||
}
|
||||
template <Personality personality>
|
||||
HalfCycles TMS9918<personality>::get_next_sequence_point() const {
|
||||
HalfCycles TMS9918<personality>::next_sequence_point() const {
|
||||
if(!this->generate_interrupts_ && !this->enable_line_interrupts_) return HalfCycles::max();
|
||||
if(get_interrupt_line()) return HalfCycles::max();
|
||||
|
||||
|
@@ -68,11 +68,9 @@ void Executor::set_interrupt_line(bool line) {
|
||||
// is active, amongst other things.
|
||||
if(interrupt_line_ != line) {
|
||||
interrupt_line_ = line;
|
||||
|
||||
// TODO: verify interrupt connection. Externally, but stubbed off here.
|
||||
// if(!interrupt_disable_ && line) {
|
||||
// perform_interrupt<false>(0x1ff4);
|
||||
// }
|
||||
if(line) {
|
||||
set_interrupt_request(interrupt_control_, 0x02, 0x1ff4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,6 +223,16 @@ template<bool is_brk> inline void Executor::perform_interrupt(uint16_t vector) {
|
||||
set_program_counter(uint16_t(memory_[vector] | (memory_[vector+1] << 8)));
|
||||
}
|
||||
|
||||
void Executor::set_interrupt_request(uint8_t ®, uint8_t value, uint16_t vector) {
|
||||
// TODO: this allows interrupts through only if fully enabled at the time they
|
||||
// signal. Which isn't quite correct, albeit that it seems sufficient for the
|
||||
// IIgs ADB controller.
|
||||
reg |= value;
|
||||
if(!interrupt_disable_ && (reg & (value >> 1))) {
|
||||
perform_interrupt<false>(vector);
|
||||
}
|
||||
}
|
||||
|
||||
template <Operation operation, AddressingMode addressing_mode> void Executor::perform() {
|
||||
// Post cycle cost; this emulation _does not provide accurate timing_.
|
||||
#define TLength(mode, base) case AddressingMode::mode: subtract_duration(base + t_lengths[index_mode_]); break;
|
||||
@@ -789,12 +797,17 @@ inline void Executor::subtract_duration(int duration) {
|
||||
// this additional divide by 4 produces the correct net divide by 16.
|
||||
|
||||
timer_divider_ += duration;
|
||||
const int t12_ticks = update_timer(prescalers_[0], timer_divider_ / t12_divider);
|
||||
const int clock_ticks = timer_divider_ / t12_divider;
|
||||
timer_divider_ &= (t12_divider-1);
|
||||
|
||||
// Update timers 1 and 2. TODO: interrupts (elsewhere?).
|
||||
if(update_timer(timers_[0], t12_ticks)) interrupt_control_ |= 0x20;
|
||||
if(update_timer(timers_[1], t12_ticks)) interrupt_control_ |= 0x08;
|
||||
// Update timers 1 and 2.
|
||||
const int t12_ticks = update_timer(prescalers_[0], timer_divider_ / t12_divider);
|
||||
if(update_timer(timers_[0], t12_ticks)) {
|
||||
set_interrupt_request(interrupt_control_, 0x20, 0x1ff8);
|
||||
}
|
||||
if(update_timer(timers_[1], t12_ticks)) {
|
||||
set_interrupt_request(interrupt_control_, 0x08, 0x1ff6);
|
||||
}
|
||||
|
||||
// If timer X is disabled, stop.
|
||||
if(timer_control_&0x20) {
|
||||
@@ -804,9 +817,10 @@ inline void Executor::subtract_duration(int duration) {
|
||||
// Update timer X prescaler.
|
||||
switch(timer_control_ & 0x0c) {
|
||||
default: {
|
||||
const int tx_ticks = update_timer(prescalers_[1], t12_ticks); // TODO: don't hard code this. And is this even right?
|
||||
if(update_timer(timers_[2], tx_ticks))
|
||||
timer_control_ |= 0x80; // TODO: interrupt result of this.
|
||||
const int tx_ticks = update_timer(prescalers_[1], clock_ticks);
|
||||
if(update_timer(timers_[2], tx_ticks)) {
|
||||
set_interrupt_request(timer_control_, 0x80, 0x1ffa);
|
||||
}
|
||||
} break;
|
||||
case 0x04:
|
||||
LOG("TODO: Timer X; Pulse output mode");
|
||||
|
@@ -165,6 +165,8 @@ class Executor: public CachingExecutor {
|
||||
template<bool is_brk> inline void perform_interrupt(uint16_t vector);
|
||||
inline void set_port_output(int port);
|
||||
|
||||
void set_interrupt_request(uint8_t ®, uint8_t value, uint16_t vector);
|
||||
|
||||
// MARK: - Execution time
|
||||
|
||||
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) {
|
||||
// ADB keyboard events: low 7 bits are a key code; bit 7 is either 0 for pressed or 1 for released.
|
||||
std::lock_guard lock_guard(keys_mutex_);
|
||||
if(pressed_keys_[size_t(key)] == is_pressed) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// ADB keyboard events: low 7 bits are a key code;
|
||||
// bit 7 is either 0 for pressed or 1 for released.
|
||||
pending_events_.push_back(uint8_t(key) | (is_pressed ? 0x00 : 0x80));
|
||||
pressed_keys_[size_t(key)] = is_pressed;
|
||||
|
||||
|
@@ -106,7 +106,7 @@ class Keyboard: public ReactiveDevice {
|
||||
void did_receive_data(const Command &, const std::vector<uint8_t> &) override;
|
||||
|
||||
std::mutex keys_mutex_;
|
||||
std::array<bool, 128> pressed_keys_;
|
||||
std::array<bool, 128> pressed_keys_{};
|
||||
std::vector<uint8_t> pending_events_;
|
||||
uint16_t modifiers_ = 0xffff;
|
||||
};
|
||||
|
@@ -15,24 +15,36 @@ using namespace Apple::ADB;
|
||||
Mouse::Mouse(Bus &bus) : ReactiveDevice(bus, 3) {}
|
||||
|
||||
void Mouse::perform_command(const Command &command) {
|
||||
if(command.type == Command::Type::Talk && command.reg == 0) {
|
||||
// Read current deltas and buttons, thread safely.
|
||||
auto delta_x = delta_x_.exchange(0);
|
||||
auto delta_y = delta_y_.exchange(0);
|
||||
const int buttons = button_flags_;
|
||||
// Mouse deltas are confined to a seven-bit signed field; this implementation keeps things symmetrical by
|
||||
// limiting them to a maximum absolute value of 63 in any direction.
|
||||
static constexpr int16_t max_delta = 63;
|
||||
|
||||
// Clamp deltas.
|
||||
delta_x = std::clamp(delta_x, int16_t(-128), int16_t(127));
|
||||
delta_y = std::clamp(delta_y, int16_t(-128), int16_t(127));
|
||||
if(command.type == Command::Type::Talk && command.reg == 0) {
|
||||
// Read and clamp current deltas and buttons.
|
||||
//
|
||||
// There's some small chance of creating negative feedback here — taking too much off delta_x_ or delta_y_
|
||||
// due to a change in the underlying value between the load and the arithmetic. But if that occurs it means
|
||||
// the user moved the mouse again in the interim, so it'll just play out as very slight latency.
|
||||
auto delta_x = delta_x_.load(std::memory_order::memory_order_relaxed);
|
||||
auto delta_y = delta_y_.load(std::memory_order::memory_order_relaxed);
|
||||
if(abs(delta_x) > max_delta || abs(delta_y) > max_delta) {
|
||||
int max = std::max(abs(delta_x), abs(delta_y));
|
||||
delta_x = delta_x * max_delta / max;
|
||||
delta_y = delta_y * max_delta / max;
|
||||
}
|
||||
|
||||
const int buttons = button_flags_.load(std::memory_order::memory_order_relaxed);
|
||||
delta_x_ -= delta_x;
|
||||
delta_y_ -= delta_y;
|
||||
|
||||
// Figure out what that would look like, and don't respond if there's
|
||||
// no change to report.
|
||||
// no change or deltas to report.
|
||||
const uint16_t reg0 =
|
||||
((buttons & 1) ? 0x0000 : 0x8000) |
|
||||
((buttons & 2) ? 0x0000 : 0x0080) |
|
||||
uint16_t(delta_x & 0x7f) |
|
||||
uint16_t((delta_y & 0x7f) << 8);
|
||||
if(reg0 == last_posted_reg0_) return;
|
||||
if(!(reg0 & 0x7f7f) && (reg0 & 0x8080) == (last_posted_reg0_ & 0x8080)) return;
|
||||
|
||||
// Post change.
|
||||
last_posted_reg0_ = reg0;
|
||||
|
@@ -76,11 +76,15 @@ uint8_t GLU::get_mouse_data() {
|
||||
// b5–b0: mouse delta.
|
||||
|
||||
const uint8_t result = registers_[visible_mouse_register_];
|
||||
if(visible_mouse_register_ == 2) {
|
||||
++visible_mouse_register_;
|
||||
} else {
|
||||
if(visible_mouse_register_ == 3) {
|
||||
status_ &= ~uint8_t(CPUFlags::MouseDataFull);
|
||||
}
|
||||
|
||||
// Spelt out at tedious length because Clang has trust issues.
|
||||
static constexpr int first_register = 2;
|
||||
static constexpr int second_register = 3;
|
||||
static constexpr int flip_mask = first_register ^ second_register;
|
||||
visible_mouse_register_ ^= flip_mask;
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -116,7 +120,7 @@ uint8_t GLU::get_status() {
|
||||
// b2: 1 = keyboard data interrupt is enabled.
|
||||
// b1: 1 = mouse x-data is available; 0 = y.
|
||||
// b0: 1 = command register is full (set when command is written); 0 = empty (cleared when data is read).
|
||||
return status_ | ((visible_mouse_register_ == 2) ? uint8_t(CPUFlags::MouseXIsAvailable) : 0);
|
||||
return status_ | ((visible_mouse_register_ == 2) ? 0 : uint8_t(CPUFlags::MouseXIsAvailable));
|
||||
}
|
||||
|
||||
void GLU::set_status(uint8_t status) {
|
||||
@@ -223,7 +227,6 @@ void GLU::set_port_output(int port, uint8_t value) {
|
||||
case 3:
|
||||
status_ |= uint8_t(CPUFlags::MouseDataFull);
|
||||
visible_mouse_register_ = 2;
|
||||
printf("Mouse: %d <- %02x\n", register_address_, register_latch_);
|
||||
break;
|
||||
case 7: status_ |= uint8_t(CPUFlags::CommandDataIsValid); break;
|
||||
}
|
||||
@@ -286,5 +289,6 @@ void GLU::run_ports_for(Cycles cycles) {
|
||||
}
|
||||
|
||||
void GLU::set_vertical_blank(bool is_blank) {
|
||||
vertical_blank_ = is_blank;
|
||||
executor_.set_interrupt_line(is_blank);
|
||||
}
|
||||
|
@@ -752,7 +752,7 @@ class ConcreteMachine:
|
||||
is_1Mhz = true;
|
||||
break;
|
||||
case Read(0xc045):
|
||||
// MMDELTAX byte.
|
||||
// MMDELTAY byte.
|
||||
*value = 0;
|
||||
is_1Mhz = true;
|
||||
break;
|
||||
@@ -1171,7 +1171,7 @@ class ConcreteMachine:
|
||||
machine_->update_audio();
|
||||
}
|
||||
~AudioUpdater() {
|
||||
machine_->cycles_until_audio_event_ = machine_->sound_glu_.get_next_sequence_point();
|
||||
machine_->cycles_until_audio_event_ = machine_->sound_glu_.next_sequence_point();
|
||||
}
|
||||
private:
|
||||
ConcreteMachine *machine_;
|
||||
|
@@ -218,7 +218,7 @@ uint8_t GLU::get_address_high() {
|
||||
|
||||
// MARK: - Update logic.
|
||||
|
||||
Cycles GLU::get_next_sequence_point() const {
|
||||
Cycles GLU::next_sequence_point() const {
|
||||
uint32_t result = std::numeric_limits<decltype(result)>::max();
|
||||
|
||||
for(int c = 0; c < local_.oscillator_count; c++) {
|
||||
|
@@ -31,7 +31,7 @@ class GLU: public Outputs::Speaker::SampleSource {
|
||||
uint8_t get_address_high();
|
||||
|
||||
void run_for(Cycles);
|
||||
Cycles get_next_sequence_point() const;
|
||||
Cycles next_sequence_point() const;
|
||||
bool get_interrupt_line();
|
||||
|
||||
// SampleSource.
|
||||
|
@@ -191,7 +191,7 @@ void Video::advance(Cycles cycles) {
|
||||
}
|
||||
}
|
||||
|
||||
Cycles Video::get_next_sequence_point() const {
|
||||
Cycles Video::next_sequence_point() const {
|
||||
const int cycles_into_row = cycles_into_frame_ % CyclesPerLine;
|
||||
const int row = cycles_into_frame_ / CyclesPerLine;
|
||||
|
||||
@@ -285,7 +285,7 @@ void Video::output_row(int row, int start, int end) {
|
||||
|
||||
// Post an interrupt if requested.
|
||||
if(line_control_ & 0x40) {
|
||||
set_interrupts(0x20);
|
||||
interrupts_.add(0x20);
|
||||
}
|
||||
|
||||
// Set up appropriately for fill mode (or not).
|
||||
@@ -498,19 +498,19 @@ uint8_t Video::get_new_video() {
|
||||
}
|
||||
|
||||
void Video::clear_interrupts(uint8_t mask) {
|
||||
set_interrupts(interrupts_ & ~(mask & 0x60));
|
||||
interrupts_.clear(mask);
|
||||
}
|
||||
|
||||
void Video::set_interrupt_register(uint8_t mask) {
|
||||
set_interrupts(interrupts_ | (mask & 0x6));
|
||||
interrupts_.set_control(mask);
|
||||
}
|
||||
|
||||
uint8_t Video::get_interrupt_register() {
|
||||
return interrupts_;
|
||||
return interrupts_.status();
|
||||
}
|
||||
|
||||
bool Video::get_interrupt_line() {
|
||||
return (interrupts_&0x80) || (megaii_interrupt_mask_&megaii_interrupt_state_);
|
||||
return interrupts_.active() || (megaii_interrupt_mask_ & megaii_interrupt_state_);
|
||||
}
|
||||
|
||||
void Video::set_megaii_interrupts_enabled(uint8_t mask) {
|
||||
@@ -526,13 +526,7 @@ void Video::clear_megaii_interrupts() {
|
||||
}
|
||||
|
||||
void Video::notify_clock_tick() {
|
||||
set_interrupts(interrupts_ | 0x40);
|
||||
}
|
||||
|
||||
void Video::set_interrupts(uint8_t new_value) {
|
||||
interrupts_ = new_value & 0x7f;
|
||||
if((interrupts_ >> 4) & interrupts_ & 0x6)
|
||||
interrupts_ |= 0x80;
|
||||
interrupts_.add(0x40);
|
||||
}
|
||||
|
||||
void Video::set_border_colour(uint8_t colour) {
|
||||
@@ -635,21 +629,21 @@ uint16_t *Video::output_double_high_resolution_mono(uint16_t *target, int start,
|
||||
ram_[row_address + c],
|
||||
};
|
||||
|
||||
target[0] = colours[(source[1] >> 0) & 0x1];
|
||||
target[1] = colours[(source[1] >> 1) & 0x1];
|
||||
target[2] = colours[(source[1] >> 2) & 0x1];
|
||||
target[3] = colours[(source[1] >> 3) & 0x1];
|
||||
target[4] = colours[(source[1] >> 4) & 0x1];
|
||||
target[5] = colours[(source[1] >> 5) & 0x1];
|
||||
target[6] = colours[(source[1] >> 6) & 0x1];
|
||||
target[0] = colours[(source[0] >> 0) & 0x1];
|
||||
target[1] = colours[(source[0] >> 1) & 0x1];
|
||||
target[2] = colours[(source[0] >> 2) & 0x1];
|
||||
target[3] = colours[(source[0] >> 3) & 0x1];
|
||||
target[4] = colours[(source[0] >> 4) & 0x1];
|
||||
target[5] = colours[(source[0] >> 5) & 0x1];
|
||||
target[6] = colours[(source[0] >> 6) & 0x1];
|
||||
|
||||
target[7] = colours[(source[0] >> 0) & 0x1];
|
||||
target[8] = colours[(source[0] >> 1) & 0x1];
|
||||
target[9] = colours[(source[0] >> 2) & 0x1];
|
||||
target[10] = colours[(source[0] >> 3) & 0x1];
|
||||
target[11] = colours[(source[0] >> 4) & 0x1];
|
||||
target[12] = colours[(source[0] >> 5) & 0x1];
|
||||
target[13] = colours[(source[0] >> 6) & 0x1];
|
||||
target[7] = colours[(source[1] >> 0) & 0x1];
|
||||
target[8] = colours[(source[1] >> 1) & 0x1];
|
||||
target[9] = colours[(source[1] >> 2) & 0x1];
|
||||
target[10] = colours[(source[1] >> 3) & 0x1];
|
||||
target[11] = colours[(source[1] >> 4) & 0x1];
|
||||
target[12] = colours[(source[1] >> 5) & 0x1];
|
||||
target[13] = colours[(source[1] >> 6) & 0x1];
|
||||
|
||||
target += 14;
|
||||
}
|
||||
|
@@ -60,7 +60,7 @@ class Video: public Apple::II::VideoSwitches<Cycles> {
|
||||
Outputs::Display::DisplayType get_display_type() const;
|
||||
|
||||
/// Determines the period until video might autonomously update its interrupt lines.
|
||||
Cycles get_next_sequence_point() const;
|
||||
Cycles next_sequence_point() const;
|
||||
|
||||
/// Sets the Mega II interrupt enable state — 1/4-second and VBL interrupts are
|
||||
/// generated here.
|
||||
@@ -123,8 +123,56 @@ class Video: public Apple::II::VideoSwitches<Cycles> {
|
||||
void advance(Cycles);
|
||||
|
||||
uint8_t new_video_ = 0x01;
|
||||
uint8_t interrupts_ = 0x00;
|
||||
void set_interrupts(uint8_t);
|
||||
|
||||
class Interrupts {
|
||||
public:
|
||||
void add(uint8_t value) {
|
||||
// Taken literally, status accumulates regardless of being enabled,
|
||||
// potentially to be polled, it simply doesn't trigger an interrupt.
|
||||
value_ |= value;
|
||||
test();
|
||||
}
|
||||
|
||||
void clear(uint8_t value) {
|
||||
// Zeroes in bits 5 or 6 clear the respective interrupts.
|
||||
value_ &= value | ~0x60;
|
||||
test();
|
||||
}
|
||||
|
||||
void set_control(uint8_t value) {
|
||||
// Ones in bits 1 or 2 enable the respective interrupts.
|
||||
value_ = (value_ & ~0x6) | (value & 0x6);
|
||||
test();
|
||||
}
|
||||
|
||||
uint8_t status() const {
|
||||
return value_;
|
||||
}
|
||||
|
||||
bool active() const {
|
||||
return value_ & 0x80;
|
||||
}
|
||||
|
||||
private:
|
||||
void test() {
|
||||
value_ &= 0x7f;
|
||||
if((value_ >> 4) & value_ & 0x6) {
|
||||
value_ |= 0x80;
|
||||
}
|
||||
}
|
||||
|
||||
// Overall meaning of value is as per the VGC interrupt register, i.e.
|
||||
//
|
||||
// b7: interrupt status;
|
||||
// b6: 1-second interrupt status;
|
||||
// b5: scan-line interrupt status;
|
||||
// b4: reserved;
|
||||
// b3: reserved;
|
||||
// b2: 1-second interrupt enable;
|
||||
// b1: scan-line interrupt enable;
|
||||
// b0: reserved.
|
||||
uint8_t value_ = 0x00;
|
||||
} interrupts_;
|
||||
|
||||
int cycles_into_frame_ = 0;
|
||||
const uint8_t *ram_ = nullptr;
|
||||
|
@@ -575,7 +575,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
|
||||
video_.run_for(time_until_video_event_);
|
||||
time_since_video_update_ -= time_until_video_event_;
|
||||
time_until_video_event_ = video_.get_next_sequence_point();
|
||||
time_until_video_event_ = video_.next_sequence_point();
|
||||
|
||||
via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !video_.vsync());
|
||||
}
|
||||
@@ -626,7 +626,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
|
||||
forceinline void update_video() {
|
||||
video_.run_for(time_since_video_update_.flush<HalfCycles>());
|
||||
time_until_video_event_ = video_.get_next_sequence_point();
|
||||
time_until_video_event_ = video_.next_sequence_point();
|
||||
}
|
||||
|
||||
Inputs::Mouse &get_mouse() final {
|
||||
|
@@ -170,7 +170,7 @@ bool Video::vsync() {
|
||||
return line >= 353 && line < 356;
|
||||
}
|
||||
|
||||
HalfCycles Video::get_next_sequence_point() {
|
||||
HalfCycles Video::next_sequence_point() {
|
||||
const auto line = (frame_position_ / line_length).as_integral();
|
||||
if(line >= 353 && line < 356) {
|
||||
// Currently in vsync, so get time until start of line 357,
|
||||
|
@@ -80,7 +80,7 @@ class Video {
|
||||
@returns the amount of time until there is next a transition on the
|
||||
vsync signal.
|
||||
*/
|
||||
HalfCycles get_next_sequence_point();
|
||||
HalfCycles next_sequence_point();
|
||||
|
||||
private:
|
||||
DeferredAudio &audio_;
|
||||
|
@@ -395,7 +395,7 @@ bool Video::display_enabled() {
|
||||
return public_state_.display_enable;
|
||||
}
|
||||
|
||||
HalfCycles Video::get_next_sequence_point() {
|
||||
HalfCycles Video::next_sequence_point() {
|
||||
// The next sequence point will be whenever display_enabled, vsync or hsync next changes.
|
||||
|
||||
// Sequence of events within a standard line:
|
||||
|
@@ -68,7 +68,7 @@ class Video {
|
||||
@returns the number of cycles until there is next a change in the hsync,
|
||||
vsync or display_enable outputs.
|
||||
*/
|
||||
HalfCycles get_next_sequence_point();
|
||||
HalfCycles next_sequence_point();
|
||||
|
||||
/*!
|
||||
@returns @c true if the horizontal sync output is currently active; @c false otherwise.
|
||||
|
@@ -370,7 +370,7 @@ void VideoOutput::setup_base_address() {
|
||||
|
||||
// MARK: - Interrupts
|
||||
|
||||
Cycles VideoOutput::get_next_sequence_point() {
|
||||
Cycles VideoOutput::next_sequence_point() {
|
||||
if(output_position_ < real_time_clock_interrupt_1) {
|
||||
return real_time_clock_interrupt_1 - output_position_;
|
||||
}
|
||||
|
@@ -61,7 +61,7 @@ class VideoOutput {
|
||||
|
||||
This result may be mutated by calls to @c write.
|
||||
*/
|
||||
Cycles get_next_sequence_point();
|
||||
Cycles next_sequence_point();
|
||||
|
||||
/*!
|
||||
@returns a bit mask of all interrupts that have been triggered since the last call to get_interrupt().
|
||||
|
@@ -315,7 +315,7 @@ void TimedInterruptSource::run_for(Cycles duration) {
|
||||
update_channel(1, rate_ == InterruptRate::ToneGenerator1, cycles.as<int>());
|
||||
}
|
||||
|
||||
Cycles TimedInterruptSource::get_next_sequence_point() const {
|
||||
Cycles TimedInterruptSource::next_sequence_point() const {
|
||||
// Since both the 1kHz and 50Hz timers are integer dividers of the 1Hz timer, there's no need
|
||||
// to factor that one in when determining the next sequence point for either of those.
|
||||
switch(rate_) {
|
||||
|
@@ -144,7 +144,7 @@ class TimedInterruptSource {
|
||||
|
||||
/// @returns The amount of time from now until the earliest that
|
||||
/// @c get_new_interrupts() _might_ have new interrupts to report.
|
||||
Cycles get_next_sequence_point() const;
|
||||
Cycles next_sequence_point() const;
|
||||
|
||||
private:
|
||||
static constexpr Cycles clock_rate{250000};
|
||||
|
@@ -467,7 +467,7 @@ void Nick::set_output_type(OutputType type, bool force_flush) {
|
||||
|
||||
// MARK: - Sequence points.
|
||||
|
||||
Cycles Nick::get_next_sequence_point() const {
|
||||
Cycles Nick::next_sequence_point() const {
|
||||
constexpr int load_point = 16; // i.e. 16 cycles after the start of the line, the
|
||||
// interrupt line may change. That is, after the
|
||||
// second byte of the mode line has been read.
|
||||
|
@@ -38,7 +38,7 @@ class Nick {
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const;
|
||||
|
||||
/// @returns The amount of time until the next potential change in interrupt output.
|
||||
Cycles get_next_sequence_point() const;
|
||||
Cycles next_sequence_point() const;
|
||||
|
||||
/*!
|
||||
@returns The current state of the interrupt line — @c true for active;
|
||||
|
@@ -315,7 +315,7 @@ template <Timing timing> class Video {
|
||||
/*!
|
||||
@returns The amount of time until the next change in the interrupt line, that being the only internally-observeable output.
|
||||
*/
|
||||
HalfCycles get_next_sequence_point() {
|
||||
HalfCycles next_sequence_point() {
|
||||
constexpr auto timings = get_timings();
|
||||
|
||||
// Is the frame still ahead of this interrupt?
|
||||
|
31
Numeric/Carry.hpp
Normal file
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 */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
4281572E2AA0334300E16AA1 /* Carry.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Carry.hpp; sourceTree = "<group>"; };
|
||||
428168372A16C25C008ECD27 /* LineLayout.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = LineLayout.hpp; sourceTree = "<group>"; };
|
||||
428168392A37AFB4008ECD27 /* DispatcherTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DispatcherTests.mm; sourceTree = "<group>"; };
|
||||
42AD552E2A0C4D5000ACE410 /* 68000.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000.hpp; sourceTree = "<group>"; };
|
||||
@@ -3313,6 +3314,7 @@
|
||||
4B66E1A8297719270057ED0F /* NumericCoder.hpp */,
|
||||
4BB5B995281B1D3E00522DA9 /* RegisterSizes.hpp */,
|
||||
4BFEA2F12682A90200EBF94C /* Sizes.hpp */,
|
||||
4281572E2AA0334300E16AA1 /* Carry.hpp */,
|
||||
);
|
||||
name = Numeric;
|
||||
path = ../../Numeric;
|
||||
@@ -6539,6 +6541,10 @@
|
||||
GCC_WARN_UNKNOWN_PRAGMAS = YES;
|
||||
GCC_WARN_UNUSED_LABEL = YES;
|
||||
INFOPLIST_FILE = "Clock Signal/Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(LD_RUNPATH_SEARCH_PATHS_$(IS_MACCATALYST))",
|
||||
/System/Library/PrivateFrameworks/Swift/libswiftCore.dylib,
|
||||
);
|
||||
MTL_TREAT_WARNINGS_AS_ERRORS = YES;
|
||||
OTHER_CPLUSPLUSFLAGS = (
|
||||
"$(OTHER_CFLAGS)",
|
||||
@@ -6580,6 +6586,10 @@
|
||||
GCC_WARN_UNKNOWN_PRAGMAS = YES;
|
||||
GCC_WARN_UNUSED_LABEL = YES;
|
||||
INFOPLIST_FILE = "Clock Signal/Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(LD_RUNPATH_SEARCH_PATHS_$(IS_MACCATALYST))",
|
||||
/System/Library/PrivateFrameworks/Swift/libswiftCore.dylib,
|
||||
);
|
||||
MTL_TREAT_WARNINGS_AS_ERRORS = YES;
|
||||
OTHER_CPLUSPLUSFLAGS = (
|
||||
"$(OTHER_CFLAGS)",
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1400"
|
||||
version = "1.3">
|
||||
version = "1.8">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
|
@@ -14,20 +14,27 @@
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "6502Selector.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
struct StopException {};
|
||||
|
||||
struct BusHandler: public CPU::MOS6502Esque::BusHandler<uint32_t> {
|
||||
// Use a map to store RAM contents, in order to preserve initialised state.
|
||||
std::unordered_map<uint32_t, uint8_t> ram;
|
||||
std::unordered_map<uint32_t, uint8_t> inventions;
|
||||
template <CPU::MOS6502Esque::Type type>
|
||||
struct BusHandler: public CPU::MOS6502Esque::BusHandlerT<type> {
|
||||
using AddressType = typename CPU::MOS6502Esque::BusHandlerT<type>::AddressType;
|
||||
|
||||
Cycles perform_bus_operation(CPU::MOS6502Esque::BusOperation operation, uint32_t address, uint8_t *value) {
|
||||
// Use a map to store RAM contents, in order to preserve initialised state.
|
||||
std::unordered_map<AddressType, uint8_t> ram;
|
||||
std::unordered_map<AddressType, uint8_t> inventions;
|
||||
|
||||
Cycles perform_bus_operation(CPU::MOS6502Esque::BusOperation operation, AddressType address, uint8_t *value) {
|
||||
// Record the basics of the operation.
|
||||
auto &cycle = cycles.emplace_back();
|
||||
cycle.operation = operation;
|
||||
cycle.extended_bus = processor.get_extended_bus_output();
|
||||
if constexpr (has_extended_bus_output(type)) {
|
||||
cycle.extended_bus = processor.get_extended_bus_output();
|
||||
}
|
||||
|
||||
// Perform the operation, and fill in the cycle's value.
|
||||
using BusOperation = CPU::MOS6502Esque::BusOperation;
|
||||
@@ -92,25 +99,27 @@ struct BusHandler: public CPU::MOS6502Esque::BusHandler<uint32_t> {
|
||||
allow_pc_repetition = opcode == 0x54 || opcode == 0x44;
|
||||
|
||||
using Register = CPU::MOS6502Esque::Register;
|
||||
const uint32_t pc =
|
||||
processor.value_of(Register::ProgramCounter) |
|
||||
(processor.value_of(Register::ProgramBank) << 16);
|
||||
const auto pc =
|
||||
AddressType(
|
||||
processor.value_of(Register::ProgramCounter) |
|
||||
(processor.value_of(Register::ProgramBank) << 16)
|
||||
);
|
||||
inventions[pc] = ram[pc] = opcode;
|
||||
}
|
||||
|
||||
int pc_overshoot = 0;
|
||||
std::optional<uint32_t> initial_pc;
|
||||
std::optional<AddressType> initial_pc;
|
||||
bool allow_pc_repetition = false;
|
||||
|
||||
struct Cycle {
|
||||
CPU::MOS6502Esque::BusOperation operation;
|
||||
std::optional<uint32_t> address;
|
||||
std::optional<AddressType> address;
|
||||
std::optional<uint8_t> value;
|
||||
int extended_bus;
|
||||
int extended_bus = 0;
|
||||
};
|
||||
std::vector<Cycle> cycles;
|
||||
|
||||
CPU::WDC65816::Processor<BusHandler, false> processor;
|
||||
CPU::MOS6502Esque::Processor<type, BusHandler<type>, false> processor;
|
||||
|
||||
BusHandler() : processor(*this) {
|
||||
// Never run the official reset procedure.
|
||||
@@ -118,21 +127,24 @@ struct BusHandler: public CPU::MOS6502Esque::BusHandler<uint32_t> {
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Processor> void print_registers(FILE *file, const Processor &processor, int pc_offset) {
|
||||
template <bool has_emulation, typename Processor> void print_registers(FILE *file, const Processor &processor, int pc_offset) {
|
||||
using Register = CPU::MOS6502Esque::Register;
|
||||
fprintf(file, "\"pc\": %d, ", (processor.value_of(Register::ProgramCounter) + pc_offset) & 65535);
|
||||
fprintf(file, "\"s\": %d, ", processor.value_of(Register::StackPointer));
|
||||
fprintf(file, "\"p\": %d, ", processor.value_of(Register::Flags));
|
||||
fprintf(file, "\"a\": %d, ", processor.value_of(Register::A));
|
||||
fprintf(file, "\"x\": %d, ", processor.value_of(Register::X));
|
||||
fprintf(file, "\"y\": %d, ", processor.value_of(Register::Y));
|
||||
fprintf(file, "\"dbr\": %d, ", processor.value_of(Register::DataBank));
|
||||
fprintf(file, "\"d\": %d, ", processor.value_of(Register::Direct));
|
||||
fprintf(file, "\"pbr\": %d, ", processor.value_of(Register::ProgramBank));
|
||||
fprintf(file, "\"e\": %d, ", processor.value_of(Register::EmulationFlag));
|
||||
fprintf(file, "\"p\": %d, ", processor.value_of(Register::Flags));
|
||||
if constexpr (has_emulation) {
|
||||
fprintf(file, "\"dbr\": %d, ", processor.value_of(Register::DataBank));
|
||||
fprintf(file, "\"d\": %d, ", processor.value_of(Register::Direct));
|
||||
fprintf(file, "\"pbr\": %d, ", processor.value_of(Register::ProgramBank));
|
||||
fprintf(file, "\"e\": %d, ", processor.value_of(Register::EmulationFlag));
|
||||
}
|
||||
}
|
||||
|
||||
void print_ram(FILE *file, const std::unordered_map<uint32_t, uint8_t> &data) {
|
||||
template <typename IntT>
|
||||
void print_ram(FILE *file, const std::unordered_map<IntT, uint8_t> &data) {
|
||||
fprintf(file, "\"ram\": [");
|
||||
bool is_first = true;
|
||||
for(const auto &pair: data) {
|
||||
@@ -143,30 +155,30 @@ void print_ram(FILE *file, const std::unordered_map<uint32_t, uint8_t> &data) {
|
||||
fprintf(file, "]");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - New test generator.
|
||||
|
||||
@interface TestGenerator : NSObject
|
||||
@end
|
||||
|
||||
@implementation TestGenerator
|
||||
|
||||
- (void)generate {
|
||||
BusHandler handler;
|
||||
template <CPU::MOS6502Esque::Type type> void generate() {
|
||||
BusHandler<type> handler;
|
||||
constexpr bool has_emulation = has(type, CPU::MOS6502Esque::Register::EmulationFlag);
|
||||
|
||||
NSString *const tempDir = NSTemporaryDirectory();
|
||||
NSLog(@"Outputting to %@", tempDir);
|
||||
|
||||
for(int operation = 0; operation < 512; operation++) {
|
||||
for(int operation = 0; operation < (has_emulation ? 512 : 256); operation++) {
|
||||
// Make tests repeatable, at least for any given instance of
|
||||
// the runtime.
|
||||
srand(65816 + operation);
|
||||
constexpr auto type_offset = int(CPU::MOS6502Esque::Type::TWDC65816) - int(type);
|
||||
srand(65816 + operation + type_offset);
|
||||
|
||||
const bool is_emulated = operation & 256;
|
||||
const uint8_t opcode = operation & 255;
|
||||
|
||||
NSString *const targetName = [NSString stringWithFormat:@"%@%02x.%c.json", tempDir, opcode, is_emulated ? 'e' : 'n'];
|
||||
NSString *const targetName =
|
||||
has_emulation ?
|
||||
[NSString stringWithFormat:@"%@%02x.%c.json", tempDir, opcode, is_emulated ? 'e' : 'n'] :
|
||||
[NSString stringWithFormat:@"%@%02x.json", tempDir, opcode];
|
||||
FILE *const target = fopen(targetName.UTF8String, "wt");
|
||||
|
||||
bool is_first_test = true;
|
||||
@@ -186,21 +198,28 @@ void print_ram(FILE *file, const std::unordered_map<uint32_t, uint8_t> &data) {
|
||||
handler.processor.set_value_of(Register::Y, rand() >> 8);
|
||||
handler.processor.set_value_of(Register::ProgramCounter, rand() >> 8);
|
||||
handler.processor.set_value_of(Register::StackPointer, rand() >> 8);
|
||||
handler.processor.set_value_of(Register::DataBank, rand() >> 8);
|
||||
handler.processor.set_value_of(Register::ProgramBank, rand() >> 8);
|
||||
handler.processor.set_value_of(Register::Direct, rand() >> 8);
|
||||
|
||||
// ... except for emulation mode, which is a given.
|
||||
// And is set last to ensure proper internal state is applied.
|
||||
handler.processor.set_value_of(Register::EmulationFlag, is_emulated);
|
||||
if(has_emulation) {
|
||||
handler.processor.set_value_of(Register::DataBank, rand() >> 8);
|
||||
handler.processor.set_value_of(Register::ProgramBank, rand() >> 8);
|
||||
handler.processor.set_value_of(Register::Direct, rand() >> 8);
|
||||
|
||||
// ... except for emulation mode, which is a given.
|
||||
// And is set last to ensure proper internal state is applied.
|
||||
handler.processor.set_value_of(Register::EmulationFlag, is_emulated);
|
||||
}
|
||||
|
||||
// Establish the opcode.
|
||||
handler.setup(opcode);
|
||||
|
||||
// Dump initial state.
|
||||
fprintf(target, "{ \"name\": \"%02x %c %d\", ", opcode, is_emulated ? 'e' : 'n', test + 1);
|
||||
if(has_emulation) {
|
||||
fprintf(target, "{ \"name\": \"%02x %c %d\", ", opcode, is_emulated ? 'e' : 'n', test + 1);
|
||||
} else {
|
||||
fprintf(target, "{ \"name\": \"%02x %d\", ", opcode, test + 1);
|
||||
}
|
||||
fprintf(target, "\"initial\": {");
|
||||
print_registers(target, handler.processor, 0);
|
||||
print_registers<has_emulation>(target, handler.processor, 0);
|
||||
|
||||
// Run to the second opcode fetch.
|
||||
try {
|
||||
@@ -212,7 +231,7 @@ void print_ram(FILE *file, const std::unordered_map<uint32_t, uint8_t> &data) {
|
||||
|
||||
// Dump final state.
|
||||
fprintf(target, "}, \"final\": {");
|
||||
print_registers(target, handler.processor, handler.pc_overshoot);
|
||||
print_registers<has_emulation>(target, handler.processor, handler.pc_overshoot);
|
||||
print_ram(target, handler.ram);
|
||||
fprintf(target, "}, ");
|
||||
|
||||
@@ -247,12 +266,6 @@ void print_ram(FILE *file, const std::unordered_map<uint32_t, uint8_t> &data) {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
using ExtendedBusOutput = CPU::WDC65816::ExtendedBusOutput;
|
||||
const bool emulation = cycle.extended_bus & ExtendedBusOutput::Emulation;
|
||||
const bool memory_size = cycle.extended_bus & ExtendedBusOutput::MemorySize;
|
||||
const bool index_size = cycle.extended_bus & ExtendedBusOutput::IndexSize;
|
||||
const bool memory_lock = cycle.extended_bus & ExtendedBusOutput::MemoryLock;
|
||||
|
||||
fprintf(target, "[");
|
||||
if(cycle.address) {
|
||||
fprintf(target, "%d, ", *cycle.address);
|
||||
@@ -264,16 +277,31 @@ void print_ram(FILE *file, const std::unordered_map<uint32_t, uint8_t> &data) {
|
||||
} else {
|
||||
fprintf(target, "null, ");
|
||||
}
|
||||
fprintf(target, "\"%c%c%c%c%c%c%c%c\"]",
|
||||
vda ? 'd' : '-',
|
||||
vpa ? 'p' : '-',
|
||||
vpb ? 'v' : '-',
|
||||
wait ? '-' : (read ? 'r' : 'w'),
|
||||
wait ? '-' : (emulation ? 'e' : '-'),
|
||||
wait ? '-' : (memory_size ? 'm' : '-'),
|
||||
wait ? '-' : (index_size ? 'x' : '-'),
|
||||
wait ? '-' : (memory_lock ? 'l' : '-')
|
||||
);
|
||||
|
||||
if(has_emulation) {
|
||||
using ExtendedBusOutput = CPU::WDC65816::ExtendedBusOutput;
|
||||
const bool emulation = cycle.extended_bus & ExtendedBusOutput::Emulation;
|
||||
const bool memory_size = cycle.extended_bus & ExtendedBusOutput::MemorySize;
|
||||
const bool index_size = cycle.extended_bus & ExtendedBusOutput::IndexSize;
|
||||
const bool memory_lock = cycle.extended_bus & ExtendedBusOutput::MemoryLock;
|
||||
|
||||
fprintf(target, "\"%c%c%c%c%c%c%c%c\"]",
|
||||
vda ? 'd' : '-',
|
||||
vpa ? 'p' : '-',
|
||||
vpb ? 'v' : '-',
|
||||
wait ? '-' : (read ? 'r' : 'w'),
|
||||
wait ? '-' : (emulation ? 'e' : '-'),
|
||||
wait ? '-' : (memory_size ? 'm' : '-'),
|
||||
wait ? '-' : (index_size ? 'x' : '-'),
|
||||
wait ? '-' : (memory_lock ? 'l' : '-')
|
||||
);
|
||||
} else {
|
||||
if(read) {
|
||||
fprintf(target, "\"read\"]");
|
||||
} else {
|
||||
fprintf(target, "\"write\"]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Terminate object.
|
||||
@@ -285,7 +313,7 @@ void print_ram(FILE *file, const std::unordered_map<uint32_t, uint8_t> &data) {
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
}
|
||||
|
||||
// MARK: - Existing test evaluator.
|
||||
|
||||
@@ -296,7 +324,7 @@ void print_ram(FILE *file, const std::unordered_map<uint32_t, uint8_t> &data) {
|
||||
|
||||
// A generator for tests; not intended to be a permanent fixture.
|
||||
//- (void)testGenerate {
|
||||
// [[[TestGenerator alloc] init] generate];
|
||||
// generate<CPU::MOS6502Esque::Type::TWDC65816>();
|
||||
//}
|
||||
|
||||
@end
|
||||
|
@@ -685,7 +685,7 @@ void print_transactions(FILE *target, const std::vector<Transaction> &transactio
|
||||
} else {
|
||||
printf("\nAll failing operations:\n");
|
||||
for(const auto operation: failing_operations) {
|
||||
printf("%d,\n", int(operation));
|
||||
printf("%s,\n", InstructionSet::M68k::to_string(operation));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -124,7 +124,7 @@ struct VideoTester {
|
||||
display_enable = _video->display_enabled();
|
||||
vsync = _video->vsync();
|
||||
hsync = _video->hsync();
|
||||
next_event = _video->get_next_sequence_point();
|
||||
next_event = _video->next_sequence_point();
|
||||
} else {
|
||||
NSAssert(display_enable == _video->display_enabled(), @"Unannounced change in display enabled at cycle %zu [%d before next sequence point]", c, next_event.as<int>());
|
||||
NSAssert(vsync == _video->vsync(), @"Unannounced change in vsync at cycle %zu [%d before next sequence point]", c, next_event.as<int>());
|
||||
|
@@ -11,10 +11,10 @@ import XCTest
|
||||
|
||||
class BCDTest: XCTestCase, CSTestMachineTrapHandler {
|
||||
|
||||
func testBCD() {
|
||||
func testBCD(processor: CSTestMachine6502Processor) {
|
||||
if let filename = Bundle(for: type(of: self)).path(forResource: "BCDTEST_beeb", ofType: nil) {
|
||||
if let bcdTest = try? Data(contentsOf: URL(fileURLWithPath: filename)) {
|
||||
let machine = CSTestMachine6502(processor: .processor6502)
|
||||
let machine = CSTestMachine6502(processor: processor)
|
||||
machine.trapHandler = self
|
||||
|
||||
machine.setData(bcdTest, atAddress: 0x2900)
|
||||
@@ -40,13 +40,23 @@ class BCDTest: XCTestCase, CSTestMachineTrapHandler {
|
||||
}
|
||||
}
|
||||
|
||||
func test6502BCD() {
|
||||
testBCD(processor: .processor6502)
|
||||
}
|
||||
|
||||
func test65C02BCD() {
|
||||
testBCD(processor: .processor65C02)
|
||||
}
|
||||
|
||||
private var output: String = ""
|
||||
func testMachine(_ testMachine: CSTestMachine, didTrapAtAddress address: UInt16) {
|
||||
let machine6502 = testMachine as! CSTestMachine6502
|
||||
|
||||
// Only OSWRCH is trapped, so...
|
||||
let character = machine6502.value(for: .A)
|
||||
output.append(Character(UnicodeScalar(character)!))
|
||||
let character = Character(UnicodeScalar(machine6502.value(for: .A))!)
|
||||
if character != "\r" { // The test internally uses \r\n; keep only one of those.
|
||||
output.append(character)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -38,7 +38,7 @@
|
||||
int toggles = 0;
|
||||
int interrupts = 0;
|
||||
uint8_t dividerState = _interruptSource->get_divider_state() & 1;
|
||||
int nextSequencePoint = _interruptSource->get_next_sequence_point().as<int>();
|
||||
int nextSequencePoint = _interruptSource->next_sequence_point().as<int>();
|
||||
|
||||
for(int c = 0; c < 250000 * 5; c++) {
|
||||
// Advance one cycle. Clock is 500,000 Hz.
|
||||
@@ -55,7 +55,7 @@
|
||||
const uint8_t newInterrupts = _interruptSource->get_new_interrupts();
|
||||
if(newInterrupts) {
|
||||
XCTAssertEqual(nextSequencePoint, 0);
|
||||
nextSequencePoint = _interruptSource->get_next_sequence_point().as<int>();
|
||||
nextSequencePoint = _interruptSource->next_sequence_point().as<int>();
|
||||
|
||||
if(newInterrupts & 0x02) {
|
||||
++interrupts;
|
||||
@@ -66,7 +66,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
XCTAssertEqual(nextSequencePoint, _interruptSource->get_next_sequence_point().as<int>(), @"At cycle %d", c);
|
||||
XCTAssertEqual(nextSequencePoint, _interruptSource->next_sequence_point().as<int>(), @"At cycle %d", c);
|
||||
}
|
||||
|
||||
XCTAssertEqual(toggles, int(expectedInterruptsPerSecond * 5.0));
|
||||
|
@@ -46,7 +46,7 @@
|
||||
|
||||
- (void)testInterruptPrediction {
|
||||
// Run for the number of cycles implied by the number of lines.
|
||||
int next_sequence_point = _nick->get_next_sequence_point().as<int>();
|
||||
int next_sequence_point = _nick->next_sequence_point().as<int>();
|
||||
bool last_interrupt_line = _nick->get_interrupt_line();
|
||||
|
||||
for(int c = 0; c < _totalLines*912; c++) {
|
||||
@@ -61,9 +61,9 @@
|
||||
last_interrupt_line = interrupt_line;
|
||||
|
||||
if(!next_sequence_point) {
|
||||
next_sequence_point = _nick->get_next_sequence_point().as<int>();
|
||||
next_sequence_point = _nick->next_sequence_point().as<int>();
|
||||
} else {
|
||||
const int expected_next_sequence_point = _nick->get_next_sequence_point().as<int>();
|
||||
const int expected_next_sequence_point = _nick->next_sequence_point().as<int>();
|
||||
XCTAssertEqual(next_sequence_point, expected_next_sequence_point);
|
||||
}
|
||||
}
|
||||
|
@@ -31,14 +31,14 @@
|
||||
int c = 5;
|
||||
bool vsync = _video->vsync();
|
||||
while(c--) {
|
||||
auto remaining_time_in_state = _video->get_next_sequence_point().as_integral();
|
||||
auto remaining_time_in_state = _video->next_sequence_point().as_integral();
|
||||
NSLog(@"Vsync %@ expected for %@ half-cycles", vsync ? @"on" : @"off", @(remaining_time_in_state));
|
||||
while(remaining_time_in_state--) {
|
||||
XCTAssertEqual(vsync, _video->vsync());
|
||||
_video->run_for(HalfCycles(1));
|
||||
|
||||
if(remaining_time_in_state)
|
||||
XCTAssertEqual(remaining_time_in_state, _video->get_next_sequence_point().as_integral());
|
||||
XCTAssertEqual(remaining_time_in_state, _video->next_sequence_point().as_integral());
|
||||
}
|
||||
vsync ^= true;
|
||||
}
|
||||
|
@@ -36,7 +36,7 @@ using VDP = TI::TMS::TMS9918<TI::TMS::Personality::SMSVDP>;
|
||||
vdp.write(1, 0x8a);
|
||||
|
||||
// Get time until interrupt.
|
||||
auto time_until_interrupt = vdp.get_next_sequence_point().as_integral() - 1;
|
||||
auto time_until_interrupt = vdp.next_sequence_point().as_integral() - 1;
|
||||
|
||||
// Check that an interrupt is now scheduled.
|
||||
NSAssert(time_until_interrupt != HalfCycles::max().as_integral() - 1, @"No interrupt scheduled");
|
||||
@@ -55,7 +55,7 @@ using VDP = TI::TMS::TMS9918<TI::TMS::Personality::SMSVDP>;
|
||||
NSAssert(!vdp.get_interrupt_line(), @"Interrupt wasn't reset by status read");
|
||||
|
||||
// Check interrupt flag isn't set prior to the reported time.
|
||||
time_until_interrupt = vdp.get_next_sequence_point().as_integral() - 1;
|
||||
time_until_interrupt = vdp.next_sequence_point().as_integral() - 1;
|
||||
vdp.run_for(HalfCycles(time_until_interrupt));
|
||||
NSAssert(!vdp.get_interrupt_line(), @"Interrupt line went active early [2]");
|
||||
|
||||
@@ -82,7 +82,7 @@ using VDP = TI::TMS::TMS9918<TI::TMS::Personality::SMSVDP>;
|
||||
|
||||
// Clear the pending interrupt and ask about the next one (i.e. the first one).
|
||||
vdp.read(1);
|
||||
auto time_until_interrupt = vdp.get_next_sequence_point().as_integral() - 1;
|
||||
auto time_until_interrupt = vdp.next_sequence_point().as_integral() - 1;
|
||||
|
||||
// Check that an interrupt is now scheduled.
|
||||
NSAssert(time_until_interrupt != HalfCycles::max().as_integral() - 1, @"No interrupt scheduled");
|
||||
@@ -116,7 +116,7 @@ using VDP = TI::TMS::TMS9918<TI::TMS::Personality::SMSVDP>;
|
||||
|
||||
// Now run through an entire frame...
|
||||
int half_cycles = 262*228*2;
|
||||
auto last_time_until_interrupt = vdp.get_next_sequence_point().as_integral();
|
||||
auto last_time_until_interrupt = vdp.next_sequence_point().as_integral();
|
||||
while(half_cycles--) {
|
||||
// Validate that an interrupt happened if one was expected, and clear anything that's present.
|
||||
NSAssert(vdp.get_interrupt_line() == (last_time_until_interrupt == HalfCycles::max().as_integral()), @"Unexpected interrupt state change; expected %d but got %d; position %d %d @ %d", (last_time_until_interrupt == 0), vdp.get_interrupt_line(), c, with_eof, half_cycles);
|
||||
@@ -129,7 +129,7 @@ using VDP = TI::TMS::TMS9918<TI::TMS::Personality::SMSVDP>;
|
||||
vdp.run_for(HalfCycles(1));
|
||||
|
||||
// Get the time until interrupt.
|
||||
auto time_until_interrupt = vdp.get_next_sequence_point().as_integral();
|
||||
auto time_until_interrupt = vdp.next_sequence_point().as_integral();
|
||||
NSAssert(time_until_interrupt != HalfCycles::max().as_integral() || vdp.get_interrupt_line(), @"No interrupt scheduled; position %d %d @ %d", c, with_eof, half_cycles);
|
||||
NSAssert(time_until_interrupt >= 0, @"Interrupt is scheduled in the past; position %d %d @ %d", c, with_eof, half_cycles);
|
||||
|
||||
|
@@ -15,6 +15,7 @@
|
||||
|
||||
#include "../6502Esque/6502Esque.hpp"
|
||||
#include "../6502Esque/Implementation/LazyFlags.hpp"
|
||||
#include "../../Numeric/Carry.hpp"
|
||||
#include "../../Numeric/RegisterSizes.hpp"
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
@@ -124,6 +125,12 @@ class ProcessorBase: public ProcessorStorage {
|
||||
@returns @c true if the 6502 is jammed; @c false otherwise.
|
||||
*/
|
||||
inline bool is_jammed() const;
|
||||
|
||||
/*!
|
||||
FOR TESTING PURPOSES ONLY: forces the processor into a state where
|
||||
the next thing it intends to do is fetch a new opcode.
|
||||
*/
|
||||
inline void restart_operation_fetch();
|
||||
};
|
||||
|
||||
/*!
|
||||
|
@@ -282,55 +282,103 @@ template <Personality personality, typename T, bool uses_ready_line> void Proces
|
||||
// MARK: - ADC/SBC (and INS)
|
||||
|
||||
case OperationINS:
|
||||
operand_++;
|
||||
++operand_;
|
||||
[[fallthrough]];
|
||||
case OperationSBC:
|
||||
operand_ = ~operand_;
|
||||
|
||||
if(flags_.decimal && has_decimal_mode(personality)) {
|
||||
const uint16_t notCarry = flags_.carry ^ 0x1;
|
||||
const uint16_t decimalResult = uint16_t(a_) - uint16_t(operand_) - notCarry;
|
||||
uint16_t temp16;
|
||||
uint8_t result = a_ + operand_ + flags_.carry;
|
||||
|
||||
temp16 = (a_&0xf) - (operand_&0xf) - notCarry;
|
||||
if(temp16 > 0xf) temp16 -= 0x6;
|
||||
temp16 = (temp16&0x0f) | ((temp16 > 0x0f) ? 0xfff0 : 0x00);
|
||||
temp16 += (a_&0xf0) - (operand_&0xf0);
|
||||
// All flags are set based only on the decimal result.
|
||||
flags_.zero_result = result;
|
||||
flags_.carry = Numeric::carried_out<7>(a_, operand_, result);
|
||||
flags_.negative_result = result;
|
||||
flags_.overflow = (( (result ^ a_) & (result ^ operand_) ) & 0x80) >> 1;
|
||||
|
||||
flags_.overflow = ( ( (decimalResult^a_)&(~decimalResult^operand_) )&0x80) >> 1;
|
||||
flags_.negative_result = uint8_t(temp16);
|
||||
flags_.zero_result = uint8_t(decimalResult);
|
||||
// General SBC logic:
|
||||
//
|
||||
// Because the range of valid numbers starts at 0, any subtraction that should have
|
||||
// caused decimal carry and which requires a digit fix up will definitely have caused
|
||||
// binary carry: the subtraction will have crossed zero and gone into negative numbers.
|
||||
//
|
||||
// So just test for carry (well, actually borrow, which is !carry).
|
||||
|
||||
if(temp16 > 0xff) temp16 -= 0x60;
|
||||
// The bottom nibble is adjusted if there was borrow into the top nibble;
|
||||
// on a 6502 additional borrow isn't propagated but on a 65C02 it is.
|
||||
// This difference affects invalid BCD numbers only — valid numbers will
|
||||
// never be less than -9 so adding 10 will always generate carry.
|
||||
if(!Numeric::carried_in<4>(a_, operand_, result)) {
|
||||
if constexpr (is_65c02(personality)) {
|
||||
result += 0xfa;
|
||||
} else {
|
||||
result = (result & 0xf0) | ((result + 0xfa) & 0xf);
|
||||
}
|
||||
}
|
||||
|
||||
flags_.carry = (temp16 > 0xff) ? 0 : Flag::Carry;
|
||||
a_ = uint8_t(temp16);
|
||||
// The top nibble is adjusted only if there was borrow out of the whole byte.
|
||||
if(!flags_.carry) {
|
||||
result += 0xa0;
|
||||
}
|
||||
|
||||
if(is_65c02(personality)) {
|
||||
a_ = result;
|
||||
|
||||
// fix up in case this was INS.
|
||||
if(cycle == OperationINS) operand_ = ~operand_;
|
||||
|
||||
if constexpr (is_65c02(personality)) {
|
||||
// 65C02 fix: set the N and Z flags based on the final, decimal result.
|
||||
// Read into `operation_` for the sake of reading somewhere; the value isn't
|
||||
// used and INS will write `operand_` back to memory.
|
||||
flags_.set_nz(a_);
|
||||
read_mem(operand_, address_.full);
|
||||
read_mem(operation_, address_.full);
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
operand_ = ~operand_;
|
||||
}
|
||||
[[fallthrough]];
|
||||
|
||||
case OperationADC:
|
||||
if(flags_.decimal && has_decimal_mode(personality)) {
|
||||
const uint16_t decimalResult = uint16_t(a_) + uint16_t(operand_) + uint16_t(flags_.carry);
|
||||
uint8_t result = a_ + operand_ + flags_.carry;
|
||||
flags_.zero_result = result;
|
||||
flags_.carry = Numeric::carried_out<7>(a_, operand_, result);
|
||||
|
||||
uint8_t low_nibble = (a_ & 0xf) + (operand_ & 0xf) + flags_.carry;
|
||||
if(low_nibble >= 0xa) low_nibble = ((low_nibble + 0x6) & 0xf) + 0x10;
|
||||
uint16_t result = uint16_t(a_ & 0xf0) + uint16_t(operand_ & 0xf0) + uint16_t(low_nibble);
|
||||
flags_.negative_result = uint8_t(result);
|
||||
flags_.overflow = (( (result^a_)&(result^operand_) )&0x80) >> 1;
|
||||
if(result >= 0xa0) result += 0x60;
|
||||
// General ADC logic:
|
||||
//
|
||||
// Detecting decimal carry means finding occasions when two digits added together totalled
|
||||
// more than 9. Within each four-bit window that means testing the digit itself and also
|
||||
// testing for carry — e.g. 5 + 5 = 0xA, which is detectable only by the value of the final
|
||||
// digit, but 9 + 9 = 0x18, which is detectable only by spotting the carry.
|
||||
|
||||
flags_.carry = (result >> 8) ? 1 : 0;
|
||||
a_ = uint8_t(result);
|
||||
flags_.zero_result = uint8_t(decimalResult);
|
||||
// Only a single bit of carry can flow from the bottom nibble to the top.
|
||||
//
|
||||
// So if that carry already happened, fix up the bottom without permitting another;
|
||||
// otherwise permit the carry to happen (and check whether carry then rippled out of bit 7).
|
||||
if(Numeric::carried_in<4>(a_, operand_, result)) {
|
||||
result = (result & 0xf0) | ((result + 0x06) & 0x0f);
|
||||
} else if((result & 0xf) > 0x9) {
|
||||
flags_.carry |= result >= 0x100 - 0x6;
|
||||
result += 0x06;
|
||||
}
|
||||
|
||||
if(is_65c02(personality)) {
|
||||
// 6502 quirk: N and V are set before the full result is computed but
|
||||
// after the low nibble has been corrected.
|
||||
flags_.negative_result = result;
|
||||
flags_.overflow = (( (result ^ a_) & (result ^ operand_) ) & 0x80) >> 1;
|
||||
|
||||
// i.e. fix high nibble if there was carry out of bit 7 already, or if the
|
||||
// top nibble is too large (in which case there will be carry after the fix-up).
|
||||
flags_.carry |= result >= 0xa0;
|
||||
if(flags_.carry) {
|
||||
result += 0x60;
|
||||
}
|
||||
|
||||
a_ = result;
|
||||
|
||||
if constexpr (is_65c02(personality)) {
|
||||
// 65C02 fix: N and Z are set correctly based on the final BCD result, at the cost of
|
||||
// an extra cycle.
|
||||
flags_.set_nz(a_);
|
||||
read_mem(operand_, address_.full);
|
||||
break;
|
||||
@@ -342,7 +390,7 @@ template <Personality personality, typename T, bool uses_ready_line> void Proces
|
||||
flags_.carry = (result >> 8)&1;
|
||||
}
|
||||
|
||||
// fix up in case this was INS
|
||||
// fix up in case this was INS.
|
||||
if(cycle == OperationINS) operand_ = ~operand_;
|
||||
continue;
|
||||
|
||||
@@ -728,3 +776,8 @@ void ProcessorBase::set_value_of(Register r, uint16_t value) {
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
void ProcessorBase::restart_operation_fetch() {
|
||||
scheduled_program_counter_ = nullptr;
|
||||
next_bus_operation_ = BusOperation::None;
|
||||
}
|
||||
|
@@ -17,15 +17,15 @@ using namespace CPU::MOS6502;
|
||||
#define Absolute CycleLoadAddressAbsolute
|
||||
#define AbsoluteXr CycleLoadAddressAbsolute, CycleAddXToAddressLow, OperationCorrectAddressHigh
|
||||
#define AbsoluteYr CycleLoadAddressAbsolute, CycleAddYToAddressLow, OperationCorrectAddressHigh
|
||||
#define AbsoluteX CycleLoadAddressAbsolute, CycleAddXToAddressLowRead, OperationCorrectAddressHigh
|
||||
#define AbsoluteY CycleLoadAddressAbsolute, CycleAddYToAddressLowRead, OperationCorrectAddressHigh
|
||||
#define AbsoluteXw CycleLoadAddressAbsolute, CycleAddXToAddressLowRead, OperationCorrectAddressHigh
|
||||
#define AbsoluteYw CycleLoadAddressAbsolute, CycleAddYToAddressLowRead, OperationCorrectAddressHigh
|
||||
#define Zero OperationLoadAddressZeroPage
|
||||
#define ZeroX CycleLoadAddessZeroX
|
||||
#define ZeroY CycleLoadAddessZeroY
|
||||
#define ZeroIndirect OperationLoadAddressZeroPage, CycleFetchAddressLowFromOperand, CycleIncrementOperandFetchAddressHigh
|
||||
#define IndexedIndirect CycleIncrementPCFetchAddressLowFromOperand, CycleAddXToOperandFetchAddressLow, CycleIncrementOperandFetchAddressHigh
|
||||
#define IndirectIndexedr CycleIncrementPCFetchAddressLowFromOperand, CycleIncrementOperandFetchAddressHigh, CycleAddYToAddressLow, OperationCorrectAddressHigh
|
||||
#define IndirectIndexed CycleIncrementPCFetchAddressLowFromOperand, CycleIncrementOperandFetchAddressHigh, CycleAddYToAddressLowRead, OperationCorrectAddressHigh
|
||||
#define IndirectIndexedw CycleIncrementPCFetchAddressLowFromOperand, CycleIncrementOperandFetchAddressHigh, CycleAddYToAddressLowRead, OperationCorrectAddressHigh
|
||||
|
||||
#define Read(...) CycleFetchOperandFromAddress, __VA_ARGS__
|
||||
#define Write(...) __VA_ARGS__, CycleWriteOperandToAddress
|
||||
@@ -42,23 +42,23 @@ using namespace CPU::MOS6502;
|
||||
#define IndirectIndexedRead(op) Program(IndirectIndexedr, Read(op))
|
||||
|
||||
#define AbsoluteWrite(op) Program(Absolute, Write(op))
|
||||
#define AbsoluteXWrite(op) Program(AbsoluteX, Write(op))
|
||||
#define AbsoluteYWrite(op) Program(AbsoluteY, Write(op))
|
||||
#define AbsoluteXWrite(op) Program(AbsoluteXw, Write(op))
|
||||
#define AbsoluteYWrite(op) Program(AbsoluteYw, Write(op))
|
||||
#define ZeroWrite(op) Program(Zero, Write(op))
|
||||
#define ZeroXWrite(op) Program(ZeroX, Write(op))
|
||||
#define ZeroYWrite(op) Program(ZeroY, Write(op))
|
||||
#define ZeroIndirectWrite(op) Program(ZeroIndirect, Write(op))
|
||||
#define IndexedIndirectWrite(op) Program(IndexedIndirect, Write(op))
|
||||
#define IndirectIndexedWrite(op) Program(IndirectIndexed, Write(op))
|
||||
#define IndirectIndexedWrite(op) Program(IndirectIndexedw, Write(op))
|
||||
|
||||
#define AbsoluteReadModifyWrite(...) Program(Absolute, ReadModifyWrite(__VA_ARGS__))
|
||||
#define AbsoluteXReadModifyWrite(...) Program(AbsoluteX, ReadModifyWrite(__VA_ARGS__))
|
||||
#define AbsoluteYReadModifyWrite(...) Program(AbsoluteY, ReadModifyWrite(__VA_ARGS__))
|
||||
#define AbsoluteXReadModifyWrite(...) Program(AbsoluteXw, ReadModifyWrite(__VA_ARGS__))
|
||||
#define AbsoluteYReadModifyWrite(...) Program(AbsoluteYw, ReadModifyWrite(__VA_ARGS__))
|
||||
#define ZeroReadModifyWrite(...) Program(Zero, ReadModifyWrite(__VA_ARGS__))
|
||||
#define ZeroXReadModifyWrite(...) Program(ZeroX, ReadModifyWrite(__VA_ARGS__))
|
||||
#define ZeroYReadModifyWrite(...) Program(ZeroY, ReadModifyWrite(__VA_ARGS__))
|
||||
#define IndexedIndirectReadModifyWrite(...) Program(IndexedIndirect, ReadModifyWrite(__VA_ARGS__))
|
||||
#define IndirectIndexedReadModifyWrite(...) Program(IndirectIndexed, ReadModifyWrite(__VA_ARGS__))
|
||||
#define IndirectIndexedReadModifyWrite(...) Program(IndirectIndexedw, ReadModifyWrite(__VA_ARGS__))
|
||||
|
||||
#define FastAbsoluteXReadModifyWrite(...) Program(AbsoluteXr, ReadModifyWrite(__VA_ARGS__))
|
||||
#define FastAbsoluteYReadModifyWrite(...) Program(AbsoluteYr, ReadModifyWrite(__VA_ARGS__))
|
||||
@@ -68,8 +68,8 @@ using namespace CPU::MOS6502;
|
||||
|
||||
#define ZeroNop() Program(Zero, CycleFetchOperandFromAddress)
|
||||
#define ZeroXNop() Program(ZeroX, CycleFetchOperandFromAddress)
|
||||
#define AbsoluteNop() Program(Absolute)
|
||||
#define AbsoluteXNop() Program(AbsoluteX)
|
||||
#define AbsoluteNop() Program(Absolute, CycleFetchOperandFromAddress)
|
||||
#define AbsoluteXNop() Program(AbsoluteXr, CycleFetchOperandFromAddress)
|
||||
#define ImpliedNop() {OperationMoveToNextProgram}
|
||||
#define ImmediateNop() Program(OperationIncrementPC)
|
||||
|
||||
|
@@ -9,6 +9,7 @@
|
||||
#ifndef _502Selector_h
|
||||
#define _502Selector_h
|
||||
|
||||
#include "6502Esque.hpp"
|
||||
#include "../6502/6502.hpp"
|
||||
#include "../65816/65816.hpp"
|
||||
|
||||
@@ -45,6 +46,32 @@ template <typename BusHandler, bool uses_ready_line> class Processor<Type::TWDC6
|
||||
template <Type processor_type> class BusHandlerT: public BusHandler<uint16_t> {};
|
||||
template <> class BusHandlerT<Type::TWDC65816>: public BusHandler<uint32_t> {};
|
||||
|
||||
/*
|
||||
Query for implemented registers.
|
||||
*/
|
||||
constexpr bool has(Type processor_type, Register r) {
|
||||
switch(r) {
|
||||
case Register::LastOperationAddress:
|
||||
case Register::ProgramCounter:
|
||||
case Register::StackPointer:
|
||||
case Register::Flags:
|
||||
case Register::A:
|
||||
case Register::X:
|
||||
case Register::Y:
|
||||
return true;
|
||||
|
||||
case Register::EmulationFlag:
|
||||
case Register::DataBank:
|
||||
case Register::ProgramBank:
|
||||
case Register::Direct:
|
||||
return processor_type == Type::TWDC65816;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr bool has_extended_bus_output(Type processor_type) {
|
||||
return processor_type == Type::TWDC65816;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif /* _502Selector_h */
|
||||
|
@@ -30,7 +30,10 @@ uint16_t ProcessorBase::value_of(Register r) const {
|
||||
void ProcessorBase::set_value_of(Register r, uint16_t value) {
|
||||
switch (r) {
|
||||
case Register::ProgramCounter: registers_.pc = value; break;
|
||||
case Register::StackPointer: registers_.s.full = value; break;
|
||||
case Register::StackPointer:
|
||||
registers_.s.full = value;
|
||||
if(registers_.emulation_flag) registers_.s.halves.high = 1;
|
||||
break;
|
||||
case Register::Flags: set_flags(uint8_t(value)); break;
|
||||
case Register::A: registers_.a.full = value; break;
|
||||
case Register::X: registers_.x.full = value & registers_.x_mask; break;
|
||||
|
@@ -67,6 +67,9 @@ template <typename BusHandler, bool uses_ready_line> void Processor<BusHandler,
|
||||
last_operation_pc_ = registers_.pc;
|
||||
last_operation_program_bank_ = uint8_t(registers_.program_bank >> 16);
|
||||
memory_lock_ = false;
|
||||
|
||||
// Reenforce the top byte of S if applicable.
|
||||
registers_.s.full = stack_address();
|
||||
} continue;
|
||||
|
||||
case OperationDecode: {
|
||||
@@ -619,7 +622,7 @@ template <typename BusHandler, bool uses_ready_line> void Processor<BusHandler,
|
||||
// (and make reasonable guesses as to the N flag).
|
||||
|
||||
case TXS:
|
||||
registers_.s = registers_.x.full;
|
||||
registers_.s = registers_.x;
|
||||
break;
|
||||
|
||||
case TSX:
|
||||
@@ -668,10 +671,8 @@ template <typename BusHandler, bool uses_ready_line> void Processor<BusHandler,
|
||||
break;
|
||||
|
||||
case TCS:
|
||||
registers_.s.full = registers_.a.full;
|
||||
// No need to worry about byte masking here;
|
||||
// for the stack it's handled as the emulation runs.
|
||||
// Cf. the stack_address() macro.
|
||||
registers_.s = registers_.a;
|
||||
registers_.s.full = stack_address();
|
||||
break;
|
||||
|
||||
case TSC:
|
||||
|
@@ -1131,11 +1131,10 @@ void ProcessorStorage::set_emulation_mode(bool enabled) {
|
||||
set_m_x_flags(true, true);
|
||||
registers_.e_masks[0] = 0xff00;
|
||||
registers_.e_masks[1] = 0x00ff;
|
||||
registers_.s.halves.high = 1;
|
||||
} else {
|
||||
registers_.e_masks[0] = 0x0000;
|
||||
registers_.e_masks[1] = 0xffff;
|
||||
registers_.s.halves.high = 1; // To pretend it was 1 all along; this implementation actually ignores
|
||||
// the top byte while in emulation mode.
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user