 /*
  * UAE - The Un*x Amiga Emulator
  *
  * MC68881 emulation
  *
  * Copyright 1996 Herman ten Brugge
  */

#include <math.h>

#include "sysconfig.h"
#include "sysdeps.h"

#include "config.h"
#include "options.h"
#include "events.h"
#include "gui.h"
#include "memory.h"
#include "custom.h"
#include "newcpu.h"
#include "ersatz.h"
#include "readcpu.h"

#if CPU_LEVEL >= 2

#define	DEBUG_FPP	0

/* single   : S  8*E 23*F */
/* double   : S 11*E 52*F */
/* extended : S 15*E 64*F */
/* E = 0 & F = 0 -> 0 */
/* E = MAX & F = 0 -> Infin */
/* E = MAX & F # 0 -> NotANumber */
/* E = biased by 127 (single) ,1023 (double) ,16383 (extended) */

static __inline__ double
to_single(ULONG value)
{
	double frac;

	if ((value & 0x7fffffff) == 0) return(0.0);
	frac = (double)((value & 0x7fffff) | 0x800000) / 8388608.0;
	if (value & 0x80000000) frac = -frac;
	return(ldexp(frac, ((value >> 23) & 0xff) - 127));
}

static __inline__ ULONG
from_single(double src)
{
	int expon;
	ULONG tmp;
	double frac;

	if (src == 0.0) return 0;
	if (src < 0) { tmp = 0x80000000 ; src = -src; }
	else { tmp = 0; }
	frac = frexp(src,&expon);
	frac += 0.5 / 16777216.0;
	if (frac >= 1.0) { frac /= 2.0; expon++; }
	return (tmp | (((expon + 127 - 1) & 0xff) << 23) |
		(((int)(frac * 16777216.0)) & 0x7fffff));
}

static __inline__ double
to_exten(ULONG wrd1,ULONG wrd2,ULONG wrd3)
{
	double frac;

	if ((wrd1 & 0x7fff0000) == 0 && wrd2 == 0 && wrd3 == 0) return 0.0;
	frac = (double)wrd2 / 2147483648.0 +
	       (double)wrd3 / 9223372036854775808.0;
	if (wrd1 & 0x80000000) frac = -frac;
	return ldexp(frac, ((wrd1 >> 16) & 0x7fff) - 16383);
}

static __inline__ void
from_exten(double src,ULONG *wrd1, ULONG *wrd2, ULONG * wrd3)
{
	int expon;
	double frac;

	if (src == 0.0) { *wrd1 = 0; *wrd2 = 0; *wrd3 = 0; return; }
	if (src < 0) { *wrd1 = 0x80000000 ; src = -src; }
	else { *wrd1 = 0; }
	frac = frexp(src,&expon);
	frac += 0.5 / 18446744073709551616.0;
	if (frac >= 1.0) { frac /= 2.0; expon++; }
	*wrd1 |= (((expon + 16383 - 1) & 0x7fff) << 16);
	*wrd2 = (ULONG)(frac * 4294967296.0);
	*wrd3 = (ULONG)(frac * 18446744073709551616.0 - *wrd2 * 4294967296.0);
}

static __inline__ double
to_double(ULONG wrd1,ULONG wrd2)
{
	double frac;

	if ((wrd1 & 0x7fffffff) == 0 && wrd2 == 0) return 0.0;
	frac = (double)((wrd1 & 0xfffff) | 0x100000) / 1048576.0 +
	       (double)wrd2 / 4503599627370496.0;
	if (wrd1 & 0x80000000) frac = -frac;
	return ldexp(frac, ((wrd1 >> 20) & 0x7ff) - 1023);
}

static __inline__ void
from_double(double src,ULONG *wrd1, ULONG *wrd2)
{
	int expon;
	int tmp;
	double frac;

	if (src == 0.0) { *wrd1 = 0; *wrd2 = 0; return; }
	if (src < 0) { *wrd1 = 0x80000000 ; src = -src; }
	else { *wrd1 = 0; }
	frac = frexp(src,&expon);
	frac += 0.5 / 9007199254740992.0;
	if (frac >= 1.0) { frac /= 2.0; expon++; }
	tmp = (ULONG)(frac * 2097152.0);
	*wrd1 |= (((expon + 1023 - 1) & 0x7ff) << 20) | (tmp & 0xfffff);
	*wrd2 = (ULONG)(frac * 9007199254740992.0 - tmp * 4294967296.0);
}

