File:  [Isaki's NoNo m68k/m88k emulator] / nono / vm / dmac.cpp
Revision 1.1.1.19 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:06:00 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
//

//
// DMAC (HD63450)
//

#include "dmac.h"
#include "adpcm.h"
#include "event.h"
#include "fdc.h"
#include "interrupt.h"
#include "mainbus.h"
#include "monitor.h"
#include "mpu.h"
#include "scheduler.h"
#include "syncer.h"

// time 時間後に呼び出すコールバックをセットする。
// func の書式が面倒なのを省略して書きたいため。
#define CallAfter(func_, time_, wait_)	do { \
	event->SetCallback(&DMACDevice::func_);	\
	event->time = time_ * 80_nsec + wait_;	\
	scheduler->StartEvent(event); \
} while (0)

// InsideOut p.135
static const busdata read_wait  = busdata::Wait(37 * 40_nsec);
static const busdata write_wait = busdata::Wait(31 * 40_nsec);

// チャンネルログ
#define chan_putlog(chan, lv, fmt...)	do {	\
	if ((chan)->loglevel >= (lv))	\
		(chan)->putlogn(fmt);	\
} while (0)

// コンストラクタ
DMACDevice::DMACDevice()
	: inherited(OBJ_DMAC)
{
	channels[0].reset(new DMACChan(0, "FD"));
	channels[1].reset(new DMACChan(1, "HD"));
	channels[2].reset(new DMACChan(2, "User"));
	channels[3].reset(new DMACChan(3, "ADPCM"));

	// PCL 信号線
	// #0 はスーパーインポーズしてなければ常に Low
	channels[0]->pcl_pin = false;
	channels[0]->pcl_prev = false;
	// #1 はプルアップされているので常に High
	channels[1]->pcl_pin = true;
	channels[1]->pcl_prev = true;
	// #2 は外部スロット用だが未接続なので High
	channels[2]->pcl_pin = true;
	channels[2]->pcl_prev = true;
	// #3 は未対応

	monitor = gMonitorManager->Regist(ID_MONITOR_DMAC, this);
	monitor->SetCallback(&DMACDevice::MonitorScreen);
	monitor->SetSize(80, 34);
}

// デストラクタ
DMACDevice::~DMACDevice()
{
}

// 初期化
bool
DMACDevice::Init()
{
	adpcm = GetADPCMDevice();
	fdc = GetFDCDevice();
	interrupt = GetInterruptDevice();
	mainbus = GetMainbusDevice();
	syncer = GetSyncer();

	auto evman = GetEventManager();
	event = evman->Regist(this, NULL, "DMAC");

	return true;
}

// リセット
void
DMACDevice::ResetHard(bool poweron)
{
	putlog(2, "Reset");

	// Clears GCR, DCR, OCR, SCR, CCR, CSR, CPR and CER for all channels.
	// NIV and EIV are all set to $0F (uninitialized interrupt vector).
	// MTC, MAR, DAR, BTC, BAR, MFC, DFC and BFC are not affected.

	// 電源オン時、少なくとも BFC は $07 のようだ
	// (X68030 IPLROM が #0, #1 の BFC を初期化していないので読める)。
	// MFC、DFC くらいは同様かも知れない。
	if (poweron) {
		for (auto& chan : channels) {
			chan->SetMFC(0x07);
			chan->SetDFC(0x07);
			chan->SetBFC(0x07);
		}
	}

	gcr = 0;
	for (auto& chan : channels) {
		chan->SetDCR(0);
		chan->SetOCR(0);
		chan->SetSCR(0);
		chan->SetCCR(0);
		chan->csr = 0;
		chan->active = false;
		chan->cpr = 0;
		chan->cer = 0;

		chan->niv = 0x0f;
		chan->eiv = 0x0f;

		chan->priority = 0;
	}
	syncer->NotifyDMACActive(false);
	ChangeInterrupt();

	// イベントを停止
	event->SetName("DMAC");
	scheduler->StopEvent(event);
}

busdata
DMACDevice::Read(busaddr addr)
{
	uint32 paddr = addr.Addr();
	uint32 reqsize = addr.GetSize();
	uint32 datasize = std::min(2 - (paddr & 1U), reqsize);
	busdata data;

	if (datasize == 1) {
		data = Peek1(paddr);
	} else {
		data  = Peek1(paddr) << 8;
		data |= Peek1(paddr + 1);
	}

	if (__predict_false(loglevel >= 3)) {
		uint32 offset = paddr & 0xff;
		uint ch = offset / 0x40;
		uint n  = offset % 0x40;
		DMACChan *chan = channels[ch].get();
		if (datasize == 1) {
			if (regname1[n]) {
				chan->putlogn("%s -> $%02x", regname1[n], data.Data());
			} else if (offset == 0xff) {
				putlogn("GCR -> $%02x", data.Data());
			}
		} else {
			if (regname2[n / 2]) {
				chan->putlogn("%s -> $%04x", regname2[n / 2], data.Data());
			} else if (offset == 0xfe) {
				putlogn("GCR(W)  -> $%04x", data.Data());
			}
		}
	}

	data |= read_wait;
	data |= busdata::Size(datasize);
	return data;
}

busdata
DMACDevice::Write(busaddr addr, uint32 data)
{
	uint32 paddr = addr.Addr();
	uint32 reqsize = addr.GetSize();
	uint32 datasize = std::min(2 - (paddr & 1U), reqsize);
	data >>= (reqsize - datasize) * 8;

	uint32 offset = paddr & 0xff;
	uint32 ch = offset / 0x40;
	uint32 n  = offset % 0x40;
	DMACChan *chan = channels[ch].get();
	if (datasize == 1) {
		WriteByte(chan, n, data);
	} else {
		WriteWord(chan, n, data);
	}

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

// STR か ACT が立っている時に書き込むとタイミングエラー
#define TIMING_ERR_IF_RUNNING	do {	\
	if (chan->str || chan->active) {	\
		Error(chan, DMAC::CER_TIMING);	\
		break;	\
	}	\
} while (0)

// ログ表示版
#define TIMING_ERR_IF_RUNNING_log(regname)	do {	\
	if (chan->str || chan->active) {	\
		chan_putlog(chan, 2, "%s <- $%02x", regname, data);	\
		Error(chan, DMAC::CER_TIMING);	\
		break;	\
	}	\
} while (0)

