File:  [Isaki's NoNo m68k/m88k emulator] / nono / m680x0 / m680x0core.cpp
Revision 1.1.1.5 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:05:57 2026 UTC (3 months 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
//

#include "mpu680x0.h"
#include "bitops.h"
#include "debugger.h"
#include "event.h"
#include "interrupt.h"
#include "mainbus.h"
#include "scheduler.h"
#include "vectortable.h"

#define M680X0_CYCLE_TABLE
#include "m680x0cycle.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 "m680x0switch.h"
		}
	} 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 if (vector == M68K::EXCEP_ILLEGAL) {
			op_illegal();
		} else {
			Exception(vector);
		}
	}

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

	int cycle = used_cycle - cycle_start;
	ev->time = cycle * clock_tsec + 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 他に何か状態変えないといけないことあったっけか。
}

// 命令中に (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 from = vecaddr | (IsSuper() ? 1 : 0);
	uint32 info = BranchHistory_m680x0::InfoVectorJump();
	brhist.AddEntry(from, newpc, info);

	// 奇数番地ならアドレスエラー
	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 はベクタ番号。
// sr は例外発生時の SR。
// pc はスタックに積むプログラムカウンタの値 (例外によって、この命令位置か
// 次の命令位置かが異なる)。
inline void
MPU680x0Device::GenerateExframe0(uint vector, uint16 sr, uint32 pc)
{
	// 例外スタックフレーム $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 と addr の内容が異なるので呼び出す側で適切にセットすること。
//
// - CHK*、TRAP*、ゼロ除算:
//		PC   = 次の命令
//		Addr = 例外を起こした命令
// - 68040 のアドレスエラー:
//		PC   = アドレスエラーを起こした命令
//		Addr = 参照したアドレス - 1
inline void
MPU680x0Device::GenerateExframe2(uint vector, uint16 sr,
	uint32 pc, uint32 addr)
{
	// 例外スタックフレーム $2
	// SP+$00.W STATUS REGISTER
	//   +$02.L PROGRAM COUNTER		; こっちが pc
	//   +$06.W %0010 | VECTOR
	//   +$08.L ADDRESS				; こっちが addr

	push_4(addr);			// ADDRESS
	push_2((2 << 12) | (vector << 2));
	push_4(pc);				// PROGRAM COUNTER
	push_2(sr);
}

// 例外スタックフレーム $4 を生成する。(68LC040 only)
inline void
MPU680x0Device::GenerateExframe4(uint vector, uint16 sr, uint32 ea)
{
	// 公式の図 (のオフセット) が雑すぎる。
	//
	//  SP->| STATUS REGISTER
	// +$02 | PROGRAM COUNTER (H)			; こっちは次の命令
	// +$04 |                 (L)
	// +$06 | %0100 | VECTOR OFFSET
	// +$08 | EFFECTIVE ADDRESS (H)
	// +$0a |                   (L)
	// +$0c | PC OF FAULTED INSTRUCTION (H)
	// +$0e |                           (L)

	push_4(ppc);
	push_4(ea);
	push_2((4 << 12) | (vector << 2));
	push_4(reg.pc);
	push_2(sr);
}

// 例外スタックフレーム $7 を生成する。(68040 only)
inline void
MPU680x0Device::GenerateExframe7(uint vector, uint16 sr)
{
	uint16 ssw;
	uint32 pc;
	uint32 ea = 0;
	uint16 wb1s = 0;
	uint16 wb2s = 0;
	uint32 wb1a = 0;
	uint32 wb2a = 0;
	uint32 wb1d = 0;
	uint32 wb2d = 0;

	// 再実行について。
	//
	// MOVEM の途中でバスエラーが起きた場合、
	// 本来は RTE が SSW の CM ビットを見て途中の状態から処理を再開した上で
	// 次の命令に戻る、らしいが、
	// ここでは再アクセスに副作用はないものとして、CM ビットは立てず、RTE でも
	// 何もせずに MOVEM 自体を再実行する。
	//
	// それ以外の命令でバスエラーが起きた場合も、単に命令を再実行する。
	// そのため -(An), (An)+ は例外発生時に常に巻き戻してある。

	// 例外スタックフレーム $7
	// SP+$00.W   STATUS REGISTER
	//   +$02.L   PROGRAM COUNTER
	//   +$06.W   %0111 | VECTOR
	//   +$08.L   EFFECTIVE ADDRESS (EA)
	//   +$0c.W   SPECIAL STATUS REGISTER (SSW)
	//   +$0e.W   $00 | WRITE-BACK 3 STATUS (WB3S)
	//   +$10.W   $00 | WRITE-BACK 2 STATUS (WB2S)
	//   +$12.W   $00 | WRITE-BACK 1 STATUS (WB1S)
	//   +$14.L   FAULT ADDRESS (FA)
	//   +$18.L   WRITE-BACK 3 ADDRESS (WB3A)
	//   +$1c.L   WRITE-BACK 3 DATA    (WB3D)
	//   +$20.L   WRITE-BACK 2 ADDRESS (WB2A)
	//   +$24.L   WRITE-BACK 2 DATA    (WB2D)
	//   +$28.L   WRITE-BACK 1 ADDRESS (WB1A)
	//   +$2c.L   WRITE-BACK 1 DATA / PUSH DATA LW0 (WB1D/PD0)
	//   +$30.L   PUSH DATA LW1 (PD1)
	//   +$34.L   PUSH DATA LW2 (PD2)
	//   +$38.L   PUSH DATA LW3 (PD3)
	//   +$3c
	//	こっちから上に向かって PUSH

	// SSW
	//  15  14  13  12  11  10   9   8   7   6   5   4   3   2   1   0
	// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
	// |CP |CU |CT |CM |MA |ATC|LK |RW | - | SIZE  |  TT   |    TM     |
	// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

	// この後のスタック操作で bus が変化する前に保存。
	const busaddr fault = bus.laddr;

	ssw  = fault.GetFC();				// TM(=FC)
	ssw |= (fault.GetSize() & 3) << 5;	// Size の下位2ビット
	if (fault.IsRead()) {
		ssw |= M68040::SSW_RW;
	}
	if (bus.atcfault) {
		ssw |= M68040::SSW_ATC;
		bus.atcfault = false;

		if (bus.atcfault2) {
			ssw |= M68040::SSW_MA;
			bus.atcfault2 = false;
		}
	}
	// 再実行のため、例外を発生した命令に戻る。
	pc = ppc;

	push_4(0);		// PD3
	push_4(0);		// PD2
	push_4(0);		// PD1
	push_4(wb1d);	// WB1D
	push_4(wb1a);	// WB1A
	push_4(wb2d);	// WB2D
	push_4(wb2a);	// WB2A
	push_4(0);		// WB3D
	push_4(0);		// WB3A
	push_4(fault.Addr());
	push_2(wb1s);	// WB1S
	push_2(wb2s);	// WB2S
	push_2(0);		// WB3S
	push_2(ssw);
	push_4(ea);
	push_2(0x7000 | (vector << 2));
	push_4(pc);
	push_2(sr);
}

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

	stageBaddr = reg.pc + 2;

	// この後のスタック操作で bus が変化する前に保存
	const busaddr fault = bus.laddr;

	// SSW
	//  15  14  13  12  11  10   9   8   7   6   5   4   3   2   1   0
	// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
	// |FC |FB |RC |RB | X | X | X |DF |RM |RW | SIZE  | X |    FC     |
	// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

	ssw  = (fault.Get() >> 33) & 0x47;	// R/W+FC
	ssw |= (fault.GetSize() & 3) << 4;	// Size の下位2ビット
	ssw |= M68030::SSW_DF;				// DF は常に立てる必要がある??
	if (mmu_enable && (ssw & M68K::FC_DATA) != 0) {
		// 命令アクセスなら RB をセット。
		ssw |= M68030::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]++;
	constexpr uint32 info = BranchHistory_m680x0::InfoException(vector);
	exhist.AddEntry(reg.pc | 1, 0, info);
	brhist.AddEntry(reg.pc | 1, 0, info);
	last_vector = 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)
	ResetCache();

	// キャッシュ状態が変わったので、サイクル表も更新。
	if (GetMPUType() == m680x0MPUType::M68030) {
		cycle_table = cycle_table_030ncc;
	}

	// 7. TC:E、TTn:E をクリア
	ResetMMU();

	// ところでたぶん FPU もリセットされるはず?
	ResetFPU(true);

	// 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 from = 0x00000004 | 1;
		brhist.AddEntry(from, reg.pc, BranchHistory_m680x0::InfoVectorJump());
	} 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#0  例外は ExceptionTrap0()  .. NetBSD システムコール用
