From 24b3faa427f7c1114554922a5db052020a891c9d Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 4 Sep 2017 14:26:04 -0400 Subject: [PATCH 1/4] Deconstitutes the 6522 into component parts, templated and non-templated. Adjusts the Oric, Vic-20 and C-1540 accordingly, albeit with the quickest possible solutions. --- Components/6522/6522.hpp | 403 +++--------------- Components/6522/Implementation/6522Base.cpp | 116 +++++ .../Implementation/6522Implementation.hpp | 155 +++++++ .../6522/Implementation/6522Storage.hpp | 63 +++ .../Implementation/IRQDelegatePortHandler.cpp | 19 + Machines/Commodore/1540/C1540.cpp | 52 +-- Machines/Commodore/1540/C1540.hpp | 30 +- Machines/Commodore/Vic-20/Vic20.cpp | 80 ++-- Machines/Oric/Oric.cpp | 48 +-- .../Clock Signal.xcodeproj/project.pbxproj | 20 + 10 files changed, 546 insertions(+), 440 deletions(-) create mode 100644 Components/6522/Implementation/6522Base.cpp create mode 100644 Components/6522/Implementation/6522Implementation.hpp create mode 100644 Components/6522/Implementation/6522Storage.hpp create mode 100644 Components/6522/Implementation/IRQDelegatePortHandler.cpp diff --git a/Components/6522/6522.hpp b/Components/6522/6522.hpp index 98e41293e..e211b5399 100644 --- a/Components/6522/6522.hpp +++ b/Components/6522/6522.hpp @@ -13,9 +13,67 @@ #include #include +#include "Implementation/6522Storage.hpp" + #include "../../ClockReceiver/ClockReceiver.hpp" namespace MOS { +namespace MOS6522 { + +enum Port { + A = 0, + B = 1 +}; + +enum Line { + One = 0, + Two = 1 +}; + +class PortHandler { + public: + uint8_t get_port_input(Port port) { return 0xff; } + void set_port_output(Port port, uint8_t value, uint8_t direction_mask) {} + void set_control_line_output(Port port, Line line, bool value) {} + void set_interrupt_status(bool status) {} +}; + +/*! + Provided for optional composition with @c MOS6522, @c MOS6522IRQDelegate provides for a delegate + that will receive IRQ line change notifications. +*/ +class IRQDelegatePortHandler: public PortHandler { + public: + class Delegate { + public: + virtual void mos6522_did_change_interrupt_status(void *mos6522) = 0; + }; + + void set_interrupt_delegate(Delegate *delegate); + void set_interrupt_status(bool new_status); + + private: + Delegate *delegate_ = nullptr; +}; + +class MOS6522Base: public MOS6522Storage { + public: + 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'). @@ -28,353 +86,26 @@ namespace MOS { Consumers should derive their own curiously-recurring-template-pattern subclass, implementing bus communications as required. */ -template class MOS6522 { - private: - enum InterruptFlag: uint8_t { - CA2ActiveEdge = 1 << 0, - CA1ActiveEdge = 1 << 1, - ShiftRegister = 1 << 2, - CB2ActiveEdge = 1 << 3, - CB1ActiveEdge = 1 << 4, - Timer2 = 1 << 5, - Timer1 = 1 << 6, - }; - +template class MOS6522: public MOS6522Base { public: - enum Port { - A = 0, - B = 1 - }; - - enum Line { - One = 0, - Two = 1 - }; + MOS6522(T &bus_handler) : bus_handler_(bus_handler) {} /*! Sets a register value. */ - inline void set_register(int address, uint8_t value) { - address &= 0xf; -// printf("6522 [%s]: %0x <- %02x\n", typeid(*this).name(), address, value); - switch(address) { - case 0x0: - registers_.output[1] = value; - static_cast(this)->set_port_output(Port::B, value, registers_.data_direction[1]); // TODO: handshake - - registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | ((registers_.peripheral_control&0x20) ? 0 : InterruptFlag::CB2ActiveEdge)); - reevaluate_interrupts(); - break; - case 0xf: - case 0x1: - registers_.output[0] = value; - static_cast(this)->set_port_output(Port::A, value, registers_.data_direction[0]); // TODO: handshake - - registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | ((registers_.peripheral_control&0x02) ? 0 : InterruptFlag::CB2ActiveEdge)); - reevaluate_interrupts(); - 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; - break; - case 0x3: - registers_.data_direction[0] = value; - break; - - // Timer 1 - case 0x6: case 0x4: registers_.timer_latch[0] = (registers_.timer_latch[0]&0xff00) | value; break; - case 0x5: case 0x7: - registers_.timer_latch[0] = (registers_.timer_latch[0]&0x00ff) | (uint16_t)(value << 8); - registers_.interrupt_flags &= ~InterruptFlag::Timer1; - if(address == 0x05) { - registers_.next_timer[0] = registers_.timer_latch[0]; - timer_is_running_[0] = true; - } - reevaluate_interrupts(); - break; - - // Timer 2 - case 0x8: registers_.timer_latch[1] = value; break; - case 0x9: - registers_.interrupt_flags &= ~InterruptFlag::Timer2; - registers_.next_timer[1] = registers_.timer_latch[1] | (uint16_t)(value << 8); - timer_is_running_[1] = true; - reevaluate_interrupts(); - break; - - // Shift - case 0xa: registers_.shift = value; break; - - // Control - case 0xb: - registers_.auxiliary_control = value; - break; - case 0xc: -// printf("Peripheral control %02x\n", 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: printf("Unimplemented control line mode %d\n", (value >> 1)&7); break; - case 0x0c: static_cast(this)->set_control_line_output(Port::A, Line::Two, false); break; - case 0x0e: static_cast(this)->set_control_line_output(Port::A, Line::Two, true); break; - } - } - if(value & 0x80) { - switch(value & 0xe0) { - default: printf("Unimplemented control line mode %d\n", (value >> 5)&7); break; - case 0xc0: static_cast(this)->set_control_line_output(Port::B, Line::Two, false); break; - case 0xe0: static_cast(this)->set_control_line_output(Port::B, Line::Two, true); break; - } - } - break; - - // Interrupt control - case 0xd: - registers_.interrupt_flags &= ~value; - reevaluate_interrupts(); - break; - case 0xe: - if(value&0x80) - registers_.interrupt_enable |= value; - else - registers_.interrupt_enable &= ~value; - reevaluate_interrupts(); - break; - } - } + void set_register(int address, uint8_t value); /*! Gets a register value. */ - inline uint8_t get_register(int address) { - address &= 0xf; -// printf("6522 %p: %d\n", this, address); - switch(address) { - case 0x0: - registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge); - reevaluate_interrupts(); - return get_port_input(Port::B, registers_.data_direction[1], registers_.output[1]); - case 0xf: // TODO: handshake, latching - case 0x1: - registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | InterruptFlag::CA2ActiveEdge); - reevaluate_interrupts(); - return get_port_input(Port::A, registers_.data_direction[0], registers_.output[0]); - - case 0x2: return registers_.data_direction[1]; - case 0x3: return registers_.data_direction[0]; - - // Timer 1 - case 0x4: - registers_.interrupt_flags &= ~InterruptFlag::Timer1; - reevaluate_interrupts(); - return registers_.timer[0] & 0x00ff; - case 0x5: return registers_.timer[0] >> 8; - case 0x6: return registers_.timer_latch[0] & 0x00ff; - case 0x7: return registers_.timer_latch[0] >> 8; - - // Timer 2 - case 0x8: - registers_.interrupt_flags &= ~InterruptFlag::Timer2; - reevaluate_interrupts(); - return registers_.timer[1] & 0x00ff; - case 0x9: return registers_.timer[1] >> 8; - - case 0xa: return registers_.shift; - - case 0xb: return registers_.auxiliary_control; - case 0xc: return registers_.peripheral_control; - - case 0xd: return registers_.interrupt_flags | (get_interrupt_line() ? 0x80 : 0x00); - case 0xe: return registers_.interrupt_enable | 0x80; - } - - return 0xff; - } - - inline void 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; - } - } - -#define 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] = (uint16_t)registers_.next_timer[0]; registers_.next_timer[0] = -1; }\ - if(registers_.next_timer[1] >= 0) { registers_.timer[1] = (uint16_t)registers_.next_timer[1]; registers_.next_timer[1] = -1; }\ - - // IRQ is raised on the half cycle after overflow -#define phase1() \ - 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. */ - inline void run_for(const HalfCycles half_cycles) { - int number_of_half_cycles = half_cycles.as_int(); - - if(is_phase2_) { - phase2(); - number_of_half_cycles--; - } - - while(number_of_half_cycles >= 2) { - phase1(); - phase2(); - number_of_half_cycles -= 2; - } - - if(number_of_half_cycles) { - phase1(); - is_phase2_ = true; - } else { - is_phase2_ = false; - } - } - - /*! Runs for a specified number of cycles. */ - inline void run_for(const Cycles cycles) { - int number_of_cycles = cycles.as_int(); - while(number_of_cycles--) { - phase1(); - phase2(); - } - } - -#undef phase1 -#undef phase2 - - /*! @returns @c true if the IRQ line is currently active; @c false otherwise. */ - inline bool get_interrupt_line() { - uint8_t interrupt_status = registers_.interrupt_flags & registers_.interrupt_enable & 0x7f; - return !!interrupt_status; - } - - MOS6522() : - timer_is_running_{false, false}, - last_posted_interrupt_status_(false), - is_phase2_(false) {} + uint8_t get_register(int address); private: - // Expected to be overridden - uint8_t get_port_input(Port port) { return 0xff; } - void set_port_output(Port port, uint8_t value, uint8_t direction_mask) {} - void set_control_line_output(Port port, Line line, bool value) {} - void set_interrupt_status(bool status) {} + T &bus_handler_; - // Input/output multiplexer - uint8_t get_port_input(Port 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 - bool last_posted_interrupt_status_; - inline void reevaluate_interrupts() { - bool new_interrupt_status = get_interrupt_line(); - if(new_interrupt_status != last_posted_interrupt_status_) { - last_posted_interrupt_status_ = new_interrupt_status; - 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], last_timer[2]; - int next_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), - last_timer{0, 0}, timer_needs_reload(false), - next_timer{-1, -1} {} - } registers_; - - // control state - struct { - bool line_one, line_two; - } control_inputs_[2]; - - // Internal state other than the registers - bool timer_is_running_[2]; + uint8_t get_port_input(Port port, uint8_t output_mask, uint8_t output); + inline void reevaluate_interrupts(); }; -/*! - Provided for optional composition with @c MOS6522, @c MOS6522IRQDelegate provides for a delegate - that will receive IRQ line change notifications. -*/ -class MOS6522IRQDelegate { - public: - class Delegate { - public: - virtual void mos6522_did_change_interrupt_status(void *mos6522) = 0; - }; - - inline void set_interrupt_delegate(Delegate *delegate) { - delegate_ = delegate; - } - - inline void set_interrupt_status(bool new_status) { - if(delegate_) delegate_->mos6522_did_change_interrupt_status(this); - } - - private: - Delegate *delegate_; -}; +#include "Implementation/6522Implementation.hpp" +} } #endif /* _522_hpp */ diff --git a/Components/6522/Implementation/6522Base.cpp b/Components/6522/Implementation/6522Base.cpp new file mode 100644 index 000000000..2984bca24 --- /dev/null +++ b/Components/6522/Implementation/6522Base.cpp @@ -0,0 +1,116 @@ +// +// 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] = (uint16_t)registers_.next_timer[0]; + registers_.next_timer[0] = -1; + } + if(registers_.next_timer[1] >= 0) { + registers_.timer[1] = (uint16_t)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 new file mode 100644 index 000000000..49ec22f58 --- /dev/null +++ b/Components/6522/Implementation/6522Implementation.hpp @@ -0,0 +1,155 @@ +// +// Implementation.hpp +// Clock Signal +// +// Created by Thomas Harte on 04/09/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +template void MOS6522::set_register(int address, uint8_t value) { + address &= 0xf; + switch(address) { + case 0x0: + registers_.output[1] = value; + bus_handler_.set_port_output(Port::B, value, registers_.data_direction[1]); // TODO: handshake + + registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | ((registers_.peripheral_control&0x20) ? 0 : InterruptFlag::CB2ActiveEdge)); + reevaluate_interrupts(); + break; + case 0xf: + case 0x1: + registers_.output[0] = value; + bus_handler_.set_port_output(Port::A, value, registers_.data_direction[0]); // TODO: handshake + + registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | ((registers_.peripheral_control&0x02) ? 0 : InterruptFlag::CB2ActiveEdge)); + reevaluate_interrupts(); + break; + + case 0x2: + registers_.data_direction[1] = value; + break; + case 0x3: + registers_.data_direction[0] = value; + break; + + // Timer 1 + case 0x6: case 0x4: registers_.timer_latch[0] = (registers_.timer_latch[0]&0xff00) | value; break; + case 0x5: case 0x7: + registers_.timer_latch[0] = (registers_.timer_latch[0]&0x00ff) | (uint16_t)(value << 8); + registers_.interrupt_flags &= ~InterruptFlag::Timer1; + if(address == 0x05) { + registers_.next_timer[0] = registers_.timer_latch[0]; + timer_is_running_[0] = true; + } + reevaluate_interrupts(); + break; + + // Timer 2 + case 0x8: registers_.timer_latch[1] = value; break; + case 0x9: + registers_.interrupt_flags &= ~InterruptFlag::Timer2; + registers_.next_timer[1] = registers_.timer_latch[1] | (uint16_t)(value << 8); + timer_is_running_[1] = true; + reevaluate_interrupts(); + break; + + // Shift + case 0xa: registers_.shift = value; break; + + // Control + case 0xb: + registers_.auxiliary_control = value; + break; + case 0xc: +// printf("Peripheral control %02x\n", 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: printf("Unimplemented control line mode %d\n", (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; + } + } + if(value & 0x80) { + switch(value & 0xe0) { + default: printf("Unimplemented control line mode %d\n", (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; + + // Interrupt control + case 0xd: + registers_.interrupt_flags &= ~value; + reevaluate_interrupts(); + break; + case 0xe: + if(value&0x80) + registers_.interrupt_enable |= value; + else + registers_.interrupt_enable &= ~value; + reevaluate_interrupts(); + break; + } +} + +template uint8_t MOS6522::get_register(int address) { + address &= 0xf; + switch(address) { + case 0x0: + registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | InterruptFlag::CB2ActiveEdge); + reevaluate_interrupts(); + return get_port_input(Port::B, registers_.data_direction[1], registers_.output[1]); + case 0xf: // TODO: handshake, latching + case 0x1: + registers_.interrupt_flags &= ~(InterruptFlag::CA1ActiveEdge | InterruptFlag::CA2ActiveEdge); + reevaluate_interrupts(); + return get_port_input(Port::A, registers_.data_direction[0], registers_.output[0]); + + case 0x2: return registers_.data_direction[1]; + case 0x3: return registers_.data_direction[0]; + + // Timer 1 + case 0x4: + registers_.interrupt_flags &= ~InterruptFlag::Timer1; + reevaluate_interrupts(); + return registers_.timer[0] & 0x00ff; + case 0x5: return registers_.timer[0] >> 8; + case 0x6: return registers_.timer_latch[0] & 0x00ff; + case 0x7: return registers_.timer_latch[0] >> 8; + + // Timer 2 + case 0x8: + registers_.interrupt_flags &= ~InterruptFlag::Timer2; + reevaluate_interrupts(); + return registers_.timer[1] & 0x00ff; + case 0x9: return registers_.timer[1] >> 8; + + case 0xa: return registers_.shift; + + case 0xb: return registers_.auxiliary_control; + case 0xc: return registers_.peripheral_control; + + case 0xd: return registers_.interrupt_flags | (get_interrupt_line() ? 0x80 : 0x00); + case 0xe: return registers_.interrupt_enable | 0x80; + } + + return 0xff; +} + +template uint8_t MOS6522::get_port_input(Port port, uint8_t output_mask, uint8_t output) { + uint8_t input = bus_handler_.get_port_input(port); + return (input & ~output_mask) | (output & output_mask); +} + +// Delegate and communications +template void MOS6522::reevaluate_interrupts() { + bool new_interrupt_status = get_interrupt_line(); + if(new_interrupt_status != last_posted_interrupt_status_) { + last_posted_interrupt_status_ = new_interrupt_status; + bus_handler_.set_interrupt_status(new_interrupt_status); + } +} diff --git a/Components/6522/Implementation/6522Storage.hpp b/Components/6522/Implementation/6522Storage.hpp new file mode 100644 index 000000000..aa8ba7dd5 --- /dev/null +++ b/Components/6522/Implementation/6522Storage.hpp @@ -0,0 +1,63 @@ +// +// 6522Storage.hpp +// Clock Signal +// +// Created by Thomas Harte on 04/09/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +#ifndef _522Storage_hpp +#define _522Storage_hpp + +#include + +namespace MOS { +namespace MOS6522 { + +class MOS6522Storage { + protected: + // Phase toggle + bool is_phase2_ = false; + + // The registers + struct Registers { + // "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}; + uint16_t timer[2] = {0, 0}; + uint16_t timer_latch[2] = {0, 0}; + uint16_t last_timer[2] = {0, 0}; + int next_timer[2] = {-1, -1}; + uint8_t shift = 0; + uint8_t auxiliary_control = 0; + uint8_t peripheral_control = 0; + uint8_t interrupt_flags = 0; + uint8_t interrupt_enable = 0; + bool timer_needs_reload = false; + } registers_; + + // control state + struct { + bool line_one = false; + bool line_two = false; + } control_inputs_[2]; + + bool timer_is_running_[2] = {false, false}; + bool last_posted_interrupt_status_ = false; + + enum InterruptFlag: uint8_t { + CA2ActiveEdge = 1 << 0, + CA1ActiveEdge = 1 << 1, + ShiftRegister = 1 << 2, + CB2ActiveEdge = 1 << 3, + CB1ActiveEdge = 1 << 4, + Timer2 = 1 << 5, + Timer1 = 1 << 6, + }; +}; + +} +} + +#endif /* _522Storage_hpp */ diff --git a/Components/6522/Implementation/IRQDelegatePortHandler.cpp b/Components/6522/Implementation/IRQDelegatePortHandler.cpp new file mode 100644 index 000000000..e764175de --- /dev/null +++ b/Components/6522/Implementation/IRQDelegatePortHandler.cpp @@ -0,0 +1,19 @@ +// +// IRQDelegatePortHandler.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 IRQDelegatePortHandler::set_interrupt_delegate(Delegate *delegate) { + delegate_ = delegate; +} + +void IRQDelegatePortHandler::set_interrupt_status(bool new_status) { + if(delegate_) delegate_->mos6522_did_change_interrupt_status(this); +} diff --git a/Machines/Commodore/1540/C1540.cpp b/Machines/Commodore/1540/C1540.cpp index fe74bd13b..011869cbd 100644 --- a/Machines/Commodore/1540/C1540.cpp +++ b/Machines/Commodore/1540/C1540.cpp @@ -17,15 +17,17 @@ Machine::Machine() : shift_register_(0), Storage::Disk::Controller(1000000, 4, 300), serial_port_(new SerialPort), - serial_port_VIA_(new SerialPortVIA) { + serial_port_VIA_port_handler_(new SerialPortVIA(serial_port_VIA_)), + drive_VIA_(drive_VIA_port_handler_), + serial_port_VIA_(*serial_port_VIA_port_handler_) { // attach the serial port to its VIA and vice versa - serial_port_->set_serial_port_via(serial_port_VIA_); - serial_port_VIA_->set_serial_port(serial_port_); + serial_port_->set_serial_port_via(serial_port_VIA_port_handler_); + serial_port_VIA_port_handler_->set_serial_port(serial_port_); // set this instance as the delegate to receive interrupt requests from both VIAs - serial_port_VIA_->set_interrupt_delegate(this); - drive_VIA_.set_interrupt_delegate(this); - drive_VIA_.set_delegate(this); + serial_port_VIA_port_handler_->set_interrupt_delegate(this); + drive_VIA_port_handler_.set_interrupt_delegate(this); + drive_VIA_port_handler_.set_delegate(this); // set a bit rate set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(3)); @@ -54,9 +56,9 @@ Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint *value = rom_[address & 0x3fff]; } else if(address >= 0x1800 && address <= 0x180f) { if(isReadOperation(operation)) - *value = serial_port_VIA_->get_register(address); + *value = serial_port_VIA_.get_register(address); else - serial_port_VIA_->set_register(address, *value); + serial_port_VIA_.set_register(address, *value); } else if(address >= 0x1c00 && address <= 0x1c0f) { if(isReadOperation(operation)) *value = drive_VIA_.get_register(address); @@ -64,7 +66,7 @@ Cycles Machine::perform_bus_operation(CPU::MOS6502::BusOperation operation, uint drive_VIA_.set_register(address, *value); } - serial_port_VIA_->run_for(Cycles(1)); + serial_port_VIA_.run_for(Cycles(1)); drive_VIA_.run_for(Cycles(1)); return Cycles(1); @@ -82,8 +84,8 @@ void Machine::set_disk(std::shared_ptr disk) { void Machine::run_for(const Cycles cycles) { m6502_.run_for(cycles); - set_motor_on(drive_VIA_.get_motor_enabled()); - if(drive_VIA_.get_motor_enabled()) // TODO: motor speed up/down + set_motor_on(drive_VIA_port_handler_.get_motor_enabled()); + if(drive_VIA_port_handler_.get_motor_enabled()) // TODO: motor speed up/down Storage::Disk::Controller::run_for(cycles); } @@ -91,7 +93,7 @@ void Machine::run_for(const Cycles cycles) { void Machine::mos6522_did_change_interrupt_status(void *mos6522) { // both VIAs are connected to the IRQ line - m6502_.set_irq_line(serial_port_VIA_->get_interrupt_line() || drive_VIA_.get_interrupt_line()); + m6502_.set_irq_line(serial_port_VIA_.get_interrupt_line() || drive_VIA_.get_interrupt_line()); } #pragma mark - Disk drive @@ -99,16 +101,16 @@ void Machine::mos6522_did_change_interrupt_status(void *mos6522) { void Machine::process_input_bit(int value, unsigned int cycles_since_index_hole) { shift_register_ = (shift_register_ << 1) | value; if((shift_register_ & 0x3ff) == 0x3ff) { - drive_VIA_.set_sync_detected(true); + drive_VIA_port_handler_.set_sync_detected(true); bit_window_offset_ = -1; // i.e. this bit isn't the first within a data window, but the next might be } else { - drive_VIA_.set_sync_detected(false); + drive_VIA_port_handler_.set_sync_detected(false); } bit_window_offset_++; if(bit_window_offset_ == 8) { - drive_VIA_.set_data_input((uint8_t)shift_register_); + drive_VIA_port_handler_.set_data_input((uint8_t)shift_register_); bit_window_offset_ = 0; - if(drive_VIA_.get_should_set_overflow()) { + if(drive_VIA_port_handler_.get_should_set_overflow()) { m6502_.set_overflow_line(true); } } @@ -130,15 +132,15 @@ void Machine::drive_via_did_set_data_density(void *driveVIA, int density) { #pragma mark - SerialPortVIA -SerialPortVIA::SerialPortVIA() : - port_b_(0x00), attention_acknowledge_level_(false), attention_level_input_(true), data_level_output_(false) {} +SerialPortVIA::SerialPortVIA(MOS::MOS6522::MOS6522 &via) : + port_b_(0x00), attention_acknowledge_level_(false), attention_level_input_(true), data_level_output_(false), via_(via) {} -uint8_t SerialPortVIA::get_port_input(Port port) { +uint8_t SerialPortVIA::get_port_input(MOS::MOS6522::Port port) { if(port) return port_b_; return 0xff; } -void SerialPortVIA::set_port_output(Port port, uint8_t value, uint8_t mask) { +void SerialPortVIA::set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t mask) { if(port) { std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock(); if(serialPort) { @@ -159,7 +161,7 @@ void SerialPortVIA::set_serial_line_state(::Commodore::Serial::Line line, bool v case ::Commodore::Serial::Line::Attention: attention_level_input_ = !value; port_b_ = (port_b_ & ~0x80) | (value ? 0x00 : 0x80); - set_control_line_input(Port::A, Line::One, !value); + via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !value); update_data_line(); break; } @@ -187,7 +189,7 @@ void DriveVIA::set_delegate(Delegate *delegate) { // write protect tab uncovered DriveVIA::DriveVIA() : port_b_(0xff), port_a_(0xff), delegate_(nullptr) {} -uint8_t DriveVIA::get_port_input(Port port) { +uint8_t DriveVIA::get_port_input(MOS::MOS6522::Port port) { return port ? port_b_ : port_a_; } @@ -207,13 +209,13 @@ bool DriveVIA::get_motor_enabled() { return drive_motor_; } -void DriveVIA::set_control_line_output(Port port, Line line, bool value) { - if(port == Port::A && line == Line::Two) { +void DriveVIA::set_control_line_output(MOS::MOS6522::Port port, MOS::MOS6522::Line line, bool value) { + if(port == MOS::MOS6522::Port::A && line == MOS::MOS6522::Line::Two) { should_set_overflow_ = value; } } -void DriveVIA::set_port_output(Port port, uint8_t value, uint8_t direction_mask) { +void DriveVIA::set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t direction_mask) { if(port) { // record drive motor state drive_motor_ = !!(value&4); diff --git a/Machines/Commodore/1540/C1540.hpp b/Machines/Commodore/1540/C1540.hpp index f1fe08a68..012c5ed67 100644 --- a/Machines/Commodore/1540/C1540.hpp +++ b/Machines/Commodore/1540/C1540.hpp @@ -35,20 +35,19 @@ namespace C1540 { The attention input is also connected to CA1, similarly inverted — the CA1 wire will be high when the bus is low and vice versa. */ -class SerialPortVIA: public MOS::MOS6522, public MOS::MOS6522IRQDelegate { +class SerialPortVIA: public MOS::MOS6522::IRQDelegatePortHandler { public: - using MOS6522IRQDelegate::set_interrupt_status; + SerialPortVIA(MOS::MOS6522::MOS6522 &via); - SerialPortVIA(); + uint8_t get_port_input(MOS::MOS6522::Port); - uint8_t get_port_input(Port); - - void set_port_output(Port, uint8_t value, uint8_t mask); + void set_port_output(MOS::MOS6522::Port, uint8_t value, uint8_t mask); void set_serial_line_state(::Commodore::Serial::Line, bool); void set_serial_port(const std::shared_ptr<::Commodore::Serial::Port> &); private: + MOS::MOS6522::MOS6522 via_; uint8_t port_b_; std::weak_ptr<::Commodore::Serial::Port> serial_port_; bool attention_acknowledge_level_, attention_level_input_, data_level_output_; @@ -72,7 +71,7 @@ class SerialPortVIA: public MOS::MOS6522, public MOS::MOS6522IRQD It is implied that CA2 might be used to set processor overflow, CA1 a strobe for data input, and one of the CBs being definitive on whether the disk head is being told to read or write, but it's unclear and I've yet to investigate. So, TODO. */ -class DriveVIA: public MOS::MOS6522, public MOS::MOS6522IRQDelegate { +class DriveVIA: public MOS::MOS6522::IRQDelegatePortHandler { public: class Delegate { public: @@ -81,20 +80,18 @@ class DriveVIA: public MOS::MOS6522, public MOS::MOS6522IRQDelegate { }; void set_delegate(Delegate *); - using MOS6522IRQDelegate::set_interrupt_status; - DriveVIA(); - uint8_t get_port_input(Port port); + uint8_t get_port_input(MOS::MOS6522::Port port); void set_sync_detected(bool); void set_data_input(uint8_t); bool get_should_set_overflow(); bool get_motor_enabled(); - void set_control_line_output(Port, Line, bool value); + void set_control_line_output(MOS::MOS6522::Port, MOS::MOS6522::Line, bool value); - void set_port_output(Port, uint8_t value, uint8_t direction_mask); + void set_port_output(MOS::MOS6522::Port, uint8_t value, uint8_t direction_mask); private: uint8_t port_b_, port_a_; @@ -121,7 +118,7 @@ class SerialPort : public ::Commodore::Serial::Port { */ class Machine: public CPU::MOS6502::BusHandler, - public MOS::MOS6522IRQDelegate::Delegate, + public MOS::MOS6522::IRQDelegatePortHandler::Delegate, public DriveVIA::Delegate, public Storage::Disk::Controller { @@ -157,9 +154,12 @@ class Machine: uint8_t ram_[0x800]; uint8_t rom_[0x4000]; - std::shared_ptr serial_port_VIA_; + std::shared_ptr serial_port_VIA_port_handler_; std::shared_ptr serial_port_; - DriveVIA drive_VIA_; + DriveVIA drive_VIA_port_handler_; + + MOS::MOS6522::MOS6522 drive_VIA_; + MOS::MOS6522::MOS6522 serial_port_VIA_; int shift_register_, bit_window_offset_; virtual void process_input_bit(int value, unsigned int cycles_since_index_hole); diff --git a/Machines/Commodore/Vic-20/Vic20.cpp b/Machines/Commodore/Vic-20/Vic20.cpp index 8bb641c0a..5a7a83e6d 100644 --- a/Machines/Commodore/Vic-20/Vic20.cpp +++ b/Machines/Commodore/Vic-20/Vic20.cpp @@ -34,13 +34,12 @@ namespace Vic20 { sensing the presence or absence of a tape and controlling the tape motor — and reading the current state from its serial port. Most of the joystick input is also exposed here. */ -class UserPortVIA: public MOS::MOS6522, public MOS::MOS6522IRQDelegate { +class UserPortVIA: public MOS::MOS6522::IRQDelegatePortHandler { public: UserPortVIA() : port_a_(0xbf) {} - using MOS6522IRQDelegate::set_interrupt_status; /// Reports the current input to the 6522 port @c port. - uint8_t get_port_input(Port port) { + uint8_t get_port_input(MOS::MOS6522::Port port) { // Port A provides information about the presence or absence of a tape, and parts of // the joystick and serial port state, both of which have been statefully collected // into port_a_. @@ -51,9 +50,9 @@ class UserPortVIA: public MOS::MOS6522, public MOS::MOS6522IRQDeleg } /// Receives announcements of control line output change from the 6522. - void set_control_line_output(Port port, Line line, bool value) { + void set_control_line_output(MOS::MOS6522::Port port, MOS::MOS6522::Line line, bool value) { // The CA2 output is used to control the tape motor. - if(port == Port::A && line == Line::Two) { + if(port == MOS::MOS6522::Port::A && line == MOS::MOS6522::Line::Two) { tape_->set_motor_control(!value); } } @@ -75,7 +74,7 @@ class UserPortVIA: public MOS::MOS6522, public MOS::MOS6522IRQDeleg } /// Receives announcements from the 6522 of user-port output, which might affect what's currently being presented onto the serial bus. - void set_port_output(Port port, uint8_t value, uint8_t mask) { + void set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t mask) { // Line 7 of port A is inverted and output as serial ATN. if(!port) { std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock(); @@ -103,14 +102,12 @@ class UserPortVIA: public MOS::MOS6522, public MOS::MOS6522IRQDeleg Models the keyboard VIA, which is used by the Vic for reading its keyboard, to output to its serial port, and for the small portion of joystick input not connected to the user-port VIA. */ -class KeyboardVIA: public MOS::MOS6522, public MOS::MOS6522IRQDelegate { +class KeyboardVIA: public MOS::MOS6522::IRQDelegatePortHandler { public: KeyboardVIA() : port_b_(0xff) { clear_all_keys(); } - using MOS6522IRQDelegate::set_interrupt_status; - /// Sets whether @c key @c is_pressed. void set_key_state(uint16_t key, bool is_pressed) { if(is_pressed) @@ -125,7 +122,7 @@ class KeyboardVIA: public MOS::MOS6522, public MOS::MOS6522IRQDeleg } /// Called by the 6522 to get input. Reads the keyboard on Port A, returns a small amount of joystick state on Port B. - uint8_t get_port_input(Port port) { + uint8_t get_port_input(MOS::MOS6522::Port port) { if(!port) { uint8_t result = 0xff; for(int c = 0; c < 8; c++) { @@ -139,17 +136,17 @@ class KeyboardVIA: public MOS::MOS6522, public MOS::MOS6522IRQDeleg } /// Called by the 6522 to set output. The value of Port B selects which part of the keyboard to read. - void set_port_output(Port port, uint8_t value, uint8_t mask) { + void set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t mask) { if(port) activation_mask_ = (value & mask) | (~mask); } /// Called by the 6522 to set control line output. Which affects the serial port. - void set_control_line_output(Port port, Line line, bool value) { - if(line == Line::Two) { + void set_control_line_output(MOS::MOS6522::Port port, MOS::MOS6522::Line line, bool value) { + if(line == MOS::MOS6522::Line::Two) { std::shared_ptr<::Commodore::Serial::Port> serialPort = serial_port_.lock(); if(serialPort) { // CB2 is inverted to become serial data; CA2 is inverted to become serial clock - if(port == Port::A) + if(port == MOS::MOS6522::Port::A) serialPort->set_output(::Commodore::Serial::Line::Clock, (::Commodore::Serial::LineLevel)!value); else serialPort->set_output(::Commodore::Serial::Line::Data, (::Commodore::Serial::LineLevel)!value); @@ -214,7 +211,7 @@ class Vic6560: public MOS::MOS6560 { class ConcreteMachine: public CPU::MOS6502::BusHandler, - public MOS::MOS6522IRQDelegate::Delegate, + public MOS::MOS6522::IRQDelegatePortHandler::Delegate, public Utility::TypeRecipient, public Storage::Tape::BinaryTapePlayer::Delegate, public Machine { @@ -224,24 +221,26 @@ class ConcreteMachine: rom_(nullptr), is_running_at_zero_cost_(false), tape_(new Storage::Tape::BinaryTapePlayer(1022727)), - user_port_via_(new UserPortVIA), - keyboard_via_(new KeyboardVIA), + user_port_via_port_handler_(new UserPortVIA), + keyboard_via_port_handler_(new KeyboardVIA), serial_port_(new SerialPort), - serial_bus_(new ::Commodore::Serial::Bus) { + serial_bus_(new ::Commodore::Serial::Bus), + user_port_via_(*user_port_via_port_handler_), + keyboard_via_(*keyboard_via_port_handler_) { // communicate the tape to the user-port VIA - user_port_via_->set_tape(tape_); + user_port_via_port_handler_->set_tape(tape_); // wire up the serial bus and serial port Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus_); // wire up 6522s and serial port - user_port_via_->set_serial_port(serial_port_); - keyboard_via_->set_serial_port(serial_port_); - serial_port_->set_user_port_via(user_port_via_); + user_port_via_port_handler_->set_serial_port(serial_port_); + keyboard_via_port_handler_->set_serial_port(serial_port_); + serial_port_->set_user_port_via(user_port_via_port_handler_); // wire up the 6522s, tape and machine - user_port_via_->set_interrupt_delegate(this); - keyboard_via_->set_interrupt_delegate(this); + user_port_via_port_handler_->set_interrupt_delegate(this); + keyboard_via_port_handler_->set_interrupt_delegate(this); tape_->set_delegate(this); // establish the memory maps @@ -329,16 +328,16 @@ class ConcreteMachine: } void set_key_state(uint16_t key, bool isPressed) override final { - keyboard_via_->set_key_state(key, isPressed); + keyboard_via_port_handler_->set_key_state(key, isPressed); } void clear_all_keys() override final { - keyboard_via_->clear_all_keys(); + keyboard_via_port_handler_->clear_all_keys(); } void set_joystick_state(JoystickInput input, bool isPressed) override final { - user_port_via_->set_joystick_state(input, isPressed); - keyboard_via_->set_joystick_state(input, isPressed); + user_port_via_port_handler_->set_joystick_state(input, isPressed); + keyboard_via_port_handler_->set_joystick_state(input, isPressed); } void set_memory_size(MemorySize size) override final { @@ -409,8 +408,8 @@ class ConcreteMachine: uint8_t result = processor_read_memory_map_[address >> 10] ? processor_read_memory_map_[address >> 10][address & 0x3ff] : 0xff; if((address&0xfc00) == 0x9000) { if((address&0xff00) == 0x9000) result &= mos6560_->get_register(address); - if((address&0xfc10) == 0x9010) result &= user_port_via_->get_register(address); - if((address&0xfc20) == 0x9020) result &= keyboard_via_->get_register(address); + if((address&0xfc10) == 0x9010) result &= user_port_via_.get_register(address); + if((address&0xfc20) == 0x9020) result &= keyboard_via_.get_register(address); } *value = result; @@ -477,13 +476,13 @@ class ConcreteMachine: if(ram) ram[address & 0x3ff] = *value; if((address&0xfc00) == 0x9000) { if((address&0xff00) == 0x9000) mos6560_->set_register(address, *value); - if((address&0xfc10) == 0x9010) user_port_via_->set_register(address, *value); - if((address&0xfc20) == 0x9020) keyboard_via_->set_register(address, *value); + if((address&0xfc10) == 0x9010) user_port_via_.set_register(address, *value); + if((address&0xfc20) == 0x9020) keyboard_via_.set_register(address, *value); } } - user_port_via_->run_for(Cycles(1)); - keyboard_via_->run_for(Cycles(1)); + user_port_via_.run_for(Cycles(1)); + keyboard_via_.run_for(Cycles(1)); if(typer_ && operation == CPU::MOS6502::BusOperation::ReadOpcode && address == 0xEB1E) { if(!typer_->type_next_character()) { clear_all_keys(); @@ -529,8 +528,8 @@ class ConcreteMachine: } void mos6522_did_change_interrupt_status(void *mos6522) override final { - m6502_.set_nmi_line(user_port_via_->get_interrupt_line()); - m6502_.set_irq_line(keyboard_via_->get_interrupt_line()); + m6502_.set_nmi_line(user_port_via_.get_interrupt_line()); + m6502_.set_irq_line(keyboard_via_.get_interrupt_line()); } void set_typer_for_string(const char *string) override final { @@ -539,7 +538,7 @@ class ConcreteMachine: } void tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape) override final { - keyboard_via_->set_control_line_input(KeyboardVIA::Port::A, KeyboardVIA::Line::One, !tape->get_input()); + keyboard_via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !tape->get_input()); } private: @@ -573,11 +572,14 @@ class ConcreteMachine: Region region_; std::unique_ptr mos6560_; - std::shared_ptr user_port_via_; - std::shared_ptr keyboard_via_; + std::shared_ptr user_port_via_port_handler_; + std::shared_ptr keyboard_via_port_handler_; std::shared_ptr serial_port_; std::shared_ptr<::Commodore::Serial::Bus> serial_bus_; + MOS::MOS6522::MOS6522 user_port_via_; + MOS::MOS6522::MOS6522 keyboard_via_; + // Tape std::shared_ptr tape_; bool use_fast_tape_hack_; diff --git a/Machines/Oric/Oric.cpp b/Machines/Oric/Oric.cpp index ce1978954..5d978e9d4 100644 --- a/Machines/Oric/Oric.cpp +++ b/Machines/Oric/Oric.cpp @@ -32,7 +32,7 @@ namespace Oric { class ConcreteMachine: public CPU::MOS6502::BusHandler, - public MOS::MOS6522IRQDelegate::Delegate, + public MOS::MOS6522::IRQDelegatePortHandler::Delegate, public Utility::TypeRecipient, public Storage::Tape::BinaryTapePlayer::Delegate, public Microdisc::Delegate, @@ -47,12 +47,13 @@ class ConcreteMachine: keyboard_(new Keyboard), ram_top_(0xbfff), paged_rom_(rom_), - microdisc_is_enabled_(false) { + microdisc_is_enabled_(false), + via_(via_port_handler_) { set_clock_rate(1000000); - via_.set_interrupt_delegate(this); - via_.keyboard = keyboard_; + via_port_handler_.set_interrupt_delegate(this); + via_port_handler_.keyboard = keyboard_; clear_all_keys(); - via_.tape->set_delegate(this); + via_port_handler_.tape->set_delegate(this); Memory::Fuzz(ram_, sizeof(ram_)); } @@ -124,7 +125,7 @@ class ConcreteMachine: bool insert_media(const StaticAnalyser::Media &media) override final { if(media.tapes.size()) { - via_.tape->set_tape(media.tapes.front()); + via_port_handler_.tape->set_tape(media.tapes.front()); } int drive_index = 0; @@ -143,8 +144,8 @@ class ConcreteMachine: // 024D = 0 => fast; otherwise slow // E6C9 = read byte: return byte in A - if(address == tape_get_byte_address_ && paged_rom_ == rom_ && use_fast_tape_hack_ && operation == CPU::MOS6502::BusOperation::ReadOpcode && via_.tape->has_tape() && !via_.tape->get_tape()->is_at_end()) { - uint8_t next_byte = via_.tape->get_next_byte(!ram_[tape_speed_address_]); + if(address == tape_get_byte_address_ && paged_rom_ == rom_ && use_fast_tape_hack_ && operation == CPU::MOS6502::BusOperation::ReadOpcode && via_port_handler_.tape->has_tape() && !via_port_handler_.tape->get_tape()->is_at_end()) { + uint8_t next_byte = via_port_handler_.tape->get_next_byte(!ram_[tape_speed_address_]); m6502_.set_value_of_register(CPU::MOS6502::A, next_byte); m6502_.set_value_of_register(CPU::MOS6502::Flags, next_byte ? 0 : CPU::MOS6502::Flag::Zero); *value = 0x60; // i.e. RTS @@ -190,6 +191,7 @@ class ConcreteMachine: } via_.run_for(Cycles(1)); + via_port_handler_.run_for(Cycles(1)); if(microdisc_is_enabled_) microdisc_.run_for(Cycles(8)); cycles_since_video_update_++; return Cycles(1); @@ -197,20 +199,20 @@ class ConcreteMachine: forceinline void flush() { update_video(); - via_.flush(); + via_port_handler_.flush(); } // to satisfy CRTMachine::Machine void setup_output(float aspect_ratio) override final { - via_.ay8910.reset(new GI::AY38910::AY38910()); - via_.ay8910->set_clock_rate(1000000); + via_port_handler_.ay8910.reset(new GI::AY38910::AY38910()); + via_port_handler_.ay8910->set_clock_rate(1000000); video_output_.reset(new VideoOutput(ram_)); if(!colour_rom_.empty()) video_output_->set_colour_rom(colour_rom_); } void close_output() override final { video_output_.reset(); - via_.ay8910.reset(); + via_port_handler_.ay8910.reset(); } std::shared_ptr get_crt() override final { @@ -218,7 +220,7 @@ class ConcreteMachine: } std::shared_ptr get_speaker() override final { - return via_.ay8910; + return via_port_handler_.ay8910; } void run_for(const Cycles cycles) override final { @@ -233,7 +235,7 @@ class ConcreteMachine: // to satisfy Storage::Tape::BinaryTapePlayer::Delegate void tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape_player) override final { // set CB1 - via_.set_control_line_input(VIA::Port::B, VIA::Line::One, !tape_player->get_input()); + via_.set_control_line_input(MOS::MOS6522::Port::B, MOS::MOS6522::Line::One, !tape_player->get_input()); } // for Utility::TypeRecipient::Delegate @@ -304,22 +306,18 @@ class ConcreteMachine: bool use_fast_tape_hack_; // VIA (which owns the tape and the AY) - class VIA: public MOS::MOS6522, public MOS::MOS6522IRQDelegate { + class VIA: public MOS::MOS6522::IRQDelegatePortHandler { public: - VIA() : - MOS::MOS6522(), - tape(new TapePlayer) {} + VIA() : tape(new TapePlayer) {} - using MOS6522IRQDelegate::set_interrupt_status; - - void set_control_line_output(Port port, Line line, bool value) { + void set_control_line_output(MOS::MOS6522::Port port, MOS::MOS6522::Line line, bool value) { if(line) { if(port) ay_bdir_ = value; else ay_bc1_ = value; update_ay(); } } - void set_port_output(Port port, uint8_t value, uint8_t direction_mask) { + void set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t direction_mask) { if(port) { keyboard->row = value; tape->set_motor_control(value & 0x40); @@ -328,7 +326,7 @@ class ConcreteMachine: } } - uint8_t get_port_input(Port port) { + uint8_t get_port_input(MOS::MOS6522::Port port) { if(port) { uint8_t column = ay8910->get_port_output(false) ^ 0xff; return (keyboard->rows[keyboard->row & 7] & column) ? 0x08 : 0x00; @@ -339,7 +337,6 @@ class ConcreteMachine: inline void run_for(const Cycles cycles) { cycles_since_ay_update_ += cycles; - MOS::MOS6522::run_for(cycles); tape->run_for(cycles); } @@ -360,7 +357,8 @@ class ConcreteMachine: bool ay_bdir_, ay_bc1_; Cycles cycles_since_ay_update_; }; - VIA via_; + VIA via_port_handler_; + MOS::MOS6522::MOS6522 via_; std::shared_ptr keyboard_; // the Microdisc, if in use diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 78389748d..fbc0231ff 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -93,6 +93,8 @@ 4B8334821F5D9FF70097E338 /* PartialMachineCycle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334811F5D9FF70097E338 /* PartialMachineCycle.cpp */; }; 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 */; }; 4B8378DC1F336631005CA9E4 /* CharacterMapper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8378DA1F336631005CA9E4 /* CharacterMapper.cpp */; }; 4B8378DF1F33675F005CA9E4 /* CharacterMapper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8378DD1F33675F005CA9E4 /* CharacterMapper.cpp */; }; 4B8378E21F336920005CA9E4 /* CharacterMapper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8378E01F336920005CA9E4 /* CharacterMapper.cpp */; }; @@ -631,6 +633,10 @@ 4B8334811F5D9FF70097E338 /* PartialMachineCycle.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PartialMachineCycle.cpp; sourceTree = ""; }; 4B8334831F5DA0360097E338 /* Z80Storage.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Z80Storage.cpp; sourceTree = ""; }; 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 = ""; }; 4B8378DA1F336631005CA9E4 /* CharacterMapper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CharacterMapper.cpp; path = Electron/CharacterMapper.cpp; sourceTree = ""; }; 4B8378DB1F336631005CA9E4 /* CharacterMapper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CharacterMapper.hpp; path = Electron/CharacterMapper.hpp; sourceTree = ""; }; 4B8378DD1F33675F005CA9E4 /* CharacterMapper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CharacterMapper.cpp; path = ZX8081/CharacterMapper.cpp; sourceTree = ""; }; @@ -1568,6 +1574,17 @@ name = Z80; sourceTree = ""; }; + 4B8334881F5DB8470097E338 /* Implementation */ = { + isa = PBXGroup; + children = ( + 4B83348B1F5DB99C0097E338 /* 6522Base.cpp */, + 4B8334891F5DB94B0097E338 /* IRQDelegatePortHandler.cpp */, + 4B8334871F5DB8410097E338 /* 6522Implementation.hpp */, + 4B83348E1F5DBA6E0097E338 /* 6522Storage.hpp */, + ); + name = Implementation; + sourceTree = ""; + }; 4B8805F11DCFC9A2003085B1 /* Parsers */ = { isa = PBXGroup; children = ( @@ -2159,6 +2176,7 @@ isa = PBXGroup; children = ( 4BCA98C21D065CA20062F44C /* 6522.hpp */, + 4B8334881F5DB8470097E338 /* Implementation */, ); path = 6522; sourceTree = ""; @@ -2862,11 +2880,13 @@ 4B38F3441F2EB3E900D9235D /* StaticAnalyser.cpp in Sources */, 4B3051301D98ACC600B4FED8 /* Plus3.cpp in Sources */, 4B30512D1D989E2200B4FED8 /* Drive.cpp in Sources */, + 4B83348C1F5DB99C0097E338 /* 6522Base.cpp in Sources */, 4BCA6CC81D9DD9F000C2D7B2 /* CommodoreROM.cpp in Sources */, 4BA22B071D8817CE0008C640 /* Disk.cpp in Sources */, 4BEA52661DF3472B007E74F2 /* Speaker.cpp in Sources */, 4BBFBB6C1EE8401E00C01E7A /* ZX8081.cpp in Sources */, 4B838F1F1F35FDCD0016B5E6 /* CPCDSK.cpp in Sources */, + 4B83348A1F5DB94B0097E338 /* IRQDelegatePortHandler.cpp in Sources */, 4BC3B7521CD1956900F86E85 /* OutputShader.cpp in Sources */, 4B4C83701D4F623200CD541F /* D64.cpp in Sources */, 4B5073071DDD3B9400C48FBD /* ArrayBuilder.cpp in Sources */, From 450712f39cbbe38732dba03b0927a58c1367c173 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 4 Sep 2017 14:32:34 -0400 Subject: [PATCH 2/4] Improves and corrects 6522 header documentation. --- Components/6522/6522.hpp | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/Components/6522/6522.hpp b/Components/6522/6522.hpp index e211b5399..e65905fc5 100644 --- a/Components/6522/6522.hpp +++ b/Components/6522/6522.hpp @@ -30,26 +30,41 @@ enum Line { Two = 1 }; +/*! + Provides the mechanism for just-int-time communication from a 6522; the normal use case is to compose a + 6522 and a subclass of PortHandler in order to reproduce a 6522 and its original bus wiring. +*/ class PortHandler { public: + /// Requests the current input value of @c port from the port handler. uint8_t get_port_input(Port port) { return 0xff; } + + /// Sets the current output value of @c port and provides @c direction_mask, indicating which pins are marked as output. void set_port_output(Port port, uint8_t value, uint8_t direction_mask) {} + + /// Sets the current logical output level for line @c line on port @c port. void set_control_line_output(Port port, Line line, bool value) {} + + /// Sets the current logical value of the interrupt line. void set_interrupt_status(bool status) {} }; /*! - Provided for optional composition with @c MOS6522, @c MOS6522IRQDelegate provides for a delegate - that will receive IRQ line change notifications. + Provided as an optional alternative base to @c PortHandler for port handlers; via the delegate pattern adds + a virtual level of indirection for receiving changes to the interrupt line. */ class IRQDelegatePortHandler: public PortHandler { public: class Delegate { public: - virtual void mos6522_did_change_interrupt_status(void *mos6522) = 0; + /// Indicates that the interrupt status has changed for the IRQDelegatePortHandler provided. + virtual void mos6522_did_change_interrupt_status(void *irq_delegate) = 0; }; + /// Sets the delegate that will receive notification of changes in the interrupt line. void set_interrupt_delegate(Delegate *delegate); + + /// Overrides PortHandler::set_interrupt_status, notifying the delegate if one is set. void set_interrupt_status(bool new_status); private: @@ -58,15 +73,16 @@ class IRQDelegatePortHandler: public PortHandler { 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. */ + /// Runs for a specified number of half cycles. void run_for(const HalfCycles half_cycles); - /*! Runs for a specified number of 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. */ + /// @returns @c true if the IRQ line is currently active; @c false otherwise. bool get_interrupt_line(); private: From da09098e49e938896f06d983d96fa3e6e70ba08c Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 4 Sep 2017 17:51:02 -0400 Subject: [PATCH 3/4] Updates clipped area per latest CRT response to vertical sync. --- Machines/Oric/Video.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Machines/Oric/Video.cpp b/Machines/Oric/Video.cpp index 20db2a6ce..e29012803 100644 --- a/Machines/Oric/Video.cpp +++ b/Machines/Oric/Video.cpp @@ -45,7 +45,7 @@ VideoOutput::VideoOutput(uint8_t *memory) : crt_->set_composite_function_type(Outputs::CRT::CRT::CompositeSourceType::DiscreteFourSamplesPerCycle, 0.0f); set_output_device(Outputs::CRT::Television); - crt_->set_visible_area(crt_->get_rect_for_area(50, 224, 16 * 6, 40 * 6, 4.0f / 3.0f)); + crt_->set_visible_area(crt_->get_rect_for_area(53, 224, 16 * 6, 40 * 6, 4.0f / 3.0f)); } void VideoOutput::set_output_device(Outputs::CRT::OutputDevice output_device) { From a42ca290cb6b5f96b96f374cb9a7bc584d7f6ce0 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 4 Sep 2017 18:22:14 -0400 Subject: [PATCH 4/4] Reformulates the Oric more cleanly into the modern world. Specifically: now that the implementation is contained within the CPP file, there's no need to embed the keyboard, tape player and VIA port handler as private classes. Also the pain of additional syntax is reduced, so the keyboard has been bumped up to a fully data-hiding class. I've also transferred overall ownership of the tape player, AY and keyboard up to the Oric itself, with the VIA merely being wired to them, and added a whole bunch of extra documentation. --- Machines/Oric/Oric.cpp | 286 ++++++++++++++++++++++++++--------------- 1 file changed, 179 insertions(+), 107 deletions(-) diff --git a/Machines/Oric/Oric.cpp b/Machines/Oric/Oric.cpp index 5d978e9d4..e863a2b03 100644 --- a/Machines/Oric/Oric.cpp +++ b/Machines/Oric/Oric.cpp @@ -24,12 +24,153 @@ #include "../../ClockReceiver/ForceInline.hpp" -#include #include -#include namespace Oric { +/*! + Models the Oric's keyboard: eight key rows, containing a bitfield of keys set. + + Active line is selected through a port on the Oric's VIA, and a column mask is + selected via a port on the AY, returning a single Boolean representation of the + logical OR of every key selected by the column mask on the active row. +*/ +class Keyboard { + public: + Keyboard() { + clear_all_keys(); + } + + /// Sets whether @c key is or is not pressed, per @c is_pressed. + void set_key_state(uint16_t key, bool is_pressed) { + uint8_t mask = key & 0xff; + int line = key >> 8; + + if(is_pressed) rows_[line] |= mask; + else rows_[line] &= ~mask; + } + + /// Sets all keys as unpressed. + void clear_all_keys() { + memset(rows_, 0, sizeof(rows_)); + } + + /// Selects the active row. + void set_active_row(uint8_t row) { + row_ = row & 7; + } + + /// Queries the keys on the active row specified by @c mask. + bool query_column(uint8_t column_mask) { + return !!(rows_[row_] & column_mask); + } + + private: + uint8_t row_ = 0; + uint8_t rows_[8]; +}; + +/*! + Provide's the Oric's tape player: a standard binary-sampled tape which also holds + an instance of the Oric tape parser, to provide fast-tape loading. +*/ +class TapePlayer: public Storage::Tape::BinaryTapePlayer { + public: + TapePlayer() : Storage::Tape::BinaryTapePlayer(1000000) {} + + /*! + Parses the incoming tape event stream to obtain the next stored byte. + + @param use_fast_encoding If set to @c true , inspects the tape as though it + is encoded in the Oric's fast-loading scheme. Otherwise looks for a slow-encoded byte. + + @returns The next byte from the tape. + */ + uint8_t get_next_byte(bool use_fast_encoding) { + return (uint8_t)parser_.get_next_byte(get_tape(), use_fast_encoding); + } + + private: + Storage::Tape::Oric::Parser parser_; +}; + +/*! + Implements the Oric's VIA's port handler. On the Oric the VIA's ports connect + to the AY, the tape's motor control signal and the keyboard. +*/ +class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler { + public: + VIAPortHandler(TapePlayer &tape_player, Keyboard &keyboard) : tape_player_(tape_player), keyboard_(keyboard) {} + + /*! + Reponds to the 6522's control line output change signal; on an Oric A2 is connected to + the AY's BDIR, and B2 is connected to the AY's A2. + */ + void set_control_line_output(MOS::MOS6522::Port port, MOS::MOS6522::Line line, bool value) { + if(line) { + if(port) ay_bdir_ = value; else ay_bc1_ = value; + update_ay(); + ay8910_->set_control_lines( (GI::AY38910::ControlLines)((ay_bdir_ ? GI::AY38910::BDIR : 0) | (ay_bc1_ ? GI::AY38910::BC1 : 0) | GI::AY38910::BC2)); + } + } + + /*! + Reponds to changes in the 6522's port output. On an Oric port B sets the tape motor control + and the keyboard's active row. Port A is connected to the AY's data bus. + */ + void set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t direction_mask) { + if(port) { + keyboard_.set_active_row(value); + tape_player_.set_motor_control(value & 0x40); + } else { + update_ay(); + ay8910_->set_data_input(value); + } + } + + /*! + Provides input data for the 6522. Port B reads the keyboard, and port A reads from the AY. + */ + uint8_t get_port_input(MOS::MOS6522::Port port) { + if(port) { + uint8_t column = ay8910_->get_port_output(false) ^ 0xff; + return keyboard_.query_column(column) ? 0x08 : 0x00; + } else { + return ay8910_->get_data_output(); + } + } + + /*! + Advances time. This class manages the AY's concept of time to permit updating-on-demand. + */ + inline void run_for(const Cycles cycles) { + cycles_since_ay_update_ += cycles; + } + + /// Flushes any queued behaviour (which, specifically, means on the AY). + void flush() { + ay8910_->run_for(cycles_since_ay_update_.flush()); + ay8910_->flush(); + } + + /// Sets the AY in use by the machine the VIA that uses this port handler sits within. + void set_ay(GI::AY38910::AY38910 *ay) { + ay8910_ = ay; + } + + private: + void update_ay() { + ay8910_->run_for(cycles_since_ay_update_.flush()); + } + bool ay_bdir_ = false; + bool ay_bc1_ = false; + Cycles cycles_since_ay_update_; + + GI::AY38910::AY38910 *ay8910_ = nullptr; + TapePlayer &tape_player_; + Keyboard &keyboard_; +}; + class ConcreteMachine: public CPU::MOS6502::BusHandler, public MOS::MOS6522::IRQDelegatePortHandler::Delegate, @@ -41,19 +182,12 @@ class ConcreteMachine: public: ConcreteMachine() : m6502_(*this), - use_fast_tape_hack_(false), - typer_delay_(2500000), - keyboard_read_count_(0), - keyboard_(new Keyboard), - ram_top_(0xbfff), paged_rom_(rom_), - microdisc_is_enabled_(false), - via_(via_port_handler_) { + via_(via_port_handler_), + via_port_handler_(tape_player_, keyboard_) { set_clock_rate(1000000); via_port_handler_.set_interrupt_delegate(this); - via_port_handler_.keyboard = keyboard_; - clear_all_keys(); - via_port_handler_.tape->set_delegate(this); + tape_player_.set_delegate(this); Memory::Fuzz(ram_, sizeof(ram_)); } @@ -69,19 +203,16 @@ class ConcreteMachine: } } - void set_key_state(uint16_t key, bool isPressed) override final { + void set_key_state(uint16_t key, bool is_pressed) override final { if(key == KeyNMI) { - m6502_.set_nmi_line(isPressed); + m6502_.set_nmi_line(is_pressed); } else { - if(isPressed) - keyboard_->rows[key >> 8] |= (key & 0xff); - else - keyboard_->rows[key >> 8] &= ~(key & 0xff); + keyboard_.set_key_state(key, is_pressed); } } void clear_all_keys() override final { - memset(keyboard_->rows, 0, sizeof(keyboard_->rows)); + keyboard_.clear_all_keys(); } void set_use_fast_tape_hack(bool activate) override final { @@ -125,7 +256,7 @@ class ConcreteMachine: bool insert_media(const StaticAnalyser::Media &media) override final { if(media.tapes.size()) { - via_port_handler_.tape->set_tape(media.tapes.front()); + tape_player_.set_tape(media.tapes.front()); } int drive_index = 0; @@ -144,8 +275,14 @@ class ConcreteMachine: // 024D = 0 => fast; otherwise slow // E6C9 = read byte: return byte in A - if(address == tape_get_byte_address_ && paged_rom_ == rom_ && use_fast_tape_hack_ && operation == CPU::MOS6502::BusOperation::ReadOpcode && via_port_handler_.tape->has_tape() && !via_port_handler_.tape->get_tape()->is_at_end()) { - uint8_t next_byte = via_port_handler_.tape->get_next_byte(!ram_[tape_speed_address_]); + if( address == tape_get_byte_address_ && + paged_rom_ == rom_ && + use_fast_tape_hack_ && + operation == CPU::MOS6502::BusOperation::ReadOpcode && + tape_player_.has_tape() && + !tape_player_.get_tape()->is_at_end()) { + + uint8_t next_byte = tape_player_.get_next_byte(!ram_[tape_speed_address_]); m6502_.set_value_of_register(CPU::MOS6502::A, next_byte); m6502_.set_value_of_register(CPU::MOS6502::Flags, next_byte ? 0 : CPU::MOS6502::Flag::Zero); *value = 0x60; // i.e. RTS @@ -174,7 +311,7 @@ class ConcreteMachine: if(isReadOperation(operation)) *value = ram_[address]; else { - if(address >= 0x9800 && address <= 0xc000) { update_video(); typer_delay_ = 0; } + if(address >= 0x9800 && address <= 0xc000) update_video(); ram_[address] = *value; } } @@ -192,6 +329,7 @@ class ConcreteMachine: via_.run_for(Cycles(1)); via_port_handler_.run_for(Cycles(1)); + tape_player_.run_for(Cycles(1)); if(microdisc_is_enabled_) microdisc_.run_for(Cycles(8)); cycles_since_video_update_++; return Cycles(1); @@ -204,15 +342,18 @@ class ConcreteMachine: // to satisfy CRTMachine::Machine void setup_output(float aspect_ratio) override final { - via_port_handler_.ay8910.reset(new GI::AY38910::AY38910()); - via_port_handler_.ay8910->set_clock_rate(1000000); + ay8910_.reset(new GI::AY38910::AY38910()); + ay8910_->set_clock_rate(1000000); + via_port_handler_.set_ay(ay8910_.get()); + video_output_.reset(new VideoOutput(ram_)); if(!colour_rom_.empty()) video_output_->set_colour_rom(colour_rom_); } void close_output() override final { video_output_.reset(); - via_port_handler_.ay8910.reset(); + ay8910_.reset(); + via_port_handler_.set_ay(nullptr); } std::shared_ptr get_crt() override final { @@ -220,7 +361,7 @@ class ConcreteMachine: } std::shared_ptr get_speaker() override final { - return via_port_handler_.ay8910; + return ay8910_; } void run_for(const Cycles cycles) override final { @@ -276,95 +417,26 @@ class ConcreteMachine: } // ROM bookkeeping - bool is_using_basic11_; - uint16_t tape_get_byte_address_, scan_keyboard_address_, tape_speed_address_; - int keyboard_read_count_; + bool is_using_basic11_ = false; + uint16_t tape_get_byte_address_ = 0, scan_keyboard_address_ = 0, tape_speed_address_ = 0; + int keyboard_read_count_ = 0; // Outputs std::unique_ptr video_output_; - - // Keyboard - class Keyboard { - public: - uint8_t row; - uint8_t rows[8]; - }; - int typer_delay_; + std::shared_ptr ay8910_; // The tape - class TapePlayer: public Storage::Tape::BinaryTapePlayer { - public: - TapePlayer() : Storage::Tape::BinaryTapePlayer(1000000) {} + TapePlayer tape_player_; + bool use_fast_tape_hack_ = false; - inline uint8_t get_next_byte(bool fast) { - return (uint8_t)parser_.get_next_byte(get_tape(), fast); - } - - private: - Storage::Tape::Oric::Parser parser_; - }; - bool use_fast_tape_hack_; - - // VIA (which owns the tape and the AY) - class VIA: public MOS::MOS6522::IRQDelegatePortHandler { - public: - VIA() : tape(new TapePlayer) {} - - void set_control_line_output(MOS::MOS6522::Port port, MOS::MOS6522::Line line, bool value) { - if(line) { - if(port) ay_bdir_ = value; else ay_bc1_ = value; - update_ay(); - } - } - - void set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t direction_mask) { - if(port) { - keyboard->row = value; - tape->set_motor_control(value & 0x40); - } else { - ay8910->set_data_input(value); - } - } - - uint8_t get_port_input(MOS::MOS6522::Port port) { - if(port) { - uint8_t column = ay8910->get_port_output(false) ^ 0xff; - return (keyboard->rows[keyboard->row & 7] & column) ? 0x08 : 0x00; - } else { - return ay8910->get_data_output(); - } - } - - inline void run_for(const Cycles cycles) { - cycles_since_ay_update_ += cycles; - tape->run_for(cycles); - } - - void flush() { - ay8910->run_for(cycles_since_ay_update_.flush()); - ay8910->flush(); - } - - std::shared_ptr ay8910; - std::unique_ptr tape; - std::shared_ptr keyboard; - - private: - void update_ay() { - ay8910->run_for(cycles_since_ay_update_.flush()); - ay8910->set_control_lines( (GI::AY38910::ControlLines)((ay_bdir_ ? GI::AY38910::BDIR : 0) | (ay_bc1_ ? GI::AY38910::BC1 : 0) | GI::AY38910::BC2)); - } - bool ay_bdir_, ay_bc1_; - Cycles cycles_since_ay_update_; - }; - VIA via_port_handler_; - MOS::MOS6522::MOS6522 via_; - std::shared_ptr keyboard_; + VIAPortHandler via_port_handler_; + MOS::MOS6522::MOS6522 via_; + Keyboard keyboard_; // the Microdisc, if in use class Microdisc microdisc_; - bool microdisc_is_enabled_; - uint16_t ram_top_; + bool microdisc_is_enabled_ = false; + uint16_t ram_top_ = 0xbfff; uint8_t *paged_rom_; inline void set_interrupt_line() {