mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-27 01:31:42 +00:00
Merge pull request #237 from TomHarte/6522CleanUp
Significantly cleans up the 6522.
This commit is contained in:
commit
f26fe3756c
@ -13,9 +13,83 @@
|
||||
#include <typeinfo>
|
||||
#include <cstdio>
|
||||
|
||||
#include "Implementation/6522Storage.hpp"
|
||||
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
namespace MOS {
|
||||
namespace MOS6522 {
|
||||
|
||||
enum Port {
|
||||
A = 0,
|
||||
B = 1
|
||||
};
|
||||
|
||||
enum Line {
|
||||
One = 0,
|
||||
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 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:
|
||||
/// 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:
|
||||
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').
|
||||
@ -28,353 +102,26 @@ namespace MOS {
|
||||
Consumers should derive their own curiously-recurring-template-pattern subclass,
|
||||
implementing bus communications as required.
|
||||
*/
|
||||
template <class T> 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 T> 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<T *>(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<T *>(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<T *>(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<T *>(this)->set_control_line_output(Port::A, Line::Two, false); break;
|
||||
case 0x0e: static_cast<T *>(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<T *>(this)->set_control_line_output(Port::B, Line::Two, false); break;
|
||||
case 0xe0: static_cast<T *>(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<T *>(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<T *>(this)->set_interrupt_status(new_interrupt_status);
|
||||
}
|
||||
}
|
||||
|
||||
// The registers
|
||||
struct Registers {
|
||||
uint8_t output[2], input[2], data_direction[2];
|
||||
uint16_t timer[2], timer_latch[2], 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;
|
||||
};
|
||||
#include "Implementation/6522Implementation.hpp"
|
||||
|
||||
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_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* _522_hpp */
|
||||
|
116
Components/6522/Implementation/6522Base.cpp
Normal file
116
Components/6522/Implementation/6522Base.cpp
Normal file
@ -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;
|
||||
}
|
155
Components/6522/Implementation/6522Implementation.hpp
Normal file
155
Components/6522/Implementation/6522Implementation.hpp
Normal file
@ -0,0 +1,155 @@
|
||||
//
|
||||
// Implementation.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 04/09/2017.
|
||||
// Copyright © 2017 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
template <typename T> void MOS6522<T>::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 <typename T> uint8_t MOS6522<T>::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 <typename T> uint8_t MOS6522<T>::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 <typename T> void MOS6522<T>::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);
|
||||
}
|
||||
}
|
63
Components/6522/Implementation/6522Storage.hpp
Normal file
63
Components/6522/Implementation/6522Storage.hpp
Normal file
@ -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 <cstdint>
|
||||
|
||||
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 */
|
19
Components/6522/Implementation/IRQDelegatePortHandler.cpp
Normal file
19
Components/6522/Implementation/IRQDelegatePortHandler.cpp
Normal file
@ -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);
|
||||
}
|
@ -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<Storage::Disk::Disk> 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<SerialPortVIA> &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);
|
||||
|
@ -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<SerialPortVIA>, public MOS::MOS6522IRQDelegate {
|
||||
class SerialPortVIA: public MOS::MOS6522::IRQDelegatePortHandler {
|
||||
public:
|
||||
using MOS6522IRQDelegate::set_interrupt_status;
|
||||
SerialPortVIA(MOS::MOS6522::MOS6522<SerialPortVIA> &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<SerialPortVIA> 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<SerialPortVIA>, 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<DriveVIA>, public MOS::MOS6522IRQDelegate {
|
||||
class DriveVIA: public MOS::MOS6522::IRQDelegatePortHandler {
|
||||
public:
|
||||
class Delegate {
|
||||
public:
|
||||
@ -81,20 +80,18 @@ class DriveVIA: public MOS::MOS6522<DriveVIA>, 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<SerialPortVIA> serial_port_VIA_;
|
||||
std::shared_ptr<SerialPortVIA> serial_port_VIA_port_handler_;
|
||||
std::shared_ptr<SerialPort> serial_port_;
|
||||
DriveVIA drive_VIA_;
|
||||
DriveVIA drive_VIA_port_handler_;
|
||||
|
||||
MOS::MOS6522::MOS6522<DriveVIA> drive_VIA_;
|
||||
MOS::MOS6522::MOS6522<SerialPortVIA> serial_port_VIA_;
|
||||
|
||||
int shift_register_, bit_window_offset_;
|
||||
virtual void process_input_bit(int value, unsigned int cycles_since_index_hole);
|
||||
|
@ -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<UserPortVIA>, 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<UserPortVIA>, 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<UserPortVIA>, 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<UserPortVIA>, 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<KeyboardVIA>, 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<KeyboardVIA>, 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<KeyboardVIA>, 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<Vic6560> {
|
||||
|
||||
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<Vic6560> mos6560_;
|
||||
std::shared_ptr<UserPortVIA> user_port_via_;
|
||||
std::shared_ptr<KeyboardVIA> keyboard_via_;
|
||||
std::shared_ptr<UserPortVIA> user_port_via_port_handler_;
|
||||
std::shared_ptr<KeyboardVIA> keyboard_via_port_handler_;
|
||||
std::shared_ptr<SerialPort> serial_port_;
|
||||
std::shared_ptr<::Commodore::Serial::Bus> serial_bus_;
|
||||
|
||||
MOS::MOS6522::MOS6522<UserPortVIA> user_port_via_;
|
||||
MOS::MOS6522::MOS6522<KeyboardVIA> keyboard_via_;
|
||||
|
||||
// Tape
|
||||
std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape_;
|
||||
bool use_fast_tape_hack_;
|
||||
|
@ -24,15 +24,156 @@
|
||||
|
||||
#include "../../ClockReceiver/ForceInline.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
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::MOS6522IRQDelegate::Delegate,
|
||||
public MOS::MOS6522::IRQDelegatePortHandler::Delegate,
|
||||
public Utility::TypeRecipient,
|
||||
public Storage::Tape::BinaryTapePlayer::Delegate,
|
||||
public Microdisc::Delegate,
|
||||
@ -41,18 +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_port_handler_(tape_player_, keyboard_) {
|
||||
set_clock_rate(1000000);
|
||||
via_.set_interrupt_delegate(this);
|
||||
via_.keyboard = keyboard_;
|
||||
clear_all_keys();
|
||||
via_.tape->set_delegate(this);
|
||||
via_port_handler_.set_interrupt_delegate(this);
|
||||
tape_player_.set_delegate(this);
|
||||
Memory::Fuzz(ram_, sizeof(ram_));
|
||||
}
|
||||
|
||||
@ -68,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 {
|
||||
@ -124,7 +256,7 @@ class ConcreteMachine:
|
||||
|
||||
bool insert_media(const StaticAnalyser::Media &media) override final {
|
||||
if(media.tapes.size()) {
|
||||
via_.tape->set_tape(media.tapes.front());
|
||||
tape_player_.set_tape(media.tapes.front());
|
||||
}
|
||||
|
||||
int drive_index = 0;
|
||||
@ -143,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_.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 &&
|
||||
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
|
||||
@ -173,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;
|
||||
}
|
||||
}
|
||||
@ -190,6 +328,8 @@ 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);
|
||||
@ -197,20 +337,23 @@ 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);
|
||||
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_.ay8910.reset();
|
||||
ay8910_.reset();
|
||||
via_port_handler_.set_ay(nullptr);
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::CRT::CRT> get_crt() override final {
|
||||
@ -218,7 +361,7 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
std::shared_ptr<Outputs::Speaker> get_speaker() override final {
|
||||
return via_.ay8910;
|
||||
return ay8910_;
|
||||
}
|
||||
|
||||
void run_for(const Cycles cycles) override final {
|
||||
@ -233,7 +376,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
|
||||
@ -274,99 +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<VideoOutput> video_output_;
|
||||
|
||||
// Keyboard
|
||||
class Keyboard {
|
||||
public:
|
||||
uint8_t row;
|
||||
uint8_t rows[8];
|
||||
};
|
||||
int typer_delay_;
|
||||
std::shared_ptr<GI::AY38910::AY38910> 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<VIA>, public MOS::MOS6522IRQDelegate {
|
||||
public:
|
||||
VIA() :
|
||||
MOS::MOS6522<VIA>(),
|
||||
tape(new TapePlayer) {}
|
||||
|
||||
using MOS6522IRQDelegate::set_interrupt_status;
|
||||
|
||||
void set_control_line_output(Port port, 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) {
|
||||
if(port) {
|
||||
keyboard->row = value;
|
||||
tape->set_motor_control(value & 0x40);
|
||||
} else {
|
||||
ay8910->set_data_input(value);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t get_port_input(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;
|
||||
MOS::MOS6522<VIA>::run_for(cycles);
|
||||
tape->run_for(cycles);
|
||||
}
|
||||
|
||||
void flush() {
|
||||
ay8910->run_for(cycles_since_ay_update_.flush());
|
||||
ay8910->flush();
|
||||
}
|
||||
|
||||
std::shared_ptr<GI::AY38910::AY38910> ay8910;
|
||||
std::unique_ptr<TapePlayer> tape;
|
||||
std::shared_ptr<Keyboard> 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_;
|
||||
std::shared_ptr<Keyboard> keyboard_;
|
||||
VIAPortHandler via_port_handler_;
|
||||
MOS::MOS6522::MOS6522<VIAPortHandler> 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() {
|
||||
|
@ -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) {
|
||||
|
@ -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 = "<group>"; };
|
||||
4B8334831F5DA0360097E338 /* Z80Storage.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Z80Storage.cpp; sourceTree = "<group>"; };
|
||||
4B8334851F5DA3780097E338 /* 6502Storage.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = 6502Storage.cpp; sourceTree = "<group>"; };
|
||||
4B8334871F5DB8410097E338 /* 6522Implementation.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = 6522Implementation.hpp; path = Implementation/6522Implementation.hpp; sourceTree = "<group>"; };
|
||||
4B8334891F5DB94B0097E338 /* IRQDelegatePortHandler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = IRQDelegatePortHandler.cpp; path = Implementation/IRQDelegatePortHandler.cpp; sourceTree = "<group>"; };
|
||||
4B83348B1F5DB99C0097E338 /* 6522Base.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = 6522Base.cpp; path = Implementation/6522Base.cpp; sourceTree = "<group>"; };
|
||||
4B83348E1F5DBA6E0097E338 /* 6522Storage.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = 6522Storage.hpp; path = Implementation/6522Storage.hpp; sourceTree = "<group>"; };
|
||||
4B8378DA1F336631005CA9E4 /* CharacterMapper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CharacterMapper.cpp; path = Electron/CharacterMapper.cpp; sourceTree = "<group>"; };
|
||||
4B8378DB1F336631005CA9E4 /* CharacterMapper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CharacterMapper.hpp; path = Electron/CharacterMapper.hpp; sourceTree = "<group>"; };
|
||||
4B8378DD1F33675F005CA9E4 /* CharacterMapper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CharacterMapper.cpp; path = ZX8081/CharacterMapper.cpp; sourceTree = "<group>"; };
|
||||
@ -1568,6 +1574,17 @@
|
||||
name = Z80;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B8334881F5DB8470097E338 /* Implementation */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B83348B1F5DB99C0097E338 /* 6522Base.cpp */,
|
||||
4B8334891F5DB94B0097E338 /* IRQDelegatePortHandler.cpp */,
|
||||
4B8334871F5DB8410097E338 /* 6522Implementation.hpp */,
|
||||
4B83348E1F5DBA6E0097E338 /* 6522Storage.hpp */,
|
||||
);
|
||||
name = Implementation;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B8805F11DCFC9A2003085B1 /* Parsers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -2159,6 +2176,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BCA98C21D065CA20062F44C /* 6522.hpp */,
|
||||
4B8334881F5DB8470097E338 /* Implementation */,
|
||||
);
|
||||
path = 6522;
|
||||
sourceTree = "<group>";
|
||||
@ -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 */,
|
||||
|
Loading…
Reference in New Issue
Block a user