// o TRAP#15 例外は ExceptionTrap15() .. IOCS コール用
// o バスエラー・アドレスエラーは ExceptionBuserr()
void
MPU680x0Device::Exception(uint vector)
{
	uint32 info = BranchHistory_m680x0::InfoException(vector);
	ExceptionGeneric(vector, info);
}

// TRAP #0 の例外処理を行う。
// 処理自体は汎用の Exception() とまったく同じだが、
// NetBSD システムコールの時に分岐履歴の内容を変えるため入口を分けてある。
void
MPU680x0Device::ExceptionTrap0()
{
	// 今発生した TRAP #0 が NetBSD/m68k システムコールっぽいか調べる。
	//
	// NetBSD/m68k 方面をやってる時は TRAP #0 はシステムコールを表示して
	// ほしい。一方 Human68k などでは TRAP #0 は別用途なので、システムコール
	// かのように表示されるのは紛らわしいので、
	// 出来れば今呼ばれたこれが NetBSD/m68k システムコールなのか、そうでない
	// TRAP #0 なのかは区別したい。
	//
	// NetBSD/m68k カーネルが実行中かどうかは厳密には分からないが、これが
	// 呼ばれた時点で、
	// 1) MMU がオン。
	// 2) カーネルのエントリポイントの最初の命令が move.w #$2700,sr 命令。
	// を満たせば NetBSD/m68k システムコールと判定することにする。
	// カーネルエントリポイントは機種ごとに固有で、不変ではないものの
	// ブートローダ側にも影響があるためおそらくそう簡単には変わらないだろう。
	// だめならまた考える。

	const uint vector = M68K::EXCEP_TRAP0;
	uint32 info;
	if (__predict_true(mmu_enable)) {
		// カーネルエントリポイントの最初の命令はどれも move.w #$2700,sr。
		if (mainbus->Peek4(netbsd_entrypoint) == 0x46fc2700) {
			// NetBSD/m68k カーネルっぽい。
			info = BranchHistory_m680x0::InfoNetBSDSyscall(reg.D[0] & 0xffff);
			ExceptionGeneric(vector, info);
			return;
		}
	}
	// その他の TRAP#0 っぽい
	Exception(vector);
}

