diff --git a/Components/6522/6522.hpp b/Components/6522/6522.hpp index 15458d465..4cd616fde 100644 --- a/Components/6522/6522.hpp +++ b/Components/6522/6522.hpp @@ -14,11 +14,6 @@ namespace MOS { -class MOS6522Delegate { - public: - virtual void mos6522_did_change_interrupt_status(void *mos6522) = 0; -}; - template class MOS6522 { private: enum InterruptFlag: uint8_t { @@ -40,17 +35,17 @@ template class MOS6522 { { case 0x0: _registers.output[1] = value; - static_cast(this)->set_port_output(1, value); // TODO: handshake - break; - case 0x1: - _registers.output[0] = value; - static_cast(this)->set_port_output(0, value); // TODO: handshake + static_cast(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(this)->set_port_output(0, value); + static_cast(this)->set_port_output(0, value, _registers.data_direction[0]); // TODO: handshake break; +// // No handshake, so write directly +// _registers.output[0] = value; +// static_cast(this)->set_port_output(0, value); +// break; case 0x2: _registers.data_direction[1] = value; @@ -109,10 +104,9 @@ template class MOS6522 { // printf("6522 %p: %d\n", this, address); switch(address) { -// case 0x0: return (_registers.auxiliary_control & 0x40) ? _registers.input[1] : static_cast(this)->get_port_input(1); - case 0x0: return _registers.output[1];//static_cast(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(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 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 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(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 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(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 */ diff --git a/Machines/Vic-20/Vic20.cpp b/Machines/Vic-20/Vic20.cpp index 5a60dcc1d..71888551f 100644 --- a/Machines/Vic-20/Vic20.cpp +++ b/Machines/Vic-20/Vic20.cpp @@ -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; } diff --git a/Machines/Vic-20/Vic20.hpp b/Machines/Vic-20/Vic20.hpp index 0910b8f40..4fb639a55 100644 --- a/Machines/Vic-20/Vic20.hpp +++ b/Machines/Vic-20/Vic20.hpp @@ -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 { +class UserPortVIA: public MOS::MOS6522, public MOS::MOS6522IRQDelegate { }; -class KeyboardVIA: public MOS::MOS6522 { +class KeyboardVIA: public MOS::MOS6522, public MOS::MOS6522IRQDelegate { public: void set_key_state(Key key, bool isPressed) { if(isPressed) @@ -75,9 +75,9 @@ class KeyboardVIA: public MOS::MOS6522 { 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 { uint8_t _activation_mask; }; -class Machine: public CPU6502::Processor, public CRTMachine::Machine, public MOS::MOS6522Delegate { +class Machine: public CPU6502::Processor, public CRTMachine::Machine, public MOS::MOS6522IRQDelegate::Delegate { public: Machine(); ~Machine(); diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 057575c69..1571aec89 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -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 = ""; }; 4BC3B7501CD1956900F86E85 /* OutputShader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OutputShader.cpp; sourceTree = ""; }; 4BC3B7511CD1956900F86E85 /* OutputShader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OutputShader.hpp; sourceTree = ""; }; + 4BC751B11D157E61006C31D9 /* 6522Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6522Tests.swift; sourceTree = ""; }; + 4BC751B41D157EB3006C31D9 /* MOS6522Bridge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MOS6522Bridge.h; sourceTree = ""; }; + 4BC751B51D157EB3006C31D9 /* MOS6522Bridge.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MOS6522Bridge.mm; sourceTree = ""; }; 4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FIRFilter.cpp; sourceTree = ""; }; 4BC76E681C98E31700E6EF73 /* FIRFilter.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FIRFilter.hpp; sourceTree = ""; }; 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 = ""; @@ -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 */, diff --git a/OSBindings/Mac/Clock SignalTests/6522Tests.swift b/OSBindings/Mac/Clock SignalTests/6522Tests.swift new file mode 100644 index 000000000..cb5a7ded8 --- /dev/null +++ b/OSBindings/Mac/Clock SignalTests/6522Tests.swift @@ -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))") + } + } +} diff --git a/OSBindings/Mac/Clock SignalTests/Clock SignalTests-Bridging-Header.h b/OSBindings/Mac/Clock SignalTests/Clock SignalTests-Bridging-Header.h index a628ea2a9..0bcb4a76b 100644 --- a/OSBindings/Mac/Clock SignalTests/Clock SignalTests-Bridging-Header.h +++ b/OSBindings/Mac/Clock SignalTests/Clock SignalTests-Bridging-Header.h @@ -3,3 +3,4 @@ // #import "TestMachine.h" +#import "MOS6522Bridge.h" diff --git a/OSBindings/Mac/Clock SignalTests/MOS6522Bridge.h b/OSBindings/Mac/Clock SignalTests/MOS6522Bridge.h new file mode 100644 index 000000000..b5110ae86 --- /dev/null +++ b/OSBindings/Mac/Clock SignalTests/MOS6522Bridge.h @@ -0,0 +1,22 @@ +// +// MOS6522Bridge.h +// Clock Signal +// +// Created by Thomas Harte on 18/06/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#import + +@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 diff --git a/OSBindings/Mac/Clock SignalTests/MOS6522Bridge.mm b/OSBindings/Mac/Clock SignalTests/MOS6522Bridge.mm new file mode 100644 index 000000000..8dd444a05 --- /dev/null +++ b/OSBindings/Mac/Clock SignalTests/MOS6522Bridge.mm @@ -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 { + 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 diff --git a/Processors/6502/CPU6502.hpp b/Processors/6502/CPU6502.hpp index e4e884a06..e335df436 100644 --- a/Processors/6502/CPU6502.hpp +++ b/Processors/6502/CPU6502.hpp @@ -1057,6 +1057,13 @@ template class Processor { static_cast(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.