File:  [Isaki's NoNo m68k/m88k emulator] / nono / hd64180 / hd647180asci.cpp
Revision 1.1.1.3 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:06:01 2026 UTC (3 months ago) by root
Branches: MAIN, Isaki
CVS tags: v027, HEAD
nono 1.7.0

//
// nono
// Copyright (C) 2025 nono project
// Licensed under nono-license.txt
//

//
// HD647180 ASCI (非同期シリアル)
//

#include "hd647180asci.h"
#include "event.h"
#include "hostcom.h"
#include "monitor.h"
#include "mpu64180.h"
#include "scheduler.h"

// コンストラクタ
HD647180ASCIDevice::HD647180ASCIDevice()
	: inherited(OBJ_XP_ASCI)
{
	// 入力クロックφ= 6.144MHz だが、設定に関わらず必ず 160 分周は
	// されるので、それをした後の値を基本周波数(周期)とする。
	// 6.144MHz / 160 = 38400 [bps] = 26.04166... [usec/bit]
	// 12倍すると 312.5 [usec]
	xc12_ns = 312.5_usec;

	for (uint i = 0; i < channels.size(); i++) {
		channels[i].id = i;
	}

	monitor = gMonitorManager->Regist(ID_MONITOR_XPASCI, this);
	monitor->SetCallback(&HD647180ASCIDevice::MonitorScreen);
	monitor->SetSize(63, 12);
}

// デストラクタ
HD647180ASCIDevice::~HD647180ASCIDevice()
{
	for (uint n = 0; n < channels.size(); n++) {
		if ((bool)hostcom[n]) {
			hostcom[n]->ResetRxCallback();
		}
	}
}

bool
HD647180ASCIDevice::Create()
{
	static const char * const names[2] = {
		"XP ASCI Ch.0",
		"XP ASCI Ch.1",
	};

	// 対応するホストドライバを作成。
	for (auto& chan : channels) {
		uint n = chan.id;
		try {
			hostcom[n].reset(new HostCOMDevice(this, n + 1, names[n]));
		} catch (...) { }
		if ((bool)hostcom[n] == false) {
			warnx("Failed to initialize HostCOMDevice at %s", __method__);
			return false;
		}
		auto func = ToDeviceCallback(&HD647180ASCIDevice::HostRxCallback);
		hostcom[n]->SetRxCallback(func, n);
	}

	return true;
}

// 初期化
bool
HD647180ASCIDevice::Init()
{
	xp = GetMPU64180Device();
	auto evman = GetEventManager();

	for (auto& chan : channels) {
		uint n = chan.id;

		chan.tdr_empty = true;
		chan.rdr_full = false;

		scheduler->ConnectMessage(MessageID::HOSTCOM_RX(n + 1), this,
			ToMessageCallback(&HD647180ASCIDevice::RxMessage));

		txevent[n] = evman->Regist(this, n,
			ToEventCallback(&HD647180ASCIDevice::TxCallback),
			string_format("XP ASCI Ch%c TX", n + '0'));

		rxevent[n] = evman->Regist(this, n,
			ToEventCallback(&HD647180ASCIDevice::RxCallback),
			string_format("XP ASCI Ch%c RX", n + '0'));
	}

	return true;
}

// リセット
void
HD647180ASCIDevice::ResetHard(bool poweron)
{
	// TDR, RDR はリセットの影響を受けない。
	// リセットで転送は止まる (.. are stopped during RESET) とは書いてあるが
	// TSR, RSR がどうなるかは不明。さすがにリセットされるか。

	nRTS0 = true;
	cka1_disable = true;

	for (auto& chan : channels) {
		uint n = chan.id;

		chan.rx_enable = false;
		chan.tx_enable = false;
		chan.rx_intr_enable = false;
		chan.tx_intr_enable = false;
		chan.data8bit = false;
		chan.parity_en = false;
		chan.stop2bit = false;
		chan.parity_odd = false;
		chan.prescale = false;
		chan.divratio = false;
		chan.ss = 7;
		chan.nCTS = false;	// ?
		chan.tsr = -1;
		chan.rsr = -1;
		ChangeBaudRate(chan.id);
		ChangeInterrupt(chan.id);

		scheduler->StopEvent(txevent[n]);
		scheduler->StopEvent(rxevent[n]);
	}
}

uint32
HD647180ASCIDevice::ReadCNTLA(uint n)
{
	uint32 data = PeekCNTLA(n);
	putlog(3, "CNTLA%u -> $%02x", n, data);
	return data;
}

uint32
HD647180ASCIDevice::ReadCNTLB(uint n)
{
	uint32 data = PeekCNTLB(n);
	putlog(3, "CNTLB%u -> $%02x", n, data);
	return data;
}

uint32
HD647180ASCIDevice::ReadSTAT(uint n)
{
	uint32 data = PeekSTAT(n);
	putlog(3, "STAT%u -> $%02x", n, data);
	return data;
}

uint32
HD647180ASCIDevice::ReadTDR(uint n)
{
	// TDR は副作用なく読み出し可能。
	uint32 data = PeekTDR(n);
	putlog(3, "TDR%u -> $%02x", n, data);
	return data;
}

uint32
HD647180ASCIDevice::ReadRDR(uint n)
{
	Channel& chan = channels[n];

	uint32 data = PeekRDR(n);
	if (chan.rsr >= 0) {
		chan.rdr = chan.rsr;
		chan.rsr = -1;
	} else {
		chan.rdr_full = false;
	}
	putlog(4, "RDR%u -> $%02x", n, data);
	return data;
}

uint32
HD647180ASCIDevice::WriteCNTLA(uint n, uint32 data)
{
	Channel& chan = channels[n];
	std::string msg;

	if ((data & CNTL_MPE)) {
		putlog(0, "MPE=1 (NOT IMPLEMENTED)");
	}

	bool newrxe = (data & CNTL_RE);
	if (chan.rx_enable != newrxe) {
		chan.rx_enable = newrxe;
		if (__predict_false(loglevel >= 1)) {
			msg += string_format(" RX=%s", (newrxe ? "Enable" : "Disable"));
		}
	}

	bool newtxe = (data & CNTL_TE);
	if (chan.tx_enable != newtxe) {
		chan.tx_enable = newtxe;
		if (__predict_false(loglevel >= 1)) {
			msg += string_format(" TX=%s", (newtxe ? "Enable" : "Disable"));
		}
	}

	if (n == 0) {
		nRTS0 = (data & CNTL_nRTS0);
	} else {
		cka1_disable = (data & CNTL_CKA1D);
		if (cka1_disable == false) {
			putlog(0, "CKA1D=0 (NOT IMPLEMENTED)");
		}
	}

	if ((data & CNTL_EFR)) {
		// OVRN, FE, PE をクリアする。
		chan.overrun = false;
		ChangeInterrupt(n);
	}

	bool new8bit = (data & CNTL_DATA8);
	bool newpen  = (data & CNTL_PAREN);
	bool newstop = (data & CNTL_STOP2);
	if (chan.data8bit  != new8bit ||
		chan.parity_en != newpen  ||
		chan.stop2bit  != newstop)
	{
		chan.data8bit = new8bit;
		chan.parity_en = newpen;
		chan.stop2bit = newstop;
		ChangeBaudRate(n);
		if (__predict_false(loglevel >= 1)) {
			msg += string_format(" data=%u,stop=%u,parity=%s",
				(chan.data8bit ? 8 : 7),
				(chan.stop2bit ? 2 : 1),
				(chan.parity_en ? "on" : "off"));
		}
	}

	if (__predict_false(loglevel >= 1)) {
		// ログレベル1なら状態が変わった時だけ、2ならすべて表示。
		int req;
		if (msg.empty()) {
			// 状態が変わっていない。
			req = 2;
		} else {
			req = 1;
			msg[0] = '(';
			msg += ')';
		}
		putlog(req, "CNTLA%u <- $%02x %s", n, data, msg.c_str());
	}

	return 0;
}

uint32
HD647180ASCIDevice::WriteCNTLB(uint n, uint32 data)
{
	Channel& chan = channels[n];
	std::string msg;

	if ((data & (CNTL_MPBT | CNTL_MP))) {
		putlog(0, "MPBT|MP=1 (NOT IMPLEMENTED)");
	}

	bool newpo = (data & CNTL_PEO);
	if (chan.parity_odd != newpo) {
		chan.parity_odd = newpo;
		if (__predict_false(loglevel >= 1)) {
			msg += string_format(" Parity=%s", (newpo ? "Odd" : "Even"));
		}
	}

	bool   newps = (data & CNTL_PS);
	bool   newdr = (data & CNTL_DR);
	uint32 newss = (data & CNTL_SS);
	if (chan.prescale != newps ||
		chan.divratio != newdr ||
		chan.ss != newss)
	{
		chan.prescale = newps;
		chan.divratio = newdr;
		chan.ss = newss;
		ChangeBaudRate(n);
		if (__predict_false(loglevel >= 1)) {
			msg += string_format(" PS=%u DR=%u SS=%u",
				(uint)newps, (uint)newdr, newss);
			if (chan.baudrate > 0) {
				msg += string_format(", %u bps", chan.baudrate);
			}
		}
	}

	if (__predict_false(loglevel >= 1)) {
		// ログレベル1なら状態が変わった時だけ、2ならすべて表示。
		int req;
		if (msg.empty()) {
			// 状態が変わっていない。
			req = 2;
		} else {
			req = 1;
			msg[0] = '(';
			msg += ')';
		}
		putlog(req, "CNTLB%u <- $%02x %s", n, data, msg.c_str());
	}

	return 0;
}

uint32
HD647180ASCIDevice::WriteSTAT(uint n, uint32 data)
{
	Channel& chan = channels[n];
	std::string msg;

	if (n == 1) {
		cts1_enable = (data & STAT_CTS1E);
		if (cts1_enable) {
			putlog(0, "CTS1E=1 (NOT IMPLEMENTED)");
		}
	}

	bool newrie = (data & STAT_RIE);
	if (chan.rx_intr_enable != newrie) {
		chan.rx_intr_enable = newrie;
		ChangeInterrupt(n);
		msg += string_format(" RIE=%s", newrie ? "Enable" : "Disable");
	}

	bool newtie = (data & STAT_TIE);
	if (chan.tx_intr_enable != newtie) {
		chan.tx_intr_enable = newtie;
		ChangeInterrupt(n);
		msg += string_format(" TIE=%s", newrie ? "Enable" : "Disable");
	}

	if (__predict_false(loglevel >= 1)) {
		// ログレベル1なら状態が変わった時だけ、2ならすべて表示。
		int req;
		if (msg.empty()) {
			// 状態が変わっていない。
			req = 2;
		} else {
			req = 1;
			msg[0] = '(';
			msg += ')';
		}
		putlog(req, "STAT%u <- $%02x %s", n, data, msg.c_str());
	}

	return 0;
}

uint32
HD647180ASCIDevice::WriteTDR(uint n, uint32 data)
{
	Channel& chan = channels[n];

	// どちらにしても一旦 TDR には置く。
	chan.tdr = data;
	putlog(4, "TDR%u <- $%02x", n, data);

	// TDR  TSR     TDR  TSR
	// ---  ---     ---  ---
	// なし なし -> なし data イベント始動
	// なし 有り -> data 有り (イベントは動いてるはず)
	// 有り なし              (すぐ1行上に遷移するのでこの状態にはならない)
	// 有り 有り -> data 有り

	if (chan.tsr < 0) {
		// TSR が空なら即コピーして、送信イベント開始。
		chan.tsr = chan.tdr;
		chan.tdr_empty = true;
		if (txevent[n]->IsRunning() == false) {
			scheduler->StartEvent(txevent[n]);
		}
	} else {
		// TSR が埋まっていれば何もしない。(イベントは動いてるはず)
		chan.tdr_empty = false;
	}

	ChangeInterrupt(n);
	return 0;
}

uint32
HD647180ASCIDevice::WriteRDR(uint n, uint32 data)
{
	Channel& chan = channels[n];

	// RDR が空の時に限り、書き込み出来る。(p.69)
	if (chan.rdr_full) {
		putlog(1, "RDR%u <- $%02x (Write Ignored)", n, data);
	} else {
		chan.rdr = data;
		putlog(4, "RDR%u <- $%02x", n, data);
	}
	return 0;
}

uint32
HD647180ASCIDevice::PeekCNTLA(uint n) const
{
	const Channel& chan = channels[n];
	uint32 data = 0;

	// MPE Not implemented

	if (chan.rx_enable) {
		data |= CNTL_RE;
	}
	if (chan.tx_enable) {
		data |= CNTL_TE;
	}
	if (n == 0) {
		if (nRTS0) {
			data |= CNTL_nRTS0;
		}
	} else {
		if (cka1_disable) {
			data |= CNTL_CKA1D;
		}
	}
	if (chan.data8bit) {
		data |= CNTL_DATA8;
	}
	if (chan.parity_en) {
		data |= CNTL_PAREN;
	}
	if (chan.stop2bit) {
		data |= CNTL_STOP2;
	}

	return data;
}

uint32
HD647180ASCIDevice::PeekCNTLB(uint n) const
{
	const Channel& chan = channels[n];
	uint32 data = 0;

	// MPBT, MP: Not implemented

	if (chan.nCTS) {
		data |= CNTL_nCTS;
	}
	if (chan.parity_odd) {
		data |= CNTL_PEO;
	}
	if (chan.divratio) {
		data |= CNTL_DR;
	}
	data |= chan.ss;

	return data;
}

uint32
HD647180ASCIDevice::PeekSTAT(uint n) const
{
	const Channel& chan = channels[n];
	uint32 data = 0;

	if (chan.rdr_full) {
		data |= STAT_RDRF;
	}
	if (chan.overrun) {
		data |= STAT_OVRN;
	}

	// PE, FE は立たない。

	if (chan.rx_intr_enable) {
		data |= STAT_RIE;
	}
	if (n == 0) {
		if (nDCD0) {
			data |= STAT_nDCD0;
		}
	} else {
		if (cts1_enable) {
			data |= STAT_CTS1E;
		}
	}
	if (chan.tdr_empty) {
		data |= STAT_TDRE;
	}
	if (chan.tx_intr_enable) {
		data |= STAT_TIE;
	}

	return data;
}

uint32
HD647180ASCIDevice::PeekTDR(uint n) const
{
	const Channel& chan = channels[n];

	return chan.tdr;
}

uint32
HD647180ASCIDevice::PeekRDR(uint n) const
{
	const Channel& chan = channels[n];

	return chan.rdr;
}

void
HD647180ASCIDevice::MonitorScreen(Monitor *, TextScreen& screen)
{
	screen.Clear();

	MonitorScreenChan(screen, 0, 0);
	MonitorScreenChan(screen, 6, 1);
}

void
HD647180ASCIDevice::MonitorScreenChan(TextScreen& screen, int y, int n) const
{
	Channel tmp = channels[n];
	uint32 cntla = PeekCNTLA(n);
	uint32 cntlb = PeekCNTLB(n);
	uint32 stat  = PeekSTAT(n);
	int x;

	screen.Print(0, y++, "<Ch.%c>", n + '0');

	x = 16;
	screen.Print(0, y, "%02XH CNTLA%u=$%02x:", 0 + n, n, cntla);
	screen.Puts(x +  0, y, TA::OnOff(cntla & 0x80), "MPE");
	screen.Puts(x +  6, y, TA::OnOff(cntla & 0x40), "RE");
	screen.Puts(x + 12, y, TA::OnOff(cntla & 0x20), "TE");
	screen.Puts(x + 18, y, TA::OnOff(cntla & 0x10),
		(n == 0 ? "!RTS0" : "CKA1D"));
	screen.Puts(x + 24, y, TA::OnOff(cntla & 0x08), "MPBR");
	screen.Puts(x + 30, y, TA::OnOff(cntla & 0x04), "8BIT");
	screen.Puts(x + 36, y, TA::OnOff(cntla & 0x02), "ParEn");
	screen.Puts(x + 42, y, TA::OnOff(cntla & 0x01), "STOP2");
	y++;

	screen.Print(0, y, "%02XH CNTLB%u=$%02x:", 2 + n, n, cntlb);
	screen.Puts(x +  0, y, TA::OnOff(cntlb & 0x80), "MPBT");
	screen.Puts(x +  6, y, TA::OnOff(cntlb & 0x40), "MP");
	screen.Puts(x + 12, y, TA::OnOff(cntlb & 0x20), "!CTS");
	screen.Puts(x + 18, y, TA::OnOff(cntlb & 0x10), "PEO");
	screen.Print(x + 24, y, "DR=%u", tmp.divratio ? 1 : 0);
	screen.Print(x + 30, y, "SS[210]=%u", (cntlb & 0x07));
	screen.Print(x + 42, y, "PS=%u", tmp.prescale ? 1 : 0);
	y++;

	screen.Print(0, y, "%02XH STAT%u =$%02x:", 4 + n, n, stat);
	screen.Puts(x +  0, y, TA::OnOff(stat & 0x80), "RDRF");
	screen.Puts(x +  6, y, TA::OnOff(stat & 0x40), "OVRN");
	screen.Puts(x + 12, y, TA::OnOff(stat & 0x20), "PE");
	screen.Puts(x + 18, y, TA::OnOff(stat & 0x10), "FE");
	screen.Puts(x + 24, y, TA::OnOff(stat & 0x08), "RIE");
	screen.Puts(x + 30, y, TA::OnOff(stat & 0x04),
		(n == 0 ? "!DCD0" : "CTS1E"));
	screen.Puts(x + 36, y, TA::OnOff(stat & 0x02), "TDRE");
	screen.Puts(x + 42, y, TA::OnOff(stat & 0x01), "TIE");
	y++;

	screen.Print(0, y, "%02XH TDR%u  =", 6 + n, n);
	screen.Print(11, y, (tmp.tdr_empty ? TA::Disable : TA::Normal),
		"$%02x", tmp.tdr);
	screen.Puts(x, y, "TSR:");
	if (__predict_true(tmp.tsr < 0)) {
		screen.Puts(x + 4, y, TA::Disable, "---");
	} else {
		screen.Print(x + 4, y, "$%02x", tmp.tsr);
	}
	y++;

	screen.Print(0, y, "%02XH RDR%u  =", 8 + n, n);
	screen.Print(11, y, (tmp.rdr_full ? TA::Normal : TA::Disable),
		"$%02x", tmp.rdr);
	screen.Puts(x, y, "RSR:");
	if (__predict_true(tmp.rsr < 0)) {
		screen.Puts(x + 4, y, TA::Disable, "---");
	} else {
		screen.Print(x + 4, y, "$%02x", tmp.rsr);
	}

	// Param: 19200,N,8,2
	screen.Puts(x + 29, y, "Param:");
	if (tmp.baudrate == 0) {
		screen.Puts(x + 36, y, "(ExtClock)");
	} else {
		char p;
		if (tmp.parity_en) {
			p = tmp.parity_odd ? 'O' : 'E';
		} else {
			p = 'N';
		}
		screen.Print(x + 36, y, "%5u,%c,%u,%u",
			tmp.baudrate, p, (tmp.data8bit ? 8 : 7), (tmp.stop2bit ? 2 : 1));
	}
}


// 割り込み状態を更新する。
// RDFn, OVRNn, RIEn, TDREn, TIEn のいずれかでも変更したら呼ぶこと。
void
HD647180ASCIDevice::ChangeInterrupt(uint n)
{
	// p.77 Fig 2.10.3 より、片方のチャンネルの割り込み要求ロジック。
	// ____
	// DCD0(*) --|OR  (*) Ch0 のみ
	// RDRFn ----|OR
	// OVRNn ----|OR-----|&            :  IEF1
	// PEn  -----|OR     |&            :   |
	// FEn  -----|OR     |&-----|OR    :   |
	//                   |&     |OR    :   +--|&
	//         RIEn -----|&     |OR    :      |&----> ASCIn Int
	//                          |OR-----------|&
	//         TDREn ----|&     |OR    :
	//                   |&-----|OR    :
	//         TIEn -----|&            :
	//                                 :
	// この関数でやってるのはここまで  : こっちは xp->ChangeASCIInt()

	const Channel& chan = channels[n];

	// PE, FE (, !DCD0) はない。
	bool rcond = chan.rdr_full || chan.overrun;
	bool rintr = rcond && chan.rx_intr_enable;

	bool tintr = chan.tdr_empty && chan.tx_intr_enable;

	bool intr = (rintr || tintr);

	// IEF1 は向こうで処理している。
	xp->ChangeASCIInt(n, intr);
}

static double
to_freq(uint64 nsec)
{
	return (double)1e9 / nsec;
}

// 現在の Prescale, DivideRatio, SS からボーレートを再計算する。
// ここで 1文字分の時間も計算するため、CNTLA の MOD が変わっても呼ぶこと。
void
HD647180ASCIDevice::ChangeBaudRate(uint n)
{
	Channel& chan = channels[n];

	if (__predict_false(chan.ss == 7)) {
		chan.baudrate = 0;
	} else {
		uint d = (1U << chan.ss);

		// PreScaler で 30 か 10 分周が指定出来るが、このうち 10 は計算済み。
		if (chan.prescale) {
			d *= 3;
		}

		// DivideRatio で 64 か 16 分周が指定できるが、このうち 16 は計算済み。
		if (chan.divratio) {
			d *= 4;
		}

		chan.bit12_ns = xc12_ns * d;
		chan.baudrate = to_freq(chan.bit12_ns / 12);

		// 1文字の送受信にかかる時間。
		uint bits = 1/*start*/ + 7/*data*/ + 1/*stop*/;
		if (chan.data8bit) {
			bits++;
		}
		if (chan.parity_en) {
			bits++;
		}
		if (chan.stop2bit) {
			bits++;
		}
		uint64 time = chan.bit12_ns * bits / 12;
		txevent[n]->time = time;
		rxevent[n]->time = time;
	}
}

// 送信イベント。
void
HD647180ASCIDevice::TxCallback(Event *ev)
{
	uint n = ev->code;
	Channel& chan = channels[n];

	// 1文字分の時間が経過したので TSR の文字をホストに送信。
	hostcom[n]->Tx(chan.tsr);

	if (chan.tdr_empty) {
		// 次がなければ終わり。
		chan.tsr = -1;
	} else {
		// 次の文字がある。
		chan.tsr = chan.tdr;
		chan.tdr_empty = true;
		ChangeInterrupt(n);

		scheduler->StartEvent(ev);
	}
}

// ホストスレッドからの受信通知。
// (HostCOM スレッドで呼ばれる)
void
HD647180ASCIDevice::HostRxCallback(uint32 arg)
{
	// XXX mpscc.cpp の同関数のコメント参照。

	uint n = arg;
	const Event *ev = rxevent[n];

	if (ev->IsRunning() == false) {
		// スレッドを超えるためにメッセージを投げる。
		scheduler->SendMessage(MessageID::HOSTCOM_RX(n + 1));
	}
}

// ホストシリアルからの1バイト受信メッセージ
// (VM スレッドで呼ばれる)
void
HD647180ASCIDevice::RxMessage(MessageID msgid, uint32 arg)
{
	uint n = msgid - MessageID::HOSTCOM1_RX;
	Event *ev = rxevent[n];

	// 受信ループが止まっていれば開始。
	if (ev->IsRunning() == false) {
		scheduler->StartEvent(ev);
	}
}

// 受信イベントコールバック。
void
HD647180ASCIDevice::RxCallback(Event *ev)
{
	uint n = ev->code;
	int data;

	data = hostcom[n]->Rx();
	if (data >= 0) {
		Rx(n, data);

		scheduler->StartEvent(ev);
	}
}

// 1バイト受信する。
bool
HD647180ASCIDevice::Rx(uint n, uint32 data)
{
	Channel& chan = channels[n];

	// Rx Enable でなければ受け取らない?
	if (chan.rx_enable == false) {
		return false;
	}

	// 受信前       受信後の状態
	// RDR  RSR     RDR  RSR
	// ---  ---     ---  ---
	// なし なし -> data なし
	// なし 有り              (すぐに1行下の状態に遷移するためこの状態はない)
	// 有り なし -> 有り data
	// 有り 有り -> 有り 有り (Overrun)

	// バッファが一杯なら Overrun。
	if (chan.rdr_full && chan.rsr >= 0) {
		putlog(2, "Ch%u Rx Overrun", n);
		chan.overrun = true;
		ChangeInterrupt(n);
		return false;
	}

	// 受信。
	putlog(2, "Ch%u recv $%02x", n, data);
	if (chan.rdr_full) {
		chan.rsr = data;
	} else {
		chan.rdr = data;
		chan.rdr_full = true;
		chan.rsr = -1;
		ChangeInterrupt(n);
	}

	return true;
}

unix.superglobalmegacorp.com

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