From 1266bbb224d8bcfe732328e0ed1896d1d6695d22 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 5 Apr 2021 21:02:37 -0400 Subject: [PATCH 1/2] Makes the TMS a sequence-point-generating JustInTimeActor. --- ClockReceiver/ClockReceiver.hpp | 8 ++++++++ ClockReceiver/JustInTime.hpp | 10 +++++++++- Components/9918/9918.cpp | 6 +++--- Components/9918/9918.hpp | 8 ++++---- Machines/ColecoVision/ColecoVision.cpp | 14 +++----------- Machines/MSX/MSX.cpp | 13 +++---------- Machines/MasterSystem/MasterSystem.cpp | 14 +++----------- 7 files changed, 33 insertions(+), 40 deletions(-) diff --git a/ClockReceiver/ClockReceiver.hpp b/ClockReceiver/ClockReceiver.hpp index de2c23345..1a3ddfdb9 100644 --- a/ClockReceiver/ClockReceiver.hpp +++ b/ClockReceiver/ClockReceiver.hpp @@ -10,7 +10,9 @@ #define ClockReceiver_hpp #include "ForceInline.hpp" + #include +#include /* Informal pattern for all classes that run from a clock cycle: @@ -176,6 +178,9 @@ class Cycles: public WrappedInt { public: forceinline constexpr Cycles(IntType l) noexcept : WrappedInt(l) {} forceinline constexpr Cycles() noexcept : WrappedInt() {} + forceinline static constexpr Cycles max() { + return Cycles(std::numeric_limits::max()); + } private: friend WrappedInt; @@ -195,6 +200,9 @@ class HalfCycles: public WrappedInt { public: forceinline constexpr HalfCycles(IntType l) noexcept : WrappedInt(l) {} forceinline constexpr HalfCycles() noexcept : WrappedInt() {} + forceinline static constexpr HalfCycles max() { + return HalfCycles(std::numeric_limits::max()); + } forceinline constexpr HalfCycles(const Cycles &cycles) noexcept : WrappedInt(cycles.as_integral() * 2) {} diff --git a/ClockReceiver/JustInTime.hpp b/ClockReceiver/JustInTime.hpp index 273f79bcd..8d709559c 100644 --- a/ClockReceiver/JustInTime.hpp +++ b/ClockReceiver/JustInTime.hpp @@ -105,7 +105,9 @@ template ::value) { time_until_event_ -= rhs; if(time_until_event_ <= LocalTimeScale(0)) { + time_overrun_ = time_until_event_; flush(); + update_sequence_point(); return true; } } @@ -174,6 +176,12 @@ template get_interrupt_line()); + } time_since_sn76489_update_ += length; // Act only if necessary. @@ -263,7 +265,6 @@ class ConcreteMachine: case 5: *cycle.value = vdp_->read(address); z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line()); - time_until_interrupt_ = vdp_->get_time_until_interrupt(); break; case 7: { @@ -304,7 +305,6 @@ class ConcreteMachine: case 5: vdp_->write(address, *cycle.value); z80_.set_non_maskable_interrupt_line(vdp_->get_interrupt_line()); - time_until_interrupt_ = vdp_->get_time_until_interrupt(); break; case 7: @@ -341,13 +341,6 @@ class ConcreteMachine: } } - if(time_until_interrupt_ > 0) { - time_until_interrupt_ -= length; - if(time_until_interrupt_ <= HalfCycles(0)) { - z80_.set_non_maskable_interrupt_line(true, time_until_interrupt_); - } - } - return penalty; } @@ -408,7 +401,6 @@ class ConcreteMachine: bool joysticks_in_keypad_mode_ = false; HalfCycles time_since_sn76489_update_; - HalfCycles time_until_interrupt_; Analyser::Dynamic::ConfidenceCounter confidence_counter_; int pc_zero_accesses_ = 0; diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp index 4b1ca673d..15b2bf9ef 100644 --- a/Machines/MSX/MSX.cpp +++ b/Machines/MSX/MSX.cpp @@ -419,7 +419,9 @@ class ConcreteMachine: // but otherwise runs without pause. const HalfCycles addition((cycle.operation == CPU::Z80::PartialMachineCycle::ReadOpcode) ? 2 : 0); const HalfCycles total_length = addition + cycle.length; - vdp_ += total_length; + if(vdp_ += total_length) { + z80_.set_interrupt_line(vdp_->get_interrupt_line(), vdp_.last_sequence_point_overrun()); + } time_since_ay_update_ += total_length; memory_slots_[0].cycles_since_update += total_length; memory_slots_[1].cycles_since_update += total_length; @@ -520,7 +522,6 @@ class ConcreteMachine: case 0x98: case 0x99: *cycle.value = vdp_->read(address); z80_.set_interrupt_line(vdp_->get_interrupt_line()); - time_until_interrupt_ = vdp_->get_time_until_interrupt(); break; case 0xa2: @@ -545,7 +546,6 @@ class ConcreteMachine: case 0x98: case 0x99: vdp_->write(address, *cycle.value); z80_.set_interrupt_line(vdp_->get_interrupt_line()); - time_until_interrupt_ = vdp_->get_time_until_interrupt(); break; case 0xa0: case 0xa1: @@ -606,12 +606,6 @@ class ConcreteMachine: if(!tape_player_is_sleeping_) tape_player_.run_for(int(cycle.length.as_integral())); - if(time_until_interrupt_ > 0) { - time_until_interrupt_ -= total_length; - if(time_until_interrupt_ <= HalfCycles(0)) { - z80_.set_interrupt_line(true, time_until_interrupt_); - } - } return addition; } @@ -785,7 +779,6 @@ class ConcreteMachine: uint8_t unpopulated_[8192]; HalfCycles time_since_ay_update_; - HalfCycles time_until_interrupt_; uint8_t key_states_[16]; int selected_key_line_ = 0; diff --git a/Machines/MasterSystem/MasterSystem.cpp b/Machines/MasterSystem/MasterSystem.cpp index f43e999ea..562f48967 100644 --- a/Machines/MasterSystem/MasterSystem.cpp +++ b/Machines/MasterSystem/MasterSystem.cpp @@ -214,7 +214,9 @@ class ConcreteMachine: } forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) { - vdp_ += cycle.length; + if(vdp_ += cycle.length) { + z80_.set_interrupt_line(vdp_->get_interrupt_line(), vdp_.last_sequence_point_overrun()); + } time_since_sn76489_update_ += cycle.length; if(cycle.is_terminal()) { @@ -266,7 +268,6 @@ class ConcreteMachine: case 0x80: case 0x81: *cycle.value = vdp_->read(address); z80_.set_interrupt_line(vdp_->get_interrupt_line()); - time_until_interrupt_ = vdp_->get_time_until_interrupt(); break; case 0xc0: { if(memory_control_ & 0x4) { @@ -330,7 +331,6 @@ class ConcreteMachine: case 0x80: case 0x81: // i.e. ports 0x80–0xbf. vdp_->write(address, *cycle.value); z80_.set_interrupt_line(vdp_->get_interrupt_line()); - time_until_interrupt_ = vdp_->get_time_until_interrupt(); break; case 0xc1: case 0xc0: // i.e. ports 0xc0–0xff. if(has_fm_audio_) { @@ -374,13 +374,6 @@ class ConcreteMachine: } } - if(time_until_interrupt_ > 0) { - time_until_interrupt_ -= cycle.length; - if(time_until_interrupt_ <= HalfCycles(0)) { - z80_.set_interrupt_line(true, time_until_interrupt_); - } - } - // The pause button is debounced and takes effect only one line before pixels // begin; time_until_debounce_ keeps track of the time until then. time_until_debounce_ -= cycle.length; @@ -505,7 +498,6 @@ class ConcreteMachine: bool reset_is_pressed_ = false, pause_is_pressed_ = false; HalfCycles time_since_sn76489_update_; - HalfCycles time_until_interrupt_; HalfCycles time_until_debounce_; uint8_t ram_[8*1024]; From 094d623485314dc644e82a2215bcf458fe421e0c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 5 Apr 2021 21:33:04 -0400 Subject: [PATCH 2/2] Updates unit tests. --- Machines/Apple/AppleIIgs/MemoryMap.hpp | 2 +- .../Clock SignalTests/MasterSystemVDPTests.mm | 26 +++++++++++-------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/Machines/Apple/AppleIIgs/MemoryMap.hpp b/Machines/Apple/AppleIIgs/MemoryMap.hpp index af0d9d5cd..59aee99d3 100644 --- a/Machines/Apple/AppleIIgs/MemoryMap.hpp +++ b/Machines/Apple/AppleIIgs/MemoryMap.hpp @@ -576,7 +576,7 @@ class MemoryMap { if(region.write) { \ region.write[address] = *value; \ const bool _mm_is_shadowed = IsShadowed(map, region, address); \ - map.shadow_base[is_shadowed][(®ion.write[address] - map.ram_base) & map.shadow_mask[_mm_is_shadowed]] = *value; \ + map.shadow_base[_mm_is_shadowed][(®ion.write[address] - map.ram_base) & map.shadow_mask[_mm_is_shadowed]] = *value; \ } // Quick notes on ::IsShadowed contortions: diff --git a/OSBindings/Mac/Clock SignalTests/MasterSystemVDPTests.mm b/OSBindings/Mac/Clock SignalTests/MasterSystemVDPTests.mm index f3e4902f8..a79ab8e8a 100644 --- a/OSBindings/Mac/Clock SignalTests/MasterSystemVDPTests.mm +++ b/OSBindings/Mac/Clock SignalTests/MasterSystemVDPTests.mm @@ -34,10 +34,10 @@ vdp.write(1, 0x8a); // Get time until interrupt. - auto time_until_interrupt = vdp.get_time_until_interrupt().as_integral() - 1; + auto time_until_interrupt = vdp.get_next_sequence_point().as_integral() - 1; // Check that an interrupt is now scheduled. - NSAssert(time_until_interrupt != -2, @"No interrupt scheduled"); + NSAssert(time_until_interrupt != HalfCycles::max().as_integral() - 1, @"No interrupt scheduled"); NSAssert(time_until_interrupt > 0, @"Interrupt is scheduled in the past"); // Check interrupt flag isn't set prior to the reported time. @@ -53,7 +53,7 @@ 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_time_until_interrupt().as_integral() - 1; + time_until_interrupt = vdp.get_next_sequence_point().as_integral() - 1; vdp.run_for(HalfCycles(time_until_interrupt)); NSAssert(!vdp.get_interrupt_line(), @"Interrupt line went active early [2]"); @@ -80,10 +80,10 @@ // Clear the pending interrupt and ask about the next one (i.e. the first one). vdp.read(1); - auto time_until_interrupt = vdp.get_time_until_interrupt().as_integral() - 1; + auto time_until_interrupt = vdp.get_next_sequence_point().as_integral() - 1; // Check that an interrupt is now scheduled. - NSAssert(time_until_interrupt != -2, @"No interrupt scheduled"); + NSAssert(time_until_interrupt != HalfCycles::max().as_integral() - 1, @"No interrupt scheduled"); NSAssert(time_until_interrupt > 0, @"Interrupt is scheduled in the past"); // Check interrupt flag isn't set prior to the reported time. @@ -114,20 +114,24 @@ // Now run through an entire frame... int half_cycles = 262*228*2; - auto last_time_until_interrupt = vdp.get_time_until_interrupt().as_integral(); + auto last_time_until_interrupt = vdp.get_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 == 0), @"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); - vdp.read(1); + 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); + + if(vdp.get_interrupt_line()) { + vdp.read(1); + last_time_until_interrupt = 0; + } vdp.run_for(HalfCycles(1)); // Get the time until interrupt. - auto time_until_interrupt = vdp.get_time_until_interrupt().as_integral(); - NSAssert(time_until_interrupt != -1, @"No interrupt scheduled; position %d %d @ %d", c, with_eof, half_cycles); + auto time_until_interrupt = vdp.get_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); - if(last_time_until_interrupt) { + if(last_time_until_interrupt > 1) { NSAssert( time_until_interrupt == (last_time_until_interrupt - 1), @"Discontinuity found in interrupt prediction; from %@ to %@; position %d %d @ %d",