/* CRIS base simulator support code
   Copyright (C) 2004, 2005 Free Software Foundation, Inc.
   Contributed by Axis Communications.

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 2, 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, write to the Free Software Foundation, Inc.,
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */

/* The infrastructure is based on that of i960.c.  */

#define WANT_CPU

#include "sim-main.h"
#include "cgen-mem.h"
#include "cgen-ops.h"

#define MY(f) XCONCAT3(crisv,BASENUM,f)

/* Dispatcher for break insn.  */

USI
MY (f_break_handler) (SIM_CPU *cpu, USI breaknum, USI pc)
{
  SIM_DESC sd = CPU_STATE (cpu);
  USI ret = pc + 2;

  MY (f_h_pc_set) (cpu, ret);

  /* FIXME: Error out if IBR or ERP set.  */
  switch (breaknum)
    {
    case 13:
      MY (f_h_gr_set (cpu, 10,
		      cris_break_13_handler (cpu,
					     MY (f_h_gr_get (cpu, 9)),
					     MY (f_h_gr_get (cpu, 10)),
					     MY (f_h_gr_get (cpu, 11)),
					     MY (f_h_gr_get (cpu, 12)),
					     MY (f_h_gr_get (cpu, 13)),
					     MY (f_h_sr_get (cpu, 7)),
					     MY (f_h_sr_get (cpu, 11)),
					     pc)));
      break;

    case 14:
      sim_io_printf (sd, "%x\n", MY (f_h_gr_get (cpu, 3)));
      break;

    case 15:
      /* Re-use the Linux exit call.  */
      cris_break_13_handler (cpu, /* TARGET_SYS_exit */ 1, 0,
			     0, 0, 0, 0, 0, pc);

    default:
      abort ();
    }

  return MY (f_h_pc_get) (cpu);
}

/* Accessor function for simulator internal use.
   Note the contents of BUF are in target byte order.  */

int
MY (f_fetch_register) (SIM_CPU *current_cpu, int rn,
		      unsigned char *buf, int len ATTRIBUTE_UNUSED)
{
  SETTSI (buf, XCONCAT3(crisv,BASENUM,f_h_gr_get) (current_cpu, rn));
  return -1;
}

/* Accessor function for simulator internal use.
   Note the contents of BUF are in target byte order.  */

int
MY (f_store_register) (SIM_CPU *current_cpu, int rn,
		      unsigned char *buf, int len ATTRIBUTE_UNUSED)
{
  XCONCAT3(crisv,BASENUM,f_h_gr_set) (current_cpu, rn, GETTSI (buf));
  return -1;
}

#if WITH_PROFILE_MODEL_P

/* FIXME: Some of these should be inline or macros.  Later.  */

/* Initialize cycle counting for an insn.
   FIRST_P is non-zero if this is the first insn in a set of parallel
   insns.  */

