File:  [Isaki's NoNo m68k/m88k emulator] / nono / m680x0 / m68030fpu.cpp
Revision 1.1.1.6 (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"

// リセット用のレジスタ初期値
/*static*/ struct fpframe
MPU680x0Device::initial_fpframe;

// 初期化
void
MPU680x0Device::fpu_init()
{
	int i;
	uint32_t *p;

	// XXX cpu の初期化のところと見直してマージするとか
	memset(&fe, 0, sizeof(fe));
	memset(&reg.fpframe, 0, sizeof(reg.fpframe));
	fe.fe_fpframe = &reg.fpframe;

	// FRESTORE によるリセット用に FP レジスタの初期値を用意しておく
	memset(&initial_fpframe, 0, sizeof(initial_fpframe));
	p = initial_fpframe.fpf_regs;
	for (i = 0; i < 8; i++) {
		*p++ = 0x7fff0000;
		*p++ = 0xffffffff;
		*p++ = 0xffffffff;
	}
}

// FPU をリセットする。
// MPU リセット時と、NULL フレームのリストア時に呼ばれる。
void
MPU680x0Device::fpu_reset()
{
	// 高速化のため、あらかじめ用意しておいたイメージをコピーする
	memcpy(&reg.fpframe, &initial_fpframe, sizeof(initial_fpframe));

	fpu_dirty = false;
}

// FPU からの呼び出し用。
//
// (An)+, -(An) は、サイズが増えているのとクロックが増加している(2->6)。
// それ以外のアドレッシングモードでもクロック数は微妙に違うのだが、
// 大きくは違わないのでそっちは本体の cea と同じということにする。
//
// このルーチンは Dn、An、#imm には対応していないので(自明)、呼び出し側で
// 事前に弾くこと。いずれも消費クロック数は 0 なので問題ない。
// またそれ以外のすべてのアドレッシングモードで反応するため、命令ごとに
// 対応していないアドレッシングモードは事前に弾くこと。
inline uint32
MPU680x0Device::cea_fpu(int bytes)
{
	int n;

	switch (eamode(ir)) {
	 case 0:	// Dn
	 case 1:	// An
		break;
	 case 2:	// (An)
		return cea_anin(eanum(ir));
	 case 3:	// (An)+
		CYCLE(6);
		n = eanum(ir);
		save_reg_pi(n);
		if (bytes == 1) {
			return internal_ea_anpi_1(n);
		} else {
			// _anpi_{2,4} のマクロは用意されてるけど、これと同じなので
			reg.A[n] += bytes;
			return reg.A[n] - bytes;
		}
		break;
	 case 4:	// -(An)
		CYCLE(6);
		n = eanum(ir);
		save_reg_pd(n);
		if (bytes == 1) {
			return internal_ea_anpd_1(n);
		} else {
			// _anpd_{2,4} のマクロは用意されてるけど、これと同じなので
			reg.A[n] -= bytes;
			return reg.A[n];
		}
		break;
	 case 5:	// d16(An)
		return cea_andi(eanum(ir));
	 case 6:	// (An,IX)
		return cea_anix(eanum(ir));
	 case 7:
		switch (eanum(ir)) {
		 case 0:	// Abs.W
			return cea_absw();
		 case 1:	// Abs.L
			return cea_absl();
		 case 2:	// d16(PC)
			return cea_pcdi();
		 case 3:	// (PC,IX)
			return cea_pcix();
		 case 4:	// #imm
			break;
		 default:
			break;
		}
		break;
	}
	// 不当命令
	throw M68K::EXCEP_ILLEGAL;
}

// メモリから 64ビット値を読み込む
void
MPU680x0Device::read_8(uint32 addr, uint32 *data)
{
	data[0] = read_4(addr);
	data[1] = read_4(addr + 4);
}

// メモリから 96ビット値を読み込む
void
MPU680x0Device::read_12(uint32 addr, uint32 *data)
{
	data[0] = read_4(addr);
	data[1] = read_4(addr + 4);
	data[2] = read_4(addr + 8);
}

// 64ビット値(.D)をメモリに書き込む
void
MPU680x0Device::write_8(uint32 addr, const uint32 *data)
{
	write_4(addr,     data[0]);
	write_4(addr + 4, data[1]);
}

// 96ビット値(.D)をメモリに書き込む
void
MPU680x0Device::write_12(uint32 addr, const uint32 *data)
{
	write_4(addr,     data[0]);
	write_4(addr + 4, data[1]);
	write_4(addr + 8, data[2]);
}

// ソースを読み込む。
// 不当命令パターンなら false を返す。
// size_id は SIZE_*。
bool
MPU680x0Device::readSRC(int size_id, struct fpn *fpn)
{
	uint mr = ir & 0x3f;
	uint32 ea;
	uint32 data[3];

	// 68881 本(p6-13) にはクロック数はこう書いてあるので
	// Inst | src=FPn	| memory source or dst operand format
	//      |			| Int	Sgl		Dbl		Ext		Pakced
	// ---- +----------	+------	-------	-------	-------	-------
	// FABS | 35		| 62	54		60		58		872
	//            (差分)  +27	+19		+25		+23		+837
	// FADD | 51		| 80	72		78		76		888
	//            (差分)  +29	+21		+27		+25		+837
	//
	// FABS.X FPn,FPm は 35 としてこれは fgen() 内で加算。
	// FABS.L <ea>,FPm は 62 クロックなので、ここでは差分の 27 を加算。
	// FABS.L Dn,FPm  の場合 src がレジスタなので 62 から 5 引く。
	// FABS.P <ea>,FPm は 872 クロックなので、ここでは差分の  837 を加算。
	//
	// XXX 多くの命令が FABS と同じだけの差分だが、
	// FADD などいくつかは .P 以外に +2 クロックの追加があるようだ。
	// どうしたもんか。

	if (mr < 8) {	// Dn
		switch (size_id) {
		 case SIZE_B:
			data[0] = (int32)(int8)reg.D[mr];
			break;
		 case SIZE_W:
			data[0] = (int32)(int16)reg.D[mr];
			break;
		 case SIZE_L:
		 case SIZE_S:
			data[0] = reg.D[mr];
			break;
		 default:
			return false;
		}
		// ソースが Dn の場合は 5クロック減
		CYCLE(-5);
	} else if (mr == 0x3c) {		// #imm
		switch (size_id) {
		 case SIZE_B:
			data[0] = fetch_2() & 0xff;
			break;
		 case SIZE_W:
			data[0] = fetch_2();
			break;
		 case SIZE_L:
		 case SIZE_S:
			data[0] = fetch_4();
			break;
		 case SIZE_D:
			data[0] = fetch_4();
			data[1] = fetch_4();
			break;
		 case SIZE_X:
		 case SIZE_P:
			data[0] = fetch_4();
			data[1] = fetch_4();
			data[2] = fetch_4();
			break;
		 default:
			return false;
		}
	} else {
		// それ以外は EA を求めてからメモリから読み込む
		switch (size_id) {
		 case SIZE_B:
			ea = cea_fpu(1);
			data[0] = (int32)(int8)read_1(ea);
			break;
		 case SIZE_W:
			ea = cea_fpu(2);
			data[0] = (int32)(int16)read_2(ea);
			break;
		 case SIZE_L:
		 case SIZE_S:
			ea = cea_fpu(4);
			data[0] = read_4(ea);
			break;
		 case SIZE_D:
			ea = cea_fpu(8);
			read_8(ea, data);
			break;
		 case SIZE_X:
		 case SIZE_P:
			ea = cea_fpu(12);
			read_12(ea, data);
			break;
		 default:
			return false;
		}
	}

	// 型別に fpn に変換
	switch (size_id) {
	 case SIZE_B:
	 case SIZE_W:
	 case SIZE_L:
		CYCLE(+27);
		fpu_explode(&fe, fpn, FTYPE_LNG, data);
		break;
	 case SIZE_S:
		CYCLE(+19);
		fpu_explode(&fe, fpn, FTYPE_SNG, data);
		break;
	 case SIZE_D:
		CYCLE(+25);
		fpu_explode(&fe, fpn, FTYPE_DBL, data);
		break;
	 case SIZE_X:
		CYCLE(+23);
		fpu_explode(&fe, fpn, FTYPE_EXT, data);
		break;
	 case SIZE_P:
		CYCLE(+837);
		fpu_explode(&fe, fpn, FTYPE_BCD, data);
		break;
	}

	return true;
}

// FPU 不当命令 (1ワード目で確定)
void
MPU680x0Device::fpu_op_illg()
{
	putlog(1, "FPU illegal instruction %04X", ir);
	Exception(M68K::EXCEP_FLINE);
}

// FPU 不当命令 (2ワード目で確定)
void
MPU680x0Device::fpu_op_illg2()
{
	putlog(1, "FPU illegal instruction %04X %04X", ir, ir2);
	Exception(M68K::EXCEP_FLINE);
}

// R/M=0 (レジスタ-レジスタ演算)
void
MPU680x0Device::fpu_op_fgen_reg()
{
	uint srcnum = (ir2 >> 10) & 7;
	uint dstnum = (ir2 >> 7) & 7;

	fpu_explode(&fe, &fe.fe_f2, FTYPE_EXT, &RegFP(srcnum * 3));
	fpu_explode(&fe, &fe.fe_f1, FTYPE_EXT, &RegFP(dstnum * 3));
	fgen();
}

// R/M=1 (メモリ-レジスタ演算)
void
MPU680x0Device::fpu_op_fgen_mem()
{
	uint size_id = (ir2 >> 10) & 7;
	uint dstnum = (ir2 >> 7) & 7;

	// source specifier はオペランドサイズ
	if (size_id == 7) {
		// こんなところに FMOVECR
		fpu_op_fmovecr();
		return;
	}

	// 第1ワードの EA がソース EA
	if (!readSRC(size_id, &fe.fe_f2)) {
		fpu_op_illg2();
		return;
	}
	fpu_explode(&fe, &fe.fe_f1, FTYPE_EXT, &RegFP(dstnum * 3));
	fgen();
}

// 一般算術演算
void
MPU680x0Device::fgen()
{
	uint opmode = ir2 & 0x3f;
	uint dstnum = (ir2 >> 7) & 7;
	bool discard_result = false;
	struct fpn *res = NULL;	// shutup gcc

	switch (opmode) {
	 case 0x00:		// FMOVE
		CYCLE(33);
		res = &fe.fe_f2;
		fpu_round_prec(&fe, res);
		break;
	 case 0x01:		// FINT
		CYCLE(55);
		res = fpu_int(&fe);
		break;
	 case 0x02:		// FSINH
		CYCLE(687);
		res = fpu_sinh(&fe);
		fpu_round_prec(&fe, res);
		break;
	 case 0x03:		// FINTRZ
		CYCLE(55);
		res = fpu_intrz(&fe);
		break;
	 case 0x04:		// FSQRT
		CYCLE(107);
		res = fpu_sqrt(&fe);
		fpu_round_prec(&fe, res);
		break;
	 case 0x06:		// FLOGNP1
		CYCLE(571);
		res = fpu_lognp1(&fe);
		fpu_round_prec(&fe, res);
		break;
	 case 0x08:		// FETOXM1
		CYCLE(545);
		res = fpu_etoxm1(&fe);
		fpu_round_prec(&fe, res);
		break;
	 case 0x09:		// FTANH
		CYCLE(661);
		res = fpu_tanh(&fe);
		fpu_round_prec(&fe, res);
		break;
	 case 0x0a:		// FATAN
		CYCLE(403);
		res = fpu_atan(&fe);
		fpu_round_prec(&fe, res);
		break;
	 case 0x0c:		// FASIN
		CYCLE(581);
		res = fpu_asin(&fe);
		fpu_round_prec(&fe, res);
		break;
	 case 0x0d:		// FATANH
		CYCLE(693);
		res = fpu_atanh(&fe);
		fpu_round_prec(&fe, res);
		break;
	 case 0x0e:		// FSIN
		CYCLE(391);
		res = fpu_sin(&fe);
		fpu_round_prec(&fe, res);
		break;
	 case 0x0f:		// FTAN
		CYCLE(473);
		res = fpu_tan(&fe);
		fpu_round_prec(&fe, res);
		break;
	 case 0x10:		// FETOX
		CYCLE(497);
		res = fpu_etox(&fe);
		fpu_round_prec(&fe, res);
		break;
	 case 0x11:		// FTWOTOX
		CYCLE(567);
		res = fpu_twotox(&fe);
		fpu_round_prec(&fe, res);
		break;
	 case 0x12:		// FTENTOX
		CYCLE(567);
		res = fpu_tentox(&fe);
		fpu_round_prec(&fe, res);
		break;
	 case 0x14:		// FLOGN
		CYCLE(525);
		res = fpu_logn(&fe);
		fpu_round_prec(&fe, res);
		break;
	 case 0x15:		// FLOG10
		CYCLE(581);
		res = fpu_log10(&fe);
		fpu_round_prec(&fe, res);
		break;
	 case 0x16:		// FLOG2
		CYCLE(581);
		res = fpu_log2(&fe);
		fpu_round_prec(&fe, res);
		break;
	 case 0x18:		// FABS
		CYCLE(35);
		fe.fe_f2.fp_sign = 0;
		res = &fe.fe_f2;
		fpu_round_prec(&fe, res);
		break;
	 case 0x19:		// FCOSH
		CYCLE(607);
		res = fpu_cosh(&fe);
		fpu_round_prec(&fe, res);
		break;
	 case 0x1a:		// FNEG
		CYCLE(35);
		res = &fe.fe_f2;
		if (!ISNAN(res)) {
			res->fp_sign = !res->fp_sign;
			fpu_round_prec(&fe, res);
		}
		break;
	 case 0x1c:		// FACOS
		CYCLE(625);
		res = fpu_acos(&fe);
		fpu_round_prec(&fe, res);
		break;
	 case 0x1d:		// FCOS
		CYCLE(391);
		res = fpu_cos(&fe);
		fpu_round_prec(&fe, res);
		break;
	 case 0x1e:		// FGETEXP
		CYCLE(45);
		res = fpu_getexp(&fe);
		fpu_round_prec(&fe, res);
		break;
	 case 0x1f:		// FGETMAN
		CYCLE(31);
		res = fpu_getman(&fe);
		fpu_round_prec(&fe, res);
		break;
	 case 0x20:		// FDIV
		CYCLE(103);
		res = fpu_div(&fe);
		fpu_round_prec(&fe, res);
		break;
	 case 0x21:		// FMOD
		CYCLE(67);
		res = fpu_mod(&fe);
		fpu_round_prec(&fe, res);
		break;
	 case 0x22:		// FADD
		CYCLE(51);
		res = fpu_add(&fe);
		fpu_round_prec(&fe, res);
		break;
	 case 0x23:		// FMUL
		CYCLE(71);
		res = fpu_mul(&fe);
		fpu_round_prec(&fe, res);
		break;
	 case 0x24:		// FSGLDIV
		CYCLE(69);
		res = fpu_sgldiv(&fe);
		break;
	 case 0x25:		// FREM
		CYCLE(67);
		res = fpu_rem(&fe);
		fpu_round_prec(&fe, res);
		break;
	 case 0x26:		// FSCALE
		// こいつだけ形式が違う。
		// 結果は dst に直接格納済み、FPSR も演算済み。
		CYCLE(41);
		fpu_emul_fscale(&fe, ir2);
		return;
	 case 0x27:		// FSGLMUL
		CYCLE(59);
		res = fpu_sglmul(&fe);
		break;
	 case 0x28:		// FSUB
		CYCLE(51);
		res = fpu_sub(&fe);
		fpu_round_prec(&fe, res);
		break;
	 case 0x30:
	 case 0x31:
	 case 0x32:
	 case 0x33:
	 case 0x34:
	 case 0x35:
	 case 0x36:
	 case 0x37:		// FSINCOS
		CYCLE(451);
		res = fpu_sincos(&fe, opmode & 7);
		fpu_round_prec(&fe, res);
		break;
	 case 0x38:		// FCMP
		CYCLE(33);
		res = fpu_cmp(&fe);
		discard_result = 1;
		break;
	 case 0x3a:		// FTST
		CYCLE(33);
		res = &fe.fe_f2;
		discard_result = 1;
		break;

	 default:
		// 不当命令
		fpu_op_illg2();
		return;
	}

	assert(res);
	if (!discard_result) {
		fpu_implode(&fe, res, FTYPE_EXT, &RegFP(dstnum * 3));
	}
	fpu_upd_fpsr(&fe, res);
	fpu_upd_excp(&fe);
}

// FMOVECR
void
MPU680x0Device::fpu_op_fmovecr()
{
	CYCLE(29);

	uint offset = (ir2 & 0x7f);
	uint dstnum = (ir2 >> 7) & 7;

	fpu_const(&fe.fe_f3, offset);
	fpu_implode(&fe, &fe.fe_f3, FTYPE_EXT, &RegFP(dstnum * 3));
	fpu_upd_fpsr(&fe, &fe.fe_f3);
	fpu_upd_excp(&fe);
}

// FMOVE.B FPn,<ea>
// cf. 68000PRM.pdf p378 (5-76)
void
MPU680x0Device::fpu_op_fmove_b_mem()
{
	uint mr = ir & 0x3f;
	uint srcnum = (ir2 >> 7) & 7;
	uint32 ea;
	uint32 data;

	fpu_explode(&fe, &fe.fe_f3, FTYPE_EXT, &RegFP(srcnum * 3));
	fpu_implode(&fe, &fe.fe_f3, FTYPE_LNG, &data);
	if ((int32)data < INT8_MIN || (int32)data > INT8_MAX) {
		RegFPSR |= FPSR_OPERR | FPSR_AIOP;
	}

	if (mr < 8) {	// Dn
		CYCLE(100-2);
		reg.D[mr] &= 0xffffff00;
		reg.D[mr] |= (uint8)data;
	} else {
		CYCLE(100);
		ea = cea_fpu(1);
		write_1(ea, (uint8)data);
	}
}

// FMOVE.W FPn,<ea>
// cf. 68000PRM.pdf p378 (5-76)
void
MPU680x0Device::fpu_op_fmove_w_mem()
{
	uint mr = ir & 0x3f;
	uint srcnum = (ir2 >> 7) & 7;
	uint32 ea;
	uint32 data;

	fpu_explode(&fe, &fe.fe_f3, FTYPE_EXT, &RegFP(srcnum * 3));
	fpu_implode(&fe, &fe.fe_f3, FTYPE_LNG, &data);
	if ((int32)data < INT16_MIN || (int32)data > INT16_MAX) {
		RegFPSR |= FPSR_OPERR | FPSR_AIOP;
	}

	if (mr < 8) {	// Dn
		CYCLE(100-2);
		reg.D[mr] &= 0xffff0000;
		reg.D[mr] |= (uint16)data;
	} else {
		CYCLE(100);
		ea = cea_fpu(2);
		write_2(ea, (uint16)data);
	}
}

// FMOVE.L FPn,<ea>
// cf. 68000PRM.pdf p378 (5-76)
void
MPU680x0Device::fpu_op_fmove_l_mem()
{
	uint mr = ir & 0x3f;
	uint srcnum = (ir2 >> 7) & 7;
	uint32 ea;
	uint32 data;

	fpu_explode(&fe, &fe.fe_f3, FTYPE_EXT, &RegFP(srcnum * 3));
	fpu_implode(&fe, &fe.fe_f3, FTYPE_LNG, &data);
	// XXX オーバーフローチェック

	if (mr < 8) {	// Dn
		CYCLE(100-2);
		reg.D[mr] = data;
	} else {
		CYCLE(100);
		ea = cea_fpu(4);
		write_4(ea, data);
	}
}

// FMOVE.S FPn,<ea>
// cf. 68000PRM.pdf p378 (5-76)
void
MPU680x0Device::fpu_op_fmove_s_mem()
{
	uint mr = ir & 0x3f;
	uint srcnum = (ir2 >> 7) & 7;
	uint32 ea;
	uint32 data;

	fpu_explode(&fe, &fe.fe_f3, FTYPE_EXT, &RegFP(srcnum * 3));
	fpu_implode(&fe, &fe.fe_f3, FTYPE_SNG, &data);

	if (mr < 8) {	// Dn
		CYCLE(80-2);
		reg.D[mr] = data;
	} else {
		CYCLE(80);
		ea = cea_fpu(4);
		write_4(ea, data);
	}
}

// FMOVE.D FPn,<ea>
// cf. 68000PRM.pdf p378 (5-76)
void
MPU680x0Device::fpu_op_fmove_d_mem()
{
	CYCLE(86);

	uint srcnum = (ir2 >> 7) & 7;
	uint32 ea;
	uint32 data[2];

	fpu_explode(&fe, &fe.fe_f3, FTYPE_EXT, &RegFP(srcnum * 3));
	fpu_implode(&fe, &fe.fe_f3, FTYPE_DBL, data);

	ea = cea_fpu(8);
	write_8(ea, data);
}

// FMOVE.X FPn,<ea>
// cf. 68000PRM.pdf p378 (5-76)
void
MPU680x0Device::fpu_op_fmove_x_mem()
{
	CYCLE(72);

	uint srcnum = (ir2 >> 7) & 7;
	uint32 ea;

	ea = cea_fpu(12);
	write_12(ea, &RegFP(srcnum * 3));
}

// FMOVE.P FPn,<ea>
// cf. 68000PRM.pdf p378 (5-76)
void
MPU680x0Device::fpu_op_fmove_p_mem()
{
	uint srcnum = (ir2 >> 7) & 7;
	uint32 ea;
	uint32 data[3];
	uint32 k;

	if ((ir2 & 0x1000)) {
		// dynamic k-factor
		CYCLE(1996+14);
		k = reg.D[(ir2 >> 4) & 7];
	} else {
		// static k-factor
		CYCLE(1996);
		k = ir2;
	}
	// k は7ビット符号付き数
	k &= 0x7f;
	if ((k & 0x40)) {
		k = (int32)(int8)(k | 0x80);
	}

	fpu_explode(&fe, &fe.fe_f3, FTYPE_EXT, &RegFP(srcnum * 3));
	fpu_ftop(&fe, &fe.fe_f3, data, k);

	ea = cea_fpu(12);
	write_12(ea, data);
}

// FMOVEM.L <ea>,<ctllist>
// 1111 ccc 000 mmmrrr | 10D RRR 0000 000000
//  D=0: <ea> to FPctl
//  RRR: register list
void
MPU680x0Device::fpu_op_fmovem_ea2ctl()
{
	uint mr = ir & 0x3f;
	uint reglist = (ir2 >> 10) & 7;
	int count;
	uint32 ea;
	uint32 src[3];
	int i;

	//                      cache case	worst case
	// FMOVE  Rn,FPc		28			31
	// FMOVE  <ea>,FPc		33			36
	// FMOVE  #imm,FPc		30			31
	// FMOVEM <ea>,FPcs		27+6n		30+6n
	// FMOVEM #imm,FPcs		25+6n		29+6n
	//
	// FMOVE #imm と FMOVEM #imm (n==1) は cache case が 1クロックだけ違う
	// ようだけどもう無視する。

	// 転送するレジスタ数
	// ただし nofpc は FPIAR とみなされるようだ。see exp/fmovem.php
	if (reglist == 0)
		reglist = 1;
	count = __builtin_popcount(reglist);

	if (mr < 8) {				// Dn
		CYCLE2(28, 31);
		// 対データレジスタは転送数 1 のみ有効
		if (count > 1) {
			fpu_op_illg2();
			return;
		}
		src[0] = reg.R[mr];

	} else if (mr < 16) {		// An
		CYCLE2(28, 31);
		// 対アドレスレジスタは FPIAR のみ有効
		if (reglist != 1) {
			fpu_op_illg2();
			return;
		}
		src[0] = reg.R[mr];

	} else if (mr < 0x3c) {		// 対メモリ
		CYCLE2(28, 30);
		CYCLE(count * 6);

		// EA を取得。
		// -(An) の時も先にレジスタ数分一気に減算して、そこからプラス方向に
		// 読み込んでいくので、ロジックは全部同じになる。
		// (68000PRM.pdf p393)
		ea = cea_fpu(count * 4);
		for (i = 0; i < count; i++) {
			src[i] = read_4(ea);
			ea += 4;
		}

	} else if (mr == 0x3c) {	// #imm
		CYCLE2(25, 29);
		CYCLE(count * 6);

		for (i = 0; i < count; i++) {
			src[i] = fetch_4();
		}

	} else {
		fpu_op_illg2();
		return;
	}

	// 該当レジスタへ読み込み
	i = 0;
	if ((reglist & 4)) {
		RegFPCR = src[i++] & M68K::FPCR_MASK;
	}
	if ((reglist & 2)) {
		RegFPSR = src[i++] & M68K::FPSR_MASK;
	}
	if ((reglist & 1)) {
		RegFPIAR = src[i++];
	}
}

// FMOVEM.L <ctllist>,<ea>
// 1111 ccc 000 mmmrrr | 10D RRR 0000 000000
//  D=1: FPctl to <ea>
//  RRR: register list
void
MPU680x0Device::fpu_op_fmovem_ctl2ea()
{
	uint mr = ir & 0x3f;
	uint reglist = (ir2 >> 10) & 7;
	int count;
	uint32 ea;

	//                      cache case	worst case
	// FMOVE  FPc,Rn		31			34
	// FMOVE  FPc,<ea>		33			36
	// FMOVEM FPcs,<ea>		27+6n		30+6n

	// 転送するレジスタ数
	// ただし nofpc は FPIAR とみなされるようだ。see exp/fmovem.php
	if (reglist == 0)
		reglist = 1;
	count = __builtin_popcount(reglist);

	if (mr < 8) {			// Dn
		CYCLE2(31, 34);
		// 対データレジスタは転送数は 1 のみ有効
		if (count > 1) {
			fpu_op_illg2();
			return;
		}

		if ((reglist & 4)) {
			reg.R[mr] = RegFPCR;
		}
		if ((reglist & 2)) {
			reg.R[mr] = RegFPSR;
		}
		if ((reglist & 1)) {
			reg.R[mr] = RegFPIAR;
		}
		return;
	}
	if (mr < 16) {			// An
		CYCLE2(31, 34);
		// 対アドレスレジスタは FPIAR のみ有効
		if (reglist != 1) {
			fpu_op_illg2();
			return;
		}
		reg.R[mr] = RegFPIAR;
		return;
	}
	if (mr >= 0x3a) {		// #imm, d16(PC), (PC,IX)
		fpu_op_illg2();
		return;
	}

	// それ以外、対メモリの場合。
	CYCLE2(27, 30);
	CYCLE(count * 6);

	// EA を取得して...
	ea = cea_fpu(count * 4);
	// 該当レジスタをメモリへ書き出し
	if ((reglist & 4)) {
		write_4(ea, RegFPCR);
		ea += 4;
	}
	if ((reglist & 2)) {
		write_4(ea, RegFPSR);
		ea += 4;
	}
	if ((reglist & 1)) {
		write_4(ea, RegFPIAR);
		ea += 4;
	}
}

// FMOVEM.X <ea>,<reglist>
// FMOVEM.X <ea>,Dn
// 1111 ccc 000 mmmrrr | 11D MM000 RRRRRRRR
// D=0: <ea> to FPn
void
MPU680x0Device::fpu_op_fmovem_ea2reg()
{
	uint mr = ir & 0x3f;
	bool predec = !(ir2 & 0x1000);
	bool dynamic = (ir2 & 0x0800);
	uint8 reglist = ir2 & 0xff;
	int i;
	uint32 ea;

	//                    	cache	worst
	// FMOVEM.X <ea>,<list>	35+23n	38+23n
	// FMOVEM.X <ea>,Dn		49+23n	52+23n

	// 不当パターンを弾く
	switch (mr) {
	 case 0x00 ... 0x07:		// Dn
	 case 0x08 ... 0x0f:		// An
	 case 0x20 ... 0x27:		// -(An)
	 case 0x3c:					// #imm
	 case 0x3d ... 0x3f:
		fpu_op_illg2();
		return;
	}

	// ダイナミックモードなら reglist の %0nnn0000 にレジスタ番号が
	// 入っているので、この Dn の下位8ビットを取り出す。レジスタ番号以外の
	// ビットは無視、Dn の上位ビットも無視。
	// ついでにここでベースクロックを記録
	if (dynamic) {
		reglist = reg.D[(reglist >> 4) & 7];
		CYCLE2(49, 52);
	} else {
		CYCLE2(35, 38);
	}

	// EA は最初に一度取得しておいて...
	int count = __builtin_popcount(reglist);
	ea = cea_fpu(12 * count);

	if (predec) {
		// predec ならビット7から FP7..FP0 で、bit7 側から順に転送。
		for (i = 7; reglist; reglist <<=1, i--) {
			if ((int8)reglist < 0) {
				read_12(ea, &RegFP(i * 3));
				ea += 12;
			}
		}
	} else {
		// ctrl/postinc ならビット7から FP0..FP7 で、bit7 側から順に転送。
		for (i = 0; reglist; reglist <<= 1, i++) {
			if ((int8)reglist < 0) {
				read_12(ea, &RegFP(i * 3));
				ea += 12;
			}
		}
	}
	CYCLE(23 * count);
}

// FMOVEM.X <reglist>,<ea>
// FMOVEM.X Dn,<ea>
// 1111 ccc 000 mmmrrr | 11D MM000 RRRRRRRR
// D=1: FPn to <ea>
void
MPU680x0Device::fpu_op_fmovem_reg2ea()
{
	uint mr = ir & 0x3f;
	bool anpd = false;					// 第1ワード EA が -(An) か
	bool predec = !(ir2 & 0x1000);	// 第2ワードの mode が predec か
	bool dynamic = (ir2 & 0x0800);
	uint8 reglist = ir2 & 0xff;
	int i;
	uint32 ea;

	//                    	cache	worst
	// FMOVEM.X <list>,<ea>	37+25n	40+25n
	// FMOVEM.X Dn,<ea>		51+25n	54+25n

	// 不当パターンを弾く
	// ついでに -(An) だけ覚えておく
	switch (mr) {
	 case 0x00 ... 0x07:		// Dn
	 case 0x08 ... 0x0f:		// An
	 case 0x18 ... 0x1f:		// (An)+
	 case 0x3a:					// d16(PC)
	 case 0x3b:					// (PC,IX)
	 case 0x3c:					// #imm
	 case 0x3d ... 0x3f:
		fpu_op_illg2();
		return;
	 case 0x20 ... 0x27:		// -(An)
		anpd = true;
		break;
	}

	// ダイナミックモードなら reglist の %0nnn0000 にレジスタ番号が
	// 入っているので、この Dn の下位8ビットを取り出す。レジスタ番号以外の
	// ビットは無視、Dn の上位ビットも無視。
	// ついでにここでベースクロックを記録
	if (dynamic) {
		reglist = reg.D[(reglist >> 4) & 7];
		CYCLE2(51, 54);
	} else {
		CYCLE2(37, 40);
	}

	int count = __builtin_popcount(reglist);

	// -(An) の時だけ EA を別処理。
	// 現状の cea_fpu() は -(An) の時、巻き戻し用のレジスタを保存し
	// デクリメントしたアドレスを返すまでを一度に行う便利関数だが、
	// FMOVEM fpn,-(An) は最初に巻き戻し用レジスタを保存し (これは命令先頭
	// に戻るためなので最初の1回だけ)、その後レジスタ1つずつ 12バイトを
	// プリデクリメントしながら格納していく必要がある (この順に実行しないと
	// バスエラーの原因箇所が異なってしまう)。
	// そのため cea_fpu() がそのまま使えないので、ここだけ独自対応する。
	// cea_fpu() は1命令中で EA の取得が1回である前提のあくまで便利関数
	// なので、例外的である FMOVEM は仕方がない。
	if (anpd) {
		CYCLE(6);
		uint n = eanum(ir);
		save_reg_pd(n);

		if (predec) {
			// predec ならビット7から FP7..FP0 で、bit7 側から順に転送。
			for (i = 7; reglist; reglist <<= 1, i--) {
				if ((int8)reglist < 0) {
					reg.A[n] -= 12;
					write_12(reg.A[n], &RegFP(i * 3));
				}
			}
		} else {
			// ctrl/postinc ならビット7から FP0..FP7 で、bit7 側から順に転送。
			for (i = 0; reglist; reglist <<= 1, i++) {
				if ((int8)reglist < 0) {
					reg.A[n] -= 12;
					write_12(reg.A[n], &RegFP(i * 3));
				}
			}
		}
	} else {
		// -(An) 以外は EA を最初に一度計算しておく方法が使える
		ea = cea_fpu(12 * count);

		if (predec) {
			// predec ならビット7から FP7..FP0 で、bit7 側から順に転送。
			for (i = 7; reglist; reglist <<= 1, i--) {
				if ((int8)reglist < 0) {
					write_12(ea, &RegFP(i * 3));
					ea += 12;
				}
			}
		} else {
			// ctrl/postinc ならビット7から FP0..FP7 で、bit7 側から順に転送。
			for (i = 0; reglist; reglist <<= 1, i++) {
				if ((int8)reglist < 0) {
					write_12(ea, &RegFP(i * 3));
					ea += 12;
				}
			}
		}
	}
	CYCLE(25 * count);
}

// FScc.B <ea>
// 1111 ccc 001 mmmrrr | 0000 000 000 CCCCCC
void
MPU680x0Device::fpu_op_fscc()
{
	uint mr = ir & 0x3f;
	uint mm = eamode(ir);
	uint rr = eanum(ir);
	uint cond = ir2 & 0x3f;
	uint32 data;
	uint32 ea;

	//                   	cache	worst
	// FScc.B Dn			18		21
	// FScc.B (An)+/-(An)	22		25		; condition is true
	// FScc.B (An)+/-(An)	21		24		; condition is false
	// FScc.B <ea>			20		23

	if (cond > 0x1f) {
		fpu_op_illg2();
		return;
	}

	data = (test_cc(&fe, cond) == -1) ? 0xff : 0x00;

	switch (mm) {
	 case 0:	// Dn
		CYCLE2(18, 21);
		reg.D[rr] &= 0xffffff00;
		reg.D[rr] |= data;
		return;

	 case 3:	// (An)+
	 case 4:	// -(An)
		// ここだけ他のメモリ系 EA とはクロックが違うし、
		// 条件の成立可否でも違う。
		if (data == 0x00) {
			CYCLE2(21, 24);
		} else {
			CYCLE2(22, 25);
		}
		break;
	 case 7:
		if (mr == 0x38 || mr == 0x39) {	// Abs.W, Abs.L
		} else {
			fpu_op_illg2();
			return;
		}
		FALLTHROUGH;
	 default:
		CYCLE2(20, 23);
		break;
	}

	ea = cea_fpu(1);
	write_1(ea, data);
}

// FDBcc Dn,<label>
// 1111 ccc 001 001rrr | 0000 000 000 CCCCCC | <16bit disp>
void
MPU680x0Device::fpu_op_fdbcc()
{
	uint rr = ir & 7;
	uint cond = ir2 & 0x3f;

	if (cond > 0x1f) {
		fpu_op_illg2();
		return;
	}

	if (test_cc(&fe, cond) == -1) {
		// 真なら何もしない。disp を読み飛ばして次へ
		CYCLE2(20, 24);
		fetch_2();
	} else {
		// 偽の場合
		uint16 data = reg.D[rr] - 1;
		reg.D[rr] = (reg.D[rr] & 0xffff0000) | data;

		if (data == 0xffff) {
			// ループ終了。disp を読み飛ばす
			CYCLE2(24, 32);
			fetch_2();
		} else {
			// 計算に使う PC は FDBcc 命令+2 のところらしい
			CYCLE2(20, 26);
			uint32 origin = ppc + 2;
			int32 disp = (int32)(int16)fetch_2();
			Jump(origin + disp);
		}
	}
}

// FTRAPcc.W #<data>
void
MPU680x0Device::fpu_op_ftrapcc_w()
{
	uint cond = ir2 & 0x3f;

	if (cond > 0x1f) {
		fpu_op_illg2();
		return;
	}

	// 即値はいずれにしても読み捨てる必要がある。
	fetch_2();

	if (test_cc(&fe, cond) == -1) {
		CYCLE2(41, 45);
		Exception(M68K::EXCEP_TRAPV);
	} else {
		CYCLE2(20, 23);
	}
}

// FTRAPcc.L #<data>
void
MPU680x0Device::fpu_op_ftrapcc_l()
{
	uint cond = ir2 & 0x3f;

	if (cond > 0x1f) {
		fpu_op_illg2();
		return;
	}

	// 即値はいずれにしても読み捨てる必要がある。
	fetch_4();

	if (test_cc(&fe, cond) == -1) {
		CYCLE2(43, 52);
		Exception(M68K::EXCEP_TRAPV);
	} else {
		CYCLE2(22, 27);
	}
}

// FTRAPcc
void
MPU680x0Device::fpu_op_ftrapcc()
{
	uint cond = ir2 & 0x3f;

	if (cond > 0x1f) {
		fpu_op_illg2();
		return;
	}

	// 即値なし。

	if (test_cc(&fe, cond) == -1) {
		CYCLE2(39, 47);
		Exception(M68K::EXCEP_TRAPV);
	} else {
		CYCLE2(18, 22);
	}
}

// FBcc.W <label>
// 1111 ccc 01S CCCCCC | <16bit disp>
// S=0: 16bit disp
//
// FNOP
// 1111 ccc 010 000000 | 0000 0000 0000 0000
void
MPU680x0Device::fpu_op_fbcc_w()
{
	uint cond = ir & 0x3f;

	// TODO: Illegal condition の戻り値を処理すること。他の test_cc() も同様。
	if (test_cc(&fe, cond) == -1) {
		CYCLE2(20, 23);
		uint32 origin = reg.pc;
		int32 disp = (int32)(int16)fetch_2();
		Jump(origin + disp);
	} else {
		// disp を読み飛ばす
		CYCLE2(18, 19);
		fetch_2();
	}
}

// FBcc.L <label>
// 1111 ccc 01S CCCCCC | <32bit disp>
// S=1: 32bit disp
void
MPU680x0Device::fpu_op_fbcc_l()
{
	uint cond = ir & 0x3f;

	if (test_cc(&fe, cond) == -1) {
		CYCLE2(20, 23);
		uint32 origin = reg.pc;
		int32 disp = (int32)fetch_4();
		Jump(origin + disp);
	} else {
		// disp を読み飛ばす
		CYCLE2(18, 21);
		fetch_4();
	}
}

// FSAVE の NULL フレーム (全機種共通で4バイト)
/*static*/ const uint32
MPU680x0Device::fsave_frame_null[] = {
	0,
};

// FSAVE のアイドルフレーム (68881 は 0x1c = 28 バイト、 7ロングワード)
/*static*/ const uint32
MPU680x0Device::fsave_frame_idle[] = {
	0x1f180000,
	0,
	0,
	0,
	0,
	0,
	0x70000000,
};

// FSAVE
// スーパーバイザモードは判定済み
void
MPU680x0Device::fpu_op_fsave()
{
	uint mm = eamode(ir);
	uint rr = eanum(ir);
	uint32 ea;
	const uint32 *frame;
	int framelen;

	// XXX EA が -(An) の時 An が逐次減算て書いてあるソースきぼんぬ

	// フレームを選択
	if (fpu_dirty == false) {
		// NULL フレーム
		CYCLE2(16, 18);
		frame = fsave_frame_null;
		framelen = countof(fsave_frame_null);
	} else {
		// IDLE フレーム
		CYCLE2(52, 54);	// XXX これは最小値らしいけど
		frame = fsave_frame_idle;
		framelen = countof(fsave_frame_idle);
	}

	// EA は独自体系なのでここでべたに書く
	switch (mm) {
	 case 2:	// (An)
		ea = cea_anin(rr);
		break;
	 case 4:	// -(An)
		// これだけ処理が違うので、ここでやる
		save_reg_pd(rr);
		for (int i = framelen - 1; i >= 0; i--) {
			reg.A[rr] -= 4;
			write_4(reg.A[rr], frame[i]);
		}
		return;

	 case 5:	// d16(An)
		ea = cea_andi(rr);
		break;
	 case 6:	// (An,IX)
		ea = cea_anix(rr);
		break;
	 case 7:
		switch (rr) {
		 case 0:	// Abs.W
			ea = cea_absw();
			break;
		 case 1:	// Abs.L
			ea = cea_absl();
			break;
		 default:
			fpu_op_illg();
			return;
		}
		break;
	 default:
		fpu_op_illg();
		return;
	}

	// -(An) 以外の EA の場合
	for (int i = 0; i < framelen; i++) {
		write_4(ea, frame[i]);
		ea += 4;
	}
}

// FRESTORE
// スーパーバイザモードは判定済み
void
MPU680x0Device::fpu_op_frestore()
{
	uint mm = eamode(ir);
	uint rr = eanum(ir);
	uint32 ea;
	uint32 data;
	bool postinc;

	postinc = false;
	// EA は独自体系なのでここでべたに書く
	switch (mm) {
	 case 2:	// (An)
		ea = cea_anin(rr);
		break;
	 case 3:	// (An)+
		ea = cea_anin(rr);
		postinc = true;
		break;
	 case 5:	// d16(An)
		ea = cea_andi(rr);
		break;
	 case 6:	// (An,IX)
		ea = cea_anix(rr);
		break;
	 case 7:
		switch (rr) {
		 case 0:	// Abs.W
			ea = cea_absw();
			break;
		 case 1:	// Abs.L
			ea = cea_absl();
			break;
		 case 2:	// d16(PC)
			ea = cea_pcdi();
			break;
		 case 3:	// (PC,IX)
			ea = cea_pcix();
			break;
		 default:
			fpu_op_illg();
			return;
		}
		break;
	 default:
		fpu_op_illg();
		return;
	}

	// 1ロングワード読んでみる
	data = read_4(ea);
	ea += 4;

	if (data == 0) {
		// NULL フレームなら、これ以上フレームはない。
		CYCLE2(21, 22);

		// NULL フレームのリストアは FPU の状態をリセットする
		fpu_reset();
	} else {
		// それ以外のフレームは先頭ロングワードから長さを取得。
		// 先頭1ロングワードは $VVLL0000 で VV がバージョン(8bit)、
		// LL が(自身を含まないこれ以降の)バイト数 (8bit) を示している。
		// 現在の実装ではセーブ/リストアは何も実装していないので、
		// 全部読み捨てる。

		// 実際にはフレーム種別によってサイクル数は変わるけど、ここでは
		// FSAVE は NULL フレーム以外には IDLE フレームしか吐かないので
		// IDLE フレームのサイクルだけでいい。
		CYCLE2(57, 58);

		uint len = (data >> 16) & 0xff;
		len /= 4;
		for (int i = 0; i < len; i++) {
			read_4(ea);
			ea += 4;
		}

		fpu_dirty = true;
	}

	// (An)+ なら書き戻し
	if (postinc) {
		reg.A[rr] = ea;
	}
}

unix.superglobalmegacorp.com

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