--- nono/vm/rp5c15.cpp 2026/04/29 17:04:55 1.1.1.8 +++ nono/vm/rp5c15.cpp 2026/04/29 17:04:59 1.1.1.9 @@ -18,8 +18,12 @@ RP5C15Device::RP5C15Device() devaddr = 0xe8a000; devlen = 0x2000; + // X68k は 1980 年の時、年カウンタに 0 をセット + year_epoch = 1980; + use_localtime = true; + monitor.func = (MonitorCallback_t)&RP5C15Device::MonitorUpdate; - monitor.SetSize(30, 17); + monitor.SetSize(41, 22); monitor.Regist(ID_MONITOR_RTC); } @@ -31,13 +35,12 @@ RP5C15Device::~RP5C15Device() bool RP5C15Device::Init() { - // ホストの時刻をローカル時刻で取得 - LoadHostRTC(true); - - // それをレジスタに反映。 - UpdateReg(UPDATE_ALL); + // デフォルトで 24 時間計にしておく + reg.sel24 = 0x01; + // デフォルトで 1HZ_ON, 16HZ_ON をネゲート(1)しておく + reg.reset = RP5C15::RESET_1Hz | RP5C15::RESET_16Hz; - return true; + return inherited::Init(); } uint64 @@ -49,6 +52,7 @@ RP5C15Device::Read(uint32 offset) gMPU->AddCycle(29); // InsideOut p.135 // バンクを展開する + int bank = (reg.mode & RP5C15::MODE_BANK); n = bank * 0x10 + offset; // XXX 14, 15 は write only らしいが何が読めるか @@ -83,87 +87,63 @@ RP5C15Device::Write(uint32 offset, uint3 gMPU->AddCycle(38); // InsideOut p.135 // バンクを展開する + int bank = (reg.mode & RP5C15::MODE_BANK); n = bank * 0x10 + offset; putlog(2, "RTC Bank%d $%x <- $%x", bank, offset, (data & 0x0f)); switch (n) { case RP5C15::SEC1: - rtc.sec = reg.sec10 * 10 + (data & reg.mask[n]); - UpdateReg(UPDATE_SEC); - break; case RP5C15::SEC10: - rtc.sec = (data & reg.mask[n]) * 10 + reg.sec1; - UpdateReg(UPDATE_SEC); - break; - case RP5C15::MIN1: - rtc.min = reg.min10 * 10 + (data & reg.mask[n]); - UpdateReg(UPDATE_MIN); - break; case RP5C15::MIN10: - rtc.min = (data & reg.mask[n]) * 10 + reg.min1; - UpdateReg(UPDATE_MIN); - break; - - case RP5C15::HOUR1: // hour1 - if (reg.sel24) { - // 24時間計 - rtc.hour = reg.hour10 * 10 + (data & reg.mask[n]); - } else { - // 12時間計 - rtc.hour = ((reg.hour10 & 0x02) ? 12 : 0) - + (reg.hour10 & 0x01) * 10 + (data & reg.mask[n]); - } - UpdateReg(UPDATE_HOUR); - break; - case RP5C15::HOUR10: // hour10 - if (reg.sel24) { - // 24時間計 - rtc.hour = (data & reg.mask[n]) * 10 + reg.hour1; - } else { - // 12時間計 - rtc.hour = ((data & 0x02) ? 12 : 0) - + (data & 0x01) * 10 + reg.hour1; - } - UpdateReg(UPDATE_HOUR); - break; - - case RP5C15::WDAY: - // 曜日カウンタは日付などとは一切連動していない - rtc.wday = data & reg.mask[n]; - UpdateReg(UPDATE_DAY); - break; - + case RP5C15::HOUR1: + case RP5C15::HOUR10: + case RP5C15::WDAY: // 曜日カウンタは日付などとは一切連動していない case RP5C15::DAY1: - rtc.mday = reg.day10 * 10 + (data & reg.mask[n]); - UpdateReg(UPDATE_DAY); - break; case RP5C15::DAY10: - rtc.mday = (data & reg.mask[n]) * 10 + reg.day1; - UpdateReg(UPDATE_DAY); - break; - case RP5C15::MON1: - rtc.mon = reg.mon10 * 10 + (data & reg.mask[n]); - UpdateReg(UPDATE_MON); - break; case RP5C15::MON10: - rtc.mon = (data & reg.mask[n]) * 10 + reg.mon1; - UpdateReg(UPDATE_MON); - break; - case RP5C15::YEAR1: - rtc.year = 1980 + (reg.year10 * 10) + (data & reg.mask[n]); - UpdateReg(UPDATE_YEAR); - break; case RP5C15::YEAR10: - rtc.year = 1980 + (data & reg.mask[n]) * 10 + reg.year1; - UpdateReg(UPDATE_YEAR); + case RP5C15::SEL24: + case RP5C15::LEAP: + reg.r[n] = data & reg.mask[n]; break; case RP5C15::CLKSEL: - case 0x12 ... 0x1c: + reg.clksel = data & reg.mask[n]; + switch (reg.clksel) { + case 0: // ハイインピーダンス + case 1: // 16384Hz + case 2: // 1024Hz + case 3: // 128Hz + // 128Hz 点滅とか点灯みたいなもんだろ(適当) + clkout = true; + break; + + // (?) たぶん内部ではずっとカウンタ部分は動いていて + // 出力位置を切り替えているだけのはずなので、 + // CLKSEL を変えた時の初期状態は内部カウンタの状態に依存するはず。 + case 4: // 16Hz + clkout = ((cnt & 1) == 0); + break; + case 5: // 1Hz + clkout = ((cnt & 16) == 0); + break; + case 6: // 1/60Hz + clkout = (cnt_60sec < 30); + break; + case 7: + clkout = false; + break; + } + break; + + // アラーム関係は未実装 + case RP5C15::ALARM_MIN1 ... RP5C15::ALARM_DAY10: + case RP5C15::notused1: + case RP5C15::notused2: // マスクして書き込む reg.r[n] = data & reg.mask[n]; break; @@ -179,24 +159,30 @@ RP5C15Device::Write(uint32 offset, uint3 // 現在の秒が 0..29秒なら 0秒に切り下げ。 // 30..59秒なら +1分0秒に繰り上げ。 // いずれにしても 1秒と10秒の位は 0 にする。 - uint32 updates = UPDATE_SEC; - if (rtc.sec < 30) { - // 1分切り下げ - rtc.sec = 0; - } else { + // XXX 秒以下のカウンタもクリアするのでは? + if (GetSec() >= 30) { // 1分繰り上げ - rtc.sec = 0; - updates = CarryMin(updates); + CountUpMin(); } - UpdateReg(updates); + // マニュアルを読む限り秒以下をクリア(?) + reg.sec1 = 0; + reg.sec10 = 0; + cnt = 0; + clock_hold = false; break; } case RP5C15::MODE: case RP5C15::MODE_BANK1: reg.mode = data & reg.mask[RP5C15::MODE]; - // bank を更新 - bank = (data & RP5C15::MODE_BANK); + + if ((reg.mode & RP5C15::MODE_TIMER_EN)) { + if (clock_hold) { + // ホールドされていた秒カウントアップを実行 + CountUpSec(); + clock_hold = false; + } + } break; case RP5C15::TEST: @@ -215,8 +201,15 @@ RP5C15Device::Write(uint32 offset, uint3 reg.reset = data & reg.mask[RP5C15::RESET]; if ((reg.reset & RP5C15::RESET_TIMER)) { // %1 を書き込むと、秒未満の内部カウンタをリセット - // 繰り上がり分の保持も消えるけどいいよな? - rtc.cnt = 0; + // 繰り上がり分の保持も消す。 + cnt = 0; + clock_hold = false; + } + if ((reg.reset & RP5C15::RESET_16Hz)) { + alarm_16Hz = false; + } + if ((reg.reset & RP5C15::RESET_1Hz)) { + alarm_1Hz = false; } break; @@ -232,6 +225,7 @@ RP5C15Device::Peek(uint32 offset) int n; // バンクを展開する + int bank = (reg.mode & RP5C15::MODE_BANK); n = bank * 0x10 + offset; switch (n) { @@ -253,178 +247,332 @@ RP5C15Device::Peek(uint32 offset) void RP5C15Device::MonitorUpdate(Monitor *, TextScreen& screen) { - int x; - int y; - - // - // #0 #1 - // 1:4 2 - // - // 0 1 2 3 4 5 6 7 8 9 A B C D E F - // Bank0 0 1 2 3 4 5 6 7 8 9 a b c d e f - // Bank1 + int x = 18; + int y = 0; screen.Clear(); - screen.Puts(0, 0, " #0 #1"); - y = 1; - for (int i = 0; i < 13; i++, y++) { - screen.Print(0, y, "%X:%x %x", i, reg.r[i], reg.r[i + 16]); - } - for (int i = 13; i < 16; i++, y++) { - screen.Print(0, y, "%X:%x", i, reg.r[i]); - } - - x = 8; - y = 0; - - int year = reg.year10 * 10 + reg.year1 + 1980; - int mon = reg.mon10 * 10 + reg.mon1; - int day = reg.day10 * 10 + reg.day1; - screen.Print(x, y, "%04d/%02d/%02d(%s)", - year, mon, day, wdays[reg.wday]); - y++; - - int hour; + int year = reg.year10 * 10 + reg.year1 + year_epoch; + int hour10; + const char *ampm; if (reg.sel24) { - hour = reg.hour10 * 10 + reg.hour1; + ampm = ""; + hour10 = reg.hour10; } else { - hour = (reg.hour10 & 1) * 10 + reg.hour1; - screen.Puts(x, y, (reg.hour10 & 2) == 0 ? "AM" : "PM"); + ampm = (reg.hour10 & 2) == 0 ? " AM": " PM"; + hour10 = reg.hour10 & 1; } - int min = reg.min10 * 10 + reg.min1; - int sec = reg.sec10 * 10 + reg.sec1; - screen.Print(x + 3, y, "%02d:%02d:%02d", hour, min, sec); + screen.Print(0, y++, "%04d[%x%x]/%x%x/%x%x(%s) %x%x:%x%x:%x%x.%02x%s", + year, reg.year10, reg.year1, + reg.mon10, reg.mon1, reg.day10, reg.day1, wdays[reg.wday], + hour10, reg.hour1, reg.min10, reg.min1, reg.sec10, reg.sec1, + ((uint32)cnt & 0x1f), ampm); + + screen.Print(0, y, "TimeZone: %s", (use_localtime) ? "Local" : "UTC"); + screen.Print(x, y, "Epoch: %4d", year_epoch); + + // バンク0 + y = 3; + screen.Print(0, y++, "Bank0"); + static const char * const b0names[] = { + " 1sec", + "10sec", + " 1min", + "10min", + " 1hour", + "10hour", + "wday", + " 1day", + "10day", + " 1mon", + "10mon", + " 1year", + "10year", + }; + for (int i = 0; i < countof(b0names); i++) { + screen.Print(0, y, "$%x(%s)", i, b0names[i]); + screen.Print(10, y, ": %x", reg.r[i]); + y++; + } + + // バンク1 + y = 3; + screen.Print(x, y++, "Bank1"); + static const char * const b1names[] = { + "CLKSEL", + "ADJ", + "AL 1m", + "AL10m", + "AL 1h", + "AL10h", + "ALwday", + "AL 1d", + "AL10d", + "", + "12/24H", + "LEAP", + "", + }; + for (int i = 0; i < countof(b1names); i++) { + if (b1names[i][0] != '\0') { + screen.Print(x, y, "$%x(%s)", i, b1names[i]); + screen.Print(x + 10, y, ": %x", reg.r[16 + i]); + } else { + // $9 と $c は欠番 + screen.Print(x, y, "$%x - ", i); + } + y++; + } + + // CLKSEL + static const char * const clksel_s[] = { + "HiZ", + "16384Hz", + "1024Hz", + "128Hz", + "16Hz", + "1Hz", + "1/60Hz", + "L", + }; + screen.Print(x + 14, 4, "(%s)", clksel_s[reg.clksel]); + + // 12/24H + screen.Print(x + 14, 14, "(%dH)", reg.sel24 ? 24 : 12); + + // MODE + y = 17; + screen.Print(0, y, "$d(MODE): %x", reg.mode); + screen.Puts(13, y, TA::OnOff(reg.mode & RP5C15::MODE_TIMER_EN), "TIMER_EN"); + screen.Puts(22, y, TA::OnOff(reg.mode & RP5C15::MODE_ALARM_EN), "ALARM_EN"); + screen.Print(31, y, "BANK=%d", reg.mode & RP5C15::MODE_BANK); + + // TEST y++; + screen.Print(0, y, "$e(TEST): %x", reg.test); - screen.Print(x, y++, "Leap %d", reg.leap); + // RESET y++; + screen.Print(0, y, "$f(RESET): %x", reg.reset); + screen.Print(13, y, "1Hz=%s", + (reg.reset & RP5C15::RESET_1Hz) ? "OFF" : "ON"); + screen.Print(21, y, "16Hz=%s", + (reg.reset & RP5C15::RESET_16Hz) ? "OFF" : "ON"); + screen.Puts(30, y, TA::OnOff(reg.reset & RP5C15::RESET_TIMER), "TIMER"); + screen.Puts(36, y, TA::OnOff(reg.reset & RP5C15::RESET_ALARM), "ALARM"); - screen.Print(x, y++, "CLKOUT %c", clkout ? 'H' : 'L'); - screen.Print(x, y++, "ALARM %c", alarmout ? 'H' : 'L'); + // PIN y++; + y++; + // ハードウェアの ~ALARM ピンは負論理なのでアクティブ L だが、 + // ここでは分かりやすさ優先で正論理としている。 + screen.Print(0, y, "Pin: CLKOUT=%c ALARM=%c", + clkout ? 'H' : 'L', + AlarmOut() ? 'H' : 'L'); +} - screen.Print(x, y++, "cnt %2d", rtc.cnt); +// 秒を取得 +uint +RP5C15Device::GetSec() const +{ + return reg.sec10 * 10 + reg.sec1; } -// 32Hz クロック入力 -// -// 32Hz ごとに呼ばれて、これが RTC の時計を進める処理をするところ。 -// RTC を実時間に同期して進めるモードなら実時間を計るスケジューラから、 -// RTC を仮想時間に同期して進めるモードなら VM スケジューラから呼ばれる。 -// -// CLKOUT 128Hz 以上のモードには対応していない。 -void -RP5C15Device::ClockIn() -{ - uint32 updates; - - // 32Hz カウンタを進める - updates = CountUp(); - - // レジスタの値を更新する - UpdateReg(updates); - - // CLKOUT 端子の切り替え周期は CLKSEL による - switch (reg.clksel) { - case 0: // ハイインピーダンス - case 1: // 16384Hz - case 2: // 1024Hz - case 3: // 128Hz - // 128Hz 点滅とか点灯みたいなもんだろ(適当) - clkout = true; - break; +// 秒を設定 +void +RP5C15Device::SetSec(uint v) +{ + reg.sec1 = v % 10; + reg.sec10 = v / 10; +} - case 4: // 16Hz - clkout = !clkout; - // アラーム端子 16Hz パルス出力は %0 ならオン - if ((reg.reset & RP5C15::RESET_16Hz) == 0) { - alarmout = !alarmout; - } - break; +// 分を取得 +uint +RP5C15Device::GetMin() const +{ + return reg.min10 * 10 + reg.min1; +} + +// 分を設定 +void +RP5C15Device::SetMin(uint v) +{ + reg.min1 = v % 10; + reg.min10 = v / 10; +} + +// 時を取得 +uint +RP5C15Device::GetHour() const +{ + if (reg.sel24) { + return reg.hour10 * 10 + reg.hour1; + } else { + int h10; + h10 = (reg.hour10 & 0x1) * 10 + ((reg.hour10 & 0x2) >> 1) * 12; + return h10 + reg.hour1; + } +} - case 5: // 1Hz +// 時を設定 +void +RP5C15Device::SetHour(uint v) +{ + if (reg.sel24) { + reg.hour1 = v % 10; + reg.hour10 = v / 10; + } else { + int pm = (v < 12) ? 0x0 : 0x2; + v %= 12; + reg.hour1 = v % 10; + reg.hour10 = v / 10 + pm; + } +} + +// 曜日を取得 +uint +RP5C15Device::GetWday() const +{ + return reg.wday; +} + +// 曜日を設定 +void +RP5C15Device::SetWday(uint v) +{ + reg.wday = v; +} + +// 日を取得 +uint +RP5C15Device::GetMday() const +{ + return reg.day10 * 10 + reg.day1; +} + +// 日を設定 +void +RP5C15Device::SetMday(uint v) +{ + reg.day1 = v % 10; + reg.day10 = v / 10; +} + +// 月を取得 +uint +RP5C15Device::GetMon() const +{ + return reg.mon10 * 10 + reg.mon1; +} + +// 月を設定 +void +RP5C15Device::SetMon(uint v) +{ + reg.mon1 = v % 10; + reg.mon10 = v / 10; +} + +// 年を取得 +uint +RP5C15Device::GetYear() const +{ + return reg.year10 * 10 + reg.year1 + year_epoch; +} + +// 年を設定 +void +RP5C15Device::SetYear(uint v) +{ + v -= year_epoch; + reg.year1 = v % 10; + reg.year10 = v / 10; +} + +uint +RP5C15Device::GetLeap() const +{ + return reg.leap; +} + +void +RP5C15Device::SetLeap(uint v) +{ + reg.leap = v & 3; +} + +// 32Hz 仮想 RTC からのコール +void +RP5C15Device::Tick32Hz() +{ + if (reg.clksel == 4) { + // CLKOUT 16Hz は duty50% なので ON/OFF 駆動は 32Hz になる + clkout = ((cnt & 1) == 0); + } + + // ALARM 端子の 16Hz パルスも CLKOUT に同期するとは書いてないけど、 + // 面倒なのでとりあえず同じところで処理してしまう。 + // データシートの書き方的には同じ波形になると思われる。 + + // アラーム端子 16Hz パルス出力は %0 ならオン + if ((reg.reset & RP5C15::RESET_16Hz) == 0) { + alarm_16Hz = !alarm_16Hz; + } +} + +// 2Hz 仮想 RTC からのコール +// 分周段 0/32, 16/32 秒でコールされる。 +void +RP5C15Device::Tick2Hz() +{ + if (reg.clksel == 5) { + // CLKOUT 1Hz は duty50% なので ON/OFF 駆動は 2Hz になる // CLKOUT 端子は秒の繰り上がりに同期している。 - // ALARM 端子の 1Hz パルスもそれに同期するとは書いてないけど、 - // 面倒なのでとりあえず同じところで処理してしまう。 // TIMER_EN (秒への繰り上がり) が無効でも反転自体は起きる気が // するので、ここでは UPDATE_SEC ではなく cnt で判定する。 - if ((rtc.cnt & 31) == 0) { - // CLKOUT は 1秒の繰り上がりが起きる時に 'H' になる - clkout = true; + // CLKOUT は 1秒の繰り上がりが起きる時に 'H' になる - // ALARM 反転 - if ((reg.reset & RP5C15::RESET_1Hz) == 0) { - alarmout = !alarmout; - } - } else if ((rtc.cnt & 31) == 16) { - // CLKOUT、その反対側 - clkout = false; + clkout = ((cnt & 15) == 0); + } - // ALARM 反転 - if ((reg.reset & RP5C15::RESET_1Hz) == 0) { - alarmout = !alarmout; - } - } - break; + // ALARM 端子の 1Hz パルスも CLKOUT に同期するとは書いてないけど、 + // 面倒なのでとりあえず同じところで処理してしまう。 + // データシートの書き方的には同じ波形になると思われる。 - case 6: // 1/60Hz - if ((updates & UPDATE_MIN)) { - // 00秒になったので CLKOUT を 'H' に - clkout = true; - } else if ((updates & UPDATE_SEC) && rtc.sec == 30) { - // 30秒になったので CLKOUT を 'L' に - clkout = false; - } - break; - - case 7: // 'L' 固定 - clkout = false; - break; + // アラーム端子 16Hz パルス出力は %0 ならオン + if ((reg.reset & RP5C15::RESET_1Hz) == 0) { + alarm_1Hz = !alarm_1Hz; } } -// updates で指定された桁に該当するレジスタを更新する。 -// 繰り上がりかどうかは処理せず現在の値を RP5C15 レジスタにセットするだけ。 +// 1Hz 仮想 RTC からのコール +// 分周段 0/32 秒でコールされる。 void -RP5C15Device::UpdateReg(uint32 updates) +RP5C15Device::Tick1Hz() { - if ((updates & UPDATE_SEC)) { - reg.sec1 = rtc.sec % 10; - reg.sec10 = rtc.sec / 10; - } - if ((updates & UPDATE_MIN)) { - reg.min1 = rtc.min % 10; - reg.min10 = rtc.min / 10; - } - if ((updates & UPDATE_HOUR)) { - if (reg.sel24) { - // 24時間計モードでは普通の BCD - reg.hour1 = rtc.hour % 10; - reg.hour10 = rtc.hour / 10; - } else { - // 12時間計モードなら、時のbit1 が AM/PM を表す - int h12 = rtc.hour % 12; - reg.hour1 = h12 % 10; - reg.hour10 = h12 / 10; - reg.hour10 |= (rtc.hour >= 12) ? 0x02 : 0x00; - } + if ((reg.mode & RP5C15::MODE_TIMER_EN)) { + // 秒カウンタ更新 + CountUpSec(); + } else { + // 秒カウンタは更新させない。内部のクロックホールドをアサート。 + clock_hold = true; + } + + // CLKOUT 1/60 Hz については + // 「秒のカウントアップのタイミングでアップエッジ」 + // としか書かれておらず、00 or 30 秒で動作するとは書かれていない。 + // データシートの図からはカレンダとは独立しているように読める。 + + // 1/60Hz CLKOUT のための 60sec カウンタ + // このカウンタは clksel によらず常にカウントする。 + cnt_60sec++; + if (cnt_60sec == 60) { + cnt_60sec = 0; } - if ((updates & UPDATE_DAY)) { - reg.wday = rtc.wday; - reg.day1 = rtc.mday % 10; - reg.day10 = rtc.mday / 10; - } - if ((updates & UPDATE_MON)) { - reg.mon1 = rtc.mon % 10; - reg.mon10 = rtc.mon / 10; - } - if ((updates & UPDATE_YEAR)) { - // X680x0 では 0 を 1980年としている。 - int y = rtc.year - 1980; - reg.year1 = y % 10; - reg.year10 = y / 10; + if (reg.clksel == 6) { + // CLKOUT 1/60Hz は duty50% なので ON/OFF 駆動は 30sec になる + clkout = (cnt_60sec < 30); } }