mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-23 03:32:32 +00:00
Ensures the MFP requests and receives real-time clocking when needed.
This commit is contained in:
parent
4857ceb3eb
commit
635e18a50d
@ -16,6 +16,17 @@
|
||||
|
||||
using namespace Motorola::MFP68901;
|
||||
|
||||
ClockingHint::Preference MFP68901::preferred_clocking() {
|
||||
// Rule applied: if any timer is actively running and permitted to produce an
|
||||
// interrupt, request real-time running.
|
||||
return
|
||||
(timers_[0].mode >= TimerMode::Delay && interrupt_enable_&Interrupt::TimerA) ||
|
||||
(timers_[1].mode >= TimerMode::Delay && interrupt_enable_&Interrupt::TimerB) ||
|
||||
(timers_[2].mode >= TimerMode::Delay && interrupt_enable_&Interrupt::TimerC) ||
|
||||
(timers_[3].mode >= TimerMode::Delay && interrupt_enable_&Interrupt::TimerD)
|
||||
? ClockingHint::Preference::RealTime : ClockingHint::Preference::JustInTime;
|
||||
}
|
||||
|
||||
uint8_t MFP68901::read(int address) {
|
||||
address &= 0x1f;
|
||||
|
||||
@ -94,9 +105,14 @@ void MFP68901::write(int address, uint8_t value) {
|
||||
|
||||
// Whatever just happened may have affected the state of the interrupt line.
|
||||
update_interrupts();
|
||||
update_clocking_observer();
|
||||
return;
|
||||
}
|
||||
|
||||
const int timer_prescales[] = {
|
||||
1, 4, 10, 16, 50, 64, 100, 200
|
||||
};
|
||||
|
||||
switch(address) {
|
||||
// GPIP block: output and configuration of active edge and direction values.
|
||||
case 0x00:
|
||||
@ -141,26 +157,8 @@ void MFP68901::write(int address, uint8_t value) {
|
||||
} break;
|
||||
case 0x0e:
|
||||
timer_cd_control_ = value;
|
||||
switch(value & 7) {
|
||||
case 0: set_timer_mode(3, TimerMode::Stopped, 1, false); break;
|
||||
case 1: set_timer_mode(3, TimerMode::Delay, 4, false); break;
|
||||
case 2: set_timer_mode(3, TimerMode::Delay, 10, false); break;
|
||||
case 3: set_timer_mode(3, TimerMode::Delay, 16, false); break;
|
||||
case 4: set_timer_mode(3, TimerMode::Delay, 50, false); break;
|
||||
case 5: set_timer_mode(3, TimerMode::Delay, 64, false); break;
|
||||
case 6: set_timer_mode(3, TimerMode::Delay, 100, false); break;
|
||||
case 7: set_timer_mode(3, TimerMode::Delay, 200, false); break;
|
||||
}
|
||||
switch((value >> 4) & 7) {
|
||||
case 0: set_timer_mode(2, TimerMode::Stopped, 1, false); break;
|
||||
case 1: set_timer_mode(2, TimerMode::Delay, 4, false); break;
|
||||
case 2: set_timer_mode(2, TimerMode::Delay, 10, false); break;
|
||||
case 3: set_timer_mode(2, TimerMode::Delay, 16, false); break;
|
||||
case 4: set_timer_mode(2, TimerMode::Delay, 50, false); break;
|
||||
case 5: set_timer_mode(2, TimerMode::Delay, 64, false); break;
|
||||
case 6: set_timer_mode(2, TimerMode::Delay, 100, false); break;
|
||||
case 7: set_timer_mode(2, TimerMode::Delay, 200, false); break;
|
||||
}
|
||||
set_timer_mode(3, (value & 7) ? TimerMode::Delay : TimerMode::Stopped, timer_prescales[value & 7], false);
|
||||
set_timer_mode(2, ((value >> 4) & 7) ? TimerMode::Delay : TimerMode::Stopped, timer_prescales[(value >> 4) & 7], false);
|
||||
break;
|
||||
case 0x0f: case 0x10: case 0x11: case 0x12:
|
||||
set_timer_data(address - 0xf, value);
|
||||
@ -173,6 +171,8 @@ void MFP68901::write(int address, uint8_t value) {
|
||||
case 0x16: LOG("Write: transmitter status"); break;
|
||||
case 0x17: LOG("Write: USART data"); break;
|
||||
}
|
||||
|
||||
update_clocking_observer();
|
||||
}
|
||||
|
||||
void MFP68901::run_for(HalfCycles time) {
|
||||
@ -186,11 +186,20 @@ void MFP68901::run_for(HalfCycles time) {
|
||||
--timers_[c].divisor;
|
||||
if(!timers_[c].divisor) {
|
||||
timers_[c].divisor = timers_[c].prescale;
|
||||
decrement_timer(c);
|
||||
decrement_timer(c, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// const int cycles = int(cycles_left_.flush<Cycles>().as_integral());
|
||||
// for(int c = 0; c < 4; ++c) {
|
||||
// if(timers_[c].mode >= TimerMode::Delay) {
|
||||
// const int dividend = (cycles + timers_[c].prescale - timers_[c].divisor);
|
||||
// const int decrements = dividend / timers_[c].prescale;
|
||||
// timers_[c].divisor = timers_[c].prescale - (dividend % timers_[c].prescale);
|
||||
// if(decrements) decrement_timer(c, decrements);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
HalfCycles MFP68901::get_next_sequence_point() {
|
||||
@ -224,21 +233,23 @@ void MFP68901::set_timer_event_input(int channel, bool value) {
|
||||
|
||||
timers_[channel].event_input = value;
|
||||
if(timers_[channel].mode == TimerMode::EventCount && !value) { /* TODO: which edge is counted? "as defined by the associated Interrupt Channel’s edge bit"? */
|
||||
decrement_timer(channel);
|
||||
decrement_timer(channel, 1);
|
||||
}
|
||||
}
|
||||
|
||||
void MFP68901::decrement_timer(int timer) {
|
||||
--timers_[timer].value;
|
||||
if(!timers_[timer].value) {
|
||||
switch(timer) {
|
||||
case 0: begin_interrupts(Interrupt::TimerA); break;
|
||||
case 1: begin_interrupts(Interrupt::TimerB); break;
|
||||
case 2: begin_interrupts(Interrupt::TimerC); break;
|
||||
case 3: begin_interrupts(Interrupt::TimerD); break;
|
||||
}
|
||||
if(timers_[timer].mode == TimerMode::Delay) {
|
||||
timers_[timer].value = timers_[timer].reload_value;
|
||||
void MFP68901::decrement_timer(int timer, int amount) {
|
||||
while(amount--) {
|
||||
--timers_[timer].value;
|
||||
if(timers_[timer].value < 1) {
|
||||
switch(timer) {
|
||||
case 0: begin_interrupts(Interrupt::TimerA); break;
|
||||
case 1: begin_interrupts(Interrupt::TimerB); break;
|
||||
case 2: begin_interrupts(Interrupt::TimerC); break;
|
||||
case 3: begin_interrupts(Interrupt::TimerD); break;
|
||||
}
|
||||
if(timers_[timer].mode == TimerMode::Delay) {
|
||||
timers_[timer].value += timers_[timer].reload_value; // TODO: properly.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../ClockReceiver/ClockingHintSource.hpp"
|
||||
|
||||
namespace Motorola {
|
||||
namespace MFP68901 {
|
||||
@ -20,7 +21,7 @@ class PortHandler {
|
||||
// TODO: announce changes in output.
|
||||
};
|
||||
|
||||
class MFP68901 {
|
||||
class MFP68901: public ClockingHint::Source {
|
||||
public:
|
||||
uint8_t read(int address);
|
||||
void write(int address, uint8_t value);
|
||||
@ -42,6 +43,9 @@ class MFP68901 {
|
||||
};
|
||||
void set_interrupt_delegate(InterruptDelegate *delegate);
|
||||
|
||||
// ClockingHint::Source.
|
||||
ClockingHint::Preference preferred_clocking() final;
|
||||
|
||||
private:
|
||||
// MARK: - Timers
|
||||
enum class TimerMode {
|
||||
@ -50,7 +54,7 @@ class MFP68901 {
|
||||
void set_timer_mode(int timer, TimerMode, int prescale, bool reset_timer);
|
||||
void set_timer_data(int timer, uint8_t);
|
||||
uint8_t get_timer_data(int timer);
|
||||
void decrement_timer(int timer);
|
||||
void decrement_timer(int timer, int amount);
|
||||
|
||||
struct Timer {
|
||||
TimerMode mode = TimerMode::Stopped;
|
||||
|
@ -278,6 +278,7 @@ class ConcreteMachine:
|
||||
midi_acia_->set_clocking_hint_observer(this);
|
||||
keyboard_acia_->set_clocking_hint_observer(this);
|
||||
ikbd_.set_clocking_hint_observer(this);
|
||||
mfp_->set_clocking_hint_observer(this);
|
||||
|
||||
mfp_->set_interrupt_delegate(this);
|
||||
dma_->set_interrupt_delegate(this);
|
||||
@ -573,22 +574,34 @@ class ConcreteMachine:
|
||||
|
||||
private:
|
||||
forceinline void advance_time(HalfCycles length) {
|
||||
// Advance the relevant counters.
|
||||
cycles_since_audio_update_ += length;
|
||||
mfp_ += length;
|
||||
dma_ += length;
|
||||
|
||||
keyboard_acia_ += length;
|
||||
midi_acia_ += length;
|
||||
if(!may_defer_acias_) {
|
||||
keyboard_acia_.flush();
|
||||
midi_acia_.flush();
|
||||
}
|
||||
|
||||
// Don't even count time for the keyboard unless it has requested it.
|
||||
if(keyboard_needs_clock_) {
|
||||
cycles_since_ikbd_update_ += length;
|
||||
ikbd_.run_for(cycles_since_ikbd_update_.divide(HalfCycles(512)));
|
||||
}
|
||||
|
||||
// Flush anything that needs real-time updating.
|
||||
if(!may_defer_acias_) {
|
||||
keyboard_acia_.flush();
|
||||
midi_acia_.flush();
|
||||
}
|
||||
|
||||
if(mfp_is_realtime_) {
|
||||
mfp_.flush();
|
||||
}
|
||||
|
||||
if(dma_is_realtime_) {
|
||||
dma_.flush();
|
||||
}
|
||||
|
||||
// Update the video output, checking whether a sequence point has been hit.
|
||||
while(length >= cycles_until_video_event_) {
|
||||
length -= cycles_until_video_event_;
|
||||
video_ += cycles_until_video_event_;
|
||||
@ -635,6 +648,8 @@ class ConcreteMachine:
|
||||
// MARK: - Clocking Management.
|
||||
bool may_defer_acias_ = true;
|
||||
bool keyboard_needs_clock_ = false;
|
||||
bool mfp_is_realtime_ = false;
|
||||
bool dma_is_realtime_ = false;
|
||||
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).
|
||||
@ -642,6 +657,7 @@ class ConcreteMachine:
|
||||
(keyboard_acia_.last_valid()->preferred_clocking() != ClockingHint::Preference::RealTime) &&
|
||||
(midi_acia_.last_valid()->preferred_clocking() != ClockingHint::Preference::RealTime);
|
||||
keyboard_needs_clock_ = ikbd_.preferred_clocking() != ClockingHint::Preference::None;
|
||||
mfp_is_realtime_ = mfp_.last_valid()->preferred_clocking() == ClockingHint::Preference::RealTime;
|
||||
}
|
||||
|
||||
// MARK: - GPIP input.
|
||||
|
Loading…
Reference in New Issue
Block a user