//
// vr4300/cp1.c: VR4300 floating point unit coprocessor.
//
// CEN64: Cycle-Accurate Nintendo 64 Emulator.
// Copyright (C) 2015, Tyler J. Stachecki.
//
// This file is subject to the terms and conditions defined in
// 'LICENSE', which is part of this source code package.
//

#include "common.h"
#include "fpu/fpu.h"
#include "vr4300/cp1.h"
#include "vr4300/cpu.h"
#include "vr4300/decoder.h"
#include "vr4300/fault.h"

//
// Raises a MCI interlock for a set number of cycles.
//
static inline int vr4300_do_mci(struct vr4300 *vr4300, unsigned cycles) {
  vr4300->pipeline.cycles_to_stall = cycles - 1;
  vr4300->regs[PIPELINE_CYCLE_TYPE] = 3;
  return 1;
}

//
// ABS.fmt
//
int VR4300_CP1_ABS(struct vr4300 *vr4300,
  uint32_t iw, uint64_t fs, uint64_t ft) {
  struct vr4300_exdc_latch *exdc_latch = &vr4300->pipeline.exdc_latch;

  enum vr4300_fmt fmt = GET_FMT(iw);
  unsigned dest = GET_FD(iw);

  uint32_t fs32, fd32;
  uint64_t result;

  switch (fmt) {
    case VR4300_FMT_S:
      fs32 = fs;

      fpu_abs_32(&fs32, &fd32);
      result = fd32;
      break;

    case VR4300_FMT_D:
      fpu_abs_64(&fs, &result);
      break;

    default:
      VR4300_INV(vr4300);
      return 1;
  }

  exdc_latch->result = result;
  exdc_latch->dest = dest;
  return vr4300_do_mci(vr4300, 3);
}

//
// ADD.fmt
//
int VR4300_CP1_ADD(struct vr4300 *vr4300,
  uint32_t iw, uint64_t fs, uint64_t ft) {
  struct vr4300_exdc_latch *exdc_latch = &vr4300->pipeline.exdc_latch;

  enum vr4300_fmt fmt = GET_FMT(iw);
  unsigned dest = GET_FD(iw);

  uint32_t fs32, ft32, fd32;
  uint64_t result;

  switch (fmt) {
    case VR4300_FMT_S:
      fs32 = fs;
      ft32 = ft;

      fpu_add_32(&fs32, &ft32, &fd32);
      result = fd32;
      break;

    case VR4300_FMT_D:
      fpu_add_64(&fs, &ft, &result);
      break;

    default:
      VR4300_INV(vr4300);
      return 1;
  }

  exdc_latch->result = result;
  exdc_latch->dest = dest;
  return vr4300_do_mci(vr4300, 3);
}

//
// BC1F
// BC1FL
// BC1T
// BC1TL
//
int VR4300_BC1(struct vr4300 *vr4300,
  uint32_t iw, uint64_t fs, uint64_t ft) {
  struct vr4300_icrf_latch *icrf_latch = &vr4300->pipeline.icrf_latch;
  struct vr4300_rfex_latch *rfex_latch = &vr4300->pipeline.rfex_latch;
  unsigned opcode = (iw >> 16) & 0x3;

  uint64_t offset = (uint64_t) ((int16_t) iw) << 2;
  uint64_t taken_pc = rfex_latch->common.pc + (offset + 4);
  uint32_t cond = vr4300->regs[VR4300_CP1_FCR31];

  // XXX: The VR4300 manual says that the results of a FPU
  // FCR writes aren't ready on the next cycle, but it seems
  // that this might actually not be a limitiation of the real
  // hardware?
  if (vr4300->pipeline.dcwb_latch.dest == VR4300_CP1_FCR31)
    cond = vr4300->pipeline.dcwb_latch.result;

  switch (opcode) {
    case 0x0: // BC1F
      if (!(cond >> 23 & 0x1))
        icrf_latch->pc = taken_pc;
      break;

    case 0x1: // BC1T
      if (cond >> 23 & 0x1)
        icrf_latch->pc = taken_pc;
      break;

    case 0x2: // BC1FL
      if (!(cond >> 23 & 0x1))
        icrf_latch->pc = taken_pc;
      else
        rfex_latch->iw_mask = 0;
      break;

    case 0x3: // BC1TL
      if (cond >> 23 & 0x1)
        icrf_latch->pc = taken_pc;
      else
        rfex_latch->iw_mask = 0;
      break;
  }

  return 0;
}

//
// C.eq.fmt
// C.seq.fmt
//
int VR4300_CP1_C_EQ_C_SEQ(struct vr4300 *vr4300,
  uint32_t iw, uint64_t fs, uint64_t ft) {
  struct vr4300_exdc_latch *exdc_latch = &vr4300->pipeline.exdc_latch;

  enum vr4300_fmt fmt = GET_FMT(iw);
  unsigned dest = VR4300_CP1_FCR31;
  uint64_t result = vr4300->regs[dest];

  uint32_t fs32, ft32;
  uint8_t flag;

  switch (fmt) {
    case VR4300_FMT_S:
      fs32 = fs;
      ft32 = ft;

      result &= ~(1 << 23);
      flag = fpu_cmp_eq_32(&fs32, &ft32);
      break;

    case VR4300_FMT_D:
      result &= ~(1 << 23);
      flag = fpu_cmp_eq_64(&fs, &ft);
      break;

    default:
      VR4300_INV(vr4300);
      return 1;
  }

  exdc_latch->result = result | (flag << 23);
  exdc_latch->dest = dest;
  return 0;
}