static __inline__ double
to_pack(ULONG wrd1,ULONG wrd2,ULONG wrd3)
{
	double d;
	char *cp;
	char str[100];

	cp = str;
	if (wrd1 & 0x80000000) *cp++ = '-';
	*cp++ = (wrd1 & 0xf) + '0';
	*cp++ = '.';
	*cp++ = ((wrd2 >> 28) & 0xf) + '0';
	*cp++ = ((wrd2 >> 24) & 0xf) + '0';
	*cp++ = ((wrd2 >> 20) & 0xf) + '0';
	*cp++ = ((wrd2 >> 16) & 0xf) + '0';
	*cp++ = ((wrd2 >> 12) & 0xf) + '0';
	*cp++ = ((wrd2 >> 8) & 0xf) + '0';
	*cp++ = ((wrd2 >> 4) & 0xf) + '0';
	*cp++ = ((wrd2 >> 0) & 0xf) + '0';
	*cp++ = ((wrd3 >> 28) & 0xf) + '0';
	*cp++ = ((wrd3 >> 24) & 0xf) + '0';
	*cp++ = ((wrd3 >> 20) & 0xf) + '0';
	*cp++ = ((wrd3 >> 16) & 0xf) + '0';
	*cp++ = ((wrd3 >> 12) & 0xf) + '0';
	*cp++ = ((wrd3 >> 8) & 0xf) + '0';
	*cp++ = ((wrd3 >> 4) & 0xf) + '0';
	*cp++ = ((wrd3 >> 0) & 0xf) + '0';
	*cp++ = 'E';
	if (wrd1 & 0x40000000) *cp++ = '-';
	*cp++ = ((wrd1 >> 24) & 0xf) + '0';
	*cp++ = ((wrd1 >> 20) & 0xf) + '0';
	*cp++ = ((wrd1 >> 16) & 0xf) + '0';
	*cp = 0;
	sscanf(str,"%le",&d);
	return d;
}

static __inline__ void
from_pack(double src,ULONG *wrd1, ULONG *wrd2, ULONG * wrd3)
{
	int i;
	int t;
	char *cp;
	char str[100];

	sprintf(str,"%.16e",src);
	cp = str;
	*wrd1 = *wrd2 = *wrd3 = 0;
	if (*cp == '-') { cp++ ; *wrd1 = 0x80000000; }
	if (*cp == '+') cp++;
	*wrd1 |= (*cp++ - '0');
	if (*cp == '.') cp++;
	for (i = 0 ; i < 8 ; i++) {
		*wrd2 <<= 4;
		if (*cp >= '0' && *cp <= '9')
			*wrd2 |= *cp++ - '0';
	}
	for (i = 0 ; i < 8 ; i++) {
		*wrd3 <<= 4;
		if (*cp >= '0' && *cp <= '9')
			*wrd3 |= *cp++ - '0';
	}
	if (*cp == 'e' || *cp == 'E') {
		cp++;
		if (*cp == '-') { cp++; *wrd1 |= 0x40000000; }
		if (*cp == '+') cp++;
		t = 0;
		for (i = 0 ; i < 3 ; i++) {
			if (*cp >= '0' && *cp <= '9')
				t = (t << 4) | (*cp++ - '0');
		}
		*wrd1 |= t << 16;
	}
}

static __inline__ int
get_fp_value(ULONG opcode, UWORD extra,double *src)
{
	int size;
	int mode;
	int reg;
	ULONG ad = 0;
	static int sz1[8] = { 4,4,12,12,2,8,1,0 };
	static int sz2[8] = { 4,4,12,12,2,8,2,0 };

	if ((extra & 0x4000) == 0) {
		*src = regs.fp[(extra >> 10) & 7];
		return 1;
	}
	mode = (opcode >> 3) & 7;
	reg = opcode & 7;
	size = (extra >> 10) & 7;
	switch (mode) {
	case 0:
		switch (size) {
		case 6: *src = (double)(BYTE)regs.d[reg];
			break;
		case 4: *src = (double)(WORD)regs.d[reg];
			break;
		case 0: *src = (double)(LONG)regs.d[reg];
			break;
		case 1: *src = to_single(regs.d[reg]);
			break;
		default:
			return 0;
		}
		return 1;
	case 1:
		return 0;
	case 2:
		ad = regs.a[reg];
		break;
	case 3:
		ad = regs.a[reg];
		regs.a[reg] += reg == 7 ? sz2[size] : sz1[size];
		break;
	case 4:
		regs.a[reg] -= reg == 7 ? sz2[size] : sz1[size];
		ad = regs.a[reg];
		break;
	case 5:
		ad = regs.a[reg] + (LONG)(WORD)nextiword();
		break;
	case 6:
		ad = get_disp_ea(regs.a[reg]);
		break;
	case 7:
		switch (reg) {
		case 0: ad = (LONG)(WORD)nextiword();
			break;
		case 1: ad = nextilong();
			break;
		case 2: ad = m68k_getpc();
			ad += (LONG)(WORD)nextiword();
			break;
		case 3: ad = get_disp_ea(m68k_getpc());
			break;
		case 4: ad = m68k_getpc();
			m68k_setpc(ad + sz2[size]);
			break;
		default:
			return 0;
		}
	}
	switch (size) {
	case 0: *src = (double)(LONG)get_long(ad);
		break;
	case 1: *src = to_single(get_long(ad));
		break;
	case 2:	{ ULONG wrd1,wrd2,wrd3;
		  wrd1 = get_long(ad);ad+=4;
		  wrd2 = get_long(ad);ad+=4;
		  wrd3 = get_long(ad);
		  *src = to_exten(wrd1,wrd2,wrd3);
		}
		break;
	case 3:	{ ULONG wrd1,wrd2,wrd3;
		  wrd1 = get_long(ad);ad+=4;
		  wrd2 = get_long(ad);ad+=4;
		  wrd3 = get_long(ad);
		  *src = to_pack(wrd1,wrd2,wrd3);
		}
		break;
	case 4: *src = (double)(WORD)get_word(ad);
		break;
	case 5:	{ ULONG wrd1,wrd2;
		  wrd1 = get_long(ad);ad+=4;
		  wrd2 = get_long(ad);
		  *src = to_double(wrd1,wrd2);
		}
		break;
	case 6: *src = (double)(BYTE)get_byte(ad);
		break;
	default:
		return 0;
	}
	return 1;
}