// バイト書き込み。
busdata
DMACDevice::WriteByte(DMACChan *chan, uint32 n, uint32 data)
{
	uint ch = chan->ch;

	switch (n) {
	 case DMAC::CSR:
		WriteCSR(chan, data);
		break;

	 case DMAC::CER:
		// Read only
		break;

	 case DMAC::DCR:
		chan_putlog(chan, 2, "%s <- $%02x", regname1[n], data);
		TIMING_ERR_IF_RUNNING;
		chan->SetDCR(data);
		break;

	 case DMAC::OCR:
		chan_putlog(chan, 2, "%s <- $%02x", regname1[n], data);
		TIMING_ERR_IF_RUNNING;
		chan->SetOCR(data);
		break;

	 case DMAC::SCR:
		chan_putlog(chan, 2, "%s <- $%02x", regname1[n], data);
		TIMING_ERR_IF_RUNNING;
		chan->SetSCR(data);
		break;

	 case DMAC::CCR:
	 {
		// %0 -> %1 に変化したビットだけを評価する。
		uint8 up = (chan->GetCCR() ^ data) & data;
		// バイトサイズのログを (up 込みで) 表示。
		chan_putlog(chan, 2, "CCR <- $%02x (up=$%02x)", data, up);
		// CCR を更新してから WriteCCR() で動作をする。
		chan->SetCCR(data);
		WriteCCR(chan, data, up);
		break;
	 }

	 case DMAC::MTC:
		TIMING_ERR_IF_RUNNING_log("MTC:H");
		chan->mtc = (chan->mtc & 0x00ff) | (data << 8);
		chan_putlog(chan, 2, "MTC:H <- $%02x (MTC=$%04x)", data, chan->mtc);
		break;
	 case DMAC::MTC + 1:
		TIMING_ERR_IF_RUNNING_log("MTC:L");
		chan->mtc = (chan->mtc & 0xff00) | data;
		chan_putlog(chan, 2, "MTC:L <- $%02x (MTC=$%04x)", data, chan->mtc);
		break;

	 case DMAC::MAR:
		TIMING_ERR_IF_RUNNING_log("MAR:0");
		chan->mar = (chan->mar & 0x00ffffff) | (data << 24);
		chan_putlog(chan, 2, "MAR:0 <- $%02x (MAR=$%08x)", data, chan->mar);
		break;
	 case DMAC::MAR + 1:
		TIMING_ERR_IF_RUNNING_log("MAR:1");
		chan->mar = (chan->mar & 0xff00ffff) | (data << 16);
		chan_putlog(chan, 2, "MAR:1 <- $%02x (MAR=$%08x)", data, chan->mar);
		break;
	 case DMAC::MAR + 2:
		TIMING_ERR_IF_RUNNING_log("MAR:2");
		chan->mar = (chan->mar & 0xffff00ff) | (data << 8);
		chan_putlog(chan, 2, "MAR:2 <- $%02x (MAR=$%08x)", data, chan->mar);
		break;
	 case DMAC::MAR + 3:
		TIMING_ERR_IF_RUNNING_log("MAR:3");
		chan->mar = (chan->mar & 0xffffff00) | data;
		chan_putlog(chan, 2, "MAR:3 <- $%02x (MAR=$%08x)", data, chan->mar);
		break;

	 case DMAC::DAR:
		TIMING_ERR_IF_RUNNING_log("DAR:0");
		chan->dar = (chan->dar & 0x00ffffff) | (data << 24);
		chan_putlog(chan, 2, "DAR:0 <- $%02x (DAR=$%08x)", data, chan->dar);
		break;
	 case DMAC::DAR + 1:
		TIMING_ERR_IF_RUNNING_log("DAR:1");
		chan->dar = (chan->dar & 0xff00ffff) | (data << 16);
		chan_putlog(chan, 2, "DAR:1 <- $%02x (DAR=$%08x)", data, chan->dar);
		break;
	 case DMAC::DAR + 2:
		TIMING_ERR_IF_RUNNING_log("DAR:2");
		chan->dar = (chan->dar & 0xffff00ff) | (data << 8);
		chan_putlog(chan, 2, "DAR:2 <- $%02x (DAR=$%08x)", data, chan->dar);
		break;
	 case DMAC::DAR + 3:
		TIMING_ERR_IF_RUNNING_log("DAR:3");
		chan->dar = (chan->dar & 0xffffff00) | data;
		chan_putlog(chan, 2, "DAR:3 <- $%02x (DAR=$%08x)", data, chan->dar);
		break;

	 case DMAC::BTC:
		chan->btc = (chan->btc & 0x00ff) | (data << 8);
		chan_putlog(chan, 2, "BTC:H <- $%02x (BTC=$%04x)", data, chan->btc);
		break;
	 case DMAC::BTC + 1:
		chan->btc = (chan->btc & 0xff00) | data;
		chan_putlog(chan, 2, "BTC:L <- $%02x (BTC=$%04x)", data, chan->btc);
		break;

	 case DMAC::BAR:
		chan->bar = (chan->bar & 0x00ffffff) | (data << 24);
		chan_putlog(chan, 2, "BAR:0 <- $%02x (BAR=$%08x)", data, chan->bar);
		break;
	 case DMAC::BAR + 1:
		chan->bar = (chan->bar & 0xff00ffff) | (data << 16);
		chan_putlog(chan, 2, "BAR:1 <- $%02x (BAR=$%08x)", data, chan->bar);
		break;
	 case DMAC::BAR + 2:
		chan->bar = (chan->bar & 0xffff00ff) | (data << 8);
		chan_putlog(chan, 2, "BAR:2 <- $%02x (BAR=$%08x)", data, chan->bar);
		break;
	 case DMAC::BAR + 3:
		chan->bar = (chan->bar & 0xffffff00) | data;
		chan_putlog(chan, 2, "BAR:3 <- $%02x (BAR=$%08x)", data, chan->bar);
		break;

	 case DMAC::NIV:
		chan_putlog(chan, 2, "%s <- $%02x", regname1[n], data);
		chan->SetNIV(data);
		break;

	 case DMAC::EIV:
		chan_putlog(chan, 2, "%s <- $%02x", regname1[n], data);
		chan->SetEIV(data);
		break;

	 case DMAC::MFC:
		chan_putlog(chan, 2, "%s <- $%02x", regname1[n], data);
		TIMING_ERR_IF_RUNNING;
		chan->SetMFC(data);
		break;

	 case DMAC::CPR:
		chan_putlog(chan, 2, "%s <- $%02x", regname1[n], data);
		chan->SetCPR(data);
		break;

	 case DMAC::DFC:
		chan_putlog(chan, 2, "%s <- $%02x", regname1[n], data);
		TIMING_ERR_IF_RUNNING;
		chan->SetDFC(data);
		break;

	 case DMAC::BFC:
		chan_putlog(chan, 2, "%s <- $%02x", regname1[n], data);
		chan->SetBFC(data);
		break;

	 case DMAC::GCR:
		if (ch == 3) {
			WriteGCR(data);
		}
		break;

	 default:
		break;
	}

	return write_wait;
}

// ワード書き込み。
busdata
DMACDevice::WriteWord(DMACChan *chan, uint32 n, uint32 data)
{
	uint ch = chan->ch;

	switch (n) {
	 case DMAC::CSR:	// CSR | CER
		// XXX ログがバイトサイズのまま出るがどうするか
		WriteCSR(chan, data >> 8);
		// CER は Read Only
		break;

	 case DMAC::DCR:	// DCR | OCR
		chan_putlog(chan, 2, "%s <- $%04x", regname2[n / 2], data);
		TIMING_ERR_IF_RUNNING;
		chan->SetDCR(data >> 8);
		chan->SetOCR(data & 0xff);
		break;

	 case DMAC::SCR:	// SCR | CCR
	 {
		// CCR の %0 -> %1 に変化したビットだけを取り出す。
		uint8 up = (chan->GetCCR() ^ data) & data;
		// ワードサイズのログを (CCR の up 込みで) 表示。
		chan_putlog(chan, 2, "SCR:CCR <- $%04x (up=$%02x)", data, up);
		// 両方のレジスタの値を更新。
		chan->SetSCR(data >> 8);
		chan->SetCCR(data);
		// ワード書き込みで STR が立っているとタイミングエラー (p.17) は
		// ここで評価する。SCR 更新の後。
		TIMING_ERR_IF_RUNNING;
		// タイミングエラーでなければ CCR に基づいて動作。
		WriteCCR(chan, data, up);
		break;
	 }

	 case DMAC::MTC:
		chan_putlog(chan, 2, "%s <- $%04x", regname2[n / 2], data);
		TIMING_ERR_IF_RUNNING;
		chan->mtc = data;
		break;

	 case DMAC::MAR:
		TIMING_ERR_IF_RUNNING_log("MAR:H");
		chan->mar = (chan->mar & 0x0000ffff) | (data << 16);
		chan_putlog(chan, 2, "MAR:H <- $%04x (MAR=$%08x)", data, chan->mar);
		break;
	 case DMAC::MAR + 2:
		TIMING_ERR_IF_RUNNING_log("MAR:L");
		chan->mar = (chan->mar & 0xffff0000) | data;
		chan_putlog(chan, 2, "MAR:L <- $%04x (MAR=$%08x)", data, chan->mar);
		break;

	 case DMAC::DAR:
		TIMING_ERR_IF_RUNNING_log("DAR:H");
		chan->dar = (chan->dar & 0x0000ffff) | (data << 16);
		chan_putlog(chan, 2, "DAR:H <- $%04x (DAR=$%08x)", data, chan->dar);
		break;
	 case DMAC::DAR + 2:
		TIMING_ERR_IF_RUNNING_log("DAR:L");
		chan->dar = (chan->dar & 0xffff0000) | data;
		chan_putlog(chan, 2, "DAR:L <- $%04x (DAR=$%08x)", data, chan->dar);
		break;

	 case DMAC::BTC:
		chan_putlog(chan, 2, "%s <- $%04x", regname2[n / 2], data);
		TIMING_ERR_IF_RUNNING;
		chan->btc = data;
		break;

	 case DMAC::BAR:
		TIMING_ERR_IF_RUNNING_log("BAR:H");
		chan->bar = (chan->bar & 0x0000ffff) | (data << 16);
		chan_putlog(chan, 2, "BAR:H <- $%04x (BAR=$%08x)", data, chan->bar);
		break;
	 case DMAC::BAR + 2:
		TIMING_ERR_IF_RUNNING_log("BAR:L");
		chan->bar = (chan->bar & 0xffff0000) | data;
		chan_putlog(chan, 2, "BAR:L <- $%04x (BAR=$%08x)", data, chan->bar);
		break;

	 case DMAC::NIV & ~1U:
		chan_putlog(chan, 2, "%s <- $%04x", regname2[n / 2], data);
		chan->SetNIV(data);
		break;

	 case DMAC::EIV & ~1U:
		chan_putlog(chan, 2, "%s <- $%04x", regname2[n / 2], data);
		chan->SetEIV(data);
		break;

	 case DMAC::MFC & ~1U:
		chan_putlog(chan, 2, "%s <- $%04x", regname2[n / 2], data);
		TIMING_ERR_IF_RUNNING;
		chan->SetMFC(data);
		break;

	 case DMAC::CPR & ~1U:
		chan_putlog(chan, 2, "%s <- $%04x", regname2[n / 2], data);
		chan->SetCPR(data);
		break;

	 case DMAC::DFC & ~1U:
		chan_putlog(chan, 2, "%s <- $%04x", regname2[n / 2], data);
		TIMING_ERR_IF_RUNNING;
		chan->SetDFC(data);
		break;

	 case DMAC::BFC & ~1U:
		chan_putlog(chan, 2, "%s <- $%04x", regname2[n / 2], data);
		chan->SetBFC(data);
		break;

	 case DMAC::GCR & ~1U:
		if (ch == 3) {
			WriteGCR(data);
		}
		break;

	 default:
		break;
	}

	return write_wait;
}

