/* frv simulator support code
   Copyright (C) 1998-2018 Free Software Foundation, Inc.
   Contributed by Red Hat.

This file is part of the GNU simulators.

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/>.  */

#define WANT_CPU
#define WANT_CPU_FRVBF

#include "sim-main.h"
#include "cgen-mem.h"
#include "cgen-ops.h"
#include "cgen-engine.h"
#include "cgen-par.h"
#include "bfd.h"
#include "gdb/sim-frv.h"
#include <math.h>

/* Maintain a flag in order to know when to write the address of the next
   VLIW instruction into the LR register.  Used by JMPL. JMPIL, and CALL
   insns.  */
int frvbf_write_next_vliw_addr_to_LR;

/* The contents of BUF are in target byte order.  */
int
frvbf_fetch_register (SIM_CPU *current_cpu, int rn, unsigned char *buf, int len)
{
  if (SIM_FRV_GR0_REGNUM <= rn && rn <= SIM_FRV_GR63_REGNUM)
    {
      int hi_available, lo_available;
      int grn = rn - SIM_FRV_GR0_REGNUM;

      frv_gr_registers_available (current_cpu, &hi_available, &lo_available);

      if ((grn < 32 && !lo_available) || (grn >= 32 && !hi_available))
	return 0;
      else
	SETTSI (buf, GET_H_GR (grn));
    }
  else if (SIM_FRV_FR0_REGNUM <= rn && rn <= SIM_FRV_FR63_REGNUM)
    {
      int hi_available, lo_available;
      int frn = rn - SIM_FRV_FR0_REGNUM;

      frv_fr_registers_available (current_cpu, &hi_available, &lo_available);

      if ((frn < 32 && !lo_available) || (frn >= 32 && !hi_available))
	return 0;
      else
	SETTSI (buf, GET_H_FR (frn));
    }
  else if (rn == SIM_FRV_PC_REGNUM)
    SETTSI (buf, GET_H_PC ());
  else if (SIM_FRV_SPR0_REGNUM <= rn && rn <= SIM_FRV_SPR4095_REGNUM)
    {
      /* Make sure the register is implemented.  */
      FRV_REGISTER_CONTROL *control = CPU_REGISTER_CONTROL (current_cpu);
      int spr = rn - SIM_FRV_SPR0_REGNUM;
      if (! control->spr[spr].implemented)
	return 0;
      SETTSI (buf, GET_H_SPR (spr));
    }
  else
    {
      SETTSI (buf, 0xdeadbeef);
      return 0;
    }

  return len;
}

/* The contents of BUF are in target byte order.  */

int
frvbf_store_register (SIM_CPU *current_cpu, int rn, unsigned char *buf, int len)
{
  if (SIM_FRV_GR0_REGNUM <= rn && rn <= SIM_FRV_GR63_REGNUM)
    {
      int hi_available, lo_available;
      int grn = rn - SIM_FRV_GR0_REGNUM;

      frv_gr_registers_available (current_cpu, &hi_available, &lo_available);

      if ((grn < 32 && !lo_available) || (grn >= 32 && !hi_available))
	return 0;
      else
	SET_H_GR (grn, GETTSI (buf));
    }
  else if (SIM_FRV_FR0_REGNUM <= rn && rn <= SIM_FRV_FR63_REGNUM)
    {
      int hi_available, lo_available;
      int frn = rn - SIM_FRV_FR0_REGNUM;

      frv_fr_registers_available (current_cpu, &hi_available, &lo_available);

      if ((frn < 32 && !lo_available) || (frn >= 32 && !hi_available))
	return 0;
      else
	SET_H_FR (frn, GETTSI (buf));
    }
  else if (rn == SIM_FRV_PC_REGNUM)
    SET_H_PC (GETTSI (buf));
  else if (SIM_FRV_SPR0_REGNUM <= rn && rn <= SIM_FRV_SPR4095_REGNUM)
    {
      /* Make sure the register is implemented.  */
      FRV_REGISTER_CONTROL *control = CPU_REGISTER_CONTROL (current_cpu);
      int spr = rn - SIM_FRV_SPR0_REGNUM;
      if (! control->spr[spr].implemented)
	return 0;
      SET_H_SPR (spr, GETTSI (buf));
    }
  else
    return 0;

  return len;
}

/* Cover fns to access the general registers.  */
USI
frvbf_h_gr_get_handler (SIM_CPU *current_cpu, UINT gr)
{
  frv_check_gr_access (current_cpu, gr);
  return CPU (h_gr[gr]);
}

void
frvbf_h_gr_set_handler (SIM_CPU *current_cpu, UINT gr, USI newval)
{
  frv_check_gr_access (current_cpu, gr);

  if (gr == 0)
    return; /* Storing into gr0 has no effect.  */

  CPU (h_gr[gr]) = newval;
}

/* Cover fns to access the floating point registers.  */
SF
frvbf_h_fr_get_handler (SIM_CPU *current_cpu, UINT fr)
{
  frv_check_fr_access (current_cpu, fr);
  return CPU (h_fr[fr]);
}

void
frvbf_h_fr_set_handler (SIM_CPU *current_cpu, UINT fr, SF newval)
{
  frv_check_fr_access (current_cpu, fr);
  CPU (h_fr[fr]) = newval;
}

/* Cover fns to access the general registers as double words.  */
static UINT
check_register_alignment (SIM_CPU *current_cpu, UINT reg, int align_mask)
{
  if (reg & align_mask)
    {
      SIM_DESC sd = CPU_STATE (current_cpu);
      switch (STATE_ARCHITECTURE (sd)->mach)
	{
	  /* Note: there is a discrepancy between V2.2 of the FR400 
	     instruction manual and the various FR4xx LSI specs.
	     The former claims that unaligned registers cause a
	     register_exception while the latter say it's an
	     illegal_instruction.  The LSI specs appear to be
	     correct; in fact, the FR4xx series is not documented
	     as having a register_exception.  */
	case bfd_mach_fr400:
	case bfd_mach_fr450:
	case bfd_mach_fr550:
	  frv_queue_program_interrupt (current_cpu, FRV_ILLEGAL_INSTRUCTION);
	  break;
	case bfd_mach_frvtomcat:
	case bfd_mach_fr500:
	case bfd_mach_frv:
	  frv_queue_register_exception_interrupt (current_cpu,
						  FRV_REC_UNALIGNED);
	  break;
	default:
	  break;
	}

      reg &= ~align_mask;
    }

  return reg;
}

