--- nono/vm/mpu680x0.cpp 2026/04/29 17:05:03 1.1.1.11 +++ nono/vm/mpu680x0.cpp 2026/04/29 17:05:13 1.1.1.14 @@ -4,40 +4,51 @@ // Licensed under nono-license.txt // -// MPU (m680x0) +// +// MPU (MC68030) +// #include "mpu680x0.h" #include "config.h" +#include "debugger.h" +#include "debugger_private.h" #include "m68030bus.h" #include "m68030excep.h" #include "mainapp.h" +#include "scheduler.h" +#include "sync.h" #include "uimessage.h" #include "vm.h" -#include +#include + +static void mpu680x0_reset_inst(void *); +static void mpu680x0_halt(void *); // コンストラクタ MPU680x0Device::MPU680x0Device() : inherited("MPU(68030)") { - cpu = m68030_init(); + cpu = m68030_init(this); - // イベントコールバック設定 - event.SetName("MPU"); - event.func = (DeviceCallback_t)&MPU680x0Device::Callback; + // 割り込みイベントコールバック設定 + intr_event.func = ToEventCallback(&MPU680x0Device::InterruptCallback); + intr_event.Regist("MPU Interrupt"); // レジスタモニター - reg_monitor.func = (MonitorCallback_t)&MPU680x0Device::MonitorUpdateReg; + reg_monitor.func = ToMonitorCallback(&MPU680x0Device::MonitorUpdateReg); reg_monitor.SetSize(79, 16); reg_monitor.Regist(ID_MONITOR_MPUREG); // ATC モニター - atc_monitor.func = (MonitorCallback_t)&MPU680x0Device::MonitorUpdateATC; + atc_monitor.func = ToMonitorCallback(&MPU680x0Device::MonitorUpdateATC); #if !defined(ATC_SINGLE) atc_monitor.SetSize(131, m68030ATCTable::LINES + 3); #else atc_monitor.SetSize(31, 24); #endif atc_monitor.Regist(ID_MONITOR_MPUATC); + + SetTrace(false); } // デストラクタ @@ -52,68 +63,161 @@ MPU680x0Device::Init() if (inherited::Init() == false) { return false; } - // MPU クロックは親 Init() で読み込んで、ここで cpu にセットする - m68030_set_clockspeed(cpu, clock_khz); - // 割り込み時のディレイ - intdelay = cpu->Cycle2Vtime(4); + + // 割り込み時のディレイ。 + // + // 外部デバイスが 68030 の IPL0-2 をアサートしてから、68030 がそれを + // 認識(確定)するのに 1 ~ 2 クロックかかる。 + // その後レベルを比較するのは次のアップエッジ。 + // その後 /IPEND をアサートするのはその次のアップエッジ。 + // 合わせると、IPL0-2 が変化してから /IPEND がアサートされるまでは + // 2 ~ 3 クロックかかる。(Figure 8-4) + intr_event.time = 3 * clock_nsec; // FPU (6888x) - if (gMainApp.GetVMType() == VMTYPE_LUNA1) { + if (gMainApp.GetVMType() == VMType::LUNA1) { // LUNA は 68881 固定 (設定は見ない) cpu->has_fpu = 1; } else { // X68k は設定による - const ConfigItem& item = gConfig->Find("mpu-has-fpu"); - cpu->has_fpu = item.AsInt(); - if (cpu->has_fpu < 0 || cpu->has_fpu > 2) { + const ConfigItem& item = gConfig->Find("fpu-type"); + std::string type = item.AsString(); + if (type == "none" || type == "0") { + cpu->has_fpu = 0; + } else if (type == "68881" || type == "1") { + cpu->has_fpu = 1; + } else { item.Err(); return false; } } - cpu->halt_callback = &MPU680x0Device::HaltCallback; - cpu->halt_arg = NULL; + cpu->resetop_callback = mpu680x0_reset_inst; + cpu->resetop_arg = NULL; + cpu->halt_callback = mpu680x0_halt; + cpu->halt_arg = this; return true; } -bool -MPU680x0Device::PowerOn() +// リセット +void +MPU680x0Device::ResetHard(bool poweron) { - m68030_power_on(cpu); - return true; + if (poweron) { + m68030_power_on(cpu); + } + resetting = true; + + // リセット例外を 520 クロック後に起こす。 + // 520 は、電源オン時、Vcc が最小動作規定値に達してから最低 520 クロック + // の間アサートしなければならないという値であって、実際これだけかかる + // とかいう値ではない。が、こちら側の都合で、この後実行される RAM の + // ResetHard() がブートページを用意した後で m68030_exception_reset() で + // SP, PC をフェッチするという順序にしないといけないため、リセット例外が + // 起動するまでに時間がかかるというところを都合よく真似ておく。 + + exec_event.func = ToEventCallback(&MPU680x0Device::ResetCallback); + exec_event.time = 520 * clock_nsec; + exec_event.SetName("MPU Reset"); + gScheduler->RestartEvent(exec_event); } +// リセット例外コールバック。 +// MPU リセットから規定時間後に呼ばれる。 void -MPU680x0Device::ResetHard() +MPU680x0Device::ResetCallback(Event& ev) { - // リセット例外 - m68030_request_reset_exception(cpu); + resetting = false; + intr_pending = false; + + // リセット例外を実行 + int cycle = m68030_exception_reset(cpu); + + // コールバックを ResetCallback から Exec* に差し替える前のここで + // トレース状態を初期化する。 + SetTrace(gDebugger->IsTrace()); + + // 以降は通常走行。 + if (cpu->state == m68kcpu::CPU_STATE_NORMAL) { + exec_event.func = exec_short; + exec_event.time = cycle * clock_nsec; + exec_event.SetName("MPU Execute"); + gScheduler->RestartEvent(exec_event); + } else { + // リセット例外でダブルバスフォールトならここで停止。 + } } -// イベントコールバック -// ev.code は -// 0x0 〜 0x7: 新しい割り込みレベルを通知 (0-7) +// 通常の1命令実行のコールバック +// 割り込みチェックなどを省いたショート版。 void -MPU680x0Device::Callback(Event& ev) +MPU680x0Device::ExecShort(Event& ev) { - int level = ev.code; - m68030_interrupt(cpu, level); + int cycle = m68030_exec(cpu); + + ev.time = cycle * clock_nsec; + gScheduler->StartEvent(ev); +} + +// 割り込みチェック、STOP 処理を含んだ完全な実行サイクルのコールバック。 +// SR の I2:I0 が変わった直後などに呼ばれる。 +// ここでは割り込みをチェックし、必要なら割り込み処理を起動、 +// そうでなければ通常の命令実行を行う。 +void +MPU680x0Device::ExecFull(Event& ev) +{ + intr_pending |= m68030_check_interrupt(cpu); + + if (intr_pending) { + // 割り込み処理を起動。 + + intr_pending = false; + + // 前の命令実行後となるここで割り込みを起動 + int cycle = m68030_exception_interrupt(cpu); + + ev.time = cycle * clock_nsec; + gScheduler->StartEvent(ev); + } else { + if (cpu->state == m68kcpu::CPU_STATE_NORMAL) { + // コールバックを通常処理に戻してから... + exec_event.func = exec_short; + + // この場で次の命令を実行。 + ExecShort(ev); + } else { + // STOP ならここで停止。HALT はそもそも来ないはず。 + } + } } -// MPU を vtime [nsec] 分実行する。 -// 戻り値は outer フラグ。 -uint32 -MPU680x0Device::Run(uint32 vtime) +// トレース実行 +void +MPU680x0Device::ExecTrace(Event& ev) { - return m68030_run(cpu, vtime); + gDebugger->Exec(); + ExecFull(ev); } -// 現在の仮想時刻 [nsec] を取得する -uint64 -MPU680x0Device::GetVirtTime() const +// トレース状態を設定する +void +MPU680x0Device::SetTrace(bool enable) { - return m68030_get_vtime(cpu); + if (enable) { + exec_short = ToEventCallback(&MPU680x0Device::ExecTrace); + exec_full = ToEventCallback(&MPU680x0Device::ExecTrace); + } else { + exec_short = ToEventCallback(&MPU680x0Device::ExecShort); + exec_full = ToEventCallback(&MPU680x0Device::ExecFull); + } + exec_event.func = exec_full; + + // STOP 命令中にオンにするには、停止しているイベントを起こす。 + // STOP 命令中にオフにする場合は、何もしなくていい。 + if (enable && exec_event.IsRunning() == false) { + gScheduler->StartEvent(exec_event); + } } // 割り込みレベルが変わったことを MPU に通知。 @@ -122,23 +226,63 @@ MPU680x0Device::Interrupt(int level) { assertmsg(0 <= level && level <= 7, "level=%d", level); - // 外部デバイスが 68030 の IPL0-2 をアサートしてから、68030 がそれを - // 認識(確定)するのに 2クロックかかる。ここでレベルを比較して割り込みを - // 上げる場合は /IPEND をアサートする。 - // そこからさらに2クロック後以降の命令境界で割り込みを受け付ける(?) - // らしい? - // この 4クロック待つところはイベントスケジューラがある VM 側で担当する。 - // 本来、レベル比較はここから2クロック後に行われるはずだが、ちょっと省略。 - // 今はコア側でペンディング処理をしているのでこっち側は起きたことを全部 - // 通知する。 - // 割り込みレベルが 0 に下がる時にはこのディレイは不要のはず。 - if (level != 0) { - event.time = intdelay; - } else { - event.time = 0; + intr_event.code = level; + if (intr_event.IsRunning() == false) { + gScheduler->StartEvent(intr_event); } - event.code = level; - event.Start(); +} + +// 外部割り込みイベントコールバック。 +// 割り込みレベルが変わったことをコアに通知する。 +// ev.code 0-7 は新しい割り込みレベル。 +void +MPU680x0Device::InterruptCallback(Event& ev) +{ + int level = ev.code; + + if (m68030_interrupt(cpu, level)) { + // 通知した結果割り込みが受け付けられたので、 + // この命令終了時のコールバックを割り込み処理起動に差し替える。 + MakeNextFull(); + intr_pending = true; + + // STOP 状態だったらここでイベントを開始する + if (cpu->state == m68kcpu::CPU_STATE_STOP) { + // STOP から抜ける時間? + exec_event.time = 1 * clock_nsec; + gScheduler->StartEvent(exec_event); + } + } +} + +// 次の命令境界で割り込みチェックを行う。 +// (割り込みマスクを変更する命令の実行で呼ばれる) +void +MPU680x0Device::CheckIntr() +{ + MakeNextFull(); +} + +// 次の命令境界は Full にする。 +void +MPU680x0Device::MakeNextFull() +{ + exec_event.func = exec_full; +} + +// コアからの動作変更通知 +/*static*/ void +MPU680x0Device::NotifyCPUMode(uint32 new_mode) +{ + // new_mode (m68kcpu::CPU_STATE_*) と + // RequestCPUMode() の引数 Sync::SCHED_CPU_* は + // 実は同じ値なのでそのまま渡してよいことにする。 + static_assert(m68kcpu::CPU_STATE_NORMAL == Sync::SCHED_CPU_NORMAL, ""); + static_assert(m68kcpu::CPU_STATE_STOP == Sync::SCHED_CPU_STOP, ""); + static_assert(m68kcpu::CPU_STATE_HALT == Sync::SCHED_CPU_HALT, ""); + + // 通知 (同期のため) + gSync->RequestCPUMode(new_mode); } // A-Line 命令エミュレーションのコールバックを指定。 @@ -157,10 +301,28 @@ MPU680x0Device::SetFLineCallback(bool (* cpu->fline_arg = arg; } -// MPU ホールト時の MPU からのコールバック関数 -/*static*/ void -MPU680x0Device::HaltCallback(void *arg) +// RESET 命令の CPU からのコールバック関数 +static void +mpu680x0_reset_inst(void *arg) +{ + gVM->ResetByMPU(); +} + +// ホールト時の CPU からのコールバック関数 +static void +mpu680x0_halt(void *arg) +{ + ((MPU680x0Device *)arg)->Halt(); +} + +// ホールト状態 +void +MPU680x0Device::Halt() { + // 動作停止 + gScheduler->StopEvent(exec_event); + + // UI に通知 (メッセージボックス表示とか) UIMessage::Post(UIMessage::HALT); } @@ -234,15 +396,17 @@ MPU680x0Device::MonitorUpdateReg(Monitor { m68kreg reg; uint32 ppc; + uint32 state; int x; int y; screen.Clear(); // ローカルにコピー - // ただし PPC だけ reg 構造体ではなく cpu クラス側にある + // ただし一部は reg ではなく cpu クラス本体にある reg = cpu->reg; ppc = cpu->ppc; + state = cpu->state; // データレジスタ、アドレスレジスタ for (int i = 0; i < 4; i++) { @@ -295,12 +459,18 @@ MPU680x0Device::MonitorUpdateReg(Monitor screen.Print(x, 3, "CAAR:%08x", reg.caar); // XXX どこに置くのがいいか - if (reg.state == 0) { + switch (state) { + case m68kcpu::CPU_STATE_NORMAL: screen.Puts(25, 4, "State: Running"); - } else if (reg.state == CPU_REQ_STOP) { + break; + case m68kcpu::CPU_STATE_STOP: screen.Puts(25, 4, "State: STOP instruction"); - } else if (reg.state == CPU_REQ_HALT) { + break; + case m68kcpu::CPU_STATE_HALT: screen.Puts(25, 4, TA::On, "State: Double Bus Fault"); + break; + default: + __unreachable(); } // MMU @@ -357,12 +527,6 @@ MPU680x0Device::MonitorUpdateReg(Monitor ".DBL", ".???", }; - static const char * const rmstr[] = { - "Near", - "Zero", - "Minus", - "Plus", - }; uint32 fpcr = reg.fpframe.fpf_fpcr; screen.Print(x, y++, "FPCR:%04x", fpcr); screen.Puts(x + 2, y, TA::OnOff(fpcr & 0x8000), "BS"); @@ -470,14 +634,14 @@ MPU680x0Device::MonitorUpdateATC(Monitor // ヘッドのヒット率 screen.Puts(x + 0, i + 2, "head"); r = (double)table->hit_head * 100 / total; - if (!isnan(r)) { + if (!std::isnan(r)) { screen.Print(x + 5, i + 2, "%4.1f%%", r); } // 配列のヒット率とミス率 screen.Puts(x + 17, i + 2, "miss"); for (i = 0; i < countof(table->hit); i++) { r = (double)(table->hit[i] * 100) / total; - if (!isnan(r)) { + if (!std::isnan(r)) { screen.Print(x + 22, i + 2, "%4.1f%%", r); } } @@ -519,10 +683,3 @@ MPU680x0Device::MonitorUpdateATC(Monitor } #endif } - -// CPU 側からのコールバック。RESET 命令ハンドラ -void -m68030_reset_inst(m68kcpu *cpu) -{ - gVM->ResetByMPU(); -}