//
// C.f.fmt
// C.sf.fmt
//
int VR4300_CP1_C_F_C_SF(struct vr4300 *vr4300,
  uint32_t iw, uint64_t fs, uint64_t ft) {
  struct vr4300_exdc_latch *exdc_latch = &vr4300->pipeline.exdc_latch;

  enum vr4300_fmt fmt = GET_FMT(iw);
  unsigned dest = VR4300_CP1_FCR31;
  uint64_t result = vr4300->regs[dest];

  uint32_t fs32, ft32;
  uint8_t flag;

  switch (fmt) {
    case VR4300_FMT_S:
      fs32 = fs;
      ft32 = ft;

      result &= ~(1 << 23);
      flag = fpu_cmp_f_32(&fs32, &ft32);
      break;

    case VR4300_FMT_D:
      result &= ~(1 << 23);
      flag = fpu_cmp_f_64(&fs, &ft);
      break;

    default:
      VR4300_INV(vr4300);
      return 1;
  }

  exdc_latch->result = result | (flag << 23);
  exdc_latch->dest = dest;
  return 0;
}

//
// C.ole.fmt
// C.le.fmt
//
int VR4300_CP1_C_OLE_C_LE(struct vr4300 *vr4300,
  uint32_t iw, uint64_t fs, uint64_t ft) {
  struct vr4300_exdc_latch *exdc_latch = &vr4300->pipeline.exdc_latch;

  enum vr4300_fmt fmt = GET_FMT(iw);
  unsigned dest = VR4300_CP1_FCR31;
  uint64_t result = vr4300->regs[dest];

  uint32_t fs32, ft32;
  uint8_t flag;

  switch (fmt) {
    case VR4300_FMT_S:
      fs32 = fs;
      ft32 = ft;

      result &= ~(1 << 23);
      flag = fpu_cmp_ole_32(&fs32, &ft32);
      break;

    case VR4300_FMT_D:
      result &= ~(1 << 23);
      flag = fpu_cmp_ole_64(&fs, &ft);
      break;

    default:
      VR4300_INV(vr4300);
      return 1;
  }

  exdc_latch->result = result | (flag << 23);
  exdc_latch->dest = dest;
  return 0;
}

//
// C.olt.fmt
// C.lt.fmt
//
int VR4300_CP1_C_OLT_C_LT(struct vr4300 *vr4300,
  uint32_t iw, uint64_t fs, uint64_t ft) {
  struct vr4300_exdc_latch *exdc_latch = &vr4300->pipeline.exdc_latch;

  enum vr4300_fmt fmt = GET_FMT(iw);
  unsigned dest = VR4300_CP1_FCR31;
  uint64_t result = vr4300->regs[dest];

  uint32_t fs32, ft32;
  uint8_t flag;

  switch (fmt) {
    case VR4300_FMT_S:
      fs32 = fs;
      ft32 = ft;

      result &= ~(1 << 23);
      flag = fpu_cmp_olt_32(&fs32, &ft32);
      break;

    case VR4300_FMT_D:
      result &= ~(1 << 23);
      flag = fpu_cmp_olt_64(&fs, &ft);
      break;

    default:
      VR4300_INV(vr4300);
      return 1;
  }

  exdc_latch->result = result | (flag << 23);
  exdc_latch->dest = dest;
  return 0;
}

//
// C.ueq.fmt
// C.ngl.fmt
//
int VR4300_CP1_C_UEQ_C_NGL(struct vr4300 *vr4300,
  uint32_t iw, uint64_t fs, uint64_t ft) {
  struct vr4300_exdc_latch *exdc_latch = &vr4300->pipeline.exdc_latch;

  enum vr4300_fmt fmt = GET_FMT(iw);
  unsigned dest = VR4300_CP1_FCR31;
  uint64_t result = vr4300->regs[dest];

  uint32_t fs32, ft32;
  uint8_t flag;

  switch (fmt) {
    case VR4300_FMT_S:
      fs32 = fs;
      ft32 = ft;

      result &= ~(1 << 23);
      flag = fpu_cmp_ueq_32(&fs32, &ft32);
      break;

    case VR4300_FMT_D:
      result &= ~(1 << 23);
      flag = fpu_cmp_ueq_64(&fs, &ft);
      break;

    default:
      VR4300_INV(vr4300);
      return 1;
  }

  exdc_latch->result = result | (flag << 23);
  exdc_latch->dest = dest;
  return 0;
}

//
// C.ule.fmt
// C.ngt.fmt
//
int VR4300_CP1_C_ULE_C_NGT(struct vr4300 *vr4300,
  uint32_t iw, uint64_t fs, uint64_t ft) {
  struct vr4300_exdc_latch *exdc_latch = &vr4300->pipeline.exdc_latch;

  enum vr4300_fmt fmt = GET_FMT(iw);
  unsigned dest = VR4300_CP1_FCR31;
  uint64_t result = vr4300->regs[dest];

  uint32_t fs32, ft32;
  uint8_t flag;

  switch (fmt) {
    case VR4300_FMT_S:
      fs32 = fs;
      ft32 = ft;

      result &= ~(1 << 23);
      flag = fpu_cmp_ule_32(&fs32, &ft32);
      break;

    case VR4300_FMT_D:
      result &= ~(1 << 23);
      flag = fpu_cmp_ule_64(&fs, &ft);
      break;

    default:
      VR4300_INV(vr4300);
      return 1;
  }

  exdc_latch->result = result | (flag << 23);
  exdc_latch->dest = dest;
  return 0;
}

