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

//	+------ 030(without FPU)
//	|	+-- 030(+6888x)
//	|	|	040	LC040
//	S	o	o	4	: FSAVE/FRESTORE (有効なEA)
//	F	F	F		: FSAVE/FRESTORE (無効なEA)
//	F	o	o	4	: 040 でも実装してある命令 (有効なEA)
//	F	F	F		: 040 でも実装してある命令 (無効なEA)
//	F	o	2	4	: 6888x 命令 (有効なEA)
//	F	F	F?		: 6888x 命令 (無効なEA)
//	F	F	F	F	: 無効な命令ビットパターン
//
//	(厳密に言えばFPU命令ではないが)
//	S	S	F?	F?	: cpSAVE/cpRESTORE (有効なEA)
//	F	F	F?	F?	: cpSAVE/cpRESTORE (無効なEA)
//	…	XEiJ の flineprivilege.x によると、無効な EA のパターンは
//		68030 では F ライン例外になる。
//		040 ではコプロセッサプロトコル自体が廃止されているので
//		ここはいずれも F ライン例外になるだろうか。
//
// o: 有効な命令処理
// F: F ライン例外 (Format$0)
// 2: 浮動小数点未実装例外(Format$2)
// 4: 浮動小数点未実装例外(Format$4)
// S: 特権違反例外

// 68040:
//              	Int	.S	.D	.X	.P
// ノーマル数		o	o	o	o	x
// Zero				o	o	o	o	x
// Inf				-	o	o	o	x
// NAN				-	o	o	o	x
// 非正規化数		-	x	x	x	x
// アンノーマル数	-	-	-	x	x
//
// o: ハードウェアサポート
// x: ソフトウェアサポート

// 68040 の BUSY フレーム:
// MC68040UM.pdf (p.9-40)、fpsp/fpsp.h より。
//
//           1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
//          +---------------+---------------+---------------+---------------+
//$00       |  VERSION=$41  |     $60       |                               |
//          +---------------+---------------+---------------+---------------+
//$04       |                                                               |
//          +---------------+---------------+---------------+---------------+
//$08(LV-92)|   CU_SAVEPC : |(FPR_DIRTY_BITS)                               |
//          +---------------+---------------+---------------+---------------+
//$0c(LV-88)|                                                               |
//$10(LV-84)|                                                               |
//$14(LV-80)|                                                               |
//          +---------------+---------------+---------------+---------------+
//$18(LV-76)| |<- WBTS   WBTE[14:0]         |  (WBTEMP_SGN) |               |
//          +---------------+---------------+---------------+---------------+
//           (WBTEMP_SGN) のバイトは fpsp がワークとして使っているようだ。
//          +---------------+---------------+---------------+---------------+
//$1c(LV-72)|       WBTM[63-32]   (資料によって [65-34][33-02] 表記もあるが |
//$20(LV-68)|       WBTM[31-00]    ここでは仮数部のビット位置で数える)      |
//          +---------------+---------------+---------------+---------------+
//$24(LV-64)|                         (FPSR_SHADOW)                         |
//          +---------------+---------------+---------------+---------------+
//$28(LV-60)|                            FPIARCU                            |
//          +---------------+---------------+---------------+---------------+
//$2c(LV-56)|                                                               |
//          +---------------+---------------+---------------+---------------+
//$30(LV-52)|          (CMDREG2B.W?/.L?)                                    |
//          +---------+-----+---------------+---------------+---------------+
//$34(LV-48)|         |       CMDREG3B      |                               |
//          +---------+-----+---------------+---------------+---------------+
//$38(LV-44)|    (NMNEXC)   |    (NMCEXC)   |                               |
//          |            N N|N N N C C C C C|                               |
//          |            U N|O V D U S O V D|                               |
//          +---------------+---------------+---------------+---------------+
//     NMN Unsupported --+ | | | | | | | | +-- NMC Underflow
//     NMN SNAN        ----+ | | | | | | +---- NMC Overflow
//     NMN OPErr       ------+ | | | | +------ NMC OPErr
//     NMN Overflow    --------+ | | +-------- NMC SNAN
//     NMN Underflow   ----------+ +---------- NMC Unsupported
//          +---------------+---------------+---------------+---------------+
//$3c(LV-40)|STAG |E| |V|G|R|S|             |                               |
//          +---------------+---------------+---------------+---------------+
//     (ETEMP15) --+   | | | +-- SBIT   (WBTEMP Sticky)
//                     | | +---- WBTM0  (WBTEMP Round)
//                     | +------ WBTM1  (WBTEMP Guard)
//                     +-------- WBTM66 (WBTEMP Overflow)
//          +---------------+---------------+---------------+---------------+
//$40(LV-36)|          CMDREG1B             |                               |
//          +-------+-------+-------+-------+---------------+---------------+
//$44(LV-32)|DTAG |F|. . . .:. . .|W|. . . .|                               |
//          +-------+-------+-------+-------+---------------+---------------+
//                 +-- (FPTEMP15)  +-- (WBTEMP15)
//          +---------------+---------------+---------------+---------------+
//$48(LV-28)|. . . . .|E|E|S|X|.|U|T|. . . .|                               |
//          |         |1|3|F|F| |F|F|       |                               |
//          +---------------+---------------+---------------+---------------+
//               (SFLAG) --+ |   | +--  TFLAG
//               (XFLAG) ----+   +---- (UFLAG)
//          +---------------+---------------+---------------+---------------+
//$4c(LV-24)| |<-FPTS      FPTE             |                               |
//          +---------------+---------------+---------------+---------------+
//$50(LV-20)|                         FPTM[63-32]                           |
//$54(LV-16)|                         FPTM[31-00]                           |
//          +---------------+---------------+---------------+---------------+
//$58(LV-12)| |<-ETS       ETE              |                               |
//          +---------------+---------------+---------------+---------------+
//$5c(LV-8) |                         ETM[63-32]                            |
//$60(LV-4) |                         ETM[31-00]                            |
//          +---------------+---------------+---------------+---------------+
//$64(LV-0)

#include "mpu680x0.h"
#include "m680x0disasm.h"
#include "m680x0cycle.h"
#include "textscreen.h"

#define SIZE_L	(0)
#define SIZE_S	(1)
#define SIZE_X	(2)
#define SIZE_P	(3)
#define SIZE_W	(4)
#define SIZE_D	(5)
#define SIZE_B	(6)

// val が単精度で非正規化数なら true を返す。
static inline bool
is_denormal_s(uint32 val)
{
	uint32 e = (val >> 23) & 0xff;
	uint32 m = val & 0x7f'ffff;
	if (e == 0 && m != 0) {
		return true;
	}
	return false;
}

