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

// MPU/FPU 種別について:
//
// 構造上やむをえないが MPU が 68030 なら FPU は fpu-type で指定し、
// MPU が 68040/68LC040 なら mpu-type で MPU/FPU を指定する。
//
//	mpu-type	fpu-type
//	--------	--------
//	68030		none	: 68030、FPU なし
//	68030		68881	: 68030 + 68881
//	68LC040		-		: 68040 (FPU なし)
//	68040		-		: 68040 (FPU あり)

#include "mpu680x0.h"
#include "config.h"
#include "debugger.h"
#include "event.h"
#include "exttostr.h"
#include "interrupt.h"
#include "m68030cache.h"
#include "m68040mmu.h"
#include "mainapp.h"
#include "mainbus.h"
#include "scheduler.h"
#include "uimessage.h"
#include "vectortable.h"

// ブートストラップ。設定に応じてインスタンスを生成して返す。
// ここではとりあえず 68040 かそれ以外なら 68030 を生成しておき、
// Init() でもう一度 (機種情報も含めて) チェックする。
// Init() (か Create()) まで進まないとエラー終了出来ないが、
// その時点では MainMPU のインスタンスは必要なので。
MPU680x0Device *
NewMPU680x0Device()
{
	const ConfigItem& item = gConfig->Find("mpu-type");
	std::string mpu_type = item.AsString();

	// 設定に応じて継承側のクラスを作成する。
	// この段階では知らないものは一旦 68030 にしておく。
	if (mpu_type == "68040" || strcasecmp(mpu_type.c_str(), "68LC040") == 0) {
		return new MPU68040Device();
	} else {
		return new MPU68030Device();
	}
}


//
// 68030/040 共通クラス
//

// コンストラクタ
MPU680x0Device::MPU680x0Device()
	: inherited()
{
	// MPU 種別は継承側で設定する。設定されないままなら Init() でエラー。
	mpu_type = m680x0MPUType::NONE;

	// FPU
	fpu_init();

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

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

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

	// なる早で、もう一度 MPU 設定を読んで判定。そのついでに FPU も判定。
	// "mpu-type" の設定値が不正な場合でも (コンストラクト時点ではまだエラーを
	// 報告できない構造なので) 一旦 MPU68030Device インスタンスとしてここに
	// 来る。なのでここで改めてチェックしてエラー報告する必要がある。
	// mpu_type (メンバ変数) 自体はすでにセットされている。
	const ConfigItem& mputype_item = gConfig->Find("mpu-type");
	std::string mputype = mputype_item.AsString();
	if (mputype == "68030") {
		// FPU はこのすぐ次で機種ごとに選ぶ。
	} else if (mputype == "68040") {
		fpu_type = m680x0FPUType::M68040;
	} else if (strcasecmp(mputype.c_str(), "68LC040") == 0) {
		fpu_type = m680x0FPUType::M68LC040;
		// 名前も再設定?
		mpu_name = "MC68LC040";
		SetName("MPU(68LC040)");
	} else {
		mputype_item.Err();
		return false;
	}

	// 機種ごとに MPU/FPU のサポート状況が違う。
	// 68030 なら fpu_type はここでセットする。
	switch (gMainApp.GetVMType()) {
	 case VMType::LUNA1:
	 case VMType::NEWS:
		// NEWS は元々 68030 モデルのみ。
		// うち NWS-1750 は 68030/68882 固定のようだがここでは 68881 とする。
		// LUNA-I は基本的に 68030/68881 モデルのみ。
		if (mpu_type != m680x0MPUType::M68030) {
			mputype_item.Err("Only 68030 can be specified for vmtype=%s",
				gMainApp.GetVMTypeStr().c_str());
			return false;
		}
		fpu_type = m680x0FPUType::M68881;
		break;

	 case VMType::X68030:
	 case VMType::VIRT68K:
		// 68030/68040 をサポート。mpu_type は継承コンストラクタで設定済み。
		if (mpu_type == m680x0MPUType::M68030) {
			// 68030 なら FPU は設定で指定可能。
			const ConfigItem& fputype_item = gConfig->Find("fpu-type");
			std::string fputype = fputype_item.AsString();
			if (fputype == "none" || fputype == "0") {
				fpu_type = m680x0FPUType::M68030_NOFPU;
			} else if (fputype == "68881" || fputype == "1") {
				fpu_type = m680x0FPUType::M68881;
			} else {
				fputype_item.Err();
				return false;
			}
		} else {
			// 68040 なら FPU はすぐ上で設定済み。
		}
		break;

	 default:
		VMPANIC("vmtype=%s not configured", gMainApp.GetVMTypeStr().c_str());
		break;
	}

	// 機種ごとの NetBSD カーネルエントリポイント。
	// 履歴で TRAP #0 がシステムコールかどうかの判定に使う。
	switch (gMainApp.GetVMType()) {
	 case VMType::LUNA1:
		netbsd_entrypoint = 0xc000;
		break;
	 case VMType::X68030:
		netbsd_entrypoint = 0x0022;
		break;
	 case VMType::NEWS:
		netbsd_entrypoint = 0x0000;
		break;
	 case VMType::VIRT68K:
		netbsd_entrypoint = 0x2000;
		break;
	 default:
		__unreachable();
	}

	interruptdev = GetInterruptDevice();
	vectortable = GetVectorTable();

	// 割り込みイベントコールバック設定。
	//
	// 外部デバイスが 68030 の IPL0-2 をアサートしてから、68030 がそれを
	// 認識(確定)するのに 1 ~ 2 クロックかかる。
	// その後レベルを比較するのは次のアップエッジ。
	// その後 /IPEND をアサートするのはその次のアップエッジ。
	// 合わせると、IPL0-2 が変化してから /IPEND がアサートされるまでは
	// 2 ~ 3 クロックかかる。(Figure 8-4)
	auto evman = GetEventManager();
	intr_event = evman->Regist(this,
		ToEventCallback(&MPU680x0Device::InterruptCallback),
		GetName() + " Interrupt");
	intr_event->time = 3 * clock_tsec;

	// レジスタモニタの行数。
	int height = 9;
	if (mpu_type == m680x0MPUType::M68030) {
		height++;
	}
	if (HasFPU()) {
		height += 12;
		if (GetFPUType().Is4060FPU()) {
			height += 3;
		}
	}
	reg_monitor->SetSize(78, height);

	return true;
}