//
// C.ult.fmt
// C.nge.fmt
//
int VR4300_CP1_C_ULT_C_NGE(struct vr4300 *vr4300,
  uint32_t iw, uint64_t fs, uint64_t ft) {
  struct vr4300_exdc_latch *exdc_latch = &vr4300->pipeline.exdc_latch;

  enum vr4300_fmt fmt = GET_FMT(iw);
  unsigned dest = VR4300_CP1_FCR31;
  uint64_t result = vr4300->regs[dest];

  uint32_t fs32, ft32;
  uint8_t flag;

  switch (fmt) {
    case VR4300_FMT_S:
      fs32 = fs;
      ft32 = ft;

      result &= ~(1 << 23);
      flag = fpu_cmp_ult_32(&fs32, &ft32);
      break;

    case VR4300_FMT_D:
      result &= ~(1 << 23);
      flag = fpu_cmp_ult_64(&fs, &ft);
      break;

    default:
      VR4300_INV(vr4300);
      return 1;
  }

  exdc_latch->result = result | (flag << 23);
  exdc_latch->dest = dest;
  return 0;
}

//
// C.un.fmt
// C.ngle.fmt
//
int VR4300_CP1_C_UN_C_NGLE(struct vr4300 *vr4300,
  uint32_t iw, uint64_t fs, uint64_t ft) {
  struct vr4300_exdc_latch *exdc_latch = &vr4300->pipeline.exdc_latch;

  enum vr4300_fmt fmt = GET_FMT(iw);
  unsigned dest = VR4300_CP1_FCR31;
  uint64_t result = vr4300->regs[dest];

  uint32_t fs32, ft32;
  uint8_t flag;

  switch (fmt) {
    case VR4300_FMT_S:
      fs32 = fs;
      ft32 = ft;

      result &= ~(1 << 23);
      flag = fpu_cmp_un_32(&fs32, &ft32);
      break;

    case VR4300_FMT_D:
      result &= ~(1 << 23);
      flag = fpu_cmp_un_64(&fs, &ft);
      break;

    default:
      VR4300_INV(vr4300);
      return 1;
  }

  exdc_latch->result = result | (flag << 23);
  exdc_latch->dest = dest;
  return 0;
}

//
// CEIL.l.fmt
//
int VR4300_CP1_CEIL_L(struct vr4300 *vr4300,
  uint32_t iw, uint64_t fs, uint64_t ft) {
  struct vr4300_exdc_latch *exdc_latch = &vr4300->pipeline.exdc_latch;

  enum vr4300_fmt fmt = GET_FMT(iw);
  unsigned dest = GET_FD(iw);

  uint32_t fs32;
  uint64_t result;

#ifndef CEN64_ARCH_HAS_CEIL
  fpu_state_t saved_state = fpu_get_state();
  fpu_set_state((saved_state & ~FPU_ROUND_MASK) | FPU_ROUND_POSINF);

  switch (fmt) {
    case VR4300_FMT_S:
      fs32 = fs;

      fpu_cvt_i64_f32(&fs32, &result);
      break;

    case VR4300_FMT_D:
      fpu_cvt_i64_f64(&fs, &result);
      break;

    default:
      VR4300_INV(vr4300);
      return 1;
  }

  fpu_set_state((saved_state & FPU_ROUND_MASK) |
    (fpu_get_state() & ~FPU_ROUND_MASK));
#else
  switch (fmt) {
    case VR4300_FMT_S:
      fs32 = fs;

      fpu_ceil_i64_f32(&fs32, &result);
      break;

    case VR4300_FMT_D:
      fpu_ceil_i64_f64(&fs, &result);
      break;

    default:
      VR4300_INV(vr4300);
      return 1;
  }
#endif

  exdc_latch->result = result;
  exdc_latch->dest = dest;
  return vr4300_do_mci(vr4300, 5);
}

//
// CEIL.w.fmt
//
int VR4300_CP1_CEIL_W(struct vr4300 *vr4300,
  uint32_t iw, uint64_t fs, uint64_t ft) {
  struct vr4300_exdc_latch *exdc_latch = &vr4300->pipeline.exdc_latch;

  enum vr4300_fmt fmt = GET_FMT(iw);
  unsigned dest = GET_FD(iw);

  uint32_t fs32;
  uint32_t result;

#ifndef CEN64_ARCH_HAS_CEIL
  fpu_state_t saved_state = fpu_get_state();
  fpu_set_state((saved_state & ~FPU_ROUND_MASK) | FPU_ROUND_POSINF);

  switch (fmt) {
    case VR4300_FMT_S:
      fs32 = fs;

      fpu_cvt_i32_f32(&fs32, &result);
      break;

    case VR4300_FMT_D:
      fpu_cvt_i32_f64(&fs, &result);
      break;

    default:
      VR4300_INV(vr4300);
      return 1;
  }

  fpu_set_state((saved_state & FPU_ROUND_MASK) |
    (fpu_get_state() & ~FPU_ROUND_MASK));
#else
  switch (fmt) {
    case VR4300_FMT_S:
      fs32 = fs;

      fpu_ceil_i32_f32(&fs32, &result);
      break;

    case VR4300_FMT_D:
      fpu_ceil_i32_f64(&fs, &result);
      break;

    default:
      VR4300_INV(vr4300);
      return 1;
  }
#endif

  exdc_latch->result = result;
  exdc_latch->dest = dest;
  return vr4300_do_mci(vr4300, 5);
}