busdata
DMACDevice::Peek1(uint32 addr)
{
	uint8 data;

	uint32 offset = addr & 0xff;
	uint ch = offset / 0x40;
	uint n = offset % 0x40;
	DMACChan *chan = channels[ch].get();

	switch (n) {
	 case DMAC::CSR:
		data = chan->GetCSR();
		break;

	 case DMAC::CER:
		data = chan->cer;
		break;

	 case DMAC::DCR:
		data = chan->GetDCR();
		break;

	 case DMAC::OCR:
		data = chan->GetOCR();
		break;

	 case DMAC::SCR:
		data = chan->GetSCR();
		break;

	 case DMAC::CCR:
		data = chan->GetCCR();
		break;

	 case DMAC::MTC:
		data = chan->mtc >> 8;
		break;
	 case DMAC::MTC + 1:
		data = chan->mtc & 0xff;
		break;

	 case DMAC::MAR:
		data = chan->mar >> 24;
		break;
	 case DMAC::MAR + 1:
		data = (chan->mar >> 16) & 0xff;
		break;
	 case DMAC::MAR + 2:
		data = (chan->mar >> 8) & 0xff;
		break;
	 case DMAC::MAR + 3:
		data = chan->mar & 0xff;
		break;

	 case DMAC::DAR:
		data = chan->dar >> 24;
		break;
	 case DMAC::DAR + 1:
		data = (chan->dar >> 16) & 0xff;
		break;
	 case DMAC::DAR + 2:
		data = (chan->dar >> 8) & 0xff;
		break;
	 case DMAC::DAR + 3:
		data = chan->dar & 0xff;
		break;

	 case DMAC::BTC:
		data = chan->btc >> 8;
		break;
	 case DMAC::BTC + 1:
		data = chan->btc & 0xff;
		break;

	 case DMAC::BAR:
		data = chan->bar >> 24;
		break;
	 case DMAC::BAR + 1:
		data = (chan->bar >> 16) & 0xff;
		break;
	 case DMAC::BAR + 2:
		data = (chan->bar >> 8) & 0xff;
		break;
	 case DMAC::BAR + 3:
		data = chan->bar & 0xff;
		break;

	 case DMAC::NIV:
		data = chan->niv;
		break;

	 case DMAC::EIV:
		data = chan->eiv;
		break;

	 case DMAC::MFC:
		data = chan->mfc.GetFC();
		break;

	 case DMAC::CPR:
		data = chan->cpr;
		break;

	 case DMAC::DFC:
		data = chan->dfc.GetFC();
		break;

	 case DMAC::BFC:
		data = chan->bfc.GetFC();
		break;

	 case DMAC::GCR:
		if (ch == 3) {
			data = gcr;
		} else {
			data = 0xff;
		}
		break;

	 default:
		data = 0xff;
		break;
	}

	return data;
}

void
DMACDevice::MonitorScreen(Monitor *, TextScreen& screen)
{
	int x;
	int y;

	screen.Clear();

	y = 1;
	screen.Puts(0, y++, "BaseAddress");
	screen.Print(0, y++, "+$%02x CSR:", DMAC::CSR);
	y += 2;
	screen.Print(0, y++, "+$%02x CER:", DMAC::CER);
	screen.Print(0, y++, "+$%02x DCR:", DMAC::DCR);
	screen.Print(5, y++, "%6s", ".XRM");
	screen.Print(5, y++, "%6s", ".DTYP");
	screen.Print(5, y++, "%6s", ".DPS");
	screen.Print(5, y++, "%6s", ".PCL");
	screen.Print(0, y++, "+$%02x OCR:", DMAC::OCR);
	screen.Print(5, y++, "%6s", ".DIR");
	screen.Print(5, y++, "%6s", ".SIZE");
	screen.Print(5, y++, "%6s", ".CHAIN");
	screen.Print(5, y++, "%6s", ".REQG");
	screen.Print(0, y++, "+$%02x SCR:", DMAC::SCR);
	screen.Print(5, y++, "%6s", ".MAC");
	screen.Print(5, y++, "%6s", ".DAC");
	screen.Print(0, y++, "+$%02x CCR:", DMAC::CCR);
	y += 2;
	screen.Print(0, y++, "+$%02x MTC:", DMAC::MTC);
	screen.Print(0, y++, "+$%02x MAR:", DMAC::MAR);
	screen.Print(0, y++, "+$%02x DAR:", DMAC::DAR);
	screen.Print(0, y++, "+$%02x BTC:", DMAC::BTC);
	screen.Print(0, y++, "+$%02x BAR:", DMAC::BAR);
	screen.Print(0, y++, "+$%02x NIV:", DMAC::NIV);
	screen.Print(0, y++, "+$%02x EIV:", DMAC::EIV);
	screen.Print(0, y++, "+$%02x MFC:", DMAC::MFC);
	screen.Print(0, y++, "+$%02x CPR:", DMAC::CPR);
	screen.Print(0, y++, "+$%02x DFC:", DMAC::DFC);
	screen.Print(0, y++, "+$%02x BFC:", DMAC::BFC);

	for (auto& chan : channels) {
		uint val;
		int ch = chan->ch;
		x = 13 + ch * 17;
		y = 0;

		// 地味だけど有効なチャンネルをハイライトしてみる
		screen.Print(x, y++, (chan->active ? TA::Em : TA::Normal),
			"#%u (%s)", chan->ch, chan->desc);

		// アドレス
		screen.Print(x, y++, "$%06x", baseaddr + ch * 0x40);

		// CSR
		val = chan->GetCSR();
		screen.Print(x, y++, "$%02x", val);
		static const char * const csrname[] = {
			"COC", "BTC", "NDT", "ERR", "ACT", "DIT", "PCT", "PCS",
		};
		MonitorReg4(screen, x, y++, val >> 4,   &csrname[0]);
		MonitorReg4(screen, x, y++, val & 0xff, &csrname[4]);

		// CER
		val = chan->cer;
		const char *e;
		if (val <= 0x11 && errnames[val]) {
			e = errnames[val];
		} else {
			e = "?";
		}
		screen.Print(x, y, "$%02x", chan->cer);
		if (val != 0) {
			screen.Print(x + 3, y, ":%s", e);
		}
		y++;

		// DCR
		screen.Print(x, y++, "$%02x", chan->GetDCR());

		// DCR:XRM
		static const char * const dcr_xrm[] = {
			//1234567890123
			"Burst",
			"undefined",
			"CycleW/O Hold",
			"CycleWithHold",
		};
		val = chan->GetXRM();
		screen.Print(x, y++, "%u:%s", val, dcr_xrm[val]);

		// DCR:DTYP
		static const char * const dcr_dtyp[] = {
			//1234567890123
			"68000",
			"6800",
			"ACK",
			"ACK+READY",
		};
		val = chan->GetDTYP();
		screen.Print(x, y++, "%u:%s", val, dcr_dtyp[val]);

		// DCR:DPS
		static const char * const dcr_dps[] = {
			//1234567890123
			"8bit",
			"16bit",
		};
		val = chan->GetDPS();
		screen.Print(x, y++, "%u:%s", val, dcr_dps[val]);

		// DCR:PCL
		static const char * const dcr_pcl_name[] = {
			//1234567890123
			"Status",
			"StatusInt",
			"Pulse(output)",
			"Abort",
		};
		val = chan->dcr_pcl;
		screen.Print(x, y++, "%u:%s", val, dcr_pcl_name[val]);

		// OCR
		screen.Print(x, y++, "$%02x", chan->GetOCR());

		// OCR:DIR
		static const char * const ocr_dir[] = {
			//1234567890123
			"MemoryToDevice",
			"DeviceToMemory",
		};
		val = chan->GetDIR();
		screen.Print(x, y++, "%u:%s", val, ocr_dir[val]);

		// OCR:SIZE
		static const char * const ocr_size[] = {
			//1234567890123
			"8bit (Packed)",
			"16bit",
			"32bit",
			"8bit Unpacked",
		};
		val = chan->GetSIZE();
		screen.Print(x, y++, "%u:%s", val, ocr_size[val]);

		// OCR:CHAIN
		static const char * const ocr_chain[] = {
			//1234567890123
			"NoChain",
			"undefined",
			"ArrayChain",
			"LinkArrayChain",
		};
		val = chan->GetCHAIN();
		screen.Print(x, y++, "%u:%s", val, ocr_chain[val]);

		// OCR:REQG
		static const char * const ocr_reqg[] = {
			//1234567890123
			"AutoReq(Limit)",
			"AutoReq(Max)",
			"ExternalReq",
			"AutoThenExt",
		};
		val = chan->GetREQG();
		screen.Print(x, y++, "%u:%s", val, ocr_reqg[val]);

		// SCR
		screen.Print(x, y++, "$%02x", chan->GetSCR());

		// SCR:MAC,DAC
		static const char * const scr_xac[] = {
			//1234567890123
			"NoCount",
			"CountUp",
			"CountDown",
			"undefined",
		};
		val = chan->GetMAC();
		screen.Print(x, y++, "%u:%s", val, scr_xac[val]);
		val = chan->GetDAC();
		screen.Print(x, y++, "%u:%s", val, scr_xac[val]);

		// CCR
		val = chan->GetCCR();
		screen.Print(x, y++, "$%02x", val);
		static const char * const ccrnames[] = {
			"STR", "CNT", "HLT", "SAB", "INT", "-", "-", "-",
		};
		MonitorReg4(screen, x, y++, (val >> 4),   &ccrnames[0]);
		MonitorReg4(screen, x, y++, (val & 0xff), &ccrnames[4]);

		// MTC
		screen.Print(x, y++, "$%04x", chan->mtc);
		// MAR
		// X680x0 がターゲットなので24bit超える設定は目立たせる
		if (chan->mar > 0xffffff) {
			screen.Print(x, y++, TA::On, "$%08x", chan->mar);
		} else {
			screen.Print(x, y++, "$%06x", chan->mar);
		}
		// DAR
		if (chan->dar > 0xffffff) {
			screen.Print(x, y++, TA::On, "$%08x", chan->dar);
		} else {
			screen.Print(x, y++, "$%06x", chan->dar);
		}
		// BTC
		screen.Print(x, y++, "$%04x", chan->btc);
		// BAR
		if (chan->bar > 0xffffff) {
			screen.Print(x, y++, TA::On, "$%08x", chan->bar);
		} else {
			screen.Print(x, y++, "$%06x", chan->bar);
		}

		// NIV
		screen.Print(x, y++, "$%02x", chan->niv);
		// EIV
		screen.Print(x, y++, "$%02x", chan->eiv);

		// MFC
		screen.Print(x, y++, "$%02x", chan->mfc.GetFC());
		// CPR
		screen.Print(x, y++, "$%02x", chan->cpr);
		// DFC
		screen.Print(x, y++, "$%02x", chan->dfc.GetFC());
		// BFC
		screen.Print(x, y++, "$%02x", chan->bfc.GetFC());

		if (ch == 3) {
			screen.Print(x - 10, y++, "+$3f GCR: $%02x", gcr);
		}
	}
}

