diff --git a/ClockReceiver/JustInTime.hpp b/ClockReceiver/JustInTime.hpp index 778ba93e2..865f8ed53 100644 --- a/ClockReceiver/JustInTime.hpp +++ b/ClockReceiver/JustInTime.hpp @@ -46,13 +46,13 @@ template ()); } else { object_.run_for(time_since_update_.template divide(LocalTimeScale(divider))); } } - is_flushed_ = true; } private: diff --git a/Components/6850/6850.cpp b/Components/6850/6850.cpp index 18cd067a4..80a0afdcf 100644 --- a/Components/6850/6850.cpp +++ b/Components/6850/6850.cpp @@ -97,9 +97,13 @@ void ACIA::write(int address, uint8_t value) { void ACIA::run_for(HalfCycles length) { // Transmission. const int transmit_advance = length.as_int(); - const auto write_data_time_remaining = transmit.write_data_time_remaining(); - if(write_data_time_remaining) { + if(transmit.transmission_data_time_remaining()) { + const auto write_data_time_remaining = transmit.write_data_time_remaining(); + + // There's at most one further byte available to enqueue, so a single 'if' + // rather than a 'while' is correct here. It's the responsibilit of the caller + // to ensure run_for lengths are appropriate for longer sequences. if(transmit_advance >= write_data_time_remaining) { if(next_transmission_ != NoValue) { transmit.advance_writer(write_data_time_remaining); @@ -145,6 +149,9 @@ void ACIA::consider_transmission() { // Output all that. const int total_bits = 1 + data_bits_ + stop_bits_ + (parity_ != Parity::None); + if(!next_transmission_) { + printf(""); + } transmit.write(divider_ * 2, total_bits, transmission); printf("Transmitted %02x [%03x]\n", next_transmission_, transmission); @@ -154,7 +161,14 @@ void ACIA::consider_transmission() { } ClockingHint::Preference ACIA::preferred_clocking() { - return (transmit.write_data_time_remaining() > 0) ? ClockingHint::Preference::JustInTime : ClockingHint::Preference::None; + // Real-time clocking is required if a transmission is ongoing; this is a courtesy for whomever + // is on the receiving end. + if(transmit.transmission_data_time_remaining() > 0) return ClockingHint::Preference::RealTime; + + // TODO: real-time clocking if a process of receiving is ongoing. + + // No clocking required then. + return ClockingHint::Preference::None; } bool ACIA::get_interrupt_line() const { diff --git a/Components/6850/6850.hpp b/Components/6850/6850.hpp index 0ef3da4ff..e901814c4 100644 --- a/Components/6850/6850.hpp +++ b/Components/6850/6850.hpp @@ -62,6 +62,9 @@ class ACIA: public ClockingHint::Source { Serial::Line transmit; Serial::Line request_to_send; + // ClockingHint::Source. + ClockingHint::Preference preferred_clocking() final; + private: int divider_ = 1; enum class Parity { @@ -80,8 +83,6 @@ class ACIA: public ClockingHint::Source { bool interrupt_request_ = false; - ClockingHint::Preference preferred_clocking() final; - HalfCycles transmit_clock_rate_; HalfCycles receive_clock_rate_; }; diff --git a/Components/SerialPort/SerialPort.cpp b/Components/SerialPort/SerialPort.cpp index 31bb1a754..96e06b761 100644 --- a/Components/SerialPort/SerialPort.cpp +++ b/Components/SerialPort/SerialPort.cpp @@ -18,10 +18,16 @@ void Line::advance_writer(int cycles) { remaining_delays_ = std::max(remaining_delays_ - cycles, 0); if(events_.empty()) { write_cycles_since_delegate_call_ += cycles; - if(write_cycles_since_delegate_call_ > 256) update_delegate(level_); + if(transmission_extra_) { + transmission_extra_ -= cycles; + if(transmission_extra_ <= 0) { + transmission_extra_ = 0; + update_delegate(level_); + } + } } else { while(!events_.empty()) { - if(events_.front().delay < cycles) { + if(events_.front().delay <= cycles) { cycles -= events_.front().delay; write_cycles_since_delegate_call_ += events_.front().delay; const auto old_level = level_; @@ -36,6 +42,12 @@ void Line::advance_writer(int cycles) { if(old_level != level_) { update_delegate(old_level); } + + // Book enough extra time for the read delegate to be posted + // the final bit if one is attached. + if(events_.empty()) { + transmission_extra_ = minimum_write_cycles_for_read_delegate_bit(); + } } else { events_.front().delay -= cycles; write_cycles_since_delegate_call_ += cycles; @@ -51,6 +63,7 @@ void Line::write(bool level) { events_.back().type = level ? Event::SetHigh : Event::SetLow; } else { level_ = level; + transmission_extra_ = minimum_write_cycles_for_read_delegate_bit(); } } @@ -72,6 +85,10 @@ int Line::write_data_time_remaining() { return remaining_delays_; } +int Line::transmission_data_time_remaining() { + return remaining_delays_ + transmission_extra_; +} + void Line::reset_writing() { remaining_delays_ = 0; events_.clear(); @@ -119,3 +136,8 @@ void Line::update_delegate(bool level) { } time_left_in_bit_ -= time_left; } + +int Line::minimum_write_cycles_for_read_delegate_bit() { + if(!read_delegate_) return 0; + return 1 + (read_delegate_bit_length_ * static_cast(clock_rate_)).get(); +} diff --git a/Components/SerialPort/SerialPort.hpp b/Components/SerialPort/SerialPort.hpp index 611ace1dd..29100e279 100644 --- a/Components/SerialPort/SerialPort.hpp +++ b/Components/SerialPort/SerialPort.hpp @@ -45,6 +45,10 @@ class Line { /// @returns the number of cycles until currently enqueued write data is exhausted. int write_data_time_remaining(); + /// @returns the number of cycles left until it is guaranteed that a passive reader + /// has received all currently-enqueued bits. + int transmission_data_time_remaining(); + /// Eliminates all future write states, leaving the output at whatever it is now. void reset_writing(); @@ -74,6 +78,7 @@ class Line { }; std::vector events_; int remaining_delays_ = 0; + int transmission_extra_ = 0; bool level_ = false; int clock_rate_ = 0; @@ -86,6 +91,7 @@ class Line { } read_delegate_phase_ = ReadDelegatePhase::WaitingForZero; void update_delegate(bool level); + int minimum_write_cycles_for_read_delegate_bit(); }; /*! diff --git a/Machines/AtariST/AtariST.cpp b/Machines/AtariST/AtariST.cpp index 0c783541b..efc455bc1 100644 --- a/Machines/AtariST/AtariST.cpp +++ b/Machines/AtariST/AtariST.cpp @@ -64,7 +64,8 @@ using Target = Analyser::Static::Target; class ConcreteMachine: public Atari::ST::Machine, public CPU::MC68000::BusHandler, - public CRTMachine::Machine { + public CRTMachine::Machine, + public ClockingHint::Observer { public: ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : mc68000_(*this), @@ -99,6 +100,9 @@ class ConcreteMachine: memory_map_[0xfa] = memory_map_[0xfb] = BusDevice::Cartridge; memory_map_[0xff] = BusDevice::IO; + + midi_acia_->set_clocking_hint_observer(this); + keyboard_acia_->set_clocking_hint_observer(this); } ~ConcreteMachine() { @@ -377,8 +381,13 @@ class ConcreteMachine: forceinline void advance_time(HalfCycles length) { cycles_since_audio_update_ += length; mfp_ += length; + keyboard_acia_ += length; midi_acia_ += length; + if(!may_defer_acias_) { + keyboard_acia_.flush(); + midi_acia_.flush(); + } while(length >= cycles_until_video_event_) { length -= cycles_until_video_event_; @@ -419,6 +428,15 @@ class ConcreteMachine: MostlyRAM, RAM, ROM, Cartridge, IO, Unassigned }; BusDevice memory_map_[256]; + + bool may_defer_acias_ = true; + void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) final { + // This is being called by one of the components; avoid any time flushing here as that's + // already dealt with (and, just to be absolutely sure, to avoid recursive mania). + may_defer_acias_ = + (keyboard_acia_.last_valid()->preferred_clocking() != ClockingHint::Preference::RealTime) && + (midi_acia_.last_valid()->preferred_clocking() != ClockingHint::Preference::RealTime); + } }; }