static UINT
check_fr_register_alignment (SIM_CPU *current_cpu, UINT reg, int align_mask)
{
  if (reg & align_mask)
    {
      SIM_DESC sd = CPU_STATE (current_cpu);
      switch (STATE_ARCHITECTURE (sd)->mach)
	{
	  /* See comment in check_register_alignment().  */
	case bfd_mach_fr400:
	case bfd_mach_fr450:
	case bfd_mach_fr550:
	  frv_queue_program_interrupt (current_cpu, FRV_ILLEGAL_INSTRUCTION);
	  break;
	case bfd_mach_frvtomcat:
	case bfd_mach_fr500:
	case bfd_mach_frv:
	  {
	    struct frv_fp_exception_info fp_info = {
	      FSR_NO_EXCEPTION, FTT_INVALID_FR
	    };
	    frv_queue_fp_exception_interrupt (current_cpu, & fp_info);
	  }
	  break;
	default:
	  break;
	}

      reg &= ~align_mask;
    }

  return reg;
}

static UINT
check_memory_alignment (SIM_CPU *current_cpu, SI address, int align_mask)
{
  if (address & align_mask)
    {
      SIM_DESC sd = CPU_STATE (current_cpu);
      switch (STATE_ARCHITECTURE (sd)->mach)
	{
	  /* See comment in check_register_alignment().  */
	case bfd_mach_fr400:
	case bfd_mach_fr450:
	  frv_queue_data_access_error_interrupt (current_cpu, address);
	  break;
	case bfd_mach_frvtomcat:
	case bfd_mach_fr500:
	case bfd_mach_frv:
	  frv_queue_mem_address_not_aligned_interrupt (current_cpu, address);
	  break;
	default:
	  break;
	}

      address &= ~align_mask;
    }

  return address;
}

DI
frvbf_h_gr_double_get_handler (SIM_CPU *current_cpu, UINT gr)
{
  DI value;

  if (gr == 0)
    return 0; /* gr0 is always 0.  */

  /* Check the register alignment.  */
  gr = check_register_alignment (current_cpu, gr, 1);

  value = GET_H_GR (gr);
  value <<= 32;
  value |=  (USI) GET_H_GR (gr + 1);
  return value;
}

void
frvbf_h_gr_double_set_handler (SIM_CPU *current_cpu, UINT gr, DI newval)
{
  if (gr == 0)
    return; /* Storing into gr0 has no effect.  */

  /* Check the register alignment.  */
  gr = check_register_alignment (current_cpu, gr, 1);

  SET_H_GR (gr    , (newval >> 32) & 0xffffffff);
  SET_H_GR (gr + 1, (newval      ) & 0xffffffff);
}

/* Cover fns to access the floating point register as double words.  */
DF
frvbf_h_fr_double_get_handler (SIM_CPU *current_cpu, UINT fr)
{
  union {
    SF as_sf[2];
    DF as_df;
  } value;

  /* Check the register alignment.  */
  fr = check_fr_register_alignment (current_cpu, fr, 1);

  if (HOST_BYTE_ORDER == BFD_ENDIAN_LITTLE)
    {
      value.as_sf[1] = GET_H_FR (fr);
      value.as_sf[0] = GET_H_FR (fr + 1);
    }
  else
    {
      value.as_sf[0] = GET_H_FR (fr);
      value.as_sf[1] = GET_H_FR (fr + 1);
    }

  return value.as_df;
}

void
frvbf_h_fr_double_set_handler (SIM_CPU *current_cpu, UINT fr, DF newval)
{
  union {
    SF as_sf[2];
    DF as_df;
  } value;

  /* Check the register alignment.  */
  fr = check_fr_register_alignment (current_cpu, fr, 1);

  value.as_df = newval;
  if (HOST_BYTE_ORDER == BFD_ENDIAN_LITTLE)
    {
      SET_H_FR (fr    , value.as_sf[1]);
      SET_H_FR (fr + 1, value.as_sf[0]);
    }
  else
    {
      SET_H_FR (fr    , value.as_sf[0]);
      SET_H_FR (fr + 1, value.as_sf[1]);
    }
}

/* Cover fns to access the floating point register as integer words.  */
USI
frvbf_h_fr_int_get_handler (SIM_CPU *current_cpu, UINT fr)
{
  union {
    SF  as_sf;
    USI as_usi;
  } value;

  value.as_sf = GET_H_FR (fr);
  return value.as_usi;
}

void
frvbf_h_fr_int_set_handler (SIM_CPU *current_cpu, UINT fr, USI newval)
{
  union {
    SF  as_sf;
    USI as_usi;
  } value;

  value.as_usi = newval;
  SET_H_FR (fr, value.as_sf);
}

/* Cover fns to access the coprocessor registers as double words.  */
DI
frvbf_h_cpr_double_get_handler (SIM_CPU *current_cpu, UINT cpr)
{
  DI value;

  /* Check the register alignment.  */
  cpr = check_register_alignment (current_cpu, cpr, 1);

  value = GET_H_CPR (cpr);
  value <<= 32;
  value |=  (USI) GET_H_CPR (cpr + 1);
  return value;
}

void
frvbf_h_cpr_double_set_handler (SIM_CPU *current_cpu, UINT cpr, DI newval)
{
  /* Check the register alignment.  */
  cpr = check_register_alignment (current_cpu, cpr, 1);

  SET_H_CPR (cpr    , (newval >> 32) & 0xffffffff);
  SET_H_CPR (cpr + 1, (newval      ) & 0xffffffff);
}

/* Cover fns to write registers as quad words.  */
void
frvbf_h_gr_quad_set_handler (SIM_CPU *current_cpu, UINT gr, SI *newval)
{
  if (gr == 0)
    return; /* Storing into gr0 has no effect.  */

  /* Check the register alignment.  */
  gr = check_register_alignment (current_cpu, gr, 3);

  SET_H_GR (gr    , newval[0]);
  SET_H_GR (gr + 1, newval[1]);
  SET_H_GR (gr + 2, newval[2]);
  SET_H_GR (gr + 3, newval[3]);
}

void
frvbf_h_fr_quad_set_handler (SIM_CPU *current_cpu, UINT fr, SI *newval)
{
  /* Check the register alignment.  */
  fr = check_fr_register_alignment (current_cpu, fr, 3);

  SET_H_FR (fr    , newval[0]);
  SET_H_FR (fr + 1, newval[1]);
  SET_H_FR (fr + 2, newval[2]);
  SET_H_FR (fr + 3, newval[3]);
}

void
frvbf_h_cpr_quad_set_handler (SIM_CPU *current_cpu, UINT cpr, SI *newval)
{
  /* Check the register alignment.  */
  cpr = check_register_alignment (current_cpu, cpr, 3);

  SET_H_CPR (cpr    , newval[0]);
  SET_H_CPR (cpr + 1, newval[1]);
  SET_H_CPR (cpr + 2, newval[2]);
  SET_H_CPR (cpr + 3, newval[3]);
}

