// // MasterSystemVDPTests.m // Clock SignalTests // // Created by Thomas Harte on 09/10/2018. // Copyright © 2018 Thomas Harte. All rights reserved. // #import #include #include "../../../Machines/Atari/ST/Video.hpp" // Implement Atari::ST::Video's friend class, to expose some // internal state. struct VideoTester { static bool vsync(Atari::ST::Video &video) { return video.vertical_.sync; } }; @interface AtariSTVideoTests : XCTestCase @end @implementation AtariSTVideoTests { std::unique_ptr _video; uint16_t _ram[256*1024]; } // MARK: - Setup and tear down. - (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; } // MARK: - Helpers - (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)); } } - (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); } - (void)setVideoBaseAddress:(uint32_t)baseAddress { _video->write(0x00, baseAddress >> 16); _video->write(0x01, baseAddress >> 8); } // MARK: - Sequence Point Prediction Tests /// Tests that no events occur outside of the sequence points the video predicts. - (void)testSequencePoints50 { // Set 4bpp, 50Hz. _video->write(0x05, 0x0200); _video->write(0x30, 0x0000); [self runSequencePointsTest]; } - (void)testSequencePoints72 { // Set 1bpp, 72Hz. _video->write(0x30, 0x0200); [self runSequencePointsTest]; } - (void)runSequencePointsTest { // Run for [more than] two frames making sure that no observeable outputs // change at any time other than a sequence point. HalfCycles next_event; bool display_enable = false; bool vsync = false; bool hsync = false; for(size_t c = 0; c < 8000000 / 20; ++c) { 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->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()); } _video->run_for(HalfCycles(2)); next_event -= HalfCycles(2); } } // MARK: - Sync Line Length Tests 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]; } // MARK: - Address Reload Timing tests /// Tests that the current video address is reloaded constantly throughout vsync (subject to caveat: observed . - (void)testVsyncReload { // Set an initial video address of 0. [self setVideoBaseAddress:0]; // Find next area of non-vsync. while(VideoTester::vsync(*_video)) { _video->run_for(Cycles(1)); } // Set a different base video address. [self setVideoBaseAddress:0x800000]; // Find next area of vsync, checking that the address isn't // reloaded before then. while(!VideoTester::vsync(*_video)) { XCTAssertNotEqual([self currentVideoAddress], 0x800000); _video->run_for(Cycles(1)); } // Vsync has now started, test that video address has been set. XCTAssertEqual([self currentVideoAddress], 0x800000); // Run a few cycles, set a different video base address, // confirm that has been set. [self runVideoForCycles:200]; XCTAssertEqual([self currentVideoAddress], 0x800000); [self setVideoBaseAddress:0xc00000]; [self runVideoForCycles:1]; XCTAssertEqual([self currentVideoAddress], 0xc00000); // Find end of vertical sync, set a different base address, // check that it doesn't become current. while(VideoTester::vsync(*_video)) { _video->run_for(Cycles(1)); } [self setVideoBaseAddress:0]; [self runVideoForCycles:1]; XCTAssertNotEqual([self currentVideoAddress], 0); } // MARK: - Tests Relating To Specific Bugs - (void)test72LineLength { // Set 1bpp, 72Hz. _video->write(0x30, 0x0200); [self syncToStartOfLine]; _video->run_for(HalfCycles(400)); // 392, 399, 406 } // MARK: - Tests Correlating To Exact Pieces of Software - (void)testUnionDemoScroller { const RunLength test[] = { {72, 8}, {50, 365}, {60, 8}, {50, 59}, {72, 12}, {50, 60}, {-1} }; [self testSequence:test targetLength:230]; } - (void)testPP88 { // Test a full line. { const RunLength test[] = { {72, 8}, {50, 364}, {60, 16}, {50, 116}, {72, 8}, {-1} }; [self testSequence:test targetLength:230]; } { const RunLength test[] = { {72, 8}, {496, 50}, {72, 8}, {-1} }; [self testSequence:test targetLength:186]; } } @end