static __inline__ int
put_fp_value(double value, ULONG opcode, UWORD extra)
{
	int size;
	int mode;
	int reg;
	ULONG ad;
	static int sz1[8] = { 4,4,12,12,2,8,1,0 };
	static int sz2[8] = { 4,4,12,12,2,8,2,0 };

	if ((extra & 0x4000) == 0) {
		regs.fp[(extra >> 10) & 7] = value;
		return 1;
	}
	mode = (opcode >> 3) & 7;
	reg = opcode & 7;
	size = (extra >> 10) & 7;
	ad = -1;
	switch (mode) {
	case 0:
		switch (size) {
		case 6: regs.d[reg] = ((int)value & 0xff) |
				      (regs.d[reg] & ~0xff);
			break;
		case 4: regs.d[reg] = ((int)value & 0xffff) |
				      (regs.d[reg] & ~0xffff);
			break;
		case 0: regs.d[reg] = (int)value;
			break;
		case 1: regs.d[reg] = from_single(value);
			break;
		default:
			return 0;
		}
		return 1;
	case 1:
		return 0;
	case 2:
		ad = regs.a[reg];
		break;
	case 3:
		ad = regs.a[reg];
		regs.a[reg] += reg == 7 ? sz2[size] : sz1[size];
		break;
	case 4:
		regs.a[reg] -= reg == 7 ? sz2[size] : sz1[size];
		ad = regs.a[reg];
		break;
	case 5:
		ad = regs.a[reg] + (LONG)(WORD)nextiword();
		break;
	case 6:
		ad = get_disp_ea(regs.a[reg]);
		break;
	case 7:
		switch (reg) {
		case 0: ad = (LONG)(WORD)nextiword();
			break;
		case 1: ad = nextilong();
			break;
		case 2: ad = m68k_getpc();
			ad += (LONG)(WORD)nextiword();
			break;
		case 3: ad = get_disp_ea(m68k_getpc());
			break;
		case 4: ad = m68k_getpc();
			m68k_setpc(ad + sz2[size]);
			break;
		default:
			return 0;
		}
	}
	switch (size) {
	case 0: put_long(ad,(LONG)value);
		break;
	case 1: put_long(ad,from_single(value));
		break;
	case 2:	{ ULONG wrd1,wrd2,wrd3;
		  from_exten(value,&wrd1,&wrd2,&wrd3);
		  put_long(ad,wrd1);ad+=4;
		  put_long(ad,wrd2);ad+=4;
		  put_long(ad,wrd3);
		}
		break;
	case 3:	{ ULONG wrd1,wrd2,wrd3;
		  from_pack(value,&wrd1,&wrd2,&wrd3);
		  put_long(ad,wrd1);ad+=4;
		  put_long(ad,wrd2);ad+=4;
		  put_long(ad,wrd3);
		}
		break;
	case 4: put_word(ad,(WORD)value);
		break;
	case 5:	{ ULONG wrd1,wrd2;
		  from_double(value,&wrd1,&wrd2);
		  put_long(ad,wrd1);ad+=4;
		  put_long(ad,wrd2);
		}
		break;
	case 6: put_byte(ad,(BYTE)value);
		break;
	default:
		return 0;
	}
	return 1;
}

static __inline__ int
get_fp_ad(ULONG opcode,ULONG *ad)
{
	int mode;
	int reg;

	mode = (opcode >> 3) & 7;
	reg = opcode & 7;
	switch (mode) {
	case 0:
	case 1:
		return 0;
	case 2:
		*ad = regs.a[reg];
		break;
	case 3:
		*ad = regs.a[reg];
		break;
	case 4:
		*ad = regs.a[reg];
		break;
	case 5:
		*ad = regs.a[reg] + (LONG)(WORD)nextiword();
		break;
	case 6:
		*ad = get_disp_ea(regs.a[reg]);
		break;
	case 7:
		switch (reg) {
		case 0: *ad = (LONG)(WORD)nextiword();
			break;
		case 1: *ad = nextilong();
			break;
		case 2: *ad = m68k_getpc();
			*ad += (LONG)(WORD)nextiword();
			break;
		case 3: *ad = get_disp_ea(m68k_getpc());
			break;
		default:
			return 0;
		}
	}
	return 1;
}

