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

//
// MPU (M88xx0)
//

#include "mpu88xx0.h"
#include "config.h"
#include "debugger.h"
#include "event.h"
#include "m88100disasm.h"
#include "scheduler.h"

// コンストラクタ
MPU88xx0Device::MPU88xx0Device()
	: inherited()
{
	SetName("MPU(88100)");

	// LUNA-88K では CMMU の ID は次のように割り振ってあるようだ。
	// OpenBSD の sys/arch/luna68k/include/board.h より。
	//
	//       Inst Data
	// CPU#0 ID=7 ID=6
	// CPU#1 ID=5 ID=4
	// CPU#2 ID=3 ID=2
	// CPU#3 ID=1 ID=0
	//
	// 厳密にはこれは LUNA-88K のハードウェアがどういうコンフィグレーションで
	// m88200 を使うかなので、ここではなくて vm_luna あたりのほうがいいかも
	// しれんけど、あまり遠いのも面倒なので、とりあえずこの辺に。
	// あとマルチプロセッサになったらまたその時考える。
	cmmu[0].reset(new m88200(this, 7));
	cmmu[1].reset(new m88200(this, 6));

	// 設定の初期値。

	// MPU のデフォルトは xxx.usr バグ修正済みのバージョンに設定する。
	// 所有している実機にあわせ 0xb。OpenBSD の
	// sys/arch/m88k/m88k/m88100_machdep.c::m88100_apply_patches() 参照。
	gConfig->SetDefault("m88100-version", 0xb);

	// CMMU のデフォルトは xmem の writeback バグが修正されているバージョンに
	// 設定する。所有している実機にあわせ 0x9。
	// OpenBSD の sys/arch/m88k/m88k/m8820x_machdep.c::m8820x_apr_cmode() 参照。
	gConfig->SetDefault("m88200-version", 0x9);

	// 例外カウンタ
	excep_counter.resize(512);

	// レジスタモニター
	monitor = gMonitorManager->Regist(ID_MONITOR_MPUREG, this);
	monitor->SetCallback(&MPU88xx0Device::MonitorScreen);
	monitor->SetSize(79, 25);
}

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

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

	pseudo_stop_enable = gConfig->Find("mpu-pseudo-stop").AsBool();

	m88100disasm::use_altname = gConfig->Find(".m88k-altname").AsBool();

	// MPU のバージョン。
	const ConfigItem& item_88100ver = gConfig->Find("m88100-version");
	uint ver88100 = item_88100ver.AsInt();
	reg.pid = (ver88100 << 1) | M88100::PID_MASTER;

	// CMMU のバージョン。
	const ConfigItem& item_88200ver = gConfig->Find("m88200-version");
	uint ver88200 = item_88200ver.AsInt();
	cmmu[0]->SetVersion(ver88200);
	cmmu[1]->SetVersion(ver88200);

	return true;
}