// 4ビット分を表示する。MonitorScreen の下請け
void
DMACDevice::MonitorReg4(TextScreen& screen,
	int x, int y, uint32 reg, const char * const *names)
{
	for (uint i = 0; i < 4; i++) {
		if (names[i][0] == '-') {
			screen.Puts(x + i * 4, y, TA::Disable, "---");
		} else {
			bool b = reg & (1U << (3 - i));
			screen.Puts(x + i * 4, y, TA::OnOff(b), names[i]);
		}
	}
}

/*static*/ const char * const
DMACDevice::errnames[] = {
	//12345678901
	"",				// $00 No Error
	"ConfigErr",	// $01
	"OperTiming",	// $02
	NULL,			// $03
	NULL,			// $04
	"AddrErrInMAR",	// $05
	"AddrErrInDAR",	// $06
	"AddrErrInBAR",	// $07
	NULL,			// $08
	"BusErrInMAR",	// $09
	"BusErrInDAR",	// $0a
	"BusErrInBAR",	// $0b
	NULL,			// $0c
	"CntErrInMTC",	// $0d
	NULL,			// $0e
	"CntErrInBTC",	// $0f
	"ExternAbort",	// $10
	"SoftAbort",	// $11
};

// CSR への書き込み。
void
DMACDevice::WriteCSR(DMACChan *chan, uint32 data)
{
	// 上位3ビットと DIT は %1 の書き込みでクリア
	chan->csr &= ~(data & 0xe4);

	// ERR も %1 の書き込みでクリア。
	// このとき CER もクリアする。
	if ((data & DMAC::CSR_ERR)) {
		chan->csr &= ~DMAC::CSR_ERR;
		chan->cer = 0;
	}

	// PCT も %1 の書き込みでクリア。
	// PCT は PCL が High → Low の時セットなので pcl_prev を下げればよい
	if ((data & DMAC::CSR_PCT)) {
		chan->pcl_prev = false;
	}

	chan_putlog(chan, 2, "CSR <- $%02x (CSR = $%02x)", data, chan->GetCSR());

	ChangeInterrupt();
}

// GCR への書き込み。
void
DMACDevice::WriteGCR(uint8 data)
{
	putlog(2, "GCR <- $%02x", data);
	gcr = data & 0x0f;
}

// CCR への書き込み。これだけいろいろ変則的なので注意。
// up は data のうち %0 -> %1 に変化したビット。
// SetCCR() 実行済み、ログは出力済み。
void
DMACDevice::WriteCCR(DMACChan *chan, uint32 data, uint8 up)
{
	// コンフィギュレーションエラーのチェック。
	// データシート p43 Error Conditions (a)
	if ((up & DMAC::CCR_STR)) {
		// (i) The CNT bit is set at the same time STR bit in the chaining mode.
		if ((up & DMAC::CCR_CNT) && chan->IsChain()) {
			Error(chan, DMAC::CER_CONFIG);
			return;
		}

		if (chan->IsSingleAddress()) {
			// (ii) DTYP specifies a single addressing mode, and
			//      the device port size is not the same as the operand size.
			if (chan->GetDPS() == DMAC::DPS_8BIT) {
				if (!(chan->GetSIZE() == DMAC::SIZE_8BIT_PACK ||
				      chan->GetSIZE() == DMAC::SIZE_8BIT_UNPK))
				{
					Error(chan, DMAC::CER_CONFIG);
					return;
				}
			} else {
				if (!(chan->GetSIZE() == DMAC::SIZE_16BIT)) {
					Error(chan, DMAC::CER_CONFIG);
					return;
				}
			}
		} else {
			// (iii) DTYP specifies a dual addressing mode, DPS is 16 bits,
			//       SIZE is 8 bits and REQG is "10" or "11".
			if (chan->GetDPS() == DMAC::DPS_16BIT) {
				// XXX UNPK は?
				if (chan->GetSIZE() == DMAC::SIZE_8BIT_PACK) {
					if (chan->GetREQG() == DMAC::REQG_EXTERNAL ||
						chan->GetREQG() == DMAC::REQG_AUTOFIRST)
					{
						Error(chan, DMAC::CER_CONFIG);
						return;
					}
				}
			}
		}

		// (iv) An undefined configuration is set in the registers.
		if (chan->GetXRM() == 1 ||
			chan->GetMAC() == 3 ||
			chan->GetDAC() == 3 ||
			chan->GetCHAIN() == DMAC::CHAIN_reserved)
		{
			Error(chan, DMAC::CER_CONFIG);
			return;
		}
		if (chan->IsSingleAddress() == false &&
			chan->GetSIZE() == DMAC::SIZE_8BIT_UNPK &&
			chan->GetDPS() != DMAC::DPS_8BIT)
		{
			Error(chan, DMAC::CER_CONFIG);
			return;
		}

		// (e) Count Error
		// ここでは (i), (ii) のみ
		if (chan->IsChain() == false && chan->mtc == 0) {
			Error(chan, DMAC::CER_COUNT_MTC);
			return;
		}
		if (chan->GetCHAIN() == DMAC::CHAIN_ARRAY && chan->btc == 0) {
			Error(chan, DMAC::CER_COUNT_BTC);
			return;
		}
	}

	// SAB (Software Abort)
	if ((up & DMAC::CCR_SAB)) {
		AbortTransfer(chan);
	}

	// HLT (Halt Operation)
	if ((up & DMAC::CCR_HLT)) {
		putlog(0, "CCR HLT (NOT IMPLEMENTED)");
	}

	// CNT (Continue Operation)
	if ((up & DMAC::CCR_CNT)) {
		// Opeation Timing Error (i)
		if (chan->IsChain() && chan->active) {
			Error(chan, DMAC::CER_TIMING);
			return;
		}
		if (chan->str == false && chan->active == false) {
			Error(chan, DMAC::CER_TIMING);
			return;
		}
		putlog(0, "CCR CNT (NOT IMPLEMENTED)");
	}

	// STR (Start Operation)
	if ((up & DMAC::CCR_STR)) {
		StartTransfer(chan);
	}

	ChangeInterrupt();
}

