File:  [Isaki's NoNo m68k/m88k emulator] / nono / vm / crtc.cpp
Revision 1.1.1.16 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:05:59 2026 UTC (2 months, 3 weeks ago) by root
Branches: MAIN, Isaki
CVS tags: v027, HEAD
nono 1.7.0

//
// nono
// 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 バイトずつが繰り返して見える。
//
// CRTC はデバイスの配置としては BusIO_B 相当で、バイトアクセスも行えるが、
// 内部はバイトレジスタではなくワード単位を基本構造としている。そのため
// BusIO は使わず自力で処理する。ログの観点からもこのほうが都合がいい。

#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)
{
	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(bool poweron)
{
	// XXX 初期値適当
	crtc.r[20] = 0x0016;

	// XXX 適当
	// すぐに水平同期と垂直帰線期間を開始する
	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;
	}

	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;
}

busdata
CRTCDevice::Write(busaddr addr, uint32 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 <- $%0*x", addr.Addr(), datasize * 2, data);
		}
	} 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);
		}
	}

	r |= wait;
	r |= busdata::Size(datasize);
	return r;
}

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;
	}
}

// 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[regno];
		} else {
			data = 0;
		}
	} else {
		// 動作ポート
		if ((offset & 0x80) == 0) {
			data.SetBusErr();
		} else {
			data = crtc.op;
		}
	}

	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++, "<Video Controller>");
	videoctlr->MonitorScreen(screen, y, crtc.r[20]);
}

// CRTC レジスタへの書き込み。
// reg は CRTC::R00 .. CRTC::R23
void
CRTCDevice::SetReg(uint32 reg, uint32 data)
{
	crtc.r[reg] = data;

	switch (reg) {
	 case CRTC::R00 ... CRTC::R07:
	 case CRTC::R12 ... CRTC::R19:
		if (crtc.r[reg] == crtc_supported[reg]) {
			putlog(2, "R%02u <- $%04x", reg, data);
			return;
		}
		break;
	 case CRTC::R10:
		putlog(2, "R%02u <- $%04x", reg, data);
		tvram->SetScrollX(crtc.r[10]);
		return;
	 case CRTC::R11:
		putlog(2, "R%02u <- $%04x", reg, data);
		tvram->SetScrollY(crtc.r[11]);
		return;
	 case CRTC::R21:
		// 頻度が高いのでログレベルを上げておく
		putlog(3, "R%02u <- $%04x", reg, data);
		tvram->SetAccessPlane(crtc.r[21]);
		return;
	 case CRTC::R22:
		putlog(3, "R%02u <- $%04x", reg, data);
		return;
	 case CRTC::R23:
		// 頻度が高いのでログレベルを上げておく
		putlog(3, "R%02u <- $%04x", reg, data);
		tvram->SetAccessMask(crtc.r[23]);
		return;
	 default:
		break;
	}
	putlog(0, "R%02u <- $%04x (NOT IMPLEMENTED)", reg, data);
}

// 動作ポートへの書き込み。
void
CRTCDevice::WriteOp(uint32 data)
{
	uint16 oldop = crtc.op;

	crtc.op = data & 0x0b;
	putlog(2, "OP <- $%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, "Start Raster copy (ラスターコピー指示");
		} else {
			// 1->0 は停止じゃなくキャンセル?
			raster_copy = false;
			putlog(3, "Cancel Raster copy (ラスターコピーキャンセル)");
		}
	}

	if ((change & 0x03)) {
		putlog(0, "OP (動作ポート) $%02x <- $%02x (NOT IMPLEMENTED)",
			oldop & 0x03, crtc.op & 0x03);
	}
}

// 水平同期イベント
void
CRTCDevice::HSyncCallback(Event *ev)
{
	switch (hsync_state) {
	 case 0:	// フロントポーチ
		// フロントポーチ
		ev->time = 2.07_usec;
		hsync_state++;
		break;

	 case 1:	// 水平同期パルス
		// 水平同期期間
		ev->time = 3.45_usec;
		hsync_state++;

		// 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;
		hsync_state = 0;

		// GPIP の状態を更新
		mfp->SetHSync(false);
		break;

	 default:
		__unreachable();
	}

	scheduler->StartEvent(ev);
}

// ラスターコピー完了イベント
void
CRTCDevice::RasterCallback(Event *ev)
{
	// 終わったら動作ポートの RC ビットを %0 にする?
	crtc.op &= ~CRTC::OP_RC;
}

// 垂直表示・帰線イベント
// vdisp_state が 1 なら表示期間開始(V-disp)、0 なら帰線期間開始(V-blank)。
void
CRTCDevice::VDispCallback(Event *ev)
{
	// GPIP の状態を更新
	mfp->SetVDisp(vdisp_state);

	// 今はここで1画面まるごと作成
	if (vdisp_state) {
		videoctlr->VDisp();
	}

	// 次のタイミングを計算
	// XXX まだ CRTC から計算していない
	if (vdisp_state) {
		// 垂直表示期間
		ev->time = 16250_usec;
		vdisp_state = 0;
	} else {
		// 垂直帰線期間
		ev->time = 191_usec + 1111_usec + 476_usec;
		vdisp_state = 1;
	}
	scheduler->StartEvent(ev);
}

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.