static __inline__ int
fpp_cond(ULONG opcode, int contition)
{
	int N = (regs.fpsr & 0x8000000) != 0;
	int Z = (regs.fpsr & 0x4000000) != 0;
	/* int I = (regs.fpsr & 0x2000000) != 0; */
	int NotANumber = (regs.fpsr & 0x1000000) != 0;

	switch (contition) {
	case 0x00: return 0;
	case 0x01: return Z;
	case 0x02: return !(NotANumber || Z || N);
	case 0x03: return Z || !(NotANumber || N);
	case 0x04: return N && !(NotANumber || Z);
	case 0x05: return Z || (N && !NotANumber);
	case 0x06: return !(NotANumber || Z);
	case 0x07: return !NotANumber;
	case 0x08: return NotANumber;
	case 0x09: return NotANumber || Z;
	case 0x0a: return NotANumber || !(N || Z);
	case 0x0b: return NotANumber || Z || !N;
	case 0x0c: return NotANumber || (N && !Z);
	case 0x0d: return NotANumber || Z || N;
	case 0x0e: return !Z;
	case 0x0f: return 1;
	case 0x10: return 0;
	case 0x11: return Z;
	case 0x12: return !(NotANumber || Z || N);
	case 0x13: return Z || !(NotANumber || N);
	case 0x14: return N && !(NotANumber || Z);
	case 0x15: return Z || (N && !NotANumber);
	case 0x16: return !(NotANumber || Z);
	case 0x17: return !NotANumber;
	case 0x18: return NotANumber;
	case 0x19: return NotANumber || Z;
	case 0x1a: return NotANumber || !(N || Z);
	case 0x1b: return NotANumber || Z || !N;
	case 0x1c: return NotANumber || (Z && N);
	case 0x1d: return NotANumber || Z || N;
	case 0x1e: return !Z;
	case 0x1f: return 1;
	}
	return -1;
}

void
fdbcc_opp(ULONG opcode, UWORD extra)
{
	CPTR pc = (ULONG)m68k_getpc();
	LONG disp = (LONG)(WORD)nextiword();
	int cc;

#if DEBUG_FPP
	printf("fdbcc_opp at %08lx\n",m68k_getpc());fflush(stdout);
#endif
	cc = fpp_cond(opcode, extra & 0x3f);
	if (cc == -1) {
		m68k_setpc(pc-2);
		op_illg(opcode);
	}
	else if (!cc) {
		regs.d[opcode & 0x7] = (regs.d[opcode & 0x7] & ~0xffff) |
				       ((regs.d[opcode & 0x7]-1) & 0xffff);
		if ((regs.d[opcode & 0x7] & 0xffff) == 0xffff)
			m68k_setpc(pc + disp);
	}
}

void
fscc_opp(ULONG opcode, UWORD extra)
{
	ULONG ad;
	int cc;

#if DEBUG_FPP
	printf("fscc_opp at %08lx\n",m68k_getpc());fflush(stdout);
#endif
	cc = fpp_cond(opcode, extra & 0x3f);
	if (cc == -1) {
		m68k_setpc(m68k_getpc()-2);
		op_illg(opcode);
	}
	else if ((opcode & 0x38) == 0) {
		regs.d[opcode & 7] = (regs.d[opcode & 7] & ~0xff) |
						(cc ? 0xff : 0x00);
	}
	else {
		if (get_fp_ad(opcode,&ad) == 0) {
			m68k_setpc(m68k_getpc()-2);
			op_illg(opcode);
		}
		else
			put_byte(ad,cc ? 0xff : 0x00);
	}
}

void
ftrapcc_opp(ULONG opcode, CPTR oldpc)
{
	int cc;

#if DEBUG_FPP
	printf("ftrapcc_opp at %08lx\n",m68k_getpc());fflush(stdout);
#endif
	cc = fpp_cond(opcode, opcode & 0x3f);
	if (cc == -1) {
		m68k_setpc(oldpc);
		op_illg(opcode);
	}
	if (cc)
		Exception(7,oldpc-2);
}

void
fbcc_opp(ULONG opcode, CPTR pc, ULONG extra)
{
	int cc;

#if DEBUG_FPP
	printf("fbcc_opp at %08lx\n",m68k_getpc());fflush(stdout);
#endif
	cc = fpp_cond(opcode, opcode & 0x3f);
	if (cc == -1) {
		m68k_setpc(pc);
		op_illg(opcode);
	}
	else if (cc) {
		if ((opcode & 0x40) == 0)
			extra = (LONG)(WORD)extra;
		m68k_setpc(pc + extra);
	}
}

void
fsave_opp(ULONG opcode)
{
	ULONG ad;
	int incr = (opcode & 0x38) == 0x20 ? -1 : 1;
	int i;
	
#if DEBUG_FPP
	printf("fsave_opp at %08lx\n",m68k_getpc());fflush(stdout);
#endif
	if (get_fp_ad(opcode,&ad) == 0) {
		op_illg(opcode);
		return;
	}
	if (incr < 0) {
		ad -= 4;put_long(ad,0x70000000);
		for (i = 0 ; i < 5 ; i++) {
			ad -= 4;put_long(ad,0x00000000);
		}
		ad -= 4;put_long(ad,0x1f180000);
	}
	else {
		put_long(ad,0x1f180000); ad += 4;
		for (i = 0 ; i < 5 ; i++) {
			put_long(ad,0x00000000); ad += 4;
		}
		put_long(ad,0x70000000); ad += 4;
	}
	if ((opcode & 0x38) == 0x18) regs.a[opcode & 7] = ad;
	if ((opcode & 0x38) == 0x20) regs.a[opcode & 7] = ad;
}

