--- nono/vm/crtc.cpp 2026/04/29 17:04:29 1.1.1.2 +++ nono/vm/crtc.cpp 2026/04/29 17:05:09 1.1.1.8 @@ -1,212 +1,324 @@ // // nono -// Copyright (C) 2017 isaki@NetBSD.org +// Copyright (C) 2020 nono project +// Licensed under nono-license.txt // +// +// CRTC +// + +// e80000..e803ff 内は $40 バイトでミラー。 +// e80400..e807ff 内は 256 バイトでミラーになっており、このうち +// +$00..+$7f はバスエラー、 +// +$80..+$ff は何かが読める。 +// 仕様は $e80480.w に1ポートあるので、これがワードで全域にミラーされている? +// 全体としては この $800 バイトずつが繰り返して見える。 + #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"); + + monitor.func = ToMonitorCallback(&CRTCDevice::MonitorUpdate); + monitor.SetSize(60, 5); + 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; - gScheduler->AddEvent(&vdisp_event); + gScheduler->RestartEvent(vdisp_event); } uint64 -CRTCDevice::Read8(uint32 addr) +CRTCDevice::Read(uint32 offset) { - uint32 an = (addr - devaddr) / 2; - uint16 data; + uint64 data; - switch (an) { - case CRTC::R00 ... CRTC::R23: - data = crtc.r[an]; - if ((addr & 1) == 0) { - data >>= 8; - putlog(2, "R%02d(H) -> $%02x", an, data); - } else { - data &= 0xff; - putlog(2, "R%02d(L) -> $%02x", an, data); - } - break; + gMPU->AddCycle(9); // InsideOut p.135 - case CRTC::REG_OP: // $E80480 - data = crtc.op; - if ((addr & 1) == 0) { - data >>= 8; - putlog(2, "$E80480 -> $%02x", data); - } else { - data &= 0xff; - putlog(2, "$E80481 -> $%02x", data); + 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; } - break; - default: - putlog(1, "Read8 $%06x 未実装ポート読み込み", addr); - data = 0xff; + } else { // 動作ポート + if ((offset & (0x80 / 2)) == 0) { // +$00..+$7f + data = (uint64)-1; + } else { // +$80..+$ff + data = crtc.op; + } } return data; } uint64 -CRTCDevice::Read16(uint32 addr) +CRTCDevice::Write(uint32 offset, uint32 data) { - uint32 an = (addr - devaddr) / 2; - uint16 data; + gMPU->AddCycle(9); // InsideOut p.135 - switch (an) { - case CRTC::R00 ... CRTC::R23: - data = crtc.r[an]; - putlog(2, "R%02d -> $%04x", an, data); - return data; + if (offset < 0x400 / 2) { // CRTC レジスタ + // 0x40 バイトでミラー + offset &= (0x40 / 2) - 1; + if (offset < countof(crtc.r)) { + SetReg(offset, data); + } else { + // ここ何がいるんだ? + putlog(2, "$%06x <- $%04x", gMPU->GetPaddr(), data); + } - default: - putlog(1, "$%06x 未実装読み込み", addr); - return 0; + } else { // 動作ポート + if ((offset & (0x80 / 2)) == 0) { // +$00..+$7f + return (uint64)-1; + } else { + WriteOp(data); + } } + + return 0; } uint64 -CRTCDevice::Write8(uint32 addr, uint32 data) +CRTCDevice::Peek(uint32 offset) { - uint32 an = (addr - devaddr) / 2; + uint64 data; - switch (an) { - case CRTC::R00 ... CRTC::R23: - // 16ビットにしてから書き込む - if((addr & 1) == 0) { - data = (data << 8) | (crtc.r[an] & 0x00ff); - putlog(2, "R%02d(H) = $%04x", an, data); + if (offset < 0x400 / 2) { // CRTC レジスタ + // 0x40 バイトでミラー + offset &= (0x40 / 2) - 1; + if (offset == 20 || offset == 21) { + // R20, R21 のみ読み出し可能 + data = crtc.r[offset]; } else { - data = (data & 0xff) | (crtc.r[an] & 0xff00); - putlog(2, "R%02d(L) = $%04x", an, data); + data = 0; } - WriteReg(an, data); - break; - case CRTC::REG_OP: - // 下位8ビットのみ有効 - if ((addr & 1) == 1) { - putlog(2, "$E80481 <- $%02x", data); - crtc.op = data & 0x0b; + } else { // 動作ポート + if ((offset & (0x80 / 2)) == 0) { + data = (uint64)-1; + } else { + data = crtc.op; } - break; - - default: - putlog(1, "Write8 $%06x <- $%02x 未実装書き込み", addr, data); - break; } - return 0; + + return data; } -uint64 -CRTCDevice::Write16(uint32 addr, uint32 data) +void +CRTCDevice::MonitorUpdate(Monitor *, TextScreen& screen) { - uint32 an = (addr - devaddr) / 2; + int y; + uint16 val; - switch (an) { - case CRTC::R00 ... CRTC::R23: - putlog(2, "R%02d <- $%04x", an, data); - WriteReg(an, data); - break; + screen.Clear(); - case CRTC::REG_OP: - putlog(2, "$E80481 <- $%02x", data); - crtc.op = data & 0x0b; - break; + y = 0; + 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[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)); - default: - putlog(1, "$%06x <- $%04x 未実装書き込み", addr, data); - break; - } - return 0; + screen.Print(0, y++, "R23:$%04x (Text Access Mask)", crtc.r[23]); } -// Write8/16 の下請け。内部アクセス用 -// offset は CRTC::R00 .. CRTC::R23 +// CRTC レジスタへの書き込み。 +// reg は CRTC::R00 .. CRTC::R23 void -CRTCDevice::WriteReg(uint32 reg, uint32 data) +CRTCDevice::SetReg(uint32 reg, uint32 data) { crtc.r[reg] = data; switch (reg) { + case CRTC::R10: + putlog(2, "R%02d <- $%04x", reg, data); + gTVRAM->SetScrollX(crtc.r[10]); + break; + case CRTC::R11: + putlog(2, "R%02d <- $%04x", reg, data); + gTVRAM->SetScrollY(crtc.r[11]); + break; case CRTC::R21: - gTVRAM->set_crtc_21(crtc.r[21]); + // 頻度が高いのでログレベルを上げておく + putlog(3, "R%02d <- $%04x", reg, data); + 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); break; } } -uint64 -CRTCDevice::Peek8(uint32 addr) +// 動作ポートへの書き込み。 +void +CRTCDevice::WriteOp(uint32 data) { - uint32 an2 = addr - devaddr; + uint16 oldop = crtc.op; + + crtc.op = data & 0x0b; + putlog(2, "$E80480.W <- $%04x", crtc.op); - switch (an2) { - case CRTC::R00 ... (CRTC::R23 * 2): - if ((an2 & 1) == 0) { - return crtc.r[an2 / 2] >> 8; + uint16 change = oldop ^ crtc.op; + + if ((change & CRTC::OP_RC)) { + // テキスト画面ラスターコピー + if ((crtc.op & CRTC::OP_RC)) { + // 0->1 開始 + StartRasterCopy(); + + // XXX 本当は終わったら 0 にするはず + crtc.op &= ~CRTC::OP_RC; } else { - return crtc.r[an2 / 2] & 0xff; + // 1->0 停止 + // XXX 今の所一瞬で終わるので… + //StopRasterCopy(); } - break; + } - case CRTC::REG_OP * 2 + 1: - return crtc.op; + if ((change & 0x03)) { + putlog(0, "動作ポート $%02x->$%02x 未実装", + oldop & 0x03, crtc.op & 0x03); + } +} - default: - return 0xff; +// テキスト画面のラスターコピー開始 +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); + + // 次のタイミングを計算 + if (hsync) { + // 水平同期期間 + ev.time = 3.45_usec; + ev.code = 0; + } else { + // + ev.time = 22.09_usec + 4.14_usec + 2.07_usec; + ev.code = 1; } + gScheduler->StartEvent(ev); } // 垂直表示・帰線イベント -// 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; - gScheduler->AddEvent(&vdisp_event); + gScheduler->StartEvent(ev); }