1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-09 06:29:33 +00:00

Merge pull request #1116 from TomHarte/RP5C01

More fully implement the RP-5C01.
This commit is contained in:
Thomas Harte 2023-01-17 22:25:17 -05:00 committed by GitHub
commit f0b1c34db2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 313 additions and 42 deletions

View File

@ -8,9 +8,28 @@
#include "RP5C01.hpp"
#include "../../Numeric/NumericCoder.hpp"
#include <ctime>
using namespace Ricoh::RP5C01;
RP5C01::RP5C01(HalfCycles clock_rate) : clock_rate_(clock_rate) {}
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 % 100;
leap_year_ = time_date->tm_year % 4;
}
void RP5C01::run_for(HalfCycles cycles) {
sub_seconds_ += cycles;
@ -45,6 +64,7 @@ void RP5C01::run_for(HalfCycles cycles) {
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;
@ -68,65 +88,236 @@ void RP5C01::run_for(HalfCycles cycles) {
if(month_ == 12) {
month_ = 0;
++year_;
year_ = (year_ + 1) % 100;
leap_year_ = (leap_year_ + 1) & 3;
}
}
}
namespace {
constexpr int Reg(int mode, int address) {
return address | mode << 4;
}
constexpr int PM = 1 << 4;
constexpr int twenty_four_to_twelve(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;
// Registers DF don't depend on the mode.
if(address >= 0xd) {
switch(address) {
default: break;
case 0xd:
timer_enabled_ = value & 0x8;
alarm_enabled_ = value & 0x4;
mode_ = value & 0x3;
break;
case 0xe:
// Test register; unclear what is supposed to happen.
break;
case 0xf:
one_hz_on_ = !(value & 0x8);
sixteen_hz_on_ = !(value & 0x4);
// TODO: timer reset on bit 1, alarm reset on bit 0
break;
}
// Handle potential RAM accesses.
if(address < 0xd && mode_ >= 2) {
address += mode_ == 3 ? 13 : 0;
ram_[size_t(address)] = value & 0xf;
return;
}
switch(mode_) {
case 3:
address += 13;
[[fallthrough]];
case 2:
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); break;
case Reg(0, 0x0a): TwoDigitEncoder::encode<1>(month_, value & 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 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;
}
// TODO.
printf("RP-5C01 write of %d to %d in mode %d\n", value, address & 0xf, mode_);
}
uint8_t RP5C01::read(int address) {
address &= 0xf;
if(address < 0xd) {
switch(mode_) {
case 3:
address += 13;
[[fallthrough]];
case 2:
return 0xf0 | ram_[size_t(address)];
}
if(address < 0xd && mode_ >= 2) {
address += mode_ == 3 ? 13 : 0;
return 0xf0 | ram_[size_t(address)];
}
// TODO.
printf("RP-5C01 read from %d in mode %d\n", address & 0xf, mode_);
return 0xff;
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_); break;
case Reg(0, 0x0a): value = TwoDigitEncoder::decode<1>(month_); 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 DF 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);
}

View File

@ -43,7 +43,7 @@ class RP5C01 {
int day_of_the_week_ = 0;
int day_ = 0;
int month_ = 0;
int year_ = 1988;
int year_ = 0;
int leap_year_ = 0;
// Other flags.
@ -52,6 +52,7 @@ class RP5C01 {
int mode_ = 0;
bool one_hz_on_ = false;
bool sixteen_hz_on_ = false;
bool twentyfour_hour_clock_ = true;
};
}

77
Numeric/NumericCoder.hpp Normal file
View File

@ -0,0 +1,77 @@
//
// NumericCoder.hpp
// Clock Signal
//
// Created by Thomas Harte on 17/01/2023.
// Copyright © 2023 Thomas Harte. All rights reserved.
//
#ifndef NumericCoder_hpp
#define NumericCoder_hpp
namespace Numeric {
/// Stores and retrieves an arbitrary number of fields into an int with arbitrary modulos.
///
/// E.g. NumericCoder<8, 3, 14> establishes an encoder and decoder for three fields,
/// the first is modulo 8, the second is modulo 3, the third is modulo 14.
///
/// NumericCoder<8, 3, 14>::encode<2>(v, 9) will mutate v so that the third field
/// (i.e. field 2) has value 9.
///
/// The first given field will occupy the least significant part of the target int; e.g.
/// a NumericCoder<3, 4, 6> with ::encode<0>(v, 2), ::encode<1>(v, 1) and
/// ::encode<2>(v, 5) will have a final value of:
///
/// [value] 2 +
/// [value] 1 * [product of previous field sizes] 3 +
/// 5 * 12
/// = 65
///
template <int... Sizes> class NumericCoder {
public:
/// Modifies @c target to hold @c value at @c index.
template <int index> static void encode(int &target, int value) {
static_assert(index < sizeof...(Sizes), "Index must be within range");
NumericEncoder<Sizes...>::template encode<index>(target, value);
}
/// @returns The value from @c source at @c index.
template <int index> static int decode(int source) {
static_assert(index < sizeof...(Sizes), "Index must be within range");
return NumericDecoder<Sizes...>::template decode<index>(source);
}
private:
template <int size, int... Tail>
struct NumericEncoder {
template <int index, int i = 0, int divider = 1> static void encode(int &target, int value) {
if constexpr (i == index) {
const int suffix = target % divider;
target /= divider;
target -= target % size;
target += value % size;
target *= divider;
target += suffix;
} else {
NumericEncoder<Tail...>::template encode<index, i+1, divider*size>(target, value);
}
}
};
template <int size, int... Tail>
struct NumericDecoder {
template <int index, int i = 0, int divider = 1> static int decode(int source) {
if constexpr (i == index) {
return (source / divider) % size;
} else {
return NumericDecoder<Tail...>::template decode<index, i+1, divider*size>(source);
}
}
};
};
}
#endif /* NumericCoder_hpp */

View File

@ -1430,6 +1430,7 @@
4B643F3E1D77B88000D431D6 /* DocumentController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DocumentController.swift; sourceTree = "<group>"; };
4B644ED023F0FB55006C0CC5 /* ScanSynchroniser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ScanSynchroniser.hpp; sourceTree = "<group>"; };
4B65085F22F4CF8D009C1100 /* Keyboard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Keyboard.cpp; sourceTree = "<group>"; };
4B66E1A8297719270057ED0F /* NumericCoder.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = NumericCoder.hpp; sourceTree = "<group>"; };
4B670A832401CB8400D4E002 /* z80memptr.tap */ = {isa = PBXFileReference; lastKnownFileType = file; path = z80memptr.tap; sourceTree = "<group>"; };
4B670A852401CB8400D4E002 /* z80ccf.tap */ = {isa = PBXFileReference; lastKnownFileType = file; path = z80ccf.tap; sourceTree = "<group>"; };
4B670A872401CB8400D4E002 /* z80flags.tap */ = {isa = PBXFileReference; lastKnownFileType = file; path = z80flags.tap; sourceTree = "<group>"; };
@ -3304,6 +3305,7 @@
4BD155312716362A00410C6E /* BitSpread.hpp */,
4B7BA03E23D55E7900B98D9E /* CRC.hpp */,
4B7BA03F23D55E7900B98D9E /* LFSR.hpp */,
4B66E1A8297719270057ED0F /* NumericCoder.hpp */,
4BB5B995281B1D3E00522DA9 /* RegisterSizes.hpp */,
4BFEA2F12682A90200EBF94C /* Sizes.hpp */,
);