/*  maverick.c -- Cirrus/DSP co-processor interface.
    Copyright (C) 2003-2020 Free Software Foundation, Inc.
    Contributed by Aldy Hernandez (aldyh@redhat.com).

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>. */

#include <assert.h>
#include "armdefs.h"
#include "ansidecl.h"
#include "armemu.h"
#include "maverick.h"

/*#define CIRRUS_DEBUG 1	*/
#if CIRRUS_DEBUG
#  define printfdbg printf
#else
#  define printfdbg printf_nothing
#endif

#define POS64(i) ( (~(i)) >> 63 )
#define NEG64(i) ( (i) >> 63 )

/* These variables are defined here and made extern in maverick.h for use
   in wrapper.c for now.
   Eventually the simulator should be made to handle any coprocessor at run
   time.  */
struct maverick_regs DSPregs[16];
union maverick_acc_regs DSPacc[4];
ARMword DSPsc;

#define DEST_REG	(BITS (12, 15))
#define SRC1_REG	(BITS (16, 19))
#define SRC2_REG	(BITS (0, 3))

static int lsw_int_index, msw_int_index;
static int lsw_float_index, msw_float_index;

static double mv_getRegDouble (int);
static long long mv_getReg64int (int);
static void mv_setRegDouble (int, double val);
static void mv_setReg64int (int, long long val);

static union
{
  double d;
  long long ll;
  int ints[2];
} reg_conv;

static void
printf_nothing (void * foo, ...)
{
}

static void
cirrus_not_implemented (char * insn)
{
  fprintf (stderr, "Cirrus instruction '%s' not implemented.\n", insn);
  fprintf (stderr, "aborting!\n");

  exit (1);
}

unsigned
DSPMRC4 (ARMul_State * state ATTRIBUTE_UNUSED,
	 unsigned      type  ATTRIBUTE_UNUSED,
	 ARMword       instr,
	 ARMword *     value)
{
  switch (BITS (5, 7))
    {
    case 0: /* cfmvrdl */
      /* Move lower half of a DF stored in a DSP reg into an Arm reg.  */
      printfdbg ("cfmvrdl\n");
      printfdbg ("\tlower half=0x%x\n", DSPregs[SRC1_REG].lower.i);
      printfdbg ("\tentire thing=%g\n", mv_getRegDouble (SRC1_REG));

      *value = (ARMword) DSPregs[SRC1_REG].lower.i;
      break;

    case 1: /* cfmvrdh */
      /* Move upper half of a DF stored in a DSP reg into an Arm reg.  */
      printfdbg ("cfmvrdh\n");
      printfdbg ("\tupper half=0x%x\n", DSPregs[SRC1_REG].upper.i);
      printfdbg ("\tentire thing=%g\n", mv_getRegDouble (SRC1_REG));

      *value = (ARMword) DSPregs[SRC1_REG].upper.i;
      break;

    case 2: /* cfmvrs */
      /* Move SF from upper half of a DSP register to an Arm register.  */
      *value = (ARMword) DSPregs[SRC1_REG].upper.i;
      printfdbg ("cfmvrs = mvf%d <-- %f\n",
		 SRC1_REG,
		 DSPregs[SRC1_REG].upper.f);
      break;

#ifdef doesnt_work
    case 4: /* cfcmps */
      {
	float a, b;
	int n, z, c, v;

	a = DSPregs[SRC1_REG].upper.f;
	b = DSPregs[SRC2_REG].upper.f;

	printfdbg ("cfcmps\n");
	printfdbg ("\tcomparing %f and %f\n", a, b);

	z = a == b;		/* zero */
	n = a != b;		/* negative */
	v = a > b;		/* overflow */
	c = 0;			/* carry */
	*value = (n << 31) | (z << 30) | (c << 29) | (v << 28);
	break;
      }

    case 5: /* cfcmpd */
      {
	double a, b;
	int n, z, c, v;

	a = mv_getRegDouble (SRC1_REG);
	b = mv_getRegDouble (SRC2_REG);

	printfdbg ("cfcmpd\n");
	printfdbg ("\tcomparing %g and %g\n", a, b);

	z = a == b;		/* zero */
	n = a != b;		/* negative */
	v = a > b;		/* overflow */
	c = 0;			/* carry */
	*value = (n << 31) | (z << 30) | (c << 29) | (v << 28);
	break;
      }
#else
      case 4: /* cfcmps */
        {
	  float a, b;
	  int n, z, c, v;

	  a = DSPregs[SRC1_REG].upper.f;
	  b = DSPregs[SRC2_REG].upper.f;

	  printfdbg ("cfcmps\n");
	  printfdbg ("\tcomparing %f and %f\n", a, b);

	  z = a == b;		/* zero */
	  n = a < b;		/* negative */
	  c = a > b;		/* carry */
	  v = 0;		/* fixme */
	  printfdbg ("\tz = %d, n = %d\n", z, n);
	  *value = (n << 31) | (z << 30) | (c << 29) | (v << 28);
	  break;
        }
	
      case 5: /* cfcmpd */
        {
	  double a, b;
	  int n, z, c, v;

	  a = mv_getRegDouble (SRC1_REG);
	  b = mv_getRegDouble (SRC2_REG);

	  printfdbg ("cfcmpd\n");
	  printfdbg ("\tcomparing %g and %g\n", a, b);

	  z = a == b;		/* zero */
	  n = a < b;		/* negative */
	  c = a > b;		/* carry */
	  v = 0;		/* fixme */
	  *value = (n << 31) | (z << 30) | (c << 29) | (v << 28);
	  break;
        }
#endif
    default:
      fprintf (stderr, "unknown opcode in DSPMRC4 0x%x\n", instr);
      cirrus_not_implemented ("unknown");
      break;
    }

  return ARMul_DONE;
}

