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 23:53:26 +00:00
|
|
|
|
#include "../../Numeric/NumericCoder.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;
|
2023-01-24 03:52:26 +00:00
|
|
|
|
year_ = (time_date->tm_year + 20) % 100; // This is probably MSX specific; rethink if/when other machines use this chip.
|
2023-01-17 01:11:42 +00:00
|
|
|
|
leap_year_ = time_date->tm_year % 4;
|
|
|
|
|
}
|
2023-01-14 19:17:28 +00:00
|
|
|
|
|
2024-11-30 22:21:00 +00:00
|
|
|
|
void RP5C01::run_for(const HalfCycles cycles) {
|
2023-01-14 19:17:28 +00:00
|
|
|
|
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_) {
|
2023-01-17 03:31:03 +00:00
|
|
|
|
default:
|
2023-01-14 19:17:28 +00:00
|
|
|
|
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 {
|
|
|
|
|
|
2024-11-30 22:21:00 +00:00
|
|
|
|
constexpr int Reg(const int mode, const int address) {
|
2023-01-17 01:26:27 +00:00
|
|
|
|
return address | mode << 4;
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-18 02:12:00 +00:00
|
|
|
|
constexpr int PM = 1 << 4;
|
|
|
|
|
|
2024-11-30 22:21:00 +00:00
|
|
|
|
constexpr int twenty_four_to_twelve(const int hours) {
|
2023-01-18 02:12:00 +00:00
|
|
|
|
switch(hours) {
|
|
|
|
|
default: return (hours % 12) + (hours > 12 ? PM : 0);
|
|
|
|
|
case 0: return 12;
|
|
|
|
|
case 12: return 12 | PM;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
constexpr int twelve_to_twenty_four(int hours) {
|
|
|
|
|
hours = (hours & 0xf) + (hours & PM ? 12 : 0);
|
|
|
|
|
switch(hours) {
|
|
|
|
|
default: break;
|
|
|
|
|
case 24: return 12;
|
|
|
|
|
case 12: return 0;
|
|
|
|
|
}
|
|
|
|
|
return hours;
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-18 02:18:56 +00:00
|
|
|
|
using SecondEncoder = Numeric::NumericCoder<
|
|
|
|
|
10, 6, // Seconds.
|
|
|
|
|
10, 6, // Minutes.
|
|
|
|
|
24 // Hours
|
|
|
|
|
>;
|
|
|
|
|
using TwoDigitEncoder = Numeric::NumericCoder<10, 10>;
|
|
|
|
|
|
2023-01-17 01:26:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
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.
|
2023-01-17 23:53:26 +00:00
|
|
|
|
case Reg(0, 0x00): SecondEncoder::encode<0>(seconds_, value); break;
|
|
|
|
|
case Reg(0, 0x01): SecondEncoder::encode<1>(seconds_, value); break;
|
|
|
|
|
|
|
|
|
|
// Minutes.
|
|
|
|
|
case Reg(0, 0x02): SecondEncoder::encode<2>(seconds_, value); break;
|
|
|
|
|
case Reg(0, 0x03): SecondEncoder::encode<3>(seconds_, value); break;
|
|
|
|
|
|
|
|
|
|
// Hours.
|
2023-01-18 02:12:00 +00:00
|
|
|
|
case Reg(0, 0x04):
|
|
|
|
|
case Reg(0, 0x05): {
|
|
|
|
|
int hours = SecondEncoder::decode<4>(seconds_);
|
|
|
|
|
if(!twentyfour_hour_clock_) {
|
|
|
|
|
hours = twenty_four_to_twelve(hours);
|
|
|
|
|
}
|
|
|
|
|
if(address == 0x4) {
|
2023-01-18 02:18:56 +00:00
|
|
|
|
TwoDigitEncoder::encode<0>(hours, value);
|
2023-01-18 02:12:00 +00:00
|
|
|
|
} else {
|
2023-01-18 02:18:56 +00:00
|
|
|
|
TwoDigitEncoder::encode<1>(hours, value & 3);
|
2023-01-18 02:12:00 +00:00
|
|
|
|
}
|
|
|
|
|
if(!twentyfour_hour_clock_) {
|
|
|
|
|
hours = twelve_to_twenty_four(hours);
|
|
|
|
|
}
|
|
|
|
|
SecondEncoder::encode<4>(seconds_, hours);
|
|
|
|
|
} break;
|
2023-01-17 01:26:27 +00:00
|
|
|
|
|
2023-01-17 03:31:03 +00:00
|
|
|
|
// Day of the week.
|
2023-01-24 03:52:26 +00:00
|
|
|
|
case Reg(0, 0x06): day_of_the_week_ = value % 7; break;
|
2023-01-17 01:26:27 +00:00
|
|
|
|
|
2023-01-17 03:31:03 +00:00
|
|
|
|
// Day.
|
2023-01-24 03:52:26 +00:00
|
|
|
|
case Reg(0, 0x07): TwoDigitEncoder::encode<0>(day_, value); break;
|
|
|
|
|
case Reg(0, 0x08): TwoDigitEncoder::encode<1>(day_, value & 3); break;
|
2023-01-17 01:26:27 +00:00
|
|
|
|
|
2023-01-17 03:31:03 +00:00
|
|
|
|
// Month.
|
2023-01-24 03:52:26 +00:00
|
|
|
|
case Reg(0, 0x09): TwoDigitEncoder::encode<0>(month_, (value - 1)); break;
|
|
|
|
|
case Reg(0, 0x0a): TwoDigitEncoder::encode<1>(month_, (value - 1) & 1); break;
|
2023-01-17 01:26:27 +00:00
|
|
|
|
|
2023-01-17 03:31:03 +00:00
|
|
|
|
// Year.
|
2023-01-24 03:52:26 +00:00
|
|
|
|
case Reg(0, 0x0b): TwoDigitEncoder::encode<0>(year_, value); break;
|
|
|
|
|
case Reg(0, 0x0c): TwoDigitEncoder::encode<1>(year_, value); break;
|
2023-01-17 01:26:27 +00:00
|
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
|
2023-01-17 03:31:03 +00:00
|
|
|
|
// 24/12-hour clock.
|
2023-01-17 03:25:20 +00:00
|
|
|
|
case Reg(1, 0x0a):
|
|
|
|
|
twentyfour_hour_clock_ = value & 1;
|
|
|
|
|
break;
|
2023-01-17 01:26:27 +00:00
|
|
|
|
|
2023-01-17 03:31:03 +00:00
|
|
|
|
// Lead-year counter.
|
|
|
|
|
case Reg(1, 0x0b):
|
|
|
|
|
leap_year_ = value & 3;
|
|
|
|
|
break;
|
2023-01-17 01:26:27 +00:00
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// Registers D–F 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-17 03:25:20 +00:00
|
|
|
|
if(address < 0xd && mode_ >= 2) {
|
|
|
|
|
address += mode_ == 3 ? 13 : 0;
|
|
|
|
|
return 0xf0 | ram_[size_t(address)];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int value = 0xf;
|
|
|
|
|
switch(Reg(mode_, address)) {
|
|
|
|
|
// Second.
|
2023-01-18 02:18:56 +00:00
|
|
|
|
case Reg(0, 0x00): value = SecondEncoder::decode<0>(seconds_); break;
|
|
|
|
|
case Reg(0, 0x01): value = SecondEncoder::decode<1>(seconds_); break;
|
2023-01-17 03:25:20 +00:00
|
|
|
|
|
|
|
|
|
// Minute.
|
2023-01-18 02:18:56 +00:00
|
|
|
|
case Reg(0, 0x02): value = SecondEncoder::decode<2>(seconds_); break;
|
|
|
|
|
case Reg(0, 0x03): value = SecondEncoder::decode<3>(seconds_); break;
|
2023-01-17 03:25:20 +00:00
|
|
|
|
|
|
|
|
|
// Hour.
|
|
|
|
|
case Reg(0, 0x04):
|
2023-01-18 02:18:56 +00:00
|
|
|
|
case Reg(0, 0x05): {
|
|
|
|
|
int hours = SecondEncoder::decode<4>(seconds_);
|
|
|
|
|
if(!twentyfour_hour_clock_) {
|
|
|
|
|
hours = twenty_four_to_twelve(hours);
|
2023-01-17 03:25:20 +00:00
|
|
|
|
}
|
2023-01-18 02:18:56 +00:00
|
|
|
|
if(address == 0x4) {
|
|
|
|
|
value = TwoDigitEncoder::decode<0>(hours);
|
2023-01-17 03:25:20 +00:00
|
|
|
|
} else {
|
2023-01-18 02:18:56 +00:00
|
|
|
|
value = TwoDigitEncoder::decode<1>(hours);
|
2023-01-17 03:25:20 +00:00
|
|
|
|
}
|
2023-01-18 02:18:56 +00:00
|
|
|
|
} break;
|
2023-01-17 03:25:20 +00:00
|
|
|
|
|
|
|
|
|
// Day-of-the-week.
|
|
|
|
|
case Reg(0, 0x06): value = day_of_the_week_; break;
|
|
|
|
|
|
|
|
|
|
// Day.
|
2023-01-18 02:18:56 +00:00
|
|
|
|
case Reg(0, 0x07): value = TwoDigitEncoder::decode<0>(day_); break;
|
|
|
|
|
case Reg(0, 0x08): value = TwoDigitEncoder::decode<1>(day_); break;
|
2023-01-17 03:25:20 +00:00
|
|
|
|
|
|
|
|
|
// Month.
|
2023-01-24 03:52:26 +00:00
|
|
|
|
case Reg(0, 0x09): value = TwoDigitEncoder::decode<0>(month_ + 1); break;
|
|
|
|
|
case Reg(0, 0x0a): value = TwoDigitEncoder::decode<1>(month_ + 1); break;
|
2023-01-17 03:25:20 +00:00
|
|
|
|
|
2023-01-24 03:52:26 +00:00
|
|
|
|
// Year.
|
2023-01-18 02:18:56 +00:00
|
|
|
|
case Reg(0, 0x0b): value = TwoDigitEncoder::decode<0>(year_); break;
|
|
|
|
|
case Reg(0, 0x0c): value = TwoDigitEncoder::decode<1>(year_); break;
|
2023-01-17 03:25:20 +00:00
|
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
|
|
|
|
|
// 12/24-hour clock.
|
|
|
|
|
case Reg(1, 0x0a): value = twentyfour_hour_clock_; break;
|
|
|
|
|
|
|
|
|
|
// Leap year.
|
|
|
|
|
case Reg(1, 0x0b): value = leap_year_; break;
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// Registers D–F don't depend on the mode.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
case Reg(0, 0xd): case Reg(1, 0xd): case Reg(2, 0xd): case Reg(3, 0xd):
|
|
|
|
|
value =
|
|
|
|
|
(timer_enabled_ ? 0x8 : 0x0) |
|
|
|
|
|
(alarm_enabled_ ? 0x4 : 0x0) |
|
|
|
|
|
mode_;
|
|
|
|
|
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):
|
|
|
|
|
value =
|
|
|
|
|
(one_hz_on_ ? 0x0 : 0x8) |
|
|
|
|
|
(sixteen_hz_on_ ? 0x0 : 0x4);
|
|
|
|
|
break;
|
2023-01-14 19:58:12 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-01-17 03:25:20 +00:00
|
|
|
|
return uint8_t(0xf0 | value);
|
2023-01-14 19:17:28 +00:00
|
|
|
|
}
|