--- nono/vm/mfp.cpp 2026/04/29 17:05:14 1.1.1.13 +++ nono/vm/mfp.cpp 2026/04/29 17:05:25 1.1.1.15 @@ -15,16 +15,13 @@ #include "scheduler.h" #include "x68kkbd.h" -// グローバル参照用 -MFPDevice *gMFP; +// InsideOut p.135 +static const busdata wait = busdata::Wait(24 * 40_nsec); // コンストラクタ MFPDevice::MFPDevice() - : inherited("MFP") + : inherited(OBJ_MFP) { - devaddr = baseaddr; - devlen = 0x2000; - for (int ch = 0; ch < event.size(); ch++) { event[ch].dev = this; event[ch].func = ToEventCallback(&MFPDevice::TimerCallback); @@ -45,14 +42,31 @@ MFPDevice::MFPDevice() // デストラクタ MFPDevice::~MFPDevice() { - gMFP = NULL; +} + +// 初期化 +bool +MFPDevice::Init() +{ + if (inherited::Init() == false) { + return false; + } + + interrupt = GetInterruptDevice(); + keyboard = dynamic_cast(GetKeyboard()); + + return true; } // リセット void MFPDevice::ResetHard(bool poweron) { + // 3.3 RESET OPERATION には // All internal registers are cleared expect the TxDR, UDR, and TSR. + // と書いてあるが、6.2.1 には TxDR はリセット時に $00 と書いてある。 + // 内部カウンタは影響を受けずに next_tdr だけリセットされるという + // 意味だろうか。 mfp.aer = 0; mfp.ddr = 0; mfp.ier.w = 0; @@ -61,6 +75,7 @@ MFPDevice::ResetHard(bool poweron) mfp.imr.w = 0; for (int i = 0; i < countof(mfp.tcr); i++) { mfp.tcr[i] = 0; + mfp.next_tdr[i] = 256; } mfp.scr = 0; mfp.ucr = 0; @@ -71,9 +86,9 @@ MFPDevice::ResetHard(bool poweron) // All timers are stopped. for (auto& ev : event) { - gScheduler->StopEvent(ev); + scheduler->StopEvent(ev); } - gScheduler->StopEvent(key_event); + scheduler->StopEvent(key_event); // USART RX and TX are disabled. // SO line is placed in HighZ. @@ -88,14 +103,14 @@ MFPDevice::ResetHard(bool poweron) // CIRQ | FMIRQ | EXPON | ALARM にしておく mfp.gpip = 0x6b; + + ChangeInterrupt(); } -uint64 +busdata MFPDevice::Read(uint32 offset) { - uint8 data; - - gMPU->AddCycle(24); // InsideOut p.135 + busdata data; switch (offset) { case MFP::GPIP: @@ -176,14 +191,14 @@ MFPDevice::Read(uint32 offset) data = 0xff; break; } + + data |= wait; return data; } -uint64 +busdata MFPDevice::Write(uint32 offset, uint32 data) { - gMPU->AddCycle(24); // InsideOut p.135 - switch (offset) { case MFP::GPIP: putlog(0, "Write GPIP <- $%02x (NOT IMPLEMENTED)", data); @@ -201,11 +216,11 @@ MFPDevice::Write(uint32 offset, uint32 d break; case MFP::IERA: putlog(2, "IERA <- $%02x", data); - mfp.ier.a = data; + SetIER(mfp.ier.a, mfp.ipr.a, data); break; case MFP::IERB: putlog(2, "IERB <- $%02x", data); - mfp.ier.b = data; + SetIER(mfp.ier.b, mfp.ipr.b, data); break; case MFP::IPRA: putlog(2, "IPRA <- $%02x", data); @@ -300,10 +315,11 @@ MFPDevice::Write(uint32 offset, uint32 d default: break; } - return 0; + + return wait; } -uint64 +busdata MFPDevice::Peek(uint32 offset) { uint8 data; @@ -390,9 +406,17 @@ MFPDevice::Peek(uint32 offset) void MFPDevice::MonitorUpdate(Monitor *, TextScreen& screen) { + MFP mfp_; + std::array tdr_; int x; int y; + // ローカルにコピー。 + memcpy(&mfp_, &mfp, sizeof(mfp)); + for (int ch = 0; ch < tdr_.size(); ch++) { + tdr_[ch] = GetTDR(ch); + } + screen.Clear(); // 0 1 2 3 4 5 6 @@ -407,42 +431,42 @@ MFPDevice::MonitorUpdate(Monitor *, Text // 1列目(GPIP 関連と VR) x = 0; y = 0; - screen.Print(x, y++, "$%06x(GPIP):$%02x", A(MFP::GPIP), mfp.gpip); - screen.Print(x, y++, "$%06x(AER): $%02x", A(MFP::AER), mfp.aer); - screen.Print(x, y++, "$%06x(DDR): $%02x", A(MFP::DDR), mfp.ddr); - screen.Print(x, y++, "$%06x(VR): $%02x", A(MFP::VR), mfp.GetVR()); + screen.Print(x, y++, "$%06x(GPIP):$%02x", A(MFP::GPIP), mfp_.gpip); + screen.Print(x, y++, "$%06x(AER): $%02x", A(MFP::AER), mfp_.aer); + screen.Print(x, y++, "$%06x(DDR): $%02x", A(MFP::DDR), mfp_.ddr); + screen.Print(x, y++, "$%06x(VR): $%02x", A(MFP::VR), mfp_.GetVR()); // 2列目(割り込み関連) x = 20; y = 0; - screen.Print(x, y++, "$%06x(IERA):$%02x", A(MFP::IERA), mfp.ier.a); - screen.Print(x, y++, "$%06x(IERB):$%02x", A(MFP::IERB), mfp.ier.b); - screen.Print(x, y++, "$%06x(IPRA):$%02x", A(MFP::IPRA), mfp.ipr.a); - screen.Print(x, y++, "$%06x(IPRB):$%02x", A(MFP::IPRB), mfp.ipr.b); - screen.Print(x, y++, "$%06x(ISRA):$%02x", A(MFP::ISRA), mfp.isr.a); - screen.Print(x, y++, "$%06x(ISRB):$%02x", A(MFP::ISRB), mfp.isr.b); - screen.Print(x, y++, "$%06x(IMRA):$%02x", A(MFP::IMRA), mfp.imr.a); - screen.Print(x, y++, "$%06x(IMRB):$%02x", A(MFP::IMRB), mfp.imr.b); + screen.Print(x, y++, "$%06x(IERA):$%02x", A(MFP::IERA), mfp_.ier.a); + screen.Print(x, y++, "$%06x(IERB):$%02x", A(MFP::IERB), mfp_.ier.b); + screen.Print(x, y++, "$%06x(IPRA):$%02x", A(MFP::IPRA), mfp_.ipr.a); + screen.Print(x, y++, "$%06x(IPRB):$%02x", A(MFP::IPRB), mfp_.ipr.b); + screen.Print(x, y++, "$%06x(ISRA):$%02x", A(MFP::ISRA), mfp_.isr.a); + screen.Print(x, y++, "$%06x(ISRB):$%02x", A(MFP::ISRB), mfp_.isr.b); + screen.Print(x, y++, "$%06x(IMRA):$%02x", A(MFP::IMRA), mfp_.imr.a); + screen.Print(x, y++, "$%06x(IMRB):$%02x", A(MFP::IMRB), mfp_.imr.b); // 3列目(タイマー関連) x = 40; y = 0; - screen.Print(x, y++, "$%06x(TACR): $%02x", A(MFP::TACR), mfp.tcr[0]); - screen.Print(x, y++, "$%06x(TBCR): $%02x", A(MFP::TBCR), mfp.tcr[1]); + screen.Print(x, y++, "$%06x(TACR): $%02x", A(MFP::TACR), mfp_.tcr[0]); + screen.Print(x, y++, "$%06x(TBCR): $%02x", A(MFP::TBCR), mfp_.tcr[1]); screen.Print(x, y++, "$%06x(TCDCR):$%02x", A(MFP::TCDCR), - (mfp.tcr[2] << 4) | mfp.tcr[3]); - screen.Print(x, y++, "$%06x(TADR): $%02x", A(MFP::TADR), GetTDR(0)); - screen.Print(x, y++, "$%06x(TBDR): $%02x", A(MFP::TBDR), GetTDR(1)); - screen.Print(x, y++, "$%06x(TCDR): $%02x", A(MFP::TCDR), GetTDR(2)); - screen.Print(x, y++, "$%06x(TDDR): $%02x", A(MFP::TDDR), GetTDR(3)); + (mfp_.tcr[2] << 4) | mfp_.tcr[3]); + screen.Print(x, y++, "$%06x(TADR): $%02x", A(MFP::TADR), tdr_[0]); + screen.Print(x, y++, "$%06x(TBDR): $%02x", A(MFP::TBDR), tdr_[1]); + screen.Print(x, y++, "$%06x(TCDR): $%02x", A(MFP::TCDR), tdr_[2]); + screen.Print(x, y++, "$%06x(TDDR): $%02x", A(MFP::TDDR), tdr_[3]); // 4列目(USART 関連) x = 61; y = 0; - screen.Print(x, y++, "$%06x(SCR):$%02x", A(MFP::SCR), mfp.scr); - screen.Print(x, y++, "$%06x(UCR):$%02x", A(MFP::UCR), mfp.ucr); - screen.Print(x, y++, "$%06x(RSR):$%02x", A(MFP::RSR), mfp.rsr); - screen.Print(x, y++, "$%06x(TSR):$%02x", A(MFP::TSR), mfp.tsr); + screen.Print(x, y++, "$%06x(SCR):$%02x", A(MFP::SCR), mfp_.scr); + screen.Print(x, y++, "$%06x(UCR):$%02x", A(MFP::UCR), mfp_.ucr); + screen.Print(x, y++, "$%06x(RSR):$%02x", A(MFP::RSR), mfp_.rsr); + screen.Print(x, y++, "$%06x(TSR):$%02x", A(MFP::TSR), mfp_.tsr); screen.Print(x, y, "$%06x(UDR):", A(MFP::UDR)); - if ((int16)mfp.udr >= 0) { - screen.Print(x + 13, y, "$%02x", mfp.udr); + if ((int16)mfp_.udr >= 0) { + screen.Print(x + 13, y, "$%02x", mfp_.udr); } else { screen.Print(x + 13, y, "---"); } @@ -457,30 +481,30 @@ MFPDevice::MonitorUpdate(Monitor *, Text i >= 8 ? 'A' : 'B', (i % 8), intrname[i], - (mfp.ier.w & mask) ? "Enable" : "", - (mfp.imr.w & mask) ? "" : "Mask", // %0 でマスクする - (mfp.ipr.w & mask) ? "Pend" : "", - (mfp.isr.w & mask) ? "Serv" : ""); + (mfp_.ier.w & mask) ? "Enable" : "", + (mfp_.imr.w & mask) ? "" : "Mask", // %0 でマスクする + (mfp_.ipr.w & mask) ? "Pend" : "", + (mfp_.isr.w & mask) ? "Serv" : ""); } // タイマー関連 x = 40; y = 9; - screen.Print(x, y++, " Mode TDR Ini"); + screen.Print(x, y++, " Mode TDR Next"); for (int ch = 0; ch < 4; ch++) { screen.Print(x, y, "Timer-%c", ch + 'A'); - if (mfp.tcr[ch] == 0) { + if (mfp_.tcr[ch] == 0) { screen.Print(x + 8, y, "Stopped"); - } else if (mfp.tcr[ch] == 8) { + } else if (mfp_.tcr[ch] == 8) { screen.Print(x + 8, y, "Event"); - } else if (mfp.tcr[ch] > 8) { - screen.Print(x + 8, y, "Pulse(%s)", prescale_str[mfp.tcr[ch] - 8]); + } else if (mfp_.tcr[ch] > 8) { + screen.Print(x + 8, y, "Pulse(%s)", prescale_str[mfp_.tcr[ch] - 8]); } else { - screen.Print(x + 8, y, "Delay(%s)", prescale_str[mfp.tcr[ch]]); + screen.Print(x + 8, y, "Delay(%s)", prescale_str[mfp_.tcr[ch]]); } - screen.Print(x + 20, y, "%3d", GetTDR(ch)); - screen.Print(x + 24, y, "%3d", mfp.initial_tdr[ch]); + screen.Print(x + 20, y, "%3d", tdr_[ch]); + screen.Print(x + 25, y, "%3d", mfp_.next_tdr[ch] & 0xff); y++; } @@ -491,9 +515,9 @@ MFPDevice::MonitorUpdate(Monitor *, Text for (int i = 7; i >= 0; i--, y++) { uint mask = (1 << i); screen.Print(x, y, "b%d %-6s %d %d %s", i, gpipname[i], - (mfp.gpip & mask) ? 1 : 0, - (mfp.aer & mask) ? 1 : 0, - (mfp.ddr & mask) ? "OUT" : "IN"); + (mfp_.gpip & mask) ? 1 : 0, + (mfp_.aer & mask) ? 1 : 0, + (mfp_.ddr & mask) ? "OUT" : "IN"); } } @@ -563,6 +587,21 @@ MFPDevice::timer_vector[4] = { MFP::INTR_TIMER_D, }; +// IER[AB] に data をセットする +void +MFPDevice::SetIER(uint8& ier, uint8& ipr, uint32 data) +{ + // clr は、data によって %1 -> %0 に変化するビットだけを立てたもの。 + uint8 clr = ier & (ier ^ data); + + // IER を下げたところは IPR も下げる。(紙資料 p4-4) + ipr &= ~clr; + + // どちらにしても新しい IER によって信号線の状態は変える。 + ier = data; + ChangeInterrupt(); +} + // TxCR をセットする void MFPDevice::SetTCR(int ch, uint32 data) @@ -586,11 +625,11 @@ MFPDevice::SetTCR(int ch, uint32 data) // していたが、イベントを停止すると求められなくなるので、イベント // 停止前に求めておく。 uint64 period = prescale_ns[prev]; - uint64 now = gScheduler->GetVirtTime(); + uint64 now = scheduler->GetVirtTime(); mfp.tdr[ch] = ((event[ch].vtime - now) + period - 1) / period; // でイベント停止 - gScheduler->StopEvent(event[ch]); + scheduler->StopEvent(event[ch]); return; } else if (prev == 8) { // イベントカウントモードから停止しても何も起きないはず @@ -602,17 +641,14 @@ MFPDevice::SetTCR(int ch, uint32 data) } else if (data < 8) { // タイマー開始 - // TDR をリロード(?) - mfp.tdr[ch] = mfp.initial_tdr[ch]; - // タイマースタート + // TDR は触らずイベントを開始。 uint64 period = prescale_ns[mfp.tcr[ch]]; - uint count = mfp.tdr[ch] ?: 256; - event[ch].time = period * count; - gScheduler->RestartEvent(event[ch]); + event[ch].time = period * mfp.tdr[ch]; + scheduler->RestartEvent(event[ch]); putlog(1, "Timer-%c Start Delay mode(%u x %u.%uusec)", ch + 'A', - count, + mfp.tdr[ch], (uint)period / 1000, (uint)(period / 100) % 10); return; @@ -621,7 +657,7 @@ MFPDevice::SetTCR(int ch, uint32 data) // イベントカウントモード // TDR をロードする - mfp.initial_tdr[ch] = mfp.tdr[ch]; + mfp.next_tdr[ch] = mfp.tdr[ch]; putlog(1, "Timer-%c Event count mode", ch + 'A'); return; @@ -641,19 +677,20 @@ MFPDevice::GetTDR(int ch) const if (tcr == 0) { // 停止中なら tdr に現在値が保持されている - return mfp.tdr[ch]; + return mfp.tdr[ch] & 0xff; } else if (tcr < 8) { // ディレイモード動作中なら現在値は保持していないので、 // プリスケーラと終了時刻から算出。 uint64 period = prescale_ns[tcr]; - uint64 now = gScheduler->GetVirtTime(); - return ((event[ch].vtime - now) + period - 1) / period; + uint64 now = scheduler->GetVirtTime(); + uint8 data = ((event[ch].vtime - now) + period - 1) / period; + return data; } else { // イベントモード、パルス幅測定モードは未対応 - return mfp.tdr[ch]; + return mfp.tdr[ch] & 0xff; } } @@ -664,20 +701,23 @@ MFPDevice::SetTDR(int ch, uint32 data) { uint8& tcr = mfp.tcr[ch]; + if (data == 0) { + data = 256; + } + mfp.next_tdr[ch] = data; + if (tcr == 0) { - // 停止中の書き込みは両方更新 + // 停止中の書き込みはメインカウンタも更新。 mfp.tdr[ch] = data; - mfp.initial_tdr[ch] = data; } else if (tcr < 8) { - // ディレイモード動作中の書き込み。 - // メインカウンタは影響を受けず、次回値を更新するだけ。 - mfp.initial_tdr[ch] = data; + // ディレイモード動作中の書き込みは + // メインカウンタは影響を受けない。 } else { // イベントモード、パルス幅測定モードは未対応 putlog(0, "Write T%cDR $%02x during unsupported mode (NOT IMPLEMENTED)", - ch + 'A', data); + ch + 'A', mfp.next_tdr[ch]); } } @@ -688,7 +728,7 @@ MFPDevice::TimerCallback(Event& ev) int& ch = ev.code; // TDR がゼロになったところで呼ばれるので、TDR をリロードする - mfp.tdr[ch] = mfp.initial_tdr[ch]; + mfp.tdr[ch] = mfp.next_tdr[ch]; // 割り込みを上げる SetInterrupt(timer_vector[ch]); @@ -699,8 +739,8 @@ MFPDevice::TimerCallback(Event& ev) // period が1メインパルス期間 // time が1タイムアウトパルス期間 uint64 period = prescale_ns[mfp.tcr[ch]]; - ev.time = period * (mfp.tdr[ch] ?: 256); - gScheduler->RestartEvent(ev); + ev.time = period * mfp.tdr[ch]; + scheduler->RestartEvent(ev); } // HSync 入力 @@ -723,7 +763,7 @@ MFPDevice::SetVDisp(bool vdisp) mfp.tdr[0]--; if (mfp.tdr[0] == 0) { // 0 になったらリロード - mfp.tdr[0] = mfp.initial_tdr[0]; + mfp.tdr[0] = mfp.next_tdr[0]; SetInterrupt(MFP::INTR_TIMER_A); } @@ -840,8 +880,7 @@ MFPDevice::ClearBF() // 実際のハードウェアでは、キーボードが本体 MFP が Ready になるのを待って // いるが、それに相当。 // システムポートの KEYCTRL はキーボード側で処理している。 - X68030Keyboard *x68kkbd = dynamic_cast(gKeyboard); - x68kkbd->Ready(); + keyboard->Ready(); } // UDR への書き込み @@ -849,7 +888,7 @@ void MFPDevice::SetUDR(uint32 data) { if ((mfp.tsr & MFP::TSR_TE)) { - gKeyboard->Command(data); + keyboard->Command(data); } } @@ -898,7 +937,7 @@ MFPDevice::Rx(uint32 data) // ので、まだ Buffer Full は立てない。 key_event.code = data; - gScheduler->RestartEvent(key_event); + scheduler->RestartEvent(key_event); } // キーボードからのデータが受信し終わった頃に呼ばれるイベント。 @@ -953,28 +992,46 @@ MFPDevice::ChangeInterrupt() // ペンディングの割り込みがあればアサートされる、 // IPR をクリアするか IMR をクリアすると /IRQ はネゲートされる。 bool irq = ((mfp.ipr.w & mfp.imr.w) != 0); - gInterrupt->ChangeINT(this, irq); + interrupt->ChangeINT(this, irq); } // 割り込みアクノリッジ -int +busdata MFPDevice::InterruptAcknowledge() { + busdata data; + + // IPR が誰も立ってなければ、無視するっぽい? + // 実際には MFP がバスエラーを出しているのではなく、MFP は俺知らない + // と思って放置してると上位回路 (たぶん SAKI ちゃん) がタイムアウトして + // MPU にバスエラー応答する。 uint32 pending = mfp.ipr.w; + if (__predict_false(pending == 0)) { + data.SetBusErr(); + } else { + // ch はこの時点で IPR に立ってる一番優先度の高いチャンネルのビット + int ch = 31 - __builtin_clz(pending); + uint b = (1U << ch); - // ch はこの時点で IPR に立ってる一番優先度の高いチャンネルのビット - // XXX IPR にビット立ってなかったらどうなる? - int ch = 31 - __builtin_clz(pending); - uint b = (1U << ch); + // MPU にベクタを引き渡したので Pending をクリア + mfp.ipr.w &= ~b; + ChangeInterrupt(); - // MPU にベクタを引き渡したので Pending をクリア - mfp.ipr.w &= ~b; - ChangeInterrupt(); + // ソフトウェア EOI なら In Service を立てる + if (mfp.vr_s) { + mfp.isr.w |= b; + } - // ソフトウェア EOI なら In Service を立てる - if (mfp.vr_s) { - mfp.isr.w |= b; + data = mfp.vr_vec + ch; } - return mfp.vr_vec + ch; + // /IEI ネゲート時の /IACK と /IEI の関係がいまいち分からないけど、 + // とりあえず /IACK がアサートされてから 3 MFP クロック後に + // データバスにベクタを供給、次の 1 MFP クロックで DTACK 応答。 + // で次の 1 MFP クロック以内に CPU が /IACK を下げるとして、 + // 計 5 MFP クロック (= 1.25_usec) としておく。 + const busdata intack_wait = busdata::Wait(5 * 250_nsec); + data |= intack_wait; + + return data; }