void
frestore_opp(ULONG opcode)
{
	ULONG ad;
	ULONG d;
	int incr = (opcode & 0x38) == 0x20 ? -1 : 1;
	
#if DEBUG_FPP
	printf("frestore_opp at %08lx\n",m68k_getpc());fflush(stdout);
#endif
	if (get_fp_ad(opcode,&ad) == 0) {
		op_illg(opcode);
		return;
	}
	if (incr < 0) {
		ad -= 4; d = get_long(ad);
		if ((d & 0xff000000) != 0) {
			if ((d & 0x00ff0000) == 0x00180000)
				ad -= 6 * 4;
			else if ((d & 0x00ff0000) == 0x00380000)
				ad -= 14 * 4;
			else if ((d & 0x00ff0000) == 0x00b40000)
				ad -= 45 * 4;
		}
	}
	else {
		d = get_long(ad); ad += 4;
		if ((d & 0xff000000) != 0) {
			if ((d & 0x00ff0000) == 0x00180000)
				ad += 6 * 4;
			else if ((d & 0x00ff0000) == 0x00380000)
				ad += 14 * 4;
			else if ((d & 0x00ff0000) == 0x00b40000)
				ad += 45 * 4;
		}
	}
	if ((opcode & 0x38) == 0x18) regs.a[opcode & 7] = ad;
	if ((opcode & 0x38) == 0x20) regs.a[opcode & 7] = ad;
}