/* Cover fns to access the special purpose registers.  */
USI
frvbf_h_spr_get_handler (SIM_CPU *current_cpu, UINT spr)
{
  /* Check access restrictions.  */
  frv_check_spr_read_access (current_cpu, spr);

  switch (spr)
    {
    case H_SPR_PSR:
      return spr_psr_get_handler (current_cpu);
    case H_SPR_TBR:
      return spr_tbr_get_handler (current_cpu);
    case H_SPR_BPSR:
      return spr_bpsr_get_handler (current_cpu);
    case H_SPR_CCR:
      return spr_ccr_get_handler (current_cpu);
    case H_SPR_CCCR:
      return spr_cccr_get_handler (current_cpu);
    case H_SPR_SR0:
    case H_SPR_SR1:
    case H_SPR_SR2:
    case H_SPR_SR3:
      return spr_sr_get_handler (current_cpu, spr);
      break;
    default:
      return CPU (h_spr[spr]);
    }
  return 0;
}

void
frvbf_h_spr_set_handler (SIM_CPU *current_cpu, UINT spr, USI newval)
{
  FRV_REGISTER_CONTROL *control;
  USI mask;
  USI oldval;

  /* Check access restrictions.  */
  frv_check_spr_write_access (current_cpu, spr);

  /* Only set those fields which are writeable.  */
  control = CPU_REGISTER_CONTROL (current_cpu);
  mask = control->spr[spr].read_only_mask;
  oldval = GET_H_SPR (spr);

  newval = (newval & ~mask) | (oldval & mask);

  /* Some registers are represented by individual components which are
     referenced more often than the register itself.  */
  switch (spr)
    {
    case H_SPR_PSR:
      spr_psr_set_handler (current_cpu, newval);
      break;
    case H_SPR_TBR:
      spr_tbr_set_handler (current_cpu, newval);
      break;
    case H_SPR_BPSR:
      spr_bpsr_set_handler (current_cpu, newval);
      break;
    case H_SPR_CCR:
      spr_ccr_set_handler (current_cpu, newval);
      break;
    case H_SPR_CCCR:
      spr_cccr_set_handler (current_cpu, newval);
      break;
    case H_SPR_SR0:
    case H_SPR_SR1:
    case H_SPR_SR2:
    case H_SPR_SR3:
      spr_sr_set_handler (current_cpu, spr, newval);
      break;
    case H_SPR_IHSR8:
      frv_cache_reconfigure (current_cpu, CPU_INSN_CACHE (current_cpu));
      break;
    default:
      CPU (h_spr[spr]) = newval;
      break;
    }
}

/* Cover fns to access the gr_hi and gr_lo registers.  */
UHI
frvbf_h_gr_hi_get_handler (SIM_CPU *current_cpu, UINT gr)
{
  return (GET_H_GR(gr) >> 16) & 0xffff;
}

void
frvbf_h_gr_hi_set_handler (SIM_CPU *current_cpu, UINT gr, UHI newval)
{
  USI value = (GET_H_GR (gr) & 0xffff) | (newval << 16);
  SET_H_GR (gr, value);
}

UHI
frvbf_h_gr_lo_get_handler (SIM_CPU *current_cpu, UINT gr)
{
  return GET_H_GR(gr) & 0xffff;
}

void
frvbf_h_gr_lo_set_handler (SIM_CPU *current_cpu, UINT gr, UHI newval)
{
  USI value = (GET_H_GR (gr) & 0xffff0000) | (newval & 0xffff);
  SET_H_GR (gr, value);
}

/* Cover fns to access the tbr bits.  */
USI
spr_tbr_get_handler (SIM_CPU *current_cpu)
{
  int tbr = ((GET_H_TBR_TBA () & 0xfffff) << 12) |
            ((GET_H_TBR_TT  () &  0xff) <<  4);

  return tbr;
}

void
spr_tbr_set_handler (SIM_CPU *current_cpu, USI newval)
{
  int tbr = newval;

  SET_H_TBR_TBA ((tbr >> 12) & 0xfffff) ;
  SET_H_TBR_TT  ((tbr >>  4) & 0xff) ;
}

/* Cover fns to access the bpsr bits.  */
USI
spr_bpsr_get_handler (SIM_CPU *current_cpu)
{
  int bpsr = ((GET_H_BPSR_BS  () & 0x1) << 12) |
             ((GET_H_BPSR_BET () & 0x1)      );

  return bpsr;
}

void
spr_bpsr_set_handler (SIM_CPU *current_cpu, USI newval)
{
  int bpsr = newval;

  SET_H_BPSR_BS  ((bpsr >> 12) & 1);
  SET_H_BPSR_BET ((bpsr      ) & 1);
}

/* Cover fns to access the psr bits.  */
USI
spr_psr_get_handler (SIM_CPU *current_cpu)
{
  int psr = ((GET_H_PSR_IMPLE () & 0xf) << 28) |
            ((GET_H_PSR_VER   () & 0xf) << 24) |
            ((GET_H_PSR_ICE   () & 0x1) << 16) |
            ((GET_H_PSR_NEM   () & 0x1) << 14) |
            ((GET_H_PSR_CM    () & 0x1) << 13) |
            ((GET_H_PSR_BE    () & 0x1) << 12) |
            ((GET_H_PSR_ESR   () & 0x1) << 11) |
            ((GET_H_PSR_EF    () & 0x1) <<  8) |
            ((GET_H_PSR_EM    () & 0x1) <<  7) |
            ((GET_H_PSR_PIL   () & 0xf) <<  3) |
            ((GET_H_PSR_S     () & 0x1) <<  2) |
            ((GET_H_PSR_PS    () & 0x1) <<  1) |
            ((GET_H_PSR_ET    () & 0x1)      );

  return psr;
}

void
spr_psr_set_handler (SIM_CPU *current_cpu, USI newval)
{
  /* The handler for PSR.S references the value of PSR.ESR, so set PSR.S
     first.  */
  SET_H_PSR_S ((newval >>  2) & 1);

  SET_H_PSR_IMPLE ((newval >> 28) & 0xf);
  SET_H_PSR_VER   ((newval >> 24) & 0xf);
  SET_H_PSR_ICE   ((newval >> 16) & 1);
  SET_H_PSR_NEM   ((newval >> 14) & 1);
  SET_H_PSR_CM    ((newval >> 13) & 1);
  SET_H_PSR_BE    ((newval >> 12) & 1);
  SET_H_PSR_ESR   ((newval >> 11) & 1);
  SET_H_PSR_EF    ((newval >>  8) & 1);
  SET_H_PSR_EM    ((newval >>  7) & 1);
  SET_H_PSR_PIL   ((newval >>  3) & 0xf);
  SET_H_PSR_PS    ((newval >>  1) & 1);
  SET_H_PSR_ET    ((newval      ) & 1);
}

void
frvbf_h_psr_s_set_handler (SIM_CPU *current_cpu, BI newval)
{
  /* If switching from user to supervisor mode, or vice-versa, then switch
     the supervisor/user context.  */
  int psr_s = GET_H_PSR_S ();
  if (psr_s != (newval & 1))
    {
      frvbf_switch_supervisor_user_context (current_cpu);
      CPU (h_psr_s) = newval & 1;
    }
}