unsigned
DSPMRC5 (ARMul_State * state ATTRIBUTE_UNUSED,
	 unsigned      type  ATTRIBUTE_UNUSED,
	 ARMword       instr,
	 ARMword *     value)
{
  switch (BITS (5, 7))
    {
    case 0: /* cfmvr64l */
      /* Move lower half of 64bit int from Cirrus to Arm.  */
      *value = (ARMword) DSPregs[SRC1_REG].lower.i;
      printfdbg ("cfmvr64l ARM_REG = mvfx%d <-- %d\n",
		 DEST_REG,
		 (int) *value);
      break;

    case 1: /* cfmvr64h */
      /* Move upper half of 64bit int from Cirrus to Arm.  */
      *value = (ARMword) DSPregs[SRC1_REG].upper.i;
      printfdbg ("cfmvr64h <-- %d\n", (int) *value);
      break;

    case 4: /* cfcmp32 */
      {
	int res;
	int n, z, c, v;
	unsigned int a, b;

	printfdbg ("cfcmp32 mvfx%d - mvfx%d\n",
		   SRC1_REG,
		   SRC2_REG);

	/* FIXME: see comment for cfcmps.  */
	a = DSPregs[SRC1_REG].lower.i;
	b = DSPregs[SRC2_REG].lower.i;

	res = DSPregs[SRC1_REG].lower.i - DSPregs[SRC2_REG].lower.i;
	/* zero */
	z = res == 0;
	/* negative */
	n = res < 0;
	/* overflow */
	v = SubOverflow (DSPregs[SRC1_REG].lower.i, DSPregs[SRC2_REG].lower.i,
			 res);
	/* carry */
	c = (NEG (a) && POS (b))
	  || (NEG (a) && POS (res))
	  || (POS (b) && POS (res));

	*value = (n << 31) | (z << 30) | (c << 29) | (v << 28);
	break;
      }

    case 5: /* cfcmp64 */
      {
	long long res;
	int n, z, c, v;
	unsigned long long a, b;

	printfdbg ("cfcmp64 mvdx%d - mvdx%d\n",
		   SRC1_REG,
		   SRC2_REG);

	/* fixme: see comment for cfcmps.  */

	a = mv_getReg64int (SRC1_REG);
	b = mv_getReg64int (SRC2_REG);

	res = mv_getReg64int (SRC1_REG) - mv_getReg64int (SRC2_REG);
	/* zero */
	z = res == 0;
	/* negative */
	n = res < 0;
	/* overflow */
	v = ((NEG64 (a) && POS64 (b) && POS64 (res))
	     || (POS64 (a) && NEG64 (b) && NEG64 (res)));
	/* carry */
	c =    (NEG64 (a) && POS64 (b))
	    || (NEG64 (a) && POS64 (res))
	    || (POS64 (b) && POS64 (res));

	*value = (n << 31) | (z << 30) | (c << 29) | (v << 28);
	break;
      }

    default:
      fprintf (stderr, "unknown opcode in DSPMRC5 0x%x\n", instr);
      cirrus_not_implemented ("unknown");
      break;
    }

  return ARMul_DONE;
}

unsigned
DSPMRC6 (ARMul_State * state ATTRIBUTE_UNUSED,
	 unsigned      type  ATTRIBUTE_UNUSED,
	 ARMword       instr,
	 ARMword *     value)
{
  switch (BITS (5, 7))
    {
    case 0: /* cfmval32 */
      cirrus_not_implemented ("cfmval32");
      break;

    case 1: /* cfmvam32 */
      cirrus_not_implemented ("cfmvam32");
      break;

    case 2: /* cfmvah32 */
      cirrus_not_implemented ("cfmvah32");
      break;

    case 3: /* cfmva32 */
      cirrus_not_implemented ("cfmva32");
      break;

    case 4: /* cfmva64 */
      cirrus_not_implemented ("cfmva64");
      break;

    case 5: /* cfmvsc32 */
      cirrus_not_implemented ("cfmvsc32");
      break;

    default:
      fprintf (stderr, "unknown opcode in DSPMRC6 0x%x\n", instr);
      cirrus_not_implemented ("unknown");
      break;
    }

  return ARMul_DONE;
}

