diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index 691f5fd29..49cbc86c1 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -66,7 +66,7 @@ Base::Base(Personality p) : } if(is_sega_vdp(personality_)) { - mode_timing_.line_interrupt_position = 65; + mode_timing_.line_interrupt_position = 64; mode_timing_.end_of_frame_interrupt_position.column = 63; mode_timing_.end_of_frame_interrupt_position.row = 193; @@ -564,7 +564,7 @@ void TMS9918::set_register(int address, uint8_t value) { uint8_t TMS9918::get_current_line() { // Determine the row to return. - static const int row_change_position = 62; // This is the proper Master System value; substitute if any other VDPs turn out to have this functionality. + static const int row_change_position = 63; // This is the proper Master System value; substitute if any other VDPs turn out to have this functionality. int source_row = (write_pointer_.column < row_change_position) ? (write_pointer_.row + mode_timing_.total_lines - 1)%mode_timing_.total_lines @@ -638,23 +638,31 @@ HalfCycles TMS9918::get_time_until_interrupt() { // Calculate the amount of time until the next end-of-frame interrupt. const int frame_length = 342 * mode_timing_.total_lines; - const int time_until_frame_interrupt = + int time_until_frame_interrupt = ( ((mode_timing_.end_of_frame_interrupt_position.row * 342) + mode_timing_.end_of_frame_interrupt_position.column + frame_length) - ((write_pointer_.row * 342) + write_pointer_.column) ) % frame_length; + if(!time_until_frame_interrupt) time_until_frame_interrupt = frame_length; if(!enable_line_interrupts_) return half_cycles_before_internal_cycles(time_until_frame_interrupt); - // Calculate the row upon which the next line interrupt will occur. + // Calculate when the next line interrupt will occur. int next_line_interrupt_row = -1; + int cycles_to_next_interrupt_threshold = mode_timing_.line_interrupt_position - write_pointer_.column; + int line_of_next_interrupt_threshold = write_pointer_.row; + if(cycles_to_next_interrupt_threshold <= 0) { + cycles_to_next_interrupt_threshold += 342; + ++line_of_next_interrupt_threshold; + } + if(is_sega_vdp(personality_)) { // If there is still time for a line interrupt this frame, that'll be it; // otherwise it'll be on the next frame, supposing there's ever time for // it at all. - if(write_pointer_.row+line_interrupt_counter <= mode_timing_.pixel_lines) { - next_line_interrupt_row = write_pointer_.row+line_interrupt_counter; + if(line_of_next_interrupt_threshold + line_interrupt_counter <= mode_timing_.pixel_lines) { + next_line_interrupt_row = line_of_next_interrupt_threshold + line_interrupt_counter; } else { if(line_interrupt_target <= mode_timing_.pixel_lines) next_line_interrupt_row = mode_timing_.total_lines + line_interrupt_target; @@ -671,10 +679,7 @@ HalfCycles TMS9918::get_time_until_interrupt() { // Figure out the number of internal cycles until the next line interrupt, which is the amount // of time to the next tick over and then next_line_interrupt_row - row_ lines further. - int local_cycles_until_next_tick = (mode_timing_.line_interrupt_position - write_pointer_.column + 342) % 342; - if(!local_cycles_until_next_tick) local_cycles_until_next_tick += 342; - const int local_cycles_until_line_interrupt = local_cycles_until_next_tick + (next_line_interrupt_row - write_pointer_.row) * 342; - + const int local_cycles_until_line_interrupt = cycles_to_next_interrupt_threshold + (next_line_interrupt_row - line_of_next_interrupt_threshold) * 342; if(!generate_interrupts_) return half_cycles_before_internal_cycles(local_cycles_until_line_interrupt); // Return whichever interrupt is closer. @@ -687,9 +692,6 @@ bool TMS9918::get_interrupt_line() { // MARK: - -// if(sprite.shift_position > 0 && !sprites_magnified_) -// sprite.shift_position *= 2; - void Base::draw_tms_character(int start, int end) { LineBuffer &line_buffer = line_buffers_[read_pointer_.row]; diff --git a/OSBindings/Mac/Clock SignalTests/MasterSystemVDPTests.mm b/OSBindings/Mac/Clock SignalTests/MasterSystemVDPTests.mm index 5ef2d72b5..de25c951a 100644 --- a/OSBindings/Mac/Clock SignalTests/MasterSystemVDPTests.mm +++ b/OSBindings/Mac/Clock SignalTests/MasterSystemVDPTests.mm @@ -116,4 +116,45 @@ NSAssert(vdp.get_interrupt_line(), @"Interrupt line wasn't set when promised"); } +- (void)testPrediction { + TI::TMS::TMS9918 vdp(TI::TMS::Personality::SMSVDP); + + for(int c = 0; c < 256; ++c) { + for(int with_eof = (c < 192) ? 0 : 1; with_eof < 2; ++with_eof) { + // Enable or disable end-of-frame interrupts as required. + vdp.set_register(1, with_eof ? 0x20 : 0x00); + vdp.set_register(1, 0x81); + + // Enable line interrupts. + vdp.set_register(1, 0x10); + vdp.set_register(1, 0x80); + + // Set the line interrupt timing as desired. + vdp.set_register(1, c); + vdp.set_register(1, 0x8a); + + // Now run through an entire frame... + int half_cycles = 262*224*2; + int last_time_until_interrupt = vdp.get_time_until_interrupt().as_int(); + while(half_cycles--) { + // Validate that an interrupt happened if one was expected, and clear anything that's present. + NSAssert(vdp.get_interrupt_line() == (last_time_until_interrupt == 0), @"Unexpected interrupt state change; expected %d but got %d; position %d %d @ %d", (last_time_until_interrupt == 0), vdp.get_interrupt_line(), c, with_eof, half_cycles); + vdp.get_register(1); + + vdp.run_for(HalfCycles(1)); + + // Get the time until interrupt. + int time_until_interrupt = vdp.get_time_until_interrupt().as_int(); + NSAssert(time_until_interrupt != -1, @"No interrupt scheduled; position %d %d @ %d", c, with_eof, half_cycles); + NSAssert(time_until_interrupt >= 0, @"Interrupt is scheduled in the past; position %d %d @ %d", c, with_eof, half_cycles); + + if(last_time_until_interrupt) { + NSAssert(time_until_interrupt == (last_time_until_interrupt - 1), @"Discontinuity found in interrupt prediction; from %d to %d; position %d %d @ %d", last_time_until_interrupt, time_until_interrupt, c, with_eof, half_cycles); + } + last_time_until_interrupt = time_until_interrupt; + } + } + } +} + @end