--- nono/vm/crtc.cpp 2026/04/29 17:04:35 1.1.1.4 +++ nono/vm/crtc.cpp 2026/04/29 17:05:59 1.1.1.16 @@ -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 バイトでミラーになっており、このうち @@ -15,122 +14,221 @@ // +$80..+$ff は何かが読める。 // 仕様は $e80480.w に1ポートあるので、これがワードで全域にミラーされている? // 全体としては この $800 バイトずつが繰り返して見える。 +// +// CRTC はデバイスの配置としては BusIO_B 相当で、バイトアクセスも行えるが、 +// 内部はバイトレジスタではなくワード単位を基本構造としている。そのため +// BusIO は使わず自力で処理する。ログの観点からもこのほうが都合がいい。 -std::unique_ptr gCRTC; +#include "crtc.h" +#include "event.h" +#include "mfp.h" +#include "monitor.h" +#include "scheduler.h" +#include "tvram.h" +#include "videoctlr.h" +// InsideOut p.135 +/*static*/ const busdata +CRTCDevice::wait = busdata::Wait(9 * 40_nsec); + +// コンストラクタ CRTCDevice::CRTCDevice() + : inherited(OBJ_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"); + monitor = gMonitorManager->Regist(ID_MONITOR_CRTC, this); + monitor->SetCallback(&CRTCDevice::MonitorScreen); + monitor->SetSize(79, 27); } +// デストラクタ CRTCDevice::~CRTCDevice() { } +// 初期化 +bool +CRTCDevice::Init() +{ + mfp = GetMFPDevice(); + tvram = GetTVRAMDevice(); + videoctlr = GetVideoCtlrDevice(); + + auto evman = GetEventManager(); + hsync_event = evman->Regist(this, + ToEventCallback(&CRTCDevice::HSyncCallback), + "CRTC H-Sync"); + vdisp_event = evman->Regist(this, + ToEventCallback(&CRTCDevice::VDispCallback), + "CRTC V-Disp"); + + // ラスターコピーは実際には CRTC の機能ではなく TVRAM (VRAM チップ) の + // 機能だが、イベントの時間管理が発生するのは CRTC 側なのでここで管理。 + // X68030 搭載の VRAM チップは HM514402A だが、ここでは X68000 XVI 搭載の + // HM53461 の値を使用し、これが最小 190nsec * 2 らしいので + // とりあえず 400nsec としておく。 + raster_event = evman->Regist(this, + ToEventCallback(&CRTCDevice::RasterCallback), + "CRTC Raster Copy"); + raster_event->time = 400_nsec; + + return true; +} + +// リセット void -CRTCDevice::ResetHard() +CRTCDevice::ResetHard(bool poweron) { // XXX 初期値適当 crtc.r[20] = 0x0016; // XXX 適当 - // すぐに垂直帰線期間を開始する - vdisp_event.time = 0; - vdisp_event.code = 1; - vdisp_event.Start(); -} - -uint64 -CRTCDevice::Read(uint32 addr) -{ - uint64 data; - - if (addr < 0x400 / 2) { // CRTC レジスタ - // 0x40 バイトでミラー - addr &= (0x40 / 2) - 1; - // R20, R21 のみ読み出し可能 - switch (addr) { - case 20: - data = crtc.r[addr]; - putlog(2, "R%02d -> $%04x", addr, (uint32)data); - break; - case 21: - // R21 へのアクセスは多いのでログレベルを上げておく - data = crtc.r[addr]; - putlog(3, "R%02d -> $%04x", addr, (uint32)data); - break; - default: - // それ以外は読み出し不可 ($00 が読める) - data = 0; - if (addr < countof(crtc.r)) { - putlog(3, "R%02d -> $%04x", addr, (uint32)data); - } else { - putlog(3, "$%06x -> $%04x", gMPU->GetPaddr(), (uint32)data); - } - break; + // すぐに水平同期と垂直帰線期間を開始する + hsync_state = 1; + hsync_event->time = 0; + scheduler->RestartEvent(hsync_event); + vdisp_state = 1; + vdisp_event->time = 0; + scheduler->RestartEvent(vdisp_event); +} + +/*static*/ inline uint32 +CRTCDevice::Decoder(uint32 addr) +{ + return addr & 0x7ff; +} + +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 ((addr & (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 addr, uint32 data) +busdata +CRTCDevice::Write(busaddr addr, uint32 data) { - if (addr < 0x400 / 2) { // CRTC レジスタ - // 0x40 バイトでミラー - addr &= (0x40 / 2) - 1; - if (addr < countof(crtc.r)) { - SetReg(addr, 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 ((addr & (0x80 / 2)) == 0) { // +$00..+$7f - return (uint64)-1; + } else { + // 動作ポート + if ((offset & 0x80) == 0) { // +$00..+$7f + r.SetBusErr(); } else { - crtc.op = data & 0x0b; - putlog(2, "$E80480.W <- $%04x", crtc.op); + 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 addr) +busdata +CRTCDevice::Peek1(uint32 addr) { - uint64 data; + 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 (addr < 0x400 / 2) { // CRTC レジスタ - // 0x40 バイトでミラー - addr &= (0x40 / 2) - 1; - if (addr == 20 || addr == 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[addr]; + data = crtc.r[regno]; } else { data = 0; } - - } else { // 動作ポート - if ((addr & (0x80 / 2)) == 0) { - data = (uint64)-1; + } else { + // 動作ポート + if ((offset & 0x80) == 0) { + data.SetBusErr(); } else { data = crtc.op; } @@ -139,6 +237,122 @@ CRTCDevice::Peek(uint32 addr) return data; } +// 今の所、標準設定しかサポートしていないので、違う時だけログを出したりする。 +static const uint16 crtc_supported[] = { + 0x0089, // R00 + 0x000e, // R01 + 0x001c, // R02 + 0x007c, // R03 + 0x0237, // R04 + 0x0005, // R05 + 0x0028, // R06 + 0x0228, // R07 + 0, // R08 + 0, // R09 + 0, // R10 + 0, // R11 + 0x0000, // R12 + 0x0000, // R13 + 0x0000, // R14 + 0x0000, // R15 + 0x0000, // R16 + 0x0000, // R17 + 0x0000, // R18 + 0x0000, // R19 +}; + +void +CRTCDevice::MonitorScreen(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]); + + screen.Print(0, y++, "R12:$%04x (Grp. Page0 X)", crtc.r[12]); + screen.Print(0, y++, "R13:$%04x (Grp. Page0 Y)", crtc.r[13]); + screen.Print(0, y++, "R14:$%04x (Grp. Page1 X)", crtc.r[14]); + screen.Print(0, y++, "R15:$%04x (Grp. Page1 Y)", crtc.r[15]); + screen.Print(0, y++, "R16:$%04x (Grp. Page2 X)", crtc.r[16]); + screen.Print(0, y++, "R17:$%04x (Grp. Page2 Y)", crtc.r[17]); + screen.Print(0, y++, "R18:$%04x (Grp. Page3 X)", crtc.r[18]); + screen.Print(0, y++, "R19:$%04x (Grp. Page3 Y)", crtc.r[19]); + + for (uint i = 0; i < countof(crtc_supported); i++) { + if (8 <= i && i < 12) { + continue; + } + if (crtc.r[i] != crtc_supported[i]) { + screen.Print(29, (i < 8 ? i : i - 2), TA::On, + "(not supported value)"); + } + } + + // 3 4 5 6 7 + // 90123456789012345678901234567890123456789012345678 + // SIZ=1024x1024 COL=65536 HF=31kHz VD=undef HD=50MHz + val = crtc.r[20]; + screen.Print(0, y, "R20:$%04x (Mode)", val); + screen.Print(29, y, "SIZ=%s", VideoCtlrDevice::siz_str[(val >> 10) & 1]); + screen.Print(43, y, "COL=%s", VideoCtlrDevice::col_str[(val >> 8) & 3]); + screen.Print(53, y, "HF=%ukHz", 15 + (val & 0x10)); + static const char * const r20vd_str[] = { + "256", + "512", + "1024", + "undef", + }; + screen.Print(62, y, "VD=%s", r20vd_str[(val >> 2) & 3]); + static const char * const r20hd_str[] = { + "256", + "512", + "768", + "50MHz", + }; + screen.Print(71, 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]); + + // ビデオコントローラもここに並べたほうが読みやすいか。 + y++; + screen.Puts(0, y++, "