// CSR.ACT ビットの状態を変更。
void
DMACDevice::ChangeACT(DMACChan *chan, bool active)
{
	chan->active = active;

	// チャンネルの ACT を変更した結果、
	// トータルでアクティブかどうかを Syncer に通知する。
	bool dmac_active =
		channels[0]->active ||
		channels[1]->active ||
		channels[2]->active ||
		channels[3]->active;
	syncer->NotifyDMACActive(dmac_active);
}

// 転送開始
void
DMACDevice::StartTransfer(DMACChan *chan)
{
	// Operation Timing Error (ii)
	// CSR の ACT,COC,BTC,NDT,ERR が立ってたら開始しない
	if (chan->active || (chan->csr & 0xf0) != 0) {
		Error(chan, DMAC::CER_TIMING);
		return;
	}

	// ACT を立てたら STR を下げる。
	ChangeACT(chan, true);
	chan->str = false;

	// XXX あとで移動する
	switch (chan->GetMAC()) {
	 case DMAC::SCR_COUNT_UP:
		chan->mac = 1;
		break;
	 case DMAC::SCR_COUNT_DOWN:
		chan_putlog(chan, 0, "SCR:MAC CountDown Mode (NOT SUPPORTED)");
		chan->mac = -1;
		break;
	 default:
		chan->mac = 0;
		break;
	}
	switch (chan->GetDAC()) {
	 case DMAC::SCR_COUNT_UP:
		chan->dac = 1;
		break;
	 case DMAC::SCR_COUNT_DOWN:
		chan_putlog(chan, 0, "SCR:DAC CountDown Mode (NOT SUPPORTED)");
		chan->dac = -1;
		break;
	 default:
		chan->dac = 0;
		break;
	}

	chan->data.Clear();

	// 転送モード
	uint reqg = chan->GetREQG();
	switch (reqg) {
	 case DMAC::REQG_AUTO_LIM:
		VMPANIC("REQG_AUTO_LIM 未実装");
		break;

	 case DMAC::REQG_AUTO_MAX:
		// 自発的に転送を開始する
		chan->req = true;
		break;

	 case DMAC::REQG_EXTERNAL:
		// 外部リクエスト転送。ここでは何もしなくてよい
		break;

	 case DMAC::REQG_AUTOFIRST:
		VMPANIC("未実装 REQG");
		break;

	 default:
		VMPANIC("corrupted reqg=%u", reqg);
	}

	// チェインモードなら初回読み込み。
	// 通常モードなら何もしなくていいので、ログを出すだけ。
	switch (chan->GetCHAIN()) {
	 case DMAC::CHAIN_reserved:
		// とりあえず DISABLED と同じ動作にしておくか。
		chan_putlog(chan, 1, "Undefined CHAIN=1 (ignored)");
		FALLTHROUGH;
	 case DMAC::CHAIN_DISABLED:
		chan_putlog(chan, 1, "%s", MakeStartLog(chan).c_str());
		break;

	 case DMAC::CHAIN_ARRAY:
		chan_putlog(chan, 0, "Array Chaining Mode (NOT SUPPORTED)");
		return;

	 case DMAC::CHAIN_LINKARRAY:
		// ブロック開始時に BAR から1エントリ読んで、そのリンクアドレスが
		// $00000000 ならこのブロックが最後と判断する、と書いてあるように
		// 読めるので、開始時 BAR が $0000000 なら 0 番地から読むだろうか。
		if (LoadLinkArray(chan) == false) {
			return;
		}
		break;

	 default:
		__unreachable();
	}

	if (event->IsRunning() == false) {
		CallAfter(StartCallback, 0, 0);
	}
}

// 転送開始ログを返す。
// 全パラメータは無理なので概要だけ表示。
std::string
DMACDevice::MakeStartLog(DMACChan *chan)
{
	static const char * const countstr[] = {
		"-",
		"",
		"+",
	};

	std::string scrmsg;
	if (chan->GetDIR() == DMAC::DIR_MtoD) {
		scrmsg = string_format("$%06x%s to $%06x%s",
			chan->mar, countstr[chan->mac + 1],
			chan->dar, countstr[chan->dac + 1]);
	} else {
		scrmsg = string_format("$%06x%s to $%06x%s",
			chan->dar, countstr[chan->dac + 1],
			chan->mar, countstr[chan->mac + 1]);
	}

	return string_format("Start %s mtc=$%04x", scrmsg.c_str(), chan->mtc);
}

// BAR の指すリンクアレイチェインのブロックエントリを読み込む。
// 読めたら true を返す。エラーなら Error をセットして false を返す。
bool
DMACDevice::LoadLinkArray(DMACChan *chan)
{
	uint32 orig_bar = chan->bar;
	busdata data;

	// リンクアレイチェインの BAR は偶数から始まり、
	// +$0.L アドレス(MAR)
	// +$4.W カウント(MTC)
	// +$6.L 次のリンクアドレス(BAR)

	busaddr bar_addr = busaddr(chan->bar) | chan->bfc;
	if ((bar_addr.Addr() & 1U) != 0) {
		goto error;
	}

	data = ReadMem4(bar_addr);
	if (__predict_false(data.IsBusErr())) {
		goto error;
	}
	chan->mar = data.Data();

	bar_addr += 4;
	bar_addr.ChangeSize(2);
	data = ReadMem(bar_addr);
	if (__predict_false(data.IsBusErr())) {
		goto error;
	}
	chan->mtc = data.Data();

	bar_addr += 2;
	data = ReadMem4(bar_addr);
	if (__predict_false(data.IsBusErr())) {
		goto error;
	}
	chan->bar = data.Data();

	if (__predict_false(chan->loglevel >= 1)) {
		chan_putlog(chan, 2, "%s bar=%06x next=%06x",
			__func__, orig_bar, chan->bar);
		std::string msg = MakeStartLog(chan);
		chan->putlogn("%s", msg.c_str());
	}

	return true;

 error:
	Error(chan, DMAC::CER_ADDR_BAR);
	return false;
}

// 今回転送するチャンネルを決定して返す。なければ NULL を返す。
DMACChan *
DMACDevice::SelectChannel()
{
	int ch = -1;
	uint8 prio = 255;

	for (auto& chan : channels) {
		if (chan->active) {
			uint8 p = chan->priority;
			if (p < prio) {
				prio = p;
				// 実効プライオリティのラウンドロビン用のところを上げておく
				chan->priority = (p & 0xf0) | ((p & 0x0f) >> 1);
				ch = chan->ch;
			}
		}
	}
	if (ch == -1) {
		// 転送チャンネルはもうないのでイベントは停止したままにする
		event->SetName("DMAC");
		return NULL;
	} else {
		DMACChan *chan = channels[ch].get();
		event->SetName(string_format("DMAC #%u", chan->ch));
		return chan;
	}
}

