1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-09 06:29:33 +00:00

Merge pull request #22 from TomHarte/6522UnitTests

Starts adding unit tests for the 6522, correcting the implementation as required
This commit is contained in:
Thomas Harte 2016-06-18 17:22:23 -04:00 committed by GitHub
commit 960cae15b1
9 changed files with 338 additions and 56 deletions

View File

@ -14,11 +14,6 @@
namespace MOS {
class MOS6522Delegate {
public:
virtual void mos6522_did_change_interrupt_status(void *mos6522) = 0;
};
template <class T> class MOS6522 {
private:
enum InterruptFlag: uint8_t {
@ -40,17 +35,17 @@ template <class T> class MOS6522 {
{
case 0x0:
_registers.output[1] = value;
static_cast<T *>(this)->set_port_output(1, value); // TODO: handshake
break;
case 0x1:
_registers.output[0] = value;
static_cast<T *>(this)->set_port_output(0, value); // TODO: handshake
static_cast<T *>(this)->set_port_output(1, value, _registers.data_direction[1]); // TODO: handshake
break;
case 0xf:
// No handshake, so write directly
case 0x1:
_registers.output[0] = value;
static_cast<T *>(this)->set_port_output(0, value);
static_cast<T *>(this)->set_port_output(0, value, _registers.data_direction[0]); // TODO: handshake
break;
// // No handshake, so write directly
// _registers.output[0] = value;
// static_cast<T *>(this)->set_port_output(0, value);
// break;
case 0x2:
_registers.data_direction[1] = value;
@ -109,10 +104,9 @@ template <class T> class MOS6522 {
// printf("6522 %p: %d\n", this, address);
switch(address)
{
// case 0x0: return (_registers.auxiliary_control & 0x40) ? _registers.input[1] : static_cast<T *>(this)->get_port_input(1);
case 0x0: return _registers.output[1];//static_cast<T *>(this)->get_port_input(1);
case 0x0: return get_port_input(1, _registers.data_direction[1], _registers.output[1]);
case 0xf: // TODO: handshake, latching
case 0x1: return static_cast<T *>(this)->get_port_input(0);
case 0x1: return get_port_input(0, _registers.data_direction[0], _registers.output[0]);
case 0x2: return _registers.data_direction[1];
case 0x3: return _registers.data_direction[0];
@ -149,30 +143,49 @@ template <class T> class MOS6522 {
{
}
void run_for_cycles(unsigned int number_of_cycles)
void run_for_half_cycles(unsigned int number_of_cycles)
{
_registers.timer[0] -= number_of_cycles;
_registers.timer[1] -= number_of_cycles;
if(!_registers.timer[1] && _timer_is_running[1])
while(number_of_cycles--)
{
_timer_is_running[1] = false;
_registers.interrupt_flags |= InterruptFlag::Timer2;
reevaluate_interrupts();
}
if(_is_phase2)
{
_registers.last_timer[0] = _registers.timer[0];
_registers.last_timer[1] = _registers.timer[1];
if(!_registers.timer[0] && _timer_is_running[0])
{
_registers.interrupt_flags |= InterruptFlag::Timer1;
reevaluate_interrupts();
if(_registers.timer_needs_reload)
{
_registers.timer_needs_reload = false;
_registers.timer[0] = _registers.timer_latch[0];
}
else
_registers.timer[0] --;
// TODO: reload shouldn't occur for a further 1.5 cycles
if(_registers.auxiliary_control&0x40)
_registers.timer[0] = _registers.timer_latch[0];
_registers.timer[1] --;
}
else
_timer_is_running[0] = false;
{
// IRQ is raised on the half cycle after overflow
if((_registers.timer[1] == 0xffff) && !_registers.last_timer[1] && _timer_is_running[1])
{
_timer_is_running[1] = false;
_registers.interrupt_flags |= InterruptFlag::Timer2;
reevaluate_interrupts();
}
if((_registers.timer[0] == 0xffff) && !_registers.last_timer[0] && _timer_is_running[0])
{
_registers.interrupt_flags |= InterruptFlag::Timer1;
reevaluate_interrupts();
if(_registers.auxiliary_control&0x40)
_registers.timer_needs_reload = true;
else
_timer_is_running[0] = false;
}
}
_is_phase2 ^= true;
}
// TODO: lots of other status effects
}
bool get_interrupt_line()
@ -181,23 +194,29 @@ template <class T> class MOS6522 {
return !!interrupt_status;
}
void set_delegate(MOS6522Delegate *delegate)
{
_delegate = delegate;
}
MOS6522() :
_timer_is_running{false, false},
_last_posted_interrupt_status(false)
_last_posted_interrupt_status(false),
_is_phase2(false)
{}
private:
// Intended to be overwritten
uint8_t get_port_input(int port) { return 0xff; }
void set_port_output(int port, uint8_t value) {}
// Expected to be overridden
uint8_t get_port_input(int port) { return 0xff; }
void set_port_output(int port, uint8_t value, uint8_t direction_mask) {}
// void set_interrupt_status(bool status) {}
// Input/output multiplexer
uint8_t get_port_input(int port, uint8_t output_mask, uint8_t output)
{
uint8_t input = static_cast<T *>(this)->get_port_input(port);
return (input & ~output_mask) | (output & output_mask);
}
// Phase toggle
bool _is_phase2;
// Delegate and communications
MOS6522Delegate *_delegate;
bool _last_posted_interrupt_status;
inline void reevaluate_interrupts()
{
@ -205,29 +224,52 @@ template <class T> class MOS6522 {
if(new_interrupt_status != _last_posted_interrupt_status)
{
_last_posted_interrupt_status = new_interrupt_status;
if(_delegate) _delegate->mos6522_did_change_interrupt_status(this);
static_cast<T *>(this)->set_interrupt_status(new_interrupt_status);
}
}
// The registers
struct Registers {
uint8_t output[2], input[2], data_direction[2];
uint16_t timer[2], timer_latch[2];
uint16_t timer[2], timer_latch[2], last_timer[2];
uint8_t shift;
uint8_t auxiliary_control, peripheral_control;
uint8_t interrupt_flags, interrupt_enable;
bool timer_needs_reload;
// "A low reset (RES) input clears all R6522 internal registers to logic 0"
Registers() :
output{0, 0}, input{0, 0}, data_direction{0, 0},
auxiliary_control(0), peripheral_control(0),
interrupt_flags(0), interrupt_enable(0) {}
interrupt_flags(0), interrupt_enable(0),
last_timer{0, 0}, timer_needs_reload(false) {}
} _registers;
// Internal state other than the registers
bool _timer_is_running[2];
};
class MOS6522IRQDelegate {
public:
class Delegate {
public:
virtual void mos6522_did_change_interrupt_status(void *mos6522) = 0;
};
void set_delegate(Delegate *delegate)
{
_delegate = delegate;
}
void set_interrupt_status(bool new_status)
{
if(_delegate) _delegate->mos6522_did_change_interrupt_status(this);
}
private:
Delegate *_delegate;
};
}
#endif /* _522_hpp */

View File

@ -79,8 +79,8 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
}
}
_userPortVIA.run_for_cycles(1);
_keyboardVIA.run_for_cycles(1);
_userPortVIA.run_for_half_cycles(2);
_keyboardVIA.run_for_half_cycles(2);
return 1;
}

View File

@ -44,10 +44,10 @@ enum Key: uint16_t {
Key9 = key(0, 0x10), KeyPlus = key(0, 0x20), KeyGBP = key(0, 0x40), KeyDelete = key(0, 0x80),
};
class UserPortVIA: public MOS::MOS6522<UserPortVIA> {
class UserPortVIA: public MOS::MOS6522<UserPortVIA>, public MOS::MOS6522IRQDelegate {
};
class KeyboardVIA: public MOS::MOS6522<KeyboardVIA> {
class KeyboardVIA: public MOS::MOS6522<KeyboardVIA>, public MOS::MOS6522IRQDelegate {
public:
void set_key_state(Key key, bool isPressed) {
if(isPressed)
@ -75,9 +75,9 @@ class KeyboardVIA: public MOS::MOS6522<KeyboardVIA> {
return 0xff;
}
void set_port_output(int port, uint8_t value) {
void set_port_output(int port, uint8_t value, uint8_t mask) {
if(port)
_activation_mask = value;
_activation_mask = (value & mask) | (~mask);
}
KeyboardVIA() {
@ -88,7 +88,7 @@ class KeyboardVIA: public MOS::MOS6522<KeyboardVIA> {
uint8_t _activation_mask;
};
class Machine: public CPU6502::Processor<Machine>, public CRTMachine::Machine, public MOS::MOS6522Delegate {
class Machine: public CPU6502::Processor<Machine>, public CRTMachine::Machine, public MOS::MOS6522IRQDelegate::Delegate {
public:
Machine();
~Machine();

View File

@ -313,6 +313,8 @@
4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */; };
4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC3B74D1CD194CC00F86E85 /* Shader.cpp */; };
4BC3B7521CD1956900F86E85 /* OutputShader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC3B7501CD1956900F86E85 /* OutputShader.cpp */; };
4BC751B21D157E61006C31D9 /* 6522Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC751B11D157E61006C31D9 /* 6522Tests.swift */; };
4BC751B61D157EB3006C31D9 /* MOS6522Bridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BC751B51D157EB3006C31D9 /* MOS6522Bridge.mm */; };
4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */; };
4BC76E6B1C98F43700E6EF73 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */; };
4BC9DF451D044FCA00F44158 /* ROMImages in Resources */ = {isa = PBXBuildFile; fileRef = 4BC9DF441D044FCA00F44158 /* ROMImages */; };
@ -685,6 +687,9 @@
4BC3B74E1CD194CC00F86E85 /* Shader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Shader.hpp; sourceTree = "<group>"; };
4BC3B7501CD1956900F86E85 /* OutputShader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OutputShader.cpp; sourceTree = "<group>"; };
4BC3B7511CD1956900F86E85 /* OutputShader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OutputShader.hpp; sourceTree = "<group>"; };
4BC751B11D157E61006C31D9 /* 6522Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6522Tests.swift; sourceTree = "<group>"; };
4BC751B41D157EB3006C31D9 /* MOS6522Bridge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MOS6522Bridge.h; sourceTree = "<group>"; };
4BC751B51D157EB3006C31D9 /* MOS6522Bridge.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MOS6522Bridge.mm; sourceTree = "<group>"; };
4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FIRFilter.cpp; sourceTree = "<group>"; };
4BC76E681C98E31700E6EF73 /* FIRFilter.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FIRFilter.hpp; sourceTree = "<group>"; };
4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; };
@ -1214,15 +1219,18 @@
4BB73EB51B587A5100552FC2 /* Clock SignalTests */ = {
isa = PBXGroup;
children = (
4BB73EB81B587A5100552FC2 /* Info.plist */,
4BB297DF1B587D8200A49093 /* Clock SignalTests-Bridging-Header.h */,
4B1414631B588A1100E04248 /* Test Binaries */,
4BC751B41D157EB3006C31D9 /* MOS6522Bridge.h */,
4BB297E21B587D8300A49093 /* TestMachine.h */,
4BC751B51D157EB3006C31D9 /* MOS6522Bridge.mm */,
4BB297E31B587D8300A49093 /* TestMachine.mm */,
4BB73EB81B587A5100552FC2 /* Info.plist */,
4BC751B11D157E61006C31D9 /* 6522Tests.swift */,
4BB73EB61B587A5100552FC2 /* AllSuiteATests.swift */,
4B1414611B58888700E04248 /* KlausDormannTests.swift */,
4B14145F1B58885000E04248 /* WolfgangLorenzTests.swift */,
4B92EAC91B7C112B00246143 /* TimingTests.swift */,
4B14145F1B58885000E04248 /* WolfgangLorenzTests.swift */,
4B1414631B588A1100E04248 /* Test Binaries */,
);
path = "Clock SignalTests";
sourceTree = "<group>";
@ -1774,10 +1782,12 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
4BC751B61D157EB3006C31D9 /* MOS6522Bridge.mm in Sources */,
4B14145E1B5887AA00E04248 /* CPU6502AllRAM.cpp in Sources */,
4B14145D1B5887A600E04248 /* CPU6502.cpp in Sources */,
4B92EACA1B7C112B00246143 /* TimingTests.swift in Sources */,
4BB73EB71B587A5100552FC2 /* AllSuiteATests.swift in Sources */,
4BC751B21D157E61006C31D9 /* 6522Tests.swift in Sources */,
4B1414621B58888700E04248 /* KlausDormannTests.swift in Sources */,
4BB298F01B587D8400A49093 /* TestMachine.mm in Sources */,
4B1414601B58885000E04248 /* WolfgangLorenzTests.swift in Sources */,

View File

@ -0,0 +1,113 @@
//
// 6522Tests.swift
// Clock Signal
//
// Created by Thomas Harte on 18/06/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
import XCTest
import Foundation
class MOS6522Tests: XCTestCase {
private func with6522(action: (MOS6522Bridge) -> ()) {
let bridge = MOS6522Bridge()
action(bridge)
}
// MARK: Timer tests
func testTimerCount() {
with6522 {
// set timer 1 to a value of $000a
$0.setValue(10, forRegister: 4)
$0.setValue(0, forRegister: 5)
// run for 5 cycles
$0.runForHalfCycles(10)
// check that the timer has gone down by 5
XCTAssert($0.valueForRegister(4) == 5, "Low order byte should be 5; was \($0.valueForRegister(4))")
XCTAssert($0.valueForRegister(5) == 0, "High order byte should be 0; was \($0.valueForRegister(5))")
}
}
func testTimerLatches() {
with6522 {
// set timer 2 to $1020
$0.setValue(0x10, forRegister: 8)
$0.setValue(0x20, forRegister: 9)
// change the low-byte latch
$0.setValue(0x40, forRegister: 8)
// chek that the new latched value hasn't been copied
XCTAssert($0.valueForRegister(8) == 0x10, "Low order byte should be 0x10; was \($0.valueForRegister(8))")
XCTAssert($0.valueForRegister(9) == 0x20, "High order byte should be 0x20; was \($0.valueForRegister(9))")
// write the low-byte latch
$0.setValue(0x50, forRegister: 9)
// chek that the latched value has been copied
XCTAssert($0.valueForRegister(8) == 0x40, "Low order byte should be 0x50; was \($0.valueForRegister(8))")
XCTAssert($0.valueForRegister(9) == 0x50, "High order byte should be 0x40; was \($0.valueForRegister(9))")
}
}
func testTimerReload() {
with6522 {
// set timer 1 to a value of $0010, enable repeating mode
$0.setValue(16, forRegister: 4)
$0.setValue(0, forRegister: 5)
$0.setValue(0x40, forRegister: 11)
$0.setValue(0x40 | 0x80, forRegister: 14)
// run for 16 cycles
$0.runForHalfCycles(32)
// check that the timer has gone down to 0 but not yet triggered an interrupt
XCTAssert($0.valueForRegister(4) == 0, "Low order byte should be 0; was \($0.valueForRegister(4))")
XCTAssert($0.valueForRegister(5) == 0, "High order byte should be 0; was \($0.valueForRegister(5))")
XCTAssert(!$0.irqLine, "IRQ should not yet be active")
// check that two half-cycles later the timer is $ffff but IRQ still hasn't triggered
$0.runForHalfCycles(2)
XCTAssert($0.valueForRegister(4) == 0xff, "Low order byte should be 0xff; was \($0.valueForRegister(4))")
XCTAssert($0.valueForRegister(5) == 0xff, "High order byte should be 0xff; was \($0.valueForRegister(5))")
XCTAssert(!$0.irqLine, "IRQ should not yet be active")
// check that one half-cycle later the timer is still $ffff and IRQ has triggered...
$0.runForHalfCycles(1)
XCTAssert($0.irqLine, "IRQ should be active")
XCTAssert($0.valueForRegister(4) == 0xff, "Low order byte should be 0xff; was \($0.valueForRegister(4))")
XCTAssert($0.valueForRegister(5) == 0xff, "High order byte should be 0xff; was \($0.valueForRegister(5))")
// ... but that reading the timer cleared the interrupt
XCTAssert(!$0.irqLine, "IRQ should be active")
// check that one half-cycles later the timer has reloaded
$0.runForHalfCycles(1)
XCTAssert($0.valueForRegister(4) == 0x10, "Low order byte should be 0x10; was \($0.valueForRegister(4))")
XCTAssert($0.valueForRegister(5) == 0x00, "High order byte should be 0x00; was \($0.valueForRegister(5))")
}
}
// MARK: Data direction tests
func testDataDirection() {
with6522 {
// set low four bits of register B as output, the top four as input
$0.setValue(0xf0, forRegister: 2)
// ask to output 0x8c
$0.setValue(0x8c, forRegister: 0)
// set current input as 0xda
$0.portBInput = 0xda
// test that the result of reading register B is therefore 0x8a
XCTAssert($0.valueForRegister(0) == 0x8a, "Data direction register should mix input and output; got \($0.valueForRegister(0))")
}
}
}

View File

@ -3,3 +3,4 @@
//
#import "TestMachine.h"
#import "MOS6522Bridge.h"

View File

@ -0,0 +1,22 @@
//
// MOS6522Bridge.h
// Clock Signal
//
// Created by Thomas Harte on 18/06/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface MOS6522Bridge : NSObject
@property (nonatomic, readonly) BOOL irqLine;
@property (nonatomic) uint8_t portBInput;
@property (nonatomic) uint8_t portAInput;
- (void)setValue:(uint8_t)value forRegister:(NSUInteger)registerNumber;
- (uint8_t)valueForRegister:(NSUInteger)registerNumber;
- (void)runForHalfCycles:(NSUInteger)numberOfHalfCycles;
@end

View File

@ -0,0 +1,87 @@
//
// MOS6522Bridge.m
// Clock Signal
//
// Created by Thomas Harte on 18/06/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
#import "MOS6522Bridge.h"
#include "6522.hpp"
@class MOS6522Bridge;
class VanillaVIA: public MOS::MOS6522<VanillaVIA> {
public:
MOS6522Bridge *bridge;
bool irq_line;
uint8_t port_a_value;
uint8_t port_b_value;
void set_interrupt_status(bool new_status)
{
irq_line = new_status;
}
uint8_t get_port_input(int port)
{
return port ? port_b_value : port_a_value;
}
};
@implementation MOS6522Bridge
{
VanillaVIA _via;
}
- (instancetype)init
{
self = [super init];
if(self)
{
_via.bridge = self;
}
return self;
}
- (void)setValue:(uint8_t)value forRegister:(NSUInteger)registerNumber
{
_via.set_register((int)registerNumber, value);
}
- (uint8_t)valueForRegister:(NSUInteger)registerNumber
{
return _via.get_register((int)registerNumber);
}
- (void)runForHalfCycles:(NSUInteger)numberOfHalfCycles
{
_via.run_for_half_cycles((int)numberOfHalfCycles);
}
- (BOOL)irqLine
{
return _via.irq_line;
}
- (void)setPortAInput:(uint8_t)portAInput
{
_via.port_a_value = portAInput;
}
- (uint8_t)portAInput
{
return _via.port_a_value;
}
- (void)setPortBInput:(uint8_t)portBInput
{
_via.port_b_value = portBInput;
}
- (uint8_t)portBInput
{
return _via.port_b_value;
}
@end

View File

@ -1057,6 +1057,13 @@ template <class T> class Processor {
static_cast<T *>(this)->synchronise();
}
/*!
Called to announce the end of a run_for_cycles period, allowing deferred work to take place.
Users of the 6502 template may override this.
*/
void synchronise() {}
/*!
Gets the value of a register.