--- nono/vm/spc.cpp 2026/04/29 17:05:13 1.1.1.13 +++ nono/vm/spc.cpp 2026/04/29 17:05:24 1.1.1.15 @@ -22,21 +22,13 @@ event.func = ToEventCallback(&SPCDevice::func_); \ event.time = time_; \ event.SetName(name_); \ - gScheduler->RestartEvent(event); \ + scheduler->RestartEvent(event); \ } while (0) -// グローバル参照用 -SPCDevice *gSPC; - // コンストラクタ SPCDevice::SPCDevice() - : inherited("SPC") + : inherited(OBJ_SPC) { - // これらは XIOSpace で必要なので、LUNA ではダミー。 - // もうちょっといい方法を考えたほうがいい。 - devaddr = 0xe96000; - devlen = 0x2000; - SetConfigName("spc0"); myid = -1; @@ -56,12 +48,14 @@ SPCDevice::SPCDevice() // デストラクタ SPCDevice::~SPCDevice() { - gSPC = NULL; } +// 初期化 bool SPCDevice::Init() { + // inherited::Init() は少し下で呼んでいる + // 実行中に別インスタンスを頻繁に参照しないようにここで vmtype を覚えておく vmtype = gMainApp.GetVMType(); @@ -85,28 +79,28 @@ SPCDevice::Init() // 入力クロックは機種によって異なる。 // X68k は 5MHz が入っている。 - // LUNA-I はおそらく 6.144MHz だと思われる。LUNA88K は未確認。 + // LUNA-I はおそらく 6.144MHz だと思われる。LUNA-88K は未確認。 const ConfigItem& item = gConfig->Find("!spc-tCLF"); t_CLF = item.AsInt(); // アクセスウェイト if (vmtype == VMType::X68030) { // InsideOut p.135 - read_wait = 18; - write_wait = 19; + read_wait = busdata::Wait(18 * 40_nsec); + write_wait = busdata::Wait(19 * 40_nsec); } else { // 他機種は未調査だけど、判明するまでの間とりあえず // ノーウェイトでない適当な値を入れておく。 - read_wait = 18; - write_wait = 19; + read_wait = busdata::Wait(18 * 40_nsec); + write_wait = busdata::Wait(19 * 40_nsec); } // 割り込み信号線の接続先 if (vmtype == VMType::X68030) { // X68030 は PEDEC (I/O コントローラ) にカスケード - interrupt = gPEDEC; + interrupt = GetPEDECDevice(); } else { - interrupt = gInterrupt; + interrupt = GetInterruptDevice(); } monitor.SetSize(70, 15 + connected_devices.size()); @@ -136,138 +130,146 @@ SPCDevice::ResetHard(bool poweron) // 内部状態 MakeIntsMask(); + ChangeInterrupt(); prev_ints = (uint32)-1; prev_ssts = (uint32)-1; set_atn_in_selection = false; transfer_command_running = false; - gScheduler->StopEvent(event); - gScheduler->StopEvent(event_tc); + scheduler->StopEvent(event); + scheduler->StopEvent(event_tc); last_busfree_time = 0; } -uint64 +busdata SPCDevice::Read(uint32 offset) { - uint8 data; - - gMPU->AddCycle(read_wait); + busdata data; switch (offset) { case SPC::BDID: data = 1 << myid; - putlog(3, "BDID -> $%02x", data); - return data; + putlog(3, "BDID -> $%02x", data.Data()); + break; case SPC::SCTL: data = spc.sctl; - putlog(3, "SCTL -> $%02x", data); - return data; + putlog(3, "SCTL -> $%02x", data.Data()); + break; case SPC::SCMD: data = spc.scmd; - putlog(3, "SCMD -> $%02x", data); - return data; + putlog(3, "SCMD -> $%02x", data.Data()); + break; case SPC::TMOD: // MB89352 には存在しないレジスタ。 // 下位2ビットは %0 らしいので意味はないだろうけど真似ておく。 - return 0xfc; + data = 0xfc; + break; case SPC::INTS: data = GetINTS(); if (loglevel >= 3) { + uint32 new_ints = data.Data(); if (event_tc.IsRunning()) { // イベント中は値が変わった時だけ表示 - if (prev_ints != data) { - putlogn("INTS -> $%02x", data); - prev_ints = data; + if (prev_ints != new_ints) { + putlogn("INTS -> $%02x", new_ints); + prev_ints = new_ints; } } else { - putlogn("INTS -> $%02x", data); + putlogn("INTS -> $%02x", new_ints); prev_ints = -1U; } } - return data; + break; case SPC::PSNS: data = GetPSNS(); - putlog(3, "PSNS -> $%02x", data); - return data; + putlog(3, "PSNS -> $%02x", data.Data()); + break; case SPC::SSTS: data = GetSSTS(); if (loglevel >= 3) { + uint32 new_ssts = data.Data(); if (event_tc.IsRunning()) { // イベント中は値が変わった時だけ表示 - if (prev_ssts != data) { - putlogn("SSTS -> $%02x", data); - prev_ssts = data; + if (prev_ssts != new_ssts) { + putlogn("SSTS -> $%02x", new_ssts); + prev_ssts = new_ssts; } } else { - putlogn("SSTS -> $%02x", data); + putlogn("SSTS -> $%02x", new_ssts); prev_ssts = -1U; } } - return data; + break; case SPC::SERR: data = GetSERR(); - putlog(3, "SERR -> $%02x", data); - return data; + putlog(3, "SERR -> $%02x", data.Data()); + break; case SPC::PCTL: data = GetPCTL(); - putlog(3, "PCTL -> $%02x", data); - return data; + putlog(3, "PCTL -> $%02x", data.Data()); + break; case SPC::MBC: putlog(0, "Read MBC (NOT IMPLEMENTED)"); - return 0x0f; + data = 0x0f; + break; case SPC::DREG: - return ReadDREG(); + data = ReadDREG(); + break; case SPC::TEMP: data = spc.temp_in; - putlog(3, "TEMP -> $%02x", data); - return data; + putlog(3, "TEMP -> $%02x", data.Data()); + break; case SPC::TCH: data = spc.tc >> 16; - putlog(3, "TCH -> $%02x", data); - return data; + putlog(3, "TCH -> $%02x", data.Data()); + break; case SPC::TCM: data = (spc.tc >> 8) & 0xff; - putlog(3, "TCM -> $%02x", data); - return data; + putlog(3, "TCM -> $%02x", data.Data()); + break; case SPC::TCL: data = spc.tc & 0xff; - putlog(3, "TCL -> $%02x", data); - return data; + putlog(3, "TCL -> $%02x", data.Data()); + break; case SPC::EXBF: // MB89352 には存在しないレジスタ - return 0xff; + data = 0xff; + break; default: - __unreachable(); + VMPANIC("corrupted offset=%d", offset); } + + data |= read_wait; + return data; } -uint64 +busdata SPCDevice::Write(uint32 offset, uint32 data) { - gMPU->AddCycle(write_wait); + busdata r; switch (offset) { case SPC::BDID: putlog(3, "BDID <- $%02x", data); if (myid == data) { // 同じなら何もしない - return 0; + break; } if (myid == -1) { // 初回なのでバスに参加 @@ -276,50 +278,52 @@ SPCDevice::Write(uint32 offset, uint32 d } else { // そうでなければ移動? putlog(0, "BDID change %d to %d (NOT IMPLEMENTED)", myid, data); - return 0; } - return 0; + break; case SPC::SCTL: WriteSCTL(data); - return 0; + break; case SPC::SCMD: WriteSCMD(data); - return 0; + break; case SPC::TMOD: // MB89352 には存在しないレジスタ - return 0; + break; case SPC::INTS: WriteINTS(data); - return 0; + break; case SPC::SDGC: spc.sdgc = data; putlog(3, "SDGC <- $%02x", spc.sdgc); - return 0; + break; case SPC::SSTS: // XXX 書き込みはどうなる? - return 0; + break; case SPC::SERR: // XXX 書き込みはどうなる? - return 0; + break; case SPC::PCTL: WritePCTL(data); - return 0; + break; case SPC::MBC: // XXX 書き込みはどうなる? - return 0; + break; case SPC::DREG: + // ここだけ Retry があるので戻り値に注意。 putlog(3, "DREG <- $%02x", data); - return WriteDREG(data); + r = WriteDREG(data); + r |= write_wait; + return r; case SPC::TEMP: spc.temp_out = data; @@ -340,31 +344,33 @@ SPCDevice::Write(uint32 offset, uint32 d if (GetIO() == false) { SetData(spc.temp_out); } - return 0; + break; case SPC::TCH: spc.tc = (spc.tc & 0x00ffff) | (data << 16); putlog(3, "TCH <- $%02x (TC=$%06x)", data, spc.tc); - return 0; + break; case SPC::TCM: spc.tc = (spc.tc & 0xff00ff) | (data << 8); putlog(3, "TCM <- $%02x (TC=$%06x)", data, spc.tc); - return 0; + break; case SPC::TCL: spc.tc = (spc.tc & 0xffff00) | data; putlog(3, "TCL <- $%02x (TC=$%06x)", data, spc.tc); - return 0; + break; case SPC::EXBF: // MB89352 には存在しないレジスタ - return 0; + break; default: - __unreachable(); + VMPANIC("corrupted offset=%d", offset); } + + return write_wait; } -uint64 +busdata SPCDevice::Peek(uint32 offset) { switch (offset) { @@ -406,7 +412,7 @@ SPCDevice::Peek(uint32 offset) // すべてバスエラーになってしまう。 if (vmtype == VMType::X68030) { if ((spc.scmd & SPC::SCMD_PROGRAM)) { - return (uint64)-1; + return busdata::BusErr; } } return spc.dreg.Peek(0); @@ -428,7 +434,7 @@ SPCDevice::Peek(uint32 offset) return 0xff; default: - __unreachable(); + return 0xff; } } @@ -849,7 +855,7 @@ SPCDevice::WriteINTS(uint32 data) } else { event_tc.func = ToEventCallback(&SPCDevice::SelectionTimeout); event_tc.time = spc.tc * t_CLF * 2; - gScheduler->RestartEvent(event_tc); + scheduler->RestartEvent(event_tc); } } } @@ -988,23 +994,25 @@ SPCDevice::GetPCTL() const // DREG レジスタの読み込み // (副作用があるので Peek 系からは呼ばないこと) -uint64 +busdata SPCDevice::ReadDREG() { - uint32 data; + busdata data; // X68k は SPC の DREQ 信号から DTACK 信号を作っているため、 // DREQ 信号を出さないプログラム転送モードでは DREG へのアクセスが // すべてバスエラーになってしまう。 if (vmtype == VMType::X68030) { if ((spc.scmd & SPC::SCMD_PROGRAM)) { - return (uint64)-1; + data.SetBusErr(); + return data; } else { // DMA 転送で DREQ が出てないとき DTACK を出さないのを - // -2 を返すことで表現している + // Retry を返すことで表現している。 if (spc.dreg.Length() == 0) { putlog(3, "DREG empty during DMA transferring"); - return (uint64)-2; // DTACK まだ出てないよ + data.SetRetry(); + return data; } } } @@ -1020,9 +1028,9 @@ SPCDevice::ReadDREG() data = spc.dreg.Dequeue(); if (loglevel >= 4) { putlogn("DREG -> $%02x (q=%d TC=0x%x)", - data, spc.dreg.Length(), spc.tc); + data.Data(), spc.dreg.Length(), spc.tc); } else { - putlog(3, "DREG -> $%02x", data); + putlog(3, "DREG -> $%02x", data.Data()); } // この読み出しが最後だったらこれで転送完了。 if (spc.tc == 0 && spc.dreg.Length() == 0) { @@ -1038,7 +1046,7 @@ SPCDevice::ReadDREG() if ((spc.scmd & SPC::SCMD_PROGRAM)) { // XXX プログラム転送中ならどうなる? data = 0xff; - putlog(3, "DREG -> $%02x (empty)", data); + putlog(3, "DREG -> $%02x (empty)", data.Data()); } else { // DMA 転送 // DMA 側でリトライするのでここには来ないはず @@ -1052,20 +1060,24 @@ SPCDevice::ReadDREG() } // DREG レジスタへの書き込み -uint64 +busdata SPCDevice::WriteDREG(uint32 data) { + busdata r; + // X68k は SPC の DREQ 信号から DTACK 信号を作っているため、 // DREQ 信号を出さないプログラム転送モードでは DREG へのアクセスが // すべてバスエラーになってしまう。 if (vmtype == VMType::X68030) { if ((spc.scmd & SPC::SCMD_PROGRAM)) { - return (uint64)-1; + r.SetBusErr(); + return data; } else { // DMA 転送で DREQ が出てないとき DTACK を出さないのを - // -2 を返すことで表現している + // Retry を返すことで表現している。 if (spc.dreg.IsFull()) { - return (uint64)-2; + r.SetRetry(); + return r; } } } @@ -1076,14 +1088,21 @@ SPCDevice::WriteDREG(uint32 data) // DREG キューに入れて.. if (spc.dreg.Enqueue(data) == false) { - VMPANIC("DREG キューオーバーフロー"); + // XXX キューが一杯だったらどうなるのかは書いてないので分からない。 + // a) キューを押し出して先頭を破棄する + // b) キューの末尾を破棄して新着データで上書きする + // c) キューの内容は変わらず新着データを破棄する + // どれで実装しても「この時何かしら失われるデータがある」こと自体は + // 再現出来ているはずなので、ここではひとまず a) の動作にしておく。 + putlog(0, "DREG Queue overflow"); + spc.dreg.EnqueueForce(data); } // ハードウェア転送中で REQ が来てれば送信。 if (transfer_command_running && GetREQ()) { // XXX 時間は適当 CallAfter(HardwareTransfer, "SPC WriteDREG to Transfer", t_CLF); } - return 0; + return r; } // バスフリーフェーズに移行する @@ -1193,7 +1212,7 @@ SPCDevice::SelectCommand() // t_FRID/t_BFBL はたぶん今からではなくバスフリーになった時点から起算。 // t_FRID と t_BFBL は同じ式なのでここでは t_BFBL で統一。 uint64 t_BFBL = (6 + spc.GetTCL()) * t_CLF; // Min - uint64 now = gScheduler->GetVirtTime(); + uint64 now = scheduler->GetVirtTime(); if ((last_busfree_time + t_BFBL) > now) { wait = (last_busfree_time + t_BFBL) - now; } else { @@ -1209,7 +1228,7 @@ SPCDevice::SelectCommand() if (N != 0) { event_tc.func = ToEventCallback(&SPCDevice::SelectionTimeout); event_tc.time = (N + 15) * t_CLF * 2; - gScheduler->RestartEvent(event_tc); + scheduler->RestartEvent(event_tc); } if ((spc.sctl & SPC::SCTL_ARBIT_EN)) { @@ -1375,7 +1394,7 @@ SPCDevice::BusFreeCB(int id) putlog(4, "BusFree Callback"); inherited::BusFreeCB(id); - last_busfree_time = gScheduler->GetVirtTime(); + last_busfree_time = scheduler->GetVirtTime(); transfer_command_running = false; spc.ssts &= ~(SPC::SSTS_CONN_INIT | SPC::SSTS_CONN_TARG); @@ -1403,7 +1422,7 @@ SPCDevice::SelectionAckCB() NegateSEL(); // タイムアウトイベントを停止 - gScheduler->StopEvent(event_tc); + scheduler->StopEvent(event_tc); spc.ssts &= ~SPC::SSTS_SPC_BUSY; spc.ssts |= SPC::SSTS_CONN_INIT;