// 転送開始 (MTC 1回分の開始)
void
DMACDevice::StartCallback(Event *ev)
{
	// チャンネルを決定
	auto chan = SelectChannel();
	if (chan == NULL) {
		return;
	}

	// 実効プライオリティを同順位の最後に回す
	chan->priority |= 0x08;

	chan->retry = 0;

	// XXX バスアービトレーションとか

	// 転送シーケンス決定
	chan->seq.clear();
	chan->seq_index = 0;
	if (chan->GetDIR() == DMAC::DIR_MtoD) {
		if (chan->GetDPS() == DMAC::DPS_8BIT) {
			if (chan->GetSIZE() == DMAC::SIZE_32BIT) {
				chan->seq.push_back(RD_M16);
				chan->seq.push_back(WR_D8);
				chan->seq.push_back(WR_D8);
				chan->seq.push_back(RD_M16);
				chan->seq.push_back(WR_D8);
				chan->seq.push_back(WR_D8);
			} else if (chan->GetSIZE() == DMAC::SIZE_16BIT) {
				chan->seq.push_back(RD_M16);
				chan->seq.push_back(WR_D8);
				chan->seq.push_back(WR_D8);
			} else {
				chan->seq.push_back(RD_M8);
				chan->seq.push_back(WR_D8);
			}
		} else {
			// DPS==16
			if (chan->GetSIZE() == DMAC::SIZE_32BIT) {
				chan->seq.push_back(RD_M16);
				chan->seq.push_back(WR_D16);
				chan->seq.push_back(RD_M16);
				chan->seq.push_back(WR_D16);
			} else if (chan->GetSIZE() == DMAC::SIZE_16BIT) {
				chan->seq.push_back(RD_M16);
				chan->seq.push_back(WR_D16);
			} else {
				chan->seq.push_back(RD_M8);
				chan->seq.push_back(WR_D8);
			}
		}
	} else {	// DtoM
		if (chan->GetDPS() == DMAC::DPS_8BIT) {
			if (chan->GetSIZE() == DMAC::SIZE_32BIT) {
				chan->seq.push_back(RD_D8);
				chan->seq.push_back(RD_D8);
				chan->seq.push_back(WR_M16);
				chan->seq.push_back(RD_D8);
				chan->seq.push_back(RD_D8);
				chan->seq.push_back(WR_M16);
			} else if (chan->GetSIZE() == DMAC::SIZE_16BIT) {
				chan->seq.push_back(RD_D8);
				chan->seq.push_back(RD_D8);
				chan->seq.push_back(WR_M16);
			} else {
				chan->seq.push_back(RD_D8);
				chan->seq.push_back(WR_M8);
			}
		} else {
			// DPS==16
			if (chan->GetSIZE() == DMAC::SIZE_32BIT) {
				chan->seq.push_back(RD_D16);
				chan->seq.push_back(WR_M16);
				chan->seq.push_back(RD_D16);
				chan->seq.push_back(WR_M16);
			} else if (chan->GetSIZE() == DMAC::SIZE_16BIT) {
				chan->seq.push_back(RD_D16);
				chan->seq.push_back(WR_M16);
			} else {
				chan->seq.push_back(RD_D8);
				chan->seq.push_back(WR_M8);
			}
		}
	}

	if (__predict_false(chan->loglevel >= 5)) {
		std::string ss;
		for (const auto op : chan->seq) {
			ss += ' ';
			ss += seqname[op];
		}
		chan->putlogn("%s seq:%s", __func__, ss.c_str());
	}

	CallAfter(TransferCallback, 1, 0);
}

// 転送処理 (内部シーケンス)
void
DMACDevice::TransferCallback(Event *ev)
{
	// チャンネルを決定
	auto chan = SelectChannel();
	if (chan == NULL) {
		return;
	}

	uint op = chan->seq[chan->seq_index];
	chan_putlog(chan, 5, "%s [%u] %s",
		__func__, chan->seq_index, seqname[op]);

	uint64 data;
	busdata r;

	// デバイスアクセスなら ACK#n 信号と DONE 信号のドライブ
	switch (op) {
	 case RD_D8:
	 case RD_D16:
	 case WR_D8:
	 case WR_D16:
		if (chan->req == false) {
			// REQ が立つまで待つ。構造上ポーリングするしか…。
			switch (chan->ch) {
			 case 0:
			 case 1:
			 case 2:
				chan_putlog(chan, 0, "%s Wait REQ", __func__);
				ev->time = 1_usec;
				break;
			 case 3:
				// ADPCM は最短でも 64_usec とかなので長めでよい。
				ev->time = 1_usec;
				break;
			 default:
				__unreachable();
			}
			scheduler->StartEvent(ev);
			return;
		}
		AssertACK(chan);
		break;
	 default:
		break;
	}

	// パック動作が起きるケースなら op を差し替える。
	if (chan->GetSIZE() == DMAC::SIZE_8BIT_PACK) {
		if (op == RD_M8) {
			if (chan->mtc > 1 && chan->mar % 2 == 0 && chan->GetMAC() != 0) {
				if (chan->data.Length() == 0) {
					op = RD_M16;
				} else {
					op = NOP;
				}
			}
		}
		if (op == WR_M8) {
			if (chan->data.Length() >= 2) {
				op = WR_M16;
			} else {
				if (chan->mtc > 1 && chan->mar % 2 == 0 && chan->GetMAC() != 0){
					op = NOP;
				}
			}
		}
	}

	// アドレスを生成。
	busaddr addr;
	switch (op) {
	 case RD_M8:
	 case RD_M16:
	 case WR_M8:
	 case WR_M16:
		addr = busaddr(chan->mar) | chan->mfc;
		break;
	 case RD_D8:
	 case RD_D16:
	 case WR_D8:
	 case WR_D16:
		addr = busaddr(chan->dar) | chan->dfc;
		break;
	 default:
		break;
	}

	// サイズを指定。
	switch (op) {
	 case RD_M8:
	 case RD_D8:
	 case WR_M8:
	 case WR_D8:
		addr.ChangeSize(1);
		break;
	 case RD_M16:
	 case RD_D16:
	 case WR_M16:
	 case WR_D16:
		addr.ChangeSize(2);
		break;
	 default:
		break;
	}


	// 書き込みならキューからデータを取得。
	switch (op) {
	 case WR_M8:
	 case WR_D8:
		data = chan->data.Dequeue();
		break;
	 case WR_M16:
	 case WR_D16:
		data  = chan->data.Dequeue() << 8;
		data |= chan->data.Dequeue();
		break;
	 default:
		data = 0;
		break;
	}

	// ユーザ空間へのアクセスが可能かどうかここで調べる。
	// 本来は DMAC ではなくメインバスで管理すべきことだが、パフォーマンスの
	// 観点から MPU 側に FC2 カットによる可動部を増やしたくないので、
	// アクセス頻度の低い DMAC 側で肩代わりしている。
	bool accessible =
		__predict_true(addr.IsSuper() || useraccess[addr.Addr() / 0x2000]);

	// 1回分のアクセス。
	switch (op) {
	 case RD_M8:
	 case RD_D8:
	 case RD_M16:
	 case RD_D16:
		if (accessible) {
			r = ReadMem(addr);
		} else {
			r.SetBusErr();
		}
		break;
	 case WR_M8:
	 case WR_D8:
	 case WR_M16:
	 case WR_D16:
		if (accessible) {
			r = WriteMem(addr, data);
		} else {
			r.SetBusErr();
		}
		break;
	 case NOP:
		r = 0;
		break;
	 default:
		VMPANIC("corrupted op=%u", op);
	}

	if (__predict_false(loglevel >= 4)) {
		TransferLog(chan, op, addr, r, data);
	}

	// デバイスアクセスなら ACK#n 信号と DONE 信号のドライブ
	switch (op) {
	 case RD_D8:
	 case RD_D16:
	 case WR_D8:
	 case WR_D16:
		NegateACK(chan);
		break;
	 default:
		break;
	}

	// バスエラー(もしくはリトライ)か。ACK を下ろした後で行う。
	if (r.IsOK() == false) {
		TransferError(chan, op, r, data);
		return;
	}

	// 読み込んだデータをキューに投入。バスエラー判定後に行う。
	switch (op) {
	 case RD_M8:
	 case RD_D8:
		chan->data.Enqueue(r.Data());
		break;
	 case RD_M16:
	 case RD_D16:
		chan->data.Enqueue(r.Data() >> 8);
		chan->data.Enqueue(r.Data() & 0xff);
		break;
	 default:
		break;
	}

	// カウンタを更新。バスエラー判定後に行う。
	switch (op) {
	 case RD_M8:
	 case WR_M8:
		chan->mar += chan->mac;
		break;
	 case RD_M16:
	 case WR_M16:
		chan->mar += chan->mac * 2;
		break;
	 case RD_D8:
	 case WR_D8:
		chan->dar += chan->dac * 2;
		break;
	 case RD_D16:
	 case WR_D16:
		chan->dar += chan->dac * 2;
		break;
	 case NOP:
		break;
	 default:
		VMPANIC("corrupted op=%u", op);
	}

	// シーケンスを一つ進める。完了すれば転送1回分。
	chan->seq_index++;
	if (chan->seq_index < chan->seq.size()) {
		// XXX ディレイはあとから見直す
		CallAfter(TransferCallback, 4, r.GetWait());
		return;
	}

	// ここで転送1回分が完了。
	chan->mtc -= 1;

	bool complete = false;
	if (__predict_false(chan->mtc == 0)) {
		// MTC 分転送すれば1ブロック完了。
		switch (chan->GetCHAIN()) {
		 case DMAC::CHAIN_DISABLED:
		 case DMAC::CHAIN_reserved:
		 case DMAC::CHAIN_ARRAY:	// not yet
			complete = true;
			break;

		 case DMAC::CHAIN_LINKARRAY:
			// 次のブロックを BAR から読む。BAR が 0 ならここで終了。
			if (chan->bar == 0) {
				complete = true;
			} else {
				if (LoadLinkArray(chan) == false) {
					return;
				}
			}
			break;
		 default:
			__unreachable();
		}
	}

	if (complete == false) {
		// XXX ディレイはあとから見直す
		CallAfter(StartCallback, 4, r.GetWait());
	} else {
		// すべての転送が完了。
		chan_putlog(chan, 1, "Complete Transfer");
		chan->csr |= DMAC::CSR_COC;
		ChangeACT(chan, false);
		ChangeInterrupt();
	}
}