// 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 コールかどうかは厳密には分からないが、これが呼ばれた時点で
	// ワークエリアの $cb6 == 0x02、$cb7 == 0x03 であれば IOCS コールと
	// 判定することにする。
	// $cb6、$cb7 はシステムポートの機種とクロック数 (を加工したもの)
	// なので、本体が X68030 であれば常に 0x02、0x03 になるはず。
	// だめならまた考える。

	const uint vector = M68K::EXCEP_TRAP15;
	uint32 info;
	if (mainbus->Peek1(0xcb6) == 0x02 && mainbus->Peek1(0xcb7) == 0x03) {
		// IOCS コールっぽい
		info = BranchHistory_m680x0::InfoIOCSCall(reg.D[0] & 0xff);
		ExceptionGeneric(vector, info);
		return;
	}

	// その他の TRAP#15 っぽい
	Exception(vector);
}

// F ライン例外処理を行う。
// 処理自体は Exception() とまったく同じだが、
// DOS コールの時に分岐履歴の内容を変えるため入口を分けてある。
void
MPU680x0Device::ExceptionFLine()
{
	// 今発生した F ライン例外が DOS コールっぽいかどうか調べる。
	// といいつつ今のところ IOCS ワークで判定している。
	// DOS コールは本来 Human68k が起動しているかどうかなので、
	// IOCS ワークがあるかどうかとは違うが、とりあえず。
	// このすぐ上の ExceptionTrap15() のコメントも参照。

	const uint vector = M68K::EXCEP_FLINE;
	uint32 info;
	if ((ir & 0xff00) == 0xff00 &&
		mainbus->Peek1(0xcb6) == 0x02 && mainbus->Peek1(0xcb7) == 0x03)
	{
		// DOS コールっぽい。
		info = BranchHistory_m680x0::InfoDOSCall(ir);
	} else {
		// F ライン例外っぽい。
		info = BranchHistory_m680x0::InfoException(vector);
	}
	ExceptionGeneric(vector, info);
}