unsigned
DSPMCR4 (ARMul_State * state,
	 unsigned      type ATTRIBUTE_UNUSED,
	 ARMword       instr,
	 ARMword       value)
{
  switch (BITS (5, 7))
    {
    case 0: /* cfmvdlr */
      /* Move the lower half of a DF value from an Arm register into
	 the lower half of a Cirrus register.  */
      printfdbg ("cfmvdlr <-- 0x%x\n", (int) value);
      DSPregs[SRC1_REG].lower.i = (int) value;
      break;

    case 1: /* cfmvdhr */
      /* Move the upper half of a DF value from an Arm register into
	 the upper half of a Cirrus register.  */
      printfdbg ("cfmvdhr <-- 0x%x\n", (int) value);
      DSPregs[SRC1_REG].upper.i = (int) value;
      break;

    case 2: /* cfmvsr */
      /* Move SF from Arm register into upper half of Cirrus register.  */
      printfdbg ("cfmvsr <-- 0x%x\n", (int) value);
      DSPregs[SRC1_REG].upper.i = (int) value;
      break;

    default:
      fprintf (stderr, "unknown opcode in DSPMCR4 0x%x\n", instr);
      cirrus_not_implemented ("unknown");
      break;
    }

  return ARMul_DONE;
}

unsigned
DSPMCR5 (ARMul_State * state,
	 unsigned      type   ATTRIBUTE_UNUSED,
	 ARMword       instr,
	 ARMword       value)
{
  union
  {
    int s;
    unsigned int us;
  } val;

  switch (BITS (5, 7))
    {
    case 0: /* cfmv64lr */
      /* Move lower half of a 64bit int from an ARM register into the
         lower half of a DSP register and sign extend it.  */
      printfdbg ("cfmv64lr mvdx%d <-- 0x%x\n", SRC1_REG, (int) value);
      DSPregs[SRC1_REG].lower.i = (int) value;
      break;

    case 1: /* cfmv64hr */
      /* Move upper half of a 64bit int from an ARM register into the
	 upper half of a DSP register.  */
      printfdbg ("cfmv64hr ARM_REG = mvfx%d <-- 0x%x\n",
		 SRC1_REG,
		 (int) value);
      DSPregs[SRC1_REG].upper.i = (int) value;
      break;

    case 2: /* cfrshl32 */
      printfdbg ("cfrshl32\n");
      val.us = value;
      if (val.s > 0)
	DSPregs[SRC2_REG].lower.i = DSPregs[SRC1_REG].lower.i << value;
      else
	DSPregs[SRC2_REG].lower.i = DSPregs[SRC1_REG].lower.i >> -value;
      break;

    case 3: /* cfrshl64 */
      printfdbg ("cfrshl64\n");
      val.us = value;
      if (val.s > 0)
	mv_setReg64int (SRC2_REG, mv_getReg64int (SRC1_REG) << value);
      else
	mv_setReg64int (SRC2_REG, mv_getReg64int (SRC1_REG) >> -value);
      break;

    default:
      fprintf (stderr, "unknown opcode in DSPMCR5 0x%x\n", instr);
      cirrus_not_implemented ("unknown");
      break;
    }

  return ARMul_DONE;
}

unsigned
DSPMCR6 (ARMul_State * state,
	 unsigned      type   ATTRIBUTE_UNUSED,
	 ARMword       instr,
	 ARMword       value)
{
  switch (BITS (5, 7))
    {
    case 0: /* cfmv32al */
      cirrus_not_implemented ("cfmv32al");
      break;

    case 1: /* cfmv32am */
      cirrus_not_implemented ("cfmv32am");
      break;

    case 2: /* cfmv32ah */
      cirrus_not_implemented ("cfmv32ah");
      break;

    case 3: /* cfmv32a */
      cirrus_not_implemented ("cfmv32a");
      break;

    case 4: /* cfmv64a */
      cirrus_not_implemented ("cfmv64a");
      break;

    case 5: /* cfmv32sc */
      cirrus_not_implemented ("cfmv32sc");
      break;

    default:
      fprintf (stderr, "unknown opcode in DSPMCR6 0x%x\n", instr);
      cirrus_not_implemented ("unknown");
      break;
    }

  return ARMul_DONE;
}