// リセット
void
MPU88xx0Device::ResetHard(bool poweron)
{
	if (poweron) {
		// レジスタのうち不定と明記されてるものは、未初期化のまま触ったことが
		// 分かりやすいような適当なパターンで埋めておく。ただし最上位も c に
		// すると BT45x のレジスタ付近を指してしまうので、それは避けておく。
		const uint32 Undefined = 0x0ccccccc;
		for (int i = 1; i < 32; i++) {
			reg.r[i] = Undefined;
		}
		for (int i = 0; i < countof(reg.fcr); i++) {
			reg.fcr[i] = Undefined & reg.fcr_mask[i];
		}

		reg.epsr = Undefined;
		reg.ssbr = Undefined;
		reg.sfip = Undefined;
		reg.snip = Undefined;
		reg.sxip = Undefined;
		// DMTx は bit0(Valid) をクリア。DMAx/DMDx は不定。
		reg.dma0 = Undefined;
		reg.dma1 = Undefined;
		reg.dma2 = Undefined;
		reg.dmd0 = Undefined;
		reg.dmd1 = Undefined;
		reg.dmd2 = Undefined;

		// アクセスが一度もない時にどのアドレスとも一致しないようにしておく。
		lastaddr = (uint64)-1;

		// サイクルを初期化。
		used_cycle = 0;

		// 履歴は電源オン時だけ初期化。
		// (LUNA-88K は ROM が正常パスで CPU リセットを行ったりするので)
		exhist.Clear();
		brhist.Clear();

		// この後起きるリセット例外で xip を初期化する前に参照することに
		// なるので、これだけここでも初期化しておく。
		reg.xip = 0;
	}

	resetting = true;
	Exception(EXCPRI_RESET);

	// リセット例外を 8+256 クロック後に起こす。
	// 8+256 は、電源オン時に最低これだけアサートしなければならないという値
	// のはずで、実際これだけかかるとかいう値ではない。が、こちら側の都合で
	// この後実行される RAM の ResetHard() がブートページを用意した後で
	// cpu->Reset() で xip をフェッチするという順序にしないといけないため、
	// リセット例外が起動するまでに時間がかかるというところを都合よく真似て
	// おく。

	exec_event->SetCallback(&MPU88xx0Device::ResetCallback);
	exec_event->time = (8 + 256) * clock_tsec;
	exec_event->SetName(GetName() + " Reset");
	scheduler->RestartEvent(exec_event);
}

// リセット例外コールバック。
// MPU リセットから規定時間後に呼ばれる。
void
MPU88xx0Device::ResetCallback(Event *ev)
{
	// リセット例外を実行
	uint64 cycle_start = used_cycle;

	// 例外履歴に記録 (例外発生はブランチ履歴にも記録)
	// リセット例外も発生時の XIP を記録する。(LUNA-88K の PROM 1.20)
	last_vector = 0;
	exhist.AddEntry(reg.xip | 1, 0, 0xfc000000 | 0);
	brhist.AddEntry(reg.xip | 1, 0, 0xfc000000 | 0);

	// 内部の例外状態をクリア
	resetting = false;
	excep_pending = 0;
	ChangeState(CPU_STATE_NORMAL);

	// 実際どこかは分からんけど PSR 設定の前にリセットしておきたい
	cmmu[0]->Reset();
	cmmu[1]->Reset();

	// Table 6-8 (p6-47)
	reg.psr = 0x800003ff;
	// XXX ただし SFU1 は未実装なので下げておく
	reg.psr &= ~PSR_SFD1;

#if defined(TEST_INTERRUPT)
	reg.psr &= ~PSR_IND;
	reg.psr &= ~PSR_SFRZ;
#endif

	SetPSR();

	reg.fip = 0;

	// レジスタのうち明記されてるものをクリア。
	// 不定と明記されてるものはリセットでは触らないでおく。
	// scoreboard: cleared
	reg.nip = 0;
	reg.xip = 0;
	reg.vbr = 0;
	reg.fpecr = 0;
	reg.fpcr = 0;
	reg.fpsr = 0;
	// DMTx は bit0(Valid) をクリア。他は不定
	reg.dmt0 &= ~DM_VALID;
	reg.dmt1 &= ~DM_VALID;
	reg.dmt2 &= ~DM_VALID;

	fetch();

	// リセット例外はここまで。
	// ここからは最初の命令のプリフェッチ
	Prefetch();

	// コールバックを ResetCallback から Exec* に差し替える前のここで
	// トレース状態を初期化する。
	SetTrace(debugger->IsTrace());

	// 以降は通常走行
	int cycle = used_cycle - cycle_start;
	exec_event->SetCallback(exec_normal);
	exec_event->time = cycle * clock_tsec;
	exec_event->SetName(GetName() + " Execute");
	scheduler->StartEvent(exec_event);
}

// トレース実行のコールバック
void
MPU88xx0Device::ExecTrace(Event *ev)
{
	debugger->ExecMain();
	ExecNormal(ev);
}