// 汎用の例外処理本体。
// ext_vector は下位8ビットがベクタ番号、
// 8ビット目が %1 なら浮動小数点未実装命令例外 (vector は F ライン)。
// info は履歴に記録するやつ。
void
MPU680x0Device::ExceptionGeneric(uint ext_vector, uint32 info)
{
	uint vector = ext_vector & 0xff;

	// 例外履歴に記録 (例外発生はブランチ履歴にも記録)
	last_vector = vector;
	excep_counter[vector]++;
	uint32 from = ppc | (IsSuper() ? 1 : 0);
	exhist.AddEntry(from, 0, info);
	brhist.AddEntry(from, 0, info);

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

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

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

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

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

		 case M68K::EXCEP_FP_UNIMPL:	// 68040 の浮動小数点未実装命令例外
		 case M68K::EXCEP_FP_UNSUPP:	// 68040 の FPU 未実装データ型例外
			// スタックに積む PC は次の命令、ADDRESS は計算した EA。
			GenerateExframe2(vector, sr, reg.pc, fpu40.ea);
			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);

	// 例外履歴に記録 (例外発生はブランチ履歴にも記録)
	last_vector = vector;
	excep_counter[vector]++;
	uint32 from = ppc | (IsSuper() ? 1 : 0);
	uint32 info = BranchHistory_m680x0::InfoException(vector);
	exhist.AddEntry(from, 0, info);
	brhist.AddEntry(from, 0, info);

	// 現状 68030 のバスエラーに short/long の区別はないので全部 long 相当。
	CYCLE3(busfault);

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

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

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

	try {
		// フレームを作成。
		if (mpu_type == m680x0MPUType::M68030) {
			// 実際はショート($A)、ロング($B)の2種類あって、本来ならショートが
			// 使えればショートを使うのだが、ここでは全部ロングを使う。
			GenerateExframeB(vector, sr);
		} else {
			// 68040 ではバスエラーは $7、アドレスエラーは $2 を使う。
			if (__predict_true(vector == M68K::EXCEP_BUSERR)) {
				GenerateExframe7(vector, sr);
			} else {
				uint32 fault_addr = bus.laddr.Addr();
				GenerateExframe2(vector, sr, ppc, fault_addr - 1);
			}
		}
		// ベクタにジャンプ
		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;
		}
	}

	// 例外履歴に記録 (例外発生はブランチ履歴にも記録)
	last_vector = vector;
	excep_counter[vector]++;
	uint32 from = ppc | (IsSuper() ? 1 : 0);
	uint32 info = BranchHistory_m680x0::InfoException(vector);
	exhist.AddEntry(from, 0, info);
	brhist.AddEntry(from, 0, info);

	// 状態を保存
	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, oldsr, reg.pc);

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

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

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

// LC040 の未実装浮動小数点命令例外。
void
MPU680x0Device::ExceptionFPLC(uint32 ea)
{
	const uint vector = M68K::EXCEP_FLINE;

	// 例外履歴に記録 (例外発生はブランチ履歴にも記録)
	last_vector = vector;
	excep_counter[vector]++;
	uint32 from = ppc | (IsSuper() ? 1 : 0);
	constexpr uint32 info = BranchHistory_m680x0::InfoException(vector);
	exhist.AddEntry(from, 0, info);
	brhist.AddEntry(from, 0, info);

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

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

	try {
		GenerateExframe4(vector, sr, ea);

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

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

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

	try {
		sr = pop_2();
		pc = pop_4();
		frame = pop_2();
		type = frame >> 12;
		switch (type) {
		 case 0:
			CYCLE3(rte_frame0);
			SetSR(sr);
			Jump(pc);
			break;

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

		 case 0x04:
		 {
			// 68LC040 only
			if (GetFPUType().Is4060LC() == false) {
				Exception(M68K::EXCEP_FORMAT);
				return;
			}
			CYCLE(15);
			reg.A[7] += 8;
			SetSR(sr);
			Jump(pc);
			break;
		 }

		 case 0x07:
		 {
			// 68040 only
			if (mpu_type != m680x0MPUType::M68040) {
				Exception(M68K::EXCEP_FORMAT);
				return;
			}
			CYCLE(23);
			uint16 ssw;
			reg.A[7] += 4;
			ssw = pop_2();
			putlog(2, "RTE (Frame7): SSW=$%04x", ssw);
			reg.A[7] += 46;
			SetSR(sr);
			Jump(pc);
			break;
		 }

		 case 0x0b:
		 {
			// 68030 only
			if (mpu_type != m680x0MPUType::M68030) {
				Exception(M68K::EXCEP_FORMAT);
				return;
			}

			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 & M68030::SSW_DF)) {
				restore_reg_pd();
			} else {
				pc = stage_b - 2;
			}
			Jump(pc);
			break;
		 }

		 default:
			if (mpu_type == m680x0MPUType::M68030) {
				switch (type) {
				 case 0x01:
				 case 0x08:
				 case 0x09:
				 case 0x0a:
					// CPU は本来対応しているが、
					// エミュレータが対応していない (遭遇しないはず)。
					VMPANIC("RTE frame=$%x not supported", type);
				 default:
					break;
				}
			} else /* 68040 */ {
				switch (type) {
				 case 0x01:
				 case 0x03:
					// CPU は本来対応しているが、
					// エミュレータが対応していない (遭遇しないはず)。
					VMPANIC("RTE frame=$%x not supported", type);
				 default:
					break;
				}
			}
			// CPU が対応していないフレームタイプはフォーマットエラー例外。
			Exception(M68K::EXCEP_FORMAT);
			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.