// // 6526Storage.hpp // Clock Signal // // Created by Thomas Harte on 18/07/2021. // Copyright © 2021 Thomas Harte. All rights reserved. // #pragma once #include #include "../../../ClockReceiver/ClockReceiver.hpp" #include "../../../Outputs/Log.hpp" namespace MOS::MOS6526 { class TODBase { public: template void set_control(const uint8_t value) { if constexpr (is_timer2) { write_alarm = value & 0x80; } else { is_50Hz = value & 0x80; } } protected: bool write_alarm = false, is_50Hz = false; }; template class TODStorage {}; template <> class TODStorage: public TODBase { private: bool increment_ = true, latched_ = false; int divider_ = 0; std::array value_; std::array latch_; std::array alarm_; static constexpr uint8_t masks[4] = {0xf, 0x3f, 0x3f, 0x1f}; void bcd_increment(uint8_t &value) { ++value; if((value&0x0f) > 0x09) value += 0x06; } public: template void write(const uint8_t v) { if(write_alarm) { alarm_[byte] = v & masks[byte]; } else { value_[byte] = v & masks[byte]; if constexpr (byte == 0) { increment_ = true; } if constexpr (byte == 3) { increment_ = false; } } } template uint8_t read() { if(latched_) { const uint8_t result = latch_[byte]; if constexpr (byte == 0) { latched_ = false; } return result; } if constexpr (byte == 3) { latched_ = true; latch_ = value_; } return value_[byte]; } bool advance(int count) { if(!increment_) { return false; } while(count--) { // Increment the pre-10ths divider. ++divider_; if(divider_ < 5) continue; if(divider_ < 6 && !is_50Hz) continue; divider_ = 0; // Increments 10ths of a second. One BCD digit. ++value_[0]; if(value_[0] < 10) { continue; } // Increment seconds. Actual BCD needed from here onwards. bcd_increment(value_[1]); if(value_[1] != 60) { continue; } value_[1] = 0; // Increment minutes. bcd_increment(value_[2]); if(value_[2] != 60) { continue; } value_[2] = 0; // TODO: increment hours, keeping AM/PM separate? } return false; // TODO: test against alarm. } }; template <> class TODStorage: public TODBase { private: uint32_t increment_mask_ = uint32_t(~0); uint32_t latch_ = 0; uint32_t value_ = 0; uint32_t alarm_ = 0xff'ffff; public: template void write(uint8_t v) { if constexpr (byte == 3) { return; } constexpr int shift = byte << 3; // Write to either the alarm or the current value as directed; // writing to any part of the current value other than the LSB // pauses incrementing until the LSB is written. const uint32_t mask = uint32_t(~(0xff << shift)); if(write_alarm) { alarm_ = (alarm_ & mask) | uint32_t(v << shift); } else { value_ = (value_ & mask) | uint32_t(v << shift); increment_mask_ = (byte == 0) ? uint32_t(~0) : 0; } } template uint8_t read() { if constexpr (byte == 3) { return 0xff; // Assumed. Just a guess. } constexpr int shift = byte << 3; if constexpr (byte == 2) { latch_ = value_ | 0xff00'0000; } const uint32_t source = latch_ ? latch_ : value_; const uint8_t result = uint8_t((source >> shift) & 0xff); if constexpr (byte == 0) { latch_ = 0; } return result; } bool advance(int count) { // The 8250 uses a simple binary counter to replace the // 6526's time-of-day clock. So this is easy. const uint32_t distance_to_alarm = (alarm_ - value_) & 0xff'ffff; const auto increment = uint32_t(count) & increment_mask_; value_ = (value_ + increment) & 0xff'ffff; return distance_to_alarm <= increment; } }; struct MOS6526Storage { bool cnt_state_ = false; // Inactive by default. bool cnt_edge_ = false; bool flag_state_ = false; HalfCycles half_divider_; uint8_t output_[2] = {0, 0}; uint8_t data_direction_[2] = {0, 0}; uint8_t interrupt_control_ = 0; uint8_t interrupt_state_ = 0; uint8_t shift_data_ = 0; uint8_t shift_register_ = 0; int shift_bits_ = 0; bool shifter_is_output_ = false; struct Counter { uint16_t reload = 0; uint16_t value = 0; uint8_t control = 0; template void set_reload(uint8_t v) { reload = (reload & (0xff00 >> shift)) | uint16_t(v << shift); if constexpr (shift == 8) { // This seems to be a special 8250 feature per the Amiga // Hardware Reference Manual; cf. Appendix F. if(is_8250) { control |= 1; pending |= ReloadInOne; } else { if(!(control&1)) { pending |= ReloadInOne; } } } // If this write has hit during a reload cycle, reload. if(pending & ReloadNow) { value = reload; } } template void set_control(uint8_t v) { control = v; if(v&2) { Log::Logger log; log.error().append("UNIMPLEMENTED: PB strobe"); } } template bool advance(bool chained_input, bool cnt_state, bool cnt_edge) { // TODO: remove most of the conditionals here in favour of bit shuffling. pending = (pending & PendingClearMask) << 1; // // Apply feeder states inputs: anything that // will take effect in the future. // // Schedule a force reload if requested. if(control & 0x10) { pending |= ReloadInOne; control &= ~0x10; } // Keep a history of the one-shot bit. if(control & 0x08) { pending |= OneShotInOne; } // Determine whether an input clock is applicable. if constexpr(is_counter_2) { switch(control&0x60) { case 0x00: // Count Phi2 pulses. pending |= TestInputNow; break; case 0x20: // Count negative CNTs, with an extra cycle of delay. pending |= cnt_edge ? TestInputInOne : 0; break; case 0x40: // Count timer A reloads. pending |= chained_input ? TestInputNow : 0; break; case 0x60: // Count timer A transitions when CNT is low. pending |= chained_input && cnt_state ? TestInputNow : 0; break; } } else { if(!(control&0x20)) { pending |= TestInputNow; } else if (cnt_edge) { pending |= TestInputInOne; } } if(pending&TestInputNow && control&1) { pending |= ApplyClockInTwo; } // // Perform a timer tick and decide whether a reload is prompted. // if(pending & ApplyClockNow) { --value; } const bool should_reload = !value && (pending & ApplyClockInOne); // Schedule a reload if so ordered. if(should_reload) { pending |= ReloadNow; // Combine this decision with a deferred // input from the force-reoad test above. // If this was one-shot, stop. if(pending&(OneShotInOne | OneShotNow)) { control &= ~1; pending &= ~(ApplyClockInOne|ApplyClockInTwo); // Cancel scheduled ticks. } } // Reload if scheduled. if(pending & ReloadNow) { value = reload; pending &= ~ApplyClockInOne; // Skip next decrement. } return should_reload; } private: int pending = 0; static constexpr int ReloadInOne = 1 << 0; static constexpr int ReloadNow = 1 << 1; static constexpr int OneShotInOne = 1 << 2; static constexpr int OneShotNow = 1 << 3; static constexpr int ApplyClockInTwo = 1 << 4; static constexpr int ApplyClockInOne = 1 << 5; static constexpr int ApplyClockNow = 1 << 6; static constexpr int TestInputInOne = 1 << 7; static constexpr int TestInputNow = 1 << 8; static constexpr int PendingClearMask = ~(ReloadNow | OneShotNow | ApplyClockNow); } counter_[2]; static constexpr int InterruptInOne = 1 << 0; static constexpr int InterruptNow = 1 << 1; static constexpr int PendingClearMask = ~(InterruptNow); int pending_ = 0; }; }