//
// CFC1
//
int VR4300_CFC1(struct vr4300 *vr4300,
  uint32_t iw, uint64_t rs, uint64_t rt) {
  struct vr4300_exdc_latch *exdc_latch = &vr4300->pipeline.exdc_latch;

  unsigned dest = GET_RT(iw);
  unsigned src = GET_RD(iw);
  uint64_t result;

  switch (src) {
    case 0: src = VR4300_CP1_FCR0; break;
    case 31: src = VR4300_CP1_FCR31; break;

    default:
      src = 0;

      assert(0 && "CFC1: Read reserved FCR.");
      break;
  }

  result = vr4300->regs[src];

  // XXX: The VR4300 manual says that the results of a FPU
  // FCR writes aren't ready on the next cycle, but it seems
  // that this might actually not be a limitiation of the real
  // hardware?
  if (vr4300->pipeline.dcwb_latch.dest == VR4300_CP1_FCR31)
    result = vr4300->pipeline.dcwb_latch.result;

  // Undefined while the next instruction
  // executes, so we can cheat and use the RF.
  exdc_latch->result = (int32_t) result;
  exdc_latch->dest = dest;
  return 0;
}

//
// CTC1
//
// XXX: Raise exception on cause/enable.
// XXX: In such cases, ensure write occurs.
//
int VR4300_CTC1(struct vr4300 *vr4300,
  uint32_t iw, uint64_t rs, uint64_t rt) {
  struct vr4300_exdc_latch *exdc_latch = &vr4300->pipeline.exdc_latch;
  unsigned dest = GET_RD(iw);

  if (dest == 31)
    dest = VR4300_CP1_FCR31;

  else {
    assert(0 && "CTC1: Write to fixed/reserved FCR.");

    dest = 0;
    rt = 0;
  }

  // Undefined while the next instruction
  // executes, so we can cheat and use WB.
  exdc_latch->result = rt;
  exdc_latch->dest = dest;
  return 0;
}

//
// CVT.d.fmt
//
int VR4300_CP1_CVT_D(struct vr4300 *vr4300,
  uint32_t iw, uint64_t fs, uint64_t ft) {
  struct vr4300_exdc_latch *exdc_latch = &vr4300->pipeline.exdc_latch;
  enum vr4300_fmt fmt = GET_FMT(iw);
  unsigned dest = GET_FD(iw);

  uint32_t fs32;
  uint64_t result;

  switch (fmt) {
    case VR4300_FMT_S:
      fs32 = fs;

      fpu_cvt_f64_f32(&fs32, &result);
      break;

    case VR4300_FMT_W:
      fs32 = fs;

      fpu_cvt_f64_i32(&fs32, &result);
      break;

    case VR4300_FMT_L:
      fpu_cvt_f64_i64(&fs, &result);
      break;

    default:
      VR4300_INV(vr4300);
      return 1;
  }

  exdc_latch->result = result;
  exdc_latch->dest = dest;
  return fmt != VR4300_FMT_S
    ? vr4300_do_mci(vr4300, 5)
    : 0;
}

//
// CVT.l.fmt
//
int VR4300_CP1_CVT_L(struct vr4300 *vr4300,
  uint32_t iw, uint64_t fs, uint64_t ft) {
  struct vr4300_exdc_latch *exdc_latch = &vr4300->pipeline.exdc_latch;
  enum vr4300_fmt fmt = GET_FMT(iw);
  unsigned dest = GET_FD(iw);

  uint32_t fs32;
  uint64_t result;

  switch (fmt) {
    case VR4300_FMT_S:
      fs32 = fs;

      fpu_cvt_i64_f32(&fs32, &result);
      break;

    case VR4300_FMT_D:
      fpu_cvt_i64_f64(&fs, &result);
      break;

    default:
      VR4300_INV(vr4300);
      return 1;
  }

  exdc_latch->result = result;
  exdc_latch->dest = dest;
  return vr4300_do_mci(vr4300, 5);
}

//
// CVT.s.fmt
//
int VR4300_CP1_CVT_S(struct vr4300 *vr4300,
  uint32_t iw, uint64_t fs, uint64_t ft) {
  struct vr4300_exdc_latch *exdc_latch = &vr4300->pipeline.exdc_latch;
  enum vr4300_fmt fmt = GET_FMT(iw);
  unsigned dest = GET_FD(iw);

  uint32_t fs32;
  uint32_t result;

  switch (fmt) {
    case VR4300_FMT_D:
      fpu_cvt_f32_f64(&fs, &result);
      break;

    case VR4300_FMT_W:
      fs32 = fs;

      fpu_cvt_f32_i32(&fs32, &result);
      break;

    case VR4300_FMT_L:
      fpu_cvt_f32_i64(&fs, &result);
      break;

    default:
      VR4300_INV(vr4300);
      return 1;
  }

  exdc_latch->result = result;
  exdc_latch->dest = dest;
  return vr4300_do_mci(vr4300,
    fmt == VR4300_FMT_D ? 2 : 5);
}