/* Cover fns to access the ccr bits.  */
USI
spr_ccr_get_handler (SIM_CPU *current_cpu)
{
  int ccr = ((GET_H_ICCR (H_ICCR_ICC3) & 0xf) << 28) |
            ((GET_H_ICCR (H_ICCR_ICC2) & 0xf) << 24) |
            ((GET_H_ICCR (H_ICCR_ICC1) & 0xf) << 20) |
            ((GET_H_ICCR (H_ICCR_ICC0) & 0xf) << 16) |
            ((GET_H_FCCR (H_FCCR_FCC3) & 0xf) << 12) |
            ((GET_H_FCCR (H_FCCR_FCC2) & 0xf) <<  8) |
            ((GET_H_FCCR (H_FCCR_FCC1) & 0xf) <<  4) |
            ((GET_H_FCCR (H_FCCR_FCC0) & 0xf)      );

  return ccr;
}

void
spr_ccr_set_handler (SIM_CPU *current_cpu, USI newval)
{
  int ccr = newval;

  SET_H_ICCR (H_ICCR_ICC3, (newval >> 28) & 0xf);
  SET_H_ICCR (H_ICCR_ICC2, (newval >> 24) & 0xf);
  SET_H_ICCR (H_ICCR_ICC1, (newval >> 20) & 0xf);
  SET_H_ICCR (H_ICCR_ICC0, (newval >> 16) & 0xf);
  SET_H_FCCR (H_FCCR_FCC3, (newval >> 12) & 0xf);
  SET_H_FCCR (H_FCCR_FCC2, (newval >>  8) & 0xf);
  SET_H_FCCR (H_FCCR_FCC1, (newval >>  4) & 0xf);
  SET_H_FCCR (H_FCCR_FCC0, (newval      ) & 0xf);
}

QI
frvbf_set_icc_for_shift_right (
  SIM_CPU *current_cpu, SI value, SI shift, QI icc
)
{
  /* Set the C flag of the given icc to the logical OR of the bits shifted
     out.  */
  int mask = (1 << shift) - 1;
  if ((value & mask) != 0)
    return icc | 0x1;

  return icc & 0xe;
}

QI
frvbf_set_icc_for_shift_left (
  SIM_CPU *current_cpu, SI value, SI shift, QI icc
)
{
  /* Set the V flag of the given icc to the logical OR of the bits shifted
     out.  */
  int mask = ((1 << shift) - 1) << (32 - shift);
  if ((value & mask) != 0)
    return icc | 0x2;

  return icc & 0xd;
}

/* Cover fns to access the cccr bits.  */
USI
spr_cccr_get_handler (SIM_CPU *current_cpu)
{
  int cccr = ((GET_H_CCCR (H_CCCR_CC7) & 0x3) << 14) |
             ((GET_H_CCCR (H_CCCR_CC6) & 0x3) << 12) |
             ((GET_H_CCCR (H_CCCR_CC5) & 0x3) << 10) |
             ((GET_H_CCCR (H_CCCR_CC4) & 0x3) <<  8) |
             ((GET_H_CCCR (H_CCCR_CC3) & 0x3) <<  6) |
             ((GET_H_CCCR (H_CCCR_CC2) & 0x3) <<  4) |
             ((GET_H_CCCR (H_CCCR_CC1) & 0x3) <<  2) |
             ((GET_H_CCCR (H_CCCR_CC0) & 0x3)      );

  return cccr;
}

void
spr_cccr_set_handler (SIM_CPU *current_cpu, USI newval)
{
  int cccr = newval;

  SET_H_CCCR (H_CCCR_CC7, (newval >> 14) & 0x3);
  SET_H_CCCR (H_CCCR_CC6, (newval >> 12) & 0x3);
  SET_H_CCCR (H_CCCR_CC5, (newval >> 10) & 0x3);
  SET_H_CCCR (H_CCCR_CC4, (newval >>  8) & 0x3);
  SET_H_CCCR (H_CCCR_CC3, (newval >>  6) & 0x3);
  SET_H_CCCR (H_CCCR_CC2, (newval >>  4) & 0x3);
  SET_H_CCCR (H_CCCR_CC1, (newval >>  2) & 0x3);
  SET_H_CCCR (H_CCCR_CC0, (newval      ) & 0x3);
}

/* Cover fns to access the sr bits.  */
USI
spr_sr_get_handler (SIM_CPU *current_cpu, UINT spr)
{
  /* If PSR.ESR is not set, then SR0-3 map onto SGR4-7 which will be GR4-7,
     otherwise the correct mapping of USG4-7 or SGR4-7 will be in SR0-3.  */
  int psr_esr = GET_H_PSR_ESR ();
  if (! psr_esr)
    return GET_H_GR (4 + (spr - H_SPR_SR0));

  return CPU (h_spr[spr]);
}

void
spr_sr_set_handler (SIM_CPU *current_cpu, UINT spr, USI newval)
{
  /* If PSR.ESR is not set, then SR0-3 map onto SGR4-7 which will be GR4-7,
     otherwise the correct mapping of USG4-7 or SGR4-7 will be in SR0-3.  */
  int psr_esr = GET_H_PSR_ESR ();
  if (! psr_esr)
    SET_H_GR (4 + (spr - H_SPR_SR0), newval);
  else
    CPU (h_spr[spr]) = newval;
}

/* Switch SR0-SR4 with GR4-GR7 if PSR.ESR is set.  */
void
frvbf_switch_supervisor_user_context (SIM_CPU *current_cpu)
{
  if (GET_H_PSR_ESR ())
    {
      /* We need to be in supervisor mode to swap the registers. Access the
	 PSR.S directly in order to avoid recursive context switches.  */
      int i;
      int save_psr_s = CPU (h_psr_s);
      CPU (h_psr_s) = 1;
      for (i = 0; i < 4; ++i)
	{
	  int gr = i + 4;
	  int spr = i + H_SPR_SR0;
	  SI tmp = GET_H_SPR (spr);
	  SET_H_SPR (spr, GET_H_GR (gr));
	  SET_H_GR (gr, tmp);
	}
      CPU (h_psr_s) = save_psr_s;
    }
}

/* Handle load/store of quad registers.  */
void
frvbf_load_quad_GR (SIM_CPU *current_cpu, PCADDR pc, SI address, SI targ_ix)
{
  int i;
  SI value[4];

  /* Check memory alignment */
  address = check_memory_alignment (current_cpu, address, 0xf);

  /* If we need to count cycles, then the cache operation will be
     initiated from the model profiling functions.
     See frvbf_model_....  */
  if (model_insn)
    {
      CPU_LOAD_ADDRESS (current_cpu) = address;
      CPU_LOAD_LENGTH (current_cpu) = 16;
    }
  else
    {
      for (i = 0; i < 4; ++i)
	{
	  value[i] = frvbf_read_mem_SI (current_cpu, pc, address);
	  address += 4;
	}
      sim_queue_fn_xi_write (current_cpu, frvbf_h_gr_quad_set_handler, targ_ix,
			     value);
    }
}