// トレース状態を設定する
void
MPU88xx0Device::SetTrace(bool enable)
{
	if (enable) {
		exec_normal = ToEventCallback(&MPU88xx0Device::ExecTrace);
	} else {
		exec_normal = ToEventCallback(&MPU88xx0Device::ExecNormal);
	}
	exec_event->SetCallback(exec_normal);
}

// Interrupt
void
MPU88xx0Device::Interrupt(int level)
{
	// level は 0 か 1。
	intr_asserted = level;

	if (intr_asserted && IsIntrEnable()) {
		OuterException(EXCPRI_INTERRUPT);

		if (cpu_state == CPU_STATE_STOP) {
			// 割り込みを受け付けたら STOP 解除。
			ChangeState(CPU_STATE_NORMAL);
		}
	} else {
		excep_pending &= ~EXCPRI_INTERRUPT;
		// INTERRUPT を下げたことで全員いなくなれば OUTER を下げる。
		if ((excep_pending & EXCPRI_OUTER_MASK) == 0) {
			excep_pending &= ~EXCPRI_OUTER;
		}
	}
}

void
MPU88xx0Device::SetPSR()
{
	if (intr_asserted && IsIntrEnable()) {
		OuterException(EXCPRI_INTERRUPT);
	} else {
		excep_pending &= ~EXCPRI_INTERRUPT;
		// INTERRUPT を下げたことで全員いなくなれば OUTER を下げる。
		if ((excep_pending & EXCPRI_OUTER_MASK) == 0) {
			excep_pending &= ~EXCPRI_OUTER;
		}
	}

	// CMMU に S/U 信号を出す
	cmmu[0]->SetSuper(IsSuper());
	cmmu[1]->SetSuper(IsSuper());
}

// DOS call エミュレーションのコールバックを指定
void
MPU88xx0Device::SetFLineCallback(bool (*callback)(MPU88xx0Device *, void *),
	void *arg)
{
	fline_callback = callback;
	fline_arg = arg;
}

#define FA(val, bit)	TA::OnOff((val) & M88100::bit)