// 転送エラー
void
DMACDevice::TransferError(DMACChan *chan, uint op, busdata r, uint32 data)
{
	if (r.IsRetry()) {
		// DTACK が出ていない (SPC)。
		// 本当は DTACK が出たことをコールバックしてくれれば
		// 効率がいいのだがポーリングでもかまわんだろう。
		chan->retry++;
		if (chan->retry < 100) {
			// 書き込みがリトライになったら仕方ないので、今書こうとして
			// 取り出したデータをキューに戻してから再実行。うーんこの。
			switch (op) {
			 case WR_M8:
			 case WR_D8:
				chan->data.PushFront(data);
				break;
			 case WR_M16:
			 case WR_D16:
				chan->data.PushFront(data);
				chan->data.PushFront(data >> 8);
				break;
			 default:
				break;
			}

			// 現在のイベントを再実行
			event->time = 1 * 80_nsec;
			scheduler->StartEvent(event);
			return;
		} else {
			// タイムアウトしたらバスエラーにフォールスルー
			chan_putlog(chan, 3, "retry exceeds");
		}
	}

	// バスエラー
	switch (op) {
	 case RD_M8:
	 case RD_M16:
	 case WR_M8:
	 case WR_M16:
		Error(chan, DMAC::CER_BUS_MAR);
		break;
	 case RD_D8:
	 case RD_D16:
	 case WR_D8:
	 case WR_D16:
		Error(chan, DMAC::CER_BUS_DAR);
		break;
	 default:
		VMPANIC("corrupted op=%u", op);
	}
}

// 1転送ごとのログを出力。
// ログを出力することが決まってから呼ばれる。
void
DMACDevice::TransferLog(DMACChan *chan, uint op, busaddr addr,
	busdata r, uint64 data)
{
	// Nop
	// Read  $%06x -> $xx
	// Read  $%06x -> BusErr
	// Read  $%06x -> $xx (1 retried)
	// Write $%06x <- $xx
	// Write $%06x <- $xx BusErr
	// Write $%06x <- $xx (1 retried)

	if (op == NOP) {
		chan->putlogn("Nop");
		return;
	}

	if (r.IsRetry()) {
		return;
	}

	const char *act;
	const char *dir;
	switch (op) {
	 case RD_M8:
	 case RD_D8:
	 case RD_M16:
	 case RD_D16:
		act = "Read ";
		dir = "->";
		break;
	 case WR_M8:
	 case WR_D8:
	 case WR_M16:
	 case WR_D16:
		act = "Write";
		dir = "<-";
		break;
	 default:
		act = "";
		dir = "";
		break;
	}

	std::string str = string_format("%s $%06x %s ", act, addr.Addr(), dir);

	switch (op) {
	 case RD_M8:
	 case RD_D8:
		if (r.IsOK()) {
			str += string_format("$%02x", r.Data());
		}
		break;
	 case RD_M16:
	 case RD_D16:
		if (r.IsOK()) {
			str += string_format("$%04x", r.Data());
		}
		break;
	 case WR_M8:
	 case WR_D8:
		str += string_format("$%02x", (uint32)data);
		break;
	 case WR_M16:
	 case WR_D16:
		str += string_format("$%04x", (uint32)data);
		break;
	 default:
		break;
	}

	if (r.IsBusErr()) {
		str += " BusErr";
	}

	if (chan->retry != 0) {
		str += string_format(" (%u retried)", chan->retry);
	}

	chan->putlogn("%s", str.c_str());
}

// エラー発生
void
DMACDevice::Error(DMACChan *chan, uint8 errcode)
{
	// データシート p.43
	chan->cer = errcode;
	chan->csr |= DMAC::CSR_COC | DMAC::CSR_ERR;
	ChangeACT(chan, false);
	chan->str = false;
	chan->cnt = false;
	ChangeInterrupt();
	CallAfter(StartCallback, 0, 0);
}

// ACK#n 信号線をアサート
void
DMACDevice::AssertACK(DMACChan *chan)
{
	switch (chan->ch) {
	 case 0:
		fdc->AssertDACK(chan->mtc == 1);
		break;
	 case 1:
	 case 2:
		// 接続されていない
		break;
	 case 3:
		adpcm->AssertDACK();
		break;
	 default:
		__unreachable();
	}
}

// ACK#n 信号線をネゲート
void
DMACDevice::NegateACK(DMACChan *chan)
{
	switch (chan->ch) {
	 case 0:
		fdc->NegateDACK();
		break;
	 case 1:
	 case 2:
		// 接続されていない
		break;
	 case 3:
		// 不要。
		break;
	 default:
		__unreachable();
	}
}

// REQ#n 信号線アサート (外部から呼ばれる)
void
DMACDevice::AssertREQ(uint ch)
{
	assert(ch < 4);

	DMACChan *chan = channels[ch].get();
	chan->req = true;

	// #3(ADPCM) の REQ は PCL にも繋がっている
	if (ch == 3) {
		chan->pcl_pin = true;
	}

	if (event->IsRunning() == false) {
		scheduler->StartEvent(event);
	}
}

// REQ#n 信号線ネゲート (外部から呼ばれる)
void
DMACDevice::NegateREQ(uint ch)
{
	assert(ch < 4);

	DMACChan *chan = channels[ch].get();
	chan->req = false;

	// #3(ADPCM) の REQ は PCL にも繋がっている
	if (ch == 3) {
		chan->pcl_pin = false;
	}
}

// ソフトウェアアボート
void
DMACDevice::AbortTransfer(DMACChan *chan)
{
	chan_putlog(chan, 2, "CCR SAB Software Abort");

	// %1 を書き込むことで実際には SAB はセットされるが、ERR が立つと
	// SAB をクリアする動作のため、書き込んだ %1 が読めることはない。
	// ここでは SAB ビットのセットを省略。

	chan->str = false;
	chan->cnt = false;
	ChangeACT(chan, false);

	chan->cer = DMAC::CER_SOFT_ABORT;
	// ERR ビットが立つと SAB をクリアする
	chan->csr |=  DMAC::CSR_ERR;
	chan->sab = false;
	ChangeInterrupt();
}

// メインメモリから 1 or 2 バイト読み込む。
busdata
DMACDevice::ReadMem(busaddr addr)
{
	busdata data = mainbus->Read(addr);

	// IODevice からの読み込みはポートサイズ単位で行う仕様なので、
	// ここでダイナミックバスサイジングの要領で必要な部分を抜き出す。
	// 実際の HD63450 は 68000 バス用のデバイスなので、このあたりの
	// バス変換を YUKI ちゃんなどの周辺回路がやってるはず。
	uint64 rearranged = MainbusDevice::DYNAMIC_BUS_SIZING_R(addr, data);
	data.ChangeData(rearranged);

	return data;
}

// メインメモリから4バイト読み込む。
// Size フィールドは指定しなくてもよい。
busdata
DMACDevice::ReadMem4(busaddr addr)
{
	addr.ChangeSize(2);

	busdata hi = ReadMem(addr);
	if (__predict_false(hi.IsBusErr())) {
		return hi;
	}

	addr += 2;
	busdata lo = ReadMem(addr);
	if (__predict_false(lo.IsBusErr())) {
		return lo;
	}

	busdata data((hi.Data() << 16) | lo.Data());
	return data;
}

// メインメモリに書き込む。
// ほぼ対称性のためだけ。
busdata
DMACDevice::WriteMem(busaddr addr, uint32 data)
{
	return mainbus->Write(addr, data);
}

