1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-08 15:29:09 +00:00

Ensured the video subsystem correctly handles requests to run over a frame boundary.

This commit is contained in:
Thomas Harte 2016-12-11 16:17:51 -05:00
parent 52028432e1
commit c5cf8d9531
3 changed files with 151 additions and 140 deletions

View File

@ -104,10 +104,6 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
} }
else else
{ {
// if((address >> 8) == 0xfc)
// {
// printf("d");
// }
switch(address & 0xff0f) switch(address & 0xff0f)
{ {
case 0xfe00: case 0xfe00:
@ -122,6 +118,26 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
evaluate_interrupts(); evaluate_interrupts();
} }
break; break;
case 0xfe07:
if(!isReadOperation(operation))
{
// update speaker mode
bool new_speaker_is_enabled = (*value & 6) == 2;
if(new_speaker_is_enabled != speaker_is_enabled_)
{
update_audio();
speaker_->set_is_enabled(new_speaker_is_enabled);
speaker_is_enabled_ = new_speaker_is_enabled;
}
tape_.set_is_enabled((*value & 6) != 6);
tape_.set_is_in_input_mode((*value & 6) == 0);
tape_.set_is_running(((*value)&0x40) ? true : false);
// TODO: caps lock LED
}
// deliberate fallthrough
case 0xfe02: case 0xfe03: case 0xfe02: case 0xfe03:
case 0xfe08: case 0xfe09: case 0xfe0a: case 0xfe0b: case 0xfe08: case 0xfe09: case 0xfe0a: case 0xfe0b:
case 0xfe0c: case 0xfe0d: case 0xfe0e: case 0xfe0f: case 0xfe0c: case 0xfe0d: case 0xfe0e: case 0xfe0f:
@ -184,28 +200,6 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
tape_.set_counter(*value); tape_.set_counter(*value);
} }
break; break;
case 0xfe07:
if(!isReadOperation(operation))
{
update_display();
video_output_->set_register(address, *value);
// update speaker mode
bool new_speaker_is_enabled = (*value & 6) == 2;
if(new_speaker_is_enabled != speaker_is_enabled_)
{
update_audio();
speaker_->set_is_enabled(new_speaker_is_enabled);
speaker_is_enabled_ = new_speaker_is_enabled;
}
tape_.set_is_enabled((*value & 6) != 6);
tape_.set_is_in_input_mode((*value & 6) == 0);
tape_.set_is_running(((*value)&0x40) ? true : false);
// TODO: caps lock LED
}
break;
case 0xfc04: case 0xfc05: case 0xfc06: case 0xfc07: case 0xfc04: case 0xfc05: case 0xfc06: case 0xfc07:
if(plus3_ && (address&0x00f0) == 0x00c0) if(plus3_ && (address&0x00f0) == 0x00c0)
@ -326,11 +320,6 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
} }
} }
// if(operation == CPU6502::BusOperation::ReadOpcode)
// {
// printf("%04x: %02x (%d)\n", address, *value, _fieldCycles);
// }
// const int end_of_field = // const int end_of_field =
// if(frame_cycles_ < (256 + first_graphics_line) << 7)) // if(frame_cycles_ < (256 + first_graphics_line) << 7))
@ -484,7 +473,7 @@ inline void Machine::evaluate_interrupts()
inline void Machine::update_display() inline void Machine::update_display()
{ {
video_output_->run_for_cycles(frame_cycles_ - display_output_position_); video_output_->run_for_cycles((int)(frame_cycles_ - display_output_position_));
display_output_position_ = frame_cycles_; display_output_position_ = frame_cycles_;
} }

View File

@ -120,12 +120,14 @@ class Machine:
uint8_t os_[16384], ram_[32768]; uint8_t os_[16384], ram_[32768];
std::vector<uint8_t> dfs_, adfs_; std::vector<uint8_t> dfs_, adfs_;
// Things affected by registers, explicitly or otherwise. // Paging
uint8_t interrupt_status_, interrupt_control_;
uint8_t key_states_[14];
ROMSlot active_rom_; ROMSlot active_rom_;
bool keyboard_is_active_, basic_is_active_; bool keyboard_is_active_, basic_is_active_;
// Interrupt and keyboard state
uint8_t interrupt_status_, interrupt_control_;
uint8_t key_states_[14];
// Counters related to simultaneous subsystems // Counters related to simultaneous subsystems
unsigned int frame_cycles_, display_output_position_; unsigned int frame_cycles_, display_output_position_;
unsigned int audio_output_position_, audio_output_position_error_; unsigned int audio_output_position_, audio_output_position_error_;