//
// CVT.w.fmt
//
int VR4300_CP1_CVT_W(struct vr4300 *vr4300,
  uint32_t iw, uint64_t fs, uint64_t ft) {
  struct vr4300_exdc_latch *exdc_latch = &vr4300->pipeline.exdc_latch;
  enum vr4300_fmt fmt = GET_FMT(iw);
  unsigned dest = GET_FD(iw);

  uint32_t fs32;
  uint32_t result;

  switch (fmt) {
    case VR4300_FMT_S:
      fs32 = fs;

      fpu_cvt_i32_f32(&fs32, &result);
      break;

    case VR4300_FMT_D:
      fpu_cvt_i32_f64(&fs, &result);
      break;

    default:
      VR4300_INV(vr4300);
      return 1;
  }

  exdc_latch->result = result;
  exdc_latch->dest = dest;
  return vr4300_do_mci(vr4300, 5);
}

//
// DIV.fmt
//
int VR4300_CP1_DIV(struct vr4300 *vr4300,
  uint32_t iw, uint64_t fs, uint64_t ft) {
  struct vr4300_exdc_latch *exdc_latch = &vr4300->pipeline.exdc_latch;
  enum vr4300_fmt fmt = GET_FMT(iw);
  unsigned dest = GET_FD(iw);

  uint32_t fs32, ft32, fd32;
  uint64_t result;

  switch (fmt) {
    case VR4300_FMT_S:
      fs32 = fs;
      ft32 = ft;

      fpu_div_32(&fs32, &ft32, &fd32);
      result = fd32;
      break;

    case VR4300_FMT_D:
      fpu_div_64(&fs, &ft, &result);
      break;

    default:
      VR4300_INV(vr4300);
      return 1;
  }

  exdc_latch->result = result;
  exdc_latch->dest = dest;
  return vr4300_do_mci(vr4300,
    fmt == VR4300_FMT_D ? 58 : 29);
}

//
// DMFC1
//
int VR4300_DMFC1(struct vr4300 *vr4300,
  uint32_t iw, uint64_t fs, uint64_t unused(rt)) {
  struct vr4300_exdc_latch *exdc_latch = &vr4300->pipeline.exdc_latch;
  unsigned dest = GET_RT(iw);

  exdc_latch->result = fs;
  exdc_latch->dest = dest;
  return 0;
}

//
// DMTC1
//
int VR4300_DMTC1(struct vr4300 *vr4300,
  uint32_t iw, uint64_t fs, uint64_t rt) {
  struct vr4300_exdc_latch *exdc_latch = &vr4300->pipeline.exdc_latch;
  unsigned dest = GET_FS(iw);

  exdc_latch->result = rt;
  exdc_latch->dest = dest;
  return 0;
}

//
// FLOOR.l.fmt
//
int VR4300_CP1_FLOOR_L(struct vr4300 *vr4300,
  uint32_t iw, uint64_t fs, uint64_t ft) {
  struct vr4300_exdc_latch *exdc_latch = &vr4300->pipeline.exdc_latch;
  enum vr4300_fmt fmt = GET_FMT(iw);
  unsigned dest = GET_FD(iw);

  uint32_t fs32;
  uint64_t result;

#ifndef CEN64_ARCH_HAS_FLOOR
  fpu_state_t saved_state = fpu_get_state();
  fpu_set_state((saved_state & ~FPU_ROUND_MASK) | FPU_ROUND_NEGINF);

  switch (fmt) {
    case VR4300_FMT_S:
      fs32 = fs;

      fpu_cvt_i64_f32(&fs32, &result);
      break;

    case VR4300_FMT_D:
      fpu_cvt_i64_f64(&fs, &result);
      break;

    default:
      VR4300_INV(vr4300);
      return 1;
  }

  fpu_set_state((saved_state & FPU_ROUND_MASK) |
    (fpu_get_state() & ~FPU_ROUND_MASK));
#else
  switch (fmt) {
    case VR4300_FMT_S:
      fs32 = fs;

      fpu_floor_i64_f32(&fs32, &result);
      break;

    case VR4300_FMT_D:
      fpu_floor_i64_f64(&fs, &result);
      break;

    default:
      VR4300_INV(vr4300);
      return 1;
  }
#endif

  exdc_latch->result = result;
  exdc_latch->dest = dest;
  return vr4300_do_mci(vr4300, 5);
}

