diff --git a/Machines/Sinclair/ZXSpectrum/Video.hpp b/Machines/Sinclair/ZXSpectrum/Video.hpp index dde63b3ed..d96d72c45 100644 --- a/Machines/Sinclair/ZXSpectrum/Video.hpp +++ b/Machines/Sinclair/ZXSpectrum/Video.hpp @@ -225,11 +225,13 @@ template class Video { return time_since_interrupt_ < interrupt_duration; } - int access_delay() const { + int access_delay(HalfCycles offset) const { constexpr auto timings = get_timings(); - if(time_since_interrupt_ < timings.first_delay) return 0; + const int delay_time = (time_since_interrupt_ + offset.as()) % (timings.cycles_per_line * timings.lines_per_frame); - const int time_since = time_since_interrupt_ - timings.first_delay; + if(delay_time < timings.first_delay) return 0; + + const int time_since = delay_time - timings.first_delay; const int lines = time_since / timings.cycles_per_line; if(lines >= 192) return 0; diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp index 71584d8f2..69af74b2f 100644 --- a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp @@ -107,27 +107,36 @@ template class ConcreteMachine: // MARK: - BusHandler forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) { - time_since_audio_update_ += cycle.length; - - video_ += cycle.length; - if(video_.did_flush()) { - z80_.set_interrupt_line(video_.last_valid()->get_interrupt_line()); - } - // Ignore all but terminal cycles. // TODO: I doubt this is correct for timing. - if(!cycle.is_terminal()) return HalfCycles(0); + if(!cycle.is_terminal()) { + advance(cycle.length); + return HalfCycles(0); + } + HalfCycles delay(0); uint16_t address = cycle.address ? *cycle.address : 0x0000; using PartialMachineCycle = CPU::Z80::PartialMachineCycle; switch(cycle.operation) { default: break; case PartialMachineCycle::ReadOpcode: case PartialMachineCycle::Read: + // Apply contention if necessary. + if(is_contended_[address >> 14]) { + delay = video_.last_valid()->access_delay(video_.time_since_flush()); + } + *cycle.value = read_pointers_[address >> 14][address]; break; case PartialMachineCycle::Write: + // Apply contention if necessary. + // For now this causes a video sync up every time any contended area is written to. + // TODO: flush only upon a video-area write. + if(is_contended_[address >> 14]) { + delay = video_->access_delay(HalfCycles(0)); + } + write_pointers_[address >> 14][address] = *cycle.value; break; @@ -211,9 +220,22 @@ template class ConcreteMachine: break; } - return HalfCycles(0); + advance(cycle.length + delay); + return delay; } + private: + void advance(HalfCycles duration) { + time_since_audio_update_ += duration; + + video_ += duration; + if(video_.did_flush()) { + z80_.set_interrupt_line(video_.last_valid()->get_interrupt_line()); + } + } + + public: + // MARK: - Typer // HalfCycles get_typer_delay(const std::string &) const final { // return z80_.get_is_resetting() ? Cycles(7'000'000) : Cycles(0); @@ -246,6 +268,7 @@ template class ConcreteMachine: std::array scratch_; const uint8_t *read_pointers_[4]; uint8_t *write_pointers_[4]; + bool is_contended_[4]; uint8_t port1ffd_ = 0; uint8_t port7ffd_ = 0; @@ -267,31 +290,31 @@ template class ConcreteMachine: switch(port1ffd_ & 0x6) { default: case 0x00: - set_memory(0, &ram_[0 * 16384], &ram_[0 * 16384]); - set_memory(1, &ram_[1 * 16384], &ram_[1 * 16384]); - set_memory(2, &ram_[2 * 16384], &ram_[2 * 16384]); - set_memory(3, &ram_[3 * 16384], &ram_[3 * 16384]); + set_memory(0, &ram_[0 * 16384], &ram_[0 * 16384], false); + set_memory(1, &ram_[1 * 16384], &ram_[1 * 16384], false); + set_memory(2, &ram_[2 * 16384], &ram_[2 * 16384], false); + set_memory(3, &ram_[3 * 16384], &ram_[3 * 16384], false); break; case 0x02: - set_memory(0, &ram_[4 * 16384], &ram_[4 * 16384]); - set_memory(1, &ram_[5 * 16384], &ram_[5 * 16384]); - set_memory(2, &ram_[6 * 16384], &ram_[6 * 16384]); - set_memory(3, &ram_[7 * 16384], &ram_[7 * 16384]); + set_memory(0, &ram_[4 * 16384], &ram_[4 * 16384], true); + set_memory(1, &ram_[5 * 16384], &ram_[5 * 16384], true); + set_memory(2, &ram_[6 * 16384], &ram_[6 * 16384], true); + set_memory(3, &ram_[7 * 16384], &ram_[7 * 16384], true); break; case 0x04: - set_memory(0, &ram_[4 * 16384], &ram_[4 * 16384]); - set_memory(1, &ram_[5 * 16384], &ram_[5 * 16384]); - set_memory(2, &ram_[6 * 16384], &ram_[6 * 16384]); - set_memory(3, &ram_[3 * 16384], &ram_[3 * 16384]); + set_memory(0, &ram_[4 * 16384], &ram_[4 * 16384], true); + set_memory(1, &ram_[5 * 16384], &ram_[5 * 16384], true); + set_memory(2, &ram_[6 * 16384], &ram_[6 * 16384], true); + set_memory(3, &ram_[3 * 16384], &ram_[3 * 16384], false); break; case 0x06: - set_memory(0, &ram_[4 * 16384], &ram_[4 * 16384]); - set_memory(1, &ram_[7 * 16384], &ram_[7 * 16384]); - set_memory(2, &ram_[6 * 16384], &ram_[6 * 16384]); - set_memory(3, &ram_[3 * 16384], &ram_[3 * 16384]); + set_memory(0, &ram_[4 * 16384], &ram_[4 * 16384], true); + set_memory(1, &ram_[7 * 16384], &ram_[7 * 16384], true); + set_memory(2, &ram_[6 * 16384], &ram_[6 * 16384], true); + set_memory(3, &ram_[3 * 16384], &ram_[3 * 16384], false); break; } @@ -300,16 +323,17 @@ template class ConcreteMachine: // Apply standard 128kb-esque mapping (albeit with extra ROM to pick from). const auto rom = &rom_[ (((port1ffd_ >> 1) & 2) | ((port7ffd_ >> 4) & 1)) * 16384]; - set_memory(0, rom, nullptr); + set_memory(0, rom, nullptr, false); - set_memory(1, &ram_[5 * 16384], &ram_[5 * 16384]); - set_memory(2, &ram_[2 * 16384], &ram_[2 * 16384]); + set_memory(1, &ram_[5 * 16384], &ram_[5 * 16384], true); + set_memory(2, &ram_[2 * 16384], &ram_[2 * 16384], false); const auto high_ram = &ram_[(port7ffd_ & 7) * 16384]; - set_memory(3, high_ram, high_ram); + set_memory(3, high_ram, high_ram, (port7ffd_ & 7) >= 4); } - void set_memory(int bank, const uint8_t *read, uint8_t *write) { + void set_memory(int bank, const uint8_t *read, uint8_t *write, bool is_contended) { + is_contended_[bank] = is_contended; read_pointers_[bank] = read - bank*16384; write_pointers_[bank] = (write ? write : scratch_.data()) - bank*16384; }