File:  [Isaki's NoNo m68k/m88k emulator] / nono / m680x0 / m68030core.cpp
Revision 1.1.1.11 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:05:26 2026 UTC (3 months ago) by root
Branches: MAIN, Isaki
CVS tags: v020, HEAD
nono 1.0.0

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

#include "mpu680x0.h"
#include "m68030cache.h"
#include "debugger.h"
#include "interrupt.h"
#include "mainbus.h"
#include "scheduler.h"
#include "vectortable.h"

#define OP_FUNC(name)	__CONCAT(op_,name)()

// 1命令実行する。
// 割り込みチェックなどを省いたショート版。
void
MPU680x0Device::ExecShort(Event& ev)
{
	uint64 cycle_start = used_cycle;

	buswait = 0;

	try {
		// 1命令版では switch-case を使うしかない。
		ppc = reg.pc;
		flag_pi = 0;
		flag_pd = 0;
		ir = fetch_2();

		// uint16 を 6ビット右シフトしたら 1024 以上になるはずは
		// ないのだが、デフォルトでは gcc/clang どちらも律儀に
		// 1024 以上かどうか比較して分岐しようとする。
		// gcc は __assume() を入れることでこの比較・分岐を出力
		// しないようにすることが出来た(がパフォーマンスの差は
		// 見えない程度でしかなかった)。
		// clang ではどうやったら効くか不明。
		uint16 ir6 = ir >> 6;
		__assume(ir6 < 1024);
		switch (ir6) {
#include "m68030switch.h"
		}

		if (__predict_false(icache->enable == false)) {
			used_cycle += cycle_nocache;
			cycle_nocache = 0;
		}
	} catch (uint vector) {
		// ここで(C++の)例外スローをキャッチするのは
		// - メモリ空間をアクセスした先でのバスエラー例外
		// - EA パース中の不当命令例外
		// のみ。それ以外の命令途中例外はその場で処理できる。
		if (__predict_false(loglevel >= 2)) {
			const char *name = vectortable->GetExceptionName(vector);
			if (__predict_true(name)) {
				putlogn("%s", name);
			} else {
				putlogn("vector 0x%x", vector);
			}
		}
		if (vector == M68K::EXCEP_BUSERR) {
			ExceptionBuserr(vector);
		} else {
			Exception(vector);
		}
	}

	// 外部割り込みの起動は VM 側で処理している。

	int cycle = used_cycle - cycle_start;
	ev.time = cycle * clock_nsec + buswait;
	scheduler->StartEvent(ev);
}

// 二重バスフォールト
void
MPU680x0Device::DoubleBusFault(const char *where)
{
	// where をログに出す?

	// ホールト状態へ移行
	Halt();
}

// SR (と CCR) をセットする。
// リセット例外以外から使う (リセット例外はやや異なるため)。
//
// 割り込みマスクが変わる場合は、ここで次の命令実行コールバックを差し替える。
void
MPU680x0Device::SetSR(uint16 data)
{
	bool oldS = reg.s;
	bool oldM = reg.m;

	// 現在の SP を保存
	if (oldS == false) {
		reg.usp = reg.A[7];
	} else if (oldM == false) {
		reg.isp = reg.A[7];
	} else {
		reg.msp = reg.A[7];
	}

	reg.ccr.Set(data);
	reg.s = ((data & M68K::SR_S) != 0);
	reg.m = ((data & M68K::SR_M) != 0);

	// 割り込みマスクレジスタを更新して..
	uint new_mask = (data >> 8) & 7;
	if (reg.intr_mask != new_mask) {
		reg.intr_mask = new_mask;

		// 次の命令境界で割り込みをチェックさせる
		MakeNextFull();
	}

	// 新しい SP を用意
	if (reg.s == false) {
		reg.A[7] = reg.usp;
	} else if (reg.m == false) {
		reg.A[7] = reg.isp;
	} else {
		reg.A[7] = reg.msp;
	}

	// FC2-0
	fc_inst = BusAddr::Fetch | busaddr::SU(reg.s);
	fc_data = BusAddr::D     | busaddr::SU(reg.s);

	// XXX 他に何か状態変えないといけないことあったっけか。
}

// CACR レジスタ書き込み
void
MPU680x0Device::SetCACR(uint32 newval)
{
	newval &= M68K::CACR_MASK;
	uint32 oldval = reg.cacr;
	uint32 chgval = oldval ^ newval;

	// EI (命令キャッシュ有効)
	if ((chgval & M68K::CACR_EI) != 0) {
		if ((newval & M68K::CACR_EI) != 0) {
			// 命令キャッシュ有効
			icache->enable = true;
			icache->hist.Clear();
			putlog(1, "I-Cache enabled");
		} else {
			// 命令キャッシュ無効
			icache->enable = false;
			putlog(1, "I-Cache disabled");
			// 命令キャッシュが有効だった期間 nocache は使われずに
			// 積算され続けているが、キャッシュ無効になるとこれを加算
			// しようとするのでここでリセット。
			cycle_nocache = 0;
		}
	}

	// FI (命令キャッシュ凍結)
	if ((chgval & M68K::CACR_FI) != 0) {
		putlog(0, "CACR:FI Not Implemented");
		icache->freeze = (newval & M68K::CACR_FI);
	}

	// CEI (命令キャッシュの指定エントリをクリア)
	if ((chgval & M68K::CACR_CEI) != 0) {
		// CAAR の b7-2 で示されるロングワードを無効にする。
		uint32 idx = (reg.caar & 0x000000f0) >> 4;
		uint32 sel = (reg.caar & 0x0000000c) >> 2;
		auto& l = icache->line[idx];
		l.valid[sel] = 0;

		newval &= ~M68K::CACR_CEI;
	}

	// CI (命令キャッシュクリア)
	if ((chgval & M68K::CACR_CI) != 0) {
		for (auto& l : icache->line) {
			l.valid_all = 0;
		}

		newval &= ~M68K::CACR_CI;
	}

	// IBE (命令バースト有効)
	if ((chgval & M68K::CACR_IBE) != 0) {
		icache->burst_enable = (newval & M68K::CACR_IBE) && has_burst_access;
	}

	// ED (データキャッシュ有効)
	if ((chgval & M68K::CACR_ED) != 0) {
		if ((newval & M68K::CACR_ED) != 0) {
			// データキャッシュ有効。
			dcache->enable = true;
			dcache->hist.Clear();
			putlog(1, "D-Cache enabled");
		} else {
			// データキャッシュ無効。
			dcache->enable = false;
			putlog(1, "D-Cache disabled");
		}
	}

	// FD (データキャッシュ凍結)
	if ((chgval & M68K::CACR_FD) != 0) {
		dcache->freeze = (newval & M68K::CACR_FD);
		putlog(1, "CACR:FD -> %u", dcache->freeze);
	}

	// CED (データキャッシュの指定エントリをクリア)
	if ((chgval & M68K::CACR_CED) != 0) {
		// CAAR の b7-2 で示されるロングワードを無効にする。
		uint32 idx = (reg.caar & 0x000000f0) >> 4;
		uint32 sel = (reg.caar & 0x0000000c) >> 2;
		auto& l = dcache->line[idx];
		l.valid[sel] = 0;

		newval &= ~M68K::CACR_CED;
	}

	// CD (データキャッシュクリア)
	if ((chgval & M68K::CACR_CD) != 0) {
		for (auto& l : dcache->line) {
			l.valid_all = 0;
		}

		newval &= ~M68K::CACR_CD;
	}

	// DBE (データバースト有効)
	if ((chgval & M68K::CACR_DBE) != 0) {
		dcache->burst_enable = (newval & M68K::CACR_DBE) && has_burst_access;
	}

	// WA (ライトアロケート)
	if ((chgval & M68K::CACR_WA) != 0) {
		dcache->wa = (newval & M68K::CACR_WA);
	}

	// とりあえずレジスタイメージも保存。
	reg.cacr = newval;
}

// 命令中に (An)+ で変化したアドレスレジスタを命令実行前の状態に巻き戻す。
// flag_pi は bit7..0 が save_pi[7..0] の A7..A0 に対応している。
void
MPU680x0Device::restore_reg_pi()
{
	for (int i = 7; flag_pi; i--) {
		if ((int8)flag_pi < 0) {
			reg.A[i] = save_pi[i];
		}
		flag_pi <<= 1;
	}
}

// 命令中に -(An) で変化したアドレスレジスタを命令実行前の状態に巻き戻す。
// flag_pd は bit7..0 が save_pd[7..0] の A7..A0 に対応している。
void
MPU680x0Device::restore_reg_pd()
{
	for (int i = 7; flag_pd; i--) {
		if ((int8)flag_pd < 0) {
			reg.A[i] = save_pd[i];
		}
		flag_pd <<= 1;
	}
}

// vector にジャンプする。vector はベクタ番号(アドレスではない)。
void
MPU680x0Device::JumpVector(uint vector, bool from_buserr)
{
	uint32 vecaddr;
	uint32 newpc;

	vecaddr = reg.vbr + vector * 4;
	newpc = read_4(vecaddr);
	// VBR 込みのジャンプ先が確定したところで今度はブランチ履歴に記録。
	// この時のブランチ元はベクタアドレス
	// (reg.pc は fetch によって進んでしまうので先に保存)
	uint32 inst = BranchHistory_m680x0::VectorJump;
	brhist.AddEntry(vecaddr | (IsSuper() ? 1 : 0), newpc, inst);

	// 奇数番地ならアドレスエラー
	if (__predict_false((newpc & 1) != 0)) {
		if (from_buserr) {
			// バスエラー処理中ならダブルバスフォールト。
			throw M68K::EXCEP_ADDRERR;
		} else {
			ExceptionBuserr(M68K::EXCEP_ADDRERR);
		}
		return;
	}
	reg.pc = newpc;

	// 実際はここでパイプライン充填のために3ワードフェッチするので、
	// ベクタテーブルはフェッチ出来たけどそのジャンプ先がバスエラーに
	// なるケースはここでバスエラーが出なければいけないが、まだ
	// キャッシュを実装していないので、ここで1ワードフェッチしてみて
	// PC を戻すあほっぽい方法を取る。
	fetch_2();
	reg.pc -= 2;
}

// 例外スタックフレーム $0 を生成する。
// vector はベクタ番号。
// pc はスタックに積むプログラムカウンタの値 (例外によって、この命令位置か
// 次の命令位置かが異なる)。
// sr は例外発生時の SR。
inline void
MPU680x0Device::GenerateExframe0(uint vector, uint32 pc, uint16 sr)
{
	// 例外スタックフレーム $0
	// SP+$00.W STATUS REGISTER
	//   +$02.L PROGRAM COUNTER		; 例外ごとに異なる
	//   +$06.W %0000 | VECTOR

	push_2((0 << 12) | (vector << 2));
	push_4(pc);
	push_2(sr);
}

// 例外スタックフレーム $2 を生成する。
// vector はベクタ番号。
// スタックに積む PC は常に次の命令。
inline void
MPU680x0Device::GenerateExframe2(uint vector, uint16 sr)
{
	// 例外スタックフレーム $2
	// SP+$00.W STATUS REGISTER
	//   +$02.L PROGRAM COUNTER		; 次の命令位置
	//   +$06.W %0010 | VECTOR
	//   +$08.L INSTRUCTION ADDRESS	; 例外を起こした命令のアドレス

	push_4(ppc);			// 例外を起こした命令のアドレス
	push_2((2 << 12) | (vector << 2));
	push_4(reg.pc);			// 次の命令位置
	push_2(sr);
}

// 例外スタックフレーム $B を生成する。
inline void
MPU680x0Device::GenerateExframeB(uint vector, uint16 sr)
{
	uint16 ssw;
	uint32 fault_addr;
	uint32 stageBaddr;

	stageBaddr = reg.pc + 2;
	// この後のスタック操作で bus が変化する前に保存
	fault_addr = bus.laddr.Addr();

	// DF は常に立てる必要がある??
	ssw = bus.GetSSW() | M68K::SSW_DF;
	if (mmu_enable && (ssw & M68K::FC_DATA) != 0) {
		// 命令アクセスなら RB をセット。
		ssw |= M68K::SSW_RB;
	}

	// 例外スタックフレーム $B
	// SP+$00.W   STATUS REGISTER
	//   +$02.L   PROGRAM COUNTER
	//   +$06.W   %1011 | VECTOR
	//   +$08.W   (internal register)
	//   +$0a.W   SPECIAL STATUS REGISTER		; SSW
	//   +$0c.W   INSTRUCTION PIPE STAGE C
	//   +$0e.W   INSTRUCTION PIPE STAGE B
	//   +$10.L   DATA CYCLE FAULT ADDRESS
	//   +$14.W*2 (internal register)
	//   +$18.L   DATA OUTPUT BUFFER
	//   +$1c.W*4 (internal register, 4 words)
	//   +$24.L   STAGE B ADDRESS
	//   +$28.W*2 (internal register, 2 words)
	//   +$2c.L   DATA INPUT BUFFER
	//   +$30.W*3 (internal register, 3 words)
	//   +$36.W   VERSION# | internal information
	//   +$38.W*18 (internal register, 18 words)
	//   +$5a
	//	こっちから上に向かって PUSH

	// internal registers, 18 words
	// 内部レジスタ18ワードのうち、ここでは17ワード分を save_pd[8] (16ワード)、
	// flag_pd (1ワード) として使うことにする。
	// -(An) でバスエラーが起きると An は減算された状態になるが、DF ビットが
	// 立っていて再実行する時には現行構造では命令先頭から再実行せざるをえず、
	// そうするともう一度プリデクリメントが動いてしまう。そのため、RTE の時点
	// で DF=1 ならプリデクリメント発生前の An に戻すという処理をする。
	// RTE 時点で DF=0 なら命令を再実行しないため An も巻き戻してはならず、
	// そのためプリデクリメント分の巻き戻しは RTE 時点まで行えない。
	// 一方、ポストインクリメントのケースはこの少し下の
	// ExceptionBuserr() 内のコメント参照。
	for (int i = 7; i >= 0; i--) {
		push_4(save_pd[i]);
	}
	push_2(flag_pd);
	reg.A[7] -= 2;

	reg.A[7] -= 2;					// VERSION#
	reg.A[7] -= 6;
	push_4(0);						// DATA INPUT BUFFER
	reg.A[7] -= 4;
	push_4(stageBaddr);				// STAGE B ADDRESS
	reg.A[7] -= 8;
	push_4(0);						// DATA OUTPUT BUFFER
	reg.A[7] -= 4;
	push_4(fault_addr);				// DATA CYCLE FAULT ADDRESS
	push_2(0);						// INSTRUCTION PIPE STAGE B
	push_2(0);						// INSTRUCTION PIPE STAGE C
	push_2(ssw);
	reg.A[7] -= 2;
	push_2(0xb000 | (vector << 2));
	// 本当は現在の PC だが、再実行のため PPC を積む。
	push_4(ppc);					// PC
	push_2(sr);						// 例外入り口で保存した SR
}

// 例外処理について。
//
// 0. リセット例外
//	リセット例外は外部から ExceptionReset() を呼び出す。
//
// 1. バスエラー例外
//	MMU による論理アドレスでのバスエラーは (Translate*() が false を返した後)
//	m68030bus.cpp ルーチンが (C++の) 例外をスローする。
//	物理アドレス空間アクセス後のバスエラーは (VM側の Read*()/Write*() が
//	BusErr を返した後) m68030bus.cpp ルーチンが (C++) の例外をスローする。
//	どちらのケースも m68030core.cpp のメインループで catch し、その catch
//	ブロック内で ExceptionBuserr() を呼び出す。
//
// 2. アドレスエラー例外
//	アドレスエラーは Jump() 内でのみ発生するはずで、その場で
//	ExceptionBuserr() を呼んで処理する。
//
// 3. 命令中で起きる例外
//	命令途中で起きる(確定的な)例外は Exception() を呼び出す。
//	これに該当するのは
//	  o 特権違反
//	  o A系列/F系列命令
//	  o TRAP* 系命令、CHK/CHK2 命令
//	  o トレース例外 (未実装)
//	  o ゼロ除算?
//	  o MMU 構成例外
//	  o 不当命令 (*)
//	である。
//	ただし、不当命令についてはどこで不当命令と判定されるかによって
//	フローが複数存在することに注意。
//	  o 1ワード目が不当命令
//		-> 命令がそれぞれ Exception() で処理。
//	  o 1ワード目の EA が不当命令
//		-> (C++の)例外をスローして catch 内で Exception() を実行。
//	  o 2ワード目が不当命令
//		-> 命令がそれぞれ Exception() で処理。
//
// 4. 割り込み例外
//	別コメント(下のほう)参照。
//

// リセット例外処理。
// 各レジスタを所定の状態にして、リセットベクタを読み込む。
// リセットベクタが読み込めなければ HALT 状態に移行。
// 戻り値は消費したサイクル数。
int
MPU680x0Device::ExceptionReset()
{
	uint64 cycle_start = used_cycle;

	// 例外履歴に記録 (例外発生はブランチ履歴にも記録)
	// m68k では今の所リセット例外発生時の PC にはあまり意味がないけど、
	// m88k と動作を揃えておく。
	const uint vector = 0;
	excep_counter[0]++;
	exhist.AddEntry(reg.pc | 1, 0, BranchHistory_m680x0::Exception | 0);
	brhist.AddEntry(reg.pc | 1, 0, BranchHistory_m680x0::Exception | 0);
	// デバッガに通知 (例外ブレーク用)。こっちのベクタは 0 のままでよい。
	debugger->NotifyExceptionMain(vector);

	// RESET 信号線をネゲートしてから SP の読み込み開始まで 4クロック
	// (Section 7.8, Figure 7-64)
	used_cycle += 4;

	// STOP 状態とホールト状態を解除。
	ChangeState(CPU_STATE_NORMAL);

	// 1. T0, T1 クリア
	// 2. S をセット、M をクリア
	// 3. 割り込みレベルを 7

	// ここは SetSR を使わずに設定する。
	reg.s = true;
	reg.m = false;
	fc_inst = BusAddr::S | BusAddr::Fetch;
	fc_data = BusAddr::S | BusAddr::D;
	// ここは割り込みレベル変化のチェックは不要でセットだけでいい。
	reg.intr_mask = 7;

	// 4. VBR をクリア
	reg.vbr = 0;

	// 5. キャッシュの EI|ED, FI|FD, IBE|DBE, WA をクリア
	// 6. キャッシュエントリをすべてクリア (CI|CD)
	SetCACR(M68K::CACR_CD | M68K::CACR_CI);
	cycle_nocache = 0;
	icache->hist.Clear();
	dcache->hist.Clear();

	// 7. TC:E、TTn:E をクリア
	SetTC(GetTC() & ~m68030TC::TC_E);
	SetTT(0, GetTT(0) & ~m68030TT::E);
	SetTT(1, GetTT(1) & ~m68030TT::E);
	// 8. リセットベクタ生成

	// 9. SP をロード
	// 10. PC をロード
	try {
		reg.pc = 0;
		reg.A[7] = fetch_4();
		uint32 pc = fetch_4();
		// 大丈夫のはずだけど fetch_4() が reg.pc を書き換えるので一応
		reg.pc = pc;
		// リセットベクタは常に 0, 4 番地から取得したことになっている
		uint32 inst = BranchHistory_m680x0::VectorJump;
		brhist.AddEntry(0x00000004 | 1, reg.pc, inst);
	} catch (uint) {
		DoubleBusFault("reset exception");
	}

	// SP のアドレスエラーはリセット例外ではチェックしないようだ。
	// PC のアドレスエラーは本来ならここにあるはずの3ワードのプリフェッチで
	// 行われるのだが、それがないので、とりあえずここで。
	if ((reg.pc & 1) != 0) {
		DoubleBusFault("reset exception");
	}

	return used_cycle - cycle_start;
}

// 汎用の例外処理を行う。vector はベクタ番号。
// ただし以下の例外はここではなくそれぞれ固有のを呼ぶこと。
// o リセット例外は ExceptionReset()
// o 割り込み例外は ExceptionInterrupt()
// o TRAP#15 例外は ExceptionTrap15()
// o バスエラー・アドレスエラーは ExceptionBuserr()
void
MPU680x0Device::Exception(uint vector)
{
	uint32 inst = BranchHistory_m680x0::Exception | vector;
	ExceptionGeneric(vector, inst);
}

// TRAP #15 の例外処理を行う。
// 処理自体は汎用の Exception() とまったく同じだが、
// IOCS コールの時には分岐履歴の出力が変わるため入口を分けてある。
void
MPU680x0Device::ExceptionTrap15()
{
	// 今発生した TRAP #15 が IOCS コールっぽいかどうか調べる。
	//
	// Human68k 方面をやってる時は TRAP #15 は IOCS コールで表示してほしい。
	// 一方、NetBSD のカーネル内では TRAP #15 は IOCS コールではない別用途
	// なので IOCS コールかのように表示されるのは紛らわしい。さらに言えば
	// NetBSD でもブートローダの時点はまだ IOCS コールを使うので、出来れば
	// 今呼ばれたこれが IOCS コールなのか、そうでない TRAP #15 なのかは
	// 区別したい。
	//
	// IOCS コールかどうかは厳密には分からないが、これが呼ばれた時点で
	// ワークエリアの $cbc == 0x03、$cbe == 0xff であれば IOCS コールと
	// 判定することにする。
	// $cbc は MPU 種別で 3:68030。
	// $cbe は MMU 有無で 0xff:有り。
	// 今の所 68030/MMU有り しかサポートしていないので。
	// だめならまた考える。

	const uint vector = M68K::EXCEP_TRAP15;
	uint32 inst;
	if (mainbus->Peek1(0xcbc) == 0x03 && mainbus->Peek1(0xcbe) == 0xff) {
		// IOCS コールっぽい
		inst = BranchHistory_m680x0::IOCSCall | (reg.D[0] & 0xff);
	} else {
		// TRAP#15 っぽい
		inst = BranchHistory_m680x0::Exception | vector;
	}
	ExceptionGeneric(vector, inst);
}

// 汎用の例外処理本体。
void
MPU680x0Device::ExceptionGeneric(uint vector, uint32 inst)
{
	// 例外履歴に記録 (例外発生はブランチ履歴にも記録)
	excep_counter[vector]++;
	uint32 from = ppc | (IsSuper() ? 1 : 0);
	exhist.AddEntry(from, 0, inst);
	brhist.AddEntry(from, 0, inst);
	// デバッガに通知 (例外ブレーク用)
	debugger->NotifyExceptionMain(vector);

	// 状態を保存
	uint16 sr = GetSR();

	// S を立てて T1,T0 を落とす
	SetSR((GetSR() | M68K::SR_S) & ~M68K::SR_T);

	try {
		switch (vector) {
		 case M68K::EXCEP_ILLEGAL:		// 不当命令
		 case M68K::EXCEP_PRIV:			// 特権違反例外
		 case M68K::EXCEP_ALINE:		// Aライン命令例外
		 case M68K::EXCEP_FLINE:		// Fライン命令例外
			// スタックに積む PC はこの命令のアドレス
			GenerateExframe0(vector, ppc, sr);
			break;

		 case M68K::EXCEP_TRAP0			// TRAP#0
		  ... M68K::EXCEP_TRAP15:		// TRAP#15
			// スタックに積む PC は次の命令
			GenerateExframe0(vector, reg.pc, sr);
			break;

		 case M68K::EXCEP_ZERODIV:		// ゼロ除算
		 case M68K::EXCEP_CHK:			// CHK
		 case M68K::EXCEP_TRAPV:		// TRAPV
		 case M68K::EXCEP_MMU_CONFIG:	// MMU 構成例外
			GenerateExframe2(vector, sr);
			break;

		 default:
			PANIC("$%08x exception vector %u ?", ppc, vector);
			break;
		}

		// ベクタにジャンプ
		JumpVector(vector, false);
	} catch (uint vector2) {
		assertmsg(vector2 == M68K::EXCEP_BUSERR, "vector=%u", vector);

		// バスエラー処理へ
		ExceptionBuserr(vector2);
	}
}

// バスエラー・アドレスエラー例外処理を行う。
// 他の例外とは違いが多いので分けた。
void
MPU680x0Device::ExceptionBuserr(uint vector)
{
	assertmsg((vector == M68K::EXCEP_BUSERR || vector == M68K::EXCEP_ADDRERR),
		"vector=%u", vector);

	// 例外履歴に記録 (例外発生はブランチ履歴にも記録)
	excep_counter[vector]++;
	uint32 from = ppc | (IsSuper() ? 1 : 0);
	uint32 inst = BranchHistory_m680x0::Exception | vector;
	exhist.AddEntry(from, 0, inst);
	brhist.AddEntry(from, 0, inst);
	// デバッガに通知 (例外ブレーク用)
	debugger->NotifyExceptionMain(vector);

	// 状態を保存
	uint16 sr = GetSR();

	// ここで (An)+ で変化したレジスタを命令実行前の状態に巻き戻す。
	// EA 処理の時点で先にインクリメントしているのだが、バスエラーが
	// 起きたということは実際にはポストインクリメントは発生しないので、
	// ここで無条件に巻き戻してよい。SR を書き換えると A7 が切り替わって
	// しまうので、この巻き戻しは SR を書き換える前のここで行うこと。
	// 一方、プリデクリメントのケースはこの少し上の ExframeB の中の
	// コメント参照。
	restore_reg_pi();

	// S を立てて T1,T0 を落とす
	SetSR((GetSR() | M68K::SR_S) & ~M68K::SR_T);

	try {
		// フレームを作成。
		// 実際はショート($A)、ロング($B)の2種類あって、本来ならショートが
		// 使えればショートを使うのだが、ここでは全部ロングを使う。
		GenerateExframeB(vector, sr);
		// ベクタにジャンプ
		JumpVector(vector, true);
	} catch (uint vector2) {
		// ここに来るのはバスエラーかアドレスエラーだけのはず
		assertmsg(vector2 == M68K::EXCEP_BUSERR ||
		          vector2 == M68K::EXCEP_ADDRERR, "vector=%u", vector);

		// バスエラー・アドレスエラー処理中のバスエラーは
		// ダブルバスフォールト。
		DoubleBusFault("bus/address error handling");
	}
}

// 割り込み例外処理を行う。
// 割り込みの受け付けとレベルのチェック等の後、次命令境界で呼ばれるやつ。
// この処理にかかる時間 [nsec] を返す。
uint64
MPU680x0Device::ExceptionInterrupt()
{
	uint64 cycle_start = used_cycle;
	uint level = intr_level;

	// ユーザーズマニュアル(日本語) p.399 図8-5 の中ほど
	// 「/IPEND をネゲート、割り込みアクノリッジサイクルを実行」
	// 以降に相当。

	// STOP なら解除
	ChangeState(CPU_STATE_NORMAL);

	// 割り込みアクノリッジサイクルを実行
	uint32 vector;
	busdata bd = interruptdev->InterruptAcknowledge(level);
	if (__predict_false(bd.IsBusErr())) {
		// 外部回路がバスエラーを返すと Spurious を発行する。(7.4.1.3)
		vector = M68K::EXCEP_SPURIOUS;
	} else {
		vector = bd.Data();
		if ((int32)vector < 0) {
			// オートベクタ要求 (7.4.1.2)
			// (負数(-1)ならオートベクタということにしている)
			vector = M68K::EXCEP_LEVEL_BASE + level;
		}
	}

	// 例外履歴に記録 (例外発生はブランチ履歴にも記録)
	excep_counter[vector]++;
	uint32 from = ppc | (IsSuper() ? 1 : 0);
	uint32 inst = BranchHistory_m680x0::Exception | vector;
	exhist.AddEntry(from, 0, inst);
	brhist.AddEntry(from, 0, inst);
	// デバッガに通知 (例外ブレーク用)
	debugger->NotifyExceptionMain(vector);

	// 状態を保存
	uint16 oldsr = GetSR();

	// S を立てて T1,T0 を落とす。割り込みレベルも変更。
	uint16 newsr = GetSR() & ~(M68K::SR_T | M68K::SR_IMASK);
	newsr |= M68K::SR_S;
	newsr |= level << 8;
	SetSR(newsr);

	try {
		// 例外スタックフレーム $0 を生成。
		// スタックに積む PC は次の命令位置。
		// M ビットは未実装なのでスローアウェイとか無視
		GenerateExframe0(vector, reg.pc, oldsr);

		// ベクタにジャンプ
		JumpVector(vector, false);
	} catch (uint vector2) {
		assertmsg(vector2 == M68K::EXCEP_BUSERR, "vector=%u", vector);

		// バスエラー処理へ
		ExceptionBuserr(vector2);
	}

	return (used_cycle - cycle_start) * clock_nsec + bd.GetWait();
}

// RTE 命令。
// 例外処理の近くに置いておくほうが便利なので。
void
MPU680x0Device::ops_rte()
{
	uint32 pc;
	uint16 sr;
	uint16 frame;

	try {
		sr = pop_2();
		pc = pop_4();
		frame = pop_2();
		switch (frame >> 12) {
		 case 0:
			CYCLE2(18, 20);
			SetSR(sr);
			Jump(pc);
			break;

		 case 2:
			CYCLE2(18, 20);
			// +08.L INSTRUCTION ADDRESS を読み捨てる
			reg.A[7] += 4;
			SetSR(sr);
			Jump(pc);
			break;

		 case 0x0b:
		 {
			uint32 stage_b;
			uint16 ssw;

			CYCLE(76);
			reg.A[7] += 2;						// $08.W
			ssw = pop_2();						// $0a.W SSW
			reg.A[7] += 4 * 6;					// $0c.L*6
			stage_b = pop_4();					// $24.L Stage B
			reg.A[7] += 2 * 9;					// $28.W*9
			// $3a..$58 ここが save_pd の取り出し
			flag_pd = pop_2();
			for (int i = 0; i < 8; i++) {
				save_pd[i] = pop_4();
			}
			// A7 レジスタをリストアするかもしれない前に Super/User を
			// 復帰しておかないといけない
			SetSR(sr);

			// DFビットセットなら命令を再実行するため、-(An) で変化
			// したレジスタを巻き戻す。
			// DFビットクリアなら例外要因の命令はハンドラで処理された
			// とみなして、次の命令に進む。
			if ((ssw & M68K::SSW_DF)) {
				restore_reg_pd();
			} else {
				pc = stage_b - 2;
			}
			Jump(pc);
			break;
		 }

		 default:
			PANIC("RTE frame=$%x not supported", frame >> 12);
			break;
		}
	} catch (uint vector2) {
		assertmsg(vector2 == M68K::EXCEP_BUSERR, "vector2=%u", vector2);

		// RTE 処理中のバスエラーはダブルバスフォールト。
		DoubleBusFault("bus/address error handling");
	}
}

// STOP 命令。
void
MPU680x0Device::ops_stop()
{
	uint32 data = fetch_2();
	SetSR(data);
	ChangeState(CPU_STATE_STOP);

	MakeNextFull();
}

unix.superglobalmegacorp.com

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