//
// FLOOR.w.fmt
//
int VR4300_CP1_FLOOR_W(struct vr4300 *vr4300,
  uint32_t iw, uint64_t fs, uint64_t ft) {
  struct vr4300_exdc_latch *exdc_latch = &vr4300->pipeline.exdc_latch;
  enum vr4300_fmt fmt = GET_FMT(iw);
  unsigned dest = GET_FD(iw);

  uint32_t fs32;
  uint32_t result;

#ifndef CEN64_ARCH_HAS_FLOOR
  fpu_state_t saved_state = fpu_get_state();
  fpu_set_state((saved_state & ~FPU_ROUND_MASK) | FPU_ROUND_NEGINF);

  switch (fmt) {
    case VR4300_FMT_S:
      fs32 = fs;

      fpu_cvt_i32_f32(&fs32, &result);
      break;

    case VR4300_FMT_D:
      fpu_cvt_i32_f64(&fs, &result);
      break;

    default:
      VR4300_INV(vr4300);
      return 1;
  }

  fpu_set_state((saved_state & FPU_ROUND_MASK) |
    (fpu_get_state() & ~FPU_ROUND_MASK));
#else
  switch (fmt) {
    case VR4300_FMT_S:
      fs32 = fs;

      fpu_floor_i32_f32(&fs32, &result);
      break;

    case VR4300_FMT_D:
      fpu_floor_i32_f64(&fs, &result);
      break;

    default:
      VR4300_INV(vr4300);
      return 1;
  }
#endif

  exdc_latch->result = result;
  exdc_latch->dest = dest;
  return vr4300_do_mci(vr4300, 5);
}

//
// LDC1
//
// TODO/FIXME: Check for unaligned addresses.
//
int VR4300_LDC1(struct vr4300 *vr4300,
  uint32_t iw, uint64_t rs, uint64_t rt) {
  struct vr4300_exdc_latch *exdc_latch = &vr4300->pipeline.exdc_latch;
  unsigned dest = GET_FT(iw);

  exdc_latch->request.vaddr = rs + (int16_t) iw;
  exdc_latch->request.data = ~0ULL;
  exdc_latch->request.wdqm = 0ULL;
  exdc_latch->request.postshift = 0;
  exdc_latch->request.access_type = VR4300_ACCESS_DWORD;
  exdc_latch->request.type = VR4300_BUS_REQUEST_READ;
  exdc_latch->request.size = 8;

  exdc_latch->dest = dest;
  exdc_latch->result = 0;
  return 0;
}

//
// LWC1
//
// TODO/FIXME: Check for unaligned addresses.
//
int VR4300_LWC1(struct vr4300 *vr4300,
  uint32_t iw, uint64_t rs, uint64_t ft) {
  struct vr4300_exdc_latch *exdc_latch = &vr4300->pipeline.exdc_latch;
  uint32_t status = vr4300->regs[VR4300_CP0_REGISTER_STATUS];
  uint64_t address = (rs + (int16_t) iw);
  unsigned dest = GET_FT(iw);

  uint64_t result = 0;
  unsigned postshift = 0;

  if (!(status & 0x04000000)) {
    result = dest & 0x1
      ? ft & 0x00000000FFFFFFFFULL
      : ft & 0xFFFFFFFF00000000ULL;

    postshift = (dest & 0x1) << 5;
    dest &= ~0x1;
  }

  exdc_latch->request.vaddr = address;
  exdc_latch->request.data = ~0U;
  exdc_latch->request.wdqm = 0ULL;
  exdc_latch->request.postshift = postshift;
  exdc_latch->request.access_type = VR4300_ACCESS_WORD;
  exdc_latch->request.type = VR4300_BUS_REQUEST_READ;
  exdc_latch->request.size = 4;

  exdc_latch->result = result;
  exdc_latch->dest = dest;
  return 0;
}

//
// MUL.fmt
//
int VR4300_CP1_MUL(struct vr4300 *vr4300,
  uint32_t iw, uint64_t fs, uint64_t ft) {
  struct vr4300_exdc_latch *exdc_latch = &vr4300->pipeline.exdc_latch;
  enum vr4300_fmt fmt = GET_FMT(iw);
  unsigned dest = GET_FD(iw);

  uint32_t fs32, ft32, fd32;
  uint64_t result;

  switch (fmt) {
    case VR4300_FMT_S:
      fs32 = fs;
      ft32 = ft;

      fpu_mul_32(&fs32, &ft32, &fd32);
      result = fd32;
      break;

    case VR4300_FMT_D:
      fpu_mul_64(&fs, &ft, &result);
      break;

    default:
      VR4300_INV(vr4300);
      return 1;
  }

  exdc_latch->result = result;
  exdc_latch->dest = dest;
  return vr4300_do_mci(vr4300,
    fmt == VR4300_FMT_D ? 8 : 5);
}

//
// MFC1
//
int VR4300_MFC1(struct vr4300 *vr4300,
  uint32_t iw, uint64_t fs, uint64_t unused(rt)) {
  struct vr4300_exdc_latch *exdc_latch = &vr4300->pipeline.exdc_latch;
  uint32_t status = vr4300->regs[VR4300_CP0_REGISTER_STATUS];
  unsigned dest = GET_RT(iw);
  uint64_t result;

  if (status & 0x04000000)
    result = (int32_t) fs;

  else {
    result = (GET_FS(iw) & 0x1)
      ? (int32_t) (fs >> 32)
      : (int32_t) (fs);
  }

  exdc_latch->result = result;
  exdc_latch->dest = dest;
  return 0;
}

//
// MOV.fmt
//
int VR4300_CP1_MOV(struct vr4300 *vr4300,
  uint32_t iw, uint64_t fs, uint64_t ft) {
  struct vr4300_exdc_latch *exdc_latch = &vr4300->pipeline.exdc_latch;
  unsigned dest = GET_FD(iw);

  exdc_latch->result = fs;
  exdc_latch->dest = dest;
  return 0;
}

