mirror of
https://github.com/TomHarte/CLK.git
synced 2025-01-13 07:30:21 +00:00
Experiment with reads/writes earlier in the transaction.
This commit is contained in:
parent
947e890c59
commit
23f1308231
@ -847,221 +847,227 @@ class ConcreteMachine:
|
|||||||
clock_offset_ = (clock_offset_ + cycle.length) & HalfCycles(7);
|
clock_offset_ = (clock_offset_ + cycle.length) & HalfCycles(7);
|
||||||
z80_.set_wait_line(clock_offset_ >= HalfCycles(2));
|
z80_.set_wait_line(clock_offset_ >= HalfCycles(2));
|
||||||
|
|
||||||
// Update the CRTC once every eight half cycles; aiming for half-cycle 4 as
|
// Float this out as a lambda to allow easy repositioning relative to the CPU activity;
|
||||||
// per the initial seed to the crtc_counter_, but any time in the final four
|
// for now this is largely experimental.
|
||||||
// will do as it's safe to conclude that nobody else has touched video RAM
|
const auto update_subsystems = [&] {
|
||||||
// during that whole window.
|
// Update the CRTC once every eight half cycles; aiming for half-cycle 4 as
|
||||||
crtc_counter_ += cycle.length;
|
// per the initial seed to the crtc_counter_, but any time in the final four
|
||||||
const Cycles crtc_cycles = crtc_counter_.divide_cycles(Cycles(4));
|
// will do as it's safe to conclude that nobody else has touched video RAM
|
||||||
if(crtc_cycles > Cycles(0)) crtc_.run_for(crtc_cycles);
|
// during that whole window.
|
||||||
|
crtc_counter_ += cycle.length;
|
||||||
|
const Cycles crtc_cycles = crtc_counter_.divide_cycles(Cycles(4));
|
||||||
|
if(crtc_cycles > Cycles(0)) crtc_.run_for(crtc_cycles);
|
||||||
|
|
||||||
// Check whether that prompted a change in the interrupt line. If so then date
|
// Check whether that prompted a change in the interrupt line. If so then date
|
||||||
// it to whenever the cycle was triggered.
|
// it to whenever the cycle was triggered.
|
||||||
if(interrupt_timer_.request_has_changed()) z80_.set_interrupt_line(interrupt_timer_.get_request(), -crtc_counter_);
|
if(interrupt_timer_.request_has_changed()) z80_.set_interrupt_line(interrupt_timer_.get_request(), -crtc_counter_);
|
||||||
|
|
||||||
// TODO (in the player, not here): adapt it to accept an input clock rate and
|
// TODO (in the player, not here): adapt it to accept an input clock rate and
|
||||||
// run_for as HalfCycles.
|
// run_for as HalfCycles.
|
||||||
if(!tape_player_is_sleeping_) tape_player_.run_for(cycle.length.as_integral());
|
if(!tape_player_is_sleeping_) tape_player_.run_for(cycle.length.as_integral());
|
||||||
|
|
||||||
// Pump the AY.
|
// Pump the AY.
|
||||||
ay_.run_for(cycle.length);
|
ay_.run_for(cycle.length);
|
||||||
|
|
||||||
if constexpr (has_fdc) {
|
if constexpr (has_fdc) {
|
||||||
// Clock the FDC, if connected, using a lazy scale by two.
|
// Clock the FDC, if connected, using a lazy scale by two.
|
||||||
time_since_fdc_update_ += cycle.length;
|
time_since_fdc_update_ += cycle.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update typing activity.
|
// Update typing activity.
|
||||||
if(typer_) typer_->run_for(cycle.length);
|
if(typer_) typer_->run_for(cycle.length);
|
||||||
|
};
|
||||||
|
|
||||||
// Stop now if no action is strictly required.
|
// Continue only if action strictly required.
|
||||||
if(!cycle.is_terminal()) return HalfCycles(0);
|
if(cycle.is_terminal()) {
|
||||||
|
uint16_t address = cycle.address ? *cycle.address : 0x0000;
|
||||||
|
switch(cycle.operation) {
|
||||||
|
case CPU::Z80::PartialMachineCycle::ReadOpcode:
|
||||||
|
|
||||||
uint16_t address = cycle.address ? *cycle.address : 0x0000;
|
// TODO: just capturing byte reads as below doesn't seem to do that much in terms of acceleration;
|
||||||
switch(cycle.operation) {
|
// I'm not immediately clear whether that's just because the machine still has to sit through
|
||||||
case CPU::Z80::PartialMachineCycle::ReadOpcode:
|
// pilot tone in real time, or just that almost no software uses the ROM loader.
|
||||||
|
if(use_fast_tape_hack_ && address == tape_read_byte_address && read_pointers_[0] == roms_[ROMType::OS].data()) {
|
||||||
|
using Parser = Storage::Tape::ZXSpectrum::Parser;
|
||||||
|
Parser parser(Parser::MachineType::AmstradCPC);
|
||||||
|
|
||||||
// TODO: just capturing byte reads as below doesn't seem to do that much in terms of acceleration;
|
const auto speed = read_pointers_[tape_speed_value_address >> 14][tape_speed_value_address & 16383];
|
||||||
// I'm not immediately clear whether that's just because the machine still has to sit through
|
parser.set_cpc_read_speed(speed);
|
||||||
// pilot tone in real time, or just that almost no software uses the ROM loader.
|
|
||||||
if(use_fast_tape_hack_ && address == tape_read_byte_address && read_pointers_[0] == roms_[ROMType::OS].data()) {
|
|
||||||
using Parser = Storage::Tape::ZXSpectrum::Parser;
|
|
||||||
Parser parser(Parser::MachineType::AmstradCPC);
|
|
||||||
|
|
||||||
const auto speed = read_pointers_[tape_speed_value_address >> 14][tape_speed_value_address & 16383];
|
// Seed with the current pulse; the CPC will have finished the
|
||||||
parser.set_cpc_read_speed(speed);
|
// preceding symbol and be a short way into the pulse that should determine the
|
||||||
|
// first bit of this byte.
|
||||||
|
parser.process_pulse(tape_player_.get_current_pulse());
|
||||||
|
const auto byte = parser.get_byte(tape_player_.get_tape());
|
||||||
|
auto flags = z80_.value_of(CPU::Z80::Register::Flags);
|
||||||
|
|
||||||
// Seed with the current pulse; the CPC will have finished the
|
if(byte) {
|
||||||
// preceding symbol and be a short way into the pulse that should determine the
|
// In A ROM-esque fashion, begin the first pulse after the final one
|
||||||
// first bit of this byte.
|
// that was just consumed.
|
||||||
parser.process_pulse(tape_player_.get_current_pulse());
|
tape_player_.complete_pulse();
|
||||||
const auto byte = parser.get_byte(tape_player_.get_tape());
|
|
||||||
auto flags = z80_.value_of(CPU::Z80::Register::Flags);
|
|
||||||
|
|
||||||
if(byte) {
|
// Update in-memory CRC.
|
||||||
// In A ROM-esque fashion, begin the first pulse after the final one
|
auto crc_value =
|
||||||
// that was just consumed.
|
uint16_t(
|
||||||
tape_player_.complete_pulse();
|
read_pointers_[tape_crc_address >> 14][tape_crc_address & 16383] |
|
||||||
|
(read_pointers_[(tape_crc_address+1) >> 14][(tape_crc_address+1) & 16383] << 8)
|
||||||
|
);
|
||||||
|
|
||||||
// Update in-memory CRC.
|
tape_crc_.set_value(crc_value);
|
||||||
auto crc_value =
|
tape_crc_.add(*byte);
|
||||||
uint16_t(
|
crc_value = tape_crc_.get_value();
|
||||||
read_pointers_[tape_crc_address >> 14][tape_crc_address & 16383] |
|
|
||||||
(read_pointers_[(tape_crc_address+1) >> 14][(tape_crc_address+1) & 16383] << 8)
|
|
||||||
);
|
|
||||||
|
|
||||||
tape_crc_.set_value(crc_value);
|
write_pointers_[tape_crc_address >> 14][tape_crc_address & 16383] = uint8_t(crc_value);
|
||||||
tape_crc_.add(*byte);
|
write_pointers_[(tape_crc_address+1) >> 14][(tape_crc_address+1) & 16383] = uint8_t(crc_value >> 8);
|
||||||
crc_value = tape_crc_.get_value();
|
|
||||||
|
|
||||||
write_pointers_[tape_crc_address >> 14][tape_crc_address & 16383] = uint8_t(crc_value);
|
// Indicate successful byte read.
|
||||||
write_pointers_[(tape_crc_address+1) >> 14][(tape_crc_address+1) & 16383] = uint8_t(crc_value >> 8);
|
z80_.set_value_of(CPU::Z80::Register::A, *byte);
|
||||||
|
flags |= CPU::Z80::Flag::Carry;
|
||||||
|
} else {
|
||||||
|
// TODO: return tape player to previous state and decline to serve.
|
||||||
|
z80_.set_value_of(CPU::Z80::Register::A, 0);
|
||||||
|
flags &= ~CPU::Z80::Flag::Carry;
|
||||||
|
}
|
||||||
|
z80_.set_value_of(CPU::Z80::Register::Flags, flags);
|
||||||
|
|
||||||
// Indicate successful byte read.
|
// RET.
|
||||||
z80_.set_value_of(CPU::Z80::Register::A, *byte);
|
*cycle.value = 0xc9;
|
||||||
flags |= CPU::Z80::Flag::Carry;
|
break;
|
||||||
} else {
|
|
||||||
// TODO: return tape player to previous state and decline to serve.
|
|
||||||
z80_.set_value_of(CPU::Z80::Register::A, 0);
|
|
||||||
flags &= ~CPU::Z80::Flag::Carry;
|
|
||||||
}
|
}
|
||||||
z80_.set_value_of(CPU::Z80::Register::Flags, flags);
|
|
||||||
|
|
||||||
// RET.
|
if constexpr (catches_ssm) {
|
||||||
*cycle.value = 0xc9;
|
ssm_code_ = (ssm_code_ << 8) | read_pointers_[address >> 14][address & 16383];
|
||||||
break;
|
if(ssm_delegate_) {
|
||||||
}
|
if((ssm_code_ & 0xff00ff00) == 0xed00ed00) {
|
||||||
|
const auto code = uint16_t(
|
||||||
|
((ssm_code_ << 8) & 0xff00) | ((ssm_code_ >> 16) & 0x00ff)
|
||||||
|
);
|
||||||
|
|
||||||
if constexpr (catches_ssm) {
|
const auto is_valid = [](uint8_t digit) {
|
||||||
ssm_code_ = (ssm_code_ << 8) | read_pointers_[address >> 14][address & 16383];
|
return
|
||||||
if(ssm_delegate_) {
|
(digit <= 0x3f) ||
|
||||||
if((ssm_code_ & 0xff00ff00) == 0xed00ed00) {
|
(digit >= 0x7f && digit <= 0x9f) ||
|
||||||
const auto code = uint16_t(
|
(digit >= 0xa4 && digit <= 0xa7) ||
|
||||||
((ssm_code_ << 8) & 0xff00) | ((ssm_code_ >> 16) & 0x00ff)
|
(digit >= 0xac && digit <= 0xaf) ||
|
||||||
);
|
(digit >= 0xb4 && digit <= 0xb7) ||
|
||||||
|
(digit >= 0xbc && digit <= 0xbf) ||
|
||||||
|
(digit >= 0xc0 && digit <= 0xfd);
|
||||||
|
};
|
||||||
|
|
||||||
const auto is_valid = [](uint8_t digit) {
|
if(
|
||||||
return
|
is_valid(static_cast<uint8_t>(code)) && is_valid(static_cast<uint8_t>(code >> 8))
|
||||||
(digit <= 0x3f) ||
|
) {
|
||||||
(digit >= 0x7f && digit <= 0x9f) ||
|
ssm_delegate_->perform(code);
|
||||||
(digit >= 0xa4 && digit <= 0xa7) ||
|
ssm_code_ = 0;
|
||||||
(digit >= 0xac && digit <= 0xaf) ||
|
}
|
||||||
(digit >= 0xb4 && digit <= 0xb7) ||
|
} else if((ssm_code_ & 0xffff) == 0xedfe) {
|
||||||
(digit >= 0xbc && digit <= 0xbf) ||
|
ssm_delegate_->perform(0xfffe);
|
||||||
(digit >= 0xc0 && digit <= 0xfd);
|
} else if((ssm_code_ & 0xffff) == 0xedff) {
|
||||||
};
|
ssm_delegate_->perform(0xffff);
|
||||||
|
|
||||||
if(
|
|
||||||
is_valid(static_cast<uint8_t>(code)) && is_valid(static_cast<uint8_t>(code >> 8))
|
|
||||||
) {
|
|
||||||
ssm_delegate_->perform(code);
|
|
||||||
ssm_code_ = 0;
|
|
||||||
}
|
}
|
||||||
} else if((ssm_code_ & 0xffff) == 0xedfe) {
|
|
||||||
ssm_delegate_->perform(0xfffe);
|
|
||||||
} else if((ssm_code_ & 0xffff) == 0xedff) {
|
|
||||||
ssm_delegate_->perform(0xffff);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
[[fallthrough]];
|
||||||
[[fallthrough]];
|
|
||||||
|
|
||||||
case CPU::Z80::PartialMachineCycle::Read:
|
case CPU::Z80::PartialMachineCycle::Read:
|
||||||
*cycle.value = read_pointers_[address >> 14][address & 16383];
|
*cycle.value = read_pointers_[address >> 14][address & 16383];
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CPU::Z80::PartialMachineCycle::Write:
|
case CPU::Z80::PartialMachineCycle::Write:
|
||||||
write_pointers_[address >> 14][address & 16383] = *cycle.value;
|
write_pointers_[address >> 14][address & 16383] = *cycle.value;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CPU::Z80::PartialMachineCycle::Output:
|
case CPU::Z80::PartialMachineCycle::Output:
|
||||||
// Check for a gate array access.
|
// Check for a gate array access.
|
||||||
if((address & 0xc000) == 0x4000) {
|
if((address & 0xc000) == 0x4000) {
|
||||||
write_to_gate_array(*cycle.value);
|
write_to_gate_array(*cycle.value);
|
||||||
}
|
|
||||||
|
|
||||||
// Check for an upper ROM selection
|
|
||||||
if constexpr (has_fdc) {
|
|
||||||
if(!(address&0x2000)) {
|
|
||||||
upper_rom_ = (*cycle.value == 7) ? ROMType::AMSDOS : ROMType::BASIC;
|
|
||||||
if(upper_rom_is_paged_) read_pointers_[3] = roms_[upper_rom_].data();
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Check for a CRTC access
|
// Check for an upper ROM selection
|
||||||
if(!(address & 0x4000)) {
|
if constexpr (has_fdc) {
|
||||||
switch((address >> 8) & 3) {
|
if(!(address&0x2000)) {
|
||||||
case 0: crtc_.select_register(*cycle.value); break;
|
upper_rom_ = (*cycle.value == 7) ? ROMType::AMSDOS : ROMType::BASIC;
|
||||||
case 1: crtc_.set_register(*cycle.value); break;
|
if(upper_rom_is_paged_) read_pointers_[3] = roms_[upper_rom_].data();
|
||||||
default: break;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Check for an 8255 PIO access
|
// Check for a CRTC access
|
||||||
if(!(address & 0x800)) {
|
if(!(address & 0x4000)) {
|
||||||
i8255_.write((address >> 8) & 3, *cycle.value);
|
switch((address >> 8) & 3) {
|
||||||
}
|
case 0: crtc_.select_register(*cycle.value); break;
|
||||||
|
case 1: crtc_.set_register(*cycle.value); break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for an 8255 PIO access
|
||||||
|
if(!(address & 0x800)) {
|
||||||
|
i8255_.write((address >> 8) & 3, *cycle.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if constexpr (has_fdc) {
|
||||||
|
// Check for an FDC access
|
||||||
|
if((address & 0x580) == 0x100) {
|
||||||
|
flush_fdc();
|
||||||
|
fdc_.write(address & 1, *cycle.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for a disk motor access
|
||||||
|
if(!(address & 0x580)) {
|
||||||
|
flush_fdc();
|
||||||
|
fdc_.set_motor_on(!!(*cycle.value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CPU::Z80::PartialMachineCycle::Input:
|
||||||
|
// Default to nothing answering
|
||||||
|
*cycle.value = 0xff;
|
||||||
|
|
||||||
|
// Check for a PIO access
|
||||||
|
if(!(address & 0x800)) {
|
||||||
|
*cycle.value &= i8255_.read((address >> 8) & 3);
|
||||||
|
}
|
||||||
|
|
||||||
if constexpr (has_fdc) {
|
|
||||||
// Check for an FDC access
|
// Check for an FDC access
|
||||||
if((address & 0x580) == 0x100) {
|
if constexpr (has_fdc) {
|
||||||
flush_fdc();
|
if((address & 0x580) == 0x100) {
|
||||||
fdc_.write(address & 1, *cycle.value);
|
flush_fdc();
|
||||||
|
*cycle.value &= fdc_.read(address & 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for a disk motor access
|
// Check for a CRTC access; the below is not a typo, the CRTC can be selected
|
||||||
if(!(address & 0x580)) {
|
// for writing via an input, and will sample whatever happens to be available
|
||||||
flush_fdc();
|
if(!(address & 0x4000)) {
|
||||||
fdc_.set_motor_on(!!(*cycle.value));
|
switch((address >> 8) & 3) {
|
||||||
|
case 0: crtc_.select_register(*cycle.value); break;
|
||||||
|
case 1: crtc_.set_register(*cycle.value); break;
|
||||||
|
case 2: *cycle.value &= crtc_.get_status(); break;
|
||||||
|
case 3: *cycle.value &= crtc_.get_register(); break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CPU::Z80::PartialMachineCycle::Input:
|
// As with the CRTC, the gate array will sample the bus if the address decoding
|
||||||
// Default to nothing answering
|
// implies that it should, unaware of data direction
|
||||||
*cycle.value = 0xff;
|
if((address & 0xc000) == 0x4000) {
|
||||||
|
write_to_gate_array(*cycle.value);
|
||||||
// Check for a PIO access
|
|
||||||
if(!(address & 0x800)) {
|
|
||||||
*cycle.value &= i8255_.read((address >> 8) & 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for an FDC access
|
|
||||||
if constexpr (has_fdc) {
|
|
||||||
if((address & 0x580) == 0x100) {
|
|
||||||
flush_fdc();
|
|
||||||
*cycle.value &= fdc_.read(address & 1);
|
|
||||||
}
|
}
|
||||||
}
|
break;
|
||||||
|
|
||||||
// Check for a CRTC access; the below is not a typo, the CRTC can be selected
|
case CPU::Z80::PartialMachineCycle::Interrupt:
|
||||||
// for writing via an input, and will sample whatever happens to be available
|
// Nothing is loaded onto the bus during an interrupt acknowledge, but
|
||||||
if(!(address & 0x4000)) {
|
// the fact of the acknowledge needs to be posted on to the interrupt timer.
|
||||||
switch((address >> 8) & 3) {
|
*cycle.value = 0xff;
|
||||||
case 0: crtc_.select_register(*cycle.value); break;
|
interrupt_timer_.signal_interrupt_acknowledge();
|
||||||
case 1: crtc_.set_register(*cycle.value); break;
|
break;
|
||||||
case 2: *cycle.value &= crtc_.get_status(); break;
|
|
||||||
case 3: *cycle.value &= crtc_.get_register(); break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// As with the CRTC, the gate array will sample the bus if the address decoding
|
default: break;
|
||||||
// implies that it should, unaware of data direction
|
}
|
||||||
if((address & 0xc000) == 0x4000) {
|
|
||||||
write_to_gate_array(*cycle.value);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CPU::Z80::PartialMachineCycle::Interrupt:
|
|
||||||
// Nothing is loaded onto the bus during an interrupt acknowledge, but
|
|
||||||
// the fact of the acknowledge needs to be posted on to the interrupt timer.
|
|
||||||
*cycle.value = 0xff;
|
|
||||||
interrupt_timer_.signal_interrupt_acknowledge();
|
|
||||||
break;
|
|
||||||
|
|
||||||
default: break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
update_subsystems();
|
||||||
|
|
||||||
// Check whether the interrupt signal has changed the other way.
|
// Check whether the interrupt signal has changed the other way.
|
||||||
if(interrupt_timer_.request_has_changed()) z80_.set_interrupt_line(interrupt_timer_.get_request());
|
if(interrupt_timer_.request_has_changed()) z80_.set_interrupt_line(interrupt_timer_.get_request());
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user