// インデックス化されたアドレス start から end (の手前まで) のユーザ空間
// アクセスの可否を設定する。x68kio から呼ばれる。
// accessible true なら (MPU の FC2 ピンによらず) ユーザアクセス可能。
// アドレスは 8KB 単位なので start=1 ならアドレスは $2000。
void
DMACDevice::SetUdevice(uint32 start, uint32 end, bool accessible)
{
	for (int i = start; i < end; i++) {
		useraccess[i] = accessible;
	}
}

// 割り込み信号線の状態を変える。
void
DMACDevice::ChangeInterrupt()
{
	bool irq = false;

	for (auto& chan : channels) {
		if (chan->int_enable && chan->IsINTR()) {
			irq = true;
		}
	}
	interrupt->ChangeINT(this, irq);
}

// 割り込みアクノリッジ
busdata
DMACDevice::InterruptAcknowledge()
{
	int top = -1;
	uint8 prio = 255;

	// 割り込みを発生させているトッププライオリティのチャンネルを検索
	for (auto& chan : channels) {
		if (chan->int_enable && chan->IsINTR()) {
			if (chan->priority < prio) {
				prio = chan->priority;
				top = chan->ch;
			}
		}
	}

	if (__predict_true(top >= 0)) {
		DMACChan *chan = channels[top].get();
		if ((chan->csr & DMAC::CSR_ERR)) {
			return chan->eiv;
		} else {
			return chan->niv;
		}
	} else {
		return BusData::BusErr;
	}
}

//
// チャンネル
//

// コンストラクタ
DMACChan::DMACChan(int ch_, const char *desc_)
	: inherited(OBJ_DMAC_CH(ch_))
{
	ch = ch_;
	desc = desc_;

	ClearAlias();
	AddAlias(string_format("DMAC%u", ch));
}

// デストラクタ
DMACChan::~DMACChan()
{
}

// CSR を取得する
uint8
DMACChan::GetCSR() const
{
	uint32 val;

	val = csr & 0xf0;
	if (active) {
		val |= DMAC::CSR_ACT;
	}
	// PCT は PCL が High から Low に変わるとセット
	if (pcl_prev == true && pcl_pin == false) {
		val |= DMAC::CSR_PCT;
	}
	if (pcl_pin) {
		val |= DMAC::CSR_PCS;
	}
	return val;
}

// DCR を取得する
uint8
DMACChan::GetDCR() const
{
	uint8 val;

	val  = (xrm << 6);
	val |= (dtyp << 4);
	val |= (dps << 3);
	val |= dcr_pcl;

	return val;
}

// DCR を設定する
void
DMACChan::SetDCR(uint8 val)
{
	xrm  = (val >> 6);
	dtyp = (val >> 4) & 3;
	dps  = (val >> 3) & 1;
	dcr_pcl = val & 3;
}

// OCR を取得する
uint8
DMACChan::GetOCR() const
{
	uint8 val;

	val  = (dir << 7);
	val |= (size << 4);
	val |= (chain << 2);
	val |= reqg;

	return val;
}

// OCR を設定する
void
DMACChan::SetOCR(uint8 val)
{
	dir   = (val >> 7);
	size  = (val >> 4) & 3;
	chain = (val >> 2) & 3;
	reqg  = val & 3;
}

// SCR を取得する
uint8
DMACChan::GetSCR() const
{
	uint8 val;

	val  = scr_mac << 2;
	val |= scr_dac;

	return val;
}

// SCR を設定する
void
DMACChan::SetSCR(uint8 val)
{
	scr_mac = (val >> 2) & 3;
	scr_dac = val & 3;
}

// CCR を取得する
uint8
DMACChan::GetCCR() const
{
	uint8 val = 0;

	if (str) {
		val |= DMAC::CCR_STR;
	}
	if (cnt) {
		val |= DMAC::CCR_CNT;
	}
	if (hlt) {
		val |= DMAC::CCR_HLT;
	}
	if (sab) {
		val |= DMAC::CCR_SAB;
	}
	if (int_enable) {
		val |= DMAC::CCR_INT;
	}

	return val;
}

// CCR を設定する (値を置く以上のことはしない)
void
DMACChan::SetCCR(uint8 val)
{
	str = (val & DMAC::CCR_STR);
	cnt = (val & DMAC::CCR_CNT);
	hlt = (val & DMAC::CCR_HLT);
	sab = (val & DMAC::CCR_SAB);
	int_enable = (val & DMAC::CCR_INT);
}

// NIV を設定する
void
DMACChan::SetNIV(uint8 val)
{
	niv = val;
}

// EIV を設定する
void
DMACChan::SetEIV(uint8 val)
{
	eiv = val;
}

// MFC を設定する
void
DMACChan::SetMFC(uint8 val)
{
	mfc = busaddr::FC(val & 0x07);
}

// CPR を設定する
void
DMACChan::SetCPR(uint8 val)
{
	cpr = val & 0x03;
	// 実効プライオリティの上位4bitは cpr に等しい
	priority = cpr << 4;
}

// DFC を設定する
void
DMACChan::SetDFC(uint8 val)
{
	dfc = busaddr::FC(val & 0x07);
}

// BFC を設定する
void
DMACChan::SetBFC(uint8 val)
{
	bfc = busaddr::FC(val & 0x07);
}

// レジスタ名 (バイト)
/*static*/ const char * const
DMACDevice::regname1[0x40] = {
	"CSR",		// 00
	"CER",		// 01
	NULL,		// 02
	NULL,		// 03
	"DCR",		// 04
	"OCR",		// 05
	"SCR",		// 06
	"CCR",		// 07
	NULL,		// 08
	NULL,		// 09
	"MTC:H",	// 0a
	"MTC:L",	// 0b
	"MAR:0",	// 0c
	"MAR:1",	// 0d
	"MAR:2",	// 0e
	"MAR:3",	// 0f
	NULL,		// 10
	NULL,		// 11
	NULL,		// 12
	NULL,		// 13
	"DAR:0",	// 14
	"DAR:1",	// 15
	"DAR:2",	// 16
	"DAR:3",	// 17
	NULL,		// 18
	NULL,		// 19
	"BTC:H",	// 1a
	"BTC:L",	// 1b
	"BAR:0",	// 1c
	"BAR:1",	// 1d
	"BAR:2",	// 1e
	"BAR:3",	// 1f
	NULL,		// 20
	NULL,		// 21
	NULL,		// 22
	NULL,		// 23
	NULL,		// 24
	"NIV",		// 25
	NULL,		// 26
	"EIV",		// 27
	NULL,		// 28
	"MFC",		// 29
	NULL,		// 2a
	NULL,		// 2b
	NULL,		// 2c
	"CPR",		// 2d
	NULL,		// 2e
	NULL,		// 2f
	NULL,		// 30
	"DFC",		// 31
	NULL,		// 32
	NULL,		// 33
	NULL,		// 34
	NULL,		// 35
	NULL,		// 36
	NULL,		// 37
	NULL,		// 38
	"BFC",		// 39
	NULL,		// 3a
	NULL,		// 3b
	NULL,		// 3c
	NULL,		// 3d
	NULL,		// 3e
	NULL,		// 3f (GCR は別処理)
};

// レジスタ名 (ワード)
/*static*/ const char * const
DMACDevice::regname2[0x40 / 2] = {
	"CSR:CER",	// 00
	NULL,		// 02
	"DCR:OCR",	// 04
	"SCR:CCR",	// 06
	NULL,		// 08
	"MTC",		// 0a
	"MAR:H",	// 0c
	"MAR:L",	// 0e
	NULL,		// 10
	NULL,		// 12
	"DAR:H",	// 14
	"DAR:L",	// 16
	NULL,		// 18
	"BTC",		// 1a
	"BAR:H",	// 1c
	"BAR:L",	// 1e
	NULL,		// 20
	NULL,		// 22
	"NIV(W)",	// 24
	"EIV(W)",	// 26
	"MFC(W)",	// 28
	NULL,		// 2a
	"CPR(W)",	// 2c
	NULL,		// 2f
	"DFC(W)",	// 30
	NULL,		// 32
	NULL,		// 34
	NULL,		// 36
	"BFC(W)",	// 38
	NULL,		// 3a
	NULL,		// 3c
	NULL,		// 3e (GCR は別処理)
};

/*static*/ const char * const
DMACDevice::seqname[DMACDevice::SEQ_MAX] =
{
	"NOP",
	"RD_M8",
	"RD_M16",
	"RD_D8",
	"RD_D16",
	"WR_M8",
	"WR_M16",
	"WR_D8",
	"WR_D16",
};

unix.superglobalmegacorp.com

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