//
// MTC1
//
int VR4300_MTC1(struct vr4300 *vr4300,
  uint32_t iw, uint64_t fs, uint64_t rt) {
  struct vr4300_exdc_latch *exdc_latch = &vr4300->pipeline.exdc_latch;
  uint32_t status = vr4300->regs[VR4300_CP0_REGISTER_STATUS];
  uint64_t result = (int32_t) rt;
  unsigned dest = GET_FS(iw);

  if (!(status & 0x04000000)) {
    result = (dest & 0x1)
      ? ((uint32_t) fs) | (rt << 32)
      : (fs & ~0xFFFFFFFFULL) | ((uint32_t) rt);

    dest &= ~0x1;
  }

  exdc_latch->result = result;
  exdc_latch->dest = dest;
  return 0;
}

//
// NEG.fmt
//
int VR4300_CP1_NEG(struct vr4300 *vr4300,
  uint32_t iw, uint64_t fs, uint64_t ft) {
  struct vr4300_exdc_latch *exdc_latch = &vr4300->pipeline.exdc_latch;
  enum vr4300_fmt fmt = GET_FMT(iw);
  unsigned dest = GET_FD(iw);

  uint32_t fs32, fd32;
  uint64_t result;

  switch (fmt) {
    case VR4300_FMT_S:
      fs32 = fs;

      fpu_neg_32(&fs32, &fd32);
      result = fd32;
      break;

    case VR4300_FMT_D:
      fpu_neg_64(&fs, &result);
      break;

    default:
      VR4300_INV(vr4300);
      return 1;
  }

  exdc_latch->result = result;
  exdc_latch->dest = dest;
  return 0;
}

//
// ROUND.l.fmt
//
int VR4300_CP1_ROUND_L(struct vr4300 *vr4300,
  uint32_t iw, uint64_t fs, uint64_t ft) {
  struct vr4300_exdc_latch *exdc_latch = &vr4300->pipeline.exdc_latch;
  enum vr4300_fmt fmt = GET_FMT(iw);
  unsigned dest = GET_FD(iw);

  uint32_t fs32;
  uint64_t result;

#ifndef CEN64_ARCH_HAS_ROUND
  fpu_state_t saved_state = fpu_get_state();
  fpu_set_state((saved_state & ~FPU_ROUND_MASK) | FPU_ROUND_NEAREST);

  switch (fmt) {
    case VR4300_FMT_S:
      fs32 = fs;

      fpu_cvt_i64_f32(&fs32, &result);
      break;

    case VR4300_FMT_D:
      fpu_cvt_i64_f64(&fs, &result);
      break;

    default:
      VR4300_INV(vr4300);
      return 1;
  }

  fpu_set_state((saved_state & FPU_ROUND_MASK) |
    (fpu_get_state() & ~FPU_ROUND_MASK));
#else
  switch (fmt) {
    case VR4300_FMT_S:
      fs32 = fs;

      fpu_round_i64_f32(&fs32, &result);
      break;

    case VR4300_FMT_D:
      fpu_round_i64_f64(&fs, &result);
      break;

    default:
      VR4300_INV(vr4300);
      return 1;
  }
#endif

  exdc_latch->result = result;
  exdc_latch->dest = dest;
  return vr4300_do_mci(vr4300, 5);
}

//
// ROUND.w.fmt
//
int VR4300_CP1_ROUND_W(struct vr4300 *vr4300,
  uint32_t iw, uint64_t fs, uint64_t ft) {
  struct vr4300_exdc_latch *exdc_latch = &vr4300->pipeline.exdc_latch;
  enum vr4300_fmt fmt = GET_FMT(iw);
  unsigned dest = GET_FD(iw);

  uint32_t fs32;
  uint32_t result;

#ifndef CEN64_ARCH_HAS_ROUND
  fpu_state_t saved_state = fpu_get_state();
  fpu_set_state((saved_state & ~FPU_ROUND_MASK) | FPU_ROUND_NEAREST);

  switch (fmt) {
    case VR4300_FMT_S:
      fs32 = fs;

      fpu_cvt_i32_f32(&fs32, &result);
      break;

    case VR4300_FMT_D:
      fpu_cvt_i32_f64(&fs, &result);
      break;

    default:
      VR4300_INV(vr4300);
      return 1;

  }

  fpu_set_state((saved_state & FPU_ROUND_MASK) |
    (fpu_get_state() & ~FPU_ROUND_MASK));
#else
  switch (fmt) {
    case VR4300_FMT_S:
      fs32 = fs;

      fpu_round_i32_f32(&fs32, &result);
      break;

    case VR4300_FMT_D:
      fpu_round_i32_f64(&fs, &result);
      break;

    default:
      VR4300_INV(vr4300);
      return 1;
  }
#endif

  exdc_latch->result = result;
  exdc_latch->dest = dest;
  return vr4300_do_mci(vr4300, 5);
}

//
// SDC1
//
// TODO/FIXME: Check for unaligned addresses.
//
int VR4300_SDC1(struct vr4300 *vr4300,
  uint32_t iw, uint64_t rs, uint64_t ft) {
  struct vr4300_exdc_latch *exdc_latch = &vr4300->pipeline.exdc_latch;

  exdc_latch->request.vaddr = rs + (int16_t) iw;
  exdc_latch->request.data = ft;
  exdc_latch->request.wdqm = ~0ULL;
  exdc_latch->request.access_type = VR4300_ACCESS_DWORD;
  exdc_latch->request.type = VR4300_BUS_REQUEST_WRITE;
  exdc_latch->request.size = 8;

  return 0;
}