void
frvbf_store_quad_GR (SIM_CPU *current_cpu, PCADDR pc, SI address, SI src_ix)
{
  int i;
  SI value[4];
  USI hsr0;

  /* Check register and memory alignment.  */
  src_ix = check_register_alignment (current_cpu, src_ix, 3);
  address = check_memory_alignment (current_cpu, address, 0xf);

  for (i = 0; i < 4; ++i)
    {
      /* GR0 is always 0.  */
      if (src_ix == 0)
	value[i] = 0;
      else
	value[i] = GET_H_GR (src_ix + i);
    }
  hsr0 = GET_HSR0 ();
  if (GET_HSR0_DCE (hsr0))
    sim_queue_fn_mem_xi_write (current_cpu, frvbf_mem_set_XI, address, value);
  else
    sim_queue_mem_xi_write (current_cpu, address, value);
}

void
frvbf_load_quad_FRint (SIM_CPU *current_cpu, PCADDR pc, SI address, SI targ_ix)
{
  int i;
  SI value[4];

  /* Check memory alignment */
  address = check_memory_alignment (current_cpu, address, 0xf);

  /* If we need to count cycles, then the cache operation will be
     initiated from the model profiling functions.
     See frvbf_model_....  */
  if (model_insn)
    {
      CPU_LOAD_ADDRESS (current_cpu) = address;
      CPU_LOAD_LENGTH (current_cpu) = 16;
    }
  else
    {
      for (i = 0; i < 4; ++i)
	{
	  value[i] = frvbf_read_mem_SI (current_cpu, pc, address);
	  address += 4;
	}
      sim_queue_fn_xi_write (current_cpu, frvbf_h_fr_quad_set_handler, targ_ix,
			     value);
    }
}

void
frvbf_store_quad_FRint (SIM_CPU *current_cpu, PCADDR pc, SI address, SI src_ix)
{
  int i;
  SI value[4];
  USI hsr0;

  /* Check register and memory alignment.  */
  src_ix = check_fr_register_alignment (current_cpu, src_ix, 3);
  address = check_memory_alignment (current_cpu, address, 0xf);

  for (i = 0; i < 4; ++i)
    value[i] = GET_H_FR (src_ix + i);

  hsr0 = GET_HSR0 ();
  if (GET_HSR0_DCE (hsr0))
    sim_queue_fn_mem_xi_write (current_cpu, frvbf_mem_set_XI, address, value);
  else
    sim_queue_mem_xi_write (current_cpu, address, value);
}

void
frvbf_load_quad_CPR (SIM_CPU *current_cpu, PCADDR pc, SI address, SI targ_ix)
{
  int i;
  SI value[4];

  /* Check memory alignment */
  address = check_memory_alignment (current_cpu, address, 0xf);

  /* If we need to count cycles, then the cache operation will be
     initiated from the model profiling functions.
     See frvbf_model_....  */
  if (model_insn)
    {
      CPU_LOAD_ADDRESS (current_cpu) = address;
      CPU_LOAD_LENGTH (current_cpu) = 16;
    }
  else
    {
      for (i = 0; i < 4; ++i)
	{
	  value[i] = frvbf_read_mem_SI (current_cpu, pc, address);
	  address += 4;
	}
      sim_queue_fn_xi_write (current_cpu, frvbf_h_cpr_quad_set_handler, targ_ix,
			     value);
    }
}

void
frvbf_store_quad_CPR (SIM_CPU *current_cpu, PCADDR pc, SI address, SI src_ix)
{
  int i;
  SI value[4];
  USI hsr0;

  /* Check register and memory alignment.  */
  src_ix = check_register_alignment (current_cpu, src_ix, 3);
  address = check_memory_alignment (current_cpu, address, 0xf);

  for (i = 0; i < 4; ++i)
    value[i] = GET_H_CPR (src_ix + i);

  hsr0 = GET_HSR0 ();
  if (GET_HSR0_DCE (hsr0))
    sim_queue_fn_mem_xi_write (current_cpu, frvbf_mem_set_XI, address, value);
  else
    sim_queue_mem_xi_write (current_cpu, address, value);
}

void
frvbf_signed_integer_divide (
  SIM_CPU *current_cpu, SI arg1, SI arg2, int target_index, int non_excepting
)
{
  enum frv_dtt dtt = FRV_DTT_NO_EXCEPTION;
  if (arg1 == 0x80000000 && arg2 == -1)
    {
      /* 0x80000000/(-1) must result in 0x7fffffff when ISR.EDE is set
	 otherwise it may result in 0x7fffffff (sparc compatibility) or
	 0x80000000 (C language compatibility). */
      USI isr;
      dtt = FRV_DTT_OVERFLOW;

      isr = GET_ISR ();
      if (GET_ISR_EDE (isr))
	sim_queue_fn_si_write (current_cpu, frvbf_h_gr_set, target_index,
			       0x7fffffff);
      else
	sim_queue_fn_si_write (current_cpu, frvbf_h_gr_set, target_index,
			       0x80000000);
      frvbf_force_update (current_cpu); /* Force update of target register.  */
    }
  else if (arg2 == 0)
    dtt = FRV_DTT_DIVISION_BY_ZERO;
  else
    sim_queue_fn_si_write (current_cpu, frvbf_h_gr_set, target_index,
			   arg1 / arg2);

  /* Check for exceptions.  */
  if (dtt != FRV_DTT_NO_EXCEPTION)
    dtt = frvbf_division_exception (current_cpu, dtt, target_index,
				    non_excepting);
  if (non_excepting && dtt == FRV_DTT_NO_EXCEPTION)
    {
      /* Non excepting instruction. Clear the NE flag for the target
	 register.  */
      SI NE_flags[2];
      GET_NE_FLAGS (NE_flags, H_SPR_GNER0);
      CLEAR_NE_FLAG (NE_flags, target_index);
      SET_NE_FLAGS (H_SPR_GNER0, NE_flags);
    }
}

void
frvbf_unsigned_integer_divide (
  SIM_CPU *current_cpu, USI arg1, USI arg2, int target_index, int non_excepting
)
{
  if (arg2 == 0)
    frvbf_division_exception (current_cpu, FRV_DTT_DIVISION_BY_ZERO,
			      target_index, non_excepting);
  else
    {
      sim_queue_fn_si_write (current_cpu, frvbf_h_gr_set, target_index,
			     arg1 / arg2);
      if (non_excepting)
	{
	  /* Non excepting instruction. Clear the NE flag for the target
	     register.  */
	  SI NE_flags[2];
	  GET_NE_FLAGS (NE_flags, H_SPR_GNER0);
	  CLEAR_NE_FLAG (NE_flags, target_index);
	  SET_NE_FLAGS (H_SPR_GNER0, NE_flags);
	}
    }
}