// data[0..1] が倍精度で非正規化数なら true を返す。
static inline bool
is_denormal_d(const uint32 *data)
{
	uint32 e = (data[0] >> 20) & 0x7ff;
	uint64 m = ((uint64)(data[0] & 0x000f'ffff) << 32) | data[1];
	if (e == 0 && m != 0) {
		return true;
	}
	return false;
}

// data[0..2] が拡張精度で非正規化数なら true を返す。
static inline bool
is_denormal_x(const uint32 *data)
{
	uint32 e = (data[0] >> 16) & 0x7fff;
	uint32 i = (data[1] >> 31) & 1;
	uint64 m = (((uint64)(data[1] & 0x7fff'ffff)) << 32) | data[2];
	if (e == 0 && i == 0 && m != 0) {
		return true;
	}
	return false;
}

// data[0..2] が拡張精度でアンノーマル数なら true を返す。
static inline bool
is_unnormal_x(const uint32 *data)
{
	uint32 e = (data[0] >> 16) & 0x7fff;
	uint32 i = (data[1] >> 31) & 1;
	uint64 m = (((uint64)(data[1] & 0x7fff'ffff)) << 32) | data[2];
	if (e != 0 && e != 0x7fff && i == 0 && m != 0) {
		return true;
	}
	// XXX Unnormal zero はハード処理かソフト処理か分からない。
	if (e != 0 && e != 0x7fff && i == 0 && m == 0) {
		return true;
	}
	return false;
}

// リセット用のレジスタ初期値
/*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 レジスタの初期値を用意しておく。
	// FPn は NAN に。FPSR, FPCR, FPIAR はゼロクリアされる。
	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 リセット時(true)と、NULL フレームのリストア時(false)に呼ばれる。
void
MPU680x0Device::ResetFPU(bool hard)
{
	// 高速化のため、あらかじめ用意しておいたイメージをコピーする
	memcpy(&reg.fpframe, &initial_fpframe, sizeof(initial_fpframe));

	fpu_state = FPU_STATE_NULL;

	if (__predict_false(hard)) {
		// この内部情報は NULL フレームのリストア (頻繁に起きる) のたびに
		// モニタから消えるよりは、直近の1つが見え続けていてほしい。
		// ただしハードリセット前の状態は引き継がない。
		memset(&fpu40, 0, sizeof(fpu40));
		fpu40.reg1b = 0xffff;
		fpu40.stag = FPU40::TAG_NONE;
		fpu40.dtag = FPU40::TAG_NONE;
	}
	fpu40.enable = false;
}

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

// こっちは LC040 用。
//
// Dn、An、#imm に対しては 0 を返す (MC68040UM.pdf, p.A-6)。
// それ以外のすべてのアドレッシングモードで反応するため、命令ごとに
// 対応していないアドレッシングモードは事前に弾くこと。
// 不当パターンなら EXCEP_ILLEGAL をスローする。
uint32
MPU680x0Device::cea_fpulc(int bytes)
{
	uint mr = ir & 077;

	if (__predict_false(mr < 16 || mr == 074)) {
		return 0;
	} else {
		return cea_fpu(bytes);
	}
}

// メモリから 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ビット値をメモリに書き込む
void
MPU680x0Device::write_8(uint32 addr, const uint32 *data)
{
	write_4(addr,     data[0]);
	write_4(addr + 4, data[1]);
}

// 96ビット値をメモリに書き込む
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]);
}

// 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 (レジスタ-レジスタ演算)、opclass=%000
void
MPU680x0Device::fpu_op_fgen_reg()
{
	uint srcnum = (ir2 >> 10) & 7;

	fgen(&RegFP(srcnum), SIZE_X);
}

// R/M=1 (メモリ-レジスタ演算)、opclass=%010
void
MPU680x0Device::fpu_op_fgen_mem()
{
	uint mr = ir & 0x3f;
	uint size_id = (ir2 >> 10) & 7;
	uint32 ea = 0;
	uint32 data[3];

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

	if ((mr >> 3) == 1) {	// An
		fpu_op_illg2();
		return;
	}

	uint bytes;
	switch (size_id) {
	 case SIZE_B:
		bytes = 1;
		break;
	 case SIZE_W:
		bytes = 2;
		break;
	 case SIZE_L:
	 case SIZE_S:
		bytes = 4;
		break;
	 case SIZE_D:
		bytes = 8;
		break;
	 case SIZE_X:
	 case SIZE_P:
		bytes = 12;
		break;
	 default:
		__unreachable();
	}

	if (__predict_false(GetFPUType().Is4060LC())) {
		ea = cea_fpulc(bytes);
		ExceptionFPLC(ea);
		return;
	}

	// 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 (bytes) {
		 case 1:
			data[0] = (int32)(int8)reg.D[mr];
			break;
		 case 2:
			data[0] = (int32)(int16)reg.D[mr];
			break;
		 case 4:
			data[0] = reg.D[mr];
			break;
		 default:
			fpu_op_illg2();
			return;
		}
		// 参照されない EA がどうなるか分からないが、LC040 からの類推。
		fpu40.ea = 0;

		// ソースが Dn の場合は 5クロック減
		CYCLE3(fpu_src_dn);
	} else if (mr == 074) {		// #imm
		switch (bytes) {
		 case 1:
			data[0] = fetch_2() & 0xff;
			break;
		 case 2:
			data[0] = fetch_2();
			break;
		 case 4:
			data[0] = fetch_4();
			break;
		 case 8:
			data[0] = fetch_4();
			data[1] = fetch_4();
			break;
		 case 12:
			data[0] = fetch_4();
			data[1] = fetch_4();
			data[2] = fetch_4();
			break;
		 default:
			__unreachable();
		}
		// 参照されない EA がどうなるか分からないが、LC040 からの類推。
		fpu40.ea = 0;
	} else {
		// それ以外は EA を求めてからメモリから読み込む。
		ea = cea_fpu(bytes);

		switch (bytes) {
		 case 1:
			data[0] = (int32)(int8)read_1(ea);
			break;
		 case 2:
			data[0] = (int32)(int16)read_2(ea);
			break;
		 case 4:
			data[0] = read_4(ea);
			break;
		 case 8:
			read_8(ea, data);
			break;
		 case 12:
			read_12(ea, data);
			break;
		 default:
			__unreachable();
		}
		fpu40.ea = ea;
	}

	fgen(data, size_id);
}

// 一般算術演算。
// srcdata, srcsize_id がソースの生データとサイズ識別子。
// デスティネーション(FPn) は ir2 から求まる。
void
MPU680x0Device::fgen(const uint32 *srcdata, uint srcsize_id)
{
	uint opmode = ir2 & 0x3f;
	uint dstnum = (ir2 >> 7) & 7;
	bool discard_result = false;
	struct fpn *res = NULL;	// shutup gcc

	// 機種別の前処理。
	if (GetFPUType().Is4060FPU()) {
		// 未実装命令と未実装データ型の検出など。
		if (fgen040pre(opmode, dstnum, srcdata, srcsize_id) == false) {
			return;
		}
	} else {
		// fe_f1, fe_f2 にロードするだけ。
		fgen881pre(dstnum, srcdata, srcsize_id);
	}

	switch (opmode) {
	 case 0x00:		// FMOVE
	 fmove:
		CYCLE3(fmove);
		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
	 fsqrt:
		CYCLE3(fsqrt);
		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
	 fabs:
		CYCLE3(fabs);
		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
	 fneg:
		CYCLE3(fneg);
		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
	 fdiv:
		CYCLE3(fdiv);
		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
	 fadd:
		CYCLE3(fadd);
		res = fpu_add(&fe);
		fpu_round_prec(&fe, res);
		break;
	 case 0x23:		// FMUL
	 fmul:
		CYCLE3(fmul);
		res = fpu_mul(&fe);
		fpu_round_prec(&fe, res);
		break;
	 case 0x24:		// FSGLDIV
		CYCLE3(fsgldiv);
		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
		CYCLE3(fsglmul);
		res = fpu_sglmul(&fe);
		break;
	 case 0x28:		// FSUB
	 fsub:
		CYCLE3(fsub);
		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
		CYCLE3(fcmp);
		res = fpu_cmp(&fe);
		discard_result = 1;
		break;
	 case 0x3a:		// FTST
		CYCLE3(ftst);
		res = &fe.fe_f2;
		discard_result = 1;
		break;

#define ONLY_40	do {	\
	if (GetFPUType().Is40FPU() == false) {	\
		goto illegal;	\
	}	\
} while (0)
// fe の精度だけ変える。
// fe_fpcr は RegFPCR (レジスタ) のほうには伝搬しないし、
// 次の命令の冒頭でまた RegFPCR から上書きされるので、
// 書き換えっぱなしでよい。
#define ROUND_FS	do {	\
	fe.fe_fpcr &= ~FPCR_PREC;	\
	fe.fe_fpcr |= FPCR_SNGL;	\
} while (0)
#define ROUND_FD	do {	\
	fe.fe_fpcr &= ~FPCR_PREC;	\
	fe.fe_fpcr |= FPCR_DBL;	\
} while (0)

	 case 0x40:	ONLY_40; ROUND_FS; goto fmove;	// FSMOVE
	 case 0x44:	ONLY_40; ROUND_FD; goto fmove;	// FDMOVE
	 case 0x41:	ONLY_40; ROUND_FS; goto fsqrt;	// FSSQRT
	 case 0x45:	ONLY_40; ROUND_FD; goto fsqrt;	// FDSQRT
	 case 0x58:	ONLY_40; ROUND_FS; goto fabs;	// FSABS
	 case 0x5c:	ONLY_40; ROUND_FD; goto fabs;	// FDABS
	 case 0x5a:	ONLY_40; ROUND_FS; goto fneg;	// FSNEG
	 case 0x5e:	ONLY_40; ROUND_FD; goto fneg;	// FDNEG
	 case 0x60:	ONLY_40; ROUND_FS; goto fdiv;	// FSDIV
	 case 0x64:	ONLY_40; ROUND_FD; goto fdiv;	// FDDIV
	 case 0x62:	ONLY_40; ROUND_FS; goto fadd;	// FSADD
	 case 0x66:	ONLY_40; ROUND_FD; goto fadd;	// FDADD
	 case 0x63:	ONLY_40; ROUND_FS; goto fmul;	// FSMUL
	 case 0x67:	ONLY_40; ROUND_FD; goto fmul;	// FDMUL
	 case 0x68:	ONLY_40; ROUND_FS; goto fsub;	// FSSUB
	 case 0x6c:	ONLY_40; ROUND_FD; goto fsub;	// FDSUB

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

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

// fgen() の 68881 での前処理。
// src, dst を fe_f2, fe_f1 にそれぞれロードするだけ。
void
MPU680x0Device::fgen881pre(uint dstnum, const uint32 *srcdata, uint srcsize_id)
{
	switch (srcsize_id) {
	 case SIZE_B:
	 case SIZE_W:
	 case SIZE_L:
		CYCLE(+27);
		fpu_explode(&fe, &fe.fe_f2, FTYPE_LNG, srcdata);
		break;
	 case SIZE_S:
		CYCLE(+19);
		fpu_explode(&fe, &fe.fe_f2, FTYPE_SNG, srcdata);
		break;
	 case SIZE_D:
		CYCLE(+25);
		fpu_explode(&fe, &fe.fe_f2, FTYPE_DBL, srcdata);
		break;
	 case SIZE_X:
		CYCLE(+23);
		fpu_explode(&fe, &fe.fe_f2, FTYPE_EXT, srcdata);
		break;
	 case SIZE_P:
		CYCLE(+837);
		fpu_explode(&fe, &fe.fe_f2, FTYPE_BCD, srcdata);
		break;
	 default:
		__unreachable();
	}
	fpu_explode(&fe, &fe.fe_f1, FTYPE_EXT, &RegFP(dstnum));
}

// fgen() の 68040 での前処理。
// 未実装命令のチェックと該当すればその例外処理、
// 未実装データ型のチェックと該当すればその例外処理を行う。
// 例外処理を行った場合は false を返す。
bool
MPU680x0Device::fgen040pre(uint opmode, uint dstnum,
	const uint32 *srcdata, uint srcsize_id)
{
	// 未実装命令か調べる。
	bool unimpl = false;
	switch (opmode) {
	 case 0x01:		// FINT
	 case 0x02:		// FSINH
	 case 0x03:		// FINTRZ
	 case 0x06:		// FLOGNP1
	 case 0x08:		// FETOXM1
	 case 0x09:		// FTANH
	 case 0x0a:		// FATAN
	 case 0x0c:		// FASIN
	 case 0x0d:		// FATANH
	 case 0x0e:		// FSIN
	 case 0x0f:		// FTAN
	 case 0x10:		// FETOX
	 case 0x11:		// FTWOTOX
	 case 0x12:		// FTENTOX
	 case 0x14:		// FLOGN
	 case 0x15:		// FLOG10
	 case 0x16:		// FLOG2
	 case 0x19:		// FCOSH
	 case 0x1c:		// FACOS
	 case 0x1d:		// FCOS
	 case 0x1e:		// FGETEXP
	 case 0x1f:		// FGETMAN
	 case 0x21:		// FMOD
	 case 0x25:		// FREM
	 case 0x26:		// FSCALE
	 case 0x30:		// FSINCOS
	 case 0x31:		// FSINCOS
	 case 0x32:		// FSINCOS
	 case 0x33:		// FSINCOS
	 case 0x34:		// FSINCOS
	 case 0x35:		// FSINCOS
	 case 0x36:		// FSINCOS
	 case 0x37:		// FSINCOS
		unimpl = true;
		break;
	 default:
		break;
	}

	// ソースがサポートしている型なら拡張精度に変換して fe_f2 にセットし、
	// stag は NONE のままか NORMAL or ZERO をセット。
	// 未サポート型なら所定の etemp を作成して stag は DENORMAL か UNNORMAL。
	//
	//			Int			.S/.D		.X			.P
	// Normal	fe_f2,NORM	fe_f2,NONE	fe_f2,NONE	etemp,UNNO
	// Zero		fe_f2,ZERO	fe_f2,NONE	fe_f2,NONE	etemp,UNNO
	// Inf		-			fe_f2,NONE	fe_f2,NONE	etemp,UNNO
	// NAN		-			fe_f2,NONE	fe_f2,NONE	etemp,UNNO
	// Denorm	-			etemp,DENO	etemp,UNNO	etemp,UNNO
	// Unnorm	-			-			etemp,UNNO	etemp,UNNO

	// サイクル数は 68040UM.pdf p10-30 の表から適当に逆算。

	int32 stag = FPU40::TAG_NONE;
	switch (srcsize_id) {
	 case SIZE_B:
	 case SIZE_W:
	 case SIZE_L:
		CYCLE(+0);
		if (srcdata[0] == 0) {
			stag = FPU40::TAG_ZERO;
		} else {
			stag = FPU40::TAG_NORMAL;
		}
		fpu_explode(&fe, &fe.fe_f2, FTYPE_LNG, srcdata);
		break;
	 case SIZE_S:
		CYCLE(+0);
		if (is_denormal_s(srcdata[0])) {
			// 68040UM.pdf, p9-24. Figure 9-9 (a)
			uint32 s = srcdata[0] & 0x8000'0000;
			uint32 m = srcdata[0] & 0x007f'ffff;
			fpu40.etemp[0] = s;
			fpu40.etemp[1] = m << 8;
			fpu40.etemp[2] = 0;
			stag = FPU40::TAG_DENORMAL_S;
			break;
		}
		fpu_explode(&fe, &fe.fe_f2, FTYPE_SNG, srcdata);
		break;
	 case SIZE_D:
		CYCLE(+0);
		if (is_denormal_d(srcdata)) {
			// 68040UM.pdf, p9-24. Figure 9-9 (b)
			uint32 s = srcdata[0] & 0x8000'0000;
			uint64 m = ((uint64)(srcdata[0] & 0x000f'ffff) << 32) | srcdata[1];
			m <<= 11;
			fpu40.etemp[0] = s;
			fpu40.etemp[1] = m >> 32;
			fpu40.etemp[2] = m & 0xffffffff;
			stag = FPU40::TAG_DENORMAL_D;
			break;
		}
		fpu_explode(&fe, &fe.fe_f2, FTYPE_DBL, srcdata);
		break;
	 case SIZE_X:
		CYCLE(+1);
		if (is_denormal_x(srcdata)) {
			memcpy(fpu40.etemp, srcdata, 12);
			stag = FPU40::TAG_DENORMAL_X;
			break;
		} else if (is_unnormal_x(srcdata)) {
			memcpy(fpu40.etemp, srcdata, 12);
			stag = FPU40::TAG_UNNORMAL_X;
			break;
		}
		fpu_explode(&fe, &fe.fe_f2, FTYPE_EXT, srcdata);
		break;
	 case SIZE_P:
		// 上位32ビットを FPTM[31-00] に、
		// 下位64ビットを  ETM[31-00] に置く。(68040UM.pdf, pp.9-42〜43)
		fpu40.fptemp[2] = srcdata[0];
		fpu40.etemp[1]  = srcdata[1];
		fpu40.etemp[2]  = srcdata[2];
		// Packed の時 stag は undefined と書いてある。
		// ここではこの後の例外判定用および表示用として使う。
		stag = FPU40::TAG_PACKED;
		break;
	 default:
		__unreachable();
	}

	// デスティネーション(レジスタ) が未サポートデータ型になるのは
	// 非正規化数の時だけのはず。
	int32 dtag = FPU40::TAG_NONE;
	if (is_denormal_x(&RegFP(dstnum))) {
		dtag = FPU40::TAG_DENORMAL_X;
	}
	fpu_explode(&fe, &fe.fe_f1, FTYPE_EXT, &RegFP(dstnum));
	if (dtag == FPU40::TAG_NONE) {
		dtag = fpu_gettag(&fe.fe_f1);
	}

	// 例外を起こす必要があるか。
	if (unimpl ||
		stag >= FPU40::TAG_UNNORMAL ||
		dtag >= FPU40::TAG_UNNORMAL)
	{
		// stag がまだならここで fpn から決まる。
		if (stag == FPU40::TAG_NONE) {
			stag = fpu_gettag(&fe.fe_f2);
		}
		// stag が UNNORMAL, DENORMAL なら etemp はセット済み。
		if (stag < FPU40::TAG_UNNORMAL) {
			fpu_implode(&fe, &fe.fe_f2, FTYPE_EXT, fpu40.etemp);
		}
		// Packed なら E1 を立てる、
		// また FPTEMP にはソースを展開済みなので dst を書き込まない。
		// XXX 68040UM.pdf p.9-44 Table 9-16 Unimplemented Instruction には
		// E1 は always 1 とあるが、p.9-42 の E1 の説明のほうが正しそう。
		if (srcsize_id == SIZE_P) {
			fpu40.eflag = FPU40::E1;
		} else {
			fpu40.eflag = 0;
			memcpy(&fpu40.fptemp, &RegFP(dstnum), 12);
		}
		fpu40.stag = stag;
		fpu40.dtag = dtag;
		if (unimpl) {
			ExceptionFP(M68K::EXCEP_FP_UNIMPL);
		} else {
			ExceptionFP(M68K::EXCEP_FP_UNSUPP);
		}
		return false;
	}

	// ハードウェア処理可能。

	// 68040 では条件命令以外 (たぶん実質クラス0命令) を実行すると
	// NULL -> IDLE 状態になる。68040UM.pdf, p9-39
	fpu_state = FPU_STATE_IDLE;

	return true;
}

// FMOVECR
void
MPU680x0Device::fpu_op_fmovecr()
{
	if (__predict_false(GetFPUType().Is6888x() == false)) {
		if (GetFPUType().Is4060LC()) {
			ExceptionFPLC(0);
		} else {
			ExceptionFP(M68K::EXCEP_FP_UNIMPL);
		}
		return;
	}

	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));
	fpu_upd_fpsr(&fe, &fe.fe_f3);
	fpu_upd_excp(&fe);
}

// FMOVE FPn,<ea>
void
MPU680x0Device::fpu_op_fmove_to_mem()
{
	// サイズごとに分岐
	uint size = (ir2 >> 10) & 7;
	switch (size) {
	 case SIZE_B:
		fpu_op_fmove_b_mem();
		break;
	 case SIZE_W:
		fpu_op_fmove_w_mem();
		break;
	 case SIZE_L:
		fpu_op_fmove_l_mem();
		break;
	 case SIZE_S:
		fpu_op_fmove_s_mem();
		break;
	 case SIZE_D:
		fpu_op_fmove_d_mem();
		break;
	 case SIZE_X:
		fpu_op_fmove_x_mem();
		break;
	 case SIZE_P:
	 case 7:
		fpu_op_fmove_p_mem();
		break;
	}
}

// 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;

	if (__predict_false((mr >> 3) == 1 || mr >= 072)) {	// An, PC, #imm
		fpu_op_illg2();
		return;
	}
	if (__predict_false(GetFPUType().Is4060LC())) {
		ea = cea_fpulc(1);
		ExceptionFPLC(ea);
		return;
	}

	fpu_explode(&fe, &fe.fe_f3, FTYPE_EXT, &RegFP(srcnum));
	fpu_implode(&fe, &fe.fe_f3, FTYPE_LNG, &data);
	// 入力値は long か、long で表現できなければ 0x80000000 が返ってくるが、
	// ここはバイトへの変換なので表現できなければ 0x80 にする。
	if ((int32)data < INT8_MIN || (int32)data > INT8_MAX) {
		data = 0x80;
		RegFPSR |= FPSR_OPERR | FPSR_AIOP;
	}

	if (mr < 8) {	// Dn
		CYCLE3(fmove_i_fp_dn);
		reg.D[mr] &= 0xffffff00;
		reg.D[mr] |= (uint8)data;
	} else {
		CYCLE3(fmove_i_fp_mem);
		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;

	if (__predict_false((mr >> 3) == 1 || mr >= 072)) {	// An, PC, #imm
		fpu_op_illg2();
		return;
	}
	if (__predict_false(GetFPUType().Is4060LC())) {
		ea = cea_fpulc(2);
		ExceptionFPLC(ea);
		return;
	}

	fpu_explode(&fe, &fe.fe_f3, FTYPE_EXT, &RegFP(srcnum));
	fpu_implode(&fe, &fe.fe_f3, FTYPE_LNG, &data);
	// 入力値は long か、long で表現できなければ 0x80000000 が返ってくるが、
	// ここはワードへの変換なので表現できなければ 0x8000 にする。
	if ((int32)data < INT16_MIN || (int32)data > INT16_MAX) {
		data = 0x8000;
		RegFPSR |= FPSR_OPERR | FPSR_AIOP;
	}

	if (mr < 8) {	// Dn
		CYCLE3(fmove_i_fp_dn);
		reg.D[mr] &= 0xffff0000;
		reg.D[mr] |= (uint16)data;
	} else {
		CYCLE3(fmove_i_fp_mem);
		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;

	if (__predict_false((mr >> 3) == 1 || mr >= 072)) {	// An, PC, #imm
		fpu_op_illg2();
		return;
	}
	if (__predict_false(GetFPUType().Is4060LC())) {
		ea = cea_fpulc(4);
		ExceptionFPLC(ea);
		return;
	}

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

	if (mr < 8) {	// Dn
		CYCLE3(fmove_i_fp_dn);
		reg.D[mr] = data;
	} else {
		CYCLE3(fmove_i_fp_mem);
		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;

	if (__predict_false((mr >> 3) == 1 || mr >= 072)) {	// An, PC, #imm
		fpu_op_illg2();
		return;
	}
	if (__predict_false(GetFPUType().Is4060LC())) {
		ea = cea_fpulc(4);
		ExceptionFPLC(ea);
		return;
	}

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

	// 変換時に検出した UNFL, OVFL だけ外部レジスタにコピー。
	fe.fe_fpframe->fpf_fpsr &= ~(FPSR_UNFL | FPSR_OVFL);
	fe.fe_fpframe->fpf_fpsr |= fe.fe_fpsr & (FPSR_UNFL | FPSR_OVFL);
	// アクルードバイトは更新。
	fpu_upd_excp(&fe);

	if (mr < 8) {	// Dn
		CYCLE3(fmove_s_fp_dn);
		reg.D[mr] = data;
	} else {
		CYCLE3(fmove_s_fp_mem);
		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()
{
	uint mr = ir & 077;
	uint srcnum = (ir2 >> 7) & 7;
	uint32 ea;
	uint32 data[2];

	if (__predict_false(mr <= 017 || mr >= 072)) {	// Dn, An, PC, #imm
		fpu_op_illg2();
		return;
	}
	if (__predict_false(GetFPUType().Is4060LC())) {
		ea = cea_fpulc(8);
		ExceptionFPLC(ea);
		return;
	}

	CYCLE3(fmove_d_fp_ea);

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

	// 変換時に検出した UNFL, OVFL だけ外部レジスタにコピー。
	fe.fe_fpframe->fpf_fpsr &= ~(FPSR_UNFL | FPSR_OVFL);
	fe.fe_fpframe->fpf_fpsr |= fe.fe_fpsr & (FPSR_UNFL | FPSR_OVFL);
	// アクルードバイトは更新。
	fpu_upd_excp(&fe);

	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()
{
	uint mr = ir & 077;
	uint srcnum = (ir2 >> 7) & 7;
	uint32 ea;
	uint32 data[3];

	if (__predict_false(mr <= 017 || mr >= 072)) {	// Dn, An, PC, #imm
		fpu_op_illg2();
		return;
	}
	if (__predict_false(GetFPUType().Is4060LC())) {
		ea = cea_fpulc(12);
		ExceptionFPLC(ea);
		return;
	}

	CYCLE3(fmove_x_fp_ea);

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

	// 変換時に検出した UNFL, OVFL だけ外部レジスタにコピー。
	fe.fe_fpframe->fpf_fpsr &= ~(FPSR_UNFL | FPSR_OVFL);
	fe.fe_fpframe->fpf_fpsr |= fe.fe_fpsr & (FPSR_UNFL | FPSR_OVFL);
	// アクルードバイトは更新。
	fpu_upd_excp(&fe);

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

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

	if (__predict_false(mr <= 017 || mr >= 072)) {	// Dn, An, PC, #imm
		fpu_op_illg2();
		return;
	}
	if (__predict_false(GetFPUType().Is6888x() == false)) {
		if (GetFPUType().Is4060LC()) {
			ea = cea_fpulc(12);
			ExceptionFPLC(ea);
		} else {
			ExceptionFP(M68K::EXCEP_FP_UNIMPL);
		}
		return;
	}

	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));
	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クロックだけ違う
	// ようだけどもう無視する。
	// 040 で複数レジスタのケースの記述が見当たらない。

	// 転送するレジスタ数。
	// nofpc の場合は現在の 68881 の実装では FPIAR が選択される。
	if (reglist == 0)
		reglist = 1;
	count = __builtin_popcount(reglist);

	if (mr < 8) {				// Dn
		CYCLE3(fmovem_rn_ctl);
		// 対データレジスタは転送数 1 のみ有効
		if (count > 1) {
			fpu_op_illg2();
			return;
		}
		if (__predict_false(GetFPUType().Is4060LC())) {
			ExceptionFPLC(0);
			return;
		}
		src[0] = reg.R[mr];

	} else if (mr < 16) {		// An
		CYCLE3(fmovem_rn_ctl);
		// 対アドレスレジスタは FPIAR のみ有効
		if (reglist != 1) {
			fpu_op_illg2();
			return;
		}
		if (__predict_false(GetFPUType().Is4060LC())) {
			ExceptionFPLC(0);
			return;
		}
		src[0] = reg.R[mr];

	} else if (mr < 0x3c) {		// 対メモリ
		CYCLE3(fmovem_mem_ctl);
		CYCLE(count * 6);

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

		if (__predict_false(GetFPUType().Is4060LC())) {
			ExceptionFPLC(ea);
			return;
		}

		for (i = 0; i < count; i++) {
			src[i] = read_4(ea);
			ea += 4;
		}

	} else if (mr == 0x3c) {	// #imm
		if (__predict_false(GetFPUType().Is4060LC())) {
			ExceptionFPLC(0);
			return;
		}

		CYCLE3(fmovem_imm_ctl);
		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
	//
	// 040 で複数レジスタのケースの記述が見当たらない。

	// 転送するレジスタ数。
	// nofpc の場合は現在の 68881 の実装では FPIAR が選択される。
	if (reglist == 0)
		reglist = 1;
	count = __builtin_popcount(reglist);

	if (mr < 8) {			// Dn
		CYCLE3(fmovem_ctl_rn);
		// 対データレジスタは転送数は 1 のみ有効
		if (count > 1) {
			fpu_op_illg2();
			return;
		}
		if (__predict_false(GetFPUType().Is4060LC())) {
			ExceptionFPLC(0);
			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
		CYCLE3(fmovem_ctl_rn);
		// 対アドレスレジスタは FPIAR のみ有効
		if (reglist != 1) {
			fpu_op_illg2();
			return;
		}
		if (__predict_false(GetFPUType().Is4060LC())) {
			ExceptionFPLC(0);
			return;
		}
		reg.R[mr] = RegFPIAR;
		return;
	}
	if (mr >= 0x3a) {		// #imm, d16(PC), (PC,IX)
		fpu_op_illg2();
		return;
	}

	// それ以外、対メモリの場合。
	// EA を取得して...
	ea = cea_fpu(count * 4);
	if (__predict_false(GetFPUType().Is4060LC())) {
		ExceptionFPLC(ea);
		return;
	}

	CYCLE3(fmovem_ctl_mem);
	CYCLE(count * 6);

	// 該当レジスタをメモリへ書き出し
	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);	// 第2ワードの mode が predec か
	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 000 ... 007:	// Dn
	 case 010 ... 017:	// An
	 case 040 ... 047:	// -(An)
	 case 074:			// #imm
	 case 075 ... 077:
		fpu_op_illg2();
		return;
	}

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

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

	if (__predict_false(GetFPUType().Is4060LC())) {
		ExceptionFPLC(ea);
		return;
	}

	if (predec) {
		// predec ならビット7から FP7..FP0 で、bit7 側から順に転送。
		for (i = 7; reglist; reglist <<=1, i--) {
			if ((int8)reglist < 0) {
				read_12(ea, &RegFP(i));
				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));
				ea += 12;
			}
		}
	}
	CYCLE3(fmovem_ea2reg_n) * 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 000 ... 007:	// Dn
	 case 010 ... 017:	// An
	 case 030 ... 037:	// (An)+
	 case 072:			// d16(PC)
	 case 073:			// d8(PC,IX)
	 case 074:			// #imm
	 case 075 ... 077:
		fpu_op_illg2();
		return;
	 case 040 ... 047:	// -(An)
		anpd = true;
		break;
	}

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

	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) {
		CYCLE3(fmovem_reg2ea_anpd);
		uint n = eanum(ir);
		save_reg_pd(n);

		// XXX この場合 ea はどこを指すのか?
		if (__predict_false(GetFPUType().Is4060LC())) {
			// Dn,An,#imm は弾いてあるので cea_fpu() と等価になる。
			ea = cea_fpu(12 * count);
			ExceptionFPLC(ea);
			return;
		}

		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));
				}
			}
		} 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));
				}
			}
		}
	} else {
		// -(An) 以外は EA を最初に一度計算しておく方法が使える
		ea = cea_fpu(12 * count);

		if (__predict_false(GetFPUType().Is4060LC())) {
			ExceptionFPLC(ea);
			return;
		}

		if (predec) {
			// predec ならビット7から FP7..FP0 で、bit7 側から順に転送。
			for (i = 7; reglist; reglist <<= 1, i--) {
				if ((int8)reglist < 0) {
					write_12(ea, &RegFP(i));
					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));
					ea += 12;
				}
			}
		}
	}
	CYCLE3(fmovem_reg2ea_n) * 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;

	// XXX 条件部に1ビット不当パターンがあるがどっちの判定が先か。
	if (__predict_false(GetFPUType().Is4060LC())) {
		ea = cea_fpulc(1);
		ExceptionFPLC(ea);
		return;
	}

	//                   	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
		CYCLE3(fscc_dn);
		reg.D[rr] &= 0xffffff00;
		reg.D[rr] |= data;
		return;

	 case 3:	// (An)+
	 case 4:	// -(An)
		// ここだけ他のメモリ系 EA とはクロックが違うし、
		// 条件の成立可否でも違う。
		if (data == 0x00) {
			CYCLE3(fscc_anp_z);
		} else {
			CYCLE3(fscc_anp_nz);
		}
		break;
	 case 7:
		if (mr == 0x38 || mr == 0x39) {	// Abs.W, Abs.L
		} else {
			fpu_op_illg2();
			return;
		}
		FALLTHROUGH;
	 default:
		CYCLE3(fscc_mem);
		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()
{
	// XXX 条件部に1ビット不当パターンがあるがどっちの判定が先か。
	if (__predict_false(GetFPUType().Is4060LC())) {
		ExceptionFPLC(0);
		return;
	}

	uint rr = ir & 7;
	uint cond = ir2 & 0x3f;

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

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

		if (data == 0xffff) {
			// ループ終了。disp を読み飛ばす
			CYCLE3(fdbcc_done);
			fetch_2();
		} else {
			// 計算に使う PC はディスプレースメントのあるアドレス。
			CYCLE3(fdbcc_jump);
			uint32 origin = reg.pc;
			int32 disp = (int32)(int16)fetch_2();
			Jump(origin + disp);
		}
	}
}

// FTRAPcc.W #<data>
void
MPU680x0Device::fpu_op_ftrapcc_w()
{
	// XXX 条件部に1ビット不当パターンがあるがどっちの判定が先か。
	if (__predict_false(GetFPUType().Is4060LC())) {
		ExceptionFPLC(0);
		return;
	}

	uint cond = ir2 & 0x3f;

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

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

	if (test_cc(&fe, cond) == -1) {
		CYCLE3(ftrapccw_excep);
		Exception(M68K::EXCEP_TRAPV);
	} else {
		CYCLE3(ftrapccw_nop);
	}
}

// FTRAPcc.L #<data>
void
MPU680x0Device::fpu_op_ftrapcc_l()
{
	// XXX 条件部に1ビット不当パターンがあるがどっちの判定が先か。
	if (__predict_false(GetFPUType().Is4060LC())) {
		ExceptionFPLC(0);
		return;
	}

	uint cond = ir2 & 0x3f;

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

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

	if (test_cc(&fe, cond) == -1) {
		CYCLE3(ftrapccl_excep);
		Exception(M68K::EXCEP_TRAPV);
	} else {
		CYCLE3(ftrapccl_nop);
	}
}

// FTRAPcc
void
MPU680x0Device::fpu_op_ftrapcc()
{
	// XXX 条件部に1ビット不当パターンがあるがどっちの判定が先か。
	if (__predict_false(GetFPUType().Is4060LC())) {
		ExceptionFPLC(0);
		return;
	}

	uint cond = ir2 & 0x3f;

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

	// 即値なし。

	if (test_cc(&fe, cond) == -1) {
		CYCLE3(ftrapcc_excep);
		Exception(M68K::EXCEP_TRAPV);
	} else {
		CYCLE3(ftrapcc_nop);
	}
}

// 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()
{
	// XXX 条件部に1ビット不当パターンがあるがどっちの判定が先か。
	if (__predict_false(GetFPUType().Is4060LC())) {
		ExceptionFPLC(0);
		return;
	}

	uint cond = ir & 0x3f;

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

// FBcc.L <label>
// 1111 ccc 01S CCCCCC | <32bit disp>
// S=1: 32bit disp
void
MPU680x0Device::fpu_op_fbcc_l()
{
	// XXX 条件部に1ビット不当パターンがあるがどっちの判定が先か。
	if (__predict_false(GetFPUType().Is4060LC())) {
		ExceptionFPLC(0);
		return;
	}

	uint cond = ir & 0x3f;

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

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

// FSAVE のアイドルフレーム (68881)
/*static*/ const uint32
MPU680x0Device::fsave_frame_idle_68881[] = {
	0x1f180000,
	0x0000ffff,
	0,
	0,
	0,
	0,
	0x70000000,
};

// FSAVE のアイドルフレーム (68040)
/*static*/ const uint32
MPU680x0Device::fsave_frame_idle_68040[] = {
	0x41000000,
};

// FSAVE
// スーパーバイザモードは判定済み
void
MPU680x0Device::fpu_op_fsave()
{
	uint32 ea;
	std::array<uint32, 13> frame_noimpl;
	const uint32 *frame;
	int framelen;

	// フレームを選択。
	switch (fpu_state) {
	 case FPU_STATE_NULL:
		// NULL フレーム
		CYCLE3(fsave_null);
		frame = fsave_frame_null;
		framelen = countof(fsave_frame_null);
		break;
	 case FPU_STATE_IDLE:
	 case FPU_STATE_BUSY:	// 実装してないけどとりあえず。
		// IDLE フレーム
		CYCLE3(fsave_idle);	// XXX これは最小値らしいけど
		if (fpu_type.Is4060FPU()) {
			frame = fsave_frame_idle_68040;
			framelen = countof(fsave_frame_idle_68040);
		} else {
			frame = fsave_frame_idle_68881;
			framelen = countof(fsave_frame_idle_68881);
		}
		break;
	 case FPU_STATE_NOIMPL:
		// 68040 で浮動小数点未実装命令を実行した。
		fpu_op_fsave_frame_noimpl(frame_noimpl);
		frame = frame_noimpl.data();
		framelen = frame_noimpl.size();
		break;
	 default:
		PANIC("fpu_state=%u corrupted", fpu_state);
	}

	switch (ir & 077) {
	 case 020 ... 027:	// (An)
	 case 040 ... 047:	// -(An)
	 case 050 ... 057:	// d16(An)
	 case 060 ... 067:	// d8(An,IX)
	 case 070 ... 071:	// Abs.[WL]
		ea = cea_fpu(framelen * 4);
		break;
	 default:
		fpu_op_illg();
		return;
	}

	// XXX 実機がどう動くか分からないが、とりあえずここでは
	// LC なら fpu_state は NULL から変化しないのでフレーム長 4 として動く。
	if (__predict_false(GetFPUType().Is4060LC())) {
		ExceptionFPLC(ea);
		return;
	}

	// -(An) もそれ以外もどちらも、まず先頭フレームを書き出す。
	// この時点で ea は先頭フレームの位置を指している。
	write_4(ea, frame[0]);

	// -(An) もそれ以外もどちらも、
	// 残りはプリデクリメントのように高位側から順に書き出す。
	ea += framelen * 4;
	for (int i = framelen - 1; i > 0; i--) {
		ea -= 4;
		write_4(ea, frame[i]);
	}
}

// FRESTORE
// スーパーバイザモードは判定済み
void
MPU680x0Device::fpu_op_frestore()
{
	uint mm = eamode(ir);
	uint rr = eanum(ir);
	uint32 ea;
	uint32 fmtword;
	uint32 ver_len;
	uint32 ver;
	uint32 len;
	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;
	}

	if (__predict_false(GetFPUType().Is4060LC())) {
		ExceptionFPLC(ea);
		return;
	}

	// 先頭の1ロングワードを読んでみる。
	// これがフォーマットワード $VVLL'xxxx で、VV がバージョン(8bit)、
	// LL が(自身を含まないこれ以降の) バイト数 (8bit) を示している。 
	// 68881 本では下位ワードは Reserved($0000) となっているが、
	// 68040 本では Reserved のみで値の言及はない。
	//
	// 68881:
	//	NULL	$0000'0000
	//	IDLE	$1f18'0000 (以降  6 ロングワード)
	//	BUSY	$1fb4'0000 (以降 45 ロングワード) (*)
	//
	// 68040:
	//	NULL	$0000'xxxx
	//	IDLE	$4100'xxxx
	//	BUSY	$4160'xxxx (以降 24 ロングワード) (*)
	//	UNIMP	$4130'xxxx (以降 12 ロングワード)
	//
	// *: BUSY フレームは現状の FSAVE が出力しないので来ないはず。

	fmtword = read_4(ea);
	ea += 4;

	ver_len = fmtword >> 16;
	ver = ver_len >> 8;
	len = ver_len & 0xff;

	if (GetFPUType().Is4060FPU()) {
		if (ver == 0) {
			// NULL フレーム。

			// NULL フレームのリストアは FPU の状態をリセットする。
			CYCLE3(frestore_null);
			ResetFPU(false);
			goto done;
		} else if (ver_len == 0x4100) {
			// IDLE フレーム。

			// 現状何も実装していないので、状態を戻して読み捨てるだけ。
			CYCLE3(frestore_idle);
			fpu_state = FPU_STATE_IDLE;
		} else if (ver_len == 0x4130) {
			// 浮動小数点未実装命令フレーム。
			// どうなる?
			putlog(0, "FRESTORE: Unsupported UNIMP frame");
		} else if (ver_len == 0x4160) {
			// BUSY フレーム。
			putlog(0, "FRESTORE: Unsupported BUSY frame");
			if (0) {
				putlog(0, "[+00] %08x", fmtword);
				for (int offset = 4; offset < len; offset += 4) {
					uint32 m = read_4(ea + offset);
					putlog(0, "[+%02x] %08x", offset, m);
				}
			}
		} else {
			Exception(M68K::EXCEP_FORMAT);
			goto done;
		}
	} else /* 68881 */ {
		if (ver == 0) {
			// NULL フレーム。
			// 長さフィールドは見ない。(MC68881UM.pdf 6.4.2.1)

			// NULL フレームのリストアは FPU の状態をリセットする。
			CYCLE3(frestore_null);
			ResetFPU(false);
			goto done;
		} else if (ver_len == 0x1f18) {
			// IDLE フレーム。

			// 現状何も実装していないので、状態を戻して読み捨てるだけ。
			CYCLE3(frestore_idle);
			fpu_state = FPU_STATE_IDLE;
		} else {
			if (ver_len == 0x1fb4) {
				putlog(0, "FRESTORE: Unsupported BUSY frame");
			}
			Exception(M68K::EXCEP_FORMAT);
			goto done;
		}
	}

	// とにかく残りを全部読み捨てる。
	len /= 4;
	for (int i = 0; i < len; i++) {
		read_4(ea);
		ea += 4;
	}

 done:
	// (An)+ なら更新。
	// フォーマットワードを読んだところでエラーが起きたら +4 だけ更新される?
	if (postinc) {
		reg.A[rr] = ea;
	}
}

// 現在の fpu40 の状態から
// 68040 の浮動小数点未実装命令フレームを作成して frame に書き出す。
void
MPU680x0Device::fpu_op_fsave_frame_noimpl(std::array<uint32, 13>& frame)
{
	uint32 cmd1;
	uint32 reg3b;
	uint32 data6;

	// CMDREG1B 形式の Ry+CMD1 から CMDREG3B 形式の Ry+CMD3 を作る。
	cmd1 = fpu40.reg1b & 0x3ff;
	reg3b = (cmd1 & 0x3c3) | ((cmd1 >> 1) & 0x1c) | ((cmd1 << 3) & 0x20);

	data6 = fpu40.eflag;
	if (fpu40.post) {
		data6 |= 0x0010'0000;
	}

	frame[0] = 0x41300000;
	frame[1] = reg3b << 16;
	frame[2] = 0;
	frame[3] = (uint32)fpu40.stag << 29;
	frame[4] = fpu40.reg1b << 16;
	frame[5] = (uint32)fpu40.dtag << 29;
	frame[6] = data6;
	memcpy(&frame[7], fpu40.fptemp, 12);
	memcpy(&frame[10], fpu40.etemp, 12);

	// モニタ表示用。
	fpu40.enable = false;
}

// 浮動小数点系の例外。
// fpu40 の stag, dtag, etemp, fptemp, eflag を必要に応じてセットしてから
// 呼ぶこと。
void
MPU680x0Device::ExceptionFP(uint vector)
{
	fpu_state = FPU_STATE_NOIMPL;

	// CMDREG1B は実質 2ワード目のことのようだ。
	// FMOVECR のケースはドキュメントに見当たらないが、FPSP を読む限り
	// CMDREG1B はここでも FMOVECR の第2ワードそのままのようだ。
	// FSQRT 用のハックで cmd(offset) の $04 を $05 に書き換えるが、FPSP の
	// FMOVECR はどちらも 0.0 を返すので、書き換えてしまっても問題ないはず?
	uint32 reg1b = ir2;
	if ((reg1b & 0x3f) == 0x04) {
		// FSQRT だけマッピングが違う。
		reg1b++;
	}
	fpu40.reg1b = reg1b;
	fpu40.post = false;
	fpu40.enable = true;

	// この例外を起こした命令のアドレスは FPIAR に置く。
	reg.fpframe.fpf_fpiar = ppc;

	// EXCEP_FP_UNIMPL (未実装命令例外) はベクタが F ラインと同じで、
	// フレームフォーマットが違うので、定数値に細工がしてある。
	const uint32 info = BranchHistory_m680x0::InfoException(vector & 0xff);
	ExceptionGeneric(vector, info);
}

// CMDREG1B からコマンド名を返す。
// FSQRT 命令の第2ワードの cmd 部分は $04 だが CMDREG1B では $05 になる。
/*static*/ std::string
MPU68040Device::GetReg1BName(uint32 opclass, uint32 sz, uint32 cmd)
{
	std::string name;

	if (opclass != 0 && opclass != 2) {
		return "";
	}

	if (sz == 7) {
		return "fmovecr";
	}

	if (__predict_false(0x30 <= cmd && cmd < 0x38)) {
		name = "fsincos";
		name += '0' + (cmd - 0x30);
		return name;
	}

	if (__predict_false(cmd == 0x04)) {
		// 来ないはずだけど一応。
		return "fsqrt(4)";
	}

	if (__predict_false(cmd == 0x05)) {
		cmd = 0x04;
	}
	name = m680x0disasm::make_fpgen(cmd);
	return name;
}

/*static*/ std::string
MPU68040Device::GetFPUTagName(int32 tag)
{
	const char * const tagnames[] = {
		"(Normal)",
		"(Zero)",
		"(Inf)",
		"(NAN)",
		"(Unnormal)",
		"(Denormal)",
	};

	if (__predict_true(tag < countof(tagnames))) {
		return tagnames[tag];
	}
	if (tag == FPU40::TAG_DENORMAL_S) {
		return "(Denormal.S)";
	}
	if (tag == FPU40::TAG_DENORMAL_D) {
		return "(Denormal.D)";
	}
	if (tag == FPU40::TAG_DENORMAL_X) {
		return "(Denormal.X)";
	}
	if (tag == FPU40::TAG_UNNORMAL_X) {
		return "(Unnormal.X)";
	}
	if (tag == FPU40::TAG_NONE) {
		return "(NONE)";
	}
	return "";
}

// モニター更新 (040 内部情報)
void
MPU68040Device::MonitorScreenFPU40(TextScreen& screen, int y, const FPU40& tmp)
{
	// CMDREG1B:$xxxx OPCLASS=0 Rs=7 Rd=7 cmd=00(FTWOTOX) Flags: E3 E1 T
	// ETEMP :$01234567_01234567_01234567   STAG:1(Unnormal)
	// FPTEMP:

	TA attr = tmp.enable ? TA::Normal : TA::Disable;

	uint32 reg1b = tmp.reg1b;
	uint32 opclass = reg1b >> 13;
	uint32 rx = (reg1b >> 10) & 7;
	uint32 ry = (reg1b >>  7) & 7;
	uint32 cmd = reg1b & 0x7f;

	std::string cmdname = GetReg1BName(opclass, rx, cmd);
	if (__predict_true(cmdname.empty() == false)) {
		cmdname = '(' + cmdname + ')';
	}

	std::string stagname = GetFPUTagName(tmp.stag);
	std::string dtagname = GetFPUTagName(tmp.dtag);

	screen.Print(0, y, attr,
		"CMDREG1B:%04x OPCLASS=%u Rs=%u Rd=%u CMD=$%02x%s",
		reg1b, opclass, rx, ry, cmd, cmdname.c_str());
	screen.Puts(56, y, attr, "Flags:");
	screen.Puts(62, y, attr, (tmp.eflag & FPU40::E1) ? "E1" : "--");
	screen.Puts(65, y, attr, (tmp.eflag & FPU40::E3) ? "E3" : "--");
	screen.Puts(68, y, attr, (tmp.post) ? "T" : "-");
	y++;
	screen.Print(0, y, attr, "ETEMP: %08x_%08x_%08x   STAG:%d%s",
		tmp.etemp[0], tmp.etemp[1], tmp.etemp[2],
		(__predict_true(tmp.stag >= 0) ? (tmp.stag & 7) : tmp.stag),
		stagname.c_str());
	screen.Print(56, y, attr, "EA:%08x", tmp.ea);
	y++;
	screen.Print(0, y++, attr, "FPTEMP:%08x_%08x_%08x   DTAG:%d%s",
		tmp.fptemp[0], tmp.fptemp[1], tmp.fptemp[2],
		(__predict_true(tmp.dtag >= 0) ? (tmp.dtag & 7) : tmp.dtag),
		dtagname.c_str());
}

unix.superglobalmegacorp.com

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