// リセット
void
MPU680x0Device::ResetHard(bool poweron)
{
	if (poweron) {
		// サイクルを初期化。
		used_cycle = 0;

		// 履歴を初期化。電源(再)投入時のみ行う。
		exhist.Clear();
		brhist.Clear();

		for (int i = 0; i < 16; i++) {
			reg.R[i] = 0xcccccccc;
		}
		// XXX ただし X68030 IPLROM には A6 を初期化せずに書き込んでいるバグが
		// あるため、A6 を概ね RAM のあるあたりに指定しておく…。
		reg.A[6] = 0x000ccccc;

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

	// リセット例外を 520 クロック後に起こす。
	// 520 は、電源オン時、Vcc が最小動作規定値に達してから最低 520 クロック
	// の間アサートしなければならないという値であって、実際これだけかかる
	// とかいう値ではない。が、こちら側の都合で、この後実行される RAM の
	// ResetHard() がブートページを用意した後で ExceptionReset() で
	// SP, PC をフェッチするという順序にしないといけないため、リセット例外が
	// 起動するまでに時間がかかるというところを都合よく真似ておく。

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

// リセット例外コールバック。
// MPU リセットから規定時間後に呼ばれる。
void
MPU680x0Device::ResetCallback(Event *ev)
{
	resetting = false;
	intr_pending = false;

	// リセット例外を実行
	int cycle = ExceptionReset();

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

	// 以降は通常走行。
	if (cpu_state == CPU_STATE_NORMAL) {
		exec_event->SetCallback(exec_short);
		exec_event->time = cycle * clock_tsec;
		exec_event->SetName(GetName() + " Execute");
		scheduler->RestartEvent(exec_event);
	} else {
		// リセット例外でダブルバスフォールトならここで停止。
	}
}

// 割り込みチェック、STOP/HALT 処理を含んだ完全な実行サイクルのコールバック。
// SR の I2:I0 が変わった直後などに呼ばれる。
// ここでは割り込みをチェックし、必要なら割り込み処理を起動、
// そうでなければ通常の命令実行を行う。
void
MPU680x0Device::ExecFull(Event *ev)
{
	if (cpu_state == CPU_STATE_HALT) {
		// HALT なら停止。
		return;
	}

	// マスクよりも割り込みレベルのほうが高ければ割り込み処理。
	// この時点で intr_pending が true の可能性もあることに留意。
	// 割り込みレベルは変化してないので Level 7 の特別処理は不要
	// (割り込みレベルの変化はここではなく InterruptCallback() で処理)。
	if (intr_level > reg.intr_mask) {
		intr_pending = true;
	}

	if (intr_pending) {
		// 割り込み処理を起動。

		intr_pending = false;

		// 前の命令実行後となるここで割り込みを起動
		ev->time = ExceptionInterrupt();
		scheduler->StartEvent(ev);
	} else {
		if (cpu_state == CPU_STATE_NORMAL) {
			// コールバックを通常処理に戻してから...
			exec_event->SetCallback(exec_short);

			// この場で次の命令を実行。
			ExecShort(ev);
		} else {
			// STOP ならここで停止。
		}
	}
}

// トレース実行
void
MPU680x0Device::ExecTrace(Event *ev)
{
	debugger->ExecMain();
	ExecFull(ev);
}

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

	// STOP 命令中にオンにするには、停止しているイベントを起こす。
	// STOP 命令中にオフにする場合は、何もしなくていい。
	if (enable && exec_event->IsRunning() == false) {
		scheduler->StartEvent(exec_event);
	}
}

// 割り込みレベルが変わったことを MPU に通知。
void
MPU680x0Device::Interrupt(int level)
{
	assertmsg(0 <= level && level <= 7, "level=%d", level);

	intr_event->code = level;
	if (intr_event->IsRunning() == false) {
		scheduler->StartEvent(intr_event);
	}
}

// 外部割り込みイベントコールバック。
// ev.code 0-7 は新しい割り込みレベル。
//
// 割り込みは本来命令境界ごとにチェックするが、実際に割り込みが起きうるのは
// 1) 割り込み信号線が変化した時
// 2) 割り込みマスクを変更した時
// しかないはずなので、この時だけ命令境界でチェックする。
// ここはその 1) のほう。その2のほうは core.cpp:SetSR() 参照。
//
// 外部割り込みが来たので、レベル比較などをして、必要なら次の命令境界で
// 割り込みチェックを行うようにするところ。
void
MPU680x0Device::InterruptCallback(Event *ev)
{
	uint level = ev->code;
	uint mask = reg.intr_mask;

	// 信号線が 2 クロック期間安定していること云々は省略

	// 割り込みを起こすかどうかに関わらずここで更新。
	intr_level = level;

	// ホールトならここまで。
	if (cpu_state == CPU_STATE_HALT) {
		return;
	}

	// 割り込み例外を起動するのは
	// o レベルがマスクより高い
	// o マスクに関係なく、レベルが下位から 7 に変化した場合
	//   (ここは変化した時だけ呼ばれるのと 7 より上はないので
	//   level==7 なら必ず「下位から 7 に変化」のはず)
	if (level > mask || level == 7) {
		// 通知した結果割り込みが受け付けられたので、
		// この命令終了時のコールバックを割り込み処理起動に差し替える。
		MakeNextFull();
		intr_pending = true;

		// STOP 状態だったらここでイベントを開始する
		if (cpu_state == CPU_STATE_STOP) {
			// STOP から抜ける時間?
			exec_event->time = 1 * clock_tsec;
			scheduler->StartEvent(exec_event);
		}
	}
}

// 次の命令境界は Full にする。
void
MPU680x0Device::MakeNextFull()
{
	exec_event->SetCallback(exec_full);
}

// A-Line 命令エミュレーションのコールバックを指定。
void
MPU680x0Device::SetALineCallback(bool (*callback)(MPU680x0Device *, void*),
	void *arg)
{
	aline_callback = callback;
	aline_arg = arg;
}

// F-Line 命令エミュレーションのコールバックを指定。
void
MPU680x0Device::SetFLineCallback(bool (*callback)(MPU680x0Device *, void*),
	void *arg)
{
	fline_callback = callback;
	fline_arg = arg;
}

// RESET 命令
void
MPU680x0Device::ops_reset()
{
	mainbus->ResetByMPU();
}

// ホールト状態
void
MPU680x0Device::Halt()
{
	ChangeState(CPU_STATE_HALT);

	// この命令完了後に停止するため exec を差し替える。
	MakeNextFull();

	// UI に通知 (メッセージボックス表示とか)
	gMainApp.GetUIMessage()->Post(UIMessage::HALT);
}

// モニター更新の下請け (レジスタ共通部分)
void
MPU680x0Device::MonitorScreenRegCommon(TextScreen& screen,
	m68kreg& tmp, uint32 tmp_ppc, uint32 tmp_state)
{
	int x;

	// データレジスタ、アドレスレジスタ
	for (int i = 0; i < 4; i++) {
		screen.Print(0, i, "D%u:%08x D%u:%08x  A%u:%08x A%u:%08x",
			i + 0, tmp.R[i],
			i + 4, tmp.R[i + 4],
			i + 0, tmp.R[i + 8],
			i + 4, tmp.R[i + 12]);
	}

	// 5列目
	x = 50;
	// SR
	screen.Print(x, 0, "SR:%04x(%c%c%c%c%c)",
		tmp.GetSR(),
		(tmp.ccr.IsX()) ? 'X' : '-',
		(tmp.ccr.IsN()) ? 'N' : '-',
		(tmp.ccr.IsZ()) ? 'Z' : '-',
		(tmp.ccr.IsV()) ? 'V' : '-',
		(tmp.ccr.IsC()) ? 'C' : '-');

	// *SP
	uint ms = (tmp.s ? 2 : 0) | (tmp.m ? 1 : 0); // T1 T0 S M
	if (ms == 3) {
		// Supervisor (Master) mode (A7=MSP, USP/ISP)
		screen.Print(x, 1, TA::Disable, "USP:%08x", tmp.usp);
		screen.Print(x, 2, TA::Disable, "ISP:%08x", tmp.isp);
	} else if (ms == 2) {
		// Supervisor (Interrupt) mode (A7=ISP, USP/MSP)
		screen.Print(x, 1, TA::Disable, "USP:%08x", tmp.usp);
		screen.Print(x, 2, TA::Disable, "MSP:%08x", tmp.msp);
	} else {
		// User mode (A7=USP, ISP/MSP)
		screen.Print(x, 1, TA::Disable, "ISP:%08x", tmp.isp);
		screen.Print(x, 2, TA::Disable, "MSP:%08x", tmp.msp);
	}

	// 6列目
	// PC: 01234567
	// SFC:1  DFC:1
	// VBR:01234567
	x = 66;
	screen.Print(x, 0, "PC: %08x", tmp_ppc);
	screen.Print(x, 1, "SFC:%u  DFC:%u", tmp.GetSFC(), tmp.GetDFC());
	screen.Print(x, 2, "VBR:%08x", tmp.vbr);

	switch (tmp_state) {
	 case CPU_STATE_NORMAL:
		screen.Puts(50, 3, "State: Running");
		break;
	 case CPU_STATE_STOP:
		screen.Puts(50, 3, "State: STOP instruction");
		break;
	 case CPU_STATE_HALT:
		screen.Puts(50, 3, TA::On, "State: Double Bus Fault");
		break;
	 default:
		screen.Print(50, 3, TA::On, "corrupted cpu_state=%u", tmp_state);
		break;
	}
}

// モニタ更新の下請け (FPU)
int
MPU680x0Device::MonitorScreenFPU(TextScreen& screen, int y, m68kreg& tmp)
{
	int x;

/*
0123456789012345678901234567890123456789012345678901234567890123456789012345
FP0:7fff_ffffffff_ffffffff = NAN
FPCR:     0000                    BS SN OP OV UF DZ I2 I1  Prec=.X Mode=Minus
FPSR: 00000000 N Z INF NAN Q=$xx  BS SN OP OV UF DZ I2 I1  AV AO AU AD AI
FPIAR:00000000
*/

	x = 0;
	screen.Puts(x, y++, "<FPU>");
	for (int i = 0; i < 8; i++) {
		screen.Print(x, y++, "FP%u:%04x_%08x_%08x = %s",
			i,
			tmp.fpframe.fpf_regs[i * 3 + 0] >> 16,
			tmp.fpframe.fpf_regs[i * 3 + 1],
			tmp.fpframe.fpf_regs[i * 3 + 2],
			ExtToStr(&tmp.fpframe.fpf_regs[i * 3]).c_str());
	}

	// FPCR
	uint32 fpcr = tmp.fpframe.fpf_fpcr;
	screen.Puts(0, y, "FPCR:");
	screen.Print(10, y, "%04x", fpcr);
	x = 34;
	screen.Puts(x +  0, y, TA::OnOff(fpcr & 0x8000), "BS");
	screen.Puts(x +  3, y, TA::OnOff(fpcr & 0x4000), "SN");
	screen.Puts(x +  6, y, TA::OnOff(fpcr & 0x2000), "OE");
	screen.Puts(x +  9, y, TA::OnOff(fpcr & 0x1000), "OF");
	screen.Puts(x + 12, y, TA::OnOff(fpcr & 0x0800), "UF");
	screen.Puts(x + 15, y, TA::OnOff(fpcr & 0x0400), "DZ");
	screen.Puts(x + 18, y, TA::OnOff(fpcr & 0x0200), "X2");
	screen.Puts(x + 21, y, TA::OnOff(fpcr & 0x0100), "X1");
	screen.Print(x + 25, y, "RP=.%c RM=%s",
		"XSD?"[(fpcr >> 6) & 3],
		rmstr[(fpcr >> 4) & 3]);
	y++;

	// FPSR
	uint32 fpsr = tmp.fpframe.fpf_fpsr;
	uint32 cc = fpsr >> 24;
	screen.Print(0, y, "FPSR: %08x", fpsr);
	x = 15;
	screen.Puts(x +  0, y, TA::OnOff(cc & 0x08), "N");
	screen.Puts(x +  2, y, TA::OnOff(cc & 0x04), "Z");
	screen.Puts(x +  4, y, TA::OnOff(cc & 0x02), "Inf");
	screen.Puts(x +  8, y, TA::OnOff(cc & 0x01), "NAN");
	screen.Print(x + 12, y, "Q=$%02x", (fpsr >> 16) & 0xff);
	x = 34;
	screen.Puts(x +  0, y, TA::OnOff(fpsr & 0x8000), "BS");
	screen.Puts(x +  3, y, TA::OnOff(fpsr & 0x4000), "SN");
	screen.Puts(x +  6, y, TA::OnOff(fpsr & 0x2000), "OE");
	screen.Puts(x +  9, y, TA::OnOff(fpsr & 0x1000), "OF");
	screen.Puts(x + 12, y, TA::OnOff(fpsr & 0x0800), "UF");
	screen.Puts(x + 15, y, TA::OnOff(fpsr & 0x0400), "DZ");
	screen.Puts(x + 18, y, TA::OnOff(fpsr & 0x0200), "X2");
	screen.Puts(x + 21, y, TA::OnOff(fpsr & 0x0100), "X1");
	screen.Puts(x + 25, y, TA::OnOff(fpsr & 0x80), "AV");
	screen.Puts(x + 28, y, TA::OnOff(fpsr & 0x40), "AO");
	screen.Puts(x + 31, y, TA::OnOff(fpsr & 0x20), "AU");
	screen.Puts(x + 34, y, TA::OnOff(fpsr & 0x10), "AZ");
	screen.Puts(x + 37, y, TA::OnOff(fpsr & 0x08), "AX");
	y++;

	// FPIAR
	screen.Print(0, y++, "FPIAR:%08x", tmp.fpframe.fpf_fpiar);

	return y;
}


//
// 68030
//

// コンストラクタ
MPU68030Device::MPU68030Device()
	: inherited()
{
	mpu_type = m680x0MPUType::M68030;
	mpu_name = "MC68030";
	SetName("MPU(68030)");

	// MMU.xRP.DT == 0 になってはいけない(MMU 構成例外)。
	SetSRP(0x00000001, 0);
	SetCRP(0x00000001, 0);

	// キャッシュ
	icache.reset(new m68030Cache());
	dcache.reset(new m68030Cache());

	// レジスタモニター (サイズは Init で確定する)
	reg_monitor = gMonitorManager->Regist(ID_MONITOR_MPUREG, this);
	reg_monitor->SetCallback(&MPU68030Device::MonitorScreenReg);

	// ATC モニター
	atc_monitor = gMonitorManager->Regist(ID_MONITOR_MPUATC, this);
	atc_monitor->SetCallback(&MPU68030Device::MonitorScreenATC);
#if defined(M68030_CUSTOM_ATC)
	atc_monitor->SetSize(114, m68030ATCTable::LINES + 6);
#else
	atc_monitor->SetSize(45, m68030ATCTable::LINES + 2);
#endif

	// キャッシュモニター
	cache_monitor = gMonitorManager->Regist(ID_MONITOR_MPUCACHE, this);
	cache_monitor->SetCallback(&MPU68030Device::MonitorScreenCache);
	cache_monitor->SetSize(48, 20 * 2 + 1);
}

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

void
MPU68030Device::SetLogLevel(int loglevel_)
{
	inherited::SetLogLevel(loglevel_);

	atc.SetLogLevel(loglevel_);
}

void
MPU68030Device::ResetHard(bool poweron)
{
	inherited::ResetHard(poweron);

	if (poweron) {
		// 電源(再)投入で統計情報もクリア。
		atc.ResetStat();
	}
}

static int __unused
sort_atc(const void *a, const void *b)
{
	const uint32 *ua = (const uint32 *)a;
	const uint32 *ub = (const uint32 *)b;
	return *ub - *ua;
}

// モニター更新 (レジスタ)
void
MPU68030Device::MonitorScreenReg(Monitor *, TextScreen& screen)
{
	m68kreg tmp;
	uint32 tmp_ppc;
	uint32 tmp_state;
	int y;

	screen.Clear();

	// ローカルにコピーする。
	// ただし一部は reg ではなく cpu クラス本体にある。
	tmp = reg;
	tmp_ppc = ppc;
	tmp_state = cpu_state;

	// 共通部分。
	MonitorScreenRegCommon(screen, tmp, tmp_ppc, tmp_state);

	// Cache/MMU 部分。
	y = 4;
	y = MonitorScreenMMU(screen, y, tmp);

	// FPU はある時だけ表示。
	if (HasFPU()) {
		MonitorScreenFPU(screen, y, tmp);
	}
}

// モニター更新の下請け (Cache/MMU 部分)
int
MPU68030Device::MonitorScreenMMU(TextScreen& screen, int y, m68kreg& tmp)
{
	int x;

	// Cache
	screen.Puts(0, y++, "<Cache>");
	screen.Print(0, y,  "CACR:%08x(", tmp.cacr);
	screen.Puts(14, y, TA::OnOff(tmp.cacr & M68030::CACR_WA),  "WA");
	screen.Puts(17, y, TA::OnOff(tmp.cacr & M68030::CACR_DBE), "DBE");
	screen.Puts(21, y, "- -");
	screen.Puts(25, y, TA::OnOff(tmp.cacr & M68030::CACR_FD),  "FD");
	screen.Puts(28, y, TA::OnOff(tmp.cacr & M68030::CACR_ED),  "ED");
	screen.Puts(31, y, "000");
	screen.Puts(35, y, TA::OnOff(tmp.cacr & M68030::CACR_IBE), "IBE");
	screen.Puts(39, y, "- -");
	screen.Puts(43, y, TA::OnOff(tmp.cacr & M68030::CACR_FI),  "FI");
	screen.Puts(46, y, TA::OnOff(tmp.cacr & M68030::CACR_EI),  "EI");
	screen.Putc(48, y, ')');
	screen.Print(50, y, "CAAR:%08x", tmp.caar);
	y++;

	// MMU
	x = 0;
	screen.Puts(x, y++, "<MMU>");
	screen.Print(x, y,     "SRP:%08x_%08x", tmp.srp.h, tmp.srp.l);
	screen.Print(x, y + 1, "CRP:%08x_%08x", tmp.crp.h, tmp.crp.l);
	x += 25;
	for (int i = 0; i < 2; i++) {
		uint32 tt = tmp.tt[i];
		screen.Print(x, y + i,
			"TT%u:%08x(%c%c%c Addr=$%02x Mask=$%02x FC=%u FCMask=%u)",
			i, tt,
			(tt & m68030TT::E)   ? 'E' : '-',
			(tt & m68030TT::CI)  ? 'C' : '-',
			(tt & m68030TT::RWM) ? '-' : ((tt & m68030TT::RW)  ? 'R' : 'W'),
			(tt >> 24),
			(tt >> 16) & 0xff,
			(tt >>  4) & 0x07,
			(tt      ) & 0x07);
	}
	y += 2;
	screen.Print(0, y,
		"TC:%08x(%c%c%c IS=$%x TIA-D=%x:%x:%x:%x PS=$%x)",
		tmp.tc,
		(tmp.tc & m68030TC::TC_E)   ? 'E' : '-',
		(tmp.tc & m68030TC::TC_SRE) ? 'S' : '-',
		(tmp.tc & m68030TC::TC_FCL) ? 'F' : '-',
		(tmp.tc >> 16) & 0xf,
		(tmp.tc >> 12) & 0xf,
		(tmp.tc >>  8) & 0xf,
		(tmp.tc >>  4) & 0xf,
		(tmp.tc      ) & 0xf,
		(tmp.tc >> 20) & 0xf);
	x = 49;
	screen.Print(x, y, "MMUSR:%04x(", tmp.mmusr);
	x += 11;
	screen.Putc(x++, y, TA::OnOff(tmp.mmusr & m68030MMUSR::B) | 'B');
	screen.Putc(x++, y, TA::OnOff(tmp.mmusr & m68030MMUSR::L) | 'L');
	screen.Putc(x++, y, TA::OnOff(tmp.mmusr & m68030MMUSR::S) | 'S');
	screen.Putc(x++, y, '-');
	screen.Putc(x++, y, TA::OnOff(tmp.mmusr & m68030MMUSR::W) | 'W');
	screen.Putc(x++, y, TA::OnOff(tmp.mmusr & m68030MMUSR::I) | 'I');
	screen.Putc(x++, y, TA::OnOff(tmp.mmusr & m68030MMUSR::M) | 'M');
	screen.Puts(x, y, "- -");
	x += 3;
	screen.Putc(x++, y, TA::OnOff(tmp.mmusr & m68030MMUSR::T) | 'T');
	screen.Print(x, y, "---N=%u)", tmp.mmusr & m68030MMUSR::N);
	y++;

	return y;
}

// モニター更新 (68030 ATC)
void
MPU68030Device::MonitorScreenATC(Monitor *, TextScreen& screen)
{
	TA e_attr = mmu_tc.e ? TA::Normal : TA::Disable;

	screen.Clear();

#if defined(M68030_CUSTOM_ATC)
	m68030ATCTable_Custom *table;

	struct {
		int fc;
		const char *name;
	} fclist[] = {
		{ 1,	"User/Data" },
		{ 2,	"User/Program" },
		{ 5,	"Super/Data" },
		{ 6,	"Super/Program" },
	};

	for (uint t = 0; t < countof(fclist); t++) {
		int x = t * 29;
		int fc = fclist[t].fc;
		const char *name = fclist[t].name;

		table = atc.GetTable(fc);
		screen.Print(x, 0, "FC=%u %s", fc, name);
		screen.Puts(x, 1, "No.  LAddr    PAddr    Flag");

		std::array<uint32, m68030ATCTable::LINES * 2> sbuf;
		for (uint i = 0; i < m68030ATCTable::LINES; i++) {
			sbuf[i * 2 + 0] = table->line[i].age;
			sbuf[i * 2 + 1] = i;
		}
		qsort(&sbuf[0], m68030ATCTable::LINES, sizeof(uint32) * 2, sort_atc);

		// エントリ表示
		for (uint i = 0; i < m68030ATCTable::LINES; i++) {
			int idx = sbuf[i * 2 + 1];
			m68030ATCLine& a = table->line[idx];
			TA v_attr;
			if (a.IsInvalid()) {
				v_attr = TA::Disable;
			} else {
				v_attr = TA::Normal;
			}

			screen.Print(x,     i + 2, e_attr, "[%02x]", idx);
			screen.Print(x + 5, i + 2, v_attr, "%08x %08x %c%c%c%c",
				a.GetLAddr(),
				a.GetPAddr(),
				a.IsBusError() ? 'B' : '-',
				a.IsCInhibit() ? 'C' : '-',
				a.IsWProtect() ? 'P' : '-',
				a.IsModified() ? 'M' : '-');
		}

		// ヒット率とミス率。
		uint64 total = 0;
		uint64 tthit = 0;
		uint64 miss = 0;
		bool tt_once_hit;

		{
			std::lock_guard<std::mutex> lock(table->hist_mtx);
			for (int i = 0; i < table->hist.Length(); i++) {
				const auto& s = table->hist.Peek(i);
				total += s.total;
				tthit += s.tthit;
				miss  += s.miss;
			}
			tt_once_hit = table->hist_tt_once_hit;
		}

		int y = m68030ATCTable::LINES + 2;
		screen.Puts(x + 1, y, e_attr, "TT Match");
		if (tt_once_hit) {
			screen.Print(x + 10, y, e_attr, "%5.1f%%",
				(double)(tthit * 100) / total);
		} else {
			screen.Puts(x + 12, y, e_attr, "0");
		}
		y++;

		screen.Puts(x + 1, y, e_attr, "ATC Match");
		if (total == 0) {
			screen.Puts(x + 12, y, e_attr, "0");
		} else {
			screen.Print(x + 10, y, e_attr, "%5.1f%%",
				(double)((total - miss - tthit) * 100) / total);
		}
		y++;

		screen.Puts(x + 1, y, e_attr, "ATC Miss");
		if (miss == 0) {
			screen.Puts(x + 12, y, e_attr, "0");
		} else {
			screen.Print(x + 10, y, e_attr, "%5.1f%%",
				(double)(miss * 100) / total);
		}
		y++;
	}

	screen.Print(0, m68030ATCTable::LINES + 5,
		"Note: The real 68030 ATC is single FC-mixed 22-entries. "
		"This ATC with FC-separated %u-entries is by nono.",
		m68030ATCTable::LINES);
#else
	//                 00 [00] SD(5).01234'567 01234'567 ----
	screen.Puts(0, 0, "No Idx  FC    LAddr     PAddr     Flag   Hit%");
	uint y = 1;
	auto table = atc.GetTable();
	for (uint pri = 0; pri < m68030ATCTable::LINES; pri++) {
		uint idx = table->GetIdx(pri);
		const m68030ATCLine *a = &table->line[idx];
		TA attr;
		if (a->IsInvalid()) {
			attr = TA::Disable;
		} else {
			attr = TA::Normal;
		}
		uint fc = a->GetFC();
		screen.Print(0, y, e_attr, "%2d [%02d]", pri, idx);
		screen.Print(8, y++, attr,
			"%u(%s).%06x'00 %06x'00 %c%c%c%c",
			fc, FCstr(fc),
			(a->GetLAddr() >> 8),
			(a->GetPAddr() >> 8),
			(a->IsBusError() ? 'B' : '-'),
			(a->IsCInhibit() ? 'C' : '-'),
			(a->IsWProtect() ? 'P' : '-'),
			(a->IsModified() ? 'M' : '-'));
	}

	// ATC ヒット/ミス率。
	m68030ATCStat tmp = table->stat;
	std::array<float, m68030ATCTable::LINES> hitrate {};
	float missrate = 0;
	// total はヒットとミスの合計。
	uint64 total = tmp.miss;
	for (auto v : tmp.hit) {
		total += v;
	}
	if (total != 0) {
		for (uint i = 0; i < hitrate.size(); i++) {
			hitrate[i] = (float)tmp.hit[i] / total * 100;
		}
		missrate = (float)tmp.miss / total * 100;
	}
	y = 1;
	for (auto r : hitrate) {
		screen.Print(38, y++, e_attr, "%6.2f%%", r);
	}
	screen.Print(31, y, e_attr, "ATCMiss%6.2f%%", missrate);
#endif
}

// 統計情報を更新 (Syncer から呼ばれる)
void
MPU68030Device::CalcStat()
{
	// ATC のヒット率。
	atc.CalcStat();

	// キャッシュのヒット率。
	icache->CalcStat();
	dcache->CalcStat();
}

static char
icache_fc2str(const m68030CacheLine& l)
{
	return l.IsSuper() ? 'S' : 'U';
}

static char
dcache_fc2str(const m68030CacheLine& l)
{
	return "0U234S67"[l.GetFC()];
}

// モニタ更新 (68030 キャッシュ)
void
MPU68030Device::MonitorScreenCache(Monitor *, TextScreen& screen)
{
	int y;

	screen.Clear();

	// <Instruction Cache>
	// 01234567890123456789012345678901234567890123456789
	// Tag Address  Data
	// S.012345'00: 00000000 00000000 -------- 00000000

	y = 0;
	screen.Puts(0, y++, "<Instruction Cache>");
	y = MonitorScreenCache1(screen, y, icache.get(), icache_fc2str);

	y++;
	screen.Puts(0, y++, "<Data Cache>");
	MonitorScreenCache1(screen, y, dcache.get(), dcache_fc2str);
}

// モニタ更新 (68030 キャッシュ片方の下請け)
int
MPU68030Device::MonitorScreenCache1(TextScreen& screen, int y,
	m68030Cache *cache, char (*fc2str)(const m68030CacheLine&))
{
	screen.Puts(0, y, "Tag Address  Data[0]");
	screen.Puts(13 + 1 * 9, y, "[1]");
	screen.Puts(13 + 2 * 9, y, "[2]");
	screen.Puts(13 + 3 * 9, y, "[3]");
	y++;

	TA attr = cache->enable ? TA::Normal : TA::Disable;
	for (int idx = 0; idx < cache->line.size(); idx++) {
		auto& l = cache->line[idx];
		screen.Print(0, y, attr, "%c.%06x'%x0:",
			fc2str(l), (l.TagAddr() >> 8), idx);

		for (int i = 0; i < 4; i++) {
			if (l.valid[i]) {
				screen.Print(13 + 9 * i, y, attr, "%08x", l.data[i]);
			} else {
				screen.Puts(13 + 9 * i, y, TA::Disable, "--------");
			}
		}
		y++;
	}

	// 統計。
	float hit = 0.0;
	{
		std::lock_guard<std::mutex> lock(cache->hist_mtx);
		int len = cache->hist.Length();
		if (len != 0) {
			for (int i = 0; i < cache->hist.Length(); i++) {
				const auto& s = cache->hist.Peek(i);
				hit += s.hit;
			}
			hit /= len;
		}
	}

	y++;
	screen.Print(1, y++, attr, "Cache Read Hit            %5.1f%%", hit);

	return y;
}

/*static*/ const char
MPU68030Device::fcstr[] =
	"--\0\0"	// 0
	"UD\0\0"	// 1
	"UP\0\0"	// 2
	"--\0\0"	// 3
	"--\0\0"	// 4
	"SD\0\0"	// 5
	"SP\0\0"	// 6
	"--";		// 7


//
// 68040
//

// コンストラクタ
MPU68040Device::MPU68040Device()
	: inherited()
{
	mpu_type = m680x0MPUType::M68040;
	mpu_name = "MC68040";
	SetName("MPU(68040)");

	cycle_table = cycle_table_040;

	mmu_itt[0].reset(new m68040TT("ITT0", &reg.itt[0]));
	mmu_itt[1].reset(new m68040TT("ITT1", &reg.itt[1]));
	mmu_dtt[0].reset(new m68040TT("DTT0", &reg.dtt[0]));
	mmu_dtt[1].reset(new m68040TT("DTT1", &reg.dtt[1]));
	mmu_tc.reset(new m68040TC());
	atc_inst.reset(new m68040ATC());
	atc_data.reset(new m68040ATC());

	// レジスタモニター (サイズは Init で確定する)
	reg_monitor = gMonitorManager->Regist(ID_MONITOR_MPUREG, this);
	reg_monitor->SetCallback(&MPU68040Device::MonitorScreenReg);

	// ATC モニター (Inst/Data)
	atc_monitor = gMonitorManager->Regist(ID_MONITOR_MPUATC, this);
	atc_monitor->SetCallback(&MPU68040Device::MonitorScreenATC);
	atc_monitor->SetSize(125, 34);
}

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

// モニター更新 (レジスタ)
void
MPU68040Device::MonitorScreenReg(Monitor *, TextScreen& screen)
{
	m68kreg tmp;
	uint32 tmp_ppc;
	uint32 tmp_state;
	FPU40 tmp_fpu40;
	int y;

	screen.Clear();

	// ローカルにコピーする。
	// ただし一部は reg ではなく cpu クラス本体にある。
	tmp = reg;
	tmp_ppc = ppc;
	tmp_state = cpu_state;
	tmp_fpu40 = fpu40;

	// 共通部分。
	MonitorScreenRegCommon(screen, tmp, tmp_ppc, tmp_state);

	// Cache/MMU 部分。
	y = 4;
	y = MonitorScreenMMU(screen, y, tmp);

	// FPU はある時だけ表示。
	if (HasFPU()) {
		y = MonitorScreenFPU(screen, y, tmp);
		MonitorScreenFPU40(screen, y, tmp_fpu40);
	}
}

// モニター更新の下請け (Cache/MMU 部分)
int
MPU68040Device::MonitorScreenMMU(TextScreen& screen, int y, m68kreg& tmp)
{
	int x;

	screen.Puts(0, y++, "<MMU/Cache>");
	screen.Print(0, y, "SRP:%08x    TC:%04x(   )", tmp.srp40, tmp.tc40);
	screen.Putc(24, y, TA::OnOff(tmp.tc40 & m68040TC::E) | 'E');
	screen.Putc(26, y, TA::OnOff(tmp.tc40 & m68040TC::P) | 'P');
	y++;
	screen.Print(0, y++, "URP:%08x", tmp.urp40);
	screen.Print(0, y, "MMUSR:%05x'%03x",
		(tmp.mmusr40 >> 12),
		(tmp.mmusr40 & 0xfff));
	x = 16;
	screen.Putc(x++, y, TA::OnOff(tmp.mmusr40 & m68040MMUSR::B) | 'B');
	screen.Putc(x++, y, TA::OnOff(tmp.mmusr40 & m68040MMUSR::G) | 'G');
	screen.Putc(x++, y, TA::OnOff(tmp.mmusr40 & m68040MMUSR::U1)| 'U');
	screen.Putc(x++, y, TA::OnOff(tmp.mmusr40 & m68040MMUSR::U0)| 'U');
	screen.Putc(x++, y, TA::OnOff(tmp.mmusr40 & m68040MMUSR::S) | 'S');
	x++;
	screen.Print(x,  y, "CM%u", (tmp.mmusr40 & m68040MMUSR::CM) >> 5);
	x += 4;
	screen.Putc(x++, y, TA::OnOff(tmp.mmusr40 & m68040MMUSR::M) | 'M');
	screen.Putc(x++, y, '-');
	screen.Putc(x++, y, TA::OnOff(tmp.mmusr40 & m68040MMUSR::W) | 'W');
	screen.Putc(x++, y, TA::OnOff(tmp.mmusr40 & m68040MMUSR::T) | 'T');
	screen.Putc(x++, y, TA::OnOff(tmp.mmusr40 & m68040MMUSR::R) | 'R');
	y++;

	screen.Print(0, y, "CACR:%08x(", tmp.cacr);
	screen.Puts(14, y, TA::OnOff(tmp.cacr & M68040::CACR_DE), "DE");
	screen.Puts(17, y, TA::OnOff(tmp.cacr & M68040::CACR_IE), "IE");
	screen.Putc(19, y, ')');
	y++;

	y -= 4;
	x = 33;
	MonitorScreenTT(screen, x, y++, "ITT0", tmp.itt[0]);
	MonitorScreenTT(screen, x, y++, "ITT1", tmp.itt[1]);
	MonitorScreenTT(screen, x, y++, "DTT0", tmp.dtt[0]);
	MonitorScreenTT(screen, x, y++, "DTT1", tmp.dtt[1]);

	return y;
}

// *TTn 1つ分の下請け。
void
MPU68040Device::MonitorScreenTT(TextScreen& screen, int x, int y,
	const char *name, uint32 tt)
{
	bool e = (tt & m68040TT::E);

	screen.Print(x, y, "%s:%08x(", name, tt);
	x += 14;
	screen.Putc(x++, y, TA::OnOff(e) | 'E');
	x++;
	screen.Print(x, y, (e ? TA::Normal : TA::Disable),
		"Base=$%02x Mask=$%02x", (tt >> 24), (tt >> 16) & 0xff);

	x += 18;
	if (e) {
		screen.Putc(x++, y, TA::OnOff(tt & m68040TT::S_IGN) | 'S');
		screen.Putc(x++, y, TA::OnOff(tt & m68040TT::S_FC2) | 'S');
		screen.Putc(x++, y, TA::OnOff(tt & m68040TT::U1)    | 'U');
		screen.Putc(x++, y, TA::OnOff(tt & m68040TT::U0)    | 'U');
		x++;
		screen.Print(x, y, "CM%u", (tt & m68040TT::CM) >> 5);
		x += 4;
		screen.Putc(x++, y, TA::OnOff(tt & m68040TT::W)     | 'W');
	} else {
		screen.Puts(x, y, TA::Disable, "---- --- -");
		x += 10;
	}
	screen.Putc(x, y, ')');
}

void
MPU68040Device::CalcStat()
{
}

unix.superglobalmegacorp.com

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