/* Clear accumulators.  */
void
frvbf_clear_accumulators (SIM_CPU *current_cpu, SI acc_ix, int A)
{
  SIM_DESC sd = CPU_STATE (current_cpu);
  int acc_mask =
    (STATE_ARCHITECTURE (sd)->mach == bfd_mach_fr500) ? 7 :
    (STATE_ARCHITECTURE (sd)->mach == bfd_mach_fr550) ? 7 :
    (STATE_ARCHITECTURE (sd)->mach == bfd_mach_fr450) ? 11 :
    (STATE_ARCHITECTURE (sd)->mach == bfd_mach_fr400) ? 3 :
    63;
  FRV_PROFILE_STATE *ps = CPU_PROFILE_STATE (current_cpu);

  ps->mclracc_acc = acc_ix;
  ps->mclracc_A   = A;
  if (A == 0 || acc_ix != 0) /* Clear 1 accumuator?  */
    {
      /* This instruction is a nop if the referenced accumulator is not
	 implemented. */
      if ((acc_ix & acc_mask) == acc_ix)
	sim_queue_fn_di_write (current_cpu, frvbf_h_acc40S_set, acc_ix, 0);
    }
  else
    {
      /* Clear all implemented accumulators.  */
      int i;
      for (i = 0; i <= acc_mask; ++i)
	if ((i & acc_mask) == i)
	  sim_queue_fn_di_write (current_cpu, frvbf_h_acc40S_set, i, 0);
    }
}

/* Functions to aid insn semantics.  */

/* Compute the result of the SCAN and SCANI insns after the shift and xor.  */
SI
frvbf_scan_result (SIM_CPU *current_cpu, SI value)
{
  SI i;
  SI mask;

  if (value == 0)
    return 63;

  /* Find the position of the first non-zero bit.
     The loop will terminate since there is guaranteed to be at least one
     non-zero bit.  */
  mask = 1 << (sizeof (mask) * 8 - 1);
  for (i = 0; (value & mask) == 0; ++i)
    value <<= 1;

  return i;
}

/* Compute the result of the cut insns.  */
SI
frvbf_cut (SIM_CPU *current_cpu, SI reg1, SI reg2, SI cut_point)
{
  SI result;
  cut_point &= 0x3f;
  if (cut_point < 32)
    {
      result = reg1 << cut_point;
      result |= (reg2 >> (32 - cut_point)) & ((1 << cut_point) - 1);
    }
  else
    result = reg2 << (cut_point - 32);

  return result;
}

/* Compute the result of the cut insns.  */
SI
frvbf_media_cut (SIM_CPU *current_cpu, DI acc, SI cut_point)
{
  /* The cut point is the lower 6 bits (signed) of what we are passed.  */
  cut_point = cut_point << 26 >> 26;

  /* The cut_point is relative to bit 40 of 64 bits.  */
  if (cut_point >= 0)
    return (acc << (cut_point + 24)) >> 32;

  /* Extend the sign bit (bit 40) for negative cuts.  */
  if (cut_point == -32)
    return (acc << 24) >> 63; /* Special case for full shiftout.  */

  return (acc << 24) >> (32 + -cut_point);
}

/* Compute the result of the cut insns.  */
SI
frvbf_media_cut_ss (SIM_CPU *current_cpu, DI acc, SI cut_point)
{
  /* The cut point is the lower 6 bits (signed) of what we are passed.  */
  cut_point = cut_point << 26 >> 26;

  if (cut_point >= 0)
    {
      /* The cut_point is relative to bit 40 of 64 bits.  */
      DI shifted = acc << (cut_point + 24);
      DI unshifted = shifted >> (cut_point + 24);

      /* The result will be saturated if significant bits are shifted out.  */
      if (unshifted != acc)
	{
	  if (acc < 0)
	    return 0x80000000;
	  return 0x7fffffff;
	}
    }

  /* The result will not be saturated, so use the code for the normal cut.  */
  return frvbf_media_cut (current_cpu, acc, cut_point);
}

/* Compute the result of int accumulator cut (SCUTSS).  */
SI
frvbf_iacc_cut (SIM_CPU *current_cpu, DI acc, SI cut_point)
{
  DI lower, upper;

  /* The cut point is the lower 7 bits (signed) of what we are passed.  */
  cut_point = cut_point << 25 >> 25;

  /* Conceptually, the operation is on a 128-bit sign-extension of ACC.
     The top bit of the return value corresponds to bit (63 - CUT_POINT)
     of this 128-bit value.

     Since we can't deal with 128-bit values very easily, convert the
     operation into an equivalent 64-bit one.  */
  if (cut_point < 0)
    {
      /* Avoid an undefined shift operation.  */
      if (cut_point == -64)
	acc >>= 63;
      else
	acc >>= -cut_point;
      cut_point = 0;
    }

  /* Get the shifted but unsaturated result.  Set LOWER to the lowest
     32 bits of the result and UPPER to the result >> 31.  */
  if (cut_point < 32)
    {
      /* The cut loses the (32 - CUT_POINT) least significant bits.
	 Round the result up if the most significant of these lost bits
	 is 1.  */
      lower = acc >> (32 - cut_point);
      if (lower < 0x7fffffff)
	if (acc & LSBIT64 (32 - cut_point - 1))
	  lower++;
      upper = lower >> 31;
    }
  else
    {
      lower = acc << (cut_point - 32);
      upper = acc >> (63 - cut_point);
    }

  /* Saturate the result.  */
  if (upper < -1)
    return ~0x7fffffff;
  else if (upper > 0)
    return 0x7fffffff;
  else
    return lower;
}

/* Compute the result of shift-left-arithmetic-with-saturation (SLASS).  */
SI
frvbf_shift_left_arith_saturate (SIM_CPU *current_cpu, SI arg1, SI arg2)
{
  int neg_arg1;

  /* FIXME: what to do with negative shift amt?  */
  if (arg2 <= 0)
    return arg1;

  if (arg1 == 0)
    return 0;

  /* Signed shift by 31 or greater saturates by definition.  */
  if (arg2 >= 31)
    if (arg1 > 0)
      return (SI) 0x7fffffff;
    else
      return (SI) 0x80000000;

  /* OK, arg2 is between 1 and 31.  */
  neg_arg1 = (arg1 < 0);
  do {
    arg1 <<= 1;
    /* Check for sign bit change (saturation).  */
    if (neg_arg1 && (arg1 >= 0))
      return (SI) 0x80000000;
    else if (!neg_arg1 && (arg1 < 0))
      return (SI) 0x7fffffff;
  } while (--arg2 > 0);

  return arg1;
}

/* Simulate the media custom insns.  */
void
frvbf_media_cop (SIM_CPU *current_cpu, int cop_num)
{
  /* The semantics of the insn are a nop, since it is implementation defined.
     We do need to check whether it's implemented and set up for MTRAP
     if it's not.  */
  USI msr0 = GET_MSR (0);
  if (GET_MSR_EMCI (msr0) == 0)
    {
      /* no interrupt queued at this time.  */
      frv_set_mp_exception_registers (current_cpu, MTT_UNIMPLEMENTED_MPOP, 0);
    }
}