void
fpp_opp(ULONG opcode, UWORD extra)
{
	int reg;
	double src;

#if DEBUG_FPP
	printf("FPP %04lx %04x at %08lx\n",opcode & 0xffff,extra & 0xffff,
				 	  m68k_getpc()-4);fflush(stdout);
#endif
	switch ((extra >> 13) & 0x7) {
	case 3:
		if (put_fp_value(regs.fp[(extra >> 7) & 7], opcode, extra)== 0){
			m68k_setpc(m68k_getpc()-2);
			op_illg(opcode);
		}
		return;
	case 4:
	case 5:
		if ((opcode & 0x38) == 0) {
			if (extra & 0x2000) {
				if (extra & 0x1000)
					regs.d[opcode & 7] = regs.fpcr;
				if (extra & 0x0800)
					regs.d[opcode & 7] = regs.fpsr;
				if (extra & 0x0400)
					regs.d[opcode & 7] = regs.fpiar;
			}
			else {
				if (extra & 0x1000)
					regs.fpcr = regs.d[opcode & 7];
				if (extra & 0x0800)
					regs.fpsr = regs.d[opcode & 7];
				if (extra & 0x0400)
					regs.fpiar = regs.d[opcode & 7];
			}
		}
		else if ((opcode & 0x38) == 1) {
			if (extra & 0x2000) {
				if (extra & 0x1000)
					regs.a[opcode & 7] = regs.fpcr;
				if (extra & 0x0800)
					regs.a[opcode & 7] = regs.fpsr;
				if (extra & 0x0400)
					regs.a[opcode & 7] = regs.fpiar;
			}
			else {
				if (extra & 0x1000)
					regs.fpcr = regs.a[opcode & 7];
				if (extra & 0x0800)
					regs.fpsr = regs.a[opcode & 7];
				if (extra & 0x0400)
					regs.fpiar = regs.a[opcode & 7];
			}
		}
		else if ((opcode & 0x3f) == 0x3c) {
			if ((extra & 0x2000) == 0) {
				if (extra & 0x1000)
					regs.fpcr = nextilong();
				if (extra & 0x0800)
					regs.fpsr = nextilong();
				if (extra & 0x0400)
					regs.fpiar = nextilong();
			}
		}
		else if (extra & 0x2000) {
			/* FMOVEM FPP->memory */
			ULONG ad;
			int incr = 0;

			if (get_fp_ad(opcode,&ad) == 0) {
				m68k_setpc(m68k_getpc()-2);
				op_illg(opcode);
				return;
			}
			if ((opcode & 0x38) == 0x20) { 
				if (extra & 0x1000) incr += 4;
				if (extra & 0x0800) incr += 4;
				if (extra & 0x0400) incr += 4;
			}
			ad -= incr;
			if (extra & 0x1000) {
				put_long(ad,regs.fpcr);ad+=4;
			}
			if (extra & 0x0800) {
				put_long(ad,regs.fpsr);ad+=4;
			}
			if (extra & 0x0400) {
				put_long(ad,regs.fpiar);ad+=4;
			}
			ad -= incr;
			if ((opcode & 0x38) == 0x18) regs.a[opcode & 7] = ad;
			if ((opcode & 0x38) == 0x20) regs.a[opcode & 7] = ad;
		}
		else {
			/* FMOVEM memory->FPP */
			ULONG ad;

			if (get_fp_ad(opcode,&ad) == 0) {
				m68k_setpc(m68k_getpc()-2);
				op_illg(opcode);
				return;
			}
			ad = (opcode & 0x38) == 0x20 ? ad-12 : ad;
			if (extra & 0x1000) {
				regs.fpcr = get_long(ad);ad+=4;
			}
			if (extra & 0x0800) {
				regs.fpsr = get_long(ad);ad+=4;
			}
			if (extra & 0x0400) {
				regs.fpiar = get_long(ad);ad+=4;
			}
			if ((opcode & 0x38) == 0x18) regs.a[opcode & 7] = ad;
			if ((opcode & 0x38) == 0x20) regs.a[opcode & 7] = ad-12;
		}
		return;
	case 6:
	case 7:
		{
		    ULONG ad,list = 0;
		    int incr = 0;
		    if (extra & 0x2000) {
			/* FMOVEM FPP->memory */
			if (get_fp_ad(opcode,&ad) == 0) {
				m68k_setpc(m68k_getpc()-2);
				op_illg(opcode);
				return;
			}
			switch ((extra >> 11) & 3) {
			case 0:	/* static pred */
				list = extra & 0xff;
				incr = -1;
				break;
			case 1: /* dynamic pred */
				list = regs.d[(extra >> 4) & 3] & 0xff;
				incr = -1;
				break;
			case 2: /* static postinc */
				list = extra & 0xff;
				incr = 1;
				break;
			case 3: /* dynamic postinc */
				list = regs.d[(extra >> 4) & 3] & 0xff;
				incr = 1;
				break;
			}
			while (list) {
				ULONG wrd1,wrd2,wrd3;
				if (incr < 0) {
					from_exten(
					    regs.fp[fpp_movem_index2[list]],
					    &wrd1,&wrd2,&wrd3);
					ad-=4;put_long(ad,wrd3);
					ad-=4;put_long(ad,wrd2);
					ad-=4;put_long(ad,wrd1);
				}
				else {
					from_exten(
					    regs.fp[fpp_movem_index1[list]],
					    &wrd1,&wrd2,&wrd3);
					put_long(ad,wrd1);ad+=4;
					put_long(ad,wrd2);ad+=4;
					put_long(ad,wrd3);ad+=4;
				}
				list = fpp_movem_next[list];
			}
			if ((opcode & 0x38) == 0x18) regs.a[opcode & 7] = ad;
			if ((opcode & 0x38) == 0x20) regs.a[opcode & 7] = ad;
		    }
		    else {
			/* FMOVEM memory->FPP */
			if (get_fp_ad(opcode,&ad) == 0) {
				m68k_setpc(m68k_getpc()-2);
				op_illg(opcode);
				return;
			}
			switch ((extra >> 11) & 3) {
			case 0:	/* static pred */
				list = extra & 0xff;
				incr = -1;
				break;
			case 1: /* dynamic pred */
				list = regs.d[(extra >> 4) & 3] & 0xff;
				incr = -1;
				break;
			case 2: /* static postinc */
				list = extra & 0xff;
				incr = 1;
				break;
			case 3: /* dynamic postinc */
				list = regs.d[(extra >> 4) & 3] & 0xff;
				incr = 1;
				break;
			}
			while (list) {
				ULONG wrd1,wrd2,wrd3;
				if (incr < 0) {
					ad-=4;wrd3 = get_long(ad);
					ad-=4;wrd2 = get_long(ad);
					ad-=4;wrd1 = get_long(ad);
					regs.fp[fpp_movem_index2[list]] =
						to_exten(wrd1,wrd2,wrd3);
				}
				else {
					wrd1 = get_long(ad);ad+=4;
					wrd2 = get_long(ad);ad+=4;
					wrd3 = get_long(ad);ad+=4;
					regs.fp[fpp_movem_index1[list]] =
						to_exten(wrd1,wrd2,wrd3);
				}
				list = fpp_movem_next[list];
			}
			if ((opcode & 0x38) == 0x18) regs.a[opcode & 7] = ad;
			if ((opcode & 0x38) == 0x20) regs.a[opcode & 7] = ad;
		    }
		}
		return;
	case 0:
	case 2:
		reg = (extra >> 7) & 7;
		if ((extra & 0xfc00) == 0x5c00) {
			switch (extra & 0x7f) {
			case 0x00: regs.fp[reg] = 4.0*atan(1.0); break;
			case 0x0b: regs.fp[reg] = log10(2.0); break;
			case 0x0c: regs.fp[reg] = exp(1.0); break;
			case 0x0d: regs.fp[reg] = log(exp(1.0))/log(2.0); break;
			case 0x0e: regs.fp[reg] = log(exp(1.0))/log(10.0); break;
			case 0x0f: regs.fp[reg] = 0.0; break;
			case 0x30: regs.fp[reg] = log(2.0); break;
			case 0x31: regs.fp[reg] = log(10.0); break;
			case 0x32: regs.fp[reg] = 1.0e0; break;
			case 0x33: regs.fp[reg] = 1.0e1; break;
			case 0x34: regs.fp[reg] = 1.0e2; break;
			case 0x35: regs.fp[reg] = 1.0e4; break;
			case 0x36: regs.fp[reg] = 1.0e8; break;
			case 0x37: regs.fp[reg] = 1.0e16; break;
			case 0x38: regs.fp[reg] = 1.0e32; break;
			case 0x39: regs.fp[reg] = 1.0e64; break;
			case 0x3a: regs.fp[reg] = 1.0e128; break;
			case 0x3b: regs.fp[reg] = 1.0e256; break;
#if 0
			case 0x3c: regs.fp[reg] = 1.0e512; break;
			case 0x3d: regs.fp[reg] = 1.0e1024; break;
			case 0x3e: regs.fp[reg] = 1.0e2048; break;
			case 0x3f: regs.fp[reg] = 1.0e4096; break;
#endif
			default:
				m68k_setpc(m68k_getpc()-2);
				op_illg(opcode);
				break;
			}
			return;
		}
		if (get_fp_value(opcode, extra, &src) == 0) {
			m68k_setpc(m68k_getpc()-2);
			op_illg(opcode);
			return;
		}
		switch (extra & 0x7f) {
		case 0x00:	/* FMOVE */
			regs.fp[reg] = src;
			break;
		case 0x01:	/* FINT */
			regs.fp[reg] = (int)(src + 0.5);
			regs.fpsr = (regs.fp[reg] == 0 ? 0x4000000 : 0) |
				    (regs.fp[reg] < 0 ? 0x8000000 : 0);
			break;
		case 0x02:	/* FSINH */
			regs.fp[reg] = sinh(src);
			regs.fpsr = (regs.fp[reg] == 0 ? 0x4000000 : 0) |
				    (regs.fp[reg] < 0 ? 0x8000000 : 0);
			break;
		case 0x03:	/* FINTRZ */
			regs.fp[reg] = (int)src;
			regs.fpsr = (regs.fp[reg] == 0 ? 0x4000000 : 0) |
				    (regs.fp[reg] < 0 ? 0x8000000 : 0);
			break;
		case 0x04:	/* FSQRT */
			regs.fp[reg] = sqrt(src);
			regs.fpsr = (regs.fp[reg] == 0 ? 0x4000000 : 0) |
				    (regs.fp[reg] < 0 ? 0x8000000 : 0);
			break;
		case 0x06:	/* FLOGNP1 */
			regs.fp[reg] = log(src + 1.0);
			regs.fpsr = (regs.fp[reg] == 0 ? 0x4000000 : 0) |
				    (regs.fp[reg] < 0 ? 0x8000000 : 0);
			break;
		case 0x08:	/* FETOXM1 */
			regs.fp[reg] = exp(src) - 1.0;
			regs.fpsr = (regs.fp[reg] == 0 ? 0x4000000 : 0) |
				    (regs.fp[reg] < 0 ? 0x8000000 : 0);
			break;
		case 0x09:	/* FTANH */
			regs.fp[reg] = tanh(src);
			regs.fpsr = (regs.fp[reg] == 0 ? 0x4000000 : 0) |
				    (regs.fp[reg] < 0 ? 0x8000000 : 0);
			break;
		case 0x0a:	/* FATAN */
			regs.fp[reg] = atan(src);
			regs.fpsr = (regs.fp[reg] == 0 ? 0x4000000 : 0) |
				    (regs.fp[reg] < 0 ? 0x8000000 : 0);
			break;
		case 0x0c:	/* FASIN */
			regs.fp[reg] = asin(src);
			regs.fpsr = (regs.fp[reg] == 0 ? 0x4000000 : 0) |
				    (regs.fp[reg] < 0 ? 0x8000000 : 0);
			break;
		case 0x0d:	/* FATANH */
#ifdef __bebox__
			printf("atanh\n");
#else
			regs.fp[reg] = atanh(src);
#endif
			regs.fpsr = (regs.fp[reg] == 0 ? 0x4000000 : 0) |
				    (regs.fp[reg] < 0 ? 0x8000000 : 0);
			break;
		case 0x0e:	/* FSIN */
			regs.fp[reg] = sin(src);
			regs.fpsr = (regs.fp[reg] == 0 ? 0x4000000 : 0) |
				    (regs.fp[reg] < 0 ? 0x8000000 : 0);
			break;
		case 0x0f:	/* FTAN */
			regs.fp[reg] = tan(src);
			regs.fpsr = (regs.fp[reg] == 0 ? 0x4000000 : 0) |
				    (regs.fp[reg] < 0 ? 0x8000000 : 0);
			break;
		case 0x10:	/* FETOX */
			regs.fp[reg] = exp(src);
			regs.fpsr = (regs.fp[reg] == 0 ? 0x4000000 : 0) |
				    (regs.fp[reg] < 0 ? 0x8000000 : 0);
			break;
		case 0x11:	/* FTWOTOX */
			regs.fp[reg] = pow(2.0,src);
			regs.fpsr = (regs.fp[reg] == 0 ? 0x4000000 : 0) |
				    (regs.fp[reg] < 0 ? 0x8000000 : 0);
			break;
		case 0x12:	/* FTENTOX */
			regs.fp[reg] = pow(10.0,src);
			regs.fpsr = (regs.fp[reg] == 0 ? 0x4000000 : 0) |
				    (regs.fp[reg] < 0 ? 0x8000000 : 0);
			break;
		case 0x14:	/* FLOGN */
			regs.fp[reg] = log(src);
			regs.fpsr = (regs.fp[reg] == 0 ? 0x4000000 : 0) |
				    (regs.fp[reg] < 0 ? 0x8000000 : 0);
			break;
		case 0x15:	/* FLOG10 */
			regs.fp[reg] = log10(src);
			regs.fpsr = (regs.fp[reg] == 0 ? 0x4000000 : 0) |
				    (regs.fp[reg] < 0 ? 0x8000000 : 0);
			break;
		case 0x16:	/* FLOG2 */
			regs.fp[reg] = log(src) / log(2.0);
			regs.fpsr = (regs.fp[reg] == 0 ? 0x4000000 : 0) |
				    (regs.fp[reg] < 0 ? 0x8000000 : 0);
			break;
		case 0x18:	/* FABS */
			regs.fp[reg] = src < 0 ? -src : src;
			regs.fpsr = (regs.fp[reg] == 0 ? 0x4000000 : 0) |
				    (regs.fp[reg] < 0 ? 0x8000000 : 0);
			break;
		case 0x19:	/* FCOSH */
			regs.fp[reg] = cosh(src);
			regs.fpsr = (regs.fp[reg] == 0 ? 0x4000000 : 0) |
				    (regs.fp[reg] < 0 ? 0x8000000 : 0);
			break;
		case 0x1a:	/* FNEG */
			regs.fp[reg] = -src;
			regs.fpsr = (regs.fp[reg] == 0 ? 0x4000000 : 0) |
				    (regs.fp[reg] < 0 ? 0x8000000 : 0);
			break;
		case 0x1c:	/* FACOS */
			regs.fp[reg] = acos(src);
			regs.fpsr = (regs.fp[reg] == 0 ? 0x4000000 : 0) |
				    (regs.fp[reg] < 0 ? 0x8000000 : 0);
			break;
		case 0x1d:	/* FCOS */
			regs.fp[reg] = cos(src);
			regs.fpsr = (regs.fp[reg] == 0 ? 0x4000000 : 0) |
				    (regs.fp[reg] < 0 ? 0x8000000 : 0);
			break;
		case 0x1e:	/* FGETEXP */
			{ int expon;
			frexp(src,&expon);
			regs.fp[reg] = (double)(expon-1);
			regs.fpsr = (regs.fp[reg] == 0 ? 0x4000000 : 0) |
				    (regs.fp[reg] < 0 ? 0x8000000 : 0);
			}
			break;
		case 0x1f:	/* FGETMAN */
			{ int expon;
			regs.fp[reg] = frexp(src,&expon) * 2.0;
			regs.fpsr = (regs.fp[reg] == 0 ? 0x4000000 : 0) |
				    (regs.fp[reg] < 0 ? 0x8000000 : 0);
			}
			break;
		case 0x20:	/* FDIV */
			regs.fp[reg] /= src;
			regs.fpsr = (regs.fp[reg] == 0 ? 0x4000000 : 0) |
				    (regs.fp[reg] < 0 ? 0x8000000 : 0);
			break;
		case 0x21:	/* FMOD */
			regs.fp[reg] = regs.fp[reg] - 
				(double)((int)(regs.fp[reg] / src)) * src;
			regs.fpsr = (regs.fp[reg] == 0 ? 0x4000000 : 0) |
				    (regs.fp[reg] < 0 ? 0x8000000 : 0);
			break;
		case 0x22:	/* FADD */
			regs.fp[reg] += src;
			regs.fpsr = (regs.fp[reg] == 0 ? 0x4000000 : 0) |
				    (regs.fp[reg] < 0 ? 0x8000000 : 0);
			break;
		case 0x23:	/* FMUL */
			regs.fp[reg] *= src;
			regs.fpsr = (regs.fp[reg] == 0 ? 0x4000000 : 0) |
				    (regs.fp[reg] < 0 ? 0x8000000 : 0);
			break;
		case 0x24:	/* FSGLDIV */
			regs.fp[reg] /= src;
			regs.fpsr = (regs.fp[reg] == 0 ? 0x4000000 : 0) |
				    (regs.fp[reg] < 0 ? 0x8000000 : 0);
			break;
		case 0x25:	/* FREM */
			regs.fp[reg] = regs.fp[reg] - 
				(double)((int)(regs.fp[reg] / src + 0.5)) * src;
			regs.fpsr = (regs.fp[reg] == 0 ? 0x4000000 : 0) |
				    (regs.fp[reg] < 0 ? 0x8000000 : 0);
			break;
		case 0x26:	/* FSCALE */
			regs.fp[reg] *= exp(log(2.0)*src);
			regs.fpsr = (regs.fp[reg] == 0 ? 0x4000000 : 0) |
				    (regs.fp[reg] < 0 ? 0x8000000 : 0);
			break;
		case 0x27:	/* FSGLMUL */
			regs.fp[reg] *= src;
			regs.fpsr = (regs.fp[reg] == 0 ? 0x4000000 : 0) |
				    (regs.fp[reg] < 0 ? 0x8000000 : 0);
			break;
		case 0x28:	/* FSUB */
			regs.fp[reg] -= src;
			regs.fpsr = (regs.fp[reg] == 0 ? 0x4000000 : 0) |
				    (regs.fp[reg] < 0 ? 0x8000000 : 0);
			break;
		case 0x30:	/* FSINCOS */
		case 0x31:
		case 0x32:
		case 0x33:
		case 0x34:
		case 0x35:
		case 0x36:
		case 0x37:
			regs.fp[reg] = sin(src);
			regs.fp[extra & 7] = cos(src);
			regs.fpsr = (regs.fp[reg] == 0 ? 0x4000000 : 0) |
				    (regs.fp[reg] < 0 ? 0x8000000 : 0);
			break;
		case 0x38:	/* FCMP */
			{ double tmp = regs.fp[reg] - src;
			regs.fpsr = (tmp == 0 ? 0x4000000 : 0) |
				    (tmp < 0 ? 0x8000000 : 0);
			}
			break;
		case 0x3a:	/* FTST */
			regs.fpsr = (src == 0 ? 0x4000000 : 0) |
				    (src < 0 ? 0x8000000 : 0);
			break;
		default:
			m68k_setpc(m68k_getpc()-2);
			op_illg(opcode);
			break;
		}
		return;
	}
	m68k_setpc(m68k_getpc()-2);
	op_illg(opcode);
}

#endif