unsigned
DSPLDC4 (ARMul_State * state ATTRIBUTE_UNUSED,
	 unsigned      type,
	 ARMword       instr,
	 ARMword       data)
{
  static unsigned words;

  if (type != ARMul_DATA)
    {
      words = 0;
      return ARMul_DONE;
    }

  if (BIT (22))
    {				/* it's a long access, get two words */
      /* cfldrd */

      printfdbg ("cfldrd: %x (words = %d) (bigend = %d) DESTREG = %d\n",
		 data, words, state->bigendSig, DEST_REG);

      if (words == 0)
	{
	  if (state->bigendSig)
	    DSPregs[DEST_REG].upper.i = (int) data;
	  else
	    DSPregs[DEST_REG].lower.i = (int) data;
	}
      else
	{
	  if (state->bigendSig)
	    DSPregs[DEST_REG].lower.i = (int) data;
	  else
	    DSPregs[DEST_REG].upper.i = (int) data;
	}

      ++ words;

      if (words == 2)
	{
	  printfdbg ("\tmvd%d <-- mem = %g\n", DEST_REG,
		     mv_getRegDouble (DEST_REG));

	  return ARMul_DONE;
	}
      else
	return ARMul_INC;
    }
  else
    {
      /* Get just one word.  */

      /* cfldrs */
      printfdbg ("cfldrs\n");

      DSPregs[DEST_REG].upper.i = (int) data;

      printfdbg ("\tmvf%d <-- mem = %f\n", DEST_REG,
		 DSPregs[DEST_REG].upper.f);

      return ARMul_DONE;
    }
}

unsigned
DSPLDC5 (ARMul_State * state ATTRIBUTE_UNUSED,
	 unsigned      type,
	 ARMword       instr,
	 ARMword       data)
{
  static unsigned words;

  if (type != ARMul_DATA)
    {
      words = 0;
      return ARMul_DONE;
    }

  if (BIT (22))
    {
      /* It's a long access, get two words.  */

      /* cfldr64 */
      printfdbg ("cfldr64: %d\n", data);

      if (words == 0)
	{
	  if (state->bigendSig)
	    DSPregs[DEST_REG].upper.i = (int) data;
	  else
	    DSPregs[DEST_REG].lower.i = (int) data;
	}
      else
	{
	  if (state->bigendSig)
	    DSPregs[DEST_REG].lower.i = (int) data;
	  else
	    DSPregs[DEST_REG].upper.i = (int) data;
	}

      ++ words;

      if (words == 2)
	{
	  printfdbg ("\tmvdx%d <-- mem = %lld\n", DEST_REG,
		     mv_getReg64int (DEST_REG));

	  return ARMul_DONE;
	}
      else
	return ARMul_INC;
    }
  else
    {
      /* Get just one word.  */

      /* cfldr32 */
      printfdbg ("cfldr32 mvfx%d <-- %d\n", DEST_REG, (int) data);

      /* 32bit ints should be sign extended to 64bits when loaded.  */
      mv_setReg64int (DEST_REG, (long long) data);

      return ARMul_DONE;
    }
}

unsigned
DSPSTC4 (ARMul_State * state ATTRIBUTE_UNUSED,
	 unsigned      type,
	 ARMword       instr,
	 ARMword *     data)
{
  static unsigned words;

  if (type != ARMul_DATA)
    {
      words = 0;
      return ARMul_DONE;
    }

  if (BIT (22))
    {
      /* It's a long access, get two words.  */
      /* cfstrd */
      printfdbg ("cfstrd\n");

      if (words == 0)
	{
	  if (state->bigendSig)
	    *data = (ARMword) DSPregs[DEST_REG].upper.i;
	  else
	    *data = (ARMword) DSPregs[DEST_REG].lower.i;
	}
      else
	{
	  if (state->bigendSig)
	    *data = (ARMword) DSPregs[DEST_REG].lower.i;
	  else
	    *data = (ARMword) DSPregs[DEST_REG].upper.i;
	}

      ++ words;

      if (words == 2)
	{
	  printfdbg ("\tmem = mvd%d = %g\n", DEST_REG,
		     mv_getRegDouble (DEST_REG));

	  return ARMul_DONE;
	}
      else
	return ARMul_INC;
    }
  else
    {
      /* Get just one word.  */
      /* cfstrs */
      printfdbg ("cfstrs mvf%d <-- %f\n", DEST_REG,
		 DSPregs[DEST_REG].upper.f);

      *data = (ARMword) DSPregs[DEST_REG].upper.i;

      return ARMul_DONE;
    }
}

