diff --git a/Components/6522/6522.hpp b/Components/6522/6522.hpp index 2369c500c..47d436847 100644 --- a/Components/6522/6522.hpp +++ b/Components/6522/6522.hpp @@ -71,26 +71,6 @@ class IRQDelegatePortHandler: public PortHandler { Delegate *delegate_ = nullptr; }; -class MOS6522Base: public MOS6522Storage { - public: - /// Sets the input value of line @c line on port @c port. - void set_control_line_input(Port port, Line line, bool value); - - /// Runs for a specified number of half cycles. - void run_for(const HalfCycles half_cycles); - - /// Runs for a specified number of cycles. - void run_for(const Cycles cycles); - - /// @returns @c true if the IRQ line is currently active; @c false otherwise. - bool get_interrupt_line(); - - private: - inline void do_phase1(); - inline void do_phase2(); - virtual void reevaluate_interrupts() = 0; -}; - /*! Implements a template for emulation of the MOS 6522 Versatile Interface Adaptor ('VIA'). @@ -102,7 +82,7 @@ class MOS6522Base: public MOS6522Storage { Consumers should derive their own curiously-recurring-template-pattern subclass, implementing bus communications as required. */ -template class MOS6522: public MOS6522Base { +template class MOS6522: public MOS6522Storage { public: MOS6522(T &bus_handler) noexcept : bus_handler_(bus_handler) {} MOS6522(const MOS6522 &) = delete; @@ -116,7 +96,21 @@ template class MOS6522: public MOS6522Base { /*! @returns the bus handler. */ T &bus_handler(); + /// Sets the input value of line @c line on port @c port. + void set_control_line_input(Port port, Line line, bool value); + + /// Runs for a specified number of half cycles. + void run_for(const HalfCycles half_cycles); + + /// Runs for a specified number of cycles. + void run_for(const Cycles cycles); + + /// @returns @c true if the IRQ line is currently active; @c false otherwise. + bool get_interrupt_line(); + private: + void do_phase1(); + void do_phase2(); T &bus_handler_; uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output); diff --git a/Components/6522/Implementation/6522Base.cpp b/Components/6522/Implementation/6522Base.cpp deleted file mode 100644 index 5fcb2c19f..000000000 --- a/Components/6522/Implementation/6522Base.cpp +++ /dev/null @@ -1,116 +0,0 @@ -// -// 6522Base.cpp -// Clock Signal -// -// Created by Thomas Harte on 04/09/2017. -// Copyright 2017 Thomas Harte. All rights reserved. -// - -#include "../6522.hpp" - -using namespace MOS::MOS6522; - -void MOS6522Base::set_control_line_input(Port port, Line line, bool value) { - switch(line) { - case Line::One: - if( value != control_inputs_[port].line_one && - value == !!(registers_.peripheral_control & (port ? 0x10 : 0x01)) - ) { - registers_.interrupt_flags |= port ? InterruptFlag::CB1ActiveEdge : InterruptFlag::CA1ActiveEdge; - reevaluate_interrupts(); - } - control_inputs_[port].line_one = value; - break; - - case Line::Two: - // TODO: output modes, but probably elsewhere? - if( value != control_inputs_[port].line_two && // i.e. value has changed ... - !(registers_.peripheral_control & (port ? 0x80 : 0x08)) && // ... and line is input ... - value == !!(registers_.peripheral_control & (port ? 0x40 : 0x04)) // ... and it's either high or low, as required - ) { - registers_.interrupt_flags |= port ? InterruptFlag::CB2ActiveEdge : InterruptFlag::CA2ActiveEdge; - reevaluate_interrupts(); - } - control_inputs_[port].line_two = value; - break; - } -} - -void MOS6522Base::do_phase2() { - registers_.last_timer[0] = registers_.timer[0]; - registers_.last_timer[1] = registers_.timer[1]; - - if(registers_.timer_needs_reload) { - registers_.timer_needs_reload = false; - registers_.timer[0] = registers_.timer_latch[0]; - } else { - registers_.timer[0] --; - } - - registers_.timer[1] --; - if(registers_.next_timer[0] >= 0) { - registers_.timer[0] = static_cast(registers_.next_timer[0]); - registers_.next_timer[0] = -1; - } - if(registers_.next_timer[1] >= 0) { - registers_.timer[1] = static_cast(registers_.next_timer[1]); - registers_.next_timer[1] = -1; - } -} - -void MOS6522Base::do_phase1() { - // 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; - } -} - -/*! Runs for a specified number of half cycles. */ -void MOS6522Base::run_for(const HalfCycles half_cycles) { - int number_of_half_cycles = half_cycles.as_int(); - - if(is_phase2_) { - do_phase2(); - number_of_half_cycles--; - } - - while(number_of_half_cycles >= 2) { - do_phase1(); - do_phase2(); - number_of_half_cycles -= 2; - } - - if(number_of_half_cycles) { - do_phase1(); - is_phase2_ = true; - } else { - is_phase2_ = false; - } -} - -/*! Runs for a specified number of cycles. */ -void MOS6522Base::run_for(const Cycles cycles) { - int number_of_cycles = cycles.as_int(); - while(number_of_cycles--) { - do_phase1(); - do_phase2(); - } -} - -/*! @returns @c true if the IRQ line is currently active; @c false otherwise. */ -bool MOS6522Base::get_interrupt_line() { - uint8_t interrupt_status = registers_.interrupt_flags & registers_.interrupt_enable & 0x7f; - return !!interrupt_status; -} diff --git a/Components/6522/Implementation/6522Implementation.hpp b/Components/6522/Implementation/6522Implementation.hpp index 5db653ba1..c1490df08 100644 --- a/Components/6522/Implementation/6522Implementation.hpp +++ b/Components/6522/Implementation/6522Implementation.hpp @@ -14,26 +14,34 @@ namespace MOS6522 { template void MOS6522::set_register(int address, uint8_t value) { address &= 0xf; switch(address) { - case 0x0: + case 0x0: // Write Port B. registers_.output[1] = value; - bus_handler_.set_port_output(Port::B, value, registers_.data_direction[1]); // TODO: handshake + bus_handler_.set_port_output(Port::B, value, registers_.data_direction[1]); + + if(handshake_modes_[0] != HandshakeMode::None) { + bus_handler_.set_control_line_output(Port::B, Line::Two, false); + } registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | ((registers_.peripheral_control&0x20) ? 0 : InterruptFlag::CB2ActiveEdge)); reevaluate_interrupts(); break; case 0xf: - case 0x1: + case 0x1: // Write Port A. registers_.output[0] = value; - bus_handler_.set_port_output(Port::A, value, registers_.data_direction[0]); // TODO: handshake + bus_handler_.set_port_output(Port::A, value, registers_.data_direction[0]); + + if(handshake_modes_[1] != HandshakeMode::None) { + bus_handler_.set_control_line_output(Port::A, Line::Two, false); + } registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | ((registers_.peripheral_control&0x02) ? 0 : InterruptFlag::CB2ActiveEdge)); reevaluate_interrupts(); break; - case 0x2: + case 0x2: // Port B direction. registers_.data_direction[1] = value; break; - case 0x3: + case 0x3: // Port A direction. registers_.data_direction[0] = value; break; @@ -69,19 +77,36 @@ template void MOS6522::set_register(int address, uint8_t value) registers_.peripheral_control = value; // TODO: simplify below; trying to avoid improper logging of unimplemented warnings in input mode - if(value & 0x08) { - switch(value & 0x0e) { - default: LOG("Unimplemented control CA2 line mode " << int((value >> 1)&7)); break; - case 0x0c: bus_handler_.set_control_line_output(Port::A, Line::Two, false); break; - case 0x0e: bus_handler_.set_control_line_output(Port::A, Line::Two, true); break; - } + handshake_modes_[0] = HandshakeMode::None; + switch(value & 0x0e) { + default: LOG("Unimplemented control line CA2 mode " << int((value >> 1)&7)); break; + + case 0x00: // Negative interrupt input; set CA2 interrupt on negative CA2 transition, clear on access to Port A register. + case 0x02: // Independent negative interrupt input; set CA2 interrupt on negative transition, don't clear automatically. + case 0x04: // Positive interrupt input; set CA2 interrupt on positive CA2 transition, clear on access to Port A register. + case 0x06: // Independent positive interrupt input; set CA2 interrupt on positive transition, don't clear automatically. + break; + + case 0x08: // Handshake: set CA2 to low on any read or write of Port A; set to high on an active transition of CA1. + handshake_modes_[0] = HandshakeMode::Handshake; + break; + + case 0x0a: // Pulse output: CA2 is low for one cycle following a read or write of Port A. + handshake_modes_[0] = HandshakeMode::Pulse; + break; + + case 0x0c: // Manual output: CA2 low. + bus_handler_.set_control_line_output(Port::A, Line::Two, false); + break; + + case 0x0e: // Manual output: CA2 high. + bus_handler_.set_control_line_output(Port::A, Line::Two, true); + break; } - if(value & 0x80) { - switch(value & 0xe0) { - default: LOG("Unimplemented control CB2 line mode " << int((value >> 5)&7)); break; - case 0xc0: bus_handler_.set_control_line_output(Port::B, Line::Two, false); break; - case 0xe0: bus_handler_.set_control_line_output(Port::B, Line::Two, true); break; - } + switch(value & 0xe0) { + default: LOG("Unimplemented control line CB2 mode " << int((value >> 5)&7)); break; + case 0xc0: bus_handler_.set_control_line_output(Port::B, Line::Two, false); break; + case 0xe0: bus_handler_.set_control_line_output(Port::B, Line::Two, true); break; } break; @@ -162,5 +187,114 @@ template void MOS6522::reevaluate_interrupts() { } } +template void MOS6522::set_control_line_input(Port port, Line line, bool value) { + switch(line) { + case Line::One: + if( value != control_inputs_[port].line_one && + value == !!(registers_.peripheral_control & (port ? 0x10 : 0x01)) + ) { + if(handshake_modes_[port] == HandshakeMode::Handshake) { +// bus_handler_ + } + + registers_.interrupt_flags |= port ? InterruptFlag::CB1ActiveEdge : InterruptFlag::CA1ActiveEdge; + reevaluate_interrupts(); + } + control_inputs_[port].line_one = value; + break; + + case Line::Two: + // TODO: output modes, but probably elsewhere? + if( value != control_inputs_[port].line_two && // i.e. value has changed ... + !(registers_.peripheral_control & (port ? 0x80 : 0x08)) && // ... and line is input ... + value == !!(registers_.peripheral_control & (port ? 0x40 : 0x04)) // ... and it's either high or low, as required + ) { + registers_.interrupt_flags |= port ? InterruptFlag::CB2ActiveEdge : InterruptFlag::CA2ActiveEdge; + reevaluate_interrupts(); + } + control_inputs_[port].line_two = value; + break; + } +} + +template void MOS6522::do_phase2() { + registers_.last_timer[0] = registers_.timer[0]; + registers_.last_timer[1] = registers_.timer[1]; + + if(registers_.timer_needs_reload) { + registers_.timer_needs_reload = false; + registers_.timer[0] = registers_.timer_latch[0]; + } else { + registers_.timer[0] --; + } + + registers_.timer[1] --; + if(registers_.next_timer[0] >= 0) { + registers_.timer[0] = static_cast(registers_.next_timer[0]); + registers_.next_timer[0] = -1; + } + if(registers_.next_timer[1] >= 0) { + registers_.timer[1] = static_cast(registers_.next_timer[1]); + registers_.next_timer[1] = -1; + } +} + +template void MOS6522::do_phase1() { + // 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; + } +} + +/*! Runs for a specified number of half cycles. */ +template void MOS6522::run_for(const HalfCycles half_cycles) { + int number_of_half_cycles = half_cycles.as_int(); + + if(is_phase2_) { + do_phase2(); + number_of_half_cycles--; + } + + while(number_of_half_cycles >= 2) { + do_phase1(); + do_phase2(); + number_of_half_cycles -= 2; + } + + if(number_of_half_cycles) { + do_phase1(); + is_phase2_ = true; + } else { + is_phase2_ = false; + } +} + +/*! Runs for a specified number of cycles. */ +template void MOS6522::run_for(const Cycles cycles) { + int number_of_cycles = cycles.as_int(); + while(number_of_cycles--) { + do_phase1(); + do_phase2(); + } +} + +/*! @returns @c true if the IRQ line is currently active; @c false otherwise. */ +template bool MOS6522::get_interrupt_line() { + uint8_t interrupt_status = registers_.interrupt_flags & registers_.interrupt_enable & 0x7f; + return !!interrupt_status; +} + } } diff --git a/Components/6522/Implementation/6522Storage.hpp b/Components/6522/Implementation/6522Storage.hpp index 0f37abe22..60f303dee 100644 --- a/Components/6522/Implementation/6522Storage.hpp +++ b/Components/6522/Implementation/6522Storage.hpp @@ -21,7 +21,7 @@ class MOS6522Storage { // The registers struct Registers { - // "A low reset (RES) input clears all R6522 internal registers to logic 0" + // "A low reset (RES) input clears all R6522 internal registers to logic 0" uint8_t output[2] = {0, 0}; uint8_t input[2] = {0, 0}; uint8_t data_direction[2] = {0, 0}; @@ -43,6 +43,12 @@ class MOS6522Storage { bool line_two = false; } control_inputs_[2]; + enum class HandshakeMode { + None, + Handshake, + Pulse + } handshake_modes_[2] = { HandshakeMode::None, HandshakeMode::None }; + bool timer_is_running_[2] = {false, false}; bool last_posted_interrupt_status_ = false; diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 2393d521a..78852d3f5 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -93,7 +93,6 @@ 4B055ADB1FAE9B460060FFFF /* 6560.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9DF4D1D04691600F44158 /* 6560.cpp */; }; 4B055ADC1FAE9B460060FFFF /* AY38910.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4A762E1DB1A3FA007AAE2E /* AY38910.cpp */; }; 4B055ADD1FAE9B460060FFFF /* i8272.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBC951C1F368D83008F4C34 /* i8272.cpp */; }; - 4B055ADE1FAE9B4C0060FFFF /* 6522Base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B83348B1F5DB99C0097E338 /* 6522Base.cpp */; }; 4B055ADF1FAE9B4C0060FFFF /* IRQDelegatePortHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334891F5DB94B0097E338 /* IRQDelegatePortHandler.cpp */; }; 4B055AE01FAE9B660060FFFF /* CRT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0CCC421C62D0B3001CAC5F /* CRT.cpp */; }; 4B055AE81FAE9B7B0060FFFF /* FIRFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */; }; @@ -240,7 +239,6 @@ 4B8334841F5DA0360097E338 /* Z80Storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334831F5DA0360097E338 /* Z80Storage.cpp */; }; 4B8334861F5DA3780097E338 /* 6502Storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334851F5DA3780097E338 /* 6502Storage.cpp */; }; 4B83348A1F5DB94B0097E338 /* IRQDelegatePortHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334891F5DB94B0097E338 /* IRQDelegatePortHandler.cpp */; }; - 4B83348C1F5DB99C0097E338 /* 6522Base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B83348B1F5DB99C0097E338 /* 6522Base.cpp */; }; 4B8334951F5E25B60097E338 /* C1540.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334941F5E25B60097E338 /* C1540.cpp */; }; 4B85322D227793CB00F26553 /* etos192uk.trace.txt.gz in Resources */ = {isa = PBXBuildFile; fileRef = 4B85322C227793CA00F26553 /* etos192uk.trace.txt.gz */; }; 4B85322F2277ABDE00F26553 /* tos100.trace.txt.gz in Resources */ = {isa = PBXBuildFile; fileRef = 4B85322E2277ABDD00F26553 /* tos100.trace.txt.gz */; }; @@ -958,7 +956,6 @@ 4B8334851F5DA3780097E338 /* 6502Storage.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = 6502Storage.cpp; sourceTree = ""; }; 4B8334871F5DB8410097E338 /* 6522Implementation.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = 6522Implementation.hpp; path = Implementation/6522Implementation.hpp; sourceTree = ""; }; 4B8334891F5DB94B0097E338 /* IRQDelegatePortHandler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = IRQDelegatePortHandler.cpp; path = Implementation/IRQDelegatePortHandler.cpp; sourceTree = ""; }; - 4B83348B1F5DB99C0097E338 /* 6522Base.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = 6522Base.cpp; path = Implementation/6522Base.cpp; sourceTree = ""; }; 4B83348E1F5DBA6E0097E338 /* 6522Storage.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = 6522Storage.hpp; path = Implementation/6522Storage.hpp; sourceTree = ""; }; 4B8334911F5E24FF0097E338 /* C1540Base.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = C1540Base.hpp; path = Implementation/C1540Base.hpp; sourceTree = ""; }; 4B8334941F5E25B60097E338 /* C1540.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = C1540.cpp; path = Implementation/C1540.cpp; sourceTree = ""; }; @@ -1417,6 +1414,7 @@ 4BD67DCE209BF27B00AB2146 /* Encoder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Encoder.cpp; sourceTree = ""; }; 4BD67DCF209BF27B00AB2146 /* Encoder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Encoder.hpp; sourceTree = ""; }; 4BD9137D1F311BC5009BCF85 /* i8255.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = i8255.hpp; path = 8255/i8255.hpp; sourceTree = ""; }; + 4BDB3D8522833321002D3CEE /* Keyboard.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Keyboard.hpp; sourceTree = ""; }; 4BDCC5F81FB27A5E001220C5 /* ROMMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ROMMachine.hpp; sourceTree = ""; }; 4BDDBA981EF3451200347E61 /* Z80MachineCycleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Z80MachineCycleTests.swift; sourceTree = ""; }; 4BE3231220532443006EF799 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = ""; }; @@ -2244,7 +2242,6 @@ 4B8334881F5DB8470097E338 /* Implementation */ = { isa = PBXGroup; children = ( - 4B83348B1F5DB99C0097E338 /* 6522Base.cpp */, 4B8334891F5DB94B0097E338 /* IRQDelegatePortHandler.cpp */, 4B8334871F5DB8410097E338 /* 6522Implementation.hpp */, 4B83348E1F5DBA6E0097E338 /* 6522Storage.hpp */, @@ -3073,6 +3070,7 @@ 4BCE0059227CFFCA000CA200 /* Macintosh.hpp */, 4BD0692B22828A2D00D2A54F /* RealTimeClock.hpp */, 4BCE005F227D39AB000CA200 /* Video.hpp */, + 4BDB3D8522833321002D3CEE /* Keyboard.hpp */, ); path = Macintosh; sourceTree = ""; @@ -3871,7 +3869,6 @@ 4BD424E82193B5830097291A /* Rectangle.cpp in Sources */, 4B055A961FAE85BB0060FFFF /* Commodore.cpp in Sources */, 4B0333B02094081A0050B93D /* AppleDSK.cpp in Sources */, - 4B055ADE1FAE9B4C0060FFFF /* 6522Base.cpp in Sources */, 4B894535201967B4007DE474 /* AddressMapper.cpp in Sources */, 4B055AD41FAE9B0B0060FFFF /* Oric.cpp in Sources */, 4B055A921FAE85B50060FFFF /* PRG.cpp in Sources */, @@ -4037,7 +4034,6 @@ 4B3051301D98ACC600B4FED8 /* Plus3.cpp in Sources */, 4B30512D1D989E2200B4FED8 /* Drive.cpp in Sources */, 4BCE005D227D30CC000CA200 /* MemoryPacker.cpp in Sources */, - 4B83348C1F5DB99C0097E338 /* 6522Base.cpp in Sources */, 4BCE0051227CE8CA000CA200 /* Video.cpp in Sources */, 4B894536201967B4007DE474 /* Z80.cpp in Sources */, 4BCA6CC81D9DD9F000C2D7B2 /* CommodoreROM.cpp in Sources */,