/* Simulate the media average (MAVEH) insn.  */
static HI
do_media_average (SIM_CPU *current_cpu, HI arg1, HI arg2)
{
  SIM_DESC sd = CPU_STATE (current_cpu);
  SI sum = (arg1 + arg2);
  HI result = sum >> 1;
  int rounding_value;

  /* On fr4xx and fr550, check the rounding mode.  On other machines
     rounding is always toward negative infinity and the result is
     already correctly rounded.  */
  switch (STATE_ARCHITECTURE (sd)->mach)
    {
      /* Need to check rounding mode. */
    case bfd_mach_fr400:
    case bfd_mach_fr450:
    case bfd_mach_fr550:
      /* Check whether rounding will be required.  Rounding will be required
	 if the sum is an odd number.  */
      rounding_value = sum & 1;
      if (rounding_value)
	{
	  USI msr0 = GET_MSR (0);
	  /* Check MSR0.SRDAV to determine which bits control the rounding.  */
	  if (GET_MSR_SRDAV (msr0))
	    {
	      /* MSR0.RD controls rounding.  */
	      switch (GET_MSR_RD (msr0))
		{
		case 0:
		  /* Round to nearest.  */
		  if (result >= 0)
		    ++result;
		  break;
		case 1:
		  /* Round toward 0. */
		  if (result < 0)
		    ++result;
		  break;
		case 2:
		  /* Round toward positive infinity.  */
		  ++result;
		  break;
		case 3:
		  /* Round toward negative infinity.  The result is already
		     correctly rounded.  */
		  break;
		default:
		  abort ();
		  break;
		}
	    }
	  else
	    {
	      /* MSR0.RDAV controls rounding.  If set, round toward positive
		 infinity.  Otherwise the result is already rounded correctly
		 toward negative infinity.  */
	      if (GET_MSR_RDAV (msr0))
		++result;
	    }
	}
      break;
    default:
      break;
    }

  return result;
}

SI
frvbf_media_average (SIM_CPU *current_cpu, SI reg1, SI reg2)
{
  SI result;
  result  = do_media_average (current_cpu, reg1 & 0xffff, reg2 & 0xffff);
  result &= 0xffff;
  result |= do_media_average (current_cpu, (reg1 >> 16) & 0xffff,
			      (reg2 >> 16) & 0xffff) << 16;
  return result;
}

/* Maintain a flag in order to know when to write the address of the next
   VLIW instruction into the LR register.  Used by JMPL. JMPIL, and CALL.  */
void
frvbf_set_write_next_vliw_addr_to_LR (SIM_CPU *current_cpu, int value)
{
  frvbf_write_next_vliw_addr_to_LR = value;
}

void
frvbf_set_ne_index (SIM_CPU *current_cpu, int index)
{
  USI NE_flags[2];

  /* Save the target register so interrupt processing can set its NE flag
     in the event of an exception.  */
  frv_interrupt_state.ne_index = index;

  /* Clear the NE flag of the target register. It will be reset if necessary
     in the event of an exception.  */
  GET_NE_FLAGS (NE_flags, H_SPR_FNER0);
  CLEAR_NE_FLAG (NE_flags, index);
  SET_NE_FLAGS (H_SPR_FNER0, NE_flags);
}

void
frvbf_force_update (SIM_CPU *current_cpu)
{
  CGEN_WRITE_QUEUE *q = CPU_WRITE_QUEUE (current_cpu);
  int ix = CGEN_WRITE_QUEUE_INDEX (q);
  if (ix > 0)
    {
      CGEN_WRITE_QUEUE_ELEMENT *item = CGEN_WRITE_QUEUE_ELEMENT (q, ix - 1);
      item->flags |= FRV_WRITE_QUEUE_FORCE_WRITE;
    }
}

/* Condition code logic.  */
enum cr_ops {
  andcr, orcr, xorcr, nandcr, norcr, andncr, orncr, nandncr, norncr,
  num_cr_ops
};

enum cr_result {cr_undefined, cr_undefined1, cr_false, cr_true};

static enum cr_result
cr_logic[num_cr_ops][4][4] = {
  /* andcr */
  {
    /*                undefined     undefined       false         true */
    /* undefined */ {cr_undefined, cr_undefined, cr_undefined, cr_undefined},
    /* undefined */ {cr_undefined, cr_undefined, cr_undefined, cr_undefined},
    /* false     */ {cr_undefined, cr_undefined, cr_undefined, cr_undefined},
    /* true      */ {cr_undefined, cr_undefined, cr_false,     cr_true     }
  },
  /* orcr */
  {
    /*                undefined     undefined       false         true */
    /* undefined */ {cr_undefined, cr_undefined, cr_false,     cr_true     },
    /* undefined */ {cr_undefined, cr_undefined, cr_false,     cr_true     },
    /* false     */ {cr_false,     cr_false,     cr_false,     cr_true     },
    /* true      */ {cr_true,      cr_true,      cr_true,      cr_true     }
  },
  /* xorcr */
  {
    /*                undefined     undefined       false         true */
    /* undefined */ {cr_undefined, cr_undefined, cr_undefined, cr_undefined},
    /* undefined */ {cr_undefined, cr_undefined, cr_undefined, cr_undefined},
    /* false     */ {cr_undefined, cr_undefined, cr_false,     cr_true     },
    /* true      */ {cr_true,      cr_true,      cr_true,      cr_false    }
  },
  /* nandcr */
  {
    /*                undefined     undefined       false         true */
    /* undefined */ {cr_undefined, cr_undefined, cr_undefined, cr_undefined},
    /* undefined */ {cr_undefined, cr_undefined, cr_undefined, cr_undefined},
    /* false     */ {cr_undefined, cr_undefined, cr_undefined, cr_undefined},
    /* true      */ {cr_undefined, cr_undefined, cr_true,      cr_false    }
  },
  /* norcr */
  {
    /*                undefined     undefined       false         true */
    /* undefined */ {cr_undefined, cr_undefined, cr_true,      cr_false    },
    /* undefined */ {cr_undefined, cr_undefined, cr_true,      cr_false    },
    /* false     */ {cr_true,      cr_true,      cr_true,      cr_false    },
    /* true      */ {cr_false,     cr_false,     cr_false,     cr_false    }
  },
  /* andncr */
  {
    /*                undefined     undefined       false         true */
    /* undefined */ {cr_undefined, cr_undefined, cr_undefined, cr_undefined},
    /* undefined */ {cr_undefined, cr_undefined, cr_undefined, cr_undefined},
    /* false     */ {cr_undefined, cr_undefined, cr_false,     cr_true     },
    /* true      */ {cr_undefined, cr_undefined, cr_undefined, cr_undefined}
  },
  /* orncr */
  {
    /*                undefined     undefined       false         true */
    /* undefined */ {cr_undefined, cr_undefined, cr_false,     cr_true     },
    /* undefined */ {cr_undefined, cr_undefined, cr_false,     cr_true     },
    /* false     */ {cr_true,      cr_true,      cr_true,      cr_true     },
    /* true      */ {cr_false,     cr_false,     cr_false,     cr_true     }
  },
  /* nandncr */
  {
    /*                undefined     undefined       false         true */
    /* undefined */ {cr_undefined, cr_undefined, cr_undefined, cr_undefined},
    /* undefined */ {cr_undefined, cr_undefined, cr_undefined, cr_undefined},
    /* false     */ {cr_undefined, cr_undefined, cr_true,      cr_false    },
    /* true      */ {cr_undefined, cr_undefined, cr_undefined, cr_undefined}
  },
  /* norncr */
  {
    /*                undefined     undefined       false         true */
    /* undefined */ {cr_undefined, cr_undefined, cr_true,      cr_false    },
    /* undefined */ {cr_undefined, cr_undefined, cr_true,      cr_false    },
    /* false     */ {cr_false,     cr_false,     cr_false,     cr_false    },
    /* true      */ {cr_true,      cr_true,      cr_true,      cr_false    }
  }
};

