--- nono/vm/crtc.cpp 2026/04/29 17:05:09 1.1.1.8 +++ nono/vm/crtc.cpp 2026/04/29 17:05:32 1.1.1.13 @@ -14,38 +14,65 @@ // +$80..+$ff は何かが読める。 // 仕様は $e80480.w に1ポートあるので、これがワードで全域にミラーされている? // 全体としては この $800 バイトずつが繰り返して見える。 +// +// CRTC はデバイスの配置としては BusIO_B 相当で、バイトアクセスも行えるが、 +// 内部はバイトレジスタではなくワード単位を基本構造としている。そのため +// BusIO は使わず自力で処理する。ログの観点からもこのほうが都合がいい。 #include "crtc.h" #include "mfp.h" -#include "mpu.h" -#include "renderer.h" +#include "monitor.h" #include "scheduler.h" #include "tvram.h" +#include "videoctlr.h" -// グローバル参照用 -CRTCDevice *gCRTC; +// InsideOut p.135 +const busdata wait = busdata::Wait(9 * 40_nsec); // コンストラクタ CRTCDevice::CRTCDevice() - : inherited("CRTC") + : inherited(OBJ_CRTC) { - devaddr = 0xe80000; - devlen = 0x2000; - - hsync_event.func = ToEventCallback(&CRTCDevice::HSyncCallback); - hsync_event.Regist("CRTC H-Sync"); - vdisp_event.func = ToEventCallback(&CRTCDevice::VDispCallback); - vdisp_event.Regist("CRTC V-DISP"); - - monitor.func = ToMonitorCallback(&CRTCDevice::MonitorUpdate); - monitor.SetSize(60, 5); - monitor.Regist(ID_MONITOR_CRTC); + monitor = gMonitorManager->Regist(ID_MONITOR_CRTC, this); + monitor->func = ToMonitorCallback(&CRTCDevice::MonitorUpdate); + monitor->SetSize(62, 14); } // デストラクタ CRTCDevice::~CRTCDevice() { - gCRTC = NULL; +} + +// 初期化 +bool +CRTCDevice::Init() +{ + if (inherited::Init() == false) { + return false; + } + + mfp = GetMFPDevice(); + tvram = GetTVRAMDevice(); + videoctlr = GetVideoCtlrDevice(); + + hsync_event.func = ToEventCallback(&CRTCDevice::HSyncCallback); + hsync_event.SetName("CRTC H-Sync"); + vdisp_event.func = ToEventCallback(&CRTCDevice::VDispCallback); + vdisp_event.SetName("CRTC V-Disp"); + scheduler->RegistEvent(hsync_event); + scheduler->RegistEvent(vdisp_event); + + // ラスターコピーは実際には CRTC の機能ではなく TVRAM (VRAM チップ) の + // 機能だが、イベントの時間管理が発生するのは CRTC 側なのでここで管理。 + // X68030 搭載の VRAM チップは HM514402A だが、ここでは X68000 XVI 搭載の + // HM53461 の値を使用し、これが最小 190nsec * 2 らしいので + // とりあえず 400nsec としておく。 + raster_event.func = ToEventCallback(&CRTCDevice::RasterCallback); + raster_event.time = 400_nsec; + raster_event.SetName("CRTC Raster Copy"); + scheduler->RegistEvent(raster_event); + + return true; } // リセット @@ -59,99 +86,150 @@ CRTCDevice::ResetHard(bool poweron) // すぐに水平同期と垂直帰線期間を開始する hsync_event.time = 0; hsync_event.code = 1; - gScheduler->RestartEvent(hsync_event); + scheduler->RestartEvent(hsync_event); vdisp_event.time = 0; vdisp_event.code = 1; - gScheduler->RestartEvent(vdisp_event); + scheduler->RestartEvent(vdisp_event); } -uint64 -CRTCDevice::Read(uint32 offset) +/*static*/ inline uint32 +CRTCDevice::Decoder(uint32 addr) { - uint64 data; + return addr & 0x7ff; +} - gMPU->AddCycle(9); // InsideOut p.135 - - if (offset < 0x400 / 2) { // CRTC レジスタ - // 0x40 バイトでミラー - offset &= (0x40 / 2) - 1; - // R20, R21 のみ読み出し可能 - switch (offset) { - case 20: - data = crtc.r[offset]; - putlog(2, "R%02d -> $%04x", offset, (uint32)data); - break; - case 21: - // R21 へのアクセスは多いのでログレベルを上げておく - data = crtc.r[offset]; - putlog(3, "R%02d -> $%04x", offset, (uint32)data); - break; - default: - // それ以外は読み出し不可 ($00 が読める) - data = 0; - if (offset < countof(crtc.r)) { - putlog(3, "R%02d -> $%04x", offset, (uint32)data); - } else { - putlog(3, "$%06x -> $%04x", gMPU->GetPaddr(), (uint32)data); - } - break; +busdata +CRTCDevice::Read(busaddr addr) +{ + uint32 offset = Decoder(addr.Addr()); + busdata data = Peek2(offset); + + uint32 reqsize = addr.GetSize(); + if (reqsize == 1) { + if ((offset & 1) == 0) { + data >>= 8; + } else { + data &= 0xff; } + data |= BusData::Size1; + } else { + data |= BusData::Size2; + } - } else { // 動作ポート - if ((offset & (0x80 / 2)) == 0) { // +$00..+$7f - data = (uint64)-1; - } else { // +$80..+$ff - data = crtc.op; + if (__predict_false(loglevel >= 2)) { + if (offset < 0x400) { + // CRTC レジスタは 0x40 バイトでミラー + uint32 regno = (offset >> 1) & 0x1f; + if (regno < countof(crtc.r)) { + // R20, R21 のみ読み出し可能だが、 + // R21 へのアクセスは多いのでログレベルを上げておく。 + int lv = (regno == 21) ? 3 : 2; + if (reqsize == 1) { + putlog(lv, "R%02u.%c -> $%02x", regno, + (((offset & 1) == 0) ? 'H' : 'L'), data.Data()); + } else { + putlog(lv, "R%02u -> $%04x", regno, data.Data()); + } + } else { + putlog(3, "$%06x -> $%0*x", addr.Addr(), + reqsize * 2, data.Data()); + } } } + data |= wait; return data; } -uint64 -CRTCDevice::Write(uint32 offset, uint32 data) +busdata +CRTCDevice::Write(busaddr addr, uint32 data) { - gMPU->AddCycle(9); // InsideOut p.135 - - if (offset < 0x400 / 2) { // CRTC レジスタ - // 0x40 バイトでミラー - offset &= (0x40 / 2) - 1; - if (offset < countof(crtc.r)) { - SetReg(offset, data); + busdata r; + uint32 offset = Decoder(addr.Addr()); + uint32 reqsize = addr.GetSize(); + uint32 datasize = std::min(2 - (offset & 1), reqsize); + data >>= (reqsize - datasize) * 8; + + if (offset < 0x400) { + // CRTC レジスタは 0x40 バイトでミラー + uint32 regno = (offset >> 1) & 0x1f; + if (regno < countof(crtc.r)) { + if (datasize == 1) { + // バイト書き込み + if ((offset & 1) == 0) { + data = (data << 8) | (crtc.r[regno] & 0xff); + } else { + data = (crtc.r[regno] & 0xff00) | data; + } + } else { + // ワード書き込み + } + SetReg(regno, data); } else { // ここ何がいるんだ? - putlog(2, "$%06x <- $%04x", gMPU->GetPaddr(), data); + putlog(2, "$%06x <- $%0*x", addr.Addr(), datasize * 2, data); } - - } else { // 動作ポート - if ((offset & (0x80 / 2)) == 0) { // +$00..+$7f - return (uint64)-1; + } else { + // 動作ポート + if ((offset & 0x80) == 0) { // +$00..+$7f + r.SetBusErr(); } else { + if (datasize == 1) { + // バイト書き込み + if ((offset & 1) == 0) { + data = (data << 8) | crtc.op; + } else { + data = (crtc.op & 0xff00) | data; + } + } else { + // ワード書き込み + } WriteOp(data); } } - return 0; + r |= wait; + r |= busdata::Size(datasize); + return r; } -uint64 -CRTCDevice::Peek(uint32 offset) -{ - uint64 data; +busdata +CRTCDevice::Peek1(uint32 addr) +{ + busdata data = Peek2(addr); + if (data.IsOK()) { + if ((addr & 1) == 0) { + return data.Data() >> 8; + } else { + return data.Data() & 0xff; + } + } else { + return BusData::BusErr; + } +} - if (offset < 0x400 / 2) { // CRTC レジスタ - // 0x40 バイトでミラー - offset &= (0x40 / 2) - 1; - if (offset == 20 || offset == 21) { +// addr のワードとしての読み出し値を返す。 +// これは内部用で Read() からも呼ばれるので、 +// バスエラーなら BusData::BusErr を返す。 +busdata +CRTCDevice::Peek2(uint32 addr) const +{ + busdata data; + + uint32 offset = Decoder(addr); + if (offset < 0x400) { + // CRTC レジスタは 0x40 バイトでミラー + uint32 regno = (offset & 0x3f) / 2; + if (regno == 20 || regno == 21) { // R20, R21 のみ読み出し可能 - data = crtc.r[offset]; + data = crtc.r[regno]; } else { data = 0; } - - } else { // 動作ポート - if ((offset & (0x80 / 2)) == 0) { - data = (uint64)-1; + } else { + // 動作ポート + if ((offset & 0x80) == 0) { + data.SetBusErr(); } else { data = crtc.op; } @@ -167,11 +245,48 @@ CRTCDevice::MonitorUpdate(Monitor *, Tex uint16 val; screen.Clear(); - y = 0; + + screen.Print(0, y++, "R00:$%04x (V.Total)", crtc.r[0]); + screen.Print(0, y++, "R01:$%04x (V.Sync End)", crtc.r[1]); + screen.Print(0, y++, "R02:$%04x (V.Disp Start)", crtc.r[2]); + screen.Print(0, y++, "R02:$%04x (V.Disp End)", crtc.r[3]); + + screen.Print(0, y++, "R04:$%04x (H.Total)", crtc.r[4]); + screen.Print(0, y++, "R05:$%04x (H.Sync End)", crtc.r[5]); + screen.Print(0, y++, "R06:$%04x (H.Disp Start)", crtc.r[6]); + screen.Print(0, y++, "R07:$%04x (H.Disp End)", crtc.r[7]); + screen.Print(0, y++, "R10:$%04x (Text ScrollX)", crtc.r[10]); screen.Print(0, y++, "R11:$%04x (Text ScrollY)", crtc.r[11]); + val = crtc.r[20]; + screen.Print(0, y, "R20:$%04x (Mode)", val); + screen.Print(29, y, TA::OnOff(val & CRTC::R20_MEM), "MEM"); + static const char * const r20col_str[] = { + "16", + "256", + "undef", + "65536", + }; + screen.Print(33, y, "COL=%s", r20col_str[(val >> 8) & 3]); + screen.Print(43, y, TA::OnOff(val & CRTC::R20_HF), "HF"); + static const char * const r20vd_str[] = { + "256", + "512", + "1024", + "undef", + }; + screen.Print(46, y, "VD=%s", r20vd_str[(val >> 2) & 3]); + static const char * const r20hd_str[] = { + "256", + "512", + "768", + "50MHz", + }; + screen.Print(55, y, "HD=%s", r20hd_str[val & 3]); + y++; + val = crtc.r[21]; screen.Print(0, y, "R21:$%04x (Text Plane)", val); screen.Print(29, y, "MEN=%c SA=%c AP=%%%c%c%c%c CP=%%%c%c%c%c", @@ -203,28 +318,28 @@ CRTCDevice::SetReg(uint32 reg, uint32 da switch (reg) { case CRTC::R10: - putlog(2, "R%02d <- $%04x", reg, data); - gTVRAM->SetScrollX(crtc.r[10]); + putlog(2, "R%02u <- $%04x", reg, data); + tvram->SetScrollX(crtc.r[10]); break; case CRTC::R11: - putlog(2, "R%02d <- $%04x", reg, data); - gTVRAM->SetScrollY(crtc.r[11]); + putlog(2, "R%02u <- $%04x", reg, data); + tvram->SetScrollY(crtc.r[11]); break; case CRTC::R21: // 頻度が高いのでログレベルを上げておく - putlog(3, "R%02d <- $%04x", reg, data); - gTVRAM->SetAccessPlane(crtc.r[21]); + putlog(3, "R%02u <- $%04x", reg, data); + tvram->SetAccessPlane(crtc.r[21]); break; case CRTC::R22: - putlog(3, "R%02d <- $%04x", reg, data); + putlog(3, "R%02u <- $%04x", reg, data); break; case CRTC::R23: // 頻度が高いのでログレベルを上げておく - putlog(3, "R%02d <- $%04x", reg, data); - gTVRAM->SetAccessMask(crtc.r[23]); + putlog(3, "R%02u <- $%04x", reg, data); + tvram->SetAccessMask(crtc.r[23]); break; default: - putlog(2, "R%02d <- $%04x (未実装)", reg, data); + putlog(2, "R%02u <- $%04x (NOT IMPLEMENTED)", reg, data); break; } } @@ -236,7 +351,7 @@ CRTCDevice::WriteOp(uint32 data) uint16 oldop = crtc.op; crtc.op = data & 0x0b; - putlog(2, "$E80480.W <- $%04x", crtc.op); + putlog(2, "OP <- $%04x", crtc.op); uint16 change = oldop ^ crtc.op; @@ -244,53 +359,72 @@ CRTCDevice::WriteOp(uint32 data) // テキスト画面ラスターコピー if ((crtc.op & CRTC::OP_RC)) { // 0->1 開始 - StartRasterCopy(); - - // XXX 本当は終わったら 0 にするはず - crtc.op &= ~CRTC::OP_RC; + raster_copy = true; + putlog(3, "Start Raster copy (ラスターコピー指示"); } else { - // 1->0 停止 - // XXX 今の所一瞬で終わるので… - //StopRasterCopy(); + // 1->0 は停止じゃなくキャンセル? + raster_copy = false; + putlog(3, "Cancel Raster copy (ラスターコピーキャンセル)"); } } if ((change & 0x03)) { - putlog(0, "動作ポート $%02x->$%02x 未実装", + putlog(0, "OP (動作ポート) $%02x <- $%02x (NOT IMPLEMENTED)", oldop & 0x03, crtc.op & 0x03); } } -// テキスト画面のラスターコピー開始 -void -CRTCDevice::StartRasterCopy() -{ - int src = (crtc.r[22] >> 8) * 4; - int dst = (crtc.r[22] & 0xff) * 4; - - gTVRAM->RasterCopy(src, dst); -} - // 水平同期イベント void CRTCDevice::HSyncCallback(Event& ev) { - int hsync = ev.code; - - // GPIP の状態を更新 - gMFP->SetHSync(hsync); + switch (ev.code) { + case 0: // フロントポーチ + // フロントポーチ + ev.time = 2.07_usec; + ev.code++; + break; - // 次のタイミングを計算 - if (hsync) { + case 1: // 水平同期パルス // 水平同期期間 ev.time = 3.45_usec; + ev.code++; + + // GPIP の状態を更新 + mfp->SetHSync(true); + + // ラスターコピーが指示されていたら実行 + if (raster_copy) { + raster_copy = false; + + uint src = (crtc.r[22] >> 8) * 4; + uint dst = (crtc.r[22] & 0xff) * 4; + tvram->RasterCopy(src, dst); + + // 所定時間後に完了させる + scheduler->RestartEvent(raster_event); + } + break; + + case 2: // バックポーチ + 表示期間 + // バックポーチ + Hdisp + ev.time = 4.14_usec + 22.09_usec; ev.code = 0; - } else { - // - ev.time = 22.09_usec + 4.14_usec + 2.07_usec; - ev.code = 1; + + // GPIP の状態を更新 + mfp->SetHSync(false); + break; } - gScheduler->StartEvent(ev); + + scheduler->StartEvent(ev); +} + +// ラスターコピー完了イベント +void +CRTCDevice::RasterCallback(Event& ev) +{ + // 終わったら動作ポートの RC ビットを %0 にする? + crtc.op &= ~CRTC::OP_RC; } // 垂直表示・帰線イベント @@ -301,12 +435,11 @@ CRTCDevice::VDispCallback(Event& ev) int vdisp = ev.code; // GPIP の状態を更新 - gMFP->SetVDisp(vdisp); + mfp->SetVDisp(vdisp); // 今はここで1画面まるごと作成 if (vdisp) { - // Render に作画指示 - gRenderer->Render(); + videoctlr->VDisp(); } // 次のタイミングを計算 @@ -320,5 +453,5 @@ CRTCDevice::VDispCallback(Event& ev) ev.time = 191_usec + 1111_usec + 476_usec; ev.code = 1; } - gScheduler->StartEvent(ev); + scheduler->StartEvent(ev); }