diff --git a/Machines/Atari/ST/Video.cpp b/Machines/Atari/ST/Video.cpp index e0f01a5df..1285a2ee4 100644 --- a/Machines/Atari/ST/Video.cpp +++ b/Machines/Atari/ST/Video.cpp @@ -197,10 +197,6 @@ void Video::advance(HalfCycles duration) { } } - // 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) { video_stream_.output(run_length, VideoStream::OutputMode::Sync); } else if(horizontal_.blank || vertical_.blank) { @@ -295,14 +291,17 @@ void Video::advance(HalfCycles duration) { x_ += run_length; integer_duration -= run_length; - // Check horizontal events. + // Check horizontal events; the first six are guaranteed to occur separately. if(horizontal_timings.reset_blank == x_) horizontal_.blank = false; 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_ - 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_ is less predictable; test separately because it may coincide + // with one of the above tests. + if(next_load_toggle_ == x_) { next_load_toggle_ = -1; load_ ^= true; load_base_ = x_; diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme index 7b1a6914b..1465a4f62 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme +++ b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme @@ -23,9 +23,9 @@ diff --git a/OSBindings/Mac/Clock SignalTests/AtariSTVideoTests.mm b/OSBindings/Mac/Clock SignalTests/AtariSTVideoTests.mm index 078c47718..305b732ab 100644 --- a/OSBindings/Mac/Clock SignalTests/AtariSTVideoTests.mm +++ b/OSBindings/Mac/Clock SignalTests/AtariSTVideoTests.mm @@ -8,30 +8,38 @@ #import +#include + #include "../../../Machines/Atari/ST/Video.hpp" @interface AtariSTVideoTests : XCTestCase @end -@implementation AtariSTVideoTests +@implementation AtariSTVideoTests { + std::unique_ptr _video; + uint16_t _ram[256*1024]; +} - (void)setUp { [super setUp]; + + // Establish an instance of video. + _video = std::make_unique(); + _video->set_ram(_ram, sizeof(_ram)); } - (void)tearDown { [super tearDown]; + + // Release the video instance. + _video = nullptr; } +/// Tests that no events occur outside of the sequence points the video predicts. - (void)testSequencePoints { - // Establish an instance of video. - Atari::ST::Video video; - uint16_t ram[256*1024]; - video.set_ram(ram, sizeof(ram)); - // Set 4bpp, 50Hz. - video.write(0x05, 0x0200); - video.write(0x30, 0x0000); + _video->write(0x05, 0x0200); + _video->write(0x30, 0x0000); // Run for [more than] a whole frame making sure that no observeable outputs // change at any time other than a sequence point. @@ -43,18 +51,213 @@ const bool is_transition_point = next_event == HalfCycles(0); if(is_transition_point) { - display_enable = video.display_enabled(); - vsync = video.vsync(); - hsync = video.hsync(); - next_event = video.get_next_sequence_point(); + display_enable = _video->display_enabled(); + vsync = _video->vsync(); + hsync = _video->hsync(); + next_event = _video->get_next_sequence_point(); } else { - NSAssert(display_enable == video.display_enabled(), @"Unannounced change in display enabled at cycle %zu [%d before next sequence point]", c, next_event.as()); - NSAssert(vsync == video.vsync(), @"Unannounced change in vsync at cycle %zu [%d before next sequence point]", c, next_event.as()); - NSAssert(hsync == video.hsync(), @"Unannounced change in hsync at cycle %zu [%d before next sequence point]", c, next_event.as()); + NSAssert(display_enable == _video->display_enabled(), @"Unannounced change in display enabled at cycle %zu [%d before next sequence point]", c, next_event.as()); + NSAssert(vsync == _video->vsync(), @"Unannounced change in vsync at cycle %zu [%d before next sequence point]", c, next_event.as()); + NSAssert(hsync == _video->hsync(), @"Unannounced change in hsync at cycle %zu [%d before next sequence point]", c, next_event.as()); } - video.run_for(HalfCycles(2)); + _video->run_for(HalfCycles(2)); next_event -= HalfCycles(2); } } +- (void)runVideoForCycles:(int)cycles { + while(cycles--) { + _video->run_for(Cycles(1)); + } +} + +- (void)syncToStartOfLine { + // Run until the visible fetch address changes, to get to the start of the pixel zone. + const uint32_t original_address = [self currentVideoAddress]; + while(original_address == [self currentVideoAddress]) { + _video->run_for(Cycles(1)); + } + + // Run until start of hsync. + while(!_video->hsync()) { + _video->run_for(Cycles(1)); + } + + // Run until end of hsync. + while(_video->hsync()) { + _video->run_for(Cycles(1)); + } + + // Run 12 cycles further. + [self runVideoForCycles:8]; +} + +- (void)setFrequency:(int)frequency { + switch(frequency) { + default: + case 50: _video->write(0x05, 0x200); _video->write(0x30, 0x000); break; + case 60: _video->write(0x05, 0x000); _video->write(0x30, 0x000); break; + case 72: _video->write(0x30, 0x200); break; + } +} + +- (uint32_t)currentVideoAddress { + return + (_video->read(0x04) & 0xff) | + ((_video->read(0x03) & 0xff) << 8) | + ((_video->read(0x02) & 0xff) << 16); +} + +struct RunLength { + int frequency; + int length; +}; + +- (void)testSequence:(const RunLength *)sequence targetLength:(int)duration { + [self syncToStartOfLine]; + + const uint32_t start_address = [self currentVideoAddress]; + + while(sequence->frequency != -1) { + [self setFrequency:sequence->frequency]; + [self runVideoForCycles:sequence->length]; + ++sequence; + } + const uint32_t final_address = [self currentVideoAddress]; + + XCTAssertEqual(final_address - start_address, duration); +} + +- (void)testLineLength54 { + // Run as though a regular 50Hz line at least until cycle 52; + // then switch to 72 Hz by 164, and allow the line to finish. + const RunLength test[] = { + {50, 60}, + {72, 452}, + {-1} + }; + [self testSequence:test targetLength:54]; +} + +- (void)testLineLength56 { + // Run as though a regular 60Hz line at least until cycle 52; + // then switch to 72 Hz by 164, and allow the line to finish. + const RunLength test[] = { + {60, 60}, + {72, 452}, + {-1} + }; + [self testSequence:test targetLength:56]; +} + +- (void)testLineLength80 { + // Run a standard 72Hz line. + const RunLength test[] = { + {72, 224}, + {-1} + }; + [self testSequence:test targetLength:80]; +} + +- (void)testLineLengthLong80 { + // Run a 72Hz line with a switch through 50Hz to extend the length to 512 cycles. + const RunLength test[] = { + {72, 50}, + {50, 20}, + {72, 442}, + {-1} + }; + [self testSequence:test targetLength:80]; +} + +- (void)testLineLength158 { + // Transition from 50Hz to 60Hz mid-line. + const RunLength test[] = { + {50, 60}, + {60, 458}, + {-1} + }; + [self testSequence:test targetLength:158]; +} + +- (void)testLineLength160_60Hz { + const RunLength test[] = { + {60, 508}, + {-1} + }; + [self testSequence:test targetLength:160]; +} + +- (void)testLineLength160_50Hz { + const RunLength test[] = { + {50, 512}, + {-1} + }; + [self testSequence:test targetLength:160]; +} + +- (void)testLineLength162 { + // Transition from 60Hz to 50Hz mid-line. + const RunLength test[] = { + {60, 54}, + {50, 458}, + {-1} + }; + [self testSequence:test targetLength:162]; +} + +- (void)testLineLength184 { + // Start off in 72Hz, switch to 60 during pixels. + const RunLength test[] = { + {72, 8}, + {60, 500}, + {-1} + }; + [self testSequence:test targetLength:184]; +} + +- (void)testLineLength186 { + // Start off in 72Hz, switch to 50 during pixels. + const RunLength test[] = { + {72, 8}, + {50, 504}, + {-1} + }; + [self testSequence:test targetLength:186]; +} + +- (void)testLineLength204 { + // Start in 50Hz, avoid DE disable. + const RunLength test[] = { + {50, 374}, + {60, 138}, + {-1} + }; + [self testSequence:test targetLength:204]; +} + +- (void)testLineLength206 { + // Start in 60Hz, get a 50Hz line length, avoid DE disable. + const RunLength test[] = { + {60, 53}, + {50, 3}, // To 56. + {60, 314}, // 370. + {50, 4}, // 374. + {60, 138}, // 512. + {-1} + }; + [self testSequence:test targetLength:206]; +} + +- (void)testLineLength230 { + // Start in 72Hz, avoid DE disable. + const RunLength test[] = { + {72, 8}, + {50, 366}, + {60, 138}, + {-1} + }; + [self testSequence:test targetLength:230]; +} + @end