unsigned
DSPSTC5 (ARMul_State * state ATTRIBUTE_UNUSED,
	 unsigned      type,
	 ARMword       instr,
	 ARMword *     data)
{
  static unsigned words;

  if (type != ARMul_DATA)
    {
      words = 0;
      return ARMul_DONE;
    }

  if (BIT (22))
    {
      /* It's a long access, store two words.  */
      /* cfstr64 */
      printfdbg ("cfstr64\n");

      if (words == 0)
	{
	  if (state->bigendSig)
	    *data = (ARMword) DSPregs[DEST_REG].upper.i;
	  else
	    *data = (ARMword) DSPregs[DEST_REG].lower.i;
	}
      else
	{
	  if (state->bigendSig)
	    *data = (ARMword) DSPregs[DEST_REG].lower.i;
	  else
	    *data = (ARMword) DSPregs[DEST_REG].upper.i;
	}

      ++ words;

      if (words == 2)
	{
	  printfdbg ("\tmem = mvd%d = %lld\n", DEST_REG,
		     mv_getReg64int (DEST_REG));

	  return ARMul_DONE;
	}
      else
	return ARMul_INC;
    }
  else
    {
      /* Store just one word.  */
      /* cfstr32 */
      *data = (ARMword) DSPregs[DEST_REG].lower.i;

      printfdbg ("cfstr32 MEM = %d\n", (int) *data);

      return ARMul_DONE;
    }
}

