File:  [Isaki's NoNo m68k/m88k emulator] / nono / vm / mfp.cpp
Revision 1.1.1.21 (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
//

//
// MFP (MC68901)
//

// タイマーについて。
//
// ディレイモードでは、プリスケーラで設定した回数だけ入力をカウントすると
// メインカウンタにパルスが入る。このパルスはメインカウンタを 1 減らす。
// メインカウンタが $01 になった次のパルスで (おそらく外から見ると $00 に
// ならないからこういう言い回しなのだが要するに 1 減らして内部的に $00 に
// なったらでいいと思う)、TxDR をリロードして「タイムアウトパルス」を起こす。
// タイムアウトパルスが起きると、割り込みを(許可されていれば)上げると共に、
// カウンタをリロードして、アウトプットピンの状態をトグルする。(MC68901.pdf)
//
// 1つのタイマーあたり
// o タイマー開始時のカウンタ初期値
// o タイマー開始時刻
// o 次回のリロード値
// を保持する。
//
// タイムアウトパルスとタイムアウトパルスの間は一定時間ごとにメインカウンタの
// 値が減算されていってるだけだし、割り込みを有効にしていなければメインカウンタ
// はタイムアウト周期でこの減算を繰り返しているだけなので、TxDR の読み出し値は
// 読み出し時刻から計算で求めることができる。
//
//  現在値 = 初期値 - int((現在時刻 - 開始時刻) / メインカウンタ周期) % 初期値
//
// 例えば、プリスケーラ 16分周(4usec)、カウント 5 の場合
//
//       (1)(2)                              (3)
//        v  v                               t+26
//          t+0  +4   +8   +12  +16  +20 +24  v  +28 +32  +36  +40   [usec]
//        ---+----+----+----+----+----+----+-----+----+----+----+--> time
//  TxDR  5  5    4    3    2    1    5    4     3    2    1    5
//
// (1) で TxDR に 5 をセット、(2) t+0 でタイマースタートして、(3) t+26 で
// TxDR を読み出された場合、値は
//    5 - int(((t+26 - t+0) % 20_usec) / 4_usec)
//  = 5 - int(6_usec / 4_usec)
//  = 5 - 1
//  = 4
//
// 一方割り込みが必要な場合は、(2) でタイマースタートして、(4)、(4')… の
// タイミングでタイマーイベントにより割り込みを起こす。
//
//          (2)                      (4)                      (4')
//           v                        v                        v
//        ---+----+----+----+----+----+----+----+----+----+----+--> time
//  TxDR  5  5    4    3    2    1    5    4    3    2    1    5

// 実時間追従について。
//
// MFP の実時間追従はタイマーの「割り込みに対してのみ」作用する。
// そもそも実時間追従とは、LUNA ではこの手のタイマーが固定のシステムクロック
// しかなくこれによるタイマー割り込み HZ 回でゲスト OS の時刻が1秒進むという
// ゲスト実装の特性と、割り込みの頻度は比較的低いことを利用した技法。
// 従って MFP でも 10msec 程度のタイマー割り込みであれば動作する。
//
// 一方 TxDR の減算 (プリスケーラ 200 分周でも 1 カウンタ減算が 50usec) は、
// 実時間軸を作っている syncer イベントの分解能 (仮想時間で 50msec) より
// 遥かに高いため、これを (syncer の) 実時間軸に追従させることは現状不可能。
// 例えば、より上位の割り込みが起きない Timer-C 割り込みのハンドラ冒頭で
// TCDR を読み出すと、通常はリロードされた初期値が読めるはずだが、これが
// 何が読めるか分からないという状態になる。
// NetBSD/x68k の timecounter=mfp はまさにこの TxDR の読み出しで時間差を計測
// して (10msec よりも高精度な 50usec 粒度で) 時間を進めようとするものなので
// (のはず)、TxDR の読み出しが信頼できなくなるこのモードでは使用できない。
// 一方 timecounter=clockinterrupt はタイマー割り込み1回で 1/HZ 秒ずつ進める
// (そのため時間の分解能は 1/HZ 秒 = 10msec になる) モード(のはず…)のため、
// 実時間追従でも動作する。
//
// nono のオプション clock-sync の値、動作モード(等速 or フルスピード) と
// ゲスト OS NetBSD の sysctl kern.timecounter.hardware の値の組み合わせは
// 以下のようになる。
//
//  clock-sync	mode	sysctl timecounter
//  ----------	----	---------------
//  virtual		等速	mfp				: ◯
//  virtual		等速	clockinterrupt	: ◯
//  virtual		フル	mfp				: △
//  virtual		フル	clockinterrupt	: △
//  real		等速	mfp				: ×
//  real		等速	clockinterrupt	: ◯
//  real		フル	mfp				: ×
//  real		フル	clockinterrupt	: ◯
//
//  ◯: ゲスト OS の時間は実時間通りに進む。
//  △: VM 内の時間は実時間から切り離されてフルスピード時間軸で進む。
//  ×: ゲスト OS の時間が正しく進まない。

// Timer-B は 13 usec のタイムアウトごとに反転する出力ピン TOB を自身の
// TC, RC (UART の送受信クロック入力) に入れているだけで、割り込みは
// 使っていない。実機ではこれを変更するとキーボードと正しく通信できない
// ことになると思う。nono ではキーボードとの通信にこのクロック入力は
// 使っていないので、Timer-B は実質何もしていない。

#include "mfp.h"
#include "event.h"
#include "interrupt.h"
#include "keyboard.h"
#include "mainbus.h"
#include "monitor.h"
#include "scheduler.h"
#include "syncer.h"
#include "x68kkbd.h"

// InsideOut p.135
/*static*/ const busdata
MFPDevice::wait = busdata::Wait(24 * 40_nsec);

// コンストラクタ
MFPDevice::MFPDevice()
	: inherited(OBJ_MFP)
{
	monitor = gMonitorManager->Regist(ID_MONITOR_MFP, this);
	monitor->SetCallback(&MFPDevice::MonitorScreen);
	monitor->SetSize(77, 26);
}

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

// 初期化
bool
MFPDevice::Init()
{
	interrupt = GetInterruptDevice();
	keyboard = dynamic_cast<X68030Keyboard *>(GetKeyboard());

	// クロックの同期方法。
	syncer = GetSyncer();
	sync_rt = syncer->GetSyncRealtime();

	// TxDR は 1-256 の範囲でなければいけないので。
	for (int ch = 0; ch < countof(mfp.timer); ch++) {
		mfp.timer[ch].tdr = 256;
	}

	auto evman = GetEventManager();
	for (int ch = 0; ch < event.size(); ch++) {
		event[ch] = evman->Regist(this, ch,
			ToEventCallback(&MFPDevice::TimerCallback),
			string_format("MFP Timer-%c", ch + 'A'));
	}

	// キーボード周りは x68kkbd.cpp のコメント参照
	key_event = evman->Regist(this,
		ToEventCallback(&MFPDevice::KeyCallback),
		"MFP USART");
	key_event->time = 3750_usec;

	return true;
}

// リセット
void
MFPDevice::ResetHard(bool poweron)
{
	// 3.3 RESET OPERATION には
	// All internal registers are cleared expect the TxDR, UDR, and TSR.
	// と書いてあるが、6.2.1 には TxDR はリセット時に $00 と書いてある。
	// 内部カウンタは影響を受けずに next_tdr だけリセットされるという
	// 意味だろうか。
	mfp.aer = 0;
	mfp.ddr = 0;
	mfp.ier.w = 0;
	mfp.ipr.w = 0;
	mfp.isr.w = 0;
	mfp.imr.w = 0;
	for (int i = 0; i < countof(mfp.timer); i++) {
		mfp.timer[i].tcr = 0;
		mfp.timer[i].next_tdr = 256;
	}
	mfp.scr = 0;
	mfp.ucr = 0;
	mfp.rsr = 0;
	// XXX TSR は変化しないがたぶんバッファはクリアされるので BE は立つ
	// ということかな。
	mfp.tsr = MFP::TSR_BE;

	// All timers are stopped.
	for (int ch = 0; ch < event.size(); ch++) {
		StopTimerEvent(ch);
	}
	scheduler->StopEvent(key_event);

	// USART RX and TX are disabled.
	// SO line is placed in HighZ.
	// The interrupt channels are disabled, and
	// any pending interrupts are cleared.
	// GPIO lines are placed in HighZ.
	// timer outputs are driven low.
	// External signals are nagated.
	// VR is initialized to $00 (not $0f).
	mfp.vr_vec = 0;
	mfp.vr_s   = 0;

	// CIRQ | FMIRQ | EXPON | ALARM にしておく
	mfp.gpip = 0x6b;

	// Human68k モードなら、10msec ごとに Timer-C 割り込みを上げる。
	// 時間計測用。
	if (gMainApp.human_mode) {
		WritePort(MFP::VR, 0x40);
		WritePort(MFP::IERB, 0x20);
		WritePort(MFP::TCDR, 200);
		WritePort(MFP::TCDCR, 0x70);
		WritePort(MFP::IMRB, 0x20);
	}

	ChangeInterrupt();
}

busdata
MFPDevice::ReadPort(uint32 offset)
{
	busdata data;

	switch (offset) {
	 case MFP::GPIP:
		data = mfp.gpip;
		break;
	 case MFP::AER:
		data = mfp.aer;
		break;
	 case MFP::DDR:
		data = mfp.ddr;
		break;
	 case MFP::IERA:
		data = mfp.ier.a;
		break;
	 case MFP::IERB:
		data = mfp.ier.b;
		break;
	 case MFP::IPRA:
		data = mfp.ipr.a;
		break;
	 case MFP::IPRB:
		data = mfp.ipr.b;
		break;
	 case MFP::ISRA:
		data = mfp.isr.a;
		break;
	 case MFP::ISRB:
		data = mfp.isr.b;
		break;
	 case MFP::IMRA:
		data = mfp.imr.a;
		break;
	 case MFP::IMRB:
		data = mfp.imr.b;
		break;
	 case MFP::VR:
		data = mfp.GetVR();
		break;
	 case MFP::TACR:
		data = mfp.timer[0].tcr;
		break;
	 case MFP::TBCR:
		data = mfp.timer[1].tcr;
		break;
	 case MFP::TCDCR:
		data = mfp.GetTCDCR();
		break;
	 case MFP::TADR:
		data = GetTDR(0);
		break;
	 case MFP::TBDR:
		data = GetTDR(1);
		break;
	 case MFP::TCDR:
		data = GetTDR(2);
		break;
	 case MFP::TDDR:
		data = GetTDR(3);
		break;
	 case MFP::SCR:
		data = mfp.scr;
		break;
	 case MFP::UCR:
		data = mfp.ucr;
		break;
	 case MFP::RSR:
		data = mfp.rsr;
		break;
	 case MFP::TSR:
		data = mfp.tsr;
		break;
	 case MFP::UDR:
		// UDR からデータを読み出したら Buffer Full をクリア
		data = mfp.udr;
		ClearBF();
		break;
	 default:
		data = 0xff;
		break;
	}

	if (__predict_false(loglevel >= 3)) {
		if (offset == MFP::TCDR) {
			putlog(4, "%s -> $%02x", regname[offset], data.Data());
		} else if (offset < MFP::RegMax) {
			putlogn("%s -> $%02x", regname[offset], data.Data());
		} else {
			putlogn("$%06x -> $%02x",
				GetMainbusDevice()->GetPaddr(), data.Data());
		}
	}
	data |= wait;
	data |= BusData::Size1;
	return data;
}

busdata
MFPDevice::WritePort(uint32 offset, uint32 data)
{
	switch (offset) {
	 case MFP::GPIP:
		putlog(0, "Write GPIP <- $%02x (NOT IMPLEMENTED)", data);
		mfp.gpip = data;
		break;
	 case MFP::AER:
		putlog(2, "%s <- $%02x", regname[offset], data);
		mfp.aer = data;
		break;
	 case MFP::DDR:
		if (data != 0) {
			putlog(0, "Write DDR <- $%02x (NOT IMPLEMENTED)", data);
		}
		mfp.ddr = data;
		break;
	 case MFP::IERA:
		putlog(2, "%s <- $%02x", regname[offset], data);
		SetIER((data << 8) | mfp.ier.b);
		break;
	 case MFP::IERB:
		putlog(2, "%s <- $%02x", regname[offset], data);
		SetIER((mfp.ier.a << 8) | data);
		break;
	 case MFP::IPRA:
		putlog(2, "%s <- $%02x", regname[offset], data);
		mfp.ipr.a = data;
		ChangeInterrupt();
		break;
	 case MFP::IPRB:
		putlog(2, "%s <- $%02x", regname[offset], data);
		mfp.ipr.b = data;
		ChangeInterrupt();
		break;
	 case MFP::ISRA:
		putlog(2, "%s <- $%02x", regname[offset], data);
		mfp.isr.a = data;
		break;
	 case MFP::ISRB:
		putlog(2, "%s <- $%02x", regname[offset], data);
		mfp.isr.b = data;
		break;
	 case MFP::IMRA:
		putlog(2, "%s <- $%02x", regname[offset], data);
		mfp.imr.a = data;
		ChangeInterrupt();
		break;
	 case MFP::IMRB:
		putlog(2, "%s <- $%02x", regname[offset], data);
		mfp.imr.b = data;
		ChangeInterrupt();
		break;
	 case MFP::VR:
		mfp.vr_vec = data & 0xf0;
		mfp.vr_s   = data & 0x08;
		putlog(2, "%s <- $%02x", regname[offset], mfp.GetVR());
		break;
	 case MFP::TACR:
		putlog(2, "%s <- $%02x", regname[offset], data);
		SetTCR(0, data);
		break;
	 case MFP::TBCR:
		putlog(2, "%s <- $%02x", regname[offset], data);
		SetTCR(1, data);
		break;
	 case MFP::TCDCR:
		putlog(2, "%s <- $%02x", regname[offset], data);
		SetTCR(2, data >> 4);
		SetTCR(3, data & 7);
		break;
	 case MFP::TADR:
		putlog(2, "%s <- $%02x", regname[offset], data);
		SetTDR(0, data);
		break;
	 case MFP::TBDR:
		putlog(2, "%s <- $%02x", regname[offset], data);
		SetTDR(1, data);
		break;
	 case MFP::TCDR:
		putlog(2, "%s <- $%02x", regname[offset], data);
		SetTDR(2, data);
		break;
	 case MFP::TDDR:
		putlog(2, "%s <- $%02x", regname[offset], data);
		SetTDR(3, data);
		break;
	 case MFP::SCR:
		putlog(0, "SCR <- $%02x (NOT IMPLEMENTED)", data);
		mfp.scr = data;
		break;
	 case MFP::UCR:
		putlog(2, "%s <- $%02x", regname[offset], data);
		SetUCR(data);
		break;
	 case MFP::RSR:
		putlog(2, "%s <- $%02x", regname[offset], data);
		SetRSR(data);
		break;
	 case MFP::TSR:
		if ((data & 0x0e)) {
			// SO 端子関係の B, H, L ビットは未実装
			putlog(0, "TSR <- $%02x (B,H,L bit NOT IMPLEMENTED)", data);
		}
		mfp.tsr = (mfp.tsr & 0xf0) | (data & 0x0f);
		break;
	 case MFP::UDR:
		if (__predict_false(loglevel >= 2)) {
			if (data == 0x40 || data == 0x41) {
				// MSCTRL だけ頻度が高いのでログレベルを上げておく
				putlog(3, "%s <- $%02x", regname[offset], data);
			} else {
				putlogn("%s <- $%02x", regname[offset], data);
			}
		}
		SetUDR(data);
		break;
	 default:
		break;
	}

	busdata r = wait;
	r |= BusData::Size1;
	return r;
}

busdata
MFPDevice::PeekPort(uint32 offset)
{
	uint8 data;

	switch (offset) {
	 case MFP::GPIP:
		data = mfp.gpip;
		break;
	 case MFP::AER:
		data = mfp.aer;
		break;
	 case MFP::DDR:
		data = mfp.ddr;
		break;
	 case MFP::IERA:
		data = mfp.ier.a;
		break;
	 case MFP::IERB:
		data = mfp.ier.b;
		break;
	 case MFP::IPRA:
		data = mfp.ipr.a;
		break;
	 case MFP::IPRB:
		data = mfp.ipr.b;
		break;
	 case MFP::ISRA:
		data = mfp.isr.a;
		break;
	 case MFP::ISRB:
		data = mfp.isr.b;
		break;
	 case MFP::IMRA:
		data = mfp.imr.a;
		break;
	 case MFP::IMRB:
		data = mfp.imr.b;
		break;
	 case MFP::VR:
		data = mfp.GetVR();
		break;
	 case MFP::TACR:
		data = mfp.timer[0].tcr;
		break;
	 case MFP::TBCR:
		data = mfp.timer[1].tcr;
		break;
	 case MFP::TCDCR:
		data = mfp.GetTCDCR();
		break;
	 case MFP::TADR:
		data = GetTDR(0);
		break;
	 case MFP::TBDR:
		data = GetTDR(1);
		break;
	 case MFP::TCDR:
		data = GetTDR(2);
		break;
	 case MFP::TDDR:
		data = GetTDR(3);
		break;
	 case MFP::SCR:
		data = mfp.scr;
		break;
	 case MFP::UCR:
		data = mfp.ucr;
		break;
	 case MFP::RSR:
		data = mfp.rsr;
		break;
	 case MFP::TSR:
		data = mfp.tsr;
		break;
	 case MFP::UDR:
		data = mfp.udr;
		break;
	 default:
		data = 0xff;
	}
	return data;
}

bool
MFPDevice::PokePort(uint32 offset, uint32 data)
{
	return false;
}

void
MFPDevice::MonitorScreen(Monitor *, TextScreen& screen)
{
	MFP mfp_;
	std::array<uint8, 4> tdr_;
	int x;
	int y;

	// ローカルにコピー。
	memcpy(&mfp_, &mfp, sizeof(mfp));
	for (int ch = 0; ch < tdr_.size(); ch++) {
		tdr_[ch] = GetTDR(ch);
	}

	screen.Clear();

	// 0         1         2         3         4         5         6
	// 01234567890123456789012345678901234567890123456789012345678901234567890
	// $e88001(GPIP):$00   $e88007(IERA):$00   $e88019(TACR):$00   $e88027
	//                 IER    IMR  IPR  ISR            Mode        TDR Cnt
	// An:MPSC TxEmpty Enable Mask Pend Serv   Timer-A Delay(50us) 255 255

#define A(x)	(baseaddr + (x * 2) + 1)

	// レジスタ生値
	// 1列目(GPIP 関連と VR)
	x = 0;
	y = 0;
	screen.Print(x, y++, "$%06x(GPIP):$%02x", A(MFP::GPIP), mfp_.gpip);
	screen.Print(x, y++, "$%06x(AER): $%02x", A(MFP::AER),  mfp_.aer);
	screen.Print(x, y++, "$%06x(DDR): $%02x", A(MFP::DDR),  mfp_.ddr);
	screen.Print(x, y++, "$%06x(VR):  $%02x", A(MFP::VR),   mfp_.GetVR());
	// 2列目(割り込み関連)
	x = 20;
	y = 0;
	screen.Print(x, y++, "$%06x(IERA):$%02x", A(MFP::IERA), mfp_.ier.a);
	screen.Print(x, y++, "$%06x(IERB):$%02x", A(MFP::IERB), mfp_.ier.b);
	screen.Print(x, y++, "$%06x(IPRA):$%02x", A(MFP::IPRA), mfp_.ipr.a);
	screen.Print(x, y++, "$%06x(IPRB):$%02x", A(MFP::IPRB), mfp_.ipr.b);
	screen.Print(x, y++, "$%06x(ISRA):$%02x", A(MFP::ISRA), mfp_.isr.a);
	screen.Print(x, y++, "$%06x(ISRB):$%02x", A(MFP::ISRB), mfp_.isr.b);
	screen.Print(x, y++, "$%06x(IMRA):$%02x", A(MFP::IMRA), mfp_.imr.a);
	screen.Print(x, y++, "$%06x(IMRB):$%02x", A(MFP::IMRB), mfp_.imr.b);
	// 3列目(タイマー関連)
	x = 40;
	y = 0;
	screen.Print(x, y++, "$%06x(TACR): $%02x", A(MFP::TACR), mfp_.timer[0].tcr);
	screen.Print(x, y++, "$%06x(TBCR): $%02x", A(MFP::TBCR), mfp_.timer[1].tcr);
	screen.Print(x, y++, "$%06x(TCDCR):$%02x", A(MFP::TCDCR), mfp_.GetTCDCR());
	screen.Print(x, y++, "$%06x(TADR): $%02x", A(MFP::TADR), tdr_[0]);
	screen.Print(x, y++, "$%06x(TBDR): $%02x", A(MFP::TBDR), tdr_[1]);
	screen.Print(x, y++, "$%06x(TCDR): $%02x", A(MFP::TCDR), tdr_[2]);
	screen.Print(x, y++, "$%06x(TDDR): $%02x", A(MFP::TDDR), tdr_[3]);
	// 4列目(USART 関連)
	x = 61;
	y = 0;
	screen.Print(x, y++, "$%06x(SCR):$%02x", A(MFP::SCR), mfp_.scr);
	screen.Print(x, y++, "$%06x(UCR):$%02x", A(MFP::UCR), mfp_.ucr);
	screen.Print(x, y++, "$%06x(RSR):$%02x", A(MFP::RSR), mfp_.rsr);
	screen.Print(x, y++, "$%06x(TSR):$%02x", A(MFP::TSR), mfp_.tsr);
	screen.Print(x, y, "$%06x(UDR):", A(MFP::UDR));
	if ((int16)mfp_.udr >= 0) {
		screen.Print(x + 13, y, "$%02x", mfp_.udr);
	} else {
		screen.Print(x + 13, y, "---");
	}

	// 割り込み関連
	y = 9;
	screen.Print(0, y++, "<Interrupt>     %-6s %-4s %-4s %-4s",
		"IER", "IMR", "IPR", "ISR");
	for (int i = 15; i >= 0; i--, y++) {
		uint16 mask = (1U << i);
		screen.Print(0, y, "%c%u:%-12s %-6s %-4s %-4s %-4s",
			i >= 8 ? 'A' : 'B',
			(i % 8),
			intrname[i],
			(mfp_.ier.w & mask) ? "Enable" : "",
			(mfp_.imr.w & mask) ? ""       : "Mask",	// %0 でマスクする
			(mfp_.ipr.w & mask) ? "Pend"   : "",
			(mfp_.isr.w & mask) ? "Serv"   : "");
	}

	// タイマー関連
	x = 40;
	y = 9;
	screen.Print(x, y++, "<Timer> Mode        TDR Next");
	for (int ch = 0; ch < 4; ch++) {
		screen.Print(x, y, "Timer-%c", ch + 'A');
		uint8 tcr = mfp_.timer[ch].tcr;
		if (tcr == 0) {
			screen.Print(x + 8, y, "Stopped");
		} else if (tcr < 8) {
			screen.Print(x + 8, y, "Delay(%s)", prescale_str[tcr]);
		} else if (tcr == 8) {
			screen.Print(x + 8, y, "Event");
		} else {
			screen.Print(x + 8, y, "Pulse(%s)", prescale_str[tcr - 8]);
		}

		screen.Print(x + 20, y, "%3u", tdr_[ch]);
		screen.Print(x + 25, y, "%3u", mfp_.timer[ch].next_tdr & 0xff);
		y++;
	}

	// GPIP 関連
	x = 40;
	y = 9 + 8;
	screen.Puts(x, y++, "<GPIP>    GPIP AER DDR");
	for (int i = 7; i >= 0; i--, y++) {
		uint mask = (1U << i);
		screen.Print(x, y, "b%u %-6s  %u   %u   %s", i, gpipname[i],
			(mfp_.gpip & mask) ? 1 : 0,
			(mfp_.aer & mask) ? 1 : 0,
			(mfp_.ddr & mask) ? "OUT" : "IN");
	}
}

/*static*/ const char *
MFPDevice::intrname[] = {
	"RTC Alarm",
	"EXPON",
	"POW SW",
	"FM IRQ",
	"Timer-D",
	"Timer-C",
	"V-Disp",
	"---",
	"Timer-B",
	"MPSC TxErr",
	"MPSC TxEmpty",
	"MPSC RxErr",
	"MPSC RxFull",
	"Timer-A",
	"CRTC IRQ",
	"H-Sync",
};

/*static*/ const char *
MFPDevice::gpipname[] = {
	"ALARM",
	"EXPON",
	"POW SW",
	"FMIRQ",
	"V-Disp",
	"------",
	"CIRQ",
	"H-Sync",
};

// TxCR の設定値から分周期間というかメインカウンタ1回分の時間を返す
/*static*/ const uint64
MFPDevice::prescale_tsec[8] = {
	0,
	1000_nsec,	// /4
	2500_nsec,	// /10
	4000_nsec,	// /16
	12500_nsec,	// /50
	16000_nsec,	// /64
	25000_nsec,	// /100
	50000_nsec,	// /200
};

/*static*/ const char *
MFPDevice::prescale_str[8] = {
	"",
	"1us",
	"2.5us",
	"4us",
	"12.5us",
	"16us",
	"25us",
	"50us",
};

// タイマーのチャンネルから MFP 割り込みチャンネル番号に変換する
/*static*/ const uint
MFPDevice::timer_vector[4] = {
	MFP::INTR_TIMER_A,
	MFP::INTR_TIMER_B,
	MFP::INTR_TIMER_C,
	MFP::INTR_TIMER_D,
};

// IER[AB] に new_ier (16 ビット全体) をセットする。
void
MFPDevice::SetIER(uint16 new_ier)
{
	// raising はこの書き込みで %0 -> %1 に変化するビットだけを立てたもの、
	// falling はこの書き込みで %1 -> %0 に変化するビットだけを立てたもの。
	uint16 raising = new_ier   & (mfp.ier.w ^ new_ier);
	uint16 falling = mfp.ier.w & (mfp.ier.w ^ new_ier);

	// タイマー用の event[] は、割り込みを起こす必要がある時しか回さない
	// 構造なので、割り込みなしでスタートしたタイマーをその動作中に後から
	// 割り込み許可に変更したら、ここで何事もなかったようにタイマーイベントを
	// 開始しなければいけない。
	for (int ch = 0; ch < countof(mfp.timer); ch++) {
		uint timer_mask = 1U << timer_vector[ch];
		if ((raising & timer_mask) != 0 && mfp.timer[ch].IsDelay()) {
			event[ch]->SetName(string_format("MFP Timer-%c", ch + 'A'));
			RestartTimerEvent(ch);
		}
	}

	// IER を下げたところは IPR も下げる。(紙資料 p4-4)
	mfp.ipr.w &= ~falling;

	// どちらにしても新しい IER によって信号線の状態は変える。
	mfp.ier.w = new_ier;
	ChangeInterrupt();
}

// このタイマーの現在のメインカウンタの値を求める。
inline uint8
MFP::Timer::GetCounter(uint64 now, uint64 period) const
{
	uint8 data = tdr - (((now - start) / period) % tdr);
	return data;
}

// TxCR をセットする
void
MFPDevice::SetTCR(int ch, uint32 data)
{
	MFP::Timer& timer = mfp.timer[ch];
	uint32 prev;

	prev = timer.tcr;
	data &= 0x0f;

	timer.tcr = data;
	if (data == 0) {
		if (prev == 0) {
			// 0 -> 0 なら何も起きない
			return;
		} else if (prev < 8) {
			// タイマー停止
			putlog(1, "Timer-%c Stop", ch + 'A');

			// ここで TDR を確定させる。
			// タイマー動作中の TDR の参照はイベント開始時刻から求めていたが
			// イベントを停止すると求められなくなるので、停止前に求めておく。
			uint64 period = prescale_tsec[prev];
			uint64 now = scheduler->GetVirtTime();
			mfp.timer[ch].tdr = timer.GetCounter(now, period);

			// でイベント停止
			StopTimerEvent(ch);
			return;
		} else if (prev == 8) {
			// イベントカウントモードから停止しても何も起きないはず
		} else {
			// パルス幅測定モード (未実装)
		}
		return;

	} else if (data < 8) {
		// タイマー開始。

		// タイマー開始時は TDR はリロードしない。
		// 割り込みを上げる必要がある時だけイベントを使う。

		uint64 period = prescale_tsec[data];
		uint64 now = scheduler->GetVirtTime();
		timer.start = now;
		if (IsTimerIntrEnable(ch)) {
			event[ch]->time = period * timer.tdr;
			event[ch]->SetName(string_format("MFP Timer-%c", ch + 'A'));
			if (sync_rt) {
				// 自分の実時間軸の基準をここにする。
				stime[ch] = syncer->GetRealTime();
				// event.time はこの後変動するので、周期は別で覚えておく。
				rt_period[ch] = event[ch]->time;

				scheduler->StartRealtimeEvent(event[ch],
					stime[ch], rt_period[ch]);
			} else {
				scheduler->RestartEvent(event[ch]);
			}
		} else {
			event[ch]->SetName(string_format("MFP Timer-%c FreeRun", ch + 'A'));
		}

		putlog(1, "Timer-%c Start Delay mode(%u x %.1fusec)",
			ch + 'A',
			timer.tdr,
			(double)period / 1_usec);
		return;

	} else if (data == 8) {
		// イベントカウントモード

		// TDR をロードする
		timer.next_tdr = timer.tdr;
		putlog(1, "Timer-%c Event count mode", ch + 'A');
		StopTimerEvent(ch);
		return;

	} else {
		putlog(0, "T%cCR Pulse-Width Measurement Mode $%02x (NOT IMPLEMENTED)",
			ch + 'A', data);
		StopTimerEvent(ch);
		return;
	}
}

// TxDR の読み出し
// (副作用はないので Peek 系からも呼び出してよい)
uint8
MFPDevice::GetTDR(int ch) const
{
	const MFP::Timer& timer = mfp.timer[ch];

	if (timer.IsStopped()) {
		// 停止中なら tdr に現在値が保持されている
		return timer.tdr & 0xff;

	} else if (timer.IsDelay()) {
		// ディレイモード動作中なら現在値は保持していないので、算出する。
		// XXX sync_rt == true で割り込み有効にしているタイマーの
		//     TDR を読み出しても割り込みと整合しないがもう仕方ない。
		uint64 period = prescale_tsec[timer.tcr];
		uint64 now = scheduler->GetVirtTime();
		return timer.GetCounter(now, period);

	} else {
		// イベントモード、パルス幅測定モードは未対応
		return timer.tdr & 0xff;

	}
}

// TxDR への書き込み
void
MFPDevice::SetTDR(int ch, uint32 data)
{
	MFP::Timer& timer = mfp.timer[ch];

	if (data == 0) {
		data = 256;
	}
	timer.next_tdr = data;

	if (timer.IsStopped()) {
		// 停止中の書き込みはメインカウンタも更新。
		timer.tdr = data;

	} else if (timer.IsDelay()) {
		// ディレイモード動作中の書き込みは
		// メインカウンタは影響を受けない。
		// 割り込みが回っていれば次の TimerCallback() で自動的にリロードされる
		// ので、ここでは何もしなくてよい。
		// フリーランの場合は、仕方ないので次のタイムアウトパルスで
		// 1回コールバックを呼んでリロードしてもらう。
		if (IsTimerIntrEnable(ch) == false) {
			RestartTimerEvent(ch);
		}

	} else {
		// イベントモード、パルス幅測定モードは未対応
		putlog(0, "Write T%cDR $%02x during unsupported mode (NOT IMPLEMENTED)",
			ch + 'A', timer.next_tdr);
	}
}

// イベントコールバック
void
MFPDevice::TimerCallback(Event *ev)
{
	int ch = ev->code;
	MFP::Timer& timer = mfp.timer[ch];

	// TDR がゼロになったところで呼ばれるので、TDR をリロードする
	if (__predict_false(timer.tdr != timer.next_tdr)) {
		timer.tdr = timer.next_tdr;
		timer.start = 0;
		// TDR (のリロード値) が変わったのでタイマー周りも再設定。
		uint64 period = prescale_tsec[timer.tcr];
		ev->time = period * timer.tdr;
		if (sync_rt) {
			rt_period[ch] = ev->time;
		}
	}

	// カウントが変わったり基準点がまだなければ、基準点も更新。
	if (__predict_false(timer.start == 0)) {
		uint64 now = scheduler->GetVirtTime();
		timer.start = now;
	}

	// 割り込みを上げる
	SetInterrupt(timer_vector[ch]);

	// 次回の割り込みを起こす必要があれば、次のイベントをセット。
	// フリーラン動作中に TCR を書き換えられた時にはワンショットで来る。
	if (__predict_true(IsTimerIntrEnable(ch))) {
		if (sync_rt) {
			// 実時間追従動作の場合。

			// 足す前は前回の割り込み発生時刻なので、足すと現在時刻。
			stime[ch] += rt_period[ch];

			scheduler->StartRealtimeEvent(ev, stime[ch], rt_period[ch]);
		} else {
			scheduler->RestartEvent(ev);
		}
	} else {
		// タイマー動作中に IER を下げた時にはここで次回以降のイベントを停止。
		ev->SetName(string_format("MFP Timer-%c FreeRun", ch + 'A'));
	}
}

// 次のタイムアウトパルス時刻にイベントを起こす。
void
MFPDevice::RestartTimerEvent(int ch)
{
	MFP::Timer& timer = mfp.timer[ch];

	assert(timer.start != 0);

	uint64 now = scheduler->GetVirtTime();
	uint64 period = prescale_tsec[timer.tcr];
	uint64 timeout = period * timer.tdr;
	event[ch]->time = timeout - ((now - timer.start) % timeout);
	scheduler->RestartEvent(event[ch]);
}

// タイマーイベントを停止する。
void
MFPDevice::StopTimerEvent(int ch)
{
	scheduler->StopEvent(event[ch]);
	event[ch]->SetName(string_format("MFP Timer-%c Stopped", ch + 'A'));
	mfp.timer[ch].start = 0;
}

// タイマー割り込みを行うなら true を返す。
// タイマーの動作、停止状態ではなく、あくまで割り込み設定のほう。
bool
MFPDevice::IsTimerIntrEnable(int ch) const
{
	// 割り込みを起こすかどうかは IER のみで決まる。
	return ((mfp.ier.w & (1U << timer_vector[ch])) != 0);
}

// HSync 入力
void
MFPDevice::SetHSync(bool hsync)
{
	SetGPIP(MFP::GPIP_HSYNC, hsync);
}

// VDisp 入力
void
MFPDevice::SetVDisp(bool vdisp)
{
	MFP::Timer& timer = mfp.timer[0];

	// TAI への入力はイベントカウントモードの時のみ
	if (timer.IsEvent()) {
		// AER が示す方向と同じエッジ方向の時
		bool aer = (mfp.aer & (1U << MFP::GPIP_VDISP));
		if (aer == vdisp) {
			// カウンタを減算
			timer.tdr--;
			if (timer.tdr == 0) {
				// 0 になったらリロード
				timer.tdr = timer.next_tdr;

				SetInterrupt(MFP::INTR_TIMER_A);
			}
		}
	}

	// GPIP への入力
	SetGPIP(MFP::GPIP_VDISP, vdisp);
}

// 電源ボタン状態入力 (PowerDevice から呼ばれる)
void
MFPDevice::SetPowSW(bool powsw)
{
	SetGPIP(MFP::GPIP_POWSW, powsw);
}

// ALARM 信号入力 (RTC の ALARM_OUT 出力端子)
void
MFPDevice::SetAlarmOut(bool alarm)
{
	SetGPIP(MFP::GPIP_ALARM, alarm);
}

// GPIP への入力。
// num はビット番号(0..7)。
void
MFPDevice::SetGPIP(int num, bool val)
{
	bool aer = (mfp.aer & (1U << num));
	bool old = (mfp.gpip & (1U << num));

	// 立ち上がりか立ち下がりで割り込みを上げる (AER による)
	// AER OLD VAL   Interrupt
	//   0   0   0   0
	//   0   0   1   0
	//   0   1   0   1
	//   0   1   1   0
	//   1   0   0   0
	//   1   0   1   1
	//   1   1   0   0
	//   1   1   1   0
	if ((old != val) && (aer == val)) {
		// ここで割り込みを上げる
		// XXX GPIP ビット番号と INTR の対応どうなってんの
		int intr;
		if (num < 4) {
			intr = num;
		} else if (num < 6) {
			intr = num + 2;
		} else {
			intr = num - MFP::GPIP_CIRQ + MFP::INTR_CRTC_IRQ;
		}
		SetInterrupt(intr);
	}

	if (val) {
		mfp.gpip |= (1U << num);
	} else {
		mfp.gpip &= ~(1U << num);
	}
}

// UCR への書き込み
void
MFPDevice::SetUCR(uint32 data)
{
	mfp.ucr = data;

	if ((mfp.ucr & MFP::UCR_CLK) == 0) {
		putlog(0, "UCR CLK=0 (NOT IMPLEMENTED)");
	}
	if (((mfp.ucr & MFP::UCR_WL) >> 5) != 0) {
		putlog(0, "UCR WL=%u (NOT IMPLEMENTED)", (mfp.ucr >> 5) & 3);
	}
	if (((mfp.ucr & MFP::UCR_ST) >> 3) != 1) {
		putlog(0, "UCR ST=%u (NOT IMPLEMENTED)", (mfp.ucr >> 3) & 3);
	}
}

// RSR への書き込み
void
MFPDevice::SetRSR(uint32 data)
{
	uint32 old = mfp.rsr;

	mfp.rsr = data;

	if ((old & MFP::RSR_RE) == 0 && (mfp.rsr & MFP::RSR_RE)) {
		// RE 0 -> 1
	}
	if ((old & MFP::RSR_RE) && (mfp.rsr & MFP::RSR_RE) == 0) {
		// RE 1 -> 0
		// 上位4ビットのステータスを %0 に
		mfp.rsr &= ~(MFP::RSR_OE | MFP::RSR_PE | MFP::RSR_FE);
		ClearBF();
	}
}

// RSR::BF (Buffer Full) をセットする
void
MFPDevice::SetBF()
{
	mfp.rsr |= MFP::RSR_BF;
}

// RSR::BF (Buffer Full) をクリアする
void
MFPDevice::ClearBF()
{
	mfp.rsr &= ~MFP::RSR_BF;

	// バッファが空いたのでキーボードに次の転送を催促。
	// 実際のハードウェアでは、キーボードが本体 MFP が Ready になるのを待って
	// いるが、それに相当。
	// システムポートの KEYCTRL はキーボード側で処理している。
	if (RxIsReady()) {
		keyboard->Ready();
	}
}

// UDR への書き込み
void
MFPDevice::SetUDR(uint32 data)
{
	if ((mfp.tsr & MFP::TSR_TE)) {
		keyboard->Command(data);
	}
}

// __
// RR 線の状態を返す。アサートなら true。
bool
MFPDevice::IsRR() const
{
	//        __
	// 実際の RR 信号線は、BF が立っていて、PE | FE がクリアされている場合に
	// アサートされる。ただしエミュレータでは Parity Error も Frame Error も
	// 起きないので、この2ビットは無視する。
	return ((mfp.rsr & MFP::RSR_BF) != 0);
}

// キーボードからの受信が可能なら true を返す。
bool
MFPDevice::RxIsReady() const
{
	// __
	// RR は本来 MFP から CPU/DMAC など上位に対しての Ready で、
	// UDR にデータが用意できたので引き取ってほしいの意。
	// X680x0 ではこれを CPU/DMAC (上位) 向けではなく、下位のキーボードからの
	// フロー制御に流用しており、
	// 上位への(本来の)「UDR にデータが用意できた」(IsRR() = true) は、
	// キーボードに対しては「UDR にデータがあるので送信禁止」、
	// 上位への(本来の)「UDR にデータが用意できていない」(IsRR() = false) は、
	// キーボードに対しては「UDR は空いているので送信してよい」と
	// ここで意味が論理反転することに注意。
	//
	// また、システムポートの KEYCTRL は X680x0Keyboard 側で処理しているので
	// こちらでは不要。
	//
	// UDR が空いていてキーボードからデータが送られてきている間は RR は Ready
	// だが、そもそもキーボードは送出作業中で、新たな送信はできないはずなので
	// この場合、つまり key_event の動作中も false (送信不可) にする。

	return !IsRR() && !key_event->IsRunning();
}

// キーボードからデータを受信
void
MFPDevice::Rx(uint32 data)
{
	// ここは受信したビットをシフトレジスタへ入れ始めたところに相当する
	// ので、まだ Buffer Full は立てない。

	key_event->code = data;
	scheduler->RestartEvent(key_event);
}

// キーボードからのデータが受信し終わった頃に呼ばれるイベント。
void
MFPDevice::KeyCallback(Event *ev)
{
	// UDR にデータを置いて Buffer Full をセット
	mfp.udr = ev->code;
	SetBF();

	// Manual 7-5 7.2.1 Receiver Interrupt Channels より
	// | 割り込みは1文字受信ごとに1回しか発生しませんが、2つの専用割り込み
	// | チャネルにより、正常な受信状態と異常な受信状態に別々のベクター番号を
	// | 割り当てることができます。受信したワードにエラーがあり、エラー割り込み
	// | チャネルが有効な場合は、エラーチャネルのみに割り込みが発生します。
	// | しかし、エラーチャネルが無効の場合、エラー状態の割り込みは、
	// | バッファフル状態の割り込みと一緒にバッファフル割り込みチャネルに
	// | 生成されます。どのエラー条件で割り込みが発生したかを判断するためには、
	// | 常にRSRを読み出す必要があります。
	// XXX: まだエラー状態は実装されていない、なおエラーは起きないので
	// メモだけのままかも知れない。

	SetInterrupt(MFP::INTR_RXFULL);
}

// 必要なら割り込みを上げる。
// ch は MFP 内の割り込みチャンネル番号。MFP::INTR_*
void
MFPDevice::SetInterrupt(uint ch)
{
	assertmsg(ch < 16, "ch=%u", ch);
	__assume(ch < 16);

	uint b = (1U << ch);

	// IER で割り込みが無効なら何もしない
	if ((mfp.ier.w & b) == 0) {
		return;
	}

	// IPR にペンディングを立てる
	mfp.ipr.w |= b;

	// 割り込み信号線の状態を変える
	ChangeInterrupt();
}

// 割り込み信号線の状態を変える
void
MFPDevice::ChangeInterrupt()
{
	// ペンディングの割り込みがあればアサートされる、
	// IPR をクリアするか IMR をクリアすると /IRQ はネゲートされる。
	bool irq = ((mfp.ipr.w & mfp.imr.w) != 0);
	interrupt->ChangeINT(this, irq);
}

// 割り込みアクノリッジ
busdata
MFPDevice::InterruptAcknowledge()
{
	busdata data;

	// IPR が誰も立ってなければ、無視するっぽい?
	// 実際には MFP がバスエラーを出しているのではなく、MFP は俺知らない
	// と思って放置してると上位回路 (たぶん SAKI ちゃん) がタイムアウトして
	// MPU にバスエラー応答する。
	uint32 pending = mfp.ipr.w;
	if (__predict_false(pending == 0)) {
		data.SetBusErr();
	} else {
		// ch はこの時点で IPR に立ってる一番優先度の高いチャンネルのビット
		int ch = 31 - __builtin_clz(pending);
		uint b = (1U << ch);

		// MPU にベクタを引き渡したので Pending をクリア
		mfp.ipr.w &= ~b;
		ChangeInterrupt();

		// ソフトウェア EOI なら In Service を立てる
		if (mfp.vr_s) {
			mfp.isr.w |= b;
		}

		data = mfp.vr_vec + ch;
	}

	// /IEI ネゲート時の /IACK と /IEI の関係がいまいち分からないけど、
	// とりあえず /IACK がアサートされてから 3 MFP クロック後に
	// データバスにベクタを供給、次の 1 MFP クロックで DTACK 応答。
	// で次の 1 MFP クロック以内に CPU が /IACK を下げるとして、
	// 計 5 MFP クロック (= 1.25_usec) としておく。
	const busdata intack_wait = busdata::Wait(5 * 250_nsec);
	data |= intack_wait;

	return data;
}

/*static*/ const char *
MFPDevice::regname[MFP::RegMax] = {
	"GPIP",
	"AER",
	"DDR",
	"IERA",
	"IERB",
	"IPRA",
	"IPRB",
	"ISRA",
	"ISRB",
	"IMRA",
	"IMRB",
	"VR",
	"TACR",
	"TBCR",
	"TCDCR",
	"TADR",
	"TBDR",
	"TCDR",
	"TDDR",
	"SCR",
	"UCR",
	"RSR",
	"TSR",
	"UDR",
};

unix.superglobalmegacorp.com

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