mirror of https://github.com/TomHarte/CLK.git
Compare commits
12 Commits
a882faa7f6
...
e6724a701a
Author | SHA1 | Date |
---|---|---|
Thomas Harte | e6724a701a | |
Thomas Harte | d90eedfc8c | |
Thomas Harte | 63009d00b4 | |
Thomas Harte | 6a2261d217 | |
Thomas Harte | c3ad2154b5 | |
Thomas Harte | 3d61861737 | |
Thomas Harte | 7545786436 | |
Thomas Harte | a997b6c677 | |
Thomas Harte | 72d4f638aa | |
Thomas Harte | b15ff6d442 | |
Thomas Harte | cb70967971 | |
Thomas Harte | 42aea2663c |
|
@ -336,7 +336,7 @@ class ConcreteMachine:
|
|||
//
|
||||
// The implementation of this is coupled to the ClockRate above, hence its
|
||||
// appearance here.
|
||||
template <int video_divider>
|
||||
template <int video_divider, bool original_speed>
|
||||
void macro_tick() {
|
||||
macro_counter_ -= 24;
|
||||
|
||||
|
@ -349,39 +349,37 @@ class ConcreteMachine:
|
|||
// * timers: 2;
|
||||
// * sound: 1.
|
||||
|
||||
tick_cpu_video<0, video_divider>(); tick_cpu_video<1, video_divider>();
|
||||
tick_cpu_video<2, video_divider>(); tick_floppy();
|
||||
tick_cpu_video<3, video_divider>(); tick_cpu_video<4, video_divider>();
|
||||
tick_cpu_video<5, video_divider>(); tick_floppy();
|
||||
tick_cpu_video<6, video_divider>(); tick_cpu_video<7, video_divider>();
|
||||
tick_cpu_video<8, video_divider>(); tick_floppy();
|
||||
tick_cpu_video<9, video_divider>(); tick_cpu_video<10, video_divider>();
|
||||
tick_cpu_video<11, video_divider>(); tick_floppy();
|
||||
tick_cpu_video<0, video_divider, original_speed>(); tick_cpu_video<1, video_divider, original_speed>();
|
||||
tick_cpu_video<2, video_divider, original_speed>(); tick_floppy();
|
||||
tick_cpu_video<3, video_divider, original_speed>(); tick_cpu_video<4, video_divider, original_speed>();
|
||||
tick_cpu_video<5, video_divider, original_speed>(); tick_floppy();
|
||||
tick_cpu_video<6, video_divider, original_speed>(); tick_cpu_video<7, video_divider, original_speed>();
|
||||
tick_cpu_video<8, video_divider, original_speed>(); tick_floppy();
|
||||
tick_cpu_video<9, video_divider, original_speed>(); tick_cpu_video<10, video_divider, original_speed>();
|
||||
tick_cpu_video<11, video_divider, original_speed>(); tick_floppy();
|
||||
tick_timers();
|
||||
|
||||
tick_cpu_video<12, video_divider>(); tick_cpu_video<13, video_divider>();
|
||||
tick_cpu_video<14, video_divider>(); tick_floppy();
|
||||
tick_cpu_video<15, video_divider>(); tick_cpu_video<16, video_divider>();
|
||||
tick_cpu_video<17, video_divider>(); tick_floppy();
|
||||
tick_cpu_video<18, video_divider>(); tick_cpu_video<19, video_divider>();
|
||||
tick_cpu_video<20, video_divider>(); tick_floppy();
|
||||
tick_cpu_video<21, video_divider>(); tick_cpu_video<22, video_divider>();
|
||||
tick_cpu_video<23, video_divider>(); tick_floppy();
|
||||
tick_cpu_video<12, video_divider, original_speed>(); tick_cpu_video<13, video_divider, original_speed>();
|
||||
tick_cpu_video<14, video_divider, original_speed>(); tick_floppy();
|
||||
tick_cpu_video<15, video_divider, original_speed>(); tick_cpu_video<16, video_divider, original_speed>();
|
||||
tick_cpu_video<17, video_divider, original_speed>(); tick_floppy();
|
||||
tick_cpu_video<18, video_divider, original_speed>(); tick_cpu_video<19, video_divider, original_speed>();
|
||||
tick_cpu_video<20, video_divider, original_speed>(); tick_floppy();
|
||||
tick_cpu_video<21, video_divider, original_speed>(); tick_cpu_video<22, video_divider, original_speed>();
|
||||
tick_cpu_video<23, video_divider, original_speed>(); tick_floppy();
|
||||
tick_timers();
|
||||
tick_sound();
|
||||
}
|
||||
int macro_counter_ = 0;
|
||||
|
||||
template <int offset, int video_divider>
|
||||
template <int offset, int video_divider, bool original_speed>
|
||||
void tick_cpu_video() {
|
||||
if constexpr (!(offset % video_divider)) {
|
||||
tick_video();
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
// Debug mode: run CPU a lot slower. Actually at close to original advertised MIPS speed.
|
||||
if constexpr (offset & 7) return;
|
||||
#endif
|
||||
if constexpr (original_speed && (offset & 7)) return;
|
||||
if constexpr (offset & 1) return;
|
||||
tick_cpu();
|
||||
}
|
||||
|
@ -473,20 +471,36 @@ class ConcreteMachine:
|
|||
}
|
||||
|
||||
// MARK: - TimedMachine.
|
||||
int video_divider_ = 1;
|
||||
void run_for(Cycles cycles) override {
|
||||
#ifndef NDEBUG
|
||||
// Debug mode: always run 'slowly' because that's less of a burden, and
|
||||
// because it allows me to peer at problems with greater leisure.
|
||||
const bool use_original_speed = true;
|
||||
#else
|
||||
// As a first, blunt implementation: try to model something close
|
||||
// to original speed if there have been 10 frame rate overages in total.
|
||||
const bool use_original_speed = executor_.bus.video().frame_rate_overages() > 10;
|
||||
#endif
|
||||
|
||||
if(use_original_speed) run_for<true>(cycles);
|
||||
else run_for<false>(cycles);
|
||||
}
|
||||
|
||||
template <bool original_speed>
|
||||
void run_for(Cycles cycles) {
|
||||
macro_counter_ += cycles.as<int>();
|
||||
|
||||
while(macro_counter_ > 0) {
|
||||
switch(video_divider_) {
|
||||
default: macro_tick<2>(); break;
|
||||
case 3: macro_tick<3>(); break;
|
||||
case 4: macro_tick<4>(); break;
|
||||
case 6: macro_tick<6>(); break;
|
||||
default: macro_tick<2, original_speed>(); break;
|
||||
case 3: macro_tick<3, original_speed>(); break;
|
||||
case 4: macro_tick<4, original_speed>(); break;
|
||||
case 6: macro_tick<6, original_speed>(); break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
int video_divider_ = 1;
|
||||
|
||||
void tick_cpu() {
|
||||
const uint32_t instruction = advance_pipeline(executor_.pc() + 8);
|
||||
|
|
|
@ -16,7 +16,7 @@ template <typename InterruptObserverT>
|
|||
class FloppyDisc: public WD::WD1770, public WD::WD1770::Delegate {
|
||||
public:
|
||||
FloppyDisc(InterruptObserverT &observer) : WD::WD1770(P1772), observer_(observer) {
|
||||
emplace_drives(1, 8000000, 300, 2);
|
||||
emplace_drives(1, 8000000, 300, 2, Storage::Disk::Drive::ReadyType::ShugartModifiedRDY); // A guess at RDY type.
|
||||
set_delegate(this);
|
||||
}
|
||||
|
||||
|
@ -40,6 +40,10 @@ public:
|
|||
get_drive(drive).set_disk(disk);
|
||||
}
|
||||
|
||||
bool ready() const {
|
||||
return get_drive().get_is_ready();
|
||||
}
|
||||
|
||||
private:
|
||||
InterruptObserverT &observer_;
|
||||
};
|
||||
|
|
|
@ -200,8 +200,9 @@ struct InputOutputController: public ClockingHint::Observer {
|
|||
|
||||
case 0x00: {
|
||||
uint8_t value = control_ | 0xc0;
|
||||
value &= ~(i2c_.clock() ? 0x02 : 0x00);
|
||||
value &= ~(i2c_.data() ? 0x01 : 0x00);
|
||||
value &= ~(i2c_.clock() ? 0x02 : 0x00);
|
||||
value &= ~(floppy_.ready() ? 0x00 : 0x04);
|
||||
value &= ~(video_.flyback_active() ? 0x00 : 0x80); // i.e. high during flyback.
|
||||
set_byte(value);
|
||||
// logger.error().append("IOC control read: C:%d D:%d", !(value & 2), !(value & 1));
|
||||
|
|
|
@ -79,9 +79,7 @@ struct Video {
|
|||
horizontal_timing_.cursor_start = (value >> 13) & 0x7ff;
|
||||
cursor_shift_ = (value >> 11) & 3;
|
||||
break;
|
||||
case 0x9c:
|
||||
logger.error().append("TODO: Video horizontal interlace: %d", (value >> 14) & 0x3ff);
|
||||
break;
|
||||
case 0x9c: horizontal_timing_.interlace_sync_position = timing_value(value); break;
|
||||
|
||||
case 0xa0: vertical_timing_.period = timing_value(value); break;
|
||||
case 0xa4: vertical_timing_.sync_width = timing_value(value); break;
|
||||
|
@ -104,6 +102,9 @@ struct Video {
|
|||
|
||||
// Set colour depth.
|
||||
colour_depth_ = Depth((value >> 2) & 0b11);
|
||||
|
||||
// Crib interlace-enable.
|
||||
vertical_timing_.is_interlaced = value & (1 << 6);
|
||||
break;
|
||||
|
||||
//
|
||||
|
@ -142,6 +143,16 @@ struct Video {
|
|||
if(phase == Phase::Display) {
|
||||
address_ = frame_start_;
|
||||
cursor_address_ = cursor_start_;
|
||||
|
||||
// Accumulate a count of how many times the processor has tried
|
||||
// to update the visible buffer more than once in a frame; this
|
||||
// will usually indicate that the software being run isn't properly
|
||||
// synchronised to actual machine speed.
|
||||
++frame_starts_;
|
||||
if(frame_start_sets_ > 10) {
|
||||
overages_ += frame_start_sets_ > frame_starts_;
|
||||
frame_start_sets_ = frame_starts_ = 0;
|
||||
}
|
||||
}
|
||||
if(old_phase == Phase::Display) {
|
||||
entered_flyback_ = true;
|
||||
|
@ -191,10 +202,12 @@ struct Video {
|
|||
|
||||
// Move along line.
|
||||
switch(vertical_state_.phase()) {
|
||||
case Phase::Sync: tick_horizontal<Phase::Sync>(); break;
|
||||
case Phase::Blank: tick_horizontal<Phase::Blank>(); break;
|
||||
case Phase::Border: tick_horizontal<Phase::Border>(); break;
|
||||
case Phase::Display: tick_horizontal<Phase::Display>(); break;
|
||||
case Phase::Sync: tick_horizontal<Phase::Sync>(); break;
|
||||
case Phase::Blank: tick_horizontal<Phase::Blank>(); break;
|
||||
case Phase::Border: tick_horizontal<Phase::Border>(); break;
|
||||
case Phase::Display: tick_horizontal<Phase::Display>(); break;
|
||||
case Phase::StartInterlacedSync: tick_horizontal<Phase::StartInterlacedSync>(); break;
|
||||
case Phase::EndInterlacedSync: tick_horizontal<Phase::EndInterlacedSync>(); break;
|
||||
}
|
||||
++time_in_phase_;
|
||||
}
|
||||
|
@ -211,7 +224,10 @@ struct Video {
|
|||
return vertical_state_.phase() != Phase::Display;
|
||||
}
|
||||
|
||||
void set_frame_start(uint32_t address) { frame_start_ = address; }
|
||||
void set_frame_start(uint32_t address) {
|
||||
frame_start_ = address;
|
||||
++frame_start_sets_;
|
||||
}
|
||||
void set_buffer_start(uint32_t address) { buffer_start_ = address; }
|
||||
void set_buffer_end(uint32_t address) { buffer_end_ = address; }
|
||||
void set_cursor_start(uint32_t address) { cursor_start_ = address; }
|
||||
|
@ -223,6 +239,10 @@ struct Video {
|
|||
return static_cast<int>(clock_divider_);
|
||||
}
|
||||
|
||||
int frame_rate_overages() const {
|
||||
return overages_;
|
||||
}
|
||||
|
||||
private:
|
||||
Log::Logger<Log::Source::ARMIOC> logger;
|
||||
InterruptObserverT &interrupt_observer_;
|
||||
|
@ -244,6 +264,9 @@ private:
|
|||
uint32_t display_end = 0;
|
||||
uint32_t cursor_start = 0;
|
||||
uint32_t cursor_end = 0;
|
||||
|
||||
uint32_t interlace_sync_position = 0;
|
||||
bool is_interlaced = false;
|
||||
};
|
||||
uint32_t cursor_shift_ = 0;
|
||||
Timing horizontal_timing_, vertical_timing_;
|
||||
|
@ -257,7 +280,7 @@ private:
|
|||
|
||||
// Current video state.
|
||||
enum class Phase {
|
||||
Sync, Blank, Border, Display,
|
||||
Sync, Blank, Border, Display, StartInterlacedSync, EndInterlacedSync,
|
||||
};
|
||||
template <bool is_vertical>
|
||||
struct State {
|
||||
|
@ -265,8 +288,20 @@ private:
|
|||
uint32_t display_start = 0;
|
||||
uint32_t display_end = 0;
|
||||
|
||||
bool is_odd_iteration_ = false;
|
||||
|
||||
void increment_position(const Timing &timing) {
|
||||
if(position == timing.sync_width) state |= SyncEnded;
|
||||
const auto previous_override = interlace_override_;
|
||||
if constexpr (is_vertical) {
|
||||
interlace_override_ = Phase::Sync; // i.e. no override.
|
||||
}
|
||||
if(position == timing.sync_width) {
|
||||
state |= SyncEnded;
|
||||
if(is_vertical && timing.is_interlaced && is_odd_iteration_ && previous_override == Phase::Sync) {
|
||||
--position;
|
||||
interlace_override_ = Phase::EndInterlacedSync;
|
||||
}
|
||||
}
|
||||
if(position == timing.display_start) { state |= DisplayStarted; display_start = position; }
|
||||
if(position == timing.display_end) { state |= DisplayEnded; display_end = position; }
|
||||
if(position == timing.border_start) state |= BorderStarted;
|
||||
|
@ -278,10 +313,16 @@ private:
|
|||
if(position == timing.period) {
|
||||
state = DidRestart;
|
||||
position = 0;
|
||||
is_odd_iteration_ ^= true;
|
||||
|
||||
// Both display start and end need to be seeded as bigger than can be reached,
|
||||
// while having some overhead for addition.
|
||||
display_end = display_start = std::numeric_limits<uint32_t>::max() >> 1;
|
||||
|
||||
// Possibly label the next as a start-of-interlaced-sync.
|
||||
if(is_vertical && timing.is_interlaced && is_odd_iteration_) {
|
||||
interlace_override_ = Phase::StartInterlacedSync;
|
||||
}
|
||||
} else {
|
||||
++position;
|
||||
if(position == 1024) position = 0;
|
||||
|
@ -310,6 +351,7 @@ private:
|
|||
static constexpr uint8_t DisplayEnded = 0x10;
|
||||
static constexpr uint8_t DidRestart = 0x20;
|
||||
uint8_t state = 0;
|
||||
Phase interlace_override_ = Phase::Sync;
|
||||
|
||||
bool cursor_active = false;
|
||||
|
||||
|
@ -324,6 +366,9 @@ private:
|
|||
}
|
||||
|
||||
Phase phase(Phase horizontal_fallback = Phase::Border) const {
|
||||
if(is_vertical && interlace_override_ != Phase::Sync) {
|
||||
return interlace_override_;
|
||||
}
|
||||
// TODO: turn the following logic into a lookup table.
|
||||
if(!(state & SyncEnded)) {
|
||||
return Phase::Sync;
|
||||
|
@ -361,6 +406,10 @@ private:
|
|||
uint32_t frame_start_ = 0;
|
||||
uint32_t cursor_start_ = 0;
|
||||
|
||||
int frame_start_sets_ = 0;
|
||||
int frame_starts_ = 0;
|
||||
int overages_ = 0;
|
||||
|
||||
// Ephemeral address state.
|
||||
uint32_t address_ = 0;
|
||||
|
||||
|
@ -419,7 +468,7 @@ private:
|
|||
void set_phase(Phase phase) {
|
||||
if(time_in_phase_) {
|
||||
switch(phase_) {
|
||||
case Phase::Sync: crt_.output_sync(time_in_phase_); break;
|
||||
default: crt_.output_sync(time_in_phase_); break;
|
||||
case Phase::Blank: crt_.output_blank(time_in_phase_); break;
|
||||
case Phase::Border: crt_.output_level<uint16_t>(time_in_phase_, phased_border_colour_); break;
|
||||
case Phase::Display: flush_pixels(); break;
|
||||
|
@ -444,6 +493,26 @@ private:
|
|||
return;
|
||||
}
|
||||
|
||||
// Start interlaced sync lines: do blank from horizontal sync up to the programmed
|
||||
// cutoff, then do sync.
|
||||
if constexpr (vertical_phase == Phase::StartInterlacedSync) {
|
||||
if(phase_ == Phase::Sync && horizontal_state_.phase() != Phase::Sync) {
|
||||
set_phase(Phase::Blank);
|
||||
}
|
||||
if(phase_ == Phase::Blank && horizontal_state_.position == horizontal_timing_.interlace_sync_position) {
|
||||
set_phase(Phase::Sync);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// End interlaced sync lines: do sync up to the programmed cutoff, then do blank.
|
||||
if constexpr (vertical_phase == Phase::EndInterlacedSync) {
|
||||
if(phase_ == Phase::Sync && horizontal_state_.position == horizontal_timing_.interlace_sync_position) {
|
||||
set_phase(Phase::Blank);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Blank lines: obey only the transition from sync to non-sync.
|
||||
if constexpr (vertical_phase == Phase::Blank) {
|
||||
if(phase_ == Phase::Sync && horizontal_state_.phase() != Phase::Sync) {
|
||||
|
|
|
@ -50,6 +50,10 @@ Drive &Controller::get_drive() {
|
|||
return *drive_;
|
||||
}
|
||||
|
||||
const Drive &Controller::get_drive() const {
|
||||
return *drive_;
|
||||
}
|
||||
|
||||
// MARK: - Drive::EventDelegate
|
||||
|
||||
void Controller::process_event(const Drive::Event &event) {
|
||||
|
|
|
@ -117,10 +117,14 @@ class Controller:
|
|||
made about the lifetime or the exclusivity of the invented drive.
|
||||
*/
|
||||
Drive &get_drive();
|
||||
const Drive &get_drive() const;
|
||||
|
||||
Drive &get_drive(size_t index) {
|
||||
return *drives_[index];
|
||||
}
|
||||
const Drive &get_drive(size_t index) const {
|
||||
return *drives_[index];
|
||||
}
|
||||
|
||||
void for_all_drives(const std::function<void(Drive &, size_t)> &func) {
|
||||
size_t index = 0;
|
||||
|
|
Loading…
Reference in New Issue