1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-11-26 23:52:26 +00:00

Merge pull request #685 from TomHarte/LoadDelay

Adds a delay on load following DE
This commit is contained in:
Thomas Harte 2019-12-13 21:38:10 -05:00 committed by GitHub
commit 77fe14cdb3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 118 additions and 52 deletions

View File

@ -67,7 +67,7 @@ class ConcreteMachine:
speaker_(ay_),
ikbd_(keyboard_acia_->transmit, keyboard_acia_->receive) {
set_clock_rate(CLOCK_RATE);
speaker_.set_input_rate(CLOCK_RATE / 4);
speaker_.set_input_rate(float(CLOCK_RATE) / 4.0f);
ram_.resize(512 * 1024); // i.e. 512kb
video_->set_ram(reinterpret_cast<uint16_t *>(ram_.data()), ram_.size());

View File

@ -38,6 +38,7 @@ const VerticalParams &vertical_parameters(Video::FieldFrequency frequency) {
}
#define CYCLE(x) ((x) * 2)
/*!
Defines the horizontal counts at which mode-specific events will occur:
horizontal enable being set and being reset, blank being set and reset, and the
@ -58,9 +59,9 @@ const struct HorizontalParams {
const int length;
} horizontal_params[3] = {
{56*2, 376*2, 450*2, 28*2, 512*2},
{52*2, 372*2, 450*2, 24*2, 508*2},
{4*2, 164*2, 184*2, 2*2, 224*2}
{CYCLE(56), CYCLE(376), CYCLE(450), CYCLE(28), CYCLE(512)},
{CYCLE(52), CYCLE(372), CYCLE(450), CYCLE(24), CYCLE(508)},
{CYCLE(4), CYCLE(164), CYCLE(999), CYCLE(999), CYCLE(224)} // 72Hz mode doesn't set or reset blank.
};
const HorizontalParams &horizontal_parameters(Video::FieldFrequency frequency) {
@ -73,10 +74,16 @@ struct Checker {
for(int c = 0; c < 3; ++c) {
// Expected horizontal order of events: reset blank, enable display, disable display, enable blank (at least 50 before end of line), end of line
const auto horizontal = horizontal_parameters(Video::FieldFrequency(c));
if(c < 2) {
assert(horizontal.reset_blank < horizontal.set_enable);
assert(horizontal.set_enable < horizontal.reset_enable);
assert(horizontal.reset_enable < horizontal.set_blank);
assert(horizontal.set_blank+50 < horizontal.length);
} else {
assert(horizontal.set_enable < horizontal.reset_enable);
assert(horizontal.set_enable+50 <horizontal.length);
}
// Expected vertical order of events: reset blank, enable display, disable display, enable blank (at least 50 before end of line), end of line
const auto vertical = vertical_parameters(Video::FieldFrequency(c));
@ -87,8 +94,10 @@ struct Checker {
} checker;
#endif
const int de_delay_period = 28*2; // Number of half cycles after DE that observed DE changes.
const int vsync_x_position = 54*2; // Horizontal cycle on which vertical sync changes happen.
const int de_delay_period = CYCLE(28); // Number of half cycles after DE that observed DE changes.
const int vsync_x_position = CYCLE(54); // Horizontal cycle on which vertical sync changes happen.
const int hsync_start = CYCLE(48); // Cycles before end of line when hsync starts.
const int hsync_end = CYCLE(8); // Cycles before end of line when hsync ends.
// "VSYNC starts 104 cycles after the start of the previous line's HSYNC, so that's 4 cycles before DE would be activated. ";
// hsync is at -50, so that's +54
@ -96,6 +105,7 @@ const int vsync_x_position = 54*2; // Horizontal cycle on which vertical sync ch
}
Video::Video() :
deferrer_([=] (HalfCycles duration) { advance(duration); }),
crt_(1024, 1, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red4Green4Blue4),
shifter_(crt_, palette_) {
@ -113,6 +123,10 @@ void Video::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
}
void Video::run_for(HalfCycles duration) {
deferrer_.run_for(duration);
}
void Video::advance(HalfCycles duration) {
const auto horizontal_timings = horizontal_parameters(field_frequency_);
const auto vertical_timings = vertical_parameters(field_frequency_);
int integer_duration = int(duration.as_integral());
@ -145,10 +159,11 @@ void Video::run_for(HalfCycles duration) {
if(horizontal_timings.set_blank > x_) next_event = std::min(next_event, horizontal_timings.set_blank);
if(horizontal_timings.reset_enable > x_) next_event = std::min(next_event, horizontal_timings.reset_enable);
if(horizontal_timings.set_enable > x_) next_event = std::min(next_event, horizontal_timings.set_enable);
if(next_load_toggle_ > x_) next_event = std::min(next_event, next_load_toggle_);
// Check for events that are relative to existing latched state.
if(line_length_ - 50*2 > x_) next_event = std::min(next_event, line_length_ - 50*2);
if(line_length_ - 10*2 > x_) next_event = std::min(next_event, line_length_ - 10*2);
if(line_length_ - hsync_start > x_) next_event = std::min(next_event, line_length_ - hsync_start);
if(line_length_ - hsync_end > x_) next_event = std::min(next_event, line_length_ - hsync_end);
// Also, a vertical sync event might intercede.
if(vertical_.sync_schedule != VerticalState::SyncSchedule::None && x_ < vsync_x_position && next_event >= vsync_x_position) {
@ -159,18 +174,42 @@ void Video::run_for(HalfCycles duration) {
const int run_length = std::min(integer_duration, next_event - x_);
const bool display_enable = vertical_.enable && horizontal_.enable;
// Ensure proper fetching irrespective of the output.
if(load_) {
const int since_load = x_ - load_base_;
// There will be pixels this line, subject to the shifter pipeline.
// Divide into 8-[half-]cycle windows; at the start of each window fetch a word,
// and during the rest of the window, shift out.
int start_column = since_load >> 3;
const int end_column = (since_load + run_length) >> 3;
while(start_column != end_column) {
data_latch_[data_latch_position_] = ram_[current_address_ & 262143];
data_latch_position_ = (data_latch_position_ + 1) & 127;
++current_address_;
++start_column;
}
}
// TODO: if I'm asserting that sync and blank override the shifter (but, presumably,
// the shifter keeps shifting), then output_sync and output_blank need to have an effect
// inside the shifter on the temporary register values.
if(horizontal_.sync || vertical_.sync) {
shifter_.output_sync(run_length);
} else if(horizontal_.blank || vertical_.blank) {
shifter_.output_blank(run_length);
} else if(!vertical_.enable || !horizontal_.enable) {
} else if(!load_) {
shifter_.output_border(run_length, output_bpp_);
} else {
const int since_load = x_ - load_base_;
// There will be pixels this line, subject to the shifter pipeline.
// Divide into 8-[half-]cycle windows; at the start of each window fetch a word,
// and during the rest of the window, shift out.
int start_column = x_ >> 3;
const int end_column = (x_ + run_length) >> 3;
int start_column = since_load >> 3;
const int end_column = (since_load + run_length) >> 3;
// Rules obeyed below:
//
@ -182,32 +221,32 @@ void Video::run_for(HalfCycles duration) {
shifter_.output_pixels(run_length, output_bpp_);
} else {
// Continue the current column if partway across.
if(x_&7) {
if(since_load&7) {
// If at least one column boundary is crossed, complete this column.
shifter_.output_pixels(8 - (x_ & 7), output_bpp_);
shifter_.output_pixels(8 - (since_load & 7), output_bpp_);
++start_column; // This starts a new column, so latch a new word.
latch_word();
push_latched_data();
}
// Run for all columns that have their starts in this time period.
int complete_columns = end_column - start_column;
while(complete_columns--) {
shifter_.output_pixels(8, output_bpp_);
latch_word();
push_latched_data();
}
// Output the start of the next column, if necessary.
if((x_ + run_length) & 7) {
shifter_.output_pixels((x_ + run_length) & 7, output_bpp_);
if((since_load + run_length) & 7) {
shifter_.output_pixels((since_load + run_length) & 7, output_bpp_);
}
}
}
// Check for whether line length should have been latched during this run.
if(x_ <= 54*2 && (x_ + run_length) > 54*2) line_length_ = horizontal_timings.length;
if(x_ <= CYCLE(54) && (x_ + run_length) > CYCLE(54)) line_length_ = horizontal_timings.length;
// Make a decision about vertical state on cycle 502.
if(x_ <= 502*2 && (x_ + run_length) > 502*2) {
if(x_ <= CYCLE(502) && (x_ + run_length) > CYCLE(502)) {
next_y_ = y_ + 1;
next_vertical_ = vertical_;
next_vertical_.sync_schedule = VerticalState::SyncSchedule::None;
@ -224,6 +263,7 @@ void Video::run_for(HalfCycles duration) {
} else if(next_y_ == vertical_timings.height) {
next_y_ = 0;
current_address_ = base_address_ >> 1;
reset_fifo(); // TODO: remove this, I think, once otherwise stable.
// Consider a shout out to the range observer.
if(previous_base_address_ != base_address_) {
@ -248,8 +288,13 @@ void Video::run_for(HalfCycles duration) {
else if(horizontal_timings.set_blank == x_) horizontal_.blank = true;
else if(horizontal_timings.reset_enable == x_) horizontal_.enable = false;
else if(horizontal_timings.set_enable == x_) horizontal_.enable = true;
else if(line_length_ - 50*2 == x_) horizontal_.sync = true;
else if(line_length_ - 10*2 == x_) horizontal_.sync = false;
else if(line_length_ - hsync_start == x_) { horizontal_.sync = true; horizontal_.enable = false; }
else if(line_length_ - hsync_end == x_) horizontal_.sync = false;
else if(next_load_toggle_ == x_) {
next_load_toggle_ = -1;
load_ ^= true;
load_base_ = x_;
}
// Check vertical events.
if(vertical_.sync_schedule != VerticalState::SyncSchedule::None && x_ == vsync_x_position) {
@ -268,26 +313,32 @@ void Video::run_for(HalfCycles duration) {
// Chuck any deferred output changes into the queue.
const bool next_display_enable = vertical_.enable && horizontal_.enable;
if(display_enable != next_display_enable) {
// Schedule change in outwardly-visible DE line.
add_event(de_delay_period - integer_duration, next_display_enable ? Event::Type::SetDisplayEnable : Event::Type::ResetDisplayEnable);
// Schedule change in inwardly-visible effect.
next_load_toggle_ = x_ + 8; // 4 cycles = 8 half-cycles
}
}
}
void Video::latch_word() {
data_latch_[data_latch_position_] = ram_[current_address_ & 262143];
++current_address_;
++data_latch_position_;
if(data_latch_position_ == 4) {
data_latch_position_ = 0;
void Video::push_latched_data() {
data_latch_read_position_ = (data_latch_read_position_ + 1) & 127;
if(!(data_latch_read_position_ & 3)) {
shifter_.load(
(uint64_t(data_latch_[0]) << 48) |
(uint64_t(data_latch_[1]) << 32) |
(uint64_t(data_latch_[2]) << 16) |
uint64_t(data_latch_[3])
(uint64_t(data_latch_[(data_latch_read_position_ - 4) & 127]) << 48) |
(uint64_t(data_latch_[(data_latch_read_position_ - 3) & 127]) << 32) |
(uint64_t(data_latch_[(data_latch_read_position_ - 2) & 127]) << 16) |
uint64_t(data_latch_[(data_latch_read_position_ - 1) & 127])
);
}
}
void Video::reset_fifo() {
data_latch_read_position_ = data_latch_position_ = 0;
}
bool Video::hsync() {
return horizontal_.sync;
}
@ -342,8 +393,8 @@ HalfCycles Video::get_next_sequence_point() {
}
// Test for beginning and end of horizontal sync.
if(x_ < line_length_ - 50*2) event_time = std::min(line_length_ - 50*2, event_time);
else if(x_ < line_length_ - 10*2) event_time = std::min(line_length_ - 10*2, event_time);
if(x_ < line_length_ - hsync_start) event_time = std::min(line_length_ - hsync_start, event_time);
else if(x_ < line_length_ - hsync_end) event_time = std::min(line_length_ - hsync_end, event_time);
// It wasn't any of those, so as a temporary expedient, just supply end of line.
return HalfCycles(event_time - x_);
@ -384,8 +435,11 @@ void Video::write(int address, uint16_t value) {
// Sync mode and pixel mode.
case 0x05:
// Writes to sync mode have a one-cycle delay in effect.
deferrer_.defer(HalfCycles(2), [=] {
sync_mode_ = value;
update_output_mode();
});
break;
case 0x30:
video_mode_ = value;
@ -406,24 +460,26 @@ void Video::write(int address, uint16_t value) {
}
void Video::update_output_mode() {
const auto old_bpp_ = output_bpp_;
// If this is black and white mode, that's that.
switch((video_mode_ >> 8) & 3) {
case 0: output_bpp_ = OutputBpp::Four; break;
case 1: output_bpp_ = OutputBpp::Two; break;
// 1bpp mode ignores the otherwise-programmed frequency.
default:
case 2:
output_bpp_ = OutputBpp::One;
field_frequency_ = FieldFrequency::SeventyTwo;
return;
case 2: output_bpp_ = OutputBpp::One; break;
}
// const auto old_frequency = field_frequency_;
// 1bpp mode ignores the otherwise-programmed frequency.
if(output_bpp_ == OutputBpp::One) {
field_frequency_ = FieldFrequency::SeventyTwo;
} else {
field_frequency_ = (sync_mode_ & 0x200) ? FieldFrequency::Fifty : FieldFrequency::Sixty;
// if(field_frequency_ != old_frequency) {
// printf("%d, %d -> %d\n", x_, y_, field_frequency_);
// }
}
if(output_bpp_ != old_bpp_) {
// "the 71-Hz-switch does something like a shifter-reset." (and some people use a high-low resolutions switch instead)
reset_fifo();
}
}
// MARK: - The shifter

View File

@ -11,6 +11,7 @@
#include "../../../Outputs/CRT/CRT.hpp"
#include "../../../ClockReceiver/ClockReceiver.hpp"
#include "../../../ClockReceiver/DeferredQueue.hpp"
#include <vector>
@ -103,6 +104,9 @@ class Video {
Range get_memory_access_range();
private:
void advance(HalfCycles duration);
DeferredQueue<HalfCycles> deferrer_;
Outputs::CRT::CRT crt_;
RangeObserver *range_observer_ = nullptr;
@ -116,6 +120,9 @@ class Video {
uint16_t line_buffer_[256];
int x_ = 0, y_ = 0, next_y_ = 0;
int next_load_toggle_ = -1;
bool load_ = false;
int load_base_ = 0;
uint16_t video_mode_ = 0;
uint16_t sync_mode_ = 0;
@ -148,8 +155,11 @@ class Video {
int line_length_ = 1024;
int data_latch_position_ = 0;
uint16_t data_latch_[4] = {0, 0, 0, 0};
void latch_word();
int data_latch_read_position_ = 0;
uint16_t data_latch_[128];
void push_latched_data();
void reset_fifo();
class Shifter {
public: