--- nono/vm/crtc.cpp 2026/04/29 17:04:35 1.1.1.4 +++ nono/vm/crtc.cpp 2026/04/29 17:05:13 1.1.1.9 @@ -4,10 +4,9 @@ // Licensed under nono-license.txt // -#include "crtc.h" -#include "mfp.h" -#include "renderer.h" -#include "tvram.h" +// +// CRTC +// // e80000..e803ff 内は $40 バイトでミラー。 // e80400..e807ff 内は 256 バイトでミラーになっており、このうち @@ -16,61 +15,88 @@ // 仕様は $e80480.w に1ポートあるので、これがワードで全域にミラーされている? // 全体としては この $800 バイトずつが繰り返して見える。 -std::unique_ptr gCRTC; +#include "crtc.h" +#include "mfp.h" +#include "mpu.h" +#include "renderer.h" +#include "scheduler.h" +#include "tvram.h" + +// グローバル参照用 +CRTCDevice *gCRTC; +// コンストラクタ CRTCDevice::CRTCDevice() + : inherited("CRTC") { - logname = "crtc"; - devname = "CRTC"; devaddr = 0xe80000; devlen = 0x2000; - vdisp_event.dev = this; - vdisp_event.func = (DeviceCallback_t)&CRTCDevice::VDispCallback; - vdisp_event.SetName("CRTC V-DISP"); + hsync_event.func = ToEventCallback(&CRTCDevice::HSyncCallback); + hsync_event.Regist("CRTC H-Sync"); + vdisp_event.func = ToEventCallback(&CRTCDevice::VDispCallback); + vdisp_event.Regist("CRTC V-Disp"); + + // ラスターコピーにかかる時間は最小 190nsec * 2 らしいので + // とりあえず 400nsec としておく。 + raster_event.func = ToEventCallback(&CRTCDevice::RasterCallback); + raster_event.time = 400_nsec; + raster_event.Regist("CRTC Raster Copy"); + + monitor.func = ToMonitorCallback(&CRTCDevice::MonitorUpdate); + monitor.SetSize(62, 14); + monitor.Regist(ID_MONITOR_CRTC); } +// デストラクタ CRTCDevice::~CRTCDevice() { + gCRTC = NULL; } +// リセット void -CRTCDevice::ResetHard() +CRTCDevice::ResetHard(bool poweron) { // XXX 初期値適当 crtc.r[20] = 0x0016; // XXX 適当 - // すぐに垂直帰線期間を開始する + // すぐに水平同期と垂直帰線期間を開始する + hsync_event.time = 0; + hsync_event.code = 1; + gScheduler->RestartEvent(hsync_event); vdisp_event.time = 0; vdisp_event.code = 1; - vdisp_event.Start(); + gScheduler->RestartEvent(vdisp_event); } uint64 -CRTCDevice::Read(uint32 addr) +CRTCDevice::Read(uint32 offset) { uint64 data; - if (addr < 0x400 / 2) { // CRTC レジスタ + gMPU->AddCycle(9); // InsideOut p.135 + + if (offset < 0x400 / 2) { // CRTC レジスタ // 0x40 バイトでミラー - addr &= (0x40 / 2) - 1; + offset &= (0x40 / 2) - 1; // R20, R21 のみ読み出し可能 - switch (addr) { + switch (offset) { case 20: - data = crtc.r[addr]; - putlog(2, "R%02d -> $%04x", addr, (uint32)data); + data = crtc.r[offset]; + putlog(2, "R%02d -> $%04x", offset, (uint32)data); break; case 21: // R21 へのアクセスは多いのでログレベルを上げておく - data = crtc.r[addr]; - putlog(3, "R%02d -> $%04x", addr, (uint32)data); + data = crtc.r[offset]; + putlog(3, "R%02d -> $%04x", offset, (uint32)data); break; default: // それ以外は読み出し不可 ($00 が読める) data = 0; - if (addr < countof(crtc.r)) { - putlog(3, "R%02d -> $%04x", addr, (uint32)data); + if (offset < countof(crtc.r)) { + putlog(3, "R%02d -> $%04x", offset, (uint32)data); } else { putlog(3, "$%06x -> $%04x", gMPU->GetPaddr(), (uint32)data); } @@ -78,9 +104,9 @@ CRTCDevice::Read(uint32 addr) } } else { // 動作ポート - if ((addr & (0x80 / 2)) == 0) { // +$00..+$7f + if ((offset & (0x80 / 2)) == 0) { // +$00..+$7f data = (uint64)-1; - } else { // +$80..+$ff + } else { // +$80..+$ff data = crtc.op; } } @@ -89,24 +115,25 @@ CRTCDevice::Read(uint32 addr) } uint64 -CRTCDevice::Write(uint32 addr, uint32 data) +CRTCDevice::Write(uint32 offset, uint32 data) { - if (addr < 0x400 / 2) { // CRTC レジスタ + gMPU->AddCycle(9); // InsideOut p.135 + + if (offset < 0x400 / 2) { // CRTC レジスタ // 0x40 バイトでミラー - addr &= (0x40 / 2) - 1; - if (addr < countof(crtc.r)) { - SetReg(addr, data); + offset &= (0x40 / 2) - 1; + if (offset < countof(crtc.r)) { + SetReg(offset, data); } else { // ここ何がいるんだ? putlog(2, "$%06x <- $%04x", gMPU->GetPaddr(), data); } } else { // 動作ポート - if ((addr & (0x80 / 2)) == 0) { // +$00..+$7f + if ((offset & (0x80 / 2)) == 0) { // +$00..+$7f return (uint64)-1; } else { - crtc.op = data & 0x0b; - putlog(2, "$E80480.W <- $%04x", crtc.op); + WriteOp(data); } } @@ -114,22 +141,22 @@ CRTCDevice::Write(uint32 addr, uint32 da } uint64 -CRTCDevice::Peek(uint32 addr) +CRTCDevice::Peek(uint32 offset) { uint64 data; - if (addr < 0x400 / 2) { // CRTC レジスタ + if (offset < 0x400 / 2) { // CRTC レジスタ // 0x40 バイトでミラー - addr &= (0x40 / 2) - 1; - if (addr == 20 || addr == 21) { + offset &= (0x40 / 2) - 1; + if (offset == 20 || offset == 21) { // R20, R21 のみ読み出し可能 - data = crtc.r[addr]; + data = crtc.r[offset]; } else { data = 0; } } else { // 動作ポート - if ((addr & (0x80 / 2)) == 0) { + if ((offset & (0x80 / 2)) == 0) { data = (uint64)-1; } else { data = crtc.op; @@ -139,6 +166,77 @@ CRTCDevice::Peek(uint32 addr) return data; } +void +CRTCDevice::MonitorUpdate(Monitor *, TextScreen& screen) +{ + int y; + 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", + (val & 0x0200) ? '1' : '0', + (val & 0x0100) ? '1' : '0', + (val & 0x0080) ? '1' : '0', + (val & 0x0040) ? '1' : '0', + (val & 0x0020) ? '1' : '0', + (val & 0x0010) ? '1' : '0', + (val & 0x0008) ? '1' : '0', + (val & 0x0004) ? '1' : '0', + (val & 0x0002) ? '1' : '0', + (val & 0x0001) ? '1' : '0'); + y++; + + val = crtc.r[22]; + screen.Print(0, y++, "R22:$%04x (Text Raster Copy) src=$%02x dst=$%02x", + val, (val >> 8), (val & 0xff)); + + screen.Print(0, y++, "R23:$%04x (Text Access Mask)", crtc.r[23]); +} + // CRTC レジスタへの書き込み。 // reg は CRTC::R00 .. CRTC::R23 void @@ -158,37 +256,131 @@ CRTCDevice::SetReg(uint32 reg, uint32 da case CRTC::R21: // 頻度が高いのでログレベルを上げておく putlog(3, "R%02d <- $%04x", reg, data); - gTVRAM->set_crtc_21(crtc.r[21]); + gTVRAM->SetAccessPlane(crtc.r[21]); + break; + case CRTC::R22: + putlog(3, "R%02d <- $%04x", reg, data); + break; + case CRTC::R23: + // 頻度が高いのでログレベルを上げておく + putlog(3, "R%02d <- $%04x", reg, data); + gTVRAM->SetAccessMask(crtc.r[23]); break; default: - putlog(2, "R%02d <- $%04x (未実装)", reg, data); + putlog(2, "R%02d <- $%04x (NOT IMPLEMENTED)", reg, data); + break; + } +} + +// 動作ポートへの書き込み。 +void +CRTCDevice::WriteOp(uint32 data) +{ + uint16 oldop = crtc.op; + + crtc.op = data & 0x0b; + putlog(2, "$e80480.W <- $%04x", crtc.op); + + uint16 change = oldop ^ crtc.op; + + if ((change & CRTC::OP_RC)) { + // テキスト画面ラスターコピー + if ((crtc.op & CRTC::OP_RC)) { + // 0->1 開始 + raster_copy = true; + putlog(3, "ラスターコピー指示"); + } else { + // 1->0 は停止じゃなくキャンセル? + raster_copy = false; + putlog(3, "ラスターコピーキャンセル"); + } + } + + if ((change & 0x03)) { + putlog(0, "動作ポート $%02x <- $%02x (NOT IMPLEMENTED)", + oldop & 0x03, crtc.op & 0x03); + } +} + +// 水平同期イベント +void +CRTCDevice::HSyncCallback(Event& ev) +{ + switch (ev.code) { + case 0: // フロントポーチ + // フロントポーチ + ev.time = 2.07_usec; + ev.code++; + break; + + case 1: // 水平同期パルス + // 水平同期期間 + ev.time = 3.45_usec; + ev.code++; + + // GPIP の状態を更新 + gMFP->SetHSync(true); + + // ラスターコピーが指示されていたら実行 + if (raster_copy) { + raster_copy = false; + + int src = (crtc.r[22] >> 8) * 4; + int dst = (crtc.r[22] & 0xff) * 4; + gTVRAM->RasterCopy(src, dst); + + // 所定時間後に完了させる + gScheduler->RestartEvent(raster_event); + } + break; + + case 2: // バックポーチ + 表示期間 + // バックポーチ + Hdisp + ev.time = 4.14_usec + 22.09_usec; + ev.code = 0; + + // GPIP の状態を更新 + gMFP->SetHSync(false); break; } + + gScheduler->StartEvent(ev); +} + +// ラスターコピー完了イベント +void +CRTCDevice::RasterCallback(Event& ev) +{ + // 終わったら動作ポートの RC ビットを %0 にする? + crtc.op &= ~CRTC::OP_RC; } // 垂直表示・帰線イベント -// arg == 1 なら表示期間開始、arg == 0 なら帰線期間開始。 +// ev.code が 1 なら表示期間開始(V-disp)、0 なら帰線期間開始(V-blank)。 void -CRTCDevice::VDispCallback(int arg) +CRTCDevice::VDispCallback(Event& ev) { + int vdisp = ev.code; + // GPIP の状態を更新 - gMFP->SetVDisp(arg); + gMFP->SetVDisp(vdisp); // 今はここで1画面まるごと作成 - if (arg) { + if (vdisp) { // Render に作画指示 gRenderer->Render(); } // 次のタイミングを計算 // XXX まだ CRTC から計算していない - if (arg) { + if (vdisp) { // 垂直表示期間 - vdisp_event.time = 16250_usec; + ev.time = 16250_usec; + ev.code = 0; } else { // 垂直帰線期間 - vdisp_event.time = 1778_usec; + ev.time = 191_usec + 1111_usec + 476_usec; + ev.code = 1; } - vdisp_event.code = 1 - arg; - vdisp_event.Start(); + gScheduler->StartEvent(ev); }