// // RP5C01.cpp // Clock Signal // // Created by Thomas Harte on 14/01/2023. // Copyright © 2023 Thomas Harte. All rights reserved. // #include "RP5C01.hpp" #include "../../Numeric/NumericCoder.hpp" #include using namespace Ricoh::RP5C01; RP5C01::RP5C01(HalfCycles clock_rate) : clock_rate_(clock_rate) { // Seed internal clock. std::time_t now = std::time(NULL); std::tm *time_date = std::localtime(&now); 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 + 20) % 100; // This is probably MSX specific; rethink if/when other machines use this chip. leap_year_ = time_date->tm_year % 4; } void RP5C01::run_for(const 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_) { default: 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; year_ = (year_ + 1) % 100; leap_year_ = (leap_year_ + 1) & 3; } } } namespace { constexpr int Reg(const int mode, const int address) { return address | mode << 4; } constexpr int PM = 1 << 4; constexpr int twenty_four_to_twelve(const int hours) { 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; } using SecondEncoder = Numeric::NumericCoder< 10, 6, // Seconds. 10, 6, // Minutes. 24 // Hours >; using TwoDigitEncoder = Numeric::NumericCoder<10, 10>; } /// Performs a write of @c value to @c address. void RP5C01::write(int address, uint8_t value) { address &= 0xf; value &= 0xf; // Handle potential RAM accesses. if(address < 0xd && mode_ >= 2) { address += mode_ == 3 ? 13 : 0; ram_[size_t(address)] = value & 0xf; return; } switch(Reg(mode_, address)) { default: break; // Seconds. 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. 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) { TwoDigitEncoder::encode<0>(hours, value); } else { TwoDigitEncoder::encode<1>(hours, value & 3); } if(!twentyfour_hour_clock_) { hours = twelve_to_twenty_four(hours); } SecondEncoder::encode<4>(seconds_, hours); } break; // Day of the week. case Reg(0, 0x06): day_of_the_week_ = value % 7; break; // Day. case Reg(0, 0x07): TwoDigitEncoder::encode<0>(day_, value); break; case Reg(0, 0x08): TwoDigitEncoder::encode<1>(day_, value & 3); break; // Month. case Reg(0, 0x09): TwoDigitEncoder::encode<0>(month_, (value - 1)); break; case Reg(0, 0x0a): TwoDigitEncoder::encode<1>(month_, (value - 1) & 1); break; // Year. case Reg(0, 0x0b): TwoDigitEncoder::encode<0>(year_, value); break; case Reg(0, 0x0c): TwoDigitEncoder::encode<1>(year_, value); 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; // 24/12-hour clock. case Reg(1, 0x0a): twentyfour_hour_clock_ = value & 1; break; // Lead-year counter. case Reg(1, 0x0b): leap_year_ = value & 3; 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): 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; } } uint8_t RP5C01::read(int address) { address &= 0xf; if(address < 0xd && mode_ >= 2) { address += mode_ == 3 ? 13 : 0; return 0xf0 | ram_[size_t(address)]; } int value = 0xf; switch(Reg(mode_, address)) { // Second. case Reg(0, 0x00): value = SecondEncoder::decode<0>(seconds_); break; case Reg(0, 0x01): value = SecondEncoder::decode<1>(seconds_); break; // Minute. case Reg(0, 0x02): value = SecondEncoder::decode<2>(seconds_); break; case Reg(0, 0x03): value = SecondEncoder::decode<3>(seconds_); break; // Hour. 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) { value = TwoDigitEncoder::decode<0>(hours); } else { value = TwoDigitEncoder::decode<1>(hours); } } break; // Day-of-the-week. case Reg(0, 0x06): value = day_of_the_week_; break; // Day. case Reg(0, 0x07): value = TwoDigitEncoder::decode<0>(day_); break; case Reg(0, 0x08): value = TwoDigitEncoder::decode<1>(day_); break; // Month. case Reg(0, 0x09): value = TwoDigitEncoder::decode<0>(month_ + 1); break; case Reg(0, 0x0a): value = TwoDigitEncoder::decode<1>(month_ + 1); break; // Year. case Reg(0, 0x0b): value = TwoDigitEncoder::decode<0>(year_); break; case Reg(0, 0x0c): value = TwoDigitEncoder::decode<1>(year_); 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; // 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; } return uint8_t(0xf0 | value); }