unsigned
DSPCDP4 (ARMul_State * state,
	 unsigned      type,
	 ARMword       instr)
{
  int opcode2;

  opcode2 = BITS (5,7);

  switch (BITS (20,21))
    {
    case 0:
      switch (opcode2)
	{
	case 0: /* cfcpys */
	  printfdbg ("cfcpys mvf%d = mvf%d = %f\n",
		     DEST_REG,
		     SRC1_REG,
		     DSPregs[SRC1_REG].upper.f);
	  DSPregs[DEST_REG].upper.f = DSPregs[SRC1_REG].upper.f;
	  break;

	case 1: /* cfcpyd */
	  printfdbg ("cfcpyd mvd%d = mvd%d = %g\n",
		     DEST_REG,
		     SRC1_REG,
		     mv_getRegDouble (SRC1_REG));
	  mv_setRegDouble (DEST_REG, mv_getRegDouble (SRC1_REG));
	  break;

	case 2: /* cfcvtds */
	  printfdbg ("cfcvtds mvf%d = (float) mvd%d = %f\n",
		     DEST_REG,
		     SRC1_REG,
		     (float) mv_getRegDouble (SRC1_REG));
	  DSPregs[DEST_REG].upper.f = (float) mv_getRegDouble (SRC1_REG);
	  break;

	case 3: /* cfcvtsd */
	  printfdbg ("cfcvtsd mvd%d = mvf%d = %g\n",
		     DEST_REG,
		     SRC1_REG,
		     (double) DSPregs[SRC1_REG].upper.f);
	  mv_setRegDouble (DEST_REG, (double) DSPregs[SRC1_REG].upper.f);
	  break;

	case 4: /* cfcvt32s */
	  printfdbg ("cfcvt32s mvf%d = mvfx%d = %f\n",
		     DEST_REG,
		     SRC1_REG,
		     (float) DSPregs[SRC1_REG].lower.i);
	  DSPregs[DEST_REG].upper.f = (float) DSPregs[SRC1_REG].lower.i;
	  break;

	case 5: /* cfcvt32d */
	  printfdbg ("cfcvt32d mvd%d = mvfx%d = %g\n",
		     DEST_REG,
		     SRC1_REG,
		     (double) DSPregs[SRC1_REG].lower.i);
	  mv_setRegDouble (DEST_REG, (double) DSPregs[SRC1_REG].lower.i);
	  break;

	case 6: /* cfcvt64s */
	  printfdbg ("cfcvt64s mvf%d = mvdx%d = %f\n",
		     DEST_REG,
		     SRC1_REG,
		     (float) mv_getReg64int (SRC1_REG));
	  DSPregs[DEST_REG].upper.f = (float) mv_getReg64int (SRC1_REG);
	  break;

	case 7: /* cfcvt64d */
	  printfdbg ("cfcvt64d mvd%d = mvdx%d = %g\n",
		     DEST_REG,
		     SRC1_REG,
		     (double) mv_getReg64int (SRC1_REG));
	  mv_setRegDouble (DEST_REG, (double) mv_getReg64int (SRC1_REG));
	  break;
	}
      break;

    case 1:
      switch (opcode2)
	{
	case 0: /* cfmuls */
	  printfdbg ("cfmuls mvf%d = mvf%d = %f\n",
		     DEST_REG,
		     SRC1_REG,
		     DSPregs[SRC1_REG].upper.f * DSPregs[SRC2_REG].upper.f);

	  DSPregs[DEST_REG].upper.f = DSPregs[SRC1_REG].upper.f
	    * DSPregs[SRC2_REG].upper.f;
	  break;

	case 1: /* cfmuld */
	  printfdbg ("cfmuld mvd%d = mvd%d = %g\n",
		     DEST_REG,
		     SRC1_REG,
		     mv_getRegDouble (SRC1_REG) * mv_getRegDouble (SRC2_REG));

	  mv_setRegDouble (DEST_REG,
			   mv_getRegDouble (SRC1_REG)
			   * mv_getRegDouble (SRC2_REG));
	  break;

	default:
	  fprintf (stderr, "unknown opcode in DSPCDP4 0x%x\n", instr);
	  cirrus_not_implemented ("unknown");
	  break;
	}
      break;

    case 3:
      switch (opcode2)
	{
	case 0: /* cfabss */
	  DSPregs[DEST_REG].upper.f = (DSPregs[SRC1_REG].upper.f < 0.0F ?
				       -DSPregs[SRC1_REG].upper.f
				       : DSPregs[SRC1_REG].upper.f);
	  printfdbg ("cfabss mvf%d = |mvf%d| = %f\n",
		     DEST_REG,
		     SRC1_REG,
		     DSPregs[DEST_REG].upper.f);
	  break;

	case 1: /* cfabsd */
	  mv_setRegDouble (DEST_REG,
			   (mv_getRegDouble (SRC1_REG) < 0.0 ?
			    -mv_getRegDouble (SRC1_REG)
			    : mv_getRegDouble (SRC1_REG)));
	  printfdbg ("cfabsd mvd%d = |mvd%d| = %g\n",
		     DEST_REG,
		     SRC1_REG,
		     mv_getRegDouble (DEST_REG));
	  break;

	case 2: /* cfnegs */
	  DSPregs[DEST_REG].upper.f = -DSPregs[SRC1_REG].upper.f;
	  printfdbg ("cfnegs mvf%d = -mvf%d = %f\n",
		     DEST_REG,
		     SRC1_REG,
		     DSPregs[DEST_REG].upper.f);
	  break;

	case 3: /* cfnegd */
	  mv_setRegDouble (DEST_REG,
			   -mv_getRegDouble (SRC1_REG));
	  printfdbg ("cfnegd mvd%d = -mvd%d = %g\n",
		     DEST_REG, DEST_REG,
		     mv_getRegDouble (DEST_REG));
	  break;

	case 4: /* cfadds */
	  DSPregs[DEST_REG].upper.f = DSPregs[SRC1_REG].upper.f
	    + DSPregs[SRC2_REG].upper.f;
	  printfdbg ("cfadds mvf%d = mvf%d + mvf%d = %f\n",
		     DEST_REG,
		     SRC1_REG,
		     SRC2_REG,
		     DSPregs[DEST_REG].upper.f);
	  break;

	case 5: /* cfaddd */
	  mv_setRegDouble (DEST_REG,
			   mv_getRegDouble (SRC1_REG)
			   + mv_getRegDouble (SRC2_REG));
	  printfdbg ("cfaddd: mvd%d = mvd%d + mvd%d = %g\n",
		     DEST_REG,
		     SRC1_REG,
		     SRC2_REG,
		     mv_getRegDouble (DEST_REG));
	  break;

	case 6: /* cfsubs */
	  DSPregs[DEST_REG].upper.f = DSPregs[SRC1_REG].upper.f
	    - DSPregs[SRC2_REG].upper.f;
	  printfdbg ("cfsubs: mvf%d = mvf%d - mvf%d = %f\n",
		     DEST_REG,
		     SRC1_REG,
		     SRC2_REG,
		     DSPregs[DEST_REG].upper.f);
	  break;

	case 7: /* cfsubd */
	  mv_setRegDouble (DEST_REG,
			   mv_getRegDouble (SRC1_REG)
			   - mv_getRegDouble (SRC2_REG));
	  printfdbg ("cfsubd: mvd%d = mvd%d - mvd%d = %g\n",
		     DEST_REG,
		     SRC1_REG,
		     SRC2_REG,
		     mv_getRegDouble (DEST_REG));
	  break;
	}
      break;

    default:
      fprintf (stderr, "unknown opcode in DSPCDP4 0x%x\n", instr);
      cirrus_not_implemented ("unknown");
      break;
    }

  return ARMul_DONE;
}

