1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-11-30 04:50:08 +00:00
CLK/OSBindings/Mac/Clock SignalTests/AmigaBlitterTests.mm

348 lines
10 KiB
Plaintext
Raw Normal View History

//
// AmigaBlitterTests.m
// Clock SignalTests
//
// Created by Thomas Harte on 25/09/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#import <XCTest/XCTest.h>
#include "Blitter.hpp"
2022-08-22 14:03:24 +00:00
#include "BlitterSequencer.hpp"
2022-08-08 14:52:55 +00:00
#include "NSData+dataWithContentsOfGZippedFile.h"
#include <unordered_map>
#include <vector>
namespace Amiga {
/// An empty stub to satisfy Amiga::Blitter's inheritance from Amiga::DMADevice;
struct Chipset {
// Hyper ugliness: make a gross assumption about the effect of
// the only call the Blitter will make into the Chipset, i.e.
// that it will write something but do nothing more.
//
// Bonus ugliness: assume the real Chipset struct is 1kb in
// size, at most.
uint8_t _[1024];
};
};
@interface AmigaBlitterTests: XCTestCase
@end
@implementation AmigaBlitterTests
- (void)testCase:(NSString *)name capturedAllBusActivity:(BOOL)capturedAllBusActivity {
2021-09-26 22:15:32 +00:00
uint16_t ram[256 * 1024]{};
Amiga::Chipset nonChipset;
Amiga::Blitter<true> blitter(nonChipset, ram, 256 * 1024);
2022-08-08 14:52:55 +00:00
NSString *const tracePath = [[NSBundle bundleForClass:[self class]]
pathForResource:name ofType:@"json.gz" inDirectory:@"Amiga Blitter Tests"];
NSData *const traceData = [NSData dataWithContentsOfGZippedFile:tracePath];
NSError *error;
NSArray *const trace = [NSJSONSerialization JSONObjectWithData:traceData options:0 error:&error];
XCTAssertNotNil(trace, @"JSON decoding failed with error %@", error);
using TransactionType = Amiga::Blitter<true>::Transaction::Type;
2022-08-06 14:10:19 +00:00
NSUInteger index = -1;
for(NSArray *const event in trace) {
NSString *const type = event[0];
const NSInteger param1 = [event[1] integerValue];
2022-08-06 14:10:19 +00:00
++index;
//
// Register writes. Pass straight along.
//
2022-08-06 18:59:13 +00:00
if(![type isEqualToString:@"cread"] && ![type isEqualToString:@"bread"] && ![type isEqualToString:@"aread"] && ![type isEqualToString:@"write"]) {
2022-08-07 01:52:26 +00:00
// Ensure all blitting is completed between register writes; none of the tests
// in this test set are about illegal usage.
while(blitter.get_status() & 0x4000) {
blitter.advance_dma<false>();
2022-08-07 01:52:26 +00:00
}
const auto transactions = blitter.get_and_reset_transactions();
if(capturedAllBusActivity) {
for(const auto &transaction: transactions) {
if(transaction.type != TransactionType::SkippedSlot && transaction.type != TransactionType::WriteFromPipeline) {
2022-08-07 23:03:56 +00:00
XCTAssert(false, "Unexpected transaction found at index %lu: %s", (unsigned long)index, transaction.to_string().c_str());
return;
}
2022-08-07 01:52:26 +00:00
}
}
2022-08-06 18:59:13 +00:00
}
if([type isEqualToString:@"bltcon0"]) {
blitter.set_control(0, param1);
continue;
}
if([type isEqualToString:@"bltcon1"]) {
blitter.set_control(1, param1);
continue;
}
if([type isEqualToString:@"bltsize"]) {
blitter.set_size(param1);
continue;
}
if([type isEqualToString:@"bltafwm"]) {
blitter.set_first_word_mask(param1);
continue;
}
if([type isEqualToString:@"bltalwm"]) {
blitter.set_last_word_mask(param1);
continue;
}
if([type isEqualToString:@"bltadat"]) {
blitter.set_data(0, param1);
continue;
}
if([type isEqualToString:@"bltbdat"]) {
blitter.set_data(1, param1);
continue;
}
if([type isEqualToString:@"bltcdat"]) {
blitter.set_data(2, param1);
2021-09-26 22:15:32 +00:00
continue;
}
if([type isEqualToString:@"bltamod"]) {
2021-10-29 18:29:22 +00:00
blitter.set_modulo<0>(param1);
2021-09-26 22:15:32 +00:00
continue;
}
if([type isEqualToString:@"bltbmod"]) {
2021-10-29 18:29:22 +00:00
blitter.set_modulo<1>(param1);
2021-09-26 22:15:32 +00:00
continue;
}
if([type isEqualToString:@"bltcmod"]) {
2021-10-29 18:29:22 +00:00
blitter.set_modulo<2>(param1);
2021-09-26 22:15:32 +00:00
continue;
}
if([type isEqualToString:@"bltdmod"]) {
2021-10-29 18:29:22 +00:00
blitter.set_modulo<3>(param1);
continue;
}
if([type isEqualToString:@"bltaptl"]) {
blitter.set_pointer<0, 0>(param1);
continue;
}
if([type isEqualToString:@"bltbptl"]) {
blitter.set_pointer<1, 0>(param1);
continue;
}
if([type isEqualToString:@"bltcptl"]) {
blitter.set_pointer<2, 0>(param1);
continue;
}
if([type isEqualToString:@"bltdptl"]) {
blitter.set_pointer<3, 0>(param1);
continue;
}
if([type isEqualToString:@"bltapth"]) {
blitter.set_pointer<0, 16>(param1);
continue;
}
if([type isEqualToString:@"bltbpth"]) {
blitter.set_pointer<1, 16>(param1);
continue;
}
if([type isEqualToString:@"bltcpth"]) {
blitter.set_pointer<2, 16>(param1);
continue;
}
if([type isEqualToString:@"bltdpth"]) {
blitter.set_pointer<3, 16>(param1);
continue;
}
//
// Bus activity. Store as initial state, and translate for comparison.
//
Amiga::Blitter<true>::Transaction expected_transaction;
expected_transaction.address = uint32_t(param1 >> 1);
expected_transaction.value = uint16_t([event[2] integerValue]);
if([type isEqualToString:@"cread"] || [type isEqualToString:@"bread"] || [type isEqualToString:@"aread"]) {
XCTAssert(param1 < sizeof(ram) - 1);
ram[param1 >> 1] = [event[2] integerValue];
if([type isEqualToString:@"aread"]) expected_transaction.type = TransactionType::ReadA;
if([type isEqualToString:@"bread"]) expected_transaction.type = TransactionType::ReadB;
if([type isEqualToString:@"cread"]) expected_transaction.type = TransactionType::ReadC;
} else if([type isEqualToString:@"write"]) {
2022-08-06 14:11:26 +00:00
expected_transaction.type = TransactionType::AddToPipeline;
} else {
NSLog(@"Unhandled type: %@", type);
XCTAssert(false);
break;
}
// Loop until another [comparable] bus transaction appears, and test.
while(true) {
2022-08-06 18:42:09 +00:00
if(!(blitter.get_status() & 0x4000)) {
2022-08-06 18:47:24 +00:00
XCTAssert(false, @"Blitter terminated early at index %lu; waiting for %s", (unsigned long)index, expected_transaction.to_string().c_str());
2022-08-06 18:42:09 +00:00
return;
}
blitter.advance_dma<false>();
const auto transactions = blitter.get_and_reset_transactions();
if(transactions.empty()) {
continue;
}
bool did_compare = false;
for(const auto &transaction : transactions) {
// Skipped slots and data coming out of the pipeline aren't captured
// by the original test data.
switch(transaction.type) {
case TransactionType::SkippedSlot:
case TransactionType::WriteFromPipeline:
continue;
default: break;
}
2022-08-06 18:40:12 +00:00
XCTAssertEqual(transaction.type, expected_transaction.type, @"Type mismatch at index %lu: %s expected vs %s found", (unsigned long)index, expected_transaction.to_string().c_str(), transaction.to_string().c_str());
XCTAssertEqual(transaction.value, expected_transaction.value, @"Value mismatch at index %lu: %s expected vs %s found", (unsigned long)index, expected_transaction.to_string().c_str(), transaction.to_string().c_str());
XCTAssertEqual(transaction.address, expected_transaction.address, @"Address mismatch at index %lu: %s expected vs %s found", (unsigned long)index, expected_transaction.to_string().c_str(), transaction.to_string().c_str());
2022-08-06 14:10:19 +00:00
if(
transaction.type != expected_transaction.type ||
transaction.value != expected_transaction.value ||
transaction.address != expected_transaction.address) {
return;
}
did_compare = true;
}
if(did_compare) break;
}
}
}
2021-10-26 04:48:43 +00:00
- (void)testGadgetToggle {
[self testCase:@"gadget toggle" capturedAllBusActivity:YES];
2021-10-26 04:48:43 +00:00
}
- (void)testIconHighlight {
[self testCase:@"icon highlight" capturedAllBusActivity:NO];
2021-10-26 04:48:43 +00:00
}
- (void)testKickstart13BootLogo {
[self testCase:@"kickstart13 boot logo" capturedAllBusActivity:YES];
}
2021-10-26 04:48:43 +00:00
- (void)testSectorDecode {
[self testCase:@"sector decode" capturedAllBusActivity:YES];
}
2021-10-26 04:48:43 +00:00
- (void)testWindowDrag {
[self testCase:@"window drag" capturedAllBusActivity:NO];
}
2021-10-27 03:58:38 +00:00
- (void)testWindowResize {
[self testCase:@"window resize" capturedAllBusActivity:NO];
2021-10-27 03:58:38 +00:00
}
- (void)testRAMDiskOpen {
[self testCase:@"RAM disk open" capturedAllBusActivity:NO];
2021-10-27 03:58:38 +00:00
}
2021-10-31 21:08:37 +00:00
- (void)testSpots {
[self testCase:@"spots" capturedAllBusActivity:YES];
2021-10-31 21:08:37 +00:00
}
- (void)testClock {
[self testCase:@"clock" capturedAllBusActivity:NO];
}
- (void)testInclusiveFills {
[self testCase:@"inclusive fills" capturedAllBusActivity:YES];
}
2022-08-07 01:47:36 +00:00
- (void)testAddamsFamilyIntro {
[self testCase:@"Addams Family Intro" capturedAllBusActivity:YES];
2022-08-07 01:47:36 +00:00
}
2022-08-08 01:27:11 +00:00
- (void)testSpindizzyWorlds {
2022-08-08 15:01:07 +00:00
[self testCase:@"Spindizzy Worlds" capturedAllBusActivity:YES];
2022-08-08 01:27:11 +00:00
}
- (void)testSequencer {
// These patterns are faithfully transcribed from the HRM's
// 'Pipeline Register' section, as captured online at
// http://www.amigadev.elowar.com/read/ADCD_2.1/Hardware_Manual_guide/node0127.html
NSArray<NSString *> *const patterns = @[
2022-07-27 01:47:02 +00:00
/* 0 */ @"- - - -",
/* 1 */ @"D0 - D1 - D2",
/* 2 */ @"C0 - C1 - C2",
/* 3 */ @"C0 - - C1 D0 - C2 D1 - D2",
/* 4 */ @"B0 - - B1 - - B2",
/* 5 */ @"B0 - - B1 D0 - B2 D1 - D2",
/* 6 */ @"B0 C0 - B1 C1 - B2 C2",
/* 7 */ @"B0 C0 - - B1 C1 D0 - B2 C2 D1 - D2",
/* 8 */ @"A0 - A1 - A2",
/* 9 */ @"A0 - A1 D0 A2 D1 - D2",
/* A */ @"A0 C0 A1 C1 A2 C2",
/* B */ @"A0 C0 - A1 C1 D0 A2 C2 D1 - D2",
/* C */ @"A0 B0 - A1 B1 - A2 B2",
/* D */ @"A0 B0 - A1 B1 D0 A2 B2 D1 - D2",
/* E */ @"A0 B0 C0 A1 B1 C1 A2 B2 C2",
/* F */ @"A0 B0 C0 - A1 B1 C1 D0 A2 B2 C2 D1 D2",
];
for(int c = 0; c < 16; c++) {
Amiga::BlitterSequencer sequencer;
sequencer.set_control(c);
sequencer.begin();
int writes = 0;
2022-07-27 01:47:02 +00:00
NSUInteger length = [[patterns[c] componentsSeparatedByString:@" "] count];
bool is_first_write = c > 1; // control = 1 is D only, in which case don't pipeline.
NSMutableArray<NSString *> *const components = [[NSMutableArray alloc] init];
while(length--) {
const auto next = sequencer.next();
using Channel = Amiga::BlitterSequencer::Channel;
switch(next.first) {
case Channel::None: [components addObject:@"-"]; break;
case Channel::A: [components addObject:[NSString stringWithFormat:@"A%d", next.second]]; break;
case Channel::B: [components addObject:[NSString stringWithFormat:@"B%d", next.second]]; break;
case Channel::C: [components addObject:[NSString stringWithFormat:@"C%d", next.second]]; break;
case Channel::Write:
case Channel::FlushPipeline:
if(is_first_write) {
is_first_write = false;
[components addObject:@"-"];
} else {
[components addObject:[NSString stringWithFormat:@"D%d", writes++]];
}
break;
default: break;
}
if(next.second == 2) {
sequencer.complete();
}
}
NSString *pattern = [components componentsJoinedByString:@" "];
2022-07-27 01:47:02 +00:00
XCTAssertEqualObjects(
pattern,
patterns[c],
@"Pattern didn't match for control value %x", c);
}
}
@end