mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-26 09:29:45 +00:00
243 lines
6.6 KiB
C++
243 lines
6.6 KiB
C++
//
|
|
// 6526Implementation.hpp
|
|
// Clock Signal
|
|
//
|
|
// Created by Thomas Harte on 18/07/2021.
|
|
// Copyright © 2021 Thomas Harte. All rights reserved.
|
|
//
|
|
|
|
#ifndef _526Implementation_h
|
|
#define _526Implementation_h
|
|
|
|
#include <cassert>
|
|
#include <cstdio>
|
|
|
|
namespace MOS::MOS6526 {
|
|
|
|
enum Interrupts: uint8_t {
|
|
TimerA = 1 << 0,
|
|
TimerB = 1 << 1,
|
|
Alarm = 1 << 2,
|
|
SerialPort = 1 << 3,
|
|
Flag = 1 << 4,
|
|
};
|
|
|
|
template <typename BusHandlerT, Personality personality>
|
|
template <int port> void MOS6526<BusHandlerT, personality>::set_port_output() {
|
|
const uint8_t output = output_[port] | (~data_direction_[port]);
|
|
port_handler_.set_port_output(Port(port), output);
|
|
}
|
|
|
|
template <typename BusHandlerT, Personality personality>
|
|
template <int port> uint8_t MOS6526<BusHandlerT, personality>::get_port_input() {
|
|
// Avoid bothering the port handler if there's no input active.
|
|
const uint8_t input_mask = ~data_direction_[port];
|
|
const uint8_t input = input_mask ? port_handler_.get_port_input(Port(port)) : 0x00;
|
|
return (input & input_mask) | (output_[port] & data_direction_[port]);
|
|
}
|
|
|
|
template <typename BusHandlerT, Personality personality>
|
|
void MOS6526<BusHandlerT, personality>::posit_interrupt(uint8_t mask) {
|
|
if(!mask) {
|
|
return;
|
|
}
|
|
interrupt_state_ |= mask;
|
|
update_interrupts();
|
|
}
|
|
|
|
template <typename BusHandlerT, Personality personality>
|
|
void MOS6526<BusHandlerT, personality>::update_interrupts() {
|
|
if(interrupt_state_ & interrupt_control_) {
|
|
pending_ |= InterruptInOne;
|
|
}
|
|
}
|
|
|
|
template <typename BusHandlerT, Personality personality>
|
|
bool MOS6526<BusHandlerT, personality>::get_interrupt_line() {
|
|
return interrupt_state_ & 0x80;
|
|
}
|
|
|
|
template <typename BusHandlerT, Personality personality>
|
|
void MOS6526<BusHandlerT, personality>::set_cnt_input(bool active) {
|
|
cnt_edge_ = active && !cnt_state_;
|
|
cnt_state_ = active;
|
|
}
|
|
|
|
template <typename BusHandlerT, Personality personality>
|
|
void MOS6526<BusHandlerT, personality>::set_flag_input(bool low) {
|
|
if(low && !flag_state_) {
|
|
posit_interrupt(Interrupts::Flag);
|
|
}
|
|
flag_state_ = low;
|
|
}
|
|
|
|
template <typename BusHandlerT, Personality personality>
|
|
void MOS6526<BusHandlerT, personality>::write(int address, uint8_t value) {
|
|
address &= 0xf;
|
|
switch(address) {
|
|
// Port output.
|
|
case 0:
|
|
output_[0] = value;
|
|
set_port_output<0>();
|
|
break;
|
|
case 1:
|
|
output_[1] = value;
|
|
set_port_output<1>();
|
|
break;
|
|
|
|
// Port direction.
|
|
case 2:
|
|
data_direction_[0] = value;
|
|
set_port_output<0>();
|
|
break;
|
|
case 3:
|
|
data_direction_[1] = value;
|
|
set_port_output<1>();
|
|
break;
|
|
|
|
// Counters; writes set the reload values.
|
|
case 4: counter_[0].template set_reload<0, personality == Personality::P8250>(value); break;
|
|
case 5: counter_[0].template set_reload<8, personality == Personality::P8250>(value); break;
|
|
case 6: counter_[1].template set_reload<0, personality == Personality::P8250>(value); break;
|
|
case 7: counter_[1].template set_reload<8, personality == Personality::P8250>(value); break;
|
|
|
|
// Time-of-day clock.
|
|
case 8: tod_.template write<0>(value); break;
|
|
case 9: tod_.template write<1>(value); break;
|
|
case 10: tod_.template write<2>(value); break;
|
|
case 11: tod_.template write<3>(value); break;
|
|
|
|
// Interrupt control.
|
|
case 13: {
|
|
if(value & 0x80) {
|
|
interrupt_control_ |= value & 0x7f;
|
|
} else {
|
|
interrupt_control_ &= ~(value & 0x7f);
|
|
}
|
|
update_interrupts();
|
|
} break;
|
|
|
|
// Control. Posted to both the counters and the clock as it affects both.
|
|
case 14:
|
|
counter_[0].template set_control<false>(value);
|
|
tod_.template set_control<false>(value);
|
|
if(shifter_is_output_ != bool(value & 0x40)) {
|
|
shifter_is_output_ = value & 0x40;
|
|
shift_bits_ = 0;
|
|
}
|
|
break;
|
|
case 15:
|
|
counter_[1].template set_control<true>(value);
|
|
tod_.template set_control<true>(value);
|
|
break;
|
|
|
|
// Shift control.
|
|
case 12:
|
|
printf("TODO: write to shift register\n");
|
|
break;
|
|
|
|
default:
|
|
printf("Unhandled 6526 write: %02x to %d\n", value, address);
|
|
assert(false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
template <typename BusHandlerT, Personality personality>
|
|
uint8_t MOS6526<BusHandlerT, personality>::read(int address) {
|
|
address &= 0xf;
|
|
switch(address) {
|
|
case 0: return get_port_input<0>();
|
|
case 1: return get_port_input<1>();
|
|
|
|
case 2: case 3:
|
|
return data_direction_[address - 2];
|
|
|
|
// Counters; reads obtain the current values.
|
|
case 4: return uint8_t(counter_[0].value >> 0);
|
|
case 5: return uint8_t(counter_[0].value >> 8);
|
|
case 6: return uint8_t(counter_[1].value >> 0);
|
|
case 7: return uint8_t(counter_[1].value >> 8);
|
|
|
|
// Interrupt state.
|
|
case 13: {
|
|
const uint8_t result = interrupt_state_;
|
|
interrupt_state_ = 0;
|
|
pending_ &= ~(InterruptNow | InterruptInOne);
|
|
update_interrupts();
|
|
return result;
|
|
} break;
|
|
|
|
case 14: case 15:
|
|
return counter_[address - 14].control;
|
|
|
|
// Time-of-day clock.
|
|
case 8: return tod_.template read<0>();
|
|
case 9: return tod_.template read<1>();
|
|
case 10: return tod_.template read<2>();
|
|
case 11: return tod_.template read<3>();
|
|
|
|
// Shift register.
|
|
case 12: return shift_data_;
|
|
|
|
default:
|
|
printf("Unhandled 6526 read from %d\n", address);
|
|
assert(false);
|
|
break;
|
|
}
|
|
return 0xff;
|
|
}
|
|
|
|
template <typename BusHandlerT, Personality personality>
|
|
void MOS6526<BusHandlerT, personality>::run_for(const HalfCycles half_cycles) {
|
|
half_divider_ += half_cycles;
|
|
int sub = half_divider_.divide_cycles().template as<int>();
|
|
|
|
while(sub--) {
|
|
pending_ <<= 1;
|
|
if(pending_ & InterruptNow) {
|
|
interrupt_state_ |= 0x80;
|
|
}
|
|
pending_ &= PendingClearMask;
|
|
|
|
// TODO: use CNT potentially to clock timer A, elimiante conditional above.
|
|
const bool timer1_did_reload = counter_[0].template advance<false>(false, cnt_state_, cnt_edge_);
|
|
|
|
const bool timer1_carry = timer1_did_reload && (counter_[1].control & 0x60) == 0x40;
|
|
const bool timer2_did_reload = counter_[1].template advance<true>(timer1_carry, cnt_state_, cnt_edge_);
|
|
posit_interrupt((timer1_did_reload ? Interrupts::TimerA : 0x00) | (timer2_did_reload ? Interrupts::TimerB : 0x00));
|
|
|
|
cnt_edge_ = false;
|
|
}
|
|
}
|
|
|
|
template <typename BusHandlerT, Personality personality>
|
|
void MOS6526<BusHandlerT, personality>::advance_tod(int count) {
|
|
if(!count) return;
|
|
if(tod_.advance(count)) {
|
|
posit_interrupt(Interrupts::Alarm);
|
|
}
|
|
}
|
|
|
|
template <typename BusHandlerT, Personality personality>
|
|
bool MOS6526<BusHandlerT, personality>::serial_line_did_produce_bit(Serial::Line<true> *, int bit) {
|
|
// TODO: post CNT change; might affect timer.
|
|
|
|
if(!shifter_is_output_) {
|
|
shift_register_ = uint8_t((shift_register_ << 1) | bit);
|
|
++shift_bits_;
|
|
|
|
if(shift_bits_ == 8) {
|
|
shift_bits_ = 0;
|
|
shift_data_ = shift_register_;
|
|
posit_interrupt(Interrupts::SerialPort);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
}
|
|
|
|
#endif /* _526Implementation_h */
|