File:  [Isaki's NoNo m68k/m88k emulator] / nono / vm / mpu64180.cpp
Revision 1.1.1.8 (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) 2022 nono project
// Licensed under nono-license.txt
//

//
// MPU (HD647180)
//

#include "mpu64180.h"
#include "config.h"
#include "debugger.h"
#include "event.h"
#include "hd647180.h"
#include "hd647180asci.h"
#include "pio.h"
#include "scheduler.h"
#include "ssg.h"
#include "xpbus.h"

// コンストラクタ
MPU64180Device::MPU64180Device()
	: inherited(OBJ_MPUXP)
{
	ClearAlias();
	AddAlias("XP");
	AddAlias("HD647180");

	// レジスタモニター
	reg_monitor = gMonitorManager->Regist(ID_MONITOR_XPREG, this);
	reg_monitor->SetCallback(&MPU64180Device::MonitorScreenReg);
	reg_monitor->SetSize(38, 6);

	// I/O モニター
	io_monitor = gMonitorManager->Regist(ID_MONITOR_XPIO, this);
	io_monitor->SetCallback(&MPU64180Device::MonitorScreenIO);
	io_monitor->SetSize(55, 20);

	// 割り込みモニター
	intr_monitor = gMonitorManager->Regist(ID_MONITOR_XPINTR, this);
	intr_monitor->SetCallback(&MPU64180Device::MonitorScreenIntr);
	intr_monitor->SetSize(64, 18);
}

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

// ログ表示を差し替える。
// こっちでは XP の PC を表示したい。
void
MPU64180Device::putlogn(const char *fmt, ...) const
{
	char buf[1024];
	va_list ap;
	uint64 vt;
	int len;

	vt = scheduler->GetVirtTime();
	len = snprintf(buf, sizeof(buf), "%16s %04x %s ",
		SecToStr(vt).c_str(),
		GetPPC(),
		GetName().c_str());

	va_start(ap, fmt);
	vsnprintf(buf + len, sizeof(buf) - len, fmt, ap);
	va_end(ap);

	WriteLog(buf);
}

bool
MPU64180Device::Create()
{
	try {
		asci.reset(new HD647180ASCIDevice());
	} catch (...) { }
	if ((bool)asci == false) {
		warnx("Failed to initialize HD647180ASCIDevice at %s", __method__);
		return false;
	}

	return true;
}

// 初期化
bool
MPU64180Device::Init()
{
	if (inherited::Init() == false) {
		return false;
	}

	// MPU クロック。
	// "xp-clock" は MHz 単位だが、変数は kHz 単位。
	int clock_khz;
	const ConfigItem& xp_clock_item = gConfig->Find("xp-clock");
	if (xp_clock_item.TryFixedDecimal(&clock_khz, 3) == false) {
		xp_clock_item.Err();
		return false;
	}
	// 適当な下限値でエラーにする?
	if (clock_khz < 100) {
		xp_clock_item.Err();
		return false;
	}
	// 6.144MHz = 162.76… [nsec] であり、変数導入前は 163 をハードコード
	// して使っていたので、それとの互換性のため四捨五入する。
	clock_tsec = 10_msec / clock_khz;
	clock_tsec = (clock_tsec + 5) / 10;
	if (clock_tsec < 1) {
		xp_clock_item.Err("Clock speed too high");
		return false;
	}

	pio0 = GetPIO0Device();
	xpbus = GetXPbusDevice();

	ssg = gMainApp.FindObject<SSGDevice>(OBJ_SSG);

	scheduler->ConnectMessage(MessageID::MPU_TRACE_XP, this,
		ToMessageCallback(&MPU64180Device::TraceMessage));

	// 命令実行イベント。func, time は都度設定する。
	// 登録は親クラスで行ってある。
	exec_event->SetName("XP(HD647180) Execute");

	// タイマーイベント。time は都度設定する。
	auto evman = GetEventManager();
	timer_event = evman->Regist(this,
		ToEventCallback(&MPU64180Device::TimerCallback),
		"XP(HD647180) Timer");

	return true;
}

// こっちは本体リセットと電源オン
void
MPU64180Device::ResetHard(bool poweron)
{
	if (poweron) {
		used_cycle = 0;

		// 履歴は電源オン時だけ初期化。
		exhist.Clear();
		brhist.Clear();
	}

	// リセット信号でさすがにリセットされるよな?
	Reset();
}

// RESET 信号を変化させる。
// new_reset が true ならアサート、false ならネゲート。
void
MPU64180Device::ChangeRESET(bool new_reset)
{
	if (new_reset) {
		AssertRESET();
	} else {
		NegateRESET();
	}
}

// RESET 信号をアサートする。
void
MPU64180Device::AssertRESET()
{
	if (opmode != OpMode::Reset) {
		Reset();
	}
}

// RESET 信号をネゲートする。
void
MPU64180Device::NegateRESET()
{
	if (opmode == OpMode::Reset) {
		// リセット解除がタイマーの開始時刻
		timer_epoch = scheduler->GetVirtTime();

		EnterNormal();
	}
}

// プロセッサをリセット
// (どうすべ)
void
MPU64180Device::Reset()
{
	putlog(1, "RESET");
	opmode = OpMode::Reset;

	// 命令実行サイクルを停止
	exec_event->SetName("XP(HD647180) Reset");
	scheduler->StopEvent(exec_event);

	// 汎用レジスタの値は不定。目印として $cc で埋めておく。
	memset((void *)&reg, 0xcc, sizeof(reg));
	reg.f.Set(0xcc);
	reg.ex.f.Set(0xcc);

	ppc = 0;
	reg.pc = 0;
	reg.sp = 0;
	reg_i = 0;
	reg_r = 0;
	ief1 = false;
	ief2 = false;
	int0mode = 0;

	for (int i = 0; i < timer.size(); i++) {
		timer[i].reload = 0xffff;
		timer[i].count = 0xffff;
		timer[i].running = false;
		timer[i].intr_enable = false;
		timer[i].tif = 0;
		// 一時レジスタの初期値は定義されてないがとりあえず
		timer[i].tmpcount = 0xffff;
	}
	MakeActiveTimer();
	timer_toc = 0;
	scheduler->StopEvent(timer_event);

	memwait = 3;
	iowait = 4;
	dcntl = 0;
	itc_trap = false;
	itc_ufo  = false;
	itc_ite0 = true;	// ITE0 は初期値 1
	itc_ite1 = false;
	itc_ite2 = false;
	rcr = 0xfc;
	commbase = 0;
	bankbase = 0;
	commarea = 0xf000;
	bankarea = 0x0000;
	io_address = 0x00;

	memset(&int_counter, 0, sizeof(int_counter));

	xpbus->SetRMCR(0xf0);
}

// ノーマルモードに入る。
void
MPU64180Device::EnterNormal()
{
	putlog(1, "Normal mode");
	opmode = OpMode::Normal;

	SetExec(ToEventCallback(&MPU64180Device::ExecNormal));
	exec_event->SetName("XP(HD647180) Execute");

	SetTrace(debugger->IsTrace());

	// 3 クロック後から通常サイクル開始? (HD647180.pdf, p23)
	exec_event->time = 3 * clock_tsec;
	scheduler->RestartEvent(exec_event);
}

// スリープモードに入る。(SLP 命令から呼ばれる)
void
MPU64180Device::EnterSleep()
{
	// ExecNormal() 内で StartEvent() をしないに分岐するのは無駄が多いので、
	// ここでは一旦コールバックだけ差し替えて、イベントを停止する。
	SetExec(ToEventCallback(&MPU64180Device::ExecSleep));
	exec_event->SetName("XP(HD647180) Sleep");
	exec_event->time = 0;

	// 内蔵デバイスのイベントも停止する。
	scheduler->StopEvent(timer_event);
}

// スリープモードから抜ける。
void
MPU64180Device::EnterWakeup()
{
	// 復帰処理のため一旦 Wakeup へ。
	SetExec(ToEventCallback(&MPU64180Device::ExecWakeup));
	exec_event->time = 3 * clock_tsec;

	scheduler->StartEvent(exec_event);
}

// スリープに入る時のイベント
void
MPU64180Device::ExecSleep(Event *ev)
{
	// 実行イベントを停止する。(StartEvent() を呼ばない)
}

// スリープから復帰するイベント
void
MPU64180Device::ExecWakeup(Event *ev)
{
	// EI なら、割り込み処理から再開。
	// DI なら、次の命令から再開。
	if (GetIEF1()) {
		DoInterrupt();
	}

	EnterNormal();
}

// MPU トレース状態設定要求メッセージ
void
MPU64180Device::TraceMessage(MessageID msgid, uint32 arg)
{
	// リセット中は SetTrace しない
	if (opmode == OpMode::Reset) {
		return;
	}

	// デバッガから MPU のトレース状態を設定してくれと言われた
	SetTrace((bool)arg);
}

// トレース実行
void
MPU64180Device::ExecTrace(Event *ev)
{
	debugger->ExecXP();
	(this->*exec_func)(ev);
}

// 実行コールバックを設定する。
void
MPU64180Device::SetExec(EventCallback_t new_exec)
{
	exec_func = new_exec;

	auto execTrace = ToEventCallback(&MPU64180Device::ExecTrace);
	if (exec_event->GetCallback() != execTrace) {
		exec_event->SetCallback(exec_func);
	}
}

// トレース状態を設定する。
void
MPU64180Device::SetTrace(bool enable)
{
	if (enable) {
		exec_event->SetCallback(&MPU64180Device::ExecTrace);
	} else {
		exec_event->SetCallback(exec_func);
	}
}

// IX/IY 分を考慮したサイクル数
void
MPU64180Device::CYCLE_IX(int cycle_hl, int cycle_ix)
{
	if (__predict_true(ixiy == USE_HL)) {
		used_cycle += cycle_hl;
	} else {
		used_cycle += cycle_ix;
	}
}

// メモリ空間からの読み込み。
uint32
MPU64180Device::Read1(uint32 laddr)
{
	uint32 paddr = Translate(laddr);
	// XXX 内蔵 RAM はノーウェイト
	CYCLE(memwait);

	uint32 data = xpbus->Read1(paddr);
	return data;
}

// メモリ空間への書き出し。
uint32
MPU64180Device::Write1(uint32 laddr, uint32 data)
{
	uint32 paddr = Translate(laddr);
	// XXX 内蔵 RAM はノーウェイト
	CYCLE(memwait);

	uint32 r = xpbus->Write1(paddr, data);
	return r;
}

uint32
MPU64180Device::Fetch1()
{
	uint8 data = Read1(reg.pc++);
	ops.push_back(data);
	if (__predict_false(reg.pc > 0xffff)) {
		reg.pc = 0;
	}
	return data;
}

uint32
MPU64180Device::Fetch2()
{
	uint32 data;

	data  = Fetch1();
	data |= Fetch1() << 8;

	return data;
}

uint32
MPU64180Device::Read2(uint32 addr)
{
	uint32 data;

	data  = Read1(addr);
	data |= Read1(addr + 1) << 8;

	return data;
}

void
MPU64180Device::Write2(uint32 addr, uint32 data)
{
	Write1(addr,     data & 0xff);
	Write1(addr + 1, data >> 8);
}

uint32
MPU64180Device::Peek1(uint32 laddr) const
{
	uint32 addr = TranslatePeek(laddr);
	return xpbus->Peek1(addr);
}

uint32
MPU64180Device::Peek2(uint32 laddr) const
{
	uint32 data;
	data  = Peek1(laddr);
	data |= Peek1(laddr + 1) << 8;
	return data;
}

// アドレス変換
uint32
MPU64180Device::Translate(uint32 laddr) const
{
	uint32 paddr;

	paddr = TranslatePeek(laddr);
	putlog(3, "Translate: $%04x -> $%05x", laddr, paddr);
	return paddr;
}

// アドレス変換 (デバッガ用)
uint32
MPU64180Device::TranslatePeek(uint32 laddr) const
{
	uint32 paddr;

	// 論理アドレスから物理アドレスへの変換
	if (laddr >= commarea) {
		// Common Area 1
		paddr = commbase + laddr;
	} else if (laddr >= bankarea) {
		// Bank Area
		paddr = bankbase + laddr;
	} else {
		// Common Area 0
		paddr = laddr;
	}

	return paddr;
}

// MSXDOS エミュレーションのコールバックを指定
void
MPU64180Device::SetSYSCALLCallback(void (*callback)(void *), void *arg)
{
	syscall_callback = callback;
	syscall_arg = arg;
}

void
MPU64180Device::MonitorScreenReg(Monitor *, TextScreen& screen)
{
	screen.Clear();

	// 0         1         2         3
	// 012345678901234567890123456789012345678
	// AF:00 00 (------)  PC:8000   AF':00 00
	// BC:00 00           SP:8000   BC':00 00
	// DE:00 00  IX:0000   I:00     DE':00 00
	// HL:00 00  IY:0000   R:00     HL':00 00
	//
	// Operation Mode: Normal

	hd64180reg tmp = reg;

	screen.Print(0, 0, "AF:%02x %02x", tmp.a, tmp.f.Get());
	screen.Print(0, 1, "BC:%02x %02x", tmp.b, tmp.c);
	screen.Print(0, 2, "DE:%02x %02x", tmp.d, tmp.e);
	screen.Print(0, 3, "HL:%02x %02x", tmp.h, tmp.l);

	screen.Print(9, 0, "(%c%c%c%c%c%c)",
		tmp.f.IsS() ? 'S' : '-',
		tmp.f.IsZ() ? 'Z' : '-',
		tmp.f.IsH() ? 'H' : '-',
		tmp.f.IsV() ? 'V' : (tmp.f.IsP() ? 'P' : '-'),
		tmp.f.IsN() ? 'N' : '-',
		tmp.f.IsC() ? 'C' : '-');
	screen.Print(10, 2, "IX:%04x", tmp.ix);
	screen.Print(10, 3, "IY:%04x", tmp.iy);

	screen.Print(19, 0, "PC:%04x", tmp.pc);
	screen.Print(19, 1, "SP:%04x", tmp.sp);
	screen.Print(20, 2, "I:%02x", reg_i);
	screen.Print(20, 3, "R:%02x", GetR());

	screen.Print(29, 0, "AF':%02x %02x", tmp.ex.a, tmp.ex.f.Get());
	screen.Print(29, 1, "BC':%02x %02x", tmp.ex.b, tmp.ex.c);
	screen.Print(29, 2, "DE':%02x %02x", tmp.ex.d, tmp.ex.e);
	screen.Print(29, 3, "HL':%02x %02x", tmp.ex.h, tmp.ex.l);

	screen.Puts(0, 5, "Operation Mode:");
	screen.Puts(16, 5, (opmode == OpMode::Reset) ? TA::On : TA::Normal,
		opmode_names[(uint)opmode]);
}

/*static*/ const char * const
MPU64180Device::opmode_names[] = {
	"Reset",
	"Normal",
	"Halt",
	"Sleep",
};

void
MPU64180Device::MonitorScreenIO(Monitor *, TextScreen& screen)
{
	int y;
	uint32 val;

	screen.Clear();

/*
0         1         2         3         4         5         6         7
01234567890123456789012345678901234567890123456789012345678901234567890123456789
<MMU>
38H CBR  = $20: Common Base Addr = $20000
39H BBR  = $00:   Bank Base Addr = $00000
3AH CBAR = $83: Common Area = $8000
                  Bank Area = $3000
51H RMCR = $00: RAM Address = $00000
*/
	y = 0;
	screen.Puts(0, y++, "<DMA and wait>");
	val = PeekDCNTL();
	screen.Print(0, y, "32H DCNTL= $%02x:", val);
	screen.Print(16, y, "MemWait=%u", memwait);
	screen.Print(26, y, "IOWait=%u", iowait);
	y++;

	screen.Puts(0, y++, "<Interrupt Control>");
	screen.Print(0, y++, "33H IL   = $%02x", intvec_low);

	val = PeekITC();
	screen.Print(0, y, "34H ITC  = $%02x:", val);
	static const char * const itc_str[] = {
		"TRAP", "UFO", "----", "----", "----", "ITE2", "ITE1", "ITE0"
	};
	for (int i = 0; i < 8; i++) {
		screen.Puts(16 + 5 * i, y,
			TA::OnOff(val & (1U << (7 - i))), itc_str[i]);
	}
	y++;
	val = PeekRCR();
	screen.Print(0, y, "36H RCR  = $%02x:", val);
	static const char * const rcr_str[] = {
		"REFE", "REFW", "----", "----", "----", "----"
	};
	for (int i = 0; i < countof(rcr_str); i++) {
		screen.Puts(16 + 5 * i, y,
			TA::OnOff(val & (1U << (7 - i))), rcr_str[i]);
	}
	screen.Print(46, y, "RefCyc=%u", (1U << (val & 3)) * 10);
	y++;

	y++;
	screen.Puts(0, y++, "<Timer>");
	val = PeekTCR();
	screen.Print(0, y, "10H TCR  = $%02x:", val);
	static const char * const tcr_str[] = {
		"TIF1", "TIF0", "TIE1", "TIE0", "", "", "TDE1", "TDE0"
	};
	for (int i = 0; i < 8; i++) {
		screen.Puts(16 + 5 * i, y,
			TA::OnOff(val & (1U << (7 - i))), tcr_str[i]);
	}
	screen.Print(16 + 5 * 4, y, "TOC=$%x", timer_toc);
	y++;
	for (int ch = 0; ch < timer.size(); ch++) {
		auto& t = timer[ch];
		uint64 reload_tsec = t.reload * clock_tsec * 20;
		screen.Print(4, y++, "Timer%u: Count=$%04x Reload=$%04x(%10.3f usec)",
			ch, t.count, t.reload,
			(double)reload_tsec / 1000);
	}
	screen.Print(0, y++, "18H FRC  = $%02x", PeekFRC());

	y++;
	screen.Puts(0, y++, "<Memory Control>");
	screen.Print(0, y++, "38H CBR  = $%02x: Common Base Addr = $%05x",
		PeekCBR(), commbase);
	screen.Print(0, y++, "39H BBR  = $%02x:   Bank Base Addr = $%05x",
		PeekBBR(), bankbase);
	screen.Print(0, y++, "3AH CBAR = $%02x: Common Area = $%04x",
		PeekCBAR(), commarea);
	screen.Print(18, y++, "Bank Area = $%04x", bankarea);

	screen.Print(0, y++, "3FH ICR  = $%02x: I/O Address = $%02x",
		PeekICR(), io_address);
	screen.Print(0, y++, "51H RMCR = $%02x: RAM Address = $%05x",
		PeekRMCR(), xpbus->GetXPRAMAddr());
}

void
MPU64180Device::MonitorScreenIntr(Monitor *, TextScreen& screen)
{
	static const struct {
		uint offset;
		const char *flagname;
	} table[] = {
		{ 0x00, "" },	// TRAP
		{ 0x66,	"" },	// NMI
		{ 0,	"ITC:ITE0" },	// INT0
		{ 0x00, "ITC:ITE1" },	// INT1
		{ 0x02, "ITC:ITE2" },	// INT2
		{ 0x12, "" },	// Input Capture
		{ 0x14, "" },	// Output Compare
		{ 0x16, "" },	// Timer Overflow
		{ 0x04, "TCR:TIE0" },	// Timer0
		{ 0x06, "TCR:TIE1" },	// Timer1
		{ 0x08, "" },	// DMA0
		{ 0x0a, "" },	// DMA1
		{ 0x0c, "" },	// CSIO
		{ 0x0e, "" },	// ASCI0
		{ 0x10, "" },	// ASCI1
	};
	struct {
		bool enable;	// この割り込みが有効か
		uint32 vecaddr;	// ベクタが格納されているアドレス
		uint16 handler;	// ハンドラのアドレス
	} data[countof(table)];
	int x, y;

	// 割り込み有効制御ビットは各地に散らばってるのでここで集める。
	uint32 itc = PeekITC();
	uint32 tcr = PeekTCR();
	data[HD647180::IntmapINT0].enable	= (itc & 0x01);
	data[HD647180::IntmapINT1].enable	= (itc & 0x02);
	data[HD647180::IntmapINT2].enable	= (itc & 0x04);
	data[HD647180::IntmapTimer0].enable	= (tcr & 0x10);
	data[HD647180::IntmapTimer1].enable	= (tcr & 0x20);

	// ベクタを集める。
	// INT1 (Pri=3) 以降が I + IL + ベクタ方式。
	// 0(TRAP), 1(NMI), 2(INT0) は個別処理。
	uint32 intvec = (reg_i << 8) + intvec_low;
	for (int i = 3; i < countof(data); i++) {
		data[i].vecaddr = intvec + table[i].offset;
		data[i].handler = Peek2(data[i].vecaddr);
	}

	screen.Clear();

	screen.Puts(0, 0, "Mask:");
	screen.Puts(6,  0, TA::OnOff(GetIEF1()), "IEF1");
	screen.Putc(11, 0, '(');
	screen.Puts(12, 0, TA::OnOff(GetIEF2()), "IEF2");
	screen.Putc(16, 0, ')');
	screen.Print(26, 0, "INT0 Mode: %u", int0mode);

	// 0         1         2         3         4         5         6
	// 0123456789012345678901234567890123456789012345678901234567890123
	// Pri Name         Enable   Vec                              Count
	// 00  InputCapture ITC:ITE0 0000H(0000H)01234567890123456789012345

	y = 2; //          012345678901234567890123456789012345
	screen.Puts(0, y, "Pri Name         Enable   Vector");
	screen.Puts(59, y, "Count");
	y++;

	// TRAP, NMI だけ別処理
	screen.Puts(3, y, InterruptName[0]);
	screen.Puts(3, y + 1, TA::Disable, "NMI (NotConn)");

	// INT0 以降はレベルトリガー & マスク可能なので現在の状態を表示
	for (int i = 2; i < 15; i++) {
		screen.Puts(3, y + i, TA::OnOff(intmap & (1U << (31 - i))),
			InterruptName[i]);
		screen.Puts(17, y + i, TA::OnOff(data[i].enable), table[i].flagname);
	}

	// ベクタ
	x = 26;
	screen.Puts(x, y + 0, "0000H");
	screen.Puts(x, y + 1, "0066H");
	if (int0mode == 1) {
		screen.Puts(x, y + 2, "0038H");
	} else {
		// Mode0 はデータバスから命令自体を読み込む方式。
		// Mode1 はデータバスからベクタの下位アドレスを読み込む方式。
		// どちらもサポートしていない。
		screen.Print(x, y + 2, "(IM%u)", int0mode);
	}
	for (int i = 3; i < 15; i++) {
		screen.Print(x, y + i, "%04XH(%04XH)",
			data[i].handler, data[i].vecaddr);
	}

	// カウンタ
	x = 38;
	for (int i = 0; i < 15; i++) {
		screen.Print(0, y + i, "%2u", i);
		screen.Print(x, y + i, "%26s", format_number(int_counter[i]).c_str());
	}
}

/*static*/ std::vector<const char *>
MPU64180Device::InterruptName = {
	"TRAP",
	"NMI",
	"INT0(PIO1)",
	"INT1",
	"INT2",
	"InputCapture",
	"OutputCompare",
	"TimerOverflow",
	"Timer 0",
	"Timer 1",
	"DMA 0",
	"DMA 1",
	"CSIO",
	"ASCI 0",
	"ASCI 1",
};

unix.superglobalmegacorp.com

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