View File

@ -13,7 +13,7 @@ using namespace Electron;
namespace { namespace {
static const unsigned int cycles_per_line = 128; static const unsigned int cycles_per_line = 128;
static const unsigned int lines_per_frame = 625; static const unsigned int lines_per_frame = 625;
static const unsigned int cycles_per_frame = lines_per_frame * cycles_per_line; static const int cycles_per_frame = lines_per_frame * cycles_per_line;
static const unsigned int crt_cycles_multiplier = 8; static const unsigned int crt_cycles_multiplier = 8;
static const unsigned int crt_cycles_per_line = crt_cycles_multiplier * cycles_per_line; static const unsigned int crt_cycles_per_line = crt_cycles_multiplier * cycles_per_line;
@ -255,130 +255,138 @@ void VideoOutput::run_for_cycles(int number_of_cycles)
*/ */
int final_position = output_position_ + number_of_cycles; int final_position = output_position_ + number_of_cycles;
int final_line = final_position >> 7;
while(output_position_ < final_position) int number_of_frames = 1 + (final_position / cycles_per_frame);
while(number_of_frames--)
{ {
int line = output_position_ >> 7; int frame_final = number_of_frames ? cycles_per_frame : (final_position % cycles_per_frame);
int final_line = frame_final >> 7;
// Priority one: sync. while(output_position_ < frame_final)
// ===================
// full sync lines are 0, 1, field_divider_line+1 and field_divider_line+2
if(line == 0 || line == 1 || line == field_divider_line+1 || line == field_divider_line+2)
{ {
// wait for the line to complete before signalling int line = output_position_ >> 7;
if(final_line == line) return;
crt_->output_sync(128 * crt_cycles_multiplier);
output_position_ += 128;
continue;
}
// line 2 is a left-sync line // Priority one: sync.
if(line == 2) // ===================
{
// wait for the line to complete before signalling
if(final_line == line) return;
crt_->output_sync(64 * crt_cycles_multiplier);
crt_->output_blank(64 * crt_cycles_multiplier);
output_position_ += 128;
continue;
}
// line field_divider_line is a right-sync line // full sync lines are 0, 1, field_divider_line+1 and field_divider_line+2
if(line == field_divider_line) if(line == 0 || line == 1 || line == field_divider_line+1 || line == field_divider_line+2)
{
// wait for the line to complete before signalling
if(final_line == line) return;
crt_->output_sync(9 * crt_cycles_multiplier);
crt_->output_blank(55 * crt_cycles_multiplier);
crt_->output_sync(64 * crt_cycles_multiplier);
output_position_ += 128;
continue;
}
// Priority two: blank lines.
// ==========================
//
// Given that it is not a sync line, this is a blank line if it is less than first_graphics_line, or greater
// than first_graphics_line+255 and less than first_graphics_line+field_divider_line, or greater than
// first_graphics_line+field_divider_line+255 (TODO: or this is Mode 3 or 6 and this should be blank)
if(
line < first_graphics_line ||
(line > first_graphics_line+255 && line < first_graphics_line+field_divider_line) ||
line > first_graphics_line+field_divider_line+255)
{
if(final_line == line) return;
crt_->output_sync(9 * crt_cycles_multiplier);
crt_->output_blank(119 * crt_cycles_multiplier);
output_position_ += 128;
continue;
}
// Final possibility: this is a pixel line.
// ========================================
// determine how far we're going from left to right
unsigned int this_cycle = output_position_&127;
unsigned int final_cycle = final_position&127;
if(final_line > line)
{
final_cycle = 128;
}
// output format is:
// 9 cycles: sync
// ... to 24 cycles: colour burst
// ... to first_graphics_cycle: blank
// ... for 80 cycles: pixels
// ... until end of line: blank
while(this_cycle < final_cycle)
{
if(this_cycle < 9)
{ {
if(final_cycle < 9) return; // wait for the line to complete before signalling
if(final_line == line) return;
crt_->output_sync(128 * crt_cycles_multiplier);
output_position_ += 128;
continue;
}
// line 2 is a left-sync line
if(line == 2)
{
// wait for the line to complete before signalling
if(final_line == line) return;
crt_->output_sync(64 * crt_cycles_multiplier);
crt_->output_blank(64 * crt_cycles_multiplier);
output_position_ += 128;
continue;
}
// line field_divider_line is a right-sync line
if(line == field_divider_line)
{
// wait for the line to complete before signalling
if(final_line == line) return;
crt_->output_sync(9 * crt_cycles_multiplier); crt_->output_sync(9 * crt_cycles_multiplier);
output_position_ += 9; crt_->output_blank(55 * crt_cycles_multiplier);
this_cycle = 9; crt_->output_sync(64 * crt_cycles_multiplier);
output_position_ += 128;
continue;
} }
if(this_cycle < 24) // Priority two: blank lines.
// ==========================
//
// Given that it is not a sync line, this is a blank line if it is less than first_graphics_line, or greater
// than first_graphics_line+255 and less than first_graphics_line+field_divider_line, or greater than
// first_graphics_line+field_divider_line+255 (TODO: or this is Mode 3 or 6 and this should be blank)
if(
line < first_graphics_line ||
(line > first_graphics_line+255 && line < first_graphics_line+field_divider_line) ||
line > first_graphics_line+field_divider_line+255)
{ {
if(final_cycle < 24) return; if(final_line == line) return;
crt_->output_default_colour_burst((24-9) * crt_cycles_multiplier); crt_->output_sync(9 * crt_cycles_multiplier);
output_position_ += 24-9; crt_->output_blank(119 * crt_cycles_multiplier);
this_cycle = 24; output_position_ += 128;
// TODO: phase shouldn't be zero on every line continue;
} }
if(this_cycle < first_graphics_cycle) // Final possibility: this is a pixel line.
// ========================================
// determine how far we're going from left to right
unsigned int this_cycle = output_position_&127;
unsigned int final_cycle = frame_final&127;
if(final_line > line)
{ {
if(final_cycle < first_graphics_cycle) return; final_cycle = 128;
crt_->output_blank((first_graphics_cycle - 24) * crt_cycles_multiplier);
output_position_ += first_graphics_cycle - 24;
this_cycle = first_graphics_cycle;
start_pixel_line();
} }
if(this_cycle < first_graphics_cycle + 80) // output format is:
// 9 cycles: sync
// ... to 24 cycles: colour burst
// ... to first_graphics_cycle: blank
// ... for 80 cycles: pixels
// ... until end of line: blank
while(this_cycle < final_cycle)
{ {
unsigned int length_to_output = std::min(final_cycle, (first_graphics_cycle + 80)) - this_cycle; if(this_cycle < 9)
output_pixels(length_to_output); {
output_position_ += length_to_output; if(final_cycle < 9) return;
this_cycle += length_to_output; crt_->output_sync(9 * crt_cycles_multiplier);
} output_position_ += 9;
this_cycle = 9;
}
if(this_cycle >= first_graphics_cycle + 80) if(this_cycle < 24)
{ {
if(final_cycle < 128) return; if(final_cycle < 24) return;
end_pixel_line(); crt_->output_default_colour_burst((24-9) * crt_cycles_multiplier);
crt_->output_blank((128 - (first_graphics_cycle + 80)) * crt_cycles_multiplier); output_position_ += 24-9;
output_position_ += 128 - (first_graphics_cycle + 80); this_cycle = 24;
this_cycle = 128; // TODO: phase shouldn't be zero on every line
}
if(this_cycle < first_graphics_cycle)
{
if(final_cycle < first_graphics_cycle) return;
crt_->output_blank((first_graphics_cycle - 24) * crt_cycles_multiplier);
output_position_ += first_graphics_cycle - 24;
this_cycle = first_graphics_cycle;
start_pixel_line();
}
if(this_cycle < first_graphics_cycle + 80)
{
unsigned int length_to_output = std::min(final_cycle, (first_graphics_cycle + 80)) - this_cycle;
output_pixels(length_to_output);
output_position_ += length_to_output;
this_cycle += length_to_output;
}
if(this_cycle >= first_graphics_cycle + 80)
{
if(final_cycle < 128) return;
end_pixel_line();
crt_->output_blank((128 - (first_graphics_cycle + 80)) * crt_cycles_multiplier);
output_position_ += 128 - (first_graphics_cycle + 80);
this_cycle = 128;
}
} }
} }
}
output_position_ %= cycles_per_frame; output_position_ %= cycles_per_frame;
}
} }
void VideoOutput::set_register(int address, uint8_t value) void VideoOutput::set_register(int address, uint8_t value)
@ -470,3 +478,15 @@ void VideoOutput::set_register(int address, uint8_t value)
break; break;
} }
} }
#pragma mark - Interrupts
int VideoOutput::get_cycles_until_next_interrupt()
{
return 0;
}
Interrupt VideoOutput::get_next_interrupt()
{
return DisplayEnd;
}