1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-10-15 20:24:07 +00:00
CLK/Components/RP5C01/RP5C01.cpp

207 lines
4.5 KiB
C++
Raw Normal View History

2023-01-14 19:17:28 +00:00
//
// RP5C01.cpp
// Clock Signal
//
// Created by Thomas Harte on 14/01/2023.
// Copyright © 2023 Thomas Harte. All rights reserved.
//
#include "RP5C01.hpp"
2023-01-17 01:11:42 +00:00
#include <ctime>
2023-01-14 19:17:28 +00:00
using namespace Ricoh::RP5C01;
2023-01-17 01:11:42 +00:00
RP5C01::RP5C01(HalfCycles clock_rate) : clock_rate_(clock_rate) {
// Seed internal clock.
2023-01-17 01:29:32 +00:00
std::time_t now = std::time(NULL);
std::tm *time_date = std::localtime(&now);
2023-01-17 01:11:42 +00:00
seconds_ =
time_date->tm_sec +
time_date->tm_min * 60 +
time_date->tm_hour * 60 * 60;
day_of_the_week_ = time_date->tm_wday;
day_ = time_date->tm_mday;
month_ = time_date->tm_mon;
year_ = time_date->tm_year % 100;
leap_year_ = time_date->tm_year % 4;
}
2023-01-14 19:17:28 +00:00
void RP5C01::run_for(HalfCycles cycles) {
sub_seconds_ += cycles;
// Guess: this happens so rarely (i.e. once a second, ordinarily) that
// it's not worth worrying about the branch prediction consequences.
//
// ... and ditto all the conditionals below, which will be very rarely reached.
if(sub_seconds_ < clock_rate_) {
return;
}
const auto elapsed_seconds = int(sub_seconds_.as_integral() / clock_rate_.as_integral());
sub_seconds_ %= clock_rate_;
// Update time within day.
seconds_ += elapsed_seconds;
constexpr int day_length = 60 * 60 * 24;
if(seconds_ < day_length) {
return;
}
const int elapsed_days = seconds_ / day_length;
seconds_ %= day_length;
// Day of the week doesn't aggregate upwards.
day_of_the_week_ = (day_of_the_week_ + elapsed_days) % 7;
// Assumed for now: day and month run from 0.
// A leap year count of 0 implies a leap year.
// TODO: verify.
day_ += elapsed_days;
while(true) {
int month_length = 1;
switch(month_) {
case 0: month_length = 31; break;
case 1: month_length = 28 + !leap_year_; break;
case 2: month_length = 31; break;
case 3: month_length = 30; break;
case 4: month_length = 31; break;
case 5: month_length = 30; break;
case 6: month_length = 31; break;
case 7: month_length = 31; break;
case 8: month_length = 30; break;
case 9: month_length = 31; break;
case 10: month_length = 30; break;
case 11: month_length = 31; break;
}
if(day_ < month_length) {
return;
}
day_ -= month_length;
++month_;
if(month_ == 12) {
month_ = 0;
2023-01-17 01:11:42 +00:00
year_ = (year_ + 1) % 100;
2023-01-14 19:17:28 +00:00
leap_year_ = (leap_year_ + 1) & 3;
}
}
}
2023-01-17 01:26:27 +00:00
namespace {
constexpr int Reg(int mode, int address) {
return address | mode << 4;
}
}
2023-01-14 19:17:28 +00:00
/// Performs a write of @c value to @c address.
void RP5C01::write(int address, uint8_t value) {
2023-01-14 19:52:07 +00:00
address &= 0xf;
2023-01-17 01:26:27 +00:00
value &= 0xf;
2023-01-14 19:52:07 +00:00
2023-01-17 01:26:27 +00:00
// Handle potential RAM accesses.
if(address < 0xd && mode_ >= 2) {
address += mode_ == 3 ? 13 : 0;
ram_[size_t(address)] = value & 0xf;
2023-01-14 19:17:28 +00:00
return;
}
2023-01-14 19:52:07 +00:00
2023-01-17 01:26:27 +00:00
switch(Reg(mode_, address)) {
default: break;
// Seconds.
case Reg(0, 0x00):
seconds_ = seconds_ - (seconds_ % 10) + (value % 10);
break;
case Reg(0, 0x01):
seconds_ = (seconds_ % 10) + ((value % 6) * 10);
break;
// TODO: minutes.
case Reg(0, 0x02):
case Reg(0, 0x03): break;
// TODO: hours.
case Reg(0, 0x04):
case Reg(0, 0x05): break;
// TODO: day-of-the-week counter.
case Reg(0, 0x06): break;
// TODO: day counter.
case Reg(0, 0x07):
case Reg(0, 0x08): break;
// TODO: month counter.
case Reg(0, 0x09):
case Reg(0, 0x0a): break;
// TODO: year counter.
case Reg(0, 0x0b):
case Reg(0, 0x0c): break;
// TODO: alarm minutes.
case Reg(1, 0x02):
case Reg(1, 0x03): break;
// TODO: alarm hours.
case Reg(1, 0x04):
case Reg(1, 0x05): break;
// TODO: alarm day-of-the-week.
case Reg(1, 0x06): break;
// TODO: alarm day.
case Reg(1, 0x07):
case Reg(1, 0x08): break;
// TODO: 12/24 hour select.
case Reg(1, 0x0a): break;
// TODO: leap-year counter.
case Reg(1, 0x0b): break;
//
// Registers DF don't depend on the mode.
//
case Reg(0, 0xd): case Reg(1, 0xd): case Reg(2, 0xd): case Reg(3, 0xd):
timer_enabled_ = value & 0x8;
alarm_enabled_ = value & 0x4;
mode_ = value & 0x3;
break;
case Reg(0, 0xe): case Reg(1, 0xe): case Reg(2, 0xe): case Reg(3, 0xe):
// Test register; unclear what is supposed to happen.
break;
case Reg(0, 0xf): case Reg(1, 0xf): case Reg(2, 0xf): case Reg(3, 0xf):
one_hz_on_ = !(value & 0x8);
sixteen_hz_on_ = !(value & 0x4);
// TODO: b0 = alarm reset; b1 = timer reset.
break;
2023-01-14 19:58:12 +00:00
}
2023-01-14 19:52:07 +00:00
}
uint8_t RP5C01::read(int address) {
address &= 0xf;
2023-01-14 19:58:12 +00:00
if(address < 0xd) {
switch(mode_) {
case 3:
address += 13;
[[fallthrough]];
case 2:
return 0xf0 | ram_[size_t(address)];
}
}
2023-01-14 19:52:07 +00:00
// TODO.
printf("RP-5C01 read from %d in mode %d\n", address & 0xf, mode_);
return 0xff;
2023-01-14 19:17:28 +00:00
}