// モニター更新
void
MPU88xx0Device::MonitorScreen(Monitor *, TextScreen& screen)
{
	m88100reg tmp;
	int x;
	int y;

	screen.Clear();

	// ローカルにコピー
	tmp = reg;

	// 汎用レジスタ
	for (uint i = 0; i < countof(reg.r); i++) {
		screen.Print((i / 8) * 14, i % 8, "r%-2u:%08x", i, tmp.r[i]);
	}

	// 制御レジスタ (上段右)
	x = 57;
	y = 0;
	for (int i = 0; i < 4; i++) {
		int rn = 17 + i;
		screen.Print(x, y++, "sr%u(cr%u):%08x", i, rn, tmp.cr[rn]);
	}
	screen.Print(x, y++, "ssbr(cr3):%08x", tmp.ssbr);
	for (int i = 0; i < 3; i++) {
		screen.Print(x, y++, "%s(cr%u):%08x:%c%c",
			m88100reg::sipname[i], 4 + i,
			(tmp.cr[4 + i] & M88100::SIP_MASK),
			(tmp.cr[4 + i] & M88100::SIP_V) ? 'V' : '-',
			(tmp.cr[4 + i] & M88100::SIP_E) ? 'E' : '-');
	}
	screen.Print(x, y++, "vbr (cr7):%08x", tmp.vbr);

	// 制御レジスタ(中段上; *IP)
	x = 0;
	y = 9;
	screen.Print(x, y++, "xip:%08x (op:%08x:%c)",
		tmp.xip, (uint32)tmp.opX, OpIsBusErr(tmp.opX) ? 'B' : '-');
	screen.Print(x, y++, "nip:%08x (op:%08x:%c)",
		tmp.nip, (uint32)tmp.opF, OpIsBusErr(tmp.opF) ? 'B' : '-');
	screen.Print(x, y++, "fip:%08x", tmp.fip);

	// 制御レジスタ 中段上; pid,*psr)
	x = 30;
	y = 9;
	screen.Print(x, y++,  "pid(cr0):%08x(Arch=$%02x Ver=$%02x MC=%s)",
		tmp.pid,
		(tmp.pid >> 8) & 0xff,
		(tmp.pid >> 1) & 0x7f,
		(tmp.pid & 1) ? "Master" : "Checker");
	screen.Print(x, y, "psr(cr1):%08x(", tmp.psr);
	x--;
	screen.Puts(x + 19, y, FA(tmp.psr,  PSR_SUPER), "S");
	screen.Puts(x + 21, y, ((tmp.psr & M88100::PSR_BO_LE) ? "LE" : "BE"));
	screen.Puts(x + 24, y, FA(tmp.psr,  PSR_SER),  "SER");
	screen.Puts(x + 28, y, FA(tmp.psr,  PSR_C),    "Cy");
	screen.Puts(x + 31, y, FA(tmp.psr,  PSR_SFD1), "SFD1");
	screen.Puts(x + 36, y, FA(tmp.psr,  PSR_MXM),  "MXM");
	screen.Puts(x + 40, y, FA(tmp.psr,  PSR_IND),  "IND");
	screen.Puts(x + 44, y, FA(tmp.psr,  PSR_SFRZ), "SFRZ");
	screen.Puts(x + 48, y, ")");
	y++;
	screen.Print(x, y, "epsr(cr2):%08x(", tmp.epsr);
	screen.Puts(x + 19, y, FA(tmp.epsr, PSR_SUPER), "S");
	screen.Puts(x + 21, y, ((tmp.epsr& M88100::PSR_BO_LE) ? "LE" : "BE"));
	screen.Puts(x + 24, y, FA(tmp.epsr, PSR_SER),  "SER");
	screen.Puts(x + 28, y, FA(tmp.epsr, PSR_C),    "Cy");
	screen.Puts(x + 31, y, FA(tmp.epsr, PSR_SFD1), "SFD1");
	screen.Puts(x + 36, y, FA(tmp.epsr, PSR_MXM),  "MXM");
	screen.Puts(x + 40, y, FA(tmp.epsr, PSR_IND),  "IND");
	screen.Puts(x + 44, y, FA(tmp.epsr, PSR_SFRZ), "SFRZ");
	screen.Puts(x + 48, y, ")");

	// MMU レジスタ(中段下)
	y = 12;
	for (uint i = 0; i <= 2; i++) {
		uint dt =  8 + i * 3;
		uint dd =  9 + i * 3;
		uint da = 10 + i * 3;

		// DMT は "cr8 " があるので左揃え
		screen.Print(0, y, "dmt%u(cr%-2u):%04x",
			i, dt, (tmp.cr[dt] & 0xffff));
		screen.Puts(15, y, "(b,s,d,l,rx, s,en  ,w,v)");
		screen.Puts(16, y, (tmp.cr[dt] & M88100::DM_BO)     ? "B" : "-");
		screen.Puts(18, y, (tmp.cr[dt] & M88100::DM_DAS)    ? "S" : "U");
		screen.Puts(20, y, (tmp.cr[dt] & M88100::DM_DOUB1)  ? "D" : "-");
		screen.Puts(22, y, (tmp.cr[dt] & M88100::DM_LOCK)   ? "L" : "-");
		// カンマが自然に見えるようにここだけカンマも含めて前詰めで出力
		screen.Print(25, y, "%u,",
			(tmp.cr[dt] & M88100::DM_DREG_MASK) >> 7);
		screen.Puts(28, y, (tmp.cr[dt] & M88100::DM_SIGNED) ? "S" : "-");
		uint32 en = (tmp.cr[dt] & M88100::DM_EN_MASK) >> 2;
		screen.Puts(30, y, m88100reg::dmt_en_str[en]);
		screen.Puts(35, y, (tmp.cr[dt] & M88100::DM_WRITE)  ? "W" : "-");
		screen.Puts(37, y, (tmp.cr[dt] & M88100::DM_VALID)  ? "V" : "-");

		// DMD は "cr9 " があるので左揃え
		screen.Print(40, y, "dmd%u(cr%-2u):%08x", i, dd, tmp.cr[dd]);
		screen.Print(60, y, "dma%u(cr%2u):%08x",  i, da, tmp.cr[da]);
		y++;
	}
	y++;

	// FPU レジスタ (下段)
	screen.Print(0, y, "fpecr(fcr0):%08x(", tmp.fpecr);
	x = 21;
	screen.Puts(x +  0, y, FA(tmp.fpecr, FPECR_FIOV),	"FIOV");
	screen.Puts(x +  5, y, FA(tmp.fpecr, FPECR_FUNIMP),	"FUNIMP");
	screen.Puts(x + 12, y, FA(tmp.fpecr, FPECR_FPRV),	"FPRV");
	screen.Puts(x + 17, y, FA(tmp.fpecr, FPECR_FROP),	"FROP");
	screen.Puts(x + 22, y, FA(tmp.fpecr, FPECR_FDVZ),	"FDVZ");
	screen.Puts(x + 27, y, FA(tmp.fpecr, FPECR_FUNF),	"FUNF");
	screen.Puts(x + 32, y, FA(tmp.fpecr, FPECR_FOVF),	"FOVF");
	screen.Puts(x + 37, y, FA(tmp.fpecr, FPECR_FINX),	"FINX");
	screen.Puts(x + 41, y, ")");
	y++;

	char tsiz[4] = { 's', 'd', '2', '3' };
	screen.Print(0, y++,
		"fppt(fcr5): %08x(OpCode=%%%c%c%c%c%c T1=%c T2=%c TD=%c Dest=r%u)",
		tmp.fppt,
		((tmp.fppt >> 15) & 1) + '0',
		((tmp.fppt >> 14) & 1) + '0',
		((tmp.fppt >> 13) & 1) + '0',
		((tmp.fppt >> 12) & 1) + '0',
		((tmp.fppt >> 11) & 1) + '0',
		tsiz[(tmp.fppt >> 9) & 3],
		tsiz[(tmp.fppt >> 7) & 3],
		tsiz[(tmp.fppt >> 5) & 3],
		(tmp.fppt & M88100::FPPT_DEST));

	for (uint i = 0; i < 2; i++) {
		uint32 hs;
		uint32 ls;
		bool isfloat;
		if (i == 0) {
			hs = tmp.fphs1;
			ls = tmp.fpls1;
			isfloat = (tmp.fppt & 0x600) == 0;
		} else {
			hs = tmp.fphs2;
			ls = tmp.fpls2;
			isfloat = (tmp.fppt & 0x180) == 0;
		}
		uint n = i * 2;
		screen.Print(0, y, "fphs%u(fcr%u)/fpls%u(fcr%u): %08x'%08x =",
			(i + 1), (n + 1), (i + 1), (n + 2), hs, ls);
		int32 e = ((int32)(hs << 1)) >> 21;
		screen.Print(45, y, "%c0.%05x'",
			((hs & 0x80000000) ? '-' : '+'),
			(hs & 0x000f'ffff));
		if (isfloat) {
			screen.Print(45 + 9,  y, "%1x", (ls >> 28) & 0xf);
			screen.Print(45 + 10, y, TA::Disable, "%07x", (ls & 0x0fff'ffff));
			screen.Print(45 + 18, y, "e%+d", e);
		} else {
			screen.Print(45 + 9, y, "%08x e%+d", ls, e);
		}
		y++;
	}

	screen.Print(0, y, "fprh(fcr6) /fprl(fcr7):  %08x'%08x(",
		tmp.fprh, tmp.fprl);
	x = 43;
	screen.Puts(x + 0, y, FA(tmp.fprh, FPRH_ADDONE), "ADDONE");
	screen.Print(x + 7, y, "RM=%s)", rmstr[(tmp.fprh >> 29) & 3]);
	y++;

	x = 21;
	screen.Print(0, y, "fpit(fcr8): %08x(OpCode=%%%c%c%c%c%c TD=%u",
		tmp.fpit,
		((tmp.fpit >> 15) & 1) + '0',
		((tmp.fpit >> 14) & 1) + '0',
		((tmp.fpit >> 13) & 1) + '0',
		((tmp.fpit >> 12) & 1) + '0',
		((tmp.fpit >> 11) & 1) + '0',
		((tmp.fpit >> 10) & 1));
	screen.Puts(x + 19, y, FA(tmp.fpit, FPIT_EFINV), "EINV");
	screen.Puts(x + 24, y, FA(tmp.fpit, FPIT_EFDVZ), "EDVZ");
	screen.Puts(x + 29, y, FA(tmp.fpit, FPIT_EFUNF), "EUNF");
	screen.Puts(x + 34, y, FA(tmp.fpit, FPIT_EFOVF), "EOVF");
	screen.Puts(x + 39, y, FA(tmp.fpit, FPIT_EFINX), "EINX");
	screen.Print(x + 44, y, "Dest=r%u)", tmp.fpit & M88100::FPIT_DEST);
	y++;

	uint32 sign = (tmp.fprh & M88100::FPRH_SIGN);
	uint32 exp  = (int32)tmp.fpit >> 20;
	uint32 mant_h = (tmp.fprh & 0x000fffff);
	uint32 mant_l = tmp.fprl;
	screen.Print(12, y, "result: %c%1x.%05x'%08x'grs e%+d",
		(sign ? '-' : '+'),
		((tmp.fprh >> 20) & 1),
		mant_h,
		mant_l,
		exp - 0x3ff);
	screen.Puts(38, y, FA(tmp.fprh, FPRH_GUARD),  "G");
	screen.Puts(39, y, FA(tmp.fprh, FPRH_ROUND),  "R");
	screen.Puts(40, y, FA(tmp.fprh, FPRH_STICKY), "S");
	screen.Print(48, y, "($%03x)", exp & 0x7ff);
	// Inf と NAN はこの形式だけからでは分かりづらいので別途追加で表示。
	// exp は符号拡張しているので指数部 $7ff は exp == -1。
	if (exp == 0xffffffff) {
		x = 55;
		if ((mant_h | mant_l) == 0) {
			if (sign == 0) {
				screen.Puts(x, y, "= +Inf");
			} else {
				screen.Puts(x, y, "= -Inf");
			}
		} else {
			if (sign == 0) {
				screen.Puts(x, y, "= +NAN");
			} else {
				screen.Puts(x, y, "= -NAN");
			}
		}
	}
	y++;

	screen.Print(0, y, "fpsr(fcr62):%08x(", tmp.fpsr);
	x = 21;
	screen.Puts(x +  0, y, FA(tmp.fpsr, FPSR_AFINV), "AFINV");
	screen.Puts(x +  6, y, FA(tmp.fpsr, FPSR_AFDVZ), "AFDVZ");
	screen.Puts(x + 12, y, FA(tmp.fpsr, FPSR_AFUNF), "AFUNF");
	screen.Puts(x + 18, y, FA(tmp.fpsr, FPSR_AFOVF), "AFOVF");
	screen.Puts(x + 24, y, FA(tmp.fpsr, FPSR_AFINX), "AFINX");
	screen.Puts(x + 29, y, ")");
	y++;

	screen.Print(0, y, "fpcr(fcr63):%08x(", tmp.fpcr);
	screen.Puts(x +  0, y, FA(tmp.fpcr, FPCR_EFINV), "EFINV");
	screen.Puts(x +  6, y, FA(tmp.fpcr, FPCR_EFDVZ), "EFDVZ");
	screen.Puts(x + 12, y, FA(tmp.fpcr, FPCR_EFUNF), "EFUNF");
	screen.Puts(x + 18, y, FA(tmp.fpcr, FPCR_EFOVF), "EFOVF");
	screen.Puts(x + 24, y, FA(tmp.fpcr, FPCR_EFINX), "EFINX");
	screen.Print(x + 30, y, "RM=%s)", rmstr[(tmp.fpcr >> 14) & 3]);
}

unix.superglobalmegacorp.com

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