1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-08-09 20:25:19 +00:00

Merge pull request #1410 from TomHarte/CPCLatency

Further improve the CPC side of the CPC:CRTC relationship.
This commit is contained in:
Thomas Harte
2024-10-15 21:30:39 -04:00
committed by GitHub
2 changed files with 210 additions and 210 deletions

View File

@@ -55,8 +55,6 @@ class InterruptTimer {
trailing edge because it is active high.
*/
inline void signal_hsync() {
// printf("count h: %d/%d [%d]\n", timer_, reset_counter_, interrupt_request_);
// Increment the timer and if it has hit 52 then reset it and
// set the interrupt request line to true.
++timer_;
@@ -81,13 +79,11 @@ class InterruptTimer {
/// Indicates the leading edge of a new vertical sync.
inline void signal_vsync() {
// printf("count v\n");
reset_counter_ = 2;
}
/// Indicates that an interrupt acknowledge has been received from the Z80.
inline void signal_interrupt_acknowledge() {
// printf("count IRQA\n");
interrupt_request_ = false;
timer_ &= ~32;
}
@@ -104,7 +100,6 @@ class InterruptTimer {
/// Resets the timer.
inline void reset_count() {
// printf("count reset\n");
timer_ = 0;
interrupt_request_ = false;
}
@@ -190,6 +185,8 @@ class CRTCBusHandler {
bus state and determines what output to produce based on the current palette and mode.
*/
forceinline void perform_bus_cycle(const Motorola::CRTC::BusState &state) {
// TODO: there's a one-tick delay on pixel output; incorporate that.
// The gate array waits 2us to react to the CRTC's vsync signal, and then
// caps output at 4us. Since the clock rate is 1Mhz, that's 2 and 4 cycles,
// respectively.
@@ -240,10 +237,10 @@ class CRTCBusHandler {
previous_output_mode_ = output_mode;
}
// increment cycles since state changed
// Increment cycles since state changed.
cycles_++;
// collect some more pixels if output is ongoing
// Collect some more pixels if output is ongoing.
if(previous_output_mode_ == OutputMode::Pixels) {
if(!pixel_data_) {
pixel_pointer_ = pixel_data_ = crt_.begin_data(320, 8);
@@ -300,17 +297,8 @@ class CRTCBusHandler {
}
}
// Notify a leading hsync edge to the interrupt timer.
// Per Interrupts in the CPC: "to be confirmed: does gate array count positive or negative edge transitions of HSYNC signal?";
// if you take it as given that display mode is latched as a result of hsync then Pipe Mania seems to imply that the count
// occurs on a leading edge and the mode lock on a trailing.
if(!was_hsync_ && state.hsync) {
interrupt_timer_.signal_hsync();
}
// Check for a trailing CRTC hsync; if one occurred then that's the trigger potentially to change modes.
if(was_hsync_ && !state.hsync) {
if(mode_ != next_mode_) {
// Latch mode four cycles after HSYNC was signalled, if still active.
if(cycles_into_hsync_ == 4 && mode_ != next_mode_) {
mode_ = next_mode_;
switch(mode_) {
default:
@@ -320,14 +308,17 @@ class CRTCBusHandler {
}
build_mode_table();
}
}
// check for a leading vsync; that also needs to be communicated to the interrupt timer
// For the interrupt timer: notify the leading edge of vertical sync and the
// trailing edge of horizontal sync.
if(!was_vsync_ && state.vsync) {
interrupt_timer_.signal_vsync();
}
if(was_hsync_ && !state.hsync) {
interrupt_timer_.signal_hsync();
}
// update current state for edge detection next time around
// Update current state for edge detection next time around.
was_vsync_ = state.vsync;
was_hsync_ = state.hsync;
}
@@ -858,6 +849,9 @@ class ConcreteMachine:
clock_offset_ = (clock_offset_ + cycle.length) & HalfCycles(7);
z80_.set_wait_line(clock_offset_ >= HalfCycles(2));
// Float this out as a lambda to allow easy repositioning relative to the CPU activity;
// for now this is largely experimental.
const auto update_subsystems = [&] {
// Update the CRTC once every eight half cycles; aiming for half-cycle 4 as
// per the initial seed to the crtc_counter_, but any time in the final four
// will do as it's safe to conclude that nobody else has touched video RAM
@@ -884,10 +878,10 @@ class ConcreteMachine:
// Update typing activity.
if(typer_) typer_->run_for(cycle.length);
};
// Stop now if no action is strictly required.
if(!cycle.is_terminal()) return HalfCycles(0);
// Continue only if action strictly required.
if(cycle.is_terminal()) {
uint16_t address = cycle.address ? *cycle.address : 0x0000;
switch(cycle.operation) {
case CPU::Z80::PartialMachineCycle::ReadOpcode:
@@ -1073,8 +1067,11 @@ class ConcreteMachine:
default: break;
}
// Check whether the interrupt signal has changed the other way.
// Check whether the interrupt signal has changed due to CPU intervention.
if(interrupt_timer_.request_has_changed()) z80_.set_interrupt_line(interrupt_timer_.get_request());
}
update_subsystems();
// This implementation doesn't use time-stuffing; once in-phase waits won't be longer
// than a single cycle so there's no real performance benefit to trying to find the

View File

@@ -43,9 +43,6 @@ struct ScanTarget: public Outputs::Display::ScanTarget {
const int src_pixels = scan_.end_points[1].data_offset - scan_.end_points[0].data_offset;
const int dst_pixels = (scan_.end_points[1].x - scan_.end_points[0].x) / WidthDivider;
const int step = (src_pixels << 16) / dst_pixels;
int position = 0;
const auto x1 = scan_.end_points[0].x / WidthDivider;
const auto x2 = scan_.end_points[1].x / WidthDivider;
@@ -53,10 +50,16 @@ struct ScanTarget: public Outputs::Display::ScanTarget {
if(x_ < x1) {
std::fill(&line[x_], &line[x1], 0);
}
if(x2 != x1) {
const int step = (src_pixels << 16) / dst_pixels;
int position = 0;
for(int x = x1; x < x2; x++) {
line[x] = data_[position >> 16];
position += step;
}
}
x_ = x2;
}
void announce(Event event, bool, const Scan::EndPoint &, uint8_t) override {