void
MY (f_model_insn_before) (SIM_CPU *current_cpu, int first_p ATTRIBUTE_UNUSED)
{
  /* To give the impression that we actually know what PC is, we have to
     dump register contents *before* the *next* insn, not after the
     *previous* insn.  Uhh...  */

  /* FIXME: Move this to separate, overridable function.  */
  if ((CPU_CRIS_MISC_PROFILE (current_cpu)->flags
       & FLAG_CRIS_MISC_PROFILE_XSIM_TRACE)
#ifdef GET_H_INSN_PREFIXED_P
      /* For versions with prefixed insns, trace the combination as
	 one insn.  */
      && !GET_H_INSN_PREFIXED_P ()
#endif
      && 1)
  {
    int i;
    char flags[7];
    SIM_DESC sd = CPU_STATE (current_cpu);

    cris_trace_printf (sd, current_cpu, "%lx ", (unsigned long) (CPU (h_pc)));

    for (i = 0; i < 15; i++)
      cris_trace_printf (sd, current_cpu, "%lx ",
			 (unsigned long) (XCONCAT3(crisv,BASENUM,
						   f_h_gr_get) (current_cpu,
								i)));
    flags[0] = GET_H_IBIT () != 0 ? 'I' : 'i';
    flags[1] = GET_H_XBIT () != 0 ? 'X' : 'x';
    flags[2] = GET_H_NBIT () != 0 ? 'N' : 'n';
    flags[3] = GET_H_ZBIT () != 0 ? 'Z' : 'z';
    flags[4] = GET_H_VBIT () != 0 ? 'V' : 'v';
    flags[5] = GET_H_CBIT () != 0 ? 'C' : 'c';
    flags[6] = 0;

    /* Emit ACR after flags and cycle count for this insn.  */
    if (BASENUM == 32)
      cris_trace_printf (sd, current_cpu, "%s %d %lx\n", flags,
			 (int)
			 ((CPU_CRIS_MISC_PROFILE (current_cpu)
			   ->basic_cycle_count
			   - CPU_CRIS_PREV_MISC_PROFILE (current_cpu)
			   ->basic_cycle_count)
			  + (CPU_CRIS_MISC_PROFILE (current_cpu)
			     ->unaligned_mem_dword_count
			     - CPU_CRIS_PREV_MISC_PROFILE (current_cpu)
			     ->unaligned_mem_dword_count)),
			 (unsigned long) (XCONCAT3(crisv,BASENUM,
						   f_h_gr_get) (current_cpu,
								15)));
    else
      cris_trace_printf (sd, current_cpu, "%s %d\n", flags,
			 (int)
			 ((CPU_CRIS_MISC_PROFILE (current_cpu)
			   ->basic_cycle_count
			   - CPU_CRIS_PREV_MISC_PROFILE (current_cpu)
			   ->basic_cycle_count)
			  + (CPU_CRIS_MISC_PROFILE (current_cpu)
			     ->unaligned_mem_dword_count
			     - CPU_CRIS_PREV_MISC_PROFILE (current_cpu)
			     ->unaligned_mem_dword_count)));

    CPU_CRIS_PREV_MISC_PROFILE (current_cpu)[0]
      = CPU_CRIS_MISC_PROFILE (current_cpu)[0];
  }
}

/* Record the cycles computed for an insn.
   LAST_P is non-zero if this is the last insn in a set of parallel insns,
   and we update the total cycle count.
   CYCLES is the cycle count of the insn.  */

void
MY (f_model_insn_after) (SIM_CPU *current_cpu, int last_p ATTRIBUTE_UNUSED,
			 int cycles)
{
  PROFILE_DATA *p = CPU_PROFILE_DATA (current_cpu);

  PROFILE_MODEL_TOTAL_CYCLES (p) += cycles;
  CPU_CRIS_MISC_PROFILE (current_cpu)->basic_cycle_count += cycles;
  PROFILE_MODEL_CUR_INSN_CYCLES (p) = cycles;
}

/* Initialize cycle counting for an insn.
   FIRST_P is non-zero if this is the first insn in a set of parallel
   insns.  */

void
MY (f_model_init_insn_cycles) (SIM_CPU *current_cpu ATTRIBUTE_UNUSED,
			       int first_p ATTRIBUTE_UNUSED)
{
  abort ();
}

/* Record the cycles computed for an insn.
   LAST_P is non-zero if this is the last insn in a set of parallel insns,
   and we update the total cycle count.  */

void
MY (f_model_update_insn_cycles) (SIM_CPU *current_cpu ATTRIBUTE_UNUSED,
				 int last_p ATTRIBUTE_UNUSED)
{
  abort ();
}

#if 0
void
MY (f_model_record_cycles) (SIM_CPU *current_cpu, unsigned long cycles)
{
  abort ();
}

void
MY (f_model_mark_get_h_gr) (SIM_CPU *current_cpu, ARGBUF *abuf)
{
  abort ();
}

void
MY (f_model_mark_set_h_gr) (SIM_CPU *current_cpu, ARGBUF *abuf)
{
  abort ();
}
#endif

/* Create the context for a thread.  */

void *
MY (make_thread_cpu_data) (SIM_CPU *current_cpu, void *context)
{
  void *info = xmalloc (current_cpu->thread_cpu_data_size);

  if (context != NULL)
    memcpy (info,
	    context,
	    current_cpu->thread_cpu_data_size);
  else
    memset (info, 0, current_cpu->thread_cpu_data_size),abort();
  return info;
}

/* Hook function for per-cpu simulator initialization.  */

void
MY (f_specific_init) (SIM_CPU *current_cpu)
{
  current_cpu->make_thread_cpu_data = MY (make_thread_cpu_data);
  current_cpu->thread_cpu_data_size = sizeof (current_cpu->cpu_data);
}

