2019-11-20 02:54:13 +00:00
//
// MasterSystemVDPTests.m
// Clock SignalTests
//
// Created by Thomas Harte on 09/10/2018.
// Copyright © 2018 Thomas Harte. All rights reserved.
//
#import <XCTest/XCTest.h>
2019-12-29 03:50:34 +00:00
#include <memory>
2019-11-20 02:54:13 +00:00
#include "../../../Machines/Atari/ST/Video.hpp"
2019-12-30 01:55:34 +00:00
// 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;
}
};
2019-11-20 02:54:13 +00:00
@interface AtariSTVideoTests : XCTestCase
@end
2019-12-29 03:50:34 +00:00
@implementation AtariSTVideoTests {
std::unique_ptr<Atari::ST::Video> _video;
uint16_t _ram[256*1024];
}
2019-11-20 02:54:13 +00:00
2019-12-29 22:37:09 +00:00
// MARK: - Setup and tear down.
2019-11-20 02:54:13 +00:00
- (void)setUp {
[super setUp];
2019-12-29 03:50:34 +00:00
// Establish an instance of video.
_video = std::make_unique<Atari::ST::Video>();
_video->set_ram(_ram, sizeof(_ram));
2019-11-20 02:54:13 +00:00
}
- (void)tearDown {
[super tearDown];
2019-12-29 03:50:34 +00:00
// Release the video instance.
_video = nullptr;
2019-11-20 02:54:13 +00:00
}
2019-12-29 22:37:09 +00:00
// MARK: - Helpers
2019-11-20 02:54:13 +00:00
2019-12-29 03:50:34 +00:00
- (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);
}
2019-12-29 22:37:09 +00:00
- (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.
2020-01-29 01:27:24 +00:00
- (void)testSequencePoints50 {
2019-12-29 22:37:09 +00:00
// Set 4bpp, 50Hz.
_video->write(0x05, 0x0200);
_video->write(0x30, 0x0000);
2020-01-29 01:27:24 +00:00
[self runSequencePointsTest];
}
- (void)testSequencePoints72 {
// Set 1bpp, 72Hz.
_video->write(0x30, 0x0200);
[self runSequencePointsTest];
}
- (void)runSequencePointsTest {
2020-01-29 04:25:01 +00:00
// Run for [more than] two frames making sure that no observeable outputs
2019-12-29 22:37:09 +00:00
// change at any time other than a sequence point.
HalfCycles next_event;
bool display_enable = false;
bool vsync = false;
bool hsync = false;
2020-01-29 04:25:01 +00:00
for(size_t c = 0; c < 8000000 / 20; ++c) {
2019-12-29 22:37:09 +00:00
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();
} else {
NSAssert(display_enable == _video->display_enabled(), @"Unannounced change in display enabled at cycle %zu [%d before next sequence point]", c, next_event.as<int>());
NSAssert(vsync == _video->vsync(), @"Unannounced change in vsync at cycle %zu [%d before next sequence point]", c, next_event.as<int>());
NSAssert(hsync == _video->hsync(), @"Unannounced change in hsync at cycle %zu [%d before next sequence point]", c, next_event.as<int>());
}
_video->run_for(HalfCycles(2));
next_event -= HalfCycles(2);
}
}
// MARK: - Sync Line Length Tests
2019-12-29 03:50:34 +00:00
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];
}
2019-12-29 22:37:09 +00:00
// MARK: - Address Reload Timing tests
2019-12-30 01:55:34 +00:00
/// Tests that the current video address is reloaded constantly throughout vsync (subject to caveat: observed .
- (void)testVsyncReload {
2019-12-29 22:37:09 +00:00
// Set an initial video address of 0.
[self setVideoBaseAddress:0];
// Find next area of non-vsync.
2019-12-30 01:55:34 +00:00
while(VideoTester::vsync(*_video)) {
2019-12-29 22:37:09 +00:00
_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.
2019-12-30 01:55:34 +00:00
while(!VideoTester::vsync(*_video)) {
2019-12-29 22:37:09 +00:00
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.
2019-12-30 01:55:34 +00:00
while(VideoTester::vsync(*_video)) {
2019-12-29 22:37:09 +00:00
_video->run_for(Cycles(1));
}
[self setVideoBaseAddress:0];
[self runVideoForCycles:1];
XCTAssertNotEqual([self currentVideoAddress], 0);
}
2020-02-02 22:26:39 +00:00
// 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
}
2019-12-29 22:48:43 +00:00
// 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];
}
2020-01-03 04:14:05 +00:00
- (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];
}
}
2019-11-20 02:54:13 +00:00
@end