unsigned
DSPCDP5 (ARMul_State * state,
	 unsigned      type,
	 ARMword       instr)
{
   int opcode2;
   char shift;

   opcode2 = BITS (5,7);

   /* Shift constants are 7bit signed numbers in bits 0..3|5..7.  */
   shift = BITS (0, 3) | (BITS (5, 7)) << 4;
   if (shift & 0x40)
     shift |= 0xc0;

   switch (BITS (20,21))
     {
     case 0:
       /* cfsh32 */
       printfdbg ("cfsh32 %s amount=%d\n", shift < 0 ? "right" : "left",
		  shift);
       if (shift < 0)
	 /* Negative shift is a right shift.  */
	 DSPregs[DEST_REG].lower.i = DSPregs[SRC1_REG].lower.i >> -shift;
       else
	 /* Positive shift is a left shift.  */
	 DSPregs[DEST_REG].lower.i = DSPregs[SRC1_REG].lower.i << shift;
       break;

     case 1:
       switch (opcode2)
         {
         case 0: /* cfmul32 */
	   DSPregs[DEST_REG].lower.i = DSPregs[SRC1_REG].lower.i
	     * DSPregs[SRC2_REG].lower.i;
	   printfdbg ("cfmul32 mvfx%d = mvfx%d * mvfx%d = %d\n",
		      DEST_REG,
		      SRC1_REG,
		      SRC2_REG,
		      DSPregs[DEST_REG].lower.i);
           break;

         case 1: /* cfmul64 */
	   mv_setReg64int (DEST_REG,
			   mv_getReg64int (SRC1_REG)
			   * mv_getReg64int (SRC2_REG));
	   printfdbg ("cfmul64 mvdx%d = mvdx%d * mvdx%d = %lld\n",
		      DEST_REG,
		      SRC1_REG,
		      SRC2_REG,
		      mv_getReg64int (DEST_REG));
           break;

         case 2: /* cfmac32 */
	   DSPregs[DEST_REG].lower.i
	     += DSPregs[SRC1_REG].lower.i * DSPregs[SRC2_REG].lower.i;
	   printfdbg ("cfmac32 mvfx%d += mvfx%d * mvfx%d = %d\n",
		      DEST_REG,
		      SRC1_REG,
		      SRC2_REG,
		      DSPregs[DEST_REG].lower.i);
           break;

         case 3: /* cfmsc32 */
	   DSPregs[DEST_REG].lower.i
	     -= DSPregs[SRC1_REG].lower.i * DSPregs[SRC2_REG].lower.i;
	   printfdbg ("cfmsc32 mvfx%d -= mvfx%d * mvfx%d = %d\n",
		      DEST_REG,
		      SRC1_REG,
		      SRC2_REG,
		      DSPregs[DEST_REG].lower.i);
           break;

         case 4: /* cfcvts32 */
	   /* fixme: this should round */
	   DSPregs[DEST_REG].lower.i = (int) DSPregs[SRC1_REG].upper.f;
	   printfdbg ("cfcvts32 mvfx%d = mvf%d = %d\n",
		      DEST_REG,
		      SRC1_REG,
		      DSPregs[DEST_REG].lower.i);
           break;

         case 5: /* cfcvtd32 */
	   /* fixme: this should round */
	   DSPregs[DEST_REG].lower.i = (int) mv_getRegDouble (SRC1_REG);
	   printfdbg ("cfcvtd32 mvdx%d = mvd%d = %d\n",
		      DEST_REG,
		      SRC1_REG,
		      DSPregs[DEST_REG].lower.i);
           break;

         case 6: /* cftruncs32 */
	   DSPregs[DEST_REG].lower.i = (int) DSPregs[SRC1_REG].upper.f;
	   printfdbg ("cftruncs32 mvfx%d = mvf%d = %d\n",
		      DEST_REG,
		      SRC1_REG,
		      DSPregs[DEST_REG].lower.i);
           break;

         case 7: /* cftruncd32 */
	   DSPregs[DEST_REG].lower.i = (int) mv_getRegDouble (SRC1_REG);
	   printfdbg ("cftruncd32 mvfx%d = mvd%d = %d\n",
		      DEST_REG,
		      SRC1_REG,
		      DSPregs[DEST_REG].lower.i);
           break;
         }
       break;

     case 2:
       /* cfsh64 */
       printfdbg ("cfsh64\n");

       if (shift < 0)
	 /* Negative shift is a right shift.  */
	 mv_setReg64int (DEST_REG,
			 mv_getReg64int (SRC1_REG) >> -shift);
       else
	 /* Positive shift is a left shift.  */
	 mv_setReg64int (DEST_REG,
			 mv_getReg64int (SRC1_REG) << shift);
       printfdbg ("\t%llx\n", mv_getReg64int(DEST_REG));
       break;

     case 3:
       switch (opcode2)
         {
         case 0: /* cfabs32 */
	   DSPregs[DEST_REG].lower.i = (DSPregs[SRC1_REG].lower.i < 0
	     ? -DSPregs[SRC1_REG].lower.i : DSPregs[SRC1_REG].lower.i);
	   printfdbg ("cfabs32 mvfx%d = |mvfx%d| = %d\n",
		      DEST_REG,
		      SRC1_REG,
		      SRC2_REG,
		      DSPregs[DEST_REG].lower.i);
           break;

         case 1: /* cfabs64 */
	   mv_setReg64int (DEST_REG,
			   (mv_getReg64int (SRC1_REG) < 0
			    ? -mv_getReg64int (SRC1_REG)
			    : mv_getReg64int (SRC1_REG)));
	   printfdbg ("cfabs64 mvdx%d = |mvdx%d| = %lld\n",
		      DEST_REG,
		      SRC1_REG,
		      SRC2_REG,
		      mv_getReg64int (DEST_REG));
           break;

         case 2: /* cfneg32 */
	   DSPregs[DEST_REG].lower.i = -DSPregs[SRC1_REG].lower.i;
	   printfdbg ("cfneg32 mvfx%d = -mvfx%d = %d\n",
		      DEST_REG,
		      SRC1_REG,
		      SRC2_REG,
		      DSPregs[DEST_REG].lower.i);
           break;

         case 3: /* cfneg64 */
	   mv_setReg64int (DEST_REG, -mv_getReg64int (SRC1_REG));
	   printfdbg ("cfneg64 mvdx%d = -mvdx%d = %lld\n",
		      DEST_REG,
		      SRC1_REG,
		      SRC2_REG,
		      mv_getReg64int (DEST_REG));
           break;

         case 4: /* cfadd32 */
	   DSPregs[DEST_REG].lower.i = DSPregs[SRC1_REG].lower.i
	     + DSPregs[SRC2_REG].lower.i;
	   printfdbg ("cfadd32 mvfx%d = mvfx%d + mvfx%d = %d\n",
		      DEST_REG,
		      SRC1_REG,
		      SRC2_REG,
		      DSPregs[DEST_REG].lower.i);
           break;

         case 5: /* cfadd64 */
	   mv_setReg64int (DEST_REG,
			   mv_getReg64int (SRC1_REG)
			   + mv_getReg64int (SRC2_REG));
	   printfdbg ("cfadd64 mvdx%d = mvdx%d + mvdx%d = %lld\n",
		      DEST_REG,
		      SRC1_REG,
		      SRC2_REG,
		      mv_getReg64int (DEST_REG));
           break;

         case 6: /* cfsub32 */
	   DSPregs[DEST_REG].lower.i = DSPregs[SRC1_REG].lower.i
	     - DSPregs[SRC2_REG].lower.i;
	   printfdbg ("cfsub32 mvfx%d = mvfx%d - mvfx%d = %d\n",
		      DEST_REG,
		      SRC1_REG,
		      SRC2_REG,
		      DSPregs[DEST_REG].lower.i);
           break;

         case 7: /* cfsub64 */
	   mv_setReg64int (DEST_REG,
			   mv_getReg64int (SRC1_REG)
			   - mv_getReg64int (SRC2_REG));
	   printfdbg ("cfsub64 mvdx%d = mvdx%d - mvdx%d = %d\n",
		      DEST_REG,
		      SRC1_REG,
		      SRC2_REG,
		      mv_getReg64int (DEST_REG));
           break;
         }
       break;

     default:
       fprintf (stderr, "unknown opcode in DSPCDP5 0x%x\n", instr);
       cirrus_not_implemented ("unknown");
       break;
     }

  return ARMul_DONE;
}