/* Model function for arbitrary single stall cycles.  */

int
MY (XCONCAT3 (f_model_crisv,BASENUM,
	      _u_stall)) (SIM_CPU *current_cpu ATTRIBUTE_UNUSED,
			  const IDESC *idesc,
			  int unit_num,
			  int referenced ATTRIBUTE_UNUSED)
{
  return idesc->timing->units[unit_num].done;
}

#ifndef SPECIFIC_U_SKIP4_FN

/* Model function for u-skip4 unit.  */

int
MY (XCONCAT3 (f_model_crisv,BASENUM,
	      _u_skip4)) (SIM_CPU *current_cpu,
			  const IDESC *idesc,
			  int unit_num,
			  int referenced ATTRIBUTE_UNUSED)
{
  /* Handle PC not being updated with pbb.  FIXME: What if not pbb?  */
  CPU (h_pc) += 4;
  return idesc->timing->units[unit_num].done;
}

#endif

#ifndef SPECIFIC_U_EXEC_FN

/* Model function for u-exec unit.  */

int
MY (XCONCAT3 (f_model_crisv,BASENUM,
	      _u_exec)) (SIM_CPU *current_cpu,
			 const IDESC *idesc,
			 int unit_num, int referenced ATTRIBUTE_UNUSED)
{
  /* Handle PC not being updated with pbb.  FIXME: What if not pbb?  */
  CPU (h_pc) += 2;
  return idesc->timing->units[unit_num].done;
}
#endif

#ifndef SPECIFIC_U_MEM_FN

/* Model function for u-mem unit.  */

int
MY (XCONCAT3 (f_model_crisv,BASENUM,
	      _u_mem)) (SIM_CPU *current_cpu ATTRIBUTE_UNUSED,
			const IDESC *idesc,
			int unit_num,
			int referenced ATTRIBUTE_UNUSED)
{
  return idesc->timing->units[unit_num].done;
}
#endif

#ifndef SPECIFIC_U_CONST16_FN

/* Model function for u-const16 unit.  */

int
MY (XCONCAT3 (f_model_crisv,BASENUM,
	      _u_const16)) (SIM_CPU *current_cpu,
			    const IDESC *idesc,
			    int unit_num,
			    int referenced ATTRIBUTE_UNUSED)
{
  CPU (h_pc) += 2;
  return idesc->timing->units[unit_num].done;
}
#endif /* SPECIFIC_U_CONST16_FN */

#ifndef SPECIFIC_U_CONST32_FN

/* This will be incorrect for early models, where a dword always take
   two cycles.  */
#define CRIS_MODEL_MASK_PC_STALL 2

/* Model function for u-const32 unit.  */

int
MY (XCONCAT3 (f_model_crisv,BASENUM,
	      _u_const32)) (SIM_CPU *current_cpu,
			    const IDESC *idesc,
			    int unit_num,
			    int referenced ATTRIBUTE_UNUSED)
{
  int unaligned_extra
    = (((CPU (h_pc) + 2) & CRIS_MODEL_MASK_PC_STALL)
       == CRIS_MODEL_MASK_PC_STALL);

  /* Handle PC not being updated with pbb.  FIXME: What if not pbb?  */
  CPU_CRIS_MISC_PROFILE (current_cpu)->unaligned_mem_dword_count
    += unaligned_extra;

  CPU (h_pc) += 4;
  return idesc->timing->units[unit_num].done;
}
#endif /* SPECIFIC_U_CONST32_FN */

#ifndef SPECIFIC_U_MOVEM_FN

/* Model function for u-movem unit.  */

int
MY (XCONCAT3 (f_model_crisv,BASENUM,
	      _u_movem)) (SIM_CPU *current_cpu ATTRIBUTE_UNUSED,
			  const IDESC *idesc ATTRIBUTE_UNUSED,
			  int unit_num ATTRIBUTE_UNUSED,
			  int referenced ATTRIBUTE_UNUSED,
			  INT limreg)
{
  /* FIXME: Add cycles for misalignment.  */

  if (limreg == -1)
    abort ();

  /* We don't record movem move cycles in movemsrc_stall_count since
     those cycles have historically been handled as ordinary cycles.  */
  return limreg + 1;
}
#endif /* SPECIFIC_U_MOVEM_FN */

#endif /* WITH_PROFILE_MODEL_P */