UQI
frvbf_cr_logic (SIM_CPU *current_cpu, SI operation, UQI arg1, UQI arg2)
{
  return cr_logic[operation][arg1][arg2];
}

/* Cache Manipulation.  */
void
frvbf_insn_cache_preload (SIM_CPU *current_cpu, SI address, USI length, int lock)
{
  /* If we need to count cycles, then the cache operation will be
     initiated from the model profiling functions.
     See frvbf_model_....  */
  int hsr0 = GET_HSR0 ();
  if (GET_HSR0_ICE (hsr0))
    {
      if (model_insn)
	{
	  CPU_LOAD_ADDRESS (current_cpu) = address;
	  CPU_LOAD_LENGTH (current_cpu) = length;
	  CPU_LOAD_LOCK (current_cpu) = lock;
	}
      else
	{
	  FRV_CACHE *cache = CPU_INSN_CACHE (current_cpu);
	  frv_cache_preload (cache, address, length, lock);
	}
    }
}

void
frvbf_data_cache_preload (SIM_CPU *current_cpu, SI address, USI length, int lock)
{
  /* If we need to count cycles, then the cache operation will be
     initiated from the model profiling functions.
     See frvbf_model_....  */
  int hsr0 = GET_HSR0 ();
  if (GET_HSR0_DCE (hsr0))
    {
      if (model_insn)
	{
	  CPU_LOAD_ADDRESS (current_cpu) = address;
	  CPU_LOAD_LENGTH (current_cpu) = length;
	  CPU_LOAD_LOCK (current_cpu) = lock;
	}
      else
	{
	  FRV_CACHE *cache = CPU_DATA_CACHE (current_cpu);
	  frv_cache_preload (cache, address, length, lock);
	}
    }
}

void
frvbf_insn_cache_unlock (SIM_CPU *current_cpu, SI address)
{
  /* If we need to count cycles, then the cache operation will be
     initiated from the model profiling functions.
     See frvbf_model_....  */
  int hsr0 = GET_HSR0 ();
  if (GET_HSR0_ICE (hsr0))
    {
      if (model_insn)
	CPU_LOAD_ADDRESS (current_cpu) = address;
      else
	{
	  FRV_CACHE *cache = CPU_INSN_CACHE (current_cpu);
	  frv_cache_unlock (cache, address);
	}
    }
}

void
frvbf_data_cache_unlock (SIM_CPU *current_cpu, SI address)
{
  /* If we need to count cycles, then the cache operation will be
     initiated from the model profiling functions.
     See frvbf_model_....  */
  int hsr0 = GET_HSR0 ();
  if (GET_HSR0_DCE (hsr0))
    {
      if (model_insn)
	CPU_LOAD_ADDRESS (current_cpu) = address;
      else
	{
	  FRV_CACHE *cache = CPU_DATA_CACHE (current_cpu);
	  frv_cache_unlock (cache, address);
	}
    }
}

void
frvbf_insn_cache_invalidate (SIM_CPU *current_cpu, SI address, int all)
{
  /* Make sure the insn was specified properly.  -1 will be passed for ALL
     for a icei with A=0.  */
  if (all == -1)
    {
      frv_queue_program_interrupt (current_cpu, FRV_ILLEGAL_INSTRUCTION);
      return;
    }

  /* If we need to count cycles, then the cache operation will be
     initiated from the model profiling functions.
     See frvbf_model_....  */
  if (model_insn)
    {
      /* Record the all-entries flag for use in profiling.  */
      FRV_PROFILE_STATE *ps = CPU_PROFILE_STATE (current_cpu);
      ps->all_cache_entries = all;
      CPU_LOAD_ADDRESS (current_cpu) = address;
    }
  else
    {
      FRV_CACHE *cache = CPU_INSN_CACHE (current_cpu);
      if (all)
	frv_cache_invalidate_all (cache, 0/* flush? */);
      else
	frv_cache_invalidate (cache, address, 0/* flush? */);
    }
}

void
frvbf_data_cache_invalidate (SIM_CPU *current_cpu, SI address, int all)
{
  /* Make sure the insn was specified properly.  -1 will be passed for ALL
     for a dcei with A=0.  */
  if (all == -1)
    {
      frv_queue_program_interrupt (current_cpu, FRV_ILLEGAL_INSTRUCTION);
      return;
    }

  /* If we need to count cycles, then the cache operation will be
     initiated from the model profiling functions.
     See frvbf_model_....  */
  if (model_insn)
    {
      /* Record the all-entries flag for use in profiling.  */
      FRV_PROFILE_STATE *ps = CPU_PROFILE_STATE (current_cpu);
      ps->all_cache_entries = all;
      CPU_LOAD_ADDRESS (current_cpu) = address;
    }
  else
    {
      FRV_CACHE *cache = CPU_DATA_CACHE (current_cpu);
      if (all)
	frv_cache_invalidate_all (cache, 0/* flush? */);
      else
	frv_cache_invalidate (cache, address, 0/* flush? */);
    }
}

void
frvbf_data_cache_flush (SIM_CPU *current_cpu, SI address, int all)
{
  /* Make sure the insn was specified properly.  -1 will be passed for ALL
     for a dcef with A=0.  */
  if (all == -1)
    {
      frv_queue_program_interrupt (current_cpu, FRV_ILLEGAL_INSTRUCTION);
      return;
    }

  /* If we need to count cycles, then the cache operation will be
     initiated from the model profiling functions.
     See frvbf_model_....  */
  if (model_insn)
    {
      /* Record the all-entries flag for use in profiling.  */
      FRV_PROFILE_STATE *ps = CPU_PROFILE_STATE (current_cpu);
      ps->all_cache_entries = all;
      CPU_LOAD_ADDRESS (current_cpu) = address;
    }
  else
    {
      FRV_CACHE *cache = CPU_DATA_CACHE (current_cpu);
      if (all)
	frv_cache_invalidate_all (cache, 1/* flush? */);
      else
	frv_cache_invalidate (cache, address, 1/* flush? */);
    }
}