unsigned
DSPCDP6 (ARMul_State * state,
	 unsigned      type,
	 ARMword       instr)
{
   switch (BITS (20,21))
     {
     case 0:
       /* cfmadd32 */
       cirrus_not_implemented ("cfmadd32");
       break;

     case 1:
       /* cfmsub32 */
       cirrus_not_implemented ("cfmsub32");
       break;

     case 2:
       /* cfmadda32 */
       cirrus_not_implemented ("cfmadda32");
       break;

     case 3:
       /* cfmsuba32 */
       cirrus_not_implemented ("cfmsuba32");
       break;

     default:
       fprintf (stderr, "unknown opcode in DSPCDP6 0x%x\n", instr);
     }

   return ARMul_DONE;
}

/* Conversion functions.

   32-bit integers are stored in the LOWER half of a 64-bit physical
   register.

   Single precision floats are stored in the UPPER half of a 64-bit
   physical register.  */

static double
mv_getRegDouble (int regnum)
{
  reg_conv.ints[lsw_float_index] = DSPregs[regnum].upper.i;
  reg_conv.ints[msw_float_index] = DSPregs[regnum].lower.i;
  return reg_conv.d;
}

static void
mv_setRegDouble (int regnum, double val)
{
  reg_conv.d = val;
  DSPregs[regnum].upper.i = reg_conv.ints[lsw_float_index];
  DSPregs[regnum].lower.i = reg_conv.ints[msw_float_index];
}

static long long
mv_getReg64int (int regnum)
{
  reg_conv.ints[lsw_int_index] = DSPregs[regnum].lower.i;
  reg_conv.ints[msw_int_index] = DSPregs[regnum].upper.i;
  return reg_conv.ll;
}

static void
mv_setReg64int (int regnum, long long val)
{
  reg_conv.ll = val;
  DSPregs[regnum].lower.i = reg_conv.ints[lsw_int_index];
  DSPregs[regnum].upper.i = reg_conv.ints[msw_int_index];
}
