mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-27 16:31:31 +00:00
387 lines
8.5 KiB
Plaintext
387 lines
8.5 KiB
Plaintext
//
|
|
// MasterSystemVDPTests.m
|
|
// Clock SignalTests
|
|
//
|
|
// Created by Thomas Harte on 09/10/2018.
|
|
// Copyright © 2018 Thomas Harte. All rights reserved.
|
|
//
|
|
|
|
#import <XCTest/XCTest.h>
|
|
|
|
#include <memory>
|
|
|
|
#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<Atari::ST::Video> _video;
|
|
uint16_t _ram[256*1024];
|
|
}
|
|
|
|
// MARK: - Setup and tear down.
|
|
|
|
- (void)setUp {
|
|
[super setUp];
|
|
|
|
// Establish an instance of video.
|
|
_video = std::make_unique<Atari::ST::Video>();
|
|
_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->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
|
|
|
|
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
|