//
// SQRT.fmt
//
int VR4300_CP1_SQRT(struct vr4300 *vr4300,
  uint32_t iw, uint64_t fs, uint64_t ft) {
  struct vr4300_exdc_latch *exdc_latch = &vr4300->pipeline.exdc_latch;
  enum vr4300_fmt fmt = GET_FMT(iw);
  unsigned dest = GET_FD(iw);

  uint32_t fs32, fd32;
  uint64_t result;

  switch (fmt) {
    case VR4300_FMT_S:
      fs32 = fs;

      fpu_sqrt_32(&fs32, &fd32);
      result = fd32;
      break;

    case VR4300_FMT_D:
      fpu_sqrt_64(&fs, &result);
      break;

    default:
      VR4300_INV(vr4300);
      return 1;
  }

  exdc_latch->result = result;
  exdc_latch->dest = dest;
  return vr4300_do_mci(vr4300,
    fmt == VR4300_FMT_D ? 58 : 29);
}

//
// SUB.fmt
//
int VR4300_CP1_SUB(struct vr4300 *vr4300,
  uint32_t iw, uint64_t fs, uint64_t ft) {
  struct vr4300_exdc_latch *exdc_latch = &vr4300->pipeline.exdc_latch;
  enum vr4300_fmt fmt = GET_FMT(iw);
  unsigned dest = GET_FD(iw);

  uint32_t fs32, ft32, fd32;
  uint64_t result;

  switch (fmt) {
    case VR4300_FMT_S:
      fs32 = fs;
      ft32 = ft;

      fpu_sub_32(&fs32, &ft32, &fd32);
      result = fd32;
      break;

    case VR4300_FMT_D:
      fpu_sub_64(&fs, &ft, &result);
      break;

    default:
      VR4300_INV(vr4300);
      return 1;
  }

  exdc_latch->result = result;
  exdc_latch->dest = dest;
  return vr4300_do_mci(vr4300, 3);
}

//
// SWC1
//
// TODO/FIXME: Check for unaligned addresses.
//
int VR4300_SWC1(struct vr4300 *vr4300,
  uint32_t iw, uint64_t rs, uint64_t ft) {
  struct vr4300_exdc_latch *exdc_latch = &vr4300->pipeline.exdc_latch;
  uint32_t status = vr4300->regs[VR4300_CP0_REGISTER_STATUS];
  unsigned ft_reg = GET_FT(iw);

  if (!(status & 0x04000000))
    ft >>= ((ft_reg & 0x1) << 5);

  exdc_latch->request.vaddr = rs + (int16_t) iw;
  exdc_latch->request.data = ft;
  exdc_latch->request.wdqm = ~0U;
  exdc_latch->request.access_type = VR4300_ACCESS_WORD;
  exdc_latch->request.type = VR4300_BUS_REQUEST_WRITE;
  exdc_latch->request.size = 4;

  return 0;
}

//
// TRUNC.l.fmt
//
int VR4300_CP1_TRUNC_L(struct vr4300 *vr4300,
  uint32_t iw, uint64_t fs, uint64_t ft) {
  struct vr4300_exdc_latch *exdc_latch = &vr4300->pipeline.exdc_latch;
  enum vr4300_fmt fmt = GET_FMT(iw);
  unsigned dest = GET_FD(iw);

  uint32_t fs32;
  uint64_t result;

  switch (fmt) {
    case VR4300_FMT_S:
      fs32 = fs;

      fpu_trunc_i64_f32(&fs32, &result);
      break;

    case VR4300_FMT_D:
      fpu_trunc_i64_f64(&fs, &result);
      break;

    default:
      VR4300_INV(vr4300);
      return 1;
  }

  exdc_latch->result = result;
  exdc_latch->dest = dest;
  return vr4300_do_mci(vr4300, 5);
}

//
// TRUNC.w.fmt
//
int VR4300_CP1_TRUNC_W(struct vr4300 *vr4300,
  uint32_t iw, uint64_t fs, uint64_t ft) {
  struct vr4300_exdc_latch *exdc_latch = &vr4300->pipeline.exdc_latch;
  enum vr4300_fmt fmt = GET_FMT(iw);
  unsigned dest = GET_FD(iw);

  uint32_t fs32;
  uint32_t result;

  switch (fmt) {
    case VR4300_FMT_S:
      fs32 = fs;

      fpu_trunc_i32_f32(&fs32, &result);
      break;

    case VR4300_FMT_D:
      fpu_trunc_i32_f64(&fs, &result);
      break;

    default:
      VR4300_INV(vr4300);
      return 1;
  }

  exdc_latch->result = result;
  exdc_latch->dest = dest;
  return vr4300_do_mci(vr4300, 5);
}

// Initializes the coprocessor.
void vr4300_cp1_init(struct vr4300 *vr4300) {
  fpu_set_state(FPU_ROUND_NEAREST | FPU_MASK_EXCPS);
  vr4300->regs[VR4300_CP1_FCR0] = 0xa00; // hardcoded fpu version of both 0xb22 and 0xb10 N64s
}
