1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-11-25 16:31:42 +00:00
CLK/Components/AppleClock/AppleClock.hpp
2024-02-16 21:50:15 -05:00

293 lines
6.8 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// RealTimeClock.hpp
// Clock Signal
//
// Created by Thomas Harte on 07/05/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#pragma once
#include <array>
namespace Apple::Clock {
/*!
Models Apple's real-time clocks, as contained in the Macintosh and IIgs.
Since tracking of time is pushed to this class, it is assumed
that whomever is translating real time into emulated time
will also signal interrupts — this is just the storage and time counting.
*/
class ClockStorage {
public:
/*!
Advances the clock by 1 second.
The caller should also signal an interrupt if applicable.
*/
void update() {
for(size_t c = 0; c < 4; ++c) {
++seconds_[c];
if(seconds_[c]) break;
}
}
/*!
Sets the current [P/B]RAM contents.
*/
template <typename CollectionT> void set_data(const CollectionT &collection) {
set_data(collection.begin(), collection.end());
}
template <typename IteratorT> void set_data(IteratorT begin, const IteratorT end) {
size_t c = 0;
while(begin != end && c < 256) {
data_[c] = *begin;
++begin;
++c;
}
}
protected:
static constexpr uint16_t NoResult = 0x100;
static constexpr uint16_t DidComplete = 0x101;
uint16_t perform(uint8_t command) {
/*
Documented commands:
z0000001 Seconds register 0 (lowest order byte)
z0000101 Seconds register 1
z0001001 Seconds register 2
z0001101 Seconds register 3
00110001 Test register (write only)
00110101 Write-protect register (write only)
z010aa01 RAM addresses 0x10 - 0x13
z1aaaa01 RAM addresses 0x00 0x0f
z0111abc, followed by 0defgh00
RAM address abcdefgh
z = 1 => a read; z = 0 => a write.
The top bit of the write-protect register enables (0) or disables (1)
writes to other locations.
All the documentation says about the test register is to set the top
two bits to 0 for normal operation. Abnormal operation is undefined.
*/
switch(phase_) {
case Phase::Command:
// Decode an address.
switch(command & 0x70) {
default:
if(command & 0x40) {
// RAM addresses 0x00 0x0f.
address_ = (command >> 2) & 0xf;
} else return DidComplete; // Unrecognised.
break;
case 0x00:
// A time access.
address_ = SecondsBuffer + ((command >> 2)&3);
break;
case 0x30:
// Either a register access or an extended instruction.
if(command & 0x08) {
address_ = unsigned((command & 0x7) << 5);
phase_ = (command & 0x80) ? Phase::SecondAddressByteRead : Phase::SecondAddressByteWrite;
return NoResult;
} else {
address_ = (command & 4) ? RegisterWriteProtect : RegisterTest;
}
break;
case 0x20:
// RAM addresses 0x10 0x13.
address_ = 0x10 + ((command >> 2) & 0x3);
break;
}
// If this is a read, return a result; otherwise prepare to write.
if(command & 0x80) {
// The two registers are write-only.
if(address_ == RegisterTest || address_ == RegisterWriteProtect) {
return DidComplete;
}
return (address_ >= SecondsBuffer) ? seconds_[address_ & 0xff] : data_[address_];
}
phase_ = Phase::WriteData;
return NoResult;
case Phase::SecondAddressByteRead:
case Phase::SecondAddressByteWrite:
if(command & 0x83) {
return DidComplete;
}
address_ |= command >> 2;
if(phase_ == Phase::SecondAddressByteRead) {
phase_ = Phase::Command;
return data_[address_]; // Only RAM accesses can get this far.
} else {
phase_ = Phase::WriteData;
}
return NoResult;
case Phase::WriteData:
// First test: is this to the write-protect register?
if(address_ == RegisterWriteProtect) {
write_protect_ = command;
return DidComplete;
}
if(address_ == RegisterTest) {
// No documentation here.
return DidComplete;
}
// No other writing is permitted if the write protect
// register won't allow it.
if(!(write_protect_ & 0x80)) {
if(address_ >= SecondsBuffer) {
seconds_[address_ & 0xff] = command;
} else {
data_[address_] = command;
}
}
phase_ = Phase::Command;
return DidComplete;
}
return NoResult;
}
private:
std::array<uint8_t, 256> data_{0xff};
std::array<uint8_t, 4> seconds_{};
uint8_t write_protect_ = 0;
unsigned int address_ = 0;
static constexpr int SecondsBuffer = 0x100;
static constexpr int RegisterTest = 0x200;
static constexpr int RegisterWriteProtect = 0x201;
enum class Phase {
Command,
SecondAddressByteRead,
SecondAddressByteWrite,
WriteData
};
Phase phase_ = Phase::Command;
};
/*!
Provides the serial interface implemented by the Macintosh.
*/
class SerialClock: public ClockStorage {
public:
/*!
Sets the current clock and data inputs to the clock.
*/
void set_input(bool clock, bool data) {
// The data line is valid when the clock transitions to level 0.
if(clock && !previous_clock_) {
// Shift into the command_ register, no matter what.
command_ = uint16_t((command_ << 1) | (data ? 1 : 0));
result_ <<= 1;
// Increment phase.
++phase_;
// If a whole byte has been collected, push it onwards.
if(!(phase_&7)) {
// Begin pessimistically.
const auto effect = perform(uint8_t(command_));
switch(effect) {
case ClockStorage::NoResult:
break;
default:
result_ = uint8_t(effect);
break;
case ClockStorage::DidComplete:
abort();
break;
}
}
}
previous_clock_ = clock;
}
/*!
Reads the current data output level from the clock.
*/
bool get_data() {
return !!(result_ & 0x80);
}
/*!
Announces that a serial command has been aborted.
*/
void abort() {
result_ = 0;
phase_ = 0;
command_ = 0;
}
private:
int phase_ = 0;
uint16_t command_;
uint8_t result_ = 0;
bool previous_clock_ = false;
};
/*!
Provides the parallel interface implemented by the IIgs.
*/
class ParallelClock: public ClockStorage {
public:
void set_control(uint8_t control) {
if(!(control&0x80)) return;
if(control & 0x40) {
// Read from the RTC.
// A no-op for now.
} else {
// Write to the RTC. Which in this implementation also sets up a future read.
const auto result = perform(data_);
if(result < 0x100) {
data_ = uint8_t(result);
}
}
// MAGIC! The transaction took 0 seconds.
// TODO: no magic.
control_ = control & 0x7f;
// Bit 5 is also meant to be 1 or 0 to indicate the final byte.
}
uint8_t get_control() {
return control_;
}
void set_data(uint8_t data) {
data_ = data;
}
uint8_t get_data() {
return data_;
}
private:
uint8_t data_;
uint8_t control_;
};
}