1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-06-26 10:29:31 +00:00

Ensures good serial line and ACIA behaviour.

Next stop: having the intelligent keyboard react.
This commit is contained in:
Thomas Harte 2019-10-20 22:10:05 -04:00
parent 313aaa8f95
commit cf07982a9b
6 changed files with 70 additions and 9 deletions

View File

@ -46,13 +46,13 @@ template <class T, int divider = 1, class LocalTimeScale = HalfCycles, class Tar
/// Flushes all accumulated time.
void flush() {
if(!is_flushed_) {
is_flushed_ = true;
if(divider == 1) {
object_.run_for(time_since_update_.template flush<TargetTimeScale>());
} else {
object_.run_for(time_since_update_.template divide<TargetTimeScale>(LocalTimeScale(divider)));
}
}
is_flushed_ = true;
}
private:

View File

@ -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 {

View File

@ -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_;
};

View File

@ -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<unsigned int>(clock_rate_)).get<int>();
}

View File

@ -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<Event> 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